@tapi-dev/sdk 0.1.5 → 0.1.7
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 +311 -68
- package/dist/catalog.d.ts +2 -2
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +144 -35
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/cloud-runs.d.ts +22 -0
- package/dist/cloud-runs.js +71 -0
- package/dist/index.d.ts +10 -8
- package/dist/index.js +12 -9
- package/dist/runners.d.ts +2 -2
- package/dist/runs.d.ts +2 -2
- package/dist/runtime.d.ts +37 -3
- package/dist/runtime.js +132 -1
- package/dist/types.d.ts +236 -1
- package/dist/website-apis.d.ts +10 -2
- package/dist/website-apis.js +61 -0
- package/package.json +36 -36
package/README.md
CHANGED
|
@@ -14,72 +14,72 @@ This package is ESM-first and works in runtimes with `fetch`, including modern N
|
|
|
14
14
|
|
|
15
15
|
Tapi Studio is the desktop app used to author and test local Tapi integrations. Install the SDK first, then use the bundled CLI:
|
|
16
16
|
|
|
17
|
-
```bash
|
|
18
|
-
npm install @tapi-dev/sdk
|
|
19
|
-
npx tapi studio install --channel pilot
|
|
20
|
-
npx tapi studio open
|
|
21
|
-
```
|
|
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
|
-
|
|
30
|
-
You can also run the CLI directly from npm without adding the package first:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
npx @tapi-dev/sdk studio install --channel pilot
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The install flow talks to the Tapi API host first:
|
|
37
|
-
|
|
38
|
-
```text
|
|
39
|
-
POST /api/sdk/v1/studio/install-token
|
|
40
|
-
GET /api/sdk/v1/studio/releases/<channel>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
It then downloads the Windows installer, verifies the manifest SHA256, caches
|
|
44
|
-
the installer locally, and runs it. The installer cache defaults to:
|
|
17
|
+
```bash
|
|
18
|
+
npm install @tapi-dev/sdk
|
|
19
|
+
npx tapi studio install --channel pilot
|
|
20
|
+
npx tapi studio open
|
|
21
|
+
```
|
|
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
|
+
|
|
30
|
+
You can also run the CLI directly from npm without adding the package first:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @tapi-dev/sdk studio install --channel pilot
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The install flow talks to the Tapi API host first:
|
|
45
37
|
|
|
46
38
|
```text
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
39
|
+
POST /api/sdk/v1/studio/install-token
|
|
40
|
+
GET /api/sdk/v1/studio/releases/<channel>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
It then downloads the Windows installer, verifies the manifest SHA256, caches
|
|
44
|
+
the installer locally, and runs it. The installer cache defaults to:
|
|
45
|
+
|
|
46
|
+
```text
|
|
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.
|
|
55
|
+
|
|
56
|
+
Useful commands:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx tapi studio install --channel pilot
|
|
60
|
+
npx tapi studio install --channel pilot --download-only
|
|
61
|
+
npx tapi studio install --api-base-url https://api.example.com
|
|
62
|
+
npx tapi studio install --install-token tsi_your_preissued_token
|
|
63
|
+
npx tapi studio open
|
|
64
|
+
npx tapi studio doctor
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Environment overrides:
|
|
68
|
+
|
|
69
|
+
```env
|
|
70
|
+
TAPI_BASE_URL=https://your-tapi-api-host
|
|
71
|
+
TAPI_STUDIO_API_BASE_URL=https://your-tapi-api-host
|
|
72
|
+
TAPI_STUDIO_CHANNEL=pilot
|
|
73
|
+
TAPI_STUDIO_MANIFEST_URL=https://d4xaf52nfwiok.cloudfront.net/studio/channels/pilot/latest.json
|
|
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
|
|
77
|
+
TAPI_STUDIO_EXE=C:\Users\you\AppData\Local\Tapi Studio\Tapi Studio.exe
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`TAPI_STUDIO_MANIFEST_URL` remains useful for `studio doctor`. Direct manifest
|
|
81
|
+
overrides are not used by the protected installer path.
|
|
82
|
+
|
|
83
83
|
Server-side protected install delivery also requires:
|
|
84
84
|
|
|
85
85
|
```env
|
|
@@ -92,6 +92,17 @@ If the Studio installer objects remain publicly downloadable, users can bypass
|
|
|
92
92
|
the approval gate by skipping the CLI entirely.
|
|
93
93
|
|
|
94
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.
|
|
95
106
|
|
|
96
107
|
## Quick Start
|
|
97
108
|
|
|
@@ -122,8 +133,111 @@ const completedRun = await tapi.runs.wait(run.id);
|
|
|
122
133
|
console.log(completedRun.status, completedRun.result);
|
|
123
134
|
```
|
|
124
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
|
+
|
|
125
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.
|
|
126
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
|
+
|
|
127
241
|
## Configuration
|
|
128
242
|
|
|
129
243
|
```env
|
|
@@ -163,20 +277,146 @@ Application code should import this shared client instead of constructing a new
|
|
|
163
277
|
await tapi.catalog.get();
|
|
164
278
|
await tapi.runners.list();
|
|
165
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");
|
|
166
285
|
|
|
167
286
|
const run = await tapi.websiteApis.run("apiName.requestKey", {
|
|
168
287
|
inputs: { example: true },
|
|
288
|
+
runtime: { profileRef: "perm_default" },
|
|
169
289
|
priority: 5,
|
|
170
290
|
runnerId: "runner-id",
|
|
171
291
|
idempotencyKey: "request-123",
|
|
172
292
|
});
|
|
173
293
|
|
|
174
294
|
await tapi.runs.get(run.id);
|
|
175
|
-
await tapi.runs.wait(run.id, { intervalMs: 1000, timeoutMs: 300000 });
|
|
176
|
-
await tapi.runs.cancel(run.id);
|
|
177
|
-
```
|
|
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.
|
|
178
415
|
|
|
179
|
-
|
|
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.
|
|
180
420
|
|
|
181
421
|
## Errors
|
|
182
422
|
|
|
@@ -202,9 +442,12 @@ The package includes generated TypeScript declarations. Common exported types in
|
|
|
202
442
|
```ts
|
|
203
443
|
import type {
|
|
204
444
|
RuntimeRequirements,
|
|
445
|
+
RuntimeProfile,
|
|
446
|
+
RuntimeRunOptions,
|
|
205
447
|
SdkCatalog,
|
|
206
448
|
TapiRun,
|
|
207
449
|
TapiRunner,
|
|
450
|
+
WebsiteApiOperation,
|
|
208
451
|
WebsiteApiRunRequest,
|
|
209
452
|
} from "@tapi-dev/sdk";
|
|
210
453
|
```
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { HttpClient } from "./client";
|
|
2
|
-
import type { SdkCatalog } from "./types";
|
|
1
|
+
import type { HttpClient } from "./client.js";
|
|
2
|
+
import type { SdkCatalog } from "./types.js";
|
|
3
3
|
export declare class CatalogResource {
|
|
4
4
|
private readonly http;
|
|
5
5
|
constructor(http: HttpClient);
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import { performance } from "node:perf_hooks";
|
|
|
10
10
|
import { Readable } from "node:stream";
|
|
11
11
|
import { pipeline } from "node:stream/promises";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { TapiClient } from "./index.js";
|
|
13
14
|
const DEFAULT_DOWNLOADS_BASE_URL = "https://d4xaf52nfwiok.cloudfront.net";
|
|
14
15
|
const DEFAULT_STUDIO_API_BASE_URL = "https://determined-motivation-production.up.railway.app";
|
|
15
16
|
const DEFAULT_STUDIO_AUTH_HTML_URL = "https://rsarlong-1f92fd.gitlab.io/auth.html";
|
|
@@ -46,6 +47,18 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
46
47
|
}
|
|
47
48
|
return runDoctor(parseStudioOptions([subcommand, ...rest].filter(Boolean)));
|
|
48
49
|
}
|
|
50
|
+
if (command === "apis") {
|
|
51
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
52
|
+
printApisHelp();
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
if (subcommand === "describe") {
|
|
56
|
+
return describeApiOperation(rest);
|
|
57
|
+
}
|
|
58
|
+
console.error(`Unknown apis command: ${subcommand}`);
|
|
59
|
+
printApisHelp();
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
49
62
|
if (command !== "studio") {
|
|
50
63
|
console.error(`Unknown command: ${command}`);
|
|
51
64
|
printHelp();
|
|
@@ -168,6 +181,84 @@ export function parseStudioOptions(args) {
|
|
|
168
181
|
installToken: raw.installToken ?? envString("TAPI_STUDIO_INSTALL_TOKEN"),
|
|
169
182
|
};
|
|
170
183
|
}
|
|
184
|
+
function parseApiOptions(args) {
|
|
185
|
+
let operation = "";
|
|
186
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
|
|
187
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
188
|
+
let appId = envString("TAPI_APP_ID");
|
|
189
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
190
|
+
const arg = args[index];
|
|
191
|
+
if (!arg)
|
|
192
|
+
continue;
|
|
193
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
194
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
198
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (arg.startsWith("--server=")) {
|
|
202
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (arg === "--api-key") {
|
|
206
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (arg.startsWith("--api-key=")) {
|
|
210
|
+
apiKey = arg.slice("--api-key=".length);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (arg === "--app") {
|
|
214
|
+
appId = requireOptionValue(args, ++index, "--app");
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (arg.startsWith("--app=")) {
|
|
218
|
+
appId = arg.slice("--app=".length);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (arg.startsWith("--")) {
|
|
222
|
+
throw new Error(`Unknown apis option: ${arg}`);
|
|
223
|
+
}
|
|
224
|
+
if (!operation) {
|
|
225
|
+
operation = arg;
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
throw new Error(`Unexpected apis argument: ${arg}`);
|
|
229
|
+
}
|
|
230
|
+
if (!operation) {
|
|
231
|
+
throw new Error("apis describe requires an operation like schwab.place_order.");
|
|
232
|
+
}
|
|
233
|
+
if (!apiKey) {
|
|
234
|
+
throw new Error("apis describe requires --api-key or TAPI_API_KEY.");
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
operation,
|
|
238
|
+
options: {
|
|
239
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
240
|
+
apiKey,
|
|
241
|
+
appId,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function describeApiOperation(args) {
|
|
246
|
+
try {
|
|
247
|
+
const { operation, options } = parseApiOptions(args);
|
|
248
|
+
const client = new TapiClient({
|
|
249
|
+
baseUrl: options.apiBaseUrl,
|
|
250
|
+
apiKey: options.apiKey,
|
|
251
|
+
appId: options.appId,
|
|
252
|
+
});
|
|
253
|
+
const description = await client.websiteApis.describe(operation);
|
|
254
|
+
console.log(JSON.stringify(description, null, 2));
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error(formatError(error));
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
171
262
|
export function getDefaultStudioCacheDir() {
|
|
172
263
|
if (process.platform === "win32") {
|
|
173
264
|
const localAppData = process.env.LOCALAPPDATA ??
|
|
@@ -252,9 +343,8 @@ export function getCliWideEventRoot() {
|
|
|
252
343
|
return override;
|
|
253
344
|
}
|
|
254
345
|
if (process.platform === "win32") {
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
return join(localAppData, "Tapi", "logs", "events");
|
|
346
|
+
const programData = process.env.PROGRAMDATA?.trim() || "C:\\ProgramData";
|
|
347
|
+
return join(programData, "Tapi", "logs", "events");
|
|
258
348
|
}
|
|
259
349
|
return join(process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state"), "tapi", "logs", "events");
|
|
260
350
|
}
|
|
@@ -555,10 +645,14 @@ async function fetchProtectedStudioManifest(apiBaseUrl, channel, installToken, f
|
|
|
555
645
|
Authorization: `Bearer ${installToken}`,
|
|
556
646
|
},
|
|
557
647
|
});
|
|
648
|
+
const responseBody = await readJsonBody(response);
|
|
558
649
|
if (!response.ok) {
|
|
559
|
-
|
|
650
|
+
const detail = typeof responseBody?.detail === "string" ? responseBody.detail : "";
|
|
651
|
+
throw new Error(detail
|
|
652
|
+
? `Failed to fetch protected Studio manifest: HTTP ${response.status} (${detail})`
|
|
653
|
+
: `Failed to fetch protected Studio manifest: HTTP ${response.status}`);
|
|
560
654
|
}
|
|
561
|
-
return validateStudioManifest(
|
|
655
|
+
return validateStudioManifest(responseBody);
|
|
562
656
|
}
|
|
563
657
|
async function fetchStudioManifest(manifestUrl, fetchImpl = fetch) {
|
|
564
658
|
const response = await fetchImpl(manifestUrl, {
|
|
@@ -971,39 +1065,54 @@ function readSdkVersion() {
|
|
|
971
1065
|
return packageJson.version;
|
|
972
1066
|
}
|
|
973
1067
|
function printHelp() {
|
|
974
|
-
console.log(`Tapi CLI
|
|
975
|
-
|
|
976
|
-
Usage:
|
|
977
|
-
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
978
|
-
tapi studio open
|
|
979
|
-
tapi studio doctor
|
|
980
|
-
tapi
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
studio
|
|
985
|
-
studio
|
|
986
|
-
doctor
|
|
1068
|
+
console.log(`Tapi CLI
|
|
1069
|
+
|
|
1070
|
+
Usage:
|
|
1071
|
+
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
1072
|
+
tapi studio open
|
|
1073
|
+
tapi studio doctor
|
|
1074
|
+
tapi apis describe <namespace.operation>
|
|
1075
|
+
tapi doctor
|
|
1076
|
+
|
|
1077
|
+
Commands:
|
|
1078
|
+
studio install Download, verify, and run the Tapi Studio installer
|
|
1079
|
+
studio open Open an installed Tapi Studio desktop app
|
|
1080
|
+
studio doctor Check local SDK and Studio release configuration
|
|
1081
|
+
apis describe Print a generated website API input/output contract
|
|
1082
|
+
doctor Alias for studio doctor
|
|
1083
|
+
`);
|
|
1084
|
+
}
|
|
1085
|
+
function printApisHelp() {
|
|
1086
|
+
console.log(`Tapi generated website API commands
|
|
1087
|
+
|
|
1088
|
+
Usage:
|
|
1089
|
+
tapi apis describe <namespace.operation> [--api-base-url URL] [--api-key KEY] [--app APP]
|
|
1090
|
+
|
|
1091
|
+
Options:
|
|
1092
|
+
--api-base-url <url> Tapi API base URL
|
|
1093
|
+
--server <url> Alias for --api-base-url
|
|
1094
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
1095
|
+
--app <id> SDK app/project id; defaults to TAPI_APP_ID
|
|
987
1096
|
`);
|
|
988
1097
|
}
|
|
989
1098
|
function printStudioHelp() {
|
|
990
|
-
console.log(`Tapi Studio commands
|
|
991
|
-
|
|
992
|
-
Usage:
|
|
993
|
-
tapi studio install [options]
|
|
994
|
-
tapi studio open [options]
|
|
995
|
-
tapi studio doctor [options]
|
|
996
|
-
|
|
997
|
-
Options:
|
|
998
|
-
--channel <name> Release channel: pilot, stable, or nightly
|
|
999
|
-
--api-base-url <url> Tapi API base URL for approval and protected downloads
|
|
1000
|
-
--server <url> Alias for --api-base-url
|
|
1001
|
-
--install-token <tok> Preissued Studio install token (skips browser sign-in)
|
|
1002
|
-
--manifest <url> Exact release manifest URL for doctor only
|
|
1003
|
-
--cache-dir <path> Installer download cache directory
|
|
1004
|
-
--download-only Download and verify without running the installer
|
|
1005
|
-
--silent Run the NSIS installer with /S
|
|
1006
|
-
--exe <path> Tapi Studio executable path for open/doctor
|
|
1099
|
+
console.log(`Tapi Studio commands
|
|
1100
|
+
|
|
1101
|
+
Usage:
|
|
1102
|
+
tapi studio install [options]
|
|
1103
|
+
tapi studio open [options]
|
|
1104
|
+
tapi studio doctor [options]
|
|
1105
|
+
|
|
1106
|
+
Options:
|
|
1107
|
+
--channel <name> Release channel: pilot, stable, or nightly
|
|
1108
|
+
--api-base-url <url> Tapi API base URL for approval and protected downloads
|
|
1109
|
+
--server <url> Alias for --api-base-url
|
|
1110
|
+
--install-token <tok> Preissued Studio install token (skips browser sign-in)
|
|
1111
|
+
--manifest <url> Exact release manifest URL for doctor only
|
|
1112
|
+
--cache-dir <path> Installer download cache directory
|
|
1113
|
+
--download-only Download and verify without running the installer
|
|
1114
|
+
--silent Run the NSIS installer with /S
|
|
1115
|
+
--exe <path> Tapi Studio executable path for open/doctor
|
|
1007
1116
|
`);
|
|
1008
1117
|
}
|
|
1009
1118
|
function formatError(error) {
|
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED