@tapi-dev/sdk 0.1.34 → 0.1.38
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 +176 -137
- package/dist/catalog.js +3 -2
- package/dist/cli.d.ts +30 -0
- package/dist/cli.js +1178 -237
- package/dist/client.d.ts +2 -1
- package/dist/client.js +20 -3
- 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 +56 -55
- 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;
|
|
@@ -113,6 +117,50 @@ async function withCliSpinner(message, work) {
|
|
|
113
117
|
throw error;
|
|
114
118
|
}
|
|
115
119
|
}
|
|
120
|
+
async function withSdkAuth(options) {
|
|
121
|
+
if (options.authToken?.trim()) {
|
|
122
|
+
return { ...options, authToken: options.authToken.trim() };
|
|
123
|
+
}
|
|
124
|
+
const envAuthToken = envString("TAPI_FIREBASE_ID_TOKEN");
|
|
125
|
+
if (envAuthToken) {
|
|
126
|
+
return { ...options, authToken: envAuthToken };
|
|
127
|
+
}
|
|
128
|
+
const credentials = await requireSdkFirebaseCredentials();
|
|
129
|
+
return { ...options, authToken: credentials.idToken };
|
|
130
|
+
}
|
|
131
|
+
async function requireSdkFirebaseCredentials() {
|
|
132
|
+
const credentials = await refreshCachedFirebaseCredentials();
|
|
133
|
+
if (!credentials) {
|
|
134
|
+
throw new Error(`Tapi sign-in is required. Run \`tapi login\`, then retry.`);
|
|
135
|
+
}
|
|
136
|
+
await writeStudioAuthCache(credentials);
|
|
137
|
+
return credentials;
|
|
138
|
+
}
|
|
139
|
+
async function runLogin() {
|
|
140
|
+
try {
|
|
141
|
+
const credentials = await withCliSpinner("Signing in to Tapi", () => authenticateSdkUser());
|
|
142
|
+
console.log(`Signed in to Tapi as ${credentials.uid}.`);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error(formatError(error));
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function authenticateSdkUser() {
|
|
151
|
+
const cached = await refreshCachedFirebaseCredentials();
|
|
152
|
+
if (cached) {
|
|
153
|
+
await writeStudioAuthCache(cached);
|
|
154
|
+
return cached;
|
|
155
|
+
}
|
|
156
|
+
const browserAuth = await browserGoogleSignIn();
|
|
157
|
+
if (!browserAuth.idToken) {
|
|
158
|
+
throw new Error(browserAuth.error || browserAuth.detail || "Browser sign-in did not return a Google ID token.");
|
|
159
|
+
}
|
|
160
|
+
const credentials = await exchangeGoogleToFirebase(browserAuth.idToken, browserAuth.accessToken);
|
|
161
|
+
await writeStudioAuthCache(credentials);
|
|
162
|
+
return credentials;
|
|
163
|
+
}
|
|
116
164
|
export async function runCli(argv = process.argv.slice(2)) {
|
|
117
165
|
const [command, subcommand, ...rest] = argv;
|
|
118
166
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
@@ -123,6 +171,18 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
123
171
|
console.log(sdkVersion);
|
|
124
172
|
return 0;
|
|
125
173
|
}
|
|
174
|
+
if (command === "login") {
|
|
175
|
+
if (hasHelpFlag([subcommand, ...rest])) {
|
|
176
|
+
printLoginHelp();
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
if (subcommand) {
|
|
180
|
+
console.error(`Unexpected login argument: ${subcommand}`);
|
|
181
|
+
printLoginHelp();
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
return runLogin();
|
|
185
|
+
}
|
|
126
186
|
if (command === "doctor") {
|
|
127
187
|
if (hasHelpFlag([subcommand, ...rest])) {
|
|
128
188
|
printStudioHelp();
|
|
@@ -144,8 +204,12 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
144
204
|
}
|
|
145
205
|
return runServiceCommand(subcommand, rest);
|
|
146
206
|
}
|
|
147
|
-
if (command === "services"
|
|
148
|
-
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h"
|
|
207
|
+
if (command === "services") {
|
|
208
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
209
|
+
printServicesHelp();
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
212
|
+
if (hasHelpFlag(rest)) {
|
|
149
213
|
printServicesHelp();
|
|
150
214
|
return 0;
|
|
151
215
|
}
|
|
@@ -155,20 +219,71 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
155
219
|
if (subcommand === "sync") {
|
|
156
220
|
return syncServiceCatalog(rest);
|
|
157
221
|
}
|
|
158
|
-
if (subcommand === "generate") {
|
|
159
|
-
return generateServiceClient(rest);
|
|
160
|
-
}
|
|
161
222
|
console.error(`Unknown service-run command: ${subcommand}`);
|
|
162
223
|
printServicesHelp();
|
|
163
224
|
return 1;
|
|
164
225
|
}
|
|
226
|
+
if (command === "tapp") {
|
|
227
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
228
|
+
printTappHelp();
|
|
229
|
+
return 0;
|
|
230
|
+
}
|
|
231
|
+
if (subcommand === "create") {
|
|
232
|
+
return createTapp(rest);
|
|
233
|
+
}
|
|
234
|
+
if (subcommand === "service") {
|
|
235
|
+
const serviceCommand = rest[0] || "";
|
|
236
|
+
if (serviceCommand === "add") {
|
|
237
|
+
return addTappService(rest.slice(1));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (subcommand === "queue") {
|
|
241
|
+
const queueCommand = rest[0] || "";
|
|
242
|
+
if (queueCommand === "add") {
|
|
243
|
+
return addTappQueue(rest.slice(1));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
console.error(`Unknown Tapp command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
|
|
247
|
+
printTappHelp();
|
|
248
|
+
return 1;
|
|
249
|
+
}
|
|
250
|
+
if (command === "queue") {
|
|
251
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
252
|
+
printQueueHelp();
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
if (subcommand === "create") {
|
|
256
|
+
return createQueue(rest);
|
|
257
|
+
}
|
|
258
|
+
console.error(`Unknown queue command: ${subcommand}`);
|
|
259
|
+
printQueueHelp();
|
|
260
|
+
return 1;
|
|
261
|
+
}
|
|
262
|
+
if (command === "runner") {
|
|
263
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
264
|
+
printRunnerHelp();
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
if (subcommand === "setup") {
|
|
268
|
+
return runRunnerSetup(rest);
|
|
269
|
+
}
|
|
270
|
+
if (subcommand === "slot") {
|
|
271
|
+
const slotCommand = rest[0] || "";
|
|
272
|
+
if (slotCommand === "set") {
|
|
273
|
+
return setRunnerSlot(rest.slice(1));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
console.error(`Unknown runner command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
|
|
277
|
+
printRunnerHelp();
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
165
280
|
if (command === "triggers") {
|
|
166
281
|
if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
|
|
167
282
|
printTriggersHelp();
|
|
168
283
|
return 0;
|
|
169
284
|
}
|
|
170
285
|
if (subcommand === "sync") {
|
|
171
|
-
return
|
|
286
|
+
return syncServiceTriggers(rest);
|
|
172
287
|
}
|
|
173
288
|
console.error(`Unknown triggers command: ${subcommand}`);
|
|
174
289
|
printTriggersHelp();
|
|
@@ -363,16 +478,406 @@ export function parseStudioOptions(args) {
|
|
|
363
478
|
workspace,
|
|
364
479
|
workspaceRoot: workspace?.root,
|
|
365
480
|
projectId,
|
|
366
|
-
projectSlug,
|
|
367
|
-
workspaceMode: Boolean(projectId || workspace),
|
|
481
|
+
projectSlug,
|
|
482
|
+
workspaceMode: Boolean(projectId || workspace),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function parseServiceOptions(args) {
|
|
486
|
+
let operation = "";
|
|
487
|
+
const workspace = loadWorkspace();
|
|
488
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
489
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
490
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
491
|
+
const arg = args[index];
|
|
492
|
+
if (!arg)
|
|
493
|
+
continue;
|
|
494
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
495
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
499
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (arg.startsWith("--server=")) {
|
|
503
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
if (arg === "--project") {
|
|
507
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (arg.startsWith("--project=")) {
|
|
511
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (arg.startsWith("--")) {
|
|
515
|
+
throw new Error(`Unknown services option: ${arg}`);
|
|
516
|
+
}
|
|
517
|
+
if (!operation) {
|
|
518
|
+
operation = arg;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
throw new Error(`Unexpected services argument: ${arg}`);
|
|
522
|
+
}
|
|
523
|
+
if (!operation) {
|
|
524
|
+
throw new Error("services describe requires a service run like schwab.place_order.");
|
|
525
|
+
}
|
|
526
|
+
if (!projectId) {
|
|
527
|
+
throw new Error("services describe requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
operation,
|
|
531
|
+
options: {
|
|
532
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
533
|
+
projectId,
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
async function describeServiceOperation(args) {
|
|
538
|
+
try {
|
|
539
|
+
const { operation, options } = parseServiceOptions(args);
|
|
540
|
+
const authenticated = await withSdkAuth(options);
|
|
541
|
+
const client = new TapiClient({
|
|
542
|
+
baseUrl: authenticated.apiBaseUrl,
|
|
543
|
+
authToken: authenticated.authToken,
|
|
544
|
+
tappId: authenticated.projectId,
|
|
545
|
+
projectId: authenticated.projectId,
|
|
546
|
+
});
|
|
547
|
+
const description = await withCliSpinner(`Fetching Tapi service-run contract for ${operation}`, () => client.services.describe(operation));
|
|
548
|
+
console.log(JSON.stringify(description, null, 2));
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
catch (error) {
|
|
552
|
+
console.error(formatError(error));
|
|
553
|
+
return 1;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function parseApiWorkspaceOptions(args) {
|
|
557
|
+
const workspace = loadWorkspace();
|
|
558
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
559
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
560
|
+
let catalogPath = workspace?.config.services?.catalog || ".tapi/services/catalog.json";
|
|
561
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
562
|
+
const arg = args[index];
|
|
563
|
+
if (!arg)
|
|
564
|
+
continue;
|
|
565
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
566
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
570
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (arg.startsWith("--server=")) {
|
|
574
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (arg === "--project") {
|
|
578
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (arg.startsWith("--project=")) {
|
|
582
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (arg === "--catalog") {
|
|
586
|
+
catalogPath = requireOptionValue(args, ++index, "--catalog");
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (arg.startsWith("--catalog=")) {
|
|
590
|
+
catalogPath = arg.slice("--catalog=".length);
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
throw new Error(`Unknown services option: ${arg}`);
|
|
594
|
+
}
|
|
595
|
+
if (!projectId) {
|
|
596
|
+
throw new Error("services command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
597
|
+
}
|
|
598
|
+
const root = workspace?.root || process.cwd();
|
|
599
|
+
return {
|
|
600
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
601
|
+
projectId,
|
|
602
|
+
catalogPath: resolve(root, catalogPath),
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
async function syncServiceCatalog(args) {
|
|
606
|
+
try {
|
|
607
|
+
const options = await withSdkAuth(parseApiWorkspaceOptions(args));
|
|
608
|
+
const catalog = await withCliSpinner("Fetching Tapi service-run catalog", () => fetchApiCatalog(options));
|
|
609
|
+
await writeJsonFile(options.catalogPath, catalog);
|
|
610
|
+
console.log(`Synced Tapi service-run catalog: ${options.catalogPath}`);
|
|
611
|
+
return 0;
|
|
612
|
+
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
console.error(formatError(error));
|
|
615
|
+
return 1;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function parseTappServiceAddOptions(args) {
|
|
619
|
+
const workspace = loadWorkspace();
|
|
620
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
621
|
+
let tappId = "";
|
|
622
|
+
let serviceCall = "";
|
|
623
|
+
let serviceMapId = "";
|
|
624
|
+
let entry = "";
|
|
625
|
+
let expectedRevision = "";
|
|
626
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
627
|
+
const arg = args[index];
|
|
628
|
+
if (!arg)
|
|
629
|
+
continue;
|
|
630
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
631
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
635
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (arg.startsWith("--server=")) {
|
|
639
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (arg === "--service-map") {
|
|
643
|
+
serviceMapId = requireOptionValue(args, ++index, "--service-map");
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
if (arg.startsWith("--service-map=")) {
|
|
647
|
+
serviceMapId = arg.slice("--service-map=".length);
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (arg === "--entry") {
|
|
651
|
+
entry = requireOptionValue(args, ++index, "--entry");
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (arg.startsWith("--entry=")) {
|
|
655
|
+
entry = arg.slice("--entry=".length);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (arg === "--expected-revision") {
|
|
659
|
+
expectedRevision = requireOptionValue(args, ++index, "--expected-revision");
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (arg.startsWith("--expected-revision=")) {
|
|
663
|
+
expectedRevision = arg.slice("--expected-revision=".length);
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (arg.startsWith("--")) {
|
|
667
|
+
throw new Error(`Unknown tapp service add option: ${arg}`);
|
|
668
|
+
}
|
|
669
|
+
if (!tappId) {
|
|
670
|
+
tappId = normalizeProjectValue(arg, "tapp");
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (!serviceCall) {
|
|
674
|
+
serviceCall = arg.trim();
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
throw new Error(`Unexpected tapp service add argument: ${arg}`);
|
|
678
|
+
}
|
|
679
|
+
if (!tappId) {
|
|
680
|
+
throw new Error("tapp service add requires a Tapp id.");
|
|
681
|
+
}
|
|
682
|
+
if (!serviceCall || !serviceCall.includes(".")) {
|
|
683
|
+
throw new Error("tapp service add requires a service call like schwab.place_order.");
|
|
684
|
+
}
|
|
685
|
+
if (!serviceMapId) {
|
|
686
|
+
throw new Error("tapp service add requires --service-map.");
|
|
687
|
+
}
|
|
688
|
+
if (!entry) {
|
|
689
|
+
throw new Error("tapp service add requires --entry.");
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
693
|
+
tappId,
|
|
694
|
+
serviceCall,
|
|
695
|
+
serviceMapId,
|
|
696
|
+
entry,
|
|
697
|
+
expectedRevision,
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
function parseTappCreateOptions(args) {
|
|
701
|
+
const workspace = loadWorkspace();
|
|
702
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
703
|
+
let tappId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
704
|
+
let name = "";
|
|
705
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
706
|
+
const arg = args[index];
|
|
707
|
+
if (!arg)
|
|
708
|
+
continue;
|
|
709
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
710
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
714
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (arg.startsWith("--server=")) {
|
|
718
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (arg === "--tapp-id" || arg === "--project") {
|
|
722
|
+
tappId = normalizeProjectValue(requireOptionValue(args, ++index, arg), "tapp");
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
if (arg.startsWith("--tapp-id=")) {
|
|
726
|
+
tappId = normalizeProjectValue(arg.slice("--tapp-id=".length), "tapp");
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
if (arg.startsWith("--project=")) {
|
|
730
|
+
tappId = normalizeProjectValue(arg.slice("--project=".length), "tapp");
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (arg === "--name") {
|
|
734
|
+
name = requireOptionValue(args, ++index, "--name").trim();
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (arg.startsWith("--name=")) {
|
|
738
|
+
name = arg.slice("--name=".length).trim();
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
if (arg.startsWith("--")) {
|
|
742
|
+
throw new Error(`Unknown tapp create option: ${arg}`);
|
|
743
|
+
}
|
|
744
|
+
if (!name) {
|
|
745
|
+
name = arg.trim();
|
|
746
|
+
if (!tappId) {
|
|
747
|
+
tappId = normalizeProjectValue(name, "tapp");
|
|
748
|
+
}
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
throw new Error(`Unexpected tapp create argument: ${arg}`);
|
|
752
|
+
}
|
|
753
|
+
if (!tappId) {
|
|
754
|
+
throw new Error("tapp create requires a Tapp id or name.");
|
|
755
|
+
}
|
|
756
|
+
if (!name) {
|
|
757
|
+
name = tappId;
|
|
758
|
+
}
|
|
759
|
+
return {
|
|
760
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
761
|
+
tappId,
|
|
762
|
+
name,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async function createTapp(args) {
|
|
766
|
+
try {
|
|
767
|
+
const options = await withSdkAuth(parseTappCreateOptions(args));
|
|
768
|
+
const tapp = await withCliSpinner(`Creating Tapi Tapp ${options.tappId}`, () => postTappCreate(options));
|
|
769
|
+
console.log(JSON.stringify(tapp, null, 2));
|
|
770
|
+
return 0;
|
|
771
|
+
}
|
|
772
|
+
catch (error) {
|
|
773
|
+
console.error(formatError(error));
|
|
774
|
+
return 1;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
async function postTappCreate(options, fetchImpl = fetch) {
|
|
778
|
+
const response = await fetchImpl(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps`, {
|
|
779
|
+
method: "POST",
|
|
780
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
781
|
+
body: JSON.stringify({
|
|
782
|
+
tappId: options.tappId,
|
|
783
|
+
name: options.name,
|
|
784
|
+
}),
|
|
785
|
+
});
|
|
786
|
+
const body = await readJsonBody(response);
|
|
787
|
+
if (!response.ok) {
|
|
788
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
789
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
790
|
+
}
|
|
791
|
+
return body ?? {};
|
|
792
|
+
}
|
|
793
|
+
async function addTappService(args) {
|
|
794
|
+
try {
|
|
795
|
+
const options = await withSdkAuth(parseTappServiceAddOptions(args));
|
|
796
|
+
const operation = await withCliSpinner(`Adding Tapi service ${options.serviceCall} to ${options.tappId}`, () => postTappServiceAdd(options));
|
|
797
|
+
console.log(JSON.stringify(operation, null, 2));
|
|
798
|
+
return 0;
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
console.error(formatError(error));
|
|
802
|
+
return 1;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async function postTappServiceAdd(options) {
|
|
806
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/services/${encodeURIComponent(options.serviceCall)}`, {
|
|
807
|
+
method: "POST",
|
|
808
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
809
|
+
body: JSON.stringify({
|
|
810
|
+
serviceMapId: options.serviceMapId,
|
|
811
|
+
entry: options.entry,
|
|
812
|
+
...(options.expectedRevision ? { expectedRevision: options.expectedRevision } : {}),
|
|
813
|
+
}),
|
|
814
|
+
});
|
|
815
|
+
const body = await readJsonBody(response);
|
|
816
|
+
if (!response.ok) {
|
|
817
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
818
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
819
|
+
}
|
|
820
|
+
return body ?? {};
|
|
821
|
+
}
|
|
822
|
+
function parseQueueCreateOptions(args) {
|
|
823
|
+
const workspace = loadWorkspace();
|
|
824
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
825
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
826
|
+
let displayName = "";
|
|
827
|
+
let maxSlots = 8;
|
|
828
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
829
|
+
const arg = args[index];
|
|
830
|
+
if (!arg)
|
|
831
|
+
continue;
|
|
832
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
833
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
837
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (arg.startsWith("--server=")) {
|
|
841
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
if (arg === "--project") {
|
|
845
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
if (arg.startsWith("--project=")) {
|
|
849
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
if (arg === "--max-slots") {
|
|
853
|
+
maxSlots = parsePositiveInteger(requireOptionValue(args, ++index, "--max-slots"), "--max-slots");
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (arg.startsWith("--max-slots=")) {
|
|
857
|
+
maxSlots = parsePositiveInteger(arg.slice("--max-slots=".length), "--max-slots");
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
if (arg.startsWith("--")) {
|
|
861
|
+
throw new Error(`Unknown queue create option: ${arg}`);
|
|
862
|
+
}
|
|
863
|
+
if (!displayName) {
|
|
864
|
+
displayName = arg.trim();
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
throw new Error(`Unexpected queue create argument: ${arg}`);
|
|
868
|
+
}
|
|
869
|
+
return {
|
|
870
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
871
|
+
projectId,
|
|
872
|
+
displayName,
|
|
873
|
+
maxSlots,
|
|
368
874
|
};
|
|
369
875
|
}
|
|
370
|
-
function
|
|
371
|
-
let operation = "";
|
|
876
|
+
function parseTappQueueAddOptions(args) {
|
|
372
877
|
const workspace = loadWorkspace();
|
|
373
878
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
374
|
-
let
|
|
375
|
-
let
|
|
879
|
+
let tappId = "";
|
|
880
|
+
let queueId = "";
|
|
376
881
|
for (let index = 0; index < args.length; index += 1) {
|
|
377
882
|
const arg = args[index];
|
|
378
883
|
if (!arg)
|
|
@@ -389,12 +894,52 @@ function parseServiceOptions(args) {
|
|
|
389
894
|
apiBaseUrl = arg.slice("--server=".length);
|
|
390
895
|
continue;
|
|
391
896
|
}
|
|
392
|
-
if (arg
|
|
393
|
-
|
|
897
|
+
if (arg.startsWith("--")) {
|
|
898
|
+
throw new Error(`Unknown tapp queue add option: ${arg}`);
|
|
899
|
+
}
|
|
900
|
+
if (!tappId) {
|
|
901
|
+
tappId = normalizeProjectValue(arg, "tapp");
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (!queueId) {
|
|
905
|
+
queueId = arg.trim();
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
throw new Error(`Unexpected tapp queue add argument: ${arg}`);
|
|
909
|
+
}
|
|
910
|
+
if (!tappId) {
|
|
911
|
+
throw new Error("tapp queue add requires a Tapp id.");
|
|
912
|
+
}
|
|
913
|
+
if (!queueId) {
|
|
914
|
+
throw new Error("tapp queue add requires a queue id.");
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
918
|
+
tappId,
|
|
919
|
+
queueId,
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function parseRunnerSlotSetOptions(args) {
|
|
923
|
+
const workspace = loadWorkspace();
|
|
924
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
925
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
926
|
+
let runnerId = "";
|
|
927
|
+
let slotIndex = 0;
|
|
928
|
+
let queueId = "";
|
|
929
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
930
|
+
const arg = args[index];
|
|
931
|
+
if (!arg)
|
|
932
|
+
continue;
|
|
933
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
934
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
938
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
394
939
|
continue;
|
|
395
940
|
}
|
|
396
|
-
if (arg.startsWith("--
|
|
397
|
-
|
|
941
|
+
if (arg.startsWith("--server=")) {
|
|
942
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
398
943
|
continue;
|
|
399
944
|
}
|
|
400
945
|
if (arg === "--project") {
|
|
@@ -406,42 +951,47 @@ function parseServiceOptions(args) {
|
|
|
406
951
|
continue;
|
|
407
952
|
}
|
|
408
953
|
if (arg.startsWith("--")) {
|
|
409
|
-
throw new Error(`Unknown
|
|
954
|
+
throw new Error(`Unknown runner slot set option: ${arg}`);
|
|
410
955
|
}
|
|
411
|
-
if (!
|
|
412
|
-
|
|
956
|
+
if (!runnerId) {
|
|
957
|
+
runnerId = arg.trim();
|
|
413
958
|
continue;
|
|
414
959
|
}
|
|
415
|
-
|
|
960
|
+
if (!slotIndex) {
|
|
961
|
+
slotIndex = parsePositiveInteger(arg.trim(), "slot index");
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
if (!queueId) {
|
|
965
|
+
queueId = arg.trim();
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
throw new Error(`Unexpected runner slot set argument: ${arg}`);
|
|
416
969
|
}
|
|
417
|
-
if (!
|
|
418
|
-
throw new Error("
|
|
970
|
+
if (!projectId) {
|
|
971
|
+
throw new Error("runner slot set requires --project or TAPI_PROJECT_ID.");
|
|
419
972
|
}
|
|
420
|
-
if (!
|
|
421
|
-
throw new Error("
|
|
973
|
+
if (!runnerId) {
|
|
974
|
+
throw new Error("runner slot set requires a runner id.");
|
|
422
975
|
}
|
|
423
|
-
if (!
|
|
424
|
-
throw new Error("
|
|
976
|
+
if (!slotIndex) {
|
|
977
|
+
throw new Error("runner slot set requires a slot index.");
|
|
978
|
+
}
|
|
979
|
+
if (!queueId) {
|
|
980
|
+
throw new Error("runner slot set requires a queue id.");
|
|
425
981
|
}
|
|
426
982
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
},
|
|
983
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
984
|
+
projectId,
|
|
985
|
+
runnerId,
|
|
986
|
+
slotIndex,
|
|
987
|
+
queueId,
|
|
433
988
|
};
|
|
434
989
|
}
|
|
435
|
-
async function
|
|
990
|
+
async function createQueue(args) {
|
|
436
991
|
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));
|
|
992
|
+
const options = await withSdkAuth(parseQueueCreateOptions(args));
|
|
993
|
+
const queue = await withCliSpinner(`Creating Tapi queue`, () => postQueueCreate(options));
|
|
994
|
+
console.log(JSON.stringify(queue, null, 2));
|
|
445
995
|
return 0;
|
|
446
996
|
}
|
|
447
997
|
catch (error) {
|
|
@@ -449,13 +999,80 @@ async function describeServiceOperation(args) {
|
|
|
449
999
|
return 1;
|
|
450
1000
|
}
|
|
451
1001
|
}
|
|
452
|
-
function
|
|
1002
|
+
async function addTappQueue(args) {
|
|
1003
|
+
try {
|
|
1004
|
+
const options = await withSdkAuth(parseTappQueueAddOptions(args));
|
|
1005
|
+
const result = await withCliSpinner(`Attaching Tapi queue ${options.queueId} to ${options.tappId}`, () => postTappQueueAdd(options));
|
|
1006
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1007
|
+
return 0;
|
|
1008
|
+
}
|
|
1009
|
+
catch (error) {
|
|
1010
|
+
console.error(formatError(error));
|
|
1011
|
+
return 1;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function setRunnerSlot(args) {
|
|
1015
|
+
try {
|
|
1016
|
+
const options = await withSdkAuth(parseRunnerSlotSetOptions(args));
|
|
1017
|
+
const result = await withCliSpinner(`Assigning runner slot ${options.runnerId}/${options.slotIndex} to queue ${options.queueId}`, () => putRunnerSlot(options));
|
|
1018
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1019
|
+
return 0;
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
console.error(formatError(error));
|
|
1023
|
+
return 1;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
async function postQueueCreate(options) {
|
|
1027
|
+
const headers = sdkJsonHeaders(options.authToken, options.projectId);
|
|
1028
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
headers,
|
|
1031
|
+
body: JSON.stringify({
|
|
1032
|
+
...(options.displayName ? { displayName: options.displayName } : {}),
|
|
1033
|
+
maxSlots: options.maxSlots,
|
|
1034
|
+
}),
|
|
1035
|
+
});
|
|
1036
|
+
const body = await readJsonBody(response);
|
|
1037
|
+
if (!response.ok) {
|
|
1038
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1039
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1040
|
+
}
|
|
1041
|
+
return body ?? {};
|
|
1042
|
+
}
|
|
1043
|
+
async function postTappQueueAdd(options) {
|
|
1044
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/queues`, {
|
|
1045
|
+
method: "POST",
|
|
1046
|
+
headers: sdkJsonHeaders(options.authToken, options.tappId),
|
|
1047
|
+
body: JSON.stringify({ queueId: options.queueId }),
|
|
1048
|
+
});
|
|
1049
|
+
const body = await readJsonBody(response);
|
|
1050
|
+
if (!response.ok) {
|
|
1051
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1052
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1053
|
+
}
|
|
1054
|
+
return body ?? {};
|
|
1055
|
+
}
|
|
1056
|
+
async function putRunnerSlot(options) {
|
|
1057
|
+
const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(options.slotIndex))}`, {
|
|
1058
|
+
method: "PUT",
|
|
1059
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1060
|
+
body: JSON.stringify({ queueId: options.queueId }),
|
|
1061
|
+
});
|
|
1062
|
+
const body = await readJsonBody(response);
|
|
1063
|
+
if (!response.ok) {
|
|
1064
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1065
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1066
|
+
}
|
|
1067
|
+
return body ?? {};
|
|
1068
|
+
}
|
|
1069
|
+
export function parseRunnerSetupOptions(args) {
|
|
453
1070
|
const workspace = loadWorkspace();
|
|
454
1071
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
455
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
456
1072
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
457
|
-
let
|
|
458
|
-
let
|
|
1073
|
+
let runnerId = envString("TAPI_RUNNER_ID") || "";
|
|
1074
|
+
let port = Number(envString("TAPI_RUNNER_SETUP_PORT") || "17687");
|
|
1075
|
+
let openBrowser = true;
|
|
459
1076
|
for (let index = 0; index < args.length; index += 1) {
|
|
460
1077
|
const arg = args[index];
|
|
461
1078
|
if (!arg)
|
|
@@ -472,14 +1089,6 @@ function parseApiWorkspaceOptions(args) {
|
|
|
472
1089
|
apiBaseUrl = arg.slice("--server=".length);
|
|
473
1090
|
continue;
|
|
474
1091
|
}
|
|
475
|
-
if (arg === "--api-key") {
|
|
476
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
if (arg.startsWith("--api-key=")) {
|
|
480
|
-
apiKey = arg.slice("--api-key=".length);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
1092
|
if (arg === "--project") {
|
|
484
1093
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
485
1094
|
continue;
|
|
@@ -488,45 +1097,63 @@ function parseApiWorkspaceOptions(args) {
|
|
|
488
1097
|
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
489
1098
|
continue;
|
|
490
1099
|
}
|
|
491
|
-
if (arg === "--
|
|
492
|
-
|
|
1100
|
+
if (arg === "--runner-id") {
|
|
1101
|
+
runnerId = requireOptionValue(args, ++index, "--runner-id").trim();
|
|
493
1102
|
continue;
|
|
494
1103
|
}
|
|
495
|
-
if (arg.startsWith("--
|
|
496
|
-
|
|
1104
|
+
if (arg.startsWith("--runner-id=")) {
|
|
1105
|
+
runnerId = arg.slice("--runner-id=".length).trim();
|
|
497
1106
|
continue;
|
|
498
1107
|
}
|
|
499
|
-
if (arg === "--
|
|
500
|
-
|
|
1108
|
+
if (arg === "--port") {
|
|
1109
|
+
port = parseNonNegativeInteger(requireOptionValue(args, ++index, "--port"), "--port");
|
|
501
1110
|
continue;
|
|
502
1111
|
}
|
|
503
|
-
if (arg.startsWith("--
|
|
504
|
-
|
|
1112
|
+
if (arg.startsWith("--port=")) {
|
|
1113
|
+
port = parseNonNegativeInteger(arg.slice("--port=".length), "--port");
|
|
505
1114
|
continue;
|
|
506
1115
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1116
|
+
if (arg === "--no-open") {
|
|
1117
|
+
openBrowser = false;
|
|
1118
|
+
continue;
|
|
1119
|
+
}
|
|
1120
|
+
if (arg.startsWith("--")) {
|
|
1121
|
+
throw new Error(`Unknown runner setup option: ${arg}`);
|
|
1122
|
+
}
|
|
1123
|
+
if (!runnerId) {
|
|
1124
|
+
runnerId = arg.trim();
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
throw new Error(`Unexpected runner setup argument: ${arg}`);
|
|
511
1128
|
}
|
|
512
1129
|
if (!projectId) {
|
|
513
|
-
throw new Error("
|
|
1130
|
+
throw new Error("runner setup requires --project or TAPI_PROJECT_ID.");
|
|
1131
|
+
}
|
|
1132
|
+
if (!runnerId) {
|
|
1133
|
+
throw new Error("runner setup requires --runner-id or TAPI_RUNNER_ID.");
|
|
1134
|
+
}
|
|
1135
|
+
if (!Number.isInteger(port) || port < 0) {
|
|
1136
|
+
throw new Error("--port must be a non-negative integer.");
|
|
514
1137
|
}
|
|
515
|
-
const root = workspace?.root || process.cwd();
|
|
516
1138
|
return {
|
|
517
1139
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
518
|
-
apiKey,
|
|
519
1140
|
projectId,
|
|
520
|
-
|
|
521
|
-
|
|
1141
|
+
runnerId,
|
|
1142
|
+
port,
|
|
1143
|
+
openBrowser,
|
|
522
1144
|
};
|
|
523
1145
|
}
|
|
524
|
-
async function
|
|
1146
|
+
async function runRunnerSetup(args) {
|
|
525
1147
|
try {
|
|
526
|
-
const options =
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
1148
|
+
const options = await withSdkAuth(parseRunnerSetupOptions(args));
|
|
1149
|
+
const handle = await startRunnerSetupServer(options);
|
|
1150
|
+
if (options.openBrowser) {
|
|
1151
|
+
openUrlInBrowser(handle.url);
|
|
1152
|
+
}
|
|
1153
|
+
console.log(`Tapi runner setup: ${handle.url}`);
|
|
1154
|
+
console.log("Press Ctrl+C to stop the setup server.");
|
|
1155
|
+
await waitForProcessSignal();
|
|
1156
|
+
await handle.close();
|
|
530
1157
|
return 0;
|
|
531
1158
|
}
|
|
532
1159
|
catch (error) {
|
|
@@ -534,24 +1161,230 @@ async function syncServiceCatalog(args) {
|
|
|
534
1161
|
return 1;
|
|
535
1162
|
}
|
|
536
1163
|
}
|
|
537
|
-
async function
|
|
1164
|
+
export async function startRunnerSetupServer(options, fetchImpl = fetch) {
|
|
1165
|
+
const server = createServer((request, response) => {
|
|
1166
|
+
void handleRunnerSetupRequest(request, response, options, fetchImpl);
|
|
1167
|
+
});
|
|
1168
|
+
await listenOnLocalhost(server, options.port);
|
|
1169
|
+
const address = server.address();
|
|
1170
|
+
const port = typeof address === "object" && address ? address.port : options.port;
|
|
1171
|
+
return {
|
|
1172
|
+
url: `http://127.0.0.1:${port}/`,
|
|
1173
|
+
port,
|
|
1174
|
+
close: () => closeServer(server),
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
export async function executeRunnerSetupAction(options, action, fetchImpl = fetch) {
|
|
1178
|
+
let queueId = String(action.queueId || "").trim();
|
|
1179
|
+
const maxSlots = coercePositiveInteger(action.maxSlots, 8, "maxSlots");
|
|
1180
|
+
const slotStart = coercePositiveInteger(action.slotStart, 1, "slotStart");
|
|
1181
|
+
const slotCount = coercePositiveInteger(action.slotCount, 1, "slotCount");
|
|
1182
|
+
let queue = {};
|
|
1183
|
+
if (!queueId) {
|
|
1184
|
+
queue = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
|
|
1185
|
+
method: "POST",
|
|
1186
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1187
|
+
body: JSON.stringify({
|
|
1188
|
+
displayName: String(action.displayName || `${options.projectId} queue`).trim(),
|
|
1189
|
+
maxSlots,
|
|
1190
|
+
}),
|
|
1191
|
+
});
|
|
1192
|
+
queueId = String(queue.queueId || queue.queue_id || "").trim();
|
|
1193
|
+
}
|
|
1194
|
+
if (!queueId) {
|
|
1195
|
+
throw new Error("Runner setup needs an existing queue id or a created queue response with queueId.");
|
|
1196
|
+
}
|
|
1197
|
+
const attachment = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.projectId)}/queues`, {
|
|
1198
|
+
method: "POST",
|
|
1199
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1200
|
+
body: JSON.stringify({ queueId }),
|
|
1201
|
+
});
|
|
1202
|
+
const slots = [];
|
|
1203
|
+
for (let offset = 0; offset < slotCount; offset += 1) {
|
|
1204
|
+
const slotIndex = slotStart + offset;
|
|
1205
|
+
slots.push(await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(slotIndex))}`, {
|
|
1206
|
+
method: "PUT",
|
|
1207
|
+
headers: sdkJsonHeaders(options.authToken, options.projectId),
|
|
1208
|
+
body: JSON.stringify({ queueId }),
|
|
1209
|
+
}));
|
|
1210
|
+
}
|
|
1211
|
+
return {
|
|
1212
|
+
queueId,
|
|
1213
|
+
queue: queueId && !Object.keys(queue).length ? { queueId } : queue,
|
|
1214
|
+
attachment,
|
|
1215
|
+
slots,
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
|
|
538
1219
|
try {
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1220
|
+
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
1221
|
+
if (request.method === "GET" && url.pathname === "/") {
|
|
1222
|
+
sendText(response, 200, runnerSetupHtml(options), "text/html; charset=utf-8");
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
if (request.method === "GET" && url.pathname === "/api/status") {
|
|
1226
|
+
sendJson(response, 200, {
|
|
1227
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
1228
|
+
projectId: options.projectId,
|
|
1229
|
+
runnerId: options.runnerId,
|
|
1230
|
+
});
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (request.method === "POST" && url.pathname === "/api/setup") {
|
|
1234
|
+
const body = await readRequestJson(request);
|
|
1235
|
+
const result = await executeRunnerSetupAction(options, body, fetchImpl);
|
|
1236
|
+
sendJson(response, 200, result);
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
sendJson(response, 404, { error: "not found" });
|
|
545
1240
|
}
|
|
546
1241
|
catch (error) {
|
|
547
|
-
|
|
548
|
-
|
|
1242
|
+
sendJson(response, 400, { error: formatError(error) });
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function runnerSetupHtml(options) {
|
|
1246
|
+
const displayName = `${options.projectId} queue`;
|
|
1247
|
+
return `<!doctype html>
|
|
1248
|
+
<html lang="en">
|
|
1249
|
+
<head>
|
|
1250
|
+
<meta charset="utf-8">
|
|
1251
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
1252
|
+
<title>Tapi Runner Setup</title>
|
|
1253
|
+
<style>
|
|
1254
|
+
:root { color-scheme: light dark; --bg: #f7f7f5; --panel: #ffffff; --text: #1d2329; --muted: #62707c; --line: #d6dde2; --accent: #176b4d; --accent-2: #0f5ca8; --danger: #ad2e24; }
|
|
1255
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #111418; --panel: #181d22; --text: #eef3f6; --muted: #a6b1bb; --line: #303a43; --accent: #59b88c; --accent-2: #6da8e8; --danger: #ee786e; } }
|
|
1256
|
+
* { box-sizing: border-box; }
|
|
1257
|
+
body { margin: 0; font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--text); background: var(--bg); }
|
|
1258
|
+
main { max-width: 920px; margin: 0 auto; padding: 28px 18px 36px; }
|
|
1259
|
+
h1 { margin: 0 0 18px; font-size: 28px; font-weight: 650; letter-spacing: 0; }
|
|
1260
|
+
h2 { margin: 0 0 12px; font-size: 16px; font-weight: 650; letter-spacing: 0; }
|
|
1261
|
+
.grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 320px); gap: 18px; align-items: start; }
|
|
1262
|
+
.panel, .summary { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 18px; }
|
|
1263
|
+
.row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
|
|
1264
|
+
label { display: grid; gap: 6px; margin: 0 0 12px; color: var(--muted); font-size: 12px; font-weight: 600; text-transform: uppercase; }
|
|
1265
|
+
input { width: 100%; min-width: 0; border: 1px solid var(--line); border-radius: 6px; padding: 10px 11px; color: var(--text); background: transparent; font: inherit; }
|
|
1266
|
+
button { border: 0; border-radius: 6px; padding: 11px 14px; color: white; background: var(--accent); font: inherit; font-weight: 650; cursor: pointer; }
|
|
1267
|
+
button:disabled { opacity: .55; cursor: progress; }
|
|
1268
|
+
code { word-break: break-all; }
|
|
1269
|
+
.summary dl { display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 8px 10px; margin: 0; }
|
|
1270
|
+
.summary dt { color: var(--muted); font-weight: 650; }
|
|
1271
|
+
.summary dd { margin: 0; min-width: 0; }
|
|
1272
|
+
.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; }
|
|
1273
|
+
.error { color: var(--danger); }
|
|
1274
|
+
.ok { color: var(--accent-2); }
|
|
1275
|
+
@media (max-width: 720px) { .grid, .row { grid-template-columns: 1fr; } main { padding: 20px 14px 28px; } h1 { font-size: 24px; } }
|
|
1276
|
+
</style>
|
|
1277
|
+
</head>
|
|
1278
|
+
<body>
|
|
1279
|
+
<main>
|
|
1280
|
+
<h1>Tapi Runner Setup</h1>
|
|
1281
|
+
<div class="grid">
|
|
1282
|
+
<section class="panel">
|
|
1283
|
+
<h2>Queue And Slots</h2>
|
|
1284
|
+
<form id="setup-form">
|
|
1285
|
+
<label>Existing Queue ID<input id="queueId" name="queueId" autocomplete="off" placeholder="queue_abc123"></label>
|
|
1286
|
+
<label>New Queue Name<input id="displayName" name="displayName" value="${escapeHtml(displayName)}" autocomplete="off"></label>
|
|
1287
|
+
<div class="row">
|
|
1288
|
+
<label>Queue Slot Limit<input id="maxSlots" name="maxSlots" type="number" min="1" step="1" value="8"></label>
|
|
1289
|
+
<label>First Runner Slot<input id="slotStart" name="slotStart" type="number" min="1" step="1" value="1"></label>
|
|
1290
|
+
</div>
|
|
1291
|
+
<label>Runner Slot Count<input id="slotCount" name="slotCount" type="number" min="1" step="1" value="1"></label>
|
|
1292
|
+
<button id="submit" type="submit">Apply Setup</button>
|
|
1293
|
+
</form>
|
|
1294
|
+
<pre id="result" class="result" aria-live="polite"></pre>
|
|
1295
|
+
</section>
|
|
1296
|
+
<aside class="summary">
|
|
1297
|
+
<h2>Target</h2>
|
|
1298
|
+
<dl>
|
|
1299
|
+
<dt>Server</dt><dd><code>${escapeHtml(options.apiBaseUrl)}</code></dd>
|
|
1300
|
+
<dt>Tapp</dt><dd><code>${escapeHtml(options.projectId)}</code></dd>
|
|
1301
|
+
<dt>Runner</dt><dd><code>${escapeHtml(options.runnerId)}</code></dd>
|
|
1302
|
+
</dl>
|
|
1303
|
+
</aside>
|
|
1304
|
+
</div>
|
|
1305
|
+
</main>
|
|
1306
|
+
<script>
|
|
1307
|
+
const form = document.getElementById("setup-form");
|
|
1308
|
+
const button = document.getElementById("submit");
|
|
1309
|
+
const result = document.getElementById("result");
|
|
1310
|
+
form.addEventListener("submit", async (event) => {
|
|
1311
|
+
event.preventDefault();
|
|
1312
|
+
button.disabled = true;
|
|
1313
|
+
result.className = "result";
|
|
1314
|
+
result.textContent = "Applying setup...";
|
|
1315
|
+
const body = Object.fromEntries(new FormData(form).entries());
|
|
1316
|
+
body.maxSlots = Number(body.maxSlots || 8);
|
|
1317
|
+
body.slotStart = Number(body.slotStart || 1);
|
|
1318
|
+
body.slotCount = Number(body.slotCount || 1);
|
|
1319
|
+
try {
|
|
1320
|
+
const response = await fetch("/api/setup", {
|
|
1321
|
+
method: "POST",
|
|
1322
|
+
headers: { "Content-Type": "application/json" },
|
|
1323
|
+
body: JSON.stringify(body),
|
|
1324
|
+
});
|
|
1325
|
+
const payload = await response.json();
|
|
1326
|
+
if (!response.ok) throw new Error(payload.error || JSON.stringify(payload));
|
|
1327
|
+
result.className = "result ok";
|
|
1328
|
+
result.textContent = JSON.stringify(payload, null, 2);
|
|
1329
|
+
document.getElementById("queueId").value = payload.queueId || body.queueId || "";
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
result.className = "result error";
|
|
1332
|
+
result.textContent = error instanceof Error ? error.message : String(error);
|
|
1333
|
+
} finally {
|
|
1334
|
+
button.disabled = false;
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
1337
|
+
</script>
|
|
1338
|
+
</body>
|
|
1339
|
+
</html>`;
|
|
1340
|
+
}
|
|
1341
|
+
async function sdkJsonRequest(fetchImpl, url, init) {
|
|
1342
|
+
const response = await fetchImpl(url, init);
|
|
1343
|
+
const body = await readJsonBody(response);
|
|
1344
|
+
if (!response.ok) {
|
|
1345
|
+
const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
|
|
1346
|
+
throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
|
|
1347
|
+
}
|
|
1348
|
+
return body ?? {};
|
|
1349
|
+
}
|
|
1350
|
+
function sdkJsonHeaders(authToken, projectId) {
|
|
1351
|
+
const headers = {
|
|
1352
|
+
"Content-Type": "application/json",
|
|
1353
|
+
Authorization: `Bearer ${requireSdkAuthToken(authToken)}`,
|
|
1354
|
+
};
|
|
1355
|
+
if (projectId) {
|
|
1356
|
+
headers["X-Tapi-Project"] = projectId;
|
|
1357
|
+
}
|
|
1358
|
+
return headers;
|
|
1359
|
+
}
|
|
1360
|
+
function requireSdkAuthToken(authToken) {
|
|
1361
|
+
const normalized = String(authToken || "").trim();
|
|
1362
|
+
if (!normalized) {
|
|
1363
|
+
throw new Error("Tapi sign-in is required. Run `tapi login`, then retry.");
|
|
1364
|
+
}
|
|
1365
|
+
return normalized;
|
|
1366
|
+
}
|
|
1367
|
+
function coercePositiveInteger(value, fallback, label) {
|
|
1368
|
+
const text = value === undefined || value === null || value === "" ? String(fallback) : String(value);
|
|
1369
|
+
return parsePositiveInteger(text, label);
|
|
1370
|
+
}
|
|
1371
|
+
function parsePositiveInteger(value, label) {
|
|
1372
|
+
const parsed = Number(value);
|
|
1373
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
1374
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1375
|
+
}
|
|
1376
|
+
return parsed;
|
|
1377
|
+
}
|
|
1378
|
+
function parseNonNegativeInteger(value, label) {
|
|
1379
|
+
const parsed = Number(value);
|
|
1380
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
1381
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
549
1382
|
}
|
|
1383
|
+
return parsed;
|
|
550
1384
|
}
|
|
551
1385
|
function parseTriggerSyncOptions(args) {
|
|
552
1386
|
const workspace = loadWorkspace();
|
|
553
1387
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
554
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
555
1388
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
556
1389
|
let configPath = "";
|
|
557
1390
|
const root = workspace?.root || process.cwd();
|
|
@@ -571,14 +1404,6 @@ function parseTriggerSyncOptions(args) {
|
|
|
571
1404
|
apiBaseUrl = arg.slice("--server=".length);
|
|
572
1405
|
continue;
|
|
573
1406
|
}
|
|
574
|
-
if (arg === "--api-key") {
|
|
575
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
if (arg.startsWith("--api-key=")) {
|
|
579
|
-
apiKey = arg.slice("--api-key=".length);
|
|
580
|
-
continue;
|
|
581
|
-
}
|
|
582
1407
|
if (arg === "--project") {
|
|
583
1408
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
584
1409
|
continue;
|
|
@@ -597,29 +1422,26 @@ function parseTriggerSyncOptions(args) {
|
|
|
597
1422
|
}
|
|
598
1423
|
throw new Error(`Unknown triggers option: ${arg}`);
|
|
599
1424
|
}
|
|
600
|
-
if (!apiKey) {
|
|
601
|
-
throw new Error("triggers sync requires --api-key or TAPI_API_KEY.");
|
|
602
|
-
}
|
|
603
1425
|
if (!projectId) {
|
|
604
1426
|
throw new Error("triggers sync requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
605
1427
|
}
|
|
606
1428
|
return {
|
|
607
1429
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
608
|
-
apiKey,
|
|
609
1430
|
projectId,
|
|
610
1431
|
configPath: configPath ? resolve(root, configPath) : defaultTriggerConfigPath(root),
|
|
611
1432
|
};
|
|
612
1433
|
}
|
|
613
|
-
async function
|
|
1434
|
+
async function syncServiceTriggers(args) {
|
|
614
1435
|
try {
|
|
615
|
-
const options = parseTriggerSyncOptions(args);
|
|
616
|
-
const triggers = await
|
|
1436
|
+
const options = await withSdkAuth(parseTriggerSyncOptions(args));
|
|
1437
|
+
const triggers = await readServiceTriggerConfig(options.configPath);
|
|
617
1438
|
if (triggers.length === 0) {
|
|
618
1439
|
throw new Error(`No Tapi service triggers found in ${options.configPath}.`);
|
|
619
1440
|
}
|
|
620
1441
|
const client = new TapiClient({
|
|
621
1442
|
baseUrl: options.apiBaseUrl,
|
|
622
|
-
|
|
1443
|
+
authToken: options.authToken,
|
|
1444
|
+
tappId: options.projectId,
|
|
623
1445
|
projectId: options.projectId,
|
|
624
1446
|
});
|
|
625
1447
|
await withCliSpinner(`Syncing ${triggers.length} Tapi service trigger(s)`, async () => {
|
|
@@ -638,7 +1460,6 @@ async function syncApiTriggers(args) {
|
|
|
638
1460
|
function parseSessionsOptions(args) {
|
|
639
1461
|
const workspace = loadWorkspace();
|
|
640
1462
|
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
641
|
-
let apiKey = envString("TAPI_API_KEY") || "";
|
|
642
1463
|
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
643
1464
|
let site = "";
|
|
644
1465
|
let sessionId = "";
|
|
@@ -660,14 +1481,6 @@ function parseSessionsOptions(args) {
|
|
|
660
1481
|
apiBaseUrl = arg.slice("--server=".length);
|
|
661
1482
|
continue;
|
|
662
1483
|
}
|
|
663
|
-
if (arg === "--api-key") {
|
|
664
|
-
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
665
|
-
continue;
|
|
666
|
-
}
|
|
667
|
-
if (arg.startsWith("--api-key=")) {
|
|
668
|
-
apiKey = arg.slice("--api-key=".length);
|
|
669
|
-
continue;
|
|
670
|
-
}
|
|
671
1484
|
if (arg === "--project") {
|
|
672
1485
|
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
673
1486
|
continue;
|
|
@@ -702,9 +1515,6 @@ function parseSessionsOptions(args) {
|
|
|
702
1515
|
}
|
|
703
1516
|
throw new Error(`Unexpected sessions argument: ${arg}`);
|
|
704
1517
|
}
|
|
705
|
-
if (!apiKey) {
|
|
706
|
-
throw new Error("sessions command requires --api-key or TAPI_API_KEY.");
|
|
707
|
-
}
|
|
708
1518
|
if (!projectId) {
|
|
709
1519
|
throw new Error("sessions command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
710
1520
|
}
|
|
@@ -712,7 +1522,6 @@ function parseSessionsOptions(args) {
|
|
|
712
1522
|
sessionId,
|
|
713
1523
|
options: {
|
|
714
1524
|
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
715
|
-
apiKey,
|
|
716
1525
|
projectId,
|
|
717
1526
|
site: site || undefined,
|
|
718
1527
|
json,
|
|
@@ -723,18 +1532,19 @@ function parseSessionsOptions(args) {
|
|
|
723
1532
|
async function listDevSessions(args) {
|
|
724
1533
|
try {
|
|
725
1534
|
const { options } = parseSessionsOptions(args);
|
|
726
|
-
const
|
|
727
|
-
|
|
1535
|
+
const authenticated = await withSdkAuth(options);
|
|
1536
|
+
const sessions = await fetchDevSessions(authenticated);
|
|
1537
|
+
if (authenticated.json) {
|
|
728
1538
|
console.log(JSON.stringify(sessions, null, 2));
|
|
729
1539
|
return 0;
|
|
730
1540
|
}
|
|
731
1541
|
const rows = flattenDevSessions(sessions);
|
|
732
1542
|
if (rows.length === 0) {
|
|
733
|
-
console.log(`No Tapi dev sessions found for project ${sessions.projectId ||
|
|
1543
|
+
console.log(`No Tapi dev sessions found for project ${sessions.projectId || authenticated.projectId}.`);
|
|
734
1544
|
return 0;
|
|
735
1545
|
}
|
|
736
|
-
if (!
|
|
737
|
-
return await runSessionsPicker(sessions,
|
|
1546
|
+
if (!authenticated.noInteractive && process.stdin.isTTY && process.stdout.isTTY && rows.some((session) => session.openable)) {
|
|
1547
|
+
return await runSessionsPicker(sessions, authenticated);
|
|
738
1548
|
}
|
|
739
1549
|
console.log(renderDevSessions(sessions));
|
|
740
1550
|
const openable = rows.find((session) => session.openable);
|
|
@@ -751,15 +1561,16 @@ async function listDevSessions(args) {
|
|
|
751
1561
|
async function openDevSession(args) {
|
|
752
1562
|
try {
|
|
753
1563
|
const { sessionId, options } = parseSessionsOptions(args);
|
|
1564
|
+
const authenticated = await withSdkAuth(options);
|
|
754
1565
|
if (!sessionId) {
|
|
755
1566
|
throw new Error("sessions open requires a session id.");
|
|
756
1567
|
}
|
|
757
|
-
const sessions = await fetchDevSessions(
|
|
1568
|
+
const sessions = await fetchDevSessions(authenticated);
|
|
758
1569
|
const session = findDevSession(sessions, sessionId);
|
|
759
1570
|
if (!session) {
|
|
760
1571
|
throw new Error(`Tapi session not found: ${sessionId}`);
|
|
761
1572
|
}
|
|
762
|
-
return await openDevSessionInStudio(session,
|
|
1573
|
+
return await openDevSessionInStudio(session, authenticated);
|
|
763
1574
|
}
|
|
764
1575
|
catch (error) {
|
|
765
1576
|
console.error(formatError(error));
|
|
@@ -769,7 +1580,8 @@ async function openDevSession(args) {
|
|
|
769
1580
|
async function fetchDevSessions(options) {
|
|
770
1581
|
const client = new TapiClient({
|
|
771
1582
|
baseUrl: options.apiBaseUrl,
|
|
772
|
-
|
|
1583
|
+
authToken: options.authToken,
|
|
1584
|
+
tappId: options.projectId,
|
|
773
1585
|
projectId: options.projectId,
|
|
774
1586
|
});
|
|
775
1587
|
return await withCliSpinner("Fetching Tapi dev sessions", () => client.sessions.list({ site: options.site }));
|
|
@@ -897,7 +1709,6 @@ async function openDevSessionInStudio(session, options) {
|
|
|
897
1709
|
function studioSessionLaunchParams(session) {
|
|
898
1710
|
const params = new URLSearchParams();
|
|
899
1711
|
params.set("runId", session.id);
|
|
900
|
-
params.set("apiRunId", session.id);
|
|
901
1712
|
if (session.sitemap)
|
|
902
1713
|
params.set("sitemap", session.sitemap);
|
|
903
1714
|
if (session.runtimeSessionId)
|
|
@@ -908,7 +1719,6 @@ function studioSessionSelectIntent(session) {
|
|
|
908
1719
|
return {
|
|
909
1720
|
sessionId: session.id,
|
|
910
1721
|
runId: session.id,
|
|
911
|
-
apiRunId: session.id,
|
|
912
1722
|
...(session.sitemap ? { sitemap: session.sitemap } : {}),
|
|
913
1723
|
...(session.runtimeSessionId ? { runtimeSessionId: session.runtimeSessionId } : {}),
|
|
914
1724
|
source: "cli",
|
|
@@ -961,12 +1771,12 @@ function renderDevSessions(sessions, selectedId = "") {
|
|
|
961
1771
|
const openable = session.openable ? "*" : " ";
|
|
962
1772
|
const id = truncateText(session.id || "", 18).padEnd(18, " ");
|
|
963
1773
|
const status = truncateText(String(session.status || ""), 24).padEnd(24, " ");
|
|
964
|
-
const
|
|
965
|
-
? `${session.
|
|
966
|
-
: session.
|
|
1774
|
+
const serviceRun = session.serviceRun || (session.serviceName && session.serviceKey
|
|
1775
|
+
? `${session.serviceName}.${session.serviceKey}`
|
|
1776
|
+
: session.serviceName || session.serviceKey || "unknown");
|
|
967
1777
|
const state = session.stateLabel ? ` state=${session.stateLabel}` : "";
|
|
968
1778
|
const url = session.currentUrl ? ` ${truncateText(session.currentUrl, 60)}` : "";
|
|
969
|
-
lines.push(`${selected}${openable} ${id} ${status} ${
|
|
1779
|
+
lines.push(`${selected}${openable} ${id} ${status} ${serviceRun}${state}${url}`);
|
|
970
1780
|
}
|
|
971
1781
|
}
|
|
972
1782
|
lines.push("");
|
|
@@ -988,7 +1798,7 @@ function defaultTriggerConfigPath(root) {
|
|
|
988
1798
|
}
|
|
989
1799
|
return join(root, "tapi.config.json");
|
|
990
1800
|
}
|
|
991
|
-
async function
|
|
1801
|
+
async function readServiceTriggerConfig(configPath) {
|
|
992
1802
|
if (!existsSync(configPath)) {
|
|
993
1803
|
throw new Error(`Tapi trigger config not found at ${configPath}. Create tapi.config.json or pass --config.`);
|
|
994
1804
|
}
|
|
@@ -1051,26 +1861,31 @@ function normalizeTriggerEntry(entry, index, configPath) {
|
|
|
1051
1861
|
throw new Error(`Trigger #${index + 1} in ${configPath} must be an object.`);
|
|
1052
1862
|
}
|
|
1053
1863
|
const name = stringField(entry.name, `Trigger #${index + 1} name`);
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
serviceRunFromParts(entry.serviceName ?? entry.apiName, entry.requestKey);
|
|
1864
|
+
if (RETIRED_TRIGGER_TARGET_FIELD in entry) {
|
|
1865
|
+
throw new Error(`Trigger '${name}' uses a retired trigger target field; use serviceRun.`);
|
|
1866
|
+
}
|
|
1867
|
+
const serviceRun = optionalStringField(entry.serviceRun, "serviceRun");
|
|
1059
1868
|
if (!serviceRun) {
|
|
1060
|
-
throw new Error(`Trigger '${name}' requires serviceRun
|
|
1869
|
+
throw new Error(`Trigger '${name}' requires serviceRun.`);
|
|
1061
1870
|
}
|
|
1062
1871
|
if (!serviceRun.includes(".")) {
|
|
1063
|
-
throw new Error(`Trigger '${name}' serviceRun must be formatted as '<serviceName>.<
|
|
1872
|
+
throw new Error(`Trigger '${name}' serviceRun must be formatted as '<serviceName>.<serviceKey>'.`);
|
|
1873
|
+
}
|
|
1874
|
+
if ("runnerId" in entry) {
|
|
1875
|
+
throw new Error(`Trigger '${name}' uses runnerId; use queueId.`);
|
|
1064
1876
|
}
|
|
1065
1877
|
const schedule = normalizeTriggerSchedule(entry.schedule, entry.interval, name);
|
|
1878
|
+
const runtime = entry.runtime === undefined ? undefined : recordField(entry.runtime, `Trigger '${name}' runtime`);
|
|
1879
|
+
rejectNonPolicyTriggerRuntimeOwner(runtime, `Trigger '${name}' runtime`);
|
|
1880
|
+
const queueId = stringField(entry.queueId, `Trigger '${name}' queueId`);
|
|
1066
1881
|
const request = {
|
|
1067
1882
|
name,
|
|
1068
|
-
|
|
1883
|
+
serviceRun,
|
|
1884
|
+
queueId,
|
|
1069
1885
|
...(entry.enabled === undefined ? {} : { enabled: booleanField(entry.enabled, `Trigger '${name}' enabled`) }),
|
|
1070
1886
|
...(schedule ? { schedule } : {}),
|
|
1071
1887
|
...(entry.inputs === undefined ? {} : { inputs: recordField(entry.inputs, `Trigger '${name}' inputs`) }),
|
|
1072
|
-
...(
|
|
1073
|
-
...(entry.runnerId === undefined ? {} : { runnerId: stringField(entry.runnerId, `Trigger '${name}' runnerId`) }),
|
|
1888
|
+
...(runtime === undefined ? {} : { runtime }),
|
|
1074
1889
|
...(entry.priority === undefined ? {} : { priority: numberField(entry.priority, `Trigger '${name}' priority`) }),
|
|
1075
1890
|
...(entry.site === undefined ? {} : { site: stringField(entry.site, `Trigger '${name}' site`) }),
|
|
1076
1891
|
};
|
|
@@ -1153,11 +1968,6 @@ function parseDurationSeconds(value, fieldName) {
|
|
|
1153
1968
|
}
|
|
1154
1969
|
return seconds;
|
|
1155
1970
|
}
|
|
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
1971
|
function stringField(value, fieldName) {
|
|
1162
1972
|
if (typeof value !== "string" || !value.trim()) {
|
|
1163
1973
|
throw new Error(`${fieldName} must be a non-empty string.`);
|
|
@@ -1188,10 +1998,30 @@ function recordField(value, fieldName) {
|
|
|
1188
1998
|
}
|
|
1189
1999
|
return value;
|
|
1190
2000
|
}
|
|
2001
|
+
function rejectNonPolicyTriggerRuntimeOwner(runtime, fieldName) {
|
|
2002
|
+
if (!runtime) {
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
for (const field of RETIRED_RUNTIME_OWNER_ALIAS_FIELDS) {
|
|
2006
|
+
if (field in runtime) {
|
|
2007
|
+
throw new Error(`unsupported service trigger runtime field: ${field}`);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
for (const field of RUNTIME_OWNER_FIELDS) {
|
|
2011
|
+
if (!(field in runtime)) {
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
const owner = String(runtime[field] ?? "").trim().toLowerCase();
|
|
2015
|
+
if (owner && owner !== SERVICE_RUN_RUNTIME_OWNER) {
|
|
2016
|
+
throw new Error(`${fieldName} owner must be service_run.`);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
1191
2020
|
async function fetchApiCatalog(options) {
|
|
1192
2021
|
const client = new TapiClient({
|
|
1193
2022
|
baseUrl: options.apiBaseUrl,
|
|
1194
|
-
|
|
2023
|
+
authToken: options.authToken,
|
|
2024
|
+
tappId: options.projectId,
|
|
1195
2025
|
projectId: options.projectId,
|
|
1196
2026
|
});
|
|
1197
2027
|
return client.catalog.get();
|
|
@@ -1203,50 +2033,6 @@ async function writeTextFile(path, content) {
|
|
|
1203
2033
|
await mkdir(dirname(path), { recursive: true });
|
|
1204
2034
|
await writeFile(path, content, "utf8");
|
|
1205
2035
|
}
|
|
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
2036
|
function isRecord(value) {
|
|
1251
2037
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1252
2038
|
}
|
|
@@ -2994,6 +3780,56 @@ function openBrowser(url) {
|
|
|
2994
3780
|
});
|
|
2995
3781
|
child.unref();
|
|
2996
3782
|
}
|
|
3783
|
+
function openUrlInBrowser(url) {
|
|
3784
|
+
if (process.platform === "win32") {
|
|
3785
|
+
openBrowser(url);
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
const command = process.platform === "darwin" ? "open" : "xdg-open";
|
|
3789
|
+
const child = spawn(command, [url], {
|
|
3790
|
+
detached: true,
|
|
3791
|
+
stdio: "ignore",
|
|
3792
|
+
windowsHide: true,
|
|
3793
|
+
});
|
|
3794
|
+
child.unref();
|
|
3795
|
+
}
|
|
3796
|
+
async function listenOnLocalhost(server, port) {
|
|
3797
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
3798
|
+
const onError = (error) => {
|
|
3799
|
+
server.off("listening", onListening);
|
|
3800
|
+
rejectPromise(error);
|
|
3801
|
+
};
|
|
3802
|
+
const onListening = () => {
|
|
3803
|
+
server.off("error", onError);
|
|
3804
|
+
resolvePromise();
|
|
3805
|
+
};
|
|
3806
|
+
server.once("error", onError);
|
|
3807
|
+
server.once("listening", onListening);
|
|
3808
|
+
server.listen(port, "127.0.0.1");
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
async function closeServer(server) {
|
|
3812
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
3813
|
+
server.close((error) => {
|
|
3814
|
+
if (error) {
|
|
3815
|
+
rejectPromise(error);
|
|
3816
|
+
return;
|
|
3817
|
+
}
|
|
3818
|
+
resolvePromise();
|
|
3819
|
+
});
|
|
3820
|
+
});
|
|
3821
|
+
}
|
|
3822
|
+
async function waitForProcessSignal() {
|
|
3823
|
+
await new Promise((resolvePromise) => {
|
|
3824
|
+
const done = () => {
|
|
3825
|
+
process.off("SIGINT", done);
|
|
3826
|
+
process.off("SIGTERM", done);
|
|
3827
|
+
resolvePromise();
|
|
3828
|
+
};
|
|
3829
|
+
process.once("SIGINT", done);
|
|
3830
|
+
process.once("SIGTERM", done);
|
|
3831
|
+
});
|
|
3832
|
+
}
|
|
2997
3833
|
async function readJsonBody(response) {
|
|
2998
3834
|
const text = await response.text();
|
|
2999
3835
|
if (!text.trim()) {
|
|
@@ -3006,6 +3842,40 @@ async function readJsonBody(response) {
|
|
|
3006
3842
|
return null;
|
|
3007
3843
|
}
|
|
3008
3844
|
}
|
|
3845
|
+
async function readRequestJson(request) {
|
|
3846
|
+
let body = "";
|
|
3847
|
+
for await (const chunk of request) {
|
|
3848
|
+
body += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
3849
|
+
if (Buffer.byteLength(body, "utf8") > 64 * 1024) {
|
|
3850
|
+
throw new Error("request body is too large");
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
if (!body.trim()) {
|
|
3854
|
+
return {};
|
|
3855
|
+
}
|
|
3856
|
+
const parsed = JSON.parse(body);
|
|
3857
|
+
if (!isRecord(parsed)) {
|
|
3858
|
+
throw new Error("request body must be a JSON object");
|
|
3859
|
+
}
|
|
3860
|
+
return parsed;
|
|
3861
|
+
}
|
|
3862
|
+
function sendText(response, status, text, contentType) {
|
|
3863
|
+
response.statusCode = status;
|
|
3864
|
+
response.setHeader("Content-Type", contentType);
|
|
3865
|
+
response.setHeader("Cache-Control", "no-store");
|
|
3866
|
+
response.end(text);
|
|
3867
|
+
}
|
|
3868
|
+
function sendJson(response, status, payload) {
|
|
3869
|
+
sendText(response, status, JSON.stringify(payload), "application/json; charset=utf-8");
|
|
3870
|
+
}
|
|
3871
|
+
function escapeHtml(value) {
|
|
3872
|
+
return value
|
|
3873
|
+
.replace(/&/g, "&")
|
|
3874
|
+
.replace(/</g, "<")
|
|
3875
|
+
.replace(/>/g, ">")
|
|
3876
|
+
.replace(/"/g, """)
|
|
3877
|
+
.replace(/'/g, "'");
|
|
3878
|
+
}
|
|
3009
3879
|
function isApprovalTerminalError(error) {
|
|
3010
3880
|
return error instanceof StudioInstallApprovalError
|
|
3011
3881
|
&& (error.code === "pending_approval" || error.code === "access_pending" || error.code === "access_rejected");
|
|
@@ -3555,52 +4425,119 @@ function readSdkVersion() {
|
|
|
3555
4425
|
function printHelp() {
|
|
3556
4426
|
console.log(`Tapi CLI
|
|
3557
4427
|
|
|
3558
|
-
Usage:
|
|
3559
|
-
tapi
|
|
4428
|
+
Usage:
|
|
4429
|
+
tapi login
|
|
4430
|
+
tapi init --project PROJECT
|
|
3560
4431
|
tapi link --project PROJECT
|
|
3561
4432
|
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
3562
4433
|
tapi studio
|
|
3563
|
-
tapi studio open
|
|
3564
|
-
tapi studio doctor
|
|
3565
|
-
tapi
|
|
4434
|
+
tapi studio open
|
|
4435
|
+
tapi studio doctor
|
|
4436
|
+
tapi tapp create <tapp>
|
|
4437
|
+
tapi queue create [display-name]
|
|
4438
|
+
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY
|
|
4439
|
+
tapi tapp queue add <tapp> <queue-id>
|
|
4440
|
+
tapi runner setup --runner-id RUNNER --project TAPP
|
|
4441
|
+
tapi runner slot set <runner-id> <slot-index> <queue-id>
|
|
4442
|
+
tapi services describe <servicemap.run>
|
|
3566
4443
|
tapi services sync
|
|
3567
|
-
tapi services generate
|
|
3568
4444
|
tapi triggers sync
|
|
3569
4445
|
tapi sessions
|
|
3570
4446
|
tapi service status
|
|
3571
4447
|
tapi doctor
|
|
3572
4448
|
|
|
3573
|
-
Commands:
|
|
3574
|
-
|
|
4449
|
+
Commands:
|
|
4450
|
+
login Sign in with Firebase credentials for SDK commands
|
|
4451
|
+
init Create .tapi/project.json for this repo
|
|
3575
4452
|
link Rebind this repo to an existing Tapi project
|
|
3576
4453
|
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
|
-
|
|
4454
|
+
studio Open Tapi Studio for this repo
|
|
4455
|
+
studio open Open Tapi Studio for this repo
|
|
4456
|
+
studio doctor Check local SDK and Studio release configuration
|
|
4457
|
+
tapp create Create a Tapp
|
|
4458
|
+
queue create Create a queue for service-run routing
|
|
4459
|
+
tapp service add
|
|
4460
|
+
Add a ServiceMap-backed service call to a Tapp
|
|
4461
|
+
tapp queue add Attach a queue to a Tapp
|
|
4462
|
+
runner setup Open the local runner queue/slot setup UI
|
|
4463
|
+
runner slot set Assign a queue to a runner slot
|
|
4464
|
+
services describe
|
|
3581
4465
|
Print a ServiceMap service-run input/output contract
|
|
3582
4466
|
services sync Save the service-run catalog to .tapi/services
|
|
3583
|
-
services generate
|
|
3584
|
-
Write a TypeScript service-run wrapper from the catalog
|
|
3585
4467
|
triggers sync Upsert service triggers from tapi.config
|
|
3586
4468
|
sessions List dev-mode API sessions and open takeover sessions
|
|
3587
4469
|
service Inspect or control the local Tapi Windows service
|
|
3588
4470
|
doctor Alias for studio doctor
|
|
3589
4471
|
`);
|
|
3590
4472
|
}
|
|
4473
|
+
function printLoginHelp() {
|
|
4474
|
+
console.log(`Tapi login
|
|
4475
|
+
|
|
4476
|
+
Usage:
|
|
4477
|
+
tapi login
|
|
4478
|
+
|
|
4479
|
+
Signs in with the browser and caches Firebase credentials for SDK commands.
|
|
4480
|
+
`);
|
|
4481
|
+
}
|
|
4482
|
+
function printTappHelp() {
|
|
4483
|
+
console.log(`Tapi Tapp commands
|
|
4484
|
+
|
|
4485
|
+
Usage:
|
|
4486
|
+
tapi tapp create <tapp> [--name NAME] [--api-base-url URL]
|
|
4487
|
+
tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY [--api-base-url URL]
|
|
4488
|
+
tapi tapp queue add <tapp> <queue-id> [--api-base-url URL]
|
|
4489
|
+
|
|
4490
|
+
Options:
|
|
4491
|
+
--api-base-url <url> Tapi API base URL
|
|
4492
|
+
--server <url> Alias for --api-base-url
|
|
4493
|
+
--tapp-id <id> Explicit Tapp id for tapp create
|
|
4494
|
+
--name <name> Display name for tapp create
|
|
4495
|
+
--service-map <id> Backing ServiceMap id
|
|
4496
|
+
--entry <entry> ServiceMap entry URL/name to launch
|
|
4497
|
+
--expected-revision <r> Optional ServiceMap revision guard
|
|
4498
|
+
`);
|
|
4499
|
+
}
|
|
4500
|
+
function printQueueHelp() {
|
|
4501
|
+
console.log(`Tapi queue commands
|
|
4502
|
+
|
|
4503
|
+
Usage:
|
|
4504
|
+
tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL]
|
|
4505
|
+
|
|
4506
|
+
Options:
|
|
4507
|
+
--api-base-url <url> Tapi API base URL
|
|
4508
|
+
--server <url> Alias for --api-base-url
|
|
4509
|
+
--project <id> Optional project scope for the queue request
|
|
4510
|
+
--max-slots <n> Maximum runner slots this queue may use
|
|
4511
|
+
`);
|
|
4512
|
+
}
|
|
4513
|
+
function printRunnerHelp() {
|
|
4514
|
+
console.log(`Tapi runner commands
|
|
4515
|
+
|
|
4516
|
+
Usage:
|
|
4517
|
+
tapi runner setup --runner-id RUNNER --project TAPP [--port PORT] [--no-open] [--api-base-url URL]
|
|
4518
|
+
tapi runner slot set <runner-id> <slot-index> <queue-id> [--project TAPP] [--api-base-url URL]
|
|
4519
|
+
|
|
4520
|
+
Options:
|
|
4521
|
+
--api-base-url <url> Tapi API base URL
|
|
4522
|
+
--server <url> Alias for --api-base-url
|
|
4523
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4524
|
+
--runner-id <id> Runner id for setup; defaults to TAPI_RUNNER_ID
|
|
4525
|
+
--port <n> Local setup UI port; defaults to 17687
|
|
4526
|
+
--no-open Print the setup URL without opening a browser
|
|
4527
|
+
`);
|
|
4528
|
+
}
|
|
3591
4529
|
function printSessionsHelp() {
|
|
3592
4530
|
console.log(`Tapi dev session commands
|
|
3593
4531
|
|
|
3594
|
-
Usage:
|
|
3595
|
-
tapi sessions [--api-base-url URL] [--
|
|
3596
|
-
tapi sessions list [--json] [--site SITE]
|
|
3597
|
-
tapi sessions open <session-id> [--api-base-url URL] [--
|
|
3598
|
-
|
|
3599
|
-
Options:
|
|
3600
|
-
--api-base-url <url> Tapi API base URL
|
|
3601
|
-
--server <url> Alias for --api-base-url
|
|
3602
|
-
--
|
|
3603
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4532
|
+
Usage:
|
|
4533
|
+
tapi sessions [--api-base-url URL] [--project PROJECT]
|
|
4534
|
+
tapi sessions list [--json] [--site SITE]
|
|
4535
|
+
tapi sessions open <session-id> [--api-base-url URL] [--project PROJECT]
|
|
4536
|
+
|
|
4537
|
+
Options:
|
|
4538
|
+
--api-base-url <url> Tapi API base URL
|
|
4539
|
+
--server <url> Alias for --api-base-url
|
|
4540
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3604
4541
|
--site <name> Limit sessions to one sitemap/site
|
|
3605
4542
|
--json Print raw grouped session JSON
|
|
3606
4543
|
--no-interactive Print the grouped list without the arrow-key picker
|
|
@@ -3609,32 +4546,28 @@ Options:
|
|
|
3609
4546
|
function printServicesHelp() {
|
|
3610
4547
|
console.log(`Tapi ServiceMap service-run commands
|
|
3611
4548
|
|
|
3612
|
-
Usage:
|
|
3613
|
-
tapi services describe <servicemap.run> [--api-base-url URL] [--
|
|
3614
|
-
tapi services sync [--api-base-url URL] [--
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
--
|
|
3619
|
-
--
|
|
3620
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3621
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4549
|
+
Usage:
|
|
4550
|
+
tapi services describe <servicemap.run> [--api-base-url URL] [--project PROJECT]
|
|
4551
|
+
tapi services sync [--api-base-url URL] [--project PROJECT]
|
|
4552
|
+
|
|
4553
|
+
Options:
|
|
4554
|
+
--api-base-url <url> Tapi API base URL
|
|
4555
|
+
--server <url> Alias for --api-base-url
|
|
4556
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3622
4557
|
--catalog <path> Catalog JSON output path
|
|
3623
|
-
--out <path> TypeScript wrapper output path
|
|
3624
4558
|
`);
|
|
3625
4559
|
}
|
|
3626
4560
|
function printTriggersHelp() {
|
|
3627
4561
|
console.log(`Tapi service trigger commands
|
|
3628
4562
|
|
|
3629
|
-
Usage:
|
|
3630
|
-
tapi triggers sync [--config FILE] [--api-base-url URL] [--
|
|
3631
|
-
|
|
3632
|
-
Options:
|
|
3633
|
-
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
3634
|
-
--api-base-url <url> Tapi API base URL
|
|
3635
|
-
--server <url> Alias for --api-base-url
|
|
3636
|
-
--
|
|
3637
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
4563
|
+
Usage:
|
|
4564
|
+
tapi triggers sync [--config FILE] [--api-base-url URL] [--project PROJECT]
|
|
4565
|
+
|
|
4566
|
+
Options:
|
|
4567
|
+
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
4568
|
+
--api-base-url <url> Tapi API base URL
|
|
4569
|
+
--server <url> Alias for --api-base-url
|
|
4570
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3638
4571
|
|
|
3639
4572
|
Config:
|
|
3640
4573
|
export default {
|
|
@@ -3643,7 +4576,7 @@ Config:
|
|
|
3643
4576
|
serviceRun: "schwab.get_balance",
|
|
3644
4577
|
interval: "1h",
|
|
3645
4578
|
inputs: { accountId: "main" },
|
|
3646
|
-
runtime: { profileRef: "perm_default" },
|
|
4579
|
+
runtime: { owner: "service_run", profileRef: "perm_default" },
|
|
3647
4580
|
},
|
|
3648
4581
|
},
|
|
3649
4582
|
};
|
|
@@ -3756,7 +4689,15 @@ function serializeError(error) {
|
|
|
3756
4689
|
}
|
|
3757
4690
|
function isCliEntrypoint() {
|
|
3758
4691
|
const invokedPath = process.argv[1];
|
|
3759
|
-
return Boolean(invokedPath &&
|
|
4692
|
+
return Boolean(invokedPath && realCliPath(invokedPath) === realCliPath(fileURLToPath(import.meta.url)));
|
|
4693
|
+
}
|
|
4694
|
+
function realCliPath(path) {
|
|
4695
|
+
try {
|
|
4696
|
+
return realpathSync(path);
|
|
4697
|
+
}
|
|
4698
|
+
catch {
|
|
4699
|
+
return resolve(path);
|
|
4700
|
+
}
|
|
3760
4701
|
}
|
|
3761
4702
|
if (isCliEntrypoint()) {
|
|
3762
4703
|
runCli().then((code) => {
|