@aiwg/cli 2026.8.0 → 2026.8.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/README.md +33 -0
- package/agentic/code/providers/capability-matrix.yaml +511 -0
- package/agentic/code/providers/model-capabilities.v1.json +120 -0
- package/agentic/code/providers/model-catalog.v1.json +96 -0
- package/agentic/code/providers/model-policy-evaluations.v1.json +50 -0
- package/agentic/code/providers/premium-model-allowlist.v1.json +36 -0
- package/bin/aiwg.mjs +14 -10
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +2 -0
- package/dist/src/artifacts/types.js +4 -0
- package/dist/src/auth/client.js +209 -0
- package/dist/src/auth/config.js +38 -0
- package/dist/src/auth/credential-store.js +141 -0
- package/dist/src/auth/resource-credentials.js +25 -0
- package/dist/src/auth/types.js +2 -0
- package/dist/src/channel/manager.mjs +5 -5
- package/dist/src/cli/handlers/auth.js +125 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/install.js +42 -4
- package/dist/src/cli/handlers/marketplace.js +375 -122
- package/dist/src/cli/handlers/resource-versions.js +2 -0
- package/dist/src/cli/handlers/sessions.js +23 -5
- package/dist/src/cli/handlers/subcommands.js +10 -1
- package/dist/src/cli/handlers/use.js +342 -43
- package/dist/src/config/gitignore.js +1 -0
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/marketplace/exchange.js +602 -0
- package/dist/src/marketplace/provenance-types.js +19 -0
- package/dist/src/marketplace/provenance.js +834 -0
- package/dist/src/memory/canonical-context.js +342 -0
- package/dist/src/memory/context-pack.js +282 -0
- package/dist/src/memory/index.js +4 -0
- package/dist/src/memory/intake.js +118 -0
- package/dist/src/packages/adapters/git.js +79 -29
- package/dist/src/packages/package-discovery.js +81 -0
- package/dist/src/packages/package-registry.js +2 -0
- package/dist/src/packages/registry.js +119 -20
- package/dist/src/resources/resolver.js +1 -0
- package/dist/src/resources/web-release.d.ts +3 -1
- package/dist/src/resources/web-release.js +14 -6
- package/dist/src/serve/agentic-sandbox-fleet-client.js +213 -0
- package/dist/src/serve/fleet-mission-conductor.js +293 -0
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/output-registration.js +338 -0
- package/dist/src/sessions/promotion.js +73 -2
- package/dist/src/sessions/repository.js +2 -1
- package/dist/src/update/notifier.mjs +13 -2
- package/package.json +8 -1
- package/tools/_resolve-impl.mjs +74 -0
- package/tools/agents/deploy-agents.mjs +962 -0
- package/tools/agents/providers/base.mjs +2954 -0
- package/tools/agents/providers/claude.mjs +711 -0
- package/tools/agents/providers/codex.mjs +699 -0
- package/tools/agents/providers/copilot.mjs +659 -0
- package/tools/agents/providers/cursor.mjs +714 -0
- package/tools/agents/providers/factory.mjs +1130 -0
- package/tools/agents/providers/hermes.mjs +663 -0
- package/tools/agents/providers/hook-capabilities.mjs +85 -0
- package/tools/agents/providers/model-role.mjs +56 -0
- package/tools/agents/providers/openclaw-translator.mjs +348 -0
- package/tools/agents/providers/openclaw.mjs +680 -0
- package/tools/agents/providers/opencode.mjs +675 -0
- package/tools/agents/providers/openhuman.mjs +292 -0
- package/tools/agents/providers/warp.mjs +413 -0
- package/tools/agents/providers/windsurf.mjs +748 -0
- package/tools/commands/deploy-prompts-codex.mjs +336 -0
- package/tools/plugin/package-plugins.mjs +1013 -0
- package/tools/skills/deploy-skills-codex.mjs +571 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Hook Capabilities
|
|
3
|
+
*
|
|
4
|
+
* Defines @-link support, hook file names, and directives for each provider.
|
|
5
|
+
* Used by postDeploy to write hook files and wire directives.
|
|
6
|
+
*
|
|
7
|
+
* @issue #444
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const PROVIDER_CAPABILITIES = {
|
|
11
|
+
claude: {
|
|
12
|
+
atLinkSupport: true,
|
|
13
|
+
hookFileName: 'AIWG.md',
|
|
14
|
+
hookDirective: '@AIWG.md',
|
|
15
|
+
contextFile: 'CLAUDE.md',
|
|
16
|
+
},
|
|
17
|
+
warp: {
|
|
18
|
+
atLinkSupport: true,
|
|
19
|
+
hookFileName: 'AIWG-warp.md',
|
|
20
|
+
hookDirective: '@AIWG-warp.md',
|
|
21
|
+
contextFile: 'WARP.md',
|
|
22
|
+
},
|
|
23
|
+
cursor: {
|
|
24
|
+
atLinkSupport: true,
|
|
25
|
+
hookFileName: 'AIWG-cursor.md',
|
|
26
|
+
hookDirective: '@AIWG-cursor.md',
|
|
27
|
+
// PUW-037 (#1138): .cursorrules is deprecated by Cursor in favor of
|
|
28
|
+
// AGENTS.md + .cursor/rules/*.mdc with MDC frontmatter. AGENTS.md is
|
|
29
|
+
// the canonical context file going forward.
|
|
30
|
+
contextFile: 'AGENTS.md',
|
|
31
|
+
},
|
|
32
|
+
copilot: {
|
|
33
|
+
atLinkSupport: false,
|
|
34
|
+
hookFileName: 'AIWG-copilot.md',
|
|
35
|
+
hookDirective: null,
|
|
36
|
+
contextFile: '.github/copilot-instructions.md',
|
|
37
|
+
fallback: 'full-inject',
|
|
38
|
+
},
|
|
39
|
+
windsurf: {
|
|
40
|
+
atLinkSupport: false,
|
|
41
|
+
hookFileName: 'AIWG-windsurf.md',
|
|
42
|
+
hookDirective: null,
|
|
43
|
+
contextFile: 'AGENTS.md',
|
|
44
|
+
fallback: 'full-inject',
|
|
45
|
+
},
|
|
46
|
+
factory: {
|
|
47
|
+
atLinkSupport: false,
|
|
48
|
+
hookFileName: 'AIWG-factory.md',
|
|
49
|
+
hookDirective: null,
|
|
50
|
+
contextFile: 'AGENTS.md',
|
|
51
|
+
fallback: 'full-inject',
|
|
52
|
+
},
|
|
53
|
+
opencode: {
|
|
54
|
+
atLinkSupport: true,
|
|
55
|
+
hookFileName: 'AIWG-opencode.md',
|
|
56
|
+
hookDirective: '@AIWG-opencode.md',
|
|
57
|
+
contextFile: '.opencode/context.md',
|
|
58
|
+
},
|
|
59
|
+
codex: {
|
|
60
|
+
atLinkSupport: false,
|
|
61
|
+
hookFileName: 'AIWG-codex.md',
|
|
62
|
+
hookDirective: null,
|
|
63
|
+
contextFile: 'CODEX.md',
|
|
64
|
+
fallback: 'full-inject',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get hook capability for a provider.
|
|
70
|
+
* @param {string} provider
|
|
71
|
+
* @returns {object|null}
|
|
72
|
+
*/
|
|
73
|
+
export function getHookCapability(provider) {
|
|
74
|
+
return PROVIDER_CAPABILITIES[provider] || null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check if a provider supports @-link hook files.
|
|
79
|
+
* @param {string} provider
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
export function supportsAtLink(provider) {
|
|
83
|
+
const cap = PROVIDER_CAPABILITIES[provider];
|
|
84
|
+
return cap ? cap.atLinkSupport : false;
|
|
85
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral model-role classification.
|
|
3
|
+
*
|
|
4
|
+
* Canonical agents historically used Claude family aliases, then moved to
|
|
5
|
+
* pinned and provider-qualified identifiers. Deployment and filtering must
|
|
6
|
+
* classify the family consistently without treating an unknown identifier as
|
|
7
|
+
* coding.
|
|
8
|
+
*
|
|
9
|
+
* @implements #1801
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const ROLE_PATTERNS = [
|
|
13
|
+
['reasoning', /(?:^|[/:._-])opus(?:$|[/:._-]|\d)/i],
|
|
14
|
+
['coding', /(?:^|[/:._-])sonnet(?:$|[/:._-]|\d)/i],
|
|
15
|
+
['efficiency', /(?:^|[/:._-])haiku(?:$|[/:._-]|\d)/i],
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {'reasoning' | 'coding' | 'efficiency' | 'unknown'} ModelRole
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Classify aliases, pinned family IDs, and provider-qualified IDs.
|
|
24
|
+
*
|
|
25
|
+
* Missing model metadata is handled separately from an explicit unknown pin:
|
|
26
|
+
* callers may supply `defaultRole` for legacy artifacts that omit the field.
|
|
27
|
+
*
|
|
28
|
+
* @param {unknown} model
|
|
29
|
+
* @param {{ defaultRole?: Exclude<ModelRole, 'unknown'> }} [options]
|
|
30
|
+
* @returns {ModelRole}
|
|
31
|
+
*/
|
|
32
|
+
export function classifyModelRole(model, options = {}) {
|
|
33
|
+
if (model === undefined || model === null || String(model).trim() === '') {
|
|
34
|
+
return options.defaultRole ?? 'unknown';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const normalized = String(model).trim().replace(/^['"]|['"]$/g, '');
|
|
38
|
+
for (const [role, pattern] of ROLE_PATTERNS) {
|
|
39
|
+
if (pattern.test(normalized)) return /** @type {ModelRole} */ (role);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return 'unknown';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Select a role-specific model without manufacturing a coding classification.
|
|
47
|
+
*
|
|
48
|
+
* @param {unknown} originalModel
|
|
49
|
+
* @param {{reasoning: string, coding: string, efficiency: string}} models
|
|
50
|
+
* @param {{ defaultRole?: 'reasoning' | 'coding' | 'efficiency' }} [options]
|
|
51
|
+
* @returns {string | null}
|
|
52
|
+
*/
|
|
53
|
+
export function modelForRole(originalModel, models, options = {}) {
|
|
54
|
+
const role = classifyModelRole(originalModel, options);
|
|
55
|
+
return role === 'unknown' ? null : models[role];
|
|
56
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw behavior → hook translator (PUW-008 / #1109, PUW-009 / #1110).
|
|
3
|
+
*
|
|
4
|
+
* Per ADR-3 §1, AIWG hook source (BEHAVIOR.md frontmatter + scripts/) is
|
|
5
|
+
* translated to OpenClaw-native hook artifacts (HOOK.md + handler.js) at
|
|
6
|
+
* deploy time. The original BEHAVIOR.md continues to deploy under
|
|
7
|
+
* ~/.openclaw/behaviors/<name>/ per ADR-1 §0.6 always-deploy invariant
|
|
8
|
+
* (operator visibility); this translator additionally emits to
|
|
9
|
+
* ~/.openclaw/hooks/<name>/ which is the path OpenClaw natively scans.
|
|
10
|
+
*
|
|
11
|
+
* AIWG behavior YAML frontmatter has a `hooks:` field shaped like:
|
|
12
|
+
*
|
|
13
|
+
* hooks:
|
|
14
|
+
* on_file_write:
|
|
15
|
+
* - filter: '**\/*.{ts,js}'
|
|
16
|
+
* action: run_script
|
|
17
|
+
* script: scripts/scan.sh
|
|
18
|
+
* on_deploy:
|
|
19
|
+
* - action: run_script
|
|
20
|
+
* script: scripts/post-deploy.sh
|
|
21
|
+
*
|
|
22
|
+
* The translator emits one HOOK.md per behavior carrying frontmatter that
|
|
23
|
+
* lists the events the hook subscribes to, plus a handler.js that
|
|
24
|
+
* dispatches to the right script based on the event the loader passes in.
|
|
25
|
+
*
|
|
26
|
+
* @implements ADR-3 (.aiwg/architecture/adr-hook-deployment-generalization.md)
|
|
27
|
+
* @issue #1109 #1110
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import * as fs from 'node:fs';
|
|
31
|
+
import * as path from 'node:path';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse YAML frontmatter from BEHAVIOR.md. Returns null when no frontmatter
|
|
35
|
+
* is present or the parse fails — the caller skips translation in that case.
|
|
36
|
+
*
|
|
37
|
+
* Lightweight parser that handles the subset of YAML used in BEHAVIOR.md
|
|
38
|
+
* frontmatter (key:value, lists, nested maps). For broader YAML support a
|
|
39
|
+
* library would be needed, but the format is constrained.
|
|
40
|
+
*/
|
|
41
|
+
function parseBehaviorFrontmatter(content) {
|
|
42
|
+
const match = /^---\s*\n([\s\S]*?)\n---/.exec(content);
|
|
43
|
+
if (!match) return null;
|
|
44
|
+
|
|
45
|
+
const fmRaw = match[1];
|
|
46
|
+
const lines = fmRaw.split('\n');
|
|
47
|
+
const frontmatter = {};
|
|
48
|
+
|
|
49
|
+
// Top-level fields we care about
|
|
50
|
+
let inHooks = false;
|
|
51
|
+
let currentEvent = null;
|
|
52
|
+
let currentEntry = null;
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < lines.length; i++) {
|
|
55
|
+
const line = lines[i];
|
|
56
|
+
if (!line.trim()) continue;
|
|
57
|
+
|
|
58
|
+
// Top-level fields (no indentation)
|
|
59
|
+
if (!/^\s/.test(line)) {
|
|
60
|
+
inHooks = false;
|
|
61
|
+
currentEvent = null;
|
|
62
|
+
currentEntry = null;
|
|
63
|
+
|
|
64
|
+
const nameMatch = /^name:\s*(.+)/.exec(line);
|
|
65
|
+
if (nameMatch) { frontmatter.name = nameMatch[1].trim(); continue; }
|
|
66
|
+
|
|
67
|
+
const versionMatch = /^version:\s*(.+)/.exec(line);
|
|
68
|
+
if (versionMatch) { frontmatter.version = versionMatch[1].trim(); continue; }
|
|
69
|
+
|
|
70
|
+
const descMatch = /^description:\s*(.+)?/.exec(line);
|
|
71
|
+
if (descMatch) {
|
|
72
|
+
// Multi-line descriptions continue until next top-level key.
|
|
73
|
+
let desc = (descMatch[1] || '').trim();
|
|
74
|
+
let j = i + 1;
|
|
75
|
+
while (j < lines.length && /^\s+/.test(lines[j]) && !/^\s+\w+:/.test(lines[j])) {
|
|
76
|
+
desc += ' ' + lines[j].trim();
|
|
77
|
+
j++;
|
|
78
|
+
}
|
|
79
|
+
frontmatter.description = desc;
|
|
80
|
+
i = j - 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (/^hooks:/.test(line)) {
|
|
85
|
+
inHooks = true;
|
|
86
|
+
frontmatter.hooks = {};
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Inside `hooks:` block
|
|
93
|
+
if (inHooks) {
|
|
94
|
+
// Event key (one level of indentation, ends with colon, no list dash)
|
|
95
|
+
const eventMatch = /^\s{2,4}([a-z_][a-z0-9_]*):\s*$/i.exec(line);
|
|
96
|
+
if (eventMatch) {
|
|
97
|
+
currentEvent = eventMatch[1];
|
|
98
|
+
frontmatter.hooks[currentEvent] = [];
|
|
99
|
+
currentEntry = null;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// List entry start (dash with field)
|
|
104
|
+
const listStart = /^\s+-\s*(\w+):\s*(.+)?/.exec(line);
|
|
105
|
+
if (listStart && currentEvent) {
|
|
106
|
+
currentEntry = { [listStart[1]]: (listStart[2] || '').trim().replace(/^['"]|['"]$/g, '') };
|
|
107
|
+
frontmatter.hooks[currentEvent].push(currentEntry);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Continuation field within a list entry
|
|
112
|
+
const fieldMatch = /^\s+(\w+):\s*(.+)/.exec(line);
|
|
113
|
+
if (fieldMatch && currentEntry) {
|
|
114
|
+
currentEntry[fieldMatch[1]] = fieldMatch[2].trim().replace(/^['"]|['"]$/g, '');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return frontmatter;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Render the HOOK.md content for OpenClaw's hook loader.
|
|
125
|
+
*
|
|
126
|
+
* Frontmatter fields:
|
|
127
|
+
* - name: behavior name
|
|
128
|
+
* - description: short description
|
|
129
|
+
* - events: list of event names this hook subscribes to
|
|
130
|
+
* - handler: relative path to the JS handler
|
|
131
|
+
* - aiwg_managed: provenance signature per ADR-3
|
|
132
|
+
*/
|
|
133
|
+
export function renderHookMd(behavior) {
|
|
134
|
+
const events = Object.keys(behavior.hooks || {});
|
|
135
|
+
const eventsList = events.length > 0
|
|
136
|
+
? events.map((e) => ` - ${e}`).join('\n')
|
|
137
|
+
: ' []';
|
|
138
|
+
|
|
139
|
+
const description = (behavior.description || '').replace(/"/g, '\\"').slice(0, 200);
|
|
140
|
+
|
|
141
|
+
return [
|
|
142
|
+
'---',
|
|
143
|
+
`name: ${behavior.name}`,
|
|
144
|
+
`description: "${description}"`,
|
|
145
|
+
'events:',
|
|
146
|
+
eventsList,
|
|
147
|
+
'handler: ./handler.js',
|
|
148
|
+
'aiwg_managed: true',
|
|
149
|
+
'---',
|
|
150
|
+
'',
|
|
151
|
+
`# ${behavior.name}`,
|
|
152
|
+
'',
|
|
153
|
+
behavior.description || '',
|
|
154
|
+
'',
|
|
155
|
+
`Generated by AIWG from \`agentic/code/behaviors/${behavior.name}/BEHAVIOR.md\`.`,
|
|
156
|
+
`Edits to this file are overwritten on \`aiwg use\`. Edit the source BEHAVIOR.md instead.`,
|
|
157
|
+
'',
|
|
158
|
+
].join('\n');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Render the handler.js dispatcher for one behavior.
|
|
163
|
+
*
|
|
164
|
+
* The handler receives an event payload from OpenClaw's hook loader and
|
|
165
|
+
* dispatches to the correct script based on the event name. Per ADR-3 §3,
|
|
166
|
+
* the stdin schema is normalized to the AIWG canonical shape:
|
|
167
|
+
*
|
|
168
|
+
* { event, tool, args, project_dir, session_id }
|
|
169
|
+
*
|
|
170
|
+
* Per ADR-3 §4, the handler exits 0 (allow), 1 (block), or 2 (warn).
|
|
171
|
+
*
|
|
172
|
+
* The handler is intentionally minimal — it forwards execution to the
|
|
173
|
+
* behavior's existing scripts/*.sh files. AIWG never invents new logic;
|
|
174
|
+
* the translator is a transport-layer change only.
|
|
175
|
+
*/
|
|
176
|
+
export function renderHandlerJs(behavior, scriptsRelDir = './scripts') {
|
|
177
|
+
// Build an event → scripts mapping from the hooks: frontmatter.
|
|
178
|
+
const dispatchTable = {};
|
|
179
|
+
for (const [event, entries] of Object.entries(behavior.hooks || {})) {
|
|
180
|
+
dispatchTable[event] = (entries || [])
|
|
181
|
+
.filter((e) => e.action === 'run_script' && e.script)
|
|
182
|
+
.map((e) => ({
|
|
183
|
+
script: e.script.startsWith('scripts/')
|
|
184
|
+
? e.script.slice('scripts/'.length)
|
|
185
|
+
: e.script,
|
|
186
|
+
filter: e.filter || null,
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Stable JSON for the dispatch table embedded in the handler source.
|
|
191
|
+
const dispatchJson = JSON.stringify(dispatchTable, null, 2)
|
|
192
|
+
.split('\n').map((l) => ' ' + l).join('\n').trim();
|
|
193
|
+
|
|
194
|
+
return `#!/usr/bin/env node
|
|
195
|
+
// Generated by AIWG (PUW-008/PUW-009). Do not edit; regenerated on \`aiwg use\`.
|
|
196
|
+
// Handler for OpenClaw hook \`${behavior.name}\`. Dispatches to scripts in
|
|
197
|
+
// ./scripts/ based on the event delivered by OpenClaw's hook loader.
|
|
198
|
+
//
|
|
199
|
+
// Stdin contract (AIWG canonical, per ADR-3 §3):
|
|
200
|
+
// { event, tool, args, project_dir, session_id }
|
|
201
|
+
// Exit codes (per ADR-3 §4):
|
|
202
|
+
// 0 = allow, 1 = block, 2 = warn-and-continue
|
|
203
|
+
|
|
204
|
+
import { spawnSync } from 'node:child_process';
|
|
205
|
+
import { readFileSync } from 'node:fs';
|
|
206
|
+
import { dirname, join, resolve } from 'node:path';
|
|
207
|
+
import { fileURLToPath } from 'node:url';
|
|
208
|
+
import { minimatch } from 'minimatch';
|
|
209
|
+
|
|
210
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
211
|
+
const SCRIPTS_DIR = resolve(__dirname, ${JSON.stringify(scriptsRelDir)});
|
|
212
|
+
|
|
213
|
+
const DISPATCH = ${dispatchJson};
|
|
214
|
+
|
|
215
|
+
function readStdin() {
|
|
216
|
+
try {
|
|
217
|
+
const data = readFileSync(0, 'utf8');
|
|
218
|
+
return data ? JSON.parse(data) : {};
|
|
219
|
+
} catch {
|
|
220
|
+
return {};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function pathMatches(filter, candidate) {
|
|
225
|
+
if (!filter) return true;
|
|
226
|
+
if (!candidate) return false;
|
|
227
|
+
try {
|
|
228
|
+
return minimatch(candidate, filter, { matchBase: true });
|
|
229
|
+
} catch {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const payload = readStdin();
|
|
235
|
+
const event = process.env.AIWG_HOOK_EVENT || payload.event;
|
|
236
|
+
const targetPath = payload.path || payload.file || (payload.args && payload.args[0]) || null;
|
|
237
|
+
|
|
238
|
+
const handlers = DISPATCH[event] || [];
|
|
239
|
+
if (handlers.length === 0) {
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let worstExit = 0;
|
|
244
|
+
for (const h of handlers) {
|
|
245
|
+
if (!pathMatches(h.filter, targetPath)) continue;
|
|
246
|
+
const scriptPath = join(SCRIPTS_DIR, h.script);
|
|
247
|
+
const result = spawnSync(scriptPath, [], {
|
|
248
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
249
|
+
input: JSON.stringify(payload),
|
|
250
|
+
env: {
|
|
251
|
+
...process.env,
|
|
252
|
+
AIWG_HOOK_EVENT: event,
|
|
253
|
+
AIWG_PROJECT_DIR: payload.project_dir || process.env.AIWG_PROJECT_DIR || '',
|
|
254
|
+
AIWG_TOOL_NAME: payload.tool || '',
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
if (result.status !== null && result.status > worstExit) {
|
|
258
|
+
worstExit = result.status;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
process.exit(worstExit > 2 ? 1 : worstExit);
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Translate one BEHAVIOR.md into HOOK.md + handler.js artifacts.
|
|
267
|
+
*
|
|
268
|
+
* @param {string} behaviorDir absolute path of source behavior directory
|
|
269
|
+
* @param {string} hookDestDir absolute path of destination hook directory
|
|
270
|
+
* @param {object} opts deploy options (dryRun supported)
|
|
271
|
+
* @returns {{ ok: boolean, reason?: string, hookMdPath?: string, handlerPath?: string }}
|
|
272
|
+
*/
|
|
273
|
+
export function translateBehaviorToHook(behaviorDir, hookDestDir, opts = {}) {
|
|
274
|
+
const behaviorMdPath = path.join(behaviorDir, 'BEHAVIOR.md');
|
|
275
|
+
if (!fs.existsSync(behaviorMdPath)) {
|
|
276
|
+
return { ok: false, reason: 'BEHAVIOR.md not found' };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const content = fs.readFileSync(behaviorMdPath, 'utf8');
|
|
280
|
+
const fm = parseBehaviorFrontmatter(content);
|
|
281
|
+
if (!fm || !fm.name) {
|
|
282
|
+
return { ok: false, reason: 'BEHAVIOR.md frontmatter missing or unparseable' };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Behaviors without a `hooks:` block don't have anything for OpenClaw's
|
|
286
|
+
// hook loader to subscribe to (e.g. `mode: agent` behaviors). Skip them.
|
|
287
|
+
if (!fm.hooks || Object.keys(fm.hooks).length === 0) {
|
|
288
|
+
return { ok: false, reason: 'no hooks declared in frontmatter' };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const hookMd = renderHookMd(fm);
|
|
292
|
+
const handlerJs = renderHandlerJs(fm);
|
|
293
|
+
|
|
294
|
+
if (opts.dryRun) {
|
|
295
|
+
return { ok: true, hookMdPath: '<dry-run>', handlerPath: '<dry-run>' };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
fs.mkdirSync(hookDestDir, { recursive: true });
|
|
299
|
+
const hookMdPath = path.join(hookDestDir, 'HOOK.md');
|
|
300
|
+
const handlerPath = path.join(hookDestDir, 'handler.js');
|
|
301
|
+
fs.writeFileSync(hookMdPath, hookMd, 'utf8');
|
|
302
|
+
fs.writeFileSync(handlerPath, handlerJs, 'utf8');
|
|
303
|
+
try { fs.chmodSync(handlerPath, 0o755); } catch { /* non-POSIX */ }
|
|
304
|
+
|
|
305
|
+
// Also copy scripts/ alongside the handler so it can find them by
|
|
306
|
+
// relative path. The behavior's BEHAVIOR.md references scripts/<name>.sh
|
|
307
|
+
// and the handler's spawnSync uses the same convention.
|
|
308
|
+
const sourceScripts = path.join(behaviorDir, 'scripts');
|
|
309
|
+
if (fs.existsSync(sourceScripts)) {
|
|
310
|
+
const destScripts = path.join(hookDestDir, 'scripts');
|
|
311
|
+
fs.mkdirSync(destScripts, { recursive: true });
|
|
312
|
+
for (const entry of fs.readdirSync(sourceScripts)) {
|
|
313
|
+
const src = path.join(sourceScripts, entry);
|
|
314
|
+
const dst = path.join(destScripts, entry);
|
|
315
|
+
if (fs.statSync(src).isFile()) {
|
|
316
|
+
fs.copyFileSync(src, dst);
|
|
317
|
+
try { fs.chmodSync(dst, 0o755); } catch { /* non-POSIX */ }
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { ok: true, hookMdPath, handlerPath };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Translate every behavior under behaviorsRoot into hooks under hooksRoot.
|
|
327
|
+
*
|
|
328
|
+
* @returns {{ translated: number, skipped: Array<{name: string, reason: string}> }}
|
|
329
|
+
*/
|
|
330
|
+
export function translateAllBehaviors(behaviorsRoot, hooksRoot, opts = {}) {
|
|
331
|
+
const result = { translated: 0, skipped: [] };
|
|
332
|
+
if (!fs.existsSync(behaviorsRoot)) return result;
|
|
333
|
+
|
|
334
|
+
const entries = fs.readdirSync(behaviorsRoot, { withFileTypes: true });
|
|
335
|
+
for (const entry of entries) {
|
|
336
|
+
if (!entry.isDirectory()) continue;
|
|
337
|
+
const behaviorDir = path.join(behaviorsRoot, entry.name);
|
|
338
|
+
const hookDir = path.join(hooksRoot, entry.name);
|
|
339
|
+
const r = translateBehaviorToHook(behaviorDir, hookDir, opts);
|
|
340
|
+
if (r.ok) {
|
|
341
|
+
result.translated++;
|
|
342
|
+
} else {
|
|
343
|
+
result.skipped.push({ name: entry.name, reason: r.reason });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return result;
|
|
348
|
+
}
|