@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 +289 -18
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +495 -35
- package/dist/cloud-runs.d.ts +22 -0
- package/dist/cloud-runs.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -1
- package/dist/runtime.d.ts +36 -2
- package/dist/runtime.js +132 -1
- package/dist/types.d.ts +236 -1
- package/dist/website-apis.d.ts +9 -1
- package/dist/website-apis.js +61 -0
- package/package.json +36 -36
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
|
|
36
|
+
The install flow talks to the Tapi API host first:
|
|
30
37
|
|
|
31
38
|
```text
|
|
32
|
-
|
|
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
|
|
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 --
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|