moshcode 0.58.0 → 0.60.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/README.md CHANGED
@@ -30,6 +30,7 @@ or miss one that does. A test fails the build when it drifts.
30
30
  | `moshcode start` | engines | launch an engine with its native defaults |
31
31
  | `moshcode herd` | runtime | run agent sessions that outlive this terminal |
32
32
  | `moshcode ps` | runtime | list herd sessions and what each one is doing |
33
+ | `moshcode cost` <br>`usage` | runtime | what each session is spending, read from the engines' own logs |
33
34
  | `moshcode attach` | runtime | attach this terminal to a herd session |
34
35
  | `moshcode kill` | runtime | end a herd session |
35
36
  | `moshcode wait` | runtime | block until a session is blocked, done, or idle |
@@ -262,9 +263,9 @@ moshcode agents claude -d --name api # and an agent
262
263
 
263
264
  ```sh
264
265
  $ moshcode ps
265
- api claude blocked ~/src/coinpay 3m
266
- logs shell idle ~/src/coinpay 3m
267
- work shell idle ~/src/coinpay 3m
266
+ api claude blocked ~/src/coinpay 3m screen
267
+ logs shell idle ~/src/coinpay 3m screen
268
+ work shell idle ~/src/coinpay 3m screen
268
269
 
269
270
  ⚠ 1 waiting on you — moshcode attach api
270
271
  ```
@@ -316,9 +317,9 @@ Every session carries a state: `working`, `blocked`, `done`, `idle`, or
316
317
  `unknown`. `blocked` means a human decision is the only thing missing.
317
318
 
318
319
  ```
319
- api claude blocked ~/src/coinpay 12m
320
- web codex working ~/src/ugig.net 4m
321
- audit opencode done ~/src/moshpit-dns 1h
320
+ api claude blocked ~/src/coinpay 12m hook
321
+ web codex working ~/src/ugig.net 4m screen
322
+ audit opencode done ~/src/moshpit-dns 1h runtime
322
323
  ```
323
324
 
324
325
  State comes from one authority per session, never two. An engine that reports
@@ -338,6 +339,58 @@ moshcode herd start claude --name watch # then run `moshcode herd watch` in the
338
339
 
339
340
  With `--ask`, whatever you reply is typed into the session that was waiting.
340
341
 
342
+ ### What it is costing
343
+
344
+ Every engine already writes down what it used, so nothing has to be
345
+ instrumented or proxied — `moshcode cost` reads the CLIs' own session logs and
346
+ lines them up against the herd:
347
+
348
+ ```sh
349
+ moshcode cost # per session, in the window (default: 24h)
350
+ moshcode cost api # one session, with its engine runs
351
+ moshcode cost --all --since 7d # every engine session on the box, herd or not
352
+ moshcode cost --watch # the same report, re-read every 10s
353
+ moshcode cost --json # for a script
354
+ ```
355
+
356
+ ```
357
+ session engine model in out cache cost age
358
+ api claude claude-opus-5 1.2k 27k 10.5M $9.91~ 42m
359
+ audit codex gpt-5.6-sol 400 200 600 — 12m
360
+
361
+ total $9.91~ 1.6k in · 27k out · 10.5M cached
362
+ ~ estimated from published rates; unmarked figures are the engine's own.
363
+ ⚠ no rate for gpt-5.6-sol — tokens counted, cost omitted.
364
+ ```
365
+
366
+ | engine | where the number comes from |
367
+ |---|---|
368
+ | claude | per-message `usage` in `~/.claude/projects/**/*.jsonl` |
369
+ | codex | cumulative `token_count` events in `~/.codex/sessions/…` |
370
+ | opencode, privacycode | the per-message `cost` each one computed itself |
371
+ | aider | the running session total it prints into `.aider.chat.history.md` |
372
+
373
+ **A `~` is an estimate, and an unmarked figure is not.** opencode and aider
374
+ price their own messages, and that price is reported untouched. Claude Code and
375
+ Codex record tokens only — which is the honest state of things on a
376
+ subscription, where the marginal request costs nothing extra — so those are
377
+ multiplied by published rates to answer "what would this have cost on the API".
378
+
379
+ A model nobody has priced shows its tokens and no cost, rather than a
380
+ convincing-looking zero. Price it yourself in `~/.moshcode/pricing.json`:
381
+
382
+ ```json
383
+ { "gpt-5.6-sol": { "input": 1.25, "output": 10 } }
384
+ ```
385
+
386
+ Cache tokens get their own column because on a long agent session they are most
387
+ of the traffic and a tenth of the price; folding them into `in` makes a $3
388
+ session look like a $60 one. Attribution is engine + directory + "started before
389
+ this run did", so a session that shares a directory with another agent can pick
390
+ up its neighbour's work — `--json` carries the run list when you need to check.
391
+ gemini, kimi, qwen, deepseek and openagents keep no readable usage log, so they
392
+ report no cost rather than zero cost.
393
+
341
394
  ### Driving it from a script or another agent
342
395
 
343
396
  There is no second API — every verb takes `--json`, and that is what a machine
@@ -363,6 +416,120 @@ await herdWait("api"); await herdWait("web");
363
416
  say(herdRead("api", { lines: 20 }));
364
417
  ```
365
418
 
419
+ Fanning work out is easy; joining on it used to be a hand-rolled polling loop.
420
+ `--any` returns on the first session to get there, `--all` when the last one
421
+ has, and both take the same `--state` and `--timeout` as a single wait:
422
+
423
+ ```sh
424
+ moshcode wait --any api web docs # --json names the winner
425
+ moshcode wait --all api web --state done
426
+ ```
427
+
428
+ ```js
429
+ const first = await herdWait(["api", "web", "docs"], { any: true });
430
+ await herdWait(["api", "web"], { states: ["done"] });
431
+ ```
432
+
433
+ ### Let the engine say what it is doing
434
+
435
+ Reading a screen works and it rots — engines change their wording between
436
+ releases and nothing tells you. When an engine has lifecycle hooks, install
437
+ them once and its state comes from the engine itself:
438
+
439
+ ```sh
440
+ moshcode herd hooks install claude
441
+ ✓ claude — 3 hooks installed (stop, notification, prompt-submit)
442
+ ```
443
+
444
+ `moshcode ps` then reads `hook` in its last column instead of `screen`. The
445
+ file is **merged, never clobbered** — your own hooks stay, and `hooks remove`
446
+ takes out only what moshcode put in. A hook that fires outside a herd session
447
+ does nothing and exits 0, so installing one cannot break an engine you run by
448
+ hand, and the screen rules stay as the fallback for everything else.
449
+ `moshcode herd doctor` says what is installed, what has drifted, and — for the
450
+ first time — what is wrong with your `rules.json` instead of ignoring it.
451
+
452
+ ### What happened while you slept
453
+
454
+ Every prompt through the herd mints a **task**: an id, its state transitions
455
+ with timestamps, and the output it produced. `ps` still answers "now"; this
456
+ answers "what happened".
457
+
458
+ ```sh
459
+ $ moshcode herd tasks api
460
+ t-01 22:14 done 4m "port the auth routes"
461
+ t-02 22:19 blocked 6h11 "run the migration"
462
+
463
+ $ moshcode herd task t-02 # transitions, and what came back
464
+ $ moshcode herd log api # the raw state history
465
+ $ moshcode herd stats api
466
+ api working 3h02 · blocked 6h11 · idle 1h40
467
+ blocked 6h11 over 2 spell(s) — that one is you
468
+ ```
469
+
470
+ Blocked time is the herd's name for *human latency*: the agent was ready and
471
+ you were asleep. Ledgers live in `~/.moshcode/herd/tasks/<session>.jsonl` at
472
+ `0600`, capped at the last 500 tasks per session. From a script,
473
+ `herdTasks(name)` and `herdTask(id)` return them as values.
474
+
475
+ ### Agents that are not on this box
476
+
477
+ A deployed agent can be a herd member. Two kinds: `a2a` speaks
478
+ [A2A v0.3.0](https://a2a-protocol.org/v0.3.0/specification/) (card discovery,
479
+ `message/send`, `tasks/get`, `tasks/cancel`), and `run` is a bare endpoint that
480
+ takes `POST {"prompt": …}` — the shape a `gradient agent deploy` prints.
481
+
482
+ ```sh
483
+ moshcode herd remote add research https://agents.do-ai.run/…/production --kind run
484
+ export MOSHCODE_REMOTE_RESEARCH_TOKEN=… # never written to the manifest, never synced
485
+ ```
486
+
487
+ ```
488
+ $ moshcode ps
489
+ api claude blocked ~/src/coinpay 12m hook
490
+ research remote idle agents.do-ai.run — remote
491
+ ```
492
+
493
+ `prompt`, `read`, `wait` and `kill` work on it unchanged, which is the point: a
494
+ fan-out script across a local pty and a deployed agent contains no `if
495
+ (remote)`. A remote's state is the *remote's claim* — `ps` says `remote` in the
496
+ last column so it is never mistaken for something this box verified — and
497
+ `kill` on one deregisters it here rather than reaching across the network to
498
+ end somebody else's agent.
499
+
500
+ ### The herd, over A2A
501
+
502
+ `moshcode herd serve` exposes this machine's herd to any A2A client: the herd's
503
+ card at `/.well-known/agent-card.json`, each member at `/<name>/`,
504
+ `message/send` → prompt, `tasks/get` → the ledger, `tasks/cancel` → interrupt.
505
+ `blocked` is A2A's `input-required`; the states that do not map cleanly round
506
+ down and carry the honest one in task metadata.
507
+
508
+ ```sh
509
+ moshcode login # it verifies tokens against app.moshcode.sh
510
+ moshcode herd serve # 127.0.0.1:7683 by default
511
+ ```
512
+
513
+ It is a shell on a socket and is treated like one: **no unauthenticated mode,
514
+ loopback included**, a loud warning past `127.0.0.1`, and sessions started with
515
+ `--agent` withheld unless you pass `--expose-autonomous` — an engine with its
516
+ approvals bypassed plus a network prompt is the worst pairing on the menu.
517
+
518
+ ### Which engine is best at *this* repo
519
+
520
+ Not a leaderboard run against engines nobody deploys on repos nobody has — your
521
+ dataset, your engines, your machine:
522
+
523
+ ```sh
524
+ moshcode herd eval --dataset evals/moshcode.jsonl --engines claude,codex --threshold 0.8
525
+ ```
526
+
527
+ A row is `{"prompt": "…", "expect": "pattern"}` or
528
+ `{"prompt": "…", "rubric": "…"}` (jsonl, json or csv). Scoring is either the
529
+ dataset's own patterns or an engine acting as judge (`--judge claude`). Exit
530
+ codes are distinct on purpose — `0` pass, `4` below the threshold, `5` the
531
+ harness could not run — because CI has to tell a worse agent from a broken box.
532
+
366
533
  ### After a reboot
367
534
 
368
535
  ```sh
@@ -1013,6 +1180,9 @@ agents("claude"); // drop into an autonomous sessio
1013
1180
 
1014
1181
  ```sh
1015
1182
  moshcode run examples/alive.mosh # run a script
1183
+ moshcode run examples/account.mosh --dry-run # log in, then do work that needs an account
1184
+ moshcode run examples/aliases.mosh --dry-run # define and run the pit's shortcuts
1185
+ moshcode run examples/research-desk.mosh # stocksRead/cryptoRead/newsRead → one digest
1016
1186
  moshcode run deploy.mosh --dry-run # narrate without executing
1017
1187
  moshcode run alive.mosh --max 5 # bound the while loop (default 3)
1018
1188
  moshcode run deploy.mosh staging --fast # extra args reach the script as argv
@@ -1058,6 +1228,35 @@ chmod +x deploy.mosh
1058
1228
  | `stop()` | end the loop (`alive = false`) |
1059
1229
  | `repeat()` | back to the top of the loop |
1060
1230
 
1231
+ **Account verbs** (see [Authentication](#authentication)):
1232
+
1233
+ | verb | description |
1234
+ |---|---|
1235
+ | `await requireLogin()` | gate — verify, log in if needed, **throw** if it can't; returns the user |
1236
+ | `await login({ device, browser, force })` | authenticate; no-op when already signed in; returns `{ ok, email, already }` |
1237
+ | `await whoami()` | the account as a value: `{ status, verified, api, user: { id, email, name, credits } }` |
1238
+ | `logout()` | forget this machine's credentials |
1239
+
1240
+ **Alias verbs** — the pit's own shortcuts (`~/.moshcode/aliases.json`), readable and writable from a script:
1241
+
1242
+ | verb | description |
1243
+ |---|---|
1244
+ | `alias()` | every alias, as a `name → line` map |
1245
+ | `alias(name)` | one alias's line, or `null` |
1246
+ | `alias(name, line)` | define one; refuses names moshcode already owns |
1247
+ | `unalias(name)` | forget one |
1248
+ | `runAlias(name, …args)` | run one, args appended; returns `{ ok, code }` |
1249
+
1250
+ **Read verbs** — the tools as *values* rather than tables:
1251
+
1252
+ | verb | description |
1253
+ |---|---|
1254
+ | `await stocksRead(…)` | same args as `stocks(…)`, returns the parsed JSON |
1255
+ | `await cryptoRead(…)` | same args as `crypto(…)`, returns the parsed JSON |
1256
+ | `await newsRead({ list, limit })` | headlines as `[{ title, link, source, date }, …]` |
1257
+ | `herdRead(name, { lines })` | a herd session's screen, as a string |
1258
+ | `herdList()` | the roster: `[{ name, engine, state, cwd, alive }, …]` |
1259
+
1061
1260
  **CLI verbs** (each shells out to `moshcode <name> ...args`):
1062
1261
 
1063
1262
  | verb | description |
@@ -1082,7 +1281,26 @@ chmod +x deploy.mosh
1082
1281
  | `turso(args…)` | drive the Turso CLI |
1083
1282
  | `tailscale(args…)` | drive the Tailscale CLI |
1084
1283
  | `coral(args…)` | drive the Coral CLI (SQL over APIs, databases, internal systems) |
1284
+ | `alpaca(args…)` | drive the native Alpaca trading CLI |
1085
1285
  | `mcpjam(args…)` | drive the MCPJam CLI (test, debug, and validate MCP servers) |
1286
+ | `trade(args…)` | look up tickers, inspect markets, preview/place Alpaca orders |
1287
+ | `stocks(args…)` | research tickers via advis0r (`stocksRead` returns the data) |
1288
+ | `crypto(args…)` | research crypto pairs via advis0r (`cryptoRead` returns the data) |
1289
+ | `advisor(args…)` | query advis0r directly |
1290
+ | `news(args…)` | read, search, and subscribe to news feeds (`newsRead` returns the items) |
1291
+ | `rss(args…)` | manage RSS subscriptions and reading lists |
1292
+ | `plugin(args…)` | install/manage moshcode plugins from the marketplace |
1293
+ | `engines()` | list coding engines and whether they're installed |
1294
+ | `tools()` | list the adjacent workflow CLIs and whether they're installed |
1295
+ | `dns(args…)` | drive the Moshpit DNS bridge (enable, status, resolve) |
1296
+ | `doh(args…)` | run/inspect the DNS-over-HTTPS endpoint |
1297
+ | `site(args…)` | scaffold and publish a site |
1298
+ | `serve(args…)` | serve a directory over HTTP |
1299
+ | `template(args…)` | scaffold from a moshcode template |
1300
+ | `save()` / `load()` | push/pull settings to your moshcode account (needs login) |
1301
+ | `herd(args…)` | drive the herd (`herdStart`/`herdWait`/`herdRead` return values) |
1302
+ | `ps()` | print the herd roster |
1303
+ | `ai(prompt, { engine })` | run an engine headlessly and **return** its output as a string |
1086
1304
  | `pwd()` | print the current repo/location |
1087
1305
  | `run(file)` | run another .mosh file (include/compose) |
1088
1306
 
@@ -1094,6 +1312,81 @@ chmod +x deploy.mosh
1094
1312
  | `argv` | positional args passed after the script file |
1095
1313
  | `env` | `process.env` — parameterize scripts from the environment |
1096
1314
 
1315
+ ### Authentication
1316
+
1317
+ Some verbs need an account: `notify()`/`ask()` reach you through
1318
+ `app.moshcode.sh`, and `save()`/`load()` sync settings to it. A script says so
1319
+ once, at the top, instead of failing one call at a time later on:
1320
+
1321
+ ```js
1322
+ const me = await requireLogin(); // verifies; logs in if it has to
1323
+ say(`signed in as ${me.email} (${me.credits} credits)`);
1324
+ ```
1325
+
1326
+ - **`requireLogin({ device, browser })`** — the gate. Verifies this machine
1327
+ against the app; if there's no usable session it runs the login flow, then
1328
+ re-checks. Returns the verified `{ id, email, name, credits }`. **Throws** if
1329
+ it still can't authenticate — the one verb here that does, because "require"
1330
+ means the script must not continue without an account.
1331
+ - **`login({ device, browser, force })`** — idempotent. Returns early with
1332
+ `{ already: true }` when you're already signed in, so a script you re-run all
1333
+ day never throws a browser tab at you. Returns `{ ok: false, error }` on
1334
+ failure rather than throwing, so a script can fall back to read-only work.
1335
+ - **`whoami()`** — the account as a value, verified against the app:
1336
+ `{ status, verified, api, user }` where `status` is `authenticated`,
1337
+ `not_logged_in`, `expired`, `unverified`, or `unreachable`. Never throws — an
1338
+ unreachable app is a status, not an exception.
1339
+ - **`logout()`** — forget the local credentials.
1340
+
1341
+ The flow is picked for where the script is running: the loopback/browser flow
1342
+ locally, and the device-code flow over SSH or on a headless box (where a
1343
+ `127.0.0.1` callback would land on the *browser's* machine and never arrive).
1344
+ `{ device: true }` / `{ browser: true }` pin it either way. Credentials live in
1345
+ `~/.moshcode/credentials.json` (mode `0600`) — the same ones `moshcode login`
1346
+ writes, so logging in once covers the CLI, the pit, and every script.
1347
+
1348
+ ```js
1349
+ // gate on the balance, not just the session
1350
+ const me = await whoami();
1351
+ if (!me.verified) { say("read-only run — not signed in"); }
1352
+ else if (me.user.credits < 10) notify(`only ${me.user.credits} credits left`);
1353
+ ```
1354
+
1355
+ ### Aliases
1356
+
1357
+ The pit keeps named shortcuts for the lines you retype (`/alias set gs "git
1358
+ status"`). Scripts read and write the same store, so your vocabulary and
1359
+ moshcode's are one thing rather than two:
1360
+
1361
+ ```js
1362
+ alias("gs", "git status --short"); // define (refuses names moshcode owns)
1363
+ alias("cc", "/agents claude"); // a leading `/` is a moshcode command
1364
+ runAlias("gs", "--branch"); // → git status --short --branch
1365
+ ```
1366
+
1367
+ Expansion is the pit's rule: a leading `/` routes to the moshcode command of
1368
+ that name, anything else is a shell line, and arguments are **appended** rather
1369
+ than substituted — exactly how a shell alias behaves. `runAlias()` returns
1370
+ `{ ok, code }` like `shell()`, and `{ ok: false, code: 127 }` when there's no
1371
+ such alias.
1372
+
1373
+ ### Reading the tools, not just running them
1374
+
1375
+ `stocks report NVDA` prints a table; a script usually wants the number. The
1376
+ `*Read()` verbs call the same layer the printed commands render from and hand
1377
+ back parsed data:
1378
+
1379
+ ```js
1380
+ const report = await stocksRead("report", "NVDA"); // → JSON, or null on error
1381
+ const btc = await cryptoRead("quote", "BTC/USD");
1382
+ const items = await newsRead({ limit: 5 }); // [{ title, link, source, date }]
1383
+ ```
1384
+
1385
+ A failed lookup returns `null` (or `[]`) rather than throwing, so one bad symbol
1386
+ doesn't take a briefing script down. Same reasoning as the herd's
1387
+ `herdRead()`/`herdList()`: shelling out gives you `{ ok, code }`, and the whole
1388
+ point of these is the value.
1389
+
1097
1390
  ### Human-in-the-loop
1098
1391
 
1099
1392
  - `notify(msg)` — fire-and-forget. Pings the operator across configured channels
package/bin/moshcode.mjs CHANGED
@@ -336,7 +336,7 @@ async function main() {
336
336
  const [key, engine] = resolved;
337
337
  return launchEngine(key, engine, rest.slice(1));
338
338
  }
339
- // The herd (PRD 0009). `herd` is the namespace; the five verbs people reach
339
+ // The herd (PRD 0009). `herd` is the namespace; the verbs people reach
340
340
  // for most often are also top-level, because `moshcode ps` is what someone
341
341
  // types when they want to know what is running and nobody should have to
342
342
  // learn a namespace to ask that.
@@ -344,8 +344,8 @@ async function main() {
344
344
  process.exitCode = (await herdCommand(rest)) || 0;
345
345
  return;
346
346
  }
347
- if (["ps", "attach", "kill", "wait", "restore"].includes(cmd)) {
348
- process.exitCode = (await herdCommand([cmd === "ps" ? "ps" : cmd, ...rest])) || 0;
347
+ if (["ps", "attach", "kill", "wait", "restore", "cost", "usage"].includes(cmd)) {
348
+ process.exitCode = (await herdCommand([cmd === "usage" ? "cost" : cmd, ...rest])) || 0;
349
349
  return;
350
350
  }
351
351
  if (cmd === "tools") {
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env moshscript
2
+ // account.mosh — authenticate the operator, then do work that needs an account.
3
+ //
4
+ // `requireLogin()` is the gate: it verifies this machine against
5
+ // app.moshcode.sh and, if there is no usable session, runs the login flow
6
+ // (browser locally, device code over SSH) before the script continues. Anything
7
+ // downstream that needs an account — notify(), ask(), save()/load() — would
8
+ // otherwise fail one call at a time, much later and much less legibly.
9
+ //
10
+ // Try it safely first:
11
+ //
12
+ // moshcode run examples/account.mosh --dry-run
13
+ // chmod +x examples/account.mosh && ./examples/account.mosh 5
14
+ //
15
+ // argv[0] is the minimum credit balance to demand (default: 1).
16
+
17
+ const needCredits = Number(argv[0] || 1);
18
+
19
+ // Blocks until there is a verified account. THROWS if it can't get one — that
20
+ // is what "require" means, and it stops the script here rather than halfway
21
+ // through the work.
22
+ const me = await requireLogin();
23
+ say(`🤘 signed in as ${me.email || me.name || "moshcoder"}`);
24
+
25
+ // whoami() hands the account back as a value, so a script can branch on it
26
+ // instead of re-parsing `moshcode whoami` output.
27
+ if (me.credits != null && me.credits < needCredits) {
28
+ say(`⛔ ${me.credits} credits left, need ${needCredits} — topping up is a human job`);
29
+ notify(`moshcode is out of credits (${me.credits}) — top up to keep the pit going`);
30
+ stop();
31
+ } else {
32
+ // Settings sync needs the account we just proved we have.
33
+ load(); // → moshcode load (pull settings down)
34
+ say("⚙️ settings pulled from your account");
35
+
36
+ const test = shell("pnpm -r test");
37
+ if (!test.ok) {
38
+ // The human-in-the-loop gate: this blocks until someone answers at
39
+ // app.moshcode.sh/approve/:id — which only works because we're logged in.
40
+ const next = await ask(`tests failed (exit ${test.code}) — ship anyway, or fix?`);
41
+ say(`👤 operator says: ${next ?? "(no reply — stopping)"}`);
42
+ if (next == null) stop();
43
+ } else {
44
+ say("✅ green — saving settings back up");
45
+ save(); // → moshcode save (push settings up)
46
+ notify("moshcode: tests green, settings synced 🤘");
47
+ }
48
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env moshscript
2
+ // aliases.mosh — the pit's shortcuts, from a script.
3
+ //
4
+ // The pit keeps named shortcuts for the lines you retype (`/alias set gs "git
5
+ // status"`). They are your vocabulary, not moshcode's, so a script can read the
6
+ // same store, define new ones, and run them — instead of re-spelling every one
7
+ // of those lines in every script.
8
+ //
9
+ // The expansion rule is the pit's: a leading `/` is a moshcode command,
10
+ // anything else is a shell line, and arguments are appended rather than
11
+ // substituted — so runAlias("gs", "--short") is `git status --short`.
12
+ //
13
+ // Try it safely first:
14
+ //
15
+ // moshcode run examples/aliases.mosh --dry-run
16
+ // chmod +x examples/aliases.mosh && ./examples/aliases.mosh
17
+ //
18
+ // argv[0] is the engine an alias should open (default: claude).
19
+
20
+ const engine = argv[0] || "claude";
21
+
22
+ // Set up a small kit. alias(name, line) returns { ok, error, previous }; a name
23
+ // moshcode already owns (`agents`, `install`, …) is refused rather than
24
+ // shadowed, because a shortcut that silently does nothing is worse than one
25
+ // that was never accepted.
26
+ const kit = {
27
+ gs: "git status --short --branch",
28
+ gl: "git log --oneline -12",
29
+ cc: `/agents ${engine}`, // a leading `/` → the moshcode command
30
+ };
31
+
32
+ for (const [name, line] of Object.entries(kit)) {
33
+ const r = alias(name, line);
34
+ if (!r.ok) say(`⚠️ couldn't define /${name} — ${r.error}`);
35
+ }
36
+
37
+ // alias() with no arguments is the whole map — the same one the pit shows.
38
+ const all = alias();
39
+ say(`🔖 ${Object.keys(all).length} alias(es) on this machine`);
40
+
41
+ // Run one. Returns { ok, code } like shell() and the CLI verbs, so a script can
42
+ // branch on the outcome without try/catch.
43
+ //
44
+ // Under --dry-run nothing above was actually written, so these report the
45
+ // aliases as missing (code 127) rather than pretending to run them — a dry run
46
+ // narrates what it would do, and it genuinely doesn't know what an undefined
47
+ // alias expands to. Run it for real to see them execute.
48
+ const status = runAlias("gs");
49
+ if (!status.ok && status.code !== 127) say(`git status exited ${status.code}`);
50
+
51
+ runAlias("gl", "--author", env.USER || ""); // extra args are appended
52
+
53
+ // Clean up the throwaway ones, leave the useful one behind.
54
+ unalias("gl");
55
+
56
+ say(`🤘 /cc is yours now — type it in the pit to open ${engine}`);
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env moshscript
2
+ // research-desk.mosh — a morning briefing, assembled from the read verbs.
3
+ //
4
+ // `stocks report NVDA` prints a table; `stocksRead("report", "NVDA")` hands
5
+ // back the JSON. That is the difference this script is built on: the *Read()
6
+ // verbs return values, so a script can rank, threshold, and summarize instead
7
+ // of scraping stdout. Same advis0r/feed layer the printed commands render from.
8
+ //
9
+ // Try it safely first:
10
+ //
11
+ // moshcode run examples/research-desk.mosh --dry-run
12
+ // chmod +x examples/research-desk.mosh && ./examples/research-desk.mosh NVDA AMD
13
+ //
14
+ // argv is the ticker list (default: NVDA TSLA).
15
+
16
+ const tickers = argv.length ? argv : ["NVDA", "TSLA"];
17
+ const briefing = [];
18
+
19
+ // Rank the tickers by whatever score advis0r returns, keeping only the ones
20
+ // worth a human's attention. A failed lookup comes back null rather than
21
+ // throwing, so one bad symbol doesn't take the briefing down.
22
+ for (const symbol of tickers) {
23
+ const data = await stocksRead("report", symbol);
24
+ if (!data) { say(`⚠️ no data for ${symbol}`); continue; }
25
+ const score = data.score ?? data.report?.score ?? null;
26
+ briefing.push({ symbol, score });
27
+ say(`📈 ${symbol}: ${score ?? "no score"}`);
28
+ }
29
+
30
+ // Crypto reads the same way.
31
+ const btc = await cryptoRead("quote", "BTC/USD");
32
+ if (btc) say(`🪙 BTC/USD: ${btc.price ?? btc.quote?.price ?? "?"}`);
33
+
34
+ // Headlines from your own subscriptions (or pass { list: "smallweb" } for one
35
+ // of the built-in feed lists).
36
+ const headlines = await newsRead({ limit: 5 });
37
+ for (const item of headlines) {
38
+ say(`📰 ${item.source ? `[${item.source}] ` : ""}${item.title}`);
39
+ }
40
+
41
+ // One ping with the whole picture, rather than one per lookup.
42
+ const movers = briefing
43
+ .filter((b) => b.score != null)
44
+ .sort((a, b) => b.score - a.score)
45
+ .slice(0, 3)
46
+ .map((b) => `${b.symbol} ${b.score}`)
47
+ .join(", ");
48
+
49
+ notify(`morning desk — ${movers || "no scores today"} · ${headlines.length} headline(s)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moshcode",
3
- "version": "0.58.0",
3
+ "version": "0.60.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": {