acp-vscode 0.3.2 → 0.3.8
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/.github/workflows/ci.yml +2 -0
- package/.github/workflows/release.yml +10 -6
- package/README.md +22 -9
- package/__tests__/commands-actions.test.js +0 -2
- package/__tests__/install-command-multi.test.js +0 -1
- package/package.json +2 -2
- package/scripts/release.sh +89 -0
- package/src/commands/install.js +15 -8
- package/src/commands/list.js +15 -2
- package/src/commands/search.js +11 -4
- package/src/commands/uninstall.js +0 -19
- package/src/fetcher.js +46 -9
- package/src/installer.js +3 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -5,9 +5,9 @@ name: Release
|
|
|
5
5
|
# Then push the tag: `git push --follow-tags` (or `git push origin vX.Y.Z`).
|
|
6
6
|
# - Alternatively, use a release manager like `semantic-release` to automate versioning and changelogs.
|
|
7
7
|
# - This workflow runs when a tag matching `v*.*.*` is pushed. Ensure you create/tag using the v-prefixed semver format.
|
|
8
|
-
# -
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# - This workflow uses Trusted Publishing (OIDC) for npm - no NPM_TOKEN secret needed!
|
|
9
|
+
# - Provenance attestations are automatically generated when using trusted publishing.
|
|
10
|
+
# - The default `GITHUB_TOKEN` is provided automatically by Actions for creating releases.
|
|
11
11
|
|
|
12
12
|
on:
|
|
13
13
|
push:
|
|
@@ -21,6 +21,8 @@ jobs:
|
|
|
21
21
|
permissions:
|
|
22
22
|
contents: write # needed to create a release
|
|
23
23
|
packages: write # not strictly required for npm but useful if you use GitHub Packages
|
|
24
|
+
id-token: write # Required for OIDC
|
|
25
|
+
|
|
24
26
|
steps:
|
|
25
27
|
# Fetch full history so tags and changelog generation work correctly
|
|
26
28
|
- uses: actions/checkout@v4
|
|
@@ -29,15 +31,17 @@ jobs:
|
|
|
29
31
|
- name: Use Node.js
|
|
30
32
|
uses: actions/setup-node@v4
|
|
31
33
|
with:
|
|
32
|
-
node-version: '
|
|
34
|
+
node-version: '22'
|
|
33
35
|
registry-url: 'https://registry.npmjs.org'
|
|
36
|
+
- name: Upgrade npm for trusted publishing
|
|
37
|
+
run: npm install -g npm@latest
|
|
34
38
|
- name: Install
|
|
35
39
|
run: npm ci
|
|
40
|
+
- name: Run linting
|
|
41
|
+
run: npm run lint
|
|
36
42
|
- name: Run tests
|
|
37
43
|
run: npm test
|
|
38
44
|
- name: Publish to npm
|
|
39
|
-
env:
|
|
40
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
41
45
|
run: npm publish
|
|
42
46
|
- name: Create GitHub Release
|
|
43
47
|
# Create a GitHub Release for the pushed tag. Uses the git ref by default.
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# acp-vscode
|
|
2
2
|
|
|
3
|
-
acp-vscode is a small CLI to fetch and install
|
|
3
|
+
acp-vscode is a small CLI to fetch and install agents, prompts, instructions and skills from the GitHub "awesome-copilot" repository into your VS Code workspace or VS Code User profile.
|
|
4
4
|
|
|
5
5
|
Install (when published):
|
|
6
6
|
|
|
@@ -14,9 +14,9 @@ Commands:
|
|
|
14
14
|
- install <workspace|user> [names...]
|
|
15
15
|
- target: `workspace` or `user`
|
|
16
16
|
- names: optional list of ids or names to install (supports `repo:id` form)
|
|
17
|
-
- type: specify with the option `--type <type>` (prompts|
|
|
17
|
+
- type: specify with the option `--type <type>` (prompts|agents|instructions|skills|chatmodes|all). Note: `chatmodes` is legacy/deprecated (use `agents` instead). For backwards compatibility you can still pass the type as the first positional name (e.g. `install workspace prompts p1 p2`). The `install` command also accepts a deliberate typo alias `--referesh` (alias for `--refresh`) to preserve historical behavior.
|
|
18
18
|
- list [type]
|
|
19
|
-
- list items available. type can be `prompts`, `
|
|
19
|
+
- list items available. type can be `prompts`, `agents`, `instructions`, `skills`, `chatmodes` (legacy), or `all`
|
|
20
20
|
- search <query>
|
|
21
21
|
- search across items
|
|
22
22
|
- uninstall <workspace|user> <type> [names...]
|
|
@@ -92,7 +92,7 @@ Troubleshooting
|
|
|
92
92
|
---------------
|
|
93
93
|
|
|
94
94
|
- Cache and stale index
|
|
95
|
-
-
|
|
95
|
+
- By default the CLI writes a disk cache under the user's home directory in `~/.acp/cache/index.json` (30 minute TTL). When running in development or tests the CLI preserves the old behavior and keeps the cache in the current working directory at `./.acp-cache/index.json` to avoid surprising local workflows. If you see stale results or want to force a fresh fetch, remove the cache file and retry:
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
rm -rf .acp-cache
|
|
@@ -126,7 +126,9 @@ You can inject a full, pre-built index via the `ACP_INDEX_JSON` environment vari
|
|
|
126
126
|
{
|
|
127
127
|
"prompts": [{ "id": "p1", "name": "Prompt 1", "repo": "r1", "url": "https://..." }],
|
|
128
128
|
"chatmodes": [],
|
|
129
|
-
"
|
|
129
|
+
"agents": [],
|
|
130
|
+
"instructions": [],
|
|
131
|
+
"skills": []
|
|
130
132
|
}
|
|
131
133
|
```
|
|
132
134
|
|
|
@@ -147,6 +149,17 @@ Example:
|
|
|
147
149
|
|
|
148
150
|
When multiple repos contain files with the same `id`, the fetcher adds an `_conflicts` array to the returned index listing conflicted ids. Consumers will display items as `repo:id` when necessary to disambiguate.
|
|
149
151
|
|
|
152
|
+
Local repo file (acp-repos.json)
|
|
153
|
+
-------------------------------
|
|
154
|
+
|
|
155
|
+
In addition to `ACP_REPOS_JSON` the CLI will look for a file named `acp-repos.json` in the `~/.acp` directory and use it to populate the upstream repo list if the environment variable is not set. This file should contain the same JSON array format as `ACP_REPOS_JSON` and is useful for per-user configuration without exporting environment variables. Precedence when building the repos list is:
|
|
156
|
+
|
|
157
|
+
1. `ACP_REPOS_JSON` environment variable (highest priority)
|
|
158
|
+
2. `~/.acp/acp-repos.json` file (if present)
|
|
159
|
+
3. Built-in default repo (github/awesome-copilot)
|
|
160
|
+
|
|
161
|
+
Note: when running in development or tests the CLI will attempt to read `acp-repos.json` from the current working directory instead of `~/.acp` to keep test fixtures and local development predictable.
|
|
162
|
+
|
|
150
163
|
Dry-run:
|
|
151
164
|
|
|
152
165
|
You can preview what would be installed without writing files using --dry-run:
|
|
@@ -168,8 +181,8 @@ Commands reference
|
|
|
168
181
|
Short reference for each command, key options, and quick examples.
|
|
169
182
|
|
|
170
183
|
- install <workspace|user> [names...]
|
|
171
|
-
- Description: Install prompts/
|
|
172
|
-
- Options: `-t, --type <type>` (prompts|
|
|
184
|
+
- Description: Install prompts/agents/instructions/skills into a workspace or VS Code user profile.
|
|
185
|
+
- Options: `-t, --type <type>` (prompts|agents|instructions|skills|chatmodes|all; `chatmodes` is legacy/deprecated), `--dry-run`, `--referesh` (alias for refresh), `--verbose`
|
|
173
186
|
- Examples:
|
|
174
187
|
- Install all prompts into the current workspace:
|
|
175
188
|
- `acp-vscode install workspace prompts`
|
|
@@ -177,10 +190,10 @@ Short reference for each command, key options, and quick examples.
|
|
|
177
190
|
- `acp-vscode install user --type instructions "Instruction Name"`
|
|
178
191
|
|
|
179
192
|
- list [type]
|
|
180
|
-
- Description: List available items. Type can be `prompts`, `
|
|
193
|
+
- Description: List available items. Type can be `prompts`, `agents`, `instructions`, `skills`, `chatmodes` (legacy), or `all` (default).
|
|
181
194
|
- Options: `-r, --refresh` (clear caches and refetch), `-j, --json`, `--verbose`
|
|
182
195
|
- Examples:
|
|
183
|
-
- `acp-vscode list
|
|
196
|
+
- `acp-vscode list agents`
|
|
184
197
|
- `acp-vscode list --json`
|
|
185
198
|
|
|
186
199
|
- search <query>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "acp-vscode",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "CLI to install GitHub Awesome Copilot
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"description": "CLI to install GitHub Awesome Copilot agents, prompts, instructions, and skills into VS Code workspace or user profile",
|
|
5
5
|
"bin": {
|
|
6
6
|
"acp-vscode": "./bin/acp-vscode.js"
|
|
7
7
|
},
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Release script for acp-vscode
|
|
4
|
+
# Usage: ./scripts/release.sh <major|minor|patch>
|
|
5
|
+
# Example: ./scripts/release.sh minor
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
# Colors for output
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
NC='\033[0m' # No Color
|
|
14
|
+
|
|
15
|
+
# Validate input
|
|
16
|
+
if [ $# -ne 1 ]; then
|
|
17
|
+
echo -e "${RED}Error: Release type required${NC}"
|
|
18
|
+
echo "Usage: ./scripts/release.sh <major|minor|patch>"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
VERSION_TYPE=$1
|
|
23
|
+
|
|
24
|
+
# Validate version type
|
|
25
|
+
if [[ ! $VERSION_TYPE =~ ^(major|minor|patch)$ ]]; then
|
|
26
|
+
echo -e "${RED}Error: Invalid release type '${VERSION_TYPE}'${NC}"
|
|
27
|
+
echo "Valid options: major, minor, patch"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo -e "${YELLOW}Starting release process...${NC}"
|
|
32
|
+
|
|
33
|
+
# Check if working directory is clean
|
|
34
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
35
|
+
echo -e "${RED}Error: Working directory is not clean (uncommitted or untracked changes)${NC}"
|
|
36
|
+
echo "Please commit or stash your changes before releasing."
|
|
37
|
+
git status --short
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
echo -e "${GREEN}✓ Working directory is clean${NC}"
|
|
42
|
+
|
|
43
|
+
# Run tests
|
|
44
|
+
echo -e "${YELLOW}Running tests...${NC}"
|
|
45
|
+
npm test || {
|
|
46
|
+
echo -e "${RED}Error: Tests failed${NC}"
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
echo -e "${GREEN}✓ Tests passed${NC}"
|
|
50
|
+
|
|
51
|
+
# Run linting if available
|
|
52
|
+
if node -p "require('./package.json').scripts?.lint" 2>/dev/null | grep -v "undefined" >/dev/null; then
|
|
53
|
+
echo -e "${YELLOW}Running lint...${NC}"
|
|
54
|
+
if npm run lint; then
|
|
55
|
+
echo -e "${GREEN}✓ Linting passed${NC}"
|
|
56
|
+
else
|
|
57
|
+
echo -e "${RED}Error: Linting failed${NC}"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
else
|
|
61
|
+
echo -e "${YELLOW}⚠ Linting not available (skipping)${NC}"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Display current version
|
|
65
|
+
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
66
|
+
echo -e "${YELLOW}Current version: ${CURRENT_VERSION}${NC}"
|
|
67
|
+
|
|
68
|
+
# Use npm version to bump version and create tag
|
|
69
|
+
echo -e "${YELLOW}Bumping version (${VERSION_TYPE})...${NC}"
|
|
70
|
+
npm version "$VERSION_TYPE"
|
|
71
|
+
|
|
72
|
+
# Get the new version
|
|
73
|
+
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
74
|
+
echo -e "${GREEN}✓ Version bumped to ${NEW_VERSION}${NC}"
|
|
75
|
+
|
|
76
|
+
# Get the tag that was created
|
|
77
|
+
TAG="v${NEW_VERSION}"
|
|
78
|
+
|
|
79
|
+
# Push the tag to remote
|
|
80
|
+
echo -e "${YELLOW}Pushing tag ${TAG} to remote...${NC}"
|
|
81
|
+
git push --follow-tags || {
|
|
82
|
+
echo -e "${RED}Error: Failed to push tag${NC}"
|
|
83
|
+
echo "You may need to push manually with: git push --follow-tags"
|
|
84
|
+
exit 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
echo -e "${GREEN}✓ Tag pushed successfully${NC}"
|
|
88
|
+
echo -e "${GREEN}Release complete! Tag ${TAG} has been pushed.${NC}"
|
|
89
|
+
echo -e "${YELLOW}The GitHub Actions workflow will now build and publish the release.${NC}"
|
package/src/commands/install.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const prompts = require('prompts');
|
|
2
|
-
const { fetchIndex, diskPaths } = require('../fetcher');
|
|
2
|
+
const { fetchIndex, diskPaths, cacheExists } = require('../fetcher');
|
|
3
3
|
const cache = require('../cache');
|
|
4
4
|
const { installFiles } = require('../installer');
|
|
5
5
|
const fs = require('fs-extra');
|
|
@@ -8,7 +8,7 @@ async function performInstall({ target, type, names, options, workspaceDir = pro
|
|
|
8
8
|
// support shorthand: if first arg is a package name (not 'workspace'|'user'),
|
|
9
9
|
// treat it as package-mode and default target to 'workspace'.
|
|
10
10
|
const key = 'index';
|
|
11
|
-
const TYPES = ['prompts','chatmodes','instructions','all'];
|
|
11
|
+
const TYPES = ['prompts','chatmodes','agents','instructions','skills','all'];
|
|
12
12
|
const isTargetValid = (target === 'workspace' || target === 'user');
|
|
13
13
|
let packageMode = false;
|
|
14
14
|
if (!isTargetValid) {
|
|
@@ -40,6 +40,13 @@ async function performInstall({ target, type, names, options, workspaceDir = pro
|
|
|
40
40
|
let index = cache.get(key);
|
|
41
41
|
if (!index) {
|
|
42
42
|
try {
|
|
43
|
+
// Check if disk cache exists to determine if this is a fresh fetch
|
|
44
|
+
const diskCacheExists = await cacheExists();
|
|
45
|
+
|
|
46
|
+
if (!diskCacheExists && !doRefresh) {
|
|
47
|
+
console.log('Updating cache for the first time... (use --refresh/--referesh to force refresh)');
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
index = await fetchIndex();
|
|
44
51
|
cache.set(key, index);
|
|
45
52
|
} catch (err) {
|
|
@@ -48,7 +55,7 @@ async function performInstall({ target, type, names, options, workspaceDir = pro
|
|
|
48
55
|
}
|
|
49
56
|
}
|
|
50
57
|
|
|
51
|
-
const types = (type === 'all' || !type) ? ['prompts','chatmodes','instructions'] : [type];
|
|
58
|
+
const types = (type === 'all' || !type) ? ['prompts','chatmodes','agents','instructions','skills'] : [type];
|
|
52
59
|
let anyFound = false;
|
|
53
60
|
const missingNames = new Set();
|
|
54
61
|
// If multiple types are being considered and names provided, resolve names across types
|
|
@@ -219,8 +226,8 @@ async function performInstall({ target, type, names, options, workspaceDir = pro
|
|
|
219
226
|
continue;
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
// If no specific names requested and the type is chatmodes, prompts, or
|
|
223
|
-
if ((!names || names.length === 0) && (t === 'chatmodes' || t === 'prompts' || t === 'instructions')) {
|
|
229
|
+
// If no specific names requested and the type is chatmodes, agents, prompts, instructions or skills, confirm installing all
|
|
230
|
+
if ((!names || names.length === 0) && (t === 'chatmodes' || t === 'agents' || t === 'prompts' || t === 'instructions' || t === 'skills')) {
|
|
224
231
|
// When running non-interactively (no TTY), auto-confirm so CI/tests proceed
|
|
225
232
|
const nonInteractive = !(process.stdin && process.stdin.isTTY);
|
|
226
233
|
if (!nonInteractive) {
|
|
@@ -252,15 +259,15 @@ async function performInstall({ target, type, names, options, workspaceDir = pro
|
|
|
252
259
|
function installCommand(cli) {
|
|
253
260
|
// Change signature so that names are variadic and type is provided via option to
|
|
254
261
|
// avoid the ambiguity where the second positional arg would be parsed as `type`.
|
|
255
|
-
cli.command('install <target> [names...]', 'Install items into workspace or user profile. target: workspace|user. type: prompts|chatmodes|instructions|all')
|
|
256
|
-
.option('-t, --type <type>', 'Specify type: prompts|chatmodes|instructions|all')
|
|
262
|
+
cli.command('install <target> [names...]', 'Install items into workspace or user profile. target: workspace|user. type: prompts|chatmodes|agents|instructions|skills|all')
|
|
263
|
+
.option('-t, --type <type>', 'Specify type: prompts|chatmodes|agents|instructions|skills|all')
|
|
257
264
|
// Note: --refresh is intentionally not a per-command option for install; use only with list/search
|
|
258
265
|
.option('--referesh', "Alias for --refresh (typo alias)")
|
|
259
266
|
.option('--dry-run', 'Show what would be installed without writing files')
|
|
260
267
|
.action(async (target, names, options) => {
|
|
261
268
|
// names may be undefined or an array. Support legacy positional type in case
|
|
262
269
|
// the user still passed it as the first name (e.g. `install workspace prompts p1`).
|
|
263
|
-
const TYPES = ['prompts','chatmodes','instructions','all'];
|
|
270
|
+
const TYPES = ['prompts','chatmodes','agents','instructions','skills','all'];
|
|
264
271
|
let type = options.type;
|
|
265
272
|
// Normalize names to an array. Some CLI parsers may provide a single
|
|
266
273
|
// name as a string instead of a one-element array. Preserve values.
|
package/src/commands/list.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { fetchIndex, diskPaths } = require('../fetcher');
|
|
1
|
+
const { fetchIndex, diskPaths, cacheExists } = require('../fetcher');
|
|
2
2
|
const cache = require('../cache');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
|
|
@@ -18,14 +18,20 @@ function formatItems(index, filter) {
|
|
|
18
18
|
if (!filter || filter === 'all' || filter === 'chatmodes') {
|
|
19
19
|
if (index.chatmodes) out.push(...index.chatmodes.map(c => ({ type: 'chatmode', id: displayId(c), name: c.name })));
|
|
20
20
|
}
|
|
21
|
+
if (!filter || filter === 'all' || filter === 'agents') {
|
|
22
|
+
if (index.agents) out.push(...index.agents.map(a => ({ type: 'agent', id: displayId(a), name: a.name })));
|
|
23
|
+
}
|
|
21
24
|
if (!filter || filter === 'all' || filter === 'instructions') {
|
|
22
25
|
if (index.instructions) out.push(...index.instructions.map(i => ({ type: 'instruction', id: displayId(i), name: i.name })));
|
|
23
26
|
}
|
|
27
|
+
if (!filter || filter === 'all' || filter === 'skills') {
|
|
28
|
+
if (index.skills) out.push(...index.skills.map(s => ({ type: 'skill', id: displayId(s), name: s.name })));
|
|
29
|
+
}
|
|
24
30
|
return out;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
function listCommand(cli) {
|
|
28
|
-
cli.command('list [type]', 'List available items (prompts, chatmodes, instructions, all)')
|
|
34
|
+
cli.command('list [type]', 'List available items (prompts, chatmodes, agents, instructions, skills, all)')
|
|
29
35
|
.option('-r, --refresh', 'Clear caches and force refresh from remote')
|
|
30
36
|
.option('-j, --json', 'Emit machine-readable JSON output')
|
|
31
37
|
.action(async (type, options) => {
|
|
@@ -52,6 +58,13 @@ function listCommand(cli) {
|
|
|
52
58
|
let index = cache.get(key);
|
|
53
59
|
if (!index) {
|
|
54
60
|
try {
|
|
61
|
+
// Check if disk cache exists to determine if this is a fresh fetch
|
|
62
|
+
const diskCacheExists = await cacheExists();
|
|
63
|
+
|
|
64
|
+
if (!diskCacheExists && !options.refresh) {
|
|
65
|
+
console.log('Updating cache for the first time... (use -r to force refresh)');
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
index = await fetchIndex();
|
|
56
69
|
cache.set(key, index);
|
|
57
70
|
} catch (err) {
|
package/src/commands/search.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const { fetchIndex, diskPaths } = require('../fetcher');
|
|
1
|
+
const { fetchIndex, diskPaths, cacheExists } = require('../fetcher');
|
|
2
2
|
const cache = require('../cache');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
4
|
const { formatListLines, visibleLines } = require('./list');
|
|
5
5
|
|
|
6
6
|
function searchCommand(cli) {
|
|
7
|
-
cli.command('search <query>', 'Search prompts, chatmodes, instructions')
|
|
7
|
+
cli.command('search <query>', 'Search prompts, chatmodes, agents, instructions, skills')
|
|
8
8
|
.option('-r, --refresh', 'Clear caches and force refresh from remote')
|
|
9
9
|
.option('-j, --json', 'Emit machine-readable JSON output')
|
|
10
10
|
.action(async (query, options) => {
|
|
@@ -27,6 +27,13 @@ function searchCommand(cli) {
|
|
|
27
27
|
let index = cache.get(key);
|
|
28
28
|
if (!index) {
|
|
29
29
|
try {
|
|
30
|
+
// Check if disk cache exists to determine if this is a fresh fetch
|
|
31
|
+
const diskCacheExists = await cacheExists();
|
|
32
|
+
|
|
33
|
+
if (!diskCacheExists && !options.refresh) {
|
|
34
|
+
console.log('Updating cache for the first time... (use -r to force refresh)');
|
|
35
|
+
}
|
|
36
|
+
|
|
30
37
|
index = await fetchIndex();
|
|
31
38
|
cache.set(key, index);
|
|
32
39
|
} catch (err) {
|
|
@@ -37,7 +44,7 @@ function searchCommand(cli) {
|
|
|
37
44
|
}
|
|
38
45
|
const q = query.toLowerCase();
|
|
39
46
|
const results = [];
|
|
40
|
-
['prompts','chatmodes','instructions'].forEach(cat => {
|
|
47
|
+
['prompts','chatmodes','agents','instructions','skills'].forEach(cat => {
|
|
41
48
|
const arr = index[cat] || [];
|
|
42
49
|
arr.forEach(item => {
|
|
43
50
|
const name = (item.name || item.id || '').toLowerCase();
|
|
@@ -66,7 +73,7 @@ function searchCommand(cli) {
|
|
|
66
73
|
function searchIndex(index, query) {
|
|
67
74
|
const q = query.toLowerCase();
|
|
68
75
|
const results = [];
|
|
69
|
-
['prompts','chatmodes','instructions'].forEach(cat => {
|
|
76
|
+
['prompts','chatmodes','agents','instructions','skills'].forEach(cat => {
|
|
70
77
|
const arr = index[cat] || [];
|
|
71
78
|
arr.forEach(item => {
|
|
72
79
|
const name = (item.name || item.id || '').toLowerCase();
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
const prompts = require('prompts');
|
|
2
|
-
const cache = require('../cache');
|
|
3
|
-
const { fetchIndex, diskPaths } = require('../fetcher');
|
|
4
|
-
const fs = require('fs-extra');
|
|
5
2
|
const { removeFiles } = require('../installer');
|
|
6
3
|
|
|
7
4
|
function uninstallCommand(cli) {
|
|
@@ -11,22 +8,6 @@ function uninstallCommand(cli) {
|
|
|
11
8
|
.action(async (target, type, names, options) => {
|
|
12
9
|
const workspaceDir = process.cwd();
|
|
13
10
|
if (options && options.verbose) console.log('verbose: starting uninstall', { target, type, names });
|
|
14
|
-
const key = 'index';
|
|
15
|
-
if (options && options.refresh) {
|
|
16
|
-
if (options && options.verbose) console.log('verbose: refresh requested - clearing caches');
|
|
17
|
-
try { cache.del(key); } catch (e) { if (options && options.verbose) console.log('verbose: failed to clear in-memory cache', e.message); }
|
|
18
|
-
try { const { DISK_CACHE_FILE } = diskPaths(); if (fs.existsSync(DISK_CACHE_FILE)) fs.removeSync(DISK_CACHE_FILE); } catch (e) { if (options && options.verbose) console.log('verbose: failed to remove disk cache', e.message); }
|
|
19
|
-
}
|
|
20
|
-
let index = cache.get(key);
|
|
21
|
-
if (!index) {
|
|
22
|
-
try {
|
|
23
|
-
index = await fetchIndex();
|
|
24
|
-
cache.set(key, index);
|
|
25
|
-
} catch (err) {
|
|
26
|
-
console.error('Error fetching index:', err.message);
|
|
27
|
-
return process.exitCode = 2;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
11
|
|
|
31
12
|
const toRemove = names && names.length > 0 ? names : [];
|
|
32
13
|
if (toRemove.length === 0) {
|
package/src/fetcher.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
const cache = require('./cache');
|
|
5
6
|
|
|
7
|
+
// Helper to consistently decide where to store user-level data. In
|
|
8
|
+
// development and test environments prefer process.cwd() for predictable
|
|
9
|
+
// local workflows; otherwise use the user's homedir under ~/.acp.
|
|
10
|
+
function getBaseDir() {
|
|
11
|
+
return (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test')
|
|
12
|
+
? process.cwd()
|
|
13
|
+
: path.join(os.homedir(), '.acp');
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
// Default single upstream repo for backwards compatibility
|
|
7
17
|
const DEFAULT_REPOS = [
|
|
8
18
|
{
|
|
@@ -13,7 +23,11 @@ const DEFAULT_REPOS = [
|
|
|
13
23
|
];
|
|
14
24
|
|
|
15
25
|
function diskPaths() {
|
|
16
|
-
|
|
26
|
+
// In development and tests keep the old behavior (cwd) to avoid surprising
|
|
27
|
+
// local workflows and test expectations. Otherwise store cache under the
|
|
28
|
+
// user's home directory in ~/.acp/cache/index.json
|
|
29
|
+
const baseDir = getBaseDir();
|
|
30
|
+
const DISK_CACHE_DIR = path.join(baseDir, 'cache');
|
|
17
31
|
const DISK_CACHE_FILE = path.join(DISK_CACHE_DIR, 'index.json');
|
|
18
32
|
return { DISK_CACHE_DIR, DISK_CACHE_FILE };
|
|
19
33
|
}
|
|
@@ -70,7 +84,7 @@ async function fetchIndex() {
|
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
|
|
73
|
-
// Build repos list from env or default
|
|
87
|
+
// Build repos list from env, file, or default
|
|
74
88
|
let repos = DEFAULT_REPOS;
|
|
75
89
|
if (process.env.ACP_REPOS_JSON) {
|
|
76
90
|
try {
|
|
@@ -79,6 +93,18 @@ async function fetchIndex() {
|
|
|
79
93
|
} catch (e) {
|
|
80
94
|
// ignore malformed env var and fall back to defaults
|
|
81
95
|
}
|
|
96
|
+
} else {
|
|
97
|
+
// Try reading acp-repos.json from the user acp dir. Respect NODE_ENV handling
|
|
98
|
+
try {
|
|
99
|
+
const baseDir = getBaseDir();
|
|
100
|
+
const repoFile = path.join(baseDir, 'acp-repos.json');
|
|
101
|
+
if (await fs.pathExists(repoFile)) {
|
|
102
|
+
const fileContents = await fs.readJson(repoFile);
|
|
103
|
+
if (Array.isArray(fileContents) && fileContents.length > 0) repos = fileContents;
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// ignore file read/parse errors and fall back to defaults
|
|
107
|
+
}
|
|
82
108
|
}
|
|
83
109
|
|
|
84
110
|
// Helper to parse frontmatter title from file content
|
|
@@ -98,7 +124,7 @@ async function fetchIndex() {
|
|
|
98
124
|
|
|
99
125
|
// fetch each repo's tree and build combined index
|
|
100
126
|
try {
|
|
101
|
-
const combined = { prompts: [], chatmodes: [], instructions: [] };
|
|
127
|
+
const combined = { prompts: [], chatmodes: [], agents: [], instructions: [], skills: [] };
|
|
102
128
|
for (const repo of repos) {
|
|
103
129
|
if (!repo || !repo.treeUrl) continue;
|
|
104
130
|
let res = null;
|
|
@@ -112,9 +138,9 @@ async function fetchIndex() {
|
|
|
112
138
|
continue;
|
|
113
139
|
}
|
|
114
140
|
if (!res || res.status !== 200 || !res.data) continue;
|
|
115
|
-
// If the remote returned a pre-built index object (prompts/chatmodes/instructions),
|
|
141
|
+
// If the remote returned a pre-built index object (prompts/chatmodes/agents/instructions/skills),
|
|
116
142
|
// honor it and return immediately (backwards compatibility + tests).
|
|
117
|
-
if (!Array.isArray(res.data.tree) && (res.data.prompts || res.data.chatmodes || res.data.instructions)) {
|
|
143
|
+
if (!Array.isArray(res.data.tree) && (res.data.prompts || res.data.chatmodes || res.data.agents || res.data.instructions || res.data.skills)) {
|
|
118
144
|
// Remote returned a pre-built index object. Return it unmodified for
|
|
119
145
|
// backwards compatibility and tests which expect the exact shape.
|
|
120
146
|
idx = res.data;
|
|
@@ -129,7 +155,7 @@ async function fetchIndex() {
|
|
|
129
155
|
const matches = tree.filter(t => t.path.startsWith(`${prefix}/`));
|
|
130
156
|
const parts = await Promise.all(matches.map(async t => {
|
|
131
157
|
const file = path.basename(t.path);
|
|
132
|
-
const base = file.replace(/\.prompt\.md$|\.chatmode\.md$|\.instructions?\.md$/i, '').replace(/\.md$/i, '');
|
|
158
|
+
const base = file.replace(/\.prompt\.md$|\.chatmode\.md$|\.agent\.md$|\.instructions?\.md$/i, '').replace(/\.md$/i, '');
|
|
133
159
|
const id = base;
|
|
134
160
|
let name = base.replace(/[-_]+/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
135
161
|
const rawBase = (repo.rawBase || repo.url || '').replace(/\/$/, '');
|
|
@@ -150,12 +176,14 @@ async function fetchIndex() {
|
|
|
150
176
|
|
|
151
177
|
combined.prompts.push(...(await makeEntriesForRepo('prompts')));
|
|
152
178
|
combined.chatmodes.push(...(await makeEntriesForRepo('chatmodes')));
|
|
179
|
+
combined.agents.push(...(await makeEntriesForRepo('agents')));
|
|
153
180
|
combined.instructions.push(...(await makeEntriesForRepo('instructions')));
|
|
181
|
+
combined.skills.push(...(await makeEntriesForRepo('skills')));
|
|
154
182
|
}
|
|
155
183
|
|
|
156
184
|
// detect id conflicts across repos
|
|
157
185
|
const idCounts = new Map();
|
|
158
|
-
for (const cat of ['prompts','chatmodes','instructions']) {
|
|
186
|
+
for (const cat of ['prompts','chatmodes','agents','instructions','skills']) {
|
|
159
187
|
for (const it of combined[cat]) {
|
|
160
188
|
const k = it.id || it.name || '';
|
|
161
189
|
if (!k) continue;
|
|
@@ -169,7 +197,7 @@ async function fetchIndex() {
|
|
|
169
197
|
|
|
170
198
|
// If combined result is empty (no items found for any repo) and we have
|
|
171
199
|
// a stale disk cache, prefer returning the disk payload as a fallback.
|
|
172
|
-
const totalItems = combined.prompts.length + combined.chatmodes.length + combined.instructions.length;
|
|
200
|
+
const totalItems = combined.prompts.length + combined.chatmodes.length + combined.agents.length + combined.instructions.length + combined.skills.length;
|
|
173
201
|
if (totalItems === 0 && disk && disk.payload) return disk.payload;
|
|
174
202
|
|
|
175
203
|
idx = combined;
|
|
@@ -187,4 +215,13 @@ async function fetchIndex() {
|
|
|
187
215
|
}
|
|
188
216
|
}
|
|
189
217
|
|
|
190
|
-
|
|
218
|
+
async function cacheExists() {
|
|
219
|
+
try {
|
|
220
|
+
const { DISK_CACHE_FILE } = diskPaths();
|
|
221
|
+
return await fs.pathExists(DISK_CACHE_FILE);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = { fetchIndex, diskPaths, cacheExists };
|
package/src/installer.js
CHANGED
|
@@ -25,11 +25,13 @@ function getVsCodeUserDir() {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async function installFiles({ items, type, target, workspaceDir }) {
|
|
28
|
-
// type: prompts|chatmodes|instructions
|
|
28
|
+
// type: prompts|chatmodes|agents|instructions|skills
|
|
29
29
|
// Helper to derive filename and extension
|
|
30
30
|
const extForType = t => {
|
|
31
31
|
if (t === 'chatmodes') return '.chatmode.md';
|
|
32
|
+
if (t === 'agents') return '.agent.md';
|
|
32
33
|
if (t === 'instructions' || t === 'instruction') return '.instructions.md';
|
|
34
|
+
if (t === 'skills') return '.md'; // skills typically use plain .md
|
|
33
35
|
return '.prompt.md';
|
|
34
36
|
};
|
|
35
37
|
|