@adrrr/tarmac 0.1.1 → 0.1.2

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/dist/cli.js CHANGED
@@ -99,7 +99,7 @@ try {
99
99
  if (args.command === 'serve') {
100
100
  // Unattended for hours, so it opens by saying what it decided and on whose authority.
101
101
  process.stdout.write(renderSettings(config, p.config));
102
- // The one place tarmac deletes anything: temp files its own wrapper left behind when a
102
+ // The one place the CLI deletes anything: temp files its own wrapper left behind when a
103
103
  // terminal died mid-write. Best effort, and it says what it did rather than doing it
104
104
  // quietly — this is the user's directory.
105
105
  const { reaped, failed } = reapOrphanedTemps(snapshotsDir);
package/dist/reap.js CHANGED
@@ -1,4 +1,5 @@
1
- // Wrapper hygiene — the one thing tarmac deletes.
1
+ // Wrapper hygiene — the litter, which is one of the two things tarmac deletes (the other is
2
+ // the wrapper's own amortized prune of dead sessions' snapshots, see `src/wrapper.ts`).
2
3
  //
3
4
  // The generated wrapper writes `<dir>/.tarmac-<session_id>.<pid>.tmp` and renames it over
4
5
  // `<session_id>.json`, so the snapshot a reader sees is never half-written. Kill the
package/dist/wrapper.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // P2 — the generated statusline wrapper.
2
2
  //
3
3
  // Claude Code calls `statusLine.command` on EVERY frame of its TUI, passing a documented
4
- // JSON payload on stdin. The wrapper does exactly two things:
4
+ // JSON payload on stdin. The wrapper does exactly three things:
5
5
  // 1. drop that payload as-is under <snapshotDir>/<session_id>.json (the telemetry);
6
- // 2. hand stdin to the command that was already configured, so the user's display is
6
+ // 2. prune the snapshots of sessions that stopped rendering — amortized, because every
7
+ // line here is paid on every frame (see PRUNE_MARKER below);
8
+ // 3. hand stdin to the command that was already configured, so the user's display is
7
9
  // untouched.
8
10
  //
9
11
  // Written as POSIX sh rather than Node on purpose: this sits in the render path of every
@@ -38,6 +40,20 @@ export const TEMP_PREFIX = '.tarmac-';
38
40
  * is, in itself, whatever the spelling that reached it.
39
41
  */
40
42
  export const WRAPPER_MARKER = 'tarmac statusline wrapper — GENERATED';
43
+ /**
44
+ * Bookkeeping file of the amortized prune below — its mtime is the date of the last sweep.
45
+ * A dotfile, because `readSnapshots` skips those: the wrapper's own paperwork must never be
46
+ * read back as if it were a session's telemetry.
47
+ */
48
+ export const PRUNE_MARKER = '.tarmac-last-prune';
49
+ /** At most one sweep per hour, whatever the frame rate. R3's number, and its whole point. */
50
+ export const PRUNE_EVERY_MIN = 60;
51
+ /**
52
+ * How long a snapshot nobody rewrites survives. A live session restamps its own file on
53
+ * every frame, so 48h without one is a session that is gone — and far beyond `--stale-after`,
54
+ * so nothing was reading that file as current anyway.
55
+ */
56
+ export const SNAPSHOT_TTL_MIN = 48 * 60;
41
57
  /** Single-quotes a string for POSIX sh. */
42
58
  function shQuote(s) {
43
59
  return `'${String(s).replace(/'/g, `'\\''`)}'`;
@@ -90,13 +106,82 @@ fi
90
106
  # --- drop the snapshot (best effort, atomic: temp file + rename in the same dir) ---
91
107
  if [ -n "$sid" ] && mkdir -p "$TARMAC_DIR" 2>/dev/null; then
92
108
  tmp="$TARMAC_DIR/${TEMP_PREFIX}$sid.$$.tmp"
93
- if printf '%s\\n' "$payload" > "$tmp" 2>/dev/null; then
109
+ # \`2>/dev/null\` comes FIRST, and the order is the whole point: redirections are applied
110
+ # left to right, so \`> "$tmp" 2>/dev/null\` opens the temp file while stderr is STILL the
111
+ # user's terminal — the shell prints its own \`cannot create …: Permission denied\` there,
112
+ # and the \`2>\` that was meant to swallow it only takes effect afterwards. On a snapshot
113
+ # directory that has become read-only that is one line of noise per FRAME, on the terminal
114
+ # of a script whose first rule is to be invisible. Exit code and display are untouched
115
+ # (\`printf\` is a regular built-in, so a failed redirection only fails the command), which
116
+ # is exactly why nothing but stderr itself catches this. RULE 1.
117
+ if printf '%s\\n' "$payload" 2>/dev/null > "$tmp"; then
94
118
  mv -f "$tmp" "$TARMAC_DIR/$sid.json" 2>/dev/null || rm -f "$tmp" 2>/dev/null
95
119
  else
96
120
  rm -f "$tmp" 2>/dev/null
97
121
  fi
98
122
  fi
99
123
 
124
+ # --- prune the snapshots of sessions that stopped rendering (amortized) ---
125
+ # Nothing else would ever remove them: \`reap.ts\` collects this script's own temp litter and
126
+ # refuses to touch a \`<sid>.json\`. A fleet that recycles its sessions nightly leaves one dead
127
+ # file behind per session per night, forever. A LIVE session restamps its own snapshot on
128
+ # every frame, so mtime is what tells the dead from the living.
129
+ #
130
+ # Amortized, because this is the render path: the frame pays one \`find\` on ONE file — the
131
+ # marker — and the DIRECTORY is walked at most once an hour. The marker is stamped BEFORE the
132
+ # sweep and only if stamping works, so a sweep that cannot finish is not retried on the next
133
+ # frame. (A directory where the stamp itself keeps failing does pay one \`touch\` per frame,
134
+ # forever — a fork, not a directory walk, and it means nothing can be written there anyway.)
135
+ #
136
+ # Two known holes, both of them the safe way round, and both inherited from the fleet script
137
+ # this transposes:
138
+ # • a marker dated in the FUTURE (clock skew, a restored backup, a network mount) is never
139
+ # stale, so pruning stops until the wall clock catches up. Silent, and it fails towards
140
+ # "keep files" rather than "delete files".
141
+ # • \`-mmin\` is the one thing here POSIX does not require. GNU, BSD and toybox find have it,
142
+ # busybox has it unless the build dropped CONFIG_FEATURE_FIND_MMIN; \`-mtime\` could not
143
+ # replace it (it cannot express hours, and it rounds UP on BSD and DOWN on GNU, so one
144
+ # expression would mean 24h on macOS and 48h on Linux). Where it is missing \`find\` fails,
145
+ # the substitution is empty, and the sweep simply never runs.
146
+ # In both cases snapshots pile up exactly as they did before this block existed, and the
147
+ # display is never at risk.
148
+ #
149
+ # One race, priced and accepted: a sweep can stat a cold snapshot in the microseconds before
150
+ # another session's frame renames a fresh one over that same name, and then unlink the fresh
151
+ # inode. The session writes another one on its next frame — seconds later, on a live session
152
+ # — whereas a lock would cost every frame of every session, forever.
153
+ if [ -d "$TARMAC_DIR" ]; then
154
+ marker="$TARMAC_DIR/${PRUNE_MARKER}"
155
+ # Only a plain file, or nothing at all, is a marker — and the braces are load-bearing,
156
+ # since \`&&\` and \`||\` have EQUAL precedence in sh. Both refusals are real:
157
+ # • a symlink — \`touch\` follows it, so a dangling one has the status line CREATE the
158
+ # link's target, a file outside the snapshot directory. RULE 2.
159
+ # • a directory — \`touch\` SUCCEEDS on one, so every frame reads as freshly stamped while
160
+ # \`-mmin\` never gets a regular file to judge: the sweep would run on every frame and
161
+ # the amortization would be gone, silently.
162
+ if [ ! -h "$marker" ] && { [ ! -e "$marker" ] || { [ -f "$marker" ] && [ -n "$(find "$marker" -mmin +${PRUNE_EVERY_MIN} 2>/dev/null)" ]; }; }; then
163
+ # \`touch\`, and NOT \`: > "$marker"\`: POSIX says a redirection error on a special
164
+ # built-in — and \`:\` is one — shall abort a non-interactive shell. On a snapshot
165
+ # directory that has become read-only that spelling exits dash 2 and ksh 1 with the
166
+ # status line never printed, while bash carries on: RULE 1, broken on Debian and Ubuntu
167
+ # only. An ordinary utility just reports a non-zero status, which is what this \`if\` is
168
+ # asking about.
169
+ if touch "$marker" 2>/dev/null; then
170
+ # \`<dir>/.\` with \`! -name . -prune\` is how POSIX spells \`-maxdepth 1\`, and \`-type f\`
171
+ # keeps a directory or a symlink wearing a session id's name out of it.
172
+ #
173
+ # The glob is the sid SHAPE, not \`*.json\`, and that is the same rule \`reap.ts\` states
174
+ # for the temp files: only what we wrote. A bare \`*.json\` would take \`settings.json\`
175
+ # or \`fleet.json\` with it — data this script never wrote, deleted from inside a status
176
+ # line. 8-4-4-4-12 is the UUID Claude Code emits (every fixture here, and the live
177
+ # fleet directory). The wrapper accepts wider ids than that because it refuses to guess
178
+ # what an id may look like; a sid outside this shape is therefore written and never
179
+ # pruned, which is the direction this trade has to fail in.
180
+ find "$TARMAC_DIR"/. ! -name . -prune -name '????????-????-????-????-????????????.json' -type f -mmin +${SNAPSHOT_TTL_MIN} -exec rm -f {} + 2>/dev/null
181
+ fi
182
+ fi
183
+ fi
184
+
100
185
  # --- hand over to the status line that was already there ---
101
186
  if [ -n "$TARMAC_CHAIN" ]; then
102
187
  printf '%s\\n' "$payload" | sh -c "$TARMAC_CHAIN"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adrrr/tarmac",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Fleet observability for Claude Code — reads documented surfaces only, never an internal format",
5
5
  "keywords": [
6
6
  "claude",