liferewind 0.1.3 → 0.1.6
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 +17 -5
- package/dist/cli/commands/config.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +20 -2
- package/dist/cli/detect/git.d.ts +6 -0
- package/dist/cli/detect/git.d.ts.map +1 -1
- package/dist/cli/detect/git.js +18 -0
- package/dist/cli/detect/index.d.ts +1 -1
- package/dist/cli/detect/index.d.ts.map +1 -1
- package/dist/cli/detect/index.js +1 -1
- package/dist/cli/utils/directory-browser.d.ts +9 -0
- package/dist/cli/utils/directory-browser.d.ts.map +1 -0
- package/dist/cli/utils/directory-browser.js +172 -0
- package/dist/cli/utils/prompts.d.ts +2 -1
- package/dist/cli/utils/prompts.d.ts.map +1 -1
- package/dist/cli/utils/prompts.js +17 -36
- package/dist/sources/filesystem/scanner.d.ts.map +1 -1
- package/dist/sources/filesystem/scanner.js +30 -0
- package/package.json +3 -1
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
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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;
|
|
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"}
|
|
@@ -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;AA+BpC,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC,eAAO,MAAM,WAAW,SAiRpB,CAAC"}
|
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import { confirm, select, checkbox, input } from '@inquirer/prompts';
|
|
5
|
-
import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients } from '../detect/index.js';
|
|
5
|
+
import { detectInstalledBrowsers, detectGitInstalled, detectChatbotClients, getGitUserInfo } 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';
|
|
@@ -94,7 +94,11 @@ export const initCommand = new Command('init')
|
|
|
94
94
|
// Step 3: Git Commits
|
|
95
95
|
printSection('Step 3/6: Git Commits');
|
|
96
96
|
const gitInstalled = detectGitInstalled();
|
|
97
|
+
const gitUser = getGitUserInfo();
|
|
97
98
|
printDim(gitInstalled ? ' Git is installed' : ' Git not found');
|
|
99
|
+
if (gitUser.email) {
|
|
100
|
+
printDim(` User: ${gitUser.name || 'unknown'} <${gitUser.email}>`);
|
|
101
|
+
}
|
|
98
102
|
const enableGit = await confirm({
|
|
99
103
|
message: 'Enable git commit collection?',
|
|
100
104
|
default: true,
|
|
@@ -105,6 +109,17 @@ export const initCommand = new Command('init')
|
|
|
105
109
|
printWarning('No paths selected, git collection will be disabled.');
|
|
106
110
|
}
|
|
107
111
|
else {
|
|
112
|
+
// Ask about author filtering
|
|
113
|
+
let authors;
|
|
114
|
+
if (gitUser.email) {
|
|
115
|
+
const filterByAuthor = await confirm({
|
|
116
|
+
message: `Only collect your commits? (filter by ${gitUser.email})`,
|
|
117
|
+
default: true,
|
|
118
|
+
});
|
|
119
|
+
if (filterByAuthor) {
|
|
120
|
+
authors = [gitUser.email];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
108
123
|
const gitSchedule = await select({
|
|
109
124
|
message: 'Collection schedule:',
|
|
110
125
|
choices: SCHEDULE_CHOICES_WITH_HINT,
|
|
@@ -117,6 +132,7 @@ export const initCommand = new Command('init')
|
|
|
117
132
|
options: {
|
|
118
133
|
...DEFAULT_SOURCES.git.options,
|
|
119
134
|
scanPaths,
|
|
135
|
+
...(authors && { authors }),
|
|
120
136
|
},
|
|
121
137
|
};
|
|
122
138
|
}
|
|
@@ -212,7 +228,9 @@ export const initCommand = new Command('init')
|
|
|
212
228
|
console.log(` API: ${config.api.baseUrl}`);
|
|
213
229
|
console.log(' Sources enabled:');
|
|
214
230
|
console.log(` ${config.sources.browser.enabled ? '✓' : '✗'} Browser`);
|
|
215
|
-
|
|
231
|
+
const gitAuthors = config.sources.git.options.authors;
|
|
232
|
+
const gitAuthorInfo = gitAuthors?.length ? ` (author: ${gitAuthors[0]})` : '';
|
|
233
|
+
console.log(` ${config.sources.git.enabled ? '✓' : '✗'} Git${config.sources.git.enabled ? gitAuthorInfo : ''}`);
|
|
216
234
|
console.log(` ${config.sources.filesystem.enabled ? '✓' : '✗'} Filesystem`);
|
|
217
235
|
console.log(` ${config.sources.chatbot.enabled ? '✓' : '✗'} Chatbot`);
|
|
218
236
|
const shouldSave = await confirm({
|
package/dist/cli/detect/git.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
export declare function detectGitInstalled(): boolean;
|
|
2
|
+
export interface GitUserInfo {
|
|
3
|
+
name: string | null;
|
|
4
|
+
email: string | null;
|
|
5
|
+
}
|
|
6
|
+
/** Get the current git user configuration (user.name and user.email) */
|
|
7
|
+
export declare function getGitUserInfo(): GitUserInfo;
|
|
2
8
|
//# sourceMappingURL=git.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/cli/detect/git.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,IAAI,OAAO,CAO5C"}
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../../src/cli/detect/git.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,IAAI,OAAO,CAO5C;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wEAAwE;AACxE,wBAAgB,cAAc,IAAI,WAAW,CAiB5C"}
|
package/dist/cli/detect/git.js
CHANGED
|
@@ -8,3 +8,21 @@ export function detectGitInstalled() {
|
|
|
8
8
|
return false;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/** Get the current git user configuration (user.name and user.email) */
|
|
12
|
+
export function getGitUserInfo() {
|
|
13
|
+
let name = null;
|
|
14
|
+
let email = null;
|
|
15
|
+
try {
|
|
16
|
+
name = execSync('git config user.name', { encoding: 'utf-8' }).trim() || null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Not configured
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
email = execSync('git config user.email', { encoding: 'utf-8' }).trim() || null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Not configured
|
|
26
|
+
}
|
|
27
|
+
return { name, email };
|
|
28
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { detectInstalledBrowsers, type BrowserType } from './browsers.js';
|
|
2
|
-
export { detectGitInstalled } from './git.js';
|
|
2
|
+
export { detectGitInstalled, getGitUserInfo, type GitUserInfo } from './git.js';
|
|
3
3
|
export { detectChatbotClients, type ChatbotClient } from './chatbot.js';
|
|
4
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/detect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/detect/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,oBAAoB,EAAE,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/cli/detect/index.js
CHANGED
|
@@ -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
|
|
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":"
|
|
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 {
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../src/sources/filesystem/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../../src/sources/filesystem/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAyK1E,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,uBAAuB,CAAC;CAClC;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,cAAc,CAAS;gBAEnB,OAAO,EAAE,cAAc;IAMnC,6CAA6C;IAC7C,OAAO,CAAC,UAAU;IAMlB,+CAA+C;IAC/C,OAAO,CAAC,gBAAgB;IAQxB,uCAAuC;IACvC,OAAO,CAAC,WAAW;IAInB,yCAAyC;IACzC,OAAO,CAAC,iBAAiB;IAezB,0CAA0C;IAC1C,OAAO,CAAC,aAAa;IA4ErB,sCAAsC;IACtC,IAAI,IAAI,cAAc,EAAE;CAiCzB"}
|
|
@@ -21,6 +21,32 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
21
21
|
]);
|
|
22
22
|
/** Maximum characters to include in content preview */
|
|
23
23
|
const CONTENT_PREVIEW_LENGTH = 500;
|
|
24
|
+
/** Check if a filename is a temporary or hidden file that should be skipped */
|
|
25
|
+
function isTemporaryOrHiddenFile(fileName) {
|
|
26
|
+
// Hidden files (start with .)
|
|
27
|
+
if (fileName.startsWith('.'))
|
|
28
|
+
return true;
|
|
29
|
+
// Microsoft Office temporary files (~$xxx.docx, ~WRL0001.tmp)
|
|
30
|
+
if (fileName.startsWith('~$') || fileName.startsWith('~WRL'))
|
|
31
|
+
return true;
|
|
32
|
+
// General temporary files starting with ~
|
|
33
|
+
if (fileName.startsWith('~') && !fileName.endsWith('.md'))
|
|
34
|
+
return true;
|
|
35
|
+
// macOS temporary files (.DS_Store already caught by hidden check)
|
|
36
|
+
// Windows temporary files
|
|
37
|
+
if (fileName.endsWith('.tmp') || fileName.endsWith('.temp'))
|
|
38
|
+
return true;
|
|
39
|
+
// Backup files
|
|
40
|
+
if (fileName.endsWith('~') || fileName.endsWith('.bak'))
|
|
41
|
+
return true;
|
|
42
|
+
// Lock files
|
|
43
|
+
if (fileName.endsWith('.lock') || fileName.endsWith('.lck'))
|
|
44
|
+
return true;
|
|
45
|
+
// Swap files (vim, etc.)
|
|
46
|
+
if (fileName.endsWith('.swp') || fileName.endsWith('.swo'))
|
|
47
|
+
return true;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
24
50
|
/** MIME type mapping for common document formats */
|
|
25
51
|
const MIME_MAP = {
|
|
26
52
|
// Text & Markdown
|
|
@@ -181,6 +207,10 @@ export class FilesystemScanner {
|
|
|
181
207
|
}
|
|
182
208
|
for (const entry of entries) {
|
|
183
209
|
const fullPath = resolve(dirPath, entry.name);
|
|
210
|
+
// Skip hidden and temporary files/directories
|
|
211
|
+
if (isTemporaryOrHiddenFile(entry.name)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
184
214
|
// Check exclusion patterns
|
|
185
215
|
if (this.isExcluded(fullPath)) {
|
|
186
216
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "liferewind",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|