@onebrain-ai/cli 2.0.1 → 2.0.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/onebrain +3 -3
- package/package.json +23 -1
- package/src/commands/doctor.test.ts +0 -416
- package/src/commands/doctor.ts +0 -203
- package/src/commands/init.test.ts +0 -318
- package/src/commands/init.ts +0 -477
- package/src/commands/update.test.ts +0 -413
- package/src/commands/update.ts +0 -353
- package/src/index.ts +0 -144
- package/src/internal/__snapshots__/checkpoint.test.ts.snap +0 -12
- package/src/internal/__snapshots__/orphan-scan.test.ts.snap +0 -13
- package/src/internal/__snapshots__/session-init.test.ts.snap +0 -15
- package/src/internal/checkpoint.test.ts +0 -741
- package/src/internal/checkpoint.ts +0 -427
- package/src/internal/migrate.test.ts +0 -301
- package/src/internal/migrate.ts +0 -186
- package/src/internal/orphan-scan.test.ts +0 -271
- package/src/internal/orphan-scan.ts +0 -213
- package/src/internal/qmd-reindex.test.ts +0 -117
- package/src/internal/qmd-reindex.ts +0 -44
- package/src/internal/register-hooks.test.ts +0 -343
- package/src/internal/register-hooks.ts +0 -418
- package/src/internal/session-init.test.ts +0 -318
- package/src/internal/session-init.ts +0 -264
- package/src/internal/vault-sync.test.ts +0 -419
- package/src/internal/vault-sync.ts +0 -764
- package/tests/integration/init.integration.test.ts +0 -304
- package/tests/integration/update.integration.test.ts +0 -306
- package/tsconfig.json +0 -12
package/src/commands/update.ts
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* update — Atomic OneBrain update sequence
|
|
3
|
-
*
|
|
4
|
-
* Steps:
|
|
5
|
-
* 1. Fetch latest release from GitHub (parse tag_name)
|
|
6
|
-
* 2. Sync plugin files (vault-sync)
|
|
7
|
-
* 3. (Handled by vault-sync Step 4 — merge harness files)
|
|
8
|
-
* 4. Install binary (bun install -g / npm install -g on Windows)
|
|
9
|
-
* 4b. Validate binary (ATOMIC GATE — register-hooks blocked if this fails)
|
|
10
|
-
* 5. Register hooks (only if 4b passed)
|
|
11
|
-
* 6. Write onebrain_version to vault.yml
|
|
12
|
-
*
|
|
13
|
-
* TTY: uses @clack/prompts layout
|
|
14
|
-
* Non-TTY: plain text lines
|
|
15
|
-
*
|
|
16
|
-
* Exit code: 0 on success, 1 on failure.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { readFile, rename, writeFile } from 'node:fs/promises';
|
|
20
|
-
import { join } from 'node:path';
|
|
21
|
-
import { intro, log, outro } from '@clack/prompts';
|
|
22
|
-
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// Types
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
export interface UpdateOptions {
|
|
29
|
-
/** Vault root directory (default: process.cwd()). */
|
|
30
|
-
vaultDir?: string;
|
|
31
|
-
/** Whether stdout is a TTY (default: process.stdout.isTTY). */
|
|
32
|
-
isTTY?: boolean;
|
|
33
|
-
/** Dry run — show what would change and exit 0 without making changes. */
|
|
34
|
-
check?: boolean;
|
|
35
|
-
/** Override update channel: 'stable' | 'next'. Falls back to vault.yml update_channel. */
|
|
36
|
-
channel?: 'stable' | 'next';
|
|
37
|
-
/** Mock fetch for tests. */
|
|
38
|
-
fetchFn?: typeof fetch;
|
|
39
|
-
/** Injectable vault-sync function for tests. */
|
|
40
|
-
vaultSyncFn?: (
|
|
41
|
-
vaultDir: string,
|
|
42
|
-
opts: Record<string, unknown>,
|
|
43
|
-
) => Promise<{ filesAdded: number; filesRemoved: number }>;
|
|
44
|
-
/** Injectable binary install function for tests. */
|
|
45
|
-
installBinaryFn?: (version: string) => Promise<void>;
|
|
46
|
-
/** Injectable binary validation function for tests. Returns true if binary is valid. */
|
|
47
|
-
validateBinaryFn?: () => Promise<boolean>;
|
|
48
|
-
/** Injectable register-hooks function for tests. */
|
|
49
|
-
registerHooksFn?: (vaultDir: string) => Promise<void>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface UpdateResult {
|
|
53
|
-
ok: boolean;
|
|
54
|
-
exitCode: number;
|
|
55
|
-
latestVersion?: string;
|
|
56
|
-
currentVersion?: string;
|
|
57
|
-
error?: string;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Constants
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
const GITHUB_RELEASES_URL = 'https://api.github.com/repos/kengio/onebrain/releases/latest';
|
|
65
|
-
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Helpers
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
|
-
/** Resolve branch name from channel or vault.yml update_channel. */
|
|
71
|
-
function resolveBranch(channel: string | undefined): string {
|
|
72
|
-
return channel === 'next' ? 'next' : 'main';
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Read vault.yml as raw object — non-throwing. Returns {} on missing/invalid. */
|
|
76
|
-
async function readVaultYmlRaw(vaultDir: string): Promise<Record<string, unknown>> {
|
|
77
|
-
try {
|
|
78
|
-
const text = await readFile(join(vaultDir, 'vault.yml'), 'utf8');
|
|
79
|
-
return (parseYaml(text) ?? {}) as Record<string, unknown>;
|
|
80
|
-
} catch {
|
|
81
|
-
return {};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Write onebrain_version field to vault.yml atomically (in-place merge). */
|
|
86
|
-
async function writeVersionToVaultYml(vaultDir: string, version: string): Promise<void> {
|
|
87
|
-
const raw = await readVaultYmlRaw(vaultDir);
|
|
88
|
-
raw.onebrain_version = version;
|
|
89
|
-
const content = stringifyYaml(raw, { lineWidth: 0 });
|
|
90
|
-
const vaultYmlPath = join(vaultDir, 'vault.yml');
|
|
91
|
-
const tmpPath = `${vaultYmlPath}.tmp`;
|
|
92
|
-
await writeFile(tmpPath, content, 'utf8');
|
|
93
|
-
await rename(tmpPath, vaultYmlPath);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Step 1: Fetch latest release
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
async function fetchLatestVersion(fetchFn: typeof fetch): Promise<string> {
|
|
101
|
-
const response = await fetchFn(GITHUB_RELEASES_URL, {
|
|
102
|
-
headers: { Accept: 'application/vnd.github.v3+json' },
|
|
103
|
-
});
|
|
104
|
-
if (!response.ok) {
|
|
105
|
-
throw new Error(`GitHub API returned HTTP ${response.status}`);
|
|
106
|
-
}
|
|
107
|
-
const json = (await response.json()) as Record<string, unknown>;
|
|
108
|
-
const tagName = json.tag_name;
|
|
109
|
-
if (typeof tagName !== 'string' || !tagName) {
|
|
110
|
-
throw new Error('GitHub response missing tag_name');
|
|
111
|
-
}
|
|
112
|
-
return tagName;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ---------------------------------------------------------------------------
|
|
116
|
-
// Step 4: Install binary
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
|
|
119
|
-
async function defaultInstallBinary(version: string): Promise<void> {
|
|
120
|
-
const isWindows = process.platform === 'win32';
|
|
121
|
-
const cmd = isWindows
|
|
122
|
-
? ['npm', 'install', '-g', `@onebrain-ai/cli@${version}`]
|
|
123
|
-
: ['bun', 'install', '-g', `@onebrain-ai/cli@${version}`];
|
|
124
|
-
|
|
125
|
-
const proc = Bun.spawn(cmd, { stdout: 'pipe', stderr: 'pipe' });
|
|
126
|
-
const exitCode = await proc.exited;
|
|
127
|
-
if (exitCode !== 0) {
|
|
128
|
-
const errText = await new Response(proc.stderr).text();
|
|
129
|
-
throw new Error(`Binary install failed (exit ${exitCode}): ${errText.trim()}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ---------------------------------------------------------------------------
|
|
134
|
-
// Step 4b: Validate binary
|
|
135
|
-
// ---------------------------------------------------------------------------
|
|
136
|
-
|
|
137
|
-
async function defaultValidateBinary(): Promise<boolean> {
|
|
138
|
-
try {
|
|
139
|
-
const proc = Bun.spawn(['onebrain', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
140
|
-
const exitCode = await proc.exited;
|
|
141
|
-
if (exitCode !== 0) return false;
|
|
142
|
-
const stdout = await new Response(proc.stdout).text();
|
|
143
|
-
// Expect version-like output (digits)
|
|
144
|
-
return /^\d+\.\d+/.test(stdout.trim());
|
|
145
|
-
} catch {
|
|
146
|
-
return false;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ---------------------------------------------------------------------------
|
|
151
|
-
// Main runUpdate
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
export async function runUpdate(opts: UpdateOptions = {}): Promise<UpdateResult> {
|
|
155
|
-
const vaultDir = opts.vaultDir ?? process.cwd();
|
|
156
|
-
const isTTY = opts.isTTY ?? process.stdout.isTTY ?? false;
|
|
157
|
-
const check = opts.check ?? false;
|
|
158
|
-
|
|
159
|
-
const fetchFn = opts.fetchFn ?? globalThis.fetch;
|
|
160
|
-
|
|
161
|
-
const vaultSyncFn =
|
|
162
|
-
opts.vaultSyncFn ??
|
|
163
|
-
(async (dir: string, syncOpts: Record<string, unknown>) => {
|
|
164
|
-
const { runVaultSync } = await import('../internal/vault-sync.js');
|
|
165
|
-
const result = await runVaultSync(dir, syncOpts);
|
|
166
|
-
return { filesAdded: result.filesAdded, filesRemoved: result.filesRemoved };
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
const installBinaryFn = opts.installBinaryFn ?? defaultInstallBinary;
|
|
170
|
-
const validateBinaryFn = opts.validateBinaryFn ?? defaultValidateBinary;
|
|
171
|
-
|
|
172
|
-
const registerHooksFn =
|
|
173
|
-
opts.registerHooksFn ??
|
|
174
|
-
(async (dir: string) => {
|
|
175
|
-
const { runRegisterHooks } = await import('../internal/register-hooks.js');
|
|
176
|
-
await runRegisterHooks({ vaultDir: dir });
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const result: UpdateResult = {
|
|
180
|
-
ok: false,
|
|
181
|
-
exitCode: 0,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Output helpers
|
|
185
|
-
function writeLine(msg: string) {
|
|
186
|
-
process.stdout.write(`${msg}\n`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function noteStep(label: string, detail: string) {
|
|
190
|
-
if (isTTY) {
|
|
191
|
-
log.step(`${label}\n│ ${detail}`);
|
|
192
|
-
} else {
|
|
193
|
-
writeLine(`${label}: ${detail}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Header
|
|
198
|
-
if (isTTY) {
|
|
199
|
-
intro('OneBrain Update');
|
|
200
|
-
} else {
|
|
201
|
-
writeLine('OneBrain Update');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ── Step 1: Fetch latest release ──────────────────────────────────────────
|
|
205
|
-
|
|
206
|
-
let latestVersion: string;
|
|
207
|
-
try {
|
|
208
|
-
latestVersion = await fetchLatestVersion(fetchFn);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
211
|
-
result.error = `Fetch failed: ${msg}`;
|
|
212
|
-
result.exitCode = 1;
|
|
213
|
-
process.stderr.write(`update: ${result.error}\n`);
|
|
214
|
-
return result;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
result.latestVersion = latestVersion;
|
|
218
|
-
|
|
219
|
-
// Read current version from vault.yml
|
|
220
|
-
const vaultYmlRaw = await readVaultYmlRaw(vaultDir);
|
|
221
|
-
const currentVersion =
|
|
222
|
-
typeof vaultYmlRaw.onebrain_version === 'string' ? vaultYmlRaw.onebrain_version : 'unknown';
|
|
223
|
-
result.currentVersion = currentVersion;
|
|
224
|
-
|
|
225
|
-
// Resolve channel/branch
|
|
226
|
-
const channel =
|
|
227
|
-
opts.channel ??
|
|
228
|
-
(typeof vaultYmlRaw.update_channel === 'string'
|
|
229
|
-
? (vaultYmlRaw.update_channel as 'stable' | 'next')
|
|
230
|
-
: 'stable');
|
|
231
|
-
const branch = resolveBranch(channel);
|
|
232
|
-
|
|
233
|
-
noteStep('fetching', `${latestVersion} available (current: ${currentVersion})`);
|
|
234
|
-
|
|
235
|
-
// ── --check: dry run ──────────────────────────────────────────────────────
|
|
236
|
-
|
|
237
|
-
if (check) {
|
|
238
|
-
if (isTTY) {
|
|
239
|
-
outro('Dry run complete — no changes made');
|
|
240
|
-
} else {
|
|
241
|
-
writeLine('done: dry run complete — no changes made');
|
|
242
|
-
}
|
|
243
|
-
result.ok = true;
|
|
244
|
-
result.exitCode = 0;
|
|
245
|
-
return result;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// ── Step 2: vault-sync ────────────────────────────────────────────────────
|
|
249
|
-
|
|
250
|
-
let filesAdded = 0;
|
|
251
|
-
let filesRemoved = 0;
|
|
252
|
-
try {
|
|
253
|
-
const syncResult = await vaultSyncFn(vaultDir, { branch });
|
|
254
|
-
filesAdded = syncResult.filesAdded;
|
|
255
|
-
filesRemoved = syncResult.filesRemoved;
|
|
256
|
-
} catch (err) {
|
|
257
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
258
|
-
result.error = `vault-sync failed: ${msg}`;
|
|
259
|
-
result.exitCode = 1;
|
|
260
|
-
process.stderr.write(`update: ${result.error}\n`);
|
|
261
|
-
return result;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
noteStep('syncing', `${filesAdded} files synced, ${filesRemoved} removed`);
|
|
265
|
-
|
|
266
|
-
// ── Step 4: Install binary ────────────────────────────────────────────────
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
await installBinaryFn(latestVersion);
|
|
270
|
-
} catch (err) {
|
|
271
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
272
|
-
result.error = `Binary install failed: ${msg}`;
|
|
273
|
-
result.exitCode = 1;
|
|
274
|
-
process.stderr.write(`update: ${result.error}\n`);
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
noteStep('upgrading', `@onebrain-ai/cli ${latestVersion} installed`);
|
|
279
|
-
|
|
280
|
-
// ── Step 4b: Validate binary (ATOMIC GATE) ────────────────────────────────
|
|
281
|
-
|
|
282
|
-
const binaryValid = await validateBinaryFn();
|
|
283
|
-
if (!binaryValid) {
|
|
284
|
-
result.error = 'Binary validation failed. Check PATH. register-hooks NOT called.';
|
|
285
|
-
result.exitCode = 1;
|
|
286
|
-
process.stderr.write(`update: ${result.error}\n`);
|
|
287
|
-
return result;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ── Step 5: Register hooks (only if 4b passed) ────────────────────────────
|
|
291
|
-
|
|
292
|
-
let hooksDetail = 'hooks: ✓ PATH: ✓ permissions: ✓';
|
|
293
|
-
let hooksOk = true;
|
|
294
|
-
try {
|
|
295
|
-
await registerHooksFn(vaultDir);
|
|
296
|
-
} catch (err) {
|
|
297
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
298
|
-
hooksDetail = `warning: ${msg}`;
|
|
299
|
-
hooksOk = false;
|
|
300
|
-
process.stderr.write(`update: register-hooks warning: ${msg}\n`);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (isTTY) {
|
|
304
|
-
log.step(`Registering hooks\n│ ${hooksDetail}`);
|
|
305
|
-
} else {
|
|
306
|
-
writeLine(hooksOk ? 'hooks: ok PATH: ok permissions: ok' : `hooks: warning — ${hooksDetail}`);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// ── Step 6: Write version to vault.yml ───────────────────────────────────
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
await writeVersionToVaultYml(vaultDir, latestVersion);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
315
|
-
process.stderr.write(`update: vault.yml version write warning: ${msg}\n`);
|
|
316
|
-
if (isTTY) {
|
|
317
|
-
log.warn(`vault.yml not updated — ${msg}`);
|
|
318
|
-
} else {
|
|
319
|
-
writeLine(`warning: vault.yml not updated — ${msg}`);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ── Done ──────────────────────────────────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
result.ok = true;
|
|
326
|
-
result.exitCode = 0;
|
|
327
|
-
|
|
328
|
-
const doneMsg = `OneBrain ${latestVersion}`;
|
|
329
|
-
if (isTTY) {
|
|
330
|
-
outro(`Done — ${doneMsg}`);
|
|
331
|
-
} else {
|
|
332
|
-
writeLine(`done: ${doneMsg}`);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return result;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ---------------------------------------------------------------------------
|
|
339
|
-
// CLI entry point (called from index.ts)
|
|
340
|
-
// ---------------------------------------------------------------------------
|
|
341
|
-
|
|
342
|
-
export interface UpdateCommandOptions {
|
|
343
|
-
vaultDir?: string;
|
|
344
|
-
check?: boolean;
|
|
345
|
-
channel?: 'stable' | 'next';
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export async function updateCommand(opts: UpdateCommandOptions = {}): Promise<void> {
|
|
349
|
-
const result = await runUpdate(opts);
|
|
350
|
-
if (!result.ok) {
|
|
351
|
-
process.exit(result.exitCode || 1);
|
|
352
|
-
}
|
|
353
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { doctorCommand } from './commands/doctor.js';
|
|
4
|
-
import { initCommand } from './commands/init.js';
|
|
5
|
-
import { updateCommand } from './commands/update.js';
|
|
6
|
-
import { checkpointCommand } from './internal/checkpoint.js';
|
|
7
|
-
import { migrateCommand } from './internal/migrate.js';
|
|
8
|
-
import { orphanScanCommand } from './internal/orphan-scan.js';
|
|
9
|
-
import { qmdReindexCommand } from './internal/qmd-reindex.js';
|
|
10
|
-
import { registerHooksCommand } from './internal/register-hooks.js';
|
|
11
|
-
import { resolveSessionToken, sessionInitCommand } from './internal/session-init.js';
|
|
12
|
-
import { vaultSyncCommand } from './internal/vault-sync.js';
|
|
13
|
-
|
|
14
|
-
// BUILD_VERSION and BUILD_DATE are injected as string literals at compile time
|
|
15
|
-
// via `bun build --define BUILD_VERSION='"x.y.z"'`. When running without --define
|
|
16
|
-
// (e.g. `bun run src/index.ts` during development), the identifiers are undeclared
|
|
17
|
-
// at runtime, and the typeof guard falls back to the dev placeholder.
|
|
18
|
-
declare const BUILD_VERSION: string;
|
|
19
|
-
declare const BUILD_DATE: string;
|
|
20
|
-
const VERSION = typeof BUILD_VERSION !== 'undefined' ? BUILD_VERSION : '0.0.0-dev';
|
|
21
|
-
const RELEASE_DATE = typeof BUILD_DATE !== 'undefined' ? BUILD_DATE : 'dev';
|
|
22
|
-
|
|
23
|
-
const VERSION_STRING = `OneBrain v${VERSION} — released ${RELEASE_DATE}`;
|
|
24
|
-
|
|
25
|
-
// Handle no-args case before commander parses anything.
|
|
26
|
-
if (process.argv.slice(2).length === 0) {
|
|
27
|
-
console.log(VERSION_STRING);
|
|
28
|
-
console.log('Run `onebrain help` for available commands.');
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const program = new Command();
|
|
33
|
-
|
|
34
|
-
program
|
|
35
|
-
.name('onebrain')
|
|
36
|
-
.description('OneBrain CLI — personal AI OS for Obsidian')
|
|
37
|
-
.version(VERSION_STRING, '-v, --version');
|
|
38
|
-
|
|
39
|
-
// ── User-facing commands ──────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
program
|
|
42
|
-
.command('init')
|
|
43
|
-
.description('Initialize a new OneBrain vault')
|
|
44
|
-
.option('--vault-dir <path>', 'vault root directory (default: cwd)')
|
|
45
|
-
.option('--harness <harness>', 'harness type: claude-code | gemini | direct')
|
|
46
|
-
.option('--force', 'overwrite existing vault.yml without prompting')
|
|
47
|
-
.action(async (opts: { vaultDir?: string; harness?: string; force?: boolean }) => {
|
|
48
|
-
await initCommand({
|
|
49
|
-
vaultDir: opts.vaultDir,
|
|
50
|
-
harness: opts.harness as 'claude-code' | 'gemini' | 'direct' | undefined,
|
|
51
|
-
force: opts.force,
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
program
|
|
56
|
-
.command('update')
|
|
57
|
-
.description('Update OneBrain plugin files from GitHub')
|
|
58
|
-
.option('--check', 'show what would change and exit without making changes')
|
|
59
|
-
.option('--channel <channel>', 'update channel: stable | next')
|
|
60
|
-
.action(async (opts: { check?: boolean; channel?: string }) => {
|
|
61
|
-
await updateCommand({
|
|
62
|
-
check: opts.check,
|
|
63
|
-
channel: opts.channel as 'stable' | 'next' | undefined,
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
program
|
|
68
|
-
.command('doctor')
|
|
69
|
-
.description('Run vault health checks and report issues')
|
|
70
|
-
.action(async () => {
|
|
71
|
-
const vaultRoot = process.cwd();
|
|
72
|
-
await doctorCommand({ vaultDir: vaultRoot, binaryVersion: VERSION });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
program
|
|
76
|
-
.command('help')
|
|
77
|
-
.description('Show this help message')
|
|
78
|
-
.action(() => {
|
|
79
|
-
program.help();
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ── Internal hidden commands (not shown in --help) ────────────────────────────
|
|
83
|
-
|
|
84
|
-
program
|
|
85
|
-
.command('session-init', { hidden: true })
|
|
86
|
-
.description('Emit session token and datetime (called by Claude Code hook)')
|
|
87
|
-
.action(async () => {
|
|
88
|
-
const vaultRoot = process.cwd();
|
|
89
|
-
await sessionInitCommand(vaultRoot);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
program
|
|
93
|
-
.command('orphan-scan', { hidden: true })
|
|
94
|
-
.description('Scan for orphaned checkpoint files in logs folder')
|
|
95
|
-
.argument('<logs_folder>', 'path to logs folder')
|
|
96
|
-
.argument('<session_token>', 'current session token to exclude')
|
|
97
|
-
.action(async (logsFolder: string, sessionToken: string) => {
|
|
98
|
-
await orphanScanCommand(logsFolder, sessionToken);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
program
|
|
102
|
-
.command('checkpoint', { hidden: true })
|
|
103
|
-
.description('Handle checkpoint lifecycle (stop/precompact/postcompact/reset)')
|
|
104
|
-
.argument('<mode>', 'stop | precompact | postcompact | reset')
|
|
105
|
-
.action(async (mode: string) => {
|
|
106
|
-
const token = await resolveSessionToken();
|
|
107
|
-
await checkpointCommand(mode, token, process.cwd());
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
program
|
|
111
|
-
.command('qmd-reindex', { hidden: true })
|
|
112
|
-
.description('Trigger qmd index rebuild')
|
|
113
|
-
.action(async () => {
|
|
114
|
-
const vaultRoot = process.cwd();
|
|
115
|
-
await qmdReindexCommand(vaultRoot);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
program
|
|
119
|
-
.command('vault-sync', { hidden: true })
|
|
120
|
-
.description('Sync plugin files from GitHub to vault')
|
|
121
|
-
.argument('[vault_root]', 'vault root directory (default: cwd)')
|
|
122
|
-
.option('--branch <branch>', 'override branch (main | next)')
|
|
123
|
-
.action(async (vaultRoot: string | undefined, opts: { branch?: string }) => {
|
|
124
|
-
const root = vaultRoot ?? process.cwd();
|
|
125
|
-
await vaultSyncCommand(root, { branch: opts.branch });
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
program
|
|
129
|
-
.command('register-hooks', { hidden: true })
|
|
130
|
-
.description('Install Claude Code hooks into settings.json')
|
|
131
|
-
.option('--vault-dir <path>', 'vault root directory (default: cwd)')
|
|
132
|
-
.action(async (opts: { vaultDir?: string }) => {
|
|
133
|
-
await registerHooksCommand(opts.vaultDir);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
program
|
|
137
|
-
.command('migrate', { hidden: true })
|
|
138
|
-
.description('Run one-time migration scripts')
|
|
139
|
-
.argument('<name>', 'migration name: backfill-recapped')
|
|
140
|
-
.action(async (name: string) => {
|
|
141
|
-
await migrateCommand(name);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
program.parse(process.argv);
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
|
2
|
-
|
|
3
|
-
exports[`handleStop stop block JSON shape matches snapshot { decision: "block", reason: "...-checkpoint-NN.md since ..." } 1`] = `
|
|
4
|
-
[
|
|
5
|
-
"decision",
|
|
6
|
-
"reason",
|
|
7
|
-
]
|
|
8
|
-
`;
|
|
9
|
-
|
|
10
|
-
exports[`handleStop stop block JSON shape matches snapshot { decision: "block", reason: "...-checkpoint-NN.md since ..." } 2`] = `"block"`;
|
|
11
|
-
|
|
12
|
-
exports[`handleStop stop block JSON shape matches snapshot { decision: "block", reason: "...-checkpoint-NN.md since ..." } 3`] = `"string"`;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
|
2
|
-
|
|
3
|
-
exports[`runOrphanScan output shape matches snapshot { orphan_count: N } 1`] = `
|
|
4
|
-
{
|
|
5
|
-
"orphan_count": 0,
|
|
6
|
-
}
|
|
7
|
-
`;
|
|
8
|
-
|
|
9
|
-
exports[`runOrphanScan output shape matches snapshot { orphan_count: N } 2`] = `
|
|
10
|
-
{
|
|
11
|
-
"orphan_count": 1,
|
|
12
|
-
}
|
|
13
|
-
`;
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
|
|
2
|
-
|
|
3
|
-
exports[`runSessionInit normal payload output shape matches snapshot 1`] = `
|
|
4
|
-
[
|
|
5
|
-
"datetime",
|
|
6
|
-
"qmd_unembedded",
|
|
7
|
-
"session_token",
|
|
8
|
-
]
|
|
9
|
-
`;
|
|
10
|
-
|
|
11
|
-
exports[`runSessionInit normal payload output shape matches snapshot 2`] = `"string"`;
|
|
12
|
-
|
|
13
|
-
exports[`runSessionInit normal payload output shape matches snapshot 3`] = `"string"`;
|
|
14
|
-
|
|
15
|
-
exports[`runSessionInit normal payload output shape matches snapshot 4`] = `"number"`;
|