@amdahl/cli 0.3.0 → 0.3.2
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 +35 -16
- package/dist/amdahl.js +386 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,36 +60,48 @@ The quickest way to start is `npx amdahl` with nothing after it, in a
|
|
|
60
60
|
terminal. It walks you through setup:
|
|
61
61
|
|
|
62
62
|
```
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Set up
|
|
63
|
+
$ npx amdahl
|
|
64
|
+
Amdahl · predictive ML for GTM
|
|
65
|
+
Set up in:
|
|
66
66
|
> [x] Claude Code
|
|
67
|
-
[
|
|
67
|
+
[ ] Cursor
|
|
68
68
|
[ ] Codex
|
|
69
69
|
(Use the arrow keys, Space to tick or untick, then Enter)
|
|
70
|
+
Opening your browser…
|
|
71
|
+
✓ Signed in · Acme
|
|
72
|
+
✓ Added to Claude Code
|
|
73
|
+
Try it: in Claude Code, run /mcp once to connect, then say "make this email sound less like AI"
|
|
70
74
|
```
|
|
71
75
|
|
|
72
76
|
It finds Claude Code, Cursor and Codex by their command on your PATH
|
|
73
77
|
(`claude`, `cursor`, `codex`) or their folder in your home folder (`.claude`,
|
|
74
78
|
`.cursor`, `.codex`), and ticks the ones it finds. Enter accepts. If you are
|
|
75
|
-
not signed in, it signs you in through the browser first
|
|
76
|
-
|
|
79
|
+
not signed in, it signs you in through the browser first. It prints the
|
|
80
|
+
sign-in link only if the browser could not be opened, or nothing came back
|
|
81
|
+
within a minute, and it waits up to 15 minutes, so someone new can create
|
|
82
|
+
their workspace on the way and land back in the same setup. If the server
|
|
83
|
+
rejects a key set in
|
|
77
84
|
`AMDAHL_KEY` (or `AMDAHL_API_KEY`, `AMDAHL_ACCESS_TOKEN`, `--api-key`), it says
|
|
78
85
|
so and skips the browser sign-in, since that key would still win. Then it
|
|
79
|
-
|
|
86
|
+
sets up each client you ticked the way `amdahl install` does, one line each.
|
|
87
|
+
A client that fails prints
|
|
80
88
|
its error and the rest still run; the exit code is the first failure's. It
|
|
81
|
-
ends with something to try
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
ends with something to try in the first client it set up. Each client then
|
|
90
|
+
signs in to Amdahl itself, once, the first time it connects: `/mcp` in
|
|
91
|
+
Claude Code, `codex mcp login amdahl` for Codex (always named, since the CLI
|
|
92
|
+
cannot tell whether it ran), and Cursor asks on first use.
|
|
93
|
+
`npx amdahl --verbose` also prints the files it wrote, the profile and where
|
|
94
|
+
the sign-in is stored.
|
|
84
95
|
Run it again once you are set up and it says `You're set up in Claude Code
|
|
85
|
-
and Cursor.` with
|
|
96
|
+
and Cursor.` with a line to try, and asks nothing; if it cannot reach
|
|
86
97
|
Amdahl to check your sign-in, it still says so. Ctrl+C at the question stops
|
|
87
98
|
it with nothing written (exit 130).
|
|
88
99
|
|
|
89
100
|
It only does this for a person at a terminal. When stdin, stdout or stderr
|
|
90
101
|
(where the menu draws) is not a terminal, `CI` is set, an agent runs it (`CLAUDECODE` or `CURSOR_AGENT` is
|
|
91
|
-
set), `AMDAHL_NO_PROMPT=1` is set, or any flag
|
|
92
|
-
bare `amdahl` prints the usage to stderr and exits 2, as
|
|
102
|
+
set), `AMDAHL_NO_PROMPT=1` is set, or any flag other than `--verbose` is given
|
|
103
|
+
(such as `--json`), bare `amdahl` prints the usage to stderr and exits 2, as
|
|
104
|
+
before.
|
|
93
105
|
|
|
94
106
|
To connect one AI client yourself, run `amdahl install claude-code` (or `codex`,
|
|
95
107
|
`cursor`). It adds the Amdahl MCP server and also installs the Amdahl skill,
|
|
@@ -124,8 +136,14 @@ amdahl install claude-code # connect an AI client and add the Amdahl skill (al
|
|
|
124
136
|
amdahl skills add <publisher>/<skill> # add a skill from amdahl.ai/skills to this project
|
|
125
137
|
```
|
|
126
138
|
|
|
127
|
-
No workspace yet?
|
|
128
|
-
|
|
139
|
+
No workspace yet? With a work email, the sign-in page offers to create your
|
|
140
|
+
company's workspace and then finishes signing you in. If you signed in with
|
|
141
|
+
an address Amdahl can't use, the browser tab and the terminal say what to do
|
|
142
|
+
and link a new sign-in ("Sign in with your work email", "Sign in again" for
|
|
143
|
+
a subdomain address, "Try again" once you've confirmed your email), and the
|
|
144
|
+
terminal keeps waiting for it. Anything
|
|
145
|
+
else exits 5 with the one next step (ask your company's admins, try again
|
|
146
|
+
tomorrow, and so on). The CLI itself never creates or joins a workspace.
|
|
129
147
|
|
|
130
148
|
On a server or in CI, skip `login` and set an API key in `AMDAHL_KEY`.
|
|
131
149
|
|
|
@@ -325,7 +343,8 @@ the default profile). `whoami` names the source that won. `keys create`,
|
|
|
325
343
|
tokens.
|
|
326
344
|
- Tokens: the OS keychain (service `amdahl-cli`; macOS `security`, Linux
|
|
327
345
|
`secret-tool`), passed on stdin, never on argv. Without a keychain:
|
|
328
|
-
`credentials.json` (mode 0600)
|
|
346
|
+
`credentials.json` (mode 0600), named under `--verbose`. If that file is
|
|
347
|
+
ever readable by other users, the CLI makes it owner-only and warns.
|
|
329
348
|
`AMDAHL_CREDENTIAL_STORE=keychain|file` forces either.
|
|
330
349
|
|
|
331
350
|
## Keys
|
package/dist/amdahl.js
CHANGED
|
@@ -1000,14 +1000,14 @@ var require_foldFlowLines = __commonJS({
|
|
|
1000
1000
|
var FOLD_FLOW = "flow";
|
|
1001
1001
|
var FOLD_BLOCK = "block";
|
|
1002
1002
|
var FOLD_QUOTED = "quoted";
|
|
1003
|
-
function foldFlowLines(
|
|
1003
|
+
function foldFlowLines(text2, indent, mode = "flow", { indentAtStart, lineWidth = 80, minContentWidth = 20, onFold, onOverflow } = {}) {
|
|
1004
1004
|
if (!lineWidth || lineWidth < 0)
|
|
1005
|
-
return
|
|
1005
|
+
return text2;
|
|
1006
1006
|
if (lineWidth < minContentWidth)
|
|
1007
1007
|
minContentWidth = 0;
|
|
1008
1008
|
const endStep = Math.max(1 + minContentWidth, 1 + lineWidth - indent.length);
|
|
1009
|
-
if (
|
|
1010
|
-
return
|
|
1009
|
+
if (text2.length <= endStep)
|
|
1010
|
+
return text2;
|
|
1011
1011
|
const folds = [];
|
|
1012
1012
|
const escapedFolds = {};
|
|
1013
1013
|
let end = lineWidth - indent.length;
|
|
@@ -1024,14 +1024,14 @@ var require_foldFlowLines = __commonJS({
|
|
|
1024
1024
|
let escStart = -1;
|
|
1025
1025
|
let escEnd = -1;
|
|
1026
1026
|
if (mode === FOLD_BLOCK) {
|
|
1027
|
-
i = consumeMoreIndentedLines(
|
|
1027
|
+
i = consumeMoreIndentedLines(text2, i, indent.length);
|
|
1028
1028
|
if (i !== -1)
|
|
1029
1029
|
end = i + endStep;
|
|
1030
1030
|
}
|
|
1031
|
-
for (let ch; ch =
|
|
1031
|
+
for (let ch; ch = text2[i += 1]; ) {
|
|
1032
1032
|
if (mode === FOLD_QUOTED && ch === "\\") {
|
|
1033
1033
|
escStart = i;
|
|
1034
|
-
switch (
|
|
1034
|
+
switch (text2[i + 1]) {
|
|
1035
1035
|
case "x":
|
|
1036
1036
|
i += 3;
|
|
1037
1037
|
break;
|
|
@@ -1048,12 +1048,12 @@ var require_foldFlowLines = __commonJS({
|
|
|
1048
1048
|
}
|
|
1049
1049
|
if (ch === "\n") {
|
|
1050
1050
|
if (mode === FOLD_BLOCK)
|
|
1051
|
-
i = consumeMoreIndentedLines(
|
|
1051
|
+
i = consumeMoreIndentedLines(text2, i, indent.length);
|
|
1052
1052
|
end = i + indent.length + endStep;
|
|
1053
1053
|
split = void 0;
|
|
1054
1054
|
} else {
|
|
1055
1055
|
if (ch === " " && prev && prev !== " " && prev !== "\n" && prev !== " ") {
|
|
1056
|
-
const next =
|
|
1056
|
+
const next = text2[i + 1];
|
|
1057
1057
|
if (next && next !== " " && next !== "\n" && next !== " ")
|
|
1058
1058
|
split = i;
|
|
1059
1059
|
}
|
|
@@ -1065,12 +1065,12 @@ var require_foldFlowLines = __commonJS({
|
|
|
1065
1065
|
} else if (mode === FOLD_QUOTED) {
|
|
1066
1066
|
while (prev === " " || prev === " ") {
|
|
1067
1067
|
prev = ch;
|
|
1068
|
-
ch =
|
|
1068
|
+
ch = text2[i += 1];
|
|
1069
1069
|
overflow = true;
|
|
1070
1070
|
}
|
|
1071
1071
|
const j = i > escEnd + 1 ? i - 2 : escStart - 1;
|
|
1072
1072
|
if (escapedFolds[j])
|
|
1073
|
-
return
|
|
1073
|
+
return text2;
|
|
1074
1074
|
folds.push(j);
|
|
1075
1075
|
escapedFolds[j] = true;
|
|
1076
1076
|
end = j + endStep;
|
|
@@ -1085,39 +1085,39 @@ var require_foldFlowLines = __commonJS({
|
|
|
1085
1085
|
if (overflow && onOverflow)
|
|
1086
1086
|
onOverflow();
|
|
1087
1087
|
if (folds.length === 0)
|
|
1088
|
-
return
|
|
1088
|
+
return text2;
|
|
1089
1089
|
if (onFold)
|
|
1090
1090
|
onFold();
|
|
1091
|
-
let res =
|
|
1091
|
+
let res = text2.slice(0, folds[0]);
|
|
1092
1092
|
for (let i2 = 0; i2 < folds.length; ++i2) {
|
|
1093
1093
|
const fold = folds[i2];
|
|
1094
|
-
const end2 = folds[i2 + 1] ||
|
|
1094
|
+
const end2 = folds[i2 + 1] || text2.length;
|
|
1095
1095
|
if (fold === 0)
|
|
1096
1096
|
res = `
|
|
1097
|
-
${indent}${
|
|
1097
|
+
${indent}${text2.slice(0, end2)}`;
|
|
1098
1098
|
else {
|
|
1099
1099
|
if (mode === FOLD_QUOTED && escapedFolds[fold])
|
|
1100
|
-
res += `${
|
|
1100
|
+
res += `${text2[fold]}\\`;
|
|
1101
1101
|
res += `
|
|
1102
|
-
${indent}${
|
|
1102
|
+
${indent}${text2.slice(fold + 1, end2)}`;
|
|
1103
1103
|
}
|
|
1104
1104
|
}
|
|
1105
1105
|
return res;
|
|
1106
1106
|
}
|
|
1107
|
-
function consumeMoreIndentedLines(
|
|
1107
|
+
function consumeMoreIndentedLines(text2, i, indent) {
|
|
1108
1108
|
let end = i;
|
|
1109
1109
|
let start = i + 1;
|
|
1110
|
-
let ch =
|
|
1110
|
+
let ch = text2[start];
|
|
1111
1111
|
while (ch === " " || ch === " ") {
|
|
1112
1112
|
if (i < start + indent) {
|
|
1113
|
-
ch =
|
|
1113
|
+
ch = text2[++i];
|
|
1114
1114
|
} else {
|
|
1115
1115
|
do {
|
|
1116
|
-
ch =
|
|
1116
|
+
ch = text2[++i];
|
|
1117
1117
|
} while (ch && ch !== "\n");
|
|
1118
1118
|
end = i;
|
|
1119
1119
|
start = i + 1;
|
|
1120
|
-
ch =
|
|
1120
|
+
ch = text2[start];
|
|
1121
1121
|
}
|
|
1122
1122
|
}
|
|
1123
1123
|
return end;
|
|
@@ -6347,15 +6347,15 @@ var require_parser = __commonJS({
|
|
|
6347
6347
|
var node_process = __require("process");
|
|
6348
6348
|
var cst = require_cst();
|
|
6349
6349
|
var lexer = require_lexer();
|
|
6350
|
-
function includesToken(
|
|
6351
|
-
for (let i = 0; i <
|
|
6352
|
-
if (
|
|
6350
|
+
function includesToken(list2, type) {
|
|
6351
|
+
for (let i = 0; i < list2.length; ++i)
|
|
6352
|
+
if (list2[i].type === type)
|
|
6353
6353
|
return true;
|
|
6354
6354
|
return false;
|
|
6355
6355
|
}
|
|
6356
|
-
function findNonEmptyIndex(
|
|
6357
|
-
for (let i = 0; i <
|
|
6358
|
-
switch (
|
|
6356
|
+
function findNonEmptyIndex(list2) {
|
|
6357
|
+
for (let i = 0; i < list2.length; ++i) {
|
|
6358
|
+
switch (list2[i].type) {
|
|
6359
6359
|
case "space":
|
|
6360
6360
|
case "comment":
|
|
6361
6361
|
case "newline":
|
|
@@ -7406,10 +7406,10 @@ ${SHOW_CURSOR}`);
|
|
|
7406
7406
|
reject(outcome.error);
|
|
7407
7407
|
}
|
|
7408
7408
|
};
|
|
7409
|
-
const onKeypress = (
|
|
7410
|
-
const name = key?.name ??
|
|
7409
|
+
const onKeypress = (text2, key) => {
|
|
7410
|
+
const name = key?.name ?? text2;
|
|
7411
7411
|
if (key?.ctrl && (name === "c" || name === "d")) return finish({ error: new PromptCancelled() });
|
|
7412
|
-
const result = onKey(
|
|
7412
|
+
const result = onKey(text2, name);
|
|
7413
7413
|
if (result === "redraw") output.write(`${up(height + 1)}${draw()}`);
|
|
7414
7414
|
else if (result) finish(result);
|
|
7415
7415
|
};
|
|
@@ -7432,19 +7432,19 @@ ${draw()}`);
|
|
|
7432
7432
|
function marker(options) {
|
|
7433
7433
|
return options.color ? options.trueColor ? "\x1B[1;38;2;253;75;3m" : "\x1B[1;38;5;202m" : "\x1B[1m";
|
|
7434
7434
|
}
|
|
7435
|
-
function move(current, count,
|
|
7435
|
+
function move(current, count, text2, name) {
|
|
7436
7436
|
if (name === "up" || name === "k") return (current - 1 + count) % count;
|
|
7437
7437
|
if (name === "down" || name === "j") return (current + 1) % count;
|
|
7438
|
-
if (
|
|
7438
|
+
if (text2 && /^[1-9]$/.test(text2) && Number(text2) <= count) return Number(text2) - 1;
|
|
7439
7439
|
return null;
|
|
7440
7440
|
}
|
|
7441
7441
|
function selectMenu(input, output, question, choices, options = {}) {
|
|
7442
7442
|
let current = Math.min(Math.max(options.initial ?? 0, 0), choices.length - 1);
|
|
7443
7443
|
const on = marker(options);
|
|
7444
7444
|
const rows = () => choices.map((c, i) => i === current ? `${on}> ${c}${OFF}` : ` ${c}`);
|
|
7445
|
-
return runMenu(input, output, question, HINT, rows, (
|
|
7445
|
+
return runMenu(input, output, question, HINT, rows, (text2, name) => {
|
|
7446
7446
|
if (name === "return" || name === "enter") return { pick: current, summary: choices[current] ?? "" };
|
|
7447
|
-
const next = move(current, choices.length,
|
|
7447
|
+
const next = move(current, choices.length, text2, name);
|
|
7448
7448
|
if (next === null || next === current) return null;
|
|
7449
7449
|
current = next;
|
|
7450
7450
|
return "redraw";
|
|
@@ -7458,7 +7458,7 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
|
|
|
7458
7458
|
const row = `[${ticked[i] ? "x" : " "}] ${c}`;
|
|
7459
7459
|
return i === current ? `${on}> ${row}${OFF}` : ` ${row}`;
|
|
7460
7460
|
});
|
|
7461
|
-
return runMenu(input, output, question, MULTI_HINT, rows, (
|
|
7461
|
+
return runMenu(input, output, question, MULTI_HINT, rows, (text2, name) => {
|
|
7462
7462
|
if (name === "return" || name === "enter") {
|
|
7463
7463
|
const picks = choices.flatMap((_, i) => ticked[i] ? [i] : []);
|
|
7464
7464
|
return { pick: picks, summary: picks.map((i) => choices[i]).join(", ") || "none" };
|
|
@@ -7467,12 +7467,12 @@ function multiSelectMenu(input, output, question, choices, options = {}) {
|
|
|
7467
7467
|
ticked[current] = !ticked[current];
|
|
7468
7468
|
return "redraw";
|
|
7469
7469
|
}
|
|
7470
|
-
if (
|
|
7471
|
-
current = Number(
|
|
7470
|
+
if (text2 && /^[1-9]$/.test(text2) && Number(text2) <= choices.length) {
|
|
7471
|
+
current = Number(text2) - 1;
|
|
7472
7472
|
ticked[current] = !ticked[current];
|
|
7473
7473
|
return "redraw";
|
|
7474
7474
|
}
|
|
7475
|
-
const next = move(current, choices.length,
|
|
7475
|
+
const next = move(current, choices.length, text2, name);
|
|
7476
7476
|
if (next === null || next === current) return null;
|
|
7477
7477
|
current = next;
|
|
7478
7478
|
return "redraw";
|
|
@@ -7548,8 +7548,8 @@ function openerFor(platform, url) {
|
|
|
7548
7548
|
function realIo() {
|
|
7549
7549
|
return {
|
|
7550
7550
|
env: process.env,
|
|
7551
|
-
stdout: (
|
|
7552
|
-
stderr: (
|
|
7551
|
+
stdout: (text2) => process.stdout.write(text2),
|
|
7552
|
+
stderr: (text2) => process.stderr.write(text2),
|
|
7553
7553
|
stdinIsTTY: Boolean(process.stdin.isTTY),
|
|
7554
7554
|
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
7555
7555
|
stderrIsTTY: Boolean(process.stderr.isTTY),
|
|
@@ -7567,7 +7567,7 @@ function realIo() {
|
|
|
7567
7567
|
run: runProcess,
|
|
7568
7568
|
openUrl: async (url) => {
|
|
7569
7569
|
const [cmd, args] = openerFor(process.platform, url);
|
|
7570
|
-
await runProcess(cmd, args);
|
|
7570
|
+
return (await runProcess(cmd, args)).code === 0;
|
|
7571
7571
|
},
|
|
7572
7572
|
homedir: homedir(),
|
|
7573
7573
|
cwd: process.cwd(),
|
|
@@ -7575,7 +7575,8 @@ function realIo() {
|
|
|
7575
7575
|
hostname: hostname(),
|
|
7576
7576
|
now: () => Date.now(),
|
|
7577
7577
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
7578
|
-
loginTimeoutMs:
|
|
7578
|
+
loginTimeoutMs: 9e5,
|
|
7579
|
+
browserGraceMs: 6e4,
|
|
7579
7580
|
fetchTimeoutMs: 15e3
|
|
7580
7581
|
};
|
|
7581
7582
|
}
|
|
@@ -7654,7 +7655,8 @@ var GLOBAL_OPTIONS = {
|
|
|
7654
7655
|
json: { type: "boolean" },
|
|
7655
7656
|
"no-color": { type: "boolean" },
|
|
7656
7657
|
help: { type: "boolean", short: "h" },
|
|
7657
|
-
version: { type: "boolean", short: "v" }
|
|
7658
|
+
version: { type: "boolean", short: "v" },
|
|
7659
|
+
verbose: { type: "boolean" }
|
|
7658
7660
|
};
|
|
7659
7661
|
var COMMAND_OPTIONS = {
|
|
7660
7662
|
login: {
|
|
@@ -7753,6 +7755,7 @@ var USAGE = `Usage: amdahl <command> [options]
|
|
|
7753
7755
|
What you can do:
|
|
7754
7756
|
amdahl Set up Claude Code, Cursor or Codex, in a terminal: it asks
|
|
7755
7757
|
which, signs you in, and runs install for each one
|
|
7758
|
+
(--verbose shows the files and profile it used)
|
|
7756
7759
|
amdahl optimize draft.md Rewrite an outbound email or LinkedIn message
|
|
7757
7760
|
amdahl status Check that this workspace is set up to optimize
|
|
7758
7761
|
amdahl keys create --name ci Make an API key for a server or CI
|
|
@@ -7793,6 +7796,7 @@ Global options:
|
|
|
7793
7796
|
--api-key <key> Use an API key for this command (prefer AMDAHL_KEY: argv is visible to other users)
|
|
7794
7797
|
--json Data on stdout as JSON, messages on stderr
|
|
7795
7798
|
--no-color No colour (NO_COLOR is honoured too)
|
|
7799
|
+
--verbose Say more: files written, the profile, where the sign-in is stored
|
|
7796
7800
|
-h, --help Show this help
|
|
7797
7801
|
-v, --version Show the version
|
|
7798
7802
|
`;
|
|
@@ -8087,13 +8091,55 @@ function authorizeUrl(endpoints, input) {
|
|
|
8087
8091
|
const query = params.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
|
|
8088
8092
|
return `${endpoints.authorization_endpoint}?${query}`;
|
|
8089
8093
|
}
|
|
8090
|
-
|
|
8091
|
-
|
|
8094
|
+
var SIGN_IN_AGAIN = {
|
|
8095
|
+
not_work_email: { why: "Amdahl needs your work email.", label: "Sign in with your work email" },
|
|
8096
|
+
subdomain_email: { why: "Use your main company email.", label: "Sign in again" },
|
|
8097
|
+
email_unconfirmed: { why: "Check your inbox to confirm your email, then try again.", label: "Try again" }
|
|
8098
|
+
};
|
|
8099
|
+
function signInAgainLine(reason, url) {
|
|
8100
|
+
const again = SIGN_IN_AGAIN[reason];
|
|
8101
|
+
return again ? `${again.why} ${again.label}:
|
|
8102
|
+
|
|
8103
|
+
${url}
|
|
8104
|
+
` : "";
|
|
8105
|
+
}
|
|
8106
|
+
function noBusinessMessage(consoleUrl, mayCreate, reason) {
|
|
8107
|
+
switch (reason) {
|
|
8108
|
+
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 \`amdahl 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 \`amdahl login\` again once you're in.`;
|
|
8110
|
+
case "domain_has_workspace":
|
|
8111
|
+
return `Your company already has an Amdahl workspace. Ask its admins to let you in at ${consoleUrl}, then try again.`;
|
|
8112
|
+
case "invite_only":
|
|
8113
|
+
return "Your company's Amdahl workspace is invite-only. Ask a colleague in it to invite you, then try again.";
|
|
8114
|
+
case "not_work_email":
|
|
8115
|
+
case "subdomain_email":
|
|
8116
|
+
case "email_unconfirmed": {
|
|
8117
|
+
const again = SIGN_IN_AGAIN[reason];
|
|
8118
|
+
return `${again.why} Run this again, then choose "${again.label}" in the browser.`;
|
|
8119
|
+
}
|
|
8120
|
+
case "daily_cap":
|
|
8121
|
+
return "Amdahl isn't taking more new workspaces today. Try again tomorrow.";
|
|
8122
|
+
case "paused":
|
|
8123
|
+
return "New workspaces are paused for now. Try again later.";
|
|
8124
|
+
case "domain_used":
|
|
8125
|
+
return "Your company already used its free workspace. Email hello@amdahl.ai to get access.";
|
|
8126
|
+
default:
|
|
8127
|
+
return "Amdahl couldn't create your workspace just now. Try again in a few minutes.";
|
|
8128
|
+
}
|
|
8129
|
+
}
|
|
8130
|
+
function signInAgainUrl(authorize) {
|
|
8131
|
+
const url = new URL(authorize);
|
|
8132
|
+
return `${url.origin}/login?redirect=${encodeURIComponent(`${url.pathname}${url.search}`)}`;
|
|
8133
|
+
}
|
|
8134
|
+
function html(text2) {
|
|
8135
|
+
return text2.replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
8092
8136
|
}
|
|
8093
8137
|
function page(body) {
|
|
8094
8138
|
return `<!doctype html><html><head><meta charset="utf-8"><title>Amdahl CLI</title></head><body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;line-height:1.5">${body}</body></html>`;
|
|
8095
8139
|
}
|
|
8096
8140
|
async function startLoopback(io, input) {
|
|
8141
|
+
let authorize = null;
|
|
8142
|
+
let refused = null;
|
|
8097
8143
|
let settle;
|
|
8098
8144
|
let fail;
|
|
8099
8145
|
const result = new Promise((resolve, reject) => {
|
|
@@ -8120,9 +8166,18 @@ async function startLoopback(io, input) {
|
|
|
8120
8166
|
const error = url.searchParams.get("error");
|
|
8121
8167
|
const code = url.searchParams.get("code");
|
|
8122
8168
|
if (error === "no_business") {
|
|
8123
|
-
const
|
|
8124
|
-
|
|
8125
|
-
|
|
8169
|
+
const mayCreate = url.searchParams.get("may_create") === "true";
|
|
8170
|
+
const reason = url.searchParams.get("reason") ?? void 0;
|
|
8171
|
+
const again = reason ? SIGN_IN_AGAIN[reason] : void 0;
|
|
8172
|
+
if (again && authorize) {
|
|
8173
|
+
refused = reason;
|
|
8174
|
+
const retry = signInAgainUrl(authorize);
|
|
8175
|
+
send2(200, `<p>${html(again.why)}</p><p><a href="${html(retry)}">${html(again.label)} \u2192</a></p>`);
|
|
8176
|
+
input.onSignInAgain?.(reason, retry);
|
|
8177
|
+
return;
|
|
8178
|
+
}
|
|
8179
|
+
send2(200, `<p>${html(noBusinessMessage(input.consoleUrl, mayCreate, reason))}</p>`);
|
|
8180
|
+
settle({ kind: "no_business", mayCreate, reason });
|
|
8126
8181
|
} else if (error) {
|
|
8127
8182
|
send2(400, `<p>Sign-in failed: ${html(url.searchParams.get("error_description") ?? error)}</p>`);
|
|
8128
8183
|
settle({ kind: "error", error, description: url.searchParams.get("error_description") ?? void 0 });
|
|
@@ -8150,12 +8205,17 @@ async function startLoopback(io, input) {
|
|
|
8150
8205
|
});
|
|
8151
8206
|
const port = server.address().port;
|
|
8152
8207
|
const timer = setTimeout(() => {
|
|
8153
|
-
fail(
|
|
8208
|
+
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, "Run `amdahl login` again.")
|
|
8210
|
+
);
|
|
8154
8211
|
}, input.timeoutMs);
|
|
8155
8212
|
timer.unref?.();
|
|
8156
8213
|
return {
|
|
8157
8214
|
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
8158
8215
|
result,
|
|
8216
|
+
setAuthorizeUrl: (url) => {
|
|
8217
|
+
authorize = url;
|
|
8218
|
+
},
|
|
8159
8219
|
close: () => {
|
|
8160
8220
|
clearTimeout(timer);
|
|
8161
8221
|
server.closeAllConnections?.();
|
|
@@ -8173,7 +8233,7 @@ function consoleUrlFor(io, apiUrl) {
|
|
|
8173
8233
|
}
|
|
8174
8234
|
|
|
8175
8235
|
// src/secrets.ts
|
|
8176
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
8236
|
+
import { chmodSync as chmodSync2, existsSync as existsSync2, readFileSync as readFileSync2, rmSync, statSync } from "fs";
|
|
8177
8237
|
import { join as join2 } from "path";
|
|
8178
8238
|
var KEYCHAIN_SERVICE = "amdahl-cli";
|
|
8179
8239
|
function keychainAccount(apiUrl, profile) {
|
|
@@ -8260,10 +8320,28 @@ function readCredentials(path) {
|
|
|
8260
8320
|
return { version: 1, accounts: {} };
|
|
8261
8321
|
}
|
|
8262
8322
|
}
|
|
8263
|
-
function fileStore(io,
|
|
8323
|
+
function fileStore(io, note) {
|
|
8264
8324
|
const path = credentialsPath(io);
|
|
8325
|
+
let checked = false;
|
|
8326
|
+
const checkMode = () => {
|
|
8327
|
+
if (checked || io.platform === "win32" || !existsSync2(path)) return;
|
|
8328
|
+
checked = true;
|
|
8329
|
+
if ((statSync(path).mode & 63) === 0) return;
|
|
8330
|
+
try {
|
|
8331
|
+
chmodSync2(path, 384);
|
|
8332
|
+
} catch {
|
|
8333
|
+
io.stderr(`Warning: ${path} can be read by other users on this machine, and this CLI cannot change that. Run \`chmod 600 ${path}\` as its owner.
|
|
8334
|
+
`);
|
|
8335
|
+
return;
|
|
8336
|
+
}
|
|
8337
|
+
io.stderr(
|
|
8338
|
+
`Warning: ${path} was readable by other users on this machine. It is private now; to be safe, run \`amdahl logout\` and sign in again.
|
|
8339
|
+
`
|
|
8340
|
+
);
|
|
8341
|
+
};
|
|
8265
8342
|
return {
|
|
8266
8343
|
async get(account) {
|
|
8344
|
+
checkMode();
|
|
8267
8345
|
return asSecrets(readCredentials(path).accounts[account]);
|
|
8268
8346
|
},
|
|
8269
8347
|
async set(account, secrets) {
|
|
@@ -8271,11 +8349,9 @@ function fileStore(io, warn) {
|
|
|
8271
8349
|
const file = readCredentials(path);
|
|
8272
8350
|
file.accounts[account] = secrets;
|
|
8273
8351
|
writePrivateJson(path, file);
|
|
8274
|
-
if (!existed &&
|
|
8275
|
-
io.stderr(
|
|
8276
|
-
|
|
8277
|
-
`
|
|
8278
|
-
);
|
|
8352
|
+
if (!existed && note) {
|
|
8353
|
+
io.stderr(`No OS keychain here, so your sign-in is stored in ${path} (readable only by you).
|
|
8354
|
+
`);
|
|
8279
8355
|
}
|
|
8280
8356
|
},
|
|
8281
8357
|
async delete(account) {
|
|
@@ -8287,7 +8363,7 @@ function fileStore(io, warn) {
|
|
|
8287
8363
|
}
|
|
8288
8364
|
};
|
|
8289
8365
|
}
|
|
8290
|
-
function createSecretStore(io) {
|
|
8366
|
+
function createSecretStore(io, options = {}) {
|
|
8291
8367
|
const forced = io.env.AMDAHL_CREDENTIAL_STORE;
|
|
8292
8368
|
if (forced && forced !== "keychain" && forced !== "file") {
|
|
8293
8369
|
throw new CliError(
|
|
@@ -8314,7 +8390,7 @@ function createSecretStore(io) {
|
|
|
8314
8390
|
}
|
|
8315
8391
|
};
|
|
8316
8392
|
}
|
|
8317
|
-
const file = fileStore(io, true);
|
|
8393
|
+
const file = fileStore(io, options.verbose === true);
|
|
8318
8394
|
if (!keychain) return file;
|
|
8319
8395
|
return {
|
|
8320
8396
|
async get(account) {
|
|
@@ -8366,7 +8442,7 @@ function createCtx(io, out, flags) {
|
|
|
8366
8442
|
profile,
|
|
8367
8443
|
apiUrl: normaliseApiUrl(rawApi),
|
|
8368
8444
|
nonInteractive: !io.stdinIsTTY || io.env.AMDAHL_NO_PROMPT === "1",
|
|
8369
|
-
secrets: () => store ??= createSecretStore(io),
|
|
8445
|
+
secrets: () => store ??= createSecretStore(io, { verbose: flags.verbose === true }),
|
|
8370
8446
|
saveConfig: () => saveConfig(io, config)
|
|
8371
8447
|
};
|
|
8372
8448
|
}
|
|
@@ -8464,15 +8540,45 @@ async function call(fn) {
|
|
|
8464
8540
|
}
|
|
8465
8541
|
}
|
|
8466
8542
|
|
|
8543
|
+
// src/output.ts
|
|
8544
|
+
var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
8545
|
+
function printable(text2) {
|
|
8546
|
+
return text2.replace(CONTROL_RE, "");
|
|
8547
|
+
}
|
|
8548
|
+
function createOutput(io, json, noColor) {
|
|
8549
|
+
const color = !noColor && !io.env.NO_COLOR && io.env.TERM !== "dumb" && io.stderrIsTTY && !json;
|
|
8550
|
+
return {
|
|
8551
|
+
json,
|
|
8552
|
+
color,
|
|
8553
|
+
result(data, human) {
|
|
8554
|
+
if (json) {
|
|
8555
|
+
io.stdout(`${JSON.stringify(data)}
|
|
8556
|
+
`);
|
|
8557
|
+
} else if (human !== void 0) {
|
|
8558
|
+
io.stdout(human.endsWith("\n") ? human : `${human}
|
|
8559
|
+
`);
|
|
8560
|
+
}
|
|
8561
|
+
},
|
|
8562
|
+
info(text2) {
|
|
8563
|
+
io.stderr(text2.endsWith("\n") ? text2 : `${text2}
|
|
8564
|
+
`);
|
|
8565
|
+
},
|
|
8566
|
+
event(data) {
|
|
8567
|
+
if (json) io.stderr(`${JSON.stringify(data)}
|
|
8568
|
+
`);
|
|
8569
|
+
},
|
|
8570
|
+
bold(text2) {
|
|
8571
|
+
return color ? `\x1B[1m${text2}\x1B[22m` : text2;
|
|
8572
|
+
}
|
|
8573
|
+
};
|
|
8574
|
+
}
|
|
8575
|
+
|
|
8467
8576
|
// src/commands/auth.ts
|
|
8468
8577
|
var PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
8469
|
-
function noBusinessMessage(consoleUrl, mayCreate) {
|
|
8470
|
-
return mayCreate ? `You're signed in, but you don't have a workspace yet. Create one at ${consoleUrl}/new, then run \`amdahl 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 \`amdahl login\` again once you're in.`;
|
|
8471
|
-
}
|
|
8472
8578
|
function tokenPreview(token) {
|
|
8473
8579
|
return `${token.slice(0, 10)}...`;
|
|
8474
8580
|
}
|
|
8475
|
-
async function
|
|
8581
|
+
async function browserSignIn(ctx) {
|
|
8476
8582
|
const { io, out, flags } = ctx;
|
|
8477
8583
|
const workspace = str(flags.workspace);
|
|
8478
8584
|
const rawPort = str(flags.port);
|
|
@@ -8487,35 +8593,52 @@ async function login(ctx) {
|
|
|
8487
8593
|
const endpoints = await discover(ctx.apiUrl);
|
|
8488
8594
|
const pkce = createPkce();
|
|
8489
8595
|
const consoleUrl = consoleUrlFor(io, ctx.apiUrl);
|
|
8596
|
+
let timer;
|
|
8597
|
+
let linkShown = false;
|
|
8490
8598
|
const loopback = await startLoopback(io, {
|
|
8491
8599
|
port,
|
|
8492
8600
|
state: pkce.state,
|
|
8493
8601
|
consoleUrl,
|
|
8494
|
-
timeoutMs: io.loginTimeoutMs
|
|
8602
|
+
timeoutMs: io.loginTimeoutMs,
|
|
8603
|
+
// The page carries the way on; the authorize link would only sign in as the same address again.
|
|
8604
|
+
onSignInAgain: (reason, retry) => {
|
|
8605
|
+
clearTimeout(timer);
|
|
8606
|
+
linkShown = true;
|
|
8607
|
+
out.info(signInAgainLine(reason, retry));
|
|
8608
|
+
}
|
|
8495
8609
|
});
|
|
8496
8610
|
try {
|
|
8497
8611
|
const url = authorizeUrl(endpoints, { redirectUri: loopback.redirectUri, pkce, workspace });
|
|
8612
|
+
loopback.setAuthorizeUrl(url);
|
|
8498
8613
|
out.event({ event: "open_url", url });
|
|
8499
|
-
|
|
8500
|
-
|
|
8614
|
+
const showLink = (lead) => {
|
|
8615
|
+
if (linkShown) return;
|
|
8616
|
+
linkShown = true;
|
|
8617
|
+
out.info(`${lead}
|
|
8501
8618
|
|
|
8502
8619
|
${url}
|
|
8503
8620
|
`);
|
|
8621
|
+
};
|
|
8622
|
+
if (flags["no-browser"]) {
|
|
8623
|
+
showLink("Open this URL in a browser to sign in:");
|
|
8504
8624
|
} else {
|
|
8505
|
-
out.info(
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
|
|
8625
|
+
out.info("Opening your browser\u2026");
|
|
8626
|
+
void io.openUrl(url).then((opened) => {
|
|
8627
|
+
if (!opened) showLink("Your browser did not open. Open this link to sign in:");
|
|
8628
|
+
});
|
|
8629
|
+
timer = setTimeout(() => showLink("Finish in your browser, or open this link:"), io.browserGraceMs);
|
|
8630
|
+
timer.unref?.();
|
|
8510
8631
|
}
|
|
8511
8632
|
const callback = await loopback.result;
|
|
8633
|
+
clearTimeout(timer);
|
|
8634
|
+
linkShown = true;
|
|
8512
8635
|
if (callback.kind === "no_business") {
|
|
8513
|
-
throw new CliError("no_business", noBusinessMessage(consoleUrl, callback.mayCreate), EXIT.noWorkspace);
|
|
8636
|
+
throw new CliError("no_business", noBusinessMessage(consoleUrl, callback.mayCreate, callback.reason), EXIT.noWorkspace);
|
|
8514
8637
|
}
|
|
8515
8638
|
if (callback.kind === "error") {
|
|
8516
8639
|
throw new CliError(
|
|
8517
8640
|
callback.error,
|
|
8518
|
-
`Sign-in failed: ${callback.description ?? callback.error}`,
|
|
8641
|
+
`Sign-in failed: ${printable(callback.description ?? callback.error)}`,
|
|
8519
8642
|
callback.error === "access_denied" ? EXIT.forbidden : EXIT.general
|
|
8520
8643
|
);
|
|
8521
8644
|
}
|
|
@@ -8539,22 +8662,31 @@ async function login(ctx) {
|
|
|
8539
8662
|
};
|
|
8540
8663
|
ctx.config.default_profile = name;
|
|
8541
8664
|
ctx.saveConfig();
|
|
8542
|
-
|
|
8543
|
-
{
|
|
8544
|
-
ok: true,
|
|
8545
|
-
profile: name,
|
|
8546
|
-
user: { id: me.user_id, email: me.email ?? null },
|
|
8547
|
-
workspace: ws,
|
|
8548
|
-
scopes: me.scopes,
|
|
8549
|
-
api_url: ctx.apiUrl
|
|
8550
|
-
},
|
|
8551
|
-
`Signed in as ${me.email ?? me.user_id} to ${out.bold(ws.name)} (${ws.slug}). Profile: ${name}.`
|
|
8552
|
-
);
|
|
8553
|
-
return EXIT.ok;
|
|
8665
|
+
return { profile: name, me, workspace: ws };
|
|
8554
8666
|
} finally {
|
|
8667
|
+
clearTimeout(timer);
|
|
8555
8668
|
loopback.close();
|
|
8556
8669
|
}
|
|
8557
8670
|
}
|
|
8671
|
+
function signedInLine(ctx, { profile, me, workspace }) {
|
|
8672
|
+
return `Signed in as ${printable(me.email ?? me.user_id)} to ${ctx.out.bold(printable(workspace.name))} (${printable(workspace.slug)}). Profile: ${profile}.`;
|
|
8673
|
+
}
|
|
8674
|
+
async function login(ctx) {
|
|
8675
|
+
const signedIn = await browserSignIn(ctx);
|
|
8676
|
+
const { profile, me, workspace } = signedIn;
|
|
8677
|
+
ctx.out.result(
|
|
8678
|
+
{
|
|
8679
|
+
ok: true,
|
|
8680
|
+
profile,
|
|
8681
|
+
user: { id: me.user_id, email: me.email ?? null },
|
|
8682
|
+
workspace,
|
|
8683
|
+
scopes: me.scopes,
|
|
8684
|
+
api_url: ctx.apiUrl
|
|
8685
|
+
},
|
|
8686
|
+
signedInLine(ctx, signedIn)
|
|
8687
|
+
);
|
|
8688
|
+
return EXIT.ok;
|
|
8689
|
+
}
|
|
8558
8690
|
async function logout(ctx) {
|
|
8559
8691
|
const { config, out } = ctx;
|
|
8560
8692
|
const names = ctx.flags.all ? Object.keys(config.profiles) : ctx.profileName && config.profiles[ctx.profileName] ? [ctx.profileName] : [];
|
|
@@ -8658,8 +8790,8 @@ var shipped_default = [
|
|
|
8658
8790
|
var SKILL_URL = "https://docs.amdahl.ai/skills/amdahl/SKILL.md";
|
|
8659
8791
|
var SKILL_TEXT = SKILL_default;
|
|
8660
8792
|
var SHIPPED_SKILL_SHA256 = shipped_default;
|
|
8661
|
-
function isShippedSkill(
|
|
8662
|
-
return SHIPPED_SKILL_SHA256.includes(createHash2("sha256").update(
|
|
8793
|
+
function isShippedSkill(text2) {
|
|
8794
|
+
return SHIPPED_SKILL_SHA256.includes(createHash2("sha256").update(text2).digest("hex"));
|
|
8663
8795
|
}
|
|
8664
8796
|
var SKILL_DIRS = {
|
|
8665
8797
|
"claude-code": [".claude", "skills"],
|
|
@@ -8736,14 +8868,16 @@ async function isInstalled(ctx, client) {
|
|
|
8736
8868
|
return false;
|
|
8737
8869
|
}
|
|
8738
8870
|
}
|
|
8871
|
+
async function installClient(ctx, client, print) {
|
|
8872
|
+
const mcp = await installMcp(ctx, client, print);
|
|
8873
|
+
return { mcp, skill: installSkill(ctx, client, print) };
|
|
8874
|
+
}
|
|
8739
8875
|
async function install(ctx, args) {
|
|
8740
8876
|
const client = args[0];
|
|
8741
8877
|
if (!client || args.length > 1 || !CLIENTS.includes(client)) {
|
|
8742
8878
|
throw usageError(`Usage: amdahl install ${CLIENTS.join("|")} [--print] [--force]`);
|
|
8743
8879
|
}
|
|
8744
|
-
const
|
|
8745
|
-
const mcp = await installMcp(ctx, client, print);
|
|
8746
|
-
const skill = installSkill(ctx, client, print);
|
|
8880
|
+
const { mcp, skill } = await installClient(ctx, client, ctx.flags.print === true);
|
|
8747
8881
|
const human = [mcp.human.replace(/\n$/, ""), skill.human].filter(Boolean).join("\n");
|
|
8748
8882
|
ctx.out.result({ ...mcp.data, skill: skill.data }, human);
|
|
8749
8883
|
for (const note of [...mcp.notes, ...skill.notes]) ctx.out.info(note);
|
|
@@ -9044,6 +9178,119 @@ function withTimeout(promise, ms, file) {
|
|
|
9044
9178
|
});
|
|
9045
9179
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
9046
9180
|
}
|
|
9181
|
+
var WEAKEST = {
|
|
9182
|
+
rules: "how it follows your rules",
|
|
9183
|
+
ask: "the ask",
|
|
9184
|
+
opener: "the opener",
|
|
9185
|
+
fit: "how it fits the recipient",
|
|
9186
|
+
proof: "its proof points",
|
|
9187
|
+
length: "the length",
|
|
9188
|
+
style: "the tone",
|
|
9189
|
+
preference: "how it matches your preferences",
|
|
9190
|
+
voice: "how much it sounds like you"
|
|
9191
|
+
};
|
|
9192
|
+
var CONCERNS = {
|
|
9193
|
+
conflicts_with_callers_ask: "it asks for a different length or time than your ask",
|
|
9194
|
+
drops_ask: "it leaves out your ask",
|
|
9195
|
+
adds_claim: "it states something neither your draft nor your facts support",
|
|
9196
|
+
drops_entity: "it drops a name, link or placeholder you had",
|
|
9197
|
+
inflates: "it is longer than you asked for",
|
|
9198
|
+
no_gain: "it changed little beyond the wording",
|
|
9199
|
+
breaks_rules: "it breaks one of your rules"
|
|
9200
|
+
};
|
|
9201
|
+
var EVIDENCE_MISSED = {
|
|
9202
|
+
corpus_empty: "the workspace has none yet",
|
|
9203
|
+
evidence_unavailable: "this environment does not read them",
|
|
9204
|
+
evidence_read_failed: "the read failed, so a retry may work"
|
|
9205
|
+
};
|
|
9206
|
+
var MAX_LINES = 3;
|
|
9207
|
+
function list(value) {
|
|
9208
|
+
return Array.isArray(value) ? value.filter((v) => !!v && typeof v === "object") : null;
|
|
9209
|
+
}
|
|
9210
|
+
function record(value) {
|
|
9211
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9212
|
+
}
|
|
9213
|
+
function text(value) {
|
|
9214
|
+
return typeof value === "string" ? printable(value).trim() : "";
|
|
9215
|
+
}
|
|
9216
|
+
function sentence(value) {
|
|
9217
|
+
return /[.!?]$/.test(value) ? value : `${value}.`;
|
|
9218
|
+
}
|
|
9219
|
+
function plural(n, one, many = `${one}s`) {
|
|
9220
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
9221
|
+
}
|
|
9222
|
+
function readSteps(steps) {
|
|
9223
|
+
const reasonOf = (node) => text(steps.find((s) => s.node === node)?.reason);
|
|
9224
|
+
const changed = (node) => steps.some((s) => s.node === node && s.changed === true);
|
|
9225
|
+
const counts = new Map(
|
|
9226
|
+
reasonOf("w3").split(",").map((part) => part.split(":")).filter(([, n]) => /^\d+$/.test(n ?? "")).map(([code, n]) => [code, Number(n)])
|
|
9227
|
+
);
|
|
9228
|
+
const did = [];
|
|
9229
|
+
const repaired = counts.get("repaired") ?? 0;
|
|
9230
|
+
const deleted = counts.get("deleted") ?? 0;
|
|
9231
|
+
if (repaired && deleted) did.push(`Rewrote ${plural(repaired, "sentence")} and cut ${deleted} that nothing backed.`);
|
|
9232
|
+
else if (repaired) did.push(`Rewrote ${plural(repaired, "sentence")} that did not hold up.`);
|
|
9233
|
+
else if (deleted) did.push(`Cut ${plural(deleted, "sentence")} that nothing backed.`);
|
|
9234
|
+
const repair = reasonOf("w5");
|
|
9235
|
+
if (changed("w5") && repair.startsWith("repaired:")) {
|
|
9236
|
+
const weakest = WEAKEST[repair.slice("repaired:".length)];
|
|
9237
|
+
did.push(weakest ? `Reworked ${weakest}.` : "Reworked its weakest part.");
|
|
9238
|
+
}
|
|
9239
|
+
const flagged = [];
|
|
9240
|
+
const open = counts.get("open");
|
|
9241
|
+
if (open) flagged.push(`${plural(open, "sentence problem")} it could not fix`);
|
|
9242
|
+
const second = reasonOf("w6");
|
|
9243
|
+
if (changed("w6") && !second.startsWith("kept_before_repair")) {
|
|
9244
|
+
const concern = CONCERNS[second];
|
|
9245
|
+
flagged.push(concern ? `a second-check concern (${concern})` : "a second-check concern");
|
|
9246
|
+
}
|
|
9247
|
+
return { did: did.join(" "), flagged };
|
|
9248
|
+
}
|
|
9249
|
+
function and(parts) {
|
|
9250
|
+
return parts.length < 2 ? parts[0] ?? "" : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
9251
|
+
}
|
|
9252
|
+
function whatAmdahlDid(result) {
|
|
9253
|
+
const steps = result.unchanged === true ? { did: "", flagged: [] } : readSteps(list(result.steps) ?? []);
|
|
9254
|
+
const flagged = [...steps.flagged];
|
|
9255
|
+
let checked = "";
|
|
9256
|
+
let removed = "";
|
|
9257
|
+
const evidence = record(result.evidence);
|
|
9258
|
+
const claims = list(result.unsupported_claims);
|
|
9259
|
+
if (evidence?.used === true) {
|
|
9260
|
+
const quotes = record(result.lift)?.quotes;
|
|
9261
|
+
const read = typeof quotes === "number" && quotes > 0 ? ` (${plural(quotes, "quote")})` : "";
|
|
9262
|
+
if (claims) {
|
|
9263
|
+
const still = claims.filter((c) => c.status !== "removed").length;
|
|
9264
|
+
const cut = claims.length - still;
|
|
9265
|
+
if (cut) removed = `Removed ${plural(cut, "claim")} your calls don't back.`;
|
|
9266
|
+
if (still) flagged.unshift(`${plural(still, "claim")} your calls don't back`);
|
|
9267
|
+
checked = `Checked every claim against your calls${read}${claims.length ? "" : " and found none they don't back"}.`;
|
|
9268
|
+
} else checked = `Checked the message against your calls${read}.`;
|
|
9269
|
+
} else if (evidence?.requested === "workspace" && EVIDENCE_MISSED[text(evidence.reason)]) {
|
|
9270
|
+
checked = `Skipped checking claims against your calls: ${EVIDENCE_MISSED[text(evidence.reason)]}.`;
|
|
9271
|
+
}
|
|
9272
|
+
const used = (list(record(result.context_use)?.rows) ?? []).filter((r) => r.verdict === "pass");
|
|
9273
|
+
const sources = [
|
|
9274
|
+
used.some((r) => r.bucket === "internal") ? "Amdahl's own records" : "",
|
|
9275
|
+
used.some((r) => r.bucket === "external") ? "what their company or market published" : ""
|
|
9276
|
+
].filter(Boolean);
|
|
9277
|
+
const usedLine = sources.length ? `Used ${plural(sources.length, "source")}: ${and(sources)}.` : "";
|
|
9278
|
+
const omitted = list(result.omitted) ?? [];
|
|
9279
|
+
const whys = [...new Set(omitted.map((o) => text(o.why)).filter(Boolean))];
|
|
9280
|
+
const more = whys.length > 1 ? ` (${plural(whys.length - 1, "more reason")} in --json)` : "";
|
|
9281
|
+
const leftOut = omitted.length ? `Left out ${plural(omitted.length, "thing")}${whys[0] ? `: ${sentence(whys[0])}` : "."}${more}` : "";
|
|
9282
|
+
const lines = [
|
|
9283
|
+
{ rank: 2, line: sentence(text(result.what_changed)) },
|
|
9284
|
+
{ rank: 5, line: checked },
|
|
9285
|
+
{ rank: 3, line: removed },
|
|
9286
|
+
{ rank: 6, line: usedLine },
|
|
9287
|
+
{ rank: 7, line: steps.did },
|
|
9288
|
+
{ rank: 4, line: leftOut },
|
|
9289
|
+
{ rank: 1, line: flagged.length ? `Flagged ${and(flagged)}; check before sending.` : "" }
|
|
9290
|
+
].filter((l) => l.line && l.line !== ".");
|
|
9291
|
+
const kept = new Set([...lines].sort((a, b) => a.rank - b.rank).slice(0, MAX_LINES));
|
|
9292
|
+
return lines.filter((l) => kept.has(l)).map((l) => l.line);
|
|
9293
|
+
}
|
|
9047
9294
|
async function optimize(ctx, args) {
|
|
9048
9295
|
const { io, out, flags } = ctx;
|
|
9049
9296
|
let files = args;
|
|
@@ -9108,6 +9355,8 @@ async function optimize(ctx, args) {
|
|
|
9108
9355
|
if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
|
|
9109
9356
|
else if (typeof result.summary === "string") out.info(result.summary);
|
|
9110
9357
|
out.result({}, String(result.message ?? ""));
|
|
9358
|
+
const did = whatAmdahlDid(result);
|
|
9359
|
+
if (did.length) out.info(did.join("\n"));
|
|
9111
9360
|
}
|
|
9112
9361
|
}
|
|
9113
9362
|
if (out.json) out.result(results.length === 1 ? results[0] : results);
|
|
@@ -9120,7 +9369,7 @@ import { dirname as dirname3, join as join6, relative, sep } from "path";
|
|
|
9120
9369
|
|
|
9121
9370
|
// src/commands/skills-install.ts
|
|
9122
9371
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
9123
|
-
import { chmodSync as
|
|
9372
|
+
import { chmodSync as chmodSync3, lstatSync, mkdirSync as mkdirSync3, mkdtempSync, readdirSync, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
9124
9373
|
import { dirname as dirname2, join as join5 } from "path";
|
|
9125
9374
|
function skillDirs(targets, name) {
|
|
9126
9375
|
const dirs = [];
|
|
@@ -9237,7 +9486,7 @@ function stage(target, name, archive, staged) {
|
|
|
9237
9486
|
s.createdParent = mkdirSync3(dirname2(target), { recursive: true }) ?? void 0;
|
|
9238
9487
|
refuseLinks(target);
|
|
9239
9488
|
s.temp = mkdtempSync(join5(dirname2(target), `.${name}.amdahl-tmp-`));
|
|
9240
|
-
|
|
9489
|
+
chmodSync3(s.temp, 493);
|
|
9241
9490
|
for (const dir of archive.dirs) mkdirSync3(join5(s.temp, dir));
|
|
9242
9491
|
for (const file of archive.files) writeFileSync3(join5(s.temp, file.path), file.bytes, { flag: "wx", mode: file.mode });
|
|
9243
9492
|
}
|
|
@@ -9398,9 +9647,11 @@ var SHOWN = [
|
|
|
9398
9647
|
{ client: "cursor", name: "Cursor" },
|
|
9399
9648
|
{ client: "codex", name: "Codex" }
|
|
9400
9649
|
];
|
|
9401
|
-
function tryItLine(name) {
|
|
9650
|
+
function tryItLine(name, connect = false) {
|
|
9402
9651
|
const ask2 = 'say "make this email sound less like AI"';
|
|
9403
|
-
|
|
9652
|
+
if (connect && name === "Claude Code") return `Try it: in Claude Code, run /mcp once to connect, then ${ask2}`;
|
|
9653
|
+
if (name === "Codex") return `Try it: run \`codex mcp login amdahl\`, then open Codex and ${ask2}`;
|
|
9654
|
+
return `Try it: open ${name} and ${ask2}`;
|
|
9404
9655
|
}
|
|
9405
9656
|
function shouldSetUp(io) {
|
|
9406
9657
|
const { env } = io;
|
|
@@ -9417,7 +9668,7 @@ async function signIn(ctx) {
|
|
|
9417
9668
|
}
|
|
9418
9669
|
try {
|
|
9419
9670
|
const me = await call(() => apiClient(ctx, credential).cli.whoami());
|
|
9420
|
-
return { kind: "in", email: me.email ?? me.user_id, workspace: me.workspace?.name ?? me.business_id };
|
|
9671
|
+
return { kind: "in", email: printable(me.email ?? me.user_id), workspace: printable(me.workspace?.name ?? me.business_id) };
|
|
9421
9672
|
} catch (err) {
|
|
9422
9673
|
if (!(err instanceof CliError && err.exit === EXIT.unauthenticated)) throw err;
|
|
9423
9674
|
return credential.source === "profile" ? { kind: "out" } : { kind: "rejected", source: credential.source };
|
|
@@ -9425,9 +9676,13 @@ async function signIn(ctx) {
|
|
|
9425
9676
|
}
|
|
9426
9677
|
async function setup(ctx) {
|
|
9427
9678
|
const { io, out } = ctx;
|
|
9679
|
+
const verbose = ctx.flags.verbose === true;
|
|
9428
9680
|
const say = (line) => io.stdout(`${line}
|
|
9429
9681
|
`);
|
|
9430
|
-
|
|
9682
|
+
const detail = (text2) => {
|
|
9683
|
+
if (verbose && text2) say(text2.replace(/^/gm, " "));
|
|
9684
|
+
};
|
|
9685
|
+
say(`${out.bold("Amdahl")} \xB7 predictive ML for GTM`);
|
|
9431
9686
|
const found = await detectClients(ctx);
|
|
9432
9687
|
const detected = SHOWN.filter((c) => found[c.client]);
|
|
9433
9688
|
let who;
|
|
@@ -9448,31 +9703,33 @@ async function setup(ctx) {
|
|
|
9448
9703
|
}
|
|
9449
9704
|
}
|
|
9450
9705
|
}
|
|
9451
|
-
say(`Found on this machine: ${SHOWN.map((c) => `${c.name} ${found[c.client] ? "\u2713" : "\u2013"}`).join(" ")}`);
|
|
9452
|
-
if (detected.length === 0) {
|
|
9453
|
-
say("None of them found here. Tick the ones you use with Space, then press Enter.");
|
|
9454
|
-
}
|
|
9455
9706
|
const picks = await io.multiSelect(
|
|
9456
|
-
"Set up
|
|
9707
|
+
"Set up in:",
|
|
9457
9708
|
SHOWN.map((c) => c.name),
|
|
9458
9709
|
{ ticked: SHOWN.map((c) => found[c.client]), color: out.color }
|
|
9459
9710
|
);
|
|
9460
9711
|
if (picks.length === 0) {
|
|
9461
|
-
say("Nothing picked, so nothing changed.
|
|
9712
|
+
say("Nothing picked, so nothing changed.");
|
|
9462
9713
|
return EXIT.ok;
|
|
9463
9714
|
}
|
|
9464
9715
|
who ??= await signIn(ctx);
|
|
9465
|
-
if (who.kind === "in")
|
|
9466
|
-
|
|
9467
|
-
|
|
9716
|
+
if (who.kind === "in") {
|
|
9717
|
+
say(`\u2713 Signed in \xB7 ${who.workspace}`);
|
|
9718
|
+
detail(`Signed in as ${who.email}.`);
|
|
9719
|
+
} else if (who.kind === "out") {
|
|
9720
|
+
const signedIn = await browserSignIn(ctx);
|
|
9721
|
+
say(`\u2713 Signed in \xB7 ${printable(signedIn.workspace.name)}`);
|
|
9722
|
+
detail(signedInLine(ctx, signedIn));
|
|
9723
|
+
} else {
|
|
9468
9724
|
const name = who.source === "flag" ? "--api-key" : who.source.slice("env:".length);
|
|
9469
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 \`amdahl login\`.`);
|
|
9470
9726
|
}
|
|
9471
9727
|
let exit = EXIT.ok;
|
|
9472
9728
|
let ready = null;
|
|
9473
9729
|
for (const { client, name } of picks.flatMap((i) => SHOWN[i] ?? [])) {
|
|
9730
|
+
let steps;
|
|
9474
9731
|
try {
|
|
9475
|
-
await
|
|
9732
|
+
steps = await installClient(ctx, client, false);
|
|
9476
9733
|
} catch (raw) {
|
|
9477
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);
|
|
9478
9735
|
io.stderr(`Error: ${err.message}
|
|
@@ -9481,9 +9738,18 @@ ${err.hint ? `${err.hint}
|
|
|
9481
9738
|
if (exit === EXIT.ok) exit = err.exit;
|
|
9482
9739
|
continue;
|
|
9483
9740
|
}
|
|
9484
|
-
|
|
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"));
|
|
9485
9751
|
}
|
|
9486
|
-
if (ready) say(tryItLine(ready));
|
|
9752
|
+
if (ready) say(tryItLine(ready.name, ready.connect));
|
|
9487
9753
|
return exit;
|
|
9488
9754
|
}
|
|
9489
9755
|
|
|
@@ -9491,39 +9757,6 @@ ${err.hint ? `${err.hint}
|
|
|
9491
9757
|
var import_yaml = __toESM(require_dist(), 1);
|
|
9492
9758
|
import { createHash as createHash4 } from "crypto";
|
|
9493
9759
|
|
|
9494
|
-
// src/output.ts
|
|
9495
|
-
var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
9496
|
-
function printable(text) {
|
|
9497
|
-
return text.replace(CONTROL_RE, "");
|
|
9498
|
-
}
|
|
9499
|
-
function createOutput(io, json, noColor) {
|
|
9500
|
-
const color = !noColor && !io.env.NO_COLOR && io.env.TERM !== "dumb" && io.stderrIsTTY && !json;
|
|
9501
|
-
return {
|
|
9502
|
-
json,
|
|
9503
|
-
color,
|
|
9504
|
-
result(data, human) {
|
|
9505
|
-
if (json) {
|
|
9506
|
-
io.stdout(`${JSON.stringify(data)}
|
|
9507
|
-
`);
|
|
9508
|
-
} else if (human !== void 0) {
|
|
9509
|
-
io.stdout(human.endsWith("\n") ? human : `${human}
|
|
9510
|
-
`);
|
|
9511
|
-
}
|
|
9512
|
-
},
|
|
9513
|
-
info(text) {
|
|
9514
|
-
io.stderr(text.endsWith("\n") ? text : `${text}
|
|
9515
|
-
`);
|
|
9516
|
-
},
|
|
9517
|
-
event(data) {
|
|
9518
|
-
if (json) io.stderr(`${JSON.stringify(data)}
|
|
9519
|
-
`);
|
|
9520
|
-
},
|
|
9521
|
-
bold(text) {
|
|
9522
|
-
return color ? `\x1B[1m${text}\x1B[22m` : text;
|
|
9523
|
-
}
|
|
9524
|
-
};
|
|
9525
|
-
}
|
|
9526
|
-
|
|
9527
9760
|
// src/tar-gz.ts
|
|
9528
9761
|
import { inflateRawSync } from "zlib";
|
|
9529
9762
|
var MAX_ARCHIVE_FILES = 200;
|
|
@@ -9598,8 +9831,8 @@ function field(header, start, length) {
|
|
|
9598
9831
|
}
|
|
9599
9832
|
}
|
|
9600
9833
|
function octal(header, start, length) {
|
|
9601
|
-
const
|
|
9602
|
-
return /^[0-7]{1,12}$/.test(
|
|
9834
|
+
const text2 = header.toString("latin1", start, start + length).replace(/[\0 ]+$/, "").replace(/^ +/, "");
|
|
9835
|
+
return /^[0-7]{1,12}$/.test(text2) ? parseInt(text2, 8) : null;
|
|
9603
9836
|
}
|
|
9604
9837
|
function checkPath(path) {
|
|
9605
9838
|
const shown = JSON.stringify(path);
|
|
@@ -9725,15 +9958,15 @@ var UNREADABLE_WARNING = Object.freeze({
|
|
|
9725
9958
|
function readWarning(raw) {
|
|
9726
9959
|
if (raw === void 0 || raw === null) return void 0;
|
|
9727
9960
|
if (typeof raw !== "object" || Array.isArray(raw)) return { ...UNREADABLE_WARNING };
|
|
9728
|
-
const { result, flagged, line, text } = raw;
|
|
9961
|
+
const { result, flagged, line, text: text2 } = raw;
|
|
9729
9962
|
if (result !== "warn" && result !== "fail") return { ...UNREADABLE_WARNING };
|
|
9730
|
-
const
|
|
9731
|
-
if (!
|
|
9963
|
+
const sentence2 = typeof text2 === "string" ? warningText(text2) : "";
|
|
9964
|
+
if (!sentence2) return { ...UNREADABLE_WARNING };
|
|
9732
9965
|
return {
|
|
9733
9966
|
result,
|
|
9734
9967
|
flagged: typeof flagged === "string" && CHECK_ID_RE.test(flagged) ? flagged : null,
|
|
9735
9968
|
line: typeof line === "number" && Number.isSafeInteger(line) && line > 0 ? line : null,
|
|
9736
|
-
text:
|
|
9969
|
+
text: sentence2
|
|
9737
9970
|
};
|
|
9738
9971
|
}
|
|
9739
9972
|
function skillPage(site, ref) {
|
|
@@ -9929,13 +10162,13 @@ function checkDigest(body, fileUrl, sha256) {
|
|
|
9929
10162
|
}
|
|
9930
10163
|
function checkSkillMd(body, what, ref) {
|
|
9931
10164
|
const bad = (why) => new CliError("invalid_skill", `${what} is not a valid skill: ${why}.`, EXIT.general);
|
|
9932
|
-
let
|
|
10165
|
+
let text2;
|
|
9933
10166
|
try {
|
|
9934
|
-
|
|
10167
|
+
text2 = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
9935
10168
|
} catch {
|
|
9936
10169
|
throw bad("it is not UTF-8 text");
|
|
9937
10170
|
}
|
|
9938
|
-
const fm = FRONT_MATTER_RE.exec(
|
|
10171
|
+
const fm = FRONT_MATTER_RE.exec(text2);
|
|
9939
10172
|
if (!fm) throw bad("it has no front matter");
|
|
9940
10173
|
const doc = (0, import_yaml.parseDocument)(fm[1] ?? "", { uniqueKeys: true });
|
|
9941
10174
|
const yamlError = doc.errors[0];
|
|
@@ -10130,7 +10363,7 @@ async function workspaceUse(ctx, args) {
|
|
|
10130
10363
|
}
|
|
10131
10364
|
|
|
10132
10365
|
// src/version.ts
|
|
10133
|
-
var VERSION = "0.3.
|
|
10366
|
+
var VERSION = "0.3.2";
|
|
10134
10367
|
|
|
10135
10368
|
// src/main.ts
|
|
10136
10369
|
var COMMANDS = {
|
|
@@ -10165,7 +10398,7 @@ async function runCli(argv, io) {
|
|
|
10165
10398
|
return EXIT.ok;
|
|
10166
10399
|
}
|
|
10167
10400
|
if (!parsed.command) {
|
|
10168
|
-
if (argv.
|
|
10401
|
+
if (argv.every((a) => a === "--verbose") && shouldSetUp(io)) return await setup(createCtx(io, out, parsed.flags));
|
|
10169
10402
|
io.stderr(USAGE);
|
|
10170
10403
|
return EXIT.usage;
|
|
10171
10404
|
}
|
package/package.json
CHANGED