orbital-command 0.3.0 → 1.0.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 (160) hide show
  1. package/README.md +67 -42
  2. package/bin/commands/config.js +19 -0
  3. package/bin/commands/events.js +40 -0
  4. package/bin/commands/launch.js +126 -0
  5. package/bin/commands/manifest.js +283 -0
  6. package/bin/commands/registry.js +104 -0
  7. package/bin/commands/update.js +24 -0
  8. package/bin/lib/helpers.js +229 -0
  9. package/bin/orbital.js +95 -870
  10. package/dist/assets/Landing-CfQdHR0N.js +11 -0
  11. package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
  12. package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
  13. package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
  14. package/dist/assets/Settings-DLcZwbCT.js +12 -0
  15. package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
  16. package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
  17. package/dist/assets/{arrow-down-CPy85_J6.js → arrow-down-DVPp6_qp.js} +1 -1
  18. package/dist/assets/bot-NFaJBDn_.js +6 -0
  19. package/dist/assets/{charts-DbDg0Psc.js → charts-LGLb8hyU.js} +1 -1
  20. package/dist/assets/{circle-x-Cwz6ZQDV.js → circle-x-IsFCkBZu.js} +1 -1
  21. package/dist/assets/{file-text-C46Xr65c.js → file-text-J1cebZXF.js} +1 -1
  22. package/dist/assets/{globe-Cn2yNZUD.js → globe-WzeyHsUc.js} +1 -1
  23. package/dist/assets/index-BdJ57EhC.css +1 -0
  24. package/dist/assets/index-o4ScMAuR.js +349 -0
  25. package/dist/assets/{key-OPaNTWJ5.js → key-CKR8JJSj.js} +1 -1
  26. package/dist/assets/{minus-GMsbpKym.js → minus-CHBsJyjp.js} +1 -1
  27. package/dist/assets/radio-xqZaR-Uk.js +6 -0
  28. package/dist/assets/rocket-D_xvvNG6.js +6 -0
  29. package/dist/assets/{shield-DwAFkDYI.js → shield-TdB1yv_a.js} +1 -1
  30. package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
  31. package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
  32. package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
  33. package/dist/assets/zap-C9wqYMpl.js +6 -0
  34. package/dist/index.html +3 -3
  35. package/dist/server/server/__tests__/data-routes.test.js +2 -0
  36. package/dist/server/server/__tests__/scope-routes.test.js +1 -0
  37. package/dist/server/server/config-migrator.js +0 -3
  38. package/dist/server/server/config.js +35 -6
  39. package/dist/server/server/database.js +0 -22
  40. package/dist/server/server/index.js +26 -814
  41. package/dist/server/server/init.js +32 -399
  42. package/dist/server/server/launch.js +1 -1
  43. package/dist/server/server/parsers/event-parser.js +4 -1
  44. package/dist/server/server/project-context.js +19 -9
  45. package/dist/server/server/project-manager.js +6 -6
  46. package/dist/server/server/routes/aggregate-routes.js +871 -0
  47. package/dist/server/server/routes/config-routes.js +41 -88
  48. package/dist/server/server/routes/data-routes.js +5 -15
  49. package/dist/server/server/routes/dispatch-routes.js +24 -8
  50. package/dist/server/server/routes/manifest-routes.js +1 -1
  51. package/dist/server/server/routes/scope-routes.js +10 -7
  52. package/dist/server/server/schema.js +1 -0
  53. package/dist/server/server/services/batch-orchestrator.js +17 -3
  54. package/dist/server/server/services/config-service.js +10 -1
  55. package/dist/server/server/services/scope-service.js +7 -7
  56. package/dist/server/server/services/sprint-orchestrator.js +24 -11
  57. package/dist/server/server/services/sprint-service.js +2 -2
  58. package/dist/server/server/uninstall.js +195 -0
  59. package/dist/server/server/update.js +212 -0
  60. package/dist/server/server/utils/dispatch-utils.js +8 -6
  61. package/dist/server/server/utils/flag-builder.js +54 -0
  62. package/dist/server/server/utils/json-fields.js +14 -0
  63. package/dist/server/server/utils/json-fields.test.js +73 -0
  64. package/dist/server/server/utils/route-helpers.js +37 -0
  65. package/dist/server/server/utils/route-helpers.test.js +115 -0
  66. package/dist/server/server/watchers/event-watcher.js +28 -13
  67. package/dist/server/server/wizard/config-editor.js +4 -4
  68. package/dist/server/server/wizard/doctor.js +2 -2
  69. package/dist/server/server/wizard/index.js +224 -39
  70. package/dist/server/server/wizard/phases/welcome.js +1 -4
  71. package/dist/server/server/wizard/ui.js +6 -7
  72. package/dist/server/shared/api-types.js +80 -1
  73. package/dist/server/shared/workflow-engine.js +1 -1
  74. package/package.json +20 -20
  75. package/schemas/orbital.config.schema.json +1 -19
  76. package/scripts/postinstall.js +6 -42
  77. package/scripts/release.sh +53 -0
  78. package/server/__tests__/data-routes.test.ts +2 -0
  79. package/server/__tests__/scope-routes.test.ts +1 -0
  80. package/server/config-migrator.ts +0 -3
  81. package/server/config.ts +39 -11
  82. package/server/database.ts +0 -26
  83. package/server/global-config.ts +4 -0
  84. package/server/index.ts +29 -894
  85. package/server/init.ts +32 -443
  86. package/server/launch.ts +1 -1
  87. package/server/parsers/event-parser.ts +4 -1
  88. package/server/project-context.ts +26 -10
  89. package/server/project-manager.ts +5 -6
  90. package/server/routes/aggregate-routes.ts +968 -0
  91. package/server/routes/config-routes.ts +41 -81
  92. package/server/routes/data-routes.ts +7 -16
  93. package/server/routes/dispatch-routes.ts +29 -8
  94. package/server/routes/manifest-routes.ts +1 -1
  95. package/server/routes/scope-routes.ts +12 -7
  96. package/server/schema.ts +1 -0
  97. package/server/services/batch-orchestrator.ts +18 -2
  98. package/server/services/config-service.ts +10 -1
  99. package/server/services/scope-service.ts +6 -6
  100. package/server/services/sprint-orchestrator.ts +24 -9
  101. package/server/services/sprint-service.ts +2 -2
  102. package/server/uninstall.ts +214 -0
  103. package/server/update.ts +263 -0
  104. package/server/utils/dispatch-utils.ts +8 -6
  105. package/server/utils/flag-builder.ts +56 -0
  106. package/server/utils/json-fields.test.ts +83 -0
  107. package/server/utils/json-fields.ts +14 -0
  108. package/server/utils/route-helpers.test.ts +144 -0
  109. package/server/utils/route-helpers.ts +38 -0
  110. package/server/watchers/event-watcher.ts +24 -12
  111. package/server/wizard/config-editor.ts +4 -4
  112. package/server/wizard/doctor.ts +2 -2
  113. package/server/wizard/index.ts +291 -40
  114. package/server/wizard/phases/welcome.ts +1 -5
  115. package/server/wizard/ui.ts +6 -7
  116. package/shared/api-types.ts +106 -0
  117. package/shared/workflow-engine.ts +1 -1
  118. package/templates/agents/QUICK-REFERENCE.md +1 -0
  119. package/templates/agents/README.md +1 -0
  120. package/templates/agents/SKILL-TRIGGERS.md +11 -0
  121. package/templates/agents/green-team/deep-dive.md +361 -0
  122. package/templates/hooks/end-session.sh +1 -0
  123. package/templates/hooks/init-session.sh +1 -0
  124. package/templates/hooks/scope-commit-logger.sh +2 -2
  125. package/templates/hooks/scope-create-gate.sh +2 -4
  126. package/templates/hooks/scope-gate.sh +4 -6
  127. package/templates/hooks/scope-helpers.sh +10 -1
  128. package/templates/hooks/scope-lifecycle-gate.sh +14 -5
  129. package/templates/hooks/scope-prepare.sh +1 -1
  130. package/templates/hooks/scope-transition.sh +14 -6
  131. package/templates/hooks/time-tracker.sh +2 -5
  132. package/templates/orbital.config.json +1 -4
  133. package/templates/presets/development.json +4 -4
  134. package/templates/presets/gitflow.json +7 -0
  135. package/templates/prompts/README.md +23 -0
  136. package/templates/prompts/deep-dive-audit.md +94 -0
  137. package/templates/quick/rules.md +56 -5
  138. package/templates/skills/git-commit/SKILL.md +21 -6
  139. package/templates/skills/git-dev/SKILL.md +8 -4
  140. package/templates/skills/git-main/SKILL.md +8 -4
  141. package/templates/skills/git-production/SKILL.md +6 -3
  142. package/templates/skills/git-staging/SKILL.md +6 -3
  143. package/templates/skills/scope-fix-review/SKILL.md +8 -4
  144. package/templates/skills/scope-implement/SKILL.md +13 -5
  145. package/templates/skills/scope-post-review/SKILL.md +16 -4
  146. package/templates/skills/scope-pre-review/SKILL.md +6 -2
  147. package/dist/assets/PrimitivesConfig-CrmQXYh4.js +0 -32
  148. package/dist/assets/QualityGates-BbasOsF3.js +0 -21
  149. package/dist/assets/SessionTimeline-CGeJsVvy.js +0 -1
  150. package/dist/assets/Settings-oiM496mc.js +0 -12
  151. package/dist/assets/SourceControl-B1fP2nJL.js +0 -41
  152. package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +0 -74
  153. package/dist/assets/formatDistanceToNow-BMqsSP44.js +0 -1
  154. package/dist/assets/index-Aj4sV8Al.css +0 -1
  155. package/dist/assets/index-Bc9dK3MW.js +0 -354
  156. package/dist/assets/useWorkflowEditor-BJkTX_NR.js +0 -16
  157. package/dist/assets/zap-DfbUoOty.js +0 -11
  158. package/dist/server/server/services/telemetry-service.js +0 -143
  159. package/server/services/telemetry-service.ts +0 -195
  160. /package/{shared/default-workflow.json → templates/presets/default.json} +0 -0
@@ -2,4 +2,83 @@
2
2
  //
3
3
  // Types shared between server and client. Single source of truth for
4
4
  // enums/unions that were previously duplicated across layers.
5
- export {};
5
+ export const DEFAULT_DISPATCH_FLAGS = {
6
+ permissionMode: 'bypass',
7
+ verbose: true,
8
+ allowedTools: [],
9
+ disallowedTools: [],
10
+ appendSystemPrompt: '',
11
+ outputFormat: '',
12
+ noMarkdown: false,
13
+ printMode: false,
14
+ };
15
+ export const DEFAULT_DISPATCH_CONFIG = {
16
+ staleTimeoutMinutes: 10,
17
+ maxBatchSize: 20,
18
+ maxConcurrent: 0,
19
+ envVars: {},
20
+ };
21
+ // ─── Validation ─────────────────────────────────────────────
22
+ export const VALID_PERMISSION_MODES = ['bypass', 'default', 'plan', 'acceptEdits'];
23
+ export const VALID_OUTPUT_FORMATS = ['', 'text', 'json', 'stream-json'];
24
+ export const VALID_TERMINAL_ADAPTERS = ['auto', 'iterm2', 'subprocess', 'none'];
25
+ const SAFE_TOOL_NAME = /^[a-zA-Z0-9_:.-]+$/;
26
+ const SAFE_ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
27
+ export function validateToolName(name) {
28
+ return SAFE_TOOL_NAME.test(name);
29
+ }
30
+ export function validateEnvKey(key) {
31
+ return SAFE_ENV_KEY.test(key);
32
+ }
33
+ export function validateDispatchFlags(flags) {
34
+ if (flags.permissionMode !== undefined && !VALID_PERMISSION_MODES.includes(flags.permissionMode)) {
35
+ return `Invalid permissionMode: ${flags.permissionMode}`;
36
+ }
37
+ if (flags.outputFormat !== undefined && !VALID_OUTPUT_FORMATS.includes(flags.outputFormat)) {
38
+ return `Invalid outputFormat: ${flags.outputFormat}`;
39
+ }
40
+ if (flags.allowedTools !== undefined) {
41
+ if (!Array.isArray(flags.allowedTools))
42
+ return 'allowedTools must be an array';
43
+ for (const t of flags.allowedTools) {
44
+ if (typeof t !== 'string' || !validateToolName(t))
45
+ return `Invalid tool name: ${t}`;
46
+ }
47
+ }
48
+ if (flags.disallowedTools !== undefined) {
49
+ if (!Array.isArray(flags.disallowedTools))
50
+ return 'disallowedTools must be an array';
51
+ for (const t of flags.disallowedTools) {
52
+ if (typeof t !== 'string' || !validateToolName(t))
53
+ return `Invalid tool name: ${t}`;
54
+ }
55
+ }
56
+ if (flags.appendSystemPrompt !== undefined && typeof flags.appendSystemPrompt !== 'string') {
57
+ return 'appendSystemPrompt must be a string';
58
+ }
59
+ return null;
60
+ }
61
+ export function validateDispatchConfig(config) {
62
+ if (config.terminalAdapter !== undefined && !VALID_TERMINAL_ADAPTERS.includes(config.terminalAdapter)) {
63
+ return `Invalid terminalAdapter: ${config.terminalAdapter}`;
64
+ }
65
+ if (config.staleTimeoutMinutes !== undefined && (typeof config.staleTimeoutMinutes !== 'number' || config.staleTimeoutMinutes < 1)) {
66
+ return 'staleTimeoutMinutes must be a positive number';
67
+ }
68
+ if (config.maxBatchSize !== undefined && (typeof config.maxBatchSize !== 'number' || config.maxBatchSize < 1)) {
69
+ return 'maxBatchSize must be a positive number';
70
+ }
71
+ if (config.maxConcurrent !== undefined && (typeof config.maxConcurrent !== 'number' || config.maxConcurrent < 0)) {
72
+ return 'maxConcurrent must be a non-negative number';
73
+ }
74
+ if (config.envVars !== undefined) {
75
+ if (typeof config.envVars !== 'object' || config.envVars === null || Array.isArray(config.envVars)) {
76
+ return 'envVars must be an object';
77
+ }
78
+ for (const key of Object.keys(config.envVars)) {
79
+ if (!validateEnvKey(key))
80
+ return `Invalid env var key: ${key}`;
81
+ }
82
+ }
83
+ return null;
84
+ }
@@ -313,7 +313,7 @@ export class WorkflowEngine {
313
313
  continue;
314
314
  // Generate aliases for deployment-group targets (dev, staging, production)
315
315
  const group = targetList.group;
316
- if (group === 'deployment') {
316
+ if (group?.startsWith('deploy')) {
317
317
  const sessionKey = targetList.sessionKey ?? '';
318
318
  lines.push(` "to-${edge.to}:${edge.from}:${edge.to}:${sessionKey}"`);
319
319
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orbital-command",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "Orbital Command — mission control dashboard for Claude Code projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -47,8 +47,10 @@
47
47
  "build": "./node_modules/.bin/vite build",
48
48
  "build:server": "./node_modules/.bin/tsc -p tsconfig.server.json",
49
49
  "typecheck": "./node_modules/.bin/tsc --noEmit && ./node_modules/.bin/tsc --noEmit -p tsconfig.server.json",
50
+ "validate": "npm run typecheck && npm run test && npm run build && npm run build:server",
51
+ "release": "./scripts/release.sh",
50
52
  "postinstall": "node scripts/postinstall.js",
51
- "prepublishOnly": "npm run typecheck && npm run build && npm run build:server",
53
+ "prepublishOnly": "npm run validate",
52
54
  "preview": "vite preview",
53
55
  "test": "vitest run",
54
56
  "test:unit": "vitest run --project unit",
@@ -58,31 +60,38 @@
58
60
  },
59
61
  "dependencies": {
60
62
  "@clack/prompts": "^1.2.0",
63
+ "better-sqlite3": "^12.0.0",
64
+ "chokidar": "^4.0.3",
65
+ "express": "^4.21.2",
66
+ "gray-matter": "^4.0.3",
67
+ "picocolors": "^1.1.1",
68
+ "socket.io": "^4.8.1"
69
+ },
70
+ "devDependencies": {
61
71
  "@dnd-kit/core": "^6.3.1",
62
72
  "@dnd-kit/sortable": "^10.0.0",
63
73
  "@dnd-kit/utilities": "^3.2.2",
64
74
  "@radix-ui/react-dialog": "^1.1.4",
65
- "@radix-ui/react-dropdown-menu": "^2.1.4",
66
75
  "@radix-ui/react-popover": "^1.1.15",
67
76
  "@radix-ui/react-scroll-area": "^1.2.2",
68
- "@radix-ui/react-select": "^2.1.4",
69
77
  "@radix-ui/react-separator": "^1.1.1",
70
78
  "@radix-ui/react-slot": "^1.1.1",
71
79
  "@radix-ui/react-tabs": "^1.1.2",
72
80
  "@radix-ui/react-tooltip": "^1.1.6",
81
+ "@types/better-sqlite3": "^7.6.12",
82
+ "@types/express": "^5.0.0",
83
+ "@types/react": "^18.3.18",
84
+ "@types/react-dom": "^18.3.5",
85
+ "@types/supertest": "^7.2.0",
73
86
  "@vitejs/plugin-react": "^4.7.0",
74
87
  "@xyflow/react": "^12.10.1",
75
88
  "autoprefixer": "^10.4.27",
76
- "better-sqlite3": "^12.0.0",
77
- "chokidar": "^4.0.3",
78
89
  "class-variance-authority": "^0.7.1",
79
90
  "clsx": "^2.1.1",
91
+ "concurrently": "^9.2.1",
80
92
  "date-fns": "^4.1.0",
81
- "express": "^4.21.2",
82
93
  "framer-motion": "^11.15.0",
83
- "gray-matter": "^4.0.3",
84
94
  "lucide-react": "^0.577.0",
85
- "picocolors": "^1.1.1",
86
95
  "postcss": "^8.5.8",
87
96
  "react": "^18.3.1",
88
97
  "react-dom": "^18.3.1",
@@ -90,23 +99,14 @@
90
99
  "react-router-dom": "^6.30.2",
91
100
  "recharts": "^2.15.0",
92
101
  "remark-gfm": "^4.0.1",
93
- "socket.io": "^4.8.1",
94
102
  "socket.io-client": "^4.8.1",
103
+ "supertest": "^7.2.2",
95
104
  "tailwind-merge": "^2.6.0",
96
105
  "tailwindcss": "^3.4.19",
97
106
  "tailwindcss-animate": "^1.0.7",
98
107
  "tsx": "^4.19.2",
99
- "vite": "^6.0.7"
100
- },
101
- "devDependencies": {
102
- "@types/better-sqlite3": "^7.6.12",
103
- "@types/express": "^5.0.0",
104
- "@types/react": "^18.3.18",
105
- "@types/react-dom": "^18.3.5",
106
- "@types/supertest": "^7.2.0",
107
- "concurrently": "^9.2.1",
108
- "supertest": "^7.2.2",
109
108
  "typescript": "^5.7.3",
109
+ "vite": "^6.0.7",
110
110
  "vitest": "^4.1.3"
111
111
  },
112
112
  "engines": {
@@ -35,10 +35,7 @@
35
35
  "typeCheck": { "type": ["string", "null"], "default": null },
36
36
  "lint": { "type": ["string", "null"], "default": null },
37
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 }
38
+ "test": { "type": ["string", "null"], "default": null }
42
39
  },
43
40
  "additionalProperties": false
44
41
  },
@@ -77,21 +74,6 @@
77
74
  "production": { "type": "string", "format": "uri" }
78
75
  },
79
76
  "additionalProperties": { "type": "string", "format": "uri" }
80
- },
81
- "telemetry": {
82
- "type": "object",
83
- "description": "Session telemetry — uploads Claude session JSONL files to a remote endpoint. Can also be disabled via ORBITAL_TELEMETRY=false env var.",
84
- "properties": {
85
- "enabled": { "type": "boolean", "default": true, "description": "Enable session telemetry upload" },
86
- "url": { "type": "string", "description": "Base URL of the telemetry receiver (Cloudflare Worker)" },
87
- "headers": {
88
- "type": "object",
89
- "additionalProperties": { "type": "string" },
90
- "default": {},
91
- "description": "Custom headers sent with upload requests (e.g. Authorization)"
92
- }
93
- },
94
- "additionalProperties": false
95
77
  }
96
78
  },
97
79
  "additionalProperties": false
@@ -9,14 +9,13 @@
9
9
  * 3. Otherwise, prints a banner with next steps.
10
10
  */
11
11
 
12
- import { existsSync, readFileSync } from 'fs';
12
+ import { existsSync } from 'fs';
13
13
  import { execFileSync } from 'child_process';
14
14
  import path from 'path';
15
15
  import { fileURLToPath } from 'url';
16
16
 
17
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
18
  const packageRoot = path.resolve(__dirname, '..');
19
- const orbitalHome = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.orbital');
20
19
 
21
20
  // ─── 1. Esbuild safety net ─────────────────────────────────────
22
21
 
@@ -29,44 +28,9 @@ if (existsSync(esbuildInstall)) {
29
28
  }
30
29
  }
31
30
 
32
- // ─── 2. Setup wizard or banner ──────────────────────────────────
31
+ // ─── 2. Post-install banner ─────────────────────────────────────
33
32
 
34
- const isInteractive = process.stdout.isTTY && !process.env.CI;
35
- const alreadySetUp = existsSync(path.join(orbitalHome, 'config.json'));
36
-
37
- if (isInteractive && !alreadySetUp) {
38
- // Launch the Phase 1 setup wizard
39
- try {
40
- const pkg = JSON.parse(readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
41
- const version = pkg.version || '0.0.0';
42
-
43
- let wizard;
44
- try {
45
- wizard = await import('../dist/server/server/wizard/index.js');
46
- } catch {
47
- try {
48
- wizard = await import('../server/wizard/index.js');
49
- } catch {
50
- // Wizard module not available — fall through to banner
51
- wizard = null;
52
- }
53
- }
54
-
55
- if (wizard) {
56
- await wizard.runSetupWizard(version);
57
- } else {
58
- printBanner();
59
- }
60
- } catch {
61
- printBanner();
62
- }
63
- } else if (!alreadySetUp) {
64
- printBanner();
65
- }
66
-
67
- function printBanner() {
68
- console.log('');
69
- console.log(' Orbital Command installed.');
70
- console.log(' Run `orbital` in your project directory to get started.');
71
- console.log('');
72
- }
33
+ console.log('');
34
+ console.log(' Orbital Command installed.');
35
+ console.log(' Run \x1b[36morbital\x1b[0m in your project directory to get started.');
36
+ console.log('');
@@ -0,0 +1,53 @@
1
+ #!/bin/bash
2
+ # Release Orbital Command
3
+ # Usage: ./scripts/release.sh [patch|minor|major]
4
+ #
5
+ # Runs the full validation pipeline, bumps version, tags, and pushes.
6
+ # The tag push triggers publish.yml which publishes to npm.
7
+
8
+ set -e
9
+
10
+ BUMP="${1:-patch}"
11
+
12
+ if [[ "$BUMP" != "patch" && "$BUMP" != "minor" && "$BUMP" != "major" ]]; then
13
+ echo "Usage: ./scripts/release.sh [patch|minor|major]"
14
+ echo " Default: patch"
15
+ exit 1
16
+ fi
17
+
18
+ # Ensure clean working tree
19
+ if [[ -n "$(git status --porcelain)" ]]; then
20
+ echo "Error: working tree is dirty. Commit or stash changes first."
21
+ exit 1
22
+ fi
23
+
24
+ # Ensure we're on main
25
+ BRANCH="$(git branch --show-current)"
26
+ if [[ "$BRANCH" != "main" ]]; then
27
+ echo "Error: releases must be cut from main (currently on $BRANCH)"
28
+ exit 1
29
+ fi
30
+
31
+ # Pull latest
32
+ echo "Pulling latest from origin/main..."
33
+ git pull --ff-only origin main
34
+
35
+ # Validate
36
+ echo ""
37
+ echo "Running full validation pipeline..."
38
+ echo ""
39
+ npm run validate
40
+
41
+ # Bump version and create tag
42
+ echo ""
43
+ echo "Bumping $BUMP version..."
44
+ npm version "$BUMP"
45
+
46
+ # Push commit and tag
47
+ echo ""
48
+ echo "Pushing to origin..."
49
+ git push origin main --follow-tags
50
+
51
+ VERSION="$(node -p "require('./package.json').version")"
52
+ echo ""
53
+ echo "Released v$VERSION — publish.yml will handle npm publish."
@@ -9,6 +9,7 @@ import { WorkflowEngine } from '../../shared/workflow-engine.js';
9
9
  import { createTestDb } from './helpers/db.js';
10
10
  import { createMockEmitter } from './helpers/mock-emitter.js';
11
11
  import { DEFAULT_CONFIG } from '../../shared/__fixtures__/workflow-configs.js';
12
+ import { loadConfig } from '../config.js';
12
13
  import type Database from 'better-sqlite3';
13
14
 
14
15
  describe('data-routes', () => {
@@ -42,6 +43,7 @@ describe('data-routes', () => {
42
43
  engine,
43
44
  projectRoot: '/tmp/test-project',
44
45
  inferScopeStatus: vi.fn(),
46
+ config: loadConfig('/tmp/test-project'),
45
47
  });
46
48
 
47
49
  app = express();
@@ -69,6 +69,7 @@ describe('scope-routes', () => {
69
69
  projectRoot: '/tmp/test-project',
70
70
  projectName: 'Test',
71
71
  engine,
72
+ config: { claude: { dispatchFlags: { permissionMode: 'bypass', verbose: false, noMarkdown: false, printMode: false, outputFormat: null, allowedTools: [], disallowedTools: [], appendSystemPrompt: '' } }, dispatch: { envVars: {}, maxConcurrent: 5, maxBatchSize: 20, staleTimeoutMinutes: 10 } } as any,
72
73
  });
73
74
 
74
75
  app = express();
@@ -62,9 +62,6 @@ const NESTED_DEFAULTS: Record<string, Record<string, unknown>> = {
62
62
  lint: null,
63
63
  build: null,
64
64
  test: null,
65
- validateTemplates: null,
66
- validateDocs: null,
67
- checkRules: null,
68
65
  },
69
66
  };
70
67
 
package/server/config.ts CHANGED
@@ -14,6 +14,7 @@ export interface TerminalConfig {
14
14
  export interface ClaudeConfig {
15
15
  executable: string;
16
16
  flags: string[];
17
+ dispatchFlags: DispatchFlags;
17
18
  }
18
19
 
19
20
  export interface CommandsConfig {
@@ -21,13 +22,12 @@ export interface CommandsConfig {
21
22
  lint: string | null;
22
23
  build: string | null;
23
24
  test: string | null;
24
- validateTemplates: string | null;
25
- validateDocs: string | null;
26
- checkRules: string | null;
27
25
  }
28
26
 
29
- export type { AgentConfig } from '../shared/api-types.js';
30
- import type { AgentConfig } from '../shared/api-types.js';
27
+ export type { AgentConfig, DispatchFlags, DispatchConfig } from '../shared/api-types.js';
28
+ import type { AgentConfig, DispatchFlags, DispatchConfig } from '../shared/api-types.js';
29
+ import { DEFAULT_DISPATCH_FLAGS, DEFAULT_DISPATCH_CONFIG } from '../shared/api-types.js';
30
+ import { loadGlobalConfig as loadGlobal } from './global-config.js';
31
31
 
32
32
  export interface TelemetryConfig {
33
33
  enabled: boolean;
@@ -55,6 +55,9 @@ export interface OrbitalConfig {
55
55
  // Claude Code CLI
56
56
  claude: ClaudeConfig;
57
57
 
58
+ // Dispatch operational settings
59
+ dispatch: DispatchConfig;
60
+
58
61
  // Build/test commands
59
62
  commands: CommandsConfig;
60
63
 
@@ -86,19 +89,18 @@ const DEFAULT_CONFIG: Omit<OrbitalConfig, 'projectRoot'> = {
86
89
  claude: {
87
90
  executable: 'claude',
88
91
  flags: ['--dangerously-skip-permissions'],
92
+ dispatchFlags: DEFAULT_DISPATCH_FLAGS,
89
93
  },
94
+ dispatch: DEFAULT_DISPATCH_CONFIG,
90
95
  commands: {
91
96
  typeCheck: null,
92
97
  lint: null,
93
98
  build: null,
94
99
  test: null,
95
- validateTemplates: null,
96
- validateDocs: null,
97
- checkRules: null,
98
100
  },
99
101
  logLevel: 'info' as const,
100
102
  telemetry: {
101
- enabled: true,
103
+ enabled: false,
102
104
  url: '',
103
105
  headers: {},
104
106
  },
@@ -106,7 +108,6 @@ const DEFAULT_CONFIG: Omit<OrbitalConfig, 'projectRoot'> = {
106
108
  agents: [
107
109
  { id: 'attacker', label: 'Attacker', emoji: '\u{1F5E1}\u{FE0F}', color: '#ff1744' },
108
110
  { id: 'chaos', label: 'Chaos', emoji: '\u{1F4A5}', color: '#F97316' },
109
- { id: 'solana-expert', label: 'Solana Expert', emoji: '\u{26D3}\u{FE0F}', color: '#8B5CF6' },
110
111
  { id: 'frontend-designer', label: 'Frontend Designer', emoji: '\u{1F3A8}', color: '#EC4899' },
111
112
  { id: 'architect', label: 'Architect', emoji: '\u{1F3D7}\u{FE0F}', color: '#536dfe' },
112
113
  { id: 'rules-enforcer', label: 'Rules Enforcer', emoji: '\u{1F4CB}', color: '#6B7280' },
@@ -168,6 +169,15 @@ export function getClaudeSessionsDir(projectRoot: string): string {
168
169
  export function loadConfig(projectRoot?: string): OrbitalConfig {
169
170
  const root = projectRoot ?? resolveProjectRoot();
170
171
 
172
+ // Try loading edition overrides (e.g. edition.json at repo root)
173
+ const editionPath = path.join(root, 'edition.json');
174
+ let editionConfig: Record<string, unknown> = {};
175
+ if (fs.existsSync(editionPath)) {
176
+ try {
177
+ editionConfig = JSON.parse(fs.readFileSync(editionPath, 'utf-8'));
178
+ } catch { /* malformed edition.json — ignore */ }
179
+ }
180
+
171
181
  // Try loading user config
172
182
  const configPath = path.join(root, '.claude', 'orbital.config.json');
173
183
  let userConfig: Record<string, unknown> = {};
@@ -201,11 +211,26 @@ export function loadConfig(projectRoot?: string): OrbitalConfig {
201
211
  ...(userConfig.terminal as Partial<TerminalConfig> ?? {}),
202
212
  };
203
213
 
214
+ // Global settings — seed from ~/.orbital/config.json
215
+ let globalDispatchFlags = DEFAULT_DISPATCH_FLAGS;
216
+ let globalDispatch = DEFAULT_DISPATCH_CONFIG;
217
+ let globalTelemetry: Partial<TelemetryConfig> = {};
218
+ try {
219
+ const global = loadGlobal();
220
+ if (global.dispatchFlags) globalDispatchFlags = { ...DEFAULT_DISPATCH_FLAGS, ...global.dispatchFlags };
221
+ if (global.dispatch) globalDispatch = { ...DEFAULT_DISPATCH_CONFIG, ...global.dispatch };
222
+ if (global.telemetry) globalTelemetry = global.telemetry;
223
+ } catch { /* global config may not exist yet */ }
224
+
225
+ const userClaude = (userConfig.claude as Partial<ClaudeConfig>) ?? {};
204
226
  const claude: ClaudeConfig = {
205
227
  ...DEFAULT_CONFIG.claude,
206
- ...(userConfig.claude as Partial<ClaudeConfig> ?? {}),
228
+ ...userClaude,
229
+ dispatchFlags: globalDispatchFlags,
207
230
  };
208
231
 
232
+ const dispatch: DispatchConfig = globalDispatch;
233
+
209
234
  const commands: CommandsConfig = {
210
235
  ...DEFAULT_CONFIG.commands,
211
236
  ...(userConfig.commands as Partial<CommandsConfig> ?? {}),
@@ -217,6 +242,8 @@ export function loadConfig(projectRoot?: string): OrbitalConfig {
217
242
 
218
243
  const telemetry: TelemetryConfig = {
219
244
  ...DEFAULT_CONFIG.telemetry,
245
+ ...globalTelemetry,
246
+ ...(editionConfig.telemetry as Partial<TelemetryConfig> ?? {}),
220
247
  ...(userConfig.telemetry as Partial<TelemetryConfig> ?? {}),
221
248
  };
222
249
  if (process.env.ORBITAL_TELEMETRY === 'false') telemetry.enabled = false;
@@ -232,6 +259,7 @@ export function loadConfig(projectRoot?: string): OrbitalConfig {
232
259
  clientPort,
233
260
  terminal,
234
261
  claude,
262
+ dispatch,
235
263
  commands,
236
264
  logLevel,
237
265
  categories,
@@ -2,7 +2,6 @@ import Database from 'better-sqlite3';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
4
  import { SCHEMA_DDL } from './schema.js';
5
- import { getConfig } from './config.js';
6
5
  import { createLogger } from './utils/logger.js';
7
6
 
8
7
  const log = createLogger('database');
@@ -14,7 +13,6 @@ const log = createLogger('database');
14
13
  * Creates the directory if needed, applies schema DDL and migrations.
15
14
  *
16
15
  * Each call returns a NEW connection — callers manage their own lifecycle.
17
- * This is the multi-project replacement for the getDatabase() singleton.
18
16
  */
19
17
  export function openProjectDatabase(dbDir: string): Database.Database {
20
18
  fs.mkdirSync(dbDir, { recursive: true });
@@ -33,22 +31,6 @@ export function openProjectDatabase(dbDir: string): Database.Database {
33
31
  return database;
34
32
  }
35
33
 
36
- // ─── Singleton (backward compat, used by index.ts) ──────────
37
-
38
- function getDbPaths(): { dir: string; file: string } {
39
- const config = getConfig();
40
- return { dir: config.dbDir, file: path.join(config.dbDir, 'orbital.db') };
41
- }
42
-
43
- let db: Database.Database | null = null;
44
-
45
- /** @deprecated Use openProjectDatabase() for multi-project support. */
46
- export function getDatabase(): Database.Database {
47
- if (db) return db;
48
- db = openProjectDatabase(getDbPaths().dir);
49
- return db;
50
- }
51
-
52
34
  /** Check if a table exists in the database */
53
35
  function tableExists(database: Database.Database, tableName: string): boolean {
54
36
  const row = database.prepare(
@@ -114,11 +96,3 @@ function runMigrations(database: Database.Database): void {
114
96
  `);
115
97
  }
116
98
  }
117
-
118
- export function closeDatabase(): void {
119
- if (db) {
120
- db.close();
121
- db = null;
122
- log.debug('Database closed');
123
- }
124
- }
@@ -27,6 +27,10 @@ export interface OrbitalGlobalConfig {
27
27
  version: 1;
28
28
  projects: ProjectRegistration[];
29
29
  privateMode?: boolean;
30
+ dispatchFlags?: import('../shared/api-types.js').DispatchFlags;
31
+ dispatch?: import('../shared/api-types.js').DispatchConfig;
32
+ telemetry?: import('./config.js').TelemetryConfig;
33
+ terminalAdapter?: string;
30
34
  }
31
35
 
32
36
  // ─── Constants ──────────────────────────────────────────────