@tangle-network/agent-profile-materialize 0.1.0 → 0.2.3
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/index.d.ts +4 -4
- package/dist/index.js +182 -114
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
* `node:fs` IO consumer. It was previously copied byte-for-byte into both
|
|
22
22
|
* cli-bridge and agent-dev-container — this is the single source those re-export.
|
|
23
23
|
*/
|
|
24
|
-
import type { AgentProfile } from
|
|
25
|
-
export type HarnessId =
|
|
24
|
+
import type { AgentProfile } from "@tangle-network/agent-interface";
|
|
25
|
+
export type HarnessId = "claude-code" | "claude" | "claudish" | "nanoclaw" | "codex" | "opencode" | "kimi-code" | "kimi" | "pi" | "gemini" | "hermes" | "openclaw";
|
|
26
26
|
export interface PlanFile {
|
|
27
27
|
relPath: string;
|
|
28
28
|
content: string;
|
|
@@ -30,7 +30,7 @@ export interface PlanFile {
|
|
|
30
30
|
mode?: number;
|
|
31
31
|
}
|
|
32
32
|
export interface Unsupported {
|
|
33
|
-
dimension:
|
|
33
|
+
dimension: "skills" | "mcp" | "hooks" | "subagents" | "commands" | "instructions" | "systemPrompt" | "permissions" | "tools";
|
|
34
34
|
reason: string;
|
|
35
35
|
}
|
|
36
36
|
/** The plan a caller writes into the run cwd, then applies to the spawn. */
|
|
@@ -53,7 +53,7 @@ export declare function normalizeSkillMd(name: string, raw: string): string;
|
|
|
53
53
|
* writes plan.files into the run cwd and applies plan.env/plan.flags to the spawn.
|
|
54
54
|
*/
|
|
55
55
|
export declare function materializeProfile(profile: AgentProfile, harness: HarnessId, opts?: {
|
|
56
|
-
skip?: Unsupported[
|
|
56
|
+
skip?: Unsupported["dimension"][];
|
|
57
57
|
}): WorkspacePlan;
|
|
58
58
|
/**
|
|
59
59
|
* Apply a WorkspacePlan to a real workspace dir: write every file (creating parent
|
package/dist/index.js
CHANGED
|
@@ -21,73 +21,90 @@
|
|
|
21
21
|
* `node:fs` IO consumer. It was previously copied byte-for-byte into both
|
|
22
22
|
* cli-bridge and agent-dev-container — this is the single source those re-export.
|
|
23
23
|
*/
|
|
24
|
-
import { mkdirSync, writeFileSync } from
|
|
25
|
-
import { dirname, join } from
|
|
24
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
26
26
|
/** Resolve an inline resource ref to {name, content}. github refs need an async fetch
|
|
27
27
|
* (the providers' resolveProfileResource does this); the sync materializer can't, so a
|
|
28
28
|
* github ref is reported unsupported and the caller pre-resolves it. A ref with
|
|
29
29
|
* `content` (or kind 'inline') is inline. */
|
|
30
30
|
function resolveInlineRef(ref) {
|
|
31
31
|
const r = ref;
|
|
32
|
-
if (r.kind ===
|
|
33
|
-
return {
|
|
32
|
+
if (r.kind === "inline" || typeof r.content === "string") {
|
|
33
|
+
return {
|
|
34
|
+
name: String(r.name ?? r.path ?? "resource"),
|
|
35
|
+
content: String(r.content ?? ""),
|
|
36
|
+
};
|
|
34
37
|
}
|
|
35
38
|
return null; // github / unresolved
|
|
36
39
|
}
|
|
37
40
|
// ─── Verified per-harness conventions (capability matrix) ───────────────────
|
|
38
41
|
/** Project context file each harness actually reads from cwd. NEVER a constant. */
|
|
39
42
|
const CONTEXT_FILE = {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
"claude-code": "CLAUDE.md",
|
|
44
|
+
claude: "CLAUDE.md",
|
|
45
|
+
claudish: "CLAUDE.md",
|
|
46
|
+
nanoclaw: "CLAUDE.md",
|
|
47
|
+
codex: "AGENTS.md",
|
|
48
|
+
opencode: "AGENTS.md",
|
|
49
|
+
"kimi-code": "AGENTS.md",
|
|
50
|
+
kimi: "AGENTS.md",
|
|
51
|
+
pi: "AGENTS.md",
|
|
52
|
+
gemini: "GEMINI.md", // current default; AGENTS.md only via .gemini/settings.json context.fileName
|
|
53
|
+
hermes: "HERMES.md", // highest-priority native name; only ONE context loads (first match)
|
|
54
|
+
openclaw: "AGENTS.md",
|
|
45
55
|
};
|
|
46
56
|
/** cwd-relative native skill dir, or null if the harness has no cwd skill primitive. */
|
|
47
57
|
const SKILL_DIR = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
"claude-code": ".claude/skills",
|
|
59
|
+
claude: ".claude/skills",
|
|
60
|
+
claudish: ".claude/skills",
|
|
61
|
+
nanoclaw: ".claude/skills",
|
|
62
|
+
codex: ".codex/skills",
|
|
63
|
+
opencode: ".opencode/skills",
|
|
64
|
+
"kimi-code": ".kimi/skills",
|
|
65
|
+
kimi: ".kimi/skills",
|
|
66
|
+
pi: ".pi/skills",
|
|
67
|
+
gemini: ".gemini/skills",
|
|
51
68
|
hermes: null, // skills are ~/.hermes/skills (user) only — no cwd discovery
|
|
52
|
-
openclaw:
|
|
69
|
+
openclaw: "skills", // <workspace>/skills/<name>/SKILL.md
|
|
53
70
|
};
|
|
54
71
|
function canonicalHarness(h) {
|
|
55
|
-
if (h ===
|
|
56
|
-
return
|
|
57
|
-
if (h ===
|
|
58
|
-
return
|
|
72
|
+
if (h === "claude" || h === "claudish")
|
|
73
|
+
return "claude-code";
|
|
74
|
+
if (h === "kimi")
|
|
75
|
+
return "kimi-code";
|
|
59
76
|
return h;
|
|
60
77
|
}
|
|
61
78
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
62
79
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
|
|
63
|
-
const collapse = (s) => s.replace(/\s+/g,
|
|
80
|
+
const collapse = (s) => s.replace(/\s+/g, " ").trim();
|
|
64
81
|
/** Normalize any skill markdown (VB frontmatter `skill:`/`description:`, or raw) into a
|
|
65
82
|
* harness SKILL.md with `name:`/`description:` frontmatter the harnesses require. */
|
|
66
83
|
export function normalizeSkillMd(name, raw) {
|
|
67
84
|
const m = raw.match(FRONTMATTER_RE);
|
|
68
|
-
const fm = m?.[1] ??
|
|
85
|
+
const fm = m?.[1] ?? "";
|
|
69
86
|
const body = (m?.[2] ?? raw).trim();
|
|
70
87
|
let desc = `Skill ${name}. Use when the task matches its domain.`;
|
|
71
88
|
if (fm) {
|
|
72
|
-
const lines = fm.split(
|
|
89
|
+
const lines = fm.split("\n");
|
|
73
90
|
const i = lines.findIndex((l) => /^description:/.test(l));
|
|
74
91
|
if (i >= 0) {
|
|
75
|
-
const inline = (lines[i] ??
|
|
76
|
-
if (inline && ![
|
|
92
|
+
const inline = (lines[i] ?? "").replace(/^description:\s*/, "").trim();
|
|
93
|
+
if (inline && ![">", "|", ">-", "|-"].includes(inline))
|
|
77
94
|
desc = collapse(inline);
|
|
78
95
|
else {
|
|
79
96
|
const buf = [];
|
|
80
97
|
for (let j = i + 1; j < lines.length; j++) {
|
|
81
|
-
const ln = lines[j] ??
|
|
98
|
+
const ln = lines[j] ?? "";
|
|
82
99
|
if (/^\s+\S/.test(ln))
|
|
83
100
|
buf.push(ln.trim());
|
|
84
|
-
else if (ln.trim() ===
|
|
101
|
+
else if (ln.trim() === "")
|
|
85
102
|
continue;
|
|
86
103
|
else
|
|
87
104
|
break;
|
|
88
105
|
}
|
|
89
106
|
if (buf.length)
|
|
90
|
-
desc = collapse(buf.join(
|
|
107
|
+
desc = collapse(buf.join(" "));
|
|
91
108
|
}
|
|
92
109
|
}
|
|
93
110
|
}
|
|
@@ -102,7 +119,13 @@ const tomlStr = (s) => JSON.stringify(s); // valid TOML basic string
|
|
|
102
119
|
export function materializeProfile(profile, harness, opts = {}) {
|
|
103
120
|
const h = canonicalHarness(harness);
|
|
104
121
|
const skip = new Set(opts.skip ?? []);
|
|
105
|
-
const plan = {
|
|
122
|
+
const plan = {
|
|
123
|
+
harness: h,
|
|
124
|
+
files: [],
|
|
125
|
+
env: {},
|
|
126
|
+
flags: [],
|
|
127
|
+
unsupported: [],
|
|
128
|
+
};
|
|
106
129
|
const add = (relPath, content, mode) => plan.files.push({ relPath, content, ...(mode ? { mode } : {}) });
|
|
107
130
|
const unsupported = (dimension, reason) => plan.unsupported.push({ dimension, reason });
|
|
108
131
|
// 1) CONTEXT (instructions + systemPrompt → the harness's native context file).
|
|
@@ -112,12 +135,12 @@ export function materializeProfile(profile, harness, opts = {}) {
|
|
|
112
135
|
if (profile.prompt?.instructions?.length)
|
|
113
136
|
instr.push(...profile.prompt.instructions);
|
|
114
137
|
const resInstr = profile.resources?.instructions;
|
|
115
|
-
if (typeof resInstr ===
|
|
138
|
+
if (typeof resInstr === "string")
|
|
116
139
|
instr.push(resInstr);
|
|
117
|
-
else if (resInstr && resInstr.kind ===
|
|
140
|
+
else if (resInstr && resInstr.kind === "inline")
|
|
118
141
|
instr.push(resInstr.content);
|
|
119
142
|
if (instr.length) {
|
|
120
|
-
add(CONTEXT_FILE[h], `${instr.join(
|
|
143
|
+
add(CONTEXT_FILE[h], `${instr.join("\n\n")}\n`);
|
|
121
144
|
// gemini reads AGENTS.md only via settings; we write GEMINI.md so no extra step.
|
|
122
145
|
}
|
|
123
146
|
// 2) SKILLS → native skill dir, from resources.skills refs (the box's shape), or
|
|
@@ -125,12 +148,12 @@ export function materializeProfile(profile, harness, opts = {}) {
|
|
|
125
148
|
for (const ref of profile.resources?.skills ?? []) {
|
|
126
149
|
const r = resolveInlineRef(ref);
|
|
127
150
|
if (!r) {
|
|
128
|
-
unsupported(
|
|
151
|
+
unsupported("skills", "skill ref is not inline (github refs need async pre-resolution)");
|
|
129
152
|
continue;
|
|
130
153
|
}
|
|
131
154
|
const dir = SKILL_DIR[h];
|
|
132
155
|
if (!dir) {
|
|
133
|
-
unsupported(
|
|
156
|
+
unsupported("skills", `${h}: no cwd skill dir (skills live in a user/global dir); skill "${r.name}" not mounted`);
|
|
134
157
|
continue;
|
|
135
158
|
}
|
|
136
159
|
add(`${dir}/${r.name}/SKILL.md`, normalizeSkillMd(r.name, r.content));
|
|
@@ -138,7 +161,7 @@ export function materializeProfile(profile, harness, opts = {}) {
|
|
|
138
161
|
// 3) MCP → per-harness format (the divergence the matrix names). Skippable so the
|
|
139
162
|
// additive host-wiring can leave MCP on cli-bridge's existing per-harness path.
|
|
140
163
|
const mcp = profile.mcp ?? {};
|
|
141
|
-
if (!skip.has(
|
|
164
|
+
if (!skip.has("mcp") && Object.keys(mcp).length)
|
|
142
165
|
materializeMcp(h, mcp, plan, add, unsupported);
|
|
143
166
|
// 4) HOOKS → per-harness format; cwd-native only for claude/codex/gemini/opencode(plugin).
|
|
144
167
|
const hooks = profile.hooks ?? {};
|
|
@@ -152,7 +175,7 @@ export function materializeProfile(profile, harness, opts = {}) {
|
|
|
152
175
|
for (const ref of profile.resources?.commands ?? []) {
|
|
153
176
|
const r = resolveInlineRef(ref);
|
|
154
177
|
if (!r) {
|
|
155
|
-
unsupported(
|
|
178
|
+
unsupported("commands", "command ref is not inline (github needs pre-resolution)");
|
|
156
179
|
continue;
|
|
157
180
|
}
|
|
158
181
|
materializeCommand(h, r.name, r.content, add, unsupported);
|
|
@@ -172,13 +195,21 @@ function mergeJsonFiles(files) {
|
|
|
172
195
|
byPath.set(f.relPath, f);
|
|
173
196
|
continue;
|
|
174
197
|
}
|
|
175
|
-
if (f.relPath.endsWith(
|
|
198
|
+
if (f.relPath.endsWith(".json")) {
|
|
176
199
|
try {
|
|
177
|
-
const merged = {
|
|
178
|
-
|
|
200
|
+
const merged = {
|
|
201
|
+
...JSON.parse(prev.content),
|
|
202
|
+
...JSON.parse(f.content),
|
|
203
|
+
};
|
|
204
|
+
byPath.set(f.relPath, {
|
|
205
|
+
...prev,
|
|
206
|
+
content: JSON.stringify(merged, null, 2),
|
|
207
|
+
});
|
|
179
208
|
continue;
|
|
180
209
|
}
|
|
181
|
-
catch {
|
|
210
|
+
catch {
|
|
211
|
+
/* fall through to last-wins */
|
|
212
|
+
}
|
|
182
213
|
}
|
|
183
214
|
byPath.set(f.relPath, f); // non-JSON or parse-fail: last wins
|
|
184
215
|
}
|
|
@@ -198,60 +229,67 @@ export function applyWorkspacePlan(plan, workspaceDir) {
|
|
|
198
229
|
writeFileSync(abs, f.content, { mode: f.mode ?? 0o644 });
|
|
199
230
|
written.push(f.relPath);
|
|
200
231
|
}
|
|
201
|
-
return {
|
|
232
|
+
return {
|
|
233
|
+
env: plan.env,
|
|
234
|
+
flags: plan.flags,
|
|
235
|
+
unsupported: plan.unsupported,
|
|
236
|
+
written,
|
|
237
|
+
};
|
|
202
238
|
}
|
|
203
239
|
// ─── Per-dimension routing (verified matrix) ────────────────────────────────
|
|
204
240
|
function materializeMcp(h, mcp, plan, add, unsupported) {
|
|
205
241
|
switch (h) {
|
|
206
|
-
case
|
|
207
|
-
case
|
|
242
|
+
case "claude-code":
|
|
243
|
+
case "nanoclaw":
|
|
208
244
|
// claude reads ./.mcp.json but each server needs per-project APPROVAL; we also
|
|
209
245
|
// enable them via .claude/settings.json enabledMcpjsonServers to skip the prompt.
|
|
210
|
-
add(
|
|
211
|
-
add(
|
|
212
|
-
if (h ===
|
|
213
|
-
unsupported(
|
|
246
|
+
add(".mcp.json", JSON.stringify({ mcpServers: mcp }, null, 2));
|
|
247
|
+
add(".claude/settings.json", JSON.stringify({ enabledMcpjsonServers: Object.keys(mcp) }, null, 2));
|
|
248
|
+
if (h === "nanoclaw")
|
|
249
|
+
unsupported("mcp", "nanoclaw MCP is SDK-injected (options.mcpServers); .mcp.json may be ignored — inject via the SDK adapter");
|
|
214
250
|
break;
|
|
215
|
-
case
|
|
251
|
+
case "codex": {
|
|
216
252
|
// codex reads [mcp_servers.<id>] in .codex/config.toml; trusted projects only.
|
|
217
|
-
const lines = ['[projects."."]', 'trust_level = "trusted"',
|
|
253
|
+
const lines = ['[projects."."]', 'trust_level = "trusted"', ""];
|
|
218
254
|
for (const [name, s] of Object.entries(mcp)) {
|
|
219
255
|
lines.push(`[mcp_servers.${name}]`);
|
|
220
256
|
if (s.command)
|
|
221
257
|
lines.push(`command = ${tomlStr(s.command)}`);
|
|
222
258
|
if (s.args?.length)
|
|
223
|
-
lines.push(`args = [${s.args.map(tomlStr).join(
|
|
259
|
+
lines.push(`args = [${s.args.map(tomlStr).join(", ")}]`);
|
|
224
260
|
if (s.url)
|
|
225
261
|
lines.push(`url = ${tomlStr(s.url)}`);
|
|
226
262
|
if (s.env)
|
|
227
|
-
lines.push(`env = { ${Object.entries(s.env)
|
|
228
|
-
|
|
263
|
+
lines.push(`env = { ${Object.entries(s.env)
|
|
264
|
+
.map(([k, v]) => `${k} = ${tomlStr(v)}`)
|
|
265
|
+
.join(", ")} }`);
|
|
266
|
+
lines.push("");
|
|
229
267
|
}
|
|
230
|
-
add(
|
|
268
|
+
add(".codex/config.toml", lines.join("\n"));
|
|
231
269
|
break;
|
|
232
270
|
}
|
|
233
|
-
case
|
|
271
|
+
case "opencode":
|
|
234
272
|
// opencode.json "mcp" block (local|remote).
|
|
235
|
-
add(
|
|
273
|
+
add("opencode.json", JSON.stringify({ $schema: "https://opencode.ai/config.json", mcp: opencodeMcp(mcp) }, null, 2));
|
|
236
274
|
break;
|
|
237
|
-
case
|
|
238
|
-
add(
|
|
275
|
+
case "gemini":
|
|
276
|
+
add(".gemini/settings.json", JSON.stringify({ mcpServers: mcp }, null, 2));
|
|
239
277
|
break;
|
|
240
|
-
case
|
|
278
|
+
case "kimi-code": {
|
|
241
279
|
// NO cwd auto-discovery — kimi needs an explicit --mcp-config-file FLAG.
|
|
242
|
-
const rel =
|
|
280
|
+
const rel = ".kimi/mcp.json";
|
|
243
281
|
add(rel, JSON.stringify({ mcpServers: mcp }, null, 2));
|
|
244
|
-
plan.flags.push(
|
|
282
|
+
plan.flags.push("--mcp-config-file", rel);
|
|
245
283
|
break;
|
|
246
284
|
}
|
|
247
|
-
case
|
|
248
|
-
unsupported(
|
|
285
|
+
case "pi":
|
|
286
|
+
unsupported("mcp", "pi MCP is wired via a TS extension (.pi/extensions/*.ts), not a declarative cwd file — build an extension");
|
|
249
287
|
break;
|
|
250
|
-
case
|
|
251
|
-
unsupported(
|
|
288
|
+
case "hermes":
|
|
289
|
+
unsupported("mcp", "hermes MCP is global config.yaml (mcp_servers), no cwd discovery — write to the hermes global config or pass a config flag");
|
|
252
290
|
break;
|
|
253
|
-
case
|
|
254
|
-
unsupported(
|
|
291
|
+
case "openclaw":
|
|
292
|
+
unsupported("mcp", "openclaw MCP is central ~/.openclaw/openclaw.json (mcp.servers), no cwd discovery");
|
|
255
293
|
break;
|
|
256
294
|
}
|
|
257
295
|
}
|
|
@@ -259,88 +297,117 @@ function opencodeMcp(mcp) {
|
|
|
259
297
|
const out = {};
|
|
260
298
|
for (const [name, s] of Object.entries(mcp)) {
|
|
261
299
|
out[name] = s.url
|
|
262
|
-
? {
|
|
263
|
-
|
|
300
|
+
? {
|
|
301
|
+
type: "remote",
|
|
302
|
+
url: s.url,
|
|
303
|
+
enabled: s.enabled !== false,
|
|
304
|
+
...(s.headers ? { headers: s.headers } : {}),
|
|
305
|
+
}
|
|
306
|
+
: {
|
|
307
|
+
type: "local",
|
|
308
|
+
command: [s.command, ...(s.args ?? [])].filter(Boolean),
|
|
309
|
+
enabled: s.enabled !== false,
|
|
310
|
+
...(s.env ? { environment: s.env } : {}),
|
|
311
|
+
};
|
|
264
312
|
}
|
|
265
313
|
return out;
|
|
266
314
|
}
|
|
267
315
|
function materializeHooks(h, hooks, add, unsupported, plan) {
|
|
268
316
|
switch (h) {
|
|
269
|
-
case
|
|
270
|
-
case
|
|
317
|
+
case "claude-code":
|
|
318
|
+
case "nanoclaw": {
|
|
271
319
|
// .claude/settings.json hooks{} — each event → [{matcher, hooks:[{type:command,command}]}].
|
|
272
320
|
const settingsHooks = {};
|
|
273
321
|
for (const [event, cmds] of Object.entries(hooks)) {
|
|
274
|
-
settingsHooks[event] = cmds.map((c) => ({
|
|
322
|
+
settingsHooks[event] = cmds.map((c) => ({
|
|
323
|
+
matcher: c.matcher ?? "*",
|
|
324
|
+
hooks: [
|
|
325
|
+
{
|
|
326
|
+
type: "command",
|
|
327
|
+
command: c.command,
|
|
328
|
+
...(c.timeoutMs
|
|
329
|
+
? { timeout: Math.round(c.timeoutMs / 1000) }
|
|
330
|
+
: {}),
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
}));
|
|
275
334
|
}
|
|
276
|
-
add(
|
|
335
|
+
add(".claude/settings.json", JSON.stringify({ hooks: settingsHooks }, null, 2));
|
|
277
336
|
break;
|
|
278
337
|
}
|
|
279
|
-
case
|
|
338
|
+
case "gemini": {
|
|
280
339
|
const settingsHooks = {};
|
|
281
340
|
for (const [event, cmds] of Object.entries(hooks))
|
|
282
|
-
settingsHooks[event] = cmds.map((c) => ({
|
|
283
|
-
|
|
341
|
+
settingsHooks[event] = cmds.map((c) => ({
|
|
342
|
+
matcher: c.matcher ?? "*",
|
|
343
|
+
hooks: [{ type: "command", command: c.command }],
|
|
344
|
+
}));
|
|
345
|
+
add(".gemini/settings.json", JSON.stringify({ hooks: settingsHooks }, null, 2));
|
|
284
346
|
break;
|
|
285
347
|
}
|
|
286
|
-
case
|
|
348
|
+
case "codex": {
|
|
287
349
|
// codex hooks.json (event → commands).
|
|
288
|
-
add(
|
|
350
|
+
add(".codex/hooks.json", JSON.stringify({ hooks }, null, 2));
|
|
289
351
|
break;
|
|
290
352
|
}
|
|
291
|
-
case
|
|
353
|
+
case "opencode":
|
|
292
354
|
// opencode hooks are PLUGINS (JS/TS), not a settings block — emit a plugin stub.
|
|
293
355
|
for (const [event, cmds] of Object.entries(hooks)) {
|
|
294
|
-
const body = cmds.map((c) => ` // ${event}: ${c.command}`).join(
|
|
356
|
+
const body = cmds.map((c) => ` // ${event}: ${c.command}`).join("\n");
|
|
295
357
|
add(`.opencode/plugin/vb-hooks-${event}.js`, `// auto-generated hook plugin for ${event}\nexport default async function () {\n${body}\n}\n`);
|
|
296
358
|
}
|
|
297
|
-
unsupported(
|
|
359
|
+
unsupported("hooks", "opencode hooks are JS/TS plugins — generated a stub; a real handler must run the command (format differs from settings.json)");
|
|
298
360
|
break;
|
|
299
|
-
case
|
|
300
|
-
unsupported(
|
|
361
|
+
case "kimi-code":
|
|
362
|
+
unsupported("hooks", "kimi hooks are [[hooks]] in the config TOML loaded via --config-file, not cwd — pass --config-file");
|
|
301
363
|
break;
|
|
302
|
-
case
|
|
303
|
-
unsupported(
|
|
364
|
+
case "pi":
|
|
365
|
+
unsupported("hooks", "pi hooks are TS extensions (.pi/extensions/*.ts) — build an extension");
|
|
304
366
|
break;
|
|
305
|
-
case
|
|
367
|
+
case "hermes": {
|
|
306
368
|
// opt-in project plugins; requires HERMES_ENABLE_PROJECT_PLUGINS=1.
|
|
307
369
|
for (const [event, cmds] of Object.entries(hooks))
|
|
308
|
-
add(`.hermes/plugins/vb-${event}/plugin.md`, cmds.map((c) => c.command).join(
|
|
309
|
-
plan.env.HERMES_ENABLE_PROJECT_PLUGINS =
|
|
370
|
+
add(`.hermes/plugins/vb-${event}/plugin.md`, cmds.map((c) => c.command).join("\n"));
|
|
371
|
+
plan.env.HERMES_ENABLE_PROJECT_PLUGINS = "1";
|
|
310
372
|
break;
|
|
311
373
|
}
|
|
312
|
-
case
|
|
313
|
-
unsupported(
|
|
374
|
+
case "openclaw":
|
|
375
|
+
unsupported("hooks", "openclaw hooks are DISABLED by default — enable in ~/.openclaw/openclaw.json; cwd hooks/ alone is a no-op");
|
|
314
376
|
break;
|
|
315
377
|
}
|
|
316
378
|
}
|
|
317
379
|
function materializeSubagent(h, name, sa, add, unsupported) {
|
|
318
380
|
switch (h) {
|
|
319
|
-
case
|
|
320
|
-
case
|
|
321
|
-
const fm = [
|
|
381
|
+
case "claude-code":
|
|
382
|
+
case "nanoclaw": {
|
|
383
|
+
const fm = ["---", `name: ${name}`];
|
|
322
384
|
if (sa.description)
|
|
323
385
|
fm.push(`description: ${JSON.stringify(sa.description)}`);
|
|
324
386
|
if (sa.model)
|
|
325
387
|
fm.push(`model: ${sa.model}`);
|
|
326
388
|
if (sa.tools)
|
|
327
|
-
fm.push(`tools: ${Object.keys(sa.tools)
|
|
328
|
-
|
|
329
|
-
|
|
389
|
+
fm.push(`tools: ${Object.keys(sa.tools)
|
|
390
|
+
.filter((t) => sa.tools?.[t])
|
|
391
|
+
.join(", ")}`);
|
|
392
|
+
fm.push("---", "", sa.prompt ?? sa.description ?? name);
|
|
393
|
+
add(`.claude/agents/${name}.md`, `${fm.join("\n")}\n`);
|
|
330
394
|
break;
|
|
331
395
|
}
|
|
332
|
-
case
|
|
333
|
-
const fm = [
|
|
396
|
+
case "opencode": {
|
|
397
|
+
const fm = [
|
|
398
|
+
"---",
|
|
399
|
+
`description: ${JSON.stringify(sa.description ?? name)}`,
|
|
400
|
+
];
|
|
334
401
|
if (sa.model)
|
|
335
402
|
fm.push(`model: ${sa.model}`);
|
|
336
|
-
fm.push(
|
|
337
|
-
add(`.opencode/agent/${name}.md`, fm.join(
|
|
403
|
+
fm.push("---", "", sa.prompt ?? "");
|
|
404
|
+
add(`.opencode/agent/${name}.md`, `${fm.join("\n")}\n`);
|
|
338
405
|
break;
|
|
339
406
|
}
|
|
340
|
-
case
|
|
341
|
-
add(`.gemini/agents/${name}.md`, `---\nname: ${name}\ndescription: ${JSON.stringify(sa.description ?? name)}\n---\n\n${sa.prompt ??
|
|
407
|
+
case "gemini":
|
|
408
|
+
add(`.gemini/agents/${name}.md`, `---\nname: ${name}\ndescription: ${JSON.stringify(sa.description ?? name)}\n---\n\n${sa.prompt ?? ""}\n`);
|
|
342
409
|
break;
|
|
343
|
-
case
|
|
410
|
+
case "codex": {
|
|
344
411
|
const lines = [`name = ${tomlStr(name)}`];
|
|
345
412
|
if (sa.description)
|
|
346
413
|
lines.push(`description = ${tomlStr(sa.description)}`);
|
|
@@ -348,34 +415,35 @@ function materializeSubagent(h, name, sa, add, unsupported) {
|
|
|
348
415
|
lines.push(`model = ${tomlStr(sa.model)}`);
|
|
349
416
|
if (sa.prompt)
|
|
350
417
|
lines.push(`prompt = ${tomlStr(sa.prompt)}`);
|
|
351
|
-
add(`.codex/agents/${name}.toml`, lines.join(
|
|
418
|
+
add(`.codex/agents/${name}.toml`, `${lines.join("\n")}\n`);
|
|
352
419
|
break;
|
|
353
420
|
}
|
|
354
421
|
default:
|
|
355
|
-
unsupported(
|
|
422
|
+
unsupported("subagents", `${h}: subagents have no cwd file (config/flag/global only); "${name}" not mounted`);
|
|
356
423
|
}
|
|
357
424
|
}
|
|
358
425
|
function materializeCommand(h, name, body, add, unsupported) {
|
|
359
426
|
switch (h) {
|
|
360
|
-
case
|
|
361
|
-
case
|
|
362
|
-
add(`.claude/commands/${name}.md`, body.endsWith(
|
|
427
|
+
case "claude-code":
|
|
428
|
+
case "nanoclaw":
|
|
429
|
+
add(`.claude/commands/${name}.md`, body.endsWith("\n") ? body : `${body}\n`);
|
|
363
430
|
break;
|
|
364
|
-
case
|
|
365
|
-
add(`.opencode/command/${name}.md`, body.endsWith(
|
|
431
|
+
case "opencode":
|
|
432
|
+
add(`.opencode/command/${name}.md`, body.endsWith("\n") ? body : `${body}\n`);
|
|
366
433
|
break;
|
|
367
|
-
case
|
|
434
|
+
case "gemini":
|
|
368
435
|
add(`.gemini/commands/${name}.toml`, `prompt = ${tomlStr(body)}\n`);
|
|
369
436
|
break;
|
|
370
|
-
case
|
|
371
|
-
add(`.pi/prompts/${name}.md`, body.endsWith(
|
|
437
|
+
case "pi":
|
|
438
|
+
add(`.pi/prompts/${name}.md`, body.endsWith("\n") ? body : `${body}\n`);
|
|
372
439
|
break;
|
|
373
|
-
case
|
|
374
|
-
unsupported(
|
|
440
|
+
case "kimi-code":
|
|
441
|
+
unsupported("commands", "kimi commands ARE skills (/skill:<name>) — provide as a skill instead");
|
|
375
442
|
break;
|
|
376
|
-
case
|
|
377
|
-
unsupported(
|
|
443
|
+
case "codex":
|
|
444
|
+
unsupported("commands", "codex project commands unsupported (~/.codex/prompts is user-global only)");
|
|
378
445
|
break;
|
|
379
|
-
default:
|
|
446
|
+
default:
|
|
447
|
+
unsupported("commands", `${h}: no cwd command path`);
|
|
380
448
|
}
|
|
381
449
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-profile-materialize",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Materialize an AgentProfile into the native per-harness workspace files (the one shared materializer for cli-bridge, agent-dev-container, and VB).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@tangle-network/agent-interface": "0.
|
|
19
|
+
"@tangle-network/agent-interface": "^0.18.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "25.6.0",
|