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.
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.262",
720
720
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
721
721
  supportPolicy: {
722
722
  minimumSupported: "0.1.53",
@@ -26616,6 +26616,33 @@ function extractSummaryFields(payload) {
26616
26616
  return {};
26617
26617
  }
26618
26618
 
26619
+ // ../shared_libs/plays/tool-category-descriptions.ts
26620
+ var TOOL_CATEGORY_DESCRIPTIONS = {
26621
+ company_search: "Find companies matching firmographic, ad, or web criteria.",
26622
+ people_search: "Find people matching role, company, or persona criteria.",
26623
+ people_enrich: "Enrich a known person with contact, social, and profile data.",
26624
+ company_enrich: "Enrich a known company with firmographic and account data.",
26625
+ email_finder: "Find work or personal email addresses for a person.",
26626
+ email_verify: "Validate email deliverability and catch-all status.",
26627
+ phone_finder: "Find mobile or direct phone numbers for a person.",
26628
+ phone_verify: "Validate phone-number reachability and line type.",
26629
+ research: "Research accounts, people, news, and signals for context and scoring.",
26630
+ autocomplete: "Resolve names and entities to canonical catalog records.",
26631
+ automation: "Kick off provider-side jobs, exports, and asynchronous workflows.",
26632
+ outbound_tools: "Run outbound sequencing, deliverability, and campaign operations.",
26633
+ admin: "Provider-side status, metadata, and account operations behind other tools.",
26634
+ smb: "Look up small-business listings, local records, and public filings.",
26635
+ identity_resolution: "Match a partial identity to a single resolved person or company.",
26636
+ reverse_lookup: "Resolve an email, phone, or profile back to a person.",
26637
+ enrichment: "General-purpose record enrichment across people and companies.",
26638
+ batch: "Run an enrichment or search operation over many rows at once.",
26639
+ premium: "Higher-cost tools with premium provider coverage.",
26640
+ free: "Free tools that do not spend Deepline credits."
26641
+ };
26642
+ function describeToolCategory(category) {
26643
+ return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
26644
+ }
26645
+
26619
26646
  // src/cli/commands/model-description.ts
26620
26647
  function formatModelDescription(description) {
26621
26648
  const lines = [];
@@ -26782,21 +26809,181 @@ function zeroMatchRender(emptyResult) {
26782
26809
  ]
26783
26810
  };
26784
26811
  }
26785
- async function listTools(args) {
26812
+ function firstSentence(text) {
26813
+ const clean = (text ?? "").replace(/\s+/g, " ").trim();
26814
+ if (!clean) return "";
26815
+ const match = clean.match(/^.*?[.!?](?=\s|$)/);
26816
+ return (match ? match[0] : clean).trim();
26817
+ }
26818
+ function requiredInputIds(tool) {
26819
+ const inputSchema = recordField2(
26820
+ tool,
26821
+ "inputSchema",
26822
+ "input_schema"
26823
+ );
26824
+ return toolInputFieldsForDisplay(inputSchema).filter((field) => field.required === true).map((field) => String(field.name ?? "")).filter(Boolean);
26825
+ }
26826
+ function shortPricingHint(tool) {
26827
+ const pricing = recordField2(
26828
+ tool,
26829
+ "pricing"
26830
+ );
26831
+ const credits = numberField2(pricing, "creditsPerUnit", "credits_per_unit");
26832
+ const unit = stringField2(pricing, "unit");
26833
+ if (credits !== null) {
26834
+ return `${formatDecimal(credits)} cr/${unit || "unit"}`;
26835
+ }
26836
+ const summary = stringField2(pricing, "summary");
26837
+ if (summary) return summary;
26838
+ const displayText = stringField2(pricing, "displayText", "display_text");
26839
+ return displayText || null;
26840
+ }
26841
+ function aggregateCategorySummaries(tools) {
26842
+ const counts = /* @__PURE__ */ new Map();
26843
+ for (const tool of tools) {
26844
+ for (const category of tool.categories ?? []) {
26845
+ counts.set(category, (counts.get(category) ?? 0) + 1);
26846
+ }
26847
+ }
26848
+ return [...counts.entries()].map(([name, count]) => ({
26849
+ name,
26850
+ count,
26851
+ description: describeToolCategory(name)
26852
+ })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
26853
+ }
26854
+ function categoryEnumerationRender(summaries) {
26855
+ const nameWidth = summaries.reduce(
26856
+ (max, item) => Math.max(max, item.name.length),
26857
+ 0
26858
+ );
26859
+ const countWidth = summaries.reduce(
26860
+ (max, item) => Math.max(max, String(item.count).length),
26861
+ 0
26862
+ );
26863
+ return {
26864
+ sections: [
26865
+ {
26866
+ title: `${summaries.length} tool categories:`,
26867
+ lines: summaries.map((item) => {
26868
+ const name = item.name.padEnd(nameWidth);
26869
+ const count = String(item.count).padStart(countWidth);
26870
+ const definition = item.description ? ` - ${item.description}` : "";
26871
+ return `${name} ${count}${definition}`;
26872
+ })
26873
+ },
26874
+ {
26875
+ title: "next",
26876
+ lines: ["Pass a category: deepline tools list email_finder"]
26877
+ }
26878
+ ]
26879
+ };
26880
+ }
26881
+ async function listToolCategories(emitJson, compact) {
26882
+ const client2 = new DeeplineClient();
26883
+ const rawTools = await client2.listTools({ compact });
26884
+ const items = rawTools.map(toListedTool);
26885
+ const summaries = aggregateCategorySummaries(rawTools);
26886
+ const outputItems = compact ? items.map(compactTool) : items;
26887
+ printCommandEnvelope(
26888
+ {
26889
+ tools: outputItems,
26890
+ count: outputItems.length,
26891
+ categories: summaries,
26892
+ filters: {
26893
+ categories: []
26894
+ },
26895
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26896
+ render: categoryEnumerationRender(summaries)
26897
+ },
26898
+ { json: emitJson }
26899
+ );
26900
+ return 0;
26901
+ }
26902
+ function unknownCategoryError(category, summaries, emitJson) {
26903
+ const known = summaries.map((item) => item.name);
26904
+ const message = `Unknown tool category "${category}". Run \`deepline tools list\` to browse categories.`;
26905
+ if (emitJson) {
26906
+ printJson({
26907
+ ok: false,
26908
+ exitCode: 4,
26909
+ code: "TOOL_CATEGORY_NOT_FOUND",
26910
+ message,
26911
+ category,
26912
+ categories: summaries,
26913
+ next: "deepline tools list"
26914
+ });
26915
+ return 4;
26916
+ }
26917
+ const render = categoryEnumerationRender(summaries);
26918
+ console.error(message);
26919
+ console.error("");
26920
+ console.error(renderCommandEnvelopeText({ render }).trimEnd());
26921
+ console.error("");
26922
+ console.error(`Valid categories: ${known.join(", ")}`);
26923
+ return 4;
26924
+ }
26925
+ async function listToolsInCategory(category, emitJson) {
26926
+ const client2 = new DeeplineClient();
26927
+ const rawTools = await client2.listTools({
26928
+ categories: category,
26929
+ compact: false
26930
+ });
26931
+ const allTools = await client2.listTools({ compact: true });
26932
+ const summaries = aggregateCategorySummaries(allTools);
26933
+ if (!summaries.some((item) => item.name === category)) {
26934
+ return unknownCategoryError(category, summaries, emitJson);
26935
+ }
26936
+ const rows = rawTools.filter((tool) => tool.categories?.includes(category)).map((tool) => ({
26937
+ id: tool.toolId,
26938
+ provider: tool.provider,
26939
+ required: requiredInputIds(tool),
26940
+ pricing: shortPricingHint(tool),
26941
+ description: firstSentence(tool.description)
26942
+ })).sort((a, b) => a.id.localeCompare(b.id));
26943
+ const idWidth = rows.reduce((max, row) => Math.max(max, row.id.length), 0);
26944
+ const render = {
26945
+ sections: [
26946
+ {
26947
+ title: `${rows.length} tools in ${category}:`,
26948
+ lines: rows.map((row) => {
26949
+ const id = row.id.padEnd(idWidth);
26950
+ const required = row.required.length ? `(${row.required.join(", ")})` : "(no required inputs)";
26951
+ const pricing = row.pricing ? ` [${row.pricing}]` : "";
26952
+ const description = row.description ? ` - ${row.description}` : "";
26953
+ return `${id} ${required}${pricing}${description}`;
26954
+ })
26955
+ },
26956
+ {
26957
+ title: "next",
26958
+ lines: [
26959
+ "Run `deepline tools describe <toolId>` for the full contract."
26960
+ ]
26961
+ }
26962
+ ]
26963
+ };
26964
+ printCommandEnvelope(
26965
+ {
26966
+ category,
26967
+ total: rows.length,
26968
+ tools: rows,
26969
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26970
+ render
26971
+ },
26972
+ { json: emitJson }
26973
+ );
26974
+ return 0;
26975
+ }
26976
+ async function listToolsForCategoryFilter(requestedCategories, emitJson, compact) {
26786
26977
  const client2 = new DeeplineClient();
26787
- const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
26788
- const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
26789
- const compact = !args.includes("--full");
26790
- const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
26791
26978
  const items = (await client2.listTools({
26792
- ...categoryFilter ? { categories: categoryFilter } : {},
26979
+ categories: requestedCategories.join(","),
26793
26980
  compact
26794
26981
  })).map(toListedTool).filter(
26795
- (item) => requestedCategories.length === 0 || requestedCategories.some(
26982
+ (item) => requestedCategories.some(
26796
26983
  (category) => item.categories.includes(category)
26797
26984
  )
26798
26985
  );
26799
- const emptyResult = items.length === 0 && requestedCategories.length > 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26986
+ const emptyResult = items.length === 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26800
26987
  const render = {
26801
26988
  sections: emptyResult ? zeroMatchRender(emptyResult).sections : [
26802
26989
  {
@@ -26827,10 +27014,41 @@ async function listTools(args) {
26827
27014
  commandTemplates: TOOL_COMMAND_TEMPLATES,
26828
27015
  render
26829
27016
  },
26830
- { json: argsWantJson(args) }
27017
+ { json: emitJson }
26831
27018
  );
26832
27019
  return 0;
26833
27020
  }
27021
+ async function listTools(category, options = {}) {
27022
+ const positional = category?.trim() ?? "";
27023
+ const flagCategories = (options.categories ?? "").split(",").map((item) => item.trim()).filter(Boolean);
27024
+ const compact = options.compact !== false;
27025
+ const emitJson = options.json === true || shouldEmitJson();
27026
+ if (positional && flagCategories.length > 0) {
27027
+ const flagIsSameSingle = flagCategories.length === 1 && flagCategories[0] === positional;
27028
+ if (!flagIsSameSingle) {
27029
+ const message = `Category "${positional}" and --categories ${flagCategories.join(",")} disagree. Pass one: deepline tools list ${positional}`;
27030
+ if (emitJson) {
27031
+ printJson({
27032
+ ok: false,
27033
+ exitCode: 2,
27034
+ code: "TOOL_LIST_CATEGORY_CONFLICT",
27035
+ message,
27036
+ next: `deepline tools list ${positional}`
27037
+ });
27038
+ } else {
27039
+ console.error(message);
27040
+ }
27041
+ return 2;
27042
+ }
27043
+ }
27044
+ if (positional) {
27045
+ return listToolsInCategory(positional, emitJson);
27046
+ }
27047
+ if (flagCategories.length > 0) {
27048
+ return listToolsForCategoryFilter(flagCategories, emitJson, compact);
27049
+ }
27050
+ return listToolCategories(emitJson, compact);
27051
+ }
26834
27052
  async function searchTools(queryInput, options = {}) {
26835
27053
  const query = queryInput?.trim() ?? "";
26836
27054
  const hasStructuredSearch = Boolean(
@@ -27029,27 +27247,37 @@ Output:
27029
27247
  Use execute to run a tool.
27030
27248
  `
27031
27249
  );
27032
- tools.command("list").description("List available tools.").addHelpText(
27250
+ tools.command("list [category]").description("Browse tool categories, or list every tool in a category.").addHelpText(
27033
27251
  "after",
27034
27252
  `
27035
27253
  Notes:
27036
- Inventory command for known tool ids. Use search for ranked discovery by
27037
- intent, aliases, descriptions, and schema fields.
27254
+ Bare \`tools list\` prints the category enumeration: one line per category with
27255
+ its tool count and definition. Pass a category to list every tool in it, one
27256
+ succinct row each (required inputs, Deepline pricing hint, first sentence).
27257
+ The listing is exhaustive and never truncated. Use search for ranked discovery
27258
+ by intent, aliases, descriptions, and schema fields.
27259
+
27260
+ --categories is a back-compat inventory filter. Passing it alone returns the
27261
+ legacy flat inventory envelope (one row per tool). The positional category is
27262
+ the newer exhaustive per-category view. If both are given they must name the
27263
+ same category (the positional view wins); otherwise it is a usage error.
27038
27264
 
27039
27265
  Examples:
27040
27266
  deepline tools list
27267
+ deepline tools list email_finder
27268
+ deepline tools list email_finder --json
27041
27269
  deepline tools list --categories email_finder --json
27042
27270
  deepline tools search email --json
27043
27271
  `
27044
27272
  ).option(
27045
27273
  "--categories <categories>",
27046
- "Comma-separated categories to filter inventory"
27047
- ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
27048
- process.exitCode = await listTools([
27049
- ...options.categories ? ["--categories", options.categories] : [],
27050
- ...options.compact ? ["--compact"] : [],
27051
- ...options.json ? ["--json"] : []
27052
- ]);
27274
+ "Comma-separated categories filter (back-compat alias for the positional)"
27275
+ ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (category, options) => {
27276
+ process.exitCode = await listTools(category, {
27277
+ categories: typeof options.categories === "string" ? options.categories : void 0,
27278
+ compact: options.compact !== false,
27279
+ json: Boolean(options.json)
27280
+ });
27053
27281
  });
27054
27282
  const addToolSearchCommand = (command) => command.description(
27055
27283
  "Search available tools by intent query or structured filters."
@@ -29106,10 +29334,13 @@ function buildSkillsPlan(input2) {
29106
29334
  skillNames: input2.skillNames,
29107
29335
  version: input2.version,
29108
29336
  remove: {
29109
- command: "npx",
29337
+ command: "npm",
29110
29338
  args: [
29339
+ "exec",
29111
29340
  "--yes",
29112
- "skills@latest",
29341
+ "--package=skills@latest",
29342
+ "--",
29343
+ "skills",
29113
29344
  "remove",
29114
29345
  ...scopeArgs,
29115
29346
  "--agent",
@@ -29119,10 +29350,13 @@ function buildSkillsPlan(input2) {
29119
29350
  ]
29120
29351
  },
29121
29352
  install: {
29122
- command: "npx",
29353
+ command: "npm",
29123
29354
  args: [
29355
+ "exec",
29124
29356
  "--yes",
29125
- "skills@latest",
29357
+ "--package=skills@latest",
29358
+ "--",
29359
+ "skills",
29126
29360
  "add",
29127
29361
  skillsIndexUrl(input2.baseUrl),
29128
29362
  "--agent",
@@ -29136,6 +29370,29 @@ function buildSkillsPlan(input2) {
29136
29370
  statePath: skillsStatePathForScope(input2.baseUrl, input2.scope, input2.root)
29137
29371
  };
29138
29372
  }
29373
+ function sortedStrings(value) {
29374
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
29375
+ return null;
29376
+ }
29377
+ return [...value].sort((a, b) => a.localeCompare(b));
29378
+ }
29379
+ function isSkillsPlanCurrent(plan, state) {
29380
+ if (!state || state.scope !== plan.scope || state.skillsVersion !== plan.version) {
29381
+ return false;
29382
+ }
29383
+ const installedAgents = sortedStrings(state.agents);
29384
+ const installedSkillNames = sortedStrings(state.skillNames);
29385
+ if (!installedAgents || !installedSkillNames) return false;
29386
+ 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");
29387
+ }
29388
+ function readSkillsInstallState(path) {
29389
+ try {
29390
+ const parsed = JSON.parse((0, import_node_fs16.readFileSync)(path, "utf8"));
29391
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
29392
+ } catch {
29393
+ return null;
29394
+ }
29395
+ }
29139
29396
  function runProcess(command, args, cwd) {
29140
29397
  return new Promise((resolve15, reject) => {
29141
29398
  const child = (0, import_node_child_process3.spawn)(command, args, {
@@ -29159,7 +29416,7 @@ function runProcess(command, args, cwd) {
29159
29416
  });
29160
29417
  });
29161
29418
  }
29162
- async function runSkillsCommand(options) {
29419
+ async function runSkillsCommand(options, dependencies = {}) {
29163
29420
  let scope;
29164
29421
  let root;
29165
29422
  try {
@@ -29181,7 +29438,7 @@ async function runSkillsCommand(options) {
29181
29438
  const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
29182
29439
  let catalog;
29183
29440
  try {
29184
- catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await fetchSkillCatalog(baseUrl);
29441
+ catalog = options.dryRun ? { skillNames: [...DEFAULT_SDK_SKILL_NAMES], version: "latest" } : await (dependencies.fetchCatalog ?? fetchSkillCatalog)(baseUrl);
29185
29442
  } catch (error) {
29186
29443
  printCommandEnvelope(
29187
29444
  {
@@ -29205,6 +29462,37 @@ async function runSkillsCommand(options) {
29205
29462
  );
29206
29463
  return 0;
29207
29464
  }
29465
+ if (isSkillsPlanCurrent(plan, readSkillsInstallState(plan.statePath))) {
29466
+ printCommandEnvelope(
29467
+ {
29468
+ ok: true,
29469
+ status: "current",
29470
+ complete: true,
29471
+ changed: false,
29472
+ skipped: true,
29473
+ skipReason: "skills_version_current",
29474
+ scope,
29475
+ agents,
29476
+ skillsVersion: plan.version,
29477
+ skillCount: plan.skillNames.length,
29478
+ statePath: plan.statePath,
29479
+ render: {
29480
+ sections: [
29481
+ {
29482
+ title: "skills",
29483
+ lines: [
29484
+ `Scope: ${scope}`,
29485
+ `Agents: ${agents.join(", ")}`,
29486
+ `Current: ${plan.skillNames.length}`
29487
+ ]
29488
+ }
29489
+ ]
29490
+ }
29491
+ },
29492
+ { json: options.json }
29493
+ );
29494
+ return 0;
29495
+ }
29208
29496
  if (scope === "local" && root) {
29209
29497
  const managedNames = [
29210
29498
  .../* @__PURE__ */ new Set([...plan.skillNames, ...LEGACY_SKILL_NAMES_TO_REMOVE])
@@ -29222,7 +29510,8 @@ async function runSkillsCommand(options) {
29222
29510
  `
29223
29511
  );
29224
29512
  try {
29225
- const removeCode = await runProcess(
29513
+ const execute = dependencies.runProcess ?? runProcess;
29514
+ const removeCode = await execute(
29226
29515
  plan.remove.command,
29227
29516
  plan.remove.args,
29228
29517
  root ?? void 0
@@ -29230,7 +29519,7 @@ async function runSkillsCommand(options) {
29230
29519
  if (removeCode !== 0) {
29231
29520
  throw new Error("Could not remove the existing Deepline skills.");
29232
29521
  }
29233
- const installCode = await runProcess(
29522
+ const installCode = await execute(
29234
29523
  plan.install.command,
29235
29524
  plan.install.args,
29236
29525
  root ?? void 0
@@ -29935,6 +30224,85 @@ var import_node_child_process5 = require("child_process");
29935
30224
  var import_node_fs18 = require("fs");
29936
30225
  var import_node_os15 = require("os");
29937
30226
  var import_node_path20 = require("path");
30227
+ var SETUP_PHASE_NAMES = [
30228
+ "cli",
30229
+ "cleanup",
30230
+ "skills",
30231
+ "auth",
30232
+ "verify"
30233
+ ];
30234
+ function initialSetupPhases() {
30235
+ return {
30236
+ cli: { status: "pending" },
30237
+ cleanup: { status: "pending" },
30238
+ skills: { status: "pending" },
30239
+ auth: { status: "pending" },
30240
+ verify: { status: "pending" }
30241
+ };
30242
+ }
30243
+ function isSetupPhaseStatus(value) {
30244
+ return value === "pending" || value === "in_progress" || value === "complete" || value === "waiting" || value === "failed";
30245
+ }
30246
+ function parseSetupPhases(value) {
30247
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30248
+ const source = value;
30249
+ const phases = initialSetupPhases();
30250
+ for (const name of SETUP_PHASE_NAMES) {
30251
+ const phase = source[name];
30252
+ if (!phase || typeof phase !== "object" || Array.isArray(phase)) {
30253
+ return null;
30254
+ }
30255
+ const record = phase;
30256
+ if (!isSetupPhaseStatus(record.status)) return null;
30257
+ phases[name] = {
30258
+ status: record.status,
30259
+ ...typeof record.outcome === "string" ? { outcome: record.outcome } : {},
30260
+ ...typeof record.code === "string" ? { code: record.code } : {}
30261
+ };
30262
+ }
30263
+ return phases;
30264
+ }
30265
+ function phasesFromLegacyStatus(status) {
30266
+ const phases = initialSetupPhases();
30267
+ if (status === "skills_installed" || status === "authorization_pending" || status === "complete") {
30268
+ phases.cli = { status: "complete" };
30269
+ phases.cleanup = { status: "complete" };
30270
+ phases.skills = { status: "complete" };
30271
+ }
30272
+ if (status === "authorization_pending") {
30273
+ phases.auth = { status: "waiting", outcome: "authorization_pending" };
30274
+ } else if (status === "complete") {
30275
+ phases.auth = { status: "complete" };
30276
+ phases.verify = { status: "complete" };
30277
+ }
30278
+ return phases;
30279
+ }
30280
+ function readSetupState(input2) {
30281
+ try {
30282
+ const parsed = JSON.parse(
30283
+ (0, import_node_fs18.readFileSync)(
30284
+ setupStatePath(input2.baseUrl, input2.scope, input2.root),
30285
+ "utf8"
30286
+ )
30287
+ );
30288
+ if (parsed.host !== input2.baseUrl || parsed.scope !== input2.scope || parsed.root !== input2.root || typeof parsed.status !== "string") {
30289
+ return null;
30290
+ }
30291
+ return {
30292
+ status: parsed.status,
30293
+ phases: parseSetupPhases(parsed.phases) ?? phasesFromLegacyStatus(parsed.status)
30294
+ };
30295
+ } catch {
30296
+ return null;
30297
+ }
30298
+ }
30299
+ function selectSetupProgress(previousState) {
30300
+ const resumed = Boolean(previousState && previousState.status !== "complete");
30301
+ return {
30302
+ resumed,
30303
+ phases: resumed ? previousState.phases : initialSetupPhases()
30304
+ };
30305
+ }
29938
30306
  function normalizeScope2(value) {
29939
30307
  if (!value || value === "global") return "global";
29940
30308
  if (value === "local") return "local";
@@ -30105,13 +30473,14 @@ function writeSetupState(input2) {
30105
30473
  path,
30106
30474
  `${JSON.stringify(
30107
30475
  {
30108
- schemaVersion: 1,
30476
+ schemaVersion: 2,
30109
30477
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
30110
30478
  cliVersion: SDK_VERSION,
30111
30479
  host: input2.baseUrl,
30112
30480
  scope: input2.scope,
30113
30481
  root: input2.root,
30114
- status: input2.status
30482
+ status: input2.status,
30483
+ phases: input2.phases
30115
30484
  },
30116
30485
  null,
30117
30486
  2
@@ -30121,6 +30490,24 @@ function writeSetupState(input2) {
30121
30490
  );
30122
30491
  return path;
30123
30492
  }
30493
+ function persistSetupProgress(input2) {
30494
+ return writeSetupState({
30495
+ ...input2,
30496
+ status: input2.status ?? "in_progress"
30497
+ });
30498
+ }
30499
+ function completeSetupPhase(phases, phase, outcome) {
30500
+ phases[phase] = {
30501
+ status: "complete",
30502
+ ...outcome ? { outcome } : {}
30503
+ };
30504
+ }
30505
+ function beginSetupPhase(phases, phase) {
30506
+ phases[phase] = { status: "in_progress" };
30507
+ }
30508
+ function failSetupPhase(phases, phase, code) {
30509
+ phases[phase] = { status: "failed", code };
30510
+ }
30124
30511
  function rollbackCommand(scope, root) {
30125
30512
  const prefix = scope === "local" && root ? ` --prefix ${JSON.stringify((0, import_node_path20.join)(root, ".deepline", "runtime"))}` : "";
30126
30513
  return `npm install -g${prefix} --no-audit --no-fund --include=optional --allow-scripts=esbuild deepline@${SDK_VERSION}`;
@@ -30132,6 +30519,42 @@ function setupResumeCommand(baseUrl, scope) {
30132
30519
  const hostPrefix = baseUrl === "https://code.deepline.com" ? "" : `DEEPLINE_HOST_URL=${JSON.stringify(baseUrl)} `;
30133
30520
  return `${hostPrefix}deepline setup --scope ${scope} --json`;
30134
30521
  }
30522
+ function setupRetry(input2) {
30523
+ return {
30524
+ phase: input2.phase,
30525
+ command: setupResumeCommand(input2.baseUrl, input2.scope),
30526
+ automatic: true
30527
+ };
30528
+ }
30529
+ function reportSetupPhaseFailure(input2) {
30530
+ failSetupPhase(input2.phases, input2.phase, input2.code);
30531
+ const statePath = persistSetupProgress({
30532
+ baseUrl: input2.baseUrl,
30533
+ scope: input2.scope,
30534
+ root: input2.root,
30535
+ phases: input2.phases,
30536
+ status: "failed"
30537
+ });
30538
+ const retry = setupRetry(input2);
30539
+ printCommandEnvelope(
30540
+ {
30541
+ ok: false,
30542
+ status: "failed",
30543
+ code: input2.code,
30544
+ exitCode: input2.exitCode,
30545
+ scope: input2.scope,
30546
+ message: input2.message,
30547
+ phases: input2.phases,
30548
+ failedPhase: input2.phase,
30549
+ retry,
30550
+ statePath,
30551
+ next: retry.command,
30552
+ ...input2.extra ?? {}
30553
+ },
30554
+ { json: input2.json }
30555
+ );
30556
+ return input2.exitCode;
30557
+ }
30135
30558
  async function readAuthStatus(authScope) {
30136
30559
  try {
30137
30560
  const captured = await captureStdout2(
@@ -30151,63 +30574,44 @@ async function readAuthStatus(authScope) {
30151
30574
  }
30152
30575
  function reportSetupAuthStatusFailure(input2) {
30153
30576
  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;
30577
+ return reportSetupPhaseFailure({
30578
+ baseUrl: input2.baseUrl,
30579
+ scope: input2.scope,
30580
+ root: input2.root,
30581
+ phases: input2.phases,
30582
+ phase: "auth",
30583
+ code: "AUTH_STATUS_FAILED",
30584
+ exitCode: 4,
30585
+ message: "Deepline could not verify authorization with the configured host.",
30586
+ json: input2.json,
30587
+ extra: { authScope: input2.authScope }
30588
+ });
30168
30589
  }
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);
30590
+ function buildDoctorAssessment(input2) {
30591
+ const skillsStatePath = skillsStatePathForScope(
30592
+ input2.baseUrl,
30593
+ input2.scope,
30594
+ input2.root
30595
+ );
30190
30596
  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;
30597
+ const apiKey = input2.scope === "local" ? resolveProjectApiKeyForBaseUrl(input2.baseUrl) : resolveGlobalApiKeyForBaseUrl(input2.baseUrl);
30598
+ const projectAuth = input2.scope === "local" && apiKey ? getResolvedProjectAuthSource(input2.baseUrl, apiKey) : null;
30599
+ const connected = input2.authStatus.payload?.connected === true;
30600
+ const authScopeOk = input2.scope === "local" ? Boolean(projectAuth) : Boolean(apiKey && !projectAuth);
30601
+ const skillsOk = skillsState?.scope === input2.scope && typeof skillsState.skillsVersion === "string" && Array.isArray(skillsState.agents) && skillsState.agents.length > 0;
30198
30602
  const runningCliPath = process.argv[1] ? (0, import_node_path20.resolve)(process.argv[1]) : null;
30199
- const globalCli = scope === "global" ? inspectGlobalCliAvailability() : null;
30603
+ const globalCli = input2.scope === "global" ? inspectGlobalCliAvailability() : null;
30200
30604
  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"))
30605
+ const cliPath = input2.scope === "global" ? pathGlobalCli : runningCliPath;
30606
+ const cliScopeOk = input2.scope === "global" ? Boolean(pathGlobalCli) : Boolean(
30607
+ input2.root && runningCliPath?.includes((0, import_node_path20.join)(input2.root, ".deepline", "runtime"))
30204
30608
  );
30205
30609
  const checks = {
30206
30610
  cli: {
30207
30611
  ok: Boolean(cliPath) && cliScopeOk,
30208
30612
  version: SDK_VERSION,
30209
30613
  path: cliPath,
30210
- scope
30614
+ scope: input2.scope
30211
30615
  },
30212
30616
  skills: {
30213
30617
  ok: skillsOk,
@@ -30218,16 +30622,48 @@ async function runDoctorCommand(options) {
30218
30622
  auth: {
30219
30623
  ok: connected && authScopeOk,
30220
30624
  scope: projectAuth ? "local" : apiKey ? "global" : null,
30221
- status: authStatus.payload?.status ?? "not_connected"
30625
+ status: input2.authStatus.payload?.status ?? "not_connected"
30222
30626
  },
30223
30627
  api: {
30224
- ok: connected && authStatus.exitCode === 0,
30225
- host: baseUrl,
30226
- workspace: authStatus.payload?.workspace ?? null,
30628
+ ok: connected && input2.authStatus.exitCode === 0,
30629
+ host: input2.baseUrl,
30630
+ workspace: input2.authStatus.payload?.workspace ?? null,
30227
30631
  providerSpend: false
30228
30632
  }
30229
30633
  };
30230
- const ok = Object.values(checks).every((check) => check.ok);
30634
+ return {
30635
+ ok: Object.values(checks).every((check) => check.ok),
30636
+ checks
30637
+ };
30638
+ }
30639
+ async function runDoctorCommand(options) {
30640
+ let scope;
30641
+ let root;
30642
+ try {
30643
+ scope = normalizeScope2(options.scope);
30644
+ root = resolveScopeRoot(scope);
30645
+ } catch (error) {
30646
+ printCommandEnvelope(
30647
+ {
30648
+ ok: false,
30649
+ status: "failed",
30650
+ code: "INVALID_SETUP_SCOPE",
30651
+ exitCode: 2,
30652
+ message: error instanceof Error ? error.message : String(error)
30653
+ },
30654
+ { json: options.json }
30655
+ );
30656
+ return 2;
30657
+ }
30658
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
30659
+ const authScope = authScopeForSetup(scope);
30660
+ const authStatus = await readAuthStatus(authScope);
30661
+ const { ok, checks } = buildDoctorAssessment({
30662
+ baseUrl,
30663
+ scope,
30664
+ root,
30665
+ authStatus
30666
+ });
30231
30667
  const quickstart = setupQuickstartCommand(baseUrl);
30232
30668
  printCommandEnvelope(
30233
30669
  {
@@ -30253,11 +30689,16 @@ async function runDoctorCommand(options) {
30253
30689
  return ok ? 0 : 7;
30254
30690
  }
30255
30691
  function pendingResult(input2) {
30692
+ input2.phases.auth = {
30693
+ status: "waiting",
30694
+ outcome: "authorization_pending"
30695
+ };
30256
30696
  const statePath = writeSetupState({
30257
30697
  baseUrl: input2.baseUrl,
30258
30698
  scope: input2.scope,
30259
30699
  root: input2.root,
30260
- status: "authorization_pending"
30700
+ status: "authorization_pending",
30701
+ phases: input2.phases
30261
30702
  });
30262
30703
  if (input2.authorizationUrl) {
30263
30704
  process.stderr.write(`Authorize Deepline: ${input2.authorizationUrl}
@@ -30271,6 +30712,14 @@ function pendingResult(input2) {
30271
30712
  scope: input2.scope,
30272
30713
  authorizationUrl: input2.authorizationUrl || null,
30273
30714
  statePath,
30715
+ phases: input2.phases,
30716
+ currentPhase: "auth",
30717
+ resumed: input2.resumed,
30718
+ retry: setupRetry({
30719
+ baseUrl: input2.baseUrl,
30720
+ scope: input2.scope,
30721
+ phase: "auth"
30722
+ }),
30274
30723
  next: `Approve the link, then run: ${setupResumeCommand(input2.baseUrl, input2.scope)}`,
30275
30724
  render: {
30276
30725
  sections: [
@@ -30301,84 +30750,146 @@ async function runSetupCommand(options) {
30301
30750
  status: "failed",
30302
30751
  code: "INVALID_SETUP_SCOPE",
30303
30752
  exitCode: 2,
30304
- message: error instanceof Error ? error.message : String(error)
30753
+ message: error instanceof Error ? error.message : String(error),
30754
+ phases: {
30755
+ ...initialSetupPhases(),
30756
+ cli: { status: "failed", code: "INVALID_SETUP_SCOPE" }
30757
+ },
30758
+ failedPhase: "cli"
30305
30759
  },
30306
30760
  { json: options.json }
30307
30761
  );
30308
30762
  return 2;
30309
30763
  }
30310
30764
  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,
30765
+ const previousState = readSetupState({ baseUrl, scope, root });
30766
+ const { resumed, phases } = selectSetupProgress(previousState);
30767
+ if (phases.cli.status !== "complete") {
30768
+ beginSetupPhase(phases, "cli");
30769
+ persistSetupProgress({ baseUrl, scope, root, phases });
30770
+ if (scope === "global") {
30771
+ const globalCli = inspectGlobalCliAvailability();
30772
+ if (!globalCli.ok) {
30773
+ const installedButUnreachable = Boolean(globalCli.persistentPath);
30774
+ const code = installedButUnreachable ? "GLOBAL_CLI_NOT_ON_PATH" : "GLOBAL_CLI_NOT_INSTALLED";
30775
+ return reportSetupPhaseFailure({
30776
+ baseUrl,
30321
30777
  scope,
30322
- persistentPath: globalCli.persistentPath,
30778
+ root,
30779
+ phases,
30780
+ phase: "cli",
30781
+ code,
30782
+ exitCode: 7,
30323
30783
  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;
30784
+ json: options.json,
30785
+ extra: {
30786
+ persistentPath: globalCli.persistentPath,
30787
+ fallback: "https://code.deepline.com/INSTALL.md"
30788
+ }
30789
+ });
30790
+ }
30329
30791
  }
30330
- }
30331
- const conflict = inspectPathConflict();
30332
- if (conflict) {
30333
- printCommandEnvelope(
30334
- {
30335
- ok: false,
30336
- status: "failed",
30792
+ const conflict = inspectPathConflict();
30793
+ if (conflict) {
30794
+ return reportSetupPhaseFailure({
30795
+ baseUrl,
30796
+ scope,
30797
+ root,
30798
+ phases,
30799
+ phase: "cli",
30337
30800
  code: "PATH_CONFLICT",
30338
30801
  exitCode: 7,
30339
- path: conflict,
30340
30802
  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;
30803
+ json: options.json,
30804
+ extra: { path: conflict }
30805
+ });
30806
+ }
30807
+ completeSetupPhase(phases, "cli", "available");
30808
+ persistSetupProgress({ baseUrl, scope, root, phases });
30346
30809
  }
30347
- const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30348
- if (removedLegacyPaths.length > 0) {
30349
- process.stderr.write(
30350
- `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30810
+ if (phases.cleanup.status !== "complete") {
30811
+ beginSetupPhase(phases, "cleanup");
30812
+ persistSetupProgress({ baseUrl, scope, root, phases });
30813
+ try {
30814
+ const removedLegacyPaths = removeKnownLegacyPaths(baseUrl);
30815
+ if (removedLegacyPaths.length > 0) {
30816
+ process.stderr.write(
30817
+ `Removed ${removedLegacyPaths.length} known legacy Deepline path(s).
30351
30818
  `
30352
- );
30819
+ );
30820
+ }
30821
+ completeSetupPhase(
30822
+ phases,
30823
+ "cleanup",
30824
+ removedLegacyPaths.length > 0 ? "removed_legacy_paths" : "clean"
30825
+ );
30826
+ persistSetupProgress({ baseUrl, scope, root, phases });
30827
+ } catch (error) {
30828
+ return reportSetupPhaseFailure({
30829
+ baseUrl,
30830
+ scope,
30831
+ root,
30832
+ phases,
30833
+ phase: "cleanup",
30834
+ code: "LEGACY_CLEANUP_FAILED",
30835
+ exitCode: 5,
30836
+ message: error instanceof Error ? error.message : String(error),
30837
+ json: options.json
30838
+ });
30839
+ }
30353
30840
  }
30354
- process.stderr.write(`Installing Deepline skills (${scope})...
30841
+ let skillsPayload = null;
30842
+ if (phases.skills.status !== "complete") {
30843
+ beginSetupPhase(phases, "skills");
30844
+ persistSetupProgress({ baseUrl, scope, root, phases });
30845
+ process.stderr.write(`Installing Deepline skills (${scope})...
30355
30846
  `);
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",
30847
+ const skills = await captureStdout2(
30848
+ () => runSkillsCommand({ scope, json: true })
30849
+ );
30850
+ skillsPayload = parseCapturedJson(skills.stdout);
30851
+ if (skills.exitCode !== 0) {
30852
+ return reportSetupPhaseFailure({
30853
+ baseUrl,
30854
+ scope,
30855
+ root,
30856
+ phases,
30857
+ phase: "skills",
30365
30858
  code: "SKILLS_INSTALL_FAILED",
30366
30859
  exitCode: skills.exitCode,
30367
- scope,
30368
- detail: skillsPayload,
30369
- next: `deepline skills --scope ${scope} --json`
30370
- },
30371
- { json: options.json }
30860
+ message: typeof skillsPayload?.message === "string" ? skillsPayload.message : "Deepline skills could not be installed.",
30861
+ json: options.json,
30862
+ extra: { detail: skillsPayload }
30863
+ });
30864
+ }
30865
+ completeSetupPhase(
30866
+ phases,
30867
+ "skills",
30868
+ skillsPayload?.status === "current" ? "current" : "installed"
30869
+ );
30870
+ writeSetupState({
30871
+ baseUrl,
30872
+ scope,
30873
+ root,
30874
+ status: "skills_installed",
30875
+ phases
30876
+ });
30877
+ } else {
30878
+ skillsPayload = parseCapturedJson(
30879
+ safeRead(skillsStatePathForScope(baseUrl, scope, root))
30372
30880
  );
30373
- return skills.exitCode;
30374
30881
  }
30375
- writeSetupState({ baseUrl, scope, root, status: "skills_installed" });
30376
30882
  const authScope = authScopeForSetup(scope);
30883
+ beginSetupPhase(phases, "auth");
30884
+ persistSetupProgress({ baseUrl, scope, root, phases });
30377
30885
  let auth = await readAuthStatus(authScope);
30378
30886
  const initialAuthFailure = reportSetupAuthStatusFailure({
30379
30887
  auth,
30888
+ baseUrl,
30380
30889
  authScope,
30381
30890
  scope,
30891
+ root,
30892
+ phases,
30382
30893
  json: options.json
30383
30894
  });
30384
30895
  if (initialAuthFailure !== null) return initialAuthFailure;
@@ -30393,8 +30904,11 @@ async function runSetupCommand(options) {
30393
30904
  auth = await readAuthStatus(authScope);
30394
30905
  const resumedAuthFailure = reportSetupAuthStatusFailure({
30395
30906
  auth,
30907
+ baseUrl,
30396
30908
  authScope,
30397
30909
  scope,
30910
+ root,
30911
+ phases,
30398
30912
  json: options.json
30399
30913
  });
30400
30914
  if (resumedAuthFailure !== null) return resumedAuthFailure;
@@ -30405,7 +30919,9 @@ async function runSetupCommand(options) {
30405
30919
  baseUrl,
30406
30920
  scope,
30407
30921
  root,
30922
+ phases,
30408
30923
  authorizationUrl: stillPending.claimUrl,
30924
+ resumed,
30409
30925
  json: options.json
30410
30926
  });
30411
30927
  }
@@ -30422,24 +30938,30 @@ async function runSetupCommand(options) {
30422
30938
  );
30423
30939
  printCapturedAuthorizationUrl(registered.stdout);
30424
30940
  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;
30941
+ return reportSetupPhaseFailure({
30942
+ baseUrl,
30943
+ scope,
30944
+ root,
30945
+ phases,
30946
+ phase: "auth",
30947
+ code: "AUTH_REGISTER_FAILED",
30948
+ exitCode: registered.exitCode,
30949
+ message: "Deepline browser authorization could not be started.",
30950
+ json: options.json,
30951
+ extra: {
30952
+ detail: parseCapturedJson(registered.stdout),
30953
+ authScope
30954
+ }
30955
+ });
30437
30956
  }
30438
30957
  auth = await readAuthStatus(authScope);
30439
30958
  const registeredAuthFailure = reportSetupAuthStatusFailure({
30440
30959
  auth,
30960
+ baseUrl,
30441
30961
  authScope,
30442
30962
  scope,
30963
+ root,
30964
+ phases,
30443
30965
  json: options.json
30444
30966
  });
30445
30967
  if (registeredAuthFailure !== null) return registeredAuthFailure;
@@ -30449,40 +30971,60 @@ async function runSetupCommand(options) {
30449
30971
  baseUrl,
30450
30972
  scope,
30451
30973
  root,
30974
+ phases,
30452
30975
  authorizationUrl: pending?.claimUrl ?? "",
30976
+ resumed,
30453
30977
  json: options.json
30454
30978
  });
30455
30979
  }
30456
30980
  }
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,
30981
+ completeSetupPhase(phases, "auth", "connected");
30982
+ beginSetupPhase(phases, "verify");
30983
+ persistSetupProgress({ baseUrl, scope, root, phases });
30984
+ const assessment = buildDoctorAssessment({
30985
+ baseUrl,
30986
+ scope,
30987
+ root,
30988
+ authStatus: auth
30989
+ });
30990
+ const quickstart = setupQuickstartCommand(baseUrl);
30991
+ const doctorPayload = {
30992
+ ok: assessment.ok,
30993
+ status: assessment.ok ? "complete" : "failed",
30994
+ code: assessment.ok ? "DOCTOR_OK" : "DOCTOR_FAILED",
30995
+ exitCode: assessment.ok ? 0 : 7,
30996
+ checks: assessment.checks,
30997
+ next: assessment.ok ? quickstart : `deepline doctor --scope ${scope} --json`
30998
+ };
30999
+ if (!assessment.ok) {
31000
+ return reportSetupPhaseFailure({
31001
+ baseUrl,
31002
+ scope,
31003
+ root,
31004
+ phases,
31005
+ phase: "verify",
31006
+ code: "DOCTOR_FAILED",
31007
+ exitCode: 7,
31008
+ message: "Deepline setup verification found one or more failed checks.",
31009
+ json: options.json,
31010
+ extra: {
30470
31011
  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;
31012
+ diagnostic: {
31013
+ command: `deepline doctor --scope ${scope} --json`
31014
+ }
31015
+ }
31016
+ });
30476
31017
  }
31018
+ completeSetupPhase(phases, "verify", "verified");
30477
31019
  const statePath = writeSetupState({
30478
31020
  baseUrl,
30479
31021
  scope,
30480
31022
  root,
30481
- status: "complete"
31023
+ status: "complete",
31024
+ phases
30482
31025
  });
30483
31026
  const doctorChecks = asRecord2(doctorPayload?.checks);
30484
31027
  const apiCheck = asRecord2(doctorChecks?.api);
30485
- const quickstart = setupQuickstartCommand(baseUrl);
30486
31028
  printCommandEnvelope(
30487
31029
  {
30488
31030
  ok: true,
@@ -30495,6 +31037,10 @@ async function runSetupCommand(options) {
30495
31037
  rollbackCommand: rollbackCommand(scope, root),
30496
31038
  statePath,
30497
31039
  doctor: doctorPayload,
31040
+ phases,
31041
+ currentPhase: null,
31042
+ failedPhase: null,
31043
+ resumed,
30498
31044
  next: quickstart,
30499
31045
  render: {
30500
31046
  sections: [
@@ -30517,8 +31063,9 @@ function registerSetupCommands(program) {
30517
31063
  "after",
30518
31064
  `
30519
31065
  Notes:
30520
- Setup is idempotent. Bare setup resumes pending browser authorization.
31066
+ Setup is idempotent. Bare setup resumes the first incomplete or failed phase.
30521
31067
  It installs skills before auth and does not run quickstart.
31068
+ JSON output includes phase status and an exact retry command.
30522
31069
 
30523
31070
  Examples:
30524
31071
  deepline setup --json