@skillsmith/cli 0.6.1 → 0.6.3

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 (69) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/commands/index.d.ts +1 -0
  4. package/dist/src/commands/index.d.ts.map +1 -1
  5. package/dist/src/commands/index.js +2 -0
  6. package/dist/src/commands/index.js.map +1 -1
  7. package/dist/src/commands/info.js +1 -1
  8. package/dist/src/commands/install.js +1 -1
  9. package/dist/src/commands/install.js.map +1 -1
  10. package/dist/src/commands/search.d.ts.map +1 -1
  11. package/dist/src/commands/search.helpers.d.ts +29 -0
  12. package/dist/src/commands/search.helpers.d.ts.map +1 -1
  13. package/dist/src/commands/search.helpers.js +39 -1
  14. package/dist/src/commands/search.helpers.js.map +1 -1
  15. package/dist/src/commands/search.js +26 -2
  16. package/dist/src/commands/search.js.map +1 -1
  17. package/dist/src/commands/search.test.d.ts +10 -0
  18. package/dist/src/commands/search.test.d.ts.map +1 -0
  19. package/dist/src/commands/search.test.js +179 -0
  20. package/dist/src/commands/search.test.js.map +1 -0
  21. package/dist/src/commands/sync.d.ts.map +1 -1
  22. package/dist/src/commands/sync.helpers.d.ts +29 -1
  23. package/dist/src/commands/sync.helpers.d.ts.map +1 -1
  24. package/dist/src/commands/sync.helpers.js +38 -0
  25. package/dist/src/commands/sync.helpers.js.map +1 -1
  26. package/dist/src/commands/sync.js +20 -1
  27. package/dist/src/commands/sync.js.map +1 -1
  28. package/dist/src/commands/telemetry.d.ts +38 -0
  29. package/dist/src/commands/telemetry.d.ts.map +1 -0
  30. package/dist/src/commands/telemetry.helpers.d.ts +58 -0
  31. package/dist/src/commands/telemetry.helpers.d.ts.map +1 -0
  32. package/dist/src/commands/telemetry.helpers.js +173 -0
  33. package/dist/src/commands/telemetry.helpers.js.map +1 -0
  34. package/dist/src/commands/telemetry.helpers.test.d.ts +15 -0
  35. package/dist/src/commands/telemetry.helpers.test.d.ts.map +1 -0
  36. package/dist/src/commands/telemetry.helpers.test.js +240 -0
  37. package/dist/src/commands/telemetry.helpers.test.js.map +1 -0
  38. package/dist/src/commands/telemetry.js +354 -0
  39. package/dist/src/commands/telemetry.js.map +1 -0
  40. package/dist/src/commands/telemetry.test.d.ts +18 -0
  41. package/dist/src/commands/telemetry.test.d.ts.map +1 -0
  42. package/dist/src/commands/telemetry.test.js +401 -0
  43. package/dist/src/commands/telemetry.test.js.map +1 -0
  44. package/dist/src/index.js +3 -1
  45. package/dist/src/index.js.map +1 -1
  46. package/dist/src/utils/manifest.d.ts +75 -0
  47. package/dist/src/utils/manifest.d.ts.map +1 -1
  48. package/dist/src/utils/manifest.js +78 -0
  49. package/dist/src/utils/manifest.js.map +1 -1
  50. package/dist/src/utils/manifest.test.d.ts +20 -0
  51. package/dist/src/utils/manifest.test.d.ts.map +1 -0
  52. package/dist/src/utils/manifest.test.js +344 -0
  53. package/dist/src/utils/manifest.test.js.map +1 -0
  54. package/dist/src/utils/open-database.d.ts.map +1 -1
  55. package/dist/src/utils/open-database.js +33 -4
  56. package/dist/src/utils/open-database.js.map +1 -1
  57. package/dist/tests/embedding-probe-stdio.test.d.ts +11 -0
  58. package/dist/tests/embedding-probe-stdio.test.d.ts.map +1 -0
  59. package/dist/tests/embedding-probe-stdio.test.js +58 -0
  60. package/dist/tests/embedding-probe-stdio.test.js.map +1 -0
  61. package/dist/tests/open-database.test.js +25 -0
  62. package/dist/tests/open-database.test.js.map +1 -1
  63. package/dist/tests/sync.auth-ux.test.d.ts +14 -0
  64. package/dist/tests/sync.auth-ux.test.d.ts.map +1 -0
  65. package/dist/tests/sync.auth-ux.test.js +148 -0
  66. package/dist/tests/sync.auth-ux.test.js.map +1 -0
  67. package/package.json +5 -4
  68. package/templates/skill-telemetry.cmd +16 -0
  69. package/templates/skill-telemetry.sh +199 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * @fileoverview Tests for telemetry.helpers.ts — atomic settings.json merger.
3
+ * @module @skillsmith/cli/commands/telemetry.helpers.test
4
+ * @see SMI-5021 Wave 3 Step 2 — plan line 717
5
+ *
6
+ * Covers:
7
+ * - addSkillHookEntries: empty settings → well-formed hooks block
8
+ * - addSkillHookEntries: idempotent (second add is a no-op)
9
+ * - addSkillHookEntries: throws on foreign Skill matcher command path
10
+ * - removeSkillHookEntries: removes only Skillsmith entries
11
+ * - removeSkillHookEntries: on empty settings → no-op
12
+ * - writeClaudeSettings: atomic temp-file → rename correctness
13
+ */
14
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
15
+ // ---------------------------------------------------------------------------
16
+ // fs mock — intercepts node:fs (used by telemetry.helpers.ts)
17
+ // ---------------------------------------------------------------------------
18
+ const memfs = {};
19
+ let existsResult = false;
20
+ vi.mock('node:fs', () => ({
21
+ existsSync: vi.fn((_p) => existsResult),
22
+ readFileSync: vi.fn((path) => {
23
+ const content = memfs[path];
24
+ if (content === undefined)
25
+ throw Object.assign(new Error(`ENOENT: ${path}`), { code: 'ENOENT' });
26
+ return content;
27
+ }),
28
+ writeFileSync: vi.fn((path, content) => {
29
+ memfs[path] = content;
30
+ }),
31
+ renameSync: vi.fn((src, dst) => {
32
+ const content = memfs[src];
33
+ if (content === undefined)
34
+ throw new Error(`ENOENT: ${src}`);
35
+ memfs[dst] = content;
36
+ delete memfs[src];
37
+ }),
38
+ mkdirSync: vi.fn(() => undefined),
39
+ chmodSync: vi.fn(() => undefined),
40
+ }));
41
+ import { loadClaudeSettings, addSkillHookEntries, removeSkillHookEntries, writeClaudeSettings, TelemetryHookError, } from './telemetry.helpers.js';
42
+ // ---------------------------------------------------------------------------
43
+ // Helpers
44
+ // ---------------------------------------------------------------------------
45
+ const HOOK_PATH = '/home/test/.skillsmith/hooks/skill-telemetry.sh';
46
+ function emptySettings() {
47
+ return {};
48
+ }
49
+ function settingsWithSkillsmithHooks() {
50
+ return {
51
+ hooks: {
52
+ PreToolUse: [
53
+ {
54
+ matcher: 'Skill',
55
+ hooks: [{ type: 'command', command: `${HOOK_PATH} pre` }],
56
+ },
57
+ ],
58
+ PostToolUse: [
59
+ {
60
+ matcher: 'Skill',
61
+ hooks: [{ type: 'command', command: `${HOOK_PATH} post` }],
62
+ },
63
+ ],
64
+ },
65
+ };
66
+ }
67
+ function settingsWithForeignSkillHook() {
68
+ return {
69
+ hooks: {
70
+ PreToolUse: [
71
+ {
72
+ matcher: 'Skill',
73
+ hooks: [{ type: 'command', command: '/other/tool/skill-hook.sh pre' }],
74
+ },
75
+ ],
76
+ PostToolUse: [],
77
+ },
78
+ };
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Setup / teardown
82
+ // ---------------------------------------------------------------------------
83
+ beforeEach(() => {
84
+ for (const k of Object.keys(memfs))
85
+ delete memfs[k];
86
+ existsResult = false;
87
+ vi.clearAllMocks();
88
+ });
89
+ afterEach(() => {
90
+ for (const k of Object.keys(memfs))
91
+ delete memfs[k];
92
+ });
93
+ // ---------------------------------------------------------------------------
94
+ // loadClaudeSettings
95
+ // ---------------------------------------------------------------------------
96
+ describe('loadClaudeSettings', () => {
97
+ it('returns empty object when file does not exist', () => {
98
+ existsResult = false;
99
+ const result = loadClaudeSettings('user');
100
+ expect(result).toEqual({});
101
+ });
102
+ it('returns parsed JSON when file exists and is valid', async () => {
103
+ existsResult = true;
104
+ const { existsSync, readFileSync } = await import('node:fs');
105
+ existsSync.mockReturnValueOnce(true);
106
+ readFileSync.mockReturnValueOnce(JSON.stringify({ hooks: { PreToolUse: [] } }));
107
+ const result = loadClaudeSettings('user');
108
+ expect(result).toHaveProperty('hooks');
109
+ });
110
+ });
111
+ // ---------------------------------------------------------------------------
112
+ // addSkillHookEntries
113
+ // ---------------------------------------------------------------------------
114
+ describe('addSkillHookEntries', () => {
115
+ it('adds PreToolUse and PostToolUse Skill matchers to empty settings', () => {
116
+ const result = addSkillHookEntries(emptySettings(), HOOK_PATH);
117
+ expect(result.hooks?.PreToolUse).toHaveLength(1);
118
+ expect(result.hooks?.PreToolUse?.[0]?.matcher).toBe('Skill');
119
+ expect(result.hooks?.PreToolUse?.[0]?.hooks?.[0]?.command).toBe(`${HOOK_PATH} pre`);
120
+ expect(result.hooks?.PostToolUse).toHaveLength(1);
121
+ expect(result.hooks?.PostToolUse?.[0]?.matcher).toBe('Skill');
122
+ expect(result.hooks?.PostToolUse?.[0]?.hooks?.[0]?.command).toBe(`${HOOK_PATH} post`);
123
+ });
124
+ it('is idempotent — calling add twice produces only one entry per array', () => {
125
+ const once = addSkillHookEntries(emptySettings(), HOOK_PATH);
126
+ const twice = addSkillHookEntries(once, HOOK_PATH);
127
+ expect(twice.hooks?.PreToolUse).toHaveLength(1);
128
+ expect(twice.hooks?.PostToolUse).toHaveLength(1);
129
+ });
130
+ it('preserves existing non-Skill hook entries alongside new Skill entry', () => {
131
+ const existing = {
132
+ hooks: {
133
+ PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: 'echo pre-bash' }] }],
134
+ PostToolUse: [],
135
+ },
136
+ };
137
+ const result = addSkillHookEntries(existing, HOOK_PATH);
138
+ // Should have both Bash and Skill entries
139
+ expect(result.hooks?.PreToolUse).toHaveLength(2);
140
+ expect(result.hooks?.PreToolUse?.some((e) => e.matcher === 'Bash')).toBe(true);
141
+ expect(result.hooks?.PreToolUse?.some((e) => e.matcher === 'Skill')).toBe(true);
142
+ });
143
+ it('throws TelemetryHookError with code hook.foreign_skill_matcher when a foreign Skill hook exists', () => {
144
+ const settings = settingsWithForeignSkillHook();
145
+ expect(() => addSkillHookEntries(settings, HOOK_PATH)).toThrow(TelemetryHookError);
146
+ expect(() => addSkillHookEntries(settings, HOOK_PATH)).toThrow(expect.objectContaining({ code: 'hook.foreign_skill_matcher' }));
147
+ });
148
+ it('error message from foreign Skill matcher includes removal instructions', () => {
149
+ const settings = settingsWithForeignSkillHook();
150
+ let caught;
151
+ try {
152
+ addSkillHookEntries(settings, HOOK_PATH);
153
+ }
154
+ catch (e) {
155
+ caught = e;
156
+ }
157
+ expect(caught).toBeInstanceOf(TelemetryHookError);
158
+ const err = caught;
159
+ expect(err.message).toContain('Remove it manually');
160
+ expect(err.message).toContain('skillsmith telemetry install-hook');
161
+ });
162
+ });
163
+ // ---------------------------------------------------------------------------
164
+ // removeSkillHookEntries
165
+ // ---------------------------------------------------------------------------
166
+ describe('removeSkillHookEntries', () => {
167
+ it('removes Skillsmith Skill entries from both PreToolUse and PostToolUse', () => {
168
+ const settings = settingsWithSkillsmithHooks();
169
+ const result = removeSkillHookEntries(settings, HOOK_PATH);
170
+ expect(result.hooks?.PreToolUse).toHaveLength(0);
171
+ expect(result.hooks?.PostToolUse).toHaveLength(0);
172
+ });
173
+ it('does not touch foreign hooks when removing Skillsmith entries', () => {
174
+ const settings = {
175
+ hooks: {
176
+ PreToolUse: [
177
+ { matcher: 'Bash', hooks: [{ type: 'command', command: 'echo bash' }] },
178
+ { matcher: 'Skill', hooks: [{ type: 'command', command: `${HOOK_PATH} pre` }] },
179
+ ],
180
+ PostToolUse: [
181
+ { matcher: 'Skill', hooks: [{ type: 'command', command: `${HOOK_PATH} post` }] },
182
+ ],
183
+ },
184
+ };
185
+ const result = removeSkillHookEntries(settings, HOOK_PATH);
186
+ // Bash hook preserved; Skill hook removed
187
+ expect(result.hooks?.PreToolUse).toHaveLength(1);
188
+ expect(result.hooks?.PreToolUse?.[0]?.matcher).toBe('Bash');
189
+ expect(result.hooks?.PostToolUse).toHaveLength(0);
190
+ });
191
+ it('is a no-op when settings has no hooks key', () => {
192
+ const result = removeSkillHookEntries({}, HOOK_PATH);
193
+ expect(result.hooks).toBeUndefined();
194
+ });
195
+ it('is a no-op when no Skill entries exist to remove', () => {
196
+ const settings = {
197
+ hooks: {
198
+ PreToolUse: [{ matcher: 'Bash', hooks: [{ type: 'command', command: 'echo bash' }] }],
199
+ PostToolUse: [],
200
+ },
201
+ };
202
+ const result = removeSkillHookEntries(settings, HOOK_PATH);
203
+ expect(result.hooks?.PreToolUse).toHaveLength(1);
204
+ expect(result.hooks?.PostToolUse).toHaveLength(0);
205
+ });
206
+ });
207
+ // ---------------------------------------------------------------------------
208
+ // writeClaudeSettings — atomic write correctness
209
+ // ---------------------------------------------------------------------------
210
+ describe('writeClaudeSettings', () => {
211
+ it('writes the settings to a .tmp file then renames to final path', async () => {
212
+ const { writeFileSync, renameSync } = await import('node:fs');
213
+ const settings = { hooks: { PreToolUse: [] } };
214
+ writeClaudeSettings('user', settings);
215
+ expect(writeFileSync).toHaveBeenCalledWith(expect.stringContaining('.tmp'), expect.stringContaining('"PreToolUse"'), expect.anything());
216
+ expect(renameSync).toHaveBeenCalledWith(expect.stringContaining('.tmp'), expect.stringContaining('settings.json'));
217
+ });
218
+ it('second write fully replaces first without partial state', async () => {
219
+ // Simulate sequential write correctness (mirrors manifest.test.ts pattern)
220
+ const first = { hooks: { PreToolUse: [] } };
221
+ const second = {
222
+ hooks: {
223
+ PreToolUse: [
224
+ { matcher: 'Skill', hooks: [{ type: 'command', command: `${HOOK_PATH} pre` }] },
225
+ ],
226
+ },
227
+ };
228
+ writeClaudeSettings('user', first);
229
+ writeClaudeSettings('user', second);
230
+ // After both writes, memfs should contain the second write's content
231
+ const { resolveSettingsPath } = await import('./telemetry.helpers.js');
232
+ const finalPath = resolveSettingsPath('user');
233
+ const raw = memfs[finalPath];
234
+ expect(raw).toBeDefined();
235
+ const parsed = JSON.parse(raw);
236
+ expect(parsed.hooks?.PreToolUse).toHaveLength(1);
237
+ expect(parsed.hooks?.PreToolUse?.[0]?.matcher).toBe('Skill');
238
+ });
239
+ });
240
+ //# sourceMappingURL=telemetry.helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.helpers.test.js","sourceRoot":"","sources":["../../../src/commands/telemetry.helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,354 @@
1
+ /**
2
+ * @fileoverview `skillsmith telemetry` command group — opt-in telemetry management.
3
+ * @module @skillsmith/cli/commands/telemetry
4
+ * @see SMI-5021 Wave 3 Step 2 — CLI subcommands for telemetry (plan lines 195–196, 576)
5
+ *
6
+ * Subcommands:
7
+ * skillsmith telemetry enable
8
+ * skillsmith telemetry disable
9
+ * skillsmith telemetry status
10
+ * skillsmith telemetry install-hook [--scope user|project] [--endpoint <url>]
11
+ * skillsmith telemetry uninstall-hook [--scope user|project]
12
+ * skillsmith telemetry reset-id
13
+ *
14
+ * Privacy invariants (plan line 719):
15
+ * - anonymousId is NEVER printed in full. Only the last 8 hex chars appear in stdout.
16
+ * - The full SHA-256 hex travels only to the events endpoint in hook payloads.
17
+ *
18
+ * Shared-state coordination (plan line 717):
19
+ * - install-hook reads then writes ~/.claude/settings.json via telemetry.helpers.ts
20
+ * - Refuses on foreign Skill matcher (security gate)
21
+ * - manifest read/write always via loadManifest / saveManifest (atomic rename)
22
+ */
23
+ import { existsSync, copyFileSync, chmodSync, mkdirSync } from 'node:fs';
24
+ import { homedir } from 'node:os';
25
+ import { join, dirname } from 'node:path';
26
+ import { fileURLToPath } from 'node:url';
27
+ import { readdirSync, unlinkSync, statSync } from 'node:fs';
28
+ const __dirname = dirname(fileURLToPath(import.meta.url));
29
+ import { Command } from 'commander';
30
+ import chalk from 'chalk';
31
+ import { loadManifest, saveManifest, generateAnonymousId, shouldRotateAnonymousId, rotateAnonymousId, sweepExpiredPreviousId, } from '../utils/manifest.js';
32
+ import { loadClaudeSettings, addSkillHookEntries, removeSkillHookEntries, writeClaudeSettings, TelemetryHookError, } from './telemetry.helpers.js';
33
+ import { sanitizeError } from '../utils/sanitize.js';
34
+ // ---------------------------------------------------------------------------
35
+ // Constants
36
+ // ---------------------------------------------------------------------------
37
+ const PRIVACY_URL = 'https://skillsmith.app/privacy#telemetry';
38
+ const DEFAULT_ENDPOINT = 'https://vrcnzpmndtroqxxoqkzy.supabase.co/functions/v1/events';
39
+ const ORPHAN_TTL_MS = 60 * 60 * 1000; // 1 hour
40
+ // All path helpers resolved at call time so test harnesses that swap HOME work.
41
+ function hookScriptPath() {
42
+ return join(homedir(), '.skillsmith', 'hooks', 'skill-telemetry.sh');
43
+ }
44
+ function runDir() {
45
+ return join(homedir(), '.skillsmith', 'run');
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Privacy helper — NEVER print full anonymousId
49
+ // ---------------------------------------------------------------------------
50
+ function idTail(id) {
51
+ if (!id)
52
+ return '(none)';
53
+ return `...${id.slice(-8)}`;
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Orphan GC (plan line 714)
57
+ // ---------------------------------------------------------------------------
58
+ function gcOrphanRunFiles() {
59
+ try {
60
+ const dir = runDir();
61
+ if (!existsSync(dir))
62
+ return;
63
+ const now = Date.now();
64
+ for (const f of readdirSync(dir)) {
65
+ if (!f.startsWith('skill-'))
66
+ continue;
67
+ const fp = join(dir, f);
68
+ try {
69
+ const st = statSync(fp);
70
+ if (now - st.mtimeMs > ORPHAN_TTL_MS)
71
+ unlinkSync(fp);
72
+ }
73
+ catch {
74
+ // best-effort
75
+ }
76
+ }
77
+ }
78
+ catch {
79
+ // never throw from GC
80
+ }
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // enable
84
+ // ---------------------------------------------------------------------------
85
+ async function runEnable() {
86
+ const manifest = await loadManifest();
87
+ const t = manifest.telemetry ?? { enabled: false };
88
+ if (t.enabled) {
89
+ console.log(chalk.green('Telemetry is already enabled.'));
90
+ console.log(chalk.dim(` Anonymous ID tail: ${idTail(t.anonymousId)}`));
91
+ return;
92
+ }
93
+ const now = new Date().toISOString();
94
+ const updated = {
95
+ ...t,
96
+ enabled: true,
97
+ anonymousId: t.anonymousId ?? generateAnonymousId(),
98
+ anonymousIdCreatedAt: t.anonymousIdCreatedAt ?? now,
99
+ scope: t.scope ?? 'personal',
100
+ endpoint: t.endpoint ?? DEFAULT_ENDPOINT,
101
+ };
102
+ await saveManifest({ ...manifest, telemetry: updated });
103
+ console.log(chalk.green('Telemetry enabled.'));
104
+ console.log();
105
+ console.log('Skillsmith telemetry collects anonymous skill-invocation counts to help improve\n' +
106
+ 'the registry and surface stale skills. No personal data, file contents, or\n' +
107
+ `paths are captured. Full privacy policy: ${chalk.cyan(PRIVACY_URL)}`);
108
+ console.log();
109
+ console.log(chalk.dim(` Anonymous ID tail: ${idTail(updated.anonymousId)}`));
110
+ console.log(chalk.dim(' Disable at any time: ') + chalk.cyan('skillsmith telemetry disable'));
111
+ }
112
+ // ---------------------------------------------------------------------------
113
+ // disable
114
+ // ---------------------------------------------------------------------------
115
+ async function runDisable() {
116
+ const manifest = await loadManifest();
117
+ const t = manifest.telemetry ?? { enabled: false };
118
+ if (!t.enabled) {
119
+ console.log(chalk.dim('Telemetry is already disabled.'));
120
+ return;
121
+ }
122
+ await saveManifest({ ...manifest, telemetry: { ...t, enabled: false } });
123
+ console.log(chalk.yellow('Telemetry disabled.'));
124
+ console.log(chalk.dim(' The anonymous ID is retained in the local manifest for continuity if you re-enable.\n' +
125
+ ' To fully clear it: skillsmith telemetry reset-id'));
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // status
129
+ // ---------------------------------------------------------------------------
130
+ async function runStatus() {
131
+ let manifest = await loadManifest();
132
+ let rotationTriggered = false;
133
+ // Annual auto-rotation (plan line 706, 713)
134
+ if (shouldRotateAnonymousId(manifest)) {
135
+ const rotated = rotateAnonymousId(manifest);
136
+ manifest = { ...manifest, telemetry: rotated };
137
+ await saveManifest(manifest);
138
+ rotationTriggered = true;
139
+ }
140
+ // Sweep expired previous id
141
+ const swept = sweepExpiredPreviousId(manifest);
142
+ if (swept !== manifest.telemetry) {
143
+ manifest = { ...manifest, telemetry: swept };
144
+ await saveManifest(manifest);
145
+ }
146
+ // GC orphan run files (plan line 714)
147
+ gcOrphanRunFiles();
148
+ const t = manifest.telemetry ?? { enabled: false };
149
+ const endpoint = t.endpoint ?? DEFAULT_ENDPOINT;
150
+ let ageDays = null;
151
+ if (t.anonymousIdCreatedAt) {
152
+ ageDays = Math.floor((Date.now() - new Date(t.anonymousIdCreatedAt).getTime()) / 86_400_000);
153
+ }
154
+ console.log(chalk.bold('Skillsmith Telemetry Status'));
155
+ console.log();
156
+ console.log(` Enabled: ${t.enabled ? chalk.green('yes') : chalk.dim('no')}`);
157
+ console.log(` Endpoint: ${chalk.dim(endpoint)}`);
158
+ console.log(` Anonymous ID: ${chalk.dim(idTail(t.anonymousId))}`);
159
+ console.log(` ID age (days): ${ageDays !== null ? String(ageDays) : chalk.dim('n/a')}`);
160
+ console.log(` Scope: ${t.scope ?? chalk.dim('personal')}`);
161
+ if (t.installedAt) {
162
+ console.log(` Hook installed: ${chalk.dim(t.installedAt)}`);
163
+ }
164
+ if (rotationTriggered) {
165
+ console.log();
166
+ console.log(chalk.yellow(' Annual rotation triggered.') +
167
+ chalk.dim(' New ID tail: ') +
168
+ chalk.cyan(idTail(t.anonymousId)));
169
+ if (t.previousAnonymousId) {
170
+ console.log(chalk.dim(` Previous ID retained for 7-day overlap window (retired: ${t.previousAnonymousIdRetiredAt ?? 'unknown'})`));
171
+ }
172
+ }
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // install-hook
176
+ // ---------------------------------------------------------------------------
177
+ async function runInstallHook(options) {
178
+ // Resolve the template source — relative to this file's compiled output.
179
+ // src/commands/telemetry.ts → ../../templates/skill-telemetry.sh (via dist/commands)
180
+ // and one level up for the src layout during tests.
181
+ const templateCandidates = [
182
+ join(__dirname, '..', '..', 'templates', 'skill-telemetry.sh'),
183
+ join(__dirname, '..', 'templates', 'skill-telemetry.sh'),
184
+ ];
185
+ const templateSrc = templateCandidates.find((p) => existsSync(p));
186
+ if (!templateSrc) {
187
+ throw new Error('skill-telemetry.sh template not found. ' +
188
+ 'Ensure the CLI package is fully built: npm run build');
189
+ }
190
+ // Load + validate settings.json — throws TelemetryHookError on foreign Skill matcher
191
+ const scope = options.scope;
192
+ const settings = loadClaudeSettings(scope);
193
+ const hookPath = hookScriptPath();
194
+ const updated = addSkillHookEntries(settings, hookPath);
195
+ // Copy hook script to ~/.skillsmith/hooks/
196
+ const destPath = hookScriptPath();
197
+ const hooksDir = dirname(destPath);
198
+ mkdirSync(hooksDir, { recursive: true, mode: 0o700 });
199
+ copyFileSync(templateSrc, destPath);
200
+ try {
201
+ chmodSync(destPath, 0o755);
202
+ }
203
+ catch {
204
+ // best-effort on Windows
205
+ }
206
+ // Persist settings.json atomically
207
+ writeClaudeSettings(scope, updated);
208
+ // Record installation timestamp in manifest
209
+ const manifest = await loadManifest();
210
+ const t = manifest.telemetry ?? { enabled: false };
211
+ await saveManifest({ ...manifest, telemetry: { ...t, installedAt: new Date().toISOString() } });
212
+ // Update endpoint in manifest if provided
213
+ if (options.endpoint) {
214
+ const m2 = await loadManifest();
215
+ const t2 = m2.telemetry ?? { enabled: false };
216
+ await saveManifest({ ...m2, telemetry: { ...t2, endpoint: options.endpoint } });
217
+ }
218
+ const scopeLabel = scope === 'user' ? '~/.claude/settings.json' : './.claude/settings.json';
219
+ console.log(chalk.green('Skillsmith telemetry hook installed.'));
220
+ console.log(chalk.dim(` Hook script: ${destPath}`));
221
+ console.log(chalk.dim(` Settings file: ${scopeLabel}`));
222
+ console.log();
223
+ console.log(chalk.dim('Enable telemetry to start collecting: ') + chalk.cyan('skillsmith telemetry enable'));
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // uninstall-hook
227
+ // ---------------------------------------------------------------------------
228
+ async function runUninstallHook(options) {
229
+ const scope = options.scope;
230
+ const hookPath = hookScriptPath();
231
+ const settings = loadClaudeSettings(scope);
232
+ const updated = removeSkillHookEntries(settings, hookPath);
233
+ writeClaudeSettings(scope, updated);
234
+ // Optionally remove the script file
235
+ try {
236
+ const scriptPath = hookScriptPath();
237
+ if (existsSync(scriptPath))
238
+ unlinkSync(scriptPath);
239
+ }
240
+ catch {
241
+ // best-effort
242
+ }
243
+ const scopeLabel = scope === 'user' ? '~/.claude/settings.json' : './.claude/settings.json';
244
+ console.log(chalk.yellow('Skillsmith telemetry hook removed.'));
245
+ console.log(chalk.dim(` Removed from: ${scopeLabel}`));
246
+ console.log(chalk.dim(' Foreign hooks (if any) were not touched.'));
247
+ }
248
+ // ---------------------------------------------------------------------------
249
+ // reset-id
250
+ // ---------------------------------------------------------------------------
251
+ async function runResetId() {
252
+ const manifest = await loadManifest();
253
+ const rotated = rotateAnonymousId(manifest); // unconditional rotation
254
+ await saveManifest({ ...manifest, telemetry: rotated });
255
+ console.log(chalk.green('Anonymous ID rotated.'));
256
+ console.log(chalk.dim(` New ID tail: ${idTail(rotated.anonymousId)}`));
257
+ if (rotated.previousAnonymousId) {
258
+ console.log(chalk.dim(` Previous ID tail: ${idTail(rotated.previousAnonymousId)}`));
259
+ console.log(chalk.dim(` Previous ID retirement: ${rotated.previousAnonymousIdRetiredAt ?? 'unknown'} (7-day overlap window)`));
260
+ }
261
+ }
262
+ // ---------------------------------------------------------------------------
263
+ // Command factory
264
+ // ---------------------------------------------------------------------------
265
+ export function createTelemetryCommand() {
266
+ const telemetry = new Command('telemetry').description('Manage Skillsmith telemetry preferences and Claude Code hook installation');
267
+ telemetry
268
+ .command('enable')
269
+ .description('Opt in to anonymous skill-invocation telemetry')
270
+ .action(async () => {
271
+ try {
272
+ await runEnable();
273
+ }
274
+ catch (err) {
275
+ console.error(chalk.red('Error:'), sanitizeError(err));
276
+ process.exit(1);
277
+ }
278
+ });
279
+ telemetry
280
+ .command('disable')
281
+ .description('Opt out of telemetry (anonymous ID is retained for re-enable continuity)')
282
+ .action(async () => {
283
+ try {
284
+ await runDisable();
285
+ }
286
+ catch (err) {
287
+ console.error(chalk.red('Error:'), sanitizeError(err));
288
+ process.exit(1);
289
+ }
290
+ });
291
+ telemetry
292
+ .command('status')
293
+ .description('Show current telemetry state; triggers annual ID rotation if due')
294
+ .action(async () => {
295
+ try {
296
+ await runStatus();
297
+ }
298
+ catch (err) {
299
+ console.error(chalk.red('Error:'), sanitizeError(err));
300
+ process.exit(1);
301
+ }
302
+ });
303
+ telemetry
304
+ .command('install-hook')
305
+ .description('Install the Skill telemetry hook into ~/.claude/settings.json')
306
+ .option('--scope <scope>', 'Settings scope: user (~/.claude/settings.json) or project (./.claude/settings.json)', 'user')
307
+ .option('--endpoint <url>', 'Override the telemetry endpoint (default: prod Supabase events)')
308
+ .action(async (options) => {
309
+ const scope = options.scope === 'project' ? 'project' : 'user';
310
+ try {
311
+ await runInstallHook(options.endpoint !== undefined ? { scope, endpoint: options.endpoint } : { scope });
312
+ }
313
+ catch (err) {
314
+ if (err instanceof TelemetryHookError) {
315
+ console.error(chalk.red(`Error [${err.code}]:`));
316
+ console.error(err.message);
317
+ }
318
+ else {
319
+ console.error(chalk.red('Error:'), sanitizeError(err));
320
+ }
321
+ process.exit(1);
322
+ }
323
+ });
324
+ telemetry
325
+ .command('uninstall-hook')
326
+ .description('Remove the Skillsmith Skill hook entries from settings.json')
327
+ .option('--scope <scope>', 'Settings scope: user or project', 'user')
328
+ .action(async (options) => {
329
+ const scope = options.scope === 'project' ? 'project' : 'user';
330
+ try {
331
+ await runUninstallHook({ scope });
332
+ }
333
+ catch (err) {
334
+ console.error(chalk.red('Error:'), sanitizeError(err));
335
+ process.exit(1);
336
+ }
337
+ });
338
+ telemetry
339
+ .command('reset-id')
340
+ .description('Immediately rotate the anonymous ID (previous ID kept for 7-day overlap window)')
341
+ .action(async () => {
342
+ try {
343
+ await runResetId();
344
+ }
345
+ catch (err) {
346
+ console.error(chalk.red('Error:'), sanitizeError(err));
347
+ process.exit(1);
348
+ }
349
+ });
350
+ return telemetry;
351
+ }
352
+ // Internal exports for tests
353
+ export { runEnable, runDisable, runStatus, runInstallHook, runUninstallHook, runResetId, idTail };
354
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../../src/commands/telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @fileoverview Tests for `skillsmith telemetry` subcommands.
3
+ * @module @skillsmith/cli/commands/telemetry.test
4
+ * @see SMI-5021 Wave 3 Step 2 — plan lines 700, 703, 706
5
+ *
6
+ * Covers:
7
+ * enable: fresh manifest → generates id, sets enabled=true; idempotent on re-run
8
+ * disable: enabled manifest → sets enabled=false; anonymousId preserved
9
+ * status: prints expected shape (ID tail only); detects + triggers rotation when backdated >365d
10
+ * reset-id: rotates unconditionally; new id distinct; previous id populated
11
+ * install-hook (mocked): adds PreToolUse+PostToolUse; idempotent; throws on foreign Skill matcher
12
+ * uninstall-hook: removes only Skillsmith entries; preserves foreign hooks
13
+ * install-hook --scope project: writes to ./.claude/settings.json
14
+ *
15
+ * Privacy invariant: idTail() — last 8 chars only, never full id.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=telemetry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.test.d.ts","sourceRoot":"","sources":["../../../src/commands/telemetry.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}