@tapi-dev/sdk 0.1.40 → 0.1.45

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,9 @@ 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";
21
+ import { openRunnerDev } from "./runner-dev.js";
20
22
  const DEFAULT_DOWNLOADS_BASE_URL = "https://d4xaf52nfwiok.cloudfront.net";
21
23
  const DEFAULT_STUDIO_API_BASE_URL = "https://determined-motivation-production.up.railway.app";
22
24
  const DEFAULT_STUDIO_AUTH_HTML_URL = "https://rsarlong-1f92fd.gitlab.io/auth.html";
@@ -166,6 +168,10 @@ async function authenticateSdkUser() {
166
168
  }
167
169
  export async function runCli(argv = process.argv.slice(2)) {
168
170
  const [command, subcommand, ...rest] = argv;
171
+ if (command === "runner" && subcommand === "dev") {
172
+ await openRunnerDev(rest);
173
+ return 0;
174
+ }
169
175
  if (!command || command === "help" || command === "--help" || command === "-h") {
170
176
  printHelp();
171
177
  return 0;
@@ -240,12 +246,6 @@ export async function runCli(argv = process.argv.slice(2)) {
240
246
  return addTappService(rest.slice(1));
241
247
  }
242
248
  }
243
- if (subcommand === "queue") {
244
- const queueCommand = rest[0] || "";
245
- if (queueCommand === "add") {
246
- return addTappQueue(rest.slice(1));
247
- }
248
- }
249
249
  console.error(`Unknown Tapp command: ${[subcommand, ...rest].filter(Boolean).join(" ")}`);
250
250
  printTappHelp();
251
251
  return 1;
@@ -270,6 +270,9 @@ export async function runCli(argv = process.argv.slice(2)) {
270
270
  if (subcommand === "setup") {
271
271
  return runRunnerSetup(rest);
272
272
  }
273
+ if (subcommand === "gui") {
274
+ return openRunnerGui(rest);
275
+ }
273
276
  if (subcommand === "slot") {
274
277
  const slotCommand = rest[0] || "";
275
278
  if (slotCommand === "set") {
@@ -328,6 +331,9 @@ export async function runCli(argv = process.argv.slice(2)) {
328
331
  printStudioHelp();
329
332
  return 0;
330
333
  }
334
+ if (subcommand === "dev") {
335
+ return openStudioDev(rest);
336
+ }
331
337
  const options = parseStudioOptions(rest);
332
338
  if (subcommand === "install") {
333
339
  return installStudio(options);
@@ -344,8 +350,6 @@ export async function runCli(argv = process.argv.slice(2)) {
344
350
  }
345
351
  export function parseStudioOptions(args) {
346
352
  const raw = {};
347
- let workspaceSearchRoot;
348
- let skipWorkspace = false;
349
353
  let projectIdSource;
350
354
  let tappSelectionMode = "auto";
351
355
  for (let index = 0; index < args.length; index += 1) {
@@ -382,16 +386,13 @@ export function parseStudioOptions(args) {
382
386
  continue;
383
387
  }
384
388
  if (arg === "--workspace") {
385
- workspaceSearchRoot = requireOptionValue(args, ++index, "--workspace");
386
- continue;
389
+ throw new Error("--workspace is retired. Use `tapi studio <tapp>` or `--tapp <id>`.");
387
390
  }
388
391
  if (arg.startsWith("--workspace=")) {
389
- workspaceSearchRoot = arg.slice("--workspace=".length);
390
- continue;
392
+ throw new Error("--workspace is retired. Use `tapi studio <tapp>` or `--tapp <id>`.");
391
393
  }
392
394
  if (arg === "--no-workspace") {
393
- skipWorkspace = true;
394
- continue;
395
+ throw new Error("--no-workspace is retired. Studio no longer reads local workspace configs.");
395
396
  }
396
397
  if (arg === "--tapp") {
397
398
  raw.projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--tapp"), "tapp");
@@ -482,13 +483,11 @@ export function parseStudioOptions(args) {
482
483
  }
483
484
  throw new Error(`Unknown option: ${arg}`);
484
485
  }
485
- const workspace = skipWorkspace ? undefined : loadWorkspace(workspaceSearchRoot ?? process.cwd());
486
486
  const channel = raw.channel ?? parseChannel(envString("TAPI_STUDIO_CHANNEL") ?? DEFAULT_CHANNEL);
487
487
  const apiBaseUrl = normalizeHttpUrl(raw.apiBaseUrl
488
488
  ?? envString("TAPI_STUDIO_API_BASE_URL")
489
489
  ?? envString("TAPI_BASE_URL")
490
490
  ?? envString("TAPI_STUDIO_SERVER_URL")
491
- ?? workspace?.apiBaseUrl
492
491
  ?? DEFAULT_STUDIO_API_BASE_URL, "Studio API base URL");
493
492
  const downloadsBaseUrl = normalizeHttpUrl(envString("TAPI_DOWNLOADS_BASE_URL") ?? DEFAULT_DOWNLOADS_BASE_URL, "TAPI_DOWNLOADS_BASE_URL");
494
493
  const explicitManifestUrl = raw.manifestUrlOverride ?? envString("TAPI_STUDIO_MANIFEST_URL");
@@ -496,11 +495,11 @@ export function parseStudioOptions(args) {
496
495
  ? normalizeHttpUrl(explicitManifestUrl, "Studio manifest URL")
497
496
  : `${downloadsBaseUrl.replace(/\/+$/, "")}/studio/channels/${channel}/latest.json`;
498
497
  const envProjectId = envString("TAPI_PROJECT_ID");
499
- const projectId = raw.projectId ?? envProjectId ?? workspace?.projectId;
498
+ const projectId = raw.projectId ?? envProjectId;
500
499
  if (!projectIdSource && projectId) {
501
- projectIdSource = raw.projectId ? "option" : envProjectId ? "env" : "workspace";
500
+ projectIdSource = raw.projectId ? "option" : "env";
502
501
  }
503
- const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG") ?? workspace?.projectSlug;
502
+ const projectSlug = raw.projectSlug ?? envString("TAPI_PROJECT_SLUG");
504
503
  const envInstallToken = envString("TAPI_STUDIO_INSTALL_TOKEN");
505
504
  const installToken = raw.installToken ?? envInstallToken;
506
505
  return {
@@ -515,20 +514,17 @@ export function parseStudioOptions(args) {
515
514
  exePath: raw.exePath ?? envString("TAPI_STUDIO_EXE"),
516
515
  installToken,
517
516
  installTokenSource: raw.installToken ? "option" : installToken ? "env" : undefined,
518
- workspace,
519
- workspaceRoot: workspace?.root,
520
517
  projectId,
521
518
  projectSlug,
522
519
  projectIdSource,
523
- workspaceMode: Boolean(projectId || workspace),
520
+ workspaceMode: Boolean(projectId),
524
521
  tappSelectionMode,
525
522
  };
526
523
  }
527
524
  function parseServiceOptions(args) {
528
525
  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 || "";
526
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
527
+ let projectId = envString("TAPI_PROJECT_ID") || "";
532
528
  for (let index = 0; index < args.length; index += 1) {
533
529
  const arg = args[index];
534
530
  if (!arg)
@@ -566,7 +562,7 @@ function parseServiceOptions(args) {
566
562
  throw new Error("services describe requires a service run like schwab.place_order.");
567
563
  }
568
564
  if (!projectId) {
569
- throw new Error("services describe requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
565
+ throw new Error("services describe requires --project or TAPI_PROJECT_ID.");
570
566
  }
571
567
  return {
572
568
  operation,
@@ -596,10 +592,9 @@ async function describeServiceOperation(args) {
596
592
  }
597
593
  }
598
594
  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";
595
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
596
+ let projectId = envString("TAPI_PROJECT_ID") || "";
597
+ let catalogPath = "tapi-services.catalog.json";
603
598
  for (let index = 0; index < args.length; index += 1) {
604
599
  const arg = args[index];
605
600
  if (!arg)
@@ -635,22 +630,18 @@ function parseApiWorkspaceOptions(args) {
635
630
  throw new Error(`Unknown services option: ${arg}`);
636
631
  }
637
632
  if (!projectId) {
638
- throw new Error("services command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
633
+ throw new Error("services command requires --project or TAPI_PROJECT_ID.");
639
634
  }
640
- const root = workspace?.root || process.cwd();
641
635
  return {
642
636
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
643
637
  projectId,
644
- catalogPath: resolve(root, catalogPath),
638
+ catalogPath: resolve(process.cwd(), catalogPath),
645
639
  };
646
640
  }
647
641
  async function syncServiceCatalog(args) {
648
642
  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;
643
+ void args;
644
+ throw new Error("services sync is retired. Service-run contracts are server-backed; use `tapi services describe <service.call>`.");
654
645
  }
655
646
  catch (error) {
656
647
  console.error(formatError(error));
@@ -658,8 +649,7 @@ async function syncServiceCatalog(args) {
658
649
  }
659
650
  }
660
651
  function parseTappServiceAddOptions(args) {
661
- const workspace = loadWorkspace();
662
- let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
652
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
663
653
  let tappId = "";
664
654
  let serviceCall = "";
665
655
  let serviceMapId = "";
@@ -740,9 +730,8 @@ function parseTappServiceAddOptions(args) {
740
730
  };
741
731
  }
742
732
  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 || "";
733
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
734
+ let tappId = envString("TAPI_PROJECT_ID") || "";
746
735
  let name = "";
747
736
  for (let index = 0; index < args.length; index += 1) {
748
737
  const arg = args[index];
@@ -862,9 +851,7 @@ async function postTappServiceAdd(options) {
862
851
  return body ?? {};
863
852
  }
864
853
  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 || "";
854
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
868
855
  let displayName = "";
869
856
  let maxSlots = 8;
870
857
  for (let index = 0; index < args.length; index += 1) {
@@ -883,14 +870,6 @@ function parseQueueCreateOptions(args) {
883
870
  apiBaseUrl = arg.slice("--server=".length);
884
871
  continue;
885
872
  }
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
873
  if (arg === "--max-slots") {
895
874
  maxSlots = parsePositiveInteger(requireOptionValue(args, ++index, "--max-slots"), "--max-slots");
896
875
  continue;
@@ -910,61 +889,12 @@ function parseQueueCreateOptions(args) {
910
889
  }
911
890
  return {
912
891
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
913
- projectId,
914
892
  displayName,
915
893
  maxSlots,
916
894
  };
917
895
  }
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
896
  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 || "";
897
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
968
898
  let runnerId = "";
969
899
  let slotIndex = 0;
970
900
  let queueId = "";
@@ -984,14 +914,6 @@ function parseRunnerSlotSetOptions(args) {
984
914
  apiBaseUrl = arg.slice("--server=".length);
985
915
  continue;
986
916
  }
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
917
  if (arg.startsWith("--")) {
996
918
  throw new Error(`Unknown runner slot set option: ${arg}`);
997
919
  }
@@ -1009,9 +931,6 @@ function parseRunnerSlotSetOptions(args) {
1009
931
  }
1010
932
  throw new Error(`Unexpected runner slot set argument: ${arg}`);
1011
933
  }
1012
- if (!projectId) {
1013
- throw new Error("runner slot set requires --project or TAPI_PROJECT_ID.");
1014
- }
1015
934
  if (!runnerId) {
1016
935
  throw new Error("runner slot set requires a runner id.");
1017
936
  }
@@ -1023,7 +942,6 @@ function parseRunnerSlotSetOptions(args) {
1023
942
  }
1024
943
  return {
1025
944
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
1026
- projectId,
1027
945
  runnerId,
1028
946
  slotIndex,
1029
947
  queueId,
@@ -1041,18 +959,6 @@ async function createQueue(args) {
1041
959
  return 1;
1042
960
  }
1043
961
  }
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
962
  async function setRunnerSlot(args) {
1057
963
  try {
1058
964
  const options = await withSdkAuth(parseRunnerSlotSetOptions(args));
@@ -1066,7 +972,7 @@ async function setRunnerSlot(args) {
1066
972
  }
1067
973
  }
1068
974
  async function postQueueCreate(options) {
1069
- const headers = sdkJsonHeaders(options.authToken, options.projectId);
975
+ const headers = sdkJsonHeaders(options.authToken);
1070
976
  const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
1071
977
  method: "POST",
1072
978
  headers,
@@ -1082,23 +988,10 @@ async function postQueueCreate(options) {
1082
988
  }
1083
989
  return body ?? {};
1084
990
  }
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
991
  async function putRunnerSlot(options) {
1099
992
  const response = await fetch(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(options.slotIndex))}`, {
1100
993
  method: "PUT",
1101
- headers: sdkJsonHeaders(options.authToken, options.projectId),
994
+ headers: sdkJsonHeaders(options.authToken),
1102
995
  body: JSON.stringify({ queueId: options.queueId }),
1103
996
  });
1104
997
  const body = await readJsonBody(response);
@@ -1109,9 +1002,7 @@ async function putRunnerSlot(options) {
1109
1002
  return body ?? {};
1110
1003
  }
1111
1004
  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 || "";
1005
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1115
1006
  let runnerId = envString("TAPI_RUNNER_ID") || "";
1116
1007
  let port = Number(envString("TAPI_RUNNER_SETUP_PORT") || "17687");
1117
1008
  let openBrowser = true;
@@ -1131,14 +1022,6 @@ export function parseRunnerSetupOptions(args) {
1131
1022
  apiBaseUrl = arg.slice("--server=".length);
1132
1023
  continue;
1133
1024
  }
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
1025
  if (arg === "--runner-id") {
1143
1026
  runnerId = requireOptionValue(args, ++index, "--runner-id").trim();
1144
1027
  continue;
@@ -1168,9 +1051,6 @@ export function parseRunnerSetupOptions(args) {
1168
1051
  }
1169
1052
  throw new Error(`Unexpected runner setup argument: ${arg}`);
1170
1053
  }
1171
- if (!projectId) {
1172
- throw new Error("runner setup requires --project or TAPI_PROJECT_ID.");
1173
- }
1174
1054
  if (!runnerId) {
1175
1055
  throw new Error("runner setup requires --runner-id or TAPI_RUNNER_ID.");
1176
1056
  }
@@ -1179,7 +1059,6 @@ export function parseRunnerSetupOptions(args) {
1179
1059
  }
1180
1060
  return {
1181
1061
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
1182
- projectId,
1183
1062
  runnerId,
1184
1063
  port,
1185
1064
  openBrowser,
@@ -1203,6 +1082,78 @@ async function runRunnerSetup(args) {
1203
1082
  return 1;
1204
1083
  }
1205
1084
  }
1085
+ async function openRunnerGui(args) {
1086
+ try {
1087
+ if (hasHelpFlag(args)) {
1088
+ printRunnerHelp();
1089
+ return 0;
1090
+ }
1091
+ if (args.some((arg) => arg && arg.startsWith("-"))) {
1092
+ throw new Error(`Unknown runner gui option: ${args.find((arg) => arg.startsWith("-"))}`);
1093
+ }
1094
+ const executable = findInstalledRunnerGui();
1095
+ if (!executable) {
1096
+ throw new Error("Tapi Runner GUI is not installed. Run `tapi service install --channel pilot`, then retry.");
1097
+ }
1098
+ const child = spawn(executable, [], {
1099
+ detached: true,
1100
+ stdio: "ignore",
1101
+ windowsHide: false,
1102
+ });
1103
+ child.unref();
1104
+ console.log(`Opened Tapi Runner: ${executable}`);
1105
+ return 0;
1106
+ }
1107
+ catch (error) {
1108
+ console.error(formatError(error));
1109
+ return 1;
1110
+ }
1111
+ }
1112
+ function findInstalledRunnerGui() {
1113
+ const explicit = envString("TAPI_RUNNER_GUI_PATH");
1114
+ if (explicit && existsSync(explicit)) {
1115
+ return explicit;
1116
+ }
1117
+ const candidates = [];
1118
+ const localCandidate = join(getDefaultTapiDataDir(), "Service", "releases");
1119
+ const programData = envString("ProgramData") || "C:\\ProgramData";
1120
+ const roots = [
1121
+ join(programData, "Tapi", "Service", "releases"),
1122
+ localCandidate,
1123
+ resolve(process.cwd(), "build", "v2-processes", "runtime"),
1124
+ ];
1125
+ for (const root of roots) {
1126
+ if (!existsSync(root))
1127
+ continue;
1128
+ const direct = join(root, "tapi-runner-gui.exe");
1129
+ if (existsSync(direct)) {
1130
+ candidates.push(direct);
1131
+ }
1132
+ let entries = [];
1133
+ try {
1134
+ entries = readdirSync(root);
1135
+ }
1136
+ catch {
1137
+ continue;
1138
+ }
1139
+ for (const entry of entries) {
1140
+ const nested = join(root, String(entry), "tapi-runner-gui.exe");
1141
+ if (existsSync(nested)) {
1142
+ candidates.push(nested);
1143
+ }
1144
+ }
1145
+ }
1146
+ const unique = Array.from(new Set(candidates));
1147
+ unique.sort((left, right) => {
1148
+ try {
1149
+ return statSync(right).mtimeMs - statSync(left).mtimeMs;
1150
+ }
1151
+ catch {
1152
+ return 0;
1153
+ }
1154
+ });
1155
+ return unique[0] || "";
1156
+ }
1206
1157
  export async function startRunnerSetupServer(options, fetchImpl = fetch) {
1207
1158
  const server = createServer((request, response) => {
1208
1159
  void handleRunnerSetupRequest(request, response, options, fetchImpl);
@@ -1225,9 +1176,9 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
1225
1176
  if (!queueId) {
1226
1177
  queue = await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/queues`, {
1227
1178
  method: "POST",
1228
- headers: sdkJsonHeaders(options.authToken, options.projectId),
1179
+ headers: sdkJsonHeaders(options.authToken),
1229
1180
  body: JSON.stringify({
1230
- displayName: String(action.displayName || `${options.projectId} queue`).trim(),
1181
+ displayName: String(action.displayName || `${options.runnerId} queue`).trim(),
1231
1182
  maxSlots,
1232
1183
  }),
1233
1184
  });
@@ -1236,24 +1187,18 @@ export async function executeRunnerSetupAction(options, action, fetchImpl = fetc
1236
1187
  if (!queueId) {
1237
1188
  throw new Error("Runner setup needs an existing queue id or a created queue response with queueId.");
1238
1189
  }
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
1190
  const slots = [];
1245
1191
  for (let offset = 0; offset < slotCount; offset += 1) {
1246
1192
  const slotIndex = slotStart + offset;
1247
1193
  slots.push(await sdkJsonRequest(fetchImpl, `${options.apiBaseUrl.replace(/\/+$/, "")}/api/sdk/v1/runners/${encodeURIComponent(options.runnerId)}/slots/${encodeURIComponent(String(slotIndex))}`, {
1248
1194
  method: "PUT",
1249
- headers: sdkJsonHeaders(options.authToken, options.projectId),
1195
+ headers: sdkJsonHeaders(options.authToken),
1250
1196
  body: JSON.stringify({ queueId }),
1251
1197
  }));
1252
1198
  }
1253
1199
  return {
1254
1200
  queueId,
1255
1201
  queue: queueId && !Object.keys(queue).length ? { queueId } : queue,
1256
- attachment,
1257
1202
  slots,
1258
1203
  };
1259
1204
  }
@@ -1267,7 +1212,6 @@ async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
1267
1212
  if (request.method === "GET" && url.pathname === "/api/status") {
1268
1213
  sendJson(response, 200, {
1269
1214
  apiBaseUrl: options.apiBaseUrl,
1270
- projectId: options.projectId,
1271
1215
  runnerId: options.runnerId,
1272
1216
  });
1273
1217
  return;
@@ -1285,7 +1229,7 @@ async function handleRunnerSetupRequest(request, response, options, fetchImpl) {
1285
1229
  }
1286
1230
  }
1287
1231
  function runnerSetupHtml(options) {
1288
- const displayName = `${options.projectId} queue`;
1232
+ const displayName = `${options.runnerId} queue`;
1289
1233
  return `<!doctype html>
1290
1234
  <html lang="en">
1291
1235
  <head>
@@ -1339,7 +1283,6 @@ code { word-break: break-all; }
1339
1283
  <h2>Target</h2>
1340
1284
  <dl>
1341
1285
  <dt>Server</dt><dd><code>${escapeHtml(options.apiBaseUrl)}</code></dd>
1342
- <dt>Tapp</dt><dd><code>${escapeHtml(options.projectId)}</code></dd>
1343
1286
  <dt>Runner</dt><dd><code>${escapeHtml(options.runnerId)}</code></dd>
1344
1287
  </dl>
1345
1288
  </aside>
@@ -1425,11 +1368,10 @@ function parseNonNegativeInteger(value, label) {
1425
1368
  return parsed;
1426
1369
  }
1427
1370
  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 || "";
1371
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1372
+ let projectId = envString("TAPI_PROJECT_ID") || "";
1431
1373
  let configPath = "";
1432
- const root = workspace?.root || process.cwd();
1374
+ const root = process.cwd();
1433
1375
  for (let index = 0; index < args.length; index += 1) {
1434
1376
  const arg = args[index];
1435
1377
  if (!arg)
@@ -1465,7 +1407,7 @@ function parseTriggerSyncOptions(args) {
1465
1407
  throw new Error(`Unknown triggers option: ${arg}`);
1466
1408
  }
1467
1409
  if (!projectId) {
1468
- throw new Error("triggers sync requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
1410
+ throw new Error("triggers sync requires --project or TAPI_PROJECT_ID.");
1469
1411
  }
1470
1412
  return {
1471
1413
  apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
@@ -1500,9 +1442,8 @@ async function syncServiceTriggers(args) {
1500
1442
  }
1501
1443
  }
1502
1444
  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 || "";
1445
+ let apiBaseUrl = envString("TAPI_BASE_URL") || DEFAULT_STUDIO_API_BASE_URL;
1446
+ let projectId = envString("TAPI_PROJECT_ID") || "";
1506
1447
  let site = "";
1507
1448
  let sessionId = "";
1508
1449
  let json = false;
@@ -1558,7 +1499,7 @@ function parseSessionsOptions(args) {
1558
1499
  throw new Error(`Unexpected sessions argument: ${arg}`);
1559
1500
  }
1560
1501
  if (!projectId) {
1561
- throw new Error("sessions command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
1502
+ throw new Error("sessions command requires --project or TAPI_PROJECT_ID.");
1562
1503
  }
1563
1504
  return {
1564
1505
  sessionId,
@@ -3093,7 +3034,6 @@ function normalizeTappList(body) {
3093
3034
  tappId,
3094
3035
  name: typeof item.name === "string" && item.name.trim() ? item.name.trim() : tappId,
3095
3036
  serviceCount: normalizeNonnegativeInteger(item.serviceCount),
3096
- queueCount: normalizeNonnegativeInteger(item.queueCount),
3097
3037
  });
3098
3038
  }
3099
3039
  return tapps;
@@ -3114,7 +3054,6 @@ function preferredStudioTappId(options, lastSelection, tapps) {
3114
3054
  function formatTappChoice(tapp) {
3115
3055
  const details = [
3116
3056
  `${tapp.serviceCount} service${tapp.serviceCount === 1 ? "" : "s"}`,
3117
- `${tapp.queueCount} queue${tapp.queueCount === 1 ? "" : "s"}`,
3118
3057
  ].join(", ");
3119
3058
  return tapp.name === tapp.tappId
3120
3059
  ? `${tapp.tappId} ${pc.dim(`(${details})`)}`
@@ -3317,8 +3256,6 @@ async function launchPortableStudioServer(exePath, options, manifest) {
3317
3256
  apiBaseUrl: options.apiBaseUrl,
3318
3257
  projectId: options.projectId,
3319
3258
  tappId: options.projectId,
3320
- workspaceRoot: options.workspaceRoot,
3321
- workspaceConfig: options.workspace?.configPath,
3322
3259
  manifestVersion: manifest.version,
3323
3260
  updatedAt: new Date().toISOString(),
3324
3261
  });
@@ -3486,7 +3423,6 @@ async function runDoctor(options) {
3486
3423
  console.log(`Studio channel: ${options.channel}`);
3487
3424
  console.log(`Studio manifest: ${options.manifestUrl}`);
3488
3425
  console.log(`Studio cache: ${options.cacheDir}`);
3489
- console.log(`Workspace: ${options.workspaceRoot ?? "not found"}`);
3490
3426
  console.log(`Project: ${options.projectId ?? "not set"}`);
3491
3427
  try {
3492
3428
  const manifest = await withCliSpinner("Fetching latest Tapi Studio manifest", () => fetchStudioManifest(options.manifestUrl));
@@ -3507,12 +3443,6 @@ function buildStudioLaunchEnv(options) {
3507
3443
  const env = { ...process.env };
3508
3444
  env.TAPI_STUDIO_API_BASE_URL = options.apiBaseUrl;
3509
3445
  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
3446
  if (options.projectId) {
3517
3447
  env.TAPI_PROJECT_ID = options.projectId;
3518
3448
  }
@@ -3893,7 +3823,6 @@ export function studioInstanceRecordPathForOptions(options) {
3893
3823
  .update(JSON.stringify({
3894
3824
  apiBaseUrl: comparableHttpUrl(options.apiBaseUrl),
3895
3825
  projectId: options.projectId ?? "",
3896
- workspaceRoot: comparableFsPath(options.workspaceRoot ?? ""),
3897
3826
  }))
3898
3827
  .digest("hex")
3899
3828
  .slice(0, 24);
@@ -3955,8 +3884,6 @@ async function readStudioInstanceRecord(options) {
3955
3884
  apiBaseUrl: typeof parsed.apiBaseUrl === "string" ? parsed.apiBaseUrl : "",
3956
3885
  projectId: typeof parsed.projectId === "string" ? parsed.projectId : undefined,
3957
3886
  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
3887
  manifestVersion: typeof parsed.manifestVersion === "string" ? parsed.manifestVersion : undefined,
3961
3888
  updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
3962
3889
  };
@@ -3977,11 +3904,6 @@ function studioInstanceIdentityMatches(identity, options) {
3977
3904
  if (expectedProject && actualProject && actualProject !== expectedProject) {
3978
3905
  return false;
3979
3906
  }
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
3907
  const expectedApiBaseUrl = comparableHttpUrl(options.apiBaseUrl);
3986
3908
  const actualApiBaseUrl = comparableHttpUrl(typeof identity.apiBaseUrl === "string" ? identity.apiBaseUrl : "");
3987
3909
  if (expectedApiBaseUrl && actualApiBaseUrl && actualApiBaseUrl !== expectedApiBaseUrl) {
@@ -3992,10 +3914,6 @@ function studioInstanceIdentityMatches(identity, options) {
3992
3914
  function comparableHttpUrl(value) {
3993
3915
  return String(value || "").trim().replace(/\/+$/, "").toLowerCase();
3994
3916
  }
3995
- function comparableFsPath(value) {
3996
- const trimmed = String(value || "").trim();
3997
- return trimmed ? resolve(trimmed).toLowerCase() : "";
3998
- }
3999
3917
  function getDefaultTapiDataDir() {
4000
3918
  if (process.platform === "win32") {
4001
3919
  const localAppData = process.env.LOCALAPPDATA
@@ -4506,7 +4424,9 @@ async function isTapiManagedReleaseDir(directory) {
4506
4424
  return Array.isArray(entries) && entries.length === 0;
4507
4425
  }
4508
4426
  function looksLikeTapiServiceReleaseDir(directory) {
4509
- return (existsSync(join(directory, "tapi-service-host.exe"))
4427
+ const hasServiceHost = existsSync(join(directory, "tapi-service-host.exe"))
4428
+ || existsSync(join(directory, "tapi-service-host", "tapi-service-host.exe"));
4429
+ return (hasServiceHost
4510
4430
  && existsSync(join(directory, "install_tapi_service.ps1"))
4511
4431
  && (existsSync(join(directory, "tapi-service-worker.exe"))
4512
4432
  || existsSync(join(directory, "tapi-service-worker", "tapi-service-worker.exe"))));
@@ -4693,10 +4613,9 @@ function printHelp() {
4693
4613
 
4694
4614
  Usage:
4695
4615
  tapi login
4696
- tapi init --project PROJECT
4697
- tapi link --project PROJECT
4698
4616
  tapi studio install [--channel pilot] [--api-base-url URL]
4699
4617
  tapi studio
4618
+ tapi studio dev --tapp <tapp>
4700
4619
  tapi studio --tapp <tapp>
4701
4620
  tapi studio --select
4702
4621
  tapi studio open
@@ -4704,20 +4623,20 @@ Usage:
4704
4623
  tapi tapp create <tapp>
4705
4624
  tapi queue create [display-name]
4706
4625
  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
4626
+ tapi runner gui
4627
+ tapi runner dev --tapp <tapp> [--capacity 4]
4628
+ tapi runner setup --runner-id RUNNER
4709
4629
  tapi runner slot set <runner-id> <slot-index> <queue-id>
4710
4630
  tapi services describe <servicemap.run>
4711
- tapi services sync
4712
- tapi triggers sync
4631
+ tapi triggers sync
4713
4632
  tapi sessions
4714
4633
  tapi service status
4715
4634
  tapi doctor
4716
4635
 
4717
4636
  Commands:
4718
4637
  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
4638
+ init Retired; use tapp create/login/studio instead
4639
+ link Retired; use explicit Tapp commands instead
4721
4640
  studio install Download, verify, and run the Tapi Studio installer
4722
4641
  studio Open Tapi Studio for a selected Tapp
4723
4642
  studio open Open Tapi Studio for a selected Tapp
@@ -4726,12 +4645,13 @@ Commands:
4726
4645
  queue create Create a queue for service-run routing
4727
4646
  tapp service add
4728
4647
  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
4648
+ runner gui Open the installed desktop Runner app
4649
+ runner dev Run a source Runner for a Tapp development session
4650
+ runner setup Open the legacy local browser queue/slot setup UI
4731
4651
  runner slot set Assign a queue to a runner slot
4732
4652
  services describe
4733
4653
  Print a ServiceMap service-run input/output contract
4734
- services sync Save the service-run catalog to .tapi/services
4654
+ services sync Retired; service-run contracts are server-backed
4735
4655
  triggers sync Upsert service triggers from tapi.config
4736
4656
  sessions List dev-mode API sessions and open takeover sessions
4737
4657
  service Inspect or control the local Tapi Windows service
@@ -4753,7 +4673,6 @@ function printTappHelp() {
4753
4673
  Usage:
4754
4674
  tapi tapp create <tapp> [--name NAME] [--api-base-url URL]
4755
4675
  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
4676
 
4758
4677
  Options:
4759
4678
  --api-base-url <url> Tapi API base URL
@@ -4769,12 +4688,11 @@ function printQueueHelp() {
4769
4688
  console.log(`Tapi queue commands
4770
4689
 
4771
4690
  Usage:
4772
- tapi queue create [display-name] [--max-slots N] [--project TAPP] [--api-base-url URL]
4691
+ tapi queue create [display-name] [--max-slots N] [--api-base-url URL]
4773
4692
 
4774
4693
  Options:
4775
4694
  --api-base-url <url> Tapi API base URL
4776
4695
  --server <url> Alias for --api-base-url
4777
- --project <id> Optional project scope for the queue request
4778
4696
  --max-slots <n> Maximum runner slots this queue may use
4779
4697
  `);
4780
4698
  }
@@ -4782,16 +4700,21 @@ function printRunnerHelp() {
4782
4700
  console.log(`Tapi runner commands
4783
4701
 
4784
4702
  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]
4703
+ tapi runner gui
4704
+ tapi runner dev --tapp <tapp> [--capacity COUNT] [--workspace PATH] [--no-watch]
4705
+ tapi runner setup --runner-id RUNNER [--port PORT] [--no-open] [--api-base-url URL]
4706
+ tapi runner slot set <runner-id> <slot-index> <queue-id> [--api-base-url URL]
4787
4707
 
4788
4708
  Options:
4789
4709
  --api-base-url <url> Tapi API base URL
4790
4710
  --server <url> Alias for --api-base-url
4791
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4792
4711
  --runner-id <id> Runner id for setup; defaults to TAPI_RUNNER_ID
4793
4712
  --port <n> Local setup UI port; defaults to 17687
4794
4713
  --no-open Print the setup URL without opening a browser
4714
+
4715
+ Notes:
4716
+ The normal Windows path is the Tapi Runner desktop app. The setup command is
4717
+ kept for development and automation fallback.
4795
4718
  `);
4796
4719
  }
4797
4720
  function printSessionsHelp() {
@@ -4805,7 +4728,7 @@ Usage:
4805
4728
  Options:
4806
4729
  --api-base-url <url> Tapi API base URL
4807
4730
  --server <url> Alias for --api-base-url
4808
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4731
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4809
4732
  --site <name> Limit sessions to one sitemap/site
4810
4733
  --json Print raw grouped session JSON
4811
4734
  --no-interactive Print the grouped list without the arrow-key picker
@@ -4816,13 +4739,12 @@ function printServicesHelp() {
4816
4739
 
4817
4740
  Usage:
4818
4741
  tapi services describe <servicemap.run> [--api-base-url URL] [--project PROJECT]
4819
- tapi services sync [--api-base-url URL] [--project PROJECT]
4742
+ tapi services sync
4820
4743
 
4821
4744
  Options:
4822
4745
  --api-base-url <url> Tapi API base URL
4823
4746
  --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
4747
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4826
4748
  `);
4827
4749
  }
4828
4750
  function printTriggersHelp() {
@@ -4832,10 +4754,10 @@ Usage:
4832
4754
  tapi triggers sync [--config FILE] [--api-base-url URL] [--project PROJECT]
4833
4755
 
4834
4756
  Options:
4835
- --config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
4757
+ --config <path> Trigger config path; defaults to tapi.config.ts/js/json in the current directory
4836
4758
  --api-base-url <url> Tapi API base URL
4837
4759
  --server <url> Alias for --api-base-url
4838
- --project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
4760
+ --project <id> Tapi project id; defaults to TAPI_PROJECT_ID
4839
4761
 
4840
4762
  Config:
4841
4763
  export default {
@@ -4854,8 +4776,9 @@ function printStudioHelp() {
4854
4776
  console.log(`Tapi Studio commands
4855
4777
 
4856
4778
  Usage:
4857
- tapi studio [options]
4858
- tapi studio install [options]
4779
+ tapi studio [options]
4780
+ tapi studio dev --tapp <tapp> [options]
4781
+ tapi studio install [options]
4859
4782
  tapi studio open [options]
4860
4783
  tapi studio doctor [options]
4861
4784
 
@@ -4866,9 +4789,7 @@ Options:
4866
4789
  --tapp <id> Open this Tapp directly
4867
4790
  --select Force the account Tapp picker
4868
4791
  --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
4792
+ --no-select Skip the picker and use explicit/env Tapp context
4872
4793
  --project <id> Alias for --tapp
4873
4794
  --project-slug <slug> Optional display slug for the bound project
4874
4795
  --install-token <tok> Preissued Studio install token (skips browser sign-in)
@@ -4881,18 +4802,16 @@ Options:
4881
4802
  `);
4882
4803
  }
4883
4804
  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
4805
+ console.log(`Tapi workspace ${command} is retired
4806
+
4807
+ Usage:
4808
+ tapi tapp create <tapp>
4809
+ tapi login
4810
+ tapi studio <tapp>
4811
+
4812
+ Options:
4813
+ --api-base-url <url> Tapi API base URL
4814
+ --server <url> Alias for --api-base-url
4896
4815
  `);
4897
4816
  }
4898
4817
  function printServiceHelp() {