@idapt/cli 1.8.0
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/LICENSE +202 -0
- package/NOTICE +8 -0
- package/README.md +67 -0
- package/THIRD_PARTY_NOTICES.txt +32 -0
- package/dist/bin.cjs +7403 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.js +928 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-WNQCFPPN.js +6486 -0
- package/dist/chunk-WNQCFPPN.js.map +1 -0
- package/dist/index.cjs +6500 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +380 -0
- package/dist/index.d.ts +380 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createFetchTransport, execute, autoMode, commandsForResource, findCommand, renderHelp, listResources, renderInstructions, quoteToken } from './chunk-WNQCFPPN.js';
|
|
3
|
+
import { readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync } from 'fs';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import { randomBytes, createHash } from 'crypto';
|
|
9
|
+
|
|
10
|
+
// src/version.ts
|
|
11
|
+
var VERSION = "1.8.0";
|
|
12
|
+
var USER_AGENT = `idapt-cli/${VERSION}`;
|
|
13
|
+
function canOpenBrowser(env, isTty) {
|
|
14
|
+
if (!isTty) return false;
|
|
15
|
+
if (env.SSH_CONNECTION || env.SSH_TTY) return false;
|
|
16
|
+
if (process.platform === "linux") {
|
|
17
|
+
return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
function openBrowser(url) {
|
|
22
|
+
try {
|
|
23
|
+
const platform = process.platform;
|
|
24
|
+
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
25
|
+
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
26
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
27
|
+
child.on("error", () => {
|
|
28
|
+
});
|
|
29
|
+
child.unref();
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
var APP_DIR = "idapt";
|
|
36
|
+
function configBase() {
|
|
37
|
+
const home = homedir();
|
|
38
|
+
if (process.platform === "win32") {
|
|
39
|
+
return process.env.APPDATA ?? join(home, "AppData", "Roaming");
|
|
40
|
+
}
|
|
41
|
+
if (process.platform === "darwin") {
|
|
42
|
+
return join(home, "Library", "Application Support");
|
|
43
|
+
}
|
|
44
|
+
return process.env.XDG_CONFIG_HOME ?? join(home, ".config");
|
|
45
|
+
}
|
|
46
|
+
function cacheBase() {
|
|
47
|
+
const home = homedir();
|
|
48
|
+
if (process.platform === "win32") {
|
|
49
|
+
return process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
|
|
50
|
+
}
|
|
51
|
+
if (process.platform === "darwin") {
|
|
52
|
+
return join(home, "Library", "Caches");
|
|
53
|
+
}
|
|
54
|
+
return process.env.XDG_CACHE_HOME ?? join(home, ".cache");
|
|
55
|
+
}
|
|
56
|
+
function configDir() {
|
|
57
|
+
return join(configBase(), APP_DIR);
|
|
58
|
+
}
|
|
59
|
+
function cacheDir() {
|
|
60
|
+
return join(cacheBase(), APP_DIR);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/auth/credentials.ts
|
|
64
|
+
function credentialsPath() {
|
|
65
|
+
return join(configDir(), "cli-auth.json");
|
|
66
|
+
}
|
|
67
|
+
function loadCredentials(path = credentialsPath()) {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = readFileSync(path, "utf8");
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err.code === "ENOENT") return {};
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
if (!raw.trim()) return {};
|
|
76
|
+
return JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
function saveCredentials(creds, path = credentialsPath()) {
|
|
79
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
80
|
+
writeFileSync(path, `${JSON.stringify(creds, null, 2)}
|
|
81
|
+
`, { mode: 384 });
|
|
82
|
+
chmodSync(path, 384);
|
|
83
|
+
}
|
|
84
|
+
function clearCredentials(path = credentialsPath()) {
|
|
85
|
+
try {
|
|
86
|
+
rmSync(path);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (err.code !== "ENOENT") throw err;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function hasOAuth(creds) {
|
|
92
|
+
return Boolean(creds.refreshToken);
|
|
93
|
+
}
|
|
94
|
+
function base64url(b) {
|
|
95
|
+
return b.toString("base64url");
|
|
96
|
+
}
|
|
97
|
+
function generatePkce() {
|
|
98
|
+
const verifier = base64url(randomBytes(32));
|
|
99
|
+
const challenge = base64url(createHash("sha256").update(verifier).digest());
|
|
100
|
+
return { verifier, challenge };
|
|
101
|
+
}
|
|
102
|
+
function randomState() {
|
|
103
|
+
return base64url(randomBytes(16));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/auth/oauth.ts
|
|
107
|
+
var AUTHORIZE_PATH = "/api/auth/oauth2/authorize";
|
|
108
|
+
var TOKEN_PATH = "/api/auth/oauth2/token";
|
|
109
|
+
var DEVICE_CODE_PATH = "/api/auth/device/code";
|
|
110
|
+
var DEVICE_TOKEN_PATH = "/api/auth/device/token";
|
|
111
|
+
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
112
|
+
var CLI_CLIENT_ID = "idapt-cli";
|
|
113
|
+
var SCOPE = "openid profile email offline_access";
|
|
114
|
+
var RESOURCE = "idapt-api";
|
|
115
|
+
var LOGIN_TIMEOUT_MS = 5 * 6e4;
|
|
116
|
+
var AuthError = class extends Error {
|
|
117
|
+
};
|
|
118
|
+
var ERR_ACCESS_DENIED = "the login request was denied";
|
|
119
|
+
var ERR_STATE_MISMATCH = "login state mismatch \u2014 please try again";
|
|
120
|
+
var ERR_TIMED_OUT = "timed out waiting for browser sign-in";
|
|
121
|
+
var ERR_SESSION_EXPIRED = "your session has expired \u2014 run `idapt login` again";
|
|
122
|
+
function trimRight(s) {
|
|
123
|
+
return s.replace(/\/+$/, "");
|
|
124
|
+
}
|
|
125
|
+
function originOf(baseUrl) {
|
|
126
|
+
const u = new URL(baseUrl);
|
|
127
|
+
return `${u.protocol}//${u.host}`;
|
|
128
|
+
}
|
|
129
|
+
async function postToken(baseUrl, form, userAgent) {
|
|
130
|
+
const base = trimRight(baseUrl);
|
|
131
|
+
const res = await fetch(base + TOKEN_PATH, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
135
|
+
Accept: "application/json",
|
|
136
|
+
Origin: originOf(base),
|
|
137
|
+
"User-Agent": userAgent
|
|
138
|
+
},
|
|
139
|
+
body: new URLSearchParams(form).toString()
|
|
140
|
+
});
|
|
141
|
+
const body = await res.json().catch(() => ({}));
|
|
142
|
+
if (!res.ok || !body.access_token) {
|
|
143
|
+
if (body.error === "invalid_grant")
|
|
144
|
+
throw new AuthError(ERR_SESSION_EXPIRED);
|
|
145
|
+
const msg = body.error_description || body.error || `HTTP ${res.status}`;
|
|
146
|
+
throw new AuthError(`token request failed (${res.status}): ${msg}`);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
accessToken: body.access_token,
|
|
150
|
+
refreshToken: body.refresh_token ?? "",
|
|
151
|
+
expiresIn: body.expires_in ?? 0
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
var CALLBACK_OK = `<!doctype html><html><head><meta charset="utf-8"><title>Signed in</title></head><body style="font-family:system-ui;text-align:center;padding-top:4rem"><h1>You're signed in</h1><p>Return to your terminal \u2014 you can close this tab.</p></body></html>`;
|
|
155
|
+
var CALLBACK_ERR = `<!doctype html><html><head><meta charset="utf-8"><title>Sign-in failed</title></head><body style="font-family:system-ui;text-align:center;padding-top:4rem"><h1>Sign-in failed</h1><p>Return to your terminal and run <code>idapt login</code> again.</p></body></html>`;
|
|
156
|
+
function loginAuthCode(io) {
|
|
157
|
+
const base = trimRight(io.baseUrl);
|
|
158
|
+
const { verifier, challenge } = generatePkce();
|
|
159
|
+
const state = randomState();
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
let settled = false;
|
|
162
|
+
const finish = (fn) => {
|
|
163
|
+
if (settled) return;
|
|
164
|
+
settled = true;
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
io.signal?.removeEventListener("abort", onAbort);
|
|
167
|
+
server.close();
|
|
168
|
+
fn();
|
|
169
|
+
};
|
|
170
|
+
const onAbort = () => finish(() => reject(new AuthError("sign-in cancelled")));
|
|
171
|
+
const server = createServer((req, res) => {
|
|
172
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
173
|
+
if (url.pathname !== "/callback") {
|
|
174
|
+
res.writeHead(404).end();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const q = url.searchParams;
|
|
178
|
+
const err = q.get("error");
|
|
179
|
+
if (err) {
|
|
180
|
+
res.writeHead(400, { "Content-Type": "text/html" }).end(CALLBACK_ERR);
|
|
181
|
+
finish(
|
|
182
|
+
() => reject(
|
|
183
|
+
new AuthError(
|
|
184
|
+
err === "access_denied" ? ERR_ACCESS_DENIED : `sign-in failed: ${err}`
|
|
185
|
+
)
|
|
186
|
+
)
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (q.get("state") !== state) {
|
|
191
|
+
res.writeHead(400, { "Content-Type": "text/html" }).end(CALLBACK_ERR);
|
|
192
|
+
finish(() => reject(new AuthError(ERR_STATE_MISMATCH)));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const code = q.get("code");
|
|
196
|
+
if (!code) {
|
|
197
|
+
res.writeHead(400, { "Content-Type": "text/html" }).end(CALLBACK_ERR);
|
|
198
|
+
finish(
|
|
199
|
+
() => reject(
|
|
200
|
+
new AuthError("the sign-in response had no authorization code")
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
res.writeHead(200, { "Content-Type": "text/html" }).end(CALLBACK_OK);
|
|
206
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
207
|
+
postToken(
|
|
208
|
+
base,
|
|
209
|
+
{
|
|
210
|
+
grant_type: "authorization_code",
|
|
211
|
+
client_id: CLI_CLIENT_ID,
|
|
212
|
+
code,
|
|
213
|
+
code_verifier: verifier,
|
|
214
|
+
redirect_uri: redirectUri,
|
|
215
|
+
resource: RESOURCE
|
|
216
|
+
},
|
|
217
|
+
io.userAgent
|
|
218
|
+
).then(
|
|
219
|
+
(tok) => finish(() => resolve(tok)),
|
|
220
|
+
(e) => finish(() => reject(e))
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
let port = 0;
|
|
224
|
+
const timer = setTimeout(
|
|
225
|
+
() => finish(() => reject(new AuthError(ERR_TIMED_OUT))),
|
|
226
|
+
LOGIN_TIMEOUT_MS
|
|
227
|
+
);
|
|
228
|
+
io.signal?.addEventListener("abort", onAbort);
|
|
229
|
+
server.on("error", (e) => finish(() => reject(e)));
|
|
230
|
+
server.listen(0, "127.0.0.1", () => {
|
|
231
|
+
port = server.address().port;
|
|
232
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
233
|
+
const authzUrl = `${base}${AUTHORIZE_PATH}?` + new URLSearchParams({
|
|
234
|
+
response_type: "code",
|
|
235
|
+
client_id: CLI_CLIENT_ID,
|
|
236
|
+
redirect_uri: redirectUri,
|
|
237
|
+
scope: SCOPE,
|
|
238
|
+
state,
|
|
239
|
+
code_challenge: challenge,
|
|
240
|
+
code_challenge_method: "S256"
|
|
241
|
+
}).toString();
|
|
242
|
+
io.out("");
|
|
243
|
+
const opened = io.open?.(authzUrl) ?? false;
|
|
244
|
+
io.out(
|
|
245
|
+
opened ? `Opening your browser to sign in\u2026
|
|
246
|
+
|
|
247
|
+
If it doesn't open, visit:
|
|
248
|
+
|
|
249
|
+
${authzUrl}
|
|
250
|
+
` : `To finish signing in, open this URL in your browser:
|
|
251
|
+
|
|
252
|
+
${authzUrl}
|
|
253
|
+
`
|
|
254
|
+
);
|
|
255
|
+
io.out("Waiting for sign-in\u2026 (Ctrl-C to cancel)");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
260
|
+
const t = setTimeout(resolve, ms);
|
|
261
|
+
signal?.addEventListener("abort", () => {
|
|
262
|
+
clearTimeout(t);
|
|
263
|
+
reject(new AuthError("sign-in cancelled"));
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
async function loginDevice(io) {
|
|
267
|
+
const base = trimRight(io.baseUrl);
|
|
268
|
+
const codeRes = await fetch(base + DEVICE_CODE_PATH, {
|
|
269
|
+
method: "POST",
|
|
270
|
+
headers: {
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
Origin: originOf(base),
|
|
273
|
+
"User-Agent": io.userAgent
|
|
274
|
+
},
|
|
275
|
+
body: JSON.stringify({ client_id: CLI_CLIENT_ID })
|
|
276
|
+
});
|
|
277
|
+
if (!codeRes.ok) {
|
|
278
|
+
throw new AuthError(
|
|
279
|
+
`could not start device login (HTTP ${codeRes.status})`
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
const code = await codeRes.json();
|
|
283
|
+
if (!code.device_code || !code.user_code) {
|
|
284
|
+
throw new AuthError(
|
|
285
|
+
"the server returned an incomplete login-code response"
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
io.out("");
|
|
289
|
+
if (code.verification_uri_complete) {
|
|
290
|
+
io.out(
|
|
291
|
+
`To finish signing in, open this URL in your browser:
|
|
292
|
+
|
|
293
|
+
${code.verification_uri_complete}
|
|
294
|
+
|
|
295
|
+
(it confirms code ${code.user_code})`
|
|
296
|
+
);
|
|
297
|
+
} else {
|
|
298
|
+
io.out(
|
|
299
|
+
`To finish signing in, open:
|
|
300
|
+
|
|
301
|
+
${code.verification_uri}
|
|
302
|
+
|
|
303
|
+
and enter the code: ${code.user_code}`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
io.out("\nWaiting for approval\u2026 (Ctrl-C to cancel)");
|
|
307
|
+
let interval = code.interval > 0 ? code.interval : 5;
|
|
308
|
+
const deadline = Date.now() + (code.expires_in > 0 ? code.expires_in : 900) * 1e3;
|
|
309
|
+
for (; ; ) {
|
|
310
|
+
await sleep(interval * 1e3, io.signal);
|
|
311
|
+
if (Date.now() > deadline) {
|
|
312
|
+
throw new AuthError("the login code expired before it was approved");
|
|
313
|
+
}
|
|
314
|
+
const res = await fetch(base + DEVICE_TOKEN_PATH, {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: {
|
|
317
|
+
"Content-Type": "application/json",
|
|
318
|
+
Origin: originOf(base),
|
|
319
|
+
"User-Agent": io.userAgent
|
|
320
|
+
},
|
|
321
|
+
body: JSON.stringify({
|
|
322
|
+
grant_type: DEVICE_GRANT,
|
|
323
|
+
device_code: code.device_code,
|
|
324
|
+
client_id: CLI_CLIENT_ID
|
|
325
|
+
})
|
|
326
|
+
});
|
|
327
|
+
const body = await res.json().catch(() => ({}));
|
|
328
|
+
if (res.ok && body.access_token) return body.access_token;
|
|
329
|
+
switch (body.error) {
|
|
330
|
+
case "authorization_pending":
|
|
331
|
+
break;
|
|
332
|
+
case "slow_down":
|
|
333
|
+
interval += 5;
|
|
334
|
+
break;
|
|
335
|
+
case "expired_token":
|
|
336
|
+
throw new AuthError("the login code expired before it was approved");
|
|
337
|
+
case "access_denied":
|
|
338
|
+
throw new AuthError(ERR_ACCESS_DENIED);
|
|
339
|
+
default:
|
|
340
|
+
throw new AuthError(`device login failed (HTTP ${res.status})`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function refreshAccessToken(baseUrl, refreshToken, userAgent) {
|
|
345
|
+
return postToken(
|
|
346
|
+
baseUrl,
|
|
347
|
+
{
|
|
348
|
+
grant_type: "refresh_token",
|
|
349
|
+
client_id: CLI_CLIENT_ID,
|
|
350
|
+
refresh_token: refreshToken,
|
|
351
|
+
resource: RESOURCE
|
|
352
|
+
},
|
|
353
|
+
userAgent
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/auth/resolve.ts
|
|
358
|
+
var SKEW_SECONDS = 60;
|
|
359
|
+
async function resolveCredential(opts) {
|
|
360
|
+
if (opts.apiKeyFlag) {
|
|
361
|
+
return { token: opts.apiKeyFlag, source: "flag", kind: "api-key" };
|
|
362
|
+
}
|
|
363
|
+
const envKey = opts.env.IDAPT_API_KEY;
|
|
364
|
+
if (envKey) return { token: envKey, source: "env", kind: "api-key" };
|
|
365
|
+
const creds = loadCredentials();
|
|
366
|
+
if (hasOAuth(creds)) {
|
|
367
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
368
|
+
if (creds.accessToken && creds.expiresAt && creds.expiresAt - now > SKEW_SECONDS) {
|
|
369
|
+
return { token: creds.accessToken, source: "file", kind: "oauth" };
|
|
370
|
+
}
|
|
371
|
+
const tok = await refreshAccessToken(
|
|
372
|
+
opts.baseUrl,
|
|
373
|
+
// hasOAuth guarantees refreshToken is set.
|
|
374
|
+
creds.refreshToken,
|
|
375
|
+
opts.userAgent
|
|
376
|
+
);
|
|
377
|
+
const updated = {
|
|
378
|
+
accessToken: tok.accessToken,
|
|
379
|
+
refreshToken: tok.refreshToken || creds.refreshToken,
|
|
380
|
+
expiresAt: now + (tok.expiresIn || 900)
|
|
381
|
+
};
|
|
382
|
+
saveCredentials(updated);
|
|
383
|
+
return { token: tok.accessToken, source: "file", kind: "oauth" };
|
|
384
|
+
}
|
|
385
|
+
if (creds.apiKey)
|
|
386
|
+
return { token: creds.apiKey, source: "file", kind: "api-key" };
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/auth/commands.ts
|
|
391
|
+
var AUTH_HINT = `Not signed in.
|
|
392
|
+
|
|
393
|
+
Sign in in your browser (recommended):
|
|
394
|
+
idapt login
|
|
395
|
+
|
|
396
|
+
On a headless machine / over SSH (device code):
|
|
397
|
+
idapt login --device
|
|
398
|
+
|
|
399
|
+
Or paste an API key (get one from https://idapt.app/settings/api-keys):
|
|
400
|
+
printf '%s' "$KEY" | idapt login --api-key-stdin
|
|
401
|
+
export IDAPT_API_KEY=<key> # or via env (CI / sudo)`;
|
|
402
|
+
async function fetchIdentity(baseUrl, token) {
|
|
403
|
+
try {
|
|
404
|
+
const t = createFetchTransport({ baseUrl, token, userAgent: USER_AGENT });
|
|
405
|
+
const res = await t.request({ method: "GET", path: "/api/v1/me" });
|
|
406
|
+
if (res.status !== 200) return null;
|
|
407
|
+
const data = res.json?.data ?? {};
|
|
408
|
+
const email = data.userEmail ?? data.email;
|
|
409
|
+
const name = data.userName ?? data.name;
|
|
410
|
+
return { email: email ?? void 0, name: name ?? void 0 };
|
|
411
|
+
} catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function announceSignedIn(ctx, token) {
|
|
416
|
+
const id = await fetchIdentity(ctx.baseUrl, token);
|
|
417
|
+
if (id?.email) {
|
|
418
|
+
ctx.out(`
|
|
419
|
+
\u2713 Signed in as ${id.email}${id.name ? ` (${id.name})` : ""}.`);
|
|
420
|
+
} else {
|
|
421
|
+
ctx.out("\n\u2713 Signed in.");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function runLogin(ctx, opts) {
|
|
425
|
+
if (opts.apiKeyStdin) {
|
|
426
|
+
const key = (ctx.readStdin ?? (() => readFileSync(0, "utf8")))().trim();
|
|
427
|
+
if (!key) {
|
|
428
|
+
ctx.out("idapt login: no API key on stdin.");
|
|
429
|
+
return 1;
|
|
430
|
+
}
|
|
431
|
+
saveCredentials({ apiKey: key });
|
|
432
|
+
await announceSignedIn(ctx, key);
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
const useDevice = opts.device || !opts.web && !canOpenBrowser(ctx.env, ctx.isTty);
|
|
436
|
+
const controller = new AbortController();
|
|
437
|
+
const onSigint = () => controller.abort();
|
|
438
|
+
process.once("SIGINT", onSigint);
|
|
439
|
+
try {
|
|
440
|
+
if (useDevice) {
|
|
441
|
+
const sessionToken = await loginDevice({
|
|
442
|
+
out: ctx.out,
|
|
443
|
+
baseUrl: ctx.baseUrl,
|
|
444
|
+
userAgent: USER_AGENT,
|
|
445
|
+
signal: controller.signal
|
|
446
|
+
});
|
|
447
|
+
saveCredentials({ apiKey: sessionToken });
|
|
448
|
+
await announceSignedIn(ctx, sessionToken);
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
const tokens = await loginAuthCode({
|
|
452
|
+
out: ctx.out,
|
|
453
|
+
baseUrl: ctx.baseUrl,
|
|
454
|
+
userAgent: USER_AGENT,
|
|
455
|
+
open: openBrowser,
|
|
456
|
+
signal: controller.signal
|
|
457
|
+
});
|
|
458
|
+
const creds = {
|
|
459
|
+
accessToken: tokens.accessToken,
|
|
460
|
+
refreshToken: tokens.refreshToken,
|
|
461
|
+
expiresAt: Math.floor(Date.now() / 1e3) + (tokens.expiresIn || 900)
|
|
462
|
+
};
|
|
463
|
+
saveCredentials(creds);
|
|
464
|
+
await announceSignedIn(ctx, tokens.accessToken);
|
|
465
|
+
return 0;
|
|
466
|
+
} catch (err) {
|
|
467
|
+
ctx.out(`
|
|
468
|
+
\u2717 ${err.message}`);
|
|
469
|
+
return 1;
|
|
470
|
+
} finally {
|
|
471
|
+
process.removeListener("SIGINT", onSigint);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function runLogout(ctx) {
|
|
475
|
+
clearCredentials();
|
|
476
|
+
ctx.out("Signed out \u2014 cleared the stored credential.");
|
|
477
|
+
return 0;
|
|
478
|
+
}
|
|
479
|
+
async function runStatus(ctx) {
|
|
480
|
+
const resolved = await resolveCredential({
|
|
481
|
+
apiKeyFlag: ctx.apiKeyFlag,
|
|
482
|
+
env: ctx.env,
|
|
483
|
+
baseUrl: ctx.baseUrl,
|
|
484
|
+
userAgent: USER_AGENT
|
|
485
|
+
}).catch(() => null);
|
|
486
|
+
if (!resolved) {
|
|
487
|
+
ctx.print(AUTH_HINT);
|
|
488
|
+
return 1;
|
|
489
|
+
}
|
|
490
|
+
const lines = [
|
|
491
|
+
`API URL: ${ctx.baseUrl}`,
|
|
492
|
+
`Source: ${resolved.source}`,
|
|
493
|
+
`Kind: ${resolved.kind}`
|
|
494
|
+
];
|
|
495
|
+
if (resolved.kind === "oauth") {
|
|
496
|
+
const creds = loadCredentials();
|
|
497
|
+
if (creds.expiresAt) {
|
|
498
|
+
const secs = creds.expiresAt - Math.floor(Date.now() / 1e3);
|
|
499
|
+
lines.push(
|
|
500
|
+
`Token: ${secs > 0 ? `valid (${Math.max(0, Math.round(secs / 60))} min left)` : "expired (auto-refreshes on next call)"}`
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
const id = await fetchIdentity(ctx.baseUrl, resolved.token);
|
|
505
|
+
if (!id) {
|
|
506
|
+
ctx.print(lines.join("\n"));
|
|
507
|
+
ctx.out("\n\u2717 Credential is invalid or expired \u2014 run `idapt login`.");
|
|
508
|
+
return 1;
|
|
509
|
+
}
|
|
510
|
+
lines.unshift(
|
|
511
|
+
`Signed in as ${id.email ?? "(unknown)"}${id.name ? ` (${id.name})` : ""}`,
|
|
512
|
+
""
|
|
513
|
+
);
|
|
514
|
+
ctx.print(lines.join("\n"));
|
|
515
|
+
return 0;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/doc.ts
|
|
519
|
+
function helpIndex() {
|
|
520
|
+
const resources = listResources().sort().join(", ");
|
|
521
|
+
return [
|
|
522
|
+
`idapt ${VERSION} \u2014 your AI workspace, from the terminal`,
|
|
523
|
+
"",
|
|
524
|
+
"Usage:",
|
|
525
|
+
" idapt <resource> <verb> [args] Run a command (e.g. idapt drive list)",
|
|
526
|
+
" idapt help [resource [verb]] Show a command's contract",
|
|
527
|
+
" idapt instructions [resource] Show a resource's playbook",
|
|
528
|
+
" idapt login | logout | whoami Authenticate",
|
|
529
|
+
" idapt upgrade Update to the latest version",
|
|
530
|
+
"",
|
|
531
|
+
"Global flags:",
|
|
532
|
+
" -o, --output <table|json|jsonl|quiet> Output format (TTY\u2192table, pipe\u2192json)",
|
|
533
|
+
" --api-key <key> Bearer token (or IDAPT_API_KEY env)",
|
|
534
|
+
" --api-url <url> API base URL (or IDAPT_API_URL env)",
|
|
535
|
+
"",
|
|
536
|
+
"Resources:",
|
|
537
|
+
` ${resources}`,
|
|
538
|
+
"",
|
|
539
|
+
"Auth: browser sign-in (`idapt login`) or paste a key (IDAPT_API_KEY).",
|
|
540
|
+
"Docs: https://idapt.app/cli"
|
|
541
|
+
].join("\n");
|
|
542
|
+
}
|
|
543
|
+
function resourceHelp(resource, cmds) {
|
|
544
|
+
const rows = cmds.map((c) => ` ${c.verb.padEnd(16)} ${c.summary ?? ""}`.trimEnd()).join("\n");
|
|
545
|
+
return [
|
|
546
|
+
`idapt ${resource} \u2014 commands`,
|
|
547
|
+
"",
|
|
548
|
+
rows,
|
|
549
|
+
"",
|
|
550
|
+
`Run \`idapt help ${resource} <verb>\` for a command's full contract,`,
|
|
551
|
+
`or \`idapt instructions ${resource}\` for the playbook.`
|
|
552
|
+
].join("\n");
|
|
553
|
+
}
|
|
554
|
+
function renderHelpDoc(tokens) {
|
|
555
|
+
if (tokens.length === 0) return helpIndex();
|
|
556
|
+
if (tokens.length === 1) {
|
|
557
|
+
const resource = tokens[0];
|
|
558
|
+
const cmds2 = commandsForResource(resource);
|
|
559
|
+
if (cmds2.length === 0) {
|
|
560
|
+
return `Unknown resource "${resource}".
|
|
561
|
+
|
|
562
|
+
${helpIndex()}`;
|
|
563
|
+
}
|
|
564
|
+
return resourceHelp(resource, cmds2);
|
|
565
|
+
}
|
|
566
|
+
const command = `${tokens[0]} ${tokens[1]}`;
|
|
567
|
+
const spec = findCommand(command);
|
|
568
|
+
if (spec) return renderHelp(spec);
|
|
569
|
+
const cmds = commandsForResource(tokens[0]);
|
|
570
|
+
if (cmds.length > 0) {
|
|
571
|
+
return `Unknown command "${command}".
|
|
572
|
+
|
|
573
|
+
${resourceHelp(tokens[0], cmds)}`;
|
|
574
|
+
}
|
|
575
|
+
return `Unknown command "${command}".
|
|
576
|
+
|
|
577
|
+
${helpIndex()}`;
|
|
578
|
+
}
|
|
579
|
+
function renderInstructionsDoc(tokens) {
|
|
580
|
+
if (tokens.length === 0) {
|
|
581
|
+
return [
|
|
582
|
+
"idapt instructions \u2014 resource playbooks",
|
|
583
|
+
"",
|
|
584
|
+
"Instructions are RESOURCE-SCOPED. Run `idapt instructions <resource>`:",
|
|
585
|
+
` ${listResources().sort().join(", ")}`
|
|
586
|
+
].join("\n");
|
|
587
|
+
}
|
|
588
|
+
return renderInstructions(tokens[0]);
|
|
589
|
+
}
|
|
590
|
+
var PACKAGE_NAME = "@idapt/cli";
|
|
591
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
592
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
593
|
+
function statePath() {
|
|
594
|
+
return join(cacheDir(), "cli-update-check.json");
|
|
595
|
+
}
|
|
596
|
+
function loadState() {
|
|
597
|
+
try {
|
|
598
|
+
return JSON.parse(readFileSync(statePath(), "utf8"));
|
|
599
|
+
} catch {
|
|
600
|
+
return {};
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function saveState(s) {
|
|
604
|
+
try {
|
|
605
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
606
|
+
writeFileSync(statePath(), JSON.stringify(s));
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
function isNewer(latest, current) {
|
|
611
|
+
const norm = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
612
|
+
const a = norm(latest);
|
|
613
|
+
const b = norm(current);
|
|
614
|
+
for (let i = 0; i < 3; i++) {
|
|
615
|
+
const x = a[i] ?? 0;
|
|
616
|
+
const y = b[i] ?? 0;
|
|
617
|
+
if (x > y) return true;
|
|
618
|
+
if (x < y) return false;
|
|
619
|
+
}
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
async function fetchDistTag(tag = "latest", timeoutMs = 1500) {
|
|
623
|
+
try {
|
|
624
|
+
const res = await fetch(`${REGISTRY}/${encodeURIComponent(PACKAGE_NAME)}`, {
|
|
625
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
626
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
627
|
+
});
|
|
628
|
+
if (!res.ok) return null;
|
|
629
|
+
const body = await res.json();
|
|
630
|
+
return body["dist-tags"]?.[tag] ?? null;
|
|
631
|
+
} catch {
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
function disabled(env) {
|
|
636
|
+
return Boolean(env.CI || env.NO_UPDATE_NOTIFIER || env.IDAPT_NO_UPDATE_NUDGE);
|
|
637
|
+
}
|
|
638
|
+
async function maybeNotify(io) {
|
|
639
|
+
try {
|
|
640
|
+
if (!io.isTty || disabled(io.env)) return;
|
|
641
|
+
if (VERSION.includes("-dev")) return;
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const state = loadState();
|
|
644
|
+
if (!state.lastCheckAt || now - state.lastCheckAt > DAY_MS) {
|
|
645
|
+
const latest = await fetchDistTag("latest");
|
|
646
|
+
state.lastCheckAt = now;
|
|
647
|
+
if (latest) state.latest = latest;
|
|
648
|
+
saveState(state);
|
|
649
|
+
}
|
|
650
|
+
if (!state.latest || !isNewer(state.latest, VERSION)) return;
|
|
651
|
+
if (state.lastShownAt && now - state.lastShownAt < DAY_MS) return;
|
|
652
|
+
state.lastShownAt = now;
|
|
653
|
+
saveState(state);
|
|
654
|
+
io.stderr(
|
|
655
|
+
`
|
|
656
|
+
\u25C6 Update available: idapt ${VERSION} \u2192 ${state.latest}. Run \`idapt upgrade\`.
|
|
657
|
+
`
|
|
658
|
+
);
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function detectManager(scriptPath, env, channel) {
|
|
663
|
+
const p = (scriptPath || "").replace(/\\/g, "/");
|
|
664
|
+
const tag = channel === "next" ? "@next" : "@latest";
|
|
665
|
+
const spec = `@idapt/cli${tag}`;
|
|
666
|
+
if (p.includes("/_npx/")) {
|
|
667
|
+
return {
|
|
668
|
+
kind: "manual",
|
|
669
|
+
command: `npm install -g ${spec}`,
|
|
670
|
+
reason: "you're running via npx"
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
if (process.versions.bun || p.includes("/.bun/")) {
|
|
674
|
+
return {
|
|
675
|
+
kind: "run",
|
|
676
|
+
name: "bun",
|
|
677
|
+
argv: ["bun", "add", "-g"],
|
|
678
|
+
pkgSpec: spec
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
const voltaHome = env.VOLTA_HOME?.replace(/\\/g, "/");
|
|
682
|
+
if (voltaHome && p.startsWith(voltaHome)) {
|
|
683
|
+
return {
|
|
684
|
+
kind: "run",
|
|
685
|
+
name: "volta",
|
|
686
|
+
argv: ["volta", "install"],
|
|
687
|
+
pkgSpec: channel === "next" ? "@idapt/cli@next" : "@idapt/cli"
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
if (p.includes("/pnpm/") || p.includes("/.pnpm/")) {
|
|
691
|
+
return {
|
|
692
|
+
kind: "run",
|
|
693
|
+
name: "pnpm",
|
|
694
|
+
argv: ["pnpm", "add", "-g"],
|
|
695
|
+
pkgSpec: spec
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
return {
|
|
699
|
+
kind: "run",
|
|
700
|
+
name: "npm",
|
|
701
|
+
argv: ["npm", "install", "-g"],
|
|
702
|
+
pkgSpec: spec
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function spawnInherit(cmd, args) {
|
|
706
|
+
return new Promise((resolve) => {
|
|
707
|
+
const child = process.platform === "win32" ? spawn(process.env.ComSpec || "cmd.exe", ["/c", cmd, ...args], {
|
|
708
|
+
stdio: "inherit"
|
|
709
|
+
}) : spawn(cmd, args, { stdio: "inherit" });
|
|
710
|
+
child.on("error", () => resolve(127));
|
|
711
|
+
child.on("close", (code) => resolve(code ?? 0));
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
async function runUpgrade(io, opts) {
|
|
715
|
+
const channel = opts.next ? "next" : "latest";
|
|
716
|
+
const latest = await fetchDistTag(channel, 5e3);
|
|
717
|
+
if (opts.check) {
|
|
718
|
+
if (!latest) {
|
|
719
|
+
io.stderr("idapt: could not reach the npm registry.\n");
|
|
720
|
+
return 1;
|
|
721
|
+
}
|
|
722
|
+
io.stdout(
|
|
723
|
+
isNewer(latest, VERSION) ? `Update available: idapt ${VERSION} \u2192 ${latest}. Run \`idapt upgrade\`.
|
|
724
|
+
` : `idapt ${VERSION} is up to date (${channel}).
|
|
725
|
+
`
|
|
726
|
+
);
|
|
727
|
+
return 0;
|
|
728
|
+
}
|
|
729
|
+
if (latest && channel === "latest" && !isNewer(latest, VERSION)) {
|
|
730
|
+
io.stdout(`idapt ${VERSION} is already up to date.
|
|
731
|
+
`);
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
const det = detectManager(process.argv[1] ?? "", io.env, channel);
|
|
735
|
+
if (det.kind === "manual") {
|
|
736
|
+
io.stdout(`To update (${det.reason}), run:
|
|
737
|
+
${det.command}
|
|
738
|
+
`);
|
|
739
|
+
return 0;
|
|
740
|
+
}
|
|
741
|
+
const args = [...det.argv.slice(1), det.pkgSpec];
|
|
742
|
+
const cmdline = [det.argv[0], ...args].join(" ");
|
|
743
|
+
io.stderr(`Updating idapt via ${det.name}\u2026 (${cmdline})
|
|
744
|
+
`);
|
|
745
|
+
const code = await spawnInherit(det.argv[0], args);
|
|
746
|
+
if (code !== 0) {
|
|
747
|
+
io.stderr(
|
|
748
|
+
`
|
|
749
|
+
Automatic update failed (exit ${code}). Run it manually:
|
|
750
|
+
${cmdline}
|
|
751
|
+
`
|
|
752
|
+
);
|
|
753
|
+
return code;
|
|
754
|
+
}
|
|
755
|
+
io.stdout("\n\u2713 Updated. Run `idapt version` to confirm.\n");
|
|
756
|
+
return 0;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// src/bin.ts
|
|
760
|
+
var DEFAULT_BASE_URL = "https://idapt.app";
|
|
761
|
+
var OUTPUT_MODES = /* @__PURE__ */ new Set(["json", "jsonl", "table", "quiet"]);
|
|
762
|
+
function extractOutputMode(argv) {
|
|
763
|
+
const rest = [];
|
|
764
|
+
let mode;
|
|
765
|
+
for (let i = 0; i < argv.length; i++) {
|
|
766
|
+
const a = argv[i];
|
|
767
|
+
if (a === "--output" || a === "-o") {
|
|
768
|
+
const v = argv[++i];
|
|
769
|
+
if (v && OUTPUT_MODES.has(v)) mode = v;
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
const inline = a.startsWith("--output=") ? a.slice("--output=".length) : null;
|
|
773
|
+
if (inline && OUTPUT_MODES.has(inline)) {
|
|
774
|
+
mode = inline;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
rest.push(a);
|
|
778
|
+
}
|
|
779
|
+
return { mode, rest };
|
|
780
|
+
}
|
|
781
|
+
function extractValueFlag(argv, name) {
|
|
782
|
+
const rest = [];
|
|
783
|
+
let value;
|
|
784
|
+
for (let i = 0; i < argv.length; i++) {
|
|
785
|
+
const a = argv[i];
|
|
786
|
+
if (a === name) {
|
|
787
|
+
value = argv[++i];
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (a.startsWith(`${name}=`)) {
|
|
791
|
+
value = a.slice(name.length + 1);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
rest.push(a);
|
|
795
|
+
}
|
|
796
|
+
return { value, rest };
|
|
797
|
+
}
|
|
798
|
+
function buildCommandFromArgv(argv) {
|
|
799
|
+
return ["idapt", ...argv.map(quoteToken)].join(" ");
|
|
800
|
+
}
|
|
801
|
+
function isDocCommand(rest) {
|
|
802
|
+
if (rest[0] === "help" || rest[0] === "instructions") return true;
|
|
803
|
+
return rest.some(
|
|
804
|
+
(a) => a === "--help" || a === "-h" || a === "--instructions"
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
function authCtx(io, baseUrl, apiKeyFlag) {
|
|
808
|
+
return {
|
|
809
|
+
out: io.stderr,
|
|
810
|
+
print: io.stdout,
|
|
811
|
+
env: io.env,
|
|
812
|
+
isTty: io.isTty,
|
|
813
|
+
baseUrl,
|
|
814
|
+
apiKeyFlag
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
async function run(io) {
|
|
818
|
+
const { mode: requested, rest: r1 } = extractOutputMode(io.argv);
|
|
819
|
+
const { value: apiKeyFlag, rest: r2 } = extractValueFlag(r1, "--api-key");
|
|
820
|
+
const { value: apiUrlFlag, rest } = extractValueFlag(r2, "--api-url");
|
|
821
|
+
const baseUrl = apiUrlFlag ?? io.env.IDAPT_API_URL ?? DEFAULT_BASE_URL;
|
|
822
|
+
const cmd = rest[0];
|
|
823
|
+
if (cmd === "version" || cmd === "--version") {
|
|
824
|
+
io.stdout(`idapt ${VERSION}
|
|
825
|
+
`);
|
|
826
|
+
return 0;
|
|
827
|
+
}
|
|
828
|
+
if (rest.length === 0) {
|
|
829
|
+
io.stderr("idapt: missing command. Try `idapt help`.\n");
|
|
830
|
+
return 1;
|
|
831
|
+
}
|
|
832
|
+
if (cmd === "help") {
|
|
833
|
+
io.stdout(`${renderHelpDoc(rest.slice(1))}
|
|
834
|
+
`);
|
|
835
|
+
return 0;
|
|
836
|
+
}
|
|
837
|
+
if (cmd === "instructions") {
|
|
838
|
+
io.stdout(`${renderInstructionsDoc(rest.slice(1))}
|
|
839
|
+
`);
|
|
840
|
+
return 0;
|
|
841
|
+
}
|
|
842
|
+
if (cmd === "upgrade" || cmd === "update") {
|
|
843
|
+
return runUpgrade(
|
|
844
|
+
{ stdout: io.stdout, stderr: io.stderr, env: io.env },
|
|
845
|
+
{ check: rest.includes("--check"), next: rest.includes("--next") }
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
const ctx = authCtx(io, baseUrl, apiKeyFlag);
|
|
849
|
+
if (cmd === "login") {
|
|
850
|
+
return runLogin(ctx, parseLoginOpts(rest.slice(1)));
|
|
851
|
+
}
|
|
852
|
+
if (cmd === "logout") {
|
|
853
|
+
return runLogout(ctx);
|
|
854
|
+
}
|
|
855
|
+
if (cmd === "whoami") {
|
|
856
|
+
return runStatus(ctx);
|
|
857
|
+
}
|
|
858
|
+
if (cmd === "auth") {
|
|
859
|
+
const sub = rest[1];
|
|
860
|
+
if (sub === "login") return runLogin(ctx, parseLoginOpts(rest.slice(2)));
|
|
861
|
+
if (sub === "logout") return runLogout(ctx);
|
|
862
|
+
if (sub === "status") return runStatus(ctx);
|
|
863
|
+
io.stderr(
|
|
864
|
+
"idapt auth: use `login`, `logout`, or `status` (alias: `idapt whoami`).\n"
|
|
865
|
+
);
|
|
866
|
+
return 1;
|
|
867
|
+
}
|
|
868
|
+
const doc = isDocCommand(rest);
|
|
869
|
+
let token;
|
|
870
|
+
const resolved = await resolveCredential({
|
|
871
|
+
apiKeyFlag,
|
|
872
|
+
env: io.env,
|
|
873
|
+
baseUrl,
|
|
874
|
+
userAgent: USER_AGENT
|
|
875
|
+
}).catch(() => null);
|
|
876
|
+
if (resolved) {
|
|
877
|
+
token = resolved.token;
|
|
878
|
+
} else if (doc) {
|
|
879
|
+
token = "";
|
|
880
|
+
} else {
|
|
881
|
+
io.stderr(`${AUTH_HINT}
|
|
882
|
+
`);
|
|
883
|
+
return 1;
|
|
884
|
+
}
|
|
885
|
+
const transport = createFetchTransport({
|
|
886
|
+
baseUrl,
|
|
887
|
+
token,
|
|
888
|
+
userAgent: USER_AGENT
|
|
889
|
+
});
|
|
890
|
+
const result = await execute(buildCommandFromArgv(rest), {
|
|
891
|
+
transport,
|
|
892
|
+
mode: autoMode(io.isTty, requested)
|
|
893
|
+
});
|
|
894
|
+
if (result.ok) io.stdout(`${result.rendered}
|
|
895
|
+
`);
|
|
896
|
+
else io.stderr(`${result.error ?? result.rendered}
|
|
897
|
+
`);
|
|
898
|
+
await maybeNotify({ stderr: io.stderr, env: io.env, isTty: io.isTty });
|
|
899
|
+
return result.ok ? 0 : 1;
|
|
900
|
+
}
|
|
901
|
+
function parseLoginOpts(args) {
|
|
902
|
+
return {
|
|
903
|
+
device: args.includes("--device"),
|
|
904
|
+
web: args.includes("--web"),
|
|
905
|
+
apiKeyStdin: args.includes("--api-key-stdin")
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function isCliEntrypoint(entryPath) {
|
|
909
|
+
if (!entryPath) return false;
|
|
910
|
+
return /bin(\.[cm]?[jt]s)?$/.test(entryPath) || /(?:^|[\\/])idapt$/.test(entryPath);
|
|
911
|
+
}
|
|
912
|
+
async function main() {
|
|
913
|
+
const code = await run({
|
|
914
|
+
argv: process.argv.slice(2),
|
|
915
|
+
env: process.env,
|
|
916
|
+
stdout: (s) => process.stdout.write(s),
|
|
917
|
+
stderr: (s) => process.stderr.write(s),
|
|
918
|
+
isTty: Boolean(process.stdout.isTTY)
|
|
919
|
+
});
|
|
920
|
+
process.exit(code);
|
|
921
|
+
}
|
|
922
|
+
if (typeof process !== "undefined" && isCliEntrypoint(process.argv[1])) {
|
|
923
|
+
void main();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, run };
|
|
927
|
+
//# sourceMappingURL=bin.js.map
|
|
928
|
+
//# sourceMappingURL=bin.js.map
|