@neurosquad/card-sdk 1.0.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 (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +431 -0
  3. package/dist/card-sdk.js +5266 -0
  4. package/dist/cli.js +2838 -0
  5. package/dist/react.js +257 -0
  6. package/dist/testing.js +1150 -0
  7. package/dist/types/client/base64.d.ts +7 -0
  8. package/dist/types/client/card.d.ts +653 -0
  9. package/dist/types/client/channel.d.ts +49 -0
  10. package/dist/types/client/coalesce.d.ts +14 -0
  11. package/dist/types/client/connect.d.ts +39 -0
  12. package/dist/types/client/errors.d.ts +46 -0
  13. package/dist/types/client/helpers.d.ts +25 -0
  14. package/dist/types/client/net.d.ts +111 -0
  15. package/dist/types/client/theme.d.ts +31 -0
  16. package/dist/types/client/toolResult.d.ts +16 -0
  17. package/dist/types/contract/api.d.ts +812 -0
  18. package/dist/types/contract/index.d.ts +11 -0
  19. package/dist/types/contract/jsonSchema.d.ts +88 -0
  20. package/dist/types/contract/localized.d.ts +10 -0
  21. package/dist/types/contract/manifest.d.ts +179 -0
  22. package/dist/types/contract/network.d.ts +48 -0
  23. package/dist/types/contract/permissions.d.ts +180 -0
  24. package/dist/types/contract/ports.d.ts +237 -0
  25. package/dist/types/contract/protocol.d.ts +74 -0
  26. package/dist/types/contract/source.d.ts +111 -0
  27. package/dist/types/contract/theme.d.ts +21 -0
  28. package/dist/types/contract/version.d.ts +126 -0
  29. package/dist/types/i18n.d.ts +50 -0
  30. package/dist/types/index.d.ts +28 -0
  31. package/dist/types/react/index.d.ts +132 -0
  32. package/dist/types/testing/index.d.ts +7 -0
  33. package/dist/types/testing/mockHost.d.ts +315 -0
  34. package/dist/types/version.d.ts +2 -0
  35. package/dist/ui.css +581 -0
  36. package/package.json +77 -0
  37. package/schema/neurosquad-card.v1.json +434 -0
  38. package/templates/react/README.md +34 -0
  39. package/templates/react/_gitignore +10 -0
  40. package/templates/react/icon.png +0 -0
  41. package/templates/react/index.html +12 -0
  42. package/templates/react/neurosquad-card.json +94 -0
  43. package/templates/react/package.json +25 -0
  44. package/templates/react/src/App.tsx +131 -0
  45. package/templates/react/src/i18n.ts +61 -0
  46. package/templates/react/src/main.tsx +77 -0
  47. package/templates/react/src/styles.css +42 -0
  48. package/templates/react/tsconfig.json +18 -0
  49. package/templates/react/vite.config.ts +19 -0
  50. package/templates/vanilla/README.md +37 -0
  51. package/templates/vanilla/_gitignore +6 -0
  52. package/templates/vanilla/icon.png +0 -0
  53. package/templates/vanilla/index.html +41 -0
  54. package/templates/vanilla/main.js +256 -0
  55. package/templates/vanilla/neurosquad-card.json +94 -0
  56. package/templates/vanilla/style.css +41 -0
@@ -0,0 +1,7 @@
1
+ /** Encodes bytes as standard base64. */
2
+ export declare function bytesToBase64(bytes: Uint8Array): string;
3
+ /** Decodes standard base64 into bytes. */
4
+ export declare function base64ToBytes(base64: string): Uint8Array;
5
+ /** Bytes from any binary body the SDK accepts. */
6
+ export declare function toBytes(data: ArrayBuffer | ArrayBufferView): Uint8Array;
7
+ export declare function isBinary(value: unknown): value is ArrayBuffer | ArrayBufferView;
@@ -0,0 +1,653 @@
1
+ import { type AgentInfo, type CardEventName, type CardEvents, type CardInstanceInfo, type CardMethodName, type CardSize, type CardTone, type CardVisibility, type EventTopic, type FsEntry, type FsStat, type HostContext, type HostIconName, type I18nSnapshot, type JsonValue, type MenuItem, type MethodParams, type MethodResult, type OverviewContent, type PeerInfo, type PermissionId, type PermissionState, type PortInfo, type SettingsSnapshot, type SettingValue, type ThemeSnapshot, type ToolResultPayload, type UsageSummary, type WorkspaceInfo } from '../contract/index.js';
2
+ import type { CallOptions, Channel } from './channel.js';
3
+ import { CardResponse, ChunkQueue, type CardFetchInit } from './net.js';
4
+ import { type ThemeTarget } from './theme.js';
5
+ /** Arguments of `card.call(method, …)`: params are optional for parameterless methods. */
6
+ export type CallArgs<M extends CardMethodName> = [MethodParams<M>] extends [void] ? [params?: Record<string, never> | null, options?: CallOptions] : [params: MethodParams<M>, options?: CallOptions];
7
+ /** Handler of a host event. */
8
+ export type EventHandler<E extends CardEventName> = (data: CardEvents[E]) => void;
9
+ /** Removes a listener / handler / watcher. Calling it twice is harmless. */
10
+ export type Unsubscribe = () => void;
11
+ /** Widens literal types (`''` → `string`, `0` → `number`) so `get('k', '')` is a `string`. */
12
+ export type Widen<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : T;
13
+ /** Scope of a storage call: this card on the canvas, or every card of the package. */
14
+ export type StorageScope = 'instance' | 'package';
15
+ /** What a tool handler may return; see {@link CardTools.handle}. */
16
+ export type ToolReturn = string | ToolResultPayload | JsonValue | undefined | void;
17
+ /** Context passed to a tool handler. */
18
+ export interface ToolCall {
19
+ callId: string;
20
+ /** The tool's name as declared in the manifest. */
21
+ tool: string;
22
+ /** The agent calling it. */
23
+ agent: {
24
+ id: string;
25
+ name: string;
26
+ };
27
+ /** Epoch ms after which the host has given up. */
28
+ deadline: number;
29
+ /** Aborted when the host cancels the call (`tools.cancel`) or the deadline passes. */
30
+ signal: AbortSignal;
31
+ /** Shows a short progress line on the arrow (throttled by the host to 4/s). */
32
+ progress(message: string): void;
33
+ }
34
+ /** Handler registered with {@link CardTools.handle}. */
35
+ export type ToolHandler<A = JsonValue> = (args: A, call: ToolCall) => ToolReturn | Promise<ToolReturn>;
36
+ /** Context passed to a port request handler. */
37
+ export interface PortRequest {
38
+ requestId: string;
39
+ /** Card id of the asker. */
40
+ from: string;
41
+ /** Our input port the request came in on. */
42
+ input: string;
43
+ deadline: number;
44
+ /** Aborted when the deadline passes. */
45
+ signal: AbortSignal;
46
+ }
47
+ /** Handler registered with {@link CardPorts.onRequest}. Return the reply, or throw to refuse. */
48
+ export type PortRequestHandler<T = JsonValue> = (data: T, request: PortRequest) => JsonValue | Promise<JsonValue>;
49
+ /** A menu item for {@link CardUi.setMenu}; `onSelect` stays in the card, it is not sent to the host. */
50
+ export interface CardMenuItem extends MenuItem {
51
+ onSelect?: () => void;
52
+ }
53
+ /** Options of {@link Card}. Set through `connect()`. */
54
+ export interface CardOptions {
55
+ /** Apply the app theme as `--ns-*` variables (and follow changes). `false` to skip; an element to target it. Default: `<html>`. */
56
+ theme?: boolean | ThemeTarget;
57
+ /** Keep `<html lang>` equal to the app language. Default true. */
58
+ syncLang?: boolean;
59
+ /** Forward uncaught errors and unhandled rejections to the host log. Default true. */
60
+ forwardErrors?: boolean;
61
+ }
62
+ /** Per-card key/value storage (JSON values, persisted by the host; see spec §8.4). */
63
+ export interface ScopedStorage {
64
+ /** The stored value, or `fallback` (default `undefined`) when the key does not exist. */
65
+ get<T extends JsonValue = JsonValue>(key: string): Promise<T | undefined>;
66
+ get<T extends JsonValue = JsonValue>(key: string, fallback: T): Promise<Widen<T>>;
67
+ /** Stores a JSON value (≤ 1 MB). Writes are atomic in the host. */
68
+ set(key: string, value: JsonValue): Promise<void>;
69
+ delete(key: string): Promise<void>;
70
+ /** Keys, optionally only those starting with `prefix`. */
71
+ keys(prefix?: string): Promise<string[]>;
72
+ clear(): Promise<void>;
73
+ /** Bytes used, the quota and the key count. */
74
+ usage(): Promise<{
75
+ bytes: number;
76
+ quota: number;
77
+ keys: number;
78
+ }>;
79
+ }
80
+ /** `card.storage`: instance-scope storage, with `card.storage.package` for data every card of the package shares. */
81
+ export interface CardStorage extends ScopedStorage {
82
+ /** Storage shared by every card of this package (20 MB). */
83
+ readonly package: ScopedStorage;
84
+ /** Another instance of this package wrote a package-scope key. */
85
+ onChange(handler: (change: CardEvents['storage.changed']) => void): Unsubscribe;
86
+ }
87
+ /**
88
+ * A connected card. Get one with `await connect()`.
89
+ *
90
+ * Everything the host offers is here, typed from the contract:
91
+ * `card.call(method, params)` for any method, `card.on(event, handler)` for
92
+ * any event, and namespaced helpers (`card.storage`, `card.agents`,
93
+ * `card.ports`, `card.tools`, `card.net`, `card.fs`, `card.ui`, …) that
94
+ * read nicer and add conveniences (auto-subscription, streaming,
95
+ * base64, tool/request dispatch).
96
+ *
97
+ * @example
98
+ * import { connect } from '@neurosquad/card-sdk'
99
+ *
100
+ * const card = await connect()
101
+ * card.setStatus('Ready', { tone: 'success' })
102
+ * const agents = await card.agents.list()
103
+ * card.tools.handle('count_agents', () => `${agents.length} agents`)
104
+ */
105
+ export declare class Card {
106
+ private readonly channel;
107
+ private readonly options;
108
+ private ctx;
109
+ private readonly listeners;
110
+ private readonly buffered;
111
+ private readonly contextListeners;
112
+ private readonly topicCounts;
113
+ private readonly outputAgents;
114
+ private outputSync;
115
+ private readonly toolHandlers;
116
+ private readonly waitingToolCalls;
117
+ private readonly activeToolCalls;
118
+ private readonly requestHandlers;
119
+ private readonly waitingRequests;
120
+ private readonly streams;
121
+ private readonly watchers;
122
+ private readonly suspendHandlers;
123
+ private menuHandlers;
124
+ private logWindow;
125
+ private readonly disposers;
126
+ private readonly lanes;
127
+ /** Instance-scope storage; `card.storage.package` is shared by every card of the package. */
128
+ readonly storage: CardStorage;
129
+ /** The card's settings form (rendered by the host from the manifest's `settings`). */
130
+ readonly settings: CardSettings;
131
+ /** Host dialogs, toasts and the card's kebab menu. */
132
+ readonly ui: CardUi;
133
+ /** Agents and terminals on the canvas (`agents.read`, `agents.output`, `agents.prompt`). */
134
+ readonly agents: CardAgents;
135
+ /** Shell cards connected by an arrow (`terminals.write`). */
136
+ readonly terminals: CardTerminals;
137
+ /** Typed ports between cards connected by arrows. */
138
+ readonly ports: CardPorts;
139
+ /** Tools this card offers to connected agents over MCP. */
140
+ readonly tools: CardTools;
141
+ /** Network through the host's proxy (`network`, `network.local`). */
142
+ readonly net: CardNet;
143
+ /** Files in the workspace folder (`fs.read`, `fs.write`). */
144
+ readonly fs: CardFs;
145
+ /** Declared permissions and their grant state; ask for optional ones. */
146
+ readonly permissions: CardPermissions;
147
+ /** Visibility, suspension, expansion and size. */
148
+ readonly lifecycle: CardLifecycle;
149
+ /** Messages for the package log (and the `neurosquad-card dev` console). */
150
+ readonly log: CardLog;
151
+ /** @internal Use `connect()`. */
152
+ constructor(channel: Channel, context: HostContext, options?: CardOptions);
153
+ /**
154
+ * Calls any host method, fully typed from the contract.
155
+ *
156
+ * @example
157
+ * const { keys } = await card.call('storage.keys', { scope: 'instance', prefix: 'draft:' })
158
+ * const info = await card.call('card.getInfo')
159
+ */
160
+ call<M extends CardMethodName>(method: M, ...args: CallArgs<M>): Promise<MethodResult<M>>;
161
+ /**
162
+ * Calls a method by name without compile-time types — for methods a newer
163
+ * host added within the same protocol (check `card.host.capabilities()` first).
164
+ */
165
+ callUnchecked(method: string, params?: JsonValue, options?: CallOptions): Promise<unknown>;
166
+ /**
167
+ * Listens to a host event. Subscribable topics (`agents.status`,
168
+ * `agents.turn`, `agents.changed`, `storage.changed`) are subscribed with
169
+ * the host automatically while at least one listener exists.
170
+ * `agents.output` needs agent ids: use `card.agents.onOutput()`.
171
+ *
172
+ * @returns A function that removes the listener.
173
+ */
174
+ on<E extends CardEventName>(event: E, handler: EventHandler<E>): Unsubscribe;
175
+ /** Like {@link on}, but only for the next occurrence. */
176
+ once<E extends CardEventName>(event: E, handler: EventHandler<E>): Unsubscribe;
177
+ /** Resolves with the next occurrence of an event (optionally the first one matching `filter`). */
178
+ waitFor<E extends CardEventName>(event: E, filter?: (data: CardEvents[E]) => boolean, options?: {
179
+ timeoutMs?: number;
180
+ signal?: AbortSignal;
181
+ }): Promise<CardEvents[E]>;
182
+ /** Subscribes to topics directly (normally `on()` does it for you). Resolves with the granted subset. */
183
+ subscribe(topics: EventTopic[], agentIds?: string[]): Promise<EventTopic[]>;
184
+ /** Unsubscribes topics directly. */
185
+ unsubscribe(topics: EventTopic[]): Promise<void>;
186
+ /**
187
+ * Everything the host told the card at start, kept current: visibility,
188
+ * expanded, size, theme, language, settings, permissions and peers are
189
+ * updated from their events. The object is replaced (never mutated) on
190
+ * every change — safe for React's `useSyncExternalStore`.
191
+ */
192
+ get context(): HostContext;
193
+ /** Called with the new context after any change to it. */
194
+ onContextChange(listener: (context: HostContext) => void): Unsubscribe;
195
+ /** This card's id on the canvas. */
196
+ get instanceId(): string;
197
+ /** Package, version, title and size. */
198
+ get instance(): CardInstanceInfo;
199
+ get workspace(): WorkspaceInfo;
200
+ /** `visible`, `offscreen`, `overview` or `hidden`. Pause work when not `visible`. */
201
+ get visibility(): CardVisibility;
202
+ /** The card is expanded to fill the canvas. */
203
+ get expanded(): boolean;
204
+ get size(): CardSize;
205
+ get theme(): ThemeSnapshot;
206
+ /** `en`, `ru` or `zh`, plus a BCP 47 locale for `Intl`. */
207
+ get i18n(): I18nSnapshot;
208
+ get language(): I18nSnapshot['language'];
209
+ /** Why the frame (re)started: created, opened, resumed, reloaded or updated. */
210
+ get launch(): HostContext['launch'];
211
+ /** `init` from the card that spawned this one with `card.spawn('self', { init })`, on its first start. */
212
+ get spawnInit(): JsonValue | undefined;
213
+ /** The host's limits (sizes, rates). */
214
+ get limits(): HostContext['limits'];
215
+ /** The app's version. */
216
+ get appVersion(): string;
217
+ /** Fresh instance info from the host. */
218
+ getInfo(): Promise<CardInstanceInfo>;
219
+ /** The card's own title (the user's name for the card still wins). `null` clears it. */
220
+ setTitle(title: string | null): Promise<void>;
221
+ /**
222
+ * The status chip in the card header. `null` clears it.
223
+ *
224
+ * @example
225
+ * card.setStatus('Running tests…', { busy: true })
226
+ * card.setStatus('3 failed', { tone: 'danger' })
227
+ */
228
+ setStatus(text: string | null, options?: {
229
+ tone?: CardTone;
230
+ busy?: boolean;
231
+ }): Promise<void>;
232
+ /** The header badge: a count (`3`), a short text (`'NEW'`, ≤ 12 chars), or `null` to clear. */
233
+ setBadge(value: number | string | null, options?: {
234
+ tone?: CardTone;
235
+ }): Promise<void>;
236
+ /**
237
+ * What the overview tile shows when the canvas is zoomed out (and while the
238
+ * card is suspended, and on the phone). Persisted by the host.
239
+ */
240
+ setOverview(overview: OverviewContent): Promise<void>;
241
+ /**
242
+ * Asks for the user's attention: `needs-input` pulses the card like a
243
+ * waiting agent and lists it in the Inbox; `info` is softer; `none` clears.
244
+ * The host allows one change per 10 s; when it refuses, the SDK retries
245
+ * with the latest level as soon as it is allowed (a burst folds into one).
246
+ * `card.context.chrome?.attention` says what the host shows right now
247
+ * (it survives frame reloads — clear it if it is stale).
248
+ */
249
+ attention(level: 'none' | 'info' | 'needs-input', message?: string): Promise<void>;
250
+ /** Keeps `context.chrome` in step with what the host now shows (hosts that send it). */
251
+ private noteChrome;
252
+ /**
253
+ * Sends a chrome update through its latest-wins lane (MC-8: the host
254
+ * throttles these; the lane spaces them so the host never refuses one).
255
+ */
256
+ private coalesced;
257
+ /** Asks to be resized; clamped to the manifest's min/max. Resolves with the size applied. */
258
+ requestResize(size: CardSize): Promise<CardSize>;
259
+ /** Opens an https link after the user confirms it on the card. Resolves `true` if it was opened. */
260
+ openLink(url: string): Promise<boolean>;
261
+ /** Flies the camera to another card of the same workspace (card must be visible; every 5 s at most). */
262
+ focusCard(cardId: string): Promise<void>;
263
+ /**
264
+ * Adds a card next to this one (`canvas.spawn`, ≤ 4 per card) and connects
265
+ * it with an arrow unless `connect: false`. `self` = another card of this
266
+ * package; it receives `init` as `card.spawnInit`.
267
+ */
268
+ spawn(kind: 'self' | 'note' | 'todo' | 'kanban' | 'sticky', options?: {
269
+ title?: string;
270
+ connect?: boolean;
271
+ init?: JsonValue;
272
+ }): Promise<string>;
273
+ /** Host info and feature detection. */
274
+ readonly host: {
275
+ /** Methods and events this app supports (for features added within a protocol version). */
276
+ capabilities: () => Promise<{
277
+ protocol: number;
278
+ methods: string[];
279
+ events: string[];
280
+ }>;
281
+ /** True when the app supports a method (asks the host once and caches). */
282
+ supports: (method: string) => Promise<boolean>;
283
+ };
284
+ private capabilityCache;
285
+ /** The workspace; `path` only with `fs.read`. */
286
+ getWorkspace(): Promise<WorkspaceInfo>;
287
+ /** Fresh theme snapshot (the context copy is already kept current). */
288
+ getTheme(): Promise<ThemeSnapshot>;
289
+ /** Fresh language snapshot. */
290
+ getI18n(): Promise<I18nSnapshot>;
291
+ /** Token usage and cost of this workspace (`usage.read`). Money is in micro-dollars. */
292
+ usage(period?: 'today' | '7d' | '30d'): Promise<UsageSummary>;
293
+ /** Copies text to the user's clipboard (`clipboard.write`, once per second). */
294
+ copyText(text: string): Promise<void>;
295
+ /** Closes the connection. The card stops receiving events; calls fail with UNAVAILABLE. */
296
+ close(): void;
297
+ /** True after {@link close} or when the host closed the port. */
298
+ get closed(): boolean;
299
+ /** @internal */
300
+ emitLocal(event: string, data: unknown): void;
301
+ /** @internal */
302
+ setContext(patch: Partial<HostContext>): void;
303
+ /** @internal */
304
+ notify<M extends CardMethodName>(method: M, params: MethodParams<M>): void;
305
+ /** @internal */
306
+ registerToolHandler(name: string, handler: ToolHandler<never>): Unsubscribe;
307
+ /** @internal */
308
+ registerRequestHandler(input: string, handler: PortRequestHandler<never>): Unsubscribe;
309
+ /** @internal */
310
+ openStream(requestId: string): ChunkQueue;
311
+ /** @internal */
312
+ closeStream(requestId: string): void;
313
+ /** @internal */
314
+ addWatcher(watchId: string, handler: (event: CardEvents['fs.changed']) => void): void;
315
+ /** @internal */
316
+ removeWatcher(watchId: string): void;
317
+ /** @internal */
318
+ addSuspendHandler(handler: (graceMs: number) => void | Promise<void>): Unsubscribe;
319
+ /** @internal */
320
+ setMenuHandlers(handlers: Map<string, () => void>): void;
321
+ /** @internal Reference-counted `agents.output` subscription for these agents. */
322
+ watchOutput(agentIds: string[], delta: 1 | -1): void;
323
+ private scheduleOutputSync;
324
+ private retainTopic;
325
+ private releaseTopic;
326
+ private dispatch;
327
+ private onToolCall;
328
+ private runTool;
329
+ private onPortRequest;
330
+ private runRequest;
331
+ private runSuspend;
332
+ private applyEnvironment;
333
+ private installErrorForwarding;
334
+ private writeLog;
335
+ private safely;
336
+ private reportHandlerError;
337
+ private makeStorage;
338
+ }
339
+ /** `card.log`: lines for the package log (visible in `neurosquad-card dev`). Throttled to 50 lines/s. */
340
+ export interface CardLog {
341
+ debug(...args: unknown[]): void;
342
+ info(...args: unknown[]): void;
343
+ warn(...args: unknown[]): void;
344
+ error(...args: unknown[]): void;
345
+ }
346
+ /** `card.settings` */
347
+ export declare class CardSettings {
348
+ private readonly card;
349
+ /** @internal */
350
+ constructor(card: Card);
351
+ /** Current values (defaults filled in; secrets are never included). */
352
+ get values(): Record<string, SettingValue>;
353
+ /** Which secret settings the user has filled in (the values never reach the card). */
354
+ get secrets(): Record<string, boolean>;
355
+ /** One value, typed by you. */
356
+ value<T extends SettingValue = SettingValue>(key: string): T | undefined;
357
+ /** True when the user has set the secret setting `key`. Use it in `net.fetch` headers as `{{secret:key}}`. */
358
+ hasSecret(key: string): boolean;
359
+ /** Fresh snapshot from the host. */
360
+ get(): Promise<SettingsSnapshot>;
361
+ /** Changes non-secret settings (validated against the manifest). Every instance gets `settings.changed`. */
362
+ set(values: Record<string, SettingValue>): Promise<SettingsSnapshot>;
363
+ /** Opens the host's settings form on the card (card must be visible). */
364
+ open(): Promise<void>;
365
+ /** Settings changed (by the user in the form, or by `set`). */
366
+ onChange(handler: (settings: SettingsSnapshot) => void): Unsubscribe;
367
+ }
368
+ /** `card.ui` */
369
+ export declare class CardUi {
370
+ private readonly card;
371
+ /** @internal */
372
+ constructor(card: Card);
373
+ /** A small toast inside the card box (once per 2 s). */
374
+ toast(message: string, options?: {
375
+ tone?: CardTone;
376
+ durationMs?: number;
377
+ }): Promise<void>;
378
+ /**
379
+ * A host confirmation dialog inside the card box. Resolves `true` when the
380
+ * user confirms. Card must be visible.
381
+ */
382
+ confirm(options: {
383
+ title: string;
384
+ message: string;
385
+ confirmLabel?: string;
386
+ cancelLabel?: string;
387
+ tone?: 'default' | 'danger';
388
+ }): Promise<boolean>;
389
+ /**
390
+ * Items appended to the card's kebab menu (≤ 12). Give each an `onSelect`,
391
+ * or listen with {@link onMenu}. Replaces the previous items.
392
+ *
393
+ * @example
394
+ * card.ui.setMenu([
395
+ * { id: 'refresh', label: 'Refresh', icon: 'arrow-path', onSelect: refresh },
396
+ * { id: 'reset', label: 'Reset', tone: 'danger', onSelect: reset }
397
+ * ])
398
+ */
399
+ setMenu(items: CardMenuItem[]): Promise<void>;
400
+ /** A menu item was chosen. */
401
+ onMenu(handler: (id: string) => void): Unsubscribe;
402
+ }
403
+ /** `card.agents` */
404
+ export declare class CardAgents {
405
+ private readonly card;
406
+ /** @internal */
407
+ constructor(card: Card);
408
+ /** AI agents and shells in this workspace (`agents.read`). */
409
+ list(): Promise<AgentInfo[]>;
410
+ get(agentId: string): Promise<AgentInfo>;
411
+ /** The visible screen of a connected agent or terminal (`agents.output`), up to 500 lines. */
412
+ readScreen(agentId: string, lines?: number): Promise<string>;
413
+ /** Claude Code's last reply (connected, `agents.output`); `text: null` for other harnesses. */
414
+ lastReply(agentId: string): Promise<{
415
+ text: string | null;
416
+ at: number | null;
417
+ }>;
418
+ /**
419
+ * Sends a prompt to a connected AI agent (`agents.prompt`, 6 per minute).
420
+ * While the agent works it is queued (`whenBusy: 'queue'`, default),
421
+ * sent anyway (`'send'`), or refused with BUSY (`'fail'`).
422
+ * `submit: false` only types it in; the user presses Enter.
423
+ *
424
+ * @returns How it was delivered: `sent`, `queued` or `inserted`.
425
+ */
426
+ prompt(agentId: string, text: string, options?: {
427
+ submit?: boolean;
428
+ whenBusy?: 'queue' | 'send' | 'fail';
429
+ }): Promise<'sent' | 'queued' | 'inserted'>;
430
+ /** An agent's status changed (optionally only for one agent). */
431
+ onStatus(handler: (event: CardEvents['agents.status']) => void, agentId?: string): Unsubscribe;
432
+ /** A turn started or ended (optionally only for one agent). */
433
+ onTurn(handler: (event: CardEvents['agents.turn']) => void, agentId?: string): Unsubscribe;
434
+ /** Agents were added, removed or renamed; receives the full new list. */
435
+ onChanged(handler: (agents: AgentInfo[]) => void): Unsubscribe;
436
+ /**
437
+ * Live output (ANSI stripped, coalesced) of connected agents/terminals
438
+ * (`agents.output`). Subscribes for these ids while the handler is attached.
439
+ */
440
+ onOutput(agentIds: string[], handler: (event: CardEvents['agents.output']) => void): Unsubscribe;
441
+ }
442
+ /** `card.terminals` */
443
+ export declare class CardTerminals {
444
+ private readonly card;
445
+ /** @internal */
446
+ constructor(card: Card);
447
+ /**
448
+ * Runs a command in a connected shell card and waits for it
449
+ * (`terminals.write`). `exitCode` is null for cmd.exe.
450
+ */
451
+ run(agentId: string, command: string, options?: {
452
+ timeoutMs?: number;
453
+ }): Promise<{
454
+ exitCode: number | null;
455
+ output: string;
456
+ truncated: boolean;
457
+ timedOut: boolean;
458
+ }>;
459
+ /** Types text into a connected shell; `submit: true` presses Enter after it. */
460
+ write(agentId: string, text: string, options?: {
461
+ submit?: boolean;
462
+ }): Promise<void>;
463
+ }
464
+ /** `card.ports` */
465
+ export declare class CardPorts {
466
+ private readonly card;
467
+ /** @internal */
468
+ constructor(card: Card);
469
+ /** This card's inputs, as declared in the manifest (labels resolved). */
470
+ get inputs(): PortInfo[];
471
+ /** This card's outputs. */
472
+ get outputs(): PortInfo[];
473
+ /** Cards connected by arrows, with their direction and ports (kept current in `card.context.peers`). */
474
+ get peers(): PeerInfo[];
475
+ /** Fresh peer list from the host. */
476
+ refreshPeers(): Promise<PeerInfo[]>;
477
+ /** Own ports, fresh from the host. */
478
+ describe(): Promise<{
479
+ inputs: PortInfo[];
480
+ outputs: PortInfo[];
481
+ }>;
482
+ /**
483
+ * Sends a value out of an output port to every downstream peer whose input
484
+ * accepts it (converted when the types differ). Resolves with how many
485
+ * peers got it. Retained outputs also remember the value.
486
+ */
487
+ emit(port: string, data: JsonValue): Promise<number>;
488
+ /** Sends to one peer; `input` picks its input explicitly. */
489
+ send(to: string, port: string, data: JsonValue, options?: {
490
+ input?: string;
491
+ }): Promise<number>;
492
+ /** Asks a peer's `mode: "request"` input and waits for the reply. */
493
+ request<T = JsonValue>(to: string, input: string, data: JsonValue, options?: {
494
+ timeoutMs?: number;
495
+ }): Promise<T>;
496
+ /** A connected peer's retained output value, or null. */
497
+ read<T = JsonValue>(from: string, port: string): Promise<{
498
+ data: T;
499
+ at: number;
500
+ } | null>;
501
+ /**
502
+ * Values arriving on inputs (optionally one input). Messages that arrive
503
+ * before the first listener are kept (up to 100) and delivered to it.
504
+ */
505
+ onMessage<T = JsonValue>(handler: (data: T, message: CardEvents['ports.message']) => void, options?: {
506
+ input?: string;
507
+ }): Unsubscribe;
508
+ /**
509
+ * Answers requests to one of our `mode: "request"` inputs. Return the reply
510
+ * (checked against the input's `response` type by the host), or throw to
511
+ * send an error. Requests that arrive before you register wait until
512
+ * their deadline.
513
+ */
514
+ onRequest<T = JsonValue>(input: string, handler: PortRequestHandler<T>): Unsubscribe;
515
+ /** Arrows or peers changed. */
516
+ onPeersChanged(handler: (peers: PeerInfo[]) => void): Unsubscribe;
517
+ }
518
+ /** `card.tools` */
519
+ export declare class CardTools {
520
+ private readonly card;
521
+ /** @internal */
522
+ constructor(card: Card);
523
+ /**
524
+ * Implements a tool declared in the manifest's `tools`. Connected agents
525
+ * call it over MCP; the handler's return value is the result:
526
+ * a string (text), a `ToolResultPayload` (`{ content: [...] }`, images too),
527
+ * or any JSON value (sent as pretty JSON). Throwing sends an error result.
528
+ * Calls that arrive before registration wait for it until their deadline.
529
+ *
530
+ * @example
531
+ * card.tools.handle<{ filter?: string }>('run_tests', async ({ filter }, call) => {
532
+ * call.progress('running…')
533
+ * const result = await runTests(filter, call.signal)
534
+ * return result.failures.length ? result.failures.join('\n') : 'All tests passed'
535
+ * })
536
+ */
537
+ handle<A = JsonValue>(name: string, handler: ToolHandler<A>): Unsubscribe;
538
+ /** Hides or shows a declared tool from agents (e.g. until the user signs in). */
539
+ setEnabled(tool: string, enabled: boolean): Promise<void>;
540
+ }
541
+ /** `card.net` */
542
+ export declare class CardNet {
543
+ private readonly card;
544
+ /** @internal */
545
+ constructor(card: Card);
546
+ /**
547
+ * `fetch` through the host's proxy. Only https hosts granted by the
548
+ * `network` permission (or localhost with `network.local`); cookies are
549
+ * never kept. See {@link CardFetchInit} and {@link CardResponse}.
550
+ *
551
+ * @example
552
+ * const res = await card.net.fetch('https://api.example.com/items', {
553
+ * method: 'POST',
554
+ * headers: { authorization: 'Bearer {{secret:apiKey}}' },
555
+ * body: { name: 'x' },
556
+ * responseType: 'json'
557
+ * })
558
+ */
559
+ fetch(url: string | URL, init?: CardFetchInit): Promise<CardResponse>;
560
+ }
561
+ /** `card.fs` — paths are relative to the workspace folder. */
562
+ export declare class CardFs {
563
+ private readonly card;
564
+ /** @internal */
565
+ constructor(card: Card);
566
+ stat(path: string): Promise<FsStat>;
567
+ /** Lists a folder (`recursive` skips .git and node_modules). */
568
+ list(path?: string, options?: {
569
+ recursive?: boolean;
570
+ maxEntries?: number;
571
+ }): Promise<{
572
+ entries: FsEntry[];
573
+ truncated: boolean;
574
+ }>;
575
+ /** Reads a file as UTF-8 text. */
576
+ readText(path: string, options?: {
577
+ maxBytes?: number;
578
+ }): Promise<string>;
579
+ /** Reads a file as bytes. */
580
+ readBytes(path: string, options?: {
581
+ maxBytes?: number;
582
+ }): Promise<Uint8Array>;
583
+ /** The raw read result (with `size` and `truncated`). */
584
+ read(path: string, options?: {
585
+ encoding?: 'utf8' | 'base64';
586
+ maxBytes?: number;
587
+ }): Promise<{
588
+ data: string;
589
+ encoding: 'utf8' | 'base64';
590
+ size: number;
591
+ truncated: boolean;
592
+ }>;
593
+ /**
594
+ * Writes a text file atomically (`fs.write`; never under .git/).
595
+ * `ifMtimeMs` fails with FS_ERROR (`data.conflict`) if the file changed since you read it.
596
+ */
597
+ writeText(path: string, text: string, options?: {
598
+ createDirs?: boolean;
599
+ ifMtimeMs?: number;
600
+ }): Promise<FsStat>;
601
+ /** Writes bytes atomically. */
602
+ writeBytes(path: string, data: ArrayBuffer | ArrayBufferView, options?: {
603
+ createDirs?: boolean;
604
+ ifMtimeMs?: number;
605
+ }): Promise<FsStat>;
606
+ mkdir(path: string): Promise<FsStat>;
607
+ /** Moves to the OS trash (there is no hard delete). */
608
+ trash(path: string): Promise<void>;
609
+ /**
610
+ * Watches a file or folder (debounced 100 ms; ≤ 20 watchers per card).
611
+ * Resolves with a function that stops watching.
612
+ */
613
+ watch(path: string, handler: (change: CardEvents['fs.changed']) => void, options?: {
614
+ recursive?: boolean;
615
+ }): Promise<() => Promise<void>>;
616
+ }
617
+ /** `card.permissions` */
618
+ export declare class CardPermissions {
619
+ private readonly card;
620
+ /** @internal */
621
+ constructor(card: Card);
622
+ /** Declared permissions and whether each is granted (kept current). */
623
+ get all(): PermissionState[];
624
+ /** True when the permission is granted (directly or implied, e.g. `fs.write` implies `fs.read`). */
625
+ has(id: PermissionId): boolean;
626
+ /** Fresh list from the host. */
627
+ list(): Promise<PermissionState[]>;
628
+ /**
629
+ * Asks the user for optional permissions declared in the manifest (a host
630
+ * popover on the card; the card must be visible). Resolves with the ids
631
+ * granted now.
632
+ */
633
+ request(...ids: PermissionId[]): Promise<PermissionId[]>;
634
+ onChange(handler: (permissions: PermissionState[]) => void): Unsubscribe;
635
+ }
636
+ /** `card.lifecycle` */
637
+ export declare class CardLifecycle {
638
+ private readonly card;
639
+ /** @internal */
640
+ constructor(card: Card);
641
+ /** Visibility changed: `visible`, `offscreen`, `overview` (zoomed out, frame hidden) or `hidden`. */
642
+ onVisibility(handler: (state: CardVisibility) => void): Unsubscribe;
643
+ /**
644
+ * The frame is about to be unloaded (hidden too long, or the live-frame cap).
645
+ * Save state now; the SDK waits for your promise up to `graceMs`.
646
+ * The card comes back later with `card.launch === 'resumed'`.
647
+ */
648
+ onSuspend(handler: (graceMs: number) => void | Promise<void>): Unsubscribe;
649
+ onExpanded(handler: (expanded: boolean) => void): Unsubscribe;
650
+ onResized(handler: (size: CardSize) => void): Unsubscribe;
651
+ }
652
+ /** Icons the host can draw for a card (overview tile, menu items). */
653
+ export type { HostIconName };