@crewx/cli 0.9.0-rc.145 → 0.9.0-rc.147
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/dist/bootstrap/codex-writable-roots.js +1 -113
- package/dist/bootstrap/crewx-cli.js +1 -84
- package/dist/builtin.js +5 -132
- package/dist/commands/agent.js +4 -118
- package/dist/commands/cal.js +2 -44
- package/dist/commands/db.js +4 -602
- package/dist/commands/detached.js +15 -620
- package/dist/commands/doctor.js +4 -649
- package/dist/commands/emit-trailer.js +2 -33
- package/dist/commands/execute.js +15 -294
- package/dist/commands/hook/command-marker.js +1 -5
- package/dist/commands/hook/install.js +8 -189
- package/dist/commands/hook/paths.js +1 -94
- package/dist/commands/hook/status.js +1 -86
- package/dist/commands/hook/uninstall.js +1 -71
- package/dist/commands/hook-dispatch.js +2 -180
- package/dist/commands/init.js +31 -437
- package/dist/commands/kill.js +1 -11
- package/dist/commands/log.js +3 -96
- package/dist/commands/parse-agent-message.js +1 -52
- package/dist/commands/parse-common-flags.js +3 -212
- package/dist/commands/ps.js +2 -117
- package/dist/commands/publish.js +7 -283
- package/dist/commands/query.js +16 -292
- package/dist/commands/registry.js +1 -33
- package/dist/commands/resolve-prompt.js +3 -76
- package/dist/commands/resolve-recipient.js +3 -47
- package/dist/commands/restart.js +18 -172
- package/dist/commands/result.js +1 -209
- package/dist/commands/shortcut.js +4 -253
- package/dist/commands/slack.js +16 -560
- package/dist/commands/stop.js +8 -119
- package/dist/commands/write-output.js +1 -24
- package/dist/index.js +1 -8
- package/dist/logging.js +10 -87
- package/dist/main.js +6 -289
- package/dist/register-builtin-tools.js +1 -9
- package/dist/slack/file-download.js +1 -134
- package/dist/slack/markdown.js +1 -33
- package/dist/utils/env-defaults.js +1 -15
- package/dist/utils/inherited-trace.js +1 -65
- package/dist/utils/product-package-root.js +1 -39
- package/dist/utils/sdk-compat.js +1 -120
- package/dist/utils/version.js +1 -28
- package/package.json +15 -15
|
@@ -1,94 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Shared path helpers for hook CLI commands.
|
|
4
|
-
*
|
|
5
|
-
* Project root = nearest ancestor containing crewx.yaml.
|
|
6
|
-
* Claude settings path = <projectRoot>/.claude/settings.json
|
|
7
|
-
* Codex hooks path = <projectRoot>/.codex/hooks.json
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.parseProviderArg = parseProviderArg;
|
|
11
|
-
exports.providersFromFilter = providersFromFilter;
|
|
12
|
-
exports.findProjectRoot = findProjectRoot;
|
|
13
|
-
exports.getClaudeSettingsPath = getClaudeSettingsPath;
|
|
14
|
-
exports.getCodexHooksPath = getCodexHooksPath;
|
|
15
|
-
exports.getProviderSettingsPath = getProviderSettingsPath;
|
|
16
|
-
exports.readSettings = readSettings;
|
|
17
|
-
exports.ensureClaudeSettings = ensureClaudeSettings;
|
|
18
|
-
exports.ensureCodexHooks = ensureCodexHooks;
|
|
19
|
-
exports.ensureProviderSettings = ensureProviderSettings;
|
|
20
|
-
const fs_1 = require("fs");
|
|
21
|
-
const path_1 = require("path");
|
|
22
|
-
function parseProviderArg(args) {
|
|
23
|
-
const idx = args.indexOf('--provider');
|
|
24
|
-
if (idx === -1 || idx + 1 >= args.length)
|
|
25
|
-
return 'all';
|
|
26
|
-
const val = args[idx + 1];
|
|
27
|
-
if (val === 'claude' || val === 'codex')
|
|
28
|
-
return val;
|
|
29
|
-
console.error(`[crewx] Invalid --provider value: "${val}". Expected: claude | codex`);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
function providersFromFilter(filter) {
|
|
33
|
-
return filter === 'all' ? ['claude', 'codex'] : [filter];
|
|
34
|
-
}
|
|
35
|
-
function findProjectRoot(cwd) {
|
|
36
|
-
let dir = (0, path_1.resolve)(cwd);
|
|
37
|
-
for (;;) {
|
|
38
|
-
if ((0, fs_1.existsSync)((0, path_1.resolve)(dir, 'crewx.yaml')))
|
|
39
|
-
return dir;
|
|
40
|
-
const parent = (0, path_1.dirname)(dir);
|
|
41
|
-
if (parent === dir)
|
|
42
|
-
break;
|
|
43
|
-
dir = parent;
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
function getClaudeSettingsPath(projectRoot) {
|
|
48
|
-
return (0, path_1.resolve)(projectRoot, '.claude', 'settings.json');
|
|
49
|
-
}
|
|
50
|
-
function getCodexHooksPath(projectRoot) {
|
|
51
|
-
return (0, path_1.resolve)(projectRoot, '.codex', 'hooks.json');
|
|
52
|
-
}
|
|
53
|
-
function getProviderSettingsPath(projectRoot, provider) {
|
|
54
|
-
return provider === 'claude'
|
|
55
|
-
? getClaudeSettingsPath(projectRoot)
|
|
56
|
-
: getCodexHooksPath(projectRoot);
|
|
57
|
-
}
|
|
58
|
-
function readSettings(settingsPath) {
|
|
59
|
-
if (!(0, fs_1.existsSync)(settingsPath))
|
|
60
|
-
return {};
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf8'));
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return {};
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function ensureClaudeSettings(projectRoot) {
|
|
69
|
-
const settingsPath = getClaudeSettingsPath(projectRoot);
|
|
70
|
-
const settingsDir = (0, path_1.dirname)(settingsPath);
|
|
71
|
-
if (!(0, fs_1.existsSync)(settingsDir)) {
|
|
72
|
-
(0, fs_1.mkdirSync)(settingsDir, { recursive: true });
|
|
73
|
-
}
|
|
74
|
-
if (!(0, fs_1.existsSync)(settingsPath)) {
|
|
75
|
-
(0, fs_1.writeFileSync)(settingsPath, '{}\n', 'utf8');
|
|
76
|
-
}
|
|
77
|
-
return settingsPath;
|
|
78
|
-
}
|
|
79
|
-
function ensureCodexHooks(projectRoot) {
|
|
80
|
-
const hooksPath = getCodexHooksPath(projectRoot);
|
|
81
|
-
const hooksDir = (0, path_1.dirname)(hooksPath);
|
|
82
|
-
if (!(0, fs_1.existsSync)(hooksDir)) {
|
|
83
|
-
(0, fs_1.mkdirSync)(hooksDir, { recursive: true });
|
|
84
|
-
}
|
|
85
|
-
if (!(0, fs_1.existsSync)(hooksPath)) {
|
|
86
|
-
(0, fs_1.writeFileSync)(hooksPath, '{}\n', 'utf8');
|
|
87
|
-
}
|
|
88
|
-
return hooksPath;
|
|
89
|
-
}
|
|
90
|
-
function ensureProviderSettings(projectRoot, provider) {
|
|
91
|
-
return provider === 'claude'
|
|
92
|
-
? ensureClaudeSettings(projectRoot)
|
|
93
|
-
: ensureCodexHooks(projectRoot);
|
|
94
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.parseProviderArg=d;exports.providersFromFilter=a;exports.findProjectRoot=l;exports.getClaudeSettingsPath=o;exports.getCodexHooksPath=s;exports.getProviderSettingsPath=f;exports.readSettings=S;exports.ensureClaudeSettings=u;exports.ensureCodexHooks=c;exports.ensureProviderSettings=v;const n=require("fs");const i=require("path");function d(e){const r=e.indexOf("--provider");if(r===-1||r+1>=e.length)return"all";const t=e[r+1];if(t==="claude"||t==="codex")return t;console.error(`[crewx] Invalid --provider value: "${t}". Expected: claude | codex`);process.exit(1)}function a(e){return e==="all"?["claude","codex"]:[e]}function l(e){let r=(0,i.resolve)(e);for(;;){if((0,n.existsSync)((0,i.resolve)(r,"crewx.yaml")))return r;const t=(0,i.dirname)(r);if(t===r)break;r=t}return null}function o(e){return(0,i.resolve)(e,".claude","settings.json")}function s(e){return(0,i.resolve)(e,".codex","hooks.json")}function f(e,r){return r==="claude"?o(e):s(e)}function S(e){if(!(0,n.existsSync)(e))return{};try{return JSON.parse((0,n.readFileSync)(e,"utf8"))}catch{return{}}}function u(e){const r=o(e);const t=(0,i.dirname)(r);if(!(0,n.existsSync)(t)){(0,n.mkdirSync)(t,{recursive:true})}if(!(0,n.existsSync)(r)){(0,n.writeFileSync)(r,"{}\n","utf8")}return r}function c(e){const r=s(e);const t=(0,i.dirname)(r);if(!(0,n.existsSync)(t)){(0,n.mkdirSync)(t,{recursive:true})}if(!(0,n.existsSync)(r)){(0,n.writeFileSync)(r,"{}\n","utf8")}return r}function v(e,r){return r==="claude"?u(e):c(e)}
|
|
@@ -1,86 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* crewx hook status — Show hook installation status and registered plugins.
|
|
4
|
-
*
|
|
5
|
-
* Displays status for both Claude (.claude/settings.json) and
|
|
6
|
-
* Codex (.codex/hooks.json) provider settings.
|
|
7
|
-
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.handleHookStatus = handleHookStatus;
|
|
10
|
-
const fs_1 = require("fs");
|
|
11
|
-
const path_1 = require("path");
|
|
12
|
-
const sdk_1 = require("@crewx/sdk");
|
|
13
|
-
const paths_1 = require("./paths");
|
|
14
|
-
const command_marker_1 = require("./command-marker");
|
|
15
|
-
function showProviderStatus(projectRoot, provider) {
|
|
16
|
-
const settingsPath = provider === 'claude'
|
|
17
|
-
? (0, paths_1.getClaudeSettingsPath)(projectRoot)
|
|
18
|
-
: (0, paths_1.getCodexHooksPath)(projectRoot);
|
|
19
|
-
console.log(` [${provider.toUpperCase()}]`);
|
|
20
|
-
if (!(0, fs_1.existsSync)(settingsPath)) {
|
|
21
|
-
console.log(` Settings: not found (${settingsPath})`);
|
|
22
|
-
console.log(` Status: NOT INSTALLED`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
console.log(` Settings: ${settingsPath}`);
|
|
26
|
-
try {
|
|
27
|
-
const settings = JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf8'));
|
|
28
|
-
const preHooks = settings.hooks?.PreToolUse ?? [];
|
|
29
|
-
const crewxEntry = preHooks.find((entry) => entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? '')));
|
|
30
|
-
if (crewxEntry) {
|
|
31
|
-
const command = crewxEntry.hooks.find((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command ?? ''))?.command;
|
|
32
|
-
console.log(` Status: INSTALLED`);
|
|
33
|
-
console.log(` Command: ${command}`);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
console.log(` Status: NOT INSTALLED`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
console.log(` Status: ERROR (failed to parse settings)`);
|
|
41
|
-
}
|
|
42
|
-
const backupPath = settingsPath + '.crewx-backup';
|
|
43
|
-
if ((0, fs_1.existsSync)(backupPath)) {
|
|
44
|
-
console.log(` Backup: ${backupPath}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
async function handleHookStatus(_args) {
|
|
48
|
-
const cwd = process.cwd();
|
|
49
|
-
const projectRoot = (0, paths_1.findProjectRoot)(cwd);
|
|
50
|
-
if (!projectRoot) {
|
|
51
|
-
console.error('[crewx] No crewx.yaml found in current or ancestor directories.');
|
|
52
|
-
console.error('[crewx] Run this command from within a CrewX project.');
|
|
53
|
-
process.exitCode = 1;
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
console.log('Hook Installation Status');
|
|
57
|
-
console.log('─'.repeat(40));
|
|
58
|
-
showProviderStatus(projectRoot, 'claude');
|
|
59
|
-
showProviderStatus(projectRoot, 'codex');
|
|
60
|
-
const configPath = (0, path_1.resolve)(projectRoot, 'crewx.yaml');
|
|
61
|
-
console.log('');
|
|
62
|
-
console.log('Registered HookPlugins (crewx.yaml)');
|
|
63
|
-
console.log('─'.repeat(40));
|
|
64
|
-
console.log(` Config: ${configPath}`);
|
|
65
|
-
let config;
|
|
66
|
-
try {
|
|
67
|
-
config = (0, sdk_1.loadYamlFile)(configPath);
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
console.log(' ERROR: failed to parse crewx.yaml');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const hookDefs = Array.isArray(config.hooks) ? config.hooks : [];
|
|
74
|
-
if (hookDefs.length === 0) {
|
|
75
|
-
console.log(' No hooks defined in crewx.yaml');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
for (const hook of hookDefs) {
|
|
79
|
-
console.log(` - provider: ${hook.provider ?? '(any)'}`);
|
|
80
|
-
console.log(` event: ${hook.event ?? 'PreToolUse'}`);
|
|
81
|
-
console.log(` plugin: ${hook.plugin ?? '(undefined)'}`);
|
|
82
|
-
if (hook.agents) {
|
|
83
|
-
console.log(` agents: ${JSON.stringify(hook.agents)} (reserved — Phase 1)`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.handleHookStatus=m;const i=require("fs");const f=require("path");const h=require("@crewx/sdk");const g=require("./paths");const d=require("./command-marker");function u(r,n){const o=n==="claude"?(0,g.getClaudeSettingsPath)(r):(0,g.getCodexHooksPath)(r);console.log(` [${n.toUpperCase()}]`);if(!(0,i.existsSync)(o)){console.log(` Settings: not found (${o})`);console.log(` Status: NOT INSTALLED`);return}console.log(` Settings: ${o}`);try{const t=JSON.parse((0,i.readFileSync)(o,"utf8"));const c=t.hooks?.PreToolUse??[];const e=c.find(l=>l.hooks?.some(a=>d.CREWX_HOOK_COMMAND_RE.test(a.command??"")));if(e){const l=e.hooks.find(a=>d.CREWX_HOOK_COMMAND_RE.test(a.command??""))?.command;console.log(` Status: INSTALLED`);console.log(` Command: ${l}`)}else{console.log(` Status: NOT INSTALLED`)}}catch{console.log(` Status: ERROR (failed to parse settings)`)}const s=o+".crewx-backup";if((0,i.existsSync)(s)){console.log(` Backup: ${s}`)}}async function m(r){const n=process.cwd();const o=(0,g.findProjectRoot)(n);if(!o){console.error("[crewx] No crewx.yaml found in current or ancestor directories.");console.error("[crewx] Run this command from within a CrewX project.");process.exitCode=1;return}console.log("Hook Installation Status");console.log("\u2500".repeat(40));u(o,"claude");u(o,"codex");const s=(0,f.resolve)(o,"crewx.yaml");console.log("");console.log("Registered HookPlugins (crewx.yaml)");console.log("\u2500".repeat(40));console.log(` Config: ${s}`);let t;try{t=(0,h.loadYamlFile)(s)}catch{console.log(" ERROR: failed to parse crewx.yaml");return}const c=Array.isArray(t.hooks)?t.hooks:[];if(c.length===0){console.log(" No hooks defined in crewx.yaml");return}for(const e of c){console.log(` - provider: ${e.provider??"(any)"}`);console.log(` event: ${e.event??"PreToolUse"}`);console.log(` plugin: ${e.plugin??"(undefined)"}`);if(e.agents){console.log(` agents: ${JSON.stringify(e.agents)} (reserved \u2014 Phase 1)`)}}}
|
|
@@ -1,71 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* crewx hook uninstall — Remove crewx-hook-dispatch from provider settings files.
|
|
4
|
-
*
|
|
5
|
-
* Only removes entries whose command contains the crewx hook-dispatch marker.
|
|
6
|
-
* Preserves all other user hooks. Cleans up empty PreToolUse/hooks fields.
|
|
7
|
-
* Never traverses parent directories — project root determined by crewx.yaml.
|
|
8
|
-
*/
|
|
9
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.handleHookUninstall = handleHookUninstall;
|
|
11
|
-
const fs_1 = require("fs");
|
|
12
|
-
const paths_1 = require("./paths");
|
|
13
|
-
const command_marker_1 = require("./command-marker");
|
|
14
|
-
function getSettingsPath(projectRoot, provider) {
|
|
15
|
-
return provider === 'claude'
|
|
16
|
-
? (0, paths_1.getClaudeSettingsPath)(projectRoot)
|
|
17
|
-
: (0, paths_1.getCodexHooksPath)(projectRoot);
|
|
18
|
-
}
|
|
19
|
-
function uninstallFromProvider(projectRoot, provider) {
|
|
20
|
-
const settingsPath = getSettingsPath(projectRoot, provider);
|
|
21
|
-
if (!(0, fs_1.existsSync)(settingsPath)) {
|
|
22
|
-
console.log(`[crewx] No settings file found for ${provider} (${settingsPath})`);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
let settings;
|
|
26
|
-
try {
|
|
27
|
-
settings = JSON.parse((0, fs_1.readFileSync)(settingsPath, 'utf8'));
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
console.error(`[crewx] Failed to parse ${settingsPath}`);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (!settings.hooks?.PreToolUse?.length) {
|
|
34
|
-
console.log(`[crewx] No PreToolUse hooks found for ${provider}`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const before = settings.hooks.PreToolUse.length;
|
|
38
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => !entry.hooks?.some((h) => command_marker_1.CREWX_HOOK_COMMAND_RE.test(h.command)));
|
|
39
|
-
const removed = settings.hooks.PreToolUse.length < before;
|
|
40
|
-
if (settings.hooks.PreToolUse.length === 0) {
|
|
41
|
-
delete settings.hooks.PreToolUse;
|
|
42
|
-
}
|
|
43
|
-
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
44
|
-
delete settings.hooks;
|
|
45
|
-
}
|
|
46
|
-
if (!removed) {
|
|
47
|
-
console.log(`[crewx] crewx hook not found for ${provider}`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
(0, fs_1.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
51
|
-
console.log(`[crewx] Hook uninstalled (${provider})`);
|
|
52
|
-
const backupPath = settingsPath + '.crewx-backup';
|
|
53
|
-
if ((0, fs_1.existsSync)(backupPath)) {
|
|
54
|
-
console.log(`[crewx] Backup available: ${backupPath}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
async function handleHookUninstall(args) {
|
|
58
|
-
const cwd = process.cwd();
|
|
59
|
-
const projectRoot = (0, paths_1.findProjectRoot)(cwd);
|
|
60
|
-
if (!projectRoot) {
|
|
61
|
-
console.error('[crewx] No crewx.yaml found in current or ancestor directories.');
|
|
62
|
-
console.error('[crewx] Run this command from within a CrewX project.');
|
|
63
|
-
process.exitCode = 1;
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const providerFilter = (0, paths_1.parseProviderArg)(args);
|
|
67
|
-
const providers = (0, paths_1.providersFromFilter)(providerFilter);
|
|
68
|
-
for (const provider of providers) {
|
|
69
|
-
uninstallFromProvider(projectRoot, provider);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.handleHookUninstall=k;const n=require("fs");const s=require("./paths");const f=require("./command-marker");function h(t,e){return e==="claude"?(0,s.getClaudeSettingsPath)(t):(0,s.getCodexHooksPath)(t)}function d(t,e){const r=h(t,e);if(!(0,n.existsSync)(r)){console.log(`[crewx] No settings file found for ${e} (${r})`);return}let o;try{o=JSON.parse((0,n.readFileSync)(r,"utf8"))}catch{console.error(`[crewx] Failed to parse ${r}`);return}if(!o.hooks?.PreToolUse?.length){console.log(`[crewx] No PreToolUse hooks found for ${e}`);return}const c=o.hooks.PreToolUse.length;o.hooks.PreToolUse=o.hooks.PreToolUse.filter(a=>!a.hooks?.some(u=>f.CREWX_HOOK_COMMAND_RE.test(u.command)));const l=o.hooks.PreToolUse.length<c;if(o.hooks.PreToolUse.length===0){delete o.hooks.PreToolUse}if(o.hooks&&Object.keys(o.hooks).length===0){delete o.hooks}if(!l){console.log(`[crewx] crewx hook not found for ${e}`);return}(0,n.writeFileSync)(r,JSON.stringify(o,null,2)+"\n","utf8");console.log(`[crewx] Hook uninstalled (${e})`);const i=r+".crewx-backup";if((0,n.existsSync)(i)){console.log(`[crewx] Backup available: ${i}`)}}async function k(t){const e=process.cwd();const r=(0,s.findProjectRoot)(e);if(!r){console.error("[crewx] No crewx.yaml found in current or ancestor directories.");console.error("[crewx] Run this command from within a CrewX project.");process.exitCode=1;return}const o=(0,s.parseProviderArg)(t);const c=(0,s.providersFromFilter)(o);for(const l of c){d(r,l)}}
|
|
@@ -1,180 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
* crewx hook-dispatch — CLI subcommand invoked by Claude/Codex PreToolUse hook.
|
|
4
|
-
*
|
|
5
|
-
* Reads stdin JSON from the provider, loads HookPlugins from crewx.yaml,
|
|
6
|
-
* evaluates them, and writes stdout JSON response back.
|
|
7
|
-
*
|
|
8
|
-
* Protocol:
|
|
9
|
-
* stdin → ClaudeHookInput JSON
|
|
10
|
-
* stdout → ClaudeHookOutput JSON
|
|
11
|
-
* exit 0 → allow, exit 2 → deny
|
|
12
|
-
*
|
|
13
|
-
* Security (M1):
|
|
14
|
-
* --provider argument is REQUIRED. Missing → process.exit(1) fail-closed.
|
|
15
|
-
*/
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.handleHookDispatch = handleHookDispatch;
|
|
18
|
-
const fs_1 = require("fs");
|
|
19
|
-
const path_1 = require("path");
|
|
20
|
-
const sdk_1 = require("@crewx/sdk");
|
|
21
|
-
const hooks_1 = require("@crewx/sdk/hooks");
|
|
22
|
-
function readStdin() {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
let data = '';
|
|
25
|
-
process.stdin.setEncoding('utf8');
|
|
26
|
-
process.stdin.on('data', (chunk) => { data += chunk; });
|
|
27
|
-
process.stdin.on('end', () => resolve(data));
|
|
28
|
-
process.stdin.on('error', reject);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function loadPlugin(pluginPath) {
|
|
32
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
|
-
const mod = require(pluginPath);
|
|
34
|
-
const PluginClass = mod.default ?? mod;
|
|
35
|
-
if (typeof PluginClass !== 'function') {
|
|
36
|
-
throw new Error(`Plugin module does not export a class: ${pluginPath}`);
|
|
37
|
-
}
|
|
38
|
-
const instance = new PluginClass();
|
|
39
|
-
if (typeof instance.run !== 'function') {
|
|
40
|
-
throw new Error(`Plugin does not have a run() method: ${pluginPath}`);
|
|
41
|
-
}
|
|
42
|
-
return instance;
|
|
43
|
-
}
|
|
44
|
-
function findConfigPath(cwd) {
|
|
45
|
-
let dir = cwd;
|
|
46
|
-
for (let i = 0; i < 20; i++) {
|
|
47
|
-
const candidate = (0, path_1.resolve)(dir, 'crewx.yaml');
|
|
48
|
-
try {
|
|
49
|
-
(0, fs_1.readFileSync)(candidate, 'utf8');
|
|
50
|
-
return candidate;
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
const parent = (0, path_1.resolve)(dir, '..');
|
|
54
|
-
if (parent === dir)
|
|
55
|
-
break;
|
|
56
|
-
dir = parent;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
function parseProviderFromArgs(args) {
|
|
62
|
-
const idx = args.indexOf('--provider');
|
|
63
|
-
if (idx === -1 || idx + 1 >= args.length) {
|
|
64
|
-
console.error('[crewx] Missing required --provider argument. Usage: hook-dispatch --provider <claude|codex>');
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
const val = args[idx + 1];
|
|
68
|
-
if (val !== 'claude' && val !== 'codex') {
|
|
69
|
-
console.error(`[crewx] Invalid --provider value: "${val}". Expected: claude | codex`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
return val;
|
|
73
|
-
}
|
|
74
|
-
async function handleHookDispatch(args) {
|
|
75
|
-
const provider = parseProviderFromArgs(args);
|
|
76
|
-
const stdinData = await readStdin();
|
|
77
|
-
let input;
|
|
78
|
-
try {
|
|
79
|
-
input = JSON.parse(stdinData);
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
console.error('[crewx] Failed to parse stdin JSON');
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
const configPath = findConfigPath(input.cwd || process.cwd());
|
|
86
|
-
if (!configPath) {
|
|
87
|
-
const output = {
|
|
88
|
-
hookSpecificOutput: {
|
|
89
|
-
hookEventName: input.hook_event_name,
|
|
90
|
-
permissionDecision: 'allow',
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
process.stdout.write(JSON.stringify(output));
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
const configRoot = (0, path_1.dirname)(configPath);
|
|
97
|
-
let config;
|
|
98
|
-
try {
|
|
99
|
-
config = (0, sdk_1.loadYamlFile)(configPath);
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
console.error(`[crewx] Failed to load crewx.yaml: ${configPath}`);
|
|
103
|
-
process.exit(1);
|
|
104
|
-
}
|
|
105
|
-
const hookDefs = Array.isArray(config.hooks) ? config.hooks : [];
|
|
106
|
-
const matchingDefs = hookDefs.filter((h) => {
|
|
107
|
-
const prov = h.provider;
|
|
108
|
-
if (prov === undefined)
|
|
109
|
-
return true;
|
|
110
|
-
if (typeof prov === 'string')
|
|
111
|
-
return prov === provider;
|
|
112
|
-
if (Array.isArray(prov))
|
|
113
|
-
return prov.includes(provider);
|
|
114
|
-
return false;
|
|
115
|
-
}).filter((h) => h.event === 'PreToolUse' || !h.event);
|
|
116
|
-
if (matchingDefs.length === 0) {
|
|
117
|
-
const output = {
|
|
118
|
-
hookSpecificOutput: {
|
|
119
|
-
hookEventName: input.hook_event_name,
|
|
120
|
-
permissionDecision: 'allow',
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
process.stdout.write(JSON.stringify(output));
|
|
124
|
-
process.exit(0);
|
|
125
|
-
}
|
|
126
|
-
const guideDefs = matchingDefs.filter((h) => h.guide && typeof h.guide === 'object');
|
|
127
|
-
const pluginDefs = matchingDefs.filter((h) => h.plugin && typeof h.plugin === 'string');
|
|
128
|
-
if (guideDefs.length > 0) {
|
|
129
|
-
const { valid, warnings } = (0, hooks_1.validateHooksSchema)(guideDefs);
|
|
130
|
-
for (const w of warnings) {
|
|
131
|
-
process.stderr.write(`[crewx] ${w}\n`);
|
|
132
|
-
}
|
|
133
|
-
if (valid.length > 0) {
|
|
134
|
-
const agents = Array.isArray(config.agents) ? config.agents : [];
|
|
135
|
-
const yamlPlugin = new hooks_1.YamlHookPlugin({ hooks: valid, agents });
|
|
136
|
-
const result = await (0, hooks_1.evaluateHook)(yamlPlugin, input, configRoot, provider);
|
|
137
|
-
if (result.exitCode !== 0) {
|
|
138
|
-
process.stdout.write(JSON.stringify(result.stdout));
|
|
139
|
-
process.exit(result.exitCode);
|
|
140
|
-
}
|
|
141
|
-
if (result.injected) {
|
|
142
|
-
process.stdout.write(JSON.stringify(result.stdout));
|
|
143
|
-
process.exit(0);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
for (const hookDef of pluginDefs) {
|
|
148
|
-
const pluginRelPath = hookDef.plugin;
|
|
149
|
-
if (!pluginRelPath) {
|
|
150
|
-
console.error(`[crewx] Hook definition missing 'plugin' field`);
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const pluginPath = (0, path_1.resolve)(configRoot, pluginRelPath);
|
|
154
|
-
if (!(0, hooks_1.isPathSafe)(pluginPath, configRoot)) {
|
|
155
|
-
console.error(`[crewx] Hook plugin path rejected (path traversal): ${pluginRelPath}`);
|
|
156
|
-
process.exit(1);
|
|
157
|
-
}
|
|
158
|
-
let plugin;
|
|
159
|
-
try {
|
|
160
|
-
plugin = loadPlugin(pluginPath);
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
console.error(`[crewx] Failed to load hook plugin: ${pluginRelPath} — ${err instanceof Error ? err.message : String(err)}`);
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
const result = await (0, hooks_1.evaluateHook)(plugin, input, configRoot, provider);
|
|
167
|
-
if (result.exitCode !== 0) {
|
|
168
|
-
process.stdout.write(JSON.stringify(result.stdout));
|
|
169
|
-
process.exit(result.exitCode);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const output = {
|
|
173
|
-
hookSpecificOutput: {
|
|
174
|
-
hookEventName: input.hook_event_name,
|
|
175
|
-
permissionDecision: 'allow',
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
process.stdout.write(JSON.stringify(output));
|
|
179
|
-
process.exit(0);
|
|
180
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.handleHookDispatch=D;const y=require("fs");const f=require("path");const m=require("@crewx/sdk");const u=require("@crewx/sdk/hooks");function S(){return new Promise((s,o)=>{let t="";process.stdin.setEncoding("utf8");process.stdin.on("data",r=>{t+=r});process.stdin.on("end",()=>s(t));process.stdin.on("error",o)})}function O(s){const o=require(s);const t=o.default??o;if(typeof t!=="function"){throw new Error(`Plugin module does not export a class: ${s}`)}const r=new t;if(typeof r.run!=="function"){throw new Error(`Plugin does not have a run() method: ${s}`)}return r}function P(s){let o=s;for(let t=0;t<20;t++){const r=(0,f.resolve)(o,"crewx.yaml");try{(0,y.readFileSync)(r,"utf8");return r}catch{const c=(0,f.resolve)(o,"..");if(c===o)break;o=c}}return null}function _(s){const o=s.indexOf("--provider");if(o===-1||o+1>=s.length){console.error("[crewx] Missing required --provider argument. Usage: hook-dispatch --provider <claude|codex>");process.exit(1)}const t=s[o+1];if(t!=="claude"&&t!=="codex"){console.error(`[crewx] Invalid --provider value: "${t}". Expected: claude | codex`);process.exit(1)}return t}async function D(s){const o=_(s);const t=await S();let r;try{r=JSON.parse(t)}catch{console.error("[crewx] Failed to parse stdin JSON");process.exit(1)}const c=P(r.cwd||process.cwd());if(!c){const e={hookSpecificOutput:{hookEventName:r.hook_event_name,permissionDecision:"allow"}};process.stdout.write(JSON.stringify(e));process.exit(0)}const d=(0,f.dirname)(c);let l;try{l=(0,m.loadYamlFile)(c)}catch{console.error(`[crewx] Failed to load crewx.yaml: ${c}`);process.exit(1)}const x=Array.isArray(l.hooks)?l.hooks:[];const g=x.filter(e=>{const i=e.provider;if(i===void 0)return true;if(typeof i==="string")return i===o;if(Array.isArray(i))return i.includes(o);return false}).filter(e=>e.event==="PreToolUse"||!e.event);if(g.length===0){const e={hookSpecificOutput:{hookEventName:r.hook_event_name,permissionDecision:"allow"}};process.stdout.write(JSON.stringify(e));process.exit(0)}const w=g.filter(e=>e.guide&&typeof e.guide==="object");const v=g.filter(e=>e.plugin&&typeof e.plugin==="string");if(w.length>0){const{valid:e,warnings:i}=(0,u.validateHooksSchema)(w);for(const a of i){process.stderr.write(`[crewx] ${a}
|
|
2
|
+
`)}if(e.length>0){const a=Array.isArray(l.agents)?l.agents:[];const p=new u.YamlHookPlugin({hooks:e,agents:a});const n=await(0,u.evaluateHook)(p,r,d,o);if(n.exitCode!==0){process.stdout.write(JSON.stringify(n.stdout));process.exit(n.exitCode)}if(n.injected){process.stdout.write(JSON.stringify(n.stdout));process.exit(0)}}}for(const e of v){const i=e.plugin;if(!i){console.error(`[crewx] Hook definition missing 'plugin' field`);continue}const a=(0,f.resolve)(d,i);if(!(0,u.isPathSafe)(a,d)){console.error(`[crewx] Hook plugin path rejected (path traversal): ${i}`);process.exit(1)}let p;try{p=O(a)}catch(h){console.error(`[crewx] Failed to load hook plugin: ${i} \u2014 ${h instanceof Error?h.message:String(h)}`);process.exit(1)}const n=await(0,u.evaluateHook)(p,r,d,o);if(n.exitCode!==0){process.stdout.write(JSON.stringify(n.stdout));process.exit(n.exitCode)}}const k={hookSpecificOutput:{hookEventName:r.hook_event_name,permissionDecision:"allow"}};process.stdout.write(JSON.stringify(k));process.exit(0)}
|