projmux 0.4.5 → 0.4.7

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.
@@ -0,0 +1,57 @@
1
+ # Testing
2
+
3
+ The local validation contract is exposed through make targets so CI, agents,
4
+ and humans run the same entrypoints.
5
+
6
+ ## Targets
7
+
8
+ - `make test` runs the fast Go unit suite. These tests avoid tmux, TTY, GUI,
9
+ and host shell dependencies.
10
+ - Picker unit coverage includes the backend-neutral item/action contract, fzf
11
+ adapter mapping, native title-focused filtering, numeric selection, and
12
+ shared close actions.
13
+ - `make test-integration` builds `test/docker/Dockerfile` and runs
14
+ `test/integration/linux-smoke.sh` in Docker. It validates Linux dependency
15
+ discovery, tmux config generation/install, app config reload against a real
16
+ `tmux` server, and notify queue CRUD.
17
+ - `make test-install-smoke` builds the same Docker image and runs
18
+ `test/install/smoke.sh`. It validates `make install`, atomic binary
19
+ replacement into an isolated install dir, `tmux apply`, and post-install
20
+ `notify reconcile` initialization with a fresh HOME/XDG state tree.
21
+ - `make test-e2e` builds the same Docker image and runs
22
+ `test/e2e/linux-smoke.sh`. It validates a minimal real-tmux workflow:
23
+ sessions, panes, config sourcing, reply-state notify reconciliation, focus
24
+ notify fallback, and status notify rendering.
25
+
26
+ ## Docker-Covered Checks
27
+
28
+ The Docker suites are intended to cover portable Linux behavior that can be
29
+ made deterministic in a container:
30
+
31
+ - binary build and source install into an isolated prefix
32
+ - `doctor` dependency checks for `tmux`, `fzf`, `git`, and `stty`
33
+ - tmux config print/install/apply paths
34
+ - notify queue push/list/ack/reconcile state transitions
35
+ - focus fallback behavior when a tmux server has sessions but no attached
36
+ client
37
+ - status rendering that only depends on tmux state and local files
38
+
39
+ The test container disables networking during `docker run`. The image build may
40
+ use the network to fetch the pinned base image, apt packages, and pinned fzf
41
+ version, but suite execution should not need network access after the image is
42
+ built.
43
+
44
+ ## Host-Only Checks
45
+
46
+ The Docker suites do not replace checks that depend on a real host terminal,
47
+ desktop shell, or OS integration:
48
+
49
+ - terminal emulator key delivery and swallowing for `Alt-1..5`, `Ctrl-N`, and
50
+ CSI-u chords
51
+ - Windows Terminal and WSL interop
52
+ - macOS host path, shell, and GUI behavior
53
+ - desktop notification click callbacks
54
+ - terminal-specific popup rendering and interactive key dispatch
55
+
56
+ Keep those checks as manual or host-run smoke validation until a dedicated
57
+ host harness exists.
@@ -0,0 +1,100 @@
1
+ # Upgrading
2
+
3
+ projmux has two update surfaces:
4
+
5
+ - `projmux shell` reads the cached release status before opening the app. When
6
+ the cache is fresh and a newer release is available, startup shows a picker
7
+ with Update Now, Later, and Skip This Version actions.
8
+ - Settings > About > Update shows the current version, detected installer,
9
+ cached latest version, Check Updates, and Update Now actions.
10
+
11
+ Startup never reaches the network. Refresh the cache explicitly when you want
12
+ projmux to check GitHub Releases:
13
+
14
+ ```sh
15
+ projmux update check
16
+ ```
17
+
18
+ Apply the cached update through the detected installer:
19
+
20
+ ```sh
21
+ projmux update apply
22
+ ```
23
+
24
+ Use `--dry-run` to see the planned action and `--no-apply` to skip reloading
25
+ the live tmux config after the binary changes.
26
+
27
+ ## npm Installs
28
+
29
+ The recommended install path is:
30
+
31
+ ```sh
32
+ npm install -g projmux
33
+ ```
34
+
35
+ For npm-managed installs, `projmux update apply` runs:
36
+
37
+ ```sh
38
+ npm update -g projmux
39
+ projmux tmux apply
40
+ ```
41
+
42
+ The npm shim sets `PROJMUX_INSTALLER=npm` so projmux can detect this path
43
+ automatically. You can also update manually with `npm update -g projmux`.
44
+
45
+ ## Go Installs
46
+
47
+ If you installed with `go install`, `projmux update apply` delegates to the
48
+ same atomic replacement flow as `projmux upgrade`:
49
+
50
+ ```sh
51
+ projmux upgrade # @latest, replace + apply
52
+ projmux upgrade --ref @v0.4.0 # pin a specific tag
53
+ projmux upgrade --ref @main # track a branch
54
+ projmux upgrade --target /usr/local/bin/projmux # replace another path
55
+ projmux upgrade --no-apply # skip 'projmux tmux apply'
56
+ projmux upgrade --dry-run # print the steps only
57
+ ```
58
+
59
+ `projmux upgrade` reinstalls via `go install`, atomically replaces the active
60
+ file, and reapplies the live tmux config so a running `-L projmux` server picks
61
+ up new bindings without a restart.
62
+
63
+ The command reads `PROJMUX_PROJDIR` from the calling shell and memoizes the
64
+ primary path to `~/.config/projmux/projdir`, so the new binary keeps the same
65
+ project root context as the one it replaces.
66
+
67
+ To switch the saved project root during the upgrade:
68
+
69
+ ```sh
70
+ PROJMUX_PROJDIR=/new/path projmux upgrade
71
+
72
+ # Multi-path also works; only the primary entry is persisted.
73
+ PROJMUX_PROJDIR="/main/repos:/secondary/repos" projmux upgrade
74
+ ```
75
+
76
+ ## GitHub Release Installs
77
+
78
+ When `PROJMUX_INSTALLER=github-release`, `projmux update apply` downloads the
79
+ latest matching `projmux_<version>_<goos>_<goarch>.tar.gz` asset from GitHub
80
+ Releases, extracts the binary, atomically replaces the current executable, and
81
+ then runs `projmux tmux apply` unless `--no-apply` is set.
82
+
83
+ Set the installer explicitly if you manage a release binary outside npm or Go:
84
+
85
+ ```sh
86
+ export PROJMUX_INSTALLER=github-release
87
+ ```
88
+
89
+ ## Source Checkouts
90
+
91
+ Source installs are updated from the checkout:
92
+
93
+ ```sh
94
+ git pull --ff-only
95
+ make install
96
+ ```
97
+
98
+ `projmux update apply` reports an actionable error for
99
+ `PROJMUX_INSTALLER=source` because source trees may have local changes and need
100
+ the repository's normal review and test flow.
@@ -0,0 +1,153 @@
1
+ # Usage tracking
2
+
3
+ `projmux usage` and `projmux status usage` report authoritative 5-hour
4
+ and weekly utilisation for both Claude Code and the Codex CLI. Both
5
+ adapters read from the upstream's own view of the account so the
6
+ percentages match what `claude /usage` and `codex` show natively.
7
+
8
+ ## Adapters
9
+
10
+ ### Claude (`internal/core/usage/adapters/claude`)
11
+
12
+ OAuth-authenticated HTTPS fetch:
13
+
14
+ - `GET https://api.anthropic.com/api/oauth/usage` with the bearer token
15
+ read from `~/.claude/.credentials.json`.
16
+ - On HTTP 401 the adapter performs a single refresh round-trip:
17
+ `POST https://api.anthropic.com/api/oauth/token` with the stored
18
+ refresh token, then rewrites `.credentials.json` with the rotated
19
+ access/refresh pair before retrying the usage call.
20
+ - Tokens are never logged.
21
+
22
+ Throttle: 5 minutes (`ThrottleHinter`). The `60s` cadence used in 0.3
23
+ trips 429 in practice, so the adapter raises the manager's per-adapter
24
+ floor.
25
+
26
+ 429 backoff (`BackoffStater`):
27
+
28
+ - Base `30m`, doubling per consecutive 429, cap `60m`.
29
+ - A `Retry-After` header (when ≥ 60s) raises the floor.
30
+ - During backoff `Collect` short-circuits — no network call, prior
31
+ rows are preserved.
32
+ - A clean 200 resets the consecutive counter.
33
+ - `--force` (BackoffResetter) clears the persisted state and attempts
34
+ the call regardless of streak.
35
+
36
+ ### Codex (`internal/core/usage/adapters/codex`)
37
+
38
+ Local rollout JSONL parser. No network calls.
39
+
40
+ - Walks `${HOME}/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-*.jsonl`
41
+ newest-first by mtime (NOT filename — Dropbox-synced rollouts can
42
+ arrive out of order).
43
+ - Caps the search to 8 days back so a stale tree never blocks the
44
+ status interval.
45
+ - Parses each line until it finds the latest `token_count` record's
46
+ `rate_limits` payload:
47
+ ```json
48
+ {
49
+ "limit_id": "codex",
50
+ "primary": { "used_percent": 8.0, "resets_at": "..." },
51
+ "secondary": { "used_percent": 4.5, "resets_at": "..." }
52
+ }
53
+ ```
54
+ - `primary` → 5h window, `secondary` → weekly window.
55
+
56
+ Codex shares the manager's default `30s` throttle (no
57
+ `ThrottleHinter`). It does not implement `BackoffStater` — local-only
58
+ read.
59
+
60
+ ## Snapshot store
61
+
62
+ ```
63
+ ${PROJMUX_USAGE_STATE_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/projmux/usage}/snapshots.json
64
+ ```
65
+
66
+ JSON document keyed by adapter, recording:
67
+
68
+ - per-window `Snapshot{Model, Window, Pct, Limit, ResetsAt, UpdatedAt}`
69
+ - per-adapter `last_collect` timestamp (drives the throttle)
70
+ - per-adapter `Backoff{Until, Consecutive}` (drives the cooldown)
71
+
72
+ Adapter failures merge over the prior slice rather than replacing it,
73
+ so a 429 keeps the last known good rows visible. Force-redirect the
74
+ file at install time via `PROJMUX_USAGE_STATE_DIR` to share the cache
75
+ across machines (Dropbox, iCloud Drive).
76
+
77
+ ## CLI
78
+
79
+ ### `projmux usage`
80
+
81
+ ```
82
+ projmux usage [--model codex|claude|all] [--window 5h|weekly|all]
83
+ [--json] [--force|-f]
84
+ ```
85
+
86
+ Calls `Manager.Collect` (or `ForceCollect` with `--force`), filters by
87
+ model/window, and renders the tab-aligned table:
88
+
89
+ ```
90
+ MODEL WINDOW PCT RESETS_AT STALE
91
+ claude 5h 80% 2026-05-07T14:00:00+09:00
92
+ claude weekly 35% 2026-05-09T00:00:00+09:00
93
+ codex 5h 8% 2026-05-07T11:30:00+09:00
94
+ codex weekly 4% 2026-05-09T00:00:00+09:00
95
+ ```
96
+
97
+ `STALE` is `*` when `now - UpdatedAt > 10m`. A `--json` payload returns
98
+ the `Snapshot` array; if any adapter is in backoff, the wrapper
99
+ `{snapshots, backoff: {model: {until, consecutive}}}` is emitted
100
+ instead. A backoff note is appended to the human table:
101
+
102
+ ```
103
+ claude is in backoff, try again in 30m (use --force to bypass)
104
+ ```
105
+
106
+ ### `projmux status usage`
107
+
108
+ ```
109
+ projmux status usage [--max-width N] [--force|-f]
110
+ ```
111
+
112
+ The HUD bar wired to the tmux status interval. Triggers an
113
+ opportunistic refresh: `MaybeCollect(throttle=30s)` (subject to
114
+ per-adapter throttle and active backoff). Errors are swallowed unless
115
+ `PROJMUX_USAGE_DEBUG` is set. Then loads the cache and renders.
116
+
117
+ Output degrades through six tiers as `--max-width` shrinks:
118
+
119
+ 1. Long form with last-sync age + bars: `Claude (3m) 5h [████████░░]
120
+ 80% · weekly [...] Codex 5h [...] 20% · weekly [...]`
121
+ 2. Drop the age indicator (legacy long form).
122
+ 3. Drop the weekly bar.
123
+ 4. Drop bars entirely (`Claude 5h:80% weekly:30%`).
124
+ 5. Single-letter labels (`C 5h:80% weekly:30%`).
125
+ 6. Hard rune-truncate with trailing `…`.
126
+
127
+ The age indicator's colour ramps with staleness: dim grey
128
+ (<1h), yellow (1–6h), bold red (≥6h). Codex opts out of the indicator
129
+ because the rollout file is always near-current (no throttle gap to
130
+ report).
131
+
132
+ ## Force semantics
133
+
134
+ `--force` / `-f` does two things:
135
+
136
+ 1. Bypasses the per-adapter throttle gate so every adapter's `Collect`
137
+ runs even if `now - last_collect < throttle`.
138
+ 2. Calls `BackoffResetter.ResetBackoff` on adapters that implement it,
139
+ clearing the in-memory and on-disk backoff so the network call
140
+ attempts regardless of any active 429 cooldown.
141
+
142
+ A force that itself returns 429 records a fresh `consecutive=1` —
143
+ forcing does NOT preserve the prior streak (the user explicitly chose
144
+ to attempt now). Bind it to a tmux key (e.g. `prefix U`) for a manual
145
+ "refresh now" gesture.
146
+
147
+ ## Environment variables
148
+
149
+ | Variable | Effect |
150
+ | --- | --- |
151
+ | `PROJMUX_USAGE_STATE_DIR` | Override snapshot directory. Resolved verbatim, no `~` expansion. |
152
+ | `PROJMUX_USAGE_DEBUG` | Surface adapter errors from `status usage` to stderr. |
153
+ | `PROJMUX_USAGE_LIMITS_PATH` | Deprecated; read but ignored (limits come from upstream APIs). |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projmux",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "tmux project session manager",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/crevissepartners/projmux#readme",
@@ -17,15 +17,17 @@
17
17
  },
18
18
  "files": [
19
19
  "npm/projmux.js",
20
+ "docs/*.md",
21
+ "docs/assets/*",
20
22
  "README.md",
21
23
  "README-ko.md",
22
24
  "LICENSE"
23
25
  ],
24
26
  "optionalDependencies": {
25
- "@projmux/darwin-arm64": "0.4.5",
26
- "@projmux/darwin-x64": "0.4.5",
27
- "@projmux/linux-arm64": "0.4.5",
28
- "@projmux/linux-x64": "0.4.5"
27
+ "@projmux/darwin-arm64": "0.4.7",
28
+ "@projmux/darwin-x64": "0.4.7",
29
+ "@projmux/linux-arm64": "0.4.7",
30
+ "@projmux/linux-x64": "0.4.7"
29
31
  },
30
32
  "scripts": {
31
33
  "package:npm": "scripts/package-npm.sh",