@brainjar/cli 0.6.2 → 0.6.4
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 -1
- package/src/client.ts +1 -0
- package/src/commands/persona.ts +9 -6
- package/src/commands/rules.ts +8 -5
- package/src/commands/soul.ts +9 -6
- package/src/daemon.ts +28 -4
package/package.json
CHANGED
package/src/client.ts
CHANGED
package/src/commands/persona.ts
CHANGED
|
@@ -167,7 +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
|
-
|
|
170
|
+
rev: z.number().optional().describe('Show a specific version from history'),
|
|
171
171
|
}),
|
|
172
172
|
async run(c) {
|
|
173
173
|
const api = await getApi()
|
|
@@ -178,11 +178,11 @@ export const persona = Cli.create('persona', {
|
|
|
178
178
|
return state.persona ?? 'none'
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
if (c.options.
|
|
181
|
+
if (c.options.rev) {
|
|
182
182
|
const name = c.args.name
|
|
183
|
-
if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --
|
|
183
|
+
if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --rev' })
|
|
184
184
|
const slug = normalizeSlug(name, 'persona name')
|
|
185
|
-
const v = await api.get<ApiContentVersion>(`/api/v1/personas/${slug}/versions/${c.options.
|
|
185
|
+
const v = await api.get<ApiContentVersion>(`/api/v1/personas/${slug}/versions/${c.options.rev}`)
|
|
186
186
|
return { name: slug, version: v.version, content: v.content, metadata: v.metadata, created_at: v.created_at }
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -307,8 +307,9 @@ export const persona = Cli.create('persona', {
|
|
|
307
307
|
const name = normalizeSlug(c.args.name, 'persona name')
|
|
308
308
|
const api = await getApi()
|
|
309
309
|
|
|
310
|
+
let result: { affected_brains?: string[] } | undefined
|
|
310
311
|
try {
|
|
311
|
-
await api.delete(`/api/v1/personas/${name}`)
|
|
312
|
+
result = await api.delete<{ affected_brains?: string[] }>(`/api/v1/personas/${name}`)
|
|
312
313
|
} catch (e) {
|
|
313
314
|
if (e instanceof IncurError && e.code === ErrorCode.NOT_FOUND) {
|
|
314
315
|
throw createError(ErrorCode.PERSONA_NOT_FOUND, { params: [name] })
|
|
@@ -320,7 +321,9 @@ export const persona = Cli.create('persona', {
|
|
|
320
321
|
const state = await getEffectiveState(api)
|
|
321
322
|
if (state.persona === name) await sync({ api })
|
|
322
323
|
|
|
323
|
-
|
|
324
|
+
const out: Record<string, unknown> = { deleted: name }
|
|
325
|
+
if (result?.affected_brains?.length) out.affected_brains = result.affected_brains
|
|
326
|
+
return out
|
|
324
327
|
},
|
|
325
328
|
})
|
|
326
329
|
.command('drop', {
|
package/src/commands/rules.ts
CHANGED
|
@@ -146,14 +146,14 @@ export const rules = Cli.create('rules', {
|
|
|
146
146
|
name: z.string().describe('Rule name to show'),
|
|
147
147
|
}),
|
|
148
148
|
options: z.object({
|
|
149
|
-
|
|
149
|
+
rev: z.number().optional().describe('Show a specific version from history'),
|
|
150
150
|
}),
|
|
151
151
|
async run(c) {
|
|
152
152
|
const name = normalizeSlug(c.args.name, 'rule name')
|
|
153
153
|
const api = await getApi()
|
|
154
154
|
|
|
155
|
-
if (c.options.
|
|
156
|
-
const v = await api.get<ApiContentVersion>(`/api/v1/rules/${name}/versions/${c.options.
|
|
155
|
+
if (c.options.rev) {
|
|
156
|
+
const v = await api.get<ApiContentVersion>(`/api/v1/rules/${name}/versions/${c.options.rev}`)
|
|
157
157
|
const entries = (v.metadata as { entries?: Array<{ sort_key: number; content: string }> })?.entries ?? []
|
|
158
158
|
const content = entries.map(e => e.content.trim()).join('\n\n')
|
|
159
159
|
return { name, version: v.version, content, created_at: v.created_at }
|
|
@@ -247,8 +247,9 @@ export const rules = Cli.create('rules', {
|
|
|
247
247
|
const name = normalizeSlug(c.args.name, 'rule name')
|
|
248
248
|
const api = await getApi()
|
|
249
249
|
|
|
250
|
+
let result: { affected_brains?: string[] } | undefined
|
|
250
251
|
try {
|
|
251
|
-
await api.delete(`/api/v1/rules/${name}`)
|
|
252
|
+
result = await api.delete<{ affected_brains?: string[] }>(`/api/v1/rules/${name}`)
|
|
252
253
|
} catch (e) {
|
|
253
254
|
if (e instanceof IncurError && e.code === ErrorCode.NOT_FOUND) {
|
|
254
255
|
throw createError(ErrorCode.RULE_NOT_FOUND, { params: [name] })
|
|
@@ -260,7 +261,9 @@ export const rules = Cli.create('rules', {
|
|
|
260
261
|
const state = await getEffectiveState(api)
|
|
261
262
|
if (state.rules.includes(name)) await sync({ api })
|
|
262
263
|
|
|
263
|
-
|
|
264
|
+
const out: Record<string, unknown> = { deleted: name }
|
|
265
|
+
if (result?.affected_brains?.length) out.affected_brains = result.affected_brains
|
|
266
|
+
return out
|
|
264
267
|
},
|
|
265
268
|
})
|
|
266
269
|
.command('drop', {
|
package/src/commands/soul.ts
CHANGED
|
@@ -133,7 +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
|
-
|
|
136
|
+
rev: z.number().optional().describe('Show a specific version from history'),
|
|
137
137
|
}),
|
|
138
138
|
async run(c) {
|
|
139
139
|
const api = await getApi()
|
|
@@ -144,11 +144,11 @@ export const soul = Cli.create('soul', {
|
|
|
144
144
|
return state.soul ?? 'none'
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
if (c.options.
|
|
147
|
+
if (c.options.rev) {
|
|
148
148
|
const name = c.args.name
|
|
149
|
-
if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --
|
|
149
|
+
if (!name) throw createError(ErrorCode.MISSING_ARG, { message: 'Name is required when using --rev' })
|
|
150
150
|
const slug = normalizeSlug(name, 'soul name')
|
|
151
|
-
const v = await api.get<ApiContentVersion>(`/api/v1/souls/${slug}/versions/${c.options.
|
|
151
|
+
const v = await api.get<ApiContentVersion>(`/api/v1/souls/${slug}/versions/${c.options.rev}`)
|
|
152
152
|
return { name: slug, version: v.version, content: v.content, created_at: v.created_at }
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -264,8 +264,9 @@ export const soul = Cli.create('soul', {
|
|
|
264
264
|
const name = normalizeSlug(c.args.name, 'soul name')
|
|
265
265
|
const api = await getApi()
|
|
266
266
|
|
|
267
|
+
let result: { affected_brains?: string[] } | undefined
|
|
267
268
|
try {
|
|
268
|
-
await api.delete(`/api/v1/souls/${name}`)
|
|
269
|
+
result = await api.delete<{ affected_brains?: string[] }>(`/api/v1/souls/${name}`)
|
|
269
270
|
} catch (e) {
|
|
270
271
|
if (e instanceof IncurError && e.code === ErrorCode.NOT_FOUND) {
|
|
271
272
|
throw createError(ErrorCode.SOUL_NOT_FOUND, { params: [name] })
|
|
@@ -277,7 +278,9 @@ export const soul = Cli.create('soul', {
|
|
|
277
278
|
const state = await getEffectiveState(api)
|
|
278
279
|
if (state.soul === name) await sync({ api })
|
|
279
280
|
|
|
280
|
-
|
|
281
|
+
const out: Record<string, unknown> = { deleted: name }
|
|
282
|
+
if (result?.affected_brains?.length) out.affected_brains = result.affected_brains
|
|
283
|
+
return out
|
|
281
284
|
},
|
|
282
285
|
})
|
|
283
286
|
.command('drop', {
|
package/src/daemon.ts
CHANGED
|
@@ -279,6 +279,18 @@ export async function upgradeServer(): Promise<{ version: string; alreadyLatest:
|
|
|
279
279
|
const pid = await readPid(localContext(config).pid_file)
|
|
280
280
|
if (pid !== null && isAlive(pid)) {
|
|
281
281
|
await stop()
|
|
282
|
+
|
|
283
|
+
// Verify process is actually dead before replacing binary
|
|
284
|
+
const deadline = Date.now() + 3000
|
|
285
|
+
while (Date.now() < deadline && isAlive(pid)) {
|
|
286
|
+
await new Promise(r => setTimeout(r, 100))
|
|
287
|
+
}
|
|
288
|
+
if (isAlive(pid)) {
|
|
289
|
+
throw createError(ErrorCode.SERVER_START_FAILED, {
|
|
290
|
+
message: `Server process (PID ${pid}) is still running. Cannot replace binary.`,
|
|
291
|
+
hint: `Kill it manually: kill -9 ${pid}`,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
282
294
|
}
|
|
283
295
|
|
|
284
296
|
const versionBase = `${DIST_BASE}/${version}`
|
|
@@ -351,7 +363,14 @@ export async function stop(): Promise<{ stopped: boolean }> {
|
|
|
351
363
|
return { stopped: false }
|
|
352
364
|
}
|
|
353
365
|
|
|
354
|
-
process
|
|
366
|
+
// Kill entire process group (negative pid) so child processes
|
|
367
|
+
// like embedded postgres are also terminated.
|
|
368
|
+
try {
|
|
369
|
+
process.kill(-pid, 'SIGTERM')
|
|
370
|
+
} catch {
|
|
371
|
+
// Process group doesn't exist — try single process
|
|
372
|
+
try { process.kill(pid, 'SIGTERM') } catch {}
|
|
373
|
+
}
|
|
355
374
|
|
|
356
375
|
// Poll for exit, up to 5s
|
|
357
376
|
const deadline = Date.now() + 5000
|
|
@@ -363,10 +382,15 @@ export async function stop(): Promise<{ stopped: boolean }> {
|
|
|
363
382
|
}
|
|
364
383
|
}
|
|
365
384
|
|
|
366
|
-
// Force kill
|
|
385
|
+
// Force kill entire process group
|
|
367
386
|
try {
|
|
368
|
-
process.kill(pid, 'SIGKILL')
|
|
369
|
-
} catch {
|
|
387
|
+
process.kill(-pid, 'SIGKILL')
|
|
388
|
+
} catch {
|
|
389
|
+
try { process.kill(pid, 'SIGKILL') } catch {}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Wait briefly for SIGKILL to take effect
|
|
393
|
+
await new Promise(r => setTimeout(r, 500))
|
|
370
394
|
await rm(pid_file, { force: true })
|
|
371
395
|
return { stopped: true }
|
|
372
396
|
}
|