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.
- package/dist/cli/index.js +2226 -1271
- package/dist/cli/index.mjs +2303 -1355
- package/dist/index.d.mts +21 -14
- package/dist/index.d.ts +21 -14
- package/dist/index.js +97 -23
- package/dist/index.mjs +97 -23
- package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +42 -20
- package/dist/repo/apps/play-runner-workers/src/entry.ts +135 -45
- package/dist/repo/apps/play-runner-workers/src/runtime/receipts.ts +18 -27
- package/dist/repo/apps/play-runner-workers/src/workflow-instance-create.ts +44 -0
- package/dist/repo/apps/play-runner-workers/src/workflow-retry.ts +7 -11
- package/dist/repo/sdk/src/client.ts +35 -12
- package/dist/repo/sdk/src/errors.ts +2 -2
- package/dist/repo/sdk/src/http.ts +87 -7
- package/dist/repo/sdk/src/play.ts +1 -1
- package/dist/repo/sdk/src/plays/bundle-play-file.ts +5 -1
- package/dist/repo/sdk/src/release.ts +13 -10
- package/dist/repo/sdk/src/tool-output.ts +2 -2
- package/dist/repo/sdk/src/types.ts +9 -6
- package/dist/repo/shared_libs/play-runtime/fullenrich-batching.ts +229 -0
- package/dist/repo/shared_libs/play-runtime/governor/policy.ts +1 -1
- package/dist/repo/shared_libs/play-runtime/play-runtime-batching-registry.ts +20 -0
- package/dist/repo/shared_libs/play-runtime/run-failure.ts +20 -12
- package/dist/repo/shared_libs/play-runtime/run-ledger.ts +107 -56
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +4 -2
- package/dist/repo/shared_libs/play-runtime/secret-redaction.ts +15 -0
- package/dist/repo/shared_libs/play-runtime/work-receipts.ts +1 -0
- package/dist/repo/shared_libs/plays/bundling/index.ts +69 -11
- package/dist/repo/shared_libs/plays/static-pipeline.ts +1 -3
- package/dist/repo/shared_libs/security/outbound-url-policy.ts +238 -0
- package/dist/repo/shared_libs/security/safe-fetch.ts +118 -0
- package/dist/viewer/viewer.css +617 -0
- package/dist/viewer/viewer.js +1496 -0
- package/package.json +5 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,8 +1,165 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/cli/proxy-env.ts
|
|
4
|
+
import { Agent, Dispatcher, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
5
|
+
function defaultPort(protocol) {
|
|
6
|
+
return protocol === "https:" ? "443" : "80";
|
|
7
|
+
}
|
|
8
|
+
function proxyEnvValue(name) {
|
|
9
|
+
return process.env[name]?.trim() ?? "";
|
|
10
|
+
}
|
|
11
|
+
function firstProxyEnv(...names) {
|
|
12
|
+
for (const name of names) {
|
|
13
|
+
const value = proxyEnvValue(name);
|
|
14
|
+
if (value) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
function ipv4ToNumber(value) {
|
|
21
|
+
const parts = value.split(".");
|
|
22
|
+
if (parts.length !== 4) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
let result = 0;
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
if (!/^\d+$/.test(part)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const parsed = Number(part);
|
|
31
|
+
if (parsed < 0 || parsed > 255) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
result = (result << 8) + parsed;
|
|
35
|
+
}
|
|
36
|
+
return result >>> 0;
|
|
37
|
+
}
|
|
38
|
+
function ipv4MatchesCidr(hostname3, cidr) {
|
|
39
|
+
const [range, bitsRaw] = cidr.split("/");
|
|
40
|
+
const hostValue = ipv4ToNumber(hostname3);
|
|
41
|
+
const rangeValue = range ? ipv4ToNumber(range) : null;
|
|
42
|
+
const bits = Number(bitsRaw);
|
|
43
|
+
if (hostValue === null || rangeValue === null || !Number.isInteger(bits) || bits < 0 || bits > 32) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const mask = bits === 0 ? 0 : 4294967295 << 32 - bits >>> 0;
|
|
47
|
+
return (hostValue & mask) === (rangeValue & mask);
|
|
48
|
+
}
|
|
49
|
+
function splitNoProxyHostPort(entry) {
|
|
50
|
+
const bracketed = entry.match(/^\[([^\]]+)\](?::(\d+))?$/);
|
|
51
|
+
if (bracketed) {
|
|
52
|
+
return { host: bracketed[1].toLowerCase(), port: bracketed[2] ?? null };
|
|
53
|
+
}
|
|
54
|
+
const portMatch = entry.match(/^([^:]+):(\d+)$/);
|
|
55
|
+
if (portMatch) {
|
|
56
|
+
return { host: portMatch[1].toLowerCase(), port: portMatch[2] ?? null };
|
|
57
|
+
}
|
|
58
|
+
return { host: entry.toLowerCase(), port: null };
|
|
59
|
+
}
|
|
60
|
+
function noProxyMatches(url, noProxy) {
|
|
61
|
+
const hostname3 = url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
62
|
+
const port = url.port || defaultPort(url.protocol);
|
|
63
|
+
for (const rawEntry of noProxy.split(/[,\s]+/)) {
|
|
64
|
+
const entry = rawEntry.trim().toLowerCase();
|
|
65
|
+
if (!entry) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (entry === "*") {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
const { host, port: entryPort } = splitNoProxyHostPort(entry);
|
|
72
|
+
if (entryPort && entryPort !== port) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (host.includes("/") && ipv4MatchesCidr(hostname3, host)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (host.startsWith("*.")) {
|
|
79
|
+
const suffix = host.slice(2);
|
|
80
|
+
if (hostname3.endsWith(`.${suffix}`)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (host.startsWith(".")) {
|
|
86
|
+
const suffix = host.slice(1);
|
|
87
|
+
if (hostname3 === suffix || hostname3.endsWith(host)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (hostname3 === host || hostname3.endsWith(`.${host}`)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
var EnvProxyDispatcher = class extends Dispatcher {
|
|
99
|
+
directAgent = new Agent();
|
|
100
|
+
httpProxyAgent;
|
|
101
|
+
httpsProxyAgent;
|
|
102
|
+
noProxy;
|
|
103
|
+
constructor(options) {
|
|
104
|
+
super();
|
|
105
|
+
this.httpProxyAgent = options.httpProxy ? new ProxyAgent(options.httpProxy) : null;
|
|
106
|
+
this.httpsProxyAgent = options.httpsProxy ? new ProxyAgent(options.httpsProxy) : this.httpProxyAgent;
|
|
107
|
+
this.noProxy = options.noProxy;
|
|
108
|
+
}
|
|
109
|
+
dispatch(options, handler) {
|
|
110
|
+
const origin = new URL(String(options.origin));
|
|
111
|
+
if (this.noProxy && noProxyMatches(origin, this.noProxy)) {
|
|
112
|
+
return this.directAgent.dispatch(options, handler);
|
|
113
|
+
}
|
|
114
|
+
const proxyAgent = origin.protocol === "https:" ? this.httpsProxyAgent : this.httpProxyAgent;
|
|
115
|
+
return (proxyAgent ?? this.directAgent).dispatch(options, handler);
|
|
116
|
+
}
|
|
117
|
+
close(callback) {
|
|
118
|
+
const promise = Promise.all([
|
|
119
|
+
this.directAgent.close(),
|
|
120
|
+
this.httpProxyAgent?.close(),
|
|
121
|
+
this.httpsProxyAgent && this.httpsProxyAgent !== this.httpProxyAgent ? this.httpsProxyAgent.close() : void 0
|
|
122
|
+
]).then(() => void 0);
|
|
123
|
+
if (callback) {
|
|
124
|
+
promise.then(callback, callback);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
return promise;
|
|
128
|
+
}
|
|
129
|
+
destroy(errorOrCallback, callback) {
|
|
130
|
+
const error = typeof errorOrCallback === "function" ? null : errorOrCallback ?? null;
|
|
131
|
+
const done = typeof errorOrCallback === "function" ? errorOrCallback : callback;
|
|
132
|
+
const promise = Promise.all([
|
|
133
|
+
this.directAgent.destroy(error),
|
|
134
|
+
this.httpProxyAgent?.destroy(error),
|
|
135
|
+
this.httpsProxyAgent && this.httpsProxyAgent !== this.httpProxyAgent ? this.httpsProxyAgent.destroy(error) : void 0
|
|
136
|
+
]).then(() => void 0);
|
|
137
|
+
if (done) {
|
|
138
|
+
promise.then(done, done);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
return promise;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function configureProxyFromEnv() {
|
|
145
|
+
const httpProxy = firstProxyEnv("HTTP_PROXY", "http_proxy");
|
|
146
|
+
const httpsProxy = firstProxyEnv("HTTPS_PROXY", "https_proxy") || httpProxy;
|
|
147
|
+
if (!httpProxy && !httpsProxy) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setGlobalDispatcher(
|
|
151
|
+
new EnvProxyDispatcher({
|
|
152
|
+
httpProxy,
|
|
153
|
+
httpsProxy,
|
|
154
|
+
noProxy: firstProxyEnv("NO_PROXY", "no_proxy")
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
configureProxyFromEnv();
|
|
159
|
+
|
|
3
160
|
// src/cli/index.ts
|
|
4
161
|
import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile6 } from "fs/promises";
|
|
5
|
-
import { join as
|
|
162
|
+
import { join as join16 } from "path";
|
|
6
163
|
import { tmpdir as tmpdir5 } from "os";
|
|
7
164
|
import { Command as Command3 } from "commander";
|
|
8
165
|
|
|
@@ -223,10 +380,10 @@ var SDK_RELEASE = {
|
|
|
223
380
|
// skill on the sdk sync surface, and the people-search-to-email prebuilt.
|
|
224
381
|
// 0.1.108 ships explicit dataset column/tool recompute policy and removes
|
|
225
382
|
// the SDK enrich generator's one-second stale policy.
|
|
226
|
-
version: "0.1.
|
|
383
|
+
version: "0.1.110",
|
|
227
384
|
apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
|
|
228
385
|
supportPolicy: {
|
|
229
|
-
latest: "0.1.
|
|
386
|
+
latest: "0.1.110",
|
|
230
387
|
minimumSupported: "0.1.53",
|
|
231
388
|
deprecatedBelow: "0.1.53",
|
|
232
389
|
commandMinimumSupported: [
|
|
@@ -253,6 +410,7 @@ var SYNTHETIC_RUN_HEADER = "x-deepline-synthetic-run";
|
|
|
253
410
|
|
|
254
411
|
// src/http.ts
|
|
255
412
|
var MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
|
|
413
|
+
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.";
|
|
256
414
|
var HttpClient = class {
|
|
257
415
|
constructor(config) {
|
|
258
416
|
this.config = config;
|
|
@@ -288,6 +446,7 @@ var HttpClient = class {
|
|
|
288
446
|
"User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
|
|
289
447
|
"X-Deepline-Client-Family": "sdk",
|
|
290
448
|
"X-Deepline-CLI-Family": "sdk",
|
|
449
|
+
"X-Deepline-Agent-Runtime": detectAgentRuntime(),
|
|
291
450
|
"X-Deepline-CLI-Version": SDK_VERSION,
|
|
292
451
|
"X-Deepline-SDK-Version": SDK_VERSION,
|
|
293
452
|
"X-Deepline-API-Contract": SDK_API_CONTRACT,
|
|
@@ -390,12 +549,13 @@ var HttpClient = class {
|
|
|
390
549
|
parsed = body;
|
|
391
550
|
}
|
|
392
551
|
if (!response.ok) {
|
|
552
|
+
const retryableApiError = options?.retryApiErrors === true && isRetryableApiErrorResponse(response.status);
|
|
393
553
|
const htmlError = detectHtmlErrorBody(
|
|
394
554
|
body,
|
|
395
555
|
response.headers.get("content-type")
|
|
396
556
|
);
|
|
397
557
|
if (htmlError) {
|
|
398
|
-
|
|
558
|
+
lastError = new DeeplineError(
|
|
399
559
|
htmlError.message(response.status),
|
|
400
560
|
response.status,
|
|
401
561
|
"API_ERROR",
|
|
@@ -405,12 +565,23 @@ var HttpClient = class {
|
|
|
405
565
|
...htmlError.workerThrewException ? { workerThrewException: true } : {}
|
|
406
566
|
}
|
|
407
567
|
);
|
|
568
|
+
if (retryableApiError && attempt < this.config.maxRetries) {
|
|
569
|
+
retryAfterDelayMs = parseOptionalRetryAfter(response);
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
throw lastError;
|
|
408
573
|
}
|
|
409
574
|
const errorValue = typeof parsed === "object" && parsed && "error" in parsed ? parsed.error : void 0;
|
|
410
575
|
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}`;
|
|
411
|
-
|
|
576
|
+
const apiErrorCode = errorValue && typeof errorValue === "object" && typeof errorValue.code === "string" ? errorValue.code : "API_ERROR";
|
|
577
|
+
lastError = new DeeplineError(msg, response.status, apiErrorCode, {
|
|
412
578
|
response: parsed
|
|
413
579
|
});
|
|
580
|
+
if (retryableApiError && attempt < this.config.maxRetries) {
|
|
581
|
+
retryAfterDelayMs = parseOptionalRetryAfter(response);
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
throw lastError;
|
|
414
585
|
}
|
|
415
586
|
return parsed;
|
|
416
587
|
} catch (error) {
|
|
@@ -430,8 +601,8 @@ var HttpClient = class {
|
|
|
430
601
|
if (lastError instanceof DeeplineError) {
|
|
431
602
|
throw lastError;
|
|
432
603
|
}
|
|
433
|
-
const
|
|
434
|
-
throw new DeeplineError(
|
|
604
|
+
const errorMessage4 = lastError?.message ? `Unable to connect to ${baseUrl}. ${lastError.message}` : `Unable to connect to ${baseUrl}. Is the computer able to access the url?`;
|
|
605
|
+
throw new DeeplineError(withCoworkNetworkHint(errorMessage4));
|
|
435
606
|
}
|
|
436
607
|
/**
|
|
437
608
|
* Send a GET request.
|
|
@@ -503,7 +674,9 @@ var HttpClient = class {
|
|
|
503
674
|
}
|
|
504
675
|
}
|
|
505
676
|
throw new DeeplineError(
|
|
506
|
-
|
|
677
|
+
withCoworkNetworkHint(
|
|
678
|
+
lastError?.message ? `Unable to stream from ${this.config.baseUrl}. ${lastError.message}` : `Unable to stream from ${this.config.baseUrl}.`
|
|
679
|
+
)
|
|
507
680
|
);
|
|
508
681
|
}
|
|
509
682
|
/**
|
|
@@ -513,11 +686,12 @@ var HttpClient = class {
|
|
|
513
686
|
* @param path - API path
|
|
514
687
|
* @param body - Request body (will be JSON-serialized)
|
|
515
688
|
*/
|
|
516
|
-
async post(path, body, headers) {
|
|
689
|
+
async post(path, body, headers, options) {
|
|
517
690
|
return this.request(path, {
|
|
518
691
|
method: "POST",
|
|
519
692
|
body,
|
|
520
|
-
headers
|
|
693
|
+
headers,
|
|
694
|
+
...options
|
|
521
695
|
});
|
|
522
696
|
}
|
|
523
697
|
async postFormData(path, formData, headers) {
|
|
@@ -558,6 +732,9 @@ function parseResponseBody(body) {
|
|
|
558
732
|
return body;
|
|
559
733
|
}
|
|
560
734
|
}
|
|
735
|
+
function isRetryableApiErrorResponse(status) {
|
|
736
|
+
return status === 408 || status === 425 || status >= 500 && status < 600;
|
|
737
|
+
}
|
|
561
738
|
function detectHtmlErrorBody(body, contentType) {
|
|
562
739
|
const trimmed = body.trim();
|
|
563
740
|
const lower = trimmed.toLowerCase();
|
|
@@ -597,6 +774,9 @@ function apiErrorMessage(parsed, status) {
|
|
|
597
774
|
return `HTTP ${status}`;
|
|
598
775
|
}
|
|
599
776
|
function parseRetryAfter(response) {
|
|
777
|
+
return parseOptionalRetryAfter(response) ?? 5e3;
|
|
778
|
+
}
|
|
779
|
+
function parseOptionalRetryAfter(response) {
|
|
600
780
|
const header = response.headers.get("retry-after");
|
|
601
781
|
if (header) {
|
|
602
782
|
const seconds = Number(header);
|
|
@@ -604,7 +784,7 @@ function parseRetryAfter(response) {
|
|
|
604
784
|
return seconds * 1e3;
|
|
605
785
|
}
|
|
606
786
|
}
|
|
607
|
-
return
|
|
787
|
+
return null;
|
|
608
788
|
}
|
|
609
789
|
function buildCandidateUrls(url) {
|
|
610
790
|
try {
|
|
@@ -661,6 +841,39 @@ function decodeSseFrame(frame) {
|
|
|
661
841
|
function sleep(ms) {
|
|
662
842
|
return new Promise((resolve16) => setTimeout(resolve16, ms));
|
|
663
843
|
}
|
|
844
|
+
function isTruthyEnv(name) {
|
|
845
|
+
const normalized = process.env[name]?.trim().toLowerCase();
|
|
846
|
+
return ["1", "true", "yes", "on"].includes(normalized ?? "");
|
|
847
|
+
}
|
|
848
|
+
function isCoworkLikeSandbox() {
|
|
849
|
+
const pluginMode = isTruthyEnv("DEEPLINE_PLUGIN_MODE");
|
|
850
|
+
const claudeRemote = isTruthyEnv("CLAUDE_CODE_REMOTE");
|
|
851
|
+
const projectDir = Boolean(process.env.CLAUDE_PROJECT_DIR?.trim());
|
|
852
|
+
const pluginRoot = Boolean(process.env.DEEPLINE_PLUGIN_ROOT?.trim());
|
|
853
|
+
const home = process.env.HOME?.trim() ?? "";
|
|
854
|
+
const sessionHome = home.startsWith("/sessions/");
|
|
855
|
+
return (pluginMode || pluginRoot) && (claudeRemote || projectDir || sessionHome);
|
|
856
|
+
}
|
|
857
|
+
function detectAgentRuntime() {
|
|
858
|
+
if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
|
|
859
|
+
if (isCoworkLikeSandbox()) return "claude_cowork";
|
|
860
|
+
if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
|
|
861
|
+
if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
|
|
862
|
+
if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
|
|
863
|
+
return "cursor";
|
|
864
|
+
}
|
|
865
|
+
if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
|
|
866
|
+
return "windsurf";
|
|
867
|
+
}
|
|
868
|
+
return "unknown";
|
|
869
|
+
}
|
|
870
|
+
function withCoworkNetworkHint(message) {
|
|
871
|
+
if (!isCoworkLikeSandbox() || message.includes(COWORK_NETWORK_HINT)) {
|
|
872
|
+
return message;
|
|
873
|
+
}
|
|
874
|
+
return `${message}
|
|
875
|
+
${COWORK_NETWORK_HINT}`;
|
|
876
|
+
}
|
|
664
877
|
|
|
665
878
|
// src/stream-reconnect.ts
|
|
666
879
|
var STREAM_RECONNECT_BASE_DELAY_MS = 500;
|
|
@@ -1937,12 +2150,12 @@ var DeeplineClient = class {
|
|
|
1937
2150
|
* Returns everything from {@link ToolDefinition} plus pricing info, sample
|
|
1938
2151
|
* inputs/outputs, failure modes, and cost estimates.
|
|
1939
2152
|
*
|
|
1940
|
-
* @param toolId - Tool identifier (e.g. `"
|
|
2153
|
+
* @param toolId - Tool identifier (e.g. `"dropleads_search_people"`)
|
|
1941
2154
|
* @returns Full tool metadata
|
|
1942
2155
|
*
|
|
1943
2156
|
* @example
|
|
1944
2157
|
* ```typescript
|
|
1945
|
-
* const meta = await client.getTool('
|
|
2158
|
+
* const meta = await client.getTool('dropleads_search_people');
|
|
1946
2159
|
* console.log(`Cost: ${meta.estimatedCreditsRange} credits`);
|
|
1947
2160
|
* console.log(`Input schema:`, meta.inputSchema);
|
|
1948
2161
|
* ```
|
|
@@ -1974,7 +2187,8 @@ var DeeplineClient = class {
|
|
|
1974
2187
|
return this.http.post(
|
|
1975
2188
|
`/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
|
|
1976
2189
|
{ payload: input2 },
|
|
1977
|
-
headers
|
|
2190
|
+
headers,
|
|
2191
|
+
{ forbiddenAsApiError: true }
|
|
1978
2192
|
);
|
|
1979
2193
|
}
|
|
1980
2194
|
/**
|
|
@@ -2056,7 +2270,7 @@ var DeeplineClient = class {
|
|
|
2056
2270
|
...request.force ? { force: true } : {},
|
|
2057
2271
|
...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
|
|
2058
2272
|
// Profile selection is the API's job, not the CLI's. The server
|
|
2059
|
-
//
|
|
2273
|
+
// defaults to workers_edge; tests and runtime probes that want a
|
|
2060
2274
|
// different profile pass `request.profile` explicitly.
|
|
2061
2275
|
...request.profile ? { profile: request.profile } : {}
|
|
2062
2276
|
}
|
|
@@ -2121,10 +2335,15 @@ var DeeplineClient = class {
|
|
|
2121
2335
|
sourceFiles: input2.sourceFiles,
|
|
2122
2336
|
artifact: input2.artifact
|
|
2123
2337
|
});
|
|
2124
|
-
return this.http.post(
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2338
|
+
return this.http.post(
|
|
2339
|
+
"/api/v2/plays/artifacts",
|
|
2340
|
+
{
|
|
2341
|
+
...input2,
|
|
2342
|
+
compilerManifest
|
|
2343
|
+
},
|
|
2344
|
+
void 0,
|
|
2345
|
+
{ forbiddenAsApiError: true, retryApiErrors: true }
|
|
2346
|
+
);
|
|
2128
2347
|
}
|
|
2129
2348
|
/**
|
|
2130
2349
|
* Register multiple bundled play artifacts in one request.
|
|
@@ -2134,7 +2353,12 @@ var DeeplineClient = class {
|
|
|
2134
2353
|
*/
|
|
2135
2354
|
async registerPlayArtifacts(artifacts) {
|
|
2136
2355
|
if (artifacts.length === 0) {
|
|
2137
|
-
return this.http.post(
|
|
2356
|
+
return this.http.post(
|
|
2357
|
+
"/api/v2/plays/artifacts",
|
|
2358
|
+
{ artifacts },
|
|
2359
|
+
void 0,
|
|
2360
|
+
{ forbiddenAsApiError: true, retryApiErrors: true }
|
|
2361
|
+
);
|
|
2138
2362
|
}
|
|
2139
2363
|
const compiledArtifacts = await mapWithConcurrency(
|
|
2140
2364
|
artifacts,
|
|
@@ -2152,9 +2376,14 @@ var DeeplineClient = class {
|
|
|
2152
2376
|
const responses = [];
|
|
2153
2377
|
for (const chunk of chunkRegisterPlayArtifacts(compiledArtifacts)) {
|
|
2154
2378
|
responses.push(
|
|
2155
|
-
await this.http.post(
|
|
2156
|
-
artifacts
|
|
2157
|
-
|
|
2379
|
+
await this.http.post(
|
|
2380
|
+
"/api/v2/plays/artifacts",
|
|
2381
|
+
{
|
|
2382
|
+
artifacts: chunk
|
|
2383
|
+
},
|
|
2384
|
+
void 0,
|
|
2385
|
+
{ forbiddenAsApiError: true, retryApiErrors: true }
|
|
2386
|
+
)
|
|
2158
2387
|
);
|
|
2159
2388
|
}
|
|
2160
2389
|
return {
|
|
@@ -2959,7 +3188,9 @@ var DeeplineClient = class {
|
|
|
2959
3188
|
const encodedName = encodeURIComponent(name);
|
|
2960
3189
|
return this.http.post(
|
|
2961
3190
|
`/api/v2/plays/${encodedName}/live`,
|
|
2962
|
-
request
|
|
3191
|
+
request,
|
|
3192
|
+
void 0,
|
|
3193
|
+
{ forbiddenAsApiError: true }
|
|
2963
3194
|
);
|
|
2964
3195
|
}
|
|
2965
3196
|
/**
|
|
@@ -3542,11 +3773,53 @@ function sleep3(ms) {
|
|
|
3542
3773
|
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
3543
3774
|
}
|
|
3544
3775
|
function collectLocalEnvInfo() {
|
|
3545
|
-
|
|
3776
|
+
const info = {
|
|
3546
3777
|
os: `${process.platform} ${process.arch}`,
|
|
3547
3778
|
node_version: process.version,
|
|
3548
|
-
home_dir: homedir3()
|
|
3779
|
+
home_dir: homedir3(),
|
|
3780
|
+
agent_runtime: detectAgentRuntime2()
|
|
3549
3781
|
};
|
|
3782
|
+
const env = process.env;
|
|
3783
|
+
const optional = {
|
|
3784
|
+
claude_code_remote: env.CLAUDE_CODE_REMOTE,
|
|
3785
|
+
deepline_plugin_mode: env.DEEPLINE_PLUGIN_MODE
|
|
3786
|
+
};
|
|
3787
|
+
for (const [key, value] of Object.entries(optional)) {
|
|
3788
|
+
if (value?.trim()) info[key] = value.trim();
|
|
3789
|
+
}
|
|
3790
|
+
if (env.CLAUDE_PROJECT_DIR?.trim()) {
|
|
3791
|
+
info.claude_project_dir_present = "true";
|
|
3792
|
+
}
|
|
3793
|
+
if (env.DEEPLINE_PLUGIN_ROOT?.trim()) {
|
|
3794
|
+
info.deepline_plugin_root_present = "true";
|
|
3795
|
+
}
|
|
3796
|
+
if (env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim()) {
|
|
3797
|
+
info.deepline_plugin_skills_dir_present = "true";
|
|
3798
|
+
}
|
|
3799
|
+
if (info.home_dir.startsWith("/sessions/")) {
|
|
3800
|
+
info.home_scope = "sessions";
|
|
3801
|
+
}
|
|
3802
|
+
return info;
|
|
3803
|
+
}
|
|
3804
|
+
function detectAgentRuntime2() {
|
|
3805
|
+
if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
|
|
3806
|
+
const pluginMode = process.env.DEEPLINE_PLUGIN_MODE?.trim().toLowerCase();
|
|
3807
|
+
const claudeRemote = process.env.CLAUDE_CODE_REMOTE?.trim().toLowerCase();
|
|
3808
|
+
const sessionHome = (process.env.HOME?.trim() || homedir3()).startsWith(
|
|
3809
|
+
"/sessions/"
|
|
3810
|
+
);
|
|
3811
|
+
if (["1", "true", "yes", "on"].includes(pluginMode ?? "") && (["1", "true", "yes", "on"].includes(claudeRemote ?? "") || Boolean(process.env.CLAUDE_PROJECT_DIR?.trim()) || sessionHome)) {
|
|
3812
|
+
return "claude_cowork";
|
|
3813
|
+
}
|
|
3814
|
+
if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
|
|
3815
|
+
if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
|
|
3816
|
+
if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
|
|
3817
|
+
return "cursor";
|
|
3818
|
+
}
|
|
3819
|
+
if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
|
|
3820
|
+
return "windsurf";
|
|
3821
|
+
}
|
|
3822
|
+
return "unknown";
|
|
3550
3823
|
}
|
|
3551
3824
|
function readCsvRows(csvPath) {
|
|
3552
3825
|
const raw = readFileSync3(resolve2(csvPath), "utf-8");
|
|
@@ -3814,7 +4087,14 @@ function saveEnvValues(values, baseUrl) {
|
|
|
3814
4087
|
}
|
|
3815
4088
|
async function httpJson(method, url, apiKey, body) {
|
|
3816
4089
|
const headers = {
|
|
3817
|
-
"Content-Type": "application/json"
|
|
4090
|
+
"Content-Type": "application/json",
|
|
4091
|
+
"User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
|
|
4092
|
+
"X-Deepline-Client-Family": "sdk",
|
|
4093
|
+
"X-Deepline-CLI-Family": "sdk",
|
|
4094
|
+
"X-Deepline-Agent-Runtime": detectAgentRuntime2(),
|
|
4095
|
+
"X-Deepline-CLI-Version": SDK_VERSION,
|
|
4096
|
+
"X-Deepline-SDK-Version": SDK_VERSION,
|
|
4097
|
+
"X-Deepline-API-Contract": SDK_API_CONTRACT
|
|
3818
4098
|
};
|
|
3819
4099
|
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
3820
4100
|
let response = null;
|
|
@@ -4672,7 +4952,7 @@ async function handlePlans(options) {
|
|
|
4672
4952
|
);
|
|
4673
4953
|
const activePlan = payload.active_plan ?? {};
|
|
4674
4954
|
const plans = Array.isArray(payload.plans) ? payload.plans : [];
|
|
4675
|
-
const
|
|
4955
|
+
const meters = Array.isArray(payload.meters) ? payload.meters : Array.isArray(payload.metrics) ? payload.metrics : [];
|
|
4676
4956
|
const lines = [
|
|
4677
4957
|
`Catalog: ${payload.catalog_id ?? "(unknown)"} (version ${payload.catalog_version ?? "(unknown)"})`,
|
|
4678
4958
|
`Active plan: ${activePlan.public_name ?? "(unknown)"} (${activePlan.plan_version_id ?? "(unknown)"})`,
|
|
@@ -4683,10 +4963,10 @@ async function handlePlans(options) {
|
|
|
4683
4963
|
(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"}`
|
|
4684
4964
|
)
|
|
4685
4965
|
],
|
|
4686
|
-
...
|
|
4966
|
+
...meters.length === 0 ? ["Metrics: none"] : [
|
|
4687
4967
|
"Metrics:",
|
|
4688
|
-
...
|
|
4689
|
-
(
|
|
4968
|
+
...meters.map(
|
|
4969
|
+
(meter) => `${meter.id ?? "(unknown)"} | ${meter.name ?? "(unknown)"}`
|
|
4690
4970
|
)
|
|
4691
4971
|
]
|
|
4692
4972
|
];
|
|
@@ -7143,17 +7423,58 @@ async function analyzeSourceGraph(entryFile, adapter) {
|
|
|
7143
7423
|
}
|
|
7144
7424
|
async function computeWorkersHarnessFingerprintWithAdapter(adapter) {
|
|
7145
7425
|
const { readdir } = await import("fs/promises");
|
|
7426
|
+
const addFilePart = async (parts2, rootDir, filePath) => {
|
|
7427
|
+
const contents = await readFile(filePath, "utf-8");
|
|
7428
|
+
parts2.push({
|
|
7429
|
+
name: `${basename(rootDir)}:${filePath.slice(rootDir.length + 1)}`,
|
|
7430
|
+
hash: sha256(contents)
|
|
7431
|
+
});
|
|
7432
|
+
};
|
|
7433
|
+
const collectTopLevelTsFiles = async (rootDir, parts2) => {
|
|
7434
|
+
if (!await fileExists(rootDir)) return;
|
|
7435
|
+
const entries2 = await readdir(rootDir, { withFileTypes: true });
|
|
7436
|
+
const tsFiles2 = entries2.filter((entry) => entry.isFile() && /\.[cm]?ts$/.test(entry.name)).map((entry) => entry.name).sort();
|
|
7437
|
+
for (const name of tsFiles2) {
|
|
7438
|
+
await addFilePart(parts2, rootDir, join4(rootDir, name));
|
|
7439
|
+
}
|
|
7440
|
+
};
|
|
7441
|
+
const collectIntegrationBatchingFiles = async (rootDir, parts2) => {
|
|
7442
|
+
if (!await fileExists(rootDir)) return;
|
|
7443
|
+
const entries2 = await readdir(rootDir, { withFileTypes: true });
|
|
7444
|
+
const filePaths = [];
|
|
7445
|
+
for (const entry of entries2) {
|
|
7446
|
+
if (entry.isFile() && (entry.name === "play-runtime-batching-registry.ts" || /^batching.*\.ts$/.test(entry.name))) {
|
|
7447
|
+
filePaths.push(join4(rootDir, entry.name));
|
|
7448
|
+
}
|
|
7449
|
+
if (entry.isDirectory()) {
|
|
7450
|
+
const batchingFile = join4(rootDir, entry.name, "batching.ts");
|
|
7451
|
+
if (await fileExists(batchingFile)) {
|
|
7452
|
+
filePaths.push(batchingFile);
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
for (const filePath of filePaths.sort()) {
|
|
7457
|
+
await addFilePart(parts2, rootDir, filePath);
|
|
7458
|
+
}
|
|
7459
|
+
};
|
|
7146
7460
|
const entries = await readdir(adapter.workersHarnessFilesDir, {
|
|
7147
7461
|
withFileTypes: true
|
|
7148
7462
|
});
|
|
7149
7463
|
const tsFiles = entries.filter((e) => e.isFile() && /\.[cm]?ts$/.test(e.name)).map((e) => e.name).sort();
|
|
7150
7464
|
const parts = [];
|
|
7151
7465
|
for (const name of tsFiles) {
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7466
|
+
await addFilePart(
|
|
7467
|
+
parts,
|
|
7468
|
+
adapter.workersHarnessFilesDir,
|
|
7469
|
+
join4(adapter.workersHarnessFilesDir, name)
|
|
7155
7470
|
);
|
|
7156
|
-
|
|
7471
|
+
}
|
|
7472
|
+
for (const dir of adapter.workersRuntimeFingerprintDirs ?? []) {
|
|
7473
|
+
if (basename(dir) === "integrations") {
|
|
7474
|
+
await collectIntegrationBatchingFiles(dir, parts);
|
|
7475
|
+
} else {
|
|
7476
|
+
await collectTopLevelTsFiles(dir, parts);
|
|
7477
|
+
}
|
|
7157
7478
|
}
|
|
7158
7479
|
return sha256(JSON.stringify(parts));
|
|
7159
7480
|
}
|
|
@@ -8017,6 +8338,9 @@ function createSdkPlayBundlingAdapter() {
|
|
|
8017
8338
|
sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
|
|
8018
8339
|
workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
|
|
8019
8340
|
workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
|
|
8341
|
+
workersRuntimeFingerprintDirs: [
|
|
8342
|
+
resolve8(PROJECT_ROOT, "shared_libs", "play-runtime")
|
|
8343
|
+
],
|
|
8020
8344
|
discoverPackagedLocalFiles,
|
|
8021
8345
|
warnAboutNonDevelopmentBundling
|
|
8022
8346
|
};
|
|
@@ -8027,7 +8351,8 @@ async function bundlePlayFile2(filePath, options = {}) {
|
|
|
8027
8351
|
exportName: options.exportName,
|
|
8028
8352
|
adapter: createSdkPlayBundlingAdapter()
|
|
8029
8353
|
});
|
|
8030
|
-
if (result.success)
|
|
8354
|
+
if (result.success)
|
|
8355
|
+
validatePlaySourceFilesHaveNoInlineSecrets(result.sourceFiles);
|
|
8031
8356
|
return result;
|
|
8032
8357
|
}
|
|
8033
8358
|
|
|
@@ -8209,17 +8534,17 @@ function templateExample(template) {
|
|
|
8209
8534
|
case "people-list":
|
|
8210
8535
|
return "deepline plays bootstrap people-list --from provider:dropleads_search_people --out people.play.ts";
|
|
8211
8536
|
case "company-list":
|
|
8212
|
-
return "deepline plays bootstrap company-list --from provider:
|
|
8537
|
+
return "deepline plays bootstrap company-list --from provider:crustdata_companydb_search --out companies.play.ts";
|
|
8213
8538
|
case "people-email":
|
|
8214
8539
|
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";
|
|
8215
8540
|
case "people-phone":
|
|
8216
8541
|
return "deepline plays bootstrap people-phone --from csv:data/vp_contacts.csv --using play:prebuilt/person-to-phone --out phone-flow.play.ts";
|
|
8217
8542
|
case "company-people":
|
|
8218
|
-
return "deepline plays bootstrap company-people --from provider:
|
|
8543
|
+
return "deepline plays bootstrap company-people --from provider:crustdata_companydb_search --using play:prebuilt/company-to-contact --out company-people.play.ts";
|
|
8219
8544
|
case "company-people-email":
|
|
8220
|
-
return "deepline plays bootstrap company-people-email --from provider:
|
|
8545
|
+
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";
|
|
8221
8546
|
case "company-people-phone":
|
|
8222
|
-
return "deepline plays bootstrap company-people-phone --from provider:
|
|
8547
|
+
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";
|
|
8223
8548
|
}
|
|
8224
8549
|
}
|
|
8225
8550
|
var PLAY_BOOTSTRAP_STAGE_NAMES = [
|
|
@@ -9521,8 +9846,8 @@ Examples:
|
|
|
9521
9846
|
deepline plays run email-flow.play.ts --input '{"limit":5}' --watch
|
|
9522
9847
|
|
|
9523
9848
|
deepline plays bootstrap people-email --from provider:dropleads_search_people --using providers:hunter_email_finder,leadmagic_email_finder --limit 5 --out prospecting.play.ts
|
|
9524
|
-
deepline plays bootstrap company-people-email --from provider:
|
|
9525
|
-
deepline plays bootstrap company-list --from provider:
|
|
9849
|
+
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
|
|
9850
|
+
deepline plays bootstrap company-list --from provider:crustdata_companydb_search --limit 5 --out companies.play.ts
|
|
9526
9851
|
`
|
|
9527
9852
|
).option("--name <name>", "Generated play name").option(
|
|
9528
9853
|
"--from <ref>",
|
|
@@ -9701,11 +10026,11 @@ function createCliProgress(enabled) {
|
|
|
9701
10026
|
|
|
9702
10027
|
// src/cli/trace.ts
|
|
9703
10028
|
var cliTraceStartedAt = Date.now();
|
|
9704
|
-
function
|
|
10029
|
+
function isTruthyEnv2(value) {
|
|
9705
10030
|
return value === "1" || value === "true" || value === "yes";
|
|
9706
10031
|
}
|
|
9707
10032
|
function isCliTraceEnabled() {
|
|
9708
|
-
return
|
|
10033
|
+
return isTruthyEnv2(process.env.DEEPLINE_CLI_TRACE);
|
|
9709
10034
|
}
|
|
9710
10035
|
function recordCliTrace(event) {
|
|
9711
10036
|
if (!isCliTraceEnabled()) {
|
|
@@ -12825,7 +13150,7 @@ function writeStartedPlayRun(input2) {
|
|
|
12825
13150
|
);
|
|
12826
13151
|
}
|
|
12827
13152
|
function parsePlayRunOptions(args) {
|
|
12828
|
-
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
|
|
13153
|
+
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.";
|
|
12829
13154
|
let filePath = null;
|
|
12830
13155
|
let playName = null;
|
|
12831
13156
|
let input2 = null;
|
|
@@ -14164,7 +14489,7 @@ function playRunCommand(play, options) {
|
|
|
14164
14489
|
return `deepline plays run ${target} --input '{...}' --watch`;
|
|
14165
14490
|
}
|
|
14166
14491
|
function summarizePlayListItemForCli(play, options) {
|
|
14167
|
-
const aliases = play.aliases
|
|
14492
|
+
const aliases = play.aliases ?? [];
|
|
14168
14493
|
const csvInput = playSchemaMetadata(play.inputSchema, "csvInput");
|
|
14169
14494
|
const rowOutputSchema = playSchemaMetadata(
|
|
14170
14495
|
play.outputSchema,
|
|
@@ -14619,8 +14944,8 @@ Idempotent execution:
|
|
|
14619
14944
|
.dataset('companies_v1', companies)
|
|
14620
14945
|
.withColumn('cto', (row, ctx) => ctx.tools.execute({
|
|
14621
14946
|
id: 'find_cto',
|
|
14622
|
-
tool: '
|
|
14623
|
-
input: {
|
|
14947
|
+
tool: 'dropleads_search_people',
|
|
14948
|
+
input: { filters: { companyDomains: [row.domain], jobTitles: ['CTO'] }, pagination: { page: 1, limit: 1 } },
|
|
14624
14949
|
}))
|
|
14625
14950
|
.run({ key: 'domain' });
|
|
14626
14951
|
|
|
@@ -14766,14 +15091,12 @@ Examples:
|
|
|
14766
15091
|
]);
|
|
14767
15092
|
});
|
|
14768
15093
|
addPlaySearchCommand(play.command("search <query>"));
|
|
14769
|
-
play.command("grep <query>").description(
|
|
14770
|
-
"Literal grep over play names, aliases, schemas, and descriptions."
|
|
14771
|
-
).addHelpText(
|
|
15094
|
+
play.command("grep <query>").description("Literal grep over play names, schemas, and descriptions.").addHelpText(
|
|
14772
15095
|
"after",
|
|
14773
15096
|
`
|
|
14774
15097
|
Notes:
|
|
14775
15098
|
Literal registry filtering. Terms are matched case-insensitively against play
|
|
14776
|
-
names, references, display names,
|
|
15099
|
+
names, references, display names, ownership, and schemas. Use
|
|
14777
15100
|
--mode phrase for exact phrase matching, --mode any for OR, and the default
|
|
14778
15101
|
--mode all for AND.
|
|
14779
15102
|
|
|
@@ -14794,7 +15117,7 @@ Examples:
|
|
|
14794
15117
|
"after",
|
|
14795
15118
|
`
|
|
14796
15119
|
Notes:
|
|
14797
|
-
Compact contract read for schemas,
|
|
15120
|
+
Compact contract read for schemas, examples, ownership, and the run command.
|
|
14798
15121
|
Use \`get\` when you need full metadata, revisions, source fields, or latest runs.
|
|
14799
15122
|
|
|
14800
15123
|
Examples:
|
|
@@ -16818,7 +17141,8 @@ async function buildPlanArgs(args) {
|
|
|
16818
17141
|
"--name",
|
|
16819
17142
|
"--rows",
|
|
16820
17143
|
"--with-force",
|
|
16821
|
-
"--timeout"
|
|
17144
|
+
"--timeout",
|
|
17145
|
+
"--profile"
|
|
16822
17146
|
]);
|
|
16823
17147
|
const localBooleanOptions = /* @__PURE__ */ new Set([
|
|
16824
17148
|
"--dry-run",
|
|
@@ -17958,6 +18282,9 @@ function registerEnrichCommand(program) {
|
|
|
17958
18282
|
).option("--in-place", "Write enriched output back to the input CSV.").option(
|
|
17959
18283
|
"--timeout <SECONDS>",
|
|
17960
18284
|
"API read timeout for enrich status/API requests."
|
|
18285
|
+
).option(
|
|
18286
|
+
"--profile <id>",
|
|
18287
|
+
"Internal/testing: override the execution profile for the generated play run."
|
|
17961
18288
|
).action(async (options, _command) => {
|
|
17962
18289
|
if (options.inPlace && options.dryRun) {
|
|
17963
18290
|
throw new Error("--in-place is not supported with --dry-run.");
|
|
@@ -18028,6 +18355,9 @@ function registerEnrichCommand(program) {
|
|
|
18028
18355
|
JSON.stringify(runtimeInput),
|
|
18029
18356
|
"--watch"
|
|
18030
18357
|
];
|
|
18358
|
+
if (options.profile) {
|
|
18359
|
+
runArgs.push("--profile", options.profile);
|
|
18360
|
+
}
|
|
18031
18361
|
if (options.noOpen) {
|
|
18032
18362
|
runArgs.push("--no-open");
|
|
18033
18363
|
}
|
|
@@ -18174,752 +18504,618 @@ Examples:
|
|
|
18174
18504
|
).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);
|
|
18175
18505
|
}
|
|
18176
18506
|
|
|
18177
|
-
// src/cli/commands/
|
|
18178
|
-
|
|
18179
|
-
|
|
18180
|
-
|
|
18181
|
-
|
|
18182
|
-
|
|
18183
|
-
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
|
|
18196
|
-
|
|
18197
|
-
|
|
18198
|
-
|
|
18199
|
-
|
|
18200
|
-
|
|
18201
|
-
|
|
18202
|
-
|
|
18507
|
+
// src/cli/commands/sessions.ts
|
|
18508
|
+
import {
|
|
18509
|
+
existsSync as existsSync8,
|
|
18510
|
+
mkdirSync as mkdirSync4,
|
|
18511
|
+
readdirSync as readdirSync2,
|
|
18512
|
+
readFileSync as readFileSync7,
|
|
18513
|
+
statSync as statSync3,
|
|
18514
|
+
writeFileSync as writeFileSync8
|
|
18515
|
+
} from "fs";
|
|
18516
|
+
import { homedir as homedir5, platform } from "os";
|
|
18517
|
+
import { basename as basename4, dirname as dirname9, join as join9, resolve as resolve12 } from "path";
|
|
18518
|
+
import { gzipSync } from "zlib";
|
|
18519
|
+
import { randomUUID } from "crypto";
|
|
18520
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
18521
|
+
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;
|
|
18522
|
+
var MAX_SESSION_UPLOAD_BYTES = 35e5;
|
|
18523
|
+
var MAX_DIRECT_SESSION_DECODED_BYTES = 50 * 1024 * 1024;
|
|
18524
|
+
var CHUNK_SIZE_BYTES = 25e5;
|
|
18525
|
+
var MAX_EVENT_STRING_CHARS = 8e3;
|
|
18526
|
+
var MAX_EVENT_LIST_ITEMS = 40;
|
|
18527
|
+
var MAX_EVENT_OBJECT_KEYS = 80;
|
|
18528
|
+
var TRUNCATION_MARKER = "...[truncated]";
|
|
18529
|
+
var NOISE_EVENT_TYPES = /* @__PURE__ */ new Set(["progress", "file-history-snapshot"]);
|
|
18530
|
+
function homeDir() {
|
|
18531
|
+
return process.env.HOME?.trim() || homedir5();
|
|
18532
|
+
}
|
|
18533
|
+
function detectShellContext() {
|
|
18534
|
+
const shellPath = process.env.SHELL?.trim() || process.env.ComSpec?.trim() || process.env.COMSPEC?.trim() || "";
|
|
18203
18535
|
return {
|
|
18204
|
-
|
|
18205
|
-
|
|
18206
|
-
|
|
18207
|
-
|
|
18208
|
-
family: "legacy_python_cli",
|
|
18209
|
-
sdk_behavior: "noop"
|
|
18210
|
-
},
|
|
18211
|
-
render: {
|
|
18212
|
-
sections: [
|
|
18213
|
-
{
|
|
18214
|
-
title: subject,
|
|
18215
|
-
lines: [note]
|
|
18216
|
-
}
|
|
18217
|
-
]
|
|
18218
|
-
}
|
|
18536
|
+
shell: shellPath ? basename4(shellPath).replace(/\.exe$/i, "") : "unknown",
|
|
18537
|
+
shell_path: shellPath || null,
|
|
18538
|
+
os: platform(),
|
|
18539
|
+
cwd: process.cwd()
|
|
18219
18540
|
};
|
|
18220
18541
|
}
|
|
18221
|
-
function
|
|
18222
|
-
|
|
18542
|
+
function claudeProjectsRoot() {
|
|
18543
|
+
return join9(homeDir(), ".claude", "projects");
|
|
18223
18544
|
}
|
|
18224
|
-
function
|
|
18225
|
-
|
|
18226
|
-
const familyIndex = args.indexOf(family);
|
|
18227
|
-
const nextToken = familyIndex >= 0 ? args[familyIndex + 1] : void 0;
|
|
18228
|
-
return nextToken && !nextToken.startsWith("-") ? nextToken : void 0;
|
|
18545
|
+
function codexSessionsRoot() {
|
|
18546
|
+
return join9(homeDir(), ".codex", "sessions");
|
|
18229
18547
|
}
|
|
18230
|
-
function
|
|
18231
|
-
|
|
18232
|
-
|
|
18233
|
-
|
|
18548
|
+
function listClaudeSessionFiles() {
|
|
18549
|
+
const root = claudeProjectsRoot();
|
|
18550
|
+
if (!existsSync8(root)) return [];
|
|
18551
|
+
const projectDirs = readDirectoryNames(root);
|
|
18552
|
+
const files = [];
|
|
18553
|
+
for (const projectDir of projectDirs) {
|
|
18554
|
+
const fullProjectDir = join9(root, projectDir);
|
|
18555
|
+
for (const fileName of readDirectoryNames(fullProjectDir)) {
|
|
18556
|
+
if (fileName.endsWith(".jsonl")) {
|
|
18557
|
+
const filePath = join9(fullProjectDir, fileName);
|
|
18558
|
+
const sessionId = sessionIdFromClaudeFilePath(filePath);
|
|
18559
|
+
const stat4 = statIfReadable(filePath);
|
|
18560
|
+
if (sessionId && stat4) {
|
|
18561
|
+
files.push({
|
|
18562
|
+
agent: "claude",
|
|
18563
|
+
sessionId,
|
|
18564
|
+
filePath,
|
|
18565
|
+
mtimeMs: stat4.mtimeMs
|
|
18566
|
+
});
|
|
18567
|
+
}
|
|
18568
|
+
}
|
|
18569
|
+
}
|
|
18570
|
+
}
|
|
18571
|
+
return files;
|
|
18234
18572
|
}
|
|
18235
|
-
function
|
|
18236
|
-
const
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
transcript workflows.
|
|
18246
|
-
|
|
18247
|
-
Examples:
|
|
18248
|
-
deepline session start --steps '["Inspect CSV","Run pilot"]'
|
|
18249
|
-
deepline session status --message "Running pilot"
|
|
18250
|
-
deepline session output --csv ./results.csv --label "Results"
|
|
18251
|
-
`
|
|
18252
|
-
).action((args, options) => {
|
|
18253
|
-
void args;
|
|
18254
|
-
printLegacyNoop({
|
|
18255
|
-
family: "session",
|
|
18256
|
-
subcommand: legacySubcommandFromArgv("session"),
|
|
18257
|
-
options
|
|
18258
|
-
});
|
|
18259
|
-
});
|
|
18260
|
-
for (const subcommand of SESSION_SUBCOMMANDS) {
|
|
18261
|
-
addLegacyNoopSubcommand(
|
|
18262
|
-
session,
|
|
18263
|
-
"session",
|
|
18264
|
-
subcommand,
|
|
18265
|
-
`Accept legacy "deepline session ${subcommand}" as an SDK no-op.`
|
|
18266
|
-
);
|
|
18267
|
-
}
|
|
18268
|
-
const backend = program.command("backend").description(
|
|
18269
|
-
"Compatibility no-ops for legacy Python local backend commands."
|
|
18270
|
-
).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
|
|
18271
|
-
"after",
|
|
18272
|
-
`
|
|
18273
|
-
Notes:
|
|
18274
|
-
The SDK CLI uses the configured Deepline host and V2 play runtime. It does not
|
|
18275
|
-
start, stop, or refresh the legacy Python local playground backend.
|
|
18276
|
-
|
|
18277
|
-
Examples:
|
|
18278
|
-
deepline backend start
|
|
18279
|
-
deepline backend stop --just-backend
|
|
18280
|
-
deepline backend status --json
|
|
18281
|
-
`
|
|
18282
|
-
).action((args, options) => {
|
|
18283
|
-
void args;
|
|
18284
|
-
printLegacyNoop({
|
|
18285
|
-
family: "backend",
|
|
18286
|
-
subcommand: legacySubcommandFromArgv("backend"),
|
|
18287
|
-
options
|
|
18288
|
-
});
|
|
18289
|
-
});
|
|
18290
|
-
for (const subcommand of BACKEND_SUBCOMMANDS) {
|
|
18291
|
-
addLegacyNoopSubcommand(
|
|
18292
|
-
backend,
|
|
18293
|
-
"backend",
|
|
18294
|
-
subcommand,
|
|
18295
|
-
`Accept legacy "deepline backend ${subcommand}" as an SDK no-op.`
|
|
18296
|
-
);
|
|
18573
|
+
function listCodexSessionFiles() {
|
|
18574
|
+
const root = codexSessionsRoot();
|
|
18575
|
+
if (!existsSync8(root)) return [];
|
|
18576
|
+
const files = [];
|
|
18577
|
+
for (const filePath of listJsonlFilesRecursive(root, 5)) {
|
|
18578
|
+
const stat4 = statIfReadable(filePath);
|
|
18579
|
+
if (!stat4) continue;
|
|
18580
|
+
const sessionId = sessionIdFromCodexFilePath(filePath) ?? readCodexSessionId(filePath);
|
|
18581
|
+
if (!sessionId) continue;
|
|
18582
|
+
files.push({ agent: "codex", sessionId, filePath, mtimeMs: stat4.mtimeMs });
|
|
18297
18583
|
}
|
|
18584
|
+
return files;
|
|
18298
18585
|
}
|
|
18299
|
-
|
|
18300
|
-
|
|
18301
|
-
|
|
18302
|
-
|
|
18586
|
+
function listSessionFiles(agent) {
|
|
18587
|
+
const files = [];
|
|
18588
|
+
if (agent === "auto" || agent === "claude") {
|
|
18589
|
+
files.push(...listClaudeSessionFiles());
|
|
18590
|
+
}
|
|
18591
|
+
if (agent === "auto" || agent === "codex") {
|
|
18592
|
+
files.push(...listCodexSessionFiles());
|
|
18593
|
+
}
|
|
18594
|
+
return files;
|
|
18303
18595
|
}
|
|
18304
|
-
function
|
|
18305
|
-
|
|
18306
|
-
|
|
18307
|
-
|
|
18308
|
-
return
|
|
18309
|
-
}
|
|
18596
|
+
function readDirectoryNames(dir) {
|
|
18597
|
+
try {
|
|
18598
|
+
return readdirSync2(dir);
|
|
18599
|
+
} catch {
|
|
18600
|
+
return [];
|
|
18601
|
+
}
|
|
18310
18602
|
}
|
|
18311
|
-
|
|
18312
|
-
const
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
|
|
18316
|
-
{
|
|
18317
|
-
|
|
18318
|
-
|
|
18319
|
-
|
|
18320
|
-
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18603
|
+
function listJsonlFilesRecursive(root, maxDepth) {
|
|
18604
|
+
const files = [];
|
|
18605
|
+
function visit(dir, depth) {
|
|
18606
|
+
if (depth > maxDepth) return;
|
|
18607
|
+
let entries;
|
|
18608
|
+
try {
|
|
18609
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
18610
|
+
} catch {
|
|
18611
|
+
return;
|
|
18612
|
+
}
|
|
18613
|
+
for (const entry of entries) {
|
|
18614
|
+
const fullPath = join9(dir, entry.name);
|
|
18615
|
+
if (entry.isDirectory()) {
|
|
18616
|
+
visit(fullPath, depth + 1);
|
|
18617
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
18618
|
+
files.push(fullPath);
|
|
18325
18619
|
}
|
|
18326
|
-
}
|
|
18327
|
-
|
|
18328
|
-
);
|
|
18620
|
+
}
|
|
18621
|
+
}
|
|
18622
|
+
visit(root, 0);
|
|
18623
|
+
return files;
|
|
18329
18624
|
}
|
|
18330
|
-
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
|
|
18344
|
-
|
|
18345
|
-
|
|
18346
|
-
|
|
18347
|
-
|
|
18348
|
-
|
|
18349
|
-
|
|
18625
|
+
function statIfReadable(filePath) {
|
|
18626
|
+
try {
|
|
18627
|
+
return statSync3(filePath);
|
|
18628
|
+
} catch {
|
|
18629
|
+
return null;
|
|
18630
|
+
}
|
|
18631
|
+
}
|
|
18632
|
+
function newestSessionFile(agent) {
|
|
18633
|
+
let newest = null;
|
|
18634
|
+
for (const candidate of listSessionFiles(agent)) {
|
|
18635
|
+
if (!newest || candidate.mtimeMs > newest.mtimeMs) {
|
|
18636
|
+
newest = candidate;
|
|
18637
|
+
}
|
|
18638
|
+
}
|
|
18639
|
+
return newest;
|
|
18640
|
+
}
|
|
18641
|
+
function sessionIdFromClaudeFilePath(filePath) {
|
|
18642
|
+
return basename4(filePath, ".jsonl");
|
|
18643
|
+
}
|
|
18644
|
+
function sessionIdFromCodexFilePath(filePath) {
|
|
18645
|
+
const match = basename4(filePath, ".jsonl").match(UUID_IN_TEXT_RE);
|
|
18646
|
+
return match?.[0] ?? null;
|
|
18647
|
+
}
|
|
18648
|
+
function readCodexSessionId(filePath) {
|
|
18649
|
+
try {
|
|
18650
|
+
for (const line of normalizedJsonLines(readFileSync7(filePath)).slice(
|
|
18651
|
+
0,
|
|
18652
|
+
20
|
|
18653
|
+
)) {
|
|
18654
|
+
const parsed = parseJsonLine(line);
|
|
18655
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
18656
|
+
const record = parsed;
|
|
18657
|
+
const payload = record.payload && typeof record.payload === "object" ? record.payload : null;
|
|
18658
|
+
const id = record.type === "session_meta" && typeof payload?.id === "string" ? payload.id : null;
|
|
18659
|
+
if (id && UUID_RE.test(id)) return id;
|
|
18660
|
+
}
|
|
18661
|
+
} catch {
|
|
18662
|
+
return null;
|
|
18663
|
+
}
|
|
18664
|
+
return null;
|
|
18665
|
+
}
|
|
18666
|
+
function sessionSearchDescription(agent) {
|
|
18667
|
+
if (agent === "claude") return "~/.claude/projects/*/<session-id>.jsonl";
|
|
18668
|
+
if (agent === "codex") return "~/.codex/sessions/**/*.jsonl";
|
|
18669
|
+
return "~/.claude/projects/*/<session-id>.jsonl or ~/.codex/sessions/**/*.jsonl";
|
|
18670
|
+
}
|
|
18671
|
+
function parseSessionAgent(value) {
|
|
18672
|
+
const normalized = String(value || "auto").trim().toLowerCase();
|
|
18673
|
+
if (normalized === "auto" || normalized === "claude" || normalized === "codex") {
|
|
18674
|
+
return normalized;
|
|
18675
|
+
}
|
|
18676
|
+
throw new Error("Invalid --agent value. Expected auto, claude, or codex.");
|
|
18677
|
+
}
|
|
18678
|
+
function findSessionFile(sessionId, agent) {
|
|
18679
|
+
if (!UUID_RE.test(sessionId)) {
|
|
18680
|
+
throw new Error(
|
|
18681
|
+
"Invalid session ID format. Expected a UUID such as 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca."
|
|
18350
18682
|
);
|
|
18351
|
-
return;
|
|
18352
18683
|
}
|
|
18353
|
-
|
|
18354
|
-
(
|
|
18355
|
-
|
|
18356
|
-
|
|
18357
|
-
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18362
|
-
|
|
18684
|
+
for (const candidate of listSessionFiles(agent)) {
|
|
18685
|
+
if (candidate.sessionId === sessionId) {
|
|
18686
|
+
return candidate;
|
|
18687
|
+
}
|
|
18688
|
+
}
|
|
18689
|
+
return null;
|
|
18690
|
+
}
|
|
18691
|
+
function resolveSessionTargets(input2) {
|
|
18692
|
+
const agent = parseSessionAgent(input2.agent);
|
|
18693
|
+
const targets = [];
|
|
18694
|
+
if (input2.currentSession) {
|
|
18695
|
+
const currentFile = newestSessionFile(agent);
|
|
18696
|
+
if (!currentFile) {
|
|
18697
|
+
throw new Error(
|
|
18698
|
+
`No session files found in ${sessionSearchDescription(agent)}.`
|
|
18363
18699
|
);
|
|
18364
18700
|
}
|
|
18701
|
+
targets.push({
|
|
18702
|
+
sessionId: currentFile.sessionId,
|
|
18703
|
+
label: `${currentFile.agent}-session-${currentFile.sessionId}`,
|
|
18704
|
+
filePath: currentFile.filePath,
|
|
18705
|
+
agent: currentFile.agent
|
|
18706
|
+
});
|
|
18365
18707
|
}
|
|
18366
|
-
|
|
18367
|
-
|
|
18708
|
+
for (const [index, sessionId] of (input2.sessionIds ?? []).entries()) {
|
|
18709
|
+
const candidate = findSessionFile(sessionId, agent);
|
|
18710
|
+
if (!candidate) {
|
|
18711
|
+
throw new Error(
|
|
18712
|
+
`Session file not found in ${sessionSearchDescription(agent)}: ${sessionId}`
|
|
18713
|
+
);
|
|
18714
|
+
}
|
|
18715
|
+
targets.push({
|
|
18716
|
+
sessionId,
|
|
18717
|
+
label: input2.labels?.[index] ?? `${candidate.agent}-session-${sessionId}`,
|
|
18718
|
+
filePath: candidate.filePath,
|
|
18719
|
+
agent: candidate.agent
|
|
18720
|
+
});
|
|
18368
18721
|
}
|
|
18369
|
-
if (
|
|
18370
|
-
|
|
18371
|
-
{
|
|
18372
|
-
ok: true,
|
|
18373
|
-
unchanged: true,
|
|
18374
|
-
organization: target,
|
|
18375
|
-
render: {
|
|
18376
|
-
sections: [
|
|
18377
|
-
{ title: "org switch", lines: [`Already on ${target.name}.`] }
|
|
18378
|
-
]
|
|
18379
|
-
}
|
|
18380
|
-
},
|
|
18381
|
-
{ json: options.json }
|
|
18382
|
-
);
|
|
18383
|
-
return;
|
|
18722
|
+
if (targets.length === 0) {
|
|
18723
|
+
throw new Error("One of --session-id or --current-session is required.");
|
|
18384
18724
|
}
|
|
18385
|
-
|
|
18386
|
-
api_key: config.apiKey,
|
|
18387
|
-
org_id: target.org_id
|
|
18388
|
-
});
|
|
18389
|
-
saveHostEnvValues(config.baseUrl, {
|
|
18390
|
-
DEEPLINE_API_KEY: switched.api_key,
|
|
18391
|
-
DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
|
|
18392
|
-
DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
|
|
18393
|
-
});
|
|
18394
|
-
const { api_key: _apiKey, ...publicSwitched } = switched;
|
|
18395
|
-
printCommandEnvelope(
|
|
18396
|
-
{
|
|
18397
|
-
ok: true,
|
|
18398
|
-
host_env_path: hostEnvFilePath(config.baseUrl),
|
|
18399
|
-
...publicSwitched,
|
|
18400
|
-
api_key_saved: true,
|
|
18401
|
-
render: {
|
|
18402
|
-
sections: [
|
|
18403
|
-
{
|
|
18404
|
-
title: "org switch",
|
|
18405
|
-
lines: [
|
|
18406
|
-
`Switched to ${switched.org_name}.`,
|
|
18407
|
-
`Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
|
|
18408
|
-
]
|
|
18409
|
-
}
|
|
18410
|
-
]
|
|
18411
|
-
}
|
|
18412
|
-
},
|
|
18413
|
-
{ json: options.json }
|
|
18414
|
-
);
|
|
18725
|
+
return targets;
|
|
18415
18726
|
}
|
|
18416
|
-
|
|
18417
|
-
|
|
18418
|
-
|
|
18419
|
-
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
});
|
|
18423
|
-
saveHostEnvValues(config.baseUrl, {
|
|
18424
|
-
DEEPLINE_API_KEY: created.api_key,
|
|
18425
|
-
DEEPLINE_ACTIVE_ORG_ID: created.org_id,
|
|
18426
|
-
DEEPLINE_ACTIVE_ORG_NAME: created.org_name
|
|
18427
|
-
});
|
|
18428
|
-
const { api_key: _apiKey, ...publicCreated } = created;
|
|
18429
|
-
printCommandEnvelope(
|
|
18430
|
-
{
|
|
18431
|
-
ok: true,
|
|
18432
|
-
...publicCreated,
|
|
18433
|
-
api_key_saved: true,
|
|
18434
|
-
switched: true,
|
|
18435
|
-
host_env_path: hostEnvFilePath(config.baseUrl),
|
|
18436
|
-
render: {
|
|
18437
|
-
sections: [
|
|
18438
|
-
{
|
|
18439
|
-
title: "org create",
|
|
18440
|
-
lines: [
|
|
18441
|
-
`Created organization: ${created.org_name}.`,
|
|
18442
|
-
`Switched to ${created.org_name}.`,
|
|
18443
|
-
`Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
|
|
18444
|
-
]
|
|
18445
|
-
}
|
|
18446
|
-
]
|
|
18447
|
-
}
|
|
18448
|
-
},
|
|
18449
|
-
{ json: options.json }
|
|
18450
|
-
);
|
|
18727
|
+
function parseJsonLine(line) {
|
|
18728
|
+
try {
|
|
18729
|
+
return JSON.parse(line);
|
|
18730
|
+
} catch {
|
|
18731
|
+
return null;
|
|
18732
|
+
}
|
|
18451
18733
|
}
|
|
18452
|
-
function
|
|
18453
|
-
|
|
18454
|
-
"after",
|
|
18455
|
-
`
|
|
18456
|
-
Notes:
|
|
18457
|
-
Organizations are workspaces. Switching organizations mutates the saved host
|
|
18458
|
-
auth file so later CLI commands target the selected workspace.
|
|
18459
|
-
|
|
18460
|
-
Examples:
|
|
18461
|
-
deepline org list --json
|
|
18462
|
-
deepline org create Acme --json
|
|
18463
|
-
deepline org switch 2
|
|
18464
|
-
deepline org switch --org-id org_123 --json
|
|
18465
|
-
`
|
|
18466
|
-
);
|
|
18467
|
-
org.command("list").description("List your organizations.").addHelpText(
|
|
18468
|
-
"after",
|
|
18469
|
-
`
|
|
18470
|
-
Notes:
|
|
18471
|
-
Read-only. Marks the active organization when the server returns that metadata.
|
|
18472
|
-
|
|
18473
|
-
Examples:
|
|
18474
|
-
deepline org list
|
|
18475
|
-
deepline org list --json
|
|
18476
|
-
`
|
|
18477
|
-
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
|
|
18478
|
-
org.command("create <name>").description("Create a new organization and switch this CLI to it.").addHelpText(
|
|
18479
|
-
"after",
|
|
18480
|
-
`
|
|
18481
|
-
Notes:
|
|
18482
|
-
Mutates workspace state. The new organization is created for the current
|
|
18483
|
-
authenticated user, then the returned API key is saved for this host so later
|
|
18484
|
-
CLI commands target the new organization.
|
|
18485
|
-
|
|
18486
|
-
Examples:
|
|
18487
|
-
deepline org create Acme
|
|
18488
|
-
deepline org create "Acme Sales" --json
|
|
18489
|
-
`
|
|
18490
|
-
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgCreate);
|
|
18491
|
-
org.command("switch [selection]").description(
|
|
18492
|
-
"Switch to another organization and save the new API key in the host auth file."
|
|
18493
|
-
).addHelpText(
|
|
18494
|
-
"after",
|
|
18495
|
-
`
|
|
18496
|
-
Notes:
|
|
18497
|
-
Mutates the saved host auth file. Selection can be a list number, exact
|
|
18498
|
-
organization name, or organization id. Without a selection, prints choices.
|
|
18499
|
-
|
|
18500
|
-
Examples:
|
|
18501
|
-
deepline org switch
|
|
18502
|
-
deepline org switch 2
|
|
18503
|
-
deepline org switch --org-id org_123 --json
|
|
18504
|
-
`
|
|
18505
|
-
).option("--org-id <id>", "Switch using an explicit organization id").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgSwitch);
|
|
18506
|
-
}
|
|
18507
|
-
|
|
18508
|
-
// src/cli/commands/quickstart.ts
|
|
18509
|
-
import { spawn, spawnSync } from "child_process";
|
|
18510
|
-
import { randomBytes } from "crypto";
|
|
18511
|
-
import { createServer } from "http";
|
|
18512
|
-
var EXIT_OK2 = 0;
|
|
18513
|
-
var EXIT_AUTH2 = 1;
|
|
18514
|
-
var EXIT_SERVER3 = 2;
|
|
18515
|
-
var MAX_PROMPT_LENGTH = 8e3;
|
|
18516
|
-
var MAX_BODY_BYTES = 64 * 1024;
|
|
18517
|
-
function shellQuote(arg) {
|
|
18518
|
-
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
18734
|
+
function normalizedJsonLines(raw) {
|
|
18735
|
+
return raw.toString("utf8").split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
18519
18736
|
}
|
|
18520
|
-
function
|
|
18521
|
-
|
|
18522
|
-
|
|
18523
|
-
|
|
18524
|
-
|
|
18525
|
-
|
|
18526
|
-
|
|
18527
|
-
|
|
18528
|
-
return false;
|
|
18737
|
+
function stripNoiseEvents(raw) {
|
|
18738
|
+
const lines = [];
|
|
18739
|
+
for (const line of normalizedJsonLines(raw)) {
|
|
18740
|
+
const parsed = parseJsonLine(line);
|
|
18741
|
+
if (parsed && typeof parsed === "object" && NOISE_EVENT_TYPES.has(String(parsed.type ?? ""))) {
|
|
18742
|
+
continue;
|
|
18743
|
+
}
|
|
18744
|
+
lines.push(line);
|
|
18529
18745
|
}
|
|
18746
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
18747
|
+
` : "", "utf8");
|
|
18530
18748
|
}
|
|
18531
|
-
function
|
|
18532
|
-
|
|
18533
|
-
|
|
18534
|
-
|
|
18535
|
-
|
|
18536
|
-
|
|
18537
|
-
|
|
18538
|
-
|
|
18539
|
-
|
|
18540
|
-
|
|
18541
|
-
|
|
18542
|
-
|
|
18543
|
-
|
|
18544
|
-
|
|
18545
|
-
|
|
18546
|
-
|
|
18547
|
-
|
|
18548
|
-
|
|
18549
|
-
req.destroy();
|
|
18550
|
-
}
|
|
18551
|
-
});
|
|
18552
|
-
req.on("end", () => resolve16(raw));
|
|
18553
|
-
req.on("error", reject);
|
|
18554
|
-
});
|
|
18555
|
-
}
|
|
18556
|
-
function writeJson(res, status, payload) {
|
|
18557
|
-
res.writeHead(status, { "content-type": "application/json" });
|
|
18558
|
-
res.end(JSON.stringify(payload));
|
|
18749
|
+
function messageContentKey(value) {
|
|
18750
|
+
const message = value.message;
|
|
18751
|
+
if (!message || typeof message !== "object") return null;
|
|
18752
|
+
const content = message.content;
|
|
18753
|
+
if (typeof content === "string") return content;
|
|
18754
|
+
if (!Array.isArray(content)) return null;
|
|
18755
|
+
return content.map((block) => {
|
|
18756
|
+
if (!block || typeof block !== "object") return String(block);
|
|
18757
|
+
const record = block;
|
|
18758
|
+
const type = String(record.type ?? "");
|
|
18759
|
+
if (type === "tool_use") {
|
|
18760
|
+
return `tool_use:${String(record.name ?? "")}:${String(record.id ?? "")}`;
|
|
18761
|
+
}
|
|
18762
|
+
if (type === "tool_result") {
|
|
18763
|
+
return `tool_result:${String(record.tool_use_id ?? "")}`;
|
|
18764
|
+
}
|
|
18765
|
+
return String(record.text ?? type);
|
|
18766
|
+
}).join("\n");
|
|
18559
18767
|
}
|
|
18560
|
-
function
|
|
18561
|
-
const
|
|
18562
|
-
|
|
18563
|
-
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
|
|
18568
|
-
|
|
18569
|
-
|
|
18570
|
-
|
|
18571
|
-
return;
|
|
18768
|
+
function dedupConsecutiveEvents(raw) {
|
|
18769
|
+
const rawLines = normalizedJsonLines(raw);
|
|
18770
|
+
const parsedEvents = rawLines.map(parseJsonLine);
|
|
18771
|
+
const output2 = [];
|
|
18772
|
+
let index = 0;
|
|
18773
|
+
while (index < parsedEvents.length) {
|
|
18774
|
+
const event = parsedEvents[index];
|
|
18775
|
+
if (!event || typeof event !== "object") {
|
|
18776
|
+
output2.push(rawLines[index] ?? "");
|
|
18777
|
+
index += 1;
|
|
18778
|
+
continue;
|
|
18572
18779
|
}
|
|
18573
|
-
|
|
18574
|
-
|
|
18575
|
-
|
|
18780
|
+
const record = event;
|
|
18781
|
+
const eventType = String(record.type ?? "");
|
|
18782
|
+
const eventKey = messageContentKey(record);
|
|
18783
|
+
if (!["user", "assistant"].includes(eventType) || !eventKey) {
|
|
18784
|
+
output2.push(rawLines[index] ?? "");
|
|
18785
|
+
index += 1;
|
|
18786
|
+
continue;
|
|
18576
18787
|
}
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
|
|
18581
|
-
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
const prompt = typeof body?.prompt === "string" ? body.prompt.trim() : "";
|
|
18586
|
-
const workflowId = typeof body?.workflow_id === "string" && body.workflow_id.trim() ? body.workflow_id.trim() : null;
|
|
18587
|
-
if (!state || state !== input2.state) {
|
|
18588
|
-
writeJson(res, 403, { error: "Invalid quickstart state token." });
|
|
18589
|
-
return;
|
|
18590
|
-
}
|
|
18591
|
-
if (!prompt) {
|
|
18592
|
-
writeJson(res, 400, { error: "prompt is required." });
|
|
18593
|
-
return;
|
|
18594
|
-
}
|
|
18595
|
-
if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
18596
|
-
writeJson(res, 400, {
|
|
18597
|
-
error: `prompt exceeds ${MAX_PROMPT_LENGTH} characters.`
|
|
18598
|
-
});
|
|
18599
|
-
return;
|
|
18600
|
-
}
|
|
18601
|
-
writeJson(res, 200, { ok: true });
|
|
18602
|
-
setImmediate(() => input2.onSelection({ prompt, workflowId }));
|
|
18603
|
-
}).catch(() => {
|
|
18604
|
-
writeJson(res, 400, { error: "Invalid request body." });
|
|
18605
|
-
});
|
|
18606
|
-
});
|
|
18607
|
-
return new Promise((resolve16, reject) => {
|
|
18608
|
-
server.once("error", reject);
|
|
18609
|
-
server.listen(0, "127.0.0.1", () => {
|
|
18610
|
-
const address = server.address();
|
|
18611
|
-
if (!address || typeof address === "string") {
|
|
18612
|
-
reject(new Error("Failed to bind quickstart callback server."));
|
|
18613
|
-
return;
|
|
18788
|
+
let runCount = 1;
|
|
18789
|
+
let cursor = index + 1;
|
|
18790
|
+
while (cursor < parsedEvents.length) {
|
|
18791
|
+
const next = parsedEvents[cursor];
|
|
18792
|
+
if (!next || typeof next !== "object") break;
|
|
18793
|
+
const nextRecord = next;
|
|
18794
|
+
if (String(nextRecord.type ?? "") !== eventType || messageContentKey(nextRecord) !== eventKey) {
|
|
18795
|
+
break;
|
|
18614
18796
|
}
|
|
18615
|
-
|
|
18616
|
-
|
|
18617
|
-
});
|
|
18618
|
-
}
|
|
18619
|
-
async function handleQuickstart(options) {
|
|
18620
|
-
const jsonOutput = shouldEmitJson(options.json === true);
|
|
18621
|
-
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
18622
|
-
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
18623
|
-
let apiKey = resolveApiKeyForBaseUrl(baseUrl);
|
|
18624
|
-
if (!apiKey) {
|
|
18625
|
-
if (interactive) {
|
|
18626
|
-
console.error("Not connected yet \u2014 registering this device first.");
|
|
18627
|
-
const registerExit = await handleRegister([]);
|
|
18628
|
-
if (registerExit !== EXIT_OK2) return registerExit;
|
|
18629
|
-
apiKey = resolveApiKeyForBaseUrl(baseUrl);
|
|
18797
|
+
runCount += 1;
|
|
18798
|
+
cursor += 1;
|
|
18630
18799
|
}
|
|
18631
|
-
if (
|
|
18632
|
-
|
|
18633
|
-
|
|
18800
|
+
if (runCount > 1) {
|
|
18801
|
+
record._repeat_count = runCount;
|
|
18802
|
+
record._repeat_summary = `${runCount} consecutive identical ${eventType} messages collapsed`;
|
|
18803
|
+
output2.push(JSON.stringify(record));
|
|
18804
|
+
index = cursor;
|
|
18805
|
+
continue;
|
|
18634
18806
|
}
|
|
18807
|
+
output2.push(rawLines[index] ?? "");
|
|
18808
|
+
index += 1;
|
|
18635
18809
|
}
|
|
18636
|
-
|
|
18637
|
-
|
|
18638
|
-
|
|
18639
|
-
|
|
18640
|
-
|
|
18641
|
-
|
|
18642
|
-
|
|
18643
|
-
callback = await startCallbackServer({
|
|
18644
|
-
state,
|
|
18645
|
-
onSelection: (selection) => resolveSelection(selection)
|
|
18646
|
-
});
|
|
18647
|
-
} catch (error) {
|
|
18648
|
-
console.error(
|
|
18649
|
-
`Could not start the local quickstart listener: ${error instanceof Error ? error.message : String(error)}`
|
|
18650
|
-
);
|
|
18651
|
-
return EXIT_SERVER3;
|
|
18810
|
+
return Buffer.from(output2.length > 0 ? `${output2.join("\n")}
|
|
18811
|
+
` : "", "utf8");
|
|
18812
|
+
}
|
|
18813
|
+
function compactEventValue(value) {
|
|
18814
|
+
if (typeof value === "string") {
|
|
18815
|
+
if (value.length <= MAX_EVENT_STRING_CHARS) return value;
|
|
18816
|
+
return `${value.slice(0, MAX_EVENT_STRING_CHARS - TRUNCATION_MARKER.length)}${TRUNCATION_MARKER}`;
|
|
18652
18817
|
}
|
|
18653
|
-
|
|
18654
|
-
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18818
|
+
if (Array.isArray(value)) {
|
|
18819
|
+
const compacted = value.slice(0, MAX_EVENT_LIST_ITEMS).map(compactEventValue);
|
|
18820
|
+
if (value.length > MAX_EVENT_LIST_ITEMS) {
|
|
18821
|
+
compacted.push(
|
|
18822
|
+
`${TRUNCATION_MARKER} ${value.length - MAX_EVENT_LIST_ITEMS} more item(s)`
|
|
18823
|
+
);
|
|
18824
|
+
}
|
|
18825
|
+
return compacted;
|
|
18658
18826
|
}
|
|
18659
|
-
|
|
18660
|
-
|
|
18661
|
-
|
|
18662
|
-
(
|
|
18663
|
-
|
|
18827
|
+
if (value && typeof value === "object") {
|
|
18828
|
+
const entries = Object.entries(value);
|
|
18829
|
+
const compacted = {};
|
|
18830
|
+
for (const [key, item] of entries.slice(0, MAX_EVENT_OBJECT_KEYS)) {
|
|
18831
|
+
compacted[key] = compactEventValue(item);
|
|
18832
|
+
}
|
|
18833
|
+
if (entries.length > MAX_EVENT_OBJECT_KEYS) {
|
|
18834
|
+
compacted._truncated_keys = entries.length - MAX_EVENT_OBJECT_KEYS;
|
|
18835
|
+
}
|
|
18836
|
+
return compacted;
|
|
18837
|
+
}
|
|
18838
|
+
return value;
|
|
18839
|
+
}
|
|
18840
|
+
function selectiveCompactToolResults(raw) {
|
|
18841
|
+
const lines = [];
|
|
18842
|
+
for (const line of normalizedJsonLines(raw)) {
|
|
18843
|
+
const parsed = parseJsonLine(line);
|
|
18844
|
+
if (!parsed || typeof parsed !== "object") {
|
|
18845
|
+
lines.push(line);
|
|
18846
|
+
continue;
|
|
18847
|
+
}
|
|
18848
|
+
const record = parsed;
|
|
18849
|
+
if (record.type === "user") {
|
|
18850
|
+
const message = record.message;
|
|
18851
|
+
const content = message && typeof message === "object" ? message.content : null;
|
|
18852
|
+
if (Array.isArray(content)) {
|
|
18853
|
+
message.content = content.map(
|
|
18854
|
+
(block) => block && typeof block === "object" && block.type === "tool_result" ? compactEventValue(block) : block
|
|
18855
|
+
);
|
|
18856
|
+
}
|
|
18857
|
+
}
|
|
18858
|
+
lines.push(JSON.stringify(record));
|
|
18859
|
+
}
|
|
18860
|
+
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
18861
|
+
` : "", "utf8");
|
|
18862
|
+
}
|
|
18863
|
+
function prepareSessionBuffer(raw) {
|
|
18864
|
+
return selectiveCompactToolResults(
|
|
18865
|
+
dedupConsecutiveEvents(stripNoiseEvents(raw))
|
|
18664
18866
|
);
|
|
18665
|
-
|
|
18666
|
-
|
|
18667
|
-
const
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
18671
|
-
process.removeListener("SIGINT", onSigint);
|
|
18672
|
-
callback.server.close();
|
|
18673
|
-
if (outcome === "sigint") {
|
|
18674
|
-
progress.fail();
|
|
18675
|
-
console.error("\nSkipped \u2014 run `deepline quickstart` anytime.");
|
|
18676
|
-
return EXIT_OK2;
|
|
18867
|
+
}
|
|
18868
|
+
function buildSessionUploadContent(raw) {
|
|
18869
|
+
const prepared = prepareSessionBuffer(raw);
|
|
18870
|
+
const encoded = gzipSync(prepared).toString("base64");
|
|
18871
|
+
if (encoded.length <= MAX_SESSION_UPLOAD_BYTES && prepared.length <= MAX_DIRECT_SESSION_DECODED_BYTES) {
|
|
18872
|
+
return { encodedContent: encoded, needsChunking: false };
|
|
18677
18873
|
}
|
|
18678
|
-
|
|
18679
|
-
|
|
18680
|
-
|
|
18681
|
-
|
|
18874
|
+
return { encodedContent: encoded, needsChunking: true };
|
|
18875
|
+
}
|
|
18876
|
+
async function uploadPayload(path, payload) {
|
|
18877
|
+
const { http } = getAuthedHttpClient();
|
|
18878
|
+
return await http.post(path, payload);
|
|
18879
|
+
}
|
|
18880
|
+
async function uploadChunkedSessions(sessions, options) {
|
|
18881
|
+
const uploadId = randomUUID();
|
|
18882
|
+
for (const session of sessions) {
|
|
18883
|
+
const bytes = Buffer.from(session.encodedContent, "base64");
|
|
18884
|
+
const chunks = [];
|
|
18885
|
+
for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE_BYTES) {
|
|
18886
|
+
chunks.push(bytes.subarray(offset, offset + CHUNK_SIZE_BYTES));
|
|
18887
|
+
}
|
|
18888
|
+
process.stderr.write(
|
|
18889
|
+
`Uploading ${session.label} in ${chunks.length} chunk(s)...
|
|
18890
|
+
`
|
|
18682
18891
|
);
|
|
18683
|
-
|
|
18892
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
18893
|
+
await uploadPayload("/api/v2/cli/send-session/chunk", {
|
|
18894
|
+
upload_id: uploadId,
|
|
18895
|
+
session_id: session.sessionId,
|
|
18896
|
+
index,
|
|
18897
|
+
total_chunks: chunks.length,
|
|
18898
|
+
data: chunk.toString("base64")
|
|
18899
|
+
});
|
|
18900
|
+
}
|
|
18684
18901
|
}
|
|
18685
|
-
|
|
18686
|
-
|
|
18687
|
-
|
|
18688
|
-
|
|
18689
|
-
|
|
18902
|
+
const response = await uploadPayload("/api/v2/cli/send-session/finalize", {
|
|
18903
|
+
upload_id: uploadId,
|
|
18904
|
+
session_ids: sessions.map((session) => session.sessionId),
|
|
18905
|
+
labels: sessions.map((session) => session.label),
|
|
18906
|
+
environments: sessions.map(() => detectShellContext())
|
|
18907
|
+
});
|
|
18690
18908
|
printCommandEnvelope(
|
|
18691
18909
|
{
|
|
18692
|
-
|
|
18693
|
-
|
|
18694
|
-
|
|
18695
|
-
claudeCommand,
|
|
18696
|
-
url: quickstartUrl,
|
|
18697
|
-
launched: willLaunch,
|
|
18910
|
+
...response,
|
|
18911
|
+
ok: true,
|
|
18912
|
+
uploaded: sessions.length,
|
|
18698
18913
|
render: {
|
|
18699
18914
|
sections: [
|
|
18700
18915
|
{
|
|
18701
|
-
title: "
|
|
18702
|
-
lines: [
|
|
18703
|
-
...workflowId ? [`Recipe: ${workflowId}`] : [],
|
|
18704
|
-
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."
|
|
18705
|
-
]
|
|
18916
|
+
title: "sessions send",
|
|
18917
|
+
lines: ["Session uploaded to #internal-reports (chunked)."]
|
|
18706
18918
|
}
|
|
18707
|
-
]
|
|
18708
|
-
actions: [{ label: "Run", command: claudeCommand }]
|
|
18919
|
+
]
|
|
18709
18920
|
}
|
|
18710
18921
|
},
|
|
18711
|
-
{ json:
|
|
18922
|
+
{ json: options.json }
|
|
18712
18923
|
);
|
|
18713
|
-
if (willLaunch) {
|
|
18714
|
-
return launchClaude(prompt);
|
|
18715
|
-
}
|
|
18716
|
-
return EXIT_OK2;
|
|
18717
|
-
}
|
|
18718
|
-
function registerQuickstartCommands(program) {
|
|
18719
|
-
program.command("quickstart").description(
|
|
18720
|
-
"Pick a starter recipe in the browser and launch it with Claude Code."
|
|
18721
|
-
).addHelpText(
|
|
18722
|
-
"after",
|
|
18723
|
-
`
|
|
18724
|
-
Notes:
|
|
18725
|
-
Opens the hosted recipe picker in your browser. The picker sends your
|
|
18726
|
-
selection back to a temporary listener on 127.0.0.1, so the browser must run
|
|
18727
|
-
on the same machine as the CLI. Once a recipe arrives, the CLI prints the
|
|
18728
|
-
matching claude command and, when Claude Code is installed and the shell is
|
|
18729
|
-
interactive, launches it directly. Press Ctrl+C while waiting to skip.
|
|
18730
|
-
|
|
18731
|
-
Examples:
|
|
18732
|
-
deepline quickstart
|
|
18733
|
-
deepline quickstart --no-open
|
|
18734
|
-
deepline quickstart --no-launch --json
|
|
18735
|
-
deepline quickstart --timeout 120
|
|
18736
|
-
`
|
|
18737
|
-
).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(
|
|
18738
|
-
"--timeout <seconds>",
|
|
18739
|
-
"Maximum seconds to wait for a selection",
|
|
18740
|
-
"300"
|
|
18741
|
-
).action(async (options) => {
|
|
18742
|
-
process.exitCode = await handleQuickstart(options);
|
|
18743
|
-
});
|
|
18744
18924
|
}
|
|
18745
|
-
|
|
18746
|
-
|
|
18747
|
-
|
|
18748
|
-
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
18753
|
-
|
|
18925
|
+
async function handleSessionsSend(options) {
|
|
18926
|
+
if (options.file) {
|
|
18927
|
+
const filePath = resolve12(options.file);
|
|
18928
|
+
if (!existsSync8(filePath)) {
|
|
18929
|
+
throw new Error(`File not found: ${options.file}`);
|
|
18930
|
+
}
|
|
18931
|
+
const response2 = await uploadPayload("/api/v2/cli/send-session", {
|
|
18932
|
+
file: readFileSync7(filePath).toString("base64"),
|
|
18933
|
+
filename: basename4(filePath)
|
|
18934
|
+
});
|
|
18935
|
+
printCommandEnvelope(
|
|
18936
|
+
{
|
|
18937
|
+
...response2,
|
|
18938
|
+
ok: true,
|
|
18939
|
+
filename: basename4(filePath),
|
|
18940
|
+
render: {
|
|
18941
|
+
sections: [
|
|
18942
|
+
{
|
|
18943
|
+
title: "sessions send",
|
|
18944
|
+
lines: [
|
|
18945
|
+
`File '${basename4(filePath)}' uploaded to #internal-reports.`
|
|
18946
|
+
]
|
|
18947
|
+
}
|
|
18948
|
+
]
|
|
18949
|
+
}
|
|
18950
|
+
},
|
|
18951
|
+
{ json: options.json }
|
|
18754
18952
|
);
|
|
18953
|
+
return;
|
|
18755
18954
|
}
|
|
18756
|
-
|
|
18757
|
-
|
|
18758
|
-
|
|
18759
|
-
|
|
18760
|
-
|
|
18761
|
-
}
|
|
18762
|
-
|
|
18763
|
-
|
|
18764
|
-
|
|
18765
|
-
|
|
18766
|
-
|
|
18955
|
+
const targets = resolveSessionTargets({
|
|
18956
|
+
sessionIds: options.sessionId,
|
|
18957
|
+
labels: options.label,
|
|
18958
|
+
currentSession: options.currentSession,
|
|
18959
|
+
agent: options.agent
|
|
18960
|
+
});
|
|
18961
|
+
const built = targets.map((target) => {
|
|
18962
|
+
const upload = buildSessionUploadContent(readFileSync7(target.filePath));
|
|
18963
|
+
return { ...target, ...upload };
|
|
18964
|
+
});
|
|
18965
|
+
if (built.some((session) => session.needsChunking)) {
|
|
18966
|
+
await uploadChunkedSessions(built, options);
|
|
18967
|
+
return;
|
|
18767
18968
|
}
|
|
18768
|
-
|
|
18769
|
-
|
|
18770
|
-
|
|
18771
|
-
|
|
18772
|
-
|
|
18773
|
-
|
|
18774
|
-
|
|
18775
|
-
|
|
18776
|
-
|
|
18777
|
-
|
|
18778
|
-
|
|
18779
|
-
|
|
18780
|
-
|
|
18969
|
+
const response = built.length === 1 && !options.label?.length ? await uploadPayload("/api/v2/cli/send-session", {
|
|
18970
|
+
session_id: built[0]?.sessionId,
|
|
18971
|
+
content: built[0]?.encodedContent,
|
|
18972
|
+
environment: detectShellContext()
|
|
18973
|
+
}) : await uploadPayload("/api/v2/cli/send-session", {
|
|
18974
|
+
sessions: built.map((session) => ({
|
|
18975
|
+
session_id: session.sessionId,
|
|
18976
|
+
content: session.encodedContent,
|
|
18977
|
+
label: session.label,
|
|
18978
|
+
environment: detectShellContext()
|
|
18979
|
+
})),
|
|
18980
|
+
environment: detectShellContext()
|
|
18981
|
+
});
|
|
18982
|
+
printCommandEnvelope(
|
|
18983
|
+
{
|
|
18984
|
+
...response,
|
|
18985
|
+
ok: true,
|
|
18986
|
+
uploaded: built.length,
|
|
18987
|
+
render: {
|
|
18988
|
+
sections: [
|
|
18989
|
+
{
|
|
18990
|
+
title: "sessions send",
|
|
18991
|
+
lines: ["Session uploaded to #internal-reports."]
|
|
18992
|
+
}
|
|
18993
|
+
]
|
|
18781
18994
|
}
|
|
18782
|
-
}
|
|
18783
|
-
|
|
18784
|
-
|
|
18785
|
-
settled = true;
|
|
18786
|
-
output.write("\n");
|
|
18787
|
-
cleanup();
|
|
18788
|
-
resolve16(line);
|
|
18789
|
-
};
|
|
18790
|
-
const fail = (error) => {
|
|
18791
|
-
if (settled) return;
|
|
18792
|
-
settled = true;
|
|
18793
|
-
output.write("\n");
|
|
18794
|
-
cleanup();
|
|
18795
|
-
reject(error);
|
|
18796
|
-
};
|
|
18797
|
-
const processText = (text) => {
|
|
18798
|
-
for (let index = 0; index < text.length; index++) {
|
|
18799
|
-
const char = text[index];
|
|
18800
|
-
const code = char.charCodeAt(0);
|
|
18801
|
-
if (char === "\r" || char === "\n") {
|
|
18802
|
-
hiddenInputBuffer = text.slice(index + 1);
|
|
18803
|
-
finish(value);
|
|
18804
|
-
return;
|
|
18805
|
-
}
|
|
18806
|
-
if (code === 3) {
|
|
18807
|
-
fail(new Error("Secret input cancelled."));
|
|
18808
|
-
return;
|
|
18809
|
-
}
|
|
18810
|
-
if (code === 8 || code === 127) {
|
|
18811
|
-
value = value.slice(0, -1);
|
|
18812
|
-
continue;
|
|
18813
|
-
}
|
|
18814
|
-
if (code >= 32) {
|
|
18815
|
-
value += char;
|
|
18816
|
-
}
|
|
18817
|
-
}
|
|
18818
|
-
hiddenInputBuffer = "";
|
|
18819
|
-
};
|
|
18820
|
-
const onData = (chunk) => {
|
|
18821
|
-
processText(chunk.toString());
|
|
18822
|
-
};
|
|
18823
|
-
const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
|
|
18824
|
-
const onError = (error) => fail(error);
|
|
18825
|
-
input.on("data", onData);
|
|
18826
|
-
input.once("end", onEnd);
|
|
18827
|
-
input.once("error", onError);
|
|
18828
|
-
if (hiddenInputBuffer) {
|
|
18829
|
-
const buffered = hiddenInputBuffer;
|
|
18830
|
-
hiddenInputBuffer = "";
|
|
18831
|
-
processText(buffered);
|
|
18832
|
-
}
|
|
18833
|
-
});
|
|
18995
|
+
},
|
|
18996
|
+
{ json: options.json }
|
|
18997
|
+
);
|
|
18834
18998
|
}
|
|
18835
|
-
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18999
|
+
function fallbackViewerAssets() {
|
|
19000
|
+
return {
|
|
19001
|
+
css: [
|
|
19002
|
+
"body{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;margin:0;padding:16px;background:#fafafa;color:#111}",
|
|
19003
|
+
".section{background:#fff;border:1px solid #ddd;border-radius:8px;padding:12px;margin-bottom:12px}",
|
|
19004
|
+
".section h2{margin:0 0 8px 0;font-size:14px}",
|
|
19005
|
+
"pre{margin:0;white-space:pre-wrap;word-break:break-word;background:#f6f8fa;border:1px solid #e3e5e8;border-radius:6px;padding:10px}"
|
|
19006
|
+
].join(""),
|
|
19007
|
+
js: [
|
|
19008
|
+
"(() => {",
|
|
19009
|
+
'const root=document.getElementById("main-content");',
|
|
19010
|
+
'const raw=document.getElementById("raw-sessions");',
|
|
19011
|
+
"if(!root||!raw)return;",
|
|
19012
|
+
'let sessions=[];try{sessions=JSON.parse(raw.textContent||"[]")}catch{}',
|
|
19013
|
+
'root.innerHTML="";',
|
|
19014
|
+
"for(const session of sessions){",
|
|
19015
|
+
'const section=document.createElement("section");section.className="section";',
|
|
19016
|
+
'const title=document.createElement("h2");title.textContent=String(session.label||"session");',
|
|
19017
|
+
'const pre=document.createElement("pre");',
|
|
19018
|
+
'pre.textContent=(Array.isArray(session.events)?session.events:[]).map((event)=>JSON.stringify(event)).join("\\n");',
|
|
19019
|
+
"section.append(title,pre);root.appendChild(section);",
|
|
19020
|
+
"}",
|
|
19021
|
+
"})();"
|
|
19022
|
+
].join("")
|
|
19023
|
+
};
|
|
18843
19024
|
}
|
|
18844
|
-
function
|
|
18845
|
-
|
|
18846
|
-
|
|
18847
|
-
|
|
18848
|
-
|
|
19025
|
+
function loadViewerAssets() {
|
|
19026
|
+
const cliEntry = process.argv[1]?.trim() ? resolve12(process.argv[1]) : null;
|
|
19027
|
+
const candidateRoots2 = [
|
|
19028
|
+
...cliEntry ? [
|
|
19029
|
+
join9(dirname9(dirname9(cliEntry)), "viewer"),
|
|
19030
|
+
join9(
|
|
19031
|
+
dirname9(dirname9(dirname9(cliEntry))),
|
|
19032
|
+
"src",
|
|
19033
|
+
"lib",
|
|
19034
|
+
"cli",
|
|
19035
|
+
"viewer"
|
|
19036
|
+
)
|
|
19037
|
+
] : [],
|
|
19038
|
+
join9(process.cwd(), "src", "lib", "cli", "viewer")
|
|
19039
|
+
];
|
|
19040
|
+
for (const root of candidateRoots2) {
|
|
19041
|
+
try {
|
|
19042
|
+
const cssPath = join9(root, "viewer.css");
|
|
19043
|
+
const jsPath = join9(root, "viewer.js");
|
|
19044
|
+
if (!existsSync8(cssPath) || !existsSync8(jsPath)) continue;
|
|
19045
|
+
return {
|
|
19046
|
+
css: readFileSync7(cssPath, "utf8"),
|
|
19047
|
+
js: readFileSync7(jsPath, "utf8")
|
|
19048
|
+
};
|
|
19049
|
+
} catch {
|
|
19050
|
+
continue;
|
|
19051
|
+
}
|
|
18849
19052
|
}
|
|
19053
|
+
return fallbackViewerAssets();
|
|
18850
19054
|
}
|
|
18851
|
-
|
|
18852
|
-
|
|
18853
|
-
|
|
18854
|
-
|
|
18855
|
-
|
|
18856
|
-
secrets,
|
|
18857
|
-
count: secrets.length,
|
|
18858
|
-
render: {
|
|
18859
|
-
sections: [
|
|
18860
|
-
{
|
|
18861
|
-
title: "secrets",
|
|
18862
|
-
lines: secrets.length ? secrets.map(renderSecret) : ["No play secrets are configured."]
|
|
18863
|
-
}
|
|
18864
|
-
]
|
|
18865
|
-
}
|
|
18866
|
-
},
|
|
18867
|
-
{ json: options.json }
|
|
18868
|
-
);
|
|
19055
|
+
function parsePreparedEvents(buffer) {
|
|
19056
|
+
return normalizedJsonLines(buffer).map((line) => {
|
|
19057
|
+
const parsed = parseJsonLine(line);
|
|
19058
|
+
return parsed ?? line;
|
|
19059
|
+
});
|
|
18869
19060
|
}
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
const client2 = new DeeplineClient();
|
|
18873
|
-
const secret = await client2.checkSecret(name);
|
|
18874
|
-
printCommandEnvelope(
|
|
18875
|
-
{
|
|
18876
|
-
ok: Boolean(secret),
|
|
18877
|
-
name,
|
|
18878
|
-
secret: secret ?? null,
|
|
18879
|
-
render: {
|
|
18880
|
-
sections: [
|
|
18881
|
-
{
|
|
18882
|
-
title: "secret check",
|
|
18883
|
-
lines: [
|
|
18884
|
-
secret ? `${name}: active` : `${name}: missing, disabled, or empty`
|
|
18885
|
-
]
|
|
18886
|
-
}
|
|
18887
|
-
]
|
|
18888
|
-
}
|
|
18889
|
-
},
|
|
18890
|
-
{ json: options.json }
|
|
18891
|
-
);
|
|
18892
|
-
if (!secret) process.exitCode = 4;
|
|
19061
|
+
function escapeJsonForHtmlScript(json) {
|
|
19062
|
+
return json.replace(/</g, "\\u003c");
|
|
18893
19063
|
}
|
|
18894
|
-
async function
|
|
18895
|
-
|
|
18896
|
-
|
|
18897
|
-
|
|
18898
|
-
|
|
18899
|
-
|
|
18900
|
-
|
|
19064
|
+
async function handleSessionsRender(options) {
|
|
19065
|
+
const targets = resolveSessionTargets({
|
|
19066
|
+
sessionIds: options.sessionId,
|
|
19067
|
+
labels: options.label,
|
|
19068
|
+
currentSession: options.currentSession,
|
|
19069
|
+
agent: options.agent
|
|
19070
|
+
});
|
|
19071
|
+
let outputPath = options.output ? resolve12(options.output) : "";
|
|
19072
|
+
if (!outputPath) {
|
|
19073
|
+
const outputDir = join9(process.cwd(), "deepline", "data");
|
|
19074
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
19075
|
+
outputPath = join9(
|
|
19076
|
+
outputDir,
|
|
19077
|
+
targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
|
|
19078
|
+
);
|
|
19079
|
+
} else {
|
|
19080
|
+
mkdirSync4(dirname9(outputPath), { recursive: true });
|
|
18901
19081
|
}
|
|
18902
|
-
const
|
|
18903
|
-
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
|
|
18910
|
-
|
|
18911
|
-
|
|
18912
|
-
|
|
18913
|
-
|
|
19082
|
+
const sessions = targets.map((target) => ({
|
|
19083
|
+
label: target.label,
|
|
19084
|
+
events: parsePreparedEvents(
|
|
19085
|
+
prepareSessionBuffer(readFileSync7(target.filePath))
|
|
19086
|
+
)
|
|
19087
|
+
}));
|
|
19088
|
+
const { css, js } = loadViewerAssets();
|
|
19089
|
+
const refreshMeta = options.autoRefresh ? `<meta http-equiv="refresh" content="${Number.parseInt(options.autoRefresh, 10)}">` : "";
|
|
19090
|
+
const rawJson = escapeJsonForHtmlScript(JSON.stringify(sessions));
|
|
19091
|
+
const html = `<!DOCTYPE html>
|
|
19092
|
+
<html lang="en">
|
|
19093
|
+
<head>
|
|
19094
|
+
<meta charset="UTF-8">
|
|
19095
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19096
|
+
${refreshMeta}
|
|
19097
|
+
<title>Session Viewer</title>
|
|
19098
|
+
<style>${css}</style>
|
|
19099
|
+
</head>
|
|
19100
|
+
<body>
|
|
19101
|
+
<div class="layout">
|
|
19102
|
+
<div class="main" id="main-content"></div>
|
|
19103
|
+
</div>
|
|
19104
|
+
<script type="application/json" id="raw-sessions">${rawJson}</script>
|
|
19105
|
+
<script>${js}</script>
|
|
19106
|
+
</body>
|
|
19107
|
+
</html>`;
|
|
19108
|
+
writeFileSync8(outputPath, html, "utf8");
|
|
18914
19109
|
printCommandEnvelope(
|
|
18915
19110
|
{
|
|
18916
19111
|
ok: true,
|
|
18917
|
-
|
|
19112
|
+
file: outputPath,
|
|
19113
|
+
session_count: targets.length,
|
|
18918
19114
|
render: {
|
|
18919
19115
|
sections: [
|
|
18920
19116
|
{
|
|
18921
|
-
title: "
|
|
18922
|
-
lines: [
|
|
19117
|
+
title: "sessions render",
|
|
19118
|
+
lines: [`Rendered session viewer: ${outputPath}`]
|
|
18923
19119
|
}
|
|
18924
19120
|
]
|
|
18925
19121
|
}
|
|
@@ -18927,346 +19123,759 @@ async function handleSet(nameInput, forbidden, options) {
|
|
|
18927
19123
|
{ json: options.json }
|
|
18928
19124
|
);
|
|
18929
19125
|
}
|
|
18930
|
-
function
|
|
18931
|
-
|
|
19126
|
+
function collectOption(value, previous) {
|
|
19127
|
+
previous.push(value);
|
|
19128
|
+
return previous;
|
|
19129
|
+
}
|
|
19130
|
+
function registerSessionsCommands(program) {
|
|
19131
|
+
const sessions = program.command("sessions").description("Upload and render local agent session transcripts.").addHelpText(
|
|
18932
19132
|
"after",
|
|
18933
19133
|
`
|
|
18934
19134
|
Notes:
|
|
18935
|
-
|
|
18936
|
-
|
|
18937
|
-
|
|
19135
|
+
Session commands operate on local Claude and Codex JSONL files under
|
|
19136
|
+
~/.claude/projects and ~/.codex/sessions. send uploads a compacted transcript
|
|
19137
|
+
or file to Deepline. render writes a local HTML viewer.
|
|
18938
19138
|
|
|
18939
19139
|
Examples:
|
|
18940
|
-
deepline
|
|
18941
|
-
deepline
|
|
18942
|
-
deepline
|
|
19140
|
+
deepline sessions send --current-session --json
|
|
19141
|
+
deepline sessions send --agent codex --current-session --json
|
|
19142
|
+
deepline sessions send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca
|
|
19143
|
+
deepline sessions render --current-session --output session.html
|
|
18943
19144
|
`
|
|
18944
19145
|
);
|
|
18945
|
-
|
|
18946
|
-
await handleList(options);
|
|
18947
|
-
});
|
|
18948
|
-
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) => {
|
|
18949
|
-
await handleCheck(name, options);
|
|
18950
|
-
});
|
|
18951
|
-
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(
|
|
18952
|
-
async (name, forbidden, options) => {
|
|
18953
|
-
await handleSet(name, forbidden ?? [], options);
|
|
18954
|
-
}
|
|
18955
|
-
);
|
|
19146
|
+
registerSessionSendRenderCommands(sessions, "sessions");
|
|
18956
19147
|
}
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
18960
|
-
|
|
18961
|
-
|
|
18962
|
-
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
18968
|
-
|
|
18969
|
-
|
|
18970
|
-
|
|
18971
|
-
|
|
18972
|
-
|
|
18973
|
-
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
18979
|
-
|
|
18980
|
-
|
|
18981
|
-
|
|
19148
|
+
function registerSessionSendRenderCommands(parent, familyName) {
|
|
19149
|
+
parent.command("send").description("Upload session transcript(s) or a local file to Deepline.").addHelpText(
|
|
19150
|
+
"after",
|
|
19151
|
+
`
|
|
19152
|
+
Examples:
|
|
19153
|
+
deepline ${familyName} send --current-session --json
|
|
19154
|
+
deepline ${familyName} send --agent codex --current-session --json
|
|
19155
|
+
deepline ${familyName} send --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --label "pilot run"
|
|
19156
|
+
deepline ${familyName} send --file ./debug.log --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("--file <path>", "Upload a raw local file instead of a session").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleSessionsSend);
|
|
19173
|
+
parent.command("render").description("Render local session transcript(s) to an HTML viewer.").addHelpText(
|
|
19174
|
+
"after",
|
|
19175
|
+
`
|
|
19176
|
+
Examples:
|
|
19177
|
+
deepline ${familyName} render --current-session
|
|
19178
|
+
deepline ${familyName} render --agent codex --current-session
|
|
19179
|
+
deepline ${familyName} render --session-id 5a3bfb97-a2d9-49d9-82c6-52ccc03dadca --output session.html
|
|
19180
|
+
deepline ${familyName} render --current-session --auto-refresh 5 --json
|
|
19181
|
+
`
|
|
19182
|
+
).option(
|
|
19183
|
+
"--session-id <uuid>",
|
|
19184
|
+
"Claude or Codex session UUID. Repeat for multiple sessions.",
|
|
19185
|
+
collectOption,
|
|
19186
|
+
[]
|
|
19187
|
+
).option(
|
|
19188
|
+
"--label <label>",
|
|
19189
|
+
"Label for the preceding session id",
|
|
19190
|
+
collectOption,
|
|
19191
|
+
[]
|
|
19192
|
+
).option(
|
|
19193
|
+
"--agent <auto|claude|codex>",
|
|
19194
|
+
"Limit transcript discovery to one agent runtime",
|
|
19195
|
+
"auto"
|
|
19196
|
+
).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);
|
|
18982
19197
|
}
|
|
18983
|
-
|
|
18984
|
-
|
|
19198
|
+
|
|
19199
|
+
// src/cli/commands/legacy-noops.ts
|
|
19200
|
+
var SESSION_SUBCOMMANDS = [
|
|
19201
|
+
"start",
|
|
19202
|
+
"status",
|
|
19203
|
+
"output",
|
|
19204
|
+
"alert",
|
|
19205
|
+
"usage",
|
|
19206
|
+
"limit"
|
|
19207
|
+
];
|
|
19208
|
+
var BACKEND_SUBCOMMANDS = [
|
|
19209
|
+
"start",
|
|
19210
|
+
"stop",
|
|
19211
|
+
"status",
|
|
19212
|
+
"refresh-runtime",
|
|
19213
|
+
"sync-runtime"
|
|
19214
|
+
];
|
|
19215
|
+
function legacyNoopEnvelope(input2) {
|
|
19216
|
+
const command = ["deepline", input2.family, input2.subcommand].filter(Boolean).join(" ");
|
|
19217
|
+
const subject = input2.family === "session" ? "Legacy Session UI/playground command" : "Legacy local playground backend command";
|
|
19218
|
+
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.";
|
|
18985
19219
|
return {
|
|
18986
|
-
|
|
18987
|
-
|
|
18988
|
-
|
|
18989
|
-
|
|
18990
|
-
|
|
18991
|
-
|
|
18992
|
-
|
|
18993
|
-
|
|
18994
|
-
|
|
18995
|
-
|
|
18996
|
-
|
|
18997
|
-
|
|
18998
|
-
|
|
18999
|
-
|
|
19000
|
-
for (const projectDir of projectDirs) {
|
|
19001
|
-
const fullProjectDir = join9(root, projectDir);
|
|
19002
|
-
for (const fileName of readDirectoryNames(fullProjectDir)) {
|
|
19003
|
-
if (fileName.endsWith(".jsonl")) {
|
|
19004
|
-
files.push(join9(fullProjectDir, fileName));
|
|
19005
|
-
}
|
|
19220
|
+
ok: true,
|
|
19221
|
+
noop: true,
|
|
19222
|
+
command,
|
|
19223
|
+
compatibility: {
|
|
19224
|
+
family: "legacy_python_cli",
|
|
19225
|
+
sdk_behavior: "noop"
|
|
19226
|
+
},
|
|
19227
|
+
render: {
|
|
19228
|
+
sections: [
|
|
19229
|
+
{
|
|
19230
|
+
title: subject,
|
|
19231
|
+
lines: [note]
|
|
19232
|
+
}
|
|
19233
|
+
]
|
|
19006
19234
|
}
|
|
19007
|
-
}
|
|
19008
|
-
return files;
|
|
19009
|
-
}
|
|
19010
|
-
function readDirectoryNames(dir) {
|
|
19011
|
-
try {
|
|
19012
|
-
return readdirSync2(dir);
|
|
19013
|
-
} catch {
|
|
19014
|
-
return [];
|
|
19015
|
-
}
|
|
19235
|
+
};
|
|
19016
19236
|
}
|
|
19017
|
-
function
|
|
19018
|
-
|
|
19019
|
-
for (const filePath of listClaudeSessionFiles()) {
|
|
19020
|
-
try {
|
|
19021
|
-
const stat4 = statSync3(filePath);
|
|
19022
|
-
if (!newest || stat4.mtimeMs > newest.mtimeMs) {
|
|
19023
|
-
newest = { filePath, mtimeMs: stat4.mtimeMs };
|
|
19024
|
-
}
|
|
19025
|
-
} catch {
|
|
19026
|
-
continue;
|
|
19027
|
-
}
|
|
19028
|
-
}
|
|
19029
|
-
return newest?.filePath ?? null;
|
|
19237
|
+
function printLegacyNoop(input2) {
|
|
19238
|
+
printCommandEnvelope(legacyNoopEnvelope(input2), { json: input2.options.json });
|
|
19030
19239
|
}
|
|
19031
|
-
function
|
|
19032
|
-
|
|
19240
|
+
function legacySubcommandFromArgv(family) {
|
|
19241
|
+
const args = process.argv.slice(2);
|
|
19242
|
+
const familyIndex = args.indexOf(family);
|
|
19243
|
+
const nextToken = familyIndex >= 0 ? args[familyIndex + 1] : void 0;
|
|
19244
|
+
return nextToken && !nextToken.startsWith("-") ? nextToken : void 0;
|
|
19033
19245
|
}
|
|
19034
|
-
function
|
|
19035
|
-
|
|
19036
|
-
|
|
19037
|
-
|
|
19038
|
-
);
|
|
19039
|
-
}
|
|
19040
|
-
for (const filePath of listClaudeSessionFiles()) {
|
|
19041
|
-
if (sessionIdFromFilePath(filePath) === sessionId) {
|
|
19042
|
-
return filePath;
|
|
19043
|
-
}
|
|
19044
|
-
}
|
|
19045
|
-
return null;
|
|
19246
|
+
function addLegacyNoopSubcommand(parent, family, subcommand, description) {
|
|
19247
|
+
parent.command(subcommand).description(description).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").action((_args, options) => {
|
|
19248
|
+
printLegacyNoop({ family, subcommand, options });
|
|
19249
|
+
});
|
|
19046
19250
|
}
|
|
19047
|
-
function
|
|
19048
|
-
const
|
|
19049
|
-
|
|
19050
|
-
|
|
19051
|
-
|
|
19052
|
-
|
|
19053
|
-
|
|
19054
|
-
|
|
19055
|
-
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19251
|
+
function registerLegacyNoopCommands(program) {
|
|
19252
|
+
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(
|
|
19253
|
+
"after",
|
|
19254
|
+
`
|
|
19255
|
+
Notes:
|
|
19256
|
+
The SDK CLI accepts singular session commands from older agent skills so they
|
|
19257
|
+
do not fail, but it does not manage the legacy Python Session UI.
|
|
19258
|
+
Use "deepline sessions send" or "deepline sessions render" for real SDK
|
|
19259
|
+
transcript workflows. "deepline session send" and "deepline session render"
|
|
19260
|
+
are accepted aliases for those real SDK workflows.
|
|
19261
|
+
|
|
19262
|
+
Examples:
|
|
19263
|
+
deepline session start --steps '["Inspect CSV","Run pilot"]'
|
|
19264
|
+
deepline session status --message "Running pilot"
|
|
19265
|
+
deepline session output --csv ./results.csv --label "Results"
|
|
19266
|
+
`
|
|
19267
|
+
).action((args, options) => {
|
|
19268
|
+
void args;
|
|
19269
|
+
printLegacyNoop({
|
|
19270
|
+
family: "session",
|
|
19271
|
+
subcommand: legacySubcommandFromArgv("session"),
|
|
19272
|
+
options
|
|
19059
19273
|
});
|
|
19274
|
+
});
|
|
19275
|
+
registerSessionSendRenderCommands(session, "session");
|
|
19276
|
+
for (const subcommand of SESSION_SUBCOMMANDS) {
|
|
19277
|
+
addLegacyNoopSubcommand(
|
|
19278
|
+
session,
|
|
19279
|
+
"session",
|
|
19280
|
+
subcommand,
|
|
19281
|
+
`Accept legacy "deepline session ${subcommand}" as an SDK no-op.`
|
|
19282
|
+
);
|
|
19060
19283
|
}
|
|
19061
|
-
|
|
19062
|
-
|
|
19063
|
-
|
|
19064
|
-
|
|
19065
|
-
|
|
19066
|
-
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
|
|
19071
|
-
|
|
19284
|
+
const backend = program.command("backend").description(
|
|
19285
|
+
"Compatibility no-ops for legacy Python local backend commands."
|
|
19286
|
+
).allowUnknownOption(true).allowExcessArguments(true).option("--json", "Emit JSON output").argument("[args...]").addHelpText(
|
|
19287
|
+
"after",
|
|
19288
|
+
`
|
|
19289
|
+
Notes:
|
|
19290
|
+
The SDK CLI uses the configured Deepline host and V2 play runtime. It does not
|
|
19291
|
+
start, stop, or refresh the legacy Python local playground backend.
|
|
19292
|
+
|
|
19293
|
+
Examples:
|
|
19294
|
+
deepline backend start
|
|
19295
|
+
deepline backend stop --just-backend
|
|
19296
|
+
deepline backend status --json
|
|
19297
|
+
`
|
|
19298
|
+
).action((args, options) => {
|
|
19299
|
+
void args;
|
|
19300
|
+
printLegacyNoop({
|
|
19301
|
+
family: "backend",
|
|
19302
|
+
subcommand: legacySubcommandFromArgv("backend"),
|
|
19303
|
+
options
|
|
19072
19304
|
});
|
|
19305
|
+
});
|
|
19306
|
+
for (const subcommand of BACKEND_SUBCOMMANDS) {
|
|
19307
|
+
addLegacyNoopSubcommand(
|
|
19308
|
+
backend,
|
|
19309
|
+
"backend",
|
|
19310
|
+
subcommand,
|
|
19311
|
+
`Accept legacy "deepline backend ${subcommand}" as an SDK no-op.`
|
|
19312
|
+
);
|
|
19073
19313
|
}
|
|
19074
|
-
if (targets.length === 0) {
|
|
19075
|
-
throw new Error("One of --session-id or --current-session is required.");
|
|
19076
|
-
}
|
|
19077
|
-
return targets;
|
|
19078
19314
|
}
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
return null;
|
|
19084
|
-
}
|
|
19315
|
+
|
|
19316
|
+
// src/cli/commands/org.ts
|
|
19317
|
+
async function fetchOrganizations(http, apiKey) {
|
|
19318
|
+
return http.post("/api/v2/auth/cli/organizations", { api_key: apiKey });
|
|
19085
19319
|
}
|
|
19086
|
-
function
|
|
19087
|
-
return
|
|
19320
|
+
function orgListLines(orgs) {
|
|
19321
|
+
return orgs.map((org, index) => {
|
|
19322
|
+
const current = org.is_current ? " (current)" : "";
|
|
19323
|
+
const role = org.role ? ` [${org.role}]` : "";
|
|
19324
|
+
return `${index + 1}. ${org.name}${role}${current}`;
|
|
19325
|
+
});
|
|
19088
19326
|
}
|
|
19089
|
-
function
|
|
19090
|
-
const
|
|
19091
|
-
|
|
19092
|
-
|
|
19093
|
-
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
|
|
19098
|
-
|
|
19099
|
-
|
|
19327
|
+
async function handleOrgList(options) {
|
|
19328
|
+
const config = resolveConfig();
|
|
19329
|
+
const http = new HttpClient(config);
|
|
19330
|
+
const payload = await fetchOrganizations(http, config.apiKey);
|
|
19331
|
+
printCommandEnvelope(
|
|
19332
|
+
{
|
|
19333
|
+
...payload,
|
|
19334
|
+
render: {
|
|
19335
|
+
sections: [
|
|
19336
|
+
{
|
|
19337
|
+
title: "Your organizations:",
|
|
19338
|
+
lines: orgListLines(payload.organizations)
|
|
19339
|
+
}
|
|
19340
|
+
]
|
|
19341
|
+
}
|
|
19342
|
+
},
|
|
19343
|
+
{ json: options.json }
|
|
19344
|
+
);
|
|
19100
19345
|
}
|
|
19101
|
-
function
|
|
19102
|
-
const
|
|
19103
|
-
|
|
19104
|
-
const
|
|
19105
|
-
if (
|
|
19106
|
-
|
|
19107
|
-
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
|
|
19111
|
-
|
|
19112
|
-
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
|
|
19346
|
+
async function handleOrgSwitch(selection, options) {
|
|
19347
|
+
const config = resolveConfig();
|
|
19348
|
+
const http = new HttpClient(config);
|
|
19349
|
+
const payload = await fetchOrganizations(http, config.apiKey);
|
|
19350
|
+
if (!selection && !options.orgId) {
|
|
19351
|
+
printCommandEnvelope(
|
|
19352
|
+
{
|
|
19353
|
+
...payload,
|
|
19354
|
+
next: { switch: "deepline org switch <number>" },
|
|
19355
|
+
render: {
|
|
19356
|
+
sections: [
|
|
19357
|
+
{
|
|
19358
|
+
title: "Your organizations:",
|
|
19359
|
+
lines: orgListLines(payload.organizations)
|
|
19360
|
+
}
|
|
19361
|
+
],
|
|
19362
|
+
actions: [{ label: "Run", command: "deepline org switch <number>" }]
|
|
19363
|
+
}
|
|
19364
|
+
},
|
|
19365
|
+
{ json: options.json }
|
|
19366
|
+
);
|
|
19367
|
+
return;
|
|
19368
|
+
}
|
|
19369
|
+
let target = payload.organizations.find(
|
|
19370
|
+
(org) => org.org_id === options.orgId
|
|
19371
|
+
);
|
|
19372
|
+
if (!target && selection) {
|
|
19373
|
+
const index = Number.parseInt(selection, 10);
|
|
19374
|
+
if (Number.isFinite(index) && index >= 1 && index <= payload.organizations.length) {
|
|
19375
|
+
target = payload.organizations[index - 1];
|
|
19376
|
+
} else {
|
|
19377
|
+
target = payload.organizations.find(
|
|
19378
|
+
(org) => org.name === selection || org.org_id === selection
|
|
19379
|
+
);
|
|
19116
19380
|
}
|
|
19117
|
-
|
|
19118
|
-
|
|
19381
|
+
}
|
|
19382
|
+
if (!target) {
|
|
19383
|
+
throw new Error("Could not resolve the selected organization.");
|
|
19384
|
+
}
|
|
19385
|
+
if (target.is_current) {
|
|
19386
|
+
printCommandEnvelope(
|
|
19387
|
+
{
|
|
19388
|
+
ok: true,
|
|
19389
|
+
unchanged: true,
|
|
19390
|
+
organization: target,
|
|
19391
|
+
render: {
|
|
19392
|
+
sections: [
|
|
19393
|
+
{ title: "org switch", lines: [`Already on ${target.name}.`] }
|
|
19394
|
+
]
|
|
19395
|
+
}
|
|
19396
|
+
},
|
|
19397
|
+
{ json: options.json }
|
|
19398
|
+
);
|
|
19399
|
+
return;
|
|
19400
|
+
}
|
|
19401
|
+
const switched = await http.post("/api/v2/auth/cli/switch", {
|
|
19402
|
+
api_key: config.apiKey,
|
|
19403
|
+
org_id: target.org_id
|
|
19404
|
+
});
|
|
19405
|
+
saveHostEnvValues(config.baseUrl, {
|
|
19406
|
+
DEEPLINE_API_KEY: switched.api_key,
|
|
19407
|
+
DEEPLINE_ACTIVE_ORG_ID: switched.org_id,
|
|
19408
|
+
DEEPLINE_ACTIVE_ORG_NAME: switched.org_name
|
|
19409
|
+
});
|
|
19410
|
+
const { api_key: _apiKey, ...publicSwitched } = switched;
|
|
19411
|
+
printCommandEnvelope(
|
|
19412
|
+
{
|
|
19413
|
+
ok: true,
|
|
19414
|
+
host_env_path: hostEnvFilePath(config.baseUrl),
|
|
19415
|
+
...publicSwitched,
|
|
19416
|
+
api_key_saved: true,
|
|
19417
|
+
render: {
|
|
19418
|
+
sections: [
|
|
19419
|
+
{
|
|
19420
|
+
title: "org switch",
|
|
19421
|
+
lines: [
|
|
19422
|
+
`Switched to ${switched.org_name}.`,
|
|
19423
|
+
`Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
|
|
19424
|
+
]
|
|
19425
|
+
}
|
|
19426
|
+
]
|
|
19427
|
+
}
|
|
19428
|
+
},
|
|
19429
|
+
{ json: options.json }
|
|
19430
|
+
);
|
|
19119
19431
|
}
|
|
19120
|
-
function
|
|
19121
|
-
const
|
|
19122
|
-
const
|
|
19123
|
-
const
|
|
19124
|
-
|
|
19125
|
-
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19129
|
-
|
|
19130
|
-
|
|
19432
|
+
async function handleOrgCreate(name, options) {
|
|
19433
|
+
const config = resolveConfig();
|
|
19434
|
+
const http = new HttpClient(config);
|
|
19435
|
+
const created = await http.post("/api/v2/auth/cli/org-create", {
|
|
19436
|
+
api_key: config.apiKey,
|
|
19437
|
+
name
|
|
19438
|
+
});
|
|
19439
|
+
saveHostEnvValues(config.baseUrl, {
|
|
19440
|
+
DEEPLINE_API_KEY: created.api_key,
|
|
19441
|
+
DEEPLINE_ACTIVE_ORG_ID: created.org_id,
|
|
19442
|
+
DEEPLINE_ACTIVE_ORG_NAME: created.org_name
|
|
19443
|
+
});
|
|
19444
|
+
const { api_key: _apiKey, ...publicCreated } = created;
|
|
19445
|
+
printCommandEnvelope(
|
|
19446
|
+
{
|
|
19447
|
+
ok: true,
|
|
19448
|
+
...publicCreated,
|
|
19449
|
+
api_key_saved: true,
|
|
19450
|
+
switched: true,
|
|
19451
|
+
host_env_path: hostEnvFilePath(config.baseUrl),
|
|
19452
|
+
render: {
|
|
19453
|
+
sections: [
|
|
19454
|
+
{
|
|
19455
|
+
title: "org create",
|
|
19456
|
+
lines: [
|
|
19457
|
+
`Created organization: ${created.org_name}.`,
|
|
19458
|
+
`Switched to ${created.org_name}.`,
|
|
19459
|
+
`Saved host auth in ${hostEnvFilePath(config.baseUrl)}`
|
|
19460
|
+
]
|
|
19461
|
+
}
|
|
19462
|
+
]
|
|
19463
|
+
}
|
|
19464
|
+
},
|
|
19465
|
+
{ json: options.json }
|
|
19466
|
+
);
|
|
19467
|
+
}
|
|
19468
|
+
function registerOrgCommands(program) {
|
|
19469
|
+
const org = program.command("org").description("List, create, and switch organizations.").addHelpText(
|
|
19470
|
+
"after",
|
|
19471
|
+
`
|
|
19472
|
+
Notes:
|
|
19473
|
+
Organizations are workspaces. Switching organizations mutates the saved host
|
|
19474
|
+
auth file so later CLI commands target the selected workspace.
|
|
19475
|
+
|
|
19476
|
+
Examples:
|
|
19477
|
+
deepline org list --json
|
|
19478
|
+
deepline org create Acme --json
|
|
19479
|
+
deepline org switch 2
|
|
19480
|
+
deepline org switch --org-id org_123 --json
|
|
19481
|
+
`
|
|
19482
|
+
);
|
|
19483
|
+
org.command("list").description("List your organizations.").addHelpText(
|
|
19484
|
+
"after",
|
|
19485
|
+
`
|
|
19486
|
+
Notes:
|
|
19487
|
+
Read-only. Marks the active organization when the server returns that metadata.
|
|
19488
|
+
|
|
19489
|
+
Examples:
|
|
19490
|
+
deepline org list
|
|
19491
|
+
deepline org list --json
|
|
19492
|
+
`
|
|
19493
|
+
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgList);
|
|
19494
|
+
org.command("create <name>").description("Create a new organization and switch this CLI to it.").addHelpText(
|
|
19495
|
+
"after",
|
|
19496
|
+
`
|
|
19497
|
+
Notes:
|
|
19498
|
+
Mutates workspace state. The new organization is created for the current
|
|
19499
|
+
authenticated user, then the returned API key is saved for this host so later
|
|
19500
|
+
CLI commands target the new organization.
|
|
19501
|
+
|
|
19502
|
+
Examples:
|
|
19503
|
+
deepline org create Acme
|
|
19504
|
+
deepline org create "Acme Sales" --json
|
|
19505
|
+
`
|
|
19506
|
+
).option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgCreate);
|
|
19507
|
+
org.command("switch [selection]").description(
|
|
19508
|
+
"Switch to another organization and save the new API key in the host auth file."
|
|
19509
|
+
).addHelpText(
|
|
19510
|
+
"after",
|
|
19511
|
+
`
|
|
19512
|
+
Notes:
|
|
19513
|
+
Mutates the saved host auth file. Selection can be a list number, exact
|
|
19514
|
+
organization name, or organization id. Without a selection, prints choices.
|
|
19515
|
+
|
|
19516
|
+
Examples:
|
|
19517
|
+
deepline org switch
|
|
19518
|
+
deepline org switch 2
|
|
19519
|
+
deepline org switch --org-id org_123 --json
|
|
19520
|
+
`
|
|
19521
|
+
).option("--org-id <id>", "Switch using an explicit organization id").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(handleOrgSwitch);
|
|
19522
|
+
}
|
|
19523
|
+
|
|
19524
|
+
// src/cli/commands/quickstart.ts
|
|
19525
|
+
import { spawn, spawnSync } from "child_process";
|
|
19526
|
+
import { randomBytes } from "crypto";
|
|
19527
|
+
import { createServer } from "http";
|
|
19528
|
+
var EXIT_OK2 = 0;
|
|
19529
|
+
var EXIT_AUTH2 = 1;
|
|
19530
|
+
var EXIT_SERVER3 = 2;
|
|
19531
|
+
var MAX_PROMPT_LENGTH = 8e3;
|
|
19532
|
+
var MAX_BODY_BYTES = 64 * 1024;
|
|
19533
|
+
function shellQuote(arg) {
|
|
19534
|
+
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
19535
|
+
}
|
|
19536
|
+
function hasClaudeBinary() {
|
|
19537
|
+
try {
|
|
19538
|
+
const result = spawnSync("claude", ["--version"], {
|
|
19539
|
+
stdio: "ignore",
|
|
19540
|
+
shell: process.platform === "win32"
|
|
19541
|
+
});
|
|
19542
|
+
return result.status === 0;
|
|
19543
|
+
} catch {
|
|
19544
|
+
return false;
|
|
19545
|
+
}
|
|
19546
|
+
}
|
|
19547
|
+
function launchClaude(prompt) {
|
|
19548
|
+
return new Promise((resolve16) => {
|
|
19549
|
+
const child = spawn("claude", [prompt], {
|
|
19550
|
+
stdio: "inherit",
|
|
19551
|
+
shell: process.platform === "win32"
|
|
19552
|
+
});
|
|
19553
|
+
child.on("error", () => resolve16(EXIT_SERVER3));
|
|
19554
|
+
child.on("close", (status) => resolve16(status ?? EXIT_OK2));
|
|
19555
|
+
});
|
|
19556
|
+
}
|
|
19557
|
+
function readBody(req) {
|
|
19558
|
+
return new Promise((resolve16, reject) => {
|
|
19559
|
+
let raw = "";
|
|
19560
|
+
req.setEncoding("utf8");
|
|
19561
|
+
req.on("data", (chunk) => {
|
|
19562
|
+
raw += chunk;
|
|
19563
|
+
if (raw.length > MAX_BODY_BYTES) {
|
|
19564
|
+
reject(new Error("Request body too large."));
|
|
19565
|
+
req.destroy();
|
|
19566
|
+
}
|
|
19567
|
+
});
|
|
19568
|
+
req.on("end", () => resolve16(raw));
|
|
19569
|
+
req.on("error", reject);
|
|
19570
|
+
});
|
|
19571
|
+
}
|
|
19572
|
+
function writeJson(res, status, payload) {
|
|
19573
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
19574
|
+
res.end(JSON.stringify(payload));
|
|
19575
|
+
}
|
|
19576
|
+
function startCallbackServer(input2) {
|
|
19577
|
+
const server = createServer((req, res) => {
|
|
19578
|
+
res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*");
|
|
19579
|
+
res.setHeader("Vary", "Origin");
|
|
19580
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
19581
|
+
res.setHeader("Access-Control-Allow-Headers", "content-type");
|
|
19582
|
+
res.setHeader("Access-Control-Allow-Private-Network", "true");
|
|
19583
|
+
res.setHeader("Access-Control-Max-Age", "600");
|
|
19584
|
+
if (req.method === "OPTIONS") {
|
|
19585
|
+
res.writeHead(204);
|
|
19586
|
+
res.end();
|
|
19587
|
+
return;
|
|
19131
19588
|
}
|
|
19132
|
-
|
|
19133
|
-
|
|
19134
|
-
|
|
19135
|
-
if (!["user", "assistant"].includes(eventType) || !eventKey) {
|
|
19136
|
-
output2.push(rawLines[index] ?? "");
|
|
19137
|
-
index += 1;
|
|
19138
|
-
continue;
|
|
19589
|
+
if (req.method !== "POST" || req.url !== "/submit") {
|
|
19590
|
+
writeJson(res, 404, { error: "Not found." });
|
|
19591
|
+
return;
|
|
19139
19592
|
}
|
|
19140
|
-
|
|
19141
|
-
|
|
19142
|
-
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19593
|
+
void readBody(req).then((raw) => {
|
|
19594
|
+
let body = null;
|
|
19595
|
+
try {
|
|
19596
|
+
body = JSON.parse(raw);
|
|
19597
|
+
} catch {
|
|
19598
|
+
body = null;
|
|
19599
|
+
}
|
|
19600
|
+
const state = typeof body?.state === "string" ? body.state : "";
|
|
19601
|
+
const prompt = typeof body?.prompt === "string" ? body.prompt.trim() : "";
|
|
19602
|
+
const workflowId = typeof body?.workflow_id === "string" && body.workflow_id.trim() ? body.workflow_id.trim() : null;
|
|
19603
|
+
if (!state || state !== input2.state) {
|
|
19604
|
+
writeJson(res, 403, { error: "Invalid quickstart state token." });
|
|
19605
|
+
return;
|
|
19606
|
+
}
|
|
19607
|
+
if (!prompt) {
|
|
19608
|
+
writeJson(res, 400, { error: "prompt is required." });
|
|
19609
|
+
return;
|
|
19610
|
+
}
|
|
19611
|
+
if (prompt.length > MAX_PROMPT_LENGTH) {
|
|
19612
|
+
writeJson(res, 400, {
|
|
19613
|
+
error: `prompt exceeds ${MAX_PROMPT_LENGTH} characters.`
|
|
19614
|
+
});
|
|
19615
|
+
return;
|
|
19616
|
+
}
|
|
19617
|
+
writeJson(res, 200, { ok: true });
|
|
19618
|
+
setImmediate(() => input2.onSelection({ prompt, workflowId }));
|
|
19619
|
+
}).catch(() => {
|
|
19620
|
+
writeJson(res, 400, { error: "Invalid request body." });
|
|
19621
|
+
});
|
|
19622
|
+
});
|
|
19623
|
+
return new Promise((resolve16, reject) => {
|
|
19624
|
+
server.once("error", reject);
|
|
19625
|
+
server.listen(0, "127.0.0.1", () => {
|
|
19626
|
+
const address = server.address();
|
|
19627
|
+
if (!address || typeof address === "string") {
|
|
19628
|
+
reject(new Error("Failed to bind quickstart callback server."));
|
|
19629
|
+
return;
|
|
19148
19630
|
}
|
|
19149
|
-
|
|
19150
|
-
|
|
19631
|
+
resolve16({ server, port: address.port });
|
|
19632
|
+
});
|
|
19633
|
+
});
|
|
19634
|
+
}
|
|
19635
|
+
async function handleQuickstart(options) {
|
|
19636
|
+
const jsonOutput = shouldEmitJson(options.json === true);
|
|
19637
|
+
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
19638
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
19639
|
+
let apiKey = resolveApiKeyForBaseUrl(baseUrl);
|
|
19640
|
+
if (!apiKey) {
|
|
19641
|
+
if (interactive) {
|
|
19642
|
+
console.error("Not connected yet \u2014 registering this device first.");
|
|
19643
|
+
const registerExit = await handleRegister([]);
|
|
19644
|
+
if (registerExit !== EXIT_OK2) return registerExit;
|
|
19645
|
+
apiKey = resolveApiKeyForBaseUrl(baseUrl);
|
|
19151
19646
|
}
|
|
19152
|
-
if (
|
|
19153
|
-
|
|
19154
|
-
|
|
19155
|
-
output2.push(JSON.stringify(record));
|
|
19156
|
-
index = cursor;
|
|
19157
|
-
continue;
|
|
19647
|
+
if (!apiKey) {
|
|
19648
|
+
console.error("Not connected. Run: deepline auth register");
|
|
19649
|
+
return EXIT_AUTH2;
|
|
19158
19650
|
}
|
|
19159
|
-
output2.push(rawLines[index] ?? "");
|
|
19160
|
-
index += 1;
|
|
19161
19651
|
}
|
|
19162
|
-
|
|
19163
|
-
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19167
|
-
|
|
19168
|
-
|
|
19652
|
+
const state = randomBytes(32).toString("hex");
|
|
19653
|
+
let resolveSelection;
|
|
19654
|
+
const selectionPromise = new Promise((resolve16) => {
|
|
19655
|
+
resolveSelection = resolve16;
|
|
19656
|
+
});
|
|
19657
|
+
let callback;
|
|
19658
|
+
try {
|
|
19659
|
+
callback = await startCallbackServer({
|
|
19660
|
+
state,
|
|
19661
|
+
onSelection: (selection) => resolveSelection(selection)
|
|
19662
|
+
});
|
|
19663
|
+
} catch (error) {
|
|
19664
|
+
console.error(
|
|
19665
|
+
`Could not start the local quickstart listener: ${error instanceof Error ? error.message : String(error)}`
|
|
19666
|
+
);
|
|
19667
|
+
return EXIT_SERVER3;
|
|
19169
19668
|
}
|
|
19170
|
-
|
|
19171
|
-
|
|
19172
|
-
|
|
19173
|
-
|
|
19174
|
-
|
|
19175
|
-
);
|
|
19176
|
-
}
|
|
19177
|
-
return compacted;
|
|
19669
|
+
const quickstartUrl = `${baseUrl}/cli/quickstart?cb=${callback.port}&state=${state}`;
|
|
19670
|
+
console.error(" Opening the recipe picker in your browser.");
|
|
19671
|
+
console.error(` If it didn't open, cmd+click: ${quickstartUrl}`);
|
|
19672
|
+
if (options.open !== false) {
|
|
19673
|
+
openInBrowser(quickstartUrl);
|
|
19178
19674
|
}
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19675
|
+
const timeoutSeconds = Number.parseInt(options.timeout ?? "300", 10);
|
|
19676
|
+
const timeoutMs = (Number.isFinite(timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 300) * 1e3;
|
|
19677
|
+
const timeoutHandle = setTimeout(
|
|
19678
|
+
() => resolveSelection("timeout"),
|
|
19679
|
+
timeoutMs
|
|
19680
|
+
);
|
|
19681
|
+
const progress = createCliProgress(!jsonOutput);
|
|
19682
|
+
progress.phase("waiting for your pick in the browser (Ctrl+C to skip)");
|
|
19683
|
+
const onSigint = () => resolveSelection("sigint");
|
|
19684
|
+
process.once("SIGINT", onSigint);
|
|
19685
|
+
const outcome = await selectionPromise;
|
|
19686
|
+
clearTimeout(timeoutHandle);
|
|
19687
|
+
process.removeListener("SIGINT", onSigint);
|
|
19688
|
+
callback.server.close();
|
|
19689
|
+
if (outcome === "sigint") {
|
|
19690
|
+
progress.fail();
|
|
19691
|
+
console.error("\nSkipped \u2014 run `deepline quickstart` anytime.");
|
|
19692
|
+
return EXIT_OK2;
|
|
19189
19693
|
}
|
|
19190
|
-
|
|
19694
|
+
if (outcome === "timeout") {
|
|
19695
|
+
progress.fail();
|
|
19696
|
+
console.error(
|
|
19697
|
+
"Timed out waiting for a selection. Run: deepline quickstart"
|
|
19698
|
+
);
|
|
19699
|
+
return EXIT_AUTH2;
|
|
19700
|
+
}
|
|
19701
|
+
progress.complete();
|
|
19702
|
+
const { prompt, workflowId } = outcome;
|
|
19703
|
+
const claudeCommand = `claude ${shellQuote(prompt)}`;
|
|
19704
|
+
const claudeAvailable = hasClaudeBinary();
|
|
19705
|
+
const willLaunch = options.launch !== false && !jsonOutput && interactive && claudeAvailable;
|
|
19706
|
+
printCommandEnvelope(
|
|
19707
|
+
{
|
|
19708
|
+
status: "submitted",
|
|
19709
|
+
workflowId,
|
|
19710
|
+
prompt,
|
|
19711
|
+
claudeCommand,
|
|
19712
|
+
url: quickstartUrl,
|
|
19713
|
+
launched: willLaunch,
|
|
19714
|
+
render: {
|
|
19715
|
+
sections: [
|
|
19716
|
+
{
|
|
19717
|
+
title: "quickstart",
|
|
19718
|
+
lines: [
|
|
19719
|
+
...workflowId ? [`Recipe: ${workflowId}`] : [],
|
|
19720
|
+
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."
|
|
19721
|
+
]
|
|
19722
|
+
}
|
|
19723
|
+
],
|
|
19724
|
+
actions: [{ label: "Run", command: claudeCommand }]
|
|
19725
|
+
}
|
|
19726
|
+
},
|
|
19727
|
+
{ json: jsonOutput }
|
|
19728
|
+
);
|
|
19729
|
+
if (willLaunch) {
|
|
19730
|
+
return launchClaude(prompt);
|
|
19731
|
+
}
|
|
19732
|
+
return EXIT_OK2;
|
|
19191
19733
|
}
|
|
19192
|
-
function
|
|
19193
|
-
|
|
19194
|
-
|
|
19195
|
-
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
|
|
19203
|
-
|
|
19204
|
-
|
|
19205
|
-
|
|
19206
|
-
|
|
19207
|
-
|
|
19734
|
+
function registerQuickstartCommands(program) {
|
|
19735
|
+
program.command("quickstart").description(
|
|
19736
|
+
"Pick a starter recipe in the browser and launch it with Claude Code."
|
|
19737
|
+
).addHelpText(
|
|
19738
|
+
"after",
|
|
19739
|
+
`
|
|
19740
|
+
Notes:
|
|
19741
|
+
Opens the hosted recipe picker in your browser. The picker sends your
|
|
19742
|
+
selection back to a temporary listener on 127.0.0.1, so the browser must run
|
|
19743
|
+
on the same machine as the CLI. Once a recipe arrives, the CLI prints the
|
|
19744
|
+
matching claude command and, when Claude Code is installed and the shell is
|
|
19745
|
+
interactive, launches it directly. Press Ctrl+C while waiting to skip.
|
|
19746
|
+
|
|
19747
|
+
Examples:
|
|
19748
|
+
deepline quickstart
|
|
19749
|
+
deepline quickstart --no-open
|
|
19750
|
+
deepline quickstart --no-launch --json
|
|
19751
|
+
deepline quickstart --timeout 120
|
|
19752
|
+
`
|
|
19753
|
+
).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(
|
|
19754
|
+
"--timeout <seconds>",
|
|
19755
|
+
"Maximum seconds to wait for a selection",
|
|
19756
|
+
"300"
|
|
19757
|
+
).action(async (options) => {
|
|
19758
|
+
process.exitCode = await handleQuickstart(options);
|
|
19759
|
+
});
|
|
19760
|
+
}
|
|
19761
|
+
|
|
19762
|
+
// src/cli/commands/secrets.ts
|
|
19763
|
+
import { stdin as input, stdout as output } from "process";
|
|
19764
|
+
var hiddenInputBuffer = "";
|
|
19765
|
+
function normalizeSecretName(value) {
|
|
19766
|
+
const normalized = value.trim().toUpperCase();
|
|
19767
|
+
if (!/^[A-Z][A-Z0-9_]{1,63}$/.test(normalized)) {
|
|
19768
|
+
throw new Error(
|
|
19769
|
+
"Secret names must be 2-64 characters and use uppercase letters, numbers, and underscores."
|
|
19770
|
+
);
|
|
19771
|
+
}
|
|
19772
|
+
return normalized;
|
|
19773
|
+
}
|
|
19774
|
+
function renderSecret(secret) {
|
|
19775
|
+
const scope = secret.scope === "play" && secret.playName ? `play:${secret.playName}` : secret.scope;
|
|
19776
|
+
return `${secret.name} (${scope}) - ${secret.status}${secret.hasValue ? ", set" : ", empty"}`;
|
|
19777
|
+
}
|
|
19778
|
+
async function readHiddenLine(prompt) {
|
|
19779
|
+
if (!input.isTTY || !output.isTTY) {
|
|
19780
|
+
throw new Error(
|
|
19781
|
+
"Secret values must be entered from an interactive TTY. Do not pipe, pass, or script secret values."
|
|
19782
|
+
);
|
|
19783
|
+
}
|
|
19784
|
+
output.write(prompt);
|
|
19785
|
+
const previousRawMode = input.isRaw;
|
|
19786
|
+
if (typeof input.setRawMode === "function") input.setRawMode(true);
|
|
19787
|
+
let value = "";
|
|
19788
|
+
input.resume();
|
|
19789
|
+
return await new Promise((resolve16, reject) => {
|
|
19790
|
+
let settled = false;
|
|
19791
|
+
const cleanup = () => {
|
|
19792
|
+
input.off("data", onData);
|
|
19793
|
+
input.off("end", onEnd);
|
|
19794
|
+
input.off("error", onError);
|
|
19795
|
+
if (typeof input.setRawMode === "function") {
|
|
19796
|
+
input.setRawMode(previousRawMode);
|
|
19797
|
+
}
|
|
19798
|
+
};
|
|
19799
|
+
const finish = (line) => {
|
|
19800
|
+
if (settled) return;
|
|
19801
|
+
settled = true;
|
|
19802
|
+
output.write("\n");
|
|
19803
|
+
cleanup();
|
|
19804
|
+
resolve16(line);
|
|
19805
|
+
};
|
|
19806
|
+
const fail = (error) => {
|
|
19807
|
+
if (settled) return;
|
|
19808
|
+
settled = true;
|
|
19809
|
+
output.write("\n");
|
|
19810
|
+
cleanup();
|
|
19811
|
+
reject(error);
|
|
19812
|
+
};
|
|
19813
|
+
const processText = (text) => {
|
|
19814
|
+
for (let index = 0; index < text.length; index++) {
|
|
19815
|
+
const char = text[index];
|
|
19816
|
+
const code = char.charCodeAt(0);
|
|
19817
|
+
if (char === "\r" || char === "\n") {
|
|
19818
|
+
hiddenInputBuffer = text.slice(index + 1);
|
|
19819
|
+
finish(value);
|
|
19820
|
+
return;
|
|
19821
|
+
}
|
|
19822
|
+
if (code === 3) {
|
|
19823
|
+
fail(new Error("Secret input cancelled."));
|
|
19824
|
+
return;
|
|
19825
|
+
}
|
|
19826
|
+
if (code === 8 || code === 127) {
|
|
19827
|
+
value = value.slice(0, -1);
|
|
19828
|
+
continue;
|
|
19829
|
+
}
|
|
19830
|
+
if (code >= 32) {
|
|
19831
|
+
value += char;
|
|
19832
|
+
}
|
|
19208
19833
|
}
|
|
19834
|
+
hiddenInputBuffer = "";
|
|
19835
|
+
};
|
|
19836
|
+
const onData = (chunk) => {
|
|
19837
|
+
processText(chunk.toString());
|
|
19838
|
+
};
|
|
19839
|
+
const onEnd = () => fail(new Error("Secret input ended before a value was entered."));
|
|
19840
|
+
const onError = (error) => fail(error);
|
|
19841
|
+
input.on("data", onData);
|
|
19842
|
+
input.once("end", onEnd);
|
|
19843
|
+
input.once("error", onError);
|
|
19844
|
+
if (hiddenInputBuffer) {
|
|
19845
|
+
const buffered = hiddenInputBuffer;
|
|
19846
|
+
hiddenInputBuffer = "";
|
|
19847
|
+
processText(buffered);
|
|
19209
19848
|
}
|
|
19210
|
-
|
|
19211
|
-
}
|
|
19212
|
-
return Buffer.from(lines.length > 0 ? `${lines.join("\n")}
|
|
19213
|
-
` : "", "utf8");
|
|
19214
|
-
}
|
|
19215
|
-
function prepareSessionBuffer(raw) {
|
|
19216
|
-
return selectiveCompactToolResults(
|
|
19217
|
-
dedupConsecutiveEvents(stripNoiseEvents(raw))
|
|
19218
|
-
);
|
|
19849
|
+
});
|
|
19219
19850
|
}
|
|
19220
|
-
function
|
|
19221
|
-
const
|
|
19222
|
-
|
|
19223
|
-
|
|
19224
|
-
|
|
19851
|
+
async function readSecretValue() {
|
|
19852
|
+
const first = await readHiddenLine("Secret value: ");
|
|
19853
|
+
if (!first) throw new Error("Secret value is required.");
|
|
19854
|
+
const second = await readHiddenLine("Confirm secret value: ");
|
|
19855
|
+
if (first !== second) {
|
|
19856
|
+
throw new Error("Secret values did not match.");
|
|
19225
19857
|
}
|
|
19226
|
-
return
|
|
19227
|
-
}
|
|
19228
|
-
async function uploadPayload(path, payload) {
|
|
19229
|
-
const { http } = getAuthedHttpClient();
|
|
19230
|
-
return await http.post(path, payload);
|
|
19858
|
+
return first;
|
|
19231
19859
|
}
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
const chunks = [];
|
|
19237
|
-
for (let offset = 0; offset < bytes.length; offset += CHUNK_SIZE_BYTES) {
|
|
19238
|
-
chunks.push(bytes.subarray(offset, offset + CHUNK_SIZE_BYTES));
|
|
19239
|
-
}
|
|
19240
|
-
process.stderr.write(
|
|
19241
|
-
`Uploading ${session.label} in ${chunks.length} chunk(s)...
|
|
19242
|
-
`
|
|
19860
|
+
function preventShellHistoryLeak(forbidden) {
|
|
19861
|
+
if (forbidden.length > 0) {
|
|
19862
|
+
throw new Error(
|
|
19863
|
+
"Do not pass secret values as command arguments. Run `deepline secrets set NAME` and enter the value at the hidden prompt."
|
|
19243
19864
|
);
|
|
19244
|
-
for (const [index, chunk] of chunks.entries()) {
|
|
19245
|
-
await uploadPayload("/api/v2/cli/send-session/chunk", {
|
|
19246
|
-
upload_id: uploadId,
|
|
19247
|
-
session_id: session.sessionId,
|
|
19248
|
-
index,
|
|
19249
|
-
total_chunks: chunks.length,
|
|
19250
|
-
data: chunk.toString("base64")
|
|
19251
|
-
});
|
|
19252
|
-
}
|
|
19253
19865
|
}
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
environments: sessions.map(() => detectShellContext())
|
|
19259
|
-
});
|
|
19866
|
+
}
|
|
19867
|
+
async function handleList(options) {
|
|
19868
|
+
const client2 = new DeeplineClient();
|
|
19869
|
+
const secrets = await client2.listSecrets();
|
|
19260
19870
|
printCommandEnvelope(
|
|
19261
19871
|
{
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
uploaded: sessions.length,
|
|
19872
|
+
secrets,
|
|
19873
|
+
count: secrets.length,
|
|
19265
19874
|
render: {
|
|
19266
19875
|
sections: [
|
|
19267
19876
|
{
|
|
19268
|
-
title: "
|
|
19269
|
-
lines: ["
|
|
19877
|
+
title: "secrets",
|
|
19878
|
+
lines: secrets.length ? secrets.map(renderSecret) : ["No play secrets are configured."]
|
|
19270
19879
|
}
|
|
19271
19880
|
]
|
|
19272
19881
|
}
|
|
@@ -19274,247 +19883,266 @@ async function uploadChunkedSessions(sessions, options) {
|
|
|
19274
19883
|
{ json: options.json }
|
|
19275
19884
|
);
|
|
19276
19885
|
}
|
|
19277
|
-
async function
|
|
19278
|
-
|
|
19279
|
-
|
|
19280
|
-
|
|
19281
|
-
throw new Error(`File not found: ${options.file}`);
|
|
19282
|
-
}
|
|
19283
|
-
const response2 = await uploadPayload("/api/v2/cli/send-session", {
|
|
19284
|
-
file: readFileSync7(filePath).toString("base64"),
|
|
19285
|
-
filename: basename4(filePath)
|
|
19286
|
-
});
|
|
19287
|
-
printCommandEnvelope(
|
|
19288
|
-
{
|
|
19289
|
-
...response2,
|
|
19290
|
-
ok: true,
|
|
19291
|
-
filename: basename4(filePath),
|
|
19292
|
-
render: {
|
|
19293
|
-
sections: [
|
|
19294
|
-
{
|
|
19295
|
-
title: "sessions send",
|
|
19296
|
-
lines: [
|
|
19297
|
-
`File '${basename4(filePath)}' uploaded to #internal-reports.`
|
|
19298
|
-
]
|
|
19299
|
-
}
|
|
19300
|
-
]
|
|
19301
|
-
}
|
|
19302
|
-
},
|
|
19303
|
-
{ json: options.json }
|
|
19304
|
-
);
|
|
19305
|
-
return;
|
|
19306
|
-
}
|
|
19307
|
-
const targets = resolveSessionTargets({
|
|
19308
|
-
sessionIds: options.sessionId,
|
|
19309
|
-
labels: options.label,
|
|
19310
|
-
currentSession: options.currentSession
|
|
19311
|
-
});
|
|
19312
|
-
const built = targets.map((target) => {
|
|
19313
|
-
const upload = buildSessionUploadContent(readFileSync7(target.filePath));
|
|
19314
|
-
return { ...target, ...upload };
|
|
19315
|
-
});
|
|
19316
|
-
if (built.some((session) => session.needsChunking)) {
|
|
19317
|
-
await uploadChunkedSessions(built, options);
|
|
19318
|
-
return;
|
|
19319
|
-
}
|
|
19320
|
-
const response = built.length === 1 && !options.label?.length ? await uploadPayload("/api/v2/cli/send-session", {
|
|
19321
|
-
session_id: built[0]?.sessionId,
|
|
19322
|
-
content: built[0]?.encodedContent,
|
|
19323
|
-
environment: detectShellContext()
|
|
19324
|
-
}) : await uploadPayload("/api/v2/cli/send-session", {
|
|
19325
|
-
sessions: built.map((session) => ({
|
|
19326
|
-
session_id: session.sessionId,
|
|
19327
|
-
content: session.encodedContent,
|
|
19328
|
-
label: session.label,
|
|
19329
|
-
environment: detectShellContext()
|
|
19330
|
-
})),
|
|
19331
|
-
environment: detectShellContext()
|
|
19332
|
-
});
|
|
19886
|
+
async function handleCheck(nameInput, options) {
|
|
19887
|
+
const name = normalizeSecretName(nameInput);
|
|
19888
|
+
const client2 = new DeeplineClient();
|
|
19889
|
+
const secret = await client2.checkSecret(name);
|
|
19333
19890
|
printCommandEnvelope(
|
|
19334
19891
|
{
|
|
19335
|
-
|
|
19336
|
-
|
|
19337
|
-
|
|
19892
|
+
ok: Boolean(secret),
|
|
19893
|
+
name,
|
|
19894
|
+
secret: secret ?? null,
|
|
19338
19895
|
render: {
|
|
19339
19896
|
sections: [
|
|
19340
19897
|
{
|
|
19341
|
-
title: "
|
|
19342
|
-
lines: [
|
|
19898
|
+
title: "secret check",
|
|
19899
|
+
lines: [
|
|
19900
|
+
secret ? `${name}: active` : `${name}: missing, disabled, or empty`
|
|
19901
|
+
]
|
|
19343
19902
|
}
|
|
19344
19903
|
]
|
|
19345
19904
|
}
|
|
19346
19905
|
},
|
|
19347
19906
|
{ json: options.json }
|
|
19348
19907
|
);
|
|
19908
|
+
if (!secret) process.exitCode = 4;
|
|
19349
19909
|
}
|
|
19350
|
-
function
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
|
|
19356
|
-
|
|
19357
|
-
].join(""),
|
|
19358
|
-
js: [
|
|
19359
|
-
"(() => {",
|
|
19360
|
-
'const root=document.getElementById("main-content");',
|
|
19361
|
-
'const raw=document.getElementById("raw-sessions");',
|
|
19362
|
-
"if(!root||!raw)return;",
|
|
19363
|
-
'let sessions=[];try{sessions=JSON.parse(raw.textContent||"[]")}catch{}',
|
|
19364
|
-
'root.innerHTML="";',
|
|
19365
|
-
"for(const session of sessions){",
|
|
19366
|
-
'const section=document.createElement("section");section.className="section";',
|
|
19367
|
-
'const title=document.createElement("h2");title.textContent=String(session.label||"session");',
|
|
19368
|
-
'const pre=document.createElement("pre");',
|
|
19369
|
-
'pre.textContent=(Array.isArray(session.events)?session.events:[]).map((event)=>JSON.stringify(event)).join("\\n");',
|
|
19370
|
-
"section.append(title,pre);root.appendChild(section);",
|
|
19371
|
-
"}",
|
|
19372
|
-
"})();"
|
|
19373
|
-
].join("")
|
|
19374
|
-
};
|
|
19375
|
-
}
|
|
19376
|
-
function parsePreparedEvents(buffer) {
|
|
19377
|
-
return normalizedJsonLines(buffer).map((line) => {
|
|
19378
|
-
const parsed = parseJsonLine(line);
|
|
19379
|
-
return parsed ?? line;
|
|
19380
|
-
});
|
|
19381
|
-
}
|
|
19382
|
-
async function handleSessionsRender(options) {
|
|
19383
|
-
const targets = resolveSessionTargets({
|
|
19384
|
-
sessionIds: options.sessionId,
|
|
19385
|
-
labels: options.label,
|
|
19386
|
-
currentSession: options.currentSession
|
|
19387
|
-
});
|
|
19388
|
-
let outputPath = options.output ? resolve12(options.output) : "";
|
|
19389
|
-
if (!outputPath) {
|
|
19390
|
-
const outputDir = join9(process.cwd(), "deepline", "data");
|
|
19391
|
-
mkdirSync4(outputDir, { recursive: true });
|
|
19392
|
-
outputPath = join9(
|
|
19393
|
-
outputDir,
|
|
19394
|
-
targets.length > 1 ? "session-viewer.html" : `session-${targets[0]?.sessionId}.html`
|
|
19395
|
-
);
|
|
19396
|
-
} else {
|
|
19397
|
-
mkdirSync4(dirname9(outputPath), { recursive: true });
|
|
19910
|
+
async function handleSet(nameInput, forbidden, options) {
|
|
19911
|
+
preventShellHistoryLeak(forbidden);
|
|
19912
|
+
const name = normalizeSecretName(nameInput);
|
|
19913
|
+
const scope = options.scope === "play" ? "play" : "org";
|
|
19914
|
+
const playName = options.play?.trim();
|
|
19915
|
+
if (scope === "play" && !playName) {
|
|
19916
|
+
throw new Error("--play <name> is required when --scope play is used.");
|
|
19398
19917
|
}
|
|
19399
|
-
const
|
|
19400
|
-
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
|
|
19408
|
-
|
|
19409
|
-
|
|
19410
|
-
|
|
19411
|
-
<meta charset="UTF-8">
|
|
19412
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19413
|
-
${refreshMeta}
|
|
19414
|
-
<title>Session Viewer</title>
|
|
19415
|
-
<style>${css}</style>
|
|
19416
|
-
</head>
|
|
19417
|
-
<body>
|
|
19418
|
-
<div class="layout">
|
|
19419
|
-
<div class="main" id="main-content"></div>
|
|
19420
|
-
</div>
|
|
19421
|
-
<script type="application/json" id="raw-sessions">${rawJson}</script>
|
|
19422
|
-
<script>${js}</script>
|
|
19423
|
-
</body>
|
|
19424
|
-
</html>`;
|
|
19425
|
-
writeFileSync8(outputPath, html, "utf8");
|
|
19918
|
+
const value = await readSecretValue();
|
|
19919
|
+
const { http } = getAuthedHttpClient();
|
|
19920
|
+
const response = await http.post(
|
|
19921
|
+
"/api/v2/secrets",
|
|
19922
|
+
{
|
|
19923
|
+
name,
|
|
19924
|
+
value,
|
|
19925
|
+
scope,
|
|
19926
|
+
...playName ? { playName } : {}
|
|
19927
|
+
}
|
|
19928
|
+
);
|
|
19929
|
+
const secret = response.secret;
|
|
19426
19930
|
printCommandEnvelope(
|
|
19427
19931
|
{
|
|
19428
19932
|
ok: true,
|
|
19429
|
-
|
|
19430
|
-
session_count: targets.length,
|
|
19933
|
+
secret,
|
|
19431
19934
|
render: {
|
|
19432
19935
|
sections: [
|
|
19433
19936
|
{
|
|
19434
|
-
title: "
|
|
19435
|
-
lines: [
|
|
19937
|
+
title: "secret saved",
|
|
19938
|
+
lines: [`${secret.name}: saved (${secret.scope})`]
|
|
19436
19939
|
}
|
|
19437
19940
|
]
|
|
19438
19941
|
}
|
|
19439
|
-
},
|
|
19440
|
-
{ json: options.json }
|
|
19441
|
-
);
|
|
19442
|
-
}
|
|
19443
|
-
function collectOption(value, previous) {
|
|
19444
|
-
previous.push(value);
|
|
19445
|
-
return previous;
|
|
19942
|
+
},
|
|
19943
|
+
{ json: options.json }
|
|
19944
|
+
);
|
|
19446
19945
|
}
|
|
19447
|
-
function
|
|
19448
|
-
const
|
|
19946
|
+
function registerSecretsCommands(program) {
|
|
19947
|
+
const secrets = program.command("secrets").description("Manage play secrets without revealing values.").addHelpText(
|
|
19449
19948
|
"after",
|
|
19450
19949
|
`
|
|
19451
19950
|
Notes:
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19951
|
+
Secret values are never accepted as command arguments, stdin pipes, env vars,
|
|
19952
|
+
or files. Use deepline secrets set NAME and type the value at the hidden TTY
|
|
19953
|
+
prompt. Agents can list/check metadata but should not enter secret values.
|
|
19455
19954
|
|
|
19456
19955
|
Examples:
|
|
19457
|
-
deepline
|
|
19458
|
-
deepline
|
|
19459
|
-
deepline
|
|
19956
|
+
deepline secrets list
|
|
19957
|
+
deepline secrets check HUBSPOT_TOKEN
|
|
19958
|
+
deepline secrets set HUBSPOT_TOKEN
|
|
19460
19959
|
`
|
|
19461
19960
|
);
|
|
19462
|
-
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
|
|
19466
|
-
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19477
|
-
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
19961
|
+
secrets.command("list").description("List secret metadata only.").option("--json", "Emit JSON output").action(async (options) => {
|
|
19962
|
+
await handleList(options);
|
|
19963
|
+
});
|
|
19964
|
+
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) => {
|
|
19965
|
+
await handleCheck(name, options);
|
|
19966
|
+
});
|
|
19967
|
+
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(
|
|
19968
|
+
async (name, forbidden, options) => {
|
|
19969
|
+
await handleSet(name, forbidden ?? [], options);
|
|
19970
|
+
}
|
|
19971
|
+
);
|
|
19972
|
+
}
|
|
19973
|
+
|
|
19974
|
+
// src/cli/commands/switch.ts
|
|
19975
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
19976
|
+
import { homedir as homedir6 } from "os";
|
|
19977
|
+
import { dirname as dirname10, join as join10 } from "path";
|
|
19978
|
+
function hostSlugFromBaseUrl(baseUrl) {
|
|
19979
|
+
try {
|
|
19980
|
+
const url = new URL(baseUrl);
|
|
19981
|
+
const port = url.port ? Number.parseInt(url.port, 10) : null;
|
|
19982
|
+
let slug = (url.hostname || "unknown").replace(/[^a-zA-Z0-9]/g, "-");
|
|
19983
|
+
if (port && port !== 80 && port !== 443) {
|
|
19984
|
+
slug = `${slug}-${port}`;
|
|
19985
|
+
}
|
|
19986
|
+
return slug.toLowerCase().replace(/^-+|-+$/g, "") || "unknown";
|
|
19987
|
+
} catch {
|
|
19988
|
+
return "unknown";
|
|
19989
|
+
}
|
|
19990
|
+
}
|
|
19991
|
+
function resolveConfigScope() {
|
|
19992
|
+
const explicit = (process.env.DEEPLINE_CONFIG_SCOPE || "").trim();
|
|
19993
|
+
if (explicit) return explicit;
|
|
19994
|
+
return hostSlugFromBaseUrl(autoDetectBaseUrl());
|
|
19995
|
+
}
|
|
19996
|
+
function activeFamilyPath() {
|
|
19997
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir6();
|
|
19998
|
+
return join10(
|
|
19999
|
+
home,
|
|
20000
|
+
".local",
|
|
20001
|
+
"deepline",
|
|
20002
|
+
resolveConfigScope(),
|
|
20003
|
+
"cli",
|
|
20004
|
+
".active-family"
|
|
20005
|
+
);
|
|
20006
|
+
}
|
|
20007
|
+
function readActiveFamily() {
|
|
20008
|
+
const path = activeFamilyPath();
|
|
20009
|
+
try {
|
|
20010
|
+
return readFileSync8(path, "utf-8").trim() || "sdk";
|
|
20011
|
+
} catch {
|
|
20012
|
+
return "sdk";
|
|
20013
|
+
}
|
|
20014
|
+
}
|
|
20015
|
+
function writeActiveFamily(family) {
|
|
20016
|
+
const path = activeFamilyPath();
|
|
20017
|
+
mkdirSync5(dirname10(path), { recursive: true });
|
|
20018
|
+
writeFileSync9(path, `${family}
|
|
20019
|
+
`, "utf-8");
|
|
20020
|
+
return path;
|
|
20021
|
+
}
|
|
20022
|
+
function handleSwitch(action, options) {
|
|
20023
|
+
const normalized = (action || "status").trim().toLowerCase();
|
|
20024
|
+
if (normalized === "status") {
|
|
20025
|
+
const path = activeFamilyPath();
|
|
20026
|
+
const activeFamily = readActiveFamily();
|
|
20027
|
+
printCommandEnvelope(
|
|
20028
|
+
{
|
|
20029
|
+
ok: true,
|
|
20030
|
+
active_family: activeFamily,
|
|
20031
|
+
active_family_path: path,
|
|
20032
|
+
active_family_file_exists: existsSync9(path),
|
|
20033
|
+
render: {
|
|
20034
|
+
sections: [
|
|
20035
|
+
{
|
|
20036
|
+
title: "cli switch",
|
|
20037
|
+
lines: [
|
|
20038
|
+
`Active CLI family: ${activeFamily}`,
|
|
20039
|
+
`Active family file: ${path}`
|
|
20040
|
+
]
|
|
20041
|
+
}
|
|
20042
|
+
]
|
|
20043
|
+
}
|
|
20044
|
+
},
|
|
20045
|
+
{ json: options.json }
|
|
20046
|
+
);
|
|
20047
|
+
return 0;
|
|
20048
|
+
}
|
|
20049
|
+
if (normalized === "python" || normalized === "rollback") {
|
|
20050
|
+
const path = writeActiveFamily("python");
|
|
20051
|
+
printCommandEnvelope(
|
|
20052
|
+
{
|
|
20053
|
+
ok: true,
|
|
20054
|
+
active_family: "python",
|
|
20055
|
+
active_family_path: path,
|
|
20056
|
+
render: {
|
|
20057
|
+
sections: [
|
|
20058
|
+
{
|
|
20059
|
+
title: "cli switch",
|
|
20060
|
+
lines: [
|
|
20061
|
+
"Switched installer-managed `deepline` to the Python CLI."
|
|
20062
|
+
]
|
|
20063
|
+
}
|
|
20064
|
+
]
|
|
20065
|
+
}
|
|
20066
|
+
},
|
|
20067
|
+
{ json: options.json }
|
|
20068
|
+
);
|
|
20069
|
+
return 0;
|
|
20070
|
+
}
|
|
20071
|
+
if (normalized === "sdk") {
|
|
20072
|
+
const path = writeActiveFamily("sdk");
|
|
20073
|
+
printCommandEnvelope(
|
|
20074
|
+
{
|
|
20075
|
+
ok: true,
|
|
20076
|
+
active_family: "sdk",
|
|
20077
|
+
active_family_path: path,
|
|
20078
|
+
render: {
|
|
20079
|
+
sections: [
|
|
20080
|
+
{
|
|
20081
|
+
title: "cli switch",
|
|
20082
|
+
lines: ["Switched installer-managed `deepline` to the SDK CLI."]
|
|
20083
|
+
}
|
|
20084
|
+
]
|
|
20085
|
+
}
|
|
20086
|
+
},
|
|
20087
|
+
{ json: options.json }
|
|
20088
|
+
);
|
|
20089
|
+
return 0;
|
|
20090
|
+
}
|
|
20091
|
+
const message = `Unknown switch target: ${action}. Use one of: status, sdk, python, rollback.`;
|
|
20092
|
+
const envelope = {
|
|
20093
|
+
ok: false,
|
|
20094
|
+
error: message,
|
|
20095
|
+
code: "usage_error",
|
|
20096
|
+
render: {
|
|
20097
|
+
sections: [{ title: "cli switch", lines: [message] }]
|
|
20098
|
+
}
|
|
20099
|
+
};
|
|
20100
|
+
const wantsJson = options.json === true;
|
|
20101
|
+
if (wantsJson) {
|
|
20102
|
+
printCommandEnvelope(envelope, { json: true });
|
|
20103
|
+
} else {
|
|
20104
|
+
process.stderr.write(`${message}
|
|
20105
|
+
`);
|
|
20106
|
+
}
|
|
20107
|
+
return 2;
|
|
20108
|
+
}
|
|
20109
|
+
function registerSwitchCommands(program) {
|
|
20110
|
+
program.command("switch [target]").description(
|
|
20111
|
+
"Switch the installer-managed Deepline CLI between SDK and Python families."
|
|
20112
|
+
).option("--json", "Emit JSON output").addHelpText(
|
|
19482
20113
|
"after",
|
|
19483
20114
|
`
|
|
20115
|
+
Notes:
|
|
20116
|
+
This command changes only the local installer-managed wrapper state. It does
|
|
20117
|
+
not re-authenticate, reinstall packages, or contact Deepline servers.
|
|
20118
|
+
|
|
19484
20119
|
Examples:
|
|
19485
|
-
deepline
|
|
19486
|
-
deepline
|
|
19487
|
-
deepline
|
|
20120
|
+
deepline switch status
|
|
20121
|
+
deepline switch python
|
|
20122
|
+
deepline switch rollback
|
|
20123
|
+
deepline switch sdk
|
|
19488
20124
|
`
|
|
19489
|
-
).
|
|
19490
|
-
|
|
19491
|
-
|
|
19492
|
-
collectOption,
|
|
19493
|
-
[]
|
|
19494
|
-
).option(
|
|
19495
|
-
"--label <label>",
|
|
19496
|
-
"Label for the preceding session id",
|
|
19497
|
-
collectOption,
|
|
19498
|
-
[]
|
|
19499
|
-
).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);
|
|
20125
|
+
).action((target, options) => {
|
|
20126
|
+
process.exitCode = handleSwitch(target, options);
|
|
20127
|
+
});
|
|
19500
20128
|
}
|
|
19501
20129
|
|
|
19502
20130
|
// src/cli/commands/tools.ts
|
|
19503
20131
|
import { Option } from "commander";
|
|
19504
20132
|
import {
|
|
19505
20133
|
chmodSync,
|
|
19506
|
-
existsSync as
|
|
20134
|
+
existsSync as existsSync10,
|
|
19507
20135
|
mkdtempSync,
|
|
19508
|
-
readFileSync as
|
|
19509
|
-
writeFileSync as
|
|
20136
|
+
readFileSync as readFileSync9,
|
|
20137
|
+
writeFileSync as writeFileSync11
|
|
19510
20138
|
} from "fs";
|
|
19511
20139
|
import { tmpdir as tmpdir4 } from "os";
|
|
19512
|
-
import { join as
|
|
20140
|
+
import { join as join12, resolve as resolve13 } from "path";
|
|
19513
20141
|
|
|
19514
20142
|
// src/tool-output.ts
|
|
19515
|
-
import { mkdirSync as
|
|
19516
|
-
import { homedir as
|
|
19517
|
-
import { join as
|
|
20143
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync10 } from "fs";
|
|
20144
|
+
import { homedir as homedir7 } from "os";
|
|
20145
|
+
import { join as join11 } from "path";
|
|
19518
20146
|
function isPlainObject(value) {
|
|
19519
20147
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19520
20148
|
}
|
|
@@ -19610,19 +20238,19 @@ function tryConvertToList(payload, options) {
|
|
|
19610
20238
|
return null;
|
|
19611
20239
|
}
|
|
19612
20240
|
function ensureOutputDir() {
|
|
19613
|
-
const outputDir =
|
|
19614
|
-
|
|
20241
|
+
const outputDir = join11(homedir7(), ".local", "share", "deepline", "data");
|
|
20242
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
19615
20243
|
return outputDir;
|
|
19616
20244
|
}
|
|
19617
20245
|
function writeJsonOutputFile(payload, stem) {
|
|
19618
20246
|
const outputDir = ensureOutputDir();
|
|
19619
|
-
const outputPath =
|
|
19620
|
-
|
|
20247
|
+
const outputPath = join11(outputDir, `${stem}_${Date.now()}.json`);
|
|
20248
|
+
writeFileSync10(outputPath, JSON.stringify(payload, null, 2), "utf-8");
|
|
19621
20249
|
return outputPath;
|
|
19622
20250
|
}
|
|
19623
20251
|
function writeCsvOutputFile(rows, stem) {
|
|
19624
20252
|
const outputDir = ensureOutputDir();
|
|
19625
|
-
const outputPath =
|
|
20253
|
+
const outputPath = join11(outputDir, `${stem}_${Date.now()}.csv`);
|
|
19626
20254
|
const seen = /* @__PURE__ */ new Set();
|
|
19627
20255
|
const columns = [];
|
|
19628
20256
|
for (const row of rows) {
|
|
@@ -19645,7 +20273,7 @@ function writeCsvOutputFile(rows, stem) {
|
|
|
19645
20273
|
for (const row of rows) {
|
|
19646
20274
|
lines.push(columns.map((column) => escapeCell(row[column])).join(","));
|
|
19647
20275
|
}
|
|
19648
|
-
|
|
20276
|
+
writeFileSync10(outputPath, `${lines.join("\n")}
|
|
19649
20277
|
`, "utf-8");
|
|
19650
20278
|
const previewRows = rows.slice(0, 5);
|
|
19651
20279
|
const previewColumns = columns.slice(0, 5);
|
|
@@ -19684,13 +20312,12 @@ var TOOL_COMMAND_TEMPLATES = {
|
|
|
19684
20312
|
};
|
|
19685
20313
|
function toListedTool(tool) {
|
|
19686
20314
|
if (isPlayLikeTool(tool)) {
|
|
19687
|
-
const playReference = playReferenceForTool(tool);
|
|
19688
20315
|
return {
|
|
19689
20316
|
...tool,
|
|
19690
20317
|
description: listedToolDescription(tool),
|
|
19691
20318
|
id: tool.toolId,
|
|
19692
20319
|
type: "play",
|
|
19693
|
-
executeCommand:
|
|
20320
|
+
executeCommand: playRunCommandForTool(tool, tool.toolId)
|
|
19694
20321
|
};
|
|
19695
20322
|
}
|
|
19696
20323
|
return {
|
|
@@ -19904,17 +20531,32 @@ function isPlayLikeTool(tool) {
|
|
|
19904
20531
|
}
|
|
19905
20532
|
function playReferenceForTool(tool) {
|
|
19906
20533
|
const record = tool;
|
|
19907
|
-
const
|
|
19908
|
-
|
|
20534
|
+
const declared = stringField(record, "playReference", "play_reference");
|
|
20535
|
+
if (declared.startsWith("prebuilt/")) {
|
|
20536
|
+
return declared;
|
|
20537
|
+
}
|
|
20538
|
+
return null;
|
|
20539
|
+
}
|
|
20540
|
+
function playSearchCommandForToolId(toolId) {
|
|
20541
|
+
return `deepline plays search ${JSON.stringify(toolId.replace(/_/g, " "))} --json`;
|
|
19909
20542
|
}
|
|
19910
|
-
function
|
|
19911
|
-
const playReference =
|
|
19912
|
-
return
|
|
19913
|
-
|
|
19914
|
-
|
|
20543
|
+
function playRunCommandForTool(tool, toolId, input2) {
|
|
20544
|
+
const playReference = playReferenceForTool(tool);
|
|
20545
|
+
if (!playReference) return playSearchCommandForToolId(toolId);
|
|
20546
|
+
const inputArg = input2 === void 0 ? "'{...}'" : `'${JSON.stringify(input2)}'`;
|
|
20547
|
+
return `deepline plays run ${playReference} --input ${inputArg} --watch`;
|
|
19915
20548
|
}
|
|
19916
|
-
function
|
|
19917
|
-
|
|
20549
|
+
function playLikeToolExecuteErrorMessage(toolId, tool) {
|
|
20550
|
+
const playReference = tool ? playReferenceForTool(tool) : null;
|
|
20551
|
+
const nextStep = playReference ? `Use: deepline plays run ${playReference} --input '{...}' --watch` : `Find the maintained workflow with: ${playSearchCommandForToolId(toolId)}`;
|
|
20552
|
+
return [
|
|
20553
|
+
`${toolId} is a workflow/play entry, not an atomic provider tool.`,
|
|
20554
|
+
nextStep,
|
|
20555
|
+
'Or search provider tools only with: deepline tools search "<query>" --json'
|
|
20556
|
+
].join("\n");
|
|
20557
|
+
}
|
|
20558
|
+
function printPlayLikeToolExecuteError(toolId, tool) {
|
|
20559
|
+
console.error(playLikeToolExecuteErrorMessage(toolId, tool));
|
|
19918
20560
|
}
|
|
19919
20561
|
function registerToolsCommands(program) {
|
|
19920
20562
|
const tools = program.command("tools").description("Search, describe, and execute atomic provider tools.").addHelpText(
|
|
@@ -20212,13 +20854,14 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
|
|
|
20212
20854
|
"deeplineUsdPerPricingUnit",
|
|
20213
20855
|
"deepline_usd_per_pricing_unit"
|
|
20214
20856
|
);
|
|
20215
|
-
const starterScript = extractedLists.length > 0 ? starterScriptJson(
|
|
20857
|
+
const starterScript = !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
|
|
20216
20858
|
seedToolListScript({
|
|
20217
20859
|
toolId,
|
|
20218
20860
|
payload: samplePayloadForInputFields(inputFields),
|
|
20219
20861
|
rows: []
|
|
20220
20862
|
})
|
|
20221
20863
|
) : null;
|
|
20864
|
+
const executeCommand = isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
|
|
20222
20865
|
return {
|
|
20223
20866
|
schemaVersion: 1,
|
|
20224
20867
|
toolId,
|
|
@@ -20243,7 +20886,7 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
|
|
|
20243
20886
|
extractedLists,
|
|
20244
20887
|
extractedValues
|
|
20245
20888
|
},
|
|
20246
|
-
executeCommand
|
|
20889
|
+
executeCommand,
|
|
20247
20890
|
...starterScript ? { starterScript } : {}
|
|
20248
20891
|
};
|
|
20249
20892
|
}
|
|
@@ -20355,6 +20998,10 @@ function printToolSchemaOnly(tool, requestedToolId) {
|
|
|
20355
20998
|
}
|
|
20356
20999
|
}
|
|
20357
21000
|
function printToolExamplesOnly(tool, requestedToolId, options = {}) {
|
|
21001
|
+
if (isPlayLikeTool(tool)) {
|
|
21002
|
+
printPlayLikeToolUsage(tool, requestedToolId);
|
|
21003
|
+
return;
|
|
21004
|
+
}
|
|
20358
21005
|
const contract = toolContractJsonForDescribe(tool, requestedToolId);
|
|
20359
21006
|
const toolId = String(contract.toolId);
|
|
20360
21007
|
const inputFields = Array.isArray(contract.inputFields) ? contract.inputFields : [];
|
|
@@ -20386,6 +21033,25 @@ function printToolExamplesOnly(tool, requestedToolId, options = {}) {
|
|
|
20386
21033
|
printSamples(samples);
|
|
20387
21034
|
}
|
|
20388
21035
|
}
|
|
21036
|
+
function printPlayLikeToolUsage(tool, requestedToolId) {
|
|
21037
|
+
const toolId = String(tool.toolId || requestedToolId);
|
|
21038
|
+
const inputFields = toolInputFieldsForDisplay(
|
|
21039
|
+
recordField2(tool, "inputSchema", "input_schema")
|
|
21040
|
+
);
|
|
21041
|
+
const sampleInput = samplePayloadForInputFields(inputFields);
|
|
21042
|
+
const playReference = playReferenceForTool(tool);
|
|
21043
|
+
console.log("Run as a play:");
|
|
21044
|
+
console.log(
|
|
21045
|
+
playRunCommandForTool(
|
|
21046
|
+
tool,
|
|
21047
|
+
toolId,
|
|
21048
|
+
Object.keys(sampleInput).length ? sampleInput : void 0
|
|
21049
|
+
)
|
|
21050
|
+
);
|
|
21051
|
+
if (playReference) {
|
|
21052
|
+
console.log(`deepline plays describe ${playReference} --json`);
|
|
21053
|
+
}
|
|
21054
|
+
}
|
|
20389
21055
|
function printToolGettersOnly(tool, requestedToolId) {
|
|
20390
21056
|
const contract = toolContractJsonForDescribe(tool, requestedToolId);
|
|
20391
21057
|
const getters = isRecord8(contract.getters) ? contract.getters : {};
|
|
@@ -20472,13 +21138,14 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
|
|
|
20472
21138
|
const extractedLists = extractionContractEntries(
|
|
20473
21139
|
arrayField(toolExecutionResult, "extractedLists", "extracted_lists")
|
|
20474
21140
|
);
|
|
20475
|
-
const starterScript = extractedLists.length > 0 ? starterScriptJson(
|
|
21141
|
+
const starterScript = !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
|
|
20476
21142
|
seedToolListScript({
|
|
20477
21143
|
toolId,
|
|
20478
21144
|
payload: samplePayloadForInputFields(inputFields),
|
|
20479
21145
|
rows: []
|
|
20480
21146
|
})
|
|
20481
21147
|
) : null;
|
|
21148
|
+
const observedOutputCommand = isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
|
|
20482
21149
|
const {
|
|
20483
21150
|
cost: _cost,
|
|
20484
21151
|
deeplineCreditsPerPricingUnit: _deeplineCreditsPerPricingUnit,
|
|
@@ -20504,8 +21171,8 @@ function toolMetadataJsonForDescribe(tool, requestedToolId) {
|
|
|
20504
21171
|
getterScope: "extractedValues/extractedLists .get() only works for declared Deepline getters listed in usageGuidance.toolExecutionResult.",
|
|
20505
21172
|
rawToolResponse: "Use toolExecutionResult.toolResponse.raw for provider/tool-specific fields, fields in outputSchema that are not declared getters, and debugging.",
|
|
20506
21173
|
invalidGetterHint: "If TypeScript says an extractedValues/extractedLists property does not exist, that field is not a declared Deepline getter.",
|
|
20507
|
-
observeActualShape:
|
|
20508
|
-
observedOutput:
|
|
21174
|
+
observeActualShape: observedOutputCommand,
|
|
21175
|
+
observedOutput: observedOutputCommand,
|
|
20509
21176
|
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.",
|
|
20510
21177
|
executeOutputFields: "tools execute JSON may include output_preview for this direct probe only; play debugging uses run output and returned dataset handles."
|
|
20511
21178
|
},
|
|
@@ -20661,10 +21328,10 @@ function normalizeOutputFormat(raw) {
|
|
|
20661
21328
|
function resolveAtFilePath(rawPath) {
|
|
20662
21329
|
const trimmed = rawPath.trim();
|
|
20663
21330
|
const resolved = resolve13(trimmed);
|
|
20664
|
-
if (
|
|
21331
|
+
if (existsSync10(resolved)) return resolved;
|
|
20665
21332
|
if (process.platform !== "win32" && trimmed.includes("\\")) {
|
|
20666
21333
|
const normalized = resolve13(trimmed.replace(/\\/g, "/"));
|
|
20667
|
-
if (
|
|
21334
|
+
if (existsSync10(normalized)) return normalized;
|
|
20668
21335
|
}
|
|
20669
21336
|
return resolved;
|
|
20670
21337
|
}
|
|
@@ -20675,7 +21342,7 @@ function readJsonArgument(raw, flagName) {
|
|
|
20675
21342
|
throw new Error(`Invalid ${flagName} value: empty @file path.`);
|
|
20676
21343
|
}
|
|
20677
21344
|
try {
|
|
20678
|
-
return
|
|
21345
|
+
return readFileSync9(resolveAtFilePath(filePath), "utf8").replace(
|
|
20679
21346
|
/^\uFEFF/,
|
|
20680
21347
|
""
|
|
20681
21348
|
);
|
|
@@ -20777,9 +21444,9 @@ function starterScriptJson(script) {
|
|
|
20777
21444
|
function seedToolListScript(input2) {
|
|
20778
21445
|
const stem = safeFileStem(input2.toolId);
|
|
20779
21446
|
const fileName = `${stem}-workflow-seed-${Date.now()}.play.ts`;
|
|
20780
|
-
const scriptDir = mkdtempSync(
|
|
21447
|
+
const scriptDir = mkdtempSync(join12(tmpdir4(), "deepline-workflow-seed-"));
|
|
20781
21448
|
chmodSync(scriptDir, 448);
|
|
20782
|
-
const scriptPath =
|
|
21449
|
+
const scriptPath = join12(scriptDir, fileName);
|
|
20783
21450
|
const projectDir = `deepline/projects/${stem}-workflow`;
|
|
20784
21451
|
const playName = `${stem}-workflow`;
|
|
20785
21452
|
const sampleRows = input2.rows.length > 0 ? `${JSON.stringify(input2.rows.slice(0, 2)).replace(/\]$/, "")}, ...]` : "[]";
|
|
@@ -20815,7 +21482,7 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
|
|
|
20815
21482
|
};
|
|
20816
21483
|
});
|
|
20817
21484
|
`;
|
|
20818
|
-
|
|
21485
|
+
writeFileSync11(scriptPath, script, { encoding: "utf-8", mode: 384 });
|
|
20819
21486
|
return {
|
|
20820
21487
|
path: scriptPath,
|
|
20821
21488
|
sourceCode: script,
|
|
@@ -20916,9 +21583,19 @@ async function executeTool(args) {
|
|
|
20916
21583
|
}
|
|
20917
21584
|
if (isPlayLikeTool(metadata)) {
|
|
20918
21585
|
if (argsWantJson(args)) {
|
|
20919
|
-
printJsonError(
|
|
21586
|
+
printJsonError(
|
|
21587
|
+
new Error(
|
|
21588
|
+
playLikeToolExecuteErrorMessage(
|
|
21589
|
+
parsed.toolId,
|
|
21590
|
+
metadata
|
|
21591
|
+
)
|
|
21592
|
+
)
|
|
21593
|
+
);
|
|
20920
21594
|
} else {
|
|
20921
|
-
printPlayLikeToolExecuteError(
|
|
21595
|
+
printPlayLikeToolExecuteError(
|
|
21596
|
+
parsed.toolId,
|
|
21597
|
+
metadata
|
|
21598
|
+
);
|
|
20922
21599
|
}
|
|
20923
21600
|
return 2;
|
|
20924
21601
|
}
|
|
@@ -21070,7 +21747,7 @@ async function executeTool(args) {
|
|
|
21070
21747
|
|
|
21071
21748
|
// src/cli/commands/workflow.ts
|
|
21072
21749
|
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile5 } from "fs/promises";
|
|
21073
|
-
import { dirname as
|
|
21750
|
+
import { dirname as dirname11, join as join13, resolve as resolve14 } from "path";
|
|
21074
21751
|
|
|
21075
21752
|
// src/cli/workflow-to-play.ts
|
|
21076
21753
|
import { createHash as createHash4 } from "crypto";
|
|
@@ -21311,8 +21988,8 @@ async function transformOne(api, workflowId, outDir, publish) {
|
|
|
21311
21988
|
revision.config,
|
|
21312
21989
|
{ workflowName: workflow.name, version: revision.version }
|
|
21313
21990
|
);
|
|
21314
|
-
const file =
|
|
21315
|
-
await mkdir5(
|
|
21991
|
+
const file = join13(resolve14(outDir), `${compiled.playName}.play.ts`);
|
|
21992
|
+
await mkdir5(dirname11(file), { recursive: true });
|
|
21316
21993
|
await writeFile5(file, compiled.sourceCode, "utf8");
|
|
21317
21994
|
let published = false;
|
|
21318
21995
|
if (publish) {
|
|
@@ -21559,8 +22236,8 @@ Notes:
|
|
|
21559
22236
|
|
|
21560
22237
|
// src/cli/commands/update.ts
|
|
21561
22238
|
import { spawn as spawn2 } from "child_process";
|
|
21562
|
-
import { existsSync as
|
|
21563
|
-
import { dirname as
|
|
22239
|
+
import { existsSync as existsSync11 } from "fs";
|
|
22240
|
+
import { dirname as dirname12, join as join14, resolve as resolve15 } from "path";
|
|
21564
22241
|
function posixShellQuote(value) {
|
|
21565
22242
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
21566
22243
|
}
|
|
@@ -21581,17 +22258,17 @@ function buildSourceUpdateCommand(sourceRoot) {
|
|
|
21581
22258
|
function findRepoBackedSdkRoot(startPath) {
|
|
21582
22259
|
let current = resolve15(startPath);
|
|
21583
22260
|
while (true) {
|
|
21584
|
-
if (
|
|
22261
|
+
if (existsSync11(join14(current, "sdk", "package.json")) && existsSync11(join14(current, "sdk", "bin", "deepline-dev.ts"))) {
|
|
21585
22262
|
return current;
|
|
21586
22263
|
}
|
|
21587
|
-
const parent =
|
|
22264
|
+
const parent = dirname12(current);
|
|
21588
22265
|
if (parent === current) return null;
|
|
21589
22266
|
current = parent;
|
|
21590
22267
|
}
|
|
21591
22268
|
}
|
|
21592
22269
|
function resolveUpdatePlan() {
|
|
21593
22270
|
const entrypoint = process.argv[1] ? resolve15(process.argv[1]) : "";
|
|
21594
|
-
const sourceRoot = entrypoint ? findRepoBackedSdkRoot(
|
|
22271
|
+
const sourceRoot = entrypoint ? findRepoBackedSdkRoot(dirname12(entrypoint)) : null;
|
|
21595
22272
|
if (sourceRoot) {
|
|
21596
22273
|
return {
|
|
21597
22274
|
kind: "source",
|
|
@@ -21758,6 +22435,23 @@ var install_commands_default = {
|
|
|
21758
22435
|
|
|
21759
22436
|
// src/cli/install-commands.ts
|
|
21760
22437
|
var INSTALL_COMMANDS = install_commands_default;
|
|
22438
|
+
var DEFAULT_V1_SKILL_NAMES = [
|
|
22439
|
+
"build-tam",
|
|
22440
|
+
"clay-to-deepline",
|
|
22441
|
+
"deepline-analytics",
|
|
22442
|
+
"deepline-feedback",
|
|
22443
|
+
"deepline-gtm",
|
|
22444
|
+
"deepline-quickstart",
|
|
22445
|
+
"find-qualified-titles",
|
|
22446
|
+
"linkedin-url-lookup",
|
|
22447
|
+
"niche-signal-discovery",
|
|
22448
|
+
"portfolio-prospecting",
|
|
22449
|
+
"workflow-hello-world"
|
|
22450
|
+
];
|
|
22451
|
+
var DEFAULT_SDK_SKILL_NAMES = [
|
|
22452
|
+
...DEFAULT_V1_SKILL_NAMES,
|
|
22453
|
+
"deepline-plays"
|
|
22454
|
+
];
|
|
21761
22455
|
function normalizeBaseUrl2(baseUrl) {
|
|
21762
22456
|
return baseUrl.replace(/\/$/, "");
|
|
21763
22457
|
}
|
|
@@ -21788,10 +22482,7 @@ function buildSkillsAddArgs(baseUrl, skillName, options = {}) {
|
|
|
21788
22482
|
return INSTALL_COMMANDS.skills.default_agents;
|
|
21789
22483
|
}
|
|
21790
22484
|
if (arg === "{skill_name}") {
|
|
21791
|
-
return [
|
|
21792
|
-
value,
|
|
21793
|
-
...extraSkillNames.flatMap((name) => ["--skill", name])
|
|
21794
|
-
];
|
|
22485
|
+
return [value, ...extraSkillNames.flatMap((name) => ["--skill", name])];
|
|
21795
22486
|
}
|
|
21796
22487
|
return value;
|
|
21797
22488
|
}
|
|
@@ -21835,8 +22526,8 @@ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
|
|
|
21835
22526
|
if (currentFamily === "sdk") {
|
|
21836
22527
|
lines.push(
|
|
21837
22528
|
"",
|
|
21838
|
-
" To stay on the SDK CLI,
|
|
21839
|
-
` ${skillsInstallCommand(baseUrl,
|
|
22529
|
+
" To stay on the SDK CLI, refresh the Deepline agent skills:",
|
|
22530
|
+
` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
|
|
21840
22531
|
" To use the legacy Python CLI instead:",
|
|
21841
22532
|
` ${legacyPythonInstallCommand(baseUrl)}`,
|
|
21842
22533
|
" `deepline update` updates this SDK CLI, but it will not switch CLI families."
|
|
@@ -21847,9 +22538,9 @@ function commandCompatibilityHint(currentFamily, commandName, baseUrl) {
|
|
|
21847
22538
|
} else {
|
|
21848
22539
|
lines.push(
|
|
21849
22540
|
"",
|
|
21850
|
-
" To use SDK commands, install the SDK CLI and
|
|
22541
|
+
" To use SDK commands, install the SDK CLI and refresh Deepline agent skills:",
|
|
21851
22542
|
` ${sdkNpmGlobalInstallCommand()}`,
|
|
21852
|
-
` ${skillsInstallCommand(baseUrl,
|
|
22543
|
+
` ${skillsInstallCommand(baseUrl, DEFAULT_SDK_SKILL_NAMES)}`,
|
|
21853
22544
|
" `deepline update` updates this Python CLI and its skills, but it will not switch CLI families."
|
|
21854
22545
|
);
|
|
21855
22546
|
if (compatibility.python_alternative) {
|
|
@@ -21930,23 +22621,11 @@ async function maybeAutoUpdateAndRelaunch(response) {
|
|
|
21930
22621
|
|
|
21931
22622
|
// src/cli/skills-sync.ts
|
|
21932
22623
|
import { spawn as spawn4, spawnSync as spawnSync2 } from "child_process";
|
|
21933
|
-
import {
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
readdirSync as readdirSync3,
|
|
21937
|
-
readFileSync as readFileSync9,
|
|
21938
|
-
statSync as statSync4,
|
|
21939
|
-
writeFileSync as writeFileSync11
|
|
21940
|
-
} from "fs";
|
|
21941
|
-
import { homedir as homedir7 } from "os";
|
|
21942
|
-
import { dirname as dirname12, join as join14 } from "path";
|
|
22624
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync12 } from "fs";
|
|
22625
|
+
import { homedir as homedir8 } from "os";
|
|
22626
|
+
import { dirname as dirname13, join as join15 } from "path";
|
|
21943
22627
|
var CHECK_TIMEOUT_MS2 = 3e3;
|
|
21944
|
-
var
|
|
21945
|
-
var SDK_SKILL_NAMES = [
|
|
21946
|
-
SDK_SKILL_NAME,
|
|
21947
|
-
"deepline-plays-feedback",
|
|
21948
|
-
"deepline-plays-quickstart"
|
|
21949
|
-
];
|
|
22628
|
+
var SDK_PLAY_SKILL_NAME = "deepline-plays";
|
|
21950
22629
|
var attemptedSync = false;
|
|
21951
22630
|
function shouldSkipSkillsSync() {
|
|
21952
22631
|
const value = process.env.DEEPLINE_SKIP_SKILLS_SYNC?.trim().toLowerCase();
|
|
@@ -21958,20 +22637,20 @@ function activePluginSkillsDir() {
|
|
|
21958
22637
|
return "";
|
|
21959
22638
|
}
|
|
21960
22639
|
const dir = process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim() ?? "";
|
|
21961
|
-
return dir &&
|
|
22640
|
+
return dir && existsSync12(dir) ? dir : "";
|
|
21962
22641
|
}
|
|
21963
22642
|
function readPluginSkillsVersion() {
|
|
21964
22643
|
const dir = activePluginSkillsDir();
|
|
21965
22644
|
if (!dir) return "";
|
|
21966
22645
|
try {
|
|
21967
|
-
return
|
|
22646
|
+
return readFileSync10(join15(dir, ".version"), "utf-8").trim();
|
|
21968
22647
|
} catch {
|
|
21969
22648
|
return "";
|
|
21970
22649
|
}
|
|
21971
22650
|
}
|
|
21972
22651
|
function sdkSkillsVersionPath(baseUrl) {
|
|
21973
|
-
const home = process.env.HOME?.trim() ||
|
|
21974
|
-
return
|
|
22652
|
+
const home = process.env.HOME?.trim() || homedir8();
|
|
22653
|
+
return join15(
|
|
21975
22654
|
home,
|
|
21976
22655
|
".local",
|
|
21977
22656
|
"deepline",
|
|
@@ -21984,55 +22663,46 @@ function readLocalSkillsVersion(baseUrl) {
|
|
|
21984
22663
|
const pluginVersion = readPluginSkillsVersion();
|
|
21985
22664
|
if (pluginVersion) return pluginVersion;
|
|
21986
22665
|
const path = sdkSkillsVersionPath(baseUrl);
|
|
21987
|
-
if (!
|
|
22666
|
+
if (!existsSync12(path)) return "";
|
|
21988
22667
|
try {
|
|
21989
|
-
return
|
|
22668
|
+
return readFileSync10(path, "utf-8").trim();
|
|
21990
22669
|
} catch {
|
|
21991
22670
|
return "";
|
|
21992
22671
|
}
|
|
21993
22672
|
}
|
|
21994
22673
|
function writeLocalSkillsVersion(baseUrl, version) {
|
|
21995
22674
|
const path = sdkSkillsVersionPath(baseUrl);
|
|
21996
|
-
|
|
21997
|
-
|
|
22675
|
+
mkdirSync7(dirname13(path), { recursive: true });
|
|
22676
|
+
writeFileSync12(path, `${version}
|
|
21998
22677
|
`, "utf-8");
|
|
21999
22678
|
}
|
|
22000
|
-
function
|
|
22001
|
-
|
|
22002
|
-
|
|
22003
|
-
|
|
22004
|
-
|
|
22005
|
-
|
|
22006
|
-
|
|
22007
|
-
const
|
|
22008
|
-
|
|
22009
|
-
|
|
22010
|
-
|
|
22011
|
-
|
|
22012
|
-
|
|
22013
|
-
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
|
|
22017
|
-
|
|
22018
|
-
|
|
22019
|
-
|
|
22020
|
-
|
|
22021
|
-
|
|
22022
|
-
|
|
22023
|
-
const text = readFileSync9(path, "utf-8");
|
|
22024
|
-
if (staleMarkers.some((marker) => text.includes(marker))) return true;
|
|
22025
|
-
}
|
|
22026
|
-
return false;
|
|
22027
|
-
};
|
|
22028
|
-
for (const root of roots) {
|
|
22029
|
-
try {
|
|
22030
|
-
if (existsSync11(root) && scan(root)) return true;
|
|
22031
|
-
} catch {
|
|
22032
|
-
continue;
|
|
22033
|
-
}
|
|
22679
|
+
function sortedUniqueSkillNames(names) {
|
|
22680
|
+
return [...new Set(names.map((name) => name.trim()).filter(Boolean))].sort(
|
|
22681
|
+
(a, b) => a.localeCompare(b)
|
|
22682
|
+
);
|
|
22683
|
+
}
|
|
22684
|
+
async function fetchV1SkillNames(baseUrl) {
|
|
22685
|
+
const controller = new AbortController();
|
|
22686
|
+
const timeout = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS2);
|
|
22687
|
+
try {
|
|
22688
|
+
const response = await fetch(
|
|
22689
|
+
new URL("/.well-known/skills/index.json", baseUrl),
|
|
22690
|
+
{ signal: controller.signal }
|
|
22691
|
+
);
|
|
22692
|
+
if (!response.ok) return [];
|
|
22693
|
+
const data = await response.json().catch(() => null);
|
|
22694
|
+
const names = (data?.skills ?? []).filter((skill) => skill.install_surface === "v1").map((skill) => skill.name).filter(
|
|
22695
|
+
(name) => typeof name === "string" && name.length > 0
|
|
22696
|
+
);
|
|
22697
|
+
return sortedUniqueSkillNames(names);
|
|
22698
|
+
} catch {
|
|
22699
|
+
return [];
|
|
22700
|
+
} finally {
|
|
22701
|
+
clearTimeout(timeout);
|
|
22034
22702
|
}
|
|
22035
|
-
|
|
22703
|
+
}
|
|
22704
|
+
function buildSdkSkillNames(v1SkillNames) {
|
|
22705
|
+
return sortedUniqueSkillNames([...v1SkillNames, SDK_PLAY_SKILL_NAME]);
|
|
22036
22706
|
}
|
|
22037
22707
|
async function fetchSkillsUpdate(baseUrl, localVersion) {
|
|
22038
22708
|
const controller = new AbortController();
|
|
@@ -22062,11 +22732,13 @@ async function fetchSkillsUpdate(baseUrl, localVersion) {
|
|
|
22062
22732
|
clearTimeout(timeout);
|
|
22063
22733
|
}
|
|
22064
22734
|
}
|
|
22065
|
-
function buildSkillsInstallArgs(baseUrl) {
|
|
22066
|
-
return buildSkillsAddArgs(baseUrl,
|
|
22735
|
+
function buildSkillsInstallArgs(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
|
|
22736
|
+
return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames));
|
|
22067
22737
|
}
|
|
22068
|
-
function buildBunxSkillsInstallArgs(baseUrl) {
|
|
22069
|
-
return buildSkillsAddArgs(baseUrl,
|
|
22738
|
+
function buildBunxSkillsInstallArgs(baseUrl, skillNames) {
|
|
22739
|
+
return buildSkillsAddArgs(baseUrl, sortedUniqueSkillNames(skillNames), {
|
|
22740
|
+
firstArg: "--bun"
|
|
22741
|
+
});
|
|
22070
22742
|
}
|
|
22071
22743
|
function hasCommand(command) {
|
|
22072
22744
|
const result = spawnSync2(command, ["--version"], {
|
|
@@ -22078,15 +22750,15 @@ function hasCommand(command) {
|
|
|
22078
22750
|
function shellQuote4(arg) {
|
|
22079
22751
|
return `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
22080
22752
|
}
|
|
22081
|
-
function resolveSkillsInstallCommands(baseUrl) {
|
|
22082
|
-
const npxArgs = buildSkillsInstallArgs(baseUrl);
|
|
22753
|
+
function resolveSkillsInstallCommands(baseUrl, skillNames = DEFAULT_SDK_SKILL_NAMES) {
|
|
22754
|
+
const npxArgs = buildSkillsInstallArgs(baseUrl, skillNames);
|
|
22083
22755
|
const npxInstall = {
|
|
22084
22756
|
command: "npx",
|
|
22085
22757
|
args: npxArgs,
|
|
22086
22758
|
manualCommand: `npx ${npxArgs.map(shellQuote4).join(" ")}`
|
|
22087
22759
|
};
|
|
22088
22760
|
if (hasCommand("bunx")) {
|
|
22089
|
-
const bunxArgs = buildBunxSkillsInstallArgs(baseUrl);
|
|
22761
|
+
const bunxArgs = buildBunxSkillsInstallArgs(baseUrl, skillNames);
|
|
22090
22762
|
return [
|
|
22091
22763
|
{
|
|
22092
22764
|
command: "bunx",
|
|
@@ -22129,9 +22801,9 @@ function runOneSkillsInstall(install) {
|
|
|
22129
22801
|
});
|
|
22130
22802
|
});
|
|
22131
22803
|
}
|
|
22132
|
-
async function runSkillsInstall(baseUrl) {
|
|
22804
|
+
async function runSkillsInstall(baseUrl, skillNames) {
|
|
22133
22805
|
const failures = [];
|
|
22134
|
-
for (const install of resolveSkillsInstallCommands(baseUrl)) {
|
|
22806
|
+
for (const install of resolveSkillsInstallCommands(baseUrl, skillNames)) {
|
|
22135
22807
|
const result = await runOneSkillsInstall(install);
|
|
22136
22808
|
if (result.ok) return true;
|
|
22137
22809
|
failures.push(result);
|
|
@@ -22161,26 +22833,289 @@ async function syncSdkSkillsIfNeeded(baseUrl) {
|
|
|
22161
22833
|
const usingPluginSkills = Boolean(activePluginSkillsDir());
|
|
22162
22834
|
const localVersion = readLocalSkillsVersion(baseUrl);
|
|
22163
22835
|
const update = await fetchSkillsUpdate(baseUrl, localVersion);
|
|
22164
|
-
const hasStaleInstalledSkill = installedSdkSkillHasStalePositionalExecuteExamples();
|
|
22165
22836
|
if (usingPluginSkills) {
|
|
22166
22837
|
return;
|
|
22167
22838
|
}
|
|
22168
|
-
if (!update?.needsUpdate
|
|
22839
|
+
if (!update?.needsUpdate || !update.remoteVersion) {
|
|
22169
22840
|
return;
|
|
22170
22841
|
}
|
|
22171
|
-
|
|
22172
|
-
|
|
22842
|
+
const remoteSkillNames = await fetchV1SkillNames(baseUrl);
|
|
22843
|
+
const skillNames = buildSdkSkillNames(
|
|
22844
|
+
remoteSkillNames.length > 0 ? remoteSkillNames : DEFAULT_SDK_SKILL_NAMES
|
|
22173
22845
|
);
|
|
22174
|
-
|
|
22846
|
+
if (skillNames.length === 0) return;
|
|
22847
|
+
writeSdkSkillsStatusLine("Deepline skills changed; syncing agent skills...");
|
|
22848
|
+
const installed = await runSkillsInstall(baseUrl, skillNames);
|
|
22175
22849
|
if (!installed) return;
|
|
22176
|
-
if (installedSdkSkillHasStalePositionalExecuteExamples()) {
|
|
22177
|
-
process.stderr.write(
|
|
22178
|
-
"SDK skills sync completed, but installed deepline-plays docs still contain stale positional ctx.tools.execute examples.\n"
|
|
22179
|
-
);
|
|
22180
|
-
return;
|
|
22181
|
-
}
|
|
22182
22850
|
writeLocalSkillsVersion(baseUrl, update.remoteVersion);
|
|
22183
|
-
writeSdkSkillsStatusLine("
|
|
22851
|
+
writeSdkSkillsStatusLine("Deepline agent skills are up to date.");
|
|
22852
|
+
}
|
|
22853
|
+
|
|
22854
|
+
// src/cli/failure-reporting.ts
|
|
22855
|
+
import { hostname as hostname2, platform as platform2, release } from "os";
|
|
22856
|
+
var FAILURE_REPORT_DISABLE_ENV = "DEEPLINE_DISABLE_FAILURE_REPORTING";
|
|
22857
|
+
var REPORT_FAILURE_TIMEOUT_MS = 1e4;
|
|
22858
|
+
var MAX_FAILURE_TEXT_CHARS = 4e3;
|
|
22859
|
+
var MAX_COMMAND_TOKENS = 3;
|
|
22860
|
+
var REPORTABLE_EXIT_CODES = /* @__PURE__ */ new Set([4, 5]);
|
|
22861
|
+
var EMAIL_RE = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi;
|
|
22862
|
+
var UNIX_PATH_RE = /(?:\/Users\/|\/home\/|\/var\/folders\/|\/tmp\/|\/sessions\/)[^"'\n\r`]+/g;
|
|
22863
|
+
var WINDOWS_PATH_RE = /[A-Za-z]:(?:\\{1,2})(?:Users|Temp|tmp)(?:\\{1,2})[^"'\n\r`]+/g;
|
|
22864
|
+
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;
|
|
22865
|
+
var BEARER_SECRET_RE = /\b(bearer)\s+([A-Za-z0-9._-]+)/gi;
|
|
22866
|
+
var GENERIC_TOKEN_RE = /\b(?:dlp|sk|ghp|xox[baprs])[-_A-Za-z0-9]{8,}\b/g;
|
|
22867
|
+
var SECRET_OPTION_RE = /^--?(?:access[-_]?token|api[-_]?key|apikey|auth(?:orization)?|bearer|password|secret|session|token)$/i;
|
|
22868
|
+
function truthyEnv(name) {
|
|
22869
|
+
return ["1", "true", "yes", "on"].includes(
|
|
22870
|
+
String(process.env[name] ?? "").trim().toLowerCase()
|
|
22871
|
+
);
|
|
22872
|
+
}
|
|
22873
|
+
function isFailureReportingDisabled() {
|
|
22874
|
+
return truthyEnv(FAILURE_REPORT_DISABLE_ENV);
|
|
22875
|
+
}
|
|
22876
|
+
function redactFailureText(value, maxChars = MAX_FAILURE_TEXT_CHARS) {
|
|
22877
|
+
const home = process.env.HOME?.trim();
|
|
22878
|
+
let text = String(value ?? "");
|
|
22879
|
+
if (!text) return "";
|
|
22880
|
+
if (home && home !== "/") {
|
|
22881
|
+
text = text.split(home).join("~");
|
|
22882
|
+
}
|
|
22883
|
+
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);
|
|
22884
|
+
}
|
|
22885
|
+
function sanitizeCommand(argv, prefix = "deepline") {
|
|
22886
|
+
const tokens = [];
|
|
22887
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
22888
|
+
const arg = argv[index];
|
|
22889
|
+
const value = String(arg ?? "").trim();
|
|
22890
|
+
if (!value) continue;
|
|
22891
|
+
if (value.startsWith("-")) {
|
|
22892
|
+
if (!value.includes("=") && SECRET_OPTION_RE.test(value)) {
|
|
22893
|
+
index += 1;
|
|
22894
|
+
}
|
|
22895
|
+
continue;
|
|
22896
|
+
}
|
|
22897
|
+
tokens.push(redactFailureText(value, 200));
|
|
22898
|
+
if (tokens.length >= MAX_COMMAND_TOKENS) break;
|
|
22899
|
+
}
|
|
22900
|
+
return tokens.length > 0 ? [prefix, ...tokens].join(" ") : prefix;
|
|
22901
|
+
}
|
|
22902
|
+
function errorMessage3(error) {
|
|
22903
|
+
if (error instanceof Error) return `${error.name}: ${error.message}`;
|
|
22904
|
+
return String(error ?? "");
|
|
22905
|
+
}
|
|
22906
|
+
function errorStack(error) {
|
|
22907
|
+
if (error instanceof Error && error.stack) {
|
|
22908
|
+
return redactFailureText(error.stack);
|
|
22909
|
+
}
|
|
22910
|
+
return null;
|
|
22911
|
+
}
|
|
22912
|
+
function classifyNetworkFailure(error) {
|
|
22913
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22914
|
+
let current = error;
|
|
22915
|
+
while (current && !seen.has(current)) {
|
|
22916
|
+
seen.add(current);
|
|
22917
|
+
const record = typeof current === "object" && current !== null ? current : {};
|
|
22918
|
+
const code = String(record.code ?? "").toLowerCase();
|
|
22919
|
+
const name = current instanceof Error ? current.name.toLowerCase() : "";
|
|
22920
|
+
const text = String(
|
|
22921
|
+
current instanceof Error ? current.message : current
|
|
22922
|
+
).toLowerCase();
|
|
22923
|
+
const combined = `${code} ${name} ${text}`;
|
|
22924
|
+
if (combined.includes("aborterror") || combined.includes("timeout") || combined.includes("timed out") || combined.includes("etimedout")) {
|
|
22925
|
+
return "network_timeout";
|
|
22926
|
+
}
|
|
22927
|
+
if (combined.includes("enotfound") || combined.includes("eai_again") || combined.includes("name or service not known") || combined.includes("temporary failure in name resolution")) {
|
|
22928
|
+
return "network_dns_resolution_failed";
|
|
22929
|
+
}
|
|
22930
|
+
if (combined.includes("econnrefused") || combined.includes("connection refused")) {
|
|
22931
|
+
return "network_connection_refused";
|
|
22932
|
+
}
|
|
22933
|
+
if (combined.includes("econnreset") || combined.includes("connection reset")) {
|
|
22934
|
+
return "network_connection_reset";
|
|
22935
|
+
}
|
|
22936
|
+
if (combined.includes("incompleteread") || combined.includes("incomplete read")) {
|
|
22937
|
+
return "network_incomplete_read";
|
|
22938
|
+
}
|
|
22939
|
+
if (combined.includes("remotedisconnected") || combined.includes("remote end closed connection") || combined.includes("other side closed") || combined.includes("socket hang up")) {
|
|
22940
|
+
return "network_remote_disconnected";
|
|
22941
|
+
}
|
|
22942
|
+
if (combined.includes("ssl") || combined.includes("tls") || combined.includes("unexpected_eof_while_reading")) {
|
|
22943
|
+
return "network_ssl_error";
|
|
22944
|
+
}
|
|
22945
|
+
current = record.cause ?? record.context;
|
|
22946
|
+
}
|
|
22947
|
+
return "network_error";
|
|
22948
|
+
}
|
|
22949
|
+
function isNetworkFailure(error) {
|
|
22950
|
+
if (!(error instanceof Error)) return false;
|
|
22951
|
+
if (error instanceof DeeplineError && error.statusCode) return false;
|
|
22952
|
+
const code = classifyNetworkFailure(error);
|
|
22953
|
+
return code !== "network_error" || /unable to connect|unable to stream/i.test(error.message);
|
|
22954
|
+
}
|
|
22955
|
+
function detectAgentRuntime3() {
|
|
22956
|
+
if (process.env.CODEX_THREAD_ID?.trim()) return "codex";
|
|
22957
|
+
const pluginMode = truthyEnv("DEEPLINE_PLUGIN_MODE");
|
|
22958
|
+
const claudeRemote = truthyEnv("CLAUDE_CODE_REMOTE");
|
|
22959
|
+
const projectDir = Boolean(process.env.CLAUDE_PROJECT_DIR?.trim());
|
|
22960
|
+
const pluginRoot = Boolean(process.env.DEEPLINE_PLUGIN_ROOT?.trim());
|
|
22961
|
+
const sessionHome = process.env.HOME?.trim().startsWith("/sessions/") ?? false;
|
|
22962
|
+
if ((pluginMode || pluginRoot) && (claudeRemote || projectDir || sessionHome)) {
|
|
22963
|
+
return "claude_cowork";
|
|
22964
|
+
}
|
|
22965
|
+
if (process.env.CLAUDECODE?.trim() === "1") return "claude_code";
|
|
22966
|
+
if (process.env.CLINE_ACTIVE?.trim().toLowerCase() === "true") return "cline";
|
|
22967
|
+
if (process.env.CURSOR_TRACE_ID?.trim() || process.env.CURSOR_AGENT?.trim()) {
|
|
22968
|
+
return "cursor";
|
|
22969
|
+
}
|
|
22970
|
+
if (process.env.WINDSURF?.trim() || process.env.CASCADE?.trim()) {
|
|
22971
|
+
return "windsurf";
|
|
22972
|
+
}
|
|
22973
|
+
return "unknown";
|
|
22974
|
+
}
|
|
22975
|
+
function buildEnvironmentContext() {
|
|
22976
|
+
const context = {
|
|
22977
|
+
os: platform2(),
|
|
22978
|
+
os_release: release(),
|
|
22979
|
+
platform: `${platform2()}-${release()}-${process.arch}`,
|
|
22980
|
+
node_version: process.version,
|
|
22981
|
+
runtime: "Node.js",
|
|
22982
|
+
hostname: hostname2(),
|
|
22983
|
+
agent_runtime: detectAgentRuntime3()
|
|
22984
|
+
};
|
|
22985
|
+
for (const key of ["CLAUDE_CODE_REMOTE", "DEEPLINE_PLUGIN_MODE"]) {
|
|
22986
|
+
const normalized = process.env[key]?.trim();
|
|
22987
|
+
if (normalized) context[key.toLowerCase()] = normalized;
|
|
22988
|
+
}
|
|
22989
|
+
if (process.env.CLAUDE_PROJECT_DIR?.trim()) {
|
|
22990
|
+
context.claude_project_dir_present = "true";
|
|
22991
|
+
}
|
|
22992
|
+
if (process.env.DEEPLINE_PLUGIN_ROOT?.trim()) {
|
|
22993
|
+
context.deepline_plugin_root_present = "true";
|
|
22994
|
+
}
|
|
22995
|
+
if (process.env.DEEPLINE_PLUGIN_SKILLS_DIR?.trim()) {
|
|
22996
|
+
context.deepline_plugin_skills_dir_present = "true";
|
|
22997
|
+
}
|
|
22998
|
+
if (process.env.HOME?.trim().startsWith("/sessions/")) {
|
|
22999
|
+
context.home_scope = "sessions";
|
|
23000
|
+
}
|
|
23001
|
+
return context;
|
|
23002
|
+
}
|
|
23003
|
+
function subcommandFromArgv(argv) {
|
|
23004
|
+
return argv.find((arg) => arg && !arg.startsWith("-")) ?? null;
|
|
23005
|
+
}
|
|
23006
|
+
function commandTokens(argv) {
|
|
23007
|
+
return argv.map((arg) => String(arg ?? "").trim()).filter((arg) => arg && !arg.startsWith("-"));
|
|
23008
|
+
}
|
|
23009
|
+
function isServerLoggedPlayRunStartFailure(input2) {
|
|
23010
|
+
const [subcommand, command] = commandTokens(input2.argv);
|
|
23011
|
+
return subcommand === "plays" && command === "run" && input2.error instanceof DeeplineError && typeof input2.error.statusCode === "number";
|
|
23012
|
+
}
|
|
23013
|
+
function shouldReport(input2) {
|
|
23014
|
+
if (input2.error !== void 0) return true;
|
|
23015
|
+
return input2.exitCode !== null && REPORTABLE_EXIT_CODES.has(input2.exitCode);
|
|
23016
|
+
}
|
|
23017
|
+
function failureCode(input2) {
|
|
23018
|
+
if (input2.error !== void 0) {
|
|
23019
|
+
if (isNetworkFailure(input2.error))
|
|
23020
|
+
return classifyNetworkFailure(input2.error);
|
|
23021
|
+
if (input2.error instanceof DeeplineError && input2.error.code) {
|
|
23022
|
+
return input2.error.code;
|
|
23023
|
+
}
|
|
23024
|
+
if (input2.error instanceof Error && input2.error.name)
|
|
23025
|
+
return input2.error.name;
|
|
23026
|
+
return "CLI_FAILURE";
|
|
23027
|
+
}
|
|
23028
|
+
return input2.exitCode === 4 ? "network_error" : "command_exit";
|
|
23029
|
+
}
|
|
23030
|
+
function resolvedExitCode(input2) {
|
|
23031
|
+
if (typeof input2.exitCode === "number" && Number.isFinite(input2.exitCode)) {
|
|
23032
|
+
return Math.trunc(input2.exitCode);
|
|
23033
|
+
}
|
|
23034
|
+
if (input2.error === void 0) return null;
|
|
23035
|
+
return isNetworkFailure(input2.error) ? 4 : 5;
|
|
23036
|
+
}
|
|
23037
|
+
function resolveSdkCliFailureExitCode(error) {
|
|
23038
|
+
return isNetworkFailure(error) ? 4 : 1;
|
|
23039
|
+
}
|
|
23040
|
+
function resolvedFailureKind(input2) {
|
|
23041
|
+
return input2.error === void 0 ? "command_exit" : "uncaught_exception";
|
|
23042
|
+
}
|
|
23043
|
+
function buildFailureReport(input2) {
|
|
23044
|
+
const durationMs = Math.max(0, Date.now() - input2.startedAtMs);
|
|
23045
|
+
const failureKind = resolvedFailureKind({
|
|
23046
|
+
exitCode: input2.exitCode,
|
|
23047
|
+
error: input2.error
|
|
23048
|
+
});
|
|
23049
|
+
const code = failureCode({ exitCode: input2.exitCode, error: input2.error });
|
|
23050
|
+
const errorBody = input2.error === void 0 ? `SDK CLI command exited ${input2.exitCode ?? "unknown"}` : errorMessage3(input2.error);
|
|
23051
|
+
return {
|
|
23052
|
+
command: sanitizeCommand(input2.argv),
|
|
23053
|
+
subcommand: subcommandFromArgv(input2.argv),
|
|
23054
|
+
error_status: input2.exitCode,
|
|
23055
|
+
error_body: redactFailureText(errorBody),
|
|
23056
|
+
exit_code: input2.exitCode,
|
|
23057
|
+
duration_ms: durationMs,
|
|
23058
|
+
error_class: input2.error instanceof Error ? input2.error.name : null,
|
|
23059
|
+
stack_trace: errorStack(input2.error),
|
|
23060
|
+
failure_kind: failureKind,
|
|
23061
|
+
failure_code: code,
|
|
23062
|
+
failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
|
|
23063
|
+
cli_version: SDK_VERSION,
|
|
23064
|
+
context: {
|
|
23065
|
+
base_url: redactFailureText(input2.baseUrl, 400),
|
|
23066
|
+
command_summary: sanitizeCommand(input2.argv),
|
|
23067
|
+
environment: buildEnvironmentContext(),
|
|
23068
|
+
failure_kind: failureKind,
|
|
23069
|
+
failure_code: code,
|
|
23070
|
+
failure_stage: input2.error === void 0 ? "command_exit" : "cli_main",
|
|
23071
|
+
duration_ms: durationMs,
|
|
23072
|
+
...input2.exitCode !== null ? { exit_code: input2.exitCode } : {}
|
|
23073
|
+
}
|
|
23074
|
+
};
|
|
23075
|
+
}
|
|
23076
|
+
async function maybeReportSdkCliFailure(input2) {
|
|
23077
|
+
if (isFailureReportingDisabled()) return false;
|
|
23078
|
+
if (isServerLoggedPlayRunStartFailure(input2)) return false;
|
|
23079
|
+
const exitCode = resolvedExitCode(input2);
|
|
23080
|
+
if (!shouldReport({ exitCode, error: input2.error })) return false;
|
|
23081
|
+
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
23082
|
+
const apiKey = resolveApiKeyForBaseUrl(baseUrl);
|
|
23083
|
+
if (!apiKey) return false;
|
|
23084
|
+
const controller = new AbortController();
|
|
23085
|
+
const timeout = setTimeout(
|
|
23086
|
+
() => controller.abort(),
|
|
23087
|
+
REPORT_FAILURE_TIMEOUT_MS
|
|
23088
|
+
);
|
|
23089
|
+
try {
|
|
23090
|
+
await fetch(new URL("/api/v2/cli/report-failure", baseUrl), {
|
|
23091
|
+
method: "POST",
|
|
23092
|
+
headers: {
|
|
23093
|
+
Authorization: `Bearer ${apiKey}`,
|
|
23094
|
+
"Content-Type": "application/json",
|
|
23095
|
+
"User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
|
|
23096
|
+
"X-Deepline-Client-Family": "sdk",
|
|
23097
|
+
"X-Deepline-CLI-Family": "sdk",
|
|
23098
|
+
"X-Deepline-Agent-Runtime": detectAgentRuntime3(),
|
|
23099
|
+
"X-Deepline-CLI-Version": SDK_VERSION,
|
|
23100
|
+
"X-Deepline-SDK-Version": SDK_VERSION
|
|
23101
|
+
},
|
|
23102
|
+
body: JSON.stringify(
|
|
23103
|
+
buildFailureReport({
|
|
23104
|
+
argv: input2.argv,
|
|
23105
|
+
startedAtMs: input2.startedAtMs,
|
|
23106
|
+
error: input2.error,
|
|
23107
|
+
exitCode,
|
|
23108
|
+
baseUrl
|
|
23109
|
+
})
|
|
23110
|
+
),
|
|
23111
|
+
signal: controller.signal
|
|
23112
|
+
});
|
|
23113
|
+
return true;
|
|
23114
|
+
} catch {
|
|
23115
|
+
return false;
|
|
23116
|
+
} finally {
|
|
23117
|
+
clearTimeout(timeout);
|
|
23118
|
+
}
|
|
22184
23119
|
}
|
|
22185
23120
|
|
|
22186
23121
|
// src/cli/index.ts
|
|
@@ -22224,8 +23159,8 @@ function topLevelCommandKnown(program, commandName) {
|
|
|
22224
23159
|
);
|
|
22225
23160
|
}
|
|
22226
23161
|
async function runPlayRunnerHealthCheck() {
|
|
22227
|
-
const dir = await mkdtemp2(
|
|
22228
|
-
const file =
|
|
23162
|
+
const dir = await mkdtemp2(join16(tmpdir5(), "deepline-health-play-"));
|
|
23163
|
+
const file = join16(dir, "health-check.play.ts");
|
|
22229
23164
|
try {
|
|
22230
23165
|
await writeFile6(
|
|
22231
23166
|
file,
|
|
@@ -22442,7 +23377,7 @@ Exit codes:
|
|
|
22442
23377
|
`
|
|
22443
23378
|
);
|
|
22444
23379
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
22445
|
-
if (actionCommand.name() === "version" || actionCommand.name() === "update" || isLegacyNoopInvocation()) {
|
|
23380
|
+
if (actionCommand.name() === "version" || actionCommand.name() === "update" || actionCommand.name() === "switch" || isLegacyNoopInvocation()) {
|
|
22446
23381
|
return;
|
|
22447
23382
|
}
|
|
22448
23383
|
if (printStartupPhase) {
|
|
@@ -22490,6 +23425,7 @@ Exit codes:
|
|
|
22490
23425
|
registerLegacyNoopCommands(program);
|
|
22491
23426
|
registerUpdateCommand(program);
|
|
22492
23427
|
registerQuickstartCommands(program);
|
|
23428
|
+
registerSwitchCommands(program);
|
|
22493
23429
|
program.command("preflight").description("Run compact health, auth, and Deepline billing checks.").option("--json", "Force JSON output.").addHelpText(
|
|
22494
23430
|
"after",
|
|
22495
23431
|
`
|
|
@@ -22609,6 +23545,11 @@ Examples:
|
|
|
22609
23545
|
ms: Date.now() - mainStartedAt,
|
|
22610
23546
|
ok: true
|
|
22611
23547
|
});
|
|
23548
|
+
await maybeReportSdkCliFailure({
|
|
23549
|
+
argv: process.argv.slice(2),
|
|
23550
|
+
startedAtMs: mainStartedAt,
|
|
23551
|
+
exitCode: typeof process.exitCode === "number" ? process.exitCode : null
|
|
23552
|
+
});
|
|
22612
23553
|
} catch (error) {
|
|
22613
23554
|
const commanderError = asCommanderError(error);
|
|
22614
23555
|
recordCliTrace({
|
|
@@ -22644,7 +23585,14 @@ Examples:
|
|
|
22644
23585
|
} else {
|
|
22645
23586
|
console.error(`Error: ${String(error)}`);
|
|
22646
23587
|
}
|
|
22647
|
-
|
|
23588
|
+
const failureExitCode = resolveSdkCliFailureExitCode(error);
|
|
23589
|
+
process.exitCode = failureExitCode;
|
|
23590
|
+
await maybeReportSdkCliFailure({
|
|
23591
|
+
argv: process.argv.slice(2),
|
|
23592
|
+
startedAtMs: mainStartedAt,
|
|
23593
|
+
exitCode: failureExitCode,
|
|
23594
|
+
error
|
|
23595
|
+
});
|
|
22648
23596
|
}
|
|
22649
23597
|
process.exitCode = process.exitCode ?? 0;
|
|
22650
23598
|
}
|