@kuznai/inception-engine 0.8.0 → 0.9.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.
- package/README.md +39 -13
- package/dist/config/agents.js +34 -0
- package/dist/core/adapters/index.d.ts +11 -0
- package/dist/core/adapters/index.js +18 -0
- package/dist/core/adapters/mcp.d.ts +8 -0
- package/dist/core/adapters/mcp.js +51 -0
- package/dist/core/adapters/rules.d.ts +8 -0
- package/dist/core/adapters/rules.js +54 -0
- package/dist/core/deploy.js +15 -39
- package/dist/core/resolve.d.ts +1 -0
- package/dist/core/resolve.js +1 -1
- package/dist/core/revert.js +11 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.js +41 -0
- package/dist/schemas/manifest.d.ts +40 -4
- package/dist/schemas/manifest.js +13 -14
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Plant skills directly into the minds of your installed AI coding agents — Claude Code, Codex, Gemini CLI, Antigravity, OpenCode, and GitHub Copilot. One command. They'll think they thought of it themselves.
|
|
4
4
|
|
|
5
|
-
Today, inception-engine deploys skills, single files,
|
|
5
|
+
Today, inception-engine deploys skills, single files, JSON config patches, MCP server registrations, and agent instruction files to AI coding agents.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -38,13 +38,13 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
38
38
|
|
|
39
39
|
### Feature Support
|
|
40
40
|
|
|
41
|
-
| Feature |
|
|
42
|
-
|
|
43
|
-
| Skills (SKILL.md) |
|
|
44
|
-
| File write |
|
|
45
|
-
| Config patch (JSON merge) |
|
|
46
|
-
| MCP Servers |
|
|
47
|
-
| Agent Rules |
|
|
41
|
+
| Feature | Deploy | Revert |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Skills (SKILL.md) | All agents via manifest and CLI | All agents |
|
|
44
|
+
| File write | All agents via manifest and CLI | All agents |
|
|
45
|
+
| Config patch (JSON merge) | All agents via manifest and CLI | All agents |
|
|
46
|
+
| MCP Servers | claude-code, gemini-cli; stub warning for other agents | claude-code, gemini-cli |
|
|
47
|
+
| Agent Rules | claude-code, codex, gemini-cli, opencode; stub warning for other agents | claude-code, codex, gemini-cli, opencode |
|
|
48
48
|
|
|
49
49
|
## Manifest Format
|
|
50
50
|
|
|
@@ -75,8 +75,20 @@ Create an `inception.json` file at the root of your skills directory:
|
|
|
75
75
|
"agents": ["claude-code"]
|
|
76
76
|
}
|
|
77
77
|
],
|
|
78
|
-
"mcpServers": [
|
|
79
|
-
|
|
78
|
+
"mcpServers": [
|
|
79
|
+
{
|
|
80
|
+
"name": "my-server",
|
|
81
|
+
"agents": ["claude-code", "gemini-cli"],
|
|
82
|
+
"config": { "command": "npx", "args": ["-y", "my-mcp-server"] }
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"agentRules": [
|
|
86
|
+
{
|
|
87
|
+
"name": "my-rules",
|
|
88
|
+
"path": "rules/CLAUDE.md",
|
|
89
|
+
"agents": ["claude-code"]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
80
92
|
}
|
|
81
93
|
```
|
|
82
94
|
|
|
@@ -102,7 +114,21 @@ Each **config** entry applies a [JSON merge patch (RFC 7386)](https://datatracke
|
|
|
102
114
|
|
|
103
115
|
The engine records an undo-patch for each config-patch deployment so that `revert` can restore the original values.
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
Each **mcpServer** entry registers an MCP server into the agent's config file by applying a JSON merge patch under the `mcpServers` key:
|
|
118
|
+
|
|
119
|
+
- **name** - Unique identifier (same format as skill names); used as the server's key in the config
|
|
120
|
+
- **agents** - Array of agent IDs to register this server with
|
|
121
|
+
- **config** - Raw server descriptor object (e.g. `{ "command": "...", "args": [...] }` for stdio transport). The exact shape is passed through verbatim; agent adapters handle agent-specific requirements.
|
|
122
|
+
|
|
123
|
+
MCP server registration is currently supported for `claude-code` (`~/.claude.json`) and `gemini-cli` (`~/.gemini/settings.json`). Other agents emit a warning and are skipped. Revert removes the registered server entry from the config file.
|
|
124
|
+
|
|
125
|
+
Each **agentRules** entry deploys a Markdown instruction file to the agent's global rules location:
|
|
126
|
+
|
|
127
|
+
- **name** - Unique identifier (same format as skill names)
|
|
128
|
+
- **path** - Relative path to the source Markdown file within the repo
|
|
129
|
+
- **agents** - Array of agent IDs to deploy this file to
|
|
130
|
+
|
|
131
|
+
Agent rules deployment is currently supported for `claude-code` (`~/.claude/CLAUDE.md`), `codex` (`~/.codex/AGENTS.md`), `gemini-cli` (`~/.gemini/GEMINI.md`), and `opencode` (`~/.config/opencode/AGENTS.md`). Other agents emit a warning and are skipped. Revert removes the deployed rules file.
|
|
106
132
|
|
|
107
133
|
## Creating Skills
|
|
108
134
|
|
|
@@ -119,7 +145,7 @@ description: What this skill does and when to use it
|
|
|
119
145
|
Instructions for the AI agent...
|
|
120
146
|
```
|
|
121
147
|
|
|
122
|
-
The `name` and `description` fields in the frontmatter are
|
|
148
|
+
The `name` and `description` fields in the frontmatter are used by most agents. The description determines when the agent activates the skill. inception-engine does not currently validate the frontmatter — missing or malformed fields may cause the skill to be ignored or misbehave at the agent level.
|
|
123
149
|
|
|
124
150
|
## CLI Reference
|
|
125
151
|
|
|
@@ -201,7 +227,7 @@ inception-engine maintains a centralized deployment registry at `~/.inception-en
|
|
|
201
227
|
|
|
202
228
|
- **Strong binding**: Each registry entry binds a specific target path to its skill, agent, and action kind, with action-specific provenance fields (`source` and `method` for skill-dir and file-write; `patch` and `undoPatch` for config-patch). A target is only considered managed if all relevant fields match — a stray entry or a different deployment cannot satisfy the check.
|
|
203
229
|
|
|
204
|
-
- **Atomic redeploy**: When overwriting an existing managed target, the engine renames the old target to a backup, creates the new deployment, and only removes the backup on success. If the new deployment fails, the backup is restored.
|
|
230
|
+
- **Atomic redeploy**: When overwriting an existing managed `skill-dir` target, the engine renames the old target to a backup, creates the new deployment, and only removes the backup on success. If the new deployment fails, the backup is restored. `file-write` and `config-patch` deployments write directly to the target without this backup/rollback model.
|
|
205
231
|
|
|
206
232
|
- **Cross-platform**: The registry uses the same resolved home directory as the rest of the tool, including sudo scenarios on POSIX and elevated PowerShell on Windows.
|
|
207
233
|
|
package/dist/config/agents.js
CHANGED
|
@@ -15,6 +15,16 @@ export const AGENT_REGISTRY = [
|
|
|
15
15
|
skills: "documented",
|
|
16
16
|
detectPaths: "documented",
|
|
17
17
|
detectBinary: "documented",
|
|
18
|
+
mcpConfig: "documented",
|
|
19
|
+
agentRules: "documented",
|
|
20
|
+
},
|
|
21
|
+
mcpConfigPath: {
|
|
22
|
+
posix: ["{home}", ".claude.json"],
|
|
23
|
+
windows: ["{home}", ".claude.json"],
|
|
24
|
+
},
|
|
25
|
+
agentRulesPath: {
|
|
26
|
+
posix: ["{home}", ".claude", "CLAUDE.md"],
|
|
27
|
+
windows: ["{home}", ".claude", "CLAUDE.md"],
|
|
18
28
|
},
|
|
19
29
|
},
|
|
20
30
|
{
|
|
@@ -33,6 +43,12 @@ export const AGENT_REGISTRY = [
|
|
|
33
43
|
skills: "documented",
|
|
34
44
|
detectPaths: "documented",
|
|
35
45
|
detectBinary: "documented",
|
|
46
|
+
agentRules: "documented",
|
|
47
|
+
},
|
|
48
|
+
// mcpConfigPath omitted: Codex uses TOML config, not JSON — not supported by config-patch
|
|
49
|
+
agentRulesPath: {
|
|
50
|
+
posix: ["{home}", ".codex", "AGENTS.md"],
|
|
51
|
+
windows: ["{home}", ".codex", "AGENTS.md"],
|
|
36
52
|
},
|
|
37
53
|
},
|
|
38
54
|
{
|
|
@@ -51,6 +67,16 @@ export const AGENT_REGISTRY = [
|
|
|
51
67
|
skills: "documented",
|
|
52
68
|
detectPaths: "documented",
|
|
53
69
|
detectBinary: "documented",
|
|
70
|
+
mcpConfig: "documented",
|
|
71
|
+
agentRules: "documented",
|
|
72
|
+
},
|
|
73
|
+
mcpConfigPath: {
|
|
74
|
+
posix: ["{home}", ".gemini", "settings.json"],
|
|
75
|
+
windows: ["{home}", ".gemini", "settings.json"],
|
|
76
|
+
},
|
|
77
|
+
agentRulesPath: {
|
|
78
|
+
posix: ["{home}", ".gemini", "GEMINI.md"],
|
|
79
|
+
windows: ["{home}", ".gemini", "GEMINI.md"],
|
|
54
80
|
},
|
|
55
81
|
},
|
|
56
82
|
{
|
|
@@ -70,6 +96,7 @@ export const AGENT_REGISTRY = [
|
|
|
70
96
|
detectPaths: "implementation-only",
|
|
71
97
|
detectBinary: "provisional",
|
|
72
98
|
},
|
|
99
|
+
// mcpConfigPath and agentRulesPath omitted: paths not documented strongly enough for this release
|
|
73
100
|
},
|
|
74
101
|
{
|
|
75
102
|
id: "opencode",
|
|
@@ -87,6 +114,12 @@ export const AGENT_REGISTRY = [
|
|
|
87
114
|
skills: "documented",
|
|
88
115
|
detectPaths: "documented",
|
|
89
116
|
detectBinary: "documented",
|
|
117
|
+
agentRules: "documented",
|
|
118
|
+
},
|
|
119
|
+
// mcpConfigPath omitted: OpenCode uses opencode.json (TOML-adjacent format), not plain JSON
|
|
120
|
+
agentRulesPath: {
|
|
121
|
+
posix: ["{xdg_config}", "opencode", "AGENTS.md"],
|
|
122
|
+
windows: ["{appdata}", "opencode", "AGENTS.md"],
|
|
90
123
|
},
|
|
91
124
|
},
|
|
92
125
|
{
|
|
@@ -106,6 +139,7 @@ export const AGENT_REGISTRY = [
|
|
|
106
139
|
detectPaths: "documented",
|
|
107
140
|
detectBinary: "documented",
|
|
108
141
|
},
|
|
142
|
+
// mcpConfigPath and agentRulesPath omitted: rules live in repo, not home dir; MCP config unclear
|
|
109
143
|
policyNote: "Organization policies may override locally deployed skills. Verify with your GitHub org admin if deployed skills are not active.",
|
|
110
144
|
},
|
|
111
145
|
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpServerEntry, AgentRuleEntry } from "../../schemas/manifest.ts";
|
|
2
|
+
import type { AgentId, ConfigPatchDeployAction, FileWriteDeployAction, PlanWarning } from "../../types.ts";
|
|
3
|
+
import { compileMcpServerReverts } from "./mcp.ts";
|
|
4
|
+
import { compileAgentRuleReverts } from "./rules.ts";
|
|
5
|
+
export { compileMcpServerReverts, compileAgentRuleReverts };
|
|
6
|
+
export type AdapterAction = ConfigPatchDeployAction | FileWriteDeployAction;
|
|
7
|
+
export interface AdapterResult {
|
|
8
|
+
actions: AdapterAction[];
|
|
9
|
+
warnings: PlanWarning[];
|
|
10
|
+
}
|
|
11
|
+
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string): Promise<AdapterResult>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
|
|
2
|
+
import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
|
|
3
|
+
export { compileMcpServerReverts, compileAgentRuleReverts };
|
|
4
|
+
export async function compileAdapterActions(mcpServers, agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
5
|
+
const actions = [];
|
|
6
|
+
const warnings = [];
|
|
7
|
+
for (const entry of mcpServers) {
|
|
8
|
+
const r = compileMcpServerActions(entry, detectedAgents, home);
|
|
9
|
+
actions.push(...r.actions);
|
|
10
|
+
warnings.push(...r.warnings);
|
|
11
|
+
}
|
|
12
|
+
for (const entry of agentRules) {
|
|
13
|
+
const r = await compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
14
|
+
actions.push(...r.actions);
|
|
15
|
+
warnings.push(...r.warnings);
|
|
16
|
+
}
|
|
17
|
+
return { actions, warnings };
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServerEntry } from "../../schemas/manifest.ts";
|
|
2
|
+
import type { AgentId, ConfigPatchDeployAction, ConfigPatchRevertAction, PlanWarning } from "../../types.ts";
|
|
3
|
+
export interface McpAdapterResult {
|
|
4
|
+
actions: ConfigPatchDeployAction[];
|
|
5
|
+
warnings: PlanWarning[];
|
|
6
|
+
}
|
|
7
|
+
export declare function compileMcpServerActions(entry: McpServerEntry, detectedAgents: AgentId[], home: string): McpAdapterResult;
|
|
8
|
+
export declare function compileMcpServerReverts(entry: McpServerEntry, agentFilter: AgentId[] | null, home: string): ConfigPatchRevertAction[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
|
+
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
|
+
export function compileMcpServerActions(entry, detectedAgents, home) {
|
|
5
|
+
const actions = [];
|
|
6
|
+
const warnings = [];
|
|
7
|
+
const platform = getPlatformKey();
|
|
8
|
+
for (const agentId of entry.agents) {
|
|
9
|
+
if (!detectedAgents.includes(agentId))
|
|
10
|
+
continue;
|
|
11
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
12
|
+
if (!agent?.mcpConfigPath) {
|
|
13
|
+
warnings.push({
|
|
14
|
+
kind: "confidence",
|
|
15
|
+
message: `mcpServers: agent "${agentId}" does not have a documented MCP config path — skipping "${entry.name}"`,
|
|
16
|
+
});
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const target = resolvePlaceholders(agent.mcpConfigPath[platform], "", home);
|
|
20
|
+
// Ensure no stray empty segments collapse the path unexpectedly
|
|
21
|
+
const resolvedTarget = path.resolve(target);
|
|
22
|
+
actions.push({
|
|
23
|
+
kind: "config-patch",
|
|
24
|
+
skill: entry.name,
|
|
25
|
+
agent: agentId,
|
|
26
|
+
target: resolvedTarget,
|
|
27
|
+
patch: { mcpServers: { [entry.name]: entry.config } },
|
|
28
|
+
confidence: agent.provenance.mcpConfig ?? "provisional",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return { actions, warnings };
|
|
32
|
+
}
|
|
33
|
+
export function compileMcpServerReverts(entry, agentFilter, home) {
|
|
34
|
+
const actions = [];
|
|
35
|
+
const platform = getPlatformKey();
|
|
36
|
+
for (const agentId of entry.agents) {
|
|
37
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
38
|
+
continue;
|
|
39
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
40
|
+
if (!agent?.mcpConfigPath)
|
|
41
|
+
continue;
|
|
42
|
+
const target = path.resolve(resolvePlaceholders(agent.mcpConfigPath[platform], "", home));
|
|
43
|
+
actions.push({
|
|
44
|
+
kind: "config-patch",
|
|
45
|
+
skill: entry.name,
|
|
46
|
+
agent: agentId,
|
|
47
|
+
target,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return actions;
|
|
51
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentRuleEntry } from "../../schemas/manifest.ts";
|
|
2
|
+
import type { AgentId, FileWriteDeployAction, FileWriteRevertAction, PlanWarning } from "../../types.ts";
|
|
3
|
+
export interface RulesAdapterResult {
|
|
4
|
+
actions: FileWriteDeployAction[];
|
|
5
|
+
warnings: PlanWarning[];
|
|
6
|
+
}
|
|
7
|
+
export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string): Promise<RulesAdapterResult>;
|
|
8
|
+
export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string): FileWriteRevertAction[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
|
+
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
|
+
import { validateSourceFile, validateSourcePath } from "../validation.js";
|
|
5
|
+
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
6
|
+
const actions = [];
|
|
7
|
+
const warnings = [];
|
|
8
|
+
const platform = getPlatformKey();
|
|
9
|
+
// Validate the source file once (before iterating agents) since it is shared.
|
|
10
|
+
const source = path.resolve(sourceDir, entry.path);
|
|
11
|
+
await validateSourcePath(source, entry.path, resolvedSourceDir, realRoot);
|
|
12
|
+
await validateSourceFile(source, entry.path);
|
|
13
|
+
for (const agentId of entry.agents) {
|
|
14
|
+
if (!detectedAgents.includes(agentId))
|
|
15
|
+
continue;
|
|
16
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
17
|
+
if (!agent?.agentRulesPath) {
|
|
18
|
+
warnings.push({
|
|
19
|
+
kind: "confidence",
|
|
20
|
+
message: `agentRules: agent "${agentId}" does not have a documented rules file path — skipping "${entry.name}"`,
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const target = resolvePlaceholders(agent.agentRulesPath[platform], "", home);
|
|
25
|
+
actions.push({
|
|
26
|
+
kind: "file-write",
|
|
27
|
+
skill: entry.name,
|
|
28
|
+
agent: agentId,
|
|
29
|
+
source,
|
|
30
|
+
target,
|
|
31
|
+
confidence: agent.provenance.agentRules ?? "provisional",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return { actions, warnings };
|
|
35
|
+
}
|
|
36
|
+
export function compileAgentRuleReverts(entry, agentFilter, home) {
|
|
37
|
+
const actions = [];
|
|
38
|
+
const platform = getPlatformKey();
|
|
39
|
+
for (const agentId of entry.agents) {
|
|
40
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
41
|
+
continue;
|
|
42
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
43
|
+
if (!agent?.agentRulesPath)
|
|
44
|
+
continue;
|
|
45
|
+
const target = resolvePlaceholders(agent.agentRulesPath[platform], "", home);
|
|
46
|
+
actions.push({
|
|
47
|
+
kind: "file-write",
|
|
48
|
+
skill: entry.name,
|
|
49
|
+
agent: agentId,
|
|
50
|
+
target,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return actions;
|
|
54
|
+
}
|
package/dist/core/deploy.js
CHANGED
|
@@ -5,7 +5,9 @@ import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
|
5
5
|
import { UserError } from "../errors.js";
|
|
6
6
|
import { logger } from "../logger.js";
|
|
7
7
|
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
8
|
+
import { compileAdapterActions } from "./adapters/index.js";
|
|
8
9
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
10
|
+
import { sourceAccessError, validateSourceFile, validateSourcePath, } from "./validation.js";
|
|
9
11
|
function isPlainObject(v) {
|
|
10
12
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
11
13
|
}
|
|
@@ -35,7 +37,13 @@ async function readJsonConfig(filePath) {
|
|
|
35
37
|
function computeUndoPatch(original, patch) {
|
|
36
38
|
const undoPatch = {};
|
|
37
39
|
for (const key of Object.keys(patch)) {
|
|
38
|
-
|
|
40
|
+
const patchVal = patch[key];
|
|
41
|
+
if (isPlainObject(patchVal) && isPlainObject(original[key])) {
|
|
42
|
+
undoPatch[key] = computeUndoPatch(original[key], patchVal);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
undoPatch[key] = key in original ? original[key] : null;
|
|
46
|
+
}
|
|
39
47
|
}
|
|
40
48
|
return undoPatch;
|
|
41
49
|
}
|
|
@@ -45,21 +53,15 @@ function applyMergePatch(original, patch) {
|
|
|
45
53
|
if (value === null) {
|
|
46
54
|
delete patched[key];
|
|
47
55
|
}
|
|
56
|
+
else if (isPlainObject(value) && isPlainObject(patched[key])) {
|
|
57
|
+
patched[key] = applyMergePatch(patched[key], value);
|
|
58
|
+
}
|
|
48
59
|
else {
|
|
49
60
|
patched[key] = value;
|
|
50
61
|
}
|
|
51
62
|
}
|
|
52
63
|
return patched;
|
|
53
64
|
}
|
|
54
|
-
function sourceAccessError(err, sourcePath) {
|
|
55
|
-
const code = err.code;
|
|
56
|
-
if (code === "ENOENT")
|
|
57
|
-
return `Source not found: ${sourcePath}`;
|
|
58
|
-
if (code === "EACCES" || code === "EPERM")
|
|
59
|
-
return `Permission denied accessing source: ${sourcePath}`;
|
|
60
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
61
|
-
return `Failed to access source ${sourcePath}: ${detail}`;
|
|
62
|
-
}
|
|
63
65
|
function resolveTargetTemplate(template, home) {
|
|
64
66
|
const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
65
67
|
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
@@ -69,18 +71,6 @@ function resolveTargetTemplate(template, home) {
|
|
|
69
71
|
.replace("{appdata}", appdata)
|
|
70
72
|
.replace("{xdg_config}", xdgConfig);
|
|
71
73
|
}
|
|
72
|
-
async function validateSourceFile(sourcePath, manifestPath) {
|
|
73
|
-
let stat;
|
|
74
|
-
try {
|
|
75
|
-
stat = await lstat(sourcePath);
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
throw new UserError("DEPLOY_FAILED", sourceAccessError(err, manifestPath));
|
|
79
|
-
}
|
|
80
|
-
if (!stat.isFile()) {
|
|
81
|
-
throw new UserError("DEPLOY_FAILED", `Source is not a file: ${manifestPath}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
74
|
function detectCollisions(actions) {
|
|
85
75
|
const seen = new Map();
|
|
86
76
|
const warnings = [];
|
|
@@ -194,9 +184,12 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
194
184
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
195
185
|
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
196
186
|
];
|
|
187
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
188
|
+
actions.push(...adapterResult.actions);
|
|
197
189
|
const warnings = [
|
|
198
190
|
...detectAmbiguities(detectedAgents),
|
|
199
191
|
...detectCollisions(actions),
|
|
192
|
+
...adapterResult.warnings,
|
|
200
193
|
];
|
|
201
194
|
return { actions, warnings };
|
|
202
195
|
}
|
|
@@ -386,23 +379,6 @@ async function deployConfigPatch(action, dryRun, verbose, home, planned) {
|
|
|
386
379
|
return { error: msg };
|
|
387
380
|
}
|
|
388
381
|
}
|
|
389
|
-
async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot) {
|
|
390
|
-
if (!source.startsWith(resolvedSourceDir + path.sep)) {
|
|
391
|
-
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root: ${source}`);
|
|
392
|
-
}
|
|
393
|
-
try {
|
|
394
|
-
const realSource = await realpath(source);
|
|
395
|
-
if (realSource !== realRoot &&
|
|
396
|
-
!realSource.startsWith(realRoot + path.sep)) {
|
|
397
|
-
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root via symlink: ${source} -> ${realSource}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
catch (err) {
|
|
401
|
-
if (err instanceof UserError)
|
|
402
|
-
throw err;
|
|
403
|
-
// Source doesn't exist yet — will be caught during execute
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
382
|
async function validateSkillContract(source, skillPath) {
|
|
407
383
|
let stat;
|
|
408
384
|
try {
|
package/dist/core/resolve.d.ts
CHANGED
|
@@ -9,3 +9,4 @@ export declare function resolveAgentSkillPathFor(agent: AgentConfig, skillName:
|
|
|
9
9
|
export declare function resolveAgentDetectPathFor(agent: AgentConfig, home: string, platform: "posix" | "windows"): string;
|
|
10
10
|
export declare function resolveAgentSkillPath(agent: AgentConfig, skillName: string, home: string): string;
|
|
11
11
|
export declare function resolveAgentDetectPath(agent: AgentConfig, home: string): string;
|
|
12
|
+
export declare function resolvePlaceholders(segments: string[], skillName: string, home: string): string;
|
package/dist/core/resolve.js
CHANGED
|
@@ -96,7 +96,7 @@ export function resolveAgentSkillPath(agent, skillName, home) {
|
|
|
96
96
|
export function resolveAgentDetectPath(agent, home) {
|
|
97
97
|
return resolveAgentDetectPathFor(agent, home, getPlatformKey());
|
|
98
98
|
}
|
|
99
|
-
function resolvePlaceholders(segments, skillName, home) {
|
|
99
|
+
export function resolvePlaceholders(segments, skillName, home) {
|
|
100
100
|
const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
101
101
|
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
102
102
|
const xdgConfig = xdgRaw && path.isAbsolute(xdgRaw) ? xdgRaw : path.join(home, ".config");
|
package/dist/core/revert.js
CHANGED
|
@@ -2,6 +2,7 @@ import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
4
|
import { logger } from "../logger.js";
|
|
5
|
+
import { compileMcpServerReverts, compileAgentRuleReverts, } from "./adapters/index.js";
|
|
5
6
|
import { lookupDeployment, unregisterDeployment } from "./ownership.js";
|
|
6
7
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
7
8
|
function resolveTargetTemplate(template, home) {
|
|
@@ -75,6 +76,8 @@ export function planRevert(manifest, detectedAgents, home) {
|
|
|
75
76
|
...buildSkillDirReverts(manifest, home, detectedAgents),
|
|
76
77
|
...buildFileWriteReverts(manifest, home, detectedAgents),
|
|
77
78
|
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
79
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home)),
|
|
80
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home)),
|
|
78
81
|
];
|
|
79
82
|
}
|
|
80
83
|
export function planRevertAll(manifest, home) {
|
|
@@ -82,6 +85,8 @@ export function planRevertAll(manifest, home) {
|
|
|
82
85
|
...buildSkillDirReverts(manifest, home, null),
|
|
83
86
|
...buildFileWriteReverts(manifest, home, null),
|
|
84
87
|
...buildConfigPatchReverts(manifest, home, null),
|
|
88
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home)),
|
|
89
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home)),
|
|
85
90
|
];
|
|
86
91
|
}
|
|
87
92
|
function recordOutcome(result, action, counts, failed) {
|
|
@@ -109,12 +114,18 @@ async function readJsonConfig(filePath) {
|
|
|
109
114
|
}
|
|
110
115
|
return parsed;
|
|
111
116
|
}
|
|
117
|
+
function isPlainObject(v) {
|
|
118
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
119
|
+
}
|
|
112
120
|
function applyUndoPatch(current, undoPatch) {
|
|
113
121
|
const restored = { ...current };
|
|
114
122
|
for (const [key, originalValue] of Object.entries(undoPatch)) {
|
|
115
123
|
if (originalValue === null) {
|
|
116
124
|
delete restored[key];
|
|
117
125
|
}
|
|
126
|
+
else if (isPlainObject(originalValue) && isPlainObject(restored[key])) {
|
|
127
|
+
restored[key] = applyUndoPatch(restored[key], originalValue);
|
|
128
|
+
}
|
|
118
129
|
else {
|
|
119
130
|
restored[key] = originalValue;
|
|
120
131
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function sourceAccessError(err: unknown, sourcePath: string): string;
|
|
2
|
+
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
|
+
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { UserError } from "../errors.js";
|
|
4
|
+
export function sourceAccessError(err, sourcePath) {
|
|
5
|
+
const code = err.code;
|
|
6
|
+
if (code === "ENOENT")
|
|
7
|
+
return `Source not found: ${sourcePath}`;
|
|
8
|
+
if (code === "EACCES" || code === "EPERM")
|
|
9
|
+
return `Permission denied accessing source: ${sourcePath}`;
|
|
10
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
11
|
+
return `Failed to access source ${sourcePath}: ${detail}`;
|
|
12
|
+
}
|
|
13
|
+
export async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot) {
|
|
14
|
+
if (!source.startsWith(resolvedSourceDir + path.sep)) {
|
|
15
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root: ${source}`);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const realSource = await realpath(source);
|
|
19
|
+
if (realSource !== realRoot &&
|
|
20
|
+
!realSource.startsWith(realRoot + path.sep)) {
|
|
21
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root via symlink: ${source} -> ${realSource}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (err instanceof UserError)
|
|
26
|
+
throw err;
|
|
27
|
+
// Source doesn't exist yet — will be caught during execute
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function validateSourceFile(sourcePath, manifestPath) {
|
|
31
|
+
let stat;
|
|
32
|
+
try {
|
|
33
|
+
stat = await lstat(sourcePath);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
throw new UserError("DEPLOY_FAILED", sourceAccessError(err, manifestPath));
|
|
37
|
+
}
|
|
38
|
+
if (!stat.isFile()) {
|
|
39
|
+
throw new UserError("DEPLOY_FAILED", `Source is not a file: ${manifestPath}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -50,10 +50,28 @@ export declare const ConfigEntrySchema: z.ZodObject<{
|
|
|
50
50
|
}, z.core.$strip>;
|
|
51
51
|
export declare const McpServerEntrySchema: z.ZodObject<{
|
|
52
52
|
name: z.ZodString;
|
|
53
|
-
|
|
53
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
54
|
+
"claude-code": "claude-code";
|
|
55
|
+
codex: "codex";
|
|
56
|
+
"gemini-cli": "gemini-cli";
|
|
57
|
+
antigravity: "antigravity";
|
|
58
|
+
opencode: "opencode";
|
|
59
|
+
"github-copilot": "github-copilot";
|
|
60
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
61
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
62
|
+
}, z.core.$strip>;
|
|
54
63
|
export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
55
64
|
name: z.ZodString;
|
|
56
|
-
|
|
65
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
66
|
+
"claude-code": "claude-code";
|
|
67
|
+
codex: "codex";
|
|
68
|
+
"gemini-cli": "gemini-cli";
|
|
69
|
+
antigravity: "antigravity";
|
|
70
|
+
opencode: "opencode";
|
|
71
|
+
"github-copilot": "github-copilot";
|
|
72
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
73
|
+
path: z.ZodString;
|
|
74
|
+
}, z.core.$strip>;
|
|
57
75
|
export declare const ManifestSchema: z.ZodObject<{
|
|
58
76
|
skills: z.ZodArray<z.ZodObject<{
|
|
59
77
|
name: z.ZodString;
|
|
@@ -95,10 +113,28 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
95
113
|
}, z.core.$strip>>>;
|
|
96
114
|
mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
97
115
|
name: z.ZodString;
|
|
98
|
-
|
|
116
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
117
|
+
"claude-code": "claude-code";
|
|
118
|
+
codex: "codex";
|
|
119
|
+
"gemini-cli": "gemini-cli";
|
|
120
|
+
antigravity: "antigravity";
|
|
121
|
+
opencode: "opencode";
|
|
122
|
+
"github-copilot": "github-copilot";
|
|
123
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
124
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
125
|
+
}, z.core.$strip>>>;
|
|
99
126
|
agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
100
127
|
name: z.ZodString;
|
|
101
|
-
|
|
128
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
129
|
+
"claude-code": "claude-code";
|
|
130
|
+
codex: "codex";
|
|
131
|
+
"gemini-cli": "gemini-cli";
|
|
132
|
+
antigravity: "antigravity";
|
|
133
|
+
opencode: "opencode";
|
|
134
|
+
"github-copilot": "github-copilot";
|
|
135
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
136
|
+
path: z.ZodString;
|
|
137
|
+
}, z.core.$strip>>>;
|
|
102
138
|
}, z.core.$strip>;
|
|
103
139
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
104
140
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -71,20 +71,19 @@ export const ConfigEntrySchema = z.object({
|
|
|
71
71
|
patch: z.record(z.string(), z.unknown()),
|
|
72
72
|
agents: agentsField,
|
|
73
73
|
});
|
|
74
|
-
export const McpServerEntrySchema = z
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
export const AgentRuleEntrySchema = z
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
.passthrough();
|
|
74
|
+
export const McpServerEntrySchema = z.object({
|
|
75
|
+
name: nameField,
|
|
76
|
+
agents: agentsField,
|
|
77
|
+
// Raw server descriptor passed verbatim to each agent adapter.
|
|
78
|
+
// Adapters are responsible for validating the shape they need.
|
|
79
|
+
config: z.record(z.string(), z.unknown()),
|
|
80
|
+
});
|
|
81
|
+
export const AgentRuleEntrySchema = z.object({
|
|
82
|
+
name: nameField,
|
|
83
|
+
agents: agentsField,
|
|
84
|
+
// Relative path to the rules/instruction file within the source bundle.
|
|
85
|
+
path: sourcePathField,
|
|
86
|
+
});
|
|
88
87
|
export const ManifestSchema = z.object({
|
|
89
88
|
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
90
89
|
const seen = new Set();
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface AgentProvenance {
|
|
|
9
9
|
skills: Confidence;
|
|
10
10
|
detectPaths: Confidence;
|
|
11
11
|
detectBinary: Confidence;
|
|
12
|
+
mcpConfig?: Confidence;
|
|
13
|
+
agentRules?: Confidence;
|
|
12
14
|
}
|
|
13
15
|
export interface AgentConfig {
|
|
14
16
|
id: AgentId;
|
|
@@ -17,6 +19,8 @@ export interface AgentConfig {
|
|
|
17
19
|
detectPaths: AgentPaths;
|
|
18
20
|
detectBinary: string | null;
|
|
19
21
|
provenance: AgentProvenance;
|
|
22
|
+
mcpConfigPath?: AgentPaths;
|
|
23
|
+
agentRulesPath?: AgentPaths;
|
|
20
24
|
policyNote?: string;
|
|
21
25
|
}
|
|
22
26
|
export interface PlanWarning {
|