iterate 0.2.7 → 0.4.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.
Files changed (73) hide show
  1. package/README.md +173 -81
  2. package/dist/api.d.ts +643 -0
  3. package/dist/api.mjs +0 -0
  4. package/dist/app-server.d.ts +51 -0
  5. package/dist/app-server.mjs +481 -0
  6. package/dist/app-server.mjs.map +1 -0
  7. package/dist/app-session.d.ts +49 -0
  8. package/dist/app-session.mjs +235 -0
  9. package/dist/app-session.mjs.map +1 -0
  10. package/dist/app.d.ts +29 -0
  11. package/dist/app.mjs +180 -0
  12. package/dist/app.mjs.map +1 -0
  13. package/dist/client/live-state.d.ts +63 -0
  14. package/dist/client/oauth.d.ts +17 -0
  15. package/dist/client/react.d.ts +77 -0
  16. package/dist/client/socket.d.ts +7 -0
  17. package/dist/client.mjs +156 -0
  18. package/dist/client.mjs.map +1 -0
  19. package/dist/expression.d.ts +88 -0
  20. package/dist/expression.mjs +301 -0
  21. package/dist/expression.mjs.map +1 -0
  22. package/dist/lib-BWr-5mFO.mjs +36 -0
  23. package/dist/lib-BWr-5mFO.mjs.map +1 -0
  24. package/dist/lib.d.ts +70 -0
  25. package/dist/lib.mjs +228 -0
  26. package/dist/lib.mjs.map +1 -0
  27. package/dist/node.d.ts +15 -0
  28. package/dist/node.mjs +47 -0
  29. package/dist/node.mjs.map +1 -0
  30. package/dist/oauth-scopes.d.ts +32 -0
  31. package/dist/oauth-scopes.mjs +40 -0
  32. package/dist/oauth-scopes.mjs.map +1 -0
  33. package/dist/oauth.mjs +41 -0
  34. package/dist/oauth.mjs.map +1 -0
  35. package/dist/principal.d.ts +8 -0
  36. package/dist/principal.mjs +8 -0
  37. package/dist/principal.mjs.map +1 -0
  38. package/dist/project-ingress.d.ts +58 -0
  39. package/dist/project-ingress.mjs +104 -0
  40. package/dist/project-ingress.mjs.map +1 -0
  41. package/dist/react.mjs +285 -0
  42. package/dist/react.mjs.map +1 -0
  43. package/dist/sdk/auth.d.ts +25 -0
  44. package/dist/sdk/index.d.ts +155 -0
  45. package/dist/sdk/record-pipelined-steps.d.ts +19 -0
  46. package/dist/sdk.mjs +245 -0
  47. package/dist/sdk.mjs.map +1 -0
  48. package/dist/stream/processor.d.ts +383 -0
  49. package/dist/stream/processor.mjs +605 -0
  50. package/dist/stream/processor.mjs.map +1 -0
  51. package/dist/stream/run.d.ts +61 -0
  52. package/dist/stream/run.mjs +45 -0
  53. package/dist/stream/run.mjs.map +1 -0
  54. package/dist/stream/test-support.d.ts +45 -0
  55. package/dist/stream/test-support.mjs +196 -0
  56. package/dist/stream/test-support.mjs.map +1 -0
  57. package/dist/usingCtx-inzbY1Qz.mjs +57 -0
  58. package/package.json +93 -30
  59. package/bin/iterate.js +0 -86
  60. package/dist/cli-DMS4kJph.mjs +0 -868
  61. package/dist/cli-DMS4kJph.mjs.map +0 -1
  62. package/dist/config-DtnR7Lv7.mjs +0 -170
  63. package/dist/config-DtnR7Lv7.mjs.map +0 -1
  64. package/dist/index.d.mts +0 -5
  65. package/dist/index.d.mts.map +0 -1
  66. package/dist/index.mjs +0 -8
  67. package/dist/index.mjs.map +0 -1
  68. package/dist/stream-tui/agent-chat-terminal.d.mts +0 -1
  69. package/dist/stream-tui/agent-chat-terminal.mjs +0 -933
  70. package/dist/stream-tui/agent-chat-terminal.mjs.map +0 -1
  71. package/dist/worker.d.mts +0 -33
  72. package/dist/worker.mjs +0 -18
  73. package/dist/worker.mjs.map +0 -1
package/dist/api.d.ts ADDED
@@ -0,0 +1,643 @@
1
+ import type { Ai } from "@cloudflare/workers-types";
2
+ import type { InvokeHandle, ItxExpression, ItxExpressionInput } from "./expression.ts";
3
+ import type { ConsentScope } from "./oauth-scopes.ts";
4
+ import type { Principal } from "./principal.ts";
5
+ import type { IngressRouting } from "./project-ingress.ts";
6
+ import type { StreamEvent, StreamEventInput } from "./stream/processor.ts";
7
+ /** What `authenticate` accepts: the browser (its login cookie rode the upgrade), a device or script
8
+ * (its bearer token did — or, on a socket opened bare, presented here as `token`: a static page on
9
+ * another origin cannot put a header on a WebSocket), or the operator (the deployment's admin
10
+ * secret, verified in-band). */
11
+ export type SessionCredentials = {
12
+ type: "from-server-cookie";
13
+ } | {
14
+ type: "bearer";
15
+ token?: string;
16
+ } | {
17
+ type: "admin-secret";
18
+ secret: string;
19
+ as?: {
20
+ email: string;
21
+ };
22
+ };
23
+ /** One page of a context's durable log (`readEvents`). */
24
+ export interface StreamPage {
25
+ events: StreamEvent[];
26
+ scannedThroughOffset: number;
27
+ /** True iff the scan reached the durable mark: nothing more to read until the next commit. */
28
+ atHead: boolean;
29
+ }
30
+ /** `waitForEvent`'s filter: an event type (or one of a list), a floor, a timeout. */
31
+ export type WaitForEventFilter = {
32
+ type?: string | string[];
33
+ afterOffset?: number;
34
+ timeoutMs?: number;
35
+ };
36
+ /** The `rewrite-rule-configured` event's payload — what `itx.append` writes durably and `provide`
37
+ * writes for its session: make `match` mean `target` (an expression, or `null` to deny). `description` is the one
38
+ * line a model reads for the name; it rides the row into `rewriteRules.list()`. */
39
+ export type RewriteRuleConfigured = {
40
+ match: ItxExpressionInput;
41
+ target: ItxExpressionInput | null;
42
+ /** What the name means here, in one line (≤ 500 chars). */
43
+ description?: string;
44
+ };
45
+ /** One row of `rewriteRules.list()` — the tree a context can spell. `context` is the path the row
46
+ * was read from: this context for its own rows and its implicit rows, the target context for the
47
+ * rows a bare hop row (`itx ⇒ itx.builtins.cd(path)`) reaches. A mask lists as `target: null`. */
48
+ export type RewriteRuleListEntry = {
49
+ match: string;
50
+ target: string | null;
51
+ description?: string;
52
+ context: string;
53
+ };
54
+ /** One row of `subscriptions.list()` / `processors.list()`. */
55
+ export type SubscriptionListEntry = {
56
+ name: string;
57
+ target: string;
58
+ consumes?: string[];
59
+ configuredAtOffset: number;
60
+ afterOffset?: number;
61
+ /** Set when this row hosts a facet (a processor). `restarts`: how many times the platform failed
62
+ * the facet at its start and the context restarted it under a fresh loaded identity (a platform
63
+ * defect the context works around; the count is the cheap way to ask "how often, here"). */
64
+ hostedFacet?: {
65
+ name: string;
66
+ className: string;
67
+ cacheKey?: string;
68
+ restarts: number;
69
+ };
70
+ };
71
+ /** A loaded worker's source: its modules, literally, or an itx expression that produces them (then
72
+ * `cacheKey` names the build, and the caller owns "same key ⇒ same code"). */
73
+ export type WorkerSource = Record<string, string> | ItxExpressionInput;
74
+ /** What hosts a class as a durable facet — `facets.get(name, spec)`, `processors.enable(name, spec)`. */
75
+ export type FacetSpec = {
76
+ source: WorkerSource;
77
+ cacheKey?: string;
78
+ className: string;
79
+ };
80
+ /** What `schedules.set` answers: the definition's identity, to cancel exactly it. */
81
+ export type ScheduleReceipt = {
82
+ key: string;
83
+ scheduledAtOffset: number;
84
+ };
85
+ /** A secret's material: one string (`getSecret("/secrets/<name>")` is the whole value) or a JSON
86
+ * object whose string fields `getSecret("/secrets/<name>", { field: "a.b" })` picks — the
87
+ * multidimensional shape a credential exchange needs (`{ username, password, accessToken }`,
88
+ * `{ clientId, clientSecret, refreshToken, accessToken }`). A string is always the one value: it has
89
+ * no fields, whether or not it parses as JSON. */
90
+ export type SecretMaterial = string | Record<string, unknown>;
91
+ /** How a token endpoint wants the client credential — the RFC 8414 `token_endpoint_auth_methods_supported`
92
+ * registry values, so a provider's discovery document pastes straight in: `client_secret_basic`
93
+ * (HTTP Basic — Google, Slack, the petshop; the default), `client_secret_post` (`client_id` +
94
+ * `client_secret` as form fields — GitHub, Linear), `none` (a public client: `client_id` alone,
95
+ * PKCE stands in for the secret). RFC 6749 §2.3.1 forbids sending two forms at once. */
96
+ export type ClientAuth = "client_secret_basic" | "client_secret_post" | "none";
97
+ /** How the secret's facet re-mints an expired credential, in its own trusted code: the
98
+ * exchange reads this secret's own material, POSTs to an endpoint within the pin, and writes the
99
+ * answer back into the material — `accessToken` (and a rotated `refreshToken`). Triggered on a 401
100
+ * from the pinned host, and on first use when the placeholder's field is not there yet. */
101
+ export type SecretRefresh =
102
+ /** RFC 6749 §6, the refresh_token grant: `refreshToken` + `clientId` (+ `clientSecret` for a
103
+ * confidential client) from the material → `accessToken` (+ the newest `refreshToken`). Google,
104
+ * GitHub, an MCP server's authorization server, the petshop fixture. */
105
+ {
106
+ kind: "oauth-refresh-token";
107
+ tokenEndpoint: string;
108
+ clientAuth?: ClientAuth;
109
+ }
110
+ /** The username/password → session-token archetype's one instance so far, Waitrose's login: POST
111
+ * the Android app's `NewSession` GraphQL mutation with `username`/`password` from the material →
112
+ * `accessToken`. Waitrose has no refresh grant — re-login IS the refresh — so one strategy covers
113
+ * the first-use mint and the 401 re-mint. Vendor-specific on purpose: a caller-supplied login
114
+ * template would put an arbitrary request body in trusted code; a second vendor of this shape
115
+ * earns the generalization, not before. */
116
+ | {
117
+ kind: "waitrose-session";
118
+ graphqlUrl: string;
119
+ };
120
+ /** A secret's catalog entry — `secrets.list()` — its path, the pin, the strategy's KIND and when
121
+ * it was first set; never a value (the owner root's fold of the `secret/set` certificates). */
122
+ export type SecretCatalogEntry = {
123
+ path: string;
124
+ urls: string[];
125
+ refresh?: SecretRefresh["kind"];
126
+ createdAt: string;
127
+ };
128
+ /** The input an agent gives `itx.secrets.collectFromUser`: the write-only secret path, the
129
+ * origins its material may reach, and the short explanation the authenticated collection form
130
+ * shows its user. */
131
+ export type CollectSecretInput = {
132
+ path: string;
133
+ egress: {
134
+ urls: string[];
135
+ };
136
+ description?: string;
137
+ };
138
+ /** A secret collection link. Sending this asks the person to authenticate to the intended
139
+ * Iterate instance; it is not itself permission to write a secret. */
140
+ export type CollectSecretLink = {
141
+ path: string;
142
+ url: string;
143
+ };
144
+ /** WHICH requests a fetch route takes — every field given must hold: the host's routing slug
145
+ * (`blog` for `blog--<project>`), a `URLPattern` over the URL the app sees (its init's fields, each a
146
+ * pattern string), exact header values. `{}` takes every request. */
147
+ export type FetchRouteRequestMatcher = {
148
+ routingSlug?: string;
149
+ url?: {
150
+ protocol?: string;
151
+ username?: string;
152
+ password?: string;
153
+ hostname?: string;
154
+ port?: string;
155
+ pathname?: string;
156
+ search?: string;
157
+ hash?: string;
158
+ baseURL?: string;
159
+ };
160
+ headers?: Record<string, string>;
161
+ };
162
+ /** A route as `itx.fetchRoutes.set(name, route)` takes it: the requests it takes, the itx
163
+ * expression they go to, who may use it (`project-members`: the config worker answers anyone else
164
+ * the sign-in challenge; absent or null: public) and its priority (higher first, then by name). */
165
+ export type FetchRouteInput = {
166
+ requestMatcher: FetchRouteRequestMatcher;
167
+ target: ItxExpressionInput;
168
+ authRequirement?: {
169
+ visitors: "project-members";
170
+ } | null;
171
+ priority?: number;
172
+ };
173
+ /** A live route as `list()` and `match` answer it, its target parsed, with the offset of the
174
+ * `itx/fetch-route-configured` fact that set it. */
175
+ export type FetchRouteEntry = {
176
+ fetchRouteName: string;
177
+ requestMatcher: FetchRouteRequestMatcher;
178
+ target: ItxExpression;
179
+ authRequirement: {
180
+ visitors: "project-members";
181
+ } | null;
182
+ priority: number;
183
+ configuredOffset: number;
184
+ };
185
+ /** A context (a project, a user, an organization): every `itx` root, reached through `invoke`. */
186
+ export interface IterateContextApi {
187
+ invoke(call: ItxExpressionInput, ...args: unknown[]): Promise<unknown>;
188
+ /** Another context of this project, by path (`..` and `/` allowed; the global namespace is not). */
189
+ cd(path: string): IterateContextApi;
190
+ /** Durable batches appended after a deadline (`afterMs`), at an instant (`at`) or on an interval
191
+ * (`everyMs`); a key set again is replaced; a receipt cancels exactly the definition it names. */
192
+ schedules: {
193
+ set(input: {
194
+ key: string | [string, string];
195
+ when: {
196
+ at: string;
197
+ } | {
198
+ afterMs: number;
199
+ } | {
200
+ everyMs: number;
201
+ };
202
+ events: StreamEventInput[];
203
+ }, options?: {
204
+ idempotencyKey?: string;
205
+ }): Promise<ScheduleReceipt>;
206
+ cancel(schedule: string | [string, string] | ScheduleReceipt): Promise<StreamEvent[]>;
207
+ };
208
+ whoami(): {
209
+ projectId: string;
210
+ path: string;
211
+ projectSlug?: string;
212
+ projectUrl?: string;
213
+ } | Promise<{
214
+ projectId: string;
215
+ path: string;
216
+ projectSlug?: string;
217
+ projectUrl?: string;
218
+ }>;
219
+ append(...events: StreamEventInput[]): Promise<StreamEvent[]>;
220
+ /** This project's public URL over HTTP: the apex, or a routing slug's host (`blog--<project>`),
221
+ * at `path`. Only from a session, which carries the origin to compose it with. */
222
+ url(target?: {
223
+ routingSlug?: string;
224
+ path?: string;
225
+ }): Promise<string>;
226
+ /** RESET this context (Cloudflare's `ctx.abort`): its Durable Object drops everything it holds in
227
+ * memory and the next call starts a fresh incarnation from durable storage. Resolves with the
228
+ * `events.iterate.com/itx/aborted { reason?, callerPath?, app? }` event it recorded — durable
229
+ * and attributed to the caller before anything resets — and the reset follows the answer.
230
+ * SURVIVES: the log and everything derived from it (rewrite rules, subscriptions, schedules),
231
+ * every facet's storage, kv. GOES: in-memory state, every facet instance and its in-flight work,
232
+ * every socket on the context (a provider's re-dials), and every other call in flight there — it
233
+ * rejects with the reset's message (`itx.abort() reset the context <path>: <reason>`). A handle
234
+ * you kept names its target by expression, so its next call reaches the new incarnation.
235
+ * Another context of the project: `itx.cd(path).abort()`. A rewrite rule masks it like any name
236
+ * (`provide("itx.abort", null)`). */
237
+ abort(reason?: string): Promise<StreamEvent>;
238
+ readEvents(afterOffset?: number, limit?: number, options?: {
239
+ includeEphemeral?: boolean;
240
+ }): Promise<StreamPage>;
241
+ waitForEvent(filter?: WaitForEventFilter): Promise<StreamEvent>;
242
+ fetch(request: Request): Promise<Response>;
243
+ kv: {
244
+ get(key: string): Promise<string | null>;
245
+ put(key: string, value: string): Promise<{
246
+ ok: true;
247
+ }>;
248
+ delete(key: string): Promise<{
249
+ ok: true;
250
+ }>;
251
+ list(prefix?: string): Promise<{
252
+ keys: string[];
253
+ }>;
254
+ };
255
+ /** The project's secrets, WRITE-ONLY: a secret IS its path (`/secrets/<name>`, the name
256
+ * `[a-zA-Z0-9._-]+`), and the path is what an outbound request's placeholder spells —
257
+ * `getSecret("/secrets/<name>")` in a URL or a header substitutes to the value at egress, and only
258
+ * towards the ORIGINS in `urls` (required: a secret is always pinned). `set` stores a string or a
259
+ * JSON object (`refresh` names the strategy that re-mints an expiring credential); `delete`
260
+ * forgets it (re-settable); `list` is the catalog — paths, pins, strategy kinds, when first set —
261
+ * never a value. Every change is one fact on the secret's path (`secret/set`, `secret/deleted`),
262
+ * attributed to the caller and cross-posted to the root, so the log says who set what and when. */
263
+ secrets: {
264
+ set(path: string, material: SecretMaterial, options: {
265
+ urls: string[];
266
+ refresh?: SecretRefresh;
267
+ }): Promise<{
268
+ path: string;
269
+ }>;
270
+ delete(path: string): Promise<{
271
+ path: string;
272
+ }>;
273
+ list(): Promise<SecretCatalogEntry[]>;
274
+ /** Build the authenticated Dash link where a person enters a value an agent must never see in
275
+ * chat. The link fixes the project, platform instance, secret path and egress pin. If called
276
+ * from an agent context, a successful submission messages that same agent with the path only. */
277
+ collectFromUser(input: CollectSecretInput): Promise<CollectSecretLink>;
278
+ };
279
+ /** The project's fetch routes, on its root `/`: which requests on its hosts go to which itx
280
+ * expression. `set` appends one `itx/fetch-route-configured` fact (`null` deletes the route);
281
+ * `match` answers the route a request takes, which the config worker forwards with
282
+ * `env.ITX.fetch` naming `itx.fetchRoutes.fetch('<name>')` (a WebSocket upgrade included). */
283
+ fetchRoutes: {
284
+ set(fetchRouteName: string, route: FetchRouteInput | null): Promise<{
285
+ fetchRouteName: string;
286
+ }>;
287
+ list(): Promise<FetchRouteEntry[]>;
288
+ match(request: {
289
+ method: string;
290
+ url: string;
291
+ headers: Headers | Record<string, string> | [string, string][];
292
+ }): Promise<FetchRouteEntry | null>;
293
+ };
294
+ /** The table this context resolves against, described — the tree a model reads. `list()` follows a
295
+ * bare hop row into the context it names (a Durable Object hop, hence async). */
296
+ rewriteRules: {
297
+ list(): Promise<RewriteRuleListEntry[]>;
298
+ get(match: string): Promise<RewriteRuleListEntry | null>;
299
+ resolve(call: ItxExpressionInput): string[];
300
+ };
301
+ /** A facet of this context: a caller reaches only what its class lists in `static publicMethods`
302
+ * (sdk/index.ts `FacetDurableObject`); anything else is refused FORBIDDEN. */
303
+ facets: {
304
+ get(name: string, spec?: FacetSpec): InvokeHandle;
305
+ /** RESET one facet of this context, from the context that hosts it — any facet, whether or not
306
+ * it extends the SDK's host, including one that would never answer a call. Its instance and
307
+ * in-memory state go, and every call in flight on it rejects `FACET_ABORTED`; its storage
308
+ * stays, and its next call starts it fresh. The context itself is not reset. Resolves with the
309
+ * `events.iterate.com/itx/facet-aborted { name, reason?, callerPath?, app? }` event;
310
+ * `NO_FACET` for a name never hosted here. */
311
+ abort(name: string, reason?: string): Promise<StreamEvent>;
312
+ };
313
+ subscriptions: {
314
+ list(): SubscriptionListEntry[];
315
+ get(name: string): SubscriptionListEntry | null;
316
+ };
317
+ /** The rpc stubs lent to this context right now, by key (a live session's `provide`). */
318
+ rpcStubs: {
319
+ list(): string[];
320
+ };
321
+ processors: {
322
+ enable(name: string, spec?: (FacetSpec & {
323
+ consumes?: string[];
324
+ }) | {
325
+ consumes?: string[];
326
+ }): Promise<{
327
+ name: string;
328
+ }>;
329
+ disable(name: string): Promise<void>;
330
+ list(): SubscriptionListEntry[];
331
+ /** A hosted processor's claim on this context's alarm: "revive me by `at`" (a facet with a
332
+ * `runInBackground` attempt in flight), or `null` to release it. */
333
+ claim(name: string, at: number | null): Promise<void>;
334
+ };
335
+ workers: {
336
+ get(spec: {
337
+ source: WorkerSource;
338
+ cacheKey?: string;
339
+ className?: string;
340
+ props?: unknown;
341
+ }): InvokeHandle;
342
+ };
343
+ /** A subscription: a pure itx expression, or a live callback lent to the registry (what live state
344
+ * uses); `null` removes the row. The handle's dispose removes it too. */
345
+ subscribe(input: {
346
+ name?: string;
347
+ target: ItxExpressionInput | ((events: unknown[], range: unknown) => void) | null;
348
+ consumes?: string[];
349
+ afterOffset?: number;
350
+ }): Promise<{
351
+ [Symbol.dispose](): void;
352
+ }>;
353
+ /** A rewrite rule of this context, session-scoped (the handle's dispose removes it): make `match`
354
+ * mean `target`, an expression, a live stub, or null to deny. `description` is the one line a
355
+ * model reads for the name. The durable spelling is the rule's event (`RewriteRuleConfigured`)
356
+ * through `itx.append`. */
357
+ provide(match: ItxExpressionInput, target: unknown, options?: {
358
+ description?: string;
359
+ }): Promise<{
360
+ [Symbol.dispose](): void;
361
+ }>;
362
+ /** A script — the text of `async (itx) => { … }` — run once against this context, on its log:
363
+ * `itx/run-requested` under the caller, the context's runner, `run-settled` (JSON in, JSON
364
+ * out); resolves with the result or rejects with the settlement's error. Never re-run. A script
365
+ * still running ten minutes after it started is settled failed (`failureKind: "deadline"`). */
366
+ run(script: string): Promise<unknown>;
367
+ /** The project's repos, workspaces and agents as domain objects — one shape each: `get(path)` is
368
+ * the entity's facet on the context at `path` (its verbs, plus the typed `append` on that
369
+ * context), `list()` the project catalog, `create(path)` the creation saga on that path (the
370
+ * parent link the caller's context writes first, then the processor row, the request, the
371
+ * terminal fact — created, or create-failed thrown), `delete(path)` the deletion saga (the
372
+ * request, `deleted` cross-posted to `/`, the row disabled). A relative `path` means the caller's. */
373
+ repos: {
374
+ get(path: string): InvokeHandle;
375
+ list(): Promise<{
376
+ path: string;
377
+ createdAt: string;
378
+ }[]>;
379
+ create(path: string): Promise<{
380
+ path: string;
381
+ }>;
382
+ delete(path: string): Promise<{
383
+ path: string;
384
+ }>;
385
+ };
386
+ workspaces: {
387
+ get(path: string): InvokeHandle;
388
+ list(): Promise<{
389
+ path: string;
390
+ createdAt: string;
391
+ }[]>;
392
+ create(path: string): Promise<{
393
+ path: string;
394
+ }>;
395
+ delete(path: string): Promise<{
396
+ path: string;
397
+ }>;
398
+ };
399
+ /** Workers AI under this context's capability rules. */
400
+ ai: Ai;
401
+ /** Files stored in the project's object store. */
402
+ files: {
403
+ get(path: string): InvokeHandle & {
404
+ put(input: {
405
+ contentType?: string;
406
+ data: Uint8Array | ArrayBuffer | string;
407
+ }): Promise<{
408
+ path: string;
409
+ contentType: string;
410
+ size: number;
411
+ }>;
412
+ bytes(): Promise<Uint8Array>;
413
+ head(): Promise<{
414
+ path: string;
415
+ contentType: string;
416
+ size: number;
417
+ } | null>;
418
+ delete(): Promise<void>;
419
+ url(input?: {
420
+ method?: "GET" | "PUT";
421
+ expiresInSeconds?: number;
422
+ }): Promise<{
423
+ url: string;
424
+ expiresAt: string;
425
+ }>;
426
+ };
427
+ list(prefix?: string): Promise<{
428
+ path: string;
429
+ contentType: string;
430
+ size: number;
431
+ }[]>;
432
+ };
433
+ }
434
+ /** What a grant is: a sign-in not yet exchanged, a device's key, a personal access token, or a
435
+ * browser or app session. A client labels it for display. */
436
+ export type GrantKind = "pending" | "device" | "personal" | "session";
437
+ /** One grant as `grants.list()` shows it: a session or a connected app (an OAuth grant), or a
438
+ * personal access token or a device's key (the account's own API key, `pat_…`). */
439
+ export interface GrantRecord {
440
+ id: string;
441
+ clientId?: string;
442
+ logoUri?: string;
443
+ clientDomain?: string;
444
+ name: string;
445
+ kind: GrantKind;
446
+ /** An OAuth grant's one resource: Cap'n Web at `/api` (and the projects' hosts), or `/mcp`. A
447
+ * personal access token has none: it works at `/api`, at `/mcp` and on its projects' hosts. */
448
+ resource?: "api" | "mcp";
449
+ /** A personal access token's projects, by id: all it reaches. */
450
+ projects?: string[];
451
+ createdAt: number;
452
+ expiresAt: number | null;
453
+ lastUsedAt: number | null;
454
+ expired: boolean;
455
+ /** The grant this very session rides on. */
456
+ current?: boolean;
457
+ /** A personal access token's: the grant of the session that minted it (listed here while it
458
+ * lives). */
459
+ mintedBy?: string;
460
+ }
461
+ /** What the consent screen shows for an authorization request. */
462
+ export type ConsentAnswer = {
463
+ kind: "consent";
464
+ query: string;
465
+ clientName: string;
466
+ email: string;
467
+ projects: ProjectRecord[];
468
+ orgs: OrgRecord[];
469
+ projectBound: boolean;
470
+ /** the scopes the request asked for, each with the page's copy (oauth-scopes.ts) */
471
+ scopes: ConsentScope[];
472
+ denyLocation: string;
473
+ /** how projects are reached over HTTP (project-ingress.ts) — the page composes a project's URL */
474
+ ingressRouting: IngressRouting;
475
+ /** the onboarding step's first draft of an organization name, from the person's name or email */
476
+ suggestedOrganizationName: string;
477
+ } | {
478
+ kind: "redirect";
479
+ location: string;
480
+ } | {
481
+ kind: "invalid";
482
+ description: string;
483
+ };
484
+ /** An organization's invitation link as its owners see it (`expiresAt` ISO). */
485
+ export interface InvitationRecord {
486
+ id: string;
487
+ orgId: string;
488
+ role: "owner" | "member";
489
+ emailHint: string | null;
490
+ expiresAt: string;
491
+ }
492
+ /** An organization as the session lists it: its minted id, its free-text name, the person's role
493
+ * in it, and how many projects it holds (every one of them, not only those this grant lists). */
494
+ export interface OrgRecord {
495
+ id: string;
496
+ name: string;
497
+ role?: "owner" | "member";
498
+ projects: number;
499
+ }
500
+ /** A project as the catalog lists it: addressed by `id` everywhere (`projects.get`, a grant's list,
501
+ * an MCP call's `project`, an app's URL); `slug` is the label of its hostnames and its name to a
502
+ * person. The id is the one stable identifier. */
503
+ export interface ProjectRecord {
504
+ id: string;
505
+ slug: string;
506
+ orgId: string;
507
+ }
508
+ /** The session `authenticate` vends: who is calling, and the contexts they reach. */
509
+ export interface IterateSessionApi {
510
+ whoami(): Principal;
511
+ /** Safe bootstrap data for every app, regardless of which host serves it. */
512
+ info(): {
513
+ principal: Principal;
514
+ scopes: string[];
515
+ platformOrigin: string;
516
+ /** how projects are reached over HTTP (project-ingress.ts `projectUrlOf`); null ⇒ no ingress */
517
+ ingressRouting: IngressRouting;
518
+ /** the MCP server's origin (the dash's connect page) — "" when this deployment serves none */
519
+ mcpOrigin: string;
520
+ };
521
+ /** The grants this session may manage (a signed-in person's with the `account` scope): list and
522
+ * end its sessions and personal access tokens, and mint a personal access token — its bearer
523
+ * answered once, `expiresAt` null for a key that never expires. */
524
+ grants: {
525
+ list(cursor?: string): Promise<{
526
+ items: GrantRecord[];
527
+ cursor?: string;
528
+ projects: ProjectRecord[];
529
+ canMintToken: boolean;
530
+ }>;
531
+ end(grantId: string): Promise<unknown>;
532
+ endCurrent(): Promise<unknown>;
533
+ mint(input: {
534
+ name: string;
535
+ /** project ids, each one the person reaches */
536
+ projects: string[];
537
+ /** epoch ms; omitted, the key never expires */
538
+ expiresAt?: number;
539
+ /** a device's public client metadata document (Kit): the key is listed as that device */
540
+ clientId?: string;
541
+ }): Promise<{
542
+ id: string;
543
+ token: string;
544
+ expiresAt: number | null;
545
+ }>;
546
+ };
547
+ /** The consent screen's methods (the OAuth authorize flow): describe a request, approve it. */
548
+ consent: {
549
+ describe(query: string): Promise<ConsentAnswer>;
550
+ approve(input: {
551
+ query: string;
552
+ projects: string[];
553
+ }): Promise<{
554
+ redirectTo: string;
555
+ } | {
556
+ error: string;
557
+ }>;
558
+ };
559
+ projects: {
560
+ list(): Promise<ProjectRecord[]>;
561
+ /** the project's root context, by its slug or its id */
562
+ get(project: string): Promise<IterateContextApi>;
563
+ /** Config repository presets available on this platform, besides the default config a
564
+ * creation that names no template gets. */
565
+ templates(): Promise<{
566
+ label: string;
567
+ reference: string;
568
+ }[]>;
569
+ /** a new project: `project` is slugged into its hostname label, its id is minted — the returned
570
+ * context's `whoami()` says it, so does `list()` */
571
+ create(input: {
572
+ project: string;
573
+ orgId?: string;
574
+ configRepoTemplate?: string;
575
+ /** Operator-only recovery: retain the source project identity from a project seed. */
576
+ restoreProjectId?: string;
577
+ }): Promise<IterateContextApi>;
578
+ };
579
+ /** The organizations this session reaches — the person's memberships (a grant narrowed to
580
+ * projects sees only their organizations, unless it holds `organizations:write`): the rows, the
581
+ * organization's context by membership, and the verbs (`organizations:write`; the person is the
582
+ * owner of what they create, and only an owner renames, deletes or changes members). Each verb is
583
+ * a request the control plane answers; a refusal is a coded error (FORBIDDEN, INVALID_INPUT). */
584
+ organizations: {
585
+ list(): Promise<OrgRecord[]>;
586
+ /** the organization's context — `session.user` for an organization — by membership */
587
+ get(orgId: string): Promise<IterateContextApi>;
588
+ create(input: {
589
+ name: string;
590
+ }): Promise<OrgRecord>;
591
+ rename(orgId: string, input: {
592
+ name: string;
593
+ }): Promise<OrgRecord>;
594
+ /** only while it holds no project */
595
+ delete(orgId: string): Promise<void>;
596
+ addMember(orgId: string, input: {
597
+ userId: string;
598
+ role?: "owner" | "member";
599
+ }): Promise<void>;
600
+ removeMember(orgId: string, input: {
601
+ userId: string;
602
+ }): Promise<void>;
603
+ /** the members with their emails — the operator's alone (the project-seed CLI) */
604
+ members(orgId: string): Promise<{
605
+ userId: string;
606
+ email: string;
607
+ role: "owner" | "member";
608
+ }[]>;
609
+ /** an owner's invitation link: whoever accepts it first joins in `role` (default member),
610
+ * until it expires (`expiresInDays`, default 7, 1–30). `token` is the link's secret, answered
611
+ * this once — the dash's `/invitations/<token>`. */
612
+ createInvitation(orgId: string, input?: {
613
+ role?: "owner" | "member";
614
+ emailHint?: string;
615
+ expiresInDays?: number;
616
+ }): Promise<InvitationRecord & {
617
+ token: string;
618
+ }>;
619
+ /** an owner withdraws an open link by its id */
620
+ revokeInvitation(orgId: string, input: {
621
+ invitationId: string;
622
+ }): Promise<void>;
623
+ /** what a link opens, for the signed-in person holding it; null when it names nothing */
624
+ invitation(token: string): Promise<(InvitationRecord & {
625
+ orgName: string;
626
+ status: "pending" | "accepted" | "revoked" | "expired";
627
+ /** the reader already belongs */
628
+ member: boolean;
629
+ /** the reader is the one who accepted it — accepting again answers the same and lands
630
+ * the membership's facts again */
631
+ acceptedByYou: boolean;
632
+ }) | null>;
633
+ /** join the organization a link opens, in its role — single use: the first person to accept
634
+ * consumes it (again by them answers the same; anyone after is refused INVALID_INPUT) */
635
+ acceptInvitation(token: string): Promise<OrgRecord>;
636
+ };
637
+ user: IterateContextApi;
638
+ logout(): unknown;
639
+ }
640
+ /** THE `/api` ROOT — the one thing a fresh capnweb connection holds. */
641
+ export interface IterateApi {
642
+ authenticate(credentials: SessionCredentials): Promise<IterateSessionApi>;
643
+ }
package/dist/api.mjs ADDED
File without changes