orbital-command 1.1.0 → 1.1.3

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 (51) hide show
  1. package/bin/commands/launch.js +0 -1
  2. package/bin/commands/update.js +18 -2
  3. package/bin/lib/helpers.js +1 -31
  4. package/bin/orbital.js +32 -30
  5. package/dist/assets/{Landing-B6q9U0Vd.js → Landing-D41fKTK0.js} +1 -1
  6. package/dist/assets/{PrimitivesConfig-TlFvypvg.js → PrimitivesConfig-BDKrqwMI.js} +1 -1
  7. package/dist/assets/{QualityGates-D8uvclW4.js → QualityGates-CcrwWI45.js} +1 -1
  8. package/dist/assets/{SessionTimeline-QUaJw6Aa.js → SessionTimeline-D2IN9T3a.js} +1 -1
  9. package/dist/assets/{Settings-CAEnAZAk.js → Settings-gAsObGj2.js} +1 -1
  10. package/dist/assets/{SourceControl-DPeSBaMV.js → SourceControl-CmrDWqSs.js} +1 -1
  11. package/dist/assets/{WorkflowVisualizer-DHrIjx6W.js → WorkflowVisualizer-SxM8rp11.js} +1 -1
  12. package/dist/assets/{arrow-down-DnfKgF33.js → arrow-down-CPRn0RJb.js} +1 -1
  13. package/dist/assets/{bot-DUPnHZfM.js → bot-D2wELs5q.js} +1 -1
  14. package/dist/assets/{circle-x-B4nA79je.js → circle-x-CXIlaZex.js} +1 -1
  15. package/dist/assets/{file-text-PnzRO4pO.js → file-text-b1U7rP0E.js} +1 -1
  16. package/dist/assets/{globe-CIX_GBr0.js → globe-Dxpor8Dk.js} +1 -1
  17. package/dist/assets/{index-DQVAzHMu.js → index-xFU6WWfl.js} +36 -36
  18. package/dist/assets/{key-DpAZM-3d.js → key-CBLFsYFp.js} +1 -1
  19. package/dist/assets/{minus-CJlpKNUB.js → minus-pmCQmtIS.js} +1 -1
  20. package/dist/assets/{radio-DhynLcSx.js → radio-Cf5n4Vr7.js} +1 -1
  21. package/dist/assets/{rocket-DnuQh3sF.js → rocket-grx18Wv8.js} +1 -1
  22. package/dist/assets/{shield-DLMQmvFQ.js → shield-Cgjh9h5t.js} +1 -1
  23. package/dist/assets/{useSocketListener-DPrExNDV.js → useSocketListener-BW3fKan5.js} +1 -1
  24. package/dist/assets/{useWorkflowEditor-BxN7phfr.js → useWorkflowEditor-BRepFz22.js} +1 -1
  25. package/dist/assets/{workflow-constants-ZcCN11vm.js → workflow-constants-YePmoWgU.js} +1 -1
  26. package/dist/assets/{zap-eJ3z8HRI.js → zap-rWDVJHlW.js} +1 -1
  27. package/dist/index.html +1 -1
  28. package/dist/server/server/index.js +1 -9
  29. package/dist/server/server/launch.js +0 -3
  30. package/dist/server/server/routes/sync-routes.js +2 -2
  31. package/dist/server/server/services/telemetry-service.js +143 -0
  32. package/dist/server/server/wizard/detect.js +0 -79
  33. package/dist/server/server/wizard/index.js +27 -91
  34. package/dist/server/server/wizard/types.js +2 -3
  35. package/package.json +1 -1
  36. package/server/index.ts +0 -13
  37. package/server/launch.ts +0 -3
  38. package/server/routes/sync-routes.ts +2 -2
  39. package/server/wizard/detect.ts +1 -81
  40. package/server/wizard/index.ts +29 -116
  41. package/server/wizard/types.ts +2 -17
  42. package/templates/hooks/block-push.sh +16 -2
  43. package/templates/hooks/git-commit-guard.sh +3 -2
  44. package/dist/server/server/wizard/phases/confirm.js +0 -39
  45. package/dist/server/server/wizard/phases/project-setup.js +0 -90
  46. package/dist/server/server/wizard/phases/welcome.js +0 -32
  47. package/dist/server/server/wizard/phases/workflow-setup.js +0 -22
  48. package/server/wizard/phases/confirm.ts +0 -45
  49. package/server/wizard/phases/project-setup.ts +0 -106
  50. package/server/wizard/phases/welcome.ts +0 -39
  51. package/server/wizard/phases/workflow-setup.ts +0 -28
@@ -1,4 +1,4 @@
1
- import{c}from"./index-DQVAzHMu.js";/**
1
+ import{c}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as o}from"./index-DQVAzHMu.js";/**
1
+ import{c as o}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-DQVAzHMu.js";/**
1
+ import{c as a}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-DQVAzHMu.js";/**
1
+ import{c}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c}from"./index-DQVAzHMu.js";/**
1
+ import{c}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{ac as o,ad as O,ae as S,af as _,ag as y,ah as X,ai as D,aj as T,s as x}from"./index-DQVAzHMu.js";import{a as Y}from"./vendor-Bqt8AJn2.js";function M(r,t){const e=+o(r)-+o(t);return e<0?-1:e>0?1:e}function w(r){return O(r,Date.now())}function b(r,t,e){const[s,n]=S(e==null?void 0:e.in,r,t),u=s.getFullYear()-n.getFullYear(),c=s.getMonth()-n.getMonth();return u*12+c}function F(r){return t=>{const s=(r?Math[r]:Math.trunc)(t);return s===0?0:s}}function A(r,t){return+o(r)-+o(t)}function N(r,t){const e=o(r,t==null?void 0:t.in);return e.setHours(23,59,59,999),e}function H(r,t){const e=o(r,t==null?void 0:t.in),s=e.getMonth();return e.setFullYear(e.getFullYear(),s+1,0),e.setHours(23,59,59,999),e}function L(r,t){const e=o(r,t==null?void 0:t.in);return+N(e,t)==+H(e,t)}function k(r,t,e){const[s,n,u]=S(e==null?void 0:e.in,r,r,t),c=M(n,u),a=Math.abs(b(n,u));if(a<1)return 0;n.getMonth()===1&&n.getDate()>27&&n.setDate(30),n.setMonth(n.getMonth()-c*a);let l=M(n,u)===-c;L(s)&&a===1&&M(s,u)===1&&(l=!1);const m=c*(a-+l);return m===0?0:m}function z(r,t,e){const s=A(r,t)/1e3;return F(e==null?void 0:e.roundingMethod)(s)}function E(r,t,e){const s=T(),n=(e==null?void 0:e.locale)??s.locale??_,u=2520,c=M(r,t);if(isNaN(c))throw new RangeError("Invalid time value");const a=Object.assign({},e,{addSuffix:e==null?void 0:e.addSuffix,comparison:c}),[l,m]=S(e==null?void 0:e.in,...c>0?[t,r]:[r,t]),d=z(m,l),I=(y(m)-y(l))/1e3,f=Math.round((d-I)/60);let h;if(f<2)return e!=null&&e.includeSeconds?d<5?n.formatDistance("lessThanXSeconds",5,a):d<10?n.formatDistance("lessThanXSeconds",10,a):d<20?n.formatDistance("lessThanXSeconds",20,a):d<40?n.formatDistance("halfAMinute",0,a):d<60?n.formatDistance("lessThanXMinutes",1,a):n.formatDistance("xMinutes",1,a):f===0?n.formatDistance("lessThanXMinutes",1,a):n.formatDistance("xMinutes",f,a);if(f<45)return n.formatDistance("xMinutes",f,a);if(f<90)return n.formatDistance("aboutXHours",1,a);if(f<X){const i=Math.round(f/60);return n.formatDistance("aboutXHours",i,a)}else{if(f<u)return n.formatDistance("xDays",1,a);if(f<D){const i=Math.round(f/X);return n.formatDistance("xDays",i,a)}else if(f<D*2)return h=Math.round(f/D),n.formatDistance("aboutXMonths",h,a)}if(h=k(m,l),h<12){const i=Math.round(f/D);return n.formatDistance("xMonths",i,a)}else{const i=h%12,g=Math.trunc(h/12);return i<3?n.formatDistance("aboutXYears",g,a):i<9?n.formatDistance("overXYears",g,a):n.formatDistance("almostXYears",g+1,a)}}function R(r,t){return E(r,w(r),t)}function C(r,t,e=[]){Y.useEffect(()=>{const s=Array.isArray(r)?r:[r];for(const n of s)x.on(n,t);return()=>{for(const n of s)x.off(n,t)}},e)}export{R as f,C as u};
1
+ import{ac as o,ad as O,ae as S,af as _,ag as y,ah as X,ai as D,aj as T,s as x}from"./index-xFU6WWfl.js";import{a as Y}from"./vendor-Bqt8AJn2.js";function M(r,t){const e=+o(r)-+o(t);return e<0?-1:e>0?1:e}function w(r){return O(r,Date.now())}function b(r,t,e){const[s,n]=S(e==null?void 0:e.in,r,t),u=s.getFullYear()-n.getFullYear(),c=s.getMonth()-n.getMonth();return u*12+c}function F(r){return t=>{const s=(r?Math[r]:Math.trunc)(t);return s===0?0:s}}function A(r,t){return+o(r)-+o(t)}function N(r,t){const e=o(r,t==null?void 0:t.in);return e.setHours(23,59,59,999),e}function H(r,t){const e=o(r,t==null?void 0:t.in),s=e.getMonth();return e.setFullYear(e.getFullYear(),s+1,0),e.setHours(23,59,59,999),e}function L(r,t){const e=o(r,t==null?void 0:t.in);return+N(e,t)==+H(e,t)}function k(r,t,e){const[s,n,u]=S(e==null?void 0:e.in,r,r,t),c=M(n,u),a=Math.abs(b(n,u));if(a<1)return 0;n.getMonth()===1&&n.getDate()>27&&n.setDate(30),n.setMonth(n.getMonth()-c*a);let l=M(n,u)===-c;L(s)&&a===1&&M(s,u)===1&&(l=!1);const m=c*(a-+l);return m===0?0:m}function z(r,t,e){const s=A(r,t)/1e3;return F(e==null?void 0:e.roundingMethod)(s)}function E(r,t,e){const s=T(),n=(e==null?void 0:e.locale)??s.locale??_,u=2520,c=M(r,t);if(isNaN(c))throw new RangeError("Invalid time value");const a=Object.assign({},e,{addSuffix:e==null?void 0:e.addSuffix,comparison:c}),[l,m]=S(e==null?void 0:e.in,...c>0?[t,r]:[r,t]),d=z(m,l),I=(y(m)-y(l))/1e3,f=Math.round((d-I)/60);let h;if(f<2)return e!=null&&e.includeSeconds?d<5?n.formatDistance("lessThanXSeconds",5,a):d<10?n.formatDistance("lessThanXSeconds",10,a):d<20?n.formatDistance("lessThanXSeconds",20,a):d<40?n.formatDistance("halfAMinute",0,a):d<60?n.formatDistance("lessThanXMinutes",1,a):n.formatDistance("xMinutes",1,a):f===0?n.formatDistance("lessThanXMinutes",1,a):n.formatDistance("xMinutes",f,a);if(f<45)return n.formatDistance("xMinutes",f,a);if(f<90)return n.formatDistance("aboutXHours",1,a);if(f<X){const i=Math.round(f/60);return n.formatDistance("aboutXHours",i,a)}else{if(f<u)return n.formatDistance("xDays",1,a);if(f<D){const i=Math.round(f/X);return n.formatDistance("xDays",i,a)}else if(f<D*2)return h=Math.round(f/D),n.formatDistance("aboutXMonths",h,a)}if(h=k(m,l),h<12){const i=Math.round(f/D);return n.formatDistance("xMonths",i,a)}else{const i=h%12,g=Math.trunc(h/12);return i<3?n.formatDistance("aboutXYears",g,a):i<9?n.formatDistance("overXYears",g,a):n.formatDistance("almostXYears",g+1,a)}}function R(r,t){return E(r,w(r),t)}function C(r,t,e=[]){Y.useEffect(()=>{const s=Array.isArray(r)?r:[r];for(const n of s)x.on(n,t);return()=>{for(const n of s)x.off(n,t)}},e)}export{R as f,C as u};
@@ -1,4 +1,4 @@
1
- import{c as P,aU as V,e as W}from"./index-DQVAzHMu.js";import{a as o}from"./vendor-Bqt8AJn2.js";/**
1
+ import{c as P,aU as V,e as W}from"./index-xFU6WWfl.js";import{a as o}from"./vendor-Bqt8AJn2.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as e,E as o,T as r,S as a}from"./index-DQVAzHMu.js";/**
1
+ import{c as e,E as o,T as r,S as a}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as a}from"./index-DQVAzHMu.js";/**
1
+ import{c as a}from"./index-xFU6WWfl.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
package/dist/index.html CHANGED
@@ -8,7 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.googleapis.com" />
9
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
10
  <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" />
11
- <script type="module" crossorigin src="/assets/index-DQVAzHMu.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-xFU6WWfl.js"></script>
12
12
  <link rel="modulepreload" crossorigin href="/assets/vendor-Bqt8AJn2.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/ui-QhrRH5wk.js">
14
14
  <link rel="modulepreload" crossorigin href="/assets/charts-LGLb8hyU.js">
@@ -13,7 +13,7 @@ import { SyncService } from './services/sync-service.js';
13
13
  import { startGlobalWatcher } from './watchers/global-watcher.js';
14
14
  import { createSyncRoutes } from './routes/sync-routes.js';
15
15
  import { seedGlobalPrimitives } from './init.js';
16
- import { ensureOrbitalHome, loadGlobalConfig, registerProject as registerProjectGlobal, GLOBAL_PRIMITIVES_DIR, ORBITAL_HOME, } from './global-config.js';
16
+ import { ensureOrbitalHome, GLOBAL_PRIMITIVES_DIR, ORBITAL_HOME, } from './global-config.js';
17
17
  export async function startCentralServer(overrides) {
18
18
  ensureOrbitalHome();
19
19
  const envLevel = process.env.ORBITAL_LOG_LEVEL;
@@ -23,12 +23,6 @@ export async function startCentralServer(overrides) {
23
23
  const log = createLogger('central');
24
24
  const port = overrides?.port ?? (Number(process.env.ORBITAL_SERVER_PORT) || 4444);
25
25
  const clientPort = overrides?.clientPort ?? (Number(process.env.ORBITAL_CLIENT_PORT) || 4445);
26
- // Auto-register current project if registry is empty
27
- const globalConfig = loadGlobalConfig();
28
- if (globalConfig.projects.length === 0 && overrides?.autoRegisterPath) {
29
- registerProjectGlobal(overrides.autoRegisterPath);
30
- log.info('Auto-registered current project', { path: overrides.autoRegisterPath });
31
- }
32
26
  const app = express();
33
27
  const httpServer = createServer(app);
34
28
  const io = new Server(httpServer, {
@@ -201,10 +195,8 @@ const isDirectRun = process.argv[1] && (process.argv[1].endsWith('server/index.t
201
195
  process.argv[1].endsWith('server/index.js') ||
202
196
  process.argv[1].endsWith('server'));
203
197
  if (isDirectRun) {
204
- const projectRoot = process.env.ORBITAL_PROJECT_ROOT || process.cwd();
205
198
  startCentralServer({
206
199
  port: Number(process.env.ORBITAL_SERVER_PORT) || 4444,
207
- autoRegisterPath: projectRoot,
208
200
  }).then(({ shutdown }) => {
209
201
  process.on('SIGINT', async () => {
210
202
  await shutdown();
@@ -3,17 +3,14 @@
3
3
  *
4
4
  * Reads environment variables set by bin/orbital.js:
5
5
  * ORBITAL_LAUNCH_MODE=central
6
- * ORBITAL_AUTO_REGISTER=<path> (if no projects registered yet)
7
6
  * ORBITAL_SERVER_PORT=<port>
8
7
  */
9
8
  import { startCentralServer } from './index.js';
10
9
  import { createLogger } from './utils/logger.js';
11
10
  const log = createLogger('launch');
12
11
  const port = Number(process.env.ORBITAL_SERVER_PORT) || 4444;
13
- const autoRegisterPath = process.env.ORBITAL_AUTO_REGISTER || undefined;
14
12
  startCentralServer({
15
13
  port,
16
- autoRegisterPath: autoRegisterPath || undefined,
17
14
  }).then(({ shutdown }) => {
18
15
  process.on('SIGINT', async () => {
19
16
  await shutdown();
@@ -3,7 +3,7 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import { execFile } from 'child_process';
5
5
  import { isValidRelativePath } from '../utils/route-helpers.js';
6
- import { runInit } from '../init.js';
6
+ import { runInit, TEMPLATES_DIR } from '../init.js';
7
7
  import { loadGlobalConfig } from '../global-config.js';
8
8
  import { getPackageVersion } from '../utils/package-info.js';
9
9
  export function createSyncRoutes({ syncService, projectManager }) {
@@ -253,7 +253,7 @@ export function createSyncRoutes({ syncService, projectManager }) {
253
253
  // ─── Helpers ──────────────────────────────────────────────
254
254
  function seedWelcomeCard(projectRoot, preset) {
255
255
  // Determine the planning directory from the preset
256
- const presetsDir = path.join(path.dirname(path.dirname(new URL(import.meta.url).pathname)), 'templates', 'presets');
256
+ const presetsDir = path.join(TEMPLATES_DIR, 'presets');
257
257
  let planningDir = 'planning'; // default fallback
258
258
  try {
259
259
  const presetPath = path.join(presetsDir, `${preset}.json`);
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Session Telemetry — uploads raw Claude session JSONL files to a remote
3
+ * Cloudflare Worker + R2 endpoint. This entire feature lives in this single
4
+ * file for easy removal.
5
+ */
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { Router } from 'express';
9
+ import { getClaudeSessionsDir } from '../config.js';
10
+ // ─── Service ───────────────────────────────────────────────
11
+ export class TelemetryService {
12
+ db;
13
+ config;
14
+ projectName;
15
+ projectRoot;
16
+ lastResult = null;
17
+ constructor(db, config, projectName, projectRoot) {
18
+ this.db = db;
19
+ this.config = config;
20
+ this.projectName = projectName;
21
+ this.projectRoot = projectRoot;
22
+ }
23
+ get enabled() {
24
+ return this.config.enabled && this.config.url.length > 0;
25
+ }
26
+ /** Upload sessions that have changed since last telemetry send. */
27
+ async uploadChangedSessions() {
28
+ if (!this.enabled)
29
+ return { ok: true, uploaded: 0, errors: 0 };
30
+ const rows = this.db.prepare(`SELECT id, claude_session_id, ended_at, telemetry_sent_at
31
+ FROM sessions
32
+ WHERE claude_session_id IS NOT NULL
33
+ AND (telemetry_sent_at IS NULL OR ended_at > telemetry_sent_at)`).all();
34
+ return this.uploadRows(rows);
35
+ }
36
+ /** Force re-upload all sessions regardless of telemetry_sent_at. */
37
+ async uploadAllSessions() {
38
+ if (!this.enabled)
39
+ return { ok: true, uploaded: 0, errors: 0 };
40
+ const rows = this.db.prepare(`SELECT id, claude_session_id, ended_at, telemetry_sent_at
41
+ FROM sessions
42
+ WHERE claude_session_id IS NOT NULL`).all();
43
+ return this.uploadRows(rows);
44
+ }
45
+ /** Ping the remote health endpoint. */
46
+ async testConnection() {
47
+ try {
48
+ const res = await fetch(`${this.config.url}/health`, {
49
+ method: 'GET',
50
+ headers: this.config.headers,
51
+ signal: AbortSignal.timeout(10_000),
52
+ });
53
+ const body = await res.text();
54
+ return { ok: res.ok, status: res.status, body };
55
+ }
56
+ catch (err) {
57
+ return { ok: false, status: 0, body: err.message };
58
+ }
59
+ }
60
+ getStatus() {
61
+ return {
62
+ enabled: this.enabled,
63
+ url: this.config.url || null,
64
+ lastResult: this.lastResult,
65
+ };
66
+ }
67
+ // ─── Internal ──────────────────────────────────────────────
68
+ async uploadRows(rows) {
69
+ if (rows.length === 0) {
70
+ this.lastResult = { ok: true, uploaded: 0, errors: 0 };
71
+ return this.lastResult;
72
+ }
73
+ const sessionsDir = getClaudeSessionsDir(this.projectRoot);
74
+ const now = new Date().toISOString();
75
+ let uploaded = 0;
76
+ let errors = 0;
77
+ const updateStmt = this.db.prepare('UPDATE sessions SET telemetry_sent_at = ? WHERE id = ?');
78
+ // Deduplicate by claude_session_id (multiple rows can share the same JSONL file)
79
+ const seen = new Set();
80
+ const unique = [];
81
+ for (const row of rows) {
82
+ if (row.claude_session_id && !seen.has(row.claude_session_id)) {
83
+ seen.add(row.claude_session_id);
84
+ unique.push(row);
85
+ }
86
+ }
87
+ for (const row of unique) {
88
+ const sessionId = row.claude_session_id;
89
+ const filePath = path.join(sessionsDir, `${sessionId}.jsonl`);
90
+ if (!fs.existsSync(filePath)) {
91
+ continue;
92
+ }
93
+ try {
94
+ const body = fs.readFileSync(filePath);
95
+ const encodedProject = encodeURIComponent(this.projectName);
96
+ const url = `${this.config.url}/upload/${encodedProject}/${sessionId}.jsonl`;
97
+ const res = await fetch(url, {
98
+ method: 'PUT',
99
+ body,
100
+ headers: {
101
+ 'Content-Type': 'application/x-ndjson',
102
+ ...this.config.headers,
103
+ },
104
+ signal: AbortSignal.timeout(30_000),
105
+ });
106
+ if (res.ok) {
107
+ uploaded++;
108
+ // Update telemetry_sent_at for ALL rows with this claude_session_id
109
+ const matching = rows.filter((r) => r.claude_session_id === sessionId);
110
+ for (const m of matching) {
111
+ updateStmt.run(now, m.id);
112
+ }
113
+ }
114
+ else {
115
+ errors++;
116
+ }
117
+ }
118
+ catch {
119
+ errors++;
120
+ }
121
+ }
122
+ this.lastResult = { ok: errors === 0, uploaded, errors };
123
+ return this.lastResult;
124
+ }
125
+ }
126
+ export function createTelemetryRoutes({ telemetryService }) {
127
+ const router = Router();
128
+ router.post('/telemetry/trigger', async (req, res) => {
129
+ const force = req.query.force === 'true';
130
+ const result = force
131
+ ? await telemetryService.uploadAllSessions()
132
+ : await telemetryService.uploadChangedSessions();
133
+ res.json(result);
134
+ });
135
+ router.post('/telemetry/test', async (_req, res) => {
136
+ const result = await telemetryService.testConnection();
137
+ res.json(result);
138
+ });
139
+ router.get('/telemetry/status', (_req, res) => {
140
+ res.json(telemetryService.getStatus());
141
+ });
142
+ return router;
143
+ }
@@ -5,9 +5,6 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  const ORBITAL_HOME = path.join(process.env.HOME || process.env.USERPROFILE || '~', '.orbital');
7
7
  export { ORBITAL_HOME };
8
- export function isInteractiveTerminal() {
9
- return !!(process.stdout.isTTY && !process.env.CI);
10
- }
11
8
  export function isOrbitalSetupDone() {
12
9
  return fs.existsSync(path.join(ORBITAL_HOME, 'config.json'));
13
10
  }
@@ -18,79 +15,3 @@ export function buildSetupState(packageVersion) {
18
15
  linkedProjects: [],
19
16
  };
20
17
  }
21
- export function buildProjectState(projectRoot, packageVersion) {
22
- const projectConfigExists = fs.existsSync(path.join(projectRoot, '.claude', 'orbital.config.json'));
23
- return {
24
- projectRoot,
25
- isProjectInitialized: projectConfigExists,
26
- packageVersion,
27
- };
28
- }
29
- export function detectProjectName(projectRoot) {
30
- return path.basename(projectRoot)
31
- .replace(/[-_]+/g, ' ')
32
- .replace(/\b\w/g, c => c.toUpperCase());
33
- }
34
- export function detectCommands(projectRoot) {
35
- const commands = {
36
- typeCheck: null,
37
- lint: null,
38
- build: null,
39
- test: null,
40
- };
41
- const pkgJsonPath = path.join(projectRoot, 'package.json');
42
- if (!fs.existsSync(pkgJsonPath))
43
- return commands;
44
- try {
45
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
46
- const scripts = pkg.scripts || {};
47
- if (scripts.typecheck || scripts['type-check']) {
48
- commands.typeCheck = `npm run ${scripts.typecheck ? 'typecheck' : 'type-check'}`;
49
- }
50
- if (scripts.lint)
51
- commands.lint = 'npm run lint';
52
- if (scripts.build)
53
- commands.build = 'npm run build';
54
- if (scripts.test)
55
- commands.test = 'npm run test';
56
- }
57
- catch { /* ignore malformed package.json */ }
58
- return commands;
59
- }
60
- export function detectPortConflict(serverPort) {
61
- const registryPath = path.join(ORBITAL_HOME, 'config.json');
62
- if (!fs.existsSync(registryPath))
63
- return null;
64
- try {
65
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
66
- for (const project of registry.projects || []) {
67
- const configPath = path.join(project.path, '.claude', 'orbital.config.json');
68
- if (!fs.existsSync(configPath))
69
- continue;
70
- try {
71
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
72
- if (config.serverPort === serverPort)
73
- return project.name;
74
- }
75
- catch { /* skip unreadable configs */ }
76
- }
77
- }
78
- catch { /* skip unreadable registry */ }
79
- return null;
80
- }
81
- export function isValidProjectPath(p) {
82
- const resolved = p.startsWith('~')
83
- ? path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1))
84
- : path.resolve(p);
85
- if (!fs.existsSync(resolved))
86
- return 'Directory does not exist';
87
- if (!fs.statSync(resolved).isDirectory())
88
- return 'Not a directory';
89
- return undefined;
90
- }
91
- export function resolveProjectPath(p) {
92
- if (p.startsWith('~')) {
93
- return path.join(process.env.HOME || process.env.USERPROFILE || '~', p.slice(1));
94
- }
95
- return path.resolve(p);
96
- }
@@ -2,8 +2,8 @@
2
2
  * Interactive CLI wizard — main orchestrator.
3
3
  *
4
4
  * Entry points:
5
- * runSetupWizard() — Phase 1: first-time Orbital setup (~/.orbital/)
6
- * runProjectSetup() Phase 2: per-project scaffolding (.claude/)
5
+ * runSetupWizard() — First-time Orbital setup (~/.orbital/)
6
+ * runHub() Context-aware hub menu (orbital)
7
7
  * runConfigEditor() — interactive config editor (orbital config)
8
8
  * runDoctor() — health diagnostics (orbital doctor)
9
9
  */
@@ -12,16 +12,11 @@ import path from 'path';
12
12
  import { spawn, execFileSync } from 'child_process';
13
13
  import * as p from '@clack/prompts';
14
14
  import pc from 'picocolors';
15
- import { buildSetupState, buildProjectState } from './detect.js';
15
+ import { buildSetupState } from './detect.js';
16
16
  import { phaseSetupWizard } from './phases/setup-wizard.js';
17
- import { phaseWelcome } from './phases/welcome.js';
18
- import { phaseProjectSetup } from './phases/project-setup.js';
19
- import { phaseWorkflowSetup } from './phases/workflow-setup.js';
20
- import { phaseConfirm, showPostInstall } from './phases/confirm.js';
21
17
  import { runConfigEditor } from './config-editor.js';
22
18
  import { runDoctor } from './doctor.js';
23
19
  import { isITerm2Available } from '../adapters/iterm2-adapter.js';
24
- import { registerProject } from '../global-config.js';
25
20
  export { runConfigEditor, runDoctor };
26
21
  // ─── Phase 1: Setup Wizard ─────────────────────────────────────
27
22
  /**
@@ -32,56 +27,7 @@ export async function runSetupWizard(packageVersion) {
32
27
  const state = buildSetupState(packageVersion);
33
28
  p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${packageVersion}`)}`);
34
29
  await phaseSetupWizard(state);
35
- p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard and add your first project.`);
36
- }
37
- // ─── Phase 2: Project Setup ────────────────────────────────────
38
- /**
39
- * Per-project setup. Walks through name, commands, workflow, then
40
- * calls runInit() to scaffold files into .claude/.
41
- */
42
- export async function runProjectSetup(projectRoot, packageVersion, args) {
43
- const state = buildProjectState(projectRoot, packageVersion);
44
- const force = args.includes('--force');
45
- p.intro(`${pc.bgCyan(pc.black(' Orbital Command '))} ${pc.dim(`v${packageVersion}`)}`);
46
- // Welcome gate: detect re-init / reconfigure
47
- const forceFromWelcome = await phaseWelcome(state);
48
- const useForce = force || forceFromWelcome;
49
- await runProjectPhases(state, useForce);
50
- p.outro(`Run ${pc.cyan('orbital')} to launch the dashboard.`);
51
- }
52
- // ─── Shared project phases (used by both flows) ────────────────
53
- /**
54
- * Run the project setup phases and install. Used by both
55
- * standalone runProjectSetup() and inline from runSetupWizard().
56
- */
57
- async function runProjectPhases(state, useForce) {
58
- await phaseProjectSetup(state);
59
- await phaseWorkflowSetup(state);
60
- await phaseConfirm(state);
61
- // Install
62
- const s = p.spinner();
63
- s.start('Installing into project...');
64
- try {
65
- const { runInit } = await import('../init.js');
66
- runInit(state.projectRoot, {
67
- force: useForce,
68
- quiet: true,
69
- preset: state.workflowPreset,
70
- projectName: state.projectName,
71
- serverPort: state.serverPort,
72
- clientPort: state.clientPort,
73
- commands: state.selectedCommands,
74
- });
75
- registerProject(state.projectRoot, { name: state.projectName });
76
- stampTemplateVersion(state.projectRoot, state.packageVersion);
77
- s.stop('Project ready.');
78
- }
79
- catch (err) {
80
- s.stop('Installation failed.');
81
- p.log.error(err instanceof Error ? err.message : String(err));
82
- process.exit(1);
83
- }
84
- showPostInstall(state);
30
+ p.outro('Launching dashboard...');
85
31
  }
86
32
  /** Returns true if `a` is older than `b` (semver comparison). */
87
33
  function isOlderThan(a, b) {
@@ -272,17 +218,26 @@ export async function runHub(opts) {
272
218
  });
273
219
  }
274
220
  }
275
- // ── Build menu options based on project state ──
276
- const projectHint = opts.projectNames.length > 0
277
- ? pc.dim(` (${opts.projectNames.join(', ')})`)
221
+ // ── Show menu and pick action ──
222
+ result.action = await promptHubAction(opts.projectNames);
223
+ return result;
224
+ }
225
+ /**
226
+ * Show the hub menu and return the chosen action.
227
+ * Exported separately so the CLI can loop back after executing an action.
228
+ */
229
+ export async function promptHubAction(projectNames) {
230
+ const projectHint = projectNames.length > 0
231
+ ? pc.dim(` (${projectNames.join(', ')})`)
278
232
  : '';
279
- const options = [];
280
- if (opts.isProjectInitialized) {
281
- options.push({ value: 'launch', label: `Launch dashboard${projectHint}` }, { value: 'config', label: 'Config', hint: 'modify project settings' }, { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' }, { value: 'update', label: 'Update templates', hint: 'sync to latest' }, { value: 'status', label: 'Status', hint: 'template sync status' }, { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' });
282
- }
283
- else {
284
- options.push({ value: 'init', label: 'Initialize this project' }, { value: 'launch', label: `Launch dashboard${projectHint}` });
285
- }
233
+ const options = [
234
+ { value: 'launch', label: `Launch dashboard${projectHint}` },
235
+ { value: 'config', label: 'Config', hint: 'modify project settings' },
236
+ { value: 'doctor', label: 'Doctor', hint: 'health check & diagnostics' },
237
+ { value: 'update', label: 'Update templates', hint: 'sync to latest' },
238
+ { value: 'status', label: 'Status', hint: 'template sync status' },
239
+ { value: 'reset', label: 'Reset to defaults', hint: 'force-reset all templates' },
240
+ ];
286
241
  const action = await p.select({
287
242
  message: 'What would you like to do?',
288
243
  options,
@@ -291,7 +246,7 @@ export async function runHub(opts) {
291
246
  p.cancel('Cancelled.');
292
247
  process.exit(0);
293
248
  }
294
- // ── Double-confirm for destructive reset ──
249
+ // Double-confirm for destructive reset
295
250
  if (action === 'reset') {
296
251
  p.note('This will overwrite ALL hooks, skills, agents, and workflow config\n' +
297
252
  'with the default templates. Modified and pinned files will be replaced.\n' +
@@ -301,34 +256,15 @@ export async function runHub(opts) {
301
256
  initialValue: false,
302
257
  });
303
258
  if (p.isCancel(confirmReset) || !confirmReset) {
304
- p.cancel('Reset cancelled.');
305
- process.exit(0);
259
+ return promptHubAction(projectNames);
306
260
  }
307
261
  const doubleConfirm = await p.confirm({
308
262
  message: 'This cannot be undone. Continue?',
309
263
  initialValue: false,
310
264
  });
311
265
  if (p.isCancel(doubleConfirm) || !doubleConfirm) {
312
- p.cancel('Reset cancelled.');
313
- process.exit(0);
314
- }
315
- }
316
- result.action = action;
317
- return result;
318
- }
319
- // ─── Template Version Stamping ─────────────────────────────────
320
- function stampTemplateVersion(projectRoot, packageVersion) {
321
- const configPath = path.join(projectRoot, '.claude', 'orbital.config.json');
322
- if (!fs.existsSync(configPath))
323
- return;
324
- try {
325
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
326
- if (config.templateVersion !== packageVersion) {
327
- config.templateVersion = packageVersion;
328
- const tmp = configPath + `.tmp.${process.pid}`;
329
- fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + '\n', 'utf8');
330
- fs.renameSync(tmp, configPath);
266
+ return promptHubAction(projectNames);
331
267
  }
332
268
  }
333
- catch { /* ignore malformed config */ }
269
+ return action;
334
270
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Types for the interactive CLI wizard.
3
3
  *
4
- * Two wizard flows:
5
- * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation
6
- * Phase 2 (ProjectSetupState) — per-project scaffolding into .claude/
4
+ * Phase 1 (SetupState) — first-time Orbital setup, ~/.orbital/ creation.
5
+ * Project setup is handled by the frontend Add Project modal.
7
6
  */
8
7
  export { WORKFLOW_PRESETS } from '../../shared/workflow-presets.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orbital-command",
3
- "version": "1.1.0",
3
+ "version": "1.1.3",
4
4
  "description": "Orbital Command — mission control dashboard for Claude Code projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/server/index.ts CHANGED
@@ -20,8 +20,6 @@ import { createSyncRoutes } from './routes/sync-routes.js';
20
20
  import { seedGlobalPrimitives } from './init.js';
21
21
  import {
22
22
  ensureOrbitalHome,
23
- loadGlobalConfig,
24
- registerProject as registerProjectGlobal,
25
23
  GLOBAL_PRIMITIVES_DIR,
26
24
  ORBITAL_HOME,
27
25
  } from './global-config.js';
@@ -29,8 +27,6 @@ import {
29
27
  export interface CentralServerOverrides {
30
28
  port?: number;
31
29
  clientPort?: number;
32
- /** If set, auto-register this project on first launch */
33
- autoRegisterPath?: string;
34
30
  }
35
31
 
36
32
  export interface CentralServerInstance {
@@ -53,13 +49,6 @@ export async function startCentralServer(overrides?: CentralServerOverrides): Pr
53
49
  const port = overrides?.port ?? (Number(process.env.ORBITAL_SERVER_PORT) || 4444);
54
50
  const clientPort = overrides?.clientPort ?? (Number(process.env.ORBITAL_CLIENT_PORT) || 4445);
55
51
 
56
- // Auto-register current project if registry is empty
57
- const globalConfig = loadGlobalConfig();
58
- if (globalConfig.projects.length === 0 && overrides?.autoRegisterPath) {
59
- registerProjectGlobal(overrides.autoRegisterPath);
60
- log.info('Auto-registered current project', { path: overrides.autoRegisterPath });
61
- }
62
-
63
52
  const app = express();
64
53
  const httpServer = createServer(app);
65
54
 
@@ -261,10 +250,8 @@ const isDirectRun = process.argv[1] && (
261
250
  );
262
251
 
263
252
  if (isDirectRun) {
264
- const projectRoot = process.env.ORBITAL_PROJECT_ROOT || process.cwd();
265
253
  startCentralServer({
266
254
  port: Number(process.env.ORBITAL_SERVER_PORT) || 4444,
267
- autoRegisterPath: projectRoot,
268
255
  }).then(({ shutdown }) => {
269
256
  process.on('SIGINT', async () => {
270
257
  await shutdown();
package/server/launch.ts CHANGED
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Reads environment variables set by bin/orbital.js:
5
5
  * ORBITAL_LAUNCH_MODE=central
6
- * ORBITAL_AUTO_REGISTER=<path> (if no projects registered yet)
7
6
  * ORBITAL_SERVER_PORT=<port>
8
7
  */
9
8
  import { startCentralServer } from './index.js';
@@ -12,11 +11,9 @@ import { createLogger } from './utils/logger.js';
12
11
  const log = createLogger('launch');
13
12
 
14
13
  const port = Number(process.env.ORBITAL_SERVER_PORT) || 4444;
15
- const autoRegisterPath = process.env.ORBITAL_AUTO_REGISTER || undefined;
16
14
 
17
15
  startCentralServer({
18
16
  port,
19
- autoRegisterPath: autoRegisterPath || undefined,
20
17
  }).then(({ shutdown }) => {
21
18
  process.on('SIGINT', async () => {
22
19
  await shutdown();