@brainjar/cli 0.2.3 → 0.4.0
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 +11 -10
- package/package.json +2 -2
- package/src/api-types.ts +155 -0
- package/src/cli.ts +5 -3
- package/src/client.ts +157 -0
- package/src/commands/brain.ts +99 -113
- package/src/commands/compose.ts +17 -116
- package/src/commands/init.ts +66 -42
- package/src/commands/migrate.ts +61 -0
- package/src/commands/pack.ts +1 -5
- package/src/commands/persona.ts +97 -145
- package/src/commands/rules.ts +71 -174
- package/src/commands/server.ts +212 -0
- package/src/commands/shell.ts +55 -51
- package/src/commands/soul.ts +75 -110
- package/src/commands/status.ts +37 -78
- package/src/commands/sync.ts +0 -2
- package/src/config.ts +125 -0
- package/src/daemon.ts +404 -0
- package/src/errors.ts +172 -0
- package/src/migrate.ts +247 -0
- package/src/pack.ts +149 -428
- package/src/paths.ts +1 -8
- package/src/seeds.ts +62 -105
- package/src/state.ts +12 -397
- package/src/sync.ts +61 -102
- package/src/version-check.ts +137 -0
- package/src/brain.ts +0 -69
- package/src/commands/identity.ts +0 -276
- package/src/engines/bitwarden.ts +0 -105
- package/src/engines/index.ts +0 -12
- package/src/engines/types.ts +0 -12
- package/src/hooks.test.ts +0 -132
- package/src/pack.test.ts +0 -472
- package/src/seeds/templates/persona.md +0 -19
- package/src/seeds/templates/rule.md +0 -11
- package/src/seeds/templates/soul.md +0 -20
- /package/src/seeds/rules/{default/boundaries.md → boundaries.md} +0 -0
- /package/src/seeds/rules/{default/context-recovery.md → context-recovery.md} +0 -0
- /package/src/seeds/rules/{default/task-completion.md → task-completion.md} +0 -0
package/src/engines/bitwarden.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { $ } from 'bun'
|
|
2
|
-
import { readFile } from 'node:fs/promises'
|
|
3
|
-
import { paths } from '../paths.js'
|
|
4
|
-
import type { CredentialEngine, EngineStatus } from './types.js'
|
|
5
|
-
|
|
6
|
-
export async function loadSession(): Promise<string | null> {
|
|
7
|
-
// Env var takes precedence, then session file
|
|
8
|
-
if (process.env.BW_SESSION) return process.env.BW_SESSION
|
|
9
|
-
try {
|
|
10
|
-
const session = (await readFile(paths.session, 'utf-8')).trim()
|
|
11
|
-
return session || null
|
|
12
|
-
} catch {
|
|
13
|
-
return null
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Thin shell wrapper — extracted so tests can replace it via spyOn. */
|
|
18
|
-
export const bw = {
|
|
19
|
-
async whichBw(): Promise<void> {
|
|
20
|
-
await $`which bw`.quiet()
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
async status(session: string | null): Promise<any> {
|
|
24
|
-
return session
|
|
25
|
-
? $`bw status`.env({ ...process.env, BW_SESSION: session }).json()
|
|
26
|
-
: $`bw status`.json()
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
async getItem(item: string, session: string): Promise<any> {
|
|
30
|
-
return $`bw get item ${item}`.env({ ...process.env, BW_SESSION: session }).json()
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
async lock(): Promise<void> {
|
|
34
|
-
await $`bw lock`.quiet()
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export const bitwarden: CredentialEngine = {
|
|
39
|
-
name: 'bitwarden',
|
|
40
|
-
|
|
41
|
-
async status(): Promise<EngineStatus> {
|
|
42
|
-
try {
|
|
43
|
-
await bw.whichBw()
|
|
44
|
-
} catch {
|
|
45
|
-
return { state: 'not_installed', install: 'npm install -g @bitwarden/cli' }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const session = await loadSession()
|
|
50
|
-
const result = await bw.status(session)
|
|
51
|
-
|
|
52
|
-
if (result.status === 'unauthenticated') {
|
|
53
|
-
return {
|
|
54
|
-
state: 'unauthenticated',
|
|
55
|
-
operator_action: `bw login ${result.userEmail ?? '<email>'}`,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (result.status === 'unlocked' && session) {
|
|
60
|
-
return { state: 'unlocked', session }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
state: 'locked',
|
|
65
|
-
operator_action: 'Run `bw unlock` and then `brainjar identity unlock <session>`',
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
return {
|
|
69
|
-
state: 'locked',
|
|
70
|
-
operator_action: 'Could not determine vault status. Run `bw unlock` and then `brainjar identity unlock <session>`',
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
|
|
75
|
-
async get(item: string, session: string) {
|
|
76
|
-
if (!item || item.length > 256 || /[\x00-\x1f]/.test(item)) {
|
|
77
|
-
return { error: `Invalid item name: "${item}"` }
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const result = await bw.getItem(item, session)
|
|
81
|
-
|
|
82
|
-
if (result.login?.password) {
|
|
83
|
-
return { value: result.login.password }
|
|
84
|
-
}
|
|
85
|
-
if (result.notes) {
|
|
86
|
-
return { value: result.notes }
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { error: `Item "${item}" found but has no password or notes.` }
|
|
90
|
-
} catch (e) {
|
|
91
|
-
const stderr = (e as any)?.stderr?.toString?.()?.trim?.()
|
|
92
|
-
const message = stderr || (e as Error).message || 'unknown error'
|
|
93
|
-
return { error: `Could not retrieve "${item}": ${message}` }
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
async lock() {
|
|
98
|
-
try {
|
|
99
|
-
await bw.lock()
|
|
100
|
-
} catch (e) {
|
|
101
|
-
const stderr = (e as any)?.stderr?.toString?.()?.trim?.()
|
|
102
|
-
throw new Error(`Failed to lock vault: ${stderr || (e as Error).message}`)
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
}
|
package/src/engines/index.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { CredentialEngine } from './types.js'
|
|
2
|
-
import { bitwarden } from './bitwarden.js'
|
|
3
|
-
|
|
4
|
-
const engines: Record<string, CredentialEngine> = {
|
|
5
|
-
bitwarden,
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function getEngine(name: string): CredentialEngine | null {
|
|
9
|
-
return engines[name] ?? null
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export type { CredentialEngine }
|
package/src/engines/types.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export type EngineStatus =
|
|
2
|
-
| { state: 'not_installed'; install: string }
|
|
3
|
-
| { state: 'unauthenticated'; operator_action: string }
|
|
4
|
-
| { state: 'locked'; operator_action: string }
|
|
5
|
-
| { state: 'unlocked'; session: string }
|
|
6
|
-
|
|
7
|
-
export interface CredentialEngine {
|
|
8
|
-
name: string
|
|
9
|
-
status(): Promise<EngineStatus>
|
|
10
|
-
get(item: string, session: string): Promise<{ value: string } | { error: string }>
|
|
11
|
-
lock(): Promise<void>
|
|
12
|
-
}
|
package/src/hooks.test.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach } from 'bun:test'
|
|
2
|
-
import { readFile, rm, mkdir, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { join } from 'node:path'
|
|
4
|
-
import { installHooks, removeHooks, getHooksStatus } from './hooks.js'
|
|
5
|
-
|
|
6
|
-
const TEST_HOME = join(import.meta.dir, '..', '.test-home-hooks')
|
|
7
|
-
const SETTINGS_PATH = join(TEST_HOME, '.claude', 'settings.json')
|
|
8
|
-
|
|
9
|
-
beforeEach(async () => {
|
|
10
|
-
process.env.BRAINJAR_TEST_HOME = TEST_HOME
|
|
11
|
-
await mkdir(join(TEST_HOME, '.claude'), { recursive: true })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(async () => {
|
|
15
|
-
delete process.env.BRAINJAR_TEST_HOME
|
|
16
|
-
await rm(TEST_HOME, { recursive: true, force: true })
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
describe('hooks install', () => {
|
|
20
|
-
test('creates hooks in empty settings', async () => {
|
|
21
|
-
const result = await installHooks()
|
|
22
|
-
expect(result.installed).toContain('SessionStart')
|
|
23
|
-
|
|
24
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
25
|
-
expect(settings.hooks.SessionStart).toHaveLength(1)
|
|
26
|
-
expect(settings.hooks.SessionStart[0].matcher).toBe('startup')
|
|
27
|
-
expect(settings.hooks.SessionStart[0].hooks[0].command).toBe('brainjar sync --quiet')
|
|
28
|
-
expect(settings.hooks.SessionStart[0].hooks[0]._brainjar).toBe(true)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('preserves existing settings', async () => {
|
|
32
|
-
await writeFile(SETTINGS_PATH, JSON.stringify({
|
|
33
|
-
statusLine: { type: 'command', command: 'echo hi' },
|
|
34
|
-
enabledPlugins: { foo: true },
|
|
35
|
-
}))
|
|
36
|
-
|
|
37
|
-
await installHooks()
|
|
38
|
-
|
|
39
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
40
|
-
expect(settings.statusLine.command).toBe('echo hi')
|
|
41
|
-
expect(settings.enabledPlugins.foo).toBe(true)
|
|
42
|
-
expect(settings.hooks.SessionStart).toHaveLength(1)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('preserves existing non-brainjar hooks', async () => {
|
|
46
|
-
await writeFile(SETTINGS_PATH, JSON.stringify({
|
|
47
|
-
hooks: {
|
|
48
|
-
SessionStart: [
|
|
49
|
-
{ matcher: 'startup', hooks: [{ type: 'command', command: 'echo hello' }] },
|
|
50
|
-
],
|
|
51
|
-
PreToolUse: [
|
|
52
|
-
{ matcher: 'Edit', hooks: [{ type: 'command', command: 'lint.sh' }] },
|
|
53
|
-
],
|
|
54
|
-
},
|
|
55
|
-
}))
|
|
56
|
-
|
|
57
|
-
await installHooks()
|
|
58
|
-
|
|
59
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
60
|
-
// Existing non-brainjar SessionStart hook preserved
|
|
61
|
-
expect(settings.hooks.SessionStart).toHaveLength(2)
|
|
62
|
-
// PreToolUse untouched
|
|
63
|
-
expect(settings.hooks.PreToolUse).toHaveLength(1)
|
|
64
|
-
expect(settings.hooks.PreToolUse[0].hooks[0].command).toBe('lint.sh')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('is idempotent — no duplicates on re-install', async () => {
|
|
68
|
-
await installHooks()
|
|
69
|
-
await installHooks()
|
|
70
|
-
|
|
71
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
72
|
-
const brainjarEntries = settings.hooks.SessionStart.filter(
|
|
73
|
-
(m: any) => m.hooks.some((h: any) => h._brainjar)
|
|
74
|
-
)
|
|
75
|
-
expect(brainjarEntries).toHaveLength(1)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe('hooks remove', () => {
|
|
80
|
-
test('removes brainjar hooks', async () => {
|
|
81
|
-
await installHooks()
|
|
82
|
-
const result = await removeHooks()
|
|
83
|
-
|
|
84
|
-
expect(result.removed).toContain('SessionStart')
|
|
85
|
-
|
|
86
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
87
|
-
expect(settings.hooks).toBeUndefined()
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('preserves non-brainjar hooks', async () => {
|
|
91
|
-
await writeFile(SETTINGS_PATH, JSON.stringify({
|
|
92
|
-
hooks: {
|
|
93
|
-
SessionStart: [
|
|
94
|
-
{ matcher: 'startup', hooks: [{ type: 'command', command: 'echo hello' }] },
|
|
95
|
-
],
|
|
96
|
-
},
|
|
97
|
-
}))
|
|
98
|
-
|
|
99
|
-
await installHooks()
|
|
100
|
-
await removeHooks()
|
|
101
|
-
|
|
102
|
-
const settings = JSON.parse(await readFile(SETTINGS_PATH, 'utf-8'))
|
|
103
|
-
expect(settings.hooks.SessionStart).toHaveLength(1)
|
|
104
|
-
expect(settings.hooks.SessionStart[0].hooks[0].command).toBe('echo hello')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('no-op when no brainjar hooks exist', async () => {
|
|
108
|
-
await writeFile(SETTINGS_PATH, JSON.stringify({ statusLine: { command: 'echo' } }))
|
|
109
|
-
|
|
110
|
-
const result = await removeHooks()
|
|
111
|
-
expect(result.removed).toHaveLength(0)
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
describe('hooks status', () => {
|
|
116
|
-
test('reports not installed when no hooks', async () => {
|
|
117
|
-
await writeFile(SETTINGS_PATH, JSON.stringify({}))
|
|
118
|
-
const result = await getHooksStatus()
|
|
119
|
-
expect(Object.keys(result.hooks)).toHaveLength(0)
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test('reports installed hooks', async () => {
|
|
123
|
-
await installHooks()
|
|
124
|
-
const result = await getHooksStatus()
|
|
125
|
-
expect(result.hooks.SessionStart).toBe('brainjar sync --quiet')
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
test('handles missing settings file', async () => {
|
|
129
|
-
const result = await getHooksStatus()
|
|
130
|
-
expect(Object.keys(result.hooks)).toHaveLength(0)
|
|
131
|
-
})
|
|
132
|
-
})
|