Skip to content

/timer and /billing hand over to their own CLIs - #439

Merged
ralyodio merged 1 commit into
mainfrom
worktree-timer-billing-tools
Aug 29, 2026
Merged

/timer and /billing hand over to their own CLIs#439
ralyodio merged 1 commit into
mainfrom
worktree-timer-billing-tools

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Tracking time and sending an invoice are not moshcode ideas. They are useful under any agentic CLI, and on Windows, where moshcode does not go. So both now ship standalone, published under @profullstack:

moshcode install timer billing

What changes here

With the CLIs installed, /timer and /billing hand the command straight to them, the way /gh conducts gh. Without them, the in-process implementation runs exactly as it always has, so upgrading changes nothing until you choose to install. MOSHCODE_BUILTIN_BILLING=1 pins the built-in either way.

Nothing is deleted. The two are deliberately not kept in sync — a second copy of a billing model is a copy that drifts, and the published package is the one that gets the fixes — but removing src/timer.mjs and src/billing.mjs is a separate decision, and PRD 0012 shipped hours ago. Say the word and that is a one-line follow-up.

/client, /rate, /payments and /team stay here: the rails and the permission model are moshcode integration, which is where the split belongs.

What went across

The standalone billing carries the same rate model this repo parses and bills agent-hours, so the printed quantity times the printed rate equals the printed amount and a client can check the line by hand:

auth refactor    14 agent-hours @ $100.00    $1,400.00

3h with 2 agents plus 2h with 6 (capped at 4) is 14. Each entry is charged with its own agent count before the units are summed, never after — averaging would bill a two-agent afternoon at the four-agent rate.

billing import brings across a ledger that started in ~/.moshcode/business.json and timers.json — clients, the rates map folded onto them, invoices, and the entries with their agent counts. It shows the plan and writes nothing without --apply, and never modifies the originals.

Plugins

Two join the marketplace, fronting the CLIs rather than this repo's code. Their command files carry the rules that matter: never stop somebody else's clock, never issue an invoice unprompted, always --dry-run first.

SHARED_COMMANDS is now keyed by family. The flat list only ever made sense for the stocks/crypto pair — quote and lookup are market words, and requiring them of a timer would force two commands nobody would run. A new test fails if a family ever drops to one plugin, which is how that rule would quietly stop testing anything.

One bug, found by running it

openPassthrough resolves { ok, code, signal }, not a number, so assigning it to process.exitCode threw ERR_INVALID_ARG_TYPE after the child had already printed — a successful run ending in a stack trace. exitCodeOf() normalises it, and a signal-killed child is 1 rather than 0.

Verified

All four paths run by hand against the real published packages:

CLI installed delegates, correct agent-hour math
MOSHCODE_BUILTIN_BILLING=1 built-in runs
CLI absent built-in runs, install tip on stderr, exit 0
exit codes pass through, signal-killed is non-zero

Suite is 2273 passing, 0 failing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KVUZsx4WBZK5rnJJFGgwF5

Tracking time and sending an invoice are not moshcode ideas. They are useful
under any agentic CLI, and on Windows, where moshcode does not go. So both now
ship standalone, published under @profullstack:

  npm install -g @profullstack/timer @profullstack/billing
  moshcode install timer billing

With them installed, /timer and /billing hand the command straight to the CLI,
the way /gh conducts gh. Without them the in-process implementation runs exactly
as before, so upgrading changes nothing until you choose to install, and
MOSHCODE_BUILTIN_BILLING=1 pins the built-in either way.

Nothing is deleted here. The two are deliberately NOT kept in sync — a second
copy of a billing model is a copy that drifts, and the published package is the
one that gets the fixes — but removing src/timer.mjs and src/billing.mjs is a
separate decision, and PRD 0012 shipped four hours ago.

The standalone billing carries the same rate model this repo parses
($100/hour/agent/upto:4) and bills AGENT-HOURS, so quantity x rate equals the
line amount and a client can check it by hand. `billing import` brings across a
ledger that started in ~/.moshcode/business.json, shows the plan first, and
never touches the originals. /client, /rate, /payments and /team stay here: the
rails and the permission model are moshcode integration, which is where they
belong.

Two plugins join the marketplace, fronting the CLIs rather than this repo's
code. Their command files carry the rules that matter — never stop somebody
else's clock, never issue an invoice unprompted, always --dry-run first.

SHARED_COMMANDS is now keyed by family. The flat list only ever made sense for
the stocks/crypto pair: `quote` and `lookup` are market words, and requiring
them of a timer would force two commands nobody would run. A new test fails if a
family ever drops to one plugin, which is how that rule quietly stops testing
anything.

Found by running it rather than by reading it: openPassthrough resolves
{ ok, code, signal }, not a number, so assigning it to process.exitCode threw
ERR_INVALID_ARG_TYPE after the child had already printed — a successful run
ending in a stack trace. exitCodeOf() normalises it, and a signal-killed child
is 1 rather than 0.

Suite is 2273 passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KVUZsx4WBZK5rnJJFGgwF5
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

1 finding(s) in the 21 file(s) this pull request changes.

MEDIUM: 1

Severity Rule Location
MEDIUM insecure-temp-file test/plugins.test.mjs:167
64 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 53 | **LOW**: 6

Not introduced by this pull request. The full set is in the Security tab.

Severity Rule Location
HIGH js-ssrf-outbound-request apps/pwa/public/sw.js:45
HIGH tls-verification-disabled apps/pwa/src/lib/moshpit-gateway.mjs:299
HIGH sh-remote-script-execution install.sh:79
HIGH sh-remote-script-execution install.sh:83
HIGH tls-verification-disabled src/dns.mjs:766
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:92
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:106
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:132
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:319
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:323
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:368
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:597
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:778
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:780
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:839
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:885
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:955
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1058
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1081
MEDIUM sql-template-interpolation apps/pwa/src/moshpit.mjs:1103

…and 44 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit fa789dd into main Aug 29, 2026
6 checks passed
ralyodio added a commit that referenced this pull request Aug 29, 2026
#439 handed /timer and /billing to @profullstack/timer and
@profullstack/billing the moment those packages appeared on PATH. That was
wrong, and the pit's own test caught it: only half this layer has an outside
home.

/client, /rate, /payments and /team stay in moshcode — the rails and the
permission model are ours, and /client's freeform dotted fields have no shape
in the package's typed client model, so moving it would lose data rather than
relocate it. Handing over the other half automatically splits one person's
records across two stores:

  /client create "Acme Inc"      → ~/.moshcode/business.json
  /rate set acme-inc $100/hour   → ~/.moshcode/business.json
  /billing acme-inc              → reads the package ledger: "no client acme-inc"

That is not a missing feature, it is somebody's invoice failing to exist. And
it only happens on a machine that installed the CLIs, so CI — which has not —
stayed green while every developer box that took the install went red. That
asymmetry is why it shipped.

So the hand-over is now opt-in: MOSHCODE_EXTERNAL_BILLING=1, after
`billing import` has moved the records across. The install tip only appears to
somebody who has the package installed and has not switched it on, and it names
the import, because switching over without it is the same split by hand.

Two guards, both verified to fail when the old behaviour is put back: a unit
test that installed is not the same as chosen, and a pit test that puts a fake
`timer` on PATH and checks the pit still wrote its own ledger. The second is
the one that would have caught this originally — the first pit test did, on a
machine with the tools installed, which no CI run has.

When the whole layer has an outside home, this becomes the default.

Version bumped to 0.72.0 for the release that carries #439 and this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant