@trading-boy/cli 1.2.18 → 1.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.bundle.js +266 -17
- package/dist/commands/trader.js +307 -4
- package/dist/commands/watch.js +17 -1
- package/package.json +1 -1
package/dist/cli.bundle.js
CHANGED
|
@@ -23934,7 +23934,7 @@ var init_RemoveFileError = __esm({
|
|
|
23934
23934
|
|
|
23935
23935
|
// ../../node_modules/.pnpm/@inquirer+external-editor@1.0.3_@types+node@25.2.3/node_modules/@inquirer/external-editor/dist/esm/index.js
|
|
23936
23936
|
import { spawn, spawnSync } from "child_process";
|
|
23937
|
-
import { readFileSync as
|
|
23937
|
+
import { readFileSync as readFileSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
23938
23938
|
import path2 from "node:path";
|
|
23939
23939
|
import os2 from "node:os";
|
|
23940
23940
|
import { randomUUID } from "node:crypto";
|
|
@@ -24051,14 +24051,14 @@ var init_esm5 = __esm({
|
|
|
24051
24051
|
if (Object.prototype.hasOwnProperty.call(this.fileOptions, "mode")) {
|
|
24052
24052
|
opt.mode = this.fileOptions.mode;
|
|
24053
24053
|
}
|
|
24054
|
-
|
|
24054
|
+
writeFileSync2(this.tempFile, this.text, opt);
|
|
24055
24055
|
} catch (createFileError) {
|
|
24056
24056
|
throw new CreateFileError(createFileError);
|
|
24057
24057
|
}
|
|
24058
24058
|
}
|
|
24059
24059
|
readTemporaryFile() {
|
|
24060
24060
|
try {
|
|
24061
|
-
const tempFileBuffer =
|
|
24061
|
+
const tempFileBuffer = readFileSync2(this.tempFile);
|
|
24062
24062
|
if (tempFileBuffer.length === 0) {
|
|
24063
24063
|
this.text = "";
|
|
24064
24064
|
} else {
|
|
@@ -39787,6 +39787,12 @@ var envSchema = external_exports.object({
|
|
|
39787
39787
|
COLD_START_MATURITY_THRESHOLD: external_exports.coerce.number().int().nonnegative().default(50),
|
|
39788
39788
|
// Politician trading data
|
|
39789
39789
|
FINNHUB_API_KEY: external_exports.string().default(""),
|
|
39790
|
+
// Macro data sources
|
|
39791
|
+
FRED_API_KEY: external_exports.string().default(""),
|
|
39792
|
+
NEWSDATA_API_KEY: external_exports.string().default(""),
|
|
39793
|
+
KALSHI_API_KEY: external_exports.string().default(""),
|
|
39794
|
+
OILPRICE_API_KEY: external_exports.string().default(""),
|
|
39795
|
+
EXCHANGERATE_API_KEY: external_exports.string().default(""),
|
|
39790
39796
|
// Feature flags
|
|
39791
39797
|
ENABLE_LLM: external_exports.enum(["true", "false"]).default("false").transform((v) => v === "true"),
|
|
39792
39798
|
ENABLE_LEARNING: external_exports.enum(["true", "false"]).default("false").transform((v) => v === "true"),
|
|
@@ -47664,7 +47670,7 @@ function stopWatchLoop(state) {
|
|
|
47664
47670
|
}
|
|
47665
47671
|
}
|
|
47666
47672
|
function registerWatchCommand(program2) {
|
|
47667
|
-
program2.command("watch <symbol>").description("Watch token data with periodic refresh").option("-i, --interval <seconds>", "Refresh interval in seconds", (v) => parseInt(v, 10)).option("-c, --context", "Watch full ContextPackage (default interval: 60s)").action(async (symbol2, options) => {
|
|
47673
|
+
program2.command("watch <symbol>").description("Watch token data with periodic refresh").option("-i, --interval <seconds>", "Refresh interval in seconds", (v) => parseInt(v, 10)).option("-c, --context", "Watch full ContextPackage (default interval: 60s)").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (symbol2, options) => {
|
|
47668
47674
|
const useContext = options.context === true;
|
|
47669
47675
|
const defaultInterval = useContext ? DEFAULT_CONTEXT_INTERVAL_SECONDS : DEFAULT_INTERVAL_SECONDS;
|
|
47670
47676
|
const intervalSeconds = options.interval ?? defaultInterval;
|
|
@@ -47674,8 +47680,19 @@ function registerWatchCommand(program2) {
|
|
|
47674
47680
|
return;
|
|
47675
47681
|
}
|
|
47676
47682
|
const mode = useContext ? "context" : "query";
|
|
47683
|
+
const isJson = options.format === "json";
|
|
47677
47684
|
logger8.info({ symbol: symbol2, intervalSeconds, mode }, "Starting watch mode");
|
|
47678
|
-
const
|
|
47685
|
+
const renderFn = isJson ? async (sym) => {
|
|
47686
|
+
try {
|
|
47687
|
+
const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(sym.toUpperCase())}/context`);
|
|
47688
|
+
console.log(JSON.stringify({ watchedAt: (/* @__PURE__ */ new Date()).toISOString(), symbol: sym.toUpperCase(), ...pkg }, null, 2));
|
|
47689
|
+
return pkg;
|
|
47690
|
+
} catch (error49) {
|
|
47691
|
+
console.log(JSON.stringify({ error: error49 instanceof Error ? error49.message : String(error49), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
47692
|
+
return null;
|
|
47693
|
+
}
|
|
47694
|
+
} : renderCycleRemote;
|
|
47695
|
+
const state = startWatchLoop(symbol2, intervalSeconds, renderFn);
|
|
47679
47696
|
const cleanup = () => {
|
|
47680
47697
|
logger8.info("Watch mode stopped by user");
|
|
47681
47698
|
stopWatchLoop(state);
|
|
@@ -48143,9 +48160,10 @@ function computeStats(decisions) {
|
|
|
48143
48160
|
}
|
|
48144
48161
|
|
|
48145
48162
|
// dist/commands/trader.js
|
|
48146
|
-
import { readFileSync as
|
|
48163
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
48147
48164
|
init_source();
|
|
48148
48165
|
init_utils();
|
|
48166
|
+
init_esm15();
|
|
48149
48167
|
var logger12 = createLogger("cli-trader");
|
|
48150
48168
|
function formatTraderOutput(trader, wallets) {
|
|
48151
48169
|
const lines = [];
|
|
@@ -48424,7 +48442,7 @@ function registerTraderCommand(program2) {
|
|
|
48424
48442
|
trader.command("soul").description("Show or upload a SOUL document").argument("<name-or-id>", "Trader name or ID").option("--file <path>", "Path to SOUL document file to upload").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (nameOrId, options) => {
|
|
48425
48443
|
try {
|
|
48426
48444
|
if (options.file) {
|
|
48427
|
-
const document =
|
|
48445
|
+
const document = readFileSync3(options.file, "utf-8");
|
|
48428
48446
|
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/soul`, { method: "PUT", body: { document } });
|
|
48429
48447
|
if (options.format === "json") {
|
|
48430
48448
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -48461,10 +48479,18 @@ function registerTraderCommand(program2) {
|
|
|
48461
48479
|
}
|
|
48462
48480
|
} catch (error49) {
|
|
48463
48481
|
if (error49 instanceof ApiError && error49.status === 404) {
|
|
48464
|
-
|
|
48482
|
+
if (options.format === "json") {
|
|
48483
|
+
console.log(JSON.stringify({ soul: null, message: options.file ? `Trader not found: "${nameOrId}"` : "No SOUL document found" }));
|
|
48484
|
+
} else {
|
|
48485
|
+
console.error(source_default.yellow(options.file ? `Trader not found: "${nameOrId}"` : "No SOUL document found."));
|
|
48486
|
+
}
|
|
48465
48487
|
} else {
|
|
48466
48488
|
const message = error49 instanceof Error ? error49.message : String(error49);
|
|
48467
|
-
|
|
48489
|
+
if (options.format === "json") {
|
|
48490
|
+
console.log(JSON.stringify({ error: message }));
|
|
48491
|
+
} else {
|
|
48492
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
48493
|
+
}
|
|
48468
48494
|
}
|
|
48469
48495
|
process.exitCode = error49 instanceof ApiError ? 2 : 1;
|
|
48470
48496
|
}
|
|
@@ -48472,7 +48498,7 @@ function registerTraderCommand(program2) {
|
|
|
48472
48498
|
trader.command("purpose").description("Show or upload a PURPOSE document").argument("<name-or-id>", "Trader name or ID").option("--file <path>", "Path to PURPOSE document file to upload").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (nameOrId, options) => {
|
|
48473
48499
|
try {
|
|
48474
48500
|
if (options.file) {
|
|
48475
|
-
const document =
|
|
48501
|
+
const document = readFileSync3(options.file, "utf-8");
|
|
48476
48502
|
const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/purpose`, { method: "PUT", body: { document } });
|
|
48477
48503
|
if (options.format === "json") {
|
|
48478
48504
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -48509,10 +48535,18 @@ function registerTraderCommand(program2) {
|
|
|
48509
48535
|
}
|
|
48510
48536
|
} catch (error49) {
|
|
48511
48537
|
if (error49 instanceof ApiError && error49.status === 404) {
|
|
48512
|
-
|
|
48538
|
+
if (options.format === "json") {
|
|
48539
|
+
console.log(JSON.stringify({ purpose: null, message: options.file ? `Trader not found: "${nameOrId}"` : "No PURPOSE document found" }));
|
|
48540
|
+
} else {
|
|
48541
|
+
console.error(source_default.yellow(options.file ? `Trader not found: "${nameOrId}"` : "No PURPOSE document found."));
|
|
48542
|
+
}
|
|
48513
48543
|
} else {
|
|
48514
48544
|
const message = error49 instanceof Error ? error49.message : String(error49);
|
|
48515
|
-
|
|
48545
|
+
if (options.format === "json") {
|
|
48546
|
+
console.log(JSON.stringify({ error: message }));
|
|
48547
|
+
} else {
|
|
48548
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
48549
|
+
}
|
|
48516
48550
|
}
|
|
48517
48551
|
process.exitCode = error49 instanceof ApiError ? 2 : 1;
|
|
48518
48552
|
}
|
|
@@ -48633,6 +48667,221 @@ function registerTraderCommand(program2) {
|
|
|
48633
48667
|
process.exitCode = error49 instanceof ApiError ? 2 : 1;
|
|
48634
48668
|
}
|
|
48635
48669
|
});
|
|
48670
|
+
const AUTONOMY_LEVELS = ["OBSERVE_ONLY", "SUGGEST", "AUTO_WITH_APPROVAL", "FULLY_AUTONOMOUS"];
|
|
48671
|
+
const agent = trader.command("agent").description("Manage autonomous trading agent");
|
|
48672
|
+
agent.command("status").description("Show agent runtime status").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (agentId, options) => {
|
|
48673
|
+
const jsonMode = options.format === "json";
|
|
48674
|
+
try {
|
|
48675
|
+
const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/status`);
|
|
48676
|
+
if (jsonMode) {
|
|
48677
|
+
const safeOutput = {
|
|
48678
|
+
agentId,
|
|
48679
|
+
state: result.state,
|
|
48680
|
+
autonomyLevel: result.autonomyLevel,
|
|
48681
|
+
paused: result.paused,
|
|
48682
|
+
killed: result.killed,
|
|
48683
|
+
override: result.humanOverride,
|
|
48684
|
+
openPositions: result.openPositions ?? 0,
|
|
48685
|
+
dailyTradeCount: result.dailyTradeCount ?? 0,
|
|
48686
|
+
dailyPnlUsd: result.dailyPnlUsd ?? null,
|
|
48687
|
+
lastTransitionAt: result.lastTransitionAt ?? null,
|
|
48688
|
+
healthTimestamp: result.healthTimestamp ?? null
|
|
48689
|
+
};
|
|
48690
|
+
console.log(JSON.stringify(safeOutput, null, 2));
|
|
48691
|
+
} else {
|
|
48692
|
+
console.log("");
|
|
48693
|
+
console.log(source_default.bold.cyan(" Agent Status"));
|
|
48694
|
+
console.log(source_default.gray(" " + "\u2500".repeat(50)));
|
|
48695
|
+
console.log("");
|
|
48696
|
+
console.log(` ${source_default.gray("Agent ID:")} ${source_default.white(agentId)}`);
|
|
48697
|
+
console.log(` ${source_default.gray("State:")} ${formatAgentState(result.state)}`);
|
|
48698
|
+
console.log(` ${source_default.gray("Autonomy:")} ${source_default.white(result.autonomyLevel)}`);
|
|
48699
|
+
console.log(` ${source_default.gray("Paused:")} ${result.paused ? source_default.yellow("yes") : source_default.green("no")}`);
|
|
48700
|
+
console.log(` ${source_default.gray("Killed:")} ${result.killed ? source_default.red("yes") : source_default.green("no")}`);
|
|
48701
|
+
console.log(` ${source_default.gray("Override:")} ${result.humanOverride ? source_default.yellow("active") : source_default.dim("none")}`);
|
|
48702
|
+
console.log(` ${source_default.gray("Open positions:")} ${source_default.white(String(result.openPositions ?? 0))}`);
|
|
48703
|
+
console.log(` ${source_default.gray("Trades today:")} ${source_default.white(String(result.dailyTradeCount ?? 0))}`);
|
|
48704
|
+
console.log(` ${source_default.gray("Daily PnL:")} ${formatPnl2(result.dailyPnlUsd)}`);
|
|
48705
|
+
console.log("");
|
|
48706
|
+
}
|
|
48707
|
+
} catch (error49) {
|
|
48708
|
+
handleAgentError(error49, jsonMode, agentId, "get agent status");
|
|
48709
|
+
}
|
|
48710
|
+
});
|
|
48711
|
+
agent.command("pause").description("Pause the agent (stops all trading)").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (agentId, options) => {
|
|
48712
|
+
const jsonMode = options.format === "json";
|
|
48713
|
+
try {
|
|
48714
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, { method: "POST" });
|
|
48715
|
+
if (jsonMode) {
|
|
48716
|
+
console.log(JSON.stringify({ agentId, paused: true }));
|
|
48717
|
+
} else {
|
|
48718
|
+
console.log("");
|
|
48719
|
+
console.log(source_default.yellow(` Agent ${agentId} paused.`));
|
|
48720
|
+
console.log(source_default.dim(" Resume with: trading-boy trader agent resume " + agentId));
|
|
48721
|
+
console.log("");
|
|
48722
|
+
}
|
|
48723
|
+
} catch (error49) {
|
|
48724
|
+
handleAgentError(error49, jsonMode, agentId, "pause agent");
|
|
48725
|
+
}
|
|
48726
|
+
});
|
|
48727
|
+
agent.command("resume").description("Resume a paused agent").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (agentId, options) => {
|
|
48728
|
+
const jsonMode = options.format === "json";
|
|
48729
|
+
try {
|
|
48730
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, { method: "POST" });
|
|
48731
|
+
if (jsonMode) {
|
|
48732
|
+
console.log(JSON.stringify({ agentId, resumed: true }));
|
|
48733
|
+
} else {
|
|
48734
|
+
console.log("");
|
|
48735
|
+
console.log(source_default.green(` Agent ${agentId} resumed.`));
|
|
48736
|
+
console.log("");
|
|
48737
|
+
}
|
|
48738
|
+
} catch (error49) {
|
|
48739
|
+
handleAgentError(error49, jsonMode, agentId, "resume agent");
|
|
48740
|
+
}
|
|
48741
|
+
});
|
|
48742
|
+
agent.command("kill").description("Kill the agent permanently (until revived)").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).addOption(new Option("--force", "Skip confirmation prompt")).action(async (agentId, options) => {
|
|
48743
|
+
const jsonMode = options.format === "json";
|
|
48744
|
+
if (jsonMode && !options.force) {
|
|
48745
|
+
console.error(JSON.stringify({ error: "--force is required with --format json" }));
|
|
48746
|
+
process.exitCode = 1;
|
|
48747
|
+
return;
|
|
48748
|
+
}
|
|
48749
|
+
if (!options.force) {
|
|
48750
|
+
const proceed = await esm_default4({
|
|
48751
|
+
message: `Are you sure you want to kill agent ${agentId}? This is permanent until revived.`
|
|
48752
|
+
});
|
|
48753
|
+
if (!proceed) {
|
|
48754
|
+
console.log(source_default.dim(" Kill cancelled."));
|
|
48755
|
+
return;
|
|
48756
|
+
}
|
|
48757
|
+
}
|
|
48758
|
+
try {
|
|
48759
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/kill`, { method: "POST" });
|
|
48760
|
+
if (jsonMode) {
|
|
48761
|
+
console.log(JSON.stringify({ agentId, killed: true }));
|
|
48762
|
+
} else {
|
|
48763
|
+
console.log("");
|
|
48764
|
+
console.log(source_default.red(` Agent ${agentId} killed.`));
|
|
48765
|
+
console.log(source_default.dim(" The agent will not trade until manually revived."));
|
|
48766
|
+
console.log("");
|
|
48767
|
+
}
|
|
48768
|
+
} catch (error49) {
|
|
48769
|
+
handleAgentError(error49, jsonMode, agentId, "kill agent");
|
|
48770
|
+
}
|
|
48771
|
+
});
|
|
48772
|
+
agent.command("autonomy").description("Set agent autonomy level").argument("<agent-id>", "Agent ID").argument("<level>", `Autonomy level: ${AUTONOMY_LEVELS.join(" | ")}`).addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (agentId, level, options) => {
|
|
48773
|
+
const jsonMode = options.format === "json";
|
|
48774
|
+
const upperLevel = level.toUpperCase();
|
|
48775
|
+
if (!AUTONOMY_LEVELS.includes(upperLevel)) {
|
|
48776
|
+
const msg = `Invalid autonomy level. Must be one of: ${AUTONOMY_LEVELS.join(", ")}`;
|
|
48777
|
+
if (jsonMode) {
|
|
48778
|
+
console.error(JSON.stringify({ error: msg }));
|
|
48779
|
+
} else {
|
|
48780
|
+
console.error(source_default.red(` ${msg}`));
|
|
48781
|
+
}
|
|
48782
|
+
process.exitCode = 1;
|
|
48783
|
+
return;
|
|
48784
|
+
}
|
|
48785
|
+
try {
|
|
48786
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/autonomy`, {
|
|
48787
|
+
method: "PUT",
|
|
48788
|
+
body: { level: upperLevel }
|
|
48789
|
+
});
|
|
48790
|
+
if (jsonMode) {
|
|
48791
|
+
console.log(JSON.stringify({ agentId, autonomyLevel: upperLevel }));
|
|
48792
|
+
} else {
|
|
48793
|
+
console.log("");
|
|
48794
|
+
console.log(source_default.green(` Autonomy level set to: ${source_default.white(upperLevel)}`));
|
|
48795
|
+
console.log("");
|
|
48796
|
+
}
|
|
48797
|
+
} catch (error49) {
|
|
48798
|
+
handleAgentError(error49, jsonMode, agentId, "set autonomy");
|
|
48799
|
+
}
|
|
48800
|
+
});
|
|
48801
|
+
agent.command("override").description("Set human override (agent defers all decisions to human)").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).addOption(new Option("--force", "Skip confirmation prompt")).action(async (agentId, options) => {
|
|
48802
|
+
const jsonMode = options.format === "json";
|
|
48803
|
+
if (jsonMode && !options.force) {
|
|
48804
|
+
console.error(JSON.stringify({ error: "--force is required with --format json" }));
|
|
48805
|
+
process.exitCode = 1;
|
|
48806
|
+
return;
|
|
48807
|
+
}
|
|
48808
|
+
if (!options.force) {
|
|
48809
|
+
const proceed = await esm_default4({
|
|
48810
|
+
message: `Are you sure you want to set human override for agent ${agentId}?`
|
|
48811
|
+
});
|
|
48812
|
+
if (!proceed) {
|
|
48813
|
+
console.log(source_default.dim(" Override cancelled."));
|
|
48814
|
+
return;
|
|
48815
|
+
}
|
|
48816
|
+
}
|
|
48817
|
+
try {
|
|
48818
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/override`, { method: "POST" });
|
|
48819
|
+
if (jsonMode) {
|
|
48820
|
+
console.log(JSON.stringify({ agentId, humanOverride: true }));
|
|
48821
|
+
} else {
|
|
48822
|
+
console.log("");
|
|
48823
|
+
console.log(source_default.yellow(` Human override set for agent ${agentId}.`));
|
|
48824
|
+
console.log(source_default.dim(" The agent will defer all decisions to you."));
|
|
48825
|
+
console.log(source_default.dim(" Clear with: trading-boy trader agent clear-override " + agentId));
|
|
48826
|
+
console.log("");
|
|
48827
|
+
}
|
|
48828
|
+
} catch (error49) {
|
|
48829
|
+
handleAgentError(error49, jsonMode, agentId, "set override");
|
|
48830
|
+
}
|
|
48831
|
+
});
|
|
48832
|
+
agent.command("clear-override").description("Clear human override (return control to agent)").argument("<agent-id>", "Agent ID").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (agentId, options) => {
|
|
48833
|
+
const jsonMode = options.format === "json";
|
|
48834
|
+
try {
|
|
48835
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/clear-override`, { method: "POST" });
|
|
48836
|
+
if (jsonMode) {
|
|
48837
|
+
console.log(JSON.stringify({ agentId, humanOverride: false }));
|
|
48838
|
+
} else {
|
|
48839
|
+
console.log("");
|
|
48840
|
+
console.log(source_default.green(` Human override cleared for agent ${agentId}.`));
|
|
48841
|
+
console.log(source_default.dim(" The agent will resume autonomous decision-making."));
|
|
48842
|
+
console.log("");
|
|
48843
|
+
}
|
|
48844
|
+
} catch (error49) {
|
|
48845
|
+
handleAgentError(error49, jsonMode, agentId, "clear override");
|
|
48846
|
+
}
|
|
48847
|
+
});
|
|
48848
|
+
}
|
|
48849
|
+
function formatAgentState(state) {
|
|
48850
|
+
const colors8 = {
|
|
48851
|
+
"IDLE": source_default.dim,
|
|
48852
|
+
"SCANNING": source_default.cyan,
|
|
48853
|
+
"ANALYZING": source_default.blue,
|
|
48854
|
+
"EXECUTING": source_default.yellow,
|
|
48855
|
+
"PAUSED": source_default.yellow,
|
|
48856
|
+
"KILLED": source_default.red
|
|
48857
|
+
};
|
|
48858
|
+
return (colors8[state] ?? source_default.white)(state);
|
|
48859
|
+
}
|
|
48860
|
+
function formatPnl2(pnl) {
|
|
48861
|
+
if (pnl === void 0 || pnl === null)
|
|
48862
|
+
return source_default.dim("n/a");
|
|
48863
|
+
const sign = pnl >= 0 ? "+" : "";
|
|
48864
|
+
const color = pnl >= 0 ? source_default.green : source_default.red;
|
|
48865
|
+
return color(`${sign}$${pnl.toFixed(2)}`);
|
|
48866
|
+
}
|
|
48867
|
+
function handleAgentError(error49, jsonMode, agentId, action) {
|
|
48868
|
+
if (error49 instanceof ApiError && error49.status === 404) {
|
|
48869
|
+
const msg = `Agent not found: "${agentId}"`;
|
|
48870
|
+
if (jsonMode) {
|
|
48871
|
+
console.error(JSON.stringify({ error: msg }));
|
|
48872
|
+
} else {
|
|
48873
|
+
console.error(source_default.yellow(msg));
|
|
48874
|
+
}
|
|
48875
|
+
} else {
|
|
48876
|
+
const message = error49 instanceof Error ? error49.message : String(error49);
|
|
48877
|
+
logger12.error({ error: message }, `Failed to ${action}`);
|
|
48878
|
+
if (jsonMode) {
|
|
48879
|
+
console.error(JSON.stringify({ error: message }));
|
|
48880
|
+
} else {
|
|
48881
|
+
console.error(source_default.red(`Error: ${message}`));
|
|
48882
|
+
}
|
|
48883
|
+
}
|
|
48884
|
+
process.exitCode = error49 instanceof ApiError ? 2 : 1;
|
|
48636
48885
|
}
|
|
48637
48886
|
function parseFloatOption(value) {
|
|
48638
48887
|
return parseFloat(value);
|
|
@@ -49762,7 +50011,7 @@ function registerAuditCommand(program2) {
|
|
|
49762
50011
|
|
|
49763
50012
|
// dist/commands/config-cmd.js
|
|
49764
50013
|
init_source();
|
|
49765
|
-
import { readFileSync as
|
|
50014
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync2 } from "node:fs";
|
|
49766
50015
|
import { resolve } from "node:path";
|
|
49767
50016
|
init_utils();
|
|
49768
50017
|
var logger17 = createLogger("cli-config");
|
|
@@ -49862,7 +50111,7 @@ function parseEnvFile(filePath) {
|
|
|
49862
50111
|
if (!existsSync2(filePath)) {
|
|
49863
50112
|
return { entries, lines };
|
|
49864
50113
|
}
|
|
49865
|
-
const content =
|
|
50114
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
49866
50115
|
const rawLines = content.split("\n");
|
|
49867
50116
|
for (const line of rawLines) {
|
|
49868
50117
|
lines.push(line);
|
|
@@ -49901,11 +50150,11 @@ function writeEnvValue(filePath, key, value) {
|
|
|
49901
50150
|
}
|
|
49902
50151
|
return line;
|
|
49903
50152
|
});
|
|
49904
|
-
|
|
50153
|
+
writeFileSync3(filePath, updatedLines.join("\n"));
|
|
49905
50154
|
} else {
|
|
49906
|
-
const existing = existsSync2(filePath) ?
|
|
50155
|
+
const existing = existsSync2(filePath) ? readFileSync4(filePath, "utf-8") : "";
|
|
49907
50156
|
const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
|
|
49908
|
-
|
|
50157
|
+
writeFileSync3(filePath, existing + separator + `${key}=${value}
|
|
49909
50158
|
`);
|
|
49910
50159
|
}
|
|
49911
50160
|
}
|
package/dist/commands/trader.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { createLogger } from '@trading-boy/core';
|
|
5
5
|
import { padRight } from '../utils.js';
|
|
6
6
|
import { apiRequest, ApiError } from '../api-client.js';
|
|
7
|
+
import { confirm } from '@inquirer/prompts';
|
|
7
8
|
// ─── Logger ───
|
|
8
9
|
const logger = createLogger('cli-trader');
|
|
9
10
|
// ─── Formatters ───
|
|
@@ -423,11 +424,21 @@ export function registerTraderCommand(program) {
|
|
|
423
424
|
}
|
|
424
425
|
catch (error) {
|
|
425
426
|
if (error instanceof ApiError && error.status === 404) {
|
|
426
|
-
|
|
427
|
+
if (options.format === 'json') {
|
|
428
|
+
console.log(JSON.stringify({ soul: null, message: options.file ? `Trader not found: "${nameOrId}"` : 'No SOUL document found' }));
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No SOUL document found.'));
|
|
432
|
+
}
|
|
427
433
|
}
|
|
428
434
|
else {
|
|
429
435
|
const message = error instanceof Error ? error.message : String(error);
|
|
430
|
-
|
|
436
|
+
if (options.format === 'json') {
|
|
437
|
+
console.log(JSON.stringify({ error: message }));
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
441
|
+
}
|
|
431
442
|
}
|
|
432
443
|
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
433
444
|
}
|
|
@@ -483,11 +494,21 @@ export function registerTraderCommand(program) {
|
|
|
483
494
|
}
|
|
484
495
|
catch (error) {
|
|
485
496
|
if (error instanceof ApiError && error.status === 404) {
|
|
486
|
-
|
|
497
|
+
if (options.format === 'json') {
|
|
498
|
+
console.log(JSON.stringify({ purpose: null, message: options.file ? `Trader not found: "${nameOrId}"` : 'No PURPOSE document found' }));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No PURPOSE document found.'));
|
|
502
|
+
}
|
|
487
503
|
}
|
|
488
504
|
else {
|
|
489
505
|
const message = error instanceof Error ? error.message : String(error);
|
|
490
|
-
|
|
506
|
+
if (options.format === 'json') {
|
|
507
|
+
console.log(JSON.stringify({ error: message }));
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
511
|
+
}
|
|
491
512
|
}
|
|
492
513
|
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
493
514
|
}
|
|
@@ -634,6 +655,288 @@ export function registerTraderCommand(program) {
|
|
|
634
655
|
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
635
656
|
}
|
|
636
657
|
});
|
|
658
|
+
// ─── agent (subcommand group) ───
|
|
659
|
+
const AUTONOMY_LEVELS = ['OBSERVE_ONLY', 'SUGGEST', 'AUTO_WITH_APPROVAL', 'FULLY_AUTONOMOUS'];
|
|
660
|
+
const agent = trader
|
|
661
|
+
.command('agent')
|
|
662
|
+
.description('Manage autonomous trading agent');
|
|
663
|
+
// ─── agent status ───
|
|
664
|
+
agent
|
|
665
|
+
.command('status')
|
|
666
|
+
.description('Show agent runtime status')
|
|
667
|
+
.argument('<agent-id>', 'Agent ID')
|
|
668
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
669
|
+
.action(async (agentId, options) => {
|
|
670
|
+
const jsonMode = options.format === 'json';
|
|
671
|
+
try {
|
|
672
|
+
const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/status`);
|
|
673
|
+
if (jsonMode) {
|
|
674
|
+
const safeOutput = {
|
|
675
|
+
agentId,
|
|
676
|
+
state: result.state,
|
|
677
|
+
autonomyLevel: result.autonomyLevel,
|
|
678
|
+
paused: result.paused,
|
|
679
|
+
killed: result.killed,
|
|
680
|
+
override: result.humanOverride,
|
|
681
|
+
openPositions: result.openPositions ?? 0,
|
|
682
|
+
dailyTradeCount: result.dailyTradeCount ?? 0,
|
|
683
|
+
dailyPnlUsd: result.dailyPnlUsd ?? null,
|
|
684
|
+
lastTransitionAt: result.lastTransitionAt ?? null,
|
|
685
|
+
healthTimestamp: result.healthTimestamp ?? null,
|
|
686
|
+
};
|
|
687
|
+
console.log(JSON.stringify(safeOutput, null, 2));
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
console.log('');
|
|
691
|
+
console.log(chalk.bold.cyan(' Agent Status'));
|
|
692
|
+
console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
|
|
693
|
+
console.log('');
|
|
694
|
+
console.log(` ${chalk.gray('Agent ID:')} ${chalk.white(agentId)}`);
|
|
695
|
+
console.log(` ${chalk.gray('State:')} ${formatAgentState(result.state)}`);
|
|
696
|
+
console.log(` ${chalk.gray('Autonomy:')} ${chalk.white(result.autonomyLevel)}`);
|
|
697
|
+
console.log(` ${chalk.gray('Paused:')} ${result.paused ? chalk.yellow('yes') : chalk.green('no')}`);
|
|
698
|
+
console.log(` ${chalk.gray('Killed:')} ${result.killed ? chalk.red('yes') : chalk.green('no')}`);
|
|
699
|
+
console.log(` ${chalk.gray('Override:')} ${result.humanOverride ? chalk.yellow('active') : chalk.dim('none')}`);
|
|
700
|
+
console.log(` ${chalk.gray('Open positions:')} ${chalk.white(String(result.openPositions ?? 0))}`);
|
|
701
|
+
console.log(` ${chalk.gray('Trades today:')} ${chalk.white(String(result.dailyTradeCount ?? 0))}`);
|
|
702
|
+
console.log(` ${chalk.gray('Daily PnL:')} ${formatPnl(result.dailyPnlUsd)}`);
|
|
703
|
+
console.log('');
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
catch (error) {
|
|
707
|
+
handleAgentError(error, jsonMode, agentId, 'get agent status');
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
// ─── agent pause ───
|
|
711
|
+
agent
|
|
712
|
+
.command('pause')
|
|
713
|
+
.description('Pause the agent (stops all trading)')
|
|
714
|
+
.argument('<agent-id>', 'Agent ID')
|
|
715
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
716
|
+
.action(async (agentId, options) => {
|
|
717
|
+
const jsonMode = options.format === 'json';
|
|
718
|
+
try {
|
|
719
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, { method: 'POST' });
|
|
720
|
+
if (jsonMode) {
|
|
721
|
+
console.log(JSON.stringify({ agentId, paused: true }));
|
|
722
|
+
}
|
|
723
|
+
else {
|
|
724
|
+
console.log('');
|
|
725
|
+
console.log(chalk.yellow(` Agent ${agentId} paused.`));
|
|
726
|
+
console.log(chalk.dim(' Resume with: trading-boy trader agent resume ' + agentId));
|
|
727
|
+
console.log('');
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
handleAgentError(error, jsonMode, agentId, 'pause agent');
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
// ─── agent resume ───
|
|
735
|
+
agent
|
|
736
|
+
.command('resume')
|
|
737
|
+
.description('Resume a paused agent')
|
|
738
|
+
.argument('<agent-id>', 'Agent ID')
|
|
739
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
740
|
+
.action(async (agentId, options) => {
|
|
741
|
+
const jsonMode = options.format === 'json';
|
|
742
|
+
try {
|
|
743
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, { method: 'POST' });
|
|
744
|
+
if (jsonMode) {
|
|
745
|
+
console.log(JSON.stringify({ agentId, resumed: true }));
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
console.log('');
|
|
749
|
+
console.log(chalk.green(` Agent ${agentId} resumed.`));
|
|
750
|
+
console.log('');
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
catch (error) {
|
|
754
|
+
handleAgentError(error, jsonMode, agentId, 'resume agent');
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
// ─── agent kill ───
|
|
758
|
+
agent
|
|
759
|
+
.command('kill')
|
|
760
|
+
.description('Kill the agent permanently (until revived)')
|
|
761
|
+
.argument('<agent-id>', 'Agent ID')
|
|
762
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
763
|
+
.addOption(new Option('--force', 'Skip confirmation prompt'))
|
|
764
|
+
.action(async (agentId, options) => {
|
|
765
|
+
const jsonMode = options.format === 'json';
|
|
766
|
+
if (jsonMode && !options.force) {
|
|
767
|
+
console.error(JSON.stringify({ error: '--force is required with --format json' }));
|
|
768
|
+
process.exitCode = 1;
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (!options.force) {
|
|
772
|
+
const proceed = await confirm({
|
|
773
|
+
message: `Are you sure you want to kill agent ${agentId}? This is permanent until revived.`,
|
|
774
|
+
});
|
|
775
|
+
if (!proceed) {
|
|
776
|
+
console.log(chalk.dim(' Kill cancelled.'));
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/kill`, { method: 'POST' });
|
|
782
|
+
if (jsonMode) {
|
|
783
|
+
console.log(JSON.stringify({ agentId, killed: true }));
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
console.log('');
|
|
787
|
+
console.log(chalk.red(` Agent ${agentId} killed.`));
|
|
788
|
+
console.log(chalk.dim(' The agent will not trade until manually revived.'));
|
|
789
|
+
console.log('');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
handleAgentError(error, jsonMode, agentId, 'kill agent');
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
// ─── agent autonomy ───
|
|
797
|
+
agent
|
|
798
|
+
.command('autonomy')
|
|
799
|
+
.description('Set agent autonomy level')
|
|
800
|
+
.argument('<agent-id>', 'Agent ID')
|
|
801
|
+
.argument('<level>', `Autonomy level: ${AUTONOMY_LEVELS.join(' | ')}`)
|
|
802
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
803
|
+
.action(async (agentId, level, options) => {
|
|
804
|
+
const jsonMode = options.format === 'json';
|
|
805
|
+
const upperLevel = level.toUpperCase();
|
|
806
|
+
if (!AUTONOMY_LEVELS.includes(upperLevel)) {
|
|
807
|
+
const msg = `Invalid autonomy level. Must be one of: ${AUTONOMY_LEVELS.join(', ')}`;
|
|
808
|
+
if (jsonMode) {
|
|
809
|
+
console.error(JSON.stringify({ error: msg }));
|
|
810
|
+
}
|
|
811
|
+
else {
|
|
812
|
+
console.error(chalk.red(` ${msg}`));
|
|
813
|
+
}
|
|
814
|
+
process.exitCode = 1;
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
try {
|
|
818
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/autonomy`, {
|
|
819
|
+
method: 'PUT',
|
|
820
|
+
body: { level: upperLevel },
|
|
821
|
+
});
|
|
822
|
+
if (jsonMode) {
|
|
823
|
+
console.log(JSON.stringify({ agentId, autonomyLevel: upperLevel }));
|
|
824
|
+
}
|
|
825
|
+
else {
|
|
826
|
+
console.log('');
|
|
827
|
+
console.log(chalk.green(` Autonomy level set to: ${chalk.white(upperLevel)}`));
|
|
828
|
+
console.log('');
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch (error) {
|
|
832
|
+
handleAgentError(error, jsonMode, agentId, 'set autonomy');
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
// ─── agent override ───
|
|
836
|
+
agent
|
|
837
|
+
.command('override')
|
|
838
|
+
.description('Set human override (agent defers all decisions to human)')
|
|
839
|
+
.argument('<agent-id>', 'Agent ID')
|
|
840
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
841
|
+
.addOption(new Option('--force', 'Skip confirmation prompt'))
|
|
842
|
+
.action(async (agentId, options) => {
|
|
843
|
+
const jsonMode = options.format === 'json';
|
|
844
|
+
if (jsonMode && !options.force) {
|
|
845
|
+
console.error(JSON.stringify({ error: '--force is required with --format json' }));
|
|
846
|
+
process.exitCode = 1;
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (!options.force) {
|
|
850
|
+
const proceed = await confirm({
|
|
851
|
+
message: `Are you sure you want to set human override for agent ${agentId}?`,
|
|
852
|
+
});
|
|
853
|
+
if (!proceed) {
|
|
854
|
+
console.log(chalk.dim(' Override cancelled.'));
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/override`, { method: 'POST' });
|
|
860
|
+
if (jsonMode) {
|
|
861
|
+
console.log(JSON.stringify({ agentId, humanOverride: true }));
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
console.log('');
|
|
865
|
+
console.log(chalk.yellow(` Human override set for agent ${agentId}.`));
|
|
866
|
+
console.log(chalk.dim(' The agent will defer all decisions to you.'));
|
|
867
|
+
console.log(chalk.dim(' Clear with: trading-boy trader agent clear-override ' + agentId));
|
|
868
|
+
console.log('');
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
handleAgentError(error, jsonMode, agentId, 'set override');
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
// ─── agent clear-override ───
|
|
876
|
+
agent
|
|
877
|
+
.command('clear-override')
|
|
878
|
+
.description('Clear human override (return control to agent)')
|
|
879
|
+
.argument('<agent-id>', 'Agent ID')
|
|
880
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
881
|
+
.action(async (agentId, options) => {
|
|
882
|
+
const jsonMode = options.format === 'json';
|
|
883
|
+
try {
|
|
884
|
+
await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/clear-override`, { method: 'POST' });
|
|
885
|
+
if (jsonMode) {
|
|
886
|
+
console.log(JSON.stringify({ agentId, humanOverride: false }));
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
console.log('');
|
|
890
|
+
console.log(chalk.green(` Human override cleared for agent ${agentId}.`));
|
|
891
|
+
console.log(chalk.dim(' The agent will resume autonomous decision-making.'));
|
|
892
|
+
console.log('');
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
catch (error) {
|
|
896
|
+
handleAgentError(error, jsonMode, agentId, 'clear override');
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
// ─── Agent Helpers ───
|
|
901
|
+
function formatAgentState(state) {
|
|
902
|
+
const colors = {
|
|
903
|
+
'IDLE': chalk.dim,
|
|
904
|
+
'SCANNING': chalk.cyan,
|
|
905
|
+
'ANALYZING': chalk.blue,
|
|
906
|
+
'EXECUTING': chalk.yellow,
|
|
907
|
+
'PAUSED': chalk.yellow,
|
|
908
|
+
'KILLED': chalk.red,
|
|
909
|
+
};
|
|
910
|
+
return (colors[state] ?? chalk.white)(state);
|
|
911
|
+
}
|
|
912
|
+
function formatPnl(pnl) {
|
|
913
|
+
if (pnl === undefined || pnl === null)
|
|
914
|
+
return chalk.dim('n/a');
|
|
915
|
+
const sign = pnl >= 0 ? '+' : '';
|
|
916
|
+
const color = pnl >= 0 ? chalk.green : chalk.red;
|
|
917
|
+
return color(`${sign}$${pnl.toFixed(2)}`);
|
|
918
|
+
}
|
|
919
|
+
function handleAgentError(error, jsonMode, agentId, action) {
|
|
920
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
921
|
+
const msg = `Agent not found: "${agentId}"`;
|
|
922
|
+
if (jsonMode) {
|
|
923
|
+
console.error(JSON.stringify({ error: msg }));
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
console.error(chalk.yellow(msg));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
931
|
+
logger.error({ error: message }, `Failed to ${action}`);
|
|
932
|
+
if (jsonMode) {
|
|
933
|
+
console.error(JSON.stringify({ error: message }));
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
console.error(chalk.red(`Error: ${message}`));
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
process.exitCode = error instanceof ApiError ? 2 : 1;
|
|
637
940
|
}
|
|
638
941
|
// ─── Helpers ───
|
|
639
942
|
function parseFloatOption(value) {
|
package/dist/commands/watch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Option } from 'commander';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import { createLogger } from '@trading-boy/core';
|
|
3
4
|
import { formatContextOutput } from './context.js';
|
|
@@ -61,6 +62,7 @@ export function registerWatchCommand(program) {
|
|
|
61
62
|
.description('Watch token data with periodic refresh')
|
|
62
63
|
.option('-i, --interval <seconds>', 'Refresh interval in seconds', (v) => parseInt(v, 10))
|
|
63
64
|
.option('-c, --context', 'Watch full ContextPackage (default interval: 60s)')
|
|
65
|
+
.addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
|
|
64
66
|
.action(async (symbol, options) => {
|
|
65
67
|
const useContext = options.context === true;
|
|
66
68
|
const defaultInterval = useContext ? DEFAULT_CONTEXT_INTERVAL_SECONDS : DEFAULT_INTERVAL_SECONDS;
|
|
@@ -71,8 +73,22 @@ export function registerWatchCommand(program) {
|
|
|
71
73
|
return;
|
|
72
74
|
}
|
|
73
75
|
const mode = useContext ? 'context' : 'query';
|
|
76
|
+
const isJson = options.format === 'json';
|
|
74
77
|
logger.info({ symbol, intervalSeconds, mode }, 'Starting watch mode');
|
|
75
|
-
const
|
|
78
|
+
const renderFn = isJson
|
|
79
|
+
? async (sym) => {
|
|
80
|
+
try {
|
|
81
|
+
const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(sym.toUpperCase())}/context`);
|
|
82
|
+
console.log(JSON.stringify({ watchedAt: new Date().toISOString(), symbol: sym.toUpperCase(), ...pkg }, null, 2));
|
|
83
|
+
return pkg;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error), timestamp: new Date().toISOString() }));
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
: renderCycleRemote;
|
|
91
|
+
const state = startWatchLoop(symbol, intervalSeconds, renderFn);
|
|
76
92
|
// Clean exit on SIGINT (Ctrl+C)
|
|
77
93
|
const cleanup = () => {
|
|
78
94
|
logger.info('Watch mode stopped by user');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trading-boy/cli",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.20",
|
|
4
4
|
"description": "Trading Boy CLI — crypto context intelligence for traders and AI agents. Query real-time prices, funding rates, whale activity, and DeFi risk for 100+ Solana tokens and 229 Hyperliquid perpetuals.",
|
|
5
5
|
"homepage": "https://cabal.ventures",
|
|
6
6
|
"repository": {
|