@cortexkit/antigravity-auth-core 2.2.1 → 3.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.
Files changed (253) hide show
  1. package/README.md +24 -2
  2. package/dist/abort-forwarding.d.ts +3 -0
  3. package/dist/abort-forwarding.d.ts.map +1 -0
  4. package/dist/abort-forwarding.js +19 -0
  5. package/dist/abort-forwarding.js.map +1 -0
  6. package/dist/account-identity.d.ts +52 -0
  7. package/dist/account-identity.d.ts.map +1 -0
  8. package/dist/account-identity.js +66 -0
  9. package/dist/account-identity.js.map +1 -0
  10. package/dist/account-manager.d.ts +228 -43
  11. package/dist/account-manager.d.ts.map +1 -1
  12. package/dist/account-manager.js +942 -903
  13. package/dist/account-manager.js.map +1 -1
  14. package/dist/account-migration.d.ts +309 -0
  15. package/dist/account-migration.d.ts.map +1 -0
  16. package/dist/account-migration.js +2284 -0
  17. package/dist/account-migration.js.map +1 -0
  18. package/dist/account-repository-codecs.d.ts +82 -0
  19. package/dist/account-repository-codecs.d.ts.map +1 -0
  20. package/dist/account-repository-codecs.js +609 -0
  21. package/dist/account-repository-codecs.js.map +1 -0
  22. package/dist/account-repository-types.d.ts +831 -0
  23. package/dist/account-repository-types.d.ts.map +1 -0
  24. package/dist/account-repository-types.js +137 -0
  25. package/dist/account-repository-types.js.map +1 -0
  26. package/dist/account-repository.d.ts +442 -0
  27. package/dist/account-repository.d.ts.map +1 -0
  28. package/dist/account-repository.js +2253 -0
  29. package/dist/account-repository.js.map +1 -0
  30. package/dist/account-selector.d.ts +470 -0
  31. package/dist/account-selector.d.ts.map +1 -0
  32. package/dist/account-selector.js +1383 -0
  33. package/dist/account-selector.js.map +1 -0
  34. package/dist/account-storage.d.ts.map +1 -1
  35. package/dist/account-storage.js +5 -0
  36. package/dist/account-storage.js.map +1 -1
  37. package/dist/agy-request-metadata.d.ts.map +1 -1
  38. package/dist/agy-request-metadata.js +8 -21
  39. package/dist/agy-request-metadata.js.map +1 -1
  40. package/dist/agy-transport.d.ts.map +1 -1
  41. package/dist/agy-transport.js +21 -35
  42. package/dist/agy-transport.js.map +1 -1
  43. package/dist/antigravity-command-menu.d.ts +171 -0
  44. package/dist/antigravity-command-menu.d.ts.map +1 -0
  45. package/dist/antigravity-command-menu.js +523 -0
  46. package/dist/antigravity-command-menu.js.map +1 -0
  47. package/dist/atomic-write.d.ts +18 -1
  48. package/dist/atomic-write.d.ts.map +1 -1
  49. package/dist/atomic-write.js +15 -3
  50. package/dist/atomic-write.js.map +1 -1
  51. package/dist/common-auth-embedded/NOTICE.txt +21 -0
  52. package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
  53. package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
  54. package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
  55. package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
  56. package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
  57. package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
  58. package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
  59. package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
  60. package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
  61. package/dist/common-auth-embedded/auth-menu/index.js +12 -0
  62. package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
  63. package/dist/common-auth-embedded/auth-menu/login.js +83 -0
  64. package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
  65. package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
  66. package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
  67. package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
  68. package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
  69. package/dist/common-auth-embedded/auth-menu/select.js +159 -0
  70. package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
  71. package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
  72. package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
  73. package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
  74. package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
  75. package/dist/common-auth-embedded/claustrum/custody.js +354 -0
  76. package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
  77. package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
  78. package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
  79. package/dist/common-auth-embedded/claustrum/errors.js +8 -0
  80. package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
  81. package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
  82. package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
  83. package/dist/common-auth-embedded/claustrum/index.js +22 -0
  84. package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
  85. package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
  86. package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
  87. package/dist/common-auth-embedded/claustrum/roster.js +536 -0
  88. package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
  89. package/dist/common-auth-embedded/commands/builtins.js +534 -0
  90. package/dist/common-auth-embedded/commands/index.d.ts +10 -0
  91. package/dist/common-auth-embedded/commands/index.js +5 -0
  92. package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
  93. package/dist/common-auth-embedded/commands/menu.js +300 -0
  94. package/dist/common-auth-embedded/commands/model.d.ts +238 -0
  95. package/dist/common-auth-embedded/commands/model.js +16 -0
  96. package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
  97. package/dist/common-auth-embedded/commands/pi.js +206 -0
  98. package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
  99. package/dist/common-auth-embedded/commands/seam.js +301 -0
  100. package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
  101. package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
  102. package/dist/common-auth-embedded/fs/index.d.ts +7 -0
  103. package/dist/common-auth-embedded/fs/index.js +6 -0
  104. package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
  105. package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
  106. package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
  107. package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
  108. package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
  109. package/dist/common-auth-embedded/fs/with-lock.js +48 -0
  110. package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
  111. package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
  112. package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
  113. package/dist/common-auth-embedded/logger/engine.js +246 -0
  114. package/dist/common-auth-embedded/logger/index.d.ts +6 -0
  115. package/dist/common-auth-embedded/logger/index.js +3 -0
  116. package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
  117. package/dist/common-auth-embedded/logger/redact.js +83 -0
  118. package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
  119. package/dist/common-auth-embedded/pi-slot/index.js +2 -0
  120. package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
  121. package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
  122. package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
  123. package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
  124. package/dist/common-auth-embedded/quota/codec.js +12 -0
  125. package/dist/common-auth-embedded/quota/format.d.ts +46 -0
  126. package/dist/common-auth-embedded/quota/format.js +138 -0
  127. package/dist/common-auth-embedded/quota/index.d.ts +10 -0
  128. package/dist/common-auth-embedded/quota/index.js +5 -0
  129. package/dist/common-auth-embedded/quota/map.d.ts +63 -0
  130. package/dist/common-auth-embedded/quota/map.js +105 -0
  131. package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
  132. package/dist/common-auth-embedded/quota/merge.js +162 -0
  133. package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
  134. package/dist/common-auth-embedded/quota/projection.js +134 -0
  135. package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
  136. package/dist/common-auth-embedded/routing/admission.js +142 -0
  137. package/dist/common-auth-embedded/routing/index.d.ts +8 -0
  138. package/dist/common-auth-embedded/routing/index.js +4 -0
  139. package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
  140. package/dist/common-auth-embedded/routing/ordered.js +58 -0
  141. package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
  142. package/dist/common-auth-embedded/routing/pins.js +34 -0
  143. package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
  144. package/dist/common-auth-embedded/routing/sticky.js +339 -0
  145. package/dist/common-auth-embedded/source-output.json +1090 -0
  146. package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
  147. package/dist/common-auth-embedded/store/attribution.js +60 -0
  148. package/dist/common-auth-embedded/store/errors.d.ts +55 -0
  149. package/dist/common-auth-embedded/store/errors.js +38 -0
  150. package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
  151. package/dist/common-auth-embedded/store/hooks.js +34 -0
  152. package/dist/common-auth-embedded/store/identity.d.ts +39 -0
  153. package/dist/common-auth-embedded/store/identity.js +79 -0
  154. package/dist/common-auth-embedded/store/index.d.ts +21 -0
  155. package/dist/common-auth-embedded/store/index.js +7 -0
  156. package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
  157. package/dist/common-auth-embedded/store/mutate.js +500 -0
  158. package/dist/common-auth-embedded/store/pool.d.ts +196 -0
  159. package/dist/common-auth-embedded/store/pool.js +120 -0
  160. package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
  161. package/dist/common-auth-embedded/store/provider-state.js +226 -0
  162. package/dist/common-auth-embedded/store/publication.d.ts +47 -0
  163. package/dist/common-auth-embedded/store/publication.js +304 -0
  164. package/dist/common-auth-embedded/store/pull.d.ts +36 -0
  165. package/dist/common-auth-embedded/store/pull.js +143 -0
  166. package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
  167. package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
  168. package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
  169. package/dist/common-auth-embedded/store/refresh.js +225 -0
  170. package/dist/common-auth-embedded/store/reserved.js +13 -0
  171. package/dist/common-auth-embedded/store/rows.d.ts +313 -0
  172. package/dist/common-auth-embedded/store/rows.js +1258 -0
  173. package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
  174. package/dist/common-auth-embedded/store/runtime.js +68 -0
  175. package/dist/common-auth-embedded/store/schema.d.ts +443 -0
  176. package/dist/common-auth-embedded/store/schema.js +754 -0
  177. package/dist/common-auth-embedded/store/settings.d.ts +63 -0
  178. package/dist/common-auth-embedded/store/settings.js +120 -0
  179. package/dist/common-auth-embedded/store/torn.d.ts +78 -0
  180. package/dist/common-auth-embedded/store/torn.js +294 -0
  181. package/dist/common-auth-embedded/util/any-signal.js +42 -0
  182. package/dist/common-auth-runtime.d.ts +73 -0
  183. package/dist/common-auth-runtime.d.ts.map +1 -0
  184. package/dist/common-auth-runtime.js +65 -0
  185. package/dist/common-auth-runtime.js.map +1 -0
  186. package/dist/credential-redaction.d.ts +13 -0
  187. package/dist/credential-redaction.d.ts.map +1 -0
  188. package/dist/credential-redaction.js +230 -0
  189. package/dist/credential-redaction.js.map +1 -0
  190. package/dist/fetch-timeout.d.ts +4 -2
  191. package/dist/fetch-timeout.d.ts.map +1 -1
  192. package/dist/fetch-timeout.js +121 -14
  193. package/dist/fetch-timeout.js.map +1 -1
  194. package/dist/fingerprint.d.ts +3 -3
  195. package/dist/fingerprint.d.ts.map +1 -1
  196. package/dist/fingerprint.js +4 -4
  197. package/dist/fingerprint.js.map +1 -1
  198. package/dist/index.d.ts +16 -1
  199. package/dist/index.d.ts.map +1 -1
  200. package/dist/index.js +15 -0
  201. package/dist/index.js.map +1 -1
  202. package/dist/model-registry.d.ts +16 -0
  203. package/dist/model-registry.d.ts.map +1 -1
  204. package/dist/model-registry.js +242 -30
  205. package/dist/model-registry.js.map +1 -1
  206. package/dist/quota-manager.d.ts.map +1 -1
  207. package/dist/quota-manager.js +35 -10
  208. package/dist/quota-manager.js.map +1 -1
  209. package/dist/quota-presentation.d.ts +10 -0
  210. package/dist/quota-presentation.d.ts.map +1 -0
  211. package/dist/quota-presentation.js +60 -0
  212. package/dist/quota-presentation.js.map +1 -0
  213. package/dist/quota-types.d.ts +2 -0
  214. package/dist/quota-types.d.ts.map +1 -1
  215. package/dist/rotation.d.ts +8 -0
  216. package/dist/rotation.d.ts.map +1 -1
  217. package/dist/rotation.js +24 -0
  218. package/dist/rotation.js.map +1 -1
  219. package/dist/transform/agy-generation.d.ts +3 -0
  220. package/dist/transform/agy-generation.d.ts.map +1 -0
  221. package/dist/transform/agy-generation.js +28 -0
  222. package/dist/transform/agy-generation.js.map +1 -0
  223. package/dist/transform/claude.d.ts +7 -3
  224. package/dist/transform/claude.d.ts.map +1 -1
  225. package/dist/transform/claude.js +8 -2
  226. package/dist/transform/claude.js.map +1 -1
  227. package/dist/transform/index.d.ts +1 -0
  228. package/dist/transform/index.d.ts.map +1 -1
  229. package/dist/transform/index.js +1 -0
  230. package/dist/transform/index.js.map +1 -1
  231. package/dist/transform/model-resolver.d.ts +2 -1
  232. package/dist/transform/model-resolver.d.ts.map +1 -1
  233. package/dist/transform/model-resolver.js +46 -5
  234. package/dist/transform/model-resolver.js.map +1 -1
  235. package/dist/transform/types.d.ts +1 -1
  236. package/dist/transform/types.d.ts.map +1 -1
  237. package/dist/vault-account-source.d.ts +183 -0
  238. package/dist/vault-account-source.d.ts.map +1 -0
  239. package/dist/vault-account-source.js +296 -0
  240. package/dist/vault-account-source.js.map +1 -0
  241. package/dist/vault-enrollment-authority.d.ts +16 -0
  242. package/dist/vault-enrollment-authority.d.ts.map +1 -0
  243. package/dist/vault-enrollment-authority.js +19 -0
  244. package/dist/vault-enrollment-authority.js.map +1 -0
  245. package/dist/vault-family.d.ts +11 -0
  246. package/dist/vault-family.d.ts.map +1 -0
  247. package/dist/vault-family.js +24 -0
  248. package/dist/vault-family.js.map +1 -0
  249. package/dist/vault-provider-state.d.ts +162 -0
  250. package/dist/vault-provider-state.d.ts.map +1 -0
  251. package/dist/vault-provider-state.js +422 -0
  252. package/dist/vault-provider-state.js.map +1 -0
  253. package/package.json +39 -2
@@ -0,0 +1,159 @@
1
+ import { ANSI, parseKeys, truncateAnsi } from './ansi.js';
2
+ import { isInteractive } from './terminal.js';
3
+ /** How long a lone Escape byte waits for the rest of an arrow-key sequence. */
4
+ const ESCAPE_TIMEOUT_MS = 50;
5
+ function colorCode(color) {
6
+ if (color === 'red')
7
+ return ANSI.red;
8
+ if (color === 'cyan')
9
+ return ANSI.cyan;
10
+ return '';
11
+ }
12
+ /**
13
+ * Render a bounded, keyboard-only terminal selector without a prompt
14
+ * dependency. Resolves the chosen value, or null on Escape, Ctrl-C, a signal,
15
+ * or a terminal that refuses raw mode.
16
+ */
17
+ export async function select(terminal, items, options) {
18
+ if (!isInteractive(terminal))
19
+ throw new Error('Interactive select requires a TTY terminal');
20
+ if (items.length === 0)
21
+ throw new Error('No menu items provided');
22
+ const { input: stdin, output: stdout, signals } = terminal;
23
+ let cursor = 0;
24
+ let escapeTimeout = null;
25
+ let cleaned = false;
26
+ let renderedLines = 0;
27
+ const render = () => {
28
+ const columns = stdout.columns ?? 80;
29
+ const rows = stdout.rows ?? 24;
30
+ const previousLines = renderedLines;
31
+ if (options.clearScreen) {
32
+ stdout.write(ANSI.clearScreen + ANSI.moveTo(1, 1));
33
+ }
34
+ else if (previousLines > 0) {
35
+ stdout.write(ANSI.up(previousLines));
36
+ }
37
+ let lines = 0;
38
+ const writeLine = (line) => {
39
+ stdout.write(`${ANSI.clearLine}${line}\n`);
40
+ lines += 1;
41
+ };
42
+ const extraLines = options.lines ?? [];
43
+ const headerLines = 1 +
44
+ (options.subtitle ? 3 : 0) +
45
+ extraLines.length +
46
+ (extraLines.length ? 1 : 0);
47
+ const maxVisible = Math.max(1, Math.min(items.length, rows - headerLines - 2 - 1));
48
+ const windowStart = Math.max(0, Math.min(cursor - Math.floor(maxVisible / 2), Math.max(0, items.length - maxVisible)));
49
+ const visibleItems = items.slice(windowStart, windowStart + maxVisible);
50
+ writeLine(`${ANSI.dim}┌ ${ANSI.reset}${truncateAnsi(options.message, Math.max(1, columns - 4))}`);
51
+ if (options.subtitle) {
52
+ writeLine(`${ANSI.dim}│${ANSI.reset}`);
53
+ writeLine(`${ANSI.cyan}◆${ANSI.reset} ${truncateAnsi(options.subtitle, Math.max(1, columns - 4))}`);
54
+ writeLine('');
55
+ }
56
+ for (const line of extraLines) {
57
+ writeLine(`${ANSI.cyan}│${ANSI.reset} ${truncateAnsi(line, Math.max(1, columns - 4))}`);
58
+ }
59
+ if (extraLines.length)
60
+ writeLine(`${ANSI.cyan}│${ANSI.reset}`);
61
+ for (let offset = 0; offset < visibleItems.length; offset++) {
62
+ const item = visibleItems[offset];
63
+ if (!item)
64
+ continue;
65
+ const selected = windowStart + offset === cursor;
66
+ const color = colorCode(item.color);
67
+ let label = color
68
+ ? `${selected ? '' : ANSI.dim}${color}${item.label}${ANSI.reset}`
69
+ : selected
70
+ ? item.label
71
+ : `${ANSI.dim}${item.label}${ANSI.reset}`;
72
+ if (item.hint)
73
+ label += ` ${ANSI.dim}${item.hint}${ANSI.reset}`;
74
+ label = truncateAnsi(label, Math.max(1, columns - 8));
75
+ writeLine(selected
76
+ ? `${ANSI.cyan}│${ANSI.reset} ${ANSI.green}●${ANSI.reset} ${label}`
77
+ : `${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}○${ANSI.reset} ${label}`);
78
+ }
79
+ const windowHint = visibleItems.length < items.length
80
+ ? ` (${windowStart + 1}-${windowStart + visibleItems.length}/${items.length})`
81
+ : '';
82
+ writeLine(`${ANSI.cyan}│${ANSI.reset} ${ANSI.dim}${truncateAnsi(`Up/Down to select | Enter: confirm | Esc: back${windowHint}`, Math.max(1, columns - 6))}${ANSI.reset}`);
83
+ writeLine(`${ANSI.cyan}└${ANSI.reset}`);
84
+ for (let extra = lines; extra < previousLines; extra++)
85
+ writeLine('');
86
+ renderedLines = lines;
87
+ };
88
+ return new Promise((resolve) => {
89
+ const wasRaw = stdin.isRaw ?? false;
90
+ const cleanup = () => {
91
+ if (cleaned)
92
+ return;
93
+ cleaned = true;
94
+ if (escapeTimeout)
95
+ clearTimeout(escapeTimeout);
96
+ stdin.removeListener('data', onKey);
97
+ try {
98
+ stdin.setRawMode(wasRaw);
99
+ stdin.pause();
100
+ stdout.write(ANSI.show);
101
+ }
102
+ catch { }
103
+ signals?.removeListener('SIGINT', onSignal);
104
+ signals?.removeListener('SIGTERM', onSignal);
105
+ };
106
+ const finish = (value) => {
107
+ cleanup();
108
+ resolve(value);
109
+ };
110
+ const onSignal = () => finish(null);
111
+ const onKey = (data) => {
112
+ for (const key of parseKeys(data)) {
113
+ if (cleaned)
114
+ break;
115
+ if (escapeTimeout) {
116
+ clearTimeout(escapeTimeout);
117
+ escapeTimeout = null;
118
+ }
119
+ switch (key) {
120
+ case 'up':
121
+ cursor = (cursor - 1 + items.length) % items.length;
122
+ render();
123
+ break;
124
+ case 'down':
125
+ cursor = (cursor + 1) % items.length;
126
+ render();
127
+ break;
128
+ case 'enter':
129
+ finish(items[cursor]?.value ?? null);
130
+ break;
131
+ case 'escape':
132
+ finish(null);
133
+ break;
134
+ case 'escape-start':
135
+ // A bare Escape byte is also the start of an arrow-key sequence
136
+ // that may arrive split across reads; only a lone one cancels.
137
+ escapeTimeout = setTimeout(() => finish(null), ESCAPE_TIMEOUT_MS);
138
+ break;
139
+ default:
140
+ break;
141
+ }
142
+ }
143
+ };
144
+ signals?.once('SIGINT', onSignal);
145
+ signals?.once('SIGTERM', onSignal);
146
+ try {
147
+ stdin.setRawMode(true);
148
+ }
149
+ catch {
150
+ cleanup();
151
+ resolve(null);
152
+ return;
153
+ }
154
+ stdin.resume();
155
+ stdout.write(ANSI.hide);
156
+ render();
157
+ stdin.on('data', onKey);
158
+ });
159
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The terminal the menu draws on and reads keys from. The process's own
3
+ * stdin and stdout satisfy it; tests pass a scripted stand-in, so the full
4
+ * key-in, frame-out path runs without a real TTY.
5
+ */
6
+ export interface MenuTerminal {
7
+ input: MenuInput;
8
+ output: MenuOutput;
9
+ /**
10
+ * Where SIGINT and SIGTERM are heard while a selector holds raw mode, so a
11
+ * kill restores the terminal. Defaults to nothing: raw mode turns Ctrl-C
12
+ * into a key the selector already handles.
13
+ */
14
+ signals?: MenuSignals;
15
+ }
16
+ export interface MenuInput {
17
+ /** True only on an interactive terminal; anything else gets a plain list. */
18
+ readonly isTTY?: boolean;
19
+ readonly isRaw?: boolean;
20
+ setRawMode(mode: boolean): unknown;
21
+ resume(): unknown;
22
+ pause(): unknown;
23
+ on(event: 'data', listener: (data: Buffer | string) => void): unknown;
24
+ removeListener(event: 'data', listener: (data: Buffer | string) => void): unknown;
25
+ }
26
+ export interface MenuOutput {
27
+ write(text: string): unknown;
28
+ readonly columns?: number;
29
+ readonly rows?: number;
30
+ }
31
+ export interface MenuSignals {
32
+ once(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
33
+ removeListener(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
34
+ }
35
+ /** The current process's terminal, with its signals. */
36
+ export declare function processTerminal(): MenuTerminal;
37
+ /**
38
+ * Whether keys can be read one at a time. Only the input is checked, as the
39
+ * plugins' menus always did: output piped to a file still gets the menu.
40
+ */
41
+ export declare function isInteractive(terminal: MenuTerminal): boolean;
42
+ /** Writes one line of plain output below the menu. */
43
+ export declare function printLine(terminal: MenuTerminal, line?: string): void;
@@ -0,0 +1,19 @@
1
+ /** The current process's terminal, with its signals. */
2
+ export function processTerminal() {
3
+ return {
4
+ input: process.stdin,
5
+ output: process.stdout,
6
+ signals: process,
7
+ };
8
+ }
9
+ /**
10
+ * Whether keys can be read one at a time. Only the input is checked, as the
11
+ * plugins' menus always did: output piped to a file still gets the menu.
12
+ */
13
+ export function isInteractive(terminal) {
14
+ return terminal.input.isTTY === true;
15
+ }
16
+ /** Writes one line of plain output below the menu. */
17
+ export function printLine(terminal, line = '') {
18
+ terminal.output.write(`${line}\n`);
19
+ }
@@ -0,0 +1,102 @@
1
+ import type { ClaustrumReporterSource } from '@cortexkit/claustrum-client';
2
+ import type { QuotaObservation } from '../quota/index.js';
3
+ import type { RoutingRow } from '../routing/index.js';
4
+ import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser } from './custody.js';
5
+ import { type ClaustrumLogger } from './errors.js';
6
+ import { type AccountMapper, type QuotaReceipt, type VaultRosterFile } from './roster.js';
7
+ export interface ClaustrumConsumerOptions {
8
+ /**
9
+ * The roster file: one row per vault account (route id, credential id,
10
+ * account identity, state, quota), plus the accounts the user declined. It
11
+ * never holds a token.
12
+ */
13
+ rosterPath: string;
14
+ /** This host's enrollment token, written by setup. */
15
+ tokenPath: string;
16
+ family: ClaustrumFamily;
17
+ connect: () => Promise<ClaustrumScopedClient>;
18
+ /**
19
+ * Whether the plugin currently takes its accounts from the vault (custody
20
+ * mode) rather than from local logins. Checked before every discovery,
21
+ * commit and dispatch; false closes the vault connection and refuses sends.
22
+ */
23
+ isCustodyActive?: () => boolean | Promise<boolean>;
24
+ /** Ids of the plugin's local pool rows; no vault route id may equal one. */
25
+ reservedRouteIds?: () => Iterable<string>;
26
+ routePrefix?: string;
27
+ mapAccount?: AccountMapper;
28
+ parseIdentity?: IdentityParser;
29
+ /**
30
+ * Issue a receipt only when the vault's served reply itself names the
31
+ * credential id and the roster's account identity (see
32
+ * `ClaustrumScopedCustody`). Set it for providers whose tokens do not reveal
33
+ * their account; leave it unset to accept a reply that asserts no identity.
34
+ */
35
+ requireAssertion?: boolean;
36
+ /**
37
+ * Fired once per change of the vault's view cursor, including the first
38
+ * roster. A poll that sees the same view does not fire, and neither does a
39
+ * routine token refresh, which never moves the view.
40
+ */
41
+ onRoster?: (roster: VaultRosterFile) => void;
42
+ onError?: (error: unknown) => void;
43
+ pollIntervalMs?: number;
44
+ setTimeoutImpl?: typeof setTimeout;
45
+ clearTimeoutImpl?: typeof clearTimeout;
46
+ now?: () => number;
47
+ logger?: ClaustrumLogger;
48
+ }
49
+ export interface SendOptions {
50
+ /**
51
+ * What kind of request this is (for example `model`, `quota`, `profile`),
52
+ * recorded in the 401 retry log line so a retry can be traced to its caller.
53
+ */
54
+ site: string;
55
+ signal?: AbortSignal;
56
+ reporterSource?: ClaustrumReporterSource;
57
+ }
58
+ /**
59
+ * One plugin host's view of its vault accounts. It turns vault credentials
60
+ * into rows for `/routing`, authorizes each physical send with the vault, and
61
+ * reports a 401 against the exact record version that send used. Metadata
62
+ * (the connection and discovery) is shared and coalesced; credential reads
63
+ * never are. It never enrolls: a missing token is an error here, and setup
64
+ * is where enrollment happens.
65
+ */
66
+ export declare class ClaustrumConsumer {
67
+ #private;
68
+ constructor(options: ClaustrumConsumerOptions);
69
+ /** The last committed roster this instance saw, without any I/O. */
70
+ snapshot(): VaultRosterFile | undefined;
71
+ /** Rows for `/routing`, from the last committed roster. */
72
+ routingRows(): RoutingRow[];
73
+ refresh(): Promise<VaultRosterFile | undefined>;
74
+ start(): void;
75
+ /**
76
+ * Authorize one physical send on a vault route. The roster file is re-read
77
+ * first, so a decline committed by another process applies before the next
78
+ * poll, and a route whose credential or account changed is refused.
79
+ */
80
+ authorize(routeId: string, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
81
+ reportFailure(attempt: ClaustrumScopedAttempt, status: number, source: ClaustrumReporterSource): Promise<void>;
82
+ /**
83
+ * Send on a vault route. `dispatch` builds and sends the request with the
84
+ * receipt's token and may be called twice, so it must be able to rebuild
85
+ * its body. A 401 is retried once, and only when the vault now serves a new
86
+ * record version of the same credential and account; the final 401 is
87
+ * reported against the version that send actually used. Each call to
88
+ * `dispatch` gets its own receipt.
89
+ */
90
+ send(routeId: string, dispatch: (attempt: ClaustrumScopedAttempt, signal?: AbortSignal) => Promise<Response>, options: SendOptions): Promise<Response>;
91
+ /** Decline a vault account: it stays listed and never routes until accepted. */
92
+ decline(routeId: string): Promise<void>;
93
+ accept(routeId: string): Promise<void>;
94
+ /**
95
+ * Store a quota or profile observation for a vault route. The receipt the
96
+ * reading was taken with is required: the observation lands only while the
97
+ * route still holds the credential and account that receipt was served for
98
+ * (see `recordVaultQuota`), so a reading for a replaced account is dropped.
99
+ */
100
+ recordQuota(routeId: string, observation: QuotaObservation, attempt: QuotaReceipt): Promise<boolean>;
101
+ close(): void;
102
+ }
@@ -0,0 +1,298 @@
1
+ import { createLogger } from '../logger/index.js';
2
+ import { combineAbortSignals } from '../util/any-signal.js';
3
+ import { ClaustrumScopedCustody, decideScopedRetryAfter401, } from './custody.js';
4
+ import { ClaustrumConsumerError } from './errors.js';
5
+ import { acceptVaultRoute, declineVaultRoute, readVaultRoster, recordVaultQuota, refreshVaultRoster, vaultRoutingRows, } from './roster.js';
6
+ /**
7
+ * One plugin host's view of its vault accounts. It turns vault credentials
8
+ * into rows for `/routing`, authorizes each physical send with the vault, and
9
+ * reports a 401 against the exact record version that send used. Metadata
10
+ * (the connection and discovery) is shared and coalesced; credential reads
11
+ * never are. It never enrolls: a missing token is an error here, and setup
12
+ * is where enrollment happens.
13
+ */
14
+ export class ClaustrumConsumer {
15
+ #options;
16
+ #shutdown = new AbortController();
17
+ #logger;
18
+ #custody;
19
+ #connecting;
20
+ #refreshing;
21
+ #roster;
22
+ #timer;
23
+ #started = false;
24
+ constructor(options) {
25
+ this.#options = options;
26
+ this.#logger = options.logger ?? createLogger('claustrum');
27
+ }
28
+ #assertOpen(signal) {
29
+ this.#shutdown.signal.throwIfAborted();
30
+ signal?.throwIfAborted();
31
+ }
32
+ async #active() {
33
+ return (await this.#options.isCustodyActive?.()) ?? true;
34
+ }
35
+ #wait(pending, signal) {
36
+ const combined = combineAbortSignals([
37
+ this.#shutdown.signal,
38
+ ...(signal ? [signal] : []),
39
+ ]);
40
+ return new Promise((resolve, reject) => {
41
+ const cleanup = () => {
42
+ combined.signal.removeEventListener('abort', abort);
43
+ combined.dispose();
44
+ };
45
+ const abort = () => {
46
+ cleanup();
47
+ reject(combined.signal.reason);
48
+ };
49
+ combined.signal.addEventListener('abort', abort, { once: true });
50
+ // Observe the operation even after cancellation: shared metadata work may
51
+ // finish for other callers, and a late connection must still be closed.
52
+ pending.then((value) => {
53
+ cleanup();
54
+ if (combined.signal.aborted)
55
+ reject(combined.signal.reason);
56
+ else
57
+ resolve(value);
58
+ }, (error) => {
59
+ cleanup();
60
+ reject(error);
61
+ });
62
+ if (combined.signal.aborted)
63
+ abort();
64
+ });
65
+ }
66
+ async #getCustody() {
67
+ this.#assertOpen();
68
+ if (this.#custody)
69
+ return this.#custody;
70
+ if (!this.#connecting) {
71
+ this.#connecting = this.#options
72
+ .connect()
73
+ .then((client) => {
74
+ if (this.#shutdown.signal.aborted) {
75
+ client.close();
76
+ this.#assertOpen();
77
+ }
78
+ this.#custody = new ClaustrumScopedCustody({
79
+ client,
80
+ family: this.#options.family,
81
+ tokenPath: this.#options.tokenPath,
82
+ parseIdentity: this.#options.parseIdentity,
83
+ requireAssertion: this.#options.requireAssertion,
84
+ now: this.#options.now,
85
+ logger: this.#logger,
86
+ });
87
+ return this.#custody;
88
+ })
89
+ .finally(() => {
90
+ this.#connecting = undefined;
91
+ });
92
+ }
93
+ return this.#wait(this.#connecting);
94
+ }
95
+ /** The last committed roster this instance saw, without any I/O. */
96
+ snapshot() {
97
+ return this.#roster;
98
+ }
99
+ /** Rows for `/routing`, from the last committed roster. */
100
+ routingRows() {
101
+ return vaultRoutingRows(this.#roster);
102
+ }
103
+ refresh() {
104
+ this.#assertOpen();
105
+ if (this.#refreshing)
106
+ return this.#refreshing;
107
+ this.#refreshing = (async () => {
108
+ if (!(await this.#active())) {
109
+ this.#roster = undefined;
110
+ this.#custody?.close();
111
+ this.#custody = undefined;
112
+ return undefined;
113
+ }
114
+ const custody = await this.#getCustody();
115
+ const roster = await refreshVaultRoster({
116
+ path: this.#options.rosterPath,
117
+ custody,
118
+ isActive: () => this.#active(),
119
+ signal: this.#shutdown.signal,
120
+ projection: () => ({
121
+ reservedRouteIds: new Set(this.#options.reservedRouteIds?.() ?? []),
122
+ routePrefix: this.#options.routePrefix,
123
+ mapAccount: this.#options.mapAccount,
124
+ now: (this.#options.now ?? Date.now)(),
125
+ }),
126
+ });
127
+ this.#assertOpen();
128
+ const changed = roster?.view !== this.#roster?.view;
129
+ this.#roster = roster;
130
+ // Notify on view changes only: subscribers rewrite shared state on each
131
+ // notification, and the poll runs every few seconds.
132
+ if (roster && changed)
133
+ this.#options.onRoster?.(roster);
134
+ return roster;
135
+ })().finally(() => {
136
+ this.#refreshing = undefined;
137
+ });
138
+ return this.#refreshing;
139
+ }
140
+ start() {
141
+ if (this.#started)
142
+ return;
143
+ this.#assertOpen();
144
+ this.#started = true;
145
+ const tick = async () => {
146
+ try {
147
+ await this.refresh();
148
+ }
149
+ catch (error) {
150
+ if (!this.#shutdown.signal.aborted)
151
+ this.#options.onError?.(error);
152
+ }
153
+ if (this.#shutdown.signal.aborted)
154
+ return;
155
+ const delay = this.#options.pollIntervalMs ?? 5_000;
156
+ if (delay <= 0)
157
+ return;
158
+ this.#timer = (this.#options.setTimeoutImpl ?? setTimeout)(() => {
159
+ this.#timer = undefined;
160
+ void tick();
161
+ }, delay);
162
+ this.#timer.unref?.();
163
+ };
164
+ void tick();
165
+ }
166
+ /**
167
+ * Authorize one physical send on a vault route. The roster file is re-read
168
+ * first, so a decline committed by another process applies before the next
169
+ * poll, and a route whose credential or account changed is refused.
170
+ */
171
+ async authorize(routeId, signal) {
172
+ this.#assertOpen(signal);
173
+ const roster = this.#roster ?? (await this.#wait(this.refresh(), signal));
174
+ if (!roster || !(await this.#active()))
175
+ throw new ClaustrumConsumerError('not-active', 'Claustrum scoped custody is not active');
176
+ const current = await readVaultRoster(this.#options.rosterPath);
177
+ this.#assertOpen(signal);
178
+ const observed = roster.rows.find((row) => row.routeId === routeId);
179
+ const configured = current?.rows.find((row) => row.routeId === routeId);
180
+ if (configured && !configured.enabled)
181
+ throw new ClaustrumConsumerError('route-declined', 'Claustrum route is disabled');
182
+ if (!observed ||
183
+ !configured ||
184
+ configured.state !== 'active' ||
185
+ configured.credentialId !== observed.credentialId ||
186
+ configured.accountIdentity !== observed.accountIdentity)
187
+ throw new ClaustrumConsumerError('route-unavailable', 'Claustrum route is disabled, removed or changed');
188
+ const custody = await this.#wait(this.#getCustody(), signal);
189
+ const combined = combineAbortSignals([
190
+ this.#shutdown.signal,
191
+ ...(signal ? [signal] : []),
192
+ ]);
193
+ try {
194
+ return await custody.authorize({
195
+ credentialId: configured.credentialId,
196
+ credentialType: configured.credentialType,
197
+ ...(configured.accountIdentity !== undefined && {
198
+ accountIdentity: configured.accountIdentity,
199
+ }),
200
+ }, combined.signal);
201
+ }
202
+ finally {
203
+ combined.dispose();
204
+ }
205
+ }
206
+ async reportFailure(attempt, status, source) {
207
+ this.#assertOpen();
208
+ if (!this.#custody)
209
+ throw new ClaustrumConsumerError('no-receipt', 'Claustrum dispatch receipt has no active owner');
210
+ await this.#custody.reportFailure(attempt, status, source);
211
+ }
212
+ /**
213
+ * Send on a vault route. `dispatch` builds and sends the request with the
214
+ * receipt's token and may be called twice, so it must be able to rebuild
215
+ * its body. A 401 is retried once, and only when the vault now serves a new
216
+ * record version of the same credential and account; the final 401 is
217
+ * reported against the version that send actually used. Each call to
218
+ * `dispatch` gets its own receipt.
219
+ */
220
+ async send(routeId, dispatch, options) {
221
+ const signal = options.signal;
222
+ let served = await this.authorize(routeId, signal);
223
+ let response = await dispatch(served, signal);
224
+ if (response.status === 401 && !signal?.aborted) {
225
+ let current;
226
+ try {
227
+ current = await this.authorize(routeId, signal);
228
+ }
229
+ catch {
230
+ // Re-authorization failed: keep this 401 and report it against the
231
+ // receipt (served record version) this request was sent with.
232
+ }
233
+ if (decideScopedRetryAfter401(options.site, served, current, this.#logger)) {
234
+ await response.body?.cancel().catch(() => { });
235
+ served = current;
236
+ response = await dispatch(current, signal);
237
+ }
238
+ }
239
+ if (response.status === 401) {
240
+ await this.reportFailure(served, 401, options.reporterSource ?? 'direct').catch((error) => this.#options.onError?.(error));
241
+ // After the report the vault may mark the account as needing a new
242
+ // login; refresh now so routing drops it without waiting for the next poll. Deferred so a consumer closed in
243
+ // the meantime reports to onError instead of throwing from this send.
244
+ Promise.resolve()
245
+ .then(() => this.refresh())
246
+ .catch((error) => {
247
+ if (!this.#shutdown.signal.aborted)
248
+ this.#options.onError?.(error);
249
+ });
250
+ }
251
+ return response;
252
+ }
253
+ /** Decline a vault account: it stays listed and never routes until accepted. */
254
+ async decline(routeId) {
255
+ this.#assertOpen();
256
+ await declineVaultRoute(this.#options.rosterPath, routeId);
257
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
258
+ }
259
+ async accept(routeId) {
260
+ this.#assertOpen();
261
+ await acceptVaultRoute(this.#options.rosterPath, routeId);
262
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
263
+ }
264
+ /**
265
+ * Store a quota or profile observation for a vault route. The receipt the
266
+ * reading was taken with is required: the observation lands only while the
267
+ * route still holds the credential and account that receipt was served for
268
+ * (see `recordVaultQuota`), so a reading for a replaced account is dropped.
269
+ */
270
+ async recordQuota(routeId, observation, attempt) {
271
+ this.#assertOpen();
272
+ const kept = await recordVaultQuota(this.#options.rosterPath, {
273
+ routeId,
274
+ observation,
275
+ credentialId: attempt.credentialId,
276
+ accountIdentitySource: attempt.accountIdentitySource,
277
+ ...(attempt.accountIdentity !== undefined && {
278
+ accountIdentity: attempt.accountIdentity,
279
+ }),
280
+ ...(attempt.expectedAccountIdentity !== undefined && {
281
+ expectedAccountIdentity: attempt.expectedAccountIdentity,
282
+ }),
283
+ });
284
+ if (kept)
285
+ this.#roster = await readVaultRoster(this.#options.rosterPath);
286
+ return kept;
287
+ }
288
+ close() {
289
+ if (this.#shutdown.signal.aborted)
290
+ return;
291
+ this.#shutdown.abort(new ClaustrumConsumerError('closed', 'Claustrum consumer is closed'));
292
+ if (this.#timer)
293
+ (this.#options.clearTimeoutImpl ?? clearTimeout)(this.#timer);
294
+ this.#timer = undefined;
295
+ this.#custody?.close();
296
+ this.#roster = undefined;
297
+ }
298
+ }