moshcode 0.29.0 → 0.29.2

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/README.md CHANGED
@@ -376,30 +376,43 @@ moshcode plugin install crypto # add the marketplace + install `crypto`
376
376
  moshcode plugin remove stocks # take it back off
377
377
  ```
378
378
 
379
- `stocks@moshcode` adds `/stocks`, `/signals`, `/research`, `/lookup`,
380
- `/reports`, and `/discover` — the same advis0r research surface described above,
379
+ `stocks@moshcode` adds `/stocks:stocks`, `/stocks:signals`, `/stocks:research`,
380
+ `/stocks:lookup`, `/stocks:reports`, and `/stocks:discover` — the same advis0r research surface described above,
381
381
  driven from inside a coding session.
382
382
 
383
- `crypto@moshcode` adds `/crypto`, `/quote`, `/book`, `/bars`, `/spark`,
384
- `/pairs`, and `/coin`. It ships separately because it is a different surface,
383
+ `crypto@moshcode` adds `/crypto:crypto`, `/crypto:quote`, `/crypto:book`,
384
+ `/crypto:bars`, `/crypto:spark`, `/crypto:pairs`, and `/crypto:coin`. It ships separately because it is a different surface,
385
385
  not a mode of the first: live venue reads instead of stored snapshots, and a
386
386
  score that must not be ranked against an equity's.
387
387
 
388
388
  Restart the engine after installing either; a newly installed plugin is not live
389
389
  in a session that is already running.
390
390
 
391
+ Plugin commands are namespaced `/<plugin>:<command>` — always, not only when two
392
+ plugins collide — so it is `/stocks:signals AAPL`, and a bare `/signals` answers
393
+ `Unknown command`. Typing `/` and picking from the menu inserts the right form.
394
+ This is the one place the two surfaces differ: inside the moshcode pit the same
395
+ research is plain `/stocks …` and `/crypto …`, because those are moshcode's own
396
+ commands rather than a plugin's.
397
+
391
398
  The equivalent by hand:
392
399
 
393
400
  ```sh
394
401
  claude plugin marketplace add moshcoder/moshcode
402
+ claude plugin marketplace update moshcode # `add` is a no-op if you already have it
395
403
  claude plugin install stocks@moshcode
396
404
  claude plugin install crypto@moshcode
397
405
  ```
398
406
 
407
+ The `update` line matters if you have ever installed from this marketplace
408
+ before: `add` declines to do anything for a marketplace already on disk, so
409
+ without a refresh the install reads a stale copy and fails with "not found in
410
+ any marketplace". `moshcode plugin install` runs both steps for you.
411
+
399
412
  ### Upgrading from `ticker@moshcode`
400
413
 
401
414
  `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
415
+ replace the old one — engines install plugins side by side, so `/stocks:stocks` would
403
416
  come from two plugins at once. Remove the old id first:
404
417
 
405
418
  ```sh
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
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": {
@@ -6,17 +6,22 @@ across Alpaca's US crypto venue.
6
6
 
7
7
  | command | what it does |
8
8
  | --- | --- |
9
- | `/crypto BTC` | price, technicals, score, supply, order book |
10
- | `/quote ETH-USD` | latest trade and quote, with the spread in bps |
11
- | `/book BTC-USD` | top of the order book, both sides |
12
- | `/bars ETH-USD` | historical OHLCV at any supported timeframe |
13
- | `/spark BTC ETH SOL` | recent moves across pairs, ranked |
14
- | `/pairs` | every supported pair, grouped by quote asset |
15
- | `/coin bitcoin` | asset name → `BTC/USD` |
9
+ | `/crypto:crypto BTC` | price, technicals, score, supply, order book |
10
+ | `/crypto:quote ETH-USD` | latest trade and quote, with the spread in bps |
11
+ | `/crypto:book BTC-USD` | top of the order book, both sides |
12
+ | `/crypto:bars ETH-USD` | historical OHLCV at any supported timeframe |
13
+ | `/crypto:spark BTC ETH SOL` | recent moves across pairs, ranked |
14
+ | `/crypto:pairs` | every supported pair, grouped by quote asset |
15
+ | `/crypto:coin bitcoin` | asset name → `BTC/USD` |
16
16
 
17
17
  Pairs are accepted as `BTC`, `BTC-USD`, `BTC/USD` or `BTCUSD`. A bare asset
18
18
  resolves to that asset's USD pair.
19
19
 
20
+ The `crypto:` prefix is not optional. Claude Code namespaces every plugin
21
+ command as `/<plugin>:<command>` — always, not only when two plugins collide —
22
+ so a bare `/crypto` answers `Unknown command`. Typing `/` and picking from the
23
+ menu inserts the right form for you.
24
+
20
25
  ## Install
21
26
 
22
27
  ```bash
@@ -27,6 +32,7 @@ Or straight from Claude Code:
27
32
 
28
33
  ```bash
29
34
  claude plugin marketplace add moshcoder/moshcode
35
+ claude plugin marketplace update moshcode # `add` is a no-op if you already have it
30
36
  claude plugin install crypto@moshcode
31
37
  ```
32
38
 
@@ -44,9 +50,9 @@ Point the commands at another instance with `MOSHCODE_ADVISOR_URL`.
44
50
  ## Why this is separate from `stocks`
45
51
 
46
52
  They answer different questions from different data, and share only a hostname.
47
- A `/stocks` report is a **stored snapshot** built from transcripts, SEC
53
+ A `/stocks:stocks` report is a **stored snapshot** built from transcripts, SEC
48
54
  fundamentals and extracted signals — its risk is a stale price read as a live
49
- one. A `/crypto` report is a **live venue read** with no transcripts, no
55
+ one. A `/crypto:crypto` report is a **live venue read** with no transcripts, no
50
56
  filings and no signals — its risk is the opposite: a price that is accurate to
51
57
  the second and stale by the time you act on it.
52
58
 
@@ -28,4 +28,4 @@ Fallback: `curl -sS "https://advis0r.com/api/crypto/lookup?q=<url-encoded>&limit
28
28
  match is the one they meant.
29
29
  - No match: say this venue lists no such pair, and do not invent a symbol. The
30
30
  coin may exist and simply not be listed here — those are different answers.
31
- - Offer `/crypto <PAIR>` for the match you land on.
31
+ - Offer `/crypto:crypto <PAIR>` for the match you land on.
@@ -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 `/stocks` report.
45
+ seconds, not days — the opposite failure mode from a stored `/stocks: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 `/stocks`. Do not rank the two
47
+ **not comparable** to an equity's score from `/stocks: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>`.
@@ -25,7 +25,7 @@ URL-safe spelling (`BTC-USD`) used in paths; `symbol` is canonical (`BTC/USD`).
25
25
  different markets with three different liquidity profiles.
26
26
  - **`idle` means listed but not currently printing trades** — it is not the same
27
27
  as unsupported. Show idle pairs, marked, rather than filtering them out.
28
- - If the user was looking for a specific coin, use `/coin <name>` instead of
28
+ - If the user was looking for a specific coin, use `/crypto:coin <name>` instead of
29
29
  scanning this list for them.
30
30
  - This is the coverage of one venue, not of crypto. A coin missing here is
31
31
  missing *from Alpaca's US venue*.
@@ -32,5 +32,5 @@ Fallback: `curl -sS "https://advis0r.com/api/crypto/sparklines?symbols=BTC-USD,E
32
32
  - Name the window (`start` → `end`) and the period. "Up 5%" over 24h and over 7d
33
33
  are different claims.
34
34
  - Do not extrapolate a trend from 24 points, and do not call a direction
35
- "momentum" without the technicals to back it — `/crypto <PAIR>` has those.
35
+ "momentum" without the technicals to back it — `/crypto:crypto <PAIR>` has those.
36
36
  - End with the response's own `disclaimer`.
@@ -6,21 +6,29 @@ and ranked watchlists.
6
6
 
7
7
  | command | what it does |
8
8
  | --- | --- |
9
- | `/stocks NVDA` | score, technicals, fundamentals, thesis, signals, sources |
10
- | `/signals AAPL` | what was actually said, quoted and sourced |
11
- | `/research data center` | full-text search across every indexed transcript |
12
- | `/lookup rivian` | company name → `RIVN` |
13
- | `/reports` | every stored report, best score first |
14
- | `/discover fusion` | a ranked watchlist for a topic (slow) |
9
+ | `/stocks:stocks NVDA` | score, technicals, fundamentals, thesis, signals, sources |
10
+ | `/stocks:signals AAPL` | what was actually said, quoted and sourced |
11
+ | `/stocks:research data center` | full-text search across every indexed transcript |
12
+ | `/stocks:lookup rivian` | company name → `RIVN` |
13
+ | `/stocks:reports` | every stored report, best score first |
14
+ | `/stocks:discover fusion` | a ranked watchlist for a topic (slow) |
15
+
16
+ The `stocks:` prefix is not optional. Claude Code namespaces every plugin
17
+ command as `/<plugin>:<command>` — always, not only when two plugins collide —
18
+ so a bare `/stocks` answers `Unknown command`. Typing `/` and picking from the
19
+ menu inserts the right form for you.
20
+
21
+ (Inside the moshcode pit itself, `/stocks …` *is* bare — that is moshcode's own
22
+ command, not this plugin's.)
15
23
 
16
24
  ## Upgrading from `ticker@moshcode`
17
25
 
18
26
  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
27
+ renamed so the name says which market it covers, now that `/crypto:crypto` sits beside
20
28
  it.
21
29
 
22
30
  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
31
+ plugins side by side, so `/stocks:stocks` would come from two plugins at once. Remove
24
32
  the old id first:
25
33
 
26
34
  ```bash
@@ -41,6 +49,7 @@ Or straight from Claude Code:
41
49
 
42
50
  ```bash
43
51
  claude plugin marketplace add moshcoder/moshcode
52
+ claude plugin marketplace update moshcode # `add` is a no-op if you already have it
44
53
  claude plugin install stocks@moshcode
45
54
  ```
46
55
 
@@ -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 `/stocks <SYMBOL>` for those.
25
+ - Mark which ones have a report, and offer `/stocks: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.
@@ -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 `/stocks <SYMBOL>` for anything worth a closer look.
29
+ - Offer `/stocks:stocks <SYMBOL>` for anything worth a closer look.
30
30
  - This is a coverage list, not a recommendation list. Rank order is score order.
@@ -25,5 +25,5 @@ hit is a hit — but relevance is not ranked. Read before summarizing.
25
25
  - Cluster the hits by ticker and say which companies came up, with dates.
26
26
  - Quote sparingly and attribute each quote to its speaker and ticker.
27
27
  - If nothing matches, say the *index* has no match — this searches advis0r's
28
- indexed corpus, not the whole web. Suggest `/lookup` if the query looks like
28
+ indexed corpus, not the whole web. Suggest `/stocks:lookup` if the query looks like
29
29
  a company name.
package/src/crypto.mjs CHANGED
@@ -488,7 +488,11 @@ function priceLine(snapshot, quote) {
488
488
  const bits = [bone(price(last, quote))];
489
489
  if (change) {
490
490
  const paint = changeTone(change.percent);
491
- bits.push(paint(`${change.absolute >= 0 ? "+" : ""}${price(change.absolute, quote, { like: last })}`), paint(`(${pct(change.percent)})`));
491
+ // Sign the magnitude, don't let price() sign it: a signed price puts the
492
+ // minus after the currency mark ("$-186.36"), so a down move reads unlike
493
+ // the up move's "+$186.36". The sign leads, the way pct() already signs.
494
+ const signed = `${change.absolute >= 0 ? "+" : "-"}${price(Math.abs(Number(change.absolute)), quote, { like: last })}`;
495
+ bits.push(paint(signed), paint(`(${pct(change.percent)})`));
492
496
  }
493
497
  const feed = [
494
498
  snapshot?.delayed === false ? "live" : snapshot?.delayed === true ? "delayed" : null,
@@ -345,7 +345,7 @@ export async function pluginCommand(tokens, { run, installedSet } = {}) {
345
345
  // A newly installed plugin is not live in an already-running engine, and the
346
346
  // first thing anyone does is type the slash command and conclude it failed.
347
347
  if (!anyFailed(results) && verb === "install" && results.some((r) => r.status === "installed")) {
348
- console.log(info(`restart the engine, then try ${acid(`${plugin.commands[0]} NVDA`)}`));
348
+ console.log(info(`restart the engine, then try ${acid(plugin.example ?? plugin.commands[0])}`));
349
349
  }
350
350
  return anyFailed(results) ? 1 : 0;
351
351
  }
package/src/plugins.mjs CHANGED
@@ -26,20 +26,40 @@ export function marketplaceSource(env = process.env) {
26
26
  return String(env.MOSHCODE_PLUGIN_SOURCE || "moshcoder/moshcode").trim() || "moshcoder/moshcode";
27
27
  }
28
28
 
29
- /** The plugins this marketplace ships. Mirrors .claude-plugin/marketplace.json. */
29
+ /**
30
+ * The plugins this marketplace ships. Mirrors .claude-plugin/marketplace.json.
31
+ *
32
+ * `commands` carry their namespace because that is how they are actually
33
+ * invoked. Claude Code namespaces every plugin command as
34
+ * `/<plugin>:<command>` — always, not only when two plugins collide — so a bare
35
+ * `/crypto` is simply not a command, and advertising one sends people to
36
+ * "Unknown command: /crypto" on their first try.
37
+ * https://code.claude.com/docs/en/plugins
38
+ *
39
+ * `example` exists because the invitation printed after an install has to be
40
+ * runnable. It used to append a hardcoded "NVDA" to whatever came first in
41
+ * `commands`, which told anyone installing the crypto plugin to try a stock.
42
+ */
30
43
  export const PLUGINS = [
31
44
  {
32
45
  name: "stocks",
33
46
  description: "equity research slash commands backed by advis0r.com",
34
- commands: ["/stocks", "/signals", "/research", "/lookup", "/reports", "/discover"],
47
+ commands: ["/stocks:stocks", "/stocks:signals", "/stocks:research", "/stocks:lookup", "/stocks:reports", "/stocks:discover"],
48
+ example: "/stocks:stocks NVDA",
35
49
  },
36
50
  {
37
51
  name: "crypto",
38
52
  description: "crypto market data slash commands backed by advis0r.com",
39
- commands: ["/crypto", "/quote", "/book", "/bars", "/spark", "/pairs", "/coin"],
53
+ commands: ["/crypto:crypto", "/crypto:quote", "/crypto:book", "/crypto:bars", "/crypto:spark", "/crypto:pairs", "/crypto:coin"],
54
+ example: "/crypto:crypto BTC",
40
55
  },
41
56
  ];
42
57
 
58
+ /** How Claude Code namespaces a plugin's command. */
59
+ export function pluginCommandName(plugin, file) {
60
+ return `/${plugin}:${String(file).replace(/\.md$/, "")}`;
61
+ }
62
+
43
63
  export const DEFAULT_PLUGIN = PLUGINS[0].name;
44
64
 
45
65
  /**
@@ -82,15 +102,29 @@ export function pluginId(name) {
82
102
  /**
83
103
  * The commands one engine needs to install a plugin.
84
104
  *
85
- * Adding the marketplace is idempotent and separate from installing, so it runs
86
- * every time: a machine that added the marketplace before this plugin existed
87
- * would otherwise fail the install with "not found in any marketplace".
105
+ * Both marketplace steps run every time, and they do different jobs:
106
+ *
107
+ * add — makes the marketplace exist. A no-op when it is already on disk,
108
+ * which is exactly why it is not sufficient on its own.
109
+ * update — re-fetches it. Without this, a machine that added the marketplace
110
+ * before a plugin existed installs from its stale local copy and
111
+ * fails with "not found in any marketplace" — the failure `add` was
112
+ * supposed to prevent and cannot, because it declines to do anything
113
+ * for a marketplace it already has.
114
+ *
115
+ * That gap is not theoretical: it broke `plugin install crypto` in v0.27.0 and
116
+ * `plugin install stocks` in v0.29.0, on every machine that had installed a
117
+ * plugin from this marketplace beforehand — which is all of them.
118
+ *
119
+ * `marketplace update` takes the marketplace *name*, not the source, and
120
+ * accepts no --scope.
88
121
  */
89
122
  export function pluginInstallActions(key, { plugin, source, scope }) {
90
123
  switch (key) {
91
124
  case "claude":
92
125
  return [
93
126
  { cmd: "claude", args: ["plugin", "marketplace", "add", source, ...(scope ? ["--scope", scope] : [])] },
127
+ { cmd: "claude", args: ["plugin", "marketplace", "update", MARKETPLACE_NAME] },
94
128
  { cmd: "claude", args: ["plugin", "install", pluginId(plugin.name), ...(scope ? ["--scope", scope] : [])] },
95
129
  ];
96
130
  default: