@trading-boy/cli 1.2.18 → 1.2.19

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 {
@@ -48143,9 +48143,10 @@ function computeStats(decisions) {
48143
48143
  }
48144
48144
 
48145
48145
  // dist/commands/trader.js
48146
- import { readFileSync as readFileSync2 } from "node:fs";
48146
+ import { readFileSync as readFileSync3 } from "node:fs";
48147
48147
  init_source();
48148
48148
  init_utils();
48149
+ init_esm15();
48149
48150
  var logger12 = createLogger("cli-trader");
48150
48151
  function formatTraderOutput(trader, wallets) {
48151
48152
  const lines = [];
@@ -48424,7 +48425,7 @@ function registerTraderCommand(program2) {
48424
48425
  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
48426
  try {
48426
48427
  if (options.file) {
48427
- const document = readFileSync2(options.file, "utf-8");
48428
+ const document = readFileSync3(options.file, "utf-8");
48428
48429
  const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/soul`, { method: "PUT", body: { document } });
48429
48430
  if (options.format === "json") {
48430
48431
  console.log(JSON.stringify(result, null, 2));
@@ -48472,7 +48473,7 @@ function registerTraderCommand(program2) {
48472
48473
  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
48474
  try {
48474
48475
  if (options.file) {
48475
- const document = readFileSync2(options.file, "utf-8");
48476
+ const document = readFileSync3(options.file, "utf-8");
48476
48477
  const result = await apiRequest(`/api/v1/traders/${encodeURIComponent(nameOrId)}/purpose`, { method: "PUT", body: { document } });
48477
48478
  if (options.format === "json") {
48478
48479
  console.log(JSON.stringify(result, null, 2));
@@ -48633,6 +48634,221 @@ function registerTraderCommand(program2) {
48633
48634
  process.exitCode = error49 instanceof ApiError ? 2 : 1;
48634
48635
  }
48635
48636
  });
48637
+ const AUTONOMY_LEVELS = ["OBSERVE_ONLY", "SUGGEST", "AUTO_WITH_APPROVAL", "FULLY_AUTONOMOUS"];
48638
+ const agent = trader.command("agent").description("Manage autonomous trading agent");
48639
+ 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) => {
48640
+ const jsonMode = options.format === "json";
48641
+ try {
48642
+ const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/status`);
48643
+ if (jsonMode) {
48644
+ const safeOutput = {
48645
+ agentId,
48646
+ state: result.state,
48647
+ autonomyLevel: result.autonomyLevel,
48648
+ paused: result.paused,
48649
+ killed: result.killed,
48650
+ override: result.humanOverride,
48651
+ openPositions: result.openPositions ?? 0,
48652
+ dailyTradeCount: result.dailyTradeCount ?? 0,
48653
+ dailyPnlUsd: result.dailyPnlUsd ?? null,
48654
+ lastTransitionAt: result.lastTransitionAt ?? null,
48655
+ healthTimestamp: result.healthTimestamp ?? null
48656
+ };
48657
+ console.log(JSON.stringify(safeOutput, null, 2));
48658
+ } else {
48659
+ console.log("");
48660
+ console.log(source_default.bold.cyan(" Agent Status"));
48661
+ console.log(source_default.gray(" " + "\u2500".repeat(50)));
48662
+ console.log("");
48663
+ console.log(` ${source_default.gray("Agent ID:")} ${source_default.white(agentId)}`);
48664
+ console.log(` ${source_default.gray("State:")} ${formatAgentState(result.state)}`);
48665
+ console.log(` ${source_default.gray("Autonomy:")} ${source_default.white(result.autonomyLevel)}`);
48666
+ console.log(` ${source_default.gray("Paused:")} ${result.paused ? source_default.yellow("yes") : source_default.green("no")}`);
48667
+ console.log(` ${source_default.gray("Killed:")} ${result.killed ? source_default.red("yes") : source_default.green("no")}`);
48668
+ console.log(` ${source_default.gray("Override:")} ${result.humanOverride ? source_default.yellow("active") : source_default.dim("none")}`);
48669
+ console.log(` ${source_default.gray("Open positions:")} ${source_default.white(String(result.openPositions ?? 0))}`);
48670
+ console.log(` ${source_default.gray("Trades today:")} ${source_default.white(String(result.dailyTradeCount ?? 0))}`);
48671
+ console.log(` ${source_default.gray("Daily PnL:")} ${formatPnl2(result.dailyPnlUsd)}`);
48672
+ console.log("");
48673
+ }
48674
+ } catch (error49) {
48675
+ handleAgentError(error49, jsonMode, agentId, "get agent status");
48676
+ }
48677
+ });
48678
+ 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) => {
48679
+ const jsonMode = options.format === "json";
48680
+ try {
48681
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, { method: "POST" });
48682
+ if (jsonMode) {
48683
+ console.log(JSON.stringify({ agentId, paused: true }));
48684
+ } else {
48685
+ console.log("");
48686
+ console.log(source_default.yellow(` Agent ${agentId} paused.`));
48687
+ console.log(source_default.dim(" Resume with: trading-boy trader agent resume " + agentId));
48688
+ console.log("");
48689
+ }
48690
+ } catch (error49) {
48691
+ handleAgentError(error49, jsonMode, agentId, "pause agent");
48692
+ }
48693
+ });
48694
+ 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) => {
48695
+ const jsonMode = options.format === "json";
48696
+ try {
48697
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, { method: "POST" });
48698
+ if (jsonMode) {
48699
+ console.log(JSON.stringify({ agentId, resumed: true }));
48700
+ } else {
48701
+ console.log("");
48702
+ console.log(source_default.green(` Agent ${agentId} resumed.`));
48703
+ console.log("");
48704
+ }
48705
+ } catch (error49) {
48706
+ handleAgentError(error49, jsonMode, agentId, "resume agent");
48707
+ }
48708
+ });
48709
+ 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) => {
48710
+ const jsonMode = options.format === "json";
48711
+ if (jsonMode && !options.force) {
48712
+ console.error(JSON.stringify({ error: "--force is required with --format json" }));
48713
+ process.exitCode = 1;
48714
+ return;
48715
+ }
48716
+ if (!options.force) {
48717
+ const proceed = await esm_default4({
48718
+ message: `Are you sure you want to kill agent ${agentId}? This is permanent until revived.`
48719
+ });
48720
+ if (!proceed) {
48721
+ console.log(source_default.dim(" Kill cancelled."));
48722
+ return;
48723
+ }
48724
+ }
48725
+ try {
48726
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/kill`, { method: "POST" });
48727
+ if (jsonMode) {
48728
+ console.log(JSON.stringify({ agentId, killed: true }));
48729
+ } else {
48730
+ console.log("");
48731
+ console.log(source_default.red(` Agent ${agentId} killed.`));
48732
+ console.log(source_default.dim(" The agent will not trade until manually revived."));
48733
+ console.log("");
48734
+ }
48735
+ } catch (error49) {
48736
+ handleAgentError(error49, jsonMode, agentId, "kill agent");
48737
+ }
48738
+ });
48739
+ 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) => {
48740
+ const jsonMode = options.format === "json";
48741
+ const upperLevel = level.toUpperCase();
48742
+ if (!AUTONOMY_LEVELS.includes(upperLevel)) {
48743
+ const msg = `Invalid autonomy level. Must be one of: ${AUTONOMY_LEVELS.join(", ")}`;
48744
+ if (jsonMode) {
48745
+ console.error(JSON.stringify({ error: msg }));
48746
+ } else {
48747
+ console.error(source_default.red(` ${msg}`));
48748
+ }
48749
+ process.exitCode = 1;
48750
+ return;
48751
+ }
48752
+ try {
48753
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/autonomy`, {
48754
+ method: "PUT",
48755
+ body: { level: upperLevel }
48756
+ });
48757
+ if (jsonMode) {
48758
+ console.log(JSON.stringify({ agentId, autonomyLevel: upperLevel }));
48759
+ } else {
48760
+ console.log("");
48761
+ console.log(source_default.green(` Autonomy level set to: ${source_default.white(upperLevel)}`));
48762
+ console.log("");
48763
+ }
48764
+ } catch (error49) {
48765
+ handleAgentError(error49, jsonMode, agentId, "set autonomy");
48766
+ }
48767
+ });
48768
+ 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) => {
48769
+ const jsonMode = options.format === "json";
48770
+ if (jsonMode && !options.force) {
48771
+ console.error(JSON.stringify({ error: "--force is required with --format json" }));
48772
+ process.exitCode = 1;
48773
+ return;
48774
+ }
48775
+ if (!options.force) {
48776
+ const proceed = await esm_default4({
48777
+ message: `Are you sure you want to set human override for agent ${agentId}?`
48778
+ });
48779
+ if (!proceed) {
48780
+ console.log(source_default.dim(" Override cancelled."));
48781
+ return;
48782
+ }
48783
+ }
48784
+ try {
48785
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/override`, { method: "POST" });
48786
+ if (jsonMode) {
48787
+ console.log(JSON.stringify({ agentId, humanOverride: true }));
48788
+ } else {
48789
+ console.log("");
48790
+ console.log(source_default.yellow(` Human override set for agent ${agentId}.`));
48791
+ console.log(source_default.dim(" The agent will defer all decisions to you."));
48792
+ console.log(source_default.dim(" Clear with: trading-boy trader agent clear-override " + agentId));
48793
+ console.log("");
48794
+ }
48795
+ } catch (error49) {
48796
+ handleAgentError(error49, jsonMode, agentId, "set override");
48797
+ }
48798
+ });
48799
+ 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) => {
48800
+ const jsonMode = options.format === "json";
48801
+ try {
48802
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/clear-override`, { method: "POST" });
48803
+ if (jsonMode) {
48804
+ console.log(JSON.stringify({ agentId, humanOverride: false }));
48805
+ } else {
48806
+ console.log("");
48807
+ console.log(source_default.green(` Human override cleared for agent ${agentId}.`));
48808
+ console.log(source_default.dim(" The agent will resume autonomous decision-making."));
48809
+ console.log("");
48810
+ }
48811
+ } catch (error49) {
48812
+ handleAgentError(error49, jsonMode, agentId, "clear override");
48813
+ }
48814
+ });
48815
+ }
48816
+ function formatAgentState(state) {
48817
+ const colors8 = {
48818
+ "IDLE": source_default.dim,
48819
+ "SCANNING": source_default.cyan,
48820
+ "ANALYZING": source_default.blue,
48821
+ "EXECUTING": source_default.yellow,
48822
+ "PAUSED": source_default.yellow,
48823
+ "KILLED": source_default.red
48824
+ };
48825
+ return (colors8[state] ?? source_default.white)(state);
48826
+ }
48827
+ function formatPnl2(pnl) {
48828
+ if (pnl === void 0 || pnl === null)
48829
+ return source_default.dim("n/a");
48830
+ const sign = pnl >= 0 ? "+" : "";
48831
+ const color = pnl >= 0 ? source_default.green : source_default.red;
48832
+ return color(`${sign}$${pnl.toFixed(2)}`);
48833
+ }
48834
+ function handleAgentError(error49, jsonMode, agentId, action) {
48835
+ if (error49 instanceof ApiError && error49.status === 404) {
48836
+ const msg = `Agent not found: "${agentId}"`;
48837
+ if (jsonMode) {
48838
+ console.error(JSON.stringify({ error: msg }));
48839
+ } else {
48840
+ console.error(source_default.yellow(msg));
48841
+ }
48842
+ } else {
48843
+ const message = error49 instanceof Error ? error49.message : String(error49);
48844
+ logger12.error({ error: message }, `Failed to ${action}`);
48845
+ if (jsonMode) {
48846
+ console.error(JSON.stringify({ error: message }));
48847
+ } else {
48848
+ console.error(source_default.red(`Error: ${message}`));
48849
+ }
48850
+ }
48851
+ process.exitCode = error49 instanceof ApiError ? 2 : 1;
48636
48852
  }
48637
48853
  function parseFloatOption(value) {
48638
48854
  return parseFloat(value);
@@ -49762,7 +49978,7 @@ function registerAuditCommand(program2) {
49762
49978
 
49763
49979
  // dist/commands/config-cmd.js
49764
49980
  init_source();
49765
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "node:fs";
49981
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync2 } from "node:fs";
49766
49982
  import { resolve } from "node:path";
49767
49983
  init_utils();
49768
49984
  var logger17 = createLogger("cli-config");
@@ -49862,7 +50078,7 @@ function parseEnvFile(filePath) {
49862
50078
  if (!existsSync2(filePath)) {
49863
50079
  return { entries, lines };
49864
50080
  }
49865
- const content = readFileSync3(filePath, "utf-8");
50081
+ const content = readFileSync4(filePath, "utf-8");
49866
50082
  const rawLines = content.split("\n");
49867
50083
  for (const line of rawLines) {
49868
50084
  lines.push(line);
@@ -49901,11 +50117,11 @@ function writeEnvValue(filePath, key, value) {
49901
50117
  }
49902
50118
  return line;
49903
50119
  });
49904
- writeFileSync2(filePath, updatedLines.join("\n"));
50120
+ writeFileSync3(filePath, updatedLines.join("\n"));
49905
50121
  } else {
49906
- const existing = existsSync2(filePath) ? readFileSync3(filePath, "utf-8") : "";
50122
+ const existing = existsSync2(filePath) ? readFileSync4(filePath, "utf-8") : "";
49907
50123
  const separator = existing.endsWith("\n") || existing === "" ? "" : "\n";
49908
- writeFileSync2(filePath, existing + separator + `${key}=${value}
50124
+ writeFileSync3(filePath, existing + separator + `${key}=${value}
49909
50125
  `);
49910
50126
  }
49911
50127
  }
@@ -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 ───
@@ -634,6 +635,288 @@ export function registerTraderCommand(program) {
634
635
  process.exitCode = error instanceof ApiError ? 2 : 1;
635
636
  }
636
637
  });
638
+ // ─── agent (subcommand group) ───
639
+ const AUTONOMY_LEVELS = ['OBSERVE_ONLY', 'SUGGEST', 'AUTO_WITH_APPROVAL', 'FULLY_AUTONOMOUS'];
640
+ const agent = trader
641
+ .command('agent')
642
+ .description('Manage autonomous trading agent');
643
+ // ─── agent status ───
644
+ agent
645
+ .command('status')
646
+ .description('Show agent runtime status')
647
+ .argument('<agent-id>', 'Agent ID')
648
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
649
+ .action(async (agentId, options) => {
650
+ const jsonMode = options.format === 'json';
651
+ try {
652
+ const result = await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/status`);
653
+ if (jsonMode) {
654
+ const safeOutput = {
655
+ agentId,
656
+ state: result.state,
657
+ autonomyLevel: result.autonomyLevel,
658
+ paused: result.paused,
659
+ killed: result.killed,
660
+ override: result.humanOverride,
661
+ openPositions: result.openPositions ?? 0,
662
+ dailyTradeCount: result.dailyTradeCount ?? 0,
663
+ dailyPnlUsd: result.dailyPnlUsd ?? null,
664
+ lastTransitionAt: result.lastTransitionAt ?? null,
665
+ healthTimestamp: result.healthTimestamp ?? null,
666
+ };
667
+ console.log(JSON.stringify(safeOutput, null, 2));
668
+ }
669
+ else {
670
+ console.log('');
671
+ console.log(chalk.bold.cyan(' Agent Status'));
672
+ console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
673
+ console.log('');
674
+ console.log(` ${chalk.gray('Agent ID:')} ${chalk.white(agentId)}`);
675
+ console.log(` ${chalk.gray('State:')} ${formatAgentState(result.state)}`);
676
+ console.log(` ${chalk.gray('Autonomy:')} ${chalk.white(result.autonomyLevel)}`);
677
+ console.log(` ${chalk.gray('Paused:')} ${result.paused ? chalk.yellow('yes') : chalk.green('no')}`);
678
+ console.log(` ${chalk.gray('Killed:')} ${result.killed ? chalk.red('yes') : chalk.green('no')}`);
679
+ console.log(` ${chalk.gray('Override:')} ${result.humanOverride ? chalk.yellow('active') : chalk.dim('none')}`);
680
+ console.log(` ${chalk.gray('Open positions:')} ${chalk.white(String(result.openPositions ?? 0))}`);
681
+ console.log(` ${chalk.gray('Trades today:')} ${chalk.white(String(result.dailyTradeCount ?? 0))}`);
682
+ console.log(` ${chalk.gray('Daily PnL:')} ${formatPnl(result.dailyPnlUsd)}`);
683
+ console.log('');
684
+ }
685
+ }
686
+ catch (error) {
687
+ handleAgentError(error, jsonMode, agentId, 'get agent status');
688
+ }
689
+ });
690
+ // ─── agent pause ───
691
+ agent
692
+ .command('pause')
693
+ .description('Pause the agent (stops all trading)')
694
+ .argument('<agent-id>', 'Agent ID')
695
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
696
+ .action(async (agentId, options) => {
697
+ const jsonMode = options.format === 'json';
698
+ try {
699
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/pause`, { method: 'POST' });
700
+ if (jsonMode) {
701
+ console.log(JSON.stringify({ agentId, paused: true }));
702
+ }
703
+ else {
704
+ console.log('');
705
+ console.log(chalk.yellow(` Agent ${agentId} paused.`));
706
+ console.log(chalk.dim(' Resume with: trading-boy trader agent resume ' + agentId));
707
+ console.log('');
708
+ }
709
+ }
710
+ catch (error) {
711
+ handleAgentError(error, jsonMode, agentId, 'pause agent');
712
+ }
713
+ });
714
+ // ─── agent resume ───
715
+ agent
716
+ .command('resume')
717
+ .description('Resume a paused agent')
718
+ .argument('<agent-id>', 'Agent ID')
719
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
720
+ .action(async (agentId, options) => {
721
+ const jsonMode = options.format === 'json';
722
+ try {
723
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/resume`, { method: 'POST' });
724
+ if (jsonMode) {
725
+ console.log(JSON.stringify({ agentId, resumed: true }));
726
+ }
727
+ else {
728
+ console.log('');
729
+ console.log(chalk.green(` Agent ${agentId} resumed.`));
730
+ console.log('');
731
+ }
732
+ }
733
+ catch (error) {
734
+ handleAgentError(error, jsonMode, agentId, 'resume agent');
735
+ }
736
+ });
737
+ // ─── agent kill ───
738
+ agent
739
+ .command('kill')
740
+ .description('Kill the agent permanently (until revived)')
741
+ .argument('<agent-id>', 'Agent ID')
742
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
743
+ .addOption(new Option('--force', 'Skip confirmation prompt'))
744
+ .action(async (agentId, options) => {
745
+ const jsonMode = options.format === 'json';
746
+ if (jsonMode && !options.force) {
747
+ console.error(JSON.stringify({ error: '--force is required with --format json' }));
748
+ process.exitCode = 1;
749
+ return;
750
+ }
751
+ if (!options.force) {
752
+ const proceed = await confirm({
753
+ message: `Are you sure you want to kill agent ${agentId}? This is permanent until revived.`,
754
+ });
755
+ if (!proceed) {
756
+ console.log(chalk.dim(' Kill cancelled.'));
757
+ return;
758
+ }
759
+ }
760
+ try {
761
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/kill`, { method: 'POST' });
762
+ if (jsonMode) {
763
+ console.log(JSON.stringify({ agentId, killed: true }));
764
+ }
765
+ else {
766
+ console.log('');
767
+ console.log(chalk.red(` Agent ${agentId} killed.`));
768
+ console.log(chalk.dim(' The agent will not trade until manually revived.'));
769
+ console.log('');
770
+ }
771
+ }
772
+ catch (error) {
773
+ handleAgentError(error, jsonMode, agentId, 'kill agent');
774
+ }
775
+ });
776
+ // ─── agent autonomy ───
777
+ agent
778
+ .command('autonomy')
779
+ .description('Set agent autonomy level')
780
+ .argument('<agent-id>', 'Agent ID')
781
+ .argument('<level>', `Autonomy level: ${AUTONOMY_LEVELS.join(' | ')}`)
782
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
783
+ .action(async (agentId, level, options) => {
784
+ const jsonMode = options.format === 'json';
785
+ const upperLevel = level.toUpperCase();
786
+ if (!AUTONOMY_LEVELS.includes(upperLevel)) {
787
+ const msg = `Invalid autonomy level. Must be one of: ${AUTONOMY_LEVELS.join(', ')}`;
788
+ if (jsonMode) {
789
+ console.error(JSON.stringify({ error: msg }));
790
+ }
791
+ else {
792
+ console.error(chalk.red(` ${msg}`));
793
+ }
794
+ process.exitCode = 1;
795
+ return;
796
+ }
797
+ try {
798
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/autonomy`, {
799
+ method: 'PUT',
800
+ body: { level: upperLevel },
801
+ });
802
+ if (jsonMode) {
803
+ console.log(JSON.stringify({ agentId, autonomyLevel: upperLevel }));
804
+ }
805
+ else {
806
+ console.log('');
807
+ console.log(chalk.green(` Autonomy level set to: ${chalk.white(upperLevel)}`));
808
+ console.log('');
809
+ }
810
+ }
811
+ catch (error) {
812
+ handleAgentError(error, jsonMode, agentId, 'set autonomy');
813
+ }
814
+ });
815
+ // ─── agent override ───
816
+ agent
817
+ .command('override')
818
+ .description('Set human override (agent defers all decisions to human)')
819
+ .argument('<agent-id>', 'Agent ID')
820
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
821
+ .addOption(new Option('--force', 'Skip confirmation prompt'))
822
+ .action(async (agentId, options) => {
823
+ const jsonMode = options.format === 'json';
824
+ if (jsonMode && !options.force) {
825
+ console.error(JSON.stringify({ error: '--force is required with --format json' }));
826
+ process.exitCode = 1;
827
+ return;
828
+ }
829
+ if (!options.force) {
830
+ const proceed = await confirm({
831
+ message: `Are you sure you want to set human override for agent ${agentId}?`,
832
+ });
833
+ if (!proceed) {
834
+ console.log(chalk.dim(' Override cancelled.'));
835
+ return;
836
+ }
837
+ }
838
+ try {
839
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/override`, { method: 'POST' });
840
+ if (jsonMode) {
841
+ console.log(JSON.stringify({ agentId, humanOverride: true }));
842
+ }
843
+ else {
844
+ console.log('');
845
+ console.log(chalk.yellow(` Human override set for agent ${agentId}.`));
846
+ console.log(chalk.dim(' The agent will defer all decisions to you.'));
847
+ console.log(chalk.dim(' Clear with: trading-boy trader agent clear-override ' + agentId));
848
+ console.log('');
849
+ }
850
+ }
851
+ catch (error) {
852
+ handleAgentError(error, jsonMode, agentId, 'set override');
853
+ }
854
+ });
855
+ // ─── agent clear-override ───
856
+ agent
857
+ .command('clear-override')
858
+ .description('Clear human override (return control to agent)')
859
+ .argument('<agent-id>', 'Agent ID')
860
+ .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
861
+ .action(async (agentId, options) => {
862
+ const jsonMode = options.format === 'json';
863
+ try {
864
+ await apiRequest(`/api/v1/agents/${encodeURIComponent(agentId)}/clear-override`, { method: 'POST' });
865
+ if (jsonMode) {
866
+ console.log(JSON.stringify({ agentId, humanOverride: false }));
867
+ }
868
+ else {
869
+ console.log('');
870
+ console.log(chalk.green(` Human override cleared for agent ${agentId}.`));
871
+ console.log(chalk.dim(' The agent will resume autonomous decision-making.'));
872
+ console.log('');
873
+ }
874
+ }
875
+ catch (error) {
876
+ handleAgentError(error, jsonMode, agentId, 'clear override');
877
+ }
878
+ });
879
+ }
880
+ // ─── Agent Helpers ───
881
+ function formatAgentState(state) {
882
+ const colors = {
883
+ 'IDLE': chalk.dim,
884
+ 'SCANNING': chalk.cyan,
885
+ 'ANALYZING': chalk.blue,
886
+ 'EXECUTING': chalk.yellow,
887
+ 'PAUSED': chalk.yellow,
888
+ 'KILLED': chalk.red,
889
+ };
890
+ return (colors[state] ?? chalk.white)(state);
891
+ }
892
+ function formatPnl(pnl) {
893
+ if (pnl === undefined || pnl === null)
894
+ return chalk.dim('n/a');
895
+ const sign = pnl >= 0 ? '+' : '';
896
+ const color = pnl >= 0 ? chalk.green : chalk.red;
897
+ return color(`${sign}$${pnl.toFixed(2)}`);
898
+ }
899
+ function handleAgentError(error, jsonMode, agentId, action) {
900
+ if (error instanceof ApiError && error.status === 404) {
901
+ const msg = `Agent not found: "${agentId}"`;
902
+ if (jsonMode) {
903
+ console.error(JSON.stringify({ error: msg }));
904
+ }
905
+ else {
906
+ console.error(chalk.yellow(msg));
907
+ }
908
+ }
909
+ else {
910
+ const message = error instanceof Error ? error.message : String(error);
911
+ logger.error({ error: message }, `Failed to ${action}`);
912
+ if (jsonMode) {
913
+ console.error(JSON.stringify({ error: message }));
914
+ }
915
+ else {
916
+ console.error(chalk.red(`Error: ${message}`));
917
+ }
918
+ }
919
+ process.exitCode = error instanceof ApiError ? 2 : 1;
637
920
  }
638
921
  // ─── Helpers ───
639
922
  function parseFloatOption(value) {
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.19",
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": {