@aperdomoll90/ledger-ai 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +177 -221
- package/dist/commands/add.js +51 -100
- package/dist/commands/backfill.js +55 -0
- package/dist/commands/backup.js +10 -10
- package/dist/commands/check.js +21 -29
- package/dist/commands/config.js +13 -12
- package/dist/commands/delete.js +22 -17
- package/dist/commands/eval-judge.js +11 -0
- package/dist/commands/eval.js +321 -0
- package/dist/commands/export.js +8 -10
- package/dist/commands/get.js +9 -0
- package/dist/commands/hunt.js +206 -0
- package/dist/commands/ingest.js +15 -14
- package/dist/commands/init.js +18 -20
- package/dist/commands/list.js +21 -7
- package/dist/commands/migrate.js +11 -11
- package/dist/commands/onboard.js +2 -2
- package/dist/commands/pull.js +3 -2
- package/dist/commands/push.js +8 -8
- package/dist/commands/restore.js +38 -38
- package/dist/commands/show.js +13 -16
- package/dist/commands/sync.js +58 -19
- package/dist/commands/tag.js +20 -14
- package/dist/commands/update.js +50 -18
- package/dist/commands/wizard.js +3 -3
- package/dist/lib/ai-search.js +163 -0
- package/dist/lib/audit.js +19 -0
- package/dist/lib/backfill.js +60 -0
- package/dist/lib/config.js +19 -2
- package/dist/lib/document-classification.js +5 -0
- package/dist/lib/document-fetching.js +77 -0
- package/dist/lib/document-operations.js +150 -0
- package/dist/lib/documents/classification.js +5 -0
- package/dist/lib/documents/fetching.js +89 -0
- package/dist/lib/documents/operations.js +304 -0
- package/dist/lib/domains.js +116 -0
- package/dist/lib/embeddings.js +190 -0
- package/dist/lib/errors.js +3 -1
- package/dist/lib/eval/eval-advanced.js +289 -0
- package/dist/lib/eval/eval-judge-session.js +233 -0
- package/dist/lib/eval/eval-store.js +105 -0
- package/dist/lib/eval/eval.js +303 -0
- package/dist/lib/file-writer.js +23 -0
- package/dist/lib/generators.js +44 -45
- package/dist/lib/hunter-db.js +235 -0
- package/dist/lib/hunter-rss.js +30 -0
- package/dist/lib/hunter-scoring.js +55 -0
- package/dist/lib/hunter-types.js +36 -0
- package/dist/lib/lint-configs.js +20 -0
- package/dist/lib/migrate.js +2 -2
- package/dist/lib/notes.js +173 -59
- package/dist/lib/observability.js +296 -0
- package/dist/lib/op-add-note-types.test.js +7 -6
- package/dist/lib/prompt.js +8 -8
- package/dist/lib/rate-limiter.js +103 -0
- package/dist/lib/search/ai-search.js +396 -0
- package/dist/lib/search/chunk-context-enrichment.js +155 -0
- package/dist/lib/search/embeddings.js +293 -0
- package/dist/lib/search/reranker.js +120 -0
- package/dist/lib/search/semantic-cache.js +53 -0
- package/dist/lib/type-registry.test.js +6 -6
- package/dist/mcp-server.js +553 -66
- package/dist/migrations/migrations/005-audit-log.sql +22 -0
- package/dist/migrations/migrations/005_opportunities.sql +48 -0
- package/dist/migrations/migrations/006-audited-operations.sql +235 -0
- package/dist/migrations/migrations/006_hunt_analytics.sql +38 -0
- package/dist/migrations/migrations/007-eval-golden-judgments.sql +119 -0
- package/dist/migrations/migrations/008-drop-expected-doc-ids.sql +9 -0
- package/dist/migrations/migrations/008-judge-helpers.sql +21 -0
- package/dist/migrations/migrations/009-semantic-cache.sql +216 -0
- package/dist/scripts/batch-grade.js +344 -0
- package/dist/scripts/benchmark-ingestion.js +376 -0
- package/dist/scripts/convert-judgments-to-graded.js +88 -0
- package/dist/scripts/diagnose-first-result.js +333 -0
- package/dist/scripts/drop-golden-query.js +53 -0
- package/dist/scripts/eval-search.js +115 -0
- package/dist/scripts/grade-unjudged-top1.js +138 -0
- package/dist/scripts/hunter-analytics.js +38 -0
- package/dist/scripts/hunter-cron.js +63 -0
- package/dist/scripts/hunter-purge.js +25 -0
- package/dist/scripts/migrate-v2.js +140 -0
- package/dist/scripts/reindex.js +74 -0
- package/dist/scripts/sync-local-docs.js +153 -0
- package/package.json +7 -1
package/dist/cli.js
CHANGED
|
@@ -1,110 +1,209 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// cli.ts — Ledger CLI entry point.
|
|
3
|
+
// 14 commands for managing the RAG knowledge base.
|
|
2
4
|
import { Command } from 'commander';
|
|
3
5
|
import { createRequire } from 'module';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
4
7
|
import { loadConfig } from './lib/config.js';
|
|
8
|
+
import { shutdownObservability } from './lib/observability.js';
|
|
9
|
+
// Per-invocation session ID so all searches from this CLI run group together
|
|
10
|
+
// in the Langfuse dashboard.
|
|
11
|
+
const CLI_SESSION_ID = `cli-${randomUUID()}`;
|
|
12
|
+
const CLI_ENVIRONMENT = process.env.NODE_ENV ?? 'development';
|
|
13
|
+
const CLI_CONTEXT = { sessionId: CLI_SESSION_ID, observabilityEnvironment: CLI_ENVIRONMENT };
|
|
5
14
|
const require = createRequire(import.meta.url);
|
|
6
15
|
const { version } = require('../package.json');
|
|
7
|
-
|
|
8
|
-
import { push } from './commands/push.js';
|
|
9
|
-
import { check, checkChunks } from './commands/check.js';
|
|
10
|
-
import { sync } from './commands/sync.js';
|
|
11
|
-
import { show } from './commands/show.js';
|
|
12
|
-
import { exportNote } from './commands/export.js';
|
|
13
|
-
import { ingest } from './commands/ingest.js';
|
|
16
|
+
// Commands
|
|
14
17
|
import { init } from './commands/init.js';
|
|
15
|
-
import {
|
|
16
|
-
import { setupClaudeCode, setupOpenclaw, setupChatgpt } from './commands/setup.js';
|
|
17
|
-
import { backup, enableBackupCron, disableBackupCron } from './commands/backup.js';
|
|
18
|
-
import { restore } from './commands/restore.js';
|
|
19
|
-
import { onboard } from './commands/onboard.js';
|
|
20
|
-
import { configGet, configSet, configUnset, configList } from './commands/config.js';
|
|
21
|
-
import { migrate } from './commands/migrate.js';
|
|
22
|
-
import { add } from './commands/add.js';
|
|
23
|
-
import { update } from './commands/update.js';
|
|
24
|
-
import { deleteNote } from './commands/delete.js';
|
|
18
|
+
import { addDocumentFromFile } from './commands/add.js';
|
|
25
19
|
import { list } from './commands/list.js';
|
|
20
|
+
import { show } from './commands/show.js';
|
|
21
|
+
import { get } from './commands/get.js';
|
|
22
|
+
import { exportDocument } from './commands/export.js';
|
|
23
|
+
import { push } from './commands/push.js';
|
|
24
|
+
import { updateFromFile } from './commands/update.js';
|
|
25
|
+
import { removeDocument } from './commands/delete.js';
|
|
26
26
|
import { tag } from './commands/tag.js';
|
|
27
|
+
import { check } from './commands/check.js';
|
|
28
|
+
import { backup, enableBackupCron, disableBackupCron } from './commands/backup.js';
|
|
29
|
+
import { restore } from './commands/restore.js';
|
|
27
30
|
import { lint } from './commands/lint.js';
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
import { evalSearch, sweepThreshold, showEvalRun } from './commands/eval.js';
|
|
32
|
+
import { evalJudge } from './commands/eval-judge.js';
|
|
33
|
+
process.on('unhandledRejection', (rejection) => {
|
|
34
|
+
console.error(rejection instanceof Error ? rejection.message : String(rejection));
|
|
30
35
|
process.exit(1);
|
|
31
36
|
});
|
|
37
|
+
// Flush pending Langfuse traces before exit
|
|
38
|
+
const shutdown = async () => {
|
|
39
|
+
await shutdownObservability();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
};
|
|
42
|
+
process.on('SIGINT', shutdown);
|
|
43
|
+
process.on('SIGTERM', shutdown);
|
|
32
44
|
const program = new Command();
|
|
33
45
|
program
|
|
34
46
|
.name('ledger')
|
|
35
|
-
.description('AI identity and memory system —
|
|
47
|
+
.description('AI identity and memory system — RAG-powered knowledge base for agents')
|
|
36
48
|
.version(version);
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Document management
|
|
51
|
+
// =============================================================================
|
|
37
52
|
program
|
|
38
|
-
.command('
|
|
39
|
-
.description('
|
|
40
|
-
.
|
|
41
|
-
.
|
|
53
|
+
.command('add')
|
|
54
|
+
.description('Add a new document to Ledger by reading content from a file on disk (auto-verified after create)')
|
|
55
|
+
.requiredOption('-f, --file <file>', 'absolute or relative path to the file containing the document content')
|
|
56
|
+
.requiredOption('-n, --name <name>', 'unique document name (lowercase, hyphens)')
|
|
57
|
+
.option('-d, --domain <domain>', 'domain: system, persona, workspace, project, general', 'general')
|
|
58
|
+
.option('-t, --type <type>', 'document type (architecture, reference, knowledge, etc.)', 'knowledge')
|
|
59
|
+
.option('-p, --project <project>', 'project name')
|
|
60
|
+
.option('--description <text>', 'one-line description')
|
|
61
|
+
.option('-a, --agent <agent>', 'agent name', 'cli')
|
|
62
|
+
.option('-s, --status <status>', 'status: idea, planning, active, done')
|
|
63
|
+
.option('--protection <level>', 'protection: open, guarded, protected, immutable')
|
|
42
64
|
.action(async (options) => {
|
|
43
|
-
const config = loadConfig();
|
|
44
|
-
await
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
.description('Compare local files vs Ledger, report sync status')
|
|
56
|
-
.option('--chunks', 'Check chunk group integrity')
|
|
57
|
-
.action(async (opts) => {
|
|
58
|
-
const config = loadConfig();
|
|
59
|
-
if (opts.chunks) {
|
|
60
|
-
await checkChunks(config);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
await check(config);
|
|
64
|
-
}
|
|
65
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
66
|
+
await addDocumentFromFile(config, {
|
|
67
|
+
filePath: options.file,
|
|
68
|
+
name: options.name,
|
|
69
|
+
domain: options.domain,
|
|
70
|
+
documentType: options.type,
|
|
71
|
+
project: options.project,
|
|
72
|
+
description: options.description,
|
|
73
|
+
agent: options.agent,
|
|
74
|
+
status: options.status,
|
|
75
|
+
protection: options.protection,
|
|
76
|
+
});
|
|
65
77
|
});
|
|
66
78
|
program
|
|
67
|
-
.command('
|
|
68
|
-
.description('
|
|
69
|
-
.option('-
|
|
70
|
-
.option('-
|
|
71
|
-
.option('-
|
|
79
|
+
.command('list')
|
|
80
|
+
.description('List recent documents from Ledger')
|
|
81
|
+
.option('-n, --limit <number>', 'number of documents', '20')
|
|
82
|
+
.option('-t, --type <type>', 'filter by document type')
|
|
83
|
+
.option('-p, --project <project>', 'filter by project name')
|
|
72
84
|
.action(async (options) => {
|
|
73
|
-
const config = loadConfig();
|
|
74
|
-
await
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
86
|
+
await list(config, {
|
|
87
|
+
limit: parseInt(options.limit, 10),
|
|
88
|
+
type: options.type,
|
|
89
|
+
project: options.project,
|
|
78
90
|
});
|
|
79
91
|
});
|
|
92
|
+
program
|
|
93
|
+
.command('get <name>')
|
|
94
|
+
.description('Fetch a document by exact name and print its content')
|
|
95
|
+
.action(async (name) => {
|
|
96
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
97
|
+
await get(config, name);
|
|
98
|
+
});
|
|
80
99
|
program
|
|
81
100
|
.command('show <query...>')
|
|
82
|
-
.description('Search Ledger by meaning, open matching
|
|
83
|
-
.option('-t, --type <type>', 'filter by
|
|
101
|
+
.description('Search Ledger by meaning, open matching document')
|
|
102
|
+
.option('-t, --type <type>', 'filter by document type')
|
|
84
103
|
.option('-p, --project <project>', 'filter by project name')
|
|
85
104
|
.action(async (queryParts, options) => {
|
|
86
|
-
const config = loadConfig();
|
|
105
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
87
106
|
await show(config, queryParts.join(' '), { type: options.type, project: options.project });
|
|
88
107
|
});
|
|
89
108
|
program
|
|
90
109
|
.command('export <query...>')
|
|
91
|
-
.description('Download a
|
|
110
|
+
.description('Download a document to a file')
|
|
92
111
|
.option('-o, --output <path>', 'output directory (default: current directory)')
|
|
93
112
|
.action(async (queryParts, options) => {
|
|
94
|
-
const config = loadConfig();
|
|
95
|
-
await
|
|
113
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
114
|
+
await exportDocument(config, queryParts.join(' '), options.output);
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command('push <file>')
|
|
118
|
+
.description('Upload a local file to Ledger')
|
|
119
|
+
.action(async (file) => {
|
|
120
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
121
|
+
await push(config, file);
|
|
122
|
+
});
|
|
123
|
+
program
|
|
124
|
+
.command('update <id>')
|
|
125
|
+
.description('Update a document by ID by reading new content from a file on disk (auto-verified after push)')
|
|
126
|
+
.requiredOption('-f, --file <file>', 'absolute or relative path to the file containing the new content')
|
|
127
|
+
.option('-y, --yes', 'skip the interactive confirmation prompt (for non-interactive scripts)')
|
|
128
|
+
.action(async (documentId, options) => {
|
|
129
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
130
|
+
await updateFromFile(config, parseInt(documentId, 10), options.file, { yes: options.yes });
|
|
131
|
+
});
|
|
132
|
+
program
|
|
133
|
+
.command('delete <id>')
|
|
134
|
+
.description('Soft-delete a document by ID (restorable within 30 days)')
|
|
135
|
+
.option('-y, --yes', 'skip the interactive confirmation prompt (for non-interactive scripts)')
|
|
136
|
+
.action(async (documentId, options) => {
|
|
137
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
138
|
+
await removeDocument(config, parseInt(documentId, 10), { yes: options.yes });
|
|
139
|
+
});
|
|
140
|
+
program
|
|
141
|
+
.command('tag <id>')
|
|
142
|
+
.description('Update metadata on a document (description, project, domain)')
|
|
143
|
+
.option('-d, --description <text>', 'document description')
|
|
144
|
+
.option('-p, --project <name>', 'project name')
|
|
145
|
+
.option('--domain <domain>', 'domain: system, persona, workspace, project, general')
|
|
146
|
+
.action(async (documentId, options) => {
|
|
147
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
148
|
+
await tag(config, parseInt(documentId, 10), {
|
|
149
|
+
description: options.description,
|
|
150
|
+
project: options.project,
|
|
151
|
+
domain: options.domain,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Search quality
|
|
156
|
+
// =============================================================================
|
|
157
|
+
program
|
|
158
|
+
.command('eval')
|
|
159
|
+
.description('Run search quality evaluation against golden dataset')
|
|
160
|
+
.option('--dry-run', 'print report without saving to database')
|
|
161
|
+
.action(async (options) => {
|
|
162
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
163
|
+
await evalSearch(config, { dryRun: options.dryRun ?? false });
|
|
164
|
+
});
|
|
165
|
+
program
|
|
166
|
+
.command('eval:sweep')
|
|
167
|
+
.description('Test multiple similarity thresholds to find optimal value')
|
|
168
|
+
.option('--thresholds <values>', 'comma-separated thresholds to test', '0.15,0.20,0.25,0.30,0.35,0.40')
|
|
169
|
+
.action(async (options) => {
|
|
170
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
171
|
+
await sweepThreshold(config, { thresholds: options.thresholds });
|
|
172
|
+
});
|
|
173
|
+
program
|
|
174
|
+
.command('eval:show <runId>')
|
|
175
|
+
.description('Inspect a saved eval run — summary metrics and missed queries with doc names')
|
|
176
|
+
.option('--full', 'also dump per-query results as JSON', false)
|
|
177
|
+
.action(async (runIdArg, options) => {
|
|
178
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
179
|
+
const runId = parseInt(runIdArg, 10);
|
|
180
|
+
if (isNaN(runId)) {
|
|
181
|
+
process.stderr.write(`Invalid run id: ${runIdArg}\n`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
await showEvalRun(config, runId, { full: options.full ?? false });
|
|
185
|
+
});
|
|
186
|
+
program
|
|
187
|
+
.command('eval:judge')
|
|
188
|
+
.description('Resumable rejudging walkthrough — grade top-10 search results per query using TREC 4-level scale')
|
|
189
|
+
.option('--query <id>', 'start at a specific golden query id', (value) => parseInt(value, 10))
|
|
190
|
+
.action(async (options) => {
|
|
191
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
192
|
+
await evalJudge(config, { query: options.query });
|
|
96
193
|
});
|
|
194
|
+
// =============================================================================
|
|
195
|
+
// Sync and maintenance
|
|
196
|
+
// =============================================================================
|
|
97
197
|
program
|
|
98
|
-
.command('
|
|
99
|
-
.description('
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await ingest(config, { file, auto: options.auto ?? false });
|
|
198
|
+
.command('check')
|
|
199
|
+
.description('Compare local files vs Ledger, report sync status')
|
|
200
|
+
.action(async () => {
|
|
201
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
202
|
+
await check(config);
|
|
104
203
|
});
|
|
105
204
|
program
|
|
106
205
|
.command('backup')
|
|
107
|
-
.description('Backup all
|
|
206
|
+
.description('Backup all documents to ~/.ledger/backups/')
|
|
108
207
|
.option('-q, --quiet', 'suppress output unless error')
|
|
109
208
|
.option('--enable-cron', 'enable daily backup at 1am')
|
|
110
209
|
.option('--disable-cron', 'disable daily backup cron')
|
|
@@ -117,167 +216,24 @@ program
|
|
|
117
216
|
disableBackupCron();
|
|
118
217
|
return;
|
|
119
218
|
}
|
|
120
|
-
const config = loadConfig();
|
|
219
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
121
220
|
await backup(config, { quiet: options.quiet ?? false });
|
|
122
221
|
});
|
|
123
222
|
program
|
|
124
223
|
.command('restore <file>')
|
|
125
|
-
.description('Restore
|
|
224
|
+
.description('Restore documents from a backup JSON file')
|
|
126
225
|
.action(async (file) => {
|
|
127
|
-
const config = loadConfig();
|
|
226
|
+
const config = loadConfig(CLI_CONTEXT);
|
|
128
227
|
await restore(config, file);
|
|
129
228
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
configCmd
|
|
134
|
-
.command('get <key>')
|
|
135
|
-
.description('Get a config value (or "all" for full config)')
|
|
136
|
-
.action(async (key) => {
|
|
137
|
-
await configGet(key);
|
|
138
|
-
});
|
|
139
|
-
configCmd
|
|
140
|
-
.command('set <key> <value>')
|
|
141
|
-
.description('Set a config value')
|
|
142
|
-
.action(async (key, value) => {
|
|
143
|
-
if (key.startsWith('types.')) {
|
|
144
|
-
const config = loadConfig();
|
|
145
|
-
await configSet(key, value, { supabase: config.supabase, openai: config.openai });
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
await configSet(key, value);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
configCmd
|
|
152
|
-
.command('unset <key>')
|
|
153
|
-
.description('Remove a config override (types.* keys only)')
|
|
154
|
-
.action(async (key) => {
|
|
155
|
-
if (key.startsWith('types.')) {
|
|
156
|
-
const config = loadConfig();
|
|
157
|
-
await configUnset(key, { supabase: config.supabase, openai: config.openai });
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
await configUnset(key);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
configCmd
|
|
164
|
-
.command('list')
|
|
165
|
-
.description('Show all settings')
|
|
166
|
-
.action(async () => {
|
|
167
|
-
await configList();
|
|
168
|
-
});
|
|
169
|
-
program
|
|
170
|
-
.command('onboard')
|
|
171
|
-
.description('Create your AI persona (profile, communication style, rules)')
|
|
172
|
-
.action(async () => {
|
|
173
|
-
const config = loadConfig();
|
|
174
|
-
await onboard(config);
|
|
175
|
-
});
|
|
229
|
+
// =============================================================================
|
|
230
|
+
// Setup and configuration
|
|
231
|
+
// =============================================================================
|
|
176
232
|
program
|
|
177
233
|
.command('init')
|
|
178
|
-
.description('
|
|
179
|
-
.option('--legacy', 'run legacy init (credentials + database only)')
|
|
180
|
-
.action(async (options) => {
|
|
181
|
-
if (options.legacy) {
|
|
182
|
-
await init();
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
await wizard();
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
const setupCmd = program
|
|
189
|
-
.command('setup')
|
|
190
|
-
.description('Configure an agent platform to use Ledger');
|
|
191
|
-
setupCmd
|
|
192
|
-
.command('claude-code')
|
|
193
|
-
.description('Register MCP, install hooks, pull cache (live sync)')
|
|
194
|
-
.action(async () => {
|
|
195
|
-
await setupClaudeCode();
|
|
196
|
-
});
|
|
197
|
-
setupCmd
|
|
198
|
-
.command('openclaw [path]')
|
|
199
|
-
.description('Generate persona files for OpenClaw (live sync via CLI)')
|
|
200
|
-
.action(async (path) => {
|
|
201
|
-
await setupOpenclaw(path);
|
|
202
|
-
});
|
|
203
|
-
setupCmd
|
|
204
|
-
.command('chatgpt')
|
|
205
|
-
.description('Generate system prompt for ChatGPT (static snapshot)')
|
|
206
|
-
.action(async () => {
|
|
207
|
-
await setupChatgpt();
|
|
208
|
-
});
|
|
209
|
-
program
|
|
210
|
-
.command('migrate')
|
|
211
|
-
.description('Safely migrate local files to Ledger (backup, compare, upload)')
|
|
234
|
+
.description('Set up Ledger credentials and database')
|
|
212
235
|
.action(async () => {
|
|
213
|
-
|
|
214
|
-
await migrate(config);
|
|
215
|
-
});
|
|
216
|
-
program
|
|
217
|
-
.command('add')
|
|
218
|
-
.description('Add a new note to Ledger (with duplicate detection)')
|
|
219
|
-
.requiredOption('-c, --content <content>', 'note content (or use stdin)')
|
|
220
|
-
.option('-t, --type <type>', 'note type (feedback, reference, event, etc.)')
|
|
221
|
-
.option('-a, --agent <agent>', 'agent name', 'cli')
|
|
222
|
-
.option('-p, --project <project>', 'project name')
|
|
223
|
-
.option('-k, --upsert-key <key>', 'upsert key for dedup')
|
|
224
|
-
.option('-d, --description <text>', 'one-line description of the note')
|
|
225
|
-
.option('-s, --status <status>', 'note status (idea, planning, active, done)')
|
|
226
|
-
.option('-f, --force', 'skip duplicate check and interactive prompts')
|
|
227
|
-
.action(async (options) => {
|
|
228
|
-
const config = loadConfig();
|
|
229
|
-
await add(config, options.content, {
|
|
230
|
-
type: options.type,
|
|
231
|
-
agent: options.agent,
|
|
232
|
-
project: options.project,
|
|
233
|
-
upsertKey: options.upsertKey,
|
|
234
|
-
description: options.description,
|
|
235
|
-
status: options.status,
|
|
236
|
-
force: options.force ?? false,
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
program
|
|
240
|
-
.command('update <id>')
|
|
241
|
-
.description('Update an existing note by ID (with confirmation)')
|
|
242
|
-
.requiredOption('-c, --content <content>', 'new content')
|
|
243
|
-
.action(async (id, options) => {
|
|
244
|
-
const config = loadConfig();
|
|
245
|
-
await update(config, parseInt(id, 10), options.content, {});
|
|
246
|
-
});
|
|
247
|
-
program
|
|
248
|
-
.command('delete <id>')
|
|
249
|
-
.description('Delete a note by ID (with confirmation)')
|
|
250
|
-
.action(async (id) => {
|
|
251
|
-
const config = loadConfig();
|
|
252
|
-
await deleteNote(config, parseInt(id, 10));
|
|
253
|
-
});
|
|
254
|
-
program
|
|
255
|
-
.command('list')
|
|
256
|
-
.description('List recent notes from Ledger')
|
|
257
|
-
.option('-n, --limit <number>', 'number of notes', '20')
|
|
258
|
-
.option('-t, --type <type>', 'filter by note type')
|
|
259
|
-
.option('-p, --project <project>', 'filter by project name')
|
|
260
|
-
.action(async (options) => {
|
|
261
|
-
const config = loadConfig();
|
|
262
|
-
await list(config, {
|
|
263
|
-
limit: parseInt(options.limit, 10),
|
|
264
|
-
type: options.type,
|
|
265
|
-
project: options.project,
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
program
|
|
269
|
-
.command('tag <id>')
|
|
270
|
-
.description('Update metadata on a note (description, project, scope)')
|
|
271
|
-
.option('-d, --description <text>', 'note description/purpose')
|
|
272
|
-
.option('-p, --project <name>', 'project name')
|
|
273
|
-
.option('-s, --scope <scope>', 'scope (user, system, general)')
|
|
274
|
-
.action(async (id, options) => {
|
|
275
|
-
const config = loadConfig();
|
|
276
|
-
await tag(config, parseInt(id, 10), {
|
|
277
|
-
description: options.description,
|
|
278
|
-
project: options.project,
|
|
279
|
-
scope: options.scope,
|
|
280
|
-
});
|
|
236
|
+
await init();
|
|
281
237
|
});
|
|
282
238
|
program
|
|
283
239
|
.command('lint')
|
|
@@ -287,4 +243,4 @@ program
|
|
|
287
243
|
.action(async (options) => {
|
|
288
244
|
await lint({ personal: options.personal ?? false, diff: options.diff ?? false });
|
|
289
245
|
});
|
|
290
|
-
program.
|
|
246
|
+
program.parseAsync().then(() => shutdownObservability());
|
package/dist/commands/add.js
CHANGED
|
@@ -1,103 +1,54 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
...registeredTypes,
|
|
24
|
-
'skip — use default (general)',
|
|
25
|
-
]);
|
|
26
|
-
type = typeChoice.startsWith('skip') ? 'general' : typeChoice;
|
|
27
|
-
}
|
|
28
|
-
// Handle unknown type from --type flag
|
|
29
|
-
if (type && !isRegisteredType(type)) {
|
|
30
|
-
console.error(`\nType "${type}" is not registered.`);
|
|
31
|
-
const action = await choose('What would you like to do?', [
|
|
32
|
-
'register — register it now (pick a delivery tier)',
|
|
33
|
-
'existing — use an existing type instead',
|
|
34
|
-
'proceed — save anyway (defaults to "knowledge" delivery)',
|
|
35
|
-
]);
|
|
36
|
-
if (action.startsWith('register')) {
|
|
37
|
-
const nameError = validateTypeName(type);
|
|
38
|
-
if (nameError) {
|
|
39
|
-
console.error(nameError);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
const deliveryChoice = await choose('Delivery tier?', ['persona', 'project', 'knowledge', 'protected']);
|
|
43
|
-
registerType(type, deliveryChoice);
|
|
44
|
-
console.error(`Registered type "${type}" with delivery "${deliveryChoice}".`);
|
|
45
|
-
}
|
|
46
|
-
else if (action.startsWith('existing')) {
|
|
47
|
-
const registeredTypes = getRegisteredTypes();
|
|
48
|
-
type = await choose('Choose a type:', registeredTypes);
|
|
49
|
-
}
|
|
50
|
-
// 'proceed' — use the type as-is, will default to knowledge delivery
|
|
51
|
-
}
|
|
52
|
-
// Description
|
|
53
|
-
if (!metadata.description) {
|
|
54
|
-
const desc = await ask('One-line description (what is this note for?): ');
|
|
55
|
-
if (desc)
|
|
56
|
-
metadata.description = desc;
|
|
57
|
-
}
|
|
58
|
-
// upsert_key
|
|
59
|
-
if (!metadata.upsert_key) {
|
|
60
|
-
const key = await ask('Unique key for this note (lowercase-hyphenated, or Enter to auto-generate): ');
|
|
61
|
-
if (key)
|
|
62
|
-
metadata.upsert_key = key;
|
|
63
|
-
}
|
|
64
|
-
// Project
|
|
65
|
-
if (!metadata.project) {
|
|
66
|
-
const proj = await ask('Project name (or Enter to skip): ');
|
|
67
|
-
if (proj)
|
|
68
|
-
metadata.project = proj;
|
|
69
|
-
}
|
|
70
|
-
// Status (only for project-scoped types)
|
|
71
|
-
if (inferDelivery(type) === 'project' && !metadata.status) {
|
|
72
|
-
const statusChoice = await choose('What stage is this?', [
|
|
73
|
-
...NOTE_STATUSES,
|
|
74
|
-
'skip — no status',
|
|
75
|
-
]);
|
|
76
|
-
if (!statusChoice.startsWith('skip')) {
|
|
77
|
-
metadata.status = statusChoice;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { createDocumentFromFile, VerifyMismatchError } from '../lib/documents/operations.js';
|
|
4
|
+
import { fatal, ExitCode } from '../lib/errors.js';
|
|
5
|
+
// =============================================================================
|
|
6
|
+
// Command
|
|
7
|
+
// =============================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Add a new document by reading content from a file on disk. Auto-verified after create.
|
|
10
|
+
*
|
|
11
|
+
* Bytes flow file -> createDocumentFromFile() -> Postgres without retyping.
|
|
12
|
+
* The composed-string path (`-c`) was removed in Phase 4 of the
|
|
13
|
+
* file-based-write-api rollout to close the drift class of bug.
|
|
14
|
+
*
|
|
15
|
+
* Drift surfaces as VerifyMismatchError on stderr with exit code VERIFY_MISMATCH.
|
|
16
|
+
* The document still exists on verify failure (audit_log preserves the create
|
|
17
|
+
* event for manual cleanup if needed).
|
|
18
|
+
*/
|
|
19
|
+
export async function addDocumentFromFile(config, options) {
|
|
20
|
+
const absPath = resolve(options.filePath);
|
|
21
|
+
if (!existsSync(absPath)) {
|
|
22
|
+
fatal(`File not found: ${absPath}`, ExitCode.FILE_NOT_FOUND);
|
|
80
23
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
24
|
+
const clients = {
|
|
25
|
+
supabase: config.supabase,
|
|
26
|
+
openai: config.openai,
|
|
27
|
+
};
|
|
28
|
+
process.stderr.write(`Adding document "${options.name}" from ${absPath} (${options.domain}/${options.documentType})...\n`);
|
|
29
|
+
try {
|
|
30
|
+
const result = await createDocumentFromFile(clients, {
|
|
31
|
+
filePath: absPath,
|
|
32
|
+
name: options.name,
|
|
33
|
+
domain: options.domain,
|
|
34
|
+
document_type: options.documentType,
|
|
35
|
+
description: options.description,
|
|
36
|
+
project: options.project,
|
|
37
|
+
agent: options.agent,
|
|
38
|
+
status: options.status,
|
|
39
|
+
protection: options.protection,
|
|
40
|
+
});
|
|
41
|
+
process.stdout.write(`${result.id}\n`);
|
|
42
|
+
process.stderr.write(`Document created (id: ${result.id}, ${result.bytes} bytes, verified)\n`);
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
if (error instanceof VerifyMismatchError) {
|
|
46
|
+
process.stderr.write(`\nVerify failed on document ${error.id}.\n`);
|
|
47
|
+
process.stderr.write(`Pushed ${error.expectedLength} bytes, pulled ${error.actualLength} bytes.\n`);
|
|
48
|
+
process.stderr.write(`${error.diffPreview}\n`);
|
|
49
|
+
process.stderr.write(`The document was created but the round-trip diff caught drift. Inspect the doc and re-run if needed.\n`);
|
|
50
|
+
process.exit(ExitCode.VERIFY_MISMATCH);
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
99
53
|
}
|
|
100
|
-
console.error(result.message);
|
|
101
|
-
if (result.status === 'error')
|
|
102
|
-
process.exit(1);
|
|
103
54
|
}
|