@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,206 @@
1
+ // The Pi renderer: walks the same menu model the OpenCode dialog draws, with
2
+ // Pi's extension UI (`select`, `confirm`, `input`, `notify`). It sees only the
3
+ // payloads the menu's seam produces, and applies actions through the menu,
4
+ // so the confirmation and input checks are the same as on OpenCode.
5
+ /**
6
+ * Shows `entries` with `select` and returns the chosen value. Labels are made
7
+ * unique, since `select` answers with the label.
8
+ */
9
+ async function pick(ui, title, entries) {
10
+ const byLabel = new Map();
11
+ const labels = [];
12
+ for (const entry of entries) {
13
+ let label = entry.label;
14
+ for (let n = 2; byLabel.has(label); n++)
15
+ label = `${entry.label} (${n})`;
16
+ byLabel.set(label, entry.value);
17
+ labels.push(label);
18
+ }
19
+ const chosen = await ui.select(title, labels);
20
+ return chosen === undefined ? undefined : byLabel.get(chosen);
21
+ }
22
+ function withLines(title, lines) {
23
+ return lines.length > 0 ? `${title}\n${lines.join('\n')}` : title;
24
+ }
25
+ /** A row as one line of text: its label, then its status when it has one. */
26
+ function rowText(item) {
27
+ return item.status ? `${item.label} · ${item.status}` : item.label;
28
+ }
29
+ /**
30
+ * The read-only part of a section's select title: its lines, then each item
31
+ * that has no actions, under its group's header. Such an item does nothing
32
+ * when chosen, so it is shown as text rather than offered as an option. Pi's
33
+ * `select` cannot put text between options, so the headers of groups whose
34
+ * rows are all selectable are not shown.
35
+ */
36
+ function sectionText(section) {
37
+ const text = [...section.lines];
38
+ let header;
39
+ for (const item of section.items) {
40
+ if (item.actions.length > 0)
41
+ continue;
42
+ if (item.group !== undefined && item.group !== header)
43
+ text.push(item.group);
44
+ header = item.group;
45
+ text.push(item.group !== undefined ? ` ${rowText(item)}` : rowText(item));
46
+ }
47
+ return text;
48
+ }
49
+ function itemTitle(item) {
50
+ const lines = item.detail ? [item.detail] : [];
51
+ for (const [name, value] of Object.entries(item.facts ?? {}))
52
+ lines.push(`${name}: ${String(value)}`);
53
+ return withLines(rowText(item), lines);
54
+ }
55
+ /** One knob's value, or undefined when the user backed out or typed nonsense. */
56
+ async function askKnob(ui, knob) {
57
+ switch (knob.kind) {
58
+ case 'choice': {
59
+ const value = await pick(ui, knob.label, knob.choices.map((choice) => ({
60
+ label: choice.value === knob.value
61
+ ? `${choice.label} (current)`
62
+ : choice.label,
63
+ value: choice.value,
64
+ })));
65
+ return value === undefined ? undefined : { value };
66
+ }
67
+ case 'toggle': {
68
+ const value = await pick(ui, knob.label, [
69
+ { label: knob.value ? 'On (current)' : 'On', value: true },
70
+ { label: knob.value ? 'Off' : 'Off (current)', value: false },
71
+ ]);
72
+ return value === undefined ? undefined : { value };
73
+ }
74
+ case 'number': {
75
+ const range = knob.min !== undefined && knob.max !== undefined
76
+ ? ` (${knob.min}-${knob.max}${knob.required ? '' : ', empty clears'})`
77
+ : knob.required
78
+ ? ''
79
+ : ' (empty clears)';
80
+ const typed = await ui.input(`${knob.label}${range}`, knob.value !== undefined ? String(knob.value) : undefined);
81
+ if (typed === undefined)
82
+ return undefined;
83
+ const text = typed.trim();
84
+ if (text === '')
85
+ return knob.required && knob.value !== undefined
86
+ ? { value: knob.value }
87
+ : { value: null };
88
+ const number = Number(text);
89
+ if (!Number.isFinite(number)) {
90
+ ui.notify(`${knob.label} needs a number.`, 'error');
91
+ return undefined;
92
+ }
93
+ return { value: number };
94
+ }
95
+ case 'text': {
96
+ const typed = await ui.input(knob.label, knob.value ?? knob.placeholder);
97
+ if (typed === undefined)
98
+ return undefined;
99
+ const text = typed.trim();
100
+ if (text === '')
101
+ return knob.required && knob.value !== undefined
102
+ ? { value: knob.value }
103
+ : { value: null };
104
+ return { value: text };
105
+ }
106
+ }
107
+ }
108
+ async function askValues(ui, action) {
109
+ const values = {};
110
+ for (const knob of action.knobs) {
111
+ const answer = await askKnob(ui, knob);
112
+ if (!answer)
113
+ return undefined;
114
+ values[knob.id] = answer.value;
115
+ }
116
+ return values;
117
+ }
118
+ /**
119
+ * Runs one invocation of the slash command on Pi: sections, then an item or
120
+ * a section action, then the action's inputs and confirmation; after each
121
+ * apply the user is back in the same section with the refreshed menu. Each
122
+ * section's list ends with Back, which returns to the section list; backing
123
+ * out of the section list ends the invocation.
124
+ */
125
+ export async function runPiCommandMenu(menu, ui, options = {}) {
126
+ const invocation = {
127
+ ...(options.sessionId !== undefined
128
+ ? { sessionId: options.sessionId }
129
+ : {}),
130
+ notify: (message, kind) => ui.notify(message, kind),
131
+ };
132
+ let model = (await menu.open(invocation)).menu;
133
+ for (;;) {
134
+ const chosen = await pick(ui, model.title, model.sections.map((section) => ({
135
+ label: section.lines[0]
136
+ ? `${section.title}: ${section.lines[0]}`
137
+ : section.title,
138
+ value: section.id,
139
+ })));
140
+ if (chosen === undefined)
141
+ return;
142
+ for (;;) {
143
+ const section = model.sections.find((entry) => entry.id === chosen);
144
+ if (!section)
145
+ break;
146
+ // Only rows that do something are options; the rest is title text.
147
+ const entries = [
148
+ ...section.items
149
+ .filter((item) => item.actions.length > 0)
150
+ .map((item) => ({
151
+ label: rowText(item),
152
+ value: { kind: 'item', item },
153
+ })),
154
+ ...section.actions.map((action) => ({
155
+ label: action.label,
156
+ value: { kind: 'action', action },
157
+ })),
158
+ ];
159
+ const text = sectionText(section);
160
+ if (entries.length === 0 && text.length === 0) {
161
+ ui.notify('Nothing to do here.');
162
+ break;
163
+ }
164
+ const choice = await pick(ui, withLines(section.title, text), [
165
+ ...entries,
166
+ { label: 'Back', value: { kind: 'back' } },
167
+ ]);
168
+ if (!choice || choice.kind === 'back')
169
+ break;
170
+ let action;
171
+ let item;
172
+ if (choice.kind === 'item') {
173
+ item = choice.item;
174
+ action = await pick(ui, itemTitle(item), item.actions.map((entry) => ({ label: entry.label, value: entry })));
175
+ if (!action)
176
+ continue;
177
+ }
178
+ else {
179
+ action = choice.action;
180
+ }
181
+ const values = await askValues(ui, action);
182
+ if (!values)
183
+ continue;
184
+ if (action.confirm) {
185
+ const yes = await ui.confirm(action.label, action.confirm.message);
186
+ if (!yes) {
187
+ ui.notify('Cancelled.');
188
+ continue;
189
+ }
190
+ }
191
+ const result = await menu.apply({
192
+ command: model.command,
193
+ sectionId: section.id,
194
+ ...(item ? { itemId: item.id } : {}),
195
+ actionId: action.id,
196
+ values,
197
+ ...(action.confirm ? { confirmed: true } : {}),
198
+ ...(options.sessionId !== undefined
199
+ ? { sessionId: options.sessionId }
200
+ : {}),
201
+ }, invocation);
202
+ ui.notify(result.text, result.ok ? 'info' : 'error');
203
+ model = result.menu;
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,72 @@
1
+ import { type RedactionOptions } from '../logger/index.js';
2
+ import type { ActionDefinition, CommandApplyResult, CommandDialogPayload, ItemDefinition, MenuAccount, MenuConfirmation, SectionContent, SectionSlot } from './model.js';
3
+ /** Where a dropped field is reported. Names only, never values. */
4
+ export interface SeamLogger {
5
+ warn(message: string, data?: unknown): void;
6
+ }
7
+ /** Masks the secret-shaped parts of one string. */
8
+ export type TextRedactor = (text: string) => string;
9
+ /** The string redactor every value crossing the seam goes through. */
10
+ export declare function createTextRedactor(options?: RedactionOptions): TextRedactor;
11
+ /**
12
+ * A failure whose message was written for the user. An action throws it to
13
+ * show `message` with the stable `code`; any other thrown value is shown only
14
+ * as a generic message, because its text may quote a request, a response or
15
+ * a credential. `code` is lowercase letters, digits and dashes; anything else
16
+ * is reported as `action-failed`.
17
+ */
18
+ export declare class CommandError extends Error {
19
+ readonly code: string;
20
+ constructor(code: string, message: string, options?: {
21
+ cause?: unknown;
22
+ });
23
+ }
24
+ /** What a failed action shows: a stable code and a message safe to display. */
25
+ export interface ProjectedFailure {
26
+ code: string;
27
+ text: string;
28
+ }
29
+ /** The code and message shown for an exception the seam cannot vouch for. */
30
+ export declare const ACTION_FAILED: ProjectedFailure;
31
+ /** The code and display text for a thrown value; never its raw text. */
32
+ export declare function projectFailure(error: unknown): ProjectedFailure;
33
+ /** A built-in item may carry the account it shows; plugin items cannot. */
34
+ export interface ResolvedItem extends ItemDefinition {
35
+ account?: MenuAccount;
36
+ }
37
+ /** A section with its bodies, as the menu holds it between builds. */
38
+ export interface ResolvedSection {
39
+ id: string;
40
+ slot: SectionSlot;
41
+ title: string;
42
+ content: Omit<SectionContent, 'items'> & {
43
+ items?: ResolvedItem[];
44
+ };
45
+ }
46
+ /** The confirmation shown when an irreversible action names none. */
47
+ export declare const DEFAULT_IRREVERSIBLE_CONFIRMATION = "This cannot be undone. Continue?";
48
+ /** What every payload builder needs besides the payload itself. */
49
+ export interface SeamContext {
50
+ logger: SeamLogger;
51
+ redact: TextRedactor;
52
+ }
53
+ /**
54
+ * The confirmation an action must pass before it runs, or undefined. An
55
+ * irreversible action always has one, even when its definition (built
56
+ * outside the type checker) names none.
57
+ */
58
+ export declare function confirmationOf(action: ActionDefinition): MenuConfirmation | undefined;
59
+ /** The account fields a renderer may show; see `MenuAccount`. */
60
+ export declare function projectAccount(account: MenuAccount): MenuAccount;
61
+ /** The payload a host's TUI receives when the slash command opens. */
62
+ export declare function dialogPayload(command: string, title: string, sections: readonly ResolvedSection[], seam: SeamContext): CommandDialogPayload;
63
+ /** How an apply ended, before the seam turns it into a payload. */
64
+ export interface ApplyOutcome {
65
+ ok: boolean;
66
+ text: string;
67
+ /** Present on every failure. */
68
+ code?: string;
69
+ needsConfirmation?: boolean;
70
+ }
71
+ /** An apply's result: the message and the refreshed menu. */
72
+ export declare function applyResult(command: string, title: string, sections: readonly ResolvedSection[], outcome: ApplyOutcome, seam: SeamContext): CommandApplyResult;
@@ -0,0 +1,301 @@
1
+ // The one way out of the process for anything the command menu shows. Every
2
+ // payload a renderer receives (the dialog payload and every apply result) is
3
+ // produced here: each part is projected field by field from its definition,
4
+ // so action bodies and stray properties never travel, and the whole payload
5
+ // is then scrubbed as a backstop: credential-shaped property names are
6
+ // dropped and every string value is passed through the redactor. Nothing
7
+ // outside this module can build a payload without passing through
8
+ // `dialogPayload` or `applyResult`. A failed action never shows its raw
9
+ // exception text: `projectFailure` turns it into a stable code and a message
10
+ // the library or the plugin wrote for the user.
11
+ import { createRedactor } from '../logger/index.js';
12
+ import { PoolOperationError } from '../store/index.js';
13
+ /**
14
+ * Secret shapes the seam masks on top of the logger's (bearer tokens, `sk-`
15
+ * keys, JWTs, vault host tokens): a value assigned to a secret-named field
16
+ * (`client_secret=…`, `"api_key": "…"`, `Authorization: …`), and a run of 32
17
+ * or more hex digits, the shape of a minted request secret. A plugin adds its
18
+ * provider's key shapes through `RedactionOptions.extraValuePatterns`.
19
+ */
20
+ const SEAM_VALUE_PATTERNS = [
21
+ /\b[\w-]*(?:secret|token|password|passwd|api[_-]?key|authorization|verifier)["']?\s*[:=]\s*["']?(?:(?:Bearer|Basic)\s+)?[^\s"'&,;}]+/gi,
22
+ /\b[0-9a-fA-F]{32,}\b/g,
23
+ ];
24
+ /** The string redactor every value crossing the seam goes through. */
25
+ export function createTextRedactor(options = {}) {
26
+ const { redactStrings } = createRedactor({
27
+ ...options,
28
+ extraValuePatterns: [
29
+ ...SEAM_VALUE_PATTERNS,
30
+ ...(options.extraValuePatterns ?? []),
31
+ ],
32
+ });
33
+ return (text) => redactStrings(text);
34
+ }
35
+ /**
36
+ * A failure whose message was written for the user. An action throws it to
37
+ * show `message` with the stable `code`; any other thrown value is shown only
38
+ * as a generic message, because its text may quote a request, a response or
39
+ * a credential. `code` is lowercase letters, digits and dashes; anything else
40
+ * is reported as `action-failed`.
41
+ */
42
+ export class CommandError extends Error {
43
+ code;
44
+ constructor(code, message, options) {
45
+ super(message, options);
46
+ this.name = 'CommandError';
47
+ this.code = code;
48
+ }
49
+ }
50
+ const CODE_SHAPE = /^[a-z][a-z0-9-]{0,63}$/;
51
+ /** The code and message shown for an exception the seam cannot vouch for. */
52
+ export const ACTION_FAILED = {
53
+ code: 'action-failed',
54
+ text: 'That action failed.',
55
+ };
56
+ /**
57
+ * Store failure kinds whose message the store composes itself from row ids
58
+ * and the plugin's own refusal reasons (`protect`). Other kinds can carry a
59
+ * lock or filesystem error's text, or an arbitrary exception from a hook, so
60
+ * they show a generic message with the kind as the code.
61
+ */
62
+ const STORE_KINDS_WITH_OWN_MESSAGE = new Set([
63
+ 'unknown-row',
64
+ 'invalid-row',
65
+ 'invalid-input',
66
+ 'id-exists',
67
+ 'id-removed',
68
+ 'type-mismatch',
69
+ 'no-credential',
70
+ 'row-disabled',
71
+ 'row-protected',
72
+ 'duplicate-identity',
73
+ 'row-key-changed',
74
+ 'invalid-order',
75
+ 'pending-migration',
76
+ 'provider',
77
+ 'pull',
78
+ 'attribution',
79
+ ]);
80
+ /** The code and display text for a thrown value; never its raw text. */
81
+ export function projectFailure(error) {
82
+ if (error instanceof CommandError)
83
+ return {
84
+ code: CODE_SHAPE.test(error.code) ? error.code : ACTION_FAILED.code,
85
+ text: error.message,
86
+ };
87
+ if (error instanceof PoolOperationError)
88
+ return {
89
+ code: `pool-${error.kind}`,
90
+ text: STORE_KINDS_WITH_OWN_MESSAGE.has(error.kind)
91
+ ? error.message
92
+ : `The account pool could not do that (${error.kind}).`,
93
+ };
94
+ return ACTION_FAILED;
95
+ }
96
+ /** The confirmation shown when an irreversible action names none. */
97
+ export const DEFAULT_IRREVERSIBLE_CONFIRMATION = 'This cannot be undone. Continue?';
98
+ const CREDENTIAL_NAMES = new Set([
99
+ 'access',
100
+ 'refresh',
101
+ 'apikey',
102
+ 'password',
103
+ 'authheader',
104
+ 'credential',
105
+ 'credentials',
106
+ ]);
107
+ /**
108
+ * True for a property name that looks like it holds a secret. Case, `-` and
109
+ * `_` are ignored, so `api_key`, `API-Key` and `apiKey` all match. Any name
110
+ * ending in `token`, `key` or `secret` matches: no menu field is named that
111
+ * way, so a match is a leak whatever the rest of the name says.
112
+ */
113
+ function isCredentialName(name) {
114
+ const normalized = name.toLowerCase().replace(/[-_]/g, '');
115
+ return (CREDENTIAL_NAMES.has(normalized) ||
116
+ normalized.endsWith('token') ||
117
+ normalized.endsWith('key') ||
118
+ normalized.endsWith('secret'));
119
+ }
120
+ function isPlainRecord(value) {
121
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
122
+ }
123
+ /**
124
+ * A copy of `value` without credential-shaped properties and with every
125
+ * string redacted; the paths of both go to `report`.
126
+ */
127
+ function scrub(value, path, report) {
128
+ if (typeof value === 'string') {
129
+ const clean = report.redact(value);
130
+ if (clean !== value)
131
+ report.redacted.push(path);
132
+ return clean;
133
+ }
134
+ if (Array.isArray(value))
135
+ return value.map((entry, index) => scrub(entry, `${path}[${index}]`, report));
136
+ if (!isPlainRecord(value))
137
+ return value;
138
+ const out = {};
139
+ for (const [name, entry] of Object.entries(value)) {
140
+ if (isCredentialName(name)) {
141
+ report.dropped.push(`${path}.${name}`);
142
+ continue;
143
+ }
144
+ Object.defineProperty(out, name, {
145
+ value: scrub(entry, `${path}.${name}`, report),
146
+ enumerable: true,
147
+ writable: true,
148
+ configurable: true,
149
+ });
150
+ }
151
+ return out;
152
+ }
153
+ function sealed(payload, command, seam) {
154
+ const report = {
155
+ redact: seam.redact,
156
+ dropped: [],
157
+ redacted: [],
158
+ };
159
+ const clean = scrub(payload, 'payload', report);
160
+ if (report.dropped.length > 0)
161
+ seam.logger.warn('credential-shaped field dropped from a command payload', {
162
+ command,
163
+ fields: report.dropped,
164
+ });
165
+ if (report.redacted.length > 0)
166
+ seam.logger.warn('secret-shaped text masked in a command payload', {
167
+ command,
168
+ fields: report.redacted,
169
+ });
170
+ return clean;
171
+ }
172
+ function projectChoice(choice) {
173
+ return { value: String(choice.value), label: String(choice.label) };
174
+ }
175
+ function projectKnob(knob) {
176
+ switch (knob.kind) {
177
+ case 'choice':
178
+ return {
179
+ kind: 'choice',
180
+ id: knob.id,
181
+ label: knob.label,
182
+ choices: (knob.choices ?? []).map(projectChoice),
183
+ ...(knob.value !== undefined ? { value: knob.value } : {}),
184
+ };
185
+ case 'toggle':
186
+ return {
187
+ kind: 'toggle',
188
+ id: knob.id,
189
+ label: knob.label,
190
+ value: knob.value === true,
191
+ };
192
+ case 'number':
193
+ return {
194
+ kind: 'number',
195
+ id: knob.id,
196
+ label: knob.label,
197
+ ...(knob.value !== undefined ? { value: knob.value } : {}),
198
+ ...(knob.min !== undefined ? { min: knob.min } : {}),
199
+ ...(knob.max !== undefined ? { max: knob.max } : {}),
200
+ ...(knob.required ? { required: true } : {}),
201
+ };
202
+ case 'text':
203
+ // A masked input holds a secret, so its current value is never shown;
204
+ // the action still receives it when the user leaves the input alone.
205
+ return {
206
+ kind: 'text',
207
+ id: knob.id,
208
+ label: knob.label,
209
+ ...(knob.value !== undefined && !knob.masked
210
+ ? { value: knob.value }
211
+ : {}),
212
+ ...(knob.placeholder !== undefined
213
+ ? { placeholder: knob.placeholder }
214
+ : {}),
215
+ ...(knob.masked ? { masked: true } : {}),
216
+ ...(knob.required ? { required: true } : {}),
217
+ };
218
+ }
219
+ }
220
+ /**
221
+ * The confirmation an action must pass before it runs, or undefined. An
222
+ * irreversible action always has one, even when its definition (built
223
+ * outside the type checker) names none.
224
+ */
225
+ export function confirmationOf(action) {
226
+ if (action.irreversible === true)
227
+ return {
228
+ message: action.confirm || DEFAULT_IRREVERSIBLE_CONFIRMATION,
229
+ irreversible: true,
230
+ };
231
+ if (action.confirm)
232
+ return { message: action.confirm, irreversible: false };
233
+ return undefined;
234
+ }
235
+ function projectAction(action) {
236
+ const confirm = confirmationOf(action);
237
+ return {
238
+ id: action.id,
239
+ label: action.label,
240
+ ...(action.description !== undefined
241
+ ? { description: action.description }
242
+ : {}),
243
+ ...(action.group !== undefined ? { group: String(action.group) } : {}),
244
+ knobs: (action.knobs ?? []).map(projectKnob),
245
+ ...(confirm ? { confirm } : {}),
246
+ };
247
+ }
248
+ /** The account fields a renderer may show; see `MenuAccount`. */
249
+ export function projectAccount(account) {
250
+ return {
251
+ id: account.id,
252
+ ...(account.label !== undefined ? { label: account.label } : {}),
253
+ enabled: account.enabled,
254
+ type: account.type,
255
+ ...(account.identity !== undefined ? { identity: account.identity } : {}),
256
+ };
257
+ }
258
+ function projectItem(item) {
259
+ return {
260
+ id: item.id,
261
+ label: item.label,
262
+ ...(item.detail !== undefined ? { detail: item.detail } : {}),
263
+ ...(item.group !== undefined ? { group: String(item.group) } : {}),
264
+ ...(item.status !== undefined ? { status: String(item.status) } : {}),
265
+ ...(item.account ? { account: projectAccount(item.account) } : {}),
266
+ ...(item.facts !== undefined ? { facts: item.facts } : {}),
267
+ actions: (item.actions ?? []).map(projectAction),
268
+ };
269
+ }
270
+ function projectSection(section) {
271
+ const { content } = section;
272
+ return {
273
+ id: section.id,
274
+ slot: section.slot,
275
+ title: section.title,
276
+ lines: (content.lines ?? []).map(String),
277
+ items: (content.items ?? []).map(projectItem),
278
+ actions: (content.actions ?? []).map(projectAction),
279
+ ...(content.facts !== undefined ? { facts: content.facts } : {}),
280
+ };
281
+ }
282
+ function projectMenu(command, title, sections) {
283
+ return { command, title, sections: sections.map(projectSection) };
284
+ }
285
+ /** The payload a host's TUI receives when the slash command opens. */
286
+ export function dialogPayload(command, title, sections, seam) {
287
+ return sealed({ command, menu: projectMenu(command, title, sections) }, command, seam);
288
+ }
289
+ /** An apply's result: the message and the refreshed menu. */
290
+ export function applyResult(command, title, sections, outcome, seam) {
291
+ return sealed({
292
+ command,
293
+ ok: outcome.ok,
294
+ text: String(outcome.text),
295
+ ...(!outcome.ok && outcome.code !== undefined
296
+ ? { code: CODE_SHAPE.test(outcome.code) ? outcome.code : 'refused' }
297
+ : {}),
298
+ ...(outcome.needsConfirmation ? { needsConfirmation: true } : {}),
299
+ menu: projectMenu(command, title, sections),
300
+ }, command, seam);
301
+ }
@@ -0,0 +1,20 @@
1
+ export interface AtomicWriteOptions {
2
+ serialize?: (value: unknown) => string;
3
+ beforeRename?: () => Promise<void>;
4
+ /** Test seam for forcing staging-name collisions; defaults to randomUUID. */
5
+ stageName?: () => string;
6
+ /** Sync the staged file and, after rename, its parent directory. Opt-in. */
7
+ durable?: boolean;
8
+ }
9
+ /** Internal classification of fsync failures, including a failure after rename. */
10
+ export declare class AtomicSyncError extends Error {
11
+ constructor(cause: unknown);
12
+ }
13
+ export declare function writeJsonAtomic(path: string, value: unknown, options?: AtomicWriteOptions): Promise<void>;
14
+ /**
15
+ * Notify internal callers at the rename. The store records committed writes
16
+ * and removed ids here, because a later directory sync cannot undo the rename.
17
+ */
18
+ export declare function writeJsonAtomicTracked(path: string, value: unknown, options: AtomicWriteOptions, onRenamed?: () => void): Promise<void>;
19
+ /** Make an already renamed file and its directory durable without rewriting bytes. */
20
+ export declare function syncJsonFile(path: string): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, open, rename, rm } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ /** Internal classification of fsync failures, including a failure after rename. */
5
+ export class AtomicSyncError extends Error {
6
+ constructor(cause) {
7
+ super('file or directory sync failed', { cause });
8
+ this.name = 'AtomicSyncError';
9
+ }
10
+ }
11
+ export async function writeJsonAtomic(path, value, options = {}) {
12
+ await writeJsonAtomicTracked(path, value, options);
13
+ }
14
+ /**
15
+ * Notify internal callers at the rename. The store records committed writes
16
+ * and removed ids here, because a later directory sync cannot undo the rename.
17
+ */
18
+ export async function writeJsonAtomicTracked(path, value, options, onRenamed) {
19
+ await mkdir(dirname(path), { recursive: true });
20
+ const tempPath = `${path}.${(options.stageName ?? randomUUID)()}.tmp`;
21
+ let handle;
22
+ let created = false;
23
+ try {
24
+ handle = await open(tempPath, 'wx', 0o600);
25
+ created = true;
26
+ await handle.writeFile(options.serialize
27
+ ? options.serialize(value)
28
+ : `${JSON.stringify(value, null, 2)}\n`, 'utf8');
29
+ await handle.chmod(0o600);
30
+ if (options.durable) {
31
+ try {
32
+ await handle.sync();
33
+ }
34
+ catch (cause) {
35
+ throw new AtomicSyncError(cause);
36
+ }
37
+ }
38
+ await handle.close();
39
+ handle = undefined;
40
+ await options.beforeRename?.();
41
+ await rename(tempPath, path);
42
+ created = false;
43
+ onRenamed?.();
44
+ if (options.durable) {
45
+ const directory = await open(dirname(path), 'r');
46
+ try {
47
+ await directory.sync();
48
+ }
49
+ catch (cause) {
50
+ throw new AtomicSyncError(cause);
51
+ }
52
+ finally {
53
+ await directory.close();
54
+ }
55
+ }
56
+ }
57
+ finally {
58
+ await handle?.close().catch(() => { });
59
+ // A failed write can leave partial bytes, but a collision is not ours to remove.
60
+ if (created)
61
+ await rm(tempPath, { force: true }).catch(() => { });
62
+ }
63
+ }
64
+ /** Make an already renamed file and its directory durable without rewriting bytes. */
65
+ export async function syncJsonFile(path) {
66
+ const file = await open(path, 'r');
67
+ try {
68
+ await file.sync();
69
+ }
70
+ finally {
71
+ await file.close();
72
+ }
73
+ const directory = await open(dirname(path), 'r');
74
+ try {
75
+ await directory.sync();
76
+ }
77
+ finally {
78
+ await directory.close();
79
+ }
80
+ }
@@ -0,0 +1,7 @@
1
+ export type { AtomicWriteOptions } from './atomic-write.js';
2
+ export { writeJsonAtomic } from './atomic-write.js';
3
+ export { WRITER_LOCK_CONSTANTS } from './lock-constants.js';
4
+ export type { LockLoss, RefreshFileLock } from './refresh-file-lock.js';
5
+ export { acquireRefreshFileLock, isLostMarkerRaceError, } from './refresh-file-lock.js';
6
+ export type { LockOptions, LockOwnershipDetails } from './with-lock.js';
7
+ export { LockContentionError, LockOwnershipError, lockPathFor, withLock, } from './with-lock.js';