@trading-boy/cli 1.4.1 → 1.5.1

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.
@@ -51201,6 +51201,12 @@ function contextToQueryResult(pkg) {
51201
51201
  function registerQueryCommand(program2) {
51202
51202
  program2.command("query <symbol>").description("Query token info, price, funding rate, and whale activity").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (symbol2, options) => {
51203
51203
  try {
51204
+ if (!await isRemoteMode()) {
51205
+ console.error(source_default.yellow("This command requires a remote API connection."));
51206
+ console.error(source_default.dim(" Run: trading-boy login"));
51207
+ process.exitCode = 1;
51208
+ return;
51209
+ }
51204
51210
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol2.toUpperCase())}/context`);
51205
51211
  const result = contextToQueryResult(pkg);
51206
51212
  if (result.token.name === null && result.token.chains.length === 0 && result.price.price === null) {
@@ -51497,6 +51503,12 @@ function buildHistoryQueryParams(options) {
51497
51503
  function registerContextCommand(program2) {
51498
51504
  program2.command("context <symbol>").description("Assemble and display a full ContextPackage for a token").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).option("--at <date>", "Fetch historical context at a specific date/time (e.g., 2026-01-15 or 2026-01-15T12:00:00Z)").option("--from <date>", "Start of time range for context history").option("--to <date>", "End of time range for context history").addOption(new Option("--interval <interval>", "Sampling interval for range queries").choices(["15m", "1h", "4h", "1d"])).option("--trader-id <id>", "Trader ID for personalized context").action(async (symbol2, options) => {
51499
51505
  try {
51506
+ if (!await isRemoteMode()) {
51507
+ console.error(source_default.yellow("This command requires a remote API connection."));
51508
+ console.error(source_default.dim(" Run: trading-boy login"));
51509
+ process.exitCode = 1;
51510
+ return;
51511
+ }
51500
51512
  if (options.from && !options.to || !options.from && options.to) {
51501
51513
  console.error(source_default.red("Error: Both --from and --to must be provided for range queries."));
51502
51514
  process.exitCode = 1;
@@ -51533,6 +51545,12 @@ function registerContextCommand(program2) {
51533
51545
  } else {
51534
51546
  const traderQs = options.traderId ? `?traderId=${encodeURIComponent(options.traderId)}` : "";
51535
51547
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol2.toUpperCase())}/context${traderQs}`);
51548
+ if (pkg.token.name == null && (!Array.isArray(pkg.token.chains) || pkg.token.chains.length === 0) && pkg.market?.price == null) {
51549
+ console.error(source_default.red(`Error: Token not found: ${symbol2.toUpperCase()}`));
51550
+ console.error(source_default.dim(" Check the symbol and try again. Use a symbol like SOL, JUP, or BONK."));
51551
+ process.exitCode = 1;
51552
+ return;
51553
+ }
51536
51554
  if (options.format === "json") {
51537
51555
  console.log(JSON.stringify(pkg, null, 2));
51538
51556
  } else {
@@ -51805,6 +51823,12 @@ function registerRiskCommand(program2) {
51805
51823
  process.exitCode = 1;
51806
51824
  return;
51807
51825
  }
51826
+ if (!await isRemoteMode()) {
51827
+ console.error(source_default.yellow("This command requires a remote API connection."));
51828
+ console.error(source_default.dim(" Run: trading-boy login or trading-boy subscribe"));
51829
+ process.exitCode = 1;
51830
+ return;
51831
+ }
51808
51832
  try {
51809
51833
  const apiResult = await apiRequest(`/api/v1/protocols/${encodeURIComponent(protocol.toLowerCase())}/risk`);
51810
51834
  const result = {
@@ -51981,6 +52005,12 @@ function registerDecisionsCommand(program2) {
51981
52005
  }
51982
52006
  } catch (error49) {
51983
52007
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
52008
+ const connErr = formatConnectionError(message);
52009
+ if (connErr) {
52010
+ console.error(connErr);
52011
+ process.exitCode = 1;
52012
+ return;
52013
+ }
51984
52014
  logger11.error({ error: message }, "Failed to fetch decisions");
51985
52015
  console.error(source_default.red(`Error: ${message}`));
51986
52016
  process.exitCode = error49 instanceof ApiError ? 2 : 1;
@@ -52011,6 +52041,12 @@ function registerDecisionsCommand(program2) {
52011
52041
  }
52012
52042
  } catch (error49) {
52013
52043
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
52044
+ const connErr = formatConnectionError(message);
52045
+ if (connErr) {
52046
+ console.error(connErr);
52047
+ process.exitCode = 1;
52048
+ return;
52049
+ }
52014
52050
  logger11.error({ error: message }, "Failed to fetch stats");
52015
52051
  console.error(source_default.red(`Error: ${message}`));
52016
52052
  process.exitCode = error49 instanceof ApiError ? 2 : 1;
@@ -53741,6 +53777,12 @@ function registerJournalCommand(program2) {
53741
53777
  console.log(`Entry logged: id=${result.id} hash=${result.hash}`);
53742
53778
  } catch (error49) {
53743
53779
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
53780
+ const connErr = formatConnectionError(message);
53781
+ if (connErr) {
53782
+ console.error(connErr);
53783
+ process.exitCode = 1;
53784
+ return;
53785
+ }
53744
53786
  console.error(`Error: ${message}`);
53745
53787
  if (error49 instanceof ApiError && error49.body && typeof error49.body === "object") {
53746
53788
  const detail = error49.body.error ?? error49.body.code;
@@ -53773,6 +53815,12 @@ function registerJournalCommand(program2) {
53773
53815
  console.log(`Exit logged: id=${result.id} hash=${result.hash}`);
53774
53816
  } catch (error49) {
53775
53817
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
53818
+ const connErr = formatConnectionError(message);
53819
+ if (connErr) {
53820
+ console.error(connErr);
53821
+ process.exitCode = 1;
53822
+ return;
53823
+ }
53776
53824
  console.error(`Error: ${message}`);
53777
53825
  if (error49 instanceof ApiError && error49.body && typeof error49.body === "object") {
53778
53826
  const detail = error49.body.error ?? error49.body.code;
@@ -53817,6 +53865,12 @@ function registerJournalCommand(program2) {
53817
53865
  console.log(`Total: ${decisions.length} decision(s)`);
53818
53866
  } catch (error49) {
53819
53867
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
53868
+ const connErr = formatConnectionError(message);
53869
+ if (connErr) {
53870
+ console.error(connErr);
53871
+ process.exitCode = 1;
53872
+ return;
53873
+ }
53820
53874
  console.error(`Error: ${message}`);
53821
53875
  if (error49 instanceof ApiError && error49.body && typeof error49.body === "object") {
53822
53876
  const detail = error49.body.error ?? error49.body.code;
@@ -53853,6 +53907,12 @@ function registerJournalCommand(program2) {
53853
53907
  }
53854
53908
  } catch (error49) {
53855
53909
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
53910
+ const connErr = formatConnectionError(message);
53911
+ if (connErr) {
53912
+ console.error(connErr);
53913
+ process.exitCode = 1;
53914
+ return;
53915
+ }
53856
53916
  console.error(`Error: ${message}`);
53857
53917
  process.exitCode = error49 instanceof ApiError ? 2 : 1;
53858
53918
  }
@@ -53875,6 +53935,12 @@ function registerJournalCommand(program2) {
53875
53935
  }
53876
53936
  } catch (error49) {
53877
53937
  const message = error49 instanceof ApiError ? error49.message : error49 instanceof Error ? error49.message : String(error49);
53938
+ const connErr = formatConnectionError(message);
53939
+ if (connErr) {
53940
+ console.error(connErr);
53941
+ process.exitCode = 1;
53942
+ return;
53943
+ }
53878
53944
  console.error(`Error: ${message}`);
53879
53945
  process.exitCode = error49 instanceof ApiError ? 2 : 1;
53880
53946
  }
@@ -54273,7 +54339,7 @@ function registerConfigCommand(program2) {
54273
54339
  init_source();
54274
54340
  init_utils();
54275
54341
  var logger18 = createLogger("cli-infra");
54276
- async function checkInfraStatus() {
54342
+ async function checkInfraStatus(options) {
54277
54343
  const apiBase = getApiBase();
54278
54344
  try {
54279
54345
  const response = await fetch(`${apiBase}/health`);
@@ -54286,7 +54352,7 @@ async function checkInfraStatus() {
54286
54352
  details: apiBase
54287
54353
  }
54288
54354
  ];
54289
- if (data.services) {
54355
+ if (!options?.remote && data.services) {
54290
54356
  if (data.services.neo4j) {
54291
54357
  services.push({
54292
54358
  name: "Neo4j",
@@ -54309,10 +54375,8 @@ async function checkInfraStatus() {
54309
54375
  });
54310
54376
  }
54311
54377
  }
54312
- return {
54313
- services,
54314
- overallHealthy: services.every((s) => s.healthy)
54315
- };
54378
+ const overallHealthy = options?.remote ? response.ok && data.status === "ok" : services.every((s) => s.healthy);
54379
+ return { services, overallHealthy };
54316
54380
  } catch (error49) {
54317
54381
  return {
54318
54382
  services: [
@@ -54330,7 +54394,7 @@ async function checkInfraStatus() {
54330
54394
  function formatStatusIndicator(healthy) {
54331
54395
  return healthy ? source_default.green("\u2713 UP") : source_default.red("\u2717 DOWN");
54332
54396
  }
54333
- function formatInfraStatus(result) {
54397
+ function formatInfraStatus(result, options) {
54334
54398
  const lines = [];
54335
54399
  lines.push("");
54336
54400
  lines.push(source_default.bold.cyan(" Infrastructure Status"));
@@ -54344,6 +54408,10 @@ function formatInfraStatus(result) {
54344
54408
  }
54345
54409
  }
54346
54410
  lines.push("");
54411
+ if (options?.remote) {
54412
+ lines.push(source_default.dim(" Backend services are managed infrastructure. Contact support if issues persist."));
54413
+ lines.push("");
54414
+ }
54347
54415
  if (result.overallHealthy) {
54348
54416
  lines.push(` ${source_default.green.bold("All services healthy.")}`);
54349
54417
  } else {
@@ -54357,12 +54425,13 @@ function registerInfraCommand(program2) {
54357
54425
  const infra = program2.command("infra").description("Infrastructure management commands");
54358
54426
  infra.command("status").description("Check health of API server and underlying infrastructure").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (options) => {
54359
54427
  try {
54428
+ const remote = await isRemoteMode();
54360
54429
  console.log(source_default.gray(" Checking infrastructure health..."));
54361
- const result = await checkInfraStatus();
54430
+ const result = await checkInfraStatus({ remote });
54362
54431
  if (options.format === "json") {
54363
54432
  console.log(JSON.stringify(result, null, 2));
54364
54433
  } else {
54365
- console.log(formatInfraStatus(result));
54434
+ console.log(formatInfraStatus(result, { remote }));
54366
54435
  }
54367
54436
  if (!result.overallHealthy) {
54368
54437
  process.exitCode = 1;
@@ -54433,6 +54502,7 @@ function registerLoginCommand(program2) {
54433
54502
  console.log("");
54434
54503
  console.log(source_default.bold(" Trading Boy \u2014 Login"));
54435
54504
  console.log(source_default.gray(" " + "\u2500".repeat(40)));
54505
+ console.log(source_default.dim(" Don't have a key? Run: trading-boy subscribe"));
54436
54506
  console.log("");
54437
54507
  apiKey = await password({
54438
54508
  message: "Enter your API key",
@@ -54603,7 +54673,7 @@ function formatWhoamiOutput(result) {
54603
54673
  const lines = [];
54604
54674
  lines.push("");
54605
54675
  if (!result.authenticated) {
54606
- lines.push(source_default.dim(" Not authenticated. Run `trading-boy login` to get started."));
54676
+ lines.push(source_default.dim(" Not authenticated. Run `trading-boy login` or `trading-boy subscribe` to get started."));
54607
54677
  lines.push("");
54608
54678
  return lines.join("\n");
54609
54679
  }
@@ -54883,9 +54953,9 @@ async function runOnboarding() {
54883
54953
  console.log(source_default.gray(" " + "\u2500".repeat(50)));
54884
54954
  console.log("");
54885
54955
  console.log(` ${source_default.white("trading-boy context SOL")} ${source_default.dim("Full context for any token")}`);
54886
- console.log(` ${source_default.white("trading-boy journal")} ${source_default.dim("Log a trade")}`);
54956
+ console.log(` ${source_default.white("trading-boy journal log entry SOL")} ${source_default.dim("Log a trade entry")}`);
54887
54957
  console.log(` ${source_default.white("trading-boy decisions")} ${source_default.dim("View trade history")}`);
54888
- console.log(` ${source_default.white("trading-boy review daily")} ${source_default.dim("Daily performance review")}`);
54958
+ console.log(` ${source_default.white("trading-boy journal review daily")} ${source_default.dim("Review your daily trades")}`);
54889
54959
  console.log(` ${source_default.white("trading-boy narrative list")} ${source_default.dim("Active market narratives")}`);
54890
54960
  console.log(` ${source_default.white("trading-boy catalysts")} ${source_default.dim("Upcoming events")}`);
54891
54961
  if (traderRegistered) {
@@ -55062,7 +55132,7 @@ function formatSubscribeSuccess(result) {
55062
55132
  lines.push(` ${source_default.bold("API Key:")} ${source_default.yellow(result.apiKey)}`);
55063
55133
  lines.push("");
55064
55134
  lines.push(source_default.dim(" \u26A0\uFE0F Copy this key now \u2014 it will not be shown again."));
55065
- lines.push(source_default.dim(" Use it to connect Telegram: send /start to @TradingBoyBot"));
55135
+ lines.push(source_default.dim(" Use it to connect Telegram: send /start to @TradingBoy1_Bot"));
55066
55136
  lines.push(source_default.dim(" and paste the key when prompted."));
55067
55137
  } else if (result.keyPrefix) {
55068
55138
  lines.push(` ${source_default.bold("Key ID:")} ${result.keyPrefix}`);
@@ -56644,6 +56714,12 @@ function registerSuggestionsCommand(program2) {
56644
56714
  cmd.help();
56645
56715
  });
56646
56716
  cmd.command("list").description("List strategy suggestions").option("--status <status>", "Filter by status: pending, approved, rejected", "pending").option("--strategy <id>", "Filter by strategy ID").option("--limit <n>", "Maximum results", parseInt, 20).option("--offset <n>", "Pagination offset", parseInt, 0).addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (options) => {
56717
+ if (!await isRemoteMode()) {
56718
+ console.error(source_default.yellow("This command requires a remote API connection."));
56719
+ console.error(source_default.dim(" Run: trading-boy login or trading-boy subscribe"));
56720
+ process.exitCode = 1;
56721
+ return;
56722
+ }
56647
56723
  try {
56648
56724
  const params = new URLSearchParams();
56649
56725
  params.set("status", options.status);
@@ -56665,6 +56741,12 @@ function registerSuggestionsCommand(program2) {
56665
56741
  }
56666
56742
  });
56667
56743
  cmd.command("approve <id>").description("Approve a suggestion and auto-apply to strategy").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (id, options) => {
56744
+ if (!await isRemoteMode()) {
56745
+ console.error(source_default.yellow("This command requires a remote API connection."));
56746
+ console.error(source_default.dim(" Run: trading-boy login or trading-boy subscribe"));
56747
+ process.exitCode = 1;
56748
+ return;
56749
+ }
56668
56750
  try {
56669
56751
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/approve`, { method: "POST" });
56670
56752
  if (options.format === "json") {
@@ -56682,6 +56764,12 @@ function registerSuggestionsCommand(program2) {
56682
56764
  }
56683
56765
  });
56684
56766
  cmd.command("reject <id>").description("Reject a suggestion").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (id, options) => {
56767
+ if (!await isRemoteMode()) {
56768
+ console.error(source_default.yellow("This command requires a remote API connection."));
56769
+ console.error(source_default.dim(" Run: trading-boy login or trading-boy subscribe"));
56770
+ process.exitCode = 1;
56771
+ return;
56772
+ }
56685
56773
  try {
56686
56774
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/reject`, { method: "POST" });
56687
56775
  if (options.format === "json") {
@@ -57049,9 +57137,21 @@ function parseHumanInterval(value) {
57049
57137
  var MIN_SCAN_INTERVAL_MS = 6e4;
57050
57138
  function registerAgentCommand(program2) {
57051
57139
  const agent = program2.command("agent").description("Manage autonomous trading agents");
57052
- agent.command("create").description("Create a new agent").requiredOption("--trader-id <traderId>", "Trader ID").requiredOption("--strategy-id <strategyId>", "Strategy ID").option("--name <name>", "Agent name").option("--autonomy <level>", "Autonomy level: OBSERVE_ONLY, SUGGEST, AUTO_WITH_APPROVAL, FULLY_AUTONOMOUS", "OBSERVE_ONLY").option("--scan-interval <ms>", "Scan interval in ms (min 60000)", "300000").option("--scan-interval-human <duration>", "Scan interval in human-readable format (e.g. 1m, 5m, 15m, 30m, 1h)").option("--watchlist <symbols>", "Comma-separated token symbols").option("--max-daily-trades <n>", "Max daily trades", "10").option("--max-daily-loss <usd>", "Max daily loss in USD", "500").option("--max-position-size <pct>", "Max position size as decimal (0.10 = 10%)", "0.10").option("--min-confidence <n>", "Min confidence threshold (0-1)", "0.60").option("--scan-model <model>", "LLM model for market scanning").option("--analyze-model <model>", "LLM model for deep analysis").option("--decide-model <model>", "LLM model for trade decisions").addOption(new Option("--asset-class <class>", "Asset class for this agent").choices(["crypto", "commodities", "mixed"]).default("crypto")).option("--soul-override <text>", "Custom soul/personality for this agent").option("--purpose-override <text>", "Custom purpose/mission for this agent").option("--soul-file <path>", "Load soul from a file").option("--purpose-file <path>", "Load purpose from a file").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (options) => {
57140
+ agent.command("create").description("Create a new agent").option("--trader-id <traderId>", "Trader ID").option("--strategy-id <strategyId>", "Strategy ID").option("--name <name>", "Agent name").option("--autonomy <level>", "Autonomy level: OBSERVE_ONLY, SUGGEST, AUTO_WITH_APPROVAL, FULLY_AUTONOMOUS", "OBSERVE_ONLY").option("--scan-interval <ms>", "Scan interval in ms (min 60000)", "300000").option("--scan-interval-human <duration>", "Scan interval in human-readable format (e.g. 1m, 5m, 15m, 30m, 1h)").option("--watchlist <symbols>", "Comma-separated token symbols").option("--max-daily-trades <n>", "Max daily trades", "10").option("--max-daily-loss <usd>", "Max daily loss in USD", "500").option("--max-position-size <pct>", "Max position size as decimal (0.10 = 10%)", "0.10").option("--min-confidence <n>", "Min confidence threshold (0-1)", "0.60").option("--scan-model <model>", "LLM model for market scanning").option("--analyze-model <model>", "LLM model for deep analysis").option("--decide-model <model>", "LLM model for trade decisions").addOption(new Option("--asset-class <class>", "Asset class for this agent").choices(["crypto", "commodities", "mixed"]).default("crypto")).option("--soul-override <text>", "Custom soul/personality for this agent").option("--purpose-override <text>", "Custom purpose/mission for this agent").option("--soul-file <path>", "Load soul from a file").option("--purpose-file <path>", "Load purpose from a file").addOption(new Option("--format <format>", "Output format").choices(["text", "json"]).default("text")).action(async (options) => {
57053
57141
  if (!await ensureRemote2())
57054
57142
  return;
57143
+ if (!options.traderId) {
57144
+ console.error(source_default.red("Error: --trader-id is required."));
57145
+ console.log(source_default.dim(" Find yours with: trading-boy trader list"));
57146
+ process.exitCode = 1;
57147
+ return;
57148
+ }
57149
+ if (!options.strategyId) {
57150
+ console.error(source_default.red("Error: --strategy-id is required."));
57151
+ console.log(source_default.dim(" Find yours with: trading-boy strategy list"));
57152
+ process.exitCode = 1;
57153
+ return;
57154
+ }
57055
57155
  const body = {
57056
57156
  traderId: options.traderId,
57057
57157
  strategyId: options.strategyId
@@ -57150,7 +57250,13 @@ function registerAgentCommand(program2) {
57150
57250
  return;
57151
57251
  }
57152
57252
  if (result.agents.length === 0) {
57153
- console.log(source_default.dim(" No agents found"));
57253
+ console.log(source_default.dim(" No agents found."));
57254
+ console.log("");
57255
+ console.log(source_default.dim(" Create one with:"));
57256
+ console.log(source_default.dim(" trading-boy agent create --trader-id <id> --strategy-id <id>"));
57257
+ console.log(source_default.dim(" Find your IDs with:"));
57258
+ console.log(source_default.dim(" trading-boy trader list"));
57259
+ console.log(source_default.dim(" trading-boy strategy list"));
57154
57260
  return;
57155
57261
  }
57156
57262
  console.log("");
@@ -114,8 +114,8 @@ export function registerAgentCommand(program) {
114
114
  agent
115
115
  .command('create')
116
116
  .description('Create a new agent')
117
- .requiredOption('--trader-id <traderId>', 'Trader ID')
118
- .requiredOption('--strategy-id <strategyId>', 'Strategy ID')
117
+ .option('--trader-id <traderId>', 'Trader ID')
118
+ .option('--strategy-id <strategyId>', 'Strategy ID')
119
119
  .option('--name <name>', 'Agent name')
120
120
  .option('--autonomy <level>', 'Autonomy level: OBSERVE_ONLY, SUGGEST, AUTO_WITH_APPROVAL, FULLY_AUTONOMOUS', 'OBSERVE_ONLY')
121
121
  .option('--scan-interval <ms>', 'Scan interval in ms (min 60000)', '300000')
@@ -137,6 +137,18 @@ export function registerAgentCommand(program) {
137
137
  .action(async (options) => {
138
138
  if (!(await ensureRemote()))
139
139
  return;
140
+ if (!options.traderId) {
141
+ console.error(chalk.red('Error: --trader-id is required.'));
142
+ console.log(chalk.dim(' Find yours with: trading-boy trader list'));
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ if (!options.strategyId) {
147
+ console.error(chalk.red('Error: --strategy-id is required.'));
148
+ console.log(chalk.dim(' Find yours with: trading-boy strategy list'));
149
+ process.exitCode = 1;
150
+ return;
151
+ }
140
152
  const body = {
141
153
  traderId: options.traderId,
142
154
  strategyId: options.strategyId,
@@ -249,7 +261,13 @@ export function registerAgentCommand(program) {
249
261
  return;
250
262
  }
251
263
  if (result.agents.length === 0) {
252
- console.log(chalk.dim(' No agents found'));
264
+ console.log(chalk.dim(' No agents found.'));
265
+ console.log('');
266
+ console.log(chalk.dim(' Create one with:'));
267
+ console.log(chalk.dim(' trading-boy agent create --trader-id <id> --strategy-id <id>'));
268
+ console.log(chalk.dim(' Find your IDs with:'));
269
+ console.log(chalk.dim(' trading-boy trader list'));
270
+ console.log(chalk.dim(' trading-boy strategy list'));
253
271
  return;
254
272
  }
255
273
  console.log('');
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError, colorChange, colorSentiment, colorRiskScore, formatUsd } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-context');
8
8
  // ─── Formatters ───
@@ -324,6 +324,13 @@ export function registerContextCommand(program) {
324
324
  .option('--trader-id <id>', 'Trader ID for personalized context')
325
325
  .action(async (symbol, options) => {
326
326
  try {
327
+ // ─── Auth pre-flight ───
328
+ if (!(await isRemoteMode())) {
329
+ console.error(chalk.yellow('This command requires a remote API connection.'));
330
+ console.error(chalk.dim(' Run: trading-boy login'));
331
+ process.exitCode = 1;
332
+ return;
333
+ }
327
334
  // ─── Validate range options ───
328
335
  if ((options.from && !options.to) || (!options.from && options.to)) {
329
336
  console.error(chalk.red('Error: Both --from and --to must be provided for range queries.'));
@@ -368,6 +375,13 @@ export function registerContextCommand(program) {
368
375
  // Live context
369
376
  const traderQs = options.traderId ? `?traderId=${encodeURIComponent(options.traderId)}` : '';
370
377
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol.toUpperCase())}/context${traderQs}`);
378
+ // Check if the token was actually found
379
+ if (pkg.token.name == null && (!Array.isArray(pkg.token.chains) || pkg.token.chains.length === 0) && pkg.market?.price == null) {
380
+ console.error(chalk.red(`Error: Token not found: ${symbol.toUpperCase()}`));
381
+ console.error(chalk.dim(' Check the symbol and try again. Use a symbol like SOL, JUP, or BONK.'));
382
+ process.exitCode = 1;
383
+ return;
384
+ }
371
385
  if (options.format === 'json') {
372
386
  console.log(JSON.stringify(pkg, null, 2));
373
387
  }
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
- import { padRight, parseIntOption, formatDate } from '../utils.js';
4
+ import { padRight, parseIntOption, formatDate, formatConnectionError } from '../utils.js';
5
5
  import { apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-decisions');
@@ -222,6 +222,12 @@ export function registerDecisionsCommand(program) {
222
222
  }
223
223
  catch (error) {
224
224
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
225
+ const connErr = formatConnectionError(message);
226
+ if (connErr) {
227
+ console.error(connErr);
228
+ process.exitCode = 1;
229
+ return;
230
+ }
225
231
  logger.error({ error: message }, 'Failed to fetch decisions');
226
232
  console.error(chalk.red(`Error: ${message}`));
227
233
  process.exitCode = error instanceof ApiError ? 2 : 1;
@@ -262,6 +268,12 @@ export function registerDecisionsCommand(program) {
262
268
  }
263
269
  catch (error) {
264
270
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
271
+ const connErr = formatConnectionError(message);
272
+ if (connErr) {
273
+ console.error(connErr);
274
+ process.exitCode = 1;
275
+ return;
276
+ }
265
277
  logger.error({ error: message }, 'Failed to fetch stats');
266
278
  console.error(chalk.red(`Error: ${message}`));
267
279
  process.exitCode = error instanceof ApiError ? 2 : 1;
@@ -13,8 +13,12 @@ export interface InfraStatusResult {
13
13
  * Check infrastructure health via the API /health endpoint.
14
14
  * Falls back to a basic connectivity check if the API is unreachable.
15
15
  */
16
- export declare function checkInfraStatus(): Promise<InfraStatusResult>;
17
- export declare function formatInfraStatus(result: InfraStatusResult): string;
16
+ export declare function checkInfraStatus(options?: {
17
+ remote?: boolean;
18
+ }): Promise<InfraStatusResult>;
19
+ export declare function formatInfraStatus(result: InfraStatusResult, options?: {
20
+ remote?: boolean;
21
+ }): string;
18
22
  export declare function registerInfraCommand(program: Command): void;
19
23
  export {};
20
24
  //# sourceMappingURL=infra.d.ts.map
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { padRight } from '../utils.js';
5
- import { getApiBase } from '../api-client.js';
5
+ import { getApiBase, isRemoteMode } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-infra');
8
8
  // ─── Health Check Logic ───
@@ -10,7 +10,7 @@ const logger = createLogger('cli-infra');
10
10
  * Check infrastructure health via the API /health endpoint.
11
11
  * Falls back to a basic connectivity check if the API is unreachable.
12
12
  */
13
- export async function checkInfraStatus() {
13
+ export async function checkInfraStatus(options) {
14
14
  const apiBase = getApiBase();
15
15
  try {
16
16
  // Call the API health endpoint directly (no auth needed)
@@ -24,8 +24,8 @@ export async function checkInfraStatus() {
24
24
  details: apiBase,
25
25
  },
26
26
  ];
27
- // Parse individual service statuses from the health response
28
- if (data.services) {
27
+ // In remote mode, skip individual DB statuses they are managed infrastructure
28
+ if (!options?.remote && data.services) {
29
29
  if (data.services.neo4j) {
30
30
  services.push({
31
31
  name: 'Neo4j',
@@ -48,10 +48,12 @@ export async function checkInfraStatus() {
48
48
  });
49
49
  }
50
50
  }
51
- return {
52
- services,
53
- overallHealthy: services.every((s) => s.healthy),
54
- };
51
+ // In remote mode, use API response status to determine health
52
+ // (services array only contains API Server, but backend may be degraded)
53
+ const overallHealthy = options?.remote
54
+ ? response.ok && data.status === 'ok'
55
+ : services.every((s) => s.healthy);
56
+ return { services, overallHealthy };
55
57
  }
56
58
  catch (error) {
57
59
  // API is unreachable
@@ -72,7 +74,7 @@ export async function checkInfraStatus() {
72
74
  function formatStatusIndicator(healthy) {
73
75
  return healthy ? chalk.green('\u2713 UP') : chalk.red('\u2717 DOWN');
74
76
  }
75
- export function formatInfraStatus(result) {
77
+ export function formatInfraStatus(result, options) {
76
78
  const lines = [];
77
79
  lines.push('');
78
80
  lines.push(chalk.bold.cyan(' Infrastructure Status'));
@@ -86,6 +88,10 @@ export function formatInfraStatus(result) {
86
88
  }
87
89
  }
88
90
  lines.push('');
91
+ if (options?.remote) {
92
+ lines.push(chalk.dim(' Backend services are managed infrastructure. Contact support if issues persist.'));
93
+ lines.push('');
94
+ }
89
95
  if (result.overallHealthy) {
90
96
  lines.push(` ${chalk.green.bold('All services healthy.')}`);
91
97
  }
@@ -107,13 +113,14 @@ export function registerInfraCommand(program) {
107
113
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
108
114
  .action(async (options) => {
109
115
  try {
116
+ const remote = await isRemoteMode();
110
117
  console.log(chalk.gray(' Checking infrastructure health...'));
111
- const result = await checkInfraStatus();
118
+ const result = await checkInfraStatus({ remote });
112
119
  if (options.format === 'json') {
113
120
  console.log(JSON.stringify(result, null, 2));
114
121
  }
115
122
  else {
116
- console.log(formatInfraStatus(result));
123
+ console.log(formatInfraStatus(result, { remote }));
117
124
  }
118
125
  if (!result.overallHealthy) {
119
126
  process.exitCode = 1;
@@ -1,5 +1,5 @@
1
1
  import { Option } from 'commander';
2
- import { padRight } from '../utils.js';
2
+ import { padRight, formatConnectionError } from '../utils.js';
3
3
  import { apiRequest, ApiError } from '../api-client.js';
4
4
  import { registerReviewCommand } from './review.js';
5
5
  // ─── Default Trader ───
@@ -73,6 +73,12 @@ export function registerJournalCommand(program) {
73
73
  }
74
74
  catch (error) {
75
75
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
76
+ const connErr = formatConnectionError(message);
77
+ if (connErr) {
78
+ console.error(connErr);
79
+ process.exitCode = 1;
80
+ return;
81
+ }
76
82
  console.error(`Error: ${message}`);
77
83
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
78
84
  const detail = error.body.error ?? error.body.code;
@@ -118,6 +124,12 @@ export function registerJournalCommand(program) {
118
124
  }
119
125
  catch (error) {
120
126
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
127
+ const connErr = formatConnectionError(message);
128
+ if (connErr) {
129
+ console.error(connErr);
130
+ process.exitCode = 1;
131
+ return;
132
+ }
121
133
  console.error(`Error: ${message}`);
122
134
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
123
135
  const detail = error.body.error ?? error.body.code;
@@ -185,6 +197,12 @@ export function registerJournalCommand(program) {
185
197
  }
186
198
  catch (error) {
187
199
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
200
+ const connErr = formatConnectionError(message);
201
+ if (connErr) {
202
+ console.error(connErr);
203
+ process.exitCode = 1;
204
+ return;
205
+ }
188
206
  console.error(`Error: ${message}`);
189
207
  if (error instanceof ApiError && error.body && typeof error.body === 'object') {
190
208
  const detail = error.body.error ?? error.body.code;
@@ -231,6 +249,12 @@ export function registerJournalCommand(program) {
231
249
  }
232
250
  catch (error) {
233
251
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
252
+ const connErr = formatConnectionError(message);
253
+ if (connErr) {
254
+ console.error(connErr);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
234
258
  console.error(`Error: ${message}`);
235
259
  process.exitCode = error instanceof ApiError ? 2 : 1;
236
260
  }
@@ -263,6 +287,12 @@ export function registerJournalCommand(program) {
263
287
  }
264
288
  catch (error) {
265
289
  const message = error instanceof ApiError ? error.message : (error instanceof Error ? error.message : String(error));
290
+ const connErr = formatConnectionError(message);
291
+ if (connErr) {
292
+ console.error(connErr);
293
+ process.exitCode = 1;
294
+ return;
295
+ }
266
296
  console.error(`Error: ${message}`);
267
297
  process.exitCode = error instanceof ApiError ? 2 : 1;
268
298
  }
@@ -72,6 +72,7 @@ export function registerLoginCommand(program) {
72
72
  console.log('');
73
73
  console.log(chalk.bold(' Trading Boy — Login'));
74
74
  console.log(chalk.gray(' ' + '─'.repeat(40)));
75
+ console.log(chalk.dim(' Don\'t have a key? Run: trading-boy subscribe'));
75
76
  console.log('');
76
77
  apiKey = await password({
77
78
  message: 'Enter your API key',
@@ -112,7 +112,7 @@ export async function runOnboarding() {
112
112
  }
113
113
  console.log('');
114
114
  }
115
- // ─── Step 3: Telegram Bot (was Step 2) ───
115
+ // ─── Step 3: Telegram Bot ───
116
116
  try {
117
117
  const wantsTelegram = await confirm({
118
118
  message: 'Connect Telegram for daily summaries?',
@@ -136,14 +136,14 @@ export async function runOnboarding() {
136
136
  throw error;
137
137
  }
138
138
  console.log('');
139
- // ─── Step 3: Next Steps Cheat Sheet ───
139
+ // ─── Step 4: Next Steps Cheat Sheet ───
140
140
  console.log(chalk.bold.cyan(' Quick Reference'));
141
141
  console.log(chalk.gray(' ' + '\u2500'.repeat(50)));
142
142
  console.log('');
143
143
  console.log(` ${chalk.white('trading-boy context SOL')} ${chalk.dim('Full context for any token')}`);
144
- console.log(` ${chalk.white('trading-boy journal')} ${chalk.dim('Log a trade')}`);
144
+ console.log(` ${chalk.white('trading-boy journal log entry SOL')} ${chalk.dim('Log a trade entry')}`);
145
145
  console.log(` ${chalk.white('trading-boy decisions')} ${chalk.dim('View trade history')}`);
146
- console.log(` ${chalk.white('trading-boy review daily')} ${chalk.dim('Daily performance review')}`);
146
+ console.log(` ${chalk.white('trading-boy journal review daily')} ${chalk.dim('Review your daily trades')}`);
147
147
  console.log(` ${chalk.white('trading-boy narrative list')} ${chalk.dim('Active market narratives')}`);
148
148
  console.log(` ${chalk.white('trading-boy catalysts')} ${chalk.dim('Upcoming events')}`);
149
149
  if (traderRegistered) {
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-query');
8
8
  // ─── Formatter ───
@@ -102,6 +102,13 @@ export function registerQueryCommand(program) {
102
102
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
103
103
  .action(async (symbol, options) => {
104
104
  try {
105
+ // ─── Auth pre-flight ───
106
+ if (!(await isRemoteMode())) {
107
+ console.error(chalk.yellow('This command requires a remote API connection.'));
108
+ console.error(chalk.dim(' Run: trading-boy login'));
109
+ process.exitCode = 1;
110
+ return;
111
+ }
105
112
  const pkg = await apiRequest(`/api/v1/tokens/${encodeURIComponent(symbol.toUpperCase())}/context`);
106
113
  const result = contextToQueryResult(pkg);
107
114
  // Check if the token was actually found
@@ -2,7 +2,7 @@ import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
4
  import { formatConnectionError, padRight } from '../utils.js';
5
- import { apiRequest, ApiError } from '../api-client.js';
5
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-risk');
8
8
  // ─── Formatters ───
@@ -127,6 +127,12 @@ export function registerRiskCommand(program) {
127
127
  process.exitCode = 1;
128
128
  return;
129
129
  }
130
+ if (!(await isRemoteMode())) {
131
+ console.error(chalk.yellow('This command requires a remote API connection.'));
132
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
133
+ process.exitCode = 1;
134
+ return;
135
+ }
130
136
  try {
131
137
  const apiResult = await apiRequest(`/api/v1/protocols/${encodeURIComponent(protocol.toLowerCase())}/risk`);
132
138
  const result = {
@@ -217,7 +217,7 @@ export function formatSubscribeSuccess(result) {
217
217
  lines.push(` ${chalk.bold('API Key:')} ${chalk.yellow(result.apiKey)}`);
218
218
  lines.push('');
219
219
  lines.push(chalk.dim(' ⚠️ Copy this key now — it will not be shown again.'));
220
- lines.push(chalk.dim(' Use it to connect Telegram: send /start to @TradingBoyBot'));
220
+ lines.push(chalk.dim(' Use it to connect Telegram: send /start to @TradingBoy1_Bot'));
221
221
  lines.push(chalk.dim(' and paste the key when prompted.'));
222
222
  }
223
223
  else if (result.keyPrefix) {
@@ -1,7 +1,7 @@
1
1
  import { Option } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { createLogger } from '@trading-boy/core';
4
- import { apiRequest, ApiError } from '../api-client.js';
4
+ import { isRemoteMode, apiRequest, ApiError } from '../api-client.js';
5
5
  import { padRight, truncate, formatDate } from '../utils.js';
6
6
  // ─── Logger ───
7
7
  const logger = createLogger('cli-suggestions');
@@ -72,6 +72,12 @@ export function registerSuggestionsCommand(program) {
72
72
  .option('--offset <n>', 'Pagination offset', parseInt, 0)
73
73
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
74
74
  .action(async (options) => {
75
+ if (!(await isRemoteMode())) {
76
+ console.error(chalk.yellow('This command requires a remote API connection.'));
77
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
78
+ process.exitCode = 1;
79
+ return;
80
+ }
75
81
  try {
76
82
  const params = new URLSearchParams();
77
83
  params.set('status', options.status);
@@ -100,6 +106,12 @@ export function registerSuggestionsCommand(program) {
100
106
  .description('Approve a suggestion and auto-apply to strategy')
101
107
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
102
108
  .action(async (id, options) => {
109
+ if (!(await isRemoteMode())) {
110
+ console.error(chalk.yellow('This command requires a remote API connection.'));
111
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
112
+ process.exitCode = 1;
113
+ return;
114
+ }
103
115
  try {
104
116
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/approve`, { method: 'POST' });
105
117
  if (options.format === 'json') {
@@ -122,6 +134,12 @@ export function registerSuggestionsCommand(program) {
122
134
  .description('Reject a suggestion')
123
135
  .addOption(new Option('--format <format>', 'Output format').choices(['text', 'json']).default('text'))
124
136
  .action(async (id, options) => {
137
+ if (!(await isRemoteMode())) {
138
+ console.error(chalk.yellow('This command requires a remote API connection.'));
139
+ console.error(chalk.dim(' Run: trading-boy login or trading-boy subscribe'));
140
+ process.exitCode = 1;
141
+ return;
142
+ }
125
143
  try {
126
144
  const data = await apiRequest(`/api/v1/suggestions/${encodeURIComponent(id)}/reject`, { method: 'POST' });
127
145
  if (options.format === 'json') {
@@ -33,7 +33,7 @@ export function formatWhoamiOutput(result) {
33
33
  const lines = [];
34
34
  lines.push('');
35
35
  if (!result.authenticated) {
36
- lines.push(chalk.dim(' Not authenticated. Run `trading-boy login` to get started.'));
36
+ lines.push(chalk.dim(' Not authenticated. Run `trading-boy login` or `trading-boy subscribe` to get started.'));
37
37
  lines.push('');
38
38
  return lines.join('\n');
39
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trading-boy/cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
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": {