@code-insights/cli 1.2.0 → 2.0.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 +155 -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 +77 -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 +173 -80
  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 +356 -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 +101 -0
  158. package/dist/utils/welcome.js.map +1 -0
  159. package/package.json +9 -2
@@ -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(): 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":"AAOA;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAqC/C"}
@@ -0,0 +1,101 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import chalk from 'chalk';
4
+ import { ensureConfigDir, getClaudeDir, getConfigDir } from './config.js';
5
+ const WELCOME_MARKER = '.welcome-shown';
6
+ /**
7
+ * Show a one-time welcome banner for first-time users.
8
+ *
9
+ * Only fires when no ~/.code-insights/.welcome-shown marker exists.
10
+ * Intentionally non-critical — all file I/O is wrapped so errors
11
+ * are swallowed silently rather than interrupting the user's command.
12
+ *
13
+ * Returns true if the banner was printed, false if already shown.
14
+ */
15
+ export function showWelcomeIfFirstRun() {
16
+ try {
17
+ const markerPath = path.join(getConfigDir(), WELCOME_MARKER);
18
+ // Already greeted this user — bail out fast
19
+ if (fs.existsSync(markerPath)) {
20
+ return false;
21
+ }
22
+ const { sessionCount, projectCount } = countClaudeSessions();
23
+ console.log('');
24
+ console.log(chalk.bold.cyan(' Welcome to Code Insights!'));
25
+ console.log('');
26
+ if (sessionCount > 0) {
27
+ console.log(chalk.dim(' Found ') +
28
+ chalk.white.bold(sessionCount) +
29
+ chalk.dim(` session${sessionCount === 1 ? '' : 's'} across `) +
30
+ chalk.white.bold(projectCount) +
31
+ chalk.dim(` project${projectCount === 1 ? '' : 's'} in ~/.claude/projects`));
32
+ }
33
+ else {
34
+ console.log(chalk.dim(' No sessions found yet in ~/.claude/projects'));
35
+ }
36
+ console.log('');
37
+ // Touch the marker so we never show this again
38
+ touchWelcomeMarker(markerPath);
39
+ return true;
40
+ }
41
+ catch {
42
+ // Welcome is non-critical — swallow all errors silently
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Count JSONL sessions and project directories in ~/.claude/projects/.
48
+ * Mirrors the discovery logic in ClaudeCodeProvider without pulling in
49
+ * the full provider dependency chain.
50
+ */
51
+ function countClaudeSessions() {
52
+ const baseDir = getClaudeDir();
53
+ if (!fs.existsSync(baseDir)) {
54
+ return { sessionCount: 0, projectCount: 0 };
55
+ }
56
+ let sessionCount = 0;
57
+ let projectCount = 0;
58
+ try {
59
+ const entries = fs.readdirSync(baseDir);
60
+ for (const entry of entries) {
61
+ // Skip hidden files/dirs (matches ClaudeCodeProvider behaviour)
62
+ if (entry.startsWith('.'))
63
+ continue;
64
+ const entryPath = path.join(baseDir, entry);
65
+ try {
66
+ const stat = fs.statSync(entryPath);
67
+ if (!stat.isDirectory())
68
+ continue;
69
+ }
70
+ catch {
71
+ continue;
72
+ }
73
+ projectCount++;
74
+ try {
75
+ const files = fs.readdirSync(entryPath);
76
+ for (const file of files) {
77
+ if (file.endsWith('.jsonl')) {
78
+ sessionCount++;
79
+ }
80
+ }
81
+ }
82
+ catch {
83
+ // Can't read this project dir — skip it, keep counting others
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ return { sessionCount: 0, projectCount: 0 };
89
+ }
90
+ return { sessionCount, projectCount };
91
+ }
92
+ /**
93
+ * Create the welcome-shown marker file.
94
+ * Ensures the config directory exists first (handles brand-new installs
95
+ * where no config has been written yet).
96
+ */
97
+ function touchWelcomeMarker(markerPath) {
98
+ ensureConfigDir();
99
+ fs.writeFileSync(markerPath, '', { mode: 0o600 });
100
+ }
101
+ //# sourceMappingURL=welcome.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/utils/welcome.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@code-insights/cli",
3
- "version": "1.2.0",
4
- "description": "Sync your AI coding sessions to Firebase for analysis",
3
+ "version": "2.0.0",
4
+ "description": "Parse and sync AI coding sessions (Claude Code, Cursor, Codex, Copilot) to Firebase for analysis",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "files": [
17
17
  "dist",
18
18
  "README.md",
19
+ "CHANGELOG.md",
19
20
  "LICENSE"
20
21
  ],
21
22
  "engines": {
@@ -24,6 +25,10 @@
24
25
  "keywords": [
25
26
  "claude",
26
27
  "claude-code",
28
+ "cursor",
29
+ "codex",
30
+ "copilot",
31
+ "ai-coding",
27
32
  "insights",
28
33
  "analytics",
29
34
  "firestore"
@@ -43,6 +48,7 @@
43
48
  },
44
49
  "license": "MIT",
45
50
  "dependencies": {
51
+ "better-sqlite3": "^12.6.2",
46
52
  "chalk": "^5.4.1",
47
53
  "commander": "^12.1.0",
48
54
  "firebase-admin": "^13.4.0",
@@ -50,6 +56,7 @@
50
56
  "ora": "^8.2.0"
51
57
  },
52
58
  "devDependencies": {
59
+ "@types/better-sqlite3": "^7.6.13",
53
60
  "@types/node": "^22.15.30",
54
61
  "typescript": "^5.9.3"
55
62
  }