deepline 0.1.261 → 0.1.263

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.
@@ -123,7 +123,7 @@ export const SDK_RELEASE = {
123
123
  // Deepline-native radars. Older clients must update before discovering,
124
124
  // checking, or deploying an unlaunched monitor integration.
125
125
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
126
- version: '0.1.261',
126
+ version: '0.1.263',
127
127
  apiContract: '2026-07-native-monitor-launch-hard-cutover',
128
128
  supportPolicy: {
129
129
  minimumSupported: '0.1.53',
@@ -1,5 +1,6 @@
1
1
  import { isHardBillingToolHttpError } from './tool-http-errors';
2
2
  import { isRuntimePersistenceCircuitOpenError } from './persistence-latch';
3
+ import { isWorkspaceStorageNotReadyFailure } from './run-failure';
3
4
 
4
5
  /**
5
6
  * Thrown by runner Adapters when a Play Run is externally cancelled. Row
@@ -41,6 +42,7 @@ export function isRowIsolationExemptError(error: unknown): boolean {
41
42
  if (isRuntimeReceiptPersistenceError(error)) return true;
42
43
  if (isRuntimeStoragePersistenceError(error)) return true;
43
44
  if (isRuntimePersistenceCircuitOpenError(error)) return true;
45
+ if (isWorkspaceStorageNotReadyFailure(error)) return true;
44
46
  return isHardBillingToolHttpError(error);
45
47
  }
46
48
 
@@ -59,6 +59,24 @@ export class WorkspaceStorageNotReadyError extends Error {
59
59
  }
60
60
  }
61
61
 
62
+ export function isWorkspaceStorageNotReadyFailure(error: unknown): boolean {
63
+ if (error instanceof WorkspaceStorageNotReadyError) return true;
64
+ if (!error) return false;
65
+ if (typeof error === 'object') {
66
+ const code = (error as { code?: unknown }).code;
67
+ if (code === WORKSPACE_STORAGE_NOT_READY_CODE) return true;
68
+ const nestedErrors = (error as { errors?: unknown }).errors;
69
+ if (
70
+ Array.isArray(nestedErrors) &&
71
+ nestedErrors.some(isWorkspaceStorageNotReadyFailure)
72
+ ) {
73
+ return true;
74
+ }
75
+ }
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ return /\bWORKSPACE_STORAGE_NOT_READY\b/.test(message);
78
+ }
79
+
62
80
  export const PROVIDER_EXHAUSTED_CODE = 'PROVIDER_EXHAUSTED';
63
81
 
64
82
  /**
@@ -331,7 +349,7 @@ export function normalizePlayRunFailure(error: unknown): PlayRunFailureDetails {
331
349
  };
332
350
  }
333
351
  if (
334
- error instanceof WorkspaceStorageNotReadyError ||
352
+ isWorkspaceStorageNotReadyFailure(error) ||
335
353
  /\bWORKSPACE_STORAGE_NOT_READY\b/.test(rawCause)
336
354
  ) {
337
355
  return {
@@ -490,6 +490,25 @@ function isRetryableDaytonaInfrastructureFailure(error: unknown): boolean {
490
490
  return DAYTONA_INFRASTRUCTURE_RETRY_PATTERN.test(message);
491
491
  }
492
492
 
493
+ function isRetryableUnstartedDaytonaRunner(
494
+ error: unknown,
495
+ ): error is DaytonaRunnerInitializationError {
496
+ if (!(error instanceof DaytonaRunnerInitializationError)) return false;
497
+ const diagnostic = error.startupDiagnostic;
498
+ // The sandbox runner cannot execute customer code until the worker observes
499
+ // its heartbeat, parks the scheduler attempt, and the gateway acknowledges
500
+ // that waiting state. If no heartbeat was observed and Daytona has no exit
501
+ // evidence, deleting this fenced sandbox and retrying once cannot duplicate
502
+ // customer side effects.
503
+ return (
504
+ diagnostic.commandFound &&
505
+ diagnostic.sessionFound &&
506
+ diagnostic.exitCode === null &&
507
+ diagnostic.exitCodeFile === null &&
508
+ !diagnostic.schedulerReadinessObserved
509
+ );
510
+ }
511
+
493
512
  function prepareDaytonaExecution(
494
513
  input: PlayRunnerPrepareInput,
495
514
  callbacks: Parameters<PlayRunnerBackend['execute']>[1],
@@ -930,14 +949,19 @@ export const daytonaPlayRunnerBackend: PlayRunnerBackend = {
930
949
  onCancel();
931
950
  throw new Error(DAYTONA_CANCELLED_ERROR);
932
951
  }
952
+ const retryReason = isRetryableUnstartedDaytonaRunner(error)
953
+ ? 'runner_startup_not_live'
954
+ : isRetryableDaytonaInfrastructureFailure(error)
955
+ ? 'daytona_infrastructure'
956
+ : null;
933
957
  if (
934
958
  executionAttempt < DAYTONA_INFRASTRUCTURE_MAX_ATTEMPTS &&
935
- isRetryableDaytonaInfrastructureFailure(error)
959
+ retryReason
936
960
  ) {
937
961
  emitDaytonaStage(callbacks, config.context, 'execute:retry', {
938
962
  sandboxId: sandboxForAttempt?.id ?? null,
939
963
  attempt: executionAttempt + 1,
940
- reason: 'daytona_infrastructure',
964
+ reason: retryReason,
941
965
  error: error instanceof Error ? error.message : String(error),
942
966
  elapsedMs: Date.now() - startedAt,
943
967
  });
@@ -0,0 +1,51 @@
1
+ // One-line definitions for the tool categories surfaced by `deepline tools list`.
2
+ //
3
+ // Tool categories are free-form strings that each provider tool declares in the
4
+ // generated catalog (see `src/lib/integrations/**`), not a single closed enum.
5
+ // The canonical set of well-known category slugs lives next to this file in
6
+ // `DEEPLINE_TOOL_CATEGORIES` (bootstrap-routes.ts) and in the richer
7
+ // `IntegrationCatalogTag` enum (src/lib/integrations/catalog-tags.ts). This map
8
+ // MUST track the categories that actually appear on live tools. The CLI derives
9
+ // the category enumeration from the live catalog, so a new category slug shows
10
+ // up automatically; only its human-readable definition comes from here. When a
11
+ // category has no entry the CLI renders it without a definition (never invents
12
+ // one), which is the loud signal to add it here.
13
+ //
14
+ // Keep each definition to a single, plain sentence fragment.
15
+
16
+ export const TOOL_CATEGORY_DESCRIPTIONS: Readonly<Record<string, string>> = {
17
+ company_search: 'Find companies matching firmographic, ad, or web criteria.',
18
+ people_search: 'Find people matching role, company, or persona criteria.',
19
+ people_enrich:
20
+ 'Enrich a known person with contact, social, and profile data.',
21
+ company_enrich: 'Enrich a known company with firmographic and account data.',
22
+ email_finder: 'Find work or personal email addresses for a person.',
23
+ email_verify: 'Validate email deliverability and catch-all status.',
24
+ phone_finder: 'Find mobile or direct phone numbers for a person.',
25
+ phone_verify: 'Validate phone-number reachability and line type.',
26
+ research:
27
+ 'Research accounts, people, news, and signals for context and scoring.',
28
+ autocomplete: 'Resolve names and entities to canonical catalog records.',
29
+ automation:
30
+ 'Kick off provider-side jobs, exports, and asynchronous workflows.',
31
+ outbound_tools:
32
+ 'Run outbound sequencing, deliverability, and campaign operations.',
33
+ admin:
34
+ 'Provider-side status, metadata, and account operations behind other tools.',
35
+ smb: 'Look up small-business listings, local records, and public filings.',
36
+ identity_resolution:
37
+ 'Match a partial identity to a single resolved person or company.',
38
+ reverse_lookup: 'Resolve an email, phone, or profile back to a person.',
39
+ enrichment: 'General-purpose record enrichment across people and companies.',
40
+ batch: 'Run an enrichment or search operation over many rows at once.',
41
+ premium: 'Higher-cost tools with premium provider coverage.',
42
+ free: 'Free tools that do not spend Deepline credits.',
43
+ };
44
+
45
+ /**
46
+ * One-line definition for a tool category slug, or null when none is declared.
47
+ * Never fabricates a definition for unknown slugs.
48
+ */
49
+ export function describeToolCategory(category: string): string | null {
50
+ return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
51
+ }
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.261",
719
+ version: "0.1.263",
720
720
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
721
721
  supportPolicy: {
722
722
  minimumSupported: "0.1.53",
@@ -12465,13 +12465,21 @@ function getStatusFromLiveEvent(event) {
12465
12465
  const payload = getEventPayload(event);
12466
12466
  return playStatusValue(payload.status) ?? playStatusValue(getRunRecordFromPackage(payload)?.status);
12467
12467
  }
12468
+ function getWaitKindFromLiveEvent(event) {
12469
+ const payload = getEventPayload(event);
12470
+ const progress = payload.progress && typeof payload.progress === "object" ? payload.progress : null;
12471
+ const waitKind = payload.waitKind ?? progress?.waitKind;
12472
+ return typeof waitKind === "string" && waitKind.trim() ? waitKind.trim() : null;
12473
+ }
12468
12474
  function writePlayWaitingHint(input2) {
12469
12475
  if (getStatusFromLiveEvent(input2.event) !== "waiting") return;
12470
12476
  const runId = getRunIdFromLiveEvent(input2.event);
12471
12477
  if (!runId || input2.state.waitingRunId === runId) return;
12472
12478
  input2.state.waitingRunId = runId;
12479
+ const waitKind = getWaitKindFromLiveEvent(input2.event);
12480
+ const message = waitKind === "integration_event" || waitKind === "integration_event_batch" ? "is waiting for an external event." : waitKind === "sleep" ? "is waiting for its scheduled resume." : waitKind === "detached_runner" ? "is executing in the runtime." : "is waiting on a durable runtime boundary.";
12473
12481
  input2.progress.writeLine(
12474
- `[play waiting] ${runId} is waiting for an external approval or event. The command will continue watching; inspect it with 'deepline runs get ${runId} --full --json'.`
12482
+ `[play waiting] ${runId} ${message} The command will continue watching; inspect it with 'deepline runs get ${runId} --full --json'.`
12475
12483
  );
12476
12484
  }
12477
12485
  function getFinalStatusFromLiveEvent(event) {
@@ -12734,6 +12742,9 @@ function getRunningHeartbeatLine(input2) {
12734
12742
  return `queued ${target}: waiting for worker capacity`;
12735
12743
  }
12736
12744
  if (status === "waiting") {
12745
+ if (getWaitKindFromLiveEvent(input2.event) === "detached_runner") {
12746
+ return `running ${target}: executing in detached runtime`;
12747
+ }
12737
12748
  return `waiting ${target}: waiting on external work`;
12738
12749
  }
12739
12750
  return `running ${target}: still processing`;
@@ -26616,6 +26627,33 @@ function extractSummaryFields(payload) {
26616
26627
  return {};
26617
26628
  }
26618
26629
 
26630
+ // ../shared_libs/plays/tool-category-descriptions.ts
26631
+ var TOOL_CATEGORY_DESCRIPTIONS = {
26632
+ company_search: "Find companies matching firmographic, ad, or web criteria.",
26633
+ people_search: "Find people matching role, company, or persona criteria.",
26634
+ people_enrich: "Enrich a known person with contact, social, and profile data.",
26635
+ company_enrich: "Enrich a known company with firmographic and account data.",
26636
+ email_finder: "Find work or personal email addresses for a person.",
26637
+ email_verify: "Validate email deliverability and catch-all status.",
26638
+ phone_finder: "Find mobile or direct phone numbers for a person.",
26639
+ phone_verify: "Validate phone-number reachability and line type.",
26640
+ research: "Research accounts, people, news, and signals for context and scoring.",
26641
+ autocomplete: "Resolve names and entities to canonical catalog records.",
26642
+ automation: "Kick off provider-side jobs, exports, and asynchronous workflows.",
26643
+ outbound_tools: "Run outbound sequencing, deliverability, and campaign operations.",
26644
+ admin: "Provider-side status, metadata, and account operations behind other tools.",
26645
+ smb: "Look up small-business listings, local records, and public filings.",
26646
+ identity_resolution: "Match a partial identity to a single resolved person or company.",
26647
+ reverse_lookup: "Resolve an email, phone, or profile back to a person.",
26648
+ enrichment: "General-purpose record enrichment across people and companies.",
26649
+ batch: "Run an enrichment or search operation over many rows at once.",
26650
+ premium: "Higher-cost tools with premium provider coverage.",
26651
+ free: "Free tools that do not spend Deepline credits."
26652
+ };
26653
+ function describeToolCategory(category) {
26654
+ return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
26655
+ }
26656
+
26619
26657
  // src/cli/commands/model-description.ts
26620
26658
  function formatModelDescription(description) {
26621
26659
  const lines = [];
@@ -26782,21 +26820,181 @@ function zeroMatchRender(emptyResult) {
26782
26820
  ]
26783
26821
  };
26784
26822
  }
26785
- async function listTools(args) {
26823
+ function firstSentence(text) {
26824
+ const clean = (text ?? "").replace(/\s+/g, " ").trim();
26825
+ if (!clean) return "";
26826
+ const match = clean.match(/^.*?[.!?](?=\s|$)/);
26827
+ return (match ? match[0] : clean).trim();
26828
+ }
26829
+ function requiredInputIds(tool) {
26830
+ const inputSchema = recordField2(
26831
+ tool,
26832
+ "inputSchema",
26833
+ "input_schema"
26834
+ );
26835
+ return toolInputFieldsForDisplay(inputSchema).filter((field) => field.required === true).map((field) => String(field.name ?? "")).filter(Boolean);
26836
+ }
26837
+ function shortPricingHint(tool) {
26838
+ const pricing = recordField2(
26839
+ tool,
26840
+ "pricing"
26841
+ );
26842
+ const credits = numberField2(pricing, "creditsPerUnit", "credits_per_unit");
26843
+ const unit = stringField2(pricing, "unit");
26844
+ if (credits !== null) {
26845
+ return `${formatDecimal(credits)} cr/${unit || "unit"}`;
26846
+ }
26847
+ const summary = stringField2(pricing, "summary");
26848
+ if (summary) return summary;
26849
+ const displayText = stringField2(pricing, "displayText", "display_text");
26850
+ return displayText || null;
26851
+ }
26852
+ function aggregateCategorySummaries(tools) {
26853
+ const counts = /* @__PURE__ */ new Map();
26854
+ for (const tool of tools) {
26855
+ for (const category of tool.categories ?? []) {
26856
+ counts.set(category, (counts.get(category) ?? 0) + 1);
26857
+ }
26858
+ }
26859
+ return [...counts.entries()].map(([name, count]) => ({
26860
+ name,
26861
+ count,
26862
+ description: describeToolCategory(name)
26863
+ })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
26864
+ }
26865
+ function categoryEnumerationRender(summaries) {
26866
+ const nameWidth = summaries.reduce(
26867
+ (max, item) => Math.max(max, item.name.length),
26868
+ 0
26869
+ );
26870
+ const countWidth = summaries.reduce(
26871
+ (max, item) => Math.max(max, String(item.count).length),
26872
+ 0
26873
+ );
26874
+ return {
26875
+ sections: [
26876
+ {
26877
+ title: `${summaries.length} tool categories:`,
26878
+ lines: summaries.map((item) => {
26879
+ const name = item.name.padEnd(nameWidth);
26880
+ const count = String(item.count).padStart(countWidth);
26881
+ const definition = item.description ? ` - ${item.description}` : "";
26882
+ return `${name} ${count}${definition}`;
26883
+ })
26884
+ },
26885
+ {
26886
+ title: "next",
26887
+ lines: ["Pass a category: deepline tools list email_finder"]
26888
+ }
26889
+ ]
26890
+ };
26891
+ }
26892
+ async function listToolCategories(emitJson, compact) {
26893
+ const client2 = new DeeplineClient();
26894
+ const rawTools = await client2.listTools({ compact });
26895
+ const items = rawTools.map(toListedTool);
26896
+ const summaries = aggregateCategorySummaries(rawTools);
26897
+ const outputItems = compact ? items.map(compactTool) : items;
26898
+ printCommandEnvelope(
26899
+ {
26900
+ tools: outputItems,
26901
+ count: outputItems.length,
26902
+ categories: summaries,
26903
+ filters: {
26904
+ categories: []
26905
+ },
26906
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26907
+ render: categoryEnumerationRender(summaries)
26908
+ },
26909
+ { json: emitJson }
26910
+ );
26911
+ return 0;
26912
+ }
26913
+ function unknownCategoryError(category, summaries, emitJson) {
26914
+ const known = summaries.map((item) => item.name);
26915
+ const message = `Unknown tool category "${category}". Run \`deepline tools list\` to browse categories.`;
26916
+ if (emitJson) {
26917
+ printJson({
26918
+ ok: false,
26919
+ exitCode: 4,
26920
+ code: "TOOL_CATEGORY_NOT_FOUND",
26921
+ message,
26922
+ category,
26923
+ categories: summaries,
26924
+ next: "deepline tools list"
26925
+ });
26926
+ return 4;
26927
+ }
26928
+ const render = categoryEnumerationRender(summaries);
26929
+ console.error(message);
26930
+ console.error("");
26931
+ console.error(renderCommandEnvelopeText({ render }).trimEnd());
26932
+ console.error("");
26933
+ console.error(`Valid categories: ${known.join(", ")}`);
26934
+ return 4;
26935
+ }
26936
+ async function listToolsInCategory(category, emitJson) {
26937
+ const client2 = new DeeplineClient();
26938
+ const rawTools = await client2.listTools({
26939
+ categories: category,
26940
+ compact: false
26941
+ });
26942
+ const allTools = await client2.listTools({ compact: true });
26943
+ const summaries = aggregateCategorySummaries(allTools);
26944
+ if (!summaries.some((item) => item.name === category)) {
26945
+ return unknownCategoryError(category, summaries, emitJson);
26946
+ }
26947
+ const rows = rawTools.filter((tool) => tool.categories?.includes(category)).map((tool) => ({
26948
+ id: tool.toolId,
26949
+ provider: tool.provider,
26950
+ required: requiredInputIds(tool),
26951
+ pricing: shortPricingHint(tool),
26952
+ description: firstSentence(tool.description)
26953
+ })).sort((a, b) => a.id.localeCompare(b.id));
26954
+ const idWidth = rows.reduce((max, row) => Math.max(max, row.id.length), 0);
26955
+ const render = {
26956
+ sections: [
26957
+ {
26958
+ title: `${rows.length} tools in ${category}:`,
26959
+ lines: rows.map((row) => {
26960
+ const id = row.id.padEnd(idWidth);
26961
+ const required = row.required.length ? `(${row.required.join(", ")})` : "(no required inputs)";
26962
+ const pricing = row.pricing ? ` [${row.pricing}]` : "";
26963
+ const description = row.description ? ` - ${row.description}` : "";
26964
+ return `${id} ${required}${pricing}${description}`;
26965
+ })
26966
+ },
26967
+ {
26968
+ title: "next",
26969
+ lines: [
26970
+ "Run `deepline tools describe <toolId>` for the full contract."
26971
+ ]
26972
+ }
26973
+ ]
26974
+ };
26975
+ printCommandEnvelope(
26976
+ {
26977
+ category,
26978
+ total: rows.length,
26979
+ tools: rows,
26980
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26981
+ render
26982
+ },
26983
+ { json: emitJson }
26984
+ );
26985
+ return 0;
26986
+ }
26987
+ async function listToolsForCategoryFilter(requestedCategories, emitJson, compact) {
26786
26988
  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
26989
  const items = (await client2.listTools({
26792
- ...categoryFilter ? { categories: categoryFilter } : {},
26990
+ categories: requestedCategories.join(","),
26793
26991
  compact
26794
26992
  })).map(toListedTool).filter(
26795
- (item) => requestedCategories.length === 0 || requestedCategories.some(
26993
+ (item) => requestedCategories.some(
26796
26994
  (category) => item.categories.includes(category)
26797
26995
  )
26798
26996
  );
26799
- const emptyResult = items.length === 0 && requestedCategories.length > 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26997
+ const emptyResult = items.length === 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26800
26998
  const render = {
26801
26999
  sections: emptyResult ? zeroMatchRender(emptyResult).sections : [
26802
27000
  {
@@ -26827,10 +27025,41 @@ async function listTools(args) {
26827
27025
  commandTemplates: TOOL_COMMAND_TEMPLATES,
26828
27026
  render
26829
27027
  },
26830
- { json: argsWantJson(args) }
27028
+ { json: emitJson }
26831
27029
  );
26832
27030
  return 0;
26833
27031
  }
27032
+ async function listTools(category, options = {}) {
27033
+ const positional = category?.trim() ?? "";
27034
+ const flagCategories = (options.categories ?? "").split(",").map((item) => item.trim()).filter(Boolean);
27035
+ const compact = options.compact !== false;
27036
+ const emitJson = options.json === true || shouldEmitJson();
27037
+ if (positional && flagCategories.length > 0) {
27038
+ const flagIsSameSingle = flagCategories.length === 1 && flagCategories[0] === positional;
27039
+ if (!flagIsSameSingle) {
27040
+ const message = `Category "${positional}" and --categories ${flagCategories.join(",")} disagree. Pass one: deepline tools list ${positional}`;
27041
+ if (emitJson) {
27042
+ printJson({
27043
+ ok: false,
27044
+ exitCode: 2,
27045
+ code: "TOOL_LIST_CATEGORY_CONFLICT",
27046
+ message,
27047
+ next: `deepline tools list ${positional}`
27048
+ });
27049
+ } else {
27050
+ console.error(message);
27051
+ }
27052
+ return 2;
27053
+ }
27054
+ }
27055
+ if (positional) {
27056
+ return listToolsInCategory(positional, emitJson);
27057
+ }
27058
+ if (flagCategories.length > 0) {
27059
+ return listToolsForCategoryFilter(flagCategories, emitJson, compact);
27060
+ }
27061
+ return listToolCategories(emitJson, compact);
27062
+ }
26834
27063
  async function searchTools(queryInput, options = {}) {
26835
27064
  const query = queryInput?.trim() ?? "";
26836
27065
  const hasStructuredSearch = Boolean(
@@ -27029,27 +27258,37 @@ Output:
27029
27258
  Use execute to run a tool.
27030
27259
  `
27031
27260
  );
27032
- tools.command("list").description("List available tools.").addHelpText(
27261
+ tools.command("list [category]").description("Browse tool categories, or list every tool in a category.").addHelpText(
27033
27262
  "after",
27034
27263
  `
27035
27264
  Notes:
27036
- Inventory command for known tool ids. Use search for ranked discovery by
27037
- intent, aliases, descriptions, and schema fields.
27265
+ Bare \`tools list\` prints the category enumeration: one line per category with
27266
+ its tool count and definition. Pass a category to list every tool in it, one
27267
+ succinct row each (required inputs, Deepline pricing hint, first sentence).
27268
+ The listing is exhaustive and never truncated. Use search for ranked discovery
27269
+ by intent, aliases, descriptions, and schema fields.
27270
+
27271
+ --categories is a back-compat inventory filter. Passing it alone returns the
27272
+ legacy flat inventory envelope (one row per tool). The positional category is
27273
+ the newer exhaustive per-category view. If both are given they must name the
27274
+ same category (the positional view wins); otherwise it is a usage error.
27038
27275
 
27039
27276
  Examples:
27040
27277
  deepline tools list
27278
+ deepline tools list email_finder
27279
+ deepline tools list email_finder --json
27041
27280
  deepline tools list --categories email_finder --json
27042
27281
  deepline tools search email --json
27043
27282
  `
27044
27283
  ).option(
27045
27284
  "--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
- ]);
27285
+ "Comma-separated categories filter (back-compat alias for the positional)"
27286
+ ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (category, options) => {
27287
+ process.exitCode = await listTools(category, {
27288
+ categories: typeof options.categories === "string" ? options.categories : void 0,
27289
+ compact: options.compact !== false,
27290
+ json: Boolean(options.json)
27291
+ });
27053
27292
  });
27054
27293
  const addToolSearchCommand = (command) => command.description(
27055
27294
  "Search available tools by intent query or structured filters."
@@ -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.261",
704
+ version: "0.1.263",
705
705
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
706
706
  supportPolicy: {
707
707
  minimumSupported: "0.1.53",
@@ -12494,13 +12494,21 @@ function getStatusFromLiveEvent(event) {
12494
12494
  const payload = getEventPayload(event);
12495
12495
  return playStatusValue(payload.status) ?? playStatusValue(getRunRecordFromPackage(payload)?.status);
12496
12496
  }
12497
+ function getWaitKindFromLiveEvent(event) {
12498
+ const payload = getEventPayload(event);
12499
+ const progress = payload.progress && typeof payload.progress === "object" ? payload.progress : null;
12500
+ const waitKind = payload.waitKind ?? progress?.waitKind;
12501
+ return typeof waitKind === "string" && waitKind.trim() ? waitKind.trim() : null;
12502
+ }
12497
12503
  function writePlayWaitingHint(input2) {
12498
12504
  if (getStatusFromLiveEvent(input2.event) !== "waiting") return;
12499
12505
  const runId = getRunIdFromLiveEvent(input2.event);
12500
12506
  if (!runId || input2.state.waitingRunId === runId) return;
12501
12507
  input2.state.waitingRunId = runId;
12508
+ const waitKind = getWaitKindFromLiveEvent(input2.event);
12509
+ const message = waitKind === "integration_event" || waitKind === "integration_event_batch" ? "is waiting for an external event." : waitKind === "sleep" ? "is waiting for its scheduled resume." : waitKind === "detached_runner" ? "is executing in the runtime." : "is waiting on a durable runtime boundary.";
12502
12510
  input2.progress.writeLine(
12503
- `[play waiting] ${runId} is waiting for an external approval or event. The command will continue watching; inspect it with 'deepline runs get ${runId} --full --json'.`
12511
+ `[play waiting] ${runId} ${message} The command will continue watching; inspect it with 'deepline runs get ${runId} --full --json'.`
12504
12512
  );
12505
12513
  }
12506
12514
  function getFinalStatusFromLiveEvent(event) {
@@ -12763,6 +12771,9 @@ function getRunningHeartbeatLine(input2) {
12763
12771
  return `queued ${target}: waiting for worker capacity`;
12764
12772
  }
12765
12773
  if (status === "waiting") {
12774
+ if (getWaitKindFromLiveEvent(input2.event) === "detached_runner") {
12775
+ return `running ${target}: executing in detached runtime`;
12776
+ }
12766
12777
  return `waiting ${target}: waiting on external work`;
12767
12778
  }
12768
12779
  return `running ${target}: still processing`;
@@ -26664,6 +26675,33 @@ function extractSummaryFields(payload) {
26664
26675
  return {};
26665
26676
  }
26666
26677
 
26678
+ // ../shared_libs/plays/tool-category-descriptions.ts
26679
+ var TOOL_CATEGORY_DESCRIPTIONS = {
26680
+ company_search: "Find companies matching firmographic, ad, or web criteria.",
26681
+ people_search: "Find people matching role, company, or persona criteria.",
26682
+ people_enrich: "Enrich a known person with contact, social, and profile data.",
26683
+ company_enrich: "Enrich a known company with firmographic and account data.",
26684
+ email_finder: "Find work or personal email addresses for a person.",
26685
+ email_verify: "Validate email deliverability and catch-all status.",
26686
+ phone_finder: "Find mobile or direct phone numbers for a person.",
26687
+ phone_verify: "Validate phone-number reachability and line type.",
26688
+ research: "Research accounts, people, news, and signals for context and scoring.",
26689
+ autocomplete: "Resolve names and entities to canonical catalog records.",
26690
+ automation: "Kick off provider-side jobs, exports, and asynchronous workflows.",
26691
+ outbound_tools: "Run outbound sequencing, deliverability, and campaign operations.",
26692
+ admin: "Provider-side status, metadata, and account operations behind other tools.",
26693
+ smb: "Look up small-business listings, local records, and public filings.",
26694
+ identity_resolution: "Match a partial identity to a single resolved person or company.",
26695
+ reverse_lookup: "Resolve an email, phone, or profile back to a person.",
26696
+ enrichment: "General-purpose record enrichment across people and companies.",
26697
+ batch: "Run an enrichment or search operation over many rows at once.",
26698
+ premium: "Higher-cost tools with premium provider coverage.",
26699
+ free: "Free tools that do not spend Deepline credits."
26700
+ };
26701
+ function describeToolCategory(category) {
26702
+ return TOOL_CATEGORY_DESCRIPTIONS[category] ?? null;
26703
+ }
26704
+
26667
26705
  // src/cli/commands/model-description.ts
26668
26706
  function formatModelDescription(description) {
26669
26707
  const lines = [];
@@ -26830,21 +26868,181 @@ function zeroMatchRender(emptyResult) {
26830
26868
  ]
26831
26869
  };
26832
26870
  }
26833
- async function listTools(args) {
26871
+ function firstSentence(text) {
26872
+ const clean = (text ?? "").replace(/\s+/g, " ").trim();
26873
+ if (!clean) return "";
26874
+ const match = clean.match(/^.*?[.!?](?=\s|$)/);
26875
+ return (match ? match[0] : clean).trim();
26876
+ }
26877
+ function requiredInputIds(tool) {
26878
+ const inputSchema = recordField2(
26879
+ tool,
26880
+ "inputSchema",
26881
+ "input_schema"
26882
+ );
26883
+ return toolInputFieldsForDisplay(inputSchema).filter((field) => field.required === true).map((field) => String(field.name ?? "")).filter(Boolean);
26884
+ }
26885
+ function shortPricingHint(tool) {
26886
+ const pricing = recordField2(
26887
+ tool,
26888
+ "pricing"
26889
+ );
26890
+ const credits = numberField2(pricing, "creditsPerUnit", "credits_per_unit");
26891
+ const unit = stringField2(pricing, "unit");
26892
+ if (credits !== null) {
26893
+ return `${formatDecimal(credits)} cr/${unit || "unit"}`;
26894
+ }
26895
+ const summary = stringField2(pricing, "summary");
26896
+ if (summary) return summary;
26897
+ const displayText = stringField2(pricing, "displayText", "display_text");
26898
+ return displayText || null;
26899
+ }
26900
+ function aggregateCategorySummaries(tools) {
26901
+ const counts = /* @__PURE__ */ new Map();
26902
+ for (const tool of tools) {
26903
+ for (const category of tool.categories ?? []) {
26904
+ counts.set(category, (counts.get(category) ?? 0) + 1);
26905
+ }
26906
+ }
26907
+ return [...counts.entries()].map(([name, count]) => ({
26908
+ name,
26909
+ count,
26910
+ description: describeToolCategory(name)
26911
+ })).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
26912
+ }
26913
+ function categoryEnumerationRender(summaries) {
26914
+ const nameWidth = summaries.reduce(
26915
+ (max, item) => Math.max(max, item.name.length),
26916
+ 0
26917
+ );
26918
+ const countWidth = summaries.reduce(
26919
+ (max, item) => Math.max(max, String(item.count).length),
26920
+ 0
26921
+ );
26922
+ return {
26923
+ sections: [
26924
+ {
26925
+ title: `${summaries.length} tool categories:`,
26926
+ lines: summaries.map((item) => {
26927
+ const name = item.name.padEnd(nameWidth);
26928
+ const count = String(item.count).padStart(countWidth);
26929
+ const definition = item.description ? ` - ${item.description}` : "";
26930
+ return `${name} ${count}${definition}`;
26931
+ })
26932
+ },
26933
+ {
26934
+ title: "next",
26935
+ lines: ["Pass a category: deepline tools list email_finder"]
26936
+ }
26937
+ ]
26938
+ };
26939
+ }
26940
+ async function listToolCategories(emitJson, compact) {
26941
+ const client2 = new DeeplineClient();
26942
+ const rawTools = await client2.listTools({ compact });
26943
+ const items = rawTools.map(toListedTool);
26944
+ const summaries = aggregateCategorySummaries(rawTools);
26945
+ const outputItems = compact ? items.map(compactTool) : items;
26946
+ printCommandEnvelope(
26947
+ {
26948
+ tools: outputItems,
26949
+ count: outputItems.length,
26950
+ categories: summaries,
26951
+ filters: {
26952
+ categories: []
26953
+ },
26954
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
26955
+ render: categoryEnumerationRender(summaries)
26956
+ },
26957
+ { json: emitJson }
26958
+ );
26959
+ return 0;
26960
+ }
26961
+ function unknownCategoryError(category, summaries, emitJson) {
26962
+ const known = summaries.map((item) => item.name);
26963
+ const message = `Unknown tool category "${category}". Run \`deepline tools list\` to browse categories.`;
26964
+ if (emitJson) {
26965
+ printJson({
26966
+ ok: false,
26967
+ exitCode: 4,
26968
+ code: "TOOL_CATEGORY_NOT_FOUND",
26969
+ message,
26970
+ category,
26971
+ categories: summaries,
26972
+ next: "deepline tools list"
26973
+ });
26974
+ return 4;
26975
+ }
26976
+ const render = categoryEnumerationRender(summaries);
26977
+ console.error(message);
26978
+ console.error("");
26979
+ console.error(renderCommandEnvelopeText({ render }).trimEnd());
26980
+ console.error("");
26981
+ console.error(`Valid categories: ${known.join(", ")}`);
26982
+ return 4;
26983
+ }
26984
+ async function listToolsInCategory(category, emitJson) {
26985
+ const client2 = new DeeplineClient();
26986
+ const rawTools = await client2.listTools({
26987
+ categories: category,
26988
+ compact: false
26989
+ });
26990
+ const allTools = await client2.listTools({ compact: true });
26991
+ const summaries = aggregateCategorySummaries(allTools);
26992
+ if (!summaries.some((item) => item.name === category)) {
26993
+ return unknownCategoryError(category, summaries, emitJson);
26994
+ }
26995
+ const rows = rawTools.filter((tool) => tool.categories?.includes(category)).map((tool) => ({
26996
+ id: tool.toolId,
26997
+ provider: tool.provider,
26998
+ required: requiredInputIds(tool),
26999
+ pricing: shortPricingHint(tool),
27000
+ description: firstSentence(tool.description)
27001
+ })).sort((a, b) => a.id.localeCompare(b.id));
27002
+ const idWidth = rows.reduce((max, row) => Math.max(max, row.id.length), 0);
27003
+ const render = {
27004
+ sections: [
27005
+ {
27006
+ title: `${rows.length} tools in ${category}:`,
27007
+ lines: rows.map((row) => {
27008
+ const id = row.id.padEnd(idWidth);
27009
+ const required = row.required.length ? `(${row.required.join(", ")})` : "(no required inputs)";
27010
+ const pricing = row.pricing ? ` [${row.pricing}]` : "";
27011
+ const description = row.description ? ` - ${row.description}` : "";
27012
+ return `${id} ${required}${pricing}${description}`;
27013
+ })
27014
+ },
27015
+ {
27016
+ title: "next",
27017
+ lines: [
27018
+ "Run `deepline tools describe <toolId>` for the full contract."
27019
+ ]
27020
+ }
27021
+ ]
27022
+ };
27023
+ printCommandEnvelope(
27024
+ {
27025
+ category,
27026
+ total: rows.length,
27027
+ tools: rows,
27028
+ commandTemplates: TOOL_COMMAND_TEMPLATES,
27029
+ render
27030
+ },
27031
+ { json: emitJson }
27032
+ );
27033
+ return 0;
27034
+ }
27035
+ async function listToolsForCategoryFilter(requestedCategories, emitJson, compact) {
26834
27036
  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
27037
  const items = (await client2.listTools({
26840
- ...categoryFilter ? { categories: categoryFilter } : {},
27038
+ categories: requestedCategories.join(","),
26841
27039
  compact
26842
27040
  })).map(toListedTool).filter(
26843
- (item) => requestedCategories.length === 0 || requestedCategories.some(
27041
+ (item) => requestedCategories.some(
26844
27042
  (category) => item.categories.includes(category)
26845
27043
  )
26846
27044
  );
26847
- const emptyResult = items.length === 0 && requestedCategories.length > 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
27045
+ const emptyResult = items.length === 0 ? zeroMatchToolGuidance({ categories: requestedCategories }) : null;
26848
27046
  const render = {
26849
27047
  sections: emptyResult ? zeroMatchRender(emptyResult).sections : [
26850
27048
  {
@@ -26875,10 +27073,41 @@ async function listTools(args) {
26875
27073
  commandTemplates: TOOL_COMMAND_TEMPLATES,
26876
27074
  render
26877
27075
  },
26878
- { json: argsWantJson(args) }
27076
+ { json: emitJson }
26879
27077
  );
26880
27078
  return 0;
26881
27079
  }
27080
+ async function listTools(category, options = {}) {
27081
+ const positional = category?.trim() ?? "";
27082
+ const flagCategories = (options.categories ?? "").split(",").map((item) => item.trim()).filter(Boolean);
27083
+ const compact = options.compact !== false;
27084
+ const emitJson = options.json === true || shouldEmitJson();
27085
+ if (positional && flagCategories.length > 0) {
27086
+ const flagIsSameSingle = flagCategories.length === 1 && flagCategories[0] === positional;
27087
+ if (!flagIsSameSingle) {
27088
+ const message = `Category "${positional}" and --categories ${flagCategories.join(",")} disagree. Pass one: deepline tools list ${positional}`;
27089
+ if (emitJson) {
27090
+ printJson({
27091
+ ok: false,
27092
+ exitCode: 2,
27093
+ code: "TOOL_LIST_CATEGORY_CONFLICT",
27094
+ message,
27095
+ next: `deepline tools list ${positional}`
27096
+ });
27097
+ } else {
27098
+ console.error(message);
27099
+ }
27100
+ return 2;
27101
+ }
27102
+ }
27103
+ if (positional) {
27104
+ return listToolsInCategory(positional, emitJson);
27105
+ }
27106
+ if (flagCategories.length > 0) {
27107
+ return listToolsForCategoryFilter(flagCategories, emitJson, compact);
27108
+ }
27109
+ return listToolCategories(emitJson, compact);
27110
+ }
26882
27111
  async function searchTools(queryInput, options = {}) {
26883
27112
  const query = queryInput?.trim() ?? "";
26884
27113
  const hasStructuredSearch = Boolean(
@@ -27077,27 +27306,37 @@ Output:
27077
27306
  Use execute to run a tool.
27078
27307
  `
27079
27308
  );
27080
- tools.command("list").description("List available tools.").addHelpText(
27309
+ tools.command("list [category]").description("Browse tool categories, or list every tool in a category.").addHelpText(
27081
27310
  "after",
27082
27311
  `
27083
27312
  Notes:
27084
- Inventory command for known tool ids. Use search for ranked discovery by
27085
- intent, aliases, descriptions, and schema fields.
27313
+ Bare \`tools list\` prints the category enumeration: one line per category with
27314
+ its tool count and definition. Pass a category to list every tool in it, one
27315
+ succinct row each (required inputs, Deepline pricing hint, first sentence).
27316
+ The listing is exhaustive and never truncated. Use search for ranked discovery
27317
+ by intent, aliases, descriptions, and schema fields.
27318
+
27319
+ --categories is a back-compat inventory filter. Passing it alone returns the
27320
+ legacy flat inventory envelope (one row per tool). The positional category is
27321
+ the newer exhaustive per-category view. If both are given they must name the
27322
+ same category (the positional view wins); otherwise it is a usage error.
27086
27323
 
27087
27324
  Examples:
27088
27325
  deepline tools list
27326
+ deepline tools list email_finder
27327
+ deepline tools list email_finder --json
27089
27328
  deepline tools list --categories email_finder --json
27090
27329
  deepline tools search email --json
27091
27330
  `
27092
27331
  ).option(
27093
27332
  "--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
- ]);
27333
+ "Comma-separated categories filter (back-compat alias for the positional)"
27334
+ ).option("--compact", "Drop verbose fields from JSON output").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (category, options) => {
27335
+ process.exitCode = await listTools(category, {
27336
+ categories: typeof options.categories === "string" ? options.categories : void 0,
27337
+ compact: options.compact !== false,
27338
+ json: Boolean(options.json)
27339
+ });
27101
27340
  });
27102
27341
  const addToolSearchCommand = (command) => command.description(
27103
27342
  "Search available tools by intent query or structured filters."
package/dist/index.js CHANGED
@@ -435,7 +435,7 @@ var SDK_RELEASE = {
435
435
  // Deepline-native radars. Older clients must update before discovering,
436
436
  // checking, or deploying an unlaunched monitor integration.
437
437
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
438
- version: "0.1.261",
438
+ version: "0.1.263",
439
439
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
440
440
  supportPolicy: {
441
441
  minimumSupported: "0.1.53",
package/dist/index.mjs CHANGED
@@ -365,7 +365,7 @@ var SDK_RELEASE = {
365
365
  // Deepline-native radars. Older clients must update before discovering,
366
366
  // checking, or deploying an unlaunched monitor integration.
367
367
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
368
- version: "0.1.261",
368
+ version: "0.1.263",
369
369
  apiContract: "2026-07-native-monitor-launch-hard-cutover",
370
370
  supportPolicy: {
371
371
  minimumSupported: "0.1.53",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.261",
3
+ "version": "0.1.263",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {