@amdahl/cli 0.2.2 → 0.2.3
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 -1
- package/dist/amdahl.js +107 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,7 +105,7 @@ amdahl keys list
|
|
|
105
105
|
amdahl keys revoke <id|prefix> [--yes] [--no-browser]
|
|
106
106
|
amdahl workspace list | amdahl workspace use <profile>
|
|
107
107
|
amdahl install claude-code|codex|cursor [--print] [--force]
|
|
108
|
-
amdahl skills add <publisher>/<skill> [-g] [--agents] [--force] [--site <origin>]
|
|
108
|
+
amdahl skills add <publisher>/<skill> [-g] [--agents] [--force] [--accept-warning] [--site <origin>]
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
Global flags: `--profile`, `--api-url`, `--api-key`, `--json`, `--no-color`,
|
|
@@ -164,6 +164,39 @@ Code. Without flags it says on stderr where the skill went. `-y` (`--yes`)
|
|
|
164
164
|
adds it to this project without asking. Neither replaces a different copy
|
|
165
165
|
without `--force`.
|
|
166
166
|
|
|
167
|
+
amdahl.ai's AI security review flags some skills. A flagged skill's index
|
|
168
|
+
entry carries an `x-amdahl-warning` field. `skills add` prints it before it
|
|
169
|
+
writes anything: the skill, the sentence from the skill page, and the page's
|
|
170
|
+
link.
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Warning: amdahl.ai's security review flagged amdahl/call-prep.
|
|
174
|
+
Flagged: it may send your data somewhere else (line 14), and 2 more. Read it before you install.
|
|
175
|
+
See what was flagged: https://amdahl.ai/skills/amdahl/call-prep
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
In a terminal, it then asks `Install this skill?` in the same kind of menu.
|
|
179
|
+
The marker starts on "Cancel", which writes nothing and exits 0. "Install
|
|
180
|
+
anyway" goes on as usual. With `-y`, `--json` or no terminal, it does not
|
|
181
|
+
install the skill. It does not ask an agent either, even in a terminal: when
|
|
182
|
+
`CLAUDECODE` or `CURSOR_AGENT` is set, it does not install. It says to show
|
|
183
|
+
the warning to the person, and exits 9. Once they agree, run the same
|
|
184
|
+
command again with `--accept-warning`: it prints the warning and installs. With `--json`, the result or the refusal
|
|
185
|
+
carries the warning as `warning`, with `result`, `flagged`, `line`, `text`
|
|
186
|
+
and `page`.
|
|
187
|
+
|
|
188
|
+
It treats every field of the warning as untrusted. It turns line breaks in
|
|
189
|
+
the sentence into spaces, removes control, escape and format characters, and
|
|
190
|
+
cuts the sentence at 300 characters. It keeps `line` only as a positive
|
|
191
|
+
integer, and `flagged` only as a plain id.
|
|
192
|
+
|
|
193
|
+
The site sends the field only for a flagged skill. So when the field is
|
|
194
|
+
there but `skills add` cannot read it (it is not an object, its `result` is
|
|
195
|
+
not `warn` or `fail`, or it has no sentence), the skill is still flagged. It
|
|
196
|
+
prints the sentence "amdahl.ai flagged this skill. This version of the CLI
|
|
197
|
+
could not read why." and holds the install the same way. Only a missing
|
|
198
|
+
field, or `null`, means no warning.
|
|
199
|
+
|
|
167
200
|
It reads the skill's index,
|
|
168
201
|
`https://amdahl.ai/skills/<publisher>/<skill>/.well-known/agent-skills/index.json`
|
|
169
202
|
([Agent Skills Discovery](https://schemas.agentskills.io/discovery/0.2.0/schema.json)
|
|
@@ -274,6 +307,7 @@ code shown in the terminal; then the key is printed once and stored nowhere.
|
|
|
274
307
|
| 6 | rate limited or quota (429) |
|
|
275
308
|
| 7 | approval denied, expired or locked out |
|
|
276
309
|
| 8 | network or 5xx |
|
|
310
|
+
| 9 | `skills add` did not install a skill amdahl.ai's security review flagged: no terminal to ask, `-y`, `--json` or an agent ran it, and no `--accept-warning` (nothing was written) |
|
|
277
311
|
| 130 | stopped with Ctrl+C at a question (nothing was written) |
|
|
278
312
|
|
|
279
313
|
## Development
|
package/dist/amdahl.js
CHANGED
|
@@ -7547,6 +7547,8 @@ var EXIT = {
|
|
|
7547
7547
|
rateLimited: 6,
|
|
7548
7548
|
denied: 7,
|
|
7549
7549
|
network: 8,
|
|
7550
|
+
/** `skills add` would install a skill amdahl.ai's security review flagged, with no terminal to ask and no --accept-warning. */
|
|
7551
|
+
flagged: 9,
|
|
7550
7552
|
cancelled: 130
|
|
7551
7553
|
};
|
|
7552
7554
|
var CliError = class extends Error {
|
|
@@ -7555,13 +7557,16 @@ var CliError = class extends Error {
|
|
|
7555
7557
|
hint;
|
|
7556
7558
|
/** Technical detail for `--json` only, kept out of the plain message. */
|
|
7557
7559
|
detail;
|
|
7558
|
-
|
|
7560
|
+
/** More fields for `--json` only, printed beside `ok` and `error`. */
|
|
7561
|
+
data;
|
|
7562
|
+
constructor(code, message2, exit, hint, detail, data) {
|
|
7559
7563
|
super(message2);
|
|
7560
7564
|
this.name = "CliError";
|
|
7561
7565
|
this.code = code;
|
|
7562
7566
|
this.exit = exit;
|
|
7563
7567
|
this.hint = hint;
|
|
7564
7568
|
this.detail = detail;
|
|
7569
|
+
this.data = data;
|
|
7565
7570
|
}
|
|
7566
7571
|
};
|
|
7567
7572
|
function usageError(message2, hint) {
|
|
@@ -7637,6 +7642,7 @@ var COMMAND_OPTIONS = {
|
|
|
7637
7642
|
global: { type: "boolean", short: "g" },
|
|
7638
7643
|
agents: { type: "boolean" },
|
|
7639
7644
|
force: { type: "boolean" },
|
|
7645
|
+
"accept-warning": { type: "boolean" },
|
|
7640
7646
|
site: { type: "string" }
|
|
7641
7647
|
}
|
|
7642
7648
|
};
|
|
@@ -7720,12 +7726,17 @@ Commands:
|
|
|
7720
7726
|
keys revoke <id|prefix> [--yes] [--no-browser]
|
|
7721
7727
|
workspace list | workspace use <profile>
|
|
7722
7728
|
install claude-code|codex|cursor [--print] [--force]
|
|
7723
|
-
skills add <publisher>/<skill> [-y] [-g] [--agents] [--force] [--site <origin>]
|
|
7729
|
+
skills add <publisher>/<skill> [-y] [-g] [--agents] [--force] [--accept-warning] [--site <origin>]
|
|
7724
7730
|
In a terminal it asks one question: this project (Enter) or every project.
|
|
7725
7731
|
-y add without asking, to this project
|
|
7726
7732
|
-g every project (~/.claude/skills), not just this one
|
|
7727
7733
|
--agents also .agents/skills, for Codex, Cursor and other agents
|
|
7728
7734
|
--force replace a different copy of the skill already there
|
|
7735
|
+
--accept-warning
|
|
7736
|
+
install a skill that amdahl.ai's security review flagged,
|
|
7737
|
+
after printing the warning. Without it, a terminal asks
|
|
7738
|
+
(Cancel or Install anyway); -y, --json or no terminal
|
|
7739
|
+
stops (exit 9)
|
|
7729
7740
|
|
|
7730
7741
|
Global options:
|
|
7731
7742
|
--profile <name> Use a saved profile (default: AMDAHL_PROFILE, then the default profile)
|
|
@@ -9491,6 +9502,80 @@ function nextStep(ctx, targets, seen) {
|
|
|
9491
9502
|
return `Open a new ${joinNames(names, "or")} chat ${where} to use it.`;
|
|
9492
9503
|
}
|
|
9493
9504
|
|
|
9505
|
+
// src/commands/skills-warning.ts
|
|
9506
|
+
var WARNING_FIELD = "x-amdahl-warning";
|
|
9507
|
+
var MAX_WARNING_CHARS = 300;
|
|
9508
|
+
var BREAK_RE = /[\t\n\v\f\r\u0085\u2028\u2029]/g;
|
|
9509
|
+
var UNSAFE_RE = /[\p{Cc}\p{Cf}\p{Cs}]/gu;
|
|
9510
|
+
var CHECK_ID_RE = /^[a-z][a-z0-9_]{0,39}$/;
|
|
9511
|
+
function warningText(raw) {
|
|
9512
|
+
const clean = raw.replace(BREAK_RE, " ").replace(UNSAFE_RE, "").replace(/ {2,}/g, " ").trim();
|
|
9513
|
+
const chars = Array.from(clean);
|
|
9514
|
+
if (chars.length <= MAX_WARNING_CHARS) return clean;
|
|
9515
|
+
return `${chars.slice(0, MAX_WARNING_CHARS - 3).join("").trimEnd()}...`;
|
|
9516
|
+
}
|
|
9517
|
+
var UNREADABLE_WARNING = Object.freeze({
|
|
9518
|
+
result: "warn",
|
|
9519
|
+
flagged: null,
|
|
9520
|
+
line: null,
|
|
9521
|
+
text: "amdahl.ai flagged this skill. This version of the CLI could not read why."
|
|
9522
|
+
});
|
|
9523
|
+
function readWarning(raw) {
|
|
9524
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
9525
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return { ...UNREADABLE_WARNING };
|
|
9526
|
+
const { result, flagged, line, text } = raw;
|
|
9527
|
+
if (result !== "warn" && result !== "fail") return { ...UNREADABLE_WARNING };
|
|
9528
|
+
const sentence = typeof text === "string" ? warningText(text) : "";
|
|
9529
|
+
if (!sentence) return { ...UNREADABLE_WARNING };
|
|
9530
|
+
return {
|
|
9531
|
+
result,
|
|
9532
|
+
flagged: typeof flagged === "string" && CHECK_ID_RE.test(flagged) ? flagged : null,
|
|
9533
|
+
line: typeof line === "number" && Number.isSafeInteger(line) && line > 0 ? line : null,
|
|
9534
|
+
text: sentence
|
|
9535
|
+
};
|
|
9536
|
+
}
|
|
9537
|
+
function skillPage(site, ref) {
|
|
9538
|
+
return `${site}/skills/${ref.publisher}/${ref.skill}`;
|
|
9539
|
+
}
|
|
9540
|
+
function warningJson(warning, site, ref) {
|
|
9541
|
+
return { ...warning, page: skillPage(site, ref) };
|
|
9542
|
+
}
|
|
9543
|
+
function canAsk(ctx) {
|
|
9544
|
+
return !ctx.nonInteractive && ctx.io.stdoutIsTTY && !ctx.out.json && ctx.flags.yes !== true && runningAgent(ctx) === null;
|
|
9545
|
+
}
|
|
9546
|
+
async function confirmWarning(ctx, ref, site, warning) {
|
|
9547
|
+
const { out } = ctx;
|
|
9548
|
+
const host = new URL(site).host;
|
|
9549
|
+
const name = `${ref.publisher}/${ref.skill}`;
|
|
9550
|
+
const head = warning.result === "fail" ? `${name} failed ${host}'s security review.` : `${host}'s security review flagged ${name}.`;
|
|
9551
|
+
out.info([out.bold(`Warning: ${head}`), ` ${warning.text}`, ` See what was flagged: ${skillPage(site, ref)}`].join("\n"));
|
|
9552
|
+
if (ctx.flags["accept-warning"] === true) {
|
|
9553
|
+
out.info("Installing it anyway, because of --accept-warning.");
|
|
9554
|
+
return true;
|
|
9555
|
+
}
|
|
9556
|
+
if (!canAsk(ctx)) {
|
|
9557
|
+
throw new CliError(
|
|
9558
|
+
"skill_flagged",
|
|
9559
|
+
`Did not install ${name}, because the security review flagged it. Nothing was written.`,
|
|
9560
|
+
EXIT.flagged,
|
|
9561
|
+
"Show this warning to the person you are working for. Once they agree, run the same command again with --accept-warning.",
|
|
9562
|
+
void 0,
|
|
9563
|
+
{ warning: warningJson(warning, site, ref) }
|
|
9564
|
+
);
|
|
9565
|
+
}
|
|
9566
|
+
out.info("");
|
|
9567
|
+
let pick;
|
|
9568
|
+
try {
|
|
9569
|
+
pick = await ctx.io.select("Install this skill?", ["Cancel", "Install anyway"], { initial: 0, color: out.color });
|
|
9570
|
+
} catch (err) {
|
|
9571
|
+
if (err instanceof PromptCancelled) throw new CliError("cancelled", "Stopped. Nothing was written.", EXIT.cancelled);
|
|
9572
|
+
throw err;
|
|
9573
|
+
}
|
|
9574
|
+
if (pick === 1) return true;
|
|
9575
|
+
out.info("Cancelled. Nothing was written.");
|
|
9576
|
+
return false;
|
|
9577
|
+
}
|
|
9578
|
+
|
|
9494
9579
|
// src/commands/skills.ts
|
|
9495
9580
|
var DEFAULT_SKILLS_SITE = "https://amdahl.ai";
|
|
9496
9581
|
var LOOPBACK = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
@@ -9501,7 +9586,7 @@ var MAX_SKILL_BYTES = 64 * 1024;
|
|
|
9501
9586
|
var MAX_ARCHIVE_BYTES = 4 * 1024 * 1024;
|
|
9502
9587
|
var DIGEST_RE = /^sha256:([0-9a-f]{64})$/i;
|
|
9503
9588
|
var FRONT_MATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
9504
|
-
var USAGE2 = "Usage: amdahl skills add <publisher>/<skill> [-y] [-g] [--agents] [--force] [--site <origin>]";
|
|
9589
|
+
var USAGE2 = "Usage: amdahl skills add <publisher>/<skill> [-y] [-g] [--agents] [--force] [--accept-warning] [--site <origin>]";
|
|
9505
9590
|
function normaliseSite(raw) {
|
|
9506
9591
|
let url;
|
|
9507
9592
|
try {
|
|
@@ -9618,12 +9703,14 @@ function readIndex(body, indexUrl, ref, site) {
|
|
|
9618
9703
|
if (!url) throw bad(`gives ${ref.skill} no file URL`);
|
|
9619
9704
|
if (url.origin !== site) throw bad(`points ${ref.skill} at ${url.origin}, not ${site}`);
|
|
9620
9705
|
const version = typeof entry.version === "string" ? printable(entry.version) : "";
|
|
9706
|
+
const warning = readWarning(entry[WARNING_FIELD]);
|
|
9621
9707
|
return {
|
|
9622
9708
|
type,
|
|
9623
9709
|
url: url.href,
|
|
9624
9710
|
sha256: digest[1].toLowerCase(),
|
|
9625
9711
|
description: typeof entry.description === "string" ? entry.description : "",
|
|
9626
|
-
...version ? { version } : {}
|
|
9712
|
+
...version ? { version } : {},
|
|
9713
|
+
...warning ? { warning } : {}
|
|
9627
9714
|
};
|
|
9628
9715
|
}
|
|
9629
9716
|
function checkDigest(body, fileUrl, sha256) {
|
|
@@ -9720,6 +9807,11 @@ async function skillsAdd(ctx, args) {
|
|
|
9720
9807
|
if (guided) ctx.io.stderr("OK\n\n");
|
|
9721
9808
|
const { entry, payload, meta } = fetched;
|
|
9722
9809
|
const { url, sha256 } = entry;
|
|
9810
|
+
const { warning } = entry;
|
|
9811
|
+
if (warning) {
|
|
9812
|
+
if (!await confirmWarning(ctx, ref, site, warning)) return EXIT.ok;
|
|
9813
|
+
if (guided) ctx.out.info("");
|
|
9814
|
+
}
|
|
9723
9815
|
const agent = runningAgent(ctx);
|
|
9724
9816
|
let targets;
|
|
9725
9817
|
let seen = [];
|
|
@@ -9749,7 +9841,15 @@ async function skillsAdd(ctx, args) {
|
|
|
9749
9841
|
(f) => f.action === "unchanged" ? `Already up to date: ${f.path}` : `${f.action === "wrote" ? "Wrote" : "Replaced"} ${f.path}${count}`
|
|
9750
9842
|
);
|
|
9751
9843
|
if (contents && plan.some((f) => f.action !== "unchanged")) lines.push(...contents.map((p) => ` ${p}`));
|
|
9752
|
-
const result = {
|
|
9844
|
+
const result = {
|
|
9845
|
+
ok: true,
|
|
9846
|
+
publisher: ref.publisher,
|
|
9847
|
+
skill: ref.skill,
|
|
9848
|
+
url,
|
|
9849
|
+
digest: `sha256:${sha256}`,
|
|
9850
|
+
files: plan,
|
|
9851
|
+
...warning ? { warning: warningJson(warning, site, ref) } : {}
|
|
9852
|
+
};
|
|
9753
9853
|
if (guided) {
|
|
9754
9854
|
const changed = plan.filter((f) => f.action !== "unchanged");
|
|
9755
9855
|
const done = changed.length > 0 ? `Done. Added to ${skillsFolders(ctx, changed)}.` : `Already up to date in ${skillsFolders(ctx, plan)}.`;
|
|
@@ -9828,7 +9928,7 @@ async function workspaceUse(ctx, args) {
|
|
|
9828
9928
|
}
|
|
9829
9929
|
|
|
9830
9930
|
// src/version.ts
|
|
9831
|
-
var VERSION = "0.2.
|
|
9931
|
+
var VERSION = "0.2.3";
|
|
9832
9932
|
|
|
9833
9933
|
// src/main.ts
|
|
9834
9934
|
var COMMANDS = {
|
|
@@ -9879,7 +9979,7 @@ async function runCli(argv, io) {
|
|
|
9879
9979
|
const error = { code: e.code, message: e.message };
|
|
9880
9980
|
if (e.hint) error.hint = e.hint;
|
|
9881
9981
|
if (e.detail) error.detail = e.detail;
|
|
9882
|
-
io.stdout(`${JSON.stringify({ ok: false, error })}
|
|
9982
|
+
io.stdout(`${JSON.stringify({ ...e.data, ok: false, error })}
|
|
9883
9983
|
`);
|
|
9884
9984
|
} else {
|
|
9885
9985
|
io.stderr(`${e.code === "cancelled" ? "" : "Error: "}${e.message}
|
package/package.json
CHANGED