@hoox-sh/hoox-cli 0.11.4 → 0.11.6

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 (3) hide show
  1. package/README.md +46 -5
  2. package/dist/index.js +293 -130
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,10 +6,11 @@
6
6
 
7
7
  > **Runtime requirement:** Bun ≥ 1.2. The bin shebang and bundle target are Bun-only; `npm install -g` will install the package but the CLI will not run under Node.js.
8
8
 
9
- **v0.11.x** — operator security plane (`doctor --security`, `tunnel check`, transport profile Bearer + Access), PYNE edge ops (`hoox pyne`), schema validate/generate, and wrangler **4.x**. **v0.10.x** hardened fail-closed remote TUI / management `/v1/*`. **v0.9.x** shipped hop-level `perf fastpath` observability.
9
+ **v0.11.x** — monorepo auto-detect + remember (run `hx` from any directory), Linear Rail banner, quieter secrets check, operator security plane (`doctor --security`, `tunnel check`), PYNE (`hoox pyne`), schema validate/generate, wrangler **4.x**.
10
10
 
11
11
  ## Features
12
12
 
13
+ - **Monorepo auto-detect & remember**: On every start the CLI finds the hoox monorepo (`HOOX_REPO` → walk up from cwd → `~/.hoox/config/monorepo.json` → `~/.hoox/repo`), saves the path, and `chdir`s into it so `hx` / `hoox` work from `~/Videos` or any other folder after you have run once from the monorepo.
13
14
  - **One-shot Onboarding** (`hoox onboard`): Full bootstrap from a fresh clone — collects credentials, configures workers, provisions D1/KV, generates keys, pushes secrets, deploys dashboard. The recommended entry point for new users.
14
15
  - **Config & Setup Wizards** (`hoox init`, `hoox setup`): Split into two steps — `init` writes `wrangler.jsonc` and collects integration secrets; `setup` generates keys, applies D1 schema, and pushes secrets to Cloudflare. Run separately when you need fine-grained control.
15
16
  - **Infrastructure as Code**: Manage D1, KV, R2, Queues, Vectorize, and Analytics via `hoox infra`.
@@ -56,6 +57,10 @@ bun run build # produces dist/index.js — required for `hoox` after global in
56
57
  ## Quick Start
57
58
 
58
59
  ```bash
60
+ # From the monorepo root (first time — path is remembered)
61
+ cd /path/to/hoox
62
+ hoox doctor # shows Remembered + Runtime root
63
+
59
64
  # One-shot onboarding (collects credentials, provisions, deploys)
60
65
  hoox onboard
61
66
 
@@ -70,8 +75,42 @@ hoox check health
70
75
  # Deploy to Cloudflare
71
76
  hoox deploy all --auto
72
77
 
73
- # Measure fast-path latency
74
- hoox perf fastpath run --n 50
78
+ # Later: works from any directory (uses remembered monorepo)
79
+ cd ~/Videos
80
+ hx check health
81
+ hx doctor # Source: remembered
82
+ ```
83
+
84
+ ## Monorepo resolution (any cwd)
85
+
86
+ Resolution order at CLI startup:
87
+
88
+ 1. **`HOOX_REPO`** — absolute path override (highest priority)
89
+ 2. **Walk up from cwd** — discovers a local checkout (`packages/cli` + repo markers)
90
+ 3. **Remembered path** — `~/.hoox/config/monorepo.json` (written when discovered via cwd/env)
91
+ 4. **Global clone** — `~/.hoox/repo` (create with `hoox doctor --fix-runtime`)
92
+
93
+ When a root is found, the CLI:
94
+
95
+ - refreshes the remembered path (when source is `cwd` / `env` / `remembered`)
96
+ - sets session `HOOX_REPO` for child processes
97
+ - `chdir`s into the monorepo so relative paths (`wrangler.jsonc`, `workers/`, …) work
98
+
99
+ Markers for a valid monorepo: `packages/cli/package.json` **plus** one of `wrangler.jsonc`, `wrangler.jsonc.example`, `workers/`, or `.gitmodules`.
100
+
101
+ | Env / path | Purpose |
102
+ | ------------------------------ | ------------------------------------------------------ |
103
+ | `HOOX_REPO` | Force monorepo root |
104
+ | `HOOX_HOME` | Override `~/.hoox` (default home for config/data/repo) |
105
+ | `HOOX_CLI_SILENT=1` | Suppress “using monorepo at …” notice |
106
+ | `~/.hoox/config/monorepo.json` | Persisted root `{ "root", "updatedAt" }` |
107
+
108
+ ```bash
109
+ # Inspect layout
110
+ hoox doctor
111
+ # Remembered /home/you/Git/hoox
112
+ # Runtime root /home/you/Git/hoox
113
+ # Source remembered
75
114
  ```
76
115
 
77
116
  ## Available Commands
@@ -358,8 +397,9 @@ hoox perf fastpath report --from 1h
358
397
  ### Operator Security & Private Ingress
359
398
 
360
399
  ```bash
361
- # Paths, TUI entry, and runtime layout
400
+ # Paths, TUI entry, remembered monorepo, runtime layout
362
401
  hoox doctor
402
+ hoox doctor --fix-runtime # clone monorepo into ~/.hoox/repo if needed
363
403
 
364
404
  # Hygiene + optional management /v1/health probes
365
405
  hoox doctor --security
@@ -504,8 +544,9 @@ packages/cli/
504
544
  │ │ ├── perf/ # Percentile, probe-sender, observability-reader
505
545
  │ │ ├── prerequisites/ # Tool version checks
506
546
  │ │ ├── runtime/ # Global ~/.hoox layout
547
+ │ │ ├── workspace/ # Monorepo detect, remember, chdir
507
548
  │ │ └── secrets/ # Secret management
508
- │ ├── ui/ # Interactive menu (clack)
549
+ │ ├── ui/ # Interactive menu + Linear Rail banner
509
550
  │ └── utils/ # Errors, formatters, theme
510
551
  ├── bin/
511
552
  │ └── hoox.js # CLI binary entry point
package/dist/index.js CHANGED
@@ -16200,7 +16200,13 @@ var init_sse = __esm(() => {
16200
16200
  });
16201
16201
 
16202
16202
  // ../shared/src/path-utils.ts
16203
- import { existsSync as existsSync2 } from "fs";
16203
+ import {
16204
+ existsSync as existsSync2,
16205
+ mkdirSync as mkdirSync2,
16206
+ readFileSync as readFileSync2,
16207
+ writeFileSync as writeFileSync2,
16208
+ chmodSync as chmodSync2
16209
+ } from "fs";
16204
16210
  import { homedir as homedir2 } from "os";
16205
16211
  import { dirname, join as join2, resolve } from "path";
16206
16212
  function createHooxPath(path2) {
@@ -16226,7 +16232,55 @@ function isHooxSetupRoot(dir) {
16226
16232
  return false;
16227
16233
  try {
16228
16234
  const root = resolve(dir);
16229
- return existsSync2(join2(root, "wrangler.jsonc")) && existsSync2(join2(root, "packages", "cli", "package.json"));
16235
+ if (!existsSync2(join2(root, "packages", "cli", "package.json"))) {
16236
+ return false;
16237
+ }
16238
+ return existsSync2(join2(root, "wrangler.jsonc")) || existsSync2(join2(root, "wrangler.jsonc.example")) || existsSync2(join2(root, "workers")) || existsSync2(join2(root, ".gitmodules"));
16239
+ } catch {
16240
+ return false;
16241
+ }
16242
+ }
16243
+ function getRememberedMonorepoPath() {
16244
+ return resolveHooxPath("config/monorepo.json");
16245
+ }
16246
+ function getRememberedMonorepoRoot() {
16247
+ try {
16248
+ const path2 = getRememberedMonorepoPath();
16249
+ if (!existsSync2(path2))
16250
+ return null;
16251
+ const raw = readFileSync2(path2, "utf-8");
16252
+ const data = JSON.parse(raw);
16253
+ if (!data.root || typeof data.root !== "string")
16254
+ return null;
16255
+ const resolved = resolve(data.root);
16256
+ return isHooxSetupRoot(resolved) ? resolved : null;
16257
+ } catch {
16258
+ return null;
16259
+ }
16260
+ }
16261
+ function rememberMonorepoRoot(root) {
16262
+ try {
16263
+ const resolved = resolve(root);
16264
+ if (!isHooxSetupRoot(resolved))
16265
+ return false;
16266
+ const configDir = getHooxConfigDir();
16267
+ mkdirSync2(configDir, { recursive: true, mode: 448 });
16268
+ try {
16269
+ chmodSync2(configDir, 448);
16270
+ } catch {}
16271
+ const payload = {
16272
+ root: resolved,
16273
+ updatedAt: new Date().toISOString()
16274
+ };
16275
+ const path2 = getRememberedMonorepoPath();
16276
+ writeFileSync2(path2, JSON.stringify(payload, null, 2) + `
16277
+ `, {
16278
+ mode: 384
16279
+ });
16280
+ try {
16281
+ chmodSync2(path2, 384);
16282
+ } catch {}
16283
+ return true;
16230
16284
  } catch {
16231
16285
  return false;
16232
16286
  }
@@ -16246,6 +16300,7 @@ function resolveHooxRuntimeRoot(options) {
16246
16300
  const env = options?.env ?? process.env;
16247
16301
  const cwd = resolve(options?.cwd ?? process.cwd());
16248
16302
  const globalRepo = getHooxRepoPath();
16303
+ const remembered = options?.skipRemembered ? null : getRememberedMonorepoRoot();
16249
16304
  const envRepo = env.HOOX_REPO?.trim();
16250
16305
  if (envRepo) {
16251
16306
  const resolved = resolve(envRepo);
@@ -16256,6 +16311,7 @@ function resolveHooxRuntimeRoot(options) {
16256
16311
  checked: {
16257
16312
  env: resolved,
16258
16313
  cwd: findHooxSetupRoot(cwd),
16314
+ remembered,
16259
16315
  global: globalRepo
16260
16316
  }
16261
16317
  };
@@ -16266,6 +16322,7 @@ function resolveHooxRuntimeRoot(options) {
16266
16322
  checked: {
16267
16323
  env: resolved,
16268
16324
  cwd: findHooxSetupRoot(cwd),
16325
+ remembered,
16269
16326
  global: globalRepo
16270
16327
  }
16271
16328
  };
@@ -16275,20 +16332,27 @@ function resolveHooxRuntimeRoot(options) {
16275
16332
  return {
16276
16333
  root: local,
16277
16334
  source: "cwd",
16278
- checked: { cwd: local, global: globalRepo }
16335
+ checked: { cwd: local, remembered, global: globalRepo }
16336
+ };
16337
+ }
16338
+ if (remembered) {
16339
+ return {
16340
+ root: remembered,
16341
+ source: "remembered",
16342
+ checked: { cwd: null, remembered, global: globalRepo }
16279
16343
  };
16280
16344
  }
16281
16345
  if (isHooxSetupRoot(globalRepo)) {
16282
16346
  return {
16283
16347
  root: globalRepo,
16284
16348
  source: "global",
16285
- checked: { cwd: null, global: globalRepo }
16349
+ checked: { cwd: null, remembered: null, global: globalRepo }
16286
16350
  };
16287
16351
  }
16288
16352
  return {
16289
16353
  root: null,
16290
16354
  source: "none",
16291
- checked: { cwd: null, global: globalRepo }
16355
+ checked: { cwd: null, remembered: null, global: globalRepo }
16292
16356
  };
16293
16357
  }
16294
16358
  function getTuiEntryCandidates(runtimeRoot) {
@@ -16468,7 +16532,7 @@ function formatRelativeTime2(timestampMs, nowMs) {
16468
16532
  }
16469
16533
 
16470
16534
  // ../shared/src/dashboard-manifest.ts
16471
- import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync2 } from "fs";
16535
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
16472
16536
  import { join as join3 } from "path";
16473
16537
  function isDashboardSectionFlatKv(sectionId) {
16474
16538
  return !DASHBOARD_SECTIONS_NOT_FLAT_KV.has(sectionId);
@@ -16702,7 +16766,7 @@ function tryReadFile(path2) {
16702
16766
  try {
16703
16767
  if (!existsSync3(path2))
16704
16768
  return null;
16705
- return readFileSync2(path2, "utf-8");
16769
+ return readFileSync3(path2, "utf-8");
16706
16770
  } catch {
16707
16771
  return null;
16708
16772
  }
@@ -21897,7 +21961,7 @@ var init_middleware = __esm(() => {
21897
21961
  });
21898
21962
 
21899
21963
  // ../shared/src/stores/config-store.ts
21900
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
21964
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
21901
21965
  import { homedir as homedir3 } from "os";
21902
21966
  import { join as join4 } from "path";
21903
21967
  var bunConfigStorage, DEFAULT_SHORTCUTS, DEFAULT_LOG_FILTER, DEFAULT_NOTIFICATIONS, defaults, useConfigStore;
@@ -21921,15 +21985,15 @@ var init_config_store = __esm(() => {
21921
21985
  setItem: async (_name, value) => {
21922
21986
  const dir = join4(homedir3(), ".hoox");
21923
21987
  if (!existsSync4(dir)) {
21924
- mkdirSync2(dir, { recursive: true, mode: HOOX_DIR_MODE });
21988
+ mkdirSync3(dir, { recursive: true, mode: HOOX_DIR_MODE });
21925
21989
  }
21926
21990
  try {
21927
- chmodSync2(dir, HOOX_DIR_MODE);
21991
+ chmodSync3(dir, HOOX_DIR_MODE);
21928
21992
  } catch {}
21929
21993
  const filePath = join4(dir, "config.json");
21930
21994
  await Bun.write(filePath, value);
21931
21995
  try {
21932
- chmodSync2(filePath, HOOX_CONFIG_FILE_MODE);
21996
+ chmodSync3(filePath, HOOX_CONFIG_FILE_MODE);
21933
21997
  } catch {}
21934
21998
  },
21935
21999
  removeItem: async (_name) => {}
@@ -22049,6 +22113,7 @@ __export(exports_src, {
22049
22113
  resolveHooxRuntimeRoot: () => resolveHooxRuntimeRoot,
22050
22114
  resolveHooxPath: () => resolveHooxPath,
22051
22115
  resolveDependencies: () => resolveDependencies,
22116
+ rememberMonorepoRoot: () => rememberMonorepoRoot,
22052
22117
  readConfigSync: () => readConfigSync,
22053
22118
  readConfig: () => readConfig,
22054
22119
  parseDashboardManifest: () => parseDashboardManifest,
@@ -22073,6 +22138,8 @@ __export(exports_src, {
22073
22138
  headersToObject: () => headersToObject,
22074
22139
  hasOperatorClientCredentials: () => hasOperatorClientCredentials,
22075
22140
  getTuiEntryCandidates: () => getTuiEntryCandidates,
22141
+ getRememberedMonorepoRoot: () => getRememberedMonorepoRoot,
22142
+ getRememberedMonorepoPath: () => getRememberedMonorepoPath,
22076
22143
  getRelativeHooxPath: () => getRelativeHooxPath,
22077
22144
  getHooxWranglerPath: () => getHooxWranglerPath,
22078
22145
  getHooxStatePath: () => getHooxStatePath,
@@ -25557,15 +25624,15 @@ __export(exports_fs_secure, {
25557
25624
  SECRET_FILE_MODE: () => SECRET_FILE_MODE,
25558
25625
  SECRET_DIR_MODE: () => SECRET_DIR_MODE
25559
25626
  });
25560
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync3 } from "fs";
25627
+ import { chmodSync as chmodSync4, mkdirSync as mkdirSync4 } from "fs";
25561
25628
  function secureChmod(path4, mode) {
25562
25629
  try {
25563
- chmodSync3(path4, mode);
25630
+ chmodSync4(path4, mode);
25564
25631
  } catch {}
25565
25632
  }
25566
25633
  function ensureSecretDir(dir) {
25567
25634
  try {
25568
- mkdirSync3(dir, { recursive: true, mode: SECRET_DIR_MODE });
25635
+ mkdirSync4(dir, { recursive: true, mode: SECRET_DIR_MODE });
25569
25636
  } catch {}
25570
25637
  secureChmod(dir, SECRET_DIR_MODE);
25571
25638
  }
@@ -26171,7 +26238,7 @@ var exports_schema_service = {};
26171
26238
  __export(exports_schema_service, {
26172
26239
  SchemaService: () => SchemaService
26173
26240
  });
26174
- import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
26241
+ import { readFileSync as readFileSync6, existsSync as existsSync11 } from "fs";
26175
26242
  import { resolve as resolve4 } from "path";
26176
26243
 
26177
26244
  class SchemaService {
@@ -26207,7 +26274,7 @@ class SchemaService {
26207
26274
  const errors4 = [];
26208
26275
  const isHooxProject = existsSync11(resolve4(this.projectRoot, "wrangler.jsonc")) || existsSync11(resolve4(this.projectRoot, "workers"));
26209
26276
  try {
26210
- const wranglerContent = readFileSync5(workerWranglerPath, "utf-8");
26277
+ const wranglerContent = readFileSync6(workerWranglerPath, "utf-8");
26211
26278
  errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
26212
26279
  } catch (e) {
26213
26280
  const isErrnoException = e instanceof Error && "code" in e;
@@ -26221,7 +26288,7 @@ class SchemaService {
26221
26288
  });
26222
26289
  }
26223
26290
  try {
26224
- const rootContent = readFileSync5(rootWranglerPath, "utf-8");
26291
+ const rootContent = readFileSync6(rootWranglerPath, "utf-8");
26225
26292
  errors4.push(...validateRootSecrets(name, manifest, rootContent));
26226
26293
  } catch (e) {
26227
26294
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26232,7 +26299,7 @@ class SchemaService {
26232
26299
  });
26233
26300
  }
26234
26301
  try {
26235
- const devVarsContent = readFileSync5(devVarsPath, "utf-8");
26302
+ const devVarsContent = readFileSync6(devVarsPath, "utf-8");
26236
26303
  errors4.push(...validateDevVars(name, manifest, devVarsContent));
26237
26304
  } catch (e) {
26238
26305
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26491,7 +26558,7 @@ var init_trace_service = __esm(() => {
26491
26558
  });
26492
26559
 
26493
26560
  // src/index.ts
26494
- import { readFileSync as readFileSync10 } from "fs";
26561
+ import { readFileSync as readFileSync11 } from "fs";
26495
26562
 
26496
26563
  // ../../node_modules/.bun/commander@15.0.0/node_modules/commander/lib/error.js
26497
26564
  class CommanderError extends Error {
@@ -29621,13 +29688,13 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
29621
29688
  }
29622
29689
  // src/services/setup/setup-service.ts
29623
29690
  import {
29624
- chmodSync as chmodSync4,
29691
+ chmodSync as chmodSync5,
29625
29692
  existsSync as existsSync7,
29626
- mkdirSync as mkdirSync4,
29693
+ mkdirSync as mkdirSync5,
29627
29694
  readdirSync as readdirSync3,
29628
- readFileSync as readFileSync3,
29695
+ readFileSync as readFileSync4,
29629
29696
  statSync as statSync2,
29630
- writeFileSync as writeFileSync2
29697
+ writeFileSync as writeFileSync3
29631
29698
  } from "fs";
29632
29699
  import { homedir as homedir4 } from "os";
29633
29700
  import { join as join7 } from "path";
@@ -29699,7 +29766,7 @@ function parseEnvFile(text2) {
29699
29766
  }
29700
29767
  function safeChmod(path4, mode) {
29701
29768
  try {
29702
- chmodSync4(path4, mode);
29769
+ chmodSync5(path4, mode);
29703
29770
  } catch {}
29704
29771
  }
29705
29772
  function loadExistingKeysFromDisk() {
@@ -29708,7 +29775,7 @@ function loadExistingKeysFromDisk() {
29708
29775
  return null;
29709
29776
  let text2;
29710
29777
  try {
29711
- text2 = readFileSync3(setupEnv, "utf-8");
29778
+ text2 = readFileSync4(setupEnv, "utf-8");
29712
29779
  } catch {
29713
29780
  return null;
29714
29781
  }
@@ -29773,9 +29840,9 @@ class SetupService {
29773
29840
  API_SERVICE_KEY_BINDING: internalKey
29774
29841
  };
29775
29842
  if (!existsSync7(KEYS_DIR)) {
29776
- mkdirSync4(KEYS_DIR, { recursive: true });
29843
+ mkdirSync5(KEYS_DIR, { recursive: true });
29777
29844
  } else if (!statSync2(KEYS_DIR).isDirectory()) {
29778
- mkdirSync4(KEYS_DIR, { recursive: true });
29845
+ mkdirSync5(KEYS_DIR, { recursive: true });
29779
29846
  }
29780
29847
  safeChmod(KEYS_DIR, 448);
29781
29848
  const setupEnvPath = join7(KEYS_DIR, "setup.env");
@@ -29798,7 +29865,7 @@ class SetupService {
29798
29865
  const path4 = `workers/${workerFsDir(worker)}/.dev.vars`;
29799
29866
  const meshNames = secretNames;
29800
29867
  const meshSet = new Set(meshNames);
29801
- const existing = existsSync7(path4) ? parseEnvFile(readFileSync3(path4, "utf-8")) : new Map;
29868
+ const existing = existsSync7(path4) ? parseEnvFile(readFileSync4(path4, "utf-8")) : new Map;
29802
29869
  for (const secretName of meshNames) {
29803
29870
  existing.set(secretName, keys[secretName]);
29804
29871
  }
@@ -29958,7 +30025,7 @@ class SetupService {
29958
30025
  const wranglerPath = join7("workers", workerFsDir(worker), "wrangler.jsonc");
29959
30026
  let wranglerContent;
29960
30027
  try {
29961
- wranglerContent = readFileSync3(wranglerPath, "utf-8");
30028
+ wranglerContent = readFileSync4(wranglerPath, "utf-8");
29962
30029
  } catch {
29963
30030
  return { ok: false, error: errMsg };
29964
30031
  }
@@ -29976,7 +30043,7 @@ class SetupService {
29976
30043
  let fixed = wranglerContent.replace(new RegExp(`(\\s*//[^\\n]*\\n)?\\s*"${escapeRegex2(secretName)}"\\s*:\\s*null\\s*,?\\s*\\n?`, "g"), `
29977
30044
  `);
29978
30045
  fixed = fixed.replace(/"vars"\s*:\s*\{\s*\}/, '"vars": {}');
29979
- writeFileSync2(wranglerPath, fixed, "utf-8");
30046
+ writeFileSync3(wranglerPath, fixed, "utf-8");
29980
30047
  this.onProgress({
29981
30048
  type: "info",
29982
30049
  step: "secrets",
@@ -30159,7 +30226,7 @@ class SetupService {
30159
30226
  for (const name of workerDirs) {
30160
30227
  const dir = `workers/${workerFsDir(name)}`;
30161
30228
  try {
30162
- const gitEntry = readFileSync3(join7(dir, ".git"), "utf-8").trim();
30229
+ const gitEntry = readFileSync4(join7(dir, ".git"), "utf-8").trim();
30163
30230
  if (gitEntry.startsWith("gitdir:") || gitEntry.length > 0) {
30164
30231
  if (!this.isWorkerTreePopulated(dir, name))
30165
30232
  missing.push(name);
@@ -30308,7 +30375,7 @@ class SetupService {
30308
30375
  const wranglerPath = `workers/${workerFsDir(worker)}/wrangler.jsonc`;
30309
30376
  let content;
30310
30377
  try {
30311
- content = readFileSync3(wranglerPath, "utf-8");
30378
+ content = readFileSync4(wranglerPath, "utf-8");
30312
30379
  } catch {
30313
30380
  continue;
30314
30381
  }
@@ -30340,7 +30407,7 @@ class SetupService {
30340
30407
  }
30341
30408
  if (modified) {
30342
30409
  content = content.replace(/"vars"\s*:\s*\{\s*\}/, '"vars": {}');
30343
- writeFileSync2(wranglerPath, content, "utf-8");
30410
+ writeFileSync3(wranglerPath, content, "utf-8");
30344
30411
  fixed.push(worker);
30345
30412
  }
30346
30413
  }
@@ -30495,7 +30562,7 @@ class SetupService {
30495
30562
  const devVarsPath = `workers/${workerFsDir(worker)}/.dev.vars`;
30496
30563
  let devVarsContent;
30497
30564
  try {
30498
- devVarsContent = readFileSync3(devVarsPath, "utf-8");
30565
+ devVarsContent = readFileSync4(devVarsPath, "utf-8");
30499
30566
  } catch {
30500
30567
  missing.push(`${worker}:.dev.vars (missing file)`);
30501
30568
  continue;
@@ -31300,8 +31367,8 @@ init_errors4();
31300
31367
  import {
31301
31368
  statSync as statSync3,
31302
31369
  existsSync as existsSync8,
31303
- readFileSync as readFileSync4,
31304
- writeFileSync as writeFileSync3,
31370
+ readFileSync as readFileSync5,
31371
+ writeFileSync as writeFileSync4,
31305
31372
  renameSync
31306
31373
  } from "fs";
31307
31374
  import { resolve as resolve2 } from "path";
@@ -32082,7 +32149,7 @@ async function doUpdateInternalUrls(fmt) {
32082
32149
  process.exitCode = 1 /* ERROR */;
32083
32150
  return;
32084
32151
  }
32085
- const content = readFileSync4(filePath, "utf-8");
32152
+ const content = readFileSync5(filePath, "utf-8");
32086
32153
  const errors4 = [];
32087
32154
  const parsed = parse6(content, errors4);
32088
32155
  if (errors4.length > 0) {
@@ -32108,12 +32175,12 @@ async function doUpdateInternalUrls(fmt) {
32108
32175
  formatSuccess("All service URLs already up to date.", fmt);
32109
32176
  return;
32110
32177
  }
32111
- const fresh = readFileSync4(filePath, "utf-8");
32178
+ const fresh = readFileSync5(filePath, "utf-8");
32112
32179
  const edits = modify(fresh, ["vars"], vars, {
32113
32180
  formattingOptions: { tabSize: 2, insertSpaces: true }
32114
32181
  });
32115
32182
  const tmpPath = `${filePath}.tmp-${process.pid}`;
32116
- writeFileSync3(tmpPath, applyEdits(fresh, edits), "utf-8");
32183
+ writeFileSync4(tmpPath, applyEdits(fresh, edits), "utf-8");
32117
32184
  renameSync(tmpPath, filePath);
32118
32185
  formatSuccess(`Updated ${changesCount} service URL(s) in dashboard wrangler.jsonc`, fmt);
32119
32186
  } catch (err) {
@@ -34824,6 +34891,22 @@ async function runInfraChecks(cf) {
34824
34891
  });
34825
34892
  return { name: "Infrastructure", checks: checks3 };
34826
34893
  }
34894
+ function parseRemoteSecretNames(raw) {
34895
+ try {
34896
+ const parsed = JSON.parse(raw);
34897
+ if (!Array.isArray(parsed))
34898
+ return null;
34899
+ const names = new Set;
34900
+ for (const item of parsed) {
34901
+ if (item && typeof item === "object" && typeof item.name === "string") {
34902
+ names.add(item.name);
34903
+ }
34904
+ }
34905
+ return names;
34906
+ } catch {
34907
+ return null;
34908
+ }
34909
+ }
34827
34910
  async function runSecretsChecks(secretsService, configService, cf) {
34828
34911
  const checks3 = [];
34829
34912
  const enabledWorkers = configService.listEnabledWorkers();
@@ -34832,11 +34915,11 @@ async function runSecretsChecks(secretsService, configService, cf) {
34832
34915
  for (const workerName of enabledWorkers) {
34833
34916
  const result = await secretsService.checkLocalSecrets(workerName);
34834
34917
  if (result.missing.length > 0) {
34835
- errors4.push(`Worker "${workerName}" missing secrets: ${result.missing.join(", ")}`);
34918
+ errors4.push(`Worker "${workerName}" missing secrets: ${result.missing.join(", ")}. ` + `Fix: hoox setup --skip-db --skip-dashboard (mesh keys) or ` + `hoox secrets set ${workerName} <NAME>`);
34836
34919
  }
34837
34920
  for (const secret of result.secrets) {
34838
- if (secret.set && secret.source) {
34839
- warnings.push(`Worker "${workerName}": ${secret.name} is set locally (source: ${secret.source})`);
34921
+ if (!secret.set && secret.source) {
34922
+ warnings.push(`Worker "${workerName}": ${secret.name} is present in ${secret.source} but empty/placeholder`);
34840
34923
  }
34841
34924
  }
34842
34925
  }
@@ -34849,14 +34932,22 @@ async function runSecretsChecks(secretsService, configService, cf) {
34849
34932
  const remoteErrors = [];
34850
34933
  const remoteWarnings = [];
34851
34934
  for (const workerName of enabledWorkers) {
34852
- const secrets = secretsService.listSecrets(workerName);
34853
- if (secrets.length === 0)
34935
+ const expected = secretsService.listSecrets(workerName);
34936
+ if (expected.length === 0)
34854
34937
  continue;
34855
34938
  const result = await cf.secretList(workerName);
34856
34939
  if (!result.ok) {
34857
34940
  remoteErrors.push(`Worker "${workerName}": ${result.error}`);
34858
- } else {
34859
- remoteWarnings.push(`Worker "${workerName}": remote secrets listed OK (${secrets.length} expected)`);
34941
+ continue;
34942
+ }
34943
+ const remoteNames = parseRemoteSecretNames(result.value);
34944
+ if (!remoteNames) {
34945
+ remoteWarnings.push(`Worker "${workerName}": could not parse remote secret list (raw length ${result.value.length})`);
34946
+ continue;
34947
+ }
34948
+ const missingRemote = expected.filter((n3) => !remoteNames.has(n3));
34949
+ if (missingRemote.length > 0) {
34950
+ remoteErrors.push(`Worker "${workerName}" missing on Cloudflare: ${missingRemote.join(", ")}. ` + `Fix: hoox secrets sync ${workerName}`);
34860
34951
  }
34861
34952
  }
34862
34953
  checks3.push({
@@ -37731,10 +37822,10 @@ import { existsSync as existsSync12, realpathSync } from "fs";
37731
37822
  import { homedir as homedir5 } from "os";
37732
37823
 
37733
37824
  // src/services/perf/endpoint-resolver.ts
37734
- import { readFileSync as readFileSync6 } from "fs";
37825
+ import { readFileSync as readFileSync7 } from "fs";
37735
37826
  function readSubdomainPrefix() {
37736
37827
  try {
37737
- const raw = readFileSync6("wrangler.jsonc", "utf8");
37828
+ const raw = readFileSync7("wrangler.jsonc", "utf8");
37738
37829
  const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
37739
37830
  const parsed = JSON.parse(stripped);
37740
37831
  const prefix = parsed.global?.subdomain_prefix;
@@ -38139,6 +38230,62 @@ ${install.stderr || install.stdout}`.trim());
38139
38230
  const tuiEntry = firstExisting(getTuiEntryCandidates(repoPath));
38140
38231
  return { repoPath, cloned, installed, tuiEntry };
38141
38232
  }
38233
+ // src/commands/doctor/doctor-command.ts
38234
+ init_src();
38235
+
38236
+ // src/services/workspace/workspace-context.ts
38237
+ init_src();
38238
+ import { resolve as resolve7 } from "path";
38239
+ var lastWorkspaceContext = null;
38240
+ function getLastWorkspaceContext() {
38241
+ return lastWorkspaceContext;
38242
+ }
38243
+ function ensureWorkspaceContext(options) {
38244
+ const previousCwd = resolve7(options?.cwd ?? process.cwd());
38245
+ const inTest = process.env.BUN_TEST === "1" || false || typeof process.env.BUN_TEST !== "undefined";
38246
+ const allowChdir = options?.force === true || !inTest;
38247
+ const runtime = resolveHooxRuntimeRoot({ cwd: previousCwd });
38248
+ const quiet = options?.quiet === true || process.env.HOOX_CLI_SILENT === "1" || process.argv.includes("--json") || process.argv.includes("--quiet");
38249
+ if (!runtime.root) {
38250
+ const empty = {
38251
+ root: null,
38252
+ source: runtime.source,
38253
+ chdir: false,
38254
+ previousCwd,
38255
+ runtime
38256
+ };
38257
+ lastWorkspaceContext = empty;
38258
+ return empty;
38259
+ }
38260
+ if (runtime.source === "cwd" || runtime.source === "env") {
38261
+ rememberMonorepoRoot(runtime.root);
38262
+ } else if (runtime.source === "remembered") {
38263
+ rememberMonorepoRoot(runtime.root);
38264
+ }
38265
+ if (!process.env.HOOX_REPO) {
38266
+ process.env.HOOX_REPO = runtime.root;
38267
+ }
38268
+ let didChdir = false;
38269
+ const target = resolve7(runtime.root);
38270
+ if (allowChdir && resolve7(process.cwd()) !== target) {
38271
+ process.chdir(target);
38272
+ didChdir = true;
38273
+ if (!quiet) {
38274
+ const label = runtime.source === "remembered" ? "remembered monorepo" : runtime.source === "global" ? "global runtime" : runtime.source === "env" ? "HOOX_REPO" : "monorepo";
38275
+ process.stderr.write(`hoox: using ${label} at ${target}
38276
+ `);
38277
+ }
38278
+ }
38279
+ const ctx = {
38280
+ root: target,
38281
+ source: runtime.source,
38282
+ chdir: didChdir,
38283
+ previousCwd,
38284
+ runtime
38285
+ };
38286
+ lastWorkspaceContext = ctx;
38287
+ return ctx;
38288
+ }
38142
38289
  // src/services/operator-security/operator-security-service.ts
38143
38290
  init_src();
38144
38291
  function collectSecurityHygiene(env = process.env, options) {
@@ -38389,14 +38536,19 @@ function printCheckIcon(severity) {
38389
38536
  function printStatus() {
38390
38537
  const status = getRuntimeStatus();
38391
38538
  const { runtime } = status;
38539
+ const remembered = getRememberedMonorepoRoot();
38540
+ const session = getLastWorkspaceContext();
38541
+ const sourceLabel = session?.source ?? runtime.source;
38542
+ const rootLabel = session?.root ?? runtime.root;
38392
38543
  process.stdout.write(theme.heading(`
38393
38544
  Hoox doctor
38394
38545
 
38395
38546
  `));
38396
38547
  process.stdout.write(`${theme.dim("HOOX_HOME")} ${status.hooxHome}
38397
38548
  ` + `${theme.dim("Global repo")} ${status.repoPath}
38398
- ` + `${theme.dim("Runtime root")} ${runtime.root ?? theme.warning("(none)")}
38399
- ` + `${theme.dim("Source")} ${runtime.source}
38549
+ ` + `${theme.dim("Remembered")} ${remembered ?? theme.dim("(none \u2014 run hx once from the monorepo)")}
38550
+ ` + `${theme.dim("Runtime root")} ${rootLabel ?? theme.warning("(none)")}
38551
+ ` + `${theme.dim("Source")} ${sourceLabel}
38400
38552
  ` + `${theme.dim("TUI entry")} ${status.tuiEntry ?? theme.warning("(not found)")}
38401
38553
 
38402
38554
  `);
@@ -38415,10 +38567,10 @@ Hoox doctor
38415
38567
  detail: status.isSetupRoot ? "monorepo markers present" : status.repoPresent ? "path exists but is not a setup monorepo" : runtime.root ? "optional (runtime resolved elsewhere)" : "missing \u2014 run: hoox doctor --fix-runtime"
38416
38568
  },
38417
38569
  {
38418
- ok: runtime.root !== null,
38570
+ ok: rootLabel !== null,
38419
38571
  required: true,
38420
38572
  label: "Resolved runtime root",
38421
- detail: runtime.root === null ? "set HOOX_REPO, cd into the hoox monorepo, or fix-runtime" : `${runtime.source}: ${runtime.root}`
38573
+ detail: rootLabel === null ? "set HOOX_REPO, run hx once inside the monorepo (remembers path), or: hoox doctor --fix-runtime" : `${sourceLabel}: ${rootLabel}`
38422
38574
  },
38423
38575
  {
38424
38576
  ok: status.tuiEntry !== null,
@@ -38717,7 +38869,7 @@ function registerDisclaimerCommand(program2) {
38717
38869
  }));
38718
38870
  }
38719
38871
  // src/commands/agent/agent-command.ts
38720
- import { readFileSync as readFileSync7 } from "fs";
38872
+ import { readFileSync as readFileSync8 } from "fs";
38721
38873
  init_errors4();
38722
38874
  var PROVIDERS = [
38723
38875
  {
@@ -38751,7 +38903,7 @@ function checkDevVars() {
38751
38903
  const candidates = ["workers/agent-worker/.dev.vars", ".dev.vars"];
38752
38904
  for (const filePath of candidates) {
38753
38905
  try {
38754
- const content = readFileSync7(filePath, "utf-8");
38906
+ const content = readFileSync8(filePath, "utf-8");
38755
38907
  for (const line of content.split(`
38756
38908
  `)) {
38757
38909
  const trimmed = line.trim();
@@ -39282,12 +39434,12 @@ EXAMPLES:
39282
39434
  }
39283
39435
  // src/commands/pyne/pyne-command.ts
39284
39436
  init_config2();
39285
- import { existsSync as existsSync14, readFileSync as readFileSync8 } from "fs";
39286
- import { resolve as resolve7 } from "path";
39437
+ import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
39438
+ import { resolve as resolve8 } from "path";
39287
39439
  init_errors4();
39288
39440
  var DEFAULT_TIMEOUT_MS = 30000;
39289
39441
  function getPyneWorkerDir() {
39290
- return resolve7(process.cwd(), "workers/pyne-worker");
39442
+ return resolve8(process.cwd(), "workers/pyne-worker");
39291
39443
  }
39292
39444
  function requirePyneDir() {
39293
39445
  const dir = getPyneWorkerDir();
@@ -39301,14 +39453,14 @@ function requirePyneDir() {
39301
39453
  function loadDevVars(workerDir) {
39302
39454
  const out = {};
39303
39455
  const candidates = [
39304
- resolve7(workerDir, ".dev.vars"),
39305
- resolve7(process.cwd(), ".dev.vars")
39456
+ resolve8(workerDir, ".dev.vars"),
39457
+ resolve8(process.cwd(), ".dev.vars")
39306
39458
  ];
39307
39459
  for (const filePath of candidates) {
39308
39460
  try {
39309
39461
  if (!existsSync14(filePath))
39310
39462
  continue;
39311
- const content = readFileSync8(filePath, "utf-8");
39463
+ const content = readFileSync9(filePath, "utf-8");
39312
39464
  for (const line of content.split(`
39313
39465
  `)) {
39314
39466
  const trimmed = line.trim();
@@ -39523,11 +39675,11 @@ EXAMPLES:
39523
39675
  } else if (scriptPath === "-") {
39524
39676
  body.script = await new Response(Bun.stdin).text();
39525
39677
  } else {
39526
- const abs = resolve7(process.cwd(), scriptPath);
39678
+ const abs = resolve8(process.cwd(), scriptPath);
39527
39679
  if (!existsSync14(abs)) {
39528
39680
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39529
39681
  }
39530
- body.script = readFileSync8(abs, "utf-8");
39682
+ body.script = readFileSync9(abs, "utf-8");
39531
39683
  }
39532
39684
  const res = await pyneFetch(baseUrl, "/run", {
39533
39685
  method: "POST",
@@ -39574,11 +39726,11 @@ EXAMPLES:
39574
39726
  if (!apiKey) {
39575
39727
  throw new CLIError("API key required for authenticated endpoints", 2 /* INVALID_USAGE */);
39576
39728
  }
39577
- const abs = resolve7(process.cwd(), scriptPath);
39729
+ const abs = resolve8(process.cwd(), scriptPath);
39578
39730
  if (!existsSync14(abs)) {
39579
39731
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39580
39732
  }
39581
- const script = readFileSync8(abs, "utf-8");
39733
+ const script = readFileSync9(abs, "utf-8");
39582
39734
  const baseName = abs.split(/[/\\]/).pop() ?? "script.pine";
39583
39735
  const scriptId = opts.id ?? baseName.replace(/\.pine$/i, "").replace(/[^a-zA-Z0-9_-]/g, "_");
39584
39736
  const res = await pyneFetch(baseUrl, "/scripts", {
@@ -39682,7 +39834,7 @@ Pass script flags after -- :
39682
39834
  pyneCmd.command("sync-vendor").summary("Sync pynescript into python_modules/ for Cloudflare deploy").description(`Runs workers/pyne-worker/scripts/sync_vendor.sh.
39683
39835
  Required before wrangler deploy after pulling new PYNE APIs.`).action(withErrorHandling(async () => {
39684
39836
  const dir = requirePyneDir();
39685
- const script = resolve7(dir, "scripts/sync_vendor.sh");
39837
+ const script = resolve8(dir, "scripts/sync_vendor.sh");
39686
39838
  if (!existsSync14(script)) {
39687
39839
  throw new CLIError(`sync_vendor.sh not found at ${script}`, 1 /* ERROR */);
39688
39840
  }
@@ -39691,7 +39843,7 @@ Required before wrangler deploy after pulling new PYNE APIs.`).action(withErrorH
39691
39843
  pyneCmd.command("deploy").summary("sync-vendor then wrangler deploy pyne-worker").option("--skip-sync", "Skip scripts/sync_vendor.sh").action(withErrorHandling(async (opts) => {
39692
39844
  const dir = requirePyneDir();
39693
39845
  if (!opts.skipSync) {
39694
- const script = resolve7(dir, "scripts/sync_vendor.sh");
39846
+ const script = resolve8(dir, "scripts/sync_vendor.sh");
39695
39847
  if (existsSync14(script)) {
39696
39848
  const code = await spawnInPyneDir(["bash", script], dir);
39697
39849
  if (code !== 0) {
@@ -40253,7 +40405,7 @@ ${theme.dim("Live tail duration reached. Exiting.")}
40253
40405
  `);
40254
40406
  }
40255
40407
  } catch {}
40256
- await new Promise((resolve8) => setTimeout(resolve8, pollInterval));
40408
+ await new Promise((resolve9) => setTimeout(resolve9, pollInterval));
40257
40409
  await poll();
40258
40410
  };
40259
40411
  await poll();
@@ -41241,7 +41393,7 @@ function registerCompletionCommand(program2) {
41241
41393
  // src/ui/banner.ts
41242
41394
  init_ansis();
41243
41395
  init_theme();
41244
- import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
41396
+ import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
41245
41397
  import { fileURLToPath as fileURLToPath2 } from "url";
41246
41398
  import { dirname as dirname4, join as join13 } from "path";
41247
41399
  var ORANGE = ansis_default.hex("#ff7f2a");
@@ -41259,7 +41411,7 @@ function findCliVersion() {
41259
41411
  const pkgPath = join13(dir, "package.json");
41260
41412
  if (existsSync15(pkgPath)) {
41261
41413
  try {
41262
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
41414
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
41263
41415
  if (pkg.name === PKG_NAME)
41264
41416
  return pkg.version;
41265
41417
  } catch {}
@@ -41273,68 +41425,78 @@ function findCliVersion() {
41273
41425
  }
41274
41426
  var VERSION = findCliVersion();
41275
41427
  var DISCLAIMER2 = "DISCLAIMER: Trading cryptocurrencies involves substantial risk of loss. Use at your own risk.";
41276
- var WORDMARK = [
41277
- " _ _ ___ _____ _ ",
41278
- "| | | |/ _ \\ / _ \\ \\/ / ",
41279
- "| |_| | (_) | (_) > < ",
41280
- " \\___/ \\___/ \\___/_/\\_\\ "
41281
- ];
41282
- var WORD_W = WORDMARK[0].length;
41283
- function colorWordmark(phase, mode) {
41284
- return WORDMARK.map((line, row) => {
41285
- if (mode === "assemble") {
41286
- const local = Math.max(0, Math.min(1, phase));
41287
- const cut = Math.floor(local * line.length);
41288
- let out = "";
41289
- for (let col = 0;col < line.length; col++) {
41290
- const ch = line[col];
41291
- if (ch === " ") {
41292
- out += " ";
41293
- continue;
41294
- }
41295
- if (col > cut)
41296
- out += ZINC_FAINT("\xB7");
41297
- else if (local < 0.85)
41298
- out += ZINC(ch);
41299
- else
41300
- out += theme.heading(ch);
41301
- }
41302
- return out;
41303
- }
41304
- if (mode === "pulse") {
41305
- let out = "";
41306
- for (let col = 0;col < line.length; col++) {
41307
- const ch = line[col];
41308
- if (ch === " ") {
41309
- out += " ";
41310
- continue;
41311
- }
41312
- const t2 = (phase * 1.5 + col * 0.05 + row * 0.06) % 1;
41313
- if (t2 < 0.12)
41314
- out += AMBER(ch);
41315
- else if (t2 < 0.25)
41316
- out += ORANGE(ch);
41317
- else if (t2 < 0.4)
41318
- out += INDIGO_SOFT(ch);
41319
- else
41320
- out += INDIGO.bold(ch);
41321
- }
41322
- return out;
41323
- }
41324
- return theme.heading(line);
41325
- });
41326
- }
41428
+ var LETTERS = ["H", "O", "O", "X"];
41429
+ var RULE_W = 38;
41327
41430
  var PAD = " ";
41431
+ function colorDiamond(phase, mode) {
41432
+ if (mode === "assemble") {
41433
+ if (phase < 0.05)
41434
+ return " ";
41435
+ if (phase < 0.2)
41436
+ return ZINC_FAINT("\u25C6");
41437
+ return AMBER("\u25C6");
41438
+ }
41439
+ if (mode === "pulse") {
41440
+ const t2 = phase * 2 % 1;
41441
+ return t2 < 0.5 ? AMBER("\u25C6") : ORANGE("\u25C6");
41442
+ }
41443
+ return AMBER("\u25C6");
41444
+ }
41445
+ function colorLetter(letter, index, phase, mode) {
41446
+ if (mode === "assemble") {
41447
+ const start = 0.12;
41448
+ const span = 0.88;
41449
+ const letterPhase = (phase - start) / span;
41450
+ const revealAt = index / LETTERS.length;
41451
+ if (letterPhase < revealAt)
41452
+ return " ";
41453
+ if (letterPhase < revealAt + 0.12)
41454
+ return ZINC(letter);
41455
+ return INDIGO.bold(letter);
41456
+ }
41457
+ if (mode === "pulse") {
41458
+ const t2 = (phase * 1.4 + index * 0.18) % 1;
41459
+ if (t2 < 0.15)
41460
+ return INDIGO_SOFT(letter);
41461
+ if (t2 < 0.3)
41462
+ return AMBER(letter);
41463
+ return INDIGO.bold(letter);
41464
+ }
41465
+ return INDIGO.bold(letter);
41466
+ }
41467
+ function colorDot(afterLetterIndex, phase, mode) {
41468
+ if (mode === "assemble") {
41469
+ const start = 0.12;
41470
+ const span = 0.88;
41471
+ const letterPhase = (phase - start) / span;
41472
+ const revealAt = (afterLetterIndex + 1) / LETTERS.length;
41473
+ if (letterPhase < revealAt - 0.02)
41474
+ return " ";
41475
+ return ZINC_FAINT("\xB7");
41476
+ }
41477
+ return ZINC_FAINT("\xB7");
41478
+ }
41479
+ function composeTitleLine(phase, mode) {
41480
+ const diamond = colorDiamond(phase, mode);
41481
+ const parts = [diamond, " "];
41482
+ for (let i2 = 0;i2 < LETTERS.length; i2++) {
41483
+ parts.push(colorLetter(LETTERS[i2], i2, phase, mode));
41484
+ if (i2 < LETTERS.length - 1) {
41485
+ parts.push(" ");
41486
+ parts.push(colorDot(i2, phase, mode));
41487
+ parts.push(" ");
41488
+ }
41489
+ }
41490
+ return PAD + parts.join("");
41491
+ }
41492
+ function composeMetaLine() {
41493
+ return PAD + ZINC_SOFT(TAGLINE) + " " + ZINC_FAINT("\xB7") + " " + INDIGO_SOFT(`v${VERSION}`);
41494
+ }
41328
41495
  function composeFrame(phase, mode) {
41329
- const word = colorWordmark(phase, mode);
41330
- const contentW = WORD_W;
41331
- const body = word.map((line) => PAD + line);
41332
- const rule = PAD + ZINC_FAINT("\u2500".repeat(contentW));
41333
- const metaInner = ZINC_SOFT(TAGLINE) + " " + ZINC_FAINT("\xB7") + " " + INDIGO_SOFT(`v${VERSION}`);
41334
- const metaVis = stripAnsi(metaInner).length;
41335
- const metaPad = Math.max(0, Math.floor((contentW - metaVis) / 2));
41336
- const meta3 = PAD + " ".repeat(metaPad) + metaInner;
41337
- return [rule, ...body, rule, meta3].join(`
41496
+ const title = composeTitleLine(phase, mode);
41497
+ const rule = PAD + ZINC_SOFT("\xB7") + " " + ZINC_FAINT("\u2500".repeat(RULE_W - 2));
41498
+ const meta3 = composeMetaLine();
41499
+ return [title, rule, meta3].join(`
41338
41500
  `);
41339
41501
  }
41340
41502
  function canAnimate() {
@@ -41884,7 +42046,7 @@ if (typeof Bun === "undefined") {
41884
42046
  `);
41885
42047
  process.exit(1);
41886
42048
  }
41887
- var pkgVersion = JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf-8")).version;
42049
+ var pkgVersion = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8")).version;
41888
42050
  var program2 = new Command;
41889
42051
  program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
41890
42052
 
@@ -42035,6 +42197,7 @@ registerPerfCommand(program2);
42035
42197
  registerCompletionCommand(program2);
42036
42198
  async function main() {
42037
42199
  try {
42200
+ ensureWorkspaceContext();
42038
42201
  const hasArgs = process.argv.slice(2).length > 0;
42039
42202
  if (hasArgs) {
42040
42203
  await program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/hoox-cli",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@hoox-sh/hoox-cli)",
5
5
  "type": "module",
6
6
  "bin": {