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.
@@ -206,10 +206,10 @@ import { join as join2 } from "path";
206
206
 
207
207
  // src/release.ts
208
208
  var SDK_RELEASE = {
209
- version: "0.1.76",
209
+ version: "0.1.78",
210
210
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
211
211
  supportPolicy: {
212
- latest: "0.1.76",
212
+ latest: "0.1.78",
213
213
  minimumSupported: "0.1.53",
214
214
  deprecatedBelow: "0.1.53"
215
215
  }
@@ -379,8 +379,8 @@ var HttpClient = class {
379
379
  if (lastError instanceof DeeplineError) {
380
380
  throw lastError;
381
381
  }
382
- const errorMessage2 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
383
- throw new DeeplineError(errorMessage2);
382
+ const errorMessage3 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
383
+ throw new DeeplineError(errorMessage3);
384
384
  }
385
385
  /**
386
386
  * Send a GET request.
@@ -785,6 +785,7 @@ var DeeplineClient = class {
785
785
  aliases,
786
786
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
787
787
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
788
+ staticPipeline: isRecord(play.staticPipeline) ? play.staticPipeline : isRecord(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
788
789
  ...csvInput ? { csvInput } : {},
789
790
  ...rowOutputSchema ? { rowOutputSchema } : {},
790
791
  runCommand: runCommand2,
@@ -4016,6 +4017,51 @@ function customerDbRows(result) {
4016
4017
  function customerDbColumnNames(result) {
4017
4018
  return result.columns.map((column) => column.name).filter(Boolean);
4018
4019
  }
4020
+ function errorMessage(value) {
4021
+ return value instanceof Error ? value.message : String(value ?? "");
4022
+ }
4023
+ function collectErrorText(value) {
4024
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
4025
+ return errorMessage(value);
4026
+ }
4027
+ const record = value;
4028
+ return [
4029
+ errorMessage(value),
4030
+ typeof record.error === "string" ? record.error : "",
4031
+ typeof record.message === "string" ? record.message : "",
4032
+ typeof record.detail === "string" ? record.detail : "",
4033
+ typeof record.hint === "string" ? record.hint : "",
4034
+ typeof record.code === "string" ? record.code : "",
4035
+ record.response ? collectErrorText(record.response) : "",
4036
+ record.details ? collectErrorText(record.details) : ""
4037
+ ].filter(Boolean).join("\n");
4038
+ }
4039
+ function formatDbQueryError(sql, error) {
4040
+ const text = collectErrorText(error);
4041
+ const lower = text.toLowerCase();
4042
+ const referencesStorage = /"storage"\.|storage\./i.test(sql);
4043
+ const runIdColumnMissing = /column\s+"?run_id"?\s+does not exist/i.test(text) || /run_id.*does not exist/i.test(lower);
4044
+ const relationMissing = /relation\s+["']?[^"']*storage[^"']*["']?\s+does not exist/i.test(text) || /42p01/i.test(text);
4045
+ if (referencesStorage && relationMissing) {
4046
+ return [
4047
+ "Customer DB query failed: the referenced storage table does not exist.",
4048
+ "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.",
4049
+ "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`.",
4050
+ `Original error: ${errorMessage(error)}`
4051
+ ].join("\n");
4052
+ }
4053
+ if (referencesStorage && runIdColumnMissing) {
4054
+ return [
4055
+ "Customer DB query failed: storage map tables use `_run_id`, not `run_id`.",
4056
+ "Prefer `deepline runs export <run-id> --dataset result.rows --out rows.csv` unless you are doing deep table debugging.",
4057
+ `Original error: ${errorMessage(error)}`
4058
+ ].join("\n");
4059
+ }
4060
+ if (error instanceof DeeplineError) {
4061
+ return error.message;
4062
+ }
4063
+ return errorMessage(error);
4064
+ }
4019
4065
  function writeCustomerDbCsv(result, outPath) {
4020
4066
  const resolved = resolve5(outPath);
4021
4067
  writeFileSync5(
@@ -4076,7 +4122,13 @@ async function handleDbQuery(args) {
4076
4122
  const jsonOutput = argsWantJson(args);
4077
4123
  const explicitJsonOutput = args.includes("--json");
4078
4124
  const client = new DeeplineClient();
4079
- const result = await client.queryCustomerDb({ sql, maxRows });
4125
+ let result;
4126
+ try {
4127
+ result = await client.queryCustomerDb({ sql, maxRows });
4128
+ } catch (error) {
4129
+ console.error(formatDbQueryError(sql, error));
4130
+ return 1;
4131
+ }
4080
4132
  const toolCommand = `deepline tools execute query_customer_db --payload ${JSON.stringify(
4081
4133
  {
4082
4134
  sql,
@@ -4173,13 +4225,18 @@ function registerDbCommands(program) {
4173
4225
  "after",
4174
4226
  `
4175
4227
  Notes:
4176
- Runs SQL against the active workspace customer database through Deepline APIs.
4228
+ Agent-safe SQL for the active workspace customer database.
4229
+ Reads: SELECT, EXPLAIN, and read-only WITH can inspect permitted schemas.
4230
+ Writes: CREATE TABLE, INSERT, UPDATE, DELETE, ALTER, DROP, TRUNCATE, and
4231
+ CREATE INDEX must target schema-qualified storage tables, such as storage.agent_notes.
4232
+ If CREATE TABLE blocked (...) fails, use CREATE TABLE storage.blocked (...).
4177
4233
  Results are bounded by the server and --max-rows. Use --json for stable output.
4178
4234
  Use --format csv or --format markdown for agent-readable exports and display tables.
4179
4235
 
4180
4236
  Examples:
4181
4237
  deepline db query --sql "select * from companies limit 20"
4182
4238
  deepline db query --sql "select domain, name from companies limit 20" --json
4239
+ deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
4183
4240
  deepline db query --sql "select * from contacts" --max-rows 100 --json
4184
4241
  deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
4185
4242
  deepline db query --sql "select domain, name from companies limit 20" --format markdown
@@ -4191,12 +4248,16 @@ Examples:
4191
4248
  Notes:
4192
4249
  Requires --sql. Output is a compact table in a terminal and raw JSON with
4193
4250
  --json or when stdout is piped. The active auth workspace determines scope.
4251
+ Read permitted schemas with SELECT, EXPLAIN, or read-only WITH.
4252
+ Write only to schema-qualified storage tables. For example, use
4253
+ CREATE TABLE storage.blocked (...) instead of CREATE TABLE blocked (...).
4194
4254
  --format csv and --format markdown are explicit data/display formats and can
4195
4255
  be written directly with --out.
4196
4256
 
4197
4257
  Examples:
4198
4258
  deepline db query --sql "select * from companies limit 20"
4199
4259
  deepline db query --sql "select domain, name from companies limit 20" --json
4260
+ deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
4200
4261
  deepline db psql --sql "select count(*) from contacts" --json
4201
4262
  deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
4202
4263
  deepline db query --sql "select domain, name from companies limit 20" --format markdown
@@ -4228,7 +4289,7 @@ import {
4228
4289
  readFileSync as readFileSync6,
4229
4290
  readdirSync,
4230
4291
  realpathSync,
4231
- writeFileSync as writeFileSync6
4292
+ writeFileSync as writeFileSync7
4232
4293
  } from "fs";
4233
4294
  import { basename as basename3, dirname as dirname8, join as join7, resolve as resolve10 } from "path";
4234
4295
 
@@ -5901,7 +5962,7 @@ async function bundlePlayFile2(filePath, options = {}) {
5901
5962
  }
5902
5963
 
5903
5964
  // src/cli/commands/plays/bootstrap.ts
5904
- import { closeSync, openSync, readSync, statSync } from "fs";
5965
+ import { closeSync, openSync, readSync, statSync, writeFileSync as writeFileSync6 } from "fs";
5905
5966
  import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve9 } from "path";
5906
5967
  import { parse as parseCsvSync } from "csv-parse/sync";
5907
5968
 
@@ -6076,19 +6137,19 @@ function playBootstrapTemplateConfig(template) {
6076
6137
  function templateExample(template) {
6077
6138
  switch (template) {
6078
6139
  case "people-list":
6079
- return "deepline plays bootstrap people-list --from provider:dropleads_search_people > people.play.ts";
6140
+ return "deepline plays bootstrap people-list --from provider:dropleads_search_people --out people.play.ts";
6080
6141
  case "company-list":
6081
- return "deepline plays bootstrap company-list --from provider:apollo_company_search > companies.play.ts";
6142
+ return "deepline plays bootstrap company-list --from provider:apollo_company_search --out companies.play.ts";
6082
6143
  case "people-email":
6083
- return "deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall > email-flow.play.ts";
6144
+ 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";
6084
6145
  case "people-phone":
6085
- return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone > phone-flow.play.ts";
6146
+ return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone --out phone-flow.play.ts";
6086
6147
  case "company-people":
6087
- return "deepline plays bootstrap company-people --from provider:apollo_company_search --using play:prebuilt/company-to-contact > company-people.play.ts";
6148
+ return "deepline plays bootstrap company-people --from provider:apollo_company_search --using play:prebuilt/company-to-contact --out company-people.play.ts";
6088
6149
  case "company-people-email":
6089
- 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";
6150
+ 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";
6090
6151
  case "company-people-phone":
6091
- 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";
6152
+ 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";
6092
6153
  }
6093
6154
  }
6094
6155
  var PLAY_BOOTSTRAP_STAGE_NAMES = [
@@ -6097,19 +6158,22 @@ var PLAY_BOOTSTRAP_STAGE_NAMES = [
6097
6158
  "phone"
6098
6159
  ];
6099
6160
  function playBootstrapUsageLine() {
6100
- 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`;
6161
+ 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]`;
6101
6162
  }
6102
6163
  function requireBootstrapTemplate(rawTemplate) {
6103
6164
  if (!rawTemplate) {
6104
6165
  throw new PlayBootstrapUsageError(
6105
6166
  `plays bootstrap needs a route template: ${formatPlayBootstrapTemplates()}.
6106
- Example: deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall > email-flow.play.ts`
6167
+ 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`
6107
6168
  );
6108
6169
  }
6109
6170
  if (!isPlayBootstrapTemplate(rawTemplate)) {
6171
+ const looksLikePlayReference = rawTemplate.includes("/") || rawTemplate.endsWith(".play.ts");
6110
6172
  throw new PlayBootstrapUsageError(
6111
6173
  `Unknown plays bootstrap template: ${rawTemplate}
6112
- Supported templates: ${formatPlayBootstrapTemplates()}`
6174
+ Supported templates: ${formatPlayBootstrapTemplates()}` + (looksLikePlayReference ? `
6175
+
6176
+ "${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.` : "")
6113
6177
  );
6114
6178
  }
6115
6179
  return rawTemplate;
@@ -6251,7 +6315,8 @@ function parsePlayBootstrapOptions(args) {
6251
6315
  people: null,
6252
6316
  email: null,
6253
6317
  phone: null,
6254
- limit: 5
6318
+ limit: 5,
6319
+ out: null
6255
6320
  };
6256
6321
  for (let index = 0; index < rest.length; index += 1) {
6257
6322
  const arg = rest[index];
@@ -6285,6 +6350,10 @@ function parsePlayBootstrapOptions(args) {
6285
6350
  options.limit = parsePositiveInteger2(value(), "--limit");
6286
6351
  index += 1;
6287
6352
  break;
6353
+ case "--out":
6354
+ options.out = value();
6355
+ index += 1;
6356
+ break;
6288
6357
  default:
6289
6358
  throw new PlayBootstrapUsageError(
6290
6359
  `Unknown plays bootstrap option: ${arg}
@@ -6402,7 +6471,7 @@ function packagedCsvPathForPlay(csvPath) {
6402
6471
  const relativePath = relative2(playDir, absoluteCsvPath);
6403
6472
  if (relativePath === "" || relativePath.startsWith("..") || isAbsolute3(relativePath)) {
6404
6473
  throw new PlayBootstrapUsageError(
6405
- `--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.`
6474
+ `--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.`
6406
6475
  );
6407
6476
  }
6408
6477
  const portablePath = relativePath.split("\\").join("/");
@@ -6809,8 +6878,18 @@ function validateBootstrapRoutes(input2) {
6809
6878
  "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."
6810
6879
  );
6811
6880
  }
6881
+ assertComposablePlayRoute({
6882
+ stageLabel: "--people",
6883
+ playRef: stagePlayRef(input2.options.people),
6884
+ play: input2.peoplePlay
6885
+ });
6812
6886
  for (const finder of ["email_finder", "phone_finder"]) {
6813
6887
  const requiredCategory = PLAY_BOOTSTRAP_PROVIDER_CATEGORY_BY_FINDER[finder];
6888
+ assertComposablePlayRoute({
6889
+ stageLabel: finder === "email_finder" ? "--email/--using" : "--phone/--using",
6890
+ playRef: stagePlayRef(finderStage(input2.options, finder)),
6891
+ play: input2.finderPlays[finder] ?? null
6892
+ });
6814
6893
  for (const tool of input2.finderTools[finder] ?? []) {
6815
6894
  if (!tool.categories.includes(requiredCategory)) {
6816
6895
  throw new PlayBootstrapValidationError(
@@ -6825,6 +6904,38 @@ function validateBootstrapRoutes(input2) {
6825
6904
  }
6826
6905
  }
6827
6906
  }
6907
+ function staticPipelineSubsteps(pipeline) {
6908
+ if (!isRecord3(pipeline)) return [];
6909
+ return [
6910
+ ...extractionEntries(pipeline.stages),
6911
+ ...extractionEntries(pipeline.substeps)
6912
+ ];
6913
+ }
6914
+ function playUsesMapBackedRuntime(play) {
6915
+ const pipeline = play?.staticPipeline;
6916
+ if (!isRecord3(pipeline)) return false;
6917
+ if (stringValue(pipeline.tableNamespace)) return true;
6918
+ return staticPipelineSubsteps(pipeline).some((substep) => {
6919
+ if (stringValue(substep.type) === "map") return true;
6920
+ return playUsesMapBackedRuntime({
6921
+ name: play?.name ?? "child",
6922
+ aliases: [],
6923
+ runCommand: "",
6924
+ examples: [],
6925
+ staticPipeline: isRecord3(substep.pipeline) ? substep.pipeline : null
6926
+ });
6927
+ });
6928
+ }
6929
+ function assertComposablePlayRoute(input2) {
6930
+ if (!input2.playRef || !playUsesMapBackedRuntime(input2.play)) return;
6931
+ const runCommand2 = input2.play?.runCommand?.trim() || `deepline plays run ${input2.playRef} --input '{...}' --watch`;
6932
+ throw new PlayBootstrapValidationError(
6933
+ `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}`
6934
+ );
6935
+ }
6936
+ function sourcePlayNeedsExportFirst(input2) {
6937
+ return input2.source.kind === "play" && playUsesMapBackedRuntime(input2.sourcePlay);
6938
+ }
6828
6939
  function sourceCollectionName(entity) {
6829
6940
  switch (entity) {
6830
6941
  case "company":
@@ -6854,6 +6965,22 @@ function generateCsvSourceRowsBlock(input2) {
6854
6965
  const ${input2.collection}: ${input2.collectionType}[] = await sourceDataset.peek(limit);`;
6855
6966
  }
6856
6967
  function generatePlaySourceRowsBlock(input2) {
6968
+ if (sourcePlayNeedsExportFirst({
6969
+ source: input2.source,
6970
+ sourcePlay: input2.sourcePlay
6971
+ })) {
6972
+ const sourcePlay = input2.sourcePlay;
6973
+ const runCommand2 = sourcePlay?.runCommand?.trim() || `deepline plays run ${input2.source.value} --input '{...}' --watch`;
6974
+ return `// Source play ${input2.source.value} is map-backed/direct-run-only, so this generated play is stage 2.
6975
+ // Stage 1:
6976
+ // ${runCommand2}
6977
+ // Stage 2:
6978
+ // deepline runs export <stage-1-run-id> --dataset 'result.rows' --out source-export.csv
6979
+ // Stage 3:
6980
+ // deepline plays run <this-file.play.ts> --input '{"sourceCsv":"source-export.csv","limit":${input2.sourcePlay ? "5" : "5"}}' --watch
6981
+ const sourceDataset = await ctx.csv<${input2.collectionType}>(input.sourceCsv ?? './source-export.csv');
6982
+ const ${input2.collection}: ${input2.collectionType}[] = await sourceDataset.peek(limit);`;
6983
+ }
6857
6984
  const playInput = generatePlayInputObject({
6858
6985
  schema: input2.sourcePlay?.inputSchema,
6859
6986
  indent: " ",
@@ -7138,6 +7265,7 @@ ${typeDefinitions}
7138
7265
 
7139
7266
  type Input = {
7140
7267
  limit?: number;
7268
+ sourceCsv?: string;
7141
7269
  };
7142
7270
 
7143
7271
  export default definePlay(${jsString(input2.options.name)}, async (ctx, input: Input = {}) => {
@@ -7204,6 +7332,7 @@ async function loadBootstrapContracts(client, options) {
7204
7332
  validateBootstrapRoutes({
7205
7333
  options,
7206
7334
  sourceTools: contracts.sourceTools,
7335
+ sourcePlay: contracts.sourcePlay,
7207
7336
  peoplePlay: contracts.peoplePlay,
7208
7337
  finderTools: contracts.finderTools,
7209
7338
  finderPlays: contracts.finderPlays
@@ -7225,11 +7354,11 @@ function loadCsvContext(source) {
7225
7354
  };
7226
7355
  }
7227
7356
  }
7228
- function errorMessage(error) {
7357
+ function errorMessage2(error) {
7229
7358
  return error instanceof Error ? error.message : String(error);
7230
7359
  }
7231
7360
  function renderPlayBootstrapError(error) {
7232
- console.error(errorMessage(error));
7361
+ console.error(errorMessage2(error));
7233
7362
  return error instanceof PlayBootstrapError ? error.exitCode : 1;
7234
7363
  }
7235
7364
  async function runPlayBootstrap(args) {
@@ -7242,6 +7371,12 @@ async function runPlayBootstrap(args) {
7242
7371
  ...contracts,
7243
7372
  ...csvContext
7244
7373
  });
7374
+ if (options.out) {
7375
+ writeFileSync6(resolve9(options.out), source, "utf-8");
7376
+ process.stdout.write(`Wrote ${resolve9(options.out)}
7377
+ `);
7378
+ return 0;
7379
+ }
7245
7380
  process.stdout.write(source);
7246
7381
  return 0;
7247
7382
  }
@@ -7254,7 +7389,7 @@ function registerPlayBootstrapCommand(play) {
7254
7389
  `
7255
7390
  Notes:
7256
7391
  Cloud-validated play generator for agents. Pick the JTBD as the positional
7257
- template, bind resources with typed refs, redirect stdout to a .play.ts file,
7392
+ template, bind resources with typed refs, write the .play.ts file with --out,
7258
7393
  then edit the generated TODO mapping comments. Multiple finder providers are
7259
7394
  generated as a waterfall in the order you pass them.
7260
7395
 
@@ -7262,9 +7397,8 @@ Notes:
7262
7397
  Deepline. It prints TypeScript source only and does not run paid tools; the
7263
7398
  generated play may spend credits later when you run it.
7264
7399
 
7265
- stdout is the generated .play.ts source. Errors and diagnostics go to stderr.
7266
- There is no JSON mode and no --out; use shell redirection:
7267
- deepline plays bootstrap ... > scratchpad.play.ts
7400
+ By default stdout is the generated .play.ts source. Use --out to write a file
7401
+ directly. Errors and diagnostics go to stderr. There is no JSON mode.
7268
7402
 
7269
7403
  Templates:
7270
7404
  people-list start from people/contact rows
@@ -7288,7 +7422,7 @@ Notes:
7288
7422
  email/phone finder providers must match their category and expose value getters
7289
7423
  finder plays/providers must match the route; generated code leaves input mapping explicit
7290
7424
  business-specific provider inputs and company -> people persona fields are TODOs in code
7291
- csv: paths are resolved from the directory where you run bootstrap; redirect the play file there too
7425
+ csv: paths are resolved from the directory where you run bootstrap; write the play file there too
7292
7426
 
7293
7427
  Exit codes:
7294
7428
  0 success
@@ -7296,13 +7430,13 @@ Notes:
7296
7430
  7 route validation failed
7297
7431
 
7298
7432
  Examples:
7299
- 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
7433
+ 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
7300
7434
  deepline plays check email-flow.play.ts
7301
7435
  deepline plays run email-flow.play.ts --input '{"limit":5}' --watch
7302
7436
 
7303
- deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 > prospecting.play.ts
7304
- 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
7305
- deepline plays bootstrap company-list --from provider:apollo_company_search --limit 5 > companies.play.ts
7437
+ deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 --out prospecting.play.ts
7438
+ 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
7439
+ deepline plays bootstrap company-list --from provider:apollo_company_search --limit 5 --out companies.play.ts
7306
7440
  `
7307
7441
  ).option("--name <name>", "Generated play name").option(
7308
7442
  "--from <ref>",
@@ -7319,7 +7453,7 @@ Examples:
7319
7453
  ).option(
7320
7454
  "--phone <ref>",
7321
7455
  "Phone finder stage: play:REF, provider:ID, or providers:ID,ID"
7322
- ).option("--limit <n>", "Maximum rows to fan out in the generated play").action(async (template, options) => {
7456
+ ).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) => {
7323
7457
  process.exitCode = await handlePlayBootstrap([
7324
7458
  template,
7325
7459
  ...options.name ? ["--name", options.name] : [],
@@ -7328,7 +7462,8 @@ Examples:
7328
7462
  ...options.people ? ["--people", options.people] : [],
7329
7463
  ...options.email ? ["--email", options.email] : [],
7330
7464
  ...options.phone ? ["--phone", options.phone] : [],
7331
- ...options.limit ? ["--limit", options.limit] : []
7465
+ ...options.limit ? ["--limit", options.limit] : [],
7466
+ ...options.out ? ["--out", options.out] : []
7332
7467
  ]);
7333
7468
  });
7334
7469
  }
@@ -7478,204 +7613,6 @@ function createCliProgress(enabled) {
7478
7613
  return progress;
7479
7614
  }
7480
7615
 
7481
- // ../shared_libs/plays/row-identity.ts
7482
- var POSTGRES_IDENTIFIER_MAX_LENGTH = 63;
7483
- var PLAY_NAME_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
7484
- var MAP_KEY_NAMESPACE_MAX_LENGTH = POSTGRES_IDENTIFIER_MAX_LENGTH;
7485
- var SHA256_INITIAL_HASH = [
7486
- 1779033703,
7487
- 3144134277,
7488
- 1013904242,
7489
- 2773480762,
7490
- 1359893119,
7491
- 2600822924,
7492
- 528734635,
7493
- 1541459225
7494
- ];
7495
- var SHA256_ROUND_CONSTANTS = [
7496
- 1116352408,
7497
- 1899447441,
7498
- 3049323471,
7499
- 3921009573,
7500
- 961987163,
7501
- 1508970993,
7502
- 2453635748,
7503
- 2870763221,
7504
- 3624381080,
7505
- 310598401,
7506
- 607225278,
7507
- 1426881987,
7508
- 1925078388,
7509
- 2162078206,
7510
- 2614888103,
7511
- 3248222580,
7512
- 3835390401,
7513
- 4022224774,
7514
- 264347078,
7515
- 604807628,
7516
- 770255983,
7517
- 1249150122,
7518
- 1555081692,
7519
- 1996064986,
7520
- 2554220882,
7521
- 2821834349,
7522
- 2952996808,
7523
- 3210313671,
7524
- 3336571891,
7525
- 3584528711,
7526
- 113926993,
7527
- 338241895,
7528
- 666307205,
7529
- 773529912,
7530
- 1294757372,
7531
- 1396182291,
7532
- 1695183700,
7533
- 1986661051,
7534
- 2177026350,
7535
- 2456956037,
7536
- 2730485921,
7537
- 2820302411,
7538
- 3259730800,
7539
- 3345764771,
7540
- 3516065817,
7541
- 3600352804,
7542
- 4094571909,
7543
- 275423344,
7544
- 430227734,
7545
- 506948616,
7546
- 659060556,
7547
- 883997877,
7548
- 958139571,
7549
- 1322822218,
7550
- 1537002063,
7551
- 1747873779,
7552
- 1955562222,
7553
- 2024104815,
7554
- 2227730452,
7555
- 2361852424,
7556
- 2428436474,
7557
- 2756734187,
7558
- 3204031479,
7559
- 3329325298
7560
- ];
7561
- function rightRotate32(value, bits) {
7562
- return value >>> bits | value << 32 - bits;
7563
- }
7564
- function sha256Hex(input2) {
7565
- const bytes = Array.from(new TextEncoder().encode(input2));
7566
- const bitLength = bytes.length * 8;
7567
- bytes.push(128);
7568
- while (bytes.length % 64 !== 56) {
7569
- bytes.push(0);
7570
- }
7571
- const highBits = Math.floor(bitLength / 4294967296);
7572
- const lowBits = bitLength >>> 0;
7573
- bytes.push(
7574
- highBits >>> 24 & 255,
7575
- highBits >>> 16 & 255,
7576
- highBits >>> 8 & 255,
7577
- highBits & 255,
7578
- lowBits >>> 24 & 255,
7579
- lowBits >>> 16 & 255,
7580
- lowBits >>> 8 & 255,
7581
- lowBits & 255
7582
- );
7583
- const hash = [...SHA256_INITIAL_HASH];
7584
- const words = new Array(64).fill(0);
7585
- for (let offset = 0; offset < bytes.length; offset += 64) {
7586
- for (let index = 0; index < 16; index += 1) {
7587
- const wordOffset = offset + index * 4;
7588
- words[index] = (bytes[wordOffset] ?? 0) << 24 | (bytes[wordOffset + 1] ?? 0) << 16 | (bytes[wordOffset + 2] ?? 0) << 8 | (bytes[wordOffset + 3] ?? 0);
7589
- }
7590
- for (let index = 16; index < 64; index += 1) {
7591
- const s0 = rightRotate32(words[index - 15], 7) ^ rightRotate32(words[index - 15], 18) ^ words[index - 15] >>> 3;
7592
- const s1 = rightRotate32(words[index - 2], 17) ^ rightRotate32(words[index - 2], 19) ^ words[index - 2] >>> 10;
7593
- words[index] = words[index - 16] + s0 + words[index - 7] + s1 >>> 0;
7594
- }
7595
- let [a, b, c, d, e, f, g, h] = hash;
7596
- for (let index = 0; index < 64; index += 1) {
7597
- const s1 = rightRotate32(e, 6) ^ rightRotate32(e, 11) ^ rightRotate32(e, 25);
7598
- const ch = e & f ^ ~e & g;
7599
- const temp1 = h + s1 + ch + SHA256_ROUND_CONSTANTS[index] + words[index] >>> 0;
7600
- const s0 = rightRotate32(a, 2) ^ rightRotate32(a, 13) ^ rightRotate32(a, 22);
7601
- const maj = a & b ^ a & c ^ b & c;
7602
- const temp2 = s0 + maj >>> 0;
7603
- h = g;
7604
- g = f;
7605
- f = e;
7606
- e = d + temp1 >>> 0;
7607
- d = c;
7608
- c = b;
7609
- b = a;
7610
- a = temp1 + temp2 >>> 0;
7611
- }
7612
- hash[0] = hash[0] + a >>> 0;
7613
- hash[1] = hash[1] + b >>> 0;
7614
- hash[2] = hash[2] + c >>> 0;
7615
- hash[3] = hash[3] + d >>> 0;
7616
- hash[4] = hash[4] + e >>> 0;
7617
- hash[5] = hash[5] + f >>> 0;
7618
- hash[6] = hash[6] + g >>> 0;
7619
- hash[7] = hash[7] + h >>> 0;
7620
- }
7621
- return hash.map((word) => word.toString(16).padStart(8, "0")).join("");
7622
- }
7623
- function sanitizeIdentifierPart(value) {
7624
- return value.trim().replace(/[^a-z0-9]+/gi, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
7625
- }
7626
- function validateIdentifierPart(rawValue, label, maxLength) {
7627
- const sanitized = sanitizeIdentifierPart(rawValue);
7628
- if (!sanitized) {
7629
- throw new Error(
7630
- `${label} must contain at least one letter or number after normalization. Use only letters, numbers, underscores, or hyphens.`
7631
- );
7632
- }
7633
- if (sanitized.length > maxLength) {
7634
- throw new Error(
7635
- `${label} is too long after normalization (${sanitized.length}/${maxLength}). Shorten it to ${maxLength} characters or fewer. Normalized value: "${sanitized}".`
7636
- );
7637
- }
7638
- return sanitized;
7639
- }
7640
- function normalizePlayName(value) {
7641
- if (value.includes("/")) {
7642
- throw new Error(
7643
- 'Play name cannot contain "/". Slash is reserved for qualified play references like "prebuilt/example" or "self/example".'
7644
- );
7645
- }
7646
- return validateIdentifierPart(value, "Play name", PLAY_NAME_MAX_LENGTH);
7647
- }
7648
- function normalizePlayNameForSheet(value) {
7649
- if (!value.includes("/")) {
7650
- return normalizePlayName(value);
7651
- }
7652
- const digest = sha256Hex(value).slice(0, 12);
7653
- const normalizedReference = sanitizeIdentifierPart(
7654
- value.replace(/\//g, "__")
7655
- );
7656
- const prefixLength = Math.max(1, PLAY_NAME_MAX_LENGTH - digest.length - 1);
7657
- const prefix = normalizedReference.slice(0, prefixLength).replace(/_+$/g, "") || "qualified_play";
7658
- return `${prefix}_${digest}`;
7659
- }
7660
- function normalizeTableNamespace(value) {
7661
- return validateIdentifierPart(
7662
- value,
7663
- "ctx.dataset() key",
7664
- MAP_KEY_NAMESPACE_MAX_LENGTH
7665
- );
7666
- }
7667
- function validatePlaySheetTableName(playName, tableNamespace) {
7668
- const playSegment = normalizePlayNameForSheet(playName);
7669
- const keySegment = normalizeTableNamespace(tableNamespace);
7670
- const resolved = `${playSegment}_${keySegment}`;
7671
- if (resolved.length > POSTGRES_IDENTIFIER_MAX_LENGTH) {
7672
- throw new Error(
7673
- `Play sheet table name is too long after normalization (${resolved.length}/63). Shorten the play name or ctx.dataset() key. Resolved table name: "${resolved}".`
7674
- );
7675
- }
7676
- return resolved;
7677
- }
7678
-
7679
7616
  // src/cli/trace.ts
7680
7617
  var cliTraceStartedAt = Date.now();
7681
7618
  function isTruthyEnv(value) {
@@ -7726,6 +7663,11 @@ async function traceCliSpan(phase, fields, run) {
7726
7663
 
7727
7664
  // src/cli/play-check-hints.ts
7728
7665
  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.";
7666
+ 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.";
7667
+ 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.";
7668
+ 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.";
7669
+ 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.";
7670
+ 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.";
7729
7671
  function sourceLineForError(sourceCode, error) {
7730
7672
  const match = error.match(/:(\d+):(\d+)\s/);
7731
7673
  const lineNumber = match?.[1] ? Number(match[1]) : NaN;
@@ -7736,16 +7678,61 @@ function looksLikeInvalidExtractedGetter(error, sourceLine) {
7736
7678
  if (!/Property '[^']+' does not exist on type/.test(error)) return false;
7737
7679
  return /\bextracted(?:Values|Lists)\s*\./.test(sourceLine);
7738
7680
  }
7681
+ function looksLikeDatasetApiMisuse(error, sourceLine) {
7682
+ return /Property '(?:rows|toArray|forEach|map|filter|reduce)' does not exist on type '[^']*PlayDataset/.test(
7683
+ error
7684
+ ) || /\b(?:\.rows|\.toArray\(\)|\.forEach\(|\.map\(|\.filter\(|\.reduce\()/.test(
7685
+ sourceLine
7686
+ );
7687
+ }
7688
+ function looksLikeRowPropertyMismatch(error, sourceLine) {
7689
+ if (!/Property '[^']+' does not exist on type/.test(error)) return false;
7690
+ if (looksLikeInvalidExtractedGetter(error, sourceLine)) return false;
7691
+ return /\brow\.[A-Za-z_][A-Za-z0-9_]*/.test(sourceLine);
7692
+ }
7693
+ function looksLikeToolsExecuteSignature(error, sourceLine) {
7694
+ return /ctx\.tools\.execute requires a request object/i.test(error) || /Expected 1 arguments?, but got [2-9]/.test(error) && /\btools\.execute\(/.test(sourceLine);
7695
+ }
7696
+ function looksLikeRunPlaySignature(error, sourceLine) {
7697
+ return /ctx\.runPlay/i.test(error) || /(?:Expected|Argument of type|No overload matches)/.test(error) && /\brunPlay\(/.test(sourceLine);
7698
+ }
7699
+ function looksLikeMapBackedChild(error) {
7700
+ return /map-backed child play|direct-run-only|cannot call a map-backed|own durable table/i.test(
7701
+ error
7702
+ );
7703
+ }
7704
+ function hintForError(error, sourceLine) {
7705
+ if (looksLikeInvalidExtractedGetter(error, sourceLine)) {
7706
+ return EXTRACTED_GETTER_ERROR_HINT;
7707
+ }
7708
+ if (looksLikeDatasetApiMisuse(error, sourceLine)) {
7709
+ return DATASET_API_HINT;
7710
+ }
7711
+ if (looksLikeToolsExecuteSignature(error, sourceLine)) {
7712
+ return TOOLS_EXECUTE_SIGNATURE_HINT;
7713
+ }
7714
+ if (looksLikeMapBackedChild(error)) {
7715
+ return MAP_BACKED_CHILD_HINT;
7716
+ }
7717
+ if (looksLikeRunPlaySignature(error, sourceLine)) {
7718
+ return RUN_PLAY_SIGNATURE_HINT;
7719
+ }
7720
+ if (looksLikeRowPropertyMismatch(error, sourceLine)) {
7721
+ return ROW_PROPERTY_HINT;
7722
+ }
7723
+ return null;
7724
+ }
7739
7725
  function addPlayCheckRepairHints(input2) {
7740
- let addedHint = false;
7726
+ const addedHints = /* @__PURE__ */ new Set();
7741
7727
  return input2.errors.map((error) => {
7742
7728
  const line = sourceLineForError(input2.sourceCode, error);
7743
- if (addedHint || !looksLikeInvalidExtractedGetter(error, line) || error.includes(EXTRACTED_GETTER_ERROR_HINT)) {
7729
+ const hint = hintForError(error, line);
7730
+ if (!hint || addedHints.has(hint) || error.includes(hint)) {
7744
7731
  return error;
7745
7732
  }
7746
- addedHint = true;
7733
+ addedHints.add(hint);
7747
7734
  return `${error}
7748
- ${EXTRACTED_GETTER_ERROR_HINT}`;
7735
+ ${hint}`;
7749
7736
  });
7750
7737
  }
7751
7738
 
@@ -7860,10 +7847,10 @@ function materializeRemotePlaySource(input2) {
7860
7847
  if (existingSource === input2.sourceCode) {
7861
7848
  return { path: outputPath, status: "unchanged", created: false };
7862
7849
  }
7863
- writeFileSync6(outputPath, input2.sourceCode, "utf-8");
7850
+ writeFileSync7(outputPath, input2.sourceCode, "utf-8");
7864
7851
  return { path: outputPath, status: "updated", created: false };
7865
7852
  }
7866
- writeFileSync6(outputPath, input2.sourceCode, "utf-8");
7853
+ writeFileSync7(outputPath, input2.sourceCode, "utf-8");
7867
7854
  return { path: outputPath, status: "created", created: true };
7868
7855
  }
7869
7856
  function formatLoadedPlayMessage(materializedFile) {
@@ -8302,8 +8289,6 @@ var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
8302
8289
  "cancelled"
8303
8290
  ]);
8304
8291
  var PLAY_START_TRANSIENT_RETRY_DELAYS_MS = [500, 1500];
8305
- var PLAY_CUSTOMER_STORAGE_SCHEMA_NAME = "storage";
8306
- var PLAY_INTERNAL_STEP_RECEIPT_TABLE = "_deepline_step_receipts";
8307
8292
  function getEventPayload(event) {
8308
8293
  return event.payload && typeof event.payload === "object" ? event.payload : {};
8309
8294
  }
@@ -8357,36 +8342,8 @@ function getLogLinesFromLiveEvent(event) {
8357
8342
  const lines = getEventPayload(event).lines;
8358
8343
  return Array.isArray(lines) ? lines.filter((line) => typeof line === "string") : [];
8359
8344
  }
8360
- function quoteSqlIdentifier(identifier) {
8361
- return `"${identifier.replace(/"/g, '""')}"`;
8362
- }
8363
- function quoteSqlLiteral(value) {
8364
- return `'${value.replace(/'/g, "''")}'`;
8365
- }
8366
- function buildDebugDbQueryCommand(sql) {
8367
- return `deepline db query --sql ${shellSingleQuote(sql)} --max-rows 20 --json`;
8368
- }
8369
- function buildStepReceiptsDebugCommand(runId) {
8370
- const table = `${quoteSqlIdentifier(
8371
- PLAY_CUSTOMER_STORAGE_SCHEMA_NAME
8372
- )}.${quoteSqlIdentifier(PLAY_INTERNAL_STEP_RECEIPT_TABLE)}`;
8373
- 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`;
8374
- return buildDebugDbQueryCommand(sql);
8375
- }
8376
- function buildMapTableDebugCommand(input2) {
8377
- try {
8378
- const tableName = validatePlaySheetTableName(
8379
- input2.playName,
8380
- input2.tableNamespace
8381
- );
8382
- const table = `${quoteSqlIdentifier(
8383
- PLAY_CUSTOMER_STORAGE_SCHEMA_NAME
8384
- )}.${quoteSqlIdentifier(tableName)}`;
8385
- const sql = `select * from ${table} where _run_id = ${quoteSqlLiteral(input2.runId)} limit 20`;
8386
- return buildDebugDbQueryCommand(sql);
8387
- } catch {
8388
- return null;
8389
- }
8345
+ function buildRunInspectCommand(runId) {
8346
+ return `deepline runs get ${runId} --full --json`;
8390
8347
  }
8391
8348
  function extractTableNamespaceFromLiveEvent(event) {
8392
8349
  const payload = getEventPayload(event);
@@ -8411,7 +8368,7 @@ function emitLiveDebugTableHints(input2) {
8411
8368
  if (!input2.state.emittedDebugKeys.has(receiptsKey)) {
8412
8369
  input2.state.emittedDebugKeys.add(receiptsKey);
8413
8370
  input2.progress.writeLine(
8414
- `Debug top-level outputs: ${buildStepReceiptsDebugCommand(input2.runId)}`,
8371
+ `Inspect run output: ${buildRunInspectCommand(input2.runId)}`,
8415
8372
  process.stdout
8416
8373
  );
8417
8374
  }
@@ -8423,17 +8380,9 @@ function emitLiveDebugTableHints(input2) {
8423
8380
  if (input2.state.emittedDebugKeys.has(tableKey)) {
8424
8381
  return;
8425
8382
  }
8426
- const command = buildMapTableDebugCommand({
8427
- playName: input2.playName,
8428
- runId: input2.runId,
8429
- tableNamespace
8430
- });
8431
- if (!command) {
8432
- return;
8433
- }
8434
8383
  input2.state.emittedDebugKeys.add(tableKey);
8435
8384
  input2.progress.writeLine(
8436
- `Debug rows for ${tableNamespace}: ${command}`,
8385
+ `Possible map table ${tableNamespace}: created only after this ctx.map(...).run(...) executes. Inspect returned datasets with ${buildRunInspectCommand(input2.runId)}`,
8437
8386
  process.stdout
8438
8387
  );
8439
8388
  }
@@ -8637,7 +8586,7 @@ async function waitForPlayCompletionByStream(input2) {
8637
8586
  }
8638
8587
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
8639
8588
  throw new DeeplineError(
8640
- `Play live stream ended before the run reached a terminal state runId=${input2.workflowId}${phaseSuffix}.`,
8589
+ `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}'.`,
8641
8590
  void 0,
8642
8591
  "PLAY_LIVE_STREAM_ENDED",
8643
8592
  {
@@ -8798,7 +8747,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8798
8747
  const reason = error instanceof Error ? error.message : String(error);
8799
8748
  if (!input2.jsonOutput) {
8800
8749
  process.stderr.write(
8801
- `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to run stream (${reason})
8750
+ `[play watch] start stream failed after run ${lastKnownWorkflowId}; reconnecting to canonical run stream (${reason})
8802
8751
  `
8803
8752
  );
8804
8753
  }
@@ -8835,7 +8784,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8835
8784
  if (lastKnownWorkflowId) {
8836
8785
  if (!input2.jsonOutput) {
8837
8786
  input2.progress.writeLine(
8838
- `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to run stream`
8787
+ `[play watch] start stream ended after run ${lastKnownWorkflowId}; reconnecting to canonical run stream`
8839
8788
  );
8840
8789
  }
8841
8790
  recordCliTrace({
@@ -8865,7 +8814,7 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
8865
8814
  const phaseSuffix = lastPhase && lastPhase.trim() ? ` (last observed phase: ${lastPhase.trim()})` : "";
8866
8815
  const idSuffix = lastKnownWorkflowId ? ` runId=${lastKnownWorkflowId}` : "";
8867
8816
  throw new DeeplineError(
8868
- `Play start stream ended before the run reached a terminal state${idSuffix}${phaseSuffix}.`,
8817
+ `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>'.`,
8869
8818
  void 0,
8870
8819
  "PLAY_START_STREAM_ENDED",
8871
8820
  {
@@ -9213,6 +9162,32 @@ function formatPlayErrorForDisplay(status, error) {
9213
9162
  }
9214
9163
  return error;
9215
9164
  }
9165
+ function isGenericInternalServerError(error) {
9166
+ if (!error) return false;
9167
+ return /^(?:internalservererror|internal server error|http 500|500)$/i.test(
9168
+ error.trim()
9169
+ );
9170
+ }
9171
+ function selectRunErrorForDisplay(status) {
9172
+ const progressError = getStringField(status.progress, "error");
9173
+ if (!isGenericInternalServerError(progressError)) {
9174
+ return progressError;
9175
+ }
9176
+ const directErrors = getRecordField(status, "errors");
9177
+ if (Array.isArray(directErrors)) {
9178
+ for (const entry of directErrors) {
9179
+ const message = getStringField(entry, "message");
9180
+ if (message && !isGenericInternalServerError(message)) {
9181
+ return message;
9182
+ }
9183
+ }
9184
+ }
9185
+ const directError = getStringField(status, "error");
9186
+ if (directError && !isGenericInternalServerError(directError)) {
9187
+ return directError;
9188
+ }
9189
+ return progressError;
9190
+ }
9216
9191
  function normalizeRunStatusForEnvelope(status) {
9217
9192
  const run = status.run ?? null;
9218
9193
  return {
@@ -9350,8 +9325,7 @@ function compactPlayStatus(status) {
9350
9325
  const billing = status && typeof status === "object" ? stripProviderSpendFromBilling(
9351
9326
  status.billing
9352
9327
  ) : null;
9353
- const progressError = status.progress?.error;
9354
- const error = typeof progressError === "string" ? progressError : typeof status.error === "string" ? String(status.error) : null;
9328
+ const error = selectRunErrorForDisplay(status) ?? (typeof status.error === "string" ? String(status.error) : null);
9355
9329
  const displayError = formatPlayErrorForDisplay(status, error);
9356
9330
  return {
9357
9331
  runId: status.runId,
@@ -9606,8 +9580,8 @@ function writePlayResult(status, jsonOutput, options) {
9606
9580
  for (const warning of warnings) {
9607
9581
  lines.push(` warning: ${warning}`);
9608
9582
  }
9609
- const progressError = status.progress?.error;
9610
- if (progressError && typeof progressError === "string") {
9583
+ const progressError = selectRunErrorForDisplay(status);
9584
+ if (progressError) {
9611
9585
  const billing = extractBillingForStatus(status, progressError);
9612
9586
  if (isInsufficientCreditsBilling(billing)) {
9613
9587
  lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
@@ -9659,9 +9633,6 @@ var RUN_EXPORT_PAGE_SIZE = 5e3;
9659
9633
  function shellSingleQuote(value) {
9660
9634
  return `'${value.replace(/'/g, `'\\''`)}'`;
9661
9635
  }
9662
- function sqlStringLiteral(value) {
9663
- return `'${value.replace(/'/g, "''")}'`;
9664
- }
9665
9636
  function runExportRetryCommand(runId, outPath, datasetPath) {
9666
9637
  return `deepline runs export ${runId}${datasetPath ? ` --dataset ${shellSingleQuote(datasetPath)}` : ""} --out ${shellSingleQuote(resolve10(outPath))}`;
9667
9638
  }
@@ -9680,26 +9651,6 @@ function extractRunPlayName(status) {
9680
9651
  }
9681
9652
  return null;
9682
9653
  }
9683
- function normalizeCustomerDbIdentifier(value) {
9684
- return value.split("/").pop().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
9685
- }
9686
- function buildCustomerDbQueryPlan(input2) {
9687
- const playName = extractRunPlayName(input2.status);
9688
- const tableNamespace = input2.rowsInfo.tableNamespace?.trim();
9689
- if (!playName || !tableNamespace || input2.rowsInfo.totalRows <= 0) {
9690
- return null;
9691
- }
9692
- const tableName = `${normalizeCustomerDbIdentifier(playName)}_${normalizeCustomerDbIdentifier(
9693
- tableNamespace
9694
- )}`;
9695
- const sql = `select * from "storage"."${tableName}" where _run_id = ${sqlStringLiteral(input2.status.runId)} limit ${input2.rowsInfo.totalRows}`;
9696
- const base = `deepline customer-db query --sql ${shellSingleQuote(sql)} --max-rows ${input2.rowsInfo.totalRows}`;
9697
- return {
9698
- sql,
9699
- json: `${base} --json`,
9700
- csv: `${base} --format csv --out ${shellSingleQuote(resolve10(input2.outPath))}`
9701
- };
9702
- }
9703
9654
  function exportableSheetRow(row) {
9704
9655
  if (!row || typeof row !== "object" || Array.isArray(row)) {
9705
9656
  return null;
@@ -10046,8 +9997,12 @@ function renderServerResultView(value) {
10046
9997
  lines.push(
10047
9998
  ` ${String(table.tableNamespace ?? "table")}${lineLabel}: ${rowLabel}${details.join(" ")}`
10048
9999
  );
10049
- if (typeof table.queryDatasetCommand === "string") {
10050
- lines.push(` inspect rows: ${table.queryDatasetCommand}`);
10000
+ if (typeof table.queryDatasetCommand === "string" && typeof table.rowCount === "number" && table.rowCount > 0) {
10001
+ lines.push(` debug backing table: ${table.queryDatasetCommand}`);
10002
+ } else if (typeof table.queryDatasetCommand === "string") {
10003
+ lines.push(
10004
+ " no rows observed for this run; backing table is created only if this map ran"
10005
+ );
10051
10006
  }
10052
10007
  if (table.debugHelp && typeof table.debugHelp === "object" && !Array.isArray(table.debugHelp)) {
10053
10008
  const debugHelp = table.debugHelp;
@@ -10393,9 +10348,51 @@ function printToolGetterHints(hints) {
10393
10348
  async function handlePlayCheck(args) {
10394
10349
  const options = parsePlayCheckOptions(args);
10395
10350
  if (!isFileTarget(options.target)) {
10396
- const resolved = resolve10(options.target);
10397
- console.error(`File not found: ${resolved}`);
10398
- return 1;
10351
+ const client2 = new DeeplineClient();
10352
+ try {
10353
+ await assertCanonicalNamedPlayReference(client2, options.target);
10354
+ const play = await client2.describePlay(
10355
+ parseReferencedPlayTarget2(options.target).playName,
10356
+ { compact: true }
10357
+ );
10358
+ const result2 = {
10359
+ valid: true,
10360
+ target: options.target,
10361
+ name: play.name,
10362
+ reference: play.reference ?? options.target,
10363
+ origin: play.origin ?? null,
10364
+ ownerType: play.ownerType ?? null,
10365
+ inputSchema: play.inputSchema ?? null,
10366
+ outputSchema: play.outputSchema ?? null,
10367
+ staticPipeline: play.staticPipeline ?? null,
10368
+ note: "Named/prebuilt play contract is available. No run was started."
10369
+ };
10370
+ if (options.jsonOutput) {
10371
+ process.stdout.write(`${JSON.stringify(result2)}
10372
+ `);
10373
+ } else {
10374
+ console.log(`\u2713 ${result2.reference} passed named play contract check`);
10375
+ console.log(" no run started; no Deepline credits spent");
10376
+ if (play.runCommand) console.log(` run: ${play.runCommand}`);
10377
+ }
10378
+ return 0;
10379
+ } catch (error) {
10380
+ const resolved = resolve10(options.target);
10381
+ const message = error instanceof Error && error.message ? error.message : `File not found: ${resolved}`;
10382
+ if (options.jsonOutput) {
10383
+ process.stdout.write(
10384
+ `${JSON.stringify({
10385
+ valid: false,
10386
+ target: options.target,
10387
+ errors: [message]
10388
+ })}
10389
+ `
10390
+ );
10391
+ } else {
10392
+ console.error(message);
10393
+ }
10394
+ return 1;
10395
+ }
10399
10396
  }
10400
10397
  const absolutePlayPath = resolve10(options.target);
10401
10398
  const sourceCode = readFileSync6(absolutePlayPath, "utf-8");
@@ -10932,7 +10929,7 @@ async function handleRunLogs(args) {
10932
10929
  const status = await client.runs.get(runId);
10933
10930
  const logs = status.progress?.logs ?? [];
10934
10931
  if (outPath) {
10935
- writeFileSync6(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
10932
+ writeFileSync7(outPath, `${logs.join("\n")}${logs.length > 0 ? "\n" : ""}`);
10936
10933
  printCommandEnvelope(
10937
10934
  {
10938
10935
  runId: status.runId,
@@ -11042,18 +11039,9 @@ async function handleRunExport(args) {
11042
11039
  datasetPath
11043
11040
  });
11044
11041
  const source = exportResult?.rowsInfo.source ?? datasetPath ?? null;
11045
- const queryPlan = exportResult && outPath ? buildCustomerDbQueryPlan({
11046
- status,
11047
- rowsInfo: exportResult.rowsInfo,
11048
- outPath
11049
- }) : null;
11050
11042
  const next = {
11051
11043
  ...buildRunNextCommands(status),
11052
- export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source),
11053
- ...queryPlan ? {
11054
- queryJson: queryPlan.json,
11055
- queryCsv: queryPlan.csv
11056
- } : {}
11044
+ export: runExportRetryCommand(status.runId, outPath, datasetPath ?? source)
11057
11045
  };
11058
11046
  const payload = {
11059
11047
  runId: status.runId,
@@ -11062,13 +11050,6 @@ async function handleRunExport(args) {
11062
11050
  source,
11063
11051
  rowCount: exportResult?.rowsInfo.totalRows ?? null,
11064
11052
  columns: exportResult?.rowsInfo.columns ?? [],
11065
- ...queryPlan ? {
11066
- query: {
11067
- sql: queryPlan.sql,
11068
- json: queryPlan.json,
11069
- csv: queryPlan.csv
11070
- }
11071
- } : {},
11072
11053
  ...metadataOutPath ? { metadata_path: metadataOutPath } : {},
11073
11054
  local: { csv_path: exportResult?.path ?? null },
11074
11055
  next,
@@ -11078,15 +11059,14 @@ async function handleRunExport(args) {
11078
11059
  title: "run export",
11079
11060
  lines: [
11080
11061
  `Exported ${status.runId} to ${exportResult?.path ?? outPath}`,
11081
- ...source ? [`source=${source}`] : [],
11082
- ...queryPlan ? [`query=${queryPlan.json}`] : []
11062
+ ...source ? [`source=${source}`] : []
11083
11063
  ]
11084
11064
  }
11085
11065
  ]
11086
11066
  }
11087
11067
  };
11088
11068
  if (metadataOutPath) {
11089
- writeFileSync6(
11069
+ writeFileSync7(
11090
11070
  metadataOutPath,
11091
11071
  `${JSON.stringify(payload, null, 2)}
11092
11072
  `,
@@ -11259,7 +11239,8 @@ function parsePlaySearchOptions(args) {
11259
11239
  return {
11260
11240
  query,
11261
11241
  jsonOutput: argsWantJson(args),
11262
- compact: args.includes("--compact")
11242
+ compact: args.includes("--compact"),
11243
+ prebuiltOnly: args.includes("--prebuilt")
11263
11244
  };
11264
11245
  }
11265
11246
  function printPlayDescription(play) {
@@ -11307,6 +11288,7 @@ function printPlayDescription(play) {
11307
11288
  console.log(` ${line}`);
11308
11289
  }
11309
11290
  }
11291
+ console.log(` Describe: deepline plays describe ${reference} --json`);
11310
11292
  console.log(` Run: ${play.runCommand}`);
11311
11293
  const cloneEditStarter = play.cloneEditStarter ?? buildCloneEditStarter(play);
11312
11294
  if (cloneEditStarter) {
@@ -11314,6 +11296,31 @@ function printPlayDescription(play) {
11314
11296
  console.log(` Check starter: ${cloneEditStarter.checkCommand}`);
11315
11297
  }
11316
11298
  }
11299
+ function inputFieldNames(schema) {
11300
+ const fields = Array.isArray(schema?.fields) ? schema.fields : [];
11301
+ return fields.map(
11302
+ (field) => field && typeof field === "object" ? String(field.name ?? "").trim() : ""
11303
+ ).filter(Boolean);
11304
+ }
11305
+ function printCompactPlaySearchResult(play) {
11306
+ const reference = formatPlayListReference(play);
11307
+ const aliases = play.aliases.slice(0, 6).join(", ");
11308
+ console.log(`Play: ${reference}`);
11309
+ if (play.displayName && play.displayName !== play.name) {
11310
+ console.log(` Display name: ${play.displayName}`);
11311
+ }
11312
+ if (aliases) {
11313
+ console.log(` Aliases: ${aliases}`);
11314
+ }
11315
+ const fields = inputFieldNames(play.inputSchema);
11316
+ if (fields.length > 0) {
11317
+ console.log(` Inputs: ${fields.join(", ")}`);
11318
+ } else if (play.inputSchema) {
11319
+ console.log(" Inputs: see describe");
11320
+ }
11321
+ console.log(` Describe: deepline plays describe ${reference} --json`);
11322
+ console.log(` Run: ${play.runCommand}`);
11323
+ }
11317
11324
  function compactPlaySchema(schema) {
11318
11325
  if (!schema) return null;
11319
11326
  const fields = Array.isArray(schema.fields) ? schema.fields.map(
@@ -11377,22 +11384,44 @@ async function handlePlaySearch(args) {
11377
11384
  return 1;
11378
11385
  }
11379
11386
  const client = new DeeplineClient();
11380
- const plays = await client.searchPlays({
11387
+ const plays = (await client.searchPlays({
11381
11388
  query: options.query,
11382
11389
  compact: options.compact
11383
- });
11390
+ })).filter(
11391
+ (play) => options.prebuiltOnly ? play.origin === "prebuilt" || play.ownerType === "deepline" : true
11392
+ );
11384
11393
  if (options.jsonOutput) {
11385
- process.stdout.write(`${JSON.stringify({ plays })}
11386
- `);
11394
+ const jsonPlays = options.prebuiltOnly ? plays.map((play) => ({
11395
+ ...play,
11396
+ inputSchema: compactPlaySchema(play.inputSchema)
11397
+ })) : plays;
11398
+ process.stdout.write(
11399
+ `${JSON.stringify({
11400
+ plays: jsonPlays,
11401
+ total: jsonPlays.length,
11402
+ truncated: false
11403
+ })}
11404
+ `
11405
+ );
11387
11406
  return 0;
11388
11407
  }
11389
- process.stdout.write(`${plays.length} plays found:
11408
+ const displayPlays = options.prebuiltOnly ? plays.slice(0, 5) : plays;
11409
+ process.stdout.write(
11410
+ `${plays.length} plays found${options.prebuiltOnly && plays.length > displayPlays.length ? `; showing top ${displayPlays.length}` : ""}:
11390
11411
 
11391
- `);
11392
- for (const play of plays) {
11393
- printPlayDescription(play);
11412
+ `
11413
+ );
11414
+ for (const play of displayPlays) {
11415
+ if (options.prebuiltOnly) {
11416
+ printCompactPlaySearchResult(play);
11417
+ } else {
11418
+ printPlayDescription(play);
11419
+ }
11394
11420
  console.log("");
11395
11421
  }
11422
+ if (options.prebuiltOnly && plays.length > displayPlays.length) {
11423
+ console.log("Use --json for the full machine-readable result set.");
11424
+ }
11396
11425
  return 0;
11397
11426
  }
11398
11427
  function normalizePlayGrepText(value) {
@@ -11659,15 +11688,18 @@ Common commands:
11659
11688
  deepline plays get person-linkedin-to-email --json
11660
11689
  `
11661
11690
  );
11662
- play.command("check <target>").description("Bundle-check a local play file.").addHelpText(
11691
+ play.command("check <target>").description("Check a local play file or named/prebuilt play contract.").addHelpText(
11663
11692
  "after",
11664
11693
  `
11665
11694
  Notes:
11666
11695
  Validates a local play without storing it, promoting it, or starting a run.
11667
11696
  This uses the authoritative cloud preflight path.
11697
+ For named or prebuilt plays, validates that the contract is discoverable
11698
+ without starting a run or spending Deepline credits.
11668
11699
 
11669
11700
  Examples:
11670
11701
  deepline plays check my.play.ts
11702
+ deepline plays check prebuilt/name-and-domain-to-email-waterfall-batch
11671
11703
  deepline plays check my.play.ts --json
11672
11704
  `
11673
11705
  ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (target, options) => {
@@ -11817,11 +11849,13 @@ Examples:
11817
11849
  ...options.json ? ["--json"] : []
11818
11850
  ]);
11819
11851
  });
11820
- const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").addHelpText(
11852
+ const addPlaySearchCommand = (command) => command.description("Search saved and prebuilt plays.").option("--prebuilt", "Only show Deepline-managed prebuilt plays").addHelpText(
11821
11853
  "after",
11822
11854
  `
11823
11855
  Notes:
11824
11856
  Ranked discovery for workflows. Use describe on a result before running it.
11857
+ Prefer --prebuilt for new GTM tasks so old workspace scratchpads do not
11858
+ outrank Deepline-managed routes unless the user names one explicitly.
11825
11859
  The grep alias is the same ranked retrieval surface with a more literal name
11826
11860
  for agents that are filtering the play registry.
11827
11861
 
@@ -11833,6 +11867,7 @@ Examples:
11833
11867
  ).option("--compact", "Emit compact schemas").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (query, options) => {
11834
11868
  process.exitCode = await handlePlaySearch([
11835
11869
  query,
11870
+ ...options.prebuilt ? ["--prebuilt"] : [],
11836
11871
  ...options.compact ? ["--compact"] : [],
11837
11872
  ...options.json ? ["--json"] : []
11838
11873
  ]);
@@ -12080,7 +12115,7 @@ Notes:
12080
12115
  Writes a returned dataset handle to the requested local CSV path. Use runs get
12081
12116
  first to inspect dataset paths like result.rows or result.nested.contacts.
12082
12117
  --metadata-out writes the same export metadata object returned by --json,
12083
- including source and follow-on customer-db query commands when available.
12118
+ including the source dataset path and row/column metadata.
12084
12119
 
12085
12120
  Examples:
12086
12121
  deepline runs export play/my-play/run/20260501t000000-000 --out output.csv
@@ -13787,12 +13822,12 @@ Examples:
13787
13822
 
13788
13823
  // src/cli/commands/tools.ts
13789
13824
  import { Option } from "commander";
13790
- import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
13825
+ import { chmodSync, mkdtempSync, writeFileSync as writeFileSync9 } from "fs";
13791
13826
  import { tmpdir as tmpdir4 } from "os";
13792
13827
  import { join as join10 } from "path";
13793
13828
 
13794
13829
  // src/tool-output.ts
13795
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
13830
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
13796
13831
  import { homedir as homedir5 } from "os";
13797
13832
  import { join as join9 } from "path";
13798
13833
  function isPlainObject(value) {
@@ -13897,7 +13932,7 @@ function ensureOutputDir() {
13897
13932
  function writeJsonOutputFile(payload, stem) {
13898
13933
  const outputDir = ensureOutputDir();
13899
13934
  const outputPath = join9(outputDir, `${stem}_${Date.now()}.json`);
13900
- writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
13935
+ writeFileSync8(outputPath, JSON.stringify(payload, null, 2), "utf-8");
13901
13936
  return outputPath;
13902
13937
  }
13903
13938
  function writeCsvOutputFile(rows, stem) {
@@ -13925,7 +13960,7 @@ function writeCsvOutputFile(rows, stem) {
13925
13960
  for (const row of rows) {
13926
13961
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
13927
13962
  }
13928
- writeFileSync7(outputPath, `${lines.join("\n")}
13963
+ writeFileSync8(outputPath, `${lines.join("\n")}
13929
13964
  `, "utf-8");
13930
13965
  const previewRows = rows.slice(0, 5);
13931
13966
  const previewColumns = columns.slice(0, 5);
@@ -14011,7 +14046,7 @@ async function listTools(args) {
14011
14046
  const client = new DeeplineClient();
14012
14047
  const categoryArgIndex = args.findIndex((arg) => arg === "--categories");
14013
14048
  const categoryFilter = categoryArgIndex >= 0 ? args[categoryArgIndex + 1] : "";
14014
- const compact = args.includes("--compact");
14049
+ const compact = args.includes("--compact") || !args.includes("--json");
14015
14050
  const requestedCategories = categoryFilter ? categoryFilter.split(",").map((item) => item.trim()).filter(Boolean) : [];
14016
14051
  const items = (await client.listTools({
14017
14052
  ...categoryFilter ? { categories: categoryFilter } : {}
@@ -14067,9 +14102,10 @@ async function searchTools(queryInput, options = {}) {
14067
14102
  searchMode: options.searchMode,
14068
14103
  includeSearchDebug: options.includeSearchDebug
14069
14104
  });
14070
- const payload = options.compact && Array.isArray(result.tools) ? {
14105
+ const shouldCompact = options.compact || !options.json;
14106
+ const payload = shouldCompact && Array.isArray(result.tools) ? {
14071
14107
  ...result,
14072
- tools: result.tools.map(compactTool)
14108
+ tools: result.tools.slice(0, 8).map(compactTool)
14073
14109
  } : result;
14074
14110
  printCommandEnvelope(payload, {
14075
14111
  json: options.json || shouldEmitJson()
@@ -14110,7 +14146,8 @@ async function grepTools(queryInput, options = {}) {
14110
14146
  mode
14111
14147
  )
14112
14148
  );
14113
- const outputTools = options.compact ? tools.map(compactTool) : tools;
14149
+ const shouldCompact = options.compact || !options.json;
14150
+ const outputTools = shouldCompact ? tools.slice(0, 8).map(compactTool) : tools;
14114
14151
  printCommandEnvelope(
14115
14152
  {
14116
14153
  tools: outputTools,
@@ -14129,11 +14166,19 @@ async function grepTools(queryInput, options = {}) {
14129
14166
  );
14130
14167
  return 0;
14131
14168
  }
14169
+ function numericToolField(tool, field) {
14170
+ const value = tool[field];
14171
+ return typeof value === "number" ? value : void 0;
14172
+ }
14132
14173
  function compactTool(tool) {
14133
14174
  const listed = toListedTool(tool);
14175
+ const searchScore = numericToolField(tool, "searchScore");
14176
+ const search_score = numericToolField(tool, "search_score");
14134
14177
  return {
14135
14178
  id: listed.id,
14136
14179
  toolId: listed.toolId,
14180
+ ...search_score !== void 0 ? { search_score } : {},
14181
+ ...searchScore !== void 0 ? { searchScore } : {},
14137
14182
  provider: listed.provider,
14138
14183
  displayName: listed.displayName,
14139
14184
  description: listed.description,
@@ -14454,7 +14499,7 @@ async function getTool(toolId, options = {}) {
14454
14499
  }
14455
14500
  if (shouldEmitJson()) {
14456
14501
  process.stdout.write(
14457
- `${JSON.stringify(toolMetadataJsonForDescribe(tool, toolId))}
14502
+ `${JSON.stringify(toolContractJsonForDescribe(tool, toolId))}
14458
14503
  `
14459
14504
  );
14460
14505
  return 0;
@@ -14779,8 +14824,8 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
14779
14824
  invalidGetterHint: "If TypeScript says an extractedValues/extractedLists property does not exist, that field is not a declared Deepline getter.",
14780
14825
  observeActualShape: `deepline tools execute ${toolId} --input '{...}' --json`,
14781
14826
  observedOutput: `deepline tools execute ${toolId} --input '{...}' --json`,
14782
- forPlayGetterBugs: "Run the play, then inspect the emitted table commands from runs get. Use deepline db query against the run tables before editing getters.",
14783
- executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run tables."
14827
+ 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.",
14828
+ executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run output and returned dataset handles."
14784
14829
  },
14785
14830
  starterScript: {
14786
14831
  path: starterScript.path,
@@ -15031,7 +15076,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
15031
15076
  };
15032
15077
  });
15033
15078
  `;
15034
- writeFileSync8(scriptPath, script, { encoding: "utf-8", mode: 384 });
15079
+ writeFileSync9(scriptPath, script, { encoding: "utf-8", mode: 384 });
15035
15080
  return {
15036
15081
  path: scriptPath,
15037
15082
  sourceCode: script,
@@ -15587,7 +15632,7 @@ import {
15587
15632
  readdirSync as readdirSync2,
15588
15633
  readFileSync as readFileSync7,
15589
15634
  statSync as statSync2,
15590
- writeFileSync as writeFileSync9
15635
+ writeFileSync as writeFileSync10
15591
15636
  } from "fs";
15592
15637
  import { homedir as homedir6 } from "os";
15593
15638
  import { dirname as dirname10, join as join12 } from "path";
@@ -15621,7 +15666,7 @@ function readLocalSkillsVersion(baseUrl) {
15621
15666
  function writeLocalSkillsVersion(baseUrl, version) {
15622
15667
  const path = sdkSkillsVersionPath(baseUrl);
15623
15668
  mkdirSync5(dirname10(path), { recursive: true });
15624
- writeFileSync9(path, `${version}
15669
+ writeFileSync10(path, `${version}
15625
15670
  `, "utf-8");
15626
15671
  }
15627
15672
  function installedSdkSkillHasStalePositionalExecuteExamples() {
@@ -15804,6 +15849,7 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
15804
15849
  }
15805
15850
 
15806
15851
  // src/cli/index.ts
15852
+ var PREFLIGHT_TIMEOUT_MS = 3e3;
15807
15853
  function asCommanderError(error) {
15808
15854
  if (!(error instanceof Error) || !("code" in error)) {
15809
15855
  return null;
@@ -15884,6 +15930,124 @@ async function runPlayRunnerHealthCheck() {
15884
15930
  await rm2(dir, { recursive: true, force: true });
15885
15931
  }
15886
15932
  }
15933
+ function pickString(value, ...keys) {
15934
+ for (const key of keys) {
15935
+ const candidate = value[key];
15936
+ if (typeof candidate === "string" && candidate.trim()) {
15937
+ return candidate;
15938
+ }
15939
+ }
15940
+ return null;
15941
+ }
15942
+ function preflightErrorMessage(error) {
15943
+ return error instanceof Error ? error.message : String(error);
15944
+ }
15945
+ async function runPreflightCheck() {
15946
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
15947
+ const healthController = new AbortController();
15948
+ const healthTimeout = setTimeout(
15949
+ () => healthController.abort(),
15950
+ PREFLIGHT_TIMEOUT_MS
15951
+ );
15952
+ const health = await fetch(new URL("/api/v2/health", baseUrl), {
15953
+ signal: healthController.signal
15954
+ }).then(async (response) => {
15955
+ const payload = await response.json().catch(() => ({}));
15956
+ return {
15957
+ status: response.ok ? pickString(payload, "status") ?? "ok" : "unreachable",
15958
+ version: pickString(payload, "version")
15959
+ };
15960
+ }).catch(() => ({ status: "unreachable", version: null })).finally(() => clearTimeout(healthTimeout));
15961
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
15962
+ const http = apiKey ? new HttpClient(
15963
+ resolveConfig({
15964
+ baseUrl,
15965
+ apiKey,
15966
+ timeout: PREFLIGHT_TIMEOUT_MS,
15967
+ maxRetries: 0
15968
+ })
15969
+ ) : null;
15970
+ const [auth, billing] = http ? await Promise.all([
15971
+ http.post("/api/v2/auth/cli/status", {
15972
+ api_key: apiKey,
15973
+ reveal: false
15974
+ }).catch((error) => ({
15975
+ status: "not_connected",
15976
+ connected: false,
15977
+ error: preflightErrorMessage(error)
15978
+ })),
15979
+ http.get("/api/v2/billing/balance").catch(
15980
+ (error) => ({
15981
+ balance: null,
15982
+ balance_display: "unavailable",
15983
+ balance_status: "unknown",
15984
+ error: preflightErrorMessage(error)
15985
+ })
15986
+ )
15987
+ ]) : [
15988
+ {
15989
+ status: "not_connected",
15990
+ connected: false,
15991
+ error: "No API key found. Run: deepline auth register"
15992
+ },
15993
+ {
15994
+ balance: null,
15995
+ balance_display: "unavailable until authenticated",
15996
+ balance_status: "unknown"
15997
+ }
15998
+ ];
15999
+ const authStatus = pickString(auth, "status") ?? "unknown";
16000
+ const billingRecord = billing;
16001
+ const balanceDisplay = pickString(billing, "balance_display", "balanceDisplay") ?? `${String(billingRecord.balance ?? 0)} Deepline Credits`;
16002
+ const balanceStatus = pickString(billing, "balance_status", "balanceStatus") ?? "unknown";
16003
+ return {
16004
+ status: health.status === "ok" && (authStatus === "connected" || authStatus === "claimed") ? "ok" : "check",
16005
+ host: baseUrl,
16006
+ health: {
16007
+ status: health.status,
16008
+ version: health.version ?? null
16009
+ },
16010
+ auth: {
16011
+ status: authStatus,
16012
+ connected: authStatus === "connected" || authStatus === "claimed",
16013
+ org_id: pickString(auth, "org_id", "orgId"),
16014
+ org_name: pickString(auth, "org_name", "orgName"),
16015
+ rate_limit_tier: pickString(auth, "rate_limit_tier", "rateLimitTier"),
16016
+ error: pickString(auth, "error")
16017
+ },
16018
+ billing: {
16019
+ balance: billingRecord.balance ?? null,
16020
+ balance_display: balanceDisplay,
16021
+ rough_usd_balance: billingRecord.rough_usd_balance ?? billingRecord.roughUsdBalance ?? null,
16022
+ balance_status: balanceStatus,
16023
+ error: pickString(billing, "error")
16024
+ },
16025
+ render: {
16026
+ sections: [
16027
+ {
16028
+ title: "preflight",
16029
+ lines: [
16030
+ `host: ${baseUrl}`,
16031
+ `health: ${health.status}`,
16032
+ `auth: ${authStatus}`,
16033
+ `billing: ${balanceDisplay} (${balanceStatus})`
16034
+ ]
16035
+ }
16036
+ ]
16037
+ }
16038
+ };
16039
+ }
16040
+ function printPreflightHuman(data) {
16041
+ const render = data.render;
16042
+ const lines = render?.sections?.flatMap((section) => section.lines ?? []);
16043
+ if (lines?.length) {
16044
+ process.stdout.write(`${lines.join("\n")}
16045
+ `);
16046
+ return;
16047
+ }
16048
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
16049
+ `);
16050
+ }
15887
16051
  async function main() {
15888
16052
  const mainStartedAt = Date.now();
15889
16053
  recordCliTrace({
@@ -15900,6 +16064,7 @@ async function main() {
15900
16064
  "after",
15901
16065
  `
15902
16066
  Common commands:
16067
+ deepline preflight
15903
16068
  deepline health
15904
16069
  deepline auth status --json
15905
16070
  deepline plays search email --json
@@ -15921,7 +16086,6 @@ Output:
15921
16086
 
15922
16087
  Safety:
15923
16088
  Commands that mutate state, open a browser, write files, stop work, or spend credits say so in their help.
15924
- Use --no-open where available for CI and agent runs.
15925
16089
 
15926
16090
  Exit codes:
15927
16091
  0 success; 2 usage/local input error; 3 auth/permission error; 4 not found;
@@ -15963,6 +16127,26 @@ Exit codes:
15963
16127
  registerDbCommands(program);
15964
16128
  registerFeedbackCommands(program);
15965
16129
  registerUpdateCommand(program);
16130
+ program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(
16131
+ "after",
16132
+ `
16133
+ Notes:
16134
+ Read-only setup check for the configured Deepline host. Shows server health,
16135
+ auth connection, and customer-visible Deepline balance in one compact command.
16136
+
16137
+ Examples:
16138
+ deepline preflight
16139
+ deepline preflight --json
16140
+ `
16141
+ ).action(async (options) => {
16142
+ const data = await runPreflightCheck();
16143
+ if (shouldEmitJson(options.json)) {
16144
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
16145
+ `);
16146
+ } else {
16147
+ printPreflightHuman(data);
16148
+ }
16149
+ });
15966
16150
  program.command("health").description("Check server health.").option("--json", "Force JSON output.").option(
15967
16151
  "--play-runner",
15968
16152
  "Run a tiny no-provider play to verify the full play execution plane."