liferewind 0.1.0 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/collect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,cAAc,SA0DvB,CAAC"}
1
+ {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/collect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,eAAO,MAAM,cAAc,SAyDvB,CAAC"}
@@ -4,14 +4,22 @@ import { Collector } from '../../core/collector.js';
4
4
  import { loadConfig } from '../../config/loader.js';
5
5
  import { createLogger } from '../../utils/logger.js';
6
6
  import { registerBuiltinSources } from '../../sources/index.js';
7
- import { printError, printSuccess } from '../utils/output.js';
7
+ import { printError, printSuccess, printInfo } from '../utils/output.js';
8
8
  import { SOURCE_TYPES } from '../../core/types.js';
9
+ function showSkippedInfo(result, verbose) {
10
+ if (!verbose || !result?.skipped?.count)
11
+ return;
12
+ printInfo(`Skipped ${result.skipped.count} items:`);
13
+ for (const item of result.skipped.items) {
14
+ console.log(` - ${item.path} (${item.reason})`);
15
+ }
16
+ }
9
17
  export const collectCommand = new Command('collect')
10
18
  .description('Manually trigger data collection')
11
19
  .argument('[source]', 'specific source to collect (git, browser, filesystem, chatbot)')
12
20
  .action(async (source, _options, cmd) => {
13
21
  const globalOpts = cmd.optsWithGlobals();
14
- // Validate source argument
22
+ const verbose = !!globalOpts.verbose;
15
23
  if (source && !SOURCE_TYPES.includes(source)) {
16
24
  printError(`Invalid source: ${source}`);
17
25
  console.log(`Valid sources: ${SOURCE_TYPES.join(', ')}`);
@@ -19,7 +27,6 @@ export const collectCommand = new Command('collect')
19
27
  }
20
28
  try {
21
29
  const config = loadConfig(globalOpts.config);
22
- // Apply global log level overrides
23
30
  let logLevel = config.logging.level;
24
31
  if (globalOpts.verbose)
25
32
  logLevel = 'debug';
@@ -36,21 +43,21 @@ export const collectCommand = new Command('collect')
36
43
  }
37
44
  const spinner = ora();
38
45
  if (source) {
39
- // Collect specific source
40
46
  if (!enabledSources.includes(source)) {
41
47
  printError(`Source '${source}' is not enabled or not validated.`);
42
48
  process.exit(1);
43
49
  }
44
50
  spinner.start(`Collecting from ${source}...`);
45
- await collector.triggerCollection(source);
51
+ const result = await collector.triggerCollection(source);
46
52
  spinner.succeed(`Collection from ${source} complete`);
53
+ showSkippedInfo(result, verbose);
47
54
  }
48
55
  else {
49
- // Collect all enabled sources
50
56
  for (const src of enabledSources) {
51
57
  spinner.start(`Collecting from ${src}...`);
52
- await collector.triggerCollection(src);
58
+ const result = await collector.triggerCollection(src);
53
59
  spinner.succeed(`Collection from ${src} complete`);
60
+ showSkippedInfo(result, verbose);
54
61
  }
55
62
  }
56
63
  printSuccess('All collections complete.');
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,aAAa,SAA4D,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBpC,eAAO,MAAM,aAAa,SAA4D,CAAC"}
@@ -1,20 +1,19 @@
1
1
  import { Command } from 'commander';
2
- import { execSync } from 'node:child_process';
3
2
  import { existsSync } from 'node:fs';
4
3
  import pc from 'picocolors';
4
+ import { confirm, select, checkbox, Separator } from '@inquirer/prompts';
5
5
  import { loadConfig, findConfigPath } from '../../config/loader.js';
6
+ import { writeConfig } from '../../config/writer.js';
6
7
  import { getUserConfigPath, getAllConfigPaths } from '../../config/paths.js';
7
8
  import { printError, printSuccess, printInfo, printDim } from '../utils/output.js';
9
+ import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients } from '../detect/index.js';
10
+ import { maskApiKey, SCHEDULE_CHOICES, GIT_PATH_PRESETS, FILESYSTEM_PATH_PRESETS, selectPaths, } from '../utils/prompts.js';
11
+ import { inputApiUrl, inputApiKey } from '../utils/api.js';
8
12
  export const configCommand = new Command('config').description('Manage configuration');
9
13
  function printConfigSummary(config, revealSecrets = false) {
10
14
  console.log(pc.bold('\nAPI Configuration'));
11
15
  console.log(` Base URL: ${config.api.baseUrl}`);
12
- const apiKey = revealSecrets
13
- ? config.api.apiKey
14
- : config.api.apiKey.length > 12
15
- ? config.api.apiKey.slice(0, 8) + '...' + config.api.apiKey.slice(-4)
16
- : '***';
17
- console.log(` API Key: ${apiKey}`);
16
+ console.log(` API Key: ${revealSecrets ? config.api.apiKey : maskApiKey(config.api.apiKey)}`);
18
17
  console.log(` Timeout: ${config.api.timeout}ms`);
19
18
  console.log(` Retries: ${config.api.retryAttempts}`);
20
19
  console.log(pc.bold('\nData Sources'));
@@ -41,8 +40,6 @@ configCommand
41
40
  if (configPath) {
42
41
  printDim(`Loaded from: ${configPath}`);
43
42
  }
44
- // Helper to mask API key
45
- const maskApiKey = (key) => key.length > 12 ? key.slice(0, 8) + '...' + key.slice(-4) : '***';
46
43
  if (options.json) {
47
44
  const output = {
48
45
  ...config,
@@ -77,23 +74,208 @@ configCommand
77
74
  });
78
75
  configCommand
79
76
  .command('edit')
80
- .description('Open configuration in default editor')
81
- .action((_options, cmd) => {
77
+ .description('Interactive configuration editor')
78
+ .action(async (_options, cmd) => {
82
79
  const globalOpts = cmd.optsWithGlobals();
83
- const configPath = findConfigPath(globalOpts.config);
84
- if (!configPath) {
85
- printError('No config file found.');
86
- printInfo("Run 'liferewind init' to create one.");
87
- process.exit(1);
88
- }
89
- const editor = process.env['EDITOR'] || process.env['VISUAL'] || (process.platform === 'darwin' ? 'open' : 'vi');
80
+ const configPath = findConfigPath(globalOpts.config) || getUserConfigPath();
81
+ let config;
90
82
  try {
91
- execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
83
+ config = loadConfig(globalOpts.config);
84
+ printDim(`Editing: ${configPath}`);
92
85
  }
93
- catch (error) {
94
- printError(`Failed to open editor: ${error instanceof Error ? error.message : String(error)}`);
86
+ catch {
87
+ printError('No valid config file found.');
88
+ printInfo("Run 'liferewind init' to create one.");
95
89
  process.exit(1);
96
90
  }
91
+ let hasChanges = false;
92
+ while (true) {
93
+ const choice = await select({
94
+ message: 'What would you like to configure?',
95
+ loop: false,
96
+ choices: [
97
+ { name: `API Settings ${pc.dim(`(${config.api.baseUrl})`)}`, value: 'api' },
98
+ {
99
+ name: `Browser Collection ${config.sources.browser.enabled ? pc.green('✓') : pc.dim('✗')}`,
100
+ value: 'browser',
101
+ },
102
+ { name: `Git Collection ${config.sources.git.enabled ? pc.green('✓') : pc.dim('✗')}`, value: 'git' },
103
+ {
104
+ name: `Filesystem Collection ${config.sources.filesystem.enabled ? pc.green('✓') : pc.dim('✗')}`,
105
+ value: 'filesystem',
106
+ },
107
+ {
108
+ name: `Chatbot Collection ${config.sources.chatbot.enabled ? pc.green('✓') : pc.dim('✗')}`,
109
+ value: 'chatbot',
110
+ },
111
+ { name: `Logging ${pc.dim(`(${config.logging.level})`)}`, value: 'logging' },
112
+ new Separator(),
113
+ { name: hasChanges ? pc.green('Save and Exit') : 'Exit', value: 'exit' },
114
+ ],
115
+ });
116
+ if (choice === 'exit') {
117
+ if (hasChanges) {
118
+ const shouldSave = await confirm({ message: 'Save changes?', default: true });
119
+ if (shouldSave) {
120
+ writeConfig(config, configPath);
121
+ printSuccess('Configuration saved!');
122
+ }
123
+ else {
124
+ printInfo('Changes discarded.');
125
+ }
126
+ }
127
+ break;
128
+ }
129
+ if (choice === 'api') {
130
+ const newUrl = await inputApiUrl(config.api.baseUrl);
131
+ const changeKey = await confirm({ message: 'Change API Key?', default: false });
132
+ let newKey = config.api.apiKey;
133
+ if (changeKey) {
134
+ newKey = await inputApiKey();
135
+ }
136
+ if (newUrl !== config.api.baseUrl || newKey !== config.api.apiKey) {
137
+ config.api.baseUrl = newUrl;
138
+ config.api.apiKey = newKey;
139
+ hasChanges = true;
140
+ }
141
+ }
142
+ if (choice === 'browser') {
143
+ const detectedBrowsers = detectInstalledBrowsers();
144
+ if (detectedBrowsers.length > 0) {
145
+ printDim(` Detected: ${detectedBrowsers.join(', ')}`);
146
+ }
147
+ const enabled = await confirm({
148
+ message: 'Enable browser history collection?',
149
+ default: config.sources.browser.enabled,
150
+ });
151
+ if (enabled) {
152
+ const currentBrowsers = config.sources.browser.options.browsers || [];
153
+ const selectedBrowsers = await checkbox({
154
+ message: 'Select browsers:',
155
+ loop: false,
156
+ choices: ['chrome', 'safari', 'arc', 'dia', 'comet'].map((b) => ({
157
+ name: b.charAt(0).toUpperCase() + b.slice(1),
158
+ value: b,
159
+ checked: currentBrowsers.includes(b),
160
+ })),
161
+ });
162
+ const schedule = await select({
163
+ message: 'Collection schedule:',
164
+ choices: SCHEDULE_CHOICES,
165
+ loop: false,
166
+ default: config.sources.browser.schedule,
167
+ });
168
+ config.sources.browser = {
169
+ enabled: true,
170
+ schedule,
171
+ options: { ...config.sources.browser.options, browsers: selectedBrowsers },
172
+ };
173
+ }
174
+ else {
175
+ config.sources.browser.enabled = false;
176
+ }
177
+ hasChanges = true;
178
+ }
179
+ if (choice === 'git') {
180
+ const gitInstalled = detectGitInstalled();
181
+ printDim(gitInstalled ? ' Git is installed' : ' Git not found');
182
+ const enabled = await confirm({
183
+ message: 'Enable git commit collection?',
184
+ default: config.sources.git.enabled,
185
+ });
186
+ if (enabled) {
187
+ const currentPaths = config.sources.git.options.scanPaths || [];
188
+ const scanPaths = await selectPaths('Select paths to scan for git repositories:', GIT_PATH_PRESETS, currentPaths);
189
+ const schedule = await select({
190
+ message: 'Collection schedule:',
191
+ choices: SCHEDULE_CHOICES,
192
+ loop: false,
193
+ default: config.sources.git.schedule,
194
+ });
195
+ config.sources.git = {
196
+ enabled: true,
197
+ schedule,
198
+ options: { ...config.sources.git.options, scanPaths },
199
+ };
200
+ }
201
+ else {
202
+ config.sources.git.enabled = false;
203
+ }
204
+ hasChanges = true;
205
+ }
206
+ if (choice === 'filesystem') {
207
+ const enabled = await confirm({
208
+ message: 'Enable filesystem monitoring?',
209
+ default: config.sources.filesystem.enabled,
210
+ });
211
+ if (enabled) {
212
+ const currentPaths = config.sources.filesystem.options.watchPaths || [];
213
+ const watchPaths = await selectPaths('Select paths to monitor:', FILESYSTEM_PATH_PRESETS, currentPaths);
214
+ const schedule = await select({
215
+ message: 'Collection schedule:',
216
+ choices: SCHEDULE_CHOICES,
217
+ loop: false,
218
+ default: config.sources.filesystem.schedule,
219
+ });
220
+ config.sources.filesystem = {
221
+ enabled: true,
222
+ schedule,
223
+ options: { ...config.sources.filesystem.options, watchPaths },
224
+ };
225
+ }
226
+ else {
227
+ config.sources.filesystem.enabled = false;
228
+ }
229
+ hasChanges = true;
230
+ }
231
+ if (choice === 'chatbot') {
232
+ const detectedChatbots = detectChatbotClients();
233
+ if (detectedChatbots.length > 0) {
234
+ printDim(` Detected: ${detectedChatbots.join(', ')}`);
235
+ }
236
+ const enabled = await confirm({
237
+ message: 'Enable chatbot history collection?',
238
+ default: config.sources.chatbot.enabled,
239
+ });
240
+ if (enabled) {
241
+ const schedule = await select({
242
+ message: 'Collection schedule:',
243
+ choices: SCHEDULE_CHOICES,
244
+ loop: false,
245
+ default: config.sources.chatbot.schedule,
246
+ });
247
+ config.sources.chatbot = {
248
+ enabled: true,
249
+ schedule,
250
+ options: {
251
+ ...config.sources.chatbot.options,
252
+ clients: detectedChatbots.length > 0 ? detectedChatbots : ['chatwise'],
253
+ },
254
+ };
255
+ }
256
+ else {
257
+ config.sources.chatbot.enabled = false;
258
+ }
259
+ hasChanges = true;
260
+ }
261
+ if (choice === 'logging') {
262
+ const level = await select({
263
+ message: 'Log level:',
264
+ loop: false,
265
+ choices: [
266
+ { name: 'Debug (verbose)', value: 'debug' },
267
+ { name: 'Info (default)', value: 'info' },
268
+ { name: 'Warn', value: 'warn' },
269
+ { name: 'Error (quiet)', value: 'error' },
270
+ ],
271
+ default: config.logging.level,
272
+ });
273
+ if (level !== config.logging.level) {
274
+ config.logging.level = level;
275
+ hasChanges = true;
276
+ }
277
+ }
278
+ }
97
279
  });
98
280
  configCommand
99
281
  .command('validate')
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoHpC,eAAO,MAAM,aAAa,SAiDtB,CAAC"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoHpC,eAAO,MAAM,aAAa,SA+CtB,CAAC"}
@@ -107,7 +107,6 @@ export const doctorCommand = new Command('doctor')
107
107
  const globalOpts = cmd.optsWithGlobals();
108
108
  const customPath = globalOpts.config;
109
109
  console.log('Running diagnostics...\n');
110
- // Synchronous checks
111
110
  const syncChecks = [
112
111
  { name: 'Node.js version', result: checkNodeVersion() },
113
112
  { name: 'Configuration file', result: checkConfigExists(customPath) },
@@ -128,7 +127,6 @@ export const doctorCommand = new Command('doctor')
128
127
  hasIssues = true;
129
128
  }
130
129
  }
131
- // Async check for API
132
130
  const apiResult = await checkApiConnection(customPath);
133
131
  if (apiResult.ok) {
134
132
  printSuccess(`API connectivity: ${apiResult.message}`);
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,eAAO,MAAM,WAAW,SAwPpB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,eAAO,MAAM,WAAW,SAqNpB,CAAC"}
@@ -1,27 +1,18 @@
1
1
  import { Command } from 'commander';
2
2
  import { existsSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
- import { input, confirm, select, checkbox, password } from '@inquirer/prompts';
3
+ import { confirm, select, checkbox } from '@inquirer/prompts';
5
4
  import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients } from '../detect/index.js';
6
5
  import { writeConfig } from '../../config/writer.js';
7
6
  import { getUserConfigPath } from '../../config/paths.js';
8
7
  import { printBanner, printSection, printSuccess, printInfo, printDim, printWarning } from '../utils/output.js';
9
- import { expandPath, parsePaths } from '../utils/path.js';
10
- function warnMissingPaths(paths) {
11
- for (const p of paths) {
12
- const expanded = expandPath(p);
13
- if (!existsSync(expanded)) {
14
- printWarning(`Path does not exist: ${p}`);
15
- }
16
- }
17
- }
8
+ import { SCHEDULE_CHOICES_WITH_HINT, GIT_PATH_PRESETS, FILESYSTEM_PATH_PRESETS, selectPaths, } from '../utils/prompts.js';
9
+ import { inputApiUrl, inputApiKey } from '../utils/api.js';
18
10
  export const initCommand = new Command('init')
19
11
  .description('Initialize configuration with interactive wizard')
20
12
  .option('--force', 'overwrite existing configuration')
21
13
  .action(async (options, cmd) => {
22
14
  const globalOpts = cmd.optsWithGlobals();
23
15
  const configPath = globalOpts.config || getUserConfigPath();
24
- // Check if config already exists
25
16
  if (existsSync(configPath) && !options.force) {
26
17
  printInfo(`Configuration already exists at ${configPath}`);
27
18
  const overwrite = await confirm({
@@ -55,23 +46,8 @@ export const initCommand = new Command('init')
55
46
  };
56
47
  // Step 1: API Configuration
57
48
  printSection('Step 1/5: API Configuration');
58
- config.api.baseUrl = await input({
59
- message: 'API Base URL:',
60
- default: 'http://localhost:3000',
61
- validate: (value) => {
62
- try {
63
- new URL(value);
64
- return true;
65
- }
66
- catch {
67
- return 'Please enter a valid URL';
68
- }
69
- },
70
- });
71
- config.api.apiKey = await password({
72
- message: 'API Key:',
73
- validate: (value) => (value.length > 0 ? true : 'API Key is required'),
74
- });
49
+ config.api.baseUrl = await inputApiUrl();
50
+ config.api.apiKey = await inputApiKey();
75
51
  // Step 2: Browser History
76
52
  printSection('Step 2/5: Browser History');
77
53
  const detectedBrowsers = detectInstalledBrowsers();
@@ -88,6 +64,7 @@ export const initCommand = new Command('init')
88
64
  if (enableBrowser) {
89
65
  const selectedBrowsers = await checkbox({
90
66
  message: 'Select browsers to collect:',
67
+ loop: false,
91
68
  choices: [
92
69
  { name: 'Chrome', value: 'chrome', checked: detectedBrowsers.includes('chrome') },
93
70
  { name: 'Safari', value: 'safari', checked: detectedBrowsers.includes('safari') },
@@ -96,19 +73,14 @@ export const initCommand = new Command('init')
96
73
  { name: 'Comet', value: 'comet', checked: detectedBrowsers.includes('comet') },
97
74
  ],
98
75
  });
99
- // If no browsers selected, disable the source
100
76
  if (selectedBrowsers.length === 0) {
101
77
  printWarning('No browsers selected, browser collection will be disabled.');
102
78
  }
103
79
  else {
104
80
  const browserSchedule = await select({
105
81
  message: 'Collection schedule:',
106
- choices: [
107
- { name: 'Hourly', value: 'hourly' },
108
- { name: 'Daily (recommended)', value: 'daily' },
109
- { name: 'Weekly', value: 'weekly' },
110
- { name: 'Manual only', value: 'manual' },
111
- ],
82
+ choices: SCHEDULE_CHOICES_WITH_HINT,
83
+ loop: false,
112
84
  default: 'daily',
113
85
  });
114
86
  config.sources.browser = {
@@ -131,25 +103,15 @@ export const initCommand = new Command('init')
131
103
  default: true,
132
104
  });
133
105
  if (enableGit) {
134
- const defaultPaths = [`${homedir()}/Projects`, `${homedir()}/Documents`].filter(existsSync);
135
- const scanPathsInput = await input({
136
- message: 'Paths to scan for git repositories (comma-separated):',
137
- default: defaultPaths.join(',') || `${homedir()}/Projects`,
138
- });
139
- const scanPaths = parsePaths(scanPathsInput);
106
+ const scanPaths = await selectPaths('Select paths to scan for git repositories:', GIT_PATH_PRESETS);
140
107
  if (scanPaths.length === 0) {
141
- printWarning('No paths specified, git collection will be disabled.');
108
+ printWarning('No paths selected, git collection will be disabled.');
142
109
  }
143
110
  else {
144
- warnMissingPaths(scanPaths);
145
111
  const gitSchedule = await select({
146
112
  message: 'Collection schedule:',
147
- choices: [
148
- { name: 'Hourly', value: 'hourly' },
149
- { name: 'Daily (recommended)', value: 'daily' },
150
- { name: 'Weekly', value: 'weekly' },
151
- { name: 'Manual only', value: 'manual' },
152
- ],
113
+ choices: SCHEDULE_CHOICES_WITH_HINT,
114
+ loop: false,
153
115
  default: 'daily',
154
116
  });
155
117
  config.sources.git = {
@@ -162,26 +124,27 @@ export const initCommand = new Command('init')
162
124
  };
163
125
  }
164
126
  }
165
- // Step 4: Filesystem (Optional)
127
+ // Step 4: Filesystem
166
128
  printSection('Step 4/5: Filesystem Changes');
167
129
  const enableFilesystem = await confirm({
168
130
  message: 'Enable filesystem monitoring?',
169
131
  default: true,
170
132
  });
171
133
  if (enableFilesystem) {
172
- const watchPathsInput = await input({
173
- message: 'Paths to monitor (comma-separated):',
174
- default: `${homedir()}/Documents,${homedir()}/Desktop`,
175
- });
176
- const watchPaths = parsePaths(watchPathsInput);
134
+ const watchPaths = await selectPaths('Select paths to monitor:', FILESYSTEM_PATH_PRESETS);
177
135
  if (watchPaths.length === 0) {
178
- printWarning('No paths specified, filesystem monitoring will be disabled.');
136
+ printWarning('No paths selected, filesystem monitoring will be disabled.');
179
137
  }
180
138
  else {
181
- warnMissingPaths(watchPaths);
139
+ const fsSchedule = await select({
140
+ message: 'Collection schedule:',
141
+ choices: SCHEDULE_CHOICES_WITH_HINT,
142
+ loop: false,
143
+ default: 'daily',
144
+ });
182
145
  config.sources.filesystem = {
183
146
  enabled: true,
184
- schedule: 'daily',
147
+ schedule: fsSchedule,
185
148
  options: {
186
149
  watchPaths,
187
150
  excludePatterns: ['**/node_modules/**', '**/.git/**', '**/Library/**'],
@@ -191,7 +154,7 @@ export const initCommand = new Command('init')
191
154
  };
192
155
  }
193
156
  }
194
- // Step 5: Chatbot (Optional)
157
+ // Step 5: Chatbot
195
158
  printSection('Step 5/5: Chatbot History');
196
159
  const detectedChatbots = detectChatbotClients();
197
160
  if (detectedChatbots.length > 0) {
@@ -215,7 +178,6 @@ export const initCommand = new Command('init')
215
178
  },
216
179
  };
217
180
  }
218
- // Summary
219
181
  printSection('Configuration Summary');
220
182
  console.log(` API: ${config.api.baseUrl}`);
221
183
  console.log(' Sources enabled:');
@@ -223,7 +185,6 @@ export const initCommand = new Command('init')
223
185
  console.log(` ${config.sources.git.enabled ? '✓' : '✗'} Git`);
224
186
  console.log(` ${config.sources.filesystem.enabled ? '✓' : '✗'} Filesystem`);
225
187
  console.log(` ${config.sources.chatbot.enabled ? '✓' : '✗'} Chatbot`);
226
- // Save
227
188
  const shouldSave = await confirm({
228
189
  message: `Save configuration to ${configPath}?`,
229
190
  default: true,
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,YAAY,SA8DrB,CAAC"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,YAAY,SA6DrB,CAAC"}
@@ -12,7 +12,6 @@ export const startCommand = new Command('start')
12
12
  const globalOpts = cmd.optsWithGlobals();
13
13
  try {
14
14
  const config = loadConfig(globalOpts.config);
15
- // Apply global log level overrides
16
15
  let logLevel = config.logging.level;
17
16
  if (globalOpts.verbose)
18
17
  logLevel = 'debug';
package/dist/cli/index.js CHANGED
@@ -9,7 +9,6 @@ import { collectCommand } from './commands/collect.js';
9
9
  import { configCommand } from './commands/config.js';
10
10
  import { statusCommand } from './commands/status.js';
11
11
  import { doctorCommand } from './commands/doctor.js';
12
- // Read version from package.json
13
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
13
  const packagePath = join(__dirname, '../../package.json');
15
14
  const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Input API URL with validation
3
+ */
4
+ export declare function inputApiUrl(defaultUrl?: string): Promise<string>;
5
+ /**
6
+ * Input API Key with masked confirmation
7
+ */
8
+ export declare function inputApiKey(): Promise<string>;
9
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/api.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,WAAW,CAAC,UAAU,SAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAavF;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAOnD"}
@@ -0,0 +1,31 @@
1
+ import { input } from '@inquirer/prompts';
2
+ import { showMaskedKey } from './prompts.js';
3
+ /**
4
+ * Input API URL with validation
5
+ */
6
+ export async function inputApiUrl(defaultUrl = 'http://localhost:3000') {
7
+ return input({
8
+ message: 'API Base URL:',
9
+ default: defaultUrl,
10
+ validate: (value) => {
11
+ try {
12
+ new URL(value);
13
+ return true;
14
+ }
15
+ catch {
16
+ return 'Please enter a valid URL';
17
+ }
18
+ },
19
+ });
20
+ }
21
+ /**
22
+ * Input API Key with masked confirmation
23
+ */
24
+ export async function inputApiKey() {
25
+ const apiKey = await input({
26
+ message: 'API Key:',
27
+ validate: (value) => (value.length > 0 ? true : 'API Key is required'),
28
+ });
29
+ showMaskedKey(apiKey);
30
+ return apiKey;
31
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Mask an API key for display (show first 8 and last 4 chars)
3
+ */
4
+ export declare function maskApiKey(key: string): string;
5
+ /**
6
+ * Show masked API key confirmation
7
+ */
8
+ export declare function showMaskedKey(key: string): void;
9
+ /**
10
+ * Standard schedule choices for select prompts
11
+ */
12
+ export declare const SCHEDULE_CHOICES: ({
13
+ name: string;
14
+ value: "hourly";
15
+ } | {
16
+ name: string;
17
+ value: "daily";
18
+ } | {
19
+ name: string;
20
+ value: "weekly";
21
+ } | {
22
+ name: string;
23
+ value: "monthly";
24
+ } | {
25
+ name: string;
26
+ value: "manual";
27
+ })[];
28
+ /**
29
+ * Schedule choices with (recommended) hint for init wizard
30
+ */
31
+ export declare const SCHEDULE_CHOICES_WITH_HINT: ({
32
+ name: string;
33
+ value: "hourly";
34
+ } | {
35
+ name: string;
36
+ value: "daily";
37
+ } | {
38
+ name: string;
39
+ value: "weekly";
40
+ } | {
41
+ name: string;
42
+ value: "manual";
43
+ })[];
44
+ /** Preset paths for Git repository scanning */
45
+ export declare const GIT_PATH_PRESETS: {
46
+ path: string;
47
+ defaultChecked: boolean;
48
+ }[];
49
+ /** Preset paths for Filesystem monitoring */
50
+ export declare const FILESYSTEM_PATH_PRESETS: {
51
+ path: string;
52
+ defaultChecked: boolean;
53
+ }[];
54
+ interface PathPreset {
55
+ path: string;
56
+ defaultChecked: boolean;
57
+ }
58
+ /**
59
+ * Interactive path selector with presets and custom path support
60
+ */
61
+ export declare function selectPaths(message: string, presets: PathPreset[], currentPaths?: string[]): Promise<string[]>;
62
+ export {};
63
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/prompts.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAE/C;AAED;;GAEG;AACH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;IAM5B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;IAKtC,CAAC;AAIF,+CAA+C;AAC/C,eAAO,MAAM,gBAAgB;;;GAI5B,CAAC;AAEF,6CAA6C;AAC7C,eAAO,MAAM,uBAAuB;;;GAInC,CAAC;AAIF,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EAAE,EACrB,YAAY,CAAC,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,EAAE,CAAC,CAyCnB"}
@@ -0,0 +1,91 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { checkbox, input } from '@inquirer/prompts';
4
+ import { printDim } from './output.js';
5
+ import { expandPath } from '../../utils/path.js';
6
+ /**
7
+ * Mask an API key for display (show first 8 and last 4 chars)
8
+ */
9
+ export function maskApiKey(key) {
10
+ return key.length > 12 ? key.slice(0, 8) + '***...' + key.slice(-4) : '***';
11
+ }
12
+ /**
13
+ * Show masked API key confirmation
14
+ */
15
+ export function showMaskedKey(key) {
16
+ printDim(` Saved as: ${maskApiKey(key)}`);
17
+ }
18
+ /**
19
+ * Standard schedule choices for select prompts
20
+ */
21
+ export const SCHEDULE_CHOICES = [
22
+ { name: 'Hourly', value: 'hourly' },
23
+ { name: 'Daily', value: 'daily' },
24
+ { name: 'Weekly', value: 'weekly' },
25
+ { name: 'Monthly', value: 'monthly' },
26
+ { name: 'Manual only', value: 'manual' },
27
+ ];
28
+ /**
29
+ * Schedule choices with (recommended) hint for init wizard
30
+ */
31
+ export const SCHEDULE_CHOICES_WITH_HINT = [
32
+ { name: 'Hourly', value: 'hourly' },
33
+ { name: 'Daily (recommended)', value: 'daily' },
34
+ { name: 'Weekly', value: 'weekly' },
35
+ { name: 'Manual only', value: 'manual' },
36
+ ];
37
+ const home = homedir();
38
+ /** Preset paths for Git repository scanning */
39
+ export const GIT_PATH_PRESETS = [
40
+ { path: `${home}/Documents`, defaultChecked: true },
41
+ { path: `${home}/Desktop`, defaultChecked: false },
42
+ { path: `${home}/Developer`, defaultChecked: false },
43
+ ];
44
+ /** Preset paths for Filesystem monitoring */
45
+ export const FILESYSTEM_PATH_PRESETS = [
46
+ { path: `${home}/Documents`, defaultChecked: true },
47
+ { path: `${home}/Desktop`, defaultChecked: true },
48
+ { path: `${home}/Downloads`, defaultChecked: true },
49
+ ];
50
+ const ADD_CUSTOM_PATH = '__add_custom__';
51
+ /**
52
+ * Interactive path selector with presets and custom path support
53
+ */
54
+ export async function selectPaths(message, presets, currentPaths) {
55
+ const existingPresets = presets.filter((p) => existsSync(p.path));
56
+ const choices = existingPresets.map((preset) => {
57
+ const isChecked = currentPaths ? currentPaths.includes(preset.path) : preset.defaultChecked;
58
+ return {
59
+ name: preset.path.replace(home, '~'),
60
+ value: preset.path,
61
+ checked: isChecked,
62
+ };
63
+ });
64
+ choices.push({
65
+ name: '── Add custom path...',
66
+ value: ADD_CUSTOM_PATH,
67
+ checked: false,
68
+ });
69
+ const selected = await checkbox({
70
+ message,
71
+ choices,
72
+ loop: false,
73
+ });
74
+ if (selected.includes(ADD_CUSTOM_PATH)) {
75
+ const filtered = selected.filter((p) => p !== ADD_CUSTOM_PATH);
76
+ const customPath = await input({
77
+ message: 'Enter custom path:',
78
+ validate: (value) => {
79
+ if (!value.trim())
80
+ return 'Path cannot be empty';
81
+ const expanded = expandPath(value.trim());
82
+ if (!existsSync(expanded)) {
83
+ return `Path does not exist: ${value}`;
84
+ }
85
+ return true;
86
+ },
87
+ });
88
+ return [...filtered, expandPath(customPath.trim())];
89
+ }
90
+ return selected;
91
+ }
@@ -1,5 +1,5 @@
1
1
  import type { CollectorConfig } from '../config/schema.js';
2
- import type { SourceType } from './types.js';
2
+ import type { CollectionResult, SourceType } from './types.js';
3
3
  import type { Logger } from '../utils/logger.js';
4
4
  export declare class Collector {
5
5
  private config;
@@ -11,9 +11,9 @@ export declare class Collector {
11
11
  validateSources(): Promise<void>;
12
12
  start(): Promise<void>;
13
13
  stop(): Promise<void>;
14
- collectAndPush(sourceType: SourceType): Promise<void>;
14
+ collectAndPush(sourceType: SourceType): Promise<CollectionResult | undefined>;
15
15
  collectAll(): Promise<void>;
16
- triggerCollection(sourceType: SourceType): Promise<void>;
16
+ triggerCollection(sourceType: SourceType): Promise<CollectionResult | undefined>;
17
17
  getEnabledSources(): SourceType[];
18
18
  }
19
19
  //# sourceMappingURL=collector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../src/core/collector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAA0C;gBAE7C,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM;IAO7C,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BrD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9D,iBAAiB,IAAI,UAAU,EAAE;CAGlC"}
1
+ {"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../src/core/collector.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEjD,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAA0C;gBAE7C,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM;IAO7C,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBhC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKrB,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IA4B7E,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAItF,iBAAiB,IAAI,UAAU,EAAE;CAGlC"}
@@ -55,18 +55,20 @@ export class Collector {
55
55
  const result = await source.collect();
56
56
  if (!result.success) {
57
57
  this.logger.error(`Collection failed for ${sourceType}`, result.error);
58
- return;
58
+ return result;
59
59
  }
60
60
  if (result.itemsCollected === 0) {
61
61
  this.logger.info(`No new items from ${sourceType}`);
62
- return;
62
+ return result;
63
63
  }
64
64
  this.logger.info(`Collected ${result.itemsCollected} items from ${sourceType}`);
65
65
  const response = await this.apiClient.pushData(result);
66
66
  this.logger.info(`Pushed to API: ${response.data.itemsReceived} received, ${response.data.itemsInserted} inserted`);
67
+ return result;
67
68
  }
68
69
  catch (error) {
69
70
  this.logger.error(`Error in collect/push cycle for ${sourceType}`, error);
71
+ return undefined;
70
72
  }
71
73
  }
72
74
  async collectAll() {
@@ -75,7 +77,7 @@ export class Collector {
75
77
  }
76
78
  }
77
79
  async triggerCollection(sourceType) {
78
- await this.collectAndPush(sourceType);
80
+ return this.collectAndPush(sourceType);
79
81
  }
80
82
  getEnabledSources() {
81
83
  return Array.from(this.sources.keys());
@@ -4,7 +4,7 @@ export declare class Scheduler {
4
4
  private tasks;
5
5
  private logger;
6
6
  constructor(logger: Logger);
7
- schedule(sourceType: SourceType, frequency: ScheduleFrequency, handler: () => Promise<void>): void;
7
+ schedule(sourceType: SourceType, frequency: ScheduleFrequency, handler: () => Promise<unknown>): void;
8
8
  unschedule(sourceType: SourceType): void;
9
9
  startAll(): void;
10
10
  stopAll(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgBjD,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAI1B,QAAQ,CACN,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAC3B,IAAI;IAoBP,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAQxC,QAAQ,IAAI,IAAI;IAMhB,OAAO,IAAI,IAAI;CAKhB"}
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAgBjD,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAwC;IACrD,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAI1B,QAAQ,CACN,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC9B,IAAI;IAoBP,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAQxC,QAAQ,IAAI,IAAI;IAMhB,OAAO,IAAI,IAAI;CAKhB"}
@@ -10,6 +10,16 @@ export interface CollectedItem {
10
10
  timestamp: Date;
11
11
  data: unknown;
12
12
  }
13
+ /** A single skipped item with path and reason */
14
+ export interface SkippedItem {
15
+ path: string;
16
+ reason: string;
17
+ }
18
+ /** Information about skipped items during collection */
19
+ export interface SkippedInfo {
20
+ count: number;
21
+ items: SkippedItem[];
22
+ }
13
23
  /** Result from a collection operation */
14
24
  export interface CollectionResult {
15
25
  sourceType: SourceType;
@@ -18,6 +28,8 @@ export interface CollectionResult {
18
28
  items: CollectedItem[];
19
29
  error?: Error;
20
30
  collectedAt: Date;
31
+ /** Information about items that were skipped (e.g., empty repos) */
32
+ skipped?: SkippedInfo;
21
33
  }
22
34
  /** Configuration for a specific data source */
23
35
  export interface SourceConfig {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;AAEtE,qCAAqC;AACrC,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAyD,CAAC;AAExG,6CAA6C;AAC7C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErF,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;CAClB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC;AAEtE,qCAAqC;AACrC,eAAO,MAAM,YAAY,EAAE,SAAS,UAAU,EAAyD,CAAC;AAExG,6CAA6C;AAC7C,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErF,kDAAkD;AAClD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wDAAwD;AACxD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAED,yCAAyC;AACzC,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,WAAW,EAAE,IAAI,CAAC;IAClB,oEAAoE;IACpE,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;CAClB"}
@@ -5,11 +5,13 @@ export declare class GitSource extends DataSource<GitSourceOptions> {
5
5
  readonly type: "git";
6
6
  readonly name = "Git Commits";
7
7
  private discoveredRepos;
8
+ private skippedRepos;
8
9
  validate(): Promise<boolean>;
9
10
  private discoverRepositories;
10
11
  collect(): Promise<CollectionResult>;
11
12
  private execGit;
12
13
  private getCommitsFromRepo;
14
+ private isEmptyRepoError;
13
15
  private parseGitLog;
14
16
  private getCommitStats;
15
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sources/git/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9D,qBAAa,SAAU,SAAQ,UAAU,CAAC,gBAAgB,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,OAAO,CAAC,eAAe,CAAgB;IAEjC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAqClC,OAAO,CAAC,oBAAoB;IA6BtB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA4B1C,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,kBAAkB;IA6B1B,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,cAAc;CA2BvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sources/git/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,gBAAgB,EAAe,MAAM,qBAAqB,CAAC;AACzE,OAAO,KAAK,EAAa,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG9D,qBAAa,SAAU,SAAQ,UAAU,CAAC,gBAAgB,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,YAAY,CAAqB;IAEnC,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;IAqClC,OAAO,CAAC,oBAAoB;IA6BtB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC;IA+B1C,OAAO,CAAC,OAAO;IAQf,OAAO,CAAC,kBAAkB;IAkC1B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,cAAc;CA2BvB"}
@@ -7,6 +7,7 @@ export class GitSource extends DataSource {
7
7
  type = 'git';
8
8
  name = 'Git Commits';
9
9
  discoveredRepos = [];
10
+ skippedRepos = [];
10
11
  async validate() {
11
12
  try {
12
13
  this.execGit('.', ['--version']);
@@ -68,15 +69,13 @@ export class GitSource extends DataSource {
68
69
  const items = [];
69
70
  const sinceDate = new Date();
70
71
  sinceDate.setDate(sinceDate.getDate() - this.options.sinceDays);
72
+ this.skippedRepos = [];
71
73
  for (const repoPath of this.discoveredRepos) {
72
- try {
73
- const commits = this.getCommitsFromRepo(repoPath, sinceDate);
74
- items.push(...commits);
74
+ const commits = this.getCommitsFromRepo(repoPath, sinceDate);
75
+ items.push(...commits);
76
+ if (commits.length > 0) {
75
77
  this.context.logger.debug(`Found ${commits.length} commits in ${repoPath}`);
76
78
  }
77
- catch (error) {
78
- this.context.logger.error(`Failed to get commits from ${repoPath}`, error);
79
- }
80
79
  }
81
80
  return {
82
81
  sourceType: this.type,
@@ -88,12 +87,16 @@ export class GitSource extends DataSource {
88
87
  data: commit,
89
88
  })),
90
89
  collectedAt: new Date(),
90
+ skipped: this.skippedRepos.length > 0
91
+ ? { count: this.skippedRepos.length, items: this.skippedRepos }
92
+ : undefined,
91
93
  };
92
94
  }
93
95
  execGit(repoPath, args) {
94
96
  return execFileSync('git', ['-C', repoPath, ...args], {
95
97
  encoding: 'utf-8',
96
98
  maxBuffer: 10 * 1024 * 1024,
99
+ stdio: ['pipe', 'pipe', 'pipe'], // Capture stderr instead of inheriting
97
100
  });
98
101
  }
99
102
  getCommitsFromRepo(repoPath, since) {
@@ -117,10 +120,19 @@ export class GitSource extends DataSource {
117
120
  });
118
121
  }
119
122
  catch (error) {
123
+ if (this.isEmptyRepoError(error)) {
124
+ this.skippedRepos.push({ path: repoPath, reason: 'empty repository' });
125
+ this.context.logger.debug(`Skipping empty repository: ${repoPath}`);
126
+ return [];
127
+ }
120
128
  this.context.logger.warn(`Failed to get git log from ${repoPath}`, error);
121
129
  return [];
122
130
  }
123
131
  }
132
+ isEmptyRepoError(error) {
133
+ const message = error instanceof Error ? error.message : String(error);
134
+ return message.includes('does not have any commits yet');
135
+ }
124
136
  parseGitLog(output, repoPath) {
125
137
  const commits = [];
126
138
  const records = output.split('\0\0').filter(Boolean);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liferewind",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "AI-powered personal life review tool - collect your digital footprints from git, browser history, documents, and AI chatbots",
5
5
  "keywords": [
6
6
  "cli",
@@ -43,18 +43,6 @@
43
43
  "LICENSE",
44
44
  "README.md"
45
45
  ],
46
- "scripts": {
47
- "build": "tsc",
48
- "dev": "tsx watch src/cli/index.ts",
49
- "start": "node dist/cli/index.js start",
50
- "lint": "eslint . --max-warnings 0",
51
- "typecheck": "tsc --noEmit",
52
- "prepublishOnly": "npm run build",
53
- "pm2:start": "pm2 start ecosystem.config.cjs",
54
- "pm2:stop": "pm2 stop liferewind-collector",
55
- "pm2:restart": "pm2 restart liferewind-collector",
56
- "pm2:logs": "pm2 logs liferewind-collector"
57
- },
58
46
  "dependencies": {
59
47
  "@inquirer/prompts": "^8.1.0",
60
48
  "better-sqlite3": "^12.5.0",
@@ -69,13 +57,24 @@
69
57
  "@types/better-sqlite3": "^7.6.13",
70
58
  "@types/node": "^20.19.25",
71
59
  "@types/node-cron": "^3.0.11",
72
- "@workspace/eslint-config": "workspace:*",
73
- "@workspace/typescript-config": "workspace:*",
74
60
  "eslint": "^9.39.1",
75
61
  "tsx": "^4.19.0",
76
- "typescript": "^5.9.3"
62
+ "typescript": "^5.9.3",
63
+ "@workspace/typescript-config": "0.0.0",
64
+ "@workspace/eslint-config": "0.0.0"
77
65
  },
78
66
  "engines": {
79
67
  "node": ">=20"
68
+ },
69
+ "scripts": {
70
+ "build": "tsc",
71
+ "dev": "tsx watch src/cli/index.ts",
72
+ "start": "node dist/cli/index.js start",
73
+ "lint": "eslint . --max-warnings 0",
74
+ "typecheck": "tsc --noEmit",
75
+ "pm2:start": "pm2 start ecosystem.config.cjs",
76
+ "pm2:stop": "pm2 stop liferewind-collector",
77
+ "pm2:restart": "pm2 restart liferewind-collector",
78
+ "pm2:logs": "pm2 logs liferewind-collector"
80
79
  }
81
- }
80
+ }
@@ -1,9 +0,0 @@
1
- /**
2
- * Expand a path that may start with ~ or ~/ to an absolute path.
3
- */
4
- export declare function expandPath(path: string): string;
5
- /**
6
- * Parse comma-separated paths and trim whitespace
7
- */
8
- export declare function parsePaths(input: string): string[];
9
- //# sourceMappingURL=path.d.ts.map
@@ -1,23 +0,0 @@
1
- import { homedir } from 'node:os';
2
- import { resolve } from 'node:path';
3
- /**
4
- * Expand a path that may start with ~ or ~/ to an absolute path.
5
- */
6
- export function expandPath(path) {
7
- if (path === '~') {
8
- return homedir();
9
- }
10
- if (path.startsWith('~/')) {
11
- return resolve(homedir(), path.slice(2));
12
- }
13
- return resolve(path);
14
- }
15
- /**
16
- * Parse comma-separated paths and trim whitespace
17
- */
18
- export function parsePaths(input) {
19
- return input
20
- .split(',')
21
- .map((p) => p.trim())
22
- .filter(Boolean);
23
- }