@onebrain-ai/cli 2.0.0 → 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.
@@ -1,318 +0,0 @@
1
- /**
2
- * Unit tests for `onebrain init`
3
- *
4
- * Tests run against a temp vault dir. Process TTY is always false in test
5
- * (piped stdout), so all non-TTY paths are exercised directly.
6
- *
7
- * Vault-sync and register-hooks are mocked so tests stay offline and fast.
8
- */
9
-
10
- import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
11
- import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
12
- import { tmpdir } from 'node:os';
13
- import { join } from 'node:path';
14
-
15
- import { type InitOptions, runInit } from './init.js';
16
-
17
- // ---------------------------------------------------------------------------
18
- // Helpers
19
- // ---------------------------------------------------------------------------
20
-
21
- async function makeTempVault(): Promise<string> {
22
- const dir = join(
23
- tmpdir(),
24
- `onebrain-init-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
25
- );
26
- await mkdir(dir, { recursive: true });
27
- return dir;
28
- }
29
-
30
- async function fileExists(path: string): Promise<boolean> {
31
- try {
32
- await stat(path);
33
- return true;
34
- } catch {
35
- return false;
36
- }
37
- }
38
-
39
- async function readVaultYml(vaultDir: string): Promise<Record<string, unknown>> {
40
- const { parse } = await import('yaml');
41
- const text = await readFile(join(vaultDir, 'vault.yml'), 'utf8');
42
- return (parse(text) ?? {}) as Record<string, unknown>;
43
- }
44
-
45
- // Noop mocks — capture calls but do nothing
46
- const noopVaultSync = async (_vaultDir: string, _opts: Record<string, unknown>) => {
47
- // no-op
48
- };
49
-
50
- const noopRegisterHooks = async (_vaultDir: string) => {
51
- // no-op
52
- };
53
-
54
- let tempDir: string;
55
-
56
- beforeEach(async () => {
57
- tempDir = await makeTempVault();
58
- });
59
-
60
- afterEach(async () => {
61
- await rm(tempDir, { recursive: true, force: true });
62
- // biome-ignore lint/performance/noDelete: env cleanup requires delete to unset, not assign "undefined"
63
- delete process.env.CLAUDE_CODE_HARNESS;
64
- });
65
-
66
- // ---------------------------------------------------------------------------
67
- // Tests
68
- // ---------------------------------------------------------------------------
69
-
70
- describe('runInit', () => {
71
- it('fresh vault — creates all standard folders + vault.yml + calls vault-sync', async () => {
72
- let vaultSyncCalled = false;
73
- const mockVaultSync = async (_vaultDir: string, _opts: Record<string, unknown>) => {
74
- vaultSyncCalled = true;
75
- };
76
-
77
- const opts: InitOptions = {
78
- vaultDir: tempDir,
79
- vaultSyncFn: mockVaultSync,
80
- registerHooksFn: noopRegisterHooks,
81
- };
82
-
83
- const result = await runInit(opts);
84
-
85
- expect(result.ok).toBe(true);
86
-
87
- // All 8 standard folders should exist
88
- const folders = [
89
- '00-inbox',
90
- '01-projects',
91
- '02-areas',
92
- '03-knowledge',
93
- '04-resources',
94
- '05-agent',
95
- '06-archive',
96
- '07-logs',
97
- ];
98
- for (const folder of folders) {
99
- expect(await fileExists(join(tempDir, folder))).toBe(true);
100
- }
101
-
102
- // imports subdirectory inside inbox
103
- expect(await fileExists(join(tempDir, '00-inbox', 'imports'))).toBe(true);
104
-
105
- // vault.yml written
106
- expect(await fileExists(join(tempDir, 'vault.yml'))).toBe(true);
107
- const vaultYml = await readVaultYml(tempDir);
108
- expect(vaultYml.method).toBe('onebrain');
109
- expect(vaultYml.update_channel).toBe('stable');
110
-
111
- // folders count: 8 standard + inbox/imports = 9 total
112
- expect(result.foldersCreated).toBe(9);
113
-
114
- // vault-sync was called (plugin files not present)
115
- expect(vaultSyncCalled).toBe(true);
116
- });
117
-
118
- it('existing vault.yml in non-TTY → exit 1 with message', async () => {
119
- await writeFile(join(tempDir, 'vault.yml'), 'method: onebrain\n', 'utf8');
120
-
121
- const opts: InitOptions = {
122
- vaultDir: tempDir,
123
- isTTY: false,
124
- vaultSyncFn: noopVaultSync,
125
- registerHooksFn: noopRegisterHooks,
126
- };
127
-
128
- const result = await runInit(opts);
129
-
130
- expect(result.ok).toBe(false);
131
- expect(result.exitCode).toBe(1);
132
- expect(result.message).toContain('vault.yml exists');
133
- expect(result.message).toContain('--force');
134
- });
135
-
136
- it('--force overwrites existing vault.yml', async () => {
137
- await writeFile(join(tempDir, 'vault.yml'), 'method: legacy\n', 'utf8');
138
-
139
- const opts: InitOptions = {
140
- vaultDir: tempDir,
141
- force: true,
142
- vaultSyncFn: noopVaultSync,
143
- registerHooksFn: noopRegisterHooks,
144
- };
145
-
146
- const result = await runInit(opts);
147
-
148
- expect(result.ok).toBe(true);
149
- const vaultYml = await readVaultYml(tempDir);
150
- expect(vaultYml.method).toBe('onebrain');
151
- });
152
-
153
- it('plugin files already present — skips vault-sync download', async () => {
154
- // Pre-create plugin.json to simulate existing plugin files
155
- const pluginMetaDir = join(tempDir, '.claude', 'plugins', 'onebrain', '.claude-plugin');
156
- await mkdir(pluginMetaDir, { recursive: true });
157
- await writeFile(
158
- join(pluginMetaDir, 'plugin.json'),
159
- JSON.stringify({ version: '1.11.0' }),
160
- 'utf8',
161
- );
162
-
163
- let vaultSyncCalled = false;
164
- const mockVaultSync = async (_vaultDir: string, _opts: Record<string, unknown>) => {
165
- vaultSyncCalled = true;
166
- };
167
-
168
- const opts: InitOptions = {
169
- vaultDir: tempDir,
170
- vaultSyncFn: mockVaultSync,
171
- registerHooksFn: noopRegisterHooks,
172
- };
173
-
174
- const result = await runInit(opts);
175
-
176
- expect(result.ok).toBe(true);
177
- expect(vaultSyncCalled).toBe(false);
178
- expect(result.pluginSkipped).toBe(true);
179
- });
180
-
181
- it('source:marketplace in installed_plugins.json — skips plugin registration', async () => {
182
- // Create installed_plugins.json with marketplace entry
183
- const pluginsDir = join(tempDir, '.claude-meta');
184
- await mkdir(pluginsDir, { recursive: true });
185
- const installedPluginsPath = join(pluginsDir, 'installed_plugins.json');
186
- await writeFile(
187
- installedPluginsPath,
188
- JSON.stringify({
189
- plugins: {
190
- 'onebrain@1.0.0': [{ source: 'marketplace', installPath: '/some/path' }],
191
- },
192
- }),
193
- 'utf8',
194
- );
195
-
196
- const opts: InitOptions = {
197
- vaultDir: tempDir,
198
- vaultSyncFn: noopVaultSync,
199
- registerHooksFn: noopRegisterHooks,
200
- installedPluginsPath,
201
- };
202
-
203
- const result = await runInit(opts);
204
-
205
- expect(result.ok).toBe(true);
206
- expect(result.pluginRegistrationSkipped).toBe(true);
207
- });
208
-
209
- it('harness auto-detect: .claude/ dir present → claude-code', async () => {
210
- await mkdir(join(tempDir, '.claude'), { recursive: true });
211
-
212
- const opts: InitOptions = {
213
- vaultDir: tempDir,
214
- vaultSyncFn: noopVaultSync,
215
- registerHooksFn: noopRegisterHooks,
216
- };
217
-
218
- const result = await runInit(opts);
219
-
220
- expect(result.ok).toBe(true);
221
- const vaultYml = await readVaultYml(tempDir);
222
- const runtime = vaultYml.runtime as Record<string, unknown> | undefined;
223
- expect(runtime?.harness).toBe('claude-code');
224
- });
225
-
226
- it('harness auto-detect: no .claude/ dir → direct', async () => {
227
- // Fresh vault, no .claude/ dir
228
- const opts: InitOptions = {
229
- vaultDir: tempDir,
230
- vaultSyncFn: noopVaultSync,
231
- registerHooksFn: noopRegisterHooks,
232
- };
233
-
234
- const result = await runInit(opts);
235
-
236
- expect(result.ok).toBe(true);
237
- const vaultYml = await readVaultYml(tempDir);
238
- const runtime = vaultYml.runtime as Record<string, unknown> | undefined;
239
- expect(runtime?.harness).toBe('direct');
240
- });
241
-
242
- it('--harness flag overrides auto-detect', async () => {
243
- const opts: InitOptions = {
244
- vaultDir: tempDir,
245
- harness: 'gemini',
246
- vaultSyncFn: noopVaultSync,
247
- registerHooksFn: noopRegisterHooks,
248
- };
249
-
250
- const result = await runInit(opts);
251
-
252
- expect(result.ok).toBe(true);
253
- const vaultYml = await readVaultYml(tempDir);
254
- const runtime = vaultYml.runtime as Record<string, unknown> | undefined;
255
- expect(runtime?.harness).toBe('gemini');
256
- });
257
-
258
- it('existing folders not double-counted in foldersCreated', async () => {
259
- // Pre-create some folders
260
- await mkdir(join(tempDir, '00-inbox'), { recursive: true });
261
- await mkdir(join(tempDir, '01-projects'), { recursive: true });
262
-
263
- const opts: InitOptions = {
264
- vaultDir: tempDir,
265
- vaultSyncFn: noopVaultSync,
266
- registerHooksFn: noopRegisterHooks,
267
- };
268
-
269
- const result = await runInit(opts);
270
-
271
- expect(result.ok).toBe(true);
272
- // 9 total (8 standard + imports), 2 already exist → 7 created
273
- expect(result.foldersCreated).toBe(7);
274
- });
275
-
276
- it('non-TTY output starts with OneBrain Init header', async () => {
277
- const lines: string[] = [];
278
- const originalWrite = process.stdout.write.bind(process.stdout);
279
- process.stdout.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
280
- if (typeof chunk === 'string') lines.push(chunk);
281
- return originalWrite(chunk, ...(args as Parameters<typeof originalWrite>).slice(1));
282
- };
283
-
284
- try {
285
- const opts: InitOptions = {
286
- vaultDir: tempDir,
287
- isTTY: false,
288
- vaultSyncFn: noopVaultSync,
289
- registerHooksFn: noopRegisterHooks,
290
- };
291
- const result = await runInit(opts);
292
- expect(result.ok).toBe(true);
293
- } finally {
294
- process.stdout.write = originalWrite;
295
- }
296
-
297
- const fullOutput = lines.join('');
298
- expect(fullOutput).toMatch(/^OneBrain Init\n/);
299
- });
300
-
301
- it('harness auto-detect: CLAUDE_CODE_HARNESS env → uses env value', async () => {
302
- process.env.CLAUDE_CODE_HARNESS = 'gemini';
303
-
304
- const opts: InitOptions = {
305
- vaultDir: tempDir,
306
- vaultSyncFn: noopVaultSync,
307
- registerHooksFn: noopRegisterHooks,
308
- };
309
-
310
- const result = await runInit(opts);
311
-
312
- expect(result.ok).toBe(true);
313
- expect(result.harness).toBe('gemini');
314
- const vaultYml = await readVaultYml(tempDir);
315
- const runtime = vaultYml.runtime as Record<string, unknown> | undefined;
316
- expect(runtime?.harness).toBe('gemini');
317
- });
318
- });