@ahood/cli 0.11.0 → 0.12.1
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 -6
- package/dist/archive-entries.js +59 -0
- package/dist/commands/add.js +64 -49
- package/dist/commands/completion.js +6 -5
- package/dist/commands/init.js +51 -3
- package/dist/commands/project-guides.js +47 -0
- package/dist/commands/publish.js +33 -53
- package/dist/commands/remove.js +11 -9
- package/dist/commands/update.js +6 -6
- package/dist/help.js +65 -27
- package/dist/hosted-mcp.js +36 -0
- package/dist/index.js +21 -7
- package/dist/kind-info.js +34 -2
- package/dist/kinds.js +7 -7
- package/dist/project-remote.js +80 -0
- package/dist/spec.js +9 -0
- package/dist/useme/SKILL.md +16 -9
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Git remotes for project guides (ahood-cli#185): reading this repository's
|
|
2
|
+
// origin, and normalizing a remote to the `host/owner/repo` form the ahood
|
|
3
|
+
// server stores and matches on. normalizeProjectRemote is a line-for-line port
|
|
4
|
+
// of the server's lib/projects/normalize-project-remote.ts (ahood#778) -- the
|
|
5
|
+
// two must agree, or `project init` writes a remote the lookup never matches.
|
|
6
|
+
// tests/project-remote.test.ts runs the server's own test table against it.
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
const HOST_LABEL = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
9
|
+
const PATH_SEGMENT = /^[a-z0-9._-]+$/;
|
|
10
|
+
const ALLOWED_SCHEMES = ["https", "http", "ssh", "git"];
|
|
11
|
+
const MAX_PROJECT_REMOTE_LENGTH = 255;
|
|
12
|
+
const MAX_PATH_SEGMENTS = 8;
|
|
13
|
+
// Never echoes the input back: a rejected remote can still carry a
|
|
14
|
+
// credential (`https://user:token@...`).
|
|
15
|
+
const INVALID = {
|
|
16
|
+
ok: false,
|
|
17
|
+
reason: "not a git remote; expected host/owner/repo, e.g. github.com/owner/repo",
|
|
18
|
+
};
|
|
19
|
+
// Strips the scheme, any userinfo (`user:token@`), the port, a trailing
|
|
20
|
+
// `.git` and trailing slashes, then lowercases everything.
|
|
21
|
+
export function normalizeProjectRemote(raw) {
|
|
22
|
+
const input = raw.trim();
|
|
23
|
+
if (!input || input.length > MAX_PROJECT_REMOTE_LENGTH)
|
|
24
|
+
return INVALID;
|
|
25
|
+
if (/[\s?#\\]/.test(input))
|
|
26
|
+
return INVALID;
|
|
27
|
+
let rest;
|
|
28
|
+
const scheme = input.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
29
|
+
if (scheme) {
|
|
30
|
+
if (!ALLOWED_SCHEMES.includes(scheme[1].toLowerCase()))
|
|
31
|
+
return INVALID;
|
|
32
|
+
rest = input.slice(scheme[0].length);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// scp-like form: [user@]host:path, where the path does not start with "/".
|
|
36
|
+
const scp = input.match(/^(?:[^@/:]+@)?([^@/:]+):(?!\/)(.+)$/);
|
|
37
|
+
rest = scp ? `${scp[1]}/${scp[2]}` : input;
|
|
38
|
+
}
|
|
39
|
+
const slash = rest.indexOf("/");
|
|
40
|
+
if (slash <= 0)
|
|
41
|
+
return INVALID;
|
|
42
|
+
let host = rest.slice(0, slash);
|
|
43
|
+
const at = host.lastIndexOf("@");
|
|
44
|
+
if (at !== -1)
|
|
45
|
+
host = host.slice(at + 1);
|
|
46
|
+
host = host.replace(/:\d+$/, "").toLowerCase();
|
|
47
|
+
const labels = host.split(".");
|
|
48
|
+
if (labels.length < 2 || !labels.every((label) => HOST_LABEL.test(label)))
|
|
49
|
+
return INVALID;
|
|
50
|
+
const path = rest
|
|
51
|
+
.slice(slash + 1)
|
|
52
|
+
.replace(/\/+$/, "")
|
|
53
|
+
.replace(/\.git$/i, "")
|
|
54
|
+
.replace(/\/+$/, "")
|
|
55
|
+
.toLowerCase();
|
|
56
|
+
const segments = path.split("/");
|
|
57
|
+
if (segments.length < 2 || segments.length > MAX_PATH_SEGMENTS)
|
|
58
|
+
return INVALID;
|
|
59
|
+
if (!segments.every((s) => s !== "." && s !== ".." && PATH_SEGMENT.test(s)))
|
|
60
|
+
return INVALID;
|
|
61
|
+
return { ok: true, value: `${host}/${segments.join("/")}` };
|
|
62
|
+
}
|
|
63
|
+
// The current repository's origin, normalized, or undefined when there is no
|
|
64
|
+
// git, no repository, no origin, or an origin that is not a shareable remote.
|
|
65
|
+
// The raw URL never leaves this function: it can carry a token.
|
|
66
|
+
export function detectProjectRemote() {
|
|
67
|
+
let raw;
|
|
68
|
+
try {
|
|
69
|
+
raw = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
72
|
+
timeout: 5000,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const normalized = normalizeProjectRemote(raw);
|
|
79
|
+
return normalized.ok ? normalized.value : undefined;
|
|
80
|
+
}
|
package/dist/spec.js
CHANGED
|
@@ -129,3 +129,12 @@ export function docPath(owner, doc) {
|
|
|
129
129
|
validateSegment(doc, "skill", spec);
|
|
130
130
|
return join(DOCS_ROOT, `${owner}@${doc}.md`);
|
|
131
131
|
}
|
|
132
|
+
// A project guide installs to one fixed path per repository (ahood-cli#185),
|
|
133
|
+
// beside CLAUDE.md/AGENTS.md rather than inside them, so ahood never edits a
|
|
134
|
+
// file the user owns. Being shared, the path is claimed by exactly one pin:
|
|
135
|
+
// add.ts refuses a second guide while another is pinned.
|
|
136
|
+
export const PROJECT_GUIDE_PATH = join(".claude", "project.md");
|
|
137
|
+
// Where a pinned-file kind's install lives (kind-info.ts PINNED_FILE_KINDS).
|
|
138
|
+
export function pinnedFilePath(kind, owner, slug) {
|
|
139
|
+
return kind === "doc" ? docPath(owner, slug) : PROJECT_GUIDE_PATH;
|
|
140
|
+
}
|
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),
|
|
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), and project guides (PROJECT.md). Load BEFORE running any ahood command; before writing a non-trivial reusable skill, subagent, or MCP integration 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, publish a skill, ahood mcp, ahood doc, ahood project, 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
|
+
## Five kinds, five command groups
|
|
17
17
|
|
|
18
18
|
| Kind | Root file | Installs to | Commands |
|
|
19
19
|
|---|---|---|---|
|
|
@@ -21,23 +21,29 @@ version (`ahood --version`). It needs no login or network to print.
|
|
|
21
21
|
| agent | `AGENT.md` | `.claude/agents/<owner>@<agent>.md` | `ahood agent <verb>` |
|
|
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
|
+
| project | `PROJECT.md` | `.claude/project.md` (one per repository) | `ahood project <verb>` |
|
|
24
25
|
|
|
25
|
-
All
|
|
26
|
+
All five groups take the same verbs: `search`, `list`, `view`/`show`, `read`, `versions`,
|
|
26
27
|
`diff`, `add`, `update`, `outdated`, `remove`, `edit`, `unpublish`, `star`, `unstar`,
|
|
27
28
|
`share`, `unshare`, `init`, `publish`.
|
|
28
29
|
|
|
29
|
-
- `ahood agent <verb>`, `ahood mcp <verb>` and `ahood
|
|
30
|
+
- `ahood agent <verb>`, `ahood mcp <verb>`, `ahood doc <verb>` and `ahood project <verb>` act on their own kind only.
|
|
30
31
|
A target of another kind is refused (exit 2) before anything is downloaded, prompted for,
|
|
31
32
|
written, or sent, and the error names the kind it found and the right command.
|
|
32
33
|
- `ahood skill <verb>` is the **legacy, cross-kind** group in this CLI release: `skill search`
|
|
33
34
|
and `skill list` include every kind, and `skill add`/`skill update` install whatever kind
|
|
34
|
-
they resolve, except a doc: `skill add` refuses one and points at
|
|
35
|
-
|
|
35
|
+
they resolve, except a doc or project guide: `skill add` refuses one and points at
|
|
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
38
|
`--kind all` spells out the legacy behavior. Prefer the kind-specific form in anything new --
|
|
37
39
|
a future major version makes `ahood skill` skills-only.
|
|
38
40
|
- A doc is a documentation page. `ahood doc read alice/setup-guide` prints it without
|
|
39
41
|
installing anything; `ahood doc add alice/setup-guide` saves it as one pinned file under
|
|
40
42
|
`.claude/docs/`, which `ahood doc update` and `ahood doc remove` manage like any other pin.
|
|
43
|
+
- A project guide is how to work in one repository: conventions, verification, traps. Run bare
|
|
44
|
+
`ahood project` inside a repository to list the guides naming its git remote;
|
|
45
|
+
`ahood project add alice/acme-api` saves one to `.claude/project.md` (pinned, one per
|
|
46
|
+
repository) and never touches `CLAUDE.md` or `AGENTS.md` -- point those at it yourself.
|
|
41
47
|
|
|
42
48
|
## Find, inspect, then install
|
|
43
49
|
|
|
@@ -87,7 +93,7 @@ ahood skill update --dry-run --json # preview, machine-readable
|
|
|
87
93
|
ahood agent remove alice/code-reviewer # local uninstall; prompts unless --yes
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
With no target, `agent`/`mcp`/`doc` `update` and `outdated` consider only installed pins of that
|
|
96
|
+
With no target, `agent`/`mcp`/`doc`/`project` `update` and `outdated` consider only installed pins of that
|
|
91
97
|
kind; the legacy `skill update` covers every pin. Updates only affect the current project.
|
|
92
98
|
An `.mcp.json` entry you edited by hand is refused rather than overwritten.
|
|
93
99
|
|
|
@@ -100,13 +106,14 @@ ahood skill init pdf-tools # ./pdf-tools/SKILL.md
|
|
|
100
106
|
ahood agent init code-reviewer # ./code-reviewer/AGENT.md
|
|
101
107
|
ahood mcp init github-server # ./github-server/server.json (placeholder URL)
|
|
102
108
|
ahood doc init setup-guide # ./setup-guide/DOC.md
|
|
109
|
+
ahood project init acme-api # ./acme-api/PROJECT.md, `project:` filled from git origin
|
|
103
110
|
|
|
104
111
|
ahood agent publish alice/code-reviewer@1.0.0 --path code-reviewer --name "Code Reviewer"
|
|
105
112
|
ahood skill publish alice/pdf-tools@1.1.0 --path pdf-tools --changelog "Adds split"
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
- `--name` is required only on the first publish, which creates the entry under your
|
|
109
|
-
account. `agent publish`/`mcp publish`/`doc publish` imply the kind and refuse a contradictory `--kind`
|
|
116
|
+
account. `agent publish`/`mcp publish`/`doc publish`/`project publish` imply the kind and refuse a contradictory `--kind`
|
|
110
117
|
or an existing entry of another kind before uploading anything.
|
|
111
118
|
- **New entries are private.** Make one discoverable explicitly:
|
|
112
119
|
`ahood skill edit alice/pdf-tools --visibility public`.
|
|
@@ -153,7 +160,7 @@ ahood skill publish alice/pdf-tools@1.1.0 --path pdf-tools --changelog "Adds spl
|
|
|
153
160
|
3. **The hosted registry MCP endpoint**, `https://www.ahood.dev/api/mcp`: Streamable HTTP,
|
|
154
161
|
stateless (POST only), `Authorization: Bearer <token>`. It has write tools too:
|
|
155
162
|
`search_skills`, `get_skill`, `fetch_skill`, `read_skill`, `list_skill_versions`,
|
|
156
|
-
`list_my_skills`, `search_collections`, `get_collection`, `create_skill`, `update_skill`,
|
|
163
|
+
`list_my_skills`, `search_collections`, `get_collection`, `get_project_guide`, `create_skill`, `update_skill`,
|
|
157
164
|
`report_skill`, `delete_skill`, `yank_version`, and snap tools (`create_snap`,
|
|
158
165
|
`list_snaps`, `get_snap`, `set_snap_tags`, `delete_snap`, `share_snap`, `unshare_snap`).
|
|
159
166
|
A client that opens with a legacy-SSE `GET` gets a 405 -- use a Streamable HTTP client.
|
package/package.json
CHANGED