kenaz 0.3.1 → 0.3.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/core/.formation/bifrost/codex-doctor.js +1 -0
- package/core/.formation/bifrost/setup-codex.js +14 -5
- package/core/.formation/bifrost/setup-codex.test.js +20 -0
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/plugins/kenaz/commands/kenaz.md +1 -1
- package/core/plugins/kenaz/scripts/kenaz-mode.js +10 -4
- package/lib/cli.js +14 -1
- package/lib/cli.test.js +11 -0
- package/package.json +6 -6
|
@@ -29,6 +29,7 @@ function skillText(core) {
|
|
|
29
29
|
return '---\nname: "kenaz"\ndescription: "Kenaz mode (off|assist|full|status|init) or Yggdrasil (ygg join|status|leave). Use only when the user invokes $kenaz."\n---\n\n'
|
|
30
30
|
+ 'Run exactly this shell command from the current project directory, appending the words the user wrote after $kenaz (none = print the current mode), and report its output, nothing else:\n\n'
|
|
31
31
|
+ '`node "' + script + '" <words after $kenaz>`\n\n'
|
|
32
|
+
+ '`init` and `ygg ...` talk to kenaz-server on http://127.0.0.1:18080. The sandbox blocks network, so run the command with network access (request escalated permissions); otherwise it reports the server as unreachable even when it is running.\n\n'
|
|
32
33
|
+ 'Modes: full = everything; assist = silent bookkeeping only; off = every Kenaz hook exits immediately.\n';
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -258,17 +258,26 @@ const HOOK_EVENTS = ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionSt
|
|
|
258
258
|
|
|
259
259
|
function probeNativeHooksSupported() {
|
|
260
260
|
if (!isCodexInstalled()) return false;
|
|
261
|
+
const list = () => execSync('codex features list', { stdio: 'pipe', timeout: 8000, windowsHide: true }).toString();
|
|
261
262
|
try {
|
|
262
|
-
const out =
|
|
263
|
-
|
|
263
|
+
const out = list();
|
|
264
|
+
if (/^\s*(?:codex_hooks|hooks)\b.*\btrue\s*$/im.test(out)) return true;
|
|
265
|
+
// codex 0.160 ships `hooks` as a stable feature that is off by default (field report 2026-10-10):
|
|
266
|
+
// turn it on with codex's own command (writes [features] hooks = true), then re-check.
|
|
267
|
+
const off = out.match(/^\s*(codex_hooks|hooks)\b.*\bfalse\s*$/im);
|
|
268
|
+
if (!off) return false;
|
|
269
|
+
execSync(`codex features enable ${off[1]}`, { stdio: 'pipe', timeout: 8000, windowsHide: true });
|
|
270
|
+
return /^\s*(?:codex_hooks|hooks)\b.*\btrue\s*$/im.test(list());
|
|
264
271
|
} catch {
|
|
265
272
|
return false;
|
|
266
273
|
}
|
|
267
274
|
}
|
|
268
275
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
// The shim ships next to this file in whatever core runs setup (repo checkout or ~/.kenaz/core from
|
|
277
|
+
// npm). Resolving from the cwd pointed npm installs at <project>/.formation/... which does not exist.
|
|
278
|
+
// projectRoot is kept in the signature for callers; it no longer changes the result.
|
|
279
|
+
function getHookShimPath(_projectRoot) {
|
|
280
|
+
return path.resolve(__dirname, '..', '..', HOOK_SHIM_REL);
|
|
272
281
|
}
|
|
273
282
|
|
|
274
283
|
function areHooksAlreadyRegistered(projectRoot) {
|
|
@@ -15,6 +15,9 @@ const FAKE = `
|
|
|
15
15
|
const fs=require('fs'),path=require('path');const a=process.argv.slice(2);
|
|
16
16
|
const cfg=path.join(process.env.CODEX_HOME,'config.toml');
|
|
17
17
|
if(a[0]==='--version'){console.log('codex-cli 0.0.0');process.exit(0);}
|
|
18
|
+
const hf=path.join(process.env.CODEX_HOME,'hooks_on');
|
|
19
|
+
if(a[0]==='features'&&a[1]==='list'){console.log('hooks stable '+(fs.existsSync(hf)?'true':'false'));process.exit(0);}
|
|
20
|
+
if(a[0]==='features'&&a[1]==='enable'&&a[2]==='hooks'){if(process.env.FAKE_NO_ENABLE)process.exit(2);fs.writeFileSync(hf,'');process.exit(0);}
|
|
18
21
|
if(a[0]==='mcp'&&a[1]==='remove'){const t=fs.readFileSync(cfg,'utf8').split(/\\r?\\n/);let skip=false;const o=[];
|
|
19
22
|
for(const l of t){if(/^\\s*\\[/.test(l))skip=l.trim().startsWith('[mcp_servers.'+a[2]);if(!skip)o.push(l);}
|
|
20
23
|
fs.writeFileSync(cfg,o.join('\\n'));process.exit(0);}
|
|
@@ -80,3 +83,20 @@ test('setup with the MCP entry already present still repairs the rest (skill ins
|
|
|
80
83
|
assert.strictEqual(s.status, 0, s.stderr);
|
|
81
84
|
assert.ok(fs.existsSync(path.join(f.codexHome, 'skills', 'kenaz', 'SKILL.md')), 'early return on already_registered skipped the $kenaz skill');
|
|
82
85
|
});
|
|
86
|
+
|
|
87
|
+
test('hook shim path comes from the core that runs setup, not the cwd (npm installs run from a project dir)', () => {
|
|
88
|
+
const s = require('./setup-codex');
|
|
89
|
+
const p = s.getHookShimPath(path.join(os.tmpdir(), 'some-project'));
|
|
90
|
+
assert.ok(fs.existsSync(p), p);
|
|
91
|
+
assert.strictEqual(path.dirname(p), path.join(__dirname, 'openai'));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('codex with the stable hooks feature switched off (0.160 default): setup enables it and registers the hooks', () => {
|
|
95
|
+
const f = fixture();
|
|
96
|
+
fs.writeFileSync(f.cfg, '');
|
|
97
|
+
const s = f.run('setup');
|
|
98
|
+
assert.strictEqual(s.status, 0, s.stderr);
|
|
99
|
+
assert.ok(fs.existsSync(path.join(f.codexHome, 'hooks_on')), 'codex features enable hooks was not run');
|
|
100
|
+
const cfg = fs.readFileSync(f.cfg, 'utf8');
|
|
101
|
+
for (const ev of ['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart', 'Stop']) assert.ok(cfg.includes('[[hooks.' + ev + ']]'), ev);
|
|
102
|
+
});
|
package/core/KENAZ_CORE_VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.2
|
|
@@ -9,4 +9,4 @@ Run exactly this and report its output (one line for modes; the full block for `
|
|
|
9
9
|
|
|
10
10
|
Modes: full = guidance + all hooks; assist = silent telemetry/bookkeeping hooks only (no context injection); off = every Kenaz hook exits immediately. init creates .kenaz/project.yaml so hooks act in a fresh project.
|
|
11
11
|
|
|
12
|
-
`ygg join <key> [--project|--global]` stores the Yggdrasil connection key (default: this project's own slot) and activates collaboration here; `ygg status` shows room, node, relay, peers and mail (key masked); `ygg leave` removes this project's key. Needs
|
|
12
|
+
`ygg join <key> [--project|--global]` stores the Yggdrasil connection key (default: this project's own slot) and activates collaboration here; `ygg status` shows room, node, relay, peers and mail (key masked); `ygg leave` removes this project's key. Needs kenaz-server running (installed by `npx kenaz install`).
|
|
@@ -29,10 +29,16 @@ if (MODES.includes(arg)) {
|
|
|
29
29
|
fetch('http://127.0.0.1:' + port + '/api/health', { signal: AbortSignal.timeout(2000) })
|
|
30
30
|
.then((r) => r.ok, () => false)
|
|
31
31
|
.then((up) => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
// Codex runs shell commands in a sandbox without network (it sets CODEX_SANDBOX_NETWORK_DISABLED=1),
|
|
33
|
+
// so 127.0.0.1 is unreachable from here even when kenaz-server is fine (field report, macOS Codex).
|
|
34
|
+
const codex = !!(process.env.CODEX_SANDBOX_NETWORK_DISABLED || process.env.CODEX_THREAD_ID || process.env.CODEX_SANDBOX);
|
|
35
|
+
const cmd = codex ? '$kenaz' : '/kenaz:kenaz';
|
|
36
|
+
const down = codex && process.env.CODEX_SANDBOX_NETWORK_DISABLED
|
|
37
|
+
? 'not reachable from the Codex sandbox (no network). Check from a normal terminal: npx kenaz doctor'
|
|
38
|
+
: 'NOT reachable on port ' + port + ' (check: npx kenaz doctor; reinstall: npx kenaz@latest install)';
|
|
39
|
+
console.log('kenaz-server: ' + (up ? 'running on port ' + port : down));
|
|
40
|
+
console.log('Optional: ' + cmd + ' ygg join <key> multi-device collaboration (Yggdrasil)');
|
|
41
|
+
console.log('Next step: ' + (codex ? 'ask Codex to start Ansuz for this project' : '/kenaz:Ansuz'));
|
|
36
42
|
});
|
|
37
43
|
} else {
|
|
38
44
|
const env = process.env.KENAZ_MODE ? ' (env KENAZ_MODE overrides files)' : '';
|
package/lib/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ const dash = require('./dashboard');
|
|
|
14
14
|
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
15
15
|
const PKG = require('../package.json');
|
|
16
16
|
const MIN_NODE = 18;
|
|
17
|
-
const USAGE = 'usage: kenaz <install|uninstall|status|doctor> [--dry-run] [--home <dir>] [--no-service] [--no-claude] [--no-codex] [--keep-dashboard] [--server-bin <path>]';
|
|
17
|
+
const USAGE = 'usage: kenaz <install|uninstall|status|doctor|init|ygg join <key>|ygg status|ygg leave> [--dry-run] [--home <dir>] [--no-service] [--no-claude] [--no-codex] [--keep-dashboard] [--server-bin <path>]';
|
|
18
18
|
|
|
19
19
|
function parseArgs(argv) {
|
|
20
20
|
const o = { cmd: argv[0], flags: {} };
|
|
@@ -226,7 +226,20 @@ async function doctor(p, flags, out, deps = {}) {
|
|
|
226
226
|
return rows.some((r) => r[1] === 'FAIL') ? 1 : 0;
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// `kenaz ygg join|status|leave ...` and `kenaz init`: short forms of the same core scripts the
|
|
230
|
+
// /kenaz (Claude) and $kenaz (Codex) commands run, from a normal terminal in the project folder.
|
|
231
|
+
function passthrough(argv, deps) {
|
|
232
|
+
const p = paths(os.homedir());
|
|
233
|
+
const core = fs.existsSync(path.join(p.core, 'plugins', 'kenaz')) ? p.core : path.join(PKG_ROOT, 'core');
|
|
234
|
+
const script = argv[0] === 'ygg' ? 'ygg.js' : 'kenaz-mode.js';
|
|
235
|
+
const args = argv[0] === 'ygg' ? argv.slice(1) : argv;
|
|
236
|
+
const spawn = deps.spawnSync || require('child_process').spawnSync;
|
|
237
|
+
const r = spawn(process.execPath, [path.join(core, 'plugins', 'kenaz', 'scripts', script), ...args], { stdio: 'inherit', cwd: process.cwd() });
|
|
238
|
+
return r.status === null ? 1 : r.status;
|
|
239
|
+
}
|
|
240
|
+
|
|
229
241
|
function run(argv, out = console.log, deps = {}) {
|
|
242
|
+
if (argv[0] === 'ygg' || argv[0] === 'init') return passthrough(argv, deps);
|
|
230
243
|
const { cmd, flags } = parseArgs(argv);
|
|
231
244
|
const fn = { install, uninstall, status, doctor }[cmd];
|
|
232
245
|
if (!fn) { out(USAGE); return 2; }
|
package/lib/cli.test.js
CHANGED
|
@@ -46,3 +46,14 @@ test('setup-codex uninstall strips only kenaz shim hook blocks and keeps user ho
|
|
|
46
46
|
fs.rmSync(codexHome, { recursive: true, force: true });
|
|
47
47
|
}
|
|
48
48
|
});
|
|
49
|
+
|
|
50
|
+
test('kenaz ygg / init pass through to the core scripts with the remaining args', () => {
|
|
51
|
+
const calls = [];
|
|
52
|
+
const spawnSync = (exe, args) => { calls.push(args); return { status: 0 }; };
|
|
53
|
+
assert.strictEqual(run(['ygg', 'join', 'KEY', '--global'], () => {}, { spawnSync }), 0);
|
|
54
|
+
assert.strictEqual(run(['init'], () => {}, { spawnSync }), 0);
|
|
55
|
+
assert.match(calls[0][0].split(path.sep).join('/'), /plugins\/kenaz\/scripts\/ygg\.js$/);
|
|
56
|
+
assert.deepStrictEqual(calls[0].slice(1), ['join', 'KEY', '--global']);
|
|
57
|
+
assert.match(calls[1][0].split(path.sep).join('/'), /plugins\/kenaz\/scripts\/kenaz-mode\.js$/);
|
|
58
|
+
assert.deepStrictEqual(calls[1].slice(1), ['init']);
|
|
59
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Install Kenaz (plugin, hooks, kenaz-server) for Claude Code and Codex: npx kenaz install",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kenaz": "bin/kenaz.js"
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"test": "node --test lib/cli.test.js"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@kenazai/server-darwin-arm64": "0.3.
|
|
24
|
-
"@kenazai/server-darwin-x64": "0.3.
|
|
25
|
-
"@kenazai/server-linux-arm64": "0.3.
|
|
26
|
-
"@kenazai/server-linux-x64": "0.3.
|
|
27
|
-
"@kenazai/server-win32-x64": "0.3.
|
|
23
|
+
"@kenazai/server-darwin-arm64": "0.3.2",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.2",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.2",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.2",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.2"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED"
|
|
30
30
|
}
|