@brainjar/cli 0.2.0 → 0.2.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/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![CI](https://github.com/brainjar-sh/brainjar-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/brainjar-sh/brainjar-cli/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/@brainjar/cli)](https://www.npmjs.com/package/@brainjar/cli)
5
+ [![downloads](https://img.shields.io/npm/dm/@brainjar/cli)](https://www.npmjs.com/package/@brainjar/cli)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
7
 
7
8
  Shape how your AI thinks — identity, soul, persona, rules.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainjar/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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
+ }
@@ -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 { parse as parseYaml, stringify as stringifyYaml } from 'yaml'
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
  })
@@ -15,7 +15,7 @@ import {
15
15
  stripFrontmatter,
16
16
  resolveRuleContent,
17
17
  } from '../state.js'
18
- import { readBrain } from './brain.js'
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',
@@ -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 './brain.js'
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
- const raw = await readFile(path, 'utf-8')
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 './commands/brain.js'
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
- const code = (e as NodeJS.ErrnoException).code
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)