@tjamescouch/niki 0.5.5 → 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.
- package/bin/niki +101 -13
- package/package.json +1 -1
package/bin/niki
CHANGED
|
@@ -34,6 +34,7 @@ if (SEPARATOR === -1 || SEPARATOR === process.argv.length - 1) {
|
|
|
34
34
|
Usage: niki [options] -- <command> [args...]
|
|
35
35
|
|
|
36
36
|
Options:
|
|
37
|
+
--profile <name> Apply a preset profile (default: none; profiles: longrun)
|
|
37
38
|
--budget <tokens> Max total tokens (input+output) before SIGTERM (default: 1000000)
|
|
38
39
|
--timeout <seconds> Max wall-clock runtime before SIGTERM (default: 3600)
|
|
39
40
|
--max-sends <n> Max agentchat_send calls per minute (default: 10)
|
|
@@ -53,11 +54,17 @@ Options:
|
|
|
53
54
|
--restart Restart the child process when it exits (default: off)
|
|
54
55
|
--max-restarts <n> Max restart attempts, 0=unlimited (default: 0)
|
|
55
56
|
--restart-delay <secs> Delay between restarts with ±30% jitter (default: 5)
|
|
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
|
|
56
61
|
|
|
57
62
|
Examples:
|
|
63
|
+
niki --profile longrun -- gro --persistent --model sonnet
|
|
58
64
|
niki --budget 500000 -- claude -p "your prompt" --verbose
|
|
59
65
|
niki --timeout 1800 --max-sends 5 -- claude -p "..." --model sonnet --verbose
|
|
60
|
-
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 "..."`);
|
|
61
68
|
process.exit(1);
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -65,28 +72,67 @@ const nikiArgs = process.argv.slice(2, SEPARATOR);
|
|
|
65
72
|
const childCmd = process.argv[SEPARATOR + 1];
|
|
66
73
|
const childArgs = process.argv.slice(SEPARATOR + 2);
|
|
67
74
|
|
|
75
|
+
function getProfileArg(args) {
|
|
76
|
+
const i = args.indexOf("--profile");
|
|
77
|
+
if (i === -1) return null;
|
|
78
|
+
const v = args[i + 1];
|
|
79
|
+
return v && !v.startsWith("-") ? v : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const DEFAULTS = {
|
|
83
|
+
budget: "1000000",
|
|
84
|
+
timeout: "3600",
|
|
85
|
+
"max-sends": "10",
|
|
86
|
+
"max-tool-calls": "30",
|
|
87
|
+
"stall-timeout": "60",
|
|
88
|
+
"startup-timeout": "180",
|
|
89
|
+
"dead-air-timeout": "5",
|
|
90
|
+
"max-nudges": "3",
|
|
91
|
+
cooldown: "5",
|
|
92
|
+
"poll-interval": "1000",
|
|
93
|
+
"max-restarts": "0",
|
|
94
|
+
"restart-delay": "5"
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const PROFILES = {
|
|
98
|
+
longrun: {
|
|
99
|
+
timeout: "86400",
|
|
100
|
+
"stall-timeout": "0",
|
|
101
|
+
"startup-timeout": "0",
|
|
102
|
+
"dead-air-timeout": "0",
|
|
103
|
+
cooldown: "15"
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const profileArg = getProfileArg(nikiArgs);
|
|
108
|
+
const D = profileArg && PROFILES[profileArg] ? { ...DEFAULTS, ...PROFILES[profileArg] } : DEFAULTS;
|
|
109
|
+
|
|
110
|
+
|
|
68
111
|
const { values: opts } = parseArgs({
|
|
69
112
|
args: nikiArgs,
|
|
70
113
|
options: {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
'max-
|
|
75
|
-
'
|
|
76
|
-
'
|
|
77
|
-
'
|
|
78
|
-
'
|
|
114
|
+
profile: { type: 'string' },
|
|
115
|
+
budget: { type: 'string', default: D.budget },
|
|
116
|
+
timeout: { type: 'string', default: D.timeout },
|
|
117
|
+
'max-sends': { type: 'string', default: D["max-sends"] },
|
|
118
|
+
'max-tool-calls': { type: 'string', default: D["max-tool-calls"] },
|
|
119
|
+
'stall-timeout': { type: 'string', default: D["stall-timeout"] },
|
|
120
|
+
'startup-timeout': { type: 'string', default: D["startup-timeout"] },
|
|
121
|
+
'dead-air-timeout': { type: 'string', default: D["dead-air-timeout"] },
|
|
122
|
+
'max-nudges': { type: 'string', default: D["max-nudges"] },
|
|
79
123
|
log: { type: 'string' },
|
|
80
124
|
'log-level': { type: 'string', default: 'info' },
|
|
81
125
|
'log-json': { type: 'boolean', default: false },
|
|
82
126
|
state: { type: 'string' },
|
|
83
127
|
metrics: { type: 'string' },
|
|
84
|
-
cooldown: { type: 'string', default:
|
|
128
|
+
cooldown: { type: 'string', default: D.cooldown },
|
|
85
129
|
'abort-file': { type: 'string' },
|
|
86
|
-
'poll-interval': { type: 'string', default:
|
|
130
|
+
'poll-interval': { type: 'string', default: D["poll-interval"] },
|
|
87
131
|
restart: { type: 'boolean', default: false },
|
|
88
|
-
'max-restarts': { type: 'string', default:
|
|
89
|
-
'restart-delay': { type: 'string', default:
|
|
132
|
+
'max-restarts': { type: 'string', default: D["max-restarts"] },
|
|
133
|
+
'restart-delay': { type: 'string', default: D["restart-delay"] },
|
|
134
|
+
'kill-orphaned-mcp': { type: 'boolean', default: false },
|
|
135
|
+
'on-kill': { type: 'string' },
|
|
90
136
|
},
|
|
91
137
|
});
|
|
92
138
|
|
|
@@ -107,6 +153,8 @@ const METRICS_FILE = opts.metrics;
|
|
|
107
153
|
const RESTART = opts.restart;
|
|
108
154
|
const MAX_RESTARTS = parseInt(opts['max-restarts'], 10);
|
|
109
155
|
const RESTART_DELAY_S = parseFloat(opts['restart-delay']);
|
|
156
|
+
const KILL_ORPHANED_MCP = opts['kill-orphaned-mcp'];
|
|
157
|
+
const ON_KILL = opts['on-kill'];
|
|
110
158
|
const LOG_JSON = opts['log-json'];
|
|
111
159
|
|
|
112
160
|
// --- Invocation rate limiting (prevent tight crash loops) ---
|
|
@@ -375,6 +423,35 @@ function checkRateLimits() {
|
|
|
375
423
|
}
|
|
376
424
|
return null;
|
|
377
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
|
+
|
|
378
455
|
// --- Kill logic ---
|
|
379
456
|
|
|
380
457
|
let child = null;
|
|
@@ -401,6 +478,8 @@ function killChild(reason) {
|
|
|
401
478
|
}
|
|
402
479
|
log(`KILL — reason: ${reason} | tokens: ${state.tokensTotal}/${BUDGET} | sends: ${state.sendCallsThisMinute}/min | tools: ${state.toolCallsThisMinute}/min`, 'error');
|
|
403
480
|
|
|
481
|
+
fireOnKill(reason);
|
|
482
|
+
|
|
404
483
|
child.kill('SIGTERM');
|
|
405
484
|
|
|
406
485
|
// Grace period, then SIGKILL
|
|
@@ -701,6 +780,15 @@ function startChild() {
|
|
|
701
780
|
log(`Restart: enabled | max: ${MAX_RESTARTS || 'unlimited'} | delay: ${RESTART_DELAY_S}s ±30% | restarts so far: ${state.restarts}`, 'debug');
|
|
702
781
|
}
|
|
703
782
|
|
|
783
|
+
// If --kill-orphaned-mcp: kill stale agentchat-mcp procs before spawning.
|
|
784
|
+
// Prevents session_displaced churn from orphaned prior-session MCP processes.
|
|
785
|
+
if (KILL_ORPHANED_MCP) {
|
|
786
|
+
try {
|
|
787
|
+
execSync('pkill -f agentchat-mcp', { stdio: 'ignore' });
|
|
788
|
+
log('Killed orphaned agentchat-mcp processes', 'debug');
|
|
789
|
+
} catch { /* pkill exits non-zero when nothing matched */ }
|
|
790
|
+
}
|
|
791
|
+
|
|
704
792
|
child = spawn(childCmd, childArgs, {
|
|
705
793
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
706
794
|
env: process.env,
|