@tjamescouch/niki 0.5.6 → 0.5.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.
Files changed (2) hide show
  1. package/bin/niki +38 -1
  2. package/package.json +1 -1
package/bin/niki CHANGED
@@ -55,12 +55,16 @@ Options:
55
55
  --max-restarts <n> Max restart attempts, 0=unlimited (default: 0)
56
56
  --restart-delay <secs> Delay between restarts with ±30% jitter (default: 5)
57
57
  --kill-orphaned-mcp Kill stale agentchat-mcp processes on startup (default: off)
58
+ --on-kill <command> Run shell command when agent is killed (receives context via env vars)
59
+ Env vars: NIKI_KILL_REASON, NIKI_TOKENS, NIKI_BUDGET, NIKI_DURATION,
60
+ NIKI_SENDS, NIKI_TOOL_CALLS, NIKI_PID, NIKI_CMD
58
61
 
59
62
  Examples:
60
63
  niki --profile longrun -- gro --persistent --model sonnet
61
64
  niki --budget 500000 -- claude -p "your prompt" --verbose
62
65
  niki --timeout 1800 --max-sends 5 -- claude -p "..." --model sonnet --verbose
63
- niki --restart --max-restarts 10 -- gro --model gpt-5.2 "your prompt"`);
66
+ niki --restart --max-restarts 10 -- gro --model gpt-5.2 "your prompt"
67
+ niki --on-kill 'curl -d "$NIKI_KILL_REASON: $NIKI_TOKENS tokens" ntfy.sh/my-agents' -- claude -p "..."`);
64
68
  process.exit(1);
65
69
  }
66
70
 
@@ -128,6 +132,7 @@ const { values: opts } = parseArgs({
128
132
  'max-restarts': { type: 'string', default: D["max-restarts"] },
129
133
  'restart-delay': { type: 'string', default: D["restart-delay"] },
130
134
  'kill-orphaned-mcp': { type: 'boolean', default: false },
135
+ 'on-kill': { type: 'string' },
131
136
  },
132
137
  });
133
138
 
@@ -149,6 +154,7 @@ const RESTART = opts.restart;
149
154
  const MAX_RESTARTS = parseInt(opts['max-restarts'], 10);
150
155
  const RESTART_DELAY_S = parseFloat(opts['restart-delay']);
151
156
  const KILL_ORPHANED_MCP = opts['kill-orphaned-mcp'];
157
+ const ON_KILL = opts['on-kill'];
152
158
  const LOG_JSON = opts['log-json'];
153
159
 
154
160
  // --- Invocation rate limiting (prevent tight crash loops) ---
@@ -417,6 +423,35 @@ function checkRateLimits() {
417
423
  }
418
424
  return null;
419
425
  }
426
+ // --- Kill notification hook ---
427
+
428
+ function fireOnKill(reason) {
429
+ if (!ON_KILL) return;
430
+ const duration = Math.round((Date.now() - new Date(state.startedAt).getTime()) / 1000);
431
+ const env = {
432
+ ...process.env,
433
+ NIKI_KILL_REASON: reason,
434
+ NIKI_TOKENS: String(state.tokensTotal),
435
+ NIKI_BUDGET: String(BUDGET),
436
+ NIKI_DURATION: String(duration),
437
+ NIKI_SENDS: String(state.sendCalls),
438
+ NIKI_TOOL_CALLS: String(state.toolCalls),
439
+ NIKI_PID: String(state.pid),
440
+ NIKI_CMD: childCmd,
441
+ };
442
+ try {
443
+ const hook = spawn('sh', ['-c', ON_KILL], {
444
+ env,
445
+ stdio: 'ignore',
446
+ detached: true,
447
+ });
448
+ hook.unref();
449
+ log(`on-kill hook fired: ${ON_KILL}`, 'debug');
450
+ } catch (err) {
451
+ log(`on-kill hook failed: ${err.message}`, 'warn');
452
+ }
453
+ }
454
+
420
455
  // --- Kill logic ---
421
456
 
422
457
  let child = null;
@@ -443,6 +478,8 @@ function killChild(reason) {
443
478
  }
444
479
  log(`KILL — reason: ${reason} | tokens: ${state.tokensTotal}/${BUDGET} | sends: ${state.sendCallsThisMinute}/min | tools: ${state.toolCallsThisMinute}/min`, 'error');
445
480
 
481
+ fireOnKill(reason);
482
+
446
483
  child.kill('SIGTERM');
447
484
 
448
485
  // Grace period, then SIGKILL
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tjamescouch/niki",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Deterministic process supervisor for AI agents — token budgets, rate limits, and abort control",
5
5
  "bin": {
6
6
  "niki": "./bin/niki"