@skillsmith/cli 0.5.11 → 0.6.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 (115) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +92 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/commands/audit-collisions.d.ts +71 -0
  5. package/dist/src/commands/audit-collisions.d.ts.map +1 -0
  6. package/dist/src/commands/audit-collisions.js +300 -0
  7. package/dist/src/commands/audit-collisions.js.map +1 -0
  8. package/dist/src/commands/audit.d.ts +27 -2
  9. package/dist/src/commands/audit.d.ts.map +1 -1
  10. package/dist/src/commands/audit.js +70 -10
  11. package/dist/src/commands/audit.js.map +1 -1
  12. package/dist/src/commands/audit.test.js +2 -2
  13. package/dist/src/commands/audit.test.js.map +1 -1
  14. package/dist/src/commands/config.d.ts +55 -0
  15. package/dist/src/commands/config.d.ts.map +1 -0
  16. package/dist/src/commands/config.js +211 -0
  17. package/dist/src/commands/config.js.map +1 -0
  18. package/dist/src/commands/create.d.ts +3 -3
  19. package/dist/src/commands/create.d.ts.map +1 -1
  20. package/dist/src/commands/create.js +11 -9
  21. package/dist/src/commands/create.js.map +1 -1
  22. package/dist/src/commands/diff.js +2 -2
  23. package/dist/src/commands/diff.js.map +1 -1
  24. package/dist/src/commands/import-local.d.ts +21 -0
  25. package/dist/src/commands/import-local.d.ts.map +1 -0
  26. package/dist/src/commands/import-local.helpers.d.ts +28 -0
  27. package/dist/src/commands/import-local.helpers.d.ts.map +1 -0
  28. package/dist/src/commands/import-local.helpers.js +166 -0
  29. package/dist/src/commands/import-local.helpers.js.map +1 -0
  30. package/dist/src/commands/import-local.js +265 -0
  31. package/dist/src/commands/import-local.js.map +1 -0
  32. package/dist/src/commands/import-local.types.d.ts +59 -0
  33. package/dist/src/commands/import-local.types.d.ts.map +1 -0
  34. package/dist/src/commands/import-local.types.js +2 -0
  35. package/dist/src/commands/import-local.types.js.map +1 -0
  36. package/dist/src/{import.d.ts → commands/import.d.ts} +11 -0
  37. package/dist/src/commands/import.d.ts.map +1 -0
  38. package/dist/src/{import.js → commands/import.js} +36 -2
  39. package/dist/src/commands/import.js.map +1 -0
  40. package/dist/src/commands/index.d.ts +3 -0
  41. package/dist/src/commands/index.d.ts.map +1 -1
  42. package/dist/src/commands/index.js +6 -0
  43. package/dist/src/commands/index.js.map +1 -1
  44. package/dist/src/commands/install-skill.d.ts.map +1 -1
  45. package/dist/src/commands/install-skill.js +6 -3
  46. package/dist/src/commands/install-skill.js.map +1 -1
  47. package/dist/src/commands/install.d.ts.map +1 -1
  48. package/dist/src/commands/install.js +71 -2
  49. package/dist/src/commands/install.js.map +1 -1
  50. package/dist/src/commands/manage.d.ts.map +1 -1
  51. package/dist/src/commands/manage.js +13 -0
  52. package/dist/src/commands/manage.js.map +1 -1
  53. package/dist/src/commands/recommend.helpers.d.ts +5 -1
  54. package/dist/src/commands/recommend.helpers.d.ts.map +1 -1
  55. package/dist/src/commands/recommend.helpers.js +9 -3
  56. package/dist/src/commands/recommend.helpers.js.map +1 -1
  57. package/dist/src/config.d.ts +6 -1
  58. package/dist/src/config.d.ts.map +1 -1
  59. package/dist/src/config.js +8 -2
  60. package/dist/src/config.js.map +1 -1
  61. package/dist/src/index.d.ts +1 -1
  62. package/dist/src/index.js +10 -28
  63. package/dist/src/index.js.map +1 -1
  64. package/dist/src/templates/mcp-server.template.d.ts +1 -1
  65. package/dist/src/templates/mcp-server.template.d.ts.map +1 -1
  66. package/dist/src/templates/mcp-server.template.js +13 -13
  67. package/dist/src/templates/mcp-server.template.js.map +1 -1
  68. package/dist/src/templates/mcp-server.template.snippets.d.ts +51 -0
  69. package/dist/src/templates/mcp-server.template.snippets.d.ts.map +1 -0
  70. package/dist/src/templates/mcp-server.template.snippets.js +162 -0
  71. package/dist/src/templates/mcp-server.template.snippets.js.map +1 -0
  72. package/dist/src/templates/readme.md.template.d.ts +1 -1
  73. package/dist/src/templates/readme.md.template.d.ts.map +1 -1
  74. package/dist/src/templates/readme.md.template.js +2 -2
  75. package/dist/src/utils/node-version.js +4 -4
  76. package/dist/src/utils/node-version.js.map +1 -1
  77. package/dist/src/utils/skills-directory.d.ts +26 -7
  78. package/dist/src/utils/skills-directory.d.ts.map +1 -1
  79. package/dist/src/utils/skills-directory.js +75 -30
  80. package/dist/src/utils/skills-directory.js.map +1 -1
  81. package/dist/tests/audit-collisions.test.d.ts +42 -0
  82. package/dist/tests/audit-collisions.test.d.ts.map +1 -0
  83. package/dist/tests/audit-collisions.test.js +370 -0
  84. package/dist/tests/audit-collisions.test.js.map +1 -0
  85. package/dist/tests/audit-restructure.test.d.ts +15 -0
  86. package/dist/tests/audit-restructure.test.d.ts.map +1 -0
  87. package/dist/tests/audit-restructure.test.js +151 -0
  88. package/dist/tests/audit-restructure.test.js.map +1 -0
  89. package/dist/tests/config.test.d.ts +33 -0
  90. package/dist/tests/config.test.d.ts.map +1 -0
  91. package/dist/tests/config.test.js +240 -0
  92. package/dist/tests/config.test.js.map +1 -0
  93. package/dist/tests/install-multi-client.test.d.ts +2 -0
  94. package/dist/tests/install-multi-client.test.d.ts.map +1 -0
  95. package/dist/tests/install-multi-client.test.js +202 -0
  96. package/dist/tests/install-multi-client.test.js.map +1 -0
  97. package/dist/tests/integration/import-local.test.d.ts +2 -0
  98. package/dist/tests/integration/import-local.test.d.ts.map +1 -0
  99. package/dist/tests/integration/import-local.test.js +158 -0
  100. package/dist/tests/integration/import-local.test.js.map +1 -0
  101. package/dist/tests/mcp-server-template.test.js +21 -0
  102. package/dist/tests/mcp-server-template.test.js.map +1 -1
  103. package/dist/tests/node-version.test.js +5 -5
  104. package/dist/tests/node-version.test.js.map +1 -1
  105. package/dist/tests/skills-directory.test.d.ts +2 -0
  106. package/dist/tests/skills-directory.test.d.ts.map +1 -0
  107. package/dist/tests/skills-directory.test.js +97 -0
  108. package/dist/tests/skills-directory.test.js.map +1 -0
  109. package/dist/tests/unit/import-local.helpers.test.d.ts +2 -0
  110. package/dist/tests/unit/import-local.helpers.test.d.ts.map +1 -0
  111. package/dist/tests/unit/import-local.helpers.test.js +96 -0
  112. package/dist/tests/unit/import-local.helpers.test.js.map +1 -0
  113. package/package.json +10 -4
  114. package/dist/src/import.d.ts.map +0 -1
  115. package/dist/src/import.js.map +0 -1
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview SMI-4590 Wave 4 PR 5/6 — `sklx config get/set audit_mode` tests
3
+ * @module @skillsmith/cli/tests/config
4
+ *
5
+ * Coverage (plan §810-813 — load-bearing tier-revalidation security tests):
6
+ *
7
+ * Tier revalidation matrix — the `set` path MUST refuse a write when
8
+ * `tierAllowsAuditMode(tier, mode)` returns false. No file IO occurs on
9
+ * rejection; the existing config (if any) stays intact.
10
+ *
11
+ * 1. community + power_user → audit.mode.tier_ineligible, NO write
12
+ * 2. community + governance → audit.mode.tier_ineligible, NO write
13
+ * 3. individual + power_user → audit.mode.tier_ineligible, NO write
14
+ * 4. individual + governance → audit.mode.tier_ineligible, NO write
15
+ * 5. team + power_user → SUCCESS, file written
16
+ * 6. team + governance → audit.mode.tier_ineligible (Enterprise-only)
17
+ * 7. enterprise + governance → SUCCESS, file written
18
+ *
19
+ * Value validation:
20
+ * 8. any tier + invalid_value → audit.mode.invalid_value, NO write
21
+ * 9. any tier + preventative → SUCCESS for every tier
22
+ * 10. any tier + off → SUCCESS for every tier
23
+ *
24
+ * Unsupported keys:
25
+ * 11. set with unknown key → config.unsupported_key, NO write
26
+ * 12. get with unknown key → config.unsupported_key
27
+ *
28
+ * Get path:
29
+ * 13. get audit_mode reads existing file value when present
30
+ * 14. get audit_mode falls back to tier default when unset
31
+ */
32
+ export {};
33
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../tests/config.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * @fileoverview SMI-4590 Wave 4 PR 5/6 — `sklx config get/set audit_mode` tests
3
+ * @module @skillsmith/cli/tests/config
4
+ *
5
+ * Coverage (plan §810-813 — load-bearing tier-revalidation security tests):
6
+ *
7
+ * Tier revalidation matrix — the `set` path MUST refuse a write when
8
+ * `tierAllowsAuditMode(tier, mode)` returns false. No file IO occurs on
9
+ * rejection; the existing config (if any) stays intact.
10
+ *
11
+ * 1. community + power_user → audit.mode.tier_ineligible, NO write
12
+ * 2. community + governance → audit.mode.tier_ineligible, NO write
13
+ * 3. individual + power_user → audit.mode.tier_ineligible, NO write
14
+ * 4. individual + governance → audit.mode.tier_ineligible, NO write
15
+ * 5. team + power_user → SUCCESS, file written
16
+ * 6. team + governance → audit.mode.tier_ineligible (Enterprise-only)
17
+ * 7. enterprise + governance → SUCCESS, file written
18
+ *
19
+ * Value validation:
20
+ * 8. any tier + invalid_value → audit.mode.invalid_value, NO write
21
+ * 9. any tier + preventative → SUCCESS for every tier
22
+ * 10. any tier + off → SUCCESS for every tier
23
+ *
24
+ * Unsupported keys:
25
+ * 11. set with unknown key → config.unsupported_key, NO write
26
+ * 12. get with unknown key → config.unsupported_key
27
+ *
28
+ * Get path:
29
+ * 13. get audit_mode reads existing file value when present
30
+ * 14. get audit_mode falls back to tier default when unset
31
+ */
32
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
33
+ import * as fs from 'node:fs';
34
+ import * as os from 'node:os';
35
+ import { join } from 'node:path';
36
+ const mocks = vi.hoisted(() => ({
37
+ getLicenseStatus: vi.fn(),
38
+ }));
39
+ vi.mock('../src/utils/license.js', () => ({
40
+ getLicenseStatus: () => mocks.getLicenseStatus(),
41
+ }));
42
+ import { runConfigGet, runConfigSet, ConfigError, configPath } from '../src/commands/config.js';
43
+ // ============================================================================
44
+ // Test harness — sandbox HOME so config writes are isolated.
45
+ // ============================================================================
46
+ let tmpHome;
47
+ let originalHome;
48
+ let stdoutSpy;
49
+ beforeEach(() => {
50
+ tmpHome = fs.mkdtempSync(join(os.tmpdir(), 'sklx-config-test-'));
51
+ originalHome = process.env['HOME'];
52
+ process.env['HOME'] = tmpHome;
53
+ stdoutSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
54
+ vi.clearAllMocks();
55
+ });
56
+ afterEach(() => {
57
+ if (originalHome !== undefined) {
58
+ process.env['HOME'] = originalHome;
59
+ }
60
+ else {
61
+ delete process.env['HOME'];
62
+ }
63
+ fs.rmSync(tmpHome, { recursive: true, force: true });
64
+ stdoutSpy.mockRestore();
65
+ });
66
+ function setTier(tier) {
67
+ mocks.getLicenseStatus.mockResolvedValue({ tier });
68
+ }
69
+ function configFileExists() {
70
+ return fs.existsSync(configPath());
71
+ }
72
+ function readConfigRaw() {
73
+ if (!configFileExists())
74
+ return {};
75
+ return JSON.parse(fs.readFileSync(configPath(), 'utf-8'));
76
+ }
77
+ // ============================================================================
78
+ // Tests
79
+ // ============================================================================
80
+ describe('SMI-4590 Wave 4 PR 5/6 — sklx config tier revalidation (plan §810-813)', () => {
81
+ describe('community tier — power_user / governance forbidden', () => {
82
+ it('community + power_user → tier_ineligible, NO write', async () => {
83
+ setTier('community');
84
+ try {
85
+ await runConfigSet('audit_mode', 'power_user');
86
+ expect.fail('Expected ConfigError');
87
+ }
88
+ catch (error) {
89
+ expect(error).toBeInstanceOf(ConfigError);
90
+ expect(error.code).toBe('audit.mode.tier_ineligible');
91
+ }
92
+ expect(configFileExists()).toBe(false);
93
+ });
94
+ it('community + governance → tier_ineligible, NO write', async () => {
95
+ setTier('community');
96
+ try {
97
+ await runConfigSet('audit_mode', 'governance');
98
+ expect.fail('Expected ConfigError');
99
+ }
100
+ catch (error) {
101
+ expect(error.code).toBe('audit.mode.tier_ineligible');
102
+ }
103
+ expect(configFileExists()).toBe(false);
104
+ });
105
+ });
106
+ describe('individual tier — power_user / governance forbidden', () => {
107
+ it('individual + power_user → tier_ineligible, NO write', async () => {
108
+ setTier('individual');
109
+ try {
110
+ await runConfigSet('audit_mode', 'power_user');
111
+ expect.fail('Expected ConfigError');
112
+ }
113
+ catch (error) {
114
+ expect(error.code).toBe('audit.mode.tier_ineligible');
115
+ }
116
+ expect(configFileExists()).toBe(false);
117
+ });
118
+ it('individual + governance → tier_ineligible, NO write', async () => {
119
+ setTier('individual');
120
+ try {
121
+ await runConfigSet('audit_mode', 'governance');
122
+ expect.fail('Expected ConfigError');
123
+ }
124
+ catch (error) {
125
+ expect(error.code).toBe('audit.mode.tier_ineligible');
126
+ }
127
+ expect(configFileExists()).toBe(false);
128
+ });
129
+ });
130
+ describe('team tier — power_user allowed, governance forbidden', () => {
131
+ it('team + power_user → SUCCESS, file written', async () => {
132
+ setTier('team');
133
+ await runConfigSet('audit_mode', 'power_user');
134
+ expect(configFileExists()).toBe(true);
135
+ expect(readConfigRaw()['audit_mode']).toBe('power_user');
136
+ });
137
+ it('team + governance → tier_ineligible (Enterprise-only)', async () => {
138
+ setTier('team');
139
+ try {
140
+ await runConfigSet('audit_mode', 'governance');
141
+ expect.fail('Expected ConfigError');
142
+ }
143
+ catch (error) {
144
+ expect(error.code).toBe('audit.mode.tier_ineligible');
145
+ }
146
+ expect(configFileExists()).toBe(false);
147
+ });
148
+ });
149
+ describe('enterprise tier — governance allowed', () => {
150
+ it('enterprise + governance → SUCCESS, file written', async () => {
151
+ setTier('enterprise');
152
+ await runConfigSet('audit_mode', 'governance');
153
+ expect(configFileExists()).toBe(true);
154
+ expect(readConfigRaw()['audit_mode']).toBe('governance');
155
+ });
156
+ });
157
+ });
158
+ describe('SMI-4590 Wave 4 PR 5/6 — sklx config value validation', () => {
159
+ it('invalid_value → audit.mode.invalid_value, NO write', async () => {
160
+ setTier('enterprise');
161
+ try {
162
+ await runConfigSet('audit_mode', 'definitely-not-a-mode');
163
+ expect.fail('Expected ConfigError');
164
+ }
165
+ catch (error) {
166
+ expect(error.code).toBe('audit.mode.invalid_value');
167
+ }
168
+ expect(configFileExists()).toBe(false);
169
+ });
170
+ it('preventative is allowed for every tier', async () => {
171
+ for (const tier of ['community', 'individual', 'team', 'enterprise']) {
172
+ // Reset sandbox between tiers.
173
+ fs.rmSync(configPath(), { force: true });
174
+ setTier(tier);
175
+ await runConfigSet('audit_mode', 'preventative');
176
+ expect(readConfigRaw()['audit_mode']).toBe('preventative');
177
+ }
178
+ });
179
+ it('off is allowed for every tier', async () => {
180
+ for (const tier of ['community', 'individual', 'team', 'enterprise']) {
181
+ fs.rmSync(configPath(), { force: true });
182
+ setTier(tier);
183
+ await runConfigSet('audit_mode', 'off');
184
+ expect(readConfigRaw()['audit_mode']).toBe('off');
185
+ }
186
+ });
187
+ });
188
+ describe('SMI-4590 Wave 4 PR 5/6 — sklx config unsupported keys', () => {
189
+ it('set with unknown key → config.unsupported_key, NO write', async () => {
190
+ setTier('enterprise');
191
+ try {
192
+ await runConfigSet('telemetry_endpoint', 'https://example.com');
193
+ expect.fail('Expected ConfigError');
194
+ }
195
+ catch (error) {
196
+ expect(error.code).toBe('config.unsupported_key');
197
+ }
198
+ expect(configFileExists()).toBe(false);
199
+ });
200
+ it('get with unknown key → config.unsupported_key', async () => {
201
+ setTier('enterprise');
202
+ try {
203
+ await runConfigGet('telemetry_endpoint');
204
+ expect.fail('Expected ConfigError');
205
+ }
206
+ catch (error) {
207
+ expect(error.code).toBe('config.unsupported_key');
208
+ }
209
+ });
210
+ });
211
+ describe('SMI-4590 Wave 4 PR 5/6 — sklx config get audit_mode', () => {
212
+ it('reads existing file value when present', async () => {
213
+ setTier('team');
214
+ await runConfigSet('audit_mode', 'power_user');
215
+ stdoutSpy.mockClear();
216
+ await runConfigGet('audit_mode');
217
+ const out = stdoutSpy.mock.calls.map((c) => String(c[0] ?? '')).join('\n');
218
+ expect(out).toContain('power_user');
219
+ });
220
+ it('falls back to tier default when no config file exists', async () => {
221
+ setTier('community');
222
+ // No prior write, so file does not exist.
223
+ expect(configFileExists()).toBe(false);
224
+ await runConfigGet('audit_mode');
225
+ const out = stdoutSpy.mock.calls.map((c) => String(c[0] ?? '')).join('\n');
226
+ // Output should reference the tier default plus the "(tier default)" hint.
227
+ expect(out).toMatch(/tier default/);
228
+ });
229
+ it('preserves unknown keys on read-modify-write (atomic merge)', async () => {
230
+ setTier('team');
231
+ // Seed the config with extra keys directly.
232
+ fs.mkdirSync(join(tmpHome, '.skillsmith'), { recursive: true });
233
+ fs.writeFileSync(configPath(), JSON.stringify({ extra_key: 'value', audit_mode: 'preventative' }), 'utf-8');
234
+ await runConfigSet('audit_mode', 'power_user');
235
+ const after = readConfigRaw();
236
+ expect(after['audit_mode']).toBe('power_user');
237
+ expect(after['extra_key']).toBe('value');
238
+ });
239
+ });
240
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../tests/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=install-multi-client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-multi-client.test.d.ts","sourceRoot":"","sources":["../../tests/install-multi-client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * SMI-4578 Step 6: install --client / --also-link end-to-end tests.
3
+ *
4
+ * Exercises the parseAlsoLink + assertClientId validation surface in
5
+ * install.ts plus the addLink fan-out behaviour from
6
+ * @skillsmith/core/install. Each test runs in a temp $HOME so we
7
+ * never touch the real filesystem.
8
+ *
9
+ * Skill installation itself (the SkillInstallationService.install
10
+ * call) is NOT exercised here — that's covered by install.test.ts,
11
+ * service unit tests, and the post-merge smoke. These tests focus on
12
+ * the multi-client surface: target-directory resolution, fan-out
13
+ * manifest writes, conflict refuse, cycle refuse.
14
+ */
15
+ import { mkdtemp, mkdir, readFile, rm, stat, lstat, writeFile } from 'node:fs/promises';
16
+ import { tmpdir } from 'node:os';
17
+ import * as path from 'node:path';
18
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
19
+ const ORIGINAL_HOME = process.env['HOME'];
20
+ const ORIGINAL_USERPROFILE = process.env['USERPROFILE'];
21
+ const ORIGINAL_CLIENT = process.env['SKILLSMITH_CLIENT'];
22
+ let homeDir;
23
+ beforeEach(async () => {
24
+ homeDir = await mkdtemp(path.join(tmpdir(), 'smi4578-multi-'));
25
+ process.env['HOME'] = homeDir;
26
+ process.env['USERPROFILE'] = homeDir;
27
+ delete process.env['SKILLSMITH_CLIENT'];
28
+ // CLIENT_NATIVE_PATHS computes homedir() at module import time —
29
+ // reset modules so each test sees its own $HOME.
30
+ vi.resetModules();
31
+ });
32
+ afterEach(async () => {
33
+ if (ORIGINAL_HOME === undefined)
34
+ delete process.env['HOME'];
35
+ else
36
+ process.env['HOME'] = ORIGINAL_HOME;
37
+ if (ORIGINAL_USERPROFILE === undefined)
38
+ delete process.env['USERPROFILE'];
39
+ else
40
+ process.env['USERPROFILE'] = ORIGINAL_USERPROFILE;
41
+ if (ORIGINAL_CLIENT === undefined)
42
+ delete process.env['SKILLSMITH_CLIENT'];
43
+ else
44
+ process.env['SKILLSMITH_CLIENT'] = ORIGINAL_CLIENT;
45
+ await rm(homeDir, { recursive: true, force: true });
46
+ });
47
+ async function seedSkill(skillId, body = '# test\n') {
48
+ const dir = path.join(homeDir, '.claude', 'skills', skillId);
49
+ await mkdir(dir, { recursive: true });
50
+ await writeFile(path.join(dir, 'SKILL.md'), `---\nname: ${skillId}\n---\n${body}`, 'utf-8');
51
+ return dir;
52
+ }
53
+ describe('install --client / --also-link', () => {
54
+ describe('--client target directory', () => {
55
+ it('getInstallPath returns the cursor directory for --client cursor', async () => {
56
+ const { getInstallPath } = await import('@skillsmith/core/install');
57
+ expect(getInstallPath('cursor')).toBe(path.join(homeDir, '.cursor', 'skills'));
58
+ });
59
+ it('SKILLSMITH_CLIENT env var routes resolveClientPath', async () => {
60
+ process.env['SKILLSMITH_CLIENT'] = 'windsurf';
61
+ const { resolveClientPath } = await import('@skillsmith/core/install');
62
+ expect(resolveClientPath()).toBe(path.join(homeDir, '.codeium', 'windsurf', 'skills'));
63
+ });
64
+ it('explicit override beats env var', async () => {
65
+ process.env['SKILLSMITH_CLIENT'] = 'cursor';
66
+ const { resolveClientPath } = await import('@skillsmith/core/install');
67
+ expect(resolveClientPath('agents')).toBe(path.join(homeDir, '.agents', 'skills'));
68
+ });
69
+ it('rejects invalid SKILLSMITH_CLIENT with a friendly hint', async () => {
70
+ process.env['SKILLSMITH_CLIENT'] = 'codex';
71
+ const { resolveClientPath } = await import('@skillsmith/core/install');
72
+ expect(() => resolveClientPath()).toThrow(/--client agents/);
73
+ });
74
+ });
75
+ describe('--also-link copy default', () => {
76
+ it('copies the source skill into a secondary client directory', async () => {
77
+ await seedSkill('foo', '# foo\n');
78
+ const { addLink, listLinks } = await import('@skillsmith/core/install');
79
+ const result = await addLink({
80
+ skillId: 'foo',
81
+ fromClient: 'claude-code',
82
+ toClient: 'cursor',
83
+ });
84
+ expect(result.record.kind).toBe('copy');
85
+ const dest = path.join(homeDir, '.cursor', 'skills', 'foo');
86
+ const skillMd = await readFile(path.join(dest, 'SKILL.md'), 'utf-8');
87
+ expect(skillMd).toContain('# foo');
88
+ const links = await listLinks('foo');
89
+ expect(links).toHaveLength(1);
90
+ expect(links[0]?.kind).toBe('copy');
91
+ expect(links[0]?.to).toBe(dest);
92
+ });
93
+ it('writes a manifest entry per fan-out target', async () => {
94
+ await seedSkill('multi');
95
+ const { addLink, getLinkManifestPath, loadManifest } = await import('@skillsmith/core/install');
96
+ await addLink({ skillId: 'multi', fromClient: 'claude-code', toClient: 'cursor' });
97
+ await addLink({ skillId: 'multi', fromClient: 'claude-code', toClient: 'agents' });
98
+ const manifest = await loadManifest();
99
+ expect(manifest.links.filter((l) => l.skillId === 'multi')).toHaveLength(2);
100
+ expect(manifest.version).toBe(1);
101
+ // manifest path is under ~/.skillsmith/links/manifest.json
102
+ const manifestPath = getLinkManifestPath();
103
+ expect(manifestPath).toBe(path.join(homeDir, '.skillsmith', 'links', 'manifest.json'));
104
+ await expect(stat(manifestPath)).resolves.toBeDefined();
105
+ });
106
+ });
107
+ describe('--also-link --symlink (POSIX opt-in)', () => {
108
+ it('creates a relative symlink instead of a copy', async () => {
109
+ await seedSkill('linkme');
110
+ const { addLink } = await import('@skillsmith/core/install');
111
+ const result = await addLink({
112
+ skillId: 'linkme',
113
+ fromClient: 'claude-code',
114
+ toClient: 'windsurf',
115
+ preferSymlink: true,
116
+ });
117
+ expect(result.record.kind).toBe('symlink');
118
+ const dest = path.join(homeDir, '.codeium', 'windsurf', 'skills', 'linkme');
119
+ const linkStat = await lstat(dest);
120
+ expect(linkStat.isSymbolicLink()).toBe(true);
121
+ });
122
+ });
123
+ describe('conflict policy', () => {
124
+ it('refuses to overwrite a pre-existing destination without force', async () => {
125
+ await seedSkill('clash');
126
+ // Pre-create destination with different content
127
+ const destDir = path.join(homeDir, '.cursor', 'skills', 'clash');
128
+ await mkdir(destDir, { recursive: true });
129
+ await writeFile(path.join(destDir, 'SKILL.md'), '# DIFFERENT\n', 'utf-8');
130
+ const { addLink } = await import('@skillsmith/core/install');
131
+ await expect(addLink({ skillId: 'clash', fromClient: 'claude-code', toClient: 'cursor' })).rejects.toThrow(/already exists/);
132
+ // Destination contents unchanged
133
+ const after = await readFile(path.join(destDir, 'SKILL.md'), 'utf-8');
134
+ expect(after).toBe('# DIFFERENT\n');
135
+ });
136
+ it('overwrites when force=true', async () => {
137
+ await seedSkill('clash');
138
+ const destDir = path.join(homeDir, '.cursor', 'skills', 'clash');
139
+ await mkdir(destDir, { recursive: true });
140
+ await writeFile(path.join(destDir, 'SKILL.md'), '# OLD\n', 'utf-8');
141
+ const { addLink } = await import('@skillsmith/core/install');
142
+ await addLink({
143
+ skillId: 'clash',
144
+ fromClient: 'claude-code',
145
+ toClient: 'cursor',
146
+ force: true,
147
+ });
148
+ const after = await readFile(path.join(destDir, 'SKILL.md'), 'utf-8');
149
+ expect(after).toContain('# test'); // matches seedSkill default body
150
+ });
151
+ });
152
+ describe('cycle detection', () => {
153
+ it('refuses A→B when an existing B→A entry would form a cycle', async () => {
154
+ await seedSkill('cycle');
155
+ const agentsDir = path.join(homeDir, '.agents', 'skills', 'cycle');
156
+ await mkdir(agentsDir, { recursive: true });
157
+ await writeFile(path.join(agentsDir, 'SKILL.md'), '# cycle\n', 'utf-8');
158
+ const { addLink } = await import('@skillsmith/core/install');
159
+ // Forward: agents → claude-code
160
+ await addLink({
161
+ skillId: 'cycle',
162
+ fromClient: 'agents',
163
+ toClient: 'claude-code',
164
+ force: true,
165
+ });
166
+ // Reverse: claude-code → agents — should refuse
167
+ await expect(addLink({
168
+ skillId: 'cycle',
169
+ fromClient: 'claude-code',
170
+ toClient: 'agents',
171
+ force: true,
172
+ })).rejects.toThrow(/cycle detected/);
173
+ });
174
+ });
175
+ describe('uninstall fan-out cleanup', () => {
176
+ it('removeLinks tears down both copies and symlinks', async () => {
177
+ await seedSkill('teardown');
178
+ const { addLink, removeLinks, listLinks } = await import('@skillsmith/core/install');
179
+ await addLink({ skillId: 'teardown', fromClient: 'claude-code', toClient: 'cursor' });
180
+ await addLink({
181
+ skillId: 'teardown',
182
+ fromClient: 'claude-code',
183
+ toClient: 'windsurf',
184
+ preferSymlink: true,
185
+ });
186
+ const removed = await removeLinks('teardown');
187
+ expect(removed).toBe(2);
188
+ expect(await listLinks('teardown')).toEqual([]);
189
+ // Both destinations gone
190
+ await expect(stat(path.join(homeDir, '.cursor', 'skills', 'teardown'))).rejects.toThrow(/ENOENT/);
191
+ await expect(lstat(path.join(homeDir, '.codeium', 'windsurf', 'skills', 'teardown'))).rejects.toThrow(/ENOENT/);
192
+ // Source untouched
193
+ await expect(stat(path.join(homeDir, '.claude', 'skills', 'teardown'))).resolves.toBeDefined();
194
+ });
195
+ it('removeLinks returns 0 when no manifest exists', async () => {
196
+ const { removeLinks } = await import('@skillsmith/core/install');
197
+ const removed = await removeLinks('nothing-installed');
198
+ expect(removed).toBe(0);
199
+ });
200
+ });
201
+ });
202
+ //# sourceMappingURL=install-multi-client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-multi-client.test.js","sourceRoot":"","sources":["../../tests/install-multi-client.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=import-local.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-local.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/import-local.test.ts"],"names":[],"mappings":""}