@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,48 @@
1
+ import chalk from 'chalk';
2
+ import { colors } from './colors.js';
3
+ export function getTerminalWidth() {
4
+ return process.stdout.columns || 80;
5
+ }
6
+ export function getBarWidth() {
7
+ const width = getTerminalWidth();
8
+ if (width >= 100)
9
+ return 20;
10
+ if (width >= 80)
11
+ return 16;
12
+ if (width >= 60)
13
+ return 12;
14
+ return 0;
15
+ }
16
+ export function getGridColumns() {
17
+ const width = getTerminalWidth();
18
+ if (width >= 80)
19
+ return 3;
20
+ if (width >= 60)
21
+ return 2;
22
+ return 1;
23
+ }
24
+ export function sectionHeader(title, rightText) {
25
+ const width = getTerminalWidth() - 4;
26
+ const header = colors.header(title.toUpperCase());
27
+ const right = rightText ? colors.label(rightText) : '';
28
+ const gap = width - title.length - (rightText?.length ?? 0);
29
+ return `\n ${header}${' '.repeat(Math.max(1, gap))}${right}\n ${colors.divider(width)}`;
30
+ }
31
+ export function metricGrid(metrics) {
32
+ const cols = getGridColumns();
33
+ const colWidth = cols === 3 ? 22 : cols === 2 ? 30 : 40;
34
+ const lines = [];
35
+ for (let i = 0; i < metrics.length; i += cols) {
36
+ const row = metrics.slice(i, i + cols);
37
+ const formatted = row.map(m => `${colors.label(m.label.padEnd(10))} ${colors.value(m.value.padStart(colWidth - 12))}`);
38
+ lines.push(' ' + formatted.join(' '));
39
+ }
40
+ return lines.join('\n');
41
+ }
42
+ export function projectCardHeader(name) {
43
+ const width = getTerminalWidth() - 4;
44
+ const nameSection = `${chalk.gray('─ ')}${chalk.white.bold(name)} `;
45
+ const remaining = width - name.length - 3;
46
+ return `\n ${nameSection}${chalk.gray('─'.repeat(Math.max(0, remaining)))}`;
47
+ }
48
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../../../src/commands/stats/render/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ import type { StatsFlags } from './data/types.js';
3
+ export type { StatsFlags };
4
+ export declare function applySharedFlags(cmd: Command): Command;
5
+ export declare function parseFlags(options: Record<string, unknown>): StatsFlags;
6
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../src/commands/stats/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAU,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAK1D,YAAY,EAAE,UAAU,EAAE,CAAC;AAE3B,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAQtD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAavE"}
@@ -0,0 +1,26 @@
1
+ import { InvalidPeriodError } from './data/types.js';
2
+ const VALID_PERIODS = ['7d', '30d', '90d', 'all'];
3
+ export function applySharedFlags(cmd) {
4
+ return cmd
5
+ .option('-p, --period <period>', 'Time range: 7d, 30d, 90d, all', '7d')
6
+ .option('--project <name>', 'Scope to a single project')
7
+ .option('--source <tool>', 'Filter by source tool (claude-code, cursor, etc.)')
8
+ .option('--no-sync', 'Skip auto-sync before displaying stats')
9
+ .option('--local', 'Use local session files (no Firebase required)')
10
+ .option('--remote', 'Force Firestore data source (requires Firebase config)');
11
+ }
12
+ export function parseFlags(options) {
13
+ const period = options.period || '7d';
14
+ if (!VALID_PERIODS.includes(period)) {
15
+ throw new InvalidPeriodError(`Invalid period "${period}". Expected: ${VALID_PERIODS.join(', ')}`);
16
+ }
17
+ return {
18
+ period: period,
19
+ project: options.project,
20
+ source: options.source,
21
+ noSync: !options.sync, // Commander's --no-sync sets sync=false
22
+ local: !!options.local,
23
+ remote: !!options.remote,
24
+ };
25
+ }
26
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/commands/stats/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAErD,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAIlD,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,OAAO,GAAG;SACP,MAAM,CAAC,uBAAuB,EAAE,+BAA+B,EAAE,IAAI,CAAC;SACtE,MAAM,CAAC,kBAAkB,EAAE,2BAA2B,CAAC;SACvD,MAAM,CAAC,iBAAiB,EAAE,mDAAmD,CAAC;SAC9E,MAAM,CAAC,WAAW,EAAE,wCAAwC,CAAC;SAC7D,MAAM,CAAC,SAAS,EAAE,gDAAgD,CAAC;SACnE,MAAM,CAAC,UAAU,EAAE,wDAAwD,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAgC;IACzD,MAAM,MAAM,GAAI,OAAO,CAAC,MAAiB,IAAI,IAAI,CAAC;IAClD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,MAAM,gBAAgB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACpG,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAgB;QACxB,OAAO,EAAE,OAAO,CAAC,OAA6B;QAC9C,MAAM,EAAE,OAAO,CAAC,MAA4B;QAC5C,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,EAAG,wCAAwC;QAChE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK;QACtB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM;KACzB,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAkFnD"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA6FnD"}
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
- import { loadConfig, loadSyncState, isConfigured, getConfigDir, getClaudeDir, hasWebConfig, loadWebConfig } from '../utils/config.js';
2
+ import { loadConfig, loadSyncState, isConfigured, getConfigDir, getClaudeDir, hasWebConfig, loadWebConfig, resolveDataSourcePreference } from '../utils/config.js';
3
3
  import { initializeFirebase, getProjects } from '../firebase/client.js';
4
+ import { trackEvent } from '../utils/telemetry.js';
4
5
  import * as fs from 'fs';
5
6
  /**
6
7
  * Show Code Insights status
@@ -9,17 +10,19 @@ export async function statusCommand() {
9
10
  console.log(chalk.cyan('\nšŸ“Š Code Insights Status\n'));
10
11
  // Check configuration
11
12
  console.log(chalk.white('Configuration:'));
13
+ const preference = resolveDataSourcePreference();
12
14
  if (isConfigured()) {
13
15
  console.log(chalk.green(` āœ“ Configured at ${getConfigDir()}`));
14
16
  const config = loadConfig();
15
17
  if (config) {
16
- console.log(chalk.gray(` Project: ${config.firebase.projectId}`));
18
+ console.log(chalk.gray(` Project: ${config.firebase?.projectId ?? '(local)'}`));
17
19
  }
20
+ console.log(chalk.gray(` Data source: ${preference}`));
18
21
  }
19
22
  else {
20
- console.log(chalk.red(' āœ— Not configured'));
21
- console.log(chalk.gray(' Run `code-insights init` to set up'));
22
- return;
23
+ console.log(chalk.yellow(' ā—‹ Not configured (running in zero-config mode)'));
24
+ console.log(chalk.gray(' Stats work without config: code-insights stats'));
25
+ console.log(chalk.gray(' To configure Firebase: code-insights init'));
23
26
  }
24
27
  // Check Claude directory
25
28
  console.log(chalk.white('\nClaude Code:'));
@@ -46,45 +49,55 @@ export async function statusCommand() {
46
49
  console.log(chalk.yellow(' ⚠ Never synced'));
47
50
  console.log(chalk.gray(' Run `code-insights sync` to sync'));
48
51
  }
49
- // Check Firebase connection
50
- console.log(chalk.white('\nFirebase:'));
51
- const config = loadConfig();
52
- if (config) {
53
- try {
54
- initializeFirebase(config);
55
- const projects = await getProjects();
56
- console.log(chalk.green(' āœ“ Connected'));
57
- console.log(chalk.gray(` ${projects.length} projects in Firestore`));
58
- if (projects.length > 0) {
59
- console.log(chalk.white('\nSynced Projects:'));
60
- for (const project of projects.slice(0, 5)) {
61
- console.log(chalk.gray(` ${project.name} (${project.sessionCount} sessions)`));
62
- }
63
- if (projects.length > 5) {
64
- console.log(chalk.gray(` ... and ${projects.length - 5} more`));
52
+ if (preference === 'local') {
53
+ // Local mode — skip Firebase connection check
54
+ console.log(chalk.white('\nFirebase:'));
55
+ console.log(chalk.gray(' ā—‹ Not applicable (data source is local)'));
56
+ console.log(chalk.gray(' Use `code-insights stats --local` for session analytics'));
57
+ console.log(chalk.gray(' To switch: code-insights config set-source firebase'));
58
+ }
59
+ else {
60
+ // Check Firebase connection
61
+ console.log(chalk.white('\nFirebase:'));
62
+ const config = loadConfig();
63
+ if (config) {
64
+ try {
65
+ initializeFirebase(config);
66
+ const projects = await getProjects();
67
+ console.log(chalk.green(' āœ“ Connected'));
68
+ console.log(chalk.gray(` ${projects.length} projects in Firestore`));
69
+ if (projects.length > 0) {
70
+ console.log(chalk.white('\nSynced Projects:'));
71
+ for (const project of projects.slice(0, 5)) {
72
+ console.log(chalk.gray(` ${project.name} (${project.sessionCount} sessions)`));
73
+ }
74
+ if (projects.length > 5) {
75
+ console.log(chalk.gray(` ... and ${projects.length - 5} more`));
76
+ }
65
77
  }
66
78
  }
79
+ catch (error) {
80
+ console.log(chalk.red(' āœ— Connection failed'));
81
+ console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
82
+ }
67
83
  }
68
- catch (error) {
69
- console.log(chalk.red(' āœ— Connection failed'));
70
- console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
84
+ // Check web dashboard config
85
+ console.log(chalk.white('\nWeb Dashboard:'));
86
+ if (hasWebConfig()) {
87
+ const webConfig = loadWebConfig();
88
+ console.log(chalk.green(' āœ“ Configured'));
89
+ if (webConfig && typeof webConfig.projectId === 'string') {
90
+ console.log(chalk.gray(` Project: ${webConfig.projectId}`));
91
+ }
92
+ console.log(chalk.gray(' Run "code-insights connect" to get dashboard URL'));
71
93
  }
72
- }
73
- // Check web dashboard config
74
- console.log(chalk.white('\nWeb Dashboard:'));
75
- if (hasWebConfig()) {
76
- const webConfig = loadWebConfig();
77
- console.log(chalk.green(' āœ“ Configured'));
78
- if (webConfig && typeof webConfig.projectId === 'string') {
79
- console.log(chalk.gray(` Project: ${webConfig.projectId}`));
94
+ else {
95
+ console.log(chalk.yellow(' ā—‹ Not configured'));
96
+ console.log(chalk.gray(' Run "code-insights init" to configure'));
80
97
  }
81
- console.log(chalk.gray(' Run "code-insights connect" to get dashboard URL'));
82
- }
83
- else {
84
- console.log(chalk.yellow(' ā—‹ Not configured'));
85
- console.log(chalk.gray(' Run "code-insights init" to configure'));
86
98
  }
87
99
  console.log('');
100
+ trackEvent('status', true);
88
101
  }
89
102
  /**
90
103
  * Count JSONL files in Claude directory
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,27 @@
1
1
  interface SyncOptions {
2
2
  force?: boolean;
3
+ forceRemote?: boolean;
3
4
  project?: string;
4
5
  dryRun?: boolean;
5
6
  quiet?: boolean;
6
7
  regenerateTitles?: boolean;
8
+ source?: string;
7
9
  }
10
+ export interface SyncResult {
11
+ syncedCount: number;
12
+ messageCount: number;
13
+ errorCount: number;
14
+ }
15
+ /**
16
+ * Core sync logic — reusable from stats commands and other callers.
17
+ *
18
+ * Throws on fatal errors (missing config, Firebase connection failure,
19
+ * unknown provider) instead of calling process.exit().
20
+ * Returns a SyncResult summary instead of printing one.
21
+ */
22
+ export declare function runSync(options?: SyncOptions): Promise<SyncResult>;
8
23
  /**
9
- * Sync Claude Code sessions to Firestore
24
+ * Sync AI coding sessions to Firestore
10
25
  */
11
26
  export declare function syncCommand(options?: SyncOptions): Promise<void>;
12
27
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AASA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6I1E"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAYA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAwL5E;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC1E"}
@@ -2,13 +2,19 @@ import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
- import { loadConfig, loadSyncState, saveSyncState } from '../utils/config.js';
5
+ import { loadConfig, loadSyncState, saveSyncState, resolveDataSourcePreference } from '../utils/config.js';
6
+ import { trackEvent } from '../utils/telemetry.js';
6
7
  import { initializeFirebase, uploadSession, uploadMessages, sessionExists, recalculateUsageStats } from '../firebase/client.js';
7
- import { getDefaultProvider } from '../providers/registry.js';
8
+ import { getAllProviders, getProvider } from '../providers/registry.js';
9
+ import { splitVirtualPath } from '../utils/paths.js';
8
10
  /**
9
- * Sync Claude Code sessions to Firestore
11
+ * Core sync logic — reusable from stats commands and other callers.
12
+ *
13
+ * Throws on fatal errors (missing config, Firebase connection failure,
14
+ * unknown provider) instead of calling process.exit().
15
+ * Returns a SyncResult summary instead of printing one.
10
16
  */
11
- export async function syncCommand(options = {}) {
17
+ export async function runSync(options = {}) {
12
18
  const log = options.quiet ? () => { } : console.log.bind(console);
13
19
  const noopSpinner = {
14
20
  start: function () { return this; },
@@ -20,12 +26,12 @@ export async function syncCommand(options = {}) {
20
26
  const createSpinner = options.quiet
21
27
  ? () => noopSpinner
22
28
  : ora;
23
- log(chalk.cyan('\nšŸ“¤ Code Insights Sync\n'));
29
+ log(chalk.cyan('\n\uD83D\uDCE4 Code Insights Sync\n'));
24
30
  // Load config
25
31
  const config = loadConfig();
26
32
  if (!config) {
27
- log(chalk.red('Not configured. Run `code-insights init` first.'));
28
- process.exit(1);
33
+ throw new Error('Sync requires Firebase. Run `code-insights init` to set up.\n' +
34
+ ' For local-only analytics: code-insights stats');
29
35
  }
30
36
  // Initialize Firebase
31
37
  const spinner = createSpinner('Connecting to Firebase...').start();
@@ -35,81 +41,128 @@ export async function syncCommand(options = {}) {
35
41
  }
36
42
  catch (error) {
37
43
  spinner.fail('Failed to connect to Firebase');
38
- if (!options.quiet) {
39
- console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
44
+ throw new Error(`Failed to connect to Firebase: ${error instanceof Error ? error.message : 'Unknown error'}`);
45
+ }
46
+ // Dry-run banner
47
+ if (options.dryRun) {
48
+ log(chalk.yellow('\n\uD83D\uDD0D Dry run \u2014 no changes will be made'));
49
+ }
50
+ // Get providers to sync
51
+ let providers;
52
+ if (options.source) {
53
+ try {
54
+ providers = [getProvider(options.source)];
55
+ }
56
+ catch {
57
+ throw new Error(`Unknown source: ${options.source}. Available: ${getAllProviders().map(p => p.getProviderName()).join(', ')}`);
40
58
  }
41
- process.exit(1);
42
59
  }
43
- // Discover session files via provider
44
- const provider = getDefaultProvider();
45
- spinner.start('Discovering sessions...');
46
- const jsonlFiles = await provider.discover({ projectFilter: options.project });
47
- spinner.succeed(`Found ${jsonlFiles.length} session files`);
48
- if (jsonlFiles.length === 0) {
49
- log(chalk.yellow('No sessions to sync.'));
50
- return;
60
+ else {
61
+ providers = getAllProviders();
51
62
  }
52
63
  // Load sync state
53
- const syncState = options.force ? { lastSync: '', files: {} } : loadSyncState();
54
- // Filter to only new/modified files
55
- const filesToSync = filterFilesToSync(jsonlFiles, syncState, options.force);
56
- log(chalk.gray(` ${filesToSync.length} files need syncing (${jsonlFiles.length - filesToSync.length} already synced)`));
57
- if (filesToSync.length === 0) {
58
- log(chalk.green('\nāœ… Already up to date!'));
59
- return;
60
- }
61
- if (options.dryRun) {
62
- log(chalk.yellow('\nšŸ” Dry run - no changes will be made'));
63
- for (const file of filesToSync) {
64
- log(chalk.gray(` Would sync: ${path.basename(file)}`));
64
+ // When --force is used with --source, only clear the targeted provider's entries
65
+ // instead of nuking the entire sync state.
66
+ const syncState = loadSyncState();
67
+ if (options.force) {
68
+ if (options.source) {
69
+ // Targeted force: remove only entries belonging to the specified provider's files
70
+ const targetProviderPaths = new Set();
71
+ for (const provider of providers) {
72
+ const discovered = await provider.discover({ projectFilter: options.project });
73
+ for (const p of discovered) {
74
+ const { realPath } = splitVirtualPath(p);
75
+ targetProviderPaths.add(realPath);
76
+ }
77
+ }
78
+ for (const key of Object.keys(syncState.files)) {
79
+ if (targetProviderPaths.has(key)) {
80
+ delete syncState.files[key];
81
+ }
82
+ }
83
+ }
84
+ else {
85
+ // Full force: reset everything
86
+ syncState.files = {};
65
87
  }
66
- return;
67
88
  }
68
- // Process files
69
- let syncedCount = 0;
70
- let messageCount = 0;
71
- let errorCount = 0;
72
- for (const filePath of filesToSync) {
73
- const fileName = path.basename(filePath);
74
- spinner.start(`Processing ${fileName}...`);
89
+ let totalSyncedCount = 0;
90
+ let totalMessageCount = 0;
91
+ let totalErrorCount = 0;
92
+ for (const provider of providers) {
93
+ const providerName = provider.getProviderName();
75
94
  try {
76
- // Parse session
77
- const session = await provider.parse(filePath);
78
- if (!session) {
79
- spinner.warn(`Skipped ${fileName} (no valid data)`);
95
+ if (providers.length > 1) {
96
+ log(chalk.cyan(`\n\uD83D\uDCE6 Syncing ${providerName}...`));
97
+ }
98
+ // Discovery
99
+ spinner.start(`Discovering ${providerName} sessions...`);
100
+ const sessionFiles = await provider.discover({ projectFilter: options.project });
101
+ spinner.succeed(`Found ${sessionFiles.length} ${providerName} session files`);
102
+ if (sessionFiles.length === 0)
103
+ continue;
104
+ // Filter to only new/modified files
105
+ const filesToSync = filterFilesToSync(sessionFiles, syncState, options.force);
106
+ log(chalk.gray(` ${filesToSync.length} files need syncing (${sessionFiles.length - filesToSync.length} already synced)`));
107
+ if (filesToSync.length === 0)
108
+ continue;
109
+ if (options.dryRun) {
110
+ for (const file of filesToSync) {
111
+ log(chalk.gray(` Would sync: ${path.basename(file)}`));
112
+ }
80
113
  continue;
81
114
  }
82
- // Check if already exists (unless force)
83
- if (!options.force) {
84
- const exists = await sessionExists(session.id);
85
- if (exists) {
86
- spinner.info(`Skipped ${fileName} (already synced)`);
115
+ // Process files
116
+ for (const filePath of filesToSync) {
117
+ const fileName = path.basename(filePath);
118
+ spinner.start(`Processing ${fileName}...`);
119
+ try {
120
+ // Parse session
121
+ const session = await provider.parse(filePath);
122
+ if (!session) {
123
+ spinner.warn(`Skipped ${fileName} (no valid data)`);
124
+ continue;
125
+ }
126
+ // Check if already exists (unless force)
127
+ if (!options.force) {
128
+ const exists = await sessionExists(session.id);
129
+ if (exists) {
130
+ spinner.info(`Skipped ${fileName} (already synced)`);
131
+ updateSyncState(syncState, filePath, session.id);
132
+ saveSyncState(syncState);
133
+ continue;
134
+ }
135
+ }
136
+ // Upload session and messages to Firestore
137
+ await uploadSession(session, !!options.force);
138
+ await uploadMessages(session);
139
+ // Update and persist sync state after each file
140
+ // so progress survives crashes (e.g., Firebase quota exceeded)
87
141
  updateSyncState(syncState, filePath, session.id);
88
142
  saveSyncState(syncState);
89
- continue;
143
+ totalSyncedCount++;
144
+ totalMessageCount += session.messages.length;
145
+ spinner.succeed(`Synced ${fileName} (${session.messages.length} messages)`);
146
+ }
147
+ catch (error) {
148
+ totalErrorCount++;
149
+ spinner.fail(`Failed to sync ${fileName}`);
150
+ if (!options.quiet) {
151
+ console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
152
+ }
90
153
  }
91
154
  }
92
- // Upload session and messages to Firestore
93
- await uploadSession(session, !!options.force);
94
- await uploadMessages(session);
95
- // Update and persist sync state after each file
96
- // so progress survives crashes (e.g., Firebase quota exceeded)
97
- updateSyncState(syncState, filePath, session.id);
98
- saveSyncState(syncState);
99
- syncedCount++;
100
- messageCount += session.messages.length;
101
- spinner.succeed(`Synced ${fileName} (${session.messages.length} messages)`);
102
155
  }
103
156
  catch (error) {
104
- errorCount++;
105
- spinner.fail(`Failed to sync ${fileName}`);
157
+ totalErrorCount++;
158
+ spinner.fail(`Failed to sync ${providerName}`);
106
159
  if (!options.quiet) {
107
160
  console.error(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
108
161
  }
109
162
  }
110
163
  }
111
- // Reconcile usage stats after force sync
112
- if (options.force) {
164
+ // Reconcile usage stats after force sync (skip if nothing changed)
165
+ if (options.force && (totalSyncedCount > 0 || totalErrorCount > 0)) {
113
166
  spinner.start('Recalculating usage stats...');
114
167
  try {
115
168
  const result = await recalculateUsageStats();
@@ -125,14 +178,48 @@ export async function syncCommand(options = {}) {
125
178
  // Save sync state
126
179
  syncState.lastSync = new Date().toISOString();
127
180
  saveSyncState(syncState);
128
- // Summary
129
- log(chalk.cyan('\nšŸ“Š Sync Summary'));
130
- log(chalk.white(` Sessions synced: ${syncedCount}`));
131
- log(chalk.white(` Messages uploaded: ${messageCount}`));
132
- if (errorCount > 0) {
133
- log(chalk.red(` Errors: ${errorCount}`));
181
+ return {
182
+ syncedCount: totalSyncedCount,
183
+ messageCount: totalMessageCount,
184
+ errorCount: totalErrorCount,
185
+ };
186
+ }
187
+ /**
188
+ * Sync AI coding sessions to Firestore
189
+ */
190
+ export async function syncCommand(options = {}) {
191
+ const log = options.quiet ? () => { } : console.log.bind(console);
192
+ const preference = resolveDataSourcePreference();
193
+ if (preference === 'local' && !options.forceRemote) {
194
+ log(chalk.yellow('\n ⚠ Data source is set to local. Sync is only used with Firebase.\n'));
195
+ log(chalk.gray(' To switch to Firebase: code-insights config set-source firebase'));
196
+ log(chalk.gray(' To sync anyway (one-time): code-insights sync --force-remote\n'));
197
+ return;
198
+ }
199
+ try {
200
+ const result = await runSync(options);
201
+ // Summary (only if not quiet)
202
+ if (result.syncedCount === 0 && result.errorCount === 0) {
203
+ log(chalk.green('\n\u2705 Already up to date!'));
204
+ trackEvent('sync', true);
205
+ return;
206
+ }
207
+ log(chalk.cyan('\n\uD83D\uDCCA Sync Summary'));
208
+ log(chalk.white(` Sessions synced: ${result.syncedCount}`));
209
+ log(chalk.white(` Messages uploaded: ${result.messageCount}`));
210
+ if (result.errorCount > 0) {
211
+ log(chalk.red(` Errors: ${result.errorCount}`));
212
+ }
213
+ log(chalk.green('\n\u2705 Sync complete!'));
214
+ trackEvent('sync', true);
215
+ }
216
+ catch (error) {
217
+ trackEvent('sync', false);
218
+ if (!options.quiet) {
219
+ console.error(chalk.red(error instanceof Error ? error.message : 'Sync failed'));
220
+ }
221
+ process.exit(1);
134
222
  }
135
- log(chalk.green('\nāœ… Sync complete!'));
136
223
  }
137
224
  /**
138
225
  * Filter files to only those that need syncing
@@ -141,22 +228,52 @@ function filterFilesToSync(files, syncState, force) {
141
228
  if (force)
142
229
  return files;
143
230
  return files.filter((filePath) => {
144
- const stat = fs.statSync(filePath);
231
+ const { realPath, sessionFragment } = splitVirtualPath(filePath);
232
+ const stat = fs.statSync(realPath);
145
233
  const lastModified = stat.mtime.toISOString();
146
- const fileState = syncState.files[filePath];
147
- // Sync if never synced or modified since last sync
148
- return !fileState || fileState.lastModified !== lastModified;
234
+ const fileState = syncState.files[realPath];
235
+ // If file was never synced, sync it
236
+ if (!fileState)
237
+ return true;
238
+ // For virtual paths (multi-session files), check if this specific session was synced
239
+ if (sessionFragment && fileState.syncedSessionIds) {
240
+ return !fileState.syncedSessionIds.includes(sessionFragment);
241
+ }
242
+ // For regular files, check if modified since last sync
243
+ if (sessionFragment) {
244
+ // Virtual path but no syncedSessionIds tracked yet — needs sync
245
+ return true;
246
+ }
247
+ return fileState.lastModified !== lastModified;
149
248
  });
150
249
  }
151
250
  /**
152
251
  * Update sync state for a file
153
252
  */
154
253
  function updateSyncState(state, filePath, sessionId) {
155
- const stat = fs.statSync(filePath);
156
- state.files[filePath] = {
157
- lastModified: stat.mtime.toISOString(),
158
- lastSyncedLine: 0, // Not tracking lines for now
159
- sessionId,
160
- };
254
+ const { realPath, sessionFragment } = splitVirtualPath(filePath);
255
+ const stat = fs.statSync(realPath);
256
+ if (sessionFragment) {
257
+ // Virtual path: track the session fragment in syncedSessionIds
258
+ const existing = state.files[realPath];
259
+ const syncedIds = existing?.syncedSessionIds || [];
260
+ if (!syncedIds.includes(sessionFragment)) {
261
+ syncedIds.push(sessionFragment);
262
+ }
263
+ state.files[realPath] = {
264
+ lastModified: stat.mtime.toISOString(),
265
+ lastSyncedLine: 0,
266
+ sessionId,
267
+ syncedSessionIds: syncedIds,
268
+ };
269
+ }
270
+ else {
271
+ // Regular file path
272
+ state.files[realPath] = {
273
+ lastModified: stat.mtime.toISOString(),
274
+ lastSyncedLine: 0,
275
+ sessionId,
276
+ };
277
+ }
161
278
  }
162
279
  //# sourceMappingURL=sync.js.map