deepline 0.1.73 → 0.1.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli/index.ts
4
- import { mkdtemp, rm, writeFile as writeFile4 } from "fs/promises";
5
- import { join as join12 } from "path";
6
- import { tmpdir as tmpdir4 } from "os";
4
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile5 } from "fs/promises";
5
+ import { join as join13 } from "path";
6
+ import { tmpdir as tmpdir5 } from "os";
7
7
  import { Command as Command3 } from "commander";
8
8
 
9
9
  // src/config.ts
@@ -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.73",
210
- apiContract: "2026-06-dataset-column-syntax-cutover",
209
+ version: "0.1.76",
210
+ apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
211
211
  supportPolicy: {
212
- latest: "0.1.73",
212
+ latest: "0.1.76",
213
213
  minimumSupported: "0.1.53",
214
214
  deprecatedBelow: "0.1.53"
215
215
  }
@@ -556,7 +556,7 @@ function decodeSseFrame(frame) {
556
556
  return parsed;
557
557
  }
558
558
  function sleep(ms) {
559
- return new Promise((resolve12) => setTimeout(resolve12, ms));
559
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
560
560
  }
561
561
 
562
562
  // src/client.ts
@@ -566,7 +566,7 @@ var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
566
566
  var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-response";
567
567
  var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
568
568
  function sleep2(ms) {
569
- return new Promise((resolve12) => setTimeout(resolve12, ms));
569
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
570
570
  }
571
571
  function isTransientCompileManifestError(error) {
572
572
  if (error instanceof DeeplineError && typeof error.statusCode === "number") {
@@ -1092,6 +1092,9 @@ var DeeplineClient = class {
1092
1092
  async checkPlayArtifact(input2) {
1093
1093
  return this.http.post("/api/v2/plays/check", input2);
1094
1094
  }
1095
+ async compileEnrichPlan(input2) {
1096
+ return this.http.post("/api/v2/enrich/compile", input2);
1097
+ }
1095
1098
  async startPlayRunFromBundle(input2) {
1096
1099
  const compilerManifest = input2.compilerManifest ?? await this.compilePlayManifest({
1097
1100
  name: input2.name,
@@ -2380,7 +2383,7 @@ function buildCandidateUrls2(url) {
2380
2383
  }
2381
2384
  }
2382
2385
  function sleep3(ms) {
2383
- return new Promise((resolve12) => setTimeout(resolve12, ms));
2386
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
2384
2387
  }
2385
2388
  function printDeeplineLogo() {
2386
2389
  if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
@@ -3758,38 +3761,38 @@ function buildDatasetStats(rows, totalRows = rows.length, columns = inferColumns
3758
3761
  }
3759
3762
  }
3760
3763
  const denominator = nonEmpty + empty;
3761
- const stat3 = {
3764
+ const stat4 = {
3762
3765
  non_empty: percentText(nonEmpty, denominator),
3763
3766
  unique: valueCounts.size
3764
3767
  };
3765
3768
  const rawExecutionStats = executionStats?.columnStats[column];
3766
3769
  if (rawExecutionStats) {
3767
- stat3.execution = formatDatasetExecutionStats(
3770
+ stat4.execution = formatDatasetExecutionStats(
3768
3771
  rawExecutionStats,
3769
3772
  totalRows
3770
3773
  );
3771
3774
  }
3772
3775
  if (sampleValue !== void 0 && sampleValueType) {
3773
- stat3.sample_value = sampleValue;
3774
- stat3.sample_type = sampleValueType;
3776
+ stat4.sample_value = sampleValue;
3777
+ stat4.sample_type = sampleValueType;
3775
3778
  }
3776
3779
  if (valueCounts.size > 0 && valueCounts.size < nonEmpty) {
3777
3780
  const top = [...valueCounts.entries()].sort((left, right) => right[1] - left[1]).slice(0, 3);
3778
3781
  const topKeys = new Set(top.map(([key]) => key));
3779
3782
  const otherCount = [...valueCounts.entries()].filter(([key]) => !topKeys.has(key)).reduce((sum, [, count]) => sum + count, 0);
3780
- stat3.top_values = Object.fromEntries(
3783
+ stat4.top_values = Object.fromEntries(
3781
3784
  top.map(([key, count]) => [key, countPercentText(count, denominator)])
3782
3785
  );
3783
3786
  if (otherCount > 0) {
3784
- stat3.top_values["(other)"] = countPercentText(otherCount, denominator);
3787
+ stat4.top_values["(other)"] = countPercentText(otherCount, denominator);
3785
3788
  }
3786
3789
  if (empty > 0) {
3787
- stat3.top_values["(null)"] = countPercentText(empty, denominator);
3790
+ stat4.top_values["(null)"] = countPercentText(empty, denominator);
3788
3791
  }
3789
3792
  } else if (empty > 0 && nonEmpty > 0) {
3790
- stat3.top_values = { "(null)": countPercentText(empty, denominator) };
3793
+ stat4.top_values = { "(null)": countPercentText(empty, denominator) };
3791
3794
  }
3792
- columnStats[column] = stat3;
3795
+ columnStats[column] = stat4;
3793
3796
  }
3794
3797
  return {
3795
3798
  total_rows: totalRows,
@@ -4213,210 +4216,10 @@ Examples:
4213
4216
  });
4214
4217
  }
4215
4218
 
4216
- // src/cli/commands/feedback.ts
4217
- async function handleFeedback(text, options) {
4218
- const { http } = getAuthedHttpClient();
4219
- const response = await http.post("/api/v2/cli/feedback", {
4220
- text,
4221
- environment: collectLocalEnvInfo(),
4222
- ...options.command ? { command: options.command } : {},
4223
- ...options.payload ? { payload: options.payload } : {}
4224
- });
4225
- printCommandEnvelope(
4226
- {
4227
- ...response,
4228
- render: {
4229
- sections: [
4230
- { title: "feedback", lines: ["Feedback submitted. Thank you."] }
4231
- ]
4232
- }
4233
- },
4234
- { json: options.json }
4235
- );
4236
- }
4237
- function registerFeedbackCommands(program) {
4238
- const feedback = program.command("feedback").description("Submit CLI feedback to Deepline.").addHelpText(
4239
- "after",
4240
- `
4241
- Notes:
4242
- Sends the feedback text plus local CLI environment info to Deepline support.
4243
- Use --command and --payload to attach a reproducible command shape.
4244
-
4245
- Examples:
4246
- deepline feedback "plays run failed after upload" --command "deepline plays run my.play.ts --watch"
4247
- deepline feedback "unexpected billing output" --payload '{"command":"billing usage"}' --json
4248
- `
4249
- );
4250
- feedback.argument("<text>", "Feedback text").option("--command <command>", "Command that reproduced the issue").option("--payload <payload>", "JSON or plain-text payload for the repro").option("--json", "Emit JSON output").action(handleFeedback);
4251
- program.command("provide-feedback").description("Legacy alias for `deepline feedback`.").addHelpText(
4252
- "after",
4253
- `
4254
- Notes:
4255
- Compatibility alias. Prefer deepline feedback in new scripts and docs.
4256
-
4257
- Examples:
4258
- deepline feedback "tools search returned stale results" --json
4259
- `
4260
- ).argument("<text>", "Feedback text").option("--command <command>", "Command that reproduced the issue").option("--payload <payload>", "JSON or plain-text payload for the repro").option("--json", "Emit JSON output").action(handleFeedback);
4261
- }
4262
-
4263
- // src/cli/commands/org.ts
4264
- async function fetchOrganizations(http, apiKey) {
4265
- return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
4266
- }
4267
- function orgListLines(orgs) {
4268
- return orgs.map((org, index) => {
4269
- const current = org.is_current ? " (current)" : "";
4270
- const role = org.role ? ` [${org.role}]` : "";
4271
- return `${index + 1}. ${org.name}${role}${current}`;
4272
- });
4273
- }
4274
- async function handleOrgList(options) {
4275
- const config = resolveConfig();
4276
- const http = new HttpClient(config);
4277
- const payload = await fetchOrganizations(http, config.apiKey);
4278
- printCommandEnvelope(
4279
- {
4280
- ...payload,
4281
- render: {
4282
- sections: [
4283
- {
4284
- title: "Your organizations:",
4285
- lines: orgListLines(payload.organizations)
4286
- }
4287
- ]
4288
- }
4289
- },
4290
- { json: options.json }
4291
- );
4292
- }
4293
- async function handleOrgSwitch(selection, options) {
4294
- const config = resolveConfig();
4295
- const http = new HttpClient(config);
4296
- const payload = await fetchOrganizations(http, config.apiKey);
4297
- if (!selection && !options.orgId) {
4298
- printCommandEnvelope(
4299
- {
4300
- ...payload,
4301
- next: { switch: "deepline org switch <number>" },
4302
- render: {
4303
- sections: [
4304
- {
4305
- title: "Your organizations:",
4306
- lines: orgListLines(payload.organizations)
4307
- }
4308
- ],
4309
- actions: [{ label: "Run", command: "deepline org switch <number>" }]
4310
- }
4311
- },
4312
- { json: options.json }
4313
- );
4314
- return;
4315
- }
4316
- let target = payload.organizations.find(
4317
- (org) => org.org_id === options.orgId
4318
- );
4319
- if (!target && selection) {
4320
- const index = Number.parseInt(selection, 10);
4321
- if (Number.isFinite(index) && index >= 1 && index <= payload.organizations.length) {
4322
- target = payload.organizations[index - 1];
4323
- } else {
4324
- target = payload.organizations.find(
4325
- (org) => org.name === selection || org.org_id === selection
4326
- );
4327
- }
4328
- }
4329
- if (!target) {
4330
- throw new Error("Could not resolve the selected organization.");
4331
- }
4332
- if (target.is_current) {
4333
- printCommandEnvelope(
4334
- {
4335
- ok: true,
4336
- unchanged: true,
4337
- organization: target,
4338
- render: {
4339
- sections: [
4340
- { title: "org switch", lines: [`Already on ${target.name}.`] }
4341
- ]
4342
- }
4343
- },
4344
- { json: options.json }
4345
- );
4346
- return;
4347
- }
4348
- const switched = await http.post("/api/v2/auth/cli/switch", {
4349
- api_key: config.apiKey,
4350
- org_id: target.org_id
4351
- });
4352
- saveHostEnvValues(config.baseUrl, {
4353
- DEEPLINE_API_KEY: switched.api_key,
4354
- DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
4355
- DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
4356
- });
4357
- const { api_key: _apiKey, ...publicSwitched } = switched;
4358
- printCommandEnvelope(
4359
- {
4360
- ok: true,
4361
- host_env_path: hostEnvFilePath(config.baseUrl),
4362
- ...publicSwitched,
4363
- api_key_saved: true,
4364
- render: {
4365
- sections: [
4366
- {
4367
- title: "org switch",
4368
- lines: [
4369
- `Switched to ${switched.org_name}.`,
4370
- `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
4371
- ]
4372
- }
4373
- ]
4374
- }
4375
- },
4376
- { json: options.json }
4377
- );
4378
- }
4379
- function registerOrgCommands(program) {
4380
- const org = program.command("org").description("List and switch organizations.").addHelpText(
4381
- "after",
4382
- `
4383
- Notes:
4384
- Organizations are workspaces. Switching organizations mutates the saved host
4385
- auth file so later CLI commands target the selected workspace.
4386
-
4387
- Examples:
4388
- deepline org list --json
4389
- deepline org switch 2
4390
- deepline org switch --org-id org_123 --json
4391
- `
4392
- );
4393
- org.command("list").description("List your organizations.").addHelpText(
4394
- "after",
4395
- `
4396
- Notes:
4397
- Read-only. Marks the active organization when the server returns that metadata.
4398
-
4399
- Examples:
4400
- deepline org list
4401
- deepline org list --json
4402
- `
4403
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
4404
- org.command("switch [selection]").description(
4405
- "Switch to another organization and save the new API key in the host auth file."
4406
- ).addHelpText(
4407
- "after",
4408
- `
4409
- Notes:
4410
- Mutates the saved host auth file. Selection can be a list number, exact
4411
- organization name, or organization id. Without a selection, prints choices.
4412
-
4413
- Examples:
4414
- deepline org switch
4415
- deepline org switch 2
4416
- deepline org switch --org-id org_123 --json
4417
- `
4418
- ).option("--org-id <id>", "Switch using an explicit organization id").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgSwitch);
4419
- }
4219
+ // src/cli/commands/enrich.ts
4220
+ import { mkdtemp, readFile as readFile3, rm, stat as stat3, writeFile as writeFile4 } from "fs/promises";
4221
+ import { homedir as homedir4, tmpdir as tmpdir3 } from "os";
4222
+ import { join as join8, resolve as resolve11 } from "path";
4420
4223
 
4421
4224
  // src/cli/commands/play.ts
4422
4225
  import { createHash as createHash3 } from "crypto";
@@ -6721,54 +6524,54 @@ function exampleValueComment(field) {
6721
6524
  if (field === "roles" || field.endsWith("s")) return '["..."]';
6722
6525
  return '"..."';
6723
6526
  }
6724
- function generateContactInputObjectFromSchema(schema, indent, label, fallbackFields = ["first_name", "last_name", "domain"]) {
6527
+ function generateContactInputObjectFromSchema(schema, indent2, label, fallbackFields = ["first_name", "last_name", "domain"]) {
6725
6528
  const details = schemaFieldDetails(schema);
6726
6529
  const required = details.required.length ? details.required : fallbackFields;
6727
6530
  const optional = details.optional;
6728
6531
  const lines = [
6729
- `${indent}// TODO: map row fields into ${label}.`,
6730
- ...playInspectionComments(label, indent),
6731
- `${indent}// Required: ${required.join(", ") || "none declared"}.`
6532
+ `${indent2}// TODO: map row fields into ${label}.`,
6533
+ ...playInspectionComments(label, indent2),
6534
+ `${indent2}// Required: ${required.join(", ") || "none declared"}.`
6732
6535
  ];
6733
6536
  for (const field of required) {
6734
- lines.push(`${indent}// ${field}: row["TODO_SOURCE_FIELD"],`);
6537
+ lines.push(`${indent2}// ${field}: row["TODO_SOURCE_FIELD"],`);
6735
6538
  }
6736
6539
  if (optional.length > 0) {
6737
6540
  lines.push("");
6738
- lines.push(`${indent}// optional (delete unused):`);
6541
+ lines.push(`${indent2}// optional (delete unused):`);
6739
6542
  for (const field of optional) {
6740
- lines.push(`${indent}// ${field}: row["TODO_SOURCE_FIELD"],`);
6543
+ lines.push(`${indent2}// ${field}: row["TODO_SOURCE_FIELD"],`);
6741
6544
  }
6742
6545
  }
6743
6546
  return `{
6744
6547
  ${lines.join("\n")}
6745
- ${indent.slice(2)}}`;
6548
+ ${indent2.slice(2)}}`;
6746
6549
  }
6747
- function generateCompanyInputObjectFromSchema(schema, indent, label, fallbackFields = ["domain", "company_name"]) {
6550
+ function generateCompanyInputObjectFromSchema(schema, indent2, label, fallbackFields = ["domain", "company_name"]) {
6748
6551
  const details = schemaFieldDetails(schema);
6749
6552
  const required = details.required.length ? details.required : fallbackFields;
6750
6553
  const optional = details.optional;
6751
6554
  const lines = [
6752
- `${indent}// TODO: map company fields into ${label}.`,
6753
- ...playInspectionComments(label, indent),
6754
- `${indent}// Required: ${required.join(", ") || "none declared"}.`
6555
+ `${indent2}// TODO: map company fields into ${label}.`,
6556
+ ...playInspectionComments(label, indent2),
6557
+ `${indent2}// Required: ${required.join(", ") || "none declared"}.`
6755
6558
  ];
6756
6559
  for (const field of required) {
6757
- lines.push(`${indent}// ${field}: company["TODO_SOURCE_FIELD"],`);
6560
+ lines.push(`${indent2}// ${field}: company["TODO_SOURCE_FIELD"],`);
6758
6561
  }
6759
6562
  if (optional.length > 0) {
6760
6563
  lines.push("");
6761
- lines.push(`${indent}// optional (delete unused):`);
6564
+ lines.push(`${indent2}// optional (delete unused):`);
6762
6565
  for (const field of optional) {
6763
- lines.push(`${indent}// ${field}: company["TODO_SOURCE_FIELD"],`);
6566
+ lines.push(`${indent2}// ${field}: company["TODO_SOURCE_FIELD"],`);
6764
6567
  }
6765
6568
  }
6766
6569
  return `{
6767
6570
  ${lines.join("\n")}
6768
- ${indent.slice(2)}}`;
6571
+ ${indent2.slice(2)}}`;
6769
6572
  }
6770
6573
  function generateSourceProviderInputObject(input2) {
6771
- const { tool, indent, label, entity } = input2;
6574
+ const { tool, indent: indent2, label, entity } = input2;
6772
6575
  const properties = inputPropertyNames(tool?.inputSchema);
6773
6576
  const details = schemaFieldDetails(tool?.inputSchema);
6774
6577
  const required = details.required;
@@ -6789,18 +6592,18 @@ function generateSourceProviderInputObject(input2) {
6789
6592
  ].includes(field)
6790
6593
  );
6791
6594
  const lines = [
6792
- `${indent}// TODO: fill ${entity} source inputs for ${label}.`,
6793
- `${indent}// Inspect: deepline tools describe ${label} --json`
6595
+ `${indent2}// TODO: fill ${entity} source inputs for ${label}.`,
6596
+ `${indent2}// Inspect: deepline tools describe ${label} --json`
6794
6597
  ];
6795
6598
  for (const field of required) {
6796
- lines.push(`${indent}// ${field}: ${exampleValueComment(field)},`);
6599
+ lines.push(`${indent2}// ${field}: ${exampleValueComment(field)},`);
6797
6600
  }
6798
6601
  const activeTodoField = required.length === 0 ? ["query", "q", "search", "title", "role", "persona"].find(
6799
6602
  (field) => includeOptional.includes(field)
6800
6603
  ) ?? "query" : null;
6801
6604
  if (activeTodoField) {
6802
6605
  lines.push(
6803
- `${indent}// ${activeTodoField}: ${exampleValueComment(activeTodoField)},`
6606
+ `${indent2}// ${activeTodoField}: ${exampleValueComment(activeTodoField)},`
6804
6607
  );
6805
6608
  }
6806
6609
  const optionalExamples = includeOptional.filter(
@@ -6808,40 +6611,40 @@ function generateSourceProviderInputObject(input2) {
6808
6611
  );
6809
6612
  if (optionalExamples.length > 0) {
6810
6613
  lines.push("");
6811
- lines.push(`${indent}// optional - uncomment what this provider supports:`);
6614
+ lines.push(`${indent2}// optional - uncomment what this provider supports:`);
6812
6615
  for (const field of optionalExamples) {
6813
6616
  if (field === "limit" || field === "numResults" || field === "num_results" || field === "page_size") {
6814
- lines.push(`${indent}// ${field}: limit,`);
6617
+ lines.push(`${indent2}// ${field}: limit,`);
6815
6618
  } else {
6816
- lines.push(`${indent}// ${field}: ${exampleValueComment(field)},`);
6619
+ lines.push(`${indent2}// ${field}: ${exampleValueComment(field)},`);
6817
6620
  }
6818
6621
  }
6819
6622
  }
6820
6623
  if (!required.some(
6821
6624
  (field) => ["limit", "numResults", "num_results", "page_size"].includes(field)
6822
6625
  )) {
6823
- lines.push(`${indent}limit,`);
6626
+ lines.push(`${indent2}limit,`);
6824
6627
  }
6825
6628
  return `{
6826
6629
  ${lines.join("\n")}
6827
- ${indent.slice(2)}}`;
6630
+ ${indent2.slice(2)}}`;
6828
6631
  }
6829
6632
  function generatePlayInputObject(input2) {
6830
- const { schema, indent, label, entity } = input2;
6633
+ const { schema, indent: indent2, label, entity } = input2;
6831
6634
  const details = schemaFieldDetails(schema);
6832
6635
  const fallback = entity === "company" ? ["domain", "company_name"] : ["first_name", "last_name", "domain"];
6833
6636
  const required = details.required.length ? details.required : fallback;
6834
6637
  const lines = [
6835
- `${indent}// TODO: fill source play inputs for ${label}.`,
6836
- ...playInspectionComments(label, indent)
6638
+ `${indent2}// TODO: fill source play inputs for ${label}.`,
6639
+ ...playInspectionComments(label, indent2)
6837
6640
  ];
6838
6641
  for (const field of required) {
6839
- lines.push(`${indent}// ${field}: ${exampleValueComment(field)},`);
6642
+ lines.push(`${indent2}// ${field}: ${exampleValueComment(field)},`);
6840
6643
  }
6841
- if (!required.includes("limit")) lines.push(`${indent}limit,`);
6644
+ if (!required.includes("limit")) lines.push(`${indent2}limit,`);
6842
6645
  return `{
6843
6646
  ${lines.join("\n")}
6844
- ${indent.slice(2)}}`;
6647
+ ${indent2.slice(2)}}`;
6845
6648
  }
6846
6649
  function requiredPlayInputFields(play) {
6847
6650
  const schema = play?.inputSchema;
@@ -6882,15 +6685,12 @@ function finderProviderStepPrefix(finder) {
6882
6685
  function safeIdentifier(value) {
6883
6686
  return value.replace(/[^A-Za-z0-9_]+/g, "_");
6884
6687
  }
6885
- function playInspectionComments(playRef, indent) {
6886
- return [`${indent}// Inspect: deepline plays describe ${playRef} --json`];
6688
+ function playInspectionComments(playRef, indent2) {
6689
+ return [`${indent2}// Inspect: deepline plays describe ${playRef} --json`];
6887
6690
  }
6888
6691
  function accessorExpression(base, field) {
6889
6692
  return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(field) ? `${base}.${field}` : `${base}[${jsString(field)}]`;
6890
6693
  }
6891
- function needsRunIfImport(options) {
6892
- return stageProviders(options.email).length > 1 || stageProviders(options.phone).length > 1;
6893
- }
6894
6694
  function sourceCollectionTypeName(entity) {
6895
6695
  return entity === "company" ? "CompanySourceRow" : "ContactSourceRow";
6896
6696
  }
@@ -7241,10 +7041,10 @@ function generateFinderProviderStep(input2) {
7241
7041
  const priorCandidates = input2.stepNames.slice(0, input2.index).map((name) => `row.${name}`).join(", ");
7242
7042
  return `.withColumn(
7243
7043
  ${jsString(stepName)},
7244
- runIf(
7245
- (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7246
- ${input2.resolver},
7247
- ),
7044
+ ${input2.resolver},
7045
+ {
7046
+ runIf: (row) => ![${priorCandidates}].some((candidate) => ${optionalFinderValueExpression("candidate", input2.outputField)}),
7047
+ },
7248
7048
  )`;
7249
7049
  }
7250
7050
  }
@@ -7275,7 +7075,7 @@ function generateFinderProviderWaterfall(input2) {
7275
7075
  );
7276
7076
  const candidateNames = stepNames.map((name) => `row.${name}`).join(", ");
7277
7077
  return `// ${input2.aggregateStepName} provider waterfall. Each provider leg is active once its TODO throw is removed;
7278
- // delete or comment out legs you do not want before running. Later legs are gated with runIf(...).
7078
+ // delete or comment out legs you do not want before running. Later legs are gated with { runIf }.
7279
7079
  ${providerSteps.join("\n ")}
7280
7080
  .withColumn(${jsString(input2.aggregateStepName)}, (row) => {
7281
7081
  const candidates = [${candidateNames}];
@@ -7332,8 +7132,7 @@ function generateBootstrapPlaySource(input2) {
7332
7132
  rowTypeDefinitions,
7333
7133
  finderPlayResultTypes
7334
7134
  ].filter((definition) => definition.trim().length > 0).join("\n\n");
7335
- const importNames = needsRunIfImport(input2.options) ? "definePlay, runIf" : "definePlay";
7336
- return `import { ${importNames} } from 'deepline';
7135
+ return `import { definePlay } from 'deepline';
7337
7136
 
7338
7137
  ${typeDefinitions}
7339
7138
 
@@ -7986,7 +7785,7 @@ function traceCliSync(phase, fields, run) {
7986
7785
  }
7987
7786
  }
7988
7787
  function sleep4(ms) {
7989
- return new Promise((resolve12) => setTimeout(resolve12, ms));
7788
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
7990
7789
  }
7991
7790
  function parseReferencedPlayTarget2(target) {
7992
7791
  const trimmed = target.trim();
@@ -9629,20 +9428,20 @@ function attachDatasetStatsToResult(result, datasetStats) {
9629
9428
  };
9630
9429
  return attach(result, "");
9631
9430
  }
9632
- function formatDatasetStatsLines(datasetStats, indent = " ") {
9431
+ function formatDatasetStatsLines(datasetStats, indent2 = " ") {
9633
9432
  if (!datasetStats) {
9634
9433
  return [];
9635
9434
  }
9636
- const lines = [`${indent}summary:`];
9637
- for (const [column, stat3] of Object.entries(datasetStats.columnStats).slice(
9435
+ const lines = [`${indent2}summary:`];
9436
+ for (const [column, stat4] of Object.entries(datasetStats.columnStats).slice(
9638
9437
  0,
9639
9438
  12
9640
9439
  )) {
9641
- const topValues = stat3.top_values ? `, top_values=${Object.entries(stat3.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
9642
- const sample = stat3.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat3.sample_value)}` : "";
9643
- const execution = stat3.execution ? `, execution=${Object.entries(stat3.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
9440
+ const topValues = stat4.top_values ? `, top_values=${Object.entries(stat4.top_values).slice(0, 3).map(([value, count]) => `${value}=${count}`).join(", ")}` : "";
9441
+ const sample = stat4.sample_value !== void 0 ? `, sample_value=${JSON.stringify(stat4.sample_value)}` : "";
9442
+ const execution = stat4.execution ? `, execution=${Object.entries(stat4.execution).map(([bucket, count]) => `${bucket}=${count}`).join(", ")}` : "";
9644
9443
  lines.push(
9645
- `${indent} ${column}: non_empty=${stat3.non_empty}, unique=${stat3.unique}${topValues}${sample}${execution}`
9444
+ `${indent2} ${column}: non_empty=${stat4.non_empty}, unique=${stat4.unique}${topValues}${sample}${execution}`
9646
9445
  );
9647
9446
  }
9648
9447
  return lines;
@@ -9683,7 +9482,7 @@ function formatSummaryScalarParts(record, skipKeys = /* @__PURE__ */ new Set())
9683
9482
  }
9684
9483
  return parts;
9685
9484
  }
9686
- function formatPackageDatasetSummaryLines(summary, indent = " ") {
9485
+ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
9687
9486
  const record = readRecord(summary);
9688
9487
  const columnStats = readRecord(record?.columnStats);
9689
9488
  if (!record || !columnStats) {
@@ -9692,7 +9491,7 @@ function formatPackageDatasetSummaryLines(summary, indent = " ") {
9692
9491
  const lines = [];
9693
9492
  const parts = formatSummaryScalarParts(record, /* @__PURE__ */ new Set(["columnStats"]));
9694
9493
  if (parts.length > 0) {
9695
- lines.push(`${indent}summary: ${parts.join(" ")}`);
9494
+ lines.push(`${indent2}summary: ${parts.join(" ")}`);
9696
9495
  }
9697
9496
  for (const [column, rawColumnSummary] of Object.entries(columnStats)) {
9698
9497
  const columnSummary = readRecord(rawColumnSummary);
@@ -9704,7 +9503,7 @@ function formatPackageDatasetSummaryLines(summary, indent = " ") {
9704
9503
  executionText ? `execution=${executionText}` : null
9705
9504
  ].filter(Boolean);
9706
9505
  if (columnParts.length > 0) {
9707
- lines.push(`${indent} ${column}: ${columnParts.join(" ")}`);
9506
+ lines.push(`${indent2} ${column}: ${columnParts.join(" ")}`);
9708
9507
  }
9709
9508
  }
9710
9509
  return lines;
@@ -11914,9 +11713,11 @@ Idempotent execution:
11914
11713
  .run({ key: 'domain' });
11915
11714
 
11916
11715
  Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
11917
- To refresh, change the id/dataset key or set staleAfterSeconds:
11716
+ To recompute a visible cell on a later cron/user run after a window, put
11717
+ staleAfterSeconds on the cell-producing column:
11918
11718
 
11919
- .run({ key: 'domain', staleAfterSeconds: 86400 })
11719
+ .withColumn('cto', resolver, { staleAfterSeconds: 86400 })
11720
+ .run({ key: 'domain' })
11920
11721
 
11921
11722
  Examples:
11922
11723
  deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
@@ -12445,7 +12246,9 @@ async function handlePlayShareStatus(args) {
12445
12246
  );
12446
12247
  return 0;
12447
12248
  }
12448
- console.log(`${status.playName}: published v${status.share.publishedVersion}`);
12249
+ console.log(
12250
+ `${status.playName}: published v${status.share.publishedVersion}`
12251
+ );
12449
12252
  console.log(` url: ${status.share.publicPath}`);
12450
12253
  console.log(` seo: ${status.share.seoIndexing}`);
12451
12254
  console.log(
@@ -12607,7 +12410,9 @@ async function handlePlayShareRegenerate(args) {
12607
12410
  async function handlePlayShareUnpublish(args) {
12608
12411
  const target = args[0];
12609
12412
  if (!target) {
12610
- console.error("Usage: deepline plays share unpublish <play> --yes [--json]");
12413
+ console.error(
12414
+ "Usage: deepline plays share unpublish <play> --yes [--json]"
12415
+ );
12611
12416
  return 2;
12612
12417
  }
12613
12418
  if (!args.includes("--yes")) {
@@ -12627,49 +12432,1190 @@ async function handlePlayShareUnpublish(args) {
12627
12432
  return 0;
12628
12433
  }
12629
12434
 
12630
- // src/cli/commands/secrets.ts
12631
- import { stdin as input, stdout as output } from "process";
12632
- var hiddenInputBuffer = "";
12633
- function normalizeSecretName(value) {
12634
- const normalized = value.trim().toUpperCase();
12635
- if (!/^[A-Z][A-Z0-9_]{1,63}$/.test(normalized)) {
12636
- throw new Error(
12637
- "Secret names must be 2-64 characters and use uppercase letters, numbers, and underscores."
12638
- );
12435
+ // src/cli/enrich-play-compiler.ts
12436
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
12437
+ "break",
12438
+ "case",
12439
+ "catch",
12440
+ "class",
12441
+ "const",
12442
+ "continue",
12443
+ "debugger",
12444
+ "default",
12445
+ "delete",
12446
+ "do",
12447
+ "else",
12448
+ "export",
12449
+ "extends",
12450
+ "finally",
12451
+ "for",
12452
+ "function",
12453
+ "if",
12454
+ "import",
12455
+ "in",
12456
+ "instanceof",
12457
+ "new",
12458
+ "return",
12459
+ "super",
12460
+ "switch",
12461
+ "this",
12462
+ "throw",
12463
+ "try",
12464
+ "typeof",
12465
+ "var",
12466
+ "void",
12467
+ "while",
12468
+ "with",
12469
+ "yield"
12470
+ ]);
12471
+ function isWaterfall(command) {
12472
+ return "with_waterfall" in command;
12473
+ }
12474
+ function safeIdentifier2(value, fallback) {
12475
+ const cleaned = value.replace(/[^A-Za-z0-9_$]/g, "_");
12476
+ const prefixed = /^[A-Za-z_$]/.test(cleaned) ? cleaned : `_${cleaned}`;
12477
+ if (!prefixed || RESERVED_WORDS.has(prefixed)) {
12478
+ return fallback;
12639
12479
  }
12640
- return normalized;
12480
+ return prefixed;
12641
12481
  }
12642
- function renderSecret(secret) {
12643
- const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
12644
- return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
12482
+ function stringLiteral(value) {
12483
+ return JSON.stringify(value);
12645
12484
  }
12646
- async function readHiddenLine(prompt) {
12647
- if (!input.isTTY || !output.isTTY) {
12648
- throw new Error(
12649
- "Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
12485
+ function stableJson(value) {
12486
+ if (Array.isArray(value)) {
12487
+ return `[${value.map(stableJson).join(",")}]`;
12488
+ }
12489
+ if (value && typeof value === "object") {
12490
+ const entries = Object.entries(value).sort(
12491
+ ([left], [right]) => left.localeCompare(right)
12650
12492
  );
12493
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
12651
12494
  }
12652
- output.write(prompt);
12653
- const previousRawMode = input.isRaw;
12654
- if (typeof input.setRawMode === "function") input.setRawMode(true);
12655
- let value = "";
12656
- input.resume();
12657
- return await new Promise((resolve12, reject) => {
12658
- let settled = false;
12659
- const cleanup = () => {
12660
- input.off("data", onData);
12661
- input.off("end", onEnd);
12662
- input.off("error", onError);
12663
- if (typeof input.setRawMode === "function") {
12664
- input.setRawMode(previousRawMode);
12665
- }
12666
- };
12667
- const finish = (line) => {
12668
- if (settled) return;
12669
- settled = true;
12670
- output.write("\n");
12671
- cleanup();
12672
- resolve12(line);
12495
+ return JSON.stringify(value);
12496
+ }
12497
+ function indent(source, spaces) {
12498
+ const pad = " ".repeat(spaces);
12499
+ return source.split("\n").map((line) => line ? `${pad}${line}` : line).join("\n");
12500
+ }
12501
+ function commandCallId(command) {
12502
+ return `${command.alias}__${command.tool}`;
12503
+ }
12504
+ function normalizeAlias(value) {
12505
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
12506
+ }
12507
+ function renderExecuteStep(command, options = { force: false }) {
12508
+ const alias = stringLiteral(command.alias);
12509
+ const callId = stringLiteral(commandCallId(command));
12510
+ const tool = stringLiteral(command.tool);
12511
+ const payload = stableJson(command.payload ?? {});
12512
+ const extractJs = command.extract_js ? `({ row, result, data, raw, pick, extract, target }) => { const input = row; const context = row;
12513
+ ${indent(renderJavascriptBody(command.extract_js), 6)}
12514
+ }` : "null";
12515
+ const runIfJs = command.run_if_js ? `(row) => { const input = row; const context = row;
12516
+ ${indent(renderJavascriptBody(command.run_if_js), 6)}
12517
+ }` : "null";
12518
+ const description = command.description ? `,
12519
+ description: ${stringLiteral(command.description)}` : "";
12520
+ const force = options.force ? `,
12521
+ force: true` : "";
12522
+ return [
12523
+ `async (row, stepCtx) => {`,
12524
+ ...options.precheck ? [` if (${options.precheck}) return null;`] : [],
12525
+ ` return __dlRunCommand({`,
12526
+ ` alias: ${alias},`,
12527
+ ` callId: ${callId},`,
12528
+ ` tool: ${tool},`,
12529
+ ` payload: ${payload},`,
12530
+ ` extract: ${extractJs},`,
12531
+ ` runIf: ${runIfJs},`,
12532
+ ` row,`,
12533
+ ` stepCtx${description}${force}`,
12534
+ ` });`,
12535
+ `}`
12536
+ ].join("\n");
12537
+ }
12538
+ function renderJavascriptBody(source) {
12539
+ const trimmed = source.trim();
12540
+ if (trimmed && !trimmed.includes("\n") && !trimmed.includes(";") && !/\breturn\b/.test(trimmed)) {
12541
+ return `return (${trimmed});`;
12542
+ }
12543
+ return source;
12544
+ }
12545
+ function renderWaterfallProgram(command, index, forceAliases) {
12546
+ const variableName = safeIdentifier2(
12547
+ `${command.with_waterfall}_${index}_waterfall`,
12548
+ `waterfall_${index}`
12549
+ );
12550
+ const stepLines = command.commands.map((nested, stepIndex) => {
12551
+ if (isWaterfall(nested)) {
12552
+ throw new Error("Nested with_waterfall blocks are not supported.");
12553
+ }
12554
+ if (nested.disabled) {
12555
+ return null;
12556
+ }
12557
+ const priorAliases = command.commands.slice(0, stepIndex).filter((prior) => !isWaterfall(prior)).filter((prior) => !prior.disabled).map((prior) => prior.alias);
12558
+ const minResults = typeof command.min_results === "number" ? Math.max(1, Math.trunc(command.min_results)) : 1;
12559
+ return [
12560
+ ` .step(${stringLiteral(nested.alias)},`,
12561
+ indent(
12562
+ renderExecuteStep(nested, {
12563
+ force: forceAliases.has(normalizeAlias(nested.alias)),
12564
+ precheck: priorAliases.length > 0 ? `__dlWaterfallSatisfied(row, ${stableJson(priorAliases)}, ${minResults})` : void 0
12565
+ }),
12566
+ 4
12567
+ ),
12568
+ ` )`
12569
+ ].join("\n");
12570
+ }).filter((line) => line !== null);
12571
+ const aliases = command.commands.filter((nested) => !isWaterfall(nested)).filter((nested) => !nested.disabled).map((nested) => nested.alias);
12572
+ const returnExpr = typeof command.min_results === "number" ? `__dlFirstMinResults(row, ${stableJson(aliases)}, ${Math.max(
12573
+ 1,
12574
+ Math.trunc(command.min_results)
12575
+ )})` : `__dlFirstMeaningful(row, ${stableJson(aliases)})`;
12576
+ return {
12577
+ variableName,
12578
+ hasSteps: stepLines.length > 0,
12579
+ source: [
12580
+ `const ${variableName} = steps<Record<string, unknown>>()`,
12581
+ ...stepLines,
12582
+ ` .return((row) => ${returnExpr});`
12583
+ ].join("\n")
12584
+ };
12585
+ }
12586
+ function compileEnrichConfigToPlaySource(config, options = {}) {
12587
+ const playName = options.playName ?? "deepline-enrich-v1-compat";
12588
+ const mapName = options.mapName ?? "deepline_enrich_rows";
12589
+ const forceAliases = new Set(
12590
+ [...options.forceAliases ?? []].map((alias) => normalizeAlias(alias))
12591
+ );
12592
+ const waterfalls = [];
12593
+ const mapSteps = [];
12594
+ config.commands.forEach((command, index) => {
12595
+ if (isWaterfall(command)) {
12596
+ const rendered = renderWaterfallProgram(command, index, forceAliases);
12597
+ if (!rendered.hasSteps) {
12598
+ return;
12599
+ }
12600
+ waterfalls.push({
12601
+ alias: command.with_waterfall,
12602
+ variableName: rendered.variableName,
12603
+ source: rendered.source
12604
+ });
12605
+ mapSteps.push(
12606
+ ` .step(${stringLiteral(command.with_waterfall)}, ${rendered.variableName})`
12607
+ );
12608
+ return;
12609
+ }
12610
+ if (command.disabled) {
12611
+ return;
12612
+ }
12613
+ mapSteps.push(
12614
+ [
12615
+ ` .step(${stringLiteral(command.alias)},`,
12616
+ indent(
12617
+ renderExecuteStep(command, {
12618
+ force: forceAliases.has(normalizeAlias(command.alias))
12619
+ }),
12620
+ 8
12621
+ ),
12622
+ ` )`
12623
+ ].join("\n")
12624
+ );
12625
+ });
12626
+ const waterfallSource = waterfalls.map((entry) => indent(entry.source, 4));
12627
+ const mapStepSource = mapSteps.length > 0 ? mapSteps.join("\n") : ` .step('noop', (row) => row)`;
12628
+ return [
12629
+ `import { definePlay, steps } from 'deepline';`,
12630
+ ``,
12631
+ `type EnrichInput = { file: string; rowStart?: number | null; rowEnd?: number | null };`,
12632
+ ``,
12633
+ helperSource(),
12634
+ ``,
12635
+ `export default definePlay(${stringLiteral(playName)}, async (ctx, input: EnrichInput) => {`,
12636
+ ` const allRows = await ctx.csv<Record<string, unknown>>(input.file);`,
12637
+ ` const rowStart = Number.isFinite(input.rowStart) ? Math.max(0, Math.trunc(Number(input.rowStart))) : 0;`,
12638
+ ` const rowEnd = Number.isFinite(input.rowEnd) ? Math.max(rowStart, Math.trunc(Number(input.rowEnd))) : allRows.length;`,
12639
+ ` const rows = allRows.slice(rowStart, rowEnd);`,
12640
+ ...waterfallSource,
12641
+ ` const enriched = await ctx`,
12642
+ ` .map(${stringLiteral(mapName)}, rows)`,
12643
+ mapStepSource,
12644
+ ` .run({ key: (row, index) => __dlStableRowKey(row, index + rowStart) });`,
12645
+ ` return { rows: enriched, count: await enriched.count() };`,
12646
+ `});`,
12647
+ ``
12648
+ ].join("\n");
12649
+ }
12650
+ function helperSource() {
12651
+ return [
12652
+ `function __dlGetByPath(root: unknown, path: string): unknown {`,
12653
+ ` return String(path || '')`,
12654
+ ` .replace(/\\[(\\d+)\\]/g, '.$1')`,
12655
+ ` .split('.')`,
12656
+ ` .map((part) => part.trim())`,
12657
+ ` .filter(Boolean)`,
12658
+ ` .reduce((cursor: unknown, part: string) => {`,
12659
+ ` if (!cursor || typeof cursor !== 'object') return undefined;`,
12660
+ ` const record = cursor as Record<string, unknown>;`,
12661
+ ` if (part in record) return record[part];`,
12662
+ ` const data = record.data;`,
12663
+ ` return data && typeof data === 'object' ? (data as Record<string, unknown>)[part] : undefined;`,
12664
+ ` }, root);`,
12665
+ `}`,
12666
+ ``,
12667
+ `function __dlMeaningful(value: unknown): boolean {`,
12668
+ ` return value !== null && value !== undefined && !(typeof value === 'string' && value.trim() === '') && !(Array.isArray(value) && value.length === 0);`,
12669
+ `}`,
12670
+ ``,
12671
+ `function __dlRawToolOutput(result: unknown): unknown {`,
12672
+ ` if (!result || typeof result !== 'object') return result;`,
12673
+ ` const record = result as Record<string, unknown>;`,
12674
+ ` return __dlGetByPath(record, 'toolOutput.raw') ?? __dlGetByPath(record, 'toolResponse.raw') ?? record.result ?? record.output ?? result;`,
12675
+ `}`,
12676
+ ``,
12677
+ `function __dlTemplate(value: unknown, row: Record<string, unknown>): unknown {`,
12678
+ ` if (Array.isArray(value)) return value.map((entry) => __dlTemplate(entry, row));`,
12679
+ ` if (value && typeof value === 'object') {`,
12680
+ ` return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, __dlTemplate(entry, row)]));`,
12681
+ ` }`,
12682
+ ` if (typeof value !== 'string') return value;`,
12683
+ ` const exact = value.match(/^\\{\\{\\s*([^{}]+?)\\s*\\}\\}$/);`,
12684
+ ` if (exact) return __dlGetByPath(row, exact[1] || '');`,
12685
+ ` return value.replace(/\\{\\{\\s*([^{}]+?)\\s*\\}\\}/g, (_match, path) => {`,
12686
+ ` const replacement = __dlGetByPath(row, String(path || ''));`,
12687
+ ` return replacement === null || replacement === undefined ? '' : String(replacement);`,
12688
+ ` });`,
12689
+ `}`,
12690
+ ``,
12691
+ `function __dlStableRowKey(row: Record<string, unknown>, index: number): string {`,
12692
+ ` for (const key of ['id', 'ID', 'email', 'Email', 'linkedin_url', 'LINKEDIN_URL', 'domain', 'DOMAIN']) {`,
12693
+ ` const value = row[key];`,
12694
+ ` if (__dlMeaningful(value)) return String(value);`,
12695
+ ` }`,
12696
+ ` return String(index);`,
12697
+ `}`,
12698
+ ``,
12699
+ `function __dlFirstMeaningful(row: Record<string, unknown>, aliases: string[]): unknown {`,
12700
+ ` for (const alias of aliases) {`,
12701
+ ` const value = row[alias];`,
12702
+ ` if (__dlMeaningful(value)) return value;`,
12703
+ ` }`,
12704
+ ` return null;`,
12705
+ `}`,
12706
+ ``,
12707
+ `function __dlFirstMinResults(row: Record<string, unknown>, aliases: string[], minResults: number): unknown {`,
12708
+ ` const values: unknown[] = [];`,
12709
+ ` for (const alias of aliases) {`,
12710
+ ` const value = row[alias];`,
12711
+ ` if (Array.isArray(value)) values.push(...value.filter(__dlMeaningful));`,
12712
+ ` else if (__dlMeaningful(value)) values.push(value);`,
12713
+ ` if (values.length >= minResults) return values.slice(0, minResults);`,
12714
+ ` }`,
12715
+ ` return values.length > 0 ? values : null;`,
12716
+ `}`,
12717
+ ``,
12718
+ `function __dlWaterfallSatisfied(row: Record<string, unknown>, aliases: string[], minResults: number): boolean {`,
12719
+ ` let count = 0;`,
12720
+ ` for (const alias of aliases) {`,
12721
+ ` const value = row[alias];`,
12722
+ ` if (Array.isArray(value)) count += value.filter(__dlMeaningful).length;`,
12723
+ ` else if (__dlMeaningful(value)) count += 1;`,
12724
+ ` if (count >= Math.max(1, Math.trunc(minResults))) return true;`,
12725
+ ` }`,
12726
+ ` return false;`,
12727
+ `}`,
12728
+ ``,
12729
+ `function __dlExtract(alias: string, result: unknown, row: Record<string, unknown>, extractor: ((args: { row: Record<string, unknown>; result: unknown; data: unknown; raw: unknown; pick: (paths: string[] | string) => unknown; extract: (paths: string[] | string) => unknown; target: (paths: string[] | string) => unknown }) => unknown) | null): unknown {`,
12730
+ ` const raw = __dlRawToolOutput(result);`,
12731
+ ` if (!extractor) return raw;`,
12732
+ ` const pick = (paths: string[] | string) => {`,
12733
+ ` const candidates = Array.isArray(paths) ? paths : [paths];`,
12734
+ ` for (const path of candidates) {`,
12735
+ ` const value = __dlGetByPath(raw, String(path));`,
12736
+ ` if (__dlMeaningful(value)) return value;`,
12737
+ ` }`,
12738
+ ` return null;`,
12739
+ ` };`,
12740
+ ` const extracted = extractor({ row, result, data: raw, raw, pick, extract: pick, target: pick });`,
12741
+ ` if (extracted && typeof extracted === 'object' && !Array.isArray(extracted) && alias in (extracted as Record<string, unknown>)) {`,
12742
+ ` return (extracted as Record<string, unknown>)[alias];`,
12743
+ ` }`,
12744
+ ` return extracted === undefined ? raw : extracted;`,
12745
+ `}`,
12746
+ ``,
12747
+ `async function __dlRunCommand(input: { alias: string; callId: string; tool: string; payload: Record<string, unknown>; extract: ((args: { row: Record<string, unknown>; result: unknown; data: unknown; raw: unknown; pick: (paths: string[] | string) => unknown; extract: (paths: string[] | string) => unknown; target: (paths: string[] | string) => unknown }) => unknown) | null; runIf: ((row: Record<string, unknown>) => unknown) | null; row: Record<string, unknown>; stepCtx: { tools: { execute: (request: Record<string, unknown>) => Promise<unknown> } }; description?: string; force?: boolean }): Promise<unknown> {`,
12748
+ ` if (input.runIf) {`,
12749
+ ` const shouldRun = input.runIf(input.row);`,
12750
+ ` if (!shouldRun) return null;`,
12751
+ ` }`,
12752
+ ` const result = await input.stepCtx.tools.execute({`,
12753
+ ` id: input.callId,`,
12754
+ ` tool: input.tool,`,
12755
+ ` input: __dlTemplate(input.payload, input.row) as Record<string, unknown>,`,
12756
+ ` ...(input.description ? { description: input.description } : {}),`,
12757
+ ` ...(input.force ? { staleAfterSeconds: 0 } : {}),`,
12758
+ ` });`,
12759
+ ` return __dlExtract(input.alias, result, input.row, input.extract);`,
12760
+ `}`
12761
+ ].join("\n");
12762
+ }
12763
+
12764
+ // src/cli/commands/enrich.ts
12765
+ var PLAN_SHAPING_OPTION_NAMES = [
12766
+ "with",
12767
+ "withWaterfall",
12768
+ "minResults",
12769
+ "endWaterfall"
12770
+ ];
12771
+ var ENRICH_DEPRECATION_NOTICE = {
12772
+ status: "deprecated",
12773
+ message: "The enrich compatibility command is deprecated. This run generates a temporary .play.ts file and executes it through plays run.",
12774
+ generatedPlayFile: "Temporary compatibility play file; deleted after the command exits.",
12775
+ printGeneratedPlayCommand: "deepline enrich <same args> --dry-run > enrich.play.ts",
12776
+ recommendedCommand: `deepline plays run enrich.play.ts --input '{"file":"<input.csv>"}'`
12777
+ };
12778
+ var ENRICH_DEPRECATION_TEXT = `${ENRICH_DEPRECATION_NOTICE.message} Print the generated play with: ${ENRICH_DEPRECATION_NOTICE.printGeneratedPlayCommand}. Then run: ${ENRICH_DEPRECATION_NOTICE.recommendedCommand}
12779
+ `;
12780
+ function optionWasProvided(args, ...flags) {
12781
+ return args.some(
12782
+ (arg) => flags.some((flag) => arg === flag || arg.startsWith(`${flag}=`))
12783
+ );
12784
+ }
12785
+ function normalizeAlias2(value) {
12786
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
12787
+ }
12788
+ function hasPlanShapingArgs(args) {
12789
+ return optionWasProvided(args, "--with") || optionWasProvided(args, "--with-waterfall") || optionWasProvided(args, "--min-results") || optionWasProvided(args, "--end-waterfall");
12790
+ }
12791
+ function printDeprecationNotice(options) {
12792
+ if (!options.json) {
12793
+ process.stderr.write(ENRICH_DEPRECATION_TEXT);
12794
+ }
12795
+ }
12796
+ function expandAtFilePath(rawPath) {
12797
+ let expanded = rawPath.trim();
12798
+ expanded = expanded.replace(
12799
+ /\$([A-Za-z_][A-Za-z0-9_]*)|\$\{([^}]+)\}/g,
12800
+ (_match, bareName, bracedName) => process.env[bareName ?? bracedName ?? ""] ?? ""
12801
+ );
12802
+ if (expanded === "~") {
12803
+ return homedir4();
12804
+ }
12805
+ if (expanded.startsWith("~/") || expanded.startsWith("~\\")) {
12806
+ return join8(homedir4(), expanded.slice(2));
12807
+ }
12808
+ return expanded;
12809
+ }
12810
+ async function readAtFileReference(value, argumentName, strip = true) {
12811
+ if (!value.startsWith("@")) {
12812
+ return strip ? value.trim() : value.replace(/^\uFEFF/, "");
12813
+ }
12814
+ const filePath = expandAtFilePath(value.slice(1));
12815
+ if (!filePath) {
12816
+ throw new Error(`Invalid ${argumentName} value: empty @file path.`);
12817
+ }
12818
+ try {
12819
+ const text = await readFile3(filePath, "utf8");
12820
+ const normalized = text.replace(/^\uFEFF/, "");
12821
+ return strip ? normalized.trim() : normalized;
12822
+ } catch (error) {
12823
+ throw new Error(
12824
+ `Failed to read ${argumentName} file '${filePath}': ${error instanceof Error ? error.message : String(error)}`
12825
+ );
12826
+ }
12827
+ }
12828
+ function normalizeExtractJs(source) {
12829
+ return source.replace(/\\"/g, '"').replace(/\\'/g, "'");
12830
+ }
12831
+ async function normalizeWithSpec(rawSpec) {
12832
+ const raw = await readAtFileReference(rawSpec.trim(), "--with");
12833
+ let parsed;
12834
+ try {
12835
+ parsed = JSON.parse(raw);
12836
+ } catch (error) {
12837
+ throw new Error(
12838
+ `Invalid JSON payload in --with spec: ${raw} (${error instanceof Error ? error.message : String(error)})`
12839
+ );
12840
+ }
12841
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
12842
+ throw new Error("Invalid --with spec: expected JSON object.");
12843
+ }
12844
+ const spec = parsed;
12845
+ const normalized = { ...spec };
12846
+ for (const field of ["extract_js", "run_if_js"]) {
12847
+ const value = normalized[field];
12848
+ if (typeof value !== "string" || !value.trim()) {
12849
+ continue;
12850
+ }
12851
+ const fromFile = value.trim().startsWith("@") ? await readAtFileReference(value.trim(), `--with ${field}`, false) : null;
12852
+ normalized[field] = fromFile !== null ? fromFile.trim() : normalizeExtractJs(value.trim());
12853
+ }
12854
+ const tool = String(
12855
+ normalized.tool ?? normalized.tool_ref ?? normalized.tool_id ?? ""
12856
+ ).trim();
12857
+ const payload = normalized.payload;
12858
+ if (tool === "run_javascript" && payload && typeof payload === "object" && !Array.isArray(payload)) {
12859
+ const payloadRecord = payload;
12860
+ const code = payloadRecord.code;
12861
+ if (typeof code === "string" && code.trim().startsWith("@")) {
12862
+ normalized.payload = {
12863
+ ...payloadRecord,
12864
+ code: await readAtFileReference(
12865
+ code.trim(),
12866
+ "run_javascript payload.code",
12867
+ false
12868
+ )
12869
+ };
12870
+ }
12871
+ }
12872
+ return JSON.stringify(normalized);
12873
+ }
12874
+ async function expandCompiledConfigAtFiles(value) {
12875
+ if (Array.isArray(value)) {
12876
+ return Promise.all(
12877
+ value.map((entry) => expandCompiledConfigAtFiles(entry))
12878
+ );
12879
+ }
12880
+ if (!value || typeof value !== "object") {
12881
+ return value;
12882
+ }
12883
+ const record = value;
12884
+ const expanded = {};
12885
+ for (const [key, entry] of Object.entries(record)) {
12886
+ expanded[key] = await expandCompiledConfigAtFiles(entry);
12887
+ }
12888
+ for (const field of ["extract_js", "run_if_js"]) {
12889
+ const entry = expanded[field];
12890
+ if (typeof entry === "string" && entry.trim().startsWith("@")) {
12891
+ expanded[field] = (await readAtFileReference(entry.trim(), `--config ${field}`, false)).trim();
12892
+ }
12893
+ }
12894
+ const tool = String(
12895
+ expanded.tool ?? expanded.tool_ref ?? expanded.tool_id ?? ""
12896
+ ).trim();
12897
+ const payload = expanded.payload;
12898
+ if (tool === "run_javascript" && payload && typeof payload === "object" && !Array.isArray(payload)) {
12899
+ const payloadRecord = payload;
12900
+ const code = payloadRecord.code;
12901
+ if (typeof code === "string" && code.trim().startsWith("@")) {
12902
+ expanded.payload = {
12903
+ ...payloadRecord,
12904
+ code: await readAtFileReference(
12905
+ code.trim(),
12906
+ "--config run_javascript payload.code",
12907
+ false
12908
+ )
12909
+ };
12910
+ }
12911
+ }
12912
+ return expanded;
12913
+ }
12914
+ function currentEnrichArgs() {
12915
+ const index = process.argv.findIndex((arg) => arg === "enrich");
12916
+ return index >= 0 ? process.argv.slice(index + 1) : [];
12917
+ }
12918
+ async function buildPlanArgs(args) {
12919
+ const passthrough = /* @__PURE__ */ new Set([
12920
+ "--with",
12921
+ "--with-waterfall",
12922
+ "--min-results",
12923
+ "--end-waterfall"
12924
+ ]);
12925
+ const localOptionsWithValue = /* @__PURE__ */ new Set([
12926
+ "--input",
12927
+ "--csv",
12928
+ "--output",
12929
+ "--config",
12930
+ "--rows",
12931
+ "--with-force"
12932
+ ]);
12933
+ const localBooleanOptions = /* @__PURE__ */ new Set([
12934
+ "--dry-run",
12935
+ "--json",
12936
+ "--force",
12937
+ "--all",
12938
+ "--in-place"
12939
+ ]);
12940
+ const planArgs = [];
12941
+ for (let index = 0; index < args.length; index += 1) {
12942
+ const arg = args[index];
12943
+ const equalsIndex = arg.indexOf("=");
12944
+ const flag = equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
12945
+ const inlineValue = equalsIndex >= 0 ? arg.slice(equalsIndex + 1) : void 0;
12946
+ if (!passthrough.has(flag)) {
12947
+ if (localOptionsWithValue.has(flag)) {
12948
+ if (inlineValue === void 0) {
12949
+ const value = args[index + 1];
12950
+ if (!value || value.startsWith("--")) {
12951
+ throw new Error(`${flag} requires a value.`);
12952
+ }
12953
+ index += 1;
12954
+ }
12955
+ continue;
12956
+ }
12957
+ if (localBooleanOptions.has(flag)) {
12958
+ if (inlineValue !== void 0) {
12959
+ throw new Error(`${flag} does not accept a value.`);
12960
+ }
12961
+ continue;
12962
+ }
12963
+ if (flag.startsWith("--")) {
12964
+ throw new Error(`Unknown enrich option: ${flag}.`);
12965
+ }
12966
+ throw new Error(`Unexpected enrich argument: ${arg}.`);
12967
+ }
12968
+ planArgs.push(flag);
12969
+ if (inlineValue !== void 0) {
12970
+ planArgs.push(
12971
+ flag === "--with" ? await normalizeWithSpec(inlineValue) : inlineValue
12972
+ );
12973
+ continue;
12974
+ }
12975
+ if (flag !== "--end-waterfall") {
12976
+ const value = args[++index];
12977
+ if (!value) {
12978
+ throw new Error(`${flag} requires a value.`);
12979
+ }
12980
+ planArgs.push(flag === "--with" ? await normalizeWithSpec(value) : value);
12981
+ }
12982
+ }
12983
+ return planArgs;
12984
+ }
12985
+ async function assertInputCsvExists(inputCsv) {
12986
+ const path = resolve11(inputCsv);
12987
+ try {
12988
+ const info = await stat3(path);
12989
+ if (info.isFile()) {
12990
+ return;
12991
+ }
12992
+ throw new Error("not a file");
12993
+ } catch (error) {
12994
+ throw new Error(
12995
+ `Input CSV does not exist or is not a file: ${path}${error instanceof Error && error.message !== "not a file" ? ` (${error.message})` : ""}`
12996
+ );
12997
+ }
12998
+ }
12999
+ async function assertSafeOutputPath(inputCsv, outputPath) {
13000
+ const input2 = resolve11(inputCsv);
13001
+ const output2 = resolve11(outputPath);
13002
+ if (input2 === output2) {
13003
+ throw new Error(
13004
+ "--output must be a different path from --input. --in-place is not supported by this V2 enrich runner yet."
13005
+ );
13006
+ }
13007
+ try {
13008
+ const [inputInfo, outputInfo] = await Promise.all([
13009
+ stat3(input2),
13010
+ stat3(output2)
13011
+ ]);
13012
+ if (inputInfo.dev === outputInfo.dev && inputInfo.ino === outputInfo.ino) {
13013
+ throw new Error(
13014
+ "--output must be a different file from --input. --in-place is not supported by this V2 enrich runner yet."
13015
+ );
13016
+ }
13017
+ } catch (error) {
13018
+ if (error instanceof Error && error.message.startsWith("--output must")) {
13019
+ throw error;
13020
+ }
13021
+ const code = error && typeof error === "object" ? error.code : void 0;
13022
+ if (code === "ENOENT") {
13023
+ return;
13024
+ }
13025
+ throw error;
13026
+ }
13027
+ }
13028
+ async function readConfig(path) {
13029
+ const source = await readFile3(resolve11(path), "utf8");
13030
+ let parsed;
13031
+ try {
13032
+ parsed = JSON.parse(source);
13033
+ } catch (error) {
13034
+ throw new Error(
13035
+ `Invalid JSON in --config ${path}: ${error instanceof Error ? error.message : String(error)}`
13036
+ );
13037
+ }
13038
+ return expandCompiledConfigAtFiles(parsed);
13039
+ }
13040
+ function parseRows(value, all) {
13041
+ if (all && value) {
13042
+ throw new Error("Do not combine --rows with --all.");
13043
+ }
13044
+ if (all || !value) {
13045
+ return { rowStart: null, rowEnd: null };
13046
+ }
13047
+ const trimmed = value.trim();
13048
+ const range = trimmed.match(/^(\d*)\s*:\s*(\d*)$/);
13049
+ if (range) {
13050
+ if (!range[1] && !range[2]) {
13051
+ throw new Error(
13052
+ "--rows must be a zero-based row number or end-exclusive range like 0:10."
13053
+ );
13054
+ }
13055
+ const start = range[1] ? Number.parseInt(range[1], 10) : 0;
13056
+ const end = range[2] ? Number.parseInt(range[2], 10) : null;
13057
+ return { rowStart: start, rowEnd: end };
13058
+ }
13059
+ if (!/^\d+$/.test(trimmed)) {
13060
+ throw new Error(
13061
+ "--rows must be a zero-based row number or end-exclusive range like 0:10."
13062
+ );
13063
+ }
13064
+ const single = Number.parseInt(trimmed, 10);
13065
+ if (!Number.isFinite(single) || single < 0) {
13066
+ throw new Error(
13067
+ "--rows must be a zero-based row number or end-exclusive range like 0:10."
13068
+ );
13069
+ }
13070
+ return { rowStart: single, rowEnd: single + 1 };
13071
+ }
13072
+ function summarizePlan(config, playSource) {
13073
+ const steps = [];
13074
+ for (const command of config.commands) {
13075
+ if ("with_waterfall" in command) {
13076
+ steps.push({
13077
+ type: "waterfall",
13078
+ alias: command.with_waterfall,
13079
+ min_results: command.min_results ?? 1,
13080
+ steps: command.commands.map(
13081
+ (step) => "with_waterfall" in step ? { type: "waterfall", alias: step.with_waterfall } : { alias: step.alias, tool: step.tool, operation: step.operation }
13082
+ )
13083
+ });
13084
+ continue;
13085
+ }
13086
+ steps.push({
13087
+ type: "step",
13088
+ alias: command.alias,
13089
+ tool: command.tool,
13090
+ operation: command.operation
13091
+ });
13092
+ }
13093
+ return {
13094
+ version: config.version,
13095
+ commandCount: config.commands.length,
13096
+ steps,
13097
+ generatedPlay: playSource
13098
+ };
13099
+ }
13100
+ function collectCommandAliases(config) {
13101
+ const allAliases = /* @__PURE__ */ new Set();
13102
+ const scalarAliases = /* @__PURE__ */ new Set();
13103
+ const waterfallGroups = /* @__PURE__ */ new Map();
13104
+ for (const command of config.commands) {
13105
+ if ("with_waterfall" in command) {
13106
+ const groupAlias = normalizeAlias2(command.with_waterfall);
13107
+ if (groupAlias) {
13108
+ allAliases.add(groupAlias);
13109
+ }
13110
+ const childAliases = /* @__PURE__ */ new Set();
13111
+ for (const child of command.commands) {
13112
+ if ("with_waterfall" in child || child.disabled) {
13113
+ continue;
13114
+ }
13115
+ const childAlias = normalizeAlias2(child.alias);
13116
+ if (!childAlias) {
13117
+ continue;
13118
+ }
13119
+ allAliases.add(childAlias);
13120
+ scalarAliases.add(childAlias);
13121
+ childAliases.add(childAlias);
13122
+ }
13123
+ if (groupAlias) {
13124
+ waterfallGroups.set(groupAlias, childAliases);
13125
+ }
13126
+ continue;
13127
+ }
13128
+ if (command.disabled) {
13129
+ continue;
13130
+ }
13131
+ const alias = normalizeAlias2(command.alias);
13132
+ if (alias) {
13133
+ allAliases.add(alias);
13134
+ scalarAliases.add(alias);
13135
+ }
13136
+ }
13137
+ return { allAliases, scalarAliases, waterfallGroups };
13138
+ }
13139
+ function parseWithForceAliases(values) {
13140
+ const aliases = /* @__PURE__ */ new Set();
13141
+ for (const value of values ?? []) {
13142
+ for (const item of value.split(",")) {
13143
+ const alias = normalizeAlias2(item.trim());
13144
+ if (alias) {
13145
+ aliases.add(alias);
13146
+ }
13147
+ }
13148
+ }
13149
+ return aliases;
13150
+ }
13151
+ function resolveForceAliases(config, options) {
13152
+ const { allAliases, scalarAliases, waterfallGroups } = collectCommandAliases(config);
13153
+ if (options.force) {
13154
+ return new Set(scalarAliases);
13155
+ }
13156
+ const requested = parseWithForceAliases(options.withForce);
13157
+ const unknown = [...requested].filter((alias) => !allAliases.has(alias));
13158
+ if (unknown.length > 0) {
13159
+ throw new Error(
13160
+ `--with-force references unknown --with column alias(es): ${unknown.sort().join(", ")}.`
13161
+ );
13162
+ }
13163
+ const resolved = /* @__PURE__ */ new Set();
13164
+ for (const alias of requested) {
13165
+ const children = waterfallGroups.get(alias);
13166
+ if (children) {
13167
+ for (const child of children) {
13168
+ resolved.add(child);
13169
+ }
13170
+ } else {
13171
+ resolved.add(alias);
13172
+ }
13173
+ }
13174
+ return resolved;
13175
+ }
13176
+ function parseJsonOutput(stdout) {
13177
+ const trimmed = stdout.trim();
13178
+ if (!trimmed) return null;
13179
+ try {
13180
+ return JSON.parse(trimmed);
13181
+ } catch {
13182
+ const start = trimmed.lastIndexOf("\n{");
13183
+ if (start >= 0) {
13184
+ return JSON.parse(trimmed.slice(start + 1));
13185
+ }
13186
+ throw new Error(
13187
+ "The generated play completed but did not emit parseable JSON."
13188
+ );
13189
+ }
13190
+ }
13191
+ async function captureStdout(run) {
13192
+ const originalWrite = process.stdout.write.bind(process.stdout);
13193
+ let stdout = "";
13194
+ process.stdout.write = ((chunk, ..._args) => {
13195
+ stdout += typeof chunk === "string" ? chunk : String(chunk);
13196
+ return true;
13197
+ });
13198
+ try {
13199
+ const result = await run();
13200
+ return { result, stdout };
13201
+ } finally {
13202
+ process.stdout.write = originalWrite;
13203
+ }
13204
+ }
13205
+ async function writeOutputCsv(outputPath, status) {
13206
+ const rowsInfo = extractCanonicalRowsInfo(status);
13207
+ if (!rowsInfo) {
13208
+ throw new Error("The generated play did not return row-shaped output.");
13209
+ }
13210
+ assertCompleteRowsForCsvExport(rowsInfo, status, outputPath);
13211
+ const rows = dataExportRows(rowsInfo.rows);
13212
+ const columns = dataExportColumns(rows, rowsInfo.columns);
13213
+ await writeFile4(
13214
+ resolve11(outputPath),
13215
+ csvStringFromRows(rows, columns),
13216
+ "utf8"
13217
+ );
13218
+ return { rows: rows.length, path: resolve11(outputPath) };
13219
+ }
13220
+ function assertCompleteRowsForCsvExport(rowsInfo, status, outputPath) {
13221
+ if (rowsInfo.complete) {
13222
+ return;
13223
+ }
13224
+ const runId = status && typeof status === "object" && !Array.isArray(status) && typeof status.runId === "string" ? status.runId : null;
13225
+ const dataset = rowsInfo.source ?? "result.rows";
13226
+ const retry = runId ? ` Retry after the run finalizes its backing dataset with: deepline runs export ${runId} --dataset ${dataset} --out ${outputPath}` : "";
13227
+ throw new Error(
13228
+ `Refusing to write a partial CSV export: the run returned ${rowsInfo.rows.length} preview row(s) out of ${rowsInfo.totalRows}.${retry}`
13229
+ );
13230
+ }
13231
+ async function compileConfig(input2) {
13232
+ if (input2.options.config) {
13233
+ if (hasPlanShapingArgs(input2.args)) {
13234
+ throw new Error(
13235
+ `Do not mix --config with plan-shaping flags (${PLAN_SHAPING_OPTION_NAMES.join(", ")}). Put the plan in the config file or pass flags directly.`
13236
+ );
13237
+ }
13238
+ const config = await readConfig(input2.options.config);
13239
+ return (await input2.client.compileEnrichPlan({ config })).config;
13240
+ }
13241
+ const planArgs = await buildPlanArgs(input2.args);
13242
+ return (await input2.client.compileEnrichPlan({ plan_args: planArgs })).config;
13243
+ }
13244
+ function registerEnrichCommand(program) {
13245
+ program.command("enrich").allowUnknownOption(true).description("Run v1-style CSV enrichment through the V2 play runner.").option("--input <path>", "Input CSV path.").option("--csv <path>", "Alias for --input.").option("--output <path>", "Output CSV path.").option("--config <path>", "JSON enrich config.").option(
13246
+ "--with <json>",
13247
+ "Add a scalar enrich command.",
13248
+ (value, previous = []) => [...previous, value]
13249
+ ).option(
13250
+ "--with-waterfall <alias>",
13251
+ "Start a waterfall group.",
13252
+ (value, previous = []) => [...previous, value]
13253
+ ).option(
13254
+ "--min-results <count>",
13255
+ "Minimum list results for the current waterfall."
13256
+ ).option("--end-waterfall", "End the current waterfall group.").option(
13257
+ "--rows <range>",
13258
+ "Zero-based row number or end-exclusive range, e.g. 0:10."
13259
+ ).option("--all", "Run all rows.").option(
13260
+ "--dry-run",
13261
+ "Compile and print the generated plan without starting a run."
13262
+ ).option("--json", "Emit JSON.").option("--force", "Force rerun for all enrich aliases.").option(
13263
+ "--with-force <aliases>",
13264
+ "Force rerun for selected aliases.",
13265
+ (value, previous = []) => [...previous, value]
13266
+ ).option(
13267
+ "--in-place",
13268
+ "Not supported by the V2 enrich compatibility runner yet."
13269
+ ).action(async (options, _command) => {
13270
+ if (options.inPlace) {
13271
+ throw new Error(
13272
+ "--in-place is not supported by this V2 enrich runner yet. Use --output instead."
13273
+ );
13274
+ }
13275
+ const inputCsv = options.input ?? options.csv;
13276
+ if (!inputCsv) {
13277
+ throw new Error("Missing required --input <csv> (or --csv <csv>).");
13278
+ }
13279
+ await assertInputCsvExists(inputCsv);
13280
+ if (options.output) {
13281
+ await assertSafeOutputPath(inputCsv, options.output);
13282
+ }
13283
+ if (!options.config && !hasPlanShapingArgs(currentEnrichArgs())) {
13284
+ throw new Error("Pass --config or at least one --with enrich spec.");
13285
+ }
13286
+ const client = new DeeplineClient();
13287
+ const args = currentEnrichArgs();
13288
+ const config = await compileConfig({ client, args, options });
13289
+ const forceAliases = resolveForceAliases(config, options);
13290
+ const playSource = compileEnrichConfigToPlaySource(config, {
13291
+ forceAliases
13292
+ });
13293
+ const summary = summarizePlan(config, playSource);
13294
+ printDeprecationNotice(options);
13295
+ if (options.dryRun) {
13296
+ if (options.json) {
13297
+ printJson({
13298
+ dryRun: true,
13299
+ deprecation: ENRICH_DEPRECATION_NOTICE,
13300
+ input: resolve11(inputCsv),
13301
+ output: options.output ? resolve11(options.output) : null,
13302
+ plan: summary
13303
+ });
13304
+ return;
13305
+ }
13306
+ process.stdout.write(`${playSource}
13307
+ `);
13308
+ return;
13309
+ }
13310
+ const rows = parseRows(options.rows, options.all);
13311
+ if (options.output && options.rows) {
13312
+ throw new Error(
13313
+ "CSV export with --rows is not supported yet because it would write only the selected rows. Run without --rows or omit --output."
13314
+ );
13315
+ }
13316
+ const tempDir = await mkdtemp(join8(tmpdir3(), "deepline-enrich-play-"));
13317
+ const tempPlay = join8(tempDir, "deepline-enrich.play.ts");
13318
+ try {
13319
+ await writeFile4(tempPlay, playSource, "utf8");
13320
+ const runtimeInput = {
13321
+ file: resolve11(inputCsv),
13322
+ ...rows.rowStart !== null ? { rowStart: rows.rowStart } : {},
13323
+ ...rows.rowEnd !== null ? { rowEnd: rows.rowEnd } : {}
13324
+ };
13325
+ const runArgs = [
13326
+ "--file",
13327
+ tempPlay,
13328
+ "--input",
13329
+ JSON.stringify(runtimeInput),
13330
+ "--watch",
13331
+ "--no-open",
13332
+ "--json"
13333
+ ];
13334
+ const captured = await captureStdout(() => handlePlayRun(runArgs));
13335
+ const status = parseJsonOutput(captured.stdout);
13336
+ if (captured.result !== 0) {
13337
+ if (options.json) {
13338
+ printJson({
13339
+ deprecation: ENRICH_DEPRECATION_NOTICE,
13340
+ result: status
13341
+ });
13342
+ } else {
13343
+ process.stdout.write(captured.stdout);
13344
+ }
13345
+ process.exitCode = captured.result;
13346
+ return;
13347
+ }
13348
+ const exportResult = options.output ? await writeOutputCsv(options.output, status) : null;
13349
+ if (options.json) {
13350
+ printJson({
13351
+ ok: true,
13352
+ deprecation: ENRICH_DEPRECATION_NOTICE,
13353
+ run: status,
13354
+ output: exportResult
13355
+ });
13356
+ return;
13357
+ }
13358
+ process.stdout.write(captured.stdout);
13359
+ if (exportResult) {
13360
+ process.stderr.write(
13361
+ `Wrote ${exportResult.rows} row(s) to ${exportResult.path}
13362
+ `
13363
+ );
13364
+ }
13365
+ } finally {
13366
+ await rm(tempDir, { recursive: true, force: true });
13367
+ }
13368
+ });
13369
+ }
13370
+
13371
+ // src/cli/commands/feedback.ts
13372
+ async function handleFeedback(text, options) {
13373
+ const { http } = getAuthedHttpClient();
13374
+ const response = await http.post("/api/v2/cli/feedback", {
13375
+ text,
13376
+ environment: collectLocalEnvInfo(),
13377
+ ...options.command ? { command: options.command } : {},
13378
+ ...options.payload ? { payload: options.payload } : {}
13379
+ });
13380
+ printCommandEnvelope(
13381
+ {
13382
+ ...response,
13383
+ render: {
13384
+ sections: [
13385
+ { title: "feedback", lines: ["Feedback submitted. Thank you."] }
13386
+ ]
13387
+ }
13388
+ },
13389
+ { json: options.json }
13390
+ );
13391
+ }
13392
+ function registerFeedbackCommands(program) {
13393
+ const feedback = program.command("feedback").description("Submit CLI feedback to Deepline.").addHelpText(
13394
+ "after",
13395
+ `
13396
+ Notes:
13397
+ Sends the feedback text plus local CLI environment info to Deepline support.
13398
+ Use --command and --payload to attach a reproducible command shape.
13399
+
13400
+ Examples:
13401
+ deepline feedback "plays run failed after upload" --command "deepline plays run my.play.ts --watch"
13402
+ deepline feedback "unexpected billing output" --payload '{"command":"billing usage"}' --json
13403
+ `
13404
+ );
13405
+ feedback.argument("<text>", "Feedback text").option("--command <command>", "Command that reproduced the issue").option("--payload <payload>", "JSON or plain-text payload for the repro").option("--json", "Emit JSON output").action(handleFeedback);
13406
+ program.command("provide-feedback").description("Legacy alias for `deepline feedback`.").addHelpText(
13407
+ "after",
13408
+ `
13409
+ Notes:
13410
+ Compatibility alias. Prefer deepline feedback in new scripts and docs.
13411
+
13412
+ Examples:
13413
+ deepline feedback "tools search returned stale results" --json
13414
+ `
13415
+ ).argument("<text>", "Feedback text").option("--command <command>", "Command that reproduced the issue").option("--payload <payload>", "JSON or plain-text payload for the repro").option("--json", "Emit JSON output").action(handleFeedback);
13416
+ }
13417
+
13418
+ // src/cli/commands/org.ts
13419
+ async function fetchOrganizations(http, apiKey) {
13420
+ return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
13421
+ }
13422
+ function orgListLines(orgs) {
13423
+ return orgs.map((org, index) => {
13424
+ const current = org.is_current ? " (current)" : "";
13425
+ const role = org.role ? ` [${org.role}]` : "";
13426
+ return `${index + 1}. ${org.name}${role}${current}`;
13427
+ });
13428
+ }
13429
+ async function handleOrgList(options) {
13430
+ const config = resolveConfig();
13431
+ const http = new HttpClient(config);
13432
+ const payload = await fetchOrganizations(http, config.apiKey);
13433
+ printCommandEnvelope(
13434
+ {
13435
+ ...payload,
13436
+ render: {
13437
+ sections: [
13438
+ {
13439
+ title: "Your organizations:",
13440
+ lines: orgListLines(payload.organizations)
13441
+ }
13442
+ ]
13443
+ }
13444
+ },
13445
+ { json: options.json }
13446
+ );
13447
+ }
13448
+ async function handleOrgSwitch(selection, options) {
13449
+ const config = resolveConfig();
13450
+ const http = new HttpClient(config);
13451
+ const payload = await fetchOrganizations(http, config.apiKey);
13452
+ if (!selection && !options.orgId) {
13453
+ printCommandEnvelope(
13454
+ {
13455
+ ...payload,
13456
+ next: { switch: "deepline org switch <number>" },
13457
+ render: {
13458
+ sections: [
13459
+ {
13460
+ title: "Your organizations:",
13461
+ lines: orgListLines(payload.organizations)
13462
+ }
13463
+ ],
13464
+ actions: [{ label: "Run", command: "deepline org switch <number>" }]
13465
+ }
13466
+ },
13467
+ { json: options.json }
13468
+ );
13469
+ return;
13470
+ }
13471
+ let target = payload.organizations.find(
13472
+ (org) => org.org_id === options.orgId
13473
+ );
13474
+ if (!target && selection) {
13475
+ const index = Number.parseInt(selection, 10);
13476
+ if (Number.isFinite(index) && index >= 1 && index <= payload.organizations.length) {
13477
+ target = payload.organizations[index - 1];
13478
+ } else {
13479
+ target = payload.organizations.find(
13480
+ (org) => org.name === selection || org.org_id === selection
13481
+ );
13482
+ }
13483
+ }
13484
+ if (!target) {
13485
+ throw new Error("Could not resolve the selected organization.");
13486
+ }
13487
+ if (target.is_current) {
13488
+ printCommandEnvelope(
13489
+ {
13490
+ ok: true,
13491
+ unchanged: true,
13492
+ organization: target,
13493
+ render: {
13494
+ sections: [
13495
+ { title: "org switch", lines: [`Already on ${target.name}.`] }
13496
+ ]
13497
+ }
13498
+ },
13499
+ { json: options.json }
13500
+ );
13501
+ return;
13502
+ }
13503
+ const switched = await http.post("/api/v2/auth/cli/switch", {
13504
+ api_key: config.apiKey,
13505
+ org_id: target.org_id
13506
+ });
13507
+ saveHostEnvValues(config.baseUrl, {
13508
+ DEEPLINE_API_KEY: switched.api_key,
13509
+ DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
13510
+ DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
13511
+ });
13512
+ const { api_key: _apiKey, ...publicSwitched } = switched;
13513
+ printCommandEnvelope(
13514
+ {
13515
+ ok: true,
13516
+ host_env_path: hostEnvFilePath(config.baseUrl),
13517
+ ...publicSwitched,
13518
+ api_key_saved: true,
13519
+ render: {
13520
+ sections: [
13521
+ {
13522
+ title: "org switch",
13523
+ lines: [
13524
+ `Switched to ${switched.org_name}.`,
13525
+ `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
13526
+ ]
13527
+ }
13528
+ ]
13529
+ }
13530
+ },
13531
+ { json: options.json }
13532
+ );
13533
+ }
13534
+ function registerOrgCommands(program) {
13535
+ const org = program.command("org").description("List and switch organizations.").addHelpText(
13536
+ "after",
13537
+ `
13538
+ Notes:
13539
+ Organizations are workspaces. Switching organizations mutates the saved host
13540
+ auth file so later CLI commands target the selected workspace.
13541
+
13542
+ Examples:
13543
+ deepline org list --json
13544
+ deepline org switch 2
13545
+ deepline org switch --org-id org_123 --json
13546
+ `
13547
+ );
13548
+ org.command("list").description("List your organizations.").addHelpText(
13549
+ "after",
13550
+ `
13551
+ Notes:
13552
+ Read-only. Marks the active organization when the server returns that metadata.
13553
+
13554
+ Examples:
13555
+ deepline org list
13556
+ deepline org list --json
13557
+ `
13558
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
13559
+ org.command("switch [selection]").description(
13560
+ "Switch to another organization and save the new API key in the host auth file."
13561
+ ).addHelpText(
13562
+ "after",
13563
+ `
13564
+ Notes:
13565
+ Mutates the saved host auth file. Selection can be a list number, exact
13566
+ organization name, or organization id. Without a selection, prints choices.
13567
+
13568
+ Examples:
13569
+ deepline org switch
13570
+ deepline org switch 2
13571
+ deepline org switch --org-id org_123 --json
13572
+ `
13573
+ ).option("--org-id <id>", "Switch using an explicit organization id").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgSwitch);
13574
+ }
13575
+
13576
+ // src/cli/commands/secrets.ts
13577
+ import { stdin as input, stdout as output } from "process";
13578
+ var hiddenInputBuffer = "";
13579
+ function normalizeSecretName(value) {
13580
+ const normalized = value.trim().toUpperCase();
13581
+ if (!/^[A-Z][A-Z0-9_]{1,63}$/.test(normalized)) {
13582
+ throw new Error(
13583
+ "Secret names must be 2-64 characters and use uppercase letters, numbers, and underscores."
13584
+ );
13585
+ }
13586
+ return normalized;
13587
+ }
13588
+ function renderSecret(secret) {
13589
+ const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
13590
+ return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
13591
+ }
13592
+ async function readHiddenLine(prompt) {
13593
+ if (!input.isTTY || !output.isTTY) {
13594
+ throw new Error(
13595
+ "Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
13596
+ );
13597
+ }
13598
+ output.write(prompt);
13599
+ const previousRawMode = input.isRaw;
13600
+ if (typeof input.setRawMode === "function") input.setRawMode(true);
13601
+ let value = "";
13602
+ input.resume();
13603
+ return await new Promise((resolve13, reject) => {
13604
+ let settled = false;
13605
+ const cleanup = () => {
13606
+ input.off("data", onData);
13607
+ input.off("end", onEnd);
13608
+ input.off("error", onError);
13609
+ if (typeof input.setRawMode === "function") {
13610
+ input.setRawMode(previousRawMode);
13611
+ }
13612
+ };
13613
+ const finish = (line) => {
13614
+ if (settled) return;
13615
+ settled = true;
13616
+ output.write("\n");
13617
+ cleanup();
13618
+ resolve13(line);
12673
13619
  };
12674
13620
  const fail = (error) => {
12675
13621
  if (settled) return;
@@ -12842,13 +13788,13 @@ Examples:
12842
13788
  // src/cli/commands/tools.ts
12843
13789
  import { Option } from "commander";
12844
13790
  import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
12845
- import { tmpdir as tmpdir3 } from "os";
12846
- import { join as join9 } from "path";
13791
+ import { tmpdir as tmpdir4 } from "os";
13792
+ import { join as join10 } from "path";
12847
13793
 
12848
13794
  // src/tool-output.ts
12849
13795
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
12850
- import { homedir as homedir4 } from "os";
12851
- import { join as join8 } from "path";
13796
+ import { homedir as homedir5 } from "os";
13797
+ import { join as join9 } from "path";
12852
13798
  function isPlainObject(value) {
12853
13799
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
12854
13800
  }
@@ -12944,19 +13890,19 @@ function tryConvertToList(payload, options) {
12944
13890
  return null;
12945
13891
  }
12946
13892
  function ensureOutputDir() {
12947
- const outputDir = join8(homedir4(), ".local", "share", "deepline", "data");
13893
+ const outputDir = join9(homedir5(), ".local", "share", "deepline", "data");
12948
13894
  mkdirSync4(outputDir, { recursive: true });
12949
13895
  return outputDir;
12950
13896
  }
12951
13897
  function writeJsonOutputFile(payload, stem) {
12952
13898
  const outputDir = ensureOutputDir();
12953
- const outputPath = join8(outputDir, `${stem}_${Date.now()}.json`);
13899
+ const outputPath = join9(outputDir, `${stem}_${Date.now()}.json`);
12954
13900
  writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
12955
13901
  return outputPath;
12956
13902
  }
12957
13903
  function writeCsvOutputFile(rows, stem) {
12958
13904
  const outputDir = ensureOutputDir();
12959
- const outputPath = join8(outputDir, `${stem}_${Date.now()}.csv`);
13905
+ const outputPath = join9(outputDir, `${stem}_${Date.now()}.csv`);
12960
13906
  const seen = /* @__PURE__ */ new Set();
12961
13907
  const columns = [];
12962
13908
  for (const row of rows) {
@@ -13715,7 +14661,7 @@ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
13715
14661
  const expression = stringField(firstGetter, "expression");
13716
14662
  if (expression)
13717
14663
  console.log(
13718
- `const ${safeIdentifier2(name)} = ${expression.replace(/^toolExecutionResult\./, "result.")};`
14664
+ `const ${safeIdentifier3(name)} = ${expression.replace(/^toolExecutionResult\./, "result.")};`
13719
14665
  );
13720
14666
  }
13721
14667
  console.log("```");
@@ -13784,7 +14730,7 @@ function samplePayloadForInputFields(fields) {
13784
14730
  function stableStepIdForTool(toolId) {
13785
14731
  return toolId.replace(/^[a-z0-9]+_/, "").replace(/[^a-z0-9_]+/gi, "_") || "tool_call";
13786
14732
  }
13787
- function safeIdentifier2(name) {
14733
+ function safeIdentifier3(name) {
13788
14734
  const cleaned = name.replace(/[^a-zA-Z0-9_$]+/g, "_").replace(/^[^a-zA-Z_$]+/, "");
13789
14735
  return cleaned || "value";
13790
14736
  }
@@ -14047,9 +14993,9 @@ function powerShellQuote(value) {
14047
14993
  function seedToolListScript(input2) {
14048
14994
  const stem = safeFileStem(input2.toolId);
14049
14995
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
14050
- const scriptDir = mkdtempSync(join9(tmpdir3(), "deepline-workflow-seed-"));
14996
+ const scriptDir = mkdtempSync(join10(tmpdir4(), "deepline-workflow-seed-"));
14051
14997
  chmodSync(scriptDir, 448);
14052
- const scriptPath = join9(scriptDir, fileName);
14998
+ const scriptPath = join10(scriptDir, fileName);
14053
14999
  const projectDir = `deepline/projects/${stem}-workflow`;
14054
15000
  const playName = `${stem}-workflow`;
14055
15001
  const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
@@ -14341,7 +15287,7 @@ async function executeTool(args) {
14341
15287
  // src/cli/commands/update.ts
14342
15288
  import { spawn } from "child_process";
14343
15289
  import { existsSync as existsSync8 } from "fs";
14344
- import { dirname as dirname9, join as join10, resolve as resolve11 } from "path";
15290
+ import { dirname as dirname9, join as join11, resolve as resolve12 } from "path";
14345
15291
  function posixShellQuote(value) {
14346
15292
  return `'${value.replace(/'/g, `'\\''`)}'`;
14347
15293
  }
@@ -14360,9 +15306,9 @@ function buildSourceUpdateCommand(sourceRoot) {
14360
15306
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
14361
15307
  }
14362
15308
  function findRepoBackedSdkRoot(startPath) {
14363
- let current = resolve11(startPath);
15309
+ let current = resolve12(startPath);
14364
15310
  while (true) {
14365
- if (existsSync8(join10(current, "sdk", "package.json")) && existsSync8(join10(current, "sdk", "bin", "deepline-dev.ts"))) {
15311
+ if (existsSync8(join11(current, "sdk", "package.json")) && existsSync8(join11(current, "sdk", "bin", "deepline-dev.ts"))) {
14366
15312
  return current;
14367
15313
  }
14368
15314
  const parent = dirname9(current);
@@ -14371,7 +15317,7 @@ function findRepoBackedSdkRoot(startPath) {
14371
15317
  }
14372
15318
  }
14373
15319
  function resolveUpdatePlan() {
14374
- const entrypoint = process.argv[1] ? resolve11(process.argv[1]) : "";
15320
+ const entrypoint = process.argv[1] ? resolve12(process.argv[1]) : "";
14375
15321
  const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname9(entrypoint)) : null;
14376
15322
  if (sourceRoot) {
14377
15323
  return {
@@ -14643,8 +15589,8 @@ import {
14643
15589
  statSync as statSync2,
14644
15590
  writeFileSync as writeFileSync9
14645
15591
  } from "fs";
14646
- import { homedir as homedir5 } from "os";
14647
- import { dirname as dirname10, join as join11 } from "path";
15592
+ import { homedir as homedir6 } from "os";
15593
+ import { dirname as dirname10, join as join12 } from "path";
14648
15594
  var CHECK_TIMEOUT_MS2 = 3e3;
14649
15595
  var SDK_SKILL_NAME = "deepline-sdk";
14650
15596
  var attemptedSync = false;
@@ -14653,8 +15599,8 @@ function shouldSkipSkillsSync() {
14653
15599
  return value === "1" || value === "true" || value === "yes" || value === "on";
14654
15600
  }
14655
15601
  function sdkSkillsVersionPath(baseUrl) {
14656
- const home = process.env.HOME?.trim() || homedir5();
14657
- return join11(
15602
+ const home = process.env.HOME?.trim() || homedir6();
15603
+ return join12(
14658
15604
  home,
14659
15605
  ".local",
14660
15606
  "deepline",
@@ -14679,10 +15625,10 @@ function writeLocalSkillsVersion(baseUrl, version) {
14679
15625
  `, "utf-8");
14680
15626
  }
14681
15627
  function installedSdkSkillHasStalePositionalExecuteExamples() {
14682
- const home = process.env.HOME?.trim() || homedir5();
15628
+ const home = process.env.HOME?.trim() || homedir6();
14683
15629
  const roots = [
14684
- join11(home, ".claude", "skills", SDK_SKILL_NAME),
14685
- join11(home, ".agents", "skills", SDK_SKILL_NAME)
15630
+ join12(home, ".claude", "skills", SDK_SKILL_NAME),
15631
+ join12(home, ".agents", "skills", SDK_SKILL_NAME)
14686
15632
  ];
14687
15633
  const staleMarkers = [
14688
15634
  "ctx.tools.execute(key",
@@ -14693,9 +15639,9 @@ function installedSdkSkillHasStalePositionalExecuteExamples() {
14693
15639
  ];
14694
15640
  const scan = (dir) => {
14695
15641
  for (const entry of readdirSync2(dir)) {
14696
- const path = join11(dir, entry);
14697
- const stat3 = statSync2(path);
14698
- if (stat3.isDirectory()) {
15642
+ const path = join12(dir, entry);
15643
+ const stat4 = statSync2(path);
15644
+ if (stat4.isDirectory()) {
14699
15645
  if (scan(path)) return true;
14700
15646
  continue;
14701
15647
  }
@@ -14779,7 +15725,7 @@ function resolveSkillsInstallCommands(baseUrl) {
14779
15725
  return [npxInstall];
14780
15726
  }
14781
15727
  function runOneSkillsInstall(install) {
14782
- return new Promise((resolve12) => {
15728
+ return new Promise((resolve13) => {
14783
15729
  const child = spawn2(install.command, install.args, {
14784
15730
  stdio: ["ignore", "ignore", "pipe"],
14785
15731
  env: process.env
@@ -14789,7 +15735,7 @@ function runOneSkillsInstall(install) {
14789
15735
  stderr += chunk.toString("utf-8");
14790
15736
  });
14791
15737
  child.on("error", (error) => {
14792
- resolve12({
15738
+ resolve13({
14793
15739
  ok: false,
14794
15740
  detail: `failed to start ${install.command}: ${error.message}`,
14795
15741
  manualCommand: install.manualCommand
@@ -14797,11 +15743,11 @@ function runOneSkillsInstall(install) {
14797
15743
  });
14798
15744
  child.on("close", (code) => {
14799
15745
  if (code === 0) {
14800
- resolve12({ ok: true, detail: "", manualCommand: install.manualCommand });
15746
+ resolve13({ ok: true, detail: "", manualCommand: install.manualCommand });
14801
15747
  return;
14802
15748
  }
14803
15749
  const detail = stderr.trim();
14804
- resolve12({
15750
+ resolve13({
14805
15751
  ok: false,
14806
15752
  detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
14807
15753
  manualCommand: install.manualCommand
@@ -14884,10 +15830,10 @@ function shouldDeferSkillsSyncForCommand() {
14884
15830
  return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
14885
15831
  }
14886
15832
  async function runPlayRunnerHealthCheck() {
14887
- const dir = await mkdtemp(join12(tmpdir4(), "deepline-health-play-"));
14888
- const file = join12(dir, "health-check.play.ts");
15833
+ const dir = await mkdtemp2(join13(tmpdir5(), "deepline-health-play-"));
15834
+ const file = join13(dir, "health-check.play.ts");
14889
15835
  try {
14890
- await writeFile4(
15836
+ await writeFile5(
14891
15837
  file,
14892
15838
  [
14893
15839
  "import { definePlay } from 'deepline';",
@@ -14935,7 +15881,7 @@ async function runPlayRunnerHealthCheck() {
14935
15881
  }
14936
15882
  };
14937
15883
  } finally {
14938
- await rm(dir, { recursive: true, force: true });
15884
+ await rm2(dir, { recursive: true, force: true });
14939
15885
  }
14940
15886
  }
14941
15887
  async function main() {
@@ -15012,6 +15958,7 @@ Exit codes:
15012
15958
  registerSecretsCommands(program);
15013
15959
  registerBillingCommands(program);
15014
15960
  registerOrgCommands(program);
15961
+ registerEnrichCommand(program);
15015
15962
  registerCsvCommands(program);
15016
15963
  registerDbCommands(program);
15017
15964
  registerFeedbackCommands(program);