@kuznai/inception-engine 0.8.0 → 0.10.1
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 +52 -22
- package/dist/config/agents.js +34 -0
- package/dist/core/adapters/index.d.ts +11 -0
- package/dist/core/adapters/index.js +18 -0
- package/dist/core/adapters/mcp.d.ts +8 -0
- package/dist/core/adapters/mcp.js +51 -0
- package/dist/core/adapters/rules.d.ts +8 -0
- package/dist/core/adapters/rules.js +56 -0
- package/dist/core/deploy.d.ts +22 -2
- package/dist/core/deploy.js +166 -114
- package/dist/core/ownership.d.ts +10 -5
- package/dist/core/ownership.js +14 -10
- package/dist/core/resolve.d.ts +1 -0
- package/dist/core/resolve.js +5 -6
- package/dist/core/revert.d.ts +6 -1
- package/dist/core/revert.js +26 -24
- package/dist/core/runtime-paths.d.ts +8 -0
- package/dist/core/runtime-paths.js +57 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.js +66 -0
- package/dist/schemas/manifest.d.ts +40 -4
- package/dist/schemas/manifest.js +20 -17
- package/dist/types.d.ts +4 -0
- package/package.json +5 -3
package/dist/core/revert.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { lstat, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
2
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
3
|
import { logger } from "../logger.js";
|
|
5
|
-
import {
|
|
4
|
+
import { compileAgentRuleReverts, compileMcpServerReverts, } from "./adapters/index.js";
|
|
5
|
+
import { lookupDeployment, unregisterDeployment, } from "./ownership.js";
|
|
6
6
|
import { resolveAgentSkillPath } from "./resolve.js";
|
|
7
|
-
|
|
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
|
-
}
|
|
7
|
+
import { resolveTargetTemplate } from "./runtime-paths.js";
|
|
16
8
|
function buildSkillDirReverts(manifest, home, agentFilter) {
|
|
17
9
|
const actions = [];
|
|
18
10
|
for (const skill of manifest.skills) {
|
|
@@ -75,6 +67,8 @@ export function planRevert(manifest, detectedAgents, home) {
|
|
|
75
67
|
...buildSkillDirReverts(manifest, home, detectedAgents),
|
|
76
68
|
...buildFileWriteReverts(manifest, home, detectedAgents),
|
|
77
69
|
...buildConfigPatchReverts(manifest, home, detectedAgents),
|
|
70
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, detectedAgents, home)),
|
|
71
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, detectedAgents, home)),
|
|
78
72
|
];
|
|
79
73
|
}
|
|
80
74
|
export function planRevertAll(manifest, home) {
|
|
@@ -82,6 +76,8 @@ export function planRevertAll(manifest, home) {
|
|
|
82
76
|
...buildSkillDirReverts(manifest, home, null),
|
|
83
77
|
...buildFileWriteReverts(manifest, home, null),
|
|
84
78
|
...buildConfigPatchReverts(manifest, home, null),
|
|
79
|
+
...(manifest.mcpServers ?? []).flatMap((e) => compileMcpServerReverts(e, null, home)),
|
|
80
|
+
...(manifest.agentRules ?? []).flatMap((e) => compileAgentRuleReverts(e, null, home)),
|
|
85
81
|
];
|
|
86
82
|
}
|
|
87
83
|
function recordOutcome(result, action, counts, failed) {
|
|
@@ -109,12 +105,18 @@ async function readJsonConfig(filePath) {
|
|
|
109
105
|
}
|
|
110
106
|
return parsed;
|
|
111
107
|
}
|
|
108
|
+
function isPlainObject(v) {
|
|
109
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
110
|
+
}
|
|
112
111
|
function applyUndoPatch(current, undoPatch) {
|
|
113
112
|
const restored = { ...current };
|
|
114
113
|
for (const [key, originalValue] of Object.entries(undoPatch)) {
|
|
115
114
|
if (originalValue === null) {
|
|
116
115
|
delete restored[key];
|
|
117
116
|
}
|
|
117
|
+
else if (isPlainObject(originalValue) && isPlainObject(restored[key])) {
|
|
118
|
+
restored[key] = applyUndoPatch(restored[key], originalValue);
|
|
119
|
+
}
|
|
118
120
|
else {
|
|
119
121
|
restored[key] = originalValue;
|
|
120
122
|
}
|
|
@@ -128,7 +130,7 @@ function lstatOutcome(err) {
|
|
|
128
130
|
const msg = err instanceof Error ? err.message : String(err);
|
|
129
131
|
return { outcome: "fail", error: msg };
|
|
130
132
|
}
|
|
131
|
-
export async function executeRevert(actions, dryRun, verbose, home) {
|
|
133
|
+
export async function executeRevert(actions, dryRun, verbose, home, deps = {}) {
|
|
132
134
|
let succeeded = 0;
|
|
133
135
|
let skipped = 0;
|
|
134
136
|
const failed = [];
|
|
@@ -138,13 +140,13 @@ export async function executeRevert(actions, dryRun, verbose, home) {
|
|
|
138
140
|
let result;
|
|
139
141
|
switch (action.kind) {
|
|
140
142
|
case "skill-dir":
|
|
141
|
-
result = await executeRevertAction(action, dryRun, verbose, home, planned);
|
|
143
|
+
result = await executeRevertAction(action, dryRun, verbose, home, planned, deps);
|
|
142
144
|
break;
|
|
143
145
|
case "file-write":
|
|
144
|
-
result = await revertFileWrite(action, dryRun, verbose, home, planned);
|
|
146
|
+
result = await revertFileWrite(action, dryRun, verbose, home, planned, deps);
|
|
145
147
|
break;
|
|
146
148
|
case "config-patch":
|
|
147
|
-
result = await revertConfigPatch(action, dryRun, verbose, home, planned);
|
|
149
|
+
result = await revertConfigPatch(action, dryRun, verbose, home, planned, deps);
|
|
148
150
|
break;
|
|
149
151
|
default:
|
|
150
152
|
throw new Error(`Unhandled revert action kind: ${action}`);
|
|
@@ -155,7 +157,7 @@ export async function executeRevert(actions, dryRun, verbose, home) {
|
|
|
155
157
|
skipped = counts.skipped;
|
|
156
158
|
return { succeeded, skipped, failed, planned };
|
|
157
159
|
}
|
|
158
|
-
async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
160
|
+
async function executeRevertAction(action, dryRun, verbose, home, planned, deps) {
|
|
159
161
|
const label = `${action.skill} -> ${action.agent}`;
|
|
160
162
|
try {
|
|
161
163
|
await lstat(action.target);
|
|
@@ -169,7 +171,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
169
171
|
logger.fail(label, result.error);
|
|
170
172
|
return result;
|
|
171
173
|
}
|
|
172
|
-
const entry = await lookupDeployment(home, action.target);
|
|
174
|
+
const entry = await lookupDeployment(home, action.target, deps.registry);
|
|
173
175
|
if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
|
|
174
176
|
logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
|
|
175
177
|
return { outcome: "skip" };
|
|
@@ -194,7 +196,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
194
196
|
else {
|
|
195
197
|
await rm(action.target, { recursive: true });
|
|
196
198
|
}
|
|
197
|
-
await unregisterDeployment(home, action.target);
|
|
199
|
+
await unregisterDeployment(home, action.target, deps.registry);
|
|
198
200
|
logger.ok(label);
|
|
199
201
|
if (verbose) {
|
|
200
202
|
logger.detail(`removed: ${action.target}`);
|
|
@@ -212,7 +214,7 @@ async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
|
212
214
|
return { outcome: "fail", error: msg };
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
|
-
async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
217
|
+
async function revertFileWrite(action, dryRun, verbose, home, planned, deps) {
|
|
216
218
|
const label = `${action.skill} -> ${action.agent}`;
|
|
217
219
|
try {
|
|
218
220
|
await lstat(action.target);
|
|
@@ -226,7 +228,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
226
228
|
logger.fail(label, result.error);
|
|
227
229
|
return result;
|
|
228
230
|
}
|
|
229
|
-
const entry = await lookupDeployment(home, action.target);
|
|
231
|
+
const entry = await lookupDeployment(home, action.target, deps.registry);
|
|
230
232
|
if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
|
|
231
233
|
logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
|
|
232
234
|
return { outcome: "skip" };
|
|
@@ -246,7 +248,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
246
248
|
// removal window; handle the case where the file has since disappeared.
|
|
247
249
|
await lstat(action.target);
|
|
248
250
|
await unlink(action.target);
|
|
249
|
-
await unregisterDeployment(home, action.target);
|
|
251
|
+
await unregisterDeployment(home, action.target, deps.registry);
|
|
250
252
|
logger.ok(label);
|
|
251
253
|
if (verbose) {
|
|
252
254
|
logger.detail(`removed: ${action.target}`);
|
|
@@ -263,7 +265,7 @@ async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
|
263
265
|
return { outcome: "fail", error: msg };
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
|
-
async function revertConfigPatch(action, dryRun, verbose, home, planned) {
|
|
268
|
+
async function revertConfigPatch(action, dryRun, verbose, home, planned, deps) {
|
|
267
269
|
const label = `${action.skill} -> ${action.agent}`;
|
|
268
270
|
try {
|
|
269
271
|
await lstat(action.target);
|
|
@@ -277,7 +279,7 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned) {
|
|
|
277
279
|
logger.fail(label, result.error);
|
|
278
280
|
return result;
|
|
279
281
|
}
|
|
280
|
-
const entry = await lookupDeployment(home, action.target);
|
|
282
|
+
const entry = await lookupDeployment(home, action.target, deps.registry);
|
|
281
283
|
if (!entry ||
|
|
282
284
|
entry.kind !== "config-patch" ||
|
|
283
285
|
entry.skill !== action.skill ||
|
|
@@ -301,7 +303,7 @@ async function revertConfigPatch(action, dryRun, verbose, home, planned) {
|
|
|
301
303
|
const current = await readJsonConfig(action.target);
|
|
302
304
|
const restored = applyUndoPatch(current, configPatchEntry.undoPatch);
|
|
303
305
|
await writeFile(action.target, `${JSON.stringify(restored, null, 2)}\n`, "utf-8");
|
|
304
|
-
await unregisterDeployment(home, action.target);
|
|
306
|
+
await unregisterDeployment(home, action.target, deps.registry);
|
|
305
307
|
logger.ok(label);
|
|
306
308
|
if (verbose) {
|
|
307
309
|
logger.detail(`unapplied patch from: ${action.target}`);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export interface RuntimePaths {
|
|
3
|
+
appdata: string;
|
|
4
|
+
xdgConfig: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function getPathApi(root: string): typeof path.posix | typeof path.win32;
|
|
7
|
+
export declare function resolveRuntimePaths(home: string): RuntimePaths;
|
|
8
|
+
export declare function resolveTargetTemplate(template: string, home: string): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}(?<suffix>(?:[\\/].*)?)$/;
|
|
3
|
+
export function getPathApi(root) {
|
|
4
|
+
if (root.includes("\\") || /^[a-zA-Z]:/.test(root)) {
|
|
5
|
+
return path.win32;
|
|
6
|
+
}
|
|
7
|
+
if (root.startsWith("/")) {
|
|
8
|
+
return path.posix;
|
|
9
|
+
}
|
|
10
|
+
return process.platform === "win32" ? path.win32 : path.posix;
|
|
11
|
+
}
|
|
12
|
+
function normalizePathForComparison(candidate, pathApi) {
|
|
13
|
+
const normalized = pathApi.normalize(candidate);
|
|
14
|
+
return pathApi === path.win32 ? normalized.toLowerCase() : normalized;
|
|
15
|
+
}
|
|
16
|
+
function isSameOrDescendantPath(candidate, root) {
|
|
17
|
+
const pathApi = getPathApi(root);
|
|
18
|
+
const normalizedCandidate = normalizePathForComparison(candidate, pathApi);
|
|
19
|
+
const normalizedRoot = normalizePathForComparison(root, pathApi);
|
|
20
|
+
return (normalizedCandidate === normalizedRoot ||
|
|
21
|
+
normalizedCandidate.startsWith(normalizedRoot + pathApi.sep));
|
|
22
|
+
}
|
|
23
|
+
export function resolveRuntimePaths(home) {
|
|
24
|
+
const appdataRaw = process.env.APPDATA;
|
|
25
|
+
const homePathApi = getPathApi(home);
|
|
26
|
+
const appdata = appdataRaw && getPathApi(appdataRaw).isAbsolute(appdataRaw)
|
|
27
|
+
? appdataRaw
|
|
28
|
+
: homePathApi.join(home, "AppData", "Roaming");
|
|
29
|
+
const xdgRaw = process.env.XDG_CONFIG_HOME;
|
|
30
|
+
const xdgConfig = xdgRaw && getPathApi(xdgRaw).isAbsolute(xdgRaw)
|
|
31
|
+
? xdgRaw
|
|
32
|
+
: homePathApi.join(home, ".config");
|
|
33
|
+
return { appdata, xdgConfig };
|
|
34
|
+
}
|
|
35
|
+
export function resolveTargetTemplate(template, home) {
|
|
36
|
+
const { appdata, xdgConfig } = resolveRuntimePaths(home);
|
|
37
|
+
const match = TARGET_TEMPLATE_RE.exec(template);
|
|
38
|
+
if (!match) {
|
|
39
|
+
throw new Error(`Invalid target template: ${template}`);
|
|
40
|
+
}
|
|
41
|
+
const baseByRoot = {
|
|
42
|
+
home,
|
|
43
|
+
appdata,
|
|
44
|
+
xdg_config: xdgConfig,
|
|
45
|
+
};
|
|
46
|
+
const root = match[1];
|
|
47
|
+
const suffix = match.groups?.suffix ?? "";
|
|
48
|
+
const segments = suffix.split(/[\\/]+/).filter(Boolean);
|
|
49
|
+
const pathApi = getPathApi(baseByRoot[root]);
|
|
50
|
+
const resolved = segments.length === 0
|
|
51
|
+
? baseByRoot[root]
|
|
52
|
+
: pathApi.join(baseByRoot[root], ...segments);
|
|
53
|
+
if (!isSameOrDescendantPath(resolved, baseByRoot[root])) {
|
|
54
|
+
throw new Error(`Target template resolves outside its placeholder root: ${template}`);
|
|
55
|
+
}
|
|
56
|
+
return suffix === "" ? baseByRoot[root] : `${baseByRoot[root]}${suffix}`;
|
|
57
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare function sourceAccessError(err: unknown, sourcePath: string): string;
|
|
2
|
+
export declare function validateSourcePath(source: string, skillPath: string, resolvedSourceDir: string, realRoot: string): Promise<void>;
|
|
3
|
+
export declare function validateSourceFile(sourcePath: string, manifestPath: string): Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { lstat, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { UserError } from "../errors.js";
|
|
4
|
+
export function sourceAccessError(err, sourcePath) {
|
|
5
|
+
const code = err.code;
|
|
6
|
+
if (code === "ENOENT")
|
|
7
|
+
return `Source not found: ${sourcePath}`;
|
|
8
|
+
if (code === "EACCES" || code === "EPERM")
|
|
9
|
+
return `Permission denied accessing source: ${sourcePath}`;
|
|
10
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
11
|
+
return `Failed to access source ${sourcePath}: ${detail}`;
|
|
12
|
+
}
|
|
13
|
+
function normalizePathForComparison(candidate) {
|
|
14
|
+
const normalized = path.normalize(candidate);
|
|
15
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
16
|
+
}
|
|
17
|
+
function isSameOrDescendantPath(candidate, root) {
|
|
18
|
+
const normalizedCandidate = normalizePathForComparison(candidate);
|
|
19
|
+
const normalizedRoot = normalizePathForComparison(root);
|
|
20
|
+
return (normalizedCandidate === normalizedRoot ||
|
|
21
|
+
normalizedCandidate.startsWith(normalizedRoot + path.sep));
|
|
22
|
+
}
|
|
23
|
+
async function isSameFileSystemLocation(a, b) {
|
|
24
|
+
const [aStat, bStat] = await Promise.all([stat(a), stat(b)]);
|
|
25
|
+
return aStat.dev === bStat.dev && aStat.ino === bStat.ino;
|
|
26
|
+
}
|
|
27
|
+
async function isWithinRootByIdentity(candidate, root) {
|
|
28
|
+
let current = candidate;
|
|
29
|
+
while (true) {
|
|
30
|
+
if (await isSameFileSystemLocation(current, root))
|
|
31
|
+
return true;
|
|
32
|
+
const parent = path.dirname(current);
|
|
33
|
+
if (parent === current)
|
|
34
|
+
return false;
|
|
35
|
+
current = parent;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot) {
|
|
39
|
+
if (!source.startsWith(resolvedSourceDir + path.sep)) {
|
|
40
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root: ${source}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const realSource = await realpath(source);
|
|
44
|
+
if (!(isSameOrDescendantPath(realSource, realRoot) ||
|
|
45
|
+
(await isWithinRootByIdentity(realSource, realRoot)))) {
|
|
46
|
+
throw new UserError("DEPLOY_FAILED", `Skill path "${skillPath}" resolves outside the repository root via symlink: ${source} -> ${realSource}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err instanceof UserError)
|
|
51
|
+
throw err;
|
|
52
|
+
// Source doesn't exist yet — will be caught during execute
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export async function validateSourceFile(sourcePath, manifestPath) {
|
|
56
|
+
let stat;
|
|
57
|
+
try {
|
|
58
|
+
stat = await lstat(sourcePath);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
throw new UserError("DEPLOY_FAILED", sourceAccessError(err, manifestPath));
|
|
62
|
+
}
|
|
63
|
+
if (!stat.isFile()) {
|
|
64
|
+
throw new UserError("DEPLOY_FAILED", `Source is not a file: ${manifestPath}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -50,10 +50,28 @@ export declare const ConfigEntrySchema: z.ZodObject<{
|
|
|
50
50
|
}, z.core.$strip>;
|
|
51
51
|
export declare const McpServerEntrySchema: z.ZodObject<{
|
|
52
52
|
name: z.ZodString;
|
|
53
|
-
|
|
53
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
54
|
+
"claude-code": "claude-code";
|
|
55
|
+
codex: "codex";
|
|
56
|
+
"gemini-cli": "gemini-cli";
|
|
57
|
+
antigravity: "antigravity";
|
|
58
|
+
opencode: "opencode";
|
|
59
|
+
"github-copilot": "github-copilot";
|
|
60
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
61
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
62
|
+
}, z.core.$strip>;
|
|
54
63
|
export declare const AgentRuleEntrySchema: z.ZodObject<{
|
|
55
64
|
name: z.ZodString;
|
|
56
|
-
|
|
65
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
66
|
+
"claude-code": "claude-code";
|
|
67
|
+
codex: "codex";
|
|
68
|
+
"gemini-cli": "gemini-cli";
|
|
69
|
+
antigravity: "antigravity";
|
|
70
|
+
opencode: "opencode";
|
|
71
|
+
"github-copilot": "github-copilot";
|
|
72
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
73
|
+
path: z.ZodString;
|
|
74
|
+
}, z.core.$strip>;
|
|
57
75
|
export declare const ManifestSchema: z.ZodObject<{
|
|
58
76
|
skills: z.ZodArray<z.ZodObject<{
|
|
59
77
|
name: z.ZodString;
|
|
@@ -95,10 +113,28 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
95
113
|
}, z.core.$strip>>>;
|
|
96
114
|
mcpServers: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
97
115
|
name: z.ZodString;
|
|
98
|
-
|
|
116
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
117
|
+
"claude-code": "claude-code";
|
|
118
|
+
codex: "codex";
|
|
119
|
+
"gemini-cli": "gemini-cli";
|
|
120
|
+
antigravity: "antigravity";
|
|
121
|
+
opencode: "opencode";
|
|
122
|
+
"github-copilot": "github-copilot";
|
|
123
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
124
|
+
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
125
|
+
}, z.core.$strip>>>;
|
|
99
126
|
agentRules: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
100
127
|
name: z.ZodString;
|
|
101
|
-
|
|
128
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
129
|
+
"claude-code": "claude-code";
|
|
130
|
+
codex: "codex";
|
|
131
|
+
"gemini-cli": "gemini-cli";
|
|
132
|
+
antigravity: "antigravity";
|
|
133
|
+
opencode: "opencode";
|
|
134
|
+
"github-copilot": "github-copilot";
|
|
135
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
136
|
+
path: z.ZodString;
|
|
137
|
+
}, z.core.$strip>>>;
|
|
102
138
|
}, z.core.$strip>;
|
|
103
139
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
104
140
|
export type FileEntry = z.infer<typeof FileEntrySchema>;
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -10,9 +10,10 @@ 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
|
|
14
|
-
//
|
|
15
|
-
|
|
13
|
+
// Target templates must be rooted at a known placeholder and may only add
|
|
14
|
+
// descendant path segments beneath that root. e.g. "{home}/.claude/settings.json"
|
|
15
|
+
// is valid, while "{home}/../.ssh/config" is rejected.
|
|
16
|
+
const TARGET_TEMPLATE_RE = /^\{(home|appdata|xdg_config)\}(?:[\\/].*)?$/;
|
|
16
17
|
// Standalone schema used for type derivation and single-ID validation (e.g. index.ts).
|
|
17
18
|
export const AgentIdSchema = z.enum(AGENT_IDS);
|
|
18
19
|
// Used inside SkillEntrySchema.agents so that enum failures embed the received
|
|
@@ -53,6 +54,9 @@ const targetTemplateField = z
|
|
|
53
54
|
.min(1, { message: "target must be a non-empty string" })
|
|
54
55
|
.refine((t) => TARGET_TEMPLATE_RE.test(t), {
|
|
55
56
|
message: "target must start with a known placeholder: {home}, {appdata}, or {xdg_config}",
|
|
57
|
+
})
|
|
58
|
+
.refine((t) => !t.split(/[\\/]+/).includes(".."), {
|
|
59
|
+
message: "target must not escape its placeholder root",
|
|
56
60
|
});
|
|
57
61
|
export const SkillEntrySchema = z.object({
|
|
58
62
|
name: nameField,
|
|
@@ -71,20 +75,19 @@ export const ConfigEntrySchema = z.object({
|
|
|
71
75
|
patch: z.record(z.string(), z.unknown()),
|
|
72
76
|
agents: agentsField,
|
|
73
77
|
});
|
|
74
|
-
export const McpServerEntrySchema = z
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
export const AgentRuleEntrySchema = z
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
87
|
-
.passthrough();
|
|
78
|
+
export const McpServerEntrySchema = z.object({
|
|
79
|
+
name: nameField,
|
|
80
|
+
agents: agentsField,
|
|
81
|
+
// Raw server descriptor passed verbatim to each agent adapter.
|
|
82
|
+
// Adapters are responsible for validating the shape they need.
|
|
83
|
+
config: z.record(z.string(), z.unknown()),
|
|
84
|
+
});
|
|
85
|
+
export const AgentRuleEntrySchema = z.object({
|
|
86
|
+
name: nameField,
|
|
87
|
+
agents: agentsField,
|
|
88
|
+
// Relative path to the rules/instruction file within the source bundle.
|
|
89
|
+
path: sourcePathField,
|
|
90
|
+
});
|
|
88
91
|
export const ManifestSchema = z.object({
|
|
89
92
|
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
90
93
|
const seen = new Set();
|
package/dist/types.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface AgentProvenance {
|
|
|
9
9
|
skills: Confidence;
|
|
10
10
|
detectPaths: Confidence;
|
|
11
11
|
detectBinary: Confidence;
|
|
12
|
+
mcpConfig?: Confidence;
|
|
13
|
+
agentRules?: Confidence;
|
|
12
14
|
}
|
|
13
15
|
export interface AgentConfig {
|
|
14
16
|
id: AgentId;
|
|
@@ -17,6 +19,8 @@ export interface AgentConfig {
|
|
|
17
19
|
detectPaths: AgentPaths;
|
|
18
20
|
detectBinary: string | null;
|
|
19
21
|
provenance: AgentProvenance;
|
|
22
|
+
mcpConfigPath?: AgentPaths;
|
|
23
|
+
agentRulesPath?: AgentPaths;
|
|
20
24
|
policyNote?: string;
|
|
21
25
|
}
|
|
22
26
|
export interface PlanWarning {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuznai/inception-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|
|
@@ -38,11 +38,13 @@
|
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
39
39
|
"fmt": "biome format --write .",
|
|
40
40
|
"lint": "biome lint . --max-diagnostics none",
|
|
41
|
+
"lint:fix": "biome lint . --max-diagnostics none --write --unsafe",
|
|
41
42
|
"predev": "node scripts/assert-direct-ts-node.mjs",
|
|
42
43
|
"dev": "node src/index.ts",
|
|
43
44
|
"pretest": "node scripts/assert-direct-ts-node.mjs",
|
|
44
|
-
"test": "node --test test
|
|
45
|
-
"
|
|
45
|
+
"test": "node --test --test-isolation=none test/**/*.test.ts",
|
|
46
|
+
"test:windows": "node --test --test-isolation=none test/unit/*.test.ts test/os/cross-platform/*.test.ts test/os/windows/*.test.ts",
|
|
47
|
+
"test:posix": "node --test --test-isolation=none test/unit/*.test.ts test/os/cross-platform/*.test.ts test/os/posix/*.test.ts"
|
|
46
48
|
},
|
|
47
49
|
"dependencies": {
|
|
48
50
|
"zod": "^4.0.0"
|