@vekil/app-sdk 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.
@@ -0,0 +1,223 @@
1
+ # App Definition
2
+
3
+ An App Definition describes what a Remote App offers, when it should be used,
4
+ which information it needs, and what results it can return.
5
+
6
+ Use the exported TypeScript types while authoring. Use `vekil-app schema` when
7
+ an editor or another tool needs JSON Schema. Both represent the same strict
8
+ contract.
9
+
10
+ ## Choose a starting point
11
+
12
+ `createRemoteAppStarterDefinition` creates a minimal read-only App with one
13
+ intent, capability, action, and outcome. It is useful for validating a new
14
+ Runtime and learning the release workflow.
15
+
16
+ `defineRemoteApp` accepts the complete `AppDefinitionInput`:
17
+
18
+ ```ts
19
+ import { defineRemoteApp, type AppDefinitionInput } from "@vekil/app-sdk";
20
+
21
+ const input = {
22
+ // Complete strict Definition.
23
+ } satisfies AppDefinitionInput;
24
+
25
+ export default defineRemoteApp(input);
26
+ ```
27
+
28
+ Unknown fields are rejected. Defaults are normalized during parsing, so build
29
+ and import the value returned by `defineRemoteApp`.
30
+
31
+ ## Top-level structure
32
+
33
+ | Field | Purpose |
34
+ | ------------------- | ----------------------------------------------------------------------- |
35
+ | `definitionVersion` | Selects the supported Definition contract. |
36
+ | `defaultLocale` | Fallback locale for App-owned copy. |
37
+ | `app` | Listing name, descriptions, category, optional icon, and provider name. |
38
+ | `publication` | Optional website, support email, privacy policy, and terms. |
39
+ | `connection` | Explains account connection and requested provider permissions. |
40
+ | `intents` | Requests the App understands and the information each request needs. |
41
+ | `capabilities` | User-visible abilities backed by one or more actions. |
42
+ | `actions` | Typed operations the Runtime can plan and execute. |
43
+ | `outcomes` | Typed results and allowed continuations. |
44
+ | `artifactContracts` | Versioned data passed between planned actions. |
45
+ | `settings` | Values configured separately by each installer. |
46
+ | `policies` | Installer-controlled approval behavior. |
47
+ | `knowledge` | Installed information available for answering and planning. |
48
+ | `context` | Requested App Context scopes and their disclosure copy. |
49
+ | `publicActions` | Safe quick starts shown on a public Vekil page. |
50
+ | `customRules` | App-specific rule families and examples. |
51
+ | `runtime` | Runtime base URL, JWKS URL, and connection requirement. |
52
+
53
+ The generated schema contains every field constraint:
54
+
55
+ ```bash
56
+ vekil-app schema ./app-definition.schema.json
57
+ ```
58
+
59
+ ## Local keys
60
+
61
+ Use stable local keys such as `find-options`, `create-record`, and
62
+ `record-created` to connect Definition entries.
63
+
64
+ Good keys are:
65
+
66
+ - lowercase kebab-case;
67
+ - descriptive of behavior rather than UI copy;
68
+ - stable when labels or descriptions change;
69
+ - unique within their collection.
70
+
71
+ The Runtime resolves these keys with `createAppRuntimeBindings`. Business code
72
+ should not construct runtime references by concatenating strings.
73
+
74
+ ## Localized copy
75
+
76
+ App-specific interface copy belongs in the Definition:
77
+
78
+ ```ts
79
+ const title = {
80
+ en: "Find available options",
81
+ fr: "Trouver des options disponibles"
82
+ };
83
+ ```
84
+
85
+ Use valid locale codes as keys. Every required localized field must include the
86
+ `defaultLocale`. Translate meaning, while keeping keys and schemas stable.
87
+
88
+ ## Intents, capabilities, actions, and outcomes
89
+
90
+ These layers answer different questions:
91
+
92
+ | Layer | Question |
93
+ | ---------- | --------------------------------------------- |
94
+ | Intent | What is the requester trying to accomplish? |
95
+ | Capability | What useful ability does the App provide? |
96
+ | Action | What typed operation can the Runtime perform? |
97
+ | Outcome | What happened, and may the request continue? |
98
+
99
+ ### Intents
100
+
101
+ An intent declares:
102
+
103
+ - positive and negative examples;
104
+ - required and optional fields;
105
+ - questions for missing information;
106
+ - supported conversation origins;
107
+ - related capabilities and actions;
108
+ - risk and requester-review behavior;
109
+ - response guidance.
110
+
111
+ Examples teach recognition. They are not scripted replies and requesters do not
112
+ need to repeat them exactly.
113
+
114
+ Choose the intent mode by outcome:
115
+
116
+ - `ANSWER` returns information without creating a submission;
117
+ - `COLLECT` gathers a complete structured request;
118
+ - `EXECUTE` plans and performs a Runtime action.
119
+
120
+ ### Capabilities
121
+
122
+ A capability is the installer-visible promise made by the App. Group actions
123
+ only when they form one coherent ability, and describe the result in ordinary
124
+ language.
125
+
126
+ ### Actions
127
+
128
+ An action owns:
129
+
130
+ - input and output JSON Schema;
131
+ - side-effect and risk level;
132
+ - approval behavior;
133
+ - required settings, policies, and context;
134
+ - timeout, retry, and idempotency rules;
135
+ - possible outcomes and artifacts.
136
+
137
+ Use the narrowest input and context needed. A read-only planning operation must
138
+ not perform a write.
139
+
140
+ ### Outcomes
141
+
142
+ An outcome represents a typed result. Add a continuation only when the same
143
+ request genuinely remains open, such as after presenting a bounded choice of
144
+ available options.
145
+
146
+ ## Settings and policies
147
+
148
+ Settings are configured separately for every installation. The same App can
149
+ therefore serve different people or organizations without separate releases.
150
+
151
+ Common settings include:
152
+
153
+ - a selected workspace or calendar;
154
+ - working days and hours;
155
+ - a destination queue;
156
+ - public information supplied by the installer.
157
+
158
+ Policies control approval and supported behavior. They should be explicit
159
+ installer choices rather than hidden prompt text.
160
+
161
+ Do not put provider credentials in settings. OAuth clients, access tokens, API
162
+ keys, and webhook secrets belong in the Remote Runtime's credential store.
163
+
164
+ ## App Context
165
+
166
+ Each action requests the minimum context it needs. Context is scoped to one
167
+ authorized operation and can be absent or expire.
168
+
169
+ Treat optional context as unavailable when it is not supplied. Never infer
170
+ permission from an undeclared field or ask for broad context as a convenience.
171
+
172
+ ## Artifacts
173
+
174
+ Artifacts are typed values produced by one action and consumed by a later
175
+ planned action. Declare:
176
+
177
+ - a stable local key;
178
+ - schema version and JSON Schema;
179
+ - sensitivity level;
180
+ - allowed audiences;
181
+ - producer and consumer actions;
182
+ - expected cardinality.
183
+
184
+ Read action input and reserved artifacts separately:
185
+
186
+ ```ts
187
+ import { readAppActionInput, readAppContextArtifactInputs } from "@vekil/app-sdk";
188
+
189
+ const input = inputSchema.parse(readAppActionInput(request.input));
190
+ const artifacts = readAppContextArtifactInputs(request.input);
191
+ ```
192
+
193
+ Return requester-visible data only when it is safe independently of the chat
194
+ copy around it.
195
+
196
+ ## Public actions
197
+
198
+ Public actions become concise quick starts on a Vekil page. Each one must map
199
+ to a declared intent and capability and accurately describe work the installed
200
+ App can handle.
201
+
202
+ Keep the list short and concrete. A quick start begins a conversation; normal
203
+ clarification, verification, approval, and execution rules still apply.
204
+
205
+ ## Publication metadata
206
+
207
+ Website, privacy policy, terms, and support email are optional. Add only links
208
+ that are live and maintained. Developer name, handle, and social links come
209
+ from the Builder profile and do not belong in the Definition.
210
+
211
+ ## Validate before import
212
+
213
+ ```bash
214
+ vekil-app validate ./vekil.app.mjs
215
+ vekil-app conformance ./vekil.app.mjs
216
+ vekil-app build ./vekil.app.mjs --out ./dist
217
+ ```
218
+
219
+ `validate` checks schema and semantic references. `conformance` adds Remote
220
+ Runtime requirements. `build` writes canonical JSON and the matching schema.
221
+
222
+ Import `dist/definition.json` into Builder. Builder performs the final test with
223
+ the deployed Runtime before an App can be released.
package/docs/cli.md ADDED
@@ -0,0 +1,163 @@
1
+ # CLI reference
2
+
3
+ The `vekil-app` CLI creates and validates Remote App projects with the same
4
+ contracts exported by the SDK.
5
+
6
+ Run it through a project script or directly:
7
+
8
+ ```bash
9
+ pnpm vekil-app help
10
+ pnpm dlx @vekil/app-sdk help
11
+ ```
12
+
13
+ Commands write normal results to standard output, validation issues to standard
14
+ error, and return a non-zero exit code on failure.
15
+
16
+ ## `init`
17
+
18
+ Create a fail-closed Remote App starter:
19
+
20
+ ```bash
21
+ vekil-app init <directory> [--name <name>] [--runtime-url <url>]
22
+ ```
23
+
24
+ - The target directory must be empty.
25
+ - The default Runtime URL is `http://localhost:4100`.
26
+ - Deployed URLs must use HTTPS; HTTP is accepted only on localhost.
27
+ - The generated package is private and pins the current SDK minor line.
28
+ - Planning and execution remain disabled until you implement the handlers.
29
+
30
+ Example:
31
+
32
+ ```bash
33
+ pnpm dlx @vekil/app-sdk init calendar-runtime \
34
+ --name "Calendar" \
35
+ --runtime-url http://localhost:4100
36
+ ```
37
+
38
+ ## `validate`
39
+
40
+ Parse and validate a Definition:
41
+
42
+ ```bash
43
+ vekil-app validate <definition.json|module.mjs>
44
+ ```
45
+
46
+ JavaScript modules may export the Definition as `default` or as a named
47
+ `definition` value. Validation rejects unknown fields, invalid schemas,
48
+ duplicate local keys, broken references, unsafe context, and inconsistent
49
+ approval or artifact contracts.
50
+
51
+ ## `conformance`
52
+
53
+ Run Definition checks and require a Remote Runtime declaration:
54
+
55
+ ```bash
56
+ vekil-app conformance <definition.json|module.mjs>
57
+ ```
58
+
59
+ Current local checks cover:
60
+
61
+ - strict Definition validity;
62
+ - deterministic canonical JSON;
63
+ - Remote Runtime requirements.
64
+
65
+ Builder performs the deployed Runtime test separately. Local conformance is not
66
+ a replacement for that test or for a security review.
67
+
68
+ ## `build`
69
+
70
+ Write the files needed for Builder import:
71
+
72
+ ```bash
73
+ vekil-app build <definition.json|module.mjs> [--out <directory>]
74
+ ```
75
+
76
+ The default output directory is `dist`:
77
+
78
+ ```text
79
+ dist/
80
+ ├── app-definition.schema.json
81
+ └── definition.json
82
+ ```
83
+
84
+ Import `definition.json` into a Remote App project. Do not hand-edit the built
85
+ JSON; change the source Definition and rebuild it.
86
+
87
+ ## `inspect`
88
+
89
+ Print a concise JSON summary:
90
+
91
+ ```bash
92
+ vekil-app inspect <definition.json|module.mjs>
93
+ ```
94
+
95
+ The output includes the public name, locale, category, connection type,
96
+ capability keys, publication links, and Runtime endpoints. It is useful in CI
97
+ and release reviews.
98
+
99
+ ## `schema`
100
+
101
+ Print or write the App Definition JSON Schema:
102
+
103
+ ```bash
104
+ vekil-app schema
105
+ vekil-app schema ./app-definition.schema.json
106
+ ```
107
+
108
+ Use the generated file for editors, CI validation, and tooling. Regenerate it
109
+ after changing the SDK version.
110
+
111
+ ## `keys`
112
+
113
+ Generate Runtime signing keys from the manifest downloaded from Builder:
114
+
115
+ ```bash
116
+ vekil-app keys [manifest.json] [--key-id <key-id>]
117
+ ```
118
+
119
+ The default path is `./vekil.manifest.json`.
120
+
121
+ The command prints:
122
+
123
+ - `VEKIL_APP_SIGNING_KEY_ID`;
124
+ - `VEKIL_APP_SIGNING_PRIVATE_JWK_BASE64URL`;
125
+ - the public JWKS document.
126
+
127
+ Store the private values in a secret manager and publish only the JWKS.
128
+ Redirecting command output to a committed file can leak the private key.
129
+
130
+ ## Generated project scripts
131
+
132
+ The starter includes:
133
+
134
+ ```json
135
+ {
136
+ "scripts": {
137
+ "build": "vekil-app build ./vekil.app.mjs --out ./dist",
138
+ "conformance": "vekil-app conformance ./vekil.app.mjs",
139
+ "dev": "node --env-file=.env runtime.mjs",
140
+ "keys": "vekil-app keys ./vekil.manifest.json",
141
+ "validate": "vekil-app validate ./vekil.app.mjs"
142
+ }
143
+ }
144
+ ```
145
+
146
+ Recommended CI sequence:
147
+
148
+ ```bash
149
+ pnpm install --frozen-lockfile
150
+ pnpm validate
151
+ pnpm conformance
152
+ pnpm build
153
+ git diff --exit-code -- dist/definition.json dist/app-definition.schema.json
154
+ ```
155
+
156
+ Commit generated artifacts only when the App repository's release policy
157
+ requires it. Always generate them with the SDK version locked by the project.
158
+
159
+ ## Operations handled in Builder
160
+
161
+ The CLI does not sign in to Vekil, create or publish App releases, deploy Runtime
162
+ code, or manage provider credentials. Use Builder for App testing and release;
163
+ use your own deployment and secret-management systems for the Runtime.
@@ -0,0 +1,256 @@
1
+ # Remote Runtime
2
+
3
+ A Remote Runtime is the HTTPS service that implements an App's typed actions.
4
+ The SDK verifies incoming Vekil requests before your handlers run and signs the
5
+ responses returned by those handlers.
6
+
7
+ Your Runtime remains responsible for provider APIs, credentials, business
8
+ rules, data storage, and operational reliability.
9
+
10
+ ## Recommended project structure
11
+
12
+ ```text
13
+ src/
14
+ ├── actions/
15
+ │ ├── execute.ts
16
+ │ └── plan.ts
17
+ ├── connection/
18
+ │ ├── complete.ts
19
+ │ ├── disconnect.ts
20
+ │ └── prepare.ts
21
+ ├── provider/
22
+ │ ├── client.ts
23
+ │ ├── mapper.ts
24
+ │ └── token-vault.ts
25
+ ├── runtime/
26
+ │ ├── bindings.ts
27
+ │ ├── handlers.ts
28
+ │ ├── replay-store.ts
29
+ │ └── server.ts
30
+ └── index.ts
31
+ vekil.app.mjs
32
+ vekil.manifest.json
33
+ ```
34
+
35
+ Keep provider code separate from protocol adapters. Handlers should translate
36
+ between typed App operations and provider-specific calls.
37
+
38
+ ## Runtime manifest
39
+
40
+ Builder provides `vekil.manifest.json` when a test release is prepared. Keep
41
+ that file with the matching Runtime deployment and load it at startup:
42
+
43
+ ```ts
44
+ import manifest from "../vekil.manifest.json" with { type: "json" };
45
+ import { createAppRuntimeBindings } from "@vekil/app-sdk/runtime";
46
+
47
+ export const bindings = createAppRuntimeBindings(manifest);
48
+ ```
49
+
50
+ The SDK validates the manifest and rejects local keys that are not part of the
51
+ prepared release. Download a fresh manifest whenever the Definition changes.
52
+
53
+ ## Implement the handlers
54
+
55
+ Provide all methods in `AppRuntimeV1Handlers`:
56
+
57
+ | Handler | Responsibility |
58
+ | ------------------------ | ----------------------------------------------------------------- |
59
+ | `prepareInstallation` | Start a connection flow or report that no connection is required. |
60
+ | `completeInstallation` | Verify the callback and return normalized connection status. |
61
+ | `disconnectInstallation` | Revoke provider access and App-owned connection state. |
62
+ | `planAction` | Check feasibility and return bounded typed actions or questions. |
63
+ | `executeAction` | Perform one approved action idempotently and return its result. |
64
+ | `health` | Report whether the current deployment can safely accept work. |
65
+
66
+ Protocol envelopes are validated only after signature, timestamp, audience,
67
+ nonce, and schema checks pass. Your handlers must still validate App-owned
68
+ fields before using them.
69
+
70
+ Use enums exported by `@vekil/app-sdk` instead of free-form status strings.
71
+
72
+ ## Start the server
73
+
74
+ ```ts
75
+ import { createRemoteAppRuntime } from "@vekil/app-sdk/runtime";
76
+
77
+ const runtime = createRemoteAppRuntime({
78
+ handlers,
79
+ manifest,
80
+ platformJwksUrl: process.env.VEKIL_PLATFORM_JWKS_URL!,
81
+ replayStore,
82
+ signingIdentity
83
+ });
84
+ ```
85
+
86
+ For Fetch-compatible hosts:
87
+
88
+ ```ts
89
+ export default {
90
+ fetch(request: Request): Promise<Response> {
91
+ return runtime.handle(request);
92
+ }
93
+ };
94
+ ```
95
+
96
+ For Node HTTP:
97
+
98
+ ```ts
99
+ import { createServer } from "node:http";
100
+
101
+ createServer((request, response) => {
102
+ void runtime.handleNodeRequest(request, response);
103
+ }).listen(Number(process.env.PORT ?? 4100));
104
+ ```
105
+
106
+ Both adapters preserve the signed raw body, reject bodies over 2 MiB, and serve
107
+ the public JWKS endpoint declared by the App.
108
+
109
+ ## Generate signing keys
110
+
111
+ Generate an Ed25519 key pair from the manifest downloaded from Builder:
112
+
113
+ ```bash
114
+ pnpm vekil-app keys ./vekil.manifest.json
115
+ ```
116
+
117
+ The command prints two private environment values and a public JWKS document.
118
+ Store the private values in a secret manager. Publish only the JWKS.
119
+
120
+ Load the private values without handling protocol issuers yourself:
121
+
122
+ ```ts
123
+ import { readAppRuntimeSigningIdentity } from "@vekil/app-sdk/runtime";
124
+
125
+ const signingIdentity = readAppRuntimeSigningIdentity({
126
+ manifest,
127
+ keyId: process.env.VEKIL_APP_SIGNING_KEY_ID,
128
+ privateJwkBase64url: process.env.VEKIL_APP_SIGNING_PRIVATE_JWK_BASE64URL
129
+ });
130
+
131
+ if (!signingIdentity) {
132
+ throw new Error("Runtime signing configuration is incomplete.");
133
+ }
134
+ ```
135
+
136
+ For rotation, publish both old and new public keys, deploy the new private key,
137
+ wait through the accepted signature window, and then remove the old public key.
138
+ Never expose a private JWK through the JWKS route.
139
+
140
+ ## Add replay protection
141
+
142
+ Every production Runtime needs an atomic durable replay store:
143
+
144
+ ```ts
145
+ import type { AppSignatureReplayClaim, AppSignatureReplayStore } from "@vekil/app-sdk/runtime";
146
+
147
+ export class DurableReplayStore implements AppSignatureReplayStore {
148
+ async claim(input: AppSignatureReplayClaim): Promise<boolean> {
149
+ // Atomically claim this signed request until input.expiresAt.
150
+ // Return false if it was already claimed.
151
+ return true;
152
+ }
153
+ }
154
+ ```
155
+
156
+ Use a database uniqueness constraint or an atomic cache operation with expiry.
157
+ A read followed by a separate write is unsafe under concurrency.
158
+
159
+ `createDevelopmentReplayStore()` is process-local and exists only for local
160
+ development and tests.
161
+
162
+ ## Connect provider accounts
163
+
164
+ When an App needs OAuth or another provider login:
165
+
166
+ 1. `prepareInstallation` creates an authorization attempt bound to the signed
167
+ installation request and return URL.
168
+ 2. The user authorizes directly with the provider.
169
+ 3. The provider redirects to your Runtime callback.
170
+ 4. The Runtime verifies state, exchanges the code, and stores provider tokens
171
+ in its own encrypted credential store.
172
+ 5. `completeInstallation` consumes a one-time completion reference and returns
173
+ normalized connection status to Vekil.
174
+
175
+ Provider client secrets, access tokens, refresh tokens, webhook secrets, and
176
+ raw provider account payloads must never be returned to Vekil.
177
+
178
+ Bind authorization attempts and stored credentials to the exact installation
179
+ and current authorization grant. Reject stale callbacks, reused completion
180
+ references, account switches, and permission downgrades.
181
+
182
+ ## Plan before executing
183
+
184
+ `planAction` is read-only. It may:
185
+
186
+ - validate provider reachability;
187
+ - identify missing information;
188
+ - return bounded choices;
189
+ - propose declared actions;
190
+ - provide typed grounding.
191
+
192
+ It must not perform the requested side effect.
193
+
194
+ `executeAction` receives one authorized action, App Context, idempotency key,
195
+ and attempt number. It must:
196
+
197
+ - validate App-owned input;
198
+ - recheck provider preconditions immediately before a write;
199
+ - use the supplied idempotency key for every side effect;
200
+ - honor cancellation, timeout, and retry semantics;
201
+ - return only declared outcomes, artifacts, and Bubbles;
202
+ - classify retryable and non-retryable errors accurately.
203
+
204
+ Return structured facts and results rather than an unbounded chat transcript.
205
+ Vekil turns those results into a response and can continue the conversation.
206
+
207
+ ## Read App Context and artifacts
208
+
209
+ App Context is scoped to one action and one authorization grant. Use only the
210
+ scopes declared by that action. Treat a missing optional scope as unavailable.
211
+
212
+ Reserved artifacts are available under `$artifacts`:
213
+
214
+ ```ts
215
+ import { readAppActionInput, readAppContextArtifactInputs } from "@vekil/app-sdk";
216
+
217
+ const input = inputSchema.parse(readAppActionInput(request.input));
218
+ const artifacts = readAppContextArtifactInputs(request.input);
219
+ ```
220
+
221
+ Consume only artifact contracts declared by the action. Preserve provenance and
222
+ sensitivity, and never widen visibility in Runtime code.
223
+
224
+ ## Report health
225
+
226
+ Use the SDK helper so the response always matches the loaded manifest:
227
+
228
+ ```ts
229
+ import { AppRuntimeHealthStatus } from "@vekil/app-sdk";
230
+ import { createAppRuntimeHealthResponse } from "@vekil/app-sdk/runtime";
231
+
232
+ const health = async () =>
233
+ createAppRuntimeHealthResponse({
234
+ manifest,
235
+ status: AppRuntimeHealthStatus.HEALTHY
236
+ });
237
+ ```
238
+
239
+ Return `HEALTHY` only when the deployment can safely accept all declared work.
240
+ Use `DEGRADED` when the process is reachable but a required dependency or
241
+ configuration is unavailable.
242
+
243
+ ## Production checklist
244
+
245
+ - Deploy the Runtime and JWKS endpoints over HTTPS.
246
+ - Deploy the exact manifest tested in Builder.
247
+ - Store signing keys and provider credentials outside source control.
248
+ - Use a durable atomic replay store with expiry.
249
+ - Persist idempotency records and provider write results.
250
+ - Keep outbound timeouts below the declared action timeout.
251
+ - Redact credentials, signed bodies, personal data, and App Context from logs.
252
+ - Emit request, execution, installation, and trace references safely.
253
+ - Test duplicate delivery, retry, timeout, cancellation, and stale grants.
254
+ - Test key rotation, token refresh, revocation, account switch, and scope change.
255
+ - Keep database migrations and rollback behavior release-specific.
256
+ - Run Builder's Runtime test before private installation or submission.