@kuznai/inception-engine 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Damian Piątkowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # inception-engine
2
+
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
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx inception-engine <directory>
9
+ ```
10
+
11
+ Where `<directory>` is a repo (or subdirectory) containing an `inception.json` manifest and skill files.
12
+
13
+ ## How It Works
14
+
15
+ inception-engine reads a manifest file (`inception.json`) from the target directory, detects which AI coding agents are installed on the system, and deploys skills to each agent's expected location.
16
+
17
+ - **POSIX (macOS, Linux)**: creates symlinks from the source skill directory to each agent's skill path
18
+ - **Windows**: copies skill directories to each agent's skill path
19
+
20
+ Skills always overwrite their previous version. On POSIX systems, symlinks mean updates to the source repo are reflected immediately.
21
+
22
+ ## Agent Compatibility Matrix
23
+
24
+ | Agent | ID | Skills | macOS | Linux | Windows |
25
+ |---|---|---|---|---|---|
26
+ | Claude Code | `claude-code` | `~/.claude/skills/` | Yes | Yes | Yes |
27
+ | OpenAI Codex | `codex` | `~/.codex/skills/` | Yes | Yes | Yes |
28
+ | Gemini CLI | `gemini-cli` | `~/.gemini/skills/` | Yes | Yes | Yes |
29
+ | Antigravity | `antigravity` | `~/.gemini/antigravity/skills/` | Yes | Yes | Yes |
30
+ | OpenCode | `opencode` | `~/.config/opencode/skills/` | Yes | Yes | Yes* |
31
+ | GitHub Copilot | `github-copilot` | `~/.copilot/skills/` | Yes | Yes | Yes |
32
+
33
+ \* OpenCode on Windows uses `%APPDATA%\opencode\skills\`.
34
+
35
+ ### Feature Support
36
+
37
+ | Feature | Status |
38
+ |---|---|
39
+ | Skills (SKILL.md) | Supported |
40
+ | MCP Servers | Planned |
41
+ | Agent Rules | Planned |
42
+
43
+ ## Manifest Format
44
+
45
+ Create an `inception.json` file at the root of your skills directory:
46
+
47
+ ```json
48
+ {
49
+ "skills": [
50
+ {
51
+ "name": "my-skill",
52
+ "path": "skills/my-skill",
53
+ "agents": ["claude-code", "codex", "gemini-cli", "antigravity", "opencode", "github-copilot"]
54
+ }
55
+ ],
56
+ "mcpServers": [],
57
+ "agentRules": []
58
+ }
59
+ ```
60
+
61
+ Each skill entry has:
62
+
63
+ - **name** - Unique skill identifier (lowercase, hyphens allowed)
64
+ - **path** - Relative path to the skill directory within the repo
65
+ - **agents** - Array of agent IDs to deploy this skill to. If an agent isn't installed, it's skipped.
66
+
67
+ ## Creating Skills
68
+
69
+ Each skill is a directory containing at minimum a `SKILL.md` file with YAML frontmatter:
70
+
71
+ ```markdown
72
+ ---
73
+ name: my-skill
74
+ description: What this skill does and when to use it
75
+ ---
76
+
77
+ # My Skill
78
+
79
+ Instructions for the AI agent...
80
+ ```
81
+
82
+ The `name` and `description` fields in the frontmatter are required by most agents. The description determines when the agent activates the skill.
83
+
84
+ ## CLI Reference
85
+
86
+ ```
87
+ inception-engine <directory> [options]
88
+ inception-engine revert <directory> [options]
89
+ ```
90
+
91
+ ### Commands
92
+
93
+ | Command | Description |
94
+ |---|---|
95
+ | `<directory>` | Deploy skills from the manifest in the given directory |
96
+ | `revert <directory>` | Remove all skills declared in the manifest |
97
+
98
+ ### Options
99
+
100
+ | Option | Description |
101
+ |---|---|
102
+ | `--dry-run` | Show what would be done without making changes |
103
+ | `--agents <list>` | Comma-separated list of agent IDs to target (skips detection) |
104
+ | `--verbose` | Show detailed output including file paths |
105
+ | `--debug` | Show full error stack traces |
106
+ | `--help` | Show help message |
107
+
108
+ ### Examples
109
+
110
+ ```bash
111
+ # Deploy all skills to all detected agents
112
+ npx inception-engine ./my-skills-repo
113
+
114
+ # Preview what would be deployed
115
+ npx inception-engine ./my-skills-repo --dry-run
116
+
117
+ # Deploy only to Claude Code and Codex
118
+ npx inception-engine ./my-skills-repo --agents claude-code,codex
119
+
120
+ # Remove deployed skills
121
+ npx inception-engine revert ./my-skills-repo
122
+
123
+ # Preview what would be removed
124
+ npx inception-engine revert ./my-skills-repo --dry-run
125
+ ```
126
+
127
+ ## Sample Skills
128
+
129
+ The `limbo/` directory contains exceptional sample skills for testing purposes only.
130
+
131
+ Try them out:
132
+
133
+ ```bash
134
+ npx inception-engine limbo --dry-run
135
+ ```
136
+
137
+ ## Agent Detection
138
+
139
+ inception-engine automatically detects which agents are installed by checking:
140
+
141
+ 1. Whether the agent's config directory exists (e.g., `~/.claude/` for Claude Code)
142
+ 2. Whether the agent's binary is in your PATH (e.g., `claude`, `codex`, `gemini`)
143
+
144
+ If an agent isn't detected, its skills are skipped. Use `--agents` to override detection.
145
+
146
+ ## Cross-Platform Behavior
147
+
148
+ | Platform | Deploy Method | Behavior |
149
+ |---|---|---|
150
+ | macOS | Symlink | Source changes are reflected immediately |
151
+ | Linux | Symlink | Source changes are reflected immediately |
152
+ | Windows | Copy | Source must be re-deployed after changes |
153
+
154
+ ## Running with Privilege Escalation
155
+
156
+ The tool works without elevated privileges. If run with `sudo` on POSIX systems, it resolves the real user's home directory via `SUDO_USER` so skills are deployed to the correct location (not `/root/`).
157
+
158
+ On Windows, `os.homedir()` correctly resolves even in elevated PowerShell or cmd.
159
+
160
+ ## Requirements
161
+
162
+ - Node.js >= 23.6.0
163
+
164
+ ## License
165
+
166
+ MIT
@@ -0,0 +1,2 @@
1
+ import type { AgentConfig } from "../types.ts";
2
+ export declare const AGENT_REGISTRY: readonly AgentConfig[];
@@ -0,0 +1,80 @@
1
+ export const AGENT_REGISTRY = [
2
+ {
3
+ id: "claude-code",
4
+ displayName: "Claude Code",
5
+ skills: {
6
+ posix: "{home}/.claude/skills/{name}",
7
+ windows: "{home}\\.claude\\skills\\{name}",
8
+ },
9
+ detectPaths: {
10
+ posix: "{home}/.claude",
11
+ windows: "{home}\\.claude",
12
+ },
13
+ detectBinary: "claude",
14
+ },
15
+ {
16
+ id: "codex",
17
+ displayName: "OpenAI Codex",
18
+ skills: {
19
+ posix: "{home}/.codex/skills/{name}",
20
+ windows: "{home}\\.codex\\skills\\{name}",
21
+ },
22
+ detectPaths: {
23
+ posix: "{home}/.codex",
24
+ windows: "{home}\\.codex",
25
+ },
26
+ detectBinary: "codex",
27
+ },
28
+ {
29
+ id: "gemini-cli",
30
+ displayName: "Gemini CLI",
31
+ skills: {
32
+ posix: "{home}/.gemini/skills/{name}",
33
+ windows: "{home}\\.gemini\\skills\\{name}",
34
+ },
35
+ detectPaths: {
36
+ posix: "{home}/.gemini",
37
+ windows: "{home}\\.gemini",
38
+ },
39
+ detectBinary: "gemini",
40
+ },
41
+ {
42
+ id: "antigravity",
43
+ displayName: "Antigravity",
44
+ skills: {
45
+ posix: "{home}/.gemini/antigravity/skills/{name}",
46
+ windows: "{home}\\.gemini\\antigravity\\skills\\{name}",
47
+ },
48
+ detectPaths: {
49
+ posix: "{home}/.gemini/antigravity",
50
+ windows: "{home}\\.gemini\\antigravity",
51
+ },
52
+ detectBinary: null,
53
+ },
54
+ {
55
+ id: "opencode",
56
+ displayName: "OpenCode",
57
+ skills: {
58
+ posix: "{home}/.config/opencode/skills/{name}",
59
+ windows: "{appdata}\\opencode\\skills\\{name}",
60
+ },
61
+ detectPaths: {
62
+ posix: "{home}/.config/opencode",
63
+ windows: "{appdata}\\opencode",
64
+ },
65
+ detectBinary: "opencode",
66
+ },
67
+ {
68
+ id: "github-copilot",
69
+ displayName: "GitHub Copilot",
70
+ skills: {
71
+ posix: "{home}/.copilot/skills/{name}",
72
+ windows: "{home}\\.copilot\\skills\\{name}",
73
+ },
74
+ detectPaths: {
75
+ posix: "{home}/.copilot",
76
+ windows: "{home}\\.copilot",
77
+ },
78
+ detectBinary: "github-copilot",
79
+ },
80
+ ];
@@ -0,0 +1,2 @@
1
+ import type { Manifest } from "../types.ts";
2
+ export declare function loadManifest(directory: string): Manifest;
@@ -0,0 +1,61 @@
1
+ import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { AGENT_IDS } from "../types.js";
4
+ import { UserError } from "../errors.js";
5
+ export function loadManifest(directory) {
6
+ const manifestPath = path.join(directory, "inception.json");
7
+ let raw;
8
+ try {
9
+ raw = readFileSync(manifestPath, "utf-8");
10
+ }
11
+ catch {
12
+ throw new UserError(`No inception.json found in ${directory}. Are you pointing to the right repo?`);
13
+ }
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(raw);
17
+ }
18
+ catch {
19
+ throw new UserError(`Invalid JSON in ${manifestPath}`);
20
+ }
21
+ return validateManifest(parsed, manifestPath);
22
+ }
23
+ function validateManifest(data, filePath) {
24
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
25
+ throw new UserError(`${filePath}: manifest must be a JSON object`);
26
+ }
27
+ const obj = data;
28
+ if (!Array.isArray(obj.skills)) {
29
+ throw new UserError(`${filePath}: "skills" must be an array`);
30
+ }
31
+ const skills = obj.skills.map((entry, i) => {
32
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
33
+ throw new UserError(`${filePath}: skills[${i}] must be an object`);
34
+ }
35
+ const skill = entry;
36
+ if (typeof skill.name !== "string" || skill.name.length === 0) {
37
+ throw new UserError(`${filePath}: skills[${i}].name must be a non-empty string`);
38
+ }
39
+ if (typeof skill.path !== "string" || skill.path.length === 0) {
40
+ throw new UserError(`${filePath}: skills[${i}].path must be a non-empty string`);
41
+ }
42
+ if (!Array.isArray(skill.agents) || skill.agents.length === 0) {
43
+ throw new UserError(`${filePath}: skills[${i}].agents must be a non-empty array`);
44
+ }
45
+ for (const agent of skill.agents) {
46
+ if (!AGENT_IDS.includes(agent)) {
47
+ throw new UserError(`${filePath}: skills[${i}].agents contains unknown agent "${agent}". Valid agents: ${AGENT_IDS.join(", ")}`);
48
+ }
49
+ }
50
+ return {
51
+ name: skill.name,
52
+ path: skill.path,
53
+ agents: skill.agents,
54
+ };
55
+ });
56
+ return {
57
+ skills,
58
+ mcpServers: Array.isArray(obj.mcpServers) ? obj.mcpServers : [],
59
+ agentRules: Array.isArray(obj.agentRules) ? obj.agentRules : [],
60
+ };
61
+ }
@@ -0,0 +1,9 @@
1
+ import type { AgentId, DeployAction, Manifest } from "../types.ts";
2
+ export declare function planDeploy(manifest: Manifest, sourceDir: string, detectedAgents: AgentId[], home: string): DeployAction[];
3
+ export declare function executeDeploy(actions: DeployAction[], dryRun: boolean, verbose: boolean): {
4
+ succeeded: number;
5
+ failed: Array<{
6
+ action: DeployAction;
7
+ error: string;
8
+ }>;
9
+ };
@@ -0,0 +1,87 @@
1
+ import { existsSync, lstatSync, mkdirSync, rmSync, symlinkSync, cpSync, unlinkSync, } from "node:fs";
2
+ import path from "node:path";
3
+ import { AGENT_REGISTRY } from "../config/agents.js";
4
+ import { resolveAgentSkillPath, getDeployMethod } from "./resolve.js";
5
+ export function planDeploy(manifest, sourceDir, detectedAgents, home) {
6
+ const method = getDeployMethod();
7
+ const actions = [];
8
+ for (const skill of manifest.skills) {
9
+ const source = path.resolve(sourceDir, skill.path);
10
+ for (const agentId of skill.agents) {
11
+ if (!detectedAgents.includes(agentId))
12
+ continue;
13
+ const agent = AGENT_REGISTRY.find((a) => a.id === agentId);
14
+ if (!agent)
15
+ continue;
16
+ const target = resolveAgentSkillPath(agent, skill.name, home);
17
+ actions.push({ skill: skill.name, agent: agentId, source, target, method });
18
+ }
19
+ }
20
+ return actions;
21
+ }
22
+ export function executeDeploy(actions, dryRun, verbose) {
23
+ let succeeded = 0;
24
+ const failed = [];
25
+ for (const action of actions) {
26
+ const label = `${action.skill} -> ${action.agent}`;
27
+ if (!existsSync(action.source)) {
28
+ const msg = `Source not found: ${action.source}`;
29
+ failed.push({ action, error: msg });
30
+ console.error(` \x1b[31m✗\x1b[0m ${label}: ${msg}`);
31
+ continue;
32
+ }
33
+ if (dryRun) {
34
+ console.log(` \x1b[36m○\x1b[0m ${label}`);
35
+ if (verbose) {
36
+ console.log(` ${action.method}: ${action.source} -> ${action.target}`);
37
+ }
38
+ succeeded++;
39
+ continue;
40
+ }
41
+ try {
42
+ removeExisting(action.target, verbose);
43
+ mkdirSync(path.dirname(action.target), { recursive: true });
44
+ if (action.method === "symlink") {
45
+ symlinkSync(action.source, action.target, "dir");
46
+ }
47
+ else {
48
+ cpSync(action.source, action.target, { recursive: true });
49
+ }
50
+ console.log(` \x1b[32m✓\x1b[0m ${label}`);
51
+ if (verbose) {
52
+ console.log(` ${action.method}: ${action.source} -> ${action.target}`);
53
+ }
54
+ succeeded++;
55
+ }
56
+ catch (err) {
57
+ const msg = err instanceof Error ? err.message : String(err);
58
+ failed.push({ action, error: msg });
59
+ console.error(` \x1b[31m✗\x1b[0m ${label}: ${msg}`);
60
+ }
61
+ }
62
+ return { succeeded, failed };
63
+ }
64
+ function removeExisting(targetPath, verbose) {
65
+ if (!existsSync(targetPath) && !isSymlink(targetPath))
66
+ return;
67
+ if (isSymlink(targetPath)) {
68
+ if (verbose) {
69
+ console.log(` removing existing symlink: ${targetPath}`);
70
+ }
71
+ unlinkSync(targetPath);
72
+ }
73
+ else {
74
+ if (verbose) {
75
+ console.log(` \x1b[33m!\x1b[0m replacing existing directory: ${targetPath}`);
76
+ }
77
+ rmSync(targetPath, { recursive: true });
78
+ }
79
+ }
80
+ function isSymlink(p) {
81
+ try {
82
+ return lstatSync(p).isSymbolicLink();
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
@@ -0,0 +1,2 @@
1
+ import type { AgentId } from "../types.ts";
2
+ export declare function detectInstalledAgents(home: string): AgentId[];
@@ -0,0 +1,33 @@
1
+ import { existsSync } from "node:fs";
2
+ import { execFileSync } from "node:child_process";
3
+ import { AGENT_REGISTRY } from "../config/agents.js";
4
+ import { resolveAgentDetectPath } from "./resolve.js";
5
+ export function detectInstalledAgents(home) {
6
+ const detected = [];
7
+ for (const agent of AGENT_REGISTRY) {
8
+ if (isAgentInstalled(agent, home)) {
9
+ detected.push(agent.id);
10
+ }
11
+ }
12
+ return detected;
13
+ }
14
+ function isAgentInstalled(agent, home) {
15
+ const detectPath = resolveAgentDetectPath(agent, home);
16
+ if (existsSync(detectPath)) {
17
+ return true;
18
+ }
19
+ if (agent.detectBinary) {
20
+ return isBinaryInPath(agent.detectBinary);
21
+ }
22
+ return false;
23
+ }
24
+ function isBinaryInPath(binary) {
25
+ const command = process.platform === "win32" ? "where.exe" : "which";
26
+ try {
27
+ execFileSync(command, [binary], { stdio: "ignore" });
28
+ return true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgentConfig } from "../types.ts";
2
+ export declare function resolveHome(): string;
3
+ export declare function getPlatformKey(): "posix" | "windows";
4
+ export declare function getDeployMethod(): "symlink" | "copy";
5
+ export declare function resolveAgentSkillPath(agent: AgentConfig, skillName: string, home: string): string;
6
+ export declare function resolveAgentDetectPath(agent: AgentConfig, home: string): string;
@@ -0,0 +1,38 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ export function resolveHome() {
4
+ if (process.platform === "win32") {
5
+ return os.homedir();
6
+ }
7
+ const sudoUser = process.env["SUDO_USER"];
8
+ if (sudoUser) {
9
+ const base = process.platform === "darwin" ? "/Users" : "/home";
10
+ return path.join(base, sudoUser);
11
+ }
12
+ return os.homedir();
13
+ }
14
+ export function getPlatformKey() {
15
+ return process.platform === "win32" ? "windows" : "posix";
16
+ }
17
+ export function getDeployMethod() {
18
+ return process.platform === "win32" ? "copy" : "symlink";
19
+ }
20
+ export function resolveAgentSkillPath(agent, skillName, home) {
21
+ const platform = getPlatformKey();
22
+ const template = agent.skills[platform];
23
+ return resolvePlaceholders(template, skillName, home);
24
+ }
25
+ export function resolveAgentDetectPath(agent, home) {
26
+ const platform = getPlatformKey();
27
+ const template = agent.detectPaths[platform];
28
+ return resolvePlaceholders(template, "", home);
29
+ }
30
+ function resolvePlaceholders(template, skillName, home) {
31
+ let result = template.replace("{home}", home);
32
+ result = result.replace("{name}", skillName);
33
+ if (result.includes("{appdata}")) {
34
+ const appdata = process.env["APPDATA"] ?? path.join(home, "AppData", "Roaming");
35
+ result = result.replace("{appdata}", appdata);
36
+ }
37
+ return result;
38
+ }
@@ -0,0 +1,6 @@
1
+ import type { AgentId, Manifest, RevertAction } from "../types.ts";
2
+ export declare function planRevert(manifest: Manifest, detectedAgents: AgentId[], home: string): RevertAction[];
3
+ export declare function executeRevert(actions: RevertAction[], dryRun: boolean, verbose: boolean): {
4
+ succeeded: number;
5
+ skipped: number;
6
+ };
@@ -0,0 +1,64 @@
1
+ import { existsSync, lstatSync, unlinkSync, rmSync } from "node:fs";
2
+ import { AGENT_REGISTRY } from "../config/agents.js";
3
+ import { resolveAgentSkillPath } from "./resolve.js";
4
+ export function planRevert(manifest, detectedAgents, home) {
5
+ const actions = [];
6
+ for (const skill of manifest.skills) {
7
+ for (const agentId of skill.agents) {
8
+ if (!detectedAgents.includes(agentId))
9
+ continue;
10
+ const agent = AGENT_REGISTRY.find((a) => a.id === agentId);
11
+ if (!agent)
12
+ continue;
13
+ const target = resolveAgentSkillPath(agent, skill.name, home);
14
+ actions.push({ skill: skill.name, agent: agentId, target });
15
+ }
16
+ }
17
+ return actions;
18
+ }
19
+ export function executeRevert(actions, dryRun, verbose) {
20
+ let succeeded = 0;
21
+ let skipped = 0;
22
+ for (const action of actions) {
23
+ const label = `${action.skill} -> ${action.agent}`;
24
+ if (!existsSync(action.target) && !isSymlink(action.target)) {
25
+ console.log(` \x1b[33m-\x1b[0m ${label} (not found, skipping)`);
26
+ skipped++;
27
+ continue;
28
+ }
29
+ if (dryRun) {
30
+ console.log(` \x1b[36m○\x1b[0m ${label}`);
31
+ if (verbose) {
32
+ console.log(` would remove: ${action.target}`);
33
+ }
34
+ succeeded++;
35
+ continue;
36
+ }
37
+ try {
38
+ if (isSymlink(action.target)) {
39
+ unlinkSync(action.target);
40
+ }
41
+ else {
42
+ rmSync(action.target, { recursive: true });
43
+ }
44
+ console.log(` \x1b[32m✓\x1b[0m ${label}`);
45
+ if (verbose) {
46
+ console.log(` removed: ${action.target}`);
47
+ }
48
+ succeeded++;
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ console.error(` \x1b[31m✗\x1b[0m ${label}: ${msg}`);
53
+ }
54
+ }
55
+ return { succeeded, skipped };
56
+ }
57
+ function isSymlink(p) {
58
+ try {
59
+ return lstatSync(p).isSymbolicLink();
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
@@ -0,0 +1,3 @@
1
+ export declare class UserError extends Error {
2
+ constructor(message: string);
3
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,6 @@
1
+ export class UserError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "UserError";
5
+ }
6
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { AGENT_IDS } from "./types.js";
4
+ import { loadManifest } from "./config/manifest.js";
5
+ import { AGENT_REGISTRY } from "./config/agents.js";
6
+ import { resolveHome } from "./core/resolve.js";
7
+ import { detectInstalledAgents } from "./core/detect.js";
8
+ import { planDeploy, executeDeploy } from "./core/deploy.js";
9
+ import { planRevert, executeRevert } from "./core/revert.js";
10
+ import { UserError } from "./errors.js";
11
+ const USAGE = `
12
+ inception-engine - Deploy AI agent skills
13
+
14
+ Usage:
15
+ inception-engine <directory> [options]
16
+ inception-engine revert <directory> [options]
17
+
18
+ Options:
19
+ --dry-run Show what would be done without doing it
20
+ --agents <list> Comma-separated list of agent IDs to target
21
+ --verbose Show detailed output
22
+ --debug Show full error stack traces
23
+ --help Show this help message
24
+
25
+ Supported agents:
26
+ ${AGENT_REGISTRY.map((a) => `${a.id} (${a.displayName})`).join(", ")}
27
+ `.trim();
28
+ function parseArgs(argv) {
29
+ const args = argv.slice(2);
30
+ if (args.length === 0 || args.includes("--help")) {
31
+ return { command: "help", directory: "", dryRun: false, agents: null, verbose: false, debug: false };
32
+ }
33
+ let command = "deploy";
34
+ let directory = "";
35
+ let dryRun = false;
36
+ let agents = null;
37
+ let verbose = false;
38
+ let debug = false;
39
+ let i = 0;
40
+ if (args[0] === "revert") {
41
+ command = "revert";
42
+ i = 1;
43
+ }
44
+ while (i < args.length) {
45
+ const arg = args[i];
46
+ if (arg === "--dry-run") {
47
+ dryRun = true;
48
+ }
49
+ else if (arg === "--verbose") {
50
+ verbose = true;
51
+ }
52
+ else if (arg === "--debug") {
53
+ debug = true;
54
+ }
55
+ else if (arg === "--agents") {
56
+ i++;
57
+ const next = args[i];
58
+ if (!next) {
59
+ console.error("--agents requires a comma-separated list");
60
+ process.exit(1);
61
+ }
62
+ const ids = next.split(",").map((s) => s.trim());
63
+ for (const id of ids) {
64
+ if (!AGENT_IDS.includes(id)) {
65
+ console.error(`Unknown agent: "${id}". Valid agents: ${AGENT_IDS.join(", ")}`);
66
+ process.exit(1);
67
+ }
68
+ }
69
+ agents = ids;
70
+ }
71
+ else if (arg.startsWith("--")) {
72
+ console.error(`Unknown option: ${arg}`);
73
+ process.exit(1);
74
+ }
75
+ else if (!directory) {
76
+ directory = arg;
77
+ }
78
+ else {
79
+ console.error(`Unexpected argument: ${arg}`);
80
+ process.exit(1);
81
+ }
82
+ i++;
83
+ }
84
+ if (!directory) {
85
+ console.error("Missing required <directory> argument");
86
+ process.exit(1);
87
+ }
88
+ return { command, directory: path.resolve(directory), dryRun, agents, verbose, debug };
89
+ }
90
+ function main() {
91
+ const options = parseArgs(process.argv);
92
+ if (options.command === "help") {
93
+ console.log(USAGE);
94
+ process.exit(0);
95
+ }
96
+ const manifest = loadManifest(options.directory);
97
+ const home = resolveHome();
98
+ let detectedAgents;
99
+ if (options.agents) {
100
+ detectedAgents = options.agents;
101
+ if (options.verbose) {
102
+ console.log(`Using specified agents: ${detectedAgents.join(", ")}`);
103
+ }
104
+ }
105
+ else {
106
+ detectedAgents = detectInstalledAgents(home);
107
+ if (detectedAgents.length === 0) {
108
+ console.log("No supported AI agents detected on this system.");
109
+ console.log(`Install one of: ${AGENT_REGISTRY.map((a) => a.displayName).join(", ")}`);
110
+ process.exit(0);
111
+ }
112
+ if (options.verbose) {
113
+ console.log(`Detected agents: ${detectedAgents.join(", ")}`);
114
+ }
115
+ }
116
+ const prefix = options.dryRun ? "\x1b[36m[dry-run]\x1b[0m " : "";
117
+ if (options.command === "deploy") {
118
+ const actions = planDeploy(manifest, options.directory, detectedAgents, home);
119
+ if (actions.length === 0) {
120
+ console.log("No skills to deploy for detected agents.");
121
+ return;
122
+ }
123
+ console.log(`${prefix}Deploying ${actions.length} skill(s):`);
124
+ const { succeeded, failed } = executeDeploy(actions, options.dryRun, options.verbose);
125
+ console.log();
126
+ if (failed.length > 0) {
127
+ console.log(`${succeeded} succeeded, ${failed.length} failed`);
128
+ process.exit(1);
129
+ }
130
+ else {
131
+ console.log(`${succeeded} skill(s) deployed${options.dryRun ? " (dry-run)" : ""}`);
132
+ }
133
+ }
134
+ else {
135
+ const actions = planRevert(manifest, detectedAgents, home);
136
+ if (actions.length === 0) {
137
+ console.log("No skills to revert for detected agents.");
138
+ return;
139
+ }
140
+ console.log(`${prefix}Reverting ${actions.length} skill(s):`);
141
+ const { succeeded, skipped } = executeRevert(actions, options.dryRun, options.verbose);
142
+ console.log();
143
+ const parts = [`${succeeded} removed`];
144
+ if (skipped > 0)
145
+ parts.push(`${skipped} skipped`);
146
+ console.log(`${parts.join(", ")}${options.dryRun ? " (dry-run)" : ""}`);
147
+ }
148
+ }
149
+ const debugMode = process.argv.includes("--debug");
150
+ try {
151
+ main();
152
+ }
153
+ catch (err) {
154
+ if (err instanceof UserError) {
155
+ console.error(`Error: ${err.message}`);
156
+ }
157
+ else {
158
+ console.error("Unexpected error. Run with --debug for details.");
159
+ }
160
+ if (debugMode) {
161
+ console.error(err);
162
+ }
163
+ process.exit(1);
164
+ }
@@ -0,0 +1,43 @@
1
+ export type AgentId = "claude-code" | "codex" | "gemini-cli" | "antigravity" | "opencode" | "github-copilot";
2
+ export declare const AGENT_IDS: readonly AgentId[];
3
+ export interface SkillEntry {
4
+ name: string;
5
+ path: string;
6
+ agents: AgentId[];
7
+ }
8
+ export interface Manifest {
9
+ skills: SkillEntry[];
10
+ mcpServers: unknown[];
11
+ agentRules: unknown[];
12
+ }
13
+ export interface AgentPaths {
14
+ posix: string;
15
+ windows: string;
16
+ }
17
+ export interface AgentConfig {
18
+ id: AgentId;
19
+ displayName: string;
20
+ skills: AgentPaths;
21
+ detectPaths: AgentPaths;
22
+ detectBinary: string | null;
23
+ }
24
+ export interface DeployAction {
25
+ skill: string;
26
+ agent: AgentId;
27
+ source: string;
28
+ target: string;
29
+ method: "symlink" | "copy";
30
+ }
31
+ export interface RevertAction {
32
+ skill: string;
33
+ agent: AgentId;
34
+ target: string;
35
+ }
36
+ export interface CliOptions {
37
+ command: "deploy" | "revert" | "help";
38
+ directory: string;
39
+ dryRun: boolean;
40
+ agents: AgentId[] | null;
41
+ verbose: boolean;
42
+ debug: boolean;
43
+ }
package/dist/types.js ADDED
@@ -0,0 +1,8 @@
1
+ export const AGENT_IDS = [
2
+ "claude-code",
3
+ "codex",
4
+ "gemini-cli",
5
+ "antigravity",
6
+ "opencode",
7
+ "github-copilot",
8
+ ];
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@kuznai/inception-engine",
3
+ "version": "0.1.0",
4
+ "description": "Deploy AI agent skills from a git repo to user home directories",
5
+ "license": "MIT",
6
+ "author": "Damian Piatkowski",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/KuzniAI/inception-engine.git"
10
+ },
11
+ "homepage": "https://github.com/KuzniAI/inception-engine#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/KuzniAI/inception-engine/issues"
14
+ },
15
+ "keywords": [
16
+ "cli",
17
+ "ai",
18
+ "agent",
19
+ "skills",
20
+ "claude",
21
+ "copilot",
22
+ "gemini",
23
+ "deployment",
24
+ "npx"
25
+ ],
26
+ "type": "module",
27
+ "bin": {
28
+ "inception-engine": "dist/index.js"
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "engines": {
34
+ "node": ">=23.6.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "typecheck": "tsc --noEmit",
39
+ "dev": "node src/index.ts",
40
+ "test": "node --test test/*.test.ts",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^25.5.0",
45
+ "typescript": "^5.8.0"
46
+ }
47
+ }