@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/init.ts
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* init — Initialize a new OneBrain vault
|
|
3
|
-
*
|
|
4
|
-
* Steps:
|
|
5
|
-
* 1. Detect existing vault.yml (--force, non-TTY exit-1, TTY prompt)
|
|
6
|
-
* 2. Create standard folders (8 + inbox/imports)
|
|
7
|
-
* 3. Write vault.yml (with harness auto-detect)
|
|
8
|
-
* 4. Download plugin files (skip if .claude/plugins/onebrain/.claude-plugin/plugin.json exists)
|
|
9
|
-
* 5. Register plugin (skip if source:marketplace entry exists)
|
|
10
|
-
* 6. Run register-hooks
|
|
11
|
-
*
|
|
12
|
-
* TTY: uses @clack/prompts layout
|
|
13
|
-
* Non-TTY: plain text lines
|
|
14
|
-
*
|
|
15
|
-
* Exit code: 0 on success, 1 on failure.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';
|
|
19
|
-
import { homedir } from 'node:os';
|
|
20
|
-
import { dirname, join } from 'node:path';
|
|
21
|
-
import { confirm, intro, log, outro } from '@clack/prompts';
|
|
22
|
-
import { stringify as stringifyYaml } from 'yaml';
|
|
23
|
-
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
// BUILD_VERSION shim
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
|
|
28
|
-
declare const BUILD_VERSION: string;
|
|
29
|
-
const binaryVersion = typeof BUILD_VERSION !== 'undefined' ? BUILD_VERSION : 'dev';
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// Types
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
export interface InitOptions {
|
|
36
|
-
/** Vault root directory (default: process.cwd()). */
|
|
37
|
-
vaultDir?: string;
|
|
38
|
-
/** Harness override. */
|
|
39
|
-
harness?: 'claude-code' | 'gemini' | 'direct';
|
|
40
|
-
/** Overwrite existing vault.yml without prompting. */
|
|
41
|
-
force?: boolean;
|
|
42
|
-
/** Whether stdout is a TTY (default: process.stdout.isTTY). */
|
|
43
|
-
isTTY?: boolean;
|
|
44
|
-
/** Override path to installed_plugins.json (for tests). */
|
|
45
|
-
installedPluginsPath?: string;
|
|
46
|
-
/** Injectable vault-sync function (for tests). */
|
|
47
|
-
vaultSyncFn?: (vaultDir: string, opts: Record<string, unknown>) => Promise<void>;
|
|
48
|
-
/** Injectable register-hooks function (for tests). */
|
|
49
|
-
registerHooksFn?: (vaultDir: string) => Promise<void>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface InitResult {
|
|
53
|
-
ok: boolean;
|
|
54
|
-
exitCode: number;
|
|
55
|
-
/** Human-readable message (used for non-TTY output / test assertions). */
|
|
56
|
-
message?: string;
|
|
57
|
-
foldersCreated: number;
|
|
58
|
-
harness: string;
|
|
59
|
-
pluginSkipped: boolean;
|
|
60
|
-
pluginRegistrationSkipped: boolean;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Standard vault folders
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
/** [folder-path, create-parent-only] */
|
|
68
|
-
const STANDARD_FOLDERS: string[] = [
|
|
69
|
-
'00-inbox',
|
|
70
|
-
'01-projects',
|
|
71
|
-
'02-areas',
|
|
72
|
-
'03-knowledge',
|
|
73
|
-
'04-resources',
|
|
74
|
-
'05-agent',
|
|
75
|
-
'06-archive',
|
|
76
|
-
'07-logs',
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
// inbox/imports is a sub-directory that must also be created
|
|
80
|
-
const INBOX_IMPORTS = join('00-inbox', 'imports');
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Helpers
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
async function pathExists(p: string): Promise<boolean> {
|
|
87
|
-
try {
|
|
88
|
-
await stat(p);
|
|
89
|
-
return true;
|
|
90
|
-
} catch {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Auto-detect harness from environment and vault layout.
|
|
97
|
-
* Priority: CLAUDE_CODE_HARNESS env → .claude/ directory → 'direct'
|
|
98
|
-
*/
|
|
99
|
-
async function detectHarness(vaultDir: string): Promise<string> {
|
|
100
|
-
const envHarness = process.env.CLAUDE_CODE_HARNESS;
|
|
101
|
-
if (envHarness) return envHarness;
|
|
102
|
-
|
|
103
|
-
if (await pathExists(join(vaultDir, '.claude'))) return 'claude-code';
|
|
104
|
-
|
|
105
|
-
return 'direct';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
// Steps
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
|
|
112
|
-
async function createFolders(vaultDir: string): Promise<number> {
|
|
113
|
-
let created = 0;
|
|
114
|
-
|
|
115
|
-
const allPaths = [...STANDARD_FOLDERS, INBOX_IMPORTS];
|
|
116
|
-
|
|
117
|
-
for (const rel of allPaths) {
|
|
118
|
-
const full = join(vaultDir, rel);
|
|
119
|
-
if (!(await pathExists(full))) {
|
|
120
|
-
await mkdir(full, { recursive: true });
|
|
121
|
-
created++;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return created;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const VAULT_YML_DEFAULTS = {
|
|
129
|
-
method: 'onebrain',
|
|
130
|
-
update_channel: 'stable',
|
|
131
|
-
folders: {
|
|
132
|
-
inbox: '00-inbox',
|
|
133
|
-
projects: '01-projects',
|
|
134
|
-
areas: '02-areas',
|
|
135
|
-
knowledge: '03-knowledge',
|
|
136
|
-
resources: '04-resources',
|
|
137
|
-
agent: '05-agent',
|
|
138
|
-
archive: '06-archive',
|
|
139
|
-
logs: '07-logs',
|
|
140
|
-
},
|
|
141
|
-
checkpoint: {
|
|
142
|
-
messages: 15,
|
|
143
|
-
minutes: 30,
|
|
144
|
-
},
|
|
145
|
-
runtime: {
|
|
146
|
-
harness: 'claude-code',
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
async function writeVaultYml(vaultDir: string, harness: string): Promise<void> {
|
|
151
|
-
const config = {
|
|
152
|
-
...VAULT_YML_DEFAULTS,
|
|
153
|
-
runtime: { harness },
|
|
154
|
-
};
|
|
155
|
-
const content = stringifyYaml(config, { lineWidth: 0 });
|
|
156
|
-
await writeFile(join(vaultDir, 'vault.yml'), content, 'utf8');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Step 4: Download plugin files (skip if already present).
|
|
161
|
-
* Returns { skipped, driftWarning }.
|
|
162
|
-
*/
|
|
163
|
-
async function downloadPluginFiles(
|
|
164
|
-
vaultDir: string,
|
|
165
|
-
vaultSyncFn: (vaultDir: string, opts: Record<string, unknown>) => Promise<void>,
|
|
166
|
-
): Promise<{ skipped: boolean; driftWarning?: string; failed?: boolean }> {
|
|
167
|
-
const pluginJsonPath = join(
|
|
168
|
-
vaultDir,
|
|
169
|
-
'.claude',
|
|
170
|
-
'plugins',
|
|
171
|
-
'onebrain',
|
|
172
|
-
'.claude-plugin',
|
|
173
|
-
'plugin.json',
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
if (await pathExists(pluginJsonPath)) {
|
|
177
|
-
// Check version drift
|
|
178
|
-
let pluginVersion: string | undefined;
|
|
179
|
-
try {
|
|
180
|
-
const text = await readFile(pluginJsonPath, 'utf8');
|
|
181
|
-
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
182
|
-
pluginVersion = typeof parsed.version === 'string' ? parsed.version : undefined;
|
|
183
|
-
} catch {
|
|
184
|
-
// Non-fatal
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
let driftWarning: string | undefined;
|
|
188
|
-
if (pluginVersion && binaryVersion !== 'dev' && pluginVersion !== binaryVersion) {
|
|
189
|
-
driftWarning = `Plugin files v${pluginVersion}, binary v${binaryVersion} — run onebrain update to sync.`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return { skipped: true, driftWarning };
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Plugin files not present — run vault-sync (non-fatal)
|
|
196
|
-
try {
|
|
197
|
-
await vaultSyncFn(vaultDir, {});
|
|
198
|
-
} catch (err) {
|
|
199
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
200
|
-
process.stderr.write(`init: vault-sync warning: ${msg}\n`);
|
|
201
|
-
return { skipped: false, failed: true };
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return { skipped: false };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Step 5: Register plugin in installed_plugins.json.
|
|
209
|
-
* Skips if a source:marketplace entry already exists.
|
|
210
|
-
* Returns { skipped }.
|
|
211
|
-
*/
|
|
212
|
-
async function registerPlugin(
|
|
213
|
-
vaultDir: string,
|
|
214
|
-
installedPluginsPath: string,
|
|
215
|
-
): Promise<{ skipped: boolean }> {
|
|
216
|
-
// Read existing file
|
|
217
|
-
let data: Record<string, unknown>;
|
|
218
|
-
try {
|
|
219
|
-
const text = await readFile(installedPluginsPath, 'utf8');
|
|
220
|
-
data = JSON.parse(text) as Record<string, unknown>;
|
|
221
|
-
} catch {
|
|
222
|
-
data = { plugins: {} };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const plugins = (data.plugins ?? {}) as Record<string, unknown[]>;
|
|
226
|
-
data.plugins = plugins;
|
|
227
|
-
|
|
228
|
-
// Check if any onebrain@ key has a marketplace entry
|
|
229
|
-
const hasMarketplace = Object.keys(plugins)
|
|
230
|
-
.filter((k) => k.startsWith('onebrain@'))
|
|
231
|
-
.some((k) => {
|
|
232
|
-
const entries = plugins[k] as Array<Record<string, unknown>>;
|
|
233
|
-
return entries.some((e) => e.source === 'marketplace');
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
if (hasMarketplace) {
|
|
237
|
-
return { skipped: true };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Read plugin version from .claude-plugin/plugin.json or plugin.json
|
|
241
|
-
let pluginVersion = '0.0.0';
|
|
242
|
-
const candidatePaths = [
|
|
243
|
-
join(vaultDir, '.claude', 'plugins', 'onebrain', '.claude-plugin', 'plugin.json'),
|
|
244
|
-
join(vaultDir, '.claude', 'plugins', 'onebrain', 'plugin.json'),
|
|
245
|
-
];
|
|
246
|
-
for (const p of candidatePaths) {
|
|
247
|
-
try {
|
|
248
|
-
const text = await readFile(p, 'utf8');
|
|
249
|
-
const parsed = JSON.parse(text) as Record<string, unknown>;
|
|
250
|
-
if (typeof parsed.version === 'string') {
|
|
251
|
-
pluginVersion = parsed.version;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
} catch {
|
|
255
|
-
// Try next
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const installPath = join(vaultDir, '.claude', 'plugins', 'onebrain');
|
|
260
|
-
const key = `onebrain@${pluginVersion}`;
|
|
261
|
-
|
|
262
|
-
// Upsert entry
|
|
263
|
-
if (!plugins[key]) {
|
|
264
|
-
plugins[key] = [];
|
|
265
|
-
}
|
|
266
|
-
const entries = plugins[key] as Array<Record<string, unknown>>;
|
|
267
|
-
const existingIdx = entries.findIndex((e) => e.source !== 'marketplace');
|
|
268
|
-
|
|
269
|
-
if (existingIdx >= 0) {
|
|
270
|
-
entries[existingIdx].installPath = installPath;
|
|
271
|
-
entries[existingIdx].version = pluginVersion;
|
|
272
|
-
} else {
|
|
273
|
-
entries.push({ source: 'local', installPath, version: pluginVersion });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Write atomically
|
|
277
|
-
const tmpPath = `${installedPluginsPath}.tmp`;
|
|
278
|
-
try {
|
|
279
|
-
await mkdir(dirname(installedPluginsPath), { recursive: true });
|
|
280
|
-
await writeFile(tmpPath, JSON.stringify(data, null, 4), 'utf8');
|
|
281
|
-
await rename(tmpPath, installedPluginsPath);
|
|
282
|
-
} catch (err) {
|
|
283
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
284
|
-
process.stderr.write(`init: plugin registration warning: ${msg}\n`);
|
|
285
|
-
return { skipped: false };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return { skipped: false };
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
// Main runInit
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
export async function runInit(opts: InitOptions = {}): Promise<InitResult> {
|
|
296
|
-
const vaultDir = opts.vaultDir ?? process.cwd();
|
|
297
|
-
const isTTY = opts.isTTY ?? process.stdout.isTTY ?? false;
|
|
298
|
-
const force = opts.force ?? false;
|
|
299
|
-
const installedPluginsPath =
|
|
300
|
-
opts.installedPluginsPath ?? join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
301
|
-
|
|
302
|
-
// Injectable dependencies (real implementations lazy-loaded)
|
|
303
|
-
const vaultSyncFn =
|
|
304
|
-
opts.vaultSyncFn ??
|
|
305
|
-
(async (dir: string, syncOpts: Record<string, unknown>) => {
|
|
306
|
-
const { vaultSyncCommand } = await import('../internal/vault-sync.js');
|
|
307
|
-
await vaultSyncCommand(dir, syncOpts);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
const registerHooksFn =
|
|
311
|
-
opts.registerHooksFn ??
|
|
312
|
-
(async (dir: string) => {
|
|
313
|
-
const { runRegisterHooks } = await import('../internal/register-hooks.js');
|
|
314
|
-
await runRegisterHooks({ vaultDir: dir });
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
const result: InitResult = {
|
|
318
|
-
ok: false,
|
|
319
|
-
exitCode: 0,
|
|
320
|
-
foldersCreated: 0,
|
|
321
|
-
harness: 'direct',
|
|
322
|
-
pluginSkipped: false,
|
|
323
|
-
pluginRegistrationSkipped: false,
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
// Output helpers
|
|
327
|
-
function writeLine(msg: string) {
|
|
328
|
-
process.stdout.write(`${msg}\n`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function noteStep(step: string, detail: string) {
|
|
332
|
-
if (isTTY) {
|
|
333
|
-
log.step(`${step}\n│ ${detail}`);
|
|
334
|
-
} else {
|
|
335
|
-
writeLine(`${step}: ${detail}`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function noteInfo(msg: string) {
|
|
340
|
-
if (isTTY) {
|
|
341
|
-
log.info(msg);
|
|
342
|
-
} else {
|
|
343
|
-
writeLine(msg);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// ── Step 1: Detect existing vault.yml ─────────────────────────────────────
|
|
348
|
-
|
|
349
|
-
const vaultYmlPath = join(vaultDir, 'vault.yml');
|
|
350
|
-
const vaultYmlExists = await pathExists(vaultYmlPath);
|
|
351
|
-
|
|
352
|
-
if (vaultYmlExists && !force) {
|
|
353
|
-
if (!isTTY) {
|
|
354
|
-
const msg = 'vault.yml exists. Re-run with --force to overwrite.';
|
|
355
|
-
process.stdout.write(`${msg}\n`);
|
|
356
|
-
result.message = msg;
|
|
357
|
-
result.exitCode = 1;
|
|
358
|
-
return result;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// TTY: prompt user
|
|
362
|
-
if (isTTY) {
|
|
363
|
-
intro('OneBrain Init');
|
|
364
|
-
const overwrite = await confirm({
|
|
365
|
-
message: 'vault.yml already exists. Overwrite?',
|
|
366
|
-
});
|
|
367
|
-
if (!overwrite || overwrite === Symbol.for('clack:cancel')) {
|
|
368
|
-
result.ok = true;
|
|
369
|
-
result.exitCode = 0;
|
|
370
|
-
return result;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
} else if (isTTY && !vaultYmlExists) {
|
|
374
|
-
intro('OneBrain Init');
|
|
375
|
-
log.message('');
|
|
376
|
-
} else if (isTTY && force) {
|
|
377
|
-
intro('OneBrain Init');
|
|
378
|
-
log.message('');
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Non-TTY header (TTY uses intro() above)
|
|
382
|
-
if (!isTTY) {
|
|
383
|
-
writeLine('OneBrain Init');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ── Step 2: Create standard folders ───────────────────────────────────────
|
|
387
|
-
|
|
388
|
-
const foldersCreated = await createFolders(vaultDir);
|
|
389
|
-
result.foldersCreated = foldersCreated;
|
|
390
|
-
noteStep(
|
|
391
|
-
'Creating vault structure',
|
|
392
|
-
`${foldersCreated} folder${foldersCreated !== 1 ? 's' : ''} created`,
|
|
393
|
-
);
|
|
394
|
-
|
|
395
|
-
// ── Step 3: Write vault.yml ────────────────────────────────────────────────
|
|
396
|
-
|
|
397
|
-
const harness = opts.harness ?? (await detectHarness(vaultDir));
|
|
398
|
-
result.harness = harness;
|
|
399
|
-
await writeVaultYml(vaultDir, harness);
|
|
400
|
-
noteStep('Writing vault.yml', `harness: ${harness}`);
|
|
401
|
-
|
|
402
|
-
// ── Step 4: Download plugin files ─────────────────────────────────────────
|
|
403
|
-
|
|
404
|
-
const {
|
|
405
|
-
skipped: pluginSkipped,
|
|
406
|
-
driftWarning,
|
|
407
|
-
failed: pluginDownloadFailed,
|
|
408
|
-
} = await downloadPluginFiles(vaultDir, vaultSyncFn);
|
|
409
|
-
result.pluginSkipped = pluginSkipped;
|
|
410
|
-
|
|
411
|
-
if (pluginDownloadFailed) {
|
|
412
|
-
noteInfo('vault-sync failed — run onebrain update to download plugin files');
|
|
413
|
-
} else if (driftWarning) {
|
|
414
|
-
noteInfo(driftWarning);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ── Step 5: Register plugin ────────────────────────────────────────────────
|
|
418
|
-
|
|
419
|
-
const { skipped: pluginRegistrationSkipped } = await registerPlugin(
|
|
420
|
-
vaultDir,
|
|
421
|
-
installedPluginsPath,
|
|
422
|
-
);
|
|
423
|
-
result.pluginRegistrationSkipped = pluginRegistrationSkipped;
|
|
424
|
-
noteStep(
|
|
425
|
-
'Registering plugin',
|
|
426
|
-
`installed_plugins.json: ${pluginRegistrationSkipped ? 'skipped (marketplace)' : '✓'}`,
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
// ── Step 6: Register hooks ─────────────────────────────────────────────────
|
|
430
|
-
|
|
431
|
-
let hooksOk = true;
|
|
432
|
-
try {
|
|
433
|
-
await registerHooksFn(vaultDir);
|
|
434
|
-
} catch (err) {
|
|
435
|
-
hooksOk = false;
|
|
436
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
437
|
-
process.stderr.write(`init: register-hooks warning: ${msg}\n`);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const hooksLine = hooksOk ? 'ok' : 'warning — hooks not registered; run onebrain update';
|
|
441
|
-
if (isTTY) {
|
|
442
|
-
log.step(`Registering hooks\n│ ${hooksLine}`);
|
|
443
|
-
} else {
|
|
444
|
-
writeLine(`hooks: ${hooksLine}`);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// ── Done ──────────────────────────────────────────────────────────────────
|
|
448
|
-
|
|
449
|
-
result.ok = true;
|
|
450
|
-
result.exitCode = 0;
|
|
451
|
-
|
|
452
|
-
const doneMsg = 'run /onboarding in Claude to finish setup';
|
|
453
|
-
if (isTTY) {
|
|
454
|
-
outro(`Done — ${doneMsg}`);
|
|
455
|
-
} else {
|
|
456
|
-
writeLine(`done: ${doneMsg}`);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// ---------------------------------------------------------------------------
|
|
463
|
-
// CLI entry point (called from index.ts)
|
|
464
|
-
// ---------------------------------------------------------------------------
|
|
465
|
-
|
|
466
|
-
export interface InitCommandOptions {
|
|
467
|
-
vaultDir?: string;
|
|
468
|
-
harness?: 'claude-code' | 'gemini' | 'direct';
|
|
469
|
-
force?: boolean;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
export async function initCommand(opts: InitCommandOptions = {}): Promise<void> {
|
|
473
|
-
const result = await runInit(opts);
|
|
474
|
-
if (!result.ok) {
|
|
475
|
-
process.exit(result.exitCode || 1);
|
|
476
|
-
}
|
|
477
|
-
}
|