@microck/canonfig 2.0.0 → 2.1.0

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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/cli.js +1 -1
  3. package/dist/harness-configuration/adapters/amp.js +87 -0
  4. package/dist/harness-configuration/adapters/antigravity.js +50 -0
  5. package/dist/harness-configuration/adapters/claude.js +43 -0
  6. package/dist/harness-configuration/adapters/codex.js +60 -0
  7. package/dist/harness-configuration/adapters/copilot.js +79 -0
  8. package/dist/harness-configuration/adapters/cursor.js +67 -0
  9. package/dist/harness-configuration/adapters/descriptor.js +10 -0
  10. package/dist/harness-configuration/adapters/devin.js +66 -0
  11. package/dist/harness-configuration/adapters/droid.js +32 -0
  12. package/dist/harness-configuration/adapters/grok.js +44 -0
  13. package/dist/harness-configuration/adapters/hermes.js +105 -0
  14. package/dist/harness-configuration/adapters/index.js +50 -0
  15. package/dist/harness-configuration/adapters/kilo.js +18 -0
  16. package/dist/harness-configuration/adapters/kimi.js +148 -0
  17. package/dist/harness-configuration/adapters/omp.js +64 -0
  18. package/dist/harness-configuration/adapters/open-code-family.js +94 -0
  19. package/dist/harness-configuration/adapters/opencode.js +18 -0
  20. package/dist/harness-configuration/adapters/pi.js +93 -0
  21. package/dist/harness-configuration/adapters/qwen.js +164 -0
  22. package/dist/harness-configuration/adapters/shared-common.js +66 -0
  23. package/dist/harness-configuration/adapters/shared-documents.js +117 -0
  24. package/dist/harness-configuration/adapters/shared-hooks.js +186 -0
  25. package/dist/harness-configuration/adapters/shared-mcp.js +214 -0
  26. package/dist/harness-configuration/adapters/shared.js +4 -0
  27. package/dist/harness-configuration/adapters/tools.js +24 -0
  28. package/dist/harness-configuration/cli-arguments.js +105 -0
  29. package/dist/harness-configuration/cli-output.js +77 -0
  30. package/dist/harness-configuration/cli.js +196 -0
  31. package/dist/harness-configuration/core/compiler.js +175 -0
  32. package/dist/harness-configuration/core/config.js +74 -0
  33. package/dist/harness-configuration/core/diff.js +60 -0
  34. package/dist/harness-configuration/core/doctor.js +40 -0
  35. package/dist/harness-configuration/core/errors.js +13 -0
  36. package/dist/harness-configuration/core/filesystem.js +172 -0
  37. package/dist/harness-configuration/core/frontmatter.js +49 -0
  38. package/dist/harness-configuration/core/hash.js +4 -0
  39. package/dist/harness-configuration/core/path.js +50 -0
  40. package/dist/harness-configuration/core/planner.js +255 -0
  41. package/dist/harness-configuration/core/render-cleanup.js +91 -0
  42. package/dist/harness-configuration/core/render-json.js +195 -0
  43. package/dist/harness-configuration/core/render-text.js +134 -0
  44. package/dist/harness-configuration/core/render-utils.js +202 -0
  45. package/dist/harness-configuration/core/render.js +54 -0
  46. package/dist/harness-configuration/core/scaffold.js +103 -0
  47. package/dist/harness-configuration/core/schema-components.js +167 -0
  48. package/dist/harness-configuration/core/schema-config.js +98 -0
  49. package/dist/harness-configuration/core/schema-runtime.js +143 -0
  50. package/dist/harness-configuration/core/schema-types.js +8 -0
  51. package/dist/harness-configuration/core/schema.js +13 -0
  52. package/dist/harness-configuration/core/state.js +42 -0
  53. package/dist/harness-configuration/core/types.js +5 -0
  54. package/dist/harness-configuration/core/validation.js +113 -0
  55. package/dist/harness-configuration/templates/runtime.js +212 -0
  56. package/dist/runtime/main.js +20 -14
  57. package/package.json +5 -5
@@ -0,0 +1,105 @@
1
+ import { descriptor } from "./descriptor.js";
2
+ import { enabledHooks, readCanonfigText } from "./shared.js";
3
+ function projectContext(context, root) {
4
+ const rules = context.config.instructions.rules.length === 0
5
+ ? ["- No additional scoped rules are configured."]
6
+ : context.config.instructions.rules.map((rule) => {
7
+ const scope = rule.paths.length ? rule.paths.join(", ") : "all files";
8
+ return `- ${scope}: read \`.canonfig/${rule.file}\`.`;
9
+ });
10
+ const agents = context.config.agents.length === 0
11
+ ? ["- No canonical agent profiles are configured."]
12
+ : context.config.agents.map((agent) => `- ${agent.id}: ${agent.description} (source: \`.canonfig/${agent.file}\`).`);
13
+ const commands = context.config.commands.length === 0
14
+ ? ["- No canonical commands are configured."]
15
+ : context.config.commands.map((command) => `- ${command.id}: ${command.description} (source: \`.canonfig/${command.file}\`).`);
16
+ const skills = context.config.skills.roots.length === 0
17
+ ? ["- No canonical skill roots are configured."]
18
+ : context.config.skills.roots.map((skillRoot) => `- Inspect \`.canonfig/${skillRoot}\` or the projected \`.agents/skills\` directory for relevant SKILL.md packages.`);
19
+ return [
20
+ "# Canonfig project context for Hermes",
21
+ "",
22
+ root.trim(),
23
+ "",
24
+ "## Scoped rules",
25
+ "",
26
+ ...rules,
27
+ "",
28
+ "## Canonical skills",
29
+ "",
30
+ ...skills,
31
+ "",
32
+ "Hermes does not discover project-local skills as native profile skills. Read the relevant SKILL.md package before applying a documented workflow.",
33
+ "",
34
+ "## Canonical agent profiles",
35
+ "",
36
+ ...agents,
37
+ "",
38
+ "Treat an explicitly requested profile as task-specific instructions; Hermes does not install these as native profile agents.",
39
+ "",
40
+ "## Canonical commands",
41
+ "",
42
+ ...commands,
43
+ "",
44
+ "When the user invokes one of these command names, read its source and execute that workflow.",
45
+ ].join("\n");
46
+ }
47
+ export const hermesAdapter = {
48
+ descriptor: descriptor("hermes", "Hermes Agent", ["hermes"], [
49
+ "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/context-files.md",
50
+ "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/skills.md",
51
+ "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/mcp.md",
52
+ "https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md",
53
+ ], {
54
+ instructions: "native",
55
+ rules: "translated",
56
+ skills: "translated",
57
+ mcp: "lossy",
58
+ hooks: "lossy",
59
+ agents: "translated",
60
+ commands: "translated",
61
+ permissions: "lossy",
62
+ }, [
63
+ "Hermes natively loads project AGENTS.md and .hermes.md context, including nested AGENTS.md discovery.",
64
+ "Hermes skills, MCP servers, hooks, and permanent permissions are stored under the active HERMES_HOME profile, outside Canonfig's repository-confined writer.",
65
+ "The adapter projects skills, agents, and commands into .hermes.md as explicit source references rather than mutating profile-scoped state.",
66
+ ], "2026-08-26"),
67
+ async build(context) {
68
+ const diagnostics = [];
69
+ const root = await readCanonfigText(context, context.config.instructions.root);
70
+ const artifacts = [{
71
+ kind: "managed-text",
72
+ path: ".hermes.md",
73
+ owner: "hermes",
74
+ marker: "project-context",
75
+ comments: "html",
76
+ placement: "end",
77
+ content: projectContext(context, root),
78
+ }];
79
+ if (Object.keys(context.config.mcp.servers).length > 0) {
80
+ diagnostics.push({
81
+ level: "warning",
82
+ code: "HERMES_PROFILE_MCP_REQUIRED",
83
+ target: "hermes",
84
+ message: "Hermes MCP servers live in the active profile's config.yaml; Canonfig left them uninstalled rather than writing outside the repository.",
85
+ });
86
+ }
87
+ if (enabledHooks(context).length > 0) {
88
+ diagnostics.push({
89
+ level: "warning",
90
+ code: "HERMES_PROFILE_HOOKS_REQUIRED",
91
+ target: "hermes",
92
+ message: "Hermes shell hooks live in the active profile's config.yaml; Canonfig left them uninstalled rather than writing outside the repository.",
93
+ });
94
+ }
95
+ if (context.config.permissions.rules.length > 0) {
96
+ diagnostics.push({
97
+ level: "warning",
98
+ code: "HERMES_PROFILE_PERMISSIONS_REQUIRED",
99
+ target: "hermes",
100
+ message: "Hermes permission state is profile-scoped; canonical project permission rules were not installed automatically.",
101
+ });
102
+ }
103
+ return { artifacts, diagnostics };
104
+ },
105
+ };
@@ -0,0 +1,50 @@
1
+ export { codexAdapter } from "./codex.js";
2
+ export { claudeAdapter } from "./claude.js";
3
+ export { ampAdapter } from "./amp.js";
4
+ export { ompAdapter } from "./omp.js";
5
+ export { piAdapter } from "./pi.js";
6
+ export { droidAdapter } from "./droid.js";
7
+ export { cursorAdapter } from "./cursor.js";
8
+ export { devinAdapter } from "./devin.js";
9
+ export { opencodeAdapter } from "./opencode.js";
10
+ export { grokAdapter } from "./grok.js";
11
+ export { antigravityAdapter } from "./antigravity.js";
12
+ export { copilotAdapter } from "./copilot.js";
13
+ export { kimiAdapter } from "./kimi.js";
14
+ export { kiloAdapter } from "./kilo.js";
15
+ export { hermesAdapter } from "./hermes.js";
16
+ export { qwenAdapter } from "./qwen.js";
17
+ import { codexAdapter } from "./codex.js";
18
+ import { claudeAdapter } from "./claude.js";
19
+ import { ampAdapter } from "./amp.js";
20
+ import { ompAdapter } from "./omp.js";
21
+ import { piAdapter } from "./pi.js";
22
+ import { droidAdapter } from "./droid.js";
23
+ import { cursorAdapter } from "./cursor.js";
24
+ import { devinAdapter } from "./devin.js";
25
+ import { opencodeAdapter } from "./opencode.js";
26
+ import { grokAdapter } from "./grok.js";
27
+ import { antigravityAdapter } from "./antigravity.js";
28
+ import { copilotAdapter } from "./copilot.js";
29
+ import { kimiAdapter } from "./kimi.js";
30
+ import { kiloAdapter } from "./kilo.js";
31
+ import { hermesAdapter } from "./hermes.js";
32
+ import { qwenAdapter } from "./qwen.js";
33
+ export const BUILTIN_ADAPTERS = [
34
+ codexAdapter,
35
+ claudeAdapter,
36
+ ampAdapter,
37
+ ompAdapter,
38
+ piAdapter,
39
+ droidAdapter,
40
+ cursorAdapter,
41
+ devinAdapter,
42
+ opencodeAdapter,
43
+ grokAdapter,
44
+ antigravityAdapter,
45
+ copilotAdapter,
46
+ kimiAdapter,
47
+ kiloAdapter,
48
+ hermesAdapter,
49
+ qwenAdapter,
50
+ ];
@@ -0,0 +1,18 @@
1
+ import { createOpenCodeFamilyAdapter } from "./open-code-family.js";
2
+ export const kiloAdapter = createOpenCodeFamilyAdapter({
3
+ id: "kilo",
4
+ name: "Kilo Code CLI",
5
+ executables: ["kilo"],
6
+ docs: [
7
+ "https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-docs/pages/customize/agents-md.md",
8
+ "https://github.com/Kilo-Org/kilocode/blob/main/packages/kilo-docs/pages/customize/skills.md",
9
+ "https://github.com/Kilo-Org/kilocode/blob/main/packages/opencode/src/kilocode/docs/migration.md",
10
+ ],
11
+ schemaUrl: "https://app.kilo.ai/config.json",
12
+ configPath: "kilo.json",
13
+ resourceRoot: ".kilo",
14
+ notes: [
15
+ "Kilo's CLI is an OpenCode-derived harness; this adapter shares the OpenCode compiler and changes only native paths, schema, identity, and documentation.",
16
+ "Tool hooks compile into a Kilo TypeScript plugin; non-tool lifecycle events are not emitted.",
17
+ ],
18
+ });
@@ -0,0 +1,148 @@
1
+ import { markdownWithFrontmatter } from "../core/frontmatter.js";
2
+ import { descriptor } from "./descriptor.js";
3
+ import { agentDocuments, commandSkillArtifacts, enabledHooks, secretValue, skillArtifacts, } from "./shared.js";
4
+ import { nativeTools } from "./tools.js";
5
+ function remoteHeaders(server) {
6
+ const headers = {};
7
+ let bearerTokenEnvVar;
8
+ for (const [name, value] of Object.entries(server.headers)) {
9
+ if (name.toLowerCase() === "authorization"
10
+ && typeof value !== "string"
11
+ && value.default === undefined) {
12
+ bearerTokenEnvVar = value.fromEnv;
13
+ continue;
14
+ }
15
+ headers[name] = secretValue(value);
16
+ }
17
+ return {
18
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
19
+ ...(bearerTokenEnvVar === undefined ? {} : { bearerTokenEnvVar }),
20
+ };
21
+ }
22
+ function kimiMcpServer(server) {
23
+ const common = {
24
+ enabled: server.enabled,
25
+ ...(server.timeoutMs === undefined
26
+ ? {}
27
+ : {
28
+ startupTimeoutMs: server.timeoutMs,
29
+ toolTimeoutMs: server.timeoutMs,
30
+ }),
31
+ ...(server.enabledTools?.length ? { enabledTools: server.enabledTools } : {}),
32
+ ...(server.disabledTools?.length ? { disabledTools: server.disabledTools } : {}),
33
+ };
34
+ if (server.transport === "stdio") {
35
+ return {
36
+ transport: "stdio",
37
+ command: server.command,
38
+ ...(server.args.length ? { args: server.args } : {}),
39
+ ...(Object.keys(server.env).length
40
+ ? {
41
+ env: Object.fromEntries(Object.entries(server.env).map(([key, value]) => [key, secretValue(value)])),
42
+ }
43
+ : {}),
44
+ ...(server.cwd ? { cwd: server.cwd } : {}),
45
+ ...common,
46
+ };
47
+ }
48
+ return {
49
+ transport: server.transport === "sse" ? "sse" : "http",
50
+ url: server.url,
51
+ ...remoteHeaders(server),
52
+ ...common,
53
+ };
54
+ }
55
+ function kimiMcpMap(context) {
56
+ return Object.fromEntries(Object.entries(context.config.mcp.servers).map(([name, server]) => [
57
+ name,
58
+ kimiMcpServer(server),
59
+ ]));
60
+ }
61
+ export const kimiAdapter = {
62
+ descriptor: descriptor("kimi", "Kimi Code CLI", ["kimi"], [
63
+ "https://github.com/MoonshotAI/kimi-code/blob/main/docs/en/customization/skills.md",
64
+ "https://github.com/MoonshotAI/kimi-code/blob/main/docs/en/customization/mcp.md",
65
+ "https://github.com/MoonshotAI/kimi-code/blob/main/docs/en/customization/hooks.md",
66
+ "https://github.com/MoonshotAI/kimi-code/blob/main/docs/en/customization/agents.md",
67
+ ], {
68
+ instructions: "portable",
69
+ rules: "portable",
70
+ skills: "portable",
71
+ mcp: "native",
72
+ hooks: "lossy",
73
+ agents: "native",
74
+ commands: "translated",
75
+ permissions: "lossy",
76
+ }, [
77
+ "Kimi discovers project Agent Skills from .agents/skills, so canonical skills remain shared rather than copied.",
78
+ "Commands compile to Agent Skills and remain available through Kimi's /skill:<name> interface.",
79
+ "Kimi hooks and permanent permission rules live in the user-level config.toml; Canonfig does not mutate that profile-scoped file from a project projection.",
80
+ ], "2026-08-26"),
81
+ async build(context) {
82
+ const artifacts = [];
83
+ const diagnostics = [];
84
+ if (Object.keys(context.config.mcp.servers).length > 0) {
85
+ artifacts.push({
86
+ kind: "json",
87
+ path: ".kimi-code/mcp.json",
88
+ owner: "kimi",
89
+ operations: [{
90
+ kind: "managed-map",
91
+ path: ["mcpServers"],
92
+ entries: kimiMcpMap(context),
93
+ collision: "error",
94
+ }],
95
+ });
96
+ }
97
+ for (const { agent, content } of await agentDocuments(context)) {
98
+ const tools = nativeTools("kimi", agent);
99
+ artifacts.push({
100
+ kind: "replace",
101
+ path: `.kimi-code/agents/${agent.id}.md`,
102
+ owner: "kimi",
103
+ content: markdownWithFrontmatter({
104
+ name: agent.id,
105
+ description: agent.description,
106
+ ...(agent.model === "inherit" ? {} : { model: agent.model }),
107
+ tools,
108
+ ...(!agent.writable && tools.some((tool) => tool === "Edit" || tool === "Write")
109
+ ? { disallowedTools: ["Edit", "Write"] }
110
+ : {}),
111
+ }, content),
112
+ });
113
+ }
114
+ const commonSkillPaths = new Set((await skillArtifacts(context, ".agents/skills", "common"))
115
+ .map((artifact) => artifact.path));
116
+ for (const artifact of await commandSkillArtifacts(context, ".agents/skills", "kimi")) {
117
+ if (commonSkillPaths.has(artifact.path)) {
118
+ diagnostics.push({
119
+ level: "error",
120
+ code: "TRANSLATED_SKILL_COLLISION",
121
+ target: "kimi",
122
+ path: artifact.path,
123
+ message: `Kimi command output collides with a canonical skill at ${artifact.path}; rename the skill or command.`,
124
+ });
125
+ }
126
+ else {
127
+ artifacts.push(artifact);
128
+ }
129
+ }
130
+ if (enabledHooks(context).length > 0) {
131
+ diagnostics.push({
132
+ level: "warning",
133
+ code: "KIMI_PROFILE_HOOKS_REQUIRED",
134
+ target: "kimi",
135
+ message: "Kimi Code hooks are stored in the user-level config.toml, so project hooks were not installed automatically.",
136
+ });
137
+ }
138
+ if (context.config.permissions.rules.length > 0) {
139
+ diagnostics.push({
140
+ level: "warning",
141
+ code: "KIMI_PROFILE_PERMISSIONS_REQUIRED",
142
+ target: "kimi",
143
+ message: "Kimi Code permanent permission rules are profile-scoped; canonical project permission rules were not installed automatically.",
144
+ });
145
+ }
146
+ return { artifacts, diagnostics };
147
+ },
148
+ };
@@ -0,0 +1,64 @@
1
+ import { descriptor } from "./descriptor.js";
2
+ import { agentDocuments, agentMarkdown, commandDocuments, commandMarkdown, enabledHooks, hasEnabledMcpServers, jsonMcpArtifact, ruleDocuments, ruleMarkdown, skillArtifacts, standardMcpProjectionDiagnostics, } from "./shared.js";
3
+ import { nativeTools } from "./tools.js";
4
+ import { PI_PLUGIN_EVENT_MAP, piPluginSource } from "../templates/runtime.js";
5
+ const OMP_PLUGIN_EVENTS = new Set(Object.keys(PI_PLUGIN_EVENT_MAP));
6
+ export const ompAdapter = {
7
+ descriptor: descriptor("oh-my-pi", "Oh My Pi", ["omp"], [
8
+ "https://github.com/can1357/oh-my-pi/blob/main/docs/config-usage.md",
9
+ "https://github.com/can1357/oh-my-pi/blob/main/docs/mcp-config.md",
10
+ "https://github.com/can1357/oh-my-pi/blob/main/docs/hooks.md",
11
+ ], {
12
+ instructions: "portable",
13
+ rules: "native",
14
+ skills: "native",
15
+ mcp: "native",
16
+ hooks: "shim",
17
+ agents: "native",
18
+ commands: "native",
19
+ }, ["Lifecycle hooks compile to an executable Oh My Pi extension."]),
20
+ async build(context) {
21
+ const artifacts = [];
22
+ const diagnostics = [];
23
+ artifacts.push(...await skillArtifacts(context, ".omp/skills", "oh-my-pi"));
24
+ if (hasEnabledMcpServers(context)) {
25
+ diagnostics.push(...standardMcpProjectionDiagnostics(context, "oh-my-pi"));
26
+ artifacts.push(jsonMcpArtifact(".omp/mcp.json", "oh-my-pi", context));
27
+ }
28
+ const hooks = enabledHooks(context);
29
+ const supportedHooks = hooks.filter((hook) => OMP_PLUGIN_EVENTS.has(hook.event));
30
+ for (const hook of hooks) {
31
+ if (!OMP_PLUGIN_EVENTS.has(hook.event)) {
32
+ diagnostics.push({
33
+ level: "warning",
34
+ code: "HOOK_EVENT_UNSUPPORTED",
35
+ target: "oh-my-pi",
36
+ message: `Oh My Pi's generated extension cannot map hook event ${hook.event}; it was skipped.`,
37
+ });
38
+ }
39
+ }
40
+ if (supportedHooks.length > 0) {
41
+ artifacts.push({
42
+ kind: "replace",
43
+ path: ".omp/extensions/canonfig.ts",
44
+ owner: "oh-my-pi",
45
+ content: piPluginSource("oh-my-pi", supportedHooks),
46
+ });
47
+ }
48
+ for (const { rule, content } of await ruleDocuments(context)) {
49
+ artifacts.push({ kind: "replace", path: `.omp/rules/${rule.id}.md`, owner: "oh-my-pi", content: ruleMarkdown(rule, content) });
50
+ }
51
+ for (const { agent, content } of await agentDocuments(context)) {
52
+ artifacts.push({
53
+ kind: "replace",
54
+ path: `.omp/agents/${agent.id}.md`,
55
+ owner: "oh-my-pi",
56
+ content: agentMarkdown(agent, content, nativeTools("oh-my-pi", agent)),
57
+ });
58
+ }
59
+ for (const { command, content } of await commandDocuments(context)) {
60
+ artifacts.push({ kind: "replace", path: `.omp/commands/${command.id}.md`, owner: "oh-my-pi", content: commandMarkdown(command, content) });
61
+ }
62
+ return { artifacts, diagnostics };
63
+ },
64
+ };
@@ -0,0 +1,94 @@
1
+ import { markdownWithFrontmatter } from "../core/frontmatter.js";
2
+ import { openCodePluginSource } from "../templates/runtime.js";
3
+ import { descriptor } from "./descriptor.js";
4
+ import { agentDocuments, commandDocuments, commandMarkdown, enabledHooks, enabledMcpServerEntries, openCodeMcpMap, skillArtifacts, } from "./shared.js";
5
+ import { nativeTools, nativeToolsForCapabilities } from "./tools.js";
6
+ export function createOpenCodeFamilyAdapter(definition) {
7
+ return {
8
+ descriptor: descriptor(definition.id, definition.name, definition.executables, definition.docs, {
9
+ instructions: "portable",
10
+ rules: "portable",
11
+ skills: "native",
12
+ mcp: "native",
13
+ hooks: "shim",
14
+ agents: "native",
15
+ commands: "native",
16
+ }, definition.notes, "2026-08-26"),
17
+ async build(context) {
18
+ const artifacts = [];
19
+ const diagnostics = [];
20
+ const root = definition.resourceRoot;
21
+ artifacts.push(...await skillArtifacts(context, `${root}/skills`, definition.id));
22
+ if (Object.keys(context.config.mcp.servers).length > 0) {
23
+ artifacts.push({
24
+ kind: "json",
25
+ path: definition.configPath,
26
+ owner: definition.id,
27
+ operations: [{
28
+ kind: "managed-map",
29
+ path: ["mcp"],
30
+ entries: openCodeMcpMap(context),
31
+ collision: "error",
32
+ }],
33
+ }, {
34
+ kind: "json",
35
+ path: definition.configPath,
36
+ owner: definition.id,
37
+ rootDefaults: { $schema: definition.schemaUrl },
38
+ operations: [],
39
+ });
40
+ }
41
+ const hooks = enabledHooks(context);
42
+ const supportedHooks = hooks.filter((hook) => hook.event === "before_tool" || hook.event === "after_tool");
43
+ for (const hook of hooks) {
44
+ if (hook.event !== "before_tool" && hook.event !== "after_tool") {
45
+ diagnostics.push({
46
+ level: "warning",
47
+ code: "HOOK_EVENT_UNSUPPORTED",
48
+ target: definition.id,
49
+ message: `${definition.name}'s generated plugin cannot map hook event ${hook.event}; it was skipped.`,
50
+ });
51
+ }
52
+ }
53
+ if (supportedHooks.length > 0) {
54
+ artifacts.push({
55
+ kind: "replace",
56
+ path: `${root}/plugins/canonfig.ts`,
57
+ owner: definition.id,
58
+ content: openCodePluginSource(definition.id, supportedHooks),
59
+ });
60
+ }
61
+ const readOnlyTools = new Set(nativeToolsForCapabilities(definition.id, ["read", "search"]));
62
+ const mcpServerNames = enabledMcpServerEntries(context).map(([name]) => name);
63
+ for (const { agent, content } of await agentDocuments(context)) {
64
+ const permissions = Object.fromEntries(nativeTools(definition.id, agent).map((tool) => [
65
+ tool,
66
+ agent.writable || readOnlyTools.has(tool) ? "allow" : "deny",
67
+ ]));
68
+ for (const serverName of mcpServerNames) {
69
+ permissions[`${serverName}_*`] = agent.tools.includes("mcp") ? "allow" : "deny";
70
+ }
71
+ artifacts.push({
72
+ kind: "replace",
73
+ path: `${root}/agents/${agent.id}.md`,
74
+ owner: definition.id,
75
+ content: markdownWithFrontmatter({
76
+ description: agent.description,
77
+ mode: "subagent",
78
+ ...(agent.model === "inherit" ? {} : { model: agent.model }),
79
+ permission: permissions,
80
+ }, content),
81
+ });
82
+ }
83
+ for (const { command, content } of await commandDocuments(context)) {
84
+ artifacts.push({
85
+ kind: "replace",
86
+ path: `${root}/commands/${command.id}.md`,
87
+ owner: definition.id,
88
+ content: commandMarkdown(command, content),
89
+ });
90
+ }
91
+ return { artifacts, diagnostics };
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,18 @@
1
+ import { createOpenCodeFamilyAdapter } from "./open-code-family.js";
2
+ export const opencodeAdapter = createOpenCodeFamilyAdapter({
3
+ id: "opencode",
4
+ name: "OpenCode",
5
+ executables: ["opencode"],
6
+ docs: [
7
+ "https://opencode.ai/docs/config/",
8
+ "https://opencode.ai/docs/agents/",
9
+ "https://opencode.ai/docs/skills/",
10
+ "https://opencode.ai/docs/plugins/",
11
+ ],
12
+ schemaUrl: "https://opencode.ai/config.json",
13
+ configPath: "opencode.json",
14
+ resourceRoot: ".opencode",
15
+ notes: [
16
+ "Tool hooks compile into an OpenCode TypeScript plugin; non-tool lifecycle events are not emitted.",
17
+ ],
18
+ });
@@ -0,0 +1,93 @@
1
+ import { descriptor } from "./descriptor.js";
2
+ import { agentSkillArtifacts, commandDocuments, commandMarkdown, enabledHooks, hasEnabledMcpServers, piMcpMap, ruleDocuments, ruleMarkdown, skillArtifacts, } from "./shared.js";
3
+ import { PI_PLUGIN_EVENT_MAP, piPluginSource } from "../templates/runtime.js";
4
+ const PI_PLUGIN_EVENTS = new Set(Object.keys(PI_PLUGIN_EVENT_MAP));
5
+ function mcpPackageOption(options) {
6
+ if (options.mcpPackage === false)
7
+ return false;
8
+ if (typeof options.mcpPackage === "string" && options.mcpPackage.trim())
9
+ return options.mcpPackage.trim();
10
+ return "npm:pi-mcp-extension";
11
+ }
12
+ export const piAdapter = {
13
+ descriptor: descriptor("pi", "Pi Coding Agent", ["pi"], [
14
+ "https://pi.dev/docs/latest/settings",
15
+ "https://pi.dev/docs/latest/extensions",
16
+ "https://pi.dev/docs/latest/skills",
17
+ ], {
18
+ instructions: "portable",
19
+ rules: "translated",
20
+ skills: "native",
21
+ mcp: "shim",
22
+ hooks: "shim",
23
+ agents: "lossy",
24
+ commands: "native",
25
+ }, [
26
+ "Pi does not ship a core MCP client; Canonfig writes a compatible MCP file and registers a configurable third-party package.",
27
+ "Canonical agents compile to Agent Skills because Pi has no equivalent static subagent manifest.",
28
+ ]),
29
+ async build(context) {
30
+ const artifacts = [];
31
+ const diagnostics = [];
32
+ artifacts.push(...await skillArtifacts(context, ".pi/skills", "pi"));
33
+ if (hasEnabledMcpServers(context)) {
34
+ artifacts.push({
35
+ kind: "json",
36
+ path: ".pi/mcp.json",
37
+ owner: "pi",
38
+ operations: [{ kind: "managed-map", path: ["mcpServers"], entries: piMcpMap(context), collision: "error" }],
39
+ });
40
+ const mcpPackage = mcpPackageOption(context.targetOptions);
41
+ if (mcpPackage === false) {
42
+ diagnostics.push({
43
+ level: "warning",
44
+ code: "PI_MCP_PACKAGE_DISABLED",
45
+ target: "pi",
46
+ message: "Pi MCP output was generated, but no MCP extension package will be registered because targets.pi.options.mcpPackage is false.",
47
+ });
48
+ }
49
+ else {
50
+ artifacts.push({
51
+ kind: "json",
52
+ path: ".pi/settings.json",
53
+ owner: "pi",
54
+ operations: [{ kind: "managed-array", path: ["packages"], values: [mcpPackage] }],
55
+ });
56
+ diagnostics.push({
57
+ level: "warning",
58
+ code: "PI_THIRD_PARTY_MCP",
59
+ target: "pi",
60
+ message: `Pi MCP support depends on the executable third-party package ${mcpPackage}; review and trust it before loading project resources.`,
61
+ });
62
+ }
63
+ }
64
+ const hooks = enabledHooks(context);
65
+ const supportedHooks = hooks.filter((hook) => PI_PLUGIN_EVENTS.has(hook.event));
66
+ for (const hook of hooks) {
67
+ if (!PI_PLUGIN_EVENTS.has(hook.event)) {
68
+ diagnostics.push({
69
+ level: "warning",
70
+ code: "HOOK_EVENT_UNSUPPORTED",
71
+ target: "pi",
72
+ message: `Pi's generated extension cannot map hook event ${hook.event}; it was skipped.`,
73
+ });
74
+ }
75
+ }
76
+ if (supportedHooks.length > 0) {
77
+ artifacts.push({
78
+ kind: "replace",
79
+ path: ".pi/extensions/canonfig.ts",
80
+ owner: "pi",
81
+ content: piPluginSource("pi", supportedHooks),
82
+ });
83
+ }
84
+ for (const { rule, content } of await ruleDocuments(context)) {
85
+ artifacts.push({ kind: "replace", path: `.pi/rules/${rule.id}.md`, owner: "pi", content: ruleMarkdown(rule, content) });
86
+ }
87
+ for (const { command, content } of await commandDocuments(context)) {
88
+ artifacts.push({ kind: "replace", path: `.pi/prompts/${command.id}.md`, owner: "pi", content: commandMarkdown(command, content) });
89
+ }
90
+ artifacts.push(...await agentSkillArtifacts(context, ".pi/skills", "pi"));
91
+ return { artifacts, diagnostics };
92
+ },
93
+ };