deepline 0.1.76 → 0.1.78

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
@@ -229,10 +229,10 @@ var import_node_path2 = require("path");
229
229
 
230
230
  // src/release.ts
231
231
  var SDK_RELEASE = {
232
- version: "0.1.76",
232
+ version: "0.1.78",
233
233
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
234
234
  supportPolicy: {
235
- latest: "0.1.76",
235
+ latest: "0.1.78",
236
236
  minimumSupported: "0.1.53",
237
237
  deprecatedBelow: "0.1.53"
238
238
  }
@@ -402,8 +402,8 @@ var HttpClient = class {
402
402
  if (lastError instanceof DeeplineError) {
403
403
  throw lastError;
404
404
  }
405
- const errorMessage2 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
406
- throw new DeeplineError(errorMessage2);
405
+ const errorMessage3 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
406
+ throw new DeeplineError(errorMessage3);
407
407
  }
408
408
  /**
409
409
  * Send a GET request.
@@ -808,6 +808,7 @@ var DeeplineClient = class {
808
808
  aliases,
809
809
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
810
810
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
811
+ staticPipeline: isRecord(play.staticPipeline) ? play.staticPipeline : isRecord(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
811
812
  ...csvInput ? { csvInput } : {},
812
813
  ...rowOutputSchema ? { rowOutputSchema } : {},
813
814
  runCommand: runCommand2,
@@ -4033,6 +4034,51 @@ function customerDbRows(result) {
4033
4034
  function customerDbColumnNames(result) {
4034
4035
  return result.columns.map((column) => column.name).filter(Boolean);
4035
4036
  }
4037
+ function errorMessage(value) {
4038
+ return value instanceof Error ? value.message : String(value ?? "");
4039
+ }
4040
+ function collectErrorText(value) {
4041
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4042
+ return errorMessage(value);
4043
+ }
4044
+ const record = value;
4045
+ return [
4046
+ errorMessage(value),
4047
+ typeof record.error === "string" ? record.error : "",
4048
+ typeof record.message === "string" ? record.message : "",
4049
+ typeof record.detail === "string" ? record.detail : "",
4050
+ typeof record.hint === "string" ? record.hint : "",
4051
+ typeof record.code === "string" ? record.code : "",
4052
+ record.response ? collectErrorText(record.response) : "",
4053
+ record.details ? collectErrorText(record.details) : ""
4054
+ ].filter(Boolean).join("\n");
4055
+ }
4056
+ function formatDbQueryError(sql, error) {
4057
+ const text = collectErrorText(error);
4058
+ const lower = text.toLowerCase();
4059
+ const referencesStorage = /"storage"\.|storage\./i.test(sql);
4060
+ const runIdColumnMissing = /column\s+"?run_id"?\s+does not exist/i.test(text) || /run_id.*does not exist/i.test(lower);
4061
+ const relationMissing = /relation\s+["']?[^"']*storage[^"']*["']?\s+does not exist/i.test(text) || /42p01/i.test(text);
4062
+ if (referencesStorage && relationMissing) {
4063
+ return [
4064
+ "Customer DB query failed: the referenced storage table does not exist.",
4065
+ "Play map tables are created only when the corresponding ctx.map(...).run(...) call executes. Pilot branches, early returns, and runs that fail before the map do not create that table.",
4066
+ "Use `deepline runs get <run-id> --full --json` to inspect returned dataset handles, then export them with `deepline runs export <run-id> --dataset result.rows --out rows.csv`.",
4067
+ `Original error: ${errorMessage(error)}`
4068
+ ].join("\n");
4069
+ }
4070
+ if (referencesStorage && runIdColumnMissing) {
4071
+ return [
4072
+ "Customer DB query failed: storage map tables use `_run_id`, not `run_id`.",
4073
+ "Prefer `deepline runs export <run-id> --dataset result.rows --out rows.csv` unless you are doing deep table debugging.",
4074
+ `Original error: ${errorMessage(error)}`
4075
+ ].join("\n");
4076
+ }
4077
+ if (error instanceof DeeplineError) {
4078
+ return error.message;
4079
+ }
4080
+ return errorMessage(error);
4081
+ }
4036
4082
  function writeCustomerDbCsv(result, outPath) {
4037
4083
  const resolved = (0, import_node_path7.resolve)(outPath);
4038
4084
  (0, import_node_fs6.writeFileSync)(
@@ -4093,7 +4139,13 @@ async function handleDbQuery(args) {
4093
4139
  const jsonOutput = argsWantJson(args);
4094
4140
  const explicitJsonOutput = args.includes("--json");
4095
4141
  const client = new DeeplineClient();
4096
- const result = await client.queryCustomerDb({ sql, maxRows });
4142
+ let result;
4143
+ try {
4144
+ result = await client.queryCustomerDb({ sql, maxRows });
4145
+ } catch (error) {
4146
+ console.error(formatDbQueryError(sql, error));
4147
+ return 1;
4148
+ }
4097
4149
  const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify(
4098
4150
  {
4099
4151
  sql,
@@ -4190,13 +4242,18 @@ function registerDbCommands(program) {
4190
4242
  "after",
4191
4243
  `
4192
4244
  Notes:
4193
- Runs SQL against the active workspace customer database through Deepline APIs.
4245
+ Agent-safe SQL for the active workspace customer database.
4246
+ Reads: SELECT, EXPLAIN, and read-only WITH can inspect permitted schemas.
4247
+ Writes: CREATE TABLE, INSERT, UPDATE, DELETE, ALTER, DROP, TRUNCATE, and
4248
+ CREATE INDEX must target schema-qualified storage tables, such as storage.agent_notes.
4249
+ If CREATE TABLE blocked (...) fails, use CREATE TABLE storage.blocked (...).
4194
4250
  Results are bounded by the server and --max-rows. Use --json for stable output.
4195
4251
  Use --format csv or --format markdown for agent-readable exports and display tables.
4196
4252
 
4197
4253
  Examples:
4198
4254
  deepline db query --sql "select * from companies limit 20"
4199
4255
  deepline db query --sql "select domain, name from companies limit 20" --json
4256
+ deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
4200
4257
  deepline db query --sql "select * from contacts" --max-rows 100 --json
4201
4258
  deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
4202
4259
  deepline db query --sql "select domain, name from companies limit 20" --format markdown
@@ -4208,12 +4265,16 @@ Examples:
4208
4265
  Notes:
4209
4266
  Requires --sql. Output is a compact table in a terminal and raw JSON with
4210
4267
  --json or when stdout is piped. The active auth workspace determines scope.
4268
+ Read permitted schemas with SELECT, EXPLAIN, or read-only WITH.
4269
+ Write only to schema-qualified storage tables. For example, use
4270
+ CREATE TABLE storage.blocked (...) instead of CREATE TABLE blocked (...).
4211
4271
  --format csv and --format markdown are explicit data/display formats and can
4212
4272
  be written directly with --out.
4213
4273
 
4214
4274
  Examples:
4215
4275
  deepline db query --sql "select * from companies limit 20"
4216
4276
  deepline db query --sql "select domain, name from companies limit 20" --json
4277
+ deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
4217
4278
  deepline db psql --sql "select count(*) from contacts" --json
4218
4279
  deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
4219
4280
  deepline db query --sql "select domain, name from companies limit 20" --format markdown
@@ -6073,19 +6134,19 @@ function playBootstrapTemplateConfig(template) {
6073
6134
  function templateExample(template) {
6074
6135
  switch (template) {
6075
6136
  case "people-list":
6076
- return "deepline plays bootstrap people-list --from provider:dropleads_search_people > people.play.ts";
6137
+ return "deepline plays bootstrap people-list --from provider:dropleads_search_people --out people.play.ts";
6077
6138
  case "company-list":
6078
- return "deepline plays bootstrap company-list --from provider:apollo_company_search > companies.play.ts";
6139
+ return "deepline plays bootstrap company-list --from provider:apollo_company_search --out companies.play.ts";
6079
6140
  case "people-email":
6080
- return "deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall > email-flow.play.ts";
6141
+ return "deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall --out email-flow.play.ts";
6081
6142
  case "people-phone":
6082
- return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone > phone-flow.play.ts";
6143
+ return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone --out phone-flow.play.ts";
6083
6144
  case "company-people":
6084
- return "deepline plays bootstrap company-people --from provider:apollo_company_search --using play:prebuilt/company-to-contact > company-people.play.ts";
6145
+ return "deepline plays bootstrap company-people --from provider:apollo_company_search --using play:prebuilt/company-to-contact --out company-people.play.ts";
6085
6146
  case "company-people-email":
6086
- return "deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email providers:hunter_email_finder,leadmagic_email_finder > account-emails.play.ts";
6147
+ return "deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email providers:hunter_email_finder,leadmagic_email_finder --out account-emails.play.ts";
6087
6148
  case "company-people-phone":
6088
- return "deepline plays bootstrap company-people-phone --from provider:apollo_company_search --people play:prebuilt/company-to-contact --phone providers:ai_ark_mobile_phone_finder > account-phones.play.ts";
6149
+ return "deepline plays bootstrap company-people-phone --from provider:apollo_company_search --people play:prebuilt/company-to-contact --phone providers:ai_ark_mobile_phone_finder --out account-phones.play.ts";
6089
6150
  }
6090
6151
  }
6091
6152
  var PLAY_BOOTSTRAP_STAGE_NAMES = [
@@ -6094,19 +6155,22 @@ var PLAY_BOOTSTRAP_STAGE_NAMES = [
6094
6155
  "phone"
6095
6156
  ];
6096
6157
  function playBootstrapUsageLine() {
6097
- return `Usage: deepline plays bootstrap <${formatPlayBootstrapTemplates()}> --from <csv:PATH|play:REF|provider:ID|providers:ID,ID> [--using <play:REF|providers:ID,ID>] [--people play:REF] [--email <play:REF|providers:ID,ID>] [--phone <play:REF|providers:ID,ID>] [--limit 5] > flow.play.ts`;
6158
+ return `Usage: deepline plays bootstrap <${formatPlayBootstrapTemplates()}> --from <csv:PATH|play:REF|provider:ID|providers:ID,ID> [--using <play:REF|providers:ID,ID>] [--people play:REF] [--email <play:REF|providers:ID,ID>] [--phone <play:REF|providers:ID,ID>] [--limit 5] [--out flow.play.ts]`;
6098
6159
  }
6099
6160
  function requireBootstrapTemplate(rawTemplate) {
6100
6161
  if (!rawTemplate) {
6101
6162
  throw new PlayBootstrapUsageError(
6102
6163
  `plays bootstrap needs a route template: ${formatPlayBootstrapTemplates()}.
6103
- Example: deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall > email-flow.play.ts`
6164
+ Example: deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall --out email-flow.play.ts`
6104
6165
  );
6105
6166
  }
6106
6167
  if (!isPlayBootstrapTemplate(rawTemplate)) {
6168
+ const looksLikePlayReference = rawTemplate.includes("/") || rawTemplate.endsWith(".play.ts");
6107
6169
  throw new PlayBootstrapUsageError(
6108
6170
  `Unknown plays bootstrap template: ${rawTemplate}
6109
- Supported templates: ${formatPlayBootstrapTemplates()}`
6171
+ Supported templates: ${formatPlayBootstrapTemplates()}` + (looksLikePlayReference ? `
6172
+
6173
+ "${rawTemplate}" looks like an existing play reference or file, not a bootstrap template. Do not run plays bootstrap on prebuilt/<play-name>. Use "deepline plays run ${rawTemplate} --input '{...}' --watch" to run it directly, "deepline plays describe ${rawTemplate} --json" to inspect its contract, or "deepline plays get ${rawTemplate} --source --out scratchpad.play.ts" to clone/edit it.` : "")
6110
6174
  );
6111
6175
  }
6112
6176
  return rawTemplate;
@@ -6248,7 +6312,8 @@ function parsePlayBootstrapOptions(args) {
6248
6312
  people: null,
6249
6313
  email: null,
6250
6314
  phone: null,
6251
- limit: 5
6315
+ limit: 5,
6316
+ out: null
6252
6317
  };
6253
6318
  for (let index = 0; index < rest.length; index += 1) {
6254
6319
  const arg = rest[index];
@@ -6282,6 +6347,10 @@ function parsePlayBootstrapOptions(args) {
6282
6347
  options.limit = parsePositiveInteger2(value(), "--limit");
6283
6348
  index += 1;
6284
6349
  break;
6350
+ case "--out":
6351
+ options.out = value();
6352
+ index += 1;
6353
+ break;
6285
6354
  default:
6286
6355
  throw new PlayBootstrapUsageError(
6287
6356
  `Unknown plays bootstrap option: ${arg}
@@ -6399,7 +6468,7 @@ function packagedCsvPathForPlay(csvPath) {
6399
6468
  const relativePath = (0, import_node_path11.relative)(playDir, absoluteCsvPath);
6400
6469
  if (relativePath === "" || relativePath.startsWith("..") || (0, import_node_path11.isAbsolute)(relativePath)) {
6401
6470
  throw new PlayBootstrapUsageError(
6402
- `--from csv:${csvPath} must point to a file inside the directory where you run plays bootstrap. Run bootstrap from the intended play directory, then redirect stdout to a .play.ts file there.`
6471
+ `--from csv:${csvPath} must point to a file inside the directory where you run plays bootstrap. Run bootstrap from the intended play directory and write the play with --out there.`
6403
6472
  );
6404
6473
  }
6405
6474
  const portablePath = relativePath.split("\\").join("/");
@@ -6806,8 +6875,18 @@ function validateBootstrapRoutes(input2) {
6806
6875
  "Company-to-people bootstrap only accepts --people play:<play-ref> for now. Providers are too task-specific; choose or create a people play so the generated file can show the mapping contract."
6807
6876
  );
6808
6877
  }
6878
+ assertComposablePlayRoute({
6879
+ stageLabel: "--people",
6880
+ playRef: stagePlayRef(input2.options.people),
6881
+ play: input2.peoplePlay
6882
+ });
6809
6883
  for (const finder of ["email_finder", "phone_finder"]) {
6810
6884
  const requiredCategory = PLAY_BOOTSTRAP_PROVIDER_CATEGORY_BY_FINDER[finder];
6885
+ assertComposablePlayRoute({
6886
+ stageLabel: finder === "email_finder" ? "--email/--using" : "--phone/--using",
6887
+ playRef: stagePlayRef(finderStage(input2.options, finder)),
6888
+ play: input2.finderPlays[finder] ?? null
6889
+ });
6811
6890
  for (const tool of input2.finderTools[finder] ?? []) {
6812
6891
  if (!tool.categories.includes(requiredCategory)) {
6813
6892
  throw new PlayBootstrapValidationError(
@@ -6822,6 +6901,38 @@ function validateBootstrapRoutes(input2) {
6822
6901
  }
6823
6902
  }
6824
6903
  }
6904
+ function staticPipelineSubsteps(pipeline) {
6905
+ if (!isRecord3(pipeline)) return [];
6906
+ return [
6907
+ ...extractionEntries(pipeline.stages),
6908
+ ...extractionEntries(pipeline.substeps)
6909
+ ];
6910
+ }
6911
+ function playUsesMapBackedRuntime(play) {
6912
+ const pipeline = play?.staticPipeline;
6913
+ if (!isRecord3(pipeline)) return false;
6914
+ if (stringValue(pipeline.tableNamespace)) return true;
6915
+ return staticPipelineSubsteps(pipeline).some((substep) => {
6916
+ if (stringValue(substep.type) === "map") return true;
6917
+ return playUsesMapBackedRuntime({
6918
+ name: play?.name ?? "child",
6919
+ aliases: [],
6920
+ runCommand: "",
6921
+ examples: [],
6922
+ staticPipeline: isRecord3(substep.pipeline) ? substep.pipeline : null
6923
+ });
6924
+ });
6925
+ }
6926
+ function assertComposablePlayRoute(input2) {
6927
+ if (!input2.playRef || !playUsesMapBackedRuntime(input2.play)) return;
6928
+ const runCommand2 = input2.play?.runCommand?.trim() || `deepline plays run ${input2.playRef} --input '{...}' --watch`;
6929
+ throw new PlayBootstrapValidationError(
6930
+ `Cannot use ${input2.stageLabel} play:${input2.playRef} in plays bootstrap composition: the selected play is map-backed/direct-run-only. Child plays that use ctx.map() own durable table state and must be run directly, exported, or validated as their own play instead of wrapped with ctx.runPlay. Run it directly first: ${runCommand2}`
6931
+ );
6932
+ }
6933
+ function sourcePlayNeedsExportFirst(input2) {
6934
+ return input2.source.kind === "play" && playUsesMapBackedRuntime(input2.sourcePlay);
6935
+ }
6825
6936
  function sourceCollectionName(entity) {
6826
6937
  switch (entity) {
6827
6938
  case "company":
@@ -6851,6 +6962,22 @@ function generateCsvSourceRowsBlock(input2) {
6851
6962
  const ${input2.collection}: ${input2.collectionType}[] = await sourceDataset.peek(limit);`;
6852
6963
  }
6853
6964
  function generatePlaySourceRowsBlock(input2) {
6965
+ if (sourcePlayNeedsExportFirst({
6966
+ source: input2.source,
6967
+ sourcePlay: input2.sourcePlay
6968
+ })) {
6969
+ const sourcePlay = input2.sourcePlay;
6970
+ const runCommand2 = sourcePlay?.runCommand?.trim() || `deepline plays run ${input2.source.value} --input '{...}' --watch`;
6971
+ return `// Source play ${input2.source.value} is map-backed/direct-run-only, so this generated play is stage 2.
6972
+ // Stage 1:
6973
+ // ${runCommand2}
6974
+ // Stage 2:
6975
+ // deepline runs export <stage-1-run-id> --dataset 'result.rows' --out source-export.csv
6976
+ // Stage 3:
6977
+ // deepline plays run <this-file.play.ts> --input '{"sourceCsv":"source-export.csv","limit":${input2.sourcePlay ? "5" : "5"}}' --watch
6978
+ const sourceDataset = await ctx.csv<${input2.collectionType}>(input.sourceCsv ?? './source-export.csv');
6979
+ const ${input2.collection}: ${input2.collectionType}[] = await sourceDataset.peek(limit);`;
6980
+ }
6854
6981
  const playInput = generatePlayInputObject({
6855
6982
  schema: input2.sourcePlay?.inputSchema,
6856
6983
  indent: " ",
@@ -7135,6 +7262,7 @@ ${typeDefinitions}
7135
7262
 
7136
7263
  type Input = {
7137
7264
  limit?: number;
7265
+ sourceCsv?: string;
7138
7266
  };
7139
7267
 
7140
7268
  export default definePlay(${jsString(input2.options.name)}, async (ctx, input: Input = {}) => {
@@ -7201,6 +7329,7 @@ async function loadBootstrapContracts(client, options) {
7201
7329
  validateBootstrapRoutes({
7202
7330
  options,
7203
7331
  sourceTools: contracts.sourceTools,
7332
+ sourcePlay: contracts.sourcePlay,
7204
7333
  peoplePlay: contracts.peoplePlay,
7205
7334
  finderTools: contracts.finderTools,
7206
7335
  finderPlays: contracts.finderPlays
@@ -7222,11 +7351,11 @@ function loadCsvContext(source) {
7222
7351
  };
7223
7352
  }
7224
7353
  }
7225
- function errorMessage(error) {
7354
+ function errorMessage2(error) {
7226
7355
  return error instanceof Error ? error.message : String(error);
7227
7356
  }
7228
7357
  function renderPlayBootstrapError(error) {
7229
- console.error(errorMessage(error));
7358
+ console.error(errorMessage2(error));
7230
7359
  return error instanceof PlayBootstrapError ? error.exitCode : 1;
7231
7360
  }
7232
7361
  async function runPlayBootstrap(args) {
@@ -7239,6 +7368,12 @@ async function runPlayBootstrap(args) {
7239
7368
  ...contracts,
7240
7369
  ...csvContext
7241
7370
  });
7371
+ if (options.out) {
7372
+ (0, import_node_fs9.writeFileSync)((0, import_node_path11.resolve)(options.out), source, "utf-8");
7373
+ process.stdout.write(`Wrote ${(0, import_node_path11.resolve)(options.out)}
7374
+ `);
7375
+ return 0;
7376
+ }
7242
7377
  process.stdout.write(source);
7243
7378
  return 0;
7244
7379
  }
@@ -7251,7 +7386,7 @@ function registerPlayBootstrapCommand(play) {
7251
7386
  `
7252
7387
  Notes:
7253
7388
  Cloud-validated play generator for agents. Pick the JTBD as the positional
7254
- template, bind resources with typed refs, redirect stdout to a .play.ts file,
7389
+ template, bind resources with typed refs, write the .play.ts file with --out,
7255
7390
  then edit the generated TODO mapping comments. Multiple finder providers are
7256
7391
  generated as a waterfall in the order you pass them.
7257
7392
 
@@ -7259,9 +7394,8 @@ Notes:
7259
7394
  Deepline. It prints TypeScript source only and does not run paid tools; the
7260
7395
  generated play may spend credits later when you run it.
7261
7396
 
7262
- stdout is the generated .play.ts source. Errors and diagnostics go to stderr.
7263
- There is no JSON mode and no --out; use shell redirection:
7264
- deepline plays bootstrap ... > scratchpad.play.ts
7397
+ By default stdout is the generated .play.ts source. Use --out to write a file
7398
+ directly. Errors and diagnostics go to stderr. There is no JSON mode.
7265
7399
 
7266
7400
  Templates:
7267
7401
  people-list start from people/contact rows
@@ -7285,7 +7419,7 @@ Notes:
7285
7419
  email/phone finder providers must match their category and expose value getters
7286
7420
  finder plays/providers must match the route; generated code leaves input mapping explicit
7287
7421
  business-specific provider inputs and company -> people persona fields are TODOs in code
7288
- csv: paths are resolved from the directory where you run bootstrap; redirect the play file there too
7422
+ csv: paths are resolved from the directory where you run bootstrap; write the play file there too
7289
7423
 
7290
7424
  Exit codes:
7291
7425
  0 success
@@ -7293,13 +7427,13 @@ Notes:
7293
7427
  7 route validation failed
7294
7428
 
7295
7429
  Examples:
7296
- deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall --limit 5 > email-flow.play.ts
7430
+ deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall --limit 5 --out email-flow.play.ts
7297
7431
  deepline plays check email-flow.play.ts
7298
7432
  deepline plays run email-flow.play.ts --input '{"limit":5}' --watch
7299
7433
 
7300
- deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 > prospecting.play.ts
7301
- deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email play:prebuilt/name-and-domain-to-email-waterfall --limit 5 > account-contacts.play.ts
7302
- deepline plays bootstrap company-list --from provider:apollo_company_search --limit 5 > companies.play.ts
7434
+ deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 --out prospecting.play.ts
7435
+ deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email play:prebuilt/name-and-domain-to-email-waterfall --limit 5 --out account-contacts.play.ts
7436
+ deepline plays bootstrap company-list --from provider:apollo_company_search --limit 5 --out companies.play.ts
7303
7437
  `
7304
7438
  ).option("--name <name>", "Generated play name").option(
7305
7439
  "--from <ref>",
@@ -7316,7 +7450,7 @@ Examples:
7316
7450
  ).option(
7317
7451
  "--phone <ref>",
7318
7452
  "Phone finder stage: play:REF, provider:ID, or providers:ID,ID"
7319
- ).option("--limit <n>", "Maximum rows to fan out in the generated play").action(async (template, options) => {
7453
+ ).option("--limit <n>", "Maximum rows to fan out in the generated play").option("--out <path>", "Write generated play source to a file").action(async (template, options) => {
7320
7454
  process.exitCode = await handlePlayBootstrap([
7321
7455
  template,
7322
7456
  ...options.name ? ["--name", options.name] : [],
@@ -7325,7 +7459,8 @@ Examples:
7325
7459
  ...options.people ? ["--people", options.people] : [],
7326
7460
  ...options.email ? ["--email", options.email] : [],
7327
7461
  ...options.phone ? ["--phone", options.phone] : [],
7328
- ...options.limit ? ["--limit", options.limit] : []
7462
+ ...options.limit ? ["--limit", options.limit] : [],
7463
+ ...options.out ? ["--out", options.out] : []
7329
7464
  ]);
7330
7465
  });
7331
7466
  }
@@ -7475,204 +7610,6 @@ function createCliProgress(enabled) {
7475
7610
  return progress;
7476
7611
  }
7477
7612
 
7478
- // ../shared_libs/plays/row-identity.ts
7479
- var POSTGRES_IDENTIFIER_MAX_LENGTH = 63;
7480
- var PLAY_NAME_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
7481
- var MAP_KEY_NAMESPACE_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
7482
- var SHA256_INITIAL_HASH = [
7483
- 1779033703,
7484
- 3144134277,
7485
- 1013904242,
7486
- 2773480762,
7487
- 1359893119,
7488
- 2600822924,
7489
- 528734635,
7490
- 1541459225
7491
- ];
7492
- var SHA256_ROUND_CONSTANTS = [
7493
- 1116352408,
7494
- 1899447441,
7495
- 3049323471,
7496
- 3921009573,
7497
- 961987163,
7498
- 1508970993,
7499
- 2453635748,
7500
- 2870763221,
7501
- 3624381080,
7502
- 310598401,
7503
- 607225278,
7504
- 1426881987,
7505
- 1925078388,
7506
- 2162078206,
7507
- 2614888103,
7508
- 3248222580,
7509
- 3835390401,
7510
- 4022224774,
7511
- 264347078,
7512
- 604807628,
7513
- 770255983,
7514
- 1249150122,
7515
- 1555081692,
7516
- 1996064986,
7517
- 2554220882,
7518
- 2821834349,
7519
- 2952996808,
7520
- 3210313671,
7521
- 3336571891,
7522
- 3584528711,
7523
- 113926993,
7524
- 338241895,
7525
- 666307205,
7526
- 773529912,
7527
- 1294757372,
7528
- 1396182291,
7529
- 1695183700,
7530
- 1986661051,
7531
- 2177026350,
7532
- 2456956037,
7533
- 2730485921,
7534
- 2820302411,
7535
- 3259730800,
7536
- 3345764771,
7537
- 3516065817,
7538
- 3600352804,
7539
- 4094571909,
7540
- 275423344,
7541
- 430227734,
7542
- 506948616,
7543
- 659060556,
7544
- 883997877,
7545
- 958139571,
7546
- 1322822218,
7547
- 1537002063,
7548
- 1747873779,
7549
- 1955562222,
7550
- 2024104815,
7551
- 2227730452,
7552
- 2361852424,
7553
- 2428436474,
7554
- 2756734187,
7555
- 3204031479,
7556
- 3329325298
7557
- ];
7558
- function rightRotate32(value, bits) {
7559
- return value >>> bits | value << 32 - bits;
7560
- }
7561
- function sha256Hex(input2) {
7562
- const bytes = Array.from(new TextEncoder().encode(input2));
7563
- const bitLength = bytes.length * 8;
7564
- bytes.push(128);
7565
- while (bytes.length % 64 !== 56) {
7566
- bytes.push(0);
7567
- }
7568
- const highBits = Math.floor(bitLength / 4294967296);
7569
- const lowBits = bitLength >>> 0;
7570
- bytes.push(
7571
- highBits >>> 24 & 255,
7572
- highBits >>> 16 & 255,
7573
- highBits >>> 8 & 255,
7574
- highBits & 255,
7575
- lowBits >>> 24 & 255,
7576
- lowBits >>> 16 & 255,
7577
- lowBits >>> 8 & 255,
7578
- lowBits & 255
7579
- );
7580
- const hash = [...SHA256_INITIAL_HASH];
7581
- const words = new Array(64).fill(0);
7582
- for (let offset = 0; offset < bytes.length; offset += 64) {
7583
- for (let index = 0; index < 16; index += 1) {
7584
- const wordOffset = offset + index * 4;
7585
- words[index] = (bytes[wordOffset] ?? 0) << 24 | (bytes[wordOffset + 1] ?? 0) << 16 | (bytes[wordOffset + 2] ?? 0) << 8 | (bytes[wordOffset + 3] ?? 0);
7586
- }
7587
- for (let index = 16; index < 64; index += 1) {
7588
- const s0 = rightRotate32(words[index - 15], 7) ^ rightRotate32(words[index - 15], 18) ^ words[index - 15] >>> 3;
7589
- const s1 = rightRotate32(words[index - 2], 17) ^ rightRotate32(words[index - 2], 19) ^ words[index - 2] >>> 10;
7590
- words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
7591
- }
7592
- let [a, b, c, d, e, f, g, h] = hash;
7593
- for (let index = 0; index < 64; index += 1) {
7594
- const s1 = rightRotate32(e, 6) ^ rightRotate32(e, 11) ^ rightRotate32(e, 25);
7595
- const ch = e & f ^ ~e & g;
7596
- const temp1 = h + s1 + ch + SHA256_ROUND_CONSTANTS[index] + words[index] >>> 0;
7597
- const s0 = rightRotate32(a, 2) ^ rightRotate32(a, 13) ^ rightRotate32(a, 22);
7598
- const maj = a & b ^ a & c ^ b & c;
7599
- const temp2 = s0 + maj >>> 0;
7600
- h = g;
7601
- g = f;
7602
- f = e;
7603
- e = d + temp1 >>> 0;
7604
- d = c;
7605
- c = b;
7606
- b = a;
7607
- a = temp1 + temp2 >>> 0;
7608
- }
7609
- hash[0] = hash[0] + a >>> 0;
7610
- hash[1] = hash[1] + b >>> 0;
7611
- hash[2] = hash[2] + c >>> 0;
7612
- hash[3] = hash[3] + d >>> 0;
7613
- hash[4] = hash[4] + e >>> 0;
7614
- hash[5] = hash[5] + f >>> 0;
7615
- hash[6] = hash[6] + g >>> 0;
7616
- hash[7] = hash[7] + h >>> 0;
7617
- }
7618
- return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
7619
- }
7620
- function sanitizeIdentifierPart(value) {
7621
- return value.trim().replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
7622
- }
7623
- function validateIdentifierPart(rawValue, label, maxLength) {
7624
- const sanitized = sanitizeIdentifierPart(rawValue);
7625
- if (!sanitized) {
7626
- throw new Error(
7627
- `${label} must contain at least one letter or number after normalization. Use only letters, numbers, underscores, or hyphens.`
7628
- );
7629
- }
7630
- if (sanitized.length > maxLength) {
7631
- throw new Error(
7632
- `${label} is too long after normalization (${sanitized.length}/${maxLength}). Shorten it to ${maxLength} characters or fewer. Normalized value: "${sanitized}".`
7633
- );
7634
- }
7635
- return sanitized;
7636
- }
7637
- function normalizePlayName(value) {
7638
- if (value.includes("/")) {
7639
- throw new Error(
7640
- 'Play name cannot contain "/". Slash is reserved for qualified play references like "prebuilt/example" or "self/example".'
7641
- );
7642
- }
7643
- return validateIdentifierPart(value, "Play name", PLAY_NAME_MAX_LENGTH);
7644
- }
7645
- function normalizePlayNameForSheet(value) {
7646
- if (!value.includes("/")) {
7647
- return normalizePlayName(value);
7648
- }
7649
- const digest = sha256Hex(value).slice(0, 12);
7650
- const normalizedReference = sanitizeIdentifierPart(
7651
- value.replace(/\//g, "__")
7652
- );
7653
- const prefixLength = Math.max(1, PLAY_NAME_MAX_LENGTH - digest.length - 1);
7654
- const prefix = normalizedReference.slice(0, prefixLength).replace(/_+$/g, "") || "qualified_play";
7655
- return `${prefix}_${digest}`;
7656
- }
7657
- function normalizeTableNamespace(value) {
7658
- return validateIdentifierPart(
7659
- value,
7660
- "ctx.dataset() key",
7661
- MAP_KEY_NAMESPACE_MAX_LENGTH
7662
- );
7663
- }
7664
- function validatePlaySheetTableName(playName, tableNamespace) {
7665
- const playSegment = normalizePlayNameForSheet(playName);
7666
- const keySegment = normalizeTableNamespace(tableNamespace);
7667
- const resolved = `${playSegment}_${keySegment}`;
7668
- if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7669
- throw new Error(
7670
- `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.dataset() key. Resolved table name: "${resolved}".`
7671
- );
7672
- }
7673
- return resolved;
7674
- }
7675
-
7676
7613
  // src/cli/trace.ts
7677
7614
  var cliTraceStartedAt = Date.now();
7678
7615
  function isTruthyEnv(value) {
@@ -7723,6 +7660,11 @@ async function traceCliSpan(phase, fields, run) {
7723
7660
 
7724
7661
  // src/cli/play-check-hints.ts
7725
7662
  var EXTRACTED_GETTER_ERROR_HINT = "Deepline hint: extractedValues/extractedLists .get() only works for declared Deepline getters listed by `deepline tools describe <tool> --json`. Use `toolExecutionResult.toolResponse.raw` for provider/tool-specific fields.";
7663
+ var DATASET_API_HINT = "Deepline hint: PlayDataset is lazy and durable. Use `.peek(n)` for a small preview or `.materialize()` when you intentionally need rows in memory; do not use `.rows`, `.toArray()`, or array methods directly on the dataset handle.";
7664
+ var ROW_PROPERTY_HINT = "Deepline hint: this row type only contains fields produced by the CSV/schema and previous map steps. Check source column casing and the exact output field names from earlier steps before scaling.";
7665
+ var TOOLS_EXECUTE_SIGNATURE_HINT = "Deepline hint: ctx.tools.execute requires a request object: `ctx.tools.execute({ id, tool, input, description })`. The stable `id` is required for replay-safe receipts.";
7666
+ var RUN_PLAY_SIGNATURE_HINT = "Deepline hint: ctx.runPlay uses a stable key plus a composable child play reference. Direct-run-only or map-backed batch plays must be run directly, exported, then consumed by a separate play.";
7667
+ var MAP_BACKED_CHILD_HINT = "Deepline hint: map-backed child plays own durable table state and cannot be called from another play. Run that play directly, export its dataset, then pass the CSV to the next play.";
7726
7668
  function sourceLineForError(sourceCode, error) {
7727
7669
  const match = error.match(/:(\d+):(\d+)\s/);
7728
7670
  const lineNumber = match?.[1] ? Number(match[1]) : NaN;
@@ -7733,16 +7675,61 @@ function looksLikeInvalidExtractedGetter(error, sourceLine) {
7733
7675
  if (!/Property '[^']+' does not exist on type/.test(error)) return false;
7734
7676
  return /\bextracted(?:Values|Lists)\s*\./.test(sourceLine);
7735
7677
  }
7678
+ function looksLikeDatasetApiMisuse(error, sourceLine) {
7679
+ return /Property '(?:rows|toArray|forEach|map|filter|reduce)' does not exist on type '[^']*PlayDataset/.test(
7680
+ error
7681
+ ) || /\b(?:\.rows|\.toArray\(\)|\.forEach\(|\.map\(|\.filter\(|\.reduce\()/.test(
7682
+ sourceLine
7683
+ );
7684
+ }
7685
+ function looksLikeRowPropertyMismatch(error, sourceLine) {
7686
+ if (!/Property '[^']+' does not exist on type/.test(error)) return false;
7687
+ if (looksLikeInvalidExtractedGetter(error, sourceLine)) return false;
7688
+ return /\brow\.[A-Za-z_][A-Za-z0-9_]*/.test(sourceLine);
7689
+ }
7690
+ function looksLikeToolsExecuteSignature(error, sourceLine) {
7691
+ return /ctx\.tools\.execute requires a request object/i.test(error) || /Expected 1 arguments?, but got [2-9]/.test(error) && /\btools\.execute\(/.test(sourceLine);
7692
+ }
7693
+ function looksLikeRunPlaySignature(error, sourceLine) {
7694
+ return /ctx\.runPlay/i.test(error) || /(?:Expected|Argument of type|No overload matches)/.test(error) && /\brunPlay\(/.test(sourceLine);
7695
+ }
7696
+ function looksLikeMapBackedChild(error) {
7697
+ return /map-backed child play|direct-run-only|cannot call a map-backed|own durable table/i.test(
7698
+ error
7699
+ );
7700
+ }
7701
+ function hintForError(error, sourceLine) {
7702
+ if (looksLikeInvalidExtractedGetter(error, sourceLine)) {
7703
+ return EXTRACTED_GETTER_ERROR_HINT;
7704
+ }
7705
+ if (looksLikeDatasetApiMisuse(error, sourceLine)) {
7706
+ return DATASET_API_HINT;
7707
+ }
7708
+ if (looksLikeToolsExecuteSignature(error, sourceLine)) {
7709
+ return TOOLS_EXECUTE_SIGNATURE_HINT;
7710
+ }
7711
+ if (looksLikeMapBackedChild(error)) {
7712
+ return MAP_BACKED_CHILD_HINT;
7713
+ }
7714
+ if (looksLikeRunPlaySignature(error, sourceLine)) {
7715
+ return RUN_PLAY_SIGNATURE_HINT;
7716
+ }
7717
+ if (looksLikeRowPropertyMismatch(error, sourceLine)) {
7718
+ return ROW_PROPERTY_HINT;
7719
+ }
7720
+ return null;
7721
+ }
7736
7722
  function addPlayCheckRepairHints(input2) {
7737
- let addedHint = false;
7723
+ const addedHints = /* @__PURE__ */ new Set();
7738
7724
  return input2.errors.map((error) => {
7739
7725
  const line = sourceLineForError(input2.sourceCode, error);
7740
- if (addedHint || !looksLikeInvalidExtractedGetter(error, line) || error.includes(EXTRACTED_GETTER_ERROR_HINT)) {
7726
+ const hint = hintForError(error, line);
7727
+ if (!hint || addedHints.has(hint) || error.includes(hint)) {
7741
7728
  return error;
7742
7729
  }
7743
- addedHint = true;
7730
+ addedHints.add(hint);
7744
7731
  return `${error}
7745
- ${EXTRACTED_GETTER_ERROR_HINT}`;
7732
+ ${hint}`;
7746
7733
  });
7747
7734
  }
7748
7735
 
@@ -8299,8 +8286,6 @@ var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
8299
8286
  "cancelled"
8300
8287
  ]);
8301
8288
  var PLAY_START_TRANSIENT_RETRY_DELAYS_MS = [500, 1500];
8302
- var PLAY_CUSTOMER_STORAGE_SCHEMA_NAME = "storage";
8303
- var PLAY_INTERNAL_STEP_RECEIPT_TABLE = "_deepline_step_receipts";
8304
8289
  function getEventPayload(event) {
8305
8290
  return event.payload && typeof event.payload === "object" ? event.payload : {};
8306
8291
  }
@@ -8354,36 +8339,8 @@ function getLogLinesFromLiveEvent(event) {
8354
8339
  const lines = getEventPayload(event).lines;
8355
8340
  return Array.isArray(lines) ? lines.filter((line) => typeof line === "string") : [];
8356
8341
  }
8357
- function quoteSqlIdentifier(identifier) {
8358
- return `"${identifier.replace(/"/g, '""')}"`;
8359
- }
8360
- function quoteSqlLiteral(value) {
8361
- return `'${value.replace(/'/g, "''")}'`;
8362
- }
8363
- function buildDebugDbQueryCommand(sql) {
8364
- return `deepline db query --sql ${shellSingleQuote(sql)} --max-rows 20 --json`;
8365
- }
8366
- function buildStepReceiptsDebugCommand(runId) {
8367
- const table = `${quoteSqlIdentifier(
8368
- PLAY_CUSTOMER_STORAGE_SCHEMA_NAME
8369
- )}.${quoteSqlIdentifier(PLAY_INTERNAL_STEP_RECEIPT_TABLE)}`;
8370
- const sql = `select convert_from(k, 'UTF8') as receipt_key, case status when 0 then 'pending' when 1 then 'running' when 2 then 'completed' when 3 then 'failed' when 4 then 'skipped' else status::text end as status, output, error, updated_at from ${table} where run_id = ${quoteSqlLiteral(runId)} order by updated_at asc, receipt_key asc limit 20`;
8371
- return buildDebugDbQueryCommand(sql);
8372
- }
8373
- function buildMapTableDebugCommand(input2) {
8374
- try {
8375
- const tableName = validatePlaySheetTableName(
8376
- input2.playName,
8377
- input2.tableNamespace
8378
- );
8379
- const table = `${quoteSqlIdentifier(
8380
- PLAY_CUSTOMER_STORAGE_SCHEMA_NAME
8381
- )}.${quoteSqlIdentifier(tableName)}`;
8382
- const sql = `select * from ${table} where _run_id = ${quoteSqlLiteral(input2.runId)} limit 20`;
8383
- return buildDebugDbQueryCommand(sql);
8384
- } catch {
8385
- return null;
8386
- }
8342
+ function buildRunInspectCommand(runId) {
8343
+ return `deepline runs get ${runId} --full --json`;
8387
8344
  }
8388
8345
  function extractTableNamespaceFromLiveEvent(event) {
8389
8346
  const payload = getEventPayload(event);
@@ -8408,7 +8365,7 @@ function emitLiveDebugTableHints(input2) {
8408
8365
  if (!input2.state.emittedDebugKeys.has(receiptsKey)) {
8409
8366
  input2.state.emittedDebugKeys.add(receiptsKey);
8410
8367
  input2.progress.writeLine(
8411
- `Debug top-level outputs: ${buildStepReceiptsDebugCommand(input2.runId)}`,
8368
+ `Inspect run output: ${buildRunInspectCommand(input2.runId)}`,
8412
8369
  process.stdout
8413
8370
  );
8414
8371
  }
@@ -8420,17 +8377,9 @@ function emitLiveDebugTableHints(input2) {
8420
8377
  if (input2.state.emittedDebugKeys.has(tableKey)) {
8421
8378
  return;
8422
8379
  }
8423
- const command = buildMapTableDebugCommand({
8424
- playName: input2.playName,
8425
- runId: input2.runId,
8426
- tableNamespace
8427
- });
8428
- if (!command) {
8429
- return;
8430
- }
8431
8380
  input2.state.emittedDebugKeys.add(tableKey);
8432
8381
  input2.progress.writeLine(
8433
- `Debug rows for ${tableNamespace}: ${command}`,
8382
+ `Possible map table ${tableNamespace}: created only after this ctx.map(...).run(...) executes. Inspect returned datasets with ${buildRunInspectCommand(input2.runId)}`,
8434
8383
  process.stdout
8435
8384
  );
8436
8385
  }
@@ -8634,7 +8583,7 @@ async function waitForPlayCompletionByStream(input2) {
8634
8583
  }
8635
8584
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
8636
8585
  throw new DeeplineError(
8637
- `Play live stream ended before the run reached a terminal state runId=${input2.workflowId}${phaseSuffix}.`,
8586
+ `Play watch stream ended before the run reached a terminal status. runId=${input2.workflowId}${phaseSuffix}. Inspect the current run with 'deepline runs get ${input2.workflowId} --full --json' or continue watching with 'deepline runs tail ${input2.workflowId}'.`,
8638
8587
  void 0,
8639
8588
  "PLAY_LIVE_STREAM_ENDED",
8640
8589
  {
@@ -8795,7 +8744,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8795
8744
  const reason = error instanceof Error ? error.message : String(error);
8796
8745
  if (!input2.jsonOutput) {
8797
8746
  process.stderr.write(
8798
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
8747
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to canonical run stream (${reason})
8799
8748
  `
8800
8749
  );
8801
8750
  }
@@ -8832,7 +8781,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8832
8781
  if (lastKnownWorkflowId) {
8833
8782
  if (!input2.jsonOutput) {
8834
8783
  input2.progress.writeLine(
8835
- `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
8784
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to canonical run stream`
8836
8785
  );
8837
8786
  }
8838
8787
  recordCliTrace({
@@ -8862,7 +8811,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8862
8811
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
8863
8812
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
8864
8813
  throw new DeeplineError(
8865
- `Play start stream ended before the run reached a terminal state${idSuffix}${phaseSuffix}.`,
8814
+ `Play start stream ended before a terminal status was observed${idSuffix}${phaseSuffix}. If a run id was printed, inspect it with 'deepline runs get <run-id> --full --json' or continue watching with 'deepline runs tail <run-id>'.`,
8866
8815
  void 0,
8867
8816
  "PLAY_START_STREAM_ENDED",
8868
8817
  {
@@ -9210,6 +9159,32 @@ function formatPlayErrorForDisplay(status, error) {
9210
9159
  }
9211
9160
  return error;
9212
9161
  }
9162
+ function isGenericInternalServerError(error) {
9163
+ if (!error) return false;
9164
+ return /^(?:internalservererror|internal server error|http 500|500)$/i.test(
9165
+ error.trim()
9166
+ );
9167
+ }
9168
+ function selectRunErrorForDisplay(status) {
9169
+ const progressError = getStringField(status.progress, "error");
9170
+ if (!isGenericInternalServerError(progressError)) {
9171
+ return progressError;
9172
+ }
9173
+ const directErrors = getRecordField(status, "errors");
9174
+ if (Array.isArray(directErrors)) {
9175
+ for (const entry of directErrors) {
9176
+ const message = getStringField(entry, "message");
9177
+ if (message && !isGenericInternalServerError(message)) {
9178
+ return message;
9179
+ }
9180
+ }
9181
+ }
9182
+ const directError = getStringField(status, "error");
9183
+ if (directError && !isGenericInternalServerError(directError)) {
9184
+ return directError;
9185
+ }
9186
+ return progressError;
9187
+ }
9213
9188
  function normalizeRunStatusForEnvelope(status) {
9214
9189
  const run = status.run ?? null;
9215
9190
  return {
@@ -9347,8 +9322,7 @@ function compactPlayStatus(status) {
9347
9322
  const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
9348
9323
  status.billing
9349
9324
  ) : null;
9350
- const progressError = status.progress?.error;
9351
- const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
9325
+ const error = selectRunErrorForDisplay(status) ?? (typeof status.error === "string" ? String(status.error) : null);
9352
9326
  const displayError = formatPlayErrorForDisplay(status, error);
9353
9327
  return {
9354
9328
  runId: status.runId,
@@ -9603,8 +9577,8 @@ function writePlayResult(status, jsonOutput, options) {
9603
9577
  for (const warning of warnings) {
9604
9578
  lines.push(` warning: ${warning}`);
9605
9579
  }
9606
- const progressError = status.progress?.error;
9607
- if (progressError && typeof progressError === "string") {
9580
+ const progressError = selectRunErrorForDisplay(status);
9581
+ if (progressError) {
9608
9582
  const billing = extractBillingForStatus(status, progressError);
9609
9583
  if (isInsufficientCreditsBilling(billing)) {
9610
9584
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
@@ -9656,9 +9630,6 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
9656
9630
  function shellSingleQuote(value) {
9657
9631
  return `'${value.replace(/'/g, `'\\''`)}'`;
9658
9632
  }
9659
- function sqlStringLiteral(value) {
9660
- return `'${value.replace(/'/g, "''")}'`;
9661
- }
9662
9633
  function runExportRetryCommand(runId, outPath, datasetPath) {
9663
9634
  return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote((0, import_node_path12.resolve)(outPath))}`;
9664
9635
  }
@@ -9677,26 +9648,6 @@ function extractRunPlayName(status) {
9677
9648
  }
9678
9649
  return null;
9679
9650
  }
9680
- function normalizeCustomerDbIdentifier(value) {
9681
- return value.split("/").pop().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
9682
- }
9683
- function buildCustomerDbQueryPlan(input2) {
9684
- const playName = extractRunPlayName(input2.status);
9685
- const tableNamespace = input2.rowsInfo.tableNamespace?.trim();
9686
- if (!playName || !tableNamespace || input2.rowsInfo.totalRows <= 0) {
9687
- return null;
9688
- }
9689
- const tableName = `${normalizeCustomerDbIdentifier(playName)}_${normalizeCustomerDbIdentifier(
9690
- tableNamespace
9691
- )}`;
9692
- const sql = `select * from "storage"."${tableName}" where _run_id = ${sqlStringLiteral(input2.status.runId)} limit ${input2.rowsInfo.totalRows}`;
9693
- const base = `deepline customer-db query --sql ${shellSingleQuote(sql)} --max-rows ${input2.rowsInfo.totalRows}`;
9694
- return {
9695
- sql,
9696
- json: `${base} --json`,
9697
- csv: `${base} --format csv --out ${shellSingleQuote((0, import_node_path12.resolve)(input2.outPath))}`
9698
- };
9699
- }
9700
9651
  function exportableSheetRow(row) {
9701
9652
  if (!row || typeof row !== "object" || Array.isArray(row)) {
9702
9653
  return null;
@@ -10043,8 +9994,12 @@ function renderServerResultView(value) {
10043
9994
  lines.push(
10044
9995
  ` ${String(table.tableNamespace ?? "table")}${lineLabel}: ${rowLabel}${details.join(" ")}`
10045
9996
  );
10046
- if (typeof table.queryDatasetCommand === "string") {
10047
- lines.push(` inspect rows: ${table.queryDatasetCommand}`);
9997
+ if (typeof table.queryDatasetCommand === "string" && typeof table.rowCount === "number" && table.rowCount > 0) {
9998
+ lines.push(` debug backing table: ${table.queryDatasetCommand}`);
9999
+ } else if (typeof table.queryDatasetCommand === "string") {
10000
+ lines.push(
10001
+ " no rows observed for this run; backing table is created only if this map ran"
10002
+ );
10048
10003
  }
10049
10004
  if (table.debugHelp && typeof table.debugHelp === "object" && !Array.isArray(table.debugHelp)) {
10050
10005
  const debugHelp = table.debugHelp;
@@ -10390,9 +10345,51 @@ function printToolGetterHints(hints) {
10390
10345
  async function handlePlayCheck(args) {
10391
10346
  const options = parsePlayCheckOptions(args);
10392
10347
  if (!isFileTarget(options.target)) {
10393
- const resolved = (0, import_node_path12.resolve)(options.target);
10394
- console.error(`File not found: ${resolved}`);
10395
- return 1;
10348
+ const client2 = new DeeplineClient();
10349
+ try {
10350
+ await assertCanonicalNamedPlayReference(client2, options.target);
10351
+ const play = await client2.describePlay(
10352
+ parseReferencedPlayTarget2(options.target).playName,
10353
+ { compact: true }
10354
+ );
10355
+ const result2 = {
10356
+ valid: true,
10357
+ target: options.target,
10358
+ name: play.name,
10359
+ reference: play.reference ?? options.target,
10360
+ origin: play.origin ?? null,
10361
+ ownerType: play.ownerType ?? null,
10362
+ inputSchema: play.inputSchema ?? null,
10363
+ outputSchema: play.outputSchema ?? null,
10364
+ staticPipeline: play.staticPipeline ?? null,
10365
+ note: "Named/prebuilt play contract is available. No run was started."
10366
+ };
10367
+ if (options.jsonOutput) {
10368
+ process.stdout.write(`${JSON.stringify(result2)}
10369
+ `);
10370
+ } else {
10371
+ console.log(`\u2713 ${result2.reference} passed named play contract check`);
10372
+ console.log(" no run started; no Deepline credits spent");
10373
+ if (play.runCommand) console.log(` run: ${play.runCommand}`);
10374
+ }
10375
+ return 0;
10376
+ } catch (error) {
10377
+ const resolved = (0, import_node_path12.resolve)(options.target);
10378
+ const message = error instanceof Error && error.message ? error.message : `File not found: ${resolved}`;
10379
+ if (options.jsonOutput) {
10380
+ process.stdout.write(
10381
+ `${JSON.stringify({
10382
+ valid: false,
10383
+ target: options.target,
10384
+ errors: [message]
10385
+ })}
10386
+ `
10387
+ );
10388
+ } else {
10389
+ console.error(message);
10390
+ }
10391
+ return 1;
10392
+ }
10396
10393
  }
10397
10394
  const absolutePlayPath = (0, import_node_path12.resolve)(options.target);
10398
10395
  const sourceCode = (0, import_node_fs10.readFileSync)(absolutePlayPath, "utf-8");
@@ -11039,18 +11036,9 @@ async function handleRunExport(args) {
11039
11036
  datasetPath
11040
11037
  });
11041
11038
  const source = exportResult?.rowsInfo.source ?? datasetPath ?? null;
11042
- const queryPlan = exportResult && outPath ? buildCustomerDbQueryPlan({
11043
- status,
11044
- rowsInfo: exportResult.rowsInfo,
11045
- outPath
11046
- }) : null;
11047
11039
  const next = {
11048
11040
  ...buildRunNextCommands(status),
11049
- export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source),
11050
- ...queryPlan ? {
11051
- queryJson: queryPlan.json,
11052
- queryCsv: queryPlan.csv
11053
- } : {}
11041
+ export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source)
11054
11042
  };
11055
11043
  const payload = {
11056
11044
  runId: status.runId,
@@ -11059,13 +11047,6 @@ async function handleRunExport(args) {
11059
11047
  source,
11060
11048
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
11061
11049
  columns: exportResult?.rowsInfo.columns ?? [],
11062
- ...queryPlan ? {
11063
- query: {
11064
- sql: queryPlan.sql,
11065
- json: queryPlan.json,
11066
- csv: queryPlan.csv
11067
- }
11068
- } : {},
11069
11050
  ...metadataOutPath ? { metadata_path: metadataOutPath } : {},
11070
11051
  local: { csv_path: exportResult?.path ?? null },
11071
11052
  next,
@@ -11075,8 +11056,7 @@ async function handleRunExport(args) {
11075
11056
  title: "run export",
11076
11057
  lines: [
11077
11058
  `Exported ${status.runId} to ${exportResult?.path ?? outPath}`,
11078
- ...source ? [`source=${source}`] : [],
11079
- ...queryPlan ? [`query=${queryPlan.json}`] : []
11059
+ ...source ? [`source=${source}`] : []
11080
11060
  ]
11081
11061
  }
11082
11062
  ]
@@ -11256,7 +11236,8 @@ function parsePlaySearchOptions(args) {
11256
11236
  return {
11257
11237
  query,
11258
11238
  jsonOutput: argsWantJson(args),
11259
- compact: args.includes("--compact")
11239
+ compact: args.includes("--compact"),
11240
+ prebuiltOnly: args.includes("--prebuilt")
11260
11241
  };
11261
11242
  }
11262
11243
  function printPlayDescription(play) {
@@ -11304,6 +11285,7 @@ function printPlayDescription(play) {
11304
11285
  console.log(` ${line}`);
11305
11286
  }
11306
11287
  }
11288
+ console.log(` Describe: deepline plays describe ${reference} --json`);
11307
11289
  console.log(` Run: ${play.runCommand}`);
11308
11290
  const cloneEditStarter = play.cloneEditStarter ?? buildCloneEditStarter(play);
11309
11291
  if (cloneEditStarter) {
@@ -11311,6 +11293,31 @@ function printPlayDescription(play) {
11311
11293
  console.log(` Check starter: ${cloneEditStarter.checkCommand}`);
11312
11294
  }
11313
11295
  }
11296
+ function inputFieldNames(schema) {
11297
+ const fields = Array.isArray(schema?.fields) ? schema.fields : [];
11298
+ return fields.map(
11299
+ (field) => field && typeof field === "object" ? String(field.name ?? "").trim() : ""
11300
+ ).filter(Boolean);
11301
+ }
11302
+ function printCompactPlaySearchResult(play) {
11303
+ const reference = formatPlayListReference(play);
11304
+ const aliases = play.aliases.slice(0, 6).join(", ");
11305
+ console.log(`Play: ${reference}`);
11306
+ if (play.displayName && play.displayName !== play.name) {
11307
+ console.log(` Display name: ${play.displayName}`);
11308
+ }
11309
+ if (aliases) {
11310
+ console.log(` Aliases: ${aliases}`);
11311
+ }
11312
+ const fields = inputFieldNames(play.inputSchema);
11313
+ if (fields.length > 0) {
11314
+ console.log(` Inputs: ${fields.join(", ")}`);
11315
+ } else if (play.inputSchema) {
11316
+ console.log(" Inputs: see describe");
11317
+ }
11318
+ console.log(` Describe: deepline plays describe ${reference} --json`);
11319
+ console.log(` Run: ${play.runCommand}`);
11320
+ }
11314
11321
  function compactPlaySchema(schema) {
11315
11322
  if (!schema) return null;
11316
11323
  const fields = Array.isArray(schema.fields) ? schema.fields.map(
@@ -11374,22 +11381,44 @@ async function handlePlaySearch(args) {
11374
11381
  return 1;
11375
11382
  }
11376
11383
  const client = new DeeplineClient();
11377
- const plays = await client.searchPlays({
11384
+ const plays = (await client.searchPlays({
11378
11385
  query: options.query,
11379
11386
  compact: options.compact
11380
- });
11387
+ })).filter(
11388
+ (play) => options.prebuiltOnly ? play.origin === "prebuilt" || play.ownerType === "deepline" : true
11389
+ );
11381
11390
  if (options.jsonOutput) {
11382
- process.stdout.write(`${JSON.stringify({ plays })}
11383
- `);
11391
+ const jsonPlays = options.prebuiltOnly ? plays.map((play) => ({
11392
+ ...play,
11393
+ inputSchema: compactPlaySchema(play.inputSchema)
11394
+ })) : plays;
11395
+ process.stdout.write(
11396
+ `${JSON.stringify({
11397
+ plays: jsonPlays,
11398
+ total: jsonPlays.length,
11399
+ truncated: false
11400
+ })}
11401
+ `
11402
+ );
11384
11403
  return 0;
11385
11404
  }
11386
- process.stdout.write(`${plays.length} plays found:
11405
+ const displayPlays = options.prebuiltOnly ? plays.slice(0, 5) : plays;
11406
+ process.stdout.write(
11407
+ `${plays.length} plays found${options.prebuiltOnly && plays.length > displayPlays.length ? `; showing top ${displayPlays.length}` : ""}:
11387
11408
 
11388
- `);
11389
- for (const play of plays) {
11390
- printPlayDescription(play);
11409
+ `
11410
+ );
11411
+ for (const play of displayPlays) {
11412
+ if (options.prebuiltOnly) {
11413
+ printCompactPlaySearchResult(play);
11414
+ } else {
11415
+ printPlayDescription(play);
11416
+ }
11391
11417
  console.log("");
11392
11418
  }
11419
+ if (options.prebuiltOnly && plays.length > displayPlays.length) {
11420
+ console.log("Use --json for the full machine-readable result set.");
11421
+ }
11393
11422
  return 0;
11394
11423
  }
11395
11424
  function normalizePlayGrepText(value) {
@@ -11656,15 +11685,18 @@ Common commands:
11656
11685
  deepline plays get person-linkedin-to-email --json
11657
11686
  `
11658
11687
  );
11659
- play.command("check <target>").description("Bundle-check a local play file.").addHelpText(
11688
+ play.command("check <target>").description("Check a local play file or named/prebuilt play contract.").addHelpText(
11660
11689
  "after",
11661
11690
  `
11662
11691
  Notes:
11663
11692
  Validates a local play without storing it, promoting it, or starting a run.
11664
11693
  This uses the authoritative cloud preflight path.
11694
+ For named or prebuilt plays, validates that the contract is discoverable
11695
+ without starting a run or spending Deepline credits.
11665
11696
 
11666
11697
  Examples:
11667
11698
  deepline plays check my.play.ts
11699
+ deepline plays check prebuilt/name-and-domain-to-email-waterfall-batch
11668
11700
  deepline plays check my.play.ts --json
11669
11701
  `
11670
11702
  ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
@@ -11814,11 +11846,13 @@ Examples:
11814
11846
  ...options.json ? ["--json"] : []
11815
11847
  ]);
11816
11848
  });
11817
- const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").addHelpText(
11849
+ const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").option("--prebuilt", "Only show Deepline-managed prebuilt plays").addHelpText(
11818
11850
  "after",
11819
11851
  `
11820
11852
  Notes:
11821
11853
  Ranked discovery for workflows. Use describe on a result before running it.
11854
+ Prefer --prebuilt for new GTM tasks so old workspace scratchpads do not
11855
+ outrank Deepline-managed routes unless the user names one explicitly.
11822
11856
  The grep alias is the same ranked retrieval surface with a more literal name
11823
11857
  for agents that are filtering the play registry.
11824
11858
 
@@ -11830,6 +11864,7 @@ Examples:
11830
11864
  ).option("--compact", "Emit compact schemas").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
11831
11865
  process.exitCode = await handlePlaySearch([
11832
11866
  query,
11867
+ ...options.prebuilt ? ["--prebuilt"] : [],
11833
11868
  ...options.compact ? ["--compact"] : [],
11834
11869
  ...options.json ? ["--json"] : []
11835
11870
  ]);
@@ -12077,7 +12112,7 @@ Notes:
12077
12112
  Writes a returned dataset handle to the requested local CSV path. Use runs get
12078
12113
  first to inspect dataset paths like result.rows or result.nested.contacts.
12079
12114
  --metadata-out writes the same export metadata object returned by --json,
12080
- including source and follow-on customer-db query commands when available.
12115
+ including the source dataset path and row/column metadata.
12081
12116
 
12082
12117
  Examples:
12083
12118
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
@@ -14008,7 +14043,7 @@ async function listTools(args) {
14008
14043
  const client = new DeeplineClient();
14009
14044
  const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
14010
14045
  const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
14011
- const compact = args.includes("--compact");
14046
+ const compact = args.includes("--compact") || !args.includes("--json");
14012
14047
  const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
14013
14048
  const items = (await client.listTools({
14014
14049
  ...categoryFilter ? { categories: categoryFilter } : {}
@@ -14064,9 +14099,10 @@ async function searchTools(queryInput, options = {}) {
14064
14099
  searchMode: options.searchMode,
14065
14100
  includeSearchDebug: options.includeSearchDebug
14066
14101
  });
14067
- const payload = options.compact && Array.isArray(result.tools) ? {
14102
+ const shouldCompact = options.compact || !options.json;
14103
+ const payload = shouldCompact && Array.isArray(result.tools) ? {
14068
14104
  ...result,
14069
- tools: result.tools.map(compactTool)
14105
+ tools: result.tools.slice(0, 8).map(compactTool)
14070
14106
  } : result;
14071
14107
  printCommandEnvelope(payload, {
14072
14108
  json: options.json || shouldEmitJson()
@@ -14107,7 +14143,8 @@ async function grepTools(queryInput, options = {}) {
14107
14143
  mode
14108
14144
  )
14109
14145
  );
14110
- const outputTools = options.compact ? tools.map(compactTool) : tools;
14146
+ const shouldCompact = options.compact || !options.json;
14147
+ const outputTools = shouldCompact ? tools.slice(0, 8).map(compactTool) : tools;
14111
14148
  printCommandEnvelope(
14112
14149
  {
14113
14150
  tools: outputTools,
@@ -14126,11 +14163,19 @@ async function grepTools(queryInput, options = {}) {
14126
14163
  );
14127
14164
  return 0;
14128
14165
  }
14166
+ function numericToolField(tool, field) {
14167
+ const value = tool[field];
14168
+ return typeof value === "number" ? value : void 0;
14169
+ }
14129
14170
  function compactTool(tool) {
14130
14171
  const listed = toListedTool(tool);
14172
+ const searchScore = numericToolField(tool, "searchScore");
14173
+ const search_score = numericToolField(tool, "search_score");
14131
14174
  return {
14132
14175
  id: listed.id,
14133
14176
  toolId: listed.toolId,
14177
+ ...search_score !== void 0 ? { search_score } : {},
14178
+ ...searchScore !== void 0 ? { searchScore } : {},
14134
14179
  provider: listed.provider,
14135
14180
  displayName: listed.displayName,
14136
14181
  description: listed.description,
@@ -14451,7 +14496,7 @@ async function getTool(toolId, options = {}) {
14451
14496
  }
14452
14497
  if (shouldEmitJson()) {
14453
14498
  process.stdout.write(
14454
- `${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
14499
+ `${JSON.stringify(toolContractJsonForDescribe(tool, toolId))}
14455
14500
  `
14456
14501
  );
14457
14502
  return 0;
@@ -14776,8 +14821,8 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
14776
14821
  invalidGetterHint: "If TypeScript says an extractedValues/extractedLists property does not exist, that field is not a declared Deepline getter.",
14777
14822
  observeActualShape: `deepline tools execute ${toolId} --input '{...}' --json`,
14778
14823
  observedOutput: `deepline tools execute ${toolId} --input '{...}' --json`,
14779
- forPlayGetterBugs: "Run the play, then inspect the emitted table commands from runs get. Use deepline db query against the run tables before editing getters.",
14780
- executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run tables."
14824
+ forPlayGetterBugs: "Run a tiny play, inspect `deepline runs get <run-id> --full --json`, and export returned dataset handles with `deepline runs export`. Backing tables exist only for ctx.map(...).run(...) stages that actually executed.",
14825
+ executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run output and returned dataset handles."
14781
14826
  },
14782
14827
  starterScript: {
14783
14828
  path: starterScript.path,
@@ -15794,6 +15839,7 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
15794
15839
  }
15795
15840
 
15796
15841
  // src/cli/index.ts
15842
+ var PREFLIGHT_TIMEOUT_MS = 3e3;
15797
15843
  function asCommanderError(error) {
15798
15844
  if (!(error instanceof Error) || !("code" in error)) {
15799
15845
  return null;
@@ -15874,6 +15920,124 @@ async function runPlayRunnerHealthCheck() {
15874
15920
  await (0, import_promises6.rm)(dir, { recursive: true, force: true });
15875
15921
  }
15876
15922
  }
15923
+ function pickString(value, ...keys) {
15924
+ for (const key of keys) {
15925
+ const candidate = value[key];
15926
+ if (typeof candidate === "string" && candidate.trim()) {
15927
+ return candidate;
15928
+ }
15929
+ }
15930
+ return null;
15931
+ }
15932
+ function preflightErrorMessage(error) {
15933
+ return error instanceof Error ? error.message : String(error);
15934
+ }
15935
+ async function runPreflightCheck() {
15936
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
15937
+ const healthController = new AbortController();
15938
+ const healthTimeout = setTimeout(
15939
+ () => healthController.abort(),
15940
+ PREFLIGHT_TIMEOUT_MS
15941
+ );
15942
+ const health = await fetch(new URL("/api/v2/health", baseUrl), {
15943
+ signal: healthController.signal
15944
+ }).then(async (response) => {
15945
+ const payload = await response.json().catch(() => ({}));
15946
+ return {
15947
+ status: response.ok ? pickString(payload, "status") ?? "ok" : "unreachable",
15948
+ version: pickString(payload, "version")
15949
+ };
15950
+ }).catch(() => ({ status: "unreachable", version: null })).finally(() => clearTimeout(healthTimeout));
15951
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
15952
+ const http = apiKey ? new HttpClient(
15953
+ resolveConfig({
15954
+ baseUrl,
15955
+ apiKey,
15956
+ timeout: PREFLIGHT_TIMEOUT_MS,
15957
+ maxRetries: 0
15958
+ })
15959
+ ) : null;
15960
+ const [auth, billing] = http ? await Promise.all([
15961
+ http.post("/api/v2/auth/cli/status", {
15962
+ api_key: apiKey,
15963
+ reveal: false
15964
+ }).catch((error) => ({
15965
+ status: "not_connected",
15966
+ connected: false,
15967
+ error: preflightErrorMessage(error)
15968
+ })),
15969
+ http.get("/api/v2/billing/balance").catch(
15970
+ (error) => ({
15971
+ balance: null,
15972
+ balance_display: "unavailable",
15973
+ balance_status: "unknown",
15974
+ error: preflightErrorMessage(error)
15975
+ })
15976
+ )
15977
+ ]) : [
15978
+ {
15979
+ status: "not_connected",
15980
+ connected: false,
15981
+ error: "No API key found. Run: deepline auth register"
15982
+ },
15983
+ {
15984
+ balance: null,
15985
+ balance_display: "unavailable until authenticated",
15986
+ balance_status: "unknown"
15987
+ }
15988
+ ];
15989
+ const authStatus = pickString(auth, "status") ?? "unknown";
15990
+ const billingRecord = billing;
15991
+ const balanceDisplay = pickString(billing, "balance_display", "balanceDisplay") ?? `${String(billingRecord.balance ?? 0)} Deepline Credits`;
15992
+ const balanceStatus = pickString(billing, "balance_status", "balanceStatus") ?? "unknown";
15993
+ return {
15994
+ status: health.status === "ok" && (authStatus === "connected" || authStatus === "claimed") ? "ok" : "check",
15995
+ host: baseUrl,
15996
+ health: {
15997
+ status: health.status,
15998
+ version: health.version ?? null
15999
+ },
16000
+ auth: {
16001
+ status: authStatus,
16002
+ connected: authStatus === "connected" || authStatus === "claimed",
16003
+ org_id: pickString(auth, "org_id", "orgId"),
16004
+ org_name: pickString(auth, "org_name", "orgName"),
16005
+ rate_limit_tier: pickString(auth, "rate_limit_tier", "rateLimitTier"),
16006
+ error: pickString(auth, "error")
16007
+ },
16008
+ billing: {
16009
+ balance: billingRecord.balance ?? null,
16010
+ balance_display: balanceDisplay,
16011
+ rough_usd_balance: billingRecord.rough_usd_balance ?? billingRecord.roughUsdBalance ?? null,
16012
+ balance_status: balanceStatus,
16013
+ error: pickString(billing, "error")
16014
+ },
16015
+ render: {
16016
+ sections: [
16017
+ {
16018
+ title: "preflight",
16019
+ lines: [
16020
+ `host: ${baseUrl}`,
16021
+ `health: ${health.status}`,
16022
+ `auth: ${authStatus}`,
16023
+ `billing: ${balanceDisplay} (${balanceStatus})`
16024
+ ]
16025
+ }
16026
+ ]
16027
+ }
16028
+ };
16029
+ }
16030
+ function printPreflightHuman(data) {
16031
+ const render = data.render;
16032
+ const lines = render?.sections?.flatMap((section) => section.lines ?? []);
16033
+ if (lines?.length) {
16034
+ process.stdout.write(`${lines.join("\n")}
16035
+ `);
16036
+ return;
16037
+ }
16038
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
16039
+ `);
16040
+ }
15877
16041
  async function main() {
15878
16042
  const mainStartedAt = Date.now();
15879
16043
  recordCliTrace({
@@ -15890,6 +16054,7 @@ async function main() {
15890
16054
  "after",
15891
16055
  `
15892
16056
  Common commands:
16057
+ deepline preflight
15893
16058
  deepline health
15894
16059
  deepline auth status --json
15895
16060
  deepline plays search email --json
@@ -15911,7 +16076,6 @@ Output:
15911
16076
 
15912
16077
  Safety:
15913
16078
  Commands that mutate state, open a browser, write files, stop work, or spend credits say so in their help.
15914
- Use --no-open where available for CI and agent runs.
15915
16079
 
15916
16080
  Exit codes:
15917
16081
  0 success; 2 usage/local input error; 3 auth/permission error; 4 not found;
@@ -15953,6 +16117,26 @@ Exit codes:
15953
16117
  registerDbCommands(program);
15954
16118
  registerFeedbackCommands(program);
15955
16119
  registerUpdateCommand(program);
16120
+ program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(
16121
+ "after",
16122
+ `
16123
+ Notes:
16124
+ Read-only setup check for the configured Deepline host. Shows server health,
16125
+ auth connection, and customer-visible Deepline balance in one compact command.
16126
+
16127
+ Examples:
16128
+ deepline preflight
16129
+ deepline preflight --json
16130
+ `
16131
+ ).action(async (options) => {
16132
+ const data = await runPreflightCheck();
16133
+ if (shouldEmitJson(options.json)) {
16134
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
16135
+ `);
16136
+ } else {
16137
+ printPreflightHuman(data);
16138
+ }
16139
+ });
15956
16140
  program.command("health").description("Check server health.").option("--json", "Force JSON output.").option(
15957
16141
  "--play-runner",
15958
16142
  "Run a tiny no-provider play to verify the full play execution plane."