@kuznai/inception-engine 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,6 +2,8 @@
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. It does not yet manage persistent instruction files, MCP configuration, subagents, or agent-specific config patching.
6
+
5
7
  ## Quick Start
6
8
 
7
9
  ```bash
@@ -26,10 +28,12 @@ Managed skills overwrite their previous version. If a target exists but was not
26
28
  | Claude Code | `claude-code` | `~/.claude/skills/` | Yes | Yes | Yes |
27
29
  | OpenAI Codex | `codex` | `~/.codex/skills/` | Yes | Yes | Yes |
28
30
  | Gemini CLI | `gemini-cli` | `~/.gemini/skills/` | Yes | Yes | Yes |
29
- | Antigravity | `antigravity` | `~/.gemini/antigravity/skills/` | Yes | Yes | Yes |
31
+ | Antigravity | `antigravity` | `~/.gemini/antigravity/skills/` | Yes* | Yes* | Yes* |
30
32
  | OpenCode | `opencode` | `~/.config/opencode/skills/` | Yes | Yes | Yes* |
31
33
  | GitHub Copilot | `github-copilot` | `~/.copilot/skills/` | Yes | Yes | Yes |
32
34
 
35
+ \* Antigravity support is currently based on the implementation's registry path assumptions and local validation, not a strong official doc set equivalent to the other agents.
36
+
33
37
  \* OpenCode on Windows uses `%APPDATA%\opencode\skills\`.
34
38
 
35
39
  ### Feature Support
@@ -37,8 +41,8 @@ Managed skills overwrite their previous version. If a target exists but was not
37
41
  | Feature | Status |
38
42
  |---|---|
39
43
  | Skills (SKILL.md) | Supported |
40
- | MCP Servers | Planned |
41
- | Agent Rules | Planned |
44
+ | MCP Servers | Accepted in manifest, not implemented |
45
+ | Agent Rules | Accepted in manifest, not implemented |
42
46
 
43
47
  ## Manifest Format
44
48
 
@@ -60,10 +64,12 @@ Create an `inception.json` file at the root of your skills directory:
60
64
 
61
65
  Each skill entry has:
62
66
 
63
- - **name** - Unique skill identifier (lowercase, hyphens allowed)
67
+ - **name** - Unique skill identifier using letters, digits, dots, underscores, or hyphens; it must not start with a dot
64
68
  - **path** - Relative path to the skill directory within the repo
65
69
  - **agents** - Array of agent IDs to deploy this skill to. If an agent isn't installed, it's skipped.
66
70
 
71
+ `mcpServers` and `agentRules` are currently parsed for forward compatibility, but the deployment engine ignores them today.
72
+
67
73
  ## Creating Skills
68
74
 
69
75
  Each skill is a directory containing at minimum a `SKILL.md` file with YAML frontmatter:
@@ -155,16 +161,16 @@ Revert targets all agents listed in the manifest by default (regardless of detec
155
161
 
156
162
  ### Ownership Tracking and Safe Revert
157
163
 
158
- inception-engine writes a structured `.inception-totem` marker file during every deploy so that `revert` and future deploys never touch content they did not create. The totem contains metadata (source path, skill name, agent ID, deploy timestamp) and is validated on both revert and redeploy.
159
-
160
- - **POSIX (symlink)**: `.inception-totem` is written inside the skill source directory. On revert, the tool resolves the symlink target and checks for a valid `.inception-totem` there. Only symlinks whose resolved target contains a valid totem are removed.
164
+ inception-engine maintains a centralized deployment registry at `~/.inception-engine/registry.json`. Each deploy records the target path, source path, skill name, agent ID, deploy method, and timestamp. No files are written to the source repository.
161
165
 
162
- - **Windows (copy)**: `.inception-totem` is written inside each deployed skill directory. On revert, this file must be present and valid for the directory to be removed.
166
+ - **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.
163
167
 
164
- - **Deploy safety**: Before overwriting an existing target, the engine checks for a valid `.inception-totem`. If the target exists but is not managed by inception-engine, the deploy is skipped with an error the unmanaged content is never removed.
168
+ - **Strong binding**: Each registry entry binds a specific target path to its source, skill, agent, and deploy method. A target is only considered managed if all fields match a stray entry or a different deployment cannot satisfy the check.
165
169
 
166
170
  - **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.
167
171
 
172
+ - **Cross-platform**: The registry uses the same resolved home directory as the rest of the tool, including sudo scenarios on POSIX and elevated PowerShell on Windows.
173
+
168
174
  ## Running with Privilege Escalation
169
175
 
170
176
  The tool works without elevated privileges. If run with `sudo` on POSIX systems, it looks up the real user's home directory from the OS directory services (`getent passwd` on Linux, `dscl` on macOS, `/etc/passwd` as a universal fallback) so skills are deployed to the correct location regardless of where home directories are stored — standard `/home/<user>`, LDAP/NIS paths, enterprise layouts, or otherwise.
@@ -179,7 +185,11 @@ On Windows, `os.homedir()` correctly resolves even in elevated PowerShell or cmd
179
185
 
180
186
  When run under `sudo` on POSIX, `SUDO_USER` is read as an advisory signal to identify the real user. The username is validated through OS directory services (`getent passwd` on Linux, `dscl` on macOS, `/etc/passwd` as a fallback) — it is never used as a raw path. This is standard practice for sudo-aware CLI tools.
181
187
 
182
- Automation edge case: if the tool runs inside a pipeline where `SUDO_USER` has been set externally in the environment before the process starts, the tool will look up that username's home directory. To avoid this in automation, either run without `sudo` or set `HOME` explicitly before invoking the tool.
188
+ `SUDO_USER` is only consulted when the process is actually running as root (UID 0). If `SUDO_USER` is present in the environment but the process is not root — for example, as a stale shell variable from a prior `sudo` session or an externally injected value in automation it is ignored and the standard home directory is used instead.
189
+
190
+ ### XDG_CONFIG_HOME support
191
+
192
+ On POSIX, agents whose config lives under `~/.config/` (currently OpenCode) honor `$XDG_CONFIG_HOME` when it is set to an absolute path. If the variable is unset or contains a relative path (which the XDG Base Directory Specification disallows), the tool falls back to the standard `~/.config` default. This has no effect on Windows.
183
193
 
184
194
  ### Windows %APPDATA% fallback
185
195
 
@@ -187,7 +197,7 @@ On Windows, `%APPDATA%` is used to locate agent config directories (currently on
187
197
 
188
198
  ### Future: mixed asset layouts on Windows
189
199
 
190
- Windows deployment currently uses directory-level copy (one `cp -r` per skill). This works correctly for the present skill model where each skill is a single directory. If file-based assets or mixed (file + directory) skill layouts are added in the future, the copy strategy in `src/core/deploy.ts` will need revisiting — including `.inception-totem` placement and revert logic.
200
+ Windows deployment currently uses directory-level copy (one `cp -r` per skill). This works correctly for the present skill model where each skill is a single directory. If file-based assets or mixed (file + directory) skill layouts are added in the future, the copy strategy in `src/core/deploy.ts` will need revisiting.
191
201
 
192
202
  ## Requirements
193
203
 
@@ -11,6 +11,11 @@ export const AGENT_REGISTRY = [
11
11
  windows: ["{home}", ".claude"],
12
12
  },
13
13
  detectBinary: "claude",
14
+ provenance: {
15
+ skills: "documented",
16
+ detectPaths: "documented",
17
+ detectBinary: "documented",
18
+ },
14
19
  },
15
20
  {
16
21
  id: "codex",
@@ -24,6 +29,11 @@ export const AGENT_REGISTRY = [
24
29
  windows: ["{home}", ".codex"],
25
30
  },
26
31
  detectBinary: "codex",
32
+ provenance: {
33
+ skills: "documented",
34
+ detectPaths: "documented",
35
+ detectBinary: "documented",
36
+ },
27
37
  },
28
38
  {
29
39
  id: "gemini-cli",
@@ -37,6 +47,11 @@ export const AGENT_REGISTRY = [
37
47
  windows: ["{home}", ".gemini"],
38
48
  },
39
49
  detectBinary: "gemini",
50
+ provenance: {
51
+ skills: "documented",
52
+ detectPaths: "documented",
53
+ detectBinary: "documented",
54
+ },
40
55
  },
41
56
  {
42
57
  id: "antigravity",
@@ -50,19 +65,29 @@ export const AGENT_REGISTRY = [
50
65
  windows: ["{home}", ".gemini", "antigravity"],
51
66
  },
52
67
  detectBinary: null,
68
+ provenance: {
69
+ skills: "implementation-only",
70
+ detectPaths: "implementation-only",
71
+ detectBinary: "provisional",
72
+ },
53
73
  },
54
74
  {
55
75
  id: "opencode",
56
76
  displayName: "OpenCode",
57
77
  skills: {
58
- posix: ["{home}", ".config", "opencode", "skills", "{name}"],
78
+ posix: ["{xdg_config}", "opencode", "skills", "{name}"],
59
79
  windows: ["{appdata}", "opencode", "skills", "{name}"],
60
80
  },
61
81
  detectPaths: {
62
- posix: ["{home}", ".config", "opencode"],
82
+ posix: ["{xdg_config}", "opencode"],
63
83
  windows: ["{appdata}", "opencode"],
64
84
  },
65
85
  detectBinary: "opencode",
86
+ provenance: {
87
+ skills: "documented",
88
+ detectPaths: "documented",
89
+ detectBinary: "documented",
90
+ },
66
91
  },
67
92
  {
68
93
  id: "github-copilot",
@@ -76,6 +101,11 @@ export const AGENT_REGISTRY = [
76
101
  windows: ["{home}", ".copilot"],
77
102
  },
78
103
  detectBinary: "github-copilot",
104
+ provenance: {
105
+ skills: "documented",
106
+ detectPaths: "documented",
107
+ detectBinary: "documented",
108
+ },
79
109
  },
80
110
  ];
81
111
  export const AGENT_REGISTRY_BY_ID = Object.fromEntries(AGENT_REGISTRY.map((a) => [a.id, a]));
@@ -1,6 +1,6 @@
1
1
  import type { AgentId, DeployAction, Manifest } from "../types.ts";
2
2
  export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): Promise<DeployAction[]>;
3
- export declare function executeDeploy(actions: DeployAction[], dryRun: boolean, verbose: boolean): Promise<{
3
+ export declare function executeDeploy(actions: DeployAction[], dryRun: boolean, verbose: boolean, home: string): Promise<{
4
4
  succeeded: number;
5
5
  failed: Array<{
6
6
  action: DeployAction;
@@ -3,7 +3,7 @@ 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 { isOwnedByInceptionEngine, writeTotem } from "./ownership.js";
6
+ import { registerDeployment, verifyDeployment } from "./ownership.js";
7
7
  import { getDeployMethod, resolveAgentSkillPath } from "./resolve.js";
8
8
  export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
9
9
  const method = getDeployMethod();
@@ -37,7 +37,7 @@ export async function planDeploy(manifest, sourceDir, detectedAgents, home) {
37
37
  }
38
38
  return actions;
39
39
  }
40
- export async function executeDeploy(actions, dryRun, verbose) {
40
+ export async function executeDeploy(actions, dryRun, verbose, home) {
41
41
  let succeeded = 0;
42
42
  const failed = [];
43
43
  for (const action of actions) {
@@ -60,7 +60,7 @@ export async function executeDeploy(actions, dryRun, verbose) {
60
60
  continue;
61
61
  }
62
62
  try {
63
- await executeDeployAction(action, verbose);
63
+ await executeDeployAction(action, verbose, home);
64
64
  succeeded++;
65
65
  }
66
66
  catch (err) {
@@ -99,31 +99,31 @@ async function assertTargetAbsent(targetPath) {
99
99
  // ENOENT is expected — target should not exist after backup
100
100
  }
101
101
  }
102
- async function createDeployTarget(action) {
102
+ async function createDeployTarget(action, home) {
103
103
  if (action.method === "symlink") {
104
104
  await symlink(action.source, action.target, "dir");
105
- await writeTotem(action.source, {
106
- source: action.source,
107
- skill: action.skill,
108
- agent: action.agent,
109
- });
110
105
  }
111
106
  else {
112
107
  await cp(action.source, action.target, { recursive: true });
113
- await writeTotem(action.target, {
114
- source: action.source,
115
- skill: action.skill,
116
- agent: action.agent,
117
- });
118
108
  }
109
+ await registerDeployment(home, action.target, {
110
+ source: action.source,
111
+ skill: action.skill,
112
+ agent: action.agent,
113
+ method: action.method,
114
+ });
119
115
  }
120
- async function executeDeployAction(action, verbose) {
116
+ async function executeDeployAction(action, verbose, home) {
121
117
  const label = `${action.skill} -> ${action.agent}`;
122
- const backupPath = await backupExisting(action.target, verbose);
118
+ const backupPath = await backupExisting(action.target, verbose, home, {
119
+ source: action.source,
120
+ skill: action.skill,
121
+ agent: action.agent,
122
+ });
123
123
  await mkdir(path.dirname(action.target), { recursive: true });
124
124
  try {
125
125
  await assertTargetAbsent(action.target);
126
- await createDeployTarget(action);
126
+ await createDeployTarget(action, home);
127
127
  }
128
128
  catch (createErr) {
129
129
  if (backupPath) {
@@ -144,15 +144,14 @@ async function executeDeployAction(action, verbose) {
144
144
  logger.detail(`${action.method}: ${action.source} -> ${action.target}`);
145
145
  }
146
146
  }
147
- async function backupExisting(targetPath, verbose) {
148
- let stat;
147
+ async function backupExisting(targetPath, verbose, home, expected) {
149
148
  try {
150
- stat = await lstat(targetPath);
149
+ await lstat(targetPath);
151
150
  }
152
151
  catch {
153
152
  return null;
154
153
  }
155
- if (!(await isOwnedByInceptionEngine(targetPath, stat))) {
154
+ if (!(await verifyDeployment(home, targetPath, expected))) {
156
155
  throw new Error(`Target "${targetPath}" exists but is not managed by inception-engine — refusing to overwrite`);
157
156
  }
158
157
  const backupPath = `${targetPath}.inception-backup`;
@@ -1,10 +1,17 @@
1
- import type { Stats } from "node:fs";
2
1
  import type { AgentId } from "../types.ts";
3
- export interface TotemData {
2
+ export interface RegistryEntry {
4
3
  source: string;
5
4
  skill: string;
6
5
  agent: AgentId;
6
+ method: "symlink" | "copy";
7
+ deployed: string;
7
8
  }
8
- export declare function formatTotem(data: TotemData): string;
9
- export declare function writeTotem(directory: string, data: TotemData): Promise<void>;
10
- export declare function isOwnedByInceptionEngine(targetPath: string, stat: Stats): Promise<boolean>;
9
+ export declare function registryPath(home: string): string;
10
+ export declare function registerDeployment(home: string, targetPath: string, entry: Omit<RegistryEntry, "deployed">): Promise<void>;
11
+ export declare function unregisterDeployment(home: string, targetPath: string): Promise<void>;
12
+ export declare function lookupDeployment(home: string, targetPath: string): Promise<RegistryEntry | null>;
13
+ export declare function verifyDeployment(home: string, targetPath: string, expected: {
14
+ source: string;
15
+ skill: string;
16
+ agent: AgentId;
17
+ }): Promise<RegistryEntry | null>;
@@ -1,44 +1,73 @@
1
- import { access, chmod, readFile, readlink, writeFile } from "node:fs/promises";
1
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- const TOTEM_FILE = ".inception-totem";
4
- const TOTEM_HEADER = "inception-engine";
5
- export function formatTotem(data) {
6
- const lines = [
7
- TOTEM_HEADER,
8
- `source=${data.source}`,
9
- `skill=${data.skill}`,
10
- `agent=${data.agent}`,
11
- `deployed=${new Date().toISOString()}`,
12
- ];
13
- return `${lines.join("\n")}\n`;
14
- }
15
- export async function writeTotem(directory, data) {
16
- const totemPath = path.join(directory, TOTEM_FILE);
17
- await writeFile(totemPath, formatTotem(data));
18
- await chmod(totemPath, 0o644);
19
- }
20
- export async function isOwnedByInceptionEngine(targetPath, stat) {
21
- const totemLocation = stat.isSymbolicLink()
22
- ? await resolveSymlinkTotemPath(targetPath)
23
- : path.join(targetPath, TOTEM_FILE);
24
- if (!totemLocation)
25
- return false;
3
+ const REGISTRY_DIR = ".inception-engine";
4
+ const REGISTRY_FILE = "registry.json";
5
+ export function registryPath(home) {
6
+ return path.join(home, REGISTRY_DIR, REGISTRY_FILE);
7
+ }
8
+ async function loadRegistry(home) {
26
9
  try {
27
- const content = await readFile(totemLocation, "utf-8");
28
- return content.startsWith(TOTEM_HEADER);
10
+ const content = await readFile(registryPath(home), "utf-8");
11
+ const parsed = JSON.parse(content);
12
+ if (parsed &&
13
+ typeof parsed === "object" &&
14
+ parsed.version === 1 &&
15
+ parsed.deployments &&
16
+ typeof parsed.deployments === "object") {
17
+ return parsed;
18
+ }
19
+ return emptyRegistry();
29
20
  }
30
21
  catch {
31
- return false;
22
+ return emptyRegistry();
32
23
  }
33
24
  }
34
- async function resolveSymlinkTotemPath(targetPath) {
35
- try {
36
- const linkTarget = await readlink(targetPath);
37
- const resolved = path.resolve(path.dirname(targetPath), linkTarget);
38
- await access(resolved);
39
- return path.join(resolved, TOTEM_FILE);
25
+ async function saveRegistry(home, registry) {
26
+ const dir = path.join(home, REGISTRY_DIR);
27
+ await mkdir(dir, { recursive: true });
28
+ const filePath = registryPath(home);
29
+ await writeFile(filePath, `${JSON.stringify(registry, null, 2)}\n`);
30
+ await setFilePermissions(filePath);
31
+ }
32
+ /**
33
+ * Ensure the file is not world-writable regardless of umask.
34
+ * On Windows, the OS inherits ACLs from the parent directory — no-op is correct.
35
+ */
36
+ async function setFilePermissions(filePath) {
37
+ if (process.platform !== "win32") {
38
+ await chmod(filePath, 0o644);
40
39
  }
41
- catch {
40
+ }
41
+ function emptyRegistry() {
42
+ return { version: 1, deployments: {} };
43
+ }
44
+ export async function registerDeployment(home, targetPath, entry) {
45
+ const registry = await loadRegistry(home);
46
+ registry.deployments[targetPath] = {
47
+ ...entry,
48
+ deployed: new Date().toISOString(),
49
+ };
50
+ await saveRegistry(home, registry);
51
+ }
52
+ export async function unregisterDeployment(home, targetPath) {
53
+ const registry = await loadRegistry(home);
54
+ if (!(targetPath in registry.deployments))
55
+ return;
56
+ delete registry.deployments[targetPath];
57
+ await saveRegistry(home, registry);
58
+ }
59
+ export async function lookupDeployment(home, targetPath) {
60
+ const registry = await loadRegistry(home);
61
+ return registry.deployments[targetPath] ?? null;
62
+ }
63
+ export async function verifyDeployment(home, targetPath, expected) {
64
+ const entry = await lookupDeployment(home, targetPath);
65
+ if (!entry)
66
+ return null;
67
+ if (entry.source !== expected.source ||
68
+ entry.skill !== expected.skill ||
69
+ entry.agent !== expected.agent) {
42
70
  return null;
43
71
  }
72
+ return entry;
44
73
  }
@@ -8,7 +8,7 @@ export function resolveHome() {
8
8
  return os.homedir();
9
9
  }
10
10
  const sudoUser = process.env.SUDO_USER;
11
- if (sudoUser) {
11
+ if (sudoUser && process.getuid?.() === 0) {
12
12
  return lookupHomeForUser(sudoUser);
13
13
  }
14
14
  return os.homedir();
@@ -98,9 +98,12 @@ export function resolveAgentDetectPath(agent, home) {
98
98
  }
99
99
  function resolvePlaceholders(segments, skillName, home) {
100
100
  const appdata = process.env.APPDATA ?? path.join(home, "AppData", "Roaming");
101
+ const xdgRaw = process.env.XDG_CONFIG_HOME;
102
+ const xdgConfig = xdgRaw && path.isAbsolute(xdgRaw) ? xdgRaw : path.join(home, ".config");
101
103
  const resolved = segments.map((seg) => seg
102
104
  .replace("{home}", home)
103
105
  .replace("{name}", skillName)
104
- .replace("{appdata}", appdata));
106
+ .replace("{appdata}", appdata)
107
+ .replace("{xdg_config}", xdgConfig));
105
108
  return path.join(...resolved);
106
109
  }
@@ -1,7 +1,7 @@
1
1
  import type { AgentId, Manifest, 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
- export declare function executeRevert(actions: RevertAction[], dryRun: boolean, verbose: boolean): Promise<{
4
+ export declare function executeRevert(actions: RevertAction[], dryRun: boolean, verbose: boolean, home: string): Promise<{
5
5
  succeeded: number;
6
6
  skipped: number;
7
7
  }>;
@@ -1,7 +1,7 @@
1
1
  import { lstat, rm, unlink } from "node:fs/promises";
2
2
  import { AGENT_REGISTRY_BY_ID } from "../config/agents.js";
3
3
  import { logger } from "../logger.js";
4
- import { isOwnedByInceptionEngine } from "./ownership.js";
4
+ import { lookupDeployment, unregisterDeployment } from "./ownership.js";
5
5
  import { resolveAgentSkillPath } from "./resolve.js";
6
6
  export function planRevert(manifest, detectedAgents, home) {
7
7
  const actions = [];
@@ -31,11 +31,11 @@ export function planRevertAll(manifest, home) {
31
31
  }
32
32
  return actions;
33
33
  }
34
- export async function executeRevert(actions, dryRun, verbose) {
34
+ export async function executeRevert(actions, dryRun, verbose, home) {
35
35
  let succeeded = 0;
36
36
  let skipped = 0;
37
37
  for (const action of actions) {
38
- const result = await executeRevertAction(action, dryRun, verbose);
38
+ const result = await executeRevertAction(action, dryRun, verbose, home);
39
39
  if (result === "skip") {
40
40
  skipped++;
41
41
  }
@@ -45,7 +45,7 @@ export async function executeRevert(actions, dryRun, verbose) {
45
45
  }
46
46
  return { succeeded, skipped };
47
47
  }
48
- async function executeRevertAction(action, dryRun, verbose) {
48
+ async function executeRevertAction(action, dryRun, verbose, home) {
49
49
  const label = `${action.skill} -> ${action.agent}`;
50
50
  let stat;
51
51
  try {
@@ -55,8 +55,9 @@ async function executeRevertAction(action, dryRun, verbose) {
55
55
  logger.skip(label, "(not found, skipping)");
56
56
  return "skip";
57
57
  }
58
- if (!(await isOwnedByInceptionEngine(action.target, stat))) {
59
- logger.warn(label, `skipping: ${action.target} does not have inception-engine ownership proof — not managed by inception-engine`);
58
+ const entry = await lookupDeployment(home, action.target);
59
+ if (!entry || entry.skill !== action.skill || entry.agent !== action.agent) {
60
+ logger.warn(label, `skipping: ${action.target} is not in the deployment registry — not managed by inception-engine`);
60
61
  return "skip";
61
62
  }
62
63
  if (dryRun) {
@@ -73,6 +74,7 @@ async function executeRevertAction(action, dryRun, verbose) {
73
74
  else {
74
75
  await rm(action.target, { recursive: true });
75
76
  }
77
+ await unregisterDeployment(home, action.target);
76
78
  logger.ok(label);
77
79
  if (verbose) {
78
80
  logger.detail(`removed: ${action.target}`);
package/dist/index.js CHANGED
@@ -137,7 +137,7 @@ async function runDeploy(options, manifest, home) {
137
137
  return 0;
138
138
  }
139
139
  logger.info(`${dryRunPrefix(options.dryRun)}Deploying ${actions.length} skill(s):`);
140
- const { succeeded, failed } = await executeDeploy(actions, options.dryRun, options.verbose);
140
+ const { succeeded, failed } = await executeDeploy(actions, options.dryRun, options.verbose, home);
141
141
  logger.info("");
142
142
  if (failed.length > 0) {
143
143
  logger.info(`${succeeded} succeeded, ${failed.length} failed`);
@@ -155,7 +155,7 @@ async function runRevert(options, manifest, home) {
155
155
  return 0;
156
156
  }
157
157
  logger.info(`${dryRunPrefix(options.dryRun)}Reverting ${actions.length} skill(s):`);
158
- const { succeeded, skipped } = await executeRevert(actions, options.dryRun, options.verbose);
158
+ const { succeeded, skipped } = await executeRevert(actions, options.dryRun, options.verbose, home);
159
159
  logger.info("");
160
160
  const parts = [`${succeeded} removed`];
161
161
  if (skipped > 0)
package/dist/types.d.ts CHANGED
@@ -14,12 +14,19 @@ export interface AgentPaths {
14
14
  posix: string[];
15
15
  windows: string[];
16
16
  }
17
+ export type Confidence = "documented" | "implementation-only" | "provisional";
18
+ export interface AgentProvenance {
19
+ skills: Confidence;
20
+ detectPaths: Confidence;
21
+ detectBinary: Confidence;
22
+ }
17
23
  export interface AgentConfig {
18
24
  id: AgentId;
19
25
  displayName: string;
20
26
  skills: AgentPaths;
21
27
  detectPaths: AgentPaths;
22
28
  detectBinary: string | null;
29
+ provenance: AgentProvenance;
23
30
  }
24
31
  export interface DeployAction {
25
32
  skill: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kuznai/inception-engine",
3
- "version": "0.4.1",
3
+ "version": "0.5.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",
@@ -36,7 +36,7 @@
36
36
  "scripts": {
37
37
  "build": "tsc",
38
38
  "typecheck": "tsc --noEmit",
39
- "format": "biome format --write .",
39
+ "fmt": "biome format --write .",
40
40
  "lint": "biome lint . --max-diagnostics none",
41
41
  "dev": "node src/index.ts",
42
42
  "test": "node --test test/*.test.ts",