deepline 0.1.260 → 0.1.262

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.
@@ -701,7 +701,7 @@ var SDK_RELEASE = {
701
701
  // Deepline-native radars. Older clients must update before discovering,
702
702
  // checking, or deploying an unlaunched monitor integration.
703
703
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
704
- version: "0.1.260",
704
+ version: "0.1.262",
705
705
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
706
706
  supportPolicy: {
707
707
  minimumSupported: "0.1.53",
@@ -26664,6 +26664,33 @@ function extractSummaryFields(payload) {
26664
26664
  return {};
26665
26665
  }
26666
26666
 
26667
+ // ../shared_libs/plays/tool-category-descriptions.ts
26668
+ var TOOL_CATEGORY_DESCRIPTIONS = {
26669
+ company_search: "Find companies matching firmographic, ad, or web criteria.",
26670
+ people_search: "Find people matching role, company, or persona criteria.",
26671
+ people_enrich: "Enrich a known person with contact, social, and profile data.",
26672
+ company_enrich: "Enrich a known company with firmographic and account data.",
26673
+ email_finder: "Find work or personal email addresses for a person.",
26674
+ email_verify: "Validate email deliverability and catch-all status.",
26675
+ phone_finder: "Find mobile or direct phone numbers for a person.",
26676
+ phone_verify: "Validate phone-number reachability and line type.",
26677
+ research: "Research accounts, people, news, and signals for context and scoring.",
26678
+ autocomplete: "Resolve names and entities to canonical catalog records.",
26679
+ automation: "Kick off provider-side jobs, exports, and asynchronous workflows.",
26680
+ outbound_tools: "Run outbound sequencing, deliverability, and campaign operations.",
26681
+ admin: "Provider-side status, metadata, and account operations behind other tools.",
26682
+ smb: "Look up small-business listings, local records, and public filings.",
26683
+ identity_resolution: "Match a partial identity to a single resolved person or company.",
26684
+ reverse_lookup: "Resolve an email, phone, or profile back to a person.",
26685
+ enrichment: "General-purpose record enrichment across people and companies.",
26686
+ batch: "Run an enrichment or search operation over many rows at once.",
26687
+ premium: "Higher-cost tools with premium provider coverage.",
26688
+ free: "Free tools that do not spend Deepline credits."
26689
+ };
26690
+ function describeToolCategory(category) {
26691
+ return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
26692
+ }
26693
+
26667
26694
  // src/cli/commands/model-description.ts
26668
26695
  function formatModelDescription(description) {
26669
26696
  const lines = [];
@@ -26830,21 +26857,181 @@ function zeroMatchRender(emptyResult) {
26830
26857
  ]
26831
26858
  };
26832
26859
  }
26833
- async function listTools(args) {
26860
+ function firstSentence(text) {
26861
+ const clean = (text ?? "").replace(/\s+/g, " ").trim();
26862
+ if (!clean) return "";
26863
+ const match = clean.match(/^.*?[.!?](?=\s|$)/);
26864
+ return (match ? match[0] : clean).trim();
26865
+ }
26866
+ function requiredInputIds(tool) {
26867
+ const inputSchema = recordField2(
26868
+ tool,
26869
+ "inputSchema",
26870
+ "input_schema"
26871
+ );
26872
+ return toolInputFieldsForDisplay(inputSchema).filter((field) => field.required === true).map((field) => String(field.name ?? "")).filter(Boolean);
26873
+ }
26874
+ function shortPricingHint(tool) {
26875
+ const pricing = recordField2(
26876
+ tool,
26877
+ "pricing"
26878
+ );
26879
+ const credits = numberField2(pricing, "creditsPerUnit", "credits_per_unit");
26880
+ const unit = stringField2(pricing, "unit");
26881
+ if (credits !== null) {
26882
+ return `${formatDecimal(credits)} cr/${unit || "unit"}`;
26883
+ }
26884
+ const summary = stringField2(pricing, "summary");
26885
+ if (summary) return summary;
26886
+ const displayText = stringField2(pricing, "displayText", "display_text");
26887
+ return displayText || null;
26888
+ }
26889
+ function aggregateCategorySummaries(tools) {
26890
+ const counts = /* @__PURE__ */ new Map();
26891
+ for (const tool of tools) {
26892
+ for (const category of tool.categories ?? []) {
26893
+ counts.set(category, (counts.get(category) ?? 0) + 1);
26894
+ }
26895
+ }
26896
+ return [...counts.entries()].map(([name, count]) => ({
26897
+ name,
26898
+ count,
26899
+ description: describeToolCategory(name)
26900
+ })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
26901
+ }
26902
+ function categoryEnumerationRender(summaries) {
26903
+ const nameWidth = summaries.reduce(
26904
+ (max, item) => Math.max(max, item.name.length),
26905
+ 0
26906
+ );
26907
+ const countWidth = summaries.reduce(
26908
+ (max, item) => Math.max(max, String(item.count).length),
26909
+ 0
26910
+ );
26911
+ return {
26912
+ sections: [
26913
+ {
26914
+ title: `${summaries.length} tool categories:`,
26915
+ lines: summaries.map((item) => {
26916
+ const name = item.name.padEnd(nameWidth);
26917
+ const count = String(item.count).padStart(countWidth);
26918
+ const definition = item.description ? ` - ${item.description}` : "";
26919
+ return `${name} ${count}${definition}`;
26920
+ })
26921
+ },
26922
+ {
26923
+ title: "next",
26924
+ lines: ["Pass a category: deepline tools list email_finder"]
26925
+ }
26926
+ ]
26927
+ };
26928
+ }
26929
+ async function listToolCategories(emitJson, compact) {
26930
+ const client2 = new DeeplineClient();
26931
+ const rawTools = await client2.listTools({ compact });
26932
+ const items = rawTools.map(toListedTool);
26933
+ const summaries = aggregateCategorySummaries(rawTools);
26934
+ const outputItems = compact ? items.map(compactTool) : items;
26935
+ printCommandEnvelope(
26936
+ {
26937
+ tools: outputItems,
26938
+ count: outputItems.length,
26939
+ categories: summaries,
26940
+ filters: {
26941
+ categories: []
26942
+ },
26943
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26944
+ render: categoryEnumerationRender(summaries)
26945
+ },
26946
+ { json: emitJson }
26947
+ );
26948
+ return 0;
26949
+ }
26950
+ function unknownCategoryError(category, summaries, emitJson) {
26951
+ const known = summaries.map((item) => item.name);
26952
+ const message = `Unknown tool category "${category}". Run \`deepline tools list\` to browse categories.`;
26953
+ if (emitJson) {
26954
+ printJson({
26955
+ ok: false,
26956
+ exitCode: 4,
26957
+ code: "TOOL_CATEGORY_NOT_FOUND",
26958
+ message,
26959
+ category,
26960
+ categories: summaries,
26961
+ next: "deepline tools list"
26962
+ });
26963
+ return 4;
26964
+ }
26965
+ const render = categoryEnumerationRender(summaries);
26966
+ console.error(message);
26967
+ console.error("");
26968
+ console.error(renderCommandEnvelopeText({ render }).trimEnd());
26969
+ console.error("");
26970
+ console.error(`Valid categories: ${known.join(", ")}`);
26971
+ return 4;
26972
+ }
26973
+ async function listToolsInCategory(category, emitJson) {
26974
+ const client2 = new DeeplineClient();
26975
+ const rawTools = await client2.listTools({
26976
+ categories: category,
26977
+ compact: false
26978
+ });
26979
+ const allTools = await client2.listTools({ compact: true });
26980
+ const summaries = aggregateCategorySummaries(allTools);
26981
+ if (!summaries.some((item) => item.name === category)) {
26982
+ return unknownCategoryError(category, summaries, emitJson);
26983
+ }
26984
+ const rows = rawTools.filter((tool) => tool.categories?.includes(category)).map((tool) => ({
26985
+ id: tool.toolId,
26986
+ provider: tool.provider,
26987
+ required: requiredInputIds(tool),
26988
+ pricing: shortPricingHint(tool),
26989
+ description: firstSentence(tool.description)
26990
+ })).sort((a, b) => a.id.localeCompare(b.id));
26991
+ const idWidth = rows.reduce((max, row) => Math.max(max, row.id.length), 0);
26992
+ const render = {
26993
+ sections: [
26994
+ {
26995
+ title: `${rows.length} tools in ${category}:`,
26996
+ lines: rows.map((row) => {
26997
+ const id = row.id.padEnd(idWidth);
26998
+ const required = row.required.length ? `(${row.required.join(", ")})` : "(no required inputs)";
26999
+ const pricing = row.pricing ? ` [${row.pricing}]` : "";
27000
+ const description = row.description ? ` - ${row.description}` : "";
27001
+ return `${id} ${required}${pricing}${description}`;
27002
+ })
27003
+ },
27004
+ {
27005
+ title: "next",
27006
+ lines: [
27007
+ "Run `deepline tools describe <toolId>` for the full contract."
27008
+ ]
27009
+ }
27010
+ ]
27011
+ };
27012
+ printCommandEnvelope(
27013
+ {
27014
+ category,
27015
+ total: rows.length,
27016
+ tools: rows,
27017
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
27018
+ render
27019
+ },
27020
+ { json: emitJson }
27021
+ );
27022
+ return 0;
27023
+ }
27024
+ async function listToolsForCategoryFilter(requestedCategories, emitJson, compact) {
26834
27025
  const client2 = new DeeplineClient();
26835
- const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
26836
- const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
26837
- const compact = !args.includes("--full");
26838
- const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
26839
27026
  const items = (await client2.listTools({
26840
- ...categoryFilter ? { categories: categoryFilter } : {},
27027
+ categories: requestedCategories.join(","),
26841
27028
  compact
26842
27029
  })).map(toListedTool).filter(
26843
- (item) => requestedCategories.length === 0 || requestedCategories.some(
27030
+ (item) => requestedCategories.some(
26844
27031
  (category) => item.categories.includes(category)
26845
27032
  )
26846
27033
  );
26847
- const emptyResult = items.length === 0 && requestedCategories.length > 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
27034
+ const emptyResult = items.length === 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26848
27035
  const render = {
26849
27036
  sections: emptyResult ? zeroMatchRender(emptyResult).sections : [
26850
27037
  {
@@ -26875,10 +27062,41 @@ async function listTools(args) {
26875
27062
  commandTemplates: TOOL_COMMAND_TEMPLATES,
26876
27063
  render
26877
27064
  },
26878
- { json: argsWantJson(args) }
27065
+ { json: emitJson }
26879
27066
  );
26880
27067
  return 0;
26881
27068
  }
27069
+ async function listTools(category, options = {}) {
27070
+ const positional = category?.trim() ?? "";
27071
+ const flagCategories = (options.categories ?? "").split(",").map((item) => item.trim()).filter(Boolean);
27072
+ const compact = options.compact !== false;
27073
+ const emitJson = options.json === true || shouldEmitJson();
27074
+ if (positional && flagCategories.length > 0) {
27075
+ const flagIsSameSingle = flagCategories.length === 1 && flagCategories[0] === positional;
27076
+ if (!flagIsSameSingle) {
27077
+ const message = `Category "${positional}" and --categories ${flagCategories.join(",")} disagree. Pass one: deepline tools list ${positional}`;
27078
+ if (emitJson) {
27079
+ printJson({
27080
+ ok: false,
27081
+ exitCode: 2,
27082
+ code: "TOOL_LIST_CATEGORY_CONFLICT",
27083
+ message,
27084
+ next: `deepline tools list ${positional}`
27085
+ });
27086
+ } else {
27087
+ console.error(message);
27088
+ }
27089
+ return 2;
27090
+ }
27091
+ }
27092
+ if (positional) {
27093
+ return listToolsInCategory(positional, emitJson);
27094
+ }
27095
+ if (flagCategories.length > 0) {
27096
+ return listToolsForCategoryFilter(flagCategories, emitJson, compact);
27097
+ }
27098
+ return listToolCategories(emitJson, compact);
27099
+ }
26882
27100
  async function searchTools(queryInput, options = {}) {
26883
27101
  const query = queryInput?.trim() ?? "";
26884
27102
  const hasStructuredSearch = Boolean(
@@ -27077,27 +27295,37 @@ Output:
27077
27295
  Use execute to run a tool.
27078
27296
  `
27079
27297
  );
27080
- tools.command("list").description("List available tools.").addHelpText(
27298
+ tools.command("list [category]").description("Browse tool categories, or list every tool in a category.").addHelpText(
27081
27299
  "after",
27082
27300
  `
27083
27301
  Notes:
27084
- Inventory command for known tool ids. Use search for ranked discovery by
27085
- intent, aliases, descriptions, and schema fields.
27302
+ Bare \`tools list\` prints the category enumeration: one line per category with
27303
+ its tool count and definition. Pass a category to list every tool in it, one
27304
+ succinct row each (required inputs, Deepline pricing hint, first sentence).
27305
+ The listing is exhaustive and never truncated. Use search for ranked discovery
27306
+ by intent, aliases, descriptions, and schema fields.
27307
+
27308
+ --categories is a back-compat inventory filter. Passing it alone returns the
27309
+ legacy flat inventory envelope (one row per tool). The positional category is
27310
+ the newer exhaustive per-category view. If both are given they must name the
27311
+ same category (the positional view wins); otherwise it is a usage error.
27086
27312
 
27087
27313
  Examples:
27088
27314
  deepline tools list
27315
+ deepline tools list email_finder
27316
+ deepline tools list email_finder --json
27089
27317
  deepline tools list --categories email_finder --json
27090
27318
  deepline tools search email --json
27091
27319
  `
27092
27320
  ).option(
27093
27321
  "--categories <categories>",
27094
- "Comma-separated categories to filter inventory"
27095
- ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
27096
- process.exitCode = await listTools([
27097
- ...options.categories ? ["--categories", options.categories] : [],
27098
- ...options.compact ? ["--compact"] : [],
27099
- ...options.json ? ["--json"] : []
27100
- ]);
27322
+ "Comma-separated categories filter (back-compat alias for the positional)"
27323
+ ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (category, options) => {
27324
+ process.exitCode = await listTools(category, {
27325
+ categories: typeof options.categories === "string" ? options.categories : void 0,
27326
+ compact: options.compact !== false,
27327
+ json: Boolean(options.json)
27328
+ });
27101
27329
  });
27102
27330
  const addToolSearchCommand = (command) => command.description(
27103
27331
  "Search available tools by intent query or structured filters."
@@ -28940,7 +29168,7 @@ import {
28940
29168
  existsSync as existsSync12,
28941
29169
  mkdirSync as mkdirSync10,
28942
29170
  realpathSync as realpathSync3,
28943
- readFileSync as readFileSync12,
29171
+ readFileSync as readFileSync13,
28944
29172
  renameSync,
28945
29173
  rmSync as rmSync4,
28946
29174
  unlinkSync,
@@ -28951,7 +29179,7 @@ import { dirname as dirname13, isAbsolute as isAbsolute3, join as join15, relati
28951
29179
 
28952
29180
  // src/cli/commands/skills.ts
28953
29181
  import { spawn as spawn3 } from "child_process";
28954
- import { existsSync as existsSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync14 } from "fs";
29182
+ import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync14 } from "fs";
28955
29183
  import { homedir as homedir11 } from "os";
28956
29184
  import { dirname as dirname12, join as join14 } from "path";
28957
29185
 
@@ -29163,10 +29391,13 @@ function buildSkillsPlan(input2) {
29163
29391
  skillNames: input2.skillNames,
29164
29392
  version: input2.version,
29165
29393
  remove: {
29166
- command: "npx",
29394
+ command: "npm",
29167
29395
  args: [
29396
+ "exec",
29168
29397
  "--yes",
29169
- "skills@latest",
29398
+ "--package=skills@latest",
29399
+ "--",
29400
+ "skills",
29170
29401
  "remove",
29171
29402
  ...scopeArgs,
29172
29403
  "--agent",
@@ -29176,10 +29407,13 @@ function buildSkillsPlan(input2) {
29176
29407
  ]
29177
29408
  },
29178
29409
  install: {
29179
- command: "npx",
29410
+ command: "npm",
29180
29411
  args: [
29412
+ "exec",
29181
29413
  "--yes",
29182
- "skills@latest",
29414
+ "--package=skills@latest",
29415
+ "--",
29416
+ "skills",
29183
29417
  "add",
29184
29418
  skillsIndexUrl(input2.baseUrl),
29185
29419
  "--agent",
@@ -29193,6 +29427,29 @@ function buildSkillsPlan(input2) {
29193
29427
  statePath: skillsStatePathForScope(input2.baseUrl, input2.scope, input2.root)
29194
29428
  };
29195
29429
  }
29430
+ function sortedStrings(value) {
29431
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
29432
+ return null;
29433
+ }
29434
+ return [...value].sort((a, b) => a.localeCompare(b));
29435
+ }
29436
+ function isSkillsPlanCurrent(plan, state) {
29437
+ if (!state || state.scope !== plan.scope || state.skillsVersion !== plan.version) {
29438
+ return false;
29439
+ }
29440
+ const installedAgents = sortedStrings(state.agents);
29441
+ const installedSkillNames = sortedStrings(state.skillNames);
29442
+ if (!installedAgents || !installedSkillNames) return false;
29443
+ 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");
29444
+ }
29445
+ function readSkillsInstallState(path) {
29446
+ try {
29447
+ const parsed = JSON.parse(readFileSync12(path, "utf8"));
29448
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
29449
+ } catch {
29450
+ return null;
29451
+ }
29452
+ }
29196
29453
  function runProcess(command, args, cwd) {
29197
29454
  return new Promise((resolve15, reject) => {
29198
29455
  const child = spawn3(command, args, {
@@ -29216,7 +29473,7 @@ function runProcess(command, args, cwd) {
29216
29473
  });
29217
29474
  });
29218
29475
  }
29219
- async function runSkillsCommand(options) {
29476
+ async function runSkillsCommand(options, dependencies = {}) {
29220
29477
  let scope;
29221
29478
  let root;
29222
29479
  try {
@@ -29238,7 +29495,7 @@ async function runSkillsCommand(options) {
29238
29495
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
29239
29496
  let catalog;
29240
29497
  try {
29241
- catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await fetchSkillCatalog(baseUrl);
29498
+ catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await (dependencies.fetchCatalog ?? fetchSkillCatalog)(baseUrl);
29242
29499
  } catch (error) {
29243
29500
  printCommandEnvelope(
29244
29501
  {
@@ -29262,6 +29519,37 @@ async function runSkillsCommand(options) {
29262
29519
  );
29263
29520
  return 0;
29264
29521
  }
29522
+ if (isSkillsPlanCurrent(plan, readSkillsInstallState(plan.statePath))) {
29523
+ printCommandEnvelope(
29524
+ {
29525
+ ok: true,
29526
+ status: "current",
29527
+ complete: true,
29528
+ changed: false,
29529
+ skipped: true,
29530
+ skipReason: "skills_version_current",
29531
+ scope,
29532
+ agents,
29533
+ skillsVersion: plan.version,
29534
+ skillCount: plan.skillNames.length,
29535
+ statePath: plan.statePath,
29536
+ render: {
29537
+ sections: [
29538
+ {
29539
+ title: "skills",
29540
+ lines: [
29541
+ `Scope: ${scope}`,
29542
+ `Agents: ${agents.join(", ")}`,
29543
+ `Current: ${plan.skillNames.length}`
29544
+ ]
29545
+ }
29546
+ ]
29547
+ }
29548
+ },
29549
+ { json: options.json }
29550
+ );
29551
+ return 0;
29552
+ }
29265
29553
  if (scope === "local" && root) {
29266
29554
  const managedNames = [
29267
29555
  .../* @__PURE__ */ new Set([...plan.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
@@ -29279,7 +29567,8 @@ async function runSkillsCommand(options) {
29279
29567
  `
29280
29568
  );
29281
29569
  try {
29282
- const removeCode = await runProcess(
29570
+ const execute = dependencies.runProcess ?? runProcess;
29571
+ const removeCode = await execute(
29283
29572
  plan.remove.command,
29284
29573
  plan.remove.args,
29285
29574
  root ?? void 0
@@ -29287,7 +29576,7 @@ async function runSkillsCommand(options) {
29287
29576
  if (removeCode !== 0) {
29288
29577
  throw new Error("Could not remove the existing Deepline skills.");
29289
29578
  }
29290
- const installCode = await runProcess(
29579
+ const installCode = await execute(
29291
29580
  plan.install.command,
29292
29581
  plan.install.args,
29293
29582
  root ?? void 0
@@ -29456,7 +29745,7 @@ function publicNpmFallbackRegistryUrl(hostUrl) {
29456
29745
  }
29457
29746
  function readOptionalText(path) {
29458
29747
  try {
29459
- return readFileSync12(path, "utf8").trim();
29748
+ return readFileSync13(path, "utf8").trim();
29460
29749
  } catch {
29461
29750
  return "";
29462
29751
  }
@@ -29596,7 +29885,7 @@ function readAutoUpdateFailure(plan) {
29596
29885
  if (!path) return null;
29597
29886
  try {
29598
29887
  const parsed = JSON.parse(
29599
- readFileSync12(path, "utf8")
29888
+ readFileSync13(path, "utf8")
29600
29889
  );
29601
29890
  if ((parsed.kind === "npm-global" || parsed.kind === "python-sidecar") && typeof parsed.packageSpec === "string" && typeof parsed.failedAt === "string" && typeof parsed.exitCode === "number" && typeof parsed.manualCommand === "string") {
29602
29891
  return parsed;
@@ -29680,7 +29969,7 @@ function installedPackageVersion(versionDir) {
29680
29969
  "package.json"
29681
29970
  );
29682
29971
  try {
29683
- const parsed = JSON.parse(readFileSync12(packageJsonPath, "utf8"));
29972
+ const parsed = JSON.parse(readFileSync13(packageJsonPath, "utf8"));
29684
29973
  return typeof parsed.version === "string" ? safeVersionSegment(parsed.version) : "";
29685
29974
  } catch {
29686
29975
  return "";
@@ -29993,13 +30282,92 @@ import {
29993
30282
  existsSync as existsSync13,
29994
30283
  lstatSync,
29995
30284
  mkdirSync as mkdirSync11,
29996
- readFileSync as readFileSync13,
30285
+ readFileSync as readFileSync14,
29997
30286
  realpathSync as realpathSync4,
29998
30287
  rmSync as rmSync5,
29999
30288
  writeFileSync as writeFileSync16
30000
30289
  } from "fs";
30001
30290
  import { homedir as homedir13 } from "os";
30002
30291
  import { dirname as dirname14, join as join16, resolve as resolve14 } from "path";
30292
+ var SETUP_PHASE_NAMES = [
30293
+ "cli",
30294
+ "cleanup",
30295
+ "skills",
30296
+ "auth",
30297
+ "verify"
30298
+ ];
30299
+ function initialSetupPhases() {
30300
+ return {
30301
+ cli: { status: "pending" },
30302
+ cleanup: { status: "pending" },
30303
+ skills: { status: "pending" },
30304
+ auth: { status: "pending" },
30305
+ verify: { status: "pending" }
30306
+ };
30307
+ }
30308
+ function isSetupPhaseStatus(value) {
30309
+ return value === "pending" || value === "in_progress" || value === "complete" || value === "waiting" || value === "failed";
30310
+ }
30311
+ function parseSetupPhases(value) {
30312
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30313
+ const source = value;
30314
+ const phases = initialSetupPhases();
30315
+ for (const name of SETUP_PHASE_NAMES) {
30316
+ const phase = source[name];
30317
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
30318
+ return null;
30319
+ }
30320
+ const record = phase;
30321
+ if (!isSetupPhaseStatus(record.status)) return null;
30322
+ phases[name] = {
30323
+ status: record.status,
30324
+ ...typeof record.outcome === "string" ? { outcome: record.outcome } : {},
30325
+ ...typeof record.code === "string" ? { code: record.code } : {}
30326
+ };
30327
+ }
30328
+ return phases;
30329
+ }
30330
+ function phasesFromLegacyStatus(status) {
30331
+ const phases = initialSetupPhases();
30332
+ if (status === "skills_installed" || status === "authorization_pending" || status === "complete") {
30333
+ phases.cli = { status: "complete" };
30334
+ phases.cleanup = { status: "complete" };
30335
+ phases.skills = { status: "complete" };
30336
+ }
30337
+ if (status === "authorization_pending") {
30338
+ phases.auth = { status: "waiting", outcome: "authorization_pending" };
30339
+ } else if (status === "complete") {
30340
+ phases.auth = { status: "complete" };
30341
+ phases.verify = { status: "complete" };
30342
+ }
30343
+ return phases;
30344
+ }
30345
+ function readSetupState(input2) {
30346
+ try {
30347
+ const parsed = JSON.parse(
30348
+ readFileSync14(
30349
+ setupStatePath(input2.baseUrl, input2.scope, input2.root),
30350
+ "utf8"
30351
+ )
30352
+ );
30353
+ if (parsed.host !== input2.baseUrl || parsed.scope !== input2.scope || parsed.root !== input2.root || typeof parsed.status !== "string") {
30354
+ return null;
30355
+ }
30356
+ return {
30357
+ status: parsed.status,
30358
+ phases: parseSetupPhases(parsed.phases) ?? phasesFromLegacyStatus(parsed.status)
30359
+ };
30360
+ } catch {
30361
+ return null;
30362
+ }
30363
+ }
30364
+ function selectSetupProgress(previousState) {
30365
+ const resumed = Boolean(previousState && previousState.status !== "complete");
30366
+ return {
30367
+ resumed,
30368
+ phases: resumed ? previousState.phases : initialSetupPhases()
30369
+ };
30370
+ }
30003
30371
  function normalizeScope2(value) {
30004
30372
  if (!value || value === "global") return "global";
30005
30373
  if (value === "local") return "local";
@@ -30059,7 +30427,7 @@ function printCapturedAuthorizationUrl(stdout) {
30059
30427
  }
30060
30428
  function safeRead(path) {
30061
30429
  try {
30062
- return readFileSync13(path, "utf8");
30430
+ return readFileSync14(path, "utf8");
30063
30431
  } catch {
30064
30432
  return "";
30065
30433
  }
@@ -30170,13 +30538,14 @@ function writeSetupState(input2) {
30170
30538
  path,
30171
30539
  `${JSON.stringify(
30172
30540
  {
30173
- schemaVersion: 1,
30541
+ schemaVersion: 2,
30174
30542
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30175
30543
  cliVersion: SDK_VERSION,
30176
30544
  host: input2.baseUrl,
30177
30545
  scope: input2.scope,
30178
30546
  root: input2.root,
30179
- status: input2.status
30547
+ status: input2.status,
30548
+ phases: input2.phases
30180
30549
  },
30181
30550
  null,
30182
30551
  2
@@ -30186,6 +30555,24 @@ function writeSetupState(input2) {
30186
30555
  );
30187
30556
  return path;
30188
30557
  }
30558
+ function persistSetupProgress(input2) {
30559
+ return writeSetupState({
30560
+ ...input2,
30561
+ status: input2.status ?? "in_progress"
30562
+ });
30563
+ }
30564
+ function completeSetupPhase(phases, phase, outcome) {
30565
+ phases[phase] = {
30566
+ status: "complete",
30567
+ ...outcome ? { outcome } : {}
30568
+ };
30569
+ }
30570
+ function beginSetupPhase(phases, phase) {
30571
+ phases[phase] = { status: "in_progress" };
30572
+ }
30573
+ function failSetupPhase(phases, phase, code) {
30574
+ phases[phase] = { status: "failed", code };
30575
+ }
30189
30576
  function rollbackCommand(scope, root) {
30190
30577
  const prefix = scope === "local" && root ? ` --prefix ${JSON.stringify(join16(root, ".deepline", "runtime"))}` : "";
30191
30578
  return `npm install -g${prefix} --no-audit --no-fund --include=optional --allow-scripts=esbuild deepline@${SDK_VERSION}`;
@@ -30197,6 +30584,42 @@ function setupResumeCommand(baseUrl, scope) {
30197
30584
  const hostPrefix = baseUrl === "https://code.deepline.com" ? "" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} `;
30198
30585
  return `${hostPrefix}deepline setup --scope ${scope} --json`;
30199
30586
  }
30587
+ function setupRetry(input2) {
30588
+ return {
30589
+ phase: input2.phase,
30590
+ command: setupResumeCommand(input2.baseUrl, input2.scope),
30591
+ automatic: true
30592
+ };
30593
+ }
30594
+ function reportSetupPhaseFailure(input2) {
30595
+ failSetupPhase(input2.phases, input2.phase, input2.code);
30596
+ const statePath = persistSetupProgress({
30597
+ baseUrl: input2.baseUrl,
30598
+ scope: input2.scope,
30599
+ root: input2.root,
30600
+ phases: input2.phases,
30601
+ status: "failed"
30602
+ });
30603
+ const retry = setupRetry(input2);
30604
+ printCommandEnvelope(
30605
+ {
30606
+ ok: false,
30607
+ status: "failed",
30608
+ code: input2.code,
30609
+ exitCode: input2.exitCode,
30610
+ scope: input2.scope,
30611
+ message: input2.message,
30612
+ phases: input2.phases,
30613
+ failedPhase: input2.phase,
30614
+ retry,
30615
+ statePath,
30616
+ next: retry.command,
30617
+ ...input2.extra ?? {}
30618
+ },
30619
+ { json: input2.json }
30620
+ );
30621
+ return input2.exitCode;
30622
+ }
30200
30623
  async function readAuthStatus(authScope) {
30201
30624
  try {
30202
30625
  const captured = await captureStdout2(
@@ -30216,63 +30639,44 @@ async function readAuthStatus(authScope) {
30216
30639
  }
30217
30640
  function reportSetupAuthStatusFailure(input2) {
30218
30641
  if (!input2.auth.error) return null;
30219
- printCommandEnvelope(
30220
- {
30221
- ok: false,
30222
- status: "failed",
30223
- code: "AUTH_STATUS_FAILED",
30224
- exitCode: 4,
30225
- scope: input2.scope,
30226
- message: "Deepline could not verify authorization with the configured host.",
30227
- next: `Check network access, then rerun: deepline setup --scope ${input2.scope} --json`,
30228
- authScope: input2.authScope
30229
- },
30230
- { json: input2.json }
30231
- );
30232
- return 4;
30642
+ return reportSetupPhaseFailure({
30643
+ baseUrl: input2.baseUrl,
30644
+ scope: input2.scope,
30645
+ root: input2.root,
30646
+ phases: input2.phases,
30647
+ phase: "auth",
30648
+ code: "AUTH_STATUS_FAILED",
30649
+ exitCode: 4,
30650
+ message: "Deepline could not verify authorization with the configured host.",
30651
+ json: input2.json,
30652
+ extra: { authScope: input2.authScope }
30653
+ });
30233
30654
  }
30234
- async function runDoctorCommand(options) {
30235
- let scope;
30236
- let root;
30237
- try {
30238
- scope = normalizeScope2(options.scope);
30239
- root = resolveScopeRoot(scope);
30240
- } catch (error) {
30241
- printCommandEnvelope(
30242
- {
30243
- ok: false,
30244
- status: "failed",
30245
- code: "INVALID_SETUP_SCOPE",
30246
- exitCode: 2,
30247
- message: error instanceof Error ? error.message : String(error)
30248
- },
30249
- { json: options.json }
30250
- );
30251
- return 2;
30252
- }
30253
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30254
- const skillsStatePath = skillsStatePathForScope(baseUrl, scope, root);
30655
+ function buildDoctorAssessment(input2) {
30656
+ const skillsStatePath = skillsStatePathForScope(
30657
+ input2.baseUrl,
30658
+ input2.scope,
30659
+ input2.root
30660
+ );
30255
30661
  const skillsState = parseCapturedJson(safeRead(skillsStatePath));
30256
- const authScope = authScopeForSetup(scope);
30257
- const apiKey = scope === "local" ? resolveProjectApiKeyForBaseUrl(baseUrl) : resolveGlobalApiKeyForBaseUrl(baseUrl);
30258
- const projectAuth = scope === "local" && apiKey ? getResolvedProjectAuthSource(baseUrl, apiKey) : null;
30259
- const authStatus = await readAuthStatus(authScope);
30260
- const connected = authStatus.payload?.connected === true;
30261
- const authScopeOk = scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30262
- const skillsOk = skillsState?.scope === scope && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30662
+ const apiKey = input2.scope === "local" ? resolveProjectApiKeyForBaseUrl(input2.baseUrl) : resolveGlobalApiKeyForBaseUrl(input2.baseUrl);
30663
+ const projectAuth = input2.scope === "local" && apiKey ? getResolvedProjectAuthSource(input2.baseUrl, apiKey) : null;
30664
+ const connected = input2.authStatus.payload?.connected === true;
30665
+ const authScopeOk = input2.scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30666
+ const skillsOk = skillsState?.scope === input2.scope && typeof skillsState.skillsVersion === "string" && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30263
30667
  const runningCliPath = process.argv[1] ? resolve14(process.argv[1]) : null;
30264
- const globalCli = scope === "global" ? inspectGlobalCliAvailability() : null;
30668
+ const globalCli = input2.scope === "global" ? inspectGlobalCliAvailability() : null;
30265
30669
  const pathGlobalCli = globalCli?.path ?? null;
30266
- const cliPath = scope === "global" ? pathGlobalCli : runningCliPath;
30267
- const cliScopeOk = scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30268
- root && runningCliPath?.includes(join16(root, ".deepline", "runtime"))
30670
+ const cliPath = input2.scope === "global" ? pathGlobalCli : runningCliPath;
30671
+ const cliScopeOk = input2.scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30672
+ input2.root && runningCliPath?.includes(join16(input2.root, ".deepline", "runtime"))
30269
30673
  );
30270
30674
  const checks = {
30271
30675
  cli: {
30272
30676
  ok: Boolean(cliPath) && cliScopeOk,
30273
30677
  version: SDK_VERSION,
30274
30678
  path: cliPath,
30275
- scope
30679
+ scope: input2.scope
30276
30680
  },
30277
30681
  skills: {
30278
30682
  ok: skillsOk,
@@ -30283,16 +30687,48 @@ async function runDoctorCommand(options) {
30283
30687
  auth: {
30284
30688
  ok: connected && authScopeOk,
30285
30689
  scope: projectAuth ? "local" : apiKey ? "global" : null,
30286
- status: authStatus.payload?.status ?? "not_connected"
30690
+ status: input2.authStatus.payload?.status ?? "not_connected"
30287
30691
  },
30288
30692
  api: {
30289
- ok: connected && authStatus.exitCode === 0,
30290
- host: baseUrl,
30291
- workspace: authStatus.payload?.workspace ?? null,
30693
+ ok: connected && input2.authStatus.exitCode === 0,
30694
+ host: input2.baseUrl,
30695
+ workspace: input2.authStatus.payload?.workspace ?? null,
30292
30696
  providerSpend: false
30293
30697
  }
30294
30698
  };
30295
- const ok = Object.values(checks).every((check) => check.ok);
30699
+ return {
30700
+ ok: Object.values(checks).every((check) => check.ok),
30701
+ checks
30702
+ };
30703
+ }
30704
+ async function runDoctorCommand(options) {
30705
+ let scope;
30706
+ let root;
30707
+ try {
30708
+ scope = normalizeScope2(options.scope);
30709
+ root = resolveScopeRoot(scope);
30710
+ } catch (error) {
30711
+ printCommandEnvelope(
30712
+ {
30713
+ ok: false,
30714
+ status: "failed",
30715
+ code: "INVALID_SETUP_SCOPE",
30716
+ exitCode: 2,
30717
+ message: error instanceof Error ? error.message : String(error)
30718
+ },
30719
+ { json: options.json }
30720
+ );
30721
+ return 2;
30722
+ }
30723
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30724
+ const authScope = authScopeForSetup(scope);
30725
+ const authStatus = await readAuthStatus(authScope);
30726
+ const { ok, checks } = buildDoctorAssessment({
30727
+ baseUrl,
30728
+ scope,
30729
+ root,
30730
+ authStatus
30731
+ });
30296
30732
  const quickstart = setupQuickstartCommand(baseUrl);
30297
30733
  printCommandEnvelope(
30298
30734
  {
@@ -30318,11 +30754,16 @@ async function runDoctorCommand(options) {
30318
30754
  return ok ? 0 : 7;
30319
30755
  }
30320
30756
  function pendingResult(input2) {
30757
+ input2.phases.auth = {
30758
+ status: "waiting",
30759
+ outcome: "authorization_pending"
30760
+ };
30321
30761
  const statePath = writeSetupState({
30322
30762
  baseUrl: input2.baseUrl,
30323
30763
  scope: input2.scope,
30324
30764
  root: input2.root,
30325
- status: "authorization_pending"
30765
+ status: "authorization_pending",
30766
+ phases: input2.phases
30326
30767
  });
30327
30768
  if (input2.authorizationUrl) {
30328
30769
  process.stderr.write(`Authorize Deepline: ${input2.authorizationUrl}
@@ -30336,6 +30777,14 @@ function pendingResult(input2) {
30336
30777
  scope: input2.scope,
30337
30778
  authorizationUrl: input2.authorizationUrl || null,
30338
30779
  statePath,
30780
+ phases: input2.phases,
30781
+ currentPhase: "auth",
30782
+ resumed: input2.resumed,
30783
+ retry: setupRetry({
30784
+ baseUrl: input2.baseUrl,
30785
+ scope: input2.scope,
30786
+ phase: "auth"
30787
+ }),
30339
30788
  next: `Approve the link, then run: ${setupResumeCommand(input2.baseUrl, input2.scope)}`,
30340
30789
  render: {
30341
30790
  sections: [
@@ -30366,84 +30815,146 @@ async function runSetupCommand(options) {
30366
30815
  status: "failed",
30367
30816
  code: "INVALID_SETUP_SCOPE",
30368
30817
  exitCode: 2,
30369
- message: error instanceof Error ? error.message : String(error)
30818
+ message: error instanceof Error ? error.message : String(error),
30819
+ phases: {
30820
+ ...initialSetupPhases(),
30821
+ cli: { status: "failed", code: "INVALID_SETUP_SCOPE" }
30822
+ },
30823
+ failedPhase: "cli"
30370
30824
  },
30371
30825
  { json: options.json }
30372
30826
  );
30373
30827
  return 2;
30374
30828
  }
30375
30829
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30376
- if (scope === "global") {
30377
- const globalCli = inspectGlobalCliAvailability();
30378
- if (!globalCli.ok) {
30379
- const installedButUnreachable = Boolean(globalCli.persistentPath);
30380
- printCommandEnvelope(
30381
- {
30382
- ok: false,
30383
- status: "failed",
30384
- code: installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED",
30385
- exitCode: 7,
30830
+ const previousState = readSetupState({ baseUrl, scope, root });
30831
+ const { resumed, phases } = selectSetupProgress(previousState);
30832
+ if (phases.cli.status !== "complete") {
30833
+ beginSetupPhase(phases, "cli");
30834
+ persistSetupProgress({ baseUrl, scope, root, phases });
30835
+ if (scope === "global") {
30836
+ const globalCli = inspectGlobalCliAvailability();
30837
+ if (!globalCli.ok) {
30838
+ const installedButUnreachable = Boolean(globalCli.persistentPath);
30839
+ const code = installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED";
30840
+ return reportSetupPhaseFailure({
30841
+ baseUrl,
30386
30842
  scope,
30387
- persistentPath: globalCli.persistentPath,
30843
+ root,
30844
+ phases,
30845
+ phase: "cli",
30846
+ code,
30847
+ exitCode: 7,
30388
30848
  message: installedButUnreachable ? `The global Deepline executable is not reachable on PATH: ${globalCli.persistentPath}` : "No persistent global Deepline executable was found.",
30389
- next: "Use the automatic project-local fallback in https://code.deepline.com/SKILL.md"
30390
- },
30391
- { json: options.json }
30392
- );
30393
- return 7;
30849
+ json: options.json,
30850
+ extra: {
30851
+ persistentPath: globalCli.persistentPath,
30852
+ fallback: "https://code.deepline.com/INSTALL.md"
30853
+ }
30854
+ });
30855
+ }
30394
30856
  }
30395
- }
30396
- const conflict = inspectPathConflict();
30397
- if (conflict) {
30398
- printCommandEnvelope(
30399
- {
30400
- ok: false,
30401
- status: "failed",
30857
+ const conflict = inspectPathConflict();
30858
+ if (conflict) {
30859
+ return reportSetupPhaseFailure({
30860
+ baseUrl,
30861
+ scope,
30862
+ root,
30863
+ phases,
30864
+ phase: "cli",
30402
30865
  code: "PATH_CONFLICT",
30403
30866
  exitCode: 7,
30404
- path: conflict,
30405
30867
  message: `An unknown executable named deepline is first on PATH: ${conflict}`,
30406
- next: "Move or rename that executable, then rerun: deepline setup --json"
30407
- },
30408
- { json: options.json }
30409
- );
30410
- return 7;
30868
+ json: options.json,
30869
+ extra: { path: conflict }
30870
+ });
30871
+ }
30872
+ completeSetupPhase(phases, "cli", "available");
30873
+ persistSetupProgress({ baseUrl, scope, root, phases });
30411
30874
  }
30412
- const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30413
- if (removedLegacyPaths.length > 0) {
30414
- process.stderr.write(
30415
- `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30875
+ if (phases.cleanup.status !== "complete") {
30876
+ beginSetupPhase(phases, "cleanup");
30877
+ persistSetupProgress({ baseUrl, scope, root, phases });
30878
+ try {
30879
+ const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30880
+ if (removedLegacyPaths.length > 0) {
30881
+ process.stderr.write(
30882
+ `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30416
30883
  `
30417
- );
30884
+ );
30885
+ }
30886
+ completeSetupPhase(
30887
+ phases,
30888
+ "cleanup",
30889
+ removedLegacyPaths.length > 0 ? "removed_legacy_paths" : "clean"
30890
+ );
30891
+ persistSetupProgress({ baseUrl, scope, root, phases });
30892
+ } catch (error) {
30893
+ return reportSetupPhaseFailure({
30894
+ baseUrl,
30895
+ scope,
30896
+ root,
30897
+ phases,
30898
+ phase: "cleanup",
30899
+ code: "LEGACY_CLEANUP_FAILED",
30900
+ exitCode: 5,
30901
+ message: error instanceof Error ? error.message : String(error),
30902
+ json: options.json
30903
+ });
30904
+ }
30418
30905
  }
30419
- process.stderr.write(`Installing Deepline skills (${scope})...
30906
+ let skillsPayload = null;
30907
+ if (phases.skills.status !== "complete") {
30908
+ beginSetupPhase(phases, "skills");
30909
+ persistSetupProgress({ baseUrl, scope, root, phases });
30910
+ process.stderr.write(`Installing Deepline skills (${scope})...
30420
30911
  `);
30421
- const skills = await captureStdout2(
30422
- () => runSkillsCommand({ scope, json: true })
30423
- );
30424
- const skillsPayload = parseCapturedJson(skills.stdout);
30425
- if (skills.exitCode !== 0) {
30426
- printCommandEnvelope(
30427
- {
30428
- ok: false,
30429
- status: "failed",
30912
+ const skills = await captureStdout2(
30913
+ () => runSkillsCommand({ scope, json: true })
30914
+ );
30915
+ skillsPayload = parseCapturedJson(skills.stdout);
30916
+ if (skills.exitCode !== 0) {
30917
+ return reportSetupPhaseFailure({
30918
+ baseUrl,
30919
+ scope,
30920
+ root,
30921
+ phases,
30922
+ phase: "skills",
30430
30923
  code: "SKILLS_INSTALL_FAILED",
30431
30924
  exitCode: skills.exitCode,
30432
- scope,
30433
- detail: skillsPayload,
30434
- next: `deepline skills --scope ${scope} --json`
30435
- },
30436
- { json: options.json }
30925
+ message: typeof skillsPayload?.message === "string" ? skillsPayload.message : "Deepline skills could not be installed.",
30926
+ json: options.json,
30927
+ extra: { detail: skillsPayload }
30928
+ });
30929
+ }
30930
+ completeSetupPhase(
30931
+ phases,
30932
+ "skills",
30933
+ skillsPayload?.status === "current" ? "current" : "installed"
30934
+ );
30935
+ writeSetupState({
30936
+ baseUrl,
30937
+ scope,
30938
+ root,
30939
+ status: "skills_installed",
30940
+ phases
30941
+ });
30942
+ } else {
30943
+ skillsPayload = parseCapturedJson(
30944
+ safeRead(skillsStatePathForScope(baseUrl, scope, root))
30437
30945
  );
30438
- return skills.exitCode;
30439
30946
  }
30440
- writeSetupState({ baseUrl, scope, root, status: "skills_installed" });
30441
30947
  const authScope = authScopeForSetup(scope);
30948
+ beginSetupPhase(phases, "auth");
30949
+ persistSetupProgress({ baseUrl, scope, root, phases });
30442
30950
  let auth = await readAuthStatus(authScope);
30443
30951
  const initialAuthFailure = reportSetupAuthStatusFailure({
30444
30952
  auth,
30953
+ baseUrl,
30445
30954
  authScope,
30446
30955
  scope,
30956
+ root,
30957
+ phases,
30447
30958
  json: options.json
30448
30959
  });
30449
30960
  if (initialAuthFailure !== null) return initialAuthFailure;
@@ -30458,8 +30969,11 @@ async function runSetupCommand(options) {
30458
30969
  auth = await readAuthStatus(authScope);
30459
30970
  const resumedAuthFailure = reportSetupAuthStatusFailure({
30460
30971
  auth,
30972
+ baseUrl,
30461
30973
  authScope,
30462
30974
  scope,
30975
+ root,
30976
+ phases,
30463
30977
  json: options.json
30464
30978
  });
30465
30979
  if (resumedAuthFailure !== null) return resumedAuthFailure;
@@ -30470,7 +30984,9 @@ async function runSetupCommand(options) {
30470
30984
  baseUrl,
30471
30985
  scope,
30472
30986
  root,
30987
+ phases,
30473
30988
  authorizationUrl: stillPending.claimUrl,
30989
+ resumed,
30474
30990
  json: options.json
30475
30991
  });
30476
30992
  }
@@ -30487,24 +31003,30 @@ async function runSetupCommand(options) {
30487
31003
  );
30488
31004
  printCapturedAuthorizationUrl(registered.stdout);
30489
31005
  if (registered.exitCode !== 0) {
30490
- printCommandEnvelope(
30491
- {
30492
- ok: false,
30493
- status: "failed",
30494
- code: "AUTH_REGISTER_FAILED",
30495
- exitCode: registered.exitCode,
30496
- scope,
30497
- next: `deepline auth register --auth-scope ${authScope}`
30498
- },
30499
- { json: options.json }
30500
- );
30501
- return registered.exitCode;
31006
+ return reportSetupPhaseFailure({
31007
+ baseUrl,
31008
+ scope,
31009
+ root,
31010
+ phases,
31011
+ phase: "auth",
31012
+ code: "AUTH_REGISTER_FAILED",
31013
+ exitCode: registered.exitCode,
31014
+ message: "Deepline browser authorization could not be started.",
31015
+ json: options.json,
31016
+ extra: {
31017
+ detail: parseCapturedJson(registered.stdout),
31018
+ authScope
31019
+ }
31020
+ });
30502
31021
  }
30503
31022
  auth = await readAuthStatus(authScope);
30504
31023
  const registeredAuthFailure = reportSetupAuthStatusFailure({
30505
31024
  auth,
31025
+ baseUrl,
30506
31026
  authScope,
30507
31027
  scope,
31028
+ root,
31029
+ phases,
30508
31030
  json: options.json
30509
31031
  });
30510
31032
  if (registeredAuthFailure !== null) return registeredAuthFailure;
@@ -30514,40 +31036,60 @@ async function runSetupCommand(options) {
30514
31036
  baseUrl,
30515
31037
  scope,
30516
31038
  root,
31039
+ phases,
30517
31040
  authorizationUrl: pending?.claimUrl ?? "",
31041
+ resumed,
30518
31042
  json: options.json
30519
31043
  });
30520
31044
  }
30521
31045
  }
30522
- process.stderr.write("Verifying Deepline setup...\n");
30523
- const doctor = await captureStdout2(
30524
- () => runDoctorCommand({ scope, json: true })
30525
- );
30526
- const doctorPayload = parseCapturedJson(doctor.stdout);
30527
- if (doctor.exitCode !== 0) {
30528
- printCommandEnvelope(
30529
- {
30530
- ok: false,
30531
- status: "failed",
30532
- code: "DOCTOR_FAILED",
30533
- exitCode: doctor.exitCode,
30534
- scope,
31046
+ completeSetupPhase(phases, "auth", "connected");
31047
+ beginSetupPhase(phases, "verify");
31048
+ persistSetupProgress({ baseUrl, scope, root, phases });
31049
+ const assessment = buildDoctorAssessment({
31050
+ baseUrl,
31051
+ scope,
31052
+ root,
31053
+ authStatus: auth
31054
+ });
31055
+ const quickstart = setupQuickstartCommand(baseUrl);
31056
+ const doctorPayload = {
31057
+ ok: assessment.ok,
31058
+ status: assessment.ok ? "complete" : "failed",
31059
+ code: assessment.ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
31060
+ exitCode: assessment.ok ? 0 : 7,
31061
+ checks: assessment.checks,
31062
+ next: assessment.ok ? quickstart : `deepline doctor --scope ${scope} --json`
31063
+ };
31064
+ if (!assessment.ok) {
31065
+ return reportSetupPhaseFailure({
31066
+ baseUrl,
31067
+ scope,
31068
+ root,
31069
+ phases,
31070
+ phase: "verify",
31071
+ code: "DOCTOR_FAILED",
31072
+ exitCode: 7,
31073
+ message: "Deepline setup verification found one or more failed checks.",
31074
+ json: options.json,
31075
+ extra: {
30535
31076
  doctor: doctorPayload,
30536
- next: "Review the doctor result and choose a repair, then rerun: deepline setup --json"
30537
- },
30538
- { json: options.json }
30539
- );
30540
- return doctor.exitCode;
31077
+ diagnostic: {
31078
+ command: `deepline doctor --scope ${scope} --json`
31079
+ }
31080
+ }
31081
+ });
30541
31082
  }
31083
+ completeSetupPhase(phases, "verify", "verified");
30542
31084
  const statePath = writeSetupState({
30543
31085
  baseUrl,
30544
31086
  scope,
30545
31087
  root,
30546
- status: "complete"
31088
+ status: "complete",
31089
+ phases
30547
31090
  });
30548
31091
  const doctorChecks = asRecord2(doctorPayload?.checks);
30549
31092
  const apiCheck = asRecord2(doctorChecks?.api);
30550
- const quickstart = setupQuickstartCommand(baseUrl);
30551
31093
  printCommandEnvelope(
30552
31094
  {
30553
31095
  ok: true,
@@ -30560,6 +31102,10 @@ async function runSetupCommand(options) {
30560
31102
  rollbackCommand: rollbackCommand(scope, root),
30561
31103
  statePath,
30562
31104
  doctor: doctorPayload,
31105
+ phases,
31106
+ currentPhase: null,
31107
+ failedPhase: null,
31108
+ resumed,
30563
31109
  next: quickstart,
30564
31110
  render: {
30565
31111
  sections: [
@@ -30582,8 +31128,9 @@ function registerSetupCommands(program) {
30582
31128
  "after",
30583
31129
  `
30584
31130
  Notes:
30585
- Setup is idempotent. Bare setup resumes pending browser authorization.
31131
+ Setup is idempotent. Bare setup resumes the first incomplete or failed phase.
30586
31132
  It installs skills before auth and does not run quickstart.
31133
+ JSON output includes phase status and an exact retry command.
30587
31134
 
30588
31135
  Examples:
30589
31136
  deepline setup --json
@@ -30823,7 +31370,7 @@ import { spawn as spawn6, spawnSync as spawnSync3 } from "child_process";
30823
31370
  import {
30824
31371
  existsSync as existsSync14,
30825
31372
  mkdirSync as mkdirSync12,
30826
- readFileSync as readFileSync14,
31373
+ readFileSync as readFileSync15,
30827
31374
  unlinkSync as unlinkSync2,
30828
31375
  writeFileSync as writeFileSync17
30829
31376
  } from "fs";
@@ -30849,7 +31396,7 @@ function readPluginSkillsVersion() {
30849
31396
  const dir = activePluginSkillsDir();
30850
31397
  if (!dir) return "";
30851
31398
  try {
30852
- return readFileSync14(join17(dir, ".version"), "utf-8").trim();
31399
+ return readFileSync15(join17(dir, ".version"), "utf-8").trim();
30853
31400
  } catch {
30854
31401
  return "";
30855
31402
  }
@@ -30869,7 +31416,7 @@ function readSdkSkillsLocalVersion(baseUrl) {
30869
31416
  const path = existsSync14(sdkSkillsVersionPath(baseUrl)) ? sdkSkillsVersionPath(baseUrl) : legacySdkSkillsVersionPath(baseUrl);
30870
31417
  if (!existsSync14(path)) return "";
30871
31418
  try {
30872
- return readFileSync14(path, "utf-8").trim();
31419
+ return readFileSync15(path, "utf-8").trim();
30873
31420
  } catch {
30874
31421
  return "";
30875
31422
  }
@@ -30883,7 +31430,7 @@ function writeLocalSkillsVersion(baseUrl, version) {
30883
31430
  function writeUnavailableSkillsNotice(baseUrl, remoteVersion, skillNames) {
30884
31431
  const path = unavailableSkillsNoticePath(baseUrl);
30885
31432
  try {
30886
- if (existsSync14(path) && readFileSync14(path, "utf-8").trim() === remoteVersion) {
31433
+ if (existsSync14(path) && readFileSync15(path, "utf-8").trim() === remoteVersion) {
30887
31434
  return;
30888
31435
  }
30889
31436
  mkdirSync12(dirname15(path), { recursive: true });