@kuznai/inception-engine 0.7.0 → 0.8.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 +38 -6
- package/dist/config/agents.js +1 -0
- package/dist/config/manifest.js +5 -2
- package/dist/core/deploy.js +107 -27
- package/dist/core/preflight.js +6 -0
- package/dist/core/revert.js +71 -20
- package/dist/index.js +38 -8
- package/dist/schemas/manifest.d.ts +68 -2
- package/dist/schemas/manifest.js +61 -21
- package/dist/types.d.ts +2 -1
- 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
|
|
5
|
+
Today, inception-engine deploys skills, single files, and JSON config patches to AI coding agents. MCP configuration and agent rules remain unimplemented at the manifest level.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -41,8 +41,8 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
41
41
|
| Feature | Status |
|
|
42
42
|
|---|---|
|
|
43
43
|
| Skills (SKILL.md) | Supported via manifest and CLI |
|
|
44
|
-
| File write |
|
|
45
|
-
| Config patch (JSON merge) |
|
|
44
|
+
| File write | Supported via manifest and CLI |
|
|
45
|
+
| Config patch (JSON merge) | Supported via manifest and CLI |
|
|
46
46
|
| MCP Servers | Accepted in manifest for forward compatibility, not implemented |
|
|
47
47
|
| Agent Rules | Accepted in manifest for forward compatibility, not implemented |
|
|
48
48
|
|
|
@@ -59,18 +59,50 @@ Create an `inception.json` file at the root of your skills directory:
|
|
|
59
59
|
"agents": ["claude-code", "codex", "gemini-cli", "antigravity", "opencode", "github-copilot"]
|
|
60
60
|
}
|
|
61
61
|
],
|
|
62
|
+
"files": [
|
|
63
|
+
{
|
|
64
|
+
"name": "my-settings",
|
|
65
|
+
"path": "files/settings.json",
|
|
66
|
+
"target": "{home}/.claude/settings.json",
|
|
67
|
+
"agents": ["claude-code"]
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"configs": [
|
|
71
|
+
{
|
|
72
|
+
"name": "enable-feature",
|
|
73
|
+
"target": "{home}/.claude/settings.json",
|
|
74
|
+
"patch": { "someFeature": true },
|
|
75
|
+
"agents": ["claude-code"]
|
|
76
|
+
}
|
|
77
|
+
],
|
|
62
78
|
"mcpServers": [],
|
|
63
79
|
"agentRules": []
|
|
64
80
|
}
|
|
65
81
|
```
|
|
66
82
|
|
|
67
|
-
Each skill entry has:
|
|
83
|
+
Each **skill** entry has:
|
|
68
84
|
|
|
69
|
-
- **name** - Unique
|
|
85
|
+
- **name** - Unique identifier using letters, digits, dots, underscores, or hyphens; must not start with a dot
|
|
70
86
|
- **path** - Relative path to the skill directory within the repo
|
|
71
87
|
- **agents** - Array of agent IDs to deploy this skill to. If an agent isn't installed, it's skipped.
|
|
72
88
|
|
|
73
|
-
|
|
89
|
+
Each **file** entry deploys a single file to an agent's configuration location:
|
|
90
|
+
|
|
91
|
+
- **name** - Unique identifier (same format as skill names)
|
|
92
|
+
- **path** - Relative path to the source file within the repo
|
|
93
|
+
- **target** - Destination path using a placeholder prefix: `{home}`, `{appdata}` (Windows), or `{xdg_config}` (Linux). For example: `{home}/.claude/settings.json`
|
|
94
|
+
- **agents** - Array of agent IDs to deploy this file to
|
|
95
|
+
|
|
96
|
+
Each **config** entry applies a [JSON merge patch (RFC 7386)](https://datatracker.ietf.org/doc/html/rfc7386) to an existing agent config file:
|
|
97
|
+
|
|
98
|
+
- **name** - Unique identifier (same format as skill names)
|
|
99
|
+
- **target** - Config file to patch, using the same placeholder prefix as file entries
|
|
100
|
+
- **patch** - JSON object of keys to set. A `null` value removes the key from the target file. Non-null values are set directly (deep merge is not applied).
|
|
101
|
+
- **agents** - Array of agent IDs to apply this patch to
|
|
102
|
+
|
|
103
|
+
The engine records an undo-patch for each config-patch deployment so that `revert` can restore the original values.
|
|
104
|
+
|
|
105
|
+
`mcpServers` and `agentRules` are currently parsed for forward compatibility, but the deployment engine ignores them today.
|
|
74
106
|
|
|
75
107
|
## Creating Skills
|
|
76
108
|
|
package/dist/config/agents.js
CHANGED
|
@@ -106,6 +106,7 @@ export const AGENT_REGISTRY = [
|
|
|
106
106
|
detectPaths: "documented",
|
|
107
107
|
detectBinary: "documented",
|
|
108
108
|
},
|
|
109
|
+
policyNote: "Organization policies may override locally deployed skills. Verify with your GitHub org admin if deployed skills are not active.",
|
|
109
110
|
},
|
|
110
111
|
];
|
|
111
112
|
export const AGENT_REGISTRY_BY_ID = Object.fromEntries(AGENT_REGISTRY.map((a) => [a.id, a]));
|
package/dist/config/manifest.js
CHANGED
|
@@ -38,9 +38,12 @@ function validateManifest(data, filePath) {
|
|
|
38
38
|
if (issuePath.length === 1 && issuePath[0] === "skills") {
|
|
39
39
|
throw new UserError("MANIFEST_INVALID", `${filePath}: "skills" must be an array`);
|
|
40
40
|
}
|
|
41
|
-
// Top-level
|
|
41
|
+
// Top-level array fields with wrong type → uniform message
|
|
42
42
|
if (issuePath.length === 1 &&
|
|
43
|
-
(issuePath[0] === "mcpServers" ||
|
|
43
|
+
(issuePath[0] === "mcpServers" ||
|
|
44
|
+
issuePath[0] === "agentRules" ||
|
|
45
|
+
issuePath[0] === "files" ||
|
|
46
|
+
issuePath[0] === "configs")) {
|
|
44
47
|
throw new UserError("MANIFEST_INVALID", `${filePath}: "${issuePath[0]}" must be an array`);
|
|
45
48
|
}
|
|
46
49
|
throw new UserError("MANIFEST_INVALID", `${filePath}: ${formatZodPath(issuePath)}${issue.message}`);
|
package/dist/core/deploy.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
1
2
|
import { access, copyFile, cp, lstat, mkdir, readFile, realpath, rename, rm, symlink, unlink, writeFile, } from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
@@ -59,6 +60,27 @@ function sourceAccessError(err, sourcePath) {
|
|
|
59
60
|
const detail = err instanceof Error ? err.message : String(err);
|
|
60
61
|
return `Failed to access source ${sourcePath}: ${detail}`;
|
|
61
62
|
}
|
|
63
|
+
function resolveTargetTemplate(template, home) {
|
|
64
|
+
const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
65
|
+
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
66
|
+
const xdgConfig = xdgRaw && path.isAbsolute(xdgRaw) ? xdgRaw : path.join(home, ".config");
|
|
67
|
+
return template
|
|
68
|
+
.replace("{home}", home)
|
|
69
|
+
.replace("{appdata}", appdata)
|
|
70
|
+
.replace("{xdg_config}", xdgConfig);
|
|
71
|
+
}
|
|
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
|
+
}
|
|
62
84
|
function detectCollisions(actions) {
|
|
63
85
|
const seen = new Map();
|
|
64
86
|
const warnings = [];
|
|
@@ -87,17 +109,9 @@ function detectAmbiguities(detectedAgents) {
|
|
|
87
109
|
}
|
|
88
110
|
return warnings;
|
|
89
111
|
}
|
|
90
|
-
|
|
112
|
+
async function planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
91
113
|
const method = getDeployMethod();
|
|
92
114
|
const actions = [];
|
|
93
|
-
const resolvedSourceDir = path.resolve(sourceDir);
|
|
94
|
-
let realRoot;
|
|
95
|
-
try {
|
|
96
|
-
realRoot = await realpath(resolvedSourceDir);
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
realRoot = resolvedSourceDir;
|
|
100
|
-
}
|
|
101
115
|
for (const skill of manifest.skills) {
|
|
102
116
|
const source = path.resolve(sourceDir, skill.path);
|
|
103
117
|
await validateSourcePath(source, skill.path, resolvedSourceDir, realRoot);
|
|
@@ -108,18 +122,78 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
108
122
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
109
123
|
if (!agent)
|
|
110
124
|
continue;
|
|
111
|
-
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
112
125
|
actions.push({
|
|
113
126
|
kind: "skill-dir",
|
|
114
127
|
skill: skill.name,
|
|
115
128
|
agent: agentId,
|
|
116
129
|
source,
|
|
117
|
-
target,
|
|
130
|
+
target: resolveAgentSkillPath(agent, skill.name, home),
|
|
118
131
|
method,
|
|
119
132
|
confidence: agent.provenance.skills,
|
|
120
133
|
});
|
|
121
134
|
}
|
|
122
135
|
}
|
|
136
|
+
return actions;
|
|
137
|
+
}
|
|
138
|
+
async function planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home) {
|
|
139
|
+
const actions = [];
|
|
140
|
+
for (const fileEntry of manifest.files ?? []) {
|
|
141
|
+
const source = path.resolve(sourceDir, fileEntry.path);
|
|
142
|
+
await validateSourcePath(source, fileEntry.path, resolvedSourceDir, realRoot);
|
|
143
|
+
await validateSourceFile(source, fileEntry.path);
|
|
144
|
+
for (const agentId of fileEntry.agents) {
|
|
145
|
+
if (!detectedAgents.includes(agentId))
|
|
146
|
+
continue;
|
|
147
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
148
|
+
if (!agent)
|
|
149
|
+
continue;
|
|
150
|
+
actions.push({
|
|
151
|
+
kind: "file-write",
|
|
152
|
+
skill: fileEntry.name,
|
|
153
|
+
agent: agentId,
|
|
154
|
+
source,
|
|
155
|
+
target: resolveTargetTemplate(fileEntry.target, home),
|
|
156
|
+
confidence: agent.provenance.skills,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return actions;
|
|
161
|
+
}
|
|
162
|
+
function planConfigPatchActions(manifest, detectedAgents, home) {
|
|
163
|
+
const actions = [];
|
|
164
|
+
for (const configEntry of manifest.configs ?? []) {
|
|
165
|
+
for (const agentId of configEntry.agents) {
|
|
166
|
+
if (!detectedAgents.includes(agentId))
|
|
167
|
+
continue;
|
|
168
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
169
|
+
if (!agent)
|
|
170
|
+
continue;
|
|
171
|
+
actions.push({
|
|
172
|
+
kind: "config-patch",
|
|
173
|
+
skill: configEntry.name,
|
|
174
|
+
agent: agentId,
|
|
175
|
+
target: resolveTargetTemplate(configEntry.target, home),
|
|
176
|
+
patch: configEntry.patch,
|
|
177
|
+
confidence: agent.provenance.skills,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return actions;
|
|
182
|
+
}
|
|
183
|
+
export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
184
|
+
const resolvedSourceDir = path.resolve(sourceDir);
|
|
185
|
+
let realRoot;
|
|
186
|
+
try {
|
|
187
|
+
realRoot = await realpath(resolvedSourceDir);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
realRoot = resolvedSourceDir;
|
|
191
|
+
}
|
|
192
|
+
const actions = [
|
|
193
|
+
...(await planSkillDirActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
194
|
+
...(await planFileWriteActions(manifest, sourceDir, resolvedSourceDir, realRoot, detectedAgents, home)),
|
|
195
|
+
...planConfigPatchActions(manifest, detectedAgents, home),
|
|
196
|
+
];
|
|
123
197
|
const warnings = [
|
|
124
198
|
...detectAmbiguities(detectedAgents),
|
|
125
199
|
...detectCollisions(actions),
|
|
@@ -180,8 +254,6 @@ async function deploySkillDir(action, dryRun, verbose, home, planned) {
|
|
|
180
254
|
return { error: msg };
|
|
181
255
|
}
|
|
182
256
|
if (dryRun) {
|
|
183
|
-
logger.plan(label);
|
|
184
|
-
logger.detail(`${action.method}: ${action.source} -> ${action.target}`);
|
|
185
257
|
planned.push({
|
|
186
258
|
verb: action.method === "symlink" ? "create-symlink" : "copy-dir",
|
|
187
259
|
kind: "skill-dir",
|
|
@@ -215,8 +287,6 @@ async function deployFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
215
287
|
return { error: msg };
|
|
216
288
|
}
|
|
217
289
|
if (dryRun) {
|
|
218
|
-
logger.plan(label);
|
|
219
|
-
logger.detail(`write-file: ${action.source} -> ${action.target}`);
|
|
220
290
|
planned.push({
|
|
221
291
|
verb: "write-file",
|
|
222
292
|
kind: "file-write",
|
|
@@ -275,8 +345,6 @@ async function deployConfigPatch(action, dryRun, verbose, home, planned) {
|
|
|
275
345
|
}
|
|
276
346
|
const patch = action.patch;
|
|
277
347
|
if (dryRun) {
|
|
278
|
-
logger.plan(label);
|
|
279
|
-
logger.detail(`patch-config: ${JSON.stringify(patch)} -> ${action.target}`);
|
|
280
348
|
planned.push({
|
|
281
349
|
verb: "patch-config",
|
|
282
350
|
kind: "config-patch",
|
|
@@ -354,9 +422,23 @@ async function validateSkillContract(source, skillPath) {
|
|
|
354
422
|
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source is not a directory: ${source}`);
|
|
355
423
|
}
|
|
356
424
|
try {
|
|
357
|
-
await access(
|
|
425
|
+
await access(source, constants.R_OK);
|
|
358
426
|
}
|
|
359
|
-
catch {
|
|
427
|
+
catch (err) {
|
|
428
|
+
const code = err.code;
|
|
429
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
430
|
+
throw new UserError("DEPLOY_FAILED", `Permission denied reading skill directory "${skillPath}": ${source}`);
|
|
431
|
+
}
|
|
432
|
+
throw new UserError("DEPLOY_FAILED", `Cannot read skill directory "${skillPath}": ${source}`);
|
|
433
|
+
}
|
|
434
|
+
try {
|
|
435
|
+
await access(path.join(source, "SKILL.md"), constants.R_OK);
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
const code = err.code;
|
|
439
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
440
|
+
throw new UserError("DEPLOY_FAILED", `Permission denied reading SKILL.md in skill "${skillPath}": ${source}`);
|
|
441
|
+
}
|
|
360
442
|
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source is missing SKILL.md: ${source}`);
|
|
361
443
|
}
|
|
362
444
|
}
|
|
@@ -437,17 +519,15 @@ async function backupExisting(targetPath, verbose, home, expected) {
|
|
|
437
519
|
throw new Error(`Target "${targetPath}" exists but is not managed by inception-engine — refusing to overwrite`);
|
|
438
520
|
}
|
|
439
521
|
const backupPath = `${targetPath}.inception-backup`;
|
|
440
|
-
// Clean up any stale backup from a previous failed attempt
|
|
441
|
-
try {
|
|
442
|
-
await lstat(backupPath);
|
|
443
|
-
await removeTarget(backupPath);
|
|
444
|
-
}
|
|
445
|
-
catch {
|
|
446
|
-
// No stale backup — expected
|
|
447
|
-
}
|
|
448
522
|
if (verbose) {
|
|
449
523
|
logger.detail(`backing up existing target: ${targetPath}`);
|
|
450
524
|
}
|
|
525
|
+
// Remove any stale backup from a previous failed attempt. Using rm with
|
|
526
|
+
// { force: true } avoids a separate lstat existence check and handles
|
|
527
|
+
// the case where the stale backup is a directory (which rename cannot
|
|
528
|
+
// atomically replace on POSIX). This reduces the window between the
|
|
529
|
+
// stale-backup removal and the rename to a single step.
|
|
530
|
+
await rm(backupPath, { recursive: true, force: true });
|
|
451
531
|
await rename(targetPath, backupPath);
|
|
452
532
|
return backupPath;
|
|
453
533
|
}
|
package/dist/core/preflight.js
CHANGED
|
@@ -17,6 +17,12 @@ export async function runPreflight(_options, _manifest, _home, detectedAgents) {
|
|
|
17
17
|
message: `Agent "${agentId}" skill support is provisional: behavior has not been independently verified.`,
|
|
18
18
|
});
|
|
19
19
|
}
|
|
20
|
+
if (agent.policyNote) {
|
|
21
|
+
warnings.push({
|
|
22
|
+
kind: "policy",
|
|
23
|
+
message: `Agent "${agentId}": ${agent.policyNote}`,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
20
26
|
}
|
|
21
27
|
return warnings;
|
|
22
28
|
}
|
package/dist/core/revert.js
CHANGED
|
@@ -1,46 +1,89 @@
|
|
|
1
1
|
import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
3
4
|
import { logger } from "../logger.js";
|
|
4
5
|
import { lookupDeployment, unregisterDeployment } from "./ownership.js";
|
|
5
6
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
6
|
-
|
|
7
|
+
function resolveTargetTemplate(template, home) {
|
|
8
|
+
const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
|
|
9
|
+
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
10
|
+
const xdgConfig = xdgRaw && path.isAbsolute(xdgRaw) ? xdgRaw : path.join(home, ".config");
|
|
11
|
+
return template
|
|
12
|
+
.replace("{home}", home)
|
|
13
|
+
.replace("{appdata}", appdata)
|
|
14
|
+
.replace("{xdg_config}", xdgConfig);
|
|
15
|
+
}
|
|
16
|
+
function buildSkillDirReverts(manifest, home, agentFilter) {
|
|
7
17
|
const actions = [];
|
|
8
18
|
for (const skill of manifest.skills) {
|
|
9
19
|
for (const agentId of skill.agents) {
|
|
10
|
-
if (!
|
|
20
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
11
21
|
continue;
|
|
12
22
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
13
23
|
if (!agent)
|
|
14
24
|
continue;
|
|
15
|
-
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
16
25
|
actions.push({
|
|
17
26
|
kind: "skill-dir",
|
|
18
27
|
skill: skill.name,
|
|
19
28
|
agent: agentId,
|
|
20
|
-
target,
|
|
29
|
+
target: resolveAgentSkillPath(agent, skill.name, home),
|
|
21
30
|
});
|
|
22
31
|
}
|
|
23
32
|
}
|
|
24
33
|
return actions;
|
|
25
34
|
}
|
|
26
|
-
|
|
35
|
+
function buildFileWriteReverts(manifest, home, agentFilter) {
|
|
27
36
|
const actions = [];
|
|
28
|
-
for (const
|
|
29
|
-
for (const agentId of
|
|
37
|
+
for (const fileEntry of manifest.files ?? []) {
|
|
38
|
+
for (const agentId of fileEntry.agents) {
|
|
39
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
40
|
+
continue;
|
|
30
41
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
31
42
|
if (!agent)
|
|
32
43
|
continue;
|
|
33
|
-
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
34
44
|
actions.push({
|
|
35
|
-
kind: "
|
|
36
|
-
skill:
|
|
45
|
+
kind: "file-write",
|
|
46
|
+
skill: fileEntry.name,
|
|
47
|
+
agent: agentId,
|
|
48
|
+
target: resolveTargetTemplate(fileEntry.target, home),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return actions;
|
|
53
|
+
}
|
|
54
|
+
function buildConfigPatchReverts(manifest, home, agentFilter) {
|
|
55
|
+
const actions = [];
|
|
56
|
+
for (const configEntry of manifest.configs ?? []) {
|
|
57
|
+
for (const agentId of configEntry.agents) {
|
|
58
|
+
if (agentFilter && !agentFilter.includes(agentId))
|
|
59
|
+
continue;
|
|
60
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
61
|
+
if (!agent)
|
|
62
|
+
continue;
|
|
63
|
+
actions.push({
|
|
64
|
+
kind: "config-patch",
|
|
65
|
+
skill: configEntry.name,
|
|
37
66
|
agent: agentId,
|
|
38
|
-
target,
|
|
67
|
+
target: resolveTargetTemplate(configEntry.target, home),
|
|
39
68
|
});
|
|
40
69
|
}
|
|
41
70
|
}
|
|
42
71
|
return actions;
|
|
43
72
|
}
|
|
73
|
+
export function planRevert(manifest, detectedAgents, home) {
|
|
74
|
+
return [
|
|
75
|
+
...buildSkillDirReverts(manifest, home, detectedAgents),
|
|
76
|
+
...buildFileWriteReverts(manifest, home, detectedAgents),
|
|
77
|
+
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
export function planRevertAll(manifest, home) {
|
|
81
|
+
return [
|
|
82
|
+
...buildSkillDirReverts(manifest, home, null),
|
|
83
|
+
...buildFileWriteReverts(manifest, home, null),
|
|
84
|
+
...buildConfigPatchReverts(manifest, home, null),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
44
87
|
function recordOutcome(result, action, counts, failed) {
|
|
45
88
|
if (result.outcome === "fail") {
|
|
46
89
|
failed.push({ action, error: result.error });
|
|
@@ -114,9 +157,8 @@ export async function executeRevert(actions, dryRun, verbose, home) {
|
|
|
114
157
|
}
|
|
115
158
|
async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
116
159
|
const label = `${action.skill} -> ${action.agent}`;
|
|
117
|
-
let stat;
|
|
118
160
|
try {
|
|
119
|
-
|
|
161
|
+
await lstat(action.target);
|
|
120
162
|
}
|
|
121
163
|
catch (err) {
|
|
122
164
|
const result = lstatOutcome(err);
|
|
@@ -133,8 +175,6 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
133
175
|
return { outcome: "skip" };
|
|
134
176
|
}
|
|
135
177
|
if (dryRun) {
|
|
136
|
-
logger.plan(label);
|
|
137
|
-
logger.detail(`would remove: ${action.target}`);
|
|
138
178
|
planned.push({
|
|
139
179
|
verb: "remove",
|
|
140
180
|
kind: "skill-dir",
|
|
@@ -145,7 +185,10 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
145
185
|
return { outcome: "ok" };
|
|
146
186
|
}
|
|
147
187
|
try {
|
|
148
|
-
|
|
188
|
+
// Re-stat immediately before deletion to minimise the window between the
|
|
189
|
+
// type-check and the removal syscall.
|
|
190
|
+
const currentStat = await lstat(action.target);
|
|
191
|
+
if (currentStat.isSymbolicLink()) {
|
|
149
192
|
await unlink(action.target);
|
|
150
193
|
}
|
|
151
194
|
else {
|
|
@@ -159,6 +202,11 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
159
202
|
return { outcome: "ok" };
|
|
160
203
|
}
|
|
161
204
|
catch (err) {
|
|
205
|
+
if (err.code === "ENOENT") {
|
|
206
|
+
// Target disappeared between ownership check and removal — treat as skip.
|
|
207
|
+
logger.skip(label, "(disappeared before removal, skipping)");
|
|
208
|
+
return { outcome: "skip" };
|
|
209
|
+
}
|
|
162
210
|
const msg = err instanceof Error ? err.message : String(err);
|
|
163
211
|
logger.fail(label, msg);
|
|
164
212
|
return { outcome: "fail", error: msg };
|
|
@@ -184,8 +232,6 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
184
232
|
return { outcome: "skip" };
|
|
185
233
|
}
|
|
186
234
|
if (dryRun) {
|
|
187
|
-
logger.plan(label);
|
|
188
|
-
logger.detail(`would remove: ${action.target}`);
|
|
189
235
|
planned.push({
|
|
190
236
|
verb: "remove",
|
|
191
237
|
kind: "file-write",
|
|
@@ -196,6 +242,9 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
196
242
|
return { outcome: "ok" };
|
|
197
243
|
}
|
|
198
244
|
try {
|
|
245
|
+
// Re-stat immediately before deletion to minimise the type-check to
|
|
246
|
+
// removal window; handle the case where the file has since disappeared.
|
|
247
|
+
await lstat(action.target);
|
|
199
248
|
await unlink(action.target);
|
|
200
249
|
await unregisterDeployment(home, action.target);
|
|
201
250
|
logger.ok(label);
|
|
@@ -205,6 +254,10 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
205
254
|
return { outcome: "ok" };
|
|
206
255
|
}
|
|
207
256
|
catch (err) {
|
|
257
|
+
if (err.code === "ENOENT") {
|
|
258
|
+
logger.skip(label, "(disappeared before removal, skipping)");
|
|
259
|
+
return { outcome: "skip" };
|
|
260
|
+
}
|
|
208
261
|
const msg = err instanceof Error ? err.message : String(err);
|
|
209
262
|
logger.fail(label, msg);
|
|
210
263
|
return { outcome: "fail", error: msg };
|
|
@@ -234,8 +287,6 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned) {
|
|
|
234
287
|
}
|
|
235
288
|
const configPatchEntry = entry;
|
|
236
289
|
if (dryRun) {
|
|
237
|
-
logger.plan(label);
|
|
238
|
-
logger.detail(`would unapply patch: ${JSON.stringify(configPatchEntry.undoPatch)} -> ${action.target}`);
|
|
239
290
|
planned.push({
|
|
240
291
|
verb: "unapply-patch",
|
|
241
292
|
kind: "config-patch",
|
package/dist/index.js
CHANGED
|
@@ -111,6 +111,21 @@ async function main() {
|
|
|
111
111
|
}
|
|
112
112
|
return runRevert(options, manifest, home);
|
|
113
113
|
}
|
|
114
|
+
function renderDryRunPlan(planned) {
|
|
115
|
+
for (const change of planned) {
|
|
116
|
+
logger.plan(`[${change.agent}] ${change.verb} ${change.skill}`);
|
|
117
|
+
if (change.source !== undefined) {
|
|
118
|
+
logger.detail(`source: ${change.source}`);
|
|
119
|
+
}
|
|
120
|
+
logger.detail(`target: ${change.target}`);
|
|
121
|
+
if (change.verb === "patch-config" && change.patch !== undefined) {
|
|
122
|
+
logger.detail(`patch: ${JSON.stringify(change.patch)}`);
|
|
123
|
+
}
|
|
124
|
+
else if (change.verb === "unapply-patch" && change.patch !== undefined) {
|
|
125
|
+
logger.detail(`undo: ${JSON.stringify(change.patch)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
114
129
|
async function runDeploy(options, manifest, home) {
|
|
115
130
|
let detectedAgents;
|
|
116
131
|
if (options.agents) {
|
|
@@ -132,7 +147,8 @@ async function runDeploy(options, manifest, home) {
|
|
|
132
147
|
}
|
|
133
148
|
const preflightWarnings = await runPreflight(options, manifest, home, detectedAgents);
|
|
134
149
|
for (const w of preflightWarnings) {
|
|
135
|
-
|
|
150
|
+
const label = w.kind === "policy" ? "policy" : "preflight";
|
|
151
|
+
logger.warn(label, w.message);
|
|
136
152
|
}
|
|
137
153
|
const { actions, warnings: planWarnings } = await planDeploy(manifest, options.directory, detectedAgents, home);
|
|
138
154
|
for (const w of planWarnings) {
|
|
@@ -142,14 +158,21 @@ async function runDeploy(options, manifest, home) {
|
|
|
142
158
|
logger.info("No skills to deploy for detected agents.");
|
|
143
159
|
return 0;
|
|
144
160
|
}
|
|
145
|
-
logger.info(`${dryRunPrefix(options.dryRun)}Deploying ${actions.length}
|
|
146
|
-
const { succeeded, failed } = await executeDeploy(actions, options.dryRun, options.verbose, home);
|
|
161
|
+
logger.info(`${dryRunPrefix(options.dryRun)}Deploying ${actions.length} action(s):`);
|
|
162
|
+
const { succeeded, failed, planned } = await executeDeploy(actions, options.dryRun, options.verbose, home);
|
|
163
|
+
if (options.dryRun) {
|
|
164
|
+
logger.info("");
|
|
165
|
+
renderDryRunPlan(planned);
|
|
166
|
+
logger.info("");
|
|
167
|
+
logger.info(`${planned.length} action(s) would be applied (dry-run)`);
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
147
170
|
logger.info("");
|
|
148
171
|
if (failed.length > 0) {
|
|
149
172
|
logger.info(`${succeeded} succeeded, ${failed.length} failed`);
|
|
150
173
|
return 1;
|
|
151
174
|
}
|
|
152
|
-
logger.info(`${succeeded}
|
|
175
|
+
logger.info(`${succeeded} action(s) deployed`);
|
|
153
176
|
return 0;
|
|
154
177
|
}
|
|
155
178
|
async function runRevert(options, manifest, home) {
|
|
@@ -160,21 +183,28 @@ async function runRevert(options, manifest, home) {
|
|
|
160
183
|
logger.info("No skills to revert.");
|
|
161
184
|
return 0;
|
|
162
185
|
}
|
|
163
|
-
logger.info(`${dryRunPrefix(options.dryRun)}Reverting ${actions.length}
|
|
164
|
-
const { succeeded, skipped, failed } = await executeRevert(actions, options.dryRun, options.verbose, home);
|
|
186
|
+
logger.info(`${dryRunPrefix(options.dryRun)}Reverting ${actions.length} action(s):`);
|
|
187
|
+
const { succeeded, skipped, failed, planned } = await executeRevert(actions, options.dryRun, options.verbose, home);
|
|
188
|
+
if (options.dryRun) {
|
|
189
|
+
logger.info("");
|
|
190
|
+
renderDryRunPlan(planned);
|
|
191
|
+
logger.info("");
|
|
192
|
+
logger.info(`${planned.length} action(s) would be removed (dry-run)`);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
165
195
|
logger.info("");
|
|
166
196
|
if (failed.length > 0) {
|
|
167
197
|
const parts = [`${succeeded} removed`];
|
|
168
198
|
if (skipped > 0)
|
|
169
199
|
parts.push(`${skipped} skipped`);
|
|
170
200
|
parts.push(`${failed.length} failed`);
|
|
171
|
-
logger.info(
|
|
201
|
+
logger.info(parts.join(", "));
|
|
172
202
|
return 1;
|
|
173
203
|
}
|
|
174
204
|
const parts = [`${succeeded} removed`];
|
|
175
205
|
if (skipped > 0)
|
|
176
206
|
parts.push(`${skipped} skipped`);
|
|
177
|
-
logger.info(
|
|
207
|
+
logger.info(parts.join(", "));
|
|
178
208
|
return 0;
|
|
179
209
|
}
|
|
180
210
|
const USER_ERROR_EXIT = {
|
|
@@ -22,6 +22,38 @@ export declare const SkillEntrySchema: z.ZodObject<{
|
|
|
22
22
|
"github-copilot": "github-copilot";
|
|
23
23
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
24
24
|
}, z.core.$strip>;
|
|
25
|
+
export declare const FileEntrySchema: z.ZodObject<{
|
|
26
|
+
name: z.ZodString;
|
|
27
|
+
path: z.ZodString;
|
|
28
|
+
target: z.ZodString;
|
|
29
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
30
|
+
"claude-code": "claude-code";
|
|
31
|
+
codex: "codex";
|
|
32
|
+
"gemini-cli": "gemini-cli";
|
|
33
|
+
antigravity: "antigravity";
|
|
34
|
+
opencode: "opencode";
|
|
35
|
+
"github-copilot": "github-copilot";
|
|
36
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
export declare const ConfigEntrySchema: z.ZodObject<{
|
|
39
|
+
name: z.ZodString;
|
|
40
|
+
target: z.ZodString;
|
|
41
|
+
patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
42
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
43
|
+
"claude-code": "claude-code";
|
|
44
|
+
codex: "codex";
|
|
45
|
+
"gemini-cli": "gemini-cli";
|
|
46
|
+
antigravity: "antigravity";
|
|
47
|
+
opencode: "opencode";
|
|
48
|
+
"github-copilot": "github-copilot";
|
|
49
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export declare const McpServerEntrySchema: z.ZodObject<{
|
|
52
|
+
name: z.ZodString;
|
|
53
|
+
}, z.core.$loose>;
|
|
54
|
+
export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
55
|
+
name: z.ZodString;
|
|
56
|
+
}, z.core.$loose>;
|
|
25
57
|
export declare const ManifestSchema: z.ZodObject<{
|
|
26
58
|
skills: z.ZodArray<z.ZodObject<{
|
|
27
59
|
name: z.ZodString;
|
|
@@ -35,10 +67,44 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
35
67
|
"github-copilot": "github-copilot";
|
|
36
68
|
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
37
69
|
}, z.core.$strip>>;
|
|
38
|
-
|
|
39
|
-
|
|
70
|
+
files: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
71
|
+
name: z.ZodString;
|
|
72
|
+
path: z.ZodString;
|
|
73
|
+
target: z.ZodString;
|
|
74
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
75
|
+
"claude-code": "claude-code";
|
|
76
|
+
codex: "codex";
|
|
77
|
+
"gemini-cli": "gemini-cli";
|
|
78
|
+
antigravity: "antigravity";
|
|
79
|
+
opencode: "opencode";
|
|
80
|
+
"github-copilot": "github-copilot";
|
|
81
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
82
|
+
}, z.core.$strip>>>;
|
|
83
|
+
configs: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
84
|
+
name: z.ZodString;
|
|
85
|
+
target: z.ZodString;
|
|
86
|
+
patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
87
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
88
|
+
"claude-code": "claude-code";
|
|
89
|
+
codex: "codex";
|
|
90
|
+
"gemini-cli": "gemini-cli";
|
|
91
|
+
antigravity: "antigravity";
|
|
92
|
+
opencode: "opencode";
|
|
93
|
+
"github-copilot": "github-copilot";
|
|
94
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
95
|
+
}, z.core.$strip>>>;
|
|
96
|
+
mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
97
|
+
name: z.ZodString;
|
|
98
|
+
}, z.core.$loose>>>;
|
|
99
|
+
agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
100
|
+
name: z.ZodString;
|
|
101
|
+
}, z.core.$loose>>>;
|
|
40
102
|
}, z.core.$strip>;
|
|
41
103
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
104
|
+
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
|
105
|
+
export type ConfigEntry = z.infer<typeof ConfigEntrySchema>;
|
|
106
|
+
export type McpServerEntry = z.infer<typeof McpServerEntrySchema>;
|
|
107
|
+
export type AgentRuleEntry = z.infer<typeof AgentRuleEntrySchema>;
|
|
42
108
|
export type Manifest = z.infer<typeof ManifestSchema>;
|
|
43
109
|
export declare const AgentListSchema: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<string[], string>>, z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
44
110
|
"claude-code": "claude-code";
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -10,6 +10,9 @@ const AGENT_IDS = [
|
|
|
10
10
|
];
|
|
11
11
|
export { AGENT_IDS };
|
|
12
12
|
const SAFE_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
13
|
+
// Target templates must start with a known placeholder to prevent raw absolute
|
|
14
|
+
// paths or directory traversal. e.g. "{home}/.claude/settings.json" is valid.
|
|
15
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}/;
|
|
13
16
|
// Standalone schema used for type derivation and single-ID validation (e.g. index.ts).
|
|
14
17
|
export const AgentIdSchema = z.enum(AGENT_IDS);
|
|
15
18
|
// Used inside SkillEntrySchema.agents so that enum failures embed the received
|
|
@@ -26,27 +29,62 @@ const agentIdElement = z
|
|
|
26
29
|
}
|
|
27
30
|
})
|
|
28
31
|
.pipe(AgentIdSchema);
|
|
32
|
+
const nameField = z
|
|
33
|
+
.string({ message: "name must be a non-empty string" })
|
|
34
|
+
.min(1, { message: "name must be a non-empty string" })
|
|
35
|
+
.regex(SAFE_NAME_RE, {
|
|
36
|
+
message: "name must contain only letters, digits, hyphens, underscores, and dots, and must not start with a dot",
|
|
37
|
+
});
|
|
38
|
+
const agentsField = z
|
|
39
|
+
.array(agentIdElement, { message: "agents must be a non-empty array" })
|
|
40
|
+
.min(1, { message: "agents must be a non-empty array" })
|
|
41
|
+
.transform((arr) => [...new Set(arr)]);
|
|
42
|
+
const sourcePathField = z
|
|
43
|
+
.string({ message: "path must be a non-empty string" })
|
|
44
|
+
.min(1, { message: "path must be a non-empty string" })
|
|
45
|
+
.refine((p) => !nodePath.isAbsolute(p), {
|
|
46
|
+
message: "path must be a relative path",
|
|
47
|
+
})
|
|
48
|
+
.refine((p) => !nodePath.normalize(p).startsWith(".."), {
|
|
49
|
+
message: "path must not escape the repository root",
|
|
50
|
+
});
|
|
51
|
+
const targetTemplateField = z
|
|
52
|
+
.string({ message: "target must be a non-empty string" })
|
|
53
|
+
.min(1, { message: "target must be a non-empty string" })
|
|
54
|
+
.refine((t) => TARGET_TEMPLATE_RE.test(t), {
|
|
55
|
+
message: "target must start with a known placeholder: {home}, {appdata}, or {xdg_config}",
|
|
56
|
+
});
|
|
29
57
|
export const SkillEntrySchema = z.object({
|
|
30
|
-
name:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.regex(SAFE_NAME_RE, {
|
|
34
|
-
message: "name must contain only letters, digits, hyphens, underscores, and dots, and must not start with a dot",
|
|
35
|
-
}),
|
|
36
|
-
path: z
|
|
37
|
-
.string({ message: "path must be a non-empty string" })
|
|
38
|
-
.min(1, { message: "path must be a non-empty string" })
|
|
39
|
-
.refine((p) => !nodePath.isAbsolute(p), {
|
|
40
|
-
message: "path must be a relative path",
|
|
41
|
-
})
|
|
42
|
-
.refine((p) => !nodePath.normalize(p).startsWith(".."), {
|
|
43
|
-
message: "path must not escape the repository root",
|
|
44
|
-
}),
|
|
45
|
-
agents: z
|
|
46
|
-
.array(agentIdElement, { message: "agents must be a non-empty array" })
|
|
47
|
-
.min(1, { message: "agents must be a non-empty array" })
|
|
48
|
-
.transform((arr) => [...new Set(arr)]),
|
|
58
|
+
name: nameField,
|
|
59
|
+
path: sourcePathField,
|
|
60
|
+
agents: agentsField,
|
|
49
61
|
});
|
|
62
|
+
export const FileEntrySchema = z.object({
|
|
63
|
+
name: nameField,
|
|
64
|
+
path: sourcePathField,
|
|
65
|
+
target: targetTemplateField,
|
|
66
|
+
agents: agentsField,
|
|
67
|
+
});
|
|
68
|
+
export const ConfigEntrySchema = z.object({
|
|
69
|
+
name: nameField,
|
|
70
|
+
target: targetTemplateField,
|
|
71
|
+
patch: z.record(z.string(), z.unknown()),
|
|
72
|
+
agents: agentsField,
|
|
73
|
+
});
|
|
74
|
+
export const McpServerEntrySchema = z
|
|
75
|
+
.object({
|
|
76
|
+
name: z
|
|
77
|
+
.string({ message: "mcpServers entry name must be a non-empty string" })
|
|
78
|
+
.min(1, { message: "mcpServers entry name must be a non-empty string" }),
|
|
79
|
+
})
|
|
80
|
+
.passthrough();
|
|
81
|
+
export const AgentRuleEntrySchema = z
|
|
82
|
+
.object({
|
|
83
|
+
name: z
|
|
84
|
+
.string({ message: "agentRules entry name must be a non-empty string" })
|
|
85
|
+
.min(1, { message: "agentRules entry name must be a non-empty string" }),
|
|
86
|
+
})
|
|
87
|
+
.passthrough();
|
|
50
88
|
export const ManifestSchema = z.object({
|
|
51
89
|
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
52
90
|
const seen = new Set();
|
|
@@ -61,8 +99,10 @@ export const ManifestSchema = z.object({
|
|
|
61
99
|
seen.add(skill.name);
|
|
62
100
|
}
|
|
63
101
|
}),
|
|
64
|
-
|
|
65
|
-
|
|
102
|
+
files: z.array(FileEntrySchema).default([]),
|
|
103
|
+
configs: z.array(ConfigEntrySchema).default([]),
|
|
104
|
+
mcpServers: z.array(McpServerEntrySchema).default([]),
|
|
105
|
+
agentRules: z.array(AgentRuleEntrySchema).default([]),
|
|
66
106
|
});
|
|
67
107
|
// Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
|
|
68
108
|
export const AgentListSchema = z
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentId } from "./schemas/manifest.ts";
|
|
2
|
-
export type { AgentId, Manifest, SkillEntry } from "./schemas/manifest.ts";
|
|
2
|
+
export type { AgentId, ConfigEntry, FileEntry, Manifest, SkillEntry, } from "./schemas/manifest.ts";
|
|
3
3
|
export interface AgentPaths {
|
|
4
4
|
posix: string[];
|
|
5
5
|
windows: string[];
|
|
@@ -17,6 +17,7 @@ export interface AgentConfig {
|
|
|
17
17
|
detectPaths: AgentPaths;
|
|
18
18
|
detectBinary: string | null;
|
|
19
19
|
provenance: AgentProvenance;
|
|
20
|
+
policyNote?: string;
|
|
20
21
|
}
|
|
21
22
|
export interface PlanWarning {
|
|
22
23
|
kind: "confidence" | "collision" | "ambiguity";
|