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 +21 -0
- package/README.md +41 -20
- package/docs/cli.md +28 -0
- package/docs/product/README.md +13 -2
- package/docs/product/command-model.md +49 -0
- package/docs/product/harness-targets.md +51 -0
- package/docs/product/open-questions.md +20 -0
- package/docs/product/product-scope.md +37 -0
- package/docs/product/profile-source-format.md +58 -0
- package/docs/product/safety-and-publishing.md +64 -0
- package/package.json +7 -2
- package/scripts/changelog.js +227 -0
- package/scripts/release.js +219 -0
- package/src/cli.js +6 -2
- package/src/constants.js +1 -1
- package/src/installer.js +71 -6
- package/docs/product/awesome-agents-flow-notes.md +0 -45
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
+
Use the CLI with `npx`:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
+
From this repo during development:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
|
|
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
|
|
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 /
|
|
89
|
-
awesome-agents add /
|
|
90
|
-
awesome-agents add /
|
|
91
|
-
awesome-agents add pablof7z/touch-grass --
|
|
92
|
-
awesome-agents
|
|
93
|
-
awesome-agents
|
|
94
|
-
awesome-agents
|
|
95
|
-
awesome-agents
|
|
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
|
package/docs/product/README.md
CHANGED
|
@@ -8,5 +8,16 @@ unless the user confirms them.
|
|
|
8
8
|
|
|
9
9
|
## Files
|
|
10
10
|
|
|
11
|
-
- `
|
|
12
|
-
|
|
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.
|
|
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
|
-
"
|
|
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>",
|
|
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...>",
|
|
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.
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
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`.
|