liferewind 0.1.3 → 0.1.4

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.
package/README.md CHANGED
@@ -22,6 +22,9 @@ liferewind start
22
22
 
23
23
  # Manual collection
24
24
  liferewind collect
25
+
26
+ # First-time collection with extended range (e.g., 90 days)
27
+ liferewind collect --initial
25
28
  ```
26
29
 
27
30
  ## Data Sources
@@ -43,7 +46,7 @@ Config file lookup order:
43
46
  3. `~/.liferewind-collector.json`
44
47
  4. `./collector.config.json`
45
48
 
46
- Example configuration (see `collector.config.example.json`):
49
+ Example configuration:
47
50
 
48
51
  ```json
49
52
  {
@@ -57,7 +60,8 @@ Example configuration (see `collector.config.example.json`):
57
60
  "schedule": "daily",
58
61
  "options": {
59
62
  "scanPaths": ["~/Documents", "~/Projects"],
60
- "sinceDays": 30
63
+ "sinceDays": 2,
64
+ "initialSinceDays": 90
61
65
  }
62
66
  },
63
67
  "browser": {
@@ -66,7 +70,8 @@ Example configuration (see `collector.config.example.json`):
66
70
  "options": {
67
71
  "browsers": ["chrome", "safari", "arc"],
68
72
  "excludeDomains": ["localhost", "127.0.0.1"],
69
- "sinceDays": 7
73
+ "sinceDays": 2,
74
+ "initialSinceDays": 30
70
75
  }
71
76
  },
72
77
  "filesystem": {
@@ -76,7 +81,8 @@ Example configuration (see `collector.config.example.json`):
76
81
  "watchPaths": ["~/Documents"],
77
82
  "excludePatterns": ["**/node_modules/**", "**/.git/**"],
78
83
  "fileTypes": [".md", ".txt", ".docx", ".pdf"],
79
- "sinceDays": 7,
84
+ "sinceDays": 2,
85
+ "initialSinceDays": 30,
80
86
  "includeContent": true
81
87
  }
82
88
  },
@@ -85,7 +91,8 @@ Example configuration (see `collector.config.example.json`):
85
91
  "schedule": "daily",
86
92
  "options": {
87
93
  "clients": ["chatwise"],
88
- "sinceDays": 30,
94
+ "sinceDays": 2,
95
+ "initialSinceDays": 90,
89
96
  "includeContent": true
90
97
  }
91
98
  }
@@ -96,6 +103,10 @@ Example configuration (see `collector.config.example.json`):
96
103
  }
97
104
  ```
98
105
 
106
+ **Configuration options:**
107
+ - `sinceDays`: Time range for daily scheduled collection (default: 2 days)
108
+ - `initialSinceDays`: Time range for first-time collection with `--initial` flag (default: 30-90 days)
109
+
99
110
  **Note:** Git repositories are automatically excluded from filesystem scanning.
100
111
 
101
112
  Environment variables (fallback):
@@ -119,6 +130,7 @@ liferewind start # Start collector service
119
130
  liferewind start --run-once # Collect once and exit
120
131
  liferewind collect # Manual trigger (all sources)
121
132
  liferewind collect git # Manual trigger (specific source)
133
+ liferewind collect --initial # First-time collection (extended range)
122
134
 
123
135
  # Diagnostics
124
136
  liferewind status # Show service status
@@ -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;AAqBpC,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;AAoBpC,eAAO,MAAM,aAAa,SAA4D,CAAC"}
@@ -0,0 +1,9 @@
1
+ interface DirectoryBrowserConfig {
2
+ message: string;
3
+ rootPaths: string[];
4
+ defaultSelected?: string[];
5
+ defaultExpanded?: string[];
6
+ }
7
+ export declare const directoryBrowser: import("@inquirer/type").Prompt<string[], DirectoryBrowserConfig>;
8
+ export {};
9
+ //# sourceMappingURL=directory-browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directory-browser.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/directory-browser.ts"],"names":[],"mappings":"AAgFA,UAAU,sBAAsB;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,eAAO,MAAM,gBAAgB,mEAwH3B,CAAC"}
@@ -0,0 +1,172 @@
1
+ import { createPrompt, useState, useKeypress, useMemo, makeTheme, isEnterKey, isSpaceKey, isUpKey, isDownKey } from '@inquirer/core';
2
+ import { readdirSync, statSync, existsSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { resolve, dirname, basename } from 'node:path';
5
+ import pc from 'picocolors';
6
+ const home = homedir();
7
+ /** Replace home directory with ~ for display */
8
+ function displayPath(path) {
9
+ return path.startsWith(home) ? path.replace(home, '~') : path;
10
+ }
11
+ /** Get subdirectories of a path */
12
+ function getSubdirectories(path) {
13
+ try {
14
+ return readdirSync(path)
15
+ .filter((name) => {
16
+ if (name.startsWith('.'))
17
+ return false;
18
+ try {
19
+ const fullPath = resolve(path, name);
20
+ return statSync(fullPath).isDirectory();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ })
26
+ .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ }
32
+ /** Build visible items list based on expanded state */
33
+ function buildVisibleItems(rootPaths, expanded, selected) {
34
+ const items = [];
35
+ function addPath(path, depth) {
36
+ const name = depth === 0 ? displayPath(path) : basename(path);
37
+ const isSelected = selected.has(path);
38
+ const hasChildren = !isSelected && getSubdirectories(path).length > 0;
39
+ const isExpanded = expanded.has(path);
40
+ items.push({ path, name, depth, isExpanded, hasChildren });
41
+ // Add children if expanded and not selected
42
+ if (isExpanded && !isSelected) {
43
+ for (const child of getSubdirectories(path)) {
44
+ addPath(resolve(path, child), depth + 1);
45
+ }
46
+ }
47
+ }
48
+ for (const rootPath of rootPaths) {
49
+ if (existsSync(rootPath)) {
50
+ addPath(rootPath, 0);
51
+ }
52
+ }
53
+ return items;
54
+ }
55
+ export const directoryBrowser = createPrompt((config, done) => {
56
+ const theme = makeTheme({});
57
+ const [state, setState] = useState(() => {
58
+ const selected = new Set(config.defaultSelected || []);
59
+ const expanded = new Set(config.defaultExpanded || []);
60
+ const items = buildVisibleItems(config.rootPaths, expanded, selected);
61
+ return { items, cursor: 0, selected, expanded };
62
+ });
63
+ const items = useMemo(() => buildVisibleItems(config.rootPaths, state.expanded, state.selected), [state.expanded, state.selected]);
64
+ useKeypress((key) => {
65
+ if (isEnterKey(key)) {
66
+ // Enter: if item has children and not selected, expand/collapse
67
+ // Otherwise: done (if have selections)
68
+ const currentItem = items[state.cursor];
69
+ if (currentItem && !state.selected.has(currentItem.path) && currentItem.hasChildren) {
70
+ // Toggle expand
71
+ const newExpanded = new Set(state.expanded);
72
+ if (newExpanded.has(currentItem.path)) {
73
+ newExpanded.delete(currentItem.path);
74
+ }
75
+ else {
76
+ newExpanded.add(currentItem.path);
77
+ }
78
+ setState({ ...state, expanded: newExpanded });
79
+ }
80
+ else if (state.selected.size > 0) {
81
+ // Done
82
+ done(Array.from(state.selected));
83
+ }
84
+ }
85
+ else if (isSpaceKey(key)) {
86
+ // Toggle selection
87
+ const currentItem = items[state.cursor];
88
+ if (currentItem) {
89
+ const newSelected = new Set(state.selected);
90
+ const newExpanded = new Set(state.expanded);
91
+ if (newSelected.has(currentItem.path)) {
92
+ newSelected.delete(currentItem.path);
93
+ }
94
+ else {
95
+ newSelected.add(currentItem.path);
96
+ // Collapse when selected (selected items can't be expanded)
97
+ newExpanded.delete(currentItem.path);
98
+ }
99
+ setState({ ...state, selected: newSelected, expanded: newExpanded });
100
+ }
101
+ }
102
+ else if (isUpKey(key)) {
103
+ const newCursor = state.cursor > 0 ? state.cursor - 1 : items.length - 1;
104
+ setState({ ...state, cursor: newCursor });
105
+ }
106
+ else if (isDownKey(key)) {
107
+ const newCursor = state.cursor < items.length - 1 ? state.cursor + 1 : 0;
108
+ setState({ ...state, cursor: newCursor });
109
+ }
110
+ else if (key.name === 'right') {
111
+ // Right arrow: expand
112
+ const currentItem = items[state.cursor];
113
+ if (currentItem && !state.selected.has(currentItem.path) && currentItem.hasChildren) {
114
+ const newExpanded = new Set(state.expanded);
115
+ newExpanded.add(currentItem.path);
116
+ setState({ ...state, expanded: newExpanded });
117
+ }
118
+ }
119
+ else if (key.name === 'left') {
120
+ // Left arrow: collapse or go to parent
121
+ const currentItem = items[state.cursor];
122
+ if (currentItem) {
123
+ if (state.expanded.has(currentItem.path)) {
124
+ const newExpanded = new Set(state.expanded);
125
+ newExpanded.delete(currentItem.path);
126
+ setState({ ...state, expanded: newExpanded });
127
+ }
128
+ else if (currentItem.depth > 0) {
129
+ // Find parent and move cursor there
130
+ const parentPath = dirname(currentItem.path);
131
+ const parentIndex = items.findIndex((i) => i.path === parentPath);
132
+ if (parentIndex >= 0) {
133
+ setState({ ...state, cursor: parentIndex });
134
+ }
135
+ }
136
+ }
137
+ }
138
+ else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
139
+ // Cancel
140
+ done([]);
141
+ }
142
+ else if (key.ctrl && key.name === 'd') {
143
+ // Ctrl+D: done
144
+ done(Array.from(state.selected));
145
+ }
146
+ });
147
+ // Render
148
+ const lines = [];
149
+ lines.push(`${theme.style.message(config.message, 'idle')}`);
150
+ lines.push(pc.dim(` ↑↓ navigate Space select → expand ← collapse Enter confirm`));
151
+ lines.push('');
152
+ for (let i = 0; i < items.length; i++) {
153
+ const item = items[i];
154
+ const isCurrent = i === state.cursor;
155
+ const isSelected = state.selected.has(item.path);
156
+ const indent = ' '.repeat(item.depth);
157
+ const prefix = isCurrent ? pc.cyan('❯ ') : ' ';
158
+ const checkbox = isSelected ? pc.green('●') : pc.dim('○');
159
+ let expandIndicator = ' ';
160
+ if (!isSelected && item.hasChildren) {
161
+ expandIndicator = item.isExpanded ? pc.dim('▼ ') : pc.dim('▶ ');
162
+ }
163
+ const nameDisplay = isSelected ? pc.green(item.name) : isCurrent ? pc.cyan(item.name) : item.name;
164
+ lines.push(`${prefix}${indent}${checkbox} ${expandIndicator}${nameDisplay}`);
165
+ }
166
+ if (items.length === 0) {
167
+ lines.push(pc.dim(' No directories found'));
168
+ }
169
+ lines.push('');
170
+ lines.push(pc.dim(` Selected: ${state.selected.size} ${state.selected.size === 1 ? 'path' : 'paths'}`));
171
+ return lines.join('\n');
172
+ });
@@ -56,7 +56,8 @@ interface PathPreset {
56
56
  defaultChecked: boolean;
57
57
  }
58
58
  /**
59
- * Interactive path selector with presets and custom path support
59
+ * Interactive directory browser for path selection
60
+ * Uses ↑↓ to navigate, Space to select, →/Enter to expand, ← to collapse
60
61
  */
61
62
  export declare function selectPaths(message: string, presets: PathPreset[], currentPaths?: string[]): Promise<string[]>;
62
63
  export {};
@@ -1 +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"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/prompts.ts"],"names":[],"mappings":"AAKA;;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;AAEF,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;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,CAwBnB"}
@@ -1,8 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { checkbox, input } from '@inquirer/prompts';
4
3
  import { printDim } from './output.js';
5
- import { expandPath } from '../../utils/path.js';
4
+ import { directoryBrowser } from './directory-browser.js';
6
5
  /**
7
6
  * Mask an API key for display (show first 8 and last 4 chars)
8
7
  */
@@ -47,45 +46,27 @@ export const FILESYSTEM_PATH_PRESETS = [
47
46
  { path: `${home}/Desktop`, defaultChecked: true },
48
47
  { path: `${home}/Downloads`, defaultChecked: true },
49
48
  ];
50
- const ADD_CUSTOM_PATH = '__add_custom__';
51
49
  /**
52
- * Interactive path selector with presets and custom path support
50
+ * Interactive directory browser for path selection
51
+ * Uses ↑↓ to navigate, Space to select, →/Enter to expand, ← to collapse
53
52
  */
54
53
  export async function selectPaths(message, presets, currentPaths) {
54
+ // Filter to existing paths
55
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({
56
+ // Determine default selected paths
57
+ const defaultSelected = existingPresets
58
+ .filter((preset) => (currentPaths ? currentPaths.includes(preset.path) : preset.defaultChecked))
59
+ .map((p) => p.path);
60
+ // Use all preset paths as root paths for browsing
61
+ const rootPaths = existingPresets.map((p) => p.path);
62
+ if (rootPaths.length === 0) {
63
+ printDim(' No valid paths to browse');
64
+ return [];
65
+ }
66
+ const selected = await directoryBrowser({
70
67
  message,
71
- choices,
72
- loop: false,
68
+ rootPaths,
69
+ defaultSelected,
73
70
  });
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
71
  return selected;
91
72
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liferewind",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",
@@ -44,7 +44,9 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
+ "@inquirer/core": "^11.1.0",
47
48
  "@inquirer/prompts": "^8.1.0",
49
+ "@inquirer/type": "^4.0.2",
48
50
  "better-sqlite3": "^12.5.0",
49
51
  "commander": "^14.0.2",
50
52
  "minimatch": "^10.1.1",