@tapi-dev/sdk 0.1.4 → 0.1.6

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
@@ -20,36 +20,46 @@ npx tapi studio install --channel pilot
20
20
  npx tapi studio open
21
21
  ```
22
22
 
23
+ `studio install` opens a browser sign-in before any Studio EXE download. The
24
+ CLI exchanges the browser credential for Firebase auth, requests a short-lived
25
+ Studio install token from the Tapi API, then fetches a protected manifest and
26
+ installer download URL. If the signed-in user is not yet approved, the server
27
+ submits an approval request and sends the existing email approval links to the
28
+ admin. After approval, rerun the same command.
29
+
23
30
  You can also run the CLI directly from npm without adding the package first:
24
31
 
25
32
  ```bash
26
33
  npx @tapi-dev/sdk studio install --channel pilot
27
34
  ```
28
35
 
29
- The CLI reads the release manifest from:
36
+ The install flow talks to the Tapi API host first:
30
37
 
31
38
  ```text
32
- https://d4xaf52nfwiok.cloudfront.net/studio/channels/pilot/latest.json
39
+ POST /api/sdk/v1/studio/install-token
40
+ GET /api/sdk/v1/studio/releases/<channel>
33
41
  ```
34
42
 
35
- It downloads the Windows installer, verifies the manifest SHA256, caches the installer locally, and runs it. The installer cache defaults to:
43
+ It then downloads the Windows installer, verifies the manifest SHA256, caches
44
+ the installer locally, and runs it. The installer cache defaults to:
36
45
 
37
46
  ```text
38
- %LOCALAPPDATA%\Tapi\Studio\downloads
39
- ```
40
-
41
- Tapi Studio includes the matching Tapi Service build. When the installer runs,
42
- it installs or replaces the local `tapi-service` Windows service with the
43
- version declared in the Studio manifest. Developers do not pick a service
44
- version separately; updating Studio updates the service version used by that
45
- project and by apps generated from that project.
47
+ %LOCALAPPDATA%\Tapi\Studio\downloads
48
+ ```
49
+
50
+ Tapi Studio includes the matching Tapi Service build. When the installer runs,
51
+ it installs or replaces the local `tapi-service` Windows service with the
52
+ version declared in the Studio manifest. Developers do not pick a service
53
+ version separately; updating Studio updates the service version used by that
54
+ project and by apps generated from that project.
46
55
 
47
56
  Useful commands:
48
57
 
49
58
  ```bash
50
59
  npx tapi studio install --channel pilot
51
60
  npx tapi studio install --channel pilot --download-only
52
- npx tapi studio install --manifest https://d4xaf52nfwiok.cloudfront.net/studio/channels/pilot/latest.json
61
+ npx tapi studio install --api-base-url https://api.example.com
62
+ npx tapi studio install --install-token tsi_your_preissued_token
53
63
  npx tapi studio open
54
64
  npx tapi studio doctor
55
65
  ```
@@ -57,13 +67,42 @@ npx tapi studio doctor
57
67
  Environment overrides:
58
68
 
59
69
  ```env
70
+ TAPI_BASE_URL=https://your-tapi-api-host
71
+ TAPI_STUDIO_API_BASE_URL=https://your-tapi-api-host
60
72
  TAPI_STUDIO_CHANNEL=pilot
61
73
  TAPI_STUDIO_MANIFEST_URL=https://d4xaf52nfwiok.cloudfront.net/studio/channels/pilot/latest.json
62
- TAPI_DOWNLOADS_BASE_URL=https://d4xaf52nfwiok.cloudfront.net
74
+ TAPI_STUDIO_INSTALL_TOKEN=tsi_your_preissued_token
75
+ TAPI_STUDIO_AUTH_HTML_URL=https://rsarlong-1f92fd.gitlab.io/auth.html
76
+ TAPI_FIREBASE_API_KEY=AIzaSyCDZR8lWyVQcWYfFdNZa4vuL4IWEC0h6gE
63
77
  TAPI_STUDIO_EXE=C:\Users\you\AppData\Local\Tapi Studio\Tapi Studio.exe
64
78
  ```
65
79
 
66
- Tapi Studio desktop releases are currently published for Windows x64.
80
+ `TAPI_STUDIO_MANIFEST_URL` remains useful for `studio doctor`. Direct manifest
81
+ overrides are not used by the protected installer path.
82
+
83
+ Server-side protected install delivery also requires:
84
+
85
+ ```env
86
+ STUDIO_INSTALL_TOKEN_SECRET=replace-me
87
+ S3_BUCKET=your-private-release-bucket
88
+ AWS_REGION=us-east-1
89
+ ```
90
+
91
+ If the Studio installer objects remain publicly downloadable, users can bypass
92
+ the approval gate by skipping the CLI entirely.
93
+
94
+ Tapi Studio desktop releases are currently published for Windows x64.
95
+
96
+ Cloud Windows workers use a separate protected service runtime manifest:
97
+
98
+ ```text
99
+ GET /api/sdk/v1/service/releases/<channel>
100
+ ```
101
+
102
+ The request must include either `Authorization: Bearer
103
+ $CLOUD_WORKER_BOOTSTRAP_SECRET` or `X-Tapi-Worker-Bootstrap-Secret`. The server
104
+ reads `service/channels/<channel>/latest.json` from the private S3 bucket and
105
+ returns a short-lived signed download URL for the Tapi Service zip.
67
106
 
68
107
  ## Quick Start
69
108
 
@@ -94,8 +133,111 @@ const completedRun = await tapi.runs.wait(run.id);
94
133
  console.log(completedRun.status, completedRun.result);
95
134
  ```
96
135
 
136
+ Generated website APIs are created in Tapi Studio by setting workflow bounds,
137
+ choosing the SDK operation name, selecting inputs/outputs, and publishing the
138
+ operation. Studio owns the private workflow bindings. The SDK sees the public
139
+ shape as:
140
+
141
+ ```text
142
+ <autogenerated website namespace>.<developer operation name>
143
+ ```
144
+
145
+ For example, if Studio shows `SDK name: schwab.[ place_order ]`, call:
146
+
147
+ ```ts
148
+ const operation = await tapi.websiteApis.describe("schwab.place_order");
149
+
150
+ const run = await tapi.websiteApis.run("schwab.place_order", {
151
+ inputs: {
152
+ symbol: "AAPL",
153
+ side: "buy",
154
+ quantity: 10,
155
+ orderType: "limit",
156
+ limitPrice: 190,
157
+ },
158
+ });
159
+ ```
160
+
161
+ You can inspect the same input/output contract from the CLI:
162
+
163
+ ```bash
164
+ npx tapi apis describe schwab.place_order \
165
+ --api-base-url "$TAPI_BASE_URL" \
166
+ --api-key "$TAPI_API_KEY" \
167
+ --app "$TAPI_APP_ID"
168
+ ```
169
+
97
170
  Do not expose `TAPI_API_KEY` in public browser bundles. Put the SDK behind your own backend route, server action, or job worker when using secret API keys.
98
171
 
172
+ ## Runtime Profiles and Proxies
173
+
174
+ Browser identity is selected with a runner-local `profileRef`, not by passing
175
+ Chrome folder paths through the cloud API.
176
+
177
+ Use `PermProfile` for a long-lived account identity. It owns a persistent
178
+ Chrome user-data directory and should usually use the runner's normal home IP:
179
+
180
+ ```ts
181
+ const perm = await tapi.runtime.permProfiles.create({
182
+ displayName: "Mom Walmart",
183
+ originPolicy: { type: "home" },
184
+ });
185
+
186
+ await tapi.runtime.permProfiles.launchSetup(perm.profile.profileRef);
187
+ // The user logs in, adds passkeys, then closes setup Chrome.
188
+
189
+ await tapi.websiteApis.run("walmart.reorder", {
190
+ inputs: { item: "paper towels" },
191
+ runtime: { profileRef: perm.profile.profileRef },
192
+ });
193
+ ```
194
+
195
+ Use `TempProfile` for throwaway work. Temp profiles can be cloned from a
196
+ template profile and assigned different proxies:
197
+
198
+ ```ts
199
+ const temp = await tapi.runtime.tempProfiles.provisionMany({
200
+ count: 3,
201
+ displayNamePrefix: "checkout",
202
+ proxies: [
203
+ "http://user1:pass1@proxy-a.example:8080",
204
+ "http://user2:pass2@proxy-b.example:8080",
205
+ "http://user3:pass3@proxy-c.example:8080",
206
+ ],
207
+ destroyOnRelease: true,
208
+ });
209
+
210
+ await Promise.all(
211
+ temp.profiles.map((profile) =>
212
+ tapi.websiteApis.run("walmart.lookupItem", {
213
+ inputs: { sku: "123" },
214
+ runtime: {
215
+ profileRef: profile.profileRef,
216
+ destroyOnRelease: profile.destroyOnRelease,
217
+ },
218
+ }),
219
+ ),
220
+ );
221
+ ```
222
+
223
+ `runtime.profileRef` is the only value sent to the TAPI cloud run API. Proxy
224
+ URLs, proxy credentials, and Chrome profile folders stay on the local runner
225
+ and are handled through the local control WebSocket at `ws://127.0.0.1:8765`.
226
+ When the same `PermProfile` is used concurrently for multiple different sites,
227
+ the runner creates runtime clones so Chrome does not open the same
228
+ `--user-data-dir` twice. You can also create a clone explicitly with
229
+ `tapi.runtime.profiles.clone({ profileRef, site })`, but most apps should pass
230
+ the base `profileRef` and let the runner decide.
231
+ If your JavaScript runtime does not provide `WebSocket`, pass one:
232
+
233
+ ```ts
234
+ const tapi = new TapiClient({
235
+ baseUrl: process.env.TAPI_BASE_URL!,
236
+ apiKey: process.env.TAPI_API_KEY!,
237
+ webSocket: MyWebSocketImplementation,
238
+ });
239
+ ```
240
+
99
241
  ## Configuration
100
242
 
101
243
  ```env
@@ -135,20 +277,146 @@ Application code should import this shared client instead of constructing a new
135
277
  await tapi.catalog.get();
136
278
  await tapi.runners.list();
137
279
  await tapi.runtime.requirements();
280
+ await tapi.runtime.profiles.list();
281
+ await tapi.runtime.profiles.clone({ profileRef: "perm_default", site: "walmart" });
282
+ await tapi.runtime.permProfiles.create({ displayName: "Default", originPolicy: { type: "home" } });
283
+ await tapi.runtime.tempProfiles.provisionMany({ proxies: ["http://user:pass@host:8080"] });
284
+ await tapi.websiteApis.describe("schwab.place_order");
138
285
 
139
286
  const run = await tapi.websiteApis.run("apiName.requestKey", {
140
287
  inputs: { example: true },
288
+ runtime: { profileRef: "perm_default" },
141
289
  priority: 5,
142
290
  runnerId: "runner-id",
143
291
  idempotencyKey: "request-123",
144
292
  });
145
293
 
146
294
  await tapi.runs.get(run.id);
147
- await tapi.runs.wait(run.id, { intervalMs: 1000, timeoutMs: 300000 });
148
- await tapi.runs.cancel(run.id);
149
- ```
295
+ await tapi.runs.wait(run.id, { intervalMs: 1000, timeoutMs: 300000 });
296
+ await tapi.runs.cancel(run.id);
297
+ ```
298
+
299
+ ## Cloud Batch Runs
300
+
301
+ Cloud runs are requested from the SDK, but VM provisioning, service
302
+ installation, worker leases, browser internals, and AWS cleanup stay on the
303
+ Tapi server. The SDK is only the front door.
304
+
305
+ Keep this code server-side. Do not put `TAPI_API_KEY` in a browser bundle and
306
+ do not wire AWS or Stripe from the developer's app. The app sends its own user
307
+ identity to Tapi, and Tapi handles prepaid Stripe checkout, credit accounting,
308
+ AWS worker provisioning, and cleanup.
309
+
310
+ The minimum developer flow is:
311
+
312
+ 1. App user clicks a button.
313
+ 2. The developer's backend calls `runCloudBatch` with `user.externalUserId`.
314
+ 3. If the returned run has `status: "payment_required"`, redirect the app user
315
+ to `run.checkoutUrl`.
316
+ 4. Stripe calls the Tapi webhook, Tapi credits that same `externalUserId`, and
317
+ the app retries the cloud batch.
318
+ 5. If credit is available, Tapi reserves the balance and starts cloud workers.
319
+
320
+ ```ts
321
+ // backend route or server action
322
+ const quote = await tapi.websiteApis.quoteCloud("schwab.place_order", {
323
+ inputs: [
324
+ { symbol: "AAPL", quantity: 1 },
325
+ { symbol: "MSFT", quantity: 2 },
326
+ ],
327
+ user: {
328
+ externalUserId: appUser.id,
329
+ email: appUser.email,
330
+ },
331
+ cloud: {
332
+ windows: true,
333
+ maxVms: 1,
334
+ chromePerVm: 10,
335
+ maxCostUsd: 20,
336
+ },
337
+ });
338
+
339
+ if (!quote.withinMaxCost) {
340
+ throw new Error("Cloud batch exceeds the configured cost cap");
341
+ }
342
+
343
+ const run = await tapi.websiteApis.runCloudBatch("schwab.place_order", {
344
+ inputs: [
345
+ { symbol: "AAPL", quantity: 1 },
346
+ { symbol: "MSFT", quantity: 2 },
347
+ ],
348
+ user: {
349
+ externalUserId: appUser.id,
350
+ email: appUser.email,
351
+ },
352
+ payment: {
353
+ successUrl: `https://your-app.example/cloud/success`,
354
+ cancelUrl: `https://your-app.example/cloud/cancel`,
355
+ },
356
+ cloud: {
357
+ windows: true,
358
+ maxVms: 1,
359
+ chromePerVm: 10,
360
+ maxCostUsd: 20,
361
+ },
362
+ });
363
+
364
+ if (run.status === "payment_required") {
365
+ return { redirectTo: run.checkoutUrl };
366
+ }
367
+
368
+ for await (const event of tapi.cloudRuns.stream(run.id)) {
369
+ console.log(event.status, event.traceId);
370
+ }
371
+
372
+ const results = await tapi.cloudRuns.results(run.id);
373
+ ```
374
+
375
+ When `CLOUD_BILLING_ENABLED=true`, the server refuses to launch AWS workers
376
+ unless prepaid cloud credit is available and reserved first. If balance is too
377
+ low, `runCloudBatch` converts the server's HTTP `402` into a normal
378
+ `CloudBatchRun` object with `status: "payment_required"`, `checkoutUrl`,
379
+ `availableBalanceCents`, and `requiredBalanceCents`. That keeps the button
380
+ handler simple.
381
+
382
+ Credits are scoped under the Tapi API key owner, app id, and
383
+ `user.externalUserId`. Use the same `externalUserId` for quote, balance,
384
+ checkout, and run calls. If you omit `externalUserId` but provide `email`, Tapi
385
+ uses the normalized email as the external user id.
386
+
387
+ Applications can also create a top-up checkout explicitly:
388
+
389
+ ```ts
390
+ const user = {
391
+ externalUserId: appUser.id,
392
+ email: appUser.email,
393
+ };
394
+
395
+ const balance = await tapi.cloudRuns.balance(user);
396
+
397
+ if (balance.balanceCents < balance.minimumBalanceCents) {
398
+ const checkout = await tapi.cloudRuns.checkout({
399
+ amountCents: balance.minimumBalanceCents,
400
+ user,
401
+ successUrl: "https://your-app.example/cloud/success",
402
+ cancelUrl: "https://your-app.example/cloud/cancel",
403
+ });
404
+ console.log(checkout.checkoutUrl);
405
+ }
406
+ ```
407
+
408
+ The developer application does not need VM provisioning logic, installer
409
+ deployment logic, worker command interpretation, browser runner internals, or
410
+ builder logic. Those stay behind the Tapi server API.
411
+
412
+ Website API requests are addressed as `<namespace>.<operation>`. The namespace
413
+ comes from the generated website name in Studio. The operation is the name the
414
+ developer types into the SDK name textbox at the workflow boundary.
150
415
 
151
- Website API requests are addressed as `<apiName>.<requestKey>`.
416
+ `describe()` returns the public SDK contract: input controls such as
417
+ `textbox`, `radio`, `select`, `checkbox`, conditional requirements such as
418
+ `requiredWhen`, and the output schema. It does not expose workflow ids, state
419
+ ids, action ids, selectors, runner ids, or other Studio internals.
152
420
 
153
421
  ## Errors
154
422
 
@@ -174,9 +442,12 @@ The package includes generated TypeScript declarations. Common exported types in
174
442
  ```ts
175
443
  import type {
176
444
  RuntimeRequirements,
445
+ RuntimeProfile,
446
+ RuntimeRunOptions,
177
447
  SdkCatalog,
178
448
  TapiRun,
179
449
  TapiRunner,
450
+ WebsiteApiOperation,
180
451
  WebsiteApiRunRequest,
181
452
  } from "@tapi-dev/sdk";
182
453
  ```
package/dist/cli.d.ts CHANGED
@@ -24,11 +24,14 @@ export interface StudioReleaseManifest {
24
24
  }
25
25
  interface StudioCliOptions {
26
26
  channel: StudioChannel;
27
+ apiBaseUrl: string;
27
28
  manifestUrl: string;
29
+ manifestUrlOverride?: string;
28
30
  cacheDir: string;
29
31
  downloadOnly: boolean;
30
32
  silent: boolean;
31
33
  exePath?: string;
34
+ installToken?: string;
32
35
  }
33
36
  export declare function runCli(argv?: string[]): Promise<number>;
34
37
  export declare function parseStudioOptions(args: string[]): StudioCliOptions;