orbital-command 0.1.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.
Files changed (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/bin/orbital.js +362 -0
  4. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +84 -0
  5. package/dist/assets/WorkflowVisualizer-BZV40eAE.css +1 -0
  6. package/dist/assets/charts-D__PA1zp.js +72 -0
  7. package/dist/assets/index-D1G6i0nS.css +1 -0
  8. package/dist/assets/index-DpItvKpf.js +419 -0
  9. package/dist/assets/ui-BvF022GT.js +53 -0
  10. package/dist/assets/vendor-Dzv9lrRc.js +59 -0
  11. package/dist/index.html +19 -0
  12. package/dist/scanner-sweep.png +0 -0
  13. package/dist/server/server/adapters/index.js +34 -0
  14. package/dist/server/server/adapters/iterm2-adapter.js +29 -0
  15. package/dist/server/server/adapters/subprocess-adapter.js +21 -0
  16. package/dist/server/server/adapters/terminal-adapter.js +1 -0
  17. package/dist/server/server/config.js +156 -0
  18. package/dist/server/server/database.js +90 -0
  19. package/dist/server/server/index.js +372 -0
  20. package/dist/server/server/init.js +811 -0
  21. package/dist/server/server/parsers/event-parser.js +64 -0
  22. package/dist/server/server/parsers/scope-parser.js +188 -0
  23. package/dist/server/server/routes/config-routes.js +163 -0
  24. package/dist/server/server/routes/data-routes.js +461 -0
  25. package/dist/server/server/routes/dispatch-routes.js +215 -0
  26. package/dist/server/server/routes/git-routes.js +92 -0
  27. package/dist/server/server/routes/scope-routes.js +215 -0
  28. package/dist/server/server/routes/sprint-routes.js +116 -0
  29. package/dist/server/server/routes/version-routes.js +130 -0
  30. package/dist/server/server/routes/workflow-routes.js +185 -0
  31. package/dist/server/server/schema.js +90 -0
  32. package/dist/server/server/services/batch-orchestrator.js +253 -0
  33. package/dist/server/server/services/claude-session-service.js +352 -0
  34. package/dist/server/server/services/config-service.js +132 -0
  35. package/dist/server/server/services/deploy-service.js +51 -0
  36. package/dist/server/server/services/event-service.js +63 -0
  37. package/dist/server/server/services/gate-service.js +83 -0
  38. package/dist/server/server/services/git-service.js +309 -0
  39. package/dist/server/server/services/github-service.js +145 -0
  40. package/dist/server/server/services/readiness-service.js +184 -0
  41. package/dist/server/server/services/scope-cache.js +72 -0
  42. package/dist/server/server/services/scope-service.js +424 -0
  43. package/dist/server/server/services/sprint-orchestrator.js +312 -0
  44. package/dist/server/server/services/sprint-service.js +293 -0
  45. package/dist/server/server/services/workflow-service.js +397 -0
  46. package/dist/server/server/utils/cc-hooks-parser.js +49 -0
  47. package/dist/server/server/utils/dispatch-utils.js +305 -0
  48. package/dist/server/server/utils/logger.js +86 -0
  49. package/dist/server/server/utils/terminal-launcher.js +388 -0
  50. package/dist/server/server/utils/worktree-manager.js +98 -0
  51. package/dist/server/server/watchers/event-watcher.js +81 -0
  52. package/dist/server/server/watchers/scope-watcher.js +33 -0
  53. package/dist/server/shared/api-types.js +5 -0
  54. package/dist/server/shared/default-workflow.json +616 -0
  55. package/dist/server/shared/workflow-config.js +44 -0
  56. package/dist/server/shared/workflow-engine.js +353 -0
  57. package/index.html +15 -0
  58. package/package.json +110 -0
  59. package/postcss.config.js +6 -0
  60. package/schemas/orbital.config.schema.json +83 -0
  61. package/scripts/postinstall.js +24 -0
  62. package/scripts/start.sh +20 -0
  63. package/server/adapters/index.ts +41 -0
  64. package/server/adapters/iterm2-adapter.ts +37 -0
  65. package/server/adapters/subprocess-adapter.ts +25 -0
  66. package/server/adapters/terminal-adapter.ts +24 -0
  67. package/server/config.ts +234 -0
  68. package/server/database.ts +107 -0
  69. package/server/index.ts +452 -0
  70. package/server/init.ts +891 -0
  71. package/server/parsers/event-parser.ts +74 -0
  72. package/server/parsers/scope-parser.ts +240 -0
  73. package/server/routes/config-routes.ts +182 -0
  74. package/server/routes/data-routes.ts +548 -0
  75. package/server/routes/dispatch-routes.ts +275 -0
  76. package/server/routes/git-routes.ts +112 -0
  77. package/server/routes/scope-routes.ts +262 -0
  78. package/server/routes/sprint-routes.ts +142 -0
  79. package/server/routes/version-routes.ts +156 -0
  80. package/server/routes/workflow-routes.ts +198 -0
  81. package/server/schema.ts +90 -0
  82. package/server/services/batch-orchestrator.ts +286 -0
  83. package/server/services/claude-session-service.ts +441 -0
  84. package/server/services/config-service.ts +151 -0
  85. package/server/services/deploy-service.ts +98 -0
  86. package/server/services/event-service.ts +98 -0
  87. package/server/services/gate-service.ts +126 -0
  88. package/server/services/git-service.ts +391 -0
  89. package/server/services/github-service.ts +183 -0
  90. package/server/services/readiness-service.ts +250 -0
  91. package/server/services/scope-cache.ts +81 -0
  92. package/server/services/scope-service.ts +476 -0
  93. package/server/services/sprint-orchestrator.ts +361 -0
  94. package/server/services/sprint-service.ts +415 -0
  95. package/server/services/workflow-service.ts +461 -0
  96. package/server/utils/cc-hooks-parser.ts +70 -0
  97. package/server/utils/dispatch-utils.ts +395 -0
  98. package/server/utils/logger.ts +109 -0
  99. package/server/utils/terminal-launcher.ts +462 -0
  100. package/server/utils/worktree-manager.ts +104 -0
  101. package/server/watchers/event-watcher.ts +100 -0
  102. package/server/watchers/scope-watcher.ts +38 -0
  103. package/shared/api-types.ts +20 -0
  104. package/shared/default-workflow.json +616 -0
  105. package/shared/workflow-config.ts +170 -0
  106. package/shared/workflow-engine.ts +427 -0
  107. package/src/App.tsx +33 -0
  108. package/src/components/AgentBadge.tsx +40 -0
  109. package/src/components/BatchPreflightModal.tsx +115 -0
  110. package/src/components/CardDisplayToggle.tsx +74 -0
  111. package/src/components/ColumnHeaderActions.tsx +55 -0
  112. package/src/components/ColumnMenu.tsx +99 -0
  113. package/src/components/DeployHistory.tsx +141 -0
  114. package/src/components/DispatchModal.tsx +164 -0
  115. package/src/components/DispatchPopover.tsx +139 -0
  116. package/src/components/DragOverlay.tsx +25 -0
  117. package/src/components/DriftSidebar.tsx +140 -0
  118. package/src/components/EnvironmentStrip.tsx +88 -0
  119. package/src/components/ErrorBoundary.tsx +62 -0
  120. package/src/components/FilterChip.tsx +105 -0
  121. package/src/components/GateIndicator.tsx +33 -0
  122. package/src/components/IdeaDetailModal.tsx +190 -0
  123. package/src/components/IdeaFormDialog.tsx +113 -0
  124. package/src/components/KanbanColumn.tsx +201 -0
  125. package/src/components/MarkdownRenderer.tsx +114 -0
  126. package/src/components/NeonGrid.tsx +128 -0
  127. package/src/components/PromotionQueue.tsx +89 -0
  128. package/src/components/ScopeCard.tsx +234 -0
  129. package/src/components/ScopeDetailModal.tsx +255 -0
  130. package/src/components/ScopeFilterBar.tsx +152 -0
  131. package/src/components/SearchInput.tsx +102 -0
  132. package/src/components/SessionPanel.tsx +335 -0
  133. package/src/components/SprintContainer.tsx +303 -0
  134. package/src/components/SprintDependencyDialog.tsx +78 -0
  135. package/src/components/SprintPreflightModal.tsx +138 -0
  136. package/src/components/StatusBar.tsx +168 -0
  137. package/src/components/SwimCell.tsx +67 -0
  138. package/src/components/SwimLaneRow.tsx +94 -0
  139. package/src/components/SwimlaneBoardView.tsx +108 -0
  140. package/src/components/VersionBadge.tsx +139 -0
  141. package/src/components/ViewModeSelector.tsx +114 -0
  142. package/src/components/config/AgentChip.tsx +53 -0
  143. package/src/components/config/AgentCreateDialog.tsx +321 -0
  144. package/src/components/config/AgentEditor.tsx +175 -0
  145. package/src/components/config/DirectoryTree.tsx +582 -0
  146. package/src/components/config/FileEditor.tsx +550 -0
  147. package/src/components/config/HookChip.tsx +50 -0
  148. package/src/components/config/StageCard.tsx +198 -0
  149. package/src/components/config/TransitionZone.tsx +173 -0
  150. package/src/components/config/UnifiedWorkflowPipeline.tsx +216 -0
  151. package/src/components/config/WorkflowPipeline.tsx +161 -0
  152. package/src/components/source-control/BranchList.tsx +93 -0
  153. package/src/components/source-control/BranchPanel.tsx +105 -0
  154. package/src/components/source-control/CommitLog.tsx +100 -0
  155. package/src/components/source-control/CommitRow.tsx +47 -0
  156. package/src/components/source-control/GitHubPanel.tsx +110 -0
  157. package/src/components/source-control/GitHubSetupGuide.tsx +52 -0
  158. package/src/components/source-control/GitOverviewBar.tsx +101 -0
  159. package/src/components/source-control/PullRequestList.tsx +69 -0
  160. package/src/components/source-control/WorktreeList.tsx +80 -0
  161. package/src/components/ui/badge.tsx +41 -0
  162. package/src/components/ui/button.tsx +55 -0
  163. package/src/components/ui/card.tsx +78 -0
  164. package/src/components/ui/dialog.tsx +94 -0
  165. package/src/components/ui/popover.tsx +33 -0
  166. package/src/components/ui/scroll-area.tsx +54 -0
  167. package/src/components/ui/separator.tsx +28 -0
  168. package/src/components/ui/tabs.tsx +52 -0
  169. package/src/components/ui/toggle-switch.tsx +35 -0
  170. package/src/components/ui/tooltip.tsx +27 -0
  171. package/src/components/workflow/AddEdgeDialog.tsx +217 -0
  172. package/src/components/workflow/AddListDialog.tsx +201 -0
  173. package/src/components/workflow/ChecklistEditor.tsx +239 -0
  174. package/src/components/workflow/CommandPrefixManager.tsx +118 -0
  175. package/src/components/workflow/ConfigSettingsPanel.tsx +189 -0
  176. package/src/components/workflow/DirectionSelector.tsx +133 -0
  177. package/src/components/workflow/DispatchConfigPanel.tsx +180 -0
  178. package/src/components/workflow/EdgeDetailPanel.tsx +236 -0
  179. package/src/components/workflow/EdgePropertyEditor.tsx +251 -0
  180. package/src/components/workflow/EditToolbar.tsx +138 -0
  181. package/src/components/workflow/HookDetailPanel.tsx +250 -0
  182. package/src/components/workflow/HookExecutionLog.tsx +24 -0
  183. package/src/components/workflow/HookSourceModal.tsx +129 -0
  184. package/src/components/workflow/HooksDashboard.tsx +363 -0
  185. package/src/components/workflow/ListPropertyEditor.tsx +251 -0
  186. package/src/components/workflow/MigrationPreviewDialog.tsx +237 -0
  187. package/src/components/workflow/MovementRulesPanel.tsx +188 -0
  188. package/src/components/workflow/NodeDetailPanel.tsx +245 -0
  189. package/src/components/workflow/PresetSelector.tsx +414 -0
  190. package/src/components/workflow/SkillCommandBuilder.tsx +174 -0
  191. package/src/components/workflow/WorkflowEdgeComponent.tsx +145 -0
  192. package/src/components/workflow/WorkflowNode.tsx +147 -0
  193. package/src/components/workflow/graphLayout.ts +186 -0
  194. package/src/components/workflow/mergeHooks.ts +85 -0
  195. package/src/components/workflow/useEditHistory.ts +88 -0
  196. package/src/components/workflow/useWorkflowEditor.ts +262 -0
  197. package/src/components/workflow/validateConfig.ts +70 -0
  198. package/src/hooks/useActiveDispatches.ts +198 -0
  199. package/src/hooks/useBoardSettings.ts +170 -0
  200. package/src/hooks/useCardDisplay.ts +57 -0
  201. package/src/hooks/useCcHooks.ts +24 -0
  202. package/src/hooks/useConfigTree.ts +51 -0
  203. package/src/hooks/useEnforcementRules.ts +46 -0
  204. package/src/hooks/useEvents.ts +59 -0
  205. package/src/hooks/useFileEditor.ts +165 -0
  206. package/src/hooks/useGates.ts +57 -0
  207. package/src/hooks/useIdeaActions.ts +53 -0
  208. package/src/hooks/useKanbanDnd.ts +410 -0
  209. package/src/hooks/useOrbitalConfig.ts +54 -0
  210. package/src/hooks/usePipeline.ts +47 -0
  211. package/src/hooks/usePipelineData.ts +338 -0
  212. package/src/hooks/useReconnect.ts +25 -0
  213. package/src/hooks/useScopeFilters.ts +125 -0
  214. package/src/hooks/useScopeSessions.ts +44 -0
  215. package/src/hooks/useScopes.ts +67 -0
  216. package/src/hooks/useSearch.ts +67 -0
  217. package/src/hooks/useSettings.tsx +187 -0
  218. package/src/hooks/useSocket.ts +25 -0
  219. package/src/hooks/useSourceControl.ts +105 -0
  220. package/src/hooks/useSprintPreflight.ts +55 -0
  221. package/src/hooks/useSprints.ts +154 -0
  222. package/src/hooks/useStatusBarHighlight.ts +18 -0
  223. package/src/hooks/useSwimlaneBoardSettings.ts +104 -0
  224. package/src/hooks/useTheme.ts +9 -0
  225. package/src/hooks/useTransitionReadiness.ts +53 -0
  226. package/src/hooks/useVersion.ts +155 -0
  227. package/src/hooks/useViolations.ts +65 -0
  228. package/src/hooks/useWorkflow.tsx +125 -0
  229. package/src/hooks/useZoomModifier.ts +19 -0
  230. package/src/index.css +797 -0
  231. package/src/layouts/DashboardLayout.tsx +113 -0
  232. package/src/lib/collisionDetection.ts +20 -0
  233. package/src/lib/scope-fields.ts +61 -0
  234. package/src/lib/swimlane.ts +146 -0
  235. package/src/lib/utils.ts +15 -0
  236. package/src/main.tsx +19 -0
  237. package/src/socket.ts +11 -0
  238. package/src/types/index.ts +497 -0
  239. package/src/views/AgentFeed.tsx +339 -0
  240. package/src/views/DeployPipeline.tsx +59 -0
  241. package/src/views/EnforcementView.tsx +378 -0
  242. package/src/views/PrimitivesConfig.tsx +500 -0
  243. package/src/views/QualityGates.tsx +1012 -0
  244. package/src/views/ScopeBoard.tsx +454 -0
  245. package/src/views/SessionTimeline.tsx +516 -0
  246. package/src/views/Settings.tsx +183 -0
  247. package/src/views/SourceControl.tsx +95 -0
  248. package/src/views/WorkflowVisualizer.tsx +382 -0
  249. package/tailwind.config.js +161 -0
  250. package/templates/agents/AUTO-INVOKE.md +180 -0
  251. package/templates/agents/CONFLICT-RESOLUTION.md +128 -0
  252. package/templates/agents/QUICK-REFERENCE.md +122 -0
  253. package/templates/agents/README.md +188 -0
  254. package/templates/agents/SKILL-TRIGGERS.md +100 -0
  255. package/templates/agents/blue-team/frontend-designer.md +424 -0
  256. package/templates/agents/green-team/architect.md +526 -0
  257. package/templates/agents/green-team/rules-enforcer.md +131 -0
  258. package/templates/agents/red-team/attacker-learned.md +24 -0
  259. package/templates/agents/red-team/attacker.md +486 -0
  260. package/templates/agents/red-team/chaos.md +548 -0
  261. package/templates/agents/reference/component-registry.md +82 -0
  262. package/templates/agents/workflows/full-mode.md +218 -0
  263. package/templates/agents/workflows/quick-mode.md +118 -0
  264. package/templates/agents/workflows/security-mode.md +283 -0
  265. package/templates/anti-patterns/dangerous-shortcuts.md +427 -0
  266. package/templates/config/agent-triggers.json +92 -0
  267. package/templates/hooks/agent-team-gate.sh +31 -0
  268. package/templates/hooks/agent-trigger.sh +97 -0
  269. package/templates/hooks/block-push.sh +66 -0
  270. package/templates/hooks/block-workarounds.sh +61 -0
  271. package/templates/hooks/blocker-check.sh +28 -0
  272. package/templates/hooks/completion-checklist.sh +28 -0
  273. package/templates/hooks/decision-capture.sh +15 -0
  274. package/templates/hooks/dependency-check.sh +27 -0
  275. package/templates/hooks/end-session.sh +31 -0
  276. package/templates/hooks/exploration-logger.sh +37 -0
  277. package/templates/hooks/files-changed-summary.sh +37 -0
  278. package/templates/hooks/get-session-id.sh +49 -0
  279. package/templates/hooks/git-commit-guard.sh +34 -0
  280. package/templates/hooks/init-session.sh +93 -0
  281. package/templates/hooks/orbital-emit.sh +79 -0
  282. package/templates/hooks/orbital-report-deploy.sh +78 -0
  283. package/templates/hooks/orbital-report-gates.sh +40 -0
  284. package/templates/hooks/orbital-report-violation.sh +36 -0
  285. package/templates/hooks/orbital-scope-update.sh +53 -0
  286. package/templates/hooks/phase-verify-reminder.sh +26 -0
  287. package/templates/hooks/review-gate-check.sh +82 -0
  288. package/templates/hooks/scope-commit-logger.sh +37 -0
  289. package/templates/hooks/scope-create-cleanup.sh +36 -0
  290. package/templates/hooks/scope-create-gate.sh +80 -0
  291. package/templates/hooks/scope-create-tracker.sh +17 -0
  292. package/templates/hooks/scope-file-sync.sh +53 -0
  293. package/templates/hooks/scope-gate.sh +35 -0
  294. package/templates/hooks/scope-helpers.sh +188 -0
  295. package/templates/hooks/scope-lifecycle-gate.sh +139 -0
  296. package/templates/hooks/scope-prepare.sh +244 -0
  297. package/templates/hooks/scope-transition.sh +172 -0
  298. package/templates/hooks/session-enforcer.sh +143 -0
  299. package/templates/hooks/time-tracker.sh +33 -0
  300. package/templates/lessons-learned.md +15 -0
  301. package/templates/orbital.config.json +35 -0
  302. package/templates/presets/development.json +42 -0
  303. package/templates/presets/gitflow.json +712 -0
  304. package/templates/presets/minimal.json +23 -0
  305. package/templates/quick/rules.md +218 -0
  306. package/templates/scopes/_template.md +255 -0
  307. package/templates/settings-hooks.json +98 -0
  308. package/templates/skills/git-commit/SKILL.md +85 -0
  309. package/templates/skills/git-dev/SKILL.md +99 -0
  310. package/templates/skills/git-hotfix/SKILL.md +223 -0
  311. package/templates/skills/git-main/SKILL.md +84 -0
  312. package/templates/skills/git-production/SKILL.md +165 -0
  313. package/templates/skills/git-staging/SKILL.md +112 -0
  314. package/templates/skills/scope-create/SKILL.md +81 -0
  315. package/templates/skills/scope-fix-review/SKILL.md +168 -0
  316. package/templates/skills/scope-implement/SKILL.md +110 -0
  317. package/templates/skills/scope-post-review/SKILL.md +144 -0
  318. package/templates/skills/scope-pre-review/SKILL.md +211 -0
  319. package/templates/skills/scope-verify/SKILL.md +201 -0
  320. package/templates/skills/session-init/SKILL.md +62 -0
  321. package/templates/skills/session-resume/SKILL.md +201 -0
  322. package/templates/skills/test-checks/SKILL.md +171 -0
  323. package/templates/skills/test-code-review/SKILL.md +252 -0
  324. package/tsconfig.json +25 -0
  325. package/vite.config.ts +38 -0
@@ -0,0 +1,353 @@
1
+ import { getHookEnforcement } from './workflow-config.js';
2
+ // ─── WorkflowEngine ─────────────────────────────────────────
3
+ //
4
+ // Config-driven query layer over the WorkflowConfig JSON.
5
+ // Pure class — no I/O, no global state, no side effects.
6
+ export class WorkflowEngine {
7
+ config;
8
+ listMap;
9
+ edgeMap;
10
+ edgesByFrom;
11
+ statusOrder;
12
+ hookMap;
13
+ terminalStatuses;
14
+ allowedPrefixes;
15
+ constructor(config) {
16
+ this.init(config);
17
+ }
18
+ /**
19
+ * Hot-reload the engine with a new config. All services holding a reference
20
+ * to this engine instance will see the updated config immediately.
21
+ */
22
+ reload(config) {
23
+ this.init(config);
24
+ }
25
+ init(config) {
26
+ this.config = config;
27
+ if (!config.lists.length)
28
+ throw new Error('WorkflowConfig must have at least 1 list');
29
+ if (!config.edges.length)
30
+ throw new Error('WorkflowConfig must have at least 1 edge');
31
+ const entryPoints = config.lists.filter((l) => l.isEntryPoint);
32
+ if (entryPoints.length !== 1) {
33
+ throw new Error(`WorkflowConfig must have exactly 1 entry point, found ${entryPoints.length}`);
34
+ }
35
+ this.listMap = new Map(config.lists.map((l) => [l.id, l]));
36
+ this.edgeMap = new Map(config.edges.map((e) => [`${e.from}:${e.to}`, e]));
37
+ this.edgesByFrom = new Map();
38
+ for (const edge of config.edges) {
39
+ const existing = this.edgesByFrom.get(edge.from);
40
+ if (existing)
41
+ existing.push(edge);
42
+ else
43
+ this.edgesByFrom.set(edge.from, [edge]);
44
+ }
45
+ this.statusOrder = new Map(config.lists.map((l) => [l.id, l.order]));
46
+ this.hookMap = new Map((config.hooks ?? []).map((h) => [h.id, h]));
47
+ this.terminalStatuses = new Set(config.terminalStatuses ?? []);
48
+ this.allowedPrefixes = config.allowedCommandPrefixes ?? [];
49
+ }
50
+ // ─── Config Access ──────────────────────────────────────────
51
+ getConfig() {
52
+ return this.config;
53
+ }
54
+ getBranchingMode() {
55
+ return this.config.branchingMode ?? 'trunk';
56
+ }
57
+ // ─── List Queries ───────────────────────────────────────────
58
+ getLists() {
59
+ return [...this.config.lists].sort((a, b) => a.order - b.order);
60
+ }
61
+ getList(id) {
62
+ return this.listMap.get(id);
63
+ }
64
+ getEntryPoint() {
65
+ return this.config.lists.find((l) => l.isEntryPoint);
66
+ }
67
+ getBatchLists() {
68
+ return this.config.lists.filter((l) => l.supportsBatch);
69
+ }
70
+ getSprintLists() {
71
+ return this.config.lists.filter((l) => l.supportsSprint);
72
+ }
73
+ getBoardColumns() {
74
+ return this.getLists().map((l) => ({ id: l.id, label: l.label, color: l.color }));
75
+ }
76
+ // ─── Edge Queries ───────────────────────────────────────────
77
+ findEdge(from, to) {
78
+ return this.edgeMap.get(`${from}:${to}`);
79
+ }
80
+ isValidTransition(from, to) {
81
+ return this.edgeMap.has(`${from}:${to}`);
82
+ }
83
+ getValidTargets(from) {
84
+ return (this.edgesByFrom.get(from) ?? []).map((e) => e.to);
85
+ }
86
+ getAllEdges() {
87
+ return this.config.edges;
88
+ }
89
+ getEdgesByDirection(dir) {
90
+ return this.config.edges.filter((e) => e.direction === dir);
91
+ }
92
+ // ─── Validation ─────────────────────────────────────────────
93
+ validateTransition(from, to, context) {
94
+ if (!this.listMap.has(to)) {
95
+ return { ok: false, error: `Invalid status: '${to}'`, code: 'INVALID_STATUS' };
96
+ }
97
+ // bulk-sync and rollback are trusted contexts — skip transition checks
98
+ if (context === 'bulk-sync' || context === 'rollback')
99
+ return { ok: true };
100
+ // Same status is a no-op, always allowed
101
+ if (from === to)
102
+ return { ok: true };
103
+ const edge = this.findEdge(from, to);
104
+ if (!edge) {
105
+ return { ok: false, error: `Transition '${from}' -> '${to}' is not allowed`, code: 'INVALID_TRANSITION' };
106
+ }
107
+ // patch context cannot trigger dispatch-only transitions
108
+ if (context === 'patch' && edge.dispatchOnly) {
109
+ return { ok: false, error: `Transition '${from}' -> '${to}' requires dispatch (use a skill command)`, code: 'DISPATCH_REQUIRED' };
110
+ }
111
+ return { ok: true };
112
+ }
113
+ isValidStatus(status) {
114
+ return this.listMap.has(status);
115
+ }
116
+ isTerminalStatus(status) {
117
+ return this.terminalStatuses.has(status);
118
+ }
119
+ // ─── Commands ───────────────────────────────────────────────
120
+ buildCommand(edge, scopeId) {
121
+ if (!edge.command)
122
+ return null;
123
+ return edge.command.replace('{id}', String(scopeId));
124
+ }
125
+ isAllowedCommand(cmd) {
126
+ return this.allowedPrefixes.some((prefix) => cmd.startsWith(prefix));
127
+ }
128
+ // ─── Batch / Sprint ─────────────────────────────────────────
129
+ getBatchTargetStatus(column) {
130
+ const edges = this.edgesByFrom.get(column) ?? [];
131
+ const edge = edges.find((e) => (e.direction === 'forward' || e.direction === 'shortcut') && e.dispatchOnly);
132
+ return edge?.to;
133
+ }
134
+ getBatchCommand(column) {
135
+ const edges = this.edgesByFrom.get(column) ?? [];
136
+ const edge = edges.find((e) => (e.direction === 'forward' || e.direction === 'shortcut') && e.dispatchOnly);
137
+ if (!edge?.command)
138
+ return undefined;
139
+ // Strip {id} placeholder — batch consumers handle substitution themselves
140
+ return edge.command.replace(' {id}', '').replace('{id}', '');
141
+ }
142
+ // ─── Event Inference ────────────────────────────────────────
143
+ inferStatus(eventType, currentStatus, data) {
144
+ const rules = (this.config.eventInference ?? []).filter((r) => r.eventType === eventType);
145
+ if (!rules.length)
146
+ return null;
147
+ // Check rules with conditions first (more specific), then without
148
+ const withConditions = rules.filter((r) => r.conditions && Object.keys(r.conditions).length > 0);
149
+ const withoutConditions = rules.filter((r) => !r.conditions || Object.keys(r.conditions).length === 0);
150
+ const matchedRule = withConditions.find((r) => this.matchesConditions(r.conditions, data))
151
+ ?? withoutConditions[0]
152
+ ?? null;
153
+ if (!matchedRule)
154
+ return null;
155
+ // Handle dispatch resolution (e.g. AGENT_COMPLETED with outcome)
156
+ if (matchedRule.conditions?.dispatchResolution === true) {
157
+ const outcome = data.outcome;
158
+ return { dispatchResolution: true, resolution: outcome === 'failure' ? 'failed' : 'completed' };
159
+ }
160
+ // Determine target status
161
+ let newStatus;
162
+ if (matchedRule.targetStatus === '' && matchedRule.dataField) {
163
+ const rawValue = String(data[matchedRule.dataField] ?? '');
164
+ if (matchedRule.dataMap) {
165
+ newStatus = matchedRule.dataMap[rawValue] ?? matchedRule.dataMap['_default'] ?? '';
166
+ }
167
+ else {
168
+ newStatus = rawValue;
169
+ }
170
+ }
171
+ else {
172
+ newStatus = matchedRule.targetStatus;
173
+ }
174
+ if (!newStatus)
175
+ return null;
176
+ // Forward-only guard: event inference must not regress scope status
177
+ if (matchedRule.forwardOnly) {
178
+ const currentOrder = this.statusOrder.get(currentStatus) ?? -1;
179
+ const newOrder = this.statusOrder.get(newStatus) ?? -1;
180
+ if (newOrder <= currentOrder)
181
+ return null;
182
+ }
183
+ return newStatus;
184
+ }
185
+ matchesConditions(conditions, data) {
186
+ for (const [key, expected] of Object.entries(conditions)) {
187
+ // dispatchResolution is a flag, not a data check
188
+ if (key === 'dispatchResolution')
189
+ continue;
190
+ const actual = data[key];
191
+ if (Array.isArray(expected)) {
192
+ if (!expected.includes(actual))
193
+ return false;
194
+ }
195
+ else {
196
+ if (actual !== expected)
197
+ return false;
198
+ }
199
+ }
200
+ return true;
201
+ }
202
+ // ─── Git / Lifecycle ────────────────────────────────────────
203
+ getListByGitBranch(branch) {
204
+ return this.config.lists.find((l) => l.gitBranch === branch);
205
+ }
206
+ getGitBranch(listId) {
207
+ return this.listMap.get(listId)?.gitBranch;
208
+ }
209
+ getSessionKey(listId) {
210
+ return this.listMap.get(listId)?.sessionKey;
211
+ }
212
+ getActiveHooksForList(listId) {
213
+ return this.listMap.get(listId)?.activeHooks ?? [];
214
+ }
215
+ getAgentsForEdge(from, to) {
216
+ return this.findEdge(from, to)?.agents ?? [];
217
+ }
218
+ // ─── Status Order ───────────────────────────────────────────
219
+ getStatusOrder(status) {
220
+ return this.statusOrder.get(status) ?? -1;
221
+ }
222
+ isForwardMovement(from, to) {
223
+ return this.getStatusOrder(to) > this.getStatusOrder(from);
224
+ }
225
+ // ─── Hooks ──────────────────────────────────────────────────
226
+ getHooksForEdge(from, to) {
227
+ const edge = this.findEdge(from, to);
228
+ if (!edge?.hooks?.length)
229
+ return [];
230
+ return edge.hooks
231
+ .map((id) => this.hookMap.get(id))
232
+ .filter((h) => h !== undefined);
233
+ }
234
+ getAllHooks() {
235
+ return this.config.hooks ?? [];
236
+ }
237
+ getHookEnforcement(hook) {
238
+ return getHookEnforcement(hook);
239
+ }
240
+ getHooksByCategory(category) {
241
+ return (this.config.hooks ?? []).filter((h) => h.category === category);
242
+ }
243
+ // ─── Generation ─────────────────────────────────────────────
244
+ generateCSSVariables() {
245
+ return this.getLists()
246
+ .map((l) => `--status-${l.id}: ${l.color};`)
247
+ .join('\n');
248
+ }
249
+ generateShellManifest() {
250
+ const lines = [];
251
+ const sorted = this.getLists();
252
+ // Header
253
+ lines.push('#!/bin/bash');
254
+ lines.push('# Auto-generated by WorkflowEngine — DO NOT EDIT');
255
+ lines.push(`# Generated: ${new Date().toISOString()}`);
256
+ lines.push(`# Workflow: "${this.config.name}" (version ${this.config.version})`);
257
+ lines.push('');
258
+ // Branching mode
259
+ lines.push('# ─── Branching mode (trunk or worktree) ───');
260
+ lines.push(`WORKFLOW_BRANCHING_MODE="${this.getBranchingMode()}"`);
261
+ lines.push('');
262
+ // Valid statuses
263
+ lines.push('# ─── Valid statuses (space-separated) ───');
264
+ lines.push(`WORKFLOW_STATUSES="${sorted.map((l) => l.id).join(' ')}"`);
265
+ lines.push('');
266
+ // Directory statuses
267
+ lines.push('# ─── Statuses that have a scopes/ subdirectory ───');
268
+ const dirStatuses = sorted.filter((l) => l.hasDirectory).map((l) => l.id);
269
+ lines.push(`WORKFLOW_DIR_STATUSES="${dirStatuses.join(' ')}"`);
270
+ lines.push('');
271
+ // Terminal statuses
272
+ lines.push('# ─── Terminal statuses ───');
273
+ lines.push(`WORKFLOW_TERMINAL_STATUSES="${[...this.terminalStatuses].join(' ')}"`);
274
+ lines.push('');
275
+ // Entry point
276
+ lines.push('# ─── Entry point status ───');
277
+ lines.push(`WORKFLOW_ENTRY_STATUS="${this.getEntryPoint().id}"`);
278
+ lines.push('');
279
+ // Edges
280
+ lines.push('# ─── Transition edges (from:to:sessionKey) ───');
281
+ lines.push('WORKFLOW_EDGES=(');
282
+ for (const edge of this.config.edges) {
283
+ const targetList = this.listMap.get(edge.to);
284
+ const sessionKey = targetList?.sessionKey ?? '';
285
+ lines.push(` "${edge.from}:${edge.to}:${sessionKey}"`);
286
+ }
287
+ lines.push(')');
288
+ lines.push('');
289
+ // Branch map
290
+ lines.push('# ─── Branch-to-transition mapping (gitBranch:from:to:sessionKey) ───');
291
+ lines.push('WORKFLOW_BRANCH_MAP=(');
292
+ for (const edge of this.config.edges) {
293
+ const targetList = this.listMap.get(edge.to);
294
+ if (targetList?.gitBranch) {
295
+ const sessionKey = targetList.sessionKey ?? '';
296
+ lines.push(` "${targetList.gitBranch}:${edge.from}:${edge.to}:${sessionKey}"`);
297
+ }
298
+ }
299
+ lines.push(')');
300
+ lines.push('');
301
+ // Commit branch patterns
302
+ lines.push('# ─── Commit session branch patterns (regex) ───');
303
+ lines.push(`WORKFLOW_COMMIT_BRANCHES="${this.config.commitBranchPatterns ?? ''}"`);
304
+ lines.push('');
305
+ // Direction aliases (deployment edges: forward+dispatchOnly targeting deployment-group lists)
306
+ lines.push('# ─── Backward-compat direction aliases (alias:from:to:sessionKey) ───');
307
+ lines.push('WORKFLOW_DIRECTION_ALIASES=(');
308
+ for (const edge of this.config.edges) {
309
+ if (edge.direction !== 'forward' || !edge.dispatchOnly)
310
+ continue;
311
+ const targetList = this.listMap.get(edge.to);
312
+ if (!targetList)
313
+ continue;
314
+ // Generate aliases for deployment-group targets (dev, staging, production)
315
+ const group = targetList.group;
316
+ if (group === 'deployment') {
317
+ const sessionKey = targetList.sessionKey ?? '';
318
+ lines.push(` "to-${edge.to}:${edge.from}:${edge.to}:${sessionKey}"`);
319
+ }
320
+ }
321
+ lines.push(')');
322
+ lines.push('');
323
+ // Helper functions
324
+ lines.push('# ─── Helper functions ──────────────────────────────');
325
+ lines.push('');
326
+ lines.push('status_to_dir() {');
327
+ lines.push(' local scope_status="$1"');
328
+ lines.push(' for s in $WORKFLOW_DIR_STATUSES; do');
329
+ lines.push(' [ "$s" = "$scope_status" ] && echo "$scope_status" && return 0');
330
+ lines.push(' done');
331
+ lines.push(' echo "$WORKFLOW_ENTRY_STATUS"');
332
+ lines.push('}');
333
+ lines.push('');
334
+ lines.push('status_to_branch() {');
335
+ lines.push(' local status="$1"');
336
+ lines.push(' for entry in "${WORKFLOW_BRANCH_MAP[@]}"; do');
337
+ lines.push(' IFS=\':\' read -r branch from to skey <<< "$entry"');
338
+ lines.push(' [ "$to" = "$status" ] && echo "$branch" && return 0');
339
+ lines.push(' done');
340
+ lines.push(' echo ""');
341
+ lines.push('}');
342
+ lines.push('');
343
+ lines.push('is_valid_status() {');
344
+ lines.push(' local status="$1"');
345
+ lines.push(' for s in $WORKFLOW_STATUSES; do');
346
+ lines.push(' [ "$s" = "$status" ] && return 0');
347
+ lines.push(' done');
348
+ lines.push(' return 1');
349
+ lines.push('}');
350
+ return lines.join('\n') + '\n';
351
+ }
352
+ }
353
+ export default WorkflowEngine;
package/index.html ADDED
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="h-full bg-gray-950" data-theme="neon-glass">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Orbital Command</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
10
+ </head>
11
+ <body class="h-full">
12
+ <div id="root" class="h-full"></div>
13
+ <script type="module" src="/src/main.tsx"></script>
14
+ </body>
15
+ </html>
package/package.json ADDED
@@ -0,0 +1,110 @@
1
+ {
2
+ "name": "orbital-command",
3
+ "version": "0.1.0",
4
+ "description": "Orbital Command — mission control dashboard for Claude Code projects",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "claude-code",
9
+ "project-management",
10
+ "kanban",
11
+ "mission-control",
12
+ "dashboard",
13
+ "workflow",
14
+ "ai-agents",
15
+ "scope-management",
16
+ "sprint",
17
+ "devtools"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/SakaraLabs/orbital-command.git"
22
+ },
23
+ "homepage": "https://github.com/SakaraLabs/orbital-command#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/SakaraLabs/orbital-command/issues"
26
+ },
27
+ "bin": {
28
+ "orbital": "bin/orbital.js"
29
+ },
30
+ "files": [
31
+ "bin/",
32
+ "dist/",
33
+ "server/",
34
+ "shared/",
35
+ "src/",
36
+ "templates/",
37
+ "schemas/",
38
+ "scripts/",
39
+ "index.html",
40
+ "vite.config.ts",
41
+ "tailwind.config.js",
42
+ "postcss.config.js",
43
+ "tsconfig.json",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "scripts": {
48
+ "dev": "orbital dev",
49
+ "dev:local": "trap 'lsof -ti:4444,4445 | xargs kill 2>/dev/null' EXIT; concurrently -n server,client -c blue,green \"npm run dev:server\" \"npm run dev:client\"",
50
+ "dev:server": "tsx watch server/index.ts",
51
+ "dev:client": "vite",
52
+ "build": "./node_modules/.bin/vite build",
53
+ "build:server": "./node_modules/.bin/tsc -p tsconfig.server.json",
54
+ "typecheck": "./node_modules/.bin/tsc --noEmit && ./node_modules/.bin/tsc --noEmit -p tsconfig.server.json",
55
+ "postinstall": "node scripts/postinstall.js",
56
+ "prepublishOnly": "npm run typecheck && npm run build && npm run build:server",
57
+ "preview": "vite preview"
58
+ },
59
+ "dependencies": {
60
+ "@dnd-kit/core": "^6.3.1",
61
+ "@dnd-kit/sortable": "^10.0.0",
62
+ "@dnd-kit/utilities": "^3.2.2",
63
+ "@radix-ui/react-dialog": "^1.1.4",
64
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
65
+ "@radix-ui/react-popover": "^1.1.15",
66
+ "@radix-ui/react-scroll-area": "^1.2.2",
67
+ "@radix-ui/react-select": "^2.1.4",
68
+ "@radix-ui/react-separator": "^1.1.1",
69
+ "@radix-ui/react-slot": "^1.1.1",
70
+ "@radix-ui/react-tabs": "^1.1.2",
71
+ "@radix-ui/react-tooltip": "^1.1.6",
72
+ "@xyflow/react": "^12.10.1",
73
+ "better-sqlite3": "^12.0.0",
74
+ "chokidar": "^4.0.3",
75
+ "class-variance-authority": "^0.7.1",
76
+ "clsx": "^2.1.1",
77
+ "date-fns": "^4.1.0",
78
+ "express": "^4.21.2",
79
+ "framer-motion": "^11.15.0",
80
+ "gray-matter": "^4.0.3",
81
+ "lucide-react": "^0.577.0",
82
+ "react": "^18.3.1",
83
+ "react-dom": "^18.3.1",
84
+ "react-markdown": "^10.1.0",
85
+ "react-router-dom": "^6.30.2",
86
+ "recharts": "^2.15.0",
87
+ "remark-gfm": "^4.0.1",
88
+ "socket.io": "^4.8.1",
89
+ "socket.io-client": "^4.8.1",
90
+ "tailwind-merge": "^2.6.0",
91
+ "tsx": "^4.19.2",
92
+ "vite": "^6.0.7"
93
+ },
94
+ "devDependencies": {
95
+ "@types/better-sqlite3": "^7.6.12",
96
+ "@types/express": "^5.0.0",
97
+ "@types/react": "^18.3.18",
98
+ "@types/react-dom": "^18.3.5",
99
+ "@vitejs/plugin-react": "^4.7.0",
100
+ "autoprefixer": "^10.4.27",
101
+ "concurrently": "^9.2.1",
102
+ "postcss": "^8.5.8",
103
+ "tailwindcss": "^3.4.19",
104
+ "tailwindcss-animate": "^1.0.7",
105
+ "typescript": "^5.7.3"
106
+ },
107
+ "engines": {
108
+ "node": ">=18.0.0"
109
+ }
110
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://orbital-command.dev/schemas/orbital.config.schema.json",
4
+ "title": "Orbital Command Configuration",
5
+ "description": "Configuration file for Orbital Command — mission control for Claude Code projects",
6
+ "type": "object",
7
+ "properties": {
8
+ "projectName": { "type": "string", "description": "Display name in dashboard header", "default": "My Project" },
9
+ "scopesDir": { "type": "string", "description": "Directory for scope documents (relative to project root)", "default": "scopes" },
10
+ "eventsDir": { "type": "string", "description": "Directory for event bus files (relative to project root)", "default": ".claude/orbital-events" },
11
+ "dbDir": { "type": "string", "description": "Directory for SQLite database (relative to project root)", "default": ".claude/orbital" },
12
+ "configDir": { "type": "string", "description": "Directory for workflow config + manifest (relative to project root)", "default": ".claude/config" },
13
+ "serverPort": { "type": "integer", "description": "API server port", "default": 4444, "minimum": 1, "maximum": 65535 },
14
+ "clientPort": { "type": "integer", "description": "Vite dev server port", "default": 4445, "minimum": 1, "maximum": 65535 },
15
+ "terminal": {
16
+ "type": "object",
17
+ "properties": {
18
+ "adapter": { "type": "string", "enum": ["auto", "iterm2", "subprocess", "none"], "default": "auto", "description": "Terminal adapter for session launching" },
19
+ "profilePrefix": { "type": "string", "default": "Orbital", "description": "Prefix for iTerm2 dynamic profile names" }
20
+ },
21
+ "additionalProperties": false
22
+ },
23
+ "claude": {
24
+ "type": "object",
25
+ "properties": {
26
+ "executable": { "type": "string", "default": "claude", "description": "Path to Claude Code CLI executable" },
27
+ "flags": { "type": "array", "items": { "type": "string" }, "default": ["--dangerously-skip-permissions"], "description": "Default flags for Claude Code sessions" }
28
+ },
29
+ "additionalProperties": false
30
+ },
31
+ "commands": {
32
+ "type": "object",
33
+ "description": "Build/test/lint commands. Set to null to skip.",
34
+ "properties": {
35
+ "typeCheck": { "type": ["string", "null"], "default": null },
36
+ "lint": { "type": ["string", "null"], "default": null },
37
+ "build": { "type": ["string", "null"], "default": null },
38
+ "test": { "type": ["string", "null"], "default": null },
39
+ "validateTemplates": { "type": ["string", "null"], "default": null },
40
+ "validateDocs": { "type": ["string", "null"], "default": null },
41
+ "checkRules": { "type": ["string", "null"], "default": null }
42
+ },
43
+ "additionalProperties": false
44
+ },
45
+ "categories": {
46
+ "type": "array",
47
+ "items": { "type": "string" },
48
+ "default": ["feature", "bugfix", "refactor", "infrastructure", "docs"],
49
+ "description": "Scope categories shown in the dashboard"
50
+ },
51
+ "agents": {
52
+ "type": "array",
53
+ "description": "Agent definitions for the dashboard and agent system",
54
+ "items": {
55
+ "type": "object",
56
+ "required": ["id", "label", "emoji", "color"],
57
+ "properties": {
58
+ "id": { "type": "string", "description": "Agent identifier (lowercase, kebab-case)" },
59
+ "label": { "type": "string", "description": "Display name" },
60
+ "emoji": { "type": "string", "description": "Emoji icon for the agent" },
61
+ "color": { "type": "string", "description": "Hex color for dashboard display" }
62
+ },
63
+ "additionalProperties": false
64
+ }
65
+ },
66
+ "logLevel": {
67
+ "type": "string",
68
+ "enum": ["debug", "info", "warn", "error"],
69
+ "default": "info",
70
+ "description": "Server log verbosity level"
71
+ },
72
+ "healthChecks": {
73
+ "type": "object",
74
+ "description": "Health check URLs by environment",
75
+ "properties": {
76
+ "staging": { "type": "string", "format": "uri" },
77
+ "production": { "type": "string", "format": "uri" }
78
+ },
79
+ "additionalProperties": { "type": "string", "format": "uri" }
80
+ }
81
+ },
82
+ "additionalProperties": false
83
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Safety net for esbuild's platform-specific binary.
4
+ //
5
+ // esbuild (a transitive dependency of Vite) ships as a stub that downloads
6
+ // its native binary via postinstall. If that step was skipped or failed
7
+ // silently, this script re-triggers the download.
8
+
9
+ import { existsSync } from 'fs';
10
+ import { execFileSync } from 'child_process';
11
+ import path from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const packageRoot = path.resolve(__dirname, '..');
16
+ const esbuildInstall = path.join(packageRoot, 'node_modules', 'esbuild', 'install.js');
17
+
18
+ if (existsSync(esbuildInstall)) {
19
+ try {
20
+ execFileSync('node', [esbuildInstall], { stdio: 'pipe' });
21
+ } catch {
22
+ // Already installed or not applicable
23
+ }
24
+ }
@@ -0,0 +1,20 @@
1
+ #!/bin/bash
2
+ # Start Orbital Command
3
+ # Usage: ./scripts/start.sh
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
9
+
10
+ cd "$PROJECT_DIR"
11
+
12
+ # Install deps if needed
13
+ if [ ! -d "node_modules" ]; then
14
+ echo "[Orbital] Installing dependencies..."
15
+ npm install
16
+ fi
17
+
18
+ # Start both server and client
19
+ echo "[Orbital] Starting Orbital Command..."
20
+ npm run dev
@@ -0,0 +1,41 @@
1
+ import type { TerminalAdapter } from './terminal-adapter.js';
2
+ import { getConfig } from '../config.js';
3
+ import { ITerm2Adapter, isITerm2Available } from './iterm2-adapter.js';
4
+ import { SubprocessAdapter } from './subprocess-adapter.js';
5
+
6
+ export type { TerminalAdapter, LaunchOptions, CategorizedLaunchOptions, WindowCategory } from './terminal-adapter.js';
7
+
8
+ let _adapter: TerminalAdapter | null = null;
9
+
10
+ /**
11
+ * Get the terminal adapter singleton.
12
+ * Auto-detects based on config and platform:
13
+ * - "iterm2" → iTerm2 adapter (macOS only)
14
+ * - "subprocess" → cross-platform subprocess fallback
15
+ * - "none" → no-op adapter (for headless/CI)
16
+ * - "auto" → iTerm2 if available, else subprocess
17
+ */
18
+ export function getTerminalAdapter(): TerminalAdapter {
19
+ if (_adapter) return _adapter;
20
+
21
+ const preference = getConfig().terminal.adapter;
22
+
23
+ if (preference === 'none') {
24
+ _adapter = { launch: async () => {}, launchCategorized: async () => {} };
25
+ return _adapter;
26
+ }
27
+
28
+ if (preference === 'iterm2' || preference === 'auto') {
29
+ try {
30
+ if (preference === 'iterm2' || isITerm2Available()) {
31
+ _adapter = new ITerm2Adapter();
32
+ return _adapter;
33
+ }
34
+ } catch {
35
+ // iTerm2 adapter not available — fall through
36
+ }
37
+ }
38
+
39
+ _adapter = new SubprocessAdapter();
40
+ return _adapter;
41
+ }