liferewind 0.1.0 → 0.1.1

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,SA6DvB,CAAC"}
@@ -4,13 +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();
22
+ const verbose = !!globalOpts.verbose;
14
23
  // Validate source argument
15
24
  if (source && !SOURCE_TYPES.includes(source)) {
16
25
  printError(`Invalid source: ${source}`);
@@ -42,15 +51,17 @@ export const collectCommand = new Command('collect')
42
51
  process.exit(1);
43
52
  }
44
53
  spinner.start(`Collecting from ${source}...`);
45
- await collector.triggerCollection(source);
54
+ const result = await collector.triggerCollection(source);
46
55
  spinner.succeed(`Collection from ${source} complete`);
56
+ showSkippedInfo(result, verbose);
47
57
  }
48
58
  else {
49
59
  // Collect all enabled sources
50
60
  for (const src of enabledSources) {
51
61
  spinner.start(`Collecting from ${src}...`);
52
- await collector.triggerCollection(src);
62
+ const result = await collector.triggerCollection(src);
53
63
  spinner.succeed(`Collection from ${src} complete`);
64
+ showSkippedInfo(result, verbose);
54
65
  }
55
66
  }
56
67
  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;AAepC,eAAO,MAAM,aAAa,SAA4D,CAAC"}
@@ -1,20 +1,20 @@
1
1
  import { Command } from 'commander';
2
- import { execSync } from 'node:child_process';
3
2
  import { existsSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
4
  import pc from 'picocolors';
5
+ import { input, confirm, select, checkbox } from '@inquirer/prompts';
5
6
  import { loadConfig, findConfigPath } from '../../config/loader.js';
7
+ import { writeConfig } from '../../config/writer.js';
6
8
  import { getUserConfigPath, getAllConfigPaths } from '../../config/paths.js';
7
9
  import { printError, printSuccess, printInfo, printDim } from '../utils/output.js';
10
+ import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients } from '../detect/index.js';
11
+ import { parsePaths } from '../utils/path.js';
12
+ import { maskApiKey, warnMissingPaths, showMaskedKey, SCHEDULE_CHOICES } from '../utils/prompts.js';
8
13
  export const configCommand = new Command('config').description('Manage configuration');
9
14
  function printConfigSummary(config, revealSecrets = false) {
10
15
  console.log(pc.bold('\nAPI Configuration'));
11
16
  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}`);
17
+ console.log(` API Key: ${revealSecrets ? config.api.apiKey : maskApiKey(config.api.apiKey)}`);
18
18
  console.log(` Timeout: ${config.api.timeout}ms`);
19
19
  console.log(` Retries: ${config.api.retryAttempts}`);
20
20
  console.log(pc.bold('\nData Sources'));
@@ -41,8 +41,6 @@ configCommand
41
41
  if (configPath) {
42
42
  printDim(`Loaded from: ${configPath}`);
43
43
  }
44
- // Helper to mask API key
45
- const maskApiKey = (key) => key.length > 12 ? key.slice(0, 8) + '...' + key.slice(-4) : '***';
46
44
  if (options.json) {
47
45
  const output = {
48
46
  ...config,
@@ -77,23 +75,229 @@ configCommand
77
75
  });
78
76
  configCommand
79
77
  .command('edit')
80
- .description('Open configuration in default editor')
81
- .action((_options, cmd) => {
78
+ .description('Interactive configuration editor')
79
+ .action(async (_options, cmd) => {
82
80
  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');
81
+ const configPath = findConfigPath(globalOpts.config) || getUserConfigPath();
82
+ let config;
90
83
  try {
91
- execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
84
+ config = loadConfig(globalOpts.config);
85
+ printDim(`Editing: ${configPath}`);
92
86
  }
93
- catch (error) {
94
- printError(`Failed to open editor: ${error instanceof Error ? error.message : String(error)}`);
87
+ catch {
88
+ printError('No valid config file found.');
89
+ printInfo("Run 'liferewind init' to create one.");
95
90
  process.exit(1);
96
91
  }
92
+ let hasChanges = false;
93
+ // Main menu loop
94
+ while (true) {
95
+ const choice = await select({
96
+ message: 'What would you like to configure?',
97
+ choices: [
98
+ { name: `API Settings ${pc.dim(`(${config.api.baseUrl})`)}`, value: 'api' },
99
+ {
100
+ name: `Browser Collection ${config.sources.browser.enabled ? pc.green('✓') : pc.dim('✗')}`,
101
+ value: 'browser',
102
+ },
103
+ { name: `Git Collection ${config.sources.git.enabled ? pc.green('✓') : pc.dim('✗')}`, value: 'git' },
104
+ {
105
+ name: `Filesystem Collection ${config.sources.filesystem.enabled ? pc.green('✓') : pc.dim('✗')}`,
106
+ value: 'filesystem',
107
+ },
108
+ {
109
+ name: `Chatbot Collection ${config.sources.chatbot.enabled ? pc.green('✓') : pc.dim('✗')}`,
110
+ value: 'chatbot',
111
+ },
112
+ { name: `Logging ${pc.dim(`(${config.logging.level})`)}`, value: 'logging' },
113
+ { name: pc.dim('─────────────'), value: 'separator', disabled: true },
114
+ { name: hasChanges ? pc.green('Save and Exit') : 'Exit', value: 'exit' },
115
+ ],
116
+ });
117
+ if (choice === 'exit') {
118
+ if (hasChanges) {
119
+ const shouldSave = await confirm({ message: 'Save changes?', default: true });
120
+ if (shouldSave) {
121
+ writeConfig(config, configPath);
122
+ printSuccess('Configuration saved!');
123
+ }
124
+ else {
125
+ printInfo('Changes discarded.');
126
+ }
127
+ }
128
+ break;
129
+ }
130
+ // Handle each section
131
+ if (choice === 'api') {
132
+ const newUrl = await input({
133
+ message: 'API Base URL:',
134
+ default: config.api.baseUrl,
135
+ validate: (value) => {
136
+ try {
137
+ new URL(value);
138
+ return true;
139
+ }
140
+ catch {
141
+ return 'Please enter a valid URL';
142
+ }
143
+ },
144
+ });
145
+ const changeKey = await confirm({ message: 'Change API Key?', default: false });
146
+ let newKey = config.api.apiKey;
147
+ if (changeKey) {
148
+ newKey = await input({
149
+ message: 'New API Key:',
150
+ validate: (value) => (value.length > 0 ? true : 'API Key is required'),
151
+ });
152
+ showMaskedKey(newKey);
153
+ }
154
+ if (newUrl !== config.api.baseUrl || newKey !== config.api.apiKey) {
155
+ config.api.baseUrl = newUrl;
156
+ config.api.apiKey = newKey;
157
+ hasChanges = true;
158
+ }
159
+ }
160
+ if (choice === 'browser') {
161
+ const detectedBrowsers = detectInstalledBrowsers();
162
+ if (detectedBrowsers.length > 0) {
163
+ printDim(` Detected: ${detectedBrowsers.join(', ')}`);
164
+ }
165
+ const enabled = await confirm({
166
+ message: 'Enable browser history collection?',
167
+ default: config.sources.browser.enabled,
168
+ });
169
+ if (enabled) {
170
+ const currentBrowsers = config.sources.browser.options.browsers || [];
171
+ const selectedBrowsers = await checkbox({
172
+ message: 'Select browsers:',
173
+ choices: ['chrome', 'safari', 'arc', 'dia', 'comet'].map((b) => ({
174
+ name: b.charAt(0).toUpperCase() + b.slice(1),
175
+ value: b,
176
+ checked: currentBrowsers.includes(b),
177
+ })),
178
+ });
179
+ const schedule = await select({
180
+ message: 'Collection schedule:',
181
+ choices: SCHEDULE_CHOICES,
182
+ default: config.sources.browser.schedule,
183
+ });
184
+ config.sources.browser = {
185
+ enabled: true,
186
+ schedule,
187
+ options: { ...config.sources.browser.options, browsers: selectedBrowsers },
188
+ };
189
+ }
190
+ else {
191
+ config.sources.browser.enabled = false;
192
+ }
193
+ hasChanges = true;
194
+ }
195
+ if (choice === 'git') {
196
+ const gitInstalled = detectGitInstalled();
197
+ printDim(gitInstalled ? ' Git is installed' : ' Git not found');
198
+ const enabled = await confirm({
199
+ message: 'Enable git commit collection?',
200
+ default: config.sources.git.enabled,
201
+ });
202
+ if (enabled) {
203
+ const currentPaths = config.sources.git.options.scanPaths || [];
204
+ const pathsInput = await input({
205
+ message: 'Paths to scan (comma-separated):',
206
+ default: currentPaths.join(',') || `${homedir()}/Projects`,
207
+ });
208
+ const scanPaths = parsePaths(pathsInput);
209
+ warnMissingPaths(scanPaths);
210
+ const schedule = await select({
211
+ message: 'Collection schedule:',
212
+ choices: SCHEDULE_CHOICES,
213
+ default: config.sources.git.schedule,
214
+ });
215
+ config.sources.git = {
216
+ enabled: true,
217
+ schedule,
218
+ options: { ...config.sources.git.options, scanPaths },
219
+ };
220
+ }
221
+ else {
222
+ config.sources.git.enabled = false;
223
+ }
224
+ hasChanges = true;
225
+ }
226
+ if (choice === 'filesystem') {
227
+ const enabled = await confirm({
228
+ message: 'Enable filesystem monitoring?',
229
+ default: config.sources.filesystem.enabled,
230
+ });
231
+ if (enabled) {
232
+ const currentPaths = config.sources.filesystem.options.watchPaths || [];
233
+ const pathsInput = await input({
234
+ message: 'Paths to monitor (comma-separated):',
235
+ default: currentPaths.join(',') || `${homedir()}/Documents`,
236
+ });
237
+ const watchPaths = parsePaths(pathsInput);
238
+ warnMissingPaths(watchPaths);
239
+ const schedule = await select({
240
+ message: 'Collection schedule:',
241
+ choices: SCHEDULE_CHOICES,
242
+ default: config.sources.filesystem.schedule,
243
+ });
244
+ config.sources.filesystem = {
245
+ enabled: true,
246
+ schedule,
247
+ options: { ...config.sources.filesystem.options, watchPaths },
248
+ };
249
+ }
250
+ else {
251
+ config.sources.filesystem.enabled = false;
252
+ }
253
+ hasChanges = true;
254
+ }
255
+ if (choice === 'chatbot') {
256
+ const detectedChatbots = detectChatbotClients();
257
+ if (detectedChatbots.length > 0) {
258
+ printDim(` Detected: ${detectedChatbots.join(', ')}`);
259
+ }
260
+ const enabled = await confirm({
261
+ message: 'Enable chatbot history collection?',
262
+ default: config.sources.chatbot.enabled,
263
+ });
264
+ if (enabled) {
265
+ const schedule = await select({
266
+ message: 'Collection schedule:',
267
+ choices: SCHEDULE_CHOICES,
268
+ default: config.sources.chatbot.schedule,
269
+ });
270
+ config.sources.chatbot = {
271
+ enabled: true,
272
+ schedule,
273
+ options: {
274
+ ...config.sources.chatbot.options,
275
+ clients: detectedChatbots.length > 0 ? detectedChatbots : ['chatwise'],
276
+ },
277
+ };
278
+ }
279
+ else {
280
+ config.sources.chatbot.enabled = false;
281
+ }
282
+ hasChanges = true;
283
+ }
284
+ if (choice === 'logging') {
285
+ const level = await select({
286
+ message: 'Log level:',
287
+ choices: [
288
+ { name: 'Debug (verbose)', value: 'debug' },
289
+ { name: 'Info (default)', value: 'info' },
290
+ { name: 'Warn', value: 'warn' },
291
+ { name: 'Error (quiet)', value: 'error' },
292
+ ],
293
+ default: config.logging.level,
294
+ });
295
+ if (level !== config.logging.level) {
296
+ config.logging.level = level;
297
+ hasChanges = true;
298
+ }
299
+ }
300
+ }
97
301
  });
98
302
  configCommand
99
303
  .command('validate')
@@ -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;AAYpC,eAAO,MAAM,WAAW,SA+OpB,CAAC"}
@@ -1,20 +1,13 @@
1
1
  import { Command } from 'commander';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { homedir } from 'node:os';
4
- import { input, confirm, select, checkbox, password } from '@inquirer/prompts';
4
+ import { input, confirm, select, checkbox } from '@inquirer/prompts';
5
5
  import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients } from '../detect/index.js';
6
6
  import { writeConfig } from '../../config/writer.js';
7
7
  import { getUserConfigPath } from '../../config/paths.js';
8
8
  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
- }
9
+ import { parsePaths } from '../utils/path.js';
10
+ import { warnMissingPaths, showMaskedKey, SCHEDULE_CHOICES_WITH_HINT } from '../utils/prompts.js';
18
11
  export const initCommand = new Command('init')
19
12
  .description('Initialize configuration with interactive wizard')
20
13
  .option('--force', 'overwrite existing configuration')
@@ -68,10 +61,11 @@ export const initCommand = new Command('init')
68
61
  }
69
62
  },
70
63
  });
71
- config.api.apiKey = await password({
64
+ config.api.apiKey = await input({
72
65
  message: 'API Key:',
73
66
  validate: (value) => (value.length > 0 ? true : 'API Key is required'),
74
67
  });
68
+ showMaskedKey(config.api.apiKey);
75
69
  // Step 2: Browser History
76
70
  printSection('Step 2/5: Browser History');
77
71
  const detectedBrowsers = detectInstalledBrowsers();
@@ -103,12 +97,7 @@ export const initCommand = new Command('init')
103
97
  else {
104
98
  const browserSchedule = await select({
105
99
  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
- ],
100
+ choices: SCHEDULE_CHOICES_WITH_HINT,
112
101
  default: 'daily',
113
102
  });
114
103
  config.sources.browser = {
@@ -144,12 +133,7 @@ export const initCommand = new Command('init')
144
133
  warnMissingPaths(scanPaths);
145
134
  const gitSchedule = await select({
146
135
  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
- ],
136
+ choices: SCHEDULE_CHOICES_WITH_HINT,
153
137
  default: 'daily',
154
138
  });
155
139
  config.sources.git = {
package/dist/cli/index.js CHANGED
File without changes
@@ -0,0 +1,48 @@
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
+ * Warn about paths that don't exist
7
+ */
8
+ export declare function warnMissingPaths(paths: string[]): void;
9
+ /**
10
+ * Show masked API key confirmation
11
+ */
12
+ export declare function showMaskedKey(key: string): void;
13
+ /**
14
+ * Standard schedule choices for select prompts
15
+ */
16
+ export declare const SCHEDULE_CHOICES: ({
17
+ name: string;
18
+ value: "hourly";
19
+ } | {
20
+ name: string;
21
+ value: "daily";
22
+ } | {
23
+ name: string;
24
+ value: "weekly";
25
+ } | {
26
+ name: string;
27
+ value: "monthly";
28
+ } | {
29
+ name: string;
30
+ value: "manual";
31
+ })[];
32
+ /**
33
+ * Schedule choices with (recommended) hint for init wizard
34
+ */
35
+ export declare const SCHEDULE_CHOICES_WITH_HINT: ({
36
+ name: string;
37
+ value: "hourly";
38
+ } | {
39
+ name: string;
40
+ value: "daily";
41
+ } | {
42
+ name: string;
43
+ value: "weekly";
44
+ } | {
45
+ name: string;
46
+ value: "manual";
47
+ })[];
48
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/prompts.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAMtD;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"}
@@ -0,0 +1,44 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { printWarning, printDim } from './output.js';
3
+ import { expandPath } from './path.js';
4
+ /**
5
+ * Mask an API key for display (show first 8 and last 4 chars)
6
+ */
7
+ export function maskApiKey(key) {
8
+ return key.length > 12 ? key.slice(0, 8) + '***...' + key.slice(-4) : '***';
9
+ }
10
+ /**
11
+ * Warn about paths that don't exist
12
+ */
13
+ export function warnMissingPaths(paths) {
14
+ for (const p of paths) {
15
+ if (!existsSync(expandPath(p))) {
16
+ printWarning(`Path does not exist: ${p}`);
17
+ }
18
+ }
19
+ }
20
+ /**
21
+ * Show masked API key confirmation
22
+ */
23
+ export function showMaskedKey(key) {
24
+ printDim(` Saved as: ${maskApiKey(key)}`);
25
+ }
26
+ /**
27
+ * Standard schedule choices for select prompts
28
+ */
29
+ export const SCHEDULE_CHOICES = [
30
+ { name: 'Hourly', value: 'hourly' },
31
+ { name: 'Daily', value: 'daily' },
32
+ { name: 'Weekly', value: 'weekly' },
33
+ { name: 'Monthly', value: 'monthly' },
34
+ { name: 'Manual only', value: 'manual' },
35
+ ];
36
+ /**
37
+ * Schedule choices with (recommended) hint for init wizard
38
+ */
39
+ export const SCHEDULE_CHOICES_WITH_HINT = [
40
+ { name: 'Hourly', value: 'hourly' },
41
+ { name: 'Daily (recommended)', value: 'daily' },
42
+ { name: 'Weekly', value: 'weekly' },
43
+ { name: 'Manual only', value: 'manual' },
44
+ ];
@@ -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.1",
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
+ }