acp-vscode 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.eslintrc.json +12 -0
  2. package/.github/workflows/ci.yml +21 -0
  3. package/.github/workflows/release.yml +50 -0
  4. package/CONTRIBUTING.md +57 -0
  5. package/PRD.md +25 -0
  6. package/README.md +236 -0
  7. package/__tests__/cache.test.js +7 -0
  8. package/__tests__/commands-actions.test.js +58 -0
  9. package/__tests__/commands.test.js +28 -0
  10. package/__tests__/e2e.test.js +40 -0
  11. package/__tests__/fetcher-tree.test.js +55 -0
  12. package/__tests__/fetcher.test.js +122 -0
  13. package/__tests__/install-ambiguous.test.js +14 -0
  14. package/__tests__/install-command-multi.test.js +37 -0
  15. package/__tests__/install-command.test.js +40 -0
  16. package/__tests__/install-extra.test.js +50 -0
  17. package/__tests__/install-multiple-names.test.js +35 -0
  18. package/__tests__/installer-multi.test.js +22 -0
  19. package/__tests__/installer-raw-and-resolve.test.js +62 -0
  20. package/__tests__/installer-user.test.js +19 -0
  21. package/__tests__/installer.test.js +14 -0
  22. package/__tests__/list-format.test.js +31 -0
  23. package/__tests__/list-items-conflict.test.js +15 -0
  24. package/__tests__/list-json.test.js +35 -0
  25. package/__tests__/search-index.test.js +14 -0
  26. package/__tests__/search-json.test.js +34 -0
  27. package/__tests__/uninstall-prefixed.test.js +52 -0
  28. package/__tests__/uninstall.test.js +21 -0
  29. package/bin/acp-vscode.js +42 -0
  30. package/jest.config.cjs +3 -0
  31. package/package.json +44 -0
  32. package/src/cache.js +19 -0
  33. package/src/commands/completion.js +14 -0
  34. package/src/commands/install.js +278 -0
  35. package/src/commands/list.js +145 -0
  36. package/src/commands/search.js +84 -0
  37. package/src/commands/uninstall.js +55 -0
  38. package/src/fetcher.js +190 -0
  39. package/src/installer.js +158 -0
package/.eslintrc.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "env": {
3
+ "node": true,
4
+ "es2021": true,
5
+ "jest": true
6
+ },
7
+ "extends": "eslint:recommended",
8
+ "parserOptions": {
9
+ "ecmaVersion": 12
10
+ },
11
+ "rules": {}
12
+ }
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Use Node.js
15
+ uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '20'
18
+ - name: Install dependencies
19
+ run: npm ci
20
+ - name: Run tests
21
+ run: npm test
@@ -0,0 +1,50 @@
1
+ name: Release
2
+
3
+ # Best practices:
4
+ # - Bump the package version locally with `npm version <major|minor|patch>` which creates a git tag.
5
+ # Then push the tag: `git push --follow-tags` (or `git push origin vX.Y.Z`).
6
+ # - Alternatively, use a release manager like `semantic-release` to automate versioning and changelogs.
7
+ # - This workflow runs when a tag matching `v*.*.*` is pushed. Ensure you create/tag using the v-prefixed semver format.
8
+ # - Secrets required:
9
+ # - `NPM_TOKEN` (used for npm publish)
10
+ # - The default `GITHUB_TOKEN` is provided automatically by Actions for creating releases.
11
+
12
+ on:
13
+ push:
14
+ tags:
15
+ - 'v*.*.*'
16
+
17
+ jobs:
18
+ publish:
19
+ runs-on: ubuntu-latest
20
+ # Minimal permissions required for publishing to npm and creating a release
21
+ permissions:
22
+ contents: write # needed to create a release
23
+ packages: write # not strictly required for npm but useful if you use GitHub Packages
24
+ steps:
25
+ # Fetch full history so tags and changelog generation work correctly
26
+ - uses: actions/checkout@v4
27
+ with:
28
+ fetch-depth: 0
29
+ - name: Use Node.js
30
+ uses: actions/setup-node@v4
31
+ with:
32
+ node-version: '20'
33
+ registry-url: 'https://registry.npmjs.org'
34
+ - name: Install
35
+ run: npm ci
36
+ - name: Run tests
37
+ run: npm test
38
+ - name: Publish to npm
39
+ env:
40
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
41
+ run: npm publish
42
+ - name: Create GitHub Release
43
+ # Create a GitHub Release for the pushed tag. Uses the git ref by default.
44
+ uses: ncipollo/release-action@v1
45
+ with:
46
+ # Generate release notes automatically based on merged PRs and commits
47
+ generateReleaseNotes: true
48
+ # Use the tag from the push event (no need to pass 'tag')
49
+ env:
50
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,57 @@
1
+ ## Contributing
2
+
3
+ Thanks for contributing! This file explains how to run tests and the CLI locally.
4
+
5
+ Development setup
6
+ -----------------
7
+
8
+ - Install dependencies:
9
+
10
+ ```bash
11
+ npm install
12
+ ```
13
+
14
+ - Run the CLI locally (help):
15
+
16
+ ```bash
17
+ node ./bin/acp-vscode.js --help
18
+ ```
19
+
20
+ Running tests
21
+ -------------
22
+
23
+ - Run the test suite with Jest:
24
+
25
+ ```bash
26
+ npm test
27
+ ```
28
+
29
+ - Tests use `ACP_INDEX_JSON` and other environment variables in some cases. To run tests or manual checks offline, generate a JSON index and set `ACP_INDEX_JSON` before running the command:
30
+
31
+ ```bash
32
+ export ACP_INDEX_JSON='{"prompts":[],"chatmodes":[],"instructions":[]}'
33
+ npm test
34
+ ```
35
+
36
+ Key notes for contributors
37
+ --------------------------
38
+ - The fetcher writes a small on-disk cache to `./.acp-cache/index.json`. Tests may create or read this file. If you see stale results during development, remove the directory:
39
+
40
+ ```bash
41
+ rm -rf .acp-cache
42
+ ```
43
+
44
+ - The CLI supports `ACP_REPOS_JSON` to override the upstream repo list when fetching.
45
+
46
+ - Keep changes small and unit-tested. There are unit and integration tests under `__tests__/`.
47
+
48
+ Submitting a PR
49
+ ----------------
50
+
51
+ - Open a pull request against `main` with an explanatory title and tests for behavior changes.
52
+ - The repository is configured to run tests on PRs. Ensure tests pass locally before opening the PR.
53
+
54
+ Contact
55
+ -------
56
+
57
+ If you need help, open an issue on the repository or mention the maintainers in your PR.
package/PRD.md ADDED
@@ -0,0 +1,25 @@
1
+ # Product Requirements Document: acp-vscode
2
+
3
+ Goal: create an npm CLI (acp-vscode) to fetch prompts, chatmodes, and instructions from https://github.com/github/awesome-copilot and install them into VS Code workspace or user profile.
4
+
5
+ Requirements:
6
+ - fetch resources from the GitHub repo and cache index for 30 minutes (in-memory and on-disk)
7
+ - install into workspace (`.github/chatmodes`, `.github/prompts`) or VS Code user profile (prompts folder under the VS Code User directory, or `.github/*` under VS Code User for chatmodes/instructions)
8
+ - list available items and filter by type
9
+ - search items using cached index
10
+ - install single or multiple instruction files by name
11
+ - include help, tests, CI (GitHub Actions), README, and publish-ready `package.json`
12
+
13
+ Additional Notes:
14
+ - Provide `--dry-run` mode on install to preview files without writing
15
+ - Add release workflow that publishes to npm when a semantic tag (vMAJOR.MINOR.PATCH) is pushed; requires the `NPM_TOKEN` secret
16
+
17
+ - Add an `uninstall` command to remove installed items from workspace or user profile
18
+ - Require explicit confirmation for user-targeted uninstall or allow `--yes` to bypass confirmation
19
+
20
+ Behavior and implementation details
21
+
22
+ The PRD should describe high-level goals and acceptance criteria. For concrete usage, examples, and troubleshooting steps (cache removal, env var usage, completion examples), see the `README.md` which contains command examples and diagnostic tips.
23
+
24
+
25
+ Non-goals: automatic PRs, editor integrations (this is CLI-only)
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # acp-vscode
2
+
3
+ acp-vscode is a small CLI to fetch and install chatmodes, prompts and instructions from the GitHub "awesome-copilot" repository into your VS Code workspace or VS Code User profile.
4
+
5
+ Install (when published):
6
+
7
+ ```bash
8
+ npm install -g acp-vscode
9
+ # or run locally
10
+ node ./bin/acp-vscode.js --help
11
+ ```
12
+
13
+ Commands:
14
+ - install <workspace|user> [names...]
15
+ - target: `workspace` or `user`
16
+ - names: optional list of ids or names to install (supports `repo:id` form)
17
+ - type: specify with the option `--type <type>` (prompts|chatmodes|instructions|all). For backwards compatibility you can still pass the type as the first positional name (e.g. `install workspace prompts p1 p2`). Note: the `install` command also accepts a deliberate typo alias `--referesh` (alias for `--refresh`) to preserve historical behavior.
18
+ - list [type]
19
+ - list items available. type can be `prompts`, `chatmodes`, `instructions`, or `all`
20
+ - search <query>
21
+ - search across items
22
+ - uninstall <workspace|user> <type> [names...]
23
+ - remove installed files from workspace or user profile; use `--yes` to skip confirmation when targeting `user`.
24
+ - completion [shell]
25
+ - print a simple shell completion script for `bash` or `zsh` (default `bash`)
26
+
27
+ Output formats
28
+ ---------------
29
+
30
+ Both `list` and `search` support a machine-readable JSON output via the `--json` (or `-j`) flag. When provided the commands will emit an array of items (objects with `type`, `id`, and `name`) instead of the human-friendly table.
31
+
32
+ Example (JSON):
33
+
34
+ ```bash
35
+ acp-vscode list prompts --json
36
+ acp-vscode search "find me" --json
37
+ ```
38
+
39
+ Examples:
40
+
41
+ Install all prompts to workspace:
42
+
43
+ ```bash
44
+ acp-vscode install workspace prompts
45
+ ```
46
+
47
+ Install specific instructions to user profile (preferred):
48
+
49
+ ```bash
50
+ acp-vscode install user --type instructions "Instruction Name"
51
+ ```
52
+
53
+ Or (legacy positional type):
54
+
55
+ ```bash
56
+ acp-vscode install user instructions "Instruction Name"
57
+ ```
58
+
59
+ Completion examples
60
+ -------------------
61
+
62
+ Print a `bash` completion helper and save it for interactive use:
63
+
64
+ ```bash
65
+ acp-vscode completion bash > /etc/bash_completion.d/acp-vscode
66
+ # or source it in your shell for testing
67
+ acp-vscode completion bash | source /dev/stdin
68
+ ```
69
+
70
+ For `zsh` add the script snippet to your `.zshrc` (the command prints a small helper function):
71
+
72
+ ```bash
73
+ acp-vscode completion zsh >> ~/.zshrc
74
+ ```
75
+
76
+ Uninstall examples
77
+ ------------------
78
+
79
+ Remove two prompts from workspace:
80
+
81
+ ```bash
82
+ acp-vscode uninstall workspace prompts one two
83
+ ```
84
+
85
+ Remove a prompt from the user profile without confirmation:
86
+
87
+ ```bash
88
+ acp-vscode uninstall user prompts my-prompt --yes
89
+ ```
90
+
91
+ Troubleshooting
92
+ ---------------
93
+
94
+ - Cache and stale index
95
+ - The CLI writes a disk cache at `./.acp-cache/index.json` (30 minute TTL). If you see stale results or want to force a fresh fetch, remove the cache file and retry:
96
+
97
+ ```bash
98
+ rm -rf .acp-cache
99
+ acp-vscode list --refresh
100
+ ```
101
+
102
+ - Offline testing / injecting a local index
103
+ - For tests or offline usage you can set `ACP_INDEX_JSON` to a JSON string representing the index. This bypasses network fetching entirely and the CLI will use the provided index verbatim.
104
+
105
+ - Multiple upstream repos
106
+ - To index multiple repos set `ACP_REPOS_JSON` to a JSON array of repo descriptors. Example:
107
+
108
+ ```json
109
+ [ { "id": "r1", "treeUrl": "https://api.github.com/repos/org/repo1/git/trees/main?recursive=1", "rawBase": "https://raw.githubusercontent.com/org/repo1/main" } ]
110
+ ```
111
+
112
+ - Verbose logging
113
+ - Add `--verbose` to commands to see extra diagnostic messages during fetch, cache clearing, and install/uninstall operations.
114
+
115
+ Cache: the CLI caches the fetched GitHub index in-memory and on-disk for 30 minutes to reduce network calls. The on-disk cache is stored under the current working directory in `.acp-cache/index.json`.
116
+
117
+ Configuration (environment variables)
118
+
119
+ Environment variables
120
+
121
+ ACP_INDEX_JSON
122
+
123
+ You can inject a full, pre-built index via the `ACP_INDEX_JSON` environment variable. This should be a JSON string representing the index shape the fetcher returns, for example:
124
+
125
+ ```json
126
+ {
127
+ "prompts": [{ "id": "p1", "name": "Prompt 1", "repo": "r1", "url": "https://..." }],
128
+ "chatmodes": [],
129
+ "instructions": []
130
+ }
131
+ ```
132
+
133
+ This is useful for tests or offline runs. When present, the fetcher will parse and return this value verbatim.
134
+
135
+ ACP_REPOS_JSON
136
+
137
+ To support multiple upstream repos, set `ACP_REPOS_JSON` to a JSON array describing the repositories to index. Each repo object should contain at least an `id` and a `treeUrl`. Optionally include `rawBase` (the base URL to fetch raw file contents).
138
+
139
+ Example:
140
+
141
+ ```json
142
+ [
143
+ { "id": "r1", "treeUrl": "https://api.github.com/repos/org/repo1/git/trees/main?recursive=1", "rawBase": "https://raw.githubusercontent.com/org/repo1/main" },
144
+ { "id": "r2", "treeUrl": "https://api.github.com/repos/org/repo2/git/trees/main?recursive=1", "rawBase": "https://raw.githubusercontent.com/org/repo2/main" }
145
+ ]
146
+ ```
147
+
148
+ 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
+
150
+ Dry-run:
151
+
152
+ You can preview what would be installed without writing files using --dry-run:
153
+
154
+ ```bash
155
+ acp-vscode install workspace prompts --dry-run
156
+ ```
157
+
158
+ Other notes
159
+
160
+ Global flags
161
+
162
+ - `--verbose` enables extra logging across commands.
163
+ - `--refresh` is a global top-level flag but currently only applied by the `list` and `search` commands to force clearing in-memory and on-disk caches. The `install` command accepts a `--referesh` alias (typo preserved) which also triggers cache clearing when provided to `install`.
164
+
165
+ Commands reference
166
+ ------------------
167
+
168
+ Short reference for each command, key options, and quick examples.
169
+
170
+ - install <workspace|user> [names...]
171
+ - Description: Install prompts/chatmodes/instructions into a workspace or VS Code user profile.
172
+ - Options: `-t, --type <type>` (prompts|chatmodes|instructions|all), `--dry-run`, `--referesh` (alias for refresh), `--verbose`
173
+ - Examples:
174
+ - Install all prompts into the current workspace:
175
+ - `acp-vscode install workspace prompts`
176
+ - Install instruction by name into user profile (preferred):
177
+ - `acp-vscode install user --type instructions "Instruction Name"`
178
+
179
+ - list [type]
180
+ - Description: List available items. Type can be `prompts`, `chatmodes`, `instructions`, or `all` (default).
181
+ - Options: `-r, --refresh` (clear caches and refetch), `-j, --json`, `--verbose`
182
+ - Examples:
183
+ - `acp-vscode list chatmodes`
184
+ - `acp-vscode list --json`
185
+
186
+ - search <query>
187
+ - Description: Search the index for matching items (name, id or content).
188
+ - Options: `-r, --refresh`, `-j, --json`, `--verbose`
189
+ - Examples:
190
+ - `acp-vscode search "temperature"`
191
+
192
+ - uninstall <workspace|user> <type> [names...]
193
+ - Description: Remove installed files from workspace or user profile. When targeting `user` you'll be prompted for confirmation unless you pass `--yes`.
194
+ - Options: `--yes`, `--verbose`
195
+ - Examples:
196
+ - `acp-vscode uninstall workspace prompts one two`
197
+
198
+ - completion [shell]
199
+ - Description: Print a small shell completion snippet for `bash` or `zsh`.
200
+ - Examples:
201
+ - `acp-vscode completion bash`
202
+
203
+
204
+ Publishing:
205
+
206
+ This repository includes a release workflow that publishes to npm when a tag like v0.1.0 is pushed. You must add an `NPM_TOKEN` secret in the repository settings for the workflow to authenticate with npm.
207
+
208
+ Publish checklist:
209
+
210
+ 1. Update `package.json` fields: `version`, `repository.url`, `bugs.url`, `author`.
211
+ 2. Create a repo secret `NPM_TOKEN` in GitHub (Settings → Secrets → Actions). Generate the token with npm's access token UI.
212
+ 3. Create a release tag and push it, e.g.:
213
+
214
+ ```bash
215
+ git tag v0.1.0
216
+ git push origin v0.1.0
217
+ ```
218
+
219
+ 4. The release workflow will run tests and publish the package to npm on success.
220
+
221
+ Uninstall and confirmation:
222
+
223
+ To remove installed files:
224
+
225
+ ```bash
226
+ acp-vscode uninstall workspace prompts one two
227
+ ```
228
+
229
+ If you're uninstalling from the VS Code user profile, the CLI will prompt for confirmation. Use `--yes` to skip the prompt:
230
+
231
+ ```bash
232
+ acp-vscode uninstall user prompts one --yes
233
+ ```
234
+
235
+
236
+
@@ -0,0 +1,7 @@
1
+ const cache = require('../src/cache');
2
+
3
+ test('cache set and get', () => {
4
+ cache.set('x', { a: 1 });
5
+ const v = cache.get('x');
6
+ expect(v).toEqual({ a: 1 });
7
+ });
@@ -0,0 +1,58 @@
1
+ const cache = require('../src/cache');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+
5
+ function makeCli() {
6
+ const cli = {
7
+ _action: null,
8
+ command() { return cli; },
9
+ option() { return cli; },
10
+ action(fn) { cli._action = fn; return cli; }
11
+ };
12
+ return cli;
13
+ }
14
+
15
+ describe('command actions (search, list, install)', () => {
16
+ afterEach(() => {
17
+ cache.del('index');
18
+ delete process.env.ACP_INDEX_JSON;
19
+ });
20
+
21
+ test('searchCommand action prints matches and respects ACP_INDEX_JSON', async () => {
22
+ process.env.ACP_INDEX_JSON = JSON.stringify({ prompts: [{ id: 's1', name: 'SearchMe', repo: 'r1' }], chatmodes: [], instructions: [], _conflicts: [] });
23
+ const stubCli = makeCli();
24
+ const sc = require('../src/commands/search');
25
+ sc.searchCommand(stubCli);
26
+ const logs = [];
27
+ jest.spyOn(console, 'log').mockImplementation((...args) => logs.push(args.join(' ')));
28
+ await stubCli._action('searchme', { refresh: true, verbose: false });
29
+ expect(logs.some(l => l.includes('s1'))).toBe(true);
30
+ console.log.mockRestore();
31
+ });
32
+
33
+ test('listCommand action prints header and items', async () => {
34
+ process.env.ACP_INDEX_JSON = JSON.stringify({ prompts: [{ id: 'p1', name: 'Prompt One', repo: 'r1' }], chatmodes: [], instructions: [], _conflicts: [] });
35
+ const stubCli = makeCli();
36
+ const lc = require('../src/commands/list');
37
+ lc.listCommand(stubCli);
38
+ const logs = [];
39
+ jest.spyOn(console, 'log').mockImplementation((...args) => logs.push(args.join(' ')));
40
+ await stubCli._action('prompts', { refresh: true, verbose: false });
41
+ // The printed output should include a header line with Type and ID
42
+ expect(logs.some(l => /Type\s+ID/.test(l))).toBe(true);
43
+ console.log.mockRestore();
44
+ });
45
+
46
+ test('installCommand action supports dry-run and type option', async () => {
47
+ process.env.ACP_INDEX_JSON = JSON.stringify({ prompts: [{ id: 'p1', name: 'P1', repo: 'r1', content: 'x' }], chatmodes: [], instructions: [], _conflicts: [] });
48
+ const stubCli = makeCli();
49
+ const ic = require('../src/commands/install');
50
+ ic.installCommand(stubCli);
51
+ const logs = [];
52
+ jest.spyOn(console, 'log').mockImplementation((...args) => logs.push(args.join(' ')));
53
+ await stubCli._action('workspace', ['p1'], { type: 'prompts', 'dry-run': true });
54
+ expect(logs.some(l => l.includes('[dry-run]'))).toBe(true);
55
+ console.log.mockRestore();
56
+ });
57
+
58
+ });
@@ -0,0 +1,28 @@
1
+ jest.mock('../src/fetcher');
2
+ const { fetchIndex } = require('../src/fetcher');
3
+ const cache = require('../src/cache');
4
+ const { listItems } = require('../src/commands/list');
5
+ const { searchIndex } = require('../src/commands/search');
6
+
7
+ const FIXTURE_INDEX = {
8
+ prompts: [{ id: 'p1', name: 'Prompt One' }],
9
+ chatmodes: [{ id: 'c1', name: 'Chat Mode One' }],
10
+ instructions: [{ id: 'i1', name: 'Instruction One' }]
11
+ };
12
+
13
+ beforeEach(() => {
14
+ fetchIndex.mockResolvedValue(FIXTURE_INDEX);
15
+ cache.del('index');
16
+ });
17
+
18
+ test('listItems returns items', async () => {
19
+ const items = listItems(FIXTURE_INDEX);
20
+ expect(items.find(i => i.type === 'prompt')).toBeTruthy();
21
+ expect(items.find(i => i.type === 'chatmode')).toBeTruthy();
22
+ expect(items.find(i => i.type === 'instruction')).toBeTruthy();
23
+ });
24
+
25
+ test('searchIndex finds query', async () => {
26
+ const results = searchIndex(FIXTURE_INDEX, 'One');
27
+ expect(results.length).toBeGreaterThanOrEqual(3);
28
+ });
@@ -0,0 +1,40 @@
1
+ const { execFileSync } = require('child_process');
2
+ const fs = require('fs-extra');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const CLI = path.join(__dirname, '..', 'bin', 'acp-vscode.js');
7
+
8
+ describe('e2e CLI', () => {
9
+ const tmp = path.join(os.tmpdir(), `acp-e2e-${Date.now()}`);
10
+ beforeAll(async () => {
11
+ await fs.ensureDir(tmp);
12
+ });
13
+ afterAll(async () => {
14
+ await fs.remove(tmp).catch(() => {});
15
+ });
16
+
17
+ test('install workspace prompts writes only to workspace and cleans up', () => {
18
+ const index = { prompts: [{ id: 'p1', name: 'p1', content: { body: 'ok' } }] };
19
+ const env = Object.assign({}, process.env, { ACP_INDEX_JSON: JSON.stringify(index) });
20
+ // run install pointing at tmp as cwd
21
+ execFileSync('node', [CLI, 'install', 'workspace', 'prompts'], { cwd: tmp, env });
22
+ const installedDir = path.join(tmp, '.github', 'prompts');
23
+ expect(fs.existsSync(installedDir)).toBe(true);
24
+ const files = fs.readdirSync(installedDir);
25
+ expect(files.length).toBeGreaterThan(0);
26
+ // ensure VS Code user profile was not touched (we won't check actual user dir), just ensure cwd-based user didn't receive files
27
+ const userPrompts = path.join(tmp, 'prompts');
28
+ expect(fs.existsSync(userPrompts)).toBe(false);
29
+ // cleanup
30
+ fs.removeSync(installedDir);
31
+ expect(fs.existsSync(installedDir)).toBe(false);
32
+ });
33
+
34
+ test('unknown option shows friendly error', () => {
35
+ const { spawnSync } = require('child_process');
36
+ const res = spawnSync('node', [CLI, '--no-such-option'], { encoding: 'utf8' });
37
+ const out = `${res.stdout || ''}${res.stderr || ''}${res.error ? res.error.message : ''}`;
38
+ expect(out).toMatch(/Run `acp-vscode --help`/);
39
+ });
40
+ });
@@ -0,0 +1,55 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const axios = require('axios');
5
+ jest.mock('axios');
6
+
7
+ let fetcher;
8
+ let fetchIndex;
9
+ let diskPaths;
10
+
11
+ describe('fetcher tree -> index conversion', () => {
12
+ const tmp = path.join(os.tmpdir(), `acp-fetcher-tree-${Date.now()}`);
13
+ const origCwd = process.cwd();
14
+
15
+ beforeAll(async () => {
16
+ await fs.ensureDir(tmp);
17
+ process.chdir(tmp);
18
+ // require after chdir so diskPaths uses tmp cwd
19
+ fetcher = require('../src/fetcher');
20
+ fetchIndex = fetcher.fetchIndex;
21
+ diskPaths = fetcher.diskPaths;
22
+ });
23
+
24
+ afterAll(async () => {
25
+ process.chdir(origCwd);
26
+ await fs.remove(tmp);
27
+ });
28
+
29
+ beforeEach(async () => {
30
+ const { DISK_CACHE_DIR } = diskPaths();
31
+ await fs.remove(DISK_CACHE_DIR).catch(() => {});
32
+ axios.get.mockReset();
33
+ });
34
+
35
+ test('constructs index from git tree', async () => {
36
+ const tree = [
37
+ { path: 'prompts/p1.prompt.md', type: 'blob' },
38
+ { path: 'chatmodes/c1.chatmode.md', type: 'blob' },
39
+ { path: 'instructions/i1.instructions.md', type: 'blob' }
40
+ ];
41
+ axios.get.mockResolvedValue({ status: 200, data: { tree } });
42
+
43
+ const idx = await fetchIndex();
44
+
45
+ expect(idx).toBeTruthy();
46
+ expect(Array.isArray(idx.prompts)).toBe(true);
47
+ expect(Array.isArray(idx.chatmodes)).toBe(true);
48
+ expect(Array.isArray(idx.instructions)).toBe(true);
49
+
50
+ expect(idx.prompts[0].path).toBe('prompts/p1.prompt.md');
51
+ expect(idx.prompts[0].url).toBe('https://raw.githubusercontent.com/github/awesome-copilot/main/prompts/p1.prompt.md');
52
+ expect(idx.chatmodes[0].path).toBe('chatmodes/c1.chatmode.md');
53
+ expect(idx.instructions[0].path).toBe('instructions/i1.instructions.md');
54
+ });
55
+ });