deepline 0.1.109 → 0.1.110

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.
Files changed (34) hide show
  1. package/dist/cli/index.js +2226 -1271
  2. package/dist/cli/index.mjs +2303 -1355
  3. package/dist/index.d.mts +21 -14
  4. package/dist/index.d.ts +21 -14
  5. package/dist/index.js +97 -23
  6. package/dist/index.mjs +97 -23
  7. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +42 -20
  8. package/dist/repo/apps/play-runner-workers/src/entry.ts +135 -45
  9. package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +18 -27
  10. package/dist/repo/apps/play-runner-workers/src/workflow-instance-create.ts +44 -0
  11. package/dist/repo/apps/play-runner-workers/src/workflow-retry.ts +7 -11
  12. package/dist/repo/sdk/src/client.ts +35 -12
  13. package/dist/repo/sdk/src/errors.ts +2 -2
  14. package/dist/repo/sdk/src/http.ts +87 -7
  15. package/dist/repo/sdk/src/play.ts +1 -1
  16. package/dist/repo/sdk/src/plays/bundle-play-file.ts +5 -1
  17. package/dist/repo/sdk/src/release.ts +13 -10
  18. package/dist/repo/sdk/src/tool-output.ts +2 -2
  19. package/dist/repo/sdk/src/types.ts +9 -6
  20. package/dist/repo/shared_libs/play-runtime/fullenrich-batching.ts +229 -0
  21. package/dist/repo/shared_libs/play-runtime/governor/policy.ts +1 -1
  22. package/dist/repo/shared_libs/play-runtime/play-runtime-batching-registry.ts +20 -0
  23. package/dist/repo/shared_libs/play-runtime/run-failure.ts +20 -12
  24. package/dist/repo/shared_libs/play-runtime/run-ledger.ts +107 -56
  25. package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +4 -2
  26. package/dist/repo/shared_libs/play-runtime/secret-redaction.ts +15 -0
  27. package/dist/repo/shared_libs/play-runtime/work-receipts.ts +1 -0
  28. package/dist/repo/shared_libs/plays/bundling/index.ts +69 -11
  29. package/dist/repo/shared_libs/plays/static-pipeline.ts +1 -3
  30. package/dist/repo/shared_libs/security/outbound-url-policy.ts +238 -0
  31. package/dist/repo/shared_libs/security/safe-fetch.ts +118 -0
  32. package/dist/viewer/viewer.css +617 -0
  33. package/dist/viewer/viewer.js +1496 -0
  34. package/package.json +5 -1
package/dist/cli/index.js CHANGED
@@ -23,10 +23,167 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  mod
24
24
  ));
25
25
 
26
+ // src/cli/proxy-env.ts
27
+ var import_undici = require("undici");
28
+ function defaultPort(protocol) {
29
+ return protocol === "https:" ? "443" : "80";
30
+ }
31
+ function proxyEnvValue(name) {
32
+ return process.env[name]?.trim() ?? "";
33
+ }
34
+ function firstProxyEnv(...names) {
35
+ for (const name of names) {
36
+ const value = proxyEnvValue(name);
37
+ if (value) {
38
+ return value;
39
+ }
40
+ }
41
+ return "";
42
+ }
43
+ function ipv4ToNumber(value) {
44
+ const parts = value.split(".");
45
+ if (parts.length !== 4) {
46
+ return null;
47
+ }
48
+ let result = 0;
49
+ for (const part of parts) {
50
+ if (!/^\d+$/.test(part)) {
51
+ return null;
52
+ }
53
+ const parsed = Number(part);
54
+ if (parsed < 0 || parsed > 255) {
55
+ return null;
56
+ }
57
+ result = (result << 8) + parsed;
58
+ }
59
+ return result >>> 0;
60
+ }
61
+ function ipv4MatchesCidr(hostname3, cidr) {
62
+ const [range, bitsRaw] = cidr.split("/");
63
+ const hostValue = ipv4ToNumber(hostname3);
64
+ const rangeValue = range ? ipv4ToNumber(range) : null;
65
+ const bits = Number(bitsRaw);
66
+ if (hostValue === null || rangeValue === null || !Number.isInteger(bits) || bits < 0 || bits > 32) {
67
+ return false;
68
+ }
69
+ const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
70
+ return (hostValue & mask) === (rangeValue & mask);
71
+ }
72
+ function splitNoProxyHostPort(entry) {
73
+ const bracketed = entry.match(/^\[([^\]]+)\](?::(\d+))?$/);
74
+ if (bracketed) {
75
+ return { host: bracketed[1].toLowerCase(), port: bracketed[2] ?? null };
76
+ }
77
+ const portMatch = entry.match(/^([^:]+):(\d+)$/);
78
+ if (portMatch) {
79
+ return { host: portMatch[1].toLowerCase(), port: portMatch[2] ?? null };
80
+ }
81
+ return { host: entry.toLowerCase(), port: null };
82
+ }
83
+ function noProxyMatches(url, noProxy) {
84
+ const hostname3 = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
85
+ const port = url.port || defaultPort(url.protocol);
86
+ for (const rawEntry of noProxy.split(/[,\s]+/)) {
87
+ const entry = rawEntry.trim().toLowerCase();
88
+ if (!entry) {
89
+ continue;
90
+ }
91
+ if (entry === "*") {
92
+ return true;
93
+ }
94
+ const { host, port: entryPort } = splitNoProxyHostPort(entry);
95
+ if (entryPort && entryPort !== port) {
96
+ continue;
97
+ }
98
+ if (host.includes("/") && ipv4MatchesCidr(hostname3, host)) {
99
+ return true;
100
+ }
101
+ if (host.startsWith("*.")) {
102
+ const suffix = host.slice(2);
103
+ if (hostname3.endsWith(`.${suffix}`)) {
104
+ return true;
105
+ }
106
+ continue;
107
+ }
108
+ if (host.startsWith(".")) {
109
+ const suffix = host.slice(1);
110
+ if (hostname3 === suffix || hostname3.endsWith(host)) {
111
+ return true;
112
+ }
113
+ continue;
114
+ }
115
+ if (hostname3 === host || hostname3.endsWith(`.${host}`)) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
120
+ }
121
+ var EnvProxyDispatcher = class extends import_undici.Dispatcher {
122
+ directAgent = new import_undici.Agent();
123
+ httpProxyAgent;
124
+ httpsProxyAgent;
125
+ noProxy;
126
+ constructor(options) {
127
+ super();
128
+ this.httpProxyAgent = options.httpProxy ? new import_undici.ProxyAgent(options.httpProxy) : null;
129
+ this.httpsProxyAgent = options.httpsProxy ? new import_undici.ProxyAgent(options.httpsProxy) : this.httpProxyAgent;
130
+ this.noProxy = options.noProxy;
131
+ }
132
+ dispatch(options, handler) {
133
+ const origin = new URL(String(options.origin));
134
+ if (this.noProxy && noProxyMatches(origin, this.noProxy)) {
135
+ return this.directAgent.dispatch(options, handler);
136
+ }
137
+ const proxyAgent = origin.protocol === "https:" ? this.httpsProxyAgent : this.httpProxyAgent;
138
+ return (proxyAgent ?? this.directAgent).dispatch(options, handler);
139
+ }
140
+ close(callback) {
141
+ const promise = Promise.all([
142
+ this.directAgent.close(),
143
+ this.httpProxyAgent?.close(),
144
+ this.httpsProxyAgent && this.httpsProxyAgent !== this.httpProxyAgent ? this.httpsProxyAgent.close() : void 0
145
+ ]).then(() => void 0);
146
+ if (callback) {
147
+ promise.then(callback, callback);
148
+ return;
149
+ }
150
+ return promise;
151
+ }
152
+ destroy(errorOrCallback, callback) {
153
+ const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
154
+ const done = typeof errorOrCallback === "function" ? errorOrCallback : callback;
155
+ const promise = Promise.all([
156
+ this.directAgent.destroy(error),
157
+ this.httpProxyAgent?.destroy(error),
158
+ this.httpsProxyAgent && this.httpsProxyAgent !== this.httpProxyAgent ? this.httpsProxyAgent.destroy(error) : void 0
159
+ ]).then(() => void 0);
160
+ if (done) {
161
+ promise.then(done, done);
162
+ return;
163
+ }
164
+ return promise;
165
+ }
166
+ };
167
+ function configureProxyFromEnv() {
168
+ const httpProxy = firstProxyEnv("HTTP_PROXY", "http_proxy");
169
+ const httpsProxy = firstProxyEnv("HTTPS_PROXY", "https_proxy") || httpProxy;
170
+ if (!httpProxy && !httpsProxy) {
171
+ return;
172
+ }
173
+ (0, import_undici.setGlobalDispatcher)(
174
+ new EnvProxyDispatcher({
175
+ httpProxy,
176
+ httpsProxy,
177
+ noProxy: firstProxyEnv("NO_PROXY", "no_proxy")
178
+ })
179
+ );
180
+ }
181
+ configureProxyFromEnv();
182
+
26
183
  // src/cli/index.ts
27
184
  var import_promises7 = require("fs/promises");
28
- var import_node_path20 = require("path");
29
- var import_node_os12 = require("os");
185
+ var import_node_path21 = require("path");
186
+ var import_node_os14 = require("os");
30
187
  var import_commander3 = require("commander");
31
188
 
32
189
  // src/config.ts
@@ -246,10 +403,10 @@ var SDK_RELEASE = {
246
403
  // skill on the sdk sync surface, and the people-search-to-email prebuilt.
247
404
  // 0.1.108 ships explicit dataset column/tool recompute policy and removes
248
405
  // the SDK enrich generator's one-second stale policy.
249
- version: "0.1.109",
406
+ version: "0.1.110",
250
407
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
251
408
  supportPolicy: {
252
- latest: "0.1.109",
409
+ latest: "0.1.110",
253
410
  minimumSupported: "0.1.53",
254
411
  deprecatedBelow: "0.1.53",
255
412
  commandMinimumSupported: [
@@ -276,6 +433,7 @@ var SYNTHETIC_RUN_HEADER = "x-deepline-synthetic-run";
276
433
 
277
434
  // src/http.ts
278
435
  var MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
436
+ var COWORK_NETWORK_HINT = "Claude Cowork appears to be running Deepline in a network-restricted sandbox. In Claude Desktop, open Settings > Capabilities, turn on Allow network egress, and set Domain allowlist to All domains for the Cowork session.";
279
437
  var HttpClient = class {
280
438
  constructor(config) {
281
439
  this.config = config;
@@ -311,6 +469,7 @@ var HttpClient = class {
311
469
  "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
312
470
  "X-Deepline-Client-Family": "sdk",
313
471
  "X-Deepline-CLI-Family": "sdk",
472
+ "X-Deepline-Agent-Runtime": detectAgentRuntime(),
314
473
  "X-Deepline-CLI-Version": SDK_VERSION,
315
474
  "X-Deepline-SDK-Version": SDK_VERSION,
316
475
  "X-Deepline-API-Contract": SDK_API_CONTRACT,
@@ -413,12 +572,13 @@ var HttpClient = class {
413
572
  parsed = body;
414
573
  }
415
574
  if (!response.ok) {
575
+ const retryableApiError = options?.retryApiErrors === true && isRetryableApiErrorResponse(response.status);
416
576
  const htmlError = detectHtmlErrorBody(
417
577
  body,
418
578
  response.headers.get("content-type")
419
579
  );
420
580
  if (htmlError) {
421
- throw new DeeplineError(
581
+ lastError = new DeeplineError(
422
582
  htmlError.message(response.status),
423
583
  response.status,
424
584
  "API_ERROR",
@@ -428,12 +588,23 @@ var HttpClient = class {
428
588
  ...htmlError.workerThrewException ? { workerThrewException: true } : {}
429
589
  }
430
590
  );
591
+ if (retryableApiError && attempt < this.config.maxRetries) {
592
+ retryAfterDelayMs = parseOptionalRetryAfter(response);
593
+ break;
594
+ }
595
+ throw lastError;
431
596
  }
432
597
  const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
433
598
  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}`;
434
- throw new DeeplineError(msg, response.status, "API_ERROR", {
599
+ const apiErrorCode = errorValue && typeof errorValue === "object" && typeof errorValue.code === "string" ? errorValue.code : "API_ERROR";
600
+ lastError = new DeeplineError(msg, response.status, apiErrorCode, {
435
601
  response: parsed
436
602
  });
603
+ if (retryableApiError && attempt < this.config.maxRetries) {
604
+ retryAfterDelayMs = parseOptionalRetryAfter(response);
605
+ break;
606
+ }
607
+ throw lastError;
437
608
  }
438
609
  return parsed;
439
610
  } catch (error) {
@@ -453,8 +624,8 @@ var HttpClient = class {
453
624
  if (lastError instanceof DeeplineError) {
454
625
  throw lastError;
455
626
  }
456
- const errorMessage3 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
457
- throw new DeeplineError(errorMessage3);
627
+ const errorMessage4 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
628
+ throw new DeeplineError(withCoworkNetworkHint(errorMessage4));
458
629
  }
459
630
  /**
460
631
  * Send a GET request.
@@ -526,7 +697,9 @@ var HttpClient = class {
526
697
  }
527
698
  }
528
699
  throw new DeeplineError(
529
- lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
700
+ withCoworkNetworkHint(
701
+ lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
702
+ )
530
703
  );
531
704
  }
532
705
  /**
@@ -536,11 +709,12 @@ var HttpClient = class {
536
709
  * @param path - API path
537
710
  * @param body - Request body (will be JSON-serialized)
538
711
  */
539
- async post(path, body, headers) {
712
+ async post(path, body, headers, options) {
540
713
  return this.request(path, {
541
714
  method: "POST",
542
715
  body,
543
- headers
716
+ headers,
717
+ ...options
544
718
  });
545
719
  }
546
720
  async postFormData(path, formData, headers) {
@@ -581,6 +755,9 @@ function parseResponseBody(body) {
581
755
  return body;
582
756
  }
583
757
  }
758
+ function isRetryableApiErrorResponse(status) {
759
+ return status === 408 || status === 425 || status >= 500 && status < 600;
760
+ }
584
761
  function detectHtmlErrorBody(body, contentType) {
585
762
  const trimmed = body.trim();
586
763
  const lower = trimmed.toLowerCase();
@@ -620,6 +797,9 @@ function apiErrorMessage(parsed, status) {
620
797
  return `HTTP ${status}`;
621
798
  }
622
799
  function parseRetryAfter(response) {
800
+ return parseOptionalRetryAfter(response) ?? 5e3;
801
+ }
802
+ function parseOptionalRetryAfter(response) {
623
803
  const header = response.headers.get("retry-after");
624
804
  if (header) {
625
805
  const seconds = Number(header);
@@ -627,7 +807,7 @@ function parseRetryAfter(response) {
627
807
  return seconds * 1e3;
628
808
  }
629
809
  }
630
- return 5e3;
810
+ return null;
631
811
  }
632
812
  function buildCandidateUrls(url) {
633
813
  try {
@@ -684,6 +864,39 @@ function decodeSseFrame(frame) {
684
864
  function sleep(ms) {
685
865
  return new Promise((resolve16) => setTimeout(resolve16, ms));
686
866
  }
867
+ function isTruthyEnv(name) {
868
+ const normalized = process.env[name]?.trim().toLowerCase();
869
+ return ["1", "true", "yes", "on"].includes(normalized ?? "");
870
+ }
871
+ function isCoworkLikeSandbox() {
872
+ const pluginMode = isTruthyEnv("DEEPLINE_PLUGIN_MODE");
873
+ const claudeRemote = isTruthyEnv("CLAUDE_CODE_REMOTE");
874
+ const projectDir = Boolean(process.env.CLAUDE_PROJECT_DIR?.trim());
875
+ const pluginRoot = Boolean(process.env.DEEPLINE_PLUGIN_ROOT?.trim());
876
+ const home = process.env.HOME?.trim() ?? "";
877
+ const sessionHome = home.startsWith("/sessions/");
878
+ return (pluginMode || pluginRoot) && (claudeRemote || projectDir || sessionHome);
879
+ }
880
+ function detectAgentRuntime() {
881
+ if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
882
+ if (isCoworkLikeSandbox()) return "claude_cowork";
883
+ if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
884
+ if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
885
+ if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
886
+ return "cursor";
887
+ }
888
+ if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
889
+ return "windsurf";
890
+ }
891
+ return "unknown";
892
+ }
893
+ function withCoworkNetworkHint(message) {
894
+ if (!isCoworkLikeSandbox() || message.includes(COWORK_NETWORK_HINT)) {
895
+ return message;
896
+ }
897
+ return `${message}
898
+ ${COWORK_NETWORK_HINT}`;
899
+ }
687
900
 
688
901
  // src/stream-reconnect.ts
689
902
  var STREAM_RECONNECT_BASE_DELAY_MS = 500;
@@ -1960,12 +2173,12 @@ var DeeplineClient = class {
1960
2173
  * Returns everything from {@link ToolDefinition} plus pricing info, sample
1961
2174
  * inputs/outputs, failure modes, and cost estimates.
1962
2175
  *
1963
- * @param toolId - Tool identifier (e.g. `"apollo_people_search"`)
2176
+ * @param toolId - Tool identifier (e.g. `"dropleads_search_people"`)
1964
2177
  * @returns Full tool metadata
1965
2178
  *
1966
2179
  * @example
1967
2180
  * ```typescript
1968
- * const meta = await client.getTool('apollo_people_search');
2181
+ * const meta = await client.getTool('dropleads_search_people');
1969
2182
  * console.log(`Cost: ${meta.estimatedCreditsRange} credits`);
1970
2183
  * console.log(`Input schema:`, meta.inputSchema);
1971
2184
  * ```
@@ -1997,7 +2210,8 @@ var DeeplineClient = class {
1997
2210
  return this.http.post(
1998
2211
  `/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
1999
2212
  { payload: input2 },
2000
- headers
2213
+ headers,
2214
+ { forbiddenAsApiError: true }
2001
2215
  );
2002
2216
  }
2003
2217
  /**
@@ -2079,7 +2293,7 @@ var DeeplineClient = class {
2079
2293
  ...request.force ? { force: true } : {},
2080
2294
  ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
2081
2295
  // Profile selection is the API's job, not the CLI's. The server
2082
- // hardcodes workers_edge as the default; tests that want a
2296
+ // defaults to workers_edge; tests and runtime probes that want a
2083
2297
  // different profile pass `request.profile` explicitly.
2084
2298
  ...request.profile ? { profile: request.profile } : {}
2085
2299
  }
@@ -2144,10 +2358,15 @@ var DeeplineClient = class {
2144
2358
  sourceFiles: input2.sourceFiles,
2145
2359
  artifact: input2.artifact
2146
2360
  });
2147
- return this.http.post("/api/v2/plays/artifacts", {
2148
- ...input2,
2149
- compilerManifest
2150
- });
2361
+ return this.http.post(
2362
+ "/api/v2/plays/artifacts",
2363
+ {
2364
+ ...input2,
2365
+ compilerManifest
2366
+ },
2367
+ void 0,
2368
+ { forbiddenAsApiError: true, retryApiErrors: true }
2369
+ );
2151
2370
  }
2152
2371
  /**
2153
2372
  * Register multiple bundled play artifacts in one request.
@@ -2157,7 +2376,12 @@ var DeeplineClient = class {
2157
2376
  */
2158
2377
  async registerPlayArtifacts(artifacts) {
2159
2378
  if (artifacts.length === 0) {
2160
- return this.http.post("/api/v2/plays/artifacts", { artifacts });
2379
+ return this.http.post(
2380
+ "/api/v2/plays/artifacts",
2381
+ { artifacts },
2382
+ void 0,
2383
+ { forbiddenAsApiError: true, retryApiErrors: true }
2384
+ );
2161
2385
  }
2162
2386
  const compiledArtifacts = await mapWithConcurrency(
2163
2387
  artifacts,
@@ -2175,9 +2399,14 @@ var DeeplineClient = class {
2175
2399
  const responses = [];
2176
2400
  for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
2177
2401
  responses.push(
2178
- await this.http.post("/api/v2/plays/artifacts", {
2179
- artifacts: chunk
2180
- })
2402
+ await this.http.post(
2403
+ "/api/v2/plays/artifacts",
2404
+ {
2405
+ artifacts: chunk
2406
+ },
2407
+ void 0,
2408
+ { forbiddenAsApiError: true, retryApiErrors: true }
2409
+ )
2181
2410
  );
2182
2411
  }
2183
2412
  return {
@@ -2982,7 +3211,9 @@ var DeeplineClient = class {
2982
3211
  const encodedName = encodeURIComponent(name);
2983
3212
  return this.http.post(
2984
3213
  `/api/v2/plays/${encodedName}/live`,
2985
- request
3214
+ request,
3215
+ void 0,
3216
+ { forbiddenAsApiError: true }
2986
3217
  );
2987
3218
  }
2988
3219
  /**
@@ -3553,11 +3784,53 @@ function sleep3(ms) {
3553
3784
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
3554
3785
  }
3555
3786
  function collectLocalEnvInfo() {
3556
- return {
3787
+ const info = {
3557
3788
  os: `${process.platform} ${process.arch}`,
3558
3789
  node_version: process.version,
3559
- home_dir: (0, import_node_os3.homedir)()
3790
+ home_dir: (0, import_node_os3.homedir)(),
3791
+ agent_runtime: detectAgentRuntime2()
3560
3792
  };
3793
+ const env = process.env;
3794
+ const optional = {
3795
+ claude_code_remote: env.CLAUDE_CODE_REMOTE,
3796
+ deepline_plugin_mode: env.DEEPLINE_PLUGIN_MODE
3797
+ };
3798
+ for (const [key, value] of Object.entries(optional)) {
3799
+ if (value?.trim()) info[key] = value.trim();
3800
+ }
3801
+ if (env.CLAUDE_PROJECT_DIR?.trim()) {
3802
+ info.claude_project_dir_present = "true";
3803
+ }
3804
+ if (env.DEEPLINE_PLUGIN_ROOT?.trim()) {
3805
+ info.deepline_plugin_root_present = "true";
3806
+ }
3807
+ if (env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim()) {
3808
+ info.deepline_plugin_skills_dir_present = "true";
3809
+ }
3810
+ if (info.home_dir.startsWith("/sessions/")) {
3811
+ info.home_scope = "sessions";
3812
+ }
3813
+ return info;
3814
+ }
3815
+ function detectAgentRuntime2() {
3816
+ if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
3817
+ const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
3818
+ const claudeRemote = process.env.CLAUDE_CODE_REMOTE?.trim().toLowerCase();
3819
+ const sessionHome = (process.env.HOME?.trim() || (0, import_node_os3.homedir)()).startsWith(
3820
+ "/sessions/"
3821
+ );
3822
+ if (["1", "true", "yes", "on"].includes(pluginMode ?? "") && (["1", "true", "yes", "on"].includes(claudeRemote ?? "") || Boolean(process.env.CLAUDE_PROJECT_DIR?.trim()) || sessionHome)) {
3823
+ return "claude_cowork";
3824
+ }
3825
+ if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
3826
+ if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
3827
+ if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
3828
+ return "cursor";
3829
+ }
3830
+ if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
3831
+ return "windsurf";
3832
+ }
3833
+ return "unknown";
3561
3834
  }
3562
3835
  function readCsvRows(csvPath) {
3563
3836
  const raw = (0, import_node_fs3.readFileSync)((0, import_node_path3.resolve)(csvPath), "utf-8");
@@ -3825,7 +4098,14 @@ function saveEnvValues(values, baseUrl) {
3825
4098
  }
3826
4099
  async function httpJson(method, url, apiKey, body) {
3827
4100
  const headers = {
3828
- "Content-Type": "application/json"
4101
+ "Content-Type": "application/json",
4102
+ "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
4103
+ "X-Deepline-Client-Family": "sdk",
4104
+ "X-Deepline-CLI-Family": "sdk",
4105
+ "X-Deepline-Agent-Runtime": detectAgentRuntime2(),
4106
+ "X-Deepline-CLI-Version": SDK_VERSION,
4107
+ "X-Deepline-SDK-Version": SDK_VERSION,
4108
+ "X-Deepline-API-Contract": SDK_API_CONTRACT
3829
4109
  };
3830
4110
  if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
3831
4111
  let response = null;
@@ -4683,7 +4963,7 @@ async function handlePlans(options) {
4683
4963
  );
4684
4964
  const activePlan = payload.active_plan ?? {};
4685
4965
  const plans = Array.isArray(payload.plans) ? payload.plans : [];
4686
- const metrics = Array.isArray(payload.metrics) ? payload.metrics : [];
4966
+ const meters = Array.isArray(payload.meters) ? payload.meters : Array.isArray(payload.metrics) ? payload.metrics : [];
4687
4967
  const lines = [
4688
4968
  `Catalog: ${payload.catalog_id ?? "(unknown)"} (version ${payload.catalog_version ?? "(unknown)"})`,
4689
4969
  `Active plan: ${activePlan.public_name ?? "(unknown)"} (${activePlan.plan_version_id ?? "(unknown)"})`,
@@ -4694,10 +4974,10 @@ async function handlePlans(options) {
4694
4974
  (plan) => `${plan.public_name ?? "(unknown)"} (${plan.plan_version_id ?? "(unknown)"}) | ${planPriceText(plan.price_usd, plan.price_interval)} | ${plan.monthly_grant_credits ?? 0} credits/cycle | ${planRolloverText(plan.rollover)} | ${plan.acquirable ? "acquirable" : "not acquirable"}`
4695
4975
  )
4696
4976
  ],
4697
- ...metrics.length === 0 ? ["Metrics: none"] : [
4977
+ ...meters.length === 0 ? ["Metrics: none"] : [
4698
4978
  "Metrics:",
4699
- ...metrics.map(
4700
- (metric) => `${metric.id ?? "(unknown)"} | ${metric.name ?? "(unknown)"}`
4979
+ ...meters.map(
4980
+ (meter) => `${meter.id ?? "(unknown)"} | ${meter.name ?? "(unknown)"}`
4701
4981
  )
4702
4982
  ]
4703
4983
  ];
@@ -7133,17 +7413,58 @@ async function analyzeSourceGraph(entryFile, adapter) {
7133
7413
  }
7134
7414
  async function computeWorkersHarnessFingerprintWithAdapter(adapter) {
7135
7415
  const { readdir } = await import("fs/promises");
7416
+ const addFilePart = async (parts2, rootDir, filePath) => {
7417
+ const contents = await (0, import_promises3.readFile)(filePath, "utf-8");
7418
+ parts2.push({
7419
+ name: `${(0, import_node_path8.basename)(rootDir)}:${filePath.slice(rootDir.length + 1)}`,
7420
+ hash: sha256(contents)
7421
+ });
7422
+ };
7423
+ const collectTopLevelTsFiles = async (rootDir, parts2) => {
7424
+ if (!await fileExists(rootDir)) return;
7425
+ const entries2 = await readdir(rootDir, { withFileTypes: true });
7426
+ const tsFiles2 = entries2.filter((entry) => entry.isFile() && /\.[cm]?ts$/.test(entry.name)).map((entry) => entry.name).sort();
7427
+ for (const name of tsFiles2) {
7428
+ await addFilePart(parts2, rootDir, (0, import_node_path8.join)(rootDir, name));
7429
+ }
7430
+ };
7431
+ const collectIntegrationBatchingFiles = async (rootDir, parts2) => {
7432
+ if (!await fileExists(rootDir)) return;
7433
+ const entries2 = await readdir(rootDir, { withFileTypes: true });
7434
+ const filePaths = [];
7435
+ for (const entry of entries2) {
7436
+ if (entry.isFile() && (entry.name === "play-runtime-batching-registry.ts" || /^batching.*\.ts$/.test(entry.name))) {
7437
+ filePaths.push((0, import_node_path8.join)(rootDir, entry.name));
7438
+ }
7439
+ if (entry.isDirectory()) {
7440
+ const batchingFile = (0, import_node_path8.join)(rootDir, entry.name, "batching.ts");
7441
+ if (await fileExists(batchingFile)) {
7442
+ filePaths.push(batchingFile);
7443
+ }
7444
+ }
7445
+ }
7446
+ for (const filePath of filePaths.sort()) {
7447
+ await addFilePart(parts2, rootDir, filePath);
7448
+ }
7449
+ };
7136
7450
  const entries = await readdir(adapter.workersHarnessFilesDir, {
7137
7451
  withFileTypes: true
7138
7452
  });
7139
7453
  const tsFiles = entries.filter((e) => e.isFile() && /\.[cm]?ts$/.test(e.name)).map((e) => e.name).sort();
7140
7454
  const parts = [];
7141
7455
  for (const name of tsFiles) {
7142
- const contents = await (0, import_promises3.readFile)(
7143
- (0, import_node_path8.join)(adapter.workersHarnessFilesDir, name),
7144
- "utf-8"
7456
+ await addFilePart(
7457
+ parts,
7458
+ adapter.workersHarnessFilesDir,
7459
+ (0, import_node_path8.join)(adapter.workersHarnessFilesDir, name)
7145
7460
  );
7146
- parts.push({ name, hash: sha256(contents) });
7461
+ }
7462
+ for (const dir of adapter.workersRuntimeFingerprintDirs ?? []) {
7463
+ if ((0, import_node_path8.basename)(dir) === "integrations") {
7464
+ await collectIntegrationBatchingFiles(dir, parts);
7465
+ } else {
7466
+ await collectTopLevelTsFiles(dir, parts);
7467
+ }
7147
7468
  }
7148
7469
  return sha256(JSON.stringify(parts));
7149
7470
  }
@@ -8000,6 +8321,9 @@ function createSdkPlayBundlingAdapter() {
8000
8321
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
8001
8322
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
8002
8323
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
8324
+ workersRuntimeFingerprintDirs: [
8325
+ (0, import_node_path10.resolve)(PROJECT_ROOT, "shared_libs", "play-runtime")
8326
+ ],
8003
8327
  discoverPackagedLocalFiles,
8004
8328
  warnAboutNonDevelopmentBundling
8005
8329
  };
@@ -8010,7 +8334,8 @@ async function bundlePlayFile2(filePath, options = {}) {
8010
8334
  exportName: options.exportName,
8011
8335
  adapter: createSdkPlayBundlingAdapter()
8012
8336
  });
8013
- if (result.success) validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
8337
+ if (result.success)
8338
+ validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
8014
8339
  return result;
8015
8340
  }
8016
8341
 
@@ -8192,17 +8517,17 @@ function templateExample(template) {
8192
8517
  case "people-list":
8193
8518
  return "deepline plays bootstrap people-list --from provider:dropleads_search_people --out people.play.ts";
8194
8519
  case "company-list":
8195
- return "deepline plays bootstrap company-list --from provider:apollo_company_search --out companies.play.ts";
8520
+ return "deepline plays bootstrap company-list --from provider:crustdata_companydb_search --out companies.play.ts";
8196
8521
  case "people-email":
8197
8522
  return "deepline plays bootstrap people-email --from csv:data/leads.csv --using play:prebuilt/name-and-domain-to-email-waterfall --out email-flow.play.ts";
8198
8523
  case "people-phone":
8199
8524
  return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone --out phone-flow.play.ts";
8200
8525
  case "company-people":
8201
- return "deepline plays bootstrap company-people --from provider:apollo_company_search --using play:prebuilt/company-to-contact --out company-people.play.ts";
8526
+ return "deepline plays bootstrap company-people --from provider:crustdata_companydb_search --using play:prebuilt/company-to-contact --out company-people.play.ts";
8202
8527
  case "company-people-email":
8203
- return "deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email providers:hunter_email_finder,leadmagic_email_finder --out account-emails.play.ts";
8528
+ return "deepline plays bootstrap company-people-email --from provider:crustdata_companydb_search --people play:prebuilt/company-to-contact --email providers:hunter_email_finder,leadmagic_email_finder --out account-emails.play.ts";
8204
8529
  case "company-people-phone":
8205
- return "deepline plays bootstrap company-people-phone --from provider:apollo_company_search --people play:prebuilt/company-to-contact --phone providers:ai_ark_mobile_phone_finder --out account-phones.play.ts";
8530
+ return "deepline plays bootstrap company-people-phone --from provider:crustdata_companydb_search --people play:prebuilt/company-to-contact --phone providers:ai_ark_mobile_phone_finder --out account-phones.play.ts";
8206
8531
  }
8207
8532
  }
8208
8533
  var PLAY_BOOTSTRAP_STAGE_NAMES = [
@@ -9504,8 +9829,8 @@ Examples:
9504
9829
  deepline plays run email-flow.play.ts --input '{"limit":5}' --watch
9505
9830
 
9506
9831
  deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 --out prospecting.play.ts
9507
- deepline plays bootstrap company-people-email --from provider:apollo_company_search --people play:prebuilt/company-to-contact --email play:prebuilt/name-and-domain-to-email-waterfall --limit 5 --out account-contacts.play.ts
9508
- deepline plays bootstrap company-list --from provider:apollo_company_search --limit 5 --out companies.play.ts
9832
+ deepline plays bootstrap company-people-email --from provider:crustdata_companydb_search --people play:prebuilt/company-to-contact --email play:prebuilt/name-and-domain-to-email-waterfall --limit 5 --out account-contacts.play.ts
9833
+ deepline plays bootstrap company-list --from provider:crustdata_companydb_search --limit 5 --out companies.play.ts
9509
9834
  `
9510
9835
  ).option("--name <name>", "Generated play name").option(
9511
9836
  "--from <ref>",
@@ -9684,11 +10009,11 @@ function createCliProgress(enabled) {
9684
10009
 
9685
10010
  // src/cli/trace.ts
9686
10011
  var cliTraceStartedAt = Date.now();
9687
- function isTruthyEnv(value) {
10012
+ function isTruthyEnv2(value) {
9688
10013
  return value === "1" || value === "true" || value === "yes";
9689
10014
  }
9690
10015
  function isCliTraceEnabled() {
9691
- return isTruthyEnv(process.env.DEEPLINE_CLI_TRACE);
10016
+ return isTruthyEnv2(process.env.DEEPLINE_CLI_TRACE);
9692
10017
  }
9693
10018
  function recordCliTrace(event) {
9694
10019
  if (!isCliTraceEnabled()) {
@@ -12808,7 +13133,7 @@ function writeStartedPlayRun(input2) {
12808
13133
  );
12809
13134
  }
12810
13135
  function parsePlayRunOptions(args) {
12811
- const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n --profile defaults to workers_edge; hatchet is the durable Daytona-backed preview runtime.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
13136
+ const usage = "Usage: deepline plays run <play-name> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run <play-file.ts> [--input '{...}'] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --file <play-file.ts> [--input '{...}'] [--profile <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--full] [--<input> value]\n deepline plays run --name <name> [--input '{...}'] [--profile <id>] [--live|--latest|--revision-id <id>] [--no-wait] [--tail-timeout-ms 30000] [--force] [--no-open] [--json] [--full] [--<input> value]\n --profile defaults to workers_edge; pass hatchet explicitly for Hatchet runtime comparison.\n Unknown --<input> value flags, such as --limit 5, are passed into play input.\nRun `deepline plays run --help` for idempotency, tool call id, and ctx.dataset guidance.";
12812
13137
  let filePath = null;
12813
13138
  let playName = null;
12814
13139
  let input2 = null;
@@ -14147,7 +14472,7 @@ function playRunCommand(play, options) {
14147
14472
  return `deepline plays run ${target} --input '{...}' --watch`;
14148
14473
  }
14149
14474
  function summarizePlayListItemForCli(play, options) {
14150
- const aliases = play.aliases?.length ? play.aliases : [play.name];
14475
+ const aliases = play.aliases ?? [];
14151
14476
  const csvInput = playSchemaMetadata(play.inputSchema, "csvInput");
14152
14477
  const rowOutputSchema = playSchemaMetadata(
14153
14478
  play.outputSchema,
@@ -14602,8 +14927,8 @@ Idempotent execution:
14602
14927
  .dataset('companies_v1', companies)
14603
14928
  .withColumn('cto', (row, ctx) => ctx.tools.execute({
14604
14929
  id: 'find_cto',
14605
- tool: 'apollo_search_people_with_match',
14606
- input: { q_organization_domains_list: [row.domain], per_page: 1 },
14930
+ tool: 'dropleads_search_people',
14931
+ input: { filters: { companyDomains: [row.domain], jobTitles: ['CTO'] }, pagination: { page: 1, limit: 1 } },
14607
14932
  }))
14608
14933
  .run({ key: 'domain' });
14609
14934
 
@@ -14749,14 +15074,12 @@ Examples:
14749
15074
  ]);
14750
15075
  });
14751
15076
  addPlaySearchCommand(play.command("search <query>"));
14752
- play.command("grep <query>").description(
14753
- "Literal grep over play names, aliases, schemas, and descriptions."
14754
- ).addHelpText(
15077
+ play.command("grep <query>").description("Literal grep over play names, schemas, and descriptions.").addHelpText(
14755
15078
  "after",
14756
15079
  `
14757
15080
  Notes:
14758
15081
  Literal registry filtering. Terms are matched case-insensitively against play
14759
- names, references, display names, aliases, ownership, and schemas. Use
15082
+ names, references, display names, ownership, and schemas. Use
14760
15083
  --mode phrase for exact phrase matching, --mode any for OR, and the default
14761
15084
  --mode all for AND.
14762
15085
 
@@ -14777,7 +15100,7 @@ Examples:
14777
15100
  "after",
14778
15101
  `
14779
15102
  Notes:
14780
- Compact contract read for schemas, aliases, examples, ownership, and the run command.
15103
+ Compact contract read for schemas, examples, ownership, and the run command.
14781
15104
  Use \`get\` when you need full metadata, revisions, source fields, or latest runs.
14782
15105
 
14783
15106
  Examples:
@@ -16801,7 +17124,8 @@ async function buildPlanArgs(args) {
16801
17124
  "--name",
16802
17125
  "--rows",
16803
17126
  "--with-force",
16804
- "--timeout"
17127
+ "--timeout",
17128
+ "--profile"
16805
17129
  ]);
16806
17130
  const localBooleanOptions = /* @__PURE__ */ new Set([
16807
17131
  "--dry-run",
@@ -17941,6 +18265,9 @@ function registerEnrichCommand(program) {
17941
18265
  ).option("--in-place", "Write enriched output back to the input CSV.").option(
17942
18266
  "--timeout <SECONDS>",
17943
18267
  "API read timeout for enrich status/API requests."
18268
+ ).option(
18269
+ "--profile <id>",
18270
+ "Internal/testing: override the execution profile for the generated play run."
17944
18271
  ).action(async (options, _command) => {
17945
18272
  if (options.inPlace && options.dryRun) {
17946
18273
  throw new Error("--in-place is not supported with --dry-run.");
@@ -18011,6 +18338,9 @@ function registerEnrichCommand(program) {
18011
18338
  JSON.stringify(runtimeInput),
18012
18339
  "--watch"
18013
18340
  ];
18341
+ if (options.profile) {
18342
+ runArgs.push("--profile", options.profile);
18343
+ }
18014
18344
  if (options.noOpen) {
18015
18345
  runArgs.push("--no-open");
18016
18346
  }
@@ -18157,308 +18487,987 @@ Examples:
18157
18487
  ).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);
18158
18488
  }
18159
18489
 
18160
- // src/cli/commands/legacy-noops.ts
18161
- var SESSION_SUBCOMMANDS = [
18162
- "start",
18163
- "status",
18164
- "output",
18165
- "alert",
18166
- "usage",
18167
- "limit",
18168
- "render",
18169
- "send"
18170
- ];
18171
- var BACKEND_SUBCOMMANDS = [
18172
- "start",
18173
- "stop",
18174
- "status",
18175
- "refresh-runtime",
18176
- "sync-runtime"
18177
- ];
18178
- function legacyNoopEnvelope(input2) {
18179
- const command = [
18180
- "deepline",
18181
- input2.family,
18182
- input2.subcommand
18183
- ].filter(Boolean).join(" ");
18184
- const subject = input2.family === "session" ? "Legacy Session UI/playground command" : "Legacy local playground backend command";
18185
- const note = input2.family === "session" ? "The SDK CLI does not manage the legacy Python Session UI. This command is accepted for backwards compatibility and intentionally does nothing." : "The SDK CLI does not start or stop the legacy Python local playground backend. This command is accepted for backwards compatibility and intentionally does nothing.";
18490
+ // src/cli/commands/sessions.ts
18491
+ var import_node_fs11 = require("fs");
18492
+ var import_node_os8 = require("os");
18493
+ var import_node_path14 = require("path");
18494
+ var import_node_zlib = require("zlib");
18495
+ var import_node_crypto4 = require("crypto");
18496
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18497
+ var UUID_IN_TEXT_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
18498
+ var MAX_SESSION_UPLOAD_BYTES = 35e5;
18499
+ var MAX_DIRECT_SESSION_DECODED_BYTES = 50 * 1024 * 1024;
18500
+ var CHUNK_SIZE_BYTES = 25e5;
18501
+ var MAX_EVENT_STRING_CHARS = 8e3;
18502
+ var MAX_EVENT_LIST_ITEMS = 40;
18503
+ var MAX_EVENT_OBJECT_KEYS = 80;
18504
+ var TRUNCATION_MARKER = "...[truncated]";
18505
+ var NOISE_EVENT_TYPES = /* @__PURE__ */ new Set(["progress", "file-history-snapshot"]);
18506
+ function homeDir() {
18507
+ return process.env.HOME?.trim() || (0, import_node_os8.homedir)();
18508
+ }
18509
+ function detectShellContext() {
18510
+ const shellPath = process.env.SHELL?.trim() || process.env.ComSpec?.trim() || process.env.COMSPEC?.trim() || "";
18186
18511
  return {
18187
- ok: true,
18188
- noop: true,
18189
- command,
18190
- compatibility: {
18191
- family: "legacy_python_cli",
18192
- sdk_behavior: "noop"
18193
- },
18194
- render: {
18195
- sections: [
18196
- {
18197
- title: subject,
18198
- lines: [note]
18199
- }
18200
- ]
18201
- }
18512
+ shell: shellPath ? (0, import_node_path14.basename)(shellPath).replace(/\.exe$/i, "") : "unknown",
18513
+ shell_path: shellPath || null,
18514
+ os: (0, import_node_os8.platform)(),
18515
+ cwd: process.cwd()
18202
18516
  };
18203
18517
  }
18204
- function printLegacyNoop(input2) {
18205
- printCommandEnvelope(legacyNoopEnvelope(input2), { json: input2.options.json });
18518
+ function claudeProjectsRoot() {
18519
+ return (0, import_node_path14.join)(homeDir(), ".claude", "projects");
18206
18520
  }
18207
- function legacySubcommandFromArgv(family) {
18208
- const args = process.argv.slice(2);
18209
- const familyIndex = args.indexOf(family);
18210
- const nextToken = familyIndex >= 0 ? args[familyIndex + 1] : void 0;
18211
- return nextToken && !nextToken.startsWith("-") ? nextToken : void 0;
18521
+ function codexSessionsRoot() {
18522
+ return (0, import_node_path14.join)(homeDir(), ".codex", "sessions");
18212
18523
  }
18213
- function addLegacyNoopSubcommand(parent, family, subcommand, description) {
18214
- parent.command(subcommand).description(description).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").action((_args, options) => {
18215
- printLegacyNoop({ family, subcommand, options });
18216
- });
18524
+ function listClaudeSessionFiles() {
18525
+ const root = claudeProjectsRoot();
18526
+ if (!(0, import_node_fs11.existsSync)(root)) return [];
18527
+ const projectDirs = readDirectoryNames(root);
18528
+ const files = [];
18529
+ for (const projectDir of projectDirs) {
18530
+ const fullProjectDir = (0, import_node_path14.join)(root, projectDir);
18531
+ for (const fileName of readDirectoryNames(fullProjectDir)) {
18532
+ if (fileName.endsWith(".jsonl")) {
18533
+ const filePath = (0, import_node_path14.join)(fullProjectDir, fileName);
18534
+ const sessionId = sessionIdFromClaudeFilePath(filePath);
18535
+ const stat4 = statIfReadable(filePath);
18536
+ if (sessionId && stat4) {
18537
+ files.push({
18538
+ agent: "claude",
18539
+ sessionId,
18540
+ filePath,
18541
+ mtimeMs: stat4.mtimeMs
18542
+ });
18543
+ }
18544
+ }
18545
+ }
18546
+ }
18547
+ return files;
18217
18548
  }
18218
- function registerLegacyNoopCommands(program) {
18219
- const session = program.command("session").description(
18220
- "Compatibility no-ops for legacy Python Session UI commands."
18221
- ).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
18222
- "after",
18223
- `
18224
- Notes:
18225
- The SDK CLI accepts singular session commands from older agent skills so they
18226
- do not fail, but it does not manage the legacy Python Session UI.
18227
- Use "deepline sessions send" or "deepline sessions render" for real SDK
18228
- transcript workflows.
18229
-
18230
- Examples:
18231
- deepline session start --steps '["Inspect CSV","Run pilot"]'
18232
- deepline session status --message "Running pilot"
18233
- deepline session output --csv ./results.csv --label "Results"
18234
- `
18235
- ).action((args, options) => {
18236
- void args;
18237
- printLegacyNoop({
18238
- family: "session",
18239
- subcommand: legacySubcommandFromArgv("session"),
18240
- options
18241
- });
18242
- });
18243
- for (const subcommand of SESSION_SUBCOMMANDS) {
18244
- addLegacyNoopSubcommand(
18245
- session,
18246
- "session",
18247
- subcommand,
18248
- `Accept legacy "deepline session ${subcommand}" as an SDK no-op.`
18249
- );
18250
- }
18251
- const backend = program.command("backend").description(
18252
- "Compatibility no-ops for legacy Python local backend commands."
18253
- ).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
18254
- "after",
18255
- `
18256
- Notes:
18257
- The SDK CLI uses the configured Deepline host and V2 play runtime. It does not
18258
- start, stop, or refresh the legacy Python local playground backend.
18259
-
18260
- Examples:
18261
- deepline backend start
18262
- deepline backend stop --just-backend
18263
- deepline backend status --json
18264
- `
18265
- ).action((args, options) => {
18266
- void args;
18267
- printLegacyNoop({
18268
- family: "backend",
18269
- subcommand: legacySubcommandFromArgv("backend"),
18270
- options
18271
- });
18272
- });
18273
- for (const subcommand of BACKEND_SUBCOMMANDS) {
18274
- addLegacyNoopSubcommand(
18275
- backend,
18276
- "backend",
18277
- subcommand,
18278
- `Accept legacy "deepline backend ${subcommand}" as an SDK no-op.`
18279
- );
18549
+ function listCodexSessionFiles() {
18550
+ const root = codexSessionsRoot();
18551
+ if (!(0, import_node_fs11.existsSync)(root)) return [];
18552
+ const files = [];
18553
+ for (const filePath of listJsonlFilesRecursive(root, 5)) {
18554
+ const stat4 = statIfReadable(filePath);
18555
+ if (!stat4) continue;
18556
+ const sessionId = sessionIdFromCodexFilePath(filePath) ?? readCodexSessionId(filePath);
18557
+ if (!sessionId) continue;
18558
+ files.push({ agent: "codex", sessionId, filePath, mtimeMs: stat4.mtimeMs });
18280
18559
  }
18560
+ return files;
18281
18561
  }
18282
-
18283
- // src/cli/commands/org.ts
18284
- async function fetchOrganizations(http, apiKey) {
18285
- return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
18562
+ function listSessionFiles(agent) {
18563
+ const files = [];
18564
+ if (agent === "auto" || agent === "claude") {
18565
+ files.push(...listClaudeSessionFiles());
18566
+ }
18567
+ if (agent === "auto" || agent === "codex") {
18568
+ files.push(...listCodexSessionFiles());
18569
+ }
18570
+ return files;
18286
18571
  }
18287
- function orgListLines(orgs) {
18288
- return orgs.map((org, index) => {
18289
- const current = org.is_current ? " (current)" : "";
18290
- const role = org.role ? ` [${org.role}]` : "";
18291
- return `${index + 1}. ${org.name}${role}${current}`;
18292
- });
18572
+ function readDirectoryNames(dir) {
18573
+ try {
18574
+ return (0, import_node_fs11.readdirSync)(dir);
18575
+ } catch {
18576
+ return [];
18577
+ }
18293
18578
  }
18294
- async function handleOrgList(options) {
18295
- const config = resolveConfig();
18296
- const http = new HttpClient(config);
18297
- const payload = await fetchOrganizations(http, config.apiKey);
18298
- printCommandEnvelope(
18299
- {
18300
- ...payload,
18301
- render: {
18302
- sections: [
18303
- {
18304
- title: "Your organizations:",
18305
- lines: orgListLines(payload.organizations)
18306
- }
18307
- ]
18579
+ function listJsonlFilesRecursive(root, maxDepth) {
18580
+ const files = [];
18581
+ function visit(dir, depth) {
18582
+ if (depth > maxDepth) return;
18583
+ let entries;
18584
+ try {
18585
+ entries = (0, import_node_fs11.readdirSync)(dir, { withFileTypes: true });
18586
+ } catch {
18587
+ return;
18588
+ }
18589
+ for (const entry of entries) {
18590
+ const fullPath = (0, import_node_path14.join)(dir, entry.name);
18591
+ if (entry.isDirectory()) {
18592
+ visit(fullPath, depth + 1);
18593
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
18594
+ files.push(fullPath);
18308
18595
  }
18309
- },
18310
- { json: options.json }
18311
- );
18596
+ }
18597
+ }
18598
+ visit(root, 0);
18599
+ return files;
18312
18600
  }
18313
- async function handleOrgSwitch(selection, options) {
18314
- const config = resolveConfig();
18315
- const http = new HttpClient(config);
18316
- const payload = await fetchOrganizations(http, config.apiKey);
18317
- if (!selection && !options.orgId) {
18318
- printCommandEnvelope(
18319
- {
18320
- ...payload,
18321
- next: { switch: "deepline org switch <number>" },
18322
- render: {
18323
- sections: [
18324
- {
18325
- title: "Your organizations:",
18326
- lines: orgListLines(payload.organizations)
18327
- }
18328
- ],
18329
- actions: [{ label: "Run", command: "deepline org switch <number>" }]
18330
- }
18331
- },
18332
- { json: options.json }
18601
+ function statIfReadable(filePath) {
18602
+ try {
18603
+ return (0, import_node_fs11.statSync)(filePath);
18604
+ } catch {
18605
+ return null;
18606
+ }
18607
+ }
18608
+ function newestSessionFile(agent) {
18609
+ let newest = null;
18610
+ for (const candidate of listSessionFiles(agent)) {
18611
+ if (!newest || candidate.mtimeMs > newest.mtimeMs) {
18612
+ newest = candidate;
18613
+ }
18614
+ }
18615
+ return newest;
18616
+ }
18617
+ function sessionIdFromClaudeFilePath(filePath) {
18618
+ return (0, import_node_path14.basename)(filePath, ".jsonl");
18619
+ }
18620
+ function sessionIdFromCodexFilePath(filePath) {
18621
+ const match = (0, import_node_path14.basename)(filePath, ".jsonl").match(UUID_IN_TEXT_RE);
18622
+ return match?.[0] ?? null;
18623
+ }
18624
+ function readCodexSessionId(filePath) {
18625
+ try {
18626
+ for (const line of normalizedJsonLines((0, import_node_fs11.readFileSync)(filePath)).slice(
18627
+ 0,
18628
+ 20
18629
+ )) {
18630
+ const parsed = parseJsonLine(line);
18631
+ if (!parsed || typeof parsed !== "object") continue;
18632
+ const record = parsed;
18633
+ const payload = record.payload && typeof record.payload === "object" ? record.payload : null;
18634
+ const id = record.type === "session_meta" && typeof payload?.id === "string" ? payload.id : null;
18635
+ if (id && UUID_RE.test(id)) return id;
18636
+ }
18637
+ } catch {
18638
+ return null;
18639
+ }
18640
+ return null;
18641
+ }
18642
+ function sessionSearchDescription(agent) {
18643
+ if (agent === "claude") return "~/.claude/projects/*/<session-id>.jsonl";
18644
+ if (agent === "codex") return "~/.codex/sessions/**/*.jsonl";
18645
+ return "~/.claude/projects/*/<session-id>.jsonl or ~/.codex/sessions/**/*.jsonl";
18646
+ }
18647
+ function parseSessionAgent(value) {
18648
+ const normalized = String(value || "auto").trim().toLowerCase();
18649
+ if (normalized === "auto" || normalized === "claude" || normalized === "codex") {
18650
+ return normalized;
18651
+ }
18652
+ throw new Error("Invalid --agent value. Expected auto, claude, or codex.");
18653
+ }
18654
+ function findSessionFile(sessionId, agent) {
18655
+ if (!UUID_RE.test(sessionId)) {
18656
+ throw new Error(
18657
+ "Invalid session ID format. Expected a UUID such as 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca."
18333
18658
  );
18334
- return;
18335
18659
  }
18336
- let target = payload.organizations.find(
18337
- (org) => org.org_id === options.orgId
18338
- );
18339
- if (!target && selection) {
18340
- const index = Number.parseInt(selection, 10);
18341
- if (Number.isFinite(index) && index >= 1 && index <= payload.organizations.length) {
18342
- target = payload.organizations[index - 1];
18343
- } else {
18344
- target = payload.organizations.find(
18345
- (org) => org.name === selection || org.org_id === selection
18660
+ for (const candidate of listSessionFiles(agent)) {
18661
+ if (candidate.sessionId === sessionId) {
18662
+ return candidate;
18663
+ }
18664
+ }
18665
+ return null;
18666
+ }
18667
+ function resolveSessionTargets(input2) {
18668
+ const agent = parseSessionAgent(input2.agent);
18669
+ const targets = [];
18670
+ if (input2.currentSession) {
18671
+ const currentFile = newestSessionFile(agent);
18672
+ if (!currentFile) {
18673
+ throw new Error(
18674
+ `No session files found in ${sessionSearchDescription(agent)}.`
18346
18675
  );
18347
18676
  }
18677
+ targets.push({
18678
+ sessionId: currentFile.sessionId,
18679
+ label: `${currentFile.agent}-session-${currentFile.sessionId}`,
18680
+ filePath: currentFile.filePath,
18681
+ agent: currentFile.agent
18682
+ });
18348
18683
  }
18349
- if (!target) {
18350
- throw new Error("Could not resolve the selected organization.");
18684
+ for (const [index, sessionId] of (input2.sessionIds ?? []).entries()) {
18685
+ const candidate = findSessionFile(sessionId, agent);
18686
+ if (!candidate) {
18687
+ throw new Error(
18688
+ `Session file not found in ${sessionSearchDescription(agent)}: ${sessionId}`
18689
+ );
18690
+ }
18691
+ targets.push({
18692
+ sessionId,
18693
+ label: input2.labels?.[index] ?? `${candidate.agent}-session-${sessionId}`,
18694
+ filePath: candidate.filePath,
18695
+ agent: candidate.agent
18696
+ });
18351
18697
  }
18352
- if (target.is_current) {
18353
- printCommandEnvelope(
18354
- {
18355
- ok: true,
18356
- unchanged: true,
18357
- organization: target,
18358
- render: {
18359
- sections: [
18360
- { title: "org switch", lines: [`Already on ${target.name}.`] }
18361
- ]
18362
- }
18363
- },
18364
- { json: options.json }
18365
- );
18366
- return;
18698
+ if (targets.length === 0) {
18699
+ throw new Error("One of --session-id or --current-session is required.");
18367
18700
  }
18368
- const switched = await http.post("/api/v2/auth/cli/switch", {
18369
- api_key: config.apiKey,
18370
- org_id: target.org_id
18371
- });
18372
- saveHostEnvValues(config.baseUrl, {
18373
- DEEPLINE_API_KEY: switched.api_key,
18374
- DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
18375
- DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
18376
- });
18377
- const { api_key: _apiKey, ...publicSwitched } = switched;
18378
- printCommandEnvelope(
18379
- {
18380
- ok: true,
18381
- host_env_path: hostEnvFilePath(config.baseUrl),
18382
- ...publicSwitched,
18383
- api_key_saved: true,
18384
- render: {
18385
- sections: [
18386
- {
18387
- title: "org switch",
18388
- lines: [
18389
- `Switched to ${switched.org_name}.`,
18390
- `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
18391
- ]
18392
- }
18393
- ]
18394
- }
18395
- },
18396
- { json: options.json }
18397
- );
18701
+ return targets;
18398
18702
  }
18399
- async function handleOrgCreate(name, options) {
18400
- const config = resolveConfig();
18401
- const http = new HttpClient(config);
18402
- const created = await http.post("/api/v2/auth/cli/org-create", {
18403
- api_key: config.apiKey,
18404
- name
18405
- });
18406
- saveHostEnvValues(config.baseUrl, {
18407
- DEEPLINE_API_KEY: created.api_key,
18408
- DEEPLINE_ACTIVE_ORG_ID: created.org_id,
18409
- DEEPLINE_ACTIVE_ORG_NAME: created.org_name
18410
- });
18411
- const { api_key: _apiKey, ...publicCreated } = created;
18412
- printCommandEnvelope(
18413
- {
18414
- ok: true,
18415
- ...publicCreated,
18416
- api_key_saved: true,
18417
- switched: true,
18418
- host_env_path: hostEnvFilePath(config.baseUrl),
18419
- render: {
18420
- sections: [
18421
- {
18422
- title: "org create",
18423
- lines: [
18424
- `Created organization: ${created.org_name}.`,
18425
- `Switched to ${created.org_name}.`,
18426
- `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
18427
- ]
18428
- }
18429
- ]
18430
- }
18431
- },
18432
- { json: options.json }
18433
- );
18703
+ function parseJsonLine(line) {
18704
+ try {
18705
+ return JSON.parse(line);
18706
+ } catch {
18707
+ return null;
18708
+ }
18434
18709
  }
18435
- function registerOrgCommands(program) {
18436
- const org = program.command("org").description("List, create, and switch organizations.").addHelpText(
18437
- "after",
18438
- `
18439
- Notes:
18440
- Organizations are workspaces. Switching organizations mutates the saved host
18441
- auth file so later CLI commands target the selected workspace.
18442
-
18443
- Examples:
18444
- deepline org list --json
18445
- deepline org create Acme --json
18446
- deepline org switch 2
18447
- deepline org switch --org-id org_123 --json
18448
- `
18449
- );
18450
- org.command("list").description("List your organizations.").addHelpText(
18451
- "after",
18452
- `
18453
- Notes:
18454
- Read-only. Marks the active organization when the server returns that metadata.
18455
-
18456
- Examples:
18457
- deepline org list
18458
- deepline org list --json
18459
- `
18460
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
18461
- org.command("create <name>").description("Create a new organization and switch this CLI to it.").addHelpText(
18710
+ function normalizedJsonLines(raw) {
18711
+ return raw.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
18712
+ }
18713
+ function stripNoiseEvents(raw) {
18714
+ const lines = [];
18715
+ for (const line of normalizedJsonLines(raw)) {
18716
+ const parsed = parseJsonLine(line);
18717
+ if (parsed && typeof parsed === "object" && NOISE_EVENT_TYPES.has(String(parsed.type ?? ""))) {
18718
+ continue;
18719
+ }
18720
+ lines.push(line);
18721
+ }
18722
+ return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
18723
+ ` : "", "utf8");
18724
+ }
18725
+ function messageContentKey(value) {
18726
+ const message = value.message;
18727
+ if (!message || typeof message !== "object") return null;
18728
+ const content = message.content;
18729
+ if (typeof content === "string") return content;
18730
+ if (!Array.isArray(content)) return null;
18731
+ return content.map((block) => {
18732
+ if (!block || typeof block !== "object") return String(block);
18733
+ const record = block;
18734
+ const type = String(record.type ?? "");
18735
+ if (type === "tool_use") {
18736
+ return `tool_use:${String(record.name ?? "")}:${String(record.id ?? "")}`;
18737
+ }
18738
+ if (type === "tool_result") {
18739
+ return `tool_result:${String(record.tool_use_id ?? "")}`;
18740
+ }
18741
+ return String(record.text ?? type);
18742
+ }).join("\n");
18743
+ }
18744
+ function dedupConsecutiveEvents(raw) {
18745
+ const rawLines = normalizedJsonLines(raw);
18746
+ const parsedEvents = rawLines.map(parseJsonLine);
18747
+ const output2 = [];
18748
+ let index = 0;
18749
+ while (index < parsedEvents.length) {
18750
+ const event = parsedEvents[index];
18751
+ if (!event || typeof event !== "object") {
18752
+ output2.push(rawLines[index] ?? "");
18753
+ index += 1;
18754
+ continue;
18755
+ }
18756
+ const record = event;
18757
+ const eventType = String(record.type ?? "");
18758
+ const eventKey = messageContentKey(record);
18759
+ if (!["user", "assistant"].includes(eventType) || !eventKey) {
18760
+ output2.push(rawLines[index] ?? "");
18761
+ index += 1;
18762
+ continue;
18763
+ }
18764
+ let runCount = 1;
18765
+ let cursor = index + 1;
18766
+ while (cursor < parsedEvents.length) {
18767
+ const next = parsedEvents[cursor];
18768
+ if (!next || typeof next !== "object") break;
18769
+ const nextRecord = next;
18770
+ if (String(nextRecord.type ?? "") !== eventType || messageContentKey(nextRecord) !== eventKey) {
18771
+ break;
18772
+ }
18773
+ runCount += 1;
18774
+ cursor += 1;
18775
+ }
18776
+ if (runCount > 1) {
18777
+ record._repeat_count = runCount;
18778
+ record._repeat_summary = `${runCount} consecutive identical ${eventType} messages collapsed`;
18779
+ output2.push(JSON.stringify(record));
18780
+ index = cursor;
18781
+ continue;
18782
+ }
18783
+ output2.push(rawLines[index] ?? "");
18784
+ index += 1;
18785
+ }
18786
+ return Buffer.from(output2.length > 0 ? `${output2.join("\n")}
18787
+ ` : "", "utf8");
18788
+ }
18789
+ function compactEventValue(value) {
18790
+ if (typeof value === "string") {
18791
+ if (value.length <= MAX_EVENT_STRING_CHARS) return value;
18792
+ return `${value.slice(0, MAX_EVENT_STRING_CHARS - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`;
18793
+ }
18794
+ if (Array.isArray(value)) {
18795
+ const compacted = value.slice(0, MAX_EVENT_LIST_ITEMS).map(compactEventValue);
18796
+ if (value.length > MAX_EVENT_LIST_ITEMS) {
18797
+ compacted.push(
18798
+ `${TRUNCATION_MARKER} ${value.length - MAX_EVENT_LIST_ITEMS} more item(s)`
18799
+ );
18800
+ }
18801
+ return compacted;
18802
+ }
18803
+ if (value && typeof value === "object") {
18804
+ const entries = Object.entries(value);
18805
+ const compacted = {};
18806
+ for (const [key, item] of entries.slice(0, MAX_EVENT_OBJECT_KEYS)) {
18807
+ compacted[key] = compactEventValue(item);
18808
+ }
18809
+ if (entries.length > MAX_EVENT_OBJECT_KEYS) {
18810
+ compacted._truncated_keys = entries.length - MAX_EVENT_OBJECT_KEYS;
18811
+ }
18812
+ return compacted;
18813
+ }
18814
+ return value;
18815
+ }
18816
+ function selectiveCompactToolResults(raw) {
18817
+ const lines = [];
18818
+ for (const line of normalizedJsonLines(raw)) {
18819
+ const parsed = parseJsonLine(line);
18820
+ if (!parsed || typeof parsed !== "object") {
18821
+ lines.push(line);
18822
+ continue;
18823
+ }
18824
+ const record = parsed;
18825
+ if (record.type === "user") {
18826
+ const message = record.message;
18827
+ const content = message && typeof message === "object" ? message.content : null;
18828
+ if (Array.isArray(content)) {
18829
+ message.content = content.map(
18830
+ (block) => block && typeof block === "object" && block.type === "tool_result" ? compactEventValue(block) : block
18831
+ );
18832
+ }
18833
+ }
18834
+ lines.push(JSON.stringify(record));
18835
+ }
18836
+ return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
18837
+ ` : "", "utf8");
18838
+ }
18839
+ function prepareSessionBuffer(raw) {
18840
+ return selectiveCompactToolResults(
18841
+ dedupConsecutiveEvents(stripNoiseEvents(raw))
18842
+ );
18843
+ }
18844
+ function buildSessionUploadContent(raw) {
18845
+ const prepared = prepareSessionBuffer(raw);
18846
+ const encoded = (0, import_node_zlib.gzipSync)(prepared).toString("base64");
18847
+ if (encoded.length <= MAX_SESSION_UPLOAD_BYTES && prepared.length <= MAX_DIRECT_SESSION_DECODED_BYTES) {
18848
+ return { encodedContent: encoded, needsChunking: false };
18849
+ }
18850
+ return { encodedContent: encoded, needsChunking: true };
18851
+ }
18852
+ async function uploadPayload(path, payload) {
18853
+ const { http } = getAuthedHttpClient();
18854
+ return await http.post(path, payload);
18855
+ }
18856
+ async function uploadChunkedSessions(sessions, options) {
18857
+ const uploadId = (0, import_node_crypto4.randomUUID)();
18858
+ for (const session of sessions) {
18859
+ const bytes = Buffer.from(session.encodedContent, "base64");
18860
+ const chunks = [];
18861
+ for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE_BYTES) {
18862
+ chunks.push(bytes.subarray(offset, offset + CHUNK_SIZE_BYTES));
18863
+ }
18864
+ process.stderr.write(
18865
+ `Uploading ${session.label} in ${chunks.length} chunk(s)...
18866
+ `
18867
+ );
18868
+ for (const [index, chunk] of chunks.entries()) {
18869
+ await uploadPayload("/api/v2/cli/send-session/chunk", {
18870
+ upload_id: uploadId,
18871
+ session_id: session.sessionId,
18872
+ index,
18873
+ total_chunks: chunks.length,
18874
+ data: chunk.toString("base64")
18875
+ });
18876
+ }
18877
+ }
18878
+ const response = await uploadPayload("/api/v2/cli/send-session/finalize", {
18879
+ upload_id: uploadId,
18880
+ session_ids: sessions.map((session) => session.sessionId),
18881
+ labels: sessions.map((session) => session.label),
18882
+ environments: sessions.map(() => detectShellContext())
18883
+ });
18884
+ printCommandEnvelope(
18885
+ {
18886
+ ...response,
18887
+ ok: true,
18888
+ uploaded: sessions.length,
18889
+ render: {
18890
+ sections: [
18891
+ {
18892
+ title: "sessions send",
18893
+ lines: ["Session uploaded to #internal-reports (chunked)."]
18894
+ }
18895
+ ]
18896
+ }
18897
+ },
18898
+ { json: options.json }
18899
+ );
18900
+ }
18901
+ async function handleSessionsSend(options) {
18902
+ if (options.file) {
18903
+ const filePath = (0, import_node_path14.resolve)(options.file);
18904
+ if (!(0, import_node_fs11.existsSync)(filePath)) {
18905
+ throw new Error(`File not found: ${options.file}`);
18906
+ }
18907
+ const response2 = await uploadPayload("/api/v2/cli/send-session", {
18908
+ file: (0, import_node_fs11.readFileSync)(filePath).toString("base64"),
18909
+ filename: (0, import_node_path14.basename)(filePath)
18910
+ });
18911
+ printCommandEnvelope(
18912
+ {
18913
+ ...response2,
18914
+ ok: true,
18915
+ filename: (0, import_node_path14.basename)(filePath),
18916
+ render: {
18917
+ sections: [
18918
+ {
18919
+ title: "sessions send",
18920
+ lines: [
18921
+ `File '${(0, import_node_path14.basename)(filePath)}' uploaded to #internal-reports.`
18922
+ ]
18923
+ }
18924
+ ]
18925
+ }
18926
+ },
18927
+ { json: options.json }
18928
+ );
18929
+ return;
18930
+ }
18931
+ const targets = resolveSessionTargets({
18932
+ sessionIds: options.sessionId,
18933
+ labels: options.label,
18934
+ currentSession: options.currentSession,
18935
+ agent: options.agent
18936
+ });
18937
+ const built = targets.map((target) => {
18938
+ const upload = buildSessionUploadContent((0, import_node_fs11.readFileSync)(target.filePath));
18939
+ return { ...target, ...upload };
18940
+ });
18941
+ if (built.some((session) => session.needsChunking)) {
18942
+ await uploadChunkedSessions(built, options);
18943
+ return;
18944
+ }
18945
+ const response = built.length === 1 && !options.label?.length ? await uploadPayload("/api/v2/cli/send-session", {
18946
+ session_id: built[0]?.sessionId,
18947
+ content: built[0]?.encodedContent,
18948
+ environment: detectShellContext()
18949
+ }) : await uploadPayload("/api/v2/cli/send-session", {
18950
+ sessions: built.map((session) => ({
18951
+ session_id: session.sessionId,
18952
+ content: session.encodedContent,
18953
+ label: session.label,
18954
+ environment: detectShellContext()
18955
+ })),
18956
+ environment: detectShellContext()
18957
+ });
18958
+ printCommandEnvelope(
18959
+ {
18960
+ ...response,
18961
+ ok: true,
18962
+ uploaded: built.length,
18963
+ render: {
18964
+ sections: [
18965
+ {
18966
+ title: "sessions send",
18967
+ lines: ["Session uploaded to #internal-reports."]
18968
+ }
18969
+ ]
18970
+ }
18971
+ },
18972
+ { json: options.json }
18973
+ );
18974
+ }
18975
+ function fallbackViewerAssets() {
18976
+ return {
18977
+ css: [
18978
+ "body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;margin:0;padding:16px;background:#fafafa;color:#111}",
18979
+ ".section{background:#fff;border:1px solid #ddd;border-radius:8px;padding:12px;margin-bottom:12px}",
18980
+ ".section h2{margin:0 0 8px 0;font-size:14px}",
18981
+ "pre{margin:0;white-space:pre-wrap;word-break:break-word;background:#f6f8fa;border:1px solid #e3e5e8;border-radius:6px;padding:10px}"
18982
+ ].join(""),
18983
+ js: [
18984
+ "(() => {",
18985
+ 'const root=document.getElementById("main-content");',
18986
+ 'const raw=document.getElementById("raw-sessions");',
18987
+ "if(!root||!raw)return;",
18988
+ 'let sessions=[];try{sessions=JSON.parse(raw.textContent||"[]")}catch{}',
18989
+ 'root.innerHTML="";',
18990
+ "for(const session of sessions){",
18991
+ 'const section=document.createElement("section");section.className="section";',
18992
+ 'const title=document.createElement("h2");title.textContent=String(session.label||"session");',
18993
+ 'const pre=document.createElement("pre");',
18994
+ 'pre.textContent=(Array.isArray(session.events)?session.events:[]).map((event)=>JSON.stringify(event)).join("\\n");',
18995
+ "section.append(title,pre);root.appendChild(section);",
18996
+ "}",
18997
+ "})();"
18998
+ ].join("")
18999
+ };
19000
+ }
19001
+ function loadViewerAssets() {
19002
+ const cliEntry = process.argv[1]?.trim() ? (0, import_node_path14.resolve)(process.argv[1]) : null;
19003
+ const candidateRoots2 = [
19004
+ ...cliEntry ? [
19005
+ (0, import_node_path14.join)((0, import_node_path14.dirname)((0, import_node_path14.dirname)(cliEntry)), "viewer"),
19006
+ (0, import_node_path14.join)(
19007
+ (0, import_node_path14.dirname)((0, import_node_path14.dirname)((0, import_node_path14.dirname)(cliEntry))),
19008
+ "src",
19009
+ "lib",
19010
+ "cli",
19011
+ "viewer"
19012
+ )
19013
+ ] : [],
19014
+ (0, import_node_path14.join)(process.cwd(), "src", "lib", "cli", "viewer")
19015
+ ];
19016
+ for (const root of candidateRoots2) {
19017
+ try {
19018
+ const cssPath = (0, import_node_path14.join)(root, "viewer.css");
19019
+ const jsPath = (0, import_node_path14.join)(root, "viewer.js");
19020
+ if (!(0, import_node_fs11.existsSync)(cssPath) || !(0, import_node_fs11.existsSync)(jsPath)) continue;
19021
+ return {
19022
+ css: (0, import_node_fs11.readFileSync)(cssPath, "utf8"),
19023
+ js: (0, import_node_fs11.readFileSync)(jsPath, "utf8")
19024
+ };
19025
+ } catch {
19026
+ continue;
19027
+ }
19028
+ }
19029
+ return fallbackViewerAssets();
19030
+ }
19031
+ function parsePreparedEvents(buffer) {
19032
+ return normalizedJsonLines(buffer).map((line) => {
19033
+ const parsed = parseJsonLine(line);
19034
+ return parsed ?? line;
19035
+ });
19036
+ }
19037
+ function escapeJsonForHtmlScript(json) {
19038
+ return json.replace(/</g, "\\u003c");
19039
+ }
19040
+ async function handleSessionsRender(options) {
19041
+ const targets = resolveSessionTargets({
19042
+ sessionIds: options.sessionId,
19043
+ labels: options.label,
19044
+ currentSession: options.currentSession,
19045
+ agent: options.agent
19046
+ });
19047
+ let outputPath = options.output ? (0, import_node_path14.resolve)(options.output) : "";
19048
+ if (!outputPath) {
19049
+ const outputDir = (0, import_node_path14.join)(process.cwd(), "deepline", "data");
19050
+ (0, import_node_fs11.mkdirSync)(outputDir, { recursive: true });
19051
+ outputPath = (0, import_node_path14.join)(
19052
+ outputDir,
19053
+ targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
19054
+ );
19055
+ } else {
19056
+ (0, import_node_fs11.mkdirSync)((0, import_node_path14.dirname)(outputPath), { recursive: true });
19057
+ }
19058
+ const sessions = targets.map((target) => ({
19059
+ label: target.label,
19060
+ events: parsePreparedEvents(
19061
+ prepareSessionBuffer((0, import_node_fs11.readFileSync)(target.filePath))
19062
+ )
19063
+ }));
19064
+ const { css, js } = loadViewerAssets();
19065
+ const refreshMeta = options.autoRefresh ? `<meta http-equiv="refresh" content="${Number.parseInt(options.autoRefresh, 10)}">` : "";
19066
+ const rawJson = escapeJsonForHtmlScript(JSON.stringify(sessions));
19067
+ const html = `<!DOCTYPE html>
19068
+ <html lang="en">
19069
+ <head>
19070
+ <meta charset="UTF-8">
19071
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19072
+ ${refreshMeta}
19073
+ <title>Session Viewer</title>
19074
+ <style>${css}</style>
19075
+ </head>
19076
+ <body>
19077
+ <div class="layout">
19078
+ <div class="main" id="main-content"></div>
19079
+ </div>
19080
+ <script type="application/json" id="raw-sessions">${rawJson}</script>
19081
+ <script>${js}</script>
19082
+ </body>
19083
+ </html>`;
19084
+ (0, import_node_fs11.writeFileSync)(outputPath, html, "utf8");
19085
+ printCommandEnvelope(
19086
+ {
19087
+ ok: true,
19088
+ file: outputPath,
19089
+ session_count: targets.length,
19090
+ render: {
19091
+ sections: [
19092
+ {
19093
+ title: "sessions render",
19094
+ lines: [`Rendered session viewer: ${outputPath}`]
19095
+ }
19096
+ ]
19097
+ }
19098
+ },
19099
+ { json: options.json }
19100
+ );
19101
+ }
19102
+ function collectOption(value, previous) {
19103
+ previous.push(value);
19104
+ return previous;
19105
+ }
19106
+ function registerSessionsCommands(program) {
19107
+ const sessions = program.command("sessions").description("Upload and render local agent session transcripts.").addHelpText(
19108
+ "after",
19109
+ `
19110
+ Notes:
19111
+ Session commands operate on local Claude and Codex JSONL files under
19112
+ ~/.claude/projects and ~/.codex/sessions. send uploads a compacted transcript
19113
+ or file to Deepline. render writes a local HTML viewer.
19114
+
19115
+ Examples:
19116
+ deepline sessions send --current-session --json
19117
+ deepline sessions send --agent codex --current-session --json
19118
+ deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca
19119
+ deepline sessions render --current-session --output session.html
19120
+ `
19121
+ );
19122
+ registerSessionSendRenderCommands(sessions, "sessions");
19123
+ }
19124
+ function registerSessionSendRenderCommands(parent, familyName) {
19125
+ parent.command("send").description("Upload session transcript(s) or a local file to Deepline.").addHelpText(
19126
+ "after",
19127
+ `
19128
+ Examples:
19129
+ deepline ${familyName} send --current-session --json
19130
+ deepline ${familyName} send --agent codex --current-session --json
19131
+ deepline ${familyName} send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --label "pilot run"
19132
+ deepline ${familyName} send --file ./debug.log --json
19133
+ `
19134
+ ).option(
19135
+ "--session-id <uuid>",
19136
+ "Claude or Codex session UUID. Repeat for multiple sessions.",
19137
+ collectOption,
19138
+ []
19139
+ ).option(
19140
+ "--label <label>",
19141
+ "Label for the preceding session id",
19142
+ collectOption,
19143
+ []
19144
+ ).option(
19145
+ "--agent <auto|claude|codex>",
19146
+ "Limit transcript discovery to one agent runtime",
19147
+ "auto"
19148
+ ).option("--current-session", "Use the newest local agent 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);
19149
+ parent.command("render").description("Render local session transcript(s) to an HTML viewer.").addHelpText(
19150
+ "after",
19151
+ `
19152
+ Examples:
19153
+ deepline ${familyName} render --current-session
19154
+ deepline ${familyName} render --agent codex --current-session
19155
+ deepline ${familyName} render --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --output session.html
19156
+ deepline ${familyName} render --current-session --auto-refresh 5 --json
19157
+ `
19158
+ ).option(
19159
+ "--session-id <uuid>",
19160
+ "Claude or Codex session UUID. Repeat for multiple sessions.",
19161
+ collectOption,
19162
+ []
19163
+ ).option(
19164
+ "--label <label>",
19165
+ "Label for the preceding session id",
19166
+ collectOption,
19167
+ []
19168
+ ).option(
19169
+ "--agent <auto|claude|codex>",
19170
+ "Limit transcript discovery to one agent runtime",
19171
+ "auto"
19172
+ ).option("--current-session", "Use the newest local agent 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);
19173
+ }
19174
+
19175
+ // src/cli/commands/legacy-noops.ts
19176
+ var SESSION_SUBCOMMANDS = [
19177
+ "start",
19178
+ "status",
19179
+ "output",
19180
+ "alert",
19181
+ "usage",
19182
+ "limit"
19183
+ ];
19184
+ var BACKEND_SUBCOMMANDS = [
19185
+ "start",
19186
+ "stop",
19187
+ "status",
19188
+ "refresh-runtime",
19189
+ "sync-runtime"
19190
+ ];
19191
+ function legacyNoopEnvelope(input2) {
19192
+ const command = ["deepline", input2.family, input2.subcommand].filter(Boolean).join(" ");
19193
+ const subject = input2.family === "session" ? "Legacy Session UI/playground command" : "Legacy local playground backend command";
19194
+ const note = input2.family === "session" ? "The SDK CLI does not manage the legacy Python Session UI. This command is accepted for backwards compatibility and intentionally does nothing." : "The SDK CLI does not start or stop the legacy Python local playground backend. This command is accepted for backwards compatibility and intentionally does nothing.";
19195
+ return {
19196
+ ok: true,
19197
+ noop: true,
19198
+ command,
19199
+ compatibility: {
19200
+ family: "legacy_python_cli",
19201
+ sdk_behavior: "noop"
19202
+ },
19203
+ render: {
19204
+ sections: [
19205
+ {
19206
+ title: subject,
19207
+ lines: [note]
19208
+ }
19209
+ ]
19210
+ }
19211
+ };
19212
+ }
19213
+ function printLegacyNoop(input2) {
19214
+ printCommandEnvelope(legacyNoopEnvelope(input2), { json: input2.options.json });
19215
+ }
19216
+ function legacySubcommandFromArgv(family) {
19217
+ const args = process.argv.slice(2);
19218
+ const familyIndex = args.indexOf(family);
19219
+ const nextToken = familyIndex >= 0 ? args[familyIndex + 1] : void 0;
19220
+ return nextToken && !nextToken.startsWith("-") ? nextToken : void 0;
19221
+ }
19222
+ function addLegacyNoopSubcommand(parent, family, subcommand, description) {
19223
+ parent.command(subcommand).description(description).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").action((_args, options) => {
19224
+ printLegacyNoop({ family, subcommand, options });
19225
+ });
19226
+ }
19227
+ function registerLegacyNoopCommands(program) {
19228
+ const session = program.command("session").description("Compatibility no-ops for legacy Python Session UI commands.").allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
19229
+ "after",
19230
+ `
19231
+ Notes:
19232
+ The SDK CLI accepts singular session commands from older agent skills so they
19233
+ do not fail, but it does not manage the legacy Python Session UI.
19234
+ Use "deepline sessions send" or "deepline sessions render" for real SDK
19235
+ transcript workflows. "deepline session send" and "deepline session render"
19236
+ are accepted aliases for those real SDK workflows.
19237
+
19238
+ Examples:
19239
+ deepline session start --steps '["Inspect CSV","Run pilot"]'
19240
+ deepline session status --message "Running pilot"
19241
+ deepline session output --csv ./results.csv --label "Results"
19242
+ `
19243
+ ).action((args, options) => {
19244
+ void args;
19245
+ printLegacyNoop({
19246
+ family: "session",
19247
+ subcommand: legacySubcommandFromArgv("session"),
19248
+ options
19249
+ });
19250
+ });
19251
+ registerSessionSendRenderCommands(session, "session");
19252
+ for (const subcommand of SESSION_SUBCOMMANDS) {
19253
+ addLegacyNoopSubcommand(
19254
+ session,
19255
+ "session",
19256
+ subcommand,
19257
+ `Accept legacy "deepline session ${subcommand}" as an SDK no-op.`
19258
+ );
19259
+ }
19260
+ const backend = program.command("backend").description(
19261
+ "Compatibility no-ops for legacy Python local backend commands."
19262
+ ).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
19263
+ "after",
19264
+ `
19265
+ Notes:
19266
+ The SDK CLI uses the configured Deepline host and V2 play runtime. It does not
19267
+ start, stop, or refresh the legacy Python local playground backend.
19268
+
19269
+ Examples:
19270
+ deepline backend start
19271
+ deepline backend stop --just-backend
19272
+ deepline backend status --json
19273
+ `
19274
+ ).action((args, options) => {
19275
+ void args;
19276
+ printLegacyNoop({
19277
+ family: "backend",
19278
+ subcommand: legacySubcommandFromArgv("backend"),
19279
+ options
19280
+ });
19281
+ });
19282
+ for (const subcommand of BACKEND_SUBCOMMANDS) {
19283
+ addLegacyNoopSubcommand(
19284
+ backend,
19285
+ "backend",
19286
+ subcommand,
19287
+ `Accept legacy "deepline backend ${subcommand}" as an SDK no-op.`
19288
+ );
19289
+ }
19290
+ }
19291
+
19292
+ // src/cli/commands/org.ts
19293
+ async function fetchOrganizations(http, apiKey) {
19294
+ return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
19295
+ }
19296
+ function orgListLines(orgs) {
19297
+ return orgs.map((org, index) => {
19298
+ const current = org.is_current ? " (current)" : "";
19299
+ const role = org.role ? ` [${org.role}]` : "";
19300
+ return `${index + 1}. ${org.name}${role}${current}`;
19301
+ });
19302
+ }
19303
+ async function handleOrgList(options) {
19304
+ const config = resolveConfig();
19305
+ const http = new HttpClient(config);
19306
+ const payload = await fetchOrganizations(http, config.apiKey);
19307
+ printCommandEnvelope(
19308
+ {
19309
+ ...payload,
19310
+ render: {
19311
+ sections: [
19312
+ {
19313
+ title: "Your organizations:",
19314
+ lines: orgListLines(payload.organizations)
19315
+ }
19316
+ ]
19317
+ }
19318
+ },
19319
+ { json: options.json }
19320
+ );
19321
+ }
19322
+ async function handleOrgSwitch(selection, options) {
19323
+ const config = resolveConfig();
19324
+ const http = new HttpClient(config);
19325
+ const payload = await fetchOrganizations(http, config.apiKey);
19326
+ if (!selection && !options.orgId) {
19327
+ printCommandEnvelope(
19328
+ {
19329
+ ...payload,
19330
+ next: { switch: "deepline org switch <number>" },
19331
+ render: {
19332
+ sections: [
19333
+ {
19334
+ title: "Your organizations:",
19335
+ lines: orgListLines(payload.organizations)
19336
+ }
19337
+ ],
19338
+ actions: [{ label: "Run", command: "deepline org switch <number>" }]
19339
+ }
19340
+ },
19341
+ { json: options.json }
19342
+ );
19343
+ return;
19344
+ }
19345
+ let target = payload.organizations.find(
19346
+ (org) => org.org_id === options.orgId
19347
+ );
19348
+ if (!target && selection) {
19349
+ const index = Number.parseInt(selection, 10);
19350
+ if (Number.isFinite(index) && index >= 1 && index <= payload.organizations.length) {
19351
+ target = payload.organizations[index - 1];
19352
+ } else {
19353
+ target = payload.organizations.find(
19354
+ (org) => org.name === selection || org.org_id === selection
19355
+ );
19356
+ }
19357
+ }
19358
+ if (!target) {
19359
+ throw new Error("Could not resolve the selected organization.");
19360
+ }
19361
+ if (target.is_current) {
19362
+ printCommandEnvelope(
19363
+ {
19364
+ ok: true,
19365
+ unchanged: true,
19366
+ organization: target,
19367
+ render: {
19368
+ sections: [
19369
+ { title: "org switch", lines: [`Already on ${target.name}.`] }
19370
+ ]
19371
+ }
19372
+ },
19373
+ { json: options.json }
19374
+ );
19375
+ return;
19376
+ }
19377
+ const switched = await http.post("/api/v2/auth/cli/switch", {
19378
+ api_key: config.apiKey,
19379
+ org_id: target.org_id
19380
+ });
19381
+ saveHostEnvValues(config.baseUrl, {
19382
+ DEEPLINE_API_KEY: switched.api_key,
19383
+ DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
19384
+ DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
19385
+ });
19386
+ const { api_key: _apiKey, ...publicSwitched } = switched;
19387
+ printCommandEnvelope(
19388
+ {
19389
+ ok: true,
19390
+ host_env_path: hostEnvFilePath(config.baseUrl),
19391
+ ...publicSwitched,
19392
+ api_key_saved: true,
19393
+ render: {
19394
+ sections: [
19395
+ {
19396
+ title: "org switch",
19397
+ lines: [
19398
+ `Switched to ${switched.org_name}.`,
19399
+ `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
19400
+ ]
19401
+ }
19402
+ ]
19403
+ }
19404
+ },
19405
+ { json: options.json }
19406
+ );
19407
+ }
19408
+ async function handleOrgCreate(name, options) {
19409
+ const config = resolveConfig();
19410
+ const http = new HttpClient(config);
19411
+ const created = await http.post("/api/v2/auth/cli/org-create", {
19412
+ api_key: config.apiKey,
19413
+ name
19414
+ });
19415
+ saveHostEnvValues(config.baseUrl, {
19416
+ DEEPLINE_API_KEY: created.api_key,
19417
+ DEEPLINE_ACTIVE_ORG_ID: created.org_id,
19418
+ DEEPLINE_ACTIVE_ORG_NAME: created.org_name
19419
+ });
19420
+ const { api_key: _apiKey, ...publicCreated } = created;
19421
+ printCommandEnvelope(
19422
+ {
19423
+ ok: true,
19424
+ ...publicCreated,
19425
+ api_key_saved: true,
19426
+ switched: true,
19427
+ host_env_path: hostEnvFilePath(config.baseUrl),
19428
+ render: {
19429
+ sections: [
19430
+ {
19431
+ title: "org create",
19432
+ lines: [
19433
+ `Created organization: ${created.org_name}.`,
19434
+ `Switched to ${created.org_name}.`,
19435
+ `Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
19436
+ ]
19437
+ }
19438
+ ]
19439
+ }
19440
+ },
19441
+ { json: options.json }
19442
+ );
19443
+ }
19444
+ function registerOrgCommands(program) {
19445
+ const org = program.command("org").description("List, create, and switch organizations.").addHelpText(
19446
+ "after",
19447
+ `
19448
+ Notes:
19449
+ Organizations are workspaces. Switching organizations mutates the saved host
19450
+ auth file so later CLI commands target the selected workspace.
19451
+
19452
+ Examples:
19453
+ deepline org list --json
19454
+ deepline org create Acme --json
19455
+ deepline org switch 2
19456
+ deepline org switch --org-id org_123 --json
19457
+ `
19458
+ );
19459
+ org.command("list").description("List your organizations.").addHelpText(
19460
+ "after",
19461
+ `
19462
+ Notes:
19463
+ Read-only. Marks the active organization when the server returns that metadata.
19464
+
19465
+ Examples:
19466
+ deepline org list
19467
+ deepline org list --json
19468
+ `
19469
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
19470
+ org.command("create <name>").description("Create a new organization and switch this CLI to it.").addHelpText(
18462
19471
  "after",
18463
19472
  `
18464
19473
  Notes:
@@ -18490,7 +19499,7 @@ Examples:
18490
19499
 
18491
19500
  // src/cli/commands/quickstart.ts
18492
19501
  var import_node_child_process = require("child_process");
18493
- var import_node_crypto4 = require("crypto");
19502
+ var import_node_crypto5 = require("crypto");
18494
19503
  var import_node_http = require("http");
18495
19504
  var EXIT_OK2 = 0;
18496
19505
  var EXIT_AUTH2 = 1;
@@ -18592,657 +19601,317 @@ function startCallbackServer(input2) {
18592
19601
  server.listen(0, "127.0.0.1", () => {
18593
19602
  const address = server.address();
18594
19603
  if (!address || typeof address === "string") {
18595
- reject(new Error("Failed to bind quickstart callback server."));
18596
- return;
18597
- }
18598
- resolve16({ server, port: address.port });
18599
- });
18600
- });
18601
- }
18602
- async function handleQuickstart(options) {
18603
- const jsonOutput = shouldEmitJson(options.json === true);
18604
- const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
18605
- const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
18606
- let apiKey = resolveApiKeyForBaseUrl(baseUrl);
18607
- if (!apiKey) {
18608
- if (interactive) {
18609
- console.error("Not connected yet \u2014 registering this device first.");
18610
- const registerExit = await handleRegister([]);
18611
- if (registerExit !== EXIT_OK2) return registerExit;
18612
- apiKey = resolveApiKeyForBaseUrl(baseUrl);
18613
- }
18614
- if (!apiKey) {
18615
- console.error("Not connected. Run: deepline auth register");
18616
- return EXIT_AUTH2;
18617
- }
18618
- }
18619
- const state = (0, import_node_crypto4.randomBytes)(32).toString("hex");
18620
- let resolveSelection;
18621
- const selectionPromise = new Promise((resolve16) => {
18622
- resolveSelection = resolve16;
18623
- });
18624
- let callback;
18625
- try {
18626
- callback = await startCallbackServer({
18627
- state,
18628
- onSelection: (selection) => resolveSelection(selection)
18629
- });
18630
- } catch (error) {
18631
- console.error(
18632
- `Could not start the local quickstart listener: ${error instanceof Error ? error.message : String(error)}`
18633
- );
18634
- return EXIT_SERVER3;
18635
- }
18636
- const quickstartUrl = `${baseUrl}/cli/quickstart?cb=${callback.port}&state=${state}`;
18637
- console.error(" Opening the recipe picker in your browser.");
18638
- console.error(` If it didn't open, cmd+click: ${quickstartUrl}`);
18639
- if (options.open !== false) {
18640
- openInBrowser(quickstartUrl);
18641
- }
18642
- const timeoutSeconds = Number.parseInt(options.timeout ?? "300", 10);
18643
- const timeoutMs = (Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 300) * 1e3;
18644
- const timeoutHandle = setTimeout(
18645
- () => resolveSelection("timeout"),
18646
- timeoutMs
18647
- );
18648
- const progress = createCliProgress(!jsonOutput);
18649
- progress.phase("waiting for your pick in the browser (Ctrl+C to skip)");
18650
- const onSigint = () => resolveSelection("sigint");
18651
- process.once("SIGINT", onSigint);
18652
- const outcome = await selectionPromise;
18653
- clearTimeout(timeoutHandle);
18654
- process.removeListener("SIGINT", onSigint);
18655
- callback.server.close();
18656
- if (outcome === "sigint") {
18657
- progress.fail();
18658
- console.error("\nSkipped \u2014 run `deepline quickstart` anytime.");
18659
- return EXIT_OK2;
18660
- }
18661
- if (outcome === "timeout") {
18662
- progress.fail();
18663
- console.error(
18664
- "Timed out waiting for a selection. Run: deepline quickstart"
18665
- );
18666
- return EXIT_AUTH2;
18667
- }
18668
- progress.complete();
18669
- const { prompt, workflowId } = outcome;
18670
- const claudeCommand = `claude ${shellQuote(prompt)}`;
18671
- const claudeAvailable = hasClaudeBinary();
18672
- const willLaunch = options.launch !== false && !jsonOutput && interactive && claudeAvailable;
18673
- printCommandEnvelope(
18674
- {
18675
- status: "submitted",
18676
- workflowId,
18677
- prompt,
18678
- claudeCommand,
18679
- url: quickstartUrl,
18680
- launched: willLaunch,
18681
- render: {
18682
- sections: [
18683
- {
18684
- title: "quickstart",
18685
- lines: [
18686
- ...workflowId ? [`Recipe: ${workflowId}`] : [],
18687
- willLaunch ? "Launching Claude Code with your recipe..." : claudeAvailable ? "Run the command below to start your recipe." : "Claude Code not found. Install it (https://code.claude.com/docs/en/overview), then run the command below."
18688
- ]
18689
- }
18690
- ],
18691
- actions: [{ label: "Run", command: claudeCommand }]
18692
- }
18693
- },
18694
- { json: jsonOutput }
18695
- );
18696
- if (willLaunch) {
18697
- return launchClaude(prompt);
18698
- }
18699
- return EXIT_OK2;
18700
- }
18701
- function registerQuickstartCommands(program) {
18702
- program.command("quickstart").description(
18703
- "Pick a starter recipe in the browser and launch it with Claude Code."
18704
- ).addHelpText(
18705
- "after",
18706
- `
18707
- Notes:
18708
- Opens the hosted recipe picker in your browser. The picker sends your
18709
- selection back to a temporary listener on 127.0.0.1, so the browser must run
18710
- on the same machine as the CLI. Once a recipe arrives, the CLI prints the
18711
- matching claude command and, when Claude Code is installed and the shell is
18712
- interactive, launches it directly. Press Ctrl+C while waiting to skip.
18713
-
18714
- Examples:
18715
- deepline quickstart
18716
- deepline quickstart --no-open
18717
- deepline quickstart --no-launch --json
18718
- deepline quickstart --timeout 120
18719
- `
18720
- ).option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--no-open", "Do not open the browser; print the URL only").option("--no-launch", "Print the claude command instead of launching it").option(
18721
- "--timeout <seconds>",
18722
- "Maximum seconds to wait for a selection",
18723
- "300"
18724
- ).action(async (options) => {
18725
- process.exitCode = await handleQuickstart(options);
18726
- });
18727
- }
18728
-
18729
- // src/cli/commands/secrets.ts
18730
- var import_node_process = require("process");
18731
- var hiddenInputBuffer = "";
18732
- function normalizeSecretName(value) {
18733
- const normalized = value.trim().toUpperCase();
18734
- if (!/^[A-Z][A-Z0-9_]{1,63}$/.test(normalized)) {
18735
- throw new Error(
18736
- "Secret names must be 2-64 characters and use uppercase letters, numbers, and underscores."
18737
- );
18738
- }
18739
- return normalized;
18740
- }
18741
- function renderSecret(secret) {
18742
- const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
18743
- return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
18744
- }
18745
- async function readHiddenLine(prompt) {
18746
- if (!import_node_process.stdin.isTTY || !import_node_process.stdout.isTTY) {
18747
- throw new Error(
18748
- "Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
18749
- );
18750
- }
18751
- import_node_process.stdout.write(prompt);
18752
- const previousRawMode = import_node_process.stdin.isRaw;
18753
- if (typeof import_node_process.stdin.setRawMode === "function") import_node_process.stdin.setRawMode(true);
18754
- let value = "";
18755
- import_node_process.stdin.resume();
18756
- return await new Promise((resolve16, reject) => {
18757
- let settled = false;
18758
- const cleanup = () => {
18759
- import_node_process.stdin.off("data", onData);
18760
- import_node_process.stdin.off("end", onEnd);
18761
- import_node_process.stdin.off("error", onError);
18762
- if (typeof import_node_process.stdin.setRawMode === "function") {
18763
- import_node_process.stdin.setRawMode(previousRawMode);
18764
- }
18765
- };
18766
- const finish = (line) => {
18767
- if (settled) return;
18768
- settled = true;
18769
- import_node_process.stdout.write("\n");
18770
- cleanup();
18771
- resolve16(line);
18772
- };
18773
- const fail = (error) => {
18774
- if (settled) return;
18775
- settled = true;
18776
- import_node_process.stdout.write("\n");
18777
- cleanup();
18778
- reject(error);
18779
- };
18780
- const processText = (text) => {
18781
- for (let index = 0; index < text.length; index++) {
18782
- const char = text[index];
18783
- const code = char.charCodeAt(0);
18784
- if (char === "\r" || char === "\n") {
18785
- hiddenInputBuffer = text.slice(index + 1);
18786
- finish(value);
18787
- return;
18788
- }
18789
- if (code === 3) {
18790
- fail(new Error("Secret input cancelled."));
18791
- return;
18792
- }
18793
- if (code === 8 || code === 127) {
18794
- value = value.slice(0, -1);
18795
- continue;
18796
- }
18797
- if (code >= 32) {
18798
- value += char;
18799
- }
18800
- }
18801
- hiddenInputBuffer = "";
18802
- };
18803
- const onData = (chunk) => {
18804
- processText(chunk.toString());
18805
- };
18806
- const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
18807
- const onError = (error) => fail(error);
18808
- import_node_process.stdin.on("data", onData);
18809
- import_node_process.stdin.once("end", onEnd);
18810
- import_node_process.stdin.once("error", onError);
18811
- if (hiddenInputBuffer) {
18812
- const buffered = hiddenInputBuffer;
18813
- hiddenInputBuffer = "";
18814
- processText(buffered);
18815
- }
19604
+ reject(new Error("Failed to bind quickstart callback server."));
19605
+ return;
19606
+ }
19607
+ resolve16({ server, port: address.port });
19608
+ });
18816
19609
  });
18817
19610
  }
18818
- async function readSecretValue() {
18819
- const first = await readHiddenLine("Secret value: ");
18820
- if (!first) throw new Error("Secret value is required.");
18821
- const second = await readHiddenLine("Confirm secret value: ");
18822
- if (first !== second) {
18823
- throw new Error("Secret values did not match.");
19611
+ async function handleQuickstart(options) {
19612
+ const jsonOutput = shouldEmitJson(options.json === true);
19613
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
19614
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
19615
+ let apiKey = resolveApiKeyForBaseUrl(baseUrl);
19616
+ if (!apiKey) {
19617
+ if (interactive) {
19618
+ console.error("Not connected yet \u2014 registering this device first.");
19619
+ const registerExit = await handleRegister([]);
19620
+ if (registerExit !== EXIT_OK2) return registerExit;
19621
+ apiKey = resolveApiKeyForBaseUrl(baseUrl);
19622
+ }
19623
+ if (!apiKey) {
19624
+ console.error("Not connected. Run: deepline auth register");
19625
+ return EXIT_AUTH2;
19626
+ }
18824
19627
  }
18825
- return first;
18826
- }
18827
- function preventShellHistoryLeak(forbidden) {
18828
- if (forbidden.length > 0) {
18829
- throw new Error(
18830
- "Do not pass secret values as command arguments. Run `deepline secrets set NAME` and enter the value at the hidden prompt."
19628
+ const state = (0, import_node_crypto5.randomBytes)(32).toString("hex");
19629
+ let resolveSelection;
19630
+ const selectionPromise = new Promise((resolve16) => {
19631
+ resolveSelection = resolve16;
19632
+ });
19633
+ let callback;
19634
+ try {
19635
+ callback = await startCallbackServer({
19636
+ state,
19637
+ onSelection: (selection) => resolveSelection(selection)
19638
+ });
19639
+ } catch (error) {
19640
+ console.error(
19641
+ `Could not start the local quickstart listener: ${error instanceof Error ? error.message : String(error)}`
18831
19642
  );
19643
+ return EXIT_SERVER3;
18832
19644
  }
18833
- }
18834
- async function handleList(options) {
18835
- const client2 = new DeeplineClient();
18836
- const secrets = await client2.listSecrets();
18837
- printCommandEnvelope(
18838
- {
18839
- secrets,
18840
- count: secrets.length,
18841
- render: {
18842
- sections: [
18843
- {
18844
- title: "secrets",
18845
- lines: secrets.length ? secrets.map(renderSecret) : ["No play secrets are configured."]
18846
- }
18847
- ]
18848
- }
18849
- },
18850
- { json: options.json }
19645
+ const quickstartUrl = `${baseUrl}/cli/quickstart?cb=${callback.port}&state=${state}`;
19646
+ console.error(" Opening the recipe picker in your browser.");
19647
+ console.error(` If it didn't open, cmd+click: ${quickstartUrl}`);
19648
+ if (options.open !== false) {
19649
+ openInBrowser(quickstartUrl);
19650
+ }
19651
+ const timeoutSeconds = Number.parseInt(options.timeout ?? "300", 10);
19652
+ const timeoutMs = (Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 300) * 1e3;
19653
+ const timeoutHandle = setTimeout(
19654
+ () => resolveSelection("timeout"),
19655
+ timeoutMs
18851
19656
  );
18852
- }
18853
- async function handleCheck(nameInput, options) {
18854
- const name = normalizeSecretName(nameInput);
18855
- const client2 = new DeeplineClient();
18856
- const secret = await client2.checkSecret(name);
19657
+ const progress = createCliProgress(!jsonOutput);
19658
+ progress.phase("waiting for your pick in the browser (Ctrl+C to skip)");
19659
+ const onSigint = () => resolveSelection("sigint");
19660
+ process.once("SIGINT", onSigint);
19661
+ const outcome = await selectionPromise;
19662
+ clearTimeout(timeoutHandle);
19663
+ process.removeListener("SIGINT", onSigint);
19664
+ callback.server.close();
19665
+ if (outcome === "sigint") {
19666
+ progress.fail();
19667
+ console.error("\nSkipped \u2014 run `deepline quickstart` anytime.");
19668
+ return EXIT_OK2;
19669
+ }
19670
+ if (outcome === "timeout") {
19671
+ progress.fail();
19672
+ console.error(
19673
+ "Timed out waiting for a selection. Run: deepline quickstart"
19674
+ );
19675
+ return EXIT_AUTH2;
19676
+ }
19677
+ progress.complete();
19678
+ const { prompt, workflowId } = outcome;
19679
+ const claudeCommand = `claude ${shellQuote(prompt)}`;
19680
+ const claudeAvailable = hasClaudeBinary();
19681
+ const willLaunch = options.launch !== false && !jsonOutput && interactive && claudeAvailable;
18857
19682
  printCommandEnvelope(
18858
19683
  {
18859
- ok: Boolean(secret),
18860
- name,
18861
- secret: secret ?? null,
19684
+ status: "submitted",
19685
+ workflowId,
19686
+ prompt,
19687
+ claudeCommand,
19688
+ url: quickstartUrl,
19689
+ launched: willLaunch,
18862
19690
  render: {
18863
19691
  sections: [
18864
19692
  {
18865
- title: "secret check",
19693
+ title: "quickstart",
18866
19694
  lines: [
18867
- secret ? `${name}: active` : `${name}: missing, disabled, or empty`
19695
+ ...workflowId ? [`Recipe: ${workflowId}`] : [],
19696
+ willLaunch ? "Launching Claude Code with your recipe..." : claudeAvailable ? "Run the command below to start your recipe." : "Claude Code not found. Install it (https://code.claude.com/docs/en/overview), then run the command below."
18868
19697
  ]
18869
19698
  }
18870
- ]
19699
+ ],
19700
+ actions: [{ label: "Run", command: claudeCommand }]
18871
19701
  }
18872
19702
  },
18873
- { json: options.json }
19703
+ { json: jsonOutput }
18874
19704
  );
18875
- if (!secret) process.exitCode = 4;
18876
- }
18877
- async function handleSet(nameInput, forbidden, options) {
18878
- preventShellHistoryLeak(forbidden);
18879
- const name = normalizeSecretName(nameInput);
18880
- const scope = options.scope === "play" ? "play" : "org";
18881
- const playName = options.play?.trim();
18882
- if (scope === "play" && !playName) {
18883
- throw new Error("--play <name> is required when --scope play is used.");
19705
+ if (willLaunch) {
19706
+ return launchClaude(prompt);
18884
19707
  }
18885
- const value = await readSecretValue();
18886
- const { http } = getAuthedHttpClient();
18887
- const response = await http.post(
18888
- "/api/v2/secrets",
18889
- {
18890
- name,
18891
- value,
18892
- scope,
18893
- ...playName ? { playName } : {}
18894
- }
18895
- );
18896
- const secret = response.secret;
18897
- printCommandEnvelope(
18898
- {
18899
- ok: true,
18900
- secret,
18901
- render: {
18902
- sections: [
18903
- {
18904
- title: "secret saved",
18905
- lines: [`${secret.name}: saved (${secret.scope})`]
18906
- }
18907
- ]
18908
- }
18909
- },
18910
- { json: options.json }
18911
- );
19708
+ return EXIT_OK2;
18912
19709
  }
18913
- function registerSecretsCommands(program) {
18914
- const secrets = program.command("secrets").description("Manage play secrets without revealing values.").addHelpText(
19710
+ function registerQuickstartCommands(program) {
19711
+ program.command("quickstart").description(
19712
+ "Pick a starter recipe in the browser and launch it with Claude Code."
19713
+ ).addHelpText(
18915
19714
  "after",
18916
19715
  `
18917
19716
  Notes:
18918
- Secret values are never accepted as command arguments, stdin pipes, env vars,
18919
- or files. Use deepline secrets set NAME and type the value at the hidden TTY
18920
- prompt. Agents can list/check metadata but should not enter secret values.
19717
+ Opens the hosted recipe picker in your browser. The picker sends your
19718
+ selection back to a temporary listener on 127.0.0.1, so the browser must run
19719
+ on the same machine as the CLI. Once a recipe arrives, the CLI prints the
19720
+ matching claude command and, when Claude Code is installed and the shell is
19721
+ interactive, launches it directly. Press Ctrl+C while waiting to skip.
18921
19722
 
18922
19723
  Examples:
18923
- deepline secrets list
18924
- deepline secrets check HUBSPOT_TOKEN
18925
- deepline secrets set HUBSPOT_TOKEN
18926
- `
18927
- );
18928
- secrets.command("list").description("List secret metadata only.").option("--json", "Emit JSON output").action(async (options) => {
18929
- await handleList(options);
18930
- });
18931
- secrets.command("check").description("Check whether a secret exists and is active.").argument("<name>", "Secret name").option("--json", "Emit JSON output").action(async (name, options) => {
18932
- await handleCheck(name, options);
18933
- });
18934
- secrets.command("set").description("Set or rotate a secret through a hidden interactive prompt.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play", "org").option("--play <name>", "Play name for play-scoped secrets").action(
18935
- async (name, forbidden, options) => {
18936
- await handleSet(name, forbidden ?? [], options);
18937
- }
18938
- );
18939
- }
18940
-
18941
- // src/cli/commands/sessions.ts
18942
- var import_node_fs11 = require("fs");
18943
- var import_node_os8 = require("os");
18944
- var import_node_path14 = require("path");
18945
- var import_node_zlib = require("zlib");
18946
- var import_node_crypto5 = require("crypto");
18947
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
18948
- var MAX_SESSION_UPLOAD_BYTES = 35e5;
18949
- var MAX_DIRECT_SESSION_DECODED_BYTES = 50 * 1024 * 1024;
18950
- var CHUNK_SIZE_BYTES = 25e5;
18951
- var MAX_EVENT_STRING_CHARS = 8e3;
18952
- var MAX_EVENT_LIST_ITEMS = 40;
18953
- var MAX_EVENT_OBJECT_KEYS = 80;
18954
- var TRUNCATION_MARKER = "...[truncated]";
18955
- var NOISE_EVENT_TYPES = /* @__PURE__ */ new Set(["progress", "file-history-snapshot"]);
18956
- function homeDir() {
18957
- return process.env.HOME?.trim() || (0, import_node_os8.homedir)();
18958
- }
18959
- function detectShellContext() {
18960
- const shellPath = process.env.SHELL?.trim() || process.env.ComSpec?.trim() || process.env.COMSPEC?.trim() || "";
18961
- return {
18962
- shell: shellPath ? (0, import_node_path14.basename)(shellPath).replace(/\.exe$/i, "") : "unknown",
18963
- shell_path: shellPath || null,
18964
- os: (0, import_node_os8.platform)(),
18965
- cwd: process.cwd()
18966
- };
18967
- }
18968
- function claudeProjectsRoot() {
18969
- return (0, import_node_path14.join)(homeDir(), ".claude", "projects");
18970
- }
18971
- function listClaudeSessionFiles() {
18972
- const root = claudeProjectsRoot();
18973
- if (!(0, import_node_fs11.existsSync)(root)) return [];
18974
- const projectDirs = readDirectoryNames(root);
18975
- const files = [];
18976
- for (const projectDir of projectDirs) {
18977
- const fullProjectDir = (0, import_node_path14.join)(root, projectDir);
18978
- for (const fileName of readDirectoryNames(fullProjectDir)) {
18979
- if (fileName.endsWith(".jsonl")) {
18980
- files.push((0, import_node_path14.join)(fullProjectDir, fileName));
18981
- }
18982
- }
18983
- }
18984
- return files;
18985
- }
18986
- function readDirectoryNames(dir) {
18987
- try {
18988
- return (0, import_node_fs11.readdirSync)(dir);
18989
- } catch {
18990
- return [];
18991
- }
18992
- }
18993
- function newestClaudeSessionFile() {
18994
- let newest = null;
18995
- for (const filePath of listClaudeSessionFiles()) {
18996
- try {
18997
- const stat4 = (0, import_node_fs11.statSync)(filePath);
18998
- if (!newest || stat4.mtimeMs > newest.mtimeMs) {
18999
- newest = { filePath, mtimeMs: stat4.mtimeMs };
19000
- }
19001
- } catch {
19002
- continue;
19003
- }
19004
- }
19005
- return newest?.filePath ?? null;
19006
- }
19007
- function sessionIdFromFilePath(filePath) {
19008
- return (0, import_node_path14.basename)(filePath, ".jsonl");
19724
+ deepline quickstart
19725
+ deepline quickstart --no-open
19726
+ deepline quickstart --no-launch --json
19727
+ deepline quickstart --timeout 120
19728
+ `
19729
+ ).option("--json", "Emit JSON output. Also automatic when stdout is piped").option("--no-open", "Do not open the browser; print the URL only").option("--no-launch", "Print the claude command instead of launching it").option(
19730
+ "--timeout <seconds>",
19731
+ "Maximum seconds to wait for a selection",
19732
+ "300"
19733
+ ).action(async (options) => {
19734
+ process.exitCode = await handleQuickstart(options);
19735
+ });
19009
19736
  }
19010
- function findSessionFile(sessionId) {
19011
- if (!UUID_RE.test(sessionId)) {
19737
+
19738
+ // src/cli/commands/secrets.ts
19739
+ var import_node_process = require("process");
19740
+ var hiddenInputBuffer = "";
19741
+ function normalizeSecretName(value) {
19742
+ const normalized = value.trim().toUpperCase();
19743
+ if (!/^[A-Z][A-Z0-9_]{1,63}$/.test(normalized)) {
19012
19744
  throw new Error(
19013
- "Invalid session ID format. Expected a UUID such as 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca."
19745
+ "Secret names must be 2-64 characters and use uppercase letters, numbers, and underscores."
19014
19746
  );
19015
19747
  }
19016
- for (const filePath of listClaudeSessionFiles()) {
19017
- if (sessionIdFromFilePath(filePath) === sessionId) {
19018
- return filePath;
19019
- }
19020
- }
19021
- return null;
19022
- }
19023
- function resolveSessionTargets(input2) {
19024
- const targets = [];
19025
- if (input2.currentSession) {
19026
- const currentFile = newestClaudeSessionFile();
19027
- if (!currentFile) {
19028
- throw new Error("No session files found in ~/.claude/projects/*/.");
19029
- }
19030
- const sessionId = sessionIdFromFilePath(currentFile);
19031
- targets.push({
19032
- sessionId,
19033
- label: `session-${sessionId}`,
19034
- filePath: currentFile
19035
- });
19036
- }
19037
- for (const [index, sessionId] of (input2.sessionIds ?? []).entries()) {
19038
- const filePath = findSessionFile(sessionId);
19039
- if (!filePath) {
19040
- throw new Error(
19041
- `Session file not found: ~/.claude/projects/*/${sessionId}.jsonl`
19042
- );
19043
- }
19044
- targets.push({
19045
- sessionId,
19046
- label: input2.labels?.[index] ?? `session-${sessionId}`,
19047
- filePath
19048
- });
19049
- }
19050
- if (targets.length === 0) {
19051
- throw new Error("One of --session-id or --current-session is required.");
19052
- }
19053
- return targets;
19054
- }
19055
- function parseJsonLine(line) {
19056
- try {
19057
- return JSON.parse(line);
19058
- } catch {
19059
- return null;
19060
- }
19748
+ return normalized;
19061
19749
  }
19062
- function normalizedJsonLines(raw) {
19063
- return raw.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
19750
+ function renderSecret(secret) {
19751
+ const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
19752
+ return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
19064
19753
  }
19065
- function stripNoiseEvents(raw) {
19066
- const lines = [];
19067
- for (const line of normalizedJsonLines(raw)) {
19068
- const parsed = parseJsonLine(line);
19069
- if (parsed && typeof parsed === "object" && NOISE_EVENT_TYPES.has(String(parsed.type ?? ""))) {
19070
- continue;
19071
- }
19072
- lines.push(line);
19754
+ async function readHiddenLine(prompt) {
19755
+ if (!import_node_process.stdin.isTTY || !import_node_process.stdout.isTTY) {
19756
+ throw new Error(
19757
+ "Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
19758
+ );
19073
19759
  }
19074
- return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
19075
- ` : "", "utf8");
19076
- }
19077
- function messageContentKey(value) {
19078
- const message = value.message;
19079
- if (!message || typeof message !== "object") return null;
19080
- const content = message.content;
19081
- if (typeof content === "string") return content;
19082
- if (!Array.isArray(content)) return null;
19083
- return content.map((block) => {
19084
- if (!block || typeof block !== "object") return String(block);
19085
- const record = block;
19086
- const type = String(record.type ?? "");
19087
- if (type === "tool_use") {
19088
- return `tool_use:${String(record.name ?? "")}:${String(record.id ?? "")}`;
19089
- }
19090
- if (type === "tool_result") {
19091
- return `tool_result:${String(record.tool_use_id ?? "")}`;
19092
- }
19093
- return String(record.text ?? type);
19094
- }).join("\n");
19095
- }
19096
- function dedupConsecutiveEvents(raw) {
19097
- const rawLines = normalizedJsonLines(raw);
19098
- const parsedEvents = rawLines.map(parseJsonLine);
19099
- const output2 = [];
19100
- let index = 0;
19101
- while (index < parsedEvents.length) {
19102
- const event = parsedEvents[index];
19103
- if (!event || typeof event !== "object") {
19104
- output2.push(rawLines[index] ?? "");
19105
- index += 1;
19106
- continue;
19107
- }
19108
- const record = event;
19109
- const eventType = String(record.type ?? "");
19110
- const eventKey = messageContentKey(record);
19111
- if (!["user", "assistant"].includes(eventType) || !eventKey) {
19112
- output2.push(rawLines[index] ?? "");
19113
- index += 1;
19114
- continue;
19115
- }
19116
- let runCount = 1;
19117
- let cursor = index + 1;
19118
- while (cursor < parsedEvents.length) {
19119
- const next = parsedEvents[cursor];
19120
- if (!next || typeof next !== "object") break;
19121
- const nextRecord = next;
19122
- if (String(nextRecord.type ?? "") !== eventType || messageContentKey(nextRecord) !== eventKey) {
19123
- break;
19760
+ import_node_process.stdout.write(prompt);
19761
+ const previousRawMode = import_node_process.stdin.isRaw;
19762
+ if (typeof import_node_process.stdin.setRawMode === "function") import_node_process.stdin.setRawMode(true);
19763
+ let value = "";
19764
+ import_node_process.stdin.resume();
19765
+ return await new Promise((resolve16, reject) => {
19766
+ let settled = false;
19767
+ const cleanup = () => {
19768
+ import_node_process.stdin.off("data", onData);
19769
+ import_node_process.stdin.off("end", onEnd);
19770
+ import_node_process.stdin.off("error", onError);
19771
+ if (typeof import_node_process.stdin.setRawMode === "function") {
19772
+ import_node_process.stdin.setRawMode(previousRawMode);
19124
19773
  }
19125
- runCount += 1;
19126
- cursor += 1;
19127
- }
19128
- if (runCount > 1) {
19129
- record._repeat_count = runCount;
19130
- record._repeat_summary = `${runCount} consecutive identical ${eventType} messages collapsed`;
19131
- output2.push(JSON.stringify(record));
19132
- index = cursor;
19133
- continue;
19774
+ };
19775
+ const finish = (line) => {
19776
+ if (settled) return;
19777
+ settled = true;
19778
+ import_node_process.stdout.write("\n");
19779
+ cleanup();
19780
+ resolve16(line);
19781
+ };
19782
+ const fail = (error) => {
19783
+ if (settled) return;
19784
+ settled = true;
19785
+ import_node_process.stdout.write("\n");
19786
+ cleanup();
19787
+ reject(error);
19788
+ };
19789
+ const processText = (text) => {
19790
+ for (let index = 0; index < text.length; index++) {
19791
+ const char = text[index];
19792
+ const code = char.charCodeAt(0);
19793
+ if (char === "\r" || char === "\n") {
19794
+ hiddenInputBuffer = text.slice(index + 1);
19795
+ finish(value);
19796
+ return;
19797
+ }
19798
+ if (code === 3) {
19799
+ fail(new Error("Secret input cancelled."));
19800
+ return;
19801
+ }
19802
+ if (code === 8 || code === 127) {
19803
+ value = value.slice(0, -1);
19804
+ continue;
19805
+ }
19806
+ if (code >= 32) {
19807
+ value += char;
19808
+ }
19809
+ }
19810
+ hiddenInputBuffer = "";
19811
+ };
19812
+ const onData = (chunk) => {
19813
+ processText(chunk.toString());
19814
+ };
19815
+ const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
19816
+ const onError = (error) => fail(error);
19817
+ import_node_process.stdin.on("data", onData);
19818
+ import_node_process.stdin.once("end", onEnd);
19819
+ import_node_process.stdin.once("error", onError);
19820
+ if (hiddenInputBuffer) {
19821
+ const buffered = hiddenInputBuffer;
19822
+ hiddenInputBuffer = "";
19823
+ processText(buffered);
19134
19824
  }
19135
- output2.push(rawLines[index] ?? "");
19136
- index += 1;
19137
- }
19138
- return Buffer.from(output2.length > 0 ? `${output2.join("\n")}
19139
- ` : "", "utf8");
19825
+ });
19140
19826
  }
19141
- function compactEventValue(value) {
19142
- if (typeof value === "string") {
19143
- if (value.length <= MAX_EVENT_STRING_CHARS) return value;
19144
- return `${value.slice(0, MAX_EVENT_STRING_CHARS - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`;
19145
- }
19146
- if (Array.isArray(value)) {
19147
- const compacted = value.slice(0, MAX_EVENT_LIST_ITEMS).map(compactEventValue);
19148
- if (value.length > MAX_EVENT_LIST_ITEMS) {
19149
- compacted.push(
19150
- `${TRUNCATION_MARKER} ${value.length - MAX_EVENT_LIST_ITEMS} more item(s)`
19151
- );
19152
- }
19153
- return compacted;
19827
+ async function readSecretValue() {
19828
+ const first = await readHiddenLine("Secret value: ");
19829
+ if (!first) throw new Error("Secret value is required.");
19830
+ const second = await readHiddenLine("Confirm secret value: ");
19831
+ if (first !== second) {
19832
+ throw new Error("Secret values did not match.");
19154
19833
  }
19155
- if (value && typeof value === "object") {
19156
- const entries = Object.entries(value);
19157
- const compacted = {};
19158
- for (const [key, item] of entries.slice(0, MAX_EVENT_OBJECT_KEYS)) {
19159
- compacted[key] = compactEventValue(item);
19160
- }
19161
- if (entries.length > MAX_EVENT_OBJECT_KEYS) {
19162
- compacted._truncated_keys = entries.length - MAX_EVENT_OBJECT_KEYS;
19163
- }
19164
- return compacted;
19834
+ return first;
19835
+ }
19836
+ function preventShellHistoryLeak(forbidden) {
19837
+ if (forbidden.length > 0) {
19838
+ throw new Error(
19839
+ "Do not pass secret values as command arguments. Run `deepline secrets set NAME` and enter the value at the hidden prompt."
19840
+ );
19165
19841
  }
19166
- return value;
19167
19842
  }
19168
- function selectiveCompactToolResults(raw) {
19169
- const lines = [];
19170
- for (const line of normalizedJsonLines(raw)) {
19171
- const parsed = parseJsonLine(line);
19172
- if (!parsed || typeof parsed !== "object") {
19173
- lines.push(line);
19174
- continue;
19175
- }
19176
- const record = parsed;
19177
- if (record.type === "user") {
19178
- const message = record.message;
19179
- const content = message && typeof message === "object" ? message.content : null;
19180
- if (Array.isArray(content)) {
19181
- message.content = content.map(
19182
- (block) => block && typeof block === "object" && block.type === "tool_result" ? compactEventValue(block) : block
19183
- );
19843
+ async function handleList(options) {
19844
+ const client2 = new DeeplineClient();
19845
+ const secrets = await client2.listSecrets();
19846
+ printCommandEnvelope(
19847
+ {
19848
+ secrets,
19849
+ count: secrets.length,
19850
+ render: {
19851
+ sections: [
19852
+ {
19853
+ title: "secrets",
19854
+ lines: secrets.length ? secrets.map(renderSecret) : ["No play secrets are configured."]
19855
+ }
19856
+ ]
19184
19857
  }
19185
- }
19186
- lines.push(JSON.stringify(record));
19187
- }
19188
- return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
19189
- ` : "", "utf8");
19858
+ },
19859
+ { json: options.json }
19860
+ );
19190
19861
  }
19191
- function prepareSessionBuffer(raw) {
19192
- return selectiveCompactToolResults(
19193
- dedupConsecutiveEvents(stripNoiseEvents(raw))
19862
+ async function handleCheck(nameInput, options) {
19863
+ const name = normalizeSecretName(nameInput);
19864
+ const client2 = new DeeplineClient();
19865
+ const secret = await client2.checkSecret(name);
19866
+ printCommandEnvelope(
19867
+ {
19868
+ ok: Boolean(secret),
19869
+ name,
19870
+ secret: secret ?? null,
19871
+ render: {
19872
+ sections: [
19873
+ {
19874
+ title: "secret check",
19875
+ lines: [
19876
+ secret ? `${name}: active` : `${name}: missing, disabled, or empty`
19877
+ ]
19878
+ }
19879
+ ]
19880
+ }
19881
+ },
19882
+ { json: options.json }
19194
19883
  );
19884
+ if (!secret) process.exitCode = 4;
19195
19885
  }
19196
- function buildSessionUploadContent(raw) {
19197
- const prepared = prepareSessionBuffer(raw);
19198
- const encoded = (0, import_node_zlib.gzipSync)(prepared).toString("base64");
19199
- if (encoded.length <= MAX_SESSION_UPLOAD_BYTES && prepared.length <= MAX_DIRECT_SESSION_DECODED_BYTES) {
19200
- return { encodedContent: encoded, needsChunking: false };
19886
+ async function handleSet(nameInput, forbidden, options) {
19887
+ preventShellHistoryLeak(forbidden);
19888
+ const name = normalizeSecretName(nameInput);
19889
+ const scope = options.scope === "play" ? "play" : "org";
19890
+ const playName = options.play?.trim();
19891
+ if (scope === "play" && !playName) {
19892
+ throw new Error("--play <name> is required when --scope play is used.");
19201
19893
  }
19202
- return { encodedContent: encoded, needsChunking: true };
19203
- }
19204
- async function uploadPayload(path, payload) {
19894
+ const value = await readSecretValue();
19205
19895
  const { http } = getAuthedHttpClient();
19206
- return await http.post(path, payload);
19207
- }
19208
- async function uploadChunkedSessions(sessions, options) {
19209
- const uploadId = (0, import_node_crypto5.randomUUID)();
19210
- for (const session of sessions) {
19211
- const bytes = Buffer.from(session.encodedContent, "base64");
19212
- const chunks = [];
19213
- for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE_BYTES) {
19214
- chunks.push(bytes.subarray(offset, offset + CHUNK_SIZE_BYTES));
19215
- }
19216
- process.stderr.write(
19217
- `Uploading ${session.label} in ${chunks.length} chunk(s)...
19218
- `
19219
- );
19220
- for (const [index, chunk] of chunks.entries()) {
19221
- await uploadPayload("/api/v2/cli/send-session/chunk", {
19222
- upload_id: uploadId,
19223
- session_id: session.sessionId,
19224
- index,
19225
- total_chunks: chunks.length,
19226
- data: chunk.toString("base64")
19227
- });
19896
+ const response = await http.post(
19897
+ "/api/v2/secrets",
19898
+ {
19899
+ name,
19900
+ value,
19901
+ scope,
19902
+ ...playName ? { playName } : {}
19228
19903
  }
19229
- }
19230
- const response = await uploadPayload("/api/v2/cli/send-session/finalize", {
19231
- upload_id: uploadId,
19232
- session_ids: sessions.map((session) => session.sessionId),
19233
- labels: sessions.map((session) => session.label),
19234
- environments: sessions.map(() => detectShellContext())
19235
- });
19904
+ );
19905
+ const secret = response.secret;
19236
19906
  printCommandEnvelope(
19237
19907
  {
19238
- ...response,
19239
19908
  ok: true,
19240
- uploaded: sessions.length,
19909
+ secret,
19241
19910
  render: {
19242
19911
  sections: [
19243
19912
  {
19244
- title: "sessions send",
19245
- lines: ["Session uploaded to #internal-reports (chunked)."]
19913
+ title: "secret saved",
19914
+ lines: [`${secret.name}: saved (${secret.scope})`]
19246
19915
  }
19247
19916
  ]
19248
19917
  }
@@ -19250,27 +19919,100 @@ async function uploadChunkedSessions(sessions, options) {
19250
19919
  { json: options.json }
19251
19920
  );
19252
19921
  }
19253
- async function handleSessionsSend(options) {
19254
- if (options.file) {
19255
- const filePath = (0, import_node_path14.resolve)(options.file);
19256
- if (!(0, import_node_fs11.existsSync)(filePath)) {
19257
- throw new Error(`File not found: ${options.file}`);
19922
+ function registerSecretsCommands(program) {
19923
+ const secrets = program.command("secrets").description("Manage play secrets without revealing values.").addHelpText(
19924
+ "after",
19925
+ `
19926
+ Notes:
19927
+ Secret values are never accepted as command arguments, stdin pipes, env vars,
19928
+ or files. Use deepline secrets set NAME and type the value at the hidden TTY
19929
+ prompt. Agents can list/check metadata but should not enter secret values.
19930
+
19931
+ Examples:
19932
+ deepline secrets list
19933
+ deepline secrets check HUBSPOT_TOKEN
19934
+ deepline secrets set HUBSPOT_TOKEN
19935
+ `
19936
+ );
19937
+ secrets.command("list").description("List secret metadata only.").option("--json", "Emit JSON output").action(async (options) => {
19938
+ await handleList(options);
19939
+ });
19940
+ secrets.command("check").description("Check whether a secret exists and is active.").argument("<name>", "Secret name").option("--json", "Emit JSON output").action(async (name, options) => {
19941
+ await handleCheck(name, options);
19942
+ });
19943
+ secrets.command("set").description("Set or rotate a secret through a hidden interactive prompt.").argument("<name>", "Secret name").argument("[forbidden...]", "Do not pass secret values here").option("--json", "Emit JSON output").option("--scope <scope>", "Secret scope: org or play", "org").option("--play <name>", "Play name for play-scoped secrets").action(
19944
+ async (name, forbidden, options) => {
19945
+ await handleSet(name, forbidden ?? [], options);
19258
19946
  }
19259
- const response2 = await uploadPayload("/api/v2/cli/send-session", {
19260
- file: (0, import_node_fs11.readFileSync)(filePath).toString("base64"),
19261
- filename: (0, import_node_path14.basename)(filePath)
19262
- });
19947
+ );
19948
+ }
19949
+
19950
+ // src/cli/commands/switch.ts
19951
+ var import_node_fs12 = require("fs");
19952
+ var import_node_os9 = require("os");
19953
+ var import_node_path15 = require("path");
19954
+ function hostSlugFromBaseUrl(baseUrl) {
19955
+ try {
19956
+ const url = new URL(baseUrl);
19957
+ const port = url.port ? Number.parseInt(url.port, 10) : null;
19958
+ let slug = (url.hostname || "unknown").replace(/[^a-zA-Z0-9]/g, "-");
19959
+ if (port && port !== 80 && port !== 443) {
19960
+ slug = `${slug}-${port}`;
19961
+ }
19962
+ return slug.toLowerCase().replace(/^-+|-+$/g, "") || "unknown";
19963
+ } catch {
19964
+ return "unknown";
19965
+ }
19966
+ }
19967
+ function resolveConfigScope() {
19968
+ const explicit = (process.env.DEEPLINE_CONFIG_SCOPE || "").trim();
19969
+ if (explicit) return explicit;
19970
+ return hostSlugFromBaseUrl(autoDetectBaseUrl());
19971
+ }
19972
+ function activeFamilyPath() {
19973
+ const home = process.env.HOME || process.env.USERPROFILE || (0, import_node_os9.homedir)();
19974
+ return (0, import_node_path15.join)(
19975
+ home,
19976
+ ".local",
19977
+ "deepline",
19978
+ resolveConfigScope(),
19979
+ "cli",
19980
+ ".active-family"
19981
+ );
19982
+ }
19983
+ function readActiveFamily() {
19984
+ const path = activeFamilyPath();
19985
+ try {
19986
+ return (0, import_node_fs12.readFileSync)(path, "utf-8").trim() || "sdk";
19987
+ } catch {
19988
+ return "sdk";
19989
+ }
19990
+ }
19991
+ function writeActiveFamily(family) {
19992
+ const path = activeFamilyPath();
19993
+ (0, import_node_fs12.mkdirSync)((0, import_node_path15.dirname)(path), { recursive: true });
19994
+ (0, import_node_fs12.writeFileSync)(path, `${family}
19995
+ `, "utf-8");
19996
+ return path;
19997
+ }
19998
+ function handleSwitch(action, options) {
19999
+ const normalized = (action || "status").trim().toLowerCase();
20000
+ if (normalized === "status") {
20001
+ const path = activeFamilyPath();
20002
+ const activeFamily = readActiveFamily();
19263
20003
  printCommandEnvelope(
19264
20004
  {
19265
- ...response2,
19266
20005
  ok: true,
19267
- filename: (0, import_node_path14.basename)(filePath),
20006
+ active_family: activeFamily,
20007
+ active_family_path: path,
20008
+ active_family_file_exists: (0, import_node_fs12.existsSync)(path),
19268
20009
  render: {
19269
20010
  sections: [
19270
20011
  {
19271
- title: "sessions send",
20012
+ title: "cli switch",
19272
20013
  lines: [
19273
- `File '${(0, import_node_path14.basename)(filePath)}' uploaded to #internal-reports.`
20014
+ `Active CLI family: ${activeFamily}`,
20015
+ `Active family file: ${path}`
19274
20016
  ]
19275
20017
  }
19276
20018
  ]
@@ -19278,213 +20020,99 @@ async function handleSessionsSend(options) {
19278
20020
  },
19279
20021
  { json: options.json }
19280
20022
  );
19281
- return;
19282
- }
19283
- const targets = resolveSessionTargets({
19284
- sessionIds: options.sessionId,
19285
- labels: options.label,
19286
- currentSession: options.currentSession
19287
- });
19288
- const built = targets.map((target) => {
19289
- const upload = buildSessionUploadContent((0, import_node_fs11.readFileSync)(target.filePath));
19290
- return { ...target, ...upload };
19291
- });
19292
- if (built.some((session) => session.needsChunking)) {
19293
- await uploadChunkedSessions(built, options);
19294
- return;
19295
- }
19296
- const response = built.length === 1 && !options.label?.length ? await uploadPayload("/api/v2/cli/send-session", {
19297
- session_id: built[0]?.sessionId,
19298
- content: built[0]?.encodedContent,
19299
- environment: detectShellContext()
19300
- }) : await uploadPayload("/api/v2/cli/send-session", {
19301
- sessions: built.map((session) => ({
19302
- session_id: session.sessionId,
19303
- content: session.encodedContent,
19304
- label: session.label,
19305
- environment: detectShellContext()
19306
- })),
19307
- environment: detectShellContext()
19308
- });
19309
- printCommandEnvelope(
19310
- {
19311
- ...response,
19312
- ok: true,
19313
- uploaded: built.length,
19314
- render: {
19315
- sections: [
19316
- {
19317
- title: "sessions send",
19318
- lines: ["Session uploaded to #internal-reports."]
19319
- }
19320
- ]
19321
- }
19322
- },
19323
- { json: options.json }
19324
- );
19325
- }
19326
- function fallbackViewerAssets() {
19327
- return {
19328
- css: [
19329
- "body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;margin:0;padding:16px;background:#fafafa;color:#111}",
19330
- ".section{background:#fff;border:1px solid #ddd;border-radius:8px;padding:12px;margin-bottom:12px}",
19331
- ".section h2{margin:0 0 8px 0;font-size:14px}",
19332
- "pre{margin:0;white-space:pre-wrap;word-break:break-word;background:#f6f8fa;border:1px solid #e3e5e8;border-radius:6px;padding:10px}"
19333
- ].join(""),
19334
- js: [
19335
- "(() => {",
19336
- 'const root=document.getElementById("main-content");',
19337
- 'const raw=document.getElementById("raw-sessions");',
19338
- "if(!root||!raw)return;",
19339
- 'let sessions=[];try{sessions=JSON.parse(raw.textContent||"[]")}catch{}',
19340
- 'root.innerHTML="";',
19341
- "for(const session of sessions){",
19342
- 'const section=document.createElement("section");section.className="section";',
19343
- 'const title=document.createElement("h2");title.textContent=String(session.label||"session");',
19344
- 'const pre=document.createElement("pre");',
19345
- 'pre.textContent=(Array.isArray(session.events)?session.events:[]).map((event)=>JSON.stringify(event)).join("\\n");',
19346
- "section.append(title,pre);root.appendChild(section);",
19347
- "}",
19348
- "})();"
19349
- ].join("")
19350
- };
19351
- }
19352
- function parsePreparedEvents(buffer) {
19353
- return normalizedJsonLines(buffer).map((line) => {
19354
- const parsed = parseJsonLine(line);
19355
- return parsed ?? line;
19356
- });
19357
- }
19358
- async function handleSessionsRender(options) {
19359
- const targets = resolveSessionTargets({
19360
- sessionIds: options.sessionId,
19361
- labels: options.label,
19362
- currentSession: options.currentSession
19363
- });
19364
- let outputPath = options.output ? (0, import_node_path14.resolve)(options.output) : "";
19365
- if (!outputPath) {
19366
- const outputDir = (0, import_node_path14.join)(process.cwd(), "deepline", "data");
19367
- (0, import_node_fs11.mkdirSync)(outputDir, { recursive: true });
19368
- outputPath = (0, import_node_path14.join)(
19369
- outputDir,
19370
- targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
19371
- );
20023
+ return 0;
20024
+ }
20025
+ if (normalized === "python" || normalized === "rollback") {
20026
+ const path = writeActiveFamily("python");
20027
+ printCommandEnvelope(
20028
+ {
20029
+ ok: true,
20030
+ active_family: "python",
20031
+ active_family_path: path,
20032
+ render: {
20033
+ sections: [
20034
+ {
20035
+ title: "cli switch",
20036
+ lines: [
20037
+ "Switched installer-managed `deepline` to the Python CLI."
20038
+ ]
20039
+ }
20040
+ ]
20041
+ }
20042
+ },
20043
+ { json: options.json }
20044
+ );
20045
+ return 0;
20046
+ }
20047
+ if (normalized === "sdk") {
20048
+ const path = writeActiveFamily("sdk");
20049
+ printCommandEnvelope(
20050
+ {
20051
+ ok: true,
20052
+ active_family: "sdk",
20053
+ active_family_path: path,
20054
+ render: {
20055
+ sections: [
20056
+ {
20057
+ title: "cli switch",
20058
+ lines: ["Switched installer-managed `deepline` to the SDK CLI."]
20059
+ }
20060
+ ]
20061
+ }
20062
+ },
20063
+ { json: options.json }
20064
+ );
20065
+ return 0;
20066
+ }
20067
+ const message = `Unknown switch target: ${action}. Use one of: status, sdk, python, rollback.`;
20068
+ const envelope = {
20069
+ ok: false,
20070
+ error: message,
20071
+ code: "usage_error",
20072
+ render: {
20073
+ sections: [{ title: "cli switch", lines: [message] }]
20074
+ }
20075
+ };
20076
+ const wantsJson = options.json === true;
20077
+ if (wantsJson) {
20078
+ printCommandEnvelope(envelope, { json: true });
19372
20079
  } else {
19373
- (0, import_node_fs11.mkdirSync)((0, import_node_path14.dirname)(outputPath), { recursive: true });
20080
+ process.stderr.write(`${message}
20081
+ `);
19374
20082
  }
19375
- const sessions = targets.map((target) => ({
19376
- label: target.label,
19377
- events: parsePreparedEvents(
19378
- prepareSessionBuffer((0, import_node_fs11.readFileSync)(target.filePath))
19379
- )
19380
- }));
19381
- const { css, js } = fallbackViewerAssets();
19382
- const refreshMeta = options.autoRefresh ? `<meta http-equiv="refresh" content="${Number.parseInt(options.autoRefresh, 10)}">` : "";
19383
- const rawJson = JSON.stringify(sessions).replace(/<\//g, "<\\/");
19384
- const html = `<!DOCTYPE html>
19385
- <html lang="en">
19386
- <head>
19387
- <meta charset="UTF-8">
19388
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
19389
- ${refreshMeta}
19390
- <title>Session Viewer</title>
19391
- <style>${css}</style>
19392
- </head>
19393
- <body>
19394
- <div class="layout">
19395
- <div class="main" id="main-content"></div>
19396
- </div>
19397
- <script type="application/json" id="raw-sessions">${rawJson}</script>
19398
- <script>${js}</script>
19399
- </body>
19400
- </html>`;
19401
- (0, import_node_fs11.writeFileSync)(outputPath, html, "utf8");
19402
- printCommandEnvelope(
19403
- {
19404
- ok: true,
19405
- file: outputPath,
19406
- session_count: targets.length,
19407
- render: {
19408
- sections: [
19409
- {
19410
- title: "sessions render",
19411
- lines: [`Rendered session viewer: ${outputPath}`]
19412
- }
19413
- ]
19414
- }
19415
- },
19416
- { json: options.json }
19417
- );
19418
- }
19419
- function collectOption(value, previous) {
19420
- previous.push(value);
19421
- return previous;
20083
+ return 2;
19422
20084
  }
19423
- function registerSessionsCommands(program) {
19424
- const sessions = program.command("sessions").description("Upload and render local agent session transcripts.").addHelpText(
20085
+ function registerSwitchCommands(program) {
20086
+ program.command("switch [target]").description(
20087
+ "Switch the installer-managed Deepline CLI between SDK and Python families."
20088
+ ).option("--json", "Emit JSON output").addHelpText(
19425
20089
  "after",
19426
20090
  `
19427
20091
  Notes:
19428
- Session commands operate on local Claude session JSONL files under
19429
- ~/.claude/projects. send uploads a compacted transcript or file to Deepline.
19430
- render writes a local HTML viewer.
20092
+ This command changes only the local installer-managed wrapper state. It does
20093
+ not re-authenticate, reinstall packages, or contact Deepline servers.
19431
20094
 
19432
20095
  Examples:
19433
- deepline sessions send --current-session --json
19434
- deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca
19435
- deepline sessions render --current-session --output session.html
19436
- `
19437
- );
19438
- sessions.command("send").description("Upload session transcript(s) or a local file to Deepline.").addHelpText(
19439
- "after",
19440
- `
19441
- Examples:
19442
- deepline sessions send --current-session --json
19443
- deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --label "pilot run"
19444
- deepline sessions send --file ./debug.log --json
19445
- `
19446
- ).option(
19447
- "--session-id <uuid>",
19448
- "Claude session UUID. Repeat for multiple sessions.",
19449
- collectOption,
19450
- []
19451
- ).option(
19452
- "--label <label>",
19453
- "Label for the preceding session id",
19454
- collectOption,
19455
- []
19456
- ).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);
19457
- sessions.command("render").description("Render local session transcript(s) to an HTML viewer.").addHelpText(
19458
- "after",
19459
- `
19460
- Examples:
19461
- deepline sessions render --current-session
19462
- deepline sessions render --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --output session.html
19463
- deepline sessions render --current-session --auto-refresh 5 --json
20096
+ deepline switch status
20097
+ deepline switch python
20098
+ deepline switch rollback
20099
+ deepline switch sdk
19464
20100
  `
19465
- ).option(
19466
- "--session-id <uuid>",
19467
- "Claude session UUID. Repeat for multiple sessions.",
19468
- collectOption,
19469
- []
19470
- ).option(
19471
- "--label <label>",
19472
- "Label for the preceding session id",
19473
- collectOption,
19474
- []
19475
- ).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);
20101
+ ).action((target, options) => {
20102
+ process.exitCode = handleSwitch(target, options);
20103
+ });
19476
20104
  }
19477
20105
 
19478
20106
  // src/cli/commands/tools.ts
19479
20107
  var import_commander2 = require("commander");
20108
+ var import_node_fs14 = require("fs");
20109
+ var import_node_os11 = require("os");
20110
+ var import_node_path17 = require("path");
20111
+
20112
+ // src/tool-output.ts
19480
20113
  var import_node_fs13 = require("fs");
19481
20114
  var import_node_os10 = require("os");
19482
20115
  var import_node_path16 = require("path");
19483
-
19484
- // src/tool-output.ts
19485
- var import_node_fs12 = require("fs");
19486
- var import_node_os9 = require("os");
19487
- var import_node_path15 = require("path");
19488
20116
  function isPlainObject(value) {
19489
20117
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19490
20118
  }
@@ -19580,19 +20208,19 @@ function tryConvertToList(payload, options) {
19580
20208
  return null;
19581
20209
  }
19582
20210
  function ensureOutputDir() {
19583
- const outputDir = (0, import_node_path15.join)((0, import_node_os9.homedir)(), ".local", "share", "deepline", "data");
19584
- (0, import_node_fs12.mkdirSync)(outputDir, { recursive: true });
20211
+ const outputDir = (0, import_node_path16.join)((0, import_node_os10.homedir)(), ".local", "share", "deepline", "data");
20212
+ (0, import_node_fs13.mkdirSync)(outputDir, { recursive: true });
19585
20213
  return outputDir;
19586
20214
  }
19587
20215
  function writeJsonOutputFile(payload, stem) {
19588
20216
  const outputDir = ensureOutputDir();
19589
- const outputPath = (0, import_node_path15.join)(outputDir, `${stem}_${Date.now()}.json`);
19590
- (0, import_node_fs12.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
20217
+ const outputPath = (0, import_node_path16.join)(outputDir, `${stem}_${Date.now()}.json`);
20218
+ (0, import_node_fs13.writeFileSync)(outputPath, JSON.stringify(payload, null, 2), "utf-8");
19591
20219
  return outputPath;
19592
20220
  }
19593
20221
  function writeCsvOutputFile(rows, stem) {
19594
20222
  const outputDir = ensureOutputDir();
19595
- const outputPath = (0, import_node_path15.join)(outputDir, `${stem}_${Date.now()}.csv`);
20223
+ const outputPath = (0, import_node_path16.join)(outputDir, `${stem}_${Date.now()}.csv`);
19596
20224
  const seen = /* @__PURE__ */ new Set();
19597
20225
  const columns = [];
19598
20226
  for (const row of rows) {
@@ -19615,7 +20243,7 @@ function writeCsvOutputFile(rows, stem) {
19615
20243
  for (const row of rows) {
19616
20244
  lines.push(columns.map((column) => escapeCell(row[column])).join(","));
19617
20245
  }
19618
- (0, import_node_fs12.writeFileSync)(outputPath, `${lines.join("\n")}
20246
+ (0, import_node_fs13.writeFileSync)(outputPath, `${lines.join("\n")}
19619
20247
  `, "utf-8");
19620
20248
  const previewRows = rows.slice(0, 5);
19621
20249
  const previewColumns = columns.slice(0, 5);
@@ -19654,13 +20282,12 @@ var TOOL_COMMAND_TEMPLATES = {
19654
20282
  };
19655
20283
  function toListedTool(tool) {
19656
20284
  if (isPlayLikeTool(tool)) {
19657
- const playReference = playReferenceForTool(tool);
19658
20285
  return {
19659
20286
  ...tool,
19660
20287
  description: listedToolDescription(tool),
19661
20288
  id: tool.toolId,
19662
20289
  type: "play",
19663
- executeCommand: `deepline plays run ${playReference} --input '{...}' --watch`
20290
+ executeCommand: playRunCommandForTool(tool, tool.toolId)
19664
20291
  };
19665
20292
  }
19666
20293
  return {
@@ -19874,17 +20501,32 @@ function isPlayLikeTool(tool) {
19874
20501
  }
19875
20502
  function playReferenceForTool(tool) {
19876
20503
  const record = tool;
19877
- const toolId = typeof record.toolId === "string" ? record.toolId : "play";
19878
- return `prebuilt/${toolId.replace(/_/g, "-")}`;
20504
+ const declared = stringField(record, "playReference", "play_reference");
20505
+ if (declared.startsWith("prebuilt/")) {
20506
+ return declared;
20507
+ }
20508
+ return null;
20509
+ }
20510
+ function playSearchCommandForToolId(toolId) {
20511
+ return `deepline plays search ${JSON.stringify(toolId.replace(/_/g, " "))} --json`;
19879
20512
  }
19880
- function playLikeToolExecuteErrorMessage(toolId) {
19881
- const playReference = `prebuilt/${toolId.replace(/_/g, "-")}`;
19882
- return `${toolId} is a workflow/play entry, not an atomic provider tool.
19883
- Use: deepline plays run ${playReference} --input '{...}' --watch
19884
- Or search provider tools only with: deepline tools search "<query>" --json`;
20513
+ function playRunCommandForTool(tool, toolId, input2) {
20514
+ const playReference = playReferenceForTool(tool);
20515
+ if (!playReference) return playSearchCommandForToolId(toolId);
20516
+ const inputArg = input2 === void 0 ? "'{...}'" : `'${JSON.stringify(input2)}'`;
20517
+ return `deepline plays run ${playReference} --input ${inputArg} --watch`;
20518
+ }
20519
+ function playLikeToolExecuteErrorMessage(toolId, tool) {
20520
+ const playReference = tool ? playReferenceForTool(tool) : null;
20521
+ const nextStep = playReference ? `Use: deepline plays run ${playReference} --input '{...}' --watch` : `Find the maintained workflow with: ${playSearchCommandForToolId(toolId)}`;
20522
+ return [
20523
+ `${toolId} is a workflow/play entry, not an atomic provider tool.`,
20524
+ nextStep,
20525
+ 'Or search provider tools only with: deepline tools search "<query>" --json'
20526
+ ].join("\n");
19885
20527
  }
19886
- function printPlayLikeToolExecuteError(toolId) {
19887
- console.error(playLikeToolExecuteErrorMessage(toolId));
20528
+ function printPlayLikeToolExecuteError(toolId, tool) {
20529
+ console.error(playLikeToolExecuteErrorMessage(toolId, tool));
19888
20530
  }
19889
20531
  function registerToolsCommands(program) {
19890
20532
  const tools = program.command("tools").description("Search, describe, and execute atomic provider tools.").addHelpText(
@@ -20182,13 +20824,14 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
20182
20824
  "deeplineUsdPerPricingUnit",
20183
20825
  "deepline_usd_per_pricing_unit"
20184
20826
  );
20185
- const starterScript = extractedLists.length > 0 ? starterScriptJson(
20827
+ const starterScript = !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
20186
20828
  seedToolListScript({
20187
20829
  toolId,
20188
20830
  payload: samplePayloadForInputFields(inputFields),
20189
20831
  rows: []
20190
20832
  })
20191
20833
  ) : null;
20834
+ const executeCommand = isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
20192
20835
  return {
20193
20836
  schemaVersion: 1,
20194
20837
  toolId,
@@ -20213,7 +20856,7 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
20213
20856
  extractedLists,
20214
20857
  extractedValues
20215
20858
  },
20216
- executeCommand: `deepline tools execute ${toolId} --input '{...}' --json`,
20859
+ executeCommand,
20217
20860
  ...starterScript ? { starterScript } : {}
20218
20861
  };
20219
20862
  }
@@ -20325,6 +20968,10 @@ function printToolSchemaOnly(tool, requestedToolId) {
20325
20968
  }
20326
20969
  }
20327
20970
  function printToolExamplesOnly(tool, requestedToolId, options = {}) {
20971
+ if (isPlayLikeTool(tool)) {
20972
+ printPlayLikeToolUsage(tool, requestedToolId);
20973
+ return;
20974
+ }
20328
20975
  const contract = toolContractJsonForDescribe(tool, requestedToolId);
20329
20976
  const toolId = String(contract.toolId);
20330
20977
  const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
@@ -20356,6 +21003,25 @@ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
20356
21003
  printSamples(samples);
20357
21004
  }
20358
21005
  }
21006
+ function printPlayLikeToolUsage(tool, requestedToolId) {
21007
+ const toolId = String(tool.toolId || requestedToolId);
21008
+ const inputFields = toolInputFieldsForDisplay(
21009
+ recordField2(tool, "inputSchema", "input_schema")
21010
+ );
21011
+ const sampleInput = samplePayloadForInputFields(inputFields);
21012
+ const playReference = playReferenceForTool(tool);
21013
+ console.log("Run as a play:");
21014
+ console.log(
21015
+ playRunCommandForTool(
21016
+ tool,
21017
+ toolId,
21018
+ Object.keys(sampleInput).length ? sampleInput : void 0
21019
+ )
21020
+ );
21021
+ if (playReference) {
21022
+ console.log(`deepline plays describe ${playReference} --json`);
21023
+ }
21024
+ }
20359
21025
  function printToolGettersOnly(tool, requestedToolId) {
20360
21026
  const contract = toolContractJsonForDescribe(tool, requestedToolId);
20361
21027
  const getters = isRecord8(contract.getters) ? contract.getters : {};
@@ -20442,13 +21108,14 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
20442
21108
  const extractedLists = extractionContractEntries(
20443
21109
  arrayField(toolExecutionResult, "extractedLists", "extracted_lists")
20444
21110
  );
20445
- const starterScript = extractedLists.length > 0 ? starterScriptJson(
21111
+ const starterScript = !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
20446
21112
  seedToolListScript({
20447
21113
  toolId,
20448
21114
  payload: samplePayloadForInputFields(inputFields),
20449
21115
  rows: []
20450
21116
  })
20451
21117
  ) : null;
21118
+ const observedOutputCommand = isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
20452
21119
  const {
20453
21120
  cost: _cost,
20454
21121
  deeplineCreditsPerPricingUnit: _deeplineCreditsPerPricingUnit,
@@ -20474,8 +21141,8 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
20474
21141
  getterScope: "extractedValues/extractedLists .get() only works for declared Deepline getters listed in usageGuidance.toolExecutionResult.",
20475
21142
  rawToolResponse: "Use toolExecutionResult.toolResponse.raw for provider/tool-specific fields, fields in outputSchema that are not declared getters, and debugging.",
20476
21143
  invalidGetterHint: "If TypeScript says an extractedValues/extractedLists property does not exist, that field is not a declared Deepline getter.",
20477
- observeActualShape: `deepline tools execute ${toolId} --input '{...}' --json`,
20478
- observedOutput: `deepline tools execute ${toolId} --input '{...}' --json`,
21144
+ observeActualShape: observedOutputCommand,
21145
+ observedOutput: observedOutputCommand,
20479
21146
  forPlayGetterBugs: "Run a tiny play, inspect `deepline runs get <run-id> --full --json`, and export returned dataset handles with `deepline runs export`. Backing tables exist only for ctx.map(...).run(...) stages that actually executed.",
20480
21147
  executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run output and returned dataset handles."
20481
21148
  },
@@ -20630,11 +21297,11 @@ function normalizeOutputFormat(raw) {
20630
21297
  }
20631
21298
  function resolveAtFilePath(rawPath) {
20632
21299
  const trimmed = rawPath.trim();
20633
- const resolved = (0, import_node_path16.resolve)(trimmed);
20634
- if ((0, import_node_fs13.existsSync)(resolved)) return resolved;
21300
+ const resolved = (0, import_node_path17.resolve)(trimmed);
21301
+ if ((0, import_node_fs14.existsSync)(resolved)) return resolved;
20635
21302
  if (process.platform !== "win32" && trimmed.includes("\\")) {
20636
- const normalized = (0, import_node_path16.resolve)(trimmed.replace(/\\/g, "/"));
20637
- if ((0, import_node_fs13.existsSync)(normalized)) return normalized;
21303
+ const normalized = (0, import_node_path17.resolve)(trimmed.replace(/\\/g, "/"));
21304
+ if ((0, import_node_fs14.existsSync)(normalized)) return normalized;
20638
21305
  }
20639
21306
  return resolved;
20640
21307
  }
@@ -20645,7 +21312,7 @@ function readJsonArgument(raw, flagName) {
20645
21312
  throw new Error(`Invalid ${flagName} value: empty @file path.`);
20646
21313
  }
20647
21314
  try {
20648
- return (0, import_node_fs13.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
21315
+ return (0, import_node_fs14.readFileSync)(resolveAtFilePath(filePath), "utf8").replace(
20649
21316
  /^\uFEFF/,
20650
21317
  ""
20651
21318
  );
@@ -20747,9 +21414,9 @@ function starterScriptJson(script) {
20747
21414
  function seedToolListScript(input2) {
20748
21415
  const stem = safeFileStem(input2.toolId);
20749
21416
  const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
20750
- const scriptDir = (0, import_node_fs13.mkdtempSync)((0, import_node_path16.join)((0, import_node_os10.tmpdir)(), "deepline-workflow-seed-"));
20751
- (0, import_node_fs13.chmodSync)(scriptDir, 448);
20752
- const scriptPath = (0, import_node_path16.join)(scriptDir, fileName);
21417
+ const scriptDir = (0, import_node_fs14.mkdtempSync)((0, import_node_path17.join)((0, import_node_os11.tmpdir)(), "deepline-workflow-seed-"));
21418
+ (0, import_node_fs14.chmodSync)(scriptDir, 448);
21419
+ const scriptPath = (0, import_node_path17.join)(scriptDir, fileName);
20753
21420
  const projectDir = `deepline/projects/${stem}-workflow`;
20754
21421
  const playName = `${stem}-workflow`;
20755
21422
  const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
@@ -20785,7 +21452,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
20785
21452
  };
20786
21453
  });
20787
21454
  `;
20788
- (0, import_node_fs13.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
21455
+ (0, import_node_fs14.writeFileSync)(scriptPath, script, { encoding: "utf-8", mode: 384 });
20789
21456
  return {
20790
21457
  path: scriptPath,
20791
21458
  sourceCode: script,
@@ -20886,9 +21553,19 @@ async function executeTool(args) {
20886
21553
  }
20887
21554
  if (isPlayLikeTool(metadata)) {
20888
21555
  if (argsWantJson(args)) {
20889
- printJsonError(new Error(playLikeToolExecuteErrorMessage(parsed.toolId)));
21556
+ printJsonError(
21557
+ new Error(
21558
+ playLikeToolExecuteErrorMessage(
21559
+ parsed.toolId,
21560
+ metadata
21561
+ )
21562
+ )
21563
+ );
20890
21564
  } else {
20891
- printPlayLikeToolExecuteError(parsed.toolId);
21565
+ printPlayLikeToolExecuteError(
21566
+ parsed.toolId,
21567
+ metadata
21568
+ );
20892
21569
  }
20893
21570
  return 2;
20894
21571
  }
@@ -21040,7 +21717,7 @@ async function executeTool(args) {
21040
21717
 
21041
21718
  // src/cli/commands/workflow.ts
21042
21719
  var import_promises6 = require("fs/promises");
21043
- var import_node_path17 = require("path");
21720
+ var import_node_path18 = require("path");
21044
21721
 
21045
21722
  // src/cli/workflow-to-play.ts
21046
21723
  var import_node_crypto6 = require("crypto");
@@ -21247,7 +21924,7 @@ function readStatus(payload) {
21247
21924
  }
21248
21925
  async function readJsonOption(payload, file) {
21249
21926
  if (file) {
21250
- const raw = await (0, import_promises6.readFile)((0, import_node_path17.resolve)(file), "utf8");
21927
+ const raw = await (0, import_promises6.readFile)((0, import_node_path18.resolve)(file), "utf8");
21251
21928
  return JSON.parse(raw);
21252
21929
  }
21253
21930
  if (payload) {
@@ -21281,8 +21958,8 @@ async function transformOne(api, workflowId, outDir, publish) {
21281
21958
  revision.config,
21282
21959
  { workflowName: workflow.name, version: revision.version }
21283
21960
  );
21284
- const file = (0, import_node_path17.join)((0, import_node_path17.resolve)(outDir), `${compiled.playName}.play.ts`);
21285
- await (0, import_promises6.mkdir)((0, import_node_path17.dirname)(file), { recursive: true });
21961
+ const file = (0, import_node_path18.join)((0, import_node_path18.resolve)(outDir), `${compiled.playName}.play.ts`);
21962
+ await (0, import_promises6.mkdir)((0, import_node_path18.dirname)(file), { recursive: true });
21286
21963
  await (0, import_promises6.writeFile)(file, compiled.sourceCode, "utf8");
21287
21964
  let published = false;
21288
21965
  if (publish) {
@@ -21529,8 +22206,8 @@ Notes:
21529
22206
 
21530
22207
  // src/cli/commands/update.ts
21531
22208
  var import_node_child_process2 = require("child_process");
21532
- var import_node_fs14 = require("fs");
21533
- var import_node_path18 = require("path");
22209
+ var import_node_fs15 = require("fs");
22210
+ var import_node_path19 = require("path");
21534
22211
  function posixShellQuote(value) {
21535
22212
  return `'${value.replace(/'/g, `'\\''`)}'`;
21536
22213
  }
@@ -21549,19 +22226,19 @@ function buildSourceUpdateCommand(sourceRoot) {
21549
22226
  return `${cdCommand} && git fetch origin main --tags && git merge --ff-only origin/main`;
21550
22227
  }
21551
22228
  function findRepoBackedSdkRoot(startPath) {
21552
- let current = (0, import_node_path18.resolve)(startPath);
22229
+ let current = (0, import_node_path19.resolve)(startPath);
21553
22230
  while (true) {
21554
- if ((0, import_node_fs14.existsSync)((0, import_node_path18.join)(current, "sdk", "package.json")) && (0, import_node_fs14.existsSync)((0, import_node_path18.join)(current, "sdk", "bin", "deepline-dev.ts"))) {
22231
+ if ((0, import_node_fs15.existsSync)((0, import_node_path19.join)(current, "sdk", "package.json")) && (0, import_node_fs15.existsSync)((0, import_node_path19.join)(current, "sdk", "bin", "deepline-dev.ts"))) {
21555
22232
  return current;
21556
22233
  }
21557
- const parent = (0, import_node_path18.dirname)(current);
22234
+ const parent = (0, import_node_path19.dirname)(current);
21558
22235
  if (parent === current) return null;
21559
22236
  current = parent;
21560
22237
  }
21561
22238
  }
21562
22239
  function resolveUpdatePlan() {
21563
- const entrypoint = process.argv[1] ? (0, import_node_path18.resolve)(process.argv[1]) : "";
21564
- const sourceRoot = entrypoint ? findRepoBackedSdkRoot((0, import_node_path18.dirname)(entrypoint)) : null;
22240
+ const entrypoint = process.argv[1] ? (0, import_node_path19.resolve)(process.argv[1]) : "";
22241
+ const sourceRoot = entrypoint ? findRepoBackedSdkRoot((0, import_node_path19.dirname)(entrypoint)) : null;
21565
22242
  if (sourceRoot) {
21566
22243
  return {
21567
22244
  kind: "source",
@@ -21728,6 +22405,23 @@ var install_commands_default = {
21728
22405
 
21729
22406
  // src/cli/install-commands.ts
21730
22407
  var INSTALL_COMMANDS = install_commands_default;
22408
+ var DEFAULT_V1_SKILL_NAMES = [
22409
+ "build-tam",
22410
+ "clay-to-deepline",
22411
+ "deepline-analytics",
22412
+ "deepline-feedback",
22413
+ "deepline-gtm",
22414
+ "deepline-quickstart",
22415
+ "find-qualified-titles",
22416
+ "linkedin-url-lookup",
22417
+ "niche-signal-discovery",
22418
+ "portfolio-prospecting",
22419
+ "workflow-hello-world"
22420
+ ];
22421
+ var DEFAULT_SDK_SKILL_NAMES = [
22422
+ ...DEFAULT_V1_SKILL_NAMES,
22423
+ "deepline-plays"
22424
+ ];
21731
22425
  function normalizeBaseUrl2(baseUrl) {
21732
22426
  return baseUrl.replace(/\/$/, "");
21733
22427
  }
@@ -21758,10 +22452,7 @@ function buildSkillsAddArgs(baseUrl, skillName, options = {}) {
21758
22452
  return INSTALL_COMMANDS.skills.default_agents;
21759
22453
  }
21760
22454
  if (arg === "{skill_name}") {
21761
- return [
21762
- value,
21763
- ...extraSkillNames.flatMap((name) => ["--skill", name])
21764
- ];
22455
+ return [value, ...extraSkillNames.flatMap((name) => ["--skill", name])];
21765
22456
  }
21766
22457
  return value;
21767
22458
  }
@@ -21805,8 +22496,8 @@ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
21805
22496
  if (currentFamily === "sdk") {
21806
22497
  lines.push(
21807
22498
  "",
21808
- " To stay on the SDK CLI, install the SDK agent skill:",
21809
- ` ${skillsInstallCommand(baseUrl, "deepline-plays")}`,
22499
+ " To stay on the SDK CLI, refresh the Deepline agent skills:",
22500
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
21810
22501
  " To use the legacy Python CLI instead:",
21811
22502
  ` ${legacyPythonInstallCommand(baseUrl)}`,
21812
22503
  " `deepline update` updates this SDK CLI, but it will not switch CLI families."
@@ -21817,9 +22508,9 @@ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
21817
22508
  } else {
21818
22509
  lines.push(
21819
22510
  "",
21820
- " To use SDK commands, install the SDK CLI and SDK agent skill:",
22511
+ " To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
21821
22512
  ` ${sdkNpmGlobalInstallCommand()}`,
21822
- ` ${skillsInstallCommand(baseUrl, "deepline-plays")}`,
22513
+ ` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
21823
22514
  " `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
21824
22515
  );
21825
22516
  if (compatibility.python_alternative) {
@@ -21900,16 +22591,11 @@ async function maybeAutoUpdateAndRelaunch(response) {
21900
22591
 
21901
22592
  // src/cli/skills-sync.ts
21902
22593
  var import_node_child_process4 = require("child_process");
21903
- var import_node_fs15 = require("fs");
21904
- var import_node_os11 = require("os");
21905
- var import_node_path19 = require("path");
22594
+ var import_node_fs16 = require("fs");
22595
+ var import_node_os12 = require("os");
22596
+ var import_node_path20 = require("path");
21906
22597
  var CHECK_TIMEOUT_MS2 = 3e3;
21907
- var SDK_SKILL_NAME = "deepline-plays";
21908
- var SDK_SKILL_NAMES = [
21909
- SDK_SKILL_NAME,
21910
- "deepline-plays-feedback",
21911
- "deepline-plays-quickstart"
21912
- ];
22598
+ var SDK_PLAY_SKILL_NAME = "deepline-plays";
21913
22599
  var attemptedSync = false;
21914
22600
  function shouldSkipSkillsSync() {
21915
22601
  const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
@@ -21921,20 +22607,20 @@ function activePluginSkillsDir() {
21921
22607
  return "";
21922
22608
  }
21923
22609
  const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
21924
- return dir && (0, import_node_fs15.existsSync)(dir) ? dir : "";
22610
+ return dir && (0, import_node_fs16.existsSync)(dir) ? dir : "";
21925
22611
  }
21926
22612
  function readPluginSkillsVersion() {
21927
22613
  const dir = activePluginSkillsDir();
21928
22614
  if (!dir) return "";
21929
22615
  try {
21930
- return (0, import_node_fs15.readFileSync)((0, import_node_path19.join)(dir, ".version"), "utf-8").trim();
22616
+ return (0, import_node_fs16.readFileSync)((0, import_node_path20.join)(dir, ".version"), "utf-8").trim();
21931
22617
  } catch {
21932
22618
  return "";
21933
22619
  }
21934
22620
  }
21935
22621
  function sdkSkillsVersionPath(baseUrl) {
21936
- const home = process.env.HOME?.trim() || (0, import_node_os11.homedir)();
21937
- return (0, import_node_path19.join)(
22622
+ const home = process.env.HOME?.trim() || (0, import_node_os12.homedir)();
22623
+ return (0, import_node_path20.join)(
21938
22624
  home,
21939
22625
  ".local",
21940
22626
  "deepline",
@@ -21947,55 +22633,46 @@ function readLocalSkillsVersion(baseUrl) {
21947
22633
  const pluginVersion = readPluginSkillsVersion();
21948
22634
  if (pluginVersion) return pluginVersion;
21949
22635
  const path = sdkSkillsVersionPath(baseUrl);
21950
- if (!(0, import_node_fs15.existsSync)(path)) return "";
22636
+ if (!(0, import_node_fs16.existsSync)(path)) return "";
21951
22637
  try {
21952
- return (0, import_node_fs15.readFileSync)(path, "utf-8").trim();
22638
+ return (0, import_node_fs16.readFileSync)(path, "utf-8").trim();
21953
22639
  } catch {
21954
22640
  return "";
21955
22641
  }
21956
22642
  }
21957
22643
  function writeLocalSkillsVersion(baseUrl, version) {
21958
22644
  const path = sdkSkillsVersionPath(baseUrl);
21959
- (0, import_node_fs15.mkdirSync)((0, import_node_path19.dirname)(path), { recursive: true });
21960
- (0, import_node_fs15.writeFileSync)(path, `${version}
22645
+ (0, import_node_fs16.mkdirSync)((0, import_node_path20.dirname)(path), { recursive: true });
22646
+ (0, import_node_fs16.writeFileSync)(path, `${version}
21961
22647
  `, "utf-8");
21962
22648
  }
21963
- function installedSdkSkillHasStalePositionalExecuteExamples() {
21964
- const home = process.env.HOME?.trim() || (0, import_node_os11.homedir)();
21965
- const pluginSkillsDir = activePluginSkillsDir();
21966
- const roots = pluginSkillsDir ? [(0, import_node_path19.join)(pluginSkillsDir, SDK_SKILL_NAME)] : [
21967
- (0, import_node_path19.join)(home, ".claude", "skills", SDK_SKILL_NAME),
21968
- (0, import_node_path19.join)(home, ".agents", "skills", SDK_SKILL_NAME)
21969
- ];
21970
- const staleMarkers = [
21971
- "ctx.tools.execute(key",
21972
- "ctx.tools.execute('",
21973
- 'ctx.tools.execute("',
21974
- "rowCtx.tools.execute('",
21975
- 'rowCtx.tools.execute("'
21976
- ];
21977
- const scan = (dir) => {
21978
- for (const entry of (0, import_node_fs15.readdirSync)(dir)) {
21979
- const path = (0, import_node_path19.join)(dir, entry);
21980
- const stat4 = (0, import_node_fs15.statSync)(path);
21981
- if (stat4.isDirectory()) {
21982
- if (scan(path)) return true;
21983
- continue;
21984
- }
21985
- if (!entry.endsWith(".md")) continue;
21986
- const text = (0, import_node_fs15.readFileSync)(path, "utf-8");
21987
- if (staleMarkers.some((marker) => text.includes(marker))) return true;
21988
- }
21989
- return false;
21990
- };
21991
- for (const root of roots) {
21992
- try {
21993
- if ((0, import_node_fs15.existsSync)(root) && scan(root)) return true;
21994
- } catch {
21995
- continue;
21996
- }
22649
+ function sortedUniqueSkillNames(names) {
22650
+ return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
22651
+ (a, b) => a.localeCompare(b)
22652
+ );
22653
+ }
22654
+ async function fetchV1SkillNames(baseUrl) {
22655
+ const controller = new AbortController();
22656
+ const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
22657
+ try {
22658
+ const response = await fetch(
22659
+ new URL("/.well-known/skills/index.json", baseUrl),
22660
+ { signal: controller.signal }
22661
+ );
22662
+ if (!response.ok) return [];
22663
+ const data = await response.json().catch(() => null);
22664
+ const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
22665
+ (name) => typeof name === "string" && name.length > 0
22666
+ );
22667
+ return sortedUniqueSkillNames(names);
22668
+ } catch {
22669
+ return [];
22670
+ } finally {
22671
+ clearTimeout(timeout);
21997
22672
  }
21998
- return false;
22673
+ }
22674
+ function buildSdkSkillNames(v1SkillNames) {
22675
+ return sortedUniqueSkillNames([...v1SkillNames, SDK_PLAY_SKILL_NAME]);
21999
22676
  }
22000
22677
  async function fetchSkillsUpdate(baseUrl, localVersion) {
22001
22678
  const controller = new AbortController();
@@ -22025,11 +22702,13 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
22025
22702
  clearTimeout(timeout);
22026
22703
  }
22027
22704
  }
22028
- function buildSkillsInstallArgs(baseUrl) {
22029
- return buildSkillsAddArgs(baseUrl, SDK_SKILL_NAMES);
22705
+ function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
22706
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
22030
22707
  }
22031
- function buildBunxSkillsInstallArgs(baseUrl) {
22032
- return buildSkillsAddArgs(baseUrl, SDK_SKILL_NAMES, { firstArg: "--bun" });
22708
+ function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
22709
+ return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
22710
+ firstArg: "--bun"
22711
+ });
22033
22712
  }
22034
22713
  function hasCommand(command) {
22035
22714
  const result = (0, import_node_child_process4.spawnSync)(command, ["--version"], {
@@ -22041,15 +22720,15 @@ function hasCommand(command) {
22041
22720
  function shellQuote4(arg) {
22042
22721
  return `'${arg.replace(/'/g, `'\\''`)}'`;
22043
22722
  }
22044
- function resolveSkillsInstallCommands(baseUrl) {
22045
- const npxArgs = buildSkillsInstallArgs(baseUrl);
22723
+ function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
22724
+ const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
22046
22725
  const npxInstall = {
22047
22726
  command: "npx",
22048
22727
  args: npxArgs,
22049
22728
  manualCommand: `npx ${npxArgs.map(shellQuote4).join(" ")}`
22050
22729
  };
22051
22730
  if (hasCommand("bunx")) {
22052
- const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
22731
+ const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
22053
22732
  return [
22054
22733
  {
22055
22734
  command: "bunx",
@@ -22092,9 +22771,9 @@ function runOneSkillsInstall(install) {
22092
22771
  });
22093
22772
  });
22094
22773
  }
22095
- async function runSkillsInstall(baseUrl) {
22774
+ async function runSkillsInstall(baseUrl, skillNames) {
22096
22775
  const failures = [];
22097
- for (const install of resolveSkillsInstallCommands(baseUrl)) {
22776
+ for (const install of resolveSkillsInstallCommands(baseUrl, skillNames)) {
22098
22777
  const result = await runOneSkillsInstall(install);
22099
22778
  if (result.ok) return true;
22100
22779
  failures.push(result);
@@ -22124,26 +22803,289 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
22124
22803
  const usingPluginSkills = Boolean(activePluginSkillsDir());
22125
22804
  const localVersion = readLocalSkillsVersion(baseUrl);
22126
22805
  const update = await fetchSkillsUpdate(baseUrl, localVersion);
22127
- const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
22128
22806
  if (usingPluginSkills) {
22129
22807
  return;
22130
22808
  }
22131
- if (!update?.needsUpdate && !hasStaleInstalledSkill || !update?.remoteVersion) {
22809
+ if (!update?.needsUpdate || !update.remoteVersion) {
22132
22810
  return;
22133
22811
  }
22134
- writeSdkSkillsStatusLine(
22135
- "SDK skills changed; syncing Deepline SDK skills..."
22812
+ const remoteSkillNames = await fetchV1SkillNames(baseUrl);
22813
+ const skillNames = buildSdkSkillNames(
22814
+ remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
22136
22815
  );
22137
- const installed = await runSkillsInstall(baseUrl);
22816
+ if (skillNames.length === 0) return;
22817
+ writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
22818
+ const installed = await runSkillsInstall(baseUrl, skillNames);
22138
22819
  if (!installed) return;
22139
- if (installedSdkSkillHasStalePositionalExecuteExamples()) {
22140
- process.stderr.write(
22141
- "SDK skills sync completed, but installed deepline-plays docs still contain stale positional ctx.tools.execute examples.\n"
22142
- );
22143
- return;
22144
- }
22145
22820
  writeLocalSkillsVersion(baseUrl, update.remoteVersion);
22146
- writeSdkSkillsStatusLine("SDK skills are up to date.");
22821
+ writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
22822
+ }
22823
+
22824
+ // src/cli/failure-reporting.ts
22825
+ var import_node_os13 = require("os");
22826
+ var FAILURE_REPORT_DISABLE_ENV = "DEEPLINE_DISABLE_FAILURE_REPORTING";
22827
+ var REPORT_FAILURE_TIMEOUT_MS = 1e4;
22828
+ var MAX_FAILURE_TEXT_CHARS = 4e3;
22829
+ var MAX_COMMAND_TOKENS = 3;
22830
+ var REPORTABLE_EXIT_CODES = /* @__PURE__ */ new Set([4, 5]);
22831
+ var EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
22832
+ var UNIX_PATH_RE = /(?:\/Users\/|\/home\/|\/var\/folders\/|\/tmp\/|\/sessions\/)[^"'\n\r`]+/g;
22833
+ var WINDOWS_PATH_RE = /[A-Za-z]:(?:\\{1,2})(?:Users|Temp|tmp)(?:\\{1,2})[^"'\n\r`]+/g;
22834
+ var ASSIGNMENT_SECRET_RE = /\b(access[_-]?token|api[_-]?key|apikey|auth(?:orization)?|bearer|password|secret|session)\b(\s*[:=]\s*)(?!Bearer\s+\[redacted-secret\])([^\s,;]+)/gi;
22835
+ var BEARER_SECRET_RE = /\b(bearer)\s+([A-Za-z0-9._-]+)/gi;
22836
+ var GENERIC_TOKEN_RE = /\b(?:dlp|sk|ghp|xox[baprs])[-_A-Za-z0-9]{8,}\b/g;
22837
+ var SECRET_OPTION_RE = /^--?(?:access[-_]?token|api[-_]?key|apikey|auth(?:orization)?|bearer|password|secret|session|token)$/i;
22838
+ function truthyEnv(name) {
22839
+ return ["1", "true", "yes", "on"].includes(
22840
+ String(process.env[name] ?? "").trim().toLowerCase()
22841
+ );
22842
+ }
22843
+ function isFailureReportingDisabled() {
22844
+ return truthyEnv(FAILURE_REPORT_DISABLE_ENV);
22845
+ }
22846
+ function redactFailureText(value, maxChars = MAX_FAILURE_TEXT_CHARS) {
22847
+ const home = process.env.HOME?.trim();
22848
+ let text = String(value ?? "");
22849
+ if (!text) return "";
22850
+ if (home && home !== "/") {
22851
+ text = text.split(home).join("~");
22852
+ }
22853
+ return text.replace(EMAIL_RE, "[redacted-email]").replace(UNIX_PATH_RE, "[redacted-path]").replace(WINDOWS_PATH_RE, "[redacted-path]").replace(BEARER_SECRET_RE, "$1 [redacted-secret]").replace(ASSIGNMENT_SECRET_RE, "$1$2[redacted-secret]").replace(GENERIC_TOKEN_RE, "[redacted-secret]").slice(0, maxChars);
22854
+ }
22855
+ function sanitizeCommand(argv, prefix = "deepline") {
22856
+ const tokens = [];
22857
+ for (let index = 0; index < argv.length; index += 1) {
22858
+ const arg = argv[index];
22859
+ const value = String(arg ?? "").trim();
22860
+ if (!value) continue;
22861
+ if (value.startsWith("-")) {
22862
+ if (!value.includes("=") && SECRET_OPTION_RE.test(value)) {
22863
+ index += 1;
22864
+ }
22865
+ continue;
22866
+ }
22867
+ tokens.push(redactFailureText(value, 200));
22868
+ if (tokens.length >= MAX_COMMAND_TOKENS) break;
22869
+ }
22870
+ return tokens.length > 0 ? [prefix, ...tokens].join(" ") : prefix;
22871
+ }
22872
+ function errorMessage3(error) {
22873
+ if (error instanceof Error) return `${error.name}: ${error.message}`;
22874
+ return String(error ?? "");
22875
+ }
22876
+ function errorStack(error) {
22877
+ if (error instanceof Error && error.stack) {
22878
+ return redactFailureText(error.stack);
22879
+ }
22880
+ return null;
22881
+ }
22882
+ function classifyNetworkFailure(error) {
22883
+ const seen = /* @__PURE__ */ new Set();
22884
+ let current = error;
22885
+ while (current && !seen.has(current)) {
22886
+ seen.add(current);
22887
+ const record = typeof current === "object" && current !== null ? current : {};
22888
+ const code = String(record.code ?? "").toLowerCase();
22889
+ const name = current instanceof Error ? current.name.toLowerCase() : "";
22890
+ const text = String(
22891
+ current instanceof Error ? current.message : current
22892
+ ).toLowerCase();
22893
+ const combined = `${code} ${name} ${text}`;
22894
+ if (combined.includes("aborterror") || combined.includes("timeout") || combined.includes("timed out") || combined.includes("etimedout")) {
22895
+ return "network_timeout";
22896
+ }
22897
+ if (combined.includes("enotfound") || combined.includes("eai_again") || combined.includes("name or service not known") || combined.includes("temporary failure in name resolution")) {
22898
+ return "network_dns_resolution_failed";
22899
+ }
22900
+ if (combined.includes("econnrefused") || combined.includes("connection refused")) {
22901
+ return "network_connection_refused";
22902
+ }
22903
+ if (combined.includes("econnreset") || combined.includes("connection reset")) {
22904
+ return "network_connection_reset";
22905
+ }
22906
+ if (combined.includes("incompleteread") || combined.includes("incomplete read")) {
22907
+ return "network_incomplete_read";
22908
+ }
22909
+ if (combined.includes("remotedisconnected") || combined.includes("remote end closed connection") || combined.includes("other side closed") || combined.includes("socket hang up")) {
22910
+ return "network_remote_disconnected";
22911
+ }
22912
+ if (combined.includes("ssl") || combined.includes("tls") || combined.includes("unexpected_eof_while_reading")) {
22913
+ return "network_ssl_error";
22914
+ }
22915
+ current = record.cause ?? record.context;
22916
+ }
22917
+ return "network_error";
22918
+ }
22919
+ function isNetworkFailure(error) {
22920
+ if (!(error instanceof Error)) return false;
22921
+ if (error instanceof DeeplineError && error.statusCode) return false;
22922
+ const code = classifyNetworkFailure(error);
22923
+ return code !== "network_error" || /unable to connect|unable to stream/i.test(error.message);
22924
+ }
22925
+ function detectAgentRuntime3() {
22926
+ if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
22927
+ const pluginMode = truthyEnv("DEEPLINE_PLUGIN_MODE");
22928
+ const claudeRemote = truthyEnv("CLAUDE_CODE_REMOTE");
22929
+ const projectDir = Boolean(process.env.CLAUDE_PROJECT_DIR?.trim());
22930
+ const pluginRoot = Boolean(process.env.DEEPLINE_PLUGIN_ROOT?.trim());
22931
+ const sessionHome = process.env.HOME?.trim().startsWith("/sessions/") ?? false;
22932
+ if ((pluginMode || pluginRoot) && (claudeRemote || projectDir || sessionHome)) {
22933
+ return "claude_cowork";
22934
+ }
22935
+ if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
22936
+ if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
22937
+ if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
22938
+ return "cursor";
22939
+ }
22940
+ if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
22941
+ return "windsurf";
22942
+ }
22943
+ return "unknown";
22944
+ }
22945
+ function buildEnvironmentContext() {
22946
+ const context = {
22947
+ os: (0, import_node_os13.platform)(),
22948
+ os_release: (0, import_node_os13.release)(),
22949
+ platform: `${(0, import_node_os13.platform)()}-${(0, import_node_os13.release)()}-${process.arch}`,
22950
+ node_version: process.version,
22951
+ runtime: "Node.js",
22952
+ hostname: (0, import_node_os13.hostname)(),
22953
+ agent_runtime: detectAgentRuntime3()
22954
+ };
22955
+ for (const key of ["CLAUDE_CODE_REMOTE", "DEEPLINE_PLUGIN_MODE"]) {
22956
+ const normalized = process.env[key]?.trim();
22957
+ if (normalized) context[key.toLowerCase()] = normalized;
22958
+ }
22959
+ if (process.env.CLAUDE_PROJECT_DIR?.trim()) {
22960
+ context.claude_project_dir_present = "true";
22961
+ }
22962
+ if (process.env.DEEPLINE_PLUGIN_ROOT?.trim()) {
22963
+ context.deepline_plugin_root_present = "true";
22964
+ }
22965
+ if (process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim()) {
22966
+ context.deepline_plugin_skills_dir_present = "true";
22967
+ }
22968
+ if (process.env.HOME?.trim().startsWith("/sessions/")) {
22969
+ context.home_scope = "sessions";
22970
+ }
22971
+ return context;
22972
+ }
22973
+ function subcommandFromArgv(argv) {
22974
+ return argv.find((arg) => arg && !arg.startsWith("-")) ?? null;
22975
+ }
22976
+ function commandTokens(argv) {
22977
+ return argv.map((arg) => String(arg ?? "").trim()).filter((arg) => arg && !arg.startsWith("-"));
22978
+ }
22979
+ function isServerLoggedPlayRunStartFailure(input2) {
22980
+ const [subcommand, command] = commandTokens(input2.argv);
22981
+ return subcommand === "plays" && command === "run" && input2.error instanceof DeeplineError && typeof input2.error.statusCode === "number";
22982
+ }
22983
+ function shouldReport(input2) {
22984
+ if (input2.error !== void 0) return true;
22985
+ return input2.exitCode !== null && REPORTABLE_EXIT_CODES.has(input2.exitCode);
22986
+ }
22987
+ function failureCode(input2) {
22988
+ if (input2.error !== void 0) {
22989
+ if (isNetworkFailure(input2.error))
22990
+ return classifyNetworkFailure(input2.error);
22991
+ if (input2.error instanceof DeeplineError && input2.error.code) {
22992
+ return input2.error.code;
22993
+ }
22994
+ if (input2.error instanceof Error && input2.error.name)
22995
+ return input2.error.name;
22996
+ return "CLI_FAILURE";
22997
+ }
22998
+ return input2.exitCode === 4 ? "network_error" : "command_exit";
22999
+ }
23000
+ function resolvedExitCode(input2) {
23001
+ if (typeof input2.exitCode === "number" && Number.isFinite(input2.exitCode)) {
23002
+ return Math.trunc(input2.exitCode);
23003
+ }
23004
+ if (input2.error === void 0) return null;
23005
+ return isNetworkFailure(input2.error) ? 4 : 5;
23006
+ }
23007
+ function resolveSdkCliFailureExitCode(error) {
23008
+ return isNetworkFailure(error) ? 4 : 1;
23009
+ }
23010
+ function resolvedFailureKind(input2) {
23011
+ return input2.error === void 0 ? "command_exit" : "uncaught_exception";
23012
+ }
23013
+ function buildFailureReport(input2) {
23014
+ const durationMs = Math.max(0, Date.now() - input2.startedAtMs);
23015
+ const failureKind = resolvedFailureKind({
23016
+ exitCode: input2.exitCode,
23017
+ error: input2.error
23018
+ });
23019
+ const code = failureCode({ exitCode: input2.exitCode, error: input2.error });
23020
+ const errorBody = input2.error === void 0 ? `SDK CLI command exited ${input2.exitCode ?? "unknown"}` : errorMessage3(input2.error);
23021
+ return {
23022
+ command: sanitizeCommand(input2.argv),
23023
+ subcommand: subcommandFromArgv(input2.argv),
23024
+ error_status: input2.exitCode,
23025
+ error_body: redactFailureText(errorBody),
23026
+ exit_code: input2.exitCode,
23027
+ duration_ms: durationMs,
23028
+ error_class: input2.error instanceof Error ? input2.error.name : null,
23029
+ stack_trace: errorStack(input2.error),
23030
+ failure_kind: failureKind,
23031
+ failure_code: code,
23032
+ failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
23033
+ cli_version: SDK_VERSION,
23034
+ context: {
23035
+ base_url: redactFailureText(input2.baseUrl, 400),
23036
+ command_summary: sanitizeCommand(input2.argv),
23037
+ environment: buildEnvironmentContext(),
23038
+ failure_kind: failureKind,
23039
+ failure_code: code,
23040
+ failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
23041
+ duration_ms: durationMs,
23042
+ ...input2.exitCode !== null ? { exit_code: input2.exitCode } : {}
23043
+ }
23044
+ };
23045
+ }
23046
+ async function maybeReportSdkCliFailure(input2) {
23047
+ if (isFailureReportingDisabled()) return false;
23048
+ if (isServerLoggedPlayRunStartFailure(input2)) return false;
23049
+ const exitCode = resolvedExitCode(input2);
23050
+ if (!shouldReport({ exitCode, error: input2.error })) return false;
23051
+ const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
23052
+ const apiKey = resolveApiKeyForBaseUrl(baseUrl);
23053
+ if (!apiKey) return false;
23054
+ const controller = new AbortController();
23055
+ const timeout = setTimeout(
23056
+ () => controller.abort(),
23057
+ REPORT_FAILURE_TIMEOUT_MS
23058
+ );
23059
+ try {
23060
+ await fetch(new URL("/api/v2/cli/report-failure", baseUrl), {
23061
+ method: "POST",
23062
+ headers: {
23063
+ Authorization: `Bearer ${apiKey}`,
23064
+ "Content-Type": "application/json",
23065
+ "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
23066
+ "X-Deepline-Client-Family": "sdk",
23067
+ "X-Deepline-CLI-Family": "sdk",
23068
+ "X-Deepline-Agent-Runtime": detectAgentRuntime3(),
23069
+ "X-Deepline-CLI-Version": SDK_VERSION,
23070
+ "X-Deepline-SDK-Version": SDK_VERSION
23071
+ },
23072
+ body: JSON.stringify(
23073
+ buildFailureReport({
23074
+ argv: input2.argv,
23075
+ startedAtMs: input2.startedAtMs,
23076
+ error: input2.error,
23077
+ exitCode,
23078
+ baseUrl
23079
+ })
23080
+ ),
23081
+ signal: controller.signal
23082
+ });
23083
+ return true;
23084
+ } catch {
23085
+ return false;
23086
+ } finally {
23087
+ clearTimeout(timeout);
23088
+ }
22147
23089
  }
22148
23090
 
22149
23091
  // src/cli/index.ts
@@ -22187,8 +23129,8 @@ function topLevelCommandKnown(program, commandName) {
22187
23129
  );
22188
23130
  }
22189
23131
  async function runPlayRunnerHealthCheck() {
22190
- const dir = await (0, import_promises7.mkdtemp)((0, import_node_path20.join)((0, import_node_os12.tmpdir)(), "deepline-health-play-"));
22191
- const file = (0, import_node_path20.join)(dir, "health-check.play.ts");
23132
+ const dir = await (0, import_promises7.mkdtemp)((0, import_node_path21.join)((0, import_node_os14.tmpdir)(), "deepline-health-play-"));
23133
+ const file = (0, import_node_path21.join)(dir, "health-check.play.ts");
22192
23134
  try {
22193
23135
  await (0, import_promises7.writeFile)(
22194
23136
  file,
@@ -22405,7 +23347,7 @@ Exit codes:
22405
23347
  `
22406
23348
  );
22407
23349
  program.hook("preAction", async (_thisCommand, actionCommand) => {
22408
- if (actionCommand.name() === "version" || actionCommand.name() === "update" || isLegacyNoopInvocation()) {
23350
+ if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || isLegacyNoopInvocation()) {
22409
23351
  return;
22410
23352
  }
22411
23353
  if (printStartupPhase) {
@@ -22453,6 +23395,7 @@ Exit codes:
22453
23395
  registerLegacyNoopCommands(program);
22454
23396
  registerUpdateCommand(program);
22455
23397
  registerQuickstartCommands(program);
23398
+ registerSwitchCommands(program);
22456
23399
  program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(
22457
23400
  "after",
22458
23401
  `
@@ -22572,6 +23515,11 @@ Examples:
22572
23515
  ms: Date.now() - mainStartedAt,
22573
23516
  ok: true
22574
23517
  });
23518
+ await maybeReportSdkCliFailure({
23519
+ argv: process.argv.slice(2),
23520
+ startedAtMs: mainStartedAt,
23521
+ exitCode: typeof process.exitCode === "number" ? process.exitCode : null
23522
+ });
22575
23523
  } catch (error) {
22576
23524
  const commanderError = asCommanderError(error);
22577
23525
  recordCliTrace({
@@ -22607,7 +23555,14 @@ Examples:
22607
23555
  } else {
22608
23556
  console.error(`Error: ${String(error)}`);
22609
23557
  }
22610
- process.exitCode = 1;
23558
+ const failureExitCode = resolveSdkCliFailureExitCode(error);
23559
+ process.exitCode = failureExitCode;
23560
+ await maybeReportSdkCliFailure({
23561
+ argv: process.argv.slice(2),
23562
+ startedAtMs: mainStartedAt,
23563
+ exitCode: failureExitCode,
23564
+ error
23565
+ });
22611
23566
  }
22612
23567
  process.exitCode = process.exitCode ?? 0;
22613
23568
  }