deepline 0.3.294 → 0.3.295
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/bundling-sources/sdk/src/compat.ts +35 -7
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +14 -1
- package/dist/cli/auth-EDTRERZP.mjs +31 -0
- package/dist/cli/auth-TDHKB277.js +31 -0
- package/dist/cli/billing-ELIDPILY.js +1749 -0
- package/dist/cli/billing-EXGXJVAU.mjs +1749 -0
- package/dist/cli/chunk-2S5Y3TZP.js +5688 -0
- package/dist/cli/chunk-3KC45UVF.js +150 -0
- package/dist/cli/chunk-3XX5TEA4.js +15 -0
- package/dist/cli/chunk-4MBOJQPL.mjs +0 -0
- package/dist/cli/chunk-4NEFYBF7.js +2240 -0
- package/dist/cli/chunk-5JLQPVNX.js +3056 -0
- package/dist/cli/chunk-5KDKFHZI.js +1269 -0
- package/dist/cli/chunk-6A3TJUWC.js +76 -0
- package/dist/cli/chunk-6O5YZOVN.mjs +890 -0
- package/dist/cli/chunk-7P5AA3SN.js +344 -0
- package/dist/cli/chunk-AYHLUHE5.js +52 -0
- package/dist/cli/chunk-CBVIJSHF.mjs +560 -0
- package/dist/cli/chunk-CFVVCN4A.js +735 -0
- package/dist/cli/chunk-COGTRVWW.js +30 -0
- package/dist/cli/chunk-D4AZUZO4.js +190 -0
- package/dist/cli/chunk-DLIRSHV6.mjs +28 -0
- package/dist/cli/chunk-EACVZI6N.mjs +1995 -0
- package/dist/cli/chunk-EDWJY6T4.mjs +3056 -0
- package/dist/cli/chunk-EXMLR72R.mjs +0 -0
- package/dist/cli/chunk-F35VOIQL.js +890 -0
- package/dist/cli/chunk-GAMFK2ZP.mjs +148 -0
- package/dist/cli/chunk-GHRQQPW2.mjs +2240 -0
- package/dist/cli/chunk-GONRLVV3.js +3440 -0
- package/dist/cli/chunk-GVUJJJ5G.mjs +76 -0
- package/dist/cli/chunk-H7HIDGZ7.mjs +150 -0
- package/dist/cli/chunk-HUZXWOSK.mjs +503 -0
- package/dist/cli/chunk-I5YZLGMG.js +1 -0
- package/dist/cli/chunk-ITOFSXIU.mjs +15 -0
- package/dist/cli/chunk-J5NA2TTX.js +503 -0
- package/dist/cli/chunk-JCS5TPNY.mjs +3440 -0
- package/dist/cli/chunk-JNNOCMQY.js +47 -0
- package/dist/cli/chunk-KRD6H754.mjs +30 -0
- package/dist/cli/chunk-LA46MMTL.js +560 -0
- package/dist/cli/chunk-LDWT7RJK.mjs +190 -0
- package/dist/cli/chunk-M2V26OR5.js +499 -0
- package/dist/cli/chunk-MIMASXO4.mjs +389 -0
- package/dist/cli/chunk-MROPCKW2.mjs +190 -0
- package/dist/cli/chunk-NXDRDJ4K.js +389 -0
- package/dist/cli/chunk-NXGCO4JB.mjs +344 -0
- package/dist/cli/chunk-OG64RBAC.js +6 -0
- package/dist/cli/chunk-OQYEC4GL.mjs +34 -0
- package/dist/cli/chunk-OWJZUE3Y.mjs +103 -0
- package/dist/cli/chunk-SDK3ZA2C.js +103 -0
- package/dist/cli/chunk-SFLARO4Y.mjs +47 -0
- package/dist/cli/chunk-SVIIIYIQ.js +923 -0
- package/dist/cli/chunk-TAC3JIFE.mjs +354 -0
- package/dist/cli/chunk-U3PI7HBM.js +34 -0
- package/dist/cli/chunk-UFS752TU.mjs +52 -0
- package/dist/cli/chunk-UI37DQE7.mjs +499 -0
- package/dist/cli/chunk-UNEMDKXN.js +1 -0
- package/dist/cli/chunk-WB6Z2Q44.mjs +1622 -0
- package/dist/cli/chunk-WJYPBHTE.js +148 -0
- package/dist/cli/chunk-WOHBA5PV.js +354 -0
- package/dist/cli/chunk-WQICC2B3.js +190 -0
- package/dist/cli/chunk-WZYKRD7T.mjs +6 -0
- package/dist/cli/chunk-XL2FO6CW.mjs +5688 -0
- package/dist/cli/chunk-XNYIZMNQ.js +28 -0
- package/dist/cli/chunk-XRK2TZT5.js +1995 -0
- package/dist/cli/chunk-YJYPL5OK.mjs +1269 -0
- package/dist/cli/chunk-YWNDYCXR.mjs +735 -0
- package/dist/cli/chunk-YWWAF7KZ.mjs +923 -0
- package/dist/cli/chunk-ZDO3TO3M.js +1622 -0
- package/dist/cli/client-4JVOYVC5.js +20 -0
- package/dist/cli/client-EEN2IJN2.mjs +20 -0
- package/dist/cli/csv-BOHBEP2I.mjs +585 -0
- package/dist/cli/csv-S5NYRUEX.js +585 -0
- package/dist/cli/db-CLODWCOB.mjs +486 -0
- package/dist/cli/db-F6PKFOOV.js +486 -0
- package/dist/cli/executions-5TQAKLJU.js +28 -0
- package/dist/cli/executions-MAKW6QOY.mjs +28 -0
- package/dist/cli/feedback-GV4QJFEN.js +107 -0
- package/dist/cli/feedback-XY24TW7H.mjs +107 -0
- package/dist/cli/getting-started.js +18 -76
- package/dist/cli/getting-started.mjs +10 -37
- package/dist/cli/git-6S2C22T5.js +23 -0
- package/dist/cli/git-OZLBD5HH.mjs +23 -0
- package/dist/cli/index.js +291 -54294
- package/dist/cli/index.mjs +209 -54352
- package/dist/cli/legacy-noops-LLCGDPQE.js +157 -0
- package/dist/cli/legacy-noops-TYH3TW2D.mjs +157 -0
- package/dist/cli/monitors-CABMB4DW.js +3251 -0
- package/dist/cli/monitors-TTD55HMK.mjs +3251 -0
- package/dist/cli/org-MSF3RG76.js +849 -0
- package/dist/cli/org-SFJ77MWY.mjs +849 -0
- package/dist/cli/play-NXHJSCKL.mjs +18439 -0
- package/dist/cli/play-OKSFNNO4.js +18439 -0
- package/dist/cli/providers-QC2ERERL.js +95 -0
- package/dist/cli/providers-TIIAZEUA.mjs +95 -0
- package/dist/cli/proxy-env-EBANZHPV.js +161 -0
- package/dist/cli/proxy-env-M4ZHXHRL.mjs +161 -0
- package/dist/cli/quickstart-ICANHLMI.js +69 -0
- package/dist/cli/quickstart-KWUOP3FI.mjs +69 -0
- package/dist/cli/search-U5H2FDOO.mjs +18 -0
- package/dist/cli/search-XQRQOLWS.js +18 -0
- package/dist/cli/secrets-3J6YTWFA.mjs +400 -0
- package/dist/cli/secrets-YNMNZNOM.js +400 -0
- package/dist/cli/sessions-CLEES4L6.mjs +15 -0
- package/dist/cli/sessions-J6VR47FG.js +15 -0
- package/dist/cli/settings-EEKZMTY4.js +846 -0
- package/dist/cli/settings-UVLOQZRH.mjs +846 -0
- package/dist/cli/setup-DLMDXPGJ.mjs +1257 -0
- package/dist/cli/setup-HC4Q7QM4.js +1257 -0
- package/dist/cli/skills-33PHFBSB.mjs +28 -0
- package/dist/cli/skills-LFRY3KPX.js +28 -0
- package/dist/cli/tools-3COEPKML.mjs +2955 -0
- package/dist/cli/tools-QXEVCN45.js +2955 -0
- package/dist/cli/update-LSAC4FF5.mjs +28 -0
- package/dist/cli/update-NXHSPYXA.js +28 -0
- package/dist/cli/workflow-7S44OECF.mjs +1936 -0
- package/dist/cli/workflow-QPZWYSZE.js +1936 -0
- package/dist/helpers.js +6 -32
- package/dist/helpers.mjs +2 -0
- package/dist/index.js +1972 -12825
- package/dist/index.mjs +1910 -12672
- package/dist/install-integrity.json +110 -0
- package/dist/monitor-fleet-contract.js +31 -730
- package/dist/monitor-fleet-contract.mjs +18 -667
- package/dist/monitor-input-contract.js +10 -390
- package/dist/monitor-input-contract.mjs +7 -348
- package/dist/plays/bundle-play-file.mjs +13 -5178
- package/dist/plays/csv-data-input.js +5 -3132
- package/dist/plays/csv-data-input.mjs +5 -3105
- package/dist/plays/run-request.js +56 -664
- package/dist/plays/run-request.mjs +30 -588
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +5 -212
- package/dist/release.mjs +5 -185
- package/package.json +1 -1
|
@@ -0,0 +1,923 @@
|
|
|
1
|
+
import {
|
|
2
|
+
argsWantJson,
|
|
3
|
+
openInBrowser,
|
|
4
|
+
printCommandEnvelope
|
|
5
|
+
} from "./chunk-6O5YZOVN.mjs";
|
|
6
|
+
import {
|
|
7
|
+
ACTIVE_ORG_ID_ENV,
|
|
8
|
+
ACTIVE_ORG_NAME_ENV,
|
|
9
|
+
API_KEY_ENV,
|
|
10
|
+
FEATURES_ENV,
|
|
11
|
+
HOST_URL_ENV,
|
|
12
|
+
SDK_API_CONTRACT,
|
|
13
|
+
SDK_VERSION,
|
|
14
|
+
autoDetectBaseUrl,
|
|
15
|
+
detectAgentRuntime,
|
|
16
|
+
featureEnvValues,
|
|
17
|
+
getResolvedProjectAuthSource,
|
|
18
|
+
hostConfigDirPath,
|
|
19
|
+
hostEnvFilePath,
|
|
20
|
+
resolveApiKeyForBaseUrl,
|
|
21
|
+
resolveCliCommandContext,
|
|
22
|
+
resolveGlobalApiKeyForBaseUrl,
|
|
23
|
+
resolveProjectApiKeyForBaseUrl,
|
|
24
|
+
resolveProjectPinTarget,
|
|
25
|
+
saveHostEnvValues,
|
|
26
|
+
saveProjectDeeplineEnvValues,
|
|
27
|
+
storedHostApiKey
|
|
28
|
+
} from "./chunk-EACVZI6N.mjs";
|
|
29
|
+
|
|
30
|
+
// src/cli/commands/auth.ts
|
|
31
|
+
import {
|
|
32
|
+
existsSync,
|
|
33
|
+
mkdirSync,
|
|
34
|
+
readFileSync,
|
|
35
|
+
rmSync,
|
|
36
|
+
writeFileSync
|
|
37
|
+
} from "fs";
|
|
38
|
+
import { hostname } from "os";
|
|
39
|
+
import { dirname, join } from "path";
|
|
40
|
+
var EXIT_OK = 0;
|
|
41
|
+
var EXIT_AUTH = 3;
|
|
42
|
+
var EXIT_SERVER = 5;
|
|
43
|
+
var EXIT_WORKSPACE_MISMATCH = 6;
|
|
44
|
+
var AUTH_HTTP_TIMEOUT_MS = 1e4;
|
|
45
|
+
var AUTH_REGISTER_HTTP_TIMEOUT_MS = 3e4;
|
|
46
|
+
function statusBannerLine(value) {
|
|
47
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
48
|
+
const banner = value;
|
|
49
|
+
if (typeof banner.message !== "string") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return `Service notice: ${banner.message}`;
|
|
53
|
+
}
|
|
54
|
+
function envFilePath(baseUrl) {
|
|
55
|
+
return hostEnvFilePath(baseUrl);
|
|
56
|
+
}
|
|
57
|
+
function normalizeAuthScope(value) {
|
|
58
|
+
if (!value || value === "global") return "global";
|
|
59
|
+
if (value === "folder") return "folder";
|
|
60
|
+
throw new Error("--auth-scope must be one of: folder, global");
|
|
61
|
+
}
|
|
62
|
+
function parseAuthScope(args) {
|
|
63
|
+
const index = args.indexOf("--auth-scope");
|
|
64
|
+
return normalizeAuthScope(index >= 0 ? args[index + 1] : void 0);
|
|
65
|
+
}
|
|
66
|
+
function pendingClaimPath(baseUrl, scope) {
|
|
67
|
+
if (scope === "folder") {
|
|
68
|
+
const target = resolveProjectPinTarget();
|
|
69
|
+
if (!target.ok) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Cowork project folder is ambiguous. Candidate folders: ${target.candidates.join(
|
|
72
|
+
", "
|
|
73
|
+
)}. Set CLAUDE_PROJECT_DIR or cd into the intended project folder.`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return join(target.dir, ".deepline", "setup", "pending-auth.json");
|
|
77
|
+
}
|
|
78
|
+
return `${hostConfigDirPath(baseUrl)}/pending-auth.json`;
|
|
79
|
+
}
|
|
80
|
+
function legacyPendingClaimTokenPath(baseUrl) {
|
|
81
|
+
return `${hostConfigDirPath(baseUrl)}/pending-claim-token`;
|
|
82
|
+
}
|
|
83
|
+
function savePendingClaim(baseUrl, claim) {
|
|
84
|
+
const filePath = pendingClaimPath(baseUrl, claim.scope);
|
|
85
|
+
const dir = dirname(filePath);
|
|
86
|
+
if (!existsSync(dir)) {
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
writeFileSync(filePath, `${JSON.stringify(claim, null, 2)}
|
|
90
|
+
`, {
|
|
91
|
+
encoding: "utf-8",
|
|
92
|
+
mode: 384
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function resolvePendingAuthClaimForResume(baseUrl, requestedScope) {
|
|
96
|
+
if (requestedScope) {
|
|
97
|
+
const claim = readPendingAuthClaim(baseUrl, requestedScope);
|
|
98
|
+
return { claim, scope: claim?.scope ?? requestedScope };
|
|
99
|
+
}
|
|
100
|
+
const folder = readPendingAuthClaim(baseUrl, "folder");
|
|
101
|
+
if (folder) return { claim: folder, scope: folder.scope };
|
|
102
|
+
const global = readPendingAuthClaim(baseUrl, "global");
|
|
103
|
+
return { claim: global, scope: global?.scope ?? "global" };
|
|
104
|
+
}
|
|
105
|
+
function readPendingAuthClaim(baseUrl, scope) {
|
|
106
|
+
let filePath;
|
|
107
|
+
try {
|
|
108
|
+
filePath = pendingClaimPath(baseUrl, scope);
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (!existsSync(filePath)) {
|
|
113
|
+
if (scope !== "global") return null;
|
|
114
|
+
try {
|
|
115
|
+
const claimToken = readFileSync(
|
|
116
|
+
legacyPendingClaimTokenPath(baseUrl),
|
|
117
|
+
"utf-8"
|
|
118
|
+
).trim();
|
|
119
|
+
return claimToken ? {
|
|
120
|
+
claimToken,
|
|
121
|
+
claimUrl: `${baseUrl}/api/v2/auth/cli/claim/${encodeURIComponent(claimToken)}`,
|
|
122
|
+
scope
|
|
123
|
+
} : null;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
130
|
+
const claimToken = typeof parsed.claimToken === "string" ? parsed.claimToken.trim() : "";
|
|
131
|
+
if (!claimToken) return null;
|
|
132
|
+
return {
|
|
133
|
+
claimToken,
|
|
134
|
+
claimUrl: typeof parsed.claimUrl === "string" && parsed.claimUrl.trim() ? parsed.claimUrl.trim() : `${baseUrl}/api/v2/auth/cli/claim/${encodeURIComponent(claimToken)}`,
|
|
135
|
+
orgName: typeof parsed.orgName === "string" ? parsed.orgName.trim() : void 0,
|
|
136
|
+
// The record's own scope, when it has one. `register` writes this field
|
|
137
|
+
// precisely so a resume can learn where the credential belongs; reading
|
|
138
|
+
// back the caller's argument instead made it decorative, and a record
|
|
139
|
+
// found by searching would have reported the scope of the search rather
|
|
140
|
+
// than the scope it was created with.
|
|
141
|
+
scope: parsed.scope === "folder" || parsed.scope === "global" ? parsed.scope : scope
|
|
142
|
+
};
|
|
143
|
+
} catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function clearPendingClaim(baseUrl, scope) {
|
|
148
|
+
try {
|
|
149
|
+
rmSync(pendingClaimPath(baseUrl, scope), { force: true });
|
|
150
|
+
if (scope === "global") {
|
|
151
|
+
rmSync(legacyPendingClaimTokenPath(baseUrl), { force: true });
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function clearSatisfiedPendingClaim(baseUrl, scope, connectedWorkspaceName) {
|
|
157
|
+
const pending = readPendingAuthClaim(baseUrl, scope);
|
|
158
|
+
if (!pending) return;
|
|
159
|
+
if (pending.orgName && pending.orgName.toLowerCase() !== connectedWorkspaceName?.trim().toLowerCase()) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
clearPendingClaim(baseUrl, scope);
|
|
163
|
+
}
|
|
164
|
+
function parseRegisterWaitMode(args) {
|
|
165
|
+
let mode = "auto";
|
|
166
|
+
for (let i = 0; i < args.length; i++) {
|
|
167
|
+
if (args[i] === "--no-wait") {
|
|
168
|
+
mode = "no";
|
|
169
|
+
} else if (args[i] === "--wait" && args[i + 1]) {
|
|
170
|
+
const value = args[++i].trim().toLowerCase();
|
|
171
|
+
if (value === "auto" || value === "yes" || value === "no") {
|
|
172
|
+
mode = value;
|
|
173
|
+
} else {
|
|
174
|
+
throw new Error("--wait must be one of: auto, yes, no");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return mode;
|
|
179
|
+
}
|
|
180
|
+
function shouldWaitForRegisterClaim(mode) {
|
|
181
|
+
if (mode === "yes") return true;
|
|
182
|
+
if (mode === "no") return false;
|
|
183
|
+
return detectAgentRuntime() !== "claude_cowork";
|
|
184
|
+
}
|
|
185
|
+
function saveEnvValues(values, baseUrl, scope) {
|
|
186
|
+
const filtered = {
|
|
187
|
+
...values[HOST_URL_ENV] ? { [HOST_URL_ENV]: values[HOST_URL_ENV] } : {},
|
|
188
|
+
...values[API_KEY_ENV] ? { [API_KEY_ENV]: values[API_KEY_ENV] } : {},
|
|
189
|
+
...values[ACTIVE_ORG_ID_ENV] ? { [ACTIVE_ORG_ID_ENV]: values[ACTIVE_ORG_ID_ENV] } : {},
|
|
190
|
+
...Object.prototype.hasOwnProperty.call(values, ACTIVE_ORG_NAME_ENV) ? { [ACTIVE_ORG_NAME_ENV]: values[ACTIVE_ORG_NAME_ENV] } : {},
|
|
191
|
+
// An explicit empty list must reach the store: it clears a stale line.
|
|
192
|
+
...Object.prototype.hasOwnProperty.call(values, FEATURES_ENV) ? { [FEATURES_ENV]: values[FEATURES_ENV] } : {}
|
|
193
|
+
};
|
|
194
|
+
if (scope === "folder") {
|
|
195
|
+
saveProjectDeeplineEnvValues(filtered);
|
|
196
|
+
} else {
|
|
197
|
+
saveHostEnvValues(baseUrl, filtered);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function activeOrgEnvValues(data) {
|
|
201
|
+
const orgId = typeof data.org_id === "string" ? data.org_id.trim() : "";
|
|
202
|
+
if (!orgId) return {};
|
|
203
|
+
const orgName = typeof data.org_name === "string" ? data.org_name.trim() : "";
|
|
204
|
+
return {
|
|
205
|
+
[ACTIVE_ORG_ID_ENV]: orgId,
|
|
206
|
+
[ACTIVE_ORG_NAME_ENV]: orgName,
|
|
207
|
+
...featureEnvValues(data)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async function httpJson(method, url, apiKey, body, timeoutMs = AUTH_HTTP_TIMEOUT_MS) {
|
|
211
|
+
const headers = {
|
|
212
|
+
"Content-Type": "application/json",
|
|
213
|
+
"User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
|
|
214
|
+
"X-Deepline-Client-Family": "sdk",
|
|
215
|
+
"X-Deepline-CLI-Family": "sdk",
|
|
216
|
+
"X-Deepline-Agent-Runtime": detectAgentRuntime(),
|
|
217
|
+
"X-Deepline-CLI-Version": SDK_VERSION,
|
|
218
|
+
"X-Deepline-SDK-Version": SDK_VERSION,
|
|
219
|
+
"X-Deepline-API-Contract": SDK_API_CONTRACT
|
|
220
|
+
};
|
|
221
|
+
if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
|
|
222
|
+
const bypassToken = process.env.VERCEL_PROTECTION_BYPASS_TOKEN?.trim();
|
|
223
|
+
if (bypassToken) headers["x-vercel-protection-bypass"] = bypassToken;
|
|
224
|
+
let lastError = null;
|
|
225
|
+
for (const candidateUrl of buildCandidateUrls(url)) {
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
let requestTimedOut = false;
|
|
228
|
+
const timeout = setTimeout(() => {
|
|
229
|
+
requestTimedOut = true;
|
|
230
|
+
controller.abort();
|
|
231
|
+
}, timeoutMs);
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(candidateUrl, {
|
|
234
|
+
method,
|
|
235
|
+
headers,
|
|
236
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
237
|
+
signal: controller.signal
|
|
238
|
+
});
|
|
239
|
+
let data;
|
|
240
|
+
try {
|
|
241
|
+
data = await response.json();
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (controller.signal.aborted) throw error;
|
|
244
|
+
data = {};
|
|
245
|
+
}
|
|
246
|
+
return { status: response.status, data };
|
|
247
|
+
} catch (error) {
|
|
248
|
+
lastError = error;
|
|
249
|
+
if (requestTimedOut && method !== "GET") break;
|
|
250
|
+
} finally {
|
|
251
|
+
clearTimeout(timeout);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
255
|
+
}
|
|
256
|
+
async function reportSetupCompleted(input) {
|
|
257
|
+
const apiKey = input.authScope === "folder" ? resolveProjectApiKeyForBaseUrl(input.baseUrl) : resolveGlobalApiKeyForBaseUrl(input.baseUrl);
|
|
258
|
+
if (!apiKey) return;
|
|
259
|
+
try {
|
|
260
|
+
await httpJson(
|
|
261
|
+
"POST",
|
|
262
|
+
`${input.baseUrl}/api/v2/telemetry/setup-completed`,
|
|
263
|
+
apiKey,
|
|
264
|
+
{
|
|
265
|
+
scope: input.scope,
|
|
266
|
+
resumed: input.resumed,
|
|
267
|
+
installed_agent_count: input.installedAgentCount
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
} catch {
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function buildCandidateUrls(url) {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = new URL(url);
|
|
276
|
+
const candidates = [url];
|
|
277
|
+
const configuredInternalOrigin = process.env.DEEPLINE_INTERNAL_APP_URL || process.env.WORKTREE_INTERNAL_APP_URL || (process.env.PORT ? `http://127.0.0.1:${process.env.PORT}` : "");
|
|
278
|
+
if (parsed.hostname === "localhost") {
|
|
279
|
+
const loopback = new URL(url);
|
|
280
|
+
loopback.hostname = "127.0.0.1";
|
|
281
|
+
candidates.push(loopback.toString());
|
|
282
|
+
}
|
|
283
|
+
if (parsed.hostname.endsWith(".localhost") && configuredInternalOrigin) {
|
|
284
|
+
const internal = new URL(url);
|
|
285
|
+
const origin = new URL(configuredInternalOrigin);
|
|
286
|
+
internal.protocol = origin.protocol;
|
|
287
|
+
internal.hostname = origin.hostname;
|
|
288
|
+
internal.port = origin.port;
|
|
289
|
+
candidates.push(internal.toString());
|
|
290
|
+
}
|
|
291
|
+
return [...new Set(candidates)];
|
|
292
|
+
} catch {
|
|
293
|
+
return [url];
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function sleep(ms) {
|
|
297
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
298
|
+
}
|
|
299
|
+
function printDeeplineLogo() {
|
|
300
|
+
if (process.stdout.isTTY && (process.stdout.columns ?? 80) >= 70) {
|
|
301
|
+
console.log(
|
|
302
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"
|
|
303
|
+
);
|
|
304
|
+
console.log(
|
|
305
|
+
" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D"
|
|
306
|
+
);
|
|
307
|
+
console.log(
|
|
308
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557"
|
|
309
|
+
);
|
|
310
|
+
console.log(
|
|
311
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D"
|
|
312
|
+
);
|
|
313
|
+
console.log(
|
|
314
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557"
|
|
315
|
+
);
|
|
316
|
+
console.log(
|
|
317
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
318
|
+
);
|
|
319
|
+
console.log("");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
console.log("DEEPLINE");
|
|
323
|
+
}
|
|
324
|
+
async function claimInstallBonus(baseUrl, apiKey) {
|
|
325
|
+
try {
|
|
326
|
+
const { status, data } = await httpJson(
|
|
327
|
+
"POST",
|
|
328
|
+
`${baseUrl}/api/v2/billing/claim-install-bonus`,
|
|
329
|
+
apiKey,
|
|
330
|
+
{}
|
|
331
|
+
);
|
|
332
|
+
if (status !== 200) return;
|
|
333
|
+
const granted = typeof data.credits_granted === "number" ? data.credits_granted : 0;
|
|
334
|
+
const alreadyGranted = data.already_granted === true;
|
|
335
|
+
if (granted > 0) {
|
|
336
|
+
console.log(`
|
|
337
|
+
\u2022 +${granted} free credits added to your account!`);
|
|
338
|
+
} else if (alreadyGranted) {
|
|
339
|
+
console.log("\n \u2022 Free credits already applied to your account.");
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async function printClaimSuccessBanner(baseUrl, apiKey, statusData) {
|
|
345
|
+
console.log("");
|
|
346
|
+
printDeeplineLogo();
|
|
347
|
+
console.log("\u2713 All set! Your CLI is connected.");
|
|
348
|
+
await claimInstallBonus(baseUrl, apiKey);
|
|
349
|
+
if (statusData.org_name) {
|
|
350
|
+
console.log(` \u2022 Signed in with organization: ${statusData.org_name}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async function handleRegister(args) {
|
|
354
|
+
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
355
|
+
let orgName = "";
|
|
356
|
+
let orgId = "";
|
|
357
|
+
let agentName = "";
|
|
358
|
+
let waitMode;
|
|
359
|
+
let authScope;
|
|
360
|
+
try {
|
|
361
|
+
waitMode = parseRegisterWaitMode(args);
|
|
362
|
+
authScope = args.includes("--auth-scope") ? parseAuthScope(args) : args.includes("--org-id") ? "folder" : "global";
|
|
363
|
+
if (authScope === "folder") {
|
|
364
|
+
pendingClaimPath(baseUrl, authScope);
|
|
365
|
+
}
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
368
|
+
return 2;
|
|
369
|
+
}
|
|
370
|
+
for (let i = 0; i < args.length; i++) {
|
|
371
|
+
if (args[i] === "--org-name" && args[i + 1]) orgName = args[++i];
|
|
372
|
+
else if (args[i] === "--org-id" && args[i + 1]) orgId = args[++i];
|
|
373
|
+
else if (args[i] === "--agent-name" && args[i + 1]) agentName = args[++i];
|
|
374
|
+
}
|
|
375
|
+
if (!agentName) {
|
|
376
|
+
try {
|
|
377
|
+
agentName = hostname() || "Deepline CLI (TS)";
|
|
378
|
+
} catch {
|
|
379
|
+
agentName = "Deepline CLI (TS)";
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const payload = {};
|
|
383
|
+
if (orgName) payload.org_name = orgName;
|
|
384
|
+
if (orgId) payload.org_id = orgId;
|
|
385
|
+
if (agentName) payload.agent_name = agentName;
|
|
386
|
+
const { status, data } = await httpJson(
|
|
387
|
+
"POST",
|
|
388
|
+
`${baseUrl}/api/v2/auth/cli/register`,
|
|
389
|
+
null,
|
|
390
|
+
payload,
|
|
391
|
+
AUTH_REGISTER_HTTP_TIMEOUT_MS
|
|
392
|
+
);
|
|
393
|
+
if (status >= 400) {
|
|
394
|
+
console.error(`Auth register failed (status ${status}).`);
|
|
395
|
+
if (data.error) console.error(String(data.error));
|
|
396
|
+
return EXIT_SERVER;
|
|
397
|
+
}
|
|
398
|
+
const claimUrl = String(data.claim_url || "");
|
|
399
|
+
const claimToken = String(data.claim_token || "");
|
|
400
|
+
if (claimToken) {
|
|
401
|
+
savePendingClaim(baseUrl, {
|
|
402
|
+
claimToken,
|
|
403
|
+
claimUrl,
|
|
404
|
+
scope: authScope,
|
|
405
|
+
...orgName ? { orgName } : {}
|
|
406
|
+
});
|
|
407
|
+
saveEnvValues(
|
|
408
|
+
{
|
|
409
|
+
[HOST_URL_ENV]: baseUrl
|
|
410
|
+
},
|
|
411
|
+
baseUrl,
|
|
412
|
+
authScope
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
if (claimUrl) {
|
|
416
|
+
const shouldOpen = waitMode !== "no" && detectAgentRuntime() !== "claude_cowork";
|
|
417
|
+
console.log(
|
|
418
|
+
shouldOpen ? " Opening approval page in your browser." : " Open this approval page in your browser:"
|
|
419
|
+
);
|
|
420
|
+
console.log(` ${claimUrl}`);
|
|
421
|
+
if (shouldOpen) openInBrowser(claimUrl);
|
|
422
|
+
}
|
|
423
|
+
if (data.cli_message) {
|
|
424
|
+
console.log(String(data.cli_message));
|
|
425
|
+
}
|
|
426
|
+
if (!shouldWaitForRegisterClaim(waitMode)) return EXIT_OK;
|
|
427
|
+
if (!claimToken) {
|
|
428
|
+
console.error("Missing claim token from register response.");
|
|
429
|
+
return EXIT_SERVER;
|
|
430
|
+
}
|
|
431
|
+
while (true) {
|
|
432
|
+
const { status: s, data: statusData } = await httpJson(
|
|
433
|
+
"POST",
|
|
434
|
+
`${baseUrl}/api/v2/auth/cli/status`,
|
|
435
|
+
null,
|
|
436
|
+
{ claim_token: claimToken, reveal: true }
|
|
437
|
+
);
|
|
438
|
+
if (s === 401 || s === 403) {
|
|
439
|
+
clearPendingClaim(baseUrl, authScope);
|
|
440
|
+
console.log("Status: unauthorized");
|
|
441
|
+
return EXIT_AUTH;
|
|
442
|
+
}
|
|
443
|
+
if (s >= 500 || s === 0 || s === 400) {
|
|
444
|
+
await sleep(2e3);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (s >= 400) {
|
|
448
|
+
console.error(`Auth status error (status ${s}).`);
|
|
449
|
+
return EXIT_SERVER;
|
|
450
|
+
}
|
|
451
|
+
const state = String(statusData.status || "").toLowerCase();
|
|
452
|
+
if (statusData.code === "REQUESTED_WORKSPACE_NOT_IN_ACCOUNT") {
|
|
453
|
+
clearPendingClaim(baseUrl, authScope);
|
|
454
|
+
console.error(
|
|
455
|
+
"The requested workspace does not exist in this account. Connect to an existing workspace first; then use `deepline org create` if you need a new one."
|
|
456
|
+
);
|
|
457
|
+
return EXIT_WORKSPACE_MISMATCH;
|
|
458
|
+
}
|
|
459
|
+
if (state === "claimed") {
|
|
460
|
+
const apiKey = String(statusData.api_key || "");
|
|
461
|
+
if (apiKey) {
|
|
462
|
+
saveEnvValues(
|
|
463
|
+
{
|
|
464
|
+
[HOST_URL_ENV]: baseUrl,
|
|
465
|
+
[API_KEY_ENV]: apiKey,
|
|
466
|
+
...activeOrgEnvValues(statusData)
|
|
467
|
+
},
|
|
468
|
+
baseUrl,
|
|
469
|
+
authScope
|
|
470
|
+
);
|
|
471
|
+
clearPendingClaim(baseUrl, authScope);
|
|
472
|
+
await printClaimSuccessBanner(baseUrl, apiKey, statusData);
|
|
473
|
+
return EXIT_OK;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (state === "expired") {
|
|
477
|
+
clearPendingClaim(baseUrl, authScope);
|
|
478
|
+
console.log(
|
|
479
|
+
"That approval link expired. Please run: deepline auth register"
|
|
480
|
+
);
|
|
481
|
+
return EXIT_AUTH;
|
|
482
|
+
}
|
|
483
|
+
await sleep(2e3);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async function handleWait(args) {
|
|
487
|
+
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
488
|
+
let timeoutSeconds = 300;
|
|
489
|
+
let requestedScope;
|
|
490
|
+
if (args.includes("--auth-scope")) {
|
|
491
|
+
try {
|
|
492
|
+
requestedScope = parseAuthScope(args);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
495
|
+
return 2;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
for (let i = 0; i < args.length; i++) {
|
|
499
|
+
if (args[i] === "--timeout" && args[i + 1]) {
|
|
500
|
+
const parsed = Number.parseInt(args[++i], 10);
|
|
501
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
502
|
+
timeoutSeconds = parsed;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
const resumed = resolvePendingAuthClaimForResume(baseUrl, requestedScope);
|
|
507
|
+
const pendingClaim = resumed.claim;
|
|
508
|
+
const authScope = resumed.scope;
|
|
509
|
+
const claimToken = pendingClaim?.claimToken ?? "";
|
|
510
|
+
if (!pendingClaim) {
|
|
511
|
+
const scopedApiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : resolveGlobalApiKeyForBaseUrl(baseUrl);
|
|
512
|
+
if (scopedApiKey) {
|
|
513
|
+
console.log("Already connected.");
|
|
514
|
+
return EXIT_OK;
|
|
515
|
+
}
|
|
516
|
+
console.error("No pending approval. Run: deepline auth register --wait no");
|
|
517
|
+
return EXIT_AUTH;
|
|
518
|
+
}
|
|
519
|
+
const deadline = Date.now() + timeoutSeconds * 1e3;
|
|
520
|
+
while (Date.now() < deadline) {
|
|
521
|
+
const { status, data } = await httpJson(
|
|
522
|
+
"POST",
|
|
523
|
+
`${baseUrl}/api/v2/auth/cli/status`,
|
|
524
|
+
null,
|
|
525
|
+
{ claim_token: claimToken, reveal: true },
|
|
526
|
+
Math.min(AUTH_HTTP_TIMEOUT_MS, Math.max(1, deadline - Date.now()))
|
|
527
|
+
);
|
|
528
|
+
if (status === 401 || status === 403) {
|
|
529
|
+
clearPendingClaim(baseUrl, authScope);
|
|
530
|
+
console.error("Claim is invalid. Run: deepline auth register");
|
|
531
|
+
return EXIT_AUTH;
|
|
532
|
+
}
|
|
533
|
+
if (status >= 500 || status === 0 || status === 400) {
|
|
534
|
+
await sleep(Math.min(2e3, Math.max(0, deadline - Date.now())));
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (status >= 400) {
|
|
538
|
+
console.error(`Auth status error (status ${status}).`);
|
|
539
|
+
return EXIT_SERVER;
|
|
540
|
+
}
|
|
541
|
+
const state = String(data.status || "").toLowerCase();
|
|
542
|
+
if (data.code === "REQUESTED_WORKSPACE_NOT_IN_ACCOUNT") {
|
|
543
|
+
clearPendingClaim(baseUrl, authScope);
|
|
544
|
+
console.error(
|
|
545
|
+
"The requested workspace does not exist in this account. Connect to an existing workspace first; then use `deepline org create` if you need a new one."
|
|
546
|
+
);
|
|
547
|
+
return EXIT_WORKSPACE_MISMATCH;
|
|
548
|
+
}
|
|
549
|
+
if (state === "claimed") {
|
|
550
|
+
const apiKey = String(data.api_key || "");
|
|
551
|
+
if (apiKey) {
|
|
552
|
+
saveEnvValues(
|
|
553
|
+
{
|
|
554
|
+
[HOST_URL_ENV]: baseUrl,
|
|
555
|
+
[API_KEY_ENV]: apiKey,
|
|
556
|
+
...activeOrgEnvValues(data)
|
|
557
|
+
},
|
|
558
|
+
baseUrl,
|
|
559
|
+
authScope
|
|
560
|
+
);
|
|
561
|
+
clearPendingClaim(baseUrl, authScope);
|
|
562
|
+
await printClaimSuccessBanner(baseUrl, apiKey, data);
|
|
563
|
+
return EXIT_OK;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (state === "expired") {
|
|
567
|
+
clearPendingClaim(baseUrl, authScope);
|
|
568
|
+
console.error("That approval link expired. Run: deepline auth register");
|
|
569
|
+
return EXIT_AUTH;
|
|
570
|
+
}
|
|
571
|
+
await sleep(Math.min(2e3, Math.max(0, deadline - Date.now())));
|
|
572
|
+
}
|
|
573
|
+
console.error(
|
|
574
|
+
"Still pending. Approve the browser link, then run: deepline auth wait"
|
|
575
|
+
);
|
|
576
|
+
return EXIT_AUTH;
|
|
577
|
+
}
|
|
578
|
+
async function handleStatus(args) {
|
|
579
|
+
const baseUrl = autoDetectBaseUrl().replace(/\/$/, "");
|
|
580
|
+
const reveal = args.includes("--reveal");
|
|
581
|
+
const jsonOutput = argsWantJson(args);
|
|
582
|
+
const hasExplicitAuthScope = args.includes("--auth-scope");
|
|
583
|
+
let authScope = null;
|
|
584
|
+
if (hasExplicitAuthScope) {
|
|
585
|
+
try {
|
|
586
|
+
authScope = parseAuthScope(args);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
589
|
+
return 2;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
let hostStatusPayload = null;
|
|
593
|
+
const hostLines = [];
|
|
594
|
+
try {
|
|
595
|
+
const { status: hStatus, data: hData } = await httpJson(
|
|
596
|
+
"GET",
|
|
597
|
+
`${baseUrl}/api/v2/health`,
|
|
598
|
+
null
|
|
599
|
+
);
|
|
600
|
+
if (hStatus === 200) {
|
|
601
|
+
hostStatusPayload = {
|
|
602
|
+
host: baseUrl,
|
|
603
|
+
hostStatus: hData.status || "ok",
|
|
604
|
+
hostVersion: hData.version || "(unknown)",
|
|
605
|
+
status_banner: hData.status_banner ?? null
|
|
606
|
+
};
|
|
607
|
+
hostLines.push(`Host: ${baseUrl}`);
|
|
608
|
+
hostLines.push(`Host status: ${hData.status || "ok"}`);
|
|
609
|
+
hostLines.push(`Host version: ${hData.version || "(unknown)"}`);
|
|
610
|
+
const bannerLine = statusBannerLine(hData.status_banner);
|
|
611
|
+
if (bannerLine) hostLines.push(bannerLine);
|
|
612
|
+
}
|
|
613
|
+
} catch {
|
|
614
|
+
hostStatusPayload = {
|
|
615
|
+
host: baseUrl,
|
|
616
|
+
hostStatus: "unreachable",
|
|
617
|
+
hostVersion: null
|
|
618
|
+
};
|
|
619
|
+
hostLines.push(`Host: ${baseUrl} (unreachable)`);
|
|
620
|
+
}
|
|
621
|
+
const apiKey = authScope === "folder" ? resolveProjectApiKeyForBaseUrl(baseUrl) : authScope === "global" ? resolveGlobalApiKeyForBaseUrl(baseUrl) : resolveApiKeyForBaseUrl(baseUrl);
|
|
622
|
+
if (!apiKey) {
|
|
623
|
+
const { claim: pendingClaim } = resolvePendingAuthClaimForResume(
|
|
624
|
+
baseUrl,
|
|
625
|
+
authScope ?? void 0
|
|
626
|
+
);
|
|
627
|
+
if (pendingClaim) {
|
|
628
|
+
printCommandEnvelope(
|
|
629
|
+
{
|
|
630
|
+
...hostStatusPayload ?? { host: baseUrl },
|
|
631
|
+
status: "pending",
|
|
632
|
+
connected: false,
|
|
633
|
+
authorization_url: pendingClaim.claimUrl || null,
|
|
634
|
+
next: "deepline auth wait",
|
|
635
|
+
render: {
|
|
636
|
+
sections: [
|
|
637
|
+
{
|
|
638
|
+
title: "auth status",
|
|
639
|
+
lines: [...hostLines, "Status: pending"]
|
|
640
|
+
}
|
|
641
|
+
],
|
|
642
|
+
actions: [{ label: "Run", command: "deepline auth wait" }]
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
{ json: jsonOutput }
|
|
646
|
+
);
|
|
647
|
+
return EXIT_OK;
|
|
648
|
+
}
|
|
649
|
+
printCommandEnvelope(
|
|
650
|
+
{
|
|
651
|
+
...hostStatusPayload ?? { host: baseUrl },
|
|
652
|
+
status: "not connected",
|
|
653
|
+
connected: false,
|
|
654
|
+
next: "deepline auth register --wait auto && deepline auth wait",
|
|
655
|
+
render: {
|
|
656
|
+
sections: [
|
|
657
|
+
{
|
|
658
|
+
title: "auth status",
|
|
659
|
+
lines: [...hostLines, "Status: not connected"]
|
|
660
|
+
}
|
|
661
|
+
],
|
|
662
|
+
actions: [
|
|
663
|
+
{
|
|
664
|
+
label: "Register",
|
|
665
|
+
command: "deepline auth register --wait auto"
|
|
666
|
+
},
|
|
667
|
+
{ label: "Wait", command: "deepline auth wait" }
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
},
|
|
671
|
+
{ json: jsonOutput }
|
|
672
|
+
);
|
|
673
|
+
return EXIT_OK;
|
|
674
|
+
}
|
|
675
|
+
const { status, data } = await httpJson(
|
|
676
|
+
"POST",
|
|
677
|
+
`${baseUrl}/api/v2/auth/cli/status`,
|
|
678
|
+
apiKey,
|
|
679
|
+
{
|
|
680
|
+
api_key: apiKey,
|
|
681
|
+
reveal
|
|
682
|
+
}
|
|
683
|
+
);
|
|
684
|
+
if (status === 401 || status === 403) {
|
|
685
|
+
printCommandEnvelope(
|
|
686
|
+
{
|
|
687
|
+
...hostStatusPayload ?? { host: baseUrl },
|
|
688
|
+
status: "unauthorized",
|
|
689
|
+
connected: false,
|
|
690
|
+
next: "deepline auth register --wait auto && deepline auth wait",
|
|
691
|
+
render: {
|
|
692
|
+
sections: [
|
|
693
|
+
{
|
|
694
|
+
title: "auth status",
|
|
695
|
+
lines: [...hostLines, "Status: unauthorized"]
|
|
696
|
+
}
|
|
697
|
+
],
|
|
698
|
+
actions: [
|
|
699
|
+
{
|
|
700
|
+
label: "Register",
|
|
701
|
+
command: "deepline auth register --wait auto"
|
|
702
|
+
},
|
|
703
|
+
{ label: "Wait", command: "deepline auth wait" }
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
{ json: jsonOutput }
|
|
708
|
+
);
|
|
709
|
+
return EXIT_AUTH;
|
|
710
|
+
}
|
|
711
|
+
if (status >= 400) {
|
|
712
|
+
console.error(`Auth status error (status ${status}).`);
|
|
713
|
+
return EXIT_SERVER;
|
|
714
|
+
}
|
|
715
|
+
const resolvedAuthScope = authScope ?? (getResolvedProjectAuthSource(baseUrl, apiKey) ? "folder" : "global");
|
|
716
|
+
clearSatisfiedPendingClaim(
|
|
717
|
+
baseUrl,
|
|
718
|
+
resolvedAuthScope,
|
|
719
|
+
typeof data.org_name === "string" ? data.org_name : null
|
|
720
|
+
);
|
|
721
|
+
const payload = {
|
|
722
|
+
...hostStatusPayload ?? { host: baseUrl },
|
|
723
|
+
status: data.status || "(unknown)",
|
|
724
|
+
connected: true,
|
|
725
|
+
rateLimitTier: data.rate_limit_tier || "(unknown)",
|
|
726
|
+
workspace: {
|
|
727
|
+
id: data.org_id ?? null,
|
|
728
|
+
name: data.org_name ?? null,
|
|
729
|
+
slug: data.org_slug ?? null
|
|
730
|
+
},
|
|
731
|
+
user: {
|
|
732
|
+
id: data.user_id ?? null
|
|
733
|
+
},
|
|
734
|
+
businessDomain: typeof data.business_domain === "string" ? data.business_domain : null,
|
|
735
|
+
examples: Array.isArray(data.examples) ? data.examples : []
|
|
736
|
+
};
|
|
737
|
+
let savedApiKeyPath = null;
|
|
738
|
+
if (reveal) {
|
|
739
|
+
const apiKeyResp = String(data.api_key || apiKey);
|
|
740
|
+
if (apiKeyResp) {
|
|
741
|
+
saveEnvValues(
|
|
742
|
+
{
|
|
743
|
+
[HOST_URL_ENV]: baseUrl,
|
|
744
|
+
[API_KEY_ENV]: apiKeyResp,
|
|
745
|
+
...activeOrgEnvValues(data)
|
|
746
|
+
},
|
|
747
|
+
baseUrl,
|
|
748
|
+
resolvedAuthScope
|
|
749
|
+
);
|
|
750
|
+
if (resolvedAuthScope === "folder") {
|
|
751
|
+
const target = resolveProjectPinTarget();
|
|
752
|
+
savedApiKeyPath = target.ok ? join(target.dir, ".env.deepline") : null;
|
|
753
|
+
} else {
|
|
754
|
+
savedApiKeyPath = envFilePath(baseUrl);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} else if (resolvedAuthScope === "folder" || storedHostApiKey(baseUrl) === apiKey) {
|
|
758
|
+
saveEnvValues(featureEnvValues(data), baseUrl, resolvedAuthScope);
|
|
759
|
+
}
|
|
760
|
+
printCommandEnvelope(
|
|
761
|
+
{
|
|
762
|
+
...payload,
|
|
763
|
+
...detectAgentRuntime() !== "unknown" ? {
|
|
764
|
+
command_context: resolveCliCommandContext(
|
|
765
|
+
{ baseUrl, apiKey },
|
|
766
|
+
resolvedAuthScope
|
|
767
|
+
)
|
|
768
|
+
} : {},
|
|
769
|
+
...savedApiKeyPath ? { saved_api_key_path: savedApiKeyPath } : {},
|
|
770
|
+
render: {
|
|
771
|
+
sections: [
|
|
772
|
+
{
|
|
773
|
+
title: "auth status",
|
|
774
|
+
lines: [
|
|
775
|
+
...hostLines,
|
|
776
|
+
`Status: ${payload.status}`,
|
|
777
|
+
`Rate limit tier: ${payload.rateLimitTier}`,
|
|
778
|
+
...payload.workspace.name ? [`Workspace: ${payload.workspace.name}`] : [],
|
|
779
|
+
...payload.workspace.slug ? [`Workspace slug: ${payload.workspace.slug}`] : [],
|
|
780
|
+
...payload.workspace.id != null ? [`Org ID: ${payload.workspace.id}`] : [],
|
|
781
|
+
// Switching orgs lives under `deepline org`, but this is where
|
|
782
|
+
// people look when they discover they are in the wrong
|
|
783
|
+
// workspace. Without the pointer the reasonable next move is
|
|
784
|
+
// `deepline auth register`, which re-approves the same account
|
|
785
|
+
// and leaves the active org unchanged.
|
|
786
|
+
"Switch workspace: deepline org list, then deepline org set <org-id>",
|
|
787
|
+
...payload.user.id != null ? [`User ID: ${payload.user.id}`] : [],
|
|
788
|
+
...payload.examples.length > 0 ? [
|
|
789
|
+
"Examples:",
|
|
790
|
+
...payload.examples.slice(0, 3).map((example) => ` ${String(example)}`)
|
|
791
|
+
] : [],
|
|
792
|
+
...savedApiKeyPath ? [`Saved API key to ${savedApiKeyPath}`] : []
|
|
793
|
+
]
|
|
794
|
+
}
|
|
795
|
+
]
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
{ json: jsonOutput }
|
|
799
|
+
);
|
|
800
|
+
return EXIT_OK;
|
|
801
|
+
}
|
|
802
|
+
function registerAuthCommands(program) {
|
|
803
|
+
const auth = program.command("auth").description("Register this device and show CLI auth status.").addHelpText(
|
|
804
|
+
"after",
|
|
805
|
+
`
|
|
806
|
+
Common commands:
|
|
807
|
+
deepline auth register
|
|
808
|
+
deepline auth register --wait no
|
|
809
|
+
deepline auth wait --timeout 300
|
|
810
|
+
deepline auth status
|
|
811
|
+
deepline auth status --json
|
|
812
|
+
|
|
813
|
+
Notes:
|
|
814
|
+
Registration uses --wait auto by default: it waits locally and returns after
|
|
815
|
+
opening the approval page in Cowork. Use --wait no in CI or agent runs, then
|
|
816
|
+
finish with deepline auth wait.
|
|
817
|
+
Auth status shows the target host and active workspace without printing secrets.
|
|
818
|
+
Registering again re-approves the same account; it does not change the active
|
|
819
|
+
workspace. To switch, use deepline org list and deepline org set <org-id>.
|
|
820
|
+
`
|
|
821
|
+
);
|
|
822
|
+
auth.command("workspaces", { hidden: true }).description("Deprecated. Use deepline org list to see workspaces.").action(() => {
|
|
823
|
+
console.error(
|
|
824
|
+
"Workspaces live under `deepline org`.\n List: deepline org list\n Switch: deepline org set <org-id>\n`deepline auth register` re-approves this device; it does not change the active workspace."
|
|
825
|
+
);
|
|
826
|
+
process.exitCode = 1;
|
|
827
|
+
});
|
|
828
|
+
auth.command("register").description(
|
|
829
|
+
"Register this device and open the approval page in your browser."
|
|
830
|
+
).addHelpText(
|
|
831
|
+
"after",
|
|
832
|
+
`
|
|
833
|
+
Notes:
|
|
834
|
+
Opens a browser approval page. --wait auto waits locally and returns after
|
|
835
|
+
opening the approval page in Cowork.
|
|
836
|
+
The saved API key is scoped to the selected workspace and host.
|
|
837
|
+
|
|
838
|
+
Examples:
|
|
839
|
+
deepline auth register
|
|
840
|
+
deepline auth register --org-name Acme --agent-name local-cli
|
|
841
|
+
deepline auth register --wait no
|
|
842
|
+
deepline auth register --auth-scope folder --wait no
|
|
843
|
+
`
|
|
844
|
+
).option(
|
|
845
|
+
"--org-name <name>",
|
|
846
|
+
"Connect to an existing workspace, or name your first workspace"
|
|
847
|
+
).option(
|
|
848
|
+
"--org-id <id>",
|
|
849
|
+
"Bind to this workspace directly, skipping the browser picker"
|
|
850
|
+
).option("--agent-name <name>", "Agent name to register").option("--wait <mode>", "Wait mode: auto, yes, or no", "auto").option("--no-wait", "Alias for --wait no").option(
|
|
851
|
+
"--auth-scope <scope>",
|
|
852
|
+
"Credential scope: global or folder",
|
|
853
|
+
"global"
|
|
854
|
+
).action(async (options) => {
|
|
855
|
+
process.exitCode = await handleRegister([
|
|
856
|
+
...options.orgName ? ["--org-name", options.orgName] : [],
|
|
857
|
+
...options.orgId ? ["--org-id", String(options.orgId)] : [],
|
|
858
|
+
...options.agentName ? ["--agent-name", options.agentName] : [],
|
|
859
|
+
...options.noWait || options.wait === false ? ["--wait", "no"] : ["--wait", String(options.wait ?? "auto")],
|
|
860
|
+
"--auth-scope",
|
|
861
|
+
String(options.authScope ?? "global")
|
|
862
|
+
]);
|
|
863
|
+
});
|
|
864
|
+
auth.command("wait").description("Wait for a pending browser approval and save the API key.").addHelpText(
|
|
865
|
+
"after",
|
|
866
|
+
`
|
|
867
|
+
Notes:
|
|
868
|
+
Completes a previous deepline auth register --wait no flow.
|
|
869
|
+
Saves the approved API key into the host auth file.
|
|
870
|
+
|
|
871
|
+
Examples:
|
|
872
|
+
deepline auth wait
|
|
873
|
+
deepline auth wait --timeout 120
|
|
874
|
+
deepline auth wait --auth-scope folder --timeout 120
|
|
875
|
+
`
|
|
876
|
+
).option("--timeout <seconds>", "Maximum seconds to wait", "300").option(
|
|
877
|
+
"--auth-scope <scope>",
|
|
878
|
+
"Credential scope: global or folder",
|
|
879
|
+
"global"
|
|
880
|
+
).action(async (options) => {
|
|
881
|
+
process.exitCode = await handleWait([
|
|
882
|
+
...options.timeout ? ["--timeout", options.timeout] : [],
|
|
883
|
+
"--auth-scope",
|
|
884
|
+
String(options.authScope ?? "global")
|
|
885
|
+
]);
|
|
886
|
+
});
|
|
887
|
+
auth.command("status").description("Show the current CLI auth and workspace status.").addHelpText(
|
|
888
|
+
"after",
|
|
889
|
+
`
|
|
890
|
+
Notes:
|
|
891
|
+
Shows the host, server health, connection state, active workspace, and user id.
|
|
892
|
+
Use --reveal only when you intentionally want the server to return and persist
|
|
893
|
+
the API key into this host's CLI auth file.
|
|
894
|
+
|
|
895
|
+
Examples:
|
|
896
|
+
deepline auth status
|
|
897
|
+
deepline auth status --auth-scope folder
|
|
898
|
+
deepline auth status --json
|
|
899
|
+
`
|
|
900
|
+
).option(
|
|
901
|
+
"--reveal",
|
|
902
|
+
"Persist the revealed API key back to the host auth file"
|
|
903
|
+
).option("--auth-scope <scope>", "Read only folder or global auth").option("--json", "Emit JSON output. Also automatic when stdout is piped").action(async (options) => {
|
|
904
|
+
process.exitCode = await handleStatus([
|
|
905
|
+
...options.reveal ? ["--reveal"] : [],
|
|
906
|
+
...options.authScope ? ["--auth-scope", String(options.authScope)] : [],
|
|
907
|
+
...options.json ? ["--json"] : []
|
|
908
|
+
]);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
export {
|
|
913
|
+
EXIT_WORKSPACE_MISMATCH,
|
|
914
|
+
resolvePendingAuthClaimForResume,
|
|
915
|
+
readPendingAuthClaim,
|
|
916
|
+
clearPendingClaim,
|
|
917
|
+
clearSatisfiedPendingClaim,
|
|
918
|
+
reportSetupCompleted,
|
|
919
|
+
handleRegister,
|
|
920
|
+
handleWait,
|
|
921
|
+
handleStatus,
|
|
922
|
+
registerAuthCommands
|
|
923
|
+
};
|