@ahood/cli 0.3.3 → 0.3.5
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 +17 -14
- package/dist/commands/edit.js +3 -0
- package/dist/commands/init.js +42 -2
- package/dist/commands/search.js +1 -1
- package/dist/commands/token.js +35 -3
- package/dist/commands/unpublish.js +31 -28
- package/dist/commands/update.js +6 -3
- package/dist/commands/view.js +7 -0
- package/dist/confirm.js +31 -0
- package/dist/help.js +3 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -29,29 +29,32 @@ For CI or any non-interactive environment, set `AHOOD_TOKEN` instead of running
|
|
|
29
29
|
|
|
30
30
|
## Commands
|
|
31
31
|
|
|
32
|
-
<!--
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
<!-- Generated from src/help.ts's COMMANDS_HELP by scripts/sync-readme.mjs --
|
|
33
|
+
do not hand-edit the table below. Run `npm run sync-readme` after
|
|
34
|
+
changing COMMANDS_HELP to regenerate it. -->
|
|
35
35
|
|
|
36
|
+
<!-- COMMANDS_TABLE_START -->
|
|
36
37
|
| Command | What it does |
|
|
37
38
|
| --- | --- |
|
|
38
|
-
| `ahood login` | Device-code browser login, stores a token
|
|
39
|
+
| `ahood login` | Device-code browser login, stores a token locally. |
|
|
39
40
|
| `ahood logout` | Removes the stored token. |
|
|
40
41
|
| `ahood whoami [--json]` | Reports whether your stored token still authenticates. |
|
|
41
42
|
| `ahood search <query> [--json] [--limit <n>]` | Search published skills. |
|
|
42
|
-
| `ahood view <owner>/<skill> [--json] [--web]`
|
|
43
|
-
| `ahood
|
|
44
|
-
| `ahood
|
|
45
|
-
| `ahood
|
|
46
|
-
| `ahood
|
|
47
|
-
| `ahood
|
|
48
|
-
| `ahood
|
|
43
|
+
| `ahood view\|show <owner>/<skill> [--json] [--web]` | Show a single skill's details -- tags, license, homepage, repository, dates, and more -- without installing it (alias: ahood show). |
|
|
44
|
+
| `ahood versions <owner>/<skill> [--json]` | List a skill's published-version history -- version, changelog, size, and publish date. |
|
|
45
|
+
| `ahood list-mine [--json]` | List your own skills, public and private. |
|
|
46
|
+
| `ahood add <owner>/<skill>[@version]` | Install a skill into .claude/skills/, pinned in the lockfile. |
|
|
47
|
+
| `ahood update [<owner>/<skill> ...] [--dry-run] [--json]` | Move the lockfile pin(s) forward to the latest version, for one skill or all installed skills at once. |
|
|
48
|
+
| `ahood remove <owner>/<skill>` | Uninstall and unpin a skill (local only). |
|
|
49
|
+
| `ahood edit <owner>/<skill> [--tagline] [--tags] [--license] [--visibility] [--homepage] [--repository]` | Update a skill you own, changing only the flags you pass. |
|
|
50
|
+
| `ahood unpublish <owner>/<skill>[@version] [--yes]` | Delete a skill from the registry for every consumer, or yank a single version, not just your local install (prompts for confirmation unless --yes is passed). |
|
|
49
51
|
| `ahood star <owner>/<skill>` | Star a skill. |
|
|
50
52
|
| `ahood unstar <owner>/<skill>` | Remove your star from a skill. |
|
|
51
|
-
| `ahood init [name]` | Scaffold a new skill folder with a minimal, valid
|
|
52
|
-
| `ahood publish <owner>/<skill>@<version> [--path <dir>] [--name] [--tagline] [--tags] [--license] [--homepage] [--repository]` | Publish a new version of a skill from a folder containing
|
|
53
|
-
| `ahood token create <name>\|list [--json]\|revoke <id
|
|
53
|
+
| `ahood init [name]` | Scaffold a new skill folder with a minimal, valid SKILL.md. |
|
|
54
|
+
| `ahood publish <owner>/<skill>@<version> [--path <dir>] [--name <text>] [--tagline <text>] [--tags <comma,separated>] [--license <id>] [--homepage <url>] [--repository <url>] [--json]` | Publish a new version of a skill from a folder containing SKILL.md, creating the skill first if it doesn't already exist. |
|
|
55
|
+
| `ahood token create <name>\|list [--json]\|revoke <id> [--yes]` | Manage personal API tokens. |
|
|
54
56
|
| `ahood completion <bash\|zsh\|fish>` | Print a shell completion script for the command names. |
|
|
57
|
+
<!-- COMMANDS_TABLE_END -->
|
|
55
58
|
|
|
56
59
|
Run `ahood help`, `ahood help <command>`, or `ahood <command> --help` for the same reference directly in your terminal. `ahood --version` prints the installed CLI version.
|
|
57
60
|
|
package/dist/commands/edit.js
CHANGED
|
@@ -48,4 +48,7 @@ export async function edit(args) {
|
|
|
48
48
|
body: JSON.stringify(body),
|
|
49
49
|
});
|
|
50
50
|
console.log(`Updated ${owner}/${updated.slug}: tagline=${JSON.stringify(updated.tagline)}, license=${JSON.stringify(updated.license)}, visibility=${updated.visibility}, tags=[${updated.tags.join(", ")}]`);
|
|
51
|
+
if (updated.tags_warning) {
|
|
52
|
+
console.warn(`WARNING: ${updated.tags_warning}`);
|
|
53
|
+
}
|
|
51
54
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { basename, join, resolve } from "node:path";
|
|
2
|
+
import { basename, join, resolve, sep } from "node:path";
|
|
3
3
|
const USAGE = "Usage: ahood init [name]";
|
|
4
|
+
// Mirrors the server's slug convention referenced by ahood-cli#60 (and the
|
|
5
|
+
// same shape as spec.ts's SEGMENT_RE, minus "." and "_" -- skill slugs are
|
|
6
|
+
// strictly lowercase-alnum-and-hyphen). A name that already matches this is
|
|
7
|
+
// left untouched; one that doesn't gets normalized rather than rejected,
|
|
8
|
+
// since `init` is meant to be the friendly first-run command.
|
|
9
|
+
const SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
10
|
+
// Lowercases and collapses any run of non-alphanumeric characters (spaces,
|
|
11
|
+
// punctuation, path separators that survived the containment check, etc.)
|
|
12
|
+
// into a single hyphen, then trims leading/trailing hyphens. E.g.
|
|
13
|
+
// "Bad Name!" -> "bad-name".
|
|
14
|
+
function normalizeToSlug(name) {
|
|
15
|
+
return name
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "");
|
|
19
|
+
}
|
|
4
20
|
// publish.ts's only client-side check on SKILL.md is that the file exists
|
|
5
21
|
// (see publish.ts's `existsSync(skillMdPath)`) -- actual frontmatter
|
|
6
22
|
// validation happens server-side, after upload, as part of the processing
|
|
@@ -40,9 +56,33 @@ function skillNameFor(dirPath) {
|
|
|
40
56
|
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(base) ? base : "my-skill";
|
|
41
57
|
}
|
|
42
58
|
export async function init(args) {
|
|
43
|
-
|
|
59
|
+
let name = args[0];
|
|
44
60
|
if (name !== undefined && name.startsWith("-"))
|
|
45
61
|
throw new Error(USAGE);
|
|
62
|
+
if (name) {
|
|
63
|
+
// Path containment first (ahood-cli#61), mirroring spec.ts's
|
|
64
|
+
// validateSegment containment discipline used by add/remove: a resolved
|
|
65
|
+
// path that escapes the current directory is a hard rejection, since
|
|
66
|
+
// normalizing something meant to escape the directory doesn't make
|
|
67
|
+
// sense -- this has to run on the raw name, before any slug
|
|
68
|
+
// normalization would mangle "/" and ".." into harmless hyphens.
|
|
69
|
+
const cwd = resolve(process.cwd());
|
|
70
|
+
const resolvedName = resolve(cwd, name);
|
|
71
|
+
if (resolvedName !== cwd && !resolvedName.startsWith(cwd + sep)) {
|
|
72
|
+
throw new Error(`Invalid name "${name}" -- resolves to "${resolvedName}", which is outside the current directory (${cwd}). Refusing to create files outside the project directory.`);
|
|
73
|
+
}
|
|
74
|
+
// Then slug normalization (ahood-cli#60): a name that stays within cwd
|
|
75
|
+
// but isn't already a valid slug gets normalized with a note, rather
|
|
76
|
+
// than rejected outright.
|
|
77
|
+
if (!SLUG_RE.test(name)) {
|
|
78
|
+
const normalized = normalizeToSlug(name);
|
|
79
|
+
if (!normalized) {
|
|
80
|
+
throw new Error(`Invalid name "${name}" -- could not derive a valid name from it (must contain at least one letter or digit).`);
|
|
81
|
+
}
|
|
82
|
+
console.log(`Note: normalized "${name}" to "${normalized}".`);
|
|
83
|
+
name = normalized;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
46
86
|
const targetDir = name ? resolve(name) : process.cwd();
|
|
47
87
|
const skillMdPath = join(targetDir, "SKILL.md");
|
|
48
88
|
if (existsSync(skillMdPath)) {
|
package/dist/commands/search.js
CHANGED
|
@@ -16,7 +16,7 @@ export async function search(args) {
|
|
|
16
16
|
}
|
|
17
17
|
const qs = new URLSearchParams({ q: query });
|
|
18
18
|
if (limit !== undefined)
|
|
19
|
-
qs.set("
|
|
19
|
+
qs.set("per_page", limit);
|
|
20
20
|
const { skills } = await apiJson(`/api/v1/skills?${qs}`);
|
|
21
21
|
if (jsonOutput) {
|
|
22
22
|
console.log(JSON.stringify(skills));
|
package/dist/commands/token.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { apiJson } from "../http.js";
|
|
2
|
+
import { confirm } from "../confirm.js";
|
|
2
3
|
export async function token(args) {
|
|
3
4
|
const [subcommand, ...rest] = args;
|
|
4
5
|
switch (subcommand) {
|
|
@@ -9,7 +10,7 @@ export async function token(args) {
|
|
|
9
10
|
case "revoke":
|
|
10
11
|
return tokenRevoke(rest);
|
|
11
12
|
default:
|
|
12
|
-
throw new Error("Usage: ahood token create <name>|list [--json]|revoke <id>");
|
|
13
|
+
throw new Error("Usage: ahood token create <name>|list [--json]|revoke <id> [--yes]");
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
async function tokenCreate(args) {
|
|
@@ -48,9 +49,40 @@ async function tokenList(args) {
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
async function tokenRevoke(args) {
|
|
51
|
-
const
|
|
52
|
+
const yes = args.includes("--yes");
|
|
53
|
+
const id = args.find((a) => !a.startsWith("--"));
|
|
52
54
|
if (!id)
|
|
53
|
-
throw new Error("Usage: ahood token revoke <id>");
|
|
55
|
+
throw new Error("Usage: ahood token revoke <id> [--yes]");
|
|
56
|
+
// --yes bypasses both the lookup and the prompt entirely, preserving the
|
|
57
|
+
// original one-shot behavior (a single DELETE call) for scripts/CI that
|
|
58
|
+
// already call `ahood token revoke <id> --yes` unattended.
|
|
59
|
+
if (!yes) {
|
|
60
|
+
const confirmed = await confirm(`${await revokePrompt(id)} Type "yes" to confirm: `);
|
|
61
|
+
if (!confirmed) {
|
|
62
|
+
console.log("Aborted.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
54
66
|
await apiJson(`/api/v1/auth/tokens/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
55
67
|
console.log(`Revoked ${id}`);
|
|
56
68
|
}
|
|
69
|
+
// Looks up the token's name/prefix via the same list endpoint tokenList
|
|
70
|
+
// uses, so the confirmation prompt shows something a human recognizes
|
|
71
|
+
// (e.g. `Revoke token "ci-runner" (ahd_ab...)?`) instead of a bare opaque
|
|
72
|
+
// UUID copied from `ahood token list` output -- easy to mistype with
|
|
73
|
+
// nothing to catch it. The lookup is best-effort: if it fails for any
|
|
74
|
+
// reason (network hiccup, the id not being present in the list, ...) this
|
|
75
|
+
// falls back to the bare id rather than blocking the revoke on a lookup
|
|
76
|
+
// that isn't itself the destructive operation.
|
|
77
|
+
async function revokePrompt(id) {
|
|
78
|
+
try {
|
|
79
|
+
const { tokens } = await apiJson("/api/v1/auth/tokens");
|
|
80
|
+
const match = tokens.find((t) => t.id === id);
|
|
81
|
+
if (match)
|
|
82
|
+
return `Revoke token "${match.name}" (${match.token_prefix}...)?`;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Fall through to the bare-id form below.
|
|
86
|
+
}
|
|
87
|
+
return `Revoke token ${id}?`;
|
|
88
|
+
}
|
|
@@ -1,32 +1,8 @@
|
|
|
1
|
-
import { createInterface } from "node:readline";
|
|
2
1
|
import { apiJson } from "../http.js";
|
|
2
|
+
import { confirm } from "../confirm.js";
|
|
3
3
|
import { parseOwnerSkill, parseOwnerSkillVersion } from "../spec.js";
|
|
4
|
+
import { fetchVersionMeta } from "./add.js";
|
|
4
5
|
const USAGE = "Usage: ahood unpublish <owner>/<skill>[@version] [--yes]";
|
|
5
|
-
// On closed/non-interactive stdin, readline/promises' rl.question() promise
|
|
6
|
-
// never settles -- but since nothing else keeps the event loop alive at that
|
|
7
|
-
// point, the process used to exit 0 anyway once the loop drained, having
|
|
8
|
-
// printed only the prompt (no "Aborted.", no deletion, no error: a silent
|
|
9
|
-
// false success). The plain callback-style API is used instead specifically
|
|
10
|
-
// so the "close" listener can be a reliable signal for "closed without ever
|
|
11
|
-
// answering": `settled` is set synchronously inside the answer callback,
|
|
12
|
-
// before rl.close() is called, so that same close doesn't self-reject.
|
|
13
|
-
function confirm(promptText) {
|
|
14
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
15
|
-
return new Promise((resolve, reject) => {
|
|
16
|
-
let settled = false;
|
|
17
|
-
rl.once("close", () => {
|
|
18
|
-
if (settled)
|
|
19
|
-
return;
|
|
20
|
-
settled = true;
|
|
21
|
-
reject(new Error("Confirmation was not answered (stdin closed)."));
|
|
22
|
-
});
|
|
23
|
-
rl.question(promptText, (answer) => {
|
|
24
|
-
settled = true;
|
|
25
|
-
rl.close();
|
|
26
|
-
resolve(answer.trim().toLowerCase() === "yes");
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
6
|
// A bare "owner/skill" has no "@" at all, so lastIndexOf("@") is -1 and this
|
|
31
7
|
// is a whole-skill unpublish (preserves the exact pre-existing behavior).
|
|
32
8
|
// "owner/skill@1.2.3" has an "@" at index > 0 and is a single-version yank
|
|
@@ -42,7 +18,24 @@ export async function unpublish(args) {
|
|
|
42
18
|
if (!spec)
|
|
43
19
|
throw new Error(USAGE);
|
|
44
20
|
if (isVersioned(spec)) {
|
|
45
|
-
const { owner, skill, version } = parseOwnerSkillVersion(spec, USAGE);
|
|
21
|
+
const { owner, skill, version: parsedVersion } = parseOwnerSkillVersion(spec, USAGE);
|
|
22
|
+
// parseOwnerSkillVersion only validates *syntax* -- an omitted "@version"
|
|
23
|
+
// or an explicit "@latest" both parse to the literal string "latest".
|
|
24
|
+
// But the yank request below hits DELETE .../versions/{version}, which
|
|
25
|
+
// does an exact match against skill_versions.version (ahood-cli#62):
|
|
26
|
+
// there is no "latest" row there, only the download endpoint resolves
|
|
27
|
+
// that literal server-side. So "latest" has to be resolved to a real
|
|
28
|
+
// version string client-side, up front, or the DELETE just 404s with a
|
|
29
|
+
// generic "Not found" that gives no hint the problem is the word
|
|
30
|
+
// "latest". add.ts's fetchVersionMeta already solves exactly this (GET
|
|
31
|
+
// /api/v1/skills/{owner}/{skill}, whose response is joined against
|
|
32
|
+
// latest_version_id) for its own metadata lookup -- reused verbatim here
|
|
33
|
+
// rather than duplicating the resolution logic, and it already throws a
|
|
34
|
+
// clear "<owner>/<skill> has no published version" error for the
|
|
35
|
+
// no-versions-yet edge case instead of leaving that to a confusing 404.
|
|
36
|
+
// An explicit @1.2.3 skips this lookup entirely -- it's already a
|
|
37
|
+
// literal the DELETE endpoint understands, so no extra request is made.
|
|
38
|
+
const version = parsedVersion === "latest" ? (await fetchVersionMeta(owner, skill, "latest")).version : parsedVersion;
|
|
46
39
|
const key = `${owner}/${skill}@${version}`;
|
|
47
40
|
// Yanking is less destructive than a whole-skill delete (existing
|
|
48
41
|
// lockfile-pinned installs keep resolving/verifying against it -- see
|
|
@@ -62,8 +55,18 @@ export async function unpublish(args) {
|
|
|
62
55
|
// just scoped one level down to skill_versions.yanked_at/yanked_reason
|
|
63
56
|
// instead of skills.deleted_at. Mirroring that existing verb/path
|
|
64
57
|
// convention rather than inventing a new one.
|
|
65
|
-
await apiJson(`/api/v1/skills/${encodeURIComponent(owner)}/${encodeURIComponent(skill)}/versions/${encodeURIComponent(version)}`, { method: "DELETE" });
|
|
58
|
+
const result = await apiJson(`/api/v1/skills/${encodeURIComponent(owner)}/${encodeURIComponent(skill)}/versions/${encodeURIComponent(version)}`, { method: "DELETE" });
|
|
66
59
|
console.log(`Yanked ${key}. Existing lockfile pins still resolve; new installs will be warned off it.`);
|
|
60
|
+
// yankVersion (lib/skills/mutations.ts) still returns ok:true here even
|
|
61
|
+
// when the yank itself succeeded but the latest_version_id recompute
|
|
62
|
+
// that follows it failed -- it attaches this field instead of failing
|
|
63
|
+
// the whole request, since the yank already took effect. Discarding the
|
|
64
|
+
// response body would silently drop that signal (ahood-cli#68): the
|
|
65
|
+
// pointer could now be stale with nothing in the terminal to say so, and
|
|
66
|
+
// a later `ahood add owner/skill` trusts latest_version_id completely.
|
|
67
|
+
if (result.latest_version_warning) {
|
|
68
|
+
console.warn(`WARNING: ${result.latest_version_warning}`);
|
|
69
|
+
}
|
|
67
70
|
return;
|
|
68
71
|
}
|
|
69
72
|
const { owner, skill } = parseOwnerSkill(spec, USAGE);
|
package/dist/commands/update.js
CHANGED
|
@@ -86,9 +86,12 @@ export async function update(args) {
|
|
|
86
86
|
}
|
|
87
87
|
const failures = [];
|
|
88
88
|
for (const ownerSlashSkill of skillKeys) {
|
|
89
|
-
if (!lockfile[ownerSlashSkill]
|
|
90
|
-
//
|
|
91
|
-
//
|
|
89
|
+
if (!lockfile[ownerSlashSkill]) {
|
|
90
|
+
// Reachable two ways: an explicitly-named skill that was never
|
|
91
|
+
// installed (`ahood add` is the right command for that -- `update`
|
|
92
|
+
// must not silently install and pin it), or -- when targets came from
|
|
93
|
+
// the lockfile itself -- the lockfile changing out from under us
|
|
94
|
+
// mid-loop. Either way: skip, don't abort.
|
|
92
95
|
console.warn(`Skipping ${ownerSlashSkill}: not currently installed.`);
|
|
93
96
|
continue;
|
|
94
97
|
}
|
package/dist/commands/view.js
CHANGED
|
@@ -41,6 +41,13 @@ export async function view(args) {
|
|
|
41
41
|
}
|
|
42
42
|
const field = (label, value) => console.log(` ${label.padEnd(13)}${value}`);
|
|
43
43
|
console.log(`${detail.owner}/${detail.slug}`);
|
|
44
|
+
// Mirrors the wording/tone of `ahood add`'s yanked warning (src/commands/add.ts)
|
|
45
|
+
// and the website's skill detail page banner -- this is the one CLI surface
|
|
46
|
+
// whose entire job is "let me check this out before deciding to install it",
|
|
47
|
+
// so it must not be the one place that stays silent about a yanked version.
|
|
48
|
+
if (detail.skill_versions?.yanked_at) {
|
|
49
|
+
console.warn(`WARNING: ${detail.owner}/${detail.slug}@${detail.skill_versions.version} has been yanked${detail.skill_versions.yanked_reason ? `: ${detail.skill_versions.yanked_reason}` : "."}`);
|
|
50
|
+
}
|
|
44
51
|
field("name:", detail.name);
|
|
45
52
|
if (detail.tagline)
|
|
46
53
|
field("tagline:", detail.tagline);
|
package/dist/confirm.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
// On closed/non-interactive stdin, readline/promises' rl.question() promise
|
|
3
|
+
// never settles -- but since nothing else keeps the event loop alive at that
|
|
4
|
+
// point, the process used to exit 0 anyway once the loop drained, having
|
|
5
|
+
// printed only the prompt (no "Aborted.", no deletion, no error: a silent
|
|
6
|
+
// false success). The plain callback-style API is used instead specifically
|
|
7
|
+
// so the "close" listener can be a reliable signal for "closed without ever
|
|
8
|
+
// answering": `settled` is set synchronously inside the answer callback,
|
|
9
|
+
// before rl.close() is called, so that same close doesn't self-reject.
|
|
10
|
+
//
|
|
11
|
+
// Shared by any command that needs a "Type yes to confirm" prompt before a
|
|
12
|
+
// destructive/irreversible action (unpublish's whole-skill delete and
|
|
13
|
+
// per-version yank, token revoke, ...) so they all get the same TTY-safe
|
|
14
|
+
// behavior instead of each reimplementing it slightly differently.
|
|
15
|
+
export function confirm(promptText) {
|
|
16
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
let settled = false;
|
|
19
|
+
rl.once("close", () => {
|
|
20
|
+
if (settled)
|
|
21
|
+
return;
|
|
22
|
+
settled = true;
|
|
23
|
+
reject(new Error("Confirmation was not answered (stdin closed)."));
|
|
24
|
+
});
|
|
25
|
+
rl.question(promptText, (answer) => {
|
|
26
|
+
settled = true;
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.trim().toLowerCase() === "yes");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
package/dist/help.js
CHANGED
|
@@ -124,10 +124,11 @@ export const COMMANDS_HELP = [
|
|
|
124
124
|
],
|
|
125
125
|
},
|
|
126
126
|
{
|
|
127
|
-
usage: "ahood token create <name>|list [--json]|revoke <id>",
|
|
127
|
+
usage: "ahood token create <name>|list [--json]|revoke <id> [--yes]",
|
|
128
128
|
summary: "Manage personal API tokens.",
|
|
129
129
|
desc: "Manage personal API tokens. `create` requires an existing browser-backed session.",
|
|
130
|
-
|
|
130
|
+
flags: ["--yes (revoke only) Skip the confirmation prompt and revoke immediately."],
|
|
131
|
+
examples: ["ahood token create ci-runner", "ahood token list --json", "ahood token revoke <id>", "ahood token revoke <id> --yes"],
|
|
131
132
|
},
|
|
132
133
|
{
|
|
133
134
|
usage: "ahood completion <bash|zsh|fish>",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ahood/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "CLI for ahood, a registry for installing and publishing Claude Code skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/AlexKay28/ahood-cli",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"build": "tsc -p tsconfig.json",
|
|
30
30
|
"prepublishOnly": "npm run build",
|
|
31
|
-
"test": "vitest run"
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"sync-readme": "node scripts/sync-readme.mjs"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"tar-stream": "^3.2.1"
|