agent-hub-sdd 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -3
  3. package/components.json +25 -0
  4. package/electron/main.mjs +132 -0
  5. package/electron/preload.cjs +6 -0
  6. package/eslint.config.mjs +18 -0
  7. package/next.config.ts +12 -0
  8. package/package.json +74 -6
  9. package/public/file.svg +1 -0
  10. package/public/globe.svg +1 -0
  11. package/public/next.svg +1 -0
  12. package/public/vercel.svg +1 -0
  13. package/public/window.svg +1 -0
  14. package/scripts/cli.cjs +28 -0
  15. package/scripts/cli.mjs +3 -0
  16. package/scripts/dev.bat +5 -0
  17. package/scripts/dev.ps1 +4 -0
  18. package/scripts/pack-hub.mjs +20 -0
  19. package/src/app/api/agents/[id]/route.ts +60 -0
  20. package/src/app/api/agents/route.ts +51 -0
  21. package/src/app/api/api.test.ts +393 -0
  22. package/src/app/api/harness/route.ts +8 -0
  23. package/src/app/api/sessions/[id]/handoff/route.ts +60 -0
  24. package/src/app/api/sessions/[id]/messages/route.ts +12 -0
  25. package/src/app/api/sessions/[id]/route.ts +13 -0
  26. package/src/app/api/sessions/[id]/subagent/route.ts +51 -0
  27. package/src/app/api/sessions/[id]/turn/route.ts +55 -0
  28. package/src/app/api/sessions/route.ts +36 -0
  29. package/src/app/api/usage/route.ts +8 -0
  30. package/src/app/api/workspace/route.ts +25 -0
  31. package/src/app/favicon.ico +0 -0
  32. package/src/app/gastos/page.tsx +11 -0
  33. package/src/app/globals.css +140 -0
  34. package/src/app/layout.tsx +36 -0
  35. package/src/app/page.tsx +5 -0
  36. package/src/components/agent-dialog.tsx +159 -0
  37. package/src/components/chat-pane.tsx +230 -0
  38. package/src/components/composer.tsx +55 -0
  39. package/src/components/desktop-chrome.tsx +11 -0
  40. package/src/components/handoff-dialog.tsx +126 -0
  41. package/src/components/hub-shell.tsx +464 -0
  42. package/src/components/markdown-lite.tsx +114 -0
  43. package/src/components/sidebar.tsx +192 -0
  44. package/src/components/spend-view.tsx +171 -0
  45. package/src/components/subagent-dialog.tsx +128 -0
  46. package/src/components/ui/accordion.tsx +80 -0
  47. package/src/components/ui/badge.tsx +48 -0
  48. package/src/components/ui/button.tsx +66 -0
  49. package/src/components/ui/card.tsx +102 -0
  50. package/src/components/ui/dialog.tsx +168 -0
  51. package/src/components/ui/dropdown-menu.tsx +268 -0
  52. package/src/components/ui/input.tsx +18 -0
  53. package/src/components/ui/scroll-area.tsx +54 -0
  54. package/src/components/ui/separator.tsx +27 -0
  55. package/src/components/ui/sheet.tsx +147 -0
  56. package/src/components/ui/tabs.tsx +89 -0
  57. package/src/components/ui/textarea.tsx +17 -0
  58. package/src/components/ui/tooltip.tsx +56 -0
  59. package/src/components/workspace-field.tsx +55 -0
  60. package/src/lib/desktop.test.ts +25 -0
  61. package/src/lib/desktop.ts +12 -0
  62. package/src/lib/group-sessions.test.ts +40 -0
  63. package/src/lib/group-sessions.ts +44 -0
  64. package/src/lib/harness/events.ts +14 -0
  65. package/src/lib/harness/parse-claude.test.ts +149 -0
  66. package/src/lib/harness/parse-claude.ts +150 -0
  67. package/src/lib/harness/parse-cursor.test.ts +118 -0
  68. package/src/lib/harness/parse-cursor.ts +90 -0
  69. package/src/lib/harness/resolve.test.ts +111 -0
  70. package/src/lib/harness/resolve.ts +63 -0
  71. package/src/lib/harness/run.test.ts +392 -0
  72. package/src/lib/harness/run.ts +228 -0
  73. package/src/lib/id.ts +5 -0
  74. package/src/lib/labels.ts +20 -0
  75. package/src/lib/seed.ts +66 -0
  76. package/src/lib/session-turn.ts +88 -0
  77. package/src/lib/spend.test.ts +59 -0
  78. package/src/lib/spend.ts +41 -0
  79. package/src/lib/store.test.ts +179 -0
  80. package/src/lib/store.ts +120 -0
  81. package/src/lib/types.ts +74 -0
  82. package/src/lib/utils.ts +1 -0
  83. package/src/types/desktop.d.ts +10 -0
  84. package/tsconfig.json +34 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agent Hub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,74 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # Agent Hub
2
+
3
+ App **Windows** para orquestrar personas do SDLC no seu próprio repositório. Não é um site e não é um runtime novo: a janela do Agent Hub dispara a CLI do Cursor (`agent`) ou do Claude Code (`claude`) com o login da sua assinatura. Skills, MCP e plugins continuam na CLI. O Hub não define `CURSOR_API_KEY`, `ANTHROPIC_API_KEY` nem passa `--api-key`.
4
+
5
+ ## Instalar
6
+
7
+ Node 20+ no PATH, depois:
8
+
9
+ Na pasta do repositório (depois de `npm install`):
10
+
11
+ ```powershell
12
+ npm run app
13
+ ```
14
+
15
+ Pelo npm, quando a versão 0.1.1 estiver publicada:
16
+
17
+ ```powershell
18
+ npx agent-hub-sdd
19
+ ```
20
+
21
+ Isso abre a janela do app. Na primeira vez o npm baixa o Electron e o Next.
22
+
23
+ ## App no Windows (a partir do código)
24
+
25
+ 1. Instale o [Node.js 20+](https://nodejs.org/) e deixe `node` e `npm` no `PATH`.
26
+ 2. Instale a CLI do Cursor e/ou a do Claude Code. Faça login da assinatura, sem chave de API:
27
+
28
+ ```bat
29
+ agent login
30
+ claude auth login
31
+ ```
32
+
33
+ 3. Na pasta do Agent Hub:
34
+
35
+ ```powershell
36
+ npm install
37
+ scripts\dev.ps1
38
+ ```
39
+
40
+ Isso abre a **janela do aplicativo** (Electron), não o Chrome. O botão **Pasta** usa o seletor nativo de pastas do Windows.
41
+
42
+ ### Instalador `.exe`
43
+
44
+ Com o mesmo Node no PATH:
45
+
46
+ ```powershell
47
+ npm run dist:win
48
+ ```
49
+
50
+ O setup fica em `dist-app\Agent Hub Setup.exe`. Cria atalho no menu Iniciar. O Node 20+ continua necessário no PATH para o app empacotado subir o hub local.
51
+
52
+ ## Workspace
53
+
54
+ O workspace é a pasta do **seu** projeto, por exemplo `C:\src\meu-app`. No app, clique em **Pasta**. Enquanto o campo estiver vazio, nada é gravado no disco do projeto.
55
+
56
+ ## Skills, MCP e plugins
57
+
58
+ O Hub não carrega skills, MCP, plugins, hooks nem regras. Quem carrega é a CLI filha (`%USERPROFILE%\.cursor` e `%USERPROFILE%\.claude`). O spawn não usa `--bare` nem `--disable-slash-commands`. Na CLI do Cursor o Hub inclui `--approve-mcps`.
59
+
60
+ ## Simulador
61
+
62
+ Se a CLI pedida não estiver no `PATH`, o Hub responde no simulador e não edita o projeto.
63
+
64
+ ## Dados
65
+
66
+ `AGENT_HUB_HOME` guarda o estado. Padrão no Windows: `%USERPROFILE%\.agent-hub`.
67
+
68
+ ## Gastos
69
+
70
+ A página **Gastos** soma o que o harness reporta. Claude Code pode enviar USD. A CLI do Cursor com assinatura muitas vezes não envia preço — o Hub mostra duração e execuções, sem inventar tarifa.
71
+
72
+ ## Desenvolvimento
73
+
74
+ `npm run app` sobe o servidor local se a porta `43147` estiver livre e abre a janela. `npm run dev` deixa só o servidor (útil para iterar na UI). `npm test` roda o vitest.
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/components/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,132 @@
1
+ import { spawn } from "node:child_process";
2
+ import http from "node:http";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { app, BrowserWindow, dialog, ipcMain, shell } from "electron";
6
+
7
+ const PORT = Number(process.env.AGENT_HUB_PORT || 43147);
8
+ const HUB = `http://127.0.0.1:${PORT}`;
9
+ const here = path.dirname(fileURLToPath(import.meta.url));
10
+ const repoRoot = path.join(here, "..");
11
+
12
+ let win;
13
+ let child;
14
+ let startedServer = false;
15
+
16
+ function hubDir() {
17
+ return app.isPackaged ? path.join(process.resourcesPath, "hub") : repoRoot;
18
+ }
19
+
20
+ function isUp() {
21
+ return new Promise((resolve) => {
22
+ const req = http.get(HUB, (res) => {
23
+ res.resume();
24
+ resolve(true);
25
+ });
26
+ req.on("error", () => resolve(false));
27
+ req.setTimeout(800, () => {
28
+ req.destroy();
29
+ resolve(false);
30
+ });
31
+ });
32
+ }
33
+
34
+ async function waitForHub() {
35
+ for (let i = 0; i < 120; i++) {
36
+ if (await isUp()) return;
37
+ await new Promise((r) => setTimeout(r, 250));
38
+ }
39
+ throw new Error(`O servidor do Agent Hub não subiu em ${HUB}`);
40
+ }
41
+
42
+ function startServer() {
43
+ const cwd = hubDir();
44
+ const env = { ...process.env, HOSTNAME: "127.0.0.1", PORT: String(PORT) };
45
+ if (app.isPackaged) {
46
+ const serverJs = path.join(cwd, "server.js");
47
+ const node = process.platform === "win32" ? "node.exe" : "node";
48
+ return spawn(node, [serverJs], {
49
+ cwd,
50
+ env,
51
+ stdio: "ignore",
52
+ windowsHide: true,
53
+ shell: false,
54
+ });
55
+ }
56
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
57
+ return spawn(npm, ["run", "dev"], {
58
+ cwd,
59
+ env,
60
+ stdio: "inherit",
61
+ shell: process.platform === "win32",
62
+ windowsHide: false,
63
+ });
64
+ }
65
+
66
+ function createWindow() {
67
+ const windows = process.platform === "win32";
68
+ win = new BrowserWindow({
69
+ width: 1280,
70
+ height: 820,
71
+ minWidth: 880,
72
+ minHeight: 560,
73
+ backgroundColor: "#000000",
74
+ show: false,
75
+ autoHideMenuBar: true,
76
+ title: "Agent Hub",
77
+ ...(windows
78
+ ? {
79
+ titleBarStyle: "hidden",
80
+ titleBarOverlay: { color: "#000000", symbolColor: "#f4f4f5", height: 36 },
81
+ }
82
+ : {}),
83
+ webPreferences: {
84
+ preload: path.join(here, "preload.cjs"),
85
+ contextIsolation: true,
86
+ nodeIntegration: false,
87
+ sandbox: true,
88
+ },
89
+ });
90
+ win.once("ready-to-show", () => win.show());
91
+ win.loadURL(HUB);
92
+ win.webContents.setWindowOpenHandler(({ url }) => {
93
+ void shell.openExternal(url);
94
+ return { action: "deny" };
95
+ });
96
+ }
97
+
98
+ ipcMain.handle("pick-folder", async () => {
99
+ const parent = BrowserWindow.getFocusedWindow() ?? win;
100
+ const result = await dialog.showOpenDialog(parent ?? undefined, {
101
+ title: "Escolher workspace",
102
+ properties: ["openDirectory"],
103
+ });
104
+ if (result.canceled || !result.filePaths[0]) return null;
105
+ return result.filePaths[0];
106
+ });
107
+
108
+ app.whenReady().then(async () => {
109
+ try {
110
+ if (!(await isUp())) {
111
+ child = startServer();
112
+ startedServer = true;
113
+ child.on("error", (error) => {
114
+ void dialog.showErrorBox("Agent Hub", error.message);
115
+ });
116
+ }
117
+ await waitForHub();
118
+ createWindow();
119
+ } catch (error) {
120
+ const message = error instanceof Error ? error.message : String(error);
121
+ await dialog.showErrorBox(
122
+ "Agent Hub",
123
+ `${message}\n\nNo Windows empacotado, o Node.js 20+ precisa estar no PATH para subir o hub.`,
124
+ );
125
+ app.quit();
126
+ }
127
+ });
128
+
129
+ app.on("window-all-closed", () => {
130
+ if (startedServer && child && !child.killed) child.kill();
131
+ app.quit();
132
+ });
@@ -0,0 +1,6 @@
1
+ const { contextBridge, ipcRenderer } = require("electron");
2
+
3
+ contextBridge.exposeInMainWorld("agentHub", {
4
+ platform: process.platform,
5
+ pickFolder: () => ipcRenderer.invoke("pick-folder"),
6
+ });
@@ -0,0 +1,18 @@
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
package/next.config.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ /* config options here */
5
+ output: "standalone",
6
+ // Cache Components rejects `runtime` / `dynamic` on route handlers. The hub
7
+ // APIs stay Node and force-dynamic so the JSON store and SSE turns are not prerendered.
8
+ cacheComponents: false,
9
+ partialPrefetching: false,
10
+ };
11
+
12
+ export default nextConfig;
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
- {
2
- "name": "agent-hub-sdd",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "agent-hub-sdd",
3
+ "version": "0.1.2",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "author": "Agent Hub",
7
+ "description": "App Windows para agentes de SDLC sobre Cursor CLI e Claude Code",
8
+ "keywords": [
9
+ "agent",
10
+ "cursor",
11
+ "claude-code",
12
+ "sdd",
13
+ "electron",
14
+ "sdlc"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/GustavoCostaAM/agent-hub.git"
19
+ },
20
+ "bin": {
21
+ "agent-hub": "scripts/cli.cjs",
22
+ "agent-hub-sdd": "scripts/cli.cjs"
23
+ },
24
+ "files": [
25
+ "electron",
26
+ "src",
27
+ "public",
28
+ "scripts",
29
+ "next.config.ts",
30
+ "tsconfig.json",
31
+ "components.json",
32
+ "eslint.config.mjs"
33
+ ],
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "main": "electron/main.mjs",
38
+ "scripts": {
39
+ "dev": "next dev --webpack --hostname 127.0.0.1 --port 43147",
40
+ "app": "electron .",
41
+ "start": "next start --hostname 127.0.0.1 --port 43147",
42
+ "build": "next build",
43
+ "pack:hub": "node scripts/pack-hub.mjs",
44
+ "dist:win": "npm run build && npm run pack:hub && electron-builder --win --x64",
45
+ "lint": "eslint",
46
+ "test": "vitest run",
47
+ "prepublishOnly": "node -e \"process.exit(0)\""
48
+ },
49
+ "dependencies": {
50
+ "@tailwindcss/turbopack": "^4",
51
+ "class-variance-authority": "^0.7.1",
52
+ "cn": "^0.5.0",
53
+ "electron": "^45.0.0",
54
+ "lucide-react": "^1.61.0",
55
+ "next": "16.4.3",
56
+ "radix-ui": "^1.8.1",
57
+ "react": "19.3.0",
58
+ "react-dom": "19.3.0",
59
+ "shadcn": "^4.22.1",
60
+ "tailwindcss": "^4",
61
+ "tw-animate-css": "^1.4.0",
62
+ "typescript": "^5"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^22.20.5",
66
+ "@types/react": "^19",
67
+ "@types/react-dom": "^19",
68
+ "electron-builder": "^26.18.0",
69
+ "eslint": "^9",
70
+ "eslint-config-next": "16.4.3",
71
+ "jsdom": "^30.1.3",
72
+ "vitest": "^5.0.5"
73
+ }
74
+ }
@@ -0,0 +1 @@
1
+ <svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
@@ -0,0 +1 @@
1
+ <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ const { spawn } = require("node:child_process");
3
+ const path = require("node:path");
4
+
5
+ const root = path.join(__dirname, "..");
6
+ let electron;
7
+ try {
8
+ electron = require("electron");
9
+ } catch {
10
+ console.error("Falta o Electron. Na pasta do projeto rode: npm install");
11
+ process.exit(1);
12
+ }
13
+
14
+ const child = spawn(electron, [root], {
15
+ cwd: root,
16
+ stdio: "inherit",
17
+ env: process.env,
18
+ windowsHide: false,
19
+ shell: process.platform === "win32",
20
+ });
21
+ child.on("error", (error) => {
22
+ console.error(error.message);
23
+ process.exit(1);
24
+ });
25
+ child.on("exit", (code, signal) => {
26
+ if (signal) process.kill(process.pid, signal);
27
+ process.exit(code ?? 0);
28
+ });
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ createRequire(import.meta.url)("./cli.cjs");
@@ -0,0 +1,5 @@
1
+ @echo off
2
+ cd /d "%~dp0.."
3
+ if not exist node_modules call npm install
4
+ if errorlevel 1 exit /b 1
5
+ npm run app
@@ -0,0 +1,4 @@
1
+ $ErrorActionPreference = "Stop"
2
+ Set-Location $PSScriptRoot\..
3
+ if (-not (Test-Path node_modules)) { npm install }
4
+ npm run app
@@ -0,0 +1,20 @@
1
+ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const root = path.join(path.dirname(fileURLToPath(import.meta.url)), "..");
6
+ const standalone = path.join(root, ".next", "standalone");
7
+ const out = path.join(root, "dist-hub");
8
+
9
+ if (!existsSync(path.join(standalone, "server.js"))) {
10
+ throw new Error("Rode npm run build antes de empacotar. Falta .next/standalone/server.js");
11
+ }
12
+
13
+ rmSync(out, { recursive: true, force: true });
14
+ mkdirSync(out, { recursive: true });
15
+ cpSync(standalone, out, { recursive: true });
16
+ cpSync(path.join(root, ".next", "static"), path.join(out, ".next", "static"), { recursive: true });
17
+ if (existsSync(path.join(root, "public"))) {
18
+ cpSync(path.join(root, "public"), path.join(out, "public"), { recursive: true });
19
+ }
20
+ console.log("pack-hub: dist-hub pronto");
@@ -0,0 +1,60 @@
1
+ import { deleteAgent, getAgent, upsertAgent } from "../../../../lib/store";
2
+ import type { Agent, HarnessId, SdlcStage } from "../../../../lib/types";
3
+
4
+ export const runtime = "nodejs";
5
+ export const dynamic = "force-dynamic";
6
+
7
+ type RouteContext = { params: Promise<{ id: string }> };
8
+
9
+ const STAGES = new Set<SdlcStage>(["spec", "architect", "implement", "review", "test", "release", "custom"]);
10
+ const HARNESSES = new Set<HarnessId>(["cursor", "claude", "mock"]);
11
+
12
+ export async function GET(_request: Request, context: RouteContext) {
13
+ const { id } = await context.params;
14
+ const agent = getAgent(id);
15
+ if (!agent) return Response.json({ error: "Agente não encontrado" }, { status: 404 });
16
+ return Response.json(agent);
17
+ }
18
+
19
+ export async function PUT(request: Request, context: RouteContext) {
20
+ const { id } = await context.params;
21
+ const current = getAgent(id);
22
+ if (!current) return Response.json({ error: "Agente não encontrado" }, { status: 404 });
23
+ const body = await readJson(request);
24
+ if (!body) return Response.json({ error: "JSON inválido" }, { status: 400 });
25
+ const next: Agent = {
26
+ ...current,
27
+ name: typeof body.name === "string" && body.name.trim() ? body.name.trim() : current.name,
28
+ stage: isStage(body.stage) ? body.stage : current.stage,
29
+ harness: isHarness(body.harness) ? body.harness : current.harness,
30
+ model: body.model === undefined ? current.model : typeof body.model === "string" && body.model ? body.model : undefined,
31
+ basePrompt: typeof body.basePrompt === "string" ? body.basePrompt : current.basePrompt,
32
+ specs: typeof body.specs === "string" ? body.specs : current.specs,
33
+ color: typeof body.color === "string" && body.color ? body.color : current.color,
34
+ updatedAt: new Date().toISOString(),
35
+ };
36
+ return Response.json(upsertAgent(next));
37
+ }
38
+
39
+ export async function DELETE(_request: Request, context: RouteContext) {
40
+ const { id } = await context.params;
41
+ if (!getAgent(id)) return Response.json({ error: "Agente não encontrado" }, { status: 404 });
42
+ deleteAgent(id);
43
+ return new Response(null, { status: 204 });
44
+ }
45
+
46
+ function isStage(value: unknown): value is SdlcStage {
47
+ return typeof value === "string" && STAGES.has(value as SdlcStage);
48
+ }
49
+
50
+ function isHarness(value: unknown): value is HarnessId {
51
+ return typeof value === "string" && HARNESSES.has(value as HarnessId);
52
+ }
53
+
54
+ async function readJson(request: Request): Promise<Record<string, unknown> | undefined> {
55
+ try {
56
+ return (await request.json()) as Record<string, unknown>;
57
+ } catch {
58
+ return undefined;
59
+ }
60
+ }
@@ -0,0 +1,51 @@
1
+ import { newId } from "../../../lib/id";
2
+ import { listAgents, seedIfEmpty, upsertAgent } from "../../../lib/store";
3
+ import type { Agent, HarnessId, SdlcStage } from "../../../lib/types";
4
+
5
+ export const runtime = "nodejs";
6
+ export const dynamic = "force-dynamic";
7
+
8
+ const STAGES = new Set<SdlcStage>(["spec", "architect", "implement", "review", "test", "release", "custom"]);
9
+ const HARNESSES = new Set<HarnessId>(["cursor", "claude", "mock"]);
10
+
11
+ export async function GET() {
12
+ seedIfEmpty();
13
+ return Response.json(listAgents());
14
+ }
15
+
16
+ export async function POST(request: Request) {
17
+ const body = await readJson(request);
18
+ if (!body || typeof body.name !== "string" || !body.name.trim() || !isStage(body.stage) || !isHarness(body.harness)) {
19
+ return Response.json({ error: "Campos obrigatórios: name, stage, harness" }, { status: 400 });
20
+ }
21
+ const now = new Date().toISOString();
22
+ const agent: Agent = {
23
+ id: newId(),
24
+ name: body.name.trim(),
25
+ stage: body.stage,
26
+ harness: body.harness,
27
+ model: typeof body.model === "string" && body.model ? body.model : undefined,
28
+ basePrompt: typeof body.basePrompt === "string" ? body.basePrompt : "",
29
+ specs: typeof body.specs === "string" ? body.specs : "",
30
+ color: typeof body.color === "string" && body.color ? body.color : "#a1a1aa",
31
+ createdAt: now,
32
+ updatedAt: now,
33
+ };
34
+ return Response.json(upsertAgent(agent), { status: 201 });
35
+ }
36
+
37
+ function isStage(value: unknown): value is SdlcStage {
38
+ return typeof value === "string" && STAGES.has(value as SdlcStage);
39
+ }
40
+
41
+ function isHarness(value: unknown): value is HarnessId {
42
+ return typeof value === "string" && HARNESSES.has(value as HarnessId);
43
+ }
44
+
45
+ async function readJson(request: Request): Promise<Record<string, unknown> | undefined> {
46
+ try {
47
+ return (await request.json()) as Record<string, unknown>;
48
+ } catch {
49
+ return undefined;
50
+ }
51
+ }