@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 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
- - Emits guidance when a `files/` or `configs/` directory is detected at the repo root
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` entries cannot be inferred automatically since deployment targets are system-specific; add them manually after `init`
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
@@ -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
+ }
@@ -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 emitDirectoryHints(baseDir) {
201
- for (const [dir, section] of [
202
- ["files", "files"],
203
- ["configs", "configs"],
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
- logger.info(`Detected ${dir}/ directory — add ${section} entries manually to the generated manifest with target paths.`);
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 ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s):`);
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 ${skills.length} skill(s), ${agentRules.length} agentRule(s), and ${mcpServers.length} mcpServer(s).`);
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
  }
@@ -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>;
@@ -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";
@@ -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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuznai/inception-engine",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Deploy AI agent skills from a git repo to user home directories",
5
5
  "license": "MIT",
6
6
  "author": "Damian Piątkowski",