@kubb/cli 5.6.0 → 5.6.2

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.
Files changed (65) hide show
  1. package/dist/{Telemetry-bGdFBaGt.cjs → Telemetry-CuO35YOL.cjs} +2 -2
  2. package/dist/{Telemetry-bGdFBaGt.cjs.map → Telemetry-CuO35YOL.cjs.map} +1 -1
  3. package/dist/{Telemetry-RBcAjNxj.js → Telemetry-DqT0XYcN.js} +2 -2
  4. package/dist/{Telemetry-RBcAjNxj.js.map → Telemetry-DqT0XYcN.js.map} +1 -1
  5. package/dist/{background-0PMj56FJ.js → background-CEe9CIIk.js} +3 -7
  6. package/dist/background-CEe9CIIk.js.map +1 -0
  7. package/dist/{background-R0T_7Szu.cjs → background-DF0hM01o.cjs} +2 -2
  8. package/dist/background-DF0hM01o.cjs.map +1 -0
  9. package/dist/background-O8hzI5Pg.js +2 -0
  10. package/dist/{commands-BAgGrcvc.js → commands-D4jkosg_.js} +31 -15
  11. package/dist/commands-D4jkosg_.js.map +1 -0
  12. package/dist/{commands-Si-RFAS6.cjs → commands-DRBfcF_2.cjs} +31 -15
  13. package/dist/commands-DRBfcF_2.cjs.map +1 -0
  14. package/dist/{config-ByfJSj3q.cjs → config-B2BlaYOU.cjs} +2 -2
  15. package/dist/{config-ByfJSj3q.cjs.map → config-B2BlaYOU.cjs.map} +1 -1
  16. package/dist/{config-8Xl4yZmw.js → config-CCxOcwWu.js} +3 -3
  17. package/dist/{config-8Xl4yZmw.js.map → config-CCxOcwWu.js.map} +1 -1
  18. package/dist/index.cjs +13 -19
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +13 -19
  21. package/dist/index.js.map +1 -1
  22. package/dist/{output-B6Ondw0g.js → output-CnSY0tCG.js} +12 -22
  23. package/dist/output-CnSY0tCG.js.map +1 -0
  24. package/dist/{output-i3s5c4Xu.cjs → output-gox62nKo.cjs} +11 -21
  25. package/dist/output-gox62nKo.cjs.map +1 -0
  26. package/dist/{run-DQoKTiGk.js → run-BP_Z7A4e.js} +2 -2
  27. package/dist/{run-DQoKTiGk.js.map → run-BP_Z7A4e.js.map} +1 -1
  28. package/dist/{run-D8Wz_17X.cjs → run-BQgoSvvl.cjs} +68 -139
  29. package/dist/run-BQgoSvvl.cjs.map +1 -0
  30. package/dist/{run-Dgph0WYk.js → run-B_0Vp1ZV.js} +73 -132
  31. package/dist/run-B_0Vp1ZV.js.map +1 -0
  32. package/dist/{run-D5AeacqK.js → run-Bcgdg2P-.js} +2 -2
  33. package/dist/{run-D5AeacqK.js.map → run-Bcgdg2P-.js.map} +1 -1
  34. package/dist/{run-CmNDNEjR.js → run-BipBtba_.js} +9 -6
  35. package/dist/run-BipBtba_.js.map +1 -0
  36. package/dist/{run-BkLJ5mXn.cjs → run-BvKVx0qs.cjs} +2 -2
  37. package/dist/{run-BkLJ5mXn.cjs.map → run-BvKVx0qs.cjs.map} +1 -1
  38. package/dist/{run-t8nm7zcT.cjs → run-DaNJBaav.cjs} +9 -6
  39. package/dist/run-DaNJBaav.cjs.map +1 -0
  40. package/dist/{run-CZlkqkDc.js → run-Jx5dFmC2.js} +2 -2
  41. package/dist/{run-CZlkqkDc.js.map → run-Jx5dFmC2.js.map} +1 -1
  42. package/dist/{run-yvuot2N9.cjs → run-pidaXROC.cjs} +2 -2
  43. package/dist/{run-yvuot2N9.cjs.map → run-pidaXROC.cjs.map} +1 -1
  44. package/dist/{run-3I2NPeXG.cjs → run-yHLrxnxW.cjs} +2 -2
  45. package/dist/{run-3I2NPeXG.cjs.map → run-yHLrxnxW.cjs.map} +1 -1
  46. package/dist/{snapshot-D4nKbwkw.cjs → snapshot-BLEmwvP6.cjs} +66 -134
  47. package/dist/snapshot-BLEmwvP6.cjs.map +1 -0
  48. package/dist/{snapshot-CkHXR0Nl.js → snapshot-DVdepLl5.js} +68 -136
  49. package/dist/snapshot-DVdepLl5.js.map +1 -0
  50. package/dist/studioWorker.js +3 -9
  51. package/dist/studioWorker.js.map +1 -1
  52. package/package.json +8 -12
  53. package/dist/background-0PMj56FJ.js.map +0 -1
  54. package/dist/background-BqS01QDb.js +0 -2
  55. package/dist/background-R0T_7Szu.cjs.map +0 -1
  56. package/dist/commands-BAgGrcvc.js.map +0 -1
  57. package/dist/commands-Si-RFAS6.cjs.map +0 -1
  58. package/dist/output-B6Ondw0g.js.map +0 -1
  59. package/dist/output-i3s5c4Xu.cjs.map +0 -1
  60. package/dist/run-CmNDNEjR.js.map +0 -1
  61. package/dist/run-D8Wz_17X.cjs.map +0 -1
  62. package/dist/run-Dgph0WYk.js.map +0 -1
  63. package/dist/run-t8nm7zcT.cjs.map +0 -1
  64. package/dist/snapshot-CkHXR0Nl.js.map +0 -1
  65. package/dist/snapshot-D4nKbwkw.cjs.map +0 -1
@@ -1,8 +1,8 @@
1
1
  const require_agent = require("./agent-DElUuYuy.cjs");
2
2
  const require_errors = require("./errors-CMbMAwyW.cjs");
3
- const require_Telemetry = require("./Telemetry-bGdFBaGt.cjs");
4
- const require_output = require("./output-i3s5c4Xu.cjs");
5
- const require_config = require("./config-ByfJSj3q.cjs");
3
+ const require_Telemetry = require("./Telemetry-CuO35YOL.cjs");
4
+ const require_output = require("./output-gox62nKo.cjs");
5
+ const require_config = require("./config-B2BlaYOU.cjs");
6
6
  const require_constants = require("./constants-CdxpgX3_.cjs");
7
7
  let node_util = require("node:util");
8
8
  let node_crypto = require("node:crypto");
@@ -10,7 +10,6 @@ let node_os = require("node:os");
10
10
  let node_process = require("node:process");
11
11
  node_process = require_agent.__toESM(node_process, 1);
12
12
  let tinyexec = require("tinyexec");
13
- let node_fs_promises = require("node:fs/promises");
14
13
  let node_path = require("node:path");
15
14
  node_path = require_agent.__toESM(node_path, 1);
16
15
  let _kubb_core = require("@kubb/core");
@@ -36,68 +35,18 @@ function openInBrowser(target) {
36
35
  (0, tinyexec.x)(node_process.default.platform === "darwin" ? "open" : "xdg-open", [target]).then(ignore, ignore);
37
36
  }
38
37
  //#endregion
39
- //#region ../../internals/utils/src/fs.ts
40
- /**
41
- * Resolves to `true` when the file or directory at `path` exists.
42
- * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
43
- *
44
- * @example
45
- * ```ts
46
- * if (await exists('./kubb.config.ts')) {
47
- * const content = await read('./kubb.config.ts')
48
- * }
49
- * ```
50
- */
51
- async function exists(path) {
52
- if (require_Telemetry.runtime.isBun) return Bun.file(path).exists();
53
- return (0, node_fs_promises.access)(path).then(() => true, () => false);
54
- }
55
- /**
56
- * Reads the file at `path` as a UTF-8 string.
57
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
58
- *
59
- * @example
60
- * ```ts
61
- * const source = await read('./src/Pet.ts')
62
- * ```
63
- */
64
- async function read(path) {
65
- if (require_Telemetry.runtime.isBun) return Bun.file(path).text();
66
- return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
67
- }
68
- /**
69
- * Recursively removes `path`. Silently succeeds when `path` does not exist.
70
- *
71
- * @example
72
- * ```ts
73
- * await clean('./dist')
74
- * ```
75
- */
76
- async function clean(path) {
77
- return (0, node_fs_promises.rm)(path, {
78
- recursive: true,
79
- force: true
80
- });
81
- }
82
- //#endregion
83
38
  //#region src/runners/studio/credentials.ts
39
+ const projectBase = (project = node_process.default.cwd()) => node_path.default.join("projects", (0, node_crypto.hash)("sha256", project));
84
40
  /**
85
- * Root for everything the CLI persists between runs: the paired credential, the machine secret,
86
- * the last Studio config, and the live-session registry. `KUBB_HOME` relocates all of it.
87
- */
88
- function getKubbHome() {
89
- return node_process.default.env.KUBB_HOME ?? node_path.default.join((0, node_os.homedir)(), ".kubb");
90
- }
91
- /**
92
- * Root for the credential and machine identity of the current project.
41
+ * Root for the credential and machine identity of the current project, inside `KUBB_HOME`.
93
42
  *
94
43
  * Studio agents are project connections, so two projects on one machine must not share the same
95
44
  * persisted token or machine secret.
96
45
  */
97
46
  function getProjectKubbHome(project = node_process.default.cwd()) {
98
- const projectId = (0, node_crypto.hash)("sha256", project);
99
- return node_path.default.join(getKubbHome(), "projects", projectId);
47
+ return (0, _kubb_core.resolveKubbDir)(projectBase(project));
100
48
  }
49
+ const projectStorage = () => (0, _kubb_core.kubbStorage)({ base: projectBase() });
101
50
  const CREDENTIALS_FILENAME = "credentials.json";
102
51
  /**
103
52
  * Absolute path of the stored credential, so callers can name it in output without rebuilding it.
@@ -111,32 +60,33 @@ function getCredentialsPath() {
111
60
  * overwrite it rather than the CLI dying on it.
112
61
  */
113
62
  async function readCredentials() {
114
- const raw = await read(getCredentialsPath()).catch(() => null);
115
- if (!raw) return null;
116
- try {
117
- return JSON.parse(raw);
118
- } catch {
119
- return null;
120
- }
63
+ const stored = await projectStorage().getItem(CREDENTIALS_FILENAME).catch(() => null);
64
+ return typeof stored?.token === "string" && stored.token !== "" ? stored : null;
121
65
  }
122
66
  /**
123
- * Writes the credential with owner-only permissions. The file holds a bearer token, so the mode
124
- * is set at creation time rather than left to the process umask or fixed up afterward, which
125
- * would leave the token briefly readable under the umask's default mode.
67
+ * Writes the credential into the project folder, which only its owner can open.
126
68
  */
127
69
  async function writeCredentials(credentials) {
128
- const file = getCredentialsPath();
129
- await (0, node_fs_promises.mkdir)(node_path.default.dirname(file), {
130
- recursive: true,
131
- mode: 448
132
- });
133
- await (0, node_fs_promises.writeFile)(file, JSON.stringify(credentials, null, 2), { mode: 384 });
70
+ await projectStorage().setItem(CREDENTIALS_FILENAME, credentials);
134
71
  }
135
72
  /**
136
73
  * Forgets the stored credential. Succeeds when there was nothing to forget.
137
74
  */
138
75
  async function clearCredentials() {
139
- await clean(getCredentialsPath());
76
+ await projectStorage().removeItem(CREDENTIALS_FILENAME);
77
+ }
78
+ /**
79
+ * Reads this project's machine secret, creating it on first use. Pairing sends its hash, so pairing
80
+ * the same project again rotates one agent's token instead of creating a second agent. It lives
81
+ * apart from the credential, which {@link clearCredentials} deletes.
82
+ */
83
+ async function getMachineId() {
84
+ const storage = projectStorage();
85
+ const stored = await storage.getItem("machine-secret").catch(() => null);
86
+ if (typeof stored === "string" && stored.trim()) return stored.trim();
87
+ const secret = (0, node_crypto.randomBytes)(32).toString("hex");
88
+ await storage.setItem("machine-secret", secret);
89
+ return secret;
140
90
  }
141
91
  //#endregion
142
92
  //#region src/runners/studio/run.ts
@@ -171,6 +121,7 @@ async function login({ studioUrl, autoOpen }, { signal, previousCredentials } =
171
121
  type: "cli",
172
122
  name: node_path.default.basename(node_process.default.cwd()),
173
123
  hostname: (0, node_os.hostname)(),
124
+ machineId: await getMachineId(),
174
125
  signal,
175
126
  onCode: (session) => {
176
127
  console.log(`\nOpen ${(0, node_util.styleText)("cyan", session.verification_uri)} and approve the code ${(0, node_util.styleText)("bold", session.user_code)}`);
@@ -328,68 +279,59 @@ async function connect(options, context = {}) {
328
279
  events.once("SIGINT", stop);
329
280
  events.once("SIGTERM", stop);
330
281
  }
331
- let hinted = false;
332
- let reauthenticated = false;
333
282
  try {
334
283
  if (!context.prepared) {
335
- const { getWorkerStatus } = await Promise.resolve().then(() => require("./background-R0T_7Szu.cjs"));
284
+ const { getWorkerStatus } = await Promise.resolve().then(() => require("./background-DF0hM01o.cjs"));
336
285
  if ((await getWorkerStatus()).running) throw new Error("This project is running in the background. Run `kubb studio stop` first.");
337
286
  }
338
287
  const prepared = context.prepared ?? await prepareConnection(options, signal);
339
288
  let { credentials, permissions } = prepared;
340
289
  const { configPath } = prepared;
341
- await (0, _kubb_studio.runConnection)({
342
- credentials,
343
- signal,
344
- clientOptions: () => ({
290
+ let hinted = false;
291
+ let pairedAgain = false;
292
+ const hooks = new _kubb_core.Hookable();
293
+ require_config.setupReporters(hooks, {
294
+ logLevel: _kubb_core.logLevel[options.logLevel ?? "info"],
295
+ reporters: [_kubb_core.cliReporter],
296
+ ...context.onState ? { logger: require_output.plainLogger } : {}
297
+ });
298
+ hooks.hook("studio:connected", async () => {
299
+ pairedAgain = false;
300
+ await context.onState?.("connected");
301
+ if (hinted || context.onState || options.logLevel === "silent") return;
302
+ hinted = true;
303
+ require_output.logBlock((0, node_util.styleText)("dim", "Press Ctrl+C to disconnect"));
304
+ require_output.logTip();
305
+ });
306
+ hooks.hook("studio:reconnecting", () => context.onState?.("reconnecting"));
307
+ for (;;) try {
308
+ await (0, _kubb_studio.connectAgent)({
309
+ token: credentials.token,
345
310
  studioUrl: options.studioUrl,
346
311
  configPath,
347
312
  version: options.version,
348
313
  root: node_process.default.cwd(),
349
314
  permissions,
350
315
  loadConfig: async () => (await loadConfigs(options)).config,
351
- installLogger: async (hooks) => {
352
- require_config.setupReporters(hooks, {
353
- logLevel: _kubb_core.logLevel[options.logLevel ?? "info"],
354
- reporters: [_kubb_core.cliReporter],
355
- ...context.onState ? { logger: require_output.plainLogger } : {}
356
- });
357
- hooks.hook("studio:connected", () => {
358
- if (!hinted && !context.onState && options.logLevel !== "silent") require_output.logBlock((0, node_util.styleText)("dim", "Press Ctrl+C to disconnect"));
359
- hinted = true;
360
- });
361
- hooks.hook("studio:ready", async () => {
362
- await context.onState?.("connected");
363
- if (!context.onState) require_output.logTip();
364
- });
365
- hooks.hook("studio:reconnecting", () => context.onState?.("reconnecting"));
366
- }
367
- }),
368
- onTokenRejected: async ({ error, live }) => {
369
- await context.onState?.("authentication required");
370
- if (node_process.default.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, "envToken");
371
- if (!live) await clearCredentials();
372
- if (reauthenticated) throw explainRejectedToken(error, "reauthExhausted");
373
- if (context.onState || require_agent.isCIEnvironment() || !require_output.canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
374
- console.log((0, node_util.styleText)("yellow", live ? `${error.message} Studio needs you to approve access again.` : `${error.message} Pairing again...`));
375
- if (live) await clearCredentials();
376
- try {
377
- credentials = await login(options, {
378
- signal,
379
- previousCredentials: credentials
380
- });
381
- } catch (error) {
382
- if (error instanceof _kubb_studio.PairingCanceledError) {
383
- if (live && options.logLevel !== "silent") require_output.logOutro("Disconnected");
384
- return null;
385
- }
386
- throw error;
387
- }
388
- reauthenticated = true;
389
- permissions = await resolvePermissions(options, credentials, configPath, !node_process.default.env.KUBB_AGENT_TOKEN);
390
- return credentials;
391
- }
392
- });
316
+ hooks,
317
+ signal
318
+ });
319
+ break;
320
+ } catch (error) {
321
+ if (!(error instanceof _kubb_studio.InvalidAgentTokenError)) throw error;
322
+ await context.onState?.("authentication required");
323
+ if (node_process.default.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, "envToken");
324
+ await clearCredentials();
325
+ if (pairedAgain) throw explainRejectedToken(error, "reauthExhausted");
326
+ if (context.onState || require_agent.isCIEnvironment() || !require_output.canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
327
+ console.log((0, node_util.styleText)("yellow", `${error.message} Pairing again...`));
328
+ credentials = await login(options, {
329
+ signal,
330
+ previousCredentials: credentials
331
+ });
332
+ pairedAgain = true;
333
+ permissions = await resolvePermissions(options, credentials, configPath);
334
+ }
393
335
  if (!context.onState && signal.aborted && options.logLevel !== "silent") require_output.logOutro("Disconnected");
394
336
  } catch (error) {
395
337
  if (!(error instanceof _kubb_studio.PairingCanceledError)) throw error;
@@ -401,8 +343,7 @@ async function connect(options, context = {}) {
401
343
  /**
402
344
  * Runs a Studio command with shared setup and telemetry reporting.
403
345
  */
404
- async function run(options, action, { block = false, json = false } = {}) {
405
- (0, _kubb_studio.setStorage)((0, _kubb_studio.createFileStorage)(getProjectKubbHome()));
346
+ async function run({ options, action, block = false, json = false }) {
406
347
  const report = require_Telemetry.trackRun({
407
348
  command: "studio",
408
349
  hrStart: node_process.default.hrtime()
@@ -439,12 +380,6 @@ Object.defineProperty(exports, "createStudioOptions", {
439
380
  return createStudioOptions;
440
381
  }
441
382
  });
442
- Object.defineProperty(exports, "exists", {
443
- enumerable: true,
444
- get: function() {
445
- return exists;
446
- }
447
- });
448
383
  Object.defineProperty(exports, "formatPermissionRows", {
449
384
  enumerable: true,
450
385
  get: function() {
@@ -475,12 +410,6 @@ Object.defineProperty(exports, "prepareConnection", {
475
410
  return prepareConnection;
476
411
  }
477
412
  });
478
- Object.defineProperty(exports, "read", {
479
- enumerable: true,
480
- get: function() {
481
- return read;
482
- }
483
- });
484
413
  Object.defineProperty(exports, "readCredentials", {
485
414
  enumerable: true,
486
415
  get: function() {
@@ -494,4 +423,4 @@ Object.defineProperty(exports, "run", {
494
423
  }
495
424
  });
496
425
 
497
- //# sourceMappingURL=run-D8Wz_17X.cjs.map
426
+ //# sourceMappingURL=run-BQgoSvvl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-BQgoSvvl.cjs","names":["isCIEnvironment","process","process","path","hash","resolveKubbDir","kubbStorage","randomBytes","defaultStudioUrl","createSpinner","pairAgent","path","process","hostname","styleText","PairingCanceledError","KUBB_CONFIG_FILENAME","isCIEnvironment","canUseTTY","prompts","getConfigs","Hookable","logLevelMap","cliReporter","plainLogger","connectAgent","InvalidAgentTokenError","trackRun","toError"],"sources":["../../../internals/utils/src/openInBrowser.ts","../src/runners/studio/credentials.ts","../src/runners/studio/run.ts"],"sourcesContent":["import process from 'node:process'\nimport { x } from 'tinyexec'\nimport { isCIEnvironment } from './isCIEnvironment.ts'\n\nconst ignore = () => {}\n\n/**\n * Opens a URL or local file in the default browser, unless running in CI.\n */\nexport function openInBrowser(target: string): void {\n if (isCIEnvironment()) return\n\n if (process.platform === 'win32') {\n x('cmd', ['/c', 'start', '', target]).then(ignore, ignore)\n return\n }\n x(process.platform === 'darwin' ? 'open' : 'xdg-open', [target]).then(ignore, ignore)\n}\n","import { hash, randomBytes } from 'node:crypto'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { kubbStorage, resolveKubbDir } from '@kubb/core'\n\nconst projectBase = (project = process.cwd()) => path.join('projects', hash('sha256', project))\n\n/**\n * Root for the credential and machine identity of the current project, inside `KUBB_HOME`.\n *\n * Studio agents are project connections, so two projects on one machine must not share the same\n * persisted token or machine secret.\n */\nexport function getProjectKubbHome(project = process.cwd()): string {\n return resolveKubbDir(projectBase(project))\n}\n\nconst projectStorage = () => kubbStorage({ base: projectBase() })\n\nconst CREDENTIALS_FILENAME = 'credentials.json'\n\n/**\n * Absolute path of the stored credential, so callers can name it in output without rebuilding it.\n */\nexport function getCredentialsPath(): string {\n return path.join(getProjectKubbHome(), CREDENTIALS_FILENAME)\n}\n\nexport type Credentials = {\n /**\n * The Studio instance this machine paired with. A different `--url` means re-pairing.\n */\n studioUrl: string\n /**\n * Bearer token for this machine. Never logged, never printed.\n */\n token: string\n agentId: string\n agentSlug: string\n /**\n * Permissions the user has already granted, keyed by absolute project path, so the CLI asks\n * once per project instead of on every run.\n */\n projects?: Record<string, { allowRead?: boolean; allowWrite?: boolean; allowConfigEdit?: boolean; allowExec?: boolean }>\n}\n\n/**\n * Reads the stored credential, or null when this machine has not paired yet or the file is\n * unreadable or corrupt. A bad file is treated as \"not paired\" so `kubb studio login` can\n * overwrite it rather than the CLI dying on it.\n */\nexport async function readCredentials(): Promise<Credentials | null> {\n const stored = await projectStorage()\n .getItem<Credentials>(CREDENTIALS_FILENAME)\n .catch(() => null)\n\n return typeof stored?.token === 'string' && stored.token !== '' ? stored : null\n}\n\n/**\n * Writes the credential into the project folder, which only its owner can open.\n */\nexport async function writeCredentials(credentials: Credentials): Promise<void> {\n await projectStorage().setItem(CREDENTIALS_FILENAME, credentials)\n}\n\n/**\n * Forgets the stored credential. Succeeds when there was nothing to forget.\n */\nexport async function clearCredentials(): Promise<void> {\n await projectStorage().removeItem(CREDENTIALS_FILENAME)\n}\n\n/**\n * Reads this project's machine secret, creating it on first use. Pairing sends its hash, so pairing\n * the same project again rotates one agent's token instead of creating a second agent. It lives\n * apart from the credential, which {@link clearCredentials} deletes.\n */\nexport async function getMachineId(): Promise<string> {\n const storage = projectStorage()\n const stored = await storage.getItem('machine-secret').catch(() => null)\n if (typeof stored === 'string' && stored.trim()) return stored.trim()\n\n const secret = randomBytes(32).toString('hex')\n await storage.setItem('machine-secret', secret)\n\n return secret\n}\n","import { hostname } from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { styleText } from 'node:util'\nimport * as prompts from '@clack/prompts'\nimport { KUBB_CONFIG_FILENAME } from '@internals/shared'\nimport { isCIEnvironment, openInBrowser, toError } from '@internals/utils'\nimport type { CLIOptions, Config } from '@kubb/core'\nimport { cliReporter, Hookable, type KubbHooks, logLevel as logLevelMap } from '@kubb/core'\nimport { connectAgent, defaultStudioUrl, InvalidAgentTokenError, pairAgent, PairingCanceledError } from '@kubb/studio'\nimport { trackRun } from '../../Telemetry.ts'\nimport { plainLogger } from '../../loggers/plainLogger.ts'\nimport setupReporters from '../../loggers/reporters.ts'\nimport { createSpinner, logBlock, logIntro, logOutro, logTip } from '../../loggers/output.ts'\nimport { canUseTTY } from '../../utils/env.ts'\nimport { getConfigs } from '../../config.ts'\nimport { clearCredentials, type Credentials, getCredentialsPath, getMachineId, readCredentials, writeCredentials } from './credentials.ts'\nimport { version } from '../../../package.json'\n\ntype Permission = 'allowRead' | 'allowWrite' | 'allowConfigEdit' | 'allowExec'\n\nexport type StudioOptions = {\n /**\n * Current `@kubb/cli` version, reported to Studio and used for the telemetry payload.\n */\n version: string\n configPath?: string\n /**\n * Base URL of the Studio instance, for a self-hosted deployment. Resolved before it reaches here,\n * since stored credentials are bound to it.\n */\n studioUrl: string\n /**\n * What Studio may do in this project. Each flag grants outright; the rest are asked once per\n * project through {@link resolvePermissions}.\n */\n permission: Record<Permission, boolean>\n /**\n * Whether to open the approval page in a browser during pairing.\n */\n autoOpen: boolean\n logLevel?: CLIOptions['logLevel']\n}\n\nexport type SnapshotOptions = Pick<StudioOptions, 'configPath' | 'studioUrl' | 'logLevel'> & {\n /**\n * Organization CI API key. Falls back to `KUBB_TOKEN`.\n */\n token?: string\n /**\n * Stable identity for the CI agent. Falls back to CI auto-detection.\n */\n id?: string\n /** Identity of another CI agent to also compare with. Falls back to CI auto-detection. */\n baseId?: string\n /**\n * Package name for the generated tarball. Falls back to the nearest package.json.\n */\n name?: string\n /**\n * Package version for the generated tarball. Falls back to the nearest package.json.\n */\n packageVersion?: string\n /**\n * Print the result as one JSON object instead of a summary.\n */\n json?: boolean\n}\n\ntype StudioValues = {\n config?: string\n url?: string\n allowRead: boolean\n allowWrite: boolean\n allowConfigEdit: boolean\n allowExec: boolean\n open?: boolean\n logLevel?: CLIOptions['logLevel']\n}\n\nexport function createStudioOptions(values: StudioValues): StudioOptions {\n return {\n version,\n configPath: values.config,\n studioUrl: values.url ?? defaultStudioUrl,\n permission: {\n allowRead: values.allowRead,\n allowWrite: values.allowWrite,\n allowConfigEdit: values.allowConfigEdit,\n allowExec: values.allowExec,\n },\n autoOpen: values.open ?? true,\n logLevel: values.logLevel,\n }\n}\n\ntype LoginOptions = {\n /**\n * Aborting this cancels an in-flight pairing request or poll and rejects with\n * {@link PairingCanceledError}. Wired to `kubb studio`'s shutdown signal, so Ctrl+C during\n * pairing cancels it instead of leaving the poll running.\n */\n signal?: AbortSignal\n /**\n * Credentials from before this login. When the fresh pairing resolves to the same agent on the\n * same Studio, its saved project permissions carry forward instead of being asked again.\n * Left out for an explicitly requested `kubb studio login`, which always starts clean.\n */\n previousCredentials?: Credentials | null\n}\n\n/**\n * Pairs this machine with Studio and stores the resulting token.\n *\n * The CLI holds a code and the browser approves it, rather than the user copying a token out of\n * the UI. The token comes back over the CLI's own HTTPS POST, so it never lands in a URL, a\n * server log, or a `Referer` header.\n */\nexport async function login({ studioUrl, autoOpen }: StudioOptions, { signal, previousCredentials }: LoginOptions = {}): Promise<Credentials> {\n const spinner = createSpinner()\n // The spinner only starts once a code is shown.\n let waiting = false\n\n try {\n const { token, agent } = await pairAgent({\n studioUrl,\n type: 'cli',\n name: path.basename(process.cwd()),\n hostname: hostname(),\n machineId: await getMachineId(),\n signal,\n onCode: (session) => {\n console.log(`\\nOpen ${styleText('cyan', session.verification_uri)} and approve the code ${styleText('bold', session.user_code)}`)\n\n if (autoOpen) {\n openInBrowser(session.verification_uri_complete)\n }\n\n spinner.start('Waiting for approval')\n waiting = true\n },\n onRetry: (error) => spinner.message(`Could not reach Kubb Studio, retrying: ${error.message}`),\n })\n spinner.stop(`Paired as ${agent.name}`)\n\n const keepsIdentity = previousCredentials?.studioUrl === studioUrl && previousCredentials.agentId === agent.id\n const credentials: Credentials = {\n studioUrl,\n token,\n agentId: agent.id,\n agentSlug: agent.slug,\n ...(keepsIdentity && previousCredentials?.projects ? { projects: previousCredentials.projects } : {}),\n }\n await writeCredentials(credentials)\n\n console.log(`Credentials stored in ${getCredentialsPath()}`)\n\n return credentials\n } catch (error) {\n if (waiting) {\n spinner.stop(error instanceof PairingCanceledError ? 'Pairing canceled' : 'Pairing failed')\n }\n throw error\n }\n}\n\n/**\n * What each permission is asked as, in the order the questions appear.\n */\nconst PERMISSIONS: ReadonlyArray<{\n key: Permission\n /**\n * Short label for status output and the connect summary.\n */\n label: string\n question: (project: string, configPath: string) => string\n}> = [\n {\n key: 'allowRead',\n label: 'read generated files',\n question: () => 'Let Kubb Studio read the files a generation produced?',\n },\n { key: 'allowWrite', label: 'write generated files', question: (project) => `Let Kubb Studio write generated files into ${project}?` },\n {\n key: 'allowConfigEdit',\n label: 'edit kubb.config.ts',\n question: (_project, configPath) => `Let Kubb Studio change plugin options in ${configPath}?`,\n },\n {\n key: 'allowExec',\n label: 'run formatter, linter, postGenerate',\n question: () => 'Let Kubb Studio run the formatter, the linter, and output.postGenerate?',\n },\n]\n\n/**\n * One row per permission, for the connect banner and `kubb studio status`. A list rather than a\n * joined line: four labels this long read as one run-on sentence side by side.\n */\nexport function formatPermissionRows(granted: Record<Permission, boolean>): Array<string> {\n return PERMISSIONS.map(({ key, label }) => `${granted[key] ? styleText('green', '✔') : styleText('red', '✘')} ${label}`)\n}\n\n/**\n * Resolves Studio permissions for the current project.\n * Flags win, saved answers are reused, and new answers are stored unless `persist` is false.\n */\nexport async function resolvePermissions(\n options: StudioOptions,\n credentials: Credentials,\n configPath: string = KUBB_CONFIG_FILENAME,\n /**\n * Stores new answers in `~/.kubb/credentials.json`.\n * Pass `false` for `KUBB_AGENT_TOKEN` sessions so a temporary token is not written back to disk.\n */\n persist = true,\n): Promise<Record<Permission, boolean>> {\n const project = process.cwd()\n const remembered = credentials.projects?.[project]\n const granted: Record<Permission, boolean> = { allowRead: false, allowWrite: false, allowConfigEdit: false, allowExec: false }\n const answers: Partial<Record<Permission, boolean>> = {}\n prompts.updateSettings({ withGuide: true })\n\n for (const { key, question } of PERMISSIONS) {\n if (options.permission[key] || typeof remembered?.[key] === 'boolean') {\n granted[key] = options.permission[key] || remembered?.[key] === true\n continue\n }\n\n if (isCIEnvironment() || !canUseTTY()) {\n granted[key] = false\n continue\n }\n\n granted[key] = (await prompts.confirm({ message: question(project, configPath), initialValue: false })) === true\n answers[key] = granted[key]\n }\n if (persist && Object.keys(answers).length) {\n await writeCredentials({\n ...credentials,\n projects: { ...credentials.projects, [project]: { ...remembered, ...answers } },\n })\n }\n\n return granted\n}\n\n/**\n * Loads the project's Kubb config the same way `kubb generate` does.\n * Returns the first config.\n */\nexport async function loadConfigs(options: Pick<StudioOptions, 'configPath' | 'logLevel'>): Promise<{ configPath: string; config: Config }> {\n const { configPath, configs } = await getConfigs({ configPath: options.configPath, logLevel: options.logLevel })\n const [config] = configs\n\n if (!config) {\n throw new Error('Config not defined, create a kubb.config.ts or pass it with --config')\n }\n\n // `getConfigs` resolves this to an absolute path. Relativized here, once, so the permission\n // prompt and the path Studio receives over the wire both show the project-relative form instead\n // of leaking the local filesystem layout.\n return { configPath: path.relative(process.cwd(), configPath) || configPath, config }\n}\n\n/**\n * Why a rejected token could not be recovered automatically: it came from `KUBB_AGENT_TOKEN`, no\n * browser is available to re-pair with, or the token this run already re-paired for was rejected\n * again.\n */\ntype RejectedTokenReason = 'envToken' | 'nonInteractive' | 'reauthExhausted'\n\n/**\n * Explains a rejected token to the operator. Builds the error and nothing else, so each caller\n * decides what happens to the credentials.\n */\nfunction explainRejectedToken(error: InvalidAgentTokenError, reason: RejectedTokenReason): Error {\n if (reason === 'envToken') {\n return new Error(`${error.message} Pair again and update KUBB_AGENT_TOKEN.`)\n }\n\n if (reason === 'reauthExhausted') {\n return new Error(`${error.message} Studio rejected the newly approved token too. Run \\`kubb studio login\\` and try again.`)\n }\n\n return new Error(`${error.message} Run \\`kubb studio login\\` to pair again.`)\n}\n\nexport type PreparedConnection = {\n configPath: string\n credentials: Credentials\n permissions: StudioOptions['permission']\n}\n\n/** Validates the project and resolves pairing and permissions before starting its worker. */\nexport async function prepareConnection(options: StudioOptions, signal?: AbortSignal): Promise<PreparedConnection> {\n const { configPath } = await loadConfigs(options)\n const envToken = process.env.KUBB_AGENT_TOKEN\n const stored = envToken ? null : await readCredentials()\n const credentials = await (async () => {\n if (envToken) return { studioUrl: options.studioUrl, token: envToken, agentId: '', agentSlug: '' }\n if (stored?.studioUrl === options.studioUrl) return stored\n if (isCIEnvironment()) {\n throw new Error(`Not paired with ${options.studioUrl}. Set KUBB_AGENT_TOKEN, or run \\`kubb studio login\\` on a machine with a browser.`)\n }\n return login(options, { signal })\n })()\n const permissions = await resolvePermissions(options, credentials, configPath, !envToken)\n return { configPath, credentials, permissions }\n}\n\nexport type WorkerState = 'starting' | 'connected' | 'reconnecting' | 'authentication required' | 'stopped'\n\n/** Runs the same project connection in the foreground or in a background worker. */\nexport async function connect(\n options: StudioOptions,\n context: {\n prepared?: PreparedConnection\n signal?: AbortSignal\n onState?: (state: WorkerState) => void | Promise<void>\n } = {},\n): Promise<void> {\n const shutdown = new AbortController()\n const signal = context.signal ?? shutdown.signal\n const stop = () => shutdown.abort()\n const events = process as unknown as NodeJS.EventEmitter\n if (!context.signal) {\n events.once('SIGINT', stop)\n events.once('SIGTERM', stop)\n }\n try {\n if (!context.prepared) {\n const { getWorkerStatus } = await import('./background.ts')\n const worker = await getWorkerStatus()\n if (worker.running) throw new Error('This project is running in the background. Run `kubb studio stop` first.')\n }\n const prepared = context.prepared ?? (await prepareConnection(options, signal))\n let { credentials, permissions } = prepared\n const { configPath } = prepared\n let hinted = false\n // Set by a fresh pairing until it connects, so a token rejected right after approval fails the run instead of looping.\n let pairedAgain = false\n\n // The background worker writes to a log file, so it gets the plain logger rather than the\n // animated one. The logger also covers the `studio:*` events and each generation run.\n const hooks = new Hookable<KubbHooks>()\n setupReporters(hooks, {\n logLevel: logLevelMap[options.logLevel ?? 'info'],\n reporters: [cliReporter],\n ...(context.onState ? { logger: plainLogger } : {}),\n })\n hooks.hook('studio:connected', async () => {\n pairedAgain = false\n await context.onState?.('connected')\n if (hinted || context.onState || options.logLevel === 'silent') return\n hinted = true\n logBlock(styleText('dim', 'Press Ctrl+C to disconnect'))\n logTip()\n })\n hooks.hook('studio:reconnecting', () => context.onState?.('reconnecting'))\n\n for (;;) {\n try {\n await connectAgent({\n token: credentials.token,\n studioUrl: options.studioUrl,\n configPath,\n version: options.version,\n root: process.cwd(),\n permissions,\n loadConfig: async () => (await loadConfigs(options)).config,\n hooks,\n signal,\n })\n break\n } catch (error) {\n if (!(error instanceof InvalidAgentTokenError)) throw error\n await context.onState?.('authentication required')\n if (process.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, 'envToken')\n await clearCredentials()\n if (pairedAgain) throw explainRejectedToken(error, 'reauthExhausted')\n if (context.onState || isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, 'nonInteractive')\n\n console.log(styleText('yellow', `${error.message} Pairing again...`))\n credentials = await login(options, { signal, previousCredentials: credentials })\n pairedAgain = true\n permissions = await resolvePermissions(options, credentials, configPath)\n }\n }\n if (!context.onState && signal.aborted && options.logLevel !== 'silent') logOutro('Disconnected')\n } catch (error) {\n if (!(error instanceof PairingCanceledError)) throw error\n } finally {\n events.off('SIGINT', stop)\n events.off('SIGTERM', stop)\n }\n}\n\n/**\n * Runs a Studio command with shared setup and telemetry reporting.\n */\nexport async function run({\n options,\n action,\n block = false,\n json = false,\n}: {\n options: Pick<StudioOptions, 'logLevel'>\n action: () => Promise<unknown>\n block?: boolean\n json?: boolean\n}): Promise<void> {\n const report = trackRun({ command: 'studio', hrStart: process.hrtime() })\n\n try {\n if (options.logLevel !== 'silent' && !json) {\n logIntro({\n warning: styleText('yellow', 'This feature is still under development, use with caution'),\n block,\n })\n }\n\n await action()\n\n await report({ status: 'success' })\n } catch (error) {\n await report({ status: 'failed' })\n console.error(toError(error).message)\n process.exitCode = 1\n }\n}\n"],"mappings":"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"}
@@ -1,20 +1,19 @@
1
1
  import "./rolldown-runtime-CRm0XQPb.js";
2
2
  import { t as toError } from "./errors-BQJzaUwP.js";
3
3
  import { i as isCIEnvironment } from "./agent-BPIqnEl2.js";
4
- import { i as runtime, n as trackRun, r as version } from "./Telemetry-RBcAjNxj.js";
4
+ import { n as trackRun, r as version } from "./Telemetry-DqT0XYcN.js";
5
5
  import { t as KUBB_CONFIG_FILENAME } from "./constants-DHRspBTo.js";
6
- import { r as setupReporters, t as getConfigs } from "./config-8Xl4yZmw.js";
7
- import { g as plainLogger, o as logIntro, p as canUseTTY, r as logBlock, s as logOutro, t as createSpinner, u as logTip } from "./output-B6Ondw0g.js";
6
+ import { r as setupReporters, t as getConfigs } from "./config-CCxOcwWu.js";
7
+ import { g as plainLogger, o as logIntro, p as canUseTTY, r as logBlock, s as logOutro, t as createSpinner, u as logTip } from "./output-CnSY0tCG.js";
8
8
  import { styleText } from "node:util";
9
- import { hash } from "node:crypto";
10
- import { homedir, hostname } from "node:os";
9
+ import { hash, randomBytes } from "node:crypto";
10
+ import { hostname } from "node:os";
11
11
  import process from "node:process";
12
12
  import { x } from "tinyexec";
13
- import { access, mkdir, readFile, rm, writeFile } from "node:fs/promises";
14
13
  import path from "node:path";
15
- import { PairingCanceledError, createFileStorage, defaultStudioUrl, pairAgent, runConnection, setStorage } from "@kubb/studio";
14
+ import { Hookable, cliReporter, kubbStorage, logLevel, resolveKubbDir } from "@kubb/core";
16
15
  import * as prompts from "@clack/prompts";
17
- import { cliReporter, logLevel } from "@kubb/core";
16
+ import { InvalidAgentTokenError, PairingCanceledError, connectAgent, defaultStudioUrl, pairAgent } from "@kubb/studio";
18
17
  //#region ../../internals/utils/src/openInBrowser.ts
19
18
  const ignore = () => {};
20
19
  /**
@@ -34,68 +33,18 @@ function openInBrowser(target) {
34
33
  x(process.platform === "darwin" ? "open" : "xdg-open", [target]).then(ignore, ignore);
35
34
  }
36
35
  //#endregion
37
- //#region ../../internals/utils/src/fs.ts
38
- /**
39
- * Resolves to `true` when the file or directory at `path` exists.
40
- * Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
41
- *
42
- * @example
43
- * ```ts
44
- * if (await exists('./kubb.config.ts')) {
45
- * const content = await read('./kubb.config.ts')
46
- * }
47
- * ```
48
- */
49
- async function exists(path) {
50
- if (runtime.isBun) return Bun.file(path).exists();
51
- return access(path).then(() => true, () => false);
52
- }
53
- /**
54
- * Reads the file at `path` as a UTF-8 string.
55
- * Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
56
- *
57
- * @example
58
- * ```ts
59
- * const source = await read('./src/Pet.ts')
60
- * ```
61
- */
62
- async function read(path) {
63
- if (runtime.isBun) return Bun.file(path).text();
64
- return readFile(path, { encoding: "utf8" });
65
- }
66
- /**
67
- * Recursively removes `path`. Silently succeeds when `path` does not exist.
68
- *
69
- * @example
70
- * ```ts
71
- * await clean('./dist')
72
- * ```
73
- */
74
- async function clean(path) {
75
- return rm(path, {
76
- recursive: true,
77
- force: true
78
- });
79
- }
80
- //#endregion
81
36
  //#region src/runners/studio/credentials.ts
37
+ const projectBase = (project = process.cwd()) => path.join("projects", hash("sha256", project));
82
38
  /**
83
- * Root for everything the CLI persists between runs: the paired credential, the machine secret,
84
- * the last Studio config, and the live-session registry. `KUBB_HOME` relocates all of it.
85
- */
86
- function getKubbHome() {
87
- return process.env.KUBB_HOME ?? path.join(homedir(), ".kubb");
88
- }
89
- /**
90
- * Root for the credential and machine identity of the current project.
39
+ * Root for the credential and machine identity of the current project, inside `KUBB_HOME`.
91
40
  *
92
41
  * Studio agents are project connections, so two projects on one machine must not share the same
93
42
  * persisted token or machine secret.
94
43
  */
95
44
  function getProjectKubbHome(project = process.cwd()) {
96
- const projectId = hash("sha256", project);
97
- return path.join(getKubbHome(), "projects", projectId);
45
+ return resolveKubbDir(projectBase(project));
98
46
  }
47
+ const projectStorage = () => kubbStorage({ base: projectBase() });
99
48
  const CREDENTIALS_FILENAME = "credentials.json";
100
49
  /**
101
50
  * Absolute path of the stored credential, so callers can name it in output without rebuilding it.
@@ -109,32 +58,33 @@ function getCredentialsPath() {
109
58
  * overwrite it rather than the CLI dying on it.
110
59
  */
111
60
  async function readCredentials() {
112
- const raw = await read(getCredentialsPath()).catch(() => null);
113
- if (!raw) return null;
114
- try {
115
- return JSON.parse(raw);
116
- } catch {
117
- return null;
118
- }
61
+ const stored = await projectStorage().getItem(CREDENTIALS_FILENAME).catch(() => null);
62
+ return typeof stored?.token === "string" && stored.token !== "" ? stored : null;
119
63
  }
120
64
  /**
121
- * Writes the credential with owner-only permissions. The file holds a bearer token, so the mode
122
- * is set at creation time rather than left to the process umask or fixed up afterward, which
123
- * would leave the token briefly readable under the umask's default mode.
65
+ * Writes the credential into the project folder, which only its owner can open.
124
66
  */
125
67
  async function writeCredentials(credentials) {
126
- const file = getCredentialsPath();
127
- await mkdir(path.dirname(file), {
128
- recursive: true,
129
- mode: 448
130
- });
131
- await writeFile(file, JSON.stringify(credentials, null, 2), { mode: 384 });
68
+ await projectStorage().setItem(CREDENTIALS_FILENAME, credentials);
132
69
  }
133
70
  /**
134
71
  * Forgets the stored credential. Succeeds when there was nothing to forget.
135
72
  */
136
73
  async function clearCredentials() {
137
- await clean(getCredentialsPath());
74
+ await projectStorage().removeItem(CREDENTIALS_FILENAME);
75
+ }
76
+ /**
77
+ * Reads this project's machine secret, creating it on first use. Pairing sends its hash, so pairing
78
+ * the same project again rotates one agent's token instead of creating a second agent. It lives
79
+ * apart from the credential, which {@link clearCredentials} deletes.
80
+ */
81
+ async function getMachineId() {
82
+ const storage = projectStorage();
83
+ const stored = await storage.getItem("machine-secret").catch(() => null);
84
+ if (typeof stored === "string" && stored.trim()) return stored.trim();
85
+ const secret = randomBytes(32).toString("hex");
86
+ await storage.setItem("machine-secret", secret);
87
+ return secret;
138
88
  }
139
89
  //#endregion
140
90
  //#region src/runners/studio/run.ts
@@ -169,6 +119,7 @@ async function login({ studioUrl, autoOpen }, { signal, previousCredentials } =
169
119
  type: "cli",
170
120
  name: path.basename(process.cwd()),
171
121
  hostname: hostname(),
122
+ machineId: await getMachineId(),
172
123
  signal,
173
124
  onCode: (session) => {
174
125
  console.log(`\nOpen ${styleText("cyan", session.verification_uri)} and approve the code ${styleText("bold", session.user_code)}`);
@@ -326,68 +277,59 @@ async function connect(options, context = {}) {
326
277
  events.once("SIGINT", stop);
327
278
  events.once("SIGTERM", stop);
328
279
  }
329
- let hinted = false;
330
- let reauthenticated = false;
331
280
  try {
332
281
  if (!context.prepared) {
333
- const { getWorkerStatus } = await import("./background-BqS01QDb.js");
282
+ const { getWorkerStatus } = await import("./background-O8hzI5Pg.js");
334
283
  if ((await getWorkerStatus()).running) throw new Error("This project is running in the background. Run `kubb studio stop` first.");
335
284
  }
336
285
  const prepared = context.prepared ?? await prepareConnection(options, signal);
337
286
  let { credentials, permissions } = prepared;
338
287
  const { configPath } = prepared;
339
- await runConnection({
340
- credentials,
341
- signal,
342
- clientOptions: () => ({
288
+ let hinted = false;
289
+ let pairedAgain = false;
290
+ const hooks = new Hookable();
291
+ setupReporters(hooks, {
292
+ logLevel: logLevel[options.logLevel ?? "info"],
293
+ reporters: [cliReporter],
294
+ ...context.onState ? { logger: plainLogger } : {}
295
+ });
296
+ hooks.hook("studio:connected", async () => {
297
+ pairedAgain = false;
298
+ await context.onState?.("connected");
299
+ if (hinted || context.onState || options.logLevel === "silent") return;
300
+ hinted = true;
301
+ logBlock(styleText("dim", "Press Ctrl+C to disconnect"));
302
+ logTip();
303
+ });
304
+ hooks.hook("studio:reconnecting", () => context.onState?.("reconnecting"));
305
+ for (;;) try {
306
+ await connectAgent({
307
+ token: credentials.token,
343
308
  studioUrl: options.studioUrl,
344
309
  configPath,
345
310
  version: options.version,
346
311
  root: process.cwd(),
347
312
  permissions,
348
313
  loadConfig: async () => (await loadConfigs(options)).config,
349
- installLogger: async (hooks) => {
350
- setupReporters(hooks, {
351
- logLevel: logLevel[options.logLevel ?? "info"],
352
- reporters: [cliReporter],
353
- ...context.onState ? { logger: plainLogger } : {}
354
- });
355
- hooks.hook("studio:connected", () => {
356
- if (!hinted && !context.onState && options.logLevel !== "silent") logBlock(styleText("dim", "Press Ctrl+C to disconnect"));
357
- hinted = true;
358
- });
359
- hooks.hook("studio:ready", async () => {
360
- await context.onState?.("connected");
361
- if (!context.onState) logTip();
362
- });
363
- hooks.hook("studio:reconnecting", () => context.onState?.("reconnecting"));
364
- }
365
- }),
366
- onTokenRejected: async ({ error, live }) => {
367
- await context.onState?.("authentication required");
368
- if (process.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, "envToken");
369
- if (!live) await clearCredentials();
370
- if (reauthenticated) throw explainRejectedToken(error, "reauthExhausted");
371
- if (context.onState || isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
372
- console.log(styleText("yellow", live ? `${error.message} Studio needs you to approve access again.` : `${error.message} Pairing again...`));
373
- if (live) await clearCredentials();
374
- try {
375
- credentials = await login(options, {
376
- signal,
377
- previousCredentials: credentials
378
- });
379
- } catch (error) {
380
- if (error instanceof PairingCanceledError) {
381
- if (live && options.logLevel !== "silent") logOutro("Disconnected");
382
- return null;
383
- }
384
- throw error;
385
- }
386
- reauthenticated = true;
387
- permissions = await resolvePermissions(options, credentials, configPath, !process.env.KUBB_AGENT_TOKEN);
388
- return credentials;
389
- }
390
- });
314
+ hooks,
315
+ signal
316
+ });
317
+ break;
318
+ } catch (error) {
319
+ if (!(error instanceof InvalidAgentTokenError)) throw error;
320
+ await context.onState?.("authentication required");
321
+ if (process.env.KUBB_AGENT_TOKEN) throw explainRejectedToken(error, "envToken");
322
+ await clearCredentials();
323
+ if (pairedAgain) throw explainRejectedToken(error, "reauthExhausted");
324
+ if (context.onState || isCIEnvironment() || !canUseTTY()) throw explainRejectedToken(error, "nonInteractive");
325
+ console.log(styleText("yellow", `${error.message} Pairing again...`));
326
+ credentials = await login(options, {
327
+ signal,
328
+ previousCredentials: credentials
329
+ });
330
+ pairedAgain = true;
331
+ permissions = await resolvePermissions(options, credentials, configPath);
332
+ }
391
333
  if (!context.onState && signal.aborted && options.logLevel !== "silent") logOutro("Disconnected");
392
334
  } catch (error) {
393
335
  if (!(error instanceof PairingCanceledError)) throw error;
@@ -399,8 +341,7 @@ async function connect(options, context = {}) {
399
341
  /**
400
342
  * Runs a Studio command with shared setup and telemetry reporting.
401
343
  */
402
- async function run(options, action, { block = false, json = false } = {}) {
403
- setStorage(createFileStorage(getProjectKubbHome()));
344
+ async function run({ options, action, block = false, json = false }) {
404
345
  const report = trackRun({
405
346
  command: "studio",
406
347
  hrStart: process.hrtime()
@@ -419,6 +360,6 @@ async function run(options, action, { block = false, json = false } = {}) {
419
360
  }
420
361
  }
421
362
  //#endregion
422
- export { login as a, clearCredentials as c, exists as d, read as f, loadConfigs as i, getProjectKubbHome as l, createStudioOptions as n, prepareConnection as o, formatPermissionRows as r, run as s, connect as t, readCredentials as u };
363
+ export { login as a, clearCredentials as c, loadConfigs as i, getProjectKubbHome as l, createStudioOptions as n, prepareConnection as o, formatPermissionRows as r, run as s, connect as t, readCredentials as u };
423
364
 
424
- //# sourceMappingURL=run-Dgph0WYk.js.map
365
+ //# sourceMappingURL=run-B_0Vp1ZV.js.map