@brainjar/cli 0.2.0 → 0.2.1
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/package.json +1 -4
- package/src/brain.ts +69 -0
- package/src/commands/brain.ts +2 -62
- package/src/commands/compose.ts +1 -1
- package/src/commands/shell.ts +1 -1
- package/src/hooks.ts +7 -2
- package/src/pack.ts +1 -1
- package/src/sync.ts +1 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainjar/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Shape how your AI thinks — composable identity, soul, persona, and rules for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,9 +43,6 @@
|
|
|
43
43
|
"changeset:tag": "changeset tag",
|
|
44
44
|
"changeset:publish": "changeset publish"
|
|
45
45
|
},
|
|
46
|
-
"overrides": {
|
|
47
|
-
"hono": ">=4.12.7"
|
|
48
|
-
},
|
|
49
46
|
"dependencies": {
|
|
50
47
|
"incur": "^0.3.4",
|
|
51
48
|
"yaml": "^2.8.2"
|
package/src/brain.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Errors } from 'incur'
|
|
2
|
+
import { readFile } from 'node:fs/promises'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { parse as parseYaml } from 'yaml'
|
|
5
|
+
import { paths } from './paths.js'
|
|
6
|
+
import { normalizeSlug } from './state.js'
|
|
7
|
+
|
|
8
|
+
const { IncurError } = Errors
|
|
9
|
+
|
|
10
|
+
/** Brain YAML schema: soul + persona + rules */
|
|
11
|
+
export interface BrainConfig {
|
|
12
|
+
soul: string
|
|
13
|
+
persona: string
|
|
14
|
+
rules: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Read and validate a brain YAML file. */
|
|
18
|
+
export async function readBrain(name: string): Promise<BrainConfig> {
|
|
19
|
+
const slug = normalizeSlug(name, 'brain name')
|
|
20
|
+
const file = join(paths.brains, `${slug}.yaml`)
|
|
21
|
+
|
|
22
|
+
let raw: string
|
|
23
|
+
try {
|
|
24
|
+
raw = await readFile(file, 'utf-8')
|
|
25
|
+
} catch {
|
|
26
|
+
throw new IncurError({
|
|
27
|
+
code: 'BRAIN_NOT_FOUND',
|
|
28
|
+
message: `Brain "${slug}" not found.`,
|
|
29
|
+
hint: 'Run `brainjar brain list` to see available brains.',
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let parsed: unknown
|
|
34
|
+
try {
|
|
35
|
+
parsed = parseYaml(raw)
|
|
36
|
+
} catch (e) {
|
|
37
|
+
throw new IncurError({
|
|
38
|
+
code: 'BRAIN_CORRUPT',
|
|
39
|
+
message: `Brain "${slug}" has invalid YAML: ${(e as Error).message}`,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
44
|
+
throw new IncurError({
|
|
45
|
+
code: 'BRAIN_CORRUPT',
|
|
46
|
+
message: `Brain "${slug}" is empty or invalid.`,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const p = parsed as Record<string, unknown>
|
|
51
|
+
|
|
52
|
+
if (typeof p.soul !== 'string' || !p.soul) {
|
|
53
|
+
throw new IncurError({
|
|
54
|
+
code: 'BRAIN_INVALID',
|
|
55
|
+
message: `Brain "${slug}" is missing required field "soul".`,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof p.persona !== 'string' || !p.persona) {
|
|
60
|
+
throw new IncurError({
|
|
61
|
+
code: 'BRAIN_INVALID',
|
|
62
|
+
message: `Brain "${slug}" is missing required field "persona".`,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const rules = Array.isArray(p.rules) ? p.rules.map(String) : []
|
|
67
|
+
|
|
68
|
+
return { soul: p.soul, persona: p.persona, rules }
|
|
69
|
+
}
|
package/src/commands/brain.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Cli, z, Errors } from 'incur'
|
|
|
3
3
|
const { IncurError } = Errors
|
|
4
4
|
import { readdir, readFile, writeFile, access, rm } from 'node:fs/promises'
|
|
5
5
|
import { join, basename } from 'node:path'
|
|
6
|
-
import {
|
|
6
|
+
import { stringify as stringifyYaml } from 'yaml'
|
|
7
7
|
import { paths } from '../paths.js'
|
|
8
8
|
import {
|
|
9
9
|
readState,
|
|
@@ -17,69 +17,9 @@ import {
|
|
|
17
17
|
requireBrainjarDir,
|
|
18
18
|
normalizeSlug,
|
|
19
19
|
} from '../state.js'
|
|
20
|
+
import { readBrain, type BrainConfig } from '../brain.js'
|
|
20
21
|
import { sync } from '../sync.js'
|
|
21
22
|
|
|
22
|
-
/** Brain YAML schema: soul + persona + rules */
|
|
23
|
-
export interface BrainConfig {
|
|
24
|
-
soul: string
|
|
25
|
-
persona: string
|
|
26
|
-
rules: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Read and validate a brain YAML file. */
|
|
30
|
-
export async function readBrain(name: string): Promise<BrainConfig> {
|
|
31
|
-
const slug = normalizeSlug(name, 'brain name')
|
|
32
|
-
const file = join(paths.brains, `${slug}.yaml`)
|
|
33
|
-
|
|
34
|
-
let raw: string
|
|
35
|
-
try {
|
|
36
|
-
raw = await readFile(file, 'utf-8')
|
|
37
|
-
} catch {
|
|
38
|
-
throw new IncurError({
|
|
39
|
-
code: 'BRAIN_NOT_FOUND',
|
|
40
|
-
message: `Brain "${slug}" not found.`,
|
|
41
|
-
hint: 'Run `brainjar brain list` to see available brains.',
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let parsed: unknown
|
|
46
|
-
try {
|
|
47
|
-
parsed = parseYaml(raw)
|
|
48
|
-
} catch (e) {
|
|
49
|
-
throw new IncurError({
|
|
50
|
-
code: 'BRAIN_CORRUPT',
|
|
51
|
-
message: `Brain "${slug}" has invalid YAML: ${(e as Error).message}`,
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
56
|
-
throw new IncurError({
|
|
57
|
-
code: 'BRAIN_CORRUPT',
|
|
58
|
-
message: `Brain "${slug}" is empty or invalid.`,
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const p = parsed as Record<string, unknown>
|
|
63
|
-
|
|
64
|
-
if (typeof p.soul !== 'string' || !p.soul) {
|
|
65
|
-
throw new IncurError({
|
|
66
|
-
code: 'BRAIN_INVALID',
|
|
67
|
-
message: `Brain "${slug}" is missing required field "soul".`,
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (typeof p.persona !== 'string' || !p.persona) {
|
|
72
|
-
throw new IncurError({
|
|
73
|
-
code: 'BRAIN_INVALID',
|
|
74
|
-
message: `Brain "${slug}" is missing required field "persona".`,
|
|
75
|
-
})
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const rules = Array.isArray(p.rules) ? p.rules.map(String) : []
|
|
79
|
-
|
|
80
|
-
return { soul: p.soul, persona: p.persona, rules }
|
|
81
|
-
}
|
|
82
|
-
|
|
83
23
|
export const brain = Cli.create('brain', {
|
|
84
24
|
description: 'Manage brains — full-stack configuration snapshots (soul + persona + rules)',
|
|
85
25
|
})
|
package/src/commands/compose.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
stripFrontmatter,
|
|
16
16
|
resolveRuleContent,
|
|
17
17
|
} from '../state.js'
|
|
18
|
-
import { readBrain } from '
|
|
18
|
+
import { readBrain } from '../brain.js'
|
|
19
19
|
|
|
20
20
|
export const compose = Cli.create('compose', {
|
|
21
21
|
description: 'Assemble a full subagent prompt from a brain or ad-hoc persona',
|
package/src/commands/shell.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { access } from 'node:fs/promises'
|
|
|
6
6
|
import { requireBrainjarDir } from '../state.js'
|
|
7
7
|
import { sync } from '../sync.js'
|
|
8
8
|
import { getLocalDir } from '../paths.js'
|
|
9
|
-
import { readBrain } from '
|
|
9
|
+
import { readBrain } from '../brain.js'
|
|
10
10
|
|
|
11
11
|
export const shell = Cli.create('shell', {
|
|
12
12
|
description: 'Spawn a subshell with BRAINJAR_* env vars set',
|
package/src/hooks.ts
CHANGED
|
@@ -43,13 +43,18 @@ function getSettingsPath(local: boolean): string {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
async function readSettings(path: string): Promise<Settings> {
|
|
46
|
+
let raw: string
|
|
46
47
|
try {
|
|
47
|
-
|
|
48
|
-
return JSON.parse(raw)
|
|
48
|
+
raw = await readFile(path, 'utf-8')
|
|
49
49
|
} catch (e) {
|
|
50
50
|
if ((e as NodeJS.ErrnoException).code === 'ENOENT') return {}
|
|
51
51
|
throw e
|
|
52
52
|
}
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(raw)
|
|
55
|
+
} catch {
|
|
56
|
+
throw new Error(`Invalid JSON in ${path} — fix the file or delete it and re-run.`)
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
async function writeSettings(path: string, settings: Settings): Promise<void> {
|
package/src/pack.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml'
|
|
|
4
4
|
import { Errors } from 'incur'
|
|
5
5
|
import { paths } from './paths.js'
|
|
6
6
|
import { normalizeSlug, requireBrainjarDir, readState, writeState, withStateLock } from './state.js'
|
|
7
|
-
import { readBrain, type BrainConfig } from './
|
|
7
|
+
import { readBrain, type BrainConfig } from './brain.js'
|
|
8
8
|
import { sync } from './sync.js'
|
|
9
9
|
|
|
10
10
|
const { IncurError } = Errors
|
package/src/sync.ts
CHANGED
|
@@ -80,10 +80,7 @@ export async function sync(options?: Backend | SyncOptions) {
|
|
|
80
80
|
try {
|
|
81
81
|
existingContent = await readFile(config.configFile, 'utf-8')
|
|
82
82
|
} catch (e) {
|
|
83
|
-
|
|
84
|
-
if (code !== 'ENOENT') {
|
|
85
|
-
warnings.push(`Could not read existing config: ${(e as Error).message}`)
|
|
86
|
-
}
|
|
83
|
+
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
// Backup existing config if it has no brainjar markers (first-time takeover)
|