awesome-agents 0.1.0 → 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ This file is maintained by `npm run changelog` and `npm run release`. Release entries are generated from git commit history.
4
+
5
+ ## 0.1.1 - 2026-07-05
6
+
7
+ Initial tracked release.
8
+
9
+ ### Added
10
+
11
+ - Add release and changelog automation.
12
+ - Add chief of staff profile install support (e683a72)
13
+
14
+ ### Fixed
15
+
16
+ - Fix npm bin metadata (085e6ee)
17
+
18
+ ### Changed
19
+
20
+ - Split product notes by area (1d24611)
21
+ - Scaffold awesome-agents CLI (e1ec6a4)
package/README.md CHANGED
@@ -4,25 +4,27 @@
4
4
  It mirrors the useful parts of `npx skills`, but the unit is an operational
5
5
  agent profile instead of a skill.
6
6
 
7
- The first supported source is `touch-grass`: local at `/Users/customer/touch-grass`
8
- or remote as `pablof7z/touch-grass`. Profiles are read from
9
- `agents/profiles/*.md`, adapted for the selected harness, and installed into the
10
- right place for Codex, Claude Code, or OpenCode.
7
+ Supported sources are GitHub repos, Git URLs, or local checkouts that use the
8
+ `touch-grass` layout. Profiles are read from `agents/profiles/*.md`, adapted for
9
+ the selected harness, and installed into the right place for Codex, Claude Code,
10
+ or OpenCode.
11
11
 
12
12
  ## Install And Run
13
13
 
14
- From this repo:
14
+ Use the CLI with `npx`:
15
15
 
16
16
  ```bash
17
- npm install
18
- npm test
19
- node ./bin/awesome-agents.js add /Users/customer/touch-grass --agent codex --profile ios-tester --dry-run
17
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester
18
+ npx awesome-agents add pablof7z/touch-grass --agent chief-of-staff
19
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester --harness opencode
20
20
  ```
21
21
 
22
- Once published, the intended entrypoint is:
22
+ From this repo during development:
23
23
 
24
24
  ```bash
25
- npx awesome-agents add pablof7z/touch-grass --agent codex --profile ios-tester
25
+ npm install
26
+ npm test
27
+ node ./bin/awesome-agents.js add pablof7z/touch-grass --agent ios-tester --dry-run
26
28
  ```
27
29
 
28
30
  ## Commands
@@ -39,8 +41,10 @@ awesome-agents init [name]
39
41
 
40
42
  Useful install options:
41
43
 
42
- - `--agent codex|claude-code|opencode|*`
43
- - `--profile <slug>` or `--profile '*'`
44
+ - `--agent <slug>` to select an agent profile, for example `--agent ios-tester`
45
+ - `--profile <slug>` or `--skill <slug>` as explicit profile aliases; `--skill`
46
+ is command-shape compatibility and does not mean the artifact is a skill
47
+ - `--harness codex|claude-code|opencode|*` to select target harnesses
44
48
  - `--all` to install all profiles to all supported harnesses
45
49
  - `--dry-run` to preview writes
46
50
  - `--project` for project-level install, the default
@@ -85,12 +89,29 @@ provide harness-specific metadata such as model and reasoning effort.
85
89
  ## Examples
86
90
 
87
91
  ```bash
88
- awesome-agents add /Users/customer/touch-grass --list
89
- awesome-agents add /Users/customer/touch-grass --agent codex --profile ios-tester
90
- awesome-agents add /Users/customer/touch-grass --agent codex --profile ios-tester --global
91
- awesome-agents add pablof7z/touch-grass --all --dry-run
92
- awesome-agents use pablof7z/touch-grass@ios-ux-ui-critic --agent claude-code
93
- awesome-agents list --json
94
- awesome-agents remove ios-tester --agent codex
95
- awesome-agents update ios-tester --agent codex --dry-run
92
+ npx awesome-agents add pablof7z/touch-grass --list
93
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester
94
+ npx awesome-agents add pablof7z/touch-grass --agent chief-of-staff
95
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester --harness codex --global
96
+ npx awesome-agents add pablof7z/touch-grass --all --dry-run
97
+ npx awesome-agents use pablof7z/touch-grass --agent ios-ux-ui-critic --harness claude-code
98
+ npx awesome-agents list --json
99
+ npx awesome-agents remove ios-tester --agent codex
100
+ npx awesome-agents update ios-tester --agent codex --dry-run
101
+ ```
102
+
103
+ ## Release Workflow
104
+
105
+ Changes are tracked in `CHANGELOG.md` from git commit history.
106
+
107
+ ```bash
108
+ npm run changelog
109
+ npm run release:dry-run -- patch
110
+ npm run release -- patch
111
+ npm run release -- minor --push
96
112
  ```
113
+
114
+ `npm run release` bumps `package.json`, `package-lock.json`, and
115
+ `src/constants.js`, refreshes `CHANGELOG.md`, runs lint/tests, commits
116
+ `Release vX.Y.Z`, and creates an annotated tag. Passing `--push` pushes the
117
+ branch and tag. It does not publish to npm.
package/docs/cli.md CHANGED
@@ -25,6 +25,34 @@ Supported source values:
25
25
  For GitHub sources, the CLI clones a shallow temporary copy and reads
26
26
  `agents/profiles`.
27
27
 
28
+ ## Install Syntax
29
+
30
+ The preferred remote install form is:
31
+
32
+ ```bash
33
+ npx awesome-agents add owner/repo --agent <profile-slug>
34
+ ```
35
+
36
+ For example:
37
+
38
+ ```bash
39
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester
40
+ npx awesome-agents add pablof7z/touch-grass --agent chief-of-staff
41
+ ```
42
+
43
+ `--agent` selects the reusable agent profile. `--harness` selects where the
44
+ generated profile is installed:
45
+
46
+ ```bash
47
+ npx awesome-agents add pablof7z/touch-grass --agent ios-tester --harness opencode
48
+ ```
49
+
50
+ For backward compatibility, `--agent codex`, `--agent claude-code`, and
51
+ `--agent opencode` are still accepted as harness selectors.
52
+
53
+ The `--skill` flag is accepted only as a command-shape alias for `--profile`.
54
+ Installed artifacts remain operational agent profiles.
55
+
28
56
  ## Install Safety
29
57
 
30
58
  Generated files contain the marker `Generated by awesome-agents`. The CLI refuses
@@ -8,5 +8,16 @@ unless the user confirms them.
8
8
 
9
9
  ## Files
10
10
 
11
- - `awesome-agents-flow-notes.md`: living notes about command shape, source
12
- format, harness targets, and open questions.
11
+ - `product-scope.md`: What `awesome-agents` is for and how it differs from skills.
12
+ - `command-model.md`: Command shape, `npx skills` parity, defaults, and scriptability.
13
+ - `profile-source-format.md`: Source repositories, canonical profiles, adapters, and install sources.
14
+ - `harness-targets.md`: Codex, Claude Code, and OpenCode rendering/target behavior.
15
+ - `safety-and-publishing.md`: Install safety, registry behavior, verification, and npm/GitHub publish state.
16
+ - `open-questions.md`: Unresolved product questions.
17
+
18
+ ## Note-Taking Rules
19
+
20
+ - Split notes by product area instead of appending everything to one flow file.
21
+ - Preserve explicit user direction and accepted implementation decisions.
22
+ - Keep implementation details separate from product intent when possible.
23
+ - Mark inferred future direction as an open question or proposal.
@@ -0,0 +1,49 @@
1
+ # Command Model
2
+
3
+ These notes capture the intended command shape.
4
+
5
+ ## `npx skills` Parity
6
+
7
+ The user requested "the exact same command structure as `npx skills`" for installing agent profiles.
8
+
9
+ Accepted direction:
10
+
11
+ - `add` is the primary install command because `npx skills` uses `add`.
12
+ - `install` is also supported because the user explicitly requested an install command.
13
+ - `use` renders one profile without installing it.
14
+ - `list` and `ls` show installed profiles.
15
+ - `remove` and `rm` remove installed generated profiles.
16
+ - `update` and `upgrade` reinstall from the recorded source.
17
+ - `init` creates a starter profile source layout.
18
+
19
+ ## Selectors And Compatibility
20
+
21
+ The CLI supports:
22
+
23
+ - `--agent <slug>` as the preferred profile selector, matching user-facing
24
+ language such as `npx awesome-agents add owner/repo --agent ios-tester`.
25
+ - `--profile <slug>` to select profiles explicitly.
26
+ - `--skill <slug>` as a compatibility alias, even though the artifact is a profile.
27
+ - `--harness <codex|claude-code|opencode|*>` to select target harnesses.
28
+ - `--agent <codex|claude-code|opencode|*>` remains accepted as a legacy harness
29
+ selector when the value is a known harness or harness alias.
30
+ - `--all` to install every profile.
31
+ - `--list` to inspect source profiles before installing.
32
+
33
+ ## Defaults
34
+
35
+ Accepted implementation decision:
36
+
37
+ - Install scope defaults to project to match the `npx skills` mental model and avoid surprising user-global mutation.
38
+ - Codex is the default harness when no `--harness` or legacy harness-valued
39
+ `--agent` is provided.
40
+ - The CLI is noninteractive in the initial scaffold.
41
+ - `--yes` is accepted for command-shape parity, but prompts are not currently required.
42
+
43
+ ## Scriptability
44
+
45
+ The CLI should be useful from automation:
46
+
47
+ - Human-readable output should include clear target paths.
48
+ - `--json` should be available for scripts.
49
+ - `--dry-run` should preview writes without touching target files or registries.
@@ -0,0 +1,51 @@
1
+ # Harness Targets
2
+
3
+ These notes capture how `awesome-agents` targets agent harnesses.
4
+
5
+ ## Initial Harnesses
6
+
7
+ Initial harness targets:
8
+
9
+ - Codex
10
+ - Claude Code
11
+ - OpenCode
12
+
13
+ ## Codex
14
+
15
+ Generated Codex custom agents install to:
16
+
17
+ - Project: `.codex/agents/<profile>.toml`
18
+ - Global: `$CODEX_HOME/agents/<profile>.toml`, or `~/.codex/agents/<profile>.toml`
19
+
20
+ The generated TOML includes:
21
+
22
+ - `name`
23
+ - `description`
24
+ - optional model settings
25
+ - `developer_instructions`
26
+
27
+ Open question:
28
+
29
+ - Whether future versions should also generate Codex `--profile` config layers in addition to Codex custom agents.
30
+
31
+ ## Claude Code
32
+
33
+ Generated Claude Code subagents install to:
34
+
35
+ - Project: `.claude/agents/<profile>.md`
36
+ - Global: `$CLAUDE_HOME/agents/<profile>.md`, or `~/.claude/agents/<profile>.md`
37
+
38
+ Claude Code output is Markdown with frontmatter and a generated marker.
39
+
40
+ ## OpenCode
41
+
42
+ Generated OpenCode agents install to:
43
+
44
+ - Project: `.opencode/agents/<profile>.md`
45
+ - Global: `$OPENCODE_CONFIG_DIR/agents/<profile>.md`, or `~/.config/opencode/agents/<profile>.md`
46
+
47
+ OpenCode output is Markdown with frontmatter and a generated marker.
48
+
49
+ ## Adapter Gaps
50
+
51
+ Current `touch-grass` profiles have Codex adapters. Claude Code and OpenCode currently use generated defaults unless source repositories add native adapters for those harnesses.
@@ -0,0 +1,20 @@
1
+ # Open Questions
2
+
3
+ These are unresolved product questions for `awesome-agents`.
4
+
5
+ ## Command Experience
6
+
7
+ - Should future versions include interactive prompts like `npx skills`?
8
+ - Should there be a `find` or search command?
9
+ - Should `--skill` remain permanently as a compatibility alias or eventually become a warning?
10
+
11
+ ## Source And Versioning
12
+
13
+ - Should a neutral profile schema be formalized independently of Markdown frontmatter?
14
+ - Should remote installs record commit pins or lockfiles for reproducible updates?
15
+ - Should there eventually be a registry or curated index of profile sources?
16
+
17
+ ## Harness Support
18
+
19
+ - Should future versions generate Codex `--profile` config layers in addition to Codex custom agents?
20
+ - Should `touch-grass` add native Claude Code and OpenCode adapters instead of relying on generated defaults?
@@ -0,0 +1,37 @@
1
+ # Product Scope
2
+
3
+ These notes capture what `awesome-agents` is and why it exists.
4
+
5
+ ## Core Direction
6
+
7
+ - The package is named `awesome-agents`.
8
+ - It should be installable with `npx`.
9
+ - Its command structure should mirror `npx skills`, but for agent profiles.
10
+ - It should install profiles from `touch-grass`, both local `/Users/customer/touch-grass` and GitHub `pablof7z/touch-grass`.
11
+ - Initial harness targets are Codex, Claude Code, and OpenCode.
12
+ - The package should be published after the scaffold works.
13
+
14
+ ## Product Boundary
15
+
16
+ The user asked whether there is something like `npx skills` for agent profiles. The answer was effectively no: skills and profiles are adjacent but different layers.
17
+
18
+ Product distinction:
19
+
20
+ - `npx skills` installs reusable task workflows and instructions.
21
+ - `awesome-agents` installs reusable operational agent identities.
22
+ - A profile can define prompt, model preference, tool boundaries, notes, coordination style, and harness-specific configuration.
23
+
24
+ The CLI should not pretend profiles are skills. It can mirror `npx skills` command shape where that helps user memory, but the artifact remains an agent profile.
25
+
26
+ ## First Source
27
+
28
+ The first real source repository is `touch-grass`, which currently carries reusable operational agent profiles such as:
29
+
30
+ - `ios-tester`
31
+ - `ios-ux-ui-critic`
32
+ - `chief-of-staff`
33
+
34
+ `awesome-agents` should install those profiles without requiring the user to copy profile files manually.
35
+
36
+ The `chief-of-staff` profile is an explicit category-boundary example: it is an
37
+ operational agent identity, not a skill.
@@ -0,0 +1,58 @@
1
+ # Profile Source Format
2
+
3
+ These notes capture how profile source repositories are organized.
4
+
5
+ ## Canonical Layout
6
+
7
+ The initial source format follows the `touch-grass` layout:
8
+
9
+ ```text
10
+ agents/
11
+ profiles/
12
+ <profile>.md
13
+ adapters/
14
+ <harness>/
15
+ <profile>.md
16
+ ```
17
+
18
+ Canonical profiles live at:
19
+
20
+ ```text
21
+ agents/profiles/*.md
22
+ ```
23
+
24
+ Harness adapters live at:
25
+
26
+ ```text
27
+ agents/adapters/<harness>/*.md
28
+ ```
29
+
30
+ ## Profile Files
31
+
32
+ Profile files are Markdown with YAML frontmatter.
33
+
34
+ The CLI should preserve canonical profile content and generate harness-specific install files. A profile is reusable product content, not local machine setup.
35
+
36
+ Current `touch-grass` profiles include:
37
+
38
+ - `chief-of-staff`
39
+ - `ios-tester`
40
+ - `ios-ux-ui-critic`
41
+
42
+ The `chief-of-staff` source files are intentionally under `agents/`, not
43
+ `skills/`, because the source format models agent profiles separately from
44
+ loadable skills.
45
+
46
+ ## Source Resolution
47
+
48
+ The CLI should support:
49
+
50
+ - Local paths such as `/Users/customer/touch-grass`.
51
+ - GitHub shorthand such as `pablof7z/touch-grass`.
52
+ - GitHub URLs.
53
+
54
+ The first source repository is `touch-grass`, but the format should not be hard-coded only to that repository.
55
+
56
+ ## Registry Or Search
57
+
58
+ No registry or search command exists yet. This is an open product area, not part of the initial scaffold.
@@ -0,0 +1,64 @@
1
+ # Safety And Publishing
2
+
3
+ These notes capture install safety, verification, and publish state.
4
+
5
+ ## Install Safety
6
+
7
+ Default behavior should not overwrite or delete unmanaged harness files.
8
+
9
+ Accepted safety behavior:
10
+
11
+ - Generated files contain a `Generated by awesome-agents` marker.
12
+ - The CLI refuses to overwrite unmanaged target files unless `--force` is passed.
13
+ - The CLI refuses to remove unmanaged target files unless `--force` is passed.
14
+ - `--dry-run` should be available for install, remove, update, and init flows.
15
+ - Tests should use fake homes and fixture sources instead of writing to real user harness directories.
16
+
17
+ ## Registry
18
+
19
+ The CLI keeps an `awesome-agents` registry so `list`, `remove`, and `update` operate only on files the CLI generated.
20
+
21
+ Registry locations:
22
+
23
+ - Project installs: `.awesome-agents/installed.json`
24
+ - Global installs: `~/.awesome-agents/installed.json`
25
+
26
+ The registry is not intended to discover or manage hand-written harness profiles.
27
+
28
+ ## Verification
29
+
30
+ The initial scaffold was verified with:
31
+
32
+ - `npm run lint`
33
+ - `npm test`
34
+ - `npm pack --dry-run`
35
+ - local source install smoke tests
36
+ - GitHub source dry-run install smoke tests
37
+
38
+ ## Release Workflow
39
+
40
+ Release metadata is local and deterministic:
41
+
42
+ - `npm run changelog` refreshes `CHANGELOG.md` from git commit history.
43
+ - `npm run release -- patch|minor|major|x.y.z` bumps `package.json`,
44
+ `package-lock.json`, and `src/constants.js`.
45
+ - The release script refreshes `CHANGELOG.md`, runs lint/tests, commits
46
+ `Release vX.Y.Z`, and creates annotated tag `vX.Y.Z`.
47
+ - `npm run release -- patch --push` also pushes the current branch and tag.
48
+ - The release script does not publish to npm.
49
+
50
+ ## Publishing State
51
+
52
+ GitHub repository:
53
+
54
+ - `https://github.com/pablof7z/awesome-agents`
55
+ - Public
56
+ - `main` pushed
57
+
58
+ npm state:
59
+
60
+ - Package name `awesome-agents` was available when checked.
61
+ - Publish is blocked because npm is not authenticated on the machine.
62
+ - `npm whoami` returns `E401`.
63
+ - `npm publish --access public` returns npm's "not found or no permission" error.
64
+ - This is not an OTP blocker yet; npm auth or token setup must happen first.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awesome-agents",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Install reusable agent profiles into Codex, Claude Code, and OpenCode.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,12 +9,17 @@
9
9
  "files": [
10
10
  "bin",
11
11
  "src",
12
+ "scripts",
12
13
  "docs",
13
14
  "README.md",
15
+ "CHANGELOG.md",
14
16
  "AGENTS.md"
15
17
  ],
16
18
  "scripts": {
17
- "lint": "node --check bin/awesome-agents.js && node --check src/*.js && node --check test/*.test.js",
19
+ "changelog": "node scripts/changelog.js --write",
20
+ "lint": "node --check bin/awesome-agents.js && node --check src/*.js && node --check scripts/*.js && node --check test/*.test.js",
21
+ "release": "node scripts/release.js",
22
+ "release:dry-run": "node scripts/release.js --dry-run",
18
23
  "test": "node --test"
19
24
  },
20
25
  "keywords": [
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
+
7
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
8
+ const root = path.resolve(scriptDir, "..");
9
+ const changelogPath = path.join(root, "CHANGELOG.md");
10
+ const packagePath = path.join(root, "package.json");
11
+ const changelogHeader = "# Changelog\n\nThis file is maintained by `npm run changelog` and `npm run release`. Release entries are generated from git commit history.\n\n";
12
+ const categoryOrder = ["Breaking", "Added", "Fixed", "Changed", "Documentation", "Tests", "Maintenance"];
13
+
14
+ export async function updateChangelog(options = {}) {
15
+ const version = options.version ?? await readPackageVersion();
16
+ const date = options.date ?? new Date().toISOString().slice(0, 10);
17
+ const from = options.from ?? latestVersionTag();
18
+ const to = options.to ?? "HEAD";
19
+ const commits = readCommits(from, to);
20
+ const entry = formatEntry({ version, date, from, commits });
21
+
22
+ if (!options.write) {
23
+ return { changelogPath, entry, commits, from, to, version };
24
+ }
25
+
26
+ const existing = await readExistingChangelog();
27
+ await fs.writeFile(changelogPath, upsertEntry(existing, entry, version));
28
+ return { changelogPath, entry, commits, from, to, version };
29
+ }
30
+
31
+ function readCommits(from, to) {
32
+ const range = from ? `${from}..${to}` : to;
33
+ const output = git(["log", "--no-merges", "--pretty=format:%H%x1f%s", range], { allowEmpty: true });
34
+ if (!output) {
35
+ return [];
36
+ }
37
+
38
+ return output.split("\n").map((line) => {
39
+ const [hash, subject] = line.split("\x1f");
40
+ return {
41
+ hash,
42
+ shortHash: hash.slice(0, 7),
43
+ subject: subject.trim()
44
+ };
45
+ }).filter((commit) => commit.subject);
46
+ }
47
+
48
+ function formatEntry({ version, date, from, commits }) {
49
+ const lines = [`## ${version} - ${date}`, ""];
50
+ lines.push(from ? `Changes since \`${from}\`.` : "Initial tracked release.");
51
+ lines.push("");
52
+
53
+ if (commits.length === 0) {
54
+ lines.push("- No commits found for this release.");
55
+ return `${lines.join("\n")}\n`;
56
+ }
57
+
58
+ const groups = new Map(categoryOrder.map((category) => [category, []]));
59
+ for (const commit of commits) {
60
+ groups.get(categoryFor(commit.subject)).push(commit);
61
+ }
62
+
63
+ for (const category of categoryOrder) {
64
+ const entries = groups.get(category);
65
+ if (entries.length === 0) {
66
+ continue;
67
+ }
68
+
69
+ lines.push(`### ${category}`, "");
70
+ for (const commit of entries) {
71
+ lines.push(`- ${cleanSubject(commit.subject)} (${commit.shortHash})`);
72
+ }
73
+ lines.push("");
74
+ }
75
+
76
+ return `${lines.join("\n").trimEnd()}\n`;
77
+ }
78
+
79
+ function upsertEntry(existing, entry, version) {
80
+ const body = normalizeBody(existing);
81
+ const escapedVersion = escapeRegExp(version);
82
+ const existingEntry = new RegExp(`^## ${escapedVersion} - [^\\n]*\\n[\\s\\S]*?(?=^## \\d+\\.\\d+\\.\\d+ - |(?![\\s\\S]))`, "m");
83
+
84
+ if (existingEntry.test(body)) {
85
+ return `${changelogHeader}${body.replace(existingEntry, entry.trimEnd()).trimStart()}`;
86
+ }
87
+
88
+ return `${changelogHeader}${entry.trimEnd()}\n\n${body.trimStart()}`.trimEnd() + "\n";
89
+ }
90
+
91
+ function normalizeBody(existing) {
92
+ if (!existing) {
93
+ return "";
94
+ }
95
+
96
+ if (existing.startsWith(changelogHeader)) {
97
+ return existing.slice(changelogHeader.length);
98
+ }
99
+
100
+ return existing.replace(/^# Changelog\s*/i, "").trimStart();
101
+ }
102
+
103
+ function categoryFor(subject) {
104
+ const normalized = subject.toLowerCase();
105
+ if (/^[a-z]+(?:\([^)]+\))?!:/.test(normalized) || normalized.includes("breaking")) {
106
+ return "Breaking";
107
+ }
108
+ if (/^(feat|add)(?:\([^)]+\))?:?\s/.test(normalized)) {
109
+ return "Added";
110
+ }
111
+ if (/^(fix|repair)(?:\([^)]+\))?:?\s/.test(normalized)) {
112
+ return "Fixed";
113
+ }
114
+ if (/^(docs?|readme)(?:\([^)]+\))?:?\s/.test(normalized)) {
115
+ return "Documentation";
116
+ }
117
+ if (/^(test|tests)(?:\([^)]+\))?:?\s/.test(normalized)) {
118
+ return "Tests";
119
+ }
120
+ if (/^(build|chore|ci|release|bump)(?:\([^)]+\))?:?\s/.test(normalized)) {
121
+ return "Maintenance";
122
+ }
123
+ return "Changed";
124
+ }
125
+
126
+ function cleanSubject(subject) {
127
+ return subject
128
+ .replace(/^[a-z]+(?:\([^)]+\))?!?:\s*/i, "")
129
+ .trim();
130
+ }
131
+
132
+ function latestVersionTag() {
133
+ return git(["tag", "--list", "v[0-9]*", "--sort=-version:refname"], { allowEmpty: true })
134
+ .split("\n")
135
+ .find(Boolean);
136
+ }
137
+
138
+ async function readPackageVersion() {
139
+ const pkg = JSON.parse(await fs.readFile(packagePath, "utf8"));
140
+ return pkg.version;
141
+ }
142
+
143
+ async function readExistingChangelog() {
144
+ try {
145
+ return await fs.readFile(changelogPath, "utf8");
146
+ } catch (error) {
147
+ if (error.code === "ENOENT") {
148
+ return "";
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+
154
+ function git(args, options = {}) {
155
+ const result = spawnSync("git", args, {
156
+ cwd: root,
157
+ encoding: "utf8"
158
+ });
159
+
160
+ if (result.status !== 0 && !(options.allowEmpty && result.status === 128)) {
161
+ const detail = result.stderr.trim() || result.stdout.trim() || `git exited with ${result.status}`;
162
+ throw new Error(detail);
163
+ }
164
+
165
+ return result.stdout.trim();
166
+ }
167
+
168
+ function escapeRegExp(value) {
169
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
170
+ }
171
+
172
+ function parseArgs(argv) {
173
+ const options = { write: false };
174
+ for (let index = 0; index < argv.length; index += 1) {
175
+ const arg = argv[index];
176
+ if (arg === "--write") {
177
+ options.write = true;
178
+ } else if (arg === "--version") {
179
+ options.version = argv[++index];
180
+ } else if (arg === "--from") {
181
+ options.from = argv[++index];
182
+ } else if (arg === "--to") {
183
+ options.to = argv[++index];
184
+ } else if (arg === "--date") {
185
+ options.date = argv[++index];
186
+ } else if (arg === "-h" || arg === "--help") {
187
+ options.help = true;
188
+ } else {
189
+ throw new Error(`Unknown option ${arg}`);
190
+ }
191
+ }
192
+ return options;
193
+ }
194
+
195
+ function printHelp() {
196
+ console.log(`Usage: node scripts/changelog.js [options]
197
+
198
+ Options:
199
+ --write Update CHANGELOG.md instead of printing the next entry
200
+ --version <version> Release version to write; defaults to package.json
201
+ --from <ref> Starting git ref; defaults to latest v* tag
202
+ --to <ref> Ending git ref; defaults to HEAD
203
+ --date <date> Release date; defaults to today
204
+ `);
205
+ }
206
+
207
+ async function main() {
208
+ const options = parseArgs(process.argv.slice(2));
209
+ if (options.help) {
210
+ printHelp();
211
+ return;
212
+ }
213
+
214
+ const result = await updateChangelog(options);
215
+ if (options.write) {
216
+ console.log(`Updated ${path.relative(root, result.changelogPath)} for ${result.version} with ${result.commits.length} commit(s).`);
217
+ } else {
218
+ process.stdout.write(result.entry);
219
+ }
220
+ }
221
+
222
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
223
+ main().catch((error) => {
224
+ console.error(error.message);
225
+ process.exitCode = 1;
226
+ });
227
+ }
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { updateChangelog } from "./changelog.js";
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const root = path.resolve(scriptDir, "..");
10
+ const packagePath = path.join(root, "package.json");
11
+ const lockPath = path.join(root, "package-lock.json");
12
+ const constantsPath = path.join(root, "src", "constants.js");
13
+ const trackedReleaseFiles = [
14
+ "package.json",
15
+ "package-lock.json",
16
+ "src/constants.js",
17
+ "CHANGELOG.md"
18
+ ];
19
+
20
+ async function main() {
21
+ const options = parseArgs(process.argv.slice(2));
22
+ if (options.help) {
23
+ printHelp();
24
+ return;
25
+ }
26
+
27
+ const currentVersion = await readPackageVersion();
28
+ const nextVersion = resolveNextVersion(currentVersion, options.bump);
29
+ const tag = `v${nextVersion}`;
30
+
31
+ if (!options.dryRun) {
32
+ assertCleanWorktree();
33
+ }
34
+
35
+ console.log(`Release plan: ${currentVersion} -> ${nextVersion}`);
36
+
37
+ if (options.dryRun) {
38
+ const changelog = await updateChangelog({ version: nextVersion, write: false });
39
+ console.log("\nFiles that would be updated:");
40
+ for (const file of trackedReleaseFiles) {
41
+ console.log(`- ${file}`);
42
+ }
43
+ console.log("\nChangelog entry preview:\n");
44
+ process.stdout.write(changelog.entry);
45
+ return;
46
+ }
47
+
48
+ await updatePackageJson(nextVersion);
49
+ await updatePackageLock(nextVersion);
50
+ await updateConstants(nextVersion);
51
+ await updateChangelog({ version: nextVersion, write: true });
52
+
53
+ if (!options.skipChecks) {
54
+ run("npm", ["run", "lint"]);
55
+ run("npm", ["test"]);
56
+ }
57
+
58
+ git(["add", ...trackedReleaseFiles]);
59
+ git(["commit", "-m", `Release ${tag}`]);
60
+ git(["tag", "-a", tag, "-m", `Release ${tag}`]);
61
+
62
+ if (options.push) {
63
+ const branch = git(["branch", "--show-current"], { capture: true });
64
+ if (!branch) {
65
+ throw new Error("Cannot push from a detached HEAD.");
66
+ }
67
+ git(["push", "origin", branch]);
68
+ git(["push", "origin", tag]);
69
+ }
70
+
71
+ console.log(`Cut ${tag}.`);
72
+ if (!options.push) {
73
+ console.log(`Push with: git push origin HEAD && git push origin ${tag}`);
74
+ }
75
+ }
76
+
77
+ function parseArgs(argv) {
78
+ const options = {
79
+ bump: "patch",
80
+ dryRun: false,
81
+ push: false,
82
+ skipChecks: false
83
+ };
84
+ const positional = [];
85
+
86
+ for (let index = 0; index < argv.length; index += 1) {
87
+ const arg = argv[index];
88
+ if (arg === "--dry-run") {
89
+ options.dryRun = true;
90
+ } else if (arg === "--push") {
91
+ options.push = true;
92
+ } else if (arg === "--skip-checks") {
93
+ options.skipChecks = true;
94
+ } else if (arg === "-h" || arg === "--help") {
95
+ options.help = true;
96
+ } else if (arg.startsWith("-")) {
97
+ throw new Error(`Unknown option ${arg}`);
98
+ } else {
99
+ positional.push(arg);
100
+ }
101
+ }
102
+
103
+ if (positional.length > 1) {
104
+ throw new Error(`Expected one bump argument, received: ${positional.join(", ")}`);
105
+ }
106
+ if (positional[0]) {
107
+ options.bump = positional[0];
108
+ }
109
+
110
+ return options;
111
+ }
112
+
113
+ function resolveNextVersion(currentVersion, bump) {
114
+ const explicit = bump.replace(/^v/, "");
115
+ if (/^\d+\.\d+\.\d+$/.test(explicit)) {
116
+ return explicit;
117
+ }
118
+
119
+ const match = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)$/);
120
+ if (!match) {
121
+ throw new Error(`Cannot bump non-standard version ${currentVersion}. Pass an explicit x.y.z version.`);
122
+ }
123
+
124
+ const parts = match.slice(1).map(Number);
125
+ if (bump === "major") {
126
+ return `${parts[0] + 1}.0.0`;
127
+ }
128
+ if (bump === "minor") {
129
+ return `${parts[0]}.${parts[1] + 1}.0`;
130
+ }
131
+ if (bump === "patch") {
132
+ return `${parts[0]}.${parts[1]}.${parts[2] + 1}`;
133
+ }
134
+
135
+ throw new Error(`Unsupported release bump "${bump}". Use patch, minor, major, or x.y.z.`);
136
+ }
137
+
138
+ async function readPackageVersion() {
139
+ const pkg = JSON.parse(await fs.readFile(packagePath, "utf8"));
140
+ return pkg.version;
141
+ }
142
+
143
+ async function updatePackageJson(version) {
144
+ const pkg = JSON.parse(await fs.readFile(packagePath, "utf8"));
145
+ pkg.version = version;
146
+ await writeJson(packagePath, pkg);
147
+ }
148
+
149
+ async function updatePackageLock(version) {
150
+ const lock = JSON.parse(await fs.readFile(lockPath, "utf8"));
151
+ lock.version = version;
152
+ if (lock.packages?.[""]) {
153
+ lock.packages[""].version = version;
154
+ }
155
+ await writeJson(lockPath, lock);
156
+ }
157
+
158
+ async function updateConstants(version) {
159
+ const content = await fs.readFile(constantsPath, "utf8");
160
+ const next = content.replace(
161
+ /export const PACKAGE_VERSION = "[^"]+";/,
162
+ `export const PACKAGE_VERSION = "${version}";`
163
+ );
164
+ if (next === content) {
165
+ throw new Error("Could not find PACKAGE_VERSION in src/constants.js.");
166
+ }
167
+ await fs.writeFile(constantsPath, next);
168
+ }
169
+
170
+ async function writeJson(filePath, value) {
171
+ await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
172
+ }
173
+
174
+ function assertCleanWorktree() {
175
+ const status = git(["status", "--short"], { capture: true });
176
+ if (status) {
177
+ throw new Error(`Release requires a clean worktree:\n${status}`);
178
+ }
179
+ }
180
+
181
+ function run(command, args) {
182
+ const result = spawnSync(command, args, {
183
+ cwd: root,
184
+ stdio: "inherit"
185
+ });
186
+ if (result.status !== 0) {
187
+ throw new Error(`${command} ${args.join(" ")} failed with ${result.status}`);
188
+ }
189
+ }
190
+
191
+ function git(args, options = {}) {
192
+ const result = spawnSync("git", args, {
193
+ cwd: root,
194
+ encoding: "utf8",
195
+ stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit"
196
+ });
197
+ if (result.status !== 0) {
198
+ const detail = result.stderr?.trim() || result.stdout?.trim() || `git exited with ${result.status}`;
199
+ throw new Error(detail);
200
+ }
201
+ return options.capture ? result.stdout.trim() : "";
202
+ }
203
+
204
+ function printHelp() {
205
+ console.log(`Usage: npm run release -- [patch|minor|major|x.y.z] [options]
206
+
207
+ Cuts a release commit and annotated git tag. It does not publish to npm.
208
+
209
+ Options:
210
+ --dry-run Preview version and changelog changes
211
+ --push Push the current branch and release tag after tagging
212
+ --skip-checks Skip npm run lint and npm test
213
+ `);
214
+ }
215
+
216
+ main().catch((error) => {
217
+ console.error(error.message);
218
+ process.exitCode = 1;
219
+ });
package/src/cli.js CHANGED
@@ -28,7 +28,9 @@ export async function run(argv = process.argv) {
28
28
  .description("Print one rendered agent profile without installing it")
29
29
  .option("-s, --profile <profile>", "Profile slug to use")
30
30
  .option("--skill <profile>", "Compatibility alias for --profile")
31
- .option("-a, --agent <agent>", `Target agent harness (${SUPPORTED_AGENTS.join(", ")})`)
31
+ .option("-a, --agent <agent>", "Agent profile slug to use; accepts a harness name for backward compatibility")
32
+ .option("--harness <harness>", `Target harness (${SUPPORTED_AGENTS.join(", ")})`)
33
+ .option("--target <harness>", "Compatibility alias for --harness")
32
34
  .option("--json", "Output JSON")
33
35
  .option("--home <dir>", "Override HOME for path expansion")
34
36
  .action(async (source = undefined, options) => {
@@ -136,7 +138,9 @@ function addInstallCommand(program, commandName) {
136
138
  .description(commandName === "install" ? "Alias for add" : "Install agent profiles from a source")
137
139
  .option("-g, --global", "Install globally")
138
140
  .option("-p, --project", "Install into the current project (default)")
139
- .option("-a, --agent <agents...>", `Target agent harnesses (${SUPPORTED_AGENTS.join(", ")}, or *)`)
141
+ .option("-a, --agent <agents...>", "Agent profile slugs to install; accepts harness names for backward compatibility")
142
+ .option("--harness <harnesses...>", `Target harnesses (${SUPPORTED_AGENTS.join(", ")}, or *)`)
143
+ .option("--target <harnesses...>", "Compatibility alias for --harness")
140
144
  .option("-s, --profile <profiles...>", "Profile slugs to install (or *)")
141
145
  .option("--skill <profiles...>", "Compatibility alias for --profile")
142
146
  .option("-l, --list", "List available profiles in the source without installing")
package/src/constants.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export const PACKAGE_NAME = "awesome-agents";
2
- export const PACKAGE_VERSION = "0.1.0";
2
+ export const PACKAGE_VERSION = "0.1.1";
3
3
  export const DEFAULT_SOURCE = "pablof7z/touch-grass";
4
4
  export const DEFAULT_AGENT = "codex";
5
5
  export const SUPPORTED_AGENTS = ["codex", "claude-code", "opencode"];
package/src/installer.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
4
+ import { AGENT_ALIASES, DEFAULT_AGENT, SUPPORTED_AGENTS } from "./constants.js";
5
5
  import { contentHash, isGeneratedContent, normalizeAgentList, renderForAgent, resolveTargetPath } from "./renderers.js";
6
6
  import { loadCatalog, materializeSource, splitSourceSpec } from "./source.js";
7
7
  import { readRegistry, registryPath, removeInstall, upsertInstall, writeRegistry } from "./registry.js";
@@ -20,10 +20,9 @@ export async function listAvailable(sourceInput, options = {}) {
20
20
  export async function installFromSource(sourceSpec, options = {}) {
21
21
  const split = splitSourceSpec(sourceSpec);
22
22
  const source = split.source;
23
- const optionProfiles = normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill);
24
- const selectors = split.profile ? [...optionProfiles, split.profile] : optionProfiles;
23
+ const { selectors, harnessInput } = resolveInstallSelection(split, options);
25
24
  const scope = resolveScope(options);
26
- const harnesses = normalizeAgentList(options.agents ?? options.agent, {
25
+ const harnesses = normalizeAgentList(harnessInput, {
27
26
  all: options.all,
28
27
  defaultAgent: DEFAULT_AGENT
29
28
  });
@@ -80,12 +79,12 @@ export async function installFromSource(sourceSpec, options = {}) {
80
79
 
81
80
  export async function useFromSource(sourceSpec, options = {}) {
82
81
  const split = splitSourceSpec(sourceSpec);
83
- const profileSelector = options.profile ?? options.skill ?? split.profile;
82
+ const { profileSelector, harnessInput } = resolveUseSelection(split, options);
84
83
  if (!profileSelector) {
85
84
  throw new Error("Specify a profile with source@profile or --profile <profile>.");
86
85
  }
87
86
 
88
- const harness = normalizeAgentList(options.agent ? [options.agent] : options.agents, {
87
+ const harness = normalizeAgentList(harnessInput, {
89
88
  defaultAgent: DEFAULT_AGENT
90
89
  })[0];
91
90
  const materialized = await materializeSource(split.source, options);
@@ -285,6 +284,72 @@ function normalizeProfileSelectors(values) {
285
284
  .filter(Boolean);
286
285
  }
287
286
 
287
+ function resolveInstallSelection(split, options = {}) {
288
+ const selectors = [
289
+ ...normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill)
290
+ ];
291
+ const explicitHarnessInput = firstDefined(options.harnesses, options.harness, options.targets, options.target);
292
+ const agentValues = normalizeProfileSelectors(options.agents ?? options.agent);
293
+ const agentHarnesses = [];
294
+
295
+ for (const value of agentValues) {
296
+ if (isHarnessSelector(value)) {
297
+ agentHarnesses.push(value);
298
+ } else {
299
+ selectors.push(value);
300
+ }
301
+ }
302
+
303
+ if (split.profile) {
304
+ selectors.push(split.profile);
305
+ }
306
+
307
+ return {
308
+ selectors,
309
+ harnessInput: explicitHarnessInput ?? (agentHarnesses.length > 0 ? agentHarnesses : undefined)
310
+ };
311
+ }
312
+
313
+ function resolveUseSelection(split, options = {}) {
314
+ const selectors = [
315
+ ...normalizeProfileSelectors(options.profiles ?? options.profile ?? options.skills ?? options.skill)
316
+ ];
317
+ const explicitHarnessInput = firstDefined(options.harnesses, options.harness, options.targets, options.target);
318
+ const agentValues = normalizeProfileSelectors(options.agents ?? options.agent);
319
+ const agentHarnesses = [];
320
+
321
+ for (const value of agentValues) {
322
+ if (isHarnessSelector(value)) {
323
+ agentHarnesses.push(value);
324
+ } else {
325
+ selectors.push(value);
326
+ }
327
+ }
328
+
329
+ if (split.profile) {
330
+ selectors.push(split.profile);
331
+ }
332
+
333
+ const uniqueSelectors = [...new Set(selectors)];
334
+ if (uniqueSelectors.length > 1) {
335
+ throw new Error(`Use renders one profile at a time. Received: ${uniqueSelectors.join(", ")}`);
336
+ }
337
+
338
+ return {
339
+ profileSelector: uniqueSelectors[0],
340
+ harnessInput: explicitHarnessInput ?? (agentHarnesses.length > 0 ? agentHarnesses : undefined)
341
+ };
342
+ }
343
+
344
+ function isHarnessSelector(value) {
345
+ const normalized = String(value).toLowerCase();
346
+ return normalized === "*" || AGENT_ALIASES.has(normalized);
347
+ }
348
+
349
+ function firstDefined(...values) {
350
+ return values.find((value) => value !== undefined);
351
+ }
352
+
288
353
  function profileSummary(profile, source) {
289
354
  return {
290
355
  slug: profile.slug,
@@ -1,45 +0,0 @@
1
- # Awesome Agents Flow Notes
2
-
3
- These are factual notes from the implementation request and current scaffold.
4
-
5
- ## Core Direction
6
-
7
- - The package is named `awesome-agents`.
8
- - It should be installable with `npx`.
9
- - Its command structure should mirror `npx skills`, but for agent profiles.
10
- - It should install profiles from `touch-grass`, both local
11
- `/Users/customer/touch-grass` and GitHub `pablof7z/touch-grass`.
12
- - Initial harness targets are Codex, Claude Code, and OpenCode.
13
- - The package should be published after the scaffold works.
14
-
15
- ## Source Model
16
-
17
- - Canonical profiles live at `agents/profiles/*.md`.
18
- - Harness adapters live at `agents/adapters/<harness>/*.md`.
19
- - Current touch-grass profiles include `ios-tester` and `ios-ux-ui-critic`.
20
- - The CLI should preserve canonical profile content and generate harness-specific
21
- install files.
22
-
23
- ## Command Model
24
-
25
- - `add` is the primary install command because `npx skills` uses `add`.
26
- - `install` is an alias because the user explicitly requested an install command.
27
- - `use` renders one profile without installing it.
28
- - `list`, `remove`, and `update` operate from an `awesome-agents` registry so
29
- the CLI manages only its own generated files.
30
- - `init` creates a starter profile source layout.
31
- - Install scope defaults to project to match the `npx skills` mental model.
32
-
33
- ## Safety Constraints
34
-
35
- - Default behavior should not overwrite or delete unmanaged harness files.
36
- - `--dry-run` should be available for install, remove, update, and init flows.
37
- - Tests should exercise fake homes and fixture sources instead of writing to real
38
- user harness directories.
39
-
40
- ## Open Questions
41
-
42
- - Whether future versions should also generate Codex `--profile` config layers in addition to Codex custom agents.
43
- - Whether touch-grass should add native Claude Code and OpenCode adapters instead
44
- of relying on generated defaults.
45
- - Whether future versions should include interactive prompts like `npx skills`.