@tapi-dev/sdk 0.1.40 → 0.1.44

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/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, realpathSync } from "node:fs";
6
+ import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync, realpathSync, statSync } 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";
@@ -16,7 +16,8 @@ import { select } from "@inquirer/prompts";
16
16
  import { Presets, SingleBar } from "cli-progress";
17
17
  import pc from "yoctocolors";
18
18
  import { TapiClient } from "./index.js";
19
- import { loadWorkspace, normalizeProjectValue, writeWorkspaceConfig, } from "./workspace.js";
19
+ import { normalizeProjectValue, writeWorkspaceConfig, } from "./workspace.js";
20
+ import { openStudioDev } from "./studio-dev.js";
20
21
  const DEFAULT_DOWNLOADS_BASE_URL = "https://d4xaf52nfwiok.cloudfront.net";
21
22
  const DEFAULT_STUDIO_API_BASE_URL = "https://determined-motivation-production.up.railway.app";
22
23
  const DEFAULT_STUDIO_AUTH_HTML_URL = "https://rsarlong-1f92fd.gitlab.io/auth.html";
@@ -240,12 +241,6 @@ export async function runCli(argv = process.argv.slice(2)) {
240
241
  return addTappService(rest.slice(1));
241
242
  }
242
243
  }
243
- if (subcommand === "queue") {
244
- const queueCommand = rest[0] || "";
245
- if (queueCommand === "add") {
246
- return addTappQueue(rest.slice(1));
247
- }
248
- }
249
244
  console.error(`Unknown Tapp command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
250
245
  printTappHelp();
251
246
  return 1;
@@ -270,6 +265,9 @@ export async function runCli(argv = process.argv.slice(2)) {
270
265
  if (subcommand === "setup") {
271
266
  return runRunnerSetup(rest);
272
267
  }
268
+ if (subcommand === "gui") {
269
+ return openRunnerGui(rest);
270
+ }
273
271
  if (subcommand === "slot") {
274
272
  const slotCommand = rest[0] || "";
275
273
  if (slotCommand === "set") {
@@ -328,6 +326,9 @@ export async function runCli(argv = process.argv.slice(2)) {
328
326
  printStudioHelp();
329
327
  return 0;
330
328
  }
329
+ if (subcommand === "dev") {
330
+ return openStudioDev(rest);
331
+ }
331
332
  const options = parseStudioOptions(rest);
332
333
  if (subcommand === "install") {
333
334
  return installStudio(options);
@@ -344,8 +345,6 @@ export async function runCli(argv = process.argv.slice(2)) {
344
345
  }
345
346
  export function parseStudioOptions(args) {
346
347
  const raw = {};
347
- let workspaceSearchRoot;
348
- let skipWorkspace = false;
349
348
  let projectIdSource;
350
349
  let tappSelectionMode = "auto";
351
350
  for (let index = 0; index < args.length; index += 1) {
@@ -382,16 +381,13 @@ export function parseStudioOptions(args) {
382
381
  continue;
383
382
  }
384
383
  if (arg === "--workspace") {
385
- workspaceSearchRoot = requireOptionValue(args, ++index, "--workspace");
386
- continue;
384
+ throw new Error("--workspace is retired. Use `tapi studio <tapp>` or `--tapp <id>`.");
387
385
  }
388
386
  if (arg.startsWith("--workspace=")) {
389
- workspaceSearchRoot = arg.slice("--workspace=".length);
390
- continue;
387
+ throw new Error("--workspace is retired. Use `tapi studio <tapp>` or `--tapp <id>`.");
391
388
  }
392
389
  if (arg === "--no-workspace") {
393
- skipWorkspace = true;
394
- continue;
390
+ throw new Error("--no-workspace is retired. Studio no longer reads local workspace configs.");
395
391
  }
396
392
  if (arg === "--tapp") {
397
393
  raw.projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--tapp"), "tapp");
@@ -482,13 +478,11 @@ export function parseStudioOptions(args) {
482
478
  }
483
479
  throw new Error(`Unknown option: ${arg}`);
484
480
  }
485
- const workspace = skipWorkspace ? undefined : loadWorkspace(workspaceSearchRoot ?? process.cwd());
486
481
  const channel = raw.channel ?? parseChannel(envString("TAPI_STUDIO_CHANNEL") ?? DEFAULT_CHANNEL);
487
482
  const apiBaseUrl = normalizeHttpUrl(raw.apiBaseUrl
488
483
  ?? envString("TAPI_STUDIO_API_BASE_URL")
489
484
  ?? envString("TAPI_BASE_URL")
490
485
  ?? envString("TAPI_STUDIO_SERVER_URL")
491
- ?? workspace?.apiBaseUrl
492
486
  ?? DEFAULT_STUDIO_API_BASE_URL, "Studio API base URL");
493
487
  const downloadsBaseUrl = normalizeHttpUrl(envString("TAPI_DOWNLOADS_BASE_URL") ?? DEFAULT_DOWNLOADS_BASE_URL, "TAPI_DOWNLOADS_BASE_URL");
494
488
  const explicitManifestUrl = raw.manifestUrlOverride ?? envString("TAPI_STUDIO_MANIFEST_URL");
@@ -496,11 +490,11 @@ export function parseStudioOptions(args) {
496
490
  ? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL")
497
491
  : `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
498
492
  const envProjectId = envString("TAPI_PROJECT_ID");
499
- const projectId = raw.projectId ?? envProjectId ?? workspace?.projectId;
493
+ const projectId = raw.projectId ?? envProjectId;
500
494
  if (!projectIdSource && projectId) {
501
- projectIdSource = raw.projectId ? "option" : envProjectId ? "env" : "workspace";
495
+ projectIdSource = raw.projectId ? "option" : "env";
502
496
  }
503
- const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
497
+ const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG");
504
498
  const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
505
499
  const installToken = raw.installToken ?? envInstallToken;
506
500
  return {
@@ -515,20 +509,17 @@ export function parseStudioOptions(args) {
515
509
  exePath: raw.exePath ?? envString("TAPI_STUDIO_EXE"),
516
510
  installToken,
517
511
  installTokenSource: raw.installToken ? "option" : installToken ? "env" : undefined,
518
- workspace,
519
- workspaceRoot: workspace?.root,
520
512
  projectId,
521
513
  projectSlug,
522
514
  projectIdSource,
523
- workspaceMode: Boolean(projectId || workspace),
515
+ workspaceMode: Boolean(projectId),
524
516
  tappSelectionMode,
525
517
  };
526
518
  }
527
519
  function parseServiceOptions(args) {
528
520
  let operation = "";
529
- const workspace = loadWorkspace();
530
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
531
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
521
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
522
+ let projectId = envString("TAPI_PROJECT_ID") || "";
532
523
  for (let index = 0; index < args.length; index += 1) {
533
524
  const arg = args[index];
534
525
  if (!arg)
@@ -566,7 +557,7 @@ function parseServiceOptions(args) {
566
557
  throw new Error("services describe requires a service run like schwab.place_order.");
567
558
  }
568
559
  if (!projectId) {
569
- throw new Error("services describe requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
560
+ throw new Error("services describe requires --project or TAPI_PROJECT_ID.");
570
561
  }
571
562
  return {
572
563
  operation,
@@ -596,10 +587,9 @@ async function describeServiceOperation(args) {
596
587
  }
597
588
  }
598
589
  function parseApiWorkspaceOptions(args) {
599
- const workspace = loadWorkspace();
600
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
601
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
602
- let catalogPath = workspace?.config.services?.catalog || ".tapi/services/catalog.json";
590
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
591
+ let projectId = envString("TAPI_PROJECT_ID") || "";
592
+ let catalogPath = "tapi-services.catalog.json";
603
593
  for (let index = 0; index < args.length; index += 1) {
604
594
  const arg = args[index];
605
595
  if (!arg)
@@ -635,22 +625,18 @@ function parseApiWorkspaceOptions(args) {
635
625
  throw new Error(`Unknown services option: ${arg}`);
636
626
  }
637
627
  if (!projectId) {
638
- throw new Error("services command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
628
+ throw new Error("services command requires --project or TAPI_PROJECT_ID.");
639
629
  }
640
- const root = workspace?.root || process.cwd();
641
630
  return {
642
631
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
643
632
  projectId,
644
- catalogPath: resolve(root, catalogPath),
633
+ catalogPath: resolve(process.cwd(), catalogPath),
645
634
  };
646
635
  }
647
636
  async function syncServiceCatalog(args) {
648
637
  try {
649
- const options = await withSdkAuth(parseApiWorkspaceOptions(args));
650
- const catalog = await withCliSpinner("Fetching Tapi service-run catalog", () => fetchApiCatalog(options));
651
- await writeJsonFile(options.catalogPath, catalog);
652
- console.log(`Synced Tapi service-run catalog: ${options.catalogPath}`);
653
- return 0;
638
+ void args;
639
+ throw new Error("services sync is retired. Service-run contracts are server-backed; use `tapi services describe <service.call>`.");
654
640
  }
655
641
  catch (error) {
656
642
  console.error(formatError(error));
@@ -658,8 +644,7 @@ async function syncServiceCatalog(args) {
658
644
  }
659
645
  }
660
646
  function parseTappServiceAddOptions(args) {
661
- const workspace = loadWorkspace();
662
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
647
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
663
648
  let tappId = "";
664
649
  let serviceCall = "";
665
650
  let serviceMapId = "";
@@ -740,9 +725,8 @@ function parseTappServiceAddOptions(args) {
740
725
  };
741
726
  }
742
727
  function parseTappCreateOptions(args) {
743
- const workspace = loadWorkspace();
744
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
745
- let tappId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
728
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
729
+ let tappId = envString("TAPI_PROJECT_ID") || "";
746
730
  let name = "";
747
731
  for (let index = 0; index < args.length; index += 1) {
748
732
  const arg = args[index];
@@ -862,9 +846,7 @@ async function postTappServiceAdd(options) {
862
846
  return body ?? {};
863
847
  }
864
848
  function parseQueueCreateOptions(args) {
865
- const workspace = loadWorkspace();
866
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
867
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
849
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
868
850
  let displayName = "";
869
851
  let maxSlots = 8;
870
852
  for (let index = 0; index < args.length; index += 1) {
@@ -883,14 +865,6 @@ function parseQueueCreateOptions(args) {
883
865
  apiBaseUrl = arg.slice("--server=".length);
884
866
  continue;
885
867
  }
886
- if (arg === "--project") {
887
- projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
888
- continue;
889
- }
890
- if (arg.startsWith("--project=")) {
891
- projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
892
- continue;
893
- }
894
868
  if (arg === "--max-slots") {
895
869
  maxSlots = parsePositiveInteger(requireOptionValue(args, ++index, "--max-slots"), "--max-slots");
896
870
  continue;
@@ -910,61 +884,12 @@ function parseQueueCreateOptions(args) {
910
884
  }
911
885
  return {
912
886
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
913
- projectId,
914
887
  displayName,
915
888
  maxSlots,
916
889
  };
917
890
  }
918
- function parseTappQueueAddOptions(args) {
919
- const workspace = loadWorkspace();
920
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
921
- let tappId = "";
922
- let queueId = "";
923
- for (let index = 0; index < args.length; index += 1) {
924
- const arg = args[index];
925
- if (!arg)
926
- continue;
927
- if (arg === "--api-base-url" || arg === "--server") {
928
- apiBaseUrl = requireOptionValue(args, ++index, arg);
929
- continue;
930
- }
931
- if (arg.startsWith("--api-base-url=")) {
932
- apiBaseUrl = arg.slice("--api-base-url=".length);
933
- continue;
934
- }
935
- if (arg.startsWith("--server=")) {
936
- apiBaseUrl = arg.slice("--server=".length);
937
- continue;
938
- }
939
- if (arg.startsWith("--")) {
940
- throw new Error(`Unknown tapp queue add option: ${arg}`);
941
- }
942
- if (!tappId) {
943
- tappId = normalizeProjectValue(arg, "tapp");
944
- continue;
945
- }
946
- if (!queueId) {
947
- queueId = arg.trim();
948
- continue;
949
- }
950
- throw new Error(`Unexpected tapp queue add argument: ${arg}`);
951
- }
952
- if (!tappId) {
953
- throw new Error("tapp queue add requires a Tapp id.");
954
- }
955
- if (!queueId) {
956
- throw new Error("tapp queue add requires a queue id.");
957
- }
958
- return {
959
- apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
960
- tappId,
961
- queueId,
962
- };
963
- }
964
891
  function parseRunnerSlotSetOptions(args) {
965
- const workspace = loadWorkspace();
966
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
967
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
892
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
968
893
  let runnerId = "";
969
894
  let slotIndex = 0;
970
895
  let queueId = "";
@@ -984,14 +909,6 @@ function parseRunnerSlotSetOptions(args) {
984
909
  apiBaseUrl = arg.slice("--server=".length);
985
910
  continue;
986
911
  }
987
- if (arg === "--project") {
988
- projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
989
- continue;
990
- }
991
- if (arg.startsWith("--project=")) {
992
- projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
993
- continue;
994
- }
995
912
  if (arg.startsWith("--")) {
996
913
  throw new Error(`Unknown runner slot set option: ${arg}`);
997
914
  }
@@ -1009,9 +926,6 @@ function parseRunnerSlotSetOptions(args) {
1009
926
  }
1010
927
  throw new Error(`Unexpected runner slot set argument: ${arg}`);
1011
928
  }
1012
- if (!projectId) {
1013
- throw new Error("runner slot set requires --project or TAPI_PROJECT_ID.");
1014
- }
1015
929
  if (!runnerId) {
1016
930
  throw new Error("runner slot set requires a runner id.");
1017
931
  }
@@ -1023,7 +937,6 @@ function parseRunnerSlotSetOptions(args) {
1023
937
  }
1024
938
  return {
1025
939
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
1026
- projectId,
1027
940
  runnerId,
1028
941
  slotIndex,
1029
942
  queueId,
@@ -1041,18 +954,6 @@ async function createQueue(args) {
1041
954
  return 1;
1042
955
  }
1043
956
  }
1044
- async function addTappQueue(args) {
1045
- try {
1046
- const options = await withSdkAuth(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
957
  async function setRunnerSlot(args) {
1057
958
  try {
1058
959
  const options = await withSdkAuth(parseRunnerSlotSetOptions(args));
@@ -1066,7 +967,7 @@ async function setRunnerSlot(args) {
1066
967
  }
1067
968
  }
1068
969
  async function postQueueCreate(options) {
1069
- const headers = sdkJsonHeaders(options.authToken, options.projectId);
970
+ const headers = sdkJsonHeaders(options.authToken);
1070
971
  const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
1071
972
  method: "POST",
1072
973
  headers,
@@ -1082,23 +983,10 @@ async function postQueueCreate(options) {
1082
983
  }
1083
984
  return body ?? {};
1084
985
  }
1085
- async function postTappQueueAdd(options) {
1086
- const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.tappId)}/queues`, {
1087
- method: "POST",
1088
- headers: sdkJsonHeaders(options.authToken, options.tappId),
1089
- body: JSON.stringify({ queueId: options.queueId }),
1090
- });
1091
- const body = await readJsonBody(response);
1092
- if (!response.ok) {
1093
- const detail = body?.detail ?? body?.error ?? body ?? `HTTP ${response.status}`;
1094
- throw new Error(typeof detail === "string" ? detail : JSON.stringify(detail));
1095
- }
1096
- return body ?? {};
1097
- }
1098
986
  async function putRunnerSlot(options) {
1099
987
  const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(options.slotIndex))}`, {
1100
988
  method: "PUT",
1101
- headers: sdkJsonHeaders(options.authToken, options.projectId),
989
+ headers: sdkJsonHeaders(options.authToken),
1102
990
  body: JSON.stringify({ queueId: options.queueId }),
1103
991
  });
1104
992
  const body = await readJsonBody(response);
@@ -1109,9 +997,7 @@ async function putRunnerSlot(options) {
1109
997
  return body ?? {};
1110
998
  }
1111
999
  export function parseRunnerSetupOptions(args) {
1112
- const workspace = loadWorkspace();
1113
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
1114
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
1000
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1115
1001
  let runnerId = envString("TAPI_RUNNER_ID") || "";
1116
1002
  let port = Number(envString("TAPI_RUNNER_SETUP_PORT") || "17687");
1117
1003
  let openBrowser = true;
@@ -1131,14 +1017,6 @@ export function parseRunnerSetupOptions(args) {
1131
1017
  apiBaseUrl = arg.slice("--server=".length);
1132
1018
  continue;
1133
1019
  }
1134
- if (arg === "--project") {
1135
- projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
1136
- continue;
1137
- }
1138
- if (arg.startsWith("--project=")) {
1139
- projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
1140
- continue;
1141
- }
1142
1020
  if (arg === "--runner-id") {
1143
1021
  runnerId = requireOptionValue(args, ++index, "--runner-id").trim();
1144
1022
  continue;
@@ -1168,9 +1046,6 @@ export function parseRunnerSetupOptions(args) {
1168
1046
  }
1169
1047
  throw new Error(`Unexpected runner setup argument: ${arg}`);
1170
1048
  }
1171
- if (!projectId) {
1172
- throw new Error("runner setup requires --project or TAPI_PROJECT_ID.");
1173
- }
1174
1049
  if (!runnerId) {
1175
1050
  throw new Error("runner setup requires --runner-id or TAPI_RUNNER_ID.");
1176
1051
  }
@@ -1179,7 +1054,6 @@ export function parseRunnerSetupOptions(args) {
1179
1054
  }
1180
1055
  return {
1181
1056
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
1182
- projectId,
1183
1057
  runnerId,
1184
1058
  port,
1185
1059
  openBrowser,
@@ -1203,6 +1077,78 @@ async function runRunnerSetup(args) {
1203
1077
  return 1;
1204
1078
  }
1205
1079
  }
1080
+ async function openRunnerGui(args) {
1081
+ try {
1082
+ if (hasHelpFlag(args)) {
1083
+ printRunnerHelp();
1084
+ return 0;
1085
+ }
1086
+ if (args.some((arg) => arg && arg.startsWith("-"))) {
1087
+ throw new Error(`Unknown runner gui option: ${args.find((arg) => arg.startsWith("-"))}`);
1088
+ }
1089
+ const executable = findInstalledRunnerGui();
1090
+ if (!executable) {
1091
+ throw new Error("Tapi Runner GUI is not installed. Run `tapi service install --channel pilot`, then retry.");
1092
+ }
1093
+ const child = spawn(executable, [], {
1094
+ detached: true,
1095
+ stdio: "ignore",
1096
+ windowsHide: false,
1097
+ });
1098
+ child.unref();
1099
+ console.log(`Opened Tapi Runner: ${executable}`);
1100
+ return 0;
1101
+ }
1102
+ catch (error) {
1103
+ console.error(formatError(error));
1104
+ return 1;
1105
+ }
1106
+ }
1107
+ function findInstalledRunnerGui() {
1108
+ const explicit = envString("TAPI_RUNNER_GUI_PATH");
1109
+ if (explicit && existsSync(explicit)) {
1110
+ return explicit;
1111
+ }
1112
+ const candidates = [];
1113
+ const localCandidate = join(getDefaultTapiDataDir(), "Service", "releases");
1114
+ const programData = envString("ProgramData") || "C:\\ProgramData";
1115
+ const roots = [
1116
+ join(programData, "Tapi", "Service", "releases"),
1117
+ localCandidate,
1118
+ resolve(process.cwd(), "build", "v2-processes", "runtime"),
1119
+ ];
1120
+ for (const root of roots) {
1121
+ if (!existsSync(root))
1122
+ continue;
1123
+ const direct = join(root, "tapi-runner-gui.exe");
1124
+ if (existsSync(direct)) {
1125
+ candidates.push(direct);
1126
+ }
1127
+ let entries = [];
1128
+ try {
1129
+ entries = readdirSync(root);
1130
+ }
1131
+ catch {
1132
+ continue;
1133
+ }
1134
+ for (const entry of entries) {
1135
+ const nested = join(root, String(entry), "tapi-runner-gui.exe");
1136
+ if (existsSync(nested)) {
1137
+ candidates.push(nested);
1138
+ }
1139
+ }
1140
+ }
1141
+ const unique = Array.from(new Set(candidates));
1142
+ unique.sort((left, right) => {
1143
+ try {
1144
+ return statSync(right).mtimeMs - statSync(left).mtimeMs;
1145
+ }
1146
+ catch {
1147
+ return 0;
1148
+ }
1149
+ });
1150
+ return unique[0] || "";
1151
+ }
1206
1152
  export async function startRunnerSetupServer(options, fetchImpl = fetch) {
1207
1153
  const server = createServer((request, response) => {
1208
1154
  void handleRunnerSetupRequest(request, response, options, fetchImpl);
@@ -1225,9 +1171,9 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
1225
1171
  if (!queueId) {
1226
1172
  queue = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
1227
1173
  method: "POST",
1228
- headers: sdkJsonHeaders(options.authToken, options.projectId),
1174
+ headers: sdkJsonHeaders(options.authToken),
1229
1175
  body: JSON.stringify({
1230
- displayName: String(action.displayName || `${options.projectId} queue`).trim(),
1176
+ displayName: String(action.displayName || `${options.runnerId} queue`).trim(),
1231
1177
  maxSlots,
1232
1178
  }),
1233
1179
  });
@@ -1236,24 +1182,18 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
1236
1182
  if (!queueId) {
1237
1183
  throw new Error("Runner setup needs an existing queue id or a created queue response with queueId.");
1238
1184
  }
1239
- const attachment = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/tapps/${encodeURIComponent(options.projectId)}/queues`, {
1240
- method: "POST",
1241
- headers: sdkJsonHeaders(options.authToken, options.projectId),
1242
- body: JSON.stringify({ queueId }),
1243
- });
1244
1185
  const slots = [];
1245
1186
  for (let offset = 0; offset < slotCount; offset += 1) {
1246
1187
  const slotIndex = slotStart + offset;
1247
1188
  slots.push(await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(slotIndex))}`, {
1248
1189
  method: "PUT",
1249
- headers: sdkJsonHeaders(options.authToken, options.projectId),
1190
+ headers: sdkJsonHeaders(options.authToken),
1250
1191
  body: JSON.stringify({ queueId }),
1251
1192
  }));
1252
1193
  }
1253
1194
  return {
1254
1195
  queueId,
1255
1196
  queue: queueId && !Object.keys(queue).length ? { queueId } : queue,
1256
- attachment,
1257
1197
  slots,
1258
1198
  };
1259
1199
  }
@@ -1267,7 +1207,6 @@ async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
1267
1207
  if (request.method === "GET" && url.pathname === "/api/status") {
1268
1208
  sendJson(response, 200, {
1269
1209
  apiBaseUrl: options.apiBaseUrl,
1270
- projectId: options.projectId,
1271
1210
  runnerId: options.runnerId,
1272
1211
  });
1273
1212
  return;
@@ -1285,7 +1224,7 @@ async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
1285
1224
  }
1286
1225
  }
1287
1226
  function runnerSetupHtml(options) {
1288
- const displayName = `${options.projectId} queue`;
1227
+ const displayName = `${options.runnerId} queue`;
1289
1228
  return `<!doctype html>
1290
1229
  <html lang="en">
1291
1230
  <head>
@@ -1339,7 +1278,6 @@ code { word-break: break-all; }
1339
1278
  <h2>Target</h2>
1340
1279
  <dl>
1341
1280
  <dt>Server</dt><dd><code>${escapeHtml(options.apiBaseUrl)}</code></dd>
1342
- <dt>Tapp</dt><dd><code>${escapeHtml(options.projectId)}</code></dd>
1343
1281
  <dt>Runner</dt><dd><code>${escapeHtml(options.runnerId)}</code></dd>
1344
1282
  </dl>
1345
1283
  </aside>
@@ -1425,11 +1363,10 @@ function parseNonNegativeInteger(value, label) {
1425
1363
  return parsed;
1426
1364
  }
1427
1365
  function parseTriggerSyncOptions(args) {
1428
- const workspace = loadWorkspace();
1429
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
1430
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
1366
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1367
+ let projectId = envString("TAPI_PROJECT_ID") || "";
1431
1368
  let configPath = "";
1432
- const root = workspace?.root || process.cwd();
1369
+ const root = process.cwd();
1433
1370
  for (let index = 0; index < args.length; index += 1) {
1434
1371
  const arg = args[index];
1435
1372
  if (!arg)
@@ -1465,7 +1402,7 @@ function parseTriggerSyncOptions(args) {
1465
1402
  throw new Error(`Unknown triggers option: ${arg}`);
1466
1403
  }
1467
1404
  if (!projectId) {
1468
- throw new Error("triggers sync requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
1405
+ throw new Error("triggers sync requires --project or TAPI_PROJECT_ID.");
1469
1406
  }
1470
1407
  return {
1471
1408
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
@@ -1500,9 +1437,8 @@ async function syncServiceTriggers(args) {
1500
1437
  }
1501
1438
  }
1502
1439
  function parseSessionsOptions(args) {
1503
- const workspace = loadWorkspace();
1504
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
1505
- let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
1440
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1441
+ let projectId = envString("TAPI_PROJECT_ID") || "";
1506
1442
  let site = "";
1507
1443
  let sessionId = "";
1508
1444
  let json = false;
@@ -1558,7 +1494,7 @@ function parseSessionsOptions(args) {
1558
1494
  throw new Error(`Unexpected sessions argument: ${arg}`);
1559
1495
  }
1560
1496
  if (!projectId) {
1561
- throw new Error("sessions command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
1497
+ throw new Error("sessions command requires --project or TAPI_PROJECT_ID.");
1562
1498
  }
1563
1499
  return {
1564
1500
  sessionId,
@@ -3093,7 +3029,6 @@ function normalizeTappList(body) {
3093
3029
  tappId,
3094
3030
  name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : tappId,
3095
3031
  serviceCount: normalizeNonnegativeInteger(item.serviceCount),
3096
- queueCount: normalizeNonnegativeInteger(item.queueCount),
3097
3032
  });
3098
3033
  }
3099
3034
  return tapps;
@@ -3114,7 +3049,6 @@ function preferredStudioTappId(options, lastSelection, tapps) {
3114
3049
  function formatTappChoice(tapp) {
3115
3050
  const details = [
3116
3051
  `${tapp.serviceCount} service${tapp.serviceCount === 1 ? "" : "s"}`,
3117
- `${tapp.queueCount} queue${tapp.queueCount === 1 ? "" : "s"}`,
3118
3052
  ].join(", ");
3119
3053
  return tapp.name === tapp.tappId
3120
3054
  ? `${tapp.tappId} ${pc.dim(`(${details})`)}`
@@ -3317,8 +3251,6 @@ async function launchPortableStudioServer(exePath, options, manifest) {
3317
3251
  apiBaseUrl: options.apiBaseUrl,
3318
3252
  projectId: options.projectId,
3319
3253
  tappId: options.projectId,
3320
- workspaceRoot: options.workspaceRoot,
3321
- workspaceConfig: options.workspace?.configPath,
3322
3254
  manifestVersion: manifest.version,
3323
3255
  updatedAt: new Date().toISOString(),
3324
3256
  });
@@ -3486,7 +3418,6 @@ async function runDoctor(options) {
3486
3418
  console.log(`Studio channel: ${options.channel}`);
3487
3419
  console.log(`Studio manifest: ${options.manifestUrl}`);
3488
3420
  console.log(`Studio cache: ${options.cacheDir}`);
3489
- console.log(`Workspace: ${options.workspaceRoot ?? "not found"}`);
3490
3421
  console.log(`Project: ${options.projectId ?? "not set"}`);
3491
3422
  try {
3492
3423
  const manifest = await withCliSpinner("Fetching latest Tapi Studio manifest", () => fetchStudioManifest(options.manifestUrl));
@@ -3507,12 +3438,6 @@ function buildStudioLaunchEnv(options) {
3507
3438
  const env = { ...process.env };
3508
3439
  env.TAPI_STUDIO_API_BASE_URL = options.apiBaseUrl;
3509
3440
  env.TAPI_BASE_URL = env.TAPI_BASE_URL || options.apiBaseUrl;
3510
- if (options.workspaceRoot) {
3511
- env.TAPI_WORKSPACE_ROOT = options.workspaceRoot;
3512
- }
3513
- if (options.workspace?.configPath) {
3514
- env.TAPI_WORKSPACE_CONFIG = options.workspace.configPath;
3515
- }
3516
3441
  if (options.projectId) {
3517
3442
  env.TAPI_PROJECT_ID = options.projectId;
3518
3443
  }
@@ -3893,7 +3818,6 @@ export function studioInstanceRecordPathForOptions(options) {
3893
3818
  .update(JSON.stringify({
3894
3819
  apiBaseUrl: comparableHttpUrl(options.apiBaseUrl),
3895
3820
  projectId: options.projectId ?? "",
3896
- workspaceRoot: comparableFsPath(options.workspaceRoot ?? ""),
3897
3821
  }))
3898
3822
  .digest("hex")
3899
3823
  .slice(0, 24);
@@ -3955,8 +3879,6 @@ async function readStudioInstanceRecord(options) {
3955
3879
  apiBaseUrl: typeof parsed.apiBaseUrl === "string" ? parsed.apiBaseUrl : "",
3956
3880
  projectId: typeof parsed.projectId === "string" ? parsed.projectId : undefined,
3957
3881
  tappId: typeof parsed.tappId === "string" ? parsed.tappId : undefined,
3958
- workspaceRoot: typeof parsed.workspaceRoot === "string" ? parsed.workspaceRoot : undefined,
3959
- workspaceConfig: typeof parsed.workspaceConfig === "string" ? parsed.workspaceConfig : undefined,
3960
3882
  manifestVersion: typeof parsed.manifestVersion === "string" ? parsed.manifestVersion : undefined,
3961
3883
  updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
3962
3884
  };
@@ -3977,11 +3899,6 @@ function studioInstanceIdentityMatches(identity, options) {
3977
3899
  if (expectedProject && actualProject && actualProject !== expectedProject) {
3978
3900
  return false;
3979
3901
  }
3980
- const expectedWorkspace = comparableFsPath(options.workspaceRoot ?? "");
3981
- const actualWorkspace = comparableFsPath(typeof identity.workspaceRoot === "string" ? identity.workspaceRoot : "");
3982
- if (expectedWorkspace && actualWorkspace && actualWorkspace !== expectedWorkspace) {
3983
- return false;
3984
- }
3985
3902
  const expectedApiBaseUrl = comparableHttpUrl(options.apiBaseUrl);
3986
3903
  const actualApiBaseUrl = comparableHttpUrl(typeof identity.apiBaseUrl === "string" ? identity.apiBaseUrl : "");
3987
3904
  if (expectedApiBaseUrl && actualApiBaseUrl && actualApiBaseUrl !== expectedApiBaseUrl) {
@@ -3992,10 +3909,6 @@ function studioInstanceIdentityMatches(identity, options) {
3992
3909
  function comparableHttpUrl(value) {
3993
3910
  return String(value || "").trim().replace(/\/+$/, "").toLowerCase();
3994
3911
  }
3995
- function comparableFsPath(value) {
3996
- const trimmed = String(value || "").trim();
3997
- return trimmed ? resolve(trimmed).toLowerCase() : "";
3998
- }
3999
3912
  function getDefaultTapiDataDir() {
4000
3913
  if (process.platform === "win32") {
4001
3914
  const localAppData = process.env.LOCALAPPDATA
@@ -4506,7 +4419,9 @@ async function isTapiManagedReleaseDir(directory) {
4506
4419
  return Array.isArray(entries) && entries.length === 0;
4507
4420
  }
4508
4421
  function looksLikeTapiServiceReleaseDir(directory) {
4509
- return (existsSync(join(directory, "tapi-service-host.exe"))
4422
+ const hasServiceHost = existsSync(join(directory, "tapi-service-host.exe"))
4423
+ || existsSync(join(directory, "tapi-service-host", "tapi-service-host.exe"));
4424
+ return (hasServiceHost
4510
4425
  && existsSync(join(directory, "install_tapi_service.ps1"))
4511
4426
  && (existsSync(join(directory, "tapi-service-worker.exe"))
4512
4427
  || existsSync(join(directory, "tapi-service-worker", "tapi-service-worker.exe"))));
@@ -4693,10 +4608,9 @@ function printHelp() {
4693
4608
 
4694
4609
  Usage:
4695
4610
  tapi login
4696
- tapi init --project PROJECT
4697
- tapi link --project PROJECT
4698
4611
  tapi studio install [--channel pilot] [--api-base-url URL]
4699
4612
  tapi studio
4613
+ tapi studio dev --tapp <tapp>
4700
4614
  tapi studio --tapp <tapp>
4701
4615
  tapi studio --select
4702
4616
  tapi studio open
@@ -4704,20 +4618,19 @@ Usage:
4704
4618
  tapi tapp create <tapp>
4705
4619
  tapi queue create [display-name]
4706
4620
  tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY
4707
- tapi tapp queue add <tapp> <queue-id>
4708
- tapi runner setup --runner-id RUNNER --project TAPP
4621
+ tapi runner gui
4622
+ tapi runner setup --runner-id RUNNER
4709
4623
  tapi runner slot set <runner-id> <slot-index> <queue-id>
4710
4624
  tapi services describe <servicemap.run>
4711
- tapi services sync
4712
- tapi triggers sync
4625
+ tapi triggers sync
4713
4626
  tapi sessions
4714
4627
  tapi service status
4715
4628
  tapi doctor
4716
4629
 
4717
4630
  Commands:
4718
4631
  login Sign in with Firebase credentials for SDK commands
4719
- init Create .tapi/project.json for this repo
4720
- link Rebind this repo to an existing Tapi project
4632
+ init Retired; use tapp create/login/studio instead
4633
+ link Retired; use explicit Tapp commands instead
4721
4634
  studio install Download, verify, and run the Tapi Studio installer
4722
4635
  studio Open Tapi Studio for a selected Tapp
4723
4636
  studio open Open Tapi Studio for a selected Tapp
@@ -4726,12 +4639,12 @@ Commands:
4726
4639
  queue create Create a queue for service-run routing
4727
4640
  tapp service add
4728
4641
  Add a ServiceMap-backed service call to a Tapp
4729
- tapp queue add Attach a queue to a Tapp
4730
- runner setup Open the local runner queue/slot setup UI
4642
+ runner gui Open the installed desktop Runner app
4643
+ runner setup Open the legacy local browser queue/slot setup UI
4731
4644
  runner slot set Assign a queue to a runner slot
4732
4645
  services describe
4733
4646
  Print a ServiceMap service-run input/output contract
4734
- services sync Save the service-run catalog to .tapi/services
4647
+ services sync Retired; service-run contracts are server-backed
4735
4648
  triggers sync Upsert service triggers from tapi.config
4736
4649
  sessions List dev-mode API sessions and open takeover sessions
4737
4650
  service Inspect or control the local Tapi Windows service
@@ -4753,7 +4666,6 @@ function printTappHelp() {
4753
4666
  Usage:
4754
4667
  tapi tapp create <tapp> [--name NAME] [--api-base-url URL]
4755
4668
  tapi tapp service add <tapp> <service.call> --service-map ID --entry ENTRY [--api-base-url URL]
4756
- tapi tapp queue add <tapp> <queue-id> [--api-base-url URL]
4757
4669
 
4758
4670
  Options:
4759
4671
  --api-base-url <url> Tapi API base URL
@@ -4769,12 +4681,11 @@ function printQueueHelp() {
4769
4681
  console.log(`Tapi queue commands
4770
4682
 
4771
4683
  Usage:
4772
- tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL]
4684
+ tapi queue create [display-name] [--max-slots N] [--api-base-url URL]
4773
4685
 
4774
4686
  Options:
4775
4687
  --api-base-url <url> Tapi API base URL
4776
4688
  --server <url> Alias for --api-base-url
4777
- --project <id> Optional project scope for the queue request
4778
4689
  --max-slots <n> Maximum runner slots this queue may use
4779
4690
  `);
4780
4691
  }
@@ -4782,16 +4693,20 @@ function printRunnerHelp() {
4782
4693
  console.log(`Tapi runner commands
4783
4694
 
4784
4695
  Usage:
4785
- tapi runner setup --runner-id RUNNER --project TAPP [--port PORT] [--no-open] [--api-base-url URL]
4786
- tapi runner slot set <runner-id> <slot-index> <queue-id> [--project TAPP] [--api-base-url URL]
4696
+ tapi runner gui
4697
+ tapi runner setup --runner-id RUNNER [--port PORT] [--no-open] [--api-base-url URL]
4698
+ tapi runner slot set <runner-id> <slot-index> <queue-id> [--api-base-url URL]
4787
4699
 
4788
4700
  Options:
4789
4701
  --api-base-url <url> Tapi API base URL
4790
4702
  --server <url> Alias for --api-base-url
4791
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4792
4703
  --runner-id <id> Runner id for setup; defaults to TAPI_RUNNER_ID
4793
4704
  --port <n> Local setup UI port; defaults to 17687
4794
4705
  --no-open Print the setup URL without opening a browser
4706
+
4707
+ Notes:
4708
+ The normal Windows path is the Tapi Runner desktop app. The setup command is
4709
+ kept for development and automation fallback.
4795
4710
  `);
4796
4711
  }
4797
4712
  function printSessionsHelp() {
@@ -4805,7 +4720,7 @@ Usage:
4805
4720
  Options:
4806
4721
  --api-base-url <url> Tapi API base URL
4807
4722
  --server <url> Alias for --api-base-url
4808
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4723
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4809
4724
  --site <name> Limit sessions to one sitemap/site
4810
4725
  --json Print raw grouped session JSON
4811
4726
  --no-interactive Print the grouped list without the arrow-key picker
@@ -4816,13 +4731,12 @@ function printServicesHelp() {
4816
4731
 
4817
4732
  Usage:
4818
4733
  tapi services describe <servicemap.run> [--api-base-url URL] [--project PROJECT]
4819
- tapi services sync [--api-base-url URL] [--project PROJECT]
4734
+ tapi services sync
4820
4735
 
4821
4736
  Options:
4822
4737
  --api-base-url <url> Tapi API base URL
4823
4738
  --server <url> Alias for --api-base-url
4824
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4825
- --catalog <path> Catalog JSON output path
4739
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4826
4740
  `);
4827
4741
  }
4828
4742
  function printTriggersHelp() {
@@ -4832,10 +4746,10 @@ Usage:
4832
4746
  tapi triggers sync [--config FILE] [--api-base-url URL] [--project PROJECT]
4833
4747
 
4834
4748
  Options:
4835
- --config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
4749
+ --config <path> Trigger config path; defaults to tapi.config.ts/js/json in the current directory
4836
4750
  --api-base-url <url> Tapi API base URL
4837
4751
  --server <url> Alias for --api-base-url
4838
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4752
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4839
4753
 
4840
4754
  Config:
4841
4755
  export default {
@@ -4854,8 +4768,9 @@ function printStudioHelp() {
4854
4768
  console.log(`Tapi Studio commands
4855
4769
 
4856
4770
  Usage:
4857
- tapi studio [options]
4858
- tapi studio install [options]
4771
+ tapi studio [options]
4772
+ tapi studio dev --tapp <tapp> [options]
4773
+ tapi studio install [options]
4859
4774
  tapi studio open [options]
4860
4775
  tapi studio doctor [options]
4861
4776
 
@@ -4866,9 +4781,7 @@ Options:
4866
4781
  --tapp <id> Open this Tapp directly
4867
4782
  --select Force the account Tapp picker
4868
4783
  --last Reuse the last selected Tapp
4869
- --no-select Skip the picker and use env/workspace/default context
4870
- --workspace <path> Search this directory for .tapi/project.json
4871
- --no-workspace Do not load .tapi/project.json
4784
+ --no-select Skip the picker and use explicit/env Tapp context
4872
4785
  --project <id> Alias for --tapp
4873
4786
  --project-slug <slug> Optional display slug for the bound project
4874
4787
  --install-token <tok> Preissued Studio install token (skips browser sign-in)
@@ -4881,18 +4794,16 @@ Options:
4881
4794
  `);
4882
4795
  }
4883
4796
  function printWorkspaceHelp(command) {
4884
- console.log(`Tapi workspace ${command}
4885
-
4886
- Usage:
4887
- tapi ${command} --project PROJECT [--api-base-url URL] [--root PATH] [--force]
4888
-
4889
- Options:
4890
- --project <id> Tapi project id/name to bind this repo to
4891
- --project-slug <slug> Optional display slug
4892
- --api-base-url <url> Tapi API base URL stored in .tapi/project.json
4893
- --server <url> Alias for --api-base-url
4894
- --root <path> Directory where .tapi/project.json should be written
4895
- --force Overwrite an existing workspace config
4797
+ console.log(`Tapi workspace ${command} is retired
4798
+
4799
+ Usage:
4800
+ tapi tapp create <tapp>
4801
+ tapi login
4802
+ tapi studio <tapp>
4803
+
4804
+ Options:
4805
+ --api-base-url <url> Tapi API base URL
4806
+ --server <url> Alias for --api-base-url
4896
4807
  `);
4897
4808
  }
4898
4809
  function printServiceHelp() {