deepline 0.1.74 → 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.
- package/dist/cli/index.js +1295 -344
- package/dist/cli/index.mjs +1297 -346
- package/dist/index.d.mts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +6 -3
- package/dist/index.mjs +6 -3
- package/dist/repo/apps/play-runner-workers/src/entry.ts +101 -45
- package/dist/repo/sdk/src/client.ts +8 -0
- package/dist/repo/sdk/src/play.ts +2 -1
- package/dist/repo/sdk/src/plays/harness-stub.ts +1 -0
- package/dist/repo/sdk/src/release.ts +3 -3
- package/dist/repo/sdk/src/worker-play-entry.ts +3 -0
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +88 -0
- package/dist/repo/shared_libs/play-runtime/step-program-dataset-builder.ts +5 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +0 -40
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { mkdtemp, rm, writeFile as
|
|
5
|
-
import { join as
|
|
6
|
-
import { tmpdir as
|
|
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.
|
|
210
|
-
apiContract: "2026-06-dataset-column-
|
|
209
|
+
version: "0.1.76",
|
|
210
|
+
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
211
211
|
supportPolicy: {
|
|
212
|
-
latest: "0.1.
|
|
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((
|
|
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((
|
|
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((
|
|
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
|
|
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
|
-
|
|
3770
|
+
stat4.execution = formatDatasetExecutionStats(
|
|
3768
3771
|
rawExecutionStats,
|
|
3769
3772
|
totalRows
|
|
3770
3773
|
);
|
|
3771
3774
|
}
|
|
3772
3775
|
if (sampleValue !== void 0 && sampleValueType) {
|
|
3773
|
-
|
|
3774
|
-
|
|
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
|
-
|
|
3783
|
+
stat4.top_values = Object.fromEntries(
|
|
3781
3784
|
top.map(([key, count]) => [key, countPercentText(count, denominator)])
|
|
3782
3785
|
);
|
|
3783
3786
|
if (otherCount > 0) {
|
|
3784
|
-
|
|
3787
|
+
stat4.top_values["(other)"] = countPercentText(otherCount, denominator);
|
|
3785
3788
|
}
|
|
3786
3789
|
if (empty > 0) {
|
|
3787
|
-
|
|
3790
|
+
stat4.top_values["(null)"] = countPercentText(empty, denominator);
|
|
3788
3791
|
}
|
|
3789
3792
|
} else if (empty > 0 && nonEmpty > 0) {
|
|
3790
|
-
|
|
3793
|
+
stat4.top_values = { "(null)": countPercentText(empty, denominator) };
|
|
3791
3794
|
}
|
|
3792
|
-
columnStats[column] =
|
|
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/
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
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,
|
|
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
|
-
`${
|
|
6730
|
-
...playInspectionComments(label,
|
|
6731
|
-
`${
|
|
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(`${
|
|
6537
|
+
lines.push(`${indent2}// ${field}: row["TODO_SOURCE_FIELD"],`);
|
|
6735
6538
|
}
|
|
6736
6539
|
if (optional.length > 0) {
|
|
6737
6540
|
lines.push("");
|
|
6738
|
-
lines.push(`${
|
|
6541
|
+
lines.push(`${indent2}// optional (delete unused):`);
|
|
6739
6542
|
for (const field of optional) {
|
|
6740
|
-
lines.push(`${
|
|
6543
|
+
lines.push(`${indent2}// ${field}: row["TODO_SOURCE_FIELD"],`);
|
|
6741
6544
|
}
|
|
6742
6545
|
}
|
|
6743
6546
|
return `{
|
|
6744
6547
|
${lines.join("\n")}
|
|
6745
|
-
${
|
|
6548
|
+
${indent2.slice(2)}}`;
|
|
6746
6549
|
}
|
|
6747
|
-
function generateCompanyInputObjectFromSchema(schema,
|
|
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
|
-
`${
|
|
6753
|
-
...playInspectionComments(label,
|
|
6754
|
-
`${
|
|
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(`${
|
|
6560
|
+
lines.push(`${indent2}// ${field}: company["TODO_SOURCE_FIELD"],`);
|
|
6758
6561
|
}
|
|
6759
6562
|
if (optional.length > 0) {
|
|
6760
6563
|
lines.push("");
|
|
6761
|
-
lines.push(`${
|
|
6564
|
+
lines.push(`${indent2}// optional (delete unused):`);
|
|
6762
6565
|
for (const field of optional) {
|
|
6763
|
-
lines.push(`${
|
|
6566
|
+
lines.push(`${indent2}// ${field}: company["TODO_SOURCE_FIELD"],`);
|
|
6764
6567
|
}
|
|
6765
6568
|
}
|
|
6766
6569
|
return `{
|
|
6767
6570
|
${lines.join("\n")}
|
|
6768
|
-
${
|
|
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
|
-
`${
|
|
6793
|
-
`${
|
|
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(`${
|
|
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
|
-
`${
|
|
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(`${
|
|
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(`${
|
|
6617
|
+
lines.push(`${indent2}// ${field}: limit,`);
|
|
6815
6618
|
} else {
|
|
6816
|
-
lines.push(`${
|
|
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(`${
|
|
6626
|
+
lines.push(`${indent2}limit,`);
|
|
6824
6627
|
}
|
|
6825
6628
|
return `{
|
|
6826
6629
|
${lines.join("\n")}
|
|
6827
|
-
${
|
|
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
|
-
`${
|
|
6836
|
-
...playInspectionComments(label,
|
|
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(`${
|
|
6642
|
+
lines.push(`${indent2}// ${field}: ${exampleValueComment(field)},`);
|
|
6840
6643
|
}
|
|
6841
|
-
if (!required.includes("limit")) lines.push(`${
|
|
6644
|
+
if (!required.includes("limit")) lines.push(`${indent2}limit,`);
|
|
6842
6645
|
return `{
|
|
6843
6646
|
${lines.join("\n")}
|
|
6844
|
-
${
|
|
6647
|
+
${indent2.slice(2)}}`;
|
|
6845
6648
|
}
|
|
6846
6649
|
function requiredPlayInputFields(play) {
|
|
6847
6650
|
const schema = play?.inputSchema;
|
|
@@ -6882,8 +6685,8 @@ function finderProviderStepPrefix(finder) {
|
|
|
6882
6685
|
function safeIdentifier(value) {
|
|
6883
6686
|
return value.replace(/[^A-Za-z0-9_]+/g, "_");
|
|
6884
6687
|
}
|
|
6885
|
-
function playInspectionComments(playRef,
|
|
6886
|
-
return [`${
|
|
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)}]`;
|
|
@@ -7982,7 +7785,7 @@ function traceCliSync(phase, fields, run) {
|
|
|
7982
7785
|
}
|
|
7983
7786
|
}
|
|
7984
7787
|
function sleep4(ms) {
|
|
7985
|
-
return new Promise((
|
|
7788
|
+
return new Promise((resolve13) => setTimeout(resolve13, ms));
|
|
7986
7789
|
}
|
|
7987
7790
|
function parseReferencedPlayTarget2(target) {
|
|
7988
7791
|
const trimmed = target.trim();
|
|
@@ -9625,20 +9428,20 @@ function attachDatasetStatsToResult(result, datasetStats) {
|
|
|
9625
9428
|
};
|
|
9626
9429
|
return attach(result, "");
|
|
9627
9430
|
}
|
|
9628
|
-
function formatDatasetStatsLines(datasetStats,
|
|
9431
|
+
function formatDatasetStatsLines(datasetStats, indent2 = " ") {
|
|
9629
9432
|
if (!datasetStats) {
|
|
9630
9433
|
return [];
|
|
9631
9434
|
}
|
|
9632
|
-
const lines = [`${
|
|
9633
|
-
for (const [column,
|
|
9435
|
+
const lines = [`${indent2}summary:`];
|
|
9436
|
+
for (const [column, stat4] of Object.entries(datasetStats.columnStats).slice(
|
|
9634
9437
|
0,
|
|
9635
9438
|
12
|
|
9636
9439
|
)) {
|
|
9637
|
-
const topValues =
|
|
9638
|
-
const sample =
|
|
9639
|
-
const execution =
|
|
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(", ")}` : "";
|
|
9640
9443
|
lines.push(
|
|
9641
|
-
`${
|
|
9444
|
+
`${indent2} ${column}: non_empty=${stat4.non_empty}, unique=${stat4.unique}${topValues}${sample}${execution}`
|
|
9642
9445
|
);
|
|
9643
9446
|
}
|
|
9644
9447
|
return lines;
|
|
@@ -9679,7 +9482,7 @@ function formatSummaryScalarParts(record, skipKeys = /* @__PURE__ */ new Set())
|
|
|
9679
9482
|
}
|
|
9680
9483
|
return parts;
|
|
9681
9484
|
}
|
|
9682
|
-
function formatPackageDatasetSummaryLines(summary,
|
|
9485
|
+
function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
|
|
9683
9486
|
const record = readRecord(summary);
|
|
9684
9487
|
const columnStats = readRecord(record?.columnStats);
|
|
9685
9488
|
if (!record || !columnStats) {
|
|
@@ -9688,7 +9491,7 @@ function formatPackageDatasetSummaryLines(summary, indent = " ") {
|
|
|
9688
9491
|
const lines = [];
|
|
9689
9492
|
const parts = formatSummaryScalarParts(record, /* @__PURE__ */ new Set(["columnStats"]));
|
|
9690
9493
|
if (parts.length > 0) {
|
|
9691
|
-
lines.push(`${
|
|
9494
|
+
lines.push(`${indent2}summary: ${parts.join(" ")}`);
|
|
9692
9495
|
}
|
|
9693
9496
|
for (const [column, rawColumnSummary] of Object.entries(columnStats)) {
|
|
9694
9497
|
const columnSummary = readRecord(rawColumnSummary);
|
|
@@ -9700,7 +9503,7 @@ function formatPackageDatasetSummaryLines(summary, indent = " ") {
|
|
|
9700
9503
|
executionText ? `execution=${executionText}` : null
|
|
9701
9504
|
].filter(Boolean);
|
|
9702
9505
|
if (columnParts.length > 0) {
|
|
9703
|
-
lines.push(`${
|
|
9506
|
+
lines.push(`${indent2} ${column}: ${columnParts.join(" ")}`);
|
|
9704
9507
|
}
|
|
9705
9508
|
}
|
|
9706
9509
|
return lines;
|
|
@@ -11910,9 +11713,11 @@ Idempotent execution:
|
|
|
11910
11713
|
.run({ key: 'domain' });
|
|
11911
11714
|
|
|
11912
11715
|
Reuse needs the same play, tool id, dataset name, row key, and compatible logic.
|
|
11913
|
-
To
|
|
11716
|
+
To recompute a visible cell on a later cron/user run after a window, put
|
|
11717
|
+
staleAfterSeconds on the cell-producing column:
|
|
11914
11718
|
|
|
11915
|
-
.
|
|
11719
|
+
.withColumn('cto', resolver, { staleAfterSeconds: 86400 })
|
|
11720
|
+
.run({ key: 'domain' })
|
|
11916
11721
|
|
|
11917
11722
|
Examples:
|
|
11918
11723
|
deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
|
|
@@ -12441,7 +12246,9 @@ async function handlePlayShareStatus(args) {
|
|
|
12441
12246
|
);
|
|
12442
12247
|
return 0;
|
|
12443
12248
|
}
|
|
12444
|
-
console.log(
|
|
12249
|
+
console.log(
|
|
12250
|
+
`${status.playName}: published v${status.share.publishedVersion}`
|
|
12251
|
+
);
|
|
12445
12252
|
console.log(` url: ${status.share.publicPath}`);
|
|
12446
12253
|
console.log(` seo: ${status.share.seoIndexing}`);
|
|
12447
12254
|
console.log(
|
|
@@ -12603,7 +12410,9 @@ async function handlePlayShareRegenerate(args) {
|
|
|
12603
12410
|
async function handlePlayShareUnpublish(args) {
|
|
12604
12411
|
const target = args[0];
|
|
12605
12412
|
if (!target) {
|
|
12606
|
-
console.error(
|
|
12413
|
+
console.error(
|
|
12414
|
+
"Usage: deepline plays share unpublish <play> --yes [--json]"
|
|
12415
|
+
);
|
|
12607
12416
|
return 2;
|
|
12608
12417
|
}
|
|
12609
12418
|
if (!args.includes("--yes")) {
|
|
@@ -12623,53 +12432,1194 @@ async function handlePlayShareUnpublish(args) {
|
|
|
12623
12432
|
return 0;
|
|
12624
12433
|
}
|
|
12625
12434
|
|
|
12626
|
-
// src/cli/
|
|
12627
|
-
|
|
12628
|
-
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
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;
|
|
12635
12479
|
}
|
|
12636
|
-
return
|
|
12480
|
+
return prefixed;
|
|
12637
12481
|
}
|
|
12638
|
-
function
|
|
12639
|
-
|
|
12640
|
-
return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
|
|
12482
|
+
function stringLiteral(value) {
|
|
12483
|
+
return JSON.stringify(value);
|
|
12641
12484
|
}
|
|
12642
|
-
|
|
12643
|
-
if (
|
|
12644
|
-
|
|
12645
|
-
|
|
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)
|
|
12646
12492
|
);
|
|
12493
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`).join(",")}}`;
|
|
12647
12494
|
}
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
|
|
12656
|
-
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
|
|
12660
|
-
|
|
12661
|
-
|
|
12662
|
-
|
|
12663
|
-
|
|
12664
|
-
|
|
12665
|
-
|
|
12666
|
-
|
|
12667
|
-
|
|
12668
|
-
|
|
12669
|
-
|
|
12670
|
-
|
|
12671
|
-
|
|
12672
|
-
|
|
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);
|
|
13619
|
+
};
|
|
13620
|
+
const fail = (error) => {
|
|
13621
|
+
if (settled) return;
|
|
13622
|
+
settled = true;
|
|
12673
13623
|
output.write("\n");
|
|
12674
13624
|
cleanup();
|
|
12675
13625
|
reject(error);
|
|
@@ -12838,13 +13788,13 @@ Examples:
|
|
|
12838
13788
|
// src/cli/commands/tools.ts
|
|
12839
13789
|
import { Option } from "commander";
|
|
12840
13790
|
import { chmodSync, mkdtempSync, writeFileSync as writeFileSync8 } from "fs";
|
|
12841
|
-
import { tmpdir as
|
|
12842
|
-
import { join as
|
|
13791
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
13792
|
+
import { join as join10 } from "path";
|
|
12843
13793
|
|
|
12844
13794
|
// src/tool-output.ts
|
|
12845
13795
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync7 } from "fs";
|
|
12846
|
-
import { homedir as
|
|
12847
|
-
import { join as
|
|
13796
|
+
import { homedir as homedir5 } from "os";
|
|
13797
|
+
import { join as join9 } from "path";
|
|
12848
13798
|
function isPlainObject(value) {
|
|
12849
13799
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
12850
13800
|
}
|
|
@@ -12940,19 +13890,19 @@ function tryConvertToList(payload, options) {
|
|
|
12940
13890
|
return null;
|
|
12941
13891
|
}
|
|
12942
13892
|
function ensureOutputDir() {
|
|
12943
|
-
const outputDir =
|
|
13893
|
+
const outputDir = join9(homedir5(), ".local", "share", "deepline", "data");
|
|
12944
13894
|
mkdirSync4(outputDir, { recursive: true });
|
|
12945
13895
|
return outputDir;
|
|
12946
13896
|
}
|
|
12947
13897
|
function writeJsonOutputFile(payload, stem) {
|
|
12948
13898
|
const outputDir = ensureOutputDir();
|
|
12949
|
-
const outputPath =
|
|
13899
|
+
const outputPath = join9(outputDir, `${stem}_${Date.now()}.json`);
|
|
12950
13900
|
writeFileSync7(outputPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
12951
13901
|
return outputPath;
|
|
12952
13902
|
}
|
|
12953
13903
|
function writeCsvOutputFile(rows, stem) {
|
|
12954
13904
|
const outputDir = ensureOutputDir();
|
|
12955
|
-
const outputPath =
|
|
13905
|
+
const outputPath = join9(outputDir, `${stem}_${Date.now()}.csv`);
|
|
12956
13906
|
const seen = /* @__PURE__ */ new Set();
|
|
12957
13907
|
const columns = [];
|
|
12958
13908
|
for (const row of rows) {
|
|
@@ -13711,7 +14661,7 @@ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
|
|
|
13711
14661
|
const expression = stringField(firstGetter, "expression");
|
|
13712
14662
|
if (expression)
|
|
13713
14663
|
console.log(
|
|
13714
|
-
`const ${
|
|
14664
|
+
`const ${safeIdentifier3(name)} = ${expression.replace(/^toolExecutionResult\./, "result.")};`
|
|
13715
14665
|
);
|
|
13716
14666
|
}
|
|
13717
14667
|
console.log("```");
|
|
@@ -13780,7 +14730,7 @@ function samplePayloadForInputFields(fields) {
|
|
|
13780
14730
|
function stableStepIdForTool(toolId) {
|
|
13781
14731
|
return toolId.replace(/^[a-z0-9]+_/, "").replace(/[^a-z0-9_]+/gi, "_") || "tool_call";
|
|
13782
14732
|
}
|
|
13783
|
-
function
|
|
14733
|
+
function safeIdentifier3(name) {
|
|
13784
14734
|
const cleaned = name.replace(/[^a-zA-Z0-9_$]+/g, "_").replace(/^[^a-zA-Z_$]+/, "");
|
|
13785
14735
|
return cleaned || "value";
|
|
13786
14736
|
}
|
|
@@ -14043,9 +14993,9 @@ function powerShellQuote(value) {
|
|
|
14043
14993
|
function seedToolListScript(input2) {
|
|
14044
14994
|
const stem = safeFileStem(input2.toolId);
|
|
14045
14995
|
const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
|
|
14046
|
-
const scriptDir = mkdtempSync(
|
|
14996
|
+
const scriptDir = mkdtempSync(join10(tmpdir4(), "deepline-workflow-seed-"));
|
|
14047
14997
|
chmodSync(scriptDir, 448);
|
|
14048
|
-
const scriptPath =
|
|
14998
|
+
const scriptPath = join10(scriptDir, fileName);
|
|
14049
14999
|
const projectDir = `deepline/projects/${stem}-workflow`;
|
|
14050
15000
|
const playName = `${stem}-workflow`;
|
|
14051
15001
|
const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
|
|
@@ -14337,7 +15287,7 @@ async function executeTool(args) {
|
|
|
14337
15287
|
// src/cli/commands/update.ts
|
|
14338
15288
|
import { spawn } from "child_process";
|
|
14339
15289
|
import { existsSync as existsSync8 } from "fs";
|
|
14340
|
-
import { dirname as dirname9, join as
|
|
15290
|
+
import { dirname as dirname9, join as join11, resolve as resolve12 } from "path";
|
|
14341
15291
|
function posixShellQuote(value) {
|
|
14342
15292
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
14343
15293
|
}
|
|
@@ -14356,9 +15306,9 @@ function buildSourceUpdateCommand(sourceRoot) {
|
|
|
14356
15306
|
return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
|
|
14357
15307
|
}
|
|
14358
15308
|
function findRepoBackedSdkRoot(startPath) {
|
|
14359
|
-
let current =
|
|
15309
|
+
let current = resolve12(startPath);
|
|
14360
15310
|
while (true) {
|
|
14361
|
-
if (existsSync8(
|
|
15311
|
+
if (existsSync8(join11(current, "sdk", "package.json")) && existsSync8(join11(current, "sdk", "bin", "deepline-dev.ts"))) {
|
|
14362
15312
|
return current;
|
|
14363
15313
|
}
|
|
14364
15314
|
const parent = dirname9(current);
|
|
@@ -14367,7 +15317,7 @@ function findRepoBackedSdkRoot(startPath) {
|
|
|
14367
15317
|
}
|
|
14368
15318
|
}
|
|
14369
15319
|
function resolveUpdatePlan() {
|
|
14370
|
-
const entrypoint = process.argv[1] ?
|
|
15320
|
+
const entrypoint = process.argv[1] ? resolve12(process.argv[1]) : "";
|
|
14371
15321
|
const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname9(entrypoint)) : null;
|
|
14372
15322
|
if (sourceRoot) {
|
|
14373
15323
|
return {
|
|
@@ -14639,8 +15589,8 @@ import {
|
|
|
14639
15589
|
statSync as statSync2,
|
|
14640
15590
|
writeFileSync as writeFileSync9
|
|
14641
15591
|
} from "fs";
|
|
14642
|
-
import { homedir as
|
|
14643
|
-
import { dirname as dirname10, join as
|
|
15592
|
+
import { homedir as homedir6 } from "os";
|
|
15593
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
14644
15594
|
var CHECK_TIMEOUT_MS2 = 3e3;
|
|
14645
15595
|
var SDK_SKILL_NAME = "deepline-sdk";
|
|
14646
15596
|
var attemptedSync = false;
|
|
@@ -14649,8 +15599,8 @@ function shouldSkipSkillsSync() {
|
|
|
14649
15599
|
return value === "1" || value === "true" || value === "yes" || value === "on";
|
|
14650
15600
|
}
|
|
14651
15601
|
function sdkSkillsVersionPath(baseUrl) {
|
|
14652
|
-
const home = process.env.HOME?.trim() ||
|
|
14653
|
-
return
|
|
15602
|
+
const home = process.env.HOME?.trim() || homedir6();
|
|
15603
|
+
return join12(
|
|
14654
15604
|
home,
|
|
14655
15605
|
".local",
|
|
14656
15606
|
"deepline",
|
|
@@ -14675,10 +15625,10 @@ function writeLocalSkillsVersion(baseUrl, version) {
|
|
|
14675
15625
|
`, "utf-8");
|
|
14676
15626
|
}
|
|
14677
15627
|
function installedSdkSkillHasStalePositionalExecuteExamples() {
|
|
14678
|
-
const home = process.env.HOME?.trim() ||
|
|
15628
|
+
const home = process.env.HOME?.trim() || homedir6();
|
|
14679
15629
|
const roots = [
|
|
14680
|
-
|
|
14681
|
-
|
|
15630
|
+
join12(home, ".claude", "skills", SDK_SKILL_NAME),
|
|
15631
|
+
join12(home, ".agents", "skills", SDK_SKILL_NAME)
|
|
14682
15632
|
];
|
|
14683
15633
|
const staleMarkers = [
|
|
14684
15634
|
"ctx.tools.execute(key",
|
|
@@ -14689,9 +15639,9 @@ function installedSdkSkillHasStalePositionalExecuteExamples() {
|
|
|
14689
15639
|
];
|
|
14690
15640
|
const scan = (dir) => {
|
|
14691
15641
|
for (const entry of readdirSync2(dir)) {
|
|
14692
|
-
const path =
|
|
14693
|
-
const
|
|
14694
|
-
if (
|
|
15642
|
+
const path = join12(dir, entry);
|
|
15643
|
+
const stat4 = statSync2(path);
|
|
15644
|
+
if (stat4.isDirectory()) {
|
|
14695
15645
|
if (scan(path)) return true;
|
|
14696
15646
|
continue;
|
|
14697
15647
|
}
|
|
@@ -14775,7 +15725,7 @@ function resolveSkillsInstallCommands(baseUrl) {
|
|
|
14775
15725
|
return [npxInstall];
|
|
14776
15726
|
}
|
|
14777
15727
|
function runOneSkillsInstall(install) {
|
|
14778
|
-
return new Promise((
|
|
15728
|
+
return new Promise((resolve13) => {
|
|
14779
15729
|
const child = spawn2(install.command, install.args, {
|
|
14780
15730
|
stdio: ["ignore", "ignore", "pipe"],
|
|
14781
15731
|
env: process.env
|
|
@@ -14785,7 +15735,7 @@ function runOneSkillsInstall(install) {
|
|
|
14785
15735
|
stderr += chunk.toString("utf-8");
|
|
14786
15736
|
});
|
|
14787
15737
|
child.on("error", (error) => {
|
|
14788
|
-
|
|
15738
|
+
resolve13({
|
|
14789
15739
|
ok: false,
|
|
14790
15740
|
detail: `failed to start ${install.command}: ${error.message}`,
|
|
14791
15741
|
manualCommand: install.manualCommand
|
|
@@ -14793,11 +15743,11 @@ function runOneSkillsInstall(install) {
|
|
|
14793
15743
|
});
|
|
14794
15744
|
child.on("close", (code) => {
|
|
14795
15745
|
if (code === 0) {
|
|
14796
|
-
|
|
15746
|
+
resolve13({ ok: true, detail: "", manualCommand: install.manualCommand });
|
|
14797
15747
|
return;
|
|
14798
15748
|
}
|
|
14799
15749
|
const detail = stderr.trim();
|
|
14800
|
-
|
|
15750
|
+
resolve13({
|
|
14801
15751
|
ok: false,
|
|
14802
15752
|
detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
|
|
14803
15753
|
manualCommand: install.manualCommand
|
|
@@ -14880,10 +15830,10 @@ function shouldDeferSkillsSyncForCommand() {
|
|
|
14880
15830
|
return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
|
|
14881
15831
|
}
|
|
14882
15832
|
async function runPlayRunnerHealthCheck() {
|
|
14883
|
-
const dir = await
|
|
14884
|
-
const file =
|
|
15833
|
+
const dir = await mkdtemp2(join13(tmpdir5(), "deepline-health-play-"));
|
|
15834
|
+
const file = join13(dir, "health-check.play.ts");
|
|
14885
15835
|
try {
|
|
14886
|
-
await
|
|
15836
|
+
await writeFile5(
|
|
14887
15837
|
file,
|
|
14888
15838
|
[
|
|
14889
15839
|
"import { definePlay } from 'deepline';",
|
|
@@ -14931,7 +15881,7 @@ async function runPlayRunnerHealthCheck() {
|
|
|
14931
15881
|
}
|
|
14932
15882
|
};
|
|
14933
15883
|
} finally {
|
|
14934
|
-
await
|
|
15884
|
+
await rm2(dir, { recursive: true, force: true });
|
|
14935
15885
|
}
|
|
14936
15886
|
}
|
|
14937
15887
|
async function main() {
|
|
@@ -15008,6 +15958,7 @@ Exit codes:
|
|
|
15008
15958
|
registerSecretsCommands(program);
|
|
15009
15959
|
registerBillingCommands(program);
|
|
15010
15960
|
registerOrgCommands(program);
|
|
15961
|
+
registerEnrichCommand(program);
|
|
15011
15962
|
registerCsvCommands(program);
|
|
15012
15963
|
registerDbCommands(program);
|
|
15013
15964
|
registerFeedbackCommands(program);
|