@kuznai/inception-engine 0.15.0 → 0.16.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 +84 -2
- package/dist/config/agents.js +38 -0
- package/dist/core/adapters/index.d.ts +4 -3
- package/dist/core/adapters/index.js +8 -2
- package/dist/core/adapters/permissions.d.ts +8 -0
- package/dist/core/adapters/permissions.js +81 -0
- package/dist/core/deploy.js +1 -1
- package/dist/core/init.js +106 -14
- package/dist/core/revert.js +3 -1
- package/dist/core/validation.d.ts +1 -0
- package/dist/core/validation.js +45 -0
- package/dist/schemas/manifest.d.ts +25 -0
- package/dist/schemas/manifest.js +9 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
50
50
|
| Config patch (JSON merge) | All agents via manifest and CLI | All agents |
|
|
51
51
|
| MCP Servers | claude-code, gemini-cli, codex, antigravity, opencode; github-copilot repo-scoped surfaces are warned and skipped | claude-code, gemini-cli, codex, antigravity, opencode |
|
|
52
52
|
| Global/Repo Rules Files | All agents (antigravity uses repo-local `.agents/rules/`); github-copilot reads CLAUDE.md natively (deploy via claude-code) | All agents |
|
|
53
|
+
| Permissions / Approval Config | claude-code (`~/.claude/settings.json`), codex (`~/.codex/config.toml`); other agents are warned and skipped | claude-code, codex |
|
|
53
54
|
| `init` manifest generation | Scans `SKILL.md` directories (`skills`), `.md` files with Claude-first agent mapping (`agentRules`), and `mcp-servers.json` (`mcpServers`); emits hints for `files/` and `configs/` directories | N/A |
|
|
54
55
|
|
|
55
56
|
Features that depend on agent-specific config surfaces are intentionally conservative: if a target path or schema is not implemented with enough confidence, inception-engine warns and skips it rather than guessing.
|
|
@@ -98,6 +99,18 @@ Create an `inception.json` file at the root of your skills directory:
|
|
|
98
99
|
"path": "rules/CLAUDE.md",
|
|
99
100
|
"agents": ["claude-code"]
|
|
100
101
|
}
|
|
102
|
+
],
|
|
103
|
+
"permissions": [
|
|
104
|
+
{
|
|
105
|
+
"name": "safety-defaults",
|
|
106
|
+
"agents": ["claude-code"],
|
|
107
|
+
"config": {
|
|
108
|
+
"permissions": {
|
|
109
|
+
"allow": ["Read", "Glob"],
|
|
110
|
+
"deny": ["Bash(rm:*)"]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
101
114
|
]
|
|
102
115
|
}
|
|
103
116
|
```
|
|
@@ -145,6 +158,47 @@ Each **agentRules** entry deploys a Markdown instruction file to an agent's supp
|
|
|
145
158
|
|
|
146
159
|
Instruction rule deployment is supported for all agents. For most agents, this targets a single global rules file (e.g., `~/.claude/CLAUDE.md` for `claude-code`, `~/.codex/AGENTS.md` for `codex`, `~/.gemini/GEMINI.md` for `gemini-cli`, and `~/.config/opencode/AGENTS.md` for `opencode`). For `antigravity`, inception-engine deploys to repo-local instruction surfaces at `{repo}/.agents/rules/{name}.md`. For `github-copilot`, no separate deployment is needed because Copilot reads `CLAUDE.md` natively — target it via the `claude-code` agentRules entry and it reaches Copilot automatically. Revert removes the deployed rules file.
|
|
147
160
|
|
|
161
|
+
Each **permissions** entry deploys execution and safety-oriented configuration to an agent's permission or approval surface:
|
|
162
|
+
|
|
163
|
+
- **name** - Unique identifier (same format as skill names)
|
|
164
|
+
- **agents** - Array of agent IDs to deploy this entry to
|
|
165
|
+
- **config** - Permission config payload; shape is validated per agent (see below)
|
|
166
|
+
|
|
167
|
+
Permission deployment is currently supported for `claude-code` and `codex`. Other agents emit a warning and are skipped.
|
|
168
|
+
|
|
169
|
+
For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `permissions` key is accepted:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"name": "safety-defaults",
|
|
174
|
+
"agents": ["claude-code"],
|
|
175
|
+
"config": {
|
|
176
|
+
"permissions": {
|
|
177
|
+
"allow": ["Read", "Glob", "Bash(git:*)"],
|
|
178
|
+
"deny": ["Bash(rm:*)"]
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Both `allow` and `deny` are optional string arrays. Tool patterns follow Claude Code's permission glob syntax (e.g., `Bash(git:*)` to allow all git commands).
|
|
185
|
+
|
|
186
|
+
For `codex`, the config is merged into `~/.codex/config.toml`. Only the `approval_policy` key is accepted:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"name": "codex-approval",
|
|
191
|
+
"agents": ["codex"],
|
|
192
|
+
"config": {
|
|
193
|
+
"approval_policy": "suggest"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Valid `approval_policy` values are `"auto"`, `"manual"`, `"suggest"`, and `"on-failure"`.
|
|
199
|
+
|
|
200
|
+
Revert restores the previous config values using the undo patch recorded at deploy time.
|
|
201
|
+
|
|
148
202
|
## Creating Skills
|
|
149
203
|
|
|
150
204
|
Each skill is a directory containing at minimum a `SKILL.md` file with YAML frontmatter:
|
|
@@ -175,12 +229,14 @@ Current `init` behavior:
|
|
|
175
229
|
- Supports `--dry-run` so you can inspect the generated manifest before writing it
|
|
176
230
|
- Discovers agent-rules Markdown files in the root and conventional subdirectories (`rules/`, `instructions/`, `.github/`, `.agents/rules/`), mapping them to agents using Claude-first portability conventions: `copilot-instructions.md` maps to `claude-code` (Copilot reads `CLAUDE.md` natively), and the fallback for unrecognized files excludes agents whose agentRules surface is unsupported
|
|
177
231
|
- Reads `mcp-servers.json` from the repo root (if present) and generates `mcpServers` entries; invalid entries are warned and skipped
|
|
178
|
-
-
|
|
232
|
+
- Reads `files-manifest.json` from the repo root (if present) and generates `files` entries; invalid entries are warned and skipped
|
|
233
|
+
- Reads `configs-manifest.json` from the repo root (if present) and generates `configs` entries; invalid entries are warned and skipped
|
|
234
|
+
- Emits guidance to create a sidecar manifest when a `files/` or `configs/` directory is detected but the corresponding sidecar file is absent
|
|
179
235
|
|
|
180
236
|
Current `init` limitations:
|
|
181
237
|
|
|
182
238
|
- It does not reconcile generated manifest entries against `SKILL.md` frontmatter values
|
|
183
|
-
- `files` and `configs`
|
|
239
|
+
- `files` and `configs` target paths are deployment-system-specific and cannot be inferred; they must be provided explicitly in `files-manifest.json` and `configs-manifest.json`
|
|
184
240
|
|
|
185
241
|
### Repo Conventions Recognized by `init`
|
|
186
242
|
|
|
@@ -196,6 +252,32 @@ Place a `mcp-servers.json` file at the repo root to have `init` populate the `mc
|
|
|
196
252
|
]
|
|
197
253
|
```
|
|
198
254
|
|
|
255
|
+
Place a `files-manifest.json` file at the repo root to have `init` populate the `files` section automatically. The file must be a JSON array of file entries using the same schema as the `files` field in `inception.json`:
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
[
|
|
259
|
+
{
|
|
260
|
+
"name": "my-settings",
|
|
261
|
+
"path": "files/settings.json",
|
|
262
|
+
"target": "{home}/.claude/settings.json",
|
|
263
|
+
"agents": ["claude-code"]
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Place a `configs-manifest.json` file at the repo root to have `init` populate the `configs` section automatically. The file must be a JSON array of config entries using the same schema as the `configs` field in `inception.json`:
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
[
|
|
272
|
+
{
|
|
273
|
+
"name": "enable-feature",
|
|
274
|
+
"target": "{home}/.claude/settings.json",
|
|
275
|
+
"patch": { "someFeature": true },
|
|
276
|
+
"agents": ["claude-code"]
|
|
277
|
+
}
|
|
278
|
+
]
|
|
279
|
+
```
|
|
280
|
+
|
|
199
281
|
Invalid entries are warned and skipped; the rest are written into the generated manifest verbatim.
|
|
200
282
|
|
|
201
283
|
## CLI Reference
|
package/dist/config/agents.js
CHANGED
|
@@ -17,6 +17,7 @@ export const AGENT_REGISTRY = [
|
|
|
17
17
|
detectBinary: "documented",
|
|
18
18
|
mcpConfig: "documented",
|
|
19
19
|
agentRules: "documented",
|
|
20
|
+
permissions: "documented",
|
|
20
21
|
},
|
|
21
22
|
mcpSupport: {
|
|
22
23
|
status: "supported",
|
|
@@ -34,6 +35,14 @@ export const AGENT_REGISTRY = [
|
|
|
34
35
|
windows: ["{home}", ".claude", "CLAUDE.md"],
|
|
35
36
|
},
|
|
36
37
|
},
|
|
38
|
+
permissionsSupport: {
|
|
39
|
+
status: "supported",
|
|
40
|
+
schemaLabel: "JSON permissions config",
|
|
41
|
+
path: {
|
|
42
|
+
posix: ["{home}", ".claude", "settings.json"],
|
|
43
|
+
windows: ["{home}", ".claude", "settings.json"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
37
46
|
},
|
|
38
47
|
{
|
|
39
48
|
id: "codex",
|
|
@@ -52,6 +61,7 @@ export const AGENT_REGISTRY = [
|
|
|
52
61
|
detectPaths: "documented",
|
|
53
62
|
detectBinary: "documented",
|
|
54
63
|
agentRules: "documented",
|
|
64
|
+
permissions: "documented",
|
|
55
65
|
},
|
|
56
66
|
mcpSupport: {
|
|
57
67
|
status: "supported",
|
|
@@ -69,6 +79,14 @@ export const AGENT_REGISTRY = [
|
|
|
69
79
|
windows: ["{home}", ".codex", "AGENTS.md"],
|
|
70
80
|
},
|
|
71
81
|
},
|
|
82
|
+
permissionsSupport: {
|
|
83
|
+
status: "supported",
|
|
84
|
+
schemaLabel: "TOML approval policy config",
|
|
85
|
+
path: {
|
|
86
|
+
posix: ["{home}", ".codex", "config.toml"],
|
|
87
|
+
windows: ["{home}", ".codex", "config.toml"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
72
90
|
},
|
|
73
91
|
{
|
|
74
92
|
id: "gemini-cli",
|
|
@@ -105,6 +123,11 @@ export const AGENT_REGISTRY = [
|
|
|
105
123
|
windows: ["{home}", ".gemini", "GEMINI.md"],
|
|
106
124
|
},
|
|
107
125
|
},
|
|
126
|
+
permissionsSupport: {
|
|
127
|
+
status: "unsupported",
|
|
128
|
+
schemaLabel: "global permissions surface",
|
|
129
|
+
reason: "Gemini CLI does not expose a documented global per-user permission or approval config surface",
|
|
130
|
+
},
|
|
108
131
|
},
|
|
109
132
|
{
|
|
110
133
|
id: "antigravity",
|
|
@@ -139,6 +162,11 @@ export const AGENT_REGISTRY = [
|
|
|
139
162
|
windows: ["{repo}", ".agents", "rules", "{name}.md"],
|
|
140
163
|
},
|
|
141
164
|
},
|
|
165
|
+
permissionsSupport: {
|
|
166
|
+
status: "unsupported",
|
|
167
|
+
schemaLabel: "global permissions surface",
|
|
168
|
+
reason: "Antigravity does not expose a documented global per-user permission or approval config surface",
|
|
169
|
+
},
|
|
142
170
|
},
|
|
143
171
|
{
|
|
144
172
|
id: "opencode",
|
|
@@ -175,6 +203,11 @@ export const AGENT_REGISTRY = [
|
|
|
175
203
|
windows: ["{appdata}", "opencode", "AGENTS.md"],
|
|
176
204
|
},
|
|
177
205
|
},
|
|
206
|
+
permissionsSupport: {
|
|
207
|
+
status: "unsupported",
|
|
208
|
+
schemaLabel: "global permissions surface",
|
|
209
|
+
reason: "OpenCode does not expose a documented global per-user permission or approval config surface",
|
|
210
|
+
},
|
|
178
211
|
},
|
|
179
212
|
{
|
|
180
213
|
id: "github-copilot",
|
|
@@ -203,6 +236,11 @@ export const AGENT_REGISTRY = [
|
|
|
203
236
|
schemaLabel: "Claude-native shared instructions",
|
|
204
237
|
reason: 'GitHub Copilot reads CLAUDE.md natively, so deploy via the "claude-code" agentRules target instead of a separate rules surface',
|
|
205
238
|
},
|
|
239
|
+
permissionsSupport: {
|
|
240
|
+
status: "unsupported",
|
|
241
|
+
schemaLabel: "global permissions surface",
|
|
242
|
+
reason: "GitHub Copilot permissions are managed via organization policy, not a deployable per-user config surface",
|
|
243
|
+
},
|
|
206
244
|
policyNote: "Organization policies may override locally deployed skills. Verify with your GitHub org admin if deployed skills are not active.",
|
|
207
245
|
},
|
|
208
246
|
];
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { AgentRuleEntry, McpServerEntry } from "../../schemas/manifest.ts";
|
|
1
|
+
import type { AgentRuleEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
|
|
2
2
|
import type { AgentId, ConfigPatchDeployAction, FileWriteDeployAction, FrontmatterEmitDeployAction, PlanWarning, TomlPatchDeployAction } from "../../types.ts";
|
|
3
3
|
import { compileMcpServerReverts } from "./mcp.ts";
|
|
4
|
+
import { compilePermissionsReverts } from "./permissions.ts";
|
|
4
5
|
import { compileAgentRuleReverts } from "./rules.ts";
|
|
5
|
-
export { compileAgentRuleReverts, compileMcpServerReverts };
|
|
6
|
+
export { compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
6
7
|
export type AdapterAction = ConfigPatchDeployAction | FileWriteDeployAction | TomlPatchDeployAction | FrontmatterEmitDeployAction;
|
|
7
8
|
export interface AdapterResult {
|
|
8
9
|
actions: AdapterAction[];
|
|
9
10
|
warnings: PlanWarning[];
|
|
10
11
|
}
|
|
11
|
-
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<AdapterResult>;
|
|
12
|
+
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, realRoot: string, detectedAgents: AgentId[], home: string, repo?: string): Promise<AdapterResult>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { compileMcpServerActions, compileMcpServerReverts } from "./mcp.js";
|
|
2
|
+
import { compilePermissionsActions, compilePermissionsReverts, } from "./permissions.js";
|
|
2
3
|
import { compileAgentRuleActions, compileAgentRuleReverts } from "./rules.js";
|
|
3
|
-
export { compileAgentRuleReverts, compileMcpServerReverts };
|
|
4
|
-
export async function compileAdapterActions(mcpServers, agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
|
|
4
|
+
export { compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, };
|
|
5
|
+
export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home, repo) {
|
|
5
6
|
const actions = [];
|
|
6
7
|
const warnings = [];
|
|
7
8
|
for (const entry of mcpServers) {
|
|
@@ -14,5 +15,10 @@ export async function compileAdapterActions(mcpServers, agentRules, sourceDir, r
|
|
|
14
15
|
actions.push(...r.actions);
|
|
15
16
|
warnings.push(...r.warnings);
|
|
16
17
|
}
|
|
18
|
+
for (const entry of permissions) {
|
|
19
|
+
const r = compilePermissionsActions(entry, detectedAgents, home);
|
|
20
|
+
actions.push(...r.actions);
|
|
21
|
+
warnings.push(...r.warnings);
|
|
22
|
+
}
|
|
17
23
|
return { actions, warnings };
|
|
18
24
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PermissionsEntry } from "../../schemas/manifest.ts";
|
|
2
|
+
import type { AgentId, ConfigPatchDeployAction, ConfigPatchRevertAction, PlanWarning, TomlPatchDeployAction, TomlPatchRevertAction } from "../../types.ts";
|
|
3
|
+
export interface PermissionsAdapterResult {
|
|
4
|
+
actions: Array<ConfigPatchDeployAction | TomlPatchDeployAction>;
|
|
5
|
+
warnings: PlanWarning[];
|
|
6
|
+
}
|
|
7
|
+
export declare function compilePermissionsActions(entry: PermissionsEntry, detectedAgents: AgentId[], home: string): PermissionsAdapterResult;
|
|
8
|
+
export declare function compilePermissionsReverts(entry: PermissionsEntry, agentFilter: AgentId[] | null, home: string): Array<ConfigPatchRevertAction | TomlPatchRevertAction>;
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { validatePermissionsConfigShape } from "../validation.js";
|
|
5
|
+
function isTomlTarget(targetPath) {
|
|
6
|
+
return path.extname(targetPath).toLowerCase() === ".toml";
|
|
7
|
+
}
|
|
8
|
+
export function compilePermissionsActions(entry, detectedAgents, home) {
|
|
9
|
+
const actions = [];
|
|
10
|
+
const warnings = [];
|
|
11
|
+
const platform = getPlatformKey();
|
|
12
|
+
for (const agentId of entry.agents) {
|
|
13
|
+
if (!detectedAgents.includes(agentId))
|
|
14
|
+
continue;
|
|
15
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
16
|
+
const support = agent?.permissionsSupport;
|
|
17
|
+
if (!support || support.status === "unsupported") {
|
|
18
|
+
warnings.push({
|
|
19
|
+
kind: "confidence",
|
|
20
|
+
message: `permissions: agent "${agentId}" ${support?.status === "unsupported" ? support.reason : "does not expose a supported permissions adapter"} — skipping "${entry.name}"`,
|
|
21
|
+
});
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
validatePermissionsConfigShape(entry.config, entry.name, agentId);
|
|
25
|
+
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
|
|
26
|
+
const resolvedTarget = path.resolve(rawTarget);
|
|
27
|
+
const confidence = agent.provenance.permissions ?? "provisional";
|
|
28
|
+
if (isTomlTarget(resolvedTarget)) {
|
|
29
|
+
actions.push({
|
|
30
|
+
kind: "toml-patch",
|
|
31
|
+
skill: entry.name,
|
|
32
|
+
agent: agentId,
|
|
33
|
+
target: resolvedTarget,
|
|
34
|
+
config: entry.config,
|
|
35
|
+
confidence,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
actions.push({
|
|
40
|
+
kind: "config-patch",
|
|
41
|
+
skill: entry.name,
|
|
42
|
+
agent: agentId,
|
|
43
|
+
target: resolvedTarget,
|
|
44
|
+
patch: entry.config,
|
|
45
|
+
confidence,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { actions, warnings };
|
|
50
|
+
}
|
|
51
|
+
export function compilePermissionsReverts(entry, agentFilter, home) {
|
|
52
|
+
const actions = [];
|
|
53
|
+
const platform = getPlatformKey();
|
|
54
|
+
for (const agentId of entry.agents) {
|
|
55
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
56
|
+
continue;
|
|
57
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
58
|
+
const support = agent?.permissionsSupport;
|
|
59
|
+
if (!support || support.status === "unsupported")
|
|
60
|
+
continue;
|
|
61
|
+
const rawTarget = resolvePlaceholders(support.path[platform], entry.name, home);
|
|
62
|
+
const target = path.resolve(rawTarget);
|
|
63
|
+
if (isTomlTarget(target)) {
|
|
64
|
+
actions.push({
|
|
65
|
+
kind: "toml-patch",
|
|
66
|
+
skill: entry.name,
|
|
67
|
+
agent: agentId,
|
|
68
|
+
target,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
actions.push({
|
|
73
|
+
kind: "config-patch",
|
|
74
|
+
skill: entry.name,
|
|
75
|
+
agent: agentId,
|
|
76
|
+
target,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return actions;
|
|
81
|
+
}
|
package/dist/core/deploy.js
CHANGED
|
@@ -192,7 +192,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
192
192
|
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
193
193
|
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
194
194
|
];
|
|
195
|
-
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
195
|
+
const adapterResult = await compileAdapterActions(manifest.mcpServers, manifest.agentRules, manifest.permissions ?? [], sourceDir, resolvedSourceDir, realRoot, detectedAgents, home);
|
|
196
196
|
actions.push(...adapterResult.actions);
|
|
197
197
|
const warnings = [
|
|
198
198
|
...detectAmbiguities(detectedAgents, manifest),
|
package/dist/core/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { access, readFile, readdir, 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 { dryRunPrefix, logger } from "../logger.js";
|
|
5
|
-
import { AGENT_IDS, McpServerEntrySchema } from "../schemas/manifest.js";
|
|
5
|
+
import { AGENT_IDS, ConfigEntrySchema, FileEntrySchema, McpServerEntrySchema, } from "../schemas/manifest.js";
|
|
6
6
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
7
7
|
// Ordered list: first match wins. Catch-all is applied at call site.
|
|
8
8
|
const AGENT_RULES_FILE_PATTERNS = [
|
|
@@ -197,14 +197,82 @@ async function loadMcpServers(baseDir) {
|
|
|
197
197
|
}
|
|
198
198
|
return results;
|
|
199
199
|
}
|
|
200
|
-
async function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
async function loadFilesManifest(baseDir) {
|
|
201
|
+
const filePath = path.join(baseDir, "files-manifest.json");
|
|
202
|
+
let raw;
|
|
203
|
+
try {
|
|
204
|
+
raw = await readFile(filePath, "utf-8");
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
let parsed;
|
|
210
|
+
try {
|
|
211
|
+
parsed = JSON.parse(raw);
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
logger.warn("init", "files-manifest.json: invalid JSON, skipping");
|
|
215
|
+
return [];
|
|
216
|
+
}
|
|
217
|
+
if (!Array.isArray(parsed)) {
|
|
218
|
+
logger.warn("init", "files-manifest.json: expected a JSON array, skipping");
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
const results = [];
|
|
222
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
223
|
+
const result = FileEntrySchema.safeParse(parsed[i]);
|
|
224
|
+
if (result.success) {
|
|
225
|
+
results.push(result.data);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
logger.warn("init", `files-manifest.json: entry[${i}] invalid, skipping`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
async function loadConfigsManifest(baseDir) {
|
|
234
|
+
const filePath = path.join(baseDir, "configs-manifest.json");
|
|
235
|
+
let raw;
|
|
236
|
+
try {
|
|
237
|
+
raw = await readFile(filePath, "utf-8");
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
let parsed;
|
|
243
|
+
try {
|
|
244
|
+
parsed = JSON.parse(raw);
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
logger.warn("init", "configs-manifest.json: invalid JSON, skipping");
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
if (!Array.isArray(parsed)) {
|
|
251
|
+
logger.warn("init", "configs-manifest.json: expected a JSON array, skipping");
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
const results = [];
|
|
255
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
256
|
+
const result = ConfigEntrySchema.safeParse(parsed[i]);
|
|
257
|
+
if (result.success) {
|
|
258
|
+
results.push(result.data);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
logger.warn("init", `configs-manifest.json: entry[${i}] invalid, skipping`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return results;
|
|
265
|
+
}
|
|
266
|
+
async function emitDirectoryHints(baseDir, filesLoaded, configsLoaded) {
|
|
267
|
+
for (const [dir, section, loaded, sidecar] of [
|
|
268
|
+
["files", "files", filesLoaded, "files-manifest.json"],
|
|
269
|
+
["configs", "configs", configsLoaded, "configs-manifest.json"],
|
|
204
270
|
]) {
|
|
205
271
|
try {
|
|
206
272
|
await access(path.join(baseDir, dir));
|
|
207
|
-
|
|
273
|
+
if (loaded === 0) {
|
|
274
|
+
logger.info(`Detected ${dir}/ directory — create ${sidecar} at the repo root to have init populate ${section} entries automatically.`);
|
|
275
|
+
}
|
|
208
276
|
}
|
|
209
277
|
catch {
|
|
210
278
|
// directory does not exist — silent
|
|
@@ -220,7 +288,7 @@ async function manifestExists(manifestPath) {
|
|
|
220
288
|
return false;
|
|
221
289
|
}
|
|
222
290
|
}
|
|
223
|
-
function logVerboseManifest(skills, agentRules, mcpServers) {
|
|
291
|
+
function logVerboseManifest(skills, agentRules, mcpServers, files, configs) {
|
|
224
292
|
for (const s of skills) {
|
|
225
293
|
logger.detail(`${s.name} → ${s.path}`);
|
|
226
294
|
}
|
|
@@ -236,6 +304,18 @@ function logVerboseManifest(skills, agentRules, mcpServers) {
|
|
|
236
304
|
logger.detail(` ${m.name} [${m.agents.join(", ")}]`);
|
|
237
305
|
}
|
|
238
306
|
}
|
|
307
|
+
if (files.length > 0) {
|
|
308
|
+
logger.detail("files:");
|
|
309
|
+
for (const f of files) {
|
|
310
|
+
logger.detail(` ${f.name} → ${f.target} [${f.agents.join(", ")}]`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (configs.length > 0) {
|
|
314
|
+
logger.detail("configs:");
|
|
315
|
+
for (const c of configs) {
|
|
316
|
+
logger.detail(` ${c.name} → ${c.target} [${c.agents.join(", ")}]`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
239
319
|
}
|
|
240
320
|
export async function runInit(options) {
|
|
241
321
|
const { directory, dryRun, force, verbose } = options;
|
|
@@ -257,26 +337,38 @@ export async function runInit(options) {
|
|
|
257
337
|
const agentRulesCandidates = await findAgentRulesCandidates(directory, skillDirRelPaths);
|
|
258
338
|
const agentRules = buildAgentRules(agentRulesCandidates, agentRulesCapableAgents, skillNamesSeen);
|
|
259
339
|
const mcpServers = await loadMcpServers(directory);
|
|
340
|
+
const files = await loadFilesManifest(directory);
|
|
341
|
+
const configs = await loadConfigsManifest(directory);
|
|
260
342
|
const manifest = {
|
|
261
343
|
skills,
|
|
262
|
-
files
|
|
263
|
-
configs
|
|
344
|
+
files,
|
|
345
|
+
configs,
|
|
264
346
|
mcpServers,
|
|
265
347
|
agentRules,
|
|
266
348
|
};
|
|
267
349
|
const json = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
350
|
+
function summarize() {
|
|
351
|
+
const parts = [
|
|
352
|
+
`${skills.length} skill(s)`,
|
|
353
|
+
`${agentRules.length} agentRule(s)`,
|
|
354
|
+
`${mcpServers.length} mcpServer(s)`,
|
|
355
|
+
`${files.length} file(s)`,
|
|
356
|
+
`${configs.length} config(s)`,
|
|
357
|
+
];
|
|
358
|
+
return parts.join(", ");
|
|
359
|
+
}
|
|
268
360
|
if (dryRun) {
|
|
269
|
-
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${
|
|
361
|
+
logger.info(`${dryRunPrefix(true)}Would write ${manifestPath} with ${summarize()}:`);
|
|
270
362
|
logger.info("");
|
|
271
363
|
logger.info(json);
|
|
272
|
-
await emitDirectoryHints(directory);
|
|
364
|
+
await emitDirectoryHints(directory, files.length, configs.length);
|
|
273
365
|
return 0;
|
|
274
366
|
}
|
|
275
367
|
await writeFile(manifestPath, json, "utf-8");
|
|
276
|
-
logger.info(`Generated ${manifestPath} with ${
|
|
368
|
+
logger.info(`Generated ${manifestPath} with ${summarize()}.`);
|
|
277
369
|
if (verbose) {
|
|
278
|
-
logVerboseManifest(skills, agentRules, mcpServers);
|
|
370
|
+
logVerboseManifest(skills, agentRules, mcpServers, files, configs);
|
|
279
371
|
}
|
|
280
|
-
await emitDirectoryHints(directory);
|
|
372
|
+
await emitDirectoryHints(directory, files.length, configs.length);
|
|
281
373
|
return 0;
|
|
282
374
|
}
|
package/dist/core/revert.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
3
|
import { logger } from "../logger.js";
|
|
4
|
-
import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
|
|
4
|
+
import { compileAgentRuleReverts, compileMcpServerReverts, compilePermissionsReverts, } from "./adapters/index.js";
|
|
5
5
|
import { revertTomlMcpPatch } from "./adapters/toml.js";
|
|
6
6
|
import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
|
|
7
7
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
@@ -70,6 +70,7 @@ export function planRevert(manifest, detectedAgents, home, repo) {
|
|
|
70
70
|
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
71
71
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home, repo)),
|
|
72
72
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home, repo)),
|
|
73
|
+
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, detectedAgents, home)),
|
|
73
74
|
];
|
|
74
75
|
}
|
|
75
76
|
export function planRevertAll(manifest, home, repo) {
|
|
@@ -79,6 +80,7 @@ export function planRevertAll(manifest, home, repo) {
|
|
|
79
80
|
...buildConfigPatchReverts(manifest, home, null),
|
|
80
81
|
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home, repo)),
|
|
81
82
|
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home, repo)),
|
|
83
|
+
...(manifest.permissions ?? []).flatMap((e) => compilePermissionsReverts(e, null, home)),
|
|
82
84
|
];
|
|
83
85
|
}
|
|
84
86
|
function recordOutcome(result, action, counts, failed) {
|
|
@@ -2,5 +2,6 @@ export declare function sourceAccessError(err: unknown, sourcePath: string): str
|
|
|
2
2
|
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
3
|
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
4
4
|
export declare function validateMcpServerConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
|
+
export declare function validatePermissionsConfigShape(config: Record<string, unknown>, entryName: string, agentId: string): void;
|
|
5
6
|
export declare function validateAgentRuleMarkdownPath(manifestPath: string, agentId: string): void;
|
|
6
7
|
export declare function validateSkillDefinitionFile(sourcePath: string, manifestPath: string): Promise<void>;
|
package/dist/core/validation.js
CHANGED
|
@@ -96,6 +96,51 @@ export function validateMcpServerConfigShape(config, entryName, agentId) {
|
|
|
96
96
|
throw new UserError("DEPLOY_FAILED", `mcpServers entry "${entryName}" for agent "${agentId}" must define "env" as an object of string values when present`);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
const CODEX_APPROVAL_POLICY_VALUES = [
|
|
100
|
+
"auto",
|
|
101
|
+
"manual",
|
|
102
|
+
"suggest",
|
|
103
|
+
"on-failure",
|
|
104
|
+
];
|
|
105
|
+
function rejectUnknownKeys(config, allowed, entryName, agentId) {
|
|
106
|
+
const unknownKeys = Object.keys(config).filter((k) => !allowed.includes(k));
|
|
107
|
+
if (unknownKeys.length > 0) {
|
|
108
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "${agentId}" contains unrecognized keys: ${unknownKeys.join(", ")}. Only ${allowed.map((k) => `"${k}"`).join(", ")} is allowed.`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function validateClaudeCodePermissions(config, entryName) {
|
|
112
|
+
rejectUnknownKeys(config, ["permissions"], entryName, "claude-code");
|
|
113
|
+
const perms = config.permissions;
|
|
114
|
+
if (perms === undefined)
|
|
115
|
+
return;
|
|
116
|
+
if (typeof perms !== "object" || perms === null || Array.isArray(perms)) {
|
|
117
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions" as an object`);
|
|
118
|
+
}
|
|
119
|
+
const permsObj = perms;
|
|
120
|
+
for (const key of ["allow", "deny"]) {
|
|
121
|
+
const val = permsObj[key];
|
|
122
|
+
if (val !== undefined &&
|
|
123
|
+
(!Array.isArray(val) || val.some((item) => typeof item !== "string"))) {
|
|
124
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "claude-code" must define "permissions.${key}" as an array of strings when present`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function validateCodexPermissions(config, entryName) {
|
|
129
|
+
rejectUnknownKeys(config, ["approval_policy"], entryName, "codex");
|
|
130
|
+
const policy = config.approval_policy;
|
|
131
|
+
if (policy !== undefined &&
|
|
132
|
+
!CODEX_APPROVAL_POLICY_VALUES.includes(policy)) {
|
|
133
|
+
throw new UserError("DEPLOY_FAILED", `permissions entry "${entryName}" for agent "codex" must define "approval_policy" as one of: ${CODEX_APPROVAL_POLICY_VALUES.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function validatePermissionsConfigShape(config, entryName, agentId) {
|
|
137
|
+
if (agentId === "claude-code") {
|
|
138
|
+
validateClaudeCodePermissions(config, entryName);
|
|
139
|
+
}
|
|
140
|
+
else if (agentId === "codex") {
|
|
141
|
+
validateCodexPermissions(config, entryName);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
99
144
|
export function validateAgentRuleMarkdownPath(manifestPath, agentId) {
|
|
100
145
|
const extension = path.extname(manifestPath).toLowerCase();
|
|
101
146
|
if (extension !== ".md" && extension !== ".markdown") {
|
|
@@ -72,6 +72,18 @@ export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
|
72
72
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
73
73
|
path: z.ZodString;
|
|
74
74
|
}, z.core.$strip>;
|
|
75
|
+
export declare const PermissionsEntrySchema: z.ZodObject<{
|
|
76
|
+
name: z.ZodString;
|
|
77
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
78
|
+
"claude-code": "claude-code";
|
|
79
|
+
codex: "codex";
|
|
80
|
+
"gemini-cli": "gemini-cli";
|
|
81
|
+
antigravity: "antigravity";
|
|
82
|
+
opencode: "opencode";
|
|
83
|
+
"github-copilot": "github-copilot";
|
|
84
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
85
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
86
|
+
}, z.core.$strip>;
|
|
75
87
|
export declare const ManifestSchema: z.ZodObject<{
|
|
76
88
|
skills: z.ZodArray<z.ZodObject<{
|
|
77
89
|
name: z.ZodString;
|
|
@@ -135,12 +147,25 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
135
147
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
136
148
|
path: z.ZodString;
|
|
137
149
|
}, z.core.$strip>>>;
|
|
150
|
+
permissions: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
151
|
+
name: z.ZodString;
|
|
152
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
153
|
+
"claude-code": "claude-code";
|
|
154
|
+
codex: "codex";
|
|
155
|
+
"gemini-cli": "gemini-cli";
|
|
156
|
+
antigravity: "antigravity";
|
|
157
|
+
opencode: "opencode";
|
|
158
|
+
"github-copilot": "github-copilot";
|
|
159
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
160
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
161
|
+
}, z.core.$strip>>>;
|
|
138
162
|
}, z.core.$strip>;
|
|
139
163
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
140
164
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
|
141
165
|
export type ConfigEntry = z.infer<typeof ConfigEntrySchema>;
|
|
142
166
|
export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
|
|
143
167
|
export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
|
|
168
|
+
export type PermissionsEntry = z.infer<typeof PermissionsEntrySchema>;
|
|
144
169
|
export type Manifest = z.infer<typeof ManifestSchema>;
|
|
145
170
|
export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
146
171
|
"claude-code": "claude-code";
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -90,6 +90,14 @@ export const AgentRuleEntrySchema = z.object({
|
|
|
90
90
|
// Relative path to the rules/instruction file within the source bundle.
|
|
91
91
|
path: sourcePathField,
|
|
92
92
|
});
|
|
93
|
+
export const PermissionsEntrySchema = z.object({
|
|
94
|
+
name: nameField,
|
|
95
|
+
agents: agentsField,
|
|
96
|
+
// Raw permission config payload validated per agent by the permissions adapter.
|
|
97
|
+
// For claude-code: { permissions: { allow?: string[], deny?: string[] } }
|
|
98
|
+
// For codex: { approval_policy?: "auto" | "manual" | "suggest" | "on-failure" }
|
|
99
|
+
config: z.record(z.string(), z.unknown()),
|
|
100
|
+
});
|
|
93
101
|
export const ManifestSchema = z.object({
|
|
94
102
|
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
95
103
|
const seen = new Set();
|
|
@@ -108,6 +116,7 @@ export const ManifestSchema = z.object({
|
|
|
108
116
|
configs: z.array(ConfigEntrySchema).default([]),
|
|
109
117
|
mcpServers: z.array(McpServerEntrySchema).default([]),
|
|
110
118
|
agentRules: z.array(AgentRuleEntrySchema).default([]),
|
|
119
|
+
permissions: z.array(PermissionsEntrySchema).default([]),
|
|
111
120
|
});
|
|
112
121
|
// Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
|
|
113
122
|
export const AgentListSchema = z
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export interface AgentProvenance {
|
|
|
23
23
|
detectBinary: Confidence;
|
|
24
24
|
mcpConfig?: Confidence;
|
|
25
25
|
agentRules?: Confidence;
|
|
26
|
+
permissions?: Confidence;
|
|
26
27
|
}
|
|
27
28
|
export interface AgentConfig {
|
|
28
29
|
id: AgentId;
|
|
@@ -33,6 +34,7 @@ export interface AgentConfig {
|
|
|
33
34
|
provenance: AgentProvenance;
|
|
34
35
|
mcpSupport?: AgentSurfaceSupport;
|
|
35
36
|
agentRulesSupport?: AgentSurfaceSupport;
|
|
37
|
+
permissionsSupport?: AgentSurfaceSupport;
|
|
36
38
|
policyNote?: string;
|
|
37
39
|
}
|
|
38
40
|
export interface PlanWarning {
|