deepline 0.1.101 → 0.1.103
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/README.md +14 -12
- package/dist/cli/index.js +1138 -147
- package/dist/cli/index.mjs +1149 -150
- package/dist/index.d.mts +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +72 -6
- package/dist/index.mjs +72 -6
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +209 -68
- package/dist/repo/apps/play-runner-workers/src/entry.ts +141 -33
- package/dist/repo/sdk/src/client.ts +3 -3
- package/dist/repo/sdk/src/http.ts +89 -0
- package/dist/repo/sdk/src/index.ts +1 -1
- package/dist/repo/sdk/src/play.ts +2 -2
- package/dist/repo/sdk/src/release.ts +7 -2
- package/dist/repo/sdk/src/stream-reconnect.ts +1 -1
- package/dist/repo/sdk/src/types.ts +2 -2
- package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +204 -0
- package/dist/repo/shared_libs/play-runtime/run-ledger.ts +7 -2
- package/dist/repo/shared_libs/play-runtime/run-snapshot-stream.ts +8 -0
- package/package.json +1 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile6 } from "fs/promises";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join15 } from "path";
|
|
6
6
|
import { tmpdir as tmpdir5 } from "os";
|
|
7
7
|
import { Command as Command3 } from "commander";
|
|
8
8
|
|
|
@@ -210,10 +210,15 @@ var SDK_RELEASE = {
|
|
|
210
210
|
// 0.1.98 ships the duplicate-browser-tab fix (default-browser detection).
|
|
211
211
|
// 0.1.99 ships prebuilt job-change source-column preservation and validation fixes.
|
|
212
212
|
// 0.1.101 ships retryable play artifact publish failures and CI retry hardening.
|
|
213
|
-
|
|
213
|
+
// 0.1.102 ships the job-change ledger fixes: recovered-dataset export on
|
|
214
|
+
// failed runs, persisted/succeeded/failed row counts, strict local CSV
|
|
215
|
+
// preflight (existence, data rows, quotes, duplicate headers), HTML error
|
|
216
|
+
// scrubbing, and word-boundary watch truncation.
|
|
217
|
+
// 0.1.103 ships the refined SDK CLI command surface.
|
|
218
|
+
version: "0.1.103",
|
|
214
219
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
215
220
|
supportPolicy: {
|
|
216
|
-
latest: "0.1.
|
|
221
|
+
latest: "0.1.103",
|
|
217
222
|
minimumSupported: "0.1.53",
|
|
218
223
|
deprecatedBelow: "0.1.53"
|
|
219
224
|
}
|
|
@@ -364,6 +369,22 @@ var HttpClient = class {
|
|
|
364
369
|
parsed = body;
|
|
365
370
|
}
|
|
366
371
|
if (!response.ok) {
|
|
372
|
+
const htmlError = detectHtmlErrorBody(
|
|
373
|
+
body,
|
|
374
|
+
response.headers.get("content-type")
|
|
375
|
+
);
|
|
376
|
+
if (htmlError) {
|
|
377
|
+
throw new DeeplineError(
|
|
378
|
+
htmlError.message(response.status),
|
|
379
|
+
response.status,
|
|
380
|
+
"API_ERROR",
|
|
381
|
+
{
|
|
382
|
+
htmlErrorPage: true,
|
|
383
|
+
...htmlError.title ? { title: htmlError.title } : {},
|
|
384
|
+
...htmlError.workerThrewException ? { workerThrewException: true } : {}
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
367
388
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
368
389
|
const msg = typeof errorValue === "string" ? errorValue : errorValue && typeof errorValue === "object" && "message" in errorValue && typeof errorValue.message === "string" ? errorValue.message : typeof parsed === "object" && parsed && "message" in parsed && typeof parsed.message === "string" ? parsed.message : `HTTP ${response.status}`;
|
|
369
390
|
throw new DeeplineError(msg, response.status, "API_ERROR", {
|
|
@@ -424,6 +445,22 @@ var HttpClient = class {
|
|
|
424
445
|
}
|
|
425
446
|
if (!response.ok) {
|
|
426
447
|
const body = await response.text();
|
|
448
|
+
const htmlError = detectHtmlErrorBody(
|
|
449
|
+
body,
|
|
450
|
+
response.headers.get("content-type")
|
|
451
|
+
);
|
|
452
|
+
if (htmlError) {
|
|
453
|
+
throw new DeeplineError(
|
|
454
|
+
htmlError.message(response.status),
|
|
455
|
+
response.status,
|
|
456
|
+
"API_ERROR",
|
|
457
|
+
{
|
|
458
|
+
htmlErrorPage: true,
|
|
459
|
+
...htmlError.title ? { title: htmlError.title } : {},
|
|
460
|
+
...htmlError.workerThrewException ? { workerThrewException: true } : {}
|
|
461
|
+
}
|
|
462
|
+
);
|
|
463
|
+
}
|
|
427
464
|
const parsed = parseResponseBody(body);
|
|
428
465
|
throw new DeeplineError(
|
|
429
466
|
apiErrorMessage(parsed, response.status),
|
|
@@ -489,6 +526,31 @@ function parseResponseBody(body) {
|
|
|
489
526
|
return body;
|
|
490
527
|
}
|
|
491
528
|
}
|
|
529
|
+
function detectHtmlErrorBody(body, contentType) {
|
|
530
|
+
const trimmed = body.trim();
|
|
531
|
+
const lower = trimmed.toLowerCase();
|
|
532
|
+
const isHtml = (contentType ?? "").toLowerCase().includes("text/html") || lower.startsWith("<!doctype") || lower.startsWith("<html");
|
|
533
|
+
if (!isHtml) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
const titleMatch = trimmed.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
537
|
+
const title = titleMatch?.[1]?.replace(/\s+/g, " ").trim() || void 0;
|
|
538
|
+
const workerThrewException = /worker threw exception/i.test(trimmed);
|
|
539
|
+
return {
|
|
540
|
+
title,
|
|
541
|
+
workerThrewException,
|
|
542
|
+
message: (status) => {
|
|
543
|
+
const segments = [`HTTP ${status}`];
|
|
544
|
+
if (workerThrewException) {
|
|
545
|
+
segments.push("Worker threw exception");
|
|
546
|
+
}
|
|
547
|
+
if (title) {
|
|
548
|
+
segments.push(title);
|
|
549
|
+
}
|
|
550
|
+
return `${segments.join(": ")} (Cloudflare HTML error page suppressed)`;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
492
554
|
function apiErrorMessage(parsed, status) {
|
|
493
555
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
494
556
|
if (typeof errorValue === "string") {
|
|
@@ -565,7 +627,7 @@ function decodeSseFrame(frame) {
|
|
|
565
627
|
return parsed;
|
|
566
628
|
}
|
|
567
629
|
function sleep(ms) {
|
|
568
|
-
return new Promise((
|
|
630
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
569
631
|
}
|
|
570
632
|
|
|
571
633
|
// src/stream-reconnect.ts
|
|
@@ -584,7 +646,7 @@ function isTransientPlayStreamError(error) {
|
|
|
584
646
|
return error.statusCode >= 500 && error.statusCode < 600;
|
|
585
647
|
}
|
|
586
648
|
const text = error instanceof Error ? error.message : String(error);
|
|
587
|
-
return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
|
|
649
|
+
return /auth validation backend timed out|coordinator \/submit(?:\?[^ ]*)? 5\d\d|Worker threw exception|Internal Server Error|Service Unavailable|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
|
|
588
650
|
text
|
|
589
651
|
);
|
|
590
652
|
}
|
|
@@ -816,6 +878,10 @@ function buildSnapshotFromLedger(snapshot) {
|
|
|
816
878
|
return {
|
|
817
879
|
runId: snapshot.runId,
|
|
818
880
|
status: normalizePlayRunLiveStatus(snapshot.status),
|
|
881
|
+
createdAt: snapshot.createdAt ?? null,
|
|
882
|
+
startedAt: snapshot.startedAt ?? null,
|
|
883
|
+
finishedAt: snapshot.finishedAt ?? null,
|
|
884
|
+
durationMs: snapshot.durationMs ?? null,
|
|
819
885
|
updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
|
|
820
886
|
logs: snapshot.logTail,
|
|
821
887
|
totalLogCount: snapshot.totalLogCount,
|
|
@@ -1272,14 +1338,14 @@ async function* observeRunEvents(options) {
|
|
|
1272
1338
|
try {
|
|
1273
1339
|
for (; ; ) {
|
|
1274
1340
|
if (queue.length === 0) {
|
|
1275
|
-
const waitForItem = new Promise((
|
|
1276
|
-
wake =
|
|
1341
|
+
const waitForItem = new Promise((resolve16) => {
|
|
1342
|
+
wake = resolve16;
|
|
1277
1343
|
});
|
|
1278
1344
|
if (!sawFirstSnapshot) {
|
|
1279
1345
|
const timedOut = await Promise.race([
|
|
1280
1346
|
waitForItem.then(() => false),
|
|
1281
1347
|
new Promise(
|
|
1282
|
-
(
|
|
1348
|
+
(resolve16) => setTimeout(() => resolve16(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
|
|
1283
1349
|
)
|
|
1284
1350
|
]);
|
|
1285
1351
|
if (timedOut && queue.length === 0) {
|
|
@@ -1376,7 +1442,7 @@ var EXECUTE_RESPONSE_CONTRACT_HEADER = "x-deepline-execute-response-contract";
|
|
|
1376
1442
|
var V2_EXECUTE_RESPONSE_CONTRACT = "v2-tool-response";
|
|
1377
1443
|
var COMPILE_MANIFEST_RETRY_DELAYS_MS = [250, 1e3];
|
|
1378
1444
|
function sleep2(ms) {
|
|
1379
|
-
return new Promise((
|
|
1445
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
1380
1446
|
}
|
|
1381
1447
|
function isTransientCompileManifestError(error) {
|
|
1382
1448
|
if (error instanceof DeeplineError && typeof error.statusCode === "number") {
|
|
@@ -1818,7 +1884,7 @@ var DeeplineClient = class {
|
|
|
1818
1884
|
* or {@link runPlay}.
|
|
1819
1885
|
*
|
|
1820
1886
|
* Supported invocation surfaces intentionally share this same run contract:
|
|
1821
|
-
* `deepline
|
|
1887
|
+
* `deepline plays run`, repo scripts such as `bun run deepline -- plays run`,
|
|
1822
1888
|
* SDK context calls like `Deepline.connect().play(name).run()`, and direct
|
|
1823
1889
|
* `POST /api/v2/plays/run` calls all return a workflow/run id. The completed
|
|
1824
1890
|
* output is always retrievable from `getPlayStatus(runId).result` (or from
|
|
@@ -1987,7 +2053,7 @@ var DeeplineClient = class {
|
|
|
1987
2053
|
*
|
|
1988
2054
|
* Unlike {@link registerPlayArtifact}, this does not store the artifact,
|
|
1989
2055
|
* publish a revision, or start a run. It is the authoritative cloud validation
|
|
1990
|
-
* path used by `deepline
|
|
2056
|
+
* path used by `deepline plays check`.
|
|
1991
2057
|
*/
|
|
1992
2058
|
async checkPlayArtifact(input2) {
|
|
1993
2059
|
return this.http.post("/api/v2/plays/check", input2);
|
|
@@ -2180,7 +2246,7 @@ var DeeplineClient = class {
|
|
|
2180
2246
|
* Get the current status of a play execution.
|
|
2181
2247
|
*
|
|
2182
2248
|
* Internal/advanced primitive. Public callers should usually prefer
|
|
2183
|
-
* {@link runPlay}, {@link PlayJob.get}, or `deepline
|
|
2249
|
+
* {@link runPlay}, {@link PlayJob.get}, or `deepline plays run --watch`.
|
|
2184
2250
|
*
|
|
2185
2251
|
* @param workflowId - Play-run id from {@link startPlayRun}
|
|
2186
2252
|
* @returns Current status with progress logs and partial results
|
|
@@ -3035,9 +3101,9 @@ async function writeOutputFile(filename, content) {
|
|
|
3035
3101
|
return fullPath;
|
|
3036
3102
|
}
|
|
3037
3103
|
function browserOpenStateFile() {
|
|
3038
|
-
const
|
|
3104
|
+
const homeDir2 = process.env.HOME || homedir3();
|
|
3039
3105
|
return join3(
|
|
3040
|
-
|
|
3106
|
+
homeDir2,
|
|
3041
3107
|
".local",
|
|
3042
3108
|
"deepline",
|
|
3043
3109
|
"runtime",
|
|
@@ -3511,8 +3577,8 @@ function printCommandEnvelope(envelope, options = {}) {
|
|
|
3511
3577
|
|
|
3512
3578
|
// src/cli/commands/auth.ts
|
|
3513
3579
|
var EXIT_OK = 0;
|
|
3514
|
-
var EXIT_AUTH =
|
|
3515
|
-
var EXIT_SERVER =
|
|
3580
|
+
var EXIT_AUTH = 3;
|
|
3581
|
+
var EXIT_SERVER = 5;
|
|
3516
3582
|
function envFilePath(baseUrl) {
|
|
3517
3583
|
return hostEnvFilePath(baseUrl);
|
|
3518
3584
|
}
|
|
@@ -3604,7 +3670,7 @@ function buildCandidateUrls2(url) {
|
|
|
3604
3670
|
}
|
|
3605
3671
|
}
|
|
3606
3672
|
function sleep4(ms) {
|
|
3607
|
-
return new Promise((
|
|
3673
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
3608
3674
|
}
|
|
3609
3675
|
function printDeeplineLogo() {
|
|
3610
3676
|
if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
|
|
@@ -3867,7 +3933,7 @@ async function handleStatus(args) {
|
|
|
3867
3933
|
...hostStatusPayload ?? { host: baseUrl },
|
|
3868
3934
|
status: "not connected",
|
|
3869
3935
|
connected: false,
|
|
3870
|
-
next: "deepline auth register",
|
|
3936
|
+
next: "deepline auth register --no-wait && deepline auth wait",
|
|
3871
3937
|
render: {
|
|
3872
3938
|
sections: [
|
|
3873
3939
|
{
|
|
@@ -3875,7 +3941,10 @@ async function handleStatus(args) {
|
|
|
3875
3941
|
lines: [...hostLines, "Status: not connected"]
|
|
3876
3942
|
}
|
|
3877
3943
|
],
|
|
3878
|
-
actions: [
|
|
3944
|
+
actions: [
|
|
3945
|
+
{ label: "Register", command: "deepline auth register --no-wait" },
|
|
3946
|
+
{ label: "Wait", command: "deepline auth wait" }
|
|
3947
|
+
]
|
|
3879
3948
|
}
|
|
3880
3949
|
},
|
|
3881
3950
|
{ json: jsonOutput }
|
|
@@ -3897,7 +3966,7 @@ async function handleStatus(args) {
|
|
|
3897
3966
|
...hostStatusPayload ?? { host: baseUrl },
|
|
3898
3967
|
status: "unauthorized",
|
|
3899
3968
|
connected: false,
|
|
3900
|
-
next: "deepline auth register",
|
|
3969
|
+
next: "deepline auth register --no-wait && deepline auth wait",
|
|
3901
3970
|
render: {
|
|
3902
3971
|
sections: [
|
|
3903
3972
|
{
|
|
@@ -3905,7 +3974,10 @@ async function handleStatus(args) {
|
|
|
3905
3974
|
lines: [...hostLines, "Status: unauthorized"]
|
|
3906
3975
|
}
|
|
3907
3976
|
],
|
|
3908
|
-
actions: [
|
|
3977
|
+
actions: [
|
|
3978
|
+
{ label: "Register", command: "deepline auth register --no-wait" },
|
|
3979
|
+
{ label: "Wait", command: "deepline auth wait" }
|
|
3980
|
+
]
|
|
3909
3981
|
}
|
|
3910
3982
|
},
|
|
3911
3983
|
{ json: jsonOutput }
|
|
@@ -4538,14 +4610,25 @@ import { writeFileSync as writeFileSync4 } from "fs";
|
|
|
4538
4610
|
import { resolve as resolve4 } from "path";
|
|
4539
4611
|
|
|
4540
4612
|
// ../shared_libs/plays/dataset-summary.ts
|
|
4613
|
+
function formatDatasetRowCountsLine(counts) {
|
|
4614
|
+
const { persisted, succeeded, failed } = counts;
|
|
4615
|
+
if (succeeded === persisted && failed === 0) {
|
|
4616
|
+
return `${persisted} persisted`;
|
|
4617
|
+
}
|
|
4618
|
+
return `${persisted} persisted (${succeeded} succeeded, ${failed} failed)`;
|
|
4619
|
+
}
|
|
4541
4620
|
function datasetSummaryPercentText(numerator, denominator) {
|
|
4542
4621
|
return denominator > 0 ? `${numerator}/${denominator} (${Math.round(100 * numerator / denominator)}%)` : "0/0 (0%)";
|
|
4543
4622
|
}
|
|
4544
4623
|
function readCount(value) {
|
|
4545
4624
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : 0;
|
|
4546
4625
|
}
|
|
4547
|
-
function
|
|
4548
|
-
return
|
|
4626
|
+
function executionAttemptTotal(raw) {
|
|
4627
|
+
return readCount(raw.queued) + readCount(raw.running) + readCount(raw.completed) + readCount(raw.cached) + readCount(raw.skipped) + readCount(raw.missed) + readCount(raw.failed);
|
|
4628
|
+
}
|
|
4629
|
+
function formatDatasetExecutionStats(raw, _persistedRowTotal) {
|
|
4630
|
+
const denominator = executionAttemptTotal(raw);
|
|
4631
|
+
const stats = {
|
|
4549
4632
|
queued: datasetSummaryPercentText(readCount(raw.queued), denominator),
|
|
4550
4633
|
running: datasetSummaryPercentText(readCount(raw.running), denominator),
|
|
4551
4634
|
"completed:executed": datasetSummaryPercentText(
|
|
@@ -4566,6 +4649,17 @@ function formatDatasetExecutionStats(raw, denominator) {
|
|
|
4566
4649
|
),
|
|
4567
4650
|
failed: datasetSummaryPercentText(readCount(raw.failed), denominator)
|
|
4568
4651
|
};
|
|
4652
|
+
if (Object.values(stats).some((text) => {
|
|
4653
|
+
const match = /\((\d+)%\)/.exec(text);
|
|
4654
|
+
return match ? Number(match[1]) > 100 : false;
|
|
4655
|
+
})) {
|
|
4656
|
+
throw new Error(
|
|
4657
|
+
`formatDatasetExecutionStats produced a >100% execution stat; column counts are corrupt: ${JSON.stringify(
|
|
4658
|
+
raw
|
|
4659
|
+
)}`
|
|
4660
|
+
);
|
|
4661
|
+
}
|
|
4662
|
+
return stats;
|
|
4569
4663
|
}
|
|
4570
4664
|
|
|
4571
4665
|
// src/cli/dataset-stats.ts
|
|
@@ -4715,7 +4809,8 @@ function canonicalRowsInfoFromCandidate(input2) {
|
|
|
4715
4809
|
complete: rows2.length === totalRows2,
|
|
4716
4810
|
source: candidate.source,
|
|
4717
4811
|
datasetId: typeof candidate.value.datasetId === "string" ? candidate.value.datasetId : null,
|
|
4718
|
-
tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null
|
|
4812
|
+
tableNamespace: typeof candidate.value.tableNamespace === "string" ? candidate.value.tableNamespace : null,
|
|
4813
|
+
...candidate.value.recovered === true ? { recovered: true } : {}
|
|
4719
4814
|
};
|
|
4720
4815
|
}
|
|
4721
4816
|
if (candidate.serializedOnly) {
|
|
@@ -4865,18 +4960,46 @@ function collectCanonicalRowsInfos(statusOrResult) {
|
|
|
4865
4960
|
}
|
|
4866
4961
|
return infos;
|
|
4867
4962
|
}
|
|
4868
|
-
function
|
|
4963
|
+
function collectPackagedStepDatasetCandidates(statusOrResult) {
|
|
4869
4964
|
const root = isRecord3(statusOrResult) ? statusOrResult : null;
|
|
4870
|
-
|
|
4871
|
-
if (!result) {
|
|
4965
|
+
if (!root) {
|
|
4872
4966
|
return [];
|
|
4873
4967
|
}
|
|
4968
|
+
const pkg = isRecord3(root.package) ? root.package : root;
|
|
4969
|
+
const steps = Array.isArray(pkg.steps) ? pkg.steps : [];
|
|
4874
4970
|
const candidates = [];
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4971
|
+
for (const step of steps) {
|
|
4972
|
+
if (!isRecord3(step) || !isRecord3(step.output)) {
|
|
4973
|
+
continue;
|
|
4974
|
+
}
|
|
4975
|
+
const output2 = step.output;
|
|
4976
|
+
if (!isPackagedDatasetOutput(output2)) {
|
|
4977
|
+
continue;
|
|
4978
|
+
}
|
|
4979
|
+
const source = typeof output2.path === "string" && output2.path.trim() ? output2.path.trim() : typeof step.id === "string" ? step.id : null;
|
|
4980
|
+
if (!source) {
|
|
4981
|
+
continue;
|
|
4982
|
+
}
|
|
4983
|
+
candidates.push({
|
|
4984
|
+
source,
|
|
4985
|
+
value: output2,
|
|
4986
|
+
total: output2.rowCount ?? (isRecord3(output2.preview) ? output2.preview.totalRows : void 0) ?? (isRecord3(step.progress) ? step.progress.total : void 0)
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
return candidates;
|
|
4990
|
+
}
|
|
4991
|
+
function collectSerializedDatasetRowsInfos(statusOrResult) {
|
|
4992
|
+
const root = isRecord3(statusOrResult) ? statusOrResult : null;
|
|
4993
|
+
const result = isRecord3(root?.result) ? root.result : root;
|
|
4994
|
+
const candidates = [];
|
|
4995
|
+
if (result) {
|
|
4996
|
+
collectDatasetCandidates({
|
|
4997
|
+
value: result,
|
|
4998
|
+
path: "result",
|
|
4999
|
+
output: candidates
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
candidates.push(...collectPackagedStepDatasetCandidates(statusOrResult));
|
|
4880
5003
|
const seen = /* @__PURE__ */ new Set();
|
|
4881
5004
|
const infos = [];
|
|
4882
5005
|
for (const candidate of candidates) {
|
|
@@ -5500,7 +5623,7 @@ async function handleDbQuery(args) {
|
|
|
5500
5623
|
return 0;
|
|
5501
5624
|
}
|
|
5502
5625
|
function registerDbCommands(program) {
|
|
5503
|
-
const db = program.command("db").
|
|
5626
|
+
const db = program.command("db").description("Query the tenant customer database.").addHelpText(
|
|
5504
5627
|
"after",
|
|
5505
5628
|
`
|
|
5506
5629
|
Notes:
|
|
@@ -5521,7 +5644,7 @@ Examples:
|
|
|
5521
5644
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
|
5522
5645
|
`
|
|
5523
5646
|
);
|
|
5524
|
-
db.command("query").
|
|
5647
|
+
db.command("query").description("Run SQL against the tenant customer database.").addHelpText(
|
|
5525
5648
|
"after",
|
|
5526
5649
|
`
|
|
5527
5650
|
Notes:
|
|
@@ -5537,7 +5660,7 @@ Examples:
|
|
|
5537
5660
|
deepline db query --sql "select * from companies limit 20"
|
|
5538
5661
|
deepline db query --sql "select domain, name from companies limit 20" --json
|
|
5539
5662
|
deepline db query --sql "create table if not exists storage.agent_notes (id text primary key, note text not null)"
|
|
5540
|
-
deepline db
|
|
5663
|
+
deepline db query --sql "select count(*) from contacts" --json
|
|
5541
5664
|
deepline db query --sql "select * from contacts limit 20" --format csv --out contacts.csv
|
|
5542
5665
|
deepline db query --sql "select domain, name from companies limit 20" --format markdown
|
|
5543
5666
|
`
|
|
@@ -5575,9 +5698,11 @@ import {
|
|
|
5575
5698
|
readFileSync as readFileSync6,
|
|
5576
5699
|
readdirSync,
|
|
5577
5700
|
realpathSync,
|
|
5701
|
+
statSync as statSync2,
|
|
5578
5702
|
writeFileSync as writeFileSync7
|
|
5579
5703
|
} from "fs";
|
|
5580
5704
|
import { basename as basename3, dirname as dirname8, join as join7, resolve as resolve10 } from "path";
|
|
5705
|
+
import { parse as parseCsvSync2 } from "csv-parse/sync";
|
|
5581
5706
|
|
|
5582
5707
|
// src/plays/bundle-play-file.ts
|
|
5583
5708
|
import { tmpdir as tmpdir2 } from "os";
|
|
@@ -9120,7 +9245,7 @@ function traceCliSync(phase, fields, run) {
|
|
|
9120
9245
|
}
|
|
9121
9246
|
}
|
|
9122
9247
|
function sleep5(ms) {
|
|
9123
|
-
return new Promise((
|
|
9248
|
+
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
9124
9249
|
}
|
|
9125
9250
|
function parseReferencedPlayTarget2(target) {
|
|
9126
9251
|
const trimmed = target.trim();
|
|
@@ -9390,6 +9515,177 @@ function inputContainsLocalFilePath(value) {
|
|
|
9390
9515
|
}
|
|
9391
9516
|
return false;
|
|
9392
9517
|
}
|
|
9518
|
+
function isUrlValue(value) {
|
|
9519
|
+
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value.trim());
|
|
9520
|
+
}
|
|
9521
|
+
function looksLikeStagedFileRef(value) {
|
|
9522
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9523
|
+
return false;
|
|
9524
|
+
}
|
|
9525
|
+
const record = value;
|
|
9526
|
+
return typeof record.contentHash === "string" || typeof record.contentBase64 === "string" || typeof record.logicalPath === "string" && typeof record.bytes === "number";
|
|
9527
|
+
}
|
|
9528
|
+
var CSV_DATA_INPUT_KEY = "csv";
|
|
9529
|
+
function collectLocalFileInputRefs(value, inputPath, key, out) {
|
|
9530
|
+
if (typeof value === "string") {
|
|
9531
|
+
const trimmed = value.trim();
|
|
9532
|
+
if (!trimmed || isUrlValue(trimmed)) {
|
|
9533
|
+
return;
|
|
9534
|
+
}
|
|
9535
|
+
const keyIsCsvData = key === CSV_DATA_INPUT_KEY;
|
|
9536
|
+
const endsWithCsv = /\.csv$/i.test(trimmed);
|
|
9537
|
+
const looksLikeFile = /\.[a-z0-9]{1,8}$/i.test(trimmed);
|
|
9538
|
+
if (keyIsCsvData) {
|
|
9539
|
+
out.push({ inputPath, value: trimmed, isCsvData: true });
|
|
9540
|
+
} else if (endsWithCsv || looksLikeFile && existsSync7(resolve10(trimmed))) {
|
|
9541
|
+
out.push({ inputPath, value: trimmed, isCsvData: false });
|
|
9542
|
+
}
|
|
9543
|
+
return;
|
|
9544
|
+
}
|
|
9545
|
+
if (Array.isArray(value)) {
|
|
9546
|
+
value.forEach(
|
|
9547
|
+
(entry, index) => collectLocalFileInputRefs(entry, `${inputPath}[${index}]`, key, out)
|
|
9548
|
+
);
|
|
9549
|
+
return;
|
|
9550
|
+
}
|
|
9551
|
+
if (looksLikeStagedFileRef(value)) {
|
|
9552
|
+
return;
|
|
9553
|
+
}
|
|
9554
|
+
if (value && typeof value === "object") {
|
|
9555
|
+
for (const [childKey, child] of Object.entries(value)) {
|
|
9556
|
+
collectLocalFileInputRefs(
|
|
9557
|
+
child,
|
|
9558
|
+
inputPath ? `${inputPath}.${childKey}` : childKey,
|
|
9559
|
+
childKey,
|
|
9560
|
+
out
|
|
9561
|
+
);
|
|
9562
|
+
}
|
|
9563
|
+
}
|
|
9564
|
+
}
|
|
9565
|
+
function preflightLocalFileInputs(runtimeInput) {
|
|
9566
|
+
if (CSV_DATA_INPUT_KEY in runtimeInput) {
|
|
9567
|
+
const csvValue = runtimeInput[CSV_DATA_INPUT_KEY];
|
|
9568
|
+
if (typeof csvValue === "string" && csvValue.trim().length === 0) {
|
|
9569
|
+
throw new DeeplineError(
|
|
9570
|
+
`Input ${CSV_DATA_INPUT_KEY} is an empty string. Provide a path to a CSV file (or a CSV URL). No run was created.`,
|
|
9571
|
+
void 0,
|
|
9572
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9573
|
+
{ inputPath: CSV_DATA_INPUT_KEY, reason: "csv_empty_string" }
|
|
9574
|
+
);
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
const refs = [];
|
|
9578
|
+
for (const [key, value] of Object.entries(runtimeInput)) {
|
|
9579
|
+
collectLocalFileInputRefs(value, key, key, refs);
|
|
9580
|
+
}
|
|
9581
|
+
for (const ref of refs) {
|
|
9582
|
+
const absolutePath = resolve10(ref.value);
|
|
9583
|
+
if (!existsSync7(absolutePath)) {
|
|
9584
|
+
throw new DeeplineError(
|
|
9585
|
+
`Input ${ref.inputPath} references a local file that does not exist: ${ref.value} (resolved to ${absolutePath}). No run was created.`,
|
|
9586
|
+
void 0,
|
|
9587
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9588
|
+
{ inputPath: ref.inputPath, path: ref.value, resolved: absolutePath }
|
|
9589
|
+
);
|
|
9590
|
+
}
|
|
9591
|
+
let stat4;
|
|
9592
|
+
try {
|
|
9593
|
+
stat4 = statSync2(absolutePath);
|
|
9594
|
+
} catch (error) {
|
|
9595
|
+
throw new DeeplineError(
|
|
9596
|
+
`Input ${ref.inputPath} references a local file that is not readable: ${ref.value} (${error instanceof Error ? error.message : String(error)}). No run was created.`,
|
|
9597
|
+
void 0,
|
|
9598
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9599
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9600
|
+
);
|
|
9601
|
+
}
|
|
9602
|
+
if (!stat4.isFile()) {
|
|
9603
|
+
throw new DeeplineError(
|
|
9604
|
+
`Input ${ref.inputPath} references ${ref.value}, which is not a file. No run was created.`,
|
|
9605
|
+
void 0,
|
|
9606
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9607
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9608
|
+
);
|
|
9609
|
+
}
|
|
9610
|
+
if (!ref.isCsvData) {
|
|
9611
|
+
continue;
|
|
9612
|
+
}
|
|
9613
|
+
preflightCsvDataInput(ref, absolutePath);
|
|
9614
|
+
}
|
|
9615
|
+
}
|
|
9616
|
+
function preflightCsvDataInput(ref, absolutePath) {
|
|
9617
|
+
let content;
|
|
9618
|
+
try {
|
|
9619
|
+
content = readFileSync6(absolutePath, "utf-8");
|
|
9620
|
+
} catch (error) {
|
|
9621
|
+
throw new DeeplineError(
|
|
9622
|
+
`Input ${ref.inputPath} CSV ${ref.value} is not readable: ${error instanceof Error ? error.message : String(error)}. No run was created.`,
|
|
9623
|
+
void 0,
|
|
9624
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9625
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9626
|
+
);
|
|
9627
|
+
}
|
|
9628
|
+
let records;
|
|
9629
|
+
try {
|
|
9630
|
+
records = parseCsvSync2(content, {
|
|
9631
|
+
bom: true,
|
|
9632
|
+
columns: false,
|
|
9633
|
+
skip_empty_lines: true,
|
|
9634
|
+
relax_column_count: true,
|
|
9635
|
+
// STRICT RFC quoting: an unclosed quote or ragged quoting is a hard
|
|
9636
|
+
// error rather than being swallowed into one giant field.
|
|
9637
|
+
relax_quotes: false,
|
|
9638
|
+
trim: true
|
|
9639
|
+
});
|
|
9640
|
+
} catch (error) {
|
|
9641
|
+
throw new DeeplineError(
|
|
9642
|
+
`Input ${ref.inputPath} could not be parsed as CSV (${ref.value}): ${error instanceof Error ? error.message : String(error)}. This usually means an unclosed quote or ragged quoting. No run was created.`,
|
|
9643
|
+
void 0,
|
|
9644
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9645
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9646
|
+
);
|
|
9647
|
+
}
|
|
9648
|
+
if (records.length === 0) {
|
|
9649
|
+
throw new DeeplineError(
|
|
9650
|
+
`${ref.value} has a header row but no data rows. No run was created.`,
|
|
9651
|
+
void 0,
|
|
9652
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9653
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9654
|
+
);
|
|
9655
|
+
}
|
|
9656
|
+
const header = records[0];
|
|
9657
|
+
const seen = /* @__PURE__ */ new Map();
|
|
9658
|
+
for (let i = 0; i < header.length; i++) {
|
|
9659
|
+
const raw = header[i] ?? "";
|
|
9660
|
+
const name = raw.trim();
|
|
9661
|
+
if (name.length === 0) continue;
|
|
9662
|
+
const prior = seen.get(name);
|
|
9663
|
+
if (prior) {
|
|
9664
|
+
throw new DeeplineError(
|
|
9665
|
+
`Input ${ref.inputPath} (${ref.value}) has a duplicate CSV header "${name}" (columns ${prior.index + 1} and ${i + 1}). The second column would silently overwrite the first. Rename one of them. No run was created.`,
|
|
9666
|
+
void 0,
|
|
9667
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9668
|
+
{
|
|
9669
|
+
inputPath: ref.inputPath,
|
|
9670
|
+
path: ref.value,
|
|
9671
|
+
duplicateHeader: name,
|
|
9672
|
+
columns: [prior.index + 1, i + 1],
|
|
9673
|
+
rawSpellings: [prior.raw, raw]
|
|
9674
|
+
}
|
|
9675
|
+
);
|
|
9676
|
+
}
|
|
9677
|
+
seen.set(name, { index: i, raw });
|
|
9678
|
+
}
|
|
9679
|
+
const dataRowCount = records.length - 1;
|
|
9680
|
+
if (dataRowCount < 1) {
|
|
9681
|
+
throw new DeeplineError(
|
|
9682
|
+
`${ref.value} has a header row but no data rows. No run was created.`,
|
|
9683
|
+
void 0,
|
|
9684
|
+
"PLAY_INPUT_FILE_PREFLIGHT",
|
|
9685
|
+
{ inputPath: ref.inputPath, path: ref.value }
|
|
9686
|
+
);
|
|
9687
|
+
}
|
|
9688
|
+
}
|
|
9393
9689
|
function namedRunNeedsPlayDefinition(input2) {
|
|
9394
9690
|
return input2.revisionSelector === "latest" || inputContainsLocalFilePath(input2.runtimeInput);
|
|
9395
9691
|
}
|
|
@@ -9624,6 +9920,21 @@ function isRetryablePendingStartFailure(status) {
|
|
|
9624
9920
|
if (status.runId && status.runId !== "pending") return false;
|
|
9625
9921
|
return isTransientPlayStreamError(new Error(playStatusErrorText(status)));
|
|
9626
9922
|
}
|
|
9923
|
+
function pendingStartFailureStatus(input2) {
|
|
9924
|
+
const message = input2.error instanceof Error ? input2.error.message : String(input2.error);
|
|
9925
|
+
return {
|
|
9926
|
+
runId: "pending",
|
|
9927
|
+
name: input2.playName,
|
|
9928
|
+
playName: input2.playName,
|
|
9929
|
+
dashboardUrl: input2.dashboardUrl,
|
|
9930
|
+
status: "failed",
|
|
9931
|
+
progress: {
|
|
9932
|
+
status: "failed",
|
|
9933
|
+
logs: [],
|
|
9934
|
+
error: message
|
|
9935
|
+
}
|
|
9936
|
+
};
|
|
9937
|
+
}
|
|
9627
9938
|
var TERMINAL_PLAY_STATUSES2 = /* @__PURE__ */ new Set([
|
|
9628
9939
|
"completed",
|
|
9629
9940
|
"failed",
|
|
@@ -10403,6 +10714,23 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10403
10714
|
progress: input2.progress
|
|
10404
10715
|
});
|
|
10405
10716
|
}
|
|
10717
|
+
if (!lastKnownWorkflowId && isTransientPlayStreamError(error)) {
|
|
10718
|
+
recordCliTrace({
|
|
10719
|
+
phase: "cli.play_start_stream_transient_failure",
|
|
10720
|
+
ms: Date.now() - startedAt,
|
|
10721
|
+
ok: false,
|
|
10722
|
+
playName: input2.playName,
|
|
10723
|
+
eventCount,
|
|
10724
|
+
firstRunIdMs,
|
|
10725
|
+
lastPhase,
|
|
10726
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
10727
|
+
});
|
|
10728
|
+
return pendingStartFailureStatus({
|
|
10729
|
+
playName: input2.playName,
|
|
10730
|
+
dashboardUrl,
|
|
10731
|
+
error
|
|
10732
|
+
});
|
|
10733
|
+
}
|
|
10406
10734
|
throw error;
|
|
10407
10735
|
} finally {
|
|
10408
10736
|
if (timeout) {
|
|
@@ -10454,6 +10782,28 @@ async function startAndWaitForPlayCompletionByStreamOnce(input2) {
|
|
|
10454
10782
|
function formatInteger(value) {
|
|
10455
10783
|
return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : String(value ?? "-");
|
|
10456
10784
|
}
|
|
10785
|
+
var RUN_ERROR_DISPLAY_MAX_CHARS = 2e3;
|
|
10786
|
+
function truncateErrorForDisplay(message, runId, maxChars = RUN_ERROR_DISPLAY_MAX_CHARS) {
|
|
10787
|
+
const compact = message.replace(/\s+/g, " ").trim();
|
|
10788
|
+
if (compact.length <= maxChars) {
|
|
10789
|
+
return compact;
|
|
10790
|
+
}
|
|
10791
|
+
const slice = compact.slice(0, maxChars);
|
|
10792
|
+
const lastSpace = slice.lastIndexOf(" ");
|
|
10793
|
+
const wordBoundary = lastSpace > 0 ? slice.slice(0, lastSpace) : slice;
|
|
10794
|
+
const fullErrorHint = runId ? ` (full error: deepline runs get ${runId} --full)` : " (full error: deepline runs get <runId> --full)";
|
|
10795
|
+
return `${wordBoundary}\u2026${fullErrorHint}`;
|
|
10796
|
+
}
|
|
10797
|
+
function clampJsonPreviewText(json, maxChars) {
|
|
10798
|
+
if (json.length <= maxChars) {
|
|
10799
|
+
return json;
|
|
10800
|
+
}
|
|
10801
|
+
const slice = json.slice(0, maxChars);
|
|
10802
|
+
const lastNewline = slice.lastIndexOf("\n");
|
|
10803
|
+
const head = lastNewline > 0 ? slice.slice(0, lastNewline) : slice;
|
|
10804
|
+
return `${head}
|
|
10805
|
+
... truncated; use --json for full output`;
|
|
10806
|
+
}
|
|
10457
10807
|
var BULKY_RETURN_KEYS = /* @__PURE__ */ new Set([
|
|
10458
10808
|
"contract",
|
|
10459
10809
|
"staticPipeline",
|
|
@@ -10538,8 +10888,7 @@ function formatJsonPreview(value) {
|
|
|
10538
10888
|
return [];
|
|
10539
10889
|
}
|
|
10540
10890
|
const MAX_CHARS = 4e3;
|
|
10541
|
-
const truncated = json
|
|
10542
|
-
... truncated; use --json for full output` : json;
|
|
10891
|
+
const truncated = clampJsonPreviewText(json, MAX_CHARS);
|
|
10543
10892
|
return truncated.split("\n").map((line) => ` ${line}`);
|
|
10544
10893
|
}
|
|
10545
10894
|
function formatReturnValue(result) {
|
|
@@ -11070,6 +11419,9 @@ function readRecordArray(value) {
|
|
|
11070
11419
|
function readRecord(value) {
|
|
11071
11420
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
11072
11421
|
}
|
|
11422
|
+
function readNonNegativeInteger(value) {
|
|
11423
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
11424
|
+
}
|
|
11073
11425
|
function formatSummaryScalar(value) {
|
|
11074
11426
|
if (typeof value === "number") {
|
|
11075
11427
|
return Number.isFinite(value) ? formatInteger(Math.trunc(value)) : null;
|
|
@@ -11102,7 +11454,25 @@ function formatPackageDatasetSummaryLines(summary, indent2 = " ") {
|
|
|
11102
11454
|
return [];
|
|
11103
11455
|
}
|
|
11104
11456
|
const lines = [];
|
|
11105
|
-
const
|
|
11457
|
+
const rowCounts = readRecord(record.rowCounts);
|
|
11458
|
+
if (rowCounts) {
|
|
11459
|
+
const persisted = readNonNegativeInteger(rowCounts.persisted);
|
|
11460
|
+
const succeeded = readNonNegativeInteger(rowCounts.succeeded);
|
|
11461
|
+
const failed = readNonNegativeInteger(rowCounts.failed);
|
|
11462
|
+
if (persisted !== null) {
|
|
11463
|
+
lines.push(
|
|
11464
|
+
`${indent2}rows: ${formatDatasetRowCountsLine({
|
|
11465
|
+
persisted,
|
|
11466
|
+
succeeded: succeeded ?? persisted,
|
|
11467
|
+
failed: failed ?? 0
|
|
11468
|
+
})}`
|
|
11469
|
+
);
|
|
11470
|
+
}
|
|
11471
|
+
}
|
|
11472
|
+
const parts = formatSummaryScalarParts(
|
|
11473
|
+
record,
|
|
11474
|
+
/* @__PURE__ */ new Set(["columnStats", "rowCounts"])
|
|
11475
|
+
);
|
|
11106
11476
|
if (parts.length > 0) {
|
|
11107
11477
|
lines.push(`${indent2}summary: ${parts.join(" ")}`);
|
|
11108
11478
|
}
|
|
@@ -11163,7 +11533,7 @@ function buildRunPackageTextLines(packaged) {
|
|
|
11163
11533
|
];
|
|
11164
11534
|
const runError = typeof run.error === "string" && run.error.trim() ? run.error.trim() : null;
|
|
11165
11535
|
if (runError && (status === "failed" || status === "cancelled")) {
|
|
11166
|
-
lines.push(` error: ${runError
|
|
11536
|
+
lines.push(` error: ${truncateErrorForDisplay(runError, runId)}`);
|
|
11167
11537
|
}
|
|
11168
11538
|
for (const step of readRecordArray(packaged.steps)) {
|
|
11169
11539
|
const output2 = step.output && typeof step.output === "object" && !Array.isArray(step.output) ? step.output : null;
|
|
@@ -11248,7 +11618,7 @@ function writePlayResult(status, jsonOutput, options) {
|
|
|
11248
11618
|
lines.push(...buildInsufficientCreditsSummaryLines({ status, billing }));
|
|
11249
11619
|
}
|
|
11250
11620
|
const displayError = formatPlayErrorForDisplay(status, progressError) ?? progressError;
|
|
11251
|
-
lines.push(` error: ${displayError
|
|
11621
|
+
lines.push(` error: ${truncateErrorForDisplay(displayError, runId)}`);
|
|
11252
11622
|
}
|
|
11253
11623
|
const renderedServerView = renderServerResultView(status.resultView);
|
|
11254
11624
|
if (result) {
|
|
@@ -11419,12 +11789,51 @@ async function fetchBackingDatasetRows(input2) {
|
|
|
11419
11789
|
source: `${input2.rowsInfo.source ?? "result.rows"} -> /api/v2/plays/${playName}/sheet?tableNamespace=${tableNamespace}`
|
|
11420
11790
|
};
|
|
11421
11791
|
}
|
|
11792
|
+
function resolveDatasetByName(available, datasetPath) {
|
|
11793
|
+
const target = datasetPath.trim();
|
|
11794
|
+
const exact = available.find((info) => info.source === target);
|
|
11795
|
+
if (exact) {
|
|
11796
|
+
return exact;
|
|
11797
|
+
}
|
|
11798
|
+
const byNamespace = available.find(
|
|
11799
|
+
(info) => info.tableNamespace && info.tableNamespace === target
|
|
11800
|
+
);
|
|
11801
|
+
if (byNamespace) {
|
|
11802
|
+
return byNamespace;
|
|
11803
|
+
}
|
|
11804
|
+
const trailing = target.split(".").filter(Boolean).at(-1);
|
|
11805
|
+
if (!trailing) {
|
|
11806
|
+
return null;
|
|
11807
|
+
}
|
|
11808
|
+
const byTrailing = available.find(
|
|
11809
|
+
(info) => info.tableNamespace === trailing || typeof info.source === "string" && info.source.split(".").filter(Boolean).at(-1) === trailing
|
|
11810
|
+
);
|
|
11811
|
+
if (byTrailing) {
|
|
11812
|
+
return byTrailing;
|
|
11813
|
+
}
|
|
11814
|
+
if (target.split(".").filter(Boolean)[0] === "result") {
|
|
11815
|
+
const recovered = available.filter((info) => info.recovered);
|
|
11816
|
+
if (recovered.length === 1) {
|
|
11817
|
+
return recovered[0];
|
|
11818
|
+
}
|
|
11819
|
+
if (recovered.length > 1) {
|
|
11820
|
+
const names = recovered.map((info) => info.tableNamespace ?? info.source).filter((name) => typeof name === "string");
|
|
11821
|
+
throw new DeeplineError(
|
|
11822
|
+
`Run returned multiple recovered datasets; '${target}' is ambiguous. Choose one with --dataset <path>: ${names.join(", ")}.`,
|
|
11823
|
+
void 0,
|
|
11824
|
+
"RUN_EXPORT_DATASET_AMBIGUOUS",
|
|
11825
|
+
{ dataset: target, available: names }
|
|
11826
|
+
);
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
return null;
|
|
11830
|
+
}
|
|
11422
11831
|
async function exportPlayStatusRows(client2, status, outPath, options = {}) {
|
|
11423
11832
|
if (!outPath) {
|
|
11424
11833
|
return null;
|
|
11425
11834
|
}
|
|
11426
11835
|
const availableRows = collectSerializedDatasetRowsInfos(status);
|
|
11427
|
-
const rowsInfo = options.datasetPath ? availableRows
|
|
11836
|
+
const rowsInfo = options.datasetPath ? resolveDatasetByName(availableRows, options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
|
|
11428
11837
|
if (!rowsInfo && options.datasetPath) {
|
|
11429
11838
|
const available = availableRows.map((info) => info.source).filter((source) => typeof source === "string");
|
|
11430
11839
|
throw new DeeplineError(
|
|
@@ -11904,7 +12313,7 @@ function parsePlayRunOptions(args) {
|
|
|
11904
12313
|
function parsePlayCheckOptions(args) {
|
|
11905
12314
|
const target = args[0];
|
|
11906
12315
|
if (!target) {
|
|
11907
|
-
throw new Error("Usage: deepline
|
|
12316
|
+
throw new Error("Usage: deepline plays check <play-file.ts> [--json]");
|
|
11908
12317
|
}
|
|
11909
12318
|
const jsonOutput = argsWantJson(args);
|
|
11910
12319
|
return { target, jsonOutput };
|
|
@@ -12170,6 +12579,12 @@ async function handleFileBackedRun(options) {
|
|
|
12170
12579
|
() => readFileSync6(absolutePlayPath, "utf-8")
|
|
12171
12580
|
);
|
|
12172
12581
|
const runtimeInput = options.input ? { ...options.input } : {};
|
|
12582
|
+
try {
|
|
12583
|
+
preflightLocalFileInputs(runtimeInput);
|
|
12584
|
+
} catch (error) {
|
|
12585
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
12586
|
+
return 1;
|
|
12587
|
+
}
|
|
12173
12588
|
let graph;
|
|
12174
12589
|
try {
|
|
12175
12590
|
graph = await traceCliSpan(
|
|
@@ -12327,6 +12742,12 @@ async function handleNamedRun(options) {
|
|
|
12327
12742
|
const progress = getActiveCliProgress() ?? createCliProgress(!options.jsonOutput);
|
|
12328
12743
|
const playName = options.target.name;
|
|
12329
12744
|
const runtimeInput = options.input ? { ...options.input } : {};
|
|
12745
|
+
try {
|
|
12746
|
+
preflightLocalFileInputs(runtimeInput);
|
|
12747
|
+
} catch (error) {
|
|
12748
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
12749
|
+
return 1;
|
|
12750
|
+
}
|
|
12330
12751
|
const needsPlayDefinition = namedRunNeedsPlayDefinition({
|
|
12331
12752
|
runtimeInput,
|
|
12332
12753
|
revisionSelector: options.revisionSelector
|
|
@@ -12822,7 +13243,9 @@ async function handleRunExport(args) {
|
|
|
12822
13243
|
async function handlePlayGet(args) {
|
|
12823
13244
|
const target = args[0];
|
|
12824
13245
|
if (!target) {
|
|
12825
|
-
console.error(
|
|
13246
|
+
console.error(
|
|
13247
|
+
"Usage: deepline plays get <play-file.ts|play-name> [--json]"
|
|
13248
|
+
);
|
|
12826
13249
|
return 1;
|
|
12827
13250
|
}
|
|
12828
13251
|
if (looksLikeRunId(target)) {
|
|
@@ -12920,7 +13343,7 @@ async function handlePlayVersions(args) {
|
|
|
12920
13343
|
const nameIndex = args.indexOf("--name");
|
|
12921
13344
|
const playName = nameIndex >= 0 ? args[nameIndex + 1] : void 0;
|
|
12922
13345
|
if (!playName) {
|
|
12923
|
-
console.error("Usage: deepline
|
|
13346
|
+
console.error("Usage: deepline plays versions --name <name> [--json]");
|
|
12924
13347
|
return 1;
|
|
12925
13348
|
}
|
|
12926
13349
|
const client2 = new DeeplineClient();
|
|
@@ -13300,7 +13723,7 @@ async function handlePlayPublish(args) {
|
|
|
13300
13723
|
const playName = args[0];
|
|
13301
13724
|
if (!playName) {
|
|
13302
13725
|
console.error(
|
|
13303
|
-
"Usage: deepline
|
|
13726
|
+
"Usage: deepline plays publish <play-file.ts|play-name> [--latest|--revision-id <id>] [--json]"
|
|
13304
13727
|
);
|
|
13305
13728
|
return 1;
|
|
13306
13729
|
}
|
|
@@ -13444,7 +13867,7 @@ async function handlePlayDelete(args) {
|
|
|
13444
13867
|
return result.deleted ? 0 : 1;
|
|
13445
13868
|
}
|
|
13446
13869
|
function registerPlayCommands(program) {
|
|
13447
|
-
const play = program.command("plays").
|
|
13870
|
+
const play = program.command("plays").description("Search, validate, run, and manage cloud plays.").addHelpText(
|
|
13448
13871
|
"after",
|
|
13449
13872
|
`
|
|
13450
13873
|
Concepts:
|
|
@@ -15951,7 +16374,7 @@ async function runGeneratedEnrichPlay(runArgs, options = {}) {
|
|
|
15951
16374
|
});
|
|
15952
16375
|
} catch (error) {
|
|
15953
16376
|
if (attempt === 0 && isPlayStartStreamEndedError(error)) {
|
|
15954
|
-
await new Promise((
|
|
16377
|
+
await new Promise((resolve16) => setTimeout(resolve16, 250));
|
|
15955
16378
|
continue;
|
|
15956
16379
|
}
|
|
15957
16380
|
throw error;
|
|
@@ -16751,19 +17174,16 @@ Notes:
|
|
|
16751
17174
|
Use --command and --payload to attach a reproducible command shape.
|
|
16752
17175
|
|
|
16753
17176
|
Examples:
|
|
16754
|
-
deepline feedback "plays run failed after upload" --command "deepline plays run my.play.ts --watch"
|
|
16755
|
-
deepline feedback "unexpected billing output" --payload '{"command":"billing usage"}' --json
|
|
17177
|
+
deepline feedback send "plays run failed after upload" --command "deepline plays run my.play.ts --watch"
|
|
17178
|
+
deepline feedback send "unexpected billing output" --payload '{"command":"billing usage"}' --json
|
|
16756
17179
|
`
|
|
16757
17180
|
);
|
|
16758
|
-
feedback.
|
|
16759
|
-
program.command("provide-feedback").description("Legacy alias for `deepline feedback`.").addHelpText(
|
|
17181
|
+
feedback.command("send").description("Send CLI feedback to Deepline.").addHelpText(
|
|
16760
17182
|
"after",
|
|
16761
17183
|
`
|
|
16762
|
-
Notes:
|
|
16763
|
-
Compatibility alias. Prefer deepline feedback in new scripts and docs.
|
|
16764
|
-
|
|
16765
17184
|
Examples:
|
|
16766
|
-
deepline feedback "tools search returned stale results" --json
|
|
17185
|
+
deepline feedback send "tools search returned stale results" --json
|
|
17186
|
+
deepline feedback send "plays run failed after upload" --command "deepline plays run my.play.ts --watch"
|
|
16767
17187
|
`
|
|
16768
17188
|
).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);
|
|
16769
17189
|
}
|
|
@@ -16884,8 +17304,44 @@ async function handleOrgSwitch(selection, options) {
|
|
|
16884
17304
|
{ json: options.json }
|
|
16885
17305
|
);
|
|
16886
17306
|
}
|
|
17307
|
+
async function handleOrgCreate(name, options) {
|
|
17308
|
+
const config = resolveConfig();
|
|
17309
|
+
const http = new HttpClient(config);
|
|
17310
|
+
const created = await http.post("/api/v2/auth/cli/org-create", {
|
|
17311
|
+
api_key: config.apiKey,
|
|
17312
|
+
name
|
|
17313
|
+
});
|
|
17314
|
+
saveHostEnvValues(config.baseUrl, {
|
|
17315
|
+
DEEPLINE_API_KEY: created.api_key,
|
|
17316
|
+
DEEPLINE_ACTIVE_ORG_ID: created.org_id,
|
|
17317
|
+
DEEPLINE_ACTIVE_ORG_NAME: created.org_name
|
|
17318
|
+
});
|
|
17319
|
+
const { api_key: _apiKey, ...publicCreated } = created;
|
|
17320
|
+
printCommandEnvelope(
|
|
17321
|
+
{
|
|
17322
|
+
ok: true,
|
|
17323
|
+
...publicCreated,
|
|
17324
|
+
api_key_saved: true,
|
|
17325
|
+
switched: true,
|
|
17326
|
+
host_env_path: hostEnvFilePath(config.baseUrl),
|
|
17327
|
+
render: {
|
|
17328
|
+
sections: [
|
|
17329
|
+
{
|
|
17330
|
+
title: "org create",
|
|
17331
|
+
lines: [
|
|
17332
|
+
`Created organization: ${created.org_name}.`,
|
|
17333
|
+
`Switched to ${created.org_name}.`,
|
|
17334
|
+
`Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
|
|
17335
|
+
]
|
|
17336
|
+
}
|
|
17337
|
+
]
|
|
17338
|
+
}
|
|
17339
|
+
},
|
|
17340
|
+
{ json: options.json }
|
|
17341
|
+
);
|
|
17342
|
+
}
|
|
16887
17343
|
function registerOrgCommands(program) {
|
|
16888
|
-
const org = program.command("org").description("List and switch organizations.").addHelpText(
|
|
17344
|
+
const org = program.command("org").description("List, create, and switch organizations.").addHelpText(
|
|
16889
17345
|
"after",
|
|
16890
17346
|
`
|
|
16891
17347
|
Notes:
|
|
@@ -16894,6 +17350,7 @@ Notes:
|
|
|
16894
17350
|
|
|
16895
17351
|
Examples:
|
|
16896
17352
|
deepline org list --json
|
|
17353
|
+
deepline org create Acme --json
|
|
16897
17354
|
deepline org switch 2
|
|
16898
17355
|
deepline org switch --org-id org_123 --json
|
|
16899
17356
|
`
|
|
@@ -16909,6 +17366,19 @@ Examples:
|
|
|
16909
17366
|
deepline org list --json
|
|
16910
17367
|
`
|
|
16911
17368
|
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
|
|
17369
|
+
org.command("create <name>").description("Create a new organization and switch this CLI to it.").addHelpText(
|
|
17370
|
+
"after",
|
|
17371
|
+
`
|
|
17372
|
+
Notes:
|
|
17373
|
+
Mutates workspace state. The new organization is created for the current
|
|
17374
|
+
authenticated user, then the returned API key is saved for this host so later
|
|
17375
|
+
CLI commands target the new organization.
|
|
17376
|
+
|
|
17377
|
+
Examples:
|
|
17378
|
+
deepline org create Acme
|
|
17379
|
+
deepline org create "Acme Sales" --json
|
|
17380
|
+
`
|
|
17381
|
+
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgCreate);
|
|
16912
17382
|
org.command("switch [selection]").description(
|
|
16913
17383
|
"Switch to another organization and save the new API key in the host auth file."
|
|
16914
17384
|
).addHelpText(
|
|
@@ -16953,7 +17423,7 @@ async function readHiddenLine(prompt) {
|
|
|
16953
17423
|
if (typeof input.setRawMode === "function") input.setRawMode(true);
|
|
16954
17424
|
let value = "";
|
|
16955
17425
|
input.resume();
|
|
16956
|
-
return await new Promise((
|
|
17426
|
+
return await new Promise((resolve16, reject) => {
|
|
16957
17427
|
let settled = false;
|
|
16958
17428
|
const cleanup = () => {
|
|
16959
17429
|
input.off("data", onData);
|
|
@@ -16968,7 +17438,7 @@ async function readHiddenLine(prompt) {
|
|
|
16968
17438
|
settled = true;
|
|
16969
17439
|
output.write("\n");
|
|
16970
17440
|
cleanup();
|
|
16971
|
-
|
|
17441
|
+
resolve16(line);
|
|
16972
17442
|
};
|
|
16973
17443
|
const fail = (error) => {
|
|
16974
17444
|
if (settled) return;
|
|
@@ -17138,22 +17608,566 @@ Examples:
|
|
|
17138
17608
|
);
|
|
17139
17609
|
}
|
|
17140
17610
|
|
|
17611
|
+
// src/cli/commands/sessions.ts
|
|
17612
|
+
import {
|
|
17613
|
+
existsSync as existsSync8,
|
|
17614
|
+
mkdirSync as mkdirSync4,
|
|
17615
|
+
readdirSync as readdirSync2,
|
|
17616
|
+
readFileSync as readFileSync7,
|
|
17617
|
+
statSync as statSync3,
|
|
17618
|
+
writeFileSync as writeFileSync8
|
|
17619
|
+
} from "fs";
|
|
17620
|
+
import { homedir as homedir5, platform } from "os";
|
|
17621
|
+
import { basename as basename4, dirname as dirname9, join as join9, resolve as resolve12 } from "path";
|
|
17622
|
+
import { gzipSync } from "zlib";
|
|
17623
|
+
import { randomUUID } from "crypto";
|
|
17624
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
17625
|
+
var MAX_SESSION_UPLOAD_BYTES = 35e5;
|
|
17626
|
+
var MAX_DIRECT_SESSION_DECODED_BYTES = 50 * 1024 * 1024;
|
|
17627
|
+
var CHUNK_SIZE_BYTES = 25e5;
|
|
17628
|
+
var MAX_EVENT_STRING_CHARS = 8e3;
|
|
17629
|
+
var MAX_EVENT_LIST_ITEMS = 40;
|
|
17630
|
+
var MAX_EVENT_OBJECT_KEYS = 80;
|
|
17631
|
+
var TRUNCATION_MARKER = "...[truncated]";
|
|
17632
|
+
var NOISE_EVENT_TYPES = /* @__PURE__ */ new Set(["progress", "file-history-snapshot"]);
|
|
17633
|
+
function homeDir() {
|
|
17634
|
+
return process.env.HOME?.trim() || homedir5();
|
|
17635
|
+
}
|
|
17636
|
+
function detectShellContext() {
|
|
17637
|
+
const shellPath = process.env.SHELL?.trim() || process.env.ComSpec?.trim() || process.env.COMSPEC?.trim() || "";
|
|
17638
|
+
return {
|
|
17639
|
+
shell: shellPath ? basename4(shellPath).replace(/\.exe$/i, "") : "unknown",
|
|
17640
|
+
shell_path: shellPath || null,
|
|
17641
|
+
os: platform(),
|
|
17642
|
+
cwd: process.cwd()
|
|
17643
|
+
};
|
|
17644
|
+
}
|
|
17645
|
+
function claudeProjectsRoot() {
|
|
17646
|
+
return join9(homeDir(), ".claude", "projects");
|
|
17647
|
+
}
|
|
17648
|
+
function listClaudeSessionFiles() {
|
|
17649
|
+
const root = claudeProjectsRoot();
|
|
17650
|
+
if (!existsSync8(root)) return [];
|
|
17651
|
+
const projectDirs = readDirectoryNames(root);
|
|
17652
|
+
const files = [];
|
|
17653
|
+
for (const projectDir of projectDirs) {
|
|
17654
|
+
const fullProjectDir = join9(root, projectDir);
|
|
17655
|
+
for (const fileName of readDirectoryNames(fullProjectDir)) {
|
|
17656
|
+
if (fileName.endsWith(".jsonl")) {
|
|
17657
|
+
files.push(join9(fullProjectDir, fileName));
|
|
17658
|
+
}
|
|
17659
|
+
}
|
|
17660
|
+
}
|
|
17661
|
+
return files;
|
|
17662
|
+
}
|
|
17663
|
+
function readDirectoryNames(dir) {
|
|
17664
|
+
try {
|
|
17665
|
+
return readdirSync2(dir);
|
|
17666
|
+
} catch {
|
|
17667
|
+
return [];
|
|
17668
|
+
}
|
|
17669
|
+
}
|
|
17670
|
+
function newestClaudeSessionFile() {
|
|
17671
|
+
let newest = null;
|
|
17672
|
+
for (const filePath of listClaudeSessionFiles()) {
|
|
17673
|
+
try {
|
|
17674
|
+
const stat4 = statSync3(filePath);
|
|
17675
|
+
if (!newest || stat4.mtimeMs > newest.mtimeMs) {
|
|
17676
|
+
newest = { filePath, mtimeMs: stat4.mtimeMs };
|
|
17677
|
+
}
|
|
17678
|
+
} catch {
|
|
17679
|
+
continue;
|
|
17680
|
+
}
|
|
17681
|
+
}
|
|
17682
|
+
return newest?.filePath ?? null;
|
|
17683
|
+
}
|
|
17684
|
+
function sessionIdFromFilePath(filePath) {
|
|
17685
|
+
return basename4(filePath, ".jsonl");
|
|
17686
|
+
}
|
|
17687
|
+
function findSessionFile(sessionId) {
|
|
17688
|
+
if (!UUID_RE.test(sessionId)) {
|
|
17689
|
+
throw new Error(
|
|
17690
|
+
"Invalid session ID format. Expected a UUID such as 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca."
|
|
17691
|
+
);
|
|
17692
|
+
}
|
|
17693
|
+
for (const filePath of listClaudeSessionFiles()) {
|
|
17694
|
+
if (sessionIdFromFilePath(filePath) === sessionId) {
|
|
17695
|
+
return filePath;
|
|
17696
|
+
}
|
|
17697
|
+
}
|
|
17698
|
+
return null;
|
|
17699
|
+
}
|
|
17700
|
+
function resolveSessionTargets(input2) {
|
|
17701
|
+
const targets = [];
|
|
17702
|
+
if (input2.currentSession) {
|
|
17703
|
+
const currentFile = newestClaudeSessionFile();
|
|
17704
|
+
if (!currentFile) {
|
|
17705
|
+
throw new Error("No session files found in ~/.claude/projects/*/.");
|
|
17706
|
+
}
|
|
17707
|
+
const sessionId = sessionIdFromFilePath(currentFile);
|
|
17708
|
+
targets.push({
|
|
17709
|
+
sessionId,
|
|
17710
|
+
label: `session-${sessionId}`,
|
|
17711
|
+
filePath: currentFile
|
|
17712
|
+
});
|
|
17713
|
+
}
|
|
17714
|
+
for (const [index, sessionId] of (input2.sessionIds ?? []).entries()) {
|
|
17715
|
+
const filePath = findSessionFile(sessionId);
|
|
17716
|
+
if (!filePath) {
|
|
17717
|
+
throw new Error(
|
|
17718
|
+
`Session file not found: ~/.claude/projects/*/${sessionId}.jsonl`
|
|
17719
|
+
);
|
|
17720
|
+
}
|
|
17721
|
+
targets.push({
|
|
17722
|
+
sessionId,
|
|
17723
|
+
label: input2.labels?.[index] ?? `session-${sessionId}`,
|
|
17724
|
+
filePath
|
|
17725
|
+
});
|
|
17726
|
+
}
|
|
17727
|
+
if (targets.length === 0) {
|
|
17728
|
+
throw new Error("One of --session-id or --current-session is required.");
|
|
17729
|
+
}
|
|
17730
|
+
return targets;
|
|
17731
|
+
}
|
|
17732
|
+
function parseJsonLine(line) {
|
|
17733
|
+
try {
|
|
17734
|
+
return JSON.parse(line);
|
|
17735
|
+
} catch {
|
|
17736
|
+
return null;
|
|
17737
|
+
}
|
|
17738
|
+
}
|
|
17739
|
+
function normalizedJsonLines(raw) {
|
|
17740
|
+
return raw.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
17741
|
+
}
|
|
17742
|
+
function stripNoiseEvents(raw) {
|
|
17743
|
+
const lines = [];
|
|
17744
|
+
for (const line of normalizedJsonLines(raw)) {
|
|
17745
|
+
const parsed = parseJsonLine(line);
|
|
17746
|
+
if (parsed && typeof parsed === "object" && NOISE_EVENT_TYPES.has(String(parsed.type ?? ""))) {
|
|
17747
|
+
continue;
|
|
17748
|
+
}
|
|
17749
|
+
lines.push(line);
|
|
17750
|
+
}
|
|
17751
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
17752
|
+
` : "", "utf8");
|
|
17753
|
+
}
|
|
17754
|
+
function messageContentKey(value) {
|
|
17755
|
+
const message = value.message;
|
|
17756
|
+
if (!message || typeof message !== "object") return null;
|
|
17757
|
+
const content = message.content;
|
|
17758
|
+
if (typeof content === "string") return content;
|
|
17759
|
+
if (!Array.isArray(content)) return null;
|
|
17760
|
+
return content.map((block) => {
|
|
17761
|
+
if (!block || typeof block !== "object") return String(block);
|
|
17762
|
+
const record = block;
|
|
17763
|
+
const type = String(record.type ?? "");
|
|
17764
|
+
if (type === "tool_use") {
|
|
17765
|
+
return `tool_use:${String(record.name ?? "")}:${String(record.id ?? "")}`;
|
|
17766
|
+
}
|
|
17767
|
+
if (type === "tool_result") {
|
|
17768
|
+
return `tool_result:${String(record.tool_use_id ?? "")}`;
|
|
17769
|
+
}
|
|
17770
|
+
return String(record.text ?? type);
|
|
17771
|
+
}).join("\n");
|
|
17772
|
+
}
|
|
17773
|
+
function dedupConsecutiveEvents(raw) {
|
|
17774
|
+
const rawLines = normalizedJsonLines(raw);
|
|
17775
|
+
const parsedEvents = rawLines.map(parseJsonLine);
|
|
17776
|
+
const output2 = [];
|
|
17777
|
+
let index = 0;
|
|
17778
|
+
while (index < parsedEvents.length) {
|
|
17779
|
+
const event = parsedEvents[index];
|
|
17780
|
+
if (!event || typeof event !== "object") {
|
|
17781
|
+
output2.push(rawLines[index] ?? "");
|
|
17782
|
+
index += 1;
|
|
17783
|
+
continue;
|
|
17784
|
+
}
|
|
17785
|
+
const record = event;
|
|
17786
|
+
const eventType = String(record.type ?? "");
|
|
17787
|
+
const eventKey = messageContentKey(record);
|
|
17788
|
+
if (!["user", "assistant"].includes(eventType) || !eventKey) {
|
|
17789
|
+
output2.push(rawLines[index] ?? "");
|
|
17790
|
+
index += 1;
|
|
17791
|
+
continue;
|
|
17792
|
+
}
|
|
17793
|
+
let runCount = 1;
|
|
17794
|
+
let cursor = index + 1;
|
|
17795
|
+
while (cursor < parsedEvents.length) {
|
|
17796
|
+
const next = parsedEvents[cursor];
|
|
17797
|
+
if (!next || typeof next !== "object") break;
|
|
17798
|
+
const nextRecord = next;
|
|
17799
|
+
if (String(nextRecord.type ?? "") !== eventType || messageContentKey(nextRecord) !== eventKey) {
|
|
17800
|
+
break;
|
|
17801
|
+
}
|
|
17802
|
+
runCount += 1;
|
|
17803
|
+
cursor += 1;
|
|
17804
|
+
}
|
|
17805
|
+
if (runCount > 1) {
|
|
17806
|
+
record._repeat_count = runCount;
|
|
17807
|
+
record._repeat_summary = `${runCount} consecutive identical ${eventType} messages collapsed`;
|
|
17808
|
+
output2.push(JSON.stringify(record));
|
|
17809
|
+
index = cursor;
|
|
17810
|
+
continue;
|
|
17811
|
+
}
|
|
17812
|
+
output2.push(rawLines[index] ?? "");
|
|
17813
|
+
index += 1;
|
|
17814
|
+
}
|
|
17815
|
+
return Buffer.from(output2.length > 0 ? `${output2.join("\n")}
|
|
17816
|
+
` : "", "utf8");
|
|
17817
|
+
}
|
|
17818
|
+
function compactEventValue(value) {
|
|
17819
|
+
if (typeof value === "string") {
|
|
17820
|
+
if (value.length <= MAX_EVENT_STRING_CHARS) return value;
|
|
17821
|
+
return `${value.slice(0, MAX_EVENT_STRING_CHARS - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`;
|
|
17822
|
+
}
|
|
17823
|
+
if (Array.isArray(value)) {
|
|
17824
|
+
const compacted = value.slice(0, MAX_EVENT_LIST_ITEMS).map(compactEventValue);
|
|
17825
|
+
if (value.length > MAX_EVENT_LIST_ITEMS) {
|
|
17826
|
+
compacted.push(
|
|
17827
|
+
`${TRUNCATION_MARKER} ${value.length - MAX_EVENT_LIST_ITEMS} more item(s)`
|
|
17828
|
+
);
|
|
17829
|
+
}
|
|
17830
|
+
return compacted;
|
|
17831
|
+
}
|
|
17832
|
+
if (value && typeof value === "object") {
|
|
17833
|
+
const entries = Object.entries(value);
|
|
17834
|
+
const compacted = {};
|
|
17835
|
+
for (const [key, item] of entries.slice(0, MAX_EVENT_OBJECT_KEYS)) {
|
|
17836
|
+
compacted[key] = compactEventValue(item);
|
|
17837
|
+
}
|
|
17838
|
+
if (entries.length > MAX_EVENT_OBJECT_KEYS) {
|
|
17839
|
+
compacted._truncated_keys = entries.length - MAX_EVENT_OBJECT_KEYS;
|
|
17840
|
+
}
|
|
17841
|
+
return compacted;
|
|
17842
|
+
}
|
|
17843
|
+
return value;
|
|
17844
|
+
}
|
|
17845
|
+
function selectiveCompactToolResults(raw) {
|
|
17846
|
+
const lines = [];
|
|
17847
|
+
for (const line of normalizedJsonLines(raw)) {
|
|
17848
|
+
const parsed = parseJsonLine(line);
|
|
17849
|
+
if (!parsed || typeof parsed !== "object") {
|
|
17850
|
+
lines.push(line);
|
|
17851
|
+
continue;
|
|
17852
|
+
}
|
|
17853
|
+
const record = parsed;
|
|
17854
|
+
if (record.type === "user") {
|
|
17855
|
+
const message = record.message;
|
|
17856
|
+
const content = message && typeof message === "object" ? message.content : null;
|
|
17857
|
+
if (Array.isArray(content)) {
|
|
17858
|
+
message.content = content.map(
|
|
17859
|
+
(block) => block && typeof block === "object" && block.type === "tool_result" ? compactEventValue(block) : block
|
|
17860
|
+
);
|
|
17861
|
+
}
|
|
17862
|
+
}
|
|
17863
|
+
lines.push(JSON.stringify(record));
|
|
17864
|
+
}
|
|
17865
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
17866
|
+
` : "", "utf8");
|
|
17867
|
+
}
|
|
17868
|
+
function prepareSessionBuffer(raw) {
|
|
17869
|
+
return selectiveCompactToolResults(
|
|
17870
|
+
dedupConsecutiveEvents(stripNoiseEvents(raw))
|
|
17871
|
+
);
|
|
17872
|
+
}
|
|
17873
|
+
function buildSessionUploadContent(raw) {
|
|
17874
|
+
const prepared = prepareSessionBuffer(raw);
|
|
17875
|
+
const encoded = gzipSync(prepared).toString("base64");
|
|
17876
|
+
if (encoded.length <= MAX_SESSION_UPLOAD_BYTES && prepared.length <= MAX_DIRECT_SESSION_DECODED_BYTES) {
|
|
17877
|
+
return { encodedContent: encoded, needsChunking: false };
|
|
17878
|
+
}
|
|
17879
|
+
return { encodedContent: encoded, needsChunking: true };
|
|
17880
|
+
}
|
|
17881
|
+
async function uploadPayload(path, payload) {
|
|
17882
|
+
const { http } = getAuthedHttpClient();
|
|
17883
|
+
return await http.post(path, payload);
|
|
17884
|
+
}
|
|
17885
|
+
async function uploadChunkedSessions(sessions, options) {
|
|
17886
|
+
const uploadId = randomUUID();
|
|
17887
|
+
for (const session of sessions) {
|
|
17888
|
+
const bytes = Buffer.from(session.encodedContent, "base64");
|
|
17889
|
+
const chunks = [];
|
|
17890
|
+
for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE_BYTES) {
|
|
17891
|
+
chunks.push(bytes.subarray(offset, offset + CHUNK_SIZE_BYTES));
|
|
17892
|
+
}
|
|
17893
|
+
process.stderr.write(
|
|
17894
|
+
`Uploading ${session.label} in ${chunks.length} chunk(s)...
|
|
17895
|
+
`
|
|
17896
|
+
);
|
|
17897
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
17898
|
+
await uploadPayload("/api/v2/cli/send-session/chunk", {
|
|
17899
|
+
upload_id: uploadId,
|
|
17900
|
+
session_id: session.sessionId,
|
|
17901
|
+
index,
|
|
17902
|
+
total_chunks: chunks.length,
|
|
17903
|
+
data: chunk.toString("base64")
|
|
17904
|
+
});
|
|
17905
|
+
}
|
|
17906
|
+
}
|
|
17907
|
+
const response = await uploadPayload("/api/v2/cli/send-session/finalize", {
|
|
17908
|
+
upload_id: uploadId,
|
|
17909
|
+
session_ids: sessions.map((session) => session.sessionId),
|
|
17910
|
+
labels: sessions.map((session) => session.label),
|
|
17911
|
+
environments: sessions.map(() => detectShellContext())
|
|
17912
|
+
});
|
|
17913
|
+
printCommandEnvelope(
|
|
17914
|
+
{
|
|
17915
|
+
...response,
|
|
17916
|
+
ok: true,
|
|
17917
|
+
uploaded: sessions.length,
|
|
17918
|
+
render: {
|
|
17919
|
+
sections: [
|
|
17920
|
+
{
|
|
17921
|
+
title: "sessions send",
|
|
17922
|
+
lines: ["Session uploaded to #internal-reports (chunked)."]
|
|
17923
|
+
}
|
|
17924
|
+
]
|
|
17925
|
+
}
|
|
17926
|
+
},
|
|
17927
|
+
{ json: options.json }
|
|
17928
|
+
);
|
|
17929
|
+
}
|
|
17930
|
+
async function handleSessionsSend(options) {
|
|
17931
|
+
if (options.file) {
|
|
17932
|
+
const filePath = resolve12(options.file);
|
|
17933
|
+
if (!existsSync8(filePath)) {
|
|
17934
|
+
throw new Error(`File not found: ${options.file}`);
|
|
17935
|
+
}
|
|
17936
|
+
const response2 = await uploadPayload("/api/v2/cli/send-session", {
|
|
17937
|
+
file: readFileSync7(filePath).toString("base64"),
|
|
17938
|
+
filename: basename4(filePath)
|
|
17939
|
+
});
|
|
17940
|
+
printCommandEnvelope(
|
|
17941
|
+
{
|
|
17942
|
+
...response2,
|
|
17943
|
+
ok: true,
|
|
17944
|
+
filename: basename4(filePath),
|
|
17945
|
+
render: {
|
|
17946
|
+
sections: [
|
|
17947
|
+
{
|
|
17948
|
+
title: "sessions send",
|
|
17949
|
+
lines: [
|
|
17950
|
+
`File '${basename4(filePath)}' uploaded to #internal-reports.`
|
|
17951
|
+
]
|
|
17952
|
+
}
|
|
17953
|
+
]
|
|
17954
|
+
}
|
|
17955
|
+
},
|
|
17956
|
+
{ json: options.json }
|
|
17957
|
+
);
|
|
17958
|
+
return;
|
|
17959
|
+
}
|
|
17960
|
+
const targets = resolveSessionTargets({
|
|
17961
|
+
sessionIds: options.sessionId,
|
|
17962
|
+
labels: options.label,
|
|
17963
|
+
currentSession: options.currentSession
|
|
17964
|
+
});
|
|
17965
|
+
const built = targets.map((target) => {
|
|
17966
|
+
const upload = buildSessionUploadContent(readFileSync7(target.filePath));
|
|
17967
|
+
return { ...target, ...upload };
|
|
17968
|
+
});
|
|
17969
|
+
if (built.some((session) => session.needsChunking)) {
|
|
17970
|
+
await uploadChunkedSessions(built, options);
|
|
17971
|
+
return;
|
|
17972
|
+
}
|
|
17973
|
+
const response = built.length === 1 && !options.label?.length ? await uploadPayload("/api/v2/cli/send-session", {
|
|
17974
|
+
session_id: built[0]?.sessionId,
|
|
17975
|
+
content: built[0]?.encodedContent,
|
|
17976
|
+
environment: detectShellContext()
|
|
17977
|
+
}) : await uploadPayload("/api/v2/cli/send-session", {
|
|
17978
|
+
sessions: built.map((session) => ({
|
|
17979
|
+
session_id: session.sessionId,
|
|
17980
|
+
content: session.encodedContent,
|
|
17981
|
+
label: session.label,
|
|
17982
|
+
environment: detectShellContext()
|
|
17983
|
+
})),
|
|
17984
|
+
environment: detectShellContext()
|
|
17985
|
+
});
|
|
17986
|
+
printCommandEnvelope(
|
|
17987
|
+
{
|
|
17988
|
+
...response,
|
|
17989
|
+
ok: true,
|
|
17990
|
+
uploaded: built.length,
|
|
17991
|
+
render: {
|
|
17992
|
+
sections: [
|
|
17993
|
+
{
|
|
17994
|
+
title: "sessions send",
|
|
17995
|
+
lines: ["Session uploaded to #internal-reports."]
|
|
17996
|
+
}
|
|
17997
|
+
]
|
|
17998
|
+
}
|
|
17999
|
+
},
|
|
18000
|
+
{ json: options.json }
|
|
18001
|
+
);
|
|
18002
|
+
}
|
|
18003
|
+
function fallbackViewerAssets() {
|
|
18004
|
+
return {
|
|
18005
|
+
css: [
|
|
18006
|
+
"body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;margin:0;padding:16px;background:#fafafa;color:#111}",
|
|
18007
|
+
".section{background:#fff;border:1px solid #ddd;border-radius:8px;padding:12px;margin-bottom:12px}",
|
|
18008
|
+
".section h2{margin:0 0 8px 0;font-size:14px}",
|
|
18009
|
+
"pre{margin:0;white-space:pre-wrap;word-break:break-word;background:#f6f8fa;border:1px solid #e3e5e8;border-radius:6px;padding:10px}"
|
|
18010
|
+
].join(""),
|
|
18011
|
+
js: [
|
|
18012
|
+
"(() => {",
|
|
18013
|
+
'const root=document.getElementById("main-content");',
|
|
18014
|
+
'const raw=document.getElementById("raw-sessions");',
|
|
18015
|
+
"if(!root||!raw)return;",
|
|
18016
|
+
'let sessions=[];try{sessions=JSON.parse(raw.textContent||"[]")}catch{}',
|
|
18017
|
+
'root.innerHTML="";',
|
|
18018
|
+
"for(const session of sessions){",
|
|
18019
|
+
'const section=document.createElement("section");section.className="section";',
|
|
18020
|
+
'const title=document.createElement("h2");title.textContent=String(session.label||"session");',
|
|
18021
|
+
'const pre=document.createElement("pre");',
|
|
18022
|
+
'pre.textContent=(Array.isArray(session.events)?session.events:[]).map((event)=>JSON.stringify(event)).join("\\n");',
|
|
18023
|
+
"section.append(title,pre);root.appendChild(section);",
|
|
18024
|
+
"}",
|
|
18025
|
+
"})();"
|
|
18026
|
+
].join("")
|
|
18027
|
+
};
|
|
18028
|
+
}
|
|
18029
|
+
function parsePreparedEvents(buffer) {
|
|
18030
|
+
return normalizedJsonLines(buffer).map((line) => {
|
|
18031
|
+
const parsed = parseJsonLine(line);
|
|
18032
|
+
return parsed ?? line;
|
|
18033
|
+
});
|
|
18034
|
+
}
|
|
18035
|
+
async function handleSessionsRender(options) {
|
|
18036
|
+
const targets = resolveSessionTargets({
|
|
18037
|
+
sessionIds: options.sessionId,
|
|
18038
|
+
labels: options.label,
|
|
18039
|
+
currentSession: options.currentSession
|
|
18040
|
+
});
|
|
18041
|
+
let outputPath = options.output ? resolve12(options.output) : "";
|
|
18042
|
+
if (!outputPath) {
|
|
18043
|
+
const outputDir = join9(process.cwd(), "deepline", "data");
|
|
18044
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
18045
|
+
outputPath = join9(
|
|
18046
|
+
outputDir,
|
|
18047
|
+
targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
|
|
18048
|
+
);
|
|
18049
|
+
} else {
|
|
18050
|
+
mkdirSync4(dirname9(outputPath), { recursive: true });
|
|
18051
|
+
}
|
|
18052
|
+
const sessions = targets.map((target) => ({
|
|
18053
|
+
label: target.label,
|
|
18054
|
+
events: parsePreparedEvents(
|
|
18055
|
+
prepareSessionBuffer(readFileSync7(target.filePath))
|
|
18056
|
+
)
|
|
18057
|
+
}));
|
|
18058
|
+
const { css, js } = fallbackViewerAssets();
|
|
18059
|
+
const refreshMeta = options.autoRefresh ? `<meta http-equiv="refresh" content="${Number.parseInt(options.autoRefresh, 10)}">` : "";
|
|
18060
|
+
const rawJson = JSON.stringify(sessions).replace(/<\//g, "<\\/");
|
|
18061
|
+
const html = `<!DOCTYPE html>
|
|
18062
|
+
<html lang="en">
|
|
18063
|
+
<head>
|
|
18064
|
+
<meta charset="UTF-8">
|
|
18065
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18066
|
+
${refreshMeta}
|
|
18067
|
+
<title>Session Viewer</title>
|
|
18068
|
+
<style>${css}</style>
|
|
18069
|
+
</head>
|
|
18070
|
+
<body>
|
|
18071
|
+
<div class="layout">
|
|
18072
|
+
<div class="main" id="main-content"></div>
|
|
18073
|
+
</div>
|
|
18074
|
+
<script type="application/json" id="raw-sessions">${rawJson}</script>
|
|
18075
|
+
<script>${js}</script>
|
|
18076
|
+
</body>
|
|
18077
|
+
</html>`;
|
|
18078
|
+
writeFileSync8(outputPath, html, "utf8");
|
|
18079
|
+
printCommandEnvelope(
|
|
18080
|
+
{
|
|
18081
|
+
ok: true,
|
|
18082
|
+
file: outputPath,
|
|
18083
|
+
session_count: targets.length,
|
|
18084
|
+
render: {
|
|
18085
|
+
sections: [
|
|
18086
|
+
{
|
|
18087
|
+
title: "sessions render",
|
|
18088
|
+
lines: [`Rendered session viewer: ${outputPath}`]
|
|
18089
|
+
}
|
|
18090
|
+
]
|
|
18091
|
+
}
|
|
18092
|
+
},
|
|
18093
|
+
{ json: options.json }
|
|
18094
|
+
);
|
|
18095
|
+
}
|
|
18096
|
+
function collectOption(value, previous) {
|
|
18097
|
+
previous.push(value);
|
|
18098
|
+
return previous;
|
|
18099
|
+
}
|
|
18100
|
+
function registerSessionsCommands(program) {
|
|
18101
|
+
const sessions = program.command("sessions").description("Upload and render local agent session transcripts.").addHelpText(
|
|
18102
|
+
"after",
|
|
18103
|
+
`
|
|
18104
|
+
Notes:
|
|
18105
|
+
Session commands operate on local Claude session JSONL files under
|
|
18106
|
+
~/.claude/projects. send uploads a compacted transcript or file to Deepline.
|
|
18107
|
+
render writes a local HTML viewer.
|
|
18108
|
+
|
|
18109
|
+
Examples:
|
|
18110
|
+
deepline sessions send --current-session --json
|
|
18111
|
+
deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca
|
|
18112
|
+
deepline sessions render --current-session --output session.html
|
|
18113
|
+
`
|
|
18114
|
+
);
|
|
18115
|
+
sessions.command("send").description("Upload session transcript(s) or a local file to Deepline.").addHelpText(
|
|
18116
|
+
"after",
|
|
18117
|
+
`
|
|
18118
|
+
Examples:
|
|
18119
|
+
deepline sessions send --current-session --json
|
|
18120
|
+
deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --label "pilot run"
|
|
18121
|
+
deepline sessions send --file ./debug.log --json
|
|
18122
|
+
`
|
|
18123
|
+
).option(
|
|
18124
|
+
"--session-id <uuid>",
|
|
18125
|
+
"Claude session UUID. Repeat for multiple sessions.",
|
|
18126
|
+
collectOption,
|
|
18127
|
+
[]
|
|
18128
|
+
).option(
|
|
18129
|
+
"--label <label>",
|
|
18130
|
+
"Label for the preceding session id",
|
|
18131
|
+
collectOption,
|
|
18132
|
+
[]
|
|
18133
|
+
).option("--current-session", "Use the newest local Claude session JSONL").option("--file <path>", "Upload a raw local file instead of a session").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleSessionsSend);
|
|
18134
|
+
sessions.command("render").description("Render local session transcript(s) to an HTML viewer.").addHelpText(
|
|
18135
|
+
"after",
|
|
18136
|
+
`
|
|
18137
|
+
Examples:
|
|
18138
|
+
deepline sessions render --current-session
|
|
18139
|
+
deepline sessions render --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --output session.html
|
|
18140
|
+
deepline sessions render --current-session --auto-refresh 5 --json
|
|
18141
|
+
`
|
|
18142
|
+
).option(
|
|
18143
|
+
"--session-id <uuid>",
|
|
18144
|
+
"Claude session UUID. Repeat for multiple sessions.",
|
|
18145
|
+
collectOption,
|
|
18146
|
+
[]
|
|
18147
|
+
).option(
|
|
18148
|
+
"--label <label>",
|
|
18149
|
+
"Label for the preceding session id",
|
|
18150
|
+
collectOption,
|
|
18151
|
+
[]
|
|
18152
|
+
).option("--current-session", "Use the newest local Claude session JSONL").option("--auto-refresh <seconds>", "Add a browser auto-refresh interval").option("-o, --output <path>", "Output HTML path").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleSessionsRender);
|
|
18153
|
+
}
|
|
18154
|
+
|
|
17141
18155
|
// src/cli/commands/tools.ts
|
|
17142
18156
|
import { Option } from "commander";
|
|
17143
18157
|
import {
|
|
17144
18158
|
chmodSync,
|
|
17145
|
-
existsSync as
|
|
18159
|
+
existsSync as existsSync9,
|
|
17146
18160
|
mkdtempSync,
|
|
17147
|
-
readFileSync as
|
|
17148
|
-
writeFileSync as
|
|
18161
|
+
readFileSync as readFileSync8,
|
|
18162
|
+
writeFileSync as writeFileSync10
|
|
17149
18163
|
} from "fs";
|
|
17150
18164
|
import { tmpdir as tmpdir4 } from "os";
|
|
17151
|
-
import { join as
|
|
18165
|
+
import { join as join11, resolve as resolve13 } from "path";
|
|
17152
18166
|
|
|
17153
18167
|
// src/tool-output.ts
|
|
17154
|
-
import { mkdirSync as
|
|
17155
|
-
import { homedir as
|
|
17156
|
-
import { join as
|
|
18168
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync9 } from "fs";
|
|
18169
|
+
import { homedir as homedir6 } from "os";
|
|
18170
|
+
import { join as join10 } from "path";
|
|
17157
18171
|
function isPlainObject(value) {
|
|
17158
18172
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
17159
18173
|
}
|
|
@@ -17249,19 +18263,19 @@ function tryConvertToList(payload, options) {
|
|
|
17249
18263
|
return null;
|
|
17250
18264
|
}
|
|
17251
18265
|
function ensureOutputDir() {
|
|
17252
|
-
const outputDir =
|
|
17253
|
-
|
|
18266
|
+
const outputDir = join10(homedir6(), ".local", "share", "deepline", "data");
|
|
18267
|
+
mkdirSync5(outputDir, { recursive: true });
|
|
17254
18268
|
return outputDir;
|
|
17255
18269
|
}
|
|
17256
18270
|
function writeJsonOutputFile(payload, stem) {
|
|
17257
18271
|
const outputDir = ensureOutputDir();
|
|
17258
|
-
const outputPath =
|
|
17259
|
-
|
|
18272
|
+
const outputPath = join10(outputDir, `${stem}_${Date.now()}.json`);
|
|
18273
|
+
writeFileSync9(outputPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
17260
18274
|
return outputPath;
|
|
17261
18275
|
}
|
|
17262
18276
|
function writeCsvOutputFile(rows, stem) {
|
|
17263
18277
|
const outputDir = ensureOutputDir();
|
|
17264
|
-
const outputPath =
|
|
18278
|
+
const outputPath = join10(outputDir, `${stem}_${Date.now()}.csv`);
|
|
17265
18279
|
const seen = /* @__PURE__ */ new Set();
|
|
17266
18280
|
const columns = [];
|
|
17267
18281
|
for (const row of rows) {
|
|
@@ -17284,7 +18298,7 @@ function writeCsvOutputFile(rows, stem) {
|
|
|
17284
18298
|
for (const row of rows) {
|
|
17285
18299
|
lines.push(columns.map((column) => escapeCell(row[column])).join(","));
|
|
17286
18300
|
}
|
|
17287
|
-
|
|
18301
|
+
writeFileSync9(outputPath, `${lines.join("\n")}
|
|
17288
18302
|
`, "utf-8");
|
|
17289
18303
|
const previewRows = rows.slice(0, 5);
|
|
17290
18304
|
const previewColumns = columns.slice(0, 5);
|
|
@@ -17570,7 +18584,7 @@ Common commands:
|
|
|
17570
18584
|
|
|
17571
18585
|
Output:
|
|
17572
18586
|
Use describe for tool contracts.
|
|
17573
|
-
Use execute to run a tool.
|
|
18587
|
+
Use execute to run a tool.
|
|
17574
18588
|
`
|
|
17575
18589
|
);
|
|
17576
18590
|
tools.command("list").description("List available tools.").addHelpText(
|
|
@@ -17663,7 +18677,7 @@ Examples:
|
|
|
17663
18677
|
Notes:
|
|
17664
18678
|
Shows the compact agent contract by default: what the tool does, cost,
|
|
17665
18679
|
required inputs, play getters, and a runnable ctx.tools.execute snippet.
|
|
17666
|
-
|
|
18680
|
+
get is accepted as a compatibility alias for describe.
|
|
17667
18681
|
|
|
17668
18682
|
Examples:
|
|
17669
18683
|
deepline tools describe hunter_email_verifier
|
|
@@ -17697,29 +18711,13 @@ Examples:
|
|
|
17697
18711
|
gettersOnly: Boolean(options.gettersOnly)
|
|
17698
18712
|
});
|
|
17699
18713
|
});
|
|
17700
|
-
addToolMetadataCommand(tools.command("describe <toolId>"));
|
|
17701
|
-
tools.command("
|
|
17702
|
-
"after",
|
|
17703
|
-
`
|
|
17704
|
-
Examples:
|
|
17705
|
-
deepline tools describe hunter_email_verifier --json
|
|
17706
|
-
`
|
|
17707
|
-
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (toolId, options) => {
|
|
17708
|
-
const message = `tools get has been removed from the V2 SDK CLI. Use: deepline tools describe ${toolId} --json`;
|
|
17709
|
-
if (options.json || shouldEmitJson()) {
|
|
17710
|
-
printJsonError({ message, code: "TOOLS_GET_REMOVED" });
|
|
17711
|
-
} else {
|
|
17712
|
-
console.error(message);
|
|
17713
|
-
}
|
|
17714
|
-
process.exitCode = 2;
|
|
17715
|
-
});
|
|
17716
|
-
tools.command("execute <toolId>").alias("run").description("Execute a tool by id.").addHelpText(
|
|
18714
|
+
addToolMetadataCommand(tools.command("describe <toolId>").alias("get"));
|
|
18715
|
+
tools.command("execute <toolId>").description("Execute a tool by id.").addHelpText(
|
|
17717
18716
|
"after",
|
|
17718
18717
|
`
|
|
17719
18718
|
Notes:
|
|
17720
18719
|
Use tools for one atomic provider/API operation. Use plays for composed workflows,
|
|
17721
18720
|
waterfalls, row maps, checkpoints, and retries.
|
|
17722
|
-
execute is the canonical execution verb. run is a compatibility alias.
|
|
17723
18721
|
Calling a provider-backed tool can spend Deepline credits. Use --json for the
|
|
17724
18722
|
stable result payload plus output preview and debugging helpers.
|
|
17725
18723
|
|
|
@@ -17767,7 +18765,7 @@ Examples:
|
|
|
17767
18765
|
}
|
|
17768
18766
|
async function getTool(toolId, options = {}) {
|
|
17769
18767
|
if (!toolId) {
|
|
17770
|
-
console.error("Usage: deepline tools
|
|
18768
|
+
console.error("Usage: deepline tools describe <toolId> [--json]");
|
|
17771
18769
|
return 1;
|
|
17772
18770
|
}
|
|
17773
18771
|
const client2 = new DeeplineClient();
|
|
@@ -18318,11 +19316,11 @@ function normalizeOutputFormat(raw) {
|
|
|
18318
19316
|
}
|
|
18319
19317
|
function resolveAtFilePath(rawPath) {
|
|
18320
19318
|
const trimmed = rawPath.trim();
|
|
18321
|
-
const resolved =
|
|
18322
|
-
if (
|
|
19319
|
+
const resolved = resolve13(trimmed);
|
|
19320
|
+
if (existsSync9(resolved)) return resolved;
|
|
18323
19321
|
if (process.platform !== "win32" && trimmed.includes("\\")) {
|
|
18324
|
-
const normalized =
|
|
18325
|
-
if (
|
|
19322
|
+
const normalized = resolve13(trimmed.replace(/\\/g, "/"));
|
|
19323
|
+
if (existsSync9(normalized)) return normalized;
|
|
18326
19324
|
}
|
|
18327
19325
|
return resolved;
|
|
18328
19326
|
}
|
|
@@ -18333,7 +19331,7 @@ function readJsonArgument(raw, flagName) {
|
|
|
18333
19331
|
throw new Error(`Invalid ${flagName} value: empty @file path.`);
|
|
18334
19332
|
}
|
|
18335
19333
|
try {
|
|
18336
|
-
return
|
|
19334
|
+
return readFileSync8(resolveAtFilePath(filePath), "utf8").replace(
|
|
18337
19335
|
/^\uFEFF/,
|
|
18338
19336
|
""
|
|
18339
19337
|
);
|
|
@@ -18424,9 +19422,9 @@ function powerShellQuote(value) {
|
|
|
18424
19422
|
function seedToolListScript(input2) {
|
|
18425
19423
|
const stem = safeFileStem(input2.toolId);
|
|
18426
19424
|
const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
|
|
18427
|
-
const scriptDir = mkdtempSync(
|
|
19425
|
+
const scriptDir = mkdtempSync(join11(tmpdir4(), "deepline-workflow-seed-"));
|
|
18428
19426
|
chmodSync(scriptDir, 448);
|
|
18429
|
-
const scriptPath =
|
|
19427
|
+
const scriptPath = join11(scriptDir, fileName);
|
|
18430
19428
|
const projectDir = `deepline/projects/${stem}-workflow`;
|
|
18431
19429
|
const playName = `${stem}-workflow`;
|
|
18432
19430
|
const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
|
|
@@ -18462,7 +19460,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
|
|
|
18462
19460
|
};
|
|
18463
19461
|
});
|
|
18464
19462
|
`;
|
|
18465
|
-
|
|
19463
|
+
writeFileSync10(scriptPath, script, { encoding: "utf-8", mode: 384 });
|
|
18466
19464
|
return {
|
|
18467
19465
|
path: scriptPath,
|
|
18468
19466
|
sourceCode: script,
|
|
@@ -18717,7 +19715,7 @@ async function executeTool(args) {
|
|
|
18717
19715
|
|
|
18718
19716
|
// src/cli/commands/workflow.ts
|
|
18719
19717
|
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
18720
|
-
import { dirname as
|
|
19718
|
+
import { dirname as dirname10, join as join12, resolve as resolve14 } from "path";
|
|
18721
19719
|
|
|
18722
19720
|
// src/cli/workflow-to-play.ts
|
|
18723
19721
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -18924,7 +19922,7 @@ function readStatus(payload) {
|
|
|
18924
19922
|
}
|
|
18925
19923
|
async function readJsonOption(payload, file) {
|
|
18926
19924
|
if (file) {
|
|
18927
|
-
const raw = await readFile4(
|
|
19925
|
+
const raw = await readFile4(resolve14(file), "utf8");
|
|
18928
19926
|
return JSON.parse(raw);
|
|
18929
19927
|
}
|
|
18930
19928
|
if (payload) {
|
|
@@ -18958,8 +19956,8 @@ async function transformOne(api, workflowId, outDir, publish) {
|
|
|
18958
19956
|
revision.config,
|
|
18959
19957
|
{ workflowName: workflow.name, version: revision.version }
|
|
18960
19958
|
);
|
|
18961
|
-
const file =
|
|
18962
|
-
await mkdir5(
|
|
19959
|
+
const file = join12(resolve14(outDir), `${compiled.playName}.play.ts`);
|
|
19960
|
+
await mkdir5(dirname10(file), { recursive: true });
|
|
18963
19961
|
await writeFile5(file, compiled.sourceCode, "utf8");
|
|
18964
19962
|
let published = false;
|
|
18965
19963
|
if (publish) {
|
|
@@ -19206,8 +20204,8 @@ Notes:
|
|
|
19206
20204
|
|
|
19207
20205
|
// src/cli/commands/update.ts
|
|
19208
20206
|
import { spawn } from "child_process";
|
|
19209
|
-
import { existsSync as
|
|
19210
|
-
import { dirname as
|
|
20207
|
+
import { existsSync as existsSync10 } from "fs";
|
|
20208
|
+
import { dirname as dirname11, join as join13, resolve as resolve15 } from "path";
|
|
19211
20209
|
function posixShellQuote(value) {
|
|
19212
20210
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
19213
20211
|
}
|
|
@@ -19226,19 +20224,19 @@ function buildSourceUpdateCommand(sourceRoot) {
|
|
|
19226
20224
|
return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
|
|
19227
20225
|
}
|
|
19228
20226
|
function findRepoBackedSdkRoot(startPath) {
|
|
19229
|
-
let current =
|
|
20227
|
+
let current = resolve15(startPath);
|
|
19230
20228
|
while (true) {
|
|
19231
|
-
if (
|
|
20229
|
+
if (existsSync10(join13(current, "sdk", "package.json")) && existsSync10(join13(current, "sdk", "bin", "deepline-dev.ts"))) {
|
|
19232
20230
|
return current;
|
|
19233
20231
|
}
|
|
19234
|
-
const parent =
|
|
20232
|
+
const parent = dirname11(current);
|
|
19235
20233
|
if (parent === current) return null;
|
|
19236
20234
|
current = parent;
|
|
19237
20235
|
}
|
|
19238
20236
|
}
|
|
19239
20237
|
function resolveUpdatePlan() {
|
|
19240
|
-
const entrypoint = process.argv[1] ?
|
|
19241
|
-
const sourceRoot = entrypoint ? findRepoBackedSdkRoot(
|
|
20238
|
+
const entrypoint = process.argv[1] ? resolve15(process.argv[1]) : "";
|
|
20239
|
+
const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname11(entrypoint)) : null;
|
|
19242
20240
|
if (sourceRoot) {
|
|
19243
20241
|
return {
|
|
19244
20242
|
kind: "source",
|
|
@@ -19313,7 +20311,7 @@ async function handleUpdate(options) {
|
|
|
19313
20311
|
return runCommand(plan.command, plan.args);
|
|
19314
20312
|
}
|
|
19315
20313
|
function registerUpdateCommand(program) {
|
|
19316
|
-
program.command("update").
|
|
20314
|
+
program.command("update").description("Update the Deepline SDK/CLI.").addHelpText(
|
|
19317
20315
|
"after",
|
|
19318
20316
|
`
|
|
19319
20317
|
Notes:
|
|
@@ -19341,7 +20339,7 @@ var command_compatibility_default = {
|
|
|
19341
20339
|
session: {
|
|
19342
20340
|
family: "python",
|
|
19343
20341
|
label: "a legacy Python CLI session/playground command",
|
|
19344
|
-
sdk_alternative: "Use
|
|
20342
|
+
sdk_alternative: "Use `deepline sessions send ...` or `deepline sessions render ...` for transcript workflows."
|
|
19345
20343
|
},
|
|
19346
20344
|
workflows: {
|
|
19347
20345
|
family: "python",
|
|
@@ -19361,15 +20359,14 @@ var command_compatibility_default = {
|
|
|
19361
20359
|
label: "an SDK CLI play command",
|
|
19362
20360
|
python_alternative: "Use `deepline workflows ...` only for legacy workflows."
|
|
19363
20361
|
},
|
|
19364
|
-
play: {
|
|
19365
|
-
family: "sdk",
|
|
19366
|
-
label: "an SDK CLI play command",
|
|
19367
|
-
python_alternative: "Use `deepline workflows ...` only for legacy workflows."
|
|
19368
|
-
},
|
|
19369
20362
|
runs: {
|
|
19370
20363
|
family: "sdk",
|
|
19371
20364
|
label: "an SDK CLI run inspection command"
|
|
19372
20365
|
},
|
|
20366
|
+
sessions: {
|
|
20367
|
+
family: "sdk",
|
|
20368
|
+
label: "an SDK CLI session transcript command"
|
|
20369
|
+
},
|
|
19373
20370
|
health: {
|
|
19374
20371
|
family: "sdk",
|
|
19375
20372
|
label: "an SDK CLI health command"
|
|
@@ -19502,15 +20499,15 @@ function unknownCommandNameFromMessage(message) {
|
|
|
19502
20499
|
// src/cli/skills-sync.ts
|
|
19503
20500
|
import { spawn as spawn2, spawnSync } from "child_process";
|
|
19504
20501
|
import {
|
|
19505
|
-
existsSync as
|
|
19506
|
-
mkdirSync as
|
|
19507
|
-
readdirSync as
|
|
19508
|
-
readFileSync as
|
|
19509
|
-
statSync as
|
|
19510
|
-
writeFileSync as
|
|
20502
|
+
existsSync as existsSync11,
|
|
20503
|
+
mkdirSync as mkdirSync6,
|
|
20504
|
+
readdirSync as readdirSync3,
|
|
20505
|
+
readFileSync as readFileSync9,
|
|
20506
|
+
statSync as statSync4,
|
|
20507
|
+
writeFileSync as writeFileSync11
|
|
19511
20508
|
} from "fs";
|
|
19512
|
-
import { homedir as
|
|
19513
|
-
import { dirname as
|
|
20509
|
+
import { homedir as homedir7 } from "os";
|
|
20510
|
+
import { dirname as dirname12, join as join14 } from "path";
|
|
19514
20511
|
var CHECK_TIMEOUT_MS2 = 3e3;
|
|
19515
20512
|
var SDK_SKILL_NAME = "deepline-plays";
|
|
19516
20513
|
var attemptedSync = false;
|
|
@@ -19524,20 +20521,20 @@ function activePluginSkillsDir() {
|
|
|
19524
20521
|
return "";
|
|
19525
20522
|
}
|
|
19526
20523
|
const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
|
|
19527
|
-
return dir &&
|
|
20524
|
+
return dir && existsSync11(dir) ? dir : "";
|
|
19528
20525
|
}
|
|
19529
20526
|
function readPluginSkillsVersion() {
|
|
19530
20527
|
const dir = activePluginSkillsDir();
|
|
19531
20528
|
if (!dir) return "";
|
|
19532
20529
|
try {
|
|
19533
|
-
return
|
|
20530
|
+
return readFileSync9(join14(dir, ".version"), "utf-8").trim();
|
|
19534
20531
|
} catch {
|
|
19535
20532
|
return "";
|
|
19536
20533
|
}
|
|
19537
20534
|
}
|
|
19538
20535
|
function sdkSkillsVersionPath(baseUrl) {
|
|
19539
|
-
const home = process.env.HOME?.trim() ||
|
|
19540
|
-
return
|
|
20536
|
+
const home = process.env.HOME?.trim() || homedir7();
|
|
20537
|
+
return join14(
|
|
19541
20538
|
home,
|
|
19542
20539
|
".local",
|
|
19543
20540
|
"deepline",
|
|
@@ -19550,25 +20547,25 @@ function readLocalSkillsVersion(baseUrl) {
|
|
|
19550
20547
|
const pluginVersion = readPluginSkillsVersion();
|
|
19551
20548
|
if (pluginVersion) return pluginVersion;
|
|
19552
20549
|
const path = sdkSkillsVersionPath(baseUrl);
|
|
19553
|
-
if (!
|
|
20550
|
+
if (!existsSync11(path)) return "";
|
|
19554
20551
|
try {
|
|
19555
|
-
return
|
|
20552
|
+
return readFileSync9(path, "utf-8").trim();
|
|
19556
20553
|
} catch {
|
|
19557
20554
|
return "";
|
|
19558
20555
|
}
|
|
19559
20556
|
}
|
|
19560
20557
|
function writeLocalSkillsVersion(baseUrl, version) {
|
|
19561
20558
|
const path = sdkSkillsVersionPath(baseUrl);
|
|
19562
|
-
|
|
19563
|
-
|
|
20559
|
+
mkdirSync6(dirname12(path), { recursive: true });
|
|
20560
|
+
writeFileSync11(path, `${version}
|
|
19564
20561
|
`, "utf-8");
|
|
19565
20562
|
}
|
|
19566
20563
|
function installedSdkSkillHasStalePositionalExecuteExamples() {
|
|
19567
|
-
const home = process.env.HOME?.trim() ||
|
|
20564
|
+
const home = process.env.HOME?.trim() || homedir7();
|
|
19568
20565
|
const pluginSkillsDir = activePluginSkillsDir();
|
|
19569
|
-
const roots = pluginSkillsDir ? [
|
|
19570
|
-
|
|
19571
|
-
|
|
20566
|
+
const roots = pluginSkillsDir ? [join14(pluginSkillsDir, SDK_SKILL_NAME)] : [
|
|
20567
|
+
join14(home, ".claude", "skills", SDK_SKILL_NAME),
|
|
20568
|
+
join14(home, ".agents", "skills", SDK_SKILL_NAME)
|
|
19572
20569
|
];
|
|
19573
20570
|
const staleMarkers = [
|
|
19574
20571
|
"ctx.tools.execute(key",
|
|
@@ -19578,22 +20575,22 @@ function installedSdkSkillHasStalePositionalExecuteExamples() {
|
|
|
19578
20575
|
'rowCtx.tools.execute("'
|
|
19579
20576
|
];
|
|
19580
20577
|
const scan = (dir) => {
|
|
19581
|
-
for (const entry of
|
|
19582
|
-
const path =
|
|
19583
|
-
const stat4 =
|
|
20578
|
+
for (const entry of readdirSync3(dir)) {
|
|
20579
|
+
const path = join14(dir, entry);
|
|
20580
|
+
const stat4 = statSync4(path);
|
|
19584
20581
|
if (stat4.isDirectory()) {
|
|
19585
20582
|
if (scan(path)) return true;
|
|
19586
20583
|
continue;
|
|
19587
20584
|
}
|
|
19588
20585
|
if (!entry.endsWith(".md")) continue;
|
|
19589
|
-
const text =
|
|
20586
|
+
const text = readFileSync9(path, "utf-8");
|
|
19590
20587
|
if (staleMarkers.some((marker) => text.includes(marker))) return true;
|
|
19591
20588
|
}
|
|
19592
20589
|
return false;
|
|
19593
20590
|
};
|
|
19594
20591
|
for (const root of roots) {
|
|
19595
20592
|
try {
|
|
19596
|
-
if (
|
|
20593
|
+
if (existsSync11(root) && scan(root)) return true;
|
|
19597
20594
|
} catch {
|
|
19598
20595
|
continue;
|
|
19599
20596
|
}
|
|
@@ -19665,7 +20662,7 @@ function resolveSkillsInstallCommands(baseUrl) {
|
|
|
19665
20662
|
return [npxInstall];
|
|
19666
20663
|
}
|
|
19667
20664
|
function runOneSkillsInstall(install) {
|
|
19668
|
-
return new Promise((
|
|
20665
|
+
return new Promise((resolve16) => {
|
|
19669
20666
|
const child = spawn2(install.command, install.args, {
|
|
19670
20667
|
stdio: ["ignore", "ignore", "pipe"],
|
|
19671
20668
|
env: process.env
|
|
@@ -19675,7 +20672,7 @@ function runOneSkillsInstall(install) {
|
|
|
19675
20672
|
stderr += chunk.toString("utf-8");
|
|
19676
20673
|
});
|
|
19677
20674
|
child.on("error", (error) => {
|
|
19678
|
-
|
|
20675
|
+
resolve16({
|
|
19679
20676
|
ok: false,
|
|
19680
20677
|
detail: `failed to start ${install.command}: ${error.message}`,
|
|
19681
20678
|
manualCommand: install.manualCommand
|
|
@@ -19683,11 +20680,11 @@ function runOneSkillsInstall(install) {
|
|
|
19683
20680
|
});
|
|
19684
20681
|
child.on("close", (code) => {
|
|
19685
20682
|
if (code === 0) {
|
|
19686
|
-
|
|
20683
|
+
resolve16({ ok: true, detail: "", manualCommand: install.manualCommand });
|
|
19687
20684
|
return;
|
|
19688
20685
|
}
|
|
19689
20686
|
const detail = stderr.trim();
|
|
19690
|
-
|
|
20687
|
+
resolve16({
|
|
19691
20688
|
ok: false,
|
|
19692
20689
|
detail: detail ? `${install.command}: ${detail}` : `${install.command} exited ${code}`,
|
|
19693
20690
|
manualCommand: install.manualCommand
|
|
@@ -19775,8 +20772,8 @@ function shouldDeferSkillsSyncForCommand() {
|
|
|
19775
20772
|
return (command === "play" || command === "plays") && subcommand === "run" && args.includes("--json");
|
|
19776
20773
|
}
|
|
19777
20774
|
async function runPlayRunnerHealthCheck() {
|
|
19778
|
-
const dir = await mkdtemp2(
|
|
19779
|
-
const file =
|
|
20775
|
+
const dir = await mkdtemp2(join15(tmpdir5(), "deepline-health-play-"));
|
|
20776
|
+
const file = join15(dir, "health-check.play.ts");
|
|
19780
20777
|
try {
|
|
19781
20778
|
await writeFile6(
|
|
19782
20779
|
file,
|
|
@@ -19964,6 +20961,7 @@ Common commands:
|
|
|
19964
20961
|
deepline preflight
|
|
19965
20962
|
deepline health
|
|
19966
20963
|
deepline auth status --json
|
|
20964
|
+
deepline sessions send --current-session --json
|
|
19967
20965
|
deepline plays search email --json
|
|
19968
20966
|
deepline plays describe person-linkedin-to-email --json
|
|
19969
20967
|
deepline plays run my.play.ts --input '{"domain":"stripe.com"}'
|
|
@@ -20016,6 +21014,7 @@ Exit codes:
|
|
|
20016
21014
|
registerAuthCommands(program);
|
|
20017
21015
|
registerToolsCommands(program);
|
|
20018
21016
|
registerPlayCommands(program);
|
|
21017
|
+
registerSessionsCommands(program);
|
|
20019
21018
|
registerWorkflowCommands(program);
|
|
20020
21019
|
registerSecretsCommands(program);
|
|
20021
21020
|
registerBillingCommands(program);
|