@tapi-dev/sdk 0.1.33 → 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 +160 -120
- package/dist/catalog.js +3 -2
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +1255 -232
- package/dist/client.d.ts +1 -0
- package/dist/client.js +11 -1
- package/dist/cloud-runs.js +5 -4
- package/dist/runs.js +7 -6
- package/dist/service-contract.d.ts +6 -0
- package/dist/service-contract.js +84 -0
- package/dist/services.d.ts +6 -5
- package/dist/services.js +119 -20
- package/dist/sessions.js +3 -2
- package/dist/triggers.d.ts +8 -8
- package/dist/triggers.js +61 -1
- package/dist/types.d.ts +55 -52
- package/dist/workspace.d.ts +2 -2
- package/dist/workspace.js +10 -6
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
import { createServer as createNetServer } from "node:net";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
|
-
import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
6
|
+
import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
7
7
|
import { mkdir, readFile, readdir, rename, rm, stat, statfs, unlink, writeFile } from "node:fs/promises";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
9
|
import { basename, dirname, join, resolve } from "node:path";
|
|
@@ -21,7 +21,7 @@ const DEFAULT_FIREBASE_API_KEY = "AIzaSyCDZR8lWyVQcWYfFdNZa4vuL4IWEC0h6gE";
|
|
|
21
21
|
const DEFAULT_CHANNEL = "pilot";
|
|
22
22
|
const SUPPORTED_STUDIO_PLATFORM = "windows-x86_64";
|
|
23
23
|
const DEFAULT_INSTALL_AUTH_TIMEOUT_MS = 120_000;
|
|
24
|
-
const PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS =
|
|
24
|
+
export const PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS = 120_000;
|
|
25
25
|
const PORTABLE_STUDIO_SERVER_READY_INTERVAL_MS = 250;
|
|
26
26
|
const DOWNLOAD_STALL_TIMEOUT_MS = 30_000;
|
|
27
27
|
const DOWNLOAD_PROGRESS_LOG_BYTES = 50 * 1024 * 1024;
|
|
@@ -33,6 +33,10 @@ const WINDOWS_SERVICE_COMMAND_TIMEOUT_MS = 30_000;
|
|
|
33
33
|
const MANAGED_RELEASE_MARKER = ".tapi-managed-release";
|
|
34
34
|
const MIN_EXTRACTION_REQUIRED_BYTES = 1024 * 1024 * 1024;
|
|
35
35
|
const EXTRACTION_REQUIRED_MULTIPLIER = 3;
|
|
36
|
+
const RETIRED_TRIGGER_TARGET_FIELD = "api" + "Request";
|
|
37
|
+
const RUNTIME_OWNER_FIELDS = ["owner"];
|
|
38
|
+
const RETIRED_RUNTIME_OWNER_ALIAS_FIELDS = ["runtimeOwner", "runtime_owner"];
|
|
39
|
+
const SERVICE_RUN_RUNTIME_OWNER = "service_run";
|
|
36
40
|
class StudioInstallApprovalError extends Error {
|
|
37
41
|
code;
|
|
38
42
|
status;
|
|
@@ -144,22 +148,77 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
144
148
|
}
|
|
145
149
|
return runServiceCommand(subcommand, rest);
|
|
146
150
|
}
|
|
147
|
-
if (command === "
|
|
148
|
-
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h"
|
|
149
|
-
|
|
151
|
+
if (command === "services") {
|
|
152
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
153
|
+
printServicesHelp();
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
if (hasHelpFlag(rest)) {
|
|
157
|
+
printServicesHelp();
|
|
150
158
|
return 0;
|
|
151
159
|
}
|
|
152
160
|
if (subcommand === "describe") {
|
|
153
|
-
return
|
|
161
|
+
return describeServiceOperation(rest);
|
|
154
162
|
}
|
|
155
163
|
if (subcommand === "sync") {
|
|
156
|
-
return
|
|
164
|
+
return syncServiceCatalog(rest);
|
|
165
|
+
}
|
|
166
|
+
console.error(`Unknown service-run command: ${subcommand}`);
|
|
167
|
+
printServicesHelp();
|
|
168
|
+
return 1;
|
|
169
|
+
}
|
|
170
|
+
if (command === "tapp") {
|
|
171
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
172
|
+
printTappHelp();
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
if (subcommand === "create") {
|
|
176
|
+
return createTapp(rest);
|
|
177
|
+
}
|
|
178
|
+
if (subcommand === "service") {
|
|
179
|
+
const serviceCommand = rest[0] || "";
|
|
180
|
+
if (serviceCommand === "add") {
|
|
181
|
+
return addTappService(rest.slice(1));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (subcommand === "queue") {
|
|
185
|
+
const queueCommand = rest[0] || "";
|
|
186
|
+
if (queueCommand === "add") {
|
|
187
|
+
return addTappQueue(rest.slice(1));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
console.error(`Unknown Tapp command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
|
|
191
|
+
printTappHelp();
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
if (command === "queue") {
|
|
195
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
196
|
+
printQueueHelp();
|
|
197
|
+
return 0;
|
|
198
|
+
}
|
|
199
|
+
if (subcommand === "create") {
|
|
200
|
+
return createQueue(rest);
|
|
201
|
+
}
|
|
202
|
+
console.error(`Unknown queue command: ${subcommand}`);
|
|
203
|
+
printQueueHelp();
|
|
204
|
+
return 1;
|
|
205
|
+
}
|
|
206
|
+
if (command === "runner") {
|
|
207
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
208
|
+
printRunnerHelp();
|
|
209
|
+
return 0;
|
|
157
210
|
}
|
|
158
|
-
if (subcommand === "
|
|
159
|
-
return
|
|
211
|
+
if (subcommand === "setup") {
|
|
212
|
+
return runRunnerSetup(rest);
|
|
160
213
|
}
|
|
161
|
-
|
|
162
|
-
|
|
214
|
+
if (subcommand === "slot") {
|
|
215
|
+
const slotCommand = rest[0] || "";
|
|
216
|
+
if (slotCommand === "set") {
|
|
217
|
+
return setRunnerSlot(rest.slice(1));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
console.error(`Unknown runner command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
|
|
221
|
+
printRunnerHelp();
|
|
163
222
|
return 1;
|
|
164
223
|
}
|
|
165
224
|
if (command === "triggers") {
|
|
@@ -168,7 +227,7 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
168
227
|
return 0;
|
|
169
228
|
}
|
|
170
229
|
if (subcommand === "sync") {
|
|
171
|
-
return
|
|
230
|
+
return syncServiceTriggers(rest);
|
|
172
231
|
}
|
|
173
232
|
console.error(`Unknown triggers command: ${subcommand}`);
|
|
174
233
|
printTriggersHelp();
|
|
@@ -306,73 +365,597 @@ export function parseStudioOptions(args) {
|
|
|
306
365
|
raw.installDir = resolve(requireOptionValue(args, ++index, "--install-dir"));
|
|
307
366
|
continue;
|
|
308
367
|
}
|
|
309
|
-
if (arg.startsWith("--install-dir=")) {
|
|
310
|
-
raw.installDir = resolve(arg.slice("--install-dir=".length));
|
|
368
|
+
if (arg.startsWith("--install-dir=")) {
|
|
369
|
+
raw.installDir = resolve(arg.slice("--install-dir=".length));
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (arg === "--exe") {
|
|
373
|
+
raw.exePath = resolve(requireOptionValue(args, ++index, "--exe"));
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (arg.startsWith("--exe=")) {
|
|
377
|
+
raw.exePath = resolve(arg.slice("--exe=".length));
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
if (arg === "--download-only") {
|
|
381
|
+
raw.downloadOnly = true;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (arg === "--silent") {
|
|
385
|
+
raw.silent = true;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (arg === "--help" || arg === "-h") {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
392
|
+
}
|
|
393
|
+
const workspace = skipWorkspace ? undefined : loadWorkspace(workspaceSearchRoot ?? process.cwd());
|
|
394
|
+
const channel = raw.channel ?? parseChannel(envString("TAPI_STUDIO_CHANNEL") ?? DEFAULT_CHANNEL);
|
|
395
|
+
const apiBaseUrl = normalizeHttpUrl(raw.apiBaseUrl
|
|
396
|
+
?? envString("TAPI_STUDIO_API_BASE_URL")
|
|
397
|
+
?? envString("TAPI_BASE_URL")
|
|
398
|
+
?? envString("TAPI_STUDIO_SERVER_URL")
|
|
399
|
+
?? workspace?.apiBaseUrl
|
|
400
|
+
?? DEFAULT_STUDIO_API_BASE_URL, "Studio API base URL");
|
|
401
|
+
const downloadsBaseUrl = normalizeHttpUrl(envString("TAPI_DOWNLOADS_BASE_URL") ?? DEFAULT_DOWNLOADS_BASE_URL, "TAPI_DOWNLOADS_BASE_URL");
|
|
402
|
+
const explicitManifestUrl = raw.manifestUrlOverride ?? envString("TAPI_STUDIO_MANIFEST_URL");
|
|
403
|
+
const manifestUrl = explicitManifestUrl
|
|
404
|
+
? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL")
|
|
405
|
+
: `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
|
|
406
|
+
const projectId = raw.projectId ?? envString("TAPI_PROJECT_ID") ?? workspace?.projectId;
|
|
407
|
+
const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
|
|
408
|
+
const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
|
|
409
|
+
const installToken = raw.installToken ?? envInstallToken;
|
|
410
|
+
return {
|
|
411
|
+
channel,
|
|
412
|
+
apiBaseUrl,
|
|
413
|
+
manifestUrl,
|
|
414
|
+
manifestUrlOverride: explicitManifestUrl ? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL") : undefined,
|
|
415
|
+
cacheDir: raw.cacheDir ?? getDefaultStudioCacheDir(),
|
|
416
|
+
installDir: raw.installDir ?? resolve(envString("TAPI_STUDIO_INSTALL_DIR") ?? getDefaultPortableStudioServerReleasesDir()),
|
|
417
|
+
downloadOnly: raw.downloadOnly ?? false,
|
|
418
|
+
silent: raw.silent ?? false,
|
|
419
|
+
exePath: raw.exePath ?? envString("TAPI_STUDIO_EXE"),
|
|
420
|
+
installToken,
|
|
421
|
+
installTokenSource: raw.installToken ? "option" : installToken ? "env" : undefined,
|
|
422
|
+
workspace,
|
|
423
|
+
workspaceRoot: workspace?.root,
|
|
424
|
+
projectId,
|
|
425
|
+
projectSlug,
|
|
426
|
+
workspaceMode: Boolean(projectId || workspace),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function parseServiceOptions(args) {
|
|
430
|
+
let operation = "";
|
|
431
|
+
const workspace = loadWorkspace();
|
|
432
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
433
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
434
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
435
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
436
|
+
const arg = args[index];
|
|
437
|
+
if (!arg)
|
|
438
|
+
continue;
|
|
439
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
440
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
444
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (arg.startsWith("--server=")) {
|
|
448
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (arg === "--api-key") {
|
|
452
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (arg.startsWith("--api-key=")) {
|
|
456
|
+
apiKey = arg.slice("--api-key=".length);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (arg === "--project") {
|
|
460
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (arg.startsWith("--project=")) {
|
|
464
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (arg.startsWith("--")) {
|
|
468
|
+
throw new Error(`Unknown services option: ${arg}`);
|
|
469
|
+
}
|
|
470
|
+
if (!operation) {
|
|
471
|
+
operation = arg;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
throw new Error(`Unexpected services argument: ${arg}`);
|
|
475
|
+
}
|
|
476
|
+
if (!operation) {
|
|
477
|
+
throw new Error("services describe requires a service run like schwab.place_order.");
|
|
478
|
+
}
|
|
479
|
+
if (!apiKey) {
|
|
480
|
+
throw new Error("services describe requires --api-key or TAPI_API_KEY.");
|
|
481
|
+
}
|
|
482
|
+
if (!projectId) {
|
|
483
|
+
throw new Error("services describe requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
operation,
|
|
487
|
+
options: {
|
|
488
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
489
|
+
apiKey,
|
|
490
|
+
projectId,
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
async function describeServiceOperation(args) {
|
|
495
|
+
try {
|
|
496
|
+
const { operation, options } = parseServiceOptions(args);
|
|
497
|
+
const client = new TapiClient({
|
|
498
|
+
baseUrl: options.apiBaseUrl,
|
|
499
|
+
apiKey: options.apiKey,
|
|
500
|
+
tappId: options.projectId,
|
|
501
|
+
projectId: options.projectId,
|
|
502
|
+
});
|
|
503
|
+
const description = await withCliSpinner(`Fetching Tapi service-run contract for ${operation}`, () => client.services.describe(operation));
|
|
504
|
+
console.log(JSON.stringify(description, null, 2));
|
|
505
|
+
return 0;
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
console.error(formatError(error));
|
|
509
|
+
return 1;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
function parseApiWorkspaceOptions(args) {
|
|
513
|
+
const workspace = loadWorkspace();
|
|
514
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
515
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
516
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
517
|
+
let catalogPath = workspace?.config.services?.catalog || ".tapi/services/catalog.json";
|
|
518
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
519
|
+
const arg = args[index];
|
|
520
|
+
if (!arg)
|
|
521
|
+
continue;
|
|
522
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
523
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
527
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (arg.startsWith("--server=")) {
|
|
531
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (arg === "--api-key") {
|
|
535
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (arg.startsWith("--api-key=")) {
|
|
539
|
+
apiKey = arg.slice("--api-key=".length);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
if (arg === "--project") {
|
|
543
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
if (arg.startsWith("--project=")) {
|
|
547
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (arg === "--catalog") {
|
|
551
|
+
catalogPath = requireOptionValue(args, ++index, "--catalog");
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
if (arg.startsWith("--catalog=")) {
|
|
555
|
+
catalogPath = arg.slice("--catalog=".length);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
throw new Error(`Unknown services option: ${arg}`);
|
|
559
|
+
}
|
|
560
|
+
if (!apiKey) {
|
|
561
|
+
throw new Error("services command requires --api-key or TAPI_API_KEY.");
|
|
562
|
+
}
|
|
563
|
+
if (!projectId) {
|
|
564
|
+
throw new Error("services command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
565
|
+
}
|
|
566
|
+
const root = workspace?.root || process.cwd();
|
|
567
|
+
return {
|
|
568
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
569
|
+
apiKey,
|
|
570
|
+
projectId,
|
|
571
|
+
catalogPath: resolve(root, catalogPath),
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
async function syncServiceCatalog(args) {
|
|
575
|
+
try {
|
|
576
|
+
const options = parseApiWorkspaceOptions(args);
|
|
577
|
+
const catalog = await withCliSpinner("Fetching Tapi service-run catalog", () => fetchApiCatalog(options));
|
|
578
|
+
await writeJsonFile(options.catalogPath, catalog);
|
|
579
|
+
console.log(`Synced Tapi service-run catalog: ${options.catalogPath}`);
|
|
580
|
+
return 0;
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
console.error(formatError(error));
|
|
584
|
+
return 1;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function parseTappServiceAddOptions(args) {
|
|
588
|
+
const workspace = loadWorkspace();
|
|
589
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
590
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
591
|
+
let tappId = "";
|
|
592
|
+
let serviceCall = "";
|
|
593
|
+
let serviceMapId = "";
|
|
594
|
+
let entry = "";
|
|
595
|
+
let expectedRevision = "";
|
|
596
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
597
|
+
const arg = args[index];
|
|
598
|
+
if (!arg)
|
|
599
|
+
continue;
|
|
600
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
601
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
605
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (arg.startsWith("--server=")) {
|
|
609
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
if (arg === "--api-key") {
|
|
613
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (arg.startsWith("--api-key=")) {
|
|
617
|
+
apiKey = arg.slice("--api-key=".length);
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (arg === "--service-map") {
|
|
621
|
+
serviceMapId = requireOptionValue(args, ++index, "--service-map");
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (arg.startsWith("--service-map=")) {
|
|
625
|
+
serviceMapId = arg.slice("--service-map=".length);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (arg === "--entry") {
|
|
629
|
+
entry = requireOptionValue(args, ++index, "--entry");
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (arg.startsWith("--entry=")) {
|
|
633
|
+
entry = arg.slice("--entry=".length);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (arg === "--expected-revision") {
|
|
637
|
+
expectedRevision = requireOptionValue(args, ++index, "--expected-revision");
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
if (arg.startsWith("--expected-revision=")) {
|
|
641
|
+
expectedRevision = arg.slice("--expected-revision=".length);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (arg.startsWith("--")) {
|
|
645
|
+
throw new Error(`Unknown tapp service add option: ${arg}`);
|
|
646
|
+
}
|
|
647
|
+
if (!tappId) {
|
|
648
|
+
tappId = normalizeProjectValue(arg, "tapp");
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
if (!serviceCall) {
|
|
652
|
+
serviceCall = arg.trim();
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
throw new Error(`Unexpected tapp service add argument: ${arg}`);
|
|
656
|
+
}
|
|
657
|
+
if (!apiKey) {
|
|
658
|
+
throw new Error("tapp service add requires --api-key or TAPI_API_KEY.");
|
|
659
|
+
}
|
|
660
|
+
if (!tappId) {
|
|
661
|
+
throw new Error("tapp service add requires a Tapp id.");
|
|
662
|
+
}
|
|
663
|
+
if (!serviceCall || !serviceCall.includes(".")) {
|
|
664
|
+
throw new Error("tapp service add requires a service call like schwab.place_order.");
|
|
665
|
+
}
|
|
666
|
+
if (!serviceMapId) {
|
|
667
|
+
throw new Error("tapp service add requires --service-map.");
|
|
668
|
+
}
|
|
669
|
+
if (!entry) {
|
|
670
|
+
throw new Error("tapp service add requires --entry.");
|
|
671
|
+
}
|
|
672
|
+
return {
|
|
673
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
674
|
+
apiKey,
|
|
675
|
+
tappId,
|
|
676
|
+
serviceCall,
|
|
677
|
+
serviceMapId,
|
|
678
|
+
entry,
|
|
679
|
+
expectedRevision,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
function parseTappCreateOptions(args) {
|
|
683
|
+
const workspace = loadWorkspace();
|
|
684
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
685
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
686
|
+
let tappId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
687
|
+
let name = "";
|
|
688
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
689
|
+
const arg = args[index];
|
|
690
|
+
if (!arg)
|
|
691
|
+
continue;
|
|
692
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
693
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
697
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (arg.startsWith("--server=")) {
|
|
701
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (arg === "--api-key") {
|
|
705
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
if (arg.startsWith("--api-key=")) {
|
|
709
|
+
apiKey = arg.slice("--api-key=".length);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (arg === "--tapp-id" || arg === "--project") {
|
|
713
|
+
tappId = normalizeProjectValue(requireOptionValue(args, ++index, arg), "tapp");
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (arg.startsWith("--tapp-id=")) {
|
|
717
|
+
tappId = normalizeProjectValue(arg.slice("--tapp-id=".length), "tapp");
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (arg.startsWith("--project=")) {
|
|
721
|
+
tappId = normalizeProjectValue(arg.slice("--project=".length), "tapp");
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (arg === "--name") {
|
|
725
|
+
name = requireOptionValue(args, ++index, "--name").trim();
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
if (arg.startsWith("--name=")) {
|
|
729
|
+
name = arg.slice("--name=".length).trim();
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (arg.startsWith("--")) {
|
|
733
|
+
throw new Error(`Unknown tapp create option: ${arg}`);
|
|
734
|
+
}
|
|
735
|
+
if (!name) {
|
|
736
|
+
name = arg.trim();
|
|
737
|
+
if (!tappId) {
|
|
738
|
+
tappId = normalizeProjectValue(name, "tapp");
|
|
739
|
+
}
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
throw new Error(`Unexpected tapp create argument: ${arg}`);
|
|
743
|
+
}
|
|
744
|
+
if (!apiKey) {
|
|
745
|
+
throw new Error("tapp create requires --api-key or TAPI_API_KEY.");
|
|
746
|
+
}
|
|
747
|
+
if (!tappId) {
|
|
748
|
+
throw new Error("tapp create requires a Tapp id or name.");
|
|
749
|
+
}
|
|
750
|
+
if (!name) {
|
|
751
|
+
name = tappId;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
755
|
+
apiKey,
|
|
756
|
+
tappId,
|
|
757
|
+
name,
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
async function createTapp(args) {
|
|
761
|
+
try {
|
|
762
|
+
const options = parseTappCreateOptions(args);
|
|
763
|
+
const tapp = await withCliSpinner(`Creating Tapi Tapp ${options.tappId}`, () => postTappCreate(options));
|
|
764
|
+
console.log(JSON.stringify(tapp, null, 2));
|
|
765
|
+
return 0;
|
|
766
|
+
}
|
|
767
|
+
catch (error) {
|
|
768
|
+
console.error(formatError(error));
|
|
769
|
+
return 1;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async function postTappCreate(options, fetchImpl = fetch) {
|
|
773
|
+
const response = await fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps`, {
|
|
774
|
+
method: "POST",
|
|
775
|
+
headers: {
|
|
776
|
+
"Content-Type": "application/json",
|
|
777
|
+
"X-API-Key": options.apiKey,
|
|
778
|
+
"X-Tapi-Project": options.tappId,
|
|
779
|
+
},
|
|
780
|
+
body: JSON.stringify({
|
|
781
|
+
tappId: options.tappId,
|
|
782
|
+
name: options.name,
|
|
783
|
+
}),
|
|
784
|
+
});
|
|
785
|
+
const body = await readJsonBody(response);
|
|
786
|
+
if (!response.ok) {
|
|
787
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
788
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
789
|
+
}
|
|
790
|
+
return body ?? {};
|
|
791
|
+
}
|
|
792
|
+
async function addTappService(args) {
|
|
793
|
+
try {
|
|
794
|
+
const options = parseTappServiceAddOptions(args);
|
|
795
|
+
const operation = await withCliSpinner(`Adding Tapi service ${options.serviceCall} to ${options.tappId}`, () => postTappServiceAdd(options));
|
|
796
|
+
console.log(JSON.stringify(operation, null, 2));
|
|
797
|
+
return 0;
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
console.error(formatError(error));
|
|
801
|
+
return 1;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async function postTappServiceAdd(options) {
|
|
805
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/services/${encodeURIComponent(options.serviceCall)}`, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: {
|
|
808
|
+
"Content-Type": "application/json",
|
|
809
|
+
"X-API-Key": options.apiKey,
|
|
810
|
+
"X-Tapi-Project": options.tappId,
|
|
811
|
+
},
|
|
812
|
+
body: JSON.stringify({
|
|
813
|
+
serviceMapId: options.serviceMapId,
|
|
814
|
+
entry: options.entry,
|
|
815
|
+
...(options.expectedRevision ? { expectedRevision: options.expectedRevision } : {}),
|
|
816
|
+
}),
|
|
817
|
+
});
|
|
818
|
+
const body = await readJsonBody(response);
|
|
819
|
+
if (!response.ok) {
|
|
820
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
821
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
822
|
+
}
|
|
823
|
+
return body ?? {};
|
|
824
|
+
}
|
|
825
|
+
function parseQueueCreateOptions(args) {
|
|
826
|
+
const workspace = loadWorkspace();
|
|
827
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
828
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
829
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
830
|
+
let displayName = "";
|
|
831
|
+
let maxSlots = 8;
|
|
832
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
833
|
+
const arg = args[index];
|
|
834
|
+
if (!arg)
|
|
835
|
+
continue;
|
|
836
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
837
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
841
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (arg.startsWith("--server=")) {
|
|
845
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (arg === "--api-key") {
|
|
849
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (arg.startsWith("--api-key=")) {
|
|
853
|
+
apiKey = arg.slice("--api-key=".length);
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (arg === "--project") {
|
|
857
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (arg.startsWith("--project=")) {
|
|
861
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
if (arg === "--max-slots") {
|
|
865
|
+
maxSlots = parsePositiveInteger(requireOptionValue(args, ++index, "--max-slots"), "--max-slots");
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (arg.startsWith("--max-slots=")) {
|
|
869
|
+
maxSlots = parsePositiveInteger(arg.slice("--max-slots=".length), "--max-slots");
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (arg.startsWith("--")) {
|
|
873
|
+
throw new Error(`Unknown queue create option: ${arg}`);
|
|
874
|
+
}
|
|
875
|
+
if (!displayName) {
|
|
876
|
+
displayName = arg.trim();
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
throw new Error(`Unexpected queue create argument: ${arg}`);
|
|
880
|
+
}
|
|
881
|
+
if (!apiKey) {
|
|
882
|
+
throw new Error("queue create requires --api-key or TAPI_API_KEY.");
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
886
|
+
apiKey,
|
|
887
|
+
projectId,
|
|
888
|
+
displayName,
|
|
889
|
+
maxSlots,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
function parseTappQueueAddOptions(args) {
|
|
893
|
+
const workspace = loadWorkspace();
|
|
894
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
895
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
896
|
+
let tappId = "";
|
|
897
|
+
let queueId = "";
|
|
898
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
899
|
+
const arg = args[index];
|
|
900
|
+
if (!arg)
|
|
901
|
+
continue;
|
|
902
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
903
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
907
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
311
908
|
continue;
|
|
312
909
|
}
|
|
313
|
-
if (arg
|
|
314
|
-
|
|
910
|
+
if (arg.startsWith("--server=")) {
|
|
911
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
315
912
|
continue;
|
|
316
913
|
}
|
|
317
|
-
if (arg
|
|
318
|
-
|
|
914
|
+
if (arg === "--api-key") {
|
|
915
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
319
916
|
continue;
|
|
320
917
|
}
|
|
321
|
-
if (arg
|
|
322
|
-
|
|
918
|
+
if (arg.startsWith("--api-key=")) {
|
|
919
|
+
apiKey = arg.slice("--api-key=".length);
|
|
323
920
|
continue;
|
|
324
921
|
}
|
|
325
|
-
if (arg
|
|
326
|
-
|
|
922
|
+
if (arg.startsWith("--")) {
|
|
923
|
+
throw new Error(`Unknown tapp queue add option: ${arg}`);
|
|
924
|
+
}
|
|
925
|
+
if (!tappId) {
|
|
926
|
+
tappId = normalizeProjectValue(arg, "tapp");
|
|
327
927
|
continue;
|
|
328
928
|
}
|
|
329
|
-
if (
|
|
929
|
+
if (!queueId) {
|
|
930
|
+
queueId = arg.trim();
|
|
330
931
|
continue;
|
|
331
932
|
}
|
|
332
|
-
throw new Error(`
|
|
933
|
+
throw new Error(`Unexpected tapp queue add argument: ${arg}`);
|
|
934
|
+
}
|
|
935
|
+
if (!apiKey) {
|
|
936
|
+
throw new Error("tapp queue add requires --api-key or TAPI_API_KEY.");
|
|
937
|
+
}
|
|
938
|
+
if (!tappId) {
|
|
939
|
+
throw new Error("tapp queue add requires a Tapp id.");
|
|
940
|
+
}
|
|
941
|
+
if (!queueId) {
|
|
942
|
+
throw new Error("tapp queue add requires a queue id.");
|
|
333
943
|
}
|
|
334
|
-
const workspace = skipWorkspace ? undefined : loadWorkspace(workspaceSearchRoot ?? process.cwd());
|
|
335
|
-
const channel = raw.channel ?? parseChannel(envString("TAPI_STUDIO_CHANNEL") ?? DEFAULT_CHANNEL);
|
|
336
|
-
const apiBaseUrl = normalizeHttpUrl(raw.apiBaseUrl
|
|
337
|
-
?? envString("TAPI_STUDIO_API_BASE_URL")
|
|
338
|
-
?? envString("TAPI_BASE_URL")
|
|
339
|
-
?? envString("TAPI_STUDIO_SERVER_URL")
|
|
340
|
-
?? workspace?.apiBaseUrl
|
|
341
|
-
?? DEFAULT_STUDIO_API_BASE_URL, "Studio API base URL");
|
|
342
|
-
const downloadsBaseUrl = normalizeHttpUrl(envString("TAPI_DOWNLOADS_BASE_URL") ?? DEFAULT_DOWNLOADS_BASE_URL, "TAPI_DOWNLOADS_BASE_URL");
|
|
343
|
-
const explicitManifestUrl = raw.manifestUrlOverride ?? envString("TAPI_STUDIO_MANIFEST_URL");
|
|
344
|
-
const manifestUrl = explicitManifestUrl
|
|
345
|
-
? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL")
|
|
346
|
-
: `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
|
|
347
|
-
const projectId = raw.projectId ?? envString("TAPI_PROJECT_ID") ?? workspace?.projectId;
|
|
348
|
-
const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
|
|
349
|
-
const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
|
|
350
|
-
const installToken = raw.installToken ?? envInstallToken;
|
|
351
944
|
return {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
cacheDir: raw.cacheDir ?? getDefaultStudioCacheDir(),
|
|
357
|
-
installDir: raw.installDir ?? resolve(envString("TAPI_STUDIO_INSTALL_DIR") ?? getDefaultPortableStudioServerReleasesDir()),
|
|
358
|
-
downloadOnly: raw.downloadOnly ?? false,
|
|
359
|
-
silent: raw.silent ?? false,
|
|
360
|
-
exePath: raw.exePath ?? envString("TAPI_STUDIO_EXE"),
|
|
361
|
-
installToken,
|
|
362
|
-
installTokenSource: raw.installToken ? "option" : installToken ? "env" : undefined,
|
|
363
|
-
workspace,
|
|
364
|
-
workspaceRoot: workspace?.root,
|
|
365
|
-
projectId,
|
|
366
|
-
projectSlug,
|
|
367
|
-
workspaceMode: Boolean(projectId || workspace),
|
|
945
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
946
|
+
apiKey,
|
|
947
|
+
tappId,
|
|
948
|
+
queueId,
|
|
368
949
|
};
|
|
369
950
|
}
|
|
370
|
-
function
|
|
371
|
-
let operation = "";
|
|
951
|
+
function parseRunnerSlotSetOptions(args) {
|
|
372
952
|
const workspace = loadWorkspace();
|
|
373
953
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
374
954
|
let apiKey = envString("TAPI_API_KEY") || "";
|
|
375
955
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
956
|
+
let runnerId = "";
|
|
957
|
+
let slotIndex = 0;
|
|
958
|
+
let queueId = "";
|
|
376
959
|
for (let index = 0; index < args.length; index += 1) {
|
|
377
960
|
const arg = args[index];
|
|
378
961
|
if (!arg)
|
|
@@ -406,42 +989,51 @@ function parseApiOptions(args) {
|
|
|
406
989
|
continue;
|
|
407
990
|
}
|
|
408
991
|
if (arg.startsWith("--")) {
|
|
409
|
-
throw new Error(`Unknown
|
|
992
|
+
throw new Error(`Unknown runner slot set option: ${arg}`);
|
|
410
993
|
}
|
|
411
|
-
if (!
|
|
412
|
-
|
|
994
|
+
if (!runnerId) {
|
|
995
|
+
runnerId = arg.trim();
|
|
413
996
|
continue;
|
|
414
997
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
998
|
+
if (!slotIndex) {
|
|
999
|
+
slotIndex = parsePositiveInteger(arg.trim(), "slot index");
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (!queueId) {
|
|
1003
|
+
queueId = arg.trim();
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
throw new Error(`Unexpected runner slot set argument: ${arg}`);
|
|
419
1007
|
}
|
|
420
1008
|
if (!apiKey) {
|
|
421
|
-
throw new Error("
|
|
1009
|
+
throw new Error("runner slot set requires --api-key or TAPI_API_KEY.");
|
|
422
1010
|
}
|
|
423
1011
|
if (!projectId) {
|
|
424
|
-
throw new Error("
|
|
1012
|
+
throw new Error("runner slot set requires --project or TAPI_PROJECT_ID.");
|
|
1013
|
+
}
|
|
1014
|
+
if (!runnerId) {
|
|
1015
|
+
throw new Error("runner slot set requires a runner id.");
|
|
1016
|
+
}
|
|
1017
|
+
if (!slotIndex) {
|
|
1018
|
+
throw new Error("runner slot set requires a slot index.");
|
|
1019
|
+
}
|
|
1020
|
+
if (!queueId) {
|
|
1021
|
+
throw new Error("runner slot set requires a queue id.");
|
|
425
1022
|
}
|
|
426
1023
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1024
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
1025
|
+
apiKey,
|
|
1026
|
+
projectId,
|
|
1027
|
+
runnerId,
|
|
1028
|
+
slotIndex,
|
|
1029
|
+
queueId,
|
|
433
1030
|
};
|
|
434
1031
|
}
|
|
435
|
-
async function
|
|
1032
|
+
async function createQueue(args) {
|
|
436
1033
|
try {
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
apiKey: options.apiKey,
|
|
441
|
-
projectId: options.projectId,
|
|
442
|
-
});
|
|
443
|
-
const description = await withCliSpinner(`Fetching Tapi API description for ${operation}`, () => client.services.describe(operation));
|
|
444
|
-
console.log(JSON.stringify(description, null, 2));
|
|
1034
|
+
const options = parseQueueCreateOptions(args);
|
|
1035
|
+
const queue = await withCliSpinner(`Creating Tapi queue`, () => postQueueCreate(options));
|
|
1036
|
+
console.log(JSON.stringify(queue, null, 2));
|
|
445
1037
|
return 0;
|
|
446
1038
|
}
|
|
447
1039
|
catch (error) {
|
|
@@ -449,13 +1041,95 @@ async function describeApiOperation(args) {
|
|
|
449
1041
|
return 1;
|
|
450
1042
|
}
|
|
451
1043
|
}
|
|
452
|
-
function
|
|
1044
|
+
async function addTappQueue(args) {
|
|
1045
|
+
try {
|
|
1046
|
+
const options = parseTappQueueAddOptions(args);
|
|
1047
|
+
const result = await withCliSpinner(`Attaching Tapi queue ${options.queueId} to ${options.tappId}`, () => postTappQueueAdd(options));
|
|
1048
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1049
|
+
return 0;
|
|
1050
|
+
}
|
|
1051
|
+
catch (error) {
|
|
1052
|
+
console.error(formatError(error));
|
|
1053
|
+
return 1;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
async function setRunnerSlot(args) {
|
|
1057
|
+
try {
|
|
1058
|
+
const options = parseRunnerSlotSetOptions(args);
|
|
1059
|
+
const result = await withCliSpinner(`Assigning runner slot ${options.runnerId}/${options.slotIndex} to queue ${options.queueId}`, () => putRunnerSlot(options));
|
|
1060
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1061
|
+
return 0;
|
|
1062
|
+
}
|
|
1063
|
+
catch (error) {
|
|
1064
|
+
console.error(formatError(error));
|
|
1065
|
+
return 1;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
async function postQueueCreate(options) {
|
|
1069
|
+
const headers = {
|
|
1070
|
+
"Content-Type": "application/json",
|
|
1071
|
+
"X-API-Key": options.apiKey,
|
|
1072
|
+
};
|
|
1073
|
+
if (options.projectId) {
|
|
1074
|
+
headers["X-Tapi-Project"] = options.projectId;
|
|
1075
|
+
}
|
|
1076
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1077
|
+
method: "POST",
|
|
1078
|
+
headers,
|
|
1079
|
+
body: JSON.stringify({
|
|
1080
|
+
...(options.displayName ? { displayName: options.displayName } : {}),
|
|
1081
|
+
maxSlots: options.maxSlots,
|
|
1082
|
+
}),
|
|
1083
|
+
});
|
|
1084
|
+
const body = await readJsonBody(response);
|
|
1085
|
+
if (!response.ok) {
|
|
1086
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1087
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1088
|
+
}
|
|
1089
|
+
return body ?? {};
|
|
1090
|
+
}
|
|
1091
|
+
async function postTappQueueAdd(options) {
|
|
1092
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/queues`, {
|
|
1093
|
+
method: "POST",
|
|
1094
|
+
headers: {
|
|
1095
|
+
"Content-Type": "application/json",
|
|
1096
|
+
"X-API-Key": options.apiKey,
|
|
1097
|
+
"X-Tapi-Project": options.tappId,
|
|
1098
|
+
},
|
|
1099
|
+
body: JSON.stringify({ queueId: options.queueId }),
|
|
1100
|
+
});
|
|
1101
|
+
const body = await readJsonBody(response);
|
|
1102
|
+
if (!response.ok) {
|
|
1103
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1104
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1105
|
+
}
|
|
1106
|
+
return body ?? {};
|
|
1107
|
+
}
|
|
1108
|
+
async function putRunnerSlot(options) {
|
|
1109
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(options.slotIndex))}`, {
|
|
1110
|
+
method: "PUT",
|
|
1111
|
+
headers: {
|
|
1112
|
+
"Content-Type": "application/json",
|
|
1113
|
+
"X-API-Key": options.apiKey,
|
|
1114
|
+
"X-Tapi-Project": options.projectId,
|
|
1115
|
+
},
|
|
1116
|
+
body: JSON.stringify({ queueId: options.queueId }),
|
|
1117
|
+
});
|
|
1118
|
+
const body = await readJsonBody(response);
|
|
1119
|
+
if (!response.ok) {
|
|
1120
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1121
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1122
|
+
}
|
|
1123
|
+
return body ?? {};
|
|
1124
|
+
}
|
|
1125
|
+
export function parseRunnerSetupOptions(args) {
|
|
453
1126
|
const workspace = loadWorkspace();
|
|
454
1127
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
455
1128
|
let apiKey = envString("TAPI_API_KEY") || "";
|
|
456
1129
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
457
|
-
let
|
|
458
|
-
let
|
|
1130
|
+
let runnerId = envString("TAPI_RUNNER_ID") || "";
|
|
1131
|
+
let port = Number(envString("TAPI_RUNNER_SETUP_PORT") || "17687");
|
|
1132
|
+
let openBrowser = true;
|
|
459
1133
|
for (let index = 0; index < args.length; index += 1) {
|
|
460
1134
|
const arg = args[index];
|
|
461
1135
|
if (!arg)
|
|
@@ -488,45 +1162,67 @@ function parseApiWorkspaceOptions(args) {
|
|
|
488
1162
|
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
489
1163
|
continue;
|
|
490
1164
|
}
|
|
491
|
-
if (arg === "--
|
|
492
|
-
|
|
1165
|
+
if (arg === "--runner-id") {
|
|
1166
|
+
runnerId = requireOptionValue(args, ++index, "--runner-id").trim();
|
|
493
1167
|
continue;
|
|
494
1168
|
}
|
|
495
|
-
if (arg.startsWith("--
|
|
496
|
-
|
|
1169
|
+
if (arg.startsWith("--runner-id=")) {
|
|
1170
|
+
runnerId = arg.slice("--runner-id=".length).trim();
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
if (arg === "--port") {
|
|
1174
|
+
port = parseNonNegativeInteger(requireOptionValue(args, ++index, "--port"), "--port");
|
|
497
1175
|
continue;
|
|
498
1176
|
}
|
|
499
|
-
if (arg
|
|
500
|
-
|
|
1177
|
+
if (arg.startsWith("--port=")) {
|
|
1178
|
+
port = parseNonNegativeInteger(arg.slice("--port=".length), "--port");
|
|
501
1179
|
continue;
|
|
502
1180
|
}
|
|
503
|
-
if (arg
|
|
504
|
-
|
|
1181
|
+
if (arg === "--no-open") {
|
|
1182
|
+
openBrowser = false;
|
|
505
1183
|
continue;
|
|
506
1184
|
}
|
|
507
|
-
|
|
1185
|
+
if (arg.startsWith("--")) {
|
|
1186
|
+
throw new Error(`Unknown runner setup option: ${arg}`);
|
|
1187
|
+
}
|
|
1188
|
+
if (!runnerId) {
|
|
1189
|
+
runnerId = arg.trim();
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
throw new Error(`Unexpected runner setup argument: ${arg}`);
|
|
508
1193
|
}
|
|
509
1194
|
if (!apiKey) {
|
|
510
|
-
throw new Error("
|
|
1195
|
+
throw new Error("runner setup requires --api-key or TAPI_API_KEY.");
|
|
511
1196
|
}
|
|
512
1197
|
if (!projectId) {
|
|
513
|
-
throw new Error("
|
|
1198
|
+
throw new Error("runner setup requires --project or TAPI_PROJECT_ID.");
|
|
1199
|
+
}
|
|
1200
|
+
if (!runnerId) {
|
|
1201
|
+
throw new Error("runner setup requires --runner-id or TAPI_RUNNER_ID.");
|
|
1202
|
+
}
|
|
1203
|
+
if (!Number.isInteger(port) || port < 0) {
|
|
1204
|
+
throw new Error("--port must be a non-negative integer.");
|
|
514
1205
|
}
|
|
515
|
-
const root = workspace?.root || process.cwd();
|
|
516
1206
|
return {
|
|
517
1207
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
518
1208
|
apiKey,
|
|
519
1209
|
projectId,
|
|
520
|
-
|
|
521
|
-
|
|
1210
|
+
runnerId,
|
|
1211
|
+
port,
|
|
1212
|
+
openBrowser,
|
|
522
1213
|
};
|
|
523
1214
|
}
|
|
524
|
-
async function
|
|
1215
|
+
async function runRunnerSetup(args) {
|
|
525
1216
|
try {
|
|
526
|
-
const options =
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
1217
|
+
const options = parseRunnerSetupOptions(args);
|
|
1218
|
+
const handle = await startRunnerSetupServer(options);
|
|
1219
|
+
if (options.openBrowser) {
|
|
1220
|
+
openUrlInBrowser(handle.url);
|
|
1221
|
+
}
|
|
1222
|
+
console.log(`Tapi runner setup: ${handle.url}`);
|
|
1223
|
+
console.log("Press Ctrl+C to stop the setup server.");
|
|
1224
|
+
await waitForProcessSignal();
|
|
1225
|
+
await handle.close();
|
|
530
1226
|
return 0;
|
|
531
1227
|
}
|
|
532
1228
|
catch (error) {
|
|
@@ -534,19 +1230,216 @@ async function syncApiCatalog(args) {
|
|
|
534
1230
|
return 1;
|
|
535
1231
|
}
|
|
536
1232
|
}
|
|
537
|
-
async function
|
|
1233
|
+
export async function startRunnerSetupServer(options, fetchImpl = fetch) {
|
|
1234
|
+
const server = createServer((request, response) => {
|
|
1235
|
+
void handleRunnerSetupRequest(request, response, options, fetchImpl);
|
|
1236
|
+
});
|
|
1237
|
+
await listenOnLocalhost(server, options.port);
|
|
1238
|
+
const address = server.address();
|
|
1239
|
+
const port = typeof address === "object" && address ? address.port : options.port;
|
|
1240
|
+
return {
|
|
1241
|
+
url: `http://127.0.0.1:${port}/`,
|
|
1242
|
+
port,
|
|
1243
|
+
close: () => closeServer(server),
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
export async function executeRunnerSetupAction(options, action, fetchImpl = fetch) {
|
|
1247
|
+
let queueId = String(action.queueId || "").trim();
|
|
1248
|
+
const maxSlots = coercePositiveInteger(action.maxSlots, 8, "maxSlots");
|
|
1249
|
+
const slotStart = coercePositiveInteger(action.slotStart, 1, "slotStart");
|
|
1250
|
+
const slotCount = coercePositiveInteger(action.slotCount, 1, "slotCount");
|
|
1251
|
+
let queue = {};
|
|
1252
|
+
if (!queueId) {
|
|
1253
|
+
queue = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1254
|
+
method: "POST",
|
|
1255
|
+
headers: sdkJsonHeaders(options.apiKey, options.projectId),
|
|
1256
|
+
body: JSON.stringify({
|
|
1257
|
+
displayName: String(action.displayName || `${options.projectId} queue`).trim(),
|
|
1258
|
+
maxSlots,
|
|
1259
|
+
}),
|
|
1260
|
+
});
|
|
1261
|
+
queueId = String(queue.queueId || queue.queue_id || "").trim();
|
|
1262
|
+
}
|
|
1263
|
+
if (!queueId) {
|
|
1264
|
+
throw new Error("Runner setup needs an existing queue id or a created queue response with queueId.");
|
|
1265
|
+
}
|
|
1266
|
+
const attachment = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.projectId)}/queues`, {
|
|
1267
|
+
method: "POST",
|
|
1268
|
+
headers: sdkJsonHeaders(options.apiKey, options.projectId),
|
|
1269
|
+
body: JSON.stringify({ queueId }),
|
|
1270
|
+
});
|
|
1271
|
+
const slots = [];
|
|
1272
|
+
for (let offset = 0; offset < slotCount; offset += 1) {
|
|
1273
|
+
const slotIndex = slotStart + offset;
|
|
1274
|
+
slots.push(await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(slotIndex))}`, {
|
|
1275
|
+
method: "PUT",
|
|
1276
|
+
headers: sdkJsonHeaders(options.apiKey, options.projectId),
|
|
1277
|
+
body: JSON.stringify({ queueId }),
|
|
1278
|
+
}));
|
|
1279
|
+
}
|
|
1280
|
+
return {
|
|
1281
|
+
queueId,
|
|
1282
|
+
queue: queueId && !Object.keys(queue).length ? { queueId } : queue,
|
|
1283
|
+
attachment,
|
|
1284
|
+
slots,
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
|
|
538
1288
|
try {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1289
|
+
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
1290
|
+
if (request.method === "GET" && url.pathname === "/") {
|
|
1291
|
+
sendText(response, 200, runnerSetupHtml(options), "text/html; charset=utf-8");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (request.method === "GET" && url.pathname === "/api/status") {
|
|
1295
|
+
sendJson(response, 200, {
|
|
1296
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1297
|
+
projectId: options.projectId,
|
|
1298
|
+
runnerId: options.runnerId,
|
|
1299
|
+
});
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
if (request.method === "POST" && url.pathname === "/api/setup") {
|
|
1303
|
+
const body = await readRequestJson(request);
|
|
1304
|
+
const result = await executeRunnerSetupAction(options, body, fetchImpl);
|
|
1305
|
+
sendJson(response, 200, result);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
sendJson(response, 404, { error: "not found" });
|
|
545
1309
|
}
|
|
546
1310
|
catch (error) {
|
|
547
|
-
|
|
548
|
-
|
|
1311
|
+
sendJson(response, 400, { error: formatError(error) });
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function runnerSetupHtml(options) {
|
|
1315
|
+
const displayName = `${options.projectId} queue`;
|
|
1316
|
+
return `<!doctype html>
|
|
1317
|
+
<html lang="en">
|
|
1318
|
+
<head>
|
|
1319
|
+
<meta charset="utf-8">
|
|
1320
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
1321
|
+
<title>Tapi Runner Setup</title>
|
|
1322
|
+
<style>
|
|
1323
|
+
:root { color-scheme: light dark; --bg: #f7f7f5; --panel: #ffffff; --text: #1d2329; --muted: #62707c; --line: #d6dde2; --accent: #176b4d; --accent-2: #0f5ca8; --danger: #ad2e24; }
|
|
1324
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #111418; --panel: #181d22; --text: #eef3f6; --muted: #a6b1bb; --line: #303a43; --accent: #59b88c; --accent-2: #6da8e8; --danger: #ee786e; } }
|
|
1325
|
+
* { box-sizing: border-box; }
|
|
1326
|
+
body { margin: 0; font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--text); background: var(--bg); }
|
|
1327
|
+
main { max-width: 920px; margin: 0 auto; padding: 28px 18px 36px; }
|
|
1328
|
+
h1 { margin: 0 0 18px; font-size: 28px; font-weight: 650; letter-spacing: 0; }
|
|
1329
|
+
h2 { margin: 0 0 12px; font-size: 16px; font-weight: 650; letter-spacing: 0; }
|
|
1330
|
+
.grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 320px); gap: 18px; align-items: start; }
|
|
1331
|
+
.panel, .summary { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 18px; }
|
|
1332
|
+
.row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
1333
|
+
label { display: grid; gap: 6px; margin: 0 0 12px; color: var(--muted); font-size: 12px; font-weight: 600; text-transform: uppercase; }
|
|
1334
|
+
input { width: 100%; min-width: 0; border: 1px solid var(--line); border-radius: 6px; padding: 10px 11px; color: var(--text); background: transparent; font: inherit; }
|
|
1335
|
+
button { border: 0; border-radius: 6px; padding: 11px 14px; color: white; background: var(--accent); font: inherit; font-weight: 650; cursor: pointer; }
|
|
1336
|
+
button:disabled { opacity: .55; cursor: progress; }
|
|
1337
|
+
code { word-break: break-all; }
|
|
1338
|
+
.summary dl { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 8px 10px; margin: 0; }
|
|
1339
|
+
.summary dt { color: var(--muted); font-weight: 650; }
|
|
1340
|
+
.summary dd { margin: 0; min-width: 0; }
|
|
1341
|
+
.result { margin-top: 14px; min-height: 120px; border: 1px solid var(--line); border-radius: 8px; padding: 12px; overflow: auto; background: rgba(0,0,0,.035); white-space: pre-wrap; }
|
|
1342
|
+
.error { color: var(--danger); }
|
|
1343
|
+
.ok { color: var(--accent-2); }
|
|
1344
|
+
@media (max-width: 720px) { .grid, .row { grid-template-columns: 1fr; } main { padding: 20px 14px 28px; } h1 { font-size: 24px; } }
|
|
1345
|
+
</style>
|
|
1346
|
+
</head>
|
|
1347
|
+
<body>
|
|
1348
|
+
<main>
|
|
1349
|
+
<h1>Tapi Runner Setup</h1>
|
|
1350
|
+
<div class="grid">
|
|
1351
|
+
<section class="panel">
|
|
1352
|
+
<h2>Queue And Slots</h2>
|
|
1353
|
+
<form id="setup-form">
|
|
1354
|
+
<label>Existing Queue ID<input id="queueId" name="queueId" autocomplete="off" placeholder="queue_abc123"></label>
|
|
1355
|
+
<label>New Queue Name<input id="displayName" name="displayName" value="${escapeHtml(displayName)}" autocomplete="off"></label>
|
|
1356
|
+
<div class="row">
|
|
1357
|
+
<label>Queue Slot Limit<input id="maxSlots" name="maxSlots" type="number" min="1" step="1" value="8"></label>
|
|
1358
|
+
<label>First Runner Slot<input id="slotStart" name="slotStart" type="number" min="1" step="1" value="1"></label>
|
|
1359
|
+
</div>
|
|
1360
|
+
<label>Runner Slot Count<input id="slotCount" name="slotCount" type="number" min="1" step="1" value="1"></label>
|
|
1361
|
+
<button id="submit" type="submit">Apply Setup</button>
|
|
1362
|
+
</form>
|
|
1363
|
+
<pre id="result" class="result" aria-live="polite"></pre>
|
|
1364
|
+
</section>
|
|
1365
|
+
<aside class="summary">
|
|
1366
|
+
<h2>Target</h2>
|
|
1367
|
+
<dl>
|
|
1368
|
+
<dt>Server</dt><dd><code>${escapeHtml(options.apiBaseUrl)}</code></dd>
|
|
1369
|
+
<dt>Tapp</dt><dd><code>${escapeHtml(options.projectId)}</code></dd>
|
|
1370
|
+
<dt>Runner</dt><dd><code>${escapeHtml(options.runnerId)}</code></dd>
|
|
1371
|
+
</dl>
|
|
1372
|
+
</aside>
|
|
1373
|
+
</div>
|
|
1374
|
+
</main>
|
|
1375
|
+
<script>
|
|
1376
|
+
const form = document.getElementById("setup-form");
|
|
1377
|
+
const button = document.getElementById("submit");
|
|
1378
|
+
const result = document.getElementById("result");
|
|
1379
|
+
form.addEventListener("submit", async (event) => {
|
|
1380
|
+
event.preventDefault();
|
|
1381
|
+
button.disabled = true;
|
|
1382
|
+
result.className = "result";
|
|
1383
|
+
result.textContent = "Applying setup...";
|
|
1384
|
+
const body = Object.fromEntries(new FormData(form).entries());
|
|
1385
|
+
body.maxSlots = Number(body.maxSlots || 8);
|
|
1386
|
+
body.slotStart = Number(body.slotStart || 1);
|
|
1387
|
+
body.slotCount = Number(body.slotCount || 1);
|
|
1388
|
+
try {
|
|
1389
|
+
const response = await fetch("/api/setup", {
|
|
1390
|
+
method: "POST",
|
|
1391
|
+
headers: { "Content-Type": "application/json" },
|
|
1392
|
+
body: JSON.stringify(body),
|
|
1393
|
+
});
|
|
1394
|
+
const payload = await response.json();
|
|
1395
|
+
if (!response.ok) throw new Error(payload.error || JSON.stringify(payload));
|
|
1396
|
+
result.className = "result ok";
|
|
1397
|
+
result.textContent = JSON.stringify(payload, null, 2);
|
|
1398
|
+
document.getElementById("queueId").value = payload.queueId || body.queueId || "";
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
result.className = "result error";
|
|
1401
|
+
result.textContent = error instanceof Error ? error.message : String(error);
|
|
1402
|
+
} finally {
|
|
1403
|
+
button.disabled = false;
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
</script>
|
|
1407
|
+
</body>
|
|
1408
|
+
</html>`;
|
|
1409
|
+
}
|
|
1410
|
+
async function sdkJsonRequest(fetchImpl, url, init) {
|
|
1411
|
+
const response = await fetchImpl(url, init);
|
|
1412
|
+
const body = await readJsonBody(response);
|
|
1413
|
+
if (!response.ok) {
|
|
1414
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1415
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1416
|
+
}
|
|
1417
|
+
return body ?? {};
|
|
1418
|
+
}
|
|
1419
|
+
function sdkJsonHeaders(apiKey, projectId) {
|
|
1420
|
+
return {
|
|
1421
|
+
"Content-Type": "application/json",
|
|
1422
|
+
"X-API-Key": apiKey,
|
|
1423
|
+
"X-Tapi-Project": projectId,
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function coercePositiveInteger(value, fallback, label) {
|
|
1427
|
+
const text = value === undefined || value === null || value === "" ? String(fallback) : String(value);
|
|
1428
|
+
return parsePositiveInteger(text, label);
|
|
1429
|
+
}
|
|
1430
|
+
function parsePositiveInteger(value, label) {
|
|
1431
|
+
const parsed = Number(value);
|
|
1432
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
1433
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1434
|
+
}
|
|
1435
|
+
return parsed;
|
|
1436
|
+
}
|
|
1437
|
+
function parseNonNegativeInteger(value, label) {
|
|
1438
|
+
const parsed = Number(value);
|
|
1439
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
1440
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
549
1441
|
}
|
|
1442
|
+
return parsed;
|
|
550
1443
|
}
|
|
551
1444
|
function parseTriggerSyncOptions(args) {
|
|
552
1445
|
const workspace = loadWorkspace();
|
|
@@ -610,24 +1503,25 @@ function parseTriggerSyncOptions(args) {
|
|
|
610
1503
|
configPath: configPath ? resolve(root, configPath) : defaultTriggerConfigPath(root),
|
|
611
1504
|
};
|
|
612
1505
|
}
|
|
613
|
-
async function
|
|
1506
|
+
async function syncServiceTriggers(args) {
|
|
614
1507
|
try {
|
|
615
1508
|
const options = parseTriggerSyncOptions(args);
|
|
616
|
-
const triggers = await
|
|
1509
|
+
const triggers = await readServiceTriggerConfig(options.configPath);
|
|
617
1510
|
if (triggers.length === 0) {
|
|
618
|
-
throw new Error(`No Tapi
|
|
1511
|
+
throw new Error(`No Tapi service triggers found in ${options.configPath}.`);
|
|
619
1512
|
}
|
|
620
1513
|
const client = new TapiClient({
|
|
621
1514
|
baseUrl: options.apiBaseUrl,
|
|
622
1515
|
apiKey: options.apiKey,
|
|
1516
|
+
tappId: options.projectId,
|
|
623
1517
|
projectId: options.projectId,
|
|
624
1518
|
});
|
|
625
|
-
await withCliSpinner(`Syncing ${triggers.length} Tapi
|
|
1519
|
+
await withCliSpinner(`Syncing ${triggers.length} Tapi service trigger(s)`, async () => {
|
|
626
1520
|
for (const trigger of triggers) {
|
|
627
1521
|
await client.triggers.create(trigger);
|
|
628
1522
|
}
|
|
629
1523
|
});
|
|
630
|
-
console.log(`Synced ${triggers.length} Tapi
|
|
1524
|
+
console.log(`Synced ${triggers.length} Tapi service trigger(s) from ${options.configPath}.`);
|
|
631
1525
|
return 0;
|
|
632
1526
|
}
|
|
633
1527
|
catch (error) {
|
|
@@ -770,6 +1664,7 @@ async function fetchDevSessions(options) {
|
|
|
770
1664
|
const client = new TapiClient({
|
|
771
1665
|
baseUrl: options.apiBaseUrl,
|
|
772
1666
|
apiKey: options.apiKey,
|
|
1667
|
+
tappId: options.projectId,
|
|
773
1668
|
projectId: options.projectId,
|
|
774
1669
|
});
|
|
775
1670
|
return await withCliSpinner("Fetching Tapi dev sessions", () => client.sessions.list({ site: options.site }));
|
|
@@ -897,7 +1792,6 @@ async function openDevSessionInStudio(session, options) {
|
|
|
897
1792
|
function studioSessionLaunchParams(session) {
|
|
898
1793
|
const params = new URLSearchParams();
|
|
899
1794
|
params.set("runId", session.id);
|
|
900
|
-
params.set("apiRunId", session.id);
|
|
901
1795
|
if (session.sitemap)
|
|
902
1796
|
params.set("sitemap", session.sitemap);
|
|
903
1797
|
if (session.runtimeSessionId)
|
|
@@ -908,7 +1802,6 @@ function studioSessionSelectIntent(session) {
|
|
|
908
1802
|
return {
|
|
909
1803
|
sessionId: session.id,
|
|
910
1804
|
runId: session.id,
|
|
911
|
-
apiRunId: session.id,
|
|
912
1805
|
...(session.sitemap ? { sitemap: session.sitemap } : {}),
|
|
913
1806
|
...(session.runtimeSessionId ? { runtimeSessionId: session.runtimeSessionId } : {}),
|
|
914
1807
|
source: "cli",
|
|
@@ -961,12 +1854,12 @@ function renderDevSessions(sessions, selectedId = "") {
|
|
|
961
1854
|
const openable = session.openable ? "*" : " ";
|
|
962
1855
|
const id = truncateText(session.id || "", 18).padEnd(18, " ");
|
|
963
1856
|
const status = truncateText(String(session.status || ""), 24).padEnd(24, " ");
|
|
964
|
-
const
|
|
965
|
-
? `${session.
|
|
966
|
-
: session.
|
|
1857
|
+
const serviceRun = session.serviceRun || (session.serviceName && session.serviceKey
|
|
1858
|
+
? `${session.serviceName}.${session.serviceKey}`
|
|
1859
|
+
: session.serviceName || session.serviceKey || "unknown");
|
|
967
1860
|
const state = session.stateLabel ? ` state=${session.stateLabel}` : "";
|
|
968
1861
|
const url = session.currentUrl ? ` ${truncateText(session.currentUrl, 60)}` : "";
|
|
969
|
-
lines.push(`${selected}${openable} ${id} ${status} ${
|
|
1862
|
+
lines.push(`${selected}${openable} ${id} ${status} ${serviceRun}${state}${url}`);
|
|
970
1863
|
}
|
|
971
1864
|
}
|
|
972
1865
|
lines.push("");
|
|
@@ -988,7 +1881,7 @@ function defaultTriggerConfigPath(root) {
|
|
|
988
1881
|
}
|
|
989
1882
|
return join(root, "tapi.config.json");
|
|
990
1883
|
}
|
|
991
|
-
async function
|
|
1884
|
+
async function readServiceTriggerConfig(configPath) {
|
|
992
1885
|
if (!existsSync(configPath)) {
|
|
993
1886
|
throw new Error(`Tapi trigger config not found at ${configPath}. Create tapi.config.json or pass --config.`);
|
|
994
1887
|
}
|
|
@@ -1051,24 +1944,31 @@ function normalizeTriggerEntry(entry, index, configPath) {
|
|
|
1051
1944
|
throw new Error(`Trigger #${index + 1} in ${configPath} must be an object.`);
|
|
1052
1945
|
}
|
|
1053
1946
|
const name = stringField(entry.name, `Trigger #${index + 1} name`);
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1947
|
+
if (RETIRED_TRIGGER_TARGET_FIELD in entry) {
|
|
1948
|
+
throw new Error(`Trigger '${name}' uses a retired trigger target field; use serviceRun.`);
|
|
1949
|
+
}
|
|
1950
|
+
const serviceRun = optionalStringField(entry.serviceRun, "serviceRun");
|
|
1951
|
+
if (!serviceRun) {
|
|
1952
|
+
throw new Error(`Trigger '${name}' requires serviceRun.`);
|
|
1059
1953
|
}
|
|
1060
|
-
if (!
|
|
1061
|
-
throw new Error(`Trigger '${name}'
|
|
1954
|
+
if (!serviceRun.includes(".")) {
|
|
1955
|
+
throw new Error(`Trigger '${name}' serviceRun must be formatted as '<serviceName>.<serviceKey>'.`);
|
|
1956
|
+
}
|
|
1957
|
+
if ("runnerId" in entry) {
|
|
1958
|
+
throw new Error(`Trigger '${name}' uses runnerId; use queueId.`);
|
|
1062
1959
|
}
|
|
1063
1960
|
const schedule = normalizeTriggerSchedule(entry.schedule, entry.interval, name);
|
|
1961
|
+
const runtime = entry.runtime === undefined ? undefined : recordField(entry.runtime, `Trigger '${name}' runtime`);
|
|
1962
|
+
rejectNonPolicyTriggerRuntimeOwner(runtime, `Trigger '${name}' runtime`);
|
|
1963
|
+
const queueId = stringField(entry.queueId, `Trigger '${name}' queueId`);
|
|
1064
1964
|
const request = {
|
|
1065
1965
|
name,
|
|
1066
|
-
|
|
1966
|
+
serviceRun,
|
|
1967
|
+
queueId,
|
|
1067
1968
|
...(entry.enabled === undefined ? {} : { enabled: booleanField(entry.enabled, `Trigger '${name}' enabled`) }),
|
|
1068
1969
|
...(schedule ? { schedule } : {}),
|
|
1069
1970
|
...(entry.inputs === undefined ? {} : { inputs: recordField(entry.inputs, `Trigger '${name}' inputs`) }),
|
|
1070
|
-
...(
|
|
1071
|
-
...(entry.runnerId === undefined ? {} : { runnerId: stringField(entry.runnerId, `Trigger '${name}' runnerId`) }),
|
|
1971
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
1072
1972
|
...(entry.priority === undefined ? {} : { priority: numberField(entry.priority, `Trigger '${name}' priority`) }),
|
|
1073
1973
|
...(entry.site === undefined ? {} : { site: stringField(entry.site, `Trigger '${name}' site`) }),
|
|
1074
1974
|
};
|
|
@@ -1151,11 +2051,6 @@ function parseDurationSeconds(value, fieldName) {
|
|
|
1151
2051
|
}
|
|
1152
2052
|
return seconds;
|
|
1153
2053
|
}
|
|
1154
|
-
function apiRequestFromParts(apiName, requestKey) {
|
|
1155
|
-
const api = optionalStringField(apiName, "apiName");
|
|
1156
|
-
const request = optionalStringField(requestKey, "requestKey");
|
|
1157
|
-
return api && request ? `${api}.${request}` : undefined;
|
|
1158
|
-
}
|
|
1159
2054
|
function stringField(value, fieldName) {
|
|
1160
2055
|
if (typeof value !== "string" || !value.trim()) {
|
|
1161
2056
|
throw new Error(`${fieldName} must be a non-empty string.`);
|
|
@@ -1186,10 +2081,30 @@ function recordField(value, fieldName) {
|
|
|
1186
2081
|
}
|
|
1187
2082
|
return value;
|
|
1188
2083
|
}
|
|
2084
|
+
function rejectNonPolicyTriggerRuntimeOwner(runtime, fieldName) {
|
|
2085
|
+
if (!runtime) {
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
for (const field of RETIRED_RUNTIME_OWNER_ALIAS_FIELDS) {
|
|
2089
|
+
if (field in runtime) {
|
|
2090
|
+
throw new Error(`unsupported service trigger runtime field: ${field}`);
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const field of RUNTIME_OWNER_FIELDS) {
|
|
2094
|
+
if (!(field in runtime)) {
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
const owner = String(runtime[field] ?? "").trim().toLowerCase();
|
|
2098
|
+
if (owner && owner !== SERVICE_RUN_RUNTIME_OWNER) {
|
|
2099
|
+
throw new Error(`${fieldName} owner must be service_run.`);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
1189
2103
|
async function fetchApiCatalog(options) {
|
|
1190
2104
|
const client = new TapiClient({
|
|
1191
2105
|
baseUrl: options.apiBaseUrl,
|
|
1192
2106
|
apiKey: options.apiKey,
|
|
2107
|
+
tappId: options.projectId,
|
|
1193
2108
|
projectId: options.projectId,
|
|
1194
2109
|
});
|
|
1195
2110
|
return client.catalog.get();
|
|
@@ -1201,50 +2116,6 @@ async function writeTextFile(path, content) {
|
|
|
1201
2116
|
await mkdir(dirname(path), { recursive: true });
|
|
1202
2117
|
await writeFile(path, content, "utf8");
|
|
1203
2118
|
}
|
|
1204
|
-
function renderGeneratedApiClient(catalog) {
|
|
1205
|
-
const namespaces = new Map();
|
|
1206
|
-
for (const api of catalog.apis || []) {
|
|
1207
|
-
const namespace = safeIdentifier(api.name || "api");
|
|
1208
|
-
for (const request of api.requests || []) {
|
|
1209
|
-
const key = String(request.operation || request.sdkName || request.key || "").trim();
|
|
1210
|
-
if (!key)
|
|
1211
|
-
continue;
|
|
1212
|
-
const operationName = safeIdentifier(key);
|
|
1213
|
-
const operation = `${api.name}.${key}`;
|
|
1214
|
-
const items = namespaces.get(namespace) ?? [];
|
|
1215
|
-
items.push({ key: operation, operationName });
|
|
1216
|
-
namespaces.set(namespace, items);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
const namespaceBlocks = [...namespaces.entries()].map(([namespace, operations]) => {
|
|
1220
|
-
const lines = operations.map(({ key, operationName }) => ` ${operationName}: (inputs: Record<string, unknown> = {}, options: GeneratedRunOptions = {}) => client.services.run(${JSON.stringify(key)}, { ...options, inputs }),`);
|
|
1221
|
-
return ` ${namespace}: {\n${lines.join("\n")}\n },`;
|
|
1222
|
-
});
|
|
1223
|
-
return `/* Generated by Tapi. Do not edit by hand. */
|
|
1224
|
-
import { TapiClient, type TapiClientOptions, type RuntimeRunOptions } from "@tapi-dev/sdk";
|
|
1225
|
-
|
|
1226
|
-
export interface GeneratedRunOptions {
|
|
1227
|
-
runtime?: RuntimeRunOptions;
|
|
1228
|
-
priority?: number;
|
|
1229
|
-
runnerId?: string;
|
|
1230
|
-
idempotencyKey?: string;
|
|
1231
|
-
site?: string;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
export function createTapiGeneratedClient(options: TapiClientOptions) {
|
|
1235
|
-
const client = new TapiClient(options);
|
|
1236
|
-
return {
|
|
1237
|
-
${namespaceBlocks.join("\n")}
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
`;
|
|
1241
|
-
}
|
|
1242
|
-
function safeIdentifier(value) {
|
|
1243
|
-
const cleaned = String(value || "api")
|
|
1244
|
-
.replace(/[^a-zA-Z0-9_$]+/g, "_")
|
|
1245
|
-
.replace(/^([^a-zA-Z_$])/, "_$1");
|
|
1246
|
-
return cleaned || "api";
|
|
1247
|
-
}
|
|
1248
2119
|
function isRecord(value) {
|
|
1249
2120
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1250
2121
|
}
|
|
@@ -2444,31 +3315,31 @@ async function stopPortableStudioServerProcesses() {
|
|
|
2444
3315
|
if (process.platform !== "win32") {
|
|
2445
3316
|
return 0;
|
|
2446
3317
|
}
|
|
2447
|
-
const script = `
|
|
2448
|
-
$ErrorActionPreference = 'SilentlyContinue'
|
|
2449
|
-
$currentPid = $PID
|
|
2450
|
-
$processes = @(Get-Process -ErrorAction SilentlyContinue | Where-Object {
|
|
2451
|
-
$pidValue = [int]$_.Id
|
|
2452
|
-
if ($pidValue -le 0 -or $pidValue -eq $currentPid) {
|
|
2453
|
-
$false
|
|
2454
|
-
} else {
|
|
2455
|
-
$path = ''
|
|
2456
|
-
try { $path = [string]$_.Path } catch {}
|
|
2457
|
-
$name = [string]$_.ProcessName
|
|
2458
|
-
(
|
|
2459
|
-
$name -ieq 'tapi-studio-server' -or
|
|
2460
|
-
$path -match '\\\\Tapi\\\\Studio\\\\server\\\\releases\\\\'
|
|
2461
|
-
)
|
|
2462
|
-
}
|
|
2463
|
-
})
|
|
2464
|
-
$stopped = 0
|
|
2465
|
-
foreach ($process in $processes) {
|
|
2466
|
-
try {
|
|
2467
|
-
Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop
|
|
2468
|
-
$stopped += 1
|
|
2469
|
-
} catch {}
|
|
2470
|
-
}
|
|
2471
|
-
[pscustomobject]@{ stopped = $stopped } | ConvertTo-Json -Compress
|
|
3318
|
+
const script = `
|
|
3319
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
3320
|
+
$currentPid = $PID
|
|
3321
|
+
$processes = @(Get-Process -ErrorAction SilentlyContinue | Where-Object {
|
|
3322
|
+
$pidValue = [int]$_.Id
|
|
3323
|
+
if ($pidValue -le 0 -or $pidValue -eq $currentPid) {
|
|
3324
|
+
$false
|
|
3325
|
+
} else {
|
|
3326
|
+
$path = ''
|
|
3327
|
+
try { $path = [string]$_.Path } catch {}
|
|
3328
|
+
$name = [string]$_.ProcessName
|
|
3329
|
+
(
|
|
3330
|
+
$name -ieq 'tapi-studio-server' -or
|
|
3331
|
+
$path -match '\\\\Tapi\\\\Studio\\\\server\\\\releases\\\\'
|
|
3332
|
+
)
|
|
3333
|
+
}
|
|
3334
|
+
})
|
|
3335
|
+
$stopped = 0
|
|
3336
|
+
foreach ($process in $processes) {
|
|
3337
|
+
try {
|
|
3338
|
+
Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop
|
|
3339
|
+
$stopped += 1
|
|
3340
|
+
} catch {}
|
|
3341
|
+
}
|
|
3342
|
+
[pscustomobject]@{ stopped = $stopped } | ConvertTo-Json -Compress
|
|
2472
3343
|
`;
|
|
2473
3344
|
try {
|
|
2474
3345
|
const output = await runProcessCapture("powershell.exe", [
|
|
@@ -2992,6 +3863,56 @@ function openBrowser(url) {
|
|
|
2992
3863
|
});
|
|
2993
3864
|
child.unref();
|
|
2994
3865
|
}
|
|
3866
|
+
function openUrlInBrowser(url) {
|
|
3867
|
+
if (process.platform === "win32") {
|
|
3868
|
+
openBrowser(url);
|
|
3869
|
+
return;
|
|
3870
|
+
}
|
|
3871
|
+
const command = process.platform === "darwin" ? "open" : "xdg-open";
|
|
3872
|
+
const child = spawn(command, [url], {
|
|
3873
|
+
detached: true,
|
|
3874
|
+
stdio: "ignore",
|
|
3875
|
+
windowsHide: true,
|
|
3876
|
+
});
|
|
3877
|
+
child.unref();
|
|
3878
|
+
}
|
|
3879
|
+
async function listenOnLocalhost(server, port) {
|
|
3880
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
3881
|
+
const onError = (error) => {
|
|
3882
|
+
server.off("listening", onListening);
|
|
3883
|
+
rejectPromise(error);
|
|
3884
|
+
};
|
|
3885
|
+
const onListening = () => {
|
|
3886
|
+
server.off("error", onError);
|
|
3887
|
+
resolvePromise();
|
|
3888
|
+
};
|
|
3889
|
+
server.once("error", onError);
|
|
3890
|
+
server.once("listening", onListening);
|
|
3891
|
+
server.listen(port, "127.0.0.1");
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3894
|
+
async function closeServer(server) {
|
|
3895
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
3896
|
+
server.close((error) => {
|
|
3897
|
+
if (error) {
|
|
3898
|
+
rejectPromise(error);
|
|
3899
|
+
return;
|
|
3900
|
+
}
|
|
3901
|
+
resolvePromise();
|
|
3902
|
+
});
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
async function waitForProcessSignal() {
|
|
3906
|
+
await new Promise((resolvePromise) => {
|
|
3907
|
+
const done = () => {
|
|
3908
|
+
process.off("SIGINT", done);
|
|
3909
|
+
process.off("SIGTERM", done);
|
|
3910
|
+
resolvePromise();
|
|
3911
|
+
};
|
|
3912
|
+
process.once("SIGINT", done);
|
|
3913
|
+
process.once("SIGTERM", done);
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
2995
3916
|
async function readJsonBody(response) {
|
|
2996
3917
|
const text = await response.text();
|
|
2997
3918
|
if (!text.trim()) {
|
|
@@ -3004,6 +3925,40 @@ async function readJsonBody(response) {
|
|
|
3004
3925
|
return null;
|
|
3005
3926
|
}
|
|
3006
3927
|
}
|
|
3928
|
+
async function readRequestJson(request) {
|
|
3929
|
+
let body = "";
|
|
3930
|
+
for await (const chunk of request) {
|
|
3931
|
+
body += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
3932
|
+
if (Buffer.byteLength(body, "utf8") > 64 * 1024) {
|
|
3933
|
+
throw new Error("request body is too large");
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
if (!body.trim()) {
|
|
3937
|
+
return {};
|
|
3938
|
+
}
|
|
3939
|
+
const parsed = JSON.parse(body);
|
|
3940
|
+
if (!isRecord(parsed)) {
|
|
3941
|
+
throw new Error("request body must be a JSON object");
|
|
3942
|
+
}
|
|
3943
|
+
return parsed;
|
|
3944
|
+
}
|
|
3945
|
+
function sendText(response, status, text, contentType) {
|
|
3946
|
+
response.statusCode = status;
|
|
3947
|
+
response.setHeader("Content-Type", contentType);
|
|
3948
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3949
|
+
response.end(text);
|
|
3950
|
+
}
|
|
3951
|
+
function sendJson(response, status, payload) {
|
|
3952
|
+
sendText(response, status, JSON.stringify(payload), "application/json; charset=utf-8");
|
|
3953
|
+
}
|
|
3954
|
+
function escapeHtml(value) {
|
|
3955
|
+
return value
|
|
3956
|
+
.replace(/&/g, "&")
|
|
3957
|
+
.replace(/</g, "<")
|
|
3958
|
+
.replace(/>/g, ">")
|
|
3959
|
+
.replace(/"/g, """)
|
|
3960
|
+
.replace(/'/g, "'");
|
|
3961
|
+
}
|
|
3007
3962
|
function isApprovalTerminalError(error) {
|
|
3008
3963
|
return error instanceof StudioInstallApprovalError
|
|
3009
3964
|
&& (error.code === "pending_approval" || error.code === "access_pending" || error.code === "access_rejected");
|
|
@@ -3558,11 +4513,16 @@ Usage:
|
|
|
3558
4513
|
tapi link --project PROJECT
|
|
3559
4514
|
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
3560
4515
|
tapi studio
|
|
3561
|
-
tapi studio open
|
|
3562
|
-
tapi studio doctor
|
|
3563
|
-
tapi
|
|
3564
|
-
tapi
|
|
3565
|
-
tapi
|
|
4516
|
+
tapi studio open
|
|
4517
|
+
tapi studio doctor
|
|
4518
|
+
tapi tapp create <tapp>
|
|
4519
|
+
tapi queue create [display-name]
|
|
4520
|
+
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY
|
|
4521
|
+
tapi tapp queue add <tapp> <queue-id>
|
|
4522
|
+
tapi runner setup --runner-id RUNNER --project TAPP
|
|
4523
|
+
tapi runner slot set <runner-id> <slot-index> <queue-id>
|
|
4524
|
+
tapi services describe <servicemap.run>
|
|
4525
|
+
tapi services sync
|
|
3566
4526
|
tapi triggers sync
|
|
3567
4527
|
tapi sessions
|
|
3568
4528
|
tapi service status
|
|
@@ -3572,18 +4532,75 @@ Commands:
|
|
|
3572
4532
|
init Create .tapi/project.json for this repo
|
|
3573
4533
|
link Rebind this repo to an existing Tapi project
|
|
3574
4534
|
studio install Download, verify, and run the Tapi Studio installer
|
|
3575
|
-
studio Open Tapi Studio for this repo
|
|
3576
|
-
studio open Open Tapi Studio for this repo
|
|
3577
|
-
studio doctor Check local SDK and Studio release configuration
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
4535
|
+
studio Open Tapi Studio for this repo
|
|
4536
|
+
studio open Open Tapi Studio for this repo
|
|
4537
|
+
studio doctor Check local SDK and Studio release configuration
|
|
4538
|
+
tapp create Create a Tapp
|
|
4539
|
+
queue create Create a queue for service-run routing
|
|
4540
|
+
tapp service add
|
|
4541
|
+
Add a ServiceMap-backed service call to a Tapp
|
|
4542
|
+
tapp queue add Attach a queue to a Tapp
|
|
4543
|
+
runner setup Open the local runner queue/slot setup UI
|
|
4544
|
+
runner slot set Assign a queue to a runner slot
|
|
4545
|
+
services describe
|
|
4546
|
+
Print a ServiceMap service-run input/output contract
|
|
4547
|
+
services sync Save the service-run catalog to .tapi/services
|
|
4548
|
+
triggers sync Upsert service triggers from tapi.config
|
|
3582
4549
|
sessions List dev-mode API sessions and open takeover sessions
|
|
3583
4550
|
service Inspect or control the local Tapi Windows service
|
|
3584
4551
|
doctor Alias for studio doctor
|
|
3585
4552
|
`);
|
|
3586
4553
|
}
|
|
4554
|
+
function printTappHelp() {
|
|
4555
|
+
console.log(`Tapi Tapp commands
|
|
4556
|
+
|
|
4557
|
+
Usage:
|
|
4558
|
+
tapi tapp create <tapp> [--name NAME] [--api-base-url URL] [--api-key KEY]
|
|
4559
|
+
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY [--api-base-url URL] [--api-key KEY]
|
|
4560
|
+
tapi tapp queue add <tapp> <queue-id> [--api-base-url URL] [--api-key KEY]
|
|
4561
|
+
|
|
4562
|
+
Options:
|
|
4563
|
+
--api-base-url <url> Tapi API base URL
|
|
4564
|
+
--server <url> Alias for --api-base-url
|
|
4565
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4566
|
+
--tapp-id <id> Explicit Tapp id for tapp create
|
|
4567
|
+
--name <name> Display name for tapp create
|
|
4568
|
+
--service-map <id> Backing ServiceMap id
|
|
4569
|
+
--entry <entry> ServiceMap entry URL/name to launch
|
|
4570
|
+
--expected-revision <r> Optional ServiceMap revision guard
|
|
4571
|
+
`);
|
|
4572
|
+
}
|
|
4573
|
+
function printQueueHelp() {
|
|
4574
|
+
console.log(`Tapi queue commands
|
|
4575
|
+
|
|
4576
|
+
Usage:
|
|
4577
|
+
tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL] [--api-key KEY]
|
|
4578
|
+
|
|
4579
|
+
Options:
|
|
4580
|
+
--api-base-url <url> Tapi API base URL
|
|
4581
|
+
--server <url> Alias for --api-base-url
|
|
4582
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4583
|
+
--project <id> Optional project scope for the queue request
|
|
4584
|
+
--max-slots <n> Maximum runner slots this queue may use
|
|
4585
|
+
`);
|
|
4586
|
+
}
|
|
4587
|
+
function printRunnerHelp() {
|
|
4588
|
+
console.log(`Tapi runner commands
|
|
4589
|
+
|
|
4590
|
+
Usage:
|
|
4591
|
+
tapi runner setup --runner-id RUNNER --project TAPP [--port PORT] [--no-open] [--api-base-url URL] [--api-key KEY]
|
|
4592
|
+
tapi runner slot set <runner-id> <slot-index> <queue-id> [--project TAPP] [--api-base-url URL] [--api-key KEY]
|
|
4593
|
+
|
|
4594
|
+
Options:
|
|
4595
|
+
--api-base-url <url> Tapi API base URL
|
|
4596
|
+
--server <url> Alias for --api-base-url
|
|
4597
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
4598
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4599
|
+
--runner-id <id> Runner id for setup; defaults to TAPI_RUNNER_ID
|
|
4600
|
+
--port <n> Local setup UI port; defaults to 17687
|
|
4601
|
+
--no-open Print the setup URL without opening a browser
|
|
4602
|
+
`);
|
|
4603
|
+
}
|
|
3587
4604
|
function printSessionsHelp() {
|
|
3588
4605
|
console.log(`Tapi dev session commands
|
|
3589
4606
|
|
|
@@ -3602,13 +4619,12 @@ Options:
|
|
|
3602
4619
|
--no-interactive Print the grouped list without the arrow-key picker
|
|
3603
4620
|
`);
|
|
3604
4621
|
}
|
|
3605
|
-
function
|
|
3606
|
-
console.log(`Tapi
|
|
4622
|
+
function printServicesHelp() {
|
|
4623
|
+
console.log(`Tapi ServiceMap service-run commands
|
|
3607
4624
|
|
|
3608
4625
|
Usage:
|
|
3609
|
-
tapi
|
|
3610
|
-
tapi
|
|
3611
|
-
tapi apis generate [--api-base-url URL] [--api-key KEY] [--project PROJECT] [--out FILE]
|
|
4626
|
+
tapi services describe <servicemap.run> [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
4627
|
+
tapi services sync [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3612
4628
|
|
|
3613
4629
|
Options:
|
|
3614
4630
|
--api-base-url <url> Tapi API base URL
|
|
@@ -3616,11 +4632,10 @@ Options:
|
|
|
3616
4632
|
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3617
4633
|
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3618
4634
|
--catalog <path> Catalog JSON output path
|
|
3619
|
-
--out <path> Generated TypeScript output path
|
|
3620
4635
|
`);
|
|
3621
4636
|
}
|
|
3622
4637
|
function printTriggersHelp() {
|
|
3623
|
-
console.log(`Tapi
|
|
4638
|
+
console.log(`Tapi service trigger commands
|
|
3624
4639
|
|
|
3625
4640
|
Usage:
|
|
3626
4641
|
tapi triggers sync [--config FILE] [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
@@ -3636,10 +4651,10 @@ Config:
|
|
|
3636
4651
|
export default {
|
|
3637
4652
|
triggers: {
|
|
3638
4653
|
nightlyBalance: {
|
|
3639
|
-
|
|
4654
|
+
serviceRun: "schwab.get_balance",
|
|
3640
4655
|
interval: "1h",
|
|
3641
4656
|
inputs: { accountId: "main" },
|
|
3642
|
-
runtime: { profileRef: "perm_default" },
|
|
4657
|
+
runtime: { owner: "service_run", profileRef: "perm_default" },
|
|
3643
4658
|
},
|
|
3644
4659
|
},
|
|
3645
4660
|
};
|
|
@@ -3752,7 +4767,15 @@ function serializeError(error) {
|
|
|
3752
4767
|
}
|
|
3753
4768
|
function isCliEntrypoint() {
|
|
3754
4769
|
const invokedPath = process.argv[1];
|
|
3755
|
-
return Boolean(invokedPath &&
|
|
4770
|
+
return Boolean(invokedPath && realCliPath(invokedPath) === realCliPath(fileURLToPath(import.meta.url)));
|
|
4771
|
+
}
|
|
4772
|
+
function realCliPath(path) {
|
|
4773
|
+
try {
|
|
4774
|
+
return realpathSync(path);
|
|
4775
|
+
}
|
|
4776
|
+
catch {
|
|
4777
|
+
return resolve(path);
|
|
4778
|
+
}
|
|
3756
4779
|
}
|
|
3757
4780
|
if (isCliEntrypoint()) {
|
|
3758
4781
|
runCli().then((code) => {
|