@amdahl/cli 0.3.4 → 0.3.6
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 +165 -66
- package/dist/amdahl.js +594 -311
- package/package.json +1 -1
package/dist/amdahl.js
CHANGED
|
@@ -7367,10 +7367,10 @@ var require_dist = __commonJS({
|
|
|
7367
7367
|
import { spawn } from "child_process";
|
|
7368
7368
|
import { accessSync, constants, statSync } from "fs";
|
|
7369
7369
|
import { homedir, hostname } from "os";
|
|
7370
|
-
import { createInterface } from "readline";
|
|
7370
|
+
import { createInterface as createInterface2 } from "readline";
|
|
7371
7371
|
|
|
7372
7372
|
// src/select.ts
|
|
7373
|
-
import { emitKeypressEvents } from "readline";
|
|
7373
|
+
import { createInterface, emitKeypressEvents } from "readline";
|
|
7374
7374
|
var HINT = "(Use the arrow keys, then Enter)";
|
|
7375
7375
|
var MULTI_HINT = "\u2191\u2193 move \xB7 Space tick/untick \xB7 Enter to accept";
|
|
7376
7376
|
var OFF = "\x1B[0m";
|
|
@@ -7441,7 +7441,7 @@ ${SHOW_CURSOR}`);
|
|
|
7441
7441
|
emitKeypressEvents(input);
|
|
7442
7442
|
input.setRawMode?.(true);
|
|
7443
7443
|
const cols = width();
|
|
7444
|
-
const top = [question, ...note ? wrapWords(
|
|
7444
|
+
const top = [question, ...note ? note.split("\n").flatMap((line) => wrapWords(line, cols)) : []];
|
|
7445
7445
|
headLines = top.reduce((n, line) => n + screenLines(line, cols), 0);
|
|
7446
7446
|
output.write(`${HIDE_CURSOR}${top.map((line) => `${line}
|
|
7447
7447
|
`).join("")}${frame()}`);
|
|
@@ -7497,7 +7497,8 @@ function selectMenu(input, output, question, choices, options = {}) {
|
|
|
7497
7497
|
}
|
|
7498
7498
|
function multiSelectMenu(input, output, question, choices, options = {}) {
|
|
7499
7499
|
const ticked = choices.map((_, i) => options.ticked?.[i] === true);
|
|
7500
|
-
|
|
7500
|
+
const start = Math.min(Math.max(options.initial ?? 0, 0), choices.length - 1);
|
|
7501
|
+
let current = ticked.includes(true) ? ticked.indexOf(true) : start;
|
|
7501
7502
|
const pointer = `${marker(options)}>${OFF}`;
|
|
7502
7503
|
const pad = Math.max(...choices.map((c) => c.length));
|
|
7503
7504
|
const label = (i) => {
|
|
@@ -7568,6 +7569,56 @@ function listenForEnter(input, onEnter, onCancel) {
|
|
|
7568
7569
|
}
|
|
7569
7570
|
return stop;
|
|
7570
7571
|
}
|
|
7572
|
+
var PASTE_ON = "\x1B[?2004h";
|
|
7573
|
+
var PASTE_OFF = "\x1B[?2004l";
|
|
7574
|
+
var PASTE_SETTLE_MS = 150;
|
|
7575
|
+
function readPaste(input, output, question) {
|
|
7576
|
+
output.write(`${question}
|
|
7577
|
+
`);
|
|
7578
|
+
const rl = createInterface({ input, output, terminal: true, prompt: "" });
|
|
7579
|
+
output.write(PASTE_ON);
|
|
7580
|
+
return new Promise((resolve2, reject) => {
|
|
7581
|
+
const lines = [];
|
|
7582
|
+
let pasting = false;
|
|
7583
|
+
let settle;
|
|
7584
|
+
let settled = false;
|
|
7585
|
+
const finish = (outcome) => {
|
|
7586
|
+
if (settled) return;
|
|
7587
|
+
settled = true;
|
|
7588
|
+
clearTimeout(settle);
|
|
7589
|
+
input.removeListener("keypress", onKeypress);
|
|
7590
|
+
output.write(PASTE_OFF);
|
|
7591
|
+
rl.close();
|
|
7592
|
+
if ("text" in outcome) resolve2(outcome.text);
|
|
7593
|
+
else reject(outcome.error);
|
|
7594
|
+
};
|
|
7595
|
+
const done = () => finish({ text: lines.join("\n").trim() });
|
|
7596
|
+
const onKeypress = (_text, key) => {
|
|
7597
|
+
if (key?.name === "paste-start") pasting = true;
|
|
7598
|
+
else if (key?.name === "paste-end") pasting = false;
|
|
7599
|
+
else if (settle && key?.name !== "return" && key?.name !== "enter") {
|
|
7600
|
+
clearTimeout(settle);
|
|
7601
|
+
settle = void 0;
|
|
7602
|
+
}
|
|
7603
|
+
};
|
|
7604
|
+
input.on("keypress", onKeypress);
|
|
7605
|
+
rl.on("line", (line) => {
|
|
7606
|
+
if (line.trim() || pasting) {
|
|
7607
|
+
lines.push(line);
|
|
7608
|
+
return;
|
|
7609
|
+
}
|
|
7610
|
+
if (lines.length === 0) return;
|
|
7611
|
+
lines.push(line);
|
|
7612
|
+
clearTimeout(settle);
|
|
7613
|
+
settle = setTimeout(done, PASTE_SETTLE_MS);
|
|
7614
|
+
});
|
|
7615
|
+
rl.on("SIGINT", () => {
|
|
7616
|
+
output.write("\n");
|
|
7617
|
+
finish({ error: new PromptCancelled() });
|
|
7618
|
+
});
|
|
7619
|
+
rl.on("close", done);
|
|
7620
|
+
});
|
|
7621
|
+
}
|
|
7571
7622
|
|
|
7572
7623
|
// src/io.ts
|
|
7573
7624
|
var PromptCancelled = class extends Error {
|
|
@@ -7601,7 +7652,7 @@ async function readAllStdin() {
|
|
|
7601
7652
|
return Buffer.concat(chunks).toString("utf8");
|
|
7602
7653
|
}
|
|
7603
7654
|
function promptLine(question) {
|
|
7604
|
-
const rl =
|
|
7655
|
+
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
7605
7656
|
return new Promise((resolve2, reject) => {
|
|
7606
7657
|
let settled = false;
|
|
7607
7658
|
const cancel = () => {
|
|
@@ -7620,6 +7671,22 @@ function promptLine(question) {
|
|
|
7620
7671
|
});
|
|
7621
7672
|
});
|
|
7622
7673
|
}
|
|
7674
|
+
function runOnTerminal(command, args) {
|
|
7675
|
+
const hold = () => {
|
|
7676
|
+
};
|
|
7677
|
+
process.on("SIGINT", hold);
|
|
7678
|
+
return new Promise((resolve2) => {
|
|
7679
|
+
let child;
|
|
7680
|
+
try {
|
|
7681
|
+
child = spawn(command, args, { stdio: "inherit" });
|
|
7682
|
+
} catch {
|
|
7683
|
+
resolve2(-1);
|
|
7684
|
+
return;
|
|
7685
|
+
}
|
|
7686
|
+
child.on("error", () => resolve2(-1));
|
|
7687
|
+
child.on("close", (code) => resolve2(code ?? -1));
|
|
7688
|
+
}).finally(() => process.off("SIGINT", hold));
|
|
7689
|
+
}
|
|
7623
7690
|
function uninterruptible(fn) {
|
|
7624
7691
|
const hold = () => {
|
|
7625
7692
|
};
|
|
@@ -7653,6 +7720,7 @@ function realIo() {
|
|
|
7653
7720
|
stdoutIsTTY: Boolean(process.stdout.isTTY),
|
|
7654
7721
|
stderrIsTTY: Boolean(process.stderr.isTTY),
|
|
7655
7722
|
readStdin: readAllStdin,
|
|
7723
|
+
readPaste: (question) => readPaste(process.stdin, process.stderr, question),
|
|
7656
7724
|
prompt: promptLine,
|
|
7657
7725
|
select: (question, choices, options) => selectMenu(process.stdin, process.stderr, question, choices, {
|
|
7658
7726
|
...options,
|
|
@@ -7667,6 +7735,7 @@ function realIo() {
|
|
|
7667
7735
|
listenForEnter: (onEnter, onCancel) => listenForEnter(process.stdin, onEnter, onCancel),
|
|
7668
7736
|
uninterruptible,
|
|
7669
7737
|
run: runProcess,
|
|
7738
|
+
runInteractive: runOnTerminal,
|
|
7670
7739
|
openUrl: async (url) => {
|
|
7671
7740
|
const [cmd, args] = openerFor(process.platform, url);
|
|
7672
7741
|
return (await runProcess(cmd, args)).code === 0;
|
|
@@ -7694,7 +7763,7 @@ function startedByNpx(io) {
|
|
|
7694
7763
|
return io.env.npm_command === "exec" || io.env.npm_lifecycle_event === "npx" || /[\\/]_npx[\\/]/.test(io.scriptPath ?? "");
|
|
7695
7764
|
}
|
|
7696
7765
|
function setCommandName(io) {
|
|
7697
|
-
prefix = startedByNpx(io) ? "npx amdahl" : "amdahl";
|
|
7766
|
+
prefix = startedByNpx(io) ? "npx -y amdahl" : "amdahl";
|
|
7698
7767
|
}
|
|
7699
7768
|
function amdahlCommand(rest = "") {
|
|
7700
7769
|
return rest ? `${prefix} ${rest}` : prefix;
|
|
@@ -7848,7 +7917,7 @@ function parseCommandLine(argv) {
|
|
|
7848
7917
|
const ref = second && second.includes("/") ? second : "<publisher>/<skill>";
|
|
7849
7918
|
throw usageError(
|
|
7850
7919
|
second === void 0 ? "`amdahl skills` needs a subcommand." : `Unknown command \`amdahl skills ${second}\`.`,
|
|
7851
|
-
`To add a skill: npx amdahl skills add ${ref}`
|
|
7920
|
+
`To add a skill: npx -y amdahl skills add ${ref}`
|
|
7852
7921
|
);
|
|
7853
7922
|
}
|
|
7854
7923
|
if (second === void 0) throw usageError(`\`amdahl ${first}\` needs a subcommand.`);
|
|
@@ -7890,12 +7959,14 @@ function parseErrorMessage(err) {
|
|
|
7890
7959
|
var USAGE = `Usage: amdahl <command> [options]
|
|
7891
7960
|
|
|
7892
7961
|
What you can do:
|
|
7893
|
-
amdahl Set up Claude Code, Cursor or Codex
|
|
7894
|
-
|
|
7895
|
-
Run by an agent or with no
|
|
7896
|
-
sets up that agent, or every
|
|
7897
|
-
finds, and asks nothing (also:
|
|
7962
|
+
amdahl Set up Claude Code, Cursor or Codex, or try
|
|
7963
|
+
Amdahl in this terminal. In a terminal it
|
|
7964
|
+
asks which. Run by an agent or with no
|
|
7965
|
+
terminal, it sets up that agent, or every
|
|
7966
|
+
client it finds, and asks nothing (also:
|
|
7967
|
+
amdahl setup)
|
|
7898
7968
|
amdahl optimize draft.md Rewrite an outbound email or LinkedIn message
|
|
7969
|
+
(with no file, at a terminal, paste it)
|
|
7899
7970
|
amdahl status Check that this workspace can optimize
|
|
7900
7971
|
amdahl keys create --name ci Make an API key for a server or CI
|
|
7901
7972
|
amdahl install claude-code Connect one AI client (also codex, cursor)
|
|
@@ -8836,6 +8907,8 @@ async function browserSignIn(ctx) {
|
|
|
8836
8907
|
};
|
|
8837
8908
|
ctx.config.default_profile = name;
|
|
8838
8909
|
ctx.saveConfig();
|
|
8910
|
+
ctx.profileName = name;
|
|
8911
|
+
ctx.profile = ctx.config.profiles[name] ?? null;
|
|
8839
8912
|
return { profile: name, me, workspace: ws };
|
|
8840
8913
|
} finally {
|
|
8841
8914
|
stopKeys();
|
|
@@ -8845,6 +8918,10 @@ async function browserSignIn(ctx) {
|
|
|
8845
8918
|
function signedInLine(ctx, { profile, me, workspace }) {
|
|
8846
8919
|
return `Signed in as ${printable(me.email ?? me.user_id)} to ${ctx.out.bold(printable(workspace.name))} (${printable(workspace.slug)}). Profile: ${profile}.`;
|
|
8847
8920
|
}
|
|
8921
|
+
function loginNextLine() {
|
|
8922
|
+
return `Next: run \`${amdahlCommand("optimize")}\` and paste an email,
|
|
8923
|
+
or \`${amdahlCommand()}\` to add Amdahl to Claude Code, Cursor or Codex.`;
|
|
8924
|
+
}
|
|
8848
8925
|
async function login(ctx) {
|
|
8849
8926
|
const signedIn = await browserSignIn(ctx);
|
|
8850
8927
|
const { profile, me, workspace } = signedIn;
|
|
@@ -8857,7 +8934,8 @@ async function login(ctx) {
|
|
|
8857
8934
|
scopes: me.scopes,
|
|
8858
8935
|
api_url: ctx.apiUrl
|
|
8859
8936
|
},
|
|
8860
|
-
signedInLine(ctx, signedIn)
|
|
8937
|
+
`${signedInLine(ctx, signedIn)}
|
|
8938
|
+
${loginNextLine()}`
|
|
8861
8939
|
);
|
|
8862
8940
|
return EXIT.ok;
|
|
8863
8941
|
}
|
|
@@ -9043,9 +9121,14 @@ async function npmGlobalPrefix(ctx) {
|
|
|
9043
9121
|
return prefix2 || null;
|
|
9044
9122
|
}
|
|
9045
9123
|
async function claudeHasAmdahl(ctx, command, mcpUrl) {
|
|
9046
|
-
|
|
9047
|
-
|
|
9124
|
+
let answer = claudeAnswers.get(ctx);
|
|
9125
|
+
if (!answer) {
|
|
9126
|
+
answer = ctx.io.run(command, ["mcp", "get", "amdahl"]).then((res) => res.code === 0 && res.stdout.split("\n").some((line) => line.trim() === `URL: ${mcpUrl}`));
|
|
9127
|
+
claudeAnswers.set(ctx, answer);
|
|
9128
|
+
}
|
|
9129
|
+
return answer;
|
|
9048
9130
|
}
|
|
9131
|
+
var claudeAnswers = /* @__PURE__ */ new WeakMap();
|
|
9049
9132
|
var CONFIG_HINT = "Fix the file, or move it away, then run the same command again.";
|
|
9050
9133
|
function readJsonObject(path) {
|
|
9051
9134
|
if (!existsSync3(path)) return { config: {}, existed: false };
|
|
@@ -9138,10 +9221,7 @@ function updateConfig(path, fn) {
|
|
|
9138
9221
|
throw new CliError("write_failed", `Could not update ${path} (${reason}).`, EXIT.general);
|
|
9139
9222
|
}
|
|
9140
9223
|
}
|
|
9141
|
-
async function
|
|
9142
|
-
const skill = skillPath(client, ctx.io.homedir);
|
|
9143
|
-
const current = existsSync3(skill) ? readFileSync3(skill, "utf8") : null;
|
|
9144
|
-
if (current === null || current !== SKILL_TEXT && isShippedSkill(current)) return false;
|
|
9224
|
+
async function hasAmdahl(ctx, client) {
|
|
9145
9225
|
const mcpUrl = `${ctx.apiUrl}/mcp`;
|
|
9146
9226
|
if (client === "claude-code") {
|
|
9147
9227
|
if (claudeFileHasAmdahl(claudeConfigPath(ctx), mcpUrl)) return true;
|
|
@@ -9159,6 +9239,12 @@ async function isInstalled(ctx, client) {
|
|
|
9159
9239
|
return false;
|
|
9160
9240
|
}
|
|
9161
9241
|
}
|
|
9242
|
+
async function isInstalled(ctx, client) {
|
|
9243
|
+
const skill = skillPath(client, ctx.io.homedir);
|
|
9244
|
+
const current = existsSync3(skill) ? readFileSync3(skill, "utf8") : null;
|
|
9245
|
+
if (current === null || current !== SKILL_TEXT && isShippedSkill(current)) return false;
|
|
9246
|
+
return hasAmdahl(ctx, client);
|
|
9247
|
+
}
|
|
9162
9248
|
async function installClient(ctx, client, print) {
|
|
9163
9249
|
const mcp = await installMcp(ctx, client, print);
|
|
9164
9250
|
return { mcp, skill: installSkill(ctx, client, print) };
|
|
@@ -9211,6 +9297,7 @@ async function installMcp(ctx, client, print) {
|
|
|
9211
9297
|
if (claudeFileHasAmdahl(claudeConfigPath(ctx), mcpUrl) || await claudeHasAmdahl(ctx, route.command, mcpUrl)) {
|
|
9212
9298
|
return { data: { ok: true, client, action: "unchanged", command }, human: `Claude Code already has Amdahl at ${mcpUrl}; nothing changed.`, notes: [] };
|
|
9213
9299
|
}
|
|
9300
|
+
claudeAnswers.delete(ctx);
|
|
9214
9301
|
const res = await io.run(route.command, args);
|
|
9215
9302
|
if (res.code !== 0) {
|
|
9216
9303
|
throw new CliError("install_failed", `\`${command}\` failed: ${res.stderr.trim() || `exit ${res.code}`}`, EXIT.general);
|
|
@@ -9440,221 +9527,14 @@ async function keysRevoke(ctx, args) {
|
|
|
9440
9527
|
}
|
|
9441
9528
|
|
|
9442
9529
|
// src/commands/optimize.ts
|
|
9443
|
-
import { readFileSync as
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
var
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
if (
|
|
9450
|
-
|
|
9451
|
-
return value;
|
|
9452
|
-
}
|
|
9453
|
-
function readContext(path) {
|
|
9454
|
-
let parsed;
|
|
9455
|
-
try {
|
|
9456
|
-
parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
9457
|
-
} catch (err) {
|
|
9458
|
-
throw usageError(`Could not read --context ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9459
|
-
}
|
|
9460
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9461
|
-
throw usageError("--context must be a JSON object with rules, voice_examples and facts.");
|
|
9462
|
-
}
|
|
9463
|
-
return parsed;
|
|
9464
|
-
}
|
|
9465
|
-
function withTimeout(promise, ms, file) {
|
|
9466
|
-
let timer;
|
|
9467
|
-
const timeout = new Promise((_, reject) => {
|
|
9468
|
-
timer = setTimeout(
|
|
9469
|
-
() => reject(new CliError("timeout", `Optimizing ${file} timed out after ${ms / 1e3} s.`, EXIT.network)),
|
|
9470
|
-
ms
|
|
9471
|
-
);
|
|
9472
|
-
});
|
|
9473
|
-
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
9474
|
-
}
|
|
9475
|
-
var WEAKEST = {
|
|
9476
|
-
rules: "how it follows your rules",
|
|
9477
|
-
ask: "the ask",
|
|
9478
|
-
opener: "the opener",
|
|
9479
|
-
fit: "how it fits the recipient",
|
|
9480
|
-
proof: "its proof points",
|
|
9481
|
-
length: "the length",
|
|
9482
|
-
style: "the tone",
|
|
9483
|
-
preference: "how it matches your preferences",
|
|
9484
|
-
voice: "how much it sounds like you"
|
|
9485
|
-
};
|
|
9486
|
-
var CONCERNS = {
|
|
9487
|
-
conflicts_with_callers_ask: "it asks for a different length or time than your ask",
|
|
9488
|
-
drops_ask: "it leaves out your ask",
|
|
9489
|
-
adds_claim: "it states something neither your draft nor your facts support",
|
|
9490
|
-
drops_entity: "it drops a name, link or placeholder you had",
|
|
9491
|
-
inflates: "it is longer than you asked for",
|
|
9492
|
-
no_gain: "it changed little beyond the wording",
|
|
9493
|
-
breaks_rules: "it breaks one of your rules"
|
|
9494
|
-
};
|
|
9495
|
-
var EVIDENCE_MISSED = {
|
|
9496
|
-
corpus_empty: "the workspace has none yet",
|
|
9497
|
-
evidence_unavailable: "this environment does not read them",
|
|
9498
|
-
evidence_read_failed: "the read failed, so a retry may work"
|
|
9499
|
-
};
|
|
9500
|
-
var MAX_LINES = 3;
|
|
9501
|
-
function list(value) {
|
|
9502
|
-
return Array.isArray(value) ? value.filter((v) => !!v && typeof v === "object") : null;
|
|
9503
|
-
}
|
|
9504
|
-
function record(value) {
|
|
9505
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9506
|
-
}
|
|
9507
|
-
function text(value) {
|
|
9508
|
-
return typeof value === "string" ? printable(value).trim() : "";
|
|
9509
|
-
}
|
|
9510
|
-
function sentence(value) {
|
|
9511
|
-
return /[.!?]$/.test(value) ? value : `${value}.`;
|
|
9512
|
-
}
|
|
9513
|
-
function plural(n, one, many = `${one}s`) {
|
|
9514
|
-
return `${n} ${n === 1 ? one : many}`;
|
|
9515
|
-
}
|
|
9516
|
-
function readSteps(steps) {
|
|
9517
|
-
const reasonOf = (node) => text(steps.find((s) => s.node === node)?.reason);
|
|
9518
|
-
const changed = (node) => steps.some((s) => s.node === node && s.changed === true);
|
|
9519
|
-
const counts = new Map(
|
|
9520
|
-
reasonOf("w3").split(",").map((part) => part.split(":")).filter(([, n]) => /^\d+$/.test(n ?? "")).map(([code, n]) => [code, Number(n)])
|
|
9521
|
-
);
|
|
9522
|
-
const did = [];
|
|
9523
|
-
const repaired = counts.get("repaired") ?? 0;
|
|
9524
|
-
const deleted = counts.get("deleted") ?? 0;
|
|
9525
|
-
if (repaired && deleted) did.push(`Rewrote ${plural(repaired, "sentence")} and cut ${deleted} that nothing backed.`);
|
|
9526
|
-
else if (repaired) did.push(`Rewrote ${plural(repaired, "sentence")} that did not hold up.`);
|
|
9527
|
-
else if (deleted) did.push(`Cut ${plural(deleted, "sentence")} that nothing backed.`);
|
|
9528
|
-
const repair = reasonOf("w5");
|
|
9529
|
-
if (changed("w5") && repair.startsWith("repaired:")) {
|
|
9530
|
-
const weakest = WEAKEST[repair.slice("repaired:".length)];
|
|
9531
|
-
did.push(weakest ? `Reworked ${weakest}.` : "Reworked its weakest part.");
|
|
9532
|
-
}
|
|
9533
|
-
const flagged = [];
|
|
9534
|
-
const open = counts.get("open");
|
|
9535
|
-
if (open) flagged.push(`${plural(open, "sentence problem")} it could not fix`);
|
|
9536
|
-
const second = reasonOf("w6");
|
|
9537
|
-
if (changed("w6") && !second.startsWith("kept_before_repair")) {
|
|
9538
|
-
const concern = CONCERNS[second];
|
|
9539
|
-
flagged.push(concern ? `a second-check concern (${concern})` : "a second-check concern");
|
|
9540
|
-
}
|
|
9541
|
-
return { did: did.join(" "), flagged };
|
|
9542
|
-
}
|
|
9543
|
-
function and(parts) {
|
|
9544
|
-
return parts.length < 2 ? parts[0] ?? "" : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
9545
|
-
}
|
|
9546
|
-
function whatAmdahlDid(result) {
|
|
9547
|
-
const steps = result.unchanged === true ? { did: "", flagged: [] } : readSteps(list(result.steps) ?? []);
|
|
9548
|
-
const flagged = [...steps.flagged];
|
|
9549
|
-
let checked = "";
|
|
9550
|
-
let removed = "";
|
|
9551
|
-
const evidence = record(result.evidence);
|
|
9552
|
-
const claims = list(result.unsupported_claims);
|
|
9553
|
-
if (evidence?.used === true) {
|
|
9554
|
-
const quotes = record(result.lift)?.quotes;
|
|
9555
|
-
const read = typeof quotes === "number" && quotes > 0 ? ` (${plural(quotes, "quote")})` : "";
|
|
9556
|
-
if (claims) {
|
|
9557
|
-
const still = claims.filter((c) => c.status !== "removed").length;
|
|
9558
|
-
const cut = claims.length - still;
|
|
9559
|
-
if (cut) removed = `Removed ${plural(cut, "claim")} your calls don't back.`;
|
|
9560
|
-
if (still) flagged.unshift(`${plural(still, "claim")} your calls don't back`);
|
|
9561
|
-
checked = `Checked every claim against your calls${read}${claims.length ? "" : " and found none they don't back"}.`;
|
|
9562
|
-
} else checked = `Checked the message against your calls${read}.`;
|
|
9563
|
-
} else if (evidence?.requested === "workspace" && EVIDENCE_MISSED[text(evidence.reason)]) {
|
|
9564
|
-
checked = `Skipped checking claims against your calls: ${EVIDENCE_MISSED[text(evidence.reason)]}.`;
|
|
9565
|
-
}
|
|
9566
|
-
const used = (list(record(result.context_use)?.rows) ?? []).filter((r) => r.verdict === "pass");
|
|
9567
|
-
const sources = [
|
|
9568
|
-
used.some((r) => r.bucket === "internal") ? "Amdahl's own records" : "",
|
|
9569
|
-
used.some((r) => r.bucket === "external") ? "what their company or market published" : ""
|
|
9570
|
-
].filter(Boolean);
|
|
9571
|
-
const usedLine = sources.length ? `Used ${plural(sources.length, "source")}: ${and(sources)}.` : "";
|
|
9572
|
-
const omitted = list(result.omitted) ?? [];
|
|
9573
|
-
const whys = [...new Set(omitted.map((o) => text(o.why)).filter(Boolean))];
|
|
9574
|
-
const more = whys.length > 1 ? ` (${plural(whys.length - 1, "more reason")} in --json)` : "";
|
|
9575
|
-
const leftOut = omitted.length ? `Left out ${plural(omitted.length, "thing")}${whys[0] ? `: ${sentence(whys[0])}` : "."}${more}` : "";
|
|
9576
|
-
const lines = [
|
|
9577
|
-
{ rank: 2, line: sentence(text(result.what_changed)) },
|
|
9578
|
-
{ rank: 5, line: checked },
|
|
9579
|
-
{ rank: 3, line: removed },
|
|
9580
|
-
{ rank: 6, line: usedLine },
|
|
9581
|
-
{ rank: 7, line: steps.did },
|
|
9582
|
-
{ rank: 4, line: leftOut },
|
|
9583
|
-
{ rank: 1, line: flagged.length ? `Flagged ${and(flagged)}; check before sending.` : "" }
|
|
9584
|
-
].filter((l) => l.line && l.line !== ".");
|
|
9585
|
-
const kept = new Set([...lines].sort((a, b) => a.rank - b.rank).slice(0, MAX_LINES));
|
|
9586
|
-
return lines.filter((l) => kept.has(l)).map((l) => l.line);
|
|
9587
|
-
}
|
|
9588
|
-
async function optimize(ctx, args) {
|
|
9589
|
-
const { io, out, flags } = ctx;
|
|
9590
|
-
let files = args;
|
|
9591
|
-
if (files.length === 0) {
|
|
9592
|
-
if (io.stdinIsTTY) {
|
|
9593
|
-
throw usageError(
|
|
9594
|
-
"Give a file to optimize, or pipe the message on stdin.",
|
|
9595
|
-
"Usage: amdahl optimize <file>... | - [--channel email|linkedin]"
|
|
9596
|
-
);
|
|
9597
|
-
}
|
|
9598
|
-
files = ["-"];
|
|
9599
|
-
}
|
|
9600
|
-
if (files.filter((f) => f === "-").length > 1) throw usageError("Stdin (-) can be read only once.");
|
|
9601
|
-
const base = {};
|
|
9602
|
-
const channel = oneOf("channel", str(flags.channel), CHANNELS);
|
|
9603
|
-
const evidence = oneOf("evidence", str(flags.evidence), EVIDENCE);
|
|
9604
|
-
const onUnsupported = oneOf("on-unsupported", str(flags["on-unsupported"]), ON_UNSUPPORTED);
|
|
9605
|
-
if (channel) base.channel = channel;
|
|
9606
|
-
if (evidence) base.evidence = evidence;
|
|
9607
|
-
if (onUnsupported) base.on_unsupported = onUnsupported;
|
|
9608
|
-
if (flags.tries === true) base.include_tries = true;
|
|
9609
|
-
const contextPath = str(flags.context);
|
|
9610
|
-
if (contextPath) base.context = readContext(contextPath);
|
|
9611
|
-
const inputs = [];
|
|
9612
|
-
for (const file of files) {
|
|
9613
|
-
let message2;
|
|
9614
|
-
try {
|
|
9615
|
-
message2 = file === "-" ? await io.readStdin() : readFileSync4(file, "utf8");
|
|
9616
|
-
} catch (err) {
|
|
9617
|
-
throw usageError(`Could not read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9618
|
-
}
|
|
9619
|
-
if (!message2.trim()) throw usageError(`${file === "-" ? "Stdin" : file} is empty.`);
|
|
9620
|
-
inputs.push({ file, message: message2 });
|
|
9621
|
-
}
|
|
9622
|
-
const credential = await resolveCredential(ctx);
|
|
9623
|
-
const client = apiClient(ctx, credential);
|
|
9624
|
-
const results = [];
|
|
9625
|
-
let exit = EXIT.ok;
|
|
9626
|
-
for (const { file, message: message2 } of inputs) {
|
|
9627
|
-
let result;
|
|
9628
|
-
try {
|
|
9629
|
-
result = await withTimeout(
|
|
9630
|
-
client.request("/api/platform/v1/messages/optimize", {
|
|
9631
|
-
method: "POST",
|
|
9632
|
-
body: { message: message2, ...base }
|
|
9633
|
-
}),
|
|
9634
|
-
OPTIMIZE_TIMEOUT_MS,
|
|
9635
|
-
file
|
|
9636
|
-
);
|
|
9637
|
-
} catch (err) {
|
|
9638
|
-
throw toCliError(err);
|
|
9639
|
-
}
|
|
9640
|
-
const ok = result.ok !== false;
|
|
9641
|
-
results.push({ ok, file, result });
|
|
9642
|
-
if (!ok) {
|
|
9643
|
-
exit = EXIT.general;
|
|
9644
|
-
out.info(`${file}: not optimized (${String(result.reason ?? "unknown")}): ${String(result.detail ?? "")}`);
|
|
9645
|
-
continue;
|
|
9646
|
-
}
|
|
9647
|
-
if (!out.json) {
|
|
9648
|
-
if (inputs.length > 1) out.info(`== ${file} ==`);
|
|
9649
|
-
if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
|
|
9650
|
-
else if (typeof result.summary === "string") out.info(result.summary);
|
|
9651
|
-
out.result({}, String(result.message ?? ""));
|
|
9652
|
-
const did = whatAmdahlDid(result);
|
|
9653
|
-
if (did.length) out.info(did.join("\n"));
|
|
9654
|
-
}
|
|
9655
|
-
}
|
|
9656
|
-
if (out.json) out.result(results.length === 1 ? results[0] : results);
|
|
9657
|
-
return exit;
|
|
9530
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
9531
|
+
|
|
9532
|
+
// src/ci.ts
|
|
9533
|
+
var isSet = (value) => Boolean(value) && value !== "false" && value !== "0";
|
|
9534
|
+
function ciVariable(env) {
|
|
9535
|
+
const named = ["CI", "JENKINS_URL", "TF_BUILD", "CODEBUILD_BUILD_ID", "TEAMCITY_VERSION", "GITHUB_ACTIONS", "GITLAB_CI", "BUILDKITE", "CIRCLECI"].find((name) => isSet(env[name]));
|
|
9536
|
+
if (named) return named;
|
|
9537
|
+
return isSet(env.BUILD_NUMBER) && isSet(env.JENKINS_HOME) ? "BUILD_NUMBER with JENKINS_HOME" : null;
|
|
9658
9538
|
}
|
|
9659
9539
|
|
|
9660
9540
|
// src/commands/skills-guide.ts
|
|
@@ -9663,7 +9543,7 @@ import { dirname as dirname3, join as join6, relative, sep } from "path";
|
|
|
9663
9543
|
|
|
9664
9544
|
// src/commands/skills-install.ts
|
|
9665
9545
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
9666
|
-
import { chmodSync as chmodSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, mkdtempSync, readdirSync, readFileSync as
|
|
9546
|
+
import { chmodSync as chmodSync4, lstatSync as lstatSync2, mkdirSync as mkdirSync3, mkdtempSync, readdirSync, readFileSync as readFileSync4, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
9667
9547
|
import { dirname as dirname2, join as join5 } from "path";
|
|
9668
9548
|
function skillDirs(targets, name) {
|
|
9669
9549
|
const dirs = [];
|
|
@@ -9710,7 +9590,7 @@ function sameFolder(dir, archive) {
|
|
|
9710
9590
|
if (!file) return false;
|
|
9711
9591
|
const full = join5(dir, path);
|
|
9712
9592
|
if (process.platform !== "win32" && (lstatSync2(full).mode & 73 ? 493 : 420) !== file.mode) return false;
|
|
9713
|
-
if (!
|
|
9593
|
+
if (!readFileSync4(full).equals(file.bytes)) return false;
|
|
9714
9594
|
} else {
|
|
9715
9595
|
return false;
|
|
9716
9596
|
}
|
|
@@ -9755,7 +9635,7 @@ function planInstall(dirs, payload) {
|
|
|
9755
9635
|
refuseLinks(dir, path);
|
|
9756
9636
|
let current;
|
|
9757
9637
|
try {
|
|
9758
|
-
current = lstatSync2(path, { throwIfNoEntry: false }) ?
|
|
9638
|
+
current = lstatSync2(path, { throwIfNoEntry: false }) ? readFileSync4(path) : null;
|
|
9759
9639
|
} catch (err) {
|
|
9760
9640
|
throw writeFailure(path, err);
|
|
9761
9641
|
}
|
|
@@ -9935,40 +9815,379 @@ function nextStep(ctx, targets, seen) {
|
|
|
9935
9815
|
return `Open a new ${joinNames(names, "or")} chat ${where} to use it.`;
|
|
9936
9816
|
}
|
|
9937
9817
|
|
|
9938
|
-
// src/commands/
|
|
9939
|
-
var
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
9943
|
-
];
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
function
|
|
9950
|
-
|
|
9818
|
+
// src/commands/optimize.ts
|
|
9819
|
+
var NEW_USER = "New to Amdahl? The sign-in sets up your free workspace.";
|
|
9820
|
+
var OPTIMIZE_TIMEOUT_MS = 18e4;
|
|
9821
|
+
var CHANNELS = ["email", "linkedin"];
|
|
9822
|
+
var EVIDENCE = ["off", "workspace"];
|
|
9823
|
+
var ON_UNSUPPORTED = ["flag", "remove"];
|
|
9824
|
+
function oneOf(flag, value, allowed) {
|
|
9825
|
+
if (value === void 0) return void 0;
|
|
9826
|
+
if (!allowed.includes(value)) throw usageError(`--${flag} must be one of: ${allowed.join(", ")}.`);
|
|
9827
|
+
return value;
|
|
9828
|
+
}
|
|
9829
|
+
function readContext(path) {
|
|
9830
|
+
let parsed;
|
|
9831
|
+
try {
|
|
9832
|
+
parsed = JSON.parse(readFileSync5(path, "utf8"));
|
|
9833
|
+
} catch (err) {
|
|
9834
|
+
throw usageError(`Could not read --context ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9835
|
+
}
|
|
9836
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9837
|
+
throw usageError("--context must be a JSON object with rules, voice_examples and facts.");
|
|
9838
|
+
}
|
|
9839
|
+
return parsed;
|
|
9840
|
+
}
|
|
9841
|
+
function withTimeout(promise, ms, file) {
|
|
9842
|
+
let timer;
|
|
9843
|
+
const timeout = new Promise((_, reject) => {
|
|
9844
|
+
timer = setTimeout(
|
|
9845
|
+
() => reject(new CliError("timeout", `Optimizing ${file} timed out after ${ms / 1e3} s.`, EXIT.network)),
|
|
9846
|
+
ms
|
|
9847
|
+
);
|
|
9848
|
+
});
|
|
9849
|
+
return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));
|
|
9850
|
+
}
|
|
9851
|
+
var WEAKEST = {
|
|
9852
|
+
rules: "how it follows your rules",
|
|
9853
|
+
ask: "the ask",
|
|
9854
|
+
opener: "the opener",
|
|
9855
|
+
fit: "how it fits the recipient",
|
|
9856
|
+
proof: "its proof points",
|
|
9857
|
+
length: "the length",
|
|
9858
|
+
style: "the tone",
|
|
9859
|
+
preference: "how it matches your preferences",
|
|
9860
|
+
voice: "how much it sounds like you"
|
|
9861
|
+
};
|
|
9862
|
+
var CONCERNS = {
|
|
9863
|
+
conflicts_with_callers_ask: "it asks for a different length or time than your ask",
|
|
9864
|
+
drops_ask: "it leaves out your ask",
|
|
9865
|
+
adds_claim: "it states something neither your draft nor your facts support",
|
|
9866
|
+
drops_entity: "it drops a name, link or placeholder you had",
|
|
9867
|
+
inflates: "it is longer than you asked for",
|
|
9868
|
+
no_gain: "it changed little beyond the wording",
|
|
9869
|
+
breaks_rules: "it breaks one of your rules"
|
|
9870
|
+
};
|
|
9871
|
+
var EVIDENCE_MISSED = {
|
|
9872
|
+
corpus_empty: "the workspace has none yet",
|
|
9873
|
+
evidence_unavailable: "this environment does not read them",
|
|
9874
|
+
evidence_read_failed: "the read failed, so a retry may work"
|
|
9875
|
+
};
|
|
9876
|
+
var MAX_LINES = 3;
|
|
9877
|
+
function list(value) {
|
|
9878
|
+
return Array.isArray(value) ? value.filter((v) => !!v && typeof v === "object") : null;
|
|
9879
|
+
}
|
|
9880
|
+
function record(value) {
|
|
9881
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
9882
|
+
}
|
|
9883
|
+
function text(value) {
|
|
9884
|
+
return typeof value === "string" ? printable(value).trim() : "";
|
|
9885
|
+
}
|
|
9886
|
+
function sentence(value) {
|
|
9887
|
+
return /[.!?]$/.test(value) ? value : `${value}.`;
|
|
9888
|
+
}
|
|
9889
|
+
function plural(n, one, many = `${one}s`) {
|
|
9890
|
+
return `${n} ${n === 1 ? one : many}`;
|
|
9891
|
+
}
|
|
9892
|
+
function readSteps(steps) {
|
|
9893
|
+
const reasonOf = (node) => text(steps.find((s) => s.node === node)?.reason);
|
|
9894
|
+
const changed = (node) => steps.some((s) => s.node === node && s.changed === true);
|
|
9895
|
+
const counts = new Map(
|
|
9896
|
+
reasonOf("w3").split(",").map((part) => part.split(":")).filter(([, n]) => /^\d+$/.test(n ?? "")).map(([code, n]) => [code, Number(n)])
|
|
9897
|
+
);
|
|
9898
|
+
const did = [];
|
|
9899
|
+
const repaired = counts.get("repaired") ?? 0;
|
|
9900
|
+
const deleted = counts.get("deleted") ?? 0;
|
|
9901
|
+
if (repaired && deleted) did.push(`Rewrote ${plural(repaired, "sentence")} and cut ${deleted} that nothing backed.`);
|
|
9902
|
+
else if (repaired) did.push(`Rewrote ${plural(repaired, "sentence")} that did not hold up.`);
|
|
9903
|
+
else if (deleted) did.push(`Cut ${plural(deleted, "sentence")} that nothing backed.`);
|
|
9904
|
+
const repair = reasonOf("w5");
|
|
9905
|
+
if (changed("w5") && repair.startsWith("repaired:")) {
|
|
9906
|
+
const weakest = WEAKEST[repair.slice("repaired:".length)];
|
|
9907
|
+
did.push(weakest ? `Reworked ${weakest}.` : "Reworked its weakest part.");
|
|
9908
|
+
}
|
|
9909
|
+
const flagged = [];
|
|
9910
|
+
const open = counts.get("open");
|
|
9911
|
+
if (open) flagged.push(`${plural(open, "sentence problem")} it could not fix`);
|
|
9912
|
+
const second = reasonOf("w6");
|
|
9913
|
+
if (changed("w6") && !second.startsWith("kept_before_repair")) {
|
|
9914
|
+
const concern = CONCERNS[second];
|
|
9915
|
+
flagged.push(concern ? `a second-check concern (${concern})` : "a second-check concern");
|
|
9916
|
+
}
|
|
9917
|
+
return { did: did.join(" "), flagged };
|
|
9918
|
+
}
|
|
9919
|
+
function and(parts) {
|
|
9920
|
+
return parts.length < 2 ? parts[0] ?? "" : `${parts.slice(0, -1).join(", ")} and ${parts[parts.length - 1]}`;
|
|
9921
|
+
}
|
|
9922
|
+
function whatAmdahlDid(result) {
|
|
9923
|
+
const steps = result.unchanged === true ? { did: "", flagged: [] } : readSteps(list(result.steps) ?? []);
|
|
9924
|
+
const flagged = [...steps.flagged];
|
|
9925
|
+
let checked = "";
|
|
9926
|
+
let removed = "";
|
|
9927
|
+
const evidence = record(result.evidence);
|
|
9928
|
+
const claims = list(result.unsupported_claims);
|
|
9929
|
+
if (evidence?.used === true) {
|
|
9930
|
+
const quotes = record(result.lift)?.quotes;
|
|
9931
|
+
const read = typeof quotes === "number" && quotes > 0 ? ` (${plural(quotes, "quote")})` : "";
|
|
9932
|
+
if (claims) {
|
|
9933
|
+
const still = claims.filter((c) => c.status !== "removed").length;
|
|
9934
|
+
const cut = claims.length - still;
|
|
9935
|
+
if (cut) removed = `Removed ${plural(cut, "claim")} your calls don't back.`;
|
|
9936
|
+
if (still) flagged.unshift(`${plural(still, "claim")} your calls don't back`);
|
|
9937
|
+
checked = `Checked every claim against your calls${read}${claims.length ? "" : " and found none they don't back"}.`;
|
|
9938
|
+
} else checked = `Checked the message against your calls${read}.`;
|
|
9939
|
+
} else if (evidence?.requested === "workspace" && EVIDENCE_MISSED[text(evidence.reason)]) {
|
|
9940
|
+
checked = `Skipped checking claims against your calls: ${EVIDENCE_MISSED[text(evidence.reason)]}.`;
|
|
9941
|
+
}
|
|
9942
|
+
const used = (list(record(result.context_use)?.rows) ?? []).filter((r) => r.verdict === "pass");
|
|
9943
|
+
const sources = [
|
|
9944
|
+
used.some((r) => r.bucket === "internal") ? "Amdahl's own records" : "",
|
|
9945
|
+
used.some((r) => r.bucket === "external") ? "what their company or market published" : ""
|
|
9946
|
+
].filter(Boolean);
|
|
9947
|
+
const usedLine = sources.length ? `Used ${plural(sources.length, "source")}: ${and(sources)}.` : "";
|
|
9948
|
+
const omitted = list(result.omitted) ?? [];
|
|
9949
|
+
const whys = [...new Set(omitted.map((o) => text(o.why)).filter(Boolean))];
|
|
9950
|
+
const more = whys.length > 1 ? ` (${plural(whys.length - 1, "more reason")} in --json)` : "";
|
|
9951
|
+
const leftOut = omitted.length ? `Left out ${plural(omitted.length, "thing")}${whys[0] ? `: ${sentence(whys[0])}` : "."}${more}` : "";
|
|
9952
|
+
const lines = [
|
|
9953
|
+
{ rank: 2, line: sentence(text(result.what_changed)) },
|
|
9954
|
+
{ rank: 5, line: checked },
|
|
9955
|
+
{ rank: 3, line: removed },
|
|
9956
|
+
{ rank: 6, line: usedLine },
|
|
9957
|
+
{ rank: 7, line: steps.did },
|
|
9958
|
+
{ rank: 4, line: leftOut },
|
|
9959
|
+
{ rank: 1, line: flagged.length ? `Flagged ${and(flagged)}; check before sending.` : "" }
|
|
9960
|
+
].filter((l) => l.line && l.line !== ".");
|
|
9961
|
+
const kept = new Set([...lines].sort((a, b) => a.rank - b.rank).slice(0, MAX_LINES));
|
|
9962
|
+
return lines.filter((l) => kept.has(l)).map((l) => l.line);
|
|
9963
|
+
}
|
|
9964
|
+
var PASTE_PROMPT = "Paste an email or LinkedIn message, then press Enter on an empty line:";
|
|
9965
|
+
var VERSION_RULE = "\u2500\u2500 Amdahl's version \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
|
|
9966
|
+
function nextTimeLine(ctx) {
|
|
9967
|
+
const file = amdahlCommand("optimize draft.txt");
|
|
9968
|
+
return ctx.io.platform === "darwin" ? `Next time: ${file},
|
|
9969
|
+
or pbpaste | ${amdahlCommand("optimize -")}` : `Next time: ${file}`;
|
|
9970
|
+
}
|
|
9971
|
+
function requestOptions(ctx) {
|
|
9972
|
+
const { flags } = ctx;
|
|
9973
|
+
const base = {};
|
|
9974
|
+
const channel = oneOf("channel", str(flags.channel), CHANNELS);
|
|
9975
|
+
const evidence = oneOf("evidence", str(flags.evidence), EVIDENCE);
|
|
9976
|
+
const onUnsupported = oneOf("on-unsupported", str(flags["on-unsupported"]), ON_UNSUPPORTED);
|
|
9977
|
+
if (channel) base.channel = channel;
|
|
9978
|
+
if (evidence) base.evidence = evidence;
|
|
9979
|
+
if (onUnsupported) base.on_unsupported = onUnsupported;
|
|
9980
|
+
if (flags.tries === true) base.include_tries = true;
|
|
9981
|
+
const contextPath = str(flags.context);
|
|
9982
|
+
if (contextPath) base.context = readContext(contextPath);
|
|
9983
|
+
return base;
|
|
9984
|
+
}
|
|
9985
|
+
async function optimizeOne(ctx, credential, message2, base, file) {
|
|
9986
|
+
try {
|
|
9987
|
+
return await withTimeout(
|
|
9988
|
+
apiClient(ctx, credential).request("/api/platform/v1/messages/optimize", {
|
|
9989
|
+
method: "POST",
|
|
9990
|
+
body: { message: message2, ...base }
|
|
9991
|
+
}),
|
|
9992
|
+
OPTIMIZE_TIMEOUT_MS,
|
|
9993
|
+
file
|
|
9994
|
+
);
|
|
9995
|
+
} catch (err) {
|
|
9996
|
+
throw toCliError(err);
|
|
9997
|
+
}
|
|
9998
|
+
}
|
|
9999
|
+
async function credentialOrSignIn(ctx) {
|
|
10000
|
+
try {
|
|
10001
|
+
return await resolveCredential(ctx);
|
|
10002
|
+
} catch (err) {
|
|
10003
|
+
if (!(err instanceof CliError && err.exit === EXIT.unauthenticated)) throw err;
|
|
10004
|
+
}
|
|
10005
|
+
ctx.out.info(`Sign in first, then paste your message.
|
|
10006
|
+
${NEW_USER}`);
|
|
10007
|
+
const signedIn = await browserSignIn(ctx);
|
|
10008
|
+
ctx.out.info(`\u2713 Signed in \xB7 ${printable(signedIn.workspace.name)}`);
|
|
10009
|
+
return resolveCredential(ctx);
|
|
10010
|
+
}
|
|
10011
|
+
async function optimizePasted(ctx) {
|
|
10012
|
+
const { io, out } = ctx;
|
|
10013
|
+
const base = requestOptions(ctx);
|
|
10014
|
+
const credential = await credentialOrSignIn(ctx);
|
|
10015
|
+
const message2 = await io.readPaste(PASTE_PROMPT);
|
|
10016
|
+
if (!message2.trim()) {
|
|
10017
|
+
out.info(`Nothing pasted, so nothing was sent.
|
|
10018
|
+
${nextTimeLine(ctx)}`);
|
|
10019
|
+
return EXIT.ok;
|
|
10020
|
+
}
|
|
10021
|
+
out.info("Amdahl is rewriting it. This can take a minute.");
|
|
10022
|
+
const result = await optimizeOne(ctx, credential, message2, base, "your message");
|
|
10023
|
+
if (result.ok === false) {
|
|
10024
|
+
if (out.json) out.result({ ok: false, file: "-", result });
|
|
10025
|
+
out.info(`Not optimized (${String(result.reason ?? "unknown")}): ${String(result.detail ?? "")}`);
|
|
10026
|
+
return EXIT.general;
|
|
10027
|
+
}
|
|
10028
|
+
if (out.json) {
|
|
10029
|
+
out.result({ ok: true, file: "-", result });
|
|
10030
|
+
return EXIT.ok;
|
|
10031
|
+
}
|
|
10032
|
+
out.info(VERSION_RULE);
|
|
10033
|
+
if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
|
|
10034
|
+
out.result({}, String(result.message ?? ""));
|
|
10035
|
+
const did = whatAmdahlDid(result);
|
|
10036
|
+
out.info([...did, nextTimeLine(ctx)].join("\n"));
|
|
10037
|
+
return EXIT.ok;
|
|
10038
|
+
}
|
|
10039
|
+
async function optimize(ctx, args) {
|
|
10040
|
+
const { io, out } = ctx;
|
|
10041
|
+
let files = args;
|
|
10042
|
+
if (files.length === 0) {
|
|
10043
|
+
if (io.stdinIsTTY && !ctx.nonInteractive && runningAgent(ctx) === null && !ciVariable(io.env)) return optimizePasted(ctx);
|
|
10044
|
+
if (io.stdinIsTTY) {
|
|
10045
|
+
throw usageError(
|
|
10046
|
+
"Give a file to optimize, or pipe the message on stdin.",
|
|
10047
|
+
"Usage: amdahl optimize <file>... | - [--channel email|linkedin]"
|
|
10048
|
+
);
|
|
10049
|
+
}
|
|
10050
|
+
files = ["-"];
|
|
10051
|
+
}
|
|
10052
|
+
if (files.filter((f) => f === "-").length > 1) throw usageError("Stdin (-) can be read only once.");
|
|
10053
|
+
const base = requestOptions(ctx);
|
|
10054
|
+
const inputs = [];
|
|
10055
|
+
for (const file of files) {
|
|
10056
|
+
let message2;
|
|
10057
|
+
try {
|
|
10058
|
+
message2 = file === "-" ? await io.readStdin() : readFileSync5(file, "utf8");
|
|
10059
|
+
} catch (err) {
|
|
10060
|
+
throw usageError(`Could not read ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
10061
|
+
}
|
|
10062
|
+
if (!message2.trim()) throw usageError(`${file === "-" ? "Stdin" : file} is empty.`);
|
|
10063
|
+
inputs.push({ file, message: message2 });
|
|
10064
|
+
}
|
|
10065
|
+
const credential = await resolveCredential(ctx);
|
|
10066
|
+
const results = [];
|
|
10067
|
+
let exit = EXIT.ok;
|
|
10068
|
+
for (const { file, message: message2 } of inputs) {
|
|
10069
|
+
const result = await optimizeOne(ctx, credential, message2, base, file);
|
|
10070
|
+
const ok = result.ok !== false;
|
|
10071
|
+
results.push({ ok, file, result });
|
|
10072
|
+
if (!ok) {
|
|
10073
|
+
exit = EXIT.general;
|
|
10074
|
+
out.info(`${file}: not optimized (${String(result.reason ?? "unknown")}): ${String(result.detail ?? "")}`);
|
|
10075
|
+
continue;
|
|
10076
|
+
}
|
|
10077
|
+
if (!out.json) {
|
|
10078
|
+
if (inputs.length > 1) out.info(`== ${file} ==`);
|
|
10079
|
+
if (result.unchanged === true) out.info("No rewrite beat your draft; it is returned unchanged.");
|
|
10080
|
+
else if (typeof result.summary === "string") out.info(result.summary);
|
|
10081
|
+
out.result({}, String(result.message ?? ""));
|
|
10082
|
+
const did = whatAmdahlDid(result);
|
|
10083
|
+
if (did.length) out.info(did.join("\n"));
|
|
10084
|
+
}
|
|
10085
|
+
}
|
|
10086
|
+
if (out.json) out.result(results.length === 1 ? results[0] : results);
|
|
10087
|
+
return exit;
|
|
10088
|
+
}
|
|
10089
|
+
|
|
10090
|
+
// src/commands/setup.ts
|
|
10091
|
+
var SHOWN = [
|
|
10092
|
+
{ client: "claude-code", name: "Claude Code" },
|
|
10093
|
+
{ client: "cursor", name: "Cursor" },
|
|
10094
|
+
{ client: "codex", name: "Codex" }
|
|
10095
|
+
];
|
|
10096
|
+
var QUESTION = "Where should Amdahl work?";
|
|
10097
|
+
var TERMINAL = "This terminal";
|
|
10098
|
+
var FOUND_TAG = "found on your machine";
|
|
10099
|
+
var CONNECT_URL = "https://amdahl.ai/connect";
|
|
10100
|
+
var NONE_FOUND = `We didn't find Claude Code, Cursor or Codex on this computer.
|
|
10101
|
+
Using the Claude or ChatGPT app? Connect it at ${CONNECT_URL}`;
|
|
10102
|
+
var MENU_HINT = "\u2191\u2193 move \xB7 Space tick/untick \xB7 Enter continue";
|
|
10103
|
+
var ASK = '"Use Amdahl to make this sound less like AI"';
|
|
10104
|
+
var SIGN_IN = {
|
|
10105
|
+
"Claude Code": "If it's open, quit and reopen it. Type /mcp, pick amdahl, sign in.",
|
|
10106
|
+
// Cursor reads ~/.cursor/mcp.json at start, and lists its MCP servers under Customize > MCPs.
|
|
10107
|
+
Cursor: "If it's open, quit and reopen it. In Customize > MCPs, sign in to amdahl."
|
|
10108
|
+
};
|
|
10109
|
+
var TRY_IN = {
|
|
10110
|
+
"Claude Code": `Paste an email and say: ${ASK}`,
|
|
10111
|
+
Cursor: `In chat, paste an email, say: ${ASK}`,
|
|
10112
|
+
Codex: `In codex, paste an email, say: ${ASK}`
|
|
10113
|
+
};
|
|
10114
|
+
var CODEX_TRY = `Run codex, paste an email and say: ${ASK}`;
|
|
10115
|
+
var CODEX_LOGIN = "codex mcp login amdahl";
|
|
10116
|
+
function toolScreen(name, already) {
|
|
10117
|
+
return [
|
|
10118
|
+
already ? `\u2713 ${name} already has Amdahl.` : `\u2713 Added Amdahl to ${name}.`,
|
|
10119
|
+
`One step left, in ${name}:`,
|
|
10120
|
+
` 1. ${SIGN_IN[name] ?? ""}`,
|
|
10121
|
+
` 2. ${TRY_IN[name] ?? ""}`
|
|
10122
|
+
];
|
|
10123
|
+
}
|
|
10124
|
+
function allSetLines(names) {
|
|
10125
|
+
const [only] = names;
|
|
10126
|
+
if (only === void 0) return [];
|
|
10127
|
+
const tryHere2 = `Want to try it here instead? Run: ${amdahlCommand("optimize")}`;
|
|
10128
|
+
if (names.length === 1) {
|
|
10129
|
+
const done = {
|
|
10130
|
+
"Claude Code": ["If you've already run /mcp, you're done.", "If not: quit and reopen Claude Code, type /mcp, pick amdahl, sign in."],
|
|
10131
|
+
Cursor: ["If you've already signed in under Customize > MCPs, you're done.", "If not: quit and reopen Cursor, then sign in to amdahl there."],
|
|
10132
|
+
Codex: [`If you've already run ${CODEX_LOGIN}, you're done.`, `If not, run it now in this terminal.`]
|
|
10133
|
+
};
|
|
10134
|
+
return [`Amdahl is in ${only}. You're all set.`, `Paste an email there and say: ${ASK}`, ...done[only] ?? [], tryHere2];
|
|
10135
|
+
}
|
|
10136
|
+
const step = {
|
|
10137
|
+
"Claude Code": "quit and reopen it, type /mcp, pick amdahl, sign in",
|
|
10138
|
+
Cursor: "quit and reopen it, then in Customize > MCPs, sign in to amdahl",
|
|
10139
|
+
Codex: `run ${CODEX_LOGIN}`
|
|
10140
|
+
};
|
|
10141
|
+
return [
|
|
10142
|
+
`Amdahl is in ${joinNames(names)}. You're all set.`,
|
|
10143
|
+
`Paste an email in ${names.length === 2 ? "either" : "any of them"} and say: ${ASK}`,
|
|
10144
|
+
"If you've already signed each one in, you're done. If not:",
|
|
10145
|
+
...names.map((n) => ` ${n}: ${step[n] ?? ""}`),
|
|
10146
|
+
tryHere2
|
|
10147
|
+
];
|
|
10148
|
+
}
|
|
10149
|
+
function nothingPickedLines() {
|
|
10150
|
+
return [
|
|
10151
|
+
"Nothing picked, so nothing changed.",
|
|
10152
|
+
`To try it here, run ${amdahlCommand()} again and tick "${TERMINAL}".`,
|
|
10153
|
+
`Using the Claude or ChatGPT app? Connect it at ${CONNECT_URL}`
|
|
10154
|
+
];
|
|
10155
|
+
}
|
|
10156
|
+
function wrapLine(line, columns) {
|
|
10157
|
+
if (!columns || [...line].length <= columns) return [line];
|
|
10158
|
+
const lead = /^\s*(?:\d+\.\s+)?/.exec(line)?.[0] ?? "";
|
|
10159
|
+
const indent = " ".repeat(lead.length);
|
|
10160
|
+
const lines = [];
|
|
10161
|
+
let current = lead;
|
|
10162
|
+
let words = 0;
|
|
10163
|
+
for (const word of line.slice(lead.length).split(" ")) {
|
|
10164
|
+
if (words > 0 && [...`${current} ${word}`].length > columns) {
|
|
10165
|
+
lines.push(current);
|
|
10166
|
+
current = indent + word;
|
|
10167
|
+
} else current = words > 0 ? `${current} ${word}` : current + word;
|
|
10168
|
+
words++;
|
|
10169
|
+
}
|
|
10170
|
+
lines.push(current);
|
|
10171
|
+
return lines;
|
|
10172
|
+
}
|
|
10173
|
+
var AGENT_ASK = `paste an email and say ${ASK}`;
|
|
10174
|
+
function finishLines(names, inClaudeCode = false) {
|
|
10175
|
+
const [only] = names;
|
|
9951
10176
|
if (only === void 0) return [];
|
|
9952
10177
|
if (names.length === 1) {
|
|
9953
10178
|
const step2 = {
|
|
9954
10179
|
"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
10180
|
Cursor: "Before that, sign in when Cursor asks, the first time you use Amdahl.",
|
|
9956
|
-
Codex:
|
|
10181
|
+
Codex: `Before that, run \`${CODEX_LOGIN}\` once to connect Amdahl.`
|
|
9957
10182
|
};
|
|
9958
|
-
return [`Try it: ${
|
|
10183
|
+
return [`Try it: ${AGENT_ASK} in ${only}.`, step2[only] ?? ""];
|
|
9959
10184
|
}
|
|
9960
10185
|
const step = {
|
|
9961
10186
|
"Claude Code": inClaudeCode ? "restart it, then run /mcp" : "run /mcp",
|
|
9962
10187
|
Cursor: "sign in when it asks, on first use",
|
|
9963
|
-
Codex:
|
|
10188
|
+
Codex: `run \`${CODEX_LOGIN}\``
|
|
9964
10189
|
};
|
|
9965
|
-
return [`Try it: ${
|
|
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;
|
|
10190
|
+
return [`Try it: ${AGENT_ASK} in any of them.`, "Before that, connect each one once:", ...names.map((n) => ` ${n}: ${step[n] ?? ""}`)];
|
|
9972
10191
|
}
|
|
9973
10192
|
function setupMode(io) {
|
|
9974
10193
|
const { env } = io;
|
|
@@ -10008,35 +10227,70 @@ function reportError(io, err) {
|
|
|
10008
10227
|
${err.hint ? `${err.hint}
|
|
10009
10228
|
` : ""}`);
|
|
10010
10229
|
}
|
|
10011
|
-
async function installEach(ctx,
|
|
10230
|
+
async function installEach(ctx, tools) {
|
|
10012
10231
|
let exit = EXIT.ok;
|
|
10013
10232
|
const ready = [];
|
|
10014
|
-
for (const
|
|
10233
|
+
for (const tool of tools) {
|
|
10015
10234
|
let steps;
|
|
10016
10235
|
try {
|
|
10017
|
-
steps = await installClient(ctx, client, false);
|
|
10236
|
+
steps = await installClient(ctx, tool.client, false);
|
|
10018
10237
|
} 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);
|
|
10238
|
+
const err = raw instanceof CliError ? raw : new CliError("install_failed", `Could not set up ${tool.name}: ${raw instanceof Error ? raw.message : String(raw)}`, EXIT.general);
|
|
10020
10239
|
reportError(ctx.io, err);
|
|
10021
10240
|
if (exit === EXIT.ok) exit = err.exit;
|
|
10022
10241
|
continue;
|
|
10023
10242
|
}
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
ready.push(name);
|
|
10027
|
-
detail([steps.mcp.human, ...steps.mcp.notes, steps.skill.human, ...steps.skill.notes].filter(Boolean).join("\n"));
|
|
10243
|
+
const detail = [steps.mcp.human, ...steps.mcp.notes, steps.skill.human, ...steps.skill.notes].filter(Boolean).join("\n");
|
|
10244
|
+
ready.push({ ...tool, already: steps.mcp.data.action === "unchanged", detail });
|
|
10028
10245
|
}
|
|
10029
10246
|
return { ready, exit };
|
|
10030
10247
|
}
|
|
10248
|
+
function enterOrSkip(io) {
|
|
10249
|
+
return new Promise((resolve2) => {
|
|
10250
|
+
const stop = io.listenForEnter(
|
|
10251
|
+
() => {
|
|
10252
|
+
stop();
|
|
10253
|
+
resolve2(true);
|
|
10254
|
+
},
|
|
10255
|
+
() => resolve2(false)
|
|
10256
|
+
);
|
|
10257
|
+
});
|
|
10258
|
+
}
|
|
10259
|
+
async function codexScreen(ctx, tool, say, detail, lead) {
|
|
10260
|
+
say(tool.already ? "\u2713 Codex already has Amdahl." : "\u2713 Added Amdahl to Codex.");
|
|
10261
|
+
detail(tool.detail);
|
|
10262
|
+
for (const line of lead) say(line);
|
|
10263
|
+
if (ctx.io.platform === "win32" || !await onPath(ctx, "codex")) {
|
|
10264
|
+
say("One step left, in your terminal:");
|
|
10265
|
+
say(` 1. Run: ${CODEX_LOGIN}`);
|
|
10266
|
+
say(` 2. ${TRY_IN.Codex ?? ""}`);
|
|
10267
|
+
return;
|
|
10268
|
+
}
|
|
10269
|
+
say("Press Enter to sign Codex in to Amdahl (opens your browser).");
|
|
10270
|
+
if (!await enterOrSkip(ctx.io)) {
|
|
10271
|
+
say(`Skipped. To connect it later, run: ${CODEX_LOGIN}`);
|
|
10272
|
+
say(CODEX_TRY);
|
|
10273
|
+
return;
|
|
10274
|
+
}
|
|
10275
|
+
const code = await ctx.io.runInteractive("codex", ["mcp", "login", "amdahl"]);
|
|
10276
|
+
if (code === 0) say("\u2713 Codex is connected.");
|
|
10277
|
+
else {
|
|
10278
|
+
say("");
|
|
10279
|
+
say(`Codex didn't finish signing in. To try again, run: ${CODEX_LOGIN}`);
|
|
10280
|
+
}
|
|
10281
|
+
say(CODEX_TRY);
|
|
10282
|
+
}
|
|
10031
10283
|
async function setup(ctx, mode) {
|
|
10032
10284
|
const { io, out } = ctx;
|
|
10033
10285
|
const verbose = ctx.flags.verbose === true;
|
|
10034
|
-
const say = (line) =>
|
|
10286
|
+
const say = (line) => {
|
|
10287
|
+
for (const part of mode === "menu" ? wrapLine(line, io.columns()) : [line]) io.stdout(`${part}
|
|
10035
10288
|
`);
|
|
10289
|
+
};
|
|
10036
10290
|
const detail = (text2) => {
|
|
10037
|
-
if (verbose && text2)
|
|
10291
|
+
if (verbose && text2) io.stdout(`${text2.replace(/^/gm, " ")}
|
|
10292
|
+
`);
|
|
10038
10293
|
};
|
|
10039
|
-
const inClaudeCode = Boolean(io.env.CLAUDECODE);
|
|
10040
10294
|
say(`${out.bold("Amdahl")} \xB7 predictive ML for GTM`);
|
|
10041
10295
|
const found = await detectClients(ctx);
|
|
10042
10296
|
const detected = SHOWN.filter((c) => found[c.client]);
|
|
@@ -10049,43 +10303,43 @@ ${installLine()}
|
|
|
10049
10303
|
`);
|
|
10050
10304
|
return EXIT.general;
|
|
10051
10305
|
}
|
|
10052
|
-
const { ready, exit: exit2 } = await installEach(ctx, targets
|
|
10053
|
-
for (const
|
|
10306
|
+
const { ready, exit: exit2 } = await installEach(ctx, targets);
|
|
10307
|
+
for (const tool of ready) {
|
|
10308
|
+
say(tool.already ? `\u2713 ${tool.name} already has Amdahl` : `\u2713 Added to ${tool.name}`);
|
|
10309
|
+
detail(tool.detail);
|
|
10310
|
+
}
|
|
10311
|
+
for (const line of finishLines(ready.map((t) => t.name), Boolean(io.env.CLAUDECODE))) say(line);
|
|
10054
10312
|
return exit2;
|
|
10055
10313
|
}
|
|
10056
|
-
let who;
|
|
10057
10314
|
if (detected.length > 0) {
|
|
10058
|
-
const
|
|
10059
|
-
if (
|
|
10060
|
-
let
|
|
10061
|
-
|
|
10062
|
-
|
|
10063
|
-
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
}
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10072
|
-
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
hint: MENU_HINT,
|
|
10082
|
-
color: out.color
|
|
10083
|
-
}
|
|
10084
|
-
);
|
|
10315
|
+
const there = await Promise.all(detected.map((c) => hasAmdahl(ctx, c.client)));
|
|
10316
|
+
if (there.every(Boolean)) {
|
|
10317
|
+
let exit2 = EXIT.ok;
|
|
10318
|
+
for (const tool of detected) {
|
|
10319
|
+
if (await isInstalled(ctx, tool.client)) continue;
|
|
10320
|
+
const refreshed = await installEach(ctx, [tool]);
|
|
10321
|
+
for (const r of refreshed.ready) detail(r.detail);
|
|
10322
|
+
if (exit2 === EXIT.ok) exit2 = refreshed.exit;
|
|
10323
|
+
}
|
|
10324
|
+
for (const line of allSetLines(detected.map((c) => c.name))) say(line);
|
|
10325
|
+
return exit2;
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10328
|
+
const rows = [...SHOWN.map((c) => c.name), TERMINAL];
|
|
10329
|
+
const picks = await io.multiSelect(QUESTION, rows, {
|
|
10330
|
+
ticked: [...SHOWN.map((c) => found[c.client]), false],
|
|
10331
|
+
tags: [...SHOWN.map((c) => found[c.client] ? FOUND_TAG : void 0), void 0],
|
|
10332
|
+
note: detected.length === 0 ? NONE_FOUND : void 0,
|
|
10333
|
+
// With no tool here, the marker starts on "This terminal": Space, then Enter.
|
|
10334
|
+
initial: detected.length === 0 ? SHOWN.length : void 0,
|
|
10335
|
+
hint: MENU_HINT,
|
|
10336
|
+
color: out.color
|
|
10337
|
+
});
|
|
10085
10338
|
if (picks.length === 0) {
|
|
10086
|
-
|
|
10339
|
+
for (const line of nothingPickedLines()) say(line);
|
|
10087
10340
|
return EXIT.ok;
|
|
10088
10341
|
}
|
|
10342
|
+
const here = picks.includes(SHOWN.length);
|
|
10089
10343
|
let exit = EXIT.ok;
|
|
10090
10344
|
const chosen = [];
|
|
10091
10345
|
for (const pick of picks.flatMap((i) => SHOWN[i] ?? [])) {
|
|
@@ -10097,22 +10351,51 @@ ${installLine()}
|
|
|
10097
10351
|
reportError(io, err);
|
|
10098
10352
|
if (exit === EXIT.ok) exit = err.exit;
|
|
10099
10353
|
}
|
|
10100
|
-
|
|
10101
|
-
|
|
10354
|
+
const done = await installEach(ctx, chosen);
|
|
10355
|
+
if (exit === EXIT.ok) exit = done.exit;
|
|
10356
|
+
let lead = [NEW_USER];
|
|
10357
|
+
for (const [i, tool] of done.ready.entries()) {
|
|
10358
|
+
if (i > 0) say("");
|
|
10359
|
+
if (tool.client === "codex") {
|
|
10360
|
+
await codexScreen(ctx, tool, say, detail, lead);
|
|
10361
|
+
} else {
|
|
10362
|
+
const [first, ...steps] = toolScreen(tool.name, tool.already);
|
|
10363
|
+
say(first ?? "");
|
|
10364
|
+
detail(tool.detail);
|
|
10365
|
+
for (const line of [...steps, ...lead]) say(line);
|
|
10366
|
+
}
|
|
10367
|
+
lead = [];
|
|
10368
|
+
}
|
|
10369
|
+
if (!here) return exit;
|
|
10370
|
+
if (done.ready.length > 0) say("");
|
|
10371
|
+
try {
|
|
10372
|
+
const code = await tryHere(ctx, say, lead);
|
|
10373
|
+
return exit === EXIT.ok ? code : exit;
|
|
10374
|
+
} catch (err) {
|
|
10375
|
+
if (err instanceof PromptCancelled && done.ready.length > 0) {
|
|
10376
|
+
throw new CliError("cancelled", `Stopped. Amdahl is still set up in ${joinNames(done.ready.map((t) => t.name))}.`, EXIT.cancelled);
|
|
10377
|
+
}
|
|
10378
|
+
throw err;
|
|
10379
|
+
}
|
|
10380
|
+
}
|
|
10381
|
+
async function tryHere(ctx, say, lead) {
|
|
10382
|
+
const { out } = ctx;
|
|
10383
|
+
const verbose = ctx.flags.verbose === true;
|
|
10384
|
+
const who = await signIn(ctx);
|
|
10102
10385
|
if (who.kind === "in") {
|
|
10103
10386
|
say(`\u2713 Signed in \xB7 ${who.workspace}`);
|
|
10104
|
-
|
|
10387
|
+
if (verbose) say(` Signed in as ${who.email}.`);
|
|
10105
10388
|
} else if (who.kind === "out") {
|
|
10389
|
+
for (const line of lead) say(line);
|
|
10106
10390
|
const signedIn = await browserSignIn(ctx);
|
|
10107
10391
|
say(`\u2713 Signed in \xB7 ${printable(signedIn.workspace.name)}`);
|
|
10108
|
-
|
|
10392
|
+
if (verbose) say(` ${signedInLine(ctx, signedIn)}`);
|
|
10109
10393
|
} else {
|
|
10110
10394
|
const name = who.source === "flag" ? "--api-key" : who.source.slice("env:".length);
|
|
10111
|
-
out.info(`Amdahl rejected the key in ${name}
|
|
10395
|
+
out.info(`Amdahl rejected the key in ${name}, so a message can't be tried here. Fix it, or unset it and run \`${amdahlCommand("login")}\`.`);
|
|
10396
|
+
return EXIT.unauthenticated;
|
|
10112
10397
|
}
|
|
10113
|
-
|
|
10114
|
-
for (const line of finishLines(done.ready, inClaudeCode)) say(line);
|
|
10115
|
-
return exit === EXIT.ok ? done.exit : exit;
|
|
10398
|
+
return optimizePasted(ctx);
|
|
10116
10399
|
}
|
|
10117
10400
|
|
|
10118
10401
|
// src/commands/skills.ts
|
|
@@ -10726,7 +11009,7 @@ async function workspaceUse(ctx, args) {
|
|
|
10726
11009
|
}
|
|
10727
11010
|
|
|
10728
11011
|
// src/version.ts
|
|
10729
|
-
var VERSION = "0.3.
|
|
11012
|
+
var VERSION = "0.3.6";
|
|
10730
11013
|
|
|
10731
11014
|
// src/main.ts
|
|
10732
11015
|
var COMMANDS = {
|