@onebrain-ai/cli 2.0.1 → 2.0.3
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/README.md +48 -0
- 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/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @onebrain-ai/cli
|
|
2
|
+
|
|
3
|
+
The CLI binary for [OneBrain](https://github.com/kengio/onebrain) — a personal AI OS built on Obsidian with persistent memory, 24+ skills, and Claude Code integration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# with bun (recommended)
|
|
9
|
+
bun install -g @onebrain-ai/cli
|
|
10
|
+
|
|
11
|
+
# or with npm
|
|
12
|
+
npm install -g @onebrain-ai/cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Verify: `onebrain --version`
|
|
16
|
+
|
|
17
|
+
## What it does
|
|
18
|
+
|
|
19
|
+
The `onebrain` binary handles the low-level operations that keep your vault running.
|
|
20
|
+
|
|
21
|
+
**User-facing commands:**
|
|
22
|
+
|
|
23
|
+
| Command | Purpose |
|
|
24
|
+
|---------|---------|
|
|
25
|
+
| `onebrain init` | First-time vault initialization |
|
|
26
|
+
| `onebrain update` | Pull latest plugin files from GitHub |
|
|
27
|
+
| `onebrain doctor` | Audit vault health — orphan checkpoints, version drift, qmd embedding status, missing config |
|
|
28
|
+
| `onebrain help` | List all available commands |
|
|
29
|
+
|
|
30
|
+
**Internal commands** (not meant to be run directly):
|
|
31
|
+
|
|
32
|
+
`session-init` · `orphan-scan` · `checkpoint` · `qmd-reindex` · `vault-sync` · `register-hooks` · `migrate`
|
|
33
|
+
|
|
34
|
+
## Requirements
|
|
35
|
+
|
|
36
|
+
- macOS, Linux, or Windows (Git Bash)
|
|
37
|
+
- Bun or Node.js required (used as the runtime for the npm package)
|
|
38
|
+
- For a self-contained binary with no runtime dependency, download from [GitHub Releases](https://github.com/kengio/onebrain/releases)
|
|
39
|
+
|
|
40
|
+
## OneBrain
|
|
41
|
+
|
|
42
|
+
OneBrain gives your AI agent persistent memory across sessions, a structured Markdown vault, and 24+ pre-built skills — so every session picks up exactly where the last one left off.
|
|
43
|
+
|
|
44
|
+
**Full documentation and vault setup:** [github.com/kengio/onebrain](https://github.com/kengio/onebrain)
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
[MIT](https://github.com/kengio/onebrain/blob/main/LICENSE)
|
package/dist/onebrain
CHANGED
|
@@ -10742,7 +10742,7 @@ init_dist();
|
|
|
10742
10742
|
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, stat as stat3, writeFile as writeFile3 } from "node:fs/promises";
|
|
10743
10743
|
import { homedir as homedir3 } from "node:os";
|
|
10744
10744
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
10745
|
-
var binaryVersion =
|
|
10745
|
+
var binaryVersion = "2.0.3";
|
|
10746
10746
|
var STANDARD_FOLDERS = [
|
|
10747
10747
|
"00-inbox",
|
|
10748
10748
|
"01-projects",
|
|
@@ -12598,8 +12598,8 @@ async function vaultSyncCommand2(vaultRoot, opts = {}) {
|
|
|
12598
12598
|
}
|
|
12599
12599
|
|
|
12600
12600
|
// src/index.ts
|
|
12601
|
-
var VERSION =
|
|
12602
|
-
var RELEASE_DATE =
|
|
12601
|
+
var VERSION = "2.0.3";
|
|
12602
|
+
var RELEASE_DATE = "2026-04-25";
|
|
12603
12603
|
var VERSION_STRING = `OneBrain v${VERSION} \u2014 released ${RELEASE_DATE}`;
|
|
12604
12604
|
if (process.argv.slice(2).length === 0) {
|
|
12605
12605
|
console.log(VERSION_STRING);
|
package/package.json
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebrain-ai/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
|
+
"description": "CLI for OneBrain — personal AI OS for Obsidian with persistent memory, 24+ skills, and Claude Code integration",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"onebrain",
|
|
7
|
+
"obsidian",
|
|
8
|
+
"ai",
|
|
9
|
+
"cli",
|
|
10
|
+
"memory",
|
|
11
|
+
"knowledge-management",
|
|
12
|
+
"claude",
|
|
13
|
+
"agent",
|
|
14
|
+
"pkm",
|
|
15
|
+
"productivity",
|
|
16
|
+
"vault"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://github.com/kengio/onebrain",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/kengio/onebrain.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": "https://github.com/kengio/onebrain/issues",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"files": ["dist/onebrain"],
|
|
4
26
|
"type": "module",
|
|
5
27
|
"bin": {
|
|
6
28
|
"onebrain": "dist/onebrain"
|
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `onebrain doctor` — runDoctor()
|
|
3
|
-
*
|
|
4
|
-
* All @onebrain/core validators are injected via opts so tests are
|
|
5
|
-
* fast, offline, and deterministic. No mock.module needed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { afterEach, beforeEach, describe, expect, it, spyOn } from 'bun:test';
|
|
9
|
-
import { mkdir, rm } from 'node:fs/promises';
|
|
10
|
-
import { tmpdir } from 'node:os';
|
|
11
|
-
import { join } from 'node:path';
|
|
12
|
-
|
|
13
|
-
import type { VaultConfig } from '@onebrain/core';
|
|
14
|
-
import { type DoctorOptions, runDoctor } from './doctor.js';
|
|
15
|
-
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Helpers
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
async function makeTempVault(): Promise<string> {
|
|
21
|
-
const dir = join(
|
|
22
|
-
tmpdir(),
|
|
23
|
-
`onebrain-doctor-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
24
|
-
);
|
|
25
|
-
await mkdir(dir, { recursive: true });
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const DEFAULT_CONFIG: VaultConfig = {
|
|
30
|
-
folders: {
|
|
31
|
-
inbox: '00-inbox',
|
|
32
|
-
projects: '01-projects',
|
|
33
|
-
areas: '02-areas',
|
|
34
|
-
knowledge: '03-knowledge',
|
|
35
|
-
resources: '04-resources',
|
|
36
|
-
agent: '05-agent',
|
|
37
|
-
archive: '06-archive',
|
|
38
|
-
logs: '07-logs',
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function makeAllOkValidators(): Required<
|
|
43
|
-
Pick<
|
|
44
|
-
DoctorOptions,
|
|
45
|
-
| 'checkVaultYmlFn'
|
|
46
|
-
| 'loadVaultConfigFn'
|
|
47
|
-
| 'checkFoldersFn'
|
|
48
|
-
| 'checkHarnessBinaryFn'
|
|
49
|
-
| 'checkQmdEmbeddingsFn'
|
|
50
|
-
| 'checkVersionDriftFn'
|
|
51
|
-
| 'checkOrphanCheckpointsFn'
|
|
52
|
-
| 'checkSandboxFn'
|
|
53
|
-
>
|
|
54
|
-
> {
|
|
55
|
-
return {
|
|
56
|
-
checkVaultYmlFn: async () => ({ check: 'vault.yml', status: 'ok', message: 'valid' }),
|
|
57
|
-
loadVaultConfigFn: async () => DEFAULT_CONFIG,
|
|
58
|
-
checkFoldersFn: async () => ({ check: 'folders', status: 'ok', message: '8/8 present' }),
|
|
59
|
-
checkHarnessBinaryFn: async () => ({
|
|
60
|
-
check: 'runtime.harness',
|
|
61
|
-
status: 'ok',
|
|
62
|
-
message: 'claude-code (found)',
|
|
63
|
-
}),
|
|
64
|
-
checkQmdEmbeddingsFn: async () => ({
|
|
65
|
-
check: 'qmd-embeddings',
|
|
66
|
-
status: 'ok',
|
|
67
|
-
message: 'all embedded',
|
|
68
|
-
}),
|
|
69
|
-
checkVersionDriftFn: async () => ({ check: 'version-drift', status: 'ok', message: 'v1.0.0' }),
|
|
70
|
-
checkOrphanCheckpointsFn: async () => ({
|
|
71
|
-
check: 'orphan-checkpoints',
|
|
72
|
-
status: 'ok',
|
|
73
|
-
message: '0 orphans',
|
|
74
|
-
}),
|
|
75
|
-
checkSandboxFn: async () => ({ check: 'sandbox', status: 'ok', message: 'enabled' }),
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let tempDir: string;
|
|
80
|
-
|
|
81
|
-
beforeEach(async () => {
|
|
82
|
-
tempDir = await makeTempVault();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
afterEach(async () => {
|
|
86
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
// Tests
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
|
|
93
|
-
describe('runDoctor', () => {
|
|
94
|
-
// ── Exit codes ─────────────────────────────────────────────────────────────
|
|
95
|
-
|
|
96
|
-
describe('exit codes', () => {
|
|
97
|
-
it('returns exitCode 1 when any check returns status error', async () => {
|
|
98
|
-
const validators = makeAllOkValidators();
|
|
99
|
-
validators.checkVaultYmlFn = async () => ({
|
|
100
|
-
check: 'vault.yml',
|
|
101
|
-
status: 'error',
|
|
102
|
-
message: 'not found',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
106
|
-
|
|
107
|
-
expect(result.exitCode).toBe(1);
|
|
108
|
-
expect(result.ok).toBe(false);
|
|
109
|
-
expect(result.errorCount).toBeGreaterThanOrEqual(1);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('returns exitCode 0 when checks return only warnings (no errors)', async () => {
|
|
113
|
-
const validators = makeAllOkValidators();
|
|
114
|
-
validators.checkFoldersFn = async () => ({
|
|
115
|
-
check: 'folders',
|
|
116
|
-
status: 'warn',
|
|
117
|
-
message: '7/8 present',
|
|
118
|
-
});
|
|
119
|
-
validators.checkSandboxFn = async () => ({
|
|
120
|
-
check: 'sandbox',
|
|
121
|
-
status: 'warn',
|
|
122
|
-
message: 'disabled',
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
126
|
-
|
|
127
|
-
expect(result.exitCode).toBe(0);
|
|
128
|
-
expect(result.ok).toBe(true);
|
|
129
|
-
expect(result.warningCount).toBeGreaterThanOrEqual(2);
|
|
130
|
-
expect(result.errorCount).toBe(0);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('returns exitCode 0 when all checks pass', async () => {
|
|
134
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...makeAllOkValidators() });
|
|
135
|
-
|
|
136
|
-
expect(result.exitCode).toBe(0);
|
|
137
|
-
expect(result.ok).toBe(true);
|
|
138
|
-
expect(result.errorCount).toBe(0);
|
|
139
|
-
expect(result.warningCount).toBe(0);
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// ── binaryVersion forwarding ───────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
describe('binaryVersion forwarding', () => {
|
|
146
|
-
it('forwards binaryVersion to checkVersionDriftFn when provided', async () => {
|
|
147
|
-
let capturedBinaryVersion: string | undefined = 'not-set';
|
|
148
|
-
const validators = makeAllOkValidators();
|
|
149
|
-
validators.checkVersionDriftFn = async (_vaultDir, _config, bv) => {
|
|
150
|
-
capturedBinaryVersion = bv;
|
|
151
|
-
return { check: 'version-drift', status: 'ok', message: 'ok' };
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, binaryVersion: 'v2.0.0', ...validators });
|
|
155
|
-
|
|
156
|
-
expect(capturedBinaryVersion).toBe('v2.0.0');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('passes undefined binaryVersion to checkVersionDriftFn when omitted', async () => {
|
|
160
|
-
let capturedBinaryVersion: string | undefined = 'not-set';
|
|
161
|
-
const validators = makeAllOkValidators();
|
|
162
|
-
validators.checkVersionDriftFn = async (_vaultDir, _config, bv) => {
|
|
163
|
-
capturedBinaryVersion = bv;
|
|
164
|
-
return { check: 'version-drift', status: 'ok', message: 'ok' };
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
168
|
-
|
|
169
|
-
expect(capturedBinaryVersion).toBeUndefined();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ── Summary line selection ─────────────────────────────────────────────────
|
|
174
|
-
|
|
175
|
-
describe('summary line selection', () => {
|
|
176
|
-
it('shows "N errors, N warnings" when both errors and warnings exist', async () => {
|
|
177
|
-
const logLines: string[] = [];
|
|
178
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
179
|
-
logLines.push(msg);
|
|
180
|
-
});
|
|
181
|
-
try {
|
|
182
|
-
const validators = makeAllOkValidators();
|
|
183
|
-
validators.checkVaultYmlFn = async () => ({
|
|
184
|
-
check: 'vault.yml',
|
|
185
|
-
status: 'error',
|
|
186
|
-
message: 'not found',
|
|
187
|
-
});
|
|
188
|
-
validators.checkFoldersFn = async () => ({
|
|
189
|
-
check: 'folders',
|
|
190
|
-
status: 'warn',
|
|
191
|
-
message: '7/8 present',
|
|
192
|
-
});
|
|
193
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
194
|
-
} finally {
|
|
195
|
-
spy.mockRestore();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
expect(logLines.join('\n')).toMatch(/Summary: 1 errors, 1 warnings/);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
it('shows "N errors" (no warnings mention) when only errors', async () => {
|
|
202
|
-
const logLines: string[] = [];
|
|
203
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
204
|
-
logLines.push(msg);
|
|
205
|
-
});
|
|
206
|
-
try {
|
|
207
|
-
const validators = makeAllOkValidators();
|
|
208
|
-
validators.checkVaultYmlFn = async () => ({
|
|
209
|
-
check: 'vault.yml',
|
|
210
|
-
status: 'error',
|
|
211
|
-
message: 'not found',
|
|
212
|
-
});
|
|
213
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
214
|
-
} finally {
|
|
215
|
-
spy.mockRestore();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const output = logLines.join('\n');
|
|
219
|
-
expect(output).toMatch(/Summary: 1 errors$/m);
|
|
220
|
-
expect(output).not.toMatch(/warnings/);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('shows "N warnings — ok to run" when only warnings (no errors)', async () => {
|
|
224
|
-
const logLines: string[] = [];
|
|
225
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
226
|
-
logLines.push(msg);
|
|
227
|
-
});
|
|
228
|
-
try {
|
|
229
|
-
const validators = makeAllOkValidators();
|
|
230
|
-
validators.checkSandboxFn = async () => ({
|
|
231
|
-
check: 'sandbox',
|
|
232
|
-
status: 'warn',
|
|
233
|
-
message: 'disabled',
|
|
234
|
-
});
|
|
235
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
236
|
-
} finally {
|
|
237
|
-
spy.mockRestore();
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
expect(logLines.join('\n')).toMatch(/Summary: 1 warnings — ok to run/);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('shows "All checks passed" when no errors or warnings', async () => {
|
|
244
|
-
const logLines: string[] = [];
|
|
245
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
246
|
-
logLines.push(msg);
|
|
247
|
-
});
|
|
248
|
-
try {
|
|
249
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...makeAllOkValidators() });
|
|
250
|
-
} finally {
|
|
251
|
-
spy.mockRestore();
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
expect(logLines.join('\n')).toMatch(/Summary: All checks passed/);
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// ── TTY vs non-TTY output formatting ──────────────────────────────────────
|
|
259
|
-
|
|
260
|
-
describe('TTY vs non-TTY output', () => {
|
|
261
|
-
it('non-TTY: plain title without leading blank line', async () => {
|
|
262
|
-
const logLines: string[] = [];
|
|
263
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
264
|
-
logLines.push(msg);
|
|
265
|
-
});
|
|
266
|
-
try {
|
|
267
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...makeAllOkValidators() });
|
|
268
|
-
} finally {
|
|
269
|
-
spy.mockRestore();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
expect(logLines.join('\n')).toMatch(/^OneBrain Doctor 🔍/);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('TTY: title is padded with surrounding blank lines', async () => {
|
|
276
|
-
const logLines: string[] = [];
|
|
277
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
278
|
-
logLines.push(msg);
|
|
279
|
-
});
|
|
280
|
-
try {
|
|
281
|
-
await runDoctor({ vaultDir: tempDir, isTTY: true, ...makeAllOkValidators() });
|
|
282
|
-
} finally {
|
|
283
|
-
spy.mockRestore();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const output = logLines.join('\n');
|
|
287
|
-
expect(output).toMatch(/^\n\s+OneBrain Doctor 🔍/);
|
|
288
|
-
expect(output).toMatch(/Summary: All checks passed\n$/);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// ── loadVaultConfig failure resilience ────────────────────────────────────
|
|
293
|
-
|
|
294
|
-
describe('loadVaultConfig failure resilience', () => {
|
|
295
|
-
it('continues with default config when loadVaultConfigFn throws after valid vault.yml', async () => {
|
|
296
|
-
let foldersConfigReceived: VaultConfig | undefined;
|
|
297
|
-
const validators = makeAllOkValidators();
|
|
298
|
-
validators.checkVaultYmlFn = async () => ({
|
|
299
|
-
check: 'vault.yml',
|
|
300
|
-
status: 'ok',
|
|
301
|
-
message: 'valid',
|
|
302
|
-
});
|
|
303
|
-
validators.loadVaultConfigFn = async () => {
|
|
304
|
-
throw new Error('parse error');
|
|
305
|
-
};
|
|
306
|
-
validators.checkFoldersFn = async (_vaultDir, config) => {
|
|
307
|
-
foldersConfigReceived = config;
|
|
308
|
-
return { check: 'folders', status: 'ok', message: '8/8 present' };
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
312
|
-
|
|
313
|
-
expect(result.ok).toBe(true);
|
|
314
|
-
expect(result.exitCode).toBe(0);
|
|
315
|
-
expect(foldersConfigReceived?.folders.inbox).toBe('00-inbox');
|
|
316
|
-
expect(foldersConfigReceived?.folders.logs).toBe('07-logs');
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('skips loadVaultConfigFn when checkVaultYml returns error', async () => {
|
|
320
|
-
let loadCalled = false;
|
|
321
|
-
const validators = makeAllOkValidators();
|
|
322
|
-
validators.checkVaultYmlFn = async () => ({
|
|
323
|
-
check: 'vault.yml',
|
|
324
|
-
status: 'error',
|
|
325
|
-
message: 'not found',
|
|
326
|
-
});
|
|
327
|
-
validators.loadVaultConfigFn = async () => {
|
|
328
|
-
loadCalled = true;
|
|
329
|
-
return DEFAULT_CONFIG;
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
333
|
-
|
|
334
|
-
expect(loadCalled).toBe(false);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// ── Hint lines ────────────────────────────────────────────────────────────
|
|
339
|
-
|
|
340
|
-
describe('hint lines', () => {
|
|
341
|
-
it('includes hint line in output when a check returns a hint', async () => {
|
|
342
|
-
const logLines: string[] = [];
|
|
343
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
344
|
-
logLines.push(msg);
|
|
345
|
-
});
|
|
346
|
-
try {
|
|
347
|
-
const validators = makeAllOkValidators();
|
|
348
|
-
validators.checkVaultYmlFn = async () => ({
|
|
349
|
-
check: 'vault.yml',
|
|
350
|
-
status: 'error',
|
|
351
|
-
message: 'vault.yml not found',
|
|
352
|
-
hint: 'Run onebrain init to create vault.yml',
|
|
353
|
-
});
|
|
354
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
355
|
-
} finally {
|
|
356
|
-
spy.mockRestore();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
expect(logLines.join('\n')).toContain('→ Run onebrain init to create vault.yml');
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it('does not include a hint line when check has no hint', async () => {
|
|
363
|
-
const logLines: string[] = [];
|
|
364
|
-
const spy = spyOn(console, 'log').mockImplementation((msg: string) => {
|
|
365
|
-
logLines.push(msg);
|
|
366
|
-
});
|
|
367
|
-
try {
|
|
368
|
-
await runDoctor({ vaultDir: tempDir, isTTY: false, ...makeAllOkValidators() });
|
|
369
|
-
} finally {
|
|
370
|
-
spy.mockRestore();
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
expect(logLines.join('\n')).not.toContain('→');
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
// ── errorCount / warningCount accuracy ────────────────────────────────────
|
|
378
|
-
|
|
379
|
-
describe('result counts', () => {
|
|
380
|
-
it('accurately counts multiple errors and warnings across all checks', async () => {
|
|
381
|
-
const validators = makeAllOkValidators();
|
|
382
|
-
validators.checkVaultYmlFn = async () => ({
|
|
383
|
-
check: 'vault.yml',
|
|
384
|
-
status: 'error',
|
|
385
|
-
message: 'not found',
|
|
386
|
-
});
|
|
387
|
-
validators.checkFoldersFn = async () => ({
|
|
388
|
-
check: 'folders',
|
|
389
|
-
status: 'error',
|
|
390
|
-
message: '0/8 present',
|
|
391
|
-
});
|
|
392
|
-
validators.checkSandboxFn = async () => ({
|
|
393
|
-
check: 'sandbox',
|
|
394
|
-
status: 'warn',
|
|
395
|
-
message: 'disabled',
|
|
396
|
-
});
|
|
397
|
-
validators.checkHarnessBinaryFn = async () => ({
|
|
398
|
-
check: 'runtime.harness',
|
|
399
|
-
status: 'warn',
|
|
400
|
-
message: 'not found',
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...validators });
|
|
404
|
-
|
|
405
|
-
expect(result.errorCount).toBe(2);
|
|
406
|
-
expect(result.warningCount).toBe(2);
|
|
407
|
-
expect(result.exitCode).toBe(1);
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it('returns errorCount 0 and warningCount 0 when all checks pass', async () => {
|
|
411
|
-
const result = await runDoctor({ vaultDir: tempDir, isTTY: false, ...makeAllOkValidators() });
|
|
412
|
-
expect(result.errorCount).toBe(0);
|
|
413
|
-
expect(result.warningCount).toBe(0);
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
});
|