@cortexkit/opencode-openai-auth 0.11.1 → 1.0.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/README.md +15 -26
- package/dist/index-766sfrh5.js +1 -0
- package/dist/index-8kj7sa1p.js +12 -0
- package/dist/index-b3q3j39x.js +2 -0
- package/dist/index-bmd7x5aq.js +2 -0
- package/dist/index-eqh53k8v.js +2 -0
- package/dist/{index-sdt2d2ra.js → index-hy9030jh.js} +1 -1
- package/dist/index-jyr8y8k4.js +7 -0
- package/dist/index-rv08jmd4.js +2 -0
- package/dist/index-ww91hyzr.js +221 -0
- package/dist/index.d.ts +578 -145
- package/dist/index.js +1 -110
- package/dist/pool-lifecycle-DbDmgabd.d.ts +2430 -0
- package/dist/port-file-Do3lQIEk.d.ts +29 -0
- package/dist/protocol-BOBdkqqm.d.ts +198 -0
- package/dist/rpc/port-file.d.ts +4 -17
- package/dist/rpc/port-file.js +1 -1
- package/dist/rpc/protocol.d.ts +2 -2
- package/dist/rpc/protocol.js +1 -0
- package/dist/rpc/rpc-client.d.ts +5 -4
- package/dist/rpc/rpc-client.js +1 -1
- package/dist/rpc/rpc-dir.d.ts +2 -0
- package/dist/rpc/rpc-dir.js +1 -1
- package/dist/sidebar-state-D6cUKgkm.d.ts +385 -0
- package/dist/sidebar-state.d.ts +2 -2
- package/dist/sidebar-state.js +1 -1
- package/dist/token-util-wdza526f.js +1 -0
- package/dist/{token-9823y0pc.js → token-ypk61xj0.js} +1 -1
- package/dist/tui-preferences.js +5 -2
- package/dist/tui.d.ts +9 -4
- package/dist/v2/server.d.ts +54 -0
- package/dist/v2/server.js +4 -0
- package/package.json +21 -9
- package/src/tui/entry.mjs +26 -25
- package/src/tui-compiled/raw/logger.js +24 -0
- package/src/tui-compiled/raw/rpc/protocol.js +3 -0
- package/src/tui-compiled/raw/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/raw/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/raw/selector.js +21 -0
- package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/raw/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/raw/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/raw/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/raw/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/raw/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/raw/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/raw/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/raw/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/raw/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/raw/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/raw/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/raw/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/raw/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/raw/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/raw/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/raw/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/raw/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/raw/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/raw/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/raw/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/raw/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/raw/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/raw/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/raw/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/raw/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/raw/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/raw/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/raw/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/raw/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/raw/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/raw/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/raw/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/raw/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/raw/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/raw/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/raw/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/raw/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/raw/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/raw/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/raw/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/raw/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/raw/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/raw/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/raw/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/raw/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/raw/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/raw/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/raw/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/raw/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/raw/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/raw/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/raw/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/raw/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/raw/sidebar-state.js +960 -0
- package/src/tui-compiled/raw/tui/command-dialogs.tsx +424 -0
- package/src/tui-compiled/raw/tui-preferences.js +135 -0
- package/src/tui-compiled/raw/tui.tsx +135 -143
- package/src/tui-compiled/runtime/logger.js +24 -0
- package/src/tui-compiled/runtime/rpc/protocol.js +3 -0
- package/src/tui-compiled/runtime/rpc/rpc-client.js +7 -0
- package/src/tui-compiled/runtime/rpc/rpc-dir.js +27 -0
- package/src/tui-compiled/runtime/selector.js +21 -0
- package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +138 -0
- package/src/tui-compiled/runtime/shared/04a6357627de5ed0-errors.js +38 -0
- package/src/tui-compiled/runtime/shared/053cc17b5419261e-codec.js +12 -0
- package/src/tui-compiled/runtime/shared/0766d78c6f481f18-watcher.js +157 -0
- package/src/tui-compiled/runtime/shared/08eef45626a94992-reserved.js +13 -0
- package/src/tui-compiled/runtime/shared/39c3cb05738fcf6d-projection.js +134 -0
- package/src/tui-compiled/runtime/shared/3c2d4169d00f907e-tui-support.js +5 -0
- package/src/tui-compiled/runtime/shared/3e7d45a77c6ad4d5-index.js +4 -0
- package/src/tui-compiled/runtime/shared/415792e85d80819e-capture-sink.js +13 -0
- package/src/tui-compiled/runtime/shared/42ae93ab554fcec2-open-url.js +28 -0
- package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +136 -0
- package/src/tui-compiled/runtime/shared/458b934e92490331-mutate.js +500 -0
- package/src/tui-compiled/runtime/shared/45bf5343bbbf9add-admission.js +142 -0
- package/src/tui-compiled/runtime/shared/46f3dff1266160e8-client.js +2 -0
- package/src/tui-compiled/runtime/shared/4825e03209d6ae3e-with-lock.js +48 -0
- package/src/tui-compiled/runtime/shared/4f36d333d081d040-merge.js +162 -0
- package/src/tui-compiled/runtime/shared/54eba94fd64c8c3c-index.js +7 -0
- package/src/tui-compiled/runtime/shared/7211cfec06a298bc-atomic-write.js +80 -0
- package/src/tui-compiled/runtime/shared/754d6017f8e419cf-builtins.js +534 -0
- package/src/tui-compiled/runtime/shared/75bd8c3bd63b315f-identity.js +79 -0
- package/src/tui-compiled/runtime/shared/7a91bb630e6f4804-index.js +1 -0
- package/src/tui-compiled/runtime/shared/826342a4b6a8b89b-model.js +16 -0
- package/src/tui-compiled/runtime/shared/83068c0dbe8a6f13-publication.js +304 -0
- package/src/tui-compiled/runtime/shared/836fc615de6afbd9-settings.js +120 -0
- package/src/tui-compiled/runtime/shared/83bc3ba9d0fe2761-runtime.js +68 -0
- package/src/tui-compiled/runtime/shared/8a18abe4fa2977c8-menu.js +300 -0
- package/src/tui-compiled/runtime/shared/901e149c5a0dbbff-sticky.js +339 -0
- package/src/tui-compiled/runtime/shared/9020c407ce64f672-ordered.js +58 -0
- package/src/tui-compiled/runtime/shared/905bffc0220f43d0-map.js +105 -0
- package/src/tui-compiled/runtime/shared/94820e324f2b4c09-seam.js +301 -0
- package/src/tui-compiled/runtime/shared/97484d9bb98c705b-provider-state.js +226 -0
- package/src/tui-compiled/runtime/shared/9968e3cade6969a1-index.js +2 -0
- package/src/tui-compiled/runtime/shared/9b4da614c45f9cd0-lock-constants.js +14 -0
- package/src/tui-compiled/runtime/shared/9d3fc9e37456504e-refresh.js +225 -0
- package/src/tui-compiled/runtime/shared/9deae3c4da4fd63b-hooks.js +34 -0
- package/src/tui-compiled/runtime/shared/a09b6a533c11858f-tui-preferences.js +77 -0
- package/src/tui-compiled/runtime/shared/a62a27a204266acd-attribution.js +60 -0
- package/src/tui-compiled/runtime/shared/a910277ac43a8626-pull.js +143 -0
- package/src/tui-compiled/runtime/shared/b85c5301fddcb98a-paths.js +5 -0
- package/src/tui-compiled/runtime/shared/c14988621dc60a75-port-file.js +217 -0
- package/src/tui-compiled/runtime/shared/c64d7afbc14ca125-sidebar-file.js +157 -0
- package/src/tui-compiled/runtime/shared/c6b176708c446d6d-pins.js +34 -0
- package/src/tui-compiled/runtime/shared/c7d252bedee01600-pi.js +206 -0
- package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +120 -0
- package/src/tui-compiled/runtime/shared/cc9e0702a752cea7-index.js +3 -0
- package/src/tui-compiled/runtime/shared/cd94316a888b0a8e-schema.js +754 -0
- package/src/tui-compiled/runtime/shared/d319517e3ea6ad70-redact.js +83 -0
- package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +5 -0
- package/src/tui-compiled/runtime/shared/d669d72c40e9e116-logger.js +19 -0
- package/src/tui-compiled/runtime/shared/d7dcba96fea1685c-engine.js +246 -0
- package/src/tui-compiled/runtime/shared/dbef697d1df1cd84-index.js +5 -0
- package/src/tui-compiled/runtime/shared/e4acc4cb713f300c-protocol.js +3 -0
- package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +501 -0
- package/src/tui-compiled/runtime/shared/ec971142548df90e-index.js +6 -0
- package/src/tui-compiled/runtime/shared/f37fcefc7aedbf25-error.js +11 -0
- package/src/tui-compiled/runtime/shared/fbcdcd0f925e74c0-rpc-client.js +253 -0
- package/src/tui-compiled/runtime/shared/fd45e82f2f9c0a0d-torn.js +294 -0
- package/src/tui-compiled/runtime/shared/fe02abeadc535f77-rows.js +1258 -0
- package/src/tui-compiled/runtime/sidebar-state.js +960 -0
- package/src/tui-compiled/runtime/tui/command-dialogs.js +378 -0
- package/src/tui-compiled/runtime/tui-preferences.js +135 -0
- package/src/tui-compiled/runtime/{tui.tsx → tui.js} +288 -316
- package/dist/index-4effjmr6.js +0 -2
- package/dist/index-9ehhb051.js +0 -2
- package/dist/index-g3hsdjh4.js +0 -2
- package/dist/index-hh52v8sb.js +0 -2
- package/dist/index-jwn4r3sw.js +0 -1
- package/dist/index-sq36b120.js +0 -211
- package/dist/internal-BTndPmW2.d.ts +0 -914
- package/dist/quota-normalize-08hpp51v.js +0 -1
- package/dist/sidebar-state-CoKYKcLn.d.ts +0 -220
- package/dist/token-util-ypgz7rge.js +0 -1
- package/src/tui-compiled/raw/core-account-paths.ts +0 -114
- package/src/tui-compiled/raw/core-custody-state.ts +0 -243
- package/src/tui-compiled/raw/logger.ts +0 -53
- package/src/tui-compiled/raw/rpc-port-file.ts +0 -177
- package/src/tui-compiled/raw/rpc-protocol.ts +0 -12
- package/src/tui-compiled/raw/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/raw/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/raw/sidebar-state.ts +0 -1659
- package/src/tui-compiled/raw/tui-command-dialogs.tsx +0 -1096
- package/src/tui-compiled/raw/tui-preferences.ts +0 -328
- package/src/tui-compiled/runtime/core-account-paths.ts +0 -114
- package/src/tui-compiled/runtime/core-custody-state.ts +0 -243
- package/src/tui-compiled/runtime/logger.ts +0 -53
- package/src/tui-compiled/runtime/rpc-port-file.ts +0 -177
- package/src/tui-compiled/runtime/rpc-protocol.ts +0 -12
- package/src/tui-compiled/runtime/rpc-rpc-client.ts +0 -82
- package/src/tui-compiled/runtime/rpc-rpc-dir.ts +0 -46
- package/src/tui-compiled/runtime/sidebar-state.ts +0 -1659
- package/src/tui-compiled/runtime/tui-command-dialogs.tsx +0 -855
- package/src/tui-compiled/runtime/tui-preferences.ts +0 -328
- package/src/tui-compiled/shared/internal.ts +0 -6
- package/src/tui-compiled/shared/logger.ts +0 -264
- package/src/tui-compiled/shared/paths.ts +0 -33
- package/src/tui-compiled/shared/refresh-file-lock.ts +0 -384
- package/src/tui-compiled/shared/util/error.ts +0 -9
- package/src/tui-compiled/shared/util/open-url.ts +0 -30
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { watch } from 'node:fs'
|
|
2
|
-
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
|
-
import { basename, dirname, join } from 'node:path'
|
|
5
|
-
import { applyEdits, modify, type ParseError, parse } from 'jsonc-parser'
|
|
6
|
-
|
|
7
|
-
export const TUI_PREFS_FILE_ENV = 'OPENCODE_TUI_PREFERENCES_FILE'
|
|
8
|
-
const FILE_NAME = 'tui-preferences.jsonc'
|
|
9
|
-
|
|
10
|
-
// Shared preferences file for opencode TUI plugins. One top-level key per
|
|
11
|
-
// plugin (short name, e.g. "openai-auth"). The file is optional: every
|
|
12
|
-
// reader must fall back to defaults when it is missing or malformed.
|
|
13
|
-
export function getTuiPreferencesFile(): string {
|
|
14
|
-
const override = process.env[TUI_PREFS_FILE_ENV]
|
|
15
|
-
if (override) return override
|
|
16
|
-
const configDir =
|
|
17
|
-
process.env.OPENCODE_CONFIG_DIR ||
|
|
18
|
-
join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode')
|
|
19
|
-
return join(configDir, FILE_NAME)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
23
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Tolerant read: missing file, parse errors, or a non-object root all resolve
|
|
27
|
-
// to {} so the sidebar never crashes on user-edited content. jsonc-parser's
|
|
28
|
-
// fault-tolerant parse can still hand back a partial object for an
|
|
29
|
-
// unterminated/bracketed file or trailing garbage, so we collect errors and
|
|
30
|
-
// treat any reported fault as malformed.
|
|
31
|
-
export async function readTuiPreferencesFile(): Promise<
|
|
32
|
-
Record<string, unknown>
|
|
33
|
-
> {
|
|
34
|
-
try {
|
|
35
|
-
const raw = await readFile(getTuiPreferencesFile(), 'utf8')
|
|
36
|
-
const errors: ParseError[] = []
|
|
37
|
-
const root: unknown = parse(raw, errors, { allowTrailingComma: true })
|
|
38
|
-
if (errors.length > 0) return {}
|
|
39
|
-
return isRecord(root) ? root : {}
|
|
40
|
-
} catch {
|
|
41
|
-
return {}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export const PLUGIN_KEY = 'openai-auth'
|
|
46
|
-
export const DEFAULT_SLOT_ORDER = 160
|
|
47
|
-
|
|
48
|
-
export interface OpenaiAuthTuiPrefs {
|
|
49
|
-
forceToTop: boolean
|
|
50
|
-
order: number
|
|
51
|
-
startCollapsed: boolean
|
|
52
|
-
rememberCollapsed: boolean
|
|
53
|
-
// null = never persisted; seed the UI from startCollapsed instead.
|
|
54
|
-
collapsed: boolean | null
|
|
55
|
-
pollMs: number
|
|
56
|
-
refreshDebounceMs: number
|
|
57
|
-
header: {
|
|
58
|
-
label: string
|
|
59
|
-
showVersion: boolean
|
|
60
|
-
}
|
|
61
|
-
sections: {
|
|
62
|
-
quota: boolean
|
|
63
|
-
fallbackAccounts: boolean
|
|
64
|
-
routing: boolean
|
|
65
|
-
health: boolean
|
|
66
|
-
pacing: boolean
|
|
67
|
-
}
|
|
68
|
-
appearance: {
|
|
69
|
-
barWidth: number
|
|
70
|
-
barFilledChar: string
|
|
71
|
-
barEmptyChar: string
|
|
72
|
-
warnThreshold: number
|
|
73
|
-
errorThreshold: number
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export type AppearancePrefs = OpenaiAuthTuiPrefs['appearance']
|
|
78
|
-
|
|
79
|
-
export const DEFAULT_PREFS: OpenaiAuthTuiPrefs = {
|
|
80
|
-
forceToTop: false,
|
|
81
|
-
order: DEFAULT_SLOT_ORDER,
|
|
82
|
-
startCollapsed: false,
|
|
83
|
-
rememberCollapsed: true,
|
|
84
|
-
collapsed: null,
|
|
85
|
-
pollMs: 1500,
|
|
86
|
-
refreshDebounceMs: 200,
|
|
87
|
-
header: { label: 'OPENAI', showVersion: true },
|
|
88
|
-
sections: {
|
|
89
|
-
quota: true,
|
|
90
|
-
fallbackAccounts: true,
|
|
91
|
-
routing: true,
|
|
92
|
-
health: true,
|
|
93
|
-
pacing: true,
|
|
94
|
-
},
|
|
95
|
-
appearance: {
|
|
96
|
-
barWidth: 10,
|
|
97
|
-
barFilledChar: '\u2588',
|
|
98
|
-
barEmptyChar: '\u2591',
|
|
99
|
-
warnThreshold: 50,
|
|
100
|
-
errorThreshold: 80,
|
|
101
|
-
},
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function bool(value: unknown, fallback: boolean): boolean {
|
|
105
|
-
return typeof value === 'boolean' ? value : fallback
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function int(
|
|
109
|
-
value: unknown,
|
|
110
|
-
fallback: number,
|
|
111
|
-
min: number,
|
|
112
|
-
max: number,
|
|
113
|
-
): number {
|
|
114
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) return fallback
|
|
115
|
-
return Math.min(Math.max(Math.round(value), min), max)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function label(value: unknown, fallback: string, maxLength: number): string {
|
|
119
|
-
if (typeof value !== 'string' || value.length === 0) return fallback
|
|
120
|
-
return value.slice(0, maxLength)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function char(value: unknown, fallback: string): string {
|
|
124
|
-
if (typeof value !== 'string') return fallback
|
|
125
|
-
const first = [...value][0]
|
|
126
|
-
return first ?? fallback
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Per-key validation: every value is independently clamped/defaulted so one
|
|
130
|
-
// bad entry never poisons the rest. Never throws.
|
|
131
|
-
export function resolveOpenaiAuthPrefs(
|
|
132
|
-
root: Record<string, unknown>,
|
|
133
|
-
): OpenaiAuthTuiPrefs {
|
|
134
|
-
const entry = root[PLUGIN_KEY]
|
|
135
|
-
if (!isRecord(entry)) return structuredClone(DEFAULT_PREFS)
|
|
136
|
-
|
|
137
|
-
const d = DEFAULT_PREFS
|
|
138
|
-
const header = isRecord(entry.header) ? entry.header : {}
|
|
139
|
-
const sections = isRecord(entry.sections) ? entry.sections : {}
|
|
140
|
-
const appearance = isRecord(entry.appearance) ? entry.appearance : {}
|
|
141
|
-
|
|
142
|
-
const warnThreshold = int(
|
|
143
|
-
appearance.warnThreshold,
|
|
144
|
-
d.appearance.warnThreshold,
|
|
145
|
-
0,
|
|
146
|
-
99,
|
|
147
|
-
)
|
|
148
|
-
const errorThreshold = Math.max(
|
|
149
|
-
int(appearance.errorThreshold, d.appearance.errorThreshold, 0, 100),
|
|
150
|
-
Math.min(warnThreshold + 1, 100),
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
forceToTop: bool(entry.forceToTop, d.forceToTop),
|
|
155
|
-
order: int(entry.order, d.order, -10000, 10000),
|
|
156
|
-
startCollapsed: bool(entry.startCollapsed, d.startCollapsed),
|
|
157
|
-
rememberCollapsed: bool(entry.rememberCollapsed, d.rememberCollapsed),
|
|
158
|
-
collapsed: typeof entry.collapsed === 'boolean' ? entry.collapsed : null,
|
|
159
|
-
pollMs: int(entry.pollMs, d.pollMs, 500, 30000),
|
|
160
|
-
refreshDebounceMs: int(
|
|
161
|
-
entry.refreshDebounceMs,
|
|
162
|
-
d.refreshDebounceMs,
|
|
163
|
-
50,
|
|
164
|
-
5000,
|
|
165
|
-
),
|
|
166
|
-
header: {
|
|
167
|
-
label: label(header.label, d.header.label, 20),
|
|
168
|
-
showVersion: bool(header.showVersion, d.header.showVersion),
|
|
169
|
-
},
|
|
170
|
-
sections: {
|
|
171
|
-
quota: bool(sections.quota, d.sections.quota),
|
|
172
|
-
fallbackAccounts: bool(
|
|
173
|
-
sections.fallbackAccounts,
|
|
174
|
-
d.sections.fallbackAccounts,
|
|
175
|
-
),
|
|
176
|
-
routing: bool(sections.routing, d.sections.routing),
|
|
177
|
-
health: bool(sections.health, d.sections.health),
|
|
178
|
-
pacing: bool(sections.pacing, d.sections.pacing),
|
|
179
|
-
},
|
|
180
|
-
appearance: {
|
|
181
|
-
barWidth: int(appearance.barWidth, d.appearance.barWidth, 4, 40),
|
|
182
|
-
barFilledChar: char(appearance.barFilledChar, d.appearance.barFilledChar),
|
|
183
|
-
barEmptyChar: char(appearance.barEmptyChar, d.appearance.barEmptyChar),
|
|
184
|
-
warnThreshold,
|
|
185
|
-
errorThreshold,
|
|
186
|
-
},
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const FORCE_TOP_BASE = -100000
|
|
191
|
-
|
|
192
|
-
// Shared forceToTop convention: forced plugins sort below FORCE_TOP_BASE,
|
|
193
|
-
// ordered among themselves by their top-level key's position in the file, so
|
|
194
|
-
// users reprioritize by reordering keys. The user-facing `order` knob clamps
|
|
195
|
-
// to -10000..10000, strictly above the forced band, so a manual order can
|
|
196
|
-
// never beat forceToTop. Host slots render ascending by order; opencode's
|
|
197
|
-
// built-ins occupy 100-500.
|
|
198
|
-
//
|
|
199
|
-
// Key-naming requirement: plugin keys must be non-integer-like short names
|
|
200
|
-
// (e.g. "openai-auth"). JavaScript object key iteration order hoists
|
|
201
|
-
// integer-like keys ("0", "1", "42") ahead of any string keys, which would
|
|
202
|
-
// skew the indexOf-based ordering of forced plugins. The shared
|
|
203
|
-
// `tui-preferences.jsonc` convention requires non-numeric names, so this
|
|
204
|
-
// implementation does not paper over the JS quirk.
|
|
205
|
-
export function computeEffectiveOrder(
|
|
206
|
-
root: Record<string, unknown>,
|
|
207
|
-
pluginKey: string,
|
|
208
|
-
defaultOrder: number,
|
|
209
|
-
): number {
|
|
210
|
-
const entry = root[pluginKey]
|
|
211
|
-
if (!isRecord(entry)) return defaultOrder
|
|
212
|
-
if (entry.forceToTop === true) {
|
|
213
|
-
return FORCE_TOP_BASE + Object.keys(root).indexOf(pluginKey)
|
|
214
|
-
}
|
|
215
|
-
return int(entry.order, defaultOrder, -10000, 10000)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const TEMPLATE = `// Shared preferences for opencode TUI plugins.
|
|
219
|
-
// One top-level key per plugin (short name). See each plugin's README for
|
|
220
|
-
// its supported settings. This file is safe to hand-edit; plugins update
|
|
221
|
-
// individual keys in place and preserve comments.
|
|
222
|
-
{}
|
|
223
|
-
`
|
|
224
|
-
|
|
225
|
-
type JsonValue = string | number | boolean | null
|
|
226
|
-
|
|
227
|
-
async function writePreference(
|
|
228
|
-
pluginKey: string,
|
|
229
|
-
path: string[],
|
|
230
|
-
value: JsonValue,
|
|
231
|
-
): Promise<void> {
|
|
232
|
-
const file = getTuiPreferencesFile()
|
|
233
|
-
await mkdir(dirname(file), { recursive: true })
|
|
234
|
-
let text: string
|
|
235
|
-
try {
|
|
236
|
-
text = await readFile(file, 'utf8')
|
|
237
|
-
} catch {
|
|
238
|
-
text = ''
|
|
239
|
-
}
|
|
240
|
-
if (text.trim() === '') text = TEMPLATE
|
|
241
|
-
const edits = modify(text, [pluginKey, ...path], value, {
|
|
242
|
-
formattingOptions: { insertSpaces: true, tabSize: 2 },
|
|
243
|
-
})
|
|
244
|
-
const next = applyEdits(text, edits)
|
|
245
|
-
const tmp = `${file}.${process.pid}.tmp`
|
|
246
|
-
await writeFile(tmp, next, 'utf8')
|
|
247
|
-
await rename(tmp, file)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
let writeChain: Promise<void> = Promise.resolve()
|
|
251
|
-
|
|
252
|
-
// Writes are serialized on a promise chain: each update re-reads the file,
|
|
253
|
-
// applies a minimal comment-preserving edit to one property, and replaces the
|
|
254
|
-
// file atomically (temp + rename in the same directory). Best-effort by
|
|
255
|
-
// design — preferences are never worth crashing the TUI over.
|
|
256
|
-
export function queueTuiPreferenceUpdate(
|
|
257
|
-
pluginKey: string,
|
|
258
|
-
path: string[],
|
|
259
|
-
value: JsonValue,
|
|
260
|
-
): Promise<void> {
|
|
261
|
-
writeChain = writeChain
|
|
262
|
-
.then(() => writePreference(pluginKey, path, value))
|
|
263
|
-
.catch(() => {})
|
|
264
|
-
return writeChain
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const WATCH_DEBOUNCE_MS = 150
|
|
268
|
-
|
|
269
|
-
// Watches the directory rather than the file: editors and our own atomic
|
|
270
|
-
// writes replace the file via rename, which kills file-level watchers.
|
|
271
|
-
//
|
|
272
|
-
// Filtering is two-stage:
|
|
273
|
-
// 1. Filename pre-filter: only debounce events for the preferences file
|
|
274
|
-
// name, or our atomic-write temp file. This is a cheap first pass that
|
|
275
|
-
// drops the common case (unrelated sibling files).
|
|
276
|
-
// 2. Content check inside the debounce: after the timer fires, re-read
|
|
277
|
-
// the file and compare it against the last seen content. Only fire
|
|
278
|
-
// `onChange` when the content actually changed. This is the authority.
|
|
279
|
-
// Some platforms (notably macOS FSEvents and a few inotify backends)
|
|
280
|
-
// can misattribute a rename of an unrelated sibling file to the
|
|
281
|
-
// real preferences filename in addition to emitting the sibling's
|
|
282
|
-
// own event, so a name-only filter still produces a stray callback.
|
|
283
|
-
// A content comparison is robust against that, against coalesced
|
|
284
|
-
// events, and against mtime granularity.
|
|
285
|
-
//
|
|
286
|
-
// Returns a disposer; never throws.
|
|
287
|
-
export function watchTuiPreferences(onChange: () => void): () => void {
|
|
288
|
-
const file = getTuiPreferencesFile()
|
|
289
|
-
const name = basename(file)
|
|
290
|
-
let timer: ReturnType<typeof setTimeout> | null = null
|
|
291
|
-
let lastSeen: string | null = null
|
|
292
|
-
// Seed asynchronously; a real change that fires before the seed resolves
|
|
293
|
-
// still wins because the debounce re-reads the file fresh and compares
|
|
294
|
-
// against `lastSeen` (which will be `null` → does not match → fires).
|
|
295
|
-
void readFile(file, 'utf8')
|
|
296
|
-
.then((text) => {
|
|
297
|
-
if (lastSeen === null) lastSeen = text
|
|
298
|
-
})
|
|
299
|
-
.catch(() => {})
|
|
300
|
-
try {
|
|
301
|
-
const watcher = watch(dirname(file), (_event, filename) => {
|
|
302
|
-
// Exact match against the preferences file name, plus the temp file
|
|
303
|
-
// we use for atomic writes (carrying our pid + `.tmp`).
|
|
304
|
-
const isOurs =
|
|
305
|
-
filename === name ||
|
|
306
|
-
(filename?.startsWith(`${name}.`) && filename.endsWith('.tmp'))
|
|
307
|
-
if (filename != null && !isOurs) return
|
|
308
|
-
if (timer) clearTimeout(timer)
|
|
309
|
-
timer = setTimeout(() => {
|
|
310
|
-
timer = null
|
|
311
|
-
void readFile(file, 'utf8')
|
|
312
|
-
.catch(() => null)
|
|
313
|
-
.then((text) => {
|
|
314
|
-
if (text === null) return
|
|
315
|
-
if (text === lastSeen) return
|
|
316
|
-
lastSeen = text
|
|
317
|
-
onChange()
|
|
318
|
-
})
|
|
319
|
-
}, WATCH_DEBOUNCE_MS)
|
|
320
|
-
})
|
|
321
|
-
return () => {
|
|
322
|
-
if (timer) clearTimeout(timer)
|
|
323
|
-
watcher.close()
|
|
324
|
-
}
|
|
325
|
-
} catch {
|
|
326
|
-
return () => {}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { realpathSync } from 'node:fs'
|
|
2
|
-
import { homedir } from 'node:os'
|
|
3
|
-
import { basename, dirname, join, resolve } from 'node:path'
|
|
4
|
-
import {
|
|
5
|
-
ACCOUNT_FILE_NAME,
|
|
6
|
-
ACCOUNT_STATE_FILE_NAME,
|
|
7
|
-
type AccountPaths,
|
|
8
|
-
deriveStatePath,
|
|
9
|
-
} from '../shared/internal.ts'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Where OpenCode's account store lives.
|
|
13
|
-
*
|
|
14
|
-
* This is the ONLY definition of this host's resolution rules. The store code
|
|
15
|
-
* itself takes both paths as arguments and resolves nothing, so the lock and
|
|
16
|
-
* the write it guards can never disagree about which file they mean — a
|
|
17
|
-
* disagreement would lose mutual exclusion on a file holding credentials while
|
|
18
|
-
* every test still passed, since each side works on its own.
|
|
19
|
-
*
|
|
20
|
-
* The file names come from the core so the two hosts cannot drift apart on what
|
|
21
|
-
* the store is called.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export type { AccountPaths }
|
|
25
|
-
export { ACCOUNT_FILE_NAME, ACCOUNT_STATE_FILE_NAME, deriveStatePath }
|
|
26
|
-
|
|
27
|
-
function getConfigDir() {
|
|
28
|
-
if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
|
|
29
|
-
return process.env.OPENCODE_CONFIG_DIR.trim()
|
|
30
|
-
}
|
|
31
|
-
return join(
|
|
32
|
-
process.env.XDG_CONFIG_HOME || join(homedir(), '.config'),
|
|
33
|
-
'opencode',
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function getAccountStoragePath() {
|
|
38
|
-
return (
|
|
39
|
-
process.env.OPENCODE_OPENAI_AUTH_FILE?.trim() ||
|
|
40
|
-
join(getConfigDir(), ACCOUNT_FILE_NAME)
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function normalizePathForComparison(path: string, platform: NodeJS.Platform) {
|
|
45
|
-
const resolved = resolve(path)
|
|
46
|
-
return platform === 'win32' || platform === 'darwin'
|
|
47
|
-
? resolved.toLowerCase()
|
|
48
|
-
: resolved
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function realpathForComparison(path: string): string | undefined {
|
|
52
|
-
try {
|
|
53
|
-
return realpathSync.native(path)
|
|
54
|
-
} catch {
|
|
55
|
-
try {
|
|
56
|
-
return join(realpathSync.native(dirname(path)), basename(path))
|
|
57
|
-
} catch {
|
|
58
|
-
return undefined
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Detect path aliases without requiring either file to exist. This is defense
|
|
65
|
-
* in depth: hardlinks and bind mounts can still make distinct paths share a
|
|
66
|
-
* file, because neither is distinguishable through pathname identity.
|
|
67
|
-
*/
|
|
68
|
-
export function accountPathsCollide(
|
|
69
|
-
configPath: string,
|
|
70
|
-
statePath: string,
|
|
71
|
-
platform: NodeJS.Platform = process.platform,
|
|
72
|
-
) {
|
|
73
|
-
if (
|
|
74
|
-
normalizePathForComparison(configPath, platform) ===
|
|
75
|
-
normalizePathForComparison(statePath, platform)
|
|
76
|
-
) {
|
|
77
|
-
return true
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const configIdentity = realpathForComparison(configPath)
|
|
81
|
-
const stateIdentity = realpathForComparison(statePath)
|
|
82
|
-
return (
|
|
83
|
-
configIdentity !== undefined &&
|
|
84
|
-
stateIdentity !== undefined &&
|
|
85
|
-
normalizePathForComparison(configIdentity, platform) ===
|
|
86
|
-
normalizePathForComparison(stateIdentity, platform)
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getAccountStatePath(configPath = getAccountStoragePath()) {
|
|
91
|
-
const explicit = process.env.OPENCODE_OPENAI_AUTH_STATE_FILE?.trim()
|
|
92
|
-
if (explicit) {
|
|
93
|
-
if (accountPathsCollide(configPath, explicit)) {
|
|
94
|
-
throw new Error(
|
|
95
|
-
`OPENCODE_OPENAI_AUTH_STATE_FILE resolves to the config path (${resolve(configPath)}). Set OPENCODE_OPENAI_AUTH_STATE_FILE to a different file.`,
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
return explicit
|
|
99
|
-
}
|
|
100
|
-
return deriveStatePath(configPath)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Both file paths a store call needs, resolved the way this host resolves them.
|
|
105
|
-
*
|
|
106
|
-
* Every call site that used to pass a single config path and let the store
|
|
107
|
-
* derive the state path now passes this pair instead, so the state-file
|
|
108
|
-
* override keeps being honoured in exactly the places it was before.
|
|
109
|
-
*/
|
|
110
|
-
export function getAccountPaths(
|
|
111
|
-
configPath = getAccountStoragePath(),
|
|
112
|
-
): AccountPaths {
|
|
113
|
-
return { configPath, statePath: getAccountStatePath(configPath) }
|
|
114
|
-
}
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
export type CustodyMode = 'local' | 'claustrum'
|
|
2
|
-
|
|
3
|
-
export type ManifestCustodyState = 'absent' | 'present' | 'unreadable'
|
|
4
|
-
|
|
5
|
-
export type LocalCustodyState =
|
|
6
|
-
| 'real'
|
|
7
|
-
| 'tombstone'
|
|
8
|
-
| 'empty'
|
|
9
|
-
| 'gone'
|
|
10
|
-
| 'slot-absent'
|
|
11
|
-
|
|
12
|
-
export type VaultCustodyState =
|
|
13
|
-
| 'serves'
|
|
14
|
-
| 'cold'
|
|
15
|
-
| 'needs_reauth'
|
|
16
|
-
| 'identity_mismatch'
|
|
17
|
-
| 'no_handle'
|
|
18
|
-
|
|
19
|
-
export const CUSTODY_INERT_REASONS = [
|
|
20
|
-
'needs-login',
|
|
21
|
-
'mode-mismatch',
|
|
22
|
-
'corrupt-under-binding',
|
|
23
|
-
'manifest-unreadable',
|
|
24
|
-
'unbound-under-claustrum',
|
|
25
|
-
'orphan-tombstone',
|
|
26
|
-
'vault-cold',
|
|
27
|
-
'vault-reauth',
|
|
28
|
-
'identity-mismatch',
|
|
29
|
-
'no-handle',
|
|
30
|
-
'takeover-incomplete',
|
|
31
|
-
'new-local-family-under-claustrum',
|
|
32
|
-
'takeover-incomplete/vault-unavailable',
|
|
33
|
-
'takeover-incomplete/slot-absent',
|
|
34
|
-
] as const
|
|
35
|
-
|
|
36
|
-
export type CustodyInertReason = (typeof CUSTODY_INERT_REASONS)[number]
|
|
37
|
-
|
|
38
|
-
export type CustodyVerdict =
|
|
39
|
-
| { kind: 'LOCAL' }
|
|
40
|
-
| {
|
|
41
|
-
kind: 'VAULT'
|
|
42
|
-
canonicalize?: boolean
|
|
43
|
-
installTombstone?: boolean
|
|
44
|
-
}
|
|
45
|
-
| {
|
|
46
|
-
kind: 'INERT'
|
|
47
|
-
reason: CustodyInertReason
|
|
48
|
-
installTombstone?: boolean
|
|
49
|
-
canonicalize?: boolean
|
|
50
|
-
}
|
|
51
|
-
| { kind: 'NEEDS_LOGIN'; reason?: 'corrupt' }
|
|
52
|
-
|
|
53
|
-
export interface EvaluateCustodyStartupInput {
|
|
54
|
-
mode: CustodyMode
|
|
55
|
-
manifest: ManifestCustodyState
|
|
56
|
-
local: LocalCustodyState
|
|
57
|
-
vault: () => VaultCustodyState
|
|
58
|
-
manifestFailureReason?: string
|
|
59
|
-
fingerprintMatch?: boolean
|
|
60
|
-
verifiedInProcessLogin: boolean
|
|
61
|
-
isMain?: boolean
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function unreachable(value: never): never {
|
|
65
|
-
return value
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function withCanonicalization<
|
|
69
|
-
T extends Extract<CustodyVerdict, { kind: 'VAULT' | 'INERT' }>,
|
|
70
|
-
>(local: LocalCustodyState, verdict: T): T | (T & { canonicalize: true }) {
|
|
71
|
-
return local === 'empty' ? { ...verdict, canonicalize: true } : verdict
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function evaluateLocal(local: LocalCustodyState): CustodyVerdict {
|
|
75
|
-
switch (local) {
|
|
76
|
-
case 'real':
|
|
77
|
-
return { kind: 'LOCAL' }
|
|
78
|
-
case 'tombstone':
|
|
79
|
-
case 'empty':
|
|
80
|
-
case 'slot-absent':
|
|
81
|
-
return { kind: 'NEEDS_LOGIN' }
|
|
82
|
-
case 'gone':
|
|
83
|
-
return { kind: 'NEEDS_LOGIN', reason: 'corrupt' }
|
|
84
|
-
}
|
|
85
|
-
return unreachable(local)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function evaluateLocalWithBinding(
|
|
89
|
-
local: LocalCustodyState,
|
|
90
|
-
verifiedInProcessLogin: boolean,
|
|
91
|
-
): CustodyVerdict {
|
|
92
|
-
switch (local) {
|
|
93
|
-
case 'real':
|
|
94
|
-
return verifiedInProcessLogin
|
|
95
|
-
? { kind: 'LOCAL' }
|
|
96
|
-
: { kind: 'INERT', reason: 'needs-login' }
|
|
97
|
-
case 'tombstone':
|
|
98
|
-
case 'empty':
|
|
99
|
-
case 'slot-absent':
|
|
100
|
-
return { kind: 'INERT', reason: 'mode-mismatch' }
|
|
101
|
-
case 'gone':
|
|
102
|
-
return { kind: 'INERT', reason: 'corrupt-under-binding' }
|
|
103
|
-
}
|
|
104
|
-
return unreachable(local)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function evaluateUnboundClaustrum(local: LocalCustodyState): CustodyVerdict {
|
|
108
|
-
switch (local) {
|
|
109
|
-
case 'tombstone':
|
|
110
|
-
case 'empty':
|
|
111
|
-
return { kind: 'INERT', reason: 'orphan-tombstone' }
|
|
112
|
-
case 'real':
|
|
113
|
-
case 'gone':
|
|
114
|
-
case 'slot-absent':
|
|
115
|
-
return { kind: 'INERT', reason: 'unbound-under-claustrum' }
|
|
116
|
-
}
|
|
117
|
-
return unreachable(local)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function evaluateTombstone(
|
|
121
|
-
local: Extract<LocalCustodyState, 'tombstone' | 'empty'>,
|
|
122
|
-
vault: VaultCustodyState,
|
|
123
|
-
): CustodyVerdict {
|
|
124
|
-
switch (vault) {
|
|
125
|
-
case 'serves':
|
|
126
|
-
return withCanonicalization(local, { kind: 'VAULT' })
|
|
127
|
-
case 'cold':
|
|
128
|
-
return withCanonicalization(local, {
|
|
129
|
-
kind: 'INERT',
|
|
130
|
-
reason: 'vault-cold',
|
|
131
|
-
})
|
|
132
|
-
case 'needs_reauth':
|
|
133
|
-
return withCanonicalization(local, {
|
|
134
|
-
kind: 'INERT',
|
|
135
|
-
reason: 'vault-reauth',
|
|
136
|
-
})
|
|
137
|
-
case 'identity_mismatch':
|
|
138
|
-
return withCanonicalization(local, {
|
|
139
|
-
kind: 'INERT',
|
|
140
|
-
reason: 'identity-mismatch',
|
|
141
|
-
})
|
|
142
|
-
case 'no_handle':
|
|
143
|
-
return withCanonicalization(local, { kind: 'INERT', reason: 'no-handle' })
|
|
144
|
-
}
|
|
145
|
-
return unreachable(vault)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function evaluateReal(
|
|
149
|
-
vault: VaultCustodyState,
|
|
150
|
-
fingerprintMatch: boolean | undefined,
|
|
151
|
-
): CustodyVerdict {
|
|
152
|
-
switch (vault) {
|
|
153
|
-
case 'serves':
|
|
154
|
-
return fingerprintMatch === false
|
|
155
|
-
? { kind: 'INERT', reason: 'new-local-family-under-claustrum' }
|
|
156
|
-
: { kind: 'INERT', reason: 'takeover-incomplete' }
|
|
157
|
-
case 'cold':
|
|
158
|
-
case 'needs_reauth':
|
|
159
|
-
return { kind: 'INERT', reason: 'takeover-incomplete/vault-unavailable' }
|
|
160
|
-
case 'identity_mismatch':
|
|
161
|
-
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
162
|
-
case 'no_handle':
|
|
163
|
-
return { kind: 'INERT', reason: 'no-handle' }
|
|
164
|
-
}
|
|
165
|
-
return unreachable(vault)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function evaluateGone(vault: VaultCustodyState): CustodyVerdict {
|
|
169
|
-
switch (vault) {
|
|
170
|
-
case 'serves':
|
|
171
|
-
return { kind: 'VAULT', installTombstone: true }
|
|
172
|
-
case 'cold':
|
|
173
|
-
return { kind: 'INERT', reason: 'vault-cold', installTombstone: true }
|
|
174
|
-
case 'needs_reauth':
|
|
175
|
-
return { kind: 'INERT', reason: 'vault-reauth', installTombstone: true }
|
|
176
|
-
case 'identity_mismatch':
|
|
177
|
-
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
178
|
-
case 'no_handle':
|
|
179
|
-
return { kind: 'INERT', reason: 'no-handle' }
|
|
180
|
-
}
|
|
181
|
-
return unreachable(vault)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function evaluateSlotAbsent(vault: VaultCustodyState): CustodyVerdict {
|
|
185
|
-
switch (vault) {
|
|
186
|
-
case 'serves':
|
|
187
|
-
case 'cold':
|
|
188
|
-
case 'needs_reauth':
|
|
189
|
-
return { kind: 'INERT', reason: 'takeover-incomplete/slot-absent' }
|
|
190
|
-
case 'identity_mismatch':
|
|
191
|
-
return { kind: 'INERT', reason: 'identity-mismatch' }
|
|
192
|
-
case 'no_handle':
|
|
193
|
-
return { kind: 'INERT', reason: 'no-handle' }
|
|
194
|
-
}
|
|
195
|
-
return unreachable(vault)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function evaluatePresentClaustrum(
|
|
199
|
-
local: LocalCustodyState,
|
|
200
|
-
vault: VaultCustodyState,
|
|
201
|
-
fingerprintMatch: boolean | undefined,
|
|
202
|
-
): CustodyVerdict {
|
|
203
|
-
switch (local) {
|
|
204
|
-
case 'tombstone':
|
|
205
|
-
case 'empty':
|
|
206
|
-
return evaluateTombstone(local, vault)
|
|
207
|
-
case 'real':
|
|
208
|
-
return evaluateReal(vault, fingerprintMatch)
|
|
209
|
-
case 'gone':
|
|
210
|
-
return evaluateGone(vault)
|
|
211
|
-
case 'slot-absent':
|
|
212
|
-
return evaluateSlotAbsent(vault)
|
|
213
|
-
}
|
|
214
|
-
return unreachable(local)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export function evaluateCustodyStartup(
|
|
218
|
-
input: EvaluateCustodyStartupInput,
|
|
219
|
-
): CustodyVerdict {
|
|
220
|
-
if (input.manifest === 'unreadable') {
|
|
221
|
-
return { kind: 'INERT', reason: 'manifest-unreadable' }
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
switch (input.mode) {
|
|
225
|
-
case 'local':
|
|
226
|
-
return input.manifest === 'present'
|
|
227
|
-
? evaluateLocalWithBinding(input.local, input.verifiedInProcessLogin)
|
|
228
|
-
: evaluateLocal(input.local)
|
|
229
|
-
case 'claustrum':
|
|
230
|
-
if (input.manifest === 'absent') {
|
|
231
|
-
return evaluateUnboundClaustrum(input.local)
|
|
232
|
-
}
|
|
233
|
-
if (input.manifest === 'present') {
|
|
234
|
-
return evaluatePresentClaustrum(
|
|
235
|
-
input.local,
|
|
236
|
-
input.vault(),
|
|
237
|
-
input.fingerprintMatch,
|
|
238
|
-
)
|
|
239
|
-
}
|
|
240
|
-
return unreachable(input.manifest)
|
|
241
|
-
}
|
|
242
|
-
return unreachable(input.mode)
|
|
243
|
-
}
|