/timer and /billing hand over to their own CLIs - #439
Merged
Conversation
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
ThreatCrush Security Scan1 finding(s) in the 21 file(s) this pull request changes. MEDIUM: 1
64 pre-existing finding(s) elsewhere in the repository — **HIGH/CRITICAL**: 5 | **MEDIUM**: 53 | **LOW**: 6Not introduced by this pull request. The full set is in the Security tab.
…and 44 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This was referenced Aug 29, 2026
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:@profullstack/timer— 0.2.0, 51 tests@profullstack/billing— 0.2.0, 112 testsWhat changes here
With the CLIs installed,
/timerand/billinghand the command straight to them, the way/ghconductsgh. Without them, the in-process implementation runs exactly as it always has, so upgrading changes nothing until you choose to install.MOSHCODE_BUILTIN_BILLING=1pins 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.mjsandsrc/billing.mjsis a separate decision, and PRD 0012 shipped hours ago. Say the word and that is a one-line follow-up./client,/rate,/paymentsand/teamstay 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:
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 importbrings across a ledger that started in~/.moshcode/business.jsonandtimers.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-runfirst.SHARED_COMMANDSis now keyed by family. The flat list only ever made sense for the stocks/crypto pair —quoteandlookupare 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
openPassthroughresolves{ ok, code, signal }, not a number, so assigning it toprocess.exitCodethrewERR_INVALID_ARG_TYPEafter 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:
MOSHCODE_BUILTIN_BILLING=1Suite is 2273 passing, 0 failing.
🤖 Generated with Claude Code
https://claude.ai/code/session_01KVUZsx4WBZK5rnJJFGgwF5