@invariant.guru/cli 0.7.1 → 0.8.1

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
@@ -1,6 +1,47 @@
1
1
  # Invariant CLI
2
2
 
3
- A package manager for AI coding-agent context. Install, compose, and sync reusable agents, skills, commands, rules, contexts, and instructions. Every item is stored **once** in `.agents/` — the [Agent Skills](https://agentskills.io) standard most harnesses read natively — and each CLI target gets a view of it rather than its own copy.
3
+ A harness-agnostic workspace for spec-driven AI development. The **session file
4
+ is the unit of work**: what was asked, the plan, and where it has got to, in one
5
+ markdown file your agent reads and reports back into.
6
+
7
+ It is also a package manager for agent context — agents, skills, commands, rules
8
+ and instructions, stored **once** in `.agents/` (the
9
+ [Agent Skills](https://agentskills.io) standard most harnesses read natively),
10
+ with each CLI target getting a view of it rather than its own copy.
11
+
12
+ Everything is local. No account, no server, no network.
13
+
14
+ ## We never spend tokens
15
+
16
+ **Invariant does not call an LLM. Ever.**
17
+
18
+ You already pay for a harness subscription — Claude Code, Codex, Cursor,
19
+ whatever it is — and you are not going to pay a second time, in tokens, for a
20
+ management layer. So this tool ships no API key, proxies no model traffic, and
21
+ runs no inference. Your agent calls *it*:
22
+
23
+ ```bash
24
+ invariant session next <id> # what to do now, and the next command
25
+ invariant session status <id> planning # …and the agent reports back
26
+ ```
27
+
28
+ Those are ordinary shell commands. Any harness that can run one participates,
29
+ and nothing here is coupled to any of them.
30
+
31
+ ## Where your work lives
32
+
33
+ On your machine, in your repository:
34
+
35
+ | | |
36
+ |---|---|
37
+ | `.invariant/sessions/` | your sessions, as markdown |
38
+ | `.invariant/events/` | a local journal of what changed, git-ignored |
39
+ | `~/.invariant/` | the daemon's own state: which projects, which port, its log |
40
+
41
+ Sessions contain code excerpts and architecture plans, which is exactly why none
42
+ of it goes anywhere. When there is a hosted layer for teams it will be opt-in, it
43
+ will relay these same events and nothing else, and it will be self-hostable from
44
+ the day it exists.
4
45
 
5
46
  ## Requirements
6
47
 
@@ -28,6 +69,46 @@ invariant update --check
28
69
  invariant update
29
70
  ```
30
71
 
72
+ ## The loop, end to end
73
+
74
+ ```bash
75
+ invariant init claude # once, at the repository root
76
+ invariant ui # the dashboard; leave it running
77
+ invariant plan "add the cargo booking command"
78
+ ```
79
+
80
+ `plan` prints a line to paste into your agent:
81
+
82
+ ```
83
+ execute instructions from /repo/.invariant/sessions/session-01a0….md
84
+ ```
85
+
86
+ From there the agent drives it, and the board follows along:
87
+
88
+ ```bash
89
+ invariant session status <id> planning # it announces itself
90
+ invariant session status <id> planned --title "…" # the plan is ready — your turn
91
+ invariant session status <id> in-progress # you said go
92
+ invariant session step complete <id> 1 # …and it works through the plan
93
+ invariant session status <id> executed --summary "…"
94
+ ```
95
+
96
+ You never type those — the agent does, because `invariant sync` taught it the
97
+ protocol. When it is unsure, it asks:
98
+
99
+ ```bash
100
+ invariant session next <id>
101
+ ```
102
+
103
+ Then have the work read by someone who did not write it:
104
+
105
+ ```bash
106
+ invariant review open --session <id> # paste into a NEW conversation
107
+ ```
108
+
109
+ Triage the comments in the UI, and either implement the accepted ones or send
110
+ them to the merge request. That is the whole loop.
111
+
31
112
  ## Getting Started
32
113
 
33
114
  The core loop is **init → install → add → sync**, with `plan` on top when you start a task.
@@ -242,6 +323,21 @@ invariant ui status # the daemon, and the projects it serves
242
323
  invariant ui stop # stop it
243
324
  ```
244
325
 
326
+ `invariant ui` is the **daemon** with a browser opened on top of it. The daemon
327
+ is the process that serves every project, watches for changes, and journals
328
+ them; scripts and hooks talk to it by its own name:
329
+
330
+ ```bash
331
+ invariant daemon start # no browser
332
+ invariant daemon status --json
333
+ invariant daemon logs -f # ~/.invariant/logs/daemon.log
334
+ invariant daemon compact # snapshot and purge this project's old events
335
+ ```
336
+
337
+ Leave it running. The verbs work without it — they write the file directly and
338
+ leave the event for the daemon to pick up — but with it running, every change
339
+ reaches every open page as it happens.
340
+
245
341
  **One server, every project.** Running `invariant ui` in a second repository does
246
342
  not start a second server — it registers that folder and opens it in the one
247
343
  already running. `⌘P` switches between projects; `⇧⌘H` goes to the list of them
@@ -250,9 +346,11 @@ reload, bookmarks and a second tab on a second project all work the way you
250
346
  would expect, and nothing from one project survives into the next.
251
347
 
252
348
  ```bash
253
- invariant projects # every registered project
349
+ invariant projects # every registered project, by group
254
350
  invariant projects add [path] # register one (defaults to the current folder)
255
351
  invariant projects remove # forget one — its files are left alone
352
+ invariant projects rename … # name it on this machine (three folders called `frontend`)
353
+ invariant projects group … # file it under a group
256
354
  ```
257
355
 
258
356
  The list lives in `~/.invariant/projects.json`.
@@ -622,20 +720,124 @@ token kept in `~/.invariant/ui.json` (mode 0600). Every request names the projec
622
720
  it acts on (`/api/p/<project>/…`) and is scoped to that project's directory for
623
721
  its whole lifetime, so one project can never read or write another's files.
624
722
 
723
+ ### `invariant session <subcommand>`
724
+
725
+ The verb surface an agent drives sessions with. Every verb validates, records
726
+ what it did, and returns an exit code — see
727
+ [docs/spec-session-protocol.md](docs/spec-session-protocol.md).
728
+
729
+ ```bash
730
+ invariant session # list
731
+ invariant session next [<id>] # what to do now, and the command for it
732
+ invariant session show <id> [--body]
733
+ invariant session path <id> # the absolute path, alone
734
+ invariant session status <id> <status> [--summary "…"] [--title "…"]
735
+ invariant session title <id> "A better title"
736
+ invariant session step list <id>
737
+ invariant session step complete <id> 2 # …or part of the step's text, or --all
738
+ invariant session write <id> --section Plan --file plan.md
739
+ invariant session link <id> --parent <session> # give an orphaned review its subject
740
+ invariant session archive <id>
741
+ invariant session migrate --all # repair legacy headers
742
+ invariant session events <id> # what the log says happened
743
+ invariant session reconcile [--dry-run] # infer what nobody reported
744
+ invariant session protocol # the block that teaches an agent this
745
+ ```
746
+
747
+ | Exit | Meaning |
748
+ |---|---|
749
+ | `0` | it worked |
750
+ | `1` | it failed |
751
+ | `2` | usage: a missing argument, an unknown status, an ambiguous step |
752
+ | `3` | not found |
753
+ | `4` | a conflict: a stale etag, a review that is not triaged |
754
+ | `5` | the daemon was required (`INVARIANT_DAEMON=require`) and is not running |
755
+
756
+ `--json` on any of them prints the same shape the HTTP API returns.
757
+
758
+ ### `invariant validate [sessions...]`
759
+
760
+ Check this project's sessions against the published format. The point of the
761
+ command is the exit code: `0` clean, `1` findings.
762
+
763
+ ```bash
764
+ invariant validate # every session
765
+ invariant validate --reviews # review documents too
766
+ invariant validate --events # the event log too
767
+ invariant validate --fix # rewrite the headers a rewrite can fix
768
+ invariant validate --json --quiet # for CI
769
+ ```
770
+
771
+ Beyond each file's own rules it checks what no single file can: a `parent` that
772
+ resolves, a review document that exists when the session says it should, two
773
+ sessions claiming the same review. See
774
+ [docs/spec-session-format.md](docs/spec-session-format.md).
775
+
776
+ ### `invariant daemon <subcommand>`
777
+
778
+ The one local process. It serves every registered project, watches for changes,
779
+ journals them, and hosts the UI.
780
+
781
+ ```bash
782
+ invariant daemon start [port] # start or attach; no browser
783
+ invariant daemon stop
784
+ invariant daemon status --json
785
+ invariant daemon logs -f
786
+ invariant daemon compact # snapshot and purge this project's old events
787
+ ```
788
+
789
+ | Subcommand | Description |
790
+ |------------|-------------|
791
+ | `start [port]` | Start (or attach to) the daemon. Default port 4200, probing forward |
792
+ | `stop` | Graceful shutdown, then SIGTERM, then SIGKILL |
793
+ | `status` | The daemon and the projects it serves (`--json`). Exit 0 running, 1 not |
794
+ | `logs` | Print `~/.invariant/logs/daemon.log` (`-f` to follow) |
795
+ | `compact` | Fold old events into a snapshot and purge the segments |
796
+
797
+ `invariant ui` is the same process with a browser opened on top of it.
798
+
799
+ Environment:
800
+
801
+ | | |
802
+ |---|---|
803
+ | `INVARIANT_DAEMON` | `auto` (default) \| `require` \| `off` — whether a verb may use the daemon |
804
+ | `INVARIANT_DAEMON_PORT` | The port a spawned daemon binds |
805
+ | `INVARIANT_READ_MODEL` | `fs` turns the event-log projection off |
806
+ | `INVARIANT_HOME` | Relocates all of `~/.invariant` |
807
+
625
808
  ### `invariant projects`
626
809
 
627
810
  The projects the UI serves, kept in `~/.invariant/projects.json`.
628
811
 
629
812
  ```bash
630
- invariant projects # list them, with what each is waiting on
631
- invariant projects add [path] # register a folder (defaults to the current one)
632
- invariant projects remove [id|path] # forget one — nothing on disk is touched
813
+ invariant projects # list them, by group, with what each is waiting on
814
+ invariant projects add [path] # register a folder (defaults to the current one)
815
+ invariant projects remove [id|path] # forget one — nothing on disk is touched
816
+ invariant projects rename <id|path> <name> # what this machine calls it
817
+ invariant projects rename <id|path> --clear # back to the name in invariant.json
818
+ invariant projects group <id|path> <group> # file it under a group, creating one if needed
819
+ invariant projects group <id|path> --none # take it out of its group
820
+ invariant projects groups # the groups, and how full each one is
821
+ invariant projects groups new <name> # create an empty group
822
+ invariant projects groups rm <name|id> # delete a group — its projects survive, ungrouped
633
823
  ```
634
824
 
635
825
  A project must have been initialized (`invariant init`) before it can be added.
636
826
  Removing one only edits the list: re-adding the path brings back every session,
637
827
  review and setting exactly as they were.
638
828
 
829
+ **Names.** Three checkouts whose folders are all called `frontend` are three
830
+ unreadable rows. `rename` gives one a name *on this machine* — it is stored in
831
+ `~/.invariant/projects.json`, never in `invariant.json`, so it does not reach
832
+ anyone who clones the repository. The manifest name stays visible underneath the
833
+ override in the UI, and `--clear` goes back to it. Without an override, a rename
834
+ inside `invariant.json` still shows up on its own.
835
+
836
+ **Groups.** A group is a collapsible section on the projects screen. Groups are
837
+ machine-local too, names are unique, and deleting one never deletes a project —
838
+ its members simply become ungrouped. `--flat` on `invariant projects` keeps the
839
+ old ungrouped output for anything parsing it.
840
+
639
841
  In the UI: `⌘K` opens the command palette, `⌘P` switches project, `⇧⌘H` goes to
640
842
  the project list, `⇧⌘V` toggles rendered preview and editing, `N` starts a
641
843
  session, `?` lists every shortcut. On a review session,
@@ -718,42 +920,14 @@ invariant package publish
718
920
 
719
921
  `package create` options: `-n, --name`, `--version`, `-d, --description`, `-a, --author`, `-l, --license`, `-r, --repository`.
720
922
 
721
- ### `invariant proxy <subcommand>`
722
-
723
- Shared local LLM proxy daemon. Routes a repo's agent traffic through a local proxy by writing a managed env block (`ANTHROPIC_BASE_URL`) into `.claude/settings.json`.
724
-
725
- ```bash
726
- invariant proxy start
727
- invariant proxy status
728
- invariant proxy status --json
729
- invariant proxy on # route the current repo (or `on <dir>`)
730
- invariant proxy off # revert the managed env block
731
- invariant proxy stop
732
- ```
733
-
734
- | Subcommand | Description |
735
- |------------|-------------|
736
- | `start` | Start the shared proxy daemon |
737
- | `stop` | Stop the daemon (token is preserved) |
738
- | `status` | Daemon status, routes, and per-repo activations (`--json`) |
739
- | `on [dir]` | Route a repo through the proxy |
740
- | `off [dir]` | Revert the managed env block (daemon keeps running) |
741
-
742
- Agents read `ANTHROPIC_BASE_URL` once at launch — restart running sessions after `proxy on`.
743
-
744
- ### `invariant claude [args...]`
745
-
746
- Launch Claude Code through the proxy, auto-starting the daemon. All arguments pass through untouched.
747
-
748
- ```bash
749
- invariant claude
750
- invariant claude -- --resume
751
- ```
752
-
753
923
  ### `invariant stats [subcommand]`
754
924
 
755
925
  Inspect Claude Code token usage and cost.
756
926
 
927
+ > **Best effort, Claude Code only.** It reads local transcript files whose
928
+ > format is not a contract, so it may go quiet after a Claude Code release. It
929
+ > reads them and nothing else — no network, no LLM call.
930
+
757
931
  ```bash
758
932
  invariant stats
759
933
  invariant stats --live