@bookedsolid/reagent 0.10.0 → 0.11.0

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,13 @@
1
+ /**
2
+ * `reagent daemon eject` — unconditional nuclear kill.
3
+ *
4
+ * Does not attempt graceful shutdown. Suitable as a last resort when the
5
+ * daemon is stuck and `reagent daemon stop` is unresponsive.
6
+ *
7
+ * Steps:
8
+ * 1. SIGKILL the PID recorded in ~/.reagent/daemon.pid
9
+ * 2. pkill -f reagent-daemon as a fallback (catches orphans not in PID file)
10
+ * 3. Remove ~/.reagent/daemon.pid
11
+ */
12
+ export declare function runDaemonEject(_args: string[]): void;
13
+ //# sourceMappingURL=eject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eject.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/eject.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAoDpD"}
@@ -0,0 +1,74 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { execSync } from 'node:child_process';
5
+ /** Path to the PID file written by the daemon on startup. */
6
+ function getPidFilePath() {
7
+ return path.join(os.homedir(), '.reagent', 'daemon.pid');
8
+ }
9
+ /**
10
+ * `reagent daemon eject` — unconditional nuclear kill.
11
+ *
12
+ * Does not attempt graceful shutdown. Suitable as a last resort when the
13
+ * daemon is stuck and `reagent daemon stop` is unresponsive.
14
+ *
15
+ * Steps:
16
+ * 1. SIGKILL the PID recorded in ~/.reagent/daemon.pid
17
+ * 2. pkill -f reagent-daemon as a fallback (catches orphans not in PID file)
18
+ * 3. Remove ~/.reagent/daemon.pid
19
+ */
20
+ export function runDaemonEject(_args) {
21
+ const pidPath = getPidFilePath();
22
+ let killedViaPid = false;
23
+ // Step 1: kill via PID file
24
+ if (fs.existsSync(pidPath)) {
25
+ const raw = fs.readFileSync(pidPath, 'utf8').trim();
26
+ const pid = parseInt(raw, 10);
27
+ if (!isNaN(pid)) {
28
+ try {
29
+ process.kill(pid, 'SIGKILL');
30
+ console.log(`[reagent] SIGKILL sent to daemon (PID ${pid})`);
31
+ killedViaPid = true;
32
+ }
33
+ catch (err) {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ // ESRCH means the process does not exist — not an error for eject
36
+ if (err.code !== 'ESRCH') {
37
+ console.error(`[reagent] Could not SIGKILL PID ${pid}: ${msg}`);
38
+ }
39
+ else {
40
+ console.log(`[reagent] PID ${pid} was not running (stale PID file)`);
41
+ }
42
+ }
43
+ }
44
+ else {
45
+ console.error(`[reagent] PID file contains invalid value: ${JSON.stringify(raw)}`);
46
+ }
47
+ }
48
+ else {
49
+ console.log(`[reagent] No PID file found at ${pidPath}`);
50
+ }
51
+ // Step 2: pkill fallback — catches orphaned processes not tracked by PID file
52
+ try {
53
+ execSync('pkill -KILL -f reagent-daemon 2>/dev/null || true', { stdio: 'ignore' });
54
+ if (!killedViaPid) {
55
+ console.log('[reagent] pkill -KILL -f reagent-daemon executed (orphan sweep)');
56
+ }
57
+ }
58
+ catch {
59
+ // pkill exits non-zero when no processes matched — that is acceptable
60
+ }
61
+ // Step 3: remove PID file
62
+ if (fs.existsSync(pidPath)) {
63
+ try {
64
+ fs.unlinkSync(pidPath);
65
+ console.log(`[reagent] Removed PID file: ${pidPath}`);
66
+ }
67
+ catch (err) {
68
+ const msg = err instanceof Error ? err.message : String(err);
69
+ console.error(`[reagent] Could not remove PID file: ${msg}`);
70
+ }
71
+ }
72
+ console.log('[reagent] Eject complete.\n');
73
+ }
74
+ //# sourceMappingURL=eject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eject.js","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/eject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,6DAA6D;AAC7D,SAAS,cAAc;IACrB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,KAAe;IAC5C,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,4BAA4B;IAC5B,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAE9B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;gBAC7B,OAAO,CAAC,GAAG,CAAC,yCAAyC,GAAG,GAAG,CAAC,CAAC;gBAC7D,YAAY,GAAG,IAAI,CAAC;YACtB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,kEAAkE;gBAClE,IAAK,GAA6B,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACpD,OAAO,CAAC,KAAK,CAAC,mCAAmC,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC;gBAClE,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,mCAAmC,CAAC,CAAC;gBACvE,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8CAA8C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,8EAA8E;IAC9E,IAAI,CAAC;QACH,QAAQ,CAAC,mDAAmD,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnF,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,iEAAiE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,sEAAsE;IACxE,CAAC;IAED,0BAA0B;IAC1B,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC;QACxD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,OAAO,CAAC,KAAK,CAAC,wCAAwC,GAAG,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,6BAA6B,CAAC,CAAC;AAC7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/index.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA0B9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/index.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA6B9C"}
@@ -2,6 +2,7 @@ import { runDaemonStart } from './start.js';
2
2
  import { runDaemonStop } from './stop.js';
3
3
  import { runDaemonStatus } from './status.js';
4
4
  import { runDaemonRestart } from './restart.js';
5
+ import { runDaemonEject } from './eject.js';
5
6
  /**
6
7
  * Entry point for `reagent daemon <subcommand> [options]`.
7
8
  */
@@ -24,6 +25,9 @@ export function runDaemon(args) {
24
25
  case 'restart':
25
26
  runDaemonRestart(rest);
26
27
  break;
28
+ case 'eject':
29
+ runDaemonEject(rest);
30
+ break;
27
31
  default:
28
32
  console.error(`\nUnknown daemon subcommand: ${sub}`);
29
33
  printDaemonHelp();
@@ -42,9 +46,10 @@ Subcommands:
42
46
  stop Stop the running daemon
43
47
  status Show daemon health and active sessions
44
48
  restart Gracefully restart the daemon
49
+ eject Nuclear kill — SIGKILL daemon and sweep orphans (last resort)
45
50
 
46
51
  Options for start:
47
- --port <port> Override the listen port (default: 7777)
52
+ --port <port> Override the listen port (default: 3737)
48
53
  --bind <addr> Override the bind address (default: 127.0.0.1)
49
54
  --foreground Run in foreground instead of backgrounding
50
55
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5B,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC/D,eAAe,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IAED,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,cAAc,CAAC,IAAI,CAAC,CAAC;YACrB,MAAM;QACR,KAAK,MAAM;YACT,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,MAAM;QACR,KAAK,QAAQ;YACX,eAAe,CAAC,IAAI,CAAC,CAAC;YACtB,MAAM;QACR,KAAK,SAAS;YACZ,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACvB,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;YACrD,eAAe,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;CAuBb,CAAC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/cli/commands/daemon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5B,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC/D,eAAe,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IAED,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,cAAc,CAAC,IAAI,CAAC,CAAC;YACrB,MAAM;QACR,KAAK,MAAM;YACT,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,MAAM;QACR,KAAK,QAAQ;YACX,eAAe,CAAC,IAAI,CAAC,CAAC;YACtB,MAAM;QACR,KAAK,SAAS;YACZ,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACvB,MAAM;QACR,KAAK,OAAO;YACV,cAAc,CAAC,IAAI,CAAC,CAAC;YACrB,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;YACrD,eAAe,EAAE,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,SAAS,eAAe;IACtB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;CAwBb,CAAC,CAAC;AACH,CAAC"}
@@ -97,27 +97,49 @@ if [[ "$SCORE" == "trivial" ]]; then
97
97
  exit 0
98
98
  fi
99
99
 
100
- # ── 9. Resolve reagent CLI (node_modules/.bin first, dist fallback) ───────────
100
+ # ── 9. Resolve reagent CLI ────────────────────────────────────────────────────
101
+ # Try local installs first, then dist build, then global PATH install.
101
102
  REAGENT_CLI_ARGS=()
102
103
  if [[ -f "${REAGENT_ROOT}/node_modules/.bin/reagent" ]]; then
103
104
  REAGENT_CLI_ARGS=(node "${REAGENT_ROOT}/node_modules/.bin/reagent")
104
105
  elif [[ -f "${REAGENT_ROOT}/dist/cli/index.js" ]]; then
105
106
  REAGENT_CLI_ARGS=(node "${REAGENT_ROOT}/dist/cli/index.js")
107
+ elif command -v reagent >/dev/null 2>&1; then
108
+ REAGENT_CLI_ARGS=(reagent)
106
109
  fi
107
110
 
108
- # ── 10. Standard + Significant check review cache ───────────────────────────
109
- if [[ "$SCORE" == "standard" ]] || [[ "$SCORE" == "significant" ]]; then
110
- # Compute SHA of staged content for cache lookup
111
- STAGED_SHA=$(cd "$REAGENT_ROOT" && git diff --cached | shasum -a 256 | cut -d' ' -f1 2>/dev/null || echo "")
112
- BRANCH=$(cd "$REAGENT_ROOT" && git branch --show-current 2>/dev/null || echo "")
111
+ # ── 10. Check review cache for all non-trivial commits ────────────────────────
112
+ # Compute SHA and branch here so both standard and significant tiers share them.
113
+ STAGED_SHA=$(cd "$REAGENT_ROOT" && git diff --cached | shasum -a 256 | cut -d' ' -f1 2>/dev/null || echo "")
114
+ BRANCH=$(cd "$REAGENT_ROOT" && git branch --show-current 2>/dev/null || echo "")
115
+ CACHE_FILE="${REAGENT_ROOT}/.reagent/review-cache.json"
113
116
 
114
- if [[ -n "$STAGED_SHA" ]] && [[ ${#REAGENT_CLI_ARGS[@]} -gt 0 ]]; then
115
- # Check review cache via reagent CLI
117
+ if [[ -n "$STAGED_SHA" ]]; then
118
+ CACHE_HIT=false
119
+
120
+ # Primary: use CLI when available — handles TTL, expiry, and branch-scoped keys
121
+ if [[ ${#REAGENT_CLI_ARGS[@]} -gt 0 ]]; then
116
122
  CACHE_RESULT=$("${REAGENT_CLI_ARGS[@]}" cache check "$STAGED_SHA" --branch "$BRANCH" 2>/dev/null || echo '{"hit":false}')
117
123
  if printf '%s' "$CACHE_RESULT" | jq -e '.hit == true' >/dev/null 2>&1; then
118
- exit 0
124
+ CACHE_HIT=true
119
125
  fi
120
126
  fi
127
+
128
+ # Fallback: read cache JSON directly — works when reagent is not on PATH.
129
+ # Checks branch-scoped key ("branch:sha") first, then bare SHA (empty-branch case).
130
+ if [[ "$CACHE_HIT" == "false" ]] && [[ -f "$CACHE_FILE" ]]; then
131
+ CACHE_KEY="${BRANCH}:${STAGED_SHA}"
132
+ DIRECT_HIT=$(jq -r --arg k1 "$CACHE_KEY" --arg k2 "$STAGED_SHA" \
133
+ '(.entries[$k1] // .entries[$k2]) | if . == null then "miss" elif .result == "pass" then "hit" else "miss" end' \
134
+ "$CACHE_FILE" 2>/dev/null || echo "miss")
135
+ if [[ "$DIRECT_HIT" == "hit" ]]; then
136
+ CACHE_HIT=true
137
+ fi
138
+ fi
139
+
140
+ if [[ "$CACHE_HIT" == "true" ]]; then
141
+ exit 0
142
+ fi
121
143
  fi
122
144
 
123
145
  # ── 10. Block and request review ──────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookedsolid/reagent",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Zero-trust MCP gateway — policy enforcement, secret redaction, and audit logging for AI-assisted projects",
5
5
  "license": "MIT",
6
6
  "author": "Booked Solid Technology <oss@bookedsolid.tech> (https://bookedsolid.tech)",
@@ -52,9 +52,11 @@
52
52
  "changeset": "changeset",
53
53
  "changeset:version": "changeset version",
54
54
  "changeset:publish": "changeset publish",
55
- "daemon:start": "reagent daemon start",
56
- "daemon:stop": "reagent daemon stop",
57
- "daemon:status": "reagent daemon status",
55
+ "daemon:start": "mkdir -p ~/.reagent && nohup ./daemon/target/release/reagent-daemon > ~/.reagent/daemon.log 2>&1 & echo $! > ~/.reagent/daemon.pid && echo \"reagent daemon started (PID $(cat ~/.reagent/daemon.pid))\"",
56
+ "daemon:stop": "PID=$(cat ~/.reagent/daemon.pid 2>/dev/null); if [[ \"$PID\" =~ ^[0-9]+$ ]]; then kill -- \"$PID\" 2>/dev/null && rm -f ~/.reagent/daemon.pid && echo \"reagent daemon stopped\" || echo \"reagent daemon was not running\"; else echo \"reagent daemon was not running\"; fi",
57
+ "daemon:status": "[ -f ~/.reagent/daemon.pid ] && kill -0 $(cat ~/.reagent/daemon.pid) 2>/dev/null && echo \"running (PID $(cat ~/.reagent/daemon.pid))\" || echo \"not running\"",
58
+ "daemon:logs": "tail -f ~/.reagent/daemon.log",
59
+ "daemon:eject": "pkill -KILL -f reagent-daemon 2>/dev/null; rm -f ~/.reagent/daemon.pid; echo \"ejected\"",
58
60
  "daemon:restart": "reagent daemon restart",
59
61
  "daemon:build": "cargo build --release --manifest-path daemon/Cargo.toml",
60
62
  "lint": "eslint .",