deepline 0.1.48 → 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.
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { Command as Command2 } from "commander";
4
+ import { mkdtemp, rm, writeFile as writeFile4 } from "fs/promises";
5
+ import { join as join11 } from "path";
6
+ import { tmpdir as tmpdir4 } from "os";
7
+ import { Command as Command3 } from "commander";
5
8
 
6
9
  // src/config.ts
7
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -193,7 +196,7 @@ function resolveConfig(options) {
193
196
  }
194
197
 
195
198
  // src/version.ts
196
- var SDK_VERSION = "0.1.48";
199
+ var SDK_VERSION = "0.1.49";
197
200
  var SDK_API_CONTRACT = "2026-05-stripe-promo-checkout";
198
201
 
199
202
  // ../shared_libs/play-runtime/coordinator-headers.ts
@@ -713,9 +716,18 @@ var DeeplineClient = class {
713
716
  * console.log(`Found ${searchTools.length} search tools`);
714
717
  * ```
715
718
  */
716
- async listTools() {
719
+ async listTools(options) {
720
+ const params = new URLSearchParams();
721
+ if (options?.categories?.trim()) {
722
+ params.set("categories", options.categories.trim());
723
+ }
724
+ if (options?.grep?.trim()) {
725
+ params.set("grep", options.grep.trim());
726
+ params.set("grep_mode", options.grepMode ?? "all");
727
+ }
728
+ const suffix = params.toString() ? `?${params.toString()}` : "";
717
729
  const res = await this.http.get(
718
- "/api/v2/tools"
730
+ `/api/v2/tools${suffix}`
719
731
  );
720
732
  return res.tools;
721
733
  }
@@ -1350,9 +1362,17 @@ var DeeplineClient = class {
1350
1362
  options?.reason ? { reason: options.reason } : {}
1351
1363
  );
1352
1364
  }
1353
- async listPlays() {
1365
+ async listPlays(options) {
1366
+ const params = new URLSearchParams();
1367
+ if (options?.origin) params.set("origin", options.origin);
1368
+ if (options?.grep?.trim()) {
1369
+ params.set("grep", options.grep.trim());
1370
+ params.set("grep_mode", options.grepMode ?? "all");
1371
+ params.set("limit", "60");
1372
+ }
1373
+ const suffix = params.toString() ? `?${params.toString()}` : "";
1354
1374
  const response = await this.http.get(
1355
- "/api/v2/plays"
1375
+ `/api/v2/plays${suffix}`
1356
1376
  );
1357
1377
  return response.plays ?? [];
1358
1378
  }
@@ -5670,6 +5690,16 @@ function extractPlayName(code, filePath) {
5670
5690
  function isFileTarget(target) {
5671
5691
  return existsSync6(resolve9(target));
5672
5692
  }
5693
+ function looksLikeRunId(target) {
5694
+ return /^play\/[^/]+\/run\/[^/]+/.test(target.trim());
5695
+ }
5696
+ function formatPlayCommandReceivedRunIdError(input) {
5697
+ return `\`deepline plays ${input.command} <run-id>\` expects a play name, but this looks like a run id.
5698
+ Use:
5699
+ deepline runs get ${input.runId} --json
5700
+ deepline runs logs ${input.runId} --json
5701
+ deepline runs export ${input.runId} --out output.csv`;
5702
+ }
5673
5703
  function looksLikeFilePath(target) {
5674
5704
  if (target.trim().toLowerCase().startsWith("prebuilt/")) {
5675
5705
  return false;
@@ -6096,6 +6126,69 @@ function describeLiveEventPhase(event) {
6096
6126
  }
6097
6127
  return null;
6098
6128
  }
6129
+ function formatProgressLabel(raw) {
6130
+ const value = typeof raw === "string" && raw.trim() ? raw.trim() : "step";
6131
+ return value.replace(/^map:/, "").replace(/^tool:/, "");
6132
+ }
6133
+ function formatProgressCounts(input) {
6134
+ const completed = typeof input.completed === "number" && Number.isFinite(input.completed) ? input.completed : null;
6135
+ const total = typeof input.total === "number" && Number.isFinite(input.total) ? input.total : null;
6136
+ if (completed === null || total === null || total <= 0) {
6137
+ return null;
6138
+ }
6139
+ const percent = Math.max(0, Math.min(100, Math.round(completed / total * 100)));
6140
+ const failed = typeof input.failed === "number" && Number.isFinite(input.failed) && input.failed > 0 ? `, failed ${formatInteger(input.failed)}` : "";
6141
+ return `${formatInteger(completed)}/${formatInteger(total)} (${percent}%)${failed}`;
6142
+ }
6143
+ function getProgressLinesFromLiveEvent(event) {
6144
+ const payload = getEventPayload(event);
6145
+ if (event.type === "play.step.progress") {
6146
+ const counts = formatProgressCounts({
6147
+ completed: payload.completed,
6148
+ total: payload.total,
6149
+ failed: payload.failed
6150
+ });
6151
+ if (!counts) return [];
6152
+ return [`progress ${formatProgressLabel(payload.stepId)}: ${counts}`];
6153
+ }
6154
+ if (event.type !== "play.run.snapshot" && event.type !== "play.run.final_status") {
6155
+ return [];
6156
+ }
6157
+ const nodeStates = Array.isArray(payload.nodeStates) ? payload.nodeStates : Array.isArray(payload.steps) ? payload.steps : [];
6158
+ const lines = [];
6159
+ for (const state of nodeStates) {
6160
+ if (!state || typeof state !== "object" || Array.isArray(state)) {
6161
+ continue;
6162
+ }
6163
+ const record = state;
6164
+ const progress = record.progress && typeof record.progress === "object" && !Array.isArray(record.progress) ? record.progress : null;
6165
+ if (!progress) {
6166
+ continue;
6167
+ }
6168
+ const counts = formatProgressCounts({
6169
+ completed: progress.completed,
6170
+ total: progress.total,
6171
+ failed: progress.failed
6172
+ });
6173
+ if (!counts) {
6174
+ continue;
6175
+ }
6176
+ lines.push(
6177
+ `progress ${formatProgressLabel(record.nodeId ?? progress.artifactTableNamespace)}: ${counts}`
6178
+ );
6179
+ }
6180
+ return lines;
6181
+ }
6182
+ function printPlayProgressLines(input) {
6183
+ for (const line of input.lines) {
6184
+ const signature = line.trim();
6185
+ if (!signature || input.state.lastProgressSignature === signature) {
6186
+ continue;
6187
+ }
6188
+ input.state.lastProgressSignature = signature;
6189
+ input.progress.writeLine(line);
6190
+ }
6191
+ }
6099
6192
  function buildPlayDashboardUrl(baseUrl, playName) {
6100
6193
  const trimmedBase = baseUrl.replace(/\/$/, "");
6101
6194
  const encodedPlayName = encodeURIComponent(playName);
@@ -6169,6 +6262,13 @@ async function waitForPlayCompletionByStream(input) {
6169
6262
  state: input.state,
6170
6263
  progress: input.progress
6171
6264
  });
6265
+ if (!input.jsonOutput) {
6266
+ printPlayProgressLines({
6267
+ lines: getProgressLinesFromLiveEvent(event),
6268
+ state: input.state,
6269
+ progress: input.progress
6270
+ });
6271
+ }
6172
6272
  const status = getStatusFromLiveEvent(event);
6173
6273
  if (status && TERMINAL_PLAY_STATUSES2.has(status)) {
6174
6274
  const finalStatus = await input.client.getPlayStatus(input.workflowId, {
@@ -6237,7 +6337,8 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6237
6337
  );
6238
6338
  const state = {
6239
6339
  lastLogIndex: 0,
6240
- emittedRunnerStarted: false
6340
+ emittedRunnerStarted: false,
6341
+ lastProgressSignature: null
6241
6342
  };
6242
6343
  const controller = new AbortController();
6243
6344
  let timedOut = false;
@@ -6265,11 +6366,6 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6265
6366
  }
6266
6367
  const workflowId = lastKnownWorkflowId || "pending";
6267
6368
  if (workflowId !== "pending" && !emittedDashboardUrl) {
6268
- openPlayDashboard({
6269
- dashboardUrl,
6270
- jsonOutput: input.jsonOutput,
6271
- noOpen: input.noOpen
6272
- });
6273
6369
  if (!input.jsonOutput) {
6274
6370
  writeStartedPlayRun({
6275
6371
  runId: workflowId,
@@ -6279,6 +6375,11 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6279
6375
  progress: input.progress
6280
6376
  });
6281
6377
  }
6378
+ openPlayDashboard({
6379
+ dashboardUrl,
6380
+ jsonOutput: input.jsonOutput,
6381
+ noOpen: input.noOpen
6382
+ });
6282
6383
  input.progress.phase(`loading play on ${dashboardUrl}`);
6283
6384
  emittedDashboardUrl = true;
6284
6385
  }
@@ -6301,6 +6402,13 @@ async function startAndWaitForPlayCompletionByStreamOnce(input) {
6301
6402
  state,
6302
6403
  progress: input.progress
6303
6404
  });
6405
+ if (!input.jsonOutput) {
6406
+ printPlayProgressLines({
6407
+ lines: getProgressLinesFromLiveEvent(event),
6408
+ state,
6409
+ progress: input.progress
6410
+ });
6411
+ }
6304
6412
  const finalStatus = getFinalStatusFromLiveEvent(event);
6305
6413
  if (finalStatus) {
6306
6414
  recordCliTrace({
@@ -6569,6 +6677,7 @@ function buildRunNextCommands(status) {
6569
6677
  const commands = {
6570
6678
  get: `deepline runs get ${runId} --json`,
6571
6679
  full: `deepline runs get ${runId} --full --json`,
6680
+ export: `deepline runs export ${runId} --out output.csv`,
6572
6681
  stop: `deepline runs stop ${runId} --reason "stale lock" --json`,
6573
6682
  logs: `deepline runs logs ${runId} --out run.log --json`
6574
6683
  };
@@ -6701,6 +6810,32 @@ function formatInsufficientCreditsMessage(input) {
6701
6810
  const addSuffix = billingUrl && recommended !== "-" ? ` Add >=${recommended} at ${billingUrl}.` : billingUrl ? ` Add credits at ${billingUrl}.` : "";
6702
6811
  return `Workspace balance ${balance} < required ${required} for ${operation}${workspaceSuffix}.${addSuffix}`;
6703
6812
  }
6813
+ function buildInsufficientCreditsSummaryLines(input) {
6814
+ const progress = input.status.progress;
6815
+ const rowsInfo = extractCanonicalRowsInfo(input.status);
6816
+ const completed = getNumericField(progress, "completed") ?? getNumericField(progress, "completedRows") ?? rowsInfo?.rows.length ?? null;
6817
+ const total = getNumericField(progress, "total") ?? getNumericField(progress, "totalRows") ?? rowsInfo?.totalRows ?? null;
6818
+ const lines = [
6819
+ " status: stopped_insufficient_credits",
6820
+ completed === null ? " completed rows: unknown" : ` completed rows: ${formatInteger(completed)}${total !== null ? ` of ${formatInteger(total)}` : ""}`,
6821
+ " reusable receipts: yes; rerun after adding credits to continue from completed provider work"
6822
+ ];
6823
+ const billingUrl = getStringField(input.billing, "billing_url");
6824
+ const recommended = formatCreditAmount(
6825
+ input.billing.recommended_add_credits ?? input.billing.needed_credits
6826
+ );
6827
+ if (billingUrl) {
6828
+ lines.push(
6829
+ recommended !== "-" ? ` add credits: add >=${recommended} at ${billingUrl}` : ` add credits: ${billingUrl}`
6830
+ );
6831
+ }
6832
+ const runId = input.status.runId?.trim();
6833
+ if (runId) {
6834
+ lines.push(` inspect: deepline runs get ${runId} --json`);
6835
+ lines.push(` export partial: deepline runs export ${runId} --out output.csv`);
6836
+ }
6837
+ return lines;
6838
+ }
6704
6839
  function formatPlayErrorForDisplay(status, error) {
6705
6840
  if (!error) {
6706
6841
  return null;
@@ -6891,6 +7026,19 @@ function formatDatasetStatsLines(datasetStats) {
6891
7026
  }
6892
7027
  return lines;
6893
7028
  }
7029
+ function buildJsonRunResultRenderLines(status) {
7030
+ const publicStatus = status.status ?? "running";
7031
+ const runId = status.runId ?? "unknown";
7032
+ const lines = [`${publicStatus} ${runId}`];
7033
+ const progressError = status.progress?.error;
7034
+ if (typeof progressError === "string") {
7035
+ const billing = extractBillingForStatus(status, progressError);
7036
+ if (isInsufficientCreditsBilling(billing)) {
7037
+ lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
7038
+ }
7039
+ }
7040
+ return lines;
7041
+ }
6894
7042
  function writePlayResult(status, jsonOutput, options) {
6895
7043
  if (jsonOutput) {
6896
7044
  const payload2 = options?.fullJson ? enrichPlayStatusWithDatasetStats(status) : compactPlayStatus(status);
@@ -6900,9 +7048,7 @@ function writePlayResult(status, jsonOutput, options) {
6900
7048
  sections: [
6901
7049
  {
6902
7050
  title: "run result",
6903
- lines: [
6904
- `${status.status ?? "running"} ${status.runId ?? "unknown"}`
6905
- ]
7051
+ lines: buildJsonRunResultRenderLines(status)
6906
7052
  }
6907
7053
  ]
6908
7054
  }
@@ -6929,6 +7075,10 @@ function writePlayResult(status, jsonOutput, options) {
6929
7075
  lines.push(...formatDatasetStatsLines(datasetStats));
6930
7076
  const progressError = status.progress?.error;
6931
7077
  if (progressError && typeof progressError === "string") {
7078
+ const billing = extractBillingForStatus(status, progressError);
7079
+ if (isInsufficientCreditsBilling(billing)) {
7080
+ lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
7081
+ }
6932
7082
  const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
6933
7083
  lines.push(` error: ${displayError.slice(0, 200)}`);
6934
7084
  }
@@ -7308,24 +7458,31 @@ function writeStartedPlayRun(input) {
7308
7458
  workflowId: input.runId,
7309
7459
  name: input.playName,
7310
7460
  status: input.status ?? "started",
7311
- dashboardUrl: input.dashboardUrl
7461
+ dashboardUrl: input.dashboardUrl,
7462
+ next: {
7463
+ inspect: `deepline runs get ${input.runId} --json`,
7464
+ full: `deepline runs get ${input.runId} --full --json`,
7465
+ logs: `deepline runs logs ${input.runId} --json`,
7466
+ export: `deepline runs export ${input.runId} --out output.csv`,
7467
+ stop: `deepline runs stop ${input.runId} --reason "stale lock" --json`
7468
+ }
7312
7469
  };
7470
+ const lines = [
7471
+ `Started ${input.playName}`,
7472
+ ` run id: ${input.runId}`,
7473
+ ` inspect: deepline runs get ${input.runId} --json`,
7474
+ ` full debug: deepline runs get ${input.runId} --full --json`,
7475
+ ` logs: deepline runs logs ${input.runId} --json`,
7476
+ ` export after completion: deepline runs export ${input.runId} --out output.csv`,
7477
+ ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`
7478
+ ];
7313
7479
  if (input.jsonOutput) {
7314
7480
  printCommandEnvelope({
7315
7481
  ...payload,
7316
- render: { sections: [{ title: "play run", lines: [`Started ${input.playName}`, `run id: ${input.runId}`] }] }
7482
+ render: { sections: [{ title: "play run", lines }] }
7317
7483
  }, { json: true });
7318
7484
  return;
7319
7485
  }
7320
- const lines = [
7321
- `Started ${input.playName}`,
7322
- ` run id: ${input.runId}`,
7323
- ` get status: deepline runs get ${input.runId} --json`,
7324
- ` logs: deepline runs logs ${input.runId} --json`,
7325
- ` stop run: deepline runs stop ${input.runId} --reason "stale lock" --json`,
7326
- ` result JSON: deepline runs get ${input.runId} --json`,
7327
- ` full debug JSON: deepline runs get ${input.runId} --full --json`
7328
- ];
7329
7486
  if (input.dashboardUrl) {
7330
7487
  lines.push(` play page: ${input.dashboardUrl}`);
7331
7488
  }
@@ -7341,13 +7498,13 @@ function writeStartedPlayRun(input) {
7341
7498
  ` });
7342
7499
  }
7343
7500
  function parsePlayRunOptions(args) {
7344
- 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.";
7501
+ 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.";
7345
7502
  let filePath = null;
7346
7503
  let playName = null;
7347
7504
  let input = null;
7348
7505
  let revisionId = null;
7349
7506
  let revisionSelector = null;
7350
- const watch = args.includes("--watch") || args.includes("--wait");
7507
+ const watch = !args.includes("--no-wait");
7351
7508
  let jsonOutput = watch ? args.includes("--json") : argsWantJson(args);
7352
7509
  const emitLogs = !jsonOutput || args.includes("--logs");
7353
7510
  const force = args.includes("--force");
@@ -7393,7 +7550,7 @@ function parsePlayRunOptions(args) {
7393
7550
  waitTimeoutMs = parsePositiveInteger2(args[++index], arg);
7394
7551
  continue;
7395
7552
  }
7396
- if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
7553
+ if (arg === "--json" || arg === "--wait" || arg === "--tail" || arg === "--watch" || arg === "--no-wait" || arg === "--logs" || arg === "--force" || arg === "--no-open") {
7397
7554
  if (arg === "--watch") {
7398
7555
  continue;
7399
7556
  }
@@ -8249,6 +8406,12 @@ async function handlePlayGet(args) {
8249
8406
  console.error("Usage: deepline play get <play-file.ts|play-name> [--json]");
8250
8407
  return 1;
8251
8408
  }
8409
+ if (looksLikeRunId(target)) {
8410
+ console.error(
8411
+ formatPlayCommandReceivedRunIdError({ command: "get", runId: target })
8412
+ );
8413
+ return 2;
8414
+ }
8252
8415
  const client = new DeeplineClient();
8253
8416
  const explicitJson = args.includes("--json");
8254
8417
  const sourceOutput = args.includes("--source");
@@ -8363,8 +8526,20 @@ async function handlePlayVersions(args) {
8363
8526
  }
8364
8527
  async function handlePlayList(args) {
8365
8528
  const jsonOutput = argsWantJson(args);
8529
+ const originArgIndex = args.findIndex((arg) => arg === "--origin");
8530
+ const rawOrigin = originArgIndex >= 0 ? args[originArgIndex + 1] : void 0;
8531
+ if (rawOrigin && rawOrigin !== "prebuilt" && rawOrigin !== "owned") {
8532
+ throw new Error(`Invalid value for --origin: ${rawOrigin}`);
8533
+ }
8534
+ const origin = rawOrigin;
8366
8535
  const client = new DeeplineClient();
8367
- const plays = await client.listPlays();
8536
+ const plays = (await client.listPlays({
8537
+ ...origin ? { origin } : {}
8538
+ })).filter((play) => {
8539
+ if (!origin) return true;
8540
+ const isPrebuilt = play.origin === "prebuilt" || play.ownerType === "deepline";
8541
+ return origin === "prebuilt" ? isPrebuilt : !isPrebuilt;
8542
+ });
8368
8543
  if (jsonOutput) {
8369
8544
  process.stdout.write(`${JSON.stringify(plays)}
8370
8545
  `);
@@ -8467,6 +8642,54 @@ function printPlayDescription(play) {
8467
8642
  );
8468
8643
  }
8469
8644
  }
8645
+ function compactPlaySchema(schema) {
8646
+ if (!schema) return null;
8647
+ const fields = Array.isArray(schema.fields) ? schema.fields.map(
8648
+ (field) => field && typeof field === "object" ? {
8649
+ name: String(field.name ?? ""),
8650
+ type: field.type ?? void 0,
8651
+ required: field.required ?? void 0
8652
+ } : null
8653
+ ).filter((field) => Boolean(field?.name)) : [];
8654
+ return fields.length > 0 ? { fields } : schema;
8655
+ }
8656
+ function playSchemaMetadata(schema, key) {
8657
+ if (!schema || typeof schema !== "object" || Array.isArray(schema)) return null;
8658
+ const value = schema[key];
8659
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
8660
+ }
8661
+ function playRunCommand(play, options) {
8662
+ const target = play.reference || play.name;
8663
+ if (options?.csvInput) {
8664
+ const inputField = typeof options.csvInput.inputField === "string" && options.csvInput.inputField.trim() ? options.csvInput.inputField.trim() : "csv";
8665
+ return `deepline plays run ${target} --input '${JSON.stringify({ [inputField]: "leads.csv" })}' --watch`;
8666
+ }
8667
+ return `deepline plays run ${target} --input '{...}' --watch`;
8668
+ }
8669
+ function summarizePlayListItemForCli(play, options) {
8670
+ const aliases = play.aliases?.length ? play.aliases : [play.name];
8671
+ const csvInput = playSchemaMetadata(play.inputSchema, "csvInput");
8672
+ const rowOutputSchema = playSchemaMetadata(play.outputSchema, "rowOutputSchema");
8673
+ const runCommand2 = playRunCommand(play, { csvInput });
8674
+ return {
8675
+ name: play.name,
8676
+ ...play.reference ? { reference: play.reference } : {},
8677
+ ...play.displayName ? { displayName: play.displayName } : {},
8678
+ origin: play.origin,
8679
+ ownerType: play.ownerType,
8680
+ canEdit: play.canEdit,
8681
+ canClone: play.canClone,
8682
+ aliases,
8683
+ inputSchema: options?.compact ? compactPlaySchema(play.inputSchema) : play.inputSchema ?? null,
8684
+ outputSchema: options?.compact ? compactPlaySchema(play.outputSchema) : play.outputSchema ?? null,
8685
+ ...csvInput ? { csvInput } : {},
8686
+ ...rowOutputSchema ? { rowOutputSchema } : {},
8687
+ runCommand: runCommand2,
8688
+ examples: [runCommand2],
8689
+ currentPublishedVersion: play.currentPublishedVersion ?? null,
8690
+ isDraftDirty: play.isDraftDirty
8691
+ };
8692
+ }
8470
8693
  async function handlePlaySearch(args) {
8471
8694
  let options;
8472
8695
  try {
@@ -8488,6 +8711,100 @@ async function handlePlaySearch(args) {
8488
8711
  }
8489
8712
  process.stdout.write(`${plays.length} plays found:
8490
8713
 
8714
+ `);
8715
+ for (const play of plays) {
8716
+ printPlayDescription(play);
8717
+ console.log("");
8718
+ }
8719
+ return 0;
8720
+ }
8721
+ function normalizePlayGrepText(value) {
8722
+ if (value === null || value === void 0) return "";
8723
+ if (typeof value === "string") return value.toLowerCase();
8724
+ if (typeof value === "number" || typeof value === "boolean") {
8725
+ return String(value).toLowerCase();
8726
+ }
8727
+ if (Array.isArray(value)) return value.map(normalizePlayGrepText).join(" ");
8728
+ if (typeof value === "object") {
8729
+ return Object.values(value).map(normalizePlayGrepText).join(" ");
8730
+ }
8731
+ return "";
8732
+ }
8733
+ function parsePlayGrepTerms(query, mode) {
8734
+ const trimmed = query.trim().toLowerCase();
8735
+ if (!trimmed) return [];
8736
+ if (mode === "phrase") return [trimmed];
8737
+ const matches = trimmed.match(/"([^"]+)"|'([^']+)'|\S+/g) ?? [];
8738
+ return matches.map((term) => term.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
8739
+ }
8740
+ function matchesPlayGrepQuery(value, query, mode) {
8741
+ const terms = parsePlayGrepTerms(query, mode);
8742
+ if (terms.length === 0) return true;
8743
+ const haystack = normalizePlayGrepText(value);
8744
+ if (mode === "any") return terms.some((term) => haystack.includes(term));
8745
+ return terms.every((term) => haystack.includes(term));
8746
+ }
8747
+ async function handlePlayGrep(args) {
8748
+ const query = args[0]?.trim();
8749
+ if (!query) {
8750
+ console.error("Usage: deepline plays grep <query> [--origin prebuilt|owned] [--compact] [--json]");
8751
+ return 1;
8752
+ }
8753
+ let origin;
8754
+ let mode = "all";
8755
+ for (let index = 1; index < args.length; index += 1) {
8756
+ const arg = args[index];
8757
+ if (arg === "--origin" && args[index + 1]) {
8758
+ const rawOrigin = args[++index].trim().toLowerCase();
8759
+ if (rawOrigin !== "prebuilt" && rawOrigin !== "owned") {
8760
+ throw new Error(`Invalid value for --origin: ${rawOrigin}`);
8761
+ }
8762
+ origin = rawOrigin;
8763
+ }
8764
+ if (arg === "--mode" && args[index + 1]) {
8765
+ const rawMode = args[++index].trim().toLowerCase();
8766
+ mode = rawMode === "any" || rawMode === "phrase" ? rawMode : "all";
8767
+ }
8768
+ }
8769
+ const compact = args.includes("--compact");
8770
+ const client = new DeeplineClient();
8771
+ const plays = (await client.listPlays({
8772
+ grep: query,
8773
+ grepMode: mode,
8774
+ ...origin ? { origin } : {}
8775
+ })).filter((play) => {
8776
+ if (!origin) return true;
8777
+ const isPrebuilt = play.origin === "prebuilt" || play.ownerType === "deepline";
8778
+ return origin === "prebuilt" ? isPrebuilt : !isPrebuilt;
8779
+ }).filter(
8780
+ (play) => matchesPlayGrepQuery(
8781
+ {
8782
+ name: play.name,
8783
+ reference: play.reference,
8784
+ displayName: play.displayName,
8785
+ origin: play.origin,
8786
+ ownerType: play.ownerType,
8787
+ aliases: play.aliases,
8788
+ inputSchema: play.inputSchema,
8789
+ outputSchema: play.outputSchema
8790
+ },
8791
+ query,
8792
+ mode
8793
+ )
8794
+ ).map((play) => summarizePlayListItemForCli(play, { compact }));
8795
+ if (argsWantJson(args)) {
8796
+ process.stdout.write(`${JSON.stringify({
8797
+ plays,
8798
+ count: plays.length,
8799
+ query,
8800
+ grep: { mode, terms: parsePlayGrepTerms(query, mode) },
8801
+ filters: { origin: origin ?? null }
8802
+ })}
8803
+ `);
8804
+ return 0;
8805
+ }
8806
+ process.stdout.write(`${plays.length} plays found:
8807
+
8491
8808
  `);
8492
8809
  for (const play of plays) {
8493
8810
  printPlayDescription(play);
@@ -8503,6 +8820,15 @@ async function handlePlayDescribe(args) {
8503
8820
  );
8504
8821
  return 1;
8505
8822
  }
8823
+ if (looksLikeRunId(playName)) {
8824
+ console.error(
8825
+ formatPlayCommandReceivedRunIdError({
8826
+ command: "describe",
8827
+ runId: playName
8828
+ })
8829
+ );
8830
+ return 2;
8831
+ }
8506
8832
  const client = new DeeplineClient();
8507
8833
  await assertCanonicalNamedPlayReference(client, playName);
8508
8834
  const play = await client.describePlay(
@@ -8659,7 +8985,7 @@ Common commands:
8659
8985
  deepline plays search email --json
8660
8986
  deepline plays describe person-linkedin-to-email --json
8661
8987
  deepline plays check my.play.ts
8662
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
8988
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
8663
8989
  deepline plays set-live my.play.ts --json
8664
8990
  deepline plays get person-linkedin-to-email --json
8665
8991
  `
@@ -8690,8 +9016,10 @@ Notes:
8690
9016
  Unknown --foo value and --foo.bar value flags are passed into play input.
8691
9017
  Example: --limit 5 becomes input.limit = 5.
8692
9018
  File args accept local paths; the CLI stages files before submit.
8693
- --watch prints logs, previews, stats, and next commands.
8694
- --wait is accepted as a compatibility alias for --watch.
9019
+ By default, run waits for completion and prints logs, previews, stats, and
9020
+ next commands. --watch and --wait are accepted compatibility aliases for the
9021
+ default behavior. Use --no-wait only when you intentionally want
9022
+ a fire-and-forget run id.
8695
9023
  The play page opens in your browser as soon as the run starts; use --no-open
8696
9024
  to only print the URL.
8697
9025
  --force supersedes active runs; it does not bypass completed reuse.
@@ -8719,17 +9047,18 @@ Idempotent execution:
8719
9047
  .run({ key: 'domain', staleAfterSeconds: 86400 })
8720
9048
 
8721
9049
  Examples:
8722
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
8723
- deepline plays run my.play.ts --input @input.json --wait --json
8724
- deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}' --watch
8725
- deepline plays run cto-search.play.ts --limit 5 --watch
9050
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
9051
+ deepline plays run my.play.ts --input @input.json --json
9052
+ deepline plays run person-linkedin-to-email --input '{"linkedin_url":"..."}'
9053
+ deepline plays run cto-search.play.ts --limit 5
9054
+ deepline plays run long-background-play --no-wait
8726
9055
  deepline runs export <run-id> --out output.csv
8727
9056
  deepline runs get <run-id>
8728
9057
  `
8729
9058
  ).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(
8730
9059
  "--revision-id <id>",
8731
9060
  "Run a specific saved revision instead of the live revision"
8732
- ).option("--watch", "Stream logs until completion").option("--wait", "Alias for --watch; stream logs until completion").option(
9061
+ ).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(
8733
9062
  "--logs",
8734
9063
  "When output is non-interactive, stream play logs to stderr while waiting"
8735
9064
  ).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(
@@ -8762,6 +9091,7 @@ Pass-through input flags:
8762
9091
  ...options.live ? ["--live"] : [],
8763
9092
  ...options.latest ? ["--latest"] : [],
8764
9093
  ...options.revisionId ? ["--revision-id", options.revisionId] : [],
9094
+ ...options.wait === false ? ["--no-wait"] : [],
8765
9095
  ...options.watch || options.wait ? ["--watch"] : [],
8766
9096
  ...options.logs ? ["--logs"] : [],
8767
9097
  ...options.tailTimeoutMs ? ["--tail-timeout-ms", options.tailTimeoutMs] : [],
@@ -8802,24 +9132,27 @@ Notes:
8802
9132
 
8803
9133
  Examples:
8804
9134
  deepline plays list
8805
- deepline plays list --json
9135
+ deepline plays list --origin prebuilt --json
8806
9136
  deepline plays search email --origin prebuilt --json
8807
9137
  `
8808
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9138
+ ).option("--origin <origin>", "Filter to prebuilt or owned plays").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
8809
9139
  process.exitCode = await handlePlayList([
9140
+ ...options.origin ? ["--origin", options.origin] : [],
8810
9141
  ...options.json ? ["--json"] : []
8811
9142
  ]);
8812
9143
  });
8813
- play.command("search <query>").description("Search saved and prebuilt plays.").addHelpText(
9144
+ const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").addHelpText(
8814
9145
  "after",
8815
9146
  `
8816
9147
  Notes:
8817
9148
  Ranked discovery for workflows. Use --origin prebuilt or --origin owned when
8818
9149
  you need to narrow results. Use describe on a result before running it.
9150
+ The grep alias is the same ranked retrieval surface with a more literal name
9151
+ for agents that are filtering the play registry.
8819
9152
 
8820
9153
  Examples:
8821
9154
  deepline plays search email
8822
- deepline plays search "linkedin to email" --origin prebuilt --compact --json
9155
+ deepline plays grep "linkedin to email" --origin prebuilt --compact --json
8823
9156
  deepline plays describe person-linkedin-to-email --json
8824
9157
  `
8825
9158
  ).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) => {
@@ -8830,6 +9163,30 @@ Examples:
8830
9163
  ...options.json ? ["--json"] : []
8831
9164
  ]);
8832
9165
  });
9166
+ addPlaySearchCommand(play.command("search <query>"));
9167
+ play.command("grep <query>").description("Literal grep over play names, aliases, schemas, and descriptions.").addHelpText(
9168
+ "after",
9169
+ `
9170
+ Notes:
9171
+ Literal registry filtering. Terms are matched case-insensitively against play
9172
+ names, references, display names, aliases, ownership, and schemas. Use
9173
+ --mode phrase for exact phrase matching, --mode any for OR, and the default
9174
+ --mode all for AND.
9175
+
9176
+ Examples:
9177
+ deepline plays grep email --origin prebuilt --json
9178
+ deepline plays grep "company contact" --origin prebuilt --mode all --json
9179
+ deepline plays describe prebuilt/company-to-contact --json
9180
+ `
9181
+ ).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) => {
9182
+ process.exitCode = await handlePlayGrep([
9183
+ query,
9184
+ ...options.origin ? ["--origin", options.origin] : [],
9185
+ ...options.compact ? ["--compact"] : [],
9186
+ ...options.mode ? ["--mode", options.mode] : [],
9187
+ ...options.json ? ["--json"] : []
9188
+ ]);
9189
+ });
8833
9190
  play.command("describe <target>").description("Describe a play contract and how to run it.").addHelpText(
8834
9191
  "after",
8835
9192
  `
@@ -8858,7 +9215,7 @@ Notes:
8858
9215
  Examples:
8859
9216
  deepline plays versions --name my-play
8860
9217
  deepline plays versions --name my-play --json
8861
- deepline plays run my-play --revision-id <revision-id> --watch
9218
+ deepline plays run my-play --revision-id <revision-id>
8862
9219
  `
8863
9220
  ).option("--name <name>", "Saved play name").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
8864
9221
  process.exitCode = await handlePlayVersions([
@@ -9060,6 +9417,7 @@ Examples:
9060
9417
  }
9061
9418
 
9062
9419
  // src/cli/commands/tools.ts
9420
+ import { Option } from "commander";
9063
9421
  import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
9064
9422
  import { tmpdir as tmpdir3 } from "os";
9065
9423
  import { join as join8 } from "path";
@@ -9244,9 +9602,42 @@ function toListedTool(tool) {
9244
9602
  executeCommand: `deepline tools execute ${tool.toolId}`
9245
9603
  };
9246
9604
  }
9605
+ function normalizeGrepText(value) {
9606
+ if (value === null || value === void 0) return "";
9607
+ if (typeof value === "string") return value.toLowerCase();
9608
+ if (typeof value === "number" || typeof value === "boolean") {
9609
+ return String(value).toLowerCase();
9610
+ }
9611
+ if (Array.isArray(value)) return value.map(normalizeGrepText).join(" ");
9612
+ if (typeof value === "object") {
9613
+ return Object.values(value).map(normalizeGrepText).join(" ");
9614
+ }
9615
+ return "";
9616
+ }
9617
+ function parseGrepTerms(query, mode) {
9618
+ const trimmed = query.trim().toLowerCase();
9619
+ if (!trimmed) return [];
9620
+ if (mode === "phrase") return [trimmed];
9621
+ const matches = trimmed.match(/"([^"]+)"|'([^']+)'|\S+/g) ?? [];
9622
+ return matches.map((term) => term.replace(/^["']|["']$/g, "").trim()).filter(Boolean);
9623
+ }
9624
+ function matchesGrepQuery(value, query, mode) {
9625
+ const terms = parseGrepTerms(query, mode);
9626
+ if (terms.length === 0) return true;
9627
+ const haystack = normalizeGrepText(value);
9628
+ if (mode === "any") return terms.some((term) => haystack.includes(term));
9629
+ return terms.every((term) => haystack.includes(term));
9630
+ }
9247
9631
  async function listTools(args) {
9248
9632
  const client = new DeeplineClient();
9249
- const items = (await client.listTools()).map(toListedTool);
9633
+ const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
9634
+ const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
9635
+ const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
9636
+ const items = (await client.listTools({
9637
+ ...categoryFilter ? { categories: categoryFilter } : {}
9638
+ })).map(toListedTool).filter(
9639
+ (item) => requestedCategories.length === 0 || requestedCategories.some((category) => item.categories.includes(category))
9640
+ );
9250
9641
  const render = {
9251
9642
  sections: [
9252
9643
  {
@@ -9269,6 +9660,9 @@ async function listTools(args) {
9269
9660
  {
9270
9661
  tools: items,
9271
9662
  count: items.length,
9663
+ filters: {
9664
+ categories: requestedCategories
9665
+ },
9272
9666
  commandTemplates: TOOL_COMMAND_TEMPLATES,
9273
9667
  render
9274
9668
  },
@@ -9295,6 +9689,56 @@ async function searchTools(queryInput, options = {}) {
9295
9689
  });
9296
9690
  return 0;
9297
9691
  }
9692
+ async function grepTools(queryInput, options = {}) {
9693
+ const query = queryInput.trim();
9694
+ if (!query) {
9695
+ console.error("Usage: deepline tools grep <query> [--json]");
9696
+ return 1;
9697
+ }
9698
+ const client = new DeeplineClient();
9699
+ const requestedCategories = options.categories ? options.categories.split(",").map((item) => item.trim()).filter(Boolean) : [];
9700
+ const mode = options.mode ?? "all";
9701
+ const tools = (await client.listTools({
9702
+ grep: query,
9703
+ grepMode: mode,
9704
+ ...options.categories ? { categories: options.categories } : {}
9705
+ })).map(toListedTool).filter(
9706
+ (item) => requestedCategories.length === 0 || requestedCategories.some((category) => item.categories.includes(category))
9707
+ ).filter(
9708
+ (item) => matchesGrepQuery(
9709
+ {
9710
+ id: item.toolId,
9711
+ toolId: item.toolId,
9712
+ provider: item.provider,
9713
+ displayName: item.displayName,
9714
+ description: item.description,
9715
+ categories: item.categories,
9716
+ operation: item.operation,
9717
+ operationAliases: item.operationAliases,
9718
+ inputFields: item.inputFields
9719
+ },
9720
+ query,
9721
+ mode
9722
+ )
9723
+ );
9724
+ printCommandEnvelope(
9725
+ {
9726
+ tools,
9727
+ count: tools.length,
9728
+ query,
9729
+ grep: {
9730
+ mode,
9731
+ terms: parseGrepTerms(query, mode)
9732
+ },
9733
+ filters: {
9734
+ categories: requestedCategories
9735
+ },
9736
+ commandTemplates: TOOL_COMMAND_TEMPLATES
9737
+ },
9738
+ { json: options.json || shouldEmitJson() }
9739
+ );
9740
+ return 0;
9741
+ }
9298
9742
  function playIdentifiers(play) {
9299
9743
  return [play.name, play.reference, ...play.aliases ?? []].filter((value) => Boolean(value?.trim())).map((value) => value.trim());
9300
9744
  }
@@ -9364,24 +9808,27 @@ Notes:
9364
9808
 
9365
9809
  Examples:
9366
9810
  deepline tools list
9367
- deepline tools list --json
9811
+ deepline tools list --categories email_finder --json
9368
9812
  deepline tools search email --json
9369
9813
  `
9370
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9814
+ ).option("--categories <categories>", "Comma-separated categories to filter inventory").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
9371
9815
  process.exitCode = await listTools([
9816
+ ...options.categories ? ["--categories", options.categories] : [],
9372
9817
  ...options.json ? ["--json"] : []
9373
9818
  ]);
9374
9819
  });
9375
- tools.command("search <query>").description("Search available tools.").addHelpText(
9820
+ const addToolSearchCommand = (command) => command.description("Search available tools.").addHelpText(
9376
9821
  "after",
9377
9822
  `
9378
9823
  Notes:
9379
9824
  Ranked discovery for atomic provider/API operations. Results include tool ids
9380
9825
  that can be passed to deepline tools describe or deepline tools execute.
9826
+ The grep alias is the same ranked retrieval surface with a more literal name
9827
+ for agents that are filtering a registry rather than choosing a workflow.
9381
9828
 
9382
9829
  Examples:
9383
9830
  deepline tools search email
9384
- deepline tools search "company enrichment" --categories enrichment --json
9831
+ deepline tools grep "company enrichment" --categories enrichment --json
9385
9832
  deepline tools search verifier --search-mode v2 --json
9386
9833
  `
9387
9834
  ).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) => {
@@ -9393,24 +9840,55 @@ Examples:
9393
9840
  includeSearchDebug: Boolean(options.includeSearchDebug)
9394
9841
  });
9395
9842
  });
9843
+ addToolSearchCommand(tools.command("search <query>"));
9844
+ tools.command("grep <query>").description("Literal grep over tool ids, descriptions, categories, and input fields.").addHelpText(
9845
+ "after",
9846
+ `
9847
+ Notes:
9848
+ Literal registry filtering. Terms are matched case-insensitively against tool
9849
+ ids, provider, display name, description, categories, aliases, and input
9850
+ fields. Use --mode phrase for exact phrase matching, --mode any for OR, and
9851
+ the default --mode all for AND.
9852
+
9853
+ Examples:
9854
+ deepline tools grep email --categories email_finder --json
9855
+ deepline tools grep "phone validate" --mode all --json
9856
+ deepline tools grep hunter --mode phrase --json
9857
+ `
9858
+ ).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) => {
9859
+ const mode = options.mode === "any" || options.mode === "phrase" ? options.mode : "all";
9860
+ process.exitCode = await grepTools(query, {
9861
+ json: options.json,
9862
+ categories: options.categories,
9863
+ mode
9864
+ });
9865
+ });
9396
9866
  const addToolMetadataCommand = (command) => command.description("Show metadata for a tool.").addHelpText(
9397
9867
  "after",
9398
9868
  `
9399
9869
  Notes:
9400
- Shows the tool contract, input schema, output schema, Deepline cost, aliases,
9401
- and metadata. describe is the supported discovery verb. get is removed in
9402
- the V2 SDK CLI; use describe for the same metadata surface.
9870
+ Shows the compact agent contract by default: what the tool does, cost,
9871
+ required inputs, play getters, and a runnable ctx.tools.execute snippet.
9872
+ Use --json for the full metadata/debug payload.
9403
9873
 
9404
9874
  Examples:
9405
9875
  deepline tools describe hunter_email_verifier
9406
- deepline tools describe hunter_email_verifier --json | jq '.inputSchema'
9876
+ deepline tools describe hunter_email_verifier --pricing-only
9877
+ deepline tools describe hunter_email_verifier --schema-only
9878
+ deepline tools describe hunter_email_verifier --examples-only
9879
+ deepline tools describe hunter_email_verifier --json
9407
9880
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
9408
9881
  `
9409
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
9410
- process.exitCode = await getTool([
9411
- toolId,
9412
- ...options.json ? ["--json"] : []
9413
- ]);
9882
+ ).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 Option("--compact", "Compatibility alias for the default compact view").hideHelp()).addOption(new Option("--contract-json", "Compatibility alias for compact contract JSON").hideHelp()).action(async (toolId, options) => {
9883
+ process.exitCode = await getTool(toolId, {
9884
+ json: Boolean(options.json),
9885
+ compact: Boolean(options.compact),
9886
+ contractJson: Boolean(options.contractJson),
9887
+ pricingOnly: Boolean(options.pricingOnly),
9888
+ schemaOnly: Boolean(options.schemaOnly),
9889
+ examplesOnly: Boolean(options.examplesOnly),
9890
+ gettersOnly: Boolean(options.gettersOnly)
9891
+ });
9414
9892
  });
9415
9893
  addToolMetadataCommand(tools.command("describe <toolId>"));
9416
9894
  tools.command("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
@@ -9463,8 +9941,7 @@ Examples:
9463
9941
  process.exitCode = await executeTool(args);
9464
9942
  });
9465
9943
  }
9466
- async function getTool(args) {
9467
- const toolId = args[0];
9944
+ async function getTool(toolId, options = {}) {
9468
9945
  if (!toolId) {
9469
9946
  console.error("Usage: deepline tools get <toolId> [--json]");
9470
9947
  return 1;
@@ -9481,14 +9958,264 @@ async function getTool(args) {
9481
9958
  }
9482
9959
  throw error;
9483
9960
  }
9484
- if (argsWantJson(args)) {
9961
+ if (options.contractJson) {
9962
+ process.stdout.write(`${JSON.stringify(toolContractJsonForDescribe(tool, toolId))}
9963
+ `);
9964
+ return 0;
9965
+ }
9966
+ const emitJson = options.json === true;
9967
+ if (emitJson) {
9485
9968
  process.stdout.write(`${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
9486
9969
  `);
9487
9970
  return 0;
9488
9971
  }
9489
- printToolDetails(tool, toolId);
9972
+ const onlyModes = [
9973
+ options.pricingOnly,
9974
+ options.schemaOnly,
9975
+ options.examplesOnly,
9976
+ options.gettersOnly
9977
+ ].filter(Boolean).length;
9978
+ if (onlyModes > 1) {
9979
+ console.error("Use only one of --pricing-only, --schema-only, --examples-only, or --getters-only.");
9980
+ return 2;
9981
+ }
9982
+ if (options.pricingOnly) {
9983
+ printToolPricingOnly(tool, toolId);
9984
+ return 0;
9985
+ }
9986
+ if (options.schemaOnly) {
9987
+ printToolSchemaOnly(tool, toolId);
9988
+ return 0;
9989
+ }
9990
+ if (options.examplesOnly) {
9991
+ printToolExamplesOnly(tool, toolId);
9992
+ return 0;
9993
+ }
9994
+ if (options.gettersOnly) {
9995
+ printToolGettersOnly(tool, toolId);
9996
+ return 0;
9997
+ }
9998
+ if (options.compact) {
9999
+ printCompactToolContract(tool, toolId);
10000
+ return 0;
10001
+ }
10002
+ if (shouldEmitJson()) {
10003
+ process.stdout.write(`${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
10004
+ `);
10005
+ return 0;
10006
+ }
10007
+ printCompactToolContract(tool, toolId);
9490
10008
  return 0;
9491
10009
  }
10010
+ function toolContractJsonForDescribe(tool, requestedToolId) {
10011
+ const toolId = String(tool.toolId || requestedToolId);
10012
+ const inputFields = toolInputFieldsForDisplay(recordField(tool, "inputSchema", "input_schema"));
10013
+ const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
10014
+ const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
10015
+ const extractedLists = extractionContractEntries(
10016
+ arrayField(toolExecutionResult, "extractedLists", "extracted_lists")
10017
+ );
10018
+ const extractedValues = extractionContractEntries(
10019
+ arrayField(toolExecutionResult, "extractedValues", "extracted_values")
10020
+ );
10021
+ const cost = recordField(tool, "cost");
10022
+ const deeplineCredits = numberField(tool, "deeplineCreditsPerPricingUnit", "deepline_credits_per_pricing_unit");
10023
+ const deeplineUsdPerPricingUnit = numberField(tool, "deeplineUsdPerPricingUnit", "deepline_usd_per_pricing_unit");
10024
+ return {
10025
+ schemaVersion: 1,
10026
+ toolId,
10027
+ provider: tool.provider,
10028
+ displayName: tool.displayName,
10029
+ description: tool.description,
10030
+ categories: tool.categories,
10031
+ inputFields: inputFields.map((field) => ({
10032
+ name: field.name,
10033
+ type: field.type ?? "unknown",
10034
+ required: Boolean(field.required),
10035
+ ...field.description ? { description: field.description } : {},
10036
+ ...Object.prototype.hasOwnProperty.call(field, "default") ? { default: field.default } : {}
10037
+ })),
10038
+ cost: {
10039
+ pricingModel: stringField(cost, "pricingModel", "pricing_model") || null,
10040
+ billingMode: stringField(cost, "billingMode", "billing_mode") || null,
10041
+ deeplineCreditsPerPricingUnit: deeplineCredits,
10042
+ deeplineUsdPerPricingUnit
10043
+ },
10044
+ getters: {
10045
+ extractedLists,
10046
+ extractedValues
10047
+ },
10048
+ executeCommand: `deepline tools execute ${toolId} --input '{...}' --json`
10049
+ };
10050
+ }
10051
+ function extractionContractEntries(entries) {
10052
+ return entries.flatMap((entry) => {
10053
+ if (!isRecord4(entry)) return [];
10054
+ const name = stringField(entry, "name");
10055
+ const expression = stringField(entry, "expression");
10056
+ return name && expression ? [{ name, expression }] : [];
10057
+ });
10058
+ }
10059
+ function printCompactToolContract(tool, requestedToolId) {
10060
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10061
+ const cost = isRecord4(contract.cost) ? contract.cost : {};
10062
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10063
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10064
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10065
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10066
+ console.log(String(contract.toolId));
10067
+ if (contract.displayName) console.log(`Best for: ${contract.displayName}`);
10068
+ if (typeof contract.description === "string" && contract.description.trim()) {
10069
+ console.log(`Description: ${contract.description.trim()}`);
10070
+ }
10071
+ if (Array.isArray(contract.categories) && contract.categories.length) {
10072
+ console.log(`Tags: ${contract.categories.join(", ")}`);
10073
+ }
10074
+ printToolPricingOnly(tool, requestedToolId, { heading: "Cost" });
10075
+ if (inputFields.length) {
10076
+ console.log("");
10077
+ console.log("Inputs:");
10078
+ for (const field of inputFields) {
10079
+ if (!isRecord4(field)) continue;
10080
+ const name = stringField(field, "name");
10081
+ if (!name) continue;
10082
+ const required = field.required ? "*" : "";
10083
+ const type = stringField(field, "type") || "unknown";
10084
+ const description = stringField(field, "description");
10085
+ console.log(`- ${name}${required}: ${type}${description ? ` - ${description}` : ""}`);
10086
+ }
10087
+ }
10088
+ console.log("");
10089
+ printToolExamplesOnly(tool, requestedToolId, { includeSamples: false });
10090
+ if (listGetters.length || valueGetters.length) {
10091
+ console.log("");
10092
+ console.log("Getters:");
10093
+ if (listGetters.length) console.log("Lists:");
10094
+ for (const entry of listGetters) {
10095
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10096
+ }
10097
+ if (valueGetters.length) console.log("Values:");
10098
+ for (const entry of valueGetters) {
10099
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10100
+ }
10101
+ }
10102
+ console.log("");
10103
+ console.log(`More: deepline tools describe ${contract.toolId} --pricing-only | --schema-only | --examples-only | --getters-only | --json`);
10104
+ }
10105
+ function printToolPricingOnly(tool, requestedToolId, options = {}) {
10106
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10107
+ const cost = isRecord4(contract.cost) ? contract.cost : {};
10108
+ const pricingModel = stringField(cost, "pricingModel") || "unknown";
10109
+ const billingMode = stringField(cost, "billingMode") || "unknown";
10110
+ const unit = pricingModel === "per_page" ? "page" : pricingModel === "per_result" ? "result" : pricingModel === "fixed" ? "call" : pricingModel.replace(/^per_/, "") || "unit";
10111
+ const credits = numberField(cost, "deeplineCreditsPerPricingUnit");
10112
+ const usd = numberField(cost, "deeplineUsdPerPricingUnit");
10113
+ const price = credits !== null ? `${formatDecimal(credits)} Deepline credits${usd !== null ? ` / ${formatUsd(usd)}` : ""} per ${unit}` : "pricing unavailable";
10114
+ console.log(`${options.heading ?? `Pricing: ${contract.toolId}`}: ${price}`);
10115
+ console.log(`Billing: ${billingMode}`);
10116
+ }
10117
+ function printToolSchemaOnly(tool, requestedToolId) {
10118
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10119
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10120
+ console.log(`Schema: ${contract.toolId}`);
10121
+ if (!inputFields.length) {
10122
+ console.log("Inputs: none");
10123
+ return;
10124
+ }
10125
+ console.log("Inputs:");
10126
+ for (const field of inputFields) {
10127
+ if (!isRecord4(field)) continue;
10128
+ const name = stringField(field, "name");
10129
+ if (!name) continue;
10130
+ const required = field.required ? "*" : "";
10131
+ const type = stringField(field, "type") || "unknown";
10132
+ const description = stringField(field, "description");
10133
+ const defaultSuffix = Object.prototype.hasOwnProperty.call(field, "default") ? ` default=${JSON.stringify(field.default)}` : "";
10134
+ console.log(`- ${name}${required}: ${type}${defaultSuffix}${description ? ` - ${description}` : ""}`);
10135
+ }
10136
+ }
10137
+ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
10138
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10139
+ const toolId = String(contract.toolId);
10140
+ const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
10141
+ const sampleInput = Object.fromEntries(
10142
+ inputFields.slice(0, 4).flatMap((field) => {
10143
+ if (!isRecord4(field)) return [];
10144
+ const name = stringField(field, "name");
10145
+ if (!name) return [];
10146
+ return [[name, sampleValueForField(field)]];
10147
+ })
10148
+ );
10149
+ console.log(`Use in a play:`);
10150
+ console.log("```ts");
10151
+ console.log("const result = await ctx.tools.execute({");
10152
+ console.log(` id: '${stableStepIdForTool(toolId)}',`);
10153
+ console.log(` tool: '${toolId}',`);
10154
+ console.log(` input: ${JSON.stringify(sampleInput || {}, null, 2).replace(/\n/g, "\n ")},`);
10155
+ console.log("});");
10156
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10157
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10158
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10159
+ const firstGetter = [...valueGetters, ...listGetters].find(isRecord4);
10160
+ if (firstGetter) {
10161
+ const name = stringField(firstGetter, "name") || "value";
10162
+ const expression = stringField(firstGetter, "expression");
10163
+ if (expression) console.log(`const ${safeIdentifier(name)} = ${expression.replace(/^toolExecutionResult\./, "result.")};`);
10164
+ }
10165
+ console.log("```");
10166
+ if (options.includeSamples !== false) {
10167
+ const samples = recordField(tool, "samples");
10168
+ printSamples(samples);
10169
+ }
10170
+ }
10171
+ function printToolGettersOnly(tool, requestedToolId) {
10172
+ const contract = toolContractJsonForDescribe(tool, requestedToolId);
10173
+ const getters = isRecord4(contract.getters) ? contract.getters : {};
10174
+ const listGetters = Array.isArray(getters.extractedLists) ? getters.extractedLists : [];
10175
+ const valueGetters = Array.isArray(getters.extractedValues) ? getters.extractedValues : [];
10176
+ console.log(`Getters: ${contract.toolId}`);
10177
+ if (!listGetters.length && !valueGetters.length) {
10178
+ console.log("No generated getters declared. Use --json only if you need raw metadata.");
10179
+ return;
10180
+ }
10181
+ if (listGetters.length) {
10182
+ console.log("Lists:");
10183
+ for (const entry of listGetters) {
10184
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10185
+ }
10186
+ }
10187
+ if (valueGetters.length) {
10188
+ console.log("Values:");
10189
+ for (const entry of valueGetters) {
10190
+ if (isRecord4(entry)) console.log(`- ${stringField(entry, "name")}: ${playResultExpression(entry)}`);
10191
+ }
10192
+ }
10193
+ }
10194
+ function sampleValueForField(field) {
10195
+ const name = stringField(field, "name").toLowerCase();
10196
+ const type = stringField(field, "type").toLowerCase();
10197
+ if (Object.prototype.hasOwnProperty.call(field, "default")) return field.default;
10198
+ if (name.includes("email")) return "ada@example.com";
10199
+ if (name.includes("domain") || name.includes("website")) return "example.com";
10200
+ if (name.includes("first")) return "Ada";
10201
+ if (name.includes("last")) return "Lovelace";
10202
+ if (name.includes("name")) return "Ada Lovelace";
10203
+ if (type === "integer" || type === "number") return 1;
10204
+ if (type === "boolean") return true;
10205
+ if (type === "array") return [];
10206
+ if (type === "object") return {};
10207
+ return "...";
10208
+ }
10209
+ function stableStepIdForTool(toolId) {
10210
+ return toolId.replace(/^[a-z0-9]+_/, "").replace(/[^a-z0-9_]+/gi, "_") || "tool_call";
10211
+ }
10212
+ function safeIdentifier(name) {
10213
+ const cleaned = name.replace(/[^a-zA-Z0-9_$]+/g, "_").replace(/^[^a-zA-Z_$]+/, "");
10214
+ return cleaned || "value";
10215
+ }
10216
+ function playResultExpression(entry) {
10217
+ return stringField(entry, "expression").replace(/^toolExecutionResult\./, "result.");
10218
+ }
9492
10219
  function toolMetadataJsonForDescribe(tool, requestedToolId) {
9493
10220
  const toolId = String(tool.toolId || requestedToolId);
9494
10221
  const {
@@ -9519,152 +10246,6 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
9519
10246
  }
9520
10247
  };
9521
10248
  }
9522
- function printToolDetails(tool, requestedToolId) {
9523
- const toolId = String(tool.toolId || requestedToolId);
9524
- const operation = typeof tool.operation === "string" ? tool.operation : "";
9525
- const displayBase = operation && operation.startsWith(`${tool.provider}_`) ? operation.slice(String(tool.provider).length + 1) : operation ? `${tool.provider} ${operation}`.trim() : toolId;
9526
- const displayName = titleCase(displayBase || String(tool.displayName || toolId));
9527
- const cost = isRecord4(tool.cost) ? tool.cost : null;
9528
- const pricing = recordField(tool, "pricing");
9529
- const deeplineCredits = numberField(tool, "deeplineCreditsPerPricingUnit", "deepline_credits_per_pricing_unit");
9530
- const deeplineUsdPerPricingUnit = numberField(tool, "deeplineUsdPerPricingUnit", "deepline_usd_per_pricing_unit");
9531
- const billingSource = stringField(tool, "billingSource", "billing_source");
9532
- const billingSourceLabel = stringField(tool, "billingSourceLabel", "billing_source_label");
9533
- const estimatedCreditsRange = stringField(tool, "estimatedCreditsRange", "estimated_credits_range");
9534
- const estimatedUsdRange = stringField(tool, "estimatedUsdRange", "estimated_usd_range");
9535
- const estimateModelVersion = stringField(tool, "estimateModelVersion", "estimate_model_version");
9536
- const estimateBasedOnTools = arrayField(tool, "estimateBasedOnTools", "estimate_based_on_tools").map((item) => String(item).trim()).filter(Boolean);
9537
- const stepContributions = arrayField(tool, "stepContributions", "step_contributions");
9538
- const playExpansion = recordField(tool, "playExpansion", "play_expansion");
9539
- const samples = recordField(tool, "samples");
9540
- const usageGuidance = recordField(tool, "usageGuidance", "usage_guidance");
9541
- console.log(`Tool: ${toolId}`);
9542
- console.log(" Runtime output help:");
9543
- console.log(" describe shows declared schema/getters, not an observed provider response");
9544
- console.log(` observe actual shape: deepline tools execute ${toolId} --input '{...}' --json`);
9545
- console.log(" for play getter bugs: run the play, then use the db query commands printed by runs get");
9546
- if (displayName) {
9547
- console.log(" Display name:");
9548
- console.log(` ${displayName}`);
9549
- }
9550
- if (tool.categories.length > 0) {
9551
- console.log(" Categories:");
9552
- console.log(` ${tool.categories.join(", ")}`);
9553
- }
9554
- const printedCost = printToolCost({
9555
- pricing,
9556
- cost,
9557
- billingSource,
9558
- deeplineCredits,
9559
- deeplineUsdPerPricingUnit
9560
- });
9561
- if (!printedCost && ["run_javascript", "call_local_codex", "call_local_claude_code"].includes(toolId)) {
9562
- console.log(" Cost: free");
9563
- }
9564
- if (billingSourceLabel) {
9565
- console.log(` Billing source: ${billingSourceLabel}`);
9566
- }
9567
- if (estimatedCreditsRange) {
9568
- const usdSuffix = estimatedUsdRange ? ` (~${estimatedUsdRange})` : "";
9569
- console.log(` Estimated play spend: ${estimatedCreditsRange}${usdSuffix}`);
9570
- if (estimateModelVersion) console.log(` model: ${estimateModelVersion}`);
9571
- if (estimateBasedOnTools.length) console.log(` based on: ${estimateBasedOnTools.join(", ")}`);
9572
- if (stepContributions.length) {
9573
- console.log(" step contributions:");
9574
- for (const item of stepContributions) {
9575
- if (!isRecord4(item)) continue;
9576
- const stepTool = typeof item.tool === "string" ? item.tool.trim() : "";
9577
- const low = typeof item.lowCredits === "number" ? item.lowCredits : null;
9578
- const high = typeof item.highCredits === "number" ? item.highCredits : null;
9579
- const lowUsd = typeof item.lowUsd === "number" ? item.lowUsd : null;
9580
- const highUsd = typeof item.highUsd === "number" ? item.highUsd : null;
9581
- if (!stepTool || low === null || high === null) continue;
9582
- const stepUsdSuffix = lowUsd !== null && highUsd !== null ? ` (~${formatUsd(lowUsd)}-${formatUsd(highUsd)})` : "";
9583
- console.log(` - ${stepTool}: ${formatDecimal(low)}-${formatDecimal(high)} credits${stepUsdSuffix}`);
9584
- }
9585
- }
9586
- }
9587
- if (playExpansion && Object.keys(playExpansion).length > 0) {
9588
- const group = typeof playExpansion.group === "string" ? playExpansion.group.trim() : "";
9589
- console.log(" Play expansion:");
9590
- if (group) console.log(` group: ${group}`);
9591
- }
9592
- const fields = toolInputFieldsForDisplay(recordField(tool, "inputSchema", "input_schema"));
9593
- if (fields.length) {
9594
- console.log(" Inputs (operation-specific):");
9595
- for (const field of fields) {
9596
- const name = String(field.name || "");
9597
- const typeName = String(field.type || "unknown");
9598
- const requiredLabel = field.required ? "required" : "optional";
9599
- const defaultSuffix = Object.prototype.hasOwnProperty.call(field, "default") ? `, default: ${JSON.stringify(field.default)}` : "";
9600
- const desc = typeof field.description === "string" && field.description.trim() ? ` - ${field.description.trim()}` : "";
9601
- console.log(` - ${name} (${typeName}, ${requiredLabel}${defaultSuffix})${desc}`);
9602
- }
9603
- console.log(" Tip: pass --payload with a JSON object.");
9604
- }
9605
- printSamples(samples);
9606
- printUsageGuidance(usageGuidance);
9607
- if (isPlayTool(tool)) {
9608
- console.log(" Play contract:");
9609
- console.log(" - This is a deepline-native waterfall; the returned rows are extracted by target getters, not by hand-authored payload shape.");
9610
- if (playExpansion && typeof playExpansion.group === "string" && playExpansion.group.trim()) {
9611
- console.log(` - Output alias/runtime group is: ${playExpansion.group.trim()}`);
9612
- }
9613
- const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult");
9614
- const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9615
- const targets = extractedValues.map((entry) => isRecord4(entry) && typeof entry.name === "string" ? entry.name : "").filter(Boolean).sort();
9616
- if (targets.length) {
9617
- console.log(` - Built-in extract targets: ${targets.join(", ")}`);
9618
- }
9619
- }
9620
- console.log("");
9621
- console.log("Usage:");
9622
- const requestPayload = samplePayload(samples, "request");
9623
- if (isPlayTool(tool)) {
9624
- if (requestPayload !== void 0) {
9625
- console.log(` deepline enrich --with '${JSON.stringify({ alias: "result", tool: toolId, payload: requestPayload })}'`);
9626
- } else {
9627
- console.log(` deepline enrich --with '{"alias":"result","tool":"${toolId}","payload":{...}}'`);
9628
- }
9629
- } else if (requestPayload !== void 0) {
9630
- console.log(` deepline tools execute ${toolId} --payload '${JSON.stringify(requestPayload)}'`);
9631
- } else {
9632
- console.log(` deepline tools execute ${toolId} --payload '{...}'`);
9633
- }
9634
- console.log(" deepline tools describe <tool_id> --json");
9635
- }
9636
- function printUsageGuidance(usageGuidance) {
9637
- if (Object.keys(usageGuidance).length === 0) return;
9638
- const execute = stringField(usageGuidance, "execute");
9639
- const toolExecutionResult = recordField(usageGuidance, "toolExecutionResult", "tool_execution_result");
9640
- const toolResponse = recordField(toolExecutionResult, "toolResponse", "tool_response");
9641
- const extractedLists = arrayField(toolExecutionResult, "extractedLists", "extracted_lists");
9642
- const extractedValues = arrayField(toolExecutionResult, "extractedValues", "extracted_values");
9643
- console.log(" Usage guidance:");
9644
- if (execute) console.log(` ${execute}`);
9645
- const raw = stringField(toolResponse, "raw");
9646
- const meta = stringField(toolResponse, "meta");
9647
- if (raw) console.log(` Raw tool response: ${raw}`);
9648
- if (meta) console.log(` Tool response metadata: ${meta}`);
9649
- printExtractions("Extracted lists", extractedLists);
9650
- printExtractions("Extracted values", extractedValues);
9651
- }
9652
- function printExtractions(label, entries) {
9653
- if (!entries.length) return;
9654
- console.log(` ${label}:`);
9655
- for (const entry of entries) {
9656
- if (!isRecord4(entry)) continue;
9657
- const name = stringField(entry, "name");
9658
- const expression = stringField(entry, "expression");
9659
- const details = recordField(entry, "details");
9660
- const rawToolOutputPaths = arrayField(details, "rawToolOutputPaths", "raw_tool_output_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9661
- const candidatePaths = arrayField(details, "candidatePaths", "candidate_paths").map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
9662
- if (!name || !expression) continue;
9663
- const paths = candidatePaths.length ? candidatePaths : rawToolOutputPaths;
9664
- const pathSuffix = paths.length ? ` from ${paths.join(", ")}` : "";
9665
- console.log(` - ${name}: ${expression}${pathSuffix}`);
9666
- }
9667
- }
9668
10249
  function singleLineText(value, maxLength = 260) {
9669
10250
  if (typeof value !== "string") return "";
9670
10251
  const text = value.replace(/\s+/g, " ").trim();
@@ -9681,42 +10262,6 @@ function formatListedToolCost(tool) {
9681
10262
  const displayText = stringField(pricing, "displayText", "display_text");
9682
10263
  return displayText ? `Cost: ${displayText}` : "";
9683
10264
  }
9684
- function printToolCost(input) {
9685
- const { pricing, cost, billingSource, deeplineCredits, deeplineUsdPerPricingUnit } = input;
9686
- if (billingSource === "own_provider_credentials") {
9687
- console.log(" Cost: free through Deepline");
9688
- return true;
9689
- }
9690
- const displayText = stringField(pricing, "displayText", "display_text");
9691
- if (displayText) {
9692
- console.log(` Cost: ${displayText}`);
9693
- const details = arrayField(pricing, "details").map((item) => String(item).trim()).filter(Boolean);
9694
- if (details.length) {
9695
- console.log(" notes:");
9696
- for (const detail of details) console.log(` - ${detail}`);
9697
- }
9698
- return true;
9699
- }
9700
- const pricingModel = cost ? typeof cost.pricingModel === "string" ? cost.pricingModel : typeof cost.pricing_model === "string" ? cost.pricing_model : "" : "";
9701
- const billingMode = cost ? typeof cost.billingMode === "string" ? cost.billingMode : typeof cost.billing_mode === "string" ? cost.billing_mode : "" : "";
9702
- if (deeplineCredits === 0) {
9703
- console.log(" Cost: Free");
9704
- return true;
9705
- }
9706
- if (pricingModel && deeplineCredits !== null) {
9707
- const unit = pricingModel === "per_page" ? "page" : pricingModel === "per_result" ? "result" : "call";
9708
- const usdText = deeplineUsdPerPricingUnit !== null ? ` / ${formatUsd(deeplineUsdPerPricingUnit)}` : "";
9709
- const billingSuffix = billingMode ? ` (${billingMode})` : "";
9710
- console.log(` Cost: ${formatDecimal(deeplineCredits)} Deepline credits${usdText} per ${unit}${billingSuffix}`);
9711
- return true;
9712
- }
9713
- const summary = stringField(pricing, "summary");
9714
- if (summary) {
9715
- console.log(` Cost: ${summary}`);
9716
- return true;
9717
- }
9718
- return false;
9719
- }
9720
10265
  function toolInputFieldsForDisplay(inputSchema) {
9721
10266
  if (Array.isArray(inputSchema.fields)) return inputSchema.fields.filter(isRecord4);
9722
10267
  const jsonSchema = isRecord4(inputSchema.jsonSchema) ? inputSchema.jsonSchema : inputSchema;
@@ -9767,17 +10312,6 @@ function listExtractorPathsFromUsageGuidance(tool) {
9767
10312
  ).filter(Boolean);
9768
10313
  });
9769
10314
  }
9770
- function isPlayTool(tool) {
9771
- const provider = typeof tool.provider === "string" ? tool.provider : "";
9772
- return provider === "deepline_native" && Boolean(recordField(tool, "playExpansion", "play_expansion"));
9773
- }
9774
- function titleCase(value) {
9775
- return value.replace(/[_-]+/g, " ").split(" ").filter(Boolean).map((part) => {
9776
- const lower = part.toLowerCase();
9777
- const special = { linkedin: "LinkedIn", crm: "CRM", api: "API" };
9778
- return special[lower] ?? `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`;
9779
- }).join(" ");
9780
- }
9781
10315
  function formatDecimal(value) {
9782
10316
  const text = value.toFixed(12).replace(/0+$/, "").replace(/\.$/, "");
9783
10317
  return text || "0";
@@ -10537,6 +11071,61 @@ function shouldDeferSkillsSyncForCommand() {
10537
11071
  const subcommand = args[1];
10538
11072
  return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
10539
11073
  }
11074
+ async function runPlayRunnerHealthCheck() {
11075
+ const dir = await mkdtemp(join11(tmpdir4(), "deepline-health-play-"));
11076
+ const file = join11(dir, "health-check.play.ts");
11077
+ try {
11078
+ await writeFile4(
11079
+ file,
11080
+ [
11081
+ "import { definePlay } from 'deepline';",
11082
+ "",
11083
+ "export default definePlay('health-check', async (ctx) => {",
11084
+ " const rows = await ctx",
11085
+ " .map('health_rows', [{ id: 'a' }, { id: 'b' }])",
11086
+ " .step('echo', (row) => ({ ok: true, id: row.id }))",
11087
+ " .run({ key: 'id' });",
11088
+ " return { ok: true, rows, source: 'deepline health --play-runner' };",
11089
+ "});",
11090
+ ""
11091
+ ].join("\n"),
11092
+ "utf8"
11093
+ );
11094
+ let capturedOutput = "";
11095
+ const originalWrite = process.stdout.write.bind(process.stdout);
11096
+ process.stdout.write = ((chunk, ...args) => {
11097
+ capturedOutput += typeof chunk === "string" ? chunk : String(chunk);
11098
+ return true;
11099
+ });
11100
+ let exitCode = 1;
11101
+ try {
11102
+ exitCode = await handlePlayRun([
11103
+ file,
11104
+ "--input",
11105
+ "{}",
11106
+ "--watch",
11107
+ "--no-open",
11108
+ "--json"
11109
+ ]);
11110
+ } finally {
11111
+ process.stdout.write = originalWrite;
11112
+ }
11113
+ if (exitCode !== 0) {
11114
+ throw new Error(
11115
+ `play runner canary exited ${exitCode}: ${capturedOutput.slice(0, 1e3)}`
11116
+ );
11117
+ }
11118
+ return {
11119
+ status: "ok",
11120
+ playRunner: {
11121
+ status: "ok",
11122
+ check: "no-provider local play run completed"
11123
+ }
11124
+ };
11125
+ } finally {
11126
+ await rm(dir, { recursive: true, force: true });
11127
+ }
11128
+ }
10540
11129
  async function main() {
10541
11130
  const mainStartedAt = Date.now();
10542
11131
  recordCliTrace({
@@ -10548,7 +11137,7 @@ async function main() {
10548
11137
  if (printStartupPhase) {
10549
11138
  progress?.phase("loading deepline cli");
10550
11139
  }
10551
- const program = new Command2();
11140
+ const program = new Command3();
10552
11141
  program.name("deepline").description("Deepline CLI (TypeScript SDK)").version(SDK_VERSION, "-v, --version", "Show version").showHelpAfterError().showSuggestionAfterError(true).addHelpText(
10553
11142
  "after",
10554
11143
  `
@@ -10557,7 +11146,7 @@ Common commands:
10557
11146
  deepline auth status --json
10558
11147
  deepline plays search email --json
10559
11148
  deepline plays describe person-linkedin-to-email --json
10560
- deepline plays run my.play.ts --input '{"domain":"stripe.com"}' --watch
11149
+ deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
10561
11150
  deepline tools execute hunter_email_verifier --input '{"email":"a@b.com"}'
10562
11151
  deepline update
10563
11152
 
@@ -10613,18 +11202,30 @@ Exit codes:
10613
11202
  registerDbCommands(program);
10614
11203
  registerFeedbackCommands(program);
10615
11204
  registerUpdateCommand(program);
10616
- program.command("health").description("Check server health.").option("--json", "Force JSON output.").addHelpText(
11205
+ program.command("health").description("Check server health.").option("--json", "Force JSON output.").option(
11206
+ "--play-runner",
11207
+ "Run a tiny no-provider play to verify the full play execution plane."
11208
+ ).addHelpText(
10617
11209
  "after",
10618
11210
  `
10619
11211
  Notes:
10620
11212
  Read-only connectivity check for the configured Deepline host. Prints the raw
10621
11213
  server health payload as JSON.
11214
+ Add --play-runner to verify bundling, coordinator dispatch, runtime callbacks,
11215
+ and run streaming with a tiny no-provider play.
10622
11216
 
10623
11217
  Examples:
10624
11218
  deepline health
11219
+ deepline health --play-runner
10625
11220
  `
10626
- ).action(async () => {
11221
+ ).action(async (options) => {
10627
11222
  try {
11223
+ if (options.playRunner) {
11224
+ const data2 = await runPlayRunnerHealthCheck();
11225
+ process.stdout.write(`${JSON.stringify(data2, null, 2)}
11226
+ `);
11227
+ return;
11228
+ }
10628
11229
  const client = new DeeplineClient();
10629
11230
  const data = await client.health();
10630
11231
  process.stdout.write(`${JSON.stringify(data, null, 2)}