tmux-ide 2.6.0 → 2.7.0
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.
- package/README.md +14 -9
- package/bin/cli.js +1024 -519
- package/bin/cli.ts +63 -6
- package/bunfig.toml +4 -0
- package/package.json +18 -7
- package/packages/contracts/package.json +22 -0
- package/packages/contracts/src/__tests__/ide-config.test.ts +46 -0
- package/packages/contracts/src/__tests__/terminals.test.ts +87 -0
- package/packages/contracts/src/actions-contract.ts +310 -0
- package/packages/contracts/src/actions-errors.ts +41 -0
- package/packages/contracts/src/domain.ts +36 -0
- package/packages/contracts/src/ide-config.ts +170 -0
- package/packages/contracts/src/index.ts +24 -0
- package/packages/contracts/src/lib-internal/auth.ts +13 -0
- package/packages/contracts/src/lib-internal/hq.ts +38 -0
- package/packages/contracts/src/terminals.ts +116 -0
- package/packages/contracts/src/tmux.ts +60 -0
- package/packages/contracts/src/workspace.ts +67 -0
- package/packages/daemon/dist/agent-explain.d.ts +8 -1
- package/packages/daemon/dist/agent-explain.js +19 -3
- package/packages/daemon/dist/lib/tui-binary.d.ts +57 -0
- package/packages/daemon/dist/lib/tui-binary.js +130 -0
- package/packages/daemon/dist/widgets/explorer/breadcrumbs.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/explorer/tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/lib/help-overlay.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/agent-naming.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/config-tree.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/detect-panel.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/field-editor.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/footer.d.ts +1 -1
- package/packages/daemon/dist/widgets/setup/layout-picker.d.ts +1 -1
- package/packages/daemon/src/agent-explain.ts +298 -0
- package/packages/daemon/src/attach.ts +20 -0
- package/packages/daemon/src/bin.ts +4 -0
- package/packages/daemon/src/canonical.ts +7 -0
- package/packages/daemon/src/cli.ts +499 -0
- package/packages/daemon/src/command-center/actions/contract.ts +2 -0
- package/packages/daemon/src/command-center/actions/dispatcher.ts +137 -0
- package/packages/daemon/src/command-center/actions/errors.ts +105 -0
- package/packages/daemon/src/command-center/actions/handlers/_project-context.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/_resolve-project.ts +78 -0
- package/packages/daemon/src/command-center/actions/handlers/app-set-remote-access.ts +118 -0
- package/packages/daemon/src/command-center/actions/handlers/config-actions.ts +113 -0
- package/packages/daemon/src/command-center/actions/handlers/daemon-shutdown.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-activate.ts +30 -0
- package/packages/daemon/src/command-center/actions/handlers/project-launch.ts +70 -0
- package/packages/daemon/src/command-center/actions/handlers/project-open-terminal.ts +87 -0
- package/packages/daemon/src/command-center/actions/handlers/project-restart.ts +38 -0
- package/packages/daemon/src/command-center/actions/handlers/project-stop.ts +62 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-respawn.ts +119 -0
- package/packages/daemon/src/command-center/actions/handlers/terminal-stop.ts +35 -0
- package/packages/daemon/src/command-center/actions/registry.ts +149 -0
- package/packages/daemon/src/command-center/discovery.ts +96 -0
- package/packages/daemon/src/command-center/index.ts +31 -0
- package/packages/daemon/src/command-center/schemas.ts +85 -0
- package/packages/daemon/src/command-center/server.ts +1260 -0
- package/packages/daemon/src/command-center/ws-events.ts +316 -0
- package/packages/daemon/src/config.ts +549 -0
- package/packages/daemon/src/detect.ts +248 -0
- package/packages/daemon/src/doctor.ts +242 -0
- package/packages/daemon/src/embed.ts +5 -0
- package/packages/daemon/src/index.ts +12 -0
- package/packages/daemon/src/init.ts +211 -0
- package/packages/daemon/src/inspect.ts +178 -0
- package/packages/daemon/src/js-yaml.d.ts +10 -0
- package/packages/daemon/src/launch.ts +349 -0
- package/packages/daemon/src/lib/active-projects.ts +49 -0
- package/packages/daemon/src/lib/agent-discovery.ts +121 -0
- package/packages/daemon/src/lib/app-config.ts +427 -0
- package/packages/daemon/src/lib/app-settings.ts +53 -0
- package/packages/daemon/src/lib/auth/auth-service.ts +227 -0
- package/packages/daemon/src/lib/auth/middleware.ts +56 -0
- package/packages/daemon/src/lib/auth/types.ts +2 -0
- package/packages/daemon/src/lib/auth-token.ts +5 -0
- package/packages/daemon/src/lib/authorship.ts +280 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +122 -0
- package/packages/daemon/src/lib/cli-action-bridge.ts +216 -0
- package/packages/daemon/src/lib/daemon-embed.ts +782 -0
- package/packages/daemon/src/lib/daemon-watchdog.ts +111 -0
- package/packages/daemon/src/lib/daemon.ts +79 -0
- package/packages/daemon/src/lib/dot-path.ts +17 -0
- package/packages/daemon/src/lib/errors.ts +67 -0
- package/packages/daemon/src/lib/filesystem-browser.ts +292 -0
- package/packages/daemon/src/lib/launch-plan.ts +90 -0
- package/packages/daemon/src/lib/log.ts +134 -0
- package/packages/daemon/src/lib/output.ts +76 -0
- package/packages/daemon/src/lib/project-init-runner.ts +150 -0
- package/packages/daemon/src/lib/project-inspect.ts +80 -0
- package/packages/daemon/src/lib/project-onboard.ts +149 -0
- package/packages/daemon/src/lib/project-probe.ts +92 -0
- package/packages/daemon/src/lib/project-registry.ts +296 -0
- package/packages/daemon/src/lib/session-monitor.ts +122 -0
- package/packages/daemon/src/lib/session-options.ts +100 -0
- package/packages/daemon/src/lib/shell.ts +8 -0
- package/packages/daemon/src/lib/sizes.ts +36 -0
- package/packages/daemon/src/lib/skill-sync.ts +155 -0
- package/packages/daemon/src/lib/slugify.ts +10 -0
- package/packages/daemon/src/lib/terminals-store.ts +125 -0
- package/packages/daemon/src/lib/tui-binary.ts +165 -0
- package/packages/daemon/src/lib/update-check.ts +298 -0
- package/packages/daemon/src/lib/update.ts +158 -0
- package/packages/daemon/src/lib/workspace-registry.ts +229 -0
- package/packages/daemon/src/lib/worktree.ts +289 -0
- package/packages/daemon/src/lib/yaml-io.ts +27 -0
- package/packages/daemon/src/ls.ts +40 -0
- package/packages/daemon/src/restart.ts +24 -0
- package/packages/daemon/src/restore.ts +514 -0
- package/packages/daemon/src/schemas/domain.ts +2 -0
- package/packages/daemon/src/schemas/filesystem.ts +34 -0
- package/packages/daemon/src/schemas/ide-config.ts +2 -0
- package/packages/daemon/src/schemas/index.ts +59 -0
- package/packages/daemon/src/schemas/inspect.ts +67 -0
- package/packages/daemon/src/schemas/registry.ts +55 -0
- package/packages/daemon/src/schemas/ws-events.ts +135 -0
- package/packages/daemon/src/send.ts +171 -0
- package/packages/daemon/src/server/README.md +15 -0
- package/packages/daemon/src/server/index.ts +74 -0
- package/packages/daemon/src/server/pty-bridge.ts +532 -0
- package/packages/daemon/src/server/standalone.ts +18 -0
- package/packages/daemon/src/server/ws-route.ts +483 -0
- package/packages/daemon/src/status.ts +59 -0
- package/packages/daemon/src/stop.ts +28 -0
- package/packages/daemon/src/terminal/NodePtyAdapter.ts +271 -0
- package/packages/daemon/src/terminal/PtyAdapter.ts +140 -0
- package/packages/daemon/src/terminal/README.md +92 -0
- package/packages/daemon/src/tui/chrome/cheatsheet.ts +260 -0
- package/packages/daemon/src/tui/chrome/chip.ts +30 -0
- package/packages/daemon/src/tui/chrome/events.ts +119 -0
- package/packages/daemon/src/tui/chrome/kitty-keys.ts +55 -0
- package/packages/daemon/src/tui/chrome/menu.ts +289 -0
- package/packages/daemon/src/tui/chrome/notify.ts +382 -0
- package/packages/daemon/src/tui/chrome/panels.ts +111 -0
- package/packages/daemon/src/tui/chrome/sidebar.ts +222 -0
- package/packages/daemon/src/tui/chrome/snapshot.ts +425 -0
- package/packages/daemon/src/tui/chrome/statusline.ts +595 -0
- package/packages/daemon/src/tui/chrome/updater.ts +510 -0
- package/packages/daemon/src/tui/chrome/welcome.ts +124 -0
- package/packages/daemon/src/tui/compiled.ts +121 -0
- package/packages/daemon/src/tui/detect/classify.ts +208 -0
- package/packages/daemon/src/tui/detect/manifest-loader.ts +193 -0
- package/packages/daemon/src/tui/detect/manifest.ts +199 -0
- package/packages/daemon/src/tui/detect/manifests.ts +354 -0
- package/packages/daemon/src/tui/detect/process-tree.ts +217 -0
- package/packages/daemon/src/tui/detect/snapshot.ts +70 -0
- package/packages/daemon/src/tui/integrations/claude.ts +176 -0
- package/packages/daemon/src/tui/integrations/offer.ts +145 -0
- package/packages/daemon/src/tui/main.ts +82 -0
- package/packages/daemon/src/tui/mirror/ack-writer.ts +77 -0
- package/packages/daemon/src/tui/mirror/agent-chip.ts +97 -0
- package/packages/daemon/src/tui/mirror/agent-rows.ts +133 -0
- package/packages/daemon/src/tui/mirror/app-state.ts +179 -0
- package/packages/daemon/src/tui/mirror/app.tsx +5265 -0
- package/packages/daemon/src/tui/mirror/blit.ts +186 -0
- package/packages/daemon/src/tui/mirror/control-client.ts +214 -0
- package/packages/daemon/src/tui/mirror/control.ts +97 -0
- package/packages/daemon/src/tui/mirror/dialog-model.ts +298 -0
- package/packages/daemon/src/tui/mirror/dialog-stack.ts +354 -0
- package/packages/daemon/src/tui/mirror/diff-model.ts +112 -0
- package/packages/daemon/src/tui/mirror/editor-buffer.ts +117 -0
- package/packages/daemon/src/tui/mirror/file-tree.ts +97 -0
- package/packages/daemon/src/tui/mirror/focus-border.ts +57 -0
- package/packages/daemon/src/tui/mirror/folder-picker.ts +124 -0
- package/packages/daemon/src/tui/mirror/home-model.ts +174 -0
- package/packages/daemon/src/tui/mirror/input-coalescer.ts +105 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +187 -0
- package/packages/daemon/src/tui/mirror/palette.ts +274 -0
- package/packages/daemon/src/tui/mirror/pane-mirror.ts +561 -0
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +415 -0
- package/packages/daemon/src/tui/mirror/perf-tap.ts +160 -0
- package/packages/daemon/src/tui/mirror/resize-model.ts +85 -0
- package/packages/daemon/src/tui/mirror/scrollbar-model.ts +88 -0
- package/packages/daemon/src/tui/mirror/search-model.ts +70 -0
- package/packages/daemon/src/tui/mirror/selection.ts +262 -0
- package/packages/daemon/src/tui/mirror/session-mirror.ts +443 -0
- package/packages/daemon/src/tui/mirror/settings-model.ts +345 -0
- package/packages/daemon/src/tui/mirror/size-truth.ts +77 -0
- package/packages/daemon/src/tui/mirror/spans.ts +46 -0
- package/packages/daemon/src/tui/mirror/status-grammar.ts +32 -0
- package/packages/daemon/src/tui/team/CONTROL.md +50 -0
- package/packages/daemon/src/tui/team/entry.ts +38 -0
- package/packages/daemon/src/tui/team/fuzzy.ts +133 -0
- package/packages/daemon/src/tui/team/home.ts +170 -0
- package/packages/daemon/src/tui/team/index.tsx +1521 -0
- package/packages/daemon/src/tui/team/input.ts +34 -0
- package/packages/daemon/src/tui/team/keymap.ts +127 -0
- package/packages/daemon/src/tui/team/mouse.ts +29 -0
- package/packages/daemon/src/tui/team/nav.ts +31 -0
- package/packages/daemon/src/tui/team/preview.ts +34 -0
- package/packages/daemon/src/tui/team/projects.ts +191 -0
- package/packages/daemon/src/tui/team/report.ts +83 -0
- package/packages/daemon/src/tui/team/sessions.ts +433 -0
- package/packages/daemon/src/tui/team/tree.ts +62 -0
- package/packages/daemon/src/types.ts +13 -0
- package/packages/daemon/src/ui/index.ts +32 -0
- package/packages/daemon/src/ui/terminal/index.ts +9 -0
- package/packages/daemon/src/ui/types.ts +91 -0
- package/packages/daemon/src/ui/web/base.css +80 -0
- package/packages/daemon/src/ui/web/components/Box.tsx +59 -0
- package/packages/daemon/src/ui/web/components/Input.tsx +32 -0
- package/packages/daemon/src/ui/web/components/ScrollBox.tsx +60 -0
- package/packages/daemon/src/ui/web/components/Text.tsx +28 -0
- package/packages/daemon/src/ui/web/hooks.ts +106 -0
- package/packages/daemon/src/ui/web/index.ts +27 -0
- package/packages/daemon/src/ui/web/render.ts +77 -0
- package/packages/daemon/src/ui/web/utils/color.ts +27 -0
- package/packages/daemon/src/validate.ts +217 -0
- package/packages/daemon/src/widgets/changes/README.md +3 -0
- package/packages/daemon/src/widgets/changes/index.tsx +691 -0
- package/packages/daemon/src/widgets/config/README.md +3 -0
- package/packages/daemon/src/widgets/config/index.tsx +481 -0
- package/packages/daemon/src/widgets/explorer/README.md +3 -0
- package/packages/daemon/src/widgets/explorer/breadcrumbs.tsx +77 -0
- package/packages/daemon/src/widgets/explorer/footer.tsx +20 -0
- package/packages/daemon/src/widgets/explorer/header.tsx +23 -0
- package/packages/daemon/src/widgets/explorer/index.tsx +456 -0
- package/packages/daemon/src/widgets/explorer/tree-model.ts +103 -0
- package/packages/daemon/src/widgets/explorer/tree.tsx +165 -0
- package/packages/daemon/src/widgets/lib/config-model.ts +116 -0
- package/packages/daemon/src/widgets/lib/files.ts +88 -0
- package/packages/daemon/src/widgets/lib/git.ts +88 -0
- package/packages/daemon/src/widgets/lib/grammar.ts +126 -0
- package/packages/daemon/src/widgets/lib/help-overlay.tsx +101 -0
- package/packages/daemon/src/widgets/lib/pane-comms.ts +209 -0
- package/packages/daemon/src/widgets/lib/theme.ts +194 -0
- package/packages/daemon/src/widgets/lib/watcher.ts +132 -0
- package/packages/daemon/src/widgets/preview/README.md +3 -0
- package/packages/daemon/src/widgets/preview/index.tsx +416 -0
- package/packages/daemon/src/widgets/resolve.ts +121 -0
- package/packages/daemon/src/widgets/setup/README.md +3 -0
- package/packages/daemon/src/widgets/setup/agent-naming.tsx +112 -0
- package/packages/daemon/src/widgets/setup/config-tree.tsx +246 -0
- package/packages/daemon/src/widgets/setup/detect-panel.tsx +72 -0
- package/packages/daemon/src/widgets/setup/field-editor.tsx +265 -0
- package/packages/daemon/src/widgets/setup/footer.tsx +107 -0
- package/packages/daemon/src/widgets/setup/index.tsx +341 -0
- package/packages/daemon/src/widgets/setup/layout-picker.tsx +96 -0
- package/packages/daemon/src/widgets/setup/orchestrator-panel.tsx +200 -0
- package/packages/daemon/src/widgets/setup/review-panel.tsx +140 -0
- package/packages/daemon/src/widgets/setup/setup-model.ts +188 -0
- package/packages/daemon/src/widgets/sidebar/index.tsx +527 -0
- package/packages/tmux-bridge/package.json +22 -0
- package/packages/tmux-bridge/src/errors.ts +28 -0
- package/packages/tmux-bridge/src/index.ts +31 -0
- package/packages/tmux-bridge/src/monitor.ts +77 -0
- package/packages/tmux-bridge/src/panes.ts +136 -0
- package/packages/tmux-bridge/src/runner.test.ts +501 -0
- package/packages/tmux-bridge/src/runner.ts +91 -0
- package/packages/tmux-bridge/src/sessions.ts +126 -0
- package/packages/tmux-bridge/src/targeting.test.ts +107 -0
- package/packages/tmux-bridge/src/targeting.ts +90 -0
- package/scripts/build-tui.mjs +11 -4
- package/scripts/perf-mirror.mjs +313 -0
- package/scripts/postinstall.js +26 -2
- package/skill/SKILL.md +22 -0
- package/templates/AGENTS.md +14 -7
- package/templates/agent-team-monorepo.yml +8 -0
- package/templates/agent-team-nextjs.yml +8 -0
- package/templates/agent-team.yml +10 -0
- package/templates/convex.yml +2 -0
- package/templates/default.yml +11 -5
- package/templates/go.yml +4 -0
- package/templates/missions.yml +6 -0
- package/templates/nextjs.yml +4 -0
- package/templates/python.yml +4 -0
- package/templates/skills/backend.md +5 -12
- package/templates/skills/frontend.md +5 -12
- package/templates/skills/general-worker.md +5 -12
- package/templates/skills/researcher.md +7 -12
- package/templates/skills/reviewer.md +7 -16
- package/templates/vite.yml +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler: `project.restart`.
|
|
3
|
+
*
|
|
4
|
+
* Programmatic version of `tmux-ide restart`. Stops the session monitor +
|
|
5
|
+
* kills the existing tmux session (when present), then re-launches with
|
|
6
|
+
* `attach: false`. Always reports `restarted: true` on success — the call
|
|
7
|
+
* is meaningful whether or not a session was previously running.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { restart as restartDefault } from "../../../restart.ts";
|
|
11
|
+
import { ActionError } from "../errors.ts";
|
|
12
|
+
import { type ActionInput, type ActionResult } from "../contract.ts";
|
|
13
|
+
import { resolveProject, type ProjectResolverDeps } from "./_resolve-project.ts";
|
|
14
|
+
|
|
15
|
+
export interface ProjectRestartDeps extends ProjectResolverDeps {
|
|
16
|
+
restart?: (dir: string, options: { json?: boolean; attach?: boolean }) => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function projectRestartHandler(
|
|
20
|
+
input: ActionInput<"project.restart">,
|
|
21
|
+
deps: ProjectRestartDeps = {},
|
|
22
|
+
): Promise<ActionResult<"project.restart">> {
|
|
23
|
+
const project = resolveProject(input.name, deps);
|
|
24
|
+
const restart = deps.restart ?? restartDefault;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await restart(project.dir, { json: false, attach: false });
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new ActionError({
|
|
30
|
+
code: "launch_failed",
|
|
31
|
+
message: `Failed to restart session "${project.sessionName}": ${(err as Error).message ?? String(err)}`,
|
|
32
|
+
details: { sessionName: project.sessionName, dir: project.dir },
|
|
33
|
+
cause: err,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { sessionName: project.sessionName, restarted: true };
|
|
38
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler: `project.stop`.
|
|
3
|
+
*
|
|
4
|
+
* Wraps the CLI `tmux-ide stop` flow programmatically: stop the session
|
|
5
|
+
* monitor, then `killSession`. Idempotent —
|
|
6
|
+
* returns `{ stopped: false }` when no session was running rather than
|
|
7
|
+
* throwing, so the dashboard can call this freely.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
hasSession as hasSessionDefault,
|
|
12
|
+
killSession as killSessionDefault,
|
|
13
|
+
stopSessionMonitor as stopSessionMonitorDefault,
|
|
14
|
+
} from "@tmux-ide/tmux-bridge";
|
|
15
|
+
import { ActionError } from "../errors.ts";
|
|
16
|
+
import { type ActionInput, type ActionResult } from "../contract.ts";
|
|
17
|
+
import { resolveProject, type ProjectResolverDeps } from "./_resolve-project.ts";
|
|
18
|
+
|
|
19
|
+
export interface ProjectStopDeps extends ProjectResolverDeps {
|
|
20
|
+
hasSession?: (session: string) => boolean;
|
|
21
|
+
killSession?: (session: string) => { stopped: boolean; reason: string | null };
|
|
22
|
+
stopSessionMonitor?: (session: string) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Legacy hook retained for tests. Per-session daemon cleanup is no longer
|
|
25
|
+
* needed because the canonical daemon is app/headless owned.
|
|
26
|
+
*/
|
|
27
|
+
killOrphanDaemons?: (session: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function defaultKillOrphanDaemons(_session: string): void {
|
|
31
|
+
// No-op: per-session daemons are no longer spawned.
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function projectStopHandler(
|
|
35
|
+
input: ActionInput<"project.stop">,
|
|
36
|
+
deps: ProjectStopDeps = {},
|
|
37
|
+
): Promise<ActionResult<"project.stop">> {
|
|
38
|
+
const project = resolveProject(input.name, deps);
|
|
39
|
+
const hasSession = deps.hasSession ?? hasSessionDefault;
|
|
40
|
+
|
|
41
|
+
if (!hasSession(project.sessionName)) {
|
|
42
|
+
return { sessionName: project.sessionName, stopped: false };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const stopSessionMonitor = deps.stopSessionMonitor ?? stopSessionMonitorDefault;
|
|
46
|
+
const killSession = deps.killSession ?? killSessionDefault;
|
|
47
|
+
const killOrphanDaemons = deps.killOrphanDaemons ?? defaultKillOrphanDaemons;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
stopSessionMonitor(project.sessionName);
|
|
51
|
+
killOrphanDaemons(project.sessionName);
|
|
52
|
+
const result = killSession(project.sessionName);
|
|
53
|
+
return { sessionName: project.sessionName, stopped: result.stopped };
|
|
54
|
+
} catch (err) {
|
|
55
|
+
throw new ActionError({
|
|
56
|
+
code: "stop_failed",
|
|
57
|
+
message: `Failed to stop session "${project.sessionName}": ${(err as Error).message ?? String(err)}`,
|
|
58
|
+
details: { sessionName: project.sessionName },
|
|
59
|
+
cause: err,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler: `terminal.respawn`.
|
|
3
|
+
*
|
|
4
|
+
* Server-side equivalent of the dashboard's `bridge.restartWith(...)` call.
|
|
5
|
+
* Looks up the PTY bridge by id; if a cwd is supplied it validates the
|
|
6
|
+
* directory and passes it through; otherwise it respawns at the bridge's
|
|
7
|
+
* recorded `lastCwd`.
|
|
8
|
+
*
|
|
9
|
+
* Why server-side? The dashboard previously had to do the cwd resolution
|
|
10
|
+
* itself before it could ask the bridge to restart. Centralising it here
|
|
11
|
+
* keeps the cwd contract on the server, where it has direct access to the
|
|
12
|
+
* project registry and the bridge's filesystem.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
defaultPtyBridgeRegistry as defaultRegistry,
|
|
17
|
+
type PtyBridgeLike,
|
|
18
|
+
type PtyBridgeRegistry,
|
|
19
|
+
} from "../../../server/ws-route.ts";
|
|
20
|
+
import { assertValidCwd, TerminalCwdError } from "../../../server/pty-bridge.ts";
|
|
21
|
+
import type { Stats } from "node:fs";
|
|
22
|
+
import { ActionError, actionErrorFromCwdError } from "../errors.ts";
|
|
23
|
+
import { type ActionInput, type ActionResult } from "../contract.ts";
|
|
24
|
+
|
|
25
|
+
const DEFAULT_RESPAWN_COLS = 80;
|
|
26
|
+
const DEFAULT_RESPAWN_ROWS = 24;
|
|
27
|
+
|
|
28
|
+
export interface TerminalRespawnDeps {
|
|
29
|
+
registry?: PtyBridgeRegistry;
|
|
30
|
+
/** Override for the bridge dimensions used on respawn. Tests inject these. */
|
|
31
|
+
cols?: number;
|
|
32
|
+
rows?: number;
|
|
33
|
+
statCwd?: (cwd: string) => Stats;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function terminalRespawnHandler(
|
|
37
|
+
input: ActionInput<"terminal.respawn">,
|
|
38
|
+
deps: TerminalRespawnDeps = {},
|
|
39
|
+
): ActionResult<"terminal.respawn"> {
|
|
40
|
+
const registry = deps.registry ?? defaultRegistry;
|
|
41
|
+
const bridge = registry.peek(input.terminalId) as RespawnCapableBridge | null;
|
|
42
|
+
if (!bridge) {
|
|
43
|
+
throw new ActionError({
|
|
44
|
+
code: "terminal_not_found",
|
|
45
|
+
message: `No running terminal bridge for id "${input.terminalId}"`,
|
|
46
|
+
details: { terminalId: input.terminalId, sessionName: input.sessionName },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (!bridge.restartWith) {
|
|
50
|
+
throw new ActionError({
|
|
51
|
+
code: "internal",
|
|
52
|
+
message: "Bridge does not support restartWith",
|
|
53
|
+
details: { terminalId: input.terminalId },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const cwd = resolveRespawnCwd(input, bridge, deps.statCwd);
|
|
58
|
+
const cols = deps.cols ?? bridge.cols ?? DEFAULT_RESPAWN_COLS;
|
|
59
|
+
const rows = deps.rows ?? bridge.rows ?? DEFAULT_RESPAWN_ROWS;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
bridge.restartWith(cols, rows, { cwd });
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (err instanceof TerminalCwdError) {
|
|
65
|
+
throw actionErrorFromCwdError(err);
|
|
66
|
+
}
|
|
67
|
+
throw new ActionError({
|
|
68
|
+
code: "internal",
|
|
69
|
+
message: `Failed to respawn terminal "${input.terminalId}": ${(err as Error).message ?? String(err)}`,
|
|
70
|
+
details: { terminalId: input.terminalId },
|
|
71
|
+
cause: err,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { respawned: true, cwd };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface RespawnCapableBridge extends PtyBridgeLike {
|
|
79
|
+
cols?: number | null;
|
|
80
|
+
rows?: number | null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveRespawnCwd(
|
|
84
|
+
input: ActionInput<"terminal.respawn">,
|
|
85
|
+
bridge: RespawnCapableBridge,
|
|
86
|
+
statCwd?: (cwd: string) => Stats,
|
|
87
|
+
): string {
|
|
88
|
+
if (input.cwd) {
|
|
89
|
+
try {
|
|
90
|
+
assertValidCwd(input.cwd, statCwd);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof TerminalCwdError) {
|
|
93
|
+
throw actionErrorFromCwdError(err);
|
|
94
|
+
}
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
return input.cwd;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const last = bridge.getCwd?.() ?? null;
|
|
101
|
+
if (!last) {
|
|
102
|
+
throw new ActionError({
|
|
103
|
+
code: "internal",
|
|
104
|
+
message: "Cannot respawn terminal without an explicit cwd: bridge has no recorded cwd",
|
|
105
|
+
details: { terminalId: input.terminalId },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Validate the recorded cwd too — the directory may have been deleted
|
|
109
|
+
// since the bridge first spawned.
|
|
110
|
+
try {
|
|
111
|
+
assertValidCwd(last, statCwd);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err instanceof TerminalCwdError) {
|
|
114
|
+
throw actionErrorFromCwdError(err);
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
return last;
|
|
119
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handler: `terminal.stop`.
|
|
3
|
+
*
|
|
4
|
+
* Kill a running PTY bridge by id. Wraps {@link PtyBridgeRegistry.delete} —
|
|
5
|
+
* the registry delete is what resets the entry's clients counter, clears
|
|
6
|
+
* any idle timer, and emits SIGTERM. Returns `{ stopped: true }` only when
|
|
7
|
+
* a bridge actually existed; otherwise raises `terminal_not_found`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
defaultPtyBridgeRegistry as defaultRegistry,
|
|
12
|
+
type PtyBridgeRegistry,
|
|
13
|
+
} from "../../../server/ws-route.ts";
|
|
14
|
+
import { ActionError } from "../errors.ts";
|
|
15
|
+
import { type ActionInput, type ActionResult } from "../contract.ts";
|
|
16
|
+
|
|
17
|
+
export interface TerminalStopDeps {
|
|
18
|
+
registry?: PtyBridgeRegistry;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function terminalStopHandler(
|
|
22
|
+
input: ActionInput<"terminal.stop">,
|
|
23
|
+
deps: TerminalStopDeps = {},
|
|
24
|
+
): ActionResult<"terminal.stop"> {
|
|
25
|
+
const registry = deps.registry ?? defaultRegistry;
|
|
26
|
+
const ok = registry.delete(input.terminalId);
|
|
27
|
+
if (!ok) {
|
|
28
|
+
throw new ActionError({
|
|
29
|
+
code: "terminal_not_found",
|
|
30
|
+
message: `No terminal bridge for id "${input.terminalId}"`,
|
|
31
|
+
details: { terminalId: input.terminalId, sessionName: input.sessionName },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { stopped: true };
|
|
35
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry: maps each action name to its input/output schemas and handler.
|
|
3
|
+
*
|
|
4
|
+
* The dispatcher in `dispatcher.ts` is the only consumer — it parses the
|
|
5
|
+
* URL `:name` against `ACTION_NAMES`, then looks up the handler here. Every
|
|
6
|
+
* handler accepts a typed input and returns the typed result; the
|
|
7
|
+
* dispatcher applies output-side Zod validation as defense in depth.
|
|
8
|
+
*
|
|
9
|
+
* Adding a new action: define schemas in `contract.ts`, add a handler in
|
|
10
|
+
* `handlers/`, then wire it here.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { z } from "zod";
|
|
14
|
+
import {
|
|
15
|
+
ActionContractsZ,
|
|
16
|
+
type ActionInput,
|
|
17
|
+
type ActionName,
|
|
18
|
+
type ActionResult,
|
|
19
|
+
} from "./contract.ts";
|
|
20
|
+
import { projectOpenTerminalHandler } from "./handlers/project-open-terminal.ts";
|
|
21
|
+
import { projectActivateHandler } from "./handlers/project-activate.ts";
|
|
22
|
+
import { projectLaunchHandler } from "./handlers/project-launch.ts";
|
|
23
|
+
import { projectStopHandler } from "./handlers/project-stop.ts";
|
|
24
|
+
import { projectRestartHandler } from "./handlers/project-restart.ts";
|
|
25
|
+
import { terminalRespawnHandler } from "./handlers/terminal-respawn.ts";
|
|
26
|
+
import { terminalStopHandler } from "./handlers/terminal-stop.ts";
|
|
27
|
+
import {
|
|
28
|
+
configAddPaneHandler,
|
|
29
|
+
configAddRowHandler,
|
|
30
|
+
configDisableTeamHandler,
|
|
31
|
+
configEnableTeamHandler,
|
|
32
|
+
configRemovePaneHandler,
|
|
33
|
+
configSetHandler,
|
|
34
|
+
} from "./handlers/config-actions.ts";
|
|
35
|
+
import { appSetRemoteAccessHandler } from "./handlers/app-set-remote-access.ts";
|
|
36
|
+
import { daemonShutdownHandler } from "./handlers/daemon-shutdown.ts";
|
|
37
|
+
|
|
38
|
+
export type ActionHandler<N extends ActionName> = (
|
|
39
|
+
input: ActionInput<N>,
|
|
40
|
+
) => Promise<ActionResult<N>> | ActionResult<N>;
|
|
41
|
+
|
|
42
|
+
export interface ActionRegistryEntry<N extends ActionName> {
|
|
43
|
+
inputSchema: (typeof ActionContractsZ)[N]["input"];
|
|
44
|
+
resultSchema: (typeof ActionContractsZ)[N]["result"];
|
|
45
|
+
handler: ActionHandler<N>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type RegistryShape = {
|
|
49
|
+
[N in ActionName]: ActionRegistryEntry<N>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const actionRegistry: RegistryShape = {
|
|
53
|
+
"project.openTerminal": {
|
|
54
|
+
inputSchema: ActionContractsZ["project.openTerminal"].input,
|
|
55
|
+
resultSchema: ActionContractsZ["project.openTerminal"].result,
|
|
56
|
+
handler: projectOpenTerminalHandler,
|
|
57
|
+
},
|
|
58
|
+
"project.launch": {
|
|
59
|
+
inputSchema: ActionContractsZ["project.launch"].input,
|
|
60
|
+
resultSchema: ActionContractsZ["project.launch"].result,
|
|
61
|
+
handler: projectLaunchHandler,
|
|
62
|
+
},
|
|
63
|
+
"project.stop": {
|
|
64
|
+
inputSchema: ActionContractsZ["project.stop"].input,
|
|
65
|
+
resultSchema: ActionContractsZ["project.stop"].result,
|
|
66
|
+
handler: projectStopHandler,
|
|
67
|
+
},
|
|
68
|
+
"project.restart": {
|
|
69
|
+
inputSchema: ActionContractsZ["project.restart"].input,
|
|
70
|
+
resultSchema: ActionContractsZ["project.restart"].result,
|
|
71
|
+
handler: projectRestartHandler,
|
|
72
|
+
},
|
|
73
|
+
"project.activate": {
|
|
74
|
+
inputSchema: ActionContractsZ["project.activate"].input,
|
|
75
|
+
resultSchema: ActionContractsZ["project.activate"].result,
|
|
76
|
+
handler: projectActivateHandler,
|
|
77
|
+
},
|
|
78
|
+
"terminal.respawn": {
|
|
79
|
+
inputSchema: ActionContractsZ["terminal.respawn"].input,
|
|
80
|
+
resultSchema: ActionContractsZ["terminal.respawn"].result,
|
|
81
|
+
handler: terminalRespawnHandler,
|
|
82
|
+
},
|
|
83
|
+
"terminal.stop": {
|
|
84
|
+
inputSchema: ActionContractsZ["terminal.stop"].input,
|
|
85
|
+
resultSchema: ActionContractsZ["terminal.stop"].result,
|
|
86
|
+
handler: terminalStopHandler,
|
|
87
|
+
},
|
|
88
|
+
"config.set": {
|
|
89
|
+
inputSchema: ActionContractsZ["config.set"].input,
|
|
90
|
+
resultSchema: ActionContractsZ["config.set"].result,
|
|
91
|
+
handler: configSetHandler,
|
|
92
|
+
},
|
|
93
|
+
"config.addPane": {
|
|
94
|
+
inputSchema: ActionContractsZ["config.addPane"].input,
|
|
95
|
+
resultSchema: ActionContractsZ["config.addPane"].result,
|
|
96
|
+
handler: configAddPaneHandler,
|
|
97
|
+
},
|
|
98
|
+
"config.removePane": {
|
|
99
|
+
inputSchema: ActionContractsZ["config.removePane"].input,
|
|
100
|
+
resultSchema: ActionContractsZ["config.removePane"].result,
|
|
101
|
+
handler: configRemovePaneHandler,
|
|
102
|
+
},
|
|
103
|
+
"config.addRow": {
|
|
104
|
+
inputSchema: ActionContractsZ["config.addRow"].input,
|
|
105
|
+
resultSchema: ActionContractsZ["config.addRow"].result,
|
|
106
|
+
handler: configAddRowHandler,
|
|
107
|
+
},
|
|
108
|
+
"config.enableTeam": {
|
|
109
|
+
inputSchema: ActionContractsZ["config.enableTeam"].input,
|
|
110
|
+
resultSchema: ActionContractsZ["config.enableTeam"].result,
|
|
111
|
+
handler: configEnableTeamHandler,
|
|
112
|
+
},
|
|
113
|
+
"config.disableTeam": {
|
|
114
|
+
inputSchema: ActionContractsZ["config.disableTeam"].input,
|
|
115
|
+
resultSchema: ActionContractsZ["config.disableTeam"].result,
|
|
116
|
+
handler: configDisableTeamHandler,
|
|
117
|
+
},
|
|
118
|
+
"app.setRemoteAccess": {
|
|
119
|
+
inputSchema: ActionContractsZ["app.setRemoteAccess"].input,
|
|
120
|
+
resultSchema: ActionContractsZ["app.setRemoteAccess"].result,
|
|
121
|
+
handler: appSetRemoteAccessHandler,
|
|
122
|
+
},
|
|
123
|
+
"daemon.shutdown": {
|
|
124
|
+
inputSchema: ActionContractsZ["daemon.shutdown"].input,
|
|
125
|
+
resultSchema: ActionContractsZ["daemon.shutdown"].result,
|
|
126
|
+
handler: daemonShutdownHandler,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Generic accessor that preserves the `N` type binding. Exposed as a
|
|
132
|
+
* convenience for tests; the dispatcher uses the registry directly.
|
|
133
|
+
*/
|
|
134
|
+
export function getActionEntry<N extends ActionName>(name: N): ActionRegistryEntry<N> {
|
|
135
|
+
return actionRegistry[name];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Re-export the loosely-typed shape used by the dispatcher loop. Casting
|
|
139
|
+
// once here prevents the dispatcher from carrying the conditional type
|
|
140
|
+
// through its run-time logic.
|
|
141
|
+
export type LooseActionEntry = ActionRegistryEntry<ActionName> & {
|
|
142
|
+
inputSchema: z.ZodTypeAny;
|
|
143
|
+
resultSchema: z.ZodTypeAny;
|
|
144
|
+
handler: (input: unknown) => Promise<unknown> | unknown;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export function getLooseActionEntry(name: ActionName): LooseActionEntry {
|
|
148
|
+
return actionRegistry[name] as unknown as LooseActionEntry;
|
|
149
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { listSessionPanes } from "../widgets/lib/pane-comms.ts";
|
|
3
|
+
import type { PaneInfo } from "@tmux-ide/contracts";
|
|
4
|
+
import { getDefaultWorkspaceRegistry } from "../lib/workspace-registry.ts";
|
|
5
|
+
|
|
6
|
+
export interface SessionInfo {
|
|
7
|
+
name: string;
|
|
8
|
+
dir: string;
|
|
9
|
+
panes: PaneInfo[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface SessionOverview {
|
|
13
|
+
name: string;
|
|
14
|
+
dir: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProjectDetail {
|
|
18
|
+
session: string;
|
|
19
|
+
dir: string;
|
|
20
|
+
panes: PaneInfo[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type TmuxRunner = (args: string[]) => string;
|
|
24
|
+
|
|
25
|
+
let _tmuxRunner: TmuxRunner = (args) =>
|
|
26
|
+
execFileSync("tmux", args, {
|
|
27
|
+
encoding: "utf-8",
|
|
28
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
29
|
+
}).trim();
|
|
30
|
+
|
|
31
|
+
export function _setTmuxRunner(fn: TmuxRunner): () => void {
|
|
32
|
+
const prev = _tmuxRunner;
|
|
33
|
+
_tmuxRunner = fn;
|
|
34
|
+
return () => {
|
|
35
|
+
_tmuxRunner = prev;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function tmuxSilent(args: string[]): string {
|
|
40
|
+
try {
|
|
41
|
+
return _tmuxRunner(args);
|
|
42
|
+
} catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function listTmuxSessions(): string[] {
|
|
48
|
+
const raw = tmuxSilent(["list-sessions", "-F", "#{session_name}"]);
|
|
49
|
+
if (!raw) return [];
|
|
50
|
+
return raw.split("\n").filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function getSessionCwd(session: string): string {
|
|
54
|
+
return tmuxSilent(["display-message", "-t", session, "-p", "#{pane_current_path}"]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function discoverSessions(): SessionInfo[] {
|
|
58
|
+
const sessionNames = listTmuxSessions();
|
|
59
|
+
const results: SessionInfo[] = [];
|
|
60
|
+
|
|
61
|
+
// Once the workspace registry is loaded (post daemon-embed startup),
|
|
62
|
+
// discovery is gated by registry membership: only registered workspaces
|
|
63
|
+
// are visible. Pre-load (e.g. unit tests that bypass daemon-embed) we
|
|
64
|
+
// fall through to the legacy "any tmux session with a cwd" behavior.
|
|
65
|
+
const registry = getDefaultWorkspaceRegistry();
|
|
66
|
+
const enforceRegistry = registry._isLoaded();
|
|
67
|
+
|
|
68
|
+
for (const name of sessionNames) {
|
|
69
|
+
if (enforceRegistry && !registry.has(name)) continue;
|
|
70
|
+
const dir = getSessionCwd(name);
|
|
71
|
+
if (!dir) continue;
|
|
72
|
+
|
|
73
|
+
let panes: PaneInfo[] = [];
|
|
74
|
+
try {
|
|
75
|
+
panes = listSessionPanes(name);
|
|
76
|
+
} catch {
|
|
77
|
+
// session may have vanished
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
results.push({ name, dir, panes });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function buildOverviews(sessions: SessionInfo[]): SessionOverview[] {
|
|
87
|
+
return sessions.map((s) => ({ name: s.name, dir: s.dir }));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function buildProjectDetail(info: SessionInfo): ProjectDetail {
|
|
91
|
+
return {
|
|
92
|
+
session: info.name,
|
|
93
|
+
dir: info.dir,
|
|
94
|
+
panes: info.panes,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createServer, type Server } from "node:http";
|
|
2
|
+
import { getRequestListener } from "@hono/node-server";
|
|
3
|
+
import { createApp, type CreateAppOptions } from "./server.ts";
|
|
4
|
+
import { AuthService } from "../lib/auth/auth-service.ts";
|
|
5
|
+
import type { AuthConfig } from "../lib/auth/types.ts";
|
|
6
|
+
|
|
7
|
+
export interface CommandCenterOptions {
|
|
8
|
+
port?: number;
|
|
9
|
+
hostname?: string;
|
|
10
|
+
authService?: AuthService;
|
|
11
|
+
authConfig?: AuthConfig;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function startCommandCenter(options: CommandCenterOptions = {}): Promise<Server> {
|
|
15
|
+
const port = options.port ?? 6060;
|
|
16
|
+
const hostname = options.hostname ?? "0.0.0.0";
|
|
17
|
+
const appOpts: CreateAppOptions = {};
|
|
18
|
+
if (options.authService) appOpts.authService = options.authService;
|
|
19
|
+
if (options.authConfig) appOpts.authConfig = options.authConfig;
|
|
20
|
+
const app = createApp(appOpts);
|
|
21
|
+
|
|
22
|
+
const listener = getRequestListener(app.fetch);
|
|
23
|
+
const server = createServer(listener);
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
server.listen(port, hostname, () => {
|
|
27
|
+
console.log(`Command Center API on http://${hostname}:${port}`);
|
|
28
|
+
resolve(server);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const updateTaskSchema = z.object({
|
|
4
|
+
status: z.enum(["todo", "in-progress", "review", "done"]).optional(),
|
|
5
|
+
assignee: z.string().optional(),
|
|
6
|
+
title: z.string().optional(),
|
|
7
|
+
description: z.string().optional(),
|
|
8
|
+
priority: z.number().optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const createTaskSchema = z.object({
|
|
12
|
+
title: z.string().trim().min(1, "Title is required"),
|
|
13
|
+
description: z.string().optional(),
|
|
14
|
+
priority: z.number().optional(),
|
|
15
|
+
goal: z.string().optional(),
|
|
16
|
+
tags: z.array(z.string()).optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const savePlanSchema = z.object({
|
|
20
|
+
content: z.string().max(1_000_000, "Plan content is too large"),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const savePlanContentSchema = z.object({
|
|
24
|
+
content: z.string().max(1_000_000, "Plan content is too large"),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const sendCommandSchema = z.object({
|
|
28
|
+
target: z.string().min(1, "Target pane is required"),
|
|
29
|
+
message: z.string().min(1, "Message is required"),
|
|
30
|
+
noEnter: z.boolean().optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const createMilestoneSchema = z.object({
|
|
34
|
+
title: z.string().trim().min(1, "Title is required"),
|
|
35
|
+
sequence: z.number().int().positive(),
|
|
36
|
+
description: z.string().optional(),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const updateMilestoneSchema = z.object({
|
|
40
|
+
status: z.enum(["locked", "active", "done", "validating"]).optional(),
|
|
41
|
+
title: z.string().optional(),
|
|
42
|
+
description: z.string().optional(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const updateAssertionSchema = z.object({
|
|
46
|
+
status: z.enum(["pending", "passing", "failing", "blocked"]),
|
|
47
|
+
evidence: z.string().optional(),
|
|
48
|
+
verifiedBy: z.string().optional(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const triggerResearchSchema = z.object({
|
|
52
|
+
type: z.string().trim().min(1, "Research type is required"),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const launchSchema = z
|
|
56
|
+
.object({
|
|
57
|
+
attach: z.boolean().optional(),
|
|
58
|
+
})
|
|
59
|
+
.optional();
|
|
60
|
+
|
|
61
|
+
export const stopSchema = z.object({}).optional();
|
|
62
|
+
|
|
63
|
+
const skillNameRegex = /^[A-Za-z0-9._ -]+$/;
|
|
64
|
+
|
|
65
|
+
export const createSkillSchema = z.object({
|
|
66
|
+
name: z
|
|
67
|
+
.string()
|
|
68
|
+
.trim()
|
|
69
|
+
.min(1, "Skill name is required")
|
|
70
|
+
.regex(
|
|
71
|
+
skillNameRegex,
|
|
72
|
+
"Skill name may only contain letters, digits, dot, dash, underscore, or space",
|
|
73
|
+
),
|
|
74
|
+
role: z.string().trim().optional(),
|
|
75
|
+
description: z.string().optional(),
|
|
76
|
+
specialties: z.array(z.string()).optional(),
|
|
77
|
+
body: z.string().optional(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const updateSkillSchema = z.object({
|
|
81
|
+
role: z.string().trim().optional(),
|
|
82
|
+
description: z.string().optional(),
|
|
83
|
+
specialties: z.array(z.string()).optional(),
|
|
84
|
+
body: z.string().optional(),
|
|
85
|
+
});
|