@kuznai/inception-engine 0.6.0 → 0.7.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 +12 -7
- package/dist/config/manifest.js +6 -1
- package/dist/core/deploy.d.ts +6 -2
- package/dist/core/deploy.js +298 -25
- package/dist/core/detect.d.ts +5 -0
- package/dist/core/detect.js +30 -13
- package/dist/core/ownership.d.ts +19 -7
- package/dist/core/ownership.js +9 -3
- package/dist/core/preflight.d.ts +2 -2
- package/dist/core/preflight.js +21 -5
- package/dist/core/revert.d.ts +2 -1
- package/dist/core/revert.js +177 -17
- package/dist/index.js +9 -6
- package/dist/schemas/manifest.d.ts +6 -6
- package/dist/schemas/manifest.js +17 -4
- package/dist/schemas/registry.d.ts +112 -5
- package/dist/schemas/registry.js +23 -2
- package/dist/types.d.ts +51 -2
- package/package.json +4 -2
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 is a skills deployer.
|
|
5
|
+
Today, inception-engine is a skills deployer. The public manifest and CLI planner currently deploy and revert skill directories only. The core executor also has internal support for single-file write and JSON config patch actions, but the manifest format does not expose them yet. MCP configuration and agent-specific config patching remain unimplemented at the manifest level.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -40,9 +40,11 @@ Managed skills overwrite their previous version. If a target exists but was not
|
|
|
40
40
|
|
|
41
41
|
| Feature | Status |
|
|
42
42
|
|---|---|
|
|
43
|
-
| Skills (SKILL.md) | Supported |
|
|
44
|
-
|
|
|
45
|
-
|
|
|
43
|
+
| Skills (SKILL.md) | Supported via manifest and CLI |
|
|
44
|
+
| File write | Internal executor support only; not exposed in manifest or CLI planning |
|
|
45
|
+
| Config patch (JSON merge) | Internal executor support only; not exposed in manifest or CLI planning |
|
|
46
|
+
| MCP Servers | Accepted in manifest for forward compatibility, not implemented |
|
|
47
|
+
| Agent Rules | Accepted in manifest for forward compatibility, not implemented |
|
|
46
48
|
|
|
47
49
|
## Manifest Format
|
|
48
50
|
|
|
@@ -68,7 +70,7 @@ Each skill entry has:
|
|
|
68
70
|
- **path** - Relative path to the skill directory within the repo
|
|
69
71
|
- **agents** - Array of agent IDs to deploy this skill to. If an agent isn't installed, it's skipped.
|
|
70
72
|
|
|
71
|
-
`mcpServers` and `agentRules` are currently parsed for forward compatibility, but the deployment engine ignores them today.
|
|
73
|
+
`mcpServers` and `agentRules` are currently parsed for forward compatibility, but the deployment engine ignores them today. The manifest-driven planner and revert flow currently derive actions from `skills` entries only.
|
|
72
74
|
|
|
73
75
|
## Creating Skills
|
|
74
76
|
|
|
@@ -165,7 +167,7 @@ inception-engine maintains a centralized deployment registry at `~/.inception-en
|
|
|
165
167
|
|
|
166
168
|
- **Registry-based ownership**: On revert, the registry is checked before removing any target. Only targets with a valid registry entry are removed. On redeploy, unmanaged targets are never replaced.
|
|
167
169
|
|
|
168
|
-
- **Strong binding**: Each registry entry binds a specific target path to its
|
|
170
|
+
- **Strong binding**: Each registry entry binds a specific target path to its skill, agent, and action kind, with action-specific provenance fields (`source` and `method` for skill-dir and file-write; `patch` and `undoPatch` for config-patch). A target is only considered managed if all relevant fields match — a stray entry or a different deployment cannot satisfy the check.
|
|
169
171
|
|
|
170
172
|
- **Atomic redeploy**: When overwriting an existing managed target, the engine renames the old target to a backup, creates the new deployment, and only removes the backup on success. If the new deployment fails, the backup is restored.
|
|
171
173
|
|
|
@@ -201,7 +203,10 @@ Windows deployment currently uses directory-level copy (one `cp -r` per skill).
|
|
|
201
203
|
|
|
202
204
|
## Requirements
|
|
203
205
|
|
|
204
|
-
- Node.js >=
|
|
206
|
+
- Published CLI runtime: Node.js >= 22.3.0
|
|
207
|
+
- Direct TypeScript execution in this repo (`npm run dev`, `npm test`): Node.js >= 22.18.0
|
|
208
|
+
|
|
209
|
+
`inception-engine` publishes compiled JavaScript from `dist/`, so end users do not need the newer Node version required for this repository's direct `.ts` workflows. The higher contributor floor exists only because this repo intentionally runs TypeScript straight through `node` for local development and tests, with no `tsx`, `ts-node`, or experimental TypeScript flags.
|
|
205
210
|
|
|
206
211
|
## License
|
|
207
212
|
|
package/dist/config/manifest.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { UserError } from "../errors.js";
|
|
3
4
|
import { formatZodPath } from "../schemas/errors.js";
|
|
4
5
|
import { ManifestSchema } from "../schemas/manifest.js";
|
|
5
|
-
import { UserError } from "../errors.js";
|
|
6
6
|
export async function loadManifest(directory) {
|
|
7
7
|
const manifestPath = path.join(directory, "inception.json");
|
|
8
8
|
let raw;
|
|
@@ -38,6 +38,11 @@ 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 "mcpServers" or "agentRules": wrong type → uniform message
|
|
42
|
+
if (issuePath.length === 1 &&
|
|
43
|
+
(issuePath[0] === "mcpServers" || issuePath[0] === "agentRules")) {
|
|
44
|
+
throw new UserError("MANIFEST_INVALID", `${filePath}: "${issuePath[0]}" must be an array`);
|
|
45
|
+
}
|
|
41
46
|
throw new UserError("MANIFEST_INVALID", `${filePath}: ${formatZodPath(issuePath)}${issue.message}`);
|
|
42
47
|
}
|
|
43
48
|
return result.data;
|
package/dist/core/deploy.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { AgentId, DeployAction, Manifest } from "../types.ts";
|
|
2
|
-
export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): Promise<
|
|
1
|
+
import type { AgentId, DeployAction, Manifest, PlannedChange, PlanWarning } from "../types.ts";
|
|
2
|
+
export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): Promise<{
|
|
3
|
+
actions: DeployAction[];
|
|
4
|
+
warnings: PlanWarning[];
|
|
5
|
+
}>;
|
|
3
6
|
export declare function executeDeploy(actions: DeployAction[], dryRun: boolean, verbose: boolean, home: string): Promise<{
|
|
4
7
|
succeeded: number;
|
|
5
8
|
failed: Array<{
|
|
6
9
|
action: DeployAction;
|
|
7
10
|
error: string;
|
|
8
11
|
}>;
|
|
12
|
+
planned: PlannedChange[];
|
|
9
13
|
}>;
|
package/dist/core/deploy.js
CHANGED
|
@@ -1,10 +1,55 @@
|
|
|
1
|
-
import { access, cp, lstat, mkdir, realpath, rename, rm, symlink, unlink, } from "node:fs/promises";
|
|
1
|
+
import { access, copyFile, cp, lstat, mkdir, readFile, realpath, rename, rm, symlink, unlink, writeFile, } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
4
4
|
import { UserError } from "../errors.js";
|
|
5
5
|
import { logger } from "../logger.js";
|
|
6
|
-
import { registerDeployment, verifyDeployment } from "./ownership.js";
|
|
6
|
+
import { lookupDeployment, registerDeployment, verifyDeployment, } from "./ownership.js";
|
|
7
7
|
import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
|
|
8
|
+
function isPlainObject(v) {
|
|
9
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10
|
+
}
|
|
11
|
+
async function readJsonConfig(filePath) {
|
|
12
|
+
let rawContent;
|
|
13
|
+
try {
|
|
14
|
+
rawContent = await readFile(filePath, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const code = err.code;
|
|
18
|
+
if (code === "ENOENT")
|
|
19
|
+
throw new Error(`Config file not found: ${filePath}`);
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(rawContent);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new Error(`Config file is not valid JSON: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
if (!isPlainObject(parsed)) {
|
|
30
|
+
throw new Error(`Config file is not a JSON object: ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
return parsed;
|
|
33
|
+
}
|
|
34
|
+
function computeUndoPatch(original, patch) {
|
|
35
|
+
const undoPatch = {};
|
|
36
|
+
for (const key of Object.keys(patch)) {
|
|
37
|
+
undoPatch[key] = key in original ? original[key] : null;
|
|
38
|
+
}
|
|
39
|
+
return undoPatch;
|
|
40
|
+
}
|
|
41
|
+
function applyMergePatch(original, patch) {
|
|
42
|
+
const patched = { ...original };
|
|
43
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
44
|
+
if (value === null) {
|
|
45
|
+
delete patched[key];
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
patched[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return patched;
|
|
52
|
+
}
|
|
8
53
|
function sourceAccessError(err, sourcePath) {
|
|
9
54
|
const code = err.code;
|
|
10
55
|
if (code === "ENOENT")
|
|
@@ -14,6 +59,34 @@ function sourceAccessError(err, sourcePath) {
|
|
|
14
59
|
const detail = err instanceof Error ? err.message : String(err);
|
|
15
60
|
return `Failed to access source ${sourcePath}: ${detail}`;
|
|
16
61
|
}
|
|
62
|
+
function detectCollisions(actions) {
|
|
63
|
+
const seen = new Map();
|
|
64
|
+
const warnings = [];
|
|
65
|
+
for (const action of actions) {
|
|
66
|
+
const prev = seen.get(action.target);
|
|
67
|
+
if (prev) {
|
|
68
|
+
warnings.push({
|
|
69
|
+
kind: "collision",
|
|
70
|
+
message: `Skill "${action.skill}" for agent "${action.agent}" and skill "${prev.skill}" for agent "${prev.agent}" both resolve to the same target: ${action.target}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
seen.set(action.target, { skill: action.skill, agent: action.agent });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return warnings;
|
|
78
|
+
}
|
|
79
|
+
function detectAmbiguities(detectedAgents) {
|
|
80
|
+
const warnings = [];
|
|
81
|
+
if (detectedAgents.includes("gemini-cli") &&
|
|
82
|
+
detectedAgents.includes("antigravity")) {
|
|
83
|
+
warnings.push({
|
|
84
|
+
kind: "ambiguity",
|
|
85
|
+
message: 'Both "gemini-cli" and "antigravity" share the ~/.gemini/ base directory. antigravity skill support is implementation-only. Verify that deployed skill paths do not conflict.',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return warnings;
|
|
89
|
+
}
|
|
17
90
|
export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
18
91
|
const method = getDeployMethod();
|
|
19
92
|
const actions = [];
|
|
@@ -28,6 +101,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
28
101
|
for (const skill of manifest.skills) {
|
|
29
102
|
const source = path.resolve(sourceDir, skill.path);
|
|
30
103
|
await validateSourcePath(source, skill.path, resolvedSourceDir, realRoot);
|
|
104
|
+
await validateSkillContract(source, skill.path);
|
|
31
105
|
for (const agentId of skill.agents) {
|
|
32
106
|
if (!detectedAgents.includes(agentId))
|
|
33
107
|
continue;
|
|
@@ -36,49 +110,213 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
|
|
|
36
110
|
continue;
|
|
37
111
|
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
38
112
|
actions.push({
|
|
113
|
+
kind: "skill-dir",
|
|
39
114
|
skill: skill.name,
|
|
40
115
|
agent: agentId,
|
|
41
116
|
source,
|
|
42
117
|
target,
|
|
43
118
|
method,
|
|
119
|
+
confidence: agent.provenance.skills,
|
|
44
120
|
});
|
|
45
121
|
}
|
|
46
122
|
}
|
|
47
|
-
|
|
123
|
+
const warnings = [
|
|
124
|
+
...detectAmbiguities(detectedAgents),
|
|
125
|
+
...detectCollisions(actions),
|
|
126
|
+
];
|
|
127
|
+
return { actions, warnings };
|
|
48
128
|
}
|
|
49
129
|
export async function executeDeploy(actions, dryRun, verbose, home) {
|
|
50
130
|
let succeeded = 0;
|
|
51
131
|
const failed = [];
|
|
132
|
+
const planned = [];
|
|
52
133
|
for (const action of actions) {
|
|
53
|
-
|
|
134
|
+
switch (action.kind) {
|
|
135
|
+
case "skill-dir": {
|
|
136
|
+
const result = await deploySkillDir(action, dryRun, verbose, home, planned);
|
|
137
|
+
if (result.error === null) {
|
|
138
|
+
succeeded++;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
failed.push({ action, error: result.error });
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "file-write": {
|
|
146
|
+
const result = await deployFileWrite(action, dryRun, verbose, home, planned);
|
|
147
|
+
if (result.error === null) {
|
|
148
|
+
succeeded++;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
failed.push({ action, error: result.error });
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "config-patch": {
|
|
156
|
+
const result = await deployConfigPatch(action, dryRun, verbose, home, planned);
|
|
157
|
+
if (result.error === null) {
|
|
158
|
+
succeeded++;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
failed.push({ action, error: result.error });
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default: {
|
|
166
|
+
throw new Error(`Unhandled deploy action kind: ${action}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { succeeded, failed, planned };
|
|
171
|
+
}
|
|
172
|
+
async function deploySkillDir(action, dryRun, verbose, home, planned) {
|
|
173
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
174
|
+
try {
|
|
175
|
+
await access(action.source);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const msg = sourceAccessError(err, action.source);
|
|
179
|
+
logger.fail(label, msg);
|
|
180
|
+
return { error: msg };
|
|
181
|
+
}
|
|
182
|
+
if (dryRun) {
|
|
183
|
+
logger.plan(label);
|
|
184
|
+
logger.detail(`${action.method}: ${action.source} -> ${action.target}`);
|
|
185
|
+
planned.push({
|
|
186
|
+
verb: action.method === "symlink" ? "create-symlink" : "copy-dir",
|
|
187
|
+
kind: "skill-dir",
|
|
188
|
+
skill: action.skill,
|
|
189
|
+
agent: action.agent,
|
|
190
|
+
source: action.source,
|
|
191
|
+
target: action.target,
|
|
192
|
+
method: action.method,
|
|
193
|
+
confidence: action.confidence,
|
|
194
|
+
});
|
|
195
|
+
return { error: null };
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
await executeDeployAction(action, verbose, home);
|
|
199
|
+
return { error: null };
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
203
|
+
logger.fail(label, msg);
|
|
204
|
+
return { error: msg };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function deployFileWrite(action, dryRun, verbose, home, planned) {
|
|
208
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
209
|
+
try {
|
|
210
|
+
await access(action.source);
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
const msg = sourceAccessError(err, action.source);
|
|
214
|
+
logger.fail(label, msg);
|
|
215
|
+
return { error: msg };
|
|
216
|
+
}
|
|
217
|
+
if (dryRun) {
|
|
218
|
+
logger.plan(label);
|
|
219
|
+
logger.detail(`write-file: ${action.source} -> ${action.target}`);
|
|
220
|
+
planned.push({
|
|
221
|
+
verb: "write-file",
|
|
222
|
+
kind: "file-write",
|
|
223
|
+
skill: action.skill,
|
|
224
|
+
agent: action.agent,
|
|
225
|
+
source: action.source,
|
|
226
|
+
target: action.target,
|
|
227
|
+
});
|
|
228
|
+
return { error: null };
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
// Check if target exists — only allow overwrite if we own it
|
|
54
232
|
try {
|
|
55
|
-
await
|
|
233
|
+
await lstat(action.target);
|
|
234
|
+
const isOwned = await verifyDeployment(home, action.target, {
|
|
235
|
+
kind: "file-write",
|
|
236
|
+
source: action.source,
|
|
237
|
+
skill: action.skill,
|
|
238
|
+
agent: action.agent,
|
|
239
|
+
});
|
|
240
|
+
if (!isOwned) {
|
|
241
|
+
throw new Error(`Target "${action.target}" exists but is not managed by inception-engine — refusing to overwrite`);
|
|
242
|
+
}
|
|
56
243
|
}
|
|
57
244
|
catch (err) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
continue;
|
|
245
|
+
if (err instanceof Error && err.message.includes("refusing to overwrite"))
|
|
246
|
+
throw err;
|
|
247
|
+
// ENOENT — target doesn't exist, fine to create
|
|
62
248
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
249
|
+
await mkdir(path.dirname(action.target), { recursive: true });
|
|
250
|
+
await copyFile(action.source, action.target);
|
|
251
|
+
await registerDeployment(home, action.target, {
|
|
252
|
+
kind: "file-write",
|
|
253
|
+
source: action.source,
|
|
254
|
+
skill: action.skill,
|
|
255
|
+
agent: action.agent,
|
|
256
|
+
});
|
|
257
|
+
logger.ok(label);
|
|
258
|
+
if (verbose) {
|
|
259
|
+
logger.detail(`write-file: ${action.source} -> ${action.target}`);
|
|
70
260
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
261
|
+
return { error: null };
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
265
|
+
logger.fail(label, msg);
|
|
266
|
+
return { error: msg };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function deployConfigPatch(action, dryRun, verbose, home, planned) {
|
|
270
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
271
|
+
if (!isPlainObject(action.patch)) {
|
|
272
|
+
const msg = `Config patch for skill "${action.skill}" must be a plain object`;
|
|
273
|
+
logger.fail(label, msg);
|
|
274
|
+
return { error: msg };
|
|
275
|
+
}
|
|
276
|
+
const patch = action.patch;
|
|
277
|
+
if (dryRun) {
|
|
278
|
+
logger.plan(label);
|
|
279
|
+
logger.detail(`patch-config: ${JSON.stringify(patch)} -> ${action.target}`);
|
|
280
|
+
planned.push({
|
|
281
|
+
verb: "patch-config",
|
|
282
|
+
kind: "config-patch",
|
|
283
|
+
skill: action.skill,
|
|
284
|
+
agent: action.agent,
|
|
285
|
+
target: action.target,
|
|
286
|
+
patch,
|
|
287
|
+
});
|
|
288
|
+
return { error: null };
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
// Guard against double-patching by a different skill/agent
|
|
292
|
+
const existingEntry = await lookupDeployment(home, action.target);
|
|
293
|
+
if (existingEntry &&
|
|
294
|
+
(existingEntry.skill !== action.skill ||
|
|
295
|
+
existingEntry.agent !== action.agent)) {
|
|
296
|
+
throw new Error(`Config "${action.target}" is already patched by skill "${existingEntry.skill}" for agent "${existingEntry.agent}" — refusing to double-patch`);
|
|
74
297
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
298
|
+
const original = await readJsonConfig(action.target);
|
|
299
|
+
const undoPatch = computeUndoPatch(original, patch);
|
|
300
|
+
const patched = applyMergePatch(original, patch);
|
|
301
|
+
await writeFile(action.target, `${JSON.stringify(patched, null, 2)}\n`, "utf-8");
|
|
302
|
+
await registerDeployment(home, action.target, {
|
|
303
|
+
kind: "config-patch",
|
|
304
|
+
patch,
|
|
305
|
+
undoPatch,
|
|
306
|
+
skill: action.skill,
|
|
307
|
+
agent: action.agent,
|
|
308
|
+
});
|
|
309
|
+
logger.ok(label);
|
|
310
|
+
if (verbose) {
|
|
311
|
+
logger.detail(`patch-config: applied ${Object.keys(patch).length} key(s) to ${action.target}`);
|
|
79
312
|
}
|
|
313
|
+
return { error: null };
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
317
|
+
logger.fail(label, msg);
|
|
318
|
+
return { error: msg };
|
|
80
319
|
}
|
|
81
|
-
return { succeeded, failed };
|
|
82
320
|
}
|
|
83
321
|
async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot) {
|
|
84
322
|
if (!source.startsWith(resolvedSourceDir + path.sep)) {
|
|
@@ -97,6 +335,31 @@ async function validateSourcePath(source, skillPath, resolvedSourceDir, realRoot
|
|
|
97
335
|
// Source doesn't exist yet — will be caught during execute
|
|
98
336
|
}
|
|
99
337
|
}
|
|
338
|
+
async function validateSkillContract(source, skillPath) {
|
|
339
|
+
let stat;
|
|
340
|
+
try {
|
|
341
|
+
stat = await lstat(source);
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
const code = err.code;
|
|
345
|
+
if (code === "ENOENT") {
|
|
346
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source not found: ${source}`);
|
|
347
|
+
}
|
|
348
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
349
|
+
throw new UserError("DEPLOY_FAILED", `Permission denied accessing skill "${skillPath}" source: ${source}`);
|
|
350
|
+
}
|
|
351
|
+
throw new UserError("DEPLOY_FAILED", `Cannot access skill "${skillPath}" source: ${source}`);
|
|
352
|
+
}
|
|
353
|
+
if (!stat.isDirectory()) {
|
|
354
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source is not a directory: ${source}`);
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
await access(path.join(source, "SKILL.md"));
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
throw new UserError("DEPLOY_FAILED", `Skill "${skillPath}" source is missing SKILL.md: ${source}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
100
363
|
async function assertTargetAbsent(targetPath) {
|
|
101
364
|
try {
|
|
102
365
|
await lstat(targetPath);
|
|
@@ -116,6 +379,7 @@ async function createDeployTarget(action, home) {
|
|
|
116
379
|
await cp(action.source, action.target, { recursive: true });
|
|
117
380
|
}
|
|
118
381
|
await registerDeployment(home, action.target, {
|
|
382
|
+
kind: action.kind,
|
|
119
383
|
source: action.source,
|
|
120
384
|
skill: action.skill,
|
|
121
385
|
agent: action.agent,
|
|
@@ -125,6 +389,7 @@ async function createDeployTarget(action, home) {
|
|
|
125
389
|
async function executeDeployAction(action, verbose, home) {
|
|
126
390
|
const label = `${action.skill} -> ${action.agent}`;
|
|
127
391
|
const backupPath = await backupExisting(action.target, verbose, home, {
|
|
392
|
+
kind: action.kind,
|
|
128
393
|
source: action.source,
|
|
129
394
|
skill: action.skill,
|
|
130
395
|
agent: action.agent,
|
|
@@ -137,6 +402,9 @@ async function executeDeployAction(action, verbose, home) {
|
|
|
137
402
|
catch (createErr) {
|
|
138
403
|
if (backupPath) {
|
|
139
404
|
try {
|
|
405
|
+
await removeTarget(action.target).catch(() => {
|
|
406
|
+
/* best-effort cleanup */
|
|
407
|
+
});
|
|
140
408
|
await rename(backupPath, action.target);
|
|
141
409
|
}
|
|
142
410
|
catch {
|
|
@@ -160,7 +428,12 @@ async function backupExisting(targetPath, verbose, home, expected) {
|
|
|
160
428
|
catch {
|
|
161
429
|
return null;
|
|
162
430
|
}
|
|
163
|
-
if (!(await verifyDeployment(home, targetPath,
|
|
431
|
+
if (!(await verifyDeployment(home, targetPath, {
|
|
432
|
+
kind: "skill-dir",
|
|
433
|
+
source: expected.source,
|
|
434
|
+
skill: expected.skill,
|
|
435
|
+
agent: expected.agent,
|
|
436
|
+
}))) {
|
|
164
437
|
throw new Error(`Target "${targetPath}" exists but is not managed by inception-engine — refusing to overwrite`);
|
|
165
438
|
}
|
|
166
439
|
const backupPath = `${targetPath}.inception-backup`;
|
package/dist/core/detect.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import type { AgentId } from "../types.ts";
|
|
2
|
+
export type ExecFn = (cmd: string, args: readonly string[]) => Promise<void>;
|
|
2
3
|
export declare function detectInstalledAgents(home: string): Promise<AgentId[]>;
|
|
4
|
+
export declare function isBinaryInPath(binary: string, execFn?: ExecFn, platform?: NodeJS.Platform): Promise<boolean>;
|
|
5
|
+
export declare function isBinaryViaWhereExe(binary: string, execFn?: ExecFn): Promise<boolean>;
|
|
6
|
+
export declare function isBinaryViaCommandV(binary: string, execFn?: ExecFn): Promise<boolean>;
|
|
7
|
+
export declare function isBinaryViaWhich(binary: string, execFn?: ExecFn): Promise<boolean>;
|
package/dist/core/detect.js
CHANGED
|
@@ -4,6 +4,9 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { AGENT_REGISTRY } from "../config/agents.js";
|
|
5
5
|
import { resolveAgentDetectPath } from "./resolve.js";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
|
+
const defaultExecFn = async (cmd, args) => {
|
|
8
|
+
await execFileAsync(cmd, args);
|
|
9
|
+
};
|
|
7
10
|
export async function detectInstalledAgents(home) {
|
|
8
11
|
const detected = [];
|
|
9
12
|
for (const agent of AGENT_REGISTRY) {
|
|
@@ -27,34 +30,48 @@ async function isAgentInstalled(agent, home) {
|
|
|
27
30
|
}
|
|
28
31
|
return false;
|
|
29
32
|
}
|
|
30
|
-
async function isBinaryInPath(binary) {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
await execFileAsync("where.exe", [binary]);
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
33
|
+
export async function isBinaryInPath(binary, execFn = defaultExecFn, platform = process.platform) {
|
|
34
|
+
if (platform === "win32") {
|
|
35
|
+
return isBinaryViaWhereExe(binary, execFn);
|
|
39
36
|
}
|
|
40
37
|
try {
|
|
41
|
-
await
|
|
38
|
+
await execFn("which", [binary]);
|
|
42
39
|
return true;
|
|
43
40
|
}
|
|
44
41
|
catch (err) {
|
|
45
42
|
// `which` itself is not installed — fall back to the POSIX shell built-in
|
|
46
43
|
if (isENOENT(err)) {
|
|
47
|
-
return isBinaryViaCommandV(binary);
|
|
44
|
+
return isBinaryViaCommandV(binary, execFn);
|
|
48
45
|
}
|
|
49
46
|
return false;
|
|
50
47
|
}
|
|
51
48
|
}
|
|
49
|
+
// Windows-only: use where.exe to check if a binary is in PATH.
|
|
50
|
+
export async function isBinaryViaWhereExe(binary, execFn = defaultExecFn) {
|
|
51
|
+
try {
|
|
52
|
+
await execFn("where.exe", [binary]);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
52
59
|
// Used only when `which` is absent (e.g. minimal Alpine containers).
|
|
53
60
|
// `command -v` is a POSIX shell built-in available wherever /bin/sh is.
|
|
54
|
-
async function isBinaryViaCommandV(binary) {
|
|
61
|
+
export async function isBinaryViaCommandV(binary, execFn = defaultExecFn) {
|
|
55
62
|
try {
|
|
56
63
|
// Pass binary as a positional arg ($1) to avoid any shell-injection risk.
|
|
57
|
-
await
|
|
64
|
+
await execFn("sh", ["-c", 'command -v "$1"', "--", binary]);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// POSIX: use `which` to check if a binary is in PATH.
|
|
72
|
+
export async function isBinaryViaWhich(binary, execFn = defaultExecFn) {
|
|
73
|
+
try {
|
|
74
|
+
await execFn("which", [binary]);
|
|
58
75
|
return true;
|
|
59
76
|
}
|
|
60
77
|
catch {
|
package/dist/core/ownership.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
import { type RegistryEntry } from "../schemas/registry.ts";
|
|
1
|
+
import { type ConfigPatchRegistryEntry, type FileWriteRegistryEntry, type RegistryEntry, type SkillDirRegistryEntry } from "../schemas/registry.ts";
|
|
2
2
|
import type { AgentId } from "../types.ts";
|
|
3
3
|
export type { RegistryEntry } from "../schemas/registry.ts";
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
export type VerifyExpected = {
|
|
5
|
+
kind: "skill-dir";
|
|
6
|
+
source: string;
|
|
7
|
+
skill: string;
|
|
8
|
+
agent: AgentId;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "file-write";
|
|
9
11
|
source: string;
|
|
10
12
|
skill: string;
|
|
11
13
|
agent: AgentId;
|
|
12
|
-
}
|
|
14
|
+
} | {
|
|
15
|
+
kind: "config-patch";
|
|
16
|
+
skill: string;
|
|
17
|
+
agent: AgentId;
|
|
18
|
+
};
|
|
19
|
+
export declare function registryPath(home: string): string;
|
|
20
|
+
export type RegisterEntry = Omit<SkillDirRegistryEntry, "deployed"> | Omit<FileWriteRegistryEntry, "deployed"> | Omit<ConfigPatchRegistryEntry, "deployed">;
|
|
21
|
+
export declare function registerDeployment(home: string, targetPath: string, entry: RegisterEntry): Promise<void>;
|
|
22
|
+
export declare function unregisterDeployment(home: string, targetPath: string): Promise<void>;
|
|
23
|
+
export declare function lookupDeployment(home: string, targetPath: string): Promise<RegistryEntry | null>;
|
|
24
|
+
export declare function verifyDeployment(home: string, targetPath: string, expected: VerifyExpected): Promise<RegistryEntry | null>;
|
package/dist/core/ownership.js
CHANGED
|
@@ -59,9 +59,15 @@ export async function verifyDeployment(home, targetPath, expected) {
|
|
|
59
59
|
const entry = await lookupDeployment(home, targetPath);
|
|
60
60
|
if (!entry)
|
|
61
61
|
return null;
|
|
62
|
-
if (entry.
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
if (entry.kind !== expected.kind)
|
|
63
|
+
return null;
|
|
64
|
+
if (entry.skill !== expected.skill)
|
|
65
|
+
return null;
|
|
66
|
+
if (entry.agent !== expected.agent)
|
|
67
|
+
return null;
|
|
68
|
+
if ((expected.kind === "skill-dir" || expected.kind === "file-write") &&
|
|
69
|
+
(entry.kind === "skill-dir" || entry.kind === "file-write") &&
|
|
70
|
+
entry.source !== expected.source) {
|
|
65
71
|
return null;
|
|
66
72
|
}
|
|
67
73
|
return entry;
|
package/dist/core/preflight.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { CliOptions, Manifest } from "../types.ts";
|
|
1
|
+
import type { AgentId, CliOptions, Manifest } from "../types.ts";
|
|
2
2
|
export interface PreflightWarning {
|
|
3
3
|
kind: "policy" | "config-authority" | "info";
|
|
4
4
|
message: string;
|
|
5
5
|
}
|
|
6
|
-
export declare function runPreflight(_options: CliOptions, _manifest: Manifest, _home: string): Promise<PreflightWarning[]>;
|
|
6
|
+
export declare function runPreflight(_options: CliOptions, _manifest: Manifest, _home: string, detectedAgents: AgentId[]): Promise<PreflightWarning[]>;
|
package/dist/core/preflight.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
|
|
2
|
+
export async function runPreflight(_options, _manifest, _home, detectedAgents) {
|
|
3
|
+
const warnings = [];
|
|
4
|
+
for (const agentId of detectedAgents) {
|
|
5
|
+
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
6
|
+
if (!agent)
|
|
7
|
+
continue;
|
|
8
|
+
if (agent.provenance.skills === "implementation-only") {
|
|
9
|
+
warnings.push({
|
|
10
|
+
kind: "config-authority",
|
|
11
|
+
message: `Agent "${agentId}" skill support is implementation-only: paths are derived from source inspection, not published documentation.`,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
else if (agent.provenance.skills === "provisional") {
|
|
15
|
+
warnings.push({
|
|
16
|
+
kind: "config-authority",
|
|
17
|
+
message: `Agent "${agentId}" skill support is provisional: behavior has not been independently verified.`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return warnings;
|
|
6
22
|
}
|
package/dist/core/revert.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentId, Manifest, RevertAction } from "../types.ts";
|
|
1
|
+
import type { AgentId, Manifest, PlannedChange, RevertAction } from "../types.ts";
|
|
2
2
|
export declare function planRevert(manifest: Manifest, detectedAgents: AgentId[], home: string): RevertAction[];
|
|
3
3
|
export declare function planRevertAll(manifest: Manifest, home: string): RevertAction[];
|
|
4
4
|
export declare function executeRevert(actions: RevertAction[], dryRun: boolean, verbose: boolean, home: string): Promise<{
|
|
@@ -8,4 +8,5 @@ export declare function executeRevert(actions: RevertAction[], dryRun: boolean,
|
|
|
8
8
|
action: RevertAction;
|
|
9
9
|
error: string;
|
|
10
10
|
}>;
|
|
11
|
+
planned: PlannedChange[];
|
|
11
12
|
}>;
|
package/dist/core/revert.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { lstat, rm, unlink } from "node:fs/promises";
|
|
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
4
|
import { lookupDeployment, unregisterDeployment } from "./ownership.js";
|
|
@@ -13,7 +13,12 @@ export function planRevert(manifest, detectedAgents, home) {
|
|
|
13
13
|
if (!agent)
|
|
14
14
|
continue;
|
|
15
15
|
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
16
|
-
actions.push({
|
|
16
|
+
actions.push({
|
|
17
|
+
kind: "skill-dir",
|
|
18
|
+
skill: skill.name,
|
|
19
|
+
agent: agentId,
|
|
20
|
+
target,
|
|
21
|
+
});
|
|
17
22
|
}
|
|
18
23
|
}
|
|
19
24
|
return actions;
|
|
@@ -26,11 +31,53 @@ export function planRevertAll(manifest, home) {
|
|
|
26
31
|
if (!agent)
|
|
27
32
|
continue;
|
|
28
33
|
const target = resolveAgentSkillPath(agent, skill.name, home);
|
|
29
|
-
actions.push({
|
|
34
|
+
actions.push({
|
|
35
|
+
kind: "skill-dir",
|
|
36
|
+
skill: skill.name,
|
|
37
|
+
agent: agentId,
|
|
38
|
+
target,
|
|
39
|
+
});
|
|
30
40
|
}
|
|
31
41
|
}
|
|
32
42
|
return actions;
|
|
33
43
|
}
|
|
44
|
+
function recordOutcome(result, action, counts, failed) {
|
|
45
|
+
if (result.outcome === "fail") {
|
|
46
|
+
failed.push({ action, error: result.error });
|
|
47
|
+
}
|
|
48
|
+
else if (result.outcome === "skip") {
|
|
49
|
+
counts.skipped++;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
counts.succeeded++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function readJsonConfig(filePath) {
|
|
56
|
+
const rawContent = await readFile(filePath, "utf-8");
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(rawContent);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error(`Config file is not valid JSON: ${filePath}`);
|
|
63
|
+
}
|
|
64
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
65
|
+
throw new Error(`Config file is not a JSON object: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
function applyUndoPatch(current, undoPatch) {
|
|
70
|
+
const restored = { ...current };
|
|
71
|
+
for (const [key, originalValue] of Object.entries(undoPatch)) {
|
|
72
|
+
if (originalValue === null) {
|
|
73
|
+
delete restored[key];
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
restored[key] = originalValue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return restored;
|
|
80
|
+
}
|
|
34
81
|
function lstatOutcome(err) {
|
|
35
82
|
if (err.code === "ENOENT") {
|
|
36
83
|
return { outcome: "skip" };
|
|
@@ -42,21 +89,30 @@ export async function executeRevert(actions, dryRun, verbose, home) {
|
|
|
42
89
|
let succeeded = 0;
|
|
43
90
|
let skipped = 0;
|
|
44
91
|
const failed = [];
|
|
92
|
+
const planned = [];
|
|
93
|
+
const counts = { succeeded, skipped };
|
|
45
94
|
for (const action of actions) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
95
|
+
let result;
|
|
96
|
+
switch (action.kind) {
|
|
97
|
+
case "skill-dir":
|
|
98
|
+
result = await executeRevertAction(action, dryRun, verbose, home, planned);
|
|
99
|
+
break;
|
|
100
|
+
case "file-write":
|
|
101
|
+
result = await revertFileWrite(action, dryRun, verbose, home, planned);
|
|
102
|
+
break;
|
|
103
|
+
case "config-patch":
|
|
104
|
+
result = await revertConfigPatch(action, dryRun, verbose, home, planned);
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
throw new Error(`Unhandled revert action kind: ${action}`);
|
|
55
108
|
}
|
|
109
|
+
recordOutcome(result, action, counts, failed);
|
|
56
110
|
}
|
|
57
|
-
|
|
111
|
+
succeeded = counts.succeeded;
|
|
112
|
+
skipped = counts.skipped;
|
|
113
|
+
return { succeeded, skipped, failed, planned };
|
|
58
114
|
}
|
|
59
|
-
async function executeRevertAction(action, dryRun, verbose, home) {
|
|
115
|
+
async function executeRevertAction(action, dryRun, verbose, home, planned) {
|
|
60
116
|
const label = `${action.skill} -> ${action.agent}`;
|
|
61
117
|
let stat;
|
|
62
118
|
try {
|
|
@@ -78,9 +134,14 @@ async function executeRevertAction(action, dryRun, verbose, home) {
|
|
|
78
134
|
}
|
|
79
135
|
if (dryRun) {
|
|
80
136
|
logger.plan(label);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
137
|
+
logger.detail(`would remove: ${action.target}`);
|
|
138
|
+
planned.push({
|
|
139
|
+
verb: "remove",
|
|
140
|
+
kind: "skill-dir",
|
|
141
|
+
skill: action.skill,
|
|
142
|
+
agent: action.agent,
|
|
143
|
+
target: action.target,
|
|
144
|
+
});
|
|
84
145
|
return { outcome: "ok" };
|
|
85
146
|
}
|
|
86
147
|
try {
|
|
@@ -103,3 +164,102 @@ async function executeRevertAction(action, dryRun, verbose, home) {
|
|
|
103
164
|
return { outcome: "fail", error: msg };
|
|
104
165
|
}
|
|
105
166
|
}
|
|
167
|
+
async function revertFileWrite(action, dryRun, verbose, home, planned) {
|
|
168
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
169
|
+
try {
|
|
170
|
+
await lstat(action.target);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
const result = lstatOutcome(err);
|
|
174
|
+
if (result.outcome === "skip") {
|
|
175
|
+
logger.skip(label, "(not found, skipping)");
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
logger.fail(label, result.error);
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
const entry = await lookupDeployment(home, action.target);
|
|
182
|
+
if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
|
|
183
|
+
logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
|
|
184
|
+
return { outcome: "skip" };
|
|
185
|
+
}
|
|
186
|
+
if (dryRun) {
|
|
187
|
+
logger.plan(label);
|
|
188
|
+
logger.detail(`would remove: ${action.target}`);
|
|
189
|
+
planned.push({
|
|
190
|
+
verb: "remove",
|
|
191
|
+
kind: "file-write",
|
|
192
|
+
skill: action.skill,
|
|
193
|
+
agent: action.agent,
|
|
194
|
+
target: action.target,
|
|
195
|
+
});
|
|
196
|
+
return { outcome: "ok" };
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
await unlink(action.target);
|
|
200
|
+
await unregisterDeployment(home, action.target);
|
|
201
|
+
logger.ok(label);
|
|
202
|
+
if (verbose) {
|
|
203
|
+
logger.detail(`removed: ${action.target}`);
|
|
204
|
+
}
|
|
205
|
+
return { outcome: "ok" };
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
209
|
+
logger.fail(label, msg);
|
|
210
|
+
return { outcome: "fail", error: msg };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function revertConfigPatch(action, dryRun, verbose, home, planned) {
|
|
214
|
+
const label = `${action.skill} -> ${action.agent}`;
|
|
215
|
+
try {
|
|
216
|
+
await lstat(action.target);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
const result = lstatOutcome(err);
|
|
220
|
+
if (result.outcome === "skip") {
|
|
221
|
+
logger.skip(label, "(not found, skipping)");
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
logger.fail(label, result.error);
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
const entry = await lookupDeployment(home, action.target);
|
|
228
|
+
if (!entry ||
|
|
229
|
+
entry.kind !== "config-patch" ||
|
|
230
|
+
entry.skill !== action.skill ||
|
|
231
|
+
entry.agent !== action.agent) {
|
|
232
|
+
logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
|
|
233
|
+
return { outcome: "skip" };
|
|
234
|
+
}
|
|
235
|
+
const configPatchEntry = entry;
|
|
236
|
+
if (dryRun) {
|
|
237
|
+
logger.plan(label);
|
|
238
|
+
logger.detail(`would unapply patch: ${JSON.stringify(configPatchEntry.undoPatch)} -> ${action.target}`);
|
|
239
|
+
planned.push({
|
|
240
|
+
verb: "unapply-patch",
|
|
241
|
+
kind: "config-patch",
|
|
242
|
+
skill: action.skill,
|
|
243
|
+
agent: action.agent,
|
|
244
|
+
target: action.target,
|
|
245
|
+
patch: configPatchEntry.undoPatch,
|
|
246
|
+
});
|
|
247
|
+
return { outcome: "ok" };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const current = await readJsonConfig(action.target);
|
|
251
|
+
const restored = applyUndoPatch(current, configPatchEntry.undoPatch);
|
|
252
|
+
await writeFile(action.target, `${JSON.stringify(restored, null, 2)}\n`, "utf-8");
|
|
253
|
+
await unregisterDeployment(home, action.target);
|
|
254
|
+
logger.ok(label);
|
|
255
|
+
if (verbose) {
|
|
256
|
+
logger.detail(`unapplied patch from: ${action.target}`);
|
|
257
|
+
}
|
|
258
|
+
return { outcome: "ok" };
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
262
|
+
logger.fail(label, msg);
|
|
263
|
+
return { outcome: "fail", error: msg };
|
|
264
|
+
}
|
|
265
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import { AGENT_REGISTRY } from "./config/agents.js";
|
|
|
5
5
|
import { loadManifest } from "./config/manifest.js";
|
|
6
6
|
import { executeDeploy, planDeploy } from "./core/deploy.js";
|
|
7
7
|
import { detectInstalledAgents } from "./core/detect.js";
|
|
8
|
-
import { resolveHome } from "./core/resolve.js";
|
|
9
8
|
import { runPreflight } from "./core/preflight.js";
|
|
9
|
+
import { resolveHome } from "./core/resolve.js";
|
|
10
10
|
import { executeRevert, planRevert, planRevertAll } from "./core/revert.js";
|
|
11
11
|
import { UserError } from "./errors.js";
|
|
12
12
|
import { dryRunPrefix, logger } from "./logger.js";
|
|
@@ -106,10 +106,6 @@ async function main() {
|
|
|
106
106
|
}
|
|
107
107
|
const manifest = await loadManifest(options.directory);
|
|
108
108
|
const home = resolveHome();
|
|
109
|
-
const preflightWarnings = await runPreflight(options, manifest, home);
|
|
110
|
-
for (const w of preflightWarnings) {
|
|
111
|
-
logger.warn("preflight", w.message);
|
|
112
|
-
}
|
|
113
109
|
if (options.command === "deploy") {
|
|
114
110
|
return runDeploy(options, manifest, home);
|
|
115
111
|
}
|
|
@@ -134,7 +130,14 @@ async function runDeploy(options, manifest, home) {
|
|
|
134
130
|
logger.info(`Detected agents: ${detectedAgents.join(", ")}`);
|
|
135
131
|
}
|
|
136
132
|
}
|
|
137
|
-
const
|
|
133
|
+
const preflightWarnings = await runPreflight(options, manifest, home, detectedAgents);
|
|
134
|
+
for (const w of preflightWarnings) {
|
|
135
|
+
logger.warn("preflight", w.message);
|
|
136
|
+
}
|
|
137
|
+
const { actions, warnings: planWarnings } = await planDeploy(manifest, options.directory, detectedAgents, home);
|
|
138
|
+
for (const w of planWarnings) {
|
|
139
|
+
logger.warn("plan", w.message);
|
|
140
|
+
}
|
|
138
141
|
if (actions.length === 0) {
|
|
139
142
|
logger.info("No skills to deploy for detected agents.");
|
|
140
143
|
return 0;
|
|
@@ -13,30 +13,30 @@ export type AgentId = z.output<typeof AgentIdSchema>;
|
|
|
13
13
|
export declare const SkillEntrySchema: z.ZodObject<{
|
|
14
14
|
name: z.ZodString;
|
|
15
15
|
path: z.ZodString;
|
|
16
|
-
agents: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
16
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
17
17
|
"claude-code": "claude-code";
|
|
18
18
|
codex: "codex";
|
|
19
19
|
"gemini-cli": "gemini-cli";
|
|
20
20
|
antigravity: "antigravity";
|
|
21
21
|
opencode: "opencode";
|
|
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
25
|
export declare const ManifestSchema: z.ZodObject<{
|
|
26
26
|
skills: z.ZodArray<z.ZodObject<{
|
|
27
27
|
name: z.ZodString;
|
|
28
28
|
path: z.ZodString;
|
|
29
|
-
agents: z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
29
|
+
agents: z.ZodPipe<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEnum<{
|
|
30
30
|
"claude-code": "claude-code";
|
|
31
31
|
codex: "codex";
|
|
32
32
|
"gemini-cli": "gemini-cli";
|
|
33
33
|
antigravity: "antigravity";
|
|
34
34
|
opencode: "opencode";
|
|
35
35
|
"github-copilot": "github-copilot";
|
|
36
|
-
}
|
|
36
|
+
}>>>, z.ZodTransform<("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[], ("claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot")[]>>;
|
|
37
37
|
}, z.core.$strip>>;
|
|
38
|
-
mcpServers: z.
|
|
39
|
-
agentRules: z.
|
|
38
|
+
mcpServers: z.ZodDefault<z.ZodArray<z.ZodUnknown>>;
|
|
39
|
+
agentRules: z.ZodDefault<z.ZodArray<z.ZodUnknown>>;
|
|
40
40
|
}, z.core.$strip>;
|
|
41
41
|
export type SkillEntry = z.infer<typeof SkillEntrySchema>;
|
|
42
42
|
export type Manifest = z.infer<typeof ManifestSchema>;
|
package/dist/schemas/manifest.js
CHANGED
|
@@ -44,12 +44,25 @@ export const SkillEntrySchema = z.object({
|
|
|
44
44
|
}),
|
|
45
45
|
agents: z
|
|
46
46
|
.array(agentIdElement, { message: "agents must be a non-empty array" })
|
|
47
|
-
.min(1, { 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)]),
|
|
48
49
|
});
|
|
49
50
|
export const ManifestSchema = z.object({
|
|
50
|
-
skills: z.array(SkillEntrySchema),
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
skills: z.array(SkillEntrySchema).superRefine((skills, ctx) => {
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
for (const [i, skill] of skills.entries()) {
|
|
54
|
+
if (seen.has(skill.name)) {
|
|
55
|
+
ctx.addIssue({
|
|
56
|
+
code: "custom",
|
|
57
|
+
path: [i, "name"],
|
|
58
|
+
message: `duplicate skill name "${skill.name}"`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
seen.add(skill.name);
|
|
62
|
+
}
|
|
63
|
+
}),
|
|
64
|
+
mcpServers: z.array(z.unknown()).default([]),
|
|
65
|
+
agentRules: z.array(z.unknown()).default([]),
|
|
53
66
|
});
|
|
54
67
|
// Parses the --agents CLI flag: comma-separated agent IDs → AgentId[]
|
|
55
68
|
export const AgentListSchema = z
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
|
|
2
|
+
declare const SkillDirRegistryEntrySchema: z.ZodObject<{
|
|
3
|
+
kind: z.ZodLiteral<"skill-dir">;
|
|
3
4
|
source: z.ZodString;
|
|
4
5
|
skill: z.ZodString;
|
|
5
6
|
agent: z.ZodEnum<{
|
|
@@ -10,15 +11,90 @@ export declare const RegistryEntrySchema: z.ZodObject<{
|
|
|
10
11
|
opencode: "opencode";
|
|
11
12
|
"github-copilot": "github-copilot";
|
|
12
13
|
}>;
|
|
13
|
-
method: z.ZodEnum<{
|
|
14
|
+
method: z.ZodOptional<z.ZodEnum<{
|
|
14
15
|
symlink: "symlink";
|
|
15
16
|
copy: "copy";
|
|
17
|
+
}>>;
|
|
18
|
+
deployed: z.ZodString;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
declare const FileWriteRegistryEntrySchema: z.ZodObject<{
|
|
21
|
+
kind: z.ZodLiteral<"file-write">;
|
|
22
|
+
source: z.ZodString;
|
|
23
|
+
skill: z.ZodString;
|
|
24
|
+
agent: z.ZodEnum<{
|
|
25
|
+
"claude-code": "claude-code";
|
|
26
|
+
codex: "codex";
|
|
27
|
+
"gemini-cli": "gemini-cli";
|
|
28
|
+
antigravity: "antigravity";
|
|
29
|
+
opencode: "opencode";
|
|
30
|
+
"github-copilot": "github-copilot";
|
|
31
|
+
}>;
|
|
32
|
+
deployed: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
declare const ConfigPatchRegistryEntrySchema: z.ZodObject<{
|
|
35
|
+
kind: z.ZodLiteral<"config-patch">;
|
|
36
|
+
patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
37
|
+
undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
38
|
+
skill: z.ZodString;
|
|
39
|
+
agent: z.ZodEnum<{
|
|
40
|
+
"claude-code": "claude-code";
|
|
41
|
+
codex: "codex";
|
|
42
|
+
"gemini-cli": "gemini-cli";
|
|
43
|
+
antigravity: "antigravity";
|
|
44
|
+
opencode: "opencode";
|
|
45
|
+
"github-copilot": "github-copilot";
|
|
16
46
|
}>;
|
|
17
47
|
deployed: z.ZodString;
|
|
18
48
|
}, z.core.$strip>;
|
|
49
|
+
export declare const RegistryEntrySchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
50
|
+
kind: z.ZodLiteral<"skill-dir">;
|
|
51
|
+
source: z.ZodString;
|
|
52
|
+
skill: z.ZodString;
|
|
53
|
+
agent: 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
|
+
}>;
|
|
61
|
+
method: z.ZodOptional<z.ZodEnum<{
|
|
62
|
+
symlink: "symlink";
|
|
63
|
+
copy: "copy";
|
|
64
|
+
}>>;
|
|
65
|
+
deployed: z.ZodString;
|
|
66
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
67
|
+
kind: z.ZodLiteral<"file-write">;
|
|
68
|
+
source: z.ZodString;
|
|
69
|
+
skill: z.ZodString;
|
|
70
|
+
agent: z.ZodEnum<{
|
|
71
|
+
"claude-code": "claude-code";
|
|
72
|
+
codex: "codex";
|
|
73
|
+
"gemini-cli": "gemini-cli";
|
|
74
|
+
antigravity: "antigravity";
|
|
75
|
+
opencode: "opencode";
|
|
76
|
+
"github-copilot": "github-copilot";
|
|
77
|
+
}>;
|
|
78
|
+
deployed: z.ZodString;
|
|
79
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
80
|
+
kind: z.ZodLiteral<"config-patch">;
|
|
81
|
+
patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
82
|
+
undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
83
|
+
skill: z.ZodString;
|
|
84
|
+
agent: z.ZodEnum<{
|
|
85
|
+
"claude-code": "claude-code";
|
|
86
|
+
codex: "codex";
|
|
87
|
+
"gemini-cli": "gemini-cli";
|
|
88
|
+
antigravity: "antigravity";
|
|
89
|
+
opencode: "opencode";
|
|
90
|
+
"github-copilot": "github-copilot";
|
|
91
|
+
}>;
|
|
92
|
+
deployed: z.ZodString;
|
|
93
|
+
}, z.core.$strip>], "kind">;
|
|
19
94
|
export declare const RegistrySchema: z.ZodObject<{
|
|
20
95
|
version: z.ZodLiteral<1>;
|
|
21
|
-
deployments: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
96
|
+
deployments: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
97
|
+
kind: z.ZodLiteral<"skill-dir">;
|
|
22
98
|
source: z.ZodString;
|
|
23
99
|
skill: z.ZodString;
|
|
24
100
|
agent: z.ZodEnum<{
|
|
@@ -29,12 +105,43 @@ export declare const RegistrySchema: z.ZodObject<{
|
|
|
29
105
|
opencode: "opencode";
|
|
30
106
|
"github-copilot": "github-copilot";
|
|
31
107
|
}>;
|
|
32
|
-
method: z.ZodEnum<{
|
|
108
|
+
method: z.ZodOptional<z.ZodEnum<{
|
|
33
109
|
symlink: "symlink";
|
|
34
110
|
copy: "copy";
|
|
111
|
+
}>>;
|
|
112
|
+
deployed: z.ZodString;
|
|
113
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
114
|
+
kind: z.ZodLiteral<"file-write">;
|
|
115
|
+
source: z.ZodString;
|
|
116
|
+
skill: z.ZodString;
|
|
117
|
+
agent: z.ZodEnum<{
|
|
118
|
+
"claude-code": "claude-code";
|
|
119
|
+
codex: "codex";
|
|
120
|
+
"gemini-cli": "gemini-cli";
|
|
121
|
+
antigravity: "antigravity";
|
|
122
|
+
opencode: "opencode";
|
|
123
|
+
"github-copilot": "github-copilot";
|
|
124
|
+
}>;
|
|
125
|
+
deployed: z.ZodString;
|
|
126
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
127
|
+
kind: z.ZodLiteral<"config-patch">;
|
|
128
|
+
patch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
129
|
+
undoPatch: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
130
|
+
skill: z.ZodString;
|
|
131
|
+
agent: z.ZodEnum<{
|
|
132
|
+
"claude-code": "claude-code";
|
|
133
|
+
codex: "codex";
|
|
134
|
+
"gemini-cli": "gemini-cli";
|
|
135
|
+
antigravity: "antigravity";
|
|
136
|
+
opencode: "opencode";
|
|
137
|
+
"github-copilot": "github-copilot";
|
|
35
138
|
}>;
|
|
36
139
|
deployed: z.ZodString;
|
|
37
|
-
}, z.core.$strip>>;
|
|
140
|
+
}, z.core.$strip>], "kind">>;
|
|
38
141
|
}, z.core.$strip>;
|
|
142
|
+
export type SkillDirRegistryEntry = z.infer<typeof SkillDirRegistryEntrySchema>;
|
|
143
|
+
export type FileWriteRegistryEntry = z.infer<typeof FileWriteRegistryEntrySchema>;
|
|
144
|
+
export type ConfigPatchRegistryEntry = z.infer<typeof ConfigPatchRegistryEntrySchema>;
|
|
39
145
|
export type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
|
|
40
146
|
export type Registry = z.infer<typeof RegistrySchema>;
|
|
147
|
+
export {};
|
package/dist/schemas/registry.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { AgentIdSchema } from "./manifest.js";
|
|
3
|
-
|
|
3
|
+
const SkillDirRegistryEntrySchema = z.object({
|
|
4
|
+
kind: z.literal("skill-dir"),
|
|
4
5
|
source: z.string(),
|
|
5
6
|
skill: z.string(),
|
|
6
7
|
agent: AgentIdSchema,
|
|
7
|
-
method: z.enum(["symlink", "copy"]),
|
|
8
|
+
method: z.enum(["symlink", "copy"]).optional(),
|
|
8
9
|
deployed: z.string(),
|
|
9
10
|
});
|
|
11
|
+
const FileWriteRegistryEntrySchema = z.object({
|
|
12
|
+
kind: z.literal("file-write"),
|
|
13
|
+
source: z.string(),
|
|
14
|
+
skill: z.string(),
|
|
15
|
+
agent: AgentIdSchema,
|
|
16
|
+
deployed: z.string(),
|
|
17
|
+
});
|
|
18
|
+
const ConfigPatchRegistryEntrySchema = z.object({
|
|
19
|
+
kind: z.literal("config-patch"),
|
|
20
|
+
patch: z.record(z.string(), z.unknown()),
|
|
21
|
+
undoPatch: z.record(z.string(), z.unknown()),
|
|
22
|
+
skill: z.string(),
|
|
23
|
+
agent: AgentIdSchema,
|
|
24
|
+
deployed: z.string(),
|
|
25
|
+
});
|
|
26
|
+
export const RegistryEntrySchema = z.discriminatedUnion("kind", [
|
|
27
|
+
SkillDirRegistryEntrySchema,
|
|
28
|
+
FileWriteRegistryEntrySchema,
|
|
29
|
+
ConfigPatchRegistryEntrySchema,
|
|
30
|
+
]);
|
|
10
31
|
export const RegistrySchema = z.object({
|
|
11
32
|
version: z.literal(1),
|
|
12
33
|
deployments: z.record(z.string(), RegistryEntrySchema),
|
package/dist/types.d.ts
CHANGED
|
@@ -18,17 +18,66 @@ export interface AgentConfig {
|
|
|
18
18
|
detectBinary: string | null;
|
|
19
19
|
provenance: AgentProvenance;
|
|
20
20
|
}
|
|
21
|
-
export interface
|
|
21
|
+
export interface PlanWarning {
|
|
22
|
+
kind: "confidence" | "collision" | "ambiguity";
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
export interface SkillDirDeployAction {
|
|
26
|
+
kind: "skill-dir";
|
|
22
27
|
skill: string;
|
|
23
28
|
agent: AgentId;
|
|
24
29
|
source: string;
|
|
25
30
|
target: string;
|
|
26
31
|
method: "symlink" | "copy";
|
|
32
|
+
confidence: Confidence;
|
|
33
|
+
}
|
|
34
|
+
export interface FileWriteDeployAction {
|
|
35
|
+
kind: "file-write";
|
|
36
|
+
skill: string;
|
|
37
|
+
agent: AgentId;
|
|
38
|
+
source: string;
|
|
39
|
+
target: string;
|
|
40
|
+
confidence?: Confidence;
|
|
41
|
+
}
|
|
42
|
+
export interface ConfigPatchDeployAction {
|
|
43
|
+
kind: "config-patch";
|
|
44
|
+
skill: string;
|
|
45
|
+
agent: AgentId;
|
|
46
|
+
target: string;
|
|
47
|
+
patch: unknown;
|
|
48
|
+
confidence?: Confidence;
|
|
49
|
+
}
|
|
50
|
+
export type DeployAction = SkillDirDeployAction | FileWriteDeployAction | ConfigPatchDeployAction;
|
|
51
|
+
export interface SkillDirRevertAction {
|
|
52
|
+
kind: "skill-dir";
|
|
53
|
+
skill: string;
|
|
54
|
+
agent: AgentId;
|
|
55
|
+
target: string;
|
|
56
|
+
}
|
|
57
|
+
export interface FileWriteRevertAction {
|
|
58
|
+
kind: "file-write";
|
|
59
|
+
skill: string;
|
|
60
|
+
agent: AgentId;
|
|
61
|
+
target: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ConfigPatchRevertAction {
|
|
64
|
+
kind: "config-patch";
|
|
65
|
+
skill: string;
|
|
66
|
+
agent: AgentId;
|
|
67
|
+
target: string;
|
|
27
68
|
}
|
|
28
|
-
export
|
|
69
|
+
export type RevertAction = SkillDirRevertAction | FileWriteRevertAction | ConfigPatchRevertAction;
|
|
70
|
+
export type PlannedChangeVerb = "create-symlink" | "copy-dir" | "write-file" | "patch-config" | "remove" | "unapply-patch";
|
|
71
|
+
export interface PlannedChange {
|
|
72
|
+
verb: PlannedChangeVerb;
|
|
73
|
+
kind: "skill-dir" | "file-write" | "config-patch";
|
|
29
74
|
skill: string;
|
|
30
75
|
agent: AgentId;
|
|
76
|
+
source?: string;
|
|
31
77
|
target: string;
|
|
78
|
+
method?: "symlink" | "copy";
|
|
79
|
+
patch?: Record<string, unknown>;
|
|
80
|
+
confidence?: Confidence;
|
|
32
81
|
}
|
|
33
82
|
export interface CliOptions {
|
|
34
83
|
command: "deploy" | "revert" | "help";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuznai/inception-engine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
"dist"
|
|
32
32
|
],
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=22.3.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
37
|
"build": "tsc",
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
39
39
|
"fmt": "biome format --write .",
|
|
40
40
|
"lint": "biome lint . --max-diagnostics none",
|
|
41
|
+
"predev": "node scripts/assert-direct-ts-node.mjs",
|
|
41
42
|
"dev": "node src/index.ts",
|
|
43
|
+
"pretest": "node scripts/assert-direct-ts-node.mjs",
|
|
42
44
|
"test": "node --test test/*.test.ts",
|
|
43
45
|
"prepublishOnly": "npm run typecheck && npm run lint && npm run build"
|
|
44
46
|
},
|