@ahood/cli 0.12.0 → 0.13.0
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 +33 -6
- package/dist/archive-entries.js +59 -0
- package/dist/commands/add.js +16 -18
- package/dist/commands/completion.js +3 -2
- package/dist/commands/init.js +21 -0
- package/dist/help.js +19 -1
- package/dist/index.js +32 -5
- package/dist/kind-info.js +23 -7
- package/dist/spec.js +17 -1
- package/dist/useme/SKILL.md +16 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,9 +189,9 @@ Shared alice/pdf-tools with design-team.
|
|
|
189
189
|
Sharing is additive -- it doesn't change `alice/pdf-tools`'s own public/private visibility, it just makes it visible to everyone in `design-team` too. On the other side, whoever received the link runs `ahood group join <the link>` to join.
|
|
190
190
|
</details>
|
|
191
191
|
|
|
192
|
-
## Kinds: skills, agents, MCP servers, docs,
|
|
192
|
+
## Kinds: skills, agents, MCP servers, docs, project guides, and loops
|
|
193
193
|
|
|
194
|
-
The registry holds
|
|
194
|
+
The registry holds six kinds of artifact, each with its own command group. All six take the same verbs (`search`, `list`, `view`/`show`, `read`, `versions`, `diff`, `add`, `update`, `outdated`, `remove`, `edit`, `unpublish`, `star`, `unstar`, `share`, `unshare`, `init`, `publish`), run by the same code:
|
|
195
195
|
|
|
196
196
|
| Kind | Root file | `add` installs to | Group |
|
|
197
197
|
| --- | --- | --- | --- |
|
|
@@ -200,6 +200,7 @@ The registry holds five kinds of artifact, each with its own command group. All
|
|
|
200
200
|
| mcp | `server.json` | an entry merged into `.mcp.json` | `ahood mcp <verb>` |
|
|
201
201
|
| doc | `DOC.md` | `.claude/docs/<owner>@<doc>.md` | `ahood doc <verb>` |
|
|
202
202
|
| project | `PROJECT.md` | `.claude/project.md` (one per repository) | `ahood project <verb>` |
|
|
203
|
+
| loop | `LOOP.md` | `.claude/loops/<owner>@<loop>.md` | `ahood loop <verb>` |
|
|
203
204
|
|
|
204
205
|
```
|
|
205
206
|
$ ahood agent search review
|
|
@@ -212,15 +213,18 @@ $ ahood doc add alice/setup-guide # ./.claude/docs/alice@setup-guide.md, p
|
|
|
212
213
|
$ ahood project # project guides for this repo's git remote
|
|
213
214
|
$ ahood project add alice/acme-api # ./.claude/project.md, pinned
|
|
214
215
|
$ ahood project init acme-api # ./acme-api/PROJECT.md, `project:` filled from git origin
|
|
216
|
+
$ ahood loop read alice/code-review-pass # print a loop without installing it
|
|
217
|
+
$ ahood loop add alice/code-review-pass # ./.claude/loops/alice@code-review-pass.md, pinned
|
|
215
218
|
$ ahood agent add alice/pdf-tools
|
|
216
219
|
alice/pdf-tools is a skill, not an agent -- refusing to install it. Use `ahood skill add alice/pdf-tools` instead.
|
|
217
220
|
```
|
|
218
221
|
|
|
219
|
-
- **`ahood agent`, `ahood mcp`, `ahood doc` and `ahood
|
|
220
|
-
- **Missing or unexpected kind metadata is refused, never guessed.** If the registry's answer carries no kind (or one this CLI doesn't handle), a kind-scoped command stops with exit `1` and points at the legacy form. `remove` reads the kind from the project's own files (the skill directory, the agent file, the mcp fingerprint in the lockfile, or a doc or
|
|
221
|
-
- **`ahood skill` is legacy and cross-kind in this release.** `ahood skill search` and `ahood skill list` include every kind, and `ahood skill add`/`update`/`remove`/... act on whatever kind the target is, exactly as before -- including `--json` output and exit codes -- so existing scripts keep working. The exceptions are a doc
|
|
222
|
-
- **Docs install as one pinned file.** `ahood doc add` saves a doc's `DOC.md` to `.claude/docs/<owner>@<doc>.md` and pins it in `.claude/skills.lock.json` with `"kind": "doc"`.
|
|
222
|
+
- **`ahood agent`, `ahood mcp`, `ahood doc`, `ahood project` and `ahood loop` are strict.** A target of another kind is refused -- exit code `2`, naming the kind it found -- before anything is downloaded, prompted for, written, or sent. That covers `add`, `update`, `remove`, `edit`, `unpublish`, `share`/`unshare`, `star`/`unstar`, and `publish` (an existing entry of another kind is refused before anything is packed or uploaded); the read-only verbs refuse too, rather than show the wrong kind. `search` asks the registry for that kind only, `list` keeps only that kind of your own entries, and `update`/`outdated` with no argument consider only installed pins of that kind. `agent publish`/`mcp publish`/`doc publish`/`project publish`/`loop publish` imply their kind and reject a contradictory `--kind`.
|
|
223
|
+
- **Missing or unexpected kind metadata is refused, never guessed.** If the registry's answer carries no kind (or one this CLI doesn't handle), a kind-scoped command stops with exit `1` and points at the legacy form. `remove` reads the kind from the project's own files (the skill directory, the agent file, the mcp fingerprint in the lockfile, or a doc, project, or loop pin's recorded `kind`) and asks the registry only when those can't tell; if neither can, nothing is removed.
|
|
224
|
+
- **`ahood skill` is legacy and cross-kind in this release.** `ahood skill search` and `ahood skill list` include every kind, and `ahood skill add`/`update`/`remove`/... act on whatever kind the target is, exactly as before -- including `--json` output and exit codes -- so existing scripts keep working. The exceptions are a doc, a project guide, and a loop: `ahood skill add` refuses them and points at `ahood doc add` / `ahood project add` / `ahood loop add`. Add `--kind skill|agent|mcp|doc|project|loop` to any `ahood skill` verb (except `publish`, whose `--kind` keeps its original meaning) to scope it, or `--kind all` to spell out the cross-kind behavior.
|
|
225
|
+
- **Docs and loops install as one pinned file per owner/slug.** `ahood doc add` saves a doc's `DOC.md` to `.claude/docs/<owner>@<doc>.md` and pins it in `.claude/skills.lock.json` with `"kind": "doc"`; `ahood loop add` does the same for a loop's `LOOP.md`, to `.claude/loops/<owner>@<loop>.md` with `"kind": "loop"`. Both refuse to overwrite a file at that path that isn't pinned as that entry. `update`/`outdated`/`remove` manage those pins like any other kind. Kind dispatch is table-driven (`src/kind-info.ts`).
|
|
223
226
|
- **A project guide is one pinned file per repository.** `ahood project add` saves the guide's `PROJECT.md` to `.claude/project.md` and pins it with `"kind": "project"`. It never touches `CLAUDE.md` or `AGENTS.md` (point those at `.claude/project.md` yourself), refuses to overwrite an unpinned `.claude/project.md`, and refuses a second guide while another is pinned (`ahood project remove` it first). `ahood project init` fills the optional `project` frontmatter field from `git remote get-url origin`, normalized to `host/owner/repo` exactly as the registry does (credentials stripped; `--project <remote>` or `--no-project` to override). Bare `ahood project` lists the guides you can see for this repository's remote, through the hosted `get_project_guide` MCP tool.
|
|
227
|
+
- **A loop is a reusable procedure, not scoped to one repository.** It is pseudocode and rules for running a recurring kind of agentic session (a code review pass, a migration sweep, ...), meant to be installed the same way in many different projects -- unlike a project guide, `LOOP.md` has no `project` remote field, just `DOC.md`'s plain `name`/`description` frontmatter. `ahood loop init` has no `--project`/`--no-project` flags for that reason.
|
|
224
228
|
|
|
225
229
|
**Migration plan for a strict `ahood skill`.** A future major version will make `ahood skill <verb>` skills-only by default, matching `agent` and `mcp`. Until then: (1) this release adds the strict groups and `--kind`, and labels `ahood skill` as legacy in `--help`; (2) a following minor release prints a stderr deprecation note when an `ahood skill` command actually touches a non-skill entry (stdout and `--json` unchanged); (3) the major release flips the default, keeping `ahood skill <verb> --kind all` as the explicit cross-kind escape hatch. Scripts that want today's behavior forever can pass `--kind all` now.
|
|
226
230
|
|
|
@@ -362,6 +366,29 @@ alice/pdf-tools is a skill, not an agent -- refusing to install it. Use `ahood s
|
|
|
362
366
|
| `ahood project init [name] [--project <git-remote> \| --no-project]` | Scaffold a new project guide folder with a minimal, valid PROJECT.md. |
|
|
363
367
|
| `ahood project publish <owner>/<slug>@<version> [--path <dir>] [--name <text>] [--tagline <text>] [--tags <comma,separated>] [--license <id>] [--homepage <url>] [--repository <url>] [--changelog <text>] [--json]` | Publish a new version of a project guide from a folder containing PROJECT.md, creating it first if it doesn't exist yet. |
|
|
364
368
|
|
|
369
|
+
### Loop
|
|
370
|
+
|
|
371
|
+
| Command | What it does |
|
|
372
|
+
| --- | --- |
|
|
373
|
+
| `ahood loop search <query> [--json] [--limit <n>]` | Search published loops. |
|
|
374
|
+
| `ahood loop view\|show <owner>/<loop> [--json] [--web]` | Show a single loop's details without installing it (alias: ahood loop show). |
|
|
375
|
+
| `ahood loop read <owner>/<loop> [--json]` | Print a published loop's LOOP.md, without installing it. |
|
|
376
|
+
| `ahood loop versions <owner>/<loop> [--json]` | List a loop's published-version history. |
|
|
377
|
+
| `ahood loop diff <owner>/<loop> <versionA> <versionB> [--json]` | Show what changed between two published versions of a loop. |
|
|
378
|
+
| `ahood loop list [--json]` | List your own loops, public and private. |
|
|
379
|
+
| `ahood loop add <owner>/<loop>[@version]` | Install a loop into .claude/loops/<owner>@<loop>.md, pinned in the lockfile. |
|
|
380
|
+
| `ahood loop update [<owner>/<loop> ...] [--dry-run] [--json]` | Move loop pins forward to the latest version. |
|
|
381
|
+
| `ahood loop outdated [<owner>/<loop> ...] [--json]` | Read-only staleness check for installed loops. |
|
|
382
|
+
| `ahood loop remove <owner>/<loop> [--yes]` | Uninstall and unpin a loop (local only, prompts unless --yes is passed). |
|
|
383
|
+
| `ahood loop edit <owner>/<loop> [--tagline] [--tags] [--license] [--visibility] [--homepage] [--repository]` | Update a loop you own, changing only the flags you pass. |
|
|
384
|
+
| `ahood loop unpublish <owner>/<loop>[@version] [--yes]` | Delete a loop from the registry for every consumer, or yank one version (prompts unless --yes is passed). |
|
|
385
|
+
| `ahood loop star <owner>/<loop>` | Star a loop. |
|
|
386
|
+
| `ahood loop unstar <owner>/<loop>` | Remove your star from a loop. |
|
|
387
|
+
| `ahood loop share <owner>/<loop> --group <group>` | Share a loop you own with a group, without changing its visibility. |
|
|
388
|
+
| `ahood loop unshare <owner>/<loop> --group <group>` | Stop sharing a loop you own with a group. |
|
|
389
|
+
| `ahood loop init [name]` | Scaffold a new loop folder with a minimal, valid LOOP.md. |
|
|
390
|
+
| `ahood loop publish <owner>/<loop>@<version> [--path <dir>] [--name <text>] [--tagline <text>] [--tags <comma,separated>] [--license <id>] [--homepage <url>] [--repository <url>] [--changelog <text>] [--json]` | Publish a new version of a loop from a folder containing LOOP.md, creating it first if it doesn't exist yet. |
|
|
391
|
+
|
|
365
392
|
### Group
|
|
366
393
|
|
|
367
394
|
| Command | What it does |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import * as tarStream from "tar-stream";
|
|
3
|
+
import { sanitizeForTerminal } from "./terminal-safe.js";
|
|
4
|
+
// AlexKay28/ahood#792 -- the one archive-entry path rule, mirroring the
|
|
5
|
+
// server's lib/publish/entry-path.ts. `SKILL.md` and `././SKILL.md` used to be
|
|
6
|
+
// two entries to the server (which reads the FIRST) but one file to
|
|
7
|
+
// extractTarGz (resolve() collapsed them and the LAST write won), so a hidden
|
|
8
|
+
// later duplicate could overwrite the reviewed file at install. Every
|
|
9
|
+
// extractor now refuses such an archive up front instead.
|
|
10
|
+
/** True when `path` is already a canonical relative POSIX path: non-empty,
|
|
11
|
+
* no backslash or NUL, not absolute, no empty / "." / ".." segment, and
|
|
12
|
+
* unchanged by posix.normalize. */
|
|
13
|
+
export function isCanonicalEntryPath(path) {
|
|
14
|
+
if (path === "" || path.includes("\\") || path.includes("\0"))
|
|
15
|
+
return false;
|
|
16
|
+
if (posix.isAbsolute(path) || posix.normalize(path) !== path)
|
|
17
|
+
return false;
|
|
18
|
+
return path.split("/").every((segment) => segment !== "" && segment !== "." && segment !== "..");
|
|
19
|
+
}
|
|
20
|
+
/** A tar header name relative to the archive root. `tar -C dir .` names
|
|
21
|
+
* every entry `./x`, so exactly ONE leading "./" is dropped; whatever
|
|
22
|
+
* remains must already be canonical (`././x` is rejected, not collapsed). */
|
|
23
|
+
export function tarEntryPath(headerName) {
|
|
24
|
+
return headerName.startsWith("./") ? headerName.slice(2) : headerName;
|
|
25
|
+
}
|
|
26
|
+
/** Walks every header of an already-decompressed tar and throws unless each
|
|
27
|
+
* non-directory entry has a canonical, unique path. Runs before an extractor
|
|
28
|
+
* writes or returns anything, so a bad entry anywhere refuses the whole
|
|
29
|
+
* archive. Directory entries (`./`, `./scripts/`) are not files and are
|
|
30
|
+
* skipped. */
|
|
31
|
+
export async function assertCanonicalEntryPaths(tar) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const extract = tarStream.extract();
|
|
34
|
+
await new Promise((resolvePromise, reject) => {
|
|
35
|
+
extract.on("entry", (header, stream, next) => {
|
|
36
|
+
stream.resume();
|
|
37
|
+
if (header.type === "directory") {
|
|
38
|
+
next();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const path = tarEntryPath(header.name);
|
|
42
|
+
const problem = !isCanonicalEntryPath(path)
|
|
43
|
+
? `Refusing to extract unsafe archive entry: ${sanitizeForTerminal(header.name)} (not a canonical relative path)`
|
|
44
|
+
: seen.has(path)
|
|
45
|
+
? `Refusing to extract archive: it has more than one entry for "${sanitizeForTerminal(path)}"`
|
|
46
|
+
: null;
|
|
47
|
+
if (problem) {
|
|
48
|
+
reject(new Error(problem));
|
|
49
|
+
extract.destroy();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
seen.add(path);
|
|
53
|
+
next();
|
|
54
|
+
});
|
|
55
|
+
extract.on("finish", () => resolvePromise());
|
|
56
|
+
extract.on("error", reject);
|
|
57
|
+
extract.end(tar);
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/commands/add.js
CHANGED
|
@@ -8,6 +8,7 @@ import { LOCKFILE_PATH, parseOwnerSkillVersion, skillDir, agentPath, pinnedFileP
|
|
|
8
8
|
import { readLockfile, withLock, writeFileAtomic, writeJsonFileAtomic, writeLockfileEntryVerifyingChecksum, LockfileChecksumConflictError, } from "../lockfile.js";
|
|
9
9
|
import { promptSecret } from "../secret-prompt.js";
|
|
10
10
|
import { sanitizeForTerminal } from "../terminal-safe.js";
|
|
11
|
+
import { assertCanonicalEntryPaths, tarEntryPath } from "../archive-entries.js";
|
|
11
12
|
import { UsageError } from "../usage-error.js";
|
|
12
13
|
import { assertKind, describeKind, isPinnedFileKind, KINDS, strictKind } from "../kinds.js";
|
|
13
14
|
const USAGE = "Usage: ahood skill add <owner>/<skill>[@version]";
|
|
@@ -92,6 +93,10 @@ export async function extractTarGz(buffer, destDir) {
|
|
|
92
93
|
catch (error) {
|
|
93
94
|
throw new Error(`Archive decompresses to more than ${MAX_EXTRACTED_BYTES / (1024 * 1024)} MB, or is not valid gzip -- refusing to extract.`, { cause: error });
|
|
94
95
|
}
|
|
96
|
+
// Every entry path is canonical and unique before the first write
|
|
97
|
+
// (AlexKay28/ahood#792): otherwise `././SKILL.md` lands on SKILL.md and the
|
|
98
|
+
// last of the two wins, while the server validated the first.
|
|
99
|
+
await assertCanonicalEntryPaths(decompressed);
|
|
95
100
|
const extract = tarStream.extract();
|
|
96
101
|
const resolvedDest = resolve(destDir);
|
|
97
102
|
let entryCount = 0;
|
|
@@ -112,7 +117,7 @@ export async function extractTarGz(buffer, destDir) {
|
|
|
112
117
|
// segments (and, on Windows, drive-absolute and backslash-separated
|
|
113
118
|
// names) so the comparison catches every escape shape, not just a
|
|
114
119
|
// literal leading "../".
|
|
115
|
-
const entryPath = header.name
|
|
120
|
+
const entryPath = tarEntryPath(header.name);
|
|
116
121
|
const fullPath = resolve(destDir, entryPath);
|
|
117
122
|
if (fullPath !== resolvedDest && !fullPath.startsWith(resolvedDest + sep)) {
|
|
118
123
|
stream.resume();
|
|
@@ -198,27 +203,20 @@ export async function extractSingleFileContent(buffer, entryName) {
|
|
|
198
203
|
catch (error) {
|
|
199
204
|
throw new Error(`Archive decompresses to more than ${MAX_EXTRACTED_BYTES / (1024 * 1024)} MB, or is not valid gzip -- refusing to extract.`, { cause: error });
|
|
200
205
|
}
|
|
206
|
+
// A duplicate or non-canonical entry anywhere refuses the archive, rather
|
|
207
|
+
// than this reader and extractTarGz picking different copies of one file
|
|
208
|
+
// (AlexKay28/ahood#792; this used to take the first, extractTarGz the last).
|
|
209
|
+
await assertCanonicalEntryPaths(decompressed);
|
|
201
210
|
const extract = tarStream.extract();
|
|
202
211
|
let found = null;
|
|
203
212
|
await new Promise((resolvePromise, reject) => {
|
|
204
213
|
extract.on("entry", (header, stream, next) => {
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
if (normalizedName !== entryName) {
|
|
212
|
-
stream.resume();
|
|
213
|
-
next();
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
// Only take the FIRST matching entry, mirroring the server side's
|
|
217
|
-
// `files.find(...)` lookup -- otherwise a tar with two entries at the
|
|
218
|
-
// same normalized name would resolve to whichever one happens to come
|
|
219
|
-
// last here, diverging from what the server validated against
|
|
220
|
-
// (ahood-cli final review finding #4).
|
|
221
|
-
if (found !== null) {
|
|
214
|
+
// Drop tar's one leading "./" the same way extractTarGz and the server
|
|
215
|
+
// do -- a plain `tar czf` commonly prefixes entries with "./", which
|
|
216
|
+
// would otherwise pass server-side validation/publish but then fail to
|
|
217
|
+
// install here with "AGENT.md not found" (ahood-cli final review
|
|
218
|
+
// finding #3).
|
|
219
|
+
if (header.type === "directory" || tarEntryPath(header.name) !== entryName) {
|
|
222
220
|
stream.resume();
|
|
223
221
|
next();
|
|
224
222
|
return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { TOP_LEVEL_COMMANDS_HELP, SKILL_COMMANDS_HELP, AGENT_COMMANDS_HELP, MCP_COMMANDS_HELP, DOC_COMMANDS_HELP, PROJECT_COMMANDS_HELP, GROUP_COMMANDS_HELP, SNAP_COMMANDS_HELP, COMMAND_ALIASES, } from "../help.js";
|
|
1
|
+
import { TOP_LEVEL_COMMANDS_HELP, SKILL_COMMANDS_HELP, AGENT_COMMANDS_HELP, MCP_COMMANDS_HELP, DOC_COMMANDS_HELP, PROJECT_COMMANDS_HELP, LOOP_COMMANDS_HELP, GROUP_COMMANDS_HELP, SNAP_COMMANDS_HELP, COMMAND_ALIASES, } from "../help.js";
|
|
2
2
|
import { UsageError } from "../usage-error.js";
|
|
3
3
|
const USAGE = "Usage: ahood completion <bash|zsh|fish>";
|
|
4
4
|
// Every entity noun with its own verb list, in the order completion offers
|
|
5
|
-
// them. Registry nouns (skill, agent, mcp -- ahood-cli#172; doc -- #182; project -- #185) come first; group
|
|
5
|
+
// them. Registry nouns (skill, agent, mcp -- ahood-cli#172; doc -- #182; project -- #185; loop -- ahood#858) come first; group
|
|
6
6
|
// and snap were already real nouns but had no completion at all before.
|
|
7
7
|
const NOUNS = [
|
|
8
8
|
["skill", SKILL_COMMANDS_HELP],
|
|
@@ -10,6 +10,7 @@ const NOUNS = [
|
|
|
10
10
|
["mcp", MCP_COMMANDS_HELP],
|
|
11
11
|
["doc", DOC_COMMANDS_HELP],
|
|
12
12
|
["project", PROJECT_COMMANDS_HELP],
|
|
13
|
+
["loop", LOOP_COMMANDS_HELP],
|
|
13
14
|
["group", GROUP_COMMANDS_HELP],
|
|
14
15
|
["snap", SNAP_COMMANDS_HELP],
|
|
15
16
|
];
|
package/dist/commands/init.js
CHANGED
|
@@ -139,12 +139,32 @@ TODO -- how to work in this repository: conventions, how to verify a change, and
|
|
|
139
139
|
traps. Plain Markdown; \`ahood project add\` saves this whole file to .claude/project.md.
|
|
140
140
|
`;
|
|
141
141
|
}
|
|
142
|
+
// LOOP.md, for `ahood loop init` (ahood-cli, ahood#858). The backend's
|
|
143
|
+
// validator (lib/publish/parse-loop-frontmatter.ts) is DOC.md's exact flat
|
|
144
|
+
// reader -- no extra field, unlike PROJECT.md's optional `project` remote,
|
|
145
|
+
// since a loop is explicitly portable across repositories rather than
|
|
146
|
+
// scoped to one. Same rules as buildDocMd apply: nothing above the opening
|
|
147
|
+
// `---`, and no colon in a comment line inside the frontmatter.
|
|
148
|
+
function buildLoopMd(name) {
|
|
149
|
+
return `---
|
|
150
|
+
name: ${name}
|
|
151
|
+
description: TODO -- one line saying what kind of recurring agentic session this loop is for.
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
# ${name}
|
|
155
|
+
|
|
156
|
+
TODO -- the pseudocode and rules for running this kind of session: the steps, the order,
|
|
157
|
+
and what to do at each decision point. Plain Markdown; \`ahood loop add\` saves this whole
|
|
158
|
+
file to .claude/loops/<owner>@<loop>.md.
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
142
161
|
const TEMPLATES = {
|
|
143
162
|
skill: buildSkillMd,
|
|
144
163
|
agent: buildAgentMd,
|
|
145
164
|
mcp: buildServerJson,
|
|
146
165
|
doc: buildDocMd,
|
|
147
166
|
project: buildProjectMd,
|
|
167
|
+
loop: buildLoopMd,
|
|
148
168
|
};
|
|
149
169
|
const NEXT_STEPS = {
|
|
150
170
|
skill: ["Fill in the description, then flesh out the ## Instructions section."],
|
|
@@ -156,6 +176,7 @@ const NEXT_STEPS = {
|
|
|
156
176
|
],
|
|
157
177
|
doc: ["Fill in the description, then write the doc below the frontmatter."],
|
|
158
178
|
project: ["Fill in the description, then write the guide below the frontmatter."],
|
|
179
|
+
loop: ["Fill in the description, then write the loop's pseudocode and rules below the frontmatter."],
|
|
159
180
|
};
|
|
160
181
|
function parseProjectFlags(args) {
|
|
161
182
|
const raw = flagValue(args, "--project");
|
package/dist/help.js
CHANGED
|
@@ -386,6 +386,10 @@ function registryCommandsHelp(kind) {
|
|
|
386
386
|
}
|
|
387
387
|
export const AGENT_COMMANDS_HELP = registryCommandsHelp("agent");
|
|
388
388
|
export const DOC_COMMANDS_HELP = registryCommandsHelp("doc");
|
|
389
|
+
// `ahood loop` (ahood-cli, ahood#858): the shared verbs, no extras -- a loop
|
|
390
|
+
// is portable across repositories (unlike `project`), so it needs none of
|
|
391
|
+
// PROJECT_COMMANDS_HELP's bare-listing or `--project` flag additions below.
|
|
392
|
+
export const LOOP_COMMANDS_HELP = registryCommandsHelp("loop");
|
|
389
393
|
// `ahood project` (ahood-cli#185): the shared verbs, with init's `project`
|
|
390
394
|
// field flags, plus bare `ahood project` -- the guides for this repository.
|
|
391
395
|
export const PROJECT_COMMANDS_HELP = [
|
|
@@ -619,6 +623,7 @@ const ENTITY_COMMANDS_HELP = {
|
|
|
619
623
|
mcp: MCP_COMMANDS_HELP,
|
|
620
624
|
doc: DOC_COMMANDS_HELP,
|
|
621
625
|
project: PROJECT_COMMANDS_HELP,
|
|
626
|
+
loop: LOOP_COMMANDS_HELP,
|
|
622
627
|
group: GROUP_COMMANDS_HELP,
|
|
623
628
|
snap: SNAP_COMMANDS_HELP,
|
|
624
629
|
};
|
|
@@ -801,12 +806,22 @@ export function formatProjectHelp() {
|
|
|
801
806
|
"this repository's git remote.",
|
|
802
807
|
], PROJECT_COMMANDS_HELP, "project");
|
|
803
808
|
}
|
|
809
|
+
export function formatLoopHelp() {
|
|
810
|
+
return formatRegistryKindHelp("ahood loop -- loops (LOOP.md) in the ahood registry", [
|
|
811
|
+
"A loop is a pseudocode-and-rules procedure for running a recurring kind of agentic session",
|
|
812
|
+
"(a code review pass, a migration sweep, ...), reusable across repositories -- unlike a project",
|
|
813
|
+
"guide, it names no single repository. Every verb here acts on loops only: a target of another",
|
|
814
|
+
"kind is refused before anything is downloaded, prompted for, written, or sent. Installs land in",
|
|
815
|
+
".claude/loops/<owner>@<loop>.md. To look at a loop without installing it, use `ahood loop read`.",
|
|
816
|
+
], LOOP_COMMANDS_HELP, "loop");
|
|
817
|
+
}
|
|
804
818
|
const KIND_HELP = {
|
|
805
819
|
skill: formatSkillHelp,
|
|
806
820
|
agent: formatAgentHelp,
|
|
807
821
|
mcp: formatMcpHelp,
|
|
808
822
|
doc: formatDocHelp,
|
|
809
823
|
project: formatProjectHelp,
|
|
824
|
+
loop: formatLoopHelp,
|
|
810
825
|
};
|
|
811
826
|
export function formatKindHelp(noun) {
|
|
812
827
|
return KIND_HELP[noun]();
|
|
@@ -850,6 +865,8 @@ export function formatHelp() {
|
|
|
850
865
|
const docGroupSummary = "The same verbs, for docs (DOC.md) only -- run `ahood doc --help` for the full list.";
|
|
851
866
|
const projectGroupUsage = "ahood project <command>";
|
|
852
867
|
const projectGroupSummary = "The same verbs, for project guides (PROJECT.md) only; bare `ahood project` lists this repository's guides.";
|
|
868
|
+
const loopGroupUsage = "ahood loop <command>";
|
|
869
|
+
const loopGroupSummary = "The same verbs, for loops (LOOP.md) only -- run `ahood loop --help` for the full list.";
|
|
853
870
|
const groupGroupUsage = "ahood group <command>";
|
|
854
871
|
const groupGroupSummary = "Create private groups and share skills with them -- run `ahood group --help` for the full list.";
|
|
855
872
|
const snapGroupUsage = "ahood snap <command>";
|
|
@@ -861,6 +878,7 @@ export function formatHelp() {
|
|
|
861
878
|
{ usage: mcpGroupUsage, summary: mcpGroupSummary },
|
|
862
879
|
{ usage: docGroupUsage, summary: docGroupSummary },
|
|
863
880
|
{ usage: projectGroupUsage, summary: projectGroupSummary },
|
|
881
|
+
{ usage: loopGroupUsage, summary: loopGroupSummary },
|
|
864
882
|
{ usage: groupGroupUsage, summary: groupGroupSummary },
|
|
865
883
|
{ usage: snapGroupUsage, summary: snapGroupSummary },
|
|
866
884
|
]);
|
|
@@ -882,7 +900,7 @@ export function formatHelp() {
|
|
|
882
900
|
"",
|
|
883
901
|
"Run `ahood <command> --help` (or `ahood help <command>`) for a single command's flags and examples.",
|
|
884
902
|
"Run `ahood skill --help` for the full list of skill commands.",
|
|
885
|
-
"Run `ahood agent|mcp|doc|project --help` for the kind-scoped agent, MCP, doc
|
|
903
|
+
"Run `ahood agent|mcp|doc|project|loop --help` for the kind-scoped agent, MCP, doc, project guide and loop commands.",
|
|
886
904
|
"Run `ahood group --help` for the full list of group commands.",
|
|
887
905
|
"Run `ahood snap --help` for the full list of snap commands.",
|
|
888
906
|
"Run `ahood --version` to print the installed CLI version.",
|
package/dist/index.js
CHANGED
|
@@ -99,6 +99,7 @@ const COMMANDS = {
|
|
|
99
99
|
agent: dispatchAgent,
|
|
100
100
|
doc: dispatchDoc,
|
|
101
101
|
project: dispatchProject,
|
|
102
|
+
loop: dispatchLoop,
|
|
102
103
|
group: dispatchGroup,
|
|
103
104
|
snap: dispatchSnap,
|
|
104
105
|
};
|
|
@@ -111,7 +112,7 @@ const COMMANDS = {
|
|
|
111
112
|
// sense "skill" already used before "group" (the entity) existed. "snap"
|
|
112
113
|
// joins this set for the exact same reason (ahood-cli#107).
|
|
113
114
|
// Every registry noun joins it (ahood-cli#172), taken from the kind table so
|
|
114
|
-
// a new kind (doc #182, project #185) needs no edit here.
|
|
115
|
+
// a new kind (doc #182, project #185, loop ahood#858) needs no edit here.
|
|
115
116
|
const GROUP_COMMANDS = new Set([...CLI_KINDS, "group", "snap"]);
|
|
116
117
|
function levenshtein(a, b) {
|
|
117
118
|
const dp = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
|
|
@@ -141,6 +142,19 @@ function closestOf(input, candidates) {
|
|
|
141
142
|
function closestCommand(input) {
|
|
142
143
|
return closestOf(input, Object.keys(COMMANDS));
|
|
143
144
|
}
|
|
145
|
+
// Plural alias (ahood-cli#190): `ahood skills`, `ahood agents`, `ahood mcps`,
|
|
146
|
+
// `ahood docs`, `ahood projects`, `ahood loops` dispatch exactly as the
|
|
147
|
+
// singular kind would. A straight trailing-`s` strip-and-match
|
|
148
|
+
// against the KINDS registry is enough -- every current and planned kind
|
|
149
|
+
// pluralizes this way, and no COMMANDS key ends in "s" (checked below), so a
|
|
150
|
+
// real top-level command can never be shadowed by this. Scoped to CLI_KINDS
|
|
151
|
+
// only, not "group"/"snap" (those are separate entities, not kinds).
|
|
152
|
+
function pluralKindAlias(word) {
|
|
153
|
+
if (!word.endsWith("s"))
|
|
154
|
+
return undefined;
|
|
155
|
+
const singular = word.slice(0, -1);
|
|
156
|
+
return isCliKind(singular) ? singular : undefined;
|
|
157
|
+
}
|
|
144
158
|
function closestSkillCommand(input) {
|
|
145
159
|
return closestOf(input, Object.keys(SKILL_COMMANDS));
|
|
146
160
|
}
|
|
@@ -217,6 +231,13 @@ async function dispatchAgent(args) {
|
|
|
217
231
|
async function dispatchDoc(args) {
|
|
218
232
|
await dispatchRegistry("doc", args);
|
|
219
233
|
}
|
|
234
|
+
// `ahood loop` (ahood-cli, ahood#858): a loop is portable across
|
|
235
|
+
// repositories (unlike `project`), so it has no bare-command listing of its
|
|
236
|
+
// own -- every verb is the shared registry dispatch, help included, the
|
|
237
|
+
// same shape as dispatchDoc above.
|
|
238
|
+
async function dispatchLoop(args) {
|
|
239
|
+
await dispatchRegistry("loop", args);
|
|
240
|
+
}
|
|
220
241
|
// `ahood project` (ahood-cli#185): with no verb -- nothing, or only its own
|
|
221
242
|
// flags -- it lists the project guides for this repository's git remote
|
|
222
243
|
// (commands/project-guides.ts). Everything else is the shared registry
|
|
@@ -359,7 +380,13 @@ async function main() {
|
|
|
359
380
|
console.log(formatHelp());
|
|
360
381
|
return;
|
|
361
382
|
}
|
|
362
|
-
|
|
383
|
+
// Resolve a plural kind alias (ahood-cli#190) before the generic lookup, so
|
|
384
|
+
// everything downstream -- the handler itself and the GROUP_COMMANDS
|
|
385
|
+
// --help interception below -- treats `ahood projects` exactly like
|
|
386
|
+
// `ahood project`. Only applies when `command` isn't already a real
|
|
387
|
+
// command (so it never shadows one).
|
|
388
|
+
const resolvedCommand = COMMANDS[command] ? command : (pluralKindAlias(command) ?? command);
|
|
389
|
+
const handler = COMMANDS[resolvedCommand];
|
|
363
390
|
if (!handler) {
|
|
364
391
|
console.error(`Unknown command: ${command}`);
|
|
365
392
|
// Every previously-flat skill command moved under `ahood skill <verb>`
|
|
@@ -381,8 +408,8 @@ async function main() {
|
|
|
381
408
|
// GROUP_COMMANDS despite the name, unrelated to the "group" entity itself)
|
|
382
409
|
// own their own --help handling at both the entity and per-verb level --
|
|
383
410
|
// don't intercept here.
|
|
384
|
-
if (!GROUP_COMMANDS.has(
|
|
385
|
-
const entry = findCommandHelp(
|
|
411
|
+
if (!GROUP_COMMANDS.has(resolvedCommand) && (args.includes("--help") || args.includes("-h"))) {
|
|
412
|
+
const entry = findCommandHelp(resolvedCommand);
|
|
386
413
|
console.log(entry ? formatCommandHelp(entry) : formatHelp());
|
|
387
414
|
return;
|
|
388
415
|
}
|
|
@@ -413,4 +440,4 @@ if (isEntrypoint) {
|
|
|
413
440
|
main();
|
|
414
441
|
}
|
|
415
442
|
// Exported for tests only -- the CLI itself only ever calls main() above.
|
|
416
|
-
export { main, dispatchSkill, dispatchAgent, dispatchDoc, dispatchProject, dispatchMcp, dispatchRegistry, dispatchGroup, dispatchSnap, COMMANDS, SKILL_COMMANDS, GROUP_VERBS, SNAP_VERBS };
|
|
443
|
+
export { main, dispatchSkill, dispatchAgent, dispatchDoc, dispatchProject, dispatchLoop, dispatchMcp, dispatchRegistry, dispatchGroup, dispatchSnap, COMMANDS, SKILL_COMMANDS, GROUP_VERBS, SNAP_VERBS };
|
package/dist/kind-info.js
CHANGED
|
@@ -4,15 +4,20 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Kind-aware registry commands (ahood-cli#172).
|
|
6
6
|
//
|
|
7
|
-
// The registry stores
|
|
8
|
-
// lib/
|
|
7
|
+
// The registry stores six artifact kinds (the backend's
|
|
8
|
+
// lib/artifact-kinds.ts ALLOWED_KINDS: skill, agent, mcp, doc, project, loop), and a
|
|
9
9
|
// kind is fixed when an entry is created -- PATCH's allow-list has no `kind`
|
|
10
10
|
// -- so the kind an entry reports now is the kind it has always had. Each
|
|
11
11
|
// kind has a noun group (`ahood skill|agent|mcp|doc <verb>`), all run by one
|
|
12
12
|
// shared dispatcher. `doc` joined as the fourth in ahood-cli#182: it installs
|
|
13
13
|
// as one flat Markdown file, its DOC.md, the same shape an agent has.
|
|
14
14
|
// `project` joined as the fifth in ahood-cli#185: one PROJECT.md per
|
|
15
|
-
// repository, installed to the fixed path .claude/project.md.
|
|
15
|
+
// repository, installed to the fixed path .claude/project.md. `loop` joined
|
|
16
|
+
// as the sixth (ahood-cli, ahood#858): a portable, reusable procedure for a
|
|
17
|
+
// recurring kind of agentic session, installed many-per-project like `doc`
|
|
18
|
+
// (.claude/loops/<owner>@<loop>.md), with DOC.md's exact frontmatter shape --
|
|
19
|
+
// no extra field, unlike `project`'s optional `project` remote, since a loop
|
|
20
|
+
// is explicitly not scoped to one repository.
|
|
16
21
|
//
|
|
17
22
|
// Per-kind facts live here, not in per-command branches: `placeholder` and
|
|
18
23
|
// `example` feed help and init, `about` the legacy `skill add` refusal.
|
|
@@ -64,16 +69,27 @@ export const KINDS = {
|
|
|
64
69
|
example: { slug: "alice/acme-api", name: "Acme API" },
|
|
65
70
|
about: "a guide to working in one repository",
|
|
66
71
|
},
|
|
72
|
+
loop: {
|
|
73
|
+
label: "loop",
|
|
74
|
+
article: "a",
|
|
75
|
+
plural: "loops",
|
|
76
|
+
rootDoc: "LOOP.md",
|
|
77
|
+
installsTo: ".claude/loops/<owner>@<loop>.md",
|
|
78
|
+
placeholder: "<loop>",
|
|
79
|
+
example: { slug: "alice/code-review-pass", name: "Code Review Pass" },
|
|
80
|
+
about: "a reusable procedure for a recurring kind of agentic session, portable across repositories",
|
|
81
|
+
},
|
|
67
82
|
};
|
|
68
83
|
export const CLI_KINDS = Object.keys(KINDS);
|
|
69
84
|
export function isCliKind(value) {
|
|
70
85
|
return typeof value === "string" && CLI_KINDS.includes(value);
|
|
71
86
|
}
|
|
72
87
|
// Kinds that install as one root-doc file pinned with its kind in the
|
|
73
|
-
// lockfile (doc: ahood-cli#182, project: #185). The
|
|
74
|
-
// remove/update recognise the pin offline, and how a
|
|
75
|
-
// path is claimed by exactly one pin. Legacy
|
|
76
|
-
|
|
88
|
+
// lockfile (doc: ahood-cli#182, project: #185, loop: ahood#858). The
|
|
89
|
+
// recorded kind is how remove/update recognise the pin offline, and how a
|
|
90
|
+
// project guide's shared path is claimed by exactly one pin. Legacy
|
|
91
|
+
// `skill add` refuses these.
|
|
92
|
+
export const PINNED_FILE_KINDS = ["doc", "project", "loop"];
|
|
77
93
|
export function isPinnedFileKind(value) {
|
|
78
94
|
return typeof value === "string" && PINNED_FILE_KINDS.includes(value);
|
|
79
95
|
}
|
package/dist/spec.js
CHANGED
|
@@ -134,7 +134,23 @@ export function docPath(owner, doc) {
|
|
|
134
134
|
// file the user owns. Being shared, the path is claimed by exactly one pin:
|
|
135
135
|
// add.ts refuses a second guide while another is pinned.
|
|
136
136
|
export const PROJECT_GUIDE_PATH = join(".claude", "project.md");
|
|
137
|
+
// A loop installs as one flat file, .claude/loops/<owner>@<loop>.md
|
|
138
|
+
// (ahood-cli, ahood#858) -- the same shape as docPath above, not
|
|
139
|
+
// PROJECT_GUIDE_PATH's single fixed path, since a loop is explicitly
|
|
140
|
+
// portable across repositories rather than scoped to one (see
|
|
141
|
+
// docs/adr/backend/0015-loop-artifact-kind.md in the ahood repo).
|
|
142
|
+
export const LOOPS_ROOT = join(".claude", "loops");
|
|
143
|
+
export function loopPath(owner, loop) {
|
|
144
|
+
const spec = `${owner}/${loop}`;
|
|
145
|
+
validateSegment(owner, "owner", spec);
|
|
146
|
+
validateSegment(loop, "skill", spec);
|
|
147
|
+
return join(LOOPS_ROOT, `${owner}@${loop}.md`);
|
|
148
|
+
}
|
|
137
149
|
// Where a pinned-file kind's install lives (kind-info.ts PINNED_FILE_KINDS).
|
|
138
150
|
export function pinnedFilePath(kind, owner, slug) {
|
|
139
|
-
|
|
151
|
+
if (kind === "doc")
|
|
152
|
+
return docPath(owner, slug);
|
|
153
|
+
if (kind === "loop")
|
|
154
|
+
return loopPath(owner, slug);
|
|
155
|
+
return PROJECT_GUIDE_PATH;
|
|
140
156
|
}
|
package/dist/useme/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ahood
|
|
3
|
-
description: How to use the ahood CLI (@ahood/cli), the registry for reusable AI-agent skills, subagent definitions (AGENT.md), MCP server manifests (server.json), docs (DOC.md),
|
|
3
|
+
description: How to use the ahood CLI (@ahood/cli), the registry for reusable AI-agent skills, subagent definitions (AGENT.md), MCP server manifests (server.json), docs (DOC.md), project guides (PROJECT.md), and loops (LOOP.md). Load BEFORE running any ahood command; before writing a non-trivial reusable skill, subagent, MCP integration, or recurring-session procedure from scratch (search the registry first); when installing, updating, removing, publishing, sharing, or unpublishing one; when wiring ahood into an MCP host; or when setting up headless/CI access with AHOOD_TOKEN. Triggers - ahood, skill registry, is there a skill for this, install a skill, install an agent, add an MCP server, read a doc, project guide, loop, recurring agentic session procedure, publish a skill, ahood mcp, ahood doc, ahood project, ahood loop, AHOOD_TOKEN.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ahood
|
|
@@ -13,7 +13,7 @@ and only moves when someone runs an update.
|
|
|
13
13
|
This guide ships inside the CLI itself (`ahood help useme`), so it matches the installed
|
|
14
14
|
version (`ahood --version`). It needs no login or network to print.
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Six kinds, six command groups
|
|
17
17
|
|
|
18
18
|
| Kind | Root file | Installs to | Commands |
|
|
19
19
|
|---|---|---|---|
|
|
@@ -22,19 +22,20 @@ version (`ahood --version`). It needs no login or network to print.
|
|
|
22
22
|
| mcp | `server.json` | an entry merged into `.mcp.json` | `ahood mcp <verb>` |
|
|
23
23
|
| doc | `DOC.md` | `.claude/docs/<owner>@<doc>.md` | `ahood doc <verb>` |
|
|
24
24
|
| project | `PROJECT.md` | `.claude/project.md` (one per repository) | `ahood project <verb>` |
|
|
25
|
+
| loop | `LOOP.md` | `.claude/loops/<owner>@<loop>.md` | `ahood loop <verb>` |
|
|
25
26
|
|
|
26
|
-
All
|
|
27
|
+
All six groups take the same verbs: `search`, `list`, `view`/`show`, `read`, `versions`,
|
|
27
28
|
`diff`, `add`, `update`, `outdated`, `remove`, `edit`, `unpublish`, `star`, `unstar`,
|
|
28
29
|
`share`, `unshare`, `init`, `publish`.
|
|
29
30
|
|
|
30
|
-
- `ahood agent <verb>`, `ahood mcp <verb>`, `ahood doc <verb>` and `ahood
|
|
31
|
+
- `ahood agent <verb>`, `ahood mcp <verb>`, `ahood doc <verb>`, `ahood project <verb>` and `ahood loop <verb>` act on their own kind only.
|
|
31
32
|
A target of another kind is refused (exit 2) before anything is downloaded, prompted for,
|
|
32
33
|
written, or sent, and the error names the kind it found and the right command.
|
|
33
34
|
- `ahood skill <verb>` is the **legacy, cross-kind** group in this CLI release: `skill search`
|
|
34
35
|
and `skill list` include every kind, and `skill add`/`skill update` install whatever kind
|
|
35
|
-
they resolve, except a doc
|
|
36
|
-
`ahood doc add` / `ahood project add`.
|
|
37
|
-
Add `--kind skill` (or `agent`, `mcp`, `doc`, `project`) to any verb to scope it to one kind;
|
|
36
|
+
they resolve, except a doc, project guide, or loop: `skill add` refuses one and points at
|
|
37
|
+
`ahood doc add` / `ahood project add` / `ahood loop add`.
|
|
38
|
+
Add `--kind skill` (or `agent`, `mcp`, `doc`, `project`, `loop`) to any verb to scope it to one kind;
|
|
38
39
|
`--kind all` spells out the legacy behavior. Prefer the kind-specific form in anything new --
|
|
39
40
|
a future major version makes `ahood skill` skills-only.
|
|
40
41
|
- A doc is a documentation page. `ahood doc read alice/setup-guide` prints it without
|
|
@@ -44,6 +45,11 @@ All five groups take the same verbs: `search`, `list`, `view`/`show`, `read`, `v
|
|
|
44
45
|
`ahood project` inside a repository to list the guides naming its git remote;
|
|
45
46
|
`ahood project add alice/acme-api` saves one to `.claude/project.md` (pinned, one per
|
|
46
47
|
repository) and never touches `CLAUDE.md` or `AGENTS.md` -- point those at it yourself.
|
|
48
|
+
- A loop is a pseudocode-and-rules procedure for running a recurring kind of agentic session
|
|
49
|
+
(a code review pass, a migration sweep, ...), reusable across repositories -- unlike a
|
|
50
|
+
project guide, `LOOP.md` has no repository field. `ahood loop read alice/code-review-pass`
|
|
51
|
+
prints it without installing anything; `ahood loop add alice/code-review-pass` saves it as
|
|
52
|
+
one pinned file under `.claude/loops/`, managed like any other pin.
|
|
47
53
|
|
|
48
54
|
## Find, inspect, then install
|
|
49
55
|
|
|
@@ -93,7 +99,7 @@ ahood skill update --dry-run --json # preview, machine-readable
|
|
|
93
99
|
ahood agent remove alice/code-reviewer # local uninstall; prompts unless --yes
|
|
94
100
|
```
|
|
95
101
|
|
|
96
|
-
With no target, `agent`/`mcp`/`doc`/`project` `update` and `outdated` consider only installed pins of that
|
|
102
|
+
With no target, `agent`/`mcp`/`doc`/`project`/`loop` `update` and `outdated` consider only installed pins of that
|
|
97
103
|
kind; the legacy `skill update` covers every pin. Updates only affect the current project.
|
|
98
104
|
An `.mcp.json` entry you edited by hand is refused rather than overwritten.
|
|
99
105
|
|
|
@@ -107,13 +113,14 @@ ahood agent init code-reviewer # ./code-reviewer/AGENT.md
|
|
|
107
113
|
ahood mcp init github-server # ./github-server/server.json (placeholder URL)
|
|
108
114
|
ahood doc init setup-guide # ./setup-guide/DOC.md
|
|
109
115
|
ahood project init acme-api # ./acme-api/PROJECT.md, `project:` filled from git origin
|
|
116
|
+
ahood loop init code-review-pass # ./code-review-pass/LOOP.md
|
|
110
117
|
|
|
111
118
|
ahood agent publish alice/code-reviewer@1.0.0 --path code-reviewer --name "Code Reviewer"
|
|
112
119
|
ahood skill publish alice/pdf-tools@1.1.0 --path pdf-tools --changelog "Adds split"
|
|
113
120
|
```
|
|
114
121
|
|
|
115
122
|
- `--name` is required only on the first publish, which creates the entry under your
|
|
116
|
-
account. `agent publish`/`mcp publish`/`doc publish`/`project publish` imply the kind and refuse a contradictory `--kind`
|
|
123
|
+
account. `agent publish`/`mcp publish`/`doc publish`/`project publish`/`loop publish` imply the kind and refuse a contradictory `--kind`
|
|
117
124
|
or an existing entry of another kind before uploading anything.
|
|
118
125
|
- **New entries are private.** Make one discoverable explicitly:
|
|
119
126
|
`ahood skill edit alice/pdf-tools --visibility public`.
|
package/package.json
CHANGED