@distributionos/cli 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/api.js +59 -0
- package/src/bootstrap.js +5 -3
- package/src/cli.js +510 -201
- package/src/constants.js +3 -3
- package/src/setup-report.js +11 -4
package/README.md
CHANGED
|
@@ -22,8 +22,12 @@ distributionos setup --app <appId> --apply
|
|
|
22
22
|
distributionos login --app <appId>
|
|
23
23
|
distributionos verify --app <appId> --url <liveUrl> [--content-id <id>]
|
|
24
24
|
distributionos report-implementation --app <appId> --artifact <artifactId> [--url <liveUrl>]
|
|
25
|
+
distributionos start-work --app <appId> [--task-type <type>] [--artifact <artifactId>] [--dedup-key <key>]
|
|
26
|
+
distributionos complete-work --app <appId> --work-session <id> --status <status> --summary <text> [--url <liveUrl>] [--file <path>] [--pr-url <url>]
|
|
25
27
|
```
|
|
26
28
|
|
|
29
|
+
`start-work` and `complete-work` are compact fallbacks for agents or terminals that cannot call the MCP `start_agent_work` / `complete_agent_work` tools directly. Use one work session per meaningful task, not per prompt.
|
|
30
|
+
|
|
27
31
|
## Safety
|
|
28
32
|
|
|
29
33
|
- Uses DistributionOS MCP OAuth as the happy path.
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -252,6 +252,65 @@ export async function reportImplementation(input) {
|
|
|
252
252
|
return body?.data ?? body;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
export async function startAgentWork(input) {
|
|
256
|
+
const token = await resolveAuthToken({
|
|
257
|
+
env: input.env,
|
|
258
|
+
apiBase: input.apiBase ?? DEFAULT_API_BASE,
|
|
259
|
+
appId: input.appId,
|
|
260
|
+
fetchImpl: input.fetch,
|
|
261
|
+
});
|
|
262
|
+
if (!token) {
|
|
263
|
+
throw new Error('No stored OAuth token or env token was available.');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const apiBase = normalizeApiBase(input.apiBase ?? DEFAULT_API_BASE);
|
|
267
|
+
const response = await input.fetch(`${apiBase}/api/v1/apps/${encodeURIComponent(input.appId)}/agent-work`, {
|
|
268
|
+
method: 'POST',
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${token.value}`,
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
Accept: 'application/json',
|
|
273
|
+
},
|
|
274
|
+
body: JSON.stringify(input.payload),
|
|
275
|
+
});
|
|
276
|
+
const body = await response.json().catch(() => null);
|
|
277
|
+
if (!response.ok || body?.success === false) {
|
|
278
|
+
throw new Error(body?.error || `Agent work start failed with HTTP ${response.status}`);
|
|
279
|
+
}
|
|
280
|
+
return body?.data ?? body;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export async function completeAgentWork(input) {
|
|
284
|
+
const token = await resolveAuthToken({
|
|
285
|
+
env: input.env,
|
|
286
|
+
apiBase: input.apiBase ?? DEFAULT_API_BASE,
|
|
287
|
+
appId: input.appId,
|
|
288
|
+
fetchImpl: input.fetch,
|
|
289
|
+
});
|
|
290
|
+
if (!token) {
|
|
291
|
+
throw new Error('No stored OAuth token or env token was available.');
|
|
292
|
+
}
|
|
293
|
+
if (!input.workSessionId) {
|
|
294
|
+
throw new Error('workSessionId is required.');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const apiBase = normalizeApiBase(input.apiBase ?? DEFAULT_API_BASE);
|
|
298
|
+
const response = await input.fetch(`${apiBase}/api/v1/apps/${encodeURIComponent(input.appId)}/agent-work/${encodeURIComponent(input.workSessionId)}/complete`, {
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: {
|
|
301
|
+
Authorization: `Bearer ${token.value}`,
|
|
302
|
+
'Content-Type': 'application/json',
|
|
303
|
+
Accept: 'application/json',
|
|
304
|
+
},
|
|
305
|
+
body: JSON.stringify(input.payload),
|
|
306
|
+
});
|
|
307
|
+
const body = await response.json().catch(() => null);
|
|
308
|
+
if (!response.ok || body?.success === false) {
|
|
309
|
+
throw new Error(body?.error || `Agent work closeout failed with HTTP ${response.status}`);
|
|
310
|
+
}
|
|
311
|
+
return body?.data ?? body;
|
|
312
|
+
}
|
|
313
|
+
|
|
255
314
|
async function fetchJson({ fetchImpl, apiBase, token, path }) {
|
|
256
315
|
if (typeof fetchImpl !== 'function') {
|
|
257
316
|
throw new Error('fetch is unavailable in this Node runtime');
|
package/src/bootstrap.js
CHANGED
|
@@ -22,9 +22,11 @@ export function buildLocalBootstrapBlock(version = CURRENT_BOOTSTRAP_VERSION, ap
|
|
|
22
22
|
`<!-- DISTRIBUTIONOS:START version=${version}${appIdAttribute} -->`,
|
|
23
23
|
'## DistributionOS Agent Sync',
|
|
24
24
|
'',
|
|
25
|
-
'This repo is linked to DistributionOS.',
|
|
26
|
-
'',
|
|
27
|
-
'
|
|
25
|
+
'This repo is linked to DistributionOS.',
|
|
26
|
+
'',
|
|
27
|
+
'Session preflight: when starting a fresh agent chat in this repo, call check_distributionos_connection first, then fetch the current instructions below. If DistributionOS returns setupUpdate or setupUpdateGuidance with a status other than current or not_installed, tell the user the repo has a setup update available, show the review command, and run the apply command only after the user approves.',
|
|
28
|
+
'',
|
|
29
|
+
'Hard preflight: before editing files, creating assets, generating images, creating HyperFrames launch/demo videos, or changing copy for SEO, blog, launch, content, visual asset, competitor, social, email, or growth work, fetch the current instructions from DistributionOS:',
|
|
28
30
|
'',
|
|
29
31
|
instructionCall,
|
|
30
32
|
'',
|
package/src/cli.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import {
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { CLI_LOCAL_VERSION, DEFAULT_API_BASE } from './constants.js';
|
|
3
6
|
import {
|
|
4
7
|
ensureAnalyticsSite,
|
|
8
|
+
completeAgentWork,
|
|
5
9
|
reportSetupInstallation,
|
|
6
10
|
reportImplementation,
|
|
7
11
|
resolveAuthToken,
|
|
12
|
+
startAgentWork,
|
|
8
13
|
verifyAnalyticsInstall,
|
|
9
14
|
} from './api.js';
|
|
10
15
|
import { loginWithOAuth } from './oauth.js';
|
|
@@ -12,11 +17,11 @@ import { createSetupPlan, formatPlanText } from './plan.js';
|
|
|
12
17
|
import { applySetupPlan } from './mutate.js';
|
|
13
18
|
import { submitInitialization } from './initialization.js';
|
|
14
19
|
import { compareValidationResults, inspectInstallArtifacts, runValidationPlan } from './validation.js';
|
|
15
|
-
import { buildSetupReport } from './setup-report.js';
|
|
16
|
-
|
|
17
|
-
const DISTRIBUTIONOS_MCP_URL = 'https://distributionos.dev/api/mcp';
|
|
18
|
-
const DISTRIBUTIONOS_MCP_NAME = 'distributionos';
|
|
19
|
-
const AGENT_CHOICES = new Set(['codex', 'claude', 'terminal', 'other']);
|
|
20
|
+
import { buildSetupReport } from './setup-report.js';
|
|
21
|
+
|
|
22
|
+
const DISTRIBUTIONOS_MCP_URL = 'https://distributionos.dev/api/mcp';
|
|
23
|
+
const DISTRIBUTIONOS_MCP_NAME = 'distributionos';
|
|
24
|
+
const AGENT_CHOICES = new Set(['codex', 'claude', 'terminal', 'other']);
|
|
20
25
|
|
|
21
26
|
export async function runCli(argv, runtime) {
|
|
22
27
|
const stdout = runtime.stdout;
|
|
@@ -129,24 +134,92 @@ export async function runCli(argv, runtime) {
|
|
|
129
134
|
}
|
|
130
135
|
}
|
|
131
136
|
|
|
137
|
+
if (args.command === 'start-work') {
|
|
138
|
+
if (!args.appId) {
|
|
139
|
+
stderr.write('Missing required --app <appId>.\n\n');
|
|
140
|
+
stderr.write(helpText());
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const authReady = await ensureCliAuth({ args, runtime, stderr });
|
|
145
|
+
if (!authReady) return 1;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const result = await startAgentWork({
|
|
149
|
+
appId: args.appId,
|
|
150
|
+
apiBase: args.apiBase ?? DEFAULT_API_BASE,
|
|
151
|
+
env: runtime.env,
|
|
152
|
+
fetch: runtime.fetch,
|
|
153
|
+
payload: buildStartWorkPayload(args),
|
|
154
|
+
});
|
|
155
|
+
stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatStartWorkResult(result));
|
|
156
|
+
return 0;
|
|
157
|
+
} catch (error) {
|
|
158
|
+
stderr.write(`${error instanceof Error ? error.message : 'Agent work start failed'}\n`);
|
|
159
|
+
return 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (args.command === 'complete-work') {
|
|
164
|
+
if (!args.appId) {
|
|
165
|
+
stderr.write('Missing required --app <appId>.\n\n');
|
|
166
|
+
stderr.write(helpText());
|
|
167
|
+
return 1;
|
|
168
|
+
}
|
|
169
|
+
if (!args.workSessionId) {
|
|
170
|
+
stderr.write('Missing required --work-session <workSessionId>.\n\n');
|
|
171
|
+
stderr.write(helpText());
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
if (!args.status) {
|
|
175
|
+
stderr.write('Missing required --status <completed|shipped|reported|blocked|skipped|no_op|failed>.\n\n');
|
|
176
|
+
stderr.write(helpText());
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (args.status !== 'no_op' && !args.summary) {
|
|
180
|
+
stderr.write('Missing required --summary <text> unless --status no_op is used.\n\n');
|
|
181
|
+
stderr.write(helpText());
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const authReady = await ensureCliAuth({ args, runtime, stderr });
|
|
186
|
+
if (!authReady) return 1;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const result = await completeAgentWork({
|
|
190
|
+
appId: args.appId,
|
|
191
|
+
workSessionId: args.workSessionId,
|
|
192
|
+
apiBase: args.apiBase ?? DEFAULT_API_BASE,
|
|
193
|
+
env: runtime.env,
|
|
194
|
+
fetch: runtime.fetch,
|
|
195
|
+
payload: buildCompleteWorkPayload(args),
|
|
196
|
+
});
|
|
197
|
+
stdout.write(args.json ? `${JSON.stringify(result, null, 2)}\n` : formatCompleteWorkResult(result));
|
|
198
|
+
return 0;
|
|
199
|
+
} catch (error) {
|
|
200
|
+
stderr.write(`${error instanceof Error ? error.message : 'Agent work closeout failed'}\n`);
|
|
201
|
+
return 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
132
205
|
if (args.command !== 'setup') {
|
|
133
206
|
stderr.write(`Unknown command: ${args.command}\n\n`);
|
|
134
207
|
stderr.write(helpText());
|
|
135
208
|
return 1;
|
|
136
209
|
}
|
|
137
210
|
|
|
138
|
-
if (!args.appId) {
|
|
139
|
-
stderr.write('Missing required --app <appId>.\n\n');
|
|
140
|
-
stderr.write(helpText());
|
|
141
|
-
return 1;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const agentSetup = await ensureRequestedAgentSetup({ args, runtime, stdout, stderr });
|
|
145
|
-
if (!agentSetup.ok) return 1;
|
|
146
|
-
|
|
147
|
-
if (!args.noFetch) {
|
|
148
|
-
const authReady = await ensureCliAuth({ args, runtime, stderr });
|
|
149
|
-
if (!authReady) return 1;
|
|
211
|
+
if (!args.appId) {
|
|
212
|
+
stderr.write('Missing required --app <appId>.\n\n');
|
|
213
|
+
stderr.write(helpText());
|
|
214
|
+
return 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const agentSetup = await ensureRequestedAgentSetup({ args, runtime, stdout, stderr });
|
|
218
|
+
if (!agentSetup.ok) return 1;
|
|
219
|
+
|
|
220
|
+
if (!args.noFetch) {
|
|
221
|
+
const authReady = await ensureCliAuth({ args, runtime, stderr });
|
|
222
|
+
if (!authReady) return 1;
|
|
150
223
|
}
|
|
151
224
|
|
|
152
225
|
let plan = await createSetupPlan({
|
|
@@ -247,12 +320,12 @@ export async function runCli(argv, runtime) {
|
|
|
247
320
|
phase: 'apply_completed',
|
|
248
321
|
details: {
|
|
249
322
|
changes,
|
|
250
|
-
validation,
|
|
251
|
-
inspection,
|
|
252
|
-
requiresAgentRestart: Boolean(agentSetup.requiresAgentRestart) ||
|
|
253
|
-
changes.some((change) => (change.type === 'mcp' || change.type === 'codex-mcp') && change.changed),
|
|
254
|
-
},
|
|
255
|
-
});
|
|
323
|
+
validation,
|
|
324
|
+
inspection,
|
|
325
|
+
requiresAgentRestart: Boolean(agentSetup.requiresAgentRestart) ||
|
|
326
|
+
changes.some((change) => (change.type === 'mcp' || change.type === 'codex-mcp') && change.changed),
|
|
327
|
+
},
|
|
328
|
+
});
|
|
256
329
|
const token = await resolveAuthToken({
|
|
257
330
|
env: runtime.env,
|
|
258
331
|
apiBase: args.apiBase ?? DEFAULT_API_BASE,
|
|
@@ -354,17 +427,32 @@ export function parseArgs(rawArgs) {
|
|
|
354
427
|
skipAnalytics: false,
|
|
355
428
|
skipValidate: false,
|
|
356
429
|
noOpen: false,
|
|
357
|
-
noLogin: false,
|
|
358
|
-
skipSetupReport: false,
|
|
359
|
-
skipAgentSetup: false,
|
|
360
|
-
yes: false,
|
|
361
|
-
agent: null,
|
|
362
|
-
url: null,
|
|
430
|
+
noLogin: false,
|
|
431
|
+
skipSetupReport: false,
|
|
432
|
+
skipAgentSetup: false,
|
|
433
|
+
yes: false,
|
|
434
|
+
agent: null,
|
|
435
|
+
url: null,
|
|
363
436
|
contentId: null,
|
|
364
437
|
contractVersion: null,
|
|
365
438
|
artifactId: null,
|
|
439
|
+
sourceArtifactId: null,
|
|
440
|
+
workSessionId: null,
|
|
441
|
+
taskType: null,
|
|
442
|
+
dedupKey: null,
|
|
366
443
|
status: null,
|
|
367
444
|
summary: null,
|
|
445
|
+
commitSha: null,
|
|
446
|
+
prUrl: null,
|
|
447
|
+
deploymentUrl: null,
|
|
448
|
+
deploymentId: null,
|
|
449
|
+
implementedType: null,
|
|
450
|
+
changedFiles: [],
|
|
451
|
+
changedRoutes: [],
|
|
452
|
+
liveUrls: [],
|
|
453
|
+
unknowns: [],
|
|
454
|
+
risks: [],
|
|
455
|
+
nextSteps: [],
|
|
368
456
|
analyticsOptOutReason: null,
|
|
369
457
|
help: false,
|
|
370
458
|
version: false,
|
|
@@ -391,13 +479,13 @@ export function parseArgs(rawArgs) {
|
|
|
391
479
|
else if (arg === '--allow-dirty') parsed.allowDirty = true;
|
|
392
480
|
else if (arg === '--skip-analytics') parsed.skipAnalytics = true;
|
|
393
481
|
else if (arg === '--skip-validate') parsed.skipValidate = true;
|
|
394
|
-
else if (arg === '--no-open') parsed.noOpen = true;
|
|
395
|
-
else if (arg === '--no-login') parsed.noLogin = true;
|
|
396
|
-
else if (arg === '--skip-setup-report') parsed.skipSetupReport = true;
|
|
397
|
-
else if (arg === '--skip-agent-setup') parsed.skipAgentSetup = true;
|
|
398
|
-
else if (arg === '--yes' || arg === '-y') parsed.yes = true;
|
|
399
|
-
else if (arg === '--agent') parsed.agent = parseAgent(args[++index] ?? null);
|
|
400
|
-
else if (arg.startsWith('--agent=')) parsed.agent = parseAgent(arg.slice('--agent='.length));
|
|
482
|
+
else if (arg === '--no-open') parsed.noOpen = true;
|
|
483
|
+
else if (arg === '--no-login') parsed.noLogin = true;
|
|
484
|
+
else if (arg === '--skip-setup-report') parsed.skipSetupReport = true;
|
|
485
|
+
else if (arg === '--skip-agent-setup') parsed.skipAgentSetup = true;
|
|
486
|
+
else if (arg === '--yes' || arg === '-y') parsed.yes = true;
|
|
487
|
+
else if (arg === '--agent') parsed.agent = parseAgent(args[++index] ?? null);
|
|
488
|
+
else if (arg.startsWith('--agent=')) parsed.agent = parseAgent(arg.slice('--agent='.length));
|
|
401
489
|
else if (arg === '--app') parsed.appId = args[++index] ?? null;
|
|
402
490
|
else if (arg.startsWith('--app=')) parsed.appId = arg.slice('--app='.length);
|
|
403
491
|
else if (arg === '--cwd') parsed.cwd = args[++index] ?? null;
|
|
@@ -414,10 +502,41 @@ export function parseArgs(rawArgs) {
|
|
|
414
502
|
else if (arg.startsWith('--contract-version=')) parsed.contractVersion = arg.slice('--contract-version='.length);
|
|
415
503
|
else if (arg === '--artifact') parsed.artifactId = args[++index] ?? null;
|
|
416
504
|
else if (arg.startsWith('--artifact=')) parsed.artifactId = arg.slice('--artifact='.length);
|
|
505
|
+
else if (arg === '--source-artifact') parsed.sourceArtifactId = args[++index] ?? null;
|
|
506
|
+
else if (arg.startsWith('--source-artifact=')) parsed.sourceArtifactId = arg.slice('--source-artifact='.length);
|
|
507
|
+
else if (arg === '--work-session' || arg === '--session') parsed.workSessionId = args[++index] ?? null;
|
|
508
|
+
else if (arg.startsWith('--work-session=')) parsed.workSessionId = arg.slice('--work-session='.length);
|
|
509
|
+
else if (arg.startsWith('--session=')) parsed.workSessionId = arg.slice('--session='.length);
|
|
510
|
+
else if (arg === '--task-type') parsed.taskType = args[++index] ?? null;
|
|
511
|
+
else if (arg.startsWith('--task-type=')) parsed.taskType = arg.slice('--task-type='.length);
|
|
512
|
+
else if (arg === '--dedup-key') parsed.dedupKey = args[++index] ?? null;
|
|
513
|
+
else if (arg.startsWith('--dedup-key=')) parsed.dedupKey = arg.slice('--dedup-key='.length);
|
|
417
514
|
else if (arg === '--status') parsed.status = args[++index] ?? null;
|
|
418
515
|
else if (arg.startsWith('--status=')) parsed.status = arg.slice('--status='.length);
|
|
419
516
|
else if (arg === '--summary') parsed.summary = args[++index] ?? null;
|
|
420
517
|
else if (arg.startsWith('--summary=')) parsed.summary = arg.slice('--summary='.length);
|
|
518
|
+
else if (arg === '--commit') parsed.commitSha = args[++index] ?? null;
|
|
519
|
+
else if (arg.startsWith('--commit=')) parsed.commitSha = arg.slice('--commit='.length);
|
|
520
|
+
else if (arg === '--pr-url') parsed.prUrl = args[++index] ?? null;
|
|
521
|
+
else if (arg.startsWith('--pr-url=')) parsed.prUrl = arg.slice('--pr-url='.length);
|
|
522
|
+
else if (arg === '--deployment-url') parsed.deploymentUrl = args[++index] ?? null;
|
|
523
|
+
else if (arg.startsWith('--deployment-url=')) parsed.deploymentUrl = arg.slice('--deployment-url='.length);
|
|
524
|
+
else if (arg === '--deployment-id') parsed.deploymentId = args[++index] ?? null;
|
|
525
|
+
else if (arg.startsWith('--deployment-id=')) parsed.deploymentId = arg.slice('--deployment-id='.length);
|
|
526
|
+
else if (arg === '--implemented-type') parsed.implementedType = args[++index] ?? null;
|
|
527
|
+
else if (arg.startsWith('--implemented-type=')) parsed.implementedType = arg.slice('--implemented-type='.length);
|
|
528
|
+
else if (arg === '--file') parsed.changedFiles.push(args[++index] ?? '');
|
|
529
|
+
else if (arg.startsWith('--file=')) parsed.changedFiles.push(arg.slice('--file='.length));
|
|
530
|
+
else if (arg === '--route') parsed.changedRoutes.push(args[++index] ?? '');
|
|
531
|
+
else if (arg.startsWith('--route=')) parsed.changedRoutes.push(arg.slice('--route='.length));
|
|
532
|
+
else if (arg === '--live-url') parsed.liveUrls.push(args[++index] ?? '');
|
|
533
|
+
else if (arg.startsWith('--live-url=')) parsed.liveUrls.push(arg.slice('--live-url='.length));
|
|
534
|
+
else if (arg === '--unknown') parsed.unknowns.push(args[++index] ?? '');
|
|
535
|
+
else if (arg.startsWith('--unknown=')) parsed.unknowns.push(arg.slice('--unknown='.length));
|
|
536
|
+
else if (arg === '--risk') parsed.risks.push(args[++index] ?? '');
|
|
537
|
+
else if (arg.startsWith('--risk=')) parsed.risks.push(arg.slice('--risk='.length));
|
|
538
|
+
else if (arg === '--next-step') parsed.nextSteps.push(args[++index] ?? '');
|
|
539
|
+
else if (arg.startsWith('--next-step=')) parsed.nextSteps.push(arg.slice('--next-step='.length));
|
|
421
540
|
else if (arg === '--analytics-opt-out') parsed.analyticsOptOutReason = args[++index] ?? null;
|
|
422
541
|
else if (arg.startsWith('--analytics-opt-out=')) parsed.analyticsOptOutReason = arg.slice('--analytics-opt-out='.length);
|
|
423
542
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -426,178 +545,286 @@ export function parseArgs(rawArgs) {
|
|
|
426
545
|
return parsed;
|
|
427
546
|
}
|
|
428
547
|
|
|
429
|
-
function helpText() {
|
|
430
|
-
return [
|
|
431
|
-
'DistributionOS CLI',
|
|
432
|
-
'',
|
|
433
|
-
'Usage:',
|
|
434
|
-
' distributionos setup --app <appId> [--agent codex|claude|terminal|other] [--api-base <url>] [--cwd <path>] [--json] [--no-fetch]',
|
|
435
|
-
' distributionos setup --app <appId> --apply [--agent codex|claude|terminal|other] [--domain <domain>] [--allow-dirty] [--skip-agent-setup] [--skip-setup-report]',
|
|
548
|
+
function helpText() {
|
|
549
|
+
return [
|
|
550
|
+
'DistributionOS CLI',
|
|
551
|
+
'',
|
|
552
|
+
'Usage:',
|
|
553
|
+
' distributionos setup --app <appId> [--agent codex|claude|terminal|other] [--api-base <url>] [--cwd <path>] [--json] [--no-fetch]',
|
|
554
|
+
' distributionos setup --app <appId> --apply [--agent codex|claude|terminal|other] [--domain <domain>] [--allow-dirty] [--skip-agent-setup] [--skip-setup-report]',
|
|
436
555
|
' distributionos login --app <appId> [--api-base <url>]',
|
|
437
556
|
' distributionos verify --app <appId> --url <liveUrl> [--content-id <id>]',
|
|
438
557
|
' distributionos report-implementation --app <appId> --artifact <artifactId> [--url <liveUrl>] [--summary <text>]',
|
|
558
|
+
' distributionos start-work --app <appId> [--task-type <type>] [--artifact <artifactId>] [--dedup-key <key>] [--summary <text>]',
|
|
559
|
+
' distributionos complete-work --app <appId> --work-session <id> --status <status> --summary <text> [--url <liveUrl>] [--file <path>] [--pr-url <url>]',
|
|
439
560
|
'',
|
|
440
|
-
'Default mode reviews the setup plan without changing files. Run again with --apply after the plan looks right.',
|
|
441
|
-
'For Codex, --agent codex registers the DistributionOS MCP server and runs
|
|
442
|
-
'If the repo has existing uncommitted changes, commit/stash first or use --allow-dirty when you recognize them.',
|
|
561
|
+
'Default mode reviews the setup plan without changing files. Run again with --apply after the plan looks right.',
|
|
562
|
+
'For Codex, --agent codex registers the DistributionOS MCP server, uses the Codex Desktop CLI when available, and runs MCP login before repo setup.',
|
|
563
|
+
'If the repo has existing uncommitted changes, commit/stash first or use --allow-dirty when you recognize them.',
|
|
443
564
|
'',
|
|
444
565
|
'Authentication:',
|
|
445
566
|
' Happy path uses DistributionOS MCP OAuth and stores app-scoped credentials outside the repo.',
|
|
446
567
|
' DISTRIBUTIONOS_API_KEY, DOS_API_KEY, or DISTRIBUTIONOS_TOKEN are advanced fallbacks.',
|
|
447
568
|
' Tokens are never printed or written to committed files.',
|
|
448
569
|
'',
|
|
449
|
-
].join('\n');
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function parseAgent(value) {
|
|
453
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
454
|
-
if (!AGENT_CHOICES.has(normalized)) {
|
|
455
|
-
throw new Error('Invalid --agent value. Use codex, claude, terminal, or other.');
|
|
456
|
-
}
|
|
457
|
-
return normalized;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
async function ensureRequestedAgentSetup({ args, runtime, stdout, stderr }) {
|
|
461
|
-
if (args.skipAgentSetup || args.agent !== 'codex') {
|
|
462
|
-
return { ok: true, requiresAgentRestart: false };
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
stdout.write('Codex MCP setup\n');
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
'
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
570
|
+
].join('\n');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function parseAgent(value) {
|
|
574
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
575
|
+
if (!AGENT_CHOICES.has(normalized)) {
|
|
576
|
+
throw new Error('Invalid --agent value. Use codex, claude, terminal, or other.');
|
|
577
|
+
}
|
|
578
|
+
return normalized;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function ensureRequestedAgentSetup({ args, runtime, stdout, stderr }) {
|
|
582
|
+
if (args.skipAgentSetup || args.agent !== 'codex') {
|
|
583
|
+
return { ok: true, requiresAgentRestart: false };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
stdout.write('Codex MCP setup\n');
|
|
587
|
+
const codexCommand = resolveCodexCommand(runtime);
|
|
588
|
+
stdout.write(`- Using ${codexCommand.label}.\n`);
|
|
589
|
+
const getResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'get', DISTRIBUTIONOS_MCP_NAME]);
|
|
590
|
+
if (getResult.ok) {
|
|
591
|
+
if (!getResult.stdout.includes(DISTRIBUTIONOS_MCP_URL)) {
|
|
592
|
+
stderr.write([
|
|
593
|
+
'Codex already has a distributionos MCP server, but it does not point to the expected DistributionOS URL.',
|
|
594
|
+
`Expected: ${DISTRIBUTIONOS_MCP_URL}`,
|
|
595
|
+
`Codex CLI used: ${codexCommand.command}`,
|
|
596
|
+
'Remove the old distributionos MCP server from Codex, then rerun this setup command.',
|
|
597
|
+
'',
|
|
598
|
+
].join('\n'));
|
|
599
|
+
return { ok: false, requiresAgentRestart: false };
|
|
600
|
+
}
|
|
601
|
+
stdout.write('- DistributionOS MCP server is already registered in Codex.\n');
|
|
602
|
+
} else if (isCommandMissing(getResult.error)) {
|
|
603
|
+
stderr.write([
|
|
604
|
+
'Codex CLI was not found, so DistributionOS cannot connect Codex automatically.',
|
|
605
|
+
'Open this repo in Codex Desktop or install the Codex CLI, then rerun setup.',
|
|
606
|
+
'',
|
|
607
|
+
].join('\n'));
|
|
608
|
+
return { ok: false, requiresAgentRestart: false };
|
|
609
|
+
} else {
|
|
610
|
+
stdout.write('- Adding DistributionOS MCP server to Codex.\n');
|
|
611
|
+
const addResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'add', DISTRIBUTIONOS_MCP_NAME, '--url', DISTRIBUTIONOS_MCP_URL], { stream: true });
|
|
612
|
+
if (!addResult.ok) {
|
|
613
|
+
stderr.write(formatCodexCommandFailure('Codex MCP add', addResult, codexCommand));
|
|
614
|
+
return { ok: false, requiresAgentRestart: false };
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
stdout.write('- Starting Codex MCP OAuth. Approve the browser sign-in if Codex asks.\n');
|
|
619
|
+
const loginResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'login', DISTRIBUTIONOS_MCP_NAME], { stream: true });
|
|
620
|
+
if (!loginResult.ok) {
|
|
621
|
+
stderr.write(formatCodexCommandFailure('Codex MCP login', loginResult, codexCommand));
|
|
622
|
+
return { ok: false, requiresAgentRestart: true };
|
|
623
|
+
}
|
|
624
|
+
const listResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'list']);
|
|
625
|
+
if (!listResult.ok) {
|
|
626
|
+
stderr.write(formatCodexCommandFailure('Codex MCP verification', listResult, codexCommand));
|
|
627
|
+
return { ok: false, requiresAgentRestart: true };
|
|
628
|
+
}
|
|
629
|
+
if (!codexMcpListShowsDistributionOs(listResult.stdout)) {
|
|
630
|
+
stderr.write([
|
|
631
|
+
'Codex MCP login finished, but this Codex runtime does not list DistributionOS yet.',
|
|
632
|
+
`Codex CLI used: ${codexCommand.command}`,
|
|
633
|
+
'Fully quit and reopen Codex Desktop, then rerun the Codex setup message from the app repo.',
|
|
634
|
+
'',
|
|
635
|
+
].join('\n'));
|
|
636
|
+
return { ok: false, requiresAgentRestart: true };
|
|
637
|
+
}
|
|
638
|
+
if (codexMcpListShowsDistributionOsNotLoggedIn(listResult.stdout)) {
|
|
639
|
+
stderr.write([
|
|
640
|
+
'Codex MCP login finished, but this Codex runtime still reports DistributionOS as Not logged in.',
|
|
641
|
+
`Codex CLI used: ${codexCommand.command}`,
|
|
642
|
+
'Rerun the Codex setup message and approve the current browser sign-in while the command is waiting.',
|
|
643
|
+
'',
|
|
644
|
+
].join('\n'));
|
|
645
|
+
return { ok: false, requiresAgentRestart: true };
|
|
646
|
+
}
|
|
647
|
+
stdout.write('- Codex MCP OAuth step completed and verified.\n\n');
|
|
648
|
+
return { ok: true, requiresAgentRestart: true };
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function formatCodexCommandFailure(label, result, codexCommand) {
|
|
652
|
+
const output = oneLine(result.stderr || result.stdout || result.error?.message || '');
|
|
653
|
+
return [
|
|
654
|
+
`${label} did not complete, so Codex cannot use DistributionOS tools yet.`,
|
|
655
|
+
output ? `Last output: ${output}` : '',
|
|
656
|
+
codexCommand?.command ? `Codex CLI used: ${codexCommand.command}` : '',
|
|
657
|
+
'Rerun the Codex setup message, approve the browser sign-in, and wait for the command to say the Codex MCP OAuth step completed and verified.',
|
|
658
|
+
'',
|
|
659
|
+
].filter(Boolean).join('\n');
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function isCommandMissing(error) {
|
|
663
|
+
const message = error?.message ?? '';
|
|
664
|
+
const stderr = error?.stderr ?? '';
|
|
665
|
+
return (
|
|
666
|
+
error?.code === 'ENOENT' ||
|
|
667
|
+
error?.code === 'EPERM' ||
|
|
668
|
+
/spawn\s+(ENOENT|EPERM)/i.test(message) ||
|
|
669
|
+
/not recognized as an internal/i.test(message) ||
|
|
670
|
+
/not recognized as an internal/i.test(stderr)
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function resolveCodexCommand(runtime) {
|
|
675
|
+
const env = runtime?.env ?? process.env;
|
|
676
|
+
const configured = typeof env.CODEX_CLI_PATH === 'string' ? env.CODEX_CLI_PATH.trim() : '';
|
|
677
|
+
if (configured) {
|
|
678
|
+
return { command: configured, label: `Codex CLI at ${configured}` };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const desktopCommand = findCodexDesktopCommand(runtime);
|
|
682
|
+
if (desktopCommand) {
|
|
683
|
+
return { command: desktopCommand, label: `Codex Desktop CLI at ${desktopCommand}` };
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return { command: 'codex', label: 'Codex CLI from PATH' };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function findCodexDesktopCommand(runtime) {
|
|
690
|
+
if (Array.isArray(runtime?.codexCommandCandidates)) {
|
|
691
|
+
return runtime.codexCommandCandidates.find((candidate) => typeof candidate === 'string' && candidate.trim()) ?? null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const env = runtime?.env ?? process.env;
|
|
695
|
+
const homeDir = typeof env.USERPROFILE === 'string' && env.USERPROFILE
|
|
696
|
+
? env.USERPROFILE
|
|
697
|
+
: os.homedir();
|
|
698
|
+
const localAppData = typeof env.LOCALAPPDATA === 'string' && env.LOCALAPPDATA
|
|
699
|
+
? env.LOCALAPPDATA
|
|
700
|
+
: path.join(homeDir, 'AppData', 'Local');
|
|
701
|
+
const codexBinRoot = path.join(localAppData, 'OpenAI', 'Codex', 'bin');
|
|
702
|
+
const executableName = process.platform === 'win32' ? 'codex.exe' : 'codex';
|
|
703
|
+
const candidates = [];
|
|
704
|
+
|
|
705
|
+
const directCandidate = path.join(codexBinRoot, executableName);
|
|
706
|
+
if (existsSync(directCandidate)) {
|
|
707
|
+
candidates.push(directCandidate);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
try {
|
|
711
|
+
for (const entry of readdirSync(codexBinRoot, { withFileTypes: true })) {
|
|
712
|
+
if (!entry.isDirectory()) continue;
|
|
713
|
+
const candidate = path.join(codexBinRoot, entry.name, executableName);
|
|
714
|
+
if (existsSync(candidate)) {
|
|
715
|
+
candidates.push(candidate);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} catch {
|
|
719
|
+
return candidates[0] ?? null;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
candidates.sort((left, right) => fileModifiedAt(right) - fileModifiedAt(left));
|
|
723
|
+
return candidates[0] ?? null;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function fileModifiedAt(filePath) {
|
|
727
|
+
try {
|
|
728
|
+
return statSync(filePath).mtimeMs;
|
|
729
|
+
} catch {
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function codexMcpListShowsDistributionOs(output) {
|
|
735
|
+
return Boolean(findCodexMcpListDistributionOsLine(output));
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function codexMcpListShowsDistributionOsNotLoggedIn(output) {
|
|
739
|
+
const line = findCodexMcpListDistributionOsLine(output);
|
|
740
|
+
return Boolean(line && /not\s+logged\s+in/i.test(line));
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function findCodexMcpListDistributionOsLine(output) {
|
|
744
|
+
return String(output)
|
|
745
|
+
.split(/\r?\n/)
|
|
746
|
+
.map((line) => line.trim())
|
|
747
|
+
.find((line) => /\bdistributionos\b/i.test(line)) ?? null;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function runCodexCommand(runtime, codexCommand, args, options = {}) {
|
|
751
|
+
return runExternalCommand(runtime, codexCommand.command, args, options);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function runExternalCommand(runtime, command, args, options = {}) {
|
|
755
|
+
if (typeof runtime.execFile === 'function') {
|
|
756
|
+
try {
|
|
757
|
+
const result = await runtime.execFile(command, args, options);
|
|
758
|
+
return {
|
|
759
|
+
ok: true,
|
|
760
|
+
stdout: result?.stdout ?? '',
|
|
761
|
+
stderr: result?.stderr ?? '',
|
|
762
|
+
};
|
|
763
|
+
} catch (error) {
|
|
764
|
+
return {
|
|
765
|
+
ok: false,
|
|
766
|
+
stdout: error?.stdout ?? '',
|
|
767
|
+
stderr: error?.stderr ?? '',
|
|
768
|
+
error,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const resolved = resolveCommand(command, args, runtime);
|
|
774
|
+
return new Promise((resolve) => {
|
|
775
|
+
let stdoutText = '';
|
|
776
|
+
let stderrText = '';
|
|
777
|
+
let child;
|
|
778
|
+
try {
|
|
779
|
+
child = spawn(resolved.command, resolved.args, {
|
|
780
|
+
cwd: runtime.cwd,
|
|
781
|
+
env: runtime.env,
|
|
782
|
+
windowsHide: true,
|
|
783
|
+
stdio: ['inherit', 'pipe', 'pipe'],
|
|
784
|
+
});
|
|
785
|
+
} catch (error) {
|
|
786
|
+
resolve({ ok: false, stdout: '', stderr: '', error });
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
child.stdout?.on('data', (chunk) => {
|
|
791
|
+
const text = String(chunk);
|
|
792
|
+
stdoutText += text;
|
|
793
|
+
if (options.stream) runtime.stdout?.write(text);
|
|
794
|
+
});
|
|
795
|
+
child.stderr?.on('data', (chunk) => {
|
|
796
|
+
const text = String(chunk);
|
|
797
|
+
stderrText += text;
|
|
798
|
+
if (options.stream) runtime.stderr?.write(text);
|
|
799
|
+
});
|
|
800
|
+
child.on('error', (error) => {
|
|
801
|
+
resolve({ ok: false, stdout: stdoutText, stderr: stderrText, error });
|
|
802
|
+
});
|
|
803
|
+
child.on('close', (code) => {
|
|
804
|
+
const ok = code === 0;
|
|
805
|
+
resolve({
|
|
806
|
+
ok,
|
|
807
|
+
stdout: stdoutText,
|
|
808
|
+
stderr: stderrText,
|
|
809
|
+
error: ok ? null : Object.assign(new Error(`${command} exited with code ${code}`), { code }),
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function resolveCommand(command, args, runtime) {
|
|
816
|
+
const platform = runtime.platform ?? process.platform;
|
|
817
|
+
if (platform === 'win32') {
|
|
818
|
+
if (path.isAbsolute(command) && /\.(exe|com)$/i.test(command)) {
|
|
819
|
+
return { command, args };
|
|
820
|
+
}
|
|
821
|
+
return {
|
|
822
|
+
command: runtime.env?.ComSpec ?? process.env.ComSpec ?? 'cmd.exe',
|
|
823
|
+
args: ['/d', '/s', '/c', command, ...args],
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
return { command, args };
|
|
827
|
+
}
|
|
601
828
|
|
|
602
829
|
async function ensureCliAuth({ args, runtime, stderr }) {
|
|
603
830
|
const existingToken = await resolveAuthToken({
|
|
@@ -655,6 +882,54 @@ function buildImplementationReportPayload(args) {
|
|
|
655
882
|
};
|
|
656
883
|
}
|
|
657
884
|
|
|
885
|
+
function buildStartWorkPayload(args) {
|
|
886
|
+
return {
|
|
887
|
+
taskType: args.taskType ?? 'general_distribution',
|
|
888
|
+
source: 'cli',
|
|
889
|
+
sourceTool: 'distributionos-cli',
|
|
890
|
+
...(args.summary ? { userRequest: args.summary } : {}),
|
|
891
|
+
...(args.artifactId ? { sourceArtifactId: args.artifactId } : {}),
|
|
892
|
+
...(args.sourceArtifactId ? { sourceArtifactId: args.sourceArtifactId } : {}),
|
|
893
|
+
...(args.dedupKey ? { dedupKey: args.dedupKey } : {}),
|
|
894
|
+
metadata: {
|
|
895
|
+
startedBy: 'distributionos-cli',
|
|
896
|
+
},
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function buildCompleteWorkPayload(args) {
|
|
901
|
+
const liveUrls = [...args.liveUrls];
|
|
902
|
+
if (args.url) liveUrls.unshift(args.url);
|
|
903
|
+
return {
|
|
904
|
+
workSessionId: args.workSessionId,
|
|
905
|
+
status: args.status,
|
|
906
|
+
...(args.summary ? { summary: args.summary } : {}),
|
|
907
|
+
changedFiles: compactArray(args.changedFiles),
|
|
908
|
+
changedRoutes: compactArray(args.changedRoutes),
|
|
909
|
+
liveUrls: compactArray(liveUrls),
|
|
910
|
+
...(args.commitSha ? { commitSha: args.commitSha } : {}),
|
|
911
|
+
...(args.prUrl ? { prUrl: args.prUrl } : {}),
|
|
912
|
+
...(args.deploymentUrl ? { deploymentUrl: args.deploymentUrl } : {}),
|
|
913
|
+
...(args.deploymentId ? { deploymentId: args.deploymentId } : {}),
|
|
914
|
+
...(args.sourceArtifactId ? { sourceArtifactId: args.sourceArtifactId } : {}),
|
|
915
|
+
...(args.artifactId ? { artifactId: args.artifactId } : {}),
|
|
916
|
+
...(args.implementedType ? { implementedType: args.implementedType } : {}),
|
|
917
|
+
...(args.url ? { publishedUrl: args.url } : {}),
|
|
918
|
+
...(args.contentId ? { analyticsIds: { dosContentId: args.contentId } } : {}),
|
|
919
|
+
...(args.analyticsOptOutReason ? { analyticsOptOutReason: args.analyticsOptOutReason } : {}),
|
|
920
|
+
unknowns: compactArray(args.unknowns),
|
|
921
|
+
risks: compactArray(args.risks),
|
|
922
|
+
nextSteps: compactArray(args.nextSteps),
|
|
923
|
+
metadata: {
|
|
924
|
+
reportedBy: 'distributionos-cli',
|
|
925
|
+
},
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function compactArray(values) {
|
|
930
|
+
return values.map((value) => String(value ?? '').trim()).filter(Boolean);
|
|
931
|
+
}
|
|
932
|
+
|
|
658
933
|
function formatVerifyResult(result) {
|
|
659
934
|
const lines = [
|
|
660
935
|
'DistributionOS analytics verification',
|
|
@@ -676,6 +951,39 @@ function formatVerifyResult(result) {
|
|
|
676
951
|
return `${lines.join('\n')}\n`;
|
|
677
952
|
}
|
|
678
953
|
|
|
954
|
+
function formatStartWorkResult(result) {
|
|
955
|
+
const session = result.session ?? result;
|
|
956
|
+
const closeout = result.closeout ?? {};
|
|
957
|
+
const lines = [
|
|
958
|
+
'DistributionOS agent work started',
|
|
959
|
+
`- Work session: ${session.id ?? closeout.workSessionId ?? 'created'}`,
|
|
960
|
+
`- Task type: ${session.taskType ?? closeout.taskType ?? 'general_distribution'}`,
|
|
961
|
+
];
|
|
962
|
+
if (result.reused) {
|
|
963
|
+
lines.push('- Reused existing open session for this dedup key.');
|
|
964
|
+
}
|
|
965
|
+
if (closeout.instruction) {
|
|
966
|
+
lines.push('- Closeout: call complete-work or complete_agent_work once when this meaningful task is done.');
|
|
967
|
+
}
|
|
968
|
+
return `${lines.join('\n')}\n`;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
function formatCompleteWorkResult(result) {
|
|
972
|
+
const session = result.session ?? result;
|
|
973
|
+
const lines = [
|
|
974
|
+
'DistributionOS agent work closeout',
|
|
975
|
+
`- Status: ${session.status ?? result.status ?? 'closed'}`,
|
|
976
|
+
`- Work session: ${session.id ?? result.workSessionId ?? 'updated'}`,
|
|
977
|
+
];
|
|
978
|
+
if (Array.isArray(result.routed) && result.routed.length > 0) {
|
|
979
|
+
lines.push(`- Routed: ${result.routed.join(', ')}`);
|
|
980
|
+
}
|
|
981
|
+
if (result.idempotent) {
|
|
982
|
+
lines.push('- Duplicate closeout ignored; the session was already closed.');
|
|
983
|
+
}
|
|
984
|
+
return `${lines.join('\n')}\n`;
|
|
985
|
+
}
|
|
986
|
+
|
|
679
987
|
function formatReportResult(result) {
|
|
680
988
|
const artifact = result.artifact ?? result;
|
|
681
989
|
const lines = [
|
|
@@ -751,7 +1059,8 @@ function formatApplyResult(plan, changes, initialization, validation, inspection
|
|
|
751
1059
|
lines.push(
|
|
752
1060
|
'- Open this DistributionOS review page now:',
|
|
753
1061
|
` ${reviewUrl}`,
|
|
754
|
-
'- Codex users:
|
|
1062
|
+
'- Codex users: if you used the Codex setup option, the CLI already ran MCP add/login against the Codex runtime and verified it did not report DistributionOS as Not logged in.',
|
|
1063
|
+
'- Then fully quit and reopen Codex Desktop before checking the connection.',
|
|
755
1064
|
'- Claude Code users: if .mcp.json was created or updated, restart or reconnect Claude Code and approve the project MCP server if asked.',
|
|
756
1065
|
'- Other MCP-aware agents: reconnect the agent from this repo so it can load the DistributionOS MCP server.',
|
|
757
1066
|
'- On the review page, copy the connection check prompt into the fresh/reconnected agent chat.',
|
package/src/constants.js
CHANGED
|
@@ -6,9 +6,9 @@ export const CLI_PACKAGE_NAME = '@distributionos/cli';
|
|
|
6
6
|
export const CLI_LOCAL_VERSION = typeof cliPackageJson.version === 'string' ? cliPackageJson.version : '0.0.0-local';
|
|
7
7
|
export const DEFAULT_API_BASE = 'https://distributionos.dev';
|
|
8
8
|
export const MCP_SERVER_URL = 'https://distributionos.dev/api/mcp';
|
|
9
|
-
export const CURRENT_BOOTSTRAP_VERSION = '2026.
|
|
10
|
-
export const CURRENT_ANALYTICS_CONTRACT_VERSION = '2026.06.20';
|
|
11
|
-
export const CURRENT_SETUP_CONTRACT_VERSION = '2026.07.
|
|
9
|
+
export const CURRENT_BOOTSTRAP_VERSION = '2026.07.04.1';
|
|
10
|
+
export const CURRENT_ANALYTICS_CONTRACT_VERSION = '2026.06.20';
|
|
11
|
+
export const CURRENT_SETUP_CONTRACT_VERSION = '2026.07.04.1';
|
|
12
12
|
|
|
13
13
|
export const TOKEN_ENV_NAMES = [
|
|
14
14
|
'DISTRIBUTIONOS_API_KEY',
|
package/src/setup-report.js
CHANGED
|
@@ -16,6 +16,7 @@ const ANALYTICS_BLOCK_RE =
|
|
|
16
16
|
|
|
17
17
|
export async function buildSetupReport(plan, phase = 'plan', details = {}) {
|
|
18
18
|
const detectedCapabilities = new Set([
|
|
19
|
+
'agent.workSessionCloseout',
|
|
19
20
|
'analytics.verificationReady',
|
|
20
21
|
'setup.reporting',
|
|
21
22
|
]);
|
|
@@ -143,10 +144,16 @@ export function formatSetupReportSummary(setupReport) {
|
|
|
143
144
|
if (!localSetup) {
|
|
144
145
|
lines.push('- Server report: pending');
|
|
145
146
|
return lines;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
lines.push(`- Server status: ${localSetup.status}`);
|
|
149
|
-
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lines.push(`- Server status: ${localSetup.status}`);
|
|
150
|
+
if (localSetup.recommendedCliVersion) {
|
|
151
|
+
lines.push(`- Recommended CLI: ${localSetup.recommendedCliVersion}`);
|
|
152
|
+
}
|
|
153
|
+
if (localSetup.latestContractVersion) {
|
|
154
|
+
lines.push(`- Current setup contract: ${localSetup.latestContractVersion}`);
|
|
155
|
+
}
|
|
156
|
+
lines.push(`- Missing capabilities: ${formatList(localSetup.missingCapabilities)}`);
|
|
150
157
|
if (localSetup.status !== 'current') {
|
|
151
158
|
lines.push(`- Review command: ${localSetup.updateCommand}`);
|
|
152
159
|
lines.push(`- Apply command: ${localSetup.applyCommand}`);
|