@amdahl/cli 0.3.2 → 0.3.4
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/README.md +129 -44
- package/dist/amdahl.js +580 -213
- package/package.json +1 -1
package/dist/amdahl.js
CHANGED
|
@@ -315,8 +315,8 @@ var require_directives = __commonJS({
|
|
|
315
315
|
if (parts.length < 2)
|
|
316
316
|
return false;
|
|
317
317
|
}
|
|
318
|
-
const [handle,
|
|
319
|
-
this.tags[handle] =
|
|
318
|
+
const [handle, prefix2] = parts;
|
|
319
|
+
this.tags[handle] = prefix2;
|
|
320
320
|
return true;
|
|
321
321
|
}
|
|
322
322
|
case "%YAML": {
|
|
@@ -366,10 +366,10 @@ var require_directives = __commonJS({
|
|
|
366
366
|
const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
|
|
367
367
|
if (!suffix)
|
|
368
368
|
onError(`The ${source} tag has no suffix`);
|
|
369
|
-
const
|
|
370
|
-
if (
|
|
369
|
+
const prefix2 = this.tags[handle];
|
|
370
|
+
if (prefix2) {
|
|
371
371
|
try {
|
|
372
|
-
return
|
|
372
|
+
return prefix2 + decodeURIComponent(suffix);
|
|
373
373
|
} catch (error) {
|
|
374
374
|
onError(String(error));
|
|
375
375
|
return null;
|
|
@@ -385,9 +385,9 @@ var require_directives = __commonJS({
|
|
|
385
385
|
* taking into account current tag prefixes and defaults.
|
|
386
386
|
*/
|
|
387
387
|
tagString(tag) {
|
|
388
|
-
for (const [handle,
|
|
389
|
-
if (tag.startsWith(
|
|
390
|
-
return handle + escapeTagName(tag.substring(
|
|
388
|
+
for (const [handle, prefix2] of Object.entries(this.tags)) {
|
|
389
|
+
if (tag.startsWith(prefix2))
|
|
390
|
+
return handle + escapeTagName(tag.substring(prefix2.length));
|
|
391
391
|
}
|
|
392
392
|
return tag[0] === "!" ? tag : `!<${tag}>`;
|
|
393
393
|
}
|
|
@@ -404,11 +404,11 @@ var require_directives = __commonJS({
|
|
|
404
404
|
tagNames = Object.keys(tags);
|
|
405
405
|
} else
|
|
406
406
|
tagNames = [];
|
|
407
|
-
for (const [handle,
|
|
408
|
-
if (handle === "!!" &&
|
|
407
|
+
for (const [handle, prefix2] of tagEntries) {
|
|
408
|
+
if (handle === "!!" && prefix2 === "tag:yaml.org,2002:")
|
|
409
409
|
continue;
|
|
410
|
-
if (!doc || tagNames.some((tn) => tn.startsWith(
|
|
411
|
-
lines.push(`%TAG ${handle} ${
|
|
410
|
+
if (!doc || tagNames.some((tn) => tn.startsWith(prefix2)))
|
|
411
|
+
lines.push(`%TAG ${handle} ${prefix2}`);
|
|
412
412
|
}
|
|
413
413
|
return lines.join("\n");
|
|
414
414
|
}
|
|
@@ -443,14 +443,14 @@ var require_anchors = __commonJS({
|
|
|
443
443
|
});
|
|
444
444
|
return anchors;
|
|
445
445
|
}
|
|
446
|
-
function findNewAnchor(
|
|
446
|
+
function findNewAnchor(prefix2, exclude) {
|
|
447
447
|
for (let i = 1; true; ++i) {
|
|
448
|
-
const name = `${
|
|
448
|
+
const name = `${prefix2}${i}`;
|
|
449
449
|
if (!exclude.has(name))
|
|
450
450
|
return name;
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
|
-
function createNodeAnchors(doc,
|
|
453
|
+
function createNodeAnchors(doc, prefix2) {
|
|
454
454
|
const aliasObjects = [];
|
|
455
455
|
const sourceObjects = /* @__PURE__ */ new Map();
|
|
456
456
|
let prevAnchors = null;
|
|
@@ -458,7 +458,7 @@ var require_anchors = __commonJS({
|
|
|
458
458
|
onAnchor: (source) => {
|
|
459
459
|
aliasObjects.push(source);
|
|
460
460
|
prevAnchors ?? (prevAnchors = anchorNames(doc));
|
|
461
|
-
const anchor = findNewAnchor(
|
|
461
|
+
const anchor = findNewAnchor(prefix2, prevAnchors);
|
|
462
462
|
prevAnchors.add(anchor);
|
|
463
463
|
return anchor;
|
|
464
464
|
},
|
|
@@ -2451,10 +2451,10 @@ var require_int = __commonJS({
|
|
|
2451
2451
|
var stringifyNumber = require_stringifyNumber();
|
|
2452
2452
|
var intIdentify = (value) => typeof value === "bigint" || Number.isInteger(value);
|
|
2453
2453
|
var intResolve = (str2, offset, radix, { intAsBigInt }) => intAsBigInt ? BigInt(str2) : parseInt(str2.substring(offset), radix);
|
|
2454
|
-
function intStringify(node, radix,
|
|
2454
|
+
function intStringify(node, radix, prefix2) {
|
|
2455
2455
|
const { value } = node;
|
|
2456
2456
|
if (intIdentify(value) && value >= 0)
|
|
2457
|
-
return
|
|
2457
|
+
return prefix2 + value.toString(radix);
|
|
2458
2458
|
return stringifyNumber.stringifyNumber(node);
|
|
2459
2459
|
}
|
|
2460
2460
|
var intOct = {
|
|
@@ -2916,11 +2916,11 @@ var require_int2 = __commonJS({
|
|
|
2916
2916
|
const n = parseInt(str2, radix);
|
|
2917
2917
|
return sign === "-" ? -1 * n : n;
|
|
2918
2918
|
}
|
|
2919
|
-
function intStringify(node, radix,
|
|
2919
|
+
function intStringify(node, radix, prefix2) {
|
|
2920
2920
|
const { value } = node;
|
|
2921
2921
|
if (intIdentify(value)) {
|
|
2922
2922
|
const str2 = value.toString(radix);
|
|
2923
|
-
return value < 0 ? "-" +
|
|
2923
|
+
return value < 0 ? "-" + prefix2 + str2.substr(1) : prefix2 + str2;
|
|
2924
2924
|
}
|
|
2925
2925
|
return stringifyNumber.stringifyNumber(node);
|
|
2926
2926
|
}
|
|
@@ -7365,26 +7365,45 @@ var require_dist = __commonJS({
|
|
|
7365
7365
|
|
|
7366
7366
|
// src/io.ts
|
|
7367
7367
|
import { spawn } from "child_process";
|
|
7368
|
+
import { accessSync, constants, statSync } from "fs";
|
|
7368
7369
|
import { homedir, hostname } from "os";
|
|
7369
7370
|
import { createInterface } from "readline";
|
|
7370
7371
|
|
|
7371
7372
|
// src/select.ts
|
|
7372
7373
|
import { emitKeypressEvents } from "readline";
|
|
7373
7374
|
var HINT = "(Use the arrow keys, then Enter)";
|
|
7374
|
-
var MULTI_HINT = "
|
|
7375
|
+
var MULTI_HINT = "\u2191\u2193 move \xB7 Space tick/untick \xB7 Enter to accept";
|
|
7375
7376
|
var OFF = "\x1B[0m";
|
|
7377
|
+
var GREY = "\x1B[90m";
|
|
7378
|
+
var GREY_OFF = "\x1B[39m";
|
|
7376
7379
|
var HIDE_CURSOR = "\x1B[?25l";
|
|
7377
7380
|
var SHOW_CURSOR = "\x1B[?25h";
|
|
7378
7381
|
var CLEAR_LINE = "\x1B[2K";
|
|
7379
7382
|
var CLEAR_DOWN = "\x1B[0J";
|
|
7380
|
-
var up = (n) => `\x1B[${n}A
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7383
|
+
var up = (n) => n > 0 ? `\x1B[${n}A` : "";
|
|
7384
|
+
var ESCAPES = /\u001b\[[0-9;?]*[A-Za-z]|\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
|
|
7385
|
+
function eraseUp(lines) {
|
|
7386
|
+
return `\r${up(lines)}${CLEAR_DOWN}`;
|
|
7387
|
+
}
|
|
7388
|
+
function screenLines(text2, columns) {
|
|
7389
|
+
const width = [...text2.replace(ESCAPES, "")].length;
|
|
7390
|
+
if (!columns || columns < 1) return 1;
|
|
7391
|
+
return Math.max(1, Math.ceil(width / columns));
|
|
7392
|
+
}
|
|
7393
|
+
function runMenu(input, output, head, rows, onKey) {
|
|
7394
|
+
const { question, note, hint } = head;
|
|
7395
|
+
const width = () => head.columns?.();
|
|
7396
|
+
let headLines = 0;
|
|
7397
|
+
let drawn = 0;
|
|
7398
|
+
const frame = () => {
|
|
7399
|
+
const cols = width();
|
|
7400
|
+
const lines = [...rows(), ...fitHint(hint, cols)];
|
|
7401
|
+
drawn = lines.reduce((n, line) => n + screenLines(line, cols), 0);
|
|
7402
|
+
return lines.map((line) => `${CLEAR_LINE}\r${line}
|
|
7403
|
+
`).join("");
|
|
7404
|
+
};
|
|
7386
7405
|
const wasRaw = input.isRaw === true;
|
|
7387
|
-
return new Promise((
|
|
7406
|
+
return new Promise((resolve2, reject) => {
|
|
7388
7407
|
let done = false;
|
|
7389
7408
|
const finish = (outcome) => {
|
|
7390
7409
|
if (done) return;
|
|
@@ -7398,19 +7417,22 @@ function runMenu(input, output, question, hint, rows, onKey) {
|
|
|
7398
7417
|
}
|
|
7399
7418
|
input.pause();
|
|
7400
7419
|
if ("pick" in outcome) {
|
|
7401
|
-
output.write(
|
|
7420
|
+
output.write(`\r${up(headLines + drawn)}${CLEAR_DOWN}${question} ${outcome.summary}
|
|
7402
7421
|
${SHOW_CURSOR}`);
|
|
7403
|
-
|
|
7422
|
+
resolve2(outcome.pick);
|
|
7404
7423
|
} else {
|
|
7405
|
-
output.write(
|
|
7424
|
+
output.write(`\r${up(headLines + drawn)}${CLEAR_DOWN}${question}
|
|
7425
|
+
${SHOW_CURSOR}`);
|
|
7406
7426
|
reject(outcome.error);
|
|
7407
7427
|
}
|
|
7408
7428
|
};
|
|
7409
7429
|
const onKeypress = (text2, key) => {
|
|
7410
7430
|
const name = key?.name ?? text2;
|
|
7411
|
-
if (key?.ctrl && (name === "c" || name === "d")
|
|
7431
|
+
if (key?.ctrl && (name === "c" || name === "d") || name === "escape" || name === "q" && !key?.ctrl) {
|
|
7432
|
+
return finish({ error: new PromptCancelled() });
|
|
7433
|
+
}
|
|
7412
7434
|
const result = onKey(text2, name);
|
|
7413
|
-
if (result === "redraw") output.write(
|
|
7435
|
+
if (result === "redraw") output.write(`\r${up(drawn)}${CLEAR_DOWN}${frame()}`);
|
|
7414
7436
|
else if (result) finish(result);
|
|
7415
7437
|
};
|
|
7416
7438
|
const onError = (err) => finish({ error: err });
|
|
@@ -7418,8 +7440,11 @@ ${SHOW_CURSOR}`);
|
|
|
7418
7440
|
try {
|
|
7419
7441
|
emitKeypressEvents(input);
|
|
7420
7442
|
input.setRawMode?.(true);
|
|
7421
|
-
|
|
7422
|
-
|
|
7443
|
+
const cols = width();
|
|
7444
|
+
const top = [question, ...note ? wrapWords(note, cols) : []];
|
|
7445
|
+
headLines = top.reduce((n, line) => n + screenLines(line, cols), 0);
|
|
7446
|
+
output.write(`${HIDE_CURSOR}${top.map((line) => `${line}
|
|
7447
|
+
`).join("")}${frame()}`);
|
|
7423
7448
|
input.on("keypress", onKeypress);
|
|
7424
7449
|
input.on("error", onError);
|
|
7425
7450
|
process.on("SIGINT", onSigint);
|
|
@@ -7429,6 +7454,26 @@ ${draw()}`);
|
|
|
7429
7454
|
}
|
|
7430
7455
|
});
|
|
7431
7456
|
}
|
|
7457
|
+
function fitHint(hint, columns) {
|
|
7458
|
+
if (!columns || [...hint].length <= columns) return [hint];
|
|
7459
|
+
const lines = [];
|
|
7460
|
+
for (const part of hint.split(" \xB7 ")) {
|
|
7461
|
+
const last = lines[lines.length - 1];
|
|
7462
|
+
if (last !== void 0 && [...`${last} \xB7 ${part}`].length <= columns) lines[lines.length - 1] = `${last} \xB7 ${part}`;
|
|
7463
|
+
else lines.push(part);
|
|
7464
|
+
}
|
|
7465
|
+
return lines;
|
|
7466
|
+
}
|
|
7467
|
+
function wrapWords(text2, columns) {
|
|
7468
|
+
if (!columns || [...text2].length <= columns) return [text2];
|
|
7469
|
+
const lines = [];
|
|
7470
|
+
for (const word of text2.split(" ")) {
|
|
7471
|
+
const last = lines[lines.length - 1];
|
|
7472
|
+
if (last !== void 0 && [...`${last} ${word}`].length <= columns) lines[lines.length - 1] = `${last} ${word}`;
|
|
7473
|
+
else lines.push(word);
|
|
7474
|
+
}
|
|
7475
|
+
return lines;
|
|
7476
|
+
}
|
|
7432
7477
|
function marker(options) {
|
|
7433
7478
|
return options.color ? options.trueColor ? "\x1B[1;38;2;253;75;3m" : "\x1B[1;38;5;202m" : "\x1B[1m";
|
|
7434
7479
|
}
|
|
@@ -7442,7 +7487,7 @@ function selectMenu(input, output, question, choices, options = {}) {
|
|
|
7442
7487
|
let current = Math.min(Math.max(options.initial ?? 0, 0), choices.length - 1);
|
|
7443
7488
|
const on = marker(options);
|
|
7444
7489
|
const rows = () => choices.map((c, i) => i === current ? `${on}> ${c}${OFF}` : ` ${c}`);
|
|
7445
|
-
return runMenu(input, output, question, HINT, rows, (text2, name) => {
|
|
7490
|
+
return runMenu(input, output, { question, hint: HINT, columns: options.columns }, rows, (text2, name) => {
|
|
7446
7491
|
if (name === "return" || name === "enter") return { pick: current, summary: choices[current] ?? "" };
|
|
7447
7492
|
const next = move(current, choices.length, text2, name);
|
|
7448
7493
|
if (next === null || next === current) return null;
|
|
@@ -7451,14 +7496,23 @@ function selectMenu(input, output, question, choices, options = {}) {
|
|
|
7451
7496
|
});
|
|
7452
7497
|
}
|
|
7453
7498
|
function multiSelectMenu(input, output, question, choices, options = {}) {
|
|
7454
|
-
let current = 0;
|
|
7455
7499
|
const ticked = choices.map((_, i) => options.ticked?.[i] === true);
|
|
7456
|
-
|
|
7457
|
-
const
|
|
7458
|
-
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7500
|
+
let current = Math.max(ticked.indexOf(true), 0);
|
|
7501
|
+
const pointer = `${marker(options)}>${OFF}`;
|
|
7502
|
+
const pad = Math.max(...choices.map((c) => c.length));
|
|
7503
|
+
const label = (i) => {
|
|
7504
|
+
const tag = options.tags?.[i];
|
|
7505
|
+
const name = choices[i] ?? "";
|
|
7506
|
+
if (!tag) return name;
|
|
7507
|
+
const cols = options.columns?.() ?? Infinity;
|
|
7508
|
+
const grey = options.color ? `${GREY}${tag}${GREY_OFF}` : tag;
|
|
7509
|
+
if (6 + pad + 3 + tag.length <= cols) return `${name.padEnd(pad)} ${grey}`;
|
|
7510
|
+
if (6 + name.length + 1 + tag.length <= cols) return `${name} ${grey}`;
|
|
7511
|
+
return name;
|
|
7512
|
+
};
|
|
7513
|
+
const rows = () => choices.map((_, i) => `${i === current ? pointer : " "} [${ticked[i] ? "x" : " "}] ${label(i)}`);
|
|
7514
|
+
const head = { question, note: options.note, hint: options.hint ?? MULTI_HINT, columns: options.columns };
|
|
7515
|
+
return runMenu(input, output, head, rows, (text2, name) => {
|
|
7462
7516
|
if (name === "return" || name === "enter") {
|
|
7463
7517
|
const picks = choices.flatMap((_, i) => ticked[i] ? [i] : []);
|
|
7464
7518
|
return { pick: picks, summary: picks.map((i) => choices[i]).join(", ") || "none" };
|
|
@@ -7478,6 +7532,42 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
|
|
|
7478
7532
|
return "redraw";
|
|
7479
7533
|
});
|
|
7480
7534
|
}
|
|
7535
|
+
function listenForEnter(input, onEnter, onCancel) {
|
|
7536
|
+
const wasRaw = input.isRaw === true;
|
|
7537
|
+
let done = false;
|
|
7538
|
+
const stop = () => {
|
|
7539
|
+
if (done) return;
|
|
7540
|
+
done = true;
|
|
7541
|
+
input.removeListener("keypress", onKeypress);
|
|
7542
|
+
input.removeListener("error", stop);
|
|
7543
|
+
process.removeListener("SIGINT", cancel);
|
|
7544
|
+
try {
|
|
7545
|
+
input.setRawMode?.(wasRaw);
|
|
7546
|
+
} catch {
|
|
7547
|
+
}
|
|
7548
|
+
input.pause();
|
|
7549
|
+
};
|
|
7550
|
+
const cancel = () => {
|
|
7551
|
+
if (done) return;
|
|
7552
|
+
stop();
|
|
7553
|
+
onCancel();
|
|
7554
|
+
};
|
|
7555
|
+
const onKeypress = (_text, key) => {
|
|
7556
|
+
if (key?.ctrl && key.name === "c") cancel();
|
|
7557
|
+
else if (key?.name === "return" || key?.name === "enter") onEnter();
|
|
7558
|
+
};
|
|
7559
|
+
try {
|
|
7560
|
+
emitKeypressEvents(input);
|
|
7561
|
+
input.setRawMode?.(true);
|
|
7562
|
+
input.on("keypress", onKeypress);
|
|
7563
|
+
input.on("error", stop);
|
|
7564
|
+
process.on("SIGINT", cancel);
|
|
7565
|
+
input.resume();
|
|
7566
|
+
} catch {
|
|
7567
|
+
stop();
|
|
7568
|
+
}
|
|
7569
|
+
return stop;
|
|
7570
|
+
}
|
|
7481
7571
|
|
|
7482
7572
|
// src/io.ts
|
|
7483
7573
|
var PromptCancelled = class extends Error {
|
|
@@ -7486,20 +7576,20 @@ var PromptCancelled = class extends Error {
|
|
|
7486
7576
|
this.name = "PromptCancelled";
|
|
7487
7577
|
}
|
|
7488
7578
|
};
|
|
7489
|
-
var runProcess = (command, args, options) => new Promise((
|
|
7579
|
+
var runProcess = (command, args, options) => new Promise((resolve2) => {
|
|
7490
7580
|
let stdout = "";
|
|
7491
7581
|
let stderr = "";
|
|
7492
7582
|
let child;
|
|
7493
7583
|
try {
|
|
7494
7584
|
child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
7495
7585
|
} catch (err) {
|
|
7496
|
-
|
|
7586
|
+
resolve2({ code: -1, stdout: "", stderr: String(err) });
|
|
7497
7587
|
return;
|
|
7498
7588
|
}
|
|
7499
7589
|
child.stdout.on("data", (d) => stdout += d.toString("utf8"));
|
|
7500
7590
|
child.stderr.on("data", (d) => stderr += d.toString("utf8"));
|
|
7501
|
-
child.on("error", (err) =>
|
|
7502
|
-
child.on("close", (code) =>
|
|
7591
|
+
child.on("error", (err) => resolve2({ code: -1, stdout, stderr: String(err) }));
|
|
7592
|
+
child.on("close", (code) => resolve2({ code: code ?? -1, stdout, stderr }));
|
|
7503
7593
|
child.stdin.on("error", () => {
|
|
7504
7594
|
});
|
|
7505
7595
|
if (options?.input !== void 0) child.stdin.end(options.input);
|
|
@@ -7512,7 +7602,7 @@ async function readAllStdin() {
|
|
|
7512
7602
|
}
|
|
7513
7603
|
function promptLine(question) {
|
|
7514
7604
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7515
|
-
return new Promise((
|
|
7605
|
+
return new Promise((resolve2, reject) => {
|
|
7516
7606
|
let settled = false;
|
|
7517
7607
|
const cancel = () => {
|
|
7518
7608
|
if (settled) return;
|
|
@@ -7526,7 +7616,7 @@ function promptLine(question) {
|
|
|
7526
7616
|
rl.question(question, (answer) => {
|
|
7527
7617
|
settled = true;
|
|
7528
7618
|
rl.close();
|
|
7529
|
-
|
|
7619
|
+
resolve2(answer);
|
|
7530
7620
|
});
|
|
7531
7621
|
});
|
|
7532
7622
|
}
|
|
@@ -7540,6 +7630,15 @@ function uninterruptible(fn) {
|
|
|
7540
7630
|
setImmediate(() => process.off("SIGINT", hold));
|
|
7541
7631
|
}
|
|
7542
7632
|
}
|
|
7633
|
+
function isExecutable(path) {
|
|
7634
|
+
try {
|
|
7635
|
+
if (!statSync(path).isFile()) return false;
|
|
7636
|
+
accessSync(path, constants.X_OK);
|
|
7637
|
+
return true;
|
|
7638
|
+
} catch {
|
|
7639
|
+
return false;
|
|
7640
|
+
}
|
|
7641
|
+
}
|
|
7543
7642
|
function openerFor(platform, url) {
|
|
7544
7643
|
if (platform === "darwin") return ["open", [url]];
|
|
7545
7644
|
if (platform === "win32") return ["cmd", ["/c", "start", '""', url]];
|
|
@@ -7557,12 +7656,15 @@ function realIo() {
|
|
|
7557
7656
|
prompt: promptLine,
|
|
7558
7657
|
select: (question, choices, options) => selectMenu(process.stdin, process.stderr, question, choices, {
|
|
7559
7658
|
...options,
|
|
7560
|
-
trueColor: /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? "")
|
|
7659
|
+
trueColor: /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? ""),
|
|
7660
|
+
columns: () => process.stderr.columns
|
|
7561
7661
|
}),
|
|
7562
7662
|
multiSelect: (question, choices, options) => multiSelectMenu(process.stdin, process.stderr, question, choices, {
|
|
7563
7663
|
...options,
|
|
7564
|
-
trueColor: /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? "")
|
|
7664
|
+
trueColor: /^(truecolor|24bit)$/i.test(process.env.COLORTERM ?? ""),
|
|
7665
|
+
columns: () => process.stderr.columns
|
|
7565
7666
|
}),
|
|
7667
|
+
listenForEnter: (onEnter, onCancel) => listenForEnter(process.stdin, onEnter, onCancel),
|
|
7566
7668
|
uninterruptible,
|
|
7567
7669
|
run: runProcess,
|
|
7568
7670
|
openUrl: async (url) => {
|
|
@@ -7570,13 +7672,15 @@ function realIo() {
|
|
|
7570
7672
|
return (await runProcess(cmd, args)).code === 0;
|
|
7571
7673
|
},
|
|
7572
7674
|
homedir: homedir(),
|
|
7675
|
+
scriptPath: process.argv[1],
|
|
7676
|
+
columns: () => process.stderr.columns,
|
|
7677
|
+
isExecutable,
|
|
7573
7678
|
cwd: process.cwd(),
|
|
7574
7679
|
platform: process.platform,
|
|
7575
7680
|
hostname: hostname(),
|
|
7576
7681
|
now: () => Date.now(),
|
|
7577
7682
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
7578
7683
|
loginTimeoutMs: 9e5,
|
|
7579
|
-
browserGraceMs: 6e4,
|
|
7580
7684
|
fetchTimeoutMs: 15e3
|
|
7581
7685
|
};
|
|
7582
7686
|
}
|
|
@@ -7584,6 +7688,18 @@ function realIo() {
|
|
|
7584
7688
|
// src/args.ts
|
|
7585
7689
|
import { parseArgs } from "util";
|
|
7586
7690
|
|
|
7691
|
+
// src/command-name.ts
|
|
7692
|
+
var prefix = "amdahl";
|
|
7693
|
+
function startedByNpx(io) {
|
|
7694
|
+
return io.env.npm_command === "exec" || io.env.npm_lifecycle_event === "npx" || /[\\/]_npx[\\/]/.test(io.scriptPath ?? "");
|
|
7695
|
+
}
|
|
7696
|
+
function setCommandName(io) {
|
|
7697
|
+
prefix = startedByNpx(io) ? "npx amdahl" : "amdahl";
|
|
7698
|
+
}
|
|
7699
|
+
function amdahlCommand(rest = "") {
|
|
7700
|
+
return rest ? `${prefix} ${rest}` : prefix;
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7587
7703
|
// src/errors.ts
|
|
7588
7704
|
var EXIT = {
|
|
7589
7705
|
ok: 0,
|
|
@@ -7620,7 +7736,10 @@ var CliError = class extends Error {
|
|
|
7620
7736
|
function usageError(message2, hint) {
|
|
7621
7737
|
return new CliError("usage", message2, EXIT.usage, hint);
|
|
7622
7738
|
}
|
|
7623
|
-
|
|
7739
|
+
function notSignedIn() {
|
|
7740
|
+
return `Not signed in. Run \`${amdahlCommand("login")}\`, or set AMDAHL_KEY.`;
|
|
7741
|
+
}
|
|
7742
|
+
var NETWORK_HINT = "Check your internet connection, then run the same command again.";
|
|
7624
7743
|
var CODE_EXITS = {
|
|
7625
7744
|
oauth_required: EXIT.forbidden,
|
|
7626
7745
|
cli_client_required: EXIT.forbidden,
|
|
@@ -7658,7 +7777,10 @@ var GLOBAL_OPTIONS = {
|
|
|
7658
7777
|
version: { type: "boolean", short: "v" },
|
|
7659
7778
|
verbose: { type: "boolean" }
|
|
7660
7779
|
};
|
|
7780
|
+
var SETUP_OPTIONS = { "no-browser": { type: "boolean" } };
|
|
7661
7781
|
var COMMAND_OPTIONS = {
|
|
7782
|
+
setup: SETUP_OPTIONS,
|
|
7783
|
+
help: {},
|
|
7662
7784
|
login: {
|
|
7663
7785
|
workspace: { type: "string" },
|
|
7664
7786
|
"no-browser": { type: "boolean" },
|
|
@@ -7711,12 +7833,12 @@ function parseCommandLine(argv) {
|
|
|
7711
7833
|
try {
|
|
7712
7834
|
loose = parseArgs({ args: argv, options: allOptions(), allowPositionals: true, strict: false });
|
|
7713
7835
|
} catch (err) {
|
|
7714
|
-
throw usageError(
|
|
7836
|
+
throw usageError(...parseErrorMessage(err));
|
|
7715
7837
|
}
|
|
7716
7838
|
const positionals = loose.positionals;
|
|
7717
7839
|
const first = positionals[0];
|
|
7718
7840
|
if (first === void 0) {
|
|
7719
|
-
return { command: null, args: [], flags: strictParse(argv, GLOBAL_OPTIONS).values };
|
|
7841
|
+
return { command: null, args: [], flags: strictParse(argv, { ...GLOBAL_OPTIONS, ...SETUP_OPTIONS }).values };
|
|
7720
7842
|
}
|
|
7721
7843
|
let words = 1;
|
|
7722
7844
|
let name = first;
|
|
@@ -7735,7 +7857,7 @@ function parseCommandLine(argv) {
|
|
|
7735
7857
|
}
|
|
7736
7858
|
name = ALIASES[name] ?? name;
|
|
7737
7859
|
const spec = COMMAND_OPTIONS[name];
|
|
7738
|
-
if (!spec) throw usageError(`Unknown command \`${positionals.slice(0, words).join(" ")}
|
|
7860
|
+
if (!spec) throw usageError(`Unknown command \`${positionals.slice(0, words).join(" ")}\`.`, `Run \`${amdahlCommand("--help")}\` to see the commands.`);
|
|
7739
7861
|
const strict = strictParse(argv, { ...GLOBAL_OPTIONS, ...spec });
|
|
7740
7862
|
return { command: name, args: strict.positionals.slice(words), flags: strict.values };
|
|
7741
7863
|
}
|
|
@@ -7747,39 +7869,62 @@ function strictParse(argv, options) {
|
|
|
7747
7869
|
values: parsed.values
|
|
7748
7870
|
};
|
|
7749
7871
|
} catch (err) {
|
|
7750
|
-
throw usageError(
|
|
7872
|
+
throw usageError(...parseErrorMessage(err));
|
|
7751
7873
|
}
|
|
7752
7874
|
}
|
|
7875
|
+
function parseErrorMessage(err) {
|
|
7876
|
+
const message2 = err instanceof Error ? err.message : String(err);
|
|
7877
|
+
const code = err?.code;
|
|
7878
|
+
const help = `Run \`${amdahlCommand("--help")}\`.`;
|
|
7879
|
+
if (code === "ERR_PARSE_ARGS_UNKNOWN_OPTION") {
|
|
7880
|
+
const option = /'(-[^']*)'/.exec(message2)?.[1] ?? message2;
|
|
7881
|
+
return [`Unknown option ${option}.`, help];
|
|
7882
|
+
}
|
|
7883
|
+
if (code === "ERR_PARSE_ARGS_INVALID_OPTION_VALUE") {
|
|
7884
|
+
const option = /'(--?[\w-]+)/.exec(message2)?.[1];
|
|
7885
|
+
if (option && /argument missing/.test(message2)) return [`${option} needs a value.`, help];
|
|
7886
|
+
if (option) return [`${option} takes no value.`, help];
|
|
7887
|
+
}
|
|
7888
|
+
return [message2, help];
|
|
7889
|
+
}
|
|
7753
7890
|
var USAGE = `Usage: amdahl <command> [options]
|
|
7754
7891
|
|
|
7755
7892
|
What you can do:
|
|
7756
|
-
amdahl Set up Claude Code, Cursor or Codex
|
|
7757
|
-
|
|
7758
|
-
|
|
7893
|
+
amdahl Set up Claude Code, Cursor or Codex. In a
|
|
7894
|
+
terminal it asks which and signs you in.
|
|
7895
|
+
Run by an agent or with no terminal, it
|
|
7896
|
+
sets up that agent, or every client it
|
|
7897
|
+
finds, and asks nothing (also: amdahl setup)
|
|
7759
7898
|
amdahl optimize draft.md Rewrite an outbound email or LinkedIn message
|
|
7760
|
-
amdahl status Check that this workspace
|
|
7899
|
+
amdahl status Check that this workspace can optimize
|
|
7761
7900
|
amdahl keys create --name ci Make an API key for a server or CI
|
|
7762
|
-
amdahl install claude-code Connect
|
|
7901
|
+
amdahl install claude-code Connect one AI client (also codex, cursor)
|
|
7763
7902
|
amdahl skills add <publisher>/<skill>
|
|
7764
|
-
Add a skill from https://amdahl.ai/skills
|
|
7765
|
-
its SKILL.md, or its
|
|
7766
|
-
|
|
7903
|
+
Add a skill from https://amdahl.ai/skills
|
|
7904
|
+
to this project: its SKILL.md, or its folder
|
|
7905
|
+
Everything Amdahl can do: https://docs.amdahl.ai/skills/amdahl/SKILL.md
|
|
7767
7906
|
|
|
7768
7907
|
Commands:
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7908
|
+
setup [--no-browser] The guided setup (bare amdahl)
|
|
7909
|
+
login [--workspace <slug>] [--no-browser] [--port <n>]
|
|
7910
|
+
Sign in (alias: auth login)
|
|
7911
|
+
logout [--all] Sign out (alias: auth logout)
|
|
7912
|
+
whoami [--show-token] Who you are (alias: auth status)
|
|
7913
|
+
auth token Print a fresh access token
|
|
7914
|
+
status Can this workspace optimize?
|
|
7915
|
+
optimize [<file>... | -] [--channel email|linkedin]
|
|
7916
|
+
[--evidence off|workspace] [--on-unsupported flag|remove]
|
|
7917
|
+
[--tries] [--context <file.json>]
|
|
7918
|
+
keys create --name <name> [--preset read-only|agent|internal|admin]
|
|
7919
|
+
[--expires 30d|90d|365d] [--no-browser]
|
|
7777
7920
|
keys list
|
|
7778
7921
|
keys revoke <id|prefix> [--yes] [--no-browser]
|
|
7779
7922
|
workspace list | workspace use <profile>
|
|
7780
7923
|
install claude-code|codex|cursor [--print] [--force]
|
|
7781
|
-
skills add <publisher>/<skill> [-y] [-g] [--agents] [--force]
|
|
7782
|
-
|
|
7924
|
+
skills add <publisher>/<skill> [-y] [-g] [--agents] [--force]
|
|
7925
|
+
[--accept-warning] [--site <origin>]
|
|
7926
|
+
In a terminal it asks one question: this project (Enter) or
|
|
7927
|
+
every project.
|
|
7783
7928
|
-y add without asking, to this project
|
|
7784
7929
|
-g every project (~/.claude/skills), not just this one
|
|
7785
7930
|
--agents also .agents/skills, for Codex, Cursor and other agents
|
|
@@ -7789,14 +7934,19 @@ Commands:
|
|
|
7789
7934
|
after printing the warning. Without it, a terminal asks
|
|
7790
7935
|
(Cancel or Install anyway); -y, --json or no terminal
|
|
7791
7936
|
stops (exit 9)
|
|
7937
|
+
help Show this help
|
|
7792
7938
|
|
|
7793
7939
|
Global options:
|
|
7794
|
-
--profile <name> Use a saved profile (default: AMDAHL_PROFILE, then the
|
|
7795
|
-
|
|
7796
|
-
--api-
|
|
7940
|
+
--profile <name> Use a saved profile (default: AMDAHL_PROFILE, then the
|
|
7941
|
+
default profile)
|
|
7942
|
+
--api-url <url> API host (default: AMDAHL_API_URL, then the profile,
|
|
7943
|
+
then https://app.amdahl.ai)
|
|
7944
|
+
--api-key <key> Use an API key for this command (prefer AMDAHL_KEY: argv
|
|
7945
|
+
is visible to other users)
|
|
7797
7946
|
--json Data on stdout as JSON, messages on stderr
|
|
7798
7947
|
--no-color No colour (NO_COLOR is honoured too)
|
|
7799
|
-
--verbose Say more: files written, the profile, where the sign-in
|
|
7948
|
+
--verbose Say more: files written, the profile, where the sign-in
|
|
7949
|
+
is stored
|
|
7800
7950
|
-h, --help Show this help
|
|
7801
7951
|
-v, --version Show the version
|
|
7802
7952
|
`;
|
|
@@ -7995,7 +8145,7 @@ async function send(url, init) {
|
|
|
7995
8145
|
return await fetch(url, init);
|
|
7996
8146
|
} catch (err) {
|
|
7997
8147
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
7998
|
-
throw new CliError("network_error", `Could not reach ${new URL(url).host}: ${message2}`, EXIT.network);
|
|
8148
|
+
throw new CliError("network_error", `Could not reach ${new URL(url).host}: ${message2}`, EXIT.network, NETWORK_HINT);
|
|
7999
8149
|
}
|
|
8000
8150
|
}
|
|
8001
8151
|
async function discover(apiUrl) {
|
|
@@ -8106,7 +8256,7 @@ function signInAgainLine(reason, url) {
|
|
|
8106
8256
|
function noBusinessMessage(consoleUrl, mayCreate, reason) {
|
|
8107
8257
|
switch (reason) {
|
|
8108
8258
|
case void 0:
|
|
8109
|
-
return mayCreate ? `You're signed in, but you don't have a workspace yet. Create one at ${consoleUrl}/new, then run
|
|
8259
|
+
return mayCreate ? `You're signed in, but you don't have a workspace yet. Create one at ${consoleUrl}/new, then run \`${amdahlCommand("login")}\` again.` : `You're signed in, but you don't have access yet. Amdahl is in beta: join the waitlist at ${consoleUrl}/new, then run \`${amdahlCommand("login")}\` again once you're in.`;
|
|
8110
8260
|
case "domain_has_workspace":
|
|
8111
8261
|
return `Your company already has an Amdahl workspace. Ask its admins to let you in at ${consoleUrl}, then try again.`;
|
|
8112
8262
|
case "invite_only":
|
|
@@ -8142,8 +8292,8 @@ async function startLoopback(io, input) {
|
|
|
8142
8292
|
let refused = null;
|
|
8143
8293
|
let settle;
|
|
8144
8294
|
let fail;
|
|
8145
|
-
const result = new Promise((
|
|
8146
|
-
settle =
|
|
8295
|
+
const result = new Promise((resolve2, reject) => {
|
|
8296
|
+
settle = resolve2;
|
|
8147
8297
|
fail = reject;
|
|
8148
8298
|
});
|
|
8149
8299
|
result.catch(() => {
|
|
@@ -8159,7 +8309,7 @@ async function startLoopback(io, input) {
|
|
|
8159
8309
|
res.end(page(body));
|
|
8160
8310
|
};
|
|
8161
8311
|
if (url.searchParams.get("state") !== input.state) {
|
|
8162
|
-
send2(400,
|
|
8312
|
+
send2(400, `<p>This sign-in did not match the one your terminal started. Run <code>${amdahlCommand("login")}</code> again.</p>`);
|
|
8163
8313
|
fail(new CliError("state_mismatch", "The sign-in response did not match this login (state mismatch).", EXIT.general));
|
|
8164
8314
|
return;
|
|
8165
8315
|
}
|
|
@@ -8189,7 +8339,7 @@ async function startLoopback(io, input) {
|
|
|
8189
8339
|
settle({ kind: "error", error: "invalid_request", description: "The callback carried no code." });
|
|
8190
8340
|
}
|
|
8191
8341
|
});
|
|
8192
|
-
await new Promise((
|
|
8342
|
+
await new Promise((resolve2, reject) => {
|
|
8193
8343
|
server.once(
|
|
8194
8344
|
"error",
|
|
8195
8345
|
(err) => reject(
|
|
@@ -8201,12 +8351,12 @@ async function startLoopback(io, input) {
|
|
|
8201
8351
|
)
|
|
8202
8352
|
)
|
|
8203
8353
|
);
|
|
8204
|
-
server.listen(input.port, "127.0.0.1", () =>
|
|
8354
|
+
server.listen(input.port, "127.0.0.1", () => resolve2());
|
|
8205
8355
|
});
|
|
8206
8356
|
const port = server.address().port;
|
|
8207
8357
|
const timer = setTimeout(() => {
|
|
8208
8358
|
fail(
|
|
8209
|
-
refused ? new CliError("no_business", noBusinessMessage(input.consoleUrl, false, refused), EXIT.noWorkspace) : new CliError("login_timeout", "Timed out waiting for the browser sign-in.", EXIT.general,
|
|
8359
|
+
refused ? new CliError("no_business", noBusinessMessage(input.consoleUrl, false, refused), EXIT.noWorkspace) : new CliError("login_timeout", "Timed out waiting for the browser sign-in.", EXIT.general, `Run \`${amdahlCommand("login")}\` again.`)
|
|
8210
8360
|
);
|
|
8211
8361
|
}, input.timeoutMs);
|
|
8212
8362
|
timer.unref?.();
|
|
@@ -8233,7 +8383,7 @@ function consoleUrlFor(io, apiUrl) {
|
|
|
8233
8383
|
}
|
|
8234
8384
|
|
|
8235
8385
|
// src/secrets.ts
|
|
8236
|
-
import { chmodSync as chmodSync2, existsSync as existsSync2, readFileSync as readFileSync2, rmSync, statSync } from "fs";
|
|
8386
|
+
import { chmodSync as chmodSync2, existsSync as existsSync2, readFileSync as readFileSync2, rmSync, statSync as statSync2 } from "fs";
|
|
8237
8387
|
import { join as join2 } from "path";
|
|
8238
8388
|
var KEYCHAIN_SERVICE = "amdahl-cli";
|
|
8239
8389
|
function keychainAccount(apiUrl, profile) {
|
|
@@ -8326,7 +8476,7 @@ function fileStore(io, note) {
|
|
|
8326
8476
|
const checkMode = () => {
|
|
8327
8477
|
if (checked || io.platform === "win32" || !existsSync2(path)) return;
|
|
8328
8478
|
checked = true;
|
|
8329
|
-
if ((
|
|
8479
|
+
if ((statSync2(path).mode & 63) === 0) return;
|
|
8330
8480
|
try {
|
|
8331
8481
|
chmodSync2(path, 384);
|
|
8332
8482
|
} catch {
|
|
@@ -8335,7 +8485,7 @@ function fileStore(io, note) {
|
|
|
8335
8485
|
return;
|
|
8336
8486
|
}
|
|
8337
8487
|
io.stderr(
|
|
8338
|
-
`Warning: ${path} was readable by other users on this machine. It is private now; to be safe, run
|
|
8488
|
+
`Warning: ${path} was readable by other users on this machine. It is private now; to be safe, run \`${amdahlCommand("logout")}\` and sign in again.
|
|
8339
8489
|
`
|
|
8340
8490
|
);
|
|
8341
8491
|
};
|
|
@@ -8467,7 +8617,7 @@ async function requireOAuthCredential(ctx) {
|
|
|
8467
8617
|
"oauth_required",
|
|
8468
8618
|
`This command needs your own sign-in, but a credential from ${winner === "flag" ? "--api-key" : winner} takes precedence.`,
|
|
8469
8619
|
EXIT.forbidden,
|
|
8470
|
-
|
|
8620
|
+
`Unset it and run \`${amdahlCommand("login")}\`.`
|
|
8471
8621
|
);
|
|
8472
8622
|
}
|
|
8473
8623
|
return profileCredential(ctx);
|
|
@@ -8475,20 +8625,20 @@ async function requireOAuthCredential(ctx) {
|
|
|
8475
8625
|
async function profileCredential(ctx) {
|
|
8476
8626
|
const { profile, profileName } = ctx;
|
|
8477
8627
|
if (!profile || !profileName) {
|
|
8478
|
-
throw new CliError("not_signed_in",
|
|
8628
|
+
throw new CliError("not_signed_in", notSignedIn(), EXIT.unauthenticated);
|
|
8479
8629
|
}
|
|
8480
8630
|
if (normaliseApiUrl(profile.api_url) !== ctx.apiUrl) {
|
|
8481
8631
|
throw new CliError(
|
|
8482
8632
|
"profile_host_mismatch",
|
|
8483
8633
|
`Profile "${profileName}" is signed in to ${profile.api_url}, not ${ctx.apiUrl}.`,
|
|
8484
8634
|
EXIT.usage,
|
|
8485
|
-
|
|
8635
|
+
`Drop --api-url / AMDAHL_API_URL, or run \`${amdahlCommand("login")}\` against that host.`
|
|
8486
8636
|
);
|
|
8487
8637
|
}
|
|
8488
8638
|
const account = keychainAccount(profile.api_url, profileName);
|
|
8489
8639
|
const store = ctx.secrets();
|
|
8490
8640
|
const secrets = await store.get(account);
|
|
8491
|
-
if (!secrets) throw new CliError("not_signed_in",
|
|
8641
|
+
if (!secrets) throw new CliError("not_signed_in", notSignedIn(), EXIT.unauthenticated);
|
|
8492
8642
|
const left = Date.parse(secrets.access_expires_at) - ctx.io.now();
|
|
8493
8643
|
if (Number.isFinite(left) && left >= 6e4) {
|
|
8494
8644
|
return { source: "profile", token: secrets.access_token, profile: profileName };
|
|
@@ -8500,7 +8650,7 @@ async function profileCredential(ctx) {
|
|
|
8500
8650
|
"invalid_grant",
|
|
8501
8651
|
"Your sign-in has expired.",
|
|
8502
8652
|
EXIT.unauthenticated,
|
|
8503
|
-
|
|
8653
|
+
`Run \`${amdahlCommand("login")}\` again.`
|
|
8504
8654
|
);
|
|
8505
8655
|
}
|
|
8506
8656
|
await store.set(account, refreshed);
|
|
@@ -8521,16 +8671,16 @@ function errorDetails(body) {
|
|
|
8521
8671
|
}
|
|
8522
8672
|
function toCliError(err) {
|
|
8523
8673
|
if (err instanceof CliError) return err;
|
|
8524
|
-
if (err instanceof PromptCancelled) return new CliError("cancelled", "Stopped.", EXIT.cancelled);
|
|
8674
|
+
if (err instanceof PromptCancelled) return new CliError("cancelled", "Stopped. Nothing changed.", EXIT.cancelled);
|
|
8525
8675
|
if (err instanceof AmdahlApiError) {
|
|
8526
8676
|
const exit = exitForHttp(err.statusCode, err.errorCode);
|
|
8527
8677
|
const details = errorDetails(err.responseBody);
|
|
8528
8678
|
const retry = details.retry_after_seconds;
|
|
8529
|
-
const hint = exit === EXIT.unauthenticated ?
|
|
8679
|
+
const hint = exit === EXIT.unauthenticated ? `Run \`${amdahlCommand("login")}\`, or check AMDAHL_KEY.` : typeof retry === "number" ? `Try again in ${retry} seconds.` : void 0;
|
|
8530
8680
|
return new CliError(err.errorCode ?? `http_${err.statusCode}`, err.message, exit, hint);
|
|
8531
8681
|
}
|
|
8532
8682
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
8533
|
-
return new CliError("network_error", `Could not reach the Amdahl API: ${message2}`, EXIT.network);
|
|
8683
|
+
return new CliError("network_error", `Could not reach the Amdahl API: ${message2}`, EXIT.network, NETWORK_HINT);
|
|
8534
8684
|
}
|
|
8535
8685
|
async function call(fn) {
|
|
8536
8686
|
try {
|
|
@@ -8545,6 +8695,18 @@ var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
|
8545
8695
|
function printable(text2) {
|
|
8546
8696
|
return text2.replace(CONTROL_RE, "");
|
|
8547
8697
|
}
|
|
8698
|
+
var HYPERLINK_TERMINALS = /* @__PURE__ */ new Set(["iTerm.app", "vscode", "WarpTerminal", "Apple_Terminal", "WezTerm", "ghostty"]);
|
|
8699
|
+
function supportsHyperlinks(env, color) {
|
|
8700
|
+
if (env.FORCE_HYPERLINK === "0") return false;
|
|
8701
|
+
if (env.FORCE_HYPERLINK === "1") return true;
|
|
8702
|
+
if (!color) return false;
|
|
8703
|
+
if (HYPERLINK_TERMINALS.has(env.TERM_PROGRAM ?? "")) return true;
|
|
8704
|
+
if (Number(env.VTE_VERSION) >= 5e3) return true;
|
|
8705
|
+
return Boolean(env.WT_SESSION);
|
|
8706
|
+
}
|
|
8707
|
+
function hyperlink(text2, url) {
|
|
8708
|
+
return `\x1B]8;;${printable(url)}\x1B\\${text2}\x1B]8;;\x1B\\`;
|
|
8709
|
+
}
|
|
8548
8710
|
function createOutput(io, json, noColor) {
|
|
8549
8711
|
const color = !noColor && !io.env.NO_COLOR && io.env.TERM !== "dumb" && io.stderrIsTTY && !json;
|
|
8550
8712
|
return {
|
|
@@ -8593,7 +8755,8 @@ async function browserSignIn(ctx) {
|
|
|
8593
8755
|
const endpoints = await discover(ctx.apiUrl);
|
|
8594
8756
|
const pkce = createPkce();
|
|
8595
8757
|
const consoleUrl = consoleUrlFor(io, ctx.apiUrl);
|
|
8596
|
-
let
|
|
8758
|
+
let stopKeys = () => {
|
|
8759
|
+
};
|
|
8597
8760
|
let linkShown = false;
|
|
8598
8761
|
const loopback = await startLoopback(io, {
|
|
8599
8762
|
port,
|
|
@@ -8602,7 +8765,6 @@ async function browserSignIn(ctx) {
|
|
|
8602
8765
|
timeoutMs: io.loginTimeoutMs,
|
|
8603
8766
|
// The page carries the way on; the authorize link would only sign in as the same address again.
|
|
8604
8767
|
onSignInAgain: (reason, retry) => {
|
|
8605
|
-
clearTimeout(timer);
|
|
8606
8768
|
linkShown = true;
|
|
8607
8769
|
out.info(signInAgainLine(reason, retry));
|
|
8608
8770
|
}
|
|
@@ -8619,18 +8781,30 @@ async function browserSignIn(ctx) {
|
|
|
8619
8781
|
${url}
|
|
8620
8782
|
`);
|
|
8621
8783
|
};
|
|
8784
|
+
let cancelled;
|
|
8785
|
+
let fallbackLine = null;
|
|
8622
8786
|
if (flags["no-browser"]) {
|
|
8623
8787
|
showLink("Open this URL in a browser to sign in:");
|
|
8624
8788
|
} else {
|
|
8625
8789
|
out.info("Opening your browser\u2026");
|
|
8790
|
+
if (!ctx.nonInteractive && io.stderrIsTTY) {
|
|
8791
|
+
const fallback = supportsHyperlinks(io.env, out.color) ? hyperlink("Open the sign-in page", url) : "Press Enter for the link.";
|
|
8792
|
+
fallbackLine = `Browser didn't open? ${fallback}`;
|
|
8793
|
+
out.info(fallbackLine);
|
|
8794
|
+
cancelled = new Promise((_, reject) => {
|
|
8795
|
+
stopKeys = io.listenForEnter(
|
|
8796
|
+
() => showLink("Open this link to sign in:"),
|
|
8797
|
+
() => reject(new PromptCancelled())
|
|
8798
|
+
);
|
|
8799
|
+
});
|
|
8800
|
+
}
|
|
8626
8801
|
void io.openUrl(url).then((opened) => {
|
|
8627
8802
|
if (!opened) showLink("Your browser did not open. Open this link to sign in:");
|
|
8628
8803
|
});
|
|
8629
|
-
timer = setTimeout(() => showLink("Finish in your browser, or open this link:"), io.browserGraceMs);
|
|
8630
|
-
timer.unref?.();
|
|
8631
8804
|
}
|
|
8632
|
-
const callback = await loopback.result;
|
|
8633
|
-
|
|
8805
|
+
const callback = await (cancelled ? Promise.race([loopback.result, cancelled]) : loopback.result);
|
|
8806
|
+
stopKeys();
|
|
8807
|
+
if (fallbackLine && !linkShown && callback.kind === "code") io.stderr(eraseUp(screenLines(fallbackLine, io.columns())));
|
|
8634
8808
|
linkShown = true;
|
|
8635
8809
|
if (callback.kind === "no_business") {
|
|
8636
8810
|
throw new CliError("no_business", noBusinessMessage(consoleUrl, callback.mayCreate, callback.reason), EXIT.noWorkspace);
|
|
@@ -8664,7 +8838,7 @@ async function browserSignIn(ctx) {
|
|
|
8664
8838
|
ctx.saveConfig();
|
|
8665
8839
|
return { profile: name, me, workspace: ws };
|
|
8666
8840
|
} finally {
|
|
8667
|
-
|
|
8841
|
+
stopKeys();
|
|
8668
8842
|
loopback.close();
|
|
8669
8843
|
}
|
|
8670
8844
|
}
|
|
@@ -8770,8 +8944,8 @@ async function authToken(ctx) {
|
|
|
8770
8944
|
}
|
|
8771
8945
|
|
|
8772
8946
|
// src/commands/install.ts
|
|
8773
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
8774
|
-
import { dirname, join as join4 } from "path";
|
|
8947
|
+
import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync3, lstatSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, readlinkSync, realpathSync, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
8948
|
+
import { basename, dirname, join as join4, resolve } from "path";
|
|
8775
8949
|
|
|
8776
8950
|
// src/skill.ts
|
|
8777
8951
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -8825,23 +8999,136 @@ async function onPath(ctx, command) {
|
|
|
8825
8999
|
return res.code === 0;
|
|
8826
9000
|
}
|
|
8827
9001
|
var CLIENT_SIGNS = {
|
|
8828
|
-
"claude-code": { command: "claude",
|
|
8829
|
-
codex: { command: "codex",
|
|
8830
|
-
cursor: { command: "cursor",
|
|
9002
|
+
"claude-code": { command: "claude", paths: [".claude", ".claude.json"] },
|
|
9003
|
+
codex: { command: "codex", paths: [".codex"] },
|
|
9004
|
+
cursor: { command: "cursor", paths: [".cursor"] }
|
|
8831
9005
|
};
|
|
8832
9006
|
async function detectClients(ctx) {
|
|
8833
9007
|
const found = await Promise.all(
|
|
8834
9008
|
CLIENTS.map(async (client) => {
|
|
8835
|
-
const { command,
|
|
8836
|
-
return existsSync3(join4(ctx.io.homedir,
|
|
9009
|
+
const { command, paths } = CLIENT_SIGNS[client];
|
|
9010
|
+
return paths.some((p) => existsSync3(join4(ctx.io.homedir, p))) || await onPath(ctx, command);
|
|
8837
9011
|
})
|
|
8838
9012
|
);
|
|
8839
9013
|
return Object.fromEntries(CLIENTS.map((client, i) => [client, found[i]]));
|
|
8840
9014
|
}
|
|
8841
|
-
|
|
8842
|
-
|
|
9015
|
+
function claudeConfigPath(ctx) {
|
|
9016
|
+
return join4(ctx.io.env.CLAUDE_CONFIG_DIR || ctx.io.homedir, ".claude.json");
|
|
9017
|
+
}
|
|
9018
|
+
var routes = /* @__PURE__ */ new WeakMap();
|
|
9019
|
+
function claudeRoute(ctx) {
|
|
9020
|
+
let route = routes.get(ctx);
|
|
9021
|
+
if (!route) {
|
|
9022
|
+
route = findClaude(ctx).then((command) => command ? { kind: "command", command } : { kind: "file", path: claudeConfigPath(ctx) });
|
|
9023
|
+
routes.set(ctx, route);
|
|
9024
|
+
}
|
|
9025
|
+
return route;
|
|
9026
|
+
}
|
|
9027
|
+
async function findClaude(ctx) {
|
|
9028
|
+
const { io } = ctx;
|
|
9029
|
+
if (await onPath(ctx, "claude")) return "claude";
|
|
9030
|
+
const win = io.platform === "win32";
|
|
9031
|
+
const local = [join4(io.homedir, ".local", "bin", win ? "claude.exe" : "claude"), join4(io.homedir, ".claude", "local", win ? "claude.exe" : "claude")];
|
|
9032
|
+
const system = win ? [] : ["/opt/homebrew/bin/claude", "/usr/local/bin/claude"];
|
|
9033
|
+
const found = [...local, ...system].find((path) => io.isExecutable(path));
|
|
9034
|
+
if (found) return found;
|
|
9035
|
+
if (win) return null;
|
|
9036
|
+
const prefix2 = io.env.npm_config_global_prefix || await npmGlobalPrefix(ctx);
|
|
9037
|
+
const npmClaude = prefix2 ? join4(prefix2, "bin", "claude") : null;
|
|
9038
|
+
return npmClaude && io.isExecutable(npmClaude) ? npmClaude : null;
|
|
9039
|
+
}
|
|
9040
|
+
async function npmGlobalPrefix(ctx) {
|
|
9041
|
+
const res = await ctx.io.run("npm", ["prefix", "-g"]);
|
|
9042
|
+
const prefix2 = res.code === 0 ? res.stdout.trim() : "";
|
|
9043
|
+
return prefix2 || null;
|
|
9044
|
+
}
|
|
9045
|
+
async function claudeHasAmdahl(ctx, command, mcpUrl) {
|
|
9046
|
+
const res = await ctx.io.run(command, ["mcp", "get", "amdahl"]);
|
|
8843
9047
|
return res.code === 0 && res.stdout.split("\n").some((line) => line.trim() === `URL: ${mcpUrl}`);
|
|
8844
9048
|
}
|
|
9049
|
+
var CONFIG_HINT = "Fix the file, or move it away, then run the same command again.";
|
|
9050
|
+
function readJsonObject(path) {
|
|
9051
|
+
if (!existsSync3(path)) return { config: {}, existed: false };
|
|
9052
|
+
const raw = readFileSync3(path, "utf8");
|
|
9053
|
+
if (!raw.trim()) return { config: {}, existed: true };
|
|
9054
|
+
let config;
|
|
9055
|
+
try {
|
|
9056
|
+
config = JSON.parse(raw);
|
|
9057
|
+
} catch {
|
|
9058
|
+
throw new CliError("invalid_config", `${path} is not valid JSON; not changing it.`, EXIT.general, CONFIG_HINT);
|
|
9059
|
+
}
|
|
9060
|
+
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
9061
|
+
throw new CliError("invalid_config", `${path} does not hold a JSON object; not changing it.`, EXIT.general, CONFIG_HINT);
|
|
9062
|
+
}
|
|
9063
|
+
return { config, existed: true };
|
|
9064
|
+
}
|
|
9065
|
+
function readMcpConfig(path) {
|
|
9066
|
+
const { config, existed } = readJsonObject(path);
|
|
9067
|
+
const servers = config.mcpServers;
|
|
9068
|
+
if (servers === void 0) return { config, servers: {}, existed };
|
|
9069
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) {
|
|
9070
|
+
throw new CliError("invalid_config", `The "mcpServers" in ${path} is not a JSON object; not changing it.`, EXIT.general, CONFIG_HINT);
|
|
9071
|
+
}
|
|
9072
|
+
return { config, servers, existed };
|
|
9073
|
+
}
|
|
9074
|
+
function claudeFileHasAmdahl(path, mcpUrl) {
|
|
9075
|
+
try {
|
|
9076
|
+
const servers = readMcpConfig(path).servers;
|
|
9077
|
+
return servers.amdahl?.url === mcpUrl;
|
|
9078
|
+
} catch {
|
|
9079
|
+
return false;
|
|
9080
|
+
}
|
|
9081
|
+
}
|
|
9082
|
+
function writeTarget(path) {
|
|
9083
|
+
let target = path;
|
|
9084
|
+
for (let hops = 0; hops < 40; hops++) {
|
|
9085
|
+
let link;
|
|
9086
|
+
try {
|
|
9087
|
+
link = lstatSync(target).isSymbolicLink();
|
|
9088
|
+
} catch {
|
|
9089
|
+
return target;
|
|
9090
|
+
}
|
|
9091
|
+
if (!link) return existsSync3(target) ? realpathSync(target) : target;
|
|
9092
|
+
target = resolve(dirname(target), readlinkSync(target));
|
|
9093
|
+
}
|
|
9094
|
+
throw new CliError("invalid_config", `${path} is a chain of more than 40 symbolic links; not changing it.`, EXIT.general, CONFIG_HINT);
|
|
9095
|
+
}
|
|
9096
|
+
function writeFileAtomic(path, text2) {
|
|
9097
|
+
const target = writeTarget(path);
|
|
9098
|
+
const mode = existsSync3(target) ? statSync3(target).mode & 511 : 384;
|
|
9099
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
9100
|
+
const tmp = join4(dirname(target), `.${basename(target)}.amdahl-${process.pid}.tmp`);
|
|
9101
|
+
try {
|
|
9102
|
+
writeFileSync2(tmp, text2, { mode });
|
|
9103
|
+
chmodSync3(tmp, mode);
|
|
9104
|
+
renameSync2(tmp, target);
|
|
9105
|
+
} catch (err) {
|
|
9106
|
+
rmSync2(tmp, { force: true });
|
|
9107
|
+
throw err;
|
|
9108
|
+
}
|
|
9109
|
+
}
|
|
9110
|
+
function writeClaudeConfig(ctx, path, mcpUrl, command) {
|
|
9111
|
+
return updateConfig(path, () => {
|
|
9112
|
+
const { config, servers, existed } = readMcpConfig(path);
|
|
9113
|
+
const data = { ok: true, client: "claude-code", action: "unchanged", path, command };
|
|
9114
|
+
if (servers.amdahl?.url === mcpUrl) {
|
|
9115
|
+
return { data, human: `${path} already points Claude Code at ${mcpUrl}; nothing changed.`, notes: [] };
|
|
9116
|
+
}
|
|
9117
|
+
config.mcpServers = { ...servers, amdahl: { type: "http", url: mcpUrl } };
|
|
9118
|
+
const backup = `${writeTarget(path)}.amdahl-backup`;
|
|
9119
|
+
ctx.io.uninterruptible(() => {
|
|
9120
|
+
if (existed) copyFileSync(writeTarget(path), backup);
|
|
9121
|
+
writeFileAtomic(path, `${JSON.stringify(config, null, 2)}
|
|
9122
|
+
`);
|
|
9123
|
+
});
|
|
9124
|
+
const saved = existed ? ` The file as it was is saved at ${backup}.` : "";
|
|
9125
|
+
return {
|
|
9126
|
+
data: { ...data, action: "wrote", ...existed ? { backup } : {} },
|
|
9127
|
+
human: `Added Amdahl to Claude Code for all your projects, in ${path} (\`claude\` was not found).${saved} Run /mcp in Claude Code to sign in. If amdahl is not listed there, restart Claude Code first.`,
|
|
9128
|
+
notes: []
|
|
9129
|
+
};
|
|
9130
|
+
});
|
|
9131
|
+
}
|
|
8845
9132
|
function updateConfig(path, fn) {
|
|
8846
9133
|
try {
|
|
8847
9134
|
return fn();
|
|
@@ -8856,7 +9143,11 @@ async function isInstalled(ctx, client) {
|
|
|
8856
9143
|
const current = existsSync3(skill) ? readFileSync3(skill, "utf8") : null;
|
|
8857
9144
|
if (current === null || current !== SKILL_TEXT && isShippedSkill(current)) return false;
|
|
8858
9145
|
const mcpUrl = `${ctx.apiUrl}/mcp`;
|
|
8859
|
-
if (client === "claude-code")
|
|
9146
|
+
if (client === "claude-code") {
|
|
9147
|
+
if (claudeFileHasAmdahl(claudeConfigPath(ctx), mcpUrl)) return true;
|
|
9148
|
+
const route = await claudeRoute(ctx);
|
|
9149
|
+
return route.kind === "command" && await claudeHasAmdahl(ctx, route.command, mcpUrl);
|
|
9150
|
+
}
|
|
8860
9151
|
if (client === "codex") {
|
|
8861
9152
|
const path = codexConfigPath(ctx.io.homedir);
|
|
8862
9153
|
return existsSync3(path) && codexHasAmdahl(readFileSync3(path, "utf8"));
|
|
@@ -8914,14 +9205,13 @@ async function installMcp(ctx, client, print) {
|
|
|
8914
9205
|
if (client === "claude-code") {
|
|
8915
9206
|
const args = ["mcp", "add", "--scope", "user", "--transport", "http", "amdahl", mcpUrl];
|
|
8916
9207
|
const command = `claude ${args.join(" ")}`;
|
|
8917
|
-
if (print
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
if (await claudeHasAmdahl(ctx, mcpUrl)) {
|
|
9208
|
+
if (print) return { data: { ok: true, client, action: "printed", command }, human: command, notes: [] };
|
|
9209
|
+
const route = await claudeRoute(ctx);
|
|
9210
|
+
if (route.kind === "file") return writeClaudeConfig(ctx, route.path, mcpUrl, command);
|
|
9211
|
+
if (claudeFileHasAmdahl(claudeConfigPath(ctx), mcpUrl) || await claudeHasAmdahl(ctx, route.command, mcpUrl)) {
|
|
8922
9212
|
return { data: { ok: true, client, action: "unchanged", command }, human: `Claude Code already has Amdahl at ${mcpUrl}; nothing changed.`, notes: [] };
|
|
8923
9213
|
}
|
|
8924
|
-
const res = await io.run(
|
|
9214
|
+
const res = await io.run(route.command, args);
|
|
8925
9215
|
if (res.code !== 0) {
|
|
8926
9216
|
throw new CliError("install_failed", `\`${command}\` failed: ${res.stderr.trim() || `exit ${res.code}`}`, EXIT.general);
|
|
8927
9217
|
}
|
|
@@ -8939,9 +9229,8 @@ ${block}`, notes: [] };
|
|
|
8939
9229
|
if (codexHasAmdahl(current)) {
|
|
8940
9230
|
return { data: { ok: true, client, action: "unchanged", path: path2 }, human: `${path2} already has [mcp_servers.amdahl]; nothing changed.`, notes: [] };
|
|
8941
9231
|
}
|
|
8942
|
-
mkdirSync2(dirname(path2), { recursive: true });
|
|
8943
9232
|
const sep2 = current.length === 0 ? "" : current.endsWith("\n\n") ? "" : current.endsWith("\n") ? "\n" : "\n\n";
|
|
8944
|
-
|
|
9233
|
+
writeFileAtomic(path2, `${current}${sep2}${block}`);
|
|
8945
9234
|
return { data: { ok: true, client, action: "wrote", path: path2 }, human: `Added Amdahl to ${path2}. Run \`codex mcp login amdahl\` to sign in.`, notes: [] };
|
|
8946
9235
|
});
|
|
8947
9236
|
}
|
|
@@ -8952,28 +9241,33 @@ ${block}`, notes: [] };
|
|
|
8952
9241
|
${JSON.stringify({ mcpServers: { amdahl: entry } }, null, 2)}`, notes: [] };
|
|
8953
9242
|
}
|
|
8954
9243
|
return updateConfig(path, () => {
|
|
8955
|
-
|
|
8956
|
-
if (existsSync3(path)) {
|
|
8957
|
-
const raw = readFileSync3(path, "utf8");
|
|
8958
|
-
if (raw.trim()) {
|
|
8959
|
-
try {
|
|
8960
|
-
config = JSON.parse(raw);
|
|
8961
|
-
} catch {
|
|
8962
|
-
throw new CliError("invalid_config", `${path} is not valid JSON; not changing it.`, EXIT.general);
|
|
8963
|
-
}
|
|
8964
|
-
}
|
|
8965
|
-
}
|
|
8966
|
-
const servers = config.mcpServers && typeof config.mcpServers === "object" ? config.mcpServers : {};
|
|
9244
|
+
const { config, servers } = readMcpConfig(path);
|
|
8967
9245
|
if (JSON.stringify(servers.amdahl) === JSON.stringify(entry)) {
|
|
8968
9246
|
return { data: { ok: true, client, action: "unchanged", path }, human: `${path} already points at ${mcpUrl}; nothing changed.`, notes: [] };
|
|
8969
9247
|
}
|
|
8970
9248
|
config.mcpServers = { ...servers, amdahl: entry };
|
|
8971
|
-
|
|
8972
|
-
writeFileSync2(path, `${JSON.stringify(config, null, 2)}
|
|
9249
|
+
writeFileAtomic(path, `${JSON.stringify(config, null, 2)}
|
|
8973
9250
|
`);
|
|
8974
9251
|
return { data: { ok: true, client, action: "wrote", path }, human: `Added Amdahl to ${path}. Cursor asks you to sign in on first use.`, notes: [] };
|
|
8975
9252
|
});
|
|
8976
9253
|
}
|
|
9254
|
+
async function preflight(ctx, client) {
|
|
9255
|
+
let path = null;
|
|
9256
|
+
if (client === "cursor") path = cursorConfigPath(ctx.io.homedir);
|
|
9257
|
+
if (client === "claude-code") {
|
|
9258
|
+
const route = await claudeRoute(ctx);
|
|
9259
|
+
if (route.kind === "file") path = route.path;
|
|
9260
|
+
}
|
|
9261
|
+
if (!path) return null;
|
|
9262
|
+
try {
|
|
9263
|
+
readMcpConfig(path);
|
|
9264
|
+
return null;
|
|
9265
|
+
} catch (err) {
|
|
9266
|
+
if (err instanceof CliError) return err;
|
|
9267
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9268
|
+
return new CliError("write_failed", `Could not read ${path} (${reason}).`, EXIT.general);
|
|
9269
|
+
}
|
|
9270
|
+
}
|
|
8977
9271
|
|
|
8978
9272
|
// src/keygen.ts
|
|
8979
9273
|
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
@@ -9041,7 +9335,7 @@ async function waitForDecision(ctx, client, req) {
|
|
|
9041
9335
|
}
|
|
9042
9336
|
async function keysCreate(ctx) {
|
|
9043
9337
|
const name = str(ctx.flags.name)?.trim();
|
|
9044
|
-
if (!name) throw usageError("--name is required.",
|
|
9338
|
+
if (!name) throw usageError("--name is required.", `Example: ${amdahlCommand('keys create --name "CI pipeline"')}`);
|
|
9045
9339
|
if (name.length > 80) throw usageError("--name must be at most 80 characters.");
|
|
9046
9340
|
const preset = str(ctx.flags.preset) ?? "read-only";
|
|
9047
9341
|
const bundle = PRESET_BUNDLES[preset];
|
|
@@ -9111,7 +9405,7 @@ function findKey(keys, ref) {
|
|
|
9111
9405
|
const matches = keys.filter((k) => ref.length >= 8 && k.key_prefix.startsWith(ref));
|
|
9112
9406
|
if (matches.length === 1 && matches[0]) return matches[0];
|
|
9113
9407
|
if (matches.length > 1) throw usageError(`"${ref}" matches ${matches.length} keys. Use the key id.`);
|
|
9114
|
-
throw new CliError("key_not_found", `No active key matches "${ref}".`, EXIT.general,
|
|
9408
|
+
throw new CliError("key_not_found", `No active key matches "${ref}".`, EXIT.general, `Run \`${amdahlCommand("keys list")}\`.`);
|
|
9115
9409
|
}
|
|
9116
9410
|
async function keysRevoke(ctx, args) {
|
|
9117
9411
|
const ref = args[0];
|
|
@@ -9369,7 +9663,7 @@ import { dirname as dirname3, join as join6, relative, sep } from "path";
|
|
|
9369
9663
|
|
|
9370
9664
|
// src/commands/skills-install.ts
|
|
9371
9665
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
9372
|
-
import { chmodSync as
|
|
9666
|
+
import { chmodSync as chmodSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, mkdtempSync, readdirSync, readFileSync as readFileSync5, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9373
9667
|
import { dirname as dirname2, join as join5 } from "path";
|
|
9374
9668
|
function skillDirs(targets, name) {
|
|
9375
9669
|
const dirs = [];
|
|
@@ -9396,7 +9690,7 @@ function writeFailure(path, err, written = []) {
|
|
|
9396
9690
|
function sameFolder(dir, archive) {
|
|
9397
9691
|
let top;
|
|
9398
9692
|
try {
|
|
9399
|
-
top =
|
|
9693
|
+
top = lstatSync2(dir);
|
|
9400
9694
|
} catch (err) {
|
|
9401
9695
|
if (err.code === "ENOENT") return null;
|
|
9402
9696
|
throw writeFailure(dir, err);
|
|
@@ -9415,7 +9709,7 @@ function sameFolder(dir, archive) {
|
|
|
9415
9709
|
const file = want.get(path);
|
|
9416
9710
|
if (!file) return false;
|
|
9417
9711
|
const full = join5(dir, path);
|
|
9418
|
-
if (process.platform !== "win32" && (
|
|
9712
|
+
if (process.platform !== "win32" && (lstatSync2(full).mode & 73 ? 493 : 420) !== file.mode) return false;
|
|
9419
9713
|
if (!readFileSync5(full).equals(file.bytes)) return false;
|
|
9420
9714
|
} else {
|
|
9421
9715
|
return false;
|
|
@@ -9434,7 +9728,7 @@ function refuseLinks(dir, file, written = []) {
|
|
|
9434
9728
|
for (const part of [dirname2(dirname2(dir)), dirname2(dir), dir, ...file ? [file] : []]) {
|
|
9435
9729
|
let stat;
|
|
9436
9730
|
try {
|
|
9437
|
-
stat =
|
|
9731
|
+
stat = lstatSync2(part);
|
|
9438
9732
|
} catch (err) {
|
|
9439
9733
|
if (err.code === "ENOENT") return;
|
|
9440
9734
|
throw writeFailure(path, err, written);
|
|
@@ -9461,7 +9755,7 @@ function planInstall(dirs, payload) {
|
|
|
9461
9755
|
refuseLinks(dir, path);
|
|
9462
9756
|
let current;
|
|
9463
9757
|
try {
|
|
9464
|
-
current =
|
|
9758
|
+
current = lstatSync2(path, { throwIfNoEntry: false }) ? readFileSync5(path) : null;
|
|
9465
9759
|
} catch (err) {
|
|
9466
9760
|
throw writeFailure(path, err);
|
|
9467
9761
|
}
|
|
@@ -9486,7 +9780,7 @@ function stage(target, name, archive, staged) {
|
|
|
9486
9780
|
s.createdParent = mkdirSync3(dirname2(target), { recursive: true }) ?? void 0;
|
|
9487
9781
|
refuseLinks(target);
|
|
9488
9782
|
s.temp = mkdtempSync(join5(dirname2(target), `.${name}.amdahl-tmp-`));
|
|
9489
|
-
|
|
9783
|
+
chmodSync4(s.temp, 493);
|
|
9490
9784
|
for (const dir of archive.dirs) mkdirSync3(join5(s.temp, dir));
|
|
9491
9785
|
for (const file of archive.files) writeFileSync3(join5(s.temp, file.path), file.bytes, { flag: "wx", mode: file.mode });
|
|
9492
9786
|
}
|
|
@@ -9494,15 +9788,15 @@ function rollBack(staged) {
|
|
|
9494
9788
|
for (const s of [...staged].reverse()) {
|
|
9495
9789
|
try {
|
|
9496
9790
|
if (s.swapped) {
|
|
9497
|
-
|
|
9498
|
-
if (s.backup)
|
|
9791
|
+
rmSync3(s.target, { recursive: true, force: true });
|
|
9792
|
+
if (s.backup) renameSync3(s.backup, s.target);
|
|
9499
9793
|
} else if (s.backup) {
|
|
9500
|
-
|
|
9794
|
+
renameSync3(s.backup, s.target);
|
|
9501
9795
|
}
|
|
9502
9796
|
} catch {
|
|
9503
9797
|
}
|
|
9504
|
-
if (s.temp)
|
|
9505
|
-
if (s.createdParent)
|
|
9798
|
+
if (s.temp) rmSync3(s.temp, { recursive: true, force: true });
|
|
9799
|
+
if (s.createdParent) rmSync3(s.createdParent, { recursive: true, force: true });
|
|
9506
9800
|
}
|
|
9507
9801
|
}
|
|
9508
9802
|
function writeFolders(plan, name, archive) {
|
|
@@ -9518,9 +9812,9 @@ function writeFolders(plan, name, archive) {
|
|
|
9518
9812
|
current = s.target;
|
|
9519
9813
|
if (plan.find((p) => p.path === s.target)?.action === "replaced") {
|
|
9520
9814
|
s.backup = `${s.temp}.old`;
|
|
9521
|
-
|
|
9815
|
+
renameSync3(s.target, s.backup);
|
|
9522
9816
|
}
|
|
9523
|
-
|
|
9817
|
+
renameSync3(s.temp, s.target);
|
|
9524
9818
|
s.swapped = true;
|
|
9525
9819
|
}
|
|
9526
9820
|
} catch (err) {
|
|
@@ -9528,7 +9822,7 @@ function writeFolders(plan, name, archive) {
|
|
|
9528
9822
|
if (err instanceof CliError) throw err;
|
|
9529
9823
|
throw writeFailure(current, err);
|
|
9530
9824
|
}
|
|
9531
|
-
for (const s of staged) if (s.backup)
|
|
9825
|
+
for (const s of staged) if (s.backup) rmSync3(s.backup, { recursive: true, force: true });
|
|
9532
9826
|
}
|
|
9533
9827
|
function writeFiles(plan, body) {
|
|
9534
9828
|
const written = [];
|
|
@@ -9540,9 +9834,9 @@ function writeFiles(plan, body) {
|
|
|
9540
9834
|
mkdirSync3(dirname2(p.path), { recursive: true });
|
|
9541
9835
|
refuseLinks(dirname2(p.path), p.path, written);
|
|
9542
9836
|
writeFileSync3(temp, body, { flag: "wx", mode: 420 });
|
|
9543
|
-
|
|
9837
|
+
renameSync3(temp, p.path);
|
|
9544
9838
|
} catch (err) {
|
|
9545
|
-
|
|
9839
|
+
rmSync3(temp, { force: true });
|
|
9546
9840
|
if (err instanceof CliError) throw err;
|
|
9547
9841
|
throw writeFailure(p.path, err, written);
|
|
9548
9842
|
}
|
|
@@ -9647,16 +9941,51 @@ var SHOWN = [
|
|
|
9647
9941
|
{ client: "cursor", name: "Cursor" },
|
|
9648
9942
|
{ client: "codex", name: "Codex" }
|
|
9649
9943
|
];
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9944
|
+
var QUESTION = "Where should Amdahl work?";
|
|
9945
|
+
var FOUND_TAG = "found on your machine";
|
|
9946
|
+
var NONE_FOUND = "We didn't find any of these on this computer. Tick the ones you use.";
|
|
9947
|
+
var MENU_HINT = "\u2191\u2193 move \xB7 Space tick/untick \xB7 Enter to install";
|
|
9948
|
+
var ASK = 'say "make this email sound less like AI"';
|
|
9949
|
+
function finishLines(names, inClaudeCode = false) {
|
|
9950
|
+
const [only] = names;
|
|
9951
|
+
if (only === void 0) return [];
|
|
9952
|
+
if (names.length === 1) {
|
|
9953
|
+
const step2 = {
|
|
9954
|
+
"Claude Code": inClaudeCode ? "Before that, restart Claude Code and run /mcp once to connect Amdahl." : "Before that, run /mcp once in Claude Code to connect Amdahl.",
|
|
9955
|
+
Cursor: "Before that, sign in when Cursor asks, the first time you use Amdahl.",
|
|
9956
|
+
Codex: "Before that, run `codex mcp login amdahl` once to connect Amdahl."
|
|
9957
|
+
};
|
|
9958
|
+
return [`Try it: ${ASK} in ${only}.`, step2[only] ?? ""];
|
|
9959
|
+
}
|
|
9960
|
+
const step = {
|
|
9961
|
+
"Claude Code": inClaudeCode ? "restart it, then run /mcp" : "run /mcp",
|
|
9962
|
+
Cursor: "sign in when it asks, on first use",
|
|
9963
|
+
Codex: "run `codex mcp login amdahl`"
|
|
9964
|
+
};
|
|
9965
|
+
return [`Try it: ${ASK} in any of them.`, "Before that, connect each one once:", ...names.map((n) => ` ${n}: ${step[n] ?? ""}`)];
|
|
9966
|
+
}
|
|
9967
|
+
var isSet = (value) => Boolean(value) && value !== "false" && value !== "0";
|
|
9968
|
+
function ciVariable(env) {
|
|
9969
|
+
const named = ["CI", "JENKINS_URL", "TF_BUILD", "CODEBUILD_BUILD_ID", "TEAMCITY_VERSION", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"].find((name) => isSet(env[name]));
|
|
9970
|
+
if (named) return named;
|
|
9971
|
+
return isSet(env.BUILD_NUMBER) && isSet(env.JENKINS_HOME) ? "BUILD_NUMBER with JENKINS_HOME" : null;
|
|
9655
9972
|
}
|
|
9656
|
-
function
|
|
9973
|
+
function setupMode(io) {
|
|
9657
9974
|
const { env } = io;
|
|
9658
|
-
const ci =
|
|
9659
|
-
|
|
9975
|
+
const ci = ciVariable(env);
|
|
9976
|
+
if (ci) return { kind: "usage", reason: `${ci} is set` };
|
|
9977
|
+
if (env.AMDAHL_NO_PROMPT === "1") return { kind: "usage", reason: "AMDAHL_NO_PROMPT=1 is set" };
|
|
9978
|
+
if (env.CLAUDECODE || env.CURSOR_AGENT) return { kind: "agent" };
|
|
9979
|
+
return io.stdinIsTTY && io.stdoutIsTTY && io.stderrIsTTY ? { kind: "menu" } : { kind: "agent" };
|
|
9980
|
+
}
|
|
9981
|
+
function installLine() {
|
|
9982
|
+
return `To set up a client, run \`${amdahlCommand("install claude-code")}\` (or cursor, codex).`;
|
|
9983
|
+
}
|
|
9984
|
+
function noSetupLine(reason) {
|
|
9985
|
+
return `Setup did not run: ${reason}.
|
|
9986
|
+
${installLine()}
|
|
9987
|
+
|
|
9988
|
+
`;
|
|
9660
9989
|
}
|
|
9661
9990
|
async function signIn(ctx) {
|
|
9662
9991
|
let credential;
|
|
@@ -9674,7 +10003,32 @@ async function signIn(ctx) {
|
|
|
9674
10003
|
return credential.source === "profile" ? { kind: "out" } : { kind: "rejected", source: credential.source };
|
|
9675
10004
|
}
|
|
9676
10005
|
}
|
|
9677
|
-
|
|
10006
|
+
function reportError(io, err) {
|
|
10007
|
+
io.stderr(`Error: ${err.message}
|
|
10008
|
+
${err.hint ? `${err.hint}
|
|
10009
|
+
` : ""}`);
|
|
10010
|
+
}
|
|
10011
|
+
async function installEach(ctx, clients, detail) {
|
|
10012
|
+
let exit = EXIT.ok;
|
|
10013
|
+
const ready = [];
|
|
10014
|
+
for (const { client, name } of clients) {
|
|
10015
|
+
let steps;
|
|
10016
|
+
try {
|
|
10017
|
+
steps = await installClient(ctx, client, false);
|
|
10018
|
+
} catch (raw) {
|
|
10019
|
+
const err = raw instanceof CliError ? raw : new CliError("install_failed", `Could not set up ${name}: ${raw instanceof Error ? raw.message : String(raw)}`, EXIT.general);
|
|
10020
|
+
reportError(ctx.io, err);
|
|
10021
|
+
if (exit === EXIT.ok) exit = err.exit;
|
|
10022
|
+
continue;
|
|
10023
|
+
}
|
|
10024
|
+
ctx.io.stdout(`${steps.mcp.data.action === "unchanged" ? `\u2713 ${name} already has Amdahl` : `\u2713 Added to ${name}`}
|
|
10025
|
+
`);
|
|
10026
|
+
ready.push(name);
|
|
10027
|
+
detail([steps.mcp.human, ...steps.mcp.notes, steps.skill.human, ...steps.skill.notes].filter(Boolean).join("\n"));
|
|
10028
|
+
}
|
|
10029
|
+
return { ready, exit };
|
|
10030
|
+
}
|
|
10031
|
+
async function setup(ctx, mode) {
|
|
9678
10032
|
const { io, out } = ctx;
|
|
9679
10033
|
const verbose = ctx.flags.verbose === true;
|
|
9680
10034
|
const say = (line) => io.stdout(`${line}
|
|
@@ -9682,9 +10036,23 @@ async function setup(ctx) {
|
|
|
9682
10036
|
const detail = (text2) => {
|
|
9683
10037
|
if (verbose && text2) say(text2.replace(/^/gm, " "));
|
|
9684
10038
|
};
|
|
10039
|
+
const inClaudeCode = Boolean(io.env.CLAUDECODE);
|
|
9685
10040
|
say(`${out.bold("Amdahl")} \xB7 predictive ML for GTM`);
|
|
9686
10041
|
const found = await detectClients(ctx);
|
|
9687
10042
|
const detected = SHOWN.filter((c) => found[c.client]);
|
|
10043
|
+
if (mode === "agent") {
|
|
10044
|
+
const running = SHOWN.filter((c) => c.client === "claude-code" && io.env.CLAUDECODE || c.client === "cursor" && io.env.CURSOR_AGENT);
|
|
10045
|
+
const targets = running.length > 0 ? running : detected;
|
|
10046
|
+
if (targets.length === 0) {
|
|
10047
|
+
io.stderr(`Didn't find Claude Code, Cursor or Codex on this computer, so nothing changed.
|
|
10048
|
+
${installLine()}
|
|
10049
|
+
`);
|
|
10050
|
+
return EXIT.general;
|
|
10051
|
+
}
|
|
10052
|
+
const { ready, exit: exit2 } = await installEach(ctx, targets, detail);
|
|
10053
|
+
for (const line of finishLines(ready, inClaudeCode)) say(line);
|
|
10054
|
+
return exit2;
|
|
10055
|
+
}
|
|
9688
10056
|
let who;
|
|
9689
10057
|
if (detected.length > 0) {
|
|
9690
10058
|
const installed = await Promise.all(detected.map((c) => isInstalled(ctx, c.client)));
|
|
@@ -9698,20 +10066,38 @@ async function setup(ctx) {
|
|
|
9698
10066
|
}
|
|
9699
10067
|
if (unreachable || who?.kind === "in") {
|
|
9700
10068
|
say(`You're set up in ${joinNames(detected.map((c) => c.name))}.${unreachable ? " (Could not reach Amdahl to check your sign-in.)" : ""}`);
|
|
9701
|
-
|
|
10069
|
+
for (const line of finishLines(detected.map((c) => c.name), inClaudeCode)) say(line);
|
|
9702
10070
|
return EXIT.ok;
|
|
9703
10071
|
}
|
|
9704
10072
|
}
|
|
9705
10073
|
}
|
|
9706
10074
|
const picks = await io.multiSelect(
|
|
9707
|
-
|
|
10075
|
+
QUESTION,
|
|
9708
10076
|
SHOWN.map((c) => c.name),
|
|
9709
|
-
{
|
|
10077
|
+
{
|
|
10078
|
+
ticked: SHOWN.map((c) => found[c.client]),
|
|
10079
|
+
tags: SHOWN.map((c) => found[c.client] ? FOUND_TAG : void 0),
|
|
10080
|
+
note: detected.length === 0 ? NONE_FOUND : void 0,
|
|
10081
|
+
hint: MENU_HINT,
|
|
10082
|
+
color: out.color
|
|
10083
|
+
}
|
|
9710
10084
|
);
|
|
9711
10085
|
if (picks.length === 0) {
|
|
9712
10086
|
say("Nothing picked, so nothing changed.");
|
|
9713
10087
|
return EXIT.ok;
|
|
9714
10088
|
}
|
|
10089
|
+
let exit = EXIT.ok;
|
|
10090
|
+
const chosen = [];
|
|
10091
|
+
for (const pick of picks.flatMap((i) => SHOWN[i] ?? [])) {
|
|
10092
|
+
const err = await preflight(ctx, pick.client);
|
|
10093
|
+
if (!err) {
|
|
10094
|
+
chosen.push(pick);
|
|
10095
|
+
continue;
|
|
10096
|
+
}
|
|
10097
|
+
reportError(io, err);
|
|
10098
|
+
if (exit === EXIT.ok) exit = err.exit;
|
|
10099
|
+
}
|
|
10100
|
+
if (chosen.length === 0) return exit;
|
|
9715
10101
|
who ??= await signIn(ctx);
|
|
9716
10102
|
if (who.kind === "in") {
|
|
9717
10103
|
say(`\u2713 Signed in \xB7 ${who.workspace}`);
|
|
@@ -9722,35 +10108,11 @@ async function setup(ctx) {
|
|
|
9722
10108
|
detail(signedInLine(ctx, signedIn));
|
|
9723
10109
|
} else {
|
|
9724
10110
|
const name = who.source === "flag" ? "--api-key" : who.source.slice("env:".length);
|
|
9725
|
-
out.info(`Amdahl rejected the key in ${name}. Skipping the browser sign-in, because that key would still be used instead. Fix it, or unset it and run
|
|
10111
|
+
out.info(`Amdahl rejected the key in ${name}. Skipping the browser sign-in, because that key would still be used instead. Fix it, or unset it and run \`${amdahlCommand("login")}\`.`);
|
|
9726
10112
|
}
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
let steps;
|
|
9731
|
-
try {
|
|
9732
|
-
steps = await installClient(ctx, client, false);
|
|
9733
|
-
} catch (raw) {
|
|
9734
|
-
const err = raw instanceof CliError ? raw : new CliError("install_failed", `Could not set up ${name}: ${raw instanceof Error ? raw.message : String(raw)}`, EXIT.general);
|
|
9735
|
-
io.stderr(`Error: ${err.message}
|
|
9736
|
-
${err.hint ? `${err.hint}
|
|
9737
|
-
` : ""}`);
|
|
9738
|
-
if (exit === EXIT.ok) exit = err.exit;
|
|
9739
|
-
continue;
|
|
9740
|
-
}
|
|
9741
|
-
const action = steps.mcp.data.action;
|
|
9742
|
-
if (action === "printed") {
|
|
9743
|
-
say(`! ${name}: \`claude\` is not on your PATH. Once it is, run:
|
|
9744
|
-
${String(steps.mcp.data.command)}`);
|
|
9745
|
-
} else {
|
|
9746
|
-
say(action === "unchanged" ? `\u2713 ${name} already has Amdahl` : `\u2713 Added to ${name}`);
|
|
9747
|
-
ready ??= { name, connect: action !== "unchanged" };
|
|
9748
|
-
}
|
|
9749
|
-
const mcpDetail = action === "printed" ? [] : [steps.mcp.human, ...steps.mcp.notes];
|
|
9750
|
-
detail([...mcpDetail, steps.skill.human, ...steps.skill.notes].filter(Boolean).join("\n"));
|
|
9751
|
-
}
|
|
9752
|
-
if (ready) say(tryItLine(ready.name, ready.connect));
|
|
9753
|
-
return exit;
|
|
10113
|
+
const done = await installEach(ctx, chosen, detail);
|
|
10114
|
+
for (const line of finishLines(done.ready, inClaudeCode)) say(line);
|
|
10115
|
+
return exit === EXIT.ok ? done.exit : exit;
|
|
9754
10116
|
}
|
|
9755
10117
|
|
|
9756
10118
|
// src/commands/skills.ts
|
|
@@ -9878,8 +10240,8 @@ function readTar(tar) {
|
|
|
9878
10240
|
throw new ArchiveError("its tar is not ustar");
|
|
9879
10241
|
}
|
|
9880
10242
|
const name = field(header, 0, 100);
|
|
9881
|
-
const
|
|
9882
|
-
let path =
|
|
10243
|
+
const prefix2 = field(header, 345, 155);
|
|
10244
|
+
let path = prefix2 ? `${prefix2}/${name}` : name;
|
|
9883
10245
|
const type = String.fromCharCode(header[156]);
|
|
9884
10246
|
const size = octal(header, 124, 12);
|
|
9885
10247
|
const mode = octal(header, 100, 8);
|
|
@@ -10300,9 +10662,9 @@ ${done}`);
|
|
|
10300
10662
|
|
|
10301
10663
|
// src/commands/workspace-status.ts
|
|
10302
10664
|
var BLOCKER_FIX = {
|
|
10303
|
-
missing_scope:
|
|
10304
|
-
role_too_low: "Your role cannot optimize. Ask a workspace admin to raise it.",
|
|
10305
|
-
quota_exhausted: "The monthly optimize quota is used up."
|
|
10665
|
+
missing_scope: () => `This credential cannot optimize. Use a Customer agent key, or run \`${amdahlCommand("login")}\` again.`,
|
|
10666
|
+
role_too_low: () => "Your role cannot optimize. Ask a workspace admin to raise it.",
|
|
10667
|
+
quota_exhausted: () => "The monthly optimize quota is used up."
|
|
10306
10668
|
};
|
|
10307
10669
|
function describe(status2) {
|
|
10308
10670
|
const lines = [
|
|
@@ -10314,8 +10676,9 @@ function describe(status2) {
|
|
|
10314
10676
|
if (opt.quota) {
|
|
10315
10677
|
lines.push(`Quota: ${opt.quota.used} of ${opt.quota.limit} used, resets ${opt.quota.resets_at}`);
|
|
10316
10678
|
}
|
|
10317
|
-
|
|
10318
|
-
|
|
10679
|
+
const fix = opt.blocker ? BLOCKER_FIX[opt.blocker] : void 0;
|
|
10680
|
+
if (!opt.allowed && fix) {
|
|
10681
|
+
lines.push(` ${fix()}`);
|
|
10319
10682
|
}
|
|
10320
10683
|
const conns = status2.connections;
|
|
10321
10684
|
if ("omitted" in conns) {
|
|
@@ -10344,7 +10707,7 @@ async function workspaceList(ctx) {
|
|
|
10344
10707
|
email: p.email,
|
|
10345
10708
|
api_url: p.api_url
|
|
10346
10709
|
}));
|
|
10347
|
-
const human = profiles.length === 0 ?
|
|
10710
|
+
const human = profiles.length === 0 ? `No profiles. Run \`${amdahlCommand("login")}\`.` : profiles.map(
|
|
10348
10711
|
(p) => `${p.name === config.default_profile ? "*" : " "} ${p.name} ${p.workspace.name} ${p.email ?? ""} ${p.api_url}`
|
|
10349
10712
|
).join("\n");
|
|
10350
10713
|
ctx.out.result({ ok: true, default: config.default_profile, profiles }, human);
|
|
@@ -10354,7 +10717,7 @@ async function workspaceUse(ctx, args) {
|
|
|
10354
10717
|
const name = args[0];
|
|
10355
10718
|
if (!name || args.length > 1) throw usageError("Usage: amdahl workspace use <profile>");
|
|
10356
10719
|
if (!ctx.config.profiles[name]) {
|
|
10357
|
-
throw usageError(`No profile named "${name}".`,
|
|
10720
|
+
throw usageError(`No profile named "${name}".`, `Run \`${amdahlCommand("workspace list")}\` to see your profiles.`);
|
|
10358
10721
|
}
|
|
10359
10722
|
ctx.config.default_profile = name;
|
|
10360
10723
|
ctx.saveConfig();
|
|
@@ -10363,7 +10726,7 @@ async function workspaceUse(ctx, args) {
|
|
|
10363
10726
|
}
|
|
10364
10727
|
|
|
10365
10728
|
// src/version.ts
|
|
10366
|
-
var VERSION = "0.3.
|
|
10729
|
+
var VERSION = "0.3.4";
|
|
10367
10730
|
|
|
10368
10731
|
// src/main.ts
|
|
10369
10732
|
var COMMANDS = {
|
|
@@ -10381,8 +10744,9 @@ var COMMANDS = {
|
|
|
10381
10744
|
install,
|
|
10382
10745
|
"skills add": skillsAdd
|
|
10383
10746
|
};
|
|
10384
|
-
var NO_ARGS = /* @__PURE__ */ new Set(["login", "logout", "whoami", "auth token", "status", "keys create", "keys list", "workspace list"]);
|
|
10747
|
+
var NO_ARGS = /* @__PURE__ */ new Set(["setup", "login", "logout", "whoami", "auth token", "status", "keys create", "keys list", "workspace list"]);
|
|
10385
10748
|
async function runCli(argv, io) {
|
|
10749
|
+
setCommandName(io);
|
|
10386
10750
|
const json = argv.includes("--json");
|
|
10387
10751
|
let out = createOutput(io, json, argv.includes("--no-color"));
|
|
10388
10752
|
try {
|
|
@@ -10393,18 +10757,21 @@ async function runCli(argv, io) {
|
|
|
10393
10757
|
`);
|
|
10394
10758
|
return EXIT.ok;
|
|
10395
10759
|
}
|
|
10396
|
-
if (parsed.flags.help) {
|
|
10760
|
+
if (parsed.flags.help || parsed.command === "help") {
|
|
10397
10761
|
io.stdout(USAGE);
|
|
10398
10762
|
return EXIT.ok;
|
|
10399
10763
|
}
|
|
10400
|
-
if (
|
|
10401
|
-
if (argv.every((a) => a === "--verbose") && shouldSetUp(io)) return await setup(createCtx(io, out, parsed.flags));
|
|
10402
|
-
io.stderr(USAGE);
|
|
10403
|
-
return EXIT.usage;
|
|
10404
|
-
}
|
|
10405
|
-
if (NO_ARGS.has(parsed.command) && parsed.args.length > 0) {
|
|
10764
|
+
if (NO_ARGS.has(parsed.command ?? "") && parsed.args.length > 0) {
|
|
10406
10765
|
throw new CliError("usage", `Unexpected argument: ${parsed.args[0]}`, EXIT.usage);
|
|
10407
10766
|
}
|
|
10767
|
+
if (!parsed.command || parsed.command === "setup") {
|
|
10768
|
+
const mode = parsed.flags.json ? { kind: "usage", reason: "it has no --json output" } : setupMode(io);
|
|
10769
|
+
if (mode.kind === "usage") {
|
|
10770
|
+
io.stderr(`${noSetupLine(mode.reason)}${USAGE}`);
|
|
10771
|
+
return EXIT.usage;
|
|
10772
|
+
}
|
|
10773
|
+
return await setup(createCtx(io, out, parsed.flags), mode.kind);
|
|
10774
|
+
}
|
|
10408
10775
|
const handler = COMMANDS[parsed.command];
|
|
10409
10776
|
if (!handler) throw new CliError("usage", `Unknown command ${parsed.command}`, EXIT.usage);
|
|
10410
10777
|
const ctx = createCtx(io, out, parsed.flags);
|