@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.
@@ -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 readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
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
- writeFileSync3(this.tempFile, this.text, opt);
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 = readFileSync4(this.tempFile);
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 state = startWatchLoop(symbol2, intervalSeconds, renderCycleRemote);
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 readFileSync2 } from "node:fs";
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 = readFileSync2(options.file, "utf-8");
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
- console.error(source_default.yellow(options.file ? `Trader not found: "${nameOrId}"` : "No SOUL document found."));
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
- console.error(source_default.red(`Error: ${message}`));
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 = readFileSync2(options.file, "utf-8");
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
- console.error(source_default.yellow(options.file ? `Trader not found: "${nameOrId}"` : "No PURPOSE document found."));
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
- console.error(source_default.red(`Error: ${message}`));
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 readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "node:fs";
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 = readFileSync3(filePath, "utf-8");
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
- writeFileSync2(filePath, updatedLines.join("\n"));
50153
+ writeFileSync3(filePath, updatedLines.join("\n"));
49905
50154
  } else {
49906
- const existing = existsSync2(filePath) ? readFileSync3(filePath, "utf-8") : "";
50155
+ const existing = existsSync2(filePath) ? readFileSync4(filePath, "utf-8") : "";
49907
50156
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
49908
- writeFileSync2(filePath, existing + separator + `${key}=${value}
50157
+ writeFileSync3(filePath, existing + separator + `${key}=${value}
49909
50158
  `);
49910
50159
  }
49911
50160
  }
@@ -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
- console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No SOUL document found.'));
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
- console.error(chalk.red(`Error: ${message}`));
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
- console.error(chalk.yellow(options.file ? `Trader not found: "${nameOrId}"` : 'No PURPOSE document found.'));
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
- console.error(chalk.red(`Error: ${message}`));
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) {
@@ -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 state = startWatchLoop(symbol, intervalSeconds, renderCycleRemote);
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.18",
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": {