deepline 0.1.260 → 0.1.261

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/dist/cli/index.js CHANGED
@@ -716,7 +716,7 @@ var SDK_RELEASE = {
716
716
  // Deepline-native radars. Older clients must update before discovering,
717
717
  // checking, or deploying an unlaunched monitor integration.
718
718
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
719
- version: "0.1.260",
719
+ version: "0.1.261",
720
720
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
721
721
  supportPolicy: {
722
722
  minimumSupported: "0.1.53",
@@ -29106,10 +29106,13 @@ function buildSkillsPlan(input2) {
29106
29106
  skillNames: input2.skillNames,
29107
29107
  version: input2.version,
29108
29108
  remove: {
29109
- command: "npx",
29109
+ command: "npm",
29110
29110
  args: [
29111
+ "exec",
29111
29112
  "--yes",
29112
- "skills@latest",
29113
+ "--package=skills@latest",
29114
+ "--",
29115
+ "skills",
29113
29116
  "remove",
29114
29117
  ...scopeArgs,
29115
29118
  "--agent",
@@ -29119,10 +29122,13 @@ function buildSkillsPlan(input2) {
29119
29122
  ]
29120
29123
  },
29121
29124
  install: {
29122
- command: "npx",
29125
+ command: "npm",
29123
29126
  args: [
29127
+ "exec",
29124
29128
  "--yes",
29125
- "skills@latest",
29129
+ "--package=skills@latest",
29130
+ "--",
29131
+ "skills",
29126
29132
  "add",
29127
29133
  skillsIndexUrl(input2.baseUrl),
29128
29134
  "--agent",
@@ -29136,6 +29142,29 @@ function buildSkillsPlan(input2) {
29136
29142
  statePath: skillsStatePathForScope(input2.baseUrl, input2.scope, input2.root)
29137
29143
  };
29138
29144
  }
29145
+ function sortedStrings(value) {
29146
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
29147
+ return null;
29148
+ }
29149
+ return [...value].sort((a, b) => a.localeCompare(b));
29150
+ }
29151
+ function isSkillsPlanCurrent(plan, state) {
29152
+ if (!state || state.scope !== plan.scope || state.skillsVersion !== plan.version) {
29153
+ return false;
29154
+ }
29155
+ const installedAgents = sortedStrings(state.agents);
29156
+ const installedSkillNames = sortedStrings(state.skillNames);
29157
+ if (!installedAgents || !installedSkillNames) return false;
29158
+ return installedAgents.join("\0") === [...plan.agents].sort((a, b) => a.localeCompare(b)).join("\0") && installedSkillNames.join("\0") === [...plan.skillNames].sort((a, b) => a.localeCompare(b)).join("\0");
29159
+ }
29160
+ function readSkillsInstallState(path) {
29161
+ try {
29162
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
29163
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
29164
+ } catch {
29165
+ return null;
29166
+ }
29167
+ }
29139
29168
  function runProcess(command, args, cwd) {
29140
29169
  return new Promise((resolve15, reject) => {
29141
29170
  const child = (0, import_node_child_process3.spawn)(command, args, {
@@ -29159,7 +29188,7 @@ function runProcess(command, args, cwd) {
29159
29188
  });
29160
29189
  });
29161
29190
  }
29162
- async function runSkillsCommand(options) {
29191
+ async function runSkillsCommand(options, dependencies = {}) {
29163
29192
  let scope;
29164
29193
  let root;
29165
29194
  try {
@@ -29181,7 +29210,7 @@ async function runSkillsCommand(options) {
29181
29210
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
29182
29211
  let catalog;
29183
29212
  try {
29184
- catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await fetchSkillCatalog(baseUrl);
29213
+ catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await (dependencies.fetchCatalog ?? fetchSkillCatalog)(baseUrl);
29185
29214
  } catch (error) {
29186
29215
  printCommandEnvelope(
29187
29216
  {
@@ -29205,6 +29234,37 @@ async function runSkillsCommand(options) {
29205
29234
  );
29206
29235
  return 0;
29207
29236
  }
29237
+ if (isSkillsPlanCurrent(plan, readSkillsInstallState(plan.statePath))) {
29238
+ printCommandEnvelope(
29239
+ {
29240
+ ok: true,
29241
+ status: "current",
29242
+ complete: true,
29243
+ changed: false,
29244
+ skipped: true,
29245
+ skipReason: "skills_version_current",
29246
+ scope,
29247
+ agents,
29248
+ skillsVersion: plan.version,
29249
+ skillCount: plan.skillNames.length,
29250
+ statePath: plan.statePath,
29251
+ render: {
29252
+ sections: [
29253
+ {
29254
+ title: "skills",
29255
+ lines: [
29256
+ `Scope: ${scope}`,
29257
+ `Agents: ${agents.join(", ")}`,
29258
+ `Current: ${plan.skillNames.length}`
29259
+ ]
29260
+ }
29261
+ ]
29262
+ }
29263
+ },
29264
+ { json: options.json }
29265
+ );
29266
+ return 0;
29267
+ }
29208
29268
  if (scope === "local" && root) {
29209
29269
  const managedNames = [
29210
29270
  .../* @__PURE__ */ new Set([...plan.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
@@ -29222,7 +29282,8 @@ async function runSkillsCommand(options) {
29222
29282
  `
29223
29283
  );
29224
29284
  try {
29225
- const removeCode = await runProcess(
29285
+ const execute = dependencies.runProcess ?? runProcess;
29286
+ const removeCode = await execute(
29226
29287
  plan.remove.command,
29227
29288
  plan.remove.args,
29228
29289
  root ?? void 0
@@ -29230,7 +29291,7 @@ async function runSkillsCommand(options) {
29230
29291
  if (removeCode !== 0) {
29231
29292
  throw new Error("Could not remove the existing Deepline skills.");
29232
29293
  }
29233
- const installCode = await runProcess(
29294
+ const installCode = await execute(
29234
29295
  plan.install.command,
29235
29296
  plan.install.args,
29236
29297
  root ?? void 0
@@ -29935,6 +29996,85 @@ var import_node_child_process5 = require("child_process");
29935
29996
  var import_node_fs18 = require("fs");
29936
29997
  var import_node_os15 = require("os");
29937
29998
  var import_node_path20 = require("path");
29999
+ var SETUP_PHASE_NAMES = [
30000
+ "cli",
30001
+ "cleanup",
30002
+ "skills",
30003
+ "auth",
30004
+ "verify"
30005
+ ];
30006
+ function initialSetupPhases() {
30007
+ return {
30008
+ cli: { status: "pending" },
30009
+ cleanup: { status: "pending" },
30010
+ skills: { status: "pending" },
30011
+ auth: { status: "pending" },
30012
+ verify: { status: "pending" }
30013
+ };
30014
+ }
30015
+ function isSetupPhaseStatus(value) {
30016
+ return value === "pending" || value === "in_progress" || value === "complete" || value === "waiting" || value === "failed";
30017
+ }
30018
+ function parseSetupPhases(value) {
30019
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30020
+ const source = value;
30021
+ const phases = initialSetupPhases();
30022
+ for (const name of SETUP_PHASE_NAMES) {
30023
+ const phase = source[name];
30024
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
30025
+ return null;
30026
+ }
30027
+ const record = phase;
30028
+ if (!isSetupPhaseStatus(record.status)) return null;
30029
+ phases[name] = {
30030
+ status: record.status,
30031
+ ...typeof record.outcome === "string" ? { outcome: record.outcome } : {},
30032
+ ...typeof record.code === "string" ? { code: record.code } : {}
30033
+ };
30034
+ }
30035
+ return phases;
30036
+ }
30037
+ function phasesFromLegacyStatus(status) {
30038
+ const phases = initialSetupPhases();
30039
+ if (status === "skills_installed" || status === "authorization_pending" || status === "complete") {
30040
+ phases.cli = { status: "complete" };
30041
+ phases.cleanup = { status: "complete" };
30042
+ phases.skills = { status: "complete" };
30043
+ }
30044
+ if (status === "authorization_pending") {
30045
+ phases.auth = { status: "waiting", outcome: "authorization_pending" };
30046
+ } else if (status === "complete") {
30047
+ phases.auth = { status: "complete" };
30048
+ phases.verify = { status: "complete" };
30049
+ }
30050
+ return phases;
30051
+ }
30052
+ function readSetupState(input2) {
30053
+ try {
30054
+ const parsed = JSON.parse(
30055
+ (0, import_node_fs18.readFileSync)(
30056
+ setupStatePath(input2.baseUrl, input2.scope, input2.root),
30057
+ "utf8"
30058
+ )
30059
+ );
30060
+ if (parsed.host !== input2.baseUrl || parsed.scope !== input2.scope || parsed.root !== input2.root || typeof parsed.status !== "string") {
30061
+ return null;
30062
+ }
30063
+ return {
30064
+ status: parsed.status,
30065
+ phases: parseSetupPhases(parsed.phases) ?? phasesFromLegacyStatus(parsed.status)
30066
+ };
30067
+ } catch {
30068
+ return null;
30069
+ }
30070
+ }
30071
+ function selectSetupProgress(previousState) {
30072
+ const resumed = Boolean(previousState && previousState.status !== "complete");
30073
+ return {
30074
+ resumed,
30075
+ phases: resumed ? previousState.phases : initialSetupPhases()
30076
+ };
30077
+ }
29938
30078
  function normalizeScope2(value) {
29939
30079
  if (!value || value === "global") return "global";
29940
30080
  if (value === "local") return "local";
@@ -30105,13 +30245,14 @@ function writeSetupState(input2) {
30105
30245
  path,
30106
30246
  `${JSON.stringify(
30107
30247
  {
30108
- schemaVersion: 1,
30248
+ schemaVersion: 2,
30109
30249
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30110
30250
  cliVersion: SDK_VERSION,
30111
30251
  host: input2.baseUrl,
30112
30252
  scope: input2.scope,
30113
30253
  root: input2.root,
30114
- status: input2.status
30254
+ status: input2.status,
30255
+ phases: input2.phases
30115
30256
  },
30116
30257
  null,
30117
30258
  2
@@ -30121,6 +30262,24 @@ function writeSetupState(input2) {
30121
30262
  );
30122
30263
  return path;
30123
30264
  }
30265
+ function persistSetupProgress(input2) {
30266
+ return writeSetupState({
30267
+ ...input2,
30268
+ status: input2.status ?? "in_progress"
30269
+ });
30270
+ }
30271
+ function completeSetupPhase(phases, phase, outcome) {
30272
+ phases[phase] = {
30273
+ status: "complete",
30274
+ ...outcome ? { outcome } : {}
30275
+ };
30276
+ }
30277
+ function beginSetupPhase(phases, phase) {
30278
+ phases[phase] = { status: "in_progress" };
30279
+ }
30280
+ function failSetupPhase(phases, phase, code) {
30281
+ phases[phase] = { status: "failed", code };
30282
+ }
30124
30283
  function rollbackCommand(scope, root) {
30125
30284
  const prefix = scope === "local" && root ? ` --prefix ${JSON.stringify((0, import_node_path20.join)(root, ".deepline", "runtime"))}` : "";
30126
30285
  return `npm install -g${prefix} --no-audit --no-fund --include=optional --allow-scripts=esbuild deepline@${SDK_VERSION}`;
@@ -30132,6 +30291,42 @@ function setupResumeCommand(baseUrl, scope) {
30132
30291
  const hostPrefix = baseUrl === "https://code.deepline.com" ? "" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} `;
30133
30292
  return `${hostPrefix}deepline setup --scope ${scope} --json`;
30134
30293
  }
30294
+ function setupRetry(input2) {
30295
+ return {
30296
+ phase: input2.phase,
30297
+ command: setupResumeCommand(input2.baseUrl, input2.scope),
30298
+ automatic: true
30299
+ };
30300
+ }
30301
+ function reportSetupPhaseFailure(input2) {
30302
+ failSetupPhase(input2.phases, input2.phase, input2.code);
30303
+ const statePath = persistSetupProgress({
30304
+ baseUrl: input2.baseUrl,
30305
+ scope: input2.scope,
30306
+ root: input2.root,
30307
+ phases: input2.phases,
30308
+ status: "failed"
30309
+ });
30310
+ const retry = setupRetry(input2);
30311
+ printCommandEnvelope(
30312
+ {
30313
+ ok: false,
30314
+ status: "failed",
30315
+ code: input2.code,
30316
+ exitCode: input2.exitCode,
30317
+ scope: input2.scope,
30318
+ message: input2.message,
30319
+ phases: input2.phases,
30320
+ failedPhase: input2.phase,
30321
+ retry,
30322
+ statePath,
30323
+ next: retry.command,
30324
+ ...input2.extra ?? {}
30325
+ },
30326
+ { json: input2.json }
30327
+ );
30328
+ return input2.exitCode;
30329
+ }
30135
30330
  async function readAuthStatus(authScope) {
30136
30331
  try {
30137
30332
  const captured = await captureStdout2(
@@ -30151,63 +30346,44 @@ async function readAuthStatus(authScope) {
30151
30346
  }
30152
30347
  function reportSetupAuthStatusFailure(input2) {
30153
30348
  if (!input2.auth.error) return null;
30154
- printCommandEnvelope(
30155
- {
30156
- ok: false,
30157
- status: "failed",
30158
- code: "AUTH_STATUS_FAILED",
30159
- exitCode: 4,
30160
- scope: input2.scope,
30161
- message: "Deepline could not verify authorization with the configured host.",
30162
- next: `Check network access, then rerun: deepline setup --scope ${input2.scope} --json`,
30163
- authScope: input2.authScope
30164
- },
30165
- { json: input2.json }
30166
- );
30167
- return 4;
30349
+ return reportSetupPhaseFailure({
30350
+ baseUrl: input2.baseUrl,
30351
+ scope: input2.scope,
30352
+ root: input2.root,
30353
+ phases: input2.phases,
30354
+ phase: "auth",
30355
+ code: "AUTH_STATUS_FAILED",
30356
+ exitCode: 4,
30357
+ message: "Deepline could not verify authorization with the configured host.",
30358
+ json: input2.json,
30359
+ extra: { authScope: input2.authScope }
30360
+ });
30168
30361
  }
30169
- async function runDoctorCommand(options) {
30170
- let scope;
30171
- let root;
30172
- try {
30173
- scope = normalizeScope2(options.scope);
30174
- root = resolveScopeRoot(scope);
30175
- } catch (error) {
30176
- printCommandEnvelope(
30177
- {
30178
- ok: false,
30179
- status: "failed",
30180
- code: "INVALID_SETUP_SCOPE",
30181
- exitCode: 2,
30182
- message: error instanceof Error ? error.message : String(error)
30183
- },
30184
- { json: options.json }
30185
- );
30186
- return 2;
30187
- }
30188
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30189
- const skillsStatePath = skillsStatePathForScope(baseUrl, scope, root);
30362
+ function buildDoctorAssessment(input2) {
30363
+ const skillsStatePath = skillsStatePathForScope(
30364
+ input2.baseUrl,
30365
+ input2.scope,
30366
+ input2.root
30367
+ );
30190
30368
  const skillsState = parseCapturedJson(safeRead(skillsStatePath));
30191
- const authScope = authScopeForSetup(scope);
30192
- const apiKey = scope === "local" ? resolveProjectApiKeyForBaseUrl(baseUrl) : resolveGlobalApiKeyForBaseUrl(baseUrl);
30193
- const projectAuth = scope === "local" && apiKey ? getResolvedProjectAuthSource(baseUrl, apiKey) : null;
30194
- const authStatus = await readAuthStatus(authScope);
30195
- const connected = authStatus.payload?.connected === true;
30196
- const authScopeOk = scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30197
- const skillsOk = skillsState?.scope === scope && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30369
+ const apiKey = input2.scope === "local" ? resolveProjectApiKeyForBaseUrl(input2.baseUrl) : resolveGlobalApiKeyForBaseUrl(input2.baseUrl);
30370
+ const projectAuth = input2.scope === "local" && apiKey ? getResolvedProjectAuthSource(input2.baseUrl, apiKey) : null;
30371
+ const connected = input2.authStatus.payload?.connected === true;
30372
+ const authScopeOk = input2.scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30373
+ const skillsOk = skillsState?.scope === input2.scope && typeof skillsState.skillsVersion === "string" && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30198
30374
  const runningCliPath = process.argv[1] ? (0, import_node_path20.resolve)(process.argv[1]) : null;
30199
- const globalCli = scope === "global" ? inspectGlobalCliAvailability() : null;
30375
+ const globalCli = input2.scope === "global" ? inspectGlobalCliAvailability() : null;
30200
30376
  const pathGlobalCli = globalCli?.path ?? null;
30201
- const cliPath = scope === "global" ? pathGlobalCli : runningCliPath;
30202
- const cliScopeOk = scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30203
- root && runningCliPath?.includes((0, import_node_path20.join)(root, ".deepline", "runtime"))
30377
+ const cliPath = input2.scope === "global" ? pathGlobalCli : runningCliPath;
30378
+ const cliScopeOk = input2.scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30379
+ input2.root && runningCliPath?.includes((0, import_node_path20.join)(input2.root, ".deepline", "runtime"))
30204
30380
  );
30205
30381
  const checks = {
30206
30382
  cli: {
30207
30383
  ok: Boolean(cliPath) && cliScopeOk,
30208
30384
  version: SDK_VERSION,
30209
30385
  path: cliPath,
30210
- scope
30386
+ scope: input2.scope
30211
30387
  },
30212
30388
  skills: {
30213
30389
  ok: skillsOk,
@@ -30218,16 +30394,48 @@ async function runDoctorCommand(options) {
30218
30394
  auth: {
30219
30395
  ok: connected && authScopeOk,
30220
30396
  scope: projectAuth ? "local" : apiKey ? "global" : null,
30221
- status: authStatus.payload?.status ?? "not_connected"
30397
+ status: input2.authStatus.payload?.status ?? "not_connected"
30222
30398
  },
30223
30399
  api: {
30224
- ok: connected && authStatus.exitCode === 0,
30225
- host: baseUrl,
30226
- workspace: authStatus.payload?.workspace ?? null,
30400
+ ok: connected && input2.authStatus.exitCode === 0,
30401
+ host: input2.baseUrl,
30402
+ workspace: input2.authStatus.payload?.workspace ?? null,
30227
30403
  providerSpend: false
30228
30404
  }
30229
30405
  };
30230
- const ok = Object.values(checks).every((check) => check.ok);
30406
+ return {
30407
+ ok: Object.values(checks).every((check) => check.ok),
30408
+ checks
30409
+ };
30410
+ }
30411
+ async function runDoctorCommand(options) {
30412
+ let scope;
30413
+ let root;
30414
+ try {
30415
+ scope = normalizeScope2(options.scope);
30416
+ root = resolveScopeRoot(scope);
30417
+ } catch (error) {
30418
+ printCommandEnvelope(
30419
+ {
30420
+ ok: false,
30421
+ status: "failed",
30422
+ code: "INVALID_SETUP_SCOPE",
30423
+ exitCode: 2,
30424
+ message: error instanceof Error ? error.message : String(error)
30425
+ },
30426
+ { json: options.json }
30427
+ );
30428
+ return 2;
30429
+ }
30430
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30431
+ const authScope = authScopeForSetup(scope);
30432
+ const authStatus = await readAuthStatus(authScope);
30433
+ const { ok, checks } = buildDoctorAssessment({
30434
+ baseUrl,
30435
+ scope,
30436
+ root,
30437
+ authStatus
30438
+ });
30231
30439
  const quickstart = setupQuickstartCommand(baseUrl);
30232
30440
  printCommandEnvelope(
30233
30441
  {
@@ -30253,11 +30461,16 @@ async function runDoctorCommand(options) {
30253
30461
  return ok ? 0 : 7;
30254
30462
  }
30255
30463
  function pendingResult(input2) {
30464
+ input2.phases.auth = {
30465
+ status: "waiting",
30466
+ outcome: "authorization_pending"
30467
+ };
30256
30468
  const statePath = writeSetupState({
30257
30469
  baseUrl: input2.baseUrl,
30258
30470
  scope: input2.scope,
30259
30471
  root: input2.root,
30260
- status: "authorization_pending"
30472
+ status: "authorization_pending",
30473
+ phases: input2.phases
30261
30474
  });
30262
30475
  if (input2.authorizationUrl) {
30263
30476
  process.stderr.write(`Authorize Deepline: ${input2.authorizationUrl}
@@ -30271,6 +30484,14 @@ function pendingResult(input2) {
30271
30484
  scope: input2.scope,
30272
30485
  authorizationUrl: input2.authorizationUrl || null,
30273
30486
  statePath,
30487
+ phases: input2.phases,
30488
+ currentPhase: "auth",
30489
+ resumed: input2.resumed,
30490
+ retry: setupRetry({
30491
+ baseUrl: input2.baseUrl,
30492
+ scope: input2.scope,
30493
+ phase: "auth"
30494
+ }),
30274
30495
  next: `Approve the link, then run: ${setupResumeCommand(input2.baseUrl, input2.scope)}`,
30275
30496
  render: {
30276
30497
  sections: [
@@ -30301,84 +30522,146 @@ async function runSetupCommand(options) {
30301
30522
  status: "failed",
30302
30523
  code: "INVALID_SETUP_SCOPE",
30303
30524
  exitCode: 2,
30304
- message: error instanceof Error ? error.message : String(error)
30525
+ message: error instanceof Error ? error.message : String(error),
30526
+ phases: {
30527
+ ...initialSetupPhases(),
30528
+ cli: { status: "failed", code: "INVALID_SETUP_SCOPE" }
30529
+ },
30530
+ failedPhase: "cli"
30305
30531
  },
30306
30532
  { json: options.json }
30307
30533
  );
30308
30534
  return 2;
30309
30535
  }
30310
30536
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30311
- if (scope === "global") {
30312
- const globalCli = inspectGlobalCliAvailability();
30313
- if (!globalCli.ok) {
30314
- const installedButUnreachable = Boolean(globalCli.persistentPath);
30315
- printCommandEnvelope(
30316
- {
30317
- ok: false,
30318
- status: "failed",
30319
- code: installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED",
30320
- exitCode: 7,
30537
+ const previousState = readSetupState({ baseUrl, scope, root });
30538
+ const { resumed, phases } = selectSetupProgress(previousState);
30539
+ if (phases.cli.status !== "complete") {
30540
+ beginSetupPhase(phases, "cli");
30541
+ persistSetupProgress({ baseUrl, scope, root, phases });
30542
+ if (scope === "global") {
30543
+ const globalCli = inspectGlobalCliAvailability();
30544
+ if (!globalCli.ok) {
30545
+ const installedButUnreachable = Boolean(globalCli.persistentPath);
30546
+ const code = installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED";
30547
+ return reportSetupPhaseFailure({
30548
+ baseUrl,
30321
30549
  scope,
30322
- persistentPath: globalCli.persistentPath,
30550
+ root,
30551
+ phases,
30552
+ phase: "cli",
30553
+ code,
30554
+ exitCode: 7,
30323
30555
  message: installedButUnreachable ? `The global Deepline executable is not reachable on PATH: ${globalCli.persistentPath}` : "No persistent global Deepline executable was found.",
30324
- next: "Use the automatic project-local fallback in https://code.deepline.com/SKILL.md"
30325
- },
30326
- { json: options.json }
30327
- );
30328
- return 7;
30556
+ json: options.json,
30557
+ extra: {
30558
+ persistentPath: globalCli.persistentPath,
30559
+ fallback: "https://code.deepline.com/INSTALL.md"
30560
+ }
30561
+ });
30562
+ }
30329
30563
  }
30330
- }
30331
- const conflict = inspectPathConflict();
30332
- if (conflict) {
30333
- printCommandEnvelope(
30334
- {
30335
- ok: false,
30336
- status: "failed",
30564
+ const conflict = inspectPathConflict();
30565
+ if (conflict) {
30566
+ return reportSetupPhaseFailure({
30567
+ baseUrl,
30568
+ scope,
30569
+ root,
30570
+ phases,
30571
+ phase: "cli",
30337
30572
  code: "PATH_CONFLICT",
30338
30573
  exitCode: 7,
30339
- path: conflict,
30340
30574
  message: `An unknown executable named deepline is first on PATH: ${conflict}`,
30341
- next: "Move or rename that executable, then rerun: deepline setup --json"
30342
- },
30343
- { json: options.json }
30344
- );
30345
- return 7;
30575
+ json: options.json,
30576
+ extra: { path: conflict }
30577
+ });
30578
+ }
30579
+ completeSetupPhase(phases, "cli", "available");
30580
+ persistSetupProgress({ baseUrl, scope, root, phases });
30346
30581
  }
30347
- const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30348
- if (removedLegacyPaths.length > 0) {
30349
- process.stderr.write(
30350
- `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30582
+ if (phases.cleanup.status !== "complete") {
30583
+ beginSetupPhase(phases, "cleanup");
30584
+ persistSetupProgress({ baseUrl, scope, root, phases });
30585
+ try {
30586
+ const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30587
+ if (removedLegacyPaths.length > 0) {
30588
+ process.stderr.write(
30589
+ `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30351
30590
  `
30352
- );
30591
+ );
30592
+ }
30593
+ completeSetupPhase(
30594
+ phases,
30595
+ "cleanup",
30596
+ removedLegacyPaths.length > 0 ? "removed_legacy_paths" : "clean"
30597
+ );
30598
+ persistSetupProgress({ baseUrl, scope, root, phases });
30599
+ } catch (error) {
30600
+ return reportSetupPhaseFailure({
30601
+ baseUrl,
30602
+ scope,
30603
+ root,
30604
+ phases,
30605
+ phase: "cleanup",
30606
+ code: "LEGACY_CLEANUP_FAILED",
30607
+ exitCode: 5,
30608
+ message: error instanceof Error ? error.message : String(error),
30609
+ json: options.json
30610
+ });
30611
+ }
30353
30612
  }
30354
- process.stderr.write(`Installing Deepline skills (${scope})...
30613
+ let skillsPayload = null;
30614
+ if (phases.skills.status !== "complete") {
30615
+ beginSetupPhase(phases, "skills");
30616
+ persistSetupProgress({ baseUrl, scope, root, phases });
30617
+ process.stderr.write(`Installing Deepline skills (${scope})...
30355
30618
  `);
30356
- const skills = await captureStdout2(
30357
- () => runSkillsCommand({ scope, json: true })
30358
- );
30359
- const skillsPayload = parseCapturedJson(skills.stdout);
30360
- if (skills.exitCode !== 0) {
30361
- printCommandEnvelope(
30362
- {
30363
- ok: false,
30364
- status: "failed",
30619
+ const skills = await captureStdout2(
30620
+ () => runSkillsCommand({ scope, json: true })
30621
+ );
30622
+ skillsPayload = parseCapturedJson(skills.stdout);
30623
+ if (skills.exitCode !== 0) {
30624
+ return reportSetupPhaseFailure({
30625
+ baseUrl,
30626
+ scope,
30627
+ root,
30628
+ phases,
30629
+ phase: "skills",
30365
30630
  code: "SKILLS_INSTALL_FAILED",
30366
30631
  exitCode: skills.exitCode,
30367
- scope,
30368
- detail: skillsPayload,
30369
- next: `deepline skills --scope ${scope} --json`
30370
- },
30371
- { json: options.json }
30632
+ message: typeof skillsPayload?.message === "string" ? skillsPayload.message : "Deepline skills could not be installed.",
30633
+ json: options.json,
30634
+ extra: { detail: skillsPayload }
30635
+ });
30636
+ }
30637
+ completeSetupPhase(
30638
+ phases,
30639
+ "skills",
30640
+ skillsPayload?.status === "current" ? "current" : "installed"
30641
+ );
30642
+ writeSetupState({
30643
+ baseUrl,
30644
+ scope,
30645
+ root,
30646
+ status: "skills_installed",
30647
+ phases
30648
+ });
30649
+ } else {
30650
+ skillsPayload = parseCapturedJson(
30651
+ safeRead(skillsStatePathForScope(baseUrl, scope, root))
30372
30652
  );
30373
- return skills.exitCode;
30374
30653
  }
30375
- writeSetupState({ baseUrl, scope, root, status: "skills_installed" });
30376
30654
  const authScope = authScopeForSetup(scope);
30655
+ beginSetupPhase(phases, "auth");
30656
+ persistSetupProgress({ baseUrl, scope, root, phases });
30377
30657
  let auth = await readAuthStatus(authScope);
30378
30658
  const initialAuthFailure = reportSetupAuthStatusFailure({
30379
30659
  auth,
30660
+ baseUrl,
30380
30661
  authScope,
30381
30662
  scope,
30663
+ root,
30664
+ phases,
30382
30665
  json: options.json
30383
30666
  });
30384
30667
  if (initialAuthFailure !== null) return initialAuthFailure;
@@ -30393,8 +30676,11 @@ async function runSetupCommand(options) {
30393
30676
  auth = await readAuthStatus(authScope);
30394
30677
  const resumedAuthFailure = reportSetupAuthStatusFailure({
30395
30678
  auth,
30679
+ baseUrl,
30396
30680
  authScope,
30397
30681
  scope,
30682
+ root,
30683
+ phases,
30398
30684
  json: options.json
30399
30685
  });
30400
30686
  if (resumedAuthFailure !== null) return resumedAuthFailure;
@@ -30405,7 +30691,9 @@ async function runSetupCommand(options) {
30405
30691
  baseUrl,
30406
30692
  scope,
30407
30693
  root,
30694
+ phases,
30408
30695
  authorizationUrl: stillPending.claimUrl,
30696
+ resumed,
30409
30697
  json: options.json
30410
30698
  });
30411
30699
  }
@@ -30422,24 +30710,30 @@ async function runSetupCommand(options) {
30422
30710
  );
30423
30711
  printCapturedAuthorizationUrl(registered.stdout);
30424
30712
  if (registered.exitCode !== 0) {
30425
- printCommandEnvelope(
30426
- {
30427
- ok: false,
30428
- status: "failed",
30429
- code: "AUTH_REGISTER_FAILED",
30430
- exitCode: registered.exitCode,
30431
- scope,
30432
- next: `deepline auth register --auth-scope ${authScope}`
30433
- },
30434
- { json: options.json }
30435
- );
30436
- return registered.exitCode;
30713
+ return reportSetupPhaseFailure({
30714
+ baseUrl,
30715
+ scope,
30716
+ root,
30717
+ phases,
30718
+ phase: "auth",
30719
+ code: "AUTH_REGISTER_FAILED",
30720
+ exitCode: registered.exitCode,
30721
+ message: "Deepline browser authorization could not be started.",
30722
+ json: options.json,
30723
+ extra: {
30724
+ detail: parseCapturedJson(registered.stdout),
30725
+ authScope
30726
+ }
30727
+ });
30437
30728
  }
30438
30729
  auth = await readAuthStatus(authScope);
30439
30730
  const registeredAuthFailure = reportSetupAuthStatusFailure({
30440
30731
  auth,
30732
+ baseUrl,
30441
30733
  authScope,
30442
30734
  scope,
30735
+ root,
30736
+ phases,
30443
30737
  json: options.json
30444
30738
  });
30445
30739
  if (registeredAuthFailure !== null) return registeredAuthFailure;
@@ -30449,40 +30743,60 @@ async function runSetupCommand(options) {
30449
30743
  baseUrl,
30450
30744
  scope,
30451
30745
  root,
30746
+ phases,
30452
30747
  authorizationUrl: pending?.claimUrl ?? "",
30748
+ resumed,
30453
30749
  json: options.json
30454
30750
  });
30455
30751
  }
30456
30752
  }
30457
- process.stderr.write("Verifying Deepline setup...\n");
30458
- const doctor = await captureStdout2(
30459
- () => runDoctorCommand({ scope, json: true })
30460
- );
30461
- const doctorPayload = parseCapturedJson(doctor.stdout);
30462
- if (doctor.exitCode !== 0) {
30463
- printCommandEnvelope(
30464
- {
30465
- ok: false,
30466
- status: "failed",
30467
- code: "DOCTOR_FAILED",
30468
- exitCode: doctor.exitCode,
30469
- scope,
30753
+ completeSetupPhase(phases, "auth", "connected");
30754
+ beginSetupPhase(phases, "verify");
30755
+ persistSetupProgress({ baseUrl, scope, root, phases });
30756
+ const assessment = buildDoctorAssessment({
30757
+ baseUrl,
30758
+ scope,
30759
+ root,
30760
+ authStatus: auth
30761
+ });
30762
+ const quickstart = setupQuickstartCommand(baseUrl);
30763
+ const doctorPayload = {
30764
+ ok: assessment.ok,
30765
+ status: assessment.ok ? "complete" : "failed",
30766
+ code: assessment.ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30767
+ exitCode: assessment.ok ? 0 : 7,
30768
+ checks: assessment.checks,
30769
+ next: assessment.ok ? quickstart : `deepline doctor --scope ${scope} --json`
30770
+ };
30771
+ if (!assessment.ok) {
30772
+ return reportSetupPhaseFailure({
30773
+ baseUrl,
30774
+ scope,
30775
+ root,
30776
+ phases,
30777
+ phase: "verify",
30778
+ code: "DOCTOR_FAILED",
30779
+ exitCode: 7,
30780
+ message: "Deepline setup verification found one or more failed checks.",
30781
+ json: options.json,
30782
+ extra: {
30470
30783
  doctor: doctorPayload,
30471
- next: "Review the doctor result and choose a repair, then rerun: deepline setup --json"
30472
- },
30473
- { json: options.json }
30474
- );
30475
- return doctor.exitCode;
30784
+ diagnostic: {
30785
+ command: `deepline doctor --scope ${scope} --json`
30786
+ }
30787
+ }
30788
+ });
30476
30789
  }
30790
+ completeSetupPhase(phases, "verify", "verified");
30477
30791
  const statePath = writeSetupState({
30478
30792
  baseUrl,
30479
30793
  scope,
30480
30794
  root,
30481
- status: "complete"
30795
+ status: "complete",
30796
+ phases
30482
30797
  });
30483
30798
  const doctorChecks = asRecord2(doctorPayload?.checks);
30484
30799
  const apiCheck = asRecord2(doctorChecks?.api);
30485
- const quickstart = setupQuickstartCommand(baseUrl);
30486
30800
  printCommandEnvelope(
30487
30801
  {
30488
30802
  ok: true,
@@ -30495,6 +30809,10 @@ async function runSetupCommand(options) {
30495
30809
  rollbackCommand: rollbackCommand(scope, root),
30496
30810
  statePath,
30497
30811
  doctor: doctorPayload,
30812
+ phases,
30813
+ currentPhase: null,
30814
+ failedPhase: null,
30815
+ resumed,
30498
30816
  next: quickstart,
30499
30817
  render: {
30500
30818
  sections: [
@@ -30517,8 +30835,9 @@ function registerSetupCommands(program) {
30517
30835
  "after",
30518
30836
  `
30519
30837
  Notes:
30520
- Setup is idempotent. Bare setup resumes pending browser authorization.
30838
+ Setup is idempotent. Bare setup resumes the first incomplete or failed phase.
30521
30839
  It installs skills before auth and does not run quickstart.
30840
+ JSON output includes phase status and an exact retry command.
30522
30841
 
30523
30842
  Examples:
30524
30843
  deepline setup --json