@intentic/sandbox-contract 1.337.0 → 1.339.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +4 -7
- package/dist/chores/probes.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +32 -23
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +6020 -5994
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +13 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/approvals.contract.d.ts +8 -0
- package/dist/contracts/approvals.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.d.ts +3 -3
- package/dist/contracts/capabilities.contract.d.ts +28 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +11 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/device.contract.d.ts +225 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +10 -0
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +16 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +22 -7
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/org.contract.d.ts +35 -0
- package/dist/contracts/org.contract.d.ts.map +1 -0
- package/dist/contracts/org.contract.js +14 -0
- package/dist/contracts/org.contract.js.map +1 -0
- package/dist/contracts/phone.contract.d.ts +1 -0
- package/dist/contracts/phone.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.d.ts +115 -0
- package/dist/contracts/privacy.contract.d.ts.map +1 -1
- package/dist/contracts/privacy.contract.js +11 -2
- package/dist/contracts/privacy.contract.js.map +1 -1
- package/dist/contracts/runner.contract.d.ts +11 -7
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +85 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +48 -1
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +4 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +11 -3
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +23 -2
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +17 -4
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/requests.d.ts +4 -0
- package/dist/events/requests.d.ts.map +1 -1
- package/dist/events/requests.js +1 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/system-events.d.ts +4 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +2 -0
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +27 -6
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/index.d.ts +7562 -7426
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -257
- package/dist/index.js.map +1 -1
- package/dist/models/agent-lanes.d.ts +30 -0
- package/dist/models/agent-lanes.d.ts.map +1 -0
- package/dist/models/agent-lanes.js +67 -0
- package/dist/models/agent-lanes.js.map +1 -0
- package/dist/models/agent-standing.d.ts +55 -0
- package/dist/models/agent-standing.d.ts.map +1 -0
- package/dist/models/agent-standing.js +82 -0
- package/dist/models/agent-standing.js.map +1 -0
- package/dist/models/agent-starter.d.ts +10 -0
- package/dist/models/agent-starter.d.ts.map +1 -0
- package/dist/models/agent-starter.js +16 -0
- package/dist/models/agent-starter.js.map +1 -0
- package/dist/netd/generated/wire.d.ts +1 -1
- package/dist/netd/generated/wire.d.ts.map +1 -1
- package/dist/policy/identity-assertion.d.ts.map +1 -1
- package/dist/policy/identity-assertion.js +2 -8
- package/dist/policy/identity-assertion.js.map +1 -1
- package/dist/policy/keyring-seal.d.ts +48 -0
- package/dist/policy/keyring-seal.d.ts.map +1 -0
- package/dist/policy/keyring-seal.js +94 -0
- package/dist/policy/keyring-seal.js.map +1 -0
- package/dist/policy/message-limits.d.ts +2 -0
- package/dist/policy/message-limits.d.ts.map +1 -1
- package/dist/policy/message-limits.js +2 -0
- package/dist/policy/message-limits.js.map +1 -1
- package/dist/policy/org-locks.d.ts +58 -0
- package/dist/policy/org-locks.d.ts.map +1 -0
- package/dist/policy/org-locks.js +113 -0
- package/dist/policy/org-locks.js.map +1 -0
- package/dist/policy/persona-capability.d.ts +14 -0
- package/dist/policy/persona-capability.d.ts.map +1 -0
- package/dist/policy/persona-capability.js +23 -0
- package/dist/policy/persona-capability.js.map +1 -0
- package/dist/policy/reserved-servers.d.ts.map +1 -1
- package/dist/policy/reserved-servers.js +1 -1
- package/dist/policy/reserved-servers.js.map +1 -1
- package/dist/protocol/doors/phone-links.d.ts +5 -0
- package/dist/protocol/doors/phone-links.d.ts.map +1 -1
- package/dist/protocol/doors/phone-links.js +3 -1
- package/dist/protocol/doors/phone-links.js.map +1 -1
- package/dist/protocol/doors/webext-links.d.ts +3 -0
- package/dist/protocol/doors/webext-links.d.ts.map +1 -1
- package/dist/protocol/doors/webext-links.js +3 -0
- package/dist/protocol/doors/webext-links.js.map +1 -1
- package/dist/protocol/raw/raw-json-routes.d.ts +270 -0
- package/dist/protocol/raw/raw-json-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-json-routes.js +14 -1
- package/dist/protocol/raw/raw-json-routes.js.map +1 -1
- package/dist/protocol/raw/raw-routes.d.ts +70 -0
- package/dist/protocol/raw/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw/raw-routes.js +18 -0
- package/dist/protocol/raw/raw-routes.js.map +1 -1
- package/dist/protocol/route-meta.d.ts.map +1 -1
- package/dist/protocol/route-meta.js.map +1 -1
- package/dist/protocol/routes.d.ts +4 -0
- package/dist/protocol/routes.d.ts.map +1 -1
- package/dist/protocol/routes.js +12 -0
- package/dist/protocol/routes.js.map +1 -1
- package/dist/schemas/agents-list.d.ts +5 -0
- package/dist/schemas/agents-list.d.ts.map +1 -1
- package/dist/schemas/agents-list.js +8 -0
- package/dist/schemas/agents-list.js.map +1 -1
- package/dist/schemas/agents.d.ts +1 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +1 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/approvals.d.ts +20 -0
- package/dist/schemas/approvals.d.ts.map +1 -1
- package/dist/schemas/approvals.js +10 -0
- package/dist/schemas/approvals.js.map +1 -1
- package/dist/schemas/capabilities.d.ts +26 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +17 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/data-protection.d.ts +5 -0
- package/dist/schemas/data-protection.d.ts.map +1 -1
- package/dist/schemas/data-protection.js +29 -0
- package/dist/schemas/data-protection.js.map +1 -1
- package/dist/schemas/devices.d.ts +20 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +5 -1
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +32 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/git/git.d.ts +19 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +12 -1
- package/dist/schemas/git/git.js.map +1 -1
- package/dist/schemas/hosts.d.ts +2 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +11 -1
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/intentic-os.d.ts +218 -0
- package/dist/schemas/intentic-os.d.ts.map +1 -1
- package/dist/schemas/intentic-os.js +166 -0
- package/dist/schemas/intentic-os.js.map +1 -1
- package/dist/schemas/keyring.d.ts +421 -0
- package/dist/schemas/keyring.d.ts.map +1 -0
- package/dist/schemas/keyring.js +176 -0
- package/dist/schemas/keyring.js.map +1 -0
- package/dist/schemas/links.d.ts +208 -0
- package/dist/schemas/links.d.ts.map +1 -0
- package/dist/schemas/links.js +116 -0
- package/dist/schemas/links.js.map +1 -0
- package/dist/schemas/maintenance.d.ts.map +1 -1
- package/dist/schemas/maintenance.js.map +1 -1
- package/dist/schemas/members.d.ts.map +1 -1
- package/dist/schemas/members.js +2 -0
- package/dist/schemas/members.js.map +1 -1
- package/dist/schemas/needs.d.ts +25 -8
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +15 -3
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/organisation.d.ts +106 -0
- package/dist/schemas/organisation.d.ts.map +1 -0
- package/dist/schemas/organisation.js +52 -0
- package/dist/schemas/organisation.js.map +1 -0
- package/dist/schemas/phone.d.ts +3 -0
- package/dist/schemas/phone.d.ts.map +1 -1
- package/dist/schemas/phone.js +1 -0
- package/dist/schemas/phone.js.map +1 -1
- package/dist/schemas/privacy.d.ts +232 -0
- package/dist/schemas/privacy.d.ts.map +1 -1
- package/dist/schemas/privacy.js +154 -4
- package/dist/schemas/privacy.js.map +1 -1
- package/dist/schemas/secrets.d.ts +151 -0
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +71 -8
- package/dist/schemas/secrets.js.map +1 -1
- package/dist/schemas/settings.d.ts +8 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +4 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/speech.d.ts +12 -0
- package/dist/schemas/speech.d.ts.map +1 -1
- package/dist/schemas/speech.js +23 -0
- package/dist/schemas/speech.js.map +1 -1
- package/dist/schemas/system.d.ts +2 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +1 -1
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/terminal.d.ts +15 -0
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +11 -2
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/voice.d.ts +3 -0
- package/dist/schemas/voice.d.ts.map +1 -1
- package/dist/schemas/voice.js +7 -0
- package/dist/schemas/voice.js.map +1 -1
- package/dist/schemas/webext.d.ts +17 -0
- package/dist/schemas/webext.d.ts.map +1 -1
- package/dist/schemas/webext.js +7 -1
- package/dist/schemas/webext.js.map +1 -1
- package/dist/state/contract-lock.d.ts +3 -1
- package/dist/state/contract-lock.d.ts.map +1 -1
- package/dist/state/contract-lock.js +34 -9
- package/dist/state/contract-lock.js.map +1 -1
- package/dist/state/contract-modules.d.ts +4 -0
- package/dist/state/contract-modules.d.ts.map +1 -0
- package/dist/state/contract-modules.js +28 -0
- package/dist/state/contract-modules.js.map +1 -0
- package/dist/state/daemon-floor.d.ts +4 -0
- package/dist/state/daemon-floor.d.ts.map +1 -0
- package/dist/state/daemon-floor.js +8 -0
- package/dist/state/daemon-floor.js.map +1 -0
- package/dist/state/definition.d.ts +34 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +8 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/text/documents.d.ts +3 -1
- package/dist/text/documents.d.ts.map +1 -1
- package/dist/text/documents.js +30 -0
- package/dist/text/documents.js.map +1 -1
- package/dist/text/pages.d.ts.map +1 -1
- package/dist/text/pages.js +3 -7
- package/dist/text/pages.js.map +1 -1
- package/package.json +27 -5
- package/src/chores/probes.ts +4 -6
- package/src/contracts/agents.contract.ts +18 -1
- package/src/contracts/capabilities.contract.ts +14 -0
- package/src/contracts/device.contract.ts +30 -0
- package/src/contracts/org.contract.ts +19 -0
- package/src/contracts/privacy.contract.ts +13 -1
- package/src/contracts/secrets.contract.ts +64 -0
- package/src/events/requests.ts +3 -1
- package/src/events/system-events.ts +9 -0
- package/src/index.ts +41 -279
- package/src/models/agent-lanes.ts +152 -0
- package/src/models/agent-standing.ts +279 -0
- package/src/models/agent-starter.ts +26 -0
- package/src/netd/generated/netd-wire.json +2 -1
- package/src/netd/generated/wire.ts +1 -1
- package/src/policy/identity-assertion.ts +2 -8
- package/src/policy/keyring-seal.test.ts +93 -0
- package/src/policy/keyring-seal.ts +148 -0
- package/src/policy/message-limits.ts +3 -0
- package/src/policy/org-locks.ts +173 -0
- package/src/policy/persona-capability.ts +57 -0
- package/src/policy/reserved-servers.ts +3 -2
- package/src/policy/tests/org-locks.test.ts +92 -0
- package/src/policy/tests/reserved-servers.test.ts +3 -2
- package/src/protocol/doors/listener-protocol.ts +1 -1
- package/src/protocol/doors/phone-links.ts +11 -2
- package/src/protocol/doors/phone-protocol.test.ts +8 -1
- package/src/protocol/doors/webext-links.ts +13 -0
- package/src/protocol/ingress-contract.fixture.json +2 -0
- package/src/protocol/pairing-redeem.ts +1 -1
- package/src/protocol/peer-dial.test.ts +6 -6
- package/src/protocol/peer-mcp-server.test.ts +2 -1
- package/src/protocol/raw/raw-json-routes.ts +31 -1
- package/src/protocol/raw/raw-routes.ts +35 -1
- package/src/protocol/route-meta.ts +2 -1
- package/src/protocol/routes.test.ts +33 -1
- package/src/protocol/routes.ts +18 -0
- package/src/protocol/sse.ts +1 -1
- package/src/schemas/agent.ts +1 -1
- package/src/schemas/agents-list.ts +13 -0
- package/src/schemas/agents.ts +1 -0
- package/src/schemas/approvals.ts +14 -0
- package/src/schemas/capabilities.ts +25 -0
- package/src/schemas/data-protection.ts +47 -0
- package/src/schemas/devices.ts +15 -1
- package/src/schemas/git/git.ts +22 -2
- package/src/schemas/hosts.ts +14 -1
- package/src/schemas/intentic-os.test.ts +148 -0
- package/src/schemas/intentic-os.ts +259 -0
- package/src/schemas/keyring.ts +277 -0
- package/src/schemas/links.ts +180 -0
- package/src/schemas/maintenance.ts +1 -0
- package/src/schemas/members.ts +4 -0
- package/src/schemas/needs.ts +21 -5
- package/src/schemas/organisation.ts +110 -0
- package/src/schemas/phone.ts +3 -0
- package/src/schemas/privacy.test.ts +63 -0
- package/src/schemas/privacy.ts +213 -5
- package/src/schemas/secrets.ts +96 -8
- package/src/schemas/settings.ts +8 -0
- package/src/schemas/speech.ts +38 -1
- package/src/schemas/system.ts +5 -2
- package/src/schemas/terminal.ts +13 -2
- package/src/schemas/voice.ts +11 -0
- package/src/schemas/webext.ts +16 -1
- package/src/state/contract-lock.test.ts +12 -12
- package/src/state/contract-lock.ts +43 -12
- package/src/state/contract-modules.ts +41 -0
- package/src/state/daemon-floor.test.ts +81 -0
- package/src/state/daemon-floor.ts +32 -0
- package/src/state/history-state.ts +17 -0
- package/src/state/workspace-state.ts +1 -1
- package/src/text/documents.test.ts +30 -1
- package/src/text/documents.ts +43 -1
- package/src/text/pages.ts +5 -7
|
@@ -128,3 +128,262 @@ export const icOsRebootArgs = (): string[] => ["os", "reboot"];
|
|
|
128
128
|
// Checked here too, not only by a caller's schema: a channel is a positional, and one beginning with a hyphen would
|
|
129
129
|
// reach ic as a flag.
|
|
130
130
|
export const icOsChannelArgs = (channel: IntenticOsChannel): string[] => ["os", "channel", IntenticOsChannelSchema.parse(channel)];
|
|
131
|
+
|
|
132
|
+
// THE MACHINE'S OWN SETTINGS (2026-10-16): the network, the sound, the screen, the keyboard and the drives a person plugs
|
|
133
|
+
// in, each a verb of `ic os` (_os/README.md, "Settings"). The desktop app's This computer cards, its status bar and the
|
|
134
|
+
// machine agent's tools all read these shapes, so a person and the agent in the machine's own sandbox change the same
|
|
135
|
+
// things the same way ("connect to the guest Wi-Fi"). ic reads NetworkManager, WirePlumber, sway and udisks for them:
|
|
136
|
+
// nothing that calls ic speaks to those.
|
|
137
|
+
|
|
138
|
+
/** Exit code of the screen verbs (display, keyboard) on an edition with no screen (the server edition). */
|
|
139
|
+
export const IC_OS_NO_SCREEN_EXIT = 4;
|
|
140
|
+
|
|
141
|
+
/** Exit code of `ic os network wifi connect` when the network needs a password it was not given, or refused the one it
|
|
142
|
+
* was: ask the person for it and try again with `--password-stdin`. */
|
|
143
|
+
export const IC_OS_WIFI_PASSWORD_EXIT = 5;
|
|
144
|
+
|
|
145
|
+
/** Exit code of `ic os drives import` when what is on the drive would not fit in the sandbox: nothing was copied. */
|
|
146
|
+
export const IC_OS_DRIVE_NO_ROOM_EXIT = 6;
|
|
147
|
+
|
|
148
|
+
// `ic os network status --json`: how this machine is online right now. `name` is the Wi-Fi network's name or the wired
|
|
149
|
+
// connection's, `signal` the Wi-Fi strength 0-100 (null on a wire and offline). The status bar reads exactly this.
|
|
150
|
+
export const IntenticOsNetworkKindSchema = z.enum(["wifi", "ethernet", "none"]);
|
|
151
|
+
export type IntenticOsNetworkKind = z.infer<typeof IntenticOsNetworkKindSchema>;
|
|
152
|
+
|
|
153
|
+
const percent = z.number().int().min(0).max(100);
|
|
154
|
+
|
|
155
|
+
export const IntenticOsNetworkStatusSchema = z.object({
|
|
156
|
+
connected: z.boolean(),
|
|
157
|
+
kind: IntenticOsNetworkKindSchema,
|
|
158
|
+
name: z.string().nullable(),
|
|
159
|
+
signal: percent.nullable(),
|
|
160
|
+
});
|
|
161
|
+
export type IntenticOsNetworkStatus = z.infer<typeof IntenticOsNetworkStatusSchema>;
|
|
162
|
+
|
|
163
|
+
// One Wi-Fi network in reach, once however many access points broadcast it: the strongest signal, `secured` when it
|
|
164
|
+
// asks for a password, `known` when this machine has joined it before (it rejoins without asking), `active` when it is
|
|
165
|
+
// the one in use.
|
|
166
|
+
export const IntenticOsWifiNetworkSchema = z.object({
|
|
167
|
+
ssid: z.string(),
|
|
168
|
+
signal: percent,
|
|
169
|
+
secured: z.boolean(),
|
|
170
|
+
known: z.boolean(),
|
|
171
|
+
active: z.boolean(),
|
|
172
|
+
});
|
|
173
|
+
export type IntenticOsWifiNetwork = z.infer<typeof IntenticOsWifiNetworkSchema>;
|
|
174
|
+
|
|
175
|
+
// `ic os network wifi list --json`: `adapter` false on a machine with no Wi-Fi, `enabled` false while Wi-Fi is turned
|
|
176
|
+
// off (then `networks` is empty), the networks strongest first.
|
|
177
|
+
export const IntenticOsWifiListSchema = z.object({
|
|
178
|
+
adapter: z.boolean(),
|
|
179
|
+
enabled: z.boolean(),
|
|
180
|
+
networks: z.array(IntenticOsWifiNetworkSchema),
|
|
181
|
+
});
|
|
182
|
+
export type IntenticOsWifiList = z.infer<typeof IntenticOsWifiListSchema>;
|
|
183
|
+
|
|
184
|
+
// `ic os sound status --json`: the volume of what plays now, 0-100, whether it is muted, and the speaker or headphones it
|
|
185
|
+
// plays on (null when this machine has none). The status bar reads exactly this.
|
|
186
|
+
export const IntenticOsSoundStatusSchema = z.object({
|
|
187
|
+
volume: percent,
|
|
188
|
+
muted: z.boolean(),
|
|
189
|
+
output: z.string().nullable(),
|
|
190
|
+
});
|
|
191
|
+
export type IntenticOsSoundStatus = z.infer<typeof IntenticOsSoundStatusSchema>;
|
|
192
|
+
|
|
193
|
+
// `ic os sound outputs --json`: every speaker, headphone and screen that plays sound, `default` on the one in use.
|
|
194
|
+
// `id` is what `ic os sound output <id>` takes; it can change when a device is plugged in again.
|
|
195
|
+
export const IntenticOsSoundOutputSchema = z.object({
|
|
196
|
+
id: z.number().int().nonnegative(),
|
|
197
|
+
name: z.string(),
|
|
198
|
+
default: z.boolean(),
|
|
199
|
+
});
|
|
200
|
+
export type IntenticOsSoundOutput = z.infer<typeof IntenticOsSoundOutputSchema>;
|
|
201
|
+
export const IntenticOsSoundOutputsSchema = z.object({ outputs: z.array(IntenticOsSoundOutputSchema) });
|
|
202
|
+
export type IntenticOsSoundOutputs = z.infer<typeof IntenticOsSoundOutputsSchema>;
|
|
203
|
+
|
|
204
|
+
// A screen's mode: pixels across and down, and how many times a second it redraws (Hz, to three decimals).
|
|
205
|
+
export const IntenticOsDisplayModeSchema = z.object({
|
|
206
|
+
width: z.number().int().positive(),
|
|
207
|
+
height: z.number().int().positive(),
|
|
208
|
+
refresh: z.number().nonnegative(),
|
|
209
|
+
});
|
|
210
|
+
export type IntenticOsDisplayMode = z.infer<typeof IntenticOsDisplayModeSchema>;
|
|
211
|
+
|
|
212
|
+
// One screen as `ic os display list --json` says it: `name` is the connector (`HDMI-A-1`, `eDP-1`) every other verb
|
|
213
|
+
// names it by, `make` and `model` what the screen says it is (null where it does not), `current` the mode it shows now
|
|
214
|
+
// (null while it is off), `modes` every mode it offers, `scale` how much bigger everything is drawn (null while off).
|
|
215
|
+
export const IntenticOsDisplaySchema = z.object({
|
|
216
|
+
name: z.string(),
|
|
217
|
+
make: z.string().nullable(),
|
|
218
|
+
model: z.string().nullable(),
|
|
219
|
+
enabled: z.boolean(),
|
|
220
|
+
current: IntenticOsDisplayModeSchema.nullable(),
|
|
221
|
+
modes: z.array(IntenticOsDisplayModeSchema),
|
|
222
|
+
scale: z.number().positive().nullable(),
|
|
223
|
+
});
|
|
224
|
+
export type IntenticOsDisplay = z.infer<typeof IntenticOsDisplaySchema>;
|
|
225
|
+
export const IntenticOsDisplayListSchema = z.object({ outputs: z.array(IntenticOsDisplaySchema) });
|
|
226
|
+
export type IntenticOsDisplayList = z.infer<typeof IntenticOsDisplayListSchema>;
|
|
227
|
+
|
|
228
|
+
// `ic os keyboard status --json`: the layout in use as xkb names it (`us`, `de`), its variant (`nodeadkeys`, null for
|
|
229
|
+
// the layout's own), and its name for a person ("German (no dead keys)"), null when xkb has none for it.
|
|
230
|
+
export const IntenticOsKeyboardStatusSchema = z.object({
|
|
231
|
+
layout: z.string(),
|
|
232
|
+
variant: z.string().nullable(),
|
|
233
|
+
name: z.string().nullable(),
|
|
234
|
+
});
|
|
235
|
+
export type IntenticOsKeyboardStatus = z.infer<typeof IntenticOsKeyboardStatusSchema>;
|
|
236
|
+
|
|
237
|
+
export const IntenticOsKeyboardVariantSchema = z.object({ variant: z.string(), name: z.string() });
|
|
238
|
+
export const IntenticOsKeyboardLayoutSchema = z.object({
|
|
239
|
+
layout: z.string(),
|
|
240
|
+
name: z.string(),
|
|
241
|
+
variants: z.array(IntenticOsKeyboardVariantSchema),
|
|
242
|
+
});
|
|
243
|
+
export type IntenticOsKeyboardLayout = z.infer<typeof IntenticOsKeyboardLayoutSchema>;
|
|
244
|
+
// `ic os keyboard layouts --json`: every layout xkb knows, in its own order, each with its variants.
|
|
245
|
+
export const IntenticOsKeyboardLayoutsSchema = z.object({ layouts: z.array(IntenticOsKeyboardLayoutSchema) });
|
|
246
|
+
export type IntenticOsKeyboardLayouts = z.infer<typeof IntenticOsKeyboardLayoutsSchema>;
|
|
247
|
+
|
|
248
|
+
// What kind of removable drive: a USB stick or disk, or a memory card in a card reader.
|
|
249
|
+
export const IntenticOsDriveKindSchema = z.enum(["usb", "card", "other"]);
|
|
250
|
+
export type IntenticOsDriveKind = z.infer<typeof IntenticOsDriveKindSchema>;
|
|
251
|
+
|
|
252
|
+
// One removable drive's filesystem (a partition, or the whole stick when it has none) as `ic os drives list --json`
|
|
253
|
+
// says it: `device` is what the other drive verbs take, `label` the name it was given (null for none), `size` in bytes,
|
|
254
|
+
// `filesystem` as Linux names it (`vfat`, `exfat`, `ntfs`, `ext4`), `mountedAt` where it is open (null while it is
|
|
255
|
+
// not), `model` what the drive says it is. The machine's own disks are never listed.
|
|
256
|
+
export const IntenticOsDriveSchema = z.object({
|
|
257
|
+
device: z.string(),
|
|
258
|
+
label: z.string().nullable(),
|
|
259
|
+
size: z.number().int().nonnegative(),
|
|
260
|
+
filesystem: z.string().nullable(),
|
|
261
|
+
mountedAt: z.string().nullable(),
|
|
262
|
+
readOnly: z.boolean(),
|
|
263
|
+
model: z.string().nullable(),
|
|
264
|
+
kind: IntenticOsDriveKindSchema,
|
|
265
|
+
});
|
|
266
|
+
export type IntenticOsDrive = z.infer<typeof IntenticOsDriveSchema>;
|
|
267
|
+
export const IntenticOsDrivesSchema = z.object({ drives: z.array(IntenticOsDriveSchema) });
|
|
268
|
+
export type IntenticOsDrives = z.infer<typeof IntenticOsDrivesSchema>;
|
|
269
|
+
|
|
270
|
+
// `ic os drives import … --json` prints one of these every few seconds while it copies, then the result last.
|
|
271
|
+
export const IntenticOsDriveImportProgressSchema = z.object({
|
|
272
|
+
progress: z.object({ bytes: z.number().int().nonnegative(), total: z.number().int().nonnegative() }),
|
|
273
|
+
});
|
|
274
|
+
export type IntenticOsDriveImportProgress = z.infer<typeof IntenticOsDriveImportProgressSchema>;
|
|
275
|
+
|
|
276
|
+
// What was copied: from which drive, into which sandbox, the folder inside it (`/work/Imports/<label>-<date>`), and how
|
|
277
|
+
// many bytes in how many files.
|
|
278
|
+
export const IntenticOsDriveImportSchema = z.object({
|
|
279
|
+
device: z.string(),
|
|
280
|
+
sandbox: z.string(),
|
|
281
|
+
path: z.string(),
|
|
282
|
+
bytes: z.number().int().nonnegative(),
|
|
283
|
+
files: z.number().int().nonnegative(),
|
|
284
|
+
});
|
|
285
|
+
export type IntenticOsDriveImport = z.infer<typeof IntenticOsDriveImportSchema>;
|
|
286
|
+
|
|
287
|
+
// The pieces a caller hands ic as positionals and flag values, checked here before they reach its command line: none may
|
|
288
|
+
// begin with a hyphen (it would be a flag), and none is a path out of where it belongs. An SSID is anything a network
|
|
289
|
+
// broadcasts, so it goes after `--` instead.
|
|
290
|
+
const OUTPUT_NAME = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/;
|
|
291
|
+
const XKB_NAME = /^[A-Za-z0-9][A-Za-z0-9_+-]{0,63}$/;
|
|
292
|
+
const BLOCK_DEVICE = /^\/dev\/[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
293
|
+
const SANDBOX_SLUG = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
|
|
294
|
+
|
|
295
|
+
// An SSID is at most 32 bytes, in whatever characters the network broadcasts.
|
|
296
|
+
export const IntenticOsSsidSchema = z
|
|
297
|
+
.string()
|
|
298
|
+
.min(1)
|
|
299
|
+
.refine((ssid) => new TextEncoder().encode(ssid).length <= 32, "a Wi-Fi network's name, at most 32 bytes");
|
|
300
|
+
export const IntenticOsDisplayNameSchema = z.string().regex(OUTPUT_NAME, "a screen's connector name, like HDMI-A-1");
|
|
301
|
+
export const IntenticOsXkbNameSchema = z.string().regex(XKB_NAME, "an xkb layout or variant name, like de or nodeadkeys");
|
|
302
|
+
export const IntenticOsBlockDeviceSchema = z.string().regex(BLOCK_DEVICE, "a drive's device, like /dev/sdb1");
|
|
303
|
+
export const IntenticOsSandboxSlugSchema = z.string().regex(SANDBOX_SLUG, "a sandbox's slug").refine((slug) => !slug.includes(".."), "a sandbox's slug");
|
|
304
|
+
// A folder inside the sandbox's /work: relative, no `..`, no leading slash or hyphen.
|
|
305
|
+
export const IntenticOsImportPathSchema = z
|
|
306
|
+
.string()
|
|
307
|
+
.min(1)
|
|
308
|
+
.max(255)
|
|
309
|
+
.refine(
|
|
310
|
+
(path) => !path.startsWith("/") && !path.startsWith("-") && path.split("/").every((part) => part !== ".." && part !== "." && part !== ""),
|
|
311
|
+
"a folder inside the sandbox's /work, like Photos/2026",
|
|
312
|
+
);
|
|
313
|
+
export const IntenticOsScaleSchema = z.number().min(0.5).max(4);
|
|
314
|
+
|
|
315
|
+
/** A mode the way `ic os display set --mode` takes it: `1920x1080@60`, the rate to three decimals at most. */
|
|
316
|
+
export const displayModeArg = (mode: IntenticOsDisplayMode): string => `${mode.width}x${mode.height}@${Number(mode.refresh.toFixed(3))}`;
|
|
317
|
+
|
|
318
|
+
export const icOsNetworkStatusArgs = (): string[] => ["os", "network", "status", "--json"];
|
|
319
|
+
export const icOsWifiListArgs = (): string[] => ["os", "network", "wifi", "list", "--json"];
|
|
320
|
+
// The password never: it goes on ic's stdin with `--password-stdin`, since a command line is readable by anyone on the
|
|
321
|
+
// machine (`ps`). Without one, ic joins a known network or one that asks for none, and exits IC_OS_WIFI_PASSWORD_EXIT
|
|
322
|
+
// for one that needs a password.
|
|
323
|
+
export const icOsWifiConnectArgs = (ssid: string, withPassword: boolean): string[] => [
|
|
324
|
+
"os",
|
|
325
|
+
"network",
|
|
326
|
+
"wifi",
|
|
327
|
+
"connect",
|
|
328
|
+
...(withPassword ? ["--password-stdin"] : []),
|
|
329
|
+
"--",
|
|
330
|
+
IntenticOsSsidSchema.parse(ssid),
|
|
331
|
+
];
|
|
332
|
+
export const icOsWifiForgetArgs = (ssid: string): string[] => ["os", "network", "wifi", "forget", "--", IntenticOsSsidSchema.parse(ssid)];
|
|
333
|
+
export const icOsWifiRadioArgs = (on: boolean): string[] => ["os", "network", "wifi", on ? "on" : "off"];
|
|
334
|
+
|
|
335
|
+
export const icOsSoundStatusArgs = (): string[] => ["os", "sound", "status", "--json"];
|
|
336
|
+
// At least one of the two: a volume 0-100, and muted or not. Answers with the status after (`--json`).
|
|
337
|
+
export const icOsSoundSetArgs = (change: { readonly volume?: number; readonly muted?: boolean }): string[] => {
|
|
338
|
+
const args = ["os", "sound", "set"];
|
|
339
|
+
if (change.volume !== undefined) {
|
|
340
|
+
args.push("--volume", String(percent.parse(Math.round(change.volume))));
|
|
341
|
+
}
|
|
342
|
+
if (change.muted !== undefined) {
|
|
343
|
+
args.push(change.muted ? "--mute" : "--unmute");
|
|
344
|
+
}
|
|
345
|
+
if (args.length === 3) {
|
|
346
|
+
throw new Error("a sound change names a volume, muted, or both");
|
|
347
|
+
}
|
|
348
|
+
return [...args, "--json"];
|
|
349
|
+
};
|
|
350
|
+
export const icOsSoundOutputsArgs = (): string[] => ["os", "sound", "outputs", "--json"];
|
|
351
|
+
export const icOsSoundOutputArgs = (id: number): string[] => ["os", "sound", "output", String(z.number().int().nonnegative().parse(id))];
|
|
352
|
+
|
|
353
|
+
export const icOsDisplayListArgs = (): string[] => ["os", "display", "list", "--json"];
|
|
354
|
+
export const icOsDisplaySetArgs = (output: string, change: { readonly mode?: IntenticOsDisplayMode; readonly scale?: number }): string[] => {
|
|
355
|
+
const args = ["os", "display", "set", IntenticOsDisplayNameSchema.parse(output)];
|
|
356
|
+
if (change.mode !== undefined) {
|
|
357
|
+
args.push("--mode", displayModeArg(IntenticOsDisplayModeSchema.parse(change.mode)));
|
|
358
|
+
}
|
|
359
|
+
if (change.scale !== undefined) {
|
|
360
|
+
args.push("--scale", String(IntenticOsScaleSchema.parse(change.scale)));
|
|
361
|
+
}
|
|
362
|
+
if (args.length === 4) {
|
|
363
|
+
throw new Error("a display change names a mode, a scale, or both");
|
|
364
|
+
}
|
|
365
|
+
return args;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export const icOsKeyboardStatusArgs = (): string[] => ["os", "keyboard", "status", "--json"];
|
|
369
|
+
export const icOsKeyboardLayoutsArgs = (): string[] => ["os", "keyboard", "layouts", "--json"];
|
|
370
|
+
export const icOsKeyboardSetArgs = (layout: string, variant?: string): string[] => [
|
|
371
|
+
"os",
|
|
372
|
+
"keyboard",
|
|
373
|
+
"set",
|
|
374
|
+
IntenticOsXkbNameSchema.parse(layout),
|
|
375
|
+
...(variant === undefined ? [] : ["--variant", IntenticOsXkbNameSchema.parse(variant)]),
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
export const icOsDrivesListArgs = (): string[] => ["os", "drives", "list", "--json"];
|
|
379
|
+
export const icOsDriveImportArgs = (device: string, sandbox: string, path?: string): string[] => [
|
|
380
|
+
"os",
|
|
381
|
+
"drives",
|
|
382
|
+
"import",
|
|
383
|
+
IntenticOsBlockDeviceSchema.parse(device),
|
|
384
|
+
"--to",
|
|
385
|
+
IntenticOsSandboxSlugSchema.parse(sandbox),
|
|
386
|
+
...(path === undefined ? [] : ["--path", IntenticOsImportPathSchema.parse(path)]),
|
|
387
|
+
"--json",
|
|
388
|
+
];
|
|
389
|
+
export const icOsDriveEjectArgs = (device: string): string[] => ["os", "drives", "eject", IntenticOsBlockDeviceSchema.parse(device)];
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Secrets that live in a keyring on the owner's own computer (`intentic-keyring`, a process of its own beside
|
|
4
|
+
// intentic-machine) rather than in the sandbox. The sandbox holds a POINTER to each one shared with it, never the value,
|
|
5
|
+
// and asks for a value by sending a grant it signed: the machine relays the grant to the keyring and the answer back,
|
|
6
|
+
// and the answer is sealed to whoever the grant names, so the machine carries only ciphertext. What a grant may ask for
|
|
7
|
+
// is the keyring's to decide (its share mode, its program list, a person's tap); the sandbox's own gates run first.
|
|
8
|
+
// The sealing and signing themselves are policy/keyring-seal.ts.
|
|
9
|
+
|
|
10
|
+
// Raw 32-byte keys travel as base64url without padding, the `x` of their JWK: X25519 for sealing, Ed25519 for signing.
|
|
11
|
+
export const KEYRING_KEY_RE = /^[A-Za-z0-9_-]{43}$/u;
|
|
12
|
+
export const KeyringPublicKeySchema = z.string().regex(KEYRING_KEY_RE).describe("A raw 32-byte public key, base64url without padding.");
|
|
13
|
+
|
|
14
|
+
// One secret's name, the same shape a sandbox secret's name and a reference to it take (SECRET_KEY_RE), so a shared
|
|
15
|
+
// entry is spent, typed and listed exactly like a sandbox-held one.
|
|
16
|
+
export const KEYRING_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
|
|
17
|
+
export const KeyringSecretNameSchema = z
|
|
18
|
+
.string()
|
|
19
|
+
.regex(KEYRING_NAME_RE)
|
|
20
|
+
.describe("The secret's name, as a reference to it carries it: a letter or underscore, then letters, digits or `_`.");
|
|
21
|
+
|
|
22
|
+
export const KeyringShareModeSchema = z
|
|
23
|
+
.enum(["each-use", "while-here", "copy"])
|
|
24
|
+
.describe(
|
|
25
|
+
"How the keyring answers this sandbox: `each-use` asks the person at the computer every time (Windows Hello, or the keyring's passphrase); `while-here` answers unasked while the keyring is unlocked, for as long as the unlock lasts; `copy` keeps a copy in the sandbox, so it works with the computer off, which makes it an ordinary sandbox secret.",
|
|
26
|
+
);
|
|
27
|
+
export type KeyringShareMode = z.infer<typeof KeyringShareModeSchema>;
|
|
28
|
+
|
|
29
|
+
// A program a value may be typed into without asking: an executable's file name (`FortiClient.exe`, matched without case)
|
|
30
|
+
// or its whole path. Android apps are package names, kept apart in `packages`.
|
|
31
|
+
export const KEYRING_PROGRAM_MAX = 260;
|
|
32
|
+
export const KeyringProgramSchema = z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(KEYRING_PROGRAM_MAX)
|
|
36
|
+
.refine((value) => !/[\0\r\n]/u.test(value), "one program per entry, no line breaks")
|
|
37
|
+
.describe("A program, by its executable's file name (`FortiClient.exe`, `mstsc.exe`, `ssh`) or its whole path.");
|
|
38
|
+
export const KEYRING_PACKAGE_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z0-9_]+)+$/u;
|
|
39
|
+
export const KeyringPackageSchema = z.string().regex(KEYRING_PACKAGE_RE).max(255).describe("An Android app, by its package name (`com.fortinet.forticlient`).");
|
|
40
|
+
export const KEYRING_PROGRAMS_MAX = 32;
|
|
41
|
+
|
|
42
|
+
export const KeyringSourceSchema = z
|
|
43
|
+
.enum(["keyring", "bitwarden", "1password", "keepassxc"])
|
|
44
|
+
.describe("Where the value itself is read from when it is used: the keyring's own vault, or a password manager the keyring reads it from.");
|
|
45
|
+
export type KeyringSource = z.infer<typeof KeyringSourceSchema>;
|
|
46
|
+
|
|
47
|
+
// What the keyring tells a sandbox about one entry shared with it: everything the sandbox needs to list it, mask it
|
|
48
|
+
// once released and judge a use before asking, never the value.
|
|
49
|
+
export const KeyringPointerSchema = z.object({
|
|
50
|
+
name: KeyringSecretNameSchema,
|
|
51
|
+
mode: KeyringShareModeSchema,
|
|
52
|
+
kind: z.enum(["password", "text"]).describe("A password, typed only into a password field unless `anyField` says otherwise; or text, typed anywhere a program on the list has focus."),
|
|
53
|
+
programs: z
|
|
54
|
+
.array(KeyringProgramSchema)
|
|
55
|
+
.max(KEYRING_PROGRAMS_MAX)
|
|
56
|
+
.describe("Programs on this computer it may be typed into without asking. Empty: typing it into any window asks the person at the computer."),
|
|
57
|
+
packages: z.array(KeyringPackageSchema).max(KEYRING_PROGRAMS_MAX).describe("Android apps on a phone attached to this computer it may be typed into."),
|
|
58
|
+
hosts: z
|
|
59
|
+
.array(z.string().min(1).max(253))
|
|
60
|
+
.max(KEYRING_PROGRAMS_MAX)
|
|
61
|
+
.describe("Sites it may be typed into or sent to, exact or `*.domain`, the same shape as a sandbox secret's host guard."),
|
|
62
|
+
anyField: z.boolean().describe("Whether it may be typed into a field the program does not mark as a password field."),
|
|
63
|
+
source: KeyringSourceSchema,
|
|
64
|
+
updatedAt: z.number().describe("When the entry last changed, in milliseconds."),
|
|
65
|
+
});
|
|
66
|
+
export type KeyringPointer = z.infer<typeof KeyringPointerSchema>;
|
|
67
|
+
|
|
68
|
+
// The keyring's own public keys: `seal` for a value the sandbox sends it to type (a sandbox-held secret, or one another
|
|
69
|
+
// computer released for this one), `inbox` for a value sent to be kept, readable only once the vault is unlocked.
|
|
70
|
+
export const KeyringDeviceKeysSchema = z.object({
|
|
71
|
+
seal: KeyringPublicKeySchema.describe("Seals a value this keyring is asked to type. Its private half sits beside the vault, readable while locked."),
|
|
72
|
+
inbox: KeyringPublicKeySchema.describe("Seals a value sent to be kept. Its private half is inside the vault, so nothing reads an inbox item until a person unlocks it."),
|
|
73
|
+
});
|
|
74
|
+
export type KeyringDeviceKeys = z.infer<typeof KeyringDeviceKeysSchema>;
|
|
75
|
+
|
|
76
|
+
// A sandbox's own keyring keys, served unauthenticated at GET /keyring/keys so a keyring can fetch them over TLS from the
|
|
77
|
+
// sandbox's address itself rather than take them from whoever relays a share: `sign` verifies its grants, `seal` is what
|
|
78
|
+
// a released value is sealed to.
|
|
79
|
+
export const KeyringSandboxKeysSchema = z.object({
|
|
80
|
+
sign: KeyringPublicKeySchema.describe("Verifies the grants this sandbox signs (Ed25519)."),
|
|
81
|
+
seal: KeyringPublicKeySchema.describe("What a value released to this sandbox is sealed to (X25519)."),
|
|
82
|
+
});
|
|
83
|
+
export type KeyringSandboxKeys = z.infer<typeof KeyringSandboxKeysSchema>;
|
|
84
|
+
|
|
85
|
+
export const KeyringStateSchema = z.object({
|
|
86
|
+
version: z.string().describe("The keyring's own release."),
|
|
87
|
+
locked: z.boolean().describe("Whether the vault is locked. Locked, `while-here` entries ask like `each-use` ones."),
|
|
88
|
+
unlockedUntil: z.number().optional().describe("When the current unlock ends, in milliseconds. Absent while locked."),
|
|
89
|
+
unlock: z
|
|
90
|
+
.array(z.enum(["hello", "passphrase"]))
|
|
91
|
+
.describe("How a person unlocks it here: Windows Hello, or the keyring's passphrase."),
|
|
92
|
+
keys: KeyringDeviceKeysSchema.optional().describe("Absent until the keyring is set up: there is no vault, so no inbox to seal a value to."),
|
|
93
|
+
pendingInbox: z.number().int().nonnegative().describe("Values sent to be kept that wait for the next unlock."),
|
|
94
|
+
});
|
|
95
|
+
export type KeyringState = z.infer<typeof KeyringStateSchema>;
|
|
96
|
+
|
|
97
|
+
// The machine's answer to `keyringShares`: the keyring's state and the entries shared with the asking sandbox. Absent
|
|
98
|
+
// `keyring`: none is installed, or it does not answer.
|
|
99
|
+
export const KeyringSharesSchema = z.object({
|
|
100
|
+
keyring: KeyringStateSchema.optional().describe("The keyring on this computer. Absent when none is installed or it does not answer."),
|
|
101
|
+
pointers: z.array(KeyringPointerSchema).describe("The entries shared with the sandbox asking. Names and rules only, never values."),
|
|
102
|
+
reason: z.string().optional().describe("Why there is no keyring to speak of, in words, when there is not."),
|
|
103
|
+
});
|
|
104
|
+
export type KeyringShares = z.infer<typeof KeyringSharesSchema>;
|
|
105
|
+
|
|
106
|
+
// ---- grants ----
|
|
107
|
+
|
|
108
|
+
export const KeyringLaneSchema = z
|
|
109
|
+
.enum(["shell", "code", "browser", "desktop"])
|
|
110
|
+
.describe("Where in the sandbox a released value goes: a command, a script, a page in a sandbox browser, a window on the sandbox's desktop.");
|
|
111
|
+
|
|
112
|
+
// What a grant asks the keyring to do with one value.
|
|
113
|
+
export const KeyringGrantTargetSchema = z.discriminatedUnion("kind", [
|
|
114
|
+
// Hand the value to the sandbox, sealed to its pinned `seal` key, for one use in one lane.
|
|
115
|
+
z.object({ kind: z.literal("sandbox"), lane: KeyringLaneSchema }),
|
|
116
|
+
// Type it into the focused window on this computer. `programs` and `anyField` are the sandbox's own guard for a
|
|
117
|
+
// value it sent; for the keyring's own entry the keyring's list is the one in force and these only narrow it.
|
|
118
|
+
z.object({
|
|
119
|
+
kind: z.literal("window"),
|
|
120
|
+
programs: z.array(KeyringProgramSchema).max(KEYRING_PROGRAMS_MAX),
|
|
121
|
+
anyField: z.boolean(),
|
|
122
|
+
// The owner's own Type into…, from the editor: a moment to put the cursor where the value goes.
|
|
123
|
+
countdownSeconds: z.number().int().min(0).max(10).optional(),
|
|
124
|
+
}),
|
|
125
|
+
// Type it on an Android phone attached to this computer, into one of these apps.
|
|
126
|
+
z.object({ kind: z.literal("android"), serial: z.string().min(1).max(128), packages: z.array(KeyringPackageSchema).max(KEYRING_PROGRAMS_MAX) }),
|
|
127
|
+
// Seal it to another computer's keyring, which the sandbox then asks to type it: the value goes computer to computer
|
|
128
|
+
// and the sandbox carries only ciphertext.
|
|
129
|
+
z.object({ kind: z.literal("relay"), sealTo: KeyringPublicKeySchema, toMachine: z.string().min(1).max(128) }),
|
|
130
|
+
]);
|
|
131
|
+
export type KeyringGrantTarget = z.infer<typeof KeyringGrantTargetSchema>;
|
|
132
|
+
|
|
133
|
+
// A grant lives two minutes at most: long enough for a person to answer a Windows Hello prompt, short enough that one
|
|
134
|
+
// lifted from a log is spent or stale.
|
|
135
|
+
export const KEYRING_GRANT_TTL_MAX_MS = 2 * 60_000;
|
|
136
|
+
export const KEYRING_NONCE_RE = /^[A-Za-z0-9_-]{22}$/u;
|
|
137
|
+
|
|
138
|
+
export const KeyringGrantSchema = z
|
|
139
|
+
.object({
|
|
140
|
+
v: z.literal(1),
|
|
141
|
+
sandbox: z.string().url().describe("The sandbox's own address: which share this grant spends, and whose signing key verifies it."),
|
|
142
|
+
secret: KeyringSecretNameSchema,
|
|
143
|
+
target: KeyringGrantTargetSchema,
|
|
144
|
+
purpose: z.enum(["agent", "owner"]).describe("Whether an agent asked for this use, or the owner did from the editor's Type into…."),
|
|
145
|
+
conversation: z.string().max(200).optional().describe("The conversation the use is for, shown in the keyring's prompt and its record."),
|
|
146
|
+
detail: z.string().min(1).max(300).describe("What the use is, in words, for the person answering the prompt: the command's start, the site, the window."),
|
|
147
|
+
nonce: z.string().regex(KEYRING_NONCE_RE).describe("16 random bytes, base64url: a grant answers once."),
|
|
148
|
+
issuedAt: z.number().int(),
|
|
149
|
+
expiresAt: z.number().int(),
|
|
150
|
+
})
|
|
151
|
+
.strict();
|
|
152
|
+
export type KeyringGrant = z.infer<typeof KeyringGrantSchema>;
|
|
153
|
+
|
|
154
|
+
export const SignedKeyringGrantSchema = z
|
|
155
|
+
.object({
|
|
156
|
+
payload: z.string().min(1).max(8192).describe("The grant's JSON, base64url."),
|
|
157
|
+
sig: z.string().regex(/^[A-Za-z0-9_-]{86}$/u).describe("The sandbox's Ed25519 signature over the grant, base64url."),
|
|
158
|
+
})
|
|
159
|
+
.strict();
|
|
160
|
+
export type SignedKeyringGrant = z.infer<typeof SignedKeyringGrantSchema>;
|
|
161
|
+
|
|
162
|
+
// X25519 to the recipient's key with a fresh key per box, HKDF-SHA256, AES-256-GCM; the additional data binds a box to
|
|
163
|
+
// what it was sealed for (policy/keyring-seal.ts), so one cannot be replayed as another.
|
|
164
|
+
export const SealedBoxSchema = z
|
|
165
|
+
.object({
|
|
166
|
+
v: z.literal(1),
|
|
167
|
+
epk: KeyringPublicKeySchema.describe("The sender's one-time public key."),
|
|
168
|
+
iv: z.string().regex(/^[A-Za-z0-9_-]{16}$/u),
|
|
169
|
+
tag: z.string().regex(/^[A-Za-z0-9_-]{22}$/u),
|
|
170
|
+
data: z.string().max(65536).regex(/^[A-Za-z0-9_-]*$/u),
|
|
171
|
+
})
|
|
172
|
+
.strict();
|
|
173
|
+
export type SealedBox = z.infer<typeof SealedBoxSchema>;
|
|
174
|
+
|
|
175
|
+
export const KeyringRefusalCodeSchema = z
|
|
176
|
+
.enum(["no-keyring", "locked", "declined", "unknown", "not-shared", "bad-grant", "expired", "replayed", "guard", "unavailable", "busy"])
|
|
177
|
+
.describe(
|
|
178
|
+
"Why: no keyring here; locked with nobody to unlock it; the person said no; no such entry; not shared with this sandbox; a grant that does not verify; one past its time; one already spent; the focused window or field is not one it may go to; its password manager did not answer; another prompt is open.",
|
|
179
|
+
);
|
|
180
|
+
export type KeyringRefusalCode = z.infer<typeof KeyringRefusalCodeSchema>;
|
|
181
|
+
|
|
182
|
+
const refusal = z.object({ ok: z.literal(false), code: KeyringRefusalCodeSchema, refusal: z.string().describe("What happened, in words a person can act on.") });
|
|
183
|
+
|
|
184
|
+
export const KeyringReleaseResultSchema = z.discriminatedUnion("ok", [
|
|
185
|
+
z.object({ ok: z.literal(true), sealed: SealedBoxSchema, asked: z.boolean().describe("Whether the person at the computer was asked.") }),
|
|
186
|
+
refusal,
|
|
187
|
+
]);
|
|
188
|
+
export type KeyringReleaseResult = z.infer<typeof KeyringReleaseResultSchema>;
|
|
189
|
+
|
|
190
|
+
export const KeyringTypeInputSchema = z
|
|
191
|
+
.object({
|
|
192
|
+
grant: SignedKeyringGrantSchema,
|
|
193
|
+
// A value the keyring does not hold itself: a sandbox-held secret, or one another computer released for this one,
|
|
194
|
+
// sealed to this keyring's `seal` key.
|
|
195
|
+
sealed: SealedBoxSchema.optional(),
|
|
196
|
+
})
|
|
197
|
+
.strict();
|
|
198
|
+
export type KeyringTypeInput = z.infer<typeof KeyringTypeInputSchema>;
|
|
199
|
+
|
|
200
|
+
export const KeyringTypeResultSchema = z.discriminatedUnion("ok", [
|
|
201
|
+
z.object({
|
|
202
|
+
ok: z.literal(true),
|
|
203
|
+
program: z.string().describe("The program it was typed into: its executable's file name, or the Android app's package."),
|
|
204
|
+
field: z.enum(["password", "text"]).describe("Whether the focused field was a password field."),
|
|
205
|
+
asked: z.boolean().describe("Whether the person at the computer was asked."),
|
|
206
|
+
}),
|
|
207
|
+
refusal,
|
|
208
|
+
]);
|
|
209
|
+
export type KeyringTypeResult = z.infer<typeof KeyringTypeResultSchema>;
|
|
210
|
+
|
|
211
|
+
// A value sent from the sandbox to be kept in this computer's keyring (a `secrets ask` answered with "keep it on my
|
|
212
|
+
// computer"), sealed to the vault's inbox key and taken in at the next unlock, when the person sees its name and says yes.
|
|
213
|
+
export const KeyringInboxItemSchema = z
|
|
214
|
+
.object({
|
|
215
|
+
name: KeyringSecretNameSchema,
|
|
216
|
+
sealed: SealedBoxSchema,
|
|
217
|
+
mode: KeyringShareModeSchema,
|
|
218
|
+
kind: z.enum(["password", "text"]),
|
|
219
|
+
programs: z.array(KeyringProgramSchema).max(KEYRING_PROGRAMS_MAX),
|
|
220
|
+
hosts: z.array(z.string().min(1).max(253)).max(KEYRING_PROGRAMS_MAX),
|
|
221
|
+
sandbox: z.string().url().describe("The sandbox it came from, which it is shared with once taken in."),
|
|
222
|
+
})
|
|
223
|
+
.strict();
|
|
224
|
+
export type KeyringInboxItem = z.infer<typeof KeyringInboxItemSchema>;
|
|
225
|
+
|
|
226
|
+
export const KeyringInboxResultSchema = z.discriminatedUnion("ok", [z.object({ ok: z.literal(true), pending: z.boolean().describe("Whether it waits for the next unlock.") }), refusal]);
|
|
227
|
+
export type KeyringInboxResult = z.infer<typeof KeyringInboxResultSchema>;
|
|
228
|
+
|
|
229
|
+
// `copy` entries, sealed to the asking sandbox's key, for the sandbox to keep as its own; asked for on connect and after
|
|
230
|
+
// a change, so an edit on the computer reaches the copy.
|
|
231
|
+
export const KeyringCopiesSchema = z.object({
|
|
232
|
+
copies: z.array(z.object({ name: KeyringSecretNameSchema, sealed: SealedBoxSchema, updatedAt: z.number() })),
|
|
233
|
+
});
|
|
234
|
+
export type KeyringCopies = z.infer<typeof KeyringCopiesSchema>;
|
|
235
|
+
|
|
236
|
+
// ---- sync between the owner's computers ----
|
|
237
|
+
|
|
238
|
+
// Keyrings on two of the owner's computers keep one set of entries by sealing each change to the other's sync key and
|
|
239
|
+
// leaving it in a sandbox both link to, which carries only ciphertext and never a key that opens it. Two keyrings are
|
|
240
|
+
// paired once, at a person's word on each computer, each showing both fingerprints to compare; a sandbox that swapped a
|
|
241
|
+
// key would show a fingerprint the other screen does not.
|
|
242
|
+
export const KeyringSyncPeerSchema = z.object({
|
|
243
|
+
device: z.string().min(1).max(128).describe("The other computer, by its device card."),
|
|
244
|
+
key: KeyringPublicKeySchema.describe("Its keyring's sync key."),
|
|
245
|
+
pairedAt: z.number().describe("When the two were paired, in milliseconds."),
|
|
246
|
+
});
|
|
247
|
+
export type KeyringSyncPeer = z.infer<typeof KeyringSyncPeerSchema>;
|
|
248
|
+
|
|
249
|
+
export const KeyringSyncStateSchema = z.object({
|
|
250
|
+
key: KeyringPublicKeySchema.describe("This keyring's own sync key."),
|
|
251
|
+
peers: z.array(KeyringSyncPeerSchema).describe("The keyrings it keeps in step with."),
|
|
252
|
+
revision: z.number().int().nonnegative().describe("How many changes this keyring has made, for ordering what it sends."),
|
|
253
|
+
});
|
|
254
|
+
export type KeyringSyncState = z.infer<typeof KeyringSyncStateSchema>;
|
|
255
|
+
|
|
256
|
+
export const KeyringSyncItemSchema = z
|
|
257
|
+
.object({
|
|
258
|
+
from: KeyringPublicKeySchema.describe("The sending keyring's sync key."),
|
|
259
|
+
to: KeyringPublicKeySchema.describe("The receiving keyring's sync key."),
|
|
260
|
+
revision: z.number().int().nonnegative(),
|
|
261
|
+
sealed: SealedBoxSchema.describe("The sender's entries, sealed to the receiver for `sync` with its own revision."),
|
|
262
|
+
})
|
|
263
|
+
.strict();
|
|
264
|
+
export type KeyringSyncItem = z.infer<typeof KeyringSyncItemSchema>;
|
|
265
|
+
|
|
266
|
+
export const KeyringSyncPairSchema = z
|
|
267
|
+
.object({
|
|
268
|
+
peer: z.object({ device: z.string().min(1).max(128), key: KeyringPublicKeySchema }).strict(),
|
|
269
|
+
// The device card this keyring's own computer is known by, so the prompt can name both.
|
|
270
|
+
self: z.string().min(1).max(128),
|
|
271
|
+
})
|
|
272
|
+
.strict();
|
|
273
|
+
|
|
274
|
+
export const KeyringSyncPairResultSchema = z.discriminatedUnion("ok", [z.object({ ok: z.literal(true) }), refusal]);
|
|
275
|
+
export const KeyringSyncOutboxSchema = z.object({ items: z.array(KeyringSyncItemSchema) });
|
|
276
|
+
export const KeyringSyncDeliverSchema = z.object({ items: z.array(KeyringSyncItemSchema).max(64) }).strict();
|
|
277
|
+
export const KeyringSyncDeliverResultSchema = z.object({ accepted: z.number().int().nonnegative().describe("How many it took; the rest were not from a paired keyring or did not open.") });
|