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.
@@ -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 join14 } from "path";
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
- version: "0.1.101",
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.101",
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((resolve15) => setTimeout(resolve15, ms));
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((resolve15) => {
1276
- wake = resolve15;
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
- (resolve15) => setTimeout(() => resolve15(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
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((resolve15) => setTimeout(resolve15, ms));
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 play run`, repo scripts such as `bun run deepline -- play run`,
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 play check`.
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 play run --watch`.
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 homeDir = process.env.HOME || homedir3();
3104
+ const homeDir2 = process.env.HOME || homedir3();
3039
3105
  return join3(
3040
- homeDir,
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 = 1;
3515
- var EXIT_SERVER = 2;
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((resolve15) => setTimeout(resolve15, ms));
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: [{ label: "Run", command: "deepline auth register" }]
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: [{ label: "Run", command: "deepline auth register" }]
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 formatDatasetExecutionStats(raw, denominator) {
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 collectSerializedDatasetRowsInfos(statusOrResult) {
4963
+ function collectPackagedStepDatasetCandidates(statusOrResult) {
4869
4964
  const root = isRecord3(statusOrResult) ? statusOrResult : null;
4870
- const result = isRecord3(root?.result) ? root.result : root;
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
- collectDatasetCandidates({
4876
- value: result,
4877
- path: "result",
4878
- output: candidates
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").alias("customer-db").description("Query the tenant customer database.").addHelpText(
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").alias("psql").description("Run SQL against the tenant customer database.").addHelpText(
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 psql --sql "select count(*) from contacts" --json
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((resolve15) => setTimeout(resolve15, ms));
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.length > MAX_CHARS ? `${json.slice(0, MAX_CHARS)}
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 parts = formatSummaryScalarParts(record, /* @__PURE__ */ new Set(["columnStats"]));
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.slice(0, 200)}`);
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.slice(0, 200)}`);
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.find((info) => info.source === options.datasetPath) ?? null : availableRows.length === 1 ? availableRows[0] : null;
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 play check <play-file.ts> [--json]");
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("Usage: deepline play get <play-file.ts|play-name> [--json]");
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 play versions --name <name> [--json]");
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 play publish <play-file.ts|play-name> [--latest|--revision-id <id>] [--json]"
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").alias("play").description("Search, validate, run, and manage cloud plays.").addHelpText(
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((resolve15) => setTimeout(resolve15, 250));
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.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);
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((resolve15, reject) => {
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
- resolve15(line);
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 existsSync8,
18159
+ existsSync as existsSync9,
17146
18160
  mkdtempSync,
17147
- readFileSync as readFileSync7,
17148
- writeFileSync as writeFileSync9
18161
+ readFileSync as readFileSync8,
18162
+ writeFileSync as writeFileSync10
17149
18163
  } from "fs";
17150
18164
  import { tmpdir as tmpdir4 } from "os";
17151
- import { join as join10, resolve as resolve12 } from "path";
18165
+ import { join as join11, resolve as resolve13 } from "path";
17152
18166
 
17153
18167
  // src/tool-output.ts
17154
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync8 } from "fs";
17155
- import { homedir as homedir5 } from "os";
17156
- import { join as join9 } from "path";
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 = join9(homedir5(), ".local", "share", "deepline", "data");
17253
- mkdirSync4(outputDir, { recursive: true });
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 = join9(outputDir, `${stem}_${Date.now()}.json`);
17259
- writeFileSync8(outputPath, JSON.stringify(payload, null, 2), "utf-8");
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 = join9(outputDir, `${stem}_${Date.now()}.csv`);
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
- writeFileSync8(outputPath, `${lines.join("\n")}
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. run is accepted as a compatibility alias.
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
- Use --json for the full metadata/debug payload.
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("get <toolId>").description("Deprecated. Use tools describe.").addHelpText(
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 get <toolId> [--json]");
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 = resolve12(trimmed);
18322
- if (existsSync8(resolved)) return resolved;
19319
+ const resolved = resolve13(trimmed);
19320
+ if (existsSync9(resolved)) return resolved;
18323
19321
  if (process.platform !== "win32" && trimmed.includes("\\")) {
18324
- const normalized = resolve12(trimmed.replace(/\\/g, "/"));
18325
- if (existsSync8(normalized)) return normalized;
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 readFileSync7(resolveAtFilePath(filePath), "utf8").replace(
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(join10(tmpdir4(), "deepline-workflow-seed-"));
19425
+ const scriptDir = mkdtempSync(join11(tmpdir4(), "deepline-workflow-seed-"));
18428
19426
  chmodSync(scriptDir, 448);
18429
- const scriptPath = join10(scriptDir, fileName);
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
- writeFileSync9(scriptPath, script, { encoding: "utf-8", mode: 384 });
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 dirname9, join as join11, resolve as resolve13 } from "path";
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(resolve13(file), "utf8");
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 = join11(resolve13(outDir), `${compiled.playName}.play.ts`);
18962
- await mkdir5(dirname9(file), { recursive: true });
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 existsSync9 } from "fs";
19210
- import { dirname as dirname10, join as join12, resolve as resolve14 } from "path";
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 = resolve14(startPath);
20227
+ let current = resolve15(startPath);
19230
20228
  while (true) {
19231
- if (existsSync9(join12(current, "sdk", "package.json")) && existsSync9(join12(current, "sdk", "bin", "deepline-dev.ts"))) {
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 = dirname10(current);
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] ? resolve14(process.argv[1]) : "";
19241
- const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname10(entrypoint)) : null;
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").alias("upgrade").description("Update the Deepline SDK/CLI.").addHelpText(
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 SDK play run output and run commands such as `deepline plays run ...`."
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 existsSync10,
19506
- mkdirSync as mkdirSync5,
19507
- readdirSync as readdirSync2,
19508
- readFileSync as readFileSync8,
19509
- statSync as statSync2,
19510
- writeFileSync as writeFileSync10
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 homedir6 } from "os";
19513
- import { dirname as dirname11, join as join13 } from "path";
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 && existsSync10(dir) ? 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 readFileSync8(join13(dir, ".version"), "utf-8").trim();
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() || homedir6();
19540
- return join13(
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 (!existsSync10(path)) return "";
20550
+ if (!existsSync11(path)) return "";
19554
20551
  try {
19555
- return readFileSync8(path, "utf-8").trim();
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
- mkdirSync5(dirname11(path), { recursive: true });
19563
- writeFileSync10(path, `${version}
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() || homedir6();
20564
+ const home = process.env.HOME?.trim() || homedir7();
19568
20565
  const pluginSkillsDir = activePluginSkillsDir();
19569
- const roots = pluginSkillsDir ? [join13(pluginSkillsDir, SDK_SKILL_NAME)] : [
19570
- join13(home, ".claude", "skills", SDK_SKILL_NAME),
19571
- join13(home, ".agents", "skills", SDK_SKILL_NAME)
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 readdirSync2(dir)) {
19582
- const path = join13(dir, entry);
19583
- const stat4 = statSync2(path);
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 = readFileSync8(path, "utf-8");
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 (existsSync10(root) && scan(root)) return true;
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((resolve15) => {
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
- resolve15({
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
- resolve15({ ok: true, detail: "", manualCommand: install.manualCommand });
20683
+ resolve16({ ok: true, detail: "", manualCommand: install.manualCommand });
19687
20684
  return;
19688
20685
  }
19689
20686
  const detail = stderr.trim();
19690
- resolve15({
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(join14(tmpdir5(), "deepline-health-play-"));
19779
- const file = join14(dir, "health-check.play.ts");
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);