@hoox-sh/hoox-cli 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +211 -60
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -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/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();
|
|
@@ -29255,18 +29315,20 @@ async function runInitCommand(options, globalOpts, isNonInteractive) {
|
|
|
29255
29315
|
engine = new WizardEngine;
|
|
29256
29316
|
}
|
|
29257
29317
|
const skipRiskWarning = options.acceptRisk;
|
|
29258
|
-
if (
|
|
29259
|
-
|
|
29260
|
-
|
|
29261
|
-
|
|
29262
|
-
|
|
29263
|
-
|
|
29264
|
-
|
|
29265
|
-
|
|
29266
|
-
|
|
29267
|
-
|
|
29268
|
-
|
|
29269
|
-
|
|
29318
|
+
if (engine.getCurrentStep().id === "PREREQUISITES") {
|
|
29319
|
+
if (!skipRiskWarning) {
|
|
29320
|
+
const accepted = await confirm({
|
|
29321
|
+
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?",
|
|
29322
|
+
initialValue: false
|
|
29323
|
+
});
|
|
29324
|
+
if (isCancel(accepted)) {
|
|
29325
|
+
cancel("Setup cancelled.");
|
|
29326
|
+
process.exitCode = 0;
|
|
29327
|
+
}
|
|
29328
|
+
if (!accepted) {
|
|
29329
|
+
outro("Setup cancelled. See DISCLAIMER.md for full terms.");
|
|
29330
|
+
process.exitCode = 0;
|
|
29331
|
+
}
|
|
29270
29332
|
}
|
|
29271
29333
|
engine.execute({ checksPassed: true });
|
|
29272
29334
|
await saveState(engine);
|
|
@@ -33256,6 +33318,35 @@ init_dist4();
|
|
|
33256
33318
|
// src/services/secrets/secrets-service.ts
|
|
33257
33319
|
init_main();
|
|
33258
33320
|
init_wrangler_output();
|
|
33321
|
+
import { join as join8, resolve as resolvePath } from "path";
|
|
33322
|
+
async function resolveWorkerWranglerConfig(workerPath) {
|
|
33323
|
+
const abs = resolvePath(workerPath);
|
|
33324
|
+
const candidates = [
|
|
33325
|
+
join8(abs, "wrangler.jsonc"),
|
|
33326
|
+
join8(abs, "wrangler.toml"),
|
|
33327
|
+
join8(abs, "wrangler.jsonc.example")
|
|
33328
|
+
];
|
|
33329
|
+
for (const configPath of candidates) {
|
|
33330
|
+
if (!await Bun.file(configPath).exists())
|
|
33331
|
+
continue;
|
|
33332
|
+
let deployName = null;
|
|
33333
|
+
try {
|
|
33334
|
+
const text2 = await Bun.file(configPath).text();
|
|
33335
|
+
if (configPath.endsWith(".toml")) {
|
|
33336
|
+
const m3 = text2.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
|
|
33337
|
+
if (m3?.[1])
|
|
33338
|
+
deployName = m3[1];
|
|
33339
|
+
} else {
|
|
33340
|
+
const parsed = parse6(text2);
|
|
33341
|
+
if (typeof parsed?.name === "string" && parsed.name.trim()) {
|
|
33342
|
+
deployName = parsed.name.trim();
|
|
33343
|
+
}
|
|
33344
|
+
}
|
|
33345
|
+
} catch {}
|
|
33346
|
+
return { configPath, deployName };
|
|
33347
|
+
}
|
|
33348
|
+
throw new Error(`No wrangler.jsonc (or wrangler.jsonc.example) in ${workerPath}. ` + `Copy the example config into wrangler.jsonc and fill account resources, ` + `then re-run secrets sync.`);
|
|
33349
|
+
}
|
|
33259
33350
|
var SYSTEM_SECRET_NAMES = [
|
|
33260
33351
|
"INTERNAL_KEY_BINDING",
|
|
33261
33352
|
"AGENT_INTERNAL_KEY",
|
|
@@ -33400,7 +33491,7 @@ class SecretsService {
|
|
|
33400
33491
|
try {
|
|
33401
33492
|
const value = existingValues.get(secret);
|
|
33402
33493
|
if (value !== undefined && !this.isPlaceholder(value)) {
|
|
33403
|
-
await this.execWranglerSecretPut(worker.path, secret, value);
|
|
33494
|
+
await this.execWranglerSecretPut(worker.path, secret, value, workerName);
|
|
33404
33495
|
synced.push(secret);
|
|
33405
33496
|
items.push({ name: secret, status: "synced" });
|
|
33406
33497
|
} else {
|
|
@@ -33456,9 +33547,15 @@ class SecretsService {
|
|
|
33456
33547
|
const v = value.trim();
|
|
33457
33548
|
return v === "" || v.startsWith("placeholder_") || v.startsWith("your_") || v.startsWith("generate_") || v.startsWith("<") || v.includes("generate-with");
|
|
33458
33549
|
}
|
|
33459
|
-
async execWranglerSecretPut(workerPath, name, value) {
|
|
33460
|
-
const
|
|
33461
|
-
|
|
33550
|
+
async execWranglerSecretPut(workerPath, name, value, logicalWorkerName) {
|
|
33551
|
+
const { configPath, deployName } = await resolveWorkerWranglerConfig(workerPath);
|
|
33552
|
+
const workerName = deployName ?? logicalWorkerName ?? null;
|
|
33553
|
+
const args = ["wrangler", "secret", "put", name, "-c", configPath];
|
|
33554
|
+
if (workerName) {
|
|
33555
|
+
args.push("--name", workerName);
|
|
33556
|
+
}
|
|
33557
|
+
const proc = Bun.spawn(args, {
|
|
33558
|
+
cwd: resolvePath(workerPath),
|
|
33462
33559
|
stdin: "pipe",
|
|
33463
33560
|
stdout: "pipe",
|
|
33464
33561
|
stderr: "pipe"
|
|
@@ -33725,7 +33822,18 @@ EXAMPLES:
|
|
|
33725
33822
|
throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}".`, 2 /* INVALID_USAGE */);
|
|
33726
33823
|
}
|
|
33727
33824
|
const workerPath = svc.getWorkerPath(workerName) ?? `workers/${workerName}`;
|
|
33728
|
-
const
|
|
33825
|
+
const { configPath, deployName } = await resolveWorkerWranglerConfig(workerPath);
|
|
33826
|
+
const deploy = deployName ?? workerName;
|
|
33827
|
+
const proc = Bun.spawn([
|
|
33828
|
+
"wrangler",
|
|
33829
|
+
"secret",
|
|
33830
|
+
"delete",
|
|
33831
|
+
secretName,
|
|
33832
|
+
"-c",
|
|
33833
|
+
configPath,
|
|
33834
|
+
"--name",
|
|
33835
|
+
deploy
|
|
33836
|
+
], {
|
|
33729
33837
|
cwd: workerPath,
|
|
33730
33838
|
stdout: "pipe",
|
|
33731
33839
|
stderr: "pipe"
|
|
@@ -36175,7 +36283,7 @@ init_dist4();
|
|
|
36175
36283
|
init_config2();
|
|
36176
36284
|
init_cloudflare_service();
|
|
36177
36285
|
import { existsSync as existsSync9 } from "fs";
|
|
36178
|
-
import { join as
|
|
36286
|
+
import { join as join9 } from "path";
|
|
36179
36287
|
var D1_WRANGLER_CONFIG_CANDIDATES = [
|
|
36180
36288
|
"workers/d1-worker/wrangler.jsonc",
|
|
36181
36289
|
"workers/trade-worker/wrangler.jsonc"
|
|
@@ -36192,13 +36300,13 @@ class DbService {
|
|
|
36192
36300
|
}
|
|
36193
36301
|
resolveDefaultSchemaPath() {
|
|
36194
36302
|
if (this.homeDir) {
|
|
36195
|
-
return
|
|
36303
|
+
return join9(this.homeDir, ".hoox", "workers", "trade-worker", "schema.sql");
|
|
36196
36304
|
}
|
|
36197
36305
|
return "workers/trade-worker/schema.sql";
|
|
36198
36306
|
}
|
|
36199
36307
|
resolveMigrationScriptPath() {
|
|
36200
36308
|
if (this.homeDir) {
|
|
36201
|
-
return
|
|
36309
|
+
return join9(this.homeDir, ".hoox", "scripts", "migrate-tracking.sh");
|
|
36202
36310
|
}
|
|
36203
36311
|
return "scripts/migrate-tracking.sh";
|
|
36204
36312
|
}
|
|
@@ -37405,7 +37513,7 @@ init_theme();
|
|
|
37405
37513
|
init_errors4();
|
|
37406
37514
|
import { spawn } from "child_process";
|
|
37407
37515
|
import { createRequire } from "module";
|
|
37408
|
-
import { resolve as resolve6, dirname as dirname3, join as
|
|
37516
|
+
import { resolve as resolve6, dirname as dirname3, join as join10 } from "path";
|
|
37409
37517
|
import { fileURLToPath } from "url";
|
|
37410
37518
|
import { existsSync as existsSync11, realpathSync } from "fs";
|
|
37411
37519
|
import { homedir as homedir5 } from "os";
|
|
@@ -37458,14 +37566,14 @@ function collectTuiCandidates() {
|
|
|
37458
37566
|
candidates.push(resolve6(__dirname2, "../../../../tui/src/main.tsx"), resolve6(__dirname2, "../../../../tui/dist/main.js"), resolve6(__dirname2, "../../../tui/src/main.tsx"), resolve6(__dirname2, "../../../tui/dist/main.js"), resolve6(process.cwd(), "packages/tui/src/main.tsx"), resolve6(process.cwd(), "packages/tui/dist/main.js"), resolve6(process.cwd(), "../tui/src/main.tsx"), resolve6(process.cwd(), "../tui/dist/main.js"));
|
|
37459
37567
|
pushPackageEntries(candidates, require2);
|
|
37460
37568
|
try {
|
|
37461
|
-
const cwdRequire = createRequire(
|
|
37569
|
+
const cwdRequire = createRequire(join10(process.cwd(), "package.json"));
|
|
37462
37570
|
pushPackageEntries(candidates, cwdRequire);
|
|
37463
37571
|
} catch {}
|
|
37464
|
-
const bunGlobal =
|
|
37572
|
+
const bunGlobal = join10(homedir5(), ".bun", "install", "global", "node_modules", TUI_PKG);
|
|
37465
37573
|
pushPackageRootEntries(candidates, bunGlobal);
|
|
37466
37574
|
const npmGlobal = process.env.npm_config_prefix?.trim();
|
|
37467
37575
|
if (npmGlobal) {
|
|
37468
|
-
pushPackageRootEntries(candidates,
|
|
37576
|
+
pushPackageRootEntries(candidates, join10(npmGlobal, "lib", "node_modules", TUI_PKG));
|
|
37469
37577
|
}
|
|
37470
37578
|
pushFromWhich(candidates, "hoox-tui");
|
|
37471
37579
|
return [...new Set(candidates)];
|
|
@@ -37473,7 +37581,7 @@ function collectTuiCandidates() {
|
|
|
37473
37581
|
function pushPackageRootEntries(candidates, root) {
|
|
37474
37582
|
if (!root || !existsSync11(root))
|
|
37475
37583
|
return;
|
|
37476
|
-
candidates.push(
|
|
37584
|
+
candidates.push(join10(root, "dist", "main.js"), join10(root, "src", "main.tsx"), join10(root, "bin", "hoox-tui.js"), join10(root, "src", "main.ts"));
|
|
37477
37585
|
}
|
|
37478
37586
|
function pushPackageEntries(candidates, req) {
|
|
37479
37587
|
try {
|
|
@@ -37713,14 +37821,14 @@ TUI exited with code ${code}
|
|
|
37713
37821
|
// src/commands/doctor/doctor-command.ts
|
|
37714
37822
|
init_dist4();
|
|
37715
37823
|
init_src();
|
|
37716
|
-
import { join as
|
|
37824
|
+
import { join as join12 } from "path";
|
|
37717
37825
|
import { homedir as homedir6 } from "os";
|
|
37718
37826
|
|
|
37719
37827
|
// src/services/runtime/runtime-service.ts
|
|
37720
37828
|
init_src();
|
|
37721
37829
|
import { existsSync as existsSync12, lstatSync as lstatSync2, readlinkSync, rmSync } from "fs";
|
|
37722
37830
|
import { mkdir } from "fs/promises";
|
|
37723
|
-
import { join as
|
|
37831
|
+
import { join as join11 } from "path";
|
|
37724
37832
|
var DEFAULT_HOOX_RUNTIME_REPO = "https://github.com/hoox-sh/hoox.git";
|
|
37725
37833
|
function firstExisting(paths) {
|
|
37726
37834
|
for (const p3 of paths) {
|
|
@@ -37803,9 +37911,9 @@ ${clone2.stderr || clone2.stdout}`.trim());
|
|
|
37803
37911
|
}
|
|
37804
37912
|
let installed = false;
|
|
37805
37913
|
if (!options.skipInstall) {
|
|
37806
|
-
const nodeModules =
|
|
37807
|
-
const tuiPkg =
|
|
37808
|
-
const needsInstall = !existsSync12(nodeModules) || existsSync12(tuiPkg) && !existsSync12(
|
|
37914
|
+
const nodeModules = join11(repoPath, "node_modules");
|
|
37915
|
+
const tuiPkg = join11(repoPath, "packages", "tui", "package.json");
|
|
37916
|
+
const needsInstall = !existsSync12(nodeModules) || existsSync12(tuiPkg) && !existsSync12(join11(repoPath, "packages", "tui", "node_modules")) && !existsSync12(join11(nodeModules, "@opentui"));
|
|
37809
37917
|
if (cloned || needsInstall || !existsSync12(nodeModules)) {
|
|
37810
37918
|
log2(`Running bun install in ${repoPath}`);
|
|
37811
37919
|
const install = await runCommand(["bun", "install"], repoPath);
|
|
@@ -38134,7 +38242,7 @@ Hoox doctor
|
|
|
38134
38242
|
return failed === 0 ? 0 /* SUCCESS */ : 1 /* ERROR */;
|
|
38135
38243
|
}
|
|
38136
38244
|
function printSecurityHygieneSection(env = process.env) {
|
|
38137
|
-
const configPath =
|
|
38245
|
+
const configPath = join12(homedir6(), ".hoox", "config.json");
|
|
38138
38246
|
const lines = collectSecurityHygiene(env, { configPath });
|
|
38139
38247
|
process.stdout.write(theme.heading(`Security hygiene
|
|
38140
38248
|
`));
|
|
@@ -38152,7 +38260,7 @@ async function runSecurityDoctor(options) {
|
|
|
38152
38260
|
env.HOOX_API_URL = options.apiUrl;
|
|
38153
38261
|
}
|
|
38154
38262
|
const profile = resolveOperatorTransportProfile(env);
|
|
38155
|
-
const configPath =
|
|
38263
|
+
const configPath = join12(homedir6(), ".hoox", "config.json");
|
|
38156
38264
|
const hygiene = collectSecurityHygiene(env, { configPath });
|
|
38157
38265
|
const isLocal = profile.apiBase.includes("localhost") || profile.apiBase.includes("127.0.0.1");
|
|
38158
38266
|
const shouldProbe = options.probe === true || options.probe !== false && !isLocal;
|
|
@@ -38483,7 +38591,31 @@ function sanitizeErrorMessage(message, secrets) {
|
|
|
38483
38591
|
out = out.replace(/([?&]key=)[^&\s]+/gi, "$1[redacted]");
|
|
38484
38592
|
return out;
|
|
38485
38593
|
}
|
|
38594
|
+
var PROBE_ALLOWED_HOSTS = new Set([
|
|
38595
|
+
"api.cloudflare.com",
|
|
38596
|
+
"api.openai.com",
|
|
38597
|
+
"api.anthropic.com",
|
|
38598
|
+
"generativelanguage.googleapis.com",
|
|
38599
|
+
"api.cognitive.microsoft.com"
|
|
38600
|
+
]);
|
|
38601
|
+
function assertProbeUrl(url2) {
|
|
38602
|
+
let parsed;
|
|
38603
|
+
try {
|
|
38604
|
+
parsed = new URL(url2);
|
|
38605
|
+
} catch {
|
|
38606
|
+
throw new Error(`Invalid probe URL: ${url2}`);
|
|
38607
|
+
}
|
|
38608
|
+
if (parsed.protocol !== "https:") {
|
|
38609
|
+
throw new Error(`Probe URL must be https: ${url2}`);
|
|
38610
|
+
}
|
|
38611
|
+
const host = parsed.hostname.toLowerCase();
|
|
38612
|
+
const allowed = PROBE_ALLOWED_HOSTS.has(host) || host.endsWith(".openai.azure.com") || host.endsWith(".cognitiveservices.azure.com");
|
|
38613
|
+
if (!allowed) {
|
|
38614
|
+
throw new Error(`Probe URL host not allowlisted: ${host}`);
|
|
38615
|
+
}
|
|
38616
|
+
}
|
|
38486
38617
|
async function timedFetch(url2, init, timeoutMs) {
|
|
38618
|
+
assertProbeUrl(url2);
|
|
38487
38619
|
const start = performance.now();
|
|
38488
38620
|
const response = await fetch(url2, {
|
|
38489
38621
|
...init,
|
|
@@ -38592,9 +38724,8 @@ async function probeAnthropic(apiKey, timeoutMs) {
|
|
|
38592
38724
|
return { status: "online", latencyMs };
|
|
38593
38725
|
}
|
|
38594
38726
|
if (response.status === 404) {
|
|
38595
|
-
const start = performance.now();
|
|
38596
38727
|
try {
|
|
38597
|
-
const msgRes = await
|
|
38728
|
+
const { response: msgRes, latencyMs: lat } = await timedFetch("https://api.anthropic.com/v1/messages", {
|
|
38598
38729
|
method: "POST",
|
|
38599
38730
|
headers: {
|
|
38600
38731
|
"x-api-key": apiKey,
|
|
@@ -38605,10 +38736,8 @@ async function probeAnthropic(apiKey, timeoutMs) {
|
|
|
38605
38736
|
model: "claude-sonnet-4-20250514",
|
|
38606
38737
|
max_tokens: 1,
|
|
38607
38738
|
messages: [{ role: "user", content: "ping" }]
|
|
38608
|
-
})
|
|
38609
|
-
|
|
38610
|
-
});
|
|
38611
|
-
const lat = Math.round(performance.now() - start);
|
|
38739
|
+
})
|
|
38740
|
+
}, timeoutMs);
|
|
38612
38741
|
if (msgRes.ok)
|
|
38613
38742
|
return { status: "online", latencyMs: lat };
|
|
38614
38743
|
if (msgRes.status === 401 || msgRes.status === 403) {
|
|
@@ -38684,7 +38813,9 @@ async function probeAzure(apiKey, timeoutMs) {
|
|
|
38684
38813
|
error: "AZURE_ENDPOINT missing"
|
|
38685
38814
|
};
|
|
38686
38815
|
}
|
|
38687
|
-
|
|
38816
|
+
let base = endpoint;
|
|
38817
|
+
while (base.endsWith("/"))
|
|
38818
|
+
base = base.slice(0, -1);
|
|
38688
38819
|
const url2 = `${base}/openai/models?api-version=2024-02-01`;
|
|
38689
38820
|
try {
|
|
38690
38821
|
const { response, latencyMs } = await timedFetch(url2, {
|
|
@@ -38942,15 +39073,18 @@ init_config2();
|
|
|
38942
39073
|
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "fs";
|
|
38943
39074
|
import { resolve as resolve7 } from "path";
|
|
38944
39075
|
init_errors4();
|
|
38945
|
-
var PYNE_WORKER_DIR = resolve7(process.cwd(), "workers/pyne-worker");
|
|
38946
39076
|
var DEFAULT_TIMEOUT_MS = 30000;
|
|
39077
|
+
function getPyneWorkerDir() {
|
|
39078
|
+
return resolve7(process.cwd(), "workers/pyne-worker");
|
|
39079
|
+
}
|
|
38947
39080
|
function requirePyneDir() {
|
|
38948
|
-
|
|
38949
|
-
|
|
39081
|
+
const dir = getPyneWorkerDir();
|
|
39082
|
+
if (!existsSync13(dir)) {
|
|
39083
|
+
throw new CLIError(`pyne-worker directory not found: ${dir}
|
|
38950
39084
|
` + ` Clone it with: hoox clone pyne-worker
|
|
38951
39085
|
` + ` Or: git submodule update --init workers/pyne-worker`, 1 /* ERROR */);
|
|
38952
39086
|
}
|
|
38953
|
-
return
|
|
39087
|
+
return dir;
|
|
38954
39088
|
}
|
|
38955
39089
|
function loadDevVars(workerDir) {
|
|
38956
39090
|
const out = {};
|
|
@@ -39018,11 +39152,33 @@ function resolveApiKey(explicit, workerDir) {
|
|
|
39018
39152
|
}
|
|
39019
39153
|
return null;
|
|
39020
39154
|
}
|
|
39155
|
+
function normalizeBaseUrl(url2) {
|
|
39156
|
+
let end = url2.length;
|
|
39157
|
+
while (end > 0 && url2.charCodeAt(end - 1) === 47) {
|
|
39158
|
+
end -= 1;
|
|
39159
|
+
}
|
|
39160
|
+
return end === url2.length ? url2 : url2.slice(0, end);
|
|
39161
|
+
}
|
|
39162
|
+
function buildPyneRequestUrl(baseUrl, path6) {
|
|
39163
|
+
const base = normalizeBaseUrl(baseUrl);
|
|
39164
|
+
let parsed;
|
|
39165
|
+
try {
|
|
39166
|
+
parsed = new URL(base);
|
|
39167
|
+
} catch {
|
|
39168
|
+
throw new CLIError(`Invalid pyne-worker URL: ${baseUrl}`, 1 /* ERROR */);
|
|
39169
|
+
}
|
|
39170
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
39171
|
+
throw new CLIError(`pyne-worker URL must be http(s): ${baseUrl}`, 1 /* ERROR */);
|
|
39172
|
+
}
|
|
39173
|
+
const suffix = path6.startsWith("/") ? path6 : `/${path6}`;
|
|
39174
|
+
return `${parsed.origin}${parsed.pathname === "/" ? "" : parsed.pathname}${suffix}`;
|
|
39175
|
+
}
|
|
39021
39176
|
async function pyneFetch(baseUrl, path6, options = {}) {
|
|
39022
39177
|
const method = options.method ?? "GET";
|
|
39023
39178
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
39024
39179
|
const controller = new AbortController;
|
|
39025
39180
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
39181
|
+
const requestUrl = buildPyneRequestUrl(baseUrl, path6);
|
|
39026
39182
|
const headers = {
|
|
39027
39183
|
Accept: "application/json"
|
|
39028
39184
|
};
|
|
@@ -39035,7 +39191,7 @@ async function pyneFetch(baseUrl, path6, options = {}) {
|
|
|
39035
39191
|
body = JSON.stringify(options.body);
|
|
39036
39192
|
}
|
|
39037
39193
|
try {
|
|
39038
|
-
const res = await fetch(
|
|
39194
|
+
const res = await fetch(requestUrl, {
|
|
39039
39195
|
method,
|
|
39040
39196
|
headers,
|
|
39041
39197
|
body,
|
|
@@ -39055,8 +39211,7 @@ async function pyneFetch(baseUrl, path6, options = {}) {
|
|
|
39055
39211
|
}
|
|
39056
39212
|
function printResult(data, fmt) {
|
|
39057
39213
|
if (fmt.json) {
|
|
39058
|
-
|
|
39059
|
-
`);
|
|
39214
|
+
formatJson(data, fmt);
|
|
39060
39215
|
return;
|
|
39061
39216
|
}
|
|
39062
39217
|
if (typeof data === "string") {
|
|
@@ -39065,8 +39220,7 @@ function printResult(data, fmt) {
|
|
|
39065
39220
|
`));
|
|
39066
39221
|
return;
|
|
39067
39222
|
}
|
|
39068
|
-
|
|
39069
|
-
`);
|
|
39223
|
+
formatJson(data, fmt);
|
|
39070
39224
|
}
|
|
39071
39225
|
async function spawnInPyneDir(cmd, cwd) {
|
|
39072
39226
|
const proc = Bun.spawn(cmd, {
|
|
@@ -39118,27 +39272,24 @@ EXAMPLES:
|
|
|
39118
39272
|
if (!res.ok)
|
|
39119
39273
|
process.exitCode = 1 /* ERROR */;
|
|
39120
39274
|
if (fmt.json) {
|
|
39121
|
-
|
|
39122
|
-
`);
|
|
39275
|
+
formatJson(payload, fmt);
|
|
39123
39276
|
} else {
|
|
39124
39277
|
const icon = res.ok ? "ok" : "fail";
|
|
39125
39278
|
formatSuccess(`pyne-worker ${icon} \u2014 HTTP ${res.status} in ${latencyMs}ms (${baseUrl})`, fmt);
|
|
39126
39279
|
if (res.json) {
|
|
39127
|
-
|
|
39128
|
-
`);
|
|
39280
|
+
formatJson(res.json, fmt);
|
|
39129
39281
|
}
|
|
39130
39282
|
}
|
|
39131
39283
|
} catch (err) {
|
|
39132
39284
|
process.exitCode = 1 /* ERROR */;
|
|
39133
39285
|
const message = err instanceof Error ? err.message : String(err);
|
|
39134
39286
|
if (fmt.json) {
|
|
39135
|
-
|
|
39287
|
+
formatJson({
|
|
39136
39288
|
worker: "pyne-worker",
|
|
39137
39289
|
url: `${baseUrl}/health`,
|
|
39138
39290
|
status: "down",
|
|
39139
39291
|
error: message
|
|
39140
|
-
})
|
|
39141
|
-
`);
|
|
39292
|
+
}, fmt);
|
|
39142
39293
|
} else {
|
|
39143
39294
|
throw new CLIError(`pyne-worker health failed: ${message}`, 1 /* ERROR */);
|
|
39144
39295
|
}
|
|
@@ -40880,7 +41031,7 @@ init_ansis();
|
|
|
40880
41031
|
init_theme();
|
|
40881
41032
|
import { existsSync as existsSync14, readFileSync as readFileSync9 } from "fs";
|
|
40882
41033
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
40883
|
-
import { dirname as dirname4, join as
|
|
41034
|
+
import { dirname as dirname4, join as join13 } from "path";
|
|
40884
41035
|
var ORANGE = ansis_default.hex("#ff7f2a");
|
|
40885
41036
|
var AMBER = ansis_default.hex("#ffb722");
|
|
40886
41037
|
var INDIGO = ansis_default.hex("#818cf8");
|
|
@@ -40893,7 +41044,7 @@ function findCliVersion() {
|
|
|
40893
41044
|
const PKG_NAME = "@hoox-sh/hoox-cli";
|
|
40894
41045
|
let dir = dirname4(fileURLToPath2(import.meta.url));
|
|
40895
41046
|
for (let i2 = 0;i2 < 5; i2++) {
|
|
40896
|
-
const pkgPath =
|
|
41047
|
+
const pkgPath = join13(dir, "package.json");
|
|
40897
41048
|
if (existsSync14(pkgPath)) {
|
|
40898
41049
|
try {
|
|
40899
41050
|
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
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.2",
|
|
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"
|