moshcode 0.27.0 → 0.29.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.
@@ -8,15 +8,15 @@
8
8
  },
9
9
  "plugins": [
10
10
  {
11
- "name": "ticker",
11
+ "name": "stocks",
12
12
  "description": "Equity research slash commands backed by advis0r.com: scored reports, extracted signals, transcript search, company-name lookup, and ranked watchlists.",
13
- "source": "./plugins/ticker",
13
+ "source": "./plugins/stocks",
14
14
  "category": "productivity",
15
15
  "author": {
16
16
  "name": "moshcoder",
17
17
  "url": "https://moshcode.sh"
18
18
  },
19
- "homepage": "https://github.com/moshcoder/moshcode#ticker",
19
+ "homepage": "https://github.com/moshcoder/moshcode#stocks",
20
20
  "keywords": ["stocks", "equity", "research", "markets", "advis0r"]
21
21
  },
22
22
  {
package/README.md CHANGED
@@ -46,7 +46,7 @@ or miss one that does. A test fails the build when it drifts.
46
46
  | `moshcode engines` | engines | list engines and installation status |
47
47
  | `moshcode tools` | tools | list workflow tools and installation status |
48
48
  | `moshcode trade` | tools | look up markets and trade through Alpaca |
49
- | `moshcode ticker` <br>`advisor` | tools | equity research from advis0r.com |
49
+ | `moshcode stocks` <br>`advisor` | tools | equity research from advis0r.com |
50
50
  | `moshcode crypto` <br>`coins` | tools | crypto market data from advis0r.com |
51
51
  | `moshcode plugin` <br>`plugins` | extend | install moshcode's slash commands into Claude Code |
52
52
  | `moshcode commands` | script | list built-in moshscript commands |
@@ -214,34 +214,34 @@ Alpaca's CLI has no confirmation prompts; `--submit` intentionally removes
214
214
  MoshCode's preview guard. Live trading additionally requires Alpaca's `--live`
215
215
  opt-in or corresponding environment setting.
216
216
 
217
- ### Equity research (`moshcode ticker`)
217
+ ### Equity research (`moshcode stocks`)
218
218
 
219
- Where `trade` is Alpaca's order book, `ticker` is the research desk:
219
+ Where `trade` is Alpaca's order book, `stocks` is the research desk:
220
220
  [advis0r.com](https://advis0r.com/api)'s public read-only API, rendered in the
221
221
  pit. No key, no login, no write routes, no binary to install:
222
222
 
223
223
  ```sh
224
- moshcode ticker NVDA # score, technicals, fundamentals, thesis, signals
225
- moshcode ticker lookup rivian # company name → RIVN
226
- moshcode ticker signals AAPL # what was said, quoted and sourced
227
- moshcode ticker search "data center" # across every indexed transcript
228
- moshcode ticker reports --limit 10 # the stored index, best score first
229
- moshcode ticker discover fusion # a ranked watchlist (slow — analyzes each candidate)
230
- moshcode ticker open NVDA # the shareable report page
224
+ moshcode stocks NVDA # score, technicals, fundamentals, thesis, signals
225
+ moshcode stocks lookup rivian # company name → RIVN
226
+ moshcode stocks signals AAPL # what was said, quoted and sourced
227
+ moshcode stocks search "data center" # across every indexed transcript
228
+ moshcode stocks reports --limit 10 # the stored index, best score first
229
+ moshcode stocks discover fusion # a ranked watchlist (slow — analyzes each candidate)
230
+ moshcode stocks open NVDA # the shareable report page
231
231
  ```
232
232
 
233
- Add `--json` to any of them for the raw response. The same facade is `/ticker …`
233
+ Add `--json` to any of them for the raw response. The same facade is `/stocks …`
234
234
  in the pit, and `MOSHCODE_ADVISOR_URL` points it at another instance.
235
235
 
236
236
  Reports are **stored snapshots**, not live quotes: every response carries
237
237
  `reportGeneratedAt` and every renderer prints it, alongside whether the price is
238
238
  delayed and which feed produced it. Scores labelled `offline` come from
239
239
  deterministic rules rather than a model. It is a research aid, not advice, and
240
- nothing under `ticker` can place an order.
240
+ nothing under `stocks` can place an order.
241
241
 
242
242
  ### Crypto market data (`moshcode crypto`)
243
243
 
244
- `crypto` is `ticker`'s sibling on the same host: advis0r's read-only crypto
244
+ `crypto` is `stocks`'s sibling on the same host: advis0r's read-only crypto
245
245
  routes over Alpaca's US crypto venue, which trades 24/7 and needs no extra
246
246
  subscription.
247
247
 
@@ -260,7 +260,7 @@ Pairs are accepted as `BTC`, `BTC-USD`, `BTC/USD` or `BTCUSD` — a bare asset
260
260
  resolves to that asset's USD pair. `--json` gives the raw response, `/crypto …`
261
261
  is the same facade in the pit, and `MOSHCODE_ADVISOR_URL` points it elsewhere.
262
262
 
263
- Unlike a `ticker` report, this is a **live venue read**, not a stored snapshot —
263
+ Unlike a `stocks` report, this is a **live venue read**, not a stored snapshot —
264
264
  there are no transcripts, no filings and no signals behind a crypto pair, and
265
265
  the failure mode runs the other way: the price is accurate to the second and
266
266
  stale by the time you act on it. Every response stamps when it was fetched.
@@ -268,7 +268,7 @@ stale by the time you act on it. Every response stamps when it was fetched.
268
268
  The technical score counts venue-local liquidity, so it is **not comparable** to
269
269
  an equity's score, and each response ships the `caveats` that say so. Prices are
270
270
  Alpaca's US venue alone and can differ materially from other exchanges. Research
271
- aid, not advice — and like `ticker`, nothing under `crypto` can place an order.
271
+ aid, not advice — and like `stocks`, nothing under `crypto` can place an order.
272
272
 
273
273
  ### Social posting from the pit
274
274
 
@@ -371,12 +371,12 @@ inside your engine too:
371
371
 
372
372
  ```sh
373
373
  moshcode plugin list # what the marketplace ships, and who can take it
374
- moshcode plugin install # add the marketplace + install `ticker`
374
+ moshcode plugin install # add the marketplace + install `stocks`
375
375
  moshcode plugin install crypto # add the marketplace + install `crypto`
376
- moshcode plugin remove ticker # take it back off
376
+ moshcode plugin remove stocks # take it back off
377
377
  ```
378
378
 
379
- `ticker@moshcode` adds `/ticker`, `/signals`, `/research`, `/lookup`,
379
+ `stocks@moshcode` adds `/stocks`, `/signals`, `/research`, `/lookup`,
380
380
  `/reports`, and `/discover` — the same advis0r research surface described above,
381
381
  driven from inside a coding session.
382
382
 
@@ -392,10 +392,24 @@ The equivalent by hand:
392
392
 
393
393
  ```sh
394
394
  claude plugin marketplace add moshcoder/moshcode
395
- claude plugin install ticker@moshcode
395
+ claude plugin install stocks@moshcode
396
396
  claude plugin install crypto@moshcode
397
397
  ```
398
398
 
399
+ ### Upgrading from `ticker@moshcode`
400
+
401
+ `stocks` was called `ticker` before v0.29.0. Installing the new id does **not**
402
+ replace the old one — engines install plugins side by side, so `/stocks` would
403
+ come from two plugins at once. Remove the old id first:
404
+
405
+ ```sh
406
+ moshcode plugin remove ticker
407
+ moshcode plugin install stocks
408
+ ```
409
+
410
+ `remove ticker` keeps working for exactly that reason, even though
411
+ `install ticker` no longer does.
412
+
399
413
  Claude Code is currently the only engine with a plugin primitive. The others are
400
414
  reported as skipped with a reason, the same way they are for skills, rather than
401
415
  being left out of the summary. `MOSHCODE_PLUGIN_SOURCE=.` installs from a local
package/bin/moshcode.mjs CHANGED
@@ -20,7 +20,7 @@ import { runUpgrade } from "../src/upgrade.mjs";
20
20
  import { selfUpdateCommand } from "../src/selfupdate.mjs";
21
21
  import { describeUninstall, uninstallPlan } from "../src/uninstall.mjs";
22
22
  import { mcpCommand, pluginCommand, skillCommand } from "../src/integrations.mjs";
23
- import { tickerCommand } from "../src/advisor.mjs";
23
+ import { stocksCommand } from "../src/advisor.mjs";
24
24
  import { cryptoCommand } from "../src/crypto.mjs";
25
25
  import { canOpenBrowser, openBrowser } from "../src/open-url.mjs";
26
26
  import { locate, tilde } from "../src/pwd.mjs";
@@ -347,8 +347,8 @@ async function main() {
347
347
  propagateExit(r.code, r.signal);
348
348
  return;
349
349
  }
350
- if (cmd === "ticker" || cmd === "advisor") {
351
- const code = await tickerCommand(rest, {
350
+ if (cmd === "stocks" || cmd === "advisor") {
351
+ const code = await stocksCommand(rest, {
352
352
  openUrl: (url) => canOpenBrowser() && openBrowser(url),
353
353
  });
354
354
  if (code) process.exitCode = code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.27.0",
3
+ "version": "0.29.0",
4
4
  "type": "module",
5
5
  "description": "moshcode — a metal wrapper for coding engines and native UGig/CoinPay workflow CLIs, with OpenPRD and moshscript",
6
6
  "repository": {
@@ -41,10 +41,10 @@ command falls back to `curl` against the same endpoints.
41
41
 
42
42
  Point the commands at another instance with `MOSHCODE_ADVISOR_URL`.
43
43
 
44
- ## Why this is separate from `ticker`
44
+ ## Why this is separate from `stocks`
45
45
 
46
46
  They answer different questions from different data, and share only a hostname.
47
- A `/ticker` report is a **stored snapshot** built from transcripts, SEC
47
+ A `/stocks` report is a **stored snapshot** built from transcripts, SEC
48
48
  fundamentals and extracted signals — its risk is a stale price read as a live
49
49
  one. A `/crypto` report is a **live venue read** with no transcripts, no
50
50
  filings and no signals — its risk is the opposite: a price that is accurate to
@@ -42,9 +42,9 @@ resolves to that asset's USD pair.
42
42
  - Crypto trades 24/7 with no circuit breakers and no market close. Never
43
43
  describe a crypto price as "at the close" or "premarket".
44
44
  - State `generatedAt` / `fetchedAt`. This is a live read, so it goes stale in
45
- seconds, not days — the opposite failure mode from a stored `/ticker` report.
45
+ seconds, not days — the opposite failure mode from a stored `/stocks` report.
46
46
  - The score's liquidity component counts venue-local volume only, so it is
47
- **not comparable** to an equity's score from `/ticker`. Do not rank the two
47
+ **not comparable** to an equity's score from `/stocks`. Do not rank the two
48
48
  against each other.
49
49
  - End with the response's own `disclaimer`. This is research, not advice.
50
50
  - Link the shareable page: `https://advis0r.com/crypto/<PAIR>`.
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
- "name": "ticker",
3
+ "name": "stocks",
4
4
  "description": "Equity research slash commands backed by advis0r.com: scored reports, extracted signals, transcript search, company-name lookup, and ranked watchlists.",
5
5
  "version": "0.1.0",
6
6
  "author": {
7
7
  "name": "moshcoder",
8
8
  "url": "https://moshcode.sh"
9
9
  },
10
- "homepage": "https://github.com/moshcoder/moshcode#ticker",
10
+ "homepage": "https://github.com/moshcoder/moshcode#stocks",
11
11
  "license": "MIT",
12
12
  "keywords": ["stocks", "equity", "research", "markets", "advis0r"]
13
13
  }
@@ -1,4 +1,4 @@
1
- # ticker — equity research in your engine 🤘
1
+ # stocks — equity research in your engine 🤘
2
2
 
3
3
  Slash commands backed by [advis0r.com](https://advis0r.com/api): scored research
4
4
  reports, extracted signals with sources, transcript search, company-name lookup,
@@ -6,13 +6,31 @@ and ranked watchlists.
6
6
 
7
7
  | command | what it does |
8
8
  | --- | --- |
9
- | `/ticker NVDA` | score, technicals, fundamentals, thesis, signals, sources |
9
+ | `/stocks NVDA` | score, technicals, fundamentals, thesis, signals, sources |
10
10
  | `/signals AAPL` | what was actually said, quoted and sourced |
11
11
  | `/research data center` | full-text search across every indexed transcript |
12
12
  | `/lookup rivian` | company name → `RIVN` |
13
13
  | `/reports` | every stored report, best score first |
14
14
  | `/discover fusion` | a ranked watchlist for a topic (slow) |
15
15
 
16
+ ## Upgrading from `ticker@moshcode`
17
+
18
+ This plugin used to be called `ticker`. Both it and its headline command were
19
+ renamed so the name says which market it covers, now that `/crypto` sits beside
20
+ it.
21
+
22
+ Installing the new one does **not** replace the old one — engines install
23
+ plugins side by side, so `/stocks` would come from two plugins at once. Remove
24
+ the old id first:
25
+
26
+ ```bash
27
+ moshcode plugin remove ticker
28
+ moshcode plugin install stocks
29
+ ```
30
+
31
+ `moshcode plugin remove ticker` keeps working for exactly this reason, even
32
+ though `moshcode plugin install ticker` no longer does.
33
+
16
34
  ## Install
17
35
 
18
36
  ```bash
@@ -23,7 +41,7 @@ Or straight from Claude Code:
23
41
 
24
42
  ```bash
25
43
  claude plugin marketplace add moshcoder/moshcode
26
- claude plugin install ticker@moshcode
44
+ claude plugin install stocks@moshcode
27
45
  ```
28
46
 
29
47
  Restart the engine afterwards — a newly installed plugin is not live in a
@@ -31,7 +49,7 @@ session that is already running.
31
49
 
32
50
  ## How it works
33
51
 
34
- Each command shells out to `moshcode ticker …`, which calls advis0r's public,
52
+ Each command shells out to `moshcode stocks …`, which calls advis0r's public,
35
53
  read-only API. No key, no login, no write routes. With `moshcode` absent, every
36
54
  command falls back to `curl` against the same endpoints.
37
55
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Build a ranked watchlist for a topic (slow — it analyzes each candidate).
3
3
  argument-hint: "[topic]"
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -9,7 +9,7 @@ allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*
9
9
  Rank candidates for `$ARGUMENTS` (no topic → the default watchlist).
10
10
 
11
11
  ```bash
12
- moshcode ticker discover $ARGUMENTS --limit 10 --json
12
+ moshcode stocks discover $ARGUMENTS --limit 10 --json
13
13
  ```
14
14
 
15
15
  Fallback: `curl -sS --max-time 180 "https://advis0r.com/api/discover?topic=<url-encoded>&provider=offline&horizon=2&limit=10"`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Find a ticker symbol by company name (rivian → RIVN).
3
3
  argument-hint: <company name>
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -9,7 +9,7 @@ allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*
9
9
  Resolve `$ARGUMENTS` to a ticker symbol.
10
10
 
11
11
  ```bash
12
- moshcode ticker lookup $ARGUMENTS --limit 10 --json
12
+ moshcode stocks lookup $ARGUMENTS --limit 10 --json
13
13
  ```
14
14
 
15
15
  Fallback: `curl -sS "https://advis0r.com/api/lookup?q=<url-encoded>&limit=10"`
@@ -22,6 +22,6 @@ Fallback: `curl -sS "https://advis0r.com/api/lookup?q=<url-encoded>&limit=10"`
22
22
  ## Rules
23
23
 
24
24
  - Show every match with its exchange — "Delta" is an airline and a faucet company.
25
- - Mark which ones have a report, and offer `/ticker <SYMBOL>` for those.
25
+ - Mark which ones have a report, and offer `/stocks <SYMBOL>` for those.
26
26
  - One unambiguous match: say the symbol and go straight to offering the report.
27
27
  - No match: say the *directory* has no match, and do not invent a symbol.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Every stored advis0r research report, best score first.
3
3
  argument-hint: "[--limit n] [--sort recent|score|ticker]"
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -9,7 +9,7 @@ allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*
9
9
  List the stored reports.
10
10
 
11
11
  ```bash
12
- moshcode ticker reports $ARGUMENTS --json
12
+ moshcode stocks reports $ARGUMENTS --json
13
13
  ```
14
14
 
15
15
  Fallback: `curl -sS "https://advis0r.com/api/reports?sort=score&limit=25"`
@@ -26,5 +26,5 @@ generatedAt }`, and `total` is how many exist.
26
26
  - **`generatedAt` per row, always.** These are snapshots taken at different
27
27
  times; a table that hides that reads as one consistent as-of date.
28
28
  - A row with no `aiProvider` was scored deterministically, not by a model.
29
- - Offer `/ticker <SYMBOL>` for anything worth a closer look.
29
+ - Offer `/stocks <SYMBOL>` for anything worth a closer look.
30
30
  - This is a coverage list, not a recommendation list. Rank order is score order.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Full-text search across every indexed earnings transcript and article.
3
3
  argument-hint: <words to search for>
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -9,7 +9,7 @@ allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*
9
9
  Search the transcript index for `$ARGUMENTS`.
10
10
 
11
11
  ```bash
12
- moshcode ticker search $ARGUMENTS --limit 20 --json
12
+ moshcode stocks search $ARGUMENTS --limit 20 --json
13
13
  ```
14
14
 
15
15
  Fallback: `curl -sS "https://advis0r.com/api/search?q=<url-encoded>&limit=20"`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: What was actually said about a ticker — extracted signals with quotes and sources.
3
3
  argument-hint: <SYMBOL>
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -9,7 +9,7 @@ allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*
9
9
  List the extracted signals for `$ARGUMENTS`.
10
10
 
11
11
  ```bash
12
- moshcode ticker signals $ARGUMENTS --json
12
+ moshcode stocks signals $ARGUMENTS --json
13
13
  ```
14
14
 
15
15
  Fallback: `curl -sS "https://advis0r.com/api/signals?ticker=$ARGUMENTS"`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Research one ticker — score, technicals, fundamentals, thesis, signals, and sources.
3
3
  argument-hint: <SYMBOL>
4
- allowed-tools: Bash(moshcode ticker:*), Bash(curl -sS https://advis0r.com/api/:*)
4
+ allowed-tools: Bash(moshcode stocks:*), Bash(curl -sS https://advis0r.com/api/:*)
5
5
  ---
6
6
 
7
7
  ## Task
@@ -11,7 +11,7 @@ Pull the stored research report for `$ARGUMENTS` and summarize it for the user.
11
11
  Run:
12
12
 
13
13
  ```bash
14
- moshcode ticker $ARGUMENTS --json
14
+ moshcode stocks $ARGUMENTS --json
15
15
  ```
16
16
 
17
17
  If `moshcode` is not installed, fall back to the API directly:
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;
@@ -468,7 +468,7 @@ function renderLookup(d) {
468
468
  const report = m.hasReport ? acid(" ✓ report") : ash(" · no report yet");
469
469
  lines.push(` ${acid(String(m.symbol).padEnd(8))}${bone(clip(m.name, 44).padEnd(46))}${ash(String(m.exchange ?? ""))}${report}`);
470
470
  }
471
- lines.push("", ` ${ash("then:")} ${bone(`moshcode ticker ${matches[0].symbol}`)}`);
471
+ lines.push("", ` ${ash("then:")} ${bone(`moshcode stocks ${matches[0].symbol}`)}`);
472
472
  return lines.join("\n");
473
473
  }
474
474
 
@@ -484,7 +484,7 @@ function renderReports(d) {
484
484
  `${bone(money(r.lastPrice).padStart(11))} ${ash(clip(r.companyName, 28).padEnd(29))}${ash(day(r.generatedAt))}`,
485
485
  );
486
486
  }
487
- lines.push("", ` ${ash("detail:")} ${bone(`moshcode ticker ${reports[0].ticker}`)}`);
487
+ lines.push("", ` ${ash("detail:")} ${bone(`moshcode stocks ${reports[0].ticker}`)}`);
488
488
  return lines.join("\n");
489
489
  }
490
490
 
@@ -550,12 +550,12 @@ export function renderAdvisor(verb, data, { columns } = {}) {
550
550
  }
551
551
 
552
552
  /**
553
- * 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.
554
554
  *
555
555
  * `deps` exists so tests drive the whole command — parse, fetch, render — with
556
556
  * no network and no stdout.
557
557
  */
558
- export async function tickerCommand(argv = [], deps = {}) {
558
+ export async function stocksCommand(argv = [], deps = {}) {
559
559
  const {
560
560
  out = (s) => console.log(s),
561
561
  fail = (s) => console.error(s),
@@ -565,8 +565,8 @@ export async function tickerCommand(argv = [], deps = {}) {
565
565
  columns = process.stdout.columns,
566
566
  } = deps;
567
567
 
568
- const request = tickerArgs(argv);
569
- if (request.usage) { out(tickerUsage()); return 0; }
568
+ const request = stocksArgs(argv);
569
+ if (request.usage) { out(stocksUsage()); return 0; }
570
570
  if (request.error) { fail(danger(`✗ ${request.error}`)); return 1; }
571
571
 
572
572
  if (request.verb === "open") {
@@ -587,7 +587,7 @@ export async function tickerCommand(argv = [], deps = {}) {
587
587
  if (request.json) { out(JSON.stringify(res.data, null, 2)); return 1; }
588
588
  fail(danger(`✗ ${message}`));
589
589
  if (res.data?.didYouMean?.symbol) {
590
- fail(` ${ash("try:")} ${bone(`moshcode ticker ${res.data.didYouMean.symbol}`)}`);
590
+ fail(` ${ash("try:")} ${bone(`moshcode stocks ${res.data.didYouMean.symbol}`)}`);
591
591
  }
592
592
  return 1;
593
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,16 @@ 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
319
  {
320
320
  name: "crypto",
321
321
  group: "tools",
@@ -342,7 +342,7 @@ export const CORE_CLI_COMMANDS = [
342
342
  ["moshcode crypto bars ETH --timeframe 1Hour", "historical OHLCV"],
343
343
  ["moshcode crypto book BTC-USD --depth 5", "top of book, both sides"],
344
344
  ],
345
- seeAlso: ["ticker", "trade", "plugin"],
345
+ seeAlso: ["stocks", "trade", "plugin"],
346
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
347
  },
348
348
  { name: "coins", aliasOf: "crypto", description: "alias for crypto" },
@@ -357,7 +357,7 @@ export const CORE_CLI_COMMANDS = [
357
357
  ["moshcode plugin install", "add the marketplace and install ticker"],
358
358
  ["moshcode plugin list", "what this marketplace ships, and what is installed"],
359
359
  ],
360
- seeAlso: ["skill", "mcp", "ticker"],
360
+ seeAlso: ["skill", "mcp", "stocks"],
361
361
  note: "Claude Code is the only engine with a plugin primitive; the others are reported as skipped, exactly as they are for skills.",
362
362
  },
363
363
  { name: "plugins", aliasOf: "plugin", description: "alias for plugin" },
@@ -539,42 +539,42 @@ export const DNS_VERBS = [
539
539
  ];
540
540
 
541
541
  /**
542
- * `ticker`'s verbs.
542
+ * `stocks`'s verbs.
543
543
  *
544
544
  * `report` exists so a symbol that collides with a verb name still has an
545
545
  * unambiguous spelling; without it, the bare-symbol shortcut would have no
546
546
  * escape hatch. src/advisor.mjs owns the parser and test/advisor.test.mjs
547
547
  * fails when the two lists disagree.
548
548
  */
549
- export const TICKER_VERBS = [
550
- { name: "report", description: "the stored research report for one ticker", synopsis: [["moshcode ticker report <symbol>", "same as `moshcode ticker <symbol>`"]] },
551
- { 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>", ""]] },
552
552
  {
553
553
  name: "search", description: "full-text search across indexed transcripts",
554
- synopsis: [["moshcode ticker search <words…> [--limit n]", ""]],
554
+ synopsis: [["moshcode stocks search <words…> [--limit n]", ""]],
555
555
  },
556
556
  {
557
557
  name: "lookup", description: "find a ticker by company name",
558
- synopsis: [["moshcode ticker lookup <company…> [--limit n]", "rivian → RIVN"]],
558
+ synopsis: [["moshcode stocks lookup <company…> [--limit n]", "rivian → RIVN"]],
559
559
  },
560
560
  {
561
561
  name: "reports", description: "every stored report",
562
- synopsis: [["moshcode ticker reports [--sort recent|score|ticker] [--limit n]", ""]],
562
+ synopsis: [["moshcode stocks reports [--sort recent|score|ticker] [--limit n]", ""]],
563
563
  },
564
564
  {
565
565
  name: "discover", description: "a ranked watchlist for a topic",
566
- 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]", ""]],
567
567
  note: "ranks by analyzing each candidate — this one takes minutes, not milliseconds.",
568
568
  },
569
- { name: "tickers", description: "every ticker present in the index", synopsis: [["moshcode ticker tickers", ""]] },
570
- { name: "stats", description: "index coverage counts", synopsis: [["moshcode ticker stats", ""]] },
571
- { 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
572
  ];
573
573
 
574
574
  /**
575
575
  * `crypto`'s verbs.
576
576
  *
577
- * `report` earns its place for the same reason ticker's does — a bare pair is
577
+ * `report` earns its place for the same reason stocks's does — a bare pair is
578
578
  * the shortcut, so a pair that collides with a verb name needs a spelling that
579
579
  * cannot be mistaken for one. src/crypto.mjs owns the parser and
580
580
  * test/crypto.test.mjs fails when the two lists disagree.
@@ -626,7 +626,7 @@ export const VERB_TABLES = {
626
626
  UPGRADE_TARGETS,
627
627
  DNS_VERBS,
628
628
  TRADE_VERBS,
629
- TICKER_VERBS,
629
+ STOCKS_VERBS,
630
630
  CRYPTO_VERBS,
631
631
  PLUGIN_VERBS,
632
632
  };
@@ -655,7 +655,7 @@ export const PIT_COMMANDS = [
655
655
  description: "list workflow tools, or run one" },
656
656
  { name: "trade", args: "<verb> [args…]", cli: "trade",
657
657
  description: "look up markets and preview/place Alpaca orders" },
658
- { name: "ticker", aliases: ["advisor"], args: "<symbol|verb> [args…]", cli: "ticker",
658
+ { name: "stocks", aliases: ["advisor"], args: "<symbol|verb> [args…]", cli: "stocks",
659
659
  description: "equity research from advis0r.com" },
660
660
  { name: "crypto", aliases: ["coins"], args: "<pair|verb> [args…]", cli: "crypto",
661
661
  description: "crypto market data from advis0r.com" },
package/src/crypto.mjs CHANGED
@@ -5,8 +5,8 @@
5
5
  // function of the decoded JSON. Every route is public and read-only, so there
6
6
  // is no login verb and no write verb — `moshcode trade` is where orders live.
7
7
  //
8
- // This is a *sibling* of `ticker`, not a mode of it, because the two answer
9
- // different questions from different data. A ticker report is a stored snapshot
8
+ // This is a *sibling* of `stocks`, not a mode of it, because the two answer
9
+ // different questions from different data. A stocks report is a stored snapshot
10
10
  // built from transcripts, SEC fundamentals and extracted signals. A crypto
11
11
  // report is a live read of Alpaca's US crypto venue: no transcripts, no
12
12
  // filings, no signals, and a `fetchedAt` measured in seconds rather than days.
@@ -50,7 +50,7 @@ export const CRYPTO_VERB_NAMES = [
50
50
  "report", "quote", "snapshot", "technicals", "bars", "book", "spark", "assets", "lookup", "open",
51
51
  ];
52
52
 
53
- // The same reasoning as ticker's alias table: `/crypto price BTC` and
53
+ // The same reasoning as stocks's alias table: `/crypto price BTC` and
54
54
  // `/crypto candles BTC` should not be errors when the intent is obvious.
55
55
  // `search` maps to lookup rather than erroring — crypto has no transcript
56
56
  // index to search, and a directory lookup is what the word means here.
@@ -83,7 +83,7 @@ export function resolveVerb(word) {
83
83
  * the API resolves it to that asset's USD pair, and inventing the `-USD` here
84
84
  * would silently break the day a base has no USD pair.
85
85
  *
86
- * Deliberately narrow, like ticker's: the whole job of the check is to tell
86
+ * Deliberately narrow, like stocks's: the whole job of the check is to tell
87
87
  * `BTC` from `bitcoin` and send the second one to lookup with a useful message
88
88
  * instead of a 400. Bases run to five characters (SUSHI, MATIC, TRUMP), so six
89
89
  * leaves room for the concatenated `BTCUSD` spelling without swallowing words.
@@ -293,7 +293,7 @@ export function cryptoUrl(request, { base = advisorBase() } = {}) {
293
293
  * Execute a translated request. `fetchImpl` is injectable for tests.
294
294
  *
295
295
  * Every crypto route is a live venue read, so one timeout fits all of them —
296
- * unlike ticker, which has to budget separately for `discover`'s per-candidate
296
+ * unlike stocks, which has to budget separately for `discover`'s per-candidate
297
297
  * analysis.
298
298
  */
299
299
  export async function fetchCrypto(request, { fetchImpl = globalThis.fetch, base = advisorBase(), timeoutMs = 45_000 } = {}) {
package/src/help.mjs CHANGED
@@ -103,6 +103,18 @@ function distance(a, b) {
103
103
  return prev[b.length];
104
104
  }
105
105
 
106
+ /**
107
+ * Commands that were renamed, and what they became.
108
+ *
109
+ * Edit distance cannot help here — `ticker` is nowhere near `stocks` — so a
110
+ * rename that isn't recorded leaves people with a bare "unknown command" for a
111
+ * verb that worked yesterday. This is deliberately *not* an alias table: the
112
+ * old name stays dead, it just says where its replacement went.
113
+ */
114
+ export const RENAMED_COMMANDS = {
115
+ ticker: "stocks",
116
+ };
117
+
106
118
  /**
107
119
  * The nearest command to something that is not one, or null.
108
120
  *
@@ -114,6 +126,8 @@ function distance(a, b) {
114
126
  export function suggest(input, extra = []) {
115
127
  const typed = String(input ?? "").toLowerCase();
116
128
  if (!typed) return null;
129
+ // A known rename beats any distance match — it is not a guess.
130
+ if (RENAMED_COMMANDS[typed]) return RENAMED_COMMANDS[typed];
117
131
  const candidates = [...CORE_CLI_COMMANDS.map((c) => c.name), ...extra]
118
132
  .filter((n) => !n.startsWith("-"));
119
133
 
@@ -10,7 +10,7 @@ import {
10
10
  } from "./skills.mjs";
11
11
  import {
12
12
  MARKETPLACE_NAME, PLUGINS, PLUGIN_ENGINES, marketplaceSource, planPluginCommand,
13
- pluginId, resolvePlugin, runPluginCommand,
13
+ pluginId, resolvePlugin, resolveRetiredPlugin, runPluginCommand,
14
14
  } from "./plugins.mjs";
15
15
  import { catalogList, resolveCatalog } from "./mcp-catalog.mjs";
16
16
  import { MCP_VERBS, PLUGIN_VERBS, SKILL_VERBS } from "./cli-schema.mjs";
@@ -315,10 +315,22 @@ export async function pluginCommand(tokens, { run, installedSet } = {}) {
315
315
  const stray = rest.find((t) => String(t).startsWith("-"));
316
316
  if (stray) { console.log(err(`unknown plugin flag "${stray}"`)); return 1; }
317
317
 
318
- const plugin = resolvePlugin(rest[0]);
318
+ let plugin = resolvePlugin(rest[0]);
319
319
  if (!plugin) {
320
- console.log(err(`unknown plugin "${rest[0]}" this marketplace ships ${PLUGINS.map((p) => p.name).join(", ")}`));
321
- return 1;
320
+ // A retired name still has to be removable: the old plugin is installed in
321
+ // someone's engine right now, and installing its replacement puts a second
322
+ // copy of the same slash commands beside it rather than replacing it.
323
+ const retired = resolveRetiredPlugin(rest[0]);
324
+ if (retired && verb === "remove") {
325
+ plugin = { name: retired.name, description: `retired — renamed to ${retired.renamedTo}`, commands: [] };
326
+ } else if (retired) {
327
+ console.log(err(`"${retired.name}" is now "${retired.renamedTo}" — install ${bone(pluginId(retired.renamedTo))}`));
328
+ console.log(info(`already have the old one? ${bone(`moshcode plugin remove ${retired.name}`)} first`));
329
+ return 1;
330
+ } else {
331
+ console.log(err(`unknown plugin "${rest[0]}" — this marketplace ships ${PLUGINS.map((p) => p.name).join(", ")}`));
332
+ return 1;
333
+ }
322
334
  }
323
335
 
324
336
  const source = marketplaceSource();
package/src/plugins.mjs CHANGED
@@ -29,9 +29,9 @@ export function marketplaceSource(env = process.env) {
29
29
  /** The plugins this marketplace ships. Mirrors .claude-plugin/marketplace.json. */
30
30
  export const PLUGINS = [
31
31
  {
32
- name: "ticker",
32
+ name: "stocks",
33
33
  description: "equity research slash commands backed by advis0r.com",
34
- commands: ["/ticker", "/signals", "/research", "/lookup", "/reports", "/discover"],
34
+ commands: ["/stocks", "/signals", "/research", "/lookup", "/reports", "/discover"],
35
35
  },
36
36
  {
37
37
  name: "crypto",
@@ -42,12 +42,38 @@ export const PLUGINS = [
42
42
 
43
43
  export const DEFAULT_PLUGIN = PLUGINS[0].name;
44
44
 
45
+ /**
46
+ * Plugins this marketplace no longer ships, and what replaced them.
47
+ *
48
+ * A renamed plugin is not the same problem as a renamed command. The old
49
+ * command simply stops existing; an old *plugin* is still sitting installed in
50
+ * someone's engine, still serving its slash commands, and the new one installs
51
+ * alongside it rather than over it — so `/stocks` would resolve to two plugins
52
+ * at once. Removal therefore has to keep reaching a name that install refuses.
53
+ */
54
+ export const RETIRED_PLUGINS = [
55
+ { name: "ticker", renamedTo: "stocks" },
56
+ ];
57
+
45
58
  export function resolvePlugin(name) {
46
59
  if (!name) return PLUGINS.find((p) => p.name === DEFAULT_PLUGIN) ?? null;
47
60
  const key = String(name).toLowerCase().replace(/@.*$/, "");
48
61
  return PLUGINS.find((p) => p.name === key) ?? null;
49
62
  }
50
63
 
64
+ /**
65
+ * A retired plugin by its old name, or null.
66
+ *
67
+ * Deliberately separate from resolvePlugin: `remove ticker` must work so the
68
+ * stale install can be cleaned up, and `install ticker` must not, or the rename
69
+ * never actually happens.
70
+ */
71
+ export function resolveRetiredPlugin(name) {
72
+ if (!name) return null;
73
+ const key = String(name).toLowerCase().replace(/@.*$/, "");
74
+ return RETIRED_PLUGINS.find((p) => p.name === key) ?? null;
75
+ }
76
+
51
77
  /** Fully-qualified plugin id, the form `claude plugin install` disambiguates with. */
52
78
  export function pluginId(name) {
53
79
  return `${name}@${MARKETPLACE_NAME}`;
package/src/tui.mjs CHANGED
@@ -20,12 +20,12 @@ import { fetchMotdAd } from "./ads.mjs";
20
20
  import { runScript } from "./runtime.mjs";
21
21
  import { moshVocabulary } from "./commands.mjs";
22
22
  import { mcpCommand, pluginCommand, skillCommand } from "./integrations.mjs";
23
- import { tickerCommand } from "./advisor.mjs";
23
+ import { stocksCommand } from "./advisor.mjs";
24
24
  import { cryptoCommand } from "./crypto.mjs";
25
25
  import { canOpenBrowser, openBrowser } from "./open-url.mjs";
26
26
  import { banner, hr, acid, ash, bone, dim, ok, err, warn, info, moshcodeVersion } from "./ui.mjs";
27
27
  import { CORE_CLI_COMMAND_NAMES } from "./cli-schema.mjs";
28
- import { findPitCommand, pitHelpModel, renderPitCommand, suggest, wantsHelp } from "./help.mjs";
28
+ import { RENAMED_COMMANDS, findPitCommand, pitHelpModel, renderPitCommand, suggest, wantsHelp } from "./help.mjs";
29
29
  import { openNewTab } from "./tabs.mjs";
30
30
 
31
31
  const PROMPT = () => acid("mosh ") + dim("▸ ");
@@ -673,13 +673,13 @@ export async function tui() {
673
673
  rl = mkrl();
674
674
  continue;
675
675
  }
676
- // `/ticker` renders in the pit rather than handing the terminal to a tool:
676
+ // `/stocks` renders in the pit rather than handing the terminal to a tool:
677
677
  // there is no advis0r binary to launch, only a public read-only API.
678
- if (cmd === "ticker" || cmd === "advisor") {
679
- await tickerCommand(rest, { openUrl: (url) => canOpenBrowser() && openBrowser(url) });
678
+ if (cmd === "stocks" || cmd === "advisor") {
679
+ await stocksCommand(rest, { openUrl: (url) => canOpenBrowser() && openBrowser(url) });
680
680
  continue;
681
681
  }
682
- // `/crypto` renders in the pit for the same reason `/ticker` does: there is
682
+ // `/crypto` renders in the pit for the same reason `/stocks` does: there is
683
683
  // no crypto binary to hand the terminal to, only a public read-only API.
684
684
  if (cmd === "crypto" || cmd === "coins") {
685
685
  await cryptoCommand(rest, { openUrl: (url) => canOpenBrowser() && openBrowser(url) });
@@ -722,7 +722,12 @@ export async function tui() {
722
722
  rl = mkrl();
723
723
  continue;
724
724
  }
725
- console.log(err(`unknown command "${line}". /help for the list.`));
725
+ // A renamed verb gets pointed at its replacement; `/ticker` was a pit
726
+ // command for a release, so a bare "unknown command" is a dead end here.
727
+ const renamed = RENAMED_COMMANDS[cmd];
728
+ console.log(err(renamed
729
+ ? `unknown command "${line}" — /${cmd} is now /${renamed}.`
730
+ : `unknown command "${line}". /help for the list.`));
726
731
  }
727
732
 
728
733
  try { rl.close(); } catch { /* noop */ }