moshcode 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/advisor.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // `moshcode ticker` — equity research from advis0r.com, in the pit.
1
+ // `moshcode stocks` — equity research from advis0r.com, in the pit.
2
2
  //
3
3
  // Same split as src/trade.mjs: argument translation is pure and testable, the
4
4
  // network call is injectable, and rendering is a function of the decoded JSON.
@@ -18,7 +18,7 @@ export function advisorBase(env = process.env) {
18
18
  return (raw || DEFAULT_ADVISOR_URL).replace(/\/+$/, "");
19
19
  }
20
20
 
21
- const USAGE = `usage: moshcode ticker <symbol|verb> [args…]
21
+ const USAGE = `usage: moshcode stocks <symbol|verb> [args…]
22
22
 
23
23
  <symbol> the stored research report for one ticker
24
24
  report <symbol> same thing, when a symbol looks like a verb
@@ -40,17 +40,17 @@ const USAGE = `usage: moshcode ticker <symbol|verb> [args…]
40
40
  Research aid, not advice. Every route is public, read-only, and served from
41
41
  stored snapshots — see the generated-at stamp printed with each report.`;
42
42
 
43
- export function tickerUsage() {
43
+ export function stocksUsage() {
44
44
  return USAGE;
45
45
  }
46
46
 
47
- /** Verb names, in help order. cli-schema's TICKER_VERBS must match (drift test). */
48
- export const TICKER_VERB_NAMES = [
47
+ /** Verb names, in help order. cli-schema's STOCKS_VERBS must match (drift test). */
48
+ export const STOCKS_VERB_NAMES = [
49
49
  "report", "signals", "search", "lookup", "reports", "discover", "tickers", "stats", "open",
50
50
  ];
51
51
 
52
- // Aliases exist because muscle memory differs: `/ticker news AAPL` and
53
- // `/ticker quotes AAPL` should not be errors when the intent is obvious.
52
+ // Aliases exist because muscle memory differs: `/stocks news AAPL` and
53
+ // `/stocks quotes AAPL` should not be errors when the intent is obvious.
54
54
  const VERB_ALIASES = {
55
55
  signal: "signals", news: "signals",
56
56
  find: "search", grep: "search", q: "search",
@@ -66,7 +66,7 @@ const VERB_ALIASES = {
66
66
  /** Resolve a first argument to a canonical verb, or null when it is a symbol. */
67
67
  export function resolveVerb(word) {
68
68
  const key = String(word ?? "").toLowerCase();
69
- if (TICKER_VERB_NAMES.includes(key)) return key;
69
+ if (STOCKS_VERB_NAMES.includes(key)) return key;
70
70
  return VERB_ALIASES[key] ?? null;
71
71
  }
72
72
 
@@ -114,13 +114,13 @@ function positiveInt(value, { max }) {
114
114
  const SORTS = ["recent", "score", "ticker"];
115
115
 
116
116
  /**
117
- * Translate `ticker` arguments into a request the caller can execute.
117
+ * Translate `stocks` arguments into a request the caller can execute.
118
118
  *
119
119
  * Returns one of `{ usage }`, `{ error }`, or
120
120
  * `{ verb, path, query, json, open? }` — never performs IO, so the whole
121
121
  * argument surface is testable without a network.
122
122
  */
123
- export function tickerArgs(input = []) {
123
+ export function stocksArgs(input = []) {
124
124
  const args = input.map(String);
125
125
  const jsonFlag = takeFlag(args, "--json", { boolean: true });
126
126
  let rest = jsonFlag.rest;
@@ -131,26 +131,26 @@ export function tickerArgs(input = []) {
131
131
  const horizonFlag = takeFlag(rest, "--horizon"); rest = horizonFlag.rest;
132
132
  const providerFlag = takeFlag(rest, "--provider"); rest = providerFlag.rest;
133
133
 
134
- if (limitFlag.missing) return { error: "ticker --limit requires a positive number" };
135
- if (sortFlag.missing) return { error: `ticker --sort requires one of ${SORTS.join(", ")}` };
136
- if (horizonFlag.missing) return { error: "ticker --horizon requires 1 or 2" };
137
- if (providerFlag.missing) return { error: "ticker --provider requires a name" };
134
+ if (limitFlag.missing) return { error: "stocks --limit requires a positive number" };
135
+ if (sortFlag.missing) return { error: `stocks --sort requires one of ${SORTS.join(", ")}` };
136
+ if (horizonFlag.missing) return { error: "stocks --horizon requires 1 or 2" };
137
+ if (providerFlag.missing) return { error: "stocks --provider requires a name" };
138
138
 
139
139
  // A limit above the server's own cap is silently clamped there; clamping here
140
140
  // too keeps `--limit 9999` from reading like a promise the API never made.
141
141
  const limit = limitFlag.value == null ? null : positiveInt(limitFlag.value, { max: 50 });
142
142
  if (limitFlag.value != null && limit == null) {
143
- return { error: "ticker --limit requires a positive number" };
143
+ return { error: "stocks --limit requires a positive number" };
144
144
  }
145
145
  if (sortFlag.value != null && !SORTS.includes(sortFlag.value.toLowerCase())) {
146
- return { error: `ticker --sort must be one of ${SORTS.join(", ")}` };
146
+ return { error: `stocks --sort must be one of ${SORTS.join(", ")}` };
147
147
  }
148
148
  if (horizonFlag.value != null && !["1", "2"].includes(String(horizonFlag.value))) {
149
- return { error: "ticker --horizon must be 1 or 2" };
149
+ return { error: "stocks --horizon must be 1 or 2" };
150
150
  }
151
151
 
152
152
  const stray = rest.find((arg) => arg.startsWith("-") && arg !== "-");
153
- if (stray) return { error: `unknown ticker flag ${JSON.stringify(stray)}` };
153
+ if (stray) return { error: `unknown stocks flag ${JSON.stringify(stray)}` };
154
154
 
155
155
  const [first, ...tail] = rest;
156
156
  if (!first) return { usage: true };
@@ -158,16 +158,16 @@ export function tickerArgs(input = []) {
158
158
  const verb = resolveVerb(first);
159
159
  const words = verb ? tail : rest;
160
160
 
161
- // No verb → the first word is the ticker. `/ticker AAPL` is the headline
161
+ // No verb → the first word is the ticker. `/stocks AAPL` is the headline
162
162
  // case and must stay the shortest thing anyone types.
163
163
  const wantsReport = verb == null || verb === "report" || verb === "open";
164
164
  if (wantsReport) {
165
165
  const raw = words[0];
166
- if (!raw) return { error: `ticker ${verb === "open" ? "open" : "report"} requires a ticker symbol` };
166
+ if (!raw) return { error: `stocks ${verb === "open" ? "open" : "report"} requires a ticker symbol` };
167
167
  const symbol = normalizeSymbol(raw);
168
168
  if (!symbol) {
169
169
  return {
170
- error: `${JSON.stringify(String(raw))} is not a ticker symbol — try: moshcode ticker lookup ${String(raw)}`,
170
+ error: `${JSON.stringify(String(raw))} is not a ticker symbol — try: moshcode stocks lookup ${String(raw)}`,
171
171
  };
172
172
  }
173
173
  if (verb === "open") return { verb: "open", symbol, open: `/ticker/${encodeURIComponent(symbol)}`, json };
@@ -176,16 +176,16 @@ export function tickerArgs(input = []) {
176
176
 
177
177
  if (verb === "signals") {
178
178
  const symbol = normalizeSymbol(words[0]);
179
- if (!words[0]) return { error: "ticker signals requires a ticker symbol" };
179
+ if (!words[0]) return { error: "stocks signals requires a ticker symbol" };
180
180
  if (!symbol) {
181
- return { error: `${JSON.stringify(String(words[0]))} is not a ticker symbol — try: moshcode ticker lookup ${words[0]}` };
181
+ return { error: `${JSON.stringify(String(words[0]))} is not a ticker symbol — try: moshcode stocks lookup ${words[0]}` };
182
182
  }
183
183
  return { verb, symbol, path: "/api/signals", query: { ticker: symbol }, json };
184
184
  }
185
185
 
186
186
  if (verb === "search" || verb === "lookup") {
187
187
  const q = words.join(" ").trim();
188
- if (!q) return { error: `ticker ${verb} requires something to look for` };
188
+ if (!q) return { error: `stocks ${verb} requires something to look for` };
189
189
  const path = verb === "search" ? "/api/search" : "/api/lookup";
190
190
  return { verb, path, query: { q, ...(limit ? { limit: String(limit) } : {}) }, json };
191
191
  }
@@ -221,7 +221,7 @@ export function tickerArgs(input = []) {
221
221
  if (verb === "tickers") return { verb, path: "/api/tickers", query: {}, json };
222
222
  if (verb === "stats") return { verb, path: "/api/stats", query: {}, json };
223
223
 
224
- return { error: `unknown ticker command ${JSON.stringify(String(first))}` };
224
+ return { error: `unknown stocks command ${JSON.stringify(String(first))}` };
225
225
  }
226
226
 
227
227
  /** Build the absolute URL for a translated request. */
@@ -248,7 +248,7 @@ export async function fetchAdvisor(request, { fetchImpl = globalThis.fetch, base
248
248
  try {
249
249
  const res = await fetchImpl(url, {
250
250
  signal: controller.signal,
251
- headers: { accept: "application/json", "user-agent": "moshcode-ticker" },
251
+ headers: { accept: "application/json", "user-agent": "moshcode-stocks" },
252
252
  });
253
253
  const text = await res.text();
254
254
  let data;
@@ -279,8 +279,18 @@ function compact(v) {
279
279
  const n = Number(v);
280
280
  if (!Number.isFinite(n)) return null;
281
281
  const units = [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "K"]];
282
- for (const [size, suffix] of units) {
283
- if (Math.abs(n) >= size) return `${(n / size).toFixed(2).replace(/\.?0+$/, "")}${suffix}`;
282
+ for (let i = 0; i < units.length; i++) {
283
+ const [size, suffix] = units[i];
284
+ if (Math.abs(n) < size) continue;
285
+ // Rounding to 2 decimals can push a value just under the next boundary up to
286
+ // a full thousand of this unit (999,999,999 → "1000M"); carry it to the next
287
+ // unit up (→ "1B") so the number never reads as an un-carried thousand.
288
+ const scaled = (n / size).toFixed(2);
289
+ if (Math.abs(Number(scaled)) >= 1000 && i > 0) {
290
+ const [upSize, upSuffix] = units[i - 1];
291
+ return `${(n / upSize).toFixed(2).replace(/\.?0+$/, "")}${upSuffix}`;
292
+ }
293
+ return `${scaled.replace(/\.?0+$/, "")}${suffix}`;
284
294
  }
285
295
  return String(n);
286
296
  }
@@ -458,7 +468,7 @@ function renderLookup(d) {
458
468
  const report = m.hasReport ? acid(" ✓ report") : ash(" · no report yet");
459
469
  lines.push(` ${acid(String(m.symbol).padEnd(8))}${bone(clip(m.name, 44).padEnd(46))}${ash(String(m.exchange ?? ""))}${report}`);
460
470
  }
461
- lines.push("", ` ${ash("then:")} ${bone(`moshcode ticker ${matches[0].symbol}`)}`);
471
+ lines.push("", ` ${ash("then:")} ${bone(`moshcode stocks ${matches[0].symbol}`)}`);
462
472
  return lines.join("\n");
463
473
  }
464
474
 
@@ -474,7 +484,7 @@ function renderReports(d) {
474
484
  `${bone(money(r.lastPrice).padStart(11))} ${ash(clip(r.companyName, 28).padEnd(29))}${ash(day(r.generatedAt))}`,
475
485
  );
476
486
  }
477
- lines.push("", ` ${ash("detail:")} ${bone(`moshcode ticker ${reports[0].ticker}`)}`);
487
+ lines.push("", ` ${ash("detail:")} ${bone(`moshcode stocks ${reports[0].ticker}`)}`);
478
488
  return lines.join("\n");
479
489
  }
480
490
 
@@ -540,12 +550,12 @@ export function renderAdvisor(verb, data, { columns } = {}) {
540
550
  }
541
551
 
542
552
  /**
543
- * Run a `ticker` invocation end to end. Returns a process exit code.
553
+ * Run a `stocks` invocation end to end. Returns a process exit code.
544
554
  *
545
555
  * `deps` exists so tests drive the whole command — parse, fetch, render — with
546
556
  * no network and no stdout.
547
557
  */
548
- export async function tickerCommand(argv = [], deps = {}) {
558
+ export async function stocksCommand(argv = [], deps = {}) {
549
559
  const {
550
560
  out = (s) => console.log(s),
551
561
  fail = (s) => console.error(s),
@@ -555,8 +565,8 @@ export async function tickerCommand(argv = [], deps = {}) {
555
565
  columns = process.stdout.columns,
556
566
  } = deps;
557
567
 
558
- const request = tickerArgs(argv);
559
- if (request.usage) { out(tickerUsage()); return 0; }
568
+ const request = stocksArgs(argv);
569
+ if (request.usage) { out(stocksUsage()); return 0; }
560
570
  if (request.error) { fail(danger(`✗ ${request.error}`)); return 1; }
561
571
 
562
572
  if (request.verb === "open") {
@@ -577,7 +587,7 @@ export async function tickerCommand(argv = [], deps = {}) {
577
587
  if (request.json) { out(JSON.stringify(res.data, null, 2)); return 1; }
578
588
  fail(danger(`✗ ${message}`));
579
589
  if (res.data?.didYouMean?.symbol) {
580
- fail(` ${ash("try:")} ${bone(`moshcode ticker ${res.data.didYouMean.symbol}`)}`);
590
+ fail(` ${ash("try:")} ${bone(`moshcode stocks ${res.data.didYouMean.symbol}`)}`);
581
591
  }
582
592
  return 1;
583
593
  }
@@ -290,14 +290,14 @@ export const CORE_CLI_COMMANDS = [
290
290
  note: "buy/sell inject --dry-run unless --submit is present. Alpaca defaults to paper trading; live trading requires its separate --live opt-in.",
291
291
  },
292
292
  {
293
- name: "ticker",
293
+ name: "stocks",
294
294
  group: "tools",
295
295
  description: "equity research from advis0r.com",
296
296
  synopsis: [
297
- ["moshcode ticker <symbol>", "the stored research report for one ticker"],
298
- ["moshcode ticker <verb> [args…]", ""],
297
+ ["moshcode stocks <symbol>", "the stored research report for one ticker"],
298
+ ["moshcode stocks <verb> [args…]", ""],
299
299
  ],
300
- verbs: "TICKER_VERBS",
300
+ verbs: "STOCKS_VERBS",
301
301
  flags: [
302
302
  ["--json", "print the raw API response", ""],
303
303
  ["--limit <n>", "cap results (search/lookup/reports/discover)", "the API's own default"],
@@ -306,16 +306,46 @@ export const CORE_CLI_COMMANDS = [
306
306
  ["--provider <p>", "discover: analysis provider", "offline"],
307
307
  ],
308
308
  examples: [
309
- ["moshcode ticker NVDA", "score, technicals, thesis, signals, sources"],
310
- ["moshcode ticker lookup rivian", "company name → RIVN"],
311
- ["moshcode ticker signals AAPL", "what was actually said, with sources"],
312
- ["moshcode ticker search 'data center'", "across every indexed transcript"],
313
- ["moshcode ticker reports --limit 10", "the stored index, best score first"],
309
+ ["moshcode stocks NVDA", "score, technicals, thesis, signals, sources"],
310
+ ["moshcode stocks lookup rivian", "company name → RIVN"],
311
+ ["moshcode stocks signals AAPL", "what was actually said, with sources"],
312
+ ["moshcode stocks search 'data center'", "across every indexed transcript"],
313
+ ["moshcode stocks reports --limit 10", "the stored index, best score first"],
314
314
  ],
315
315
  seeAlso: ["trade", "plugin", "tools"],
316
316
  note: "research aid, not advice — reports are stored snapshots and every one prints when it was generated. Set MOSHCODE_ADVISOR_URL to point at another instance.",
317
317
  },
318
- { name: "advisor", aliasOf: "ticker", description: "alias for ticker" },
318
+ { name: "advisor", aliasOf: "stocks", description: "alias for stocks" },
319
+ {
320
+ name: "crypto",
321
+ group: "tools",
322
+ description: "crypto market data from advis0r.com",
323
+ synopsis: [
324
+ ["moshcode crypto <pair>", "the full report for one pair"],
325
+ ["moshcode crypto <verb> [args…]", ""],
326
+ ],
327
+ verbs: "CRYPTO_VERBS",
328
+ flags: [
329
+ ["--json", "print the raw API response", ""],
330
+ ["--timeframe <tf>", "bars: 1Min | 5Min | 15Min | 1Hour | 1Day | 1Week", "1Day"],
331
+ ["--start <iso>", "bars: window start", "the API's own default"],
332
+ ["--end <iso>", "bars: window end", "now"],
333
+ ["--limit <n>", "cap results (bars/lookup)", "the API's own default"],
334
+ ["--depth <n>", "book: levels per side", "10"],
335
+ ["--period <p>", "spark: 24h | 7d", "24h"],
336
+ ["--horizon <n>", "technicals: quarters the score looks ahead (1 or 2)", "2"],
337
+ ],
338
+ examples: [
339
+ ["moshcode crypto BTC", "price, technicals, score, supply, order book"],
340
+ ["moshcode crypto lookup bitcoin", "asset name → BTC/USD"],
341
+ ["moshcode crypto spark BTC ETH SOL", "24h closes as sparklines"],
342
+ ["moshcode crypto bars ETH --timeframe 1Hour", "historical OHLCV"],
343
+ ["moshcode crypto book BTC-USD --depth 5", "top of book, both sides"],
344
+ ],
345
+ seeAlso: ["stocks", "trade", "plugin"],
346
+ note: "research aid, not advice — prices are Alpaca's US crypto venue alone and can differ materially from other exchanges. Crypto trades 24/7 with no circuit breakers. Set MOSHCODE_ADVISOR_URL to point at another instance.",
347
+ },
348
+ { name: "coins", aliasOf: "crypto", description: "alias for crypto" },
319
349
  {
320
350
  name: "plugin",
321
351
  group: "extend",
@@ -327,7 +357,7 @@ export const CORE_CLI_COMMANDS = [
327
357
  ["moshcode plugin install", "add the marketplace and install ticker"],
328
358
  ["moshcode plugin list", "what this marketplace ships, and what is installed"],
329
359
  ],
330
- seeAlso: ["skill", "mcp", "ticker"],
360
+ seeAlso: ["skill", "mcp", "stocks"],
331
361
  note: "Claude Code is the only engine with a plugin primitive; the others are reported as skipped, exactly as they are for skills.",
332
362
  },
333
363
  { name: "plugins", aliasOf: "plugin", description: "alias for plugin" },
@@ -509,36 +539,72 @@ export const DNS_VERBS = [
509
539
  ];
510
540
 
511
541
  /**
512
- * `ticker`'s verbs.
542
+ * `stocks`'s verbs.
513
543
  *
514
544
  * `report` exists so a symbol that collides with a verb name still has an
515
545
  * unambiguous spelling; without it, the bare-symbol shortcut would have no
516
546
  * escape hatch. src/advisor.mjs owns the parser and test/advisor.test.mjs
517
547
  * fails when the two lists disagree.
518
548
  */
519
- export const TICKER_VERBS = [
520
- { name: "report", description: "the stored research report for one ticker", synopsis: [["moshcode ticker report <symbol>", "same as `moshcode ticker <symbol>`"]] },
521
- { name: "signals", description: "every extracted signal for a ticker", synopsis: [["moshcode ticker signals <symbol>", ""]] },
549
+ export const STOCKS_VERBS = [
550
+ { name: "report", description: "the stored research report for one ticker", synopsis: [["moshcode stocks report <symbol>", "same as `moshcode stocks <symbol>`"]] },
551
+ { name: "signals", description: "every extracted signal for a ticker", synopsis: [["moshcode stocks signals <symbol>", ""]] },
522
552
  {
523
553
  name: "search", description: "full-text search across indexed transcripts",
524
- synopsis: [["moshcode ticker search <words…> [--limit n]", ""]],
554
+ synopsis: [["moshcode stocks search <words…> [--limit n]", ""]],
525
555
  },
526
556
  {
527
557
  name: "lookup", description: "find a ticker by company name",
528
- synopsis: [["moshcode ticker lookup <company…> [--limit n]", "rivian → RIVN"]],
558
+ synopsis: [["moshcode stocks lookup <company…> [--limit n]", "rivian → RIVN"]],
529
559
  },
530
560
  {
531
561
  name: "reports", description: "every stored report",
532
- synopsis: [["moshcode ticker reports [--sort recent|score|ticker] [--limit n]", ""]],
562
+ synopsis: [["moshcode stocks reports [--sort recent|score|ticker] [--limit n]", ""]],
533
563
  },
534
564
  {
535
565
  name: "discover", description: "a ranked watchlist for a topic",
536
- synopsis: [["moshcode ticker discover [topic…] [--horizon 1|2] [--provider p] [--limit n]", ""]],
566
+ synopsis: [["moshcode stocks discover [topic…] [--horizon 1|2] [--provider p] [--limit n]", ""]],
537
567
  note: "ranks by analyzing each candidate — this one takes minutes, not milliseconds.",
538
568
  },
539
- { name: "tickers", description: "every ticker present in the index", synopsis: [["moshcode ticker tickers", ""]] },
540
- { name: "stats", description: "index coverage counts", synopsis: [["moshcode ticker stats", ""]] },
541
- { name: "open", description: "open the shareable report page in a browser", synopsis: [["moshcode ticker open <symbol>", ""]] },
569
+ { name: "tickers", description: "every ticker present in the index", synopsis: [["moshcode stocks tickers", ""]] },
570
+ { name: "stats", description: "index coverage counts", synopsis: [["moshcode stocks stats", ""]] },
571
+ { name: "open", description: "open the shareable report page in a browser", synopsis: [["moshcode stocks open <symbol>", ""]] },
572
+ ];
573
+
574
+ /**
575
+ * `crypto`'s verbs.
576
+ *
577
+ * `report` earns its place for the same reason stocks's does — a bare pair is
578
+ * the shortcut, so a pair that collides with a verb name needs a spelling that
579
+ * cannot be mistaken for one. src/crypto.mjs owns the parser and
580
+ * test/crypto.test.mjs fails when the two lists disagree.
581
+ */
582
+ export const CRYPTO_VERBS = [
583
+ { name: "report", description: "the full report for one pair", synopsis: [["moshcode crypto report <pair>", "same as `moshcode crypto <pair>`"]] },
584
+ { name: "quote", description: "latest trade and quote, with the bid/ask spread", synopsis: [["moshcode crypto quote <pair>", ""]] },
585
+ {
586
+ name: "snapshot", description: "trade, quote and daily bars for several pairs",
587
+ synopsis: [["moshcode crypto snapshot <pair…>", "up to 20 pairs"]],
588
+ },
589
+ {
590
+ name: "technicals", description: "indicators and the technical score",
591
+ synopsis: [["moshcode crypto technicals <pair> [--horizon 1|2]", ""]],
592
+ },
593
+ {
594
+ name: "bars", description: "historical OHLCV",
595
+ synopsis: [["moshcode crypto bars <pair> [--timeframe tf] [--start iso] [--end iso] [--limit n]", ""]],
596
+ },
597
+ { name: "book", description: "top of the order book, both sides", synopsis: [["moshcode crypto book <pair> [--depth n]", ""]] },
598
+ {
599
+ name: "spark", description: "recent closes, drawn as sparklines",
600
+ synopsis: [["moshcode crypto spark <pair…> [--period 24h|7d]", ""]],
601
+ },
602
+ { name: "assets", description: "every supported pair", synopsis: [["moshcode crypto assets", ""]] },
603
+ {
604
+ name: "lookup", description: "find a pair by asset name",
605
+ synopsis: [["moshcode crypto lookup <name…> [--limit n]", "bitcoin → BTC/USD"]],
606
+ },
607
+ { name: "open", description: "open the shareable page in a browser", synopsis: [["moshcode crypto open <pair>", ""]] },
542
608
  ];
543
609
 
544
610
  export const PLUGIN_VERBS = [
@@ -560,7 +626,8 @@ export const VERB_TABLES = {
560
626
  UPGRADE_TARGETS,
561
627
  DNS_VERBS,
562
628
  TRADE_VERBS,
563
- TICKER_VERBS,
629
+ STOCKS_VERBS,
630
+ CRYPTO_VERBS,
564
631
  PLUGIN_VERBS,
565
632
  };
566
633
 
@@ -588,8 +655,10 @@ export const PIT_COMMANDS = [
588
655
  description: "list workflow tools, or run one" },
589
656
  { name: "trade", args: "<verb> [args…]", cli: "trade",
590
657
  description: "look up markets and preview/place Alpaca orders" },
591
- { name: "ticker", aliases: ["advisor"], args: "<symbol|verb> [args…]", cli: "ticker",
658
+ { name: "stocks", aliases: ["advisor"], args: "<symbol|verb> [args…]", cli: "stocks",
592
659
  description: "equity research from advis0r.com" },
660
+ { name: "crypto", aliases: ["coins"], args: "<pair|verb> [args…]", cli: "crypto",
661
+ description: "crypto market data from advis0r.com" },
593
662
  { name: "plugin", aliases: ["plugins"], args: "<verb> [name]", cli: "plugin",
594
663
  description: "install moshcode's slash commands into Claude Code" },
595
664
  { name: "socials", aliases: ["social"], pitOnly: true,