@brainjar/cli 0.6.0 → 0.6.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/package.json +1 -1
- package/src/api-types.ts +47 -0
- package/src/cli.ts +2 -0
- package/src/client.ts +26 -4
- package/src/commands/api-key.ts +50 -0
- package/src/commands/context.ts +28 -0
- package/src/commands/persona.ts +44 -1
- package/src/commands/rules.ts +45 -1
- package/src/commands/soul.ts +43 -1
- package/src/config.ts +14 -4
package/package.json
CHANGED
package/src/api-types.ts
CHANGED
|
@@ -107,6 +107,53 @@ export interface ApiComposeResult {
|
|
|
107
107
|
warnings?: string[]
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// --- API key types ---
|
|
111
|
+
|
|
112
|
+
export interface ApiCreateKeyResult {
|
|
113
|
+
id: string
|
|
114
|
+
name: string
|
|
115
|
+
key: string
|
|
116
|
+
key_prefix: string
|
|
117
|
+
user_id: string
|
|
118
|
+
expires_at: string | null
|
|
119
|
+
created_at: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ApiKeySummary {
|
|
123
|
+
id: string
|
|
124
|
+
name: string
|
|
125
|
+
key_prefix: string
|
|
126
|
+
user_id: string
|
|
127
|
+
expires_at: string | null
|
|
128
|
+
revoked_at: string | null
|
|
129
|
+
created_at: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ApiKeyList {
|
|
133
|
+
api_keys: ApiKeySummary[]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --- Content version types ---
|
|
137
|
+
|
|
138
|
+
export interface ApiVersionSummary {
|
|
139
|
+
version: number
|
|
140
|
+
created_at: string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ApiVersionList {
|
|
144
|
+
versions: ApiVersionSummary[]
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ApiContentVersion {
|
|
148
|
+
id: string
|
|
149
|
+
content_type: string
|
|
150
|
+
slug: string
|
|
151
|
+
version: number
|
|
152
|
+
content: string | null
|
|
153
|
+
metadata: Record<string, unknown> | null
|
|
154
|
+
created_at: string
|
|
155
|
+
}
|
|
156
|
+
|
|
110
157
|
// --- Content bundle types (export/import) ---
|
|
111
158
|
|
|
112
159
|
export interface BundleSoul {
|
package/src/cli.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { server } from './commands/server.js'
|
|
|
17
17
|
import { migrate } from './commands/migrate.js'
|
|
18
18
|
import { upgrade } from './commands/upgrade.js'
|
|
19
19
|
import { context } from './commands/context.js'
|
|
20
|
+
import { apiKey } from './commands/api-key.js'
|
|
20
21
|
|
|
21
22
|
Cli.create('brainjar', {
|
|
22
23
|
description: 'Shape how your AI thinks — soul, persona, rules',
|
|
@@ -39,4 +40,5 @@ Cli.create('brainjar', {
|
|
|
39
40
|
.command(migrate)
|
|
40
41
|
.command(upgrade)
|
|
41
42
|
.command(context)
|
|
43
|
+
.command(apiKey)
|
|
42
44
|
.serve()
|
package/src/client.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Errors } from 'incur'
|
|
2
|
-
import { basename } from 'node:path'
|
|
3
|
-
import { readConfig, activeContext } from './config.js'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { basename, join } from 'node:path'
|
|
3
|
+
import { readConfig, activeContext, isLocalContext } from './config.js'
|
|
4
|
+
import type { ServerContext } from './config.js'
|
|
5
|
+
import { getBrainjarDir, getLocalDir } from './paths.js'
|
|
6
|
+
import { access, readFile } from 'node:fs/promises'
|
|
6
7
|
import { ensureRunning } from './daemon.js'
|
|
7
8
|
import { ErrorCode, createError } from './errors.js'
|
|
8
9
|
|
|
@@ -41,6 +42,24 @@ const ERROR_MAP: Record<number, { code: ErrorCode; hint?: string }> = {
|
|
|
41
42
|
503: { code: ErrorCode.SERVER_UNAVAILABLE, hint: 'Server is not ready. Try again in a moment.' },
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
async function resolveToken(ctx: ServerContext): Promise<string | null> {
|
|
46
|
+
const envToken = process.env.BRAINJAR_TOKEN
|
|
47
|
+
if (envToken) return envToken
|
|
48
|
+
|
|
49
|
+
if (isLocalContext(ctx)) {
|
|
50
|
+
const tokenFile = ctx.auth_token_file ?? join(getBrainjarDir(), 'auth-token')
|
|
51
|
+
try {
|
|
52
|
+
return (await readFile(tokenFile, 'utf-8')).trim()
|
|
53
|
+
} catch {
|
|
54
|
+
return null // token file doesn't exist yet (server not started)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (ctx.token) return ctx.token
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
async function detectProject(explicit?: string | null): Promise<string | null> {
|
|
45
64
|
if (explicit === null) return null // explicitly suppress auto-detection
|
|
46
65
|
if (explicit) return explicit
|
|
@@ -68,11 +87,14 @@ export async function createClient(options?: ClientOptions): Promise<BrainjarCli
|
|
|
68
87
|
const url = `${serverUrl}${path}`
|
|
69
88
|
const timeout = reqOpts?.timeout ?? defaultTimeout
|
|
70
89
|
|
|
90
|
+
const token = await resolveToken(ctx)
|
|
91
|
+
|
|
71
92
|
const headers: Record<string, string> = {
|
|
72
93
|
'Accept': 'application/json',
|
|
73
94
|
'X-Brainjar-Workspace': workspace,
|
|
74
95
|
...(reqOpts?.headers ?? {}),
|
|
75
96
|
}
|
|
97
|
+
if (token) headers['Authorization'] = `Bearer ${token}`
|
|
76
98
|
|
|
77
99
|
const explicitProject = reqOpts && 'project' in reqOpts ? reqOpts.project : options?.project
|
|
78
100
|
const project = await detectProject(explicitProject)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Cli, z } from 'incur'
|
|
2
|
+
import { getApi } from '../client.js'
|
|
3
|
+
import type { ApiCreateKeyResult, ApiKeyList } from '../api-types.js'
|
|
4
|
+
|
|
5
|
+
export const apiKey = Cli.create('api-key', {
|
|
6
|
+
description: 'Manage API keys for remote server authentication',
|
|
7
|
+
})
|
|
8
|
+
.command('create', {
|
|
9
|
+
description: 'Create a new API key',
|
|
10
|
+
options: z.object({
|
|
11
|
+
name: z.string().describe('Key name (e.g. ci-pipeline)'),
|
|
12
|
+
'user-id': z.string().optional().describe('User ID label for the key'),
|
|
13
|
+
'expires-in': z.string().optional().describe('Expiration duration (e.g. 30d, 90d, 365d)'),
|
|
14
|
+
}),
|
|
15
|
+
async run(c) {
|
|
16
|
+
const api = await getApi()
|
|
17
|
+
const result = await api.post<ApiCreateKeyResult>('/api/v1/api-keys', {
|
|
18
|
+
name: c.options.name,
|
|
19
|
+
user_id: c.options['user-id'] ?? '',
|
|
20
|
+
expires_in: c.options['expires-in'] ?? '',
|
|
21
|
+
})
|
|
22
|
+
return {
|
|
23
|
+
id: result.id,
|
|
24
|
+
name: result.name,
|
|
25
|
+
key: result.key,
|
|
26
|
+
key_prefix: result.key_prefix,
|
|
27
|
+
expires_at: result.expires_at,
|
|
28
|
+
warning: 'Save this key now — it will not be shown again.',
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
})
|
|
32
|
+
.command('list', {
|
|
33
|
+
description: 'List API keys',
|
|
34
|
+
async run() {
|
|
35
|
+
const api = await getApi()
|
|
36
|
+
const result = await api.get<ApiKeyList>('/api/v1/api-keys')
|
|
37
|
+
return result
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
.command('revoke', {
|
|
41
|
+
description: 'Revoke an API key',
|
|
42
|
+
args: z.object({
|
|
43
|
+
id: z.string().describe('API key ID'),
|
|
44
|
+
}),
|
|
45
|
+
async run(c) {
|
|
46
|
+
const api = await getApi()
|
|
47
|
+
await api.delete(`/api/v1/api-keys/${c.args.id}`)
|
|
48
|
+
return { revoked: c.args.id }
|
|
49
|
+
},
|
|
50
|
+
})
|
package/src/commands/context.ts
CHANGED
|
@@ -209,6 +209,33 @@ const renameCmd = Cli.create('rename', {
|
|
|
209
209
|
},
|
|
210
210
|
})
|
|
211
211
|
|
|
212
|
+
const setTokenCmd = Cli.create('set-token', {
|
|
213
|
+
description: 'Store an API key for a context',
|
|
214
|
+
args: z.object({
|
|
215
|
+
name: z.string().describe('Context name'),
|
|
216
|
+
key: z.string().describe('API key (bjk_...)'),
|
|
217
|
+
}),
|
|
218
|
+
async run(c) {
|
|
219
|
+
const config = await readConfig()
|
|
220
|
+
|
|
221
|
+
if (!(c.args.name in config.contexts)) {
|
|
222
|
+
throw createError(ErrorCode.CONTEXT_NOT_FOUND, { params: [c.args.name] })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const ctx = config.contexts[c.args.name]
|
|
226
|
+
if (isLocalContext(ctx)) {
|
|
227
|
+
throw createError(ErrorCode.VALIDATION_ERROR, {
|
|
228
|
+
message: 'Cannot set a token on a local context. Local contexts use auto-generated tokens.',
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
ctx.token = c.args.key
|
|
233
|
+
await writeConfig(config)
|
|
234
|
+
|
|
235
|
+
return { context: c.args.name, token_set: true }
|
|
236
|
+
},
|
|
237
|
+
})
|
|
238
|
+
|
|
212
239
|
export const context = Cli.create('context', {
|
|
213
240
|
description: 'Manage server contexts — named server profiles',
|
|
214
241
|
})
|
|
@@ -218,3 +245,4 @@ export const context = Cli.create('context', {
|
|
|
218
245
|
.command(useCmd)
|
|
219
246
|
.command(showCmd)
|
|
220
247
|
.command(renameCmd)
|
|
248
|
+
.command(setTokenCmd)
|
package/src/commands/persona.ts
CHANGED
|
@@ -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({
|
package/src/commands/rules.ts
CHANGED
|
@@ -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({
|
package/src/commands/soul.ts
CHANGED
|
@@ -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({
|
package/src/config.ts
CHANGED
|
@@ -13,12 +13,14 @@ export interface LocalContext {
|
|
|
13
13
|
pid_file: string
|
|
14
14
|
log_file: string
|
|
15
15
|
workspace: string
|
|
16
|
+
auth_token_file?: string
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export interface RemoteContext {
|
|
19
20
|
url: string
|
|
20
21
|
mode: 'remote'
|
|
21
22
|
workspace: string
|
|
23
|
+
token?: string
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export type ServerContext = LocalContext | RemoteContext
|
|
@@ -197,7 +199,7 @@ function parseV2(p: Record<string, unknown>): Config {
|
|
|
197
199
|
if (!raw || typeof raw !== 'object') continue
|
|
198
200
|
const ctx = raw as Record<string, unknown>
|
|
199
201
|
if (ctx.mode === 'local') {
|
|
200
|
-
|
|
202
|
+
const local: LocalContext = {
|
|
201
203
|
url: typeof ctx.url === 'string' ? ctx.url : 'http://localhost:7742',
|
|
202
204
|
mode: 'local',
|
|
203
205
|
bin: typeof ctx.bin === 'string' ? ctx.bin : defLocal.bin,
|
|
@@ -205,12 +207,16 @@ function parseV2(p: Record<string, unknown>): Config {
|
|
|
205
207
|
log_file: typeof ctx.log_file === 'string' ? ctx.log_file : defLocal.log_file,
|
|
206
208
|
workspace: typeof ctx.workspace === 'string' ? ctx.workspace : 'default',
|
|
207
209
|
}
|
|
210
|
+
if (typeof ctx.auth_token_file === 'string') local.auth_token_file = ctx.auth_token_file
|
|
211
|
+
config.contexts[name] = local
|
|
208
212
|
} else {
|
|
209
|
-
|
|
213
|
+
const remote: RemoteContext = {
|
|
210
214
|
url: typeof ctx.url === 'string' ? ctx.url : '',
|
|
211
215
|
mode: 'remote',
|
|
212
216
|
workspace: typeof ctx.workspace === 'string' ? ctx.workspace : 'default',
|
|
213
217
|
}
|
|
218
|
+
if (typeof ctx.token === 'string') remote.token = ctx.token
|
|
219
|
+
config.contexts[name] = remote
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
}
|
|
@@ -287,7 +293,7 @@ export async function writeConfig(config: Config): Promise<void> {
|
|
|
287
293
|
const contexts = doc.contexts as Record<string, unknown>
|
|
288
294
|
for (const [name, ctx] of Object.entries(config.contexts)) {
|
|
289
295
|
if (isLocalContext(ctx)) {
|
|
290
|
-
|
|
296
|
+
const local: Record<string, unknown> = {
|
|
291
297
|
url: ctx.url,
|
|
292
298
|
mode: ctx.mode,
|
|
293
299
|
bin: ctx.bin,
|
|
@@ -295,12 +301,16 @@ export async function writeConfig(config: Config): Promise<void> {
|
|
|
295
301
|
log_file: ctx.log_file,
|
|
296
302
|
workspace: ctx.workspace,
|
|
297
303
|
}
|
|
304
|
+
if (ctx.auth_token_file) local.auth_token_file = ctx.auth_token_file
|
|
305
|
+
contexts[name] = local
|
|
298
306
|
} else {
|
|
299
|
-
|
|
307
|
+
const remote: Record<string, unknown> = {
|
|
300
308
|
url: ctx.url,
|
|
301
309
|
mode: ctx.mode,
|
|
302
310
|
workspace: ctx.workspace,
|
|
303
311
|
}
|
|
312
|
+
if (ctx.token) remote.token = ctx.token
|
|
313
|
+
contexts[name] = remote
|
|
304
314
|
}
|
|
305
315
|
}
|
|
306
316
|
|