@cortexkit/opencode-anthropic-auth 1.22.0 → 1.23.0
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 +37 -20
- package/dist/billing-lineage.d.ts +51 -0
- package/dist/claustrum-enrollment-registry.d.ts +4 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +89 -6
- package/dist/custody-dimensions.d.ts +36 -0
- package/dist/custody-enrollment.d.ts +14 -0
- package/dist/custody-live.d.ts +111 -0
- package/dist/custody-manifest-watcher.d.ts +16 -0
- package/dist/custody-mode.d.ts +186 -0
- package/dist/effort-history.d.ts +5 -1
- package/dist/fable-fallback.d.ts +1 -1
- package/dist/{index-926t2bzc.js → index-0jfsxyn9.js} +1 -1
- package/dist/index-1fq7h6yd.js +430 -0
- package/dist/{index-7f57ewkp.js → index-1gr8v4fn.js} +1 -1
- package/dist/index-797pmamn.js +2 -0
- package/dist/{index-bx5hsgf8.js → index-8y2t4mwz.js} +2 -2
- package/dist/index-a5d6v3ba.js +5 -0
- package/dist/index-n96q8pks.js +2 -0
- package/dist/index-xxhyhp98.js +2 -0
- package/dist/index-xzbhzz0a.js +2 -0
- package/dist/index-z7gfdyv3.js +1 -0
- package/dist/index.js +27 -22
- package/dist/local-login.d.ts +64 -0
- package/dist/{logger-jrkranbz.js → logger-kk9vyem9.js} +1 -1
- package/dist/prime-manager-registry.d.ts +5 -1
- package/dist/rpc/notifications.d.ts +3 -3
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +9 -2
- package/dist/rpc/protocol.js +1 -1
- package/dist/rpc/rpc-client.d.ts +1 -1
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/rpc/server-registry.d.ts +10 -0
- package/dist/setup/activation.d.ts +7 -0
- package/dist/setup/claustrum.d.ts +26 -0
- package/dist/setup/command-runner.d.ts +2 -0
- package/dist/setup/command.d.ts +8 -0
- package/dist/setup/detect.d.ts +5 -0
- package/dist/setup/jsonc.d.ts +9 -0
- package/dist/setup/opencode-config.d.ts +18 -0
- package/dist/setup/paths.d.ts +15 -0
- package/dist/setup/pi.d.ts +8 -0
- package/dist/setup/process-fence.d.ts +14 -0
- package/dist/setup/types.d.ts +37 -0
- package/dist/sidebar-state.d.ts +3 -1
- package/dist/sidebar-state.js +1 -1
- package/dist/transform.d.ts +5 -1
- package/dist/tui/command-dialogs.d.ts +27 -2
- package/dist/tui-preferences.js +2 -2
- package/package.json +10 -5
- package/src/rpc/notifications.ts +28 -24
- package/src/rpc/protocol.ts +18 -2
- package/src/rpc/rpc-client.ts +1 -1
- package/src/rpc/rpc-server.ts +18 -5
- package/src/rpc/server-registry.ts +61 -0
- package/src/sidebar-state.ts +26 -2
- package/src/tui/command-dialogs.tsx +273 -68
- package/src/tui-compiled/rpc/protocol.ts +18 -2
- package/src/tui-compiled/rpc/rpc-client.ts +1 -1
- package/src/tui-compiled/sidebar-state.ts +26 -2
- package/src/tui-compiled/tui/command-dialogs.tsx +215 -61
- package/src/tui-compiled/tui.tsx +1 -0
- package/src/tui.tsx +1 -0
- package/dist/index-8aga2zwv.js +0 -4
- package/dist/index-cgez4m0h.js +0 -2
- package/dist/index-d3f8g6ve.js +0 -428
- package/dist/index-sh51yqxs.js +0 -2
package/src/rpc/protocol.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ClaustrumEnrollmentStatus } from '@cortexkit/anthropic-auth-core'
|
|
2
|
+
|
|
1
3
|
export const COMMAND_MODAL_NAMES = [
|
|
2
4
|
'claude-account',
|
|
3
5
|
'claude-cache',
|
|
@@ -17,17 +19,31 @@ export type CommandModalName = (typeof COMMAND_MODAL_NAMES)[number]
|
|
|
17
19
|
export interface AccountDialogAccount {
|
|
18
20
|
id: string
|
|
19
21
|
label: string
|
|
20
|
-
role:
|
|
22
|
+
role: 'main' | 'fallback'
|
|
21
23
|
enabled: boolean
|
|
22
24
|
quotaPercent: number | null
|
|
23
25
|
tierLabel?: string
|
|
24
26
|
claustrumGate: 'on' | 'off' | 'na'
|
|
25
27
|
vaultServed: boolean
|
|
28
|
+
vaultReauth: boolean
|
|
29
|
+
custodyState:
|
|
30
|
+
| 'na'
|
|
31
|
+
| 'off'
|
|
32
|
+
| 'on-vault-served'
|
|
33
|
+
| 'on-vault-reauth'
|
|
34
|
+
| 'on-cold'
|
|
35
|
+
| 'unknown-identity'
|
|
36
|
+
| 'on-identity-mismatch'
|
|
37
|
+
| 'on-corrupt-binding'
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
export interface AccountDialogKnobs {
|
|
29
41
|
accounts: AccountDialogAccount[]
|
|
30
42
|
claustrumDetection: string
|
|
43
|
+
custodyMode?: 'local' | 'claustrum'
|
|
44
|
+
custodyModeKnown?: boolean
|
|
45
|
+
enrollmentStatus?: string
|
|
46
|
+
claustrumEnrollment?: ClaustrumEnrollmentStatus
|
|
31
47
|
[key: string]: unknown
|
|
32
48
|
}
|
|
33
49
|
|
|
@@ -41,7 +57,7 @@ export interface RpcNotification {
|
|
|
41
57
|
id: number
|
|
42
58
|
type: 'open-dialog'
|
|
43
59
|
payload: OpenDialogPayload
|
|
44
|
-
sessionId
|
|
60
|
+
sessionId: string
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
export interface ApplyRequest {
|
package/src/rpc/rpc-client.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ApplyRequest, ApplyResult, RpcNotification } from './protocol'
|
|
|
4
4
|
export interface RpcClient {
|
|
5
5
|
pending: (
|
|
6
6
|
lastReceivedId: number,
|
|
7
|
-
sessionId
|
|
7
|
+
sessionId: string,
|
|
8
8
|
) => Promise<RpcNotification[]>
|
|
9
9
|
apply: (request: ApplyRequest) => Promise<ApplyResult>
|
|
10
10
|
}
|
package/src/rpc/rpc-server.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from 'node:crypto'
|
|
2
|
-
import { unlink } from 'node:fs/promises'
|
|
2
|
+
import { readFile, unlink } from 'node:fs/promises'
|
|
3
3
|
import {
|
|
4
4
|
createServer,
|
|
5
5
|
type IncomingMessage,
|
|
@@ -74,9 +74,15 @@ export async function startRpcServer(
|
|
|
74
74
|
unknown
|
|
75
75
|
>
|
|
76
76
|
if (method === 'pending-notifications') {
|
|
77
|
+
if (
|
|
78
|
+
typeof params.sessionId !== 'string' ||
|
|
79
|
+
params.sessionId.length === 0
|
|
80
|
+
) {
|
|
81
|
+
return json(400, { error: 'sessionId is required' })
|
|
82
|
+
}
|
|
77
83
|
const messages = options.drain(
|
|
78
84
|
Number(params.lastReceivedId ?? 0),
|
|
79
|
-
|
|
85
|
+
params.sessionId,
|
|
80
86
|
)
|
|
81
87
|
return json(200, { messages })
|
|
82
88
|
}
|
|
@@ -115,9 +121,16 @@ export async function startRpcServer(
|
|
|
115
121
|
token,
|
|
116
122
|
async stop() {
|
|
117
123
|
await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
118
|
-
|
|
119
|
-
(
|
|
120
|
-
|
|
124
|
+
try {
|
|
125
|
+
const portFile = join(options.dir, `port-${process.pid}.json`)
|
|
126
|
+
const current = JSON.parse(await readFile(portFile, 'utf8')) as {
|
|
127
|
+
port?: unknown
|
|
128
|
+
pid?: unknown
|
|
129
|
+
}
|
|
130
|
+
if (current.port === port && current.pid === process.pid) {
|
|
131
|
+
await unlink(portFile)
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
121
134
|
},
|
|
122
135
|
}
|
|
123
136
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { RpcServerHandle } from './rpc-server'
|
|
2
|
+
|
|
3
|
+
type RpcServerGlobal = typeof globalThis & {
|
|
4
|
+
__anthropicAuthRpcServers?: Map<string, RpcServerHandle>
|
|
5
|
+
__anthropicAuthRpcServersPending?: Map<string, Promise<void>>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RpcServerAdoption {
|
|
9
|
+
server: RpcServerHandle
|
|
10
|
+
release: () => Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Serializes same-directory replacement and returns an identity-fenced lease.
|
|
15
|
+
* Distinct project directories remain independent.
|
|
16
|
+
*/
|
|
17
|
+
export async function adoptRpcServer(
|
|
18
|
+
rpcDir: string,
|
|
19
|
+
create: () => Promise<RpcServerHandle>,
|
|
20
|
+
): Promise<RpcServerAdoption> {
|
|
21
|
+
const rpcGlobal = globalThis as RpcServerGlobal
|
|
22
|
+
const servers =
|
|
23
|
+
rpcGlobal.__anthropicAuthRpcServers ?? new Map<string, RpcServerHandle>()
|
|
24
|
+
const pending =
|
|
25
|
+
rpcGlobal.__anthropicAuthRpcServersPending ??
|
|
26
|
+
new Map<string, Promise<void>>()
|
|
27
|
+
rpcGlobal.__anthropicAuthRpcServers = servers
|
|
28
|
+
rpcGlobal.__anthropicAuthRpcServersPending = pending
|
|
29
|
+
|
|
30
|
+
const predecessor = pending.get(rpcDir) ?? Promise.resolve()
|
|
31
|
+
let server: RpcServerHandle | undefined
|
|
32
|
+
const start = predecessor
|
|
33
|
+
.catch(() => {})
|
|
34
|
+
.then(async () => {
|
|
35
|
+
const previous = servers.get(rpcDir)
|
|
36
|
+
if (previous) {
|
|
37
|
+
await previous.stop()
|
|
38
|
+
if (servers.get(rpcDir) === previous) servers.delete(rpcDir)
|
|
39
|
+
}
|
|
40
|
+
server = await create()
|
|
41
|
+
servers.set(rpcDir, server)
|
|
42
|
+
})
|
|
43
|
+
pending.set(rpcDir, start)
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await start
|
|
47
|
+
} finally {
|
|
48
|
+
if (pending.get(rpcDir) === start) pending.delete(rpcDir)
|
|
49
|
+
}
|
|
50
|
+
const adoptedServer = server
|
|
51
|
+
if (!adoptedServer) throw new Error('RPC server failed to start')
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
server: adoptedServer,
|
|
55
|
+
release: async () => {
|
|
56
|
+
if (servers.get(rpcDir) !== adoptedServer) return
|
|
57
|
+
await adoptedServer.stop()
|
|
58
|
+
if (servers.get(rpcDir) === adoptedServer) servers.delete(rpcDir)
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/sidebar-state.ts
CHANGED
|
@@ -36,6 +36,16 @@ export interface SidebarAccountState {
|
|
|
36
36
|
needsReauth: boolean
|
|
37
37
|
// True when the vault copy needs re-importing while the sidecar remains usable.
|
|
38
38
|
vaultReauth?: boolean
|
|
39
|
+
// A gate alone does not prove the sidecar can serve a request.
|
|
40
|
+
vaultServed?: boolean
|
|
41
|
+
custodyState?:
|
|
42
|
+
| 'off'
|
|
43
|
+
| 'on-vault-served'
|
|
44
|
+
| 'on-vault-reauth'
|
|
45
|
+
| 'on-cold'
|
|
46
|
+
| 'unknown-identity'
|
|
47
|
+
| 'on-identity-mismatch'
|
|
48
|
+
| 'on-corrupt-binding'
|
|
39
49
|
tierLabel?: string
|
|
40
50
|
}
|
|
41
51
|
|
|
@@ -59,7 +69,7 @@ export interface PrimeSidebarAccountState {
|
|
|
59
69
|
label: string
|
|
60
70
|
nextDueAt?: number | null
|
|
61
71
|
lastPrimedAt?: number | null
|
|
62
|
-
lastResult?: 'ok' | 'error'
|
|
72
|
+
lastResult?: 'ok' | 'error' | 'skipped'
|
|
63
73
|
usage?: PrimeUsageCounters
|
|
64
74
|
estimatedCostUsd?: number
|
|
65
75
|
}
|
|
@@ -180,7 +190,11 @@ function normalizePrimeAccount(
|
|
|
180
190
|
} else if (isFiniteNumber(value.lastPrimedAt)) {
|
|
181
191
|
account.lastPrimedAt = value.lastPrimedAt
|
|
182
192
|
}
|
|
183
|
-
if (
|
|
193
|
+
if (
|
|
194
|
+
value.lastResult === 'ok' ||
|
|
195
|
+
value.lastResult === 'error' ||
|
|
196
|
+
value.lastResult === 'skipped'
|
|
197
|
+
) {
|
|
184
198
|
account.lastResult = value.lastResult
|
|
185
199
|
}
|
|
186
200
|
const usage = normalizePrimeUsage(value.usage)
|
|
@@ -338,6 +352,11 @@ export function normalizeSidebarState(raw: unknown): SidebarState {
|
|
|
338
352
|
needsReauth:
|
|
339
353
|
typeof entry.needsReauth === 'boolean' ? entry.needsReauth : false,
|
|
340
354
|
...(entry.vaultReauth === true && { vaultReauth: true }),
|
|
355
|
+
...(entry.vaultServed === true && { vaultServed: true }),
|
|
356
|
+
...(typeof entry.custodyState === 'string' && {
|
|
357
|
+
custodyState:
|
|
358
|
+
entry.custodyState as SidebarAccountState['custodyState'],
|
|
359
|
+
}),
|
|
341
360
|
tierLabel:
|
|
342
361
|
typeof entry.tierLabel === 'string' && entry.tierLabel.trim()
|
|
343
362
|
? entry.tierLabel.trim()
|
|
@@ -817,6 +836,8 @@ export function formatFallbackModelLabel(modelId: string | undefined): string {
|
|
|
817
836
|
return 'Fable 5.1'
|
|
818
837
|
if (modelId === 'claude-fable-5' || modelId?.startsWith('claude-fable-5-'))
|
|
819
838
|
return 'Fable 5'
|
|
839
|
+
if (modelId === 'claude-opus-5-5' || modelId?.startsWith('claude-opus-5-5-'))
|
|
840
|
+
return 'Opus 5.5'
|
|
820
841
|
if (modelId === 'claude-opus-5' || modelId?.startsWith('claude-opus-5-'))
|
|
821
842
|
return 'Opus 5'
|
|
822
843
|
if (modelId === 'claude-opus-4-8' || modelId?.startsWith('claude-opus-4-8-'))
|
|
@@ -844,6 +865,9 @@ export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
|
|
|
844
865
|
if (account.lastResult === 'error') {
|
|
845
866
|
return { text: 'err', hasError: true }
|
|
846
867
|
}
|
|
868
|
+
if (account.lastResult === 'skipped') {
|
|
869
|
+
return { text: 'skip', hasError: false }
|
|
870
|
+
}
|
|
847
871
|
if (account.nextDueAt && account.nextDueAt > Date.now()) {
|
|
848
872
|
return { text: formatPrimeTime(account.nextDueAt), hasError: false }
|
|
849
873
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
custodyStatusLabel,
|
|
4
|
+
type PrimeAccountStatus,
|
|
5
|
+
} from '@cortexkit/anthropic-auth-core'
|
|
3
6
|
import type { TuiPluginApi } from '@opencode-ai/plugin/tui'
|
|
4
7
|
import type { AccountDialogAccount } from '../rpc/protocol'
|
|
5
8
|
import type { OpenDialogPayload } from '../rpc/protocol.js'
|
|
@@ -16,6 +19,27 @@ type KillswitchDialogConfig = {
|
|
|
16
19
|
accounts?: Record<string, Record<string, number>>
|
|
17
20
|
}
|
|
18
21
|
|
|
22
|
+
type TuiAccountDialogAccount = Omit<
|
|
23
|
+
AccountDialogAccount,
|
|
24
|
+
'vaultReauth' | 'custodyState'
|
|
25
|
+
> &
|
|
26
|
+
Partial<Pick<AccountDialogAccount, 'vaultReauth' | 'custodyState'>>
|
|
27
|
+
|
|
28
|
+
type TuiAccountDialogPayload = {
|
|
29
|
+
accounts: TuiAccountDialogAccount[]
|
|
30
|
+
claustrumDetection: string
|
|
31
|
+
custodyMode?: TuiCustodyMode
|
|
32
|
+
enrollmentStatus?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type TuiCustodyMode = 'local' | 'claustrum' | `mismatch: ${string}`
|
|
36
|
+
|
|
37
|
+
type AccountDialogOption = {
|
|
38
|
+
title: string
|
|
39
|
+
value: string
|
|
40
|
+
description?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
19
43
|
export const PRIME_DIALOG_OPTIONS = [
|
|
20
44
|
{ title: 'Enable', value: 'on' },
|
|
21
45
|
{ title: 'Disable', value: 'off' },
|
|
@@ -49,24 +73,189 @@ export function buildKillswitchThresholdSeed(
|
|
|
49
73
|
return seedParts.join(' ')
|
|
50
74
|
}
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
function normalizeTuiCustodyMode(value: string): TuiCustodyMode {
|
|
77
|
+
return value === 'local' || value === 'claustrum'
|
|
78
|
+
? value
|
|
79
|
+
: `mismatch: ${value}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeAccountDialogPayload(
|
|
83
|
+
value: unknown,
|
|
84
|
+
): TuiAccountDialogPayload {
|
|
85
|
+
const payload =
|
|
86
|
+
value && typeof value === 'object' ? (value as Record<string, unknown>) : {}
|
|
87
|
+
const rawAccounts = Array.isArray(payload.accounts) ? payload.accounts : []
|
|
88
|
+
const accounts = rawAccounts.flatMap((value) => {
|
|
89
|
+
if (!value || typeof value !== 'object') return []
|
|
90
|
+
const account = value as Record<string, unknown>
|
|
91
|
+
const quotaPercent = account.quotaPercent
|
|
92
|
+
if (
|
|
93
|
+
typeof account.id !== 'string' ||
|
|
94
|
+
typeof account.label !== 'string' ||
|
|
95
|
+
(account.role !== 'main' && account.role !== 'fallback') ||
|
|
96
|
+
typeof account.enabled !== 'boolean' ||
|
|
97
|
+
(quotaPercent !== null &&
|
|
98
|
+
(typeof quotaPercent !== 'number' || !Number.isFinite(quotaPercent))) ||
|
|
99
|
+
(account.claustrumGate !== 'on' &&
|
|
100
|
+
account.claustrumGate !== 'off' &&
|
|
101
|
+
account.claustrumGate !== 'na') ||
|
|
102
|
+
typeof account.vaultServed !== 'boolean'
|
|
103
|
+
) {
|
|
104
|
+
return []
|
|
105
|
+
}
|
|
106
|
+
const normalized = {
|
|
107
|
+
id: account.id,
|
|
108
|
+
label: account.label,
|
|
109
|
+
role: account.role,
|
|
110
|
+
enabled: account.enabled,
|
|
111
|
+
quotaPercent,
|
|
112
|
+
...(typeof account.tierLabel === 'string' && {
|
|
113
|
+
tierLabel: account.tierLabel,
|
|
114
|
+
}),
|
|
115
|
+
claustrumGate: account.claustrumGate,
|
|
116
|
+
vaultServed: account.vaultServed,
|
|
117
|
+
} satisfies TuiAccountDialogAccount
|
|
118
|
+
const custodyState = account.custodyState
|
|
119
|
+
if (
|
|
120
|
+
(custodyState === 'na' ||
|
|
121
|
+
custodyState === 'off' ||
|
|
122
|
+
custodyState === 'on-vault-served' ||
|
|
123
|
+
custodyState === 'on-vault-reauth' ||
|
|
124
|
+
custodyState === 'on-cold') &&
|
|
125
|
+
typeof account.vaultReauth === 'boolean'
|
|
126
|
+
) {
|
|
127
|
+
return [
|
|
128
|
+
{
|
|
129
|
+
...normalized,
|
|
130
|
+
custodyState,
|
|
131
|
+
vaultReauth: account.vaultReauth,
|
|
132
|
+
},
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
return [normalized]
|
|
136
|
+
})
|
|
137
|
+
const custodyMode: TuiCustodyMode | undefined =
|
|
138
|
+
payload.custodyModeKnown === true && typeof payload.custodyMode === 'string'
|
|
139
|
+
? normalizeTuiCustodyMode(payload.custodyMode)
|
|
140
|
+
: undefined
|
|
141
|
+
return {
|
|
142
|
+
accounts,
|
|
143
|
+
claustrumDetection:
|
|
144
|
+
typeof payload.claustrumDetection === 'string'
|
|
145
|
+
? payload.claustrumDetection
|
|
146
|
+
: 'unknown',
|
|
147
|
+
...(custodyMode && { custodyMode }),
|
|
148
|
+
...(typeof payload.enrollmentStatus === 'string' &&
|
|
149
|
+
payload.enrollmentStatus.length <= 2_000 && {
|
|
150
|
+
enrollmentStatus: payload.enrollmentStatus,
|
|
151
|
+
}),
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function buildAccountDialogOption(account: TuiAccountDialogAccount) {
|
|
53
156
|
const pct =
|
|
54
157
|
account.quotaPercent != null
|
|
55
158
|
? ` ${Math.round(account.quotaPercent)}%`
|
|
56
159
|
: ' \u2013%'
|
|
57
160
|
const status = !account.enabled ? ' (disabled)' : ''
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
161
|
+
const custody = account.custodyState
|
|
162
|
+
? account.custodyState === 'na'
|
|
163
|
+
? undefined
|
|
164
|
+
: custodyStatusLabel(account.custodyState).replace(
|
|
165
|
+
' (OpenCode managed)',
|
|
166
|
+
'',
|
|
167
|
+
)
|
|
168
|
+
: undefined
|
|
63
169
|
return {
|
|
64
|
-
title: `${account.label} [${account.role}]${status}${pct}${
|
|
170
|
+
title: `${account.label} [${account.role}]${status}${pct}${custody ? ` · custody ${custody}` : ''}`,
|
|
65
171
|
value: account.id,
|
|
66
172
|
...(account.tierLabel && { description: account.tierLabel }),
|
|
67
173
|
}
|
|
68
174
|
}
|
|
69
175
|
|
|
176
|
+
export function buildManageAccountOptions(account: TuiAccountDialogAccount) {
|
|
177
|
+
const options: AccountDialogOption[] = []
|
|
178
|
+
const toggleLabel = account.enabled ? 'Disable' : 'Enable'
|
|
179
|
+
options.push({
|
|
180
|
+
title: toggleLabel,
|
|
181
|
+
value: account.enabled ? 'disable' : 'enable',
|
|
182
|
+
description: account.enabled
|
|
183
|
+
? 'Stop using this fallback account'
|
|
184
|
+
: 'Allow this fallback account to be used',
|
|
185
|
+
})
|
|
186
|
+
options.push({
|
|
187
|
+
title: 'Move up',
|
|
188
|
+
value: 'move-up',
|
|
189
|
+
description: 'Higher priority in fallback order',
|
|
190
|
+
})
|
|
191
|
+
options.push({
|
|
192
|
+
title: 'Move down',
|
|
193
|
+
value: 'move-down',
|
|
194
|
+
description: 'Lower priority in fallback order',
|
|
195
|
+
})
|
|
196
|
+
options.push({
|
|
197
|
+
title: 'Remove\u2026',
|
|
198
|
+
value: 'remove',
|
|
199
|
+
description: 'Delete this account permanently',
|
|
200
|
+
})
|
|
201
|
+
options.push({ title: 'Back', value: 'back' })
|
|
202
|
+
return options
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function buildAccountDialogL1(value: unknown): {
|
|
206
|
+
header: string
|
|
207
|
+
options: AccountDialogOption[]
|
|
208
|
+
enrollmentStatus?: string
|
|
209
|
+
modeAction?: { command: 'claude-account'; arguments: 'local' | 'claustrum' }
|
|
210
|
+
} {
|
|
211
|
+
const payload = normalizeAccountDialogPayload(value)
|
|
212
|
+
const modeAction =
|
|
213
|
+
payload.custodyMode === 'local' || payload.custodyMode === 'claustrum'
|
|
214
|
+
? {
|
|
215
|
+
command: 'claude-account' as const,
|
|
216
|
+
arguments:
|
|
217
|
+
payload.custodyMode === 'local'
|
|
218
|
+
? ('claustrum' as const)
|
|
219
|
+
: ('local' as const),
|
|
220
|
+
}
|
|
221
|
+
: undefined
|
|
222
|
+
return {
|
|
223
|
+
header: payload.custodyMode
|
|
224
|
+
? `Custody mode: ${payload.custodyMode}`
|
|
225
|
+
: 'Custody mode: unavailable from older server',
|
|
226
|
+
options: [
|
|
227
|
+
...(modeAction
|
|
228
|
+
? [
|
|
229
|
+
{
|
|
230
|
+
title:
|
|
231
|
+
payload.custodyMode === 'local'
|
|
232
|
+
? 'Use Claustrum custody'
|
|
233
|
+
: 'Use local custody',
|
|
234
|
+
value: '__custody-mode__',
|
|
235
|
+
},
|
|
236
|
+
]
|
|
237
|
+
: []),
|
|
238
|
+
{
|
|
239
|
+
title: 'Add account…',
|
|
240
|
+
value: '__add__',
|
|
241
|
+
description: 'Add an API key or OAuth fallback account',
|
|
242
|
+
},
|
|
243
|
+
...payload.accounts.map(buildAccountDialogOption),
|
|
244
|
+
],
|
|
245
|
+
...(payload.enrollmentStatus && {
|
|
246
|
+
enrollmentStatus: payload.enrollmentStatus,
|
|
247
|
+
}),
|
|
248
|
+
...(modeAction && { modeAction }),
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function retainAccountDialogProjection(
|
|
253
|
+
current: Record<string, unknown>,
|
|
254
|
+
incoming: Record<string, unknown>,
|
|
255
|
+
): Record<string, unknown> {
|
|
256
|
+
return Array.isArray(incoming.accounts) ? incoming : current
|
|
257
|
+
}
|
|
258
|
+
|
|
70
259
|
function showText(api: TuiPluginApi, text: string) {
|
|
71
260
|
api.ui.dialog.setSize('xlarge')
|
|
72
261
|
api.ui.dialog.replace(() => (
|
|
@@ -99,6 +288,8 @@ export function buildPrimeStatusRows(accounts: PrimeAccountStatus[]): string[] {
|
|
|
99
288
|
const time = formatPrimeTime(account.lastPrimedAt)
|
|
100
289
|
if (account.lastResult === 'error') {
|
|
101
290
|
rows.push(`${account.label} \u00b7 primed ${time} err`)
|
|
291
|
+
} else if (account.lastResult === 'skipped') {
|
|
292
|
+
rows.push(`${account.label} \u00b7 primed ${time} skipped`)
|
|
102
293
|
} else {
|
|
103
294
|
rows.push(`${account.label} \u00b7 primed ${time} \u2713`)
|
|
104
295
|
}
|
|
@@ -377,17 +568,17 @@ export function openCommandDialog(
|
|
|
377
568
|
}
|
|
378
569
|
|
|
379
570
|
if (payload.command === 'claude-account') {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
(payload.knobs.claustrumDetection as string | undefined) ?? 'unknown'
|
|
571
|
+
let accountKnobs: Record<string, unknown> = payload.knobs
|
|
572
|
+
const accounts = normalizeAccountDialogPayload(accountKnobs).accounts
|
|
573
|
+
let statusMessage: { text: string; error?: boolean } | undefined
|
|
384
574
|
|
|
385
575
|
const updateAccounts = (r: {
|
|
386
576
|
text: string
|
|
387
577
|
knobs: Record<string, unknown>
|
|
388
578
|
}) => {
|
|
389
|
-
|
|
390
|
-
|
|
579
|
+
accountKnobs = retainAccountDialogProjection(accountKnobs, r.knobs)
|
|
580
|
+
const updated = normalizeAccountDialogPayload(accountKnobs).accounts
|
|
581
|
+
if (updated.length > 0) {
|
|
391
582
|
accounts.length = 0
|
|
392
583
|
accounts.push(...updated)
|
|
393
584
|
}
|
|
@@ -395,31 +586,61 @@ export function openCommandDialog(
|
|
|
395
586
|
|
|
396
587
|
const buildL1 = () => {
|
|
397
588
|
const DialogSelect = api.ui.DialogSelect<string>
|
|
398
|
-
const
|
|
399
|
-
title: string
|
|
400
|
-
value: string
|
|
401
|
-
description?: string
|
|
402
|
-
}> = [
|
|
403
|
-
{
|
|
404
|
-
title: 'Add account\u2026',
|
|
405
|
-
value: '__add__',
|
|
406
|
-
description: 'Add an API key or OAuth fallback account',
|
|
407
|
-
},
|
|
408
|
-
...accounts.map(buildAccountDialogOption),
|
|
409
|
-
]
|
|
589
|
+
const l1 = buildAccountDialogL1({ ...accountKnobs, accounts })
|
|
410
590
|
api.ui.dialog.setSize('xlarge')
|
|
411
591
|
api.ui.dialog.replace(() => (
|
|
412
592
|
<box flexDirection='column' padding={1} width='100%'>
|
|
413
|
-
<text>{`Claustrum: ${claustrumDetection}`}</text>
|
|
593
|
+
<text>{`Claustrum: ${normalizeAccountDialogPayload(accountKnobs).claustrumDetection}`}</text>
|
|
594
|
+
<text>{l1.header}</text>
|
|
595
|
+
{l1.enrollmentStatus && (
|
|
596
|
+
<box marginTop={1}>
|
|
597
|
+
<text>{l1.enrollmentStatus}</text>
|
|
598
|
+
</box>
|
|
599
|
+
)}
|
|
600
|
+
{statusMessage && (
|
|
601
|
+
<box marginTop={1}>
|
|
602
|
+
<text fg={statusMessage.error ? '#ef4444' : '#22c55e'}>
|
|
603
|
+
{statusMessage.text}
|
|
604
|
+
</text>
|
|
605
|
+
</box>
|
|
606
|
+
)}
|
|
414
607
|
<box marginTop={1}>
|
|
415
608
|
<DialogSelect
|
|
416
609
|
title='Claude accounts'
|
|
417
|
-
options={
|
|
610
|
+
options={l1.options}
|
|
418
611
|
onSelect={(option) => {
|
|
419
612
|
if (option.value === '__add__') {
|
|
613
|
+
statusMessage = undefined
|
|
420
614
|
openAddType()
|
|
421
615
|
return
|
|
422
616
|
}
|
|
617
|
+
if (option.value === '__custody-mode__' && l1.modeAction) {
|
|
618
|
+
void apply(l1.modeAction.command, l1.modeAction.arguments)
|
|
619
|
+
.then((r) => {
|
|
620
|
+
const isRefusalOrError =
|
|
621
|
+
r.text.includes('refused') ||
|
|
622
|
+
r.text.includes('unavailable') ||
|
|
623
|
+
r.text.includes('failed') ||
|
|
624
|
+
r.text.includes('Error') ||
|
|
625
|
+
r.text.includes('error')
|
|
626
|
+
if (isRefusalOrError) {
|
|
627
|
+
statusMessage = { text: r.text, error: true }
|
|
628
|
+
} else {
|
|
629
|
+
statusMessage = { text: r.text, error: false }
|
|
630
|
+
api.ui.toast({ message: r.text })
|
|
631
|
+
}
|
|
632
|
+
updateAccounts(r)
|
|
633
|
+
buildL1()
|
|
634
|
+
})
|
|
635
|
+
.catch((err) => {
|
|
636
|
+
const msg =
|
|
637
|
+
err instanceof Error ? err.message : String(err)
|
|
638
|
+
statusMessage = { text: `Error: ${msg}`, error: true }
|
|
639
|
+
buildL1()
|
|
640
|
+
})
|
|
641
|
+
return
|
|
642
|
+
}
|
|
643
|
+
statusMessage = undefined
|
|
423
644
|
const account = accounts.find((a) => a.id === option.value)
|
|
424
645
|
if (!account) return
|
|
425
646
|
if (account.role === 'main') {
|
|
@@ -737,37 +958,9 @@ export function openCommandDialog(
|
|
|
737
958
|
const DialogConfirm = api.ui.DialogConfirm
|
|
738
959
|
api.ui.dialog.setSize('xlarge')
|
|
739
960
|
|
|
740
|
-
const options
|
|
741
|
-
title:
|
|
742
|
-
|
|
743
|
-
description?: string
|
|
744
|
-
}> = []
|
|
745
|
-
if (!isMain) {
|
|
746
|
-
const toggleLabel = account.enabled ? 'Disable' : 'Enable'
|
|
747
|
-
options.push({
|
|
748
|
-
title: toggleLabel,
|
|
749
|
-
value: account.enabled ? 'disable' : 'enable',
|
|
750
|
-
description: account.enabled
|
|
751
|
-
? 'Stop using this fallback account'
|
|
752
|
-
: 'Allow this fallback account to be used',
|
|
753
|
-
})
|
|
754
|
-
options.push({
|
|
755
|
-
title: 'Move up',
|
|
756
|
-
value: 'move-up',
|
|
757
|
-
description: 'Higher priority in fallback order',
|
|
758
|
-
})
|
|
759
|
-
options.push({
|
|
760
|
-
title: 'Move down',
|
|
761
|
-
value: 'move-down',
|
|
762
|
-
description: 'Lower priority in fallback order',
|
|
763
|
-
})
|
|
764
|
-
options.push({
|
|
765
|
-
title: 'Remove\u2026',
|
|
766
|
-
value: 'remove',
|
|
767
|
-
description: 'Delete this account permanently',
|
|
768
|
-
})
|
|
769
|
-
}
|
|
770
|
-
options.push({ title: 'Back', value: 'back' })
|
|
961
|
+
const options = isMain
|
|
962
|
+
? [{ title: 'Back', value: 'back' }]
|
|
963
|
+
: buildManageAccountOptions(account)
|
|
771
964
|
|
|
772
965
|
api.ui.dialog.replace(() => (
|
|
773
966
|
<DialogSelect
|
|
@@ -785,13 +978,18 @@ export function openCommandDialog(
|
|
|
785
978
|
title={`Remove ${account.label}?`}
|
|
786
979
|
message={`Are you sure you want to remove the fallback account "${account.label}"?`}
|
|
787
980
|
onConfirm={() => {
|
|
788
|
-
void apply('claude-account', `remove ${account.id}`)
|
|
789
|
-
(r) => {
|
|
981
|
+
void apply('claude-account', `remove ${account.id}`)
|
|
982
|
+
.then((r) => {
|
|
790
983
|
api.ui.toast({ message: r.text })
|
|
791
984
|
updateAccounts(r)
|
|
792
985
|
buildL1()
|
|
793
|
-
}
|
|
794
|
-
|
|
986
|
+
})
|
|
987
|
+
.catch((err) => {
|
|
988
|
+
const msg =
|
|
989
|
+
err instanceof Error ? err.message : String(err)
|
|
990
|
+
statusMessage = { text: `Error: ${msg}`, error: true }
|
|
991
|
+
buildL1()
|
|
992
|
+
})
|
|
795
993
|
}}
|
|
796
994
|
onCancel={() => openManage(account, isMain)}
|
|
797
995
|
/>
|
|
@@ -799,18 +997,25 @@ export function openCommandDialog(
|
|
|
799
997
|
return
|
|
800
998
|
}
|
|
801
999
|
|
|
802
|
-
|
|
803
|
-
|
|
1000
|
+
const args = `${option.value} ${account.id}`
|
|
1001
|
+
void apply('claude-account', args)
|
|
1002
|
+
.then((r) => {
|
|
804
1003
|
api.ui.toast({ message: r.text })
|
|
805
1004
|
updateAccounts(r)
|
|
806
|
-
const updatedList =
|
|
1005
|
+
const updatedList = normalizeAccountDialogPayload(
|
|
1006
|
+
r.knobs,
|
|
1007
|
+
).accounts
|
|
807
1008
|
const refreshed =
|
|
808
1009
|
(updatedList && updatedList.length > 0
|
|
809
1010
|
? updatedList.find((a) => a.id === account.id)
|
|
810
1011
|
: undefined) ?? account
|
|
811
1012
|
openManage(refreshed, isMain)
|
|
812
|
-
}
|
|
813
|
-
|
|
1013
|
+
})
|
|
1014
|
+
.catch((err) => {
|
|
1015
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
1016
|
+
statusMessage = { text: `Error: ${msg}`, error: true }
|
|
1017
|
+
buildL1()
|
|
1018
|
+
})
|
|
814
1019
|
}}
|
|
815
1020
|
/>
|
|
816
1021
|
))
|