liferewind 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/api/client.d.ts +31 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +115 -0
- package/dist/cli/commands/collect.d.ts +3 -0
- package/dist/cli/commands/collect.d.ts.map +1 -0
- package/dist/cli/commands/collect.js +62 -0
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +112 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +150 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +244 -0
- package/dist/cli/commands/start.d.ts +3 -0
- package/dist/cli/commands/start.d.ts.map +1 -0
- package/dist/cli/commands/start.js +59 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +49 -0
- package/dist/cli/detect/browsers.d.ts +3 -0
- package/dist/cli/detect/browsers.d.ts.map +1 -0
- package/dist/cli/detect/browsers.js +19 -0
- package/dist/cli/detect/chatbot.d.ts +3 -0
- package/dist/cli/detect/chatbot.d.ts.map +1 -0
- package/dist/cli/detect/chatbot.js +15 -0
- package/dist/cli/detect/git.d.ts +2 -0
- package/dist/cli/detect/git.d.ts.map +1 -0
- package/dist/cli/detect/git.js +10 -0
- package/dist/cli/detect/index.d.ts +4 -0
- package/dist/cli/detect/index.d.ts.map +1 -0
- package/dist/cli/detect/index.js +3 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +30 -0
- package/dist/cli/utils/output.d.ts +8 -0
- package/dist/cli/utils/output.d.ts.map +1 -0
- package/dist/cli/utils/output.js +28 -0
- package/dist/cli/utils/path.d.ts +9 -0
- package/dist/cli/utils/path.d.ts.map +1 -0
- package/dist/cli/utils/path.js +23 -0
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +64 -0
- package/dist/config/paths.d.ts +8 -0
- package/dist/config/paths.d.ts.map +1 -0
- package/dist/config/paths.js +21 -0
- package/dist/config/schema.d.ts +95 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +110 -0
- package/dist/config/writer.d.ts +3 -0
- package/dist/config/writer.d.ts.map +1 -0
- package/dist/config/writer.js +19 -0
- package/dist/core/collector.d.ts +19 -0
- package/dist/core/collector.d.ts.map +1 -0
- package/dist/core/collector.js +83 -0
- package/dist/core/scheduler.d.ts +12 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +48 -0
- package/dist/core/types.d.ts +29 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/sources/base.d.ts +27 -0
- package/dist/sources/base.d.ts.map +1 -0
- package/dist/sources/base.js +23 -0
- package/dist/sources/browser/index.d.ts +14 -0
- package/dist/sources/browser/index.d.ts.map +1 -0
- package/dist/sources/browser/index.js +116 -0
- package/dist/sources/browser/readers/base.d.ts +28 -0
- package/dist/sources/browser/readers/base.d.ts.map +1 -0
- package/dist/sources/browser/readers/base.js +110 -0
- package/dist/sources/browser/readers/chromium.d.ts +13 -0
- package/dist/sources/browser/readers/chromium.d.ts.map +1 -0
- package/dist/sources/browser/readers/chromium.js +64 -0
- package/dist/sources/browser/readers/safari.d.ts +9 -0
- package/dist/sources/browser/readers/safari.d.ts.map +1 -0
- package/dist/sources/browser/readers/safari.js +34 -0
- package/dist/sources/browser/types.d.ts +35 -0
- package/dist/sources/browser/types.d.ts.map +1 -0
- package/dist/sources/browser/types.js +1 -0
- package/dist/sources/chatbot/index.d.ts +12 -0
- package/dist/sources/chatbot/index.d.ts.map +1 -0
- package/dist/sources/chatbot/index.js +67 -0
- package/dist/sources/chatbot/readers/base.d.ts +25 -0
- package/dist/sources/chatbot/readers/base.d.ts.map +1 -0
- package/dist/sources/chatbot/readers/base.js +109 -0
- package/dist/sources/chatbot/readers/chatwise.d.ts +14 -0
- package/dist/sources/chatbot/readers/chatwise.d.ts.map +1 -0
- package/dist/sources/chatbot/readers/chatwise.js +117 -0
- package/dist/sources/chatbot/types.d.ts +33 -0
- package/dist/sources/chatbot/types.d.ts.map +1 -0
- package/dist/sources/chatbot/types.js +1 -0
- package/dist/sources/filesystem/index.d.ts +10 -0
- package/dist/sources/filesystem/index.d.ts.map +1 -0
- package/dist/sources/filesystem/index.js +58 -0
- package/dist/sources/filesystem/scanner.d.ts +24 -0
- package/dist/sources/filesystem/scanner.d.ts.map +1 -0
- package/dist/sources/filesystem/scanner.js +264 -0
- package/dist/sources/filesystem/types.d.ts +39 -0
- package/dist/sources/filesystem/types.d.ts.map +1 -0
- package/dist/sources/filesystem/types.js +1 -0
- package/dist/sources/git/index.d.ts +16 -0
- package/dist/sources/git/index.d.ts.map +1 -0
- package/dist/sources/git/index.js +169 -0
- package/dist/sources/git/types.d.ts +25 -0
- package/dist/sources/git/types.d.ts.map +1 -0
- package/dist/sources/git/types.js +1 -0
- package/dist/sources/index.d.ts +7 -0
- package/dist/sources/index.d.ts.map +1 -0
- package/dist/sources/index.js +16 -0
- package/dist/sources/registry.d.ts +13 -0
- package/dist/sources/registry.d.ts.map +1 -0
- package/dist/sources/registry.js +19 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +34 -0
- package/dist/utils/path.d.ts +9 -0
- package/dist/utils/path.d.ts.map +1 -0
- package/dist/utils/path.js +22 -0
- package/dist/utils/retry.d.ts +8 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +22 -0
- package/package.json +81 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Richard Wang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# liferewind
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/liferewind)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
AI-powered personal life review tool - collect your digital footprints from git, browser history, documents, and AI chatbots.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g liferewind
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Interactive configuration wizard
|
|
18
|
+
liferewind init
|
|
19
|
+
|
|
20
|
+
# Start collecting
|
|
21
|
+
liferewind start
|
|
22
|
+
|
|
23
|
+
# Manual collection
|
|
24
|
+
liferewind collect
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Data Sources
|
|
28
|
+
|
|
29
|
+
| Type | Status | Description |
|
|
30
|
+
|------|--------|-------------|
|
|
31
|
+
| Git | ✅ Implemented | Git commit history |
|
|
32
|
+
| Browser | ✅ Implemented | Browser history (Chrome, Safari, Arc, Dia, Comet) |
|
|
33
|
+
| Filesystem | ✅ Implemented | Document change tracking (.md, .txt, .docx, .pdf, etc.) |
|
|
34
|
+
| Chatbot | ✅ Implemented | Local AI chatbot history (ChatWise) |
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
Run `liferewind init` for interactive setup, or create config manually.
|
|
39
|
+
|
|
40
|
+
Config file lookup order:
|
|
41
|
+
1. `~/.liferewind/config.json` (primary user config)
|
|
42
|
+
2. `~/.config/liferewind/collector.json`
|
|
43
|
+
3. `~/.liferewind-collector.json`
|
|
44
|
+
4. `./collector.config.json`
|
|
45
|
+
|
|
46
|
+
Example configuration (see `collector.config.example.json`):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"api": {
|
|
51
|
+
"baseUrl": "http://localhost:3000",
|
|
52
|
+
"apiKey": "your-api-key"
|
|
53
|
+
},
|
|
54
|
+
"sources": {
|
|
55
|
+
"git": {
|
|
56
|
+
"enabled": true,
|
|
57
|
+
"schedule": "daily",
|
|
58
|
+
"options": {
|
|
59
|
+
"scanPaths": ["~/Documents", "~/Projects"],
|
|
60
|
+
"sinceDays": 30
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"browser": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"schedule": "daily",
|
|
66
|
+
"options": {
|
|
67
|
+
"browsers": ["chrome", "safari", "arc"],
|
|
68
|
+
"excludeDomains": ["localhost", "127.0.0.1"],
|
|
69
|
+
"sinceDays": 7
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"filesystem": {
|
|
73
|
+
"enabled": false,
|
|
74
|
+
"schedule": "daily",
|
|
75
|
+
"options": {
|
|
76
|
+
"watchPaths": ["~/Documents"],
|
|
77
|
+
"excludePatterns": ["**/node_modules/**", "**/.git/**"],
|
|
78
|
+
"fileTypes": [".md", ".txt", ".docx", ".pdf"],
|
|
79
|
+
"sinceDays": 7,
|
|
80
|
+
"includeContent": true
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"chatbot": {
|
|
84
|
+
"enabled": false,
|
|
85
|
+
"schedule": "daily",
|
|
86
|
+
"options": {
|
|
87
|
+
"clients": ["chatwise"],
|
|
88
|
+
"sinceDays": 30,
|
|
89
|
+
"includeContent": true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"logging": {
|
|
94
|
+
"level": "info"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Note:** Git repositories are automatically excluded from filesystem scanning.
|
|
100
|
+
|
|
101
|
+
Environment variables (fallback):
|
|
102
|
+
```bash
|
|
103
|
+
export LIFEREWIND_API_URL="http://localhost:3000"
|
|
104
|
+
export LIFEREWIND_API_KEY="your-api-key"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## CLI Commands
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Configuration
|
|
111
|
+
liferewind init # Interactive setup wizard
|
|
112
|
+
liferewind config show # Display current config
|
|
113
|
+
liferewind config edit # Open config in editor
|
|
114
|
+
liferewind config validate # Validate config file
|
|
115
|
+
liferewind config path # Show config file locations
|
|
116
|
+
|
|
117
|
+
# Collection
|
|
118
|
+
liferewind start # Start collector service
|
|
119
|
+
liferewind start --run-once # Collect once and exit
|
|
120
|
+
liferewind collect # Manual trigger (all sources)
|
|
121
|
+
liferewind collect git # Manual trigger (specific source)
|
|
122
|
+
|
|
123
|
+
# Diagnostics
|
|
124
|
+
liferewind status # Show service status
|
|
125
|
+
liferewind doctor # Check environment issues
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Development with watch
|
|
132
|
+
pnpm dev
|
|
133
|
+
|
|
134
|
+
# Build
|
|
135
|
+
pnpm build
|
|
136
|
+
|
|
137
|
+
# Production
|
|
138
|
+
pnpm start
|
|
139
|
+
|
|
140
|
+
# PM2 daemon
|
|
141
|
+
pnpm pm2:start
|
|
142
|
+
pnpm pm2:stop
|
|
143
|
+
pnpm pm2:logs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Schedule Frequencies
|
|
147
|
+
|
|
148
|
+
| Value | Cron | Description |
|
|
149
|
+
|-------|------|-------------|
|
|
150
|
+
| `hourly` | `0 * * * *` | Every hour |
|
|
151
|
+
| `daily` | `0 9 * * *` | Daily at 9:00 |
|
|
152
|
+
| `weekly` | `0 9 * * 1` | Monday at 9:00 |
|
|
153
|
+
| `monthly` | `0 9 1 * *` | 1st of month at 9:00 |
|
|
154
|
+
| `manual` | - | Manual trigger only |
|
|
155
|
+
|
|
156
|
+
## Adding a Data Source
|
|
157
|
+
|
|
158
|
+
1. Create a new directory under `src/sources/`
|
|
159
|
+
2. Extend the `DataSource<TOptions>` base class
|
|
160
|
+
3. Register in `src/sources/index.ts`
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// src/sources/my-source/index.ts
|
|
164
|
+
import { DataSource } from '../base.js';
|
|
165
|
+
|
|
166
|
+
interface MySourceOptions {
|
|
167
|
+
// ...
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export class MySource extends DataSource<MySourceOptions> {
|
|
171
|
+
readonly type = 'my-source' as const;
|
|
172
|
+
readonly name = 'My Source';
|
|
173
|
+
|
|
174
|
+
async validate(): Promise<boolean> {
|
|
175
|
+
// Validate environment requirements
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async collect(): Promise<CollectionResult> {
|
|
179
|
+
// Collect data
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/sources/index.ts
|
|
184
|
+
sourceRegistry.register('my-source', (config, ctx) => new MySource(config, ctx));
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Project Structure
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
src/
|
|
191
|
+
├── index.ts # Library exports
|
|
192
|
+
├── cli/ # CLI commands
|
|
193
|
+
│ ├── index.ts # CLI entry point
|
|
194
|
+
│ ├── commands/ # Command implementations
|
|
195
|
+
│ ├── detect/ # Environment detection
|
|
196
|
+
│ └── utils/ # CLI utilities
|
|
197
|
+
├── core/
|
|
198
|
+
│ ├── types.ts # Type definitions
|
|
199
|
+
│ ├── collector.ts # Main orchestrator
|
|
200
|
+
│ └── scheduler.ts # Cron scheduler
|
|
201
|
+
├── sources/
|
|
202
|
+
│ ├── base.ts # DataSource base class
|
|
203
|
+
│ ├── registry.ts # Source registry
|
|
204
|
+
│ ├── index.ts # Source registration
|
|
205
|
+
│ ├── git/ # Git data source
|
|
206
|
+
│ ├── browser/ # Browser history (multi-browser)
|
|
207
|
+
│ ├── filesystem/ # Filesystem changes
|
|
208
|
+
│ └── chatbot/ # Chatbot history (multi-client)
|
|
209
|
+
├── api/
|
|
210
|
+
│ └── client.ts # API client
|
|
211
|
+
├── config/
|
|
212
|
+
│ ├── schema.ts # Config schema (Zod)
|
|
213
|
+
│ ├── loader.ts # Config loader
|
|
214
|
+
│ ├── writer.ts # Config writer
|
|
215
|
+
│ └── paths.ts # Config path constants
|
|
216
|
+
└── utils/
|
|
217
|
+
├── logger.ts # Logger
|
|
218
|
+
└── retry.ts # Retry with backoff
|
|
219
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { CollectionResult } from '../core/types.js';
|
|
3
|
+
import type { Logger } from '../utils/logger.js';
|
|
4
|
+
export interface ApiClientConfig {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
timeout: number;
|
|
8
|
+
retryAttempts: number;
|
|
9
|
+
}
|
|
10
|
+
declare const pushResponseSchema: z.ZodObject<{
|
|
11
|
+
success: z.ZodLiteral<true>;
|
|
12
|
+
data: z.ZodObject<{
|
|
13
|
+
itemsReceived: z.ZodNumber;
|
|
14
|
+
itemsInserted: z.ZodNumber;
|
|
15
|
+
requestId: z.ZodString;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type PushResponse = z.infer<typeof pushResponseSchema>;
|
|
19
|
+
export declare class ApiClient {
|
|
20
|
+
private static readonly BATCH_SIZE;
|
|
21
|
+
private config;
|
|
22
|
+
private logger;
|
|
23
|
+
constructor(config: ApiClientConfig, logger: Logger);
|
|
24
|
+
pushData(result: CollectionResult): Promise<PushResponse>;
|
|
25
|
+
healthCheck(): Promise<boolean>;
|
|
26
|
+
private pushInBatches;
|
|
27
|
+
private pushBatch;
|
|
28
|
+
private splitIntoBatches;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAGjD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,QAAA,MAAM,kBAAkB;;;;;;;iBAOtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAO9D,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAQ;IAE1C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM;IAK7C,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IAQzD,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;YAWvB,aAAa;YA4Db,SAAS;IAuCvB,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { retry } from '../utils/retry.js';
|
|
3
|
+
const pushResponseSchema = z.object({
|
|
4
|
+
success: z.literal(true),
|
|
5
|
+
data: z.object({
|
|
6
|
+
itemsReceived: z.number(),
|
|
7
|
+
itemsInserted: z.number(),
|
|
8
|
+
requestId: z.string(),
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
export class ApiClient {
|
|
12
|
+
static BATCH_SIZE = 1000;
|
|
13
|
+
config;
|
|
14
|
+
logger;
|
|
15
|
+
constructor(config, logger) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
}
|
|
19
|
+
async pushData(result) {
|
|
20
|
+
if (result.items.length <= ApiClient.BATCH_SIZE) {
|
|
21
|
+
return this.pushBatch(result);
|
|
22
|
+
}
|
|
23
|
+
return this.pushInBatches(result);
|
|
24
|
+
}
|
|
25
|
+
async healthCheck() {
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${this.config.baseUrl}/api/health`, {
|
|
28
|
+
signal: AbortSignal.timeout(5000),
|
|
29
|
+
});
|
|
30
|
+
return response.ok;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async pushInBatches(result) {
|
|
37
|
+
const batches = this.splitIntoBatches(result.items, ApiClient.BATCH_SIZE);
|
|
38
|
+
this.logger.info(`Splitting ${result.items.length} items into ${batches.length} batches (${ApiClient.BATCH_SIZE} items/batch)`);
|
|
39
|
+
const batchResults = [];
|
|
40
|
+
for (const [index, batch] of batches.entries()) {
|
|
41
|
+
const batchNumber = index + 1;
|
|
42
|
+
this.logger.info(`Pushing batch ${batchNumber}/${batches.length} (${batch.length} items)`);
|
|
43
|
+
try {
|
|
44
|
+
const response = await this.pushBatch({
|
|
45
|
+
...result,
|
|
46
|
+
items: batch,
|
|
47
|
+
itemsCollected: batch.length,
|
|
48
|
+
});
|
|
49
|
+
batchResults.push({
|
|
50
|
+
requestId: response.data.requestId,
|
|
51
|
+
itemsInserted: response.data.itemsInserted,
|
|
52
|
+
});
|
|
53
|
+
this.logger.info(`Batch ${batchNumber}/${batches.length} completed: ` +
|
|
54
|
+
`requestId=${response.data.requestId}, inserted=${response.data.itemsInserted}/${batch.length}`);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const successfulBatches = batchResults.length;
|
|
58
|
+
const totalInserted = batchResults.reduce((sum, r) => sum + r.itemsInserted, 0);
|
|
59
|
+
this.logger.error(`Batch ${batchNumber}/${batches.length} failed. ` +
|
|
60
|
+
`${successfulBatches} batches succeeded (${totalInserted} items inserted).`, error);
|
|
61
|
+
throw new Error(`Batch ${batchNumber}/${batches.length} failed after ${successfulBatches} successful batches ` +
|
|
62
|
+
`(${totalInserted} items inserted). ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const totalInserted = batchResults.reduce((sum, r) => sum + r.itemsInserted, 0);
|
|
66
|
+
const lastRequestId = batchResults[batchResults.length - 1]?.requestId ?? 'unknown';
|
|
67
|
+
return {
|
|
68
|
+
success: true,
|
|
69
|
+
data: {
|
|
70
|
+
itemsReceived: result.items.length,
|
|
71
|
+
itemsInserted: totalInserted,
|
|
72
|
+
requestId: lastRequestId,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async pushBatch(result) {
|
|
77
|
+
const url = `${this.config.baseUrl}/api/collector/ingest`;
|
|
78
|
+
return retry(async () => {
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
84
|
+
'X-Source-Type': result.sourceType,
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
sourceType: result.sourceType,
|
|
88
|
+
collectedAt: result.collectedAt.toISOString(),
|
|
89
|
+
items: result.items,
|
|
90
|
+
}),
|
|
91
|
+
signal: AbortSignal.timeout(this.config.timeout),
|
|
92
|
+
});
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const errorText = await response.text();
|
|
95
|
+
throw new Error(`API error ${response.status}: ${errorText}`);
|
|
96
|
+
}
|
|
97
|
+
const json = await response.json();
|
|
98
|
+
return pushResponseSchema.parse(json);
|
|
99
|
+
}, {
|
|
100
|
+
attempts: this.config.retryAttempts,
|
|
101
|
+
delay: 1000,
|
|
102
|
+
backoff: 2,
|
|
103
|
+
onRetry: (error, attempt) => {
|
|
104
|
+
this.logger.warn(`API push retry ${attempt}/${this.config.retryAttempts}`, error);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
splitIntoBatches(items, batchSize) {
|
|
109
|
+
const batches = [];
|
|
110
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
111
|
+
batches.push(items.slice(i, i + batchSize));
|
|
112
|
+
}
|
|
113
|
+
return batches;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { Collector } from '../../core/collector.js';
|
|
4
|
+
import { loadConfig } from '../../config/loader.js';
|
|
5
|
+
import { createLogger } from '../../utils/logger.js';
|
|
6
|
+
import { registerBuiltinSources } from '../../sources/index.js';
|
|
7
|
+
import { printError, printSuccess } from '../utils/output.js';
|
|
8
|
+
import { SOURCE_TYPES } from '../../core/types.js';
|
|
9
|
+
export const collectCommand = new Command('collect')
|
|
10
|
+
.description('Manually trigger data collection')
|
|
11
|
+
.argument('[source]', 'specific source to collect (git, browser, filesystem, chatbot)')
|
|
12
|
+
.action(async (source, _options, cmd) => {
|
|
13
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
14
|
+
// Validate source argument
|
|
15
|
+
if (source && !SOURCE_TYPES.includes(source)) {
|
|
16
|
+
printError(`Invalid source: ${source}`);
|
|
17
|
+
console.log(`Valid sources: ${SOURCE_TYPES.join(', ')}`);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const config = loadConfig(globalOpts.config);
|
|
22
|
+
// Apply global log level overrides
|
|
23
|
+
let logLevel = config.logging.level;
|
|
24
|
+
if (globalOpts.verbose)
|
|
25
|
+
logLevel = 'debug';
|
|
26
|
+
if (globalOpts.quiet)
|
|
27
|
+
logLevel = 'error';
|
|
28
|
+
const logger = createLogger({ level: logLevel });
|
|
29
|
+
registerBuiltinSources();
|
|
30
|
+
const collector = new Collector(config, logger);
|
|
31
|
+
await collector.validateSources();
|
|
32
|
+
const enabledSources = collector.getEnabledSources();
|
|
33
|
+
if (enabledSources.length === 0) {
|
|
34
|
+
printError('No data sources enabled or validated.');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const spinner = ora();
|
|
38
|
+
if (source) {
|
|
39
|
+
// Collect specific source
|
|
40
|
+
if (!enabledSources.includes(source)) {
|
|
41
|
+
printError(`Source '${source}' is not enabled or not validated.`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
spinner.start(`Collecting from ${source}...`);
|
|
45
|
+
await collector.triggerCollection(source);
|
|
46
|
+
spinner.succeed(`Collection from ${source} complete`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Collect all enabled sources
|
|
50
|
+
for (const src of enabledSources) {
|
|
51
|
+
spinner.start(`Collecting from ${src}...`);
|
|
52
|
+
await collector.triggerCollection(src);
|
|
53
|
+
spinner.succeed(`Collection from ${src} complete`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
printSuccess('All collections complete.');
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
printError(`Collection failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig, findConfigPath } from '../../config/loader.js';
|
|
6
|
+
import { getUserConfigPath, getAllConfigPaths } from '../../config/paths.js';
|
|
7
|
+
import { printError, printSuccess, printInfo, printDim } from '../utils/output.js';
|
|
8
|
+
export const configCommand = new Command('config').description('Manage configuration');
|
|
9
|
+
function printConfigSummary(config, revealSecrets = false) {
|
|
10
|
+
console.log(pc.bold('\nAPI Configuration'));
|
|
11
|
+
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}`);
|
|
18
|
+
console.log(` Timeout: ${config.api.timeout}ms`);
|
|
19
|
+
console.log(` Retries: ${config.api.retryAttempts}`);
|
|
20
|
+
console.log(pc.bold('\nData Sources'));
|
|
21
|
+
const sources = ['git', 'browser', 'filesystem', 'chatbot'];
|
|
22
|
+
for (const name of sources) {
|
|
23
|
+
const src = config.sources[name];
|
|
24
|
+
const status = src.enabled ? pc.green('enabled') : pc.dim('disabled');
|
|
25
|
+
const schedule = src.enabled ? ` (${src.schedule})` : '';
|
|
26
|
+
console.log(` ${name}: ${status}${schedule}`);
|
|
27
|
+
}
|
|
28
|
+
console.log(pc.bold('\nLogging'));
|
|
29
|
+
console.log(` Level: ${config.logging.level}`);
|
|
30
|
+
}
|
|
31
|
+
configCommand
|
|
32
|
+
.command('show')
|
|
33
|
+
.description('Display current configuration')
|
|
34
|
+
.option('--json', 'output as JSON')
|
|
35
|
+
.option('--reveal-secrets', 'show API keys (hidden by default)')
|
|
36
|
+
.action((options, cmd) => {
|
|
37
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
38
|
+
try {
|
|
39
|
+
const config = loadConfig(globalOpts.config);
|
|
40
|
+
const configPath = findConfigPath(globalOpts.config);
|
|
41
|
+
if (configPath) {
|
|
42
|
+
printDim(`Loaded from: ${configPath}`);
|
|
43
|
+
}
|
|
44
|
+
// Helper to mask API key
|
|
45
|
+
const maskApiKey = (key) => key.length > 12 ? key.slice(0, 8) + '...' + key.slice(-4) : '***';
|
|
46
|
+
if (options.json) {
|
|
47
|
+
const output = {
|
|
48
|
+
...config,
|
|
49
|
+
api: {
|
|
50
|
+
...config.api,
|
|
51
|
+
apiKey: options.revealSecrets ? config.api.apiKey : maskApiKey(config.api.apiKey),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
console.log(JSON.stringify(output, null, 2));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
printConfigSummary(config, options.revealSecrets);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
printError(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
configCommand
|
|
66
|
+
.command('path')
|
|
67
|
+
.description('Show configuration file paths')
|
|
68
|
+
.action(() => {
|
|
69
|
+
const paths = getAllConfigPaths();
|
|
70
|
+
console.log('Configuration file search order:');
|
|
71
|
+
paths.forEach((p, i) => {
|
|
72
|
+
const exists = existsSync(p);
|
|
73
|
+
const status = exists ? '(exists)' : '';
|
|
74
|
+
console.log(` ${i + 1}. ${p} ${status}`);
|
|
75
|
+
});
|
|
76
|
+
console.log(`\nPrimary user config: ${getUserConfigPath()}`);
|
|
77
|
+
});
|
|
78
|
+
configCommand
|
|
79
|
+
.command('edit')
|
|
80
|
+
.description('Open configuration in default editor')
|
|
81
|
+
.action((_options, cmd) => {
|
|
82
|
+
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');
|
|
90
|
+
try {
|
|
91
|
+
execSync(`${editor} "${configPath}"`, { stdio: 'inherit' });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
printError(`Failed to open editor: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
configCommand
|
|
99
|
+
.command('validate')
|
|
100
|
+
.description('Validate configuration file')
|
|
101
|
+
.action((_options, cmd) => {
|
|
102
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
103
|
+
try {
|
|
104
|
+
loadConfig(globalOpts.config);
|
|
105
|
+
const configPath = findConfigPath(globalOpts.config);
|
|
106
|
+
printSuccess(`Configuration is valid: ${configPath}`);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
printError(`Configuration invalid: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
@@ -0,0 +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"}
|