@tapi-dev/sdk 0.1.34 → 0.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,57 +55,86 @@ Useful commands:
55
55
  ```bash
56
56
  tapi init --project brokerage
57
57
  tapi link --project brokerage
58
- tapi studio
59
- tapi service install --channel pilot
60
- tapi service status
61
- tapi apis generate
62
- tapi sessions
63
- tapi publish
58
+ tapi studio
59
+ tapi service install --channel pilot
60
+ tapi service status
61
+ tapi services describe schwab.place_order
62
+ tapi tapp create brokerage
63
+ tapi queue create "John"
64
+ tapi tapp queue add brokerage queue_abc123
65
+ tapi runner setup --runner-id john-laptop --project brokerage
66
+ tapi runner slot set john-laptop 1 queue_abc123 --project brokerage
67
+ tapi tapp service add brokerage schwab.place_order --service-map sm_123 --entry place_order
68
+ tapi triggers sync
69
+ tapi sessions
70
+ ```
71
+
72
+ Local project identity lives in the app repo:
73
+
74
+ ```text
75
+ .tapi/project.json
76
+ .tapi/services/catalog.json
64
77
  ```
65
78
 
66
- Local project identity lives in the app repo:
79
+ ServiceMaps and service-run contracts are saved in the bound Tapi server
80
+ project. Runtime SDK calls read the service catalog from that project.
67
81
 
68
- ```text
69
- .tapi/project.json
70
- .tapi/generated/catalog.json
71
- src/tapi.generated.ts
82
+ ```bash
83
+ TAPI_API_KEY=tapi_project_key tapi tapp create brokerage --name "Brokerage"
84
+ TAPI_API_KEY=tapi_project_key tapi tapp service add brokerage schwab.place_order \
85
+ --service-map sm_123 \
86
+ --entry place_order
72
87
  ```
73
88
 
74
- Sitemaps and API contracts are saved in the bound Tapi server project. Run
75
- `tapi publish` to mark ready server-side requests as published and activate one
76
- immutable server release. Runtime SDK calls read the active release catalog, not
77
- mutable Studio drafts.
78
-
79
- ```bash
80
- TAPI_API_KEY=tapi_project_key tapi publish
81
- TAPI_API_KEY=tapi_project_key tapi apis generate
82
- ```
89
+ Create a queue, attach it to the Tapp, and point one or more runner slots at
90
+ that queue:
91
+
92
+ ```bash
93
+ tapi queue create "John" --project brokerage
94
+ tapi tapp queue add brokerage queue_abc123
95
+
96
+ tapi runner slot set john-laptop 1 queue_abc123 \
97
+ --project brokerage
98
+ ```
99
+
100
+ For users setting up a local runner, `tapi runner setup` opens a localhost setup
101
+ page that can create or accept a queue id, attach that queue to the Tapp, and
102
+ assign one or more slots on the runner:
103
+
104
+ ```bash
105
+ TAPI_API_KEY=tapi_project_key tapi runner setup \
106
+ --runner-id john-laptop \
107
+ --project brokerage
108
+ ```
83
109
 
84
110
  ## Quick Start
85
111
 
86
- Create one TAPI client in server-side app code. Use a project-scoped API key and
87
- the same project id from `.tapi/project.json`.
112
+ Create one TAPI client in server-side app code. Use a developer API key, the
113
+ Tapp id that owns the service call, and optionally the same project id from
114
+ `.tapi/project.json`.
88
115
 
89
116
  ```ts
90
117
  import { TapiClient } from "@tapi-dev/sdk";
91
118
 
92
- export const tapi = new TapiClient({
93
- baseUrl: process.env.TAPI_BASE_URL!,
94
- apiKey: process.env.TAPI_API_KEY!,
95
- projectId: process.env.TAPI_PROJECT_ID!,
96
- });
97
- ```
119
+ export const tapi = new TapiClient({
120
+ baseUrl: process.env.TAPI_BASE_URL!,
121
+ apiKey: process.env.TAPI_API_KEY!,
122
+ tappId: process.env.TAPI_TAPP_ID!,
123
+ projectId: process.env.TAPI_PROJECT_ID!,
124
+ });
125
+ ```
98
126
 
99
127
  Production is the default mode. Unknown website states fail the run instead of
100
128
  holding a browser open:
101
129
 
102
130
  ```ts
103
- const tapi = new TapiClient({
104
- baseUrl: process.env.TAPI_BASE_URL!,
105
- apiKey: process.env.TAPI_API_KEY!,
106
- projectId: process.env.TAPI_PROJECT_ID!,
107
- dev: false,
108
- });
131
+ const tapi = new TapiClient({
132
+ baseUrl: process.env.TAPI_BASE_URL!,
133
+ apiKey: process.env.TAPI_API_KEY!,
134
+ tappId: process.env.TAPI_TAPP_ID!,
135
+ projectId: process.env.TAPI_PROJECT_ID!,
136
+ dev: false,
137
+ });
109
138
  ```
110
139
 
111
140
  During development, set `dev: true`. If a website reaches an unknown state,
@@ -113,41 +142,46 @@ Tapi preserves the browser session for takeover instead of turning it into a
113
142
  production failure:
114
143
 
115
144
  ```ts
116
- const tapi = new TapiClient({
117
- baseUrl: process.env.TAPI_BASE_URL!,
118
- apiKey: process.env.TAPI_API_KEY!,
119
- projectId: process.env.TAPI_PROJECT_ID!,
120
- dev: true,
121
- });
145
+ const tapi = new TapiClient({
146
+ baseUrl: process.env.TAPI_BASE_URL!,
147
+ apiKey: process.env.TAPI_API_KEY!,
148
+ tappId: process.env.TAPI_TAPP_ID!,
149
+ projectId: process.env.TAPI_PROJECT_ID!,
150
+ dev: true,
151
+ });
122
152
  ```
123
153
 
124
154
  Inspect preserved sessions from the repo:
125
155
 
126
156
  ```bash
127
157
  tapi sessions
128
- tapi sessions --json
129
- tapi sessions open <session-id>
130
- ```
131
-
132
- `tapi sessions open` reuses the matching Studio window for the current repo when
133
- one is already running; otherwise it installs/starts Studio and opens directly
134
- to that session.
135
-
136
- Generated website APIs are authored visually in Tapi Studio by setting workflow
137
- bounds, selecting inputs/outputs, and publishing an API. The SDK sees the public
138
- operation name:
139
-
140
- ```text
141
- <namespace>.<operation>
158
+ tapi sessions --json
159
+ tapi sessions open <session-id>
142
160
  ```
143
161
 
144
- ```ts
145
- const operation = await tapi.websiteApis.describe("schwab.place_order");
162
+ `tapi sessions open` reuses the matching Studio window for the current repo when
163
+ one is already running; otherwise it installs/starts Studio and opens directly
164
+ to that session.
146
165
 
147
- const run = await tapi.websiteApis.run("schwab.place_order", {
148
- inputs: {
149
- symbol: "AAPL",
150
- side: "buy",
166
+ Service runs are authored visually in Tapi Studio by setting ServiceMap bounds
167
+ and selecting inputs/outputs. The SDK sees the public service-run name:
168
+
169
+ ```text
170
+ <namespace>.<operation>
171
+ ```
172
+
173
+ A service run is routed through a queue. The queue id is the only routing handle:
174
+ the Tapp must attach the queue, and one or more runner slots must be configured
175
+ with the same queue id.
176
+
177
+ ```ts
178
+ const operation = await tapi.services.describe("schwab.place_order");
179
+
180
+ const run = await tapi.services.run("schwab.place_order", {
181
+ queueId: "queue_abc123",
182
+ inputs: {
183
+ symbol: "AAPL",
184
+ side: "buy",
151
185
  quantity: 10,
152
186
  orderType: "limit",
153
187
  limitPrice: 190,
@@ -160,13 +194,13 @@ console.log(completedRun.status, completedRun.result);
160
194
 
161
195
  ## Late Inputs
162
196
 
163
- Most API inputs are known before the run starts. Use `lateInput()` when the
197
+ Most service inputs are known before the run starts. Use `lateInput()` when the
164
198
  workflow needs a value later, after earlier browser steps have triggered work
165
199
  somewhere else.
166
200
 
167
201
  For example, state 4 might submit a login form and trigger a one-time code by
168
202
  email or SMS. State 5 needs that code, but your app can only retrieve it after
169
- state 4 runs. Declare the input up front with the API call, then resolve it
203
+ state 4 runs. Declare the input up front with the service run, then resolve it
170
204
  from your own code:
171
205
 
172
206
  ```ts
@@ -179,7 +213,7 @@ const verificationCode = lateInput<string>({
179
213
  timeoutMs: 180_000,
180
214
  });
181
215
 
182
- const run = await tapi.websiteApis.run("walmart.login", {
216
+ const run = await tapi.services.run("walmart.login", {
183
217
  inputs: {
184
218
  email: "buyer@example.com",
185
219
  password: process.env.WALMART_PASSWORD!,
@@ -198,7 +232,7 @@ worker, webhook, or polling loop finds the value:
198
232
  ```ts
199
233
  const verificationCode = lateInput<string>({ timeoutMs: 180_000 });
200
234
 
201
- const run = await tapi.websiteApis.run("walmart.login", {
235
+ const run = await tapi.services.run("walmart.login", {
202
236
  inputs: { email: "buyer@example.com", verificationCode },
203
237
  });
204
238
 
@@ -214,19 +248,19 @@ not provided before `timeoutMs`, the run fails instead of guessing or using the
214
248
  recorded default.
215
249
 
216
250
  Late inputs are for values produced outside the website flow. Values read from
217
- the website itself should stay as normal API outputs.
251
+ the website itself should stay as normal service outputs.
218
252
 
219
- ## API-Call Triggers
253
+ ## Service Triggers
220
254
 
221
- Triggers are scheduled calls to published website APIs. Keep trigger definitions
222
- in the app repo and sync them after publishing the API catalog:
255
+ Triggers are scheduled service runs. Keep trigger definitions in the app repo
256
+ and sync them with the service catalog from that repo:
223
257
 
224
258
  ```ts
225
259
  // tapi.config.ts
226
260
  export default {
227
261
  triggers: {
228
262
  nightlyBalance: {
229
- apiRequest: "schwab.get_balance",
263
+ serviceRun: "schwab.get_balance",
230
264
  schedule: { cron: "0 9 * * MON-FRI" },
231
265
  inputs: { accountId: "main" },
232
266
  runtime: { profileRef: "perm_default" },
@@ -248,7 +282,7 @@ You can also manage triggers directly:
248
282
  ```ts
249
283
  await tapi.triggers.create({
250
284
  name: "nightlyBalance",
251
- apiRequest: "schwab.get_balance",
285
+ serviceRun: "schwab.get_balance",
252
286
  schedule: { cron: "0 9 * * MON-FRI" },
253
287
  inputs: { accountId: "main" },
254
288
  runtime: { profileRef: "perm_default" },
@@ -261,7 +295,7 @@ await tapi.triggers.disable("act_123");
261
295
  You can inspect the same input/output contract from the CLI:
262
296
 
263
297
  ```bash
264
- tapi apis describe schwab.place_order \
298
+ tapi services describe schwab.place_order \
265
299
  --api-base-url "$TAPI_BASE_URL" \
266
300
  --api-key "$TAPI_API_KEY" \
267
301
  --project "$TAPI_PROJECT_ID"
@@ -273,7 +307,7 @@ own backend route, server action, or job worker when using secret API keys.
273
307
  ## Runtime Profiles and Proxies
274
308
 
275
309
  Browser identity is selected with a runner-local `profileRef`, not by passing
276
- Chrome folder paths through the cloud API.
310
+ Chrome folder paths through the cloud service-run request.
277
311
 
278
312
  Use `PermProfile` for a long-lived account identity. It owns a persistent
279
313
  Chrome user-data directory and should usually use the runner's normal home IP:
@@ -284,13 +318,14 @@ const perm = await tapi.runtime.permProfiles.create({
284
318
  originPolicy: { type: "home" },
285
319
  });
286
320
 
287
- await tapi.runtime.permProfiles.launchSetup(perm.profile.profileRef);
288
- // The user logs in, adds passkeys, then closes setup Chrome.
289
-
290
- await tapi.websiteApis.run("walmart.reorder", {
291
- inputs: { item: "paper towels" },
292
- runtime: { profileRef: perm.profile.profileRef },
293
- });
321
+ await tapi.runtime.permProfiles.launchSetup(perm.profile.profileRef);
322
+ // The user logs in, adds passkeys, then closes setup Chrome.
323
+
324
+ await tapi.services.run("walmart.reorder", {
325
+ queueId: "queue_abc123",
326
+ inputs: { item: "paper towels" },
327
+ runtime: { profileRef: perm.profile.profileRef },
328
+ });
294
329
  ```
295
330
 
296
331
  Use `TempProfile` for throwaway work. Temp profiles can be cloned from a
@@ -310,8 +345,9 @@ const temp = await tapi.runtime.tempProfiles.provisionMany({
310
345
 
311
346
  await Promise.all(
312
347
  temp.profiles.map((profile) =>
313
- tapi.websiteApis.run("walmart.lookupItem", {
314
- inputs: { sku: "123" },
348
+ tapi.services.run("walmart.lookupItem", {
349
+ queueId: "queue_abc123",
350
+ inputs: { sku: "123" },
315
351
  runtime: {
316
352
  profileRef: profile.profileRef,
317
353
  destroyOnRelease: profile.destroyOnRelease,
@@ -321,7 +357,7 @@ await Promise.all(
321
357
  );
322
358
  ```
323
359
 
324
- `runtime.profileRef` is the only value sent to the TAPI cloud run API. Proxy
360
+ `runtime.profileRef` is the only value sent to the TAPI cloud run request. Proxy
325
361
  URLs, proxy credentials, and Chrome profile folders stay on the local runner
326
362
  and are handled through the local control WebSocket at `ws://127.0.0.1:8765`.
327
363
  When the same `PermProfile` is used concurrently for multiple different sites,
@@ -332,24 +368,27 @@ the base `profileRef` and let the runner decide.
332
368
  If your JavaScript runtime does not provide `WebSocket`, pass one:
333
369
 
334
370
  ```ts
335
- const tapi = new TapiClient({
336
- baseUrl: process.env.TAPI_BASE_URL!,
337
- apiKey: process.env.TAPI_API_KEY!,
338
- webSocket: MyWebSocketImplementation,
339
- });
340
- ```
371
+ const tapi = new TapiClient({
372
+ baseUrl: process.env.TAPI_BASE_URL!,
373
+ apiKey: process.env.TAPI_API_KEY!,
374
+ tappId: process.env.TAPI_TAPP_ID!,
375
+ webSocket: MyWebSocketImplementation,
376
+ });
377
+ ```
341
378
 
342
379
  ## Configuration
343
380
 
344
381
  ```env
345
- TAPI_BASE_URL=https://your-tapi-api-host
346
- TAPI_API_KEY=tapi_project_key
347
- TAPI_PROJECT_ID=brokerage
348
- ```
349
-
350
- `projectId` is optional only when the API key itself is project-scoped. If
351
- provided, the SDK sends it as the `X-Tapi-Project` header and the server rejects
352
- mismatches.
382
+ TAPI_BASE_URL=https://your-tapi-api-host
383
+ TAPI_API_KEY=tapi_project_key
384
+ TAPI_TAPP_ID=brokerage
385
+ TAPI_PROJECT_ID=brokerage
386
+ ```
387
+
388
+ `tappId` is required. Service calls are made for that Tapp, and the developer
389
+ who owns the Tapp pays for the service delivery. `projectId` is optional when
390
+ the API key is already project-scoped. If provided, the SDK sends it as the
391
+ `X-Tapi-Project` header and the server rejects mismatches.
353
392
 
354
393
  ## Common Project Setup
355
394
 
@@ -365,11 +404,12 @@ src/
365
404
  // src/lib/tapi.ts
366
405
  import { TapiClient } from "@tapi-dev/sdk";
367
406
 
368
- export const tapi = new TapiClient({
369
- baseUrl: process.env.TAPI_BASE_URL!,
370
- apiKey: process.env.TAPI_API_KEY!,
371
- projectId: process.env.TAPI_PROJECT_ID!,
372
- });
407
+ export const tapi = new TapiClient({
408
+ baseUrl: process.env.TAPI_BASE_URL!,
409
+ apiKey: process.env.TAPI_API_KEY!,
410
+ tappId: process.env.TAPI_TAPP_ID!,
411
+ projectId: process.env.TAPI_PROJECT_ID!,
412
+ });
373
413
  ```
374
414
 
375
415
  Application code should import this shared client instead of constructing a new client in every file.
@@ -384,15 +424,15 @@ await tapi.runtime.profiles.list();
384
424
  await tapi.runtime.profiles.clone({ profileRef: "perm_default", site: "walmart" });
385
425
  await tapi.runtime.permProfiles.create({ displayName: "Default", originPolicy: { type: "home" } });
386
426
  await tapi.runtime.tempProfiles.provisionMany({ proxies: ["http://user:pass@host:8080"] });
387
- await tapi.websiteApis.describe("schwab.place_order");
427
+ await tapi.services.describe("schwab.place_order");
388
428
 
389
- const run = await tapi.websiteApis.run("apiName.requestKey", {
390
- inputs: { example: true },
391
- runtime: { profileRef: "perm_default" },
392
- priority: 5,
393
- runnerId: "runner-id",
394
- idempotencyKey: "request-123",
395
- });
429
+ const run = await tapi.services.run("serviceName.serviceKey", {
430
+ queueId: "queue_abc123",
431
+ inputs: { example: true },
432
+ runtime: { profileRef: "perm_default" },
433
+ priority: 5,
434
+ idempotencyKey: "request-123",
435
+ });
396
436
 
397
437
  await tapi.runs.get(run.id);
398
438
  await tapi.runs.wait(run.id, { intervalMs: 1000, timeoutMs: 300000 });
@@ -423,7 +463,7 @@ The minimum developer flow is:
423
463
 
424
464
  ```ts
425
465
  // backend route or server action
426
- const quote = await tapi.websiteApis.quoteCloud("schwab.place_order", {
466
+ const quote = await tapi.services.quoteCloud("schwab.place_order", {
427
467
  inputs: [
428
468
  { symbol: "AAPL", quantity: 1 },
429
469
  { symbol: "MSFT", quantity: 2 },
@@ -444,7 +484,7 @@ if (!quote.withinMaxCost) {
444
484
  throw new Error("Cloud batch exceeds the configured cost cap");
445
485
  }
446
486
 
447
- const run = await tapi.websiteApis.runCloudBatch("schwab.place_order", {
487
+ const run = await tapi.services.runCloudBatch("schwab.place_order", {
448
488
  inputs: [
449
489
  { symbol: "AAPL", quantity: 1 },
450
490
  { symbol: "MSFT", quantity: 2 },
@@ -483,10 +523,10 @@ low, `runCloudBatch` converts the server's HTTP `402` into a normal
483
523
  `availableBalanceCents`, and `requiredBalanceCents`. That keeps the button
484
524
  handler simple.
485
525
 
486
- Credits are scoped under the Tapi API key owner, app id, and
487
- `user.externalUserId`. Use the same `externalUserId` for quote, balance,
488
- checkout, and run calls. If you omit `externalUserId` but provide `email`, Tapi
489
- uses the normalized email as the external user id.
526
+ Credits are scoped under the Tapi API key owner, Tapp id, and
527
+ `user.externalUserId`. Use the same `externalUserId` for quote, balance,
528
+ checkout, and run calls. If you omit `externalUserId` but provide `email`, Tapi
529
+ uses the normalized email as the external user id.
490
530
 
491
531
  Applications can also create a top-up checkout explicitly:
492
532
 
@@ -513,9 +553,9 @@ The developer application does not need VM provisioning logic, installer
513
553
  deployment logic, worker command interpretation, browser runner internals, or
514
554
  builder logic. Those stay behind the Tapi server API.
515
555
 
516
- Website API requests are addressed as `<namespace>.<operation>`. The namespace
517
- comes from the generated website name in Studio. The operation is the name the
518
- developer types into the SDK name textbox at the workflow boundary.
556
+ Service runs are addressed as `<serviceName>.<serviceKey>`. The service name
557
+ comes from the ServiceMap name in Studio. The service key is the name the
558
+ developer types into the SDK name textbox at the service boundary.
519
559
 
520
560
  `describe()` returns the public SDK contract: input controls such as
521
561
  `textbox`, `radio`, `select`, `checkbox`, conditional requirements such as
@@ -550,10 +590,10 @@ import type {
550
590
  RuntimeProfile,
551
591
  RuntimeRunOptions,
552
592
  SdkCatalog,
593
+ ServiceOperation,
594
+ ServiceRunRequest,
553
595
  TapiRun,
554
596
  TapiRunner,
555
- WebsiteApiOperation,
556
- WebsiteApiRunRequest,
557
597
  } from "@tapi-dev/sdk";
558
598
  ```
559
599
 
package/dist/catalog.js CHANGED
@@ -1,9 +1,10 @@
1
+ import { normalizeSdkCatalog } from "./service-contract.js";
1
2
  export class CatalogResource {
2
3
  http;
3
4
  constructor(http) {
4
5
  this.http = http;
5
6
  }
6
- get() {
7
- return this.http.get("/api/sdk/v1/catalog");
7
+ async get() {
8
+ return normalizeSdkCatalog(await this.http.get("/api/sdk/v1/catalog"));
8
9
  }
9
10
  }
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import type { TapiDevSession } from "./types.js";
3
3
  import { type TapiWorkspace } from "./workspace.js";
4
+ export declare const PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS = 120000;
4
5
  export type StudioChannel = "pilot" | "stable" | "nightly";
5
6
  export interface StudioReleaseManifest {
6
7
  product?: string;
@@ -68,6 +69,32 @@ export interface StudioCliOptions {
68
69
  workspaceMode: boolean;
69
70
  launchUrlQuery?: string;
70
71
  }
72
+ export interface RunnerSetupCliOptions {
73
+ apiBaseUrl: string;
74
+ apiKey: string;
75
+ projectId: string;
76
+ runnerId: string;
77
+ port: number;
78
+ openBrowser: boolean;
79
+ }
80
+ export interface RunnerSetupAction {
81
+ queueId?: string;
82
+ displayName?: string;
83
+ maxSlots?: number;
84
+ slotStart?: number;
85
+ slotCount?: number;
86
+ }
87
+ export interface RunnerSetupActionResult {
88
+ queueId: string;
89
+ queue: Record<string, unknown>;
90
+ attachment: Record<string, unknown>;
91
+ slots: Record<string, unknown>[];
92
+ }
93
+ export interface RunnerSetupServerHandle {
94
+ url: string;
95
+ port: number;
96
+ close: () => Promise<void>;
97
+ }
71
98
  export interface ServiceStatus {
72
99
  installed: boolean;
73
100
  name?: string;
@@ -80,6 +107,9 @@ type StudioPortAvailable = (port: number) => Promise<boolean>;
80
107
  type StudioPortReaper = (preferred: number, count: number) => Promise<number>;
81
108
  export declare function runCli(argv?: string[]): Promise<number>;
82
109
  export declare function parseStudioOptions(args: string[]): StudioCliOptions;
110
+ export declare function parseRunnerSetupOptions(args: string[]): RunnerSetupCliOptions;
111
+ export declare function startRunnerSetupServer(options: RunnerSetupCliOptions, fetchImpl?: FetchLike): Promise<RunnerSetupServerHandle>;
112
+ export declare function executeRunnerSetupAction(options: RunnerSetupCliOptions, action: RunnerSetupAction, fetchImpl?: FetchLike): Promise<RunnerSetupActionResult>;
83
113
  export declare function trySelectDevSessionInOpenStudio(session: TapiDevSession, options: StudioCliOptions, fetchImpl?: FetchLike): Promise<{
84
114
  selected: boolean;
85
115
  baseUrl?: string;