@brainjar/cli 0.6.0 → 0.6.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainjar/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Shape how your AI thinks — composable soul, persona, and rules for AI agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api-types.ts CHANGED
@@ -107,6 +107,27 @@ export interface ApiComposeResult {
107
107
  warnings?: string[]
108
108
  }
109
109
 
110
+ // --- Content version types ---
111
+
112
+ export interface ApiVersionSummary {
113
+ version: number
114
+ created_at: string
115
+ }
116
+
117
+ export interface ApiVersionList {
118
+ versions: ApiVersionSummary[]
119
+ }
120
+
121
+ export interface ApiContentVersion {
122
+ id: string
123
+ content_type: string
124
+ slug: string
125
+ version: number
126
+ content: string | null
127
+ metadata: Record<string, unknown> | null
128
+ created_at: string
129
+ }
130
+
110
131
  // --- Content bundle types (export/import) ---
111
132
 
112
133
  export interface BundleSoul {
@@ -6,7 +6,7 @@ import { ErrorCode, createError } from '../errors.js'
6
6
  import { normalizeSlug, getEffectiveState, getStateOverride, putState } from '../state.js'
7
7
  import { sync } from '../sync.js'
8
8
  import { getApi } from '../client.js'
9
- import type { ApiPersona, ApiPersonaList, ApiRuleList } from '../api-types.js'
9
+ import type { ApiPersona, ApiPersonaList, ApiRuleList, ApiVersionList, ApiContentVersion } from '../api-types.js'
10
10
 
11
11
  export const persona = Cli.create('persona', {
12
12
  description: 'Manage personas — role behavior and workflow for the agent',
@@ -167,6 +167,7 @@ export const persona = Cli.create('persona', {
167
167
  options: z.object({
168
168
  project: z.boolean().default(false).describe('Show project persona override (if any)'),
169
169
  short: z.boolean().default(false).describe('Print only the active persona name'),
170
+ version: z.number().optional().describe('Show a specific version from history'),
170
171
  }),
171
172
  async run(c) {
172
173
  const api = await getApi()
@@ -177,6 +178,14 @@ export const persona = Cli.create('persona', {
177
178
  return state.persona ?? 'none'
178
179
  }
179
180
 
181
+ if (c.options.version) {
182
+ const name = c.args.name
183
+ if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --version' })
184
+ const slug = normalizeSlug(name, 'persona name')
185
+ const v = await api.get<ApiContentVersion>(`/api/v1/personas/${slug}/versions/${c.options.version}`)
186
+ return { name: slug, version: v.version, content: v.content, metadata: v.metadata, created_at: v.created_at }
187
+ }
188
+
180
189
  if (c.args.name) {
181
190
  const name = normalizeSlug(c.args.name, 'persona name')
182
191
  try {
@@ -214,6 +223,40 @@ export const persona = Cli.create('persona', {
214
223
  }
215
224
  },
216
225
  })
226
+ .command('history', {
227
+ description: 'List version history for a persona',
228
+ args: z.object({
229
+ name: z.string().describe('Persona name'),
230
+ }),
231
+ async run(c) {
232
+ const name = normalizeSlug(c.args.name, 'persona name')
233
+ const api = await getApi()
234
+ const result = await api.get<ApiVersionList>(`/api/v1/personas/${name}/versions`)
235
+ return { name, versions: result.versions }
236
+ },
237
+ })
238
+ .command('revert', {
239
+ description: 'Restore a persona to a previous version',
240
+ args: z.object({
241
+ name: z.string().describe('Persona name'),
242
+ }),
243
+ options: z.object({
244
+ to: z.number().describe('Version number to restore'),
245
+ }),
246
+ async run(c) {
247
+ const name = normalizeSlug(c.args.name, 'persona name')
248
+ const api = await getApi()
249
+ const v = await api.get<ApiContentVersion>(`/api/v1/personas/${name}/versions/${c.options.to}`)
250
+ if (!v.content) throw createError(ErrorCode.BAD_REQUEST, { message: 'Version has no content to restore' })
251
+ const bundledRules = (v.metadata as { bundled_rules?: string[] })?.bundled_rules ?? []
252
+ await api.put<ApiPersona>(`/api/v1/personas/${name}`, { content: v.content, bundled_rules: bundledRules })
253
+
254
+ const state = await getEffectiveState(api)
255
+ if (state.persona === name) await sync({ api })
256
+
257
+ return { reverted: name, to_version: c.options.to }
258
+ },
259
+ })
217
260
  .command('use', {
218
261
  description: 'Activate a persona',
219
262
  args: z.object({
@@ -6,7 +6,7 @@ import { ErrorCode, createError } from '../errors.js'
6
6
  import { normalizeSlug, getEffectiveState, getStateOverride, putState } from '../state.js'
7
7
  import { sync } from '../sync.js'
8
8
  import { getApi } from '../client.js'
9
- import type { ApiRule, ApiRuleList } from '../api-types.js'
9
+ import type { ApiRule, ApiRuleList, ApiVersionList, ApiContentVersion } from '../api-types.js'
10
10
 
11
11
  export const rules = Cli.create('rules', {
12
12
  description: 'Manage rules — behavioral constraints for the agent',
@@ -145,10 +145,20 @@ export const rules = Cli.create('rules', {
145
145
  args: z.object({
146
146
  name: z.string().describe('Rule name to show'),
147
147
  }),
148
+ options: z.object({
149
+ version: z.number().optional().describe('Show a specific version from history'),
150
+ }),
148
151
  async run(c) {
149
152
  const name = normalizeSlug(c.args.name, 'rule name')
150
153
  const api = await getApi()
151
154
 
155
+ if (c.options.version) {
156
+ const v = await api.get<ApiContentVersion>(`/api/v1/rules/${name}/versions/${c.options.version}`)
157
+ const entries = (v.metadata as { entries?: Array<{ sort_key: number; content: string }> })?.entries ?? []
158
+ const content = entries.map(e => e.content.trim()).join('\n\n')
159
+ return { name, version: v.version, content, created_at: v.created_at }
160
+ }
161
+
152
162
  try {
153
163
  const rule = await api.get<ApiRule>(`/api/v1/rules/${name}`)
154
164
  const content = rule.entries.map(e => e.content.trim()).join('\n\n')
@@ -161,6 +171,40 @@ export const rules = Cli.create('rules', {
161
171
  }
162
172
  },
163
173
  })
174
+ .command('history', {
175
+ description: 'List version history for a rule',
176
+ args: z.object({
177
+ name: z.string().describe('Rule name'),
178
+ }),
179
+ async run(c) {
180
+ const name = normalizeSlug(c.args.name, 'rule name')
181
+ const api = await getApi()
182
+ const result = await api.get<ApiVersionList>(`/api/v1/rules/${name}/versions`)
183
+ return { name, versions: result.versions }
184
+ },
185
+ })
186
+ .command('revert', {
187
+ description: 'Restore a rule to a previous version',
188
+ args: z.object({
189
+ name: z.string().describe('Rule name'),
190
+ }),
191
+ options: z.object({
192
+ to: z.number().describe('Version number to restore'),
193
+ }),
194
+ async run(c) {
195
+ const name = normalizeSlug(c.args.name, 'rule name')
196
+ const api = await getApi()
197
+ const v = await api.get<ApiContentVersion>(`/api/v1/rules/${name}/versions/${c.options.to}`)
198
+ const entries = (v.metadata as { entries?: Array<{ sort_key: number; content: string }> })?.entries
199
+ if (!entries) throw createError(ErrorCode.BAD_REQUEST, { message: 'Version has no entries to restore' })
200
+ await api.put<ApiRule>(`/api/v1/rules/${name}`, { entries: entries.map(e => ({ name: `${name}.md`, content: e.content })) })
201
+
202
+ const state = await getEffectiveState(api)
203
+ if (state.rules.includes(name)) await sync({ api })
204
+
205
+ return { reverted: name, to_version: c.options.to }
206
+ },
207
+ })
164
208
  .command('add', {
165
209
  description: 'Activate a rule or rule pack',
166
210
  args: z.object({
@@ -6,7 +6,7 @@ import { ErrorCode, createError } from '../errors.js'
6
6
  import { normalizeSlug, getEffectiveState, getStateOverride, putState } from '../state.js'
7
7
  import { sync } from '../sync.js'
8
8
  import { getApi } from '../client.js'
9
- import type { ApiSoul, ApiSoulList } from '../api-types.js'
9
+ import type { ApiSoul, ApiSoulList, ApiVersionList, ApiContentVersion } from '../api-types.js'
10
10
 
11
11
  export const soul = Cli.create('soul', {
12
12
  description: 'Manage soul — personality and values for the agent',
@@ -133,6 +133,7 @@ export const soul = Cli.create('soul', {
133
133
  options: z.object({
134
134
  project: z.boolean().default(false).describe('Show project soul override (if any)'),
135
135
  short: z.boolean().default(false).describe('Print only the active soul name'),
136
+ version: z.number().optional().describe('Show a specific version from history'),
136
137
  }),
137
138
  async run(c) {
138
139
  const api = await getApi()
@@ -143,6 +144,14 @@ export const soul = Cli.create('soul', {
143
144
  return state.soul ?? 'none'
144
145
  }
145
146
 
147
+ if (c.options.version) {
148
+ const name = c.args.name
149
+ if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --version' })
150
+ const slug = normalizeSlug(name, 'soul name')
151
+ const v = await api.get<ApiContentVersion>(`/api/v1/souls/${slug}/versions/${c.options.version}`)
152
+ return { name: slug, version: v.version, content: v.content, created_at: v.created_at }
153
+ }
154
+
146
155
  if (c.args.name) {
147
156
  const name = normalizeSlug(c.args.name, 'soul name')
148
157
  try {
@@ -180,6 +189,39 @@ export const soul = Cli.create('soul', {
180
189
  }
181
190
  },
182
191
  })
192
+ .command('history', {
193
+ description: 'List version history for a soul',
194
+ args: z.object({
195
+ name: z.string().describe('Soul name'),
196
+ }),
197
+ async run(c) {
198
+ const name = normalizeSlug(c.args.name, 'soul name')
199
+ const api = await getApi()
200
+ const result = await api.get<ApiVersionList>(`/api/v1/souls/${name}/versions`)
201
+ return { name, versions: result.versions }
202
+ },
203
+ })
204
+ .command('revert', {
205
+ description: 'Restore a soul to a previous version',
206
+ args: z.object({
207
+ name: z.string().describe('Soul name'),
208
+ }),
209
+ options: z.object({
210
+ to: z.number().describe('Version number to restore'),
211
+ }),
212
+ async run(c) {
213
+ const name = normalizeSlug(c.args.name, 'soul name')
214
+ const api = await getApi()
215
+ const v = await api.get<ApiContentVersion>(`/api/v1/souls/${name}/versions/${c.options.to}`)
216
+ if (!v.content) throw createError(ErrorCode.BAD_REQUEST, { message: 'Version has no content to restore' })
217
+ await api.put<ApiSoul>(`/api/v1/souls/${name}`, { content: v.content })
218
+
219
+ const state = await getEffectiveState(api)
220
+ if (state.soul === name) await sync({ api })
221
+
222
+ return { reverted: name, to_version: c.options.to }
223
+ },
224
+ })
183
225
  .command('use', {
184
226
  description: 'Activate a soul',
185
227
  args: z.object({