@code-insights/cli 1.2.0 → 2.1.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 (159) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +101 -9
  3. package/dist/commands/config.d.ts +3 -0
  4. package/dist/commands/config.d.ts.map +1 -0
  5. package/dist/commands/config.js +130 -0
  6. package/dist/commands/config.js.map +1 -0
  7. package/dist/commands/connect.d.ts.map +1 -1
  8. package/dist/commands/connect.js +7 -1
  9. package/dist/commands/connect.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +52 -0
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/install-hook.d.ts.map +1 -1
  14. package/dist/commands/install-hook.js +16 -3
  15. package/dist/commands/install-hook.js.map +1 -1
  16. package/dist/commands/open.d.ts +9 -0
  17. package/dist/commands/open.d.ts.map +1 -0
  18. package/dist/commands/open.js +59 -0
  19. package/dist/commands/open.js.map +1 -0
  20. package/dist/commands/reset.d.ts.map +1 -1
  21. package/dist/commands/reset.js +14 -1
  22. package/dist/commands/reset.js.map +1 -1
  23. package/dist/commands/stats/actions/cost.d.ts +3 -0
  24. package/dist/commands/stats/actions/cost.d.ts.map +1 -0
  25. package/dist/commands/stats/actions/cost.js +139 -0
  26. package/dist/commands/stats/actions/cost.js.map +1 -0
  27. package/dist/commands/stats/actions/error-handler.d.ts +6 -0
  28. package/dist/commands/stats/actions/error-handler.d.ts.map +1 -0
  29. package/dist/commands/stats/actions/error-handler.js +42 -0
  30. package/dist/commands/stats/actions/error-handler.js.map +1 -0
  31. package/dist/commands/stats/actions/models.d.ts +3 -0
  32. package/dist/commands/stats/actions/models.d.ts.map +1 -0
  33. package/dist/commands/stats/actions/models.js +101 -0
  34. package/dist/commands/stats/actions/models.js.map +1 -0
  35. package/dist/commands/stats/actions/overview.d.ts +3 -0
  36. package/dist/commands/stats/actions/overview.d.ts.map +1 -0
  37. package/dist/commands/stats/actions/overview.js +147 -0
  38. package/dist/commands/stats/actions/overview.js.map +1 -0
  39. package/dist/commands/stats/actions/projects.d.ts +3 -0
  40. package/dist/commands/stats/actions/projects.d.ts.map +1 -0
  41. package/dist/commands/stats/actions/projects.js +90 -0
  42. package/dist/commands/stats/actions/projects.js.map +1 -0
  43. package/dist/commands/stats/actions/today.d.ts +3 -0
  44. package/dist/commands/stats/actions/today.d.ts.map +1 -0
  45. package/dist/commands/stats/actions/today.js +118 -0
  46. package/dist/commands/stats/actions/today.js.map +1 -0
  47. package/dist/commands/stats/data/aggregation.d.ts +60 -0
  48. package/dist/commands/stats/data/aggregation.d.ts.map +1 -0
  49. package/dist/commands/stats/data/aggregation.js +614 -0
  50. package/dist/commands/stats/data/aggregation.js.map +1 -0
  51. package/dist/commands/stats/data/cache.d.ts +29 -0
  52. package/dist/commands/stats/data/cache.d.ts.map +1 -0
  53. package/dist/commands/stats/data/cache.js +197 -0
  54. package/dist/commands/stats/data/cache.js.map +1 -0
  55. package/dist/commands/stats/data/firestore.d.ts +13 -0
  56. package/dist/commands/stats/data/firestore.d.ts.map +1 -0
  57. package/dist/commands/stats/data/firestore.js +170 -0
  58. package/dist/commands/stats/data/firestore.js.map +1 -0
  59. package/dist/commands/stats/data/fuzzy-match.d.ts +5 -0
  60. package/dist/commands/stats/data/fuzzy-match.d.ts.map +1 -0
  61. package/dist/commands/stats/data/fuzzy-match.js +36 -0
  62. package/dist/commands/stats/data/fuzzy-match.js.map +1 -0
  63. package/dist/commands/stats/data/local.d.ts +12 -0
  64. package/dist/commands/stats/data/local.d.ts.map +1 -0
  65. package/dist/commands/stats/data/local.js +81 -0
  66. package/dist/commands/stats/data/local.js.map +1 -0
  67. package/dist/commands/stats/data/source.d.ts +13 -0
  68. package/dist/commands/stats/data/source.d.ts.map +1 -0
  69. package/dist/commands/stats/data/source.js +41 -0
  70. package/dist/commands/stats/data/source.js.map +1 -0
  71. package/dist/commands/stats/data/types.d.ts +229 -0
  72. package/dist/commands/stats/data/types.d.ts.map +1 -0
  73. package/dist/commands/stats/data/types.js +50 -0
  74. package/dist/commands/stats/data/types.js.map +1 -0
  75. package/dist/commands/stats/index.d.ts +3 -0
  76. package/dist/commands/stats/index.d.ts.map +1 -0
  77. package/dist/commands/stats/index.js +41 -0
  78. package/dist/commands/stats/index.js.map +1 -0
  79. package/dist/commands/stats/render/charts.d.ts +9 -0
  80. package/dist/commands/stats/render/charts.d.ts.map +1 -0
  81. package/dist/commands/stats/render/charts.js +45 -0
  82. package/dist/commands/stats/render/charts.js.map +1 -0
  83. package/dist/commands/stats/render/colors.d.ts +21 -0
  84. package/dist/commands/stats/render/colors.d.ts.map +1 -0
  85. package/dist/commands/stats/render/colors.js +47 -0
  86. package/dist/commands/stats/render/colors.js.map +1 -0
  87. package/dist/commands/stats/render/format.d.ts +10 -0
  88. package/dist/commands/stats/render/format.d.ts.map +1 -0
  89. package/dist/commands/stats/render/format.js +57 -0
  90. package/dist/commands/stats/render/format.js.map +1 -0
  91. package/dist/commands/stats/render/layout.d.ts +10 -0
  92. package/dist/commands/stats/render/layout.d.ts.map +1 -0
  93. package/dist/commands/stats/render/layout.js +48 -0
  94. package/dist/commands/stats/render/layout.js.map +1 -0
  95. package/dist/commands/stats/shared.d.ts +6 -0
  96. package/dist/commands/stats/shared.d.ts.map +1 -0
  97. package/dist/commands/stats/shared.js +26 -0
  98. package/dist/commands/stats/shared.js.map +1 -0
  99. package/dist/commands/status.d.ts.map +1 -1
  100. package/dist/commands/status.js +50 -37
  101. package/dist/commands/status.js.map +1 -1
  102. package/dist/commands/sync.d.ts +16 -1
  103. package/dist/commands/sync.d.ts.map +1 -1
  104. package/dist/commands/sync.js +196 -79
  105. package/dist/commands/sync.js.map +1 -1
  106. package/dist/commands/telemetry.d.ts +3 -0
  107. package/dist/commands/telemetry.d.ts.map +1 -0
  108. package/dist/commands/telemetry.js +106 -0
  109. package/dist/commands/telemetry.js.map +1 -0
  110. package/dist/firebase/client.d.ts +5 -0
  111. package/dist/firebase/client.d.ts.map +1 -1
  112. package/dist/firebase/client.js +5 -2
  113. package/dist/firebase/client.js.map +1 -1
  114. package/dist/index.js +21 -4
  115. package/dist/index.js.map +1 -1
  116. package/dist/parser/jsonl.js +3 -2
  117. package/dist/parser/jsonl.js.map +1 -1
  118. package/dist/providers/claude-code.d.ts.map +1 -1
  119. package/dist/providers/claude-code.js +5 -1
  120. package/dist/providers/claude-code.js.map +1 -1
  121. package/dist/providers/codex.d.ts +14 -0
  122. package/dist/providers/codex.d.ts.map +1 -0
  123. package/dist/providers/codex.js +364 -0
  124. package/dist/providers/codex.js.map +1 -0
  125. package/dist/providers/copilot-cli.d.ts +14 -0
  126. package/dist/providers/copilot-cli.d.ts.map +1 -0
  127. package/dist/providers/copilot-cli.js +363 -0
  128. package/dist/providers/copilot-cli.js.map +1 -0
  129. package/dist/providers/cursor.d.ts +27 -0
  130. package/dist/providers/cursor.d.ts.map +1 -0
  131. package/dist/providers/cursor.js +446 -0
  132. package/dist/providers/cursor.js.map +1 -0
  133. package/dist/providers/registry.d.ts +0 -4
  134. package/dist/providers/registry.d.ts.map +1 -1
  135. package/dist/providers/registry.js +9 -6
  136. package/dist/providers/registry.js.map +1 -1
  137. package/dist/types.d.ts +5 -1
  138. package/dist/types.d.ts.map +1 -1
  139. package/dist/utils/config.d.ts +10 -1
  140. package/dist/utils/config.d.ts.map +1 -1
  141. package/dist/utils/config.js +21 -0
  142. package/dist/utils/config.js.map +1 -1
  143. package/dist/utils/paths.d.ts +10 -0
  144. package/dist/utils/paths.d.ts.map +1 -0
  145. package/dist/utils/paths.js +16 -0
  146. package/dist/utils/paths.js.map +1 -0
  147. package/dist/utils/telemetry.d.ts +52 -0
  148. package/dist/utils/telemetry.d.ts.map +1 -0
  149. package/dist/utils/telemetry.js +304 -0
  150. package/dist/utils/telemetry.js.map +1 -0
  151. package/dist/utils/tips.d.ts +11 -0
  152. package/dist/utils/tips.d.ts.map +1 -0
  153. package/dist/utils/tips.js +106 -0
  154. package/dist/utils/tips.js.map +1 -0
  155. package/dist/utils/welcome.d.ts +11 -0
  156. package/dist/utils/welcome.d.ts.map +1 -0
  157. package/dist/utils/welcome.js +72 -0
  158. package/dist/utils/welcome.js.map +1 -0
  159. package/package.json +9 -2
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Extract real filesystem path and optional session fragment from a virtual path.
3
+ * Virtual paths use '#' delimiter: "/path/to/state.vscdb#composerId"
4
+ * Regular paths pass through unchanged.
5
+ */
6
+ export declare function splitVirtualPath(filePath: string): {
7
+ realPath: string;
8
+ sessionFragment: string | null;
9
+ };
10
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CASvG"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extract real filesystem path and optional session fragment from a virtual path.
3
+ * Virtual paths use '#' delimiter: "/path/to/state.vscdb#composerId"
4
+ * Regular paths pass through unchanged.
5
+ */
6
+ export function splitVirtualPath(filePath) {
7
+ const hashIndex = filePath.lastIndexOf('#');
8
+ if (hashIndex > 0) {
9
+ return {
10
+ realPath: filePath.slice(0, hashIndex),
11
+ sessionFragment: filePath.slice(hashIndex + 1),
12
+ };
13
+ }
14
+ return { realPath: filePath, sessionFragment: null };
15
+ }
16
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;YACtC,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;SAC/C,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,52 @@
1
+ export interface TelemetryEvent {
2
+ machineId: string;
3
+ command: string;
4
+ subcommand?: string;
5
+ success: boolean;
6
+ cliVersion: string;
7
+ nodeVersion: string;
8
+ os: string;
9
+ arch: string;
10
+ providers: string[];
11
+ sessionCountBucket: string;
12
+ dataSource: string;
13
+ hasHook: boolean;
14
+ timestamp: string;
15
+ }
16
+ /**
17
+ * Check if telemetry is enabled.
18
+ *
19
+ * Check order (first match wins):
20
+ * 1. CODE_INSIGHTS_TELEMETRY_DISABLED=1 env var — respects CI/automation opt-out
21
+ * 2. DO_NOT_TRACK=1 env var — respects the community standard
22
+ * 3. config.telemetry field — user's explicit preference
23
+ * 4. Default: true (opt-out model)
24
+ */
25
+ export declare function isTelemetryEnabled(): boolean;
26
+ /**
27
+ * Show the one-time telemetry disclosure notice if it hasn't been shown yet.
28
+ *
29
+ * Uses a touch file at ~/.code-insights/.telemetry-notice-shown to track state.
30
+ * Only displays if telemetry is currently enabled — no point disclosing disabled telemetry.
31
+ *
32
+ * Returns true if the notice was shown, false if it was already shown or telemetry is off.
33
+ */
34
+ export declare function showTelemetryNoticeIfNeeded(): boolean;
35
+ /**
36
+ * Fire-and-forget telemetry event. Does NOT block the calling command.
37
+ *
38
+ * Design principles:
39
+ * - Never awaited: the caller doesn't wait for this
40
+ * - 2s AbortController timeout: we don't hang if the endpoint is slow
41
+ * - Swallows ALL errors: network failures, JSON errors, anything — telemetry
42
+ * must never cause a CLI command to fail
43
+ * - No retries: if it fails, it fails. Reliability of individual events
44
+ * matters less than not disrupting the user's workflow.
45
+ */
46
+ export declare function trackEvent(command: string, success: boolean, subcommand?: string): void;
47
+ /**
48
+ * Build the event payload that would be sent for the given command.
49
+ * Exported for use by `telemetry status` to show a preview without sending.
50
+ */
51
+ export declare function buildEventPreview(command: string): TelemetryEvent;
52
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/utils/telemetry.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAY5C;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CA4BrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAevF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAEjE"}
@@ -0,0 +1,304 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import * as crypto from 'crypto';
5
+ import { createRequire } from 'module';
6
+ import { loadConfig, getConfigDir, getClaudeDir } from './config.js';
7
+ const TELEMETRY_ENDPOINT = 'https://xrbkoqjfolxiyfxubiom.supabase.co/functions/v1/cli-telemetry';
8
+ // Touch file path that tracks whether the one-time disclosure has been shown
9
+ const NOTICE_FILE = path.join(getConfigDir(), '.telemetry-notice-shown');
10
+ /**
11
+ * Check if telemetry is enabled.
12
+ *
13
+ * Check order (first match wins):
14
+ * 1. CODE_INSIGHTS_TELEMETRY_DISABLED=1 env var — respects CI/automation opt-out
15
+ * 2. DO_NOT_TRACK=1 env var — respects the community standard
16
+ * 3. config.telemetry field — user's explicit preference
17
+ * 4. Default: true (opt-out model)
18
+ */
19
+ export function isTelemetryEnabled() {
20
+ if (process.env.CODE_INSIGHTS_TELEMETRY_DISABLED === '1')
21
+ return false;
22
+ if (process.env.DO_NOT_TRACK === '1')
23
+ return false;
24
+ const config = loadConfig();
25
+ // If explicitly set in config, respect it
26
+ if (config !== null && typeof config.telemetry === 'boolean') {
27
+ return config.telemetry;
28
+ }
29
+ // Default: enabled (opt-out model)
30
+ return true;
31
+ }
32
+ /**
33
+ * Show the one-time telemetry disclosure notice if it hasn't been shown yet.
34
+ *
35
+ * Uses a touch file at ~/.code-insights/.telemetry-notice-shown to track state.
36
+ * Only displays if telemetry is currently enabled — no point disclosing disabled telemetry.
37
+ *
38
+ * Returns true if the notice was shown, false if it was already shown or telemetry is off.
39
+ */
40
+ export function showTelemetryNoticeIfNeeded() {
41
+ if (!isTelemetryEnabled())
42
+ return false;
43
+ if (fs.existsSync(NOTICE_FILE))
44
+ return false;
45
+ // Show the disclosure banner
46
+ console.log('');
47
+ console.log(' Code Insights collects anonymous usage data to improve the CLI.');
48
+ console.log(' Includes: command name, OS, CLI version, AI tool types.');
49
+ console.log(' Never includes: file paths, project names, session content, or personal data.');
50
+ console.log('');
51
+ console.log(' Disable: code-insights telemetry disable');
52
+ console.log(' Details: code-insights telemetry status');
53
+ console.log('');
54
+ // Mark notice as shown — best-effort write, ignore failures
55
+ try {
56
+ // Ensure config dir exists before writing touch file
57
+ const configDir = getConfigDir();
58
+ if (!fs.existsSync(configDir)) {
59
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
60
+ }
61
+ fs.writeFileSync(NOTICE_FILE, '', { encoding: 'utf-8', mode: 0o600 });
62
+ }
63
+ catch {
64
+ // Non-fatal — if we can't write the touch file, we'll show the notice again
65
+ // next time. That's acceptable; we don't want to break the CLI over this.
66
+ }
67
+ return true;
68
+ }
69
+ /**
70
+ * Fire-and-forget telemetry event. Does NOT block the calling command.
71
+ *
72
+ * Design principles:
73
+ * - Never awaited: the caller doesn't wait for this
74
+ * - 2s AbortController timeout: we don't hang if the endpoint is slow
75
+ * - Swallows ALL errors: network failures, JSON errors, anything — telemetry
76
+ * must never cause a CLI command to fail
77
+ * - No retries: if it fails, it fails. Reliability of individual events
78
+ * matters less than not disrupting the user's workflow.
79
+ */
80
+ export function trackEvent(command, success, subcommand) {
81
+ if (!isTelemetryEnabled())
82
+ return;
83
+ // Build the event synchronously — filesystem reads happen here
84
+ let event;
85
+ try {
86
+ event = buildEvent(command, success, subcommand);
87
+ }
88
+ catch {
89
+ // If building the event fails (e.g., filesystem read error), silently skip
90
+ return;
91
+ }
92
+ // Fire-and-forget: intentionally not awaited
93
+ // The void cast suppresses the "floating promise" lint warning
94
+ void sendEvent(event);
95
+ }
96
+ /**
97
+ * Build the event payload that would be sent for the given command.
98
+ * Exported for use by `telemetry status` to show a preview without sending.
99
+ */
100
+ export function buildEventPreview(command) {
101
+ return buildEvent(command, true);
102
+ }
103
+ // ---------------------------------------------------------------------------
104
+ // Internal helpers
105
+ // ---------------------------------------------------------------------------
106
+ /**
107
+ * Build a TelemetryEvent with all fields populated.
108
+ * Separated from sendEvent so buildEventPreview can reuse it without sending.
109
+ */
110
+ function buildEvent(command, success, subcommand) {
111
+ return {
112
+ machineId: getMachineId(),
113
+ command,
114
+ subcommand,
115
+ success,
116
+ cliVersion: getCliVersion(),
117
+ nodeVersion: process.version.replace('v', ''),
118
+ os: process.platform,
119
+ arch: process.arch,
120
+ providers: detectProviders(),
121
+ sessionCountBucket: getSessionCountBucket(),
122
+ dataSource: getDataSource(),
123
+ hasHook: detectHook(),
124
+ // Day precision only — avoids time-of-day behavioral fingerprinting
125
+ timestamp: new Date().toISOString().slice(0, 10),
126
+ };
127
+ }
128
+ /**
129
+ * Anonymous machine ID, rotated monthly.
130
+ *
131
+ * Format: SHA-256(hostname:username:code-insights-YYYY-MM).slice(0, 16)
132
+ *
133
+ * Monthly rotation ensures:
134
+ * - Long-term tracking is not possible across months
135
+ * - Events within a month can be correlated for "unique users" metrics
136
+ * - No PII: hostname and username are never sent, only their hash
137
+ */
138
+ function getMachineId() {
139
+ const now = new Date();
140
+ // YYYY-MM format for monthly rotation
141
+ const monthSalt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
142
+ let username;
143
+ try {
144
+ username = os.userInfo().username;
145
+ }
146
+ catch {
147
+ // os.userInfo() throws in Docker/CI when UID has no /etc/passwd entry
148
+ username = `uid-${process.getuid?.() ?? 'unknown'}`;
149
+ }
150
+ const input = [os.hostname(), username, `code-insights-${monthSalt}`].join(':');
151
+ return crypto.createHash('sha256').update(input).digest('hex').slice(0, 16);
152
+ }
153
+ /**
154
+ * Read CLI version from package.json.
155
+ * Uses createRequire for JSON imports since this is ESM and JSON imports
156
+ * have inconsistent support across Node versions and bundlers.
157
+ */
158
+ function getCliVersion() {
159
+ try {
160
+ const require = createRequire(import.meta.url);
161
+ // package.json is two levels up from src/utils/ -> src/ -> cli/
162
+ const pkg = require('../../package.json');
163
+ return pkg.version;
164
+ }
165
+ catch {
166
+ return 'unknown';
167
+ }
168
+ }
169
+ /**
170
+ * Detect which AI coding tool data directories exist on this machine.
171
+ * We check for the existence of known directories — we never read their contents.
172
+ *
173
+ * Returns only the tools that are actually present. This tells us which tools
174
+ * users are running alongside code-insights, helping us prioritize provider support.
175
+ */
176
+ function detectProviders() {
177
+ const home = os.homedir();
178
+ const detected = [];
179
+ // Claude Code: ~/.claude/projects/
180
+ if (fs.existsSync(path.join(home, '.claude', 'projects'))) {
181
+ detected.push('claude-code');
182
+ }
183
+ // Cursor: workspace storage directory (cross-platform path)
184
+ const cursorStoragePaths = [
185
+ // macOS
186
+ path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage'),
187
+ // Linux
188
+ path.join(home, '.config', 'Cursor', 'User', 'workspaceStorage'),
189
+ // Windows
190
+ path.join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'workspaceStorage'),
191
+ ];
192
+ if (cursorStoragePaths.some((p) => fs.existsSync(p))) {
193
+ detected.push('cursor');
194
+ }
195
+ // Codex CLI: ~/.codex/sessions
196
+ if (fs.existsSync(path.join(home, '.codex', 'sessions'))) {
197
+ detected.push('codex-cli');
198
+ }
199
+ // GitHub Copilot CLI: ~/.copilot/session-state
200
+ if (fs.existsSync(path.join(home, '.copilot', 'session-state'))) {
201
+ detected.push('copilot-cli');
202
+ }
203
+ return detected;
204
+ }
205
+ /**
206
+ * Count .jsonl files under ~/.claude/projects/ and bucket the count.
207
+ *
208
+ * Buckets are intentionally coarse — we want to understand "heavy vs light"
209
+ * usage without counting exact files, which could vary wildly and feels more
210
+ * private than a range.
211
+ */
212
+ function getSessionCountBucket() {
213
+ try {
214
+ const claudeDir = getClaudeDir();
215
+ if (!fs.existsSync(claudeDir))
216
+ return '0';
217
+ // Count all .jsonl files recursively under the Claude projects directory
218
+ let count = 0;
219
+ const walk = (dir) => {
220
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
221
+ for (const entry of entries) {
222
+ if (entry.isDirectory()) {
223
+ walk(path.join(dir, entry.name));
224
+ }
225
+ else if (entry.name.endsWith('.jsonl')) {
226
+ count++;
227
+ }
228
+ }
229
+ };
230
+ walk(claudeDir);
231
+ // Coarse buckets for privacy — exact session counts are never sent
232
+ if (count === 0)
233
+ return '0';
234
+ if (count <= 10)
235
+ return '1-10';
236
+ if (count <= 50)
237
+ return '11-50';
238
+ if (count <= 200)
239
+ return '51-200';
240
+ return '200+';
241
+ }
242
+ catch {
243
+ return 'unknown';
244
+ }
245
+ }
246
+ /**
247
+ * Determine the configured data source preference.
248
+ * Returns 'none' if no config exists at all.
249
+ */
250
+ function getDataSource() {
251
+ const config = loadConfig();
252
+ if (!config)
253
+ return 'none';
254
+ if (config.dataSource)
255
+ return config.dataSource;
256
+ // Infer from credentials: if Firebase is configured, they're likely using it
257
+ if (config.firebase?.projectId)
258
+ return 'firebase';
259
+ return 'local';
260
+ }
261
+ /**
262
+ * Check if code-insights is registered as a Claude Code hook.
263
+ *
264
+ * Reads ~/.claude/settings.json and looks for 'code-insights' anywhere in the
265
+ * file content. A hook registration means the user has automated sync on session end.
266
+ */
267
+ function detectHook() {
268
+ try {
269
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
270
+ if (!fs.existsSync(settingsPath))
271
+ return false;
272
+ const content = fs.readFileSync(settingsPath, 'utf-8');
273
+ return content.includes('code-insights');
274
+ }
275
+ catch {
276
+ return false;
277
+ }
278
+ }
279
+ /**
280
+ * Internal: Send the event to the telemetry endpoint.
281
+ * AbortController ensures we don't hang longer than 2 seconds.
282
+ * All errors are swallowed — telemetry failures must never propagate.
283
+ */
284
+ async function sendEvent(event) {
285
+ const controller = new AbortController();
286
+ // 2s timeout — enough for a healthy network, short enough to not delay anything
287
+ const timer = setTimeout(() => controller.abort(), 2000);
288
+ try {
289
+ await fetch(TELEMETRY_ENDPOINT, {
290
+ method: 'POST',
291
+ headers: { 'Content-Type': 'application/json' },
292
+ body: JSON.stringify(event),
293
+ signal: controller.signal,
294
+ });
295
+ }
296
+ catch {
297
+ // Swallow everything: network errors, AbortError, JSON serialization errors.
298
+ // Telemetry failures are silent — the CLI command already completed.
299
+ }
300
+ finally {
301
+ clearTimeout(timer);
302
+ }
303
+ }
304
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/utils/telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Show a rotating contextual tip after a CLI command.
3
+ *
4
+ * Tips are suppressed once a command has accumulated MAX_TIPS_PER_COMMAND
5
+ * displays, so they phase out naturally after the user's first few runs.
6
+ * All state is stored in ~/.code-insights/.tips-state.json.
7
+ *
8
+ * Returns the tip string if one was printed, or null if suppressed.
9
+ */
10
+ export declare function showTip(command: string): string | null;
11
+ //# sourceMappingURL=tips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tips.d.ts","sourceRoot":"","sources":["../../src/utils/tips.ts"],"names":[],"mappings":"AA8EA;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+BtD"}
@@ -0,0 +1,106 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { ensureConfigDir, getConfigDir } from './config.js';
5
+ // Tips stop showing after this many displays per command.
6
+ // Five is enough to surface the tip on early uses without becoming annoying.
7
+ const MAX_TIPS_PER_COMMAND = 5;
8
+ const TIPS_STATE_FILE = '.tips-state.json';
9
+ /**
10
+ * Per-command tip arrays. Tips cycle with modulo so they don't repeat
11
+ * in strict sequence once the list wraps — they're educational hints,
12
+ * not a tutorial that must be seen in order.
13
+ */
14
+ const TIPS = {
15
+ stats: [
16
+ 'Try `code-insights stats cost` for a cost and token breakdown by session',
17
+ 'Try `code-insights stats today` to see only today\'s activity',
18
+ 'Try `code-insights stats models` to compare usage across AI models',
19
+ 'Try `code-insights stats projects` to see which projects you\'ve worked on most',
20
+ 'Run `code-insights init` to sync sessions to Firebase and unlock the web dashboard',
21
+ ],
22
+ 'stats cost': [
23
+ 'Use `--period 30d` to see cost over the last 30 days (default is 7d)',
24
+ 'Try `code-insights stats models` to break down cost by model',
25
+ 'Try `code-insights stats` for a full activity overview',
26
+ ],
27
+ 'stats today': [
28
+ 'Try `code-insights stats` for a full activity overview across all time',
29
+ 'Try `code-insights stats cost` to see what today\'s sessions cost',
30
+ ],
31
+ 'stats projects': [
32
+ 'Use `--project <name>` to filter sessions to a specific project',
33
+ 'Try `code-insights stats cost` to see how spend is distributed across projects',
34
+ ],
35
+ 'stats models': [
36
+ 'Try `code-insights stats cost` to see per-session cost alongside model usage',
37
+ 'Try `code-insights stats` for the full overview including all activity',
38
+ ],
39
+ };
40
+ /**
41
+ * Load tips state from ~/.code-insights/.tips-state.json.
42
+ * Returns an empty state on any I/O or parse error — tips are non-critical.
43
+ */
44
+ function loadTipsState() {
45
+ try {
46
+ const file = path.join(getConfigDir(), TIPS_STATE_FILE);
47
+ if (!fs.existsSync(file)) {
48
+ return { shown: {} };
49
+ }
50
+ const content = fs.readFileSync(file, 'utf-8');
51
+ return JSON.parse(content);
52
+ }
53
+ catch {
54
+ return { shown: {} };
55
+ }
56
+ }
57
+ /**
58
+ * Persist tips state. Silently swallows errors — a failed write just
59
+ * means the same tip might show again next run, which is acceptable.
60
+ */
61
+ function saveTipsState(state) {
62
+ try {
63
+ ensureConfigDir();
64
+ const file = path.join(getConfigDir(), TIPS_STATE_FILE);
65
+ fs.writeFileSync(file, JSON.stringify(state, null, 2), { mode: 0o600 });
66
+ }
67
+ catch {
68
+ // Non-critical — swallow silently
69
+ }
70
+ }
71
+ /**
72
+ * Show a rotating contextual tip after a CLI command.
73
+ *
74
+ * Tips are suppressed once a command has accumulated MAX_TIPS_PER_COMMAND
75
+ * displays, so they phase out naturally after the user's first few runs.
76
+ * All state is stored in ~/.code-insights/.tips-state.json.
77
+ *
78
+ * Returns the tip string if one was printed, or null if suppressed.
79
+ */
80
+ export function showTip(command) {
81
+ try {
82
+ const tips = TIPS[command];
83
+ // No tips defined for this command — nothing to show
84
+ if (!tips || tips.length === 0) {
85
+ return null;
86
+ }
87
+ const state = loadTipsState();
88
+ const count = state.shown[command] ?? 0;
89
+ // User has seen enough tips for this command — stop showing them
90
+ if (count >= MAX_TIPS_PER_COMMAND) {
91
+ return null;
92
+ }
93
+ // Cycle through tips so we don't always repeat the first one
94
+ const tip = tips[count % tips.length];
95
+ const formatted = chalk.gray(`\n Tip: ${tip}`);
96
+ console.log(formatted);
97
+ state.shown[command] = count + 1;
98
+ saveTipsState(state);
99
+ return formatted;
100
+ }
101
+ catch {
102
+ // Tips are non-critical — swallow all errors silently
103
+ return null;
104
+ }
105
+ }
106
+ //# sourceMappingURL=tips.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tips.js","sourceRoot":"","sources":["../../src/utils/tips.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Show a one-time welcome banner for first-time users.
3
+ *
4
+ * Only fires when no ~/.code-insights/.welcome-shown marker exists.
5
+ * Intentionally non-critical — all file I/O is wrapped so errors
6
+ * are swallowed silently rather than interrupting the user's command.
7
+ *
8
+ * Returns true if the banner was printed, false if already shown.
9
+ */
10
+ export declare function showWelcomeIfFirstRun(): Promise<boolean>;
11
+ //# sourceMappingURL=welcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../src/utils/welcome.ts"],"names":[],"mappings":"AAQA;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAmC9D"}
@@ -0,0 +1,72 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { ensureConfigDir, getConfigDir } from './config.js';
5
+ import { getAllProviders } from '../providers/registry.js';
6
+ const WELCOME_MARKER = '.welcome-shown';
7
+ /**
8
+ * Show a one-time welcome banner for first-time users.
9
+ *
10
+ * Only fires when no ~/.code-insights/.welcome-shown marker exists.
11
+ * Intentionally non-critical — all file I/O is wrapped so errors
12
+ * are swallowed silently rather than interrupting the user's command.
13
+ *
14
+ * Returns true if the banner was printed, false if already shown.
15
+ */
16
+ export async function showWelcomeIfFirstRun() {
17
+ try {
18
+ const markerPath = path.join(getConfigDir(), WELCOME_MARKER);
19
+ // Already greeted this user — bail out fast
20
+ if (fs.existsSync(markerPath)) {
21
+ return false;
22
+ }
23
+ const sessionCount = await countAllSessions();
24
+ console.log('');
25
+ console.log(chalk.bold.cyan(' Welcome to Code Insights!'));
26
+ console.log('');
27
+ if (sessionCount > 0) {
28
+ console.log(chalk.dim(' Found ') +
29
+ chalk.white.bold(sessionCount) +
30
+ chalk.dim(` session${sessionCount === 1 ? '' : 's'} across your dev tools`));
31
+ }
32
+ else {
33
+ console.log(chalk.dim(' No sessions found yet across your dev tools'));
34
+ }
35
+ console.log('');
36
+ // Touch the marker so we never show this again
37
+ touchWelcomeMarker(markerPath);
38
+ return true;
39
+ }
40
+ catch {
41
+ // Welcome is non-critical — swallow all errors silently
42
+ return false;
43
+ }
44
+ }
45
+ /**
46
+ * Count total sessions across all registered providers.
47
+ * Uses discover() which is fast (file/DB scan, no parsing).
48
+ */
49
+ async function countAllSessions() {
50
+ const providers = getAllProviders();
51
+ let total = 0;
52
+ await Promise.allSettled(providers.map(async (provider) => {
53
+ try {
54
+ const paths = await provider.discover();
55
+ total += paths.length;
56
+ }
57
+ catch {
58
+ // Provider unavailable or errored — skip it, keep counting
59
+ }
60
+ }));
61
+ return total;
62
+ }
63
+ /**
64
+ * Create the welcome-shown marker file.
65
+ * Ensures the config directory exists first (handles brand-new installs
66
+ * where no config has been written yet).
67
+ */
68
+ function touchWelcomeMarker(markerPath) {
69
+ ensureConfigDir();
70
+ fs.writeFileSync(markerPath, '', { mode: 0o600 });
71
+ }
72
+ //# sourceMappingURL=welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/utils/welcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,cAAc,CAAC,CAAC;QAE7D,4CAA4C;QAC5C,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAE9C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;gBACrB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC;gBAC9B,KAAK,CAAC,GAAG,CAAC,WAAW,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAC5E,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,CAAC;QAC1E,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,+CAA+C;QAC/C,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAE/B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,gBAAgB;IAC7B,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,OAAO,CAAC,UAAU,CACtB,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;QAC7D,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,UAAkB;IAC5C,eAAe,EAAE,CAAC;IAClB,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACpD,CAAC"}