deepline 0.1.47 → 0.1.49

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
@@ -24,7 +24,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli/index.ts
27
- var import_commander2 = require("commander");
27
+ var import_promises5 = require("fs/promises");
28
+ var import_node_path15 = require("path");
29
+ var import_node_os9 = require("os");
30
+ var import_commander3 = require("commander");
28
31
 
29
32
  // src/config.ts
30
33
  var import_node_fs = require("fs");
@@ -216,7 +219,7 @@ function resolveConfig(options) {
216
219
  }
217
220
 
218
221
  // src/version.ts
219
- var SDK_VERSION = "0.1.47";
222
+ var SDK_VERSION = "0.1.49";
220
223
  var SDK_API_CONTRACT = "2026-05-stripe-promo-checkout";
221
224
 
222
225
  // ../shared_libs/play-runtime/coordinator-headers.ts
@@ -736,9 +739,18 @@ var DeeplineClient = class {
736
739
  * console.log(`Found ${searchTools.length} search tools`);
737
740
  * ```
738
741
  */
739
- async listTools() {
742
+ async listTools(options) {
743
+ const params = new URLSearchParams();
744
+ if (options?.categories?.trim()) {
745
+ params.set("categories", options.categories.trim());
746
+ }
747
+ if (options?.grep?.trim()) {
748
+ params.set("grep", options.grep.trim());
749
+ params.set("grep_mode", options.grepMode ?? "all");
750
+ }
751
+ const suffix = params.toString() ? `?${params.toString()}` : "";
740
752
  const res = await this.http.get(
741
- "/api/v2/tools"
753
+ `/api/v2/tools${suffix}`
742
754
  );
743
755
  return res.tools;
744
756
  }
@@ -1373,9 +1385,17 @@ var DeeplineClient = class {
1373
1385
  options?.reason ? { reason: options.reason } : {}
1374
1386
  );
1375
1387
  }
1376
- async listPlays() {
1388
+ async listPlays(options) {
1389
+ const params = new URLSearchParams();
1390
+ if (options?.origin) params.set("origin", options.origin);
1391
+ if (options?.grep?.trim()) {
1392
+ params.set("grep", options.grep.trim());
1393
+ params.set("grep_mode", options.grepMode ?? "all");
1394
+ params.set("limit", "60");
1395
+ }
1396
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1377
1397
  const response = await this.http.get(
1378
- "/api/v2/plays"
1398
+ `/api/v2/plays${suffix}`
1379
1399
  );
1380
1400
  return response.plays ?? [];
1381
1401
  }
@@ -2795,8 +2815,10 @@ async function handleCheckout(options) {
2795
2815
  }
2796
2816
  async function handleRedeemCode(code, options) {
2797
2817
  const { http } = getAuthedHttpClient();
2798
- const payload = await http.post("/api/v2/billing/checkout/verify", { code });
2799
- const url = String(payload.url || "");
2818
+ const payload = await http.post("/api/v2/billing/checkout", {
2819
+ discountCode: code
2820
+ });
2821
+ const url = String(payload.url || payload.checkout_url || "");
2800
2822
  if (!options.json && !options.noOpen && url) openInBrowser(url);
2801
2823
  printCommandEnvelope({
2802
2824
  ...payload,
@@ -5681,6 +5703,16 @@ function extractPlayName(code, filePath) {
5681
5703
  function isFileTarget(target) {
5682
5704
  return (0, import_node_fs8.existsSync)((0, import_node_path10.resolve)(target));
5683
5705
  }
5706
+ function looksLikeRunId(target) {
5707
+ return /^play\/[^/]+\/run\/[^/]+/.test(target.trim());
5708
+ }
5709
+ function formatPlayCommandReceivedRunIdError(input) {
5710
+ return `\`deepline plays ${input.command} <run-id>\` expects a play name, but this looks like a run id.
5711
+ Use:
5712
+ deepline runs get ${input.runId} --json
5713
+ deepline runs logs ${input.runId} --json
5714
+ deepline runs export ${input.runId} --out output.csv`;
5715
+ }
5684
5716
  function looksLikeFilePath(target) {
5685
5717
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
5686
5718
  return false;
@@ -6107,6 +6139,69 @@ function describeLiveEventPhase(event) {
6107
6139
  }
6108
6140
  return null;
6109
6141
  }
6142
+ function formatProgressLabel(raw) {
6143
+ const value = typeof raw === "string" && raw.trim() ? raw.trim() : "step";
6144
+ return value.replace(/^map:/, "").replace(/^tool:/, "");
6145
+ }
6146
+ function formatProgressCounts(input) {
6147
+ const completed = typeof input.completed === "number" && Number.isFinite(input.completed) ? input.completed : null;
6148
+ const total = typeof input.total === "number" && Number.isFinite(input.total) ? input.total : null;
6149
+ if (completed === null || total === null || total <= 0) {
6150
+ return null;
6151
+ }
6152
+ const percent = Math.max(0, Math.min(100, Math.round(completed / total * 100)));
6153
+ const failed = typeof input.failed === "number" && Number.isFinite(input.failed) && input.failed > 0 ? `, failed ${formatInteger(input.failed)}` : "";
6154
+ return `${formatInteger(completed)}/${formatInteger(total)} (${percent}%)${failed}`;
6155
+ }
6156
+ function getProgressLinesFromLiveEvent(event) {
6157
+ const payload = getEventPayload(event);
6158
+ if (event.type === "play.step.progress") {
6159
+ const counts = formatProgressCounts({
6160
+ completed: payload.completed,
6161
+ total: payload.total,
6162
+ failed: payload.failed
6163
+ });
6164
+ if (!counts) return [];
6165
+ return [`progress ${formatProgressLabel(payload.stepId)}: ${counts}`];
6166
+ }
6167
+ if (event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
6168
+ return [];
6169
+ }
6170
+ const nodeStates = Array.isArray(payload.nodeStates) ? payload.nodeStates : Array.isArray(payload.steps) ? payload.steps : [];
6171
+ const lines = [];
6172
+ for (const state of nodeStates) {
6173
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
6174
+ continue;
6175
+ }
6176
+ const record = state;
6177
+ const progress = record.progress && typeof record.progress === "object" && !Array.isArray(record.progress) ? record.progress : null;
6178
+ if (!progress) {
6179
+ continue;
6180
+ }
6181
+ const counts = formatProgressCounts({
6182
+ completed: progress.completed,
6183
+ total: progress.total,
6184
+ failed: progress.failed
6185
+ });
6186
+ if (!counts) {
6187
+ continue;
6188
+ }
6189
+ lines.push(
6190
+ `progress ${formatProgressLabel(record.nodeId ?? progress.artifactTableNamespace)}: ${counts}`
6191
+ );
6192
+ }
6193
+ return lines;
6194
+ }
6195
+ function printPlayProgressLines(input) {
6196
+ for (const line of input.lines) {
6197
+ const signature = line.trim();
6198
+ if (!signature || input.state.lastProgressSignature === signature) {
6199
+ continue;
6200
+ }
6201
+ input.state.lastProgressSignature = signature;
6202
+ input.progress.writeLine(line);
6203
+ }
6204
+ }
6110
6205
  function buildPlayDashboardUrl(baseUrl, playName) {
6111
6206
  const trimmedBase = baseUrl.replace(/\/$/, "");
6112
6207
  const encodedPlayName = encodeURIComponent(playName);
@@ -6180,6 +6275,13 @@ async function waitForPlayCompletionByStream(input) {
6180
6275
  state: input.state,
6181
6276
  progress: input.progress
6182
6277
  });
6278
+ if (!input.jsonOutput) {
6279
+ printPlayProgressLines({
6280
+ lines: getProgressLinesFromLiveEvent(event),
6281
+ state: input.state,
6282
+ progress: input.progress
6283
+ });
6284
+ }
6183
6285
  const status = getStatusFromLiveEvent(event);
6184
6286
  if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
6185
6287
  const finalStatus = await input.client.getPlayStatus(input.workflowId, {
@@ -6248,7 +6350,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6248
6350
  );
6249
6351
  const state = {
6250
6352
  lastLogIndex: 0,
6251
- emittedRunnerStarted: false
6353
+ emittedRunnerStarted: false,
6354
+ lastProgressSignature: null
6252
6355
  };
6253
6356
  const controller = new AbortController();
6254
6357
  let timedOut = false;
@@ -6276,11 +6379,6 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6276
6379
  }
6277
6380
  const workflowId = lastKnownWorkflowId || "pending";
6278
6381
  if (workflowId !== "pending" && !emittedDashboardUrl) {
6279
- openPlayDashboard({
6280
- dashboardUrl,
6281
- jsonOutput: input.jsonOutput,
6282
- noOpen: input.noOpen
6283
- });
6284
6382
  if (!input.jsonOutput) {
6285
6383
  writeStartedPlayRun({
6286
6384
  runId: workflowId,
@@ -6290,6 +6388,11 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6290
6388
  progress: input.progress
6291
6389
  });
6292
6390
  }
6391
+ openPlayDashboard({
6392
+ dashboardUrl,
6393
+ jsonOutput: input.jsonOutput,
6394
+ noOpen: input.noOpen
6395
+ });
6293
6396
  input.progress.phase(`loading play on ${dashboardUrl}`);
6294
6397
  emittedDashboardUrl = true;
6295
6398
  }
@@ -6312,6 +6415,13 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6312
6415
  state,
6313
6416
  progress: input.progress
6314
6417
  });
6418
+ if (!input.jsonOutput) {
6419
+ printPlayProgressLines({
6420
+ lines: getProgressLinesFromLiveEvent(event),
6421
+ state,
6422
+ progress: input.progress
6423
+ });
6424
+ }
6315
6425
  const finalStatus = getFinalStatusFromLiveEvent(event);
6316
6426
  if (finalStatus) {
6317
6427
  recordCliTrace({
@@ -6580,6 +6690,7 @@ function buildRunNextCommands(status) {
6580
6690
  const commands = {
6581
6691
  get: `deepline runs get ${runId} --json`,
6582
6692
  full: `deepline runs get ${runId} --full --json`,
6693
+ export: `deepline runs export ${runId} --out output.csv`,
6583
6694
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6584
6695
  logs: `deepline runs logs ${runId} --out run.log --json`
6585
6696
  };
@@ -6712,6 +6823,32 @@ function formatInsufficientCreditsMessage(input) {
6712
6823
  const addSuffix = billingUrl && recommended !== "-" ? ` Add >=${recommended} at ${billingUrl}.` : billingUrl ? ` Add credits at ${billingUrl}.` : "";
6713
6824
  return `Workspace balance ${balance} < required ${required} for ${operation}${workspaceSuffix}.${addSuffix}`;
6714
6825
  }
6826
+ function buildInsufficientCreditsSummaryLines(input) {
6827
+ const progress = input.status.progress;
6828
+ const rowsInfo = extractCanonicalRowsInfo(input.status);
6829
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? rowsInfo?.rows.length ?? null;
6830
+ const total = getNumericField(progress, "total") ?? getNumericField(progress, "totalRows") ?? rowsInfo?.totalRows ?? null;
6831
+ const lines = [
6832
+ " status: stopped_insufficient_credits",
6833
+ completed === null ? " completed rows: unknown" : ` completed rows: ${formatInteger(completed)}${total !== null ? ` of ${formatInteger(total)}` : ""}`,
6834
+ " reusable receipts: yes; rerun after adding credits to continue from completed provider work"
6835
+ ];
6836
+ const billingUrl = getStringField(input.billing, "billing_url");
6837
+ const recommended = formatCreditAmount(
6838
+ input.billing.recommended_add_credits ?? input.billing.needed_credits
6839
+ );
6840
+ if (billingUrl) {
6841
+ lines.push(
6842
+ recommended !== "-" ? ` add credits: add >=${recommended} at ${billingUrl}` : ` add credits: ${billingUrl}`
6843
+ );
6844
+ }
6845
+ const runId = input.status.runId?.trim();
6846
+ if (runId) {
6847
+ lines.push(` inspect: deepline runs get ${runId} --json`);
6848
+ lines.push(` export partial: deepline runs export ${runId} --out output.csv`);
6849
+ }
6850
+ return lines;
6851
+ }
6715
6852
  function formatPlayErrorForDisplay(status, error) {
6716
6853
  if (!error) {
6717
6854
  return null;
@@ -6902,6 +7039,19 @@ function formatDatasetStatsLines(datasetStats) {
6902
7039
  }
6903
7040
  return lines;
6904
7041
  }
7042
+ function buildJsonRunResultRenderLines(status) {
7043
+ const publicStatus = status.status ?? "running";
7044
+ const runId = status.runId ?? "unknown";
7045
+ const lines = [`${publicStatus} ${runId}`];
7046
+ const progressError = status.progress?.error;
7047
+ if (typeof progressError === "string") {
7048
+ const billing = extractBillingForStatus(status, progressError);
7049
+ if (isInsufficientCreditsBilling(billing)) {
7050
+ lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
7051
+ }
7052
+ }
7053
+ return lines;
7054
+ }
6905
7055
  function writePlayResult(status, jsonOutput, options) {
6906
7056
  if (jsonOutput) {
6907
7057
  const payload2 = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
@@ -6911,9 +7061,7 @@ function writePlayResult(status, jsonOutput, options) {
6911
7061
  sections: [
6912
7062
  {
6913
7063
  title: "run result",
6914
- lines: [
6915
- `${status.status ?? "running"} ${status.runId ?? "unknown"}`
6916
- ]
7064
+ lines: buildJsonRunResultRenderLines(status)
6917
7065
  }
6918
7066
  ]
6919
7067
  }
@@ -6940,6 +7088,10 @@ function writePlayResult(status, jsonOutput, options) {
6940
7088
  lines.push(...formatDatasetStatsLines(datasetStats));
6941
7089
  const progressError = status.progress?.error;
6942
7090
  if (progressError && typeof progressError === "string") {
7091
+ const billing = extractBillingForStatus(status, progressError);
7092
+ if (isInsufficientCreditsBilling(billing)) {
7093
+ lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
7094
+ }
6943
7095
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6944
7096
  lines.push(` error: ${displayError.slice(0, 200)}`);
6945
7097
  }
@@ -7319,24 +7471,31 @@ function writeStartedPlayRun(input) {
7319
7471
  workflowId: input.runId,
7320
7472
  name: input.playName,
7321
7473
  status: input.status ?? "started",
7322
- dashboardUrl: input.dashboardUrl
7474
+ dashboardUrl: input.dashboardUrl,
7475
+ next: {
7476
+ inspect: `deepline runs get ${input.runId} --json`,
7477
+ full: `deepline runs get ${input.runId} --full --json`,
7478
+ logs: `deepline runs logs ${input.runId} --json`,
7479
+ export: `deepline runs export ${input.runId} --out output.csv`,
7480
+ stop: `deepline runs stop ${input.runId} --reason "stale lock" --json`
7481
+ }
7323
7482
  };
7483
+ const lines = [
7484
+ `Started ${input.playName}`,
7485
+ ` run id: ${input.runId}`,
7486
+ ` inspect: deepline runs get ${input.runId} --json`,
7487
+ ` full debug: deepline runs get ${input.runId} --full --json`,
7488
+ ` logs: deepline runs logs ${input.runId} --json`,
7489
+ ` export after completion: deepline runs export ${input.runId} --out output.csv`,
7490
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`
7491
+ ];
7324
7492
  if (input.jsonOutput) {
7325
7493
  printCommandEnvelope({
7326
7494
  ...payload,
7327
- render: { sections: [{ title: "play run", lines: [`Started ${input.playName}`, `run id: ${input.runId}`] }] }
7495
+ render: { sections: [{ title: "play run", lines }] }
7328
7496
  }, { json: true });
7329
7497
  return;
7330
7498
  }
7331
- const lines = [
7332
- `Started ${input.playName}`,
7333
- ` run id: ${input.runId}`,
7334
- ` get status: deepline runs get ${input.runId} --json`,
7335
- ` logs: deepline runs logs ${input.runId} --json`,
7336
- ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
7337
- ` result JSON: deepline runs get ${input.runId} --json`,
7338
- ` full debug JSON: deepline runs get ${input.runId} --full --json`
7339
- ];
7340
7499
  if (input.dashboardUrl) {
7341
7500
  lines.push(` play page: ${input.dashboardUrl}`);
7342
7501
  }
@@ -7352,13 +7511,13 @@ function writeStartedPlayRun(input) {
7352
7511
  ` });
7353
7512
  }
7354
7513
  function parsePlayRunOptions(args) {
7355
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--watch] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--watch] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
7514
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--<input> value]\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.map guidance.";
7356
7515
  let filePath = null;
7357
7516
  let playName = null;
7358
7517
  let input = null;
7359
7518
  let revisionId = null;
7360
7519
  let revisionSelector = null;
7361
- const watch = args.includes("--watch") || args.includes("--wait");
7520
+ const watch = !args.includes("--no-wait");
7362
7521
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
7363
7522
  const emitLogs = !jsonOutput || args.includes("--logs");
7364
7523
  const force = args.includes("--force");
@@ -7404,7 +7563,7 @@ function parsePlayRunOptions(args) {
7404
7563
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
7405
7564
  continue;
7406
7565
  }
7407
- if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
7566
+ if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--no-wait" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
7408
7567
  if (arg === "--watch") {
7409
7568
  continue;
7410
7569
  }
@@ -8260,6 +8419,12 @@ async function handlePlayGet(args) {
8260
8419
  console.error("Usage: deepline play get <play-file.ts|play-name> [--json]");
8261
8420
  return 1;
8262
8421
  }
8422
+ if (looksLikeRunId(target)) {
8423
+ console.error(
8424
+ formatPlayCommandReceivedRunIdError({ command: "get", runId: target })
8425
+ );
8426
+ return 2;
8427
+ }
8263
8428
  const client = new DeeplineClient();
8264
8429
  const explicitJson = args.includes("--json");
8265
8430
  const sourceOutput = args.includes("--source");
@@ -8374,8 +8539,20 @@ async function handlePlayVersions(args) {
8374
8539
  }
8375
8540
  async function handlePlayList(args) {
8376
8541
  const jsonOutput = argsWantJson(args);
8542
+ const originArgIndex = args.findIndex((arg) => arg === "--origin");
8543
+ const rawOrigin = originArgIndex >= 0 ? args[originArgIndex + 1] : void 0;
8544
+ if (rawOrigin && rawOrigin !== "prebuilt" && rawOrigin !== "owned") {
8545
+ throw new Error(`Invalid value for --origin: ${rawOrigin}`);
8546
+ }
8547
+ const origin = rawOrigin;
8377
8548
  const client = new DeeplineClient();
8378
- const plays = await client.listPlays();
8549
+ const plays = (await client.listPlays({
8550
+ ...origin ? { origin } : {}
8551
+ })).filter((play) => {
8552
+ if (!origin) return true;
8553
+ const isPrebuilt = play.origin === "prebuilt" || play.ownerType === "deepline";
8554
+ return origin === "prebuilt" ? isPrebuilt : !isPrebuilt;
8555
+ });
8379
8556
  if (jsonOutput) {
8380
8557
  process.stdout.write(`${JSON.stringify(plays)}
8381
8558
  `);
@@ -8478,6 +8655,54 @@ function printPlayDescription(play) {
8478
8655
  );
8479
8656
  }
8480
8657
  }
8658
+ function compactPlaySchema(schema) {
8659
+ if (!schema) return null;
8660
+ const fields = Array.isArray(schema.fields) ? schema.fields.map(
8661
+ (field) => field && typeof field === "object" ? {
8662
+ name: String(field.name ?? ""),
8663
+ type: field.type ?? void 0,
8664
+ required: field.required ?? void 0
8665
+ } : null
8666
+ ).filter((field) => Boolean(field?.name)) : [];
8667
+ return fields.length > 0 ? { fields } : schema;
8668
+ }
8669
+ function playSchemaMetadata(schema, key) {
8670
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return null;
8671
+ const value = schema[key];
8672
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
8673
+ }
8674
+ function playRunCommand(play, options) {
8675
+ const target = play.reference || play.name;
8676
+ if (options?.csvInput) {
8677
+ const inputField = typeof options.csvInput.inputField === "string" && options.csvInput.inputField.trim() ? options.csvInput.inputField.trim() : "csv";
8678
+ return `deepline plays run ${target} --input '${JSON.stringify({ [inputField]: "leads.csv" })}' --watch`;
8679
+ }
8680
+ return `deepline plays run ${target} --input '{...}' --watch`;
8681
+ }
8682
+ function summarizePlayListItemForCli(play, options) {
8683
+ const aliases = play.aliases?.length ? play.aliases : [play.name];
8684
+ const csvInput = playSchemaMetadata(play.inputSchema, "csvInput");
8685
+ const rowOutputSchema = playSchemaMetadata(play.outputSchema, "rowOutputSchema");
8686
+ const runCommand2 = playRunCommand(play, { csvInput });
8687
+ return {
8688
+ name: play.name,
8689
+ ...play.reference ? { reference: play.reference } : {},
8690
+ ...play.displayName ? { displayName: play.displayName } : {},
8691
+ origin: play.origin,
8692
+ ownerType: play.ownerType,
8693
+ canEdit: play.canEdit,
8694
+ canClone: play.canClone,
8695
+ aliases,
8696
+ inputSchema: options?.compact ? compactPlaySchema(play.inputSchema) : play.inputSchema ?? null,
8697
+ outputSchema: options?.compact ? compactPlaySchema(play.outputSchema) : play.outputSchema ?? null,
8698
+ ...csvInput ? { csvInput } : {},
8699
+ ...rowOutputSchema ? { rowOutputSchema } : {},
8700
+ runCommand: runCommand2,
8701
+ examples: [runCommand2],
8702
+ currentPublishedVersion: play.currentPublishedVersion ?? null,
8703
+ isDraftDirty: play.isDraftDirty
8704
+ };
8705
+ }
8481
8706
  async function handlePlaySearch(args) {
8482
8707
  let options;
8483
8708
  try {
@@ -8499,6 +8724,100 @@ async function handlePlaySearch(args) {
8499
8724
  }
8500
8725
  process.stdout.write(`${plays.length} plays found:
8501
8726
 
8727
+ `);
8728
+ for (const play of plays) {
8729
+ printPlayDescription(play);
8730
+ console.log("");
8731
+ }
8732
+ return 0;
8733
+ }
8734
+ function normalizePlayGrepText(value) {
8735
+ if (value === null || value === void 0) return "";
8736
+ if (typeof value === "string") return value.toLowerCase();
8737
+ if (typeof value === "number" || typeof value === "boolean") {
8738
+ return String(value).toLowerCase();
8739
+ }
8740
+ if (Array.isArray(value)) return value.map(normalizePlayGrepText).join(" ");
8741
+ if (typeof value === "object") {
8742
+ return Object.values(value).map(normalizePlayGrepText).join(" ");
8743
+ }
8744
+ return "";
8745
+ }
8746
+ function parsePlayGrepTerms(query, mode) {
8747
+ const trimmed = query.trim().toLowerCase();
8748
+ if (!trimmed) return [];
8749
+ if (mode === "phrase") return [trimmed];
8750
+ const matches = trimmed.match(/"([^"]+)"|'([^']+)'|\S+/g) ?? [];
8751
+ return matches.map((term) => term.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
8752
+ }
8753
+ function matchesPlayGrepQuery(value, query, mode) {
8754
+ const terms = parsePlayGrepTerms(query, mode);
8755
+ if (terms.length === 0) return true;
8756
+ const haystack = normalizePlayGrepText(value);
8757
+ if (mode === "any") return terms.some((term) => haystack.includes(term));
8758
+ return terms.every((term) => haystack.includes(term));
8759
+ }
8760
+ async function handlePlayGrep(args) {
8761
+ const query = args[0]?.trim();
8762
+ if (!query) {
8763
+ console.error("Usage: deepline plays grep <query> [--origin prebuilt|owned] [--compact] [--json]");
8764
+ return 1;
8765
+ }
8766
+ let origin;
8767
+ let mode = "all";
8768
+ for (let index = 1; index < args.length; index += 1) {
8769
+ const arg = args[index];
8770
+ if (arg === "--origin" && args[index + 1]) {
8771
+ const rawOrigin = args[++index].trim().toLowerCase();
8772
+ if (rawOrigin !== "prebuilt" && rawOrigin !== "owned") {
8773
+ throw new Error(`Invalid value for --origin: ${rawOrigin}`);
8774
+ }
8775
+ origin = rawOrigin;
8776
+ }
8777
+ if (arg === "--mode" && args[index + 1]) {
8778
+ const rawMode = args[++index].trim().toLowerCase();
8779
+ mode = rawMode === "any" || rawMode === "phrase" ? rawMode : "all";
8780
+ }
8781
+ }
8782
+ const compact = args.includes("--compact");
8783
+ const client = new DeeplineClient();
8784
+ const plays = (await client.listPlays({
8785
+ grep: query,
8786
+ grepMode: mode,
8787
+ ...origin ? { origin } : {}
8788
+ })).filter((play) => {
8789
+ if (!origin) return true;
8790
+ const isPrebuilt = play.origin === "prebuilt" || play.ownerType === "deepline";
8791
+ return origin === "prebuilt" ? isPrebuilt : !isPrebuilt;
8792
+ }).filter(
8793
+ (play) => matchesPlayGrepQuery(
8794
+ {
8795
+ name: play.name,
8796
+ reference: play.reference,
8797
+ displayName: play.displayName,
8798
+ origin: play.origin,
8799
+ ownerType: play.ownerType,
8800
+ aliases: play.aliases,
8801
+ inputSchema: play.inputSchema,
8802
+ outputSchema: play.outputSchema
8803
+ },
8804
+ query,
8805
+ mode
8806
+ )
8807
+ ).map((play) => summarizePlayListItemForCli(play, { compact }));
8808
+ if (argsWantJson(args)) {
8809
+ process.stdout.write(`${JSON.stringify({
8810
+ plays,
8811
+ count: plays.length,
8812
+ query,
8813
+ grep: { mode, terms: parsePlayGrepTerms(query, mode) },
8814
+ filters: { origin: origin ?? null }
8815
+ })}
8816
+ `);
8817
+ return 0;
8818
+ }
8819
+ process.stdout.write(`${plays.length} plays found:
8820
+
8502
8821
  `);
8503
8822
  for (const play of plays) {
8504
8823
  printPlayDescription(play);
@@ -8514,6 +8833,15 @@ async function handlePlayDescribe(args) {
8514
8833
  );
8515
8834
  return 1;
8516
8835
  }
8836
+ if (looksLikeRunId(playName)) {
8837
+ console.error(
8838
+ formatPlayCommandReceivedRunIdError({
8839
+ command: "describe",
8840
+ runId: playName
8841
+ })
8842
+ );
8843
+ return 2;
8844
+ }
8517
8845
  const client = new DeeplineClient();
8518
8846
  await assertCanonicalNamedPlayReference(client, playName);
8519
8847
  const play = await client.describePlay(
@@ -8670,7 +8998,7 @@ Common commands:
8670
8998
  deepline plays search email --json
8671
8999
  deepline plays describe person-linkedin-to-email --json
8672
9000
  deepline plays check my.play.ts
8673
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
9001
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
8674
9002
  deepline plays set-live my.play.ts --json
8675
9003
  deepline plays get person-linkedin-to-email --json
8676
9004
  `
@@ -8701,8 +9029,10 @@ Notes:
8701
9029
  Unknown --foo value and --foo.bar value flags are passed into play input.
8702
9030
  Example: --limit 5 becomes input.limit = 5.
8703
9031
  File args accept local paths; the CLI stages files before submit.
8704
- --watch prints logs, previews, stats, and next commands.
8705
- --wait is accepted as a compatibility alias for --watch.
9032
+ By default, run waits for completion and prints logs, previews, stats, and
9033
+ next commands. --watch and --wait are accepted compatibility aliases for the
9034
+ default behavior. Use --no-wait only when you intentionally want
9035
+ a fire-and-forget run id.
8706
9036
  The play page opens in your browser as soon as the run starts; use --no-open
8707
9037
  to only print the URL.
8708
9038
  --force supersedes active runs; it does not bypass completed reuse.
@@ -8730,17 +9060,18 @@ Idempotent execution:
8730
9060
  .run({ key: 'domain', staleAfterSeconds: 86400 })
8731
9061
 
8732
9062
  Examples:
8733
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
8734
- deepline plays run my.play.ts --input @input.json --wait --json
8735
- deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
8736
- deepline plays run cto-search.play.ts --limit 5 --watch
9063
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
9064
+ deepline plays run my.play.ts --input @input.json --json
9065
+ deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}'
9066
+ deepline plays run cto-search.play.ts --limit 5
9067
+ deepline plays run long-background-play --no-wait
8737
9068
  deepline runs export <run-id> --out output.csv
8738
9069
  deepline runs get <run-id>
8739
9070
  `
8740
9071
  ).option("--file <path>", "Local play file to run").option("--name <name>", "Saved play name to run").option("-i, --input <json>", "Input JSON object or @file path").option("--live", "Run the current live revision explicitly").option("--latest", "Run the newest saved revision, even if it is not live").option(
8741
9072
  "--revision-id <id>",
8742
9073
  "Run a specific saved revision instead of the live revision"
8743
- ).option("--watch", "Stream logs until completion").option("--wait", "Alias for --watch; stream logs until completion").option(
9074
+ ).option("--watch", "Compatibility alias; run waits by default").option("--wait", "Compatibility alias; run waits by default").option("--no-wait", "Start the run and return immediately").option(
8744
9075
  "--logs",
8745
9076
  "When output is non-interactive, stream play logs to stderr while waiting"
8746
9077
  ).option("--tail-timeout-ms <ms>", "Timeout while watching the run stream").option("--force", "Supersede any active runs for this play").option("--no-open", "Print the play page URL without opening a browser").option("--json", "Emit JSON output").addHelpText(
@@ -8773,6 +9104,7 @@ Pass-through input flags:
8773
9104
  ...options.live ? ["--live"] : [],
8774
9105
  ...options.latest ? ["--latest"] : [],
8775
9106
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
9107
+ ...options.wait === false ? ["--no-wait"] : [],
8776
9108
  ...options.watch || options.wait ? ["--watch"] : [],
8777
9109
  ...options.logs ? ["--logs"] : [],
8778
9110
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
@@ -8813,24 +9145,27 @@ Notes:
8813
9145
 
8814
9146
  Examples:
8815
9147
  deepline plays list
8816
- deepline plays list --json
9148
+ deepline plays list --origin prebuilt --json
8817
9149
  deepline plays search email --origin prebuilt --json
8818
9150
  `
8819
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9151
+ ).option("--origin <origin>", "Filter to prebuilt or owned plays").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
8820
9152
  process.exitCode = await handlePlayList([
9153
+ ...options.origin ? ["--origin", options.origin] : [],
8821
9154
  ...options.json ? ["--json"] : []
8822
9155
  ]);
8823
9156
  });
8824
- play.command("search <query>").description("Search saved and prebuilt plays.").addHelpText(
9157
+ const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").addHelpText(
8825
9158
  "after",
8826
9159
  `
8827
9160
  Notes:
8828
9161
  Ranked discovery for workflows. Use --origin prebuilt or --origin owned when
8829
9162
  you need to narrow results. Use describe on a result before running it.
9163
+ The grep alias is the same ranked retrieval surface with a more literal name
9164
+ for agents that are filtering the play registry.
8830
9165
 
8831
9166
  Examples:
8832
9167
  deepline plays search email
8833
- deepline plays search "linkedin to email" --origin prebuilt --compact --json
9168
+ deepline plays grep "linkedin to email" --origin prebuilt --compact --json
8834
9169
  deepline plays describe person-linkedin-to-email --json
8835
9170
  `
8836
9171
  ).option("--origin <origin>", "Filter to prebuilt or owned plays").option("--compact", "Emit compact schemas").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
@@ -8841,6 +9176,30 @@ Examples:
8841
9176
  ...options.json ? ["--json"] : []
8842
9177
  ]);
8843
9178
  });
9179
+ addPlaySearchCommand(play.command("search <query>"));
9180
+ play.command("grep <query>").description("Literal grep over play names, aliases, schemas, and descriptions.").addHelpText(
9181
+ "after",
9182
+ `
9183
+ Notes:
9184
+ Literal registry filtering. Terms are matched case-insensitively against play
9185
+ names, references, display names, aliases, ownership, and schemas. Use
9186
+ --mode phrase for exact phrase matching, --mode any for OR, and the default
9187
+ --mode all for AND.
9188
+
9189
+ Examples:
9190
+ deepline plays grep email --origin prebuilt --json
9191
+ deepline plays grep "company contact" --origin prebuilt --mode all --json
9192
+ deepline plays describe prebuilt/company-to-contact --json
9193
+ `
9194
+ ).option("--origin <origin>", "Filter to prebuilt or owned plays").option("--compact", "Emit compact schemas").option("--mode <mode>", "Grep matching mode: all, any, or phrase").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
9195
+ process.exitCode = await handlePlayGrep([
9196
+ query,
9197
+ ...options.origin ? ["--origin", options.origin] : [],
9198
+ ...options.compact ? ["--compact"] : [],
9199
+ ...options.mode ? ["--mode", options.mode] : [],
9200
+ ...options.json ? ["--json"] : []
9201
+ ]);
9202
+ });
8844
9203
  play.command("describe <target>").description("Describe a play contract and how to run it.").addHelpText(
8845
9204
  "after",
8846
9205
  `
@@ -8869,7 +9228,7 @@ Notes:
8869
9228
  Examples:
8870
9229
  deepline plays versions --name my-play
8871
9230
  deepline plays versions --name my-play --json
8872
- deepline plays run my-play --revision-id <revision-id> --watch
9231
+ deepline plays run my-play --revision-id <revision-id>
8873
9232
  `
8874
9233
  ).option("--name <name>", "Saved play name").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
8875
9234
  process.exitCode = await handlePlayVersions([
@@ -9071,6 +9430,7 @@ Examples:
9071
9430
  }
9072
9431
 
9073
9432
  // src/cli/commands/tools.ts
9433
+ var import_commander2 = require("commander");
9074
9434
  var import_node_fs10 = require("fs");
9075
9435
  var import_node_os7 = require("os");
9076
9436
  var import_node_path12 = require("path");
@@ -9255,9 +9615,42 @@ function toListedTool(tool) {
9255
9615
  executeCommand: `deepline tools execute ${tool.toolId}`
9256
9616
  };
9257
9617
  }
9618
+ function normalizeGrepText(value) {
9619
+ if (value === null || value === void 0) return "";
9620
+ if (typeof value === "string") return value.toLowerCase();
9621
+ if (typeof value === "number" || typeof value === "boolean") {
9622
+ return String(value).toLowerCase();
9623
+ }
9624
+ if (Array.isArray(value)) return value.map(normalizeGrepText).join(" ");
9625
+ if (typeof value === "object") {
9626
+ return Object.values(value).map(normalizeGrepText).join(" ");
9627
+ }
9628
+ return "";
9629
+ }
9630
+ function parseGrepTerms(query, mode) {
9631
+ const trimmed = query.trim().toLowerCase();
9632
+ if (!trimmed) return [];
9633
+ if (mode === "phrase") return [trimmed];
9634
+ const matches = trimmed.match(/"([^"]+)"|'([^']+)'|\S+/g) ?? [];
9635
+ return matches.map((term) => term.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
9636
+ }
9637
+ function matchesGrepQuery(value, query, mode) {
9638
+ const terms = parseGrepTerms(query, mode);
9639
+ if (terms.length === 0) return true;
9640
+ const haystack = normalizeGrepText(value);
9641
+ if (mode === "any") return terms.some((term) => haystack.includes(term));
9642
+ return terms.every((term) => haystack.includes(term));
9643
+ }
9258
9644
  async function listTools(args) {
9259
9645
  const client = new DeeplineClient();
9260
- const items = (await client.listTools()).map(toListedTool);
9646
+ const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
9647
+ const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
9648
+ const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
9649
+ const items = (await client.listTools({
9650
+ ...categoryFilter ? { categories: categoryFilter } : {}
9651
+ })).map(toListedTool).filter(
9652
+ (item) => requestedCategories.length === 0 || requestedCategories.some((category) => item.categories.includes(category))
9653
+ );
9261
9654
  const render = {
9262
9655
  sections: [
9263
9656
  {
@@ -9280,6 +9673,9 @@ async function listTools(args) {
9280
9673
  {
9281
9674
  tools: items,
9282
9675
  count: items.length,
9676
+ filters: {
9677
+ categories: requestedCategories
9678
+ },
9283
9679
  commandTemplates: TOOL_COMMAND_TEMPLATES,
9284
9680
  render
9285
9681
  },
@@ -9306,6 +9702,56 @@ async function searchTools(queryInput, options = {}) {
9306
9702
  });
9307
9703
  return 0;
9308
9704
  }
9705
+ async function grepTools(queryInput, options = {}) {
9706
+ const query = queryInput.trim();
9707
+ if (!query) {
9708
+ console.error("Usage: deepline tools grep <query> [--json]");
9709
+ return 1;
9710
+ }
9711
+ const client = new DeeplineClient();
9712
+ const requestedCategories = options.categories ? options.categories.split(",").map((item) => item.trim()).filter(Boolean) : [];
9713
+ const mode = options.mode ?? "all";
9714
+ const tools = (await client.listTools({
9715
+ grep: query,
9716
+ grepMode: mode,
9717
+ ...options.categories ? { categories: options.categories } : {}
9718
+ })).map(toListedTool).filter(
9719
+ (item) => requestedCategories.length === 0 || requestedCategories.some((category) => item.categories.includes(category))
9720
+ ).filter(
9721
+ (item) => matchesGrepQuery(
9722
+ {
9723
+ id: item.toolId,
9724
+ toolId: item.toolId,
9725
+ provider: item.provider,
9726
+ displayName: item.displayName,
9727
+ description: item.description,
9728
+ categories: item.categories,
9729
+ operation: item.operation,
9730
+ operationAliases: item.operationAliases,
9731
+ inputFields: item.inputFields
9732
+ },
9733
+ query,
9734
+ mode
9735
+ )
9736
+ );
9737
+ printCommandEnvelope(
9738
+ {
9739
+ tools,
9740
+ count: tools.length,
9741
+ query,
9742
+ grep: {
9743
+ mode,
9744
+ terms: parseGrepTerms(query, mode)
9745
+ },
9746
+ filters: {
9747
+ categories: requestedCategories
9748
+ },
9749
+ commandTemplates: TOOL_COMMAND_TEMPLATES
9750
+ },
9751
+ { json: options.json || shouldEmitJson() }
9752
+ );
9753
+ return 0;
9754
+ }
9309
9755
  function playIdentifiers(play) {
9310
9756
  return [play.name, play.reference, ...play.aliases ?? []].filter((value) => Boolean(value?.trim())).map((value) => value.trim());
9311
9757
  }
@@ -9375,24 +9821,27 @@ Notes:
9375
9821
 
9376
9822
  Examples:
9377
9823
  deepline tools list
9378
- deepline tools list --json
9824
+ deepline tools list --categories email_finder --json
9379
9825
  deepline tools search email --json
9380
9826
  `
9381
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9827
+ ).option("--categories <categories>", "Comma-separated categories to filter inventory").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9382
9828
  process.exitCode = await listTools([
9829
+ ...options.categories ? ["--categories", options.categories] : [],
9383
9830
  ...options.json ? ["--json"] : []
9384
9831
  ]);
9385
9832
  });
9386
- tools.command("search <query>").description("Search available tools.").addHelpText(
9833
+ const addToolSearchCommand = (command) => command.description("Search available tools.").addHelpText(
9387
9834
  "after",
9388
9835
  `
9389
9836
  Notes:
9390
9837
  Ranked discovery for atomic provider/API operations. Results include tool ids
9391
9838
  that can be passed to deepline tools describe or deepline tools execute.
9839
+ The grep alias is the same ranked retrieval surface with a more literal name
9840
+ for agents that are filtering a registry rather than choosing a workflow.
9392
9841
 
9393
9842
  Examples:
9394
9843
  deepline tools search email
9395
- deepline tools search "company enrichment" --categories enrichment --json
9844
+ deepline tools grep "company enrichment" --categories enrichment --json
9396
9845
  deepline tools search verifier --search-mode v2 --json
9397
9846
  `
9398
9847
  ).option("--categories <categories>", "Comma-separated categories to filter ranked search").option("--search_terms <terms>", "Structured search terms for ranked search").option("--search-terms <terms>", "Structured search terms for ranked search").option("--search-mode <mode>", "Ranked search mode: v1 or v2").option("--include-search-debug", "Include ranked search debug metadata").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
@@ -9404,24 +9853,55 @@ Examples:
9404
9853
  includeSearchDebug: Boolean(options.includeSearchDebug)
9405
9854
  });
9406
9855
  });
9856
+ addToolSearchCommand(tools.command("search <query>"));
9857
+ tools.command("grep <query>").description("Literal grep over tool ids, descriptions, categories, and input fields.").addHelpText(
9858
+ "after",
9859
+ `
9860
+ Notes:
9861
+ Literal registry filtering. Terms are matched case-insensitively against tool
9862
+ ids, provider, display name, description, categories, aliases, and input
9863
+ fields. Use --mode phrase for exact phrase matching, --mode any for OR, and
9864
+ the default --mode all for AND.
9865
+
9866
+ Examples:
9867
+ deepline tools grep email --categories email_finder --json
9868
+ deepline tools grep "phone validate" --mode all --json
9869
+ deepline tools grep hunter --mode phrase --json
9870
+ `
9871
+ ).option("--categories <categories>", "Comma-separated categories to filter inventory").option("--mode <mode>", "Grep matching mode: all, any, or phrase").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
9872
+ const mode = options.mode === "any" || options.mode === "phrase" ? options.mode : "all";
9873
+ process.exitCode = await grepTools(query, {
9874
+ json: options.json,
9875
+ categories: options.categories,
9876
+ mode
9877
+ });
9878
+ });
9407
9879
  const addToolMetadataCommand = (command) => command.description("Show metadata for a tool.").addHelpText(
9408
9880
  "after",
9409
9881
  `
9410
9882
  Notes:
9411
- Shows the tool contract, input schema, output schema, Deepline cost, aliases,
9412
- and metadata. describe is the supported discovery verb. get is removed in
9413
- the V2 SDK CLI; use describe for the same metadata surface.
9883
+ Shows the compact agent contract by default: what the tool does, cost,
9884
+ required inputs, play getters, and a runnable ctx.tools.execute snippet.
9885
+ Use --json for the full metadata/debug payload.
9414
9886
 
9415
9887
  Examples:
9416
9888
  deepline tools describe hunter_email_verifier
9417
- deepline tools describe hunter_email_verifier --json | jq '.inputSchema'
9889
+ deepline tools describe hunter_email_verifier --pricing-only
9890
+ deepline tools describe hunter_email_verifier --schema-only
9891
+ deepline tools describe hunter_email_verifier --examples-only
9892
+ deepline tools describe hunter_email_verifier --json
9418
9893
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
9419
9894
  `
9420
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
9421
- process.exitCode = await getTool([
9422
- toolId,
9423
- ...options.json ? ["--json"] : []
9424
- ]);
9895
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--pricing-only", "Only print pricing and billing semantics").option("--schema-only", "Only print input schema fields").option("--examples-only", "Only print runnable examples and sample payloads").option("--getters-only", "Only print extracted list/value getters").addOption(new import_commander2.Option("--compact", "Compatibility alias for the default compact view").hideHelp()).addOption(new import_commander2.Option("--contract-json", "Compatibility alias for compact contract JSON").hideHelp()).action(async (toolId, options) => {
9896
+ process.exitCode = await getTool(toolId, {
9897
+ json: Boolean(options.json),
9898
+ compact: Boolean(options.compact),
9899
+ contractJson: Boolean(options.contractJson),
9900
+ pricingOnly: Boolean(options.pricingOnly),
9901
+ schemaOnly: Boolean(options.schemaOnly),
9902
+ examplesOnly: Boolean(options.examplesOnly),
9903
+ gettersOnly: Boolean(options.gettersOnly)
9904
+ });
9425
9905
  });
9426
9906
  addToolMetadataCommand(tools.command("describe <toolId>"));
9427
9907
  tools.command("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
@@ -9474,8 +9954,7 @@ Examples:
9474
9954
  process.exitCode = await executeTool(args);
9475
9955
  });
9476
9956
  }
9477
- async function getTool(args) {
9478
- const toolId = args[0];
9957
+ async function getTool(toolId, options = {}) {
9479
9958
  if (!toolId) {
9480
9959
  console.error("Usage: deepline tools get <toolId> [--json]");
9481
9960
  return 1;
@@ -9492,14 +9971,264 @@ async function getTool(args) {
9492
9971
  }
9493
9972
  throw error;
9494
9973
  }
9495
- if (argsWantJson(args)) {
9974
+ if (options.contractJson) {
9975
+ process.stdout.write(`${JSON.stringify(toolContractJsonForDescribe(tool, toolId))}
9976
+ `);
9977
+ return 0;
9978
+ }
9979
+ const emitJson = options.json === true;
9980
+ if (emitJson) {
9496
9981
  process.stdout.write(`${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
9497
9982
  `);
9498
9983
  return 0;
9499
9984
  }
9500
- printToolDetails(tool, toolId);
9985
+ const onlyModes = [
9986
+ options.pricingOnly,
9987
+ options.schemaOnly,
9988
+ options.examplesOnly,
9989
+ options.gettersOnly
9990
+ ].filter(Boolean).length;
9991
+ if (onlyModes > 1) {
9992
+ console.error("Use only one of --pricing-only, --schema-only, --examples-only, or --getters-only.");
9993
+ return 2;
9994
+ }
9995
+ if (options.pricingOnly) {
9996
+ printToolPricingOnly(tool, toolId);
9997
+ return 0;
9998
+ }
9999
+ if (options.schemaOnly) {
10000
+ printToolSchemaOnly(tool, toolId);
10001
+ return 0;
10002
+ }
10003
+ if (options.examplesOnly) {
10004
+ printToolExamplesOnly(tool, toolId);
10005
+ return 0;
10006
+ }
10007
+ if (options.gettersOnly) {
10008
+ printToolGettersOnly(tool, toolId);
10009
+ return 0;
10010
+ }
10011
+ if (options.compact) {
10012
+ printCompactToolContract(tool, toolId);
10013
+ return 0;
10014
+ }
10015
+ if (shouldEmitJson()) {
10016
+ process.stdout.write(`${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
10017
+ `);
10018
+ return 0;
10019
+ }
10020
+ printCompactToolContract(tool, toolId);
9501
10021
  return 0;
9502
10022
  }
10023
+ function toolContractJsonForDescribe(tool, requestedToolId) {
10024
+ const toolId = String(tool.toolId || requestedToolId);
10025
+ const inputFields = toolInputFieldsForDisplay(recordField(tool, "inputSchema", "input_schema"));
10026
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
10027
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
10028
+ const extractedLists = extractionContractEntries(
10029
+ arrayField(toolExecutionResult, "extractedLists", "extracted_lists")
10030
+ );
10031
+ const extractedValues = extractionContractEntries(
10032
+ arrayField(toolExecutionResult, "extractedValues", "extracted_values")
10033
+ );
10034
+ const cost = recordField(tool, "cost");
10035
+ const deeplineCredits = numberField(tool, "deeplineCreditsPerPricingUnit", "deepline_credits_per_pricing_unit");
10036
+ const deeplineUsdPerPricingUnit = numberField(tool, "deeplineUsdPerPricingUnit", "deepline_usd_per_pricing_unit");
10037
+ return {
10038
+ schemaVersion: 1,
10039
+ toolId,
10040
+ provider: tool.provider,
10041
+ displayName: tool.displayName,
10042
+ description: tool.description,
10043
+ categories: tool.categories,
10044
+ inputFields: inputFields.map((field) => ({
10045
+ name: field.name,
10046
+ type: field.type ?? "unknown",
10047
+ required: Boolean(field.required),
10048
+ ...field.description ? { description: field.description } : {},
10049
+ ...Object.prototype.hasOwnProperty.call(field, "default") ? { default: field.default } : {}
10050
+ })),
10051
+ cost: {
10052
+ pricingModel: stringField(cost, "pricingModel", "pricing_model") || null,
10053
+ billingMode: stringField(cost, "billingMode", "billing_mode") || null,
10054
+ deeplineCreditsPerPricingUnit: deeplineCredits,
10055
+ deeplineUsdPerPricingUnit
10056
+ },
10057
+ getters: {
10058
+ extractedLists,
10059
+ extractedValues
10060
+ },
10061
+ executeCommand: `deepline tools execute ${toolId} --input '{...}' --json`
10062
+ };
10063
+ }
10064
+ function extractionContractEntries(entries) {
10065
+ return entries.flatMap((entry) => {
10066
+ if (!isRecord4(entry)) return [];
10067
+ const name = stringField(entry, "name");
10068
+ const expression = stringField(entry, "expression");
10069
+ return name && expression ? [{ name, expression }] : [];
10070
+ });
10071
+ }
10072
+ function printCompactToolContract(tool, requestedToolId) {
10073
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10074
+ const cost = isRecord4(contract.cost) ? contract.cost : {};
10075
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10076
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10077
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10078
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10079
+ console.log(String(contract.toolId));
10080
+ if (contract.displayName) console.log(`Best for: ${contract.displayName}`);
10081
+ if (typeof contract.description === "string" && contract.description.trim()) {
10082
+ console.log(`Description: ${contract.description.trim()}`);
10083
+ }
10084
+ if (Array.isArray(contract.categories) && contract.categories.length) {
10085
+ console.log(`Tags: ${contract.categories.join(", ")}`);
10086
+ }
10087
+ printToolPricingOnly(tool, requestedToolId, { heading: "Cost" });
10088
+ if (inputFields.length) {
10089
+ console.log("");
10090
+ console.log("Inputs:");
10091
+ for (const field of inputFields) {
10092
+ if (!isRecord4(field)) continue;
10093
+ const name = stringField(field, "name");
10094
+ if (!name) continue;
10095
+ const required = field.required ? "*" : "";
10096
+ const type = stringField(field, "type") || "unknown";
10097
+ const description = stringField(field, "description");
10098
+ console.log(`- ${name}${required}: ${type}${description ? ` - ${description}` : ""}`);
10099
+ }
10100
+ }
10101
+ console.log("");
10102
+ printToolExamplesOnly(tool, requestedToolId, { includeSamples: false });
10103
+ if (listGetters.length || valueGetters.length) {
10104
+ console.log("");
10105
+ console.log("Getters:");
10106
+ if (listGetters.length) console.log("Lists:");
10107
+ for (const entry of listGetters) {
10108
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10109
+ }
10110
+ if (valueGetters.length) console.log("Values:");
10111
+ for (const entry of valueGetters) {
10112
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10113
+ }
10114
+ }
10115
+ console.log("");
10116
+ console.log(`More: deepline tools describe ${contract.toolId} --pricing-only | --schema-only | --examples-only | --getters-only | --json`);
10117
+ }
10118
+ function printToolPricingOnly(tool, requestedToolId, options = {}) {
10119
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10120
+ const cost = isRecord4(contract.cost) ? contract.cost : {};
10121
+ const pricingModel = stringField(cost, "pricingModel") || "unknown";
10122
+ const billingMode = stringField(cost, "billingMode") || "unknown";
10123
+ const unit = pricingModel === "per_page" ? "page" : pricingModel === "per_result" ? "result" : pricingModel === "fixed" ? "call" : pricingModel.replace(/^per_/, "") || "unit";
10124
+ const credits = numberField(cost, "deeplineCreditsPerPricingUnit");
10125
+ const usd = numberField(cost, "deeplineUsdPerPricingUnit");
10126
+ const price = credits !== null ? `${formatDecimal(credits)} Deepline credits${usd !== null ? ` / ${formatUsd(usd)}` : ""} per ${unit}` : "pricing unavailable";
10127
+ console.log(`${options.heading ?? `Pricing: ${contract.toolId}`}: ${price}`);
10128
+ console.log(`Billing: ${billingMode}`);
10129
+ }
10130
+ function printToolSchemaOnly(tool, requestedToolId) {
10131
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10132
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10133
+ console.log(`Schema: ${contract.toolId}`);
10134
+ if (!inputFields.length) {
10135
+ console.log("Inputs: none");
10136
+ return;
10137
+ }
10138
+ console.log("Inputs:");
10139
+ for (const field of inputFields) {
10140
+ if (!isRecord4(field)) continue;
10141
+ const name = stringField(field, "name");
10142
+ if (!name) continue;
10143
+ const required = field.required ? "*" : "";
10144
+ const type = stringField(field, "type") || "unknown";
10145
+ const description = stringField(field, "description");
10146
+ const defaultSuffix = Object.prototype.hasOwnProperty.call(field, "default") ? ` default=${JSON.stringify(field.default)}` : "";
10147
+ console.log(`- ${name}${required}: ${type}${defaultSuffix}${description ? ` - ${description}` : ""}`);
10148
+ }
10149
+ }
10150
+ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
10151
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10152
+ const toolId = String(contract.toolId);
10153
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10154
+ const sampleInput = Object.fromEntries(
10155
+ inputFields.slice(0, 4).flatMap((field) => {
10156
+ if (!isRecord4(field)) return [];
10157
+ const name = stringField(field, "name");
10158
+ if (!name) return [];
10159
+ return [[name, sampleValueForField(field)]];
10160
+ })
10161
+ );
10162
+ console.log(`Use in a play:`);
10163
+ console.log("```ts");
10164
+ console.log("const result = await ctx.tools.execute({");
10165
+ console.log(` id: '${stableStepIdForTool(toolId)}',`);
10166
+ console.log(` tool: '${toolId}',`);
10167
+ console.log(` input: ${JSON.stringify(sampleInput || {}, null, 2).replace(/\n/g, "\n ")},`);
10168
+ console.log("});");
10169
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10170
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10171
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10172
+ const firstGetter = [...valueGetters, ...listGetters].find(isRecord4);
10173
+ if (firstGetter) {
10174
+ const name = stringField(firstGetter, "name") || "value";
10175
+ const expression = stringField(firstGetter, "expression");
10176
+ if (expression) console.log(`const ${safeIdentifier(name)} = ${expression.replace(/^toolExecutionResult\./, "result.")};`);
10177
+ }
10178
+ console.log("```");
10179
+ if (options.includeSamples !== false) {
10180
+ const samples = recordField(tool, "samples");
10181
+ printSamples(samples);
10182
+ }
10183
+ }
10184
+ function printToolGettersOnly(tool, requestedToolId) {
10185
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10186
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10187
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10188
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10189
+ console.log(`Getters: ${contract.toolId}`);
10190
+ if (!listGetters.length && !valueGetters.length) {
10191
+ console.log("No generated getters declared. Use --json only if you need raw metadata.");
10192
+ return;
10193
+ }
10194
+ if (listGetters.length) {
10195
+ console.log("Lists:");
10196
+ for (const entry of listGetters) {
10197
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10198
+ }
10199
+ }
10200
+ if (valueGetters.length) {
10201
+ console.log("Values:");
10202
+ for (const entry of valueGetters) {
10203
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10204
+ }
10205
+ }
10206
+ }
10207
+ function sampleValueForField(field) {
10208
+ const name = stringField(field, "name").toLowerCase();
10209
+ const type = stringField(field, "type").toLowerCase();
10210
+ if (Object.prototype.hasOwnProperty.call(field, "default")) return field.default;
10211
+ if (name.includes("email")) return "ada@example.com";
10212
+ if (name.includes("domain") || name.includes("website")) return "example.com";
10213
+ if (name.includes("first")) return "Ada";
10214
+ if (name.includes("last")) return "Lovelace";
10215
+ if (name.includes("name")) return "Ada Lovelace";
10216
+ if (type === "integer" || type === "number") return 1;
10217
+ if (type === "boolean") return true;
10218
+ if (type === "array") return [];
10219
+ if (type === "object") return {};
10220
+ return "...";
10221
+ }
10222
+ function stableStepIdForTool(toolId) {
10223
+ return toolId.replace(/^[a-z0-9]+_/, "").replace(/[^a-z0-9_]+/gi, "_") || "tool_call";
10224
+ }
10225
+ function safeIdentifier(name) {
10226
+ const cleaned = name.replace(/[^a-zA-Z0-9_$]+/g, "_").replace(/^[^a-zA-Z_$]+/, "");
10227
+ return cleaned || "value";
10228
+ }
10229
+ function playResultExpression(entry) {
10230
+ return stringField(entry, "expression").replace(/^toolExecutionResult\./, "result.");
10231
+ }
9503
10232
  function toolMetadataJsonForDescribe(tool, requestedToolId) {
9504
10233
  const toolId = String(tool.toolId || requestedToolId);
9505
10234
  const {
@@ -9530,152 +10259,6 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
9530
10259
  }
9531
10260
  };
9532
10261
  }
9533
- function printToolDetails(tool, requestedToolId) {
9534
- const toolId = String(tool.toolId || requestedToolId);
9535
- const operation = typeof tool.operation === "string" ? tool.operation : "";
9536
- const displayBase = operation && operation.startsWith(`${tool.provider}_`) ? operation.slice(String(tool.provider).length + 1) : operation ? `${tool.provider} ${operation}`.trim() : toolId;
9537
- const displayName = titleCase(displayBase || String(tool.displayName || toolId));
9538
- const cost = isRecord4(tool.cost) ? tool.cost : null;
9539
- const pricing = recordField(tool, "pricing");
9540
- const deeplineCredits = numberField(tool, "deeplineCreditsPerPricingUnit", "deepline_credits_per_pricing_unit");
9541
- const deeplineUsdPerPricingUnit = numberField(tool, "deeplineUsdPerPricingUnit", "deepline_usd_per_pricing_unit");
9542
- const billingSource = stringField(tool, "billingSource", "billing_source");
9543
- const billingSourceLabel = stringField(tool, "billingSourceLabel", "billing_source_label");
9544
- const estimatedCreditsRange = stringField(tool, "estimatedCreditsRange", "estimated_credits_range");
9545
- const estimatedUsdRange = stringField(tool, "estimatedUsdRange", "estimated_usd_range");
9546
- const estimateModelVersion = stringField(tool, "estimateModelVersion", "estimate_model_version");
9547
- const estimateBasedOnTools = arrayField(tool, "estimateBasedOnTools", "estimate_based_on_tools").map((item) => String(item).trim()).filter(Boolean);
9548
- const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
9549
- const playExpansion = recordField(tool, "playExpansion", "play_expansion");
9550
- const samples = recordField(tool, "samples");
9551
- const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
9552
- console.log(`Tool: ${toolId}`);
9553
- console.log(" Runtime output help:");
9554
- console.log(" describe shows declared schema/getters, not an observed provider response");
9555
- console.log(` observe actual shape: deepline tools execute ${toolId} --input '{...}' --json`);
9556
- console.log(" for play getter bugs: run the play, then use the db query commands printed by runs get");
9557
- if (displayName) {
9558
- console.log(" Display name:");
9559
- console.log(` ${displayName}`);
9560
- }
9561
- if (tool.categories.length > 0) {
9562
- console.log(" Categories:");
9563
- console.log(` ${tool.categories.join(", ")}`);
9564
- }
9565
- const printedCost = printToolCost({
9566
- pricing,
9567
- cost,
9568
- billingSource,
9569
- deeplineCredits,
9570
- deeplineUsdPerPricingUnit
9571
- });
9572
- if (!printedCost && ["run_javascript", "call_local_codex", "call_local_claude_code"].includes(toolId)) {
9573
- console.log(" Cost: free");
9574
- }
9575
- if (billingSourceLabel) {
9576
- console.log(` Billing source: ${billingSourceLabel}`);
9577
- }
9578
- if (estimatedCreditsRange) {
9579
- const usdSuffix = estimatedUsdRange ? ` (~${estimatedUsdRange})` : "";
9580
- console.log(` Estimated play spend: ${estimatedCreditsRange}${usdSuffix}`);
9581
- if (estimateModelVersion) console.log(` model: ${estimateModelVersion}`);
9582
- if (estimateBasedOnTools.length) console.log(` based on: ${estimateBasedOnTools.join(", ")}`);
9583
- if (stepContributions.length) {
9584
- console.log(" step contributions:");
9585
- for (const item of stepContributions) {
9586
- if (!isRecord4(item)) continue;
9587
- const stepTool = typeof item.tool === "string" ? item.tool.trim() : "";
9588
- const low = typeof item.lowCredits === "number" ? item.lowCredits : null;
9589
- const high = typeof item.highCredits === "number" ? item.highCredits : null;
9590
- const lowUsd = typeof item.lowUsd === "number" ? item.lowUsd : null;
9591
- const highUsd = typeof item.highUsd === "number" ? item.highUsd : null;
9592
- if (!stepTool || low === null || high === null) continue;
9593
- const stepUsdSuffix = lowUsd !== null && highUsd !== null ? ` (~${formatUsd(lowUsd)}-${formatUsd(highUsd)})` : "";
9594
- console.log(` - ${stepTool}: ${formatDecimal(low)}-${formatDecimal(high)} credits${stepUsdSuffix}`);
9595
- }
9596
- }
9597
- }
9598
- if (playExpansion && Object.keys(playExpansion).length > 0) {
9599
- const group = typeof playExpansion.group === "string" ? playExpansion.group.trim() : "";
9600
- console.log(" Play expansion:");
9601
- if (group) console.log(` group: ${group}`);
9602
- }
9603
- const fields = toolInputFieldsForDisplay(recordField(tool, "inputSchema", "input_schema"));
9604
- if (fields.length) {
9605
- console.log(" Inputs (operation-specific):");
9606
- for (const field of fields) {
9607
- const name = String(field.name || "");
9608
- const typeName = String(field.type || "unknown");
9609
- const requiredLabel = field.required ? "required" : "optional";
9610
- const defaultSuffix = Object.prototype.hasOwnProperty.call(field, "default") ? `, default: ${JSON.stringify(field.default)}` : "";
9611
- const desc = typeof field.description === "string" && field.description.trim() ? ` - ${field.description.trim()}` : "";
9612
- console.log(` - ${name} (${typeName}, ${requiredLabel}${defaultSuffix})${desc}`);
9613
- }
9614
- console.log(" Tip: pass --payload with a JSON object.");
9615
- }
9616
- printSamples(samples);
9617
- printUsageGuidance(usageGuidance);
9618
- if (isPlayTool(tool)) {
9619
- console.log(" Play contract:");
9620
- console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
9621
- if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
9622
- console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
9623
- }
9624
- const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult");
9625
- const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9626
- const targets = extractedValues.map((entry) => isRecord4(entry) && typeof entry.name === "string" ? entry.name : "").filter(Boolean).sort();
9627
- if (targets.length) {
9628
- console.log(` - Built-in extract targets: ${targets.join(", ")}`);
9629
- }
9630
- }
9631
- console.log("");
9632
- console.log("Usage:");
9633
- const requestPayload = samplePayload(samples, "request");
9634
- if (isPlayTool(tool)) {
9635
- if (requestPayload !== void 0) {
9636
- console.log(` deepline enrich --with '${JSON.stringify({ alias: "result", tool: toolId, payload: requestPayload })}'`);
9637
- } else {
9638
- console.log(` deepline enrich --with '{"alias":"result","tool":"${toolId}","payload":{...}}'`);
9639
- }
9640
- } else if (requestPayload !== void 0) {
9641
- console.log(` deepline tools execute ${toolId} --payload '${JSON.stringify(requestPayload)}'`);
9642
- } else {
9643
- console.log(` deepline tools execute ${toolId} --payload '{...}'`);
9644
- }
9645
- console.log(" deepline tools describe <tool_id> --json");
9646
- }
9647
- function printUsageGuidance(usageGuidance) {
9648
- if (Object.keys(usageGuidance).length === 0) return;
9649
- const execute = stringField(usageGuidance, "execute");
9650
- const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
9651
- const toolResponse = recordField(toolExecutionResult, "toolResponse", "tool_response");
9652
- const extractedLists = arrayField(toolExecutionResult, "extractedLists", "extracted_lists");
9653
- const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9654
- console.log(" Usage guidance:");
9655
- if (execute) console.log(` ${execute}`);
9656
- const raw = stringField(toolResponse, "raw");
9657
- const meta = stringField(toolResponse, "meta");
9658
- if (raw) console.log(` Raw tool response: ${raw}`);
9659
- if (meta) console.log(` Tool response metadata: ${meta}`);
9660
- printExtractions("Extracted lists", extractedLists);
9661
- printExtractions("Extracted values", extractedValues);
9662
- }
9663
- function printExtractions(label, entries) {
9664
- if (!entries.length) return;
9665
- console.log(` ${label}:`);
9666
- for (const entry of entries) {
9667
- if (!isRecord4(entry)) continue;
9668
- const name = stringField(entry, "name");
9669
- const expression = stringField(entry, "expression");
9670
- const details = recordField(entry, "details");
9671
- const rawToolOutputPaths = arrayField(details, "rawToolOutputPaths", "raw_tool_output_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9672
- const candidatePaths = arrayField(details, "candidatePaths", "candidate_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9673
- if (!name || !expression) continue;
9674
- const paths = candidatePaths.length ? candidatePaths : rawToolOutputPaths;
9675
- const pathSuffix = paths.length ? ` from ${paths.join(", ")}` : "";
9676
- console.log(` - ${name}: ${expression}${pathSuffix}`);
9677
- }
9678
- }
9679
10262
  function singleLineText(value, maxLength = 260) {
9680
10263
  if (typeof value !== "string") return "";
9681
10264
  const text = value.replace(/\s+/g, " ").trim();
@@ -9692,42 +10275,6 @@ function formatListedToolCost(tool) {
9692
10275
  const displayText = stringField(pricing, "displayText", "display_text");
9693
10276
  return displayText ? `Cost: ${displayText}` : "";
9694
10277
  }
9695
- function printToolCost(input) {
9696
- const { pricing, cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
9697
- if (billingSource === "own_provider_credentials") {
9698
- console.log(" Cost: free through Deepline");
9699
- return true;
9700
- }
9701
- const displayText = stringField(pricing, "displayText", "display_text");
9702
- if (displayText) {
9703
- console.log(` Cost: ${displayText}`);
9704
- const details = arrayField(pricing, "details").map((item) => String(item).trim()).filter(Boolean);
9705
- if (details.length) {
9706
- console.log(" notes:");
9707
- for (const detail of details) console.log(` - ${detail}`);
9708
- }
9709
- return true;
9710
- }
9711
- const pricingModel = cost ? typeof cost.pricingModel === "string" ? cost.pricingModel : typeof cost.pricing_model === "string" ? cost.pricing_model : "" : "";
9712
- const billingMode = cost ? typeof cost.billingMode === "string" ? cost.billingMode : typeof cost.billing_mode === "string" ? cost.billing_mode : "" : "";
9713
- if (deeplineCredits === 0) {
9714
- console.log(" Cost: Free");
9715
- return true;
9716
- }
9717
- if (pricingModel && deeplineCredits !== null) {
9718
- const unit = pricingModel === "per_page" ? "page" : pricingModel === "per_result" ? "result" : "call";
9719
- const usdText = deeplineUsdPerPricingUnit !== null ? ` / ${formatUsd(deeplineUsdPerPricingUnit)}` : "";
9720
- const billingSuffix = billingMode ? ` (${billingMode})` : "";
9721
- console.log(` Cost: ${formatDecimal(deeplineCredits)} Deepline credits${usdText} per ${unit}${billingSuffix}`);
9722
- return true;
9723
- }
9724
- const summary = stringField(pricing, "summary");
9725
- if (summary) {
9726
- console.log(` Cost: ${summary}`);
9727
- return true;
9728
- }
9729
- return false;
9730
- }
9731
10278
  function toolInputFieldsForDisplay(inputSchema) {
9732
10279
  if (Array.isArray(inputSchema.fields)) return inputSchema.fields.filter(isRecord4);
9733
10280
  const jsonSchema = isRecord4(inputSchema.jsonSchema) ? inputSchema.jsonSchema : inputSchema;
@@ -9778,17 +10325,6 @@ function listExtractorPathsFromUsageGuidance(tool) {
9778
10325
  ).filter(Boolean);
9779
10326
  });
9780
10327
  }
9781
- function isPlayTool(tool) {
9782
- const provider = typeof tool.provider === "string" ? tool.provider : "";
9783
- return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
9784
- }
9785
- function titleCase(value) {
9786
- return value.replace(/[_-]+/g, " ").split(" ").filter(Boolean).map((part) => {
9787
- const lower = part.toLowerCase();
9788
- const special = { linkedin: "LinkedIn", crm: "CRM", api: "API" };
9789
- return special[lower] ?? `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`;
9790
- }).join(" ");
9791
- }
9792
10328
  function formatDecimal(value) {
9793
10329
  const text = value.toFixed(12).replace(/0+$/, "").replace(/\.$/, "");
9794
10330
  return text || "0";
@@ -10548,6 +11084,61 @@ function shouldDeferSkillsSyncForCommand() {
10548
11084
  const subcommand = args[1];
10549
11085
  return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
10550
11086
  }
11087
+ async function runPlayRunnerHealthCheck() {
11088
+ const dir = await (0, import_promises5.mkdtemp)((0, import_node_path15.join)((0, import_node_os9.tmpdir)(), "deepline-health-play-"));
11089
+ const file = (0, import_node_path15.join)(dir, "health-check.play.ts");
11090
+ try {
11091
+ await (0, import_promises5.writeFile)(
11092
+ file,
11093
+ [
11094
+ "import { definePlay } from 'deepline';",
11095
+ "",
11096
+ "export default definePlay('health-check', async (ctx) => {",
11097
+ " const rows = await ctx",
11098
+ " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
11099
+ " .step('echo', (row) => ({ ok: true, id: row.id }))",
11100
+ " .run({ key: 'id' });",
11101
+ " return { ok: true, rows, source: 'deepline health --play-runner' };",
11102
+ "});",
11103
+ ""
11104
+ ].join("\n"),
11105
+ "utf8"
11106
+ );
11107
+ let capturedOutput = "";
11108
+ const originalWrite = process.stdout.write.bind(process.stdout);
11109
+ process.stdout.write = ((chunk, ...args) => {
11110
+ capturedOutput += typeof chunk === "string" ? chunk : String(chunk);
11111
+ return true;
11112
+ });
11113
+ let exitCode = 1;
11114
+ try {
11115
+ exitCode = await handlePlayRun([
11116
+ file,
11117
+ "--input",
11118
+ "{}",
11119
+ "--watch",
11120
+ "--no-open",
11121
+ "--json"
11122
+ ]);
11123
+ } finally {
11124
+ process.stdout.write = originalWrite;
11125
+ }
11126
+ if (exitCode !== 0) {
11127
+ throw new Error(
11128
+ `play runner canary exited ${exitCode}: ${capturedOutput.slice(0, 1e3)}`
11129
+ );
11130
+ }
11131
+ return {
11132
+ status: "ok",
11133
+ playRunner: {
11134
+ status: "ok",
11135
+ check: "no-provider local play run completed"
11136
+ }
11137
+ };
11138
+ } finally {
11139
+ await (0, import_promises5.rm)(dir, { recursive: true, force: true });
11140
+ }
11141
+ }
10551
11142
  async function main() {
10552
11143
  const mainStartedAt = Date.now();
10553
11144
  recordCliTrace({
@@ -10559,7 +11150,7 @@ async function main() {
10559
11150
  if (printStartupPhase) {
10560
11151
  progress?.phase("loading deepline cli");
10561
11152
  }
10562
- const program = new import_commander2.Command();
11153
+ const program = new import_commander3.Command();
10563
11154
  program.name("deepline").description("Deepline CLI (TypeScript SDK)").version(SDK_VERSION, "-v, --version", "Show version").showHelpAfterError().showSuggestionAfterError(true).addHelpText(
10564
11155
  "after",
10565
11156
  `
@@ -10568,7 +11159,7 @@ Common commands:
10568
11159
  deepline auth status --json
10569
11160
  deepline plays search email --json
10570
11161
  deepline plays describe person-linkedin-to-email --json
10571
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
11162
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
10572
11163
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
10573
11164
  deepline update
10574
11165
 
@@ -10624,18 +11215,30 @@ Exit codes:
10624
11215
  registerDbCommands(program);
10625
11216
  registerFeedbackCommands(program);
10626
11217
  registerUpdateCommand(program);
10627
- program.command("health").description("Check server health.").option("--json", "Force JSON output.").addHelpText(
11218
+ program.command("health").description("Check server health.").option("--json", "Force JSON output.").option(
11219
+ "--play-runner",
11220
+ "Run a tiny no-provider play to verify the full play execution plane."
11221
+ ).addHelpText(
10628
11222
  "after",
10629
11223
  `
10630
11224
  Notes:
10631
11225
  Read-only connectivity check for the configured Deepline host. Prints the raw
10632
11226
  server health payload as JSON.
11227
+ Add --play-runner to verify bundling, coordinator dispatch, runtime callbacks,
11228
+ and run streaming with a tiny no-provider play.
10633
11229
 
10634
11230
  Examples:
10635
11231
  deepline health
11232
+ deepline health --play-runner
10636
11233
  `
10637
- ).action(async () => {
11234
+ ).action(async (options) => {
10638
11235
  try {
11236
+ if (options.playRunner) {
11237
+ const data2 = await runPlayRunnerHealthCheck();
11238
+ process.stdout.write(`${JSON.stringify(data2, null, 2)}
11239
+ `);
11240
+ return;
11241
+ }
10639
11242
  const client = new DeeplineClient();
10640
11243
  const data = await client.health();
10641
11244
  process.stdout.write(`${JSON.stringify(data, null, 2)}