@stravica-ai/digital-operator-pa 0.1.57 → 0.1.58

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.
@@ -4,10 +4,10 @@
4
4
  //
5
5
  // `pa-operator-attendance session-start`
6
6
  // Wired into Claude Code's SessionStart hook. Computes the attendance
7
- // stamp (TTY + env + no grant), persists it to a per-session cache,
8
- // and emits a one-line `additionalContext` block carrying
9
- // `operator-present: true|false[...]` for the loaded agent to read as
10
- // the first line of its workState context.
7
+ // stamp (env + parent-process TTY + no grant), persists it to a
8
+ // per-session cache, and emits a one-line `additionalContext` block
9
+ // carrying `operator-present: true|false[...]` for the loaded agent
10
+ // to read as the first line of its workState context.
11
11
  //
12
12
  // `pa-operator-attendance pre-tool-use`
13
13
  // Wired into Claude Code's PreToolUse hook, matcher Bash + Edit +
@@ -31,10 +31,33 @@
31
31
  //
32
32
  // Zero dependencies beyond node stdlib and the shared grant module in
33
33
  // `@stravica-ai/engineering-pa-core/src/attendance/grant.mjs`.
34
+ //
35
+ // Attendance detection (0.1.58 fix for w-2026-10-02-dave-044, from Dex's
36
+ // 0.1.57 review): the hook's own stdout is always a pipe (the client
37
+ // reads the JSON envelope), so stdout.isTTY cannot discriminate. The
38
+ // discriminator is the hook's PARENT process: in an attended session
39
+ // the parent is the claude client attached to the operator's terminal
40
+ // (ps reports a real tty, e.g. `ttys000`); in a `claude -p` worker or a
41
+ // `claude --print` child the parent is a bash shell under a Bash tool
42
+ // with no controlling tty (ps reports `??`). We therefore probe
43
+ // `ps -o tty= -p <ppid>` and treat a non-empty, non-`?`/non-`??` result
44
+ // as a real tty. On win32 ps is absent, so we fall back to the env var
45
+ // alone and record `ttyProbe: 'unavailable'` on the stamp so the
46
+ // limitation is visible in the decision log.
47
+ //
48
+ // Pattern un-anchoring and log redaction (0.1.58 fixes B and C): the
49
+ // raw-claude and unattended-mutate regexes tolerate any number of
50
+ // leading `NAME=value` (and `env NAME=value`) prefixes so a line like
51
+ // `GITHUB_TOKEN=x gh pr create` still matches, and the Bash command is
52
+ // run through `redactBashCommand` before it lands in the decision log,
53
+ // stripping values of `NAME=value` prefixes, `Authorization:` header
54
+ // values, `--token`/`token=` style arguments, and
55
+ // `https://user:secret@` URL credentials.
34
56
 
35
57
  import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';
36
58
  import { dirname, join } from 'node:path';
37
59
  import { tmpdir } from 'node:os';
60
+ import { spawnSync } from 'node:child_process';
38
61
  import {
39
62
  loadOrMintHostKey,
40
63
  verifyGrant,
@@ -60,8 +83,8 @@ Environment:
60
83
  PA_ATTENDANCE_LOG_DIR=/path/to/log/dir override decision-log dir
61
84
 
62
85
  Decision log (observe mode): one JSONL line per would-deny at
63
- <logDir>/attendance-decisions.jsonl. Rejection log: a hostile grant lands
64
- at <logDir>/attendance-rejects.log.
86
+ <logDir>/attendance-decisions.jsonl, with Bash commands redacted. A
87
+ hostile grant lands at <logDir>/attendance-rejects.log.
65
88
 
66
89
  Usage:
67
90
  pa-operator-attendance session-start
@@ -69,37 +92,116 @@ Usage:
69
92
  pa-operator-attendance --help
70
93
  `;
71
94
 
72
- const RAW_CLAUDE_PATTERN = /^\s*claude(\s+[^-\s]|\s*$)/;
95
+ // Env-prefix tolerance: zero or more `NAME=value` tokens (optionally
96
+ // preceded by `env`) before the command word.
97
+ const ENV_PREFIX = String.raw`(?:(?:env\s+)?[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*`;
98
+
99
+ const RAW_CLAUDE_PATTERN = new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`claude(\s+[^-\s]|\s*$)`);
73
100
 
74
101
  // Bash commands denied for an unattended + no-grant session. Any line
75
102
  // matching one of these is a durable estate side effect: a commit, a
76
103
  // push, a write through the pa-family CLI, a redirect, a tee, an rm.
77
104
  // Read-only verbs (git status, git log, ls, cat, grep, etc.) are not
78
- // listed and pass through.
105
+ // listed and pass through. Each pattern begins with `^\s*${ENV_PREFIX}`
106
+ // so a leading `GITHUB_TOKEN=x` or `env NAME=value` does not bypass the
107
+ // rule.
79
108
  const UNATTENDED_MUTATE_PATTERNS = [
80
- /^\s*git\s+(commit|push|merge|rebase|cherry-pick|reset|checkout|branch|tag|stash|am|revert|pull)\b/,
81
- /^\s*gh\s+(pr\s+(create|edit|merge|close|comment|review)|repo\s+(create|edit|delete|archive)|issue\s+(create|edit|close|comment)|release\s+(create|edit|delete)|workflow\s+(enable|disable|run)|secret\s+(set|remove))\b/,
82
- /^\s*(pnpm\s+exec\s+)?pa\s+(work|project|library|relay|autonomy|content-pub|secrets|scheduler)\s+(?!(?:help|list|ls|get|show|view|status|tail|find|search|export|path|inspect|whoami|context|overview|today|related|domain|orphans|read|print))/,
83
- /^\s*(pnpm\s+exec\s+)?library\s+(put|flush|refresh-clones|up-merge)\b/,
84
- /^\s*(pnpm\s+exec\s+)?claude-relay\s+(send|publish|post)\b/,
85
- /^\s*rm\b/,
86
- /^\s*mv\b/,
87
- /^\s*cp\b/,
88
- /^\s*mkdir\b/,
89
- /^\s*touch\b/,
90
- /^\s*(ln|chmod|chown)\b/,
91
- /^\s*(tee|dd)\b/,
92
- /^\s*(apt|apt-get|brew|npm|pnpm|yarn)\s+(install|add|remove|uninstall|upgrade|update|publish)\b/,
109
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`git\s+(commit|push|merge|rebase|cherry-pick|reset|checkout|branch|tag|stash|am|revert|pull)\b`),
110
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`gh\s+(pr\s+(create|edit|merge|close|comment|review)|repo\s+(create|edit|delete|archive)|issue\s+(create|edit|close|comment)|release\s+(create|edit|delete)|workflow\s+(enable|disable|run)|secret\s+(set|remove))\b`),
111
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(pnpm\s+exec\s+)?pa\s+(work|project|library|relay|autonomy|content-pub|secrets|scheduler)\s+(?!(?:help|list|ls|get|show|view|status|tail|find|search|export|path|inspect|whoami|context|overview|today|related|domain|orphans|read|print))`),
112
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(pnpm\s+exec\s+)?library\s+(put|flush|refresh-clones|up-merge)\b`),
113
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(pnpm\s+exec\s+)?claude-relay\s+(send|publish|post)\b`),
114
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`rm\b`),
115
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`mv\b`),
116
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`cp\b`),
117
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`mkdir\b`),
118
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`touch\b`),
119
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(ln|chmod|chown)\b`),
120
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(tee|dd)\b`),
121
+ new RegExp(String.raw`^\s*` + ENV_PREFIX + String.raw`(apt|apt-get|brew|npm|pnpm|yarn)\s+(install|add|remove|uninstall|upgrade|update|publish)\b`),
93
122
  /[^<>]>\s*\S/, // `foo > bar`, `foo 2> bar`: any single-redirect write
94
123
  />>\s*\S/, // append redirect
95
124
  ];
96
125
 
126
+ // Default TTY probe: ask `ps` for the controlling TTY of the hook's
127
+ // parent process. Returns `null` on no-tty (`??` / `?` / empty) or
128
+ // probe error, a non-empty TTY name (e.g. `ttys000`, `pts/0`) when the
129
+ // parent has one, and `'unavailable'` on win32 (where `ps` is absent).
130
+ // Fail-safe: any error path returns `null` so the detection drops to
131
+ // "unattended", which never grants writes it should not.
132
+ function defaultTtyProbe({ ppid, platform }) {
133
+ if (platform === 'win32') return 'unavailable';
134
+ if (!Number.isInteger(ppid) || ppid <= 0) return null;
135
+ try {
136
+ const result = spawnSync('ps', ['-o', 'tty=', '-p', String(ppid)], {
137
+ encoding: 'utf8',
138
+ timeout: 2000,
139
+ });
140
+ if (result.error || result.status !== 0) return null;
141
+ const tty = (result.stdout || '').trim();
142
+ if (!tty || tty === '?' || tty === '??') return null;
143
+ return tty;
144
+ } catch {
145
+ return null;
146
+ }
147
+ }
148
+
149
+ // Resolve the detection: returns { attended, parentTty, envAttended,
150
+ // grantPresent }. The attended gate is positive-proof: env === '1' AND
151
+ // a real controlling TTY on the parent process AND no grant. On a
152
+ // platform where the TTY probe is unavailable (win32), fall back to the
153
+ // env var alone so a Windows operator can still mark the session
154
+ // attended; the stamp records `ttyProbe: 'unavailable'` so the
155
+ // limitation is visible in the decision log.
156
+ function resolveAttendance({ env, ttyProbe, platform, ppid }) {
157
+ const envAttended = env.CLAUDE_CODE_SESSION_ATTENDED === '1';
158
+ const grantPresent = typeof env.PA_WORKER_GRANT === 'string' && env.PA_WORKER_GRANT.length > 0;
159
+ const parentTty = ttyProbe({ ppid, platform, env });
160
+ let attended;
161
+ if (parentTty === 'unavailable') {
162
+ attended = envAttended && !grantPresent;
163
+ } else {
164
+ attended = envAttended && typeof parentTty === 'string' && parentTty.length > 0 && !grantPresent;
165
+ }
166
+ return { attended, parentTty, envAttended, grantPresent };
167
+ }
168
+
169
+ // Redact a Bash command before writing it into the decision log or any
170
+ // stderr trace line. Values redacted:
171
+ // 1. `NAME=value` and `env NAME=value` prefixes (and inline forms):
172
+ // the key is kept, the value is replaced with `<redacted>`.
173
+ // 2. `Authorization: Bearer ...` and `Authorization: ...` header
174
+ // values.
175
+ // 3. `--token <x>`, `--token=<x>`, `token=<x>` argument-borne tokens.
176
+ // 4. `https://user:secret@host` URL credentials: user kept, secret
177
+ // replaced.
178
+ export function redactBashCommand(cmd) {
179
+ if (typeof cmd !== 'string') return cmd;
180
+ let out = cmd;
181
+ // user:secret@host URL credential (do this BEFORE the KEY=VALUE pass
182
+ // so the scheme:// does not accidentally feed the key regex).
183
+ out = out.replace(/(https?:\/\/[^:/\s@]+):([^@\s'"`]+)@/gi, '$1:<redacted>@');
184
+ // Authorization header value (quoted or unquoted, Bearer or raw).
185
+ out = out.replace(/(Authorization\s*:\s*)(?:Bearer\s+)?([^\s'"`,;)]+)/gi, '$1<redacted>');
186
+ // --token / --token= and token=
187
+ out = out.replace(/(--?token[\s=]+)([^\s'"`;)]+)/gi, '$1<redacted>');
188
+ // NAME=value (env-style). Match a word boundary that is not preceded
189
+ // by `-` or `=` so `--foo=bar` and `=key=val` do not re-match the
190
+ // trailing key.
191
+ out = out.replace(/(^|[\s;&|`(])((?:env\s+)?)([A-Za-z_][A-Za-z0-9_]*)=([^\s'"`]+)/g,
192
+ (_m, lead, envWord, key, _val) => `${lead}${envWord}${key}=<redacted>`);
193
+ return out;
194
+ }
195
+
97
196
  export async function main(argv = [], {
98
197
  stdin = process.stdin,
99
198
  stdout = process.stdout,
100
199
  stderr = process.stderr,
101
200
  env = process.env,
102
201
  now = () => new Date(),
202
+ ttyProbe = defaultTtyProbe,
203
+ platform = process.platform,
204
+ ppid = process.ppid,
103
205
  } = {}) {
104
206
  if (argv.includes('-h') || argv.includes('--help')) {
105
207
  stderr.write(USAGE);
@@ -112,10 +214,10 @@ export async function main(argv = [], {
112
214
  }
113
215
  try {
114
216
  if (sub === 'session-start') {
115
- return await runSessionStart({ stdin, stdout, stderr, env, now });
217
+ return await runSessionStart({ stdin, stdout, stderr, env, now, ttyProbe, platform, ppid });
116
218
  }
117
219
  if (sub === 'pre-tool-use') {
118
- return await runPreToolUse({ stdin, stdout, stderr, env, now });
220
+ return await runPreToolUse({ stdin, stdout, stderr, env, now, ttyProbe, platform, ppid });
119
221
  }
120
222
  stderr.write(`pa-operator-attendance: unknown subcommand "${sub}"\n`);
121
223
  return 1;
@@ -131,25 +233,13 @@ export async function main(argv = [], {
131
233
 
132
234
  // --- session-start ---------------------------------------------------------
133
235
 
134
- async function runSessionStart({ stdin, stdout, stderr, env, now }) {
236
+ async function runSessionStart({ stdin, stdout, stderr, env, now, ttyProbe, platform, ppid }) {
135
237
  const payload = await readPayload(stdin);
136
238
  // SessionStart has no required fields we gate on; we still parse it so
137
239
  // a payload with `session_id` can key the cache properly.
138
240
  const sessionId = (payload && payload.session_id) || env.CLAUDE_CODE_SESSION_ID || 'unknown';
139
241
 
140
- // Attendance detection. The empirical probe for w-2026-10-02-dave-018
141
- // (session-start leg) proved against client 2.1.287 that:
142
- // - a `claude -p` child always sees CLAUDE_CODE_SESSION_ATTENDED=0
143
- // (not unset; the client zeroes it explicitly)
144
- // - a SessionStart hook fired by a `claude -p` child sees both
145
- // stdin and stdout as non-TTY
146
- // So the positive-proof gate is: stdout.isTTY AND env === '1' AND no
147
- // grant. Any other combination is unattended, which is the fail-safe
148
- // direction (a client bump that strips the var defaults to absent).
149
- const grantPresent = typeof env.PA_WORKER_GRANT === 'string' && env.PA_WORKER_GRANT.length > 0;
150
- const envAttended = env.CLAUDE_CODE_SESSION_ATTENDED === '1';
151
- const stdoutIsTty = Boolean(stdout.isTTY);
152
- const attended = stdoutIsTty && envAttended && !grantPresent;
242
+ const { attended, parentTty, envAttended, grantPresent } = resolveAttendance({ env, ttyProbe, platform, ppid });
153
243
 
154
244
  let stamp;
155
245
  if (attended) {
@@ -209,7 +299,7 @@ async function runSessionStart({ stdin, stdout, stderr, env, now }) {
209
299
  decision: 'stamp',
210
300
  stamp,
211
301
  sessionId,
212
- attendedSignals: { stdoutIsTty, envAttended, grantPresent },
302
+ attendedSignals: { parentTty, envAttended, grantPresent, platform },
213
303
  });
214
304
  return 0;
215
305
  }
@@ -224,7 +314,7 @@ function formatStampLine(stamp) {
224
314
 
225
315
  // --- pre-tool-use ----------------------------------------------------------
226
316
 
227
- async function runPreToolUse({ stdin, stdout, stderr, env, now }) {
317
+ async function runPreToolUse({ stdin, stdout, stderr, env, now, ttyProbe, platform, ppid }) {
228
318
  const mode = env.PA_OPERATOR_ATTENDANCE_MODE === 'enforce' ? 'enforce' : 'observe';
229
319
  let payload;
230
320
  try {
@@ -240,7 +330,7 @@ async function runPreToolUse({ stdin, stdout, stderr, env, now }) {
240
330
  return 1;
241
331
  }
242
332
  const sessionId = (payload && payload.session_id) || env.CLAUDE_CODE_SESSION_ID || 'unknown';
243
- const stamp = await readStamp({ env, sessionId }) || recomputeStamp(env);
333
+ const stamp = await readStamp({ env, sessionId }) || recomputeStamp({ env, ttyProbe, platform, ppid });
244
334
 
245
335
  const bashCommand = toolName === 'Bash' && typeof toolInput.command === 'string'
246
336
  ? toolInput.command
@@ -250,7 +340,8 @@ async function runPreToolUse({ stdin, stdout, stderr, env, now }) {
250
340
  // The flag-only forms (`claude --help`, `claude --version`, `claude -v`,
251
341
  // `claude -h`) pass; everything else (`claude help foo`, `claude -p`,
252
342
  // `claude mcp list`, bare `claude`) is a session launch and must go
253
- // through the launcher.
343
+ // through the launcher. Leading `NAME=value` prefixes do not bypass
344
+ // the rule (0.1.58 fix B).
254
345
  if (bashCommand) {
255
346
  for (const line of bashCommand.split('\n')) {
256
347
  if (RAW_CLAUDE_PATTERN.test(line)) {
@@ -299,7 +390,7 @@ async function runPreToolUse({ stdin, stdout, stderr, env, now }) {
299
390
  return 0;
300
391
  }
301
392
 
302
- function recomputeStamp(env) {
393
+ function recomputeStamp({ env, ttyProbe, platform, ppid }) {
303
394
  // Fallback when the session-start cache is missing (hook ordering, cold
304
395
  // start). Mirror the session-start detection without the key material,
305
396
  // which means a grant that LOOKS valid shape-wise is treated as
@@ -307,24 +398,24 @@ function recomputeStamp(env) {
307
398
  // closed if session-start's richer stamp was lost, but it does not
308
399
  // fabricate a verdict it does not have evidence for.
309
400
  const grantPresent = typeof env.PA_WORKER_GRANT === 'string' && env.PA_WORKER_GRANT.length > 0;
310
- const envAttended = env.CLAUDE_CODE_SESSION_ATTENDED === '1';
311
- // In the pre-tool-use leg, stdout is always the hook pipe, so TTY check
312
- // alone is unreliable. Trust the env signal only when there is no grant.
313
401
  if (grantPresent) return { operatorPresent: false, grant: 'present', scope: 'dispatch-only' };
314
- if (envAttended) return { operatorPresent: true, grant: 'none', scope: 'attended' };
315
- return { operatorPresent: false, grant: 'none', scope: 'refusal', refusal: 'no stamp cache, env signals absent' };
402
+ const { attended } = resolveAttendance({ env, ttyProbe, platform, ppid });
403
+ if (attended) return { operatorPresent: true, grant: 'none', scope: 'attended' };
404
+ return { operatorPresent: false, grant: 'none', scope: 'refusal', refusal: 'no stamp cache, env/parent-tty signals absent' };
316
405
  }
317
406
 
318
407
  async function denyOrObserve({ env, now, mode, stdout, stderr, sessionId, toolName, bashCommand, stamp, rule, matched, message }) {
408
+ const safeBashCommand = bashCommand == null ? null : redactBashCommand(bashCommand);
409
+ const safeMatched = typeof matched === 'string' ? redactBashCommand(matched) : matched;
319
410
  await appendDecisionLog({
320
411
  env,
321
412
  now: now(),
322
413
  leg: 'pre-tool-use',
323
414
  decision: mode === 'enforce' ? 'deny' : 'observe',
324
415
  rule,
325
- matched,
416
+ matched: safeMatched,
326
417
  toolName,
327
- bashCommand,
418
+ bashCommand: safeBashCommand,
328
419
  stamp,
329
420
  sessionId,
330
421
  mode,
@@ -335,7 +426,9 @@ async function denyOrObserve({ env, now, mode, stdout, stderr, sessionId, toolNa
335
426
  }
336
427
  // Observe mode: write a one-line stderr note so the operator can see
337
428
  // what would have been denied without the tool call being blocked.
338
- stderr.write(`pa-operator-attendance: OBSERVE would deny (${rule}: ${matched})\n`);
429
+ // The matched text is redacted so a secret-bearing command does not
430
+ // leak into stderr either.
431
+ stderr.write(`pa-operator-attendance: OBSERVE would deny (${rule}: ${safeMatched})\n`);
339
432
  return 0;
340
433
  }
341
434
 
@@ -212,9 +212,9 @@ The stamp is produced by `pa-operator-attendance session-start` (a Claude Code S
212
212
 
213
213
  A dispatch that genuinely needs its own child session goes through `pa-worker-launcher`, which mints the signed grant, strips the attended envelope, and spawns `claude --print` with `--setting-sources user` from a neutral cwd. Workers never invoke `claude` directly from Bash: the coordinator-hook deny plus the attendance pre-tool-use leg catch both.
214
214
 
215
- Rollout is observe-mode by default for one point release: every would-deny lands as a JSONL line under `~/.local/share/pa/attendance-decisions.jsonl` but the tool call proceeds, so operators can inspect the gate's behaviour on real traffic before `PA_OPERATOR_ATTENDANCE_MODE=enforce` becomes the shipped default. Attendance detection is positive-proof: `stdout.isTTY && CLAUDE_CODE_SESSION_ATTENDED === '1' && !PA_WORKER_GRANT`. Any other combination is unattended, which is the fail-safe direction. A client bump that strips the env var defaults the gate to "unattended" and surfaces loudly rather than silently failing open.
215
+ Rollout is observe-mode by default for one point release: every would-deny lands as a JSONL line under `~/.local/share/pa/attendance-decisions.jsonl` (with Bash commands redacted so argument-borne tokens, Authorization headers and URL credentials never land in the log) but the tool call proceeds, so operators can inspect the gate's behaviour on real traffic before `PA_OPERATOR_ATTENDANCE_MODE=enforce` becomes the shipped default. Attendance detection is positive-proof: `CLAUDE_CODE_SESSION_ATTENDED === '1' && parentHasControllingTty && !PA_WORKER_GRANT`. The hook's own stdout is always a pipe (the client reads the JSON envelope), so the discriminator is the hook's PARENT process: in an attended session the parent is the claude client attached to the operator's terminal; in a `claude -p` worker or any Bash-tool child the parent is a bash shell with no controlling tty. We probe `ps -o tty= -p <ppid>` on POSIX and treat a non-empty, non-`?`/non-`??` result as a real tty; on win32 we fall back to the env var alone and record `parentTty: 'unavailable'` on the stamp. Any other combination is unattended, which is the fail-safe direction. A client bump that strips the env var defaults the gate to "unattended" and surfaces loudly rather than silently failing open.
216
216
 
217
- Reference: `packages/cli/src/operator-attendance/cli.mjs`, `packages/cli/src/worker-launcher/cli.mjs`, `packages/core/src/attendance/grant.mjs`. Rationale and incident: `w-2026-10-02-dave-016` (review), `w-2026-10-02-dave-018` (build).
217
+ Reference: `packages/cli/src/operator-attendance/cli.mjs`, `packages/cli/src/worker-launcher/cli.mjs`, `packages/core/src/attendance/grant.mjs`. Rationale and incidents: `w-2026-10-02-dave-016` (review), `w-2026-10-02-dave-018` (build), `w-2026-10-02-dave-044` (0.1.58 detection + pattern + redaction fix, from Dex's 0.1.57 review).
218
218
 
219
219
  ---
220
220
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/digital-operator-pa",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "Digital Operator PA: single-bundled public package surface (bins + library) for the platform.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",