Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
9dd6c6f
feat(store): add Studios section to Store app
jaylfc Jun 16, 2026
5e121df
Merge branch 'worktree-agent-a05afcab04ce5f074' into feat/studios
jaylfc Jun 16, 2026
7e03e51
feat(studios): add Coding Studio optional desktop app
jaylfc Jun 16, 2026
8b49198
Merge branch 'worktree-agent-af6f6cdadb31df3de' into feat/studios
jaylfc Jun 16, 2026
cb28d78
feat(studios): add Music Studio app with DAW/compose/sounds views
jaylfc Jun 16, 2026
e9031f2
feat(studios): add Design Studio app with static-data first-pass UI
jaylfc Jun 16, 2026
70ec49f
feat(office-suite): add OfficeSuiteApp with Write, Calc, Slides views
jaylfc Jun 16, 2026
7cc51ea
feat(app-studio): add App Studio desktop app shell with Build/Templat…
jaylfc Jun 16, 2026
8cd3554
Merge branch 'worktree-agent-a99134e6470c1bbe5' into feat/studios
jaylfc Jun 16, 2026
18a15f2
Merge branch 'worktree-agent-a122d780bdd5392d5' into feat/studios
jaylfc Jun 16, 2026
e820823
Merge branch 'design-studio' into feat/studios
jaylfc Jun 16, 2026
3b63f84
feat(studios): register the five studios + wire Store install/launch
jaylfc Jun 16, 2026
1804c67
docs(status): LATEST-16 -- Creative Studios built on feat/studios + i…
jaylfc Jun 16, 2026
b335b42
Merge pull request #966 from jaylfc/feat/studios
jaylfc Jun 16, 2026
ea95493
docs(status): LATEST-17 -- feat/studios merged to dev (#966) + usersp…
jaylfc Jun 16, 2026
65d4fca
docs(status): PAUSE handoff for Mac mini reset -- resume checklist + …
jaylfc Jun 16, 2026
2444cf0
docs(status): RESUMED -- crons re-armed + task #89 userspace-app-pack…
jaylfc Jun 16, 2026
9d8fda6
docs(status): #89 P1 PR #967 open (per-app versioning + Updates UI)
jaylfc Jun 16, 2026
7fc1453
feat(apps): per-app versioning + Updates UI surface (#89 P1) (#967)
jaylfc Jun 16, 2026
02d468a
docs(status): #89 P1 merged to dev (#967); web search fixed via Pi Se…
jaylfc Jun 16, 2026
ac9785a
docs(status): app-system build plan locked (taOS repo model, Ed25519 …
jaylfc Jun 16, 2026
9f705df
fix(store): guard optional-catalog payload is an array before setStat…
jaylfc Jun 16, 2026
659e3bd
fix(studios): route StudioHero install/open through studioAppId like …
jaylfc Jun 16, 2026
035b017
feat(userspace): web app-runtime foundation (#89 P3a) (#970)
jaylfc Jun 17, 2026
2f765b7
docs(status): P3a runtime foundation merged (#970, all nits folded); …
jaylfc Jun 17, 2026
0d76944
fix(userspace): bound set_permissions to manifest-requested caps + 40…
jaylfc Jun 17, 2026
46a1c6d
feat(userspace): trust-aware runtime for first-party packages (#89 P3…
jaylfc Jun 17, 2026
02941a6
docs(status): app-system milestone -- userspace runtime + trust model…
jaylfc Jun 17, 2026
9e7a028
feat(userspace): first-party reference .taosapp + boot-seeding (#89 P…
jaylfc Jun 17, 2026
a2f44b2
docs(status): P4a merged (#973); app-system checkpoint -- runtime+tru…
jaylfc Jun 17, 2026
8349f40
fix(userspace): drop redundant PackageError from seed except clause (…
jaylfc Jun 17, 2026
2b429ed
docs(status): refresh the stale top banner to the current app-system …
jaylfc Jun 17, 2026
d6ec6d3
ci(dependabot): track Python deps via the uv ecosystem (pip -> uv)
jaylfc Jun 17, 2026
b96efcb
feat(userspace): surface and launch installed userspace apps in the L…
jaylfc Jun 17, 2026
0f0db09
fix(chat): anchor channel settings panel to its chat container so it …
jaylfc Jun 17, 2026
f212500
docs(status): LATEST-22 -- multi-agent collab live (taOS building taO…
jaylfc Jun 17, 2026
9f27e0d
docs(readme): add Discord community badge + invite link
jaylfc Jun 17, 2026
e51b2fa
fix(chat): load channel topic in settings from description when topic…
jaylfc Jun 17, 2026
efd8994
fix(messages): show newly created channels in the list immediately (#…
jaylfc Jun 17, 2026
6243ab9
feat(projects): show external registry agents in separate members sec…
jaylfc Jun 17, 2026
fa09f7e
feat(projects): add external agents to Add-agent dialog (#980)
jaylfc Jun 17, 2026
e85f64a
fix: external members flash and hide canvas/lead toggles (#981)
jaylfc Jun 17, 2026
9bfd008
docs(status): LATEST-23 -- autonomous build team operational (6 grok …
jaylfc Jun 17, 2026
b961e29
Skip update notifications for docs-only commits (#982)
jaylfc Jun 17, 2026
4111d12
Harden docs-path classification for update suppression (#983)
jaylfc Jun 17, 2026
8086528
fix(userspace): pin install fetch to the validated IP (DNS-rebind SSR…
jaylfc Jun 17, 2026
57dfde0
Merge pull request #984 from jaylfc/fix/ssrf-dns-rebind
jaylfc Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2
updates:
# Python (controller + worker)
- package-ecosystem: "pip"
# Python (controller + worker) -- managed by uv (pyproject.toml + uv.lock)
- package-ecosystem: "uv"
directory: "/"
target-branch: "dev"
schedule:
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
</a>
</p>

<p align="center">
<a href="https://discord.gg/r3BaxfxVZ"><img alt="Join the taOS Discord" src="https://img.shields.io/badge/Discord-Join%20the%20community-5865F2?logo=discord&logoColor=white"></a>
</p>

Self-hosted AI agent platform that runs on whatever hardware you have. An old laptop, a Raspberry Pi, a gaming PC, an SBC gathering dust, or all of them at once. taOS turns your spare hardware into a distributed AI compute cluster.

A full web desktop environment with 36 bundled apps, 108 catalog apps, 47 MCP plugins, 16 agent frameworks, a curated local model catalog of 112 manifests covering LLMs, vision, embeddings, audio, and image generation (including RK3588 NPU variants via c01zaut/happyme531), plus 167k+ searchable models from HuggingFace, agent deployment, training, image/video/audio generation, and full system monitoring, all from a single web dashboard. Supports Apple Silicon (MLX), NVIDIA, AMD, Rockchip NPU, Raspberry Pi, Android phones, and more.
Expand Down Expand Up @@ -217,7 +221,7 @@ Hidden internal gateway that unifies all inference providers behind a single Ope
Features unlock automatically based on your hardware and cluster. Solo Pi sees core features. Add a GPU worker and image generation, video, and training appear. No configuration, the platform just knows what's possible.

### Creative Studios
Dedicated studio apps for every kind of project, each a focused, native taOS workspace that runs entirely on your own cluster. Two ship today, with Coding, App, Design, Music, and Office studios on the way (App Studio is taOS's own app builder, so agents and users can build and share new apps).
Dedicated studio apps for every kind of project, each a focused, native taOS workspace that runs entirely on your own cluster. Two are in beta, with Coding, App, Design, Music, and Office studios on the way (App Studio is taOS's own app builder, so agents and users can build and share new apps).

<p align="center">
<img src="docs/images/images-studio.jpg" alt="Images Studio -- generate from a prompt and edit on a local GPU" width="49%">
Expand Down
1 change: 1 addition & 0 deletions desktop/node_modules
4 changes: 2 additions & 2 deletions desktop/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions desktop/src/apps/AppStudioApp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import React from "react";
import { AppStudioApp } from "./AppStudioApp";

describe("AppStudioApp", () => {
it("renders the titlebar with App Studio", () => {
render(<AppStudioApp windowId="test" />);
expect(screen.getByText("App Studio")).toBeInTheDocument();
});

it("renders all rail items", () => {
const { container } = render(<AppStudioApp windowId="test" />);
const nav = container.querySelector("nav[aria-label='App Studio views']");
expect(nav).toBeInTheDocument();
// Rail buttons are inside the nav
const railBtns = nav!.querySelectorAll("button");
const labels = Array.from(railBtns).map((b) => b.getAttribute("aria-label"));
expect(labels).toContain("Build");
expect(labels).toContain("Templates");
expect(labels).toContain("Publish");
expect(labels).toContain("SDK");
});

it("shows Build view by default", () => {
render(<AppStudioApp windowId="test" />);
expect(screen.getByRole("heading", { name: /^build$/i })).toBeInTheDocument();
// checkerboard sandbox area has the live preview header text
expect(screen.getByText("Build log")).toBeInTheDocument();
});

it("switches to Templates view and shows template cards", () => {
const { container } = render(<AppStudioApp windowId="test" />);
const nav = container.querySelector("nav[aria-label='App Studio views']")!;
const templatesBtn = Array.from(nav.querySelectorAll("button")).find(
(b) => b.getAttribute("aria-label") === "Templates"
)!;
fireEvent.click(templatesBtn);
// hero heading
expect(screen.getByText(/build a taOS app in plain words/i)).toBeInTheDocument();
// template card labels
expect(screen.getByText("Dashboard")).toBeInTheDocument();
expect(screen.getByText("Tracker")).toBeInTheDocument();
expect(screen.getByText("Kanban")).toBeInTheDocument();
expect(screen.getByText("Blank")).toBeInTheDocument();
});

it("switches to Publish view and shows capability rows", () => {
const { container } = render(<AppStudioApp windowId="test" />);
const nav = container.querySelector("nav[aria-label='App Studio views']")!;
const publishBtn = Array.from(nav.querySelectorAll("button")).find(
(b) => b.getAttribute("aria-label") === "Publish"
)!;
fireEvent.click(publishBtn);
// app identity
expect(screen.getAllByText("Chore Quest").length).toBeGreaterThan(0);
// capability row labels
expect(screen.getByTestId("perm-row-workspace")).toBeInTheDocument();
expect(screen.getByTestId("perm-row-notifications")).toBeInTheDocument();
expect(screen.getByTestId("perm-row-household")).toBeInTheDocument();
// publish button
expect(screen.getByRole("button", { name: /publish to my store/i })).toBeInTheDocument();
});
});
90 changes: 90 additions & 0 deletions desktop/src/apps/AppStudioApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { useState } from "react";
import { Sparkles, LayoutGrid, Share2, CircleDot } from "lucide-react";
import { BuildView } from "./appstudio/BuildView";
import { TemplatesView } from "./appstudio/TemplatesView";
import { PublishView } from "./appstudio/PublishView";

/* ------------------------------------------------------------------ */
/* App Studio -- shell */
/* */
/* Build taOS apps from plain words. An agent generates them against */
/* the taOS SDK, sandboxed and safe. Publish to your Store or share */
/* with family. */
/* */
/* Shell follows the canonical studio pattern from GameStudioApp: */
/* 46px centered titlebar, 68px icon rail, per-view subfolder. */
/* ------------------------------------------------------------------ */

type AppStudioView = "build" | "templates" | "publish" | "sdk";

const RAIL: { id: AppStudioView; label: string; icon: typeof Sparkles }[] = [
{ id: "build", label: "Build", icon: Sparkles },
{ id: "templates", label: "Templates", icon: LayoutGrid },
{ id: "publish", label: "Publish", icon: Share2 },
];

const RAIL_BOTTOM: { id: AppStudioView; label: string; icon: typeof Sparkles }[] = [
{ id: "sdk", label: "SDK", icon: CircleDot },
];

export function AppStudioApp({ windowId: _windowId }: { windowId: string }) {
const [view, setView] = useState<AppStudioView>("build");

function RailButton({ id, label, icon: Icon }: { id: AppStudioView; label: string; icon: typeof Sparkles }) {
const on = view === id;
return (
<button
key={id}
type="button"
aria-label={label}
aria-current={on ? "page" : undefined}
onClick={() => setView(id)}
className={`flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
on
? "bg-gradient-to-b from-accent/25 to-transparent text-accent"
: "text-shell-text-tertiary hover:bg-white/10 hover:text-shell-text-secondary"
}`}
>
<Icon size={21} />
{label}
</button>
);
}

return (
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-shell-bg text-shell-text select-none">
{/* titlebar */}
<div className="flex h-[46px] flex-none items-center justify-center border-b border-shell-border">
<span className="text-[13px] font-semibold tracking-[-0.01em]">App Studio</span>
</div>

<div className="flex min-h-0 flex-1">
{/* left rail */}
<nav
aria-label="App Studio views"
className="flex w-[68px] flex-none flex-col items-center gap-1.5 border-r border-shell-border bg-shell-bg-deep py-3.5"
>
{RAIL.map((r) => (
<RailButton key={r.id} {...r} />
))}
<div className="flex-1" />
{RAIL_BOTTOM.map((r) => (
<RailButton key={r.id} {...r} />
))}
</nav>

{/* active surface */}
<div className="flex min-w-0 flex-1 flex-col">
{view === "build" && <BuildView />}
{view === "templates" && <TemplatesView />}
{view === "publish" && <PublishView />}
{view === "sdk" && (
<div className="flex flex-1 items-center justify-center text-[13px] text-shell-text-tertiary">
SDK docs coming soon
</div>
)}
</div>
</div>
</div>
);
}
71 changes: 71 additions & 0 deletions desktop/src/apps/CodingStudioApp.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { CodingStudioApp } from "./CodingStudioApp";

function renderApp() {
return render(<CodingStudioApp windowId="test-window" />);
}

describe("CodingStudioApp", () => {
it("renders the app titlebar", () => {
renderApp();
expect(screen.getByText("Coding Studio")).toBeDefined();
});

it("renders all rail items", () => {
renderApp();
// Rail buttons use aria-label for exact matching via the nav element
const nav = screen.getByRole("navigation", { name: "Coding Studio views" });
expect(nav).toBeDefined();
expect(screen.getByRole("button", { name: "Code" })).toBeDefined();
expect(screen.getByRole("button", { name: "Preview" })).toBeDefined();
expect(screen.getByRole("button", { name: "Templates" })).toBeDefined();
expect(screen.getByRole("button", { name: "Models" })).toBeDefined();
});

it("shows Build view by default with Build rail item active", () => {
renderApp();
// The rail Build button (inside nav) should be aria-current="page"
const nav = screen.getByRole("navigation", { name: "Coding Studio views" });
const railBuildBtn = nav.querySelector('[aria-label="Build"]') as HTMLElement;
expect(railBuildBtn).toBeTruthy();
expect(railBuildBtn.getAttribute("aria-current")).toBe("page");
});

it("switches to Templates view on rail click", () => {
renderApp();
fireEvent.click(screen.getByRole("button", { name: "Templates" }));
expect(screen.getByRole("button", { name: "Templates" }).getAttribute("aria-current")).toBe(
"page",
);
expect(screen.getByText("Describe what you want to build.")).toBeDefined();
});

it("Templates view shows all 8 template cards", () => {
renderApp();
fireEvent.click(screen.getByRole("button", { name: "Templates" }));
const expectedNames = [
"Web App",
"REST API",
"CLI Tool",
"Discord Bot",
"Static Site",
"Data Pipeline",
"Python Script",
"Browser Extension",
];
for (const name of expectedNames) {
expect(screen.getByText(name)).toBeDefined();
}
});

it("switches to Preview view on rail click and shows preview header", () => {
renderApp();
fireEvent.click(screen.getByRole("button", { name: "Preview" }));
expect(screen.getByRole("button", { name: "Preview" }).getAttribute("aria-current")).toBe(
"page",
);
// Preview header h2
expect(screen.getByRole("heading", { name: "Preview" })).toBeDefined();
});
});
74 changes: 74 additions & 0 deletions desktop/src/apps/CodingStudioApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useState } from "react";
import { Sparkles, Code2, Play, LayoutGrid, Settings2 } from "lucide-react";
import { BuildView } from "./codingstudio/BuildView";
import { TemplatesView } from "./codingstudio/TemplatesView";
import { PreviewView } from "./codingstudio/PreviewView";

type CodingView = "build" | "code" | "preview" | "templates";

const RAIL: { id: CodingView; label: string; icon: typeof Sparkles }[] = [
{ id: "build", label: "Build", icon: Sparkles },
{ id: "code", label: "Code", icon: Code2 },
{ id: "preview", label: "Preview", icon: Play },
{ id: "templates", label: "Templates", icon: LayoutGrid },
];

export function CodingStudioApp({ windowId: _windowId }: { windowId: string }) {
const [view, setView] = useState<CodingView>("build");

return (
<div className="flex h-full min-h-0 flex-col overflow-hidden bg-shell-bg text-shell-text select-none">
{/* title strip */}
<div className="flex h-[46px] flex-none items-center justify-center border-b border-shell-border">
<span className="text-[13px] font-semibold tracking-[-0.01em]">Coding Studio</span>
</div>

<div className="flex min-h-0 flex-1">
{/* left rail */}
<nav
aria-label="Coding Studio views"
className="flex w-[68px] flex-none flex-col items-center gap-1.5 border-r border-shell-border bg-shell-bg-deep py-3.5"
>
{RAIL.map((r) => {
const Icon = r.icon;
const on = view === r.id;
return (
<button
key={r.id}
type="button"
aria-label={r.label}
aria-current={on ? "page" : undefined}
onClick={() => setView(r.id)}
className={`flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ${
on
? "bg-gradient-to-b from-accent/25 to-transparent text-accent"
: "text-shell-text-tertiary hover:bg-white/10 hover:text-shell-text-secondary"
}`}
>
<Icon size={21} />
{r.label}
</button>
);
})}
<div className="flex-1" />
<button
type="button"
aria-label="Models"
className="flex h-[46px] w-[46px] flex-col items-center justify-center gap-0.5 rounded-xl text-[9px] font-semibold text-shell-text-tertiary transition-colors hover:bg-white/10 hover:text-shell-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
<Settings2 size={21} />
Models
</button>
</nav>

{/* active surface */}
<div className="flex min-w-0 flex-1 flex-col">
{view === "build" && <BuildView />}
{view === "code" && <BuildView />}
{view === "preview" && <PreviewView />}
{view === "templates" && <TemplatesView />}
</div>
</div>
</div>
);
}
Loading
Loading