@hoox-sh/hoox-cli 0.11.1 → 0.11.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/bin/hoox.js +14 -0
- package/dist/index.js +360 -120
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.
|
|
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.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- **Repair & Recovery** (`hoox repair`): Comprehensive system check, guided rebuild, per-component repair.
|
|
25
25
|
- **Top-level Secrets & Keys**: `hoox secrets` and `hoox keys` for Cloudflare Worker secrets and internal auth keys.
|
|
26
26
|
- **Unified Dashboard** (`hoox dashboard dev | deploy`): Start the dev server or build & deploy the Next.js dashboard.
|
|
27
|
-
- **Interactive menu & TUI**: Running `hoox` with no arguments opens the interactive menu
|
|
27
|
+
- **Interactive menu & TUI**: Running `hoox` with **no arguments**: if `wrangler.jsonc` is missing, auto-runs `onboard` (or `onboard --resume` when `.wizard-state.json` exists); otherwise opens the interactive menu. `hoox tui` launches the full OpenTUI operations center.
|
|
28
28
|
- **Operator security plane** (`hoox doctor`, `hoox tunnel`): Path/TUI hygiene, fail-closed remote transport (Bearer + Access), and private-ingress checks.
|
|
29
29
|
- **Modern-minimal output**: Zinc/indigo palette, completion footer with “next step” suggestions, “did you mean …” for typos, custom help formatter, `--no-color` / `NO_COLOR` / `TERM=dumb`, compact `formatNumber` / `formatBytes`. Shared `format*` helpers available to plugin authors.
|
|
30
30
|
- **Shell Completions**: bash, zsh, and fish completion scripts via `hoox completion <shell>`.
|
|
@@ -455,7 +455,7 @@ The CI pipeline runs in this order:
|
|
|
455
455
|
|
|
456
456
|
1. `bun run lint` — ESLint check
|
|
457
457
|
2. `bun run typecheck` — TypeScript check (`tsc --noEmit`)
|
|
458
|
-
3. `bun test packages/cli --coverage` — Run tests with coverage (80%
|
|
458
|
+
3. `bun test packages/cli --coverage` — Run tests with coverage (~80%+ overall; `src/` ~95%)
|
|
459
459
|
4. `bun run build` — TypeScript build check
|
|
460
460
|
|
|
461
461
|
## Project Structure
|
package/bin/hoox.js
CHANGED
|
@@ -24,6 +24,20 @@
|
|
|
24
24
|
* when the module is loaded as a side effect from here.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
// Runtime guard before any dynamic import: Node users (wrong shebang, or
|
|
28
|
+
// `node bin/hoox.js`) get a clear message instead of ESM resolution stacks.
|
|
29
|
+
// The same check lives in src/index.ts for direct source entry; this one
|
|
30
|
+
// covers the bin path when Node loads this file without Bun globals.
|
|
31
|
+
if (typeof globalThis.Bun === "undefined") {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
"Error: the Hoox CLI requires Bun >= 1.2 to run.\n" +
|
|
34
|
+
" Install Bun: curl -fsSL https://bun.sh | bash\n" +
|
|
35
|
+
" Then run: bunx hoox <command>\n" +
|
|
36
|
+
" (npm install -g / node bin/hoox.js will not work.)\n"
|
|
37
|
+
);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
import { existsSync, statSync, readdirSync } from "node:fs";
|
|
28
42
|
import { fileURLToPath } from "node:url";
|
|
29
43
|
import { dirname, resolve, join } from "node:path";
|
package/dist/index.js
CHANGED
|
@@ -14719,6 +14719,7 @@ var init_types = __esm(() => {
|
|
|
14719
14719
|
price: exports_external.number().positive().finite().optional(),
|
|
14720
14720
|
leverage: exports_external.number().int().positive().max(125).optional(),
|
|
14721
14721
|
test: exports_external.boolean().optional(),
|
|
14722
|
+
idempotencyKey: exports_external.string().min(1).max(512).optional(),
|
|
14722
14723
|
queuedAt: exports_external.string().min(1).max(64)
|
|
14723
14724
|
}).strict();
|
|
14724
14725
|
TradeSignalSchema = exports_external.object({
|
|
@@ -14753,7 +14754,7 @@ var init_types = __esm(() => {
|
|
|
14753
14754
|
});
|
|
14754
14755
|
|
|
14755
14756
|
// ../shared/src/kvKeys.ts
|
|
14756
|
-
var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage", KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size", KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent", KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent", KV_TRADE_KILL_SWITCH = "trade:kill_switch", KV_TRADE_ROUTING = "trade:routing", KV_BOT_ENABLED = "bot:enabled", KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id", KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution", KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error", KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled", KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips", KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode", KV_EMAIL_SCAN_SUBJECT = "email:scan_subject", KV_EMAIL_USE_IMAP = "email:use_imap", KV_EMAIL_COIN_PATTERN = "email:coin_pattern", KV_EMAIL_ACTION_PATTERN = "email:action_pattern", KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier", KV_AGENT_CONFIG = "agent:config", KV_AGENT_OPENAI_KEY = "agent:openai_key", KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key", KV_AGENT_GOOGLE_KEY = "agent:google_key", KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary", KV_HEALTH_CHECK = "health_check", KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check", QUEUE_MODE_FAILOVER = "queue_failover", QUEUE_MODE_EVERYWHERE = "queue_everywhere", QUEUE_MODE_DISABLED = "queue_disabled", KVKeys;
|
|
14757
|
+
var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage", KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size", KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent", KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent", KV_TRADE_KILL_SWITCH = "trade:kill_switch", KV_TRADE_ROUTING = "trade:routing", KV_BOT_ENABLED = "bot:enabled", KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id", KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution", KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error", KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled", KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips", KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode", KV_EMAIL_SCAN_SUBJECT = "email:scan_subject", KV_EMAIL_USE_IMAP = "email:use_imap", KV_EMAIL_COIN_PATTERN = "email:coin_pattern", KV_EMAIL_ACTION_PATTERN = "email:action_pattern", KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier", KV_AGENT_CONFIG = "agent:config", KV_AGENT_OPENAI_KEY = "agent:openai_key", KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key", KV_AGENT_GOOGLE_KEY = "agent:google_key", KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary", KV_GLOBAL_KILL_SWITCH = "global:kill_switch", KV_HEALTH_CHECK = "health_check", KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check", QUEUE_MODE_FAILOVER = "queue_failover", QUEUE_MODE_EVERYWHERE = "queue_everywhere", QUEUE_MODE_DISABLED = "queue_disabled", KVKeys;
|
|
14757
14758
|
var init_kvKeys = __esm(() => {
|
|
14758
14759
|
KVKeys = {
|
|
14759
14760
|
KV_TRADE_DEFAULT_LEVERAGE,
|
|
@@ -14779,6 +14780,7 @@ var init_kvKeys = __esm(() => {
|
|
|
14779
14780
|
KV_AGENT_ANTHROPIC_KEY,
|
|
14780
14781
|
KV_AGENT_GOOGLE_KEY,
|
|
14781
14782
|
KV_DASHBOARD_AI_HEALTH_SUMMARY,
|
|
14783
|
+
KV_GLOBAL_KILL_SWITCH,
|
|
14782
14784
|
KV_HEALTH_CHECK,
|
|
14783
14785
|
KV_HOUSEKEEPING_LAST_CHECK,
|
|
14784
14786
|
QUEUE_MODE_FAILOVER,
|
|
@@ -14787,6 +14789,54 @@ var init_kvKeys = __esm(() => {
|
|
|
14787
14789
|
};
|
|
14788
14790
|
});
|
|
14789
14791
|
|
|
14792
|
+
// ../shared/src/kill-switch.ts
|
|
14793
|
+
function isTruthyKillSwitchFlag(value) {
|
|
14794
|
+
if (value == null)
|
|
14795
|
+
return false;
|
|
14796
|
+
const v = value.trim().toLowerCase();
|
|
14797
|
+
return v === "true" || v === "1" || v === "yes" || v === "on";
|
|
14798
|
+
}
|
|
14799
|
+
async function checkKillSwitch(kv, options) {
|
|
14800
|
+
const onMissingKv = options?.onMissingKv ?? "open";
|
|
14801
|
+
const onReadError = options?.onReadError ?? "closed";
|
|
14802
|
+
if (!kv) {
|
|
14803
|
+
if (onMissingKv === "closed") {
|
|
14804
|
+
return { enabled: true, source: "missing_kv" };
|
|
14805
|
+
}
|
|
14806
|
+
return { enabled: false };
|
|
14807
|
+
}
|
|
14808
|
+
try {
|
|
14809
|
+
const values = await Promise.all(KILL_SWITCH_KEYS.map(async (key) => ({
|
|
14810
|
+
key,
|
|
14811
|
+
value: await kv.get(key)
|
|
14812
|
+
})));
|
|
14813
|
+
for (const { key, value } of values) {
|
|
14814
|
+
if (isTruthyKillSwitchFlag(value)) {
|
|
14815
|
+
return { enabled: true, source: key };
|
|
14816
|
+
}
|
|
14817
|
+
}
|
|
14818
|
+
return { enabled: false };
|
|
14819
|
+
} catch (error51) {
|
|
14820
|
+
const errorStr = String(error51);
|
|
14821
|
+
if (onReadError === "closed") {
|
|
14822
|
+
return { enabled: true, source: "read_error", error: errorStr };
|
|
14823
|
+
}
|
|
14824
|
+
return { enabled: false, error: errorStr };
|
|
14825
|
+
}
|
|
14826
|
+
}
|
|
14827
|
+
async function isTradingPaused(kv, options) {
|
|
14828
|
+
const result = await checkKillSwitch(kv, options);
|
|
14829
|
+
return result.enabled;
|
|
14830
|
+
}
|
|
14831
|
+
var KILL_SWITCH_ACTIVE_PREFIX = "KILL_SWITCH_ACTIVE", KILL_SWITCH_KEYS;
|
|
14832
|
+
var init_kill_switch = __esm(() => {
|
|
14833
|
+
init_kvKeys();
|
|
14834
|
+
KILL_SWITCH_KEYS = [
|
|
14835
|
+
KV_TRADE_KILL_SWITCH,
|
|
14836
|
+
KV_GLOBAL_KILL_SWITCH
|
|
14837
|
+
];
|
|
14838
|
+
});
|
|
14839
|
+
|
|
14790
14840
|
// ../shared/src/errors.ts
|
|
14791
14841
|
function toError(err, fallback = "Unknown error") {
|
|
14792
14842
|
if (err instanceof Error)
|
|
@@ -15908,7 +15958,11 @@ var init_config = __esm(() => {
|
|
|
15908
15958
|
|
|
15909
15959
|
// ../shared/src/operator-transport.ts
|
|
15910
15960
|
function stripTrailingSlashes(url2) {
|
|
15911
|
-
|
|
15961
|
+
let end = url2.length;
|
|
15962
|
+
while (end > 0 && url2.charCodeAt(end - 1) === 47) {
|
|
15963
|
+
end -= 1;
|
|
15964
|
+
}
|
|
15965
|
+
return end === url2.length ? url2 : url2.slice(0, end);
|
|
15912
15966
|
}
|
|
15913
15967
|
function parseTransport(raw) {
|
|
15914
15968
|
if (!raw)
|
|
@@ -15955,7 +16009,7 @@ function hasOperatorClientCredentials(profile) {
|
|
|
15955
16009
|
return Boolean(profile.accessClientId && profile.accessClientSecret);
|
|
15956
16010
|
}
|
|
15957
16011
|
function operatorUrl(profile, path2) {
|
|
15958
|
-
const base = profile.apiBase
|
|
16012
|
+
const base = stripTrailingSlashes(profile.apiBase);
|
|
15959
16013
|
const p = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
15960
16014
|
return `${base}${p}`;
|
|
15961
16015
|
}
|
|
@@ -22006,6 +22060,8 @@ __export(exports_src, {
|
|
|
22006
22060
|
kvManifestFromDashboardManifests: () => kvManifestFromDashboardManifests,
|
|
22007
22061
|
isWithinHooxHome: () => isWithinHooxHome,
|
|
22008
22062
|
isUnixModeGroupOrWorldAccessible: () => isUnixModeGroupOrWorldAccessible,
|
|
22063
|
+
isTruthyKillSwitchFlag: () => isTruthyKillSwitchFlag,
|
|
22064
|
+
isTradingPaused: () => isTradingPaused,
|
|
22009
22065
|
isHooxSetupRoot: () => isHooxSetupRoot,
|
|
22010
22066
|
isDashboardSectionFlatKv: () => isDashboardSectionFlatKv,
|
|
22011
22067
|
isDashboardSectionEditable: () => isDashboardSectionEditable,
|
|
@@ -22064,6 +22120,7 @@ __export(exports_src, {
|
|
|
22064
22120
|
createJsonResponse: () => createJsonResponse,
|
|
22065
22121
|
createErrorResponse: () => createErrorResponse,
|
|
22066
22122
|
createCronHandler: () => createCronHandler,
|
|
22123
|
+
checkKillSwitch: () => checkKillSwitch,
|
|
22067
22124
|
buildOperatorAuthHeaders: () => buildOperatorAuthHeaders,
|
|
22068
22125
|
buildDashboardKvKey: () => buildDashboardKvKey,
|
|
22069
22126
|
authenticatedServiceFetch: () => authenticatedServiceFetch,
|
|
@@ -22090,6 +22147,8 @@ __export(exports_src, {
|
|
|
22090
22147
|
PRESETS: () => PRESETS,
|
|
22091
22148
|
LogLevelColor: () => LogLevelColor,
|
|
22092
22149
|
KVKeys: () => KVKeys,
|
|
22150
|
+
KILL_SWITCH_KEYS: () => KILL_SWITCH_KEYS,
|
|
22151
|
+
KILL_SWITCH_ACTIVE_PREFIX: () => KILL_SWITCH_ACTIVE_PREFIX,
|
|
22093
22152
|
InternalAuthKeyFields: () => InternalAuthKeyFields,
|
|
22094
22153
|
INTEGRATIONS: () => INTEGRATIONS,
|
|
22095
22154
|
HOOX_DIR_MODE: () => HOOX_DIR_MODE,
|
|
@@ -22127,6 +22186,7 @@ var init_src = __esm(() => {
|
|
|
22127
22186
|
init_legal();
|
|
22128
22187
|
init_types();
|
|
22129
22188
|
init_kvKeys();
|
|
22189
|
+
init_kill_switch();
|
|
22130
22190
|
init_errors3();
|
|
22131
22191
|
init_exchanges();
|
|
22132
22192
|
init_kvUtils();
|
|
@@ -25007,7 +25067,7 @@ class ConfigService {
|
|
|
25007
25067
|
const e = result.error;
|
|
25008
25068
|
switch (e.code) {
|
|
25009
25069
|
case "not-found":
|
|
25010
|
-
throw new Error(e.triedFallback ? `Config file not found: ${e.path}. Also tried: ${e.triedFallback}. Run 'hoox
|
|
25070
|
+
throw new Error(e.triedFallback ? `Config file not found: ${e.path}. Also tried: ${e.triedFallback}. Run 'hoox onboard' (or 'hoox init') to create one.` : `Config file not found: ${e.path}. Run 'hoox onboard' (or 'hoox init') to create one.`);
|
|
25011
25071
|
case "invalid-jsonc":
|
|
25012
25072
|
throw new Error(`Invalid JSONC in ${e.path}:
|
|
25013
25073
|
${e.errors.map((m3) => ` - ${m3}`).join(`
|
|
@@ -25831,6 +25891,7 @@ class UpdateService {
|
|
|
25831
25891
|
}
|
|
25832
25892
|
async checkAndPromptUpdate(options) {
|
|
25833
25893
|
const silent = options?.silent === true;
|
|
25894
|
+
const yes = options?.yes === true;
|
|
25834
25895
|
try {
|
|
25835
25896
|
const versionCheck = await this.prereqs.checkWranglerVersion();
|
|
25836
25897
|
if (!versionCheck.outdated) {
|
|
@@ -25840,6 +25901,9 @@ class UpdateService {
|
|
|
25840
25901
|
}
|
|
25841
25902
|
return { updated: false };
|
|
25842
25903
|
}
|
|
25904
|
+
if (silent && !yes) {
|
|
25905
|
+
return { updated: false };
|
|
25906
|
+
}
|
|
25843
25907
|
const current2 = versionCheck.current ?? "unknown";
|
|
25844
25908
|
const minimum = versionCheck.minimum ?? "unknown";
|
|
25845
25909
|
const latest2 = await this.checkLatestVersion();
|
|
@@ -26080,7 +26144,7 @@ var exports_schema_service = {};
|
|
|
26080
26144
|
__export(exports_schema_service, {
|
|
26081
26145
|
SchemaService: () => SchemaService
|
|
26082
26146
|
});
|
|
26083
|
-
import { readFileSync as readFileSync5, existsSync as
|
|
26147
|
+
import { readFileSync as readFileSync5, existsSync as existsSync11 } from "fs";
|
|
26084
26148
|
import { resolve as resolve4 } from "path";
|
|
26085
26149
|
|
|
26086
26150
|
class SchemaService {
|
|
@@ -26114,7 +26178,7 @@ class SchemaService {
|
|
|
26114
26178
|
const workerWranglerPath = resolve4(workersDir, name, "wrangler.jsonc");
|
|
26115
26179
|
const devVarsPath = resolve4(workersDir, name, ".dev.vars");
|
|
26116
26180
|
const errors4 = [];
|
|
26117
|
-
const isHooxProject =
|
|
26181
|
+
const isHooxProject = existsSync11(resolve4(this.projectRoot, "wrangler.jsonc")) || existsSync11(resolve4(this.projectRoot, "workers"));
|
|
26118
26182
|
try {
|
|
26119
26183
|
const wranglerContent = readFileSync5(workerWranglerPath, "utf-8");
|
|
26120
26184
|
errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
|
|
@@ -28919,7 +28983,12 @@ function captureHelpEvent(cmd, eventName) {
|
|
|
28919
28983
|
// src/utils/completion.ts
|
|
28920
28984
|
var SUGGEST_NEXT = {
|
|
28921
28985
|
init: { command: "hoox setup", reason: "provision infrastructure" },
|
|
28922
|
-
|
|
28986
|
+
onboard: { command: "hoox check setup", reason: "verify bootstrap" },
|
|
28987
|
+
setup: { command: "hoox check setup", reason: "verify infrastructure" },
|
|
28988
|
+
"check setup": {
|
|
28989
|
+
command: "hoox deploy all",
|
|
28990
|
+
reason: "ship workers to Cloudflare"
|
|
28991
|
+
},
|
|
28923
28992
|
"deploy all": { command: "hoox check health", reason: "verify the deploy" },
|
|
28924
28993
|
"check health": {
|
|
28925
28994
|
command: "hoox monitor trades",
|
|
@@ -28954,6 +29023,7 @@ function getCmdPath(cmd) {
|
|
|
28954
29023
|
// src/commands/init/init-command.ts
|
|
28955
29024
|
init_dist4();
|
|
28956
29025
|
init_src();
|
|
29026
|
+
import * as fs2 from "fs";
|
|
28957
29027
|
|
|
28958
29028
|
// src/services/cloudflare/index.ts
|
|
28959
29029
|
init_cloudflare_service();
|
|
@@ -29020,21 +29090,32 @@ class CLIProvisioner {
|
|
|
29020
29090
|
|
|
29021
29091
|
// src/commands/init/init-command.ts
|
|
29022
29092
|
async function verifyRepoRoot() {
|
|
29023
|
-
const rootConfig = Bun.file("wrangler.jsonc");
|
|
29024
29093
|
const cliPackage = Bun.file("packages/cli/package.json");
|
|
29025
|
-
const
|
|
29026
|
-
|
|
29027
|
-
|
|
29094
|
+
const wranglerExample = Bun.file("wrangler.jsonc.example");
|
|
29095
|
+
const wranglerConfig = Bun.file("wrangler.jsonc");
|
|
29096
|
+
const gitmodules = Bun.file(".gitmodules");
|
|
29097
|
+
const [
|
|
29098
|
+
cliPackageExists,
|
|
29099
|
+
wranglerExampleExists,
|
|
29100
|
+
wranglerConfigExists,
|
|
29101
|
+
gitmodulesExists
|
|
29102
|
+
] = await Promise.all([
|
|
29103
|
+
cliPackage.exists(),
|
|
29104
|
+
wranglerExample.exists(),
|
|
29105
|
+
wranglerConfig.exists(),
|
|
29106
|
+
gitmodules.exists()
|
|
29028
29107
|
]);
|
|
29029
|
-
|
|
29108
|
+
const workersDirExists = fs2.existsSync("workers");
|
|
29109
|
+
const hasRepoSignal = wranglerExampleExists || wranglerConfigExists || workersDirExists || gitmodulesExists;
|
|
29110
|
+
if (cliPackageExists && hasRepoSignal)
|
|
29030
29111
|
return;
|
|
29031
29112
|
const reasons = [];
|
|
29032
|
-
if (!rootConfigExists) {
|
|
29033
|
-
reasons.push("no root `wrangler.jsonc` found in the current directory");
|
|
29034
|
-
}
|
|
29035
29113
|
if (!cliPackageExists) {
|
|
29036
29114
|
reasons.push("no `packages/cli` workspace found (this is not the hoox monorepo)");
|
|
29037
29115
|
}
|
|
29116
|
+
if (cliPackageExists && !hasRepoSignal) {
|
|
29117
|
+
reasons.push("missing Hoox repo markers (expected one of: `wrangler.jsonc.example`, `wrangler.jsonc`, `workers/`, or `.gitmodules`)");
|
|
29118
|
+
}
|
|
29038
29119
|
const cloneHint = " Clone first: git clone --recursive " + "https://github.com/hoox-sh/hoox.git";
|
|
29039
29120
|
throw new CLIError(`Not inside a Hoox repository root.
|
|
29040
29121
|
` + reasons.join(`
|
|
@@ -29207,7 +29288,10 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29207
29288
|
if (error51) {
|
|
29208
29289
|
formatError2(new CLIError(error51, 1 /* ERROR */), globalOpts);
|
|
29209
29290
|
process.exitCode = 1 /* ERROR */;
|
|
29291
|
+
return;
|
|
29210
29292
|
}
|
|
29293
|
+
process.env.CLOUDFLARE_API_TOKEN = token;
|
|
29294
|
+
process.env.CLOUDFLARE_ACCOUNT_ID = account;
|
|
29211
29295
|
if (!globalOpts.quiet) {
|
|
29212
29296
|
formatSuccess("Cloudflare API token validated", globalOpts);
|
|
29213
29297
|
}
|
|
@@ -29245,6 +29329,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29245
29329
|
if (isCancel(resumed)) {
|
|
29246
29330
|
cancel("Setup cancelled.");
|
|
29247
29331
|
process.exitCode = 0;
|
|
29332
|
+
return;
|
|
29248
29333
|
}
|
|
29249
29334
|
if (!resumed) {
|
|
29250
29335
|
engine = null;
|
|
@@ -29255,18 +29340,22 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29255
29340
|
engine = new WizardEngine;
|
|
29256
29341
|
}
|
|
29257
29342
|
const skipRiskWarning = options.acceptRisk;
|
|
29258
|
-
if (
|
|
29259
|
-
|
|
29260
|
-
|
|
29261
|
-
|
|
29262
|
-
|
|
29263
|
-
|
|
29264
|
-
|
|
29265
|
-
|
|
29266
|
-
|
|
29267
|
-
|
|
29268
|
-
|
|
29269
|
-
|
|
29343
|
+
if (engine.getCurrentStep().id === "PREREQUISITES") {
|
|
29344
|
+
if (!skipRiskWarning) {
|
|
29345
|
+
const accepted = await confirm({
|
|
29346
|
+
message: "Hoox connects to live trading exchanges and can execute real trades with real money. By continuing, you acknowledge that you are solely responsible for all trading activity and accept the risk of financial loss. Do you accept these terms?",
|
|
29347
|
+
initialValue: false
|
|
29348
|
+
});
|
|
29349
|
+
if (isCancel(accepted)) {
|
|
29350
|
+
cancel("Setup cancelled.");
|
|
29351
|
+
process.exitCode = 0;
|
|
29352
|
+
return;
|
|
29353
|
+
}
|
|
29354
|
+
if (!accepted) {
|
|
29355
|
+
outro("Setup cancelled. See DISCLAIMER.md for full terms.");
|
|
29356
|
+
process.exitCode = 0;
|
|
29357
|
+
return;
|
|
29358
|
+
}
|
|
29270
29359
|
}
|
|
29271
29360
|
engine.execute({ checksPassed: true });
|
|
29272
29361
|
await saveState(engine);
|
|
@@ -29287,6 +29376,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29287
29376
|
if (isCancel(apiToken)) {
|
|
29288
29377
|
cancel("Setup cancelled.");
|
|
29289
29378
|
process.exitCode = 0;
|
|
29379
|
+
return;
|
|
29290
29380
|
}
|
|
29291
29381
|
log.step("Validating Cloudflare API token...");
|
|
29292
29382
|
const tokenError = await validateApiToken(cf, apiToken);
|
|
@@ -29295,6 +29385,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29295
29385
|
log.warn("Please check your token and try again.");
|
|
29296
29386
|
} else {
|
|
29297
29387
|
tokenValid = true;
|
|
29388
|
+
process.env.CLOUDFLARE_API_TOKEN = apiToken;
|
|
29298
29389
|
formatSuccess("Cloudflare API token validated", globalOpts);
|
|
29299
29390
|
}
|
|
29300
29391
|
}
|
|
@@ -29315,6 +29406,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29315
29406
|
if (isCancel(accountResult)) {
|
|
29316
29407
|
cancel("Setup cancelled.");
|
|
29317
29408
|
process.exitCode = 0;
|
|
29409
|
+
return;
|
|
29318
29410
|
}
|
|
29319
29411
|
const secretStoreResult = await text({
|
|
29320
29412
|
message: "Cloudflare Secret Store ID:",
|
|
@@ -29324,6 +29416,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29324
29416
|
if (isCancel(secretStoreResult)) {
|
|
29325
29417
|
cancel("Setup cancelled.");
|
|
29326
29418
|
process.exitCode = 0;
|
|
29419
|
+
return;
|
|
29327
29420
|
}
|
|
29328
29421
|
const prefixResult = await text({
|
|
29329
29422
|
message: "Subdomain prefix:",
|
|
@@ -29338,7 +29431,9 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29338
29431
|
if (isCancel(prefixResult)) {
|
|
29339
29432
|
cancel("Setup cancelled.");
|
|
29340
29433
|
process.exitCode = 0;
|
|
29434
|
+
return;
|
|
29341
29435
|
}
|
|
29436
|
+
process.env.CLOUDFLARE_ACCOUNT_ID = accountResult;
|
|
29342
29437
|
engine.execute({
|
|
29343
29438
|
apiToken,
|
|
29344
29439
|
accountId: accountResult,
|
|
@@ -29371,6 +29466,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29371
29466
|
if (isCancel(presetChoice)) {
|
|
29372
29467
|
cancel("Setup cancelled.");
|
|
29373
29468
|
process.exitCode = 0;
|
|
29469
|
+
return;
|
|
29374
29470
|
}
|
|
29375
29471
|
engine.execute({ preset: presetChoice });
|
|
29376
29472
|
await saveState(engine);
|
|
@@ -29394,6 +29490,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29394
29490
|
if (isCancel(shouldProvision)) {
|
|
29395
29491
|
cancel("Setup cancelled.");
|
|
29396
29492
|
process.exitCode = 0;
|
|
29493
|
+
return;
|
|
29397
29494
|
}
|
|
29398
29495
|
if (shouldProvision) {
|
|
29399
29496
|
log.step("Provisioning infrastructure...");
|
|
@@ -29443,6 +29540,9 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29443
29540
|
process.exitCode = 0;
|
|
29444
29541
|
}
|
|
29445
29542
|
});
|
|
29543
|
+
if (isCancel(collected)) {
|
|
29544
|
+
return;
|
|
29545
|
+
}
|
|
29446
29546
|
collectedSecrets[key] = {};
|
|
29447
29547
|
for (const [secretName] of secretEntries) {
|
|
29448
29548
|
const val = collected[secretName];
|
|
@@ -29470,6 +29570,7 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29470
29570
|
if (isCancel(shouldDeploy)) {
|
|
29471
29571
|
cancel("Setup cancelled.");
|
|
29472
29572
|
process.exitCode = 0;
|
|
29573
|
+
return;
|
|
29473
29574
|
}
|
|
29474
29575
|
if (shouldDeploy) {
|
|
29475
29576
|
log.step("Deploying workers...");
|
|
@@ -29485,7 +29586,8 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29485
29586
|
}
|
|
29486
29587
|
// src/services/setup/setup-service.ts
|
|
29487
29588
|
import {
|
|
29488
|
-
|
|
29589
|
+
chmodSync as chmodSync3,
|
|
29590
|
+
existsSync as existsSync7,
|
|
29489
29591
|
mkdirSync as mkdirSync3,
|
|
29490
29592
|
readFileSync as readFileSync3,
|
|
29491
29593
|
statSync as statSync2,
|
|
@@ -29545,6 +29647,25 @@ var KEYS_DIR = ".keys";
|
|
|
29545
29647
|
function escapeRegex2(s) {
|
|
29546
29648
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
29547
29649
|
}
|
|
29650
|
+
function parseEnvFile(text2) {
|
|
29651
|
+
const map2 = new Map;
|
|
29652
|
+
for (const line of text2.split(`
|
|
29653
|
+
`)) {
|
|
29654
|
+
const trimmed = line.trim();
|
|
29655
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
29656
|
+
continue;
|
|
29657
|
+
const eq = trimmed.indexOf("=");
|
|
29658
|
+
if (eq <= 0)
|
|
29659
|
+
continue;
|
|
29660
|
+
map2.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
|
|
29661
|
+
}
|
|
29662
|
+
return map2;
|
|
29663
|
+
}
|
|
29664
|
+
function safeChmod(path4, mode) {
|
|
29665
|
+
try {
|
|
29666
|
+
chmodSync3(path4, mode);
|
|
29667
|
+
} catch {}
|
|
29668
|
+
}
|
|
29548
29669
|
|
|
29549
29670
|
class SetupService {
|
|
29550
29671
|
onProgress;
|
|
@@ -29573,35 +29694,53 @@ class SetupService {
|
|
|
29573
29694
|
TRADE_INTERNAL_KEY: internalKey,
|
|
29574
29695
|
API_SERVICE_KEY_BINDING: internalKey
|
|
29575
29696
|
};
|
|
29576
|
-
if (!
|
|
29697
|
+
if (!existsSync7(KEYS_DIR)) {
|
|
29577
29698
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
29578
29699
|
} else if (!statSync2(KEYS_DIR).isDirectory()) {
|
|
29579
29700
|
mkdirSync3(KEYS_DIR, { recursive: true });
|
|
29580
29701
|
}
|
|
29702
|
+
safeChmod(KEYS_DIR, 448);
|
|
29703
|
+
const setupEnvPath = join7(KEYS_DIR, "setup.env");
|
|
29581
29704
|
const envLines = Object.entries(keys).map(([k, v]) => `${k}=${v}`);
|
|
29582
|
-
await Bun.write(
|
|
29705
|
+
await Bun.write(setupEnvPath, `# Hoox Setup \u2014 Auto-Generated Keys
|
|
29583
29706
|
` + "# Generated by `hoox setup`. NEVER commit this file.\n" + `# Created: ${new Date().toISOString()}
|
|
29584
29707
|
|
|
29585
29708
|
` + envLines.join(`
|
|
29586
29709
|
`) + `
|
|
29587
29710
|
`);
|
|
29711
|
+
safeChmod(setupEnvPath, 384);
|
|
29588
29712
|
for (const [name, value] of Object.entries(keys)) {
|
|
29589
|
-
|
|
29713
|
+
const keyPath = join7(KEYS_DIR, `${name.toLowerCase()}.env`);
|
|
29714
|
+
await Bun.write(keyPath, `${name}=${value}
|
|
29590
29715
|
`);
|
|
29716
|
+
safeChmod(keyPath, 384);
|
|
29591
29717
|
}
|
|
29592
29718
|
let devVarsCount = 0;
|
|
29593
29719
|
for (const [worker, secretNames] of Object.entries(DEV_VARS_WORKER_KEYS)) {
|
|
29594
29720
|
const path4 = `workers/${workerFsDir(worker)}/.dev.vars`;
|
|
29721
|
+
const meshNames = secretNames;
|
|
29722
|
+
const meshSet = new Set(meshNames);
|
|
29723
|
+
const existing = existsSync7(path4) ? parseEnvFile(readFileSync3(path4, "utf-8")) : new Map;
|
|
29724
|
+
for (const secretName of meshNames) {
|
|
29725
|
+
existing.set(secretName, keys[secretName]);
|
|
29726
|
+
}
|
|
29595
29727
|
let content = `# .dev.vars \u2014 local secrets for ${worker}
|
|
29596
29728
|
`;
|
|
29597
29729
|
content += `# Generated by \`hoox setup\`. NEVER commit this file.
|
|
29598
29730
|
|
|
29599
29731
|
`;
|
|
29600
|
-
for (const secretName of
|
|
29601
|
-
content += `${secretName}=${
|
|
29732
|
+
for (const secretName of meshNames) {
|
|
29733
|
+
content += `${secretName}=${existing.get(secretName)}
|
|
29734
|
+
`;
|
|
29735
|
+
}
|
|
29736
|
+
for (const [k, v] of existing) {
|
|
29737
|
+
if (!meshSet.has(k)) {
|
|
29738
|
+
content += `${k}=${v}
|
|
29602
29739
|
`;
|
|
29740
|
+
}
|
|
29603
29741
|
}
|
|
29604
29742
|
await Bun.write(path4, content);
|
|
29743
|
+
safeChmod(path4, 384);
|
|
29605
29744
|
devVarsCount++;
|
|
29606
29745
|
}
|
|
29607
29746
|
this.onProgress({
|
|
@@ -29930,7 +30069,7 @@ class SetupService {
|
|
|
29930
30069
|
join7(dir, "pyproject.toml"),
|
|
29931
30070
|
join7(dir, "wrangler.jsonc")
|
|
29932
30071
|
];
|
|
29933
|
-
return markers.some((p3) =>
|
|
30072
|
+
return markers.some((p3) => existsSync7(p3));
|
|
29934
30073
|
}
|
|
29935
30074
|
async ensureWorkers() {
|
|
29936
30075
|
this.onProgress({
|
|
@@ -29975,7 +30114,7 @@ class SetupService {
|
|
|
29975
30114
|
step: "workers",
|
|
29976
30115
|
message: `${missing.length} worker(s) not cloned: ${missing.join(", ")}`
|
|
29977
30116
|
});
|
|
29978
|
-
if (!
|
|
30117
|
+
if (!existsSync7(gitmodulesPath)) {
|
|
29979
30118
|
this.onProgress({
|
|
29980
30119
|
type: "step-error",
|
|
29981
30120
|
step: "workers",
|
|
@@ -30371,7 +30510,7 @@ class SetupService {
|
|
|
30371
30510
|
} else {
|
|
30372
30511
|
steps.push({ step: "d1-schema", success: true, message: "Skipped" });
|
|
30373
30512
|
}
|
|
30374
|
-
if (keys) {
|
|
30513
|
+
if (!options.skipSecrets && keys) {
|
|
30375
30514
|
const reconcile = await this.reconcileSecrets(keys);
|
|
30376
30515
|
if (reconcile.warnings.length > 0) {
|
|
30377
30516
|
for (const w of reconcile.warnings) {
|
|
@@ -30379,20 +30518,25 @@ class SetupService {
|
|
|
30379
30518
|
}
|
|
30380
30519
|
}
|
|
30381
30520
|
}
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30385
|
-
|
|
30386
|
-
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30521
|
+
if (options.skipSecrets) {
|
|
30522
|
+
steps.push({ step: "secrets", success: true, message: "Skipped" });
|
|
30523
|
+
results.secrets = [];
|
|
30524
|
+
} else {
|
|
30525
|
+
const secretResults = await this.setSecrets(keys);
|
|
30526
|
+
results.secrets = secretResults;
|
|
30527
|
+
const secretOk = secretResults.filter((r2) => r2.ok).length;
|
|
30528
|
+
const secretFail = secretResults.filter((r2) => !r2.ok).length;
|
|
30529
|
+
steps.push({
|
|
30530
|
+
step: "secrets",
|
|
30531
|
+
success: secretFail === 0,
|
|
30532
|
+
message: `${secretOk} set, ${secretFail} failed`,
|
|
30533
|
+
details: {
|
|
30534
|
+
total: secretResults.length,
|
|
30535
|
+
ok: secretOk,
|
|
30536
|
+
failed: secretFail
|
|
30537
|
+
}
|
|
30538
|
+
});
|
|
30539
|
+
}
|
|
30396
30540
|
const dashResult = await this.rebuildDashboard(options.skipDashboard);
|
|
30397
30541
|
steps.push(dashResult);
|
|
30398
30542
|
if (keys) {
|
|
@@ -30497,12 +30641,27 @@ async function runOnboard(options, fmt) {
|
|
|
30497
30641
|
acceptRisk: options.acceptRisk
|
|
30498
30642
|
};
|
|
30499
30643
|
await runInitCommand(initOpts, { json: fmt.json, quiet: fmt.quiet }, isNonInteractive);
|
|
30644
|
+
if (process.exitCode && process.exitCode !== 0) {
|
|
30645
|
+
return;
|
|
30646
|
+
}
|
|
30647
|
+
if (options.token) {
|
|
30648
|
+
process.env.CLOUDFLARE_API_TOKEN = options.token;
|
|
30649
|
+
}
|
|
30650
|
+
if (options.account) {
|
|
30651
|
+
process.env.CLOUDFLARE_ACCOUNT_ID = options.account;
|
|
30652
|
+
}
|
|
30500
30653
|
if (!quiet) {
|
|
30501
30654
|
process.stdout.write(theme.heading(`
|
|
30502
30655
|
\u2501\u2501\u2501 Step 2/2: Setup (infrastructure bootstrap) \u2501\u2501\u2501
|
|
30503
30656
|
`));
|
|
30504
30657
|
}
|
|
30505
30658
|
const setupSvc = new SetupService;
|
|
30659
|
+
const authOk = await setupSvc.checkAuth();
|
|
30660
|
+
if (!authOk) {
|
|
30661
|
+
formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR */, undefined, false, "Run `wrangler login` interactively, or set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables for CI / non-interactive use."), fmt);
|
|
30662
|
+
process.exitCode = 1 /* ERROR */;
|
|
30663
|
+
return;
|
|
30664
|
+
}
|
|
30506
30665
|
const setupOpts = {
|
|
30507
30666
|
skipKeys: Boolean(options.skipKeys),
|
|
30508
30667
|
skipDb: Boolean(options.skipDb),
|
|
@@ -30514,14 +30673,14 @@ async function runOnboard(options, fmt) {
|
|
|
30514
30673
|
const result = await setupSvc.runAll(setupOpts);
|
|
30515
30674
|
if (result.success) {
|
|
30516
30675
|
if (!quiet) {
|
|
30517
|
-
formatSuccess("Bootstrap complete!
|
|
30676
|
+
formatSuccess("Bootstrap complete! Next: `hoox check setup` then `hoox deploy all`", fmt);
|
|
30518
30677
|
}
|
|
30519
30678
|
} else {
|
|
30520
|
-
formatError2(new CLIError("Bootstrap completed with issues. See logs above.", 1 /* ERROR
|
|
30679
|
+
formatError2(new CLIError("Bootstrap completed with issues. See logs above.", 1 /* ERROR */, undefined, false, "Retry with `hoox setup`, diagnose with `hoox check setup`, or run `hoox repair check`."), fmt);
|
|
30521
30680
|
process.exitCode = 1 /* ERROR */;
|
|
30522
30681
|
}
|
|
30523
30682
|
} catch (e) {
|
|
30524
|
-
formatError2(new CLIError(e instanceof Error ? e.message : String(e), 1 /* ERROR
|
|
30683
|
+
formatError2(new CLIError(e instanceof Error ? e.message : String(e), 1 /* ERROR */, undefined, false, "Retry with `hoox setup`, diagnose with `hoox check setup`, or run `hoox repair check`."), fmt);
|
|
30525
30684
|
process.exitCode = 1 /* ERROR */;
|
|
30526
30685
|
}
|
|
30527
30686
|
}
|
|
@@ -31039,7 +31198,7 @@ function renderDefaultSummary(results) {
|
|
|
31039
31198
|
init_errors4();
|
|
31040
31199
|
import {
|
|
31041
31200
|
statSync as statSync3,
|
|
31042
|
-
existsSync as
|
|
31201
|
+
existsSync as existsSync8,
|
|
31043
31202
|
readFileSync as readFileSync4,
|
|
31044
31203
|
writeFileSync as writeFileSync3,
|
|
31045
31204
|
renameSync
|
|
@@ -31474,7 +31633,7 @@ ${section}:
|
|
|
31474
31633
|
init_main();
|
|
31475
31634
|
function getDashboardBuildInfo(dashboardPath) {
|
|
31476
31635
|
const workerPath = resolve2(dashboardPath, ".open-next", "worker.js");
|
|
31477
|
-
if (!
|
|
31636
|
+
if (!existsSync8(workerPath)) {
|
|
31478
31637
|
return { exists: false };
|
|
31479
31638
|
}
|
|
31480
31639
|
const stats = statSync3(workerPath);
|
|
@@ -31817,7 +31976,7 @@ async function doUpdateInternalUrls(fmt) {
|
|
|
31817
31976
|
const prefix = global.subdomain_prefix ?? "hoox";
|
|
31818
31977
|
const workers = config2.listEnabledWorkers();
|
|
31819
31978
|
const filePath = resolve2(process.cwd(), "workers", "dashboard", "wrangler.jsonc");
|
|
31820
|
-
if (!
|
|
31979
|
+
if (!existsSync8(filePath)) {
|
|
31821
31980
|
formatError2(new CLIError("Dashboard wrangler.jsonc not found at workers/dashboard/wrangler.jsonc", 1 /* ERROR */), fmt);
|
|
31822
31981
|
process.exitCode = 1 /* ERROR */;
|
|
31823
31982
|
return;
|
|
@@ -33894,7 +34053,7 @@ EXAMPLES:
|
|
|
33894
34053
|
}
|
|
33895
34054
|
|
|
33896
34055
|
// src/commands/config/keys-subcommands.ts
|
|
33897
|
-
import { existsSync as
|
|
34056
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "fs";
|
|
33898
34057
|
init_theme();
|
|
33899
34058
|
function generateKey(bytes = 32) {
|
|
33900
34059
|
const buf = new Uint8Array(bytes);
|
|
@@ -33920,7 +34079,7 @@ EXAMPLES:
|
|
|
33920
34079
|
hoox config keys generate`).action(withErrorHandling(async (_2, cmd) => {
|
|
33921
34080
|
const opts = getFormatOptions(cmd);
|
|
33922
34081
|
const keysDir = ".keys";
|
|
33923
|
-
if (!
|
|
34082
|
+
if (!existsSync9(keysDir)) {
|
|
33924
34083
|
mkdirSync4(keysDir, { recursive: true });
|
|
33925
34084
|
}
|
|
33926
34085
|
const meshKey = generateKey();
|
|
@@ -33954,7 +34113,7 @@ EXAMPLES:
|
|
|
33954
34113
|
hoox config keys list`).action(withErrorHandling(async (_2, cmd) => {
|
|
33955
34114
|
const opts = getFormatOptions(cmd);
|
|
33956
34115
|
const keysDir = ".keys";
|
|
33957
|
-
if (!
|
|
34116
|
+
if (!existsSync9(keysDir)) {
|
|
33958
34117
|
process.stdout.write(`${theme.dim("No .keys/ directory found.")}
|
|
33959
34118
|
`);
|
|
33960
34119
|
return;
|
|
@@ -34595,7 +34754,7 @@ async function runSecretsChecks(secretsService, configService, cf) {
|
|
|
34595
34754
|
async function runDatabaseChecks(cf, configService) {
|
|
34596
34755
|
const checks3 = [];
|
|
34597
34756
|
const d1Worker = configService.getWorker("d1-worker");
|
|
34598
|
-
const dbName = d1Worker?.vars?.database_name ?? "
|
|
34757
|
+
const dbName = d1Worker?.vars?.database_name ?? "trade-data-db";
|
|
34599
34758
|
const d1ListResult = await cf.d1List();
|
|
34600
34759
|
if (d1ListResult.ok) {
|
|
34601
34760
|
const hasDb = d1ListResult.value.includes(dbName);
|
|
@@ -36220,7 +36379,7 @@ init_dist4();
|
|
|
36220
36379
|
// src/services/db/db-service.ts
|
|
36221
36380
|
init_config2();
|
|
36222
36381
|
init_cloudflare_service();
|
|
36223
|
-
import { existsSync as
|
|
36382
|
+
import { existsSync as existsSync10 } from "fs";
|
|
36224
36383
|
import { join as join9 } from "path";
|
|
36225
36384
|
var D1_WRANGLER_CONFIG_CANDIDATES = [
|
|
36226
36385
|
"workers/d1-worker/wrangler.jsonc",
|
|
@@ -36258,7 +36417,7 @@ class DbService {
|
|
|
36258
36417
|
if (vars?.database_name)
|
|
36259
36418
|
return vars.database_name;
|
|
36260
36419
|
} catch {}
|
|
36261
|
-
return "
|
|
36420
|
+
return "trade-data-db";
|
|
36262
36421
|
}
|
|
36263
36422
|
async apply(dbName, remote, schemaPath) {
|
|
36264
36423
|
const path6 = schemaPath ?? this.resolveDefaultSchemaPath();
|
|
@@ -36322,7 +36481,7 @@ class DbService {
|
|
|
36322
36481
|
if (this.wranglerConfigPath)
|
|
36323
36482
|
return this.wranglerConfigPath;
|
|
36324
36483
|
for (const candidate of D1_WRANGLER_CONFIG_CANDIDATES) {
|
|
36325
|
-
if (
|
|
36484
|
+
if (existsSync10(candidate))
|
|
36326
36485
|
return candidate;
|
|
36327
36486
|
}
|
|
36328
36487
|
return;
|
|
@@ -37417,8 +37576,7 @@ function registerSchemaCommand(program2) {
|
|
|
37417
37576
|
const svc = new SchemaService;
|
|
37418
37577
|
const manifest = svc.getManifest(worker);
|
|
37419
37578
|
if (!manifest) {
|
|
37420
|
-
|
|
37421
|
-
return;
|
|
37579
|
+
throw new CLIError(`Unknown worker "${worker}"`, 2 /* INVALID_USAGE */, undefined, false, `Known workers: ${svc.getWorkerNames().join(", ") || "(none)"}. Run: hoox schema list`);
|
|
37422
37580
|
}
|
|
37423
37581
|
const wranglerContent = generateWranglerJsonc(manifest);
|
|
37424
37582
|
const devVarsContent = generateDevVars(manifest);
|
|
@@ -37453,7 +37611,7 @@ import { spawn } from "child_process";
|
|
|
37453
37611
|
import { createRequire } from "module";
|
|
37454
37612
|
import { resolve as resolve6, dirname as dirname3, join as join10 } from "path";
|
|
37455
37613
|
import { fileURLToPath } from "url";
|
|
37456
|
-
import { existsSync as
|
|
37614
|
+
import { existsSync as existsSync12, realpathSync } from "fs";
|
|
37457
37615
|
import { homedir as homedir5 } from "os";
|
|
37458
37616
|
|
|
37459
37617
|
// src/services/perf/endpoint-resolver.ts
|
|
@@ -37517,7 +37675,7 @@ function collectTuiCandidates() {
|
|
|
37517
37675
|
return [...new Set(candidates)];
|
|
37518
37676
|
}
|
|
37519
37677
|
function pushPackageRootEntries(candidates, root) {
|
|
37520
|
-
if (!root || !
|
|
37678
|
+
if (!root || !existsSync12(root))
|
|
37521
37679
|
return;
|
|
37522
37680
|
candidates.push(join10(root, "dist", "main.js"), join10(root, "src", "main.tsx"), join10(root, "bin", "hoox-tui.js"), join10(root, "src", "main.ts"));
|
|
37523
37681
|
}
|
|
@@ -37545,7 +37703,7 @@ function pushFromWhich(candidates, binName) {
|
|
|
37545
37703
|
function resolveTUIEntry() {
|
|
37546
37704
|
const candidates = collectTuiCandidates();
|
|
37547
37705
|
for (const path6 of candidates) {
|
|
37548
|
-
if (
|
|
37706
|
+
if (existsSync12(path6))
|
|
37549
37707
|
return path6;
|
|
37550
37708
|
}
|
|
37551
37709
|
const runtime = resolveHooxRuntimeRoot();
|
|
@@ -37764,13 +37922,13 @@ import { homedir as homedir6 } from "os";
|
|
|
37764
37922
|
|
|
37765
37923
|
// src/services/runtime/runtime-service.ts
|
|
37766
37924
|
init_src();
|
|
37767
|
-
import { existsSync as
|
|
37925
|
+
import { existsSync as existsSync13, lstatSync as lstatSync2, readlinkSync, rmSync } from "fs";
|
|
37768
37926
|
import { mkdir } from "fs/promises";
|
|
37769
37927
|
import { join as join11 } from "path";
|
|
37770
37928
|
var DEFAULT_HOOX_RUNTIME_REPO = "https://github.com/hoox-sh/hoox.git";
|
|
37771
37929
|
function firstExisting(paths) {
|
|
37772
37930
|
for (const p3 of paths) {
|
|
37773
|
-
if (
|
|
37931
|
+
if (existsSync13(p3))
|
|
37774
37932
|
return p3;
|
|
37775
37933
|
}
|
|
37776
37934
|
return null;
|
|
@@ -37784,7 +37942,7 @@ function getRuntimeStatus(cwd = process.cwd()) {
|
|
|
37784
37942
|
repoPath,
|
|
37785
37943
|
runtime,
|
|
37786
37944
|
tuiEntry,
|
|
37787
|
-
repoPresent:
|
|
37945
|
+
repoPresent: existsSync13(repoPath),
|
|
37788
37946
|
isSetupRoot: isHooxSetupRoot(repoPath)
|
|
37789
37947
|
};
|
|
37790
37948
|
}
|
|
@@ -37806,7 +37964,7 @@ function isBrokenSymlink(path6) {
|
|
|
37806
37964
|
try {
|
|
37807
37965
|
if (!lstatSync2(path6).isSymbolicLink())
|
|
37808
37966
|
return false;
|
|
37809
|
-
return !
|
|
37967
|
+
return !existsSync13(path6);
|
|
37810
37968
|
} catch {
|
|
37811
37969
|
return false;
|
|
37812
37970
|
}
|
|
@@ -37819,7 +37977,7 @@ async function ensureGlobalRuntime(options = {}) {
|
|
|
37819
37977
|
await mkdir(hooxHome, { recursive: true });
|
|
37820
37978
|
let cloned = false;
|
|
37821
37979
|
if (!isHooxSetupRoot(repoPath)) {
|
|
37822
|
-
if (
|
|
37980
|
+
if (existsSync13(repoPath) || isBrokenSymlink(repoPath)) {
|
|
37823
37981
|
if (isBrokenSymlink(repoPath)) {
|
|
37824
37982
|
let target = "";
|
|
37825
37983
|
try {
|
|
@@ -37851,8 +38009,8 @@ ${clone2.stderr || clone2.stdout}`.trim());
|
|
|
37851
38009
|
if (!options.skipInstall) {
|
|
37852
38010
|
const nodeModules = join11(repoPath, "node_modules");
|
|
37853
38011
|
const tuiPkg = join11(repoPath, "packages", "tui", "package.json");
|
|
37854
|
-
const needsInstall = !
|
|
37855
|
-
if (cloned || needsInstall || !
|
|
38012
|
+
const needsInstall = !existsSync13(nodeModules) || existsSync13(tuiPkg) && !existsSync13(join11(repoPath, "packages", "tui", "node_modules")) && !existsSync13(join11(nodeModules, "@opentui"));
|
|
38013
|
+
if (cloned || needsInstall || !existsSync13(nodeModules)) {
|
|
37856
38014
|
log2(`Running bun install in ${repoPath}`);
|
|
37857
38015
|
const install = await runCommand(["bun", "install"], repoPath);
|
|
37858
38016
|
if (!install.ok) {
|
|
@@ -38529,7 +38687,31 @@ function sanitizeErrorMessage(message, secrets) {
|
|
|
38529
38687
|
out = out.replace(/([?&]key=)[^&\s]+/gi, "$1[redacted]");
|
|
38530
38688
|
return out;
|
|
38531
38689
|
}
|
|
38690
|
+
var PROBE_ALLOWED_HOSTS = new Set([
|
|
38691
|
+
"api.cloudflare.com",
|
|
38692
|
+
"api.openai.com",
|
|
38693
|
+
"api.anthropic.com",
|
|
38694
|
+
"generativelanguage.googleapis.com",
|
|
38695
|
+
"api.cognitive.microsoft.com"
|
|
38696
|
+
]);
|
|
38697
|
+
function assertProbeUrl(url2) {
|
|
38698
|
+
let parsed;
|
|
38699
|
+
try {
|
|
38700
|
+
parsed = new URL(url2);
|
|
38701
|
+
} catch {
|
|
38702
|
+
throw new Error(`Invalid probe URL: ${url2}`);
|
|
38703
|
+
}
|
|
38704
|
+
if (parsed.protocol !== "https:") {
|
|
38705
|
+
throw new Error(`Probe URL must be https: ${url2}`);
|
|
38706
|
+
}
|
|
38707
|
+
const host = parsed.hostname.toLowerCase();
|
|
38708
|
+
const allowed = PROBE_ALLOWED_HOSTS.has(host) || host.endsWith(".openai.azure.com") || host.endsWith(".cognitiveservices.azure.com");
|
|
38709
|
+
if (!allowed) {
|
|
38710
|
+
throw new Error(`Probe URL host not allowlisted: ${host}`);
|
|
38711
|
+
}
|
|
38712
|
+
}
|
|
38532
38713
|
async function timedFetch(url2, init, timeoutMs) {
|
|
38714
|
+
assertProbeUrl(url2);
|
|
38533
38715
|
const start = performance.now();
|
|
38534
38716
|
const response = await fetch(url2, {
|
|
38535
38717
|
...init,
|
|
@@ -38638,9 +38820,8 @@ async function probeAnthropic(apiKey, timeoutMs) {
|
|
|
38638
38820
|
return { status: "online", latencyMs };
|
|
38639
38821
|
}
|
|
38640
38822
|
if (response.status === 404) {
|
|
38641
|
-
const start = performance.now();
|
|
38642
38823
|
try {
|
|
38643
|
-
const msgRes = await
|
|
38824
|
+
const { response: msgRes, latencyMs: lat } = await timedFetch("https://api.anthropic.com/v1/messages", {
|
|
38644
38825
|
method: "POST",
|
|
38645
38826
|
headers: {
|
|
38646
38827
|
"x-api-key": apiKey,
|
|
@@ -38651,10 +38832,8 @@ async function probeAnthropic(apiKey, timeoutMs) {
|
|
|
38651
38832
|
model: "claude-sonnet-4-20250514",
|
|
38652
38833
|
max_tokens: 1,
|
|
38653
38834
|
messages: [{ role: "user", content: "ping" }]
|
|
38654
|
-
})
|
|
38655
|
-
|
|
38656
|
-
});
|
|
38657
|
-
const lat = Math.round(performance.now() - start);
|
|
38835
|
+
})
|
|
38836
|
+
}, timeoutMs);
|
|
38658
38837
|
if (msgRes.ok)
|
|
38659
38838
|
return { status: "online", latencyMs: lat };
|
|
38660
38839
|
if (msgRes.status === 401 || msgRes.status === 403) {
|
|
@@ -38730,7 +38909,9 @@ async function probeAzure(apiKey, timeoutMs) {
|
|
|
38730
38909
|
error: "AZURE_ENDPOINT missing"
|
|
38731
38910
|
};
|
|
38732
38911
|
}
|
|
38733
|
-
|
|
38912
|
+
let base = endpoint;
|
|
38913
|
+
while (base.endsWith("/"))
|
|
38914
|
+
base = base.slice(0, -1);
|
|
38734
38915
|
const url2 = `${base}/openai/models?api-version=2024-02-01`;
|
|
38735
38916
|
try {
|
|
38736
38917
|
const { response, latencyMs } = await timedFetch(url2, {
|
|
@@ -38985,18 +39166,21 @@ EXAMPLES:
|
|
|
38985
39166
|
}
|
|
38986
39167
|
// src/commands/pyne/pyne-command.ts
|
|
38987
39168
|
init_config2();
|
|
38988
|
-
import { existsSync as
|
|
39169
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8 } from "fs";
|
|
38989
39170
|
import { resolve as resolve7 } from "path";
|
|
38990
39171
|
init_errors4();
|
|
38991
|
-
var PYNE_WORKER_DIR = resolve7(process.cwd(), "workers/pyne-worker");
|
|
38992
39172
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
39173
|
+
function getPyneWorkerDir() {
|
|
39174
|
+
return resolve7(process.cwd(), "workers/pyne-worker");
|
|
39175
|
+
}
|
|
38993
39176
|
function requirePyneDir() {
|
|
38994
|
-
|
|
38995
|
-
|
|
39177
|
+
const dir = getPyneWorkerDir();
|
|
39178
|
+
if (!existsSync14(dir)) {
|
|
39179
|
+
throw new CLIError(`pyne-worker directory not found: ${dir}
|
|
38996
39180
|
` + ` Clone it with: hoox clone pyne-worker
|
|
38997
39181
|
` + ` Or: git submodule update --init workers/pyne-worker`, 1 /* ERROR */);
|
|
38998
39182
|
}
|
|
38999
|
-
return
|
|
39183
|
+
return dir;
|
|
39000
39184
|
}
|
|
39001
39185
|
function loadDevVars(workerDir) {
|
|
39002
39186
|
const out = {};
|
|
@@ -39006,7 +39190,7 @@ function loadDevVars(workerDir) {
|
|
|
39006
39190
|
];
|
|
39007
39191
|
for (const filePath of candidates) {
|
|
39008
39192
|
try {
|
|
39009
|
-
if (!
|
|
39193
|
+
if (!existsSync14(filePath))
|
|
39010
39194
|
continue;
|
|
39011
39195
|
const content = readFileSync8(filePath, "utf-8");
|
|
39012
39196
|
for (const line of content.split(`
|
|
@@ -39064,11 +39248,33 @@ function resolveApiKey(explicit, workerDir) {
|
|
|
39064
39248
|
}
|
|
39065
39249
|
return null;
|
|
39066
39250
|
}
|
|
39251
|
+
function normalizeBaseUrl(url2) {
|
|
39252
|
+
let end = url2.length;
|
|
39253
|
+
while (end > 0 && url2.charCodeAt(end - 1) === 47) {
|
|
39254
|
+
end -= 1;
|
|
39255
|
+
}
|
|
39256
|
+
return end === url2.length ? url2 : url2.slice(0, end);
|
|
39257
|
+
}
|
|
39258
|
+
function buildPyneRequestUrl(baseUrl, path6) {
|
|
39259
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
39260
|
+
let parsed;
|
|
39261
|
+
try {
|
|
39262
|
+
parsed = new URL(base);
|
|
39263
|
+
} catch {
|
|
39264
|
+
throw new CLIError(`Invalid pyne-worker URL: ${baseUrl}`, 1 /* ERROR */);
|
|
39265
|
+
}
|
|
39266
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
39267
|
+
throw new CLIError(`pyne-worker URL must be http(s): ${baseUrl}`, 1 /* ERROR */);
|
|
39268
|
+
}
|
|
39269
|
+
const suffix = path6.startsWith("/") ? path6 : `/${path6}`;
|
|
39270
|
+
return `${parsed.origin}${parsed.pathname === "/" ? "" : parsed.pathname}${suffix}`;
|
|
39271
|
+
}
|
|
39067
39272
|
async function pyneFetch(baseUrl, path6, options = {}) {
|
|
39068
39273
|
const method = options.method ?? "GET";
|
|
39069
39274
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
39070
39275
|
const controller = new AbortController;
|
|
39071
39276
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39277
|
+
const requestUrl = buildPyneRequestUrl(baseUrl, path6);
|
|
39072
39278
|
const headers = {
|
|
39073
39279
|
Accept: "application/json"
|
|
39074
39280
|
};
|
|
@@ -39081,7 +39287,7 @@ async function pyneFetch(baseUrl, path6, options = {}) {
|
|
|
39081
39287
|
body = JSON.stringify(options.body);
|
|
39082
39288
|
}
|
|
39083
39289
|
try {
|
|
39084
|
-
const res = await fetch(
|
|
39290
|
+
const res = await fetch(requestUrl, {
|
|
39085
39291
|
method,
|
|
39086
39292
|
headers,
|
|
39087
39293
|
body,
|
|
@@ -39101,8 +39307,7 @@ async function pyneFetch(baseUrl, path6, options = {}) {
|
|
|
39101
39307
|
}
|
|
39102
39308
|
function printResult(data, fmt) {
|
|
39103
39309
|
if (fmt.json) {
|
|
39104
|
-
|
|
39105
|
-
`);
|
|
39310
|
+
formatJson(data, fmt);
|
|
39106
39311
|
return;
|
|
39107
39312
|
}
|
|
39108
39313
|
if (typeof data === "string") {
|
|
@@ -39111,8 +39316,7 @@ function printResult(data, fmt) {
|
|
|
39111
39316
|
`));
|
|
39112
39317
|
return;
|
|
39113
39318
|
}
|
|
39114
|
-
|
|
39115
|
-
`);
|
|
39319
|
+
formatJson(data, fmt);
|
|
39116
39320
|
}
|
|
39117
39321
|
async function spawnInPyneDir(cmd, cwd) {
|
|
39118
39322
|
const proc = Bun.spawn(cmd, {
|
|
@@ -39164,27 +39368,24 @@ EXAMPLES:
|
|
|
39164
39368
|
if (!res.ok)
|
|
39165
39369
|
process.exitCode = 1 /* ERROR */;
|
|
39166
39370
|
if (fmt.json) {
|
|
39167
|
-
|
|
39168
|
-
`);
|
|
39371
|
+
formatJson(payload, fmt);
|
|
39169
39372
|
} else {
|
|
39170
39373
|
const icon = res.ok ? "ok" : "fail";
|
|
39171
39374
|
formatSuccess(`pyne-worker ${icon} \u2014 HTTP ${res.status} in ${latencyMs}ms (${baseUrl})`, fmt);
|
|
39172
39375
|
if (res.json) {
|
|
39173
|
-
|
|
39174
|
-
`);
|
|
39376
|
+
formatJson(res.json, fmt);
|
|
39175
39377
|
}
|
|
39176
39378
|
}
|
|
39177
39379
|
} catch (err) {
|
|
39178
39380
|
process.exitCode = 1 /* ERROR */;
|
|
39179
39381
|
const message = err instanceof Error ? err.message : String(err);
|
|
39180
39382
|
if (fmt.json) {
|
|
39181
|
-
|
|
39383
|
+
formatJson({
|
|
39182
39384
|
worker: "pyne-worker",
|
|
39183
39385
|
url: `${baseUrl}/health`,
|
|
39184
39386
|
status: "down",
|
|
39185
39387
|
error: message
|
|
39186
|
-
})
|
|
39187
|
-
`);
|
|
39388
|
+
}, fmt);
|
|
39188
39389
|
} else {
|
|
39189
39390
|
throw new CLIError(`pyne-worker health failed: ${message}`, 1 /* ERROR */);
|
|
39190
39391
|
}
|
|
@@ -39207,7 +39408,7 @@ EXAMPLES:
|
|
|
39207
39408
|
body.script = await new Response(Bun.stdin).text();
|
|
39208
39409
|
} else {
|
|
39209
39410
|
const abs = resolve7(process.cwd(), scriptPath);
|
|
39210
|
-
if (!
|
|
39411
|
+
if (!existsSync14(abs)) {
|
|
39211
39412
|
throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
|
|
39212
39413
|
}
|
|
39213
39414
|
body.script = readFileSync8(abs, "utf-8");
|
|
@@ -39258,7 +39459,7 @@ EXAMPLES:
|
|
|
39258
39459
|
throw new CLIError("API key required for authenticated endpoints", 2 /* INVALID_USAGE */);
|
|
39259
39460
|
}
|
|
39260
39461
|
const abs = resolve7(process.cwd(), scriptPath);
|
|
39261
|
-
if (!
|
|
39462
|
+
if (!existsSync14(abs)) {
|
|
39262
39463
|
throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
|
|
39263
39464
|
}
|
|
39264
39465
|
const script = readFileSync8(abs, "utf-8");
|
|
@@ -39366,7 +39567,7 @@ Pass script flags after -- :
|
|
|
39366
39567
|
Required before wrangler deploy after pulling new PYNE APIs.`).action(withErrorHandling(async () => {
|
|
39367
39568
|
const dir = requirePyneDir();
|
|
39368
39569
|
const script = resolve7(dir, "scripts/sync_vendor.sh");
|
|
39369
|
-
if (!
|
|
39570
|
+
if (!existsSync14(script)) {
|
|
39370
39571
|
throw new CLIError(`sync_vendor.sh not found at ${script}`, 1 /* ERROR */);
|
|
39371
39572
|
}
|
|
39372
39573
|
process.exitCode = await spawnInPyneDir(["bash", script], dir);
|
|
@@ -39375,7 +39576,7 @@ Required before wrangler deploy after pulling new PYNE APIs.`).action(withErrorH
|
|
|
39375
39576
|
const dir = requirePyneDir();
|
|
39376
39577
|
if (!opts.skipSync) {
|
|
39377
39578
|
const script = resolve7(dir, "scripts/sync_vendor.sh");
|
|
39378
|
-
if (
|
|
39579
|
+
if (existsSync14(script)) {
|
|
39379
39580
|
const code = await spawnInPyneDir(["bash", script], dir);
|
|
39380
39581
|
if (code !== 0) {
|
|
39381
39582
|
process.exitCode = code;
|
|
@@ -40924,7 +41125,7 @@ function registerCompletionCommand(program2) {
|
|
|
40924
41125
|
// src/ui/banner.ts
|
|
40925
41126
|
init_ansis();
|
|
40926
41127
|
init_theme();
|
|
40927
|
-
import { existsSync as
|
|
41128
|
+
import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
|
|
40928
41129
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
40929
41130
|
import { dirname as dirname4, join as join13 } from "path";
|
|
40930
41131
|
var ORANGE = ansis_default.hex("#ff7f2a");
|
|
@@ -40940,7 +41141,7 @@ function findCliVersion() {
|
|
|
40940
41141
|
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
40941
41142
|
for (let i2 = 0;i2 < 5; i2++) {
|
|
40942
41143
|
const pkgPath = join13(dir, "package.json");
|
|
40943
|
-
if (
|
|
41144
|
+
if (existsSync15(pkgPath)) {
|
|
40944
41145
|
try {
|
|
40945
41146
|
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
40946
41147
|
if (pkg.name === PKG_NAME)
|
|
@@ -41579,6 +41780,13 @@ Hoox CLI \u2014 Cloudflare Workers Platform
|
|
|
41579
41780
|
styleCommandText: (str) => theme.bold(str),
|
|
41580
41781
|
styleOptionText: (str) => str,
|
|
41581
41782
|
styleDescriptionText: (str) => theme.dim(str)
|
|
41783
|
+
}).configureOutput({
|
|
41784
|
+
outputError: (str, write) => {
|
|
41785
|
+
if (/^error: unknown command/i.test(str) || /^error: unknown option/i.test(str) || /^error: missing required argument/i.test(str) || /^error: required option/i.test(str) || /^\(Did you mean /i.test(str)) {
|
|
41786
|
+
return;
|
|
41787
|
+
}
|
|
41788
|
+
write(str);
|
|
41789
|
+
}
|
|
41582
41790
|
});
|
|
41583
41791
|
program2.option("--json", "Output in JSON format");
|
|
41584
41792
|
program2.option("--quiet", "Minimal output");
|
|
@@ -41593,10 +41801,16 @@ program2.hook("preAction", async () => {
|
|
|
41593
41801
|
return;
|
|
41594
41802
|
if (process.env.BUN_TEST === "1")
|
|
41595
41803
|
return;
|
|
41804
|
+
if (process.argv.includes("--json"))
|
|
41805
|
+
return;
|
|
41596
41806
|
try {
|
|
41597
41807
|
const { UpdateService: UpdateService2 } = await Promise.resolve().then(() => (init_update(), exports_update));
|
|
41598
41808
|
const service = new UpdateService2;
|
|
41599
|
-
|
|
41809
|
+
const autoUpdate = process.env.HOOX_AUTO_UPDATE_WRANGLER === "1";
|
|
41810
|
+
await service.checkAndPromptUpdate({
|
|
41811
|
+
yes: autoUpdate,
|
|
41812
|
+
silent: true
|
|
41813
|
+
});
|
|
41600
41814
|
} catch {}
|
|
41601
41815
|
});
|
|
41602
41816
|
program2.hook("postAction", (thisCmd) => {
|
|
@@ -41608,22 +41822,29 @@ program2.hook("postAction", (thisCmd) => {
|
|
|
41608
41822
|
const durationMs = Date.now() - startedAt;
|
|
41609
41823
|
const path6 = getCmdPath(thisCmd);
|
|
41610
41824
|
const suggestion = suggestNextCommand(path6);
|
|
41611
|
-
|
|
41825
|
+
const g2 = program2.opts();
|
|
41826
|
+
formatCompletion("Done", {
|
|
41827
|
+
durationMs,
|
|
41828
|
+
suggestion,
|
|
41829
|
+
json: Boolean(g2.json),
|
|
41830
|
+
quiet: Boolean(g2.quiet),
|
|
41831
|
+
noColor: g2.color === false || Boolean(process.env.NO_COLOR)
|
|
41832
|
+
});
|
|
41612
41833
|
});
|
|
41613
41834
|
program2.exitOverride((err) => {
|
|
41614
41835
|
const globalOpts = program2.opts();
|
|
41615
41836
|
const errFmtOpts = {
|
|
41616
41837
|
json: Boolean(globalOpts.json),
|
|
41617
41838
|
quiet: Boolean(globalOpts.quiet),
|
|
41618
|
-
noColor: Boolean(
|
|
41839
|
+
noColor: globalOpts.color === false || Boolean(process.env.NO_COLOR)
|
|
41619
41840
|
};
|
|
41620
41841
|
if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
|
|
41621
41842
|
process.exitCode = 0 /* SUCCESS */;
|
|
41622
|
-
|
|
41843
|
+
throw err;
|
|
41623
41844
|
}
|
|
41624
|
-
if (err.
|
|
41845
|
+
if ((err.message === "(outputHelp)" || err.message?.includes("outputHelp")) && err.code !== "commander.missingArgument") {
|
|
41625
41846
|
process.exitCode = 0 /* SUCCESS */;
|
|
41626
|
-
|
|
41847
|
+
throw err;
|
|
41627
41848
|
}
|
|
41628
41849
|
if (err.code === "commander.unknownCommand") {
|
|
41629
41850
|
const match = err.message.match(/'([^']+)'/);
|
|
@@ -41631,31 +41852,36 @@ program2.exitOverride((err) => {
|
|
|
41631
41852
|
const suggestion = suggestForCommand(program2, badArg);
|
|
41632
41853
|
formatError2(new CLIError(err.message, 2 /* INVALID_USAGE */, undefined, false), suggestion ? { ...errFmtOpts, suggestions: [suggestion] } : errFmtOpts);
|
|
41633
41854
|
process.exitCode = 2 /* INVALID_USAGE */;
|
|
41634
|
-
|
|
41855
|
+
throw err;
|
|
41635
41856
|
}
|
|
41636
41857
|
if (err.code === "commander.unknownOption") {
|
|
41637
41858
|
formatError2(new CLIError(err.message, 2 /* INVALID_USAGE */, undefined, false), errFmtOpts);
|
|
41638
41859
|
process.exitCode = 2 /* INVALID_USAGE */;
|
|
41639
|
-
|
|
41860
|
+
throw err;
|
|
41640
41861
|
}
|
|
41641
|
-
if (err.code === "commander.
|
|
41862
|
+
if (err.code === "commander.missingArgument" || err.code === "commander.missingMandatoryOptionValue") {
|
|
41642
41863
|
formatError2(new CLIError(err.message, 2 /* INVALID_USAGE */, undefined, false), errFmtOpts);
|
|
41643
41864
|
process.exitCode = 2 /* INVALID_USAGE */;
|
|
41644
|
-
|
|
41865
|
+
throw err;
|
|
41645
41866
|
}
|
|
41646
41867
|
if (err instanceof CLIError) {
|
|
41647
41868
|
formatError2(err, errFmtOpts);
|
|
41648
41869
|
process.exitCode = err.code;
|
|
41649
|
-
|
|
41870
|
+
throw err;
|
|
41650
41871
|
}
|
|
41651
41872
|
formatError2(new CLIError(err.message, 1 /* ERROR */, undefined, false), errFmtOpts);
|
|
41652
41873
|
process.exitCode = 1 /* ERROR */;
|
|
41874
|
+
throw err;
|
|
41653
41875
|
});
|
|
41654
41876
|
process.on("uncaughtException", (err) => {
|
|
41877
|
+
if (process.exitCode !== undefined && process.exitCode !== 0)
|
|
41878
|
+
return;
|
|
41655
41879
|
formatError2(err instanceof CLIError ? err : new CLIError(`Uncaught exception: ${err.message}`, 1 /* ERROR */, err.stack, false));
|
|
41656
41880
|
process.exitCode = 1 /* ERROR */;
|
|
41657
41881
|
});
|
|
41658
41882
|
process.on("unhandledRejection", (reason) => {
|
|
41883
|
+
if (process.exitCode !== undefined && process.exitCode !== 0)
|
|
41884
|
+
return;
|
|
41659
41885
|
const err = reason instanceof Error ? reason : new Error(`Unhandled rejection: ${toError(reason)}`);
|
|
41660
41886
|
formatError2(new CLIError(err.message, 1 /* ERROR */, err.stack, false));
|
|
41661
41887
|
process.exitCode = 1 /* ERROR */;
|
|
@@ -41699,17 +41925,31 @@ async function main() {
|
|
|
41699
41925
|
const configFile = Bun.file("wrangler.jsonc");
|
|
41700
41926
|
const hasConfig = await configFile.exists();
|
|
41701
41927
|
const wizardStateFile = Bun.file(WIZARD_STATE_PATH);
|
|
41702
|
-
const hasWizardState = await wizardStateFile.exists();
|
|
41928
|
+
const hasWizardState = await wizardStateFile.exists() && (await wizardStateFile.text()).trim().length > 0;
|
|
41703
41929
|
if (!hasConfig) {
|
|
41704
41930
|
const args = hasWizardState ? ["onboard", "--resume"] : ["onboard"];
|
|
41705
41931
|
await program2.parseAsync(args, { from: "user" });
|
|
41706
41932
|
return;
|
|
41707
41933
|
}
|
|
41708
|
-
if (
|
|
41709
|
-
|
|
41934
|
+
if (await wizardStateFile.exists()) {
|
|
41935
|
+
try {
|
|
41936
|
+
const { unlinkSync } = await import("fs");
|
|
41937
|
+
unlinkSync(WIZARD_STATE_PATH);
|
|
41938
|
+
} catch {
|
|
41939
|
+
await Bun.write(WIZARD_STATE_PATH, "");
|
|
41940
|
+
}
|
|
41710
41941
|
}
|
|
41711
41942
|
await runInteractiveTUI(program2);
|
|
41712
41943
|
}
|
|
41944
|
+
} catch (err) {
|
|
41945
|
+
if (process.exitCode === undefined) {
|
|
41946
|
+
const message = err instanceof Error ? err.message : `Unhandled error: ${String(err)}`;
|
|
41947
|
+
const isCommander = err instanceof Error && "code" in err && typeof err.code === "string" && String(err.code).startsWith("commander.");
|
|
41948
|
+
if (!isCommander) {
|
|
41949
|
+
formatError2(new CLIError(message, 1 /* ERROR */, undefined, false));
|
|
41950
|
+
}
|
|
41951
|
+
process.exitCode = 1 /* ERROR */;
|
|
41952
|
+
}
|
|
41713
41953
|
} finally {
|
|
41714
41954
|
if (typeof process.exit === "function" && process.exitCode !== undefined) {
|
|
41715
41955
|
process.exit(process.exitCode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hoox-sh/hoox-cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@hoox-sh/hoox-cli)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@hoox-sh/hoox-tui": "workspace:*"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@hoox-sh/hoox-shared": "^1.2.
|
|
29
|
+
"@hoox-sh/hoox-shared": "^1.2.1",
|
|
30
30
|
"@types/node": "^26.1.1",
|
|
31
31
|
"bun-types": "^1.3.14",
|
|
32
32
|
"typescript": "^6.0.3"
|