@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.
- package/LICENSE +21 -0
- package/README.md +431 -0
- package/dist/card-sdk.js +5266 -0
- package/dist/cli.js +2838 -0
- package/dist/react.js +257 -0
- package/dist/testing.js +1150 -0
- package/dist/types/client/base64.d.ts +7 -0
- package/dist/types/client/card.d.ts +653 -0
- package/dist/types/client/channel.d.ts +49 -0
- package/dist/types/client/coalesce.d.ts +14 -0
- package/dist/types/client/connect.d.ts +39 -0
- package/dist/types/client/errors.d.ts +46 -0
- package/dist/types/client/helpers.d.ts +25 -0
- package/dist/types/client/net.d.ts +111 -0
- package/dist/types/client/theme.d.ts +31 -0
- package/dist/types/client/toolResult.d.ts +16 -0
- package/dist/types/contract/api.d.ts +812 -0
- package/dist/types/contract/index.d.ts +11 -0
- package/dist/types/contract/jsonSchema.d.ts +88 -0
- package/dist/types/contract/localized.d.ts +10 -0
- package/dist/types/contract/manifest.d.ts +179 -0
- package/dist/types/contract/network.d.ts +48 -0
- package/dist/types/contract/permissions.d.ts +180 -0
- package/dist/types/contract/ports.d.ts +237 -0
- package/dist/types/contract/protocol.d.ts +74 -0
- package/dist/types/contract/source.d.ts +111 -0
- package/dist/types/contract/theme.d.ts +21 -0
- package/dist/types/contract/version.d.ts +126 -0
- package/dist/types/i18n.d.ts +50 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/react/index.d.ts +132 -0
- package/dist/types/testing/index.d.ts +7 -0
- package/dist/types/testing/mockHost.d.ts +315 -0
- package/dist/types/version.d.ts +2 -0
- package/dist/ui.css +581 -0
- package/package.json +77 -0
- package/schema/neurosquad-card.v1.json +434 -0
- package/templates/react/README.md +34 -0
- package/templates/react/_gitignore +10 -0
- package/templates/react/icon.png +0 -0
- package/templates/react/index.html +12 -0
- package/templates/react/neurosquad-card.json +94 -0
- package/templates/react/package.json +25 -0
- package/templates/react/src/App.tsx +131 -0
- package/templates/react/src/i18n.ts +61 -0
- package/templates/react/src/main.tsx +77 -0
- package/templates/react/src/styles.css +42 -0
- package/templates/react/tsconfig.json +18 -0
- package/templates/react/vite.config.ts +19 -0
- package/templates/vanilla/README.md +37 -0
- package/templates/vanilla/_gitignore +6 -0
- package/templates/vanilla/icon.png +0 -0
- package/templates/vanilla/index.html +41 -0
- package/templates/vanilla/main.js +256 -0
- package/templates/vanilla/neurosquad-card.json +94 -0
- package/templates/vanilla/style.css +41 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import type { CardJsonSchema } from './jsonSchema.js';
|
|
2
|
+
import type { LocalizedText } from './localized.js';
|
|
3
|
+
import type { PermissionId } from './permissions.js';
|
|
4
|
+
/** `ns:*` well-known types, or `<package-name>/<type-name>` custom types (which must carry a schema). */
|
|
5
|
+
export type PortTypeId = WellKnownPortType | `${string}/${string}`;
|
|
6
|
+
export declare const CUSTOM_PORT_TYPE_RE: RegExp;
|
|
7
|
+
export declare const PORT_ID_RE: RegExp;
|
|
8
|
+
export declare const WELL_KNOWN_PORT_SCHEMAS: {
|
|
9
|
+
/** Anything JSON. As an input: accepts every output type unchanged. */
|
|
10
|
+
readonly 'ns:any': {};
|
|
11
|
+
readonly 'ns:text': {
|
|
12
|
+
readonly type: "string";
|
|
13
|
+
readonly maxLength: 1000000;
|
|
14
|
+
};
|
|
15
|
+
readonly 'ns:markdown': {
|
|
16
|
+
readonly type: "string";
|
|
17
|
+
readonly maxLength: 1000000;
|
|
18
|
+
};
|
|
19
|
+
readonly 'ns:number': {
|
|
20
|
+
readonly type: "number";
|
|
21
|
+
};
|
|
22
|
+
readonly 'ns:boolean': {
|
|
23
|
+
readonly type: "boolean";
|
|
24
|
+
};
|
|
25
|
+
readonly 'ns:json': {
|
|
26
|
+
readonly type: readonly ["object", "array"];
|
|
27
|
+
};
|
|
28
|
+
readonly 'ns:url': {
|
|
29
|
+
readonly type: "string";
|
|
30
|
+
readonly format: "uri";
|
|
31
|
+
readonly maxLength: 8192;
|
|
32
|
+
};
|
|
33
|
+
/** Base64 image, ≤ ~700 KB decoded (message limit). */
|
|
34
|
+
readonly 'ns:image': {
|
|
35
|
+
readonly type: "object";
|
|
36
|
+
readonly required: readonly ["mimeType", "data"];
|
|
37
|
+
readonly additionalProperties: false;
|
|
38
|
+
readonly properties: {
|
|
39
|
+
readonly mimeType: {
|
|
40
|
+
readonly enum: readonly ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
41
|
+
};
|
|
42
|
+
readonly data: {
|
|
43
|
+
readonly type: "string";
|
|
44
|
+
readonly maxLength: 1000000;
|
|
45
|
+
};
|
|
46
|
+
readonly alt: {
|
|
47
|
+
readonly type: "string";
|
|
48
|
+
readonly maxLength: 500;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/** A file in the workspace folder (relative path). Receiving one grants no file access. */
|
|
53
|
+
readonly 'ns:file-ref': {
|
|
54
|
+
readonly type: "object";
|
|
55
|
+
readonly required: readonly ["path"];
|
|
56
|
+
readonly additionalProperties: false;
|
|
57
|
+
readonly properties: {
|
|
58
|
+
readonly path: {
|
|
59
|
+
readonly type: "string";
|
|
60
|
+
readonly minLength: 1;
|
|
61
|
+
readonly maxLength: 1024;
|
|
62
|
+
};
|
|
63
|
+
readonly line: {
|
|
64
|
+
readonly type: "integer";
|
|
65
|
+
readonly minimum: 1;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
readonly 'ns:task': CardJsonSchema;
|
|
70
|
+
readonly 'ns:tasks': {
|
|
71
|
+
readonly type: "array";
|
|
72
|
+
readonly maxItems: 200;
|
|
73
|
+
readonly items: CardJsonSchema;
|
|
74
|
+
};
|
|
75
|
+
/** Change one existing task; `ref` is its id or its exact text. */
|
|
76
|
+
readonly 'ns:task-patch': {
|
|
77
|
+
readonly type: "object";
|
|
78
|
+
readonly required: readonly ["ref"];
|
|
79
|
+
readonly additionalProperties: false;
|
|
80
|
+
readonly properties: {
|
|
81
|
+
readonly ref: {
|
|
82
|
+
readonly type: "string";
|
|
83
|
+
readonly minLength: 1;
|
|
84
|
+
readonly maxLength: 500;
|
|
85
|
+
};
|
|
86
|
+
readonly text: {
|
|
87
|
+
readonly type: "string";
|
|
88
|
+
readonly minLength: 1;
|
|
89
|
+
readonly maxLength: 500;
|
|
90
|
+
};
|
|
91
|
+
readonly status: {
|
|
92
|
+
readonly enum: readonly ["pending", "active", "done", "error"];
|
|
93
|
+
};
|
|
94
|
+
readonly column: {
|
|
95
|
+
readonly type: "string";
|
|
96
|
+
readonly maxLength: 64;
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
readonly 'ns:table': {
|
|
101
|
+
readonly type: "object";
|
|
102
|
+
readonly required: readonly ["columns", "rows"];
|
|
103
|
+
readonly additionalProperties: false;
|
|
104
|
+
readonly properties: {
|
|
105
|
+
readonly columns: {
|
|
106
|
+
readonly type: "array";
|
|
107
|
+
readonly maxItems: 64;
|
|
108
|
+
readonly items: {
|
|
109
|
+
readonly type: "string";
|
|
110
|
+
readonly maxLength: 200;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
readonly rows: {
|
|
114
|
+
readonly type: "array";
|
|
115
|
+
readonly maxItems: 5000;
|
|
116
|
+
readonly items: {
|
|
117
|
+
readonly type: "array";
|
|
118
|
+
readonly maxItems: 64;
|
|
119
|
+
readonly items: {
|
|
120
|
+
readonly type: readonly ["string", "number", "boolean", "null"];
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
readonly 'ns:event': {
|
|
127
|
+
readonly type: "object";
|
|
128
|
+
readonly required: readonly ["type"];
|
|
129
|
+
readonly additionalProperties: false;
|
|
130
|
+
readonly properties: {
|
|
131
|
+
readonly type: {
|
|
132
|
+
readonly type: "string";
|
|
133
|
+
readonly minLength: 1;
|
|
134
|
+
readonly maxLength: 64;
|
|
135
|
+
};
|
|
136
|
+
readonly data: {};
|
|
137
|
+
readonly at: {
|
|
138
|
+
readonly type: "number";
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
/** A bare signal ("go", "refresh"). */
|
|
143
|
+
readonly 'ns:trigger': {
|
|
144
|
+
readonly type: readonly ["object", "null"];
|
|
145
|
+
readonly maxProperties: 0;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
export type WellKnownPortType = keyof typeof WELL_KNOWN_PORT_SCHEMAS;
|
|
149
|
+
export declare const WELL_KNOWN_PORT_TYPES: WellKnownPortType[];
|
|
150
|
+
export declare function isWellKnownPortType(type: string): type is WellKnownPortType;
|
|
151
|
+
export declare function isValidPortType(type: string): type is PortTypeId;
|
|
152
|
+
/** A port as the manifest declares it. */
|
|
153
|
+
export interface PortDeclaration {
|
|
154
|
+
/** Unique per direction within the card: `^[a-z][a-z0-9-]{0,31}$`. */
|
|
155
|
+
id: string;
|
|
156
|
+
label: LocalizedText;
|
|
157
|
+
/** What it means — shown to users and to peers (and agents) discovering it. */
|
|
158
|
+
description?: LocalizedText;
|
|
159
|
+
type: PortTypeId;
|
|
160
|
+
/** Extra constraints on top of the type (required for custom types). No `pattern`. */
|
|
161
|
+
schema?: CardJsonSchema;
|
|
162
|
+
/**
|
|
163
|
+
* Inputs only. `stream` (default): fire-and-forget messages.
|
|
164
|
+
* `request`: the sender waits for a reply (ports.request / ports.respond).
|
|
165
|
+
*/
|
|
166
|
+
mode?: 'stream' | 'request';
|
|
167
|
+
/** Inputs with mode `request`: the reply's type. */
|
|
168
|
+
response?: {
|
|
169
|
+
type: PortTypeId;
|
|
170
|
+
schema?: CardJsonSchema;
|
|
171
|
+
};
|
|
172
|
+
/** Outputs only: the host keeps the last value; peers can read it any time and new peers get it on connect. */
|
|
173
|
+
retain?: boolean;
|
|
174
|
+
/** Inputs only: preferred target when a peer's output fits several inputs. */
|
|
175
|
+
default?: boolean;
|
|
176
|
+
}
|
|
177
|
+
/** A port as the runtime describes it (own ports, peers' ports). */
|
|
178
|
+
export interface PortInfo {
|
|
179
|
+
id: string;
|
|
180
|
+
/** Resolved to the app language. */
|
|
181
|
+
label: string;
|
|
182
|
+
description?: string;
|
|
183
|
+
type: PortTypeId;
|
|
184
|
+
schema?: CardJsonSchema;
|
|
185
|
+
mode: 'stream' | 'request';
|
|
186
|
+
response?: {
|
|
187
|
+
type: PortTypeId;
|
|
188
|
+
schema?: CardJsonSchema;
|
|
189
|
+
};
|
|
190
|
+
retain: boolean;
|
|
191
|
+
default: boolean;
|
|
192
|
+
/** Built-in adapters: the permission the *other* card needs to use this port. */
|
|
193
|
+
permission?: PermissionId;
|
|
194
|
+
}
|
|
195
|
+
/** The schema a value on this port must satisfy: the type's own, and the declared one too (both must pass). */
|
|
196
|
+
export declare function portSchemas(port: {
|
|
197
|
+
type: PortTypeId;
|
|
198
|
+
schema?: CardJsonSchema;
|
|
199
|
+
}): CardJsonSchema[];
|
|
200
|
+
export type PortCoercion = 'exact' | 'any' | 'identity' | 'stringify' | 'json-text' | 'json-markdown' | 'table-markdown' | 'tasks-markdown' | 'wrap-array' | 'lines-to-tasks';
|
|
201
|
+
/** How a value of output type `from` reaches an input of type `to`; null = incompatible. */
|
|
202
|
+
export declare function portCoercion(from: PortTypeId, to: PortTypeId): PortCoercion | null;
|
|
203
|
+
export declare function portsCompatible(from: PortTypeId, to: PortTypeId): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Converts a value from an output of type `from` for an input of type `to`.
|
|
206
|
+
* Call only when portCoercion() is non-null; the result is validated against
|
|
207
|
+
* the input's schemas by the caller (main) before delivery.
|
|
208
|
+
*/
|
|
209
|
+
export declare function coercePortValue(from: PortTypeId, to: PortTypeId, value: unknown): unknown;
|
|
210
|
+
/**
|
|
211
|
+
* Picks the input on a peer that an output value goes to on `emit`: among
|
|
212
|
+
* compatible inputs (in declaration order) the one marked `default`,
|
|
213
|
+
* otherwise an exact type match, otherwise the first compatible. Request-mode
|
|
214
|
+
* inputs are never picked by emit (use ports.request).
|
|
215
|
+
*/
|
|
216
|
+
export declare function pickInputFor(outputType: PortTypeId, inputs: readonly PortInfo[]): PortInfo | null;
|
|
217
|
+
export declare const BUILTIN_PEER_KINDS: readonly ["note", "todo", "kanban", "sticky", "agent", "terminal"];
|
|
218
|
+
export type BuiltinPeerKind = (typeof BUILTIN_PEER_KINDS)[number];
|
|
219
|
+
type BuiltinPort = Omit<PortInfo, 'label' | 'description'> & {
|
|
220
|
+
label: string;
|
|
221
|
+
description: string;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Virtual ports of built-in cards (labels are English; the host localizes
|
|
225
|
+
* them through `ext.cardSdk.builtinPorts.<kind>.<id>`). Using one requires
|
|
226
|
+
* the named permission on the *custom* card's side. Delivery semantics
|
|
227
|
+
* (spec §8.9): note.append appends a paragraph, note.replace replaces the
|
|
228
|
+
* text; todo/kanban.add add tasks, .update applies a task patch; sticky.title
|
|
229
|
+
* sets the title; agent.prompt submits a prompt through the prompt queue
|
|
230
|
+
* (same rules as agents.prompt); terminal.command runs a command
|
|
231
|
+
* (terminals.run). Outputs are emitted by the host when the card changes.
|
|
232
|
+
*/
|
|
233
|
+
export declare const BUILTIN_CARD_PORTS: Record<BuiltinPeerKind, {
|
|
234
|
+
inputs: BuiltinPort[];
|
|
235
|
+
outputs: BuiltinPort[];
|
|
236
|
+
}>;
|
|
237
|
+
export {};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { PROTOCOL_TAG } from './version.js';
|
|
2
|
+
export declare const CARD_ERROR_CODES: readonly ["BAD_REQUEST", "INVALID_PARAMS", "METHOD_NOT_FOUND", "PERMISSION_DENIED", "NOT_CONNECTED", "NOT_FOUND", "QUOTA_EXCEEDED", "RATE_LIMITED", "TOO_LARGE", "TIMEOUT", "BUDGET_PAUSED", "BUSY", "HOST_NOT_ALLOWED", "NETWORK_ERROR", "FS_DENIED", "FS_ERROR", "NOT_VISIBLE", "USER_CANCELLED", "UNAVAILABLE", "PROTOCOL_MISMATCH", "NOT_READY", "INTERNAL"];
|
|
3
|
+
export type CardErrorCode = (typeof CARD_ERROR_CODES)[number];
|
|
4
|
+
export interface CardErrorPayload {
|
|
5
|
+
code: CardErrorCode;
|
|
6
|
+
/** English, for developers. UIs show their own text per code. */
|
|
7
|
+
message: string;
|
|
8
|
+
data?: unknown;
|
|
9
|
+
}
|
|
10
|
+
interface Envelope {
|
|
11
|
+
ns: typeof PROTOCOL_TAG;
|
|
12
|
+
v: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RequestMessage extends Envelope {
|
|
15
|
+
kind: 'req';
|
|
16
|
+
/** Positive integer, unique among the card's in-flight requests. */
|
|
17
|
+
id: number;
|
|
18
|
+
method: string;
|
|
19
|
+
params?: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface ResponseMessage extends Envelope {
|
|
22
|
+
kind: 'res';
|
|
23
|
+
id: number;
|
|
24
|
+
ok: boolean;
|
|
25
|
+
result?: unknown;
|
|
26
|
+
error?: CardErrorPayload;
|
|
27
|
+
}
|
|
28
|
+
export interface EventMessage extends Envelope {
|
|
29
|
+
kind: 'evt';
|
|
30
|
+
event: string;
|
|
31
|
+
data?: unknown;
|
|
32
|
+
}
|
|
33
|
+
export type CardToHostMessage = RequestMessage;
|
|
34
|
+
export type HostToCardMessage = ResponseMessage | EventMessage;
|
|
35
|
+
/** The first message the *renderer* posts into the frame's window, with the MessagePort as transfer. */
|
|
36
|
+
export interface ConnectMessage {
|
|
37
|
+
ns: typeof PROTOCOL_TAG;
|
|
38
|
+
type: 'connect';
|
|
39
|
+
protocol: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function isConnectMessage(data: unknown): data is ConnectMessage;
|
|
42
|
+
/** Size of a message as the limits count it (JSON text length). Infinity when it cannot be serialized. */
|
|
43
|
+
export declare function messageSize(message: unknown): number;
|
|
44
|
+
export type CardMessageParse = {
|
|
45
|
+
ok: true;
|
|
46
|
+
message: RequestMessage;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
id: number | null;
|
|
50
|
+
error: CardErrorPayload;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Main's first check on everything that arrives on a card's port. Returns the
|
|
54
|
+
* request, or an error to send back (with the id when one could be read;
|
|
55
|
+
* `id: null` means drop silently — nothing to answer to).
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseCardMessage(data: unknown, maxBytes?: number): CardMessageParse;
|
|
58
|
+
/** The SDK's check on host messages (defensive: the host is trusted, but a bug should not crash the card). */
|
|
59
|
+
export declare function parseHostMessage(data: unknown): HostToCardMessage | null;
|
|
60
|
+
export declare function okResponse(id: number, result?: unknown): ResponseMessage;
|
|
61
|
+
export declare function errorResponse(id: number, code: CardErrorCode, message: string, data?: unknown): ResponseMessage;
|
|
62
|
+
export declare function eventMessage(event: string, data?: unknown): EventMessage;
|
|
63
|
+
export declare function requestMessage(id: number, method: string, params?: unknown): RequestMessage;
|
|
64
|
+
/** Token bucket used per instance for requests (and by main per port/network where the spec says so). */
|
|
65
|
+
export declare class TokenBucket {
|
|
66
|
+
private readonly perSecond;
|
|
67
|
+
private readonly capacity;
|
|
68
|
+
private tokens;
|
|
69
|
+
private last;
|
|
70
|
+
constructor(perSecond: number, capacity: number, now: number);
|
|
71
|
+
/** Takes one token; returns 0 when allowed, else the ms until one is available. */
|
|
72
|
+
take(now: number): number;
|
|
73
|
+
}
|
|
74
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export interface GithubSource {
|
|
2
|
+
type: 'github';
|
|
3
|
+
/** Lowercased — GitHub owners and repos are case-insensitive. */
|
|
4
|
+
owner: string;
|
|
5
|
+
repo: string;
|
|
6
|
+
/** Package root inside the repo (POSIX, no leading/trailing slash), case kept. */
|
|
7
|
+
subdir?: string;
|
|
8
|
+
/** Branch, tag or commit the user asked for; absent = the default branch. */
|
|
9
|
+
ref?: string;
|
|
10
|
+
/** True when the input named a release (`/releases/tag/<tag>`): updates follow the latest release. */
|
|
11
|
+
release?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DevSource {
|
|
14
|
+
type: 'dev';
|
|
15
|
+
/** Absolute folder path as main resolved it (realpath). */
|
|
16
|
+
folder: string;
|
|
17
|
+
}
|
|
18
|
+
export type PackageSource = GithubSource | DevSource;
|
|
19
|
+
export type ParseResult<T> = {
|
|
20
|
+
ok: true;
|
|
21
|
+
value: T;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
error: string;
|
|
25
|
+
};
|
|
26
|
+
/** A git ref name we are willing to put in an API URL. */
|
|
27
|
+
export declare function isSafeRef(ref: string): boolean;
|
|
28
|
+
export declare function isCommitSha(value: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Parses what a user types into "Install from GitHub":
|
|
31
|
+
*
|
|
32
|
+
* owner/repo owner/repo@v1.2.0
|
|
33
|
+
* owner/repo/cards/pomodoro owner/repo/cards/pomodoro@main
|
|
34
|
+
* github.com/owner/repo https://github.com/owner/repo.git
|
|
35
|
+
* https://github.com/owner/repo/tree/<ref>/<subdir…> (ref without slashes)
|
|
36
|
+
* https://github.com/owner/repo/releases/tag/<tag>
|
|
37
|
+
*
|
|
38
|
+
* A ref containing slashes must use the `@ref` form.
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseGithubSource(input: string): ParseResult<GithubSource>;
|
|
41
|
+
/**
|
|
42
|
+
* The package identity: `github:<owner>/<repo>[/<subdir>]` or `dev:<folder>`.
|
|
43
|
+
* Ref is deliberately not part of it — updating a package keeps its id,
|
|
44
|
+
* grants, storage and card instances.
|
|
45
|
+
*/
|
|
46
|
+
export declare function packageIdFor(source: PackageSource): string;
|
|
47
|
+
/** `https://github.com/<owner>/<repo>[/tree/<commit>/<subdir>]` — the "view source" link consent shows. */
|
|
48
|
+
export declare function githubBrowseUrl(source: GithubSource, commit?: string): string;
|
|
49
|
+
/** `https://github.com/<owner>/<repo>/compare/<from>...<to>` — what an update changes. */
|
|
50
|
+
export declare function githubCompareUrl(source: GithubSource, from: string, to: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* A path inside a package (manifest `entry`, `icon`, an archive entry after
|
|
53
|
+
* its root is stripped, a URL path the protocol serves). Relative, POSIX
|
|
54
|
+
* separators, portable to Windows, within the depth/length limits, and never
|
|
55
|
+
* inside the host's reserved `__ns/` prefix. Returns it normalized.
|
|
56
|
+
*/
|
|
57
|
+
export declare function validatePackagePath(path: string): ParseResult<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Maps one tarball entry name to a package path. GitHub tarballs put
|
|
60
|
+
* everything under `<owner>-<repo>-<shortsha>/`; that first segment is
|
|
61
|
+
* dropped, then `subdir` (when set) must prefix the rest and is dropped too.
|
|
62
|
+
*
|
|
63
|
+
* `null` — the entry is outside the package (other folders of a monorepo, the
|
|
64
|
+
* root directory entry itself): skip it silently. An error — the archive is
|
|
65
|
+
* hostile or broken: abort the whole install.
|
|
66
|
+
*/
|
|
67
|
+
export declare function archiveEntryToPackagePath(entryName: string, subdir?: string): {
|
|
68
|
+
ok: true;
|
|
69
|
+
value: string | null;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
error: string;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A path a card passes to `fs.*`: relative to the workspace folder. Accepts
|
|
76
|
+
* `''`/`'.'` (the root) and backslashes (converted); rejects anything absolute,
|
|
77
|
+
* any drive/UNC/ADS form, NUL, and `..` segments at all — even ones that
|
|
78
|
+
* would stay inside, so the check never depends on normalization order.
|
|
79
|
+
* Returns the normalized relative path (`''` = the root).
|
|
80
|
+
*
|
|
81
|
+
* This is only the first gate: main joins it to the workspace folder and
|
|
82
|
+
* then requires realpath(target) — or, for a path that does not exist yet,
|
|
83
|
+
* realpath of its nearest existing parent — to stay inside realpath(root).
|
|
84
|
+
*/
|
|
85
|
+
export declare function normalizeWorkspacePath(input: string): ParseResult<string>;
|
|
86
|
+
/**
|
|
87
|
+
* Is any segment of this path `.git`? Hooks and config in a git folder run
|
|
88
|
+
* code, and a `.git` *file* (submodule, worktree) points git at another
|
|
89
|
+
* folder, so both are refused at any depth: a workspace often holds several
|
|
90
|
+
* repositories (MC-1). Case-insensitive (Windows, macOS).
|
|
91
|
+
*/
|
|
92
|
+
export declare function isGitInternalPath(normalized: string): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Why `fs.write`/`mkdir`/`trash` must refuse this workspace-relative path, or
|
|
95
|
+
* null. Covers `.git` anywhere (MC-1) and the files and folders that make
|
|
96
|
+
* agents or developer tools run commands without a build step (MC-10): agent
|
|
97
|
+
* hooks and MCP configs (`.claude/`, `.mcp.json`, …), agent instructions
|
|
98
|
+
* (`CLAUDE.md`, `AGENTS.md`, …: persistent prompt injection), editor tasks
|
|
99
|
+
* (`.vscode/`), git hooks managers (`.husky/`), CI (`.github/workflows/`),
|
|
100
|
+
* direnv and package-manager rc files. Case-insensitive.
|
|
101
|
+
*/
|
|
102
|
+
export declare function protectedWriteReason(normalized: string): string | null;
|
|
103
|
+
/**
|
|
104
|
+
* The canonical text a package's tree hash is the sha256 of: one line per
|
|
105
|
+
* file, `<sha256 hex> <path>\n`, sorted by path (UTF-16 code unit order).
|
|
106
|
+
* Recorded at install; a reinstall of the same commit must reproduce it.
|
|
107
|
+
*/
|
|
108
|
+
export declare function treeHashInput(entries: readonly {
|
|
109
|
+
path: string;
|
|
110
|
+
sha256: string;
|
|
111
|
+
}[]): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const THEME_TOKENS: readonly ["background", "background-secondary", "background-tertiary", "foreground", "muted", "surface", "surface-foreground", "surface-secondary", "surface-secondary-foreground", "surface-tertiary", "surface-tertiary-foreground", "overlay", "overlay-foreground", "default", "default-foreground", "accent", "accent-foreground", "accent-soft", "accent-soft-foreground", "success", "success-foreground", "success-soft", "warning", "warning-foreground", "warning-soft", "danger", "danger-foreground", "danger-soft", "border", "border-secondary", "separator", "focus", "link", "field-background", "field-foreground", "field-placeholder", "field-border", "radius", "field-radius"];
|
|
2
|
+
export type ThemeToken = (typeof THEME_TOKENS)[number];
|
|
3
|
+
export interface ThemeSnapshot {
|
|
4
|
+
/** Always 'dark' today; a card must not assume it stays that way. */
|
|
5
|
+
scheme: 'dark' | 'light';
|
|
6
|
+
tokens: Record<ThemeToken, string>;
|
|
7
|
+
/** Computed font stacks of the app. The fonts themselves are not shared; cards ship their own or use system fonts. */
|
|
8
|
+
fontSans: string;
|
|
9
|
+
fontMono: string;
|
|
10
|
+
}
|
|
11
|
+
/** Header/status tones the host renders. */
|
|
12
|
+
export declare const CARD_TONES: readonly ["default", "accent", "success", "warning", "danger"];
|
|
13
|
+
export type CardTone = (typeof CARD_TONES)[number];
|
|
14
|
+
/**
|
|
15
|
+
* Icons a card may ask the *host* to draw (overview tile, menu items,
|
|
16
|
+
* status). Host UI is heroicons-only (CLAUDE.md); these are
|
|
17
|
+
* `@heroicons/react/24/outline` names in kebab case. Inside its own frame a
|
|
18
|
+
* card draws whatever it likes.
|
|
19
|
+
*/
|
|
20
|
+
export declare const HOST_ICON_NAMES: readonly ["academic-cap", "arrow-path", "beaker", "bell", "bolt", "book-open", "bug-ant", "calendar", "camera", "chart-bar", "chart-pie", "chat-bubble-left-right", "check-circle", "clock", "cloud", "code-bracket", "command-line", "cpu-chip", "cube", "currency-dollar", "document-text", "exclamation-triangle", "film", "fire", "flag", "folder", "globe-alt", "heart", "inbox", "key", "light-bulb", "link", "list-bullet", "map", "megaphone", "moon", "musical-note", "newspaper", "paper-airplane", "pause", "photo", "play", "puzzle-piece", "rocket-launch", "server", "shield-check", "signal", "sparkles", "star", "stop", "sun", "table-cells", "tag", "trash", "trophy", "users", "wrench-screwdriver"];
|
|
21
|
+
export type HostIconName = (typeof HOST_ICON_NAMES)[number];
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Major version of the wire protocol. A card built for a higher one is refused with PROTOCOL_MISMATCH. */
|
|
2
|
+
export declare const CARD_PROTOCOL_VERSION = 1;
|
|
3
|
+
/** Version of this contract (and of `@neurosquad/card-sdk`'s protocol layer). */
|
|
4
|
+
export declare const CARD_SDK_CONTRACT_VERSION = "1.0.0";
|
|
5
|
+
/** The manifest at the root of every card package. */
|
|
6
|
+
export declare const MANIFEST_FILE = "neurosquad-card.json";
|
|
7
|
+
/** `manifestVersion` this app reads. */
|
|
8
|
+
export declare const MANIFEST_VERSION = 1;
|
|
9
|
+
/** Custom protocol that serves installed packages: `nscard://<hostId>/<path>`. */
|
|
10
|
+
export declare const CARD_SCHEME = "nscard";
|
|
11
|
+
/** `ns` field of every wire message. */
|
|
12
|
+
export declare const PROTOCOL_TAG = "nscard";
|
|
13
|
+
/**
|
|
14
|
+
* Paths under this prefix are served by the host, never from the package
|
|
15
|
+
* (`/__ns/boot.js`). A package file whose path starts with it is refused at install.
|
|
16
|
+
*/
|
|
17
|
+
export declare const RESERVED_PATH_PREFIX = "__ns/";
|
|
18
|
+
/** Query parameter carrying the single-use document token (spec §4.3). */
|
|
19
|
+
export declare const FRAME_TOKEN_PARAM = "__ns";
|
|
20
|
+
/** The plugin-card kind (shared/pluginCards.ts) every custom card instance is: `Agent.harness === 'custom'`. */
|
|
21
|
+
export declare const CUSTOM_CARD_KIND = "custom";
|
|
22
|
+
/**
|
|
23
|
+
* `hostId` — the DNS label a package is served under. `p` + 26 chars of
|
|
24
|
+
* lowercase base32 (RFC 4648 alphabet, lowercased) of sha256(packageId).
|
|
25
|
+
* Main computes it; everyone else only checks the shape.
|
|
26
|
+
*/
|
|
27
|
+
export declare const CARD_HOST_ID_RE: RegExp;
|
|
28
|
+
/**
|
|
29
|
+
* Hard limits. Main enforces every one of them; the SDK checks the cheap ones
|
|
30
|
+
* early to give authors a readable error. Changing a limit is a contract change.
|
|
31
|
+
*/
|
|
32
|
+
export declare const LIMITS: {
|
|
33
|
+
/** Serialized size of one message (JSON length in UTF-16 code units, as measured by JSON.stringify). */
|
|
34
|
+
readonly messageBytes: number;
|
|
35
|
+
/** Responses of fs.read / net.fetch may be this large. */
|
|
36
|
+
readonly largeMessageBytes: number;
|
|
37
|
+
readonly inFlightRequests: 64;
|
|
38
|
+
/** Token bucket per instance: refill per second / bucket size. */
|
|
39
|
+
readonly requestsPerSecond: 200;
|
|
40
|
+
readonly requestBurst: 400;
|
|
41
|
+
readonly valueDepth: 64;
|
|
42
|
+
readonly valueNodes: 200000;
|
|
43
|
+
readonly tarballBytes: number;
|
|
44
|
+
readonly unpackedBytes: number;
|
|
45
|
+
readonly fileBytes: number;
|
|
46
|
+
readonly files: 5000;
|
|
47
|
+
readonly pathDepth: 20;
|
|
48
|
+
readonly pathChars: 240;
|
|
49
|
+
readonly manifestBytes: number;
|
|
50
|
+
readonly iconBytes: number;
|
|
51
|
+
readonly storageKeyChars: 256;
|
|
52
|
+
readonly storageValueBytes: number;
|
|
53
|
+
readonly storageKeys: 10000;
|
|
54
|
+
readonly instanceStorageBytes: number;
|
|
55
|
+
readonly packageStorageBytes: number;
|
|
56
|
+
readonly settings: 40;
|
|
57
|
+
readonly portsPerDirection: 16;
|
|
58
|
+
readonly toolsPerPackage: 32;
|
|
59
|
+
readonly permissionHosts: 32;
|
|
60
|
+
readonly schemaNodes: 500;
|
|
61
|
+
readonly schemaDepth: 16;
|
|
62
|
+
/** `enum` options per schema node, and their serialized size (MC-2: comparisons cost main CPU). */
|
|
63
|
+
readonly schemaEnumOptions: 256;
|
|
64
|
+
readonly schemaEnumBytes: number;
|
|
65
|
+
/** Serialized size of one `const` value in a card schema. */
|
|
66
|
+
readonly schemaConstBytes: number;
|
|
67
|
+
readonly menuItems: 12;
|
|
68
|
+
readonly netRequestBodyBytes: number;
|
|
69
|
+
readonly netResponseBytes: number;
|
|
70
|
+
readonly netConcurrent: 6;
|
|
71
|
+
readonly netPerMinute: 120;
|
|
72
|
+
readonly netRedirects: 5;
|
|
73
|
+
readonly netTimeoutMs: 30000;
|
|
74
|
+
readonly netMaxTimeoutMs: 120000;
|
|
75
|
+
readonly netStreams: 4;
|
|
76
|
+
readonly fsReadBytes: number;
|
|
77
|
+
readonly fsWriteBytes: number;
|
|
78
|
+
readonly fsListEntries: 5000;
|
|
79
|
+
readonly fsWatchers: 20;
|
|
80
|
+
readonly promptChars: 20000;
|
|
81
|
+
readonly promptsPerMinute: 6;
|
|
82
|
+
readonly terminalWriteChars: 20000;
|
|
83
|
+
readonly terminalRunTimeoutMs: 120000;
|
|
84
|
+
readonly screenLines: 500;
|
|
85
|
+
readonly outputFlushMs: 100;
|
|
86
|
+
readonly outputFlushBytes: number;
|
|
87
|
+
readonly toolResultBytes: number;
|
|
88
|
+
readonly toolTimeoutMs: 30000;
|
|
89
|
+
readonly toolMaxTimeoutMs: 120000;
|
|
90
|
+
readonly portMessagesPerSecond: 20;
|
|
91
|
+
readonly retainedValueBytes: number;
|
|
92
|
+
readonly portRequestTimeoutMs: 30000;
|
|
93
|
+
readonly attentionIntervalMs: 10000;
|
|
94
|
+
readonly focusIntervalMs: 5000;
|
|
95
|
+
readonly toastIntervalMs: 2000;
|
|
96
|
+
readonly clipboardIntervalMs: 1000;
|
|
97
|
+
readonly clipboardChars: number;
|
|
98
|
+
readonly spawnsPerInstance: 4;
|
|
99
|
+
readonly resizeIntervalMs: 500;
|
|
100
|
+
readonly titleChars: 120;
|
|
101
|
+
/** card.setStatus / setBadge / setOverview per minute (the SDK client coalesces to stay under it). */
|
|
102
|
+
readonly chromeUpdatesPerMinute: 120;
|
|
103
|
+
readonly statusChars: 80;
|
|
104
|
+
readonly overviewChars: 160;
|
|
105
|
+
readonly toastChars: 280;
|
|
106
|
+
readonly confirmChars: 1000;
|
|
107
|
+
readonly logLineChars: 2000;
|
|
108
|
+
readonly logLinesPerSecond: 50;
|
|
109
|
+
/** Card frames alive at once across the app; beyond it the least recently seen offscreen/hidden ones are suspended. */
|
|
110
|
+
readonly liveFrames: 24;
|
|
111
|
+
/** Of those, how many may be kept alive while hidden thanks to the `background` permission. */
|
|
112
|
+
readonly backgroundFrames: 8;
|
|
113
|
+
/** A card in a hidden workspace is suspended after this long (unless `background`). */
|
|
114
|
+
readonly hiddenSuspendMs: 60000;
|
|
115
|
+
/** Time a card gets between `lifecycle.suspend` and its frame being unloaded. */
|
|
116
|
+
readonly suspendGraceMs: 1000;
|
|
117
|
+
/** Single-use document token lifetime. */
|
|
118
|
+
readonly frameTokenTtlMs: 30000;
|
|
119
|
+
/** From frame attach to `host.ready`; after that the card shows "not responding". */
|
|
120
|
+
readonly readyTimeoutMs: 10000;
|
|
121
|
+
readonly heartbeatMs: 5000;
|
|
122
|
+
readonly unresponsiveMs: 15000;
|
|
123
|
+
/** Mounting at most this many card frames per animation frame (canvas perf). */
|
|
124
|
+
readonly frameMountsPerFrame: 2;
|
|
125
|
+
};
|
|
126
|
+
export type CardLimits = typeof LIMITS;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CardLanguage, I18nSnapshot } from './contract/index.js';
|
|
2
|
+
/** Nested dictionary of strings. Keys are joined with dots: `{ list: { empty: '…' } }` → `list.empty`. */
|
|
3
|
+
export interface Messages {
|
|
4
|
+
[key: string]: string | Messages;
|
|
5
|
+
}
|
|
6
|
+
/** Translations per app language; `en` is required and is the fallback. */
|
|
7
|
+
export type Catalog = {
|
|
8
|
+
en: Messages;
|
|
9
|
+
} & Partial<Record<Exclude<CardLanguage, 'en'>, Messages>>;
|
|
10
|
+
/** Values for `{{name}}` placeholders. `count` also picks plural forms. */
|
|
11
|
+
export type TranslateValues = Record<string, string | number | boolean | null | undefined>;
|
|
12
|
+
/** Something that reports the app language — a `Card` fits. */
|
|
13
|
+
export interface LanguageSource {
|
|
14
|
+
readonly i18n: I18nSnapshot;
|
|
15
|
+
on(event: 'i18n.changed', handler: (snapshot: I18nSnapshot) => void): () => void;
|
|
16
|
+
}
|
|
17
|
+
/** The function {@link createTranslator} returns, with a few extras. */
|
|
18
|
+
export interface Translator {
|
|
19
|
+
/**
|
|
20
|
+
* The text for `key` in the current language (falling back to English,
|
|
21
|
+
* then to the key itself), with `{{name}}` placeholders filled in.
|
|
22
|
+
*
|
|
23
|
+
* Plurals: with `values.count`, `key_one`/`key_few`/`key_many`/`key_other`
|
|
24
|
+
* (chosen by `Intl.PluralRules`) are tried before `key` — the same
|
|
25
|
+
* convention as the app (ru has one/few/many/other, zh only other).
|
|
26
|
+
*/
|
|
27
|
+
(key: string, values?: TranslateValues): string;
|
|
28
|
+
/** Current language. */
|
|
29
|
+
readonly language: CardLanguage;
|
|
30
|
+
/** BCP 47 locale for `Intl` formatting. */
|
|
31
|
+
readonly locale: string;
|
|
32
|
+
/** Switches language by hand (normally it follows the card). */
|
|
33
|
+
setLanguage(language: CardLanguage, locale?: string): void;
|
|
34
|
+
/** Called after every language change. */
|
|
35
|
+
onChange(listener: (language: CardLanguage) => void): () => void;
|
|
36
|
+
/** Stops following the card's language. */
|
|
37
|
+
dispose(): void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Creates a translator. Pass the card (or anything with `i18n` and
|
|
41
|
+
* `on('i18n.changed')`) to follow the app's language live.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const t = createTranslator({
|
|
45
|
+
* en: { title: 'Tests', failed_one: '{{count}} test failed', failed_other: '{{count}} tests failed' },
|
|
46
|
+
* ru: { title: 'Тесты', failed_one: '{{count}} тест упал', failed_few: '{{count}} теста упало', failed_many: '{{count}} тестов упало', failed_other: '{{count}} теста упало' }
|
|
47
|
+
* }, card)
|
|
48
|
+
* t('failed', { count: 3 })
|
|
49
|
+
*/
|
|
50
|
+
export declare function createTranslator(catalog: Catalog, source?: LanguageSource): Translator;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@neurosquad/card-sdk` — build custom cards for NeuroSquad.
|
|
3
|
+
*
|
|
4
|
+
* A card is a small web app that runs in a sandboxed frame on the canvas.
|
|
5
|
+
* `connect()` gives it a typed {@link Card}: agents, typed ports between
|
|
6
|
+
* cards, tools for connected agents, storage, settings, network through the
|
|
7
|
+
* host's proxy, workspace files, theme and language.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { connect } from '@neurosquad/card-sdk'
|
|
11
|
+
*
|
|
12
|
+
* const card = await connect()
|
|
13
|
+
* card.setStatus('Hello from my card', { tone: 'success' })
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
export { connect, type ConnectOptions, type HandshakeWindow } from './client/connect.js';
|
|
18
|
+
export { Card, CardAgents, CardFs, CardLifecycle, CardNet, CardPermissions, CardPorts, CardSettings, CardTerminals, CardTools, CardUi, type CallArgs, type CardLog, type CardMenuItem, type CardOptions, type CardStorage, type EventHandler, type PortRequest, type PortRequestHandler, type ScopedStorage, type StorageScope, type ToolCall, type ToolHandler, type ToolReturn, type Unsubscribe, type Widen } from './client/card.js';
|
|
19
|
+
export { LARGE_REQUEST_METHODS, type CallOptions, type CardPort } from './client/channel.js';
|
|
20
|
+
export { CardSdkError, isCardSdkError } from './client/errors.js';
|
|
21
|
+
export { CardHeaders, CardResponse, type CardFetchInit, type NetResponseType } from './client/net.js';
|
|
22
|
+
export { applyTheme, DEFAULT_DARK_THEME, type ThemeTarget } from './client/theme.js';
|
|
23
|
+
export { base64ToBytes, bytesToBase64 } from './client/base64.js';
|
|
24
|
+
export { toolError, toolImage, toolText } from './client/toolResult.js';
|
|
25
|
+
export { hasDownstreamPeer, permissionForPeer, requestPermissions } from './client/helpers.js';
|
|
26
|
+
export { createTranslator, type Catalog, type LanguageSource, type Messages, type TranslateValues, type Translator } from './i18n.js';
|
|
27
|
+
export { SDK_VERSION } from './version.js';
|
|
28
|
+
export * from './contract/index.js';
|