@distributionos/cli 0.1.18 → 0.1.20
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 +4 -0
- package/src/cli.js +370 -171
- package/src/constants.js +3 -3
- package/src/setup-report.js +37 -11
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
|
@@ -24,6 +24,10 @@ export function buildLocalBootstrapBlock(version = CURRENT_BOOTSTRAP_VERSION, ap
|
|
|
24
24
|
'',
|
|
25
25
|
'This repo is linked to DistributionOS.',
|
|
26
26
|
'',
|
|
27
|
+
'Session preflight: when starting a fresh agent chat in this repo, call check_distributionos_connection first, then fetch the current instructions below, then call list_agent_alerts. 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
|
+
'Startup alerts: after get_agent_instructions, call list_agent_alerts for this app. If alerts are returned, summarize them to the user before unrelated work. For SEO incidents, call get_seo_incident for full evidence before editing code, report each fix attempt with report_seo_incident_attempt, and verify with verify_seo_incident_fix.',
|
|
30
|
+
'',
|
|
27
31
|
'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
32
|
'',
|
|
29
33
|
instructionCall,
|
package/src/cli.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
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';
|
|
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';
|
|
6
6
|
import {
|
|
7
7
|
ensureAnalyticsSite,
|
|
8
|
+
completeAgentWork,
|
|
8
9
|
reportSetupInstallation,
|
|
9
10
|
reportImplementation,
|
|
10
11
|
resolveAuthToken,
|
|
12
|
+
startAgentWork,
|
|
11
13
|
verifyAnalyticsInstall,
|
|
12
14
|
} from './api.js';
|
|
13
15
|
import { loginWithOAuth } from './oauth.js';
|
|
@@ -132,6 +134,74 @@ export async function runCli(argv, runtime) {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
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
|
+
|
|
135
205
|
if (args.command !== 'setup') {
|
|
136
206
|
stderr.write(`Unknown command: ${args.command}\n\n`);
|
|
137
207
|
stderr.write(helpText());
|
|
@@ -366,8 +436,23 @@ export function parseArgs(rawArgs) {
|
|
|
366
436
|
contentId: null,
|
|
367
437
|
contractVersion: null,
|
|
368
438
|
artifactId: null,
|
|
439
|
+
sourceArtifactId: null,
|
|
440
|
+
workSessionId: null,
|
|
441
|
+
taskType: null,
|
|
442
|
+
dedupKey: null,
|
|
369
443
|
status: null,
|
|
370
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: [],
|
|
371
456
|
analyticsOptOutReason: null,
|
|
372
457
|
help: false,
|
|
373
458
|
version: false,
|
|
@@ -417,10 +502,41 @@ export function parseArgs(rawArgs) {
|
|
|
417
502
|
else if (arg.startsWith('--contract-version=')) parsed.contractVersion = arg.slice('--contract-version='.length);
|
|
418
503
|
else if (arg === '--artifact') parsed.artifactId = args[++index] ?? null;
|
|
419
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);
|
|
420
514
|
else if (arg === '--status') parsed.status = args[++index] ?? null;
|
|
421
515
|
else if (arg.startsWith('--status=')) parsed.status = arg.slice('--status='.length);
|
|
422
516
|
else if (arg === '--summary') parsed.summary = args[++index] ?? null;
|
|
423
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));
|
|
424
540
|
else if (arg === '--analytics-opt-out') parsed.analyticsOptOutReason = args[++index] ?? null;
|
|
425
541
|
else if (arg.startsWith('--analytics-opt-out=')) parsed.analyticsOptOutReason = arg.slice('--analytics-opt-out='.length);
|
|
426
542
|
else throw new Error(`Unknown argument: ${arg}`);
|
|
@@ -439,9 +555,11 @@ function helpText() {
|
|
|
439
555
|
' distributionos login --app <appId> [--api-base <url>]',
|
|
440
556
|
' distributionos verify --app <appId> --url <liveUrl> [--content-id <id>]',
|
|
441
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>]',
|
|
442
560
|
'',
|
|
443
561
|
'Default mode reviews the setup plan without changing files. Run again with --apply after the plan looks right.',
|
|
444
|
-
'For Codex, --agent codex registers the DistributionOS MCP server, uses the Codex Desktop CLI when available, and runs MCP login before repo setup.',
|
|
562
|
+
'For Codex, --agent codex registers the DistributionOS MCP server, uses the Codex Desktop CLI when available, and runs MCP login before repo setup.',
|
|
445
563
|
'If the repo has existing uncommitted changes, commit/stash first or use --allow-dirty when you recognize them.',
|
|
446
564
|
'',
|
|
447
565
|
'Authentication:',
|
|
@@ -463,83 +581,83 @@ function parseAgent(value) {
|
|
|
463
581
|
async function ensureRequestedAgentSetup({ args, runtime, stdout, stderr }) {
|
|
464
582
|
if (args.skipAgentSetup || args.agent !== 'codex') {
|
|
465
583
|
return { ok: true, requiresAgentRestart: false };
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
stdout.write('Codex MCP setup\n');
|
|
469
|
-
const codexCommand = resolveCodexCommand(runtime);
|
|
470
|
-
stdout.write(`- Using ${codexCommand.label}.\n`);
|
|
471
|
-
const getResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'get', DISTRIBUTIONOS_MCP_NAME]);
|
|
472
|
-
if (getResult.ok) {
|
|
473
|
-
if (!getResult.stdout.includes(DISTRIBUTIONOS_MCP_URL)) {
|
|
474
|
-
stderr.write([
|
|
475
|
-
'Codex already has a distributionos MCP server, but it does not point to the expected DistributionOS URL.',
|
|
476
|
-
`Expected: ${DISTRIBUTIONOS_MCP_URL}`,
|
|
477
|
-
`Codex CLI used: ${codexCommand.command}`,
|
|
478
|
-
'Remove the old distributionos MCP server from Codex, then rerun this setup command.',
|
|
479
|
-
'',
|
|
480
|
-
].join('\n'));
|
|
481
|
-
return { ok: false, 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 };
|
|
482
600
|
}
|
|
483
601
|
stdout.write('- DistributionOS MCP server is already registered in Codex.\n');
|
|
484
|
-
} else if (isCommandMissing(getResult.error)) {
|
|
485
|
-
stderr.write([
|
|
486
|
-
'Codex CLI was not found, so DistributionOS cannot connect Codex automatically.',
|
|
487
|
-
'Open this repo in Codex Desktop or install the Codex CLI, then rerun setup.',
|
|
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.',
|
|
488
606
|
'',
|
|
489
607
|
].join('\n'));
|
|
490
608
|
return { ok: false, requiresAgentRestart: false };
|
|
491
|
-
} else {
|
|
492
|
-
stdout.write('- Adding DistributionOS MCP server to Codex.\n');
|
|
493
|
-
const addResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'add', DISTRIBUTIONOS_MCP_NAME, '--url', DISTRIBUTIONOS_MCP_URL], { stream: true });
|
|
494
|
-
if (!addResult.ok) {
|
|
495
|
-
stderr.write(formatCodexCommandFailure('Codex MCP add', addResult, codexCommand));
|
|
496
|
-
return { ok: false, requiresAgentRestart: false };
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
stdout.write('- Starting Codex MCP OAuth. Approve the browser sign-in if Codex asks.\n');
|
|
501
|
-
const loginResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'login', DISTRIBUTIONOS_MCP_NAME], { stream: true });
|
|
502
|
-
if (!loginResult.ok) {
|
|
503
|
-
stderr.write(formatCodexCommandFailure('Codex MCP login', loginResult, codexCommand));
|
|
504
|
-
return { ok: false, requiresAgentRestart: true };
|
|
505
|
-
}
|
|
506
|
-
const listResult = await runCodexCommand(runtime, codexCommand, ['mcp', 'list']);
|
|
507
|
-
if (!listResult.ok) {
|
|
508
|
-
stderr.write(formatCodexCommandFailure('Codex MCP verification', listResult, codexCommand));
|
|
509
|
-
return { ok: false, requiresAgentRestart: true };
|
|
510
|
-
}
|
|
511
|
-
if (!codexMcpListShowsDistributionOs(listResult.stdout)) {
|
|
512
|
-
stderr.write([
|
|
513
|
-
'Codex MCP login finished, but this Codex runtime does not list DistributionOS yet.',
|
|
514
|
-
`Codex CLI used: ${codexCommand.command}`,
|
|
515
|
-
'Fully quit and reopen Codex Desktop, then rerun the Codex setup message from the app repo.',
|
|
516
|
-
'',
|
|
517
|
-
].join('\n'));
|
|
518
|
-
return { ok: false, requiresAgentRestart: true };
|
|
519
|
-
}
|
|
520
|
-
if (codexMcpListShowsDistributionOsNotLoggedIn(listResult.stdout)) {
|
|
521
|
-
stderr.write([
|
|
522
|
-
'Codex MCP login finished, but this Codex runtime still reports DistributionOS as Not logged in.',
|
|
523
|
-
`Codex CLI used: ${codexCommand.command}`,
|
|
524
|
-
'Rerun the Codex setup message and approve the current browser sign-in while the command is waiting.',
|
|
525
|
-
'',
|
|
526
|
-
].join('\n'));
|
|
527
|
-
return { ok: false, requiresAgentRestart: true };
|
|
528
|
-
}
|
|
529
|
-
stdout.write('- Codex MCP OAuth step completed and verified.\n\n');
|
|
530
|
-
return { ok: true, requiresAgentRestart: true };
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function formatCodexCommandFailure(label, result, codexCommand) {
|
|
534
|
-
const output = oneLine(result.stderr || result.stdout || result.error?.message || '');
|
|
535
|
-
return [
|
|
536
|
-
`${label} did not complete, so Codex cannot use DistributionOS tools yet.`,
|
|
537
|
-
output ? `Last output: ${output}` : '',
|
|
538
|
-
codexCommand?.command ? `Codex CLI used: ${codexCommand.command}` : '',
|
|
539
|
-
'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.',
|
|
540
|
-
'',
|
|
541
|
-
].filter(Boolean).join('\n');
|
|
542
|
-
}
|
|
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
|
+
}
|
|
543
661
|
|
|
544
662
|
function isCommandMissing(error) {
|
|
545
663
|
const message = error?.message ?? '';
|
|
@@ -553,85 +671,85 @@ function isCommandMissing(error) {
|
|
|
553
671
|
);
|
|
554
672
|
}
|
|
555
673
|
|
|
556
|
-
function resolveCodexCommand(runtime) {
|
|
557
|
-
const env = runtime?.env ?? process.env;
|
|
558
|
-
const configured = typeof env.CODEX_CLI_PATH === 'string' ? env.CODEX_CLI_PATH.trim() : '';
|
|
559
|
-
if (configured) {
|
|
560
|
-
return { command: configured, label: `Codex CLI at ${configured}` };
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const desktopCommand = findCodexDesktopCommand(runtime);
|
|
564
|
-
if (desktopCommand) {
|
|
565
|
-
return { command: desktopCommand, label: `Codex Desktop CLI at ${desktopCommand}` };
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
return { command: 'codex', label: 'Codex CLI from PATH' };
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function findCodexDesktopCommand(runtime) {
|
|
572
|
-
if (Array.isArray(runtime?.codexCommandCandidates)) {
|
|
573
|
-
return runtime.codexCommandCandidates.find((candidate) => typeof candidate === 'string' && candidate.trim()) ?? null;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const env = runtime?.env ?? process.env;
|
|
577
|
-
const homeDir = typeof env.USERPROFILE === 'string' && env.USERPROFILE
|
|
578
|
-
? env.USERPROFILE
|
|
579
|
-
: os.homedir();
|
|
580
|
-
const localAppData = typeof env.LOCALAPPDATA === 'string' && env.LOCALAPPDATA
|
|
581
|
-
? env.LOCALAPPDATA
|
|
582
|
-
: path.join(homeDir, 'AppData', 'Local');
|
|
583
|
-
const codexBinRoot = path.join(localAppData, 'OpenAI', 'Codex', 'bin');
|
|
584
|
-
const executableName = process.platform === 'win32' ? 'codex.exe' : 'codex';
|
|
585
|
-
const candidates = [];
|
|
586
|
-
|
|
587
|
-
const directCandidate = path.join(codexBinRoot, executableName);
|
|
588
|
-
if (existsSync(directCandidate)) {
|
|
589
|
-
candidates.push(directCandidate);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
try {
|
|
593
|
-
for (const entry of readdirSync(codexBinRoot, { withFileTypes: true })) {
|
|
594
|
-
if (!entry.isDirectory()) continue;
|
|
595
|
-
const candidate = path.join(codexBinRoot, entry.name, executableName);
|
|
596
|
-
if (existsSync(candidate)) {
|
|
597
|
-
candidates.push(candidate);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
} catch {
|
|
601
|
-
return candidates[0] ?? null;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
candidates.sort((left, right) => fileModifiedAt(right) - fileModifiedAt(left));
|
|
605
|
-
return candidates[0] ?? null;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function fileModifiedAt(filePath) {
|
|
609
|
-
try {
|
|
610
|
-
return statSync(filePath).mtimeMs;
|
|
611
|
-
} catch {
|
|
612
|
-
return 0;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function codexMcpListShowsDistributionOs(output) {
|
|
617
|
-
return Boolean(findCodexMcpListDistributionOsLine(output));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function codexMcpListShowsDistributionOsNotLoggedIn(output) {
|
|
621
|
-
const line = findCodexMcpListDistributionOsLine(output);
|
|
622
|
-
return Boolean(line && /not\s+logged\s+in/i.test(line));
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
function findCodexMcpListDistributionOsLine(output) {
|
|
626
|
-
return String(output)
|
|
627
|
-
.split(/\r?\n/)
|
|
628
|
-
.map((line) => line.trim())
|
|
629
|
-
.find((line) => /\bdistributionos\b/i.test(line)) ?? null;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
async function runCodexCommand(runtime, codexCommand, args, options = {}) {
|
|
633
|
-
return runExternalCommand(runtime, codexCommand.command, args, options);
|
|
634
|
-
}
|
|
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
|
+
}
|
|
635
753
|
|
|
636
754
|
async function runExternalCommand(runtime, command, args, options = {}) {
|
|
637
755
|
if (typeof runtime.execFile === 'function') {
|
|
@@ -694,15 +812,15 @@ async function runExternalCommand(runtime, command, args, options = {}) {
|
|
|
694
812
|
});
|
|
695
813
|
}
|
|
696
814
|
|
|
697
|
-
function resolveCommand(command, args, runtime) {
|
|
698
|
-
const platform = runtime.platform ?? process.platform;
|
|
699
|
-
if (platform === 'win32') {
|
|
700
|
-
if (path.isAbsolute(command) && /\.(exe|com)$/i.test(command)) {
|
|
701
|
-
return { command, args };
|
|
702
|
-
}
|
|
703
|
-
return {
|
|
704
|
-
command: runtime.env?.ComSpec ?? process.env.ComSpec ?? 'cmd.exe',
|
|
705
|
-
args: ['/d', '/s', '/c', command, ...args],
|
|
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],
|
|
706
824
|
};
|
|
707
825
|
}
|
|
708
826
|
return { command, args };
|
|
@@ -764,6 +882,54 @@ function buildImplementationReportPayload(args) {
|
|
|
764
882
|
};
|
|
765
883
|
}
|
|
766
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
|
+
|
|
767
933
|
function formatVerifyResult(result) {
|
|
768
934
|
const lines = [
|
|
769
935
|
'DistributionOS analytics verification',
|
|
@@ -785,6 +951,39 @@ function formatVerifyResult(result) {
|
|
|
785
951
|
return `${lines.join('\n')}\n`;
|
|
786
952
|
}
|
|
787
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
|
+
|
|
788
987
|
function formatReportResult(result) {
|
|
789
988
|
const artifact = result.artifact ?? result;
|
|
790
989
|
const lines = [
|
|
@@ -858,11 +1057,11 @@ function formatApplyResult(plan, changes, initialization, validation, inspection
|
|
|
858
1057
|
lines.push('', 'Next step');
|
|
859
1058
|
if (reviewUrl) {
|
|
860
1059
|
lines.push(
|
|
861
|
-
'- Open this DistributionOS review page now:',
|
|
862
|
-
` ${reviewUrl}`,
|
|
863
|
-
'- 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.',
|
|
864
|
-
'- Then fully quit and reopen Codex Desktop before checking the connection.',
|
|
865
|
-
'- Claude Code users: if .mcp.json was created or updated, restart or reconnect Claude Code and approve the project MCP server if asked.',
|
|
1060
|
+
'- Open this DistributionOS review page now:',
|
|
1061
|
+
` ${reviewUrl}`,
|
|
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.',
|
|
1064
|
+
'- Claude Code users: if .mcp.json was created or updated, restart or reconnect Claude Code and approve the project MCP server if asked.',
|
|
866
1065
|
'- Other MCP-aware agents: reconnect the agent from this repo so it can load the DistributionOS MCP server.',
|
|
867
1066
|
'- On the review page, copy the connection check prompt into the fresh/reconnected agent chat.',
|
|
868
1067
|
'- If your agent only sees DistributionOS authentication tools, copy the auth prompt from the review page and complete OAuth before continuing.',
|
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.09';
|
|
10
|
+
export const CURRENT_ANALYTICS_CONTRACT_VERSION = '2026.06.20';
|
|
11
|
+
export const CURRENT_SETUP_CONTRACT_VERSION = '2026.07.09';
|
|
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
|
]);
|
|
@@ -23,10 +24,13 @@ export async function buildSetupReport(plan, phase = 'plan', details = {}) {
|
|
|
23
24
|
let blockedReason = null;
|
|
24
25
|
|
|
25
26
|
const bootstrapVersion = detectBootstrapVersion(plan);
|
|
26
|
-
if (bootstrapVersion && plan.bootstrap.action === 'none') {
|
|
27
|
-
detectedCapabilities.add('agent.bootstrap');
|
|
28
|
-
detectedCapabilities.add('agent.liveInstructions');
|
|
29
|
-
|
|
27
|
+
if (bootstrapVersion && plan.bootstrap.action === 'none') {
|
|
28
|
+
detectedCapabilities.add('agent.bootstrap');
|
|
29
|
+
detectedCapabilities.add('agent.liveInstructions');
|
|
30
|
+
if (compareVersion(bootstrapVersion, '2026.07.09') >= 0) {
|
|
31
|
+
detectedCapabilities.add('agent.startupAlerts');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
30
34
|
managedFiles.push({
|
|
31
35
|
path: plan.bootstrap.target,
|
|
32
36
|
type: 'agent.bootstrap',
|
|
@@ -146,6 +150,12 @@ export function formatSetupReportSummary(setupReport) {
|
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
lines.push(`- Server status: ${localSetup.status}`);
|
|
153
|
+
if (localSetup.recommendedCliVersion) {
|
|
154
|
+
lines.push(`- Recommended CLI: ${localSetup.recommendedCliVersion}`);
|
|
155
|
+
}
|
|
156
|
+
if (localSetup.latestContractVersion) {
|
|
157
|
+
lines.push(`- Current setup contract: ${localSetup.latestContractVersion}`);
|
|
158
|
+
}
|
|
149
159
|
lines.push(`- Missing capabilities: ${formatList(localSetup.missingCapabilities)}`);
|
|
150
160
|
if (localSetup.status !== 'current') {
|
|
151
161
|
lines.push(`- Review command: ${localSetup.updateCommand}`);
|
|
@@ -157,13 +167,29 @@ export function formatSetupReportSummary(setupReport) {
|
|
|
157
167
|
return lines;
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
function detectBootstrapVersion(plan) {
|
|
161
|
-
const current = (plan.repo.instructionFiles ?? []).find((file) => file.hasManagedDistributionOsBlock);
|
|
162
|
-
return current?.managedDistributionOsVersion ??
|
|
163
|
-
(plan.bootstrap.action === 'none' ? CURRENT_BOOTSTRAP_VERSION : null);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
function detectBootstrapVersion(plan) {
|
|
171
|
+
const current = (plan.repo.instructionFiles ?? []).find((file) => file.hasManagedDistributionOsBlock);
|
|
172
|
+
return current?.managedDistributionOsVersion ??
|
|
173
|
+
(plan.bootstrap.action === 'none' ? CURRENT_BOOTSTRAP_VERSION : null);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function compareVersion(left, right) {
|
|
177
|
+
const a = String(left ?? '').split(/[.-]/).map(toComparableNumber);
|
|
178
|
+
const b = String(right ?? '').split(/[.-]/).map(toComparableNumber);
|
|
179
|
+
const length = Math.max(a.length, b.length);
|
|
180
|
+
for (let index = 0; index < length; index += 1) {
|
|
181
|
+
const diff = (a[index] ?? 0) - (b[index] ?? 0);
|
|
182
|
+
if (diff !== 0) return diff;
|
|
183
|
+
}
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function toComparableNumber(value) {
|
|
188
|
+
const parsed = Number.parseInt(value, 10);
|
|
189
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function inspectAnalyticsInstall(plan) {
|
|
167
193
|
const target = plan.analytics.layoutTarget;
|
|
168
194
|
if (!target) {
|
|
169
195
|
return {
|