@tapi-dev/sdk 0.1.34 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -120
- package/dist/catalog.js +3 -2
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +1208 -189
- 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 +54 -5
- package/dist/types.d.ts +55 -54
- package/dist/workspace.d.ts +0 -6
- package/dist/workspace.js +6 -4
- 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,8 +148,12 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
144
148
|
}
|
|
145
149
|
return runServiceCommand(subcommand, rest);
|
|
146
150
|
}
|
|
147
|
-
if (command === "services"
|
|
148
|
-
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h"
|
|
151
|
+
if (command === "services") {
|
|
152
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
153
|
+
printServicesHelp();
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
if (hasHelpFlag(rest)) {
|
|
149
157
|
printServicesHelp();
|
|
150
158
|
return 0;
|
|
151
159
|
}
|
|
@@ -155,20 +163,71 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
155
163
|
if (subcommand === "sync") {
|
|
156
164
|
return syncServiceCatalog(rest);
|
|
157
165
|
}
|
|
158
|
-
if (subcommand === "generate") {
|
|
159
|
-
return generateServiceClient(rest);
|
|
160
|
-
}
|
|
161
166
|
console.error(`Unknown service-run command: ${subcommand}`);
|
|
162
167
|
printServicesHelp();
|
|
163
168
|
return 1;
|
|
164
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;
|
|
210
|
+
}
|
|
211
|
+
if (subcommand === "setup") {
|
|
212
|
+
return runRunnerSetup(rest);
|
|
213
|
+
}
|
|
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();
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
165
224
|
if (command === "triggers") {
|
|
166
225
|
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
167
226
|
printTriggersHelp();
|
|
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();
|
|
@@ -314,65 +373,589 @@ export function parseStudioOptions(args) {
|
|
|
314
373
|
raw.exePath = resolve(requireOptionValue(args, ++index, "--exe"));
|
|
315
374
|
continue;
|
|
316
375
|
}
|
|
317
|
-
if (arg.startsWith("--exe=")) {
|
|
318
|
-
raw.exePath = resolve(arg.slice("--exe=".length));
|
|
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);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (arg.startsWith("--server=")) {
|
|
911
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (arg === "--api-key") {
|
|
915
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (arg.startsWith("--api-key=")) {
|
|
919
|
+
apiKey = arg.slice("--api-key=".length);
|
|
319
920
|
continue;
|
|
320
921
|
}
|
|
321
|
-
if (arg
|
|
322
|
-
|
|
323
|
-
continue;
|
|
922
|
+
if (arg.startsWith("--")) {
|
|
923
|
+
throw new Error(`Unknown tapp queue add option: ${arg}`);
|
|
324
924
|
}
|
|
325
|
-
if (
|
|
326
|
-
|
|
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 parseServiceOptions(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 service-run contract 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 describeServiceOperation(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();
|
|
497
1171
|
continue;
|
|
498
1172
|
}
|
|
499
|
-
if (arg === "--
|
|
500
|
-
|
|
1173
|
+
if (arg === "--port") {
|
|
1174
|
+
port = parseNonNegativeInteger(requireOptionValue(args, ++index, "--port"), "--port");
|
|
501
1175
|
continue;
|
|
502
1176
|
}
|
|
503
|
-
if (arg.startsWith("--
|
|
504
|
-
|
|
1177
|
+
if (arg.startsWith("--port=")) {
|
|
1178
|
+
port = parseNonNegativeInteger(arg.slice("--port=".length), "--port");
|
|
505
1179
|
continue;
|
|
506
1180
|
}
|
|
507
|
-
|
|
1181
|
+
if (arg === "--no-open") {
|
|
1182
|
+
openBrowser = false;
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
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 syncServiceCatalog(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,16 +1503,17 @@ 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
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
1519
|
await withCliSpinner(`Syncing ${triggers.length} Tapi service trigger(s)`, async () => {
|
|
@@ -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,26 +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
|
-
serviceRunFromParts(entry.serviceName ?? entry.apiName, entry.requestKey);
|
|
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");
|
|
1059
1951
|
if (!serviceRun) {
|
|
1060
|
-
throw new Error(`Trigger '${name}' requires serviceRun
|
|
1952
|
+
throw new Error(`Trigger '${name}' requires serviceRun.`);
|
|
1061
1953
|
}
|
|
1062
1954
|
if (!serviceRun.includes(".")) {
|
|
1063
|
-
throw new Error(`Trigger '${name}' serviceRun must be formatted as '<serviceName>.<
|
|
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.`);
|
|
1064
1959
|
}
|
|
1065
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`);
|
|
1066
1964
|
const request = {
|
|
1067
1965
|
name,
|
|
1068
|
-
|
|
1966
|
+
serviceRun,
|
|
1967
|
+
queueId,
|
|
1069
1968
|
...(entry.enabled === undefined ? {} : { enabled: booleanField(entry.enabled, `Trigger '${name}' enabled`) }),
|
|
1070
1969
|
...(schedule ? { schedule } : {}),
|
|
1071
1970
|
...(entry.inputs === undefined ? {} : { inputs: recordField(entry.inputs, `Trigger '${name}' inputs`) }),
|
|
1072
|
-
...(
|
|
1073
|
-
...(entry.runnerId === undefined ? {} : { runnerId: stringField(entry.runnerId, `Trigger '${name}' runnerId`) }),
|
|
1971
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
1074
1972
|
...(entry.priority === undefined ? {} : { priority: numberField(entry.priority, `Trigger '${name}' priority`) }),
|
|
1075
1973
|
...(entry.site === undefined ? {} : { site: stringField(entry.site, `Trigger '${name}' site`) }),
|
|
1076
1974
|
};
|
|
@@ -1153,11 +2051,6 @@ function parseDurationSeconds(value, fieldName) {
|
|
|
1153
2051
|
}
|
|
1154
2052
|
return seconds;
|
|
1155
2053
|
}
|
|
1156
|
-
function serviceRunFromParts(serviceName, requestKey) {
|
|
1157
|
-
const api = optionalStringField(serviceName, "serviceName");
|
|
1158
|
-
const request = optionalStringField(requestKey, "requestKey");
|
|
1159
|
-
return api && request ? `${api}.${request}` : undefined;
|
|
1160
|
-
}
|
|
1161
2054
|
function stringField(value, fieldName) {
|
|
1162
2055
|
if (typeof value !== "string" || !value.trim()) {
|
|
1163
2056
|
throw new Error(`${fieldName} must be a non-empty string.`);
|
|
@@ -1188,10 +2081,30 @@ function recordField(value, fieldName) {
|
|
|
1188
2081
|
}
|
|
1189
2082
|
return value;
|
|
1190
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
|
+
}
|
|
1191
2103
|
async function fetchApiCatalog(options) {
|
|
1192
2104
|
const client = new TapiClient({
|
|
1193
2105
|
baseUrl: options.apiBaseUrl,
|
|
1194
2106
|
apiKey: options.apiKey,
|
|
2107
|
+
tappId: options.projectId,
|
|
1195
2108
|
projectId: options.projectId,
|
|
1196
2109
|
});
|
|
1197
2110
|
return client.catalog.get();
|
|
@@ -1203,50 +2116,6 @@ async function writeTextFile(path, content) {
|
|
|
1203
2116
|
await mkdir(dirname(path), { recursive: true });
|
|
1204
2117
|
await writeFile(path, content, "utf8");
|
|
1205
2118
|
}
|
|
1206
|
-
function renderServiceRunClient(catalog) {
|
|
1207
|
-
const namespaces = new Map();
|
|
1208
|
-
for (const api of catalog.apis || []) {
|
|
1209
|
-
const namespace = safeIdentifier(api.name || "api");
|
|
1210
|
-
for (const request of api.requests || []) {
|
|
1211
|
-
const key = String(request.operation || request.sdkName || request.key || "").trim();
|
|
1212
|
-
if (!key)
|
|
1213
|
-
continue;
|
|
1214
|
-
const operationName = safeIdentifier(key);
|
|
1215
|
-
const operation = `${api.name}.${key}`;
|
|
1216
|
-
const items = namespaces.get(namespace) ?? [];
|
|
1217
|
-
items.push({ key: operation, operationName });
|
|
1218
|
-
namespaces.set(namespace, items);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
const namespaceBlocks = [...namespaces.entries()].map(([namespace, operations]) => {
|
|
1222
|
-
const lines = operations.map(({ key, operationName }) => ` ${operationName}: (inputs: Record<string, unknown> = {}, options: ServiceRunOptions = {}) => client.services.run(${JSON.stringify(key)}, { ...options, inputs }),`);
|
|
1223
|
-
return ` ${namespace}: {\n${lines.join("\n")}\n },`;
|
|
1224
|
-
});
|
|
1225
|
-
return `/* Created by Tapi. Do not edit by hand. */
|
|
1226
|
-
import { TapiClient, type TapiClientOptions, type RuntimeRunOptions } from "@tapi-dev/sdk";
|
|
1227
|
-
|
|
1228
|
-
export interface ServiceRunOptions {
|
|
1229
|
-
runtime?: RuntimeRunOptions;
|
|
1230
|
-
priority?: number;
|
|
1231
|
-
runnerId?: string;
|
|
1232
|
-
idempotencyKey?: string;
|
|
1233
|
-
site?: string;
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
export function createTapiServicesClient(options: TapiClientOptions) {
|
|
1237
|
-
const client = new TapiClient(options);
|
|
1238
|
-
return {
|
|
1239
|
-
${namespaceBlocks.join("\n")}
|
|
1240
|
-
};
|
|
1241
|
-
}
|
|
1242
|
-
`;
|
|
1243
|
-
}
|
|
1244
|
-
function safeIdentifier(value) {
|
|
1245
|
-
const cleaned = String(value || "api")
|
|
1246
|
-
.replace(/[^a-zA-Z0-9_$]+/g, "_")
|
|
1247
|
-
.replace(/^([^a-zA-Z_$])/, "_$1");
|
|
1248
|
-
return cleaned || "api";
|
|
1249
|
-
}
|
|
1250
2119
|
function isRecord(value) {
|
|
1251
2120
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1252
2121
|
}
|
|
@@ -2994,6 +3863,56 @@ function openBrowser(url) {
|
|
|
2994
3863
|
});
|
|
2995
3864
|
child.unref();
|
|
2996
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
|
+
}
|
|
2997
3916
|
async function readJsonBody(response) {
|
|
2998
3917
|
const text = await response.text();
|
|
2999
3918
|
if (!text.trim()) {
|
|
@@ -3006,6 +3925,40 @@ async function readJsonBody(response) {
|
|
|
3006
3925
|
return null;
|
|
3007
3926
|
}
|
|
3008
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
|
+
}
|
|
3009
3962
|
function isApprovalTerminalError(error) {
|
|
3010
3963
|
return error instanceof StudioInstallApprovalError
|
|
3011
3964
|
&& (error.code === "pending_approval" || error.code === "access_pending" || error.code === "access_rejected");
|
|
@@ -3560,11 +4513,16 @@ Usage:
|
|
|
3560
4513
|
tapi link --project PROJECT
|
|
3561
4514
|
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
3562
4515
|
tapi studio
|
|
3563
|
-
tapi studio open
|
|
3564
|
-
tapi studio doctor
|
|
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>
|
|
3566
4525
|
tapi services sync
|
|
3567
|
-
tapi services generate
|
|
3568
4526
|
tapi triggers sync
|
|
3569
4527
|
tapi sessions
|
|
3570
4528
|
tapi service status
|
|
@@ -3574,20 +4532,75 @@ Commands:
|
|
|
3574
4532
|
init Create .tapi/project.json for this repo
|
|
3575
4533
|
link Rebind this repo to an existing Tapi project
|
|
3576
4534
|
studio install Download, verify, and run the Tapi Studio installer
|
|
3577
|
-
studio Open Tapi Studio for this repo
|
|
3578
|
-
studio open Open Tapi Studio for this repo
|
|
3579
|
-
studio doctor Check local SDK and Studio release configuration
|
|
3580
|
-
|
|
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
|
|
3581
4546
|
Print a ServiceMap service-run input/output contract
|
|
3582
4547
|
services sync Save the service-run catalog to .tapi/services
|
|
3583
|
-
services generate
|
|
3584
|
-
Write a TypeScript service-run wrapper from the catalog
|
|
3585
4548
|
triggers sync Upsert service triggers from tapi.config
|
|
3586
4549
|
sessions List dev-mode API sessions and open takeover sessions
|
|
3587
4550
|
service Inspect or control the local Tapi Windows service
|
|
3588
4551
|
doctor Alias for studio doctor
|
|
3589
4552
|
`);
|
|
3590
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
|
+
}
|
|
3591
4604
|
function printSessionsHelp() {
|
|
3592
4605
|
console.log(`Tapi dev session commands
|
|
3593
4606
|
|
|
@@ -3612,7 +4625,6 @@ function printServicesHelp() {
|
|
|
3612
4625
|
Usage:
|
|
3613
4626
|
tapi services describe <servicemap.run> [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3614
4627
|
tapi services sync [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3615
|
-
tapi services generate [--api-base-url URL] [--api-key KEY] [--project PROJECT] [--out FILE]
|
|
3616
4628
|
|
|
3617
4629
|
Options:
|
|
3618
4630
|
--api-base-url <url> Tapi API base URL
|
|
@@ -3620,7 +4632,6 @@ Options:
|
|
|
3620
4632
|
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3621
4633
|
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3622
4634
|
--catalog <path> Catalog JSON output path
|
|
3623
|
-
--out <path> TypeScript wrapper output path
|
|
3624
4635
|
`);
|
|
3625
4636
|
}
|
|
3626
4637
|
function printTriggersHelp() {
|
|
@@ -3643,7 +4654,7 @@ Config:
|
|
|
3643
4654
|
serviceRun: "schwab.get_balance",
|
|
3644
4655
|
interval: "1h",
|
|
3645
4656
|
inputs: { accountId: "main" },
|
|
3646
|
-
runtime: { profileRef: "perm_default" },
|
|
4657
|
+
runtime: { owner: "service_run", profileRef: "perm_default" },
|
|
3647
4658
|
},
|
|
3648
4659
|
},
|
|
3649
4660
|
};
|
|
@@ -3756,7 +4767,15 @@ function serializeError(error) {
|
|
|
3756
4767
|
}
|
|
3757
4768
|
function isCliEntrypoint() {
|
|
3758
4769
|
const invokedPath = process.argv[1];
|
|
3759
|
-
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
|
+
}
|
|
3760
4779
|
}
|
|
3761
4780
|
if (isCliEntrypoint()) {
|
|
3762
4781
|
runCli().then((code) => {
|