agent-installer 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/README.md +138 -0
- package/dist/chunk-JUX37ZAN.js +70 -0
- package/dist/chunk-JUX37ZAN.js.map +1 -0
- package/dist/cli.js +431 -0
- package/dist/cli.js.map +1 -0
- package/dist/source-D2B3SR46.js +7 -0
- package/dist/source-D2B3SR46.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# agent-installer
|
|
2
|
+
|
|
3
|
+
`agent-installer` is a local TypeScript CLI for installing agent artifacts from a repository into Codex and Claude Code.
|
|
4
|
+
|
|
5
|
+
It is built for repositories that keep:
|
|
6
|
+
|
|
7
|
+
- skills in `skills/`
|
|
8
|
+
- reusable prompt or command files in `prompts/` or `commands/`
|
|
9
|
+
|
|
10
|
+
The CLI scans that repository, shows what is available, and installs the selected items into the expected local directories.
|
|
11
|
+
|
|
12
|
+
## What It Supports
|
|
13
|
+
|
|
14
|
+
- Codex skills
|
|
15
|
+
- Claude Code skills
|
|
16
|
+
- Claude Code command-style prompts
|
|
17
|
+
|
|
18
|
+
Current scope:
|
|
19
|
+
|
|
20
|
+
- local directories only
|
|
21
|
+
- convention-based scanning
|
|
22
|
+
- no manifest file
|
|
23
|
+
|
|
24
|
+
## Expected Repository Layout
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
my-agent-repo/
|
|
28
|
+
skills/
|
|
29
|
+
review/
|
|
30
|
+
SKILL.md
|
|
31
|
+
prompts/
|
|
32
|
+
commit-message.md
|
|
33
|
+
commands/
|
|
34
|
+
release-notes.md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Rules:
|
|
38
|
+
|
|
39
|
+
- a skill is valid only if its directory contains `SKILL.md`
|
|
40
|
+
- prompt and command files must be Markdown files
|
|
41
|
+
- `prompts/foo.md` and `commands/foo.md` cannot both exist
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Install directly from GitHub:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add -g github:lbacik/agent-installer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or with npm:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install -g github:lbacik/agent-installer
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For local development, install dependencies:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pnpm install
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Build the CLI:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm build
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Run it during development:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pnpm dev
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
From inside a source repository:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
agent-installer
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Or point it at a repository explicitly:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
agent-installer /path/to/repo
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Commands
|
|
90
|
+
|
|
91
|
+
Interactive install:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
agent-installer [path]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Show scan results only:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
agent-installer scan [path]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Install or update everything found:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
agent-installer install [path] --all
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Remove managed entries by id:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
agent-installer uninstall skill:review prompt:commit-message
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
List installed managed entries:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
agent-installer list
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Typical Workflow
|
|
122
|
+
|
|
123
|
+
1. Change into a repository that contains `skills/`, `prompts/`, or `commands/`.
|
|
124
|
+
2. Run `agent-installer`.
|
|
125
|
+
3. Review the discovered items and their statuses.
|
|
126
|
+
4. Keep selected items installed, update changed ones, or remove managed items that should no longer remain installed.
|
|
127
|
+
|
|
128
|
+
## Development
|
|
129
|
+
|
|
130
|
+
Useful commands:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pnpm typecheck
|
|
134
|
+
pnpm test
|
|
135
|
+
pnpm build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`AGENTS.md` is the maintainer and agent-facing document for repository rules, internal structure, and implementation invariants.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/source.ts
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
async function pathExists(targetPath) {
|
|
5
|
+
try {
|
|
6
|
+
await fs.access(targetPath);
|
|
7
|
+
return true;
|
|
8
|
+
} catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
async function scanSourceRepository(inputPath) {
|
|
13
|
+
const sourceRoot = await fs.realpath(inputPath);
|
|
14
|
+
const artifacts = [];
|
|
15
|
+
const skillsDir = path.join(sourceRoot, "skills");
|
|
16
|
+
if (await pathExists(skillsDir)) {
|
|
17
|
+
const dirents = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
18
|
+
for (const dirent of dirents) {
|
|
19
|
+
if (!dirent.isDirectory()) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const skillPath = path.join(skillsDir, dirent.name);
|
|
23
|
+
const entrypoint = path.join(skillPath, "SKILL.md");
|
|
24
|
+
if (await pathExists(entrypoint)) {
|
|
25
|
+
artifacts.push({
|
|
26
|
+
kind: "skill",
|
|
27
|
+
name: dirent.name,
|
|
28
|
+
sourceRoot,
|
|
29
|
+
sourcePath: skillPath,
|
|
30
|
+
relativeSourcePath: path.relative(sourceRoot, skillPath)
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const promptCandidates = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const folder of ["prompts", "commands"]) {
|
|
37
|
+
const folderPath = path.join(sourceRoot, folder);
|
|
38
|
+
if (!await pathExists(folderPath)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const dirents = await fs.readdir(folderPath, { withFileTypes: true });
|
|
42
|
+
for (const dirent of dirents) {
|
|
43
|
+
if (!dirent.isFile() || path.extname(dirent.name) !== ".md") {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const name = path.basename(dirent.name, ".md");
|
|
47
|
+
if (promptCandidates.has(name)) {
|
|
48
|
+
const first = promptCandidates.get(name);
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Duplicate prompt or command name "${name}" found at "${first?.relativeSourcePath}" and "${path.join(folder, dirent.name)}".`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const sourcePath = path.join(folderPath, dirent.name);
|
|
54
|
+
promptCandidates.set(name, {
|
|
55
|
+
kind: "prompt",
|
|
56
|
+
name,
|
|
57
|
+
sourceRoot,
|
|
58
|
+
sourcePath,
|
|
59
|
+
relativeSourcePath: path.relative(sourceRoot, sourcePath)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
artifacts.push(...promptCandidates.values());
|
|
64
|
+
return artifacts.sort((left, right) => left.kind.localeCompare(right.kind) || left.name.localeCompare(right.name));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
scanSourceRepository
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=chunk-JUX37ZAN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/source.ts"],"sourcesContent":["import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { DiscoveredArtifact } from \"./types.js\";\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.access(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function scanSourceRepository(inputPath: string): Promise<DiscoveredArtifact[]> {\n const sourceRoot = await fs.realpath(inputPath);\n const artifacts: DiscoveredArtifact[] = [];\n\n const skillsDir = path.join(sourceRoot, \"skills\");\n if (await pathExists(skillsDir)) {\n const dirents = await fs.readdir(skillsDir, { withFileTypes: true });\n for (const dirent of dirents) {\n if (!dirent.isDirectory()) {\n continue;\n }\n\n const skillPath = path.join(skillsDir, dirent.name);\n const entrypoint = path.join(skillPath, \"SKILL.md\");\n if (await pathExists(entrypoint)) {\n artifacts.push({\n kind: \"skill\",\n name: dirent.name,\n sourceRoot,\n sourcePath: skillPath,\n relativeSourcePath: path.relative(sourceRoot, skillPath)\n });\n }\n }\n }\n\n const promptCandidates = new Map<string, DiscoveredArtifact>();\n for (const folder of [\"prompts\", \"commands\"]) {\n const folderPath = path.join(sourceRoot, folder);\n if (!(await pathExists(folderPath))) {\n continue;\n }\n\n const dirents = await fs.readdir(folderPath, { withFileTypes: true });\n for (const dirent of dirents) {\n if (!dirent.isFile() || path.extname(dirent.name) !== \".md\") {\n continue;\n }\n\n const name = path.basename(dirent.name, \".md\");\n if (promptCandidates.has(name)) {\n const first = promptCandidates.get(name);\n throw new Error(\n `Duplicate prompt or command name \"${name}\" found at \"${first?.relativeSourcePath}\" and \"${path.join(folder, dirent.name)}\".`\n );\n }\n\n const sourcePath = path.join(folderPath, dirent.name);\n promptCandidates.set(name, {\n kind: \"prompt\",\n name,\n sourceRoot,\n sourcePath,\n relativeSourcePath: path.relative(sourceRoot, sourcePath)\n });\n }\n }\n\n artifacts.push(...promptCandidates.values());\n return artifacts.sort((left, right) => left.kind.localeCompare(right.kind) || left.name.localeCompare(right.name));\n}\n"],"mappings":";AAAA,SAAS,YAAY,UAAU;AAC/B,OAAO,UAAU;AAGjB,eAAe,WAAW,YAAsC;AAC9D,MAAI;AACF,UAAM,GAAG,OAAO,UAAU;AAC1B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,qBAAqB,WAAkD;AAC3F,QAAM,aAAa,MAAM,GAAG,SAAS,SAAS;AAC9C,QAAM,YAAkC,CAAC;AAEzC,QAAM,YAAY,KAAK,KAAK,YAAY,QAAQ;AAChD,MAAI,MAAM,WAAW,SAAS,GAAG;AAC/B,UAAM,UAAU,MAAM,GAAG,QAAQ,WAAW,EAAE,eAAe,KAAK,CAAC;AACnE,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAO,YAAY,GAAG;AACzB;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,KAAK,WAAW,OAAO,IAAI;AAClD,YAAM,aAAa,KAAK,KAAK,WAAW,UAAU;AAClD,UAAI,MAAM,WAAW,UAAU,GAAG;AAChC,kBAAU,KAAK;AAAA,UACb,MAAM;AAAA,UACN,MAAM,OAAO;AAAA,UACb;AAAA,UACA,YAAY;AAAA,UACZ,oBAAoB,KAAK,SAAS,YAAY,SAAS;AAAA,QACzD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB,oBAAI,IAAgC;AAC7D,aAAW,UAAU,CAAC,WAAW,UAAU,GAAG;AAC5C,UAAM,aAAa,KAAK,KAAK,YAAY,MAAM;AAC/C,QAAI,CAAE,MAAM,WAAW,UAAU,GAAI;AACnC;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,GAAG,QAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AACpE,eAAW,UAAU,SAAS;AAC5B,UAAI,CAAC,OAAO,OAAO,KAAK,KAAK,QAAQ,OAAO,IAAI,MAAM,OAAO;AAC3D;AAAA,MACF;AAEA,YAAM,OAAO,KAAK,SAAS,OAAO,MAAM,KAAK;AAC7C,UAAI,iBAAiB,IAAI,IAAI,GAAG;AAC9B,cAAM,QAAQ,iBAAiB,IAAI,IAAI;AACvC,cAAM,IAAI;AAAA,UACR,qCAAqC,IAAI,eAAe,OAAO,kBAAkB,UAAU,KAAK,KAAK,QAAQ,OAAO,IAAI,CAAC;AAAA,QAC3H;AAAA,MACF;AAEA,YAAM,aAAa,KAAK,KAAK,YAAY,OAAO,IAAI;AACpD,uBAAiB,IAAI,MAAM;AAAA,QACzB,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAoB,KAAK,SAAS,YAAY,UAAU;AAAA,MAC1D,CAAC;AAAA,IACH;AAAA,EACF;AAEA,YAAU,KAAK,GAAG,iBAAiB,OAAO,CAAC;AAC3C,SAAO,UAAU,KAAK,CAAC,MAAM,UAAU,KAAK,KAAK,cAAc,MAAM,IAAI,KAAK,KAAK,KAAK,cAAc,MAAM,IAAI,CAAC;AACnH;","names":[]}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
scanSourceRepository
|
|
4
|
+
} from "./chunk-JUX37ZAN.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
import path4 from "path";
|
|
9
|
+
|
|
10
|
+
// src/install.ts
|
|
11
|
+
import { promises as fs3 } from "fs";
|
|
12
|
+
import path3 from "path";
|
|
13
|
+
|
|
14
|
+
// src/hash.ts
|
|
15
|
+
import { createHash } from "crypto";
|
|
16
|
+
import { promises as fs } from "fs";
|
|
17
|
+
import path from "path";
|
|
18
|
+
async function collectDirectoryEntries(dir) {
|
|
19
|
+
const dirents = await fs.readdir(dir, { withFileTypes: true });
|
|
20
|
+
const entries = await Promise.all(
|
|
21
|
+
dirents.filter((dirent) => dirent.name !== ".agent-installer.json").map(async (dirent) => {
|
|
22
|
+
const fullPath = path.join(dir, dirent.name);
|
|
23
|
+
if (dirent.isDirectory()) {
|
|
24
|
+
return collectDirectoryEntries(fullPath);
|
|
25
|
+
}
|
|
26
|
+
if (dirent.isFile()) {
|
|
27
|
+
return [fullPath];
|
|
28
|
+
}
|
|
29
|
+
return [];
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
return entries.flat().sort();
|
|
33
|
+
}
|
|
34
|
+
async function hashArtifact(kind, targetPath) {
|
|
35
|
+
const hash = createHash("sha256");
|
|
36
|
+
if (kind === "prompt") {
|
|
37
|
+
const content = await fs.readFile(targetPath);
|
|
38
|
+
hash.update(content);
|
|
39
|
+
return hash.digest("hex");
|
|
40
|
+
}
|
|
41
|
+
const files = await collectDirectoryEntries(targetPath);
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
hash.update(path.relative(targetPath, file));
|
|
44
|
+
hash.update("\n");
|
|
45
|
+
hash.update(await fs.readFile(file));
|
|
46
|
+
}
|
|
47
|
+
return hash.digest("hex");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/paths.ts
|
|
51
|
+
import path2 from "path";
|
|
52
|
+
function resolveTargetPaths(home = process.env.HOME ?? path2.join(process.cwd(), ".home")) {
|
|
53
|
+
const agentsRoot = path2.join(home, ".agents");
|
|
54
|
+
const claudeRoot = path2.join(home, ".claude");
|
|
55
|
+
const stateDir = path2.join(agentsRoot, "agent-installer");
|
|
56
|
+
return {
|
|
57
|
+
agentsRoot,
|
|
58
|
+
agentsSkillsDir: path2.join(agentsRoot, "skills"),
|
|
59
|
+
agentsPromptsDir: path2.join(agentsRoot, "prompts"),
|
|
60
|
+
claudeRoot,
|
|
61
|
+
claudeSkillsDir: path2.join(claudeRoot, "skills"),
|
|
62
|
+
claudeCommandsDir: path2.join(claudeRoot, "commands"),
|
|
63
|
+
stateDir,
|
|
64
|
+
stateFile: path2.join(stateDir, "state.json")
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function artifactId(kind, name) {
|
|
68
|
+
return `${kind}:${name}`;
|
|
69
|
+
}
|
|
70
|
+
function getBasePath(paths, artifact) {
|
|
71
|
+
if (artifact.kind === "skill") {
|
|
72
|
+
return path2.join(paths.agentsSkillsDir, artifact.name);
|
|
73
|
+
}
|
|
74
|
+
return path2.join(paths.agentsPromptsDir, `${artifact.name}.md`);
|
|
75
|
+
}
|
|
76
|
+
function getExposurePath(paths, artifact) {
|
|
77
|
+
if (artifact.kind === "skill") {
|
|
78
|
+
return path2.join(paths.claudeSkillsDir, artifact.name);
|
|
79
|
+
}
|
|
80
|
+
return path2.join(paths.claudeCommandsDir, `${artifact.name}.md`);
|
|
81
|
+
}
|
|
82
|
+
function getMarkerPath(basePath, kind) {
|
|
83
|
+
return kind === "skill" ? path2.join(basePath, ".agent-installer.json") : `${basePath}.agent-installer.json`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/state.ts
|
|
87
|
+
import { promises as fs2 } from "fs";
|
|
88
|
+
import { z } from "zod";
|
|
89
|
+
var managedEntrySchema = z.object({
|
|
90
|
+
id: z.string(),
|
|
91
|
+
kind: z.enum(["skill", "prompt"]),
|
|
92
|
+
name: z.string(),
|
|
93
|
+
sourceRoot: z.string(),
|
|
94
|
+
relativeSourcePath: z.string(),
|
|
95
|
+
basePath: z.string(),
|
|
96
|
+
exposurePath: z.string(),
|
|
97
|
+
sourceHash: z.string(),
|
|
98
|
+
installedHash: z.string(),
|
|
99
|
+
installedAt: z.string()
|
|
100
|
+
});
|
|
101
|
+
var stateSchema = z.object({
|
|
102
|
+
version: z.literal(1),
|
|
103
|
+
entries: z.array(managedEntrySchema)
|
|
104
|
+
});
|
|
105
|
+
async function loadState(paths) {
|
|
106
|
+
try {
|
|
107
|
+
const raw = await fs2.readFile(paths.stateFile, "utf8");
|
|
108
|
+
return stateSchema.parse(JSON.parse(raw));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error.code === "ENOENT") {
|
|
111
|
+
return { version: 1, entries: [] };
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function saveState(paths, state) {
|
|
117
|
+
await fs2.mkdir(paths.stateDir, { recursive: true });
|
|
118
|
+
await fs2.writeFile(paths.stateFile, `${JSON.stringify(state, null, 2)}
|
|
119
|
+
`, "utf8");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/install.ts
|
|
123
|
+
async function pathExists(targetPath) {
|
|
124
|
+
try {
|
|
125
|
+
await fs3.lstat(targetPath);
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function ensureParentDir(targetPath) {
|
|
132
|
+
await fs3.mkdir(path3.dirname(targetPath), { recursive: true });
|
|
133
|
+
}
|
|
134
|
+
async function removePath(targetPath) {
|
|
135
|
+
await fs3.rm(targetPath, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
async function copyArtifact(artifact, basePath) {
|
|
138
|
+
await removePath(basePath);
|
|
139
|
+
await ensureParentDir(basePath);
|
|
140
|
+
if (artifact.kind === "skill") {
|
|
141
|
+
await fs3.cp(artifact.sourcePath, basePath, { recursive: true });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await fs3.copyFile(artifact.sourcePath, basePath);
|
|
145
|
+
}
|
|
146
|
+
async function readSymlinkTarget(targetPath) {
|
|
147
|
+
try {
|
|
148
|
+
return await fs3.readlink(targetPath);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function writeMarker(entry) {
|
|
154
|
+
const markerPath = getMarkerPath(entry.basePath, entry.kind);
|
|
155
|
+
await fs3.writeFile(markerPath, `${JSON.stringify({ id: entry.id, installedAt: entry.installedAt }, null, 2)}
|
|
156
|
+
`, "utf8");
|
|
157
|
+
}
|
|
158
|
+
function buildManagedEntry(artifact, paths, sourceHash, installedHash) {
|
|
159
|
+
return {
|
|
160
|
+
id: artifactId(artifact.kind, artifact.name),
|
|
161
|
+
kind: artifact.kind,
|
|
162
|
+
name: artifact.name,
|
|
163
|
+
sourceRoot: artifact.sourceRoot,
|
|
164
|
+
relativeSourcePath: artifact.relativeSourcePath,
|
|
165
|
+
basePath: getBasePath(paths, artifact),
|
|
166
|
+
exposurePath: getExposurePath(paths, artifact),
|
|
167
|
+
sourceHash,
|
|
168
|
+
installedHash,
|
|
169
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async function collectArtifactStates(sourceArtifacts, home, sourceRoot) {
|
|
173
|
+
const paths = resolveTargetPaths(home);
|
|
174
|
+
const state = await loadState(paths);
|
|
175
|
+
const sourceIds = new Set(sourceArtifacts.map((artifact) => artifactId(artifact.kind, artifact.name)));
|
|
176
|
+
const entriesById = new Map(state.entries.map((entry) => [entry.id, entry]));
|
|
177
|
+
const states = [];
|
|
178
|
+
for (const artifact of sourceArtifacts) {
|
|
179
|
+
const id = artifactId(artifact.kind, artifact.name);
|
|
180
|
+
const sourceHash = await hashArtifact(artifact.kind, artifact.sourcePath);
|
|
181
|
+
const managedEntry = entriesById.get(id) ?? null;
|
|
182
|
+
const basePath = getBasePath(paths, artifact);
|
|
183
|
+
const exposurePath = getExposurePath(paths, artifact);
|
|
184
|
+
let status = "new";
|
|
185
|
+
let installedHash = null;
|
|
186
|
+
let conflictReason;
|
|
187
|
+
const baseExists = await pathExists(basePath);
|
|
188
|
+
const exposureExists = await pathExists(exposurePath);
|
|
189
|
+
if (!baseExists && !exposureExists) {
|
|
190
|
+
status = "new";
|
|
191
|
+
} else if (managedEntry && managedEntry.sourceRoot === artifact.sourceRoot) {
|
|
192
|
+
if (baseExists) {
|
|
193
|
+
installedHash = await hashArtifact(artifact.kind, basePath);
|
|
194
|
+
}
|
|
195
|
+
const symlinkTarget = exposureExists ? await readSymlinkTarget(exposurePath) : null;
|
|
196
|
+
const expectedTarget = basePath;
|
|
197
|
+
const exposureValid = !exposureExists || symlinkTarget === expectedTarget;
|
|
198
|
+
if (!exposureValid) {
|
|
199
|
+
status = "conflict";
|
|
200
|
+
conflictReason = `Exposure path already exists and does not point to "${expectedTarget}".`;
|
|
201
|
+
} else if (installedHash === sourceHash) {
|
|
202
|
+
status = "installed-same";
|
|
203
|
+
} else {
|
|
204
|
+
status = "installed-different";
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
status = "conflict";
|
|
208
|
+
conflictReason = "A target path already exists but is not managed by this installer.";
|
|
209
|
+
if (baseExists) {
|
|
210
|
+
installedHash = await hashArtifact(artifact.kind, basePath);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const nextState = {
|
|
214
|
+
artifact,
|
|
215
|
+
id,
|
|
216
|
+
basePath,
|
|
217
|
+
exposurePath,
|
|
218
|
+
sourceHash,
|
|
219
|
+
installedHash,
|
|
220
|
+
status,
|
|
221
|
+
managedEntry
|
|
222
|
+
};
|
|
223
|
+
if (conflictReason !== void 0) {
|
|
224
|
+
nextState.conflictReason = conflictReason;
|
|
225
|
+
}
|
|
226
|
+
states.push(nextState);
|
|
227
|
+
}
|
|
228
|
+
const scopedSourceRoot = sourceRoot ? await fs3.realpath(sourceRoot) : sourceArtifacts[0]?.sourceRoot;
|
|
229
|
+
const removed = state.entries.filter((entry) => scopedSourceRoot !== void 0 && entry.sourceRoot === scopedSourceRoot).filter((entry) => !sourceIds.has(entry.id)).map((entry) => ({
|
|
230
|
+
id: entry.id,
|
|
231
|
+
name: entry.name,
|
|
232
|
+
kind: entry.kind,
|
|
233
|
+
basePath: entry.basePath,
|
|
234
|
+
exposurePath: entry.exposurePath,
|
|
235
|
+
status: "source-missing",
|
|
236
|
+
managedEntry: entry
|
|
237
|
+
}));
|
|
238
|
+
return { states, removed };
|
|
239
|
+
}
|
|
240
|
+
async function installArtifacts(states, home) {
|
|
241
|
+
const paths = resolveTargetPaths(home);
|
|
242
|
+
const state = await loadState(paths);
|
|
243
|
+
const entries = new Map(state.entries.map((entry) => [entry.id, entry]));
|
|
244
|
+
const installed = [];
|
|
245
|
+
for (const current of states) {
|
|
246
|
+
if (current.status === "conflict") {
|
|
247
|
+
throw new Error(`Cannot install ${current.id}: ${current.conflictReason}`);
|
|
248
|
+
}
|
|
249
|
+
await copyArtifact(current.artifact, current.basePath);
|
|
250
|
+
await ensureParentDir(current.exposurePath);
|
|
251
|
+
await removePath(current.exposurePath);
|
|
252
|
+
await fs3.symlink(current.basePath, current.exposurePath);
|
|
253
|
+
const installedHash = await hashArtifact(current.artifact.kind, current.basePath);
|
|
254
|
+
const entry = buildManagedEntry(current.artifact, paths, current.sourceHash, installedHash);
|
|
255
|
+
await writeMarker(entry);
|
|
256
|
+
entries.set(entry.id, entry);
|
|
257
|
+
installed.push(entry);
|
|
258
|
+
}
|
|
259
|
+
await saveState(paths, { version: 1, entries: [...entries.values()].sort((left, right) => left.id.localeCompare(right.id)) });
|
|
260
|
+
return installed;
|
|
261
|
+
}
|
|
262
|
+
async function removeArtifacts(ids, home) {
|
|
263
|
+
const paths = resolveTargetPaths(home);
|
|
264
|
+
const state = await loadState(paths);
|
|
265
|
+
const entries = new Map(state.entries.map((entry) => [entry.id, entry]));
|
|
266
|
+
const removed = [];
|
|
267
|
+
for (const id of ids) {
|
|
268
|
+
const entry = entries.get(id);
|
|
269
|
+
if (!entry) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
await removePath(entry.exposurePath);
|
|
273
|
+
await removePath(entry.basePath);
|
|
274
|
+
await removePath(getMarkerPath(entry.basePath, entry.kind));
|
|
275
|
+
entries.delete(id);
|
|
276
|
+
removed.push(entry);
|
|
277
|
+
}
|
|
278
|
+
await saveState(paths, { version: 1, entries: [...entries.values()].sort((left, right) => left.id.localeCompare(right.id)) });
|
|
279
|
+
return removed;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/format.ts
|
|
283
|
+
import pc from "picocolors";
|
|
284
|
+
function colorizeStatus(status) {
|
|
285
|
+
switch (status) {
|
|
286
|
+
case "new":
|
|
287
|
+
return pc.cyan(status);
|
|
288
|
+
case "installed-same":
|
|
289
|
+
return pc.green(status);
|
|
290
|
+
case "installed-different":
|
|
291
|
+
return pc.yellow(status);
|
|
292
|
+
case "source-missing":
|
|
293
|
+
return pc.magenta(status);
|
|
294
|
+
case "conflict":
|
|
295
|
+
return pc.red(status);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function formatArtifactLine(state) {
|
|
299
|
+
const detail = state.status === "conflict" ? ` (${state.conflictReason ?? "conflict"})` : state.status === "installed-different" ? " (update available)" : "";
|
|
300
|
+
return `${colorizeStatus(state.status)} ${state.id} <- ${state.artifact.relativeSourcePath}${detail}`;
|
|
301
|
+
}
|
|
302
|
+
function formatRemovedLine(state) {
|
|
303
|
+
return `${colorizeStatus(state.status)} ${state.id} <- missing from source`;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/interactive.ts
|
|
307
|
+
import { checkbox, confirm } from "@inquirer/prompts";
|
|
308
|
+
async function promptForSelections(states, removed) {
|
|
309
|
+
const currentSelections = new Set(
|
|
310
|
+
await checkbox({
|
|
311
|
+
message: "Select artifacts that should remain installed",
|
|
312
|
+
choices: states.map((state) => ({
|
|
313
|
+
name: `${state.id} [${state.status}]`,
|
|
314
|
+
value: state.id,
|
|
315
|
+
checked: state.status === "installed-same" || state.status === "installed-different",
|
|
316
|
+
disabled: state.status === "conflict" ? state.conflictReason ?? "conflict" : false
|
|
317
|
+
}))
|
|
318
|
+
})
|
|
319
|
+
);
|
|
320
|
+
const installIds = states.filter((state) => currentSelections.has(state.id)).filter((state) => state.status === "new" || state.status === "installed-different").map((state) => state.id);
|
|
321
|
+
const removeIds = states.filter((state) => !currentSelections.has(state.id)).filter((state) => state.status === "installed-same" || state.status === "installed-different").map((state) => state.id);
|
|
322
|
+
if (removed.length === 0) {
|
|
323
|
+
return { installIds, removeIds };
|
|
324
|
+
}
|
|
325
|
+
const cleanup = await confirm({
|
|
326
|
+
message: `Remove ${removed.length} managed artifact(s) that are no longer present in the source repository?`,
|
|
327
|
+
default: false
|
|
328
|
+
});
|
|
329
|
+
if (!cleanup) {
|
|
330
|
+
return { installIds, removeIds };
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
installIds,
|
|
334
|
+
removeIds: [...removeIds, ...removed.map((entry) => entry.id)]
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/cli.ts
|
|
339
|
+
function resolveSourcePath(inputPath) {
|
|
340
|
+
return path4.resolve(inputPath ?? process.cwd());
|
|
341
|
+
}
|
|
342
|
+
function printLines(lines) {
|
|
343
|
+
for (const line of lines) {
|
|
344
|
+
console.log(line);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function toStateMap(states) {
|
|
348
|
+
return new Map(states.map((state) => [state.id, state]));
|
|
349
|
+
}
|
|
350
|
+
async function scanWithState(inputPath, home) {
|
|
351
|
+
const sourcePath = resolveSourcePath(inputPath);
|
|
352
|
+
const artifacts = await scanSourceRepository(sourcePath);
|
|
353
|
+
return collectArtifactStates(artifacts, home, sourcePath);
|
|
354
|
+
}
|
|
355
|
+
async function runInteractive(inputPath) {
|
|
356
|
+
const { states, removed } = await scanWithState(inputPath);
|
|
357
|
+
printLines(states.map(formatArtifactLine));
|
|
358
|
+
if (removed.length > 0) {
|
|
359
|
+
printLines(removed.map(formatRemovedLine));
|
|
360
|
+
}
|
|
361
|
+
const selection = await promptForSelections(states, removed);
|
|
362
|
+
const stateMap = toStateMap(states);
|
|
363
|
+
const installTargets = selection.installIds.map((id) => {
|
|
364
|
+
const state = stateMap.get(id);
|
|
365
|
+
if (!state) {
|
|
366
|
+
throw new Error(`Unknown artifact id: ${id}`);
|
|
367
|
+
}
|
|
368
|
+
return state;
|
|
369
|
+
});
|
|
370
|
+
if (selection.installIds.length > 0) {
|
|
371
|
+
await installArtifacts(installTargets);
|
|
372
|
+
}
|
|
373
|
+
if (selection.removeIds.length > 0) {
|
|
374
|
+
await removeArtifacts(selection.removeIds);
|
|
375
|
+
}
|
|
376
|
+
const summary = [];
|
|
377
|
+
if (selection.installIds.length > 0) {
|
|
378
|
+
summary.push(`installed/updated ${selection.installIds.length}`);
|
|
379
|
+
}
|
|
380
|
+
if (selection.removeIds.length > 0) {
|
|
381
|
+
summary.push(`removed ${selection.removeIds.length}`);
|
|
382
|
+
}
|
|
383
|
+
console.log(summary.length > 0 ? summary.join(", ") : "No changes applied.");
|
|
384
|
+
}
|
|
385
|
+
function createProgram() {
|
|
386
|
+
const program = new Command();
|
|
387
|
+
program.name("agent-installer").description("Install Codex skills and Claude Code skills and commands from a local repository.").argument("[path]", "Source repository to scan", process.cwd()).action(async (inputPath) => {
|
|
388
|
+
await runInteractive(inputPath);
|
|
389
|
+
});
|
|
390
|
+
program.command("scan").argument("[path]", "Source repository to scan", process.cwd()).action(async (inputPath) => {
|
|
391
|
+
const { states, removed } = await scanWithState(inputPath);
|
|
392
|
+
printLines(states.map(formatArtifactLine));
|
|
393
|
+
if (removed.length > 0) {
|
|
394
|
+
printLines(removed.map(formatRemovedLine));
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
program.command("install").description("Install or update all discovered artifacts from the source repository.").argument("[path]", "Source repository to scan", process.cwd()).option("--all", "Install all discovered artifacts").action(async (inputPath, options) => {
|
|
398
|
+
if (!options.all) {
|
|
399
|
+
throw new Error("Use --all for non-interactive installation.");
|
|
400
|
+
}
|
|
401
|
+
const { states } = await scanWithState(inputPath);
|
|
402
|
+
const installable = states.filter((state) => state.status === "new" || state.status === "installed-different");
|
|
403
|
+
await installArtifacts(installable);
|
|
404
|
+
console.log(`installed/updated ${installable.length}`);
|
|
405
|
+
});
|
|
406
|
+
program.command("uninstall").description("Remove managed artifacts by id, for example skill:review or prompt:commit-message.").argument("<ids...>", "Managed artifact ids").action(async (ids) => {
|
|
407
|
+
const removed = await removeArtifacts(ids);
|
|
408
|
+
console.log(`removed ${removed.length}`);
|
|
409
|
+
});
|
|
410
|
+
program.command("list").description("List currently managed artifacts from the base store.").action(async () => {
|
|
411
|
+
const state = await loadState(resolveTargetPaths());
|
|
412
|
+
if (state.entries.length === 0) {
|
|
413
|
+
console.log("No managed artifacts.");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
for (const entry of state.entries) {
|
|
417
|
+
console.log(`${entry.id} -> ${entry.basePath}`);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return program;
|
|
421
|
+
}
|
|
422
|
+
async function main() {
|
|
423
|
+
const program = createProgram();
|
|
424
|
+
await program.parseAsync(process.argv);
|
|
425
|
+
}
|
|
426
|
+
main().catch((error) => {
|
|
427
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
428
|
+
console.error(message);
|
|
429
|
+
process.exitCode = 1;
|
|
430
|
+
});
|
|
431
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/install.ts","../src/hash.ts","../src/paths.ts","../src/state.ts","../src/format.ts","../src/interactive.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from \"commander\";\nimport path from \"node:path\";\nimport { collectArtifactStates, installArtifacts, removeArtifacts } from \"./install.js\";\nimport { formatArtifactLine, formatRemovedLine } from \"./format.js\";\nimport { promptForSelections } from \"./interactive.js\";\nimport { resolveTargetPaths } from \"./paths.js\";\nimport { scanSourceRepository } from \"./source.js\";\nimport { loadState } from \"./state.js\";\nimport { ArtifactState } from \"./types.js\";\n\nfunction resolveSourcePath(inputPath?: string): string {\n return path.resolve(inputPath ?? process.cwd());\n}\n\nfunction printLines(lines: string[]): void {\n for (const line of lines) {\n console.log(line);\n }\n}\n\nfunction toStateMap(states: ArtifactState[]): Map<string, ArtifactState> {\n return new Map(states.map((state) => [state.id, state]));\n}\n\nasync function scanWithState(inputPath?: string, home?: string) {\n const sourcePath = resolveSourcePath(inputPath);\n const artifacts = await scanSourceRepository(sourcePath);\n return collectArtifactStates(artifacts, home, sourcePath);\n}\n\nasync function runInteractive(inputPath?: string): Promise<void> {\n const { states, removed } = await scanWithState(inputPath);\n printLines(states.map(formatArtifactLine));\n if (removed.length > 0) {\n printLines(removed.map(formatRemovedLine));\n }\n\n const selection = await promptForSelections(states, removed);\n const stateMap = toStateMap(states);\n const installTargets = selection.installIds.map((id) => {\n const state = stateMap.get(id);\n if (!state) {\n throw new Error(`Unknown artifact id: ${id}`);\n }\n\n return state;\n });\n\n if (selection.installIds.length > 0) {\n await installArtifacts(installTargets);\n }\n\n if (selection.removeIds.length > 0) {\n await removeArtifacts(selection.removeIds);\n }\n\n const summary = [];\n if (selection.installIds.length > 0) {\n summary.push(`installed/updated ${selection.installIds.length}`);\n }\n if (selection.removeIds.length > 0) {\n summary.push(`removed ${selection.removeIds.length}`);\n }\n\n console.log(summary.length > 0 ? summary.join(\", \") : \"No changes applied.\");\n}\n\nfunction createProgram(): Command {\n const program = new Command();\n program\n .name(\"agent-installer\")\n .description(\"Install Codex skills and Claude Code skills and commands from a local repository.\")\n .argument(\"[path]\", \"Source repository to scan\", process.cwd())\n .action(async (inputPath) => {\n await runInteractive(inputPath);\n });\n\n program\n .command(\"scan\")\n .argument(\"[path]\", \"Source repository to scan\", process.cwd())\n .action(async (inputPath) => {\n const { states, removed } = await scanWithState(inputPath);\n printLines(states.map(formatArtifactLine));\n if (removed.length > 0) {\n printLines(removed.map(formatRemovedLine));\n }\n });\n\n program\n .command(\"install\")\n .description(\"Install or update all discovered artifacts from the source repository.\")\n .argument(\"[path]\", \"Source repository to scan\", process.cwd())\n .option(\"--all\", \"Install all discovered artifacts\")\n .action(async (inputPath, options: { all?: boolean }) => {\n if (!options.all) {\n throw new Error(\"Use --all for non-interactive installation.\");\n }\n\n const { states } = await scanWithState(inputPath);\n const installable = states.filter((state) => state.status === \"new\" || state.status === \"installed-different\");\n await installArtifacts(installable);\n console.log(`installed/updated ${installable.length}`);\n });\n\n program\n .command(\"uninstall\")\n .description(\"Remove managed artifacts by id, for example skill:review or prompt:commit-message.\")\n .argument(\"<ids...>\", \"Managed artifact ids\")\n .action(async (ids: string[]) => {\n const removed = await removeArtifacts(ids);\n console.log(`removed ${removed.length}`);\n });\n\n program\n .command(\"list\")\n .description(\"List currently managed artifacts from the base store.\")\n .action(async () => {\n const state = await loadState(resolveTargetPaths());\n if (state.entries.length === 0) {\n console.log(\"No managed artifacts.\");\n return;\n }\n\n for (const entry of state.entries) {\n console.log(`${entry.id} -> ${entry.basePath}`);\n }\n });\n\n return program;\n}\n\nasync function main(): Promise<void> {\n const program = createProgram();\n await program.parseAsync(process.argv);\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(message);\n process.exitCode = 1;\n});\n","import { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { hashArtifact } from \"./hash.js\";\nimport { artifactId, getBasePath, getExposurePath, getMarkerPath, resolveTargetPaths, TargetPaths } from \"./paths.js\";\nimport { loadState, saveState } from \"./state.js\";\nimport { ArtifactState, DiscoveredArtifact, ManagedEntry, RemovedArtifactState } from \"./types.js\";\n\nasync function pathExists(targetPath: string): Promise<boolean> {\n try {\n await fs.lstat(targetPath);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function ensureParentDir(targetPath: string): Promise<void> {\n await fs.mkdir(path.dirname(targetPath), { recursive: true });\n}\n\nasync function removePath(targetPath: string): Promise<void> {\n await fs.rm(targetPath, { recursive: true, force: true });\n}\n\nasync function copyArtifact(artifact: DiscoveredArtifact, basePath: string): Promise<void> {\n await removePath(basePath);\n await ensureParentDir(basePath);\n\n if (artifact.kind === \"skill\") {\n await fs.cp(artifact.sourcePath, basePath, { recursive: true });\n return;\n }\n\n await fs.copyFile(artifact.sourcePath, basePath);\n}\n\nasync function readSymlinkTarget(targetPath: string): Promise<string | null> {\n try {\n return await fs.readlink(targetPath);\n } catch {\n return null;\n }\n}\n\nasync function writeMarker(entry: ManagedEntry): Promise<void> {\n const markerPath = getMarkerPath(entry.basePath, entry.kind);\n await fs.writeFile(markerPath, `${JSON.stringify({ id: entry.id, installedAt: entry.installedAt }, null, 2)}\\n`, \"utf8\");\n}\n\nfunction buildManagedEntry(\n artifact: DiscoveredArtifact,\n paths: TargetPaths,\n sourceHash: string,\n installedHash: string\n): ManagedEntry {\n return {\n id: artifactId(artifact.kind, artifact.name),\n kind: artifact.kind,\n name: artifact.name,\n sourceRoot: artifact.sourceRoot,\n relativeSourcePath: artifact.relativeSourcePath,\n basePath: getBasePath(paths, artifact),\n exposurePath: getExposurePath(paths, artifact),\n sourceHash,\n installedHash,\n installedAt: new Date().toISOString()\n };\n}\n\nexport async function collectArtifactStates(\n sourceArtifacts: DiscoveredArtifact[],\n home?: string,\n sourceRoot?: string\n): Promise<{\n states: ArtifactState[];\n removed: RemovedArtifactState[];\n}> {\n const paths = resolveTargetPaths(home);\n const state = await loadState(paths);\n const sourceIds = new Set(sourceArtifacts.map((artifact) => artifactId(artifact.kind, artifact.name)));\n const entriesById = new Map(state.entries.map((entry) => [entry.id, entry]));\n const states: ArtifactState[] = [];\n\n for (const artifact of sourceArtifacts) {\n const id = artifactId(artifact.kind, artifact.name);\n const sourceHash = await hashArtifact(artifact.kind, artifact.sourcePath);\n const managedEntry = entriesById.get(id) ?? null;\n const basePath = getBasePath(paths, artifact);\n const exposurePath = getExposurePath(paths, artifact);\n\n let status: ArtifactState[\"status\"] = \"new\";\n let installedHash: string | null = null;\n let conflictReason: string | undefined;\n\n const baseExists = await pathExists(basePath);\n const exposureExists = await pathExists(exposurePath);\n\n if (!baseExists && !exposureExists) {\n status = \"new\";\n } else if (managedEntry && managedEntry.sourceRoot === artifact.sourceRoot) {\n if (baseExists) {\n installedHash = await hashArtifact(artifact.kind, basePath);\n }\n\n const symlinkTarget = exposureExists ? await readSymlinkTarget(exposurePath) : null;\n const expectedTarget = basePath;\n const exposureValid = !exposureExists || symlinkTarget === expectedTarget;\n\n if (!exposureValid) {\n status = \"conflict\";\n conflictReason = `Exposure path already exists and does not point to \"${expectedTarget}\".`;\n } else if (installedHash === sourceHash) {\n status = \"installed-same\";\n } else {\n status = \"installed-different\";\n }\n } else {\n status = \"conflict\";\n conflictReason = \"A target path already exists but is not managed by this installer.\";\n if (baseExists) {\n installedHash = await hashArtifact(artifact.kind, basePath);\n }\n }\n\n const nextState: ArtifactState = {\n artifact,\n id,\n basePath,\n exposurePath,\n sourceHash,\n installedHash,\n status,\n managedEntry\n };\n\n if (conflictReason !== undefined) {\n nextState.conflictReason = conflictReason;\n }\n\n states.push(nextState);\n }\n\n const scopedSourceRoot = sourceRoot ? await fs.realpath(sourceRoot) : sourceArtifacts[0]?.sourceRoot;\n const removed = state.entries\n .filter((entry) => scopedSourceRoot !== undefined && entry.sourceRoot === scopedSourceRoot)\n .filter((entry) => !sourceIds.has(entry.id))\n .map((entry) => ({\n id: entry.id,\n name: entry.name,\n kind: entry.kind,\n basePath: entry.basePath,\n exposurePath: entry.exposurePath,\n status: \"source-missing\" as const,\n managedEntry: entry\n }));\n\n return { states, removed };\n}\n\nexport async function installArtifacts(states: ArtifactState[], home?: string): Promise<ManagedEntry[]> {\n const paths = resolveTargetPaths(home);\n const state = await loadState(paths);\n const entries = new Map(state.entries.map((entry) => [entry.id, entry]));\n const installed: ManagedEntry[] = [];\n\n for (const current of states) {\n if (current.status === \"conflict\") {\n throw new Error(`Cannot install ${current.id}: ${current.conflictReason}`);\n }\n\n await copyArtifact(current.artifact, current.basePath);\n await ensureParentDir(current.exposurePath);\n await removePath(current.exposurePath);\n await fs.symlink(current.basePath, current.exposurePath);\n\n const installedHash = await hashArtifact(current.artifact.kind, current.basePath);\n const entry = buildManagedEntry(current.artifact, paths, current.sourceHash, installedHash);\n await writeMarker(entry);\n entries.set(entry.id, entry);\n installed.push(entry);\n }\n\n await saveState(paths, { version: 1, entries: [...entries.values()].sort((left, right) => left.id.localeCompare(right.id)) });\n return installed;\n}\n\nexport async function removeArtifacts(ids: string[], home?: string): Promise<ManagedEntry[]> {\n const paths = resolveTargetPaths(home);\n const state = await loadState(paths);\n const entries = new Map(state.entries.map((entry) => [entry.id, entry]));\n const removed: ManagedEntry[] = [];\n\n for (const id of ids) {\n const entry = entries.get(id);\n if (!entry) {\n continue;\n }\n\n await removePath(entry.exposurePath);\n await removePath(entry.basePath);\n await removePath(getMarkerPath(entry.basePath, entry.kind));\n entries.delete(id);\n removed.push(entry);\n }\n\n await saveState(paths, { version: 1, entries: [...entries.values()].sort((left, right) => left.id.localeCompare(right.id)) });\n return removed;\n}\n\nexport async function installAllFromSource(sourcePath: string, home?: string): Promise<ArtifactState[]> {\n const { scanSourceRepository } = await import(\"./source.js\");\n const artifacts = await scanSourceRepository(sourcePath);\n const { states } = await collectArtifactStates(artifacts, home, sourcePath);\n const installable = states.filter((state) => state.status === \"new\" || state.status === \"installed-different\");\n await installArtifacts(installable, home);\n return states;\n}\n","import { createHash } from \"node:crypto\";\nimport { promises as fs } from \"node:fs\";\nimport path from \"node:path\";\nimport { ArtifactKind } from \"./types.js\";\n\nasync function collectDirectoryEntries(dir: string): Promise<string[]> {\n const dirents = await fs.readdir(dir, { withFileTypes: true });\n const entries = await Promise.all(\n dirents\n .filter((dirent) => dirent.name !== \".agent-installer.json\")\n .map(async (dirent) => {\n const fullPath = path.join(dir, dirent.name);\n if (dirent.isDirectory()) {\n return collectDirectoryEntries(fullPath);\n }\n\n if (dirent.isFile()) {\n return [fullPath];\n }\n\n return [];\n })\n );\n\n return entries.flat().sort();\n}\n\nexport async function hashArtifact(kind: ArtifactKind, targetPath: string): Promise<string> {\n const hash = createHash(\"sha256\");\n\n if (kind === \"prompt\") {\n const content = await fs.readFile(targetPath);\n hash.update(content);\n return hash.digest(\"hex\");\n }\n\n const files = await collectDirectoryEntries(targetPath);\n\n for (const file of files) {\n hash.update(path.relative(targetPath, file));\n hash.update(\"\\n\");\n hash.update(await fs.readFile(file));\n }\n\n return hash.digest(\"hex\");\n}\n","import path from \"node:path\";\nimport { ArtifactKind, DiscoveredArtifact } from \"./types.js\";\n\nexport interface TargetPaths {\n agentsRoot: string;\n agentsSkillsDir: string;\n agentsPromptsDir: string;\n claudeRoot: string;\n claudeSkillsDir: string;\n claudeCommandsDir: string;\n stateDir: string;\n stateFile: string;\n}\n\nexport function resolveTargetPaths(home = process.env.HOME ?? path.join(process.cwd(), \".home\")): TargetPaths {\n const agentsRoot = path.join(home, \".agents\");\n const claudeRoot = path.join(home, \".claude\");\n const stateDir = path.join(agentsRoot, \"agent-installer\");\n\n return {\n agentsRoot,\n agentsSkillsDir: path.join(agentsRoot, \"skills\"),\n agentsPromptsDir: path.join(agentsRoot, \"prompts\"),\n claudeRoot,\n claudeSkillsDir: path.join(claudeRoot, \"skills\"),\n claudeCommandsDir: path.join(claudeRoot, \"commands\"),\n stateDir,\n stateFile: path.join(stateDir, \"state.json\")\n };\n}\n\nexport function artifactId(kind: ArtifactKind, name: string): string {\n return `${kind}:${name}`;\n}\n\nexport function getBasePath(paths: TargetPaths, artifact: Pick<DiscoveredArtifact, \"kind\" | \"name\">): string {\n if (artifact.kind === \"skill\") {\n return path.join(paths.agentsSkillsDir, artifact.name);\n }\n\n return path.join(paths.agentsPromptsDir, `${artifact.name}.md`);\n}\n\nexport function getExposurePath(paths: TargetPaths, artifact: Pick<DiscoveredArtifact, \"kind\" | \"name\">): string {\n if (artifact.kind === \"skill\") {\n return path.join(paths.claudeSkillsDir, artifact.name);\n }\n\n return path.join(paths.claudeCommandsDir, `${artifact.name}.md`);\n}\n\nexport function getMarkerPath(basePath: string, kind: ArtifactKind): string {\n return kind === \"skill\" ? path.join(basePath, \".agent-installer.json\") : `${basePath}.agent-installer.json`;\n}\n","import { promises as fs } from \"node:fs\";\nimport { z } from \"zod\";\nimport { ManagedEntry } from \"./types.js\";\nimport { TargetPaths } from \"./paths.js\";\n\nconst managedEntrySchema = z.object({\n id: z.string(),\n kind: z.enum([\"skill\", \"prompt\"]),\n name: z.string(),\n sourceRoot: z.string(),\n relativeSourcePath: z.string(),\n basePath: z.string(),\n exposurePath: z.string(),\n sourceHash: z.string(),\n installedHash: z.string(),\n installedAt: z.string()\n});\n\nconst stateSchema = z.object({\n version: z.literal(1),\n entries: z.array(managedEntrySchema)\n});\n\nexport interface InstallerState {\n version: 1;\n entries: ManagedEntry[];\n}\n\nexport async function loadState(paths: TargetPaths): Promise<InstallerState> {\n try {\n const raw = await fs.readFile(paths.stateFile, \"utf8\");\n return stateSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { version: 1, entries: [] };\n }\n\n throw error;\n }\n}\n\nexport async function saveState(paths: TargetPaths, state: InstallerState): Promise<void> {\n await fs.mkdir(paths.stateDir, { recursive: true });\n await fs.writeFile(paths.stateFile, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n","import pc from \"picocolors\";\nimport { ArtifactState, RemovedArtifactState } from \"./types.js\";\n\nfunction colorizeStatus(status: ArtifactState[\"status\"] | RemovedArtifactState[\"status\"]): string {\n switch (status) {\n case \"new\":\n return pc.cyan(status);\n case \"installed-same\":\n return pc.green(status);\n case \"installed-different\":\n return pc.yellow(status);\n case \"source-missing\":\n return pc.magenta(status);\n case \"conflict\":\n return pc.red(status);\n }\n}\n\nexport function formatArtifactLine(state: ArtifactState): string {\n const detail =\n state.status === \"conflict\"\n ? ` (${state.conflictReason ?? \"conflict\"})`\n : state.status === \"installed-different\"\n ? \" (update available)\"\n : \"\";\n\n return `${colorizeStatus(state.status)} ${state.id} <- ${state.artifact.relativeSourcePath}${detail}`;\n}\n\nexport function formatRemovedLine(state: RemovedArtifactState): string {\n return `${colorizeStatus(state.status)} ${state.id} <- missing from source`;\n}\n","import { checkbox, confirm } from \"@inquirer/prompts\";\nimport { ArtifactState, RemovedArtifactState } from \"./types.js\";\n\nexport interface InteractiveSelection {\n installIds: string[];\n removeIds: string[];\n}\n\nexport async function promptForSelections(\n states: ArtifactState[],\n removed: RemovedArtifactState[]\n): Promise<InteractiveSelection> {\n const currentSelections = new Set(\n await checkbox({\n message: \"Select artifacts that should remain installed\",\n choices: states.map((state) => ({\n name: `${state.id} [${state.status}]`,\n value: state.id,\n checked: state.status === \"installed-same\" || state.status === \"installed-different\",\n disabled: state.status === \"conflict\" ? state.conflictReason ?? \"conflict\" : false\n }))\n })\n );\n\n const installIds = states\n .filter((state) => currentSelections.has(state.id))\n .filter((state) => state.status === \"new\" || state.status === \"installed-different\")\n .map((state) => state.id);\n\n const removeIds = states\n .filter((state) => !currentSelections.has(state.id))\n .filter((state) => state.status === \"installed-same\" || state.status === \"installed-different\")\n .map((state) => state.id);\n\n if (removed.length === 0) {\n return { installIds, removeIds };\n }\n\n const cleanup = await confirm({\n message: `Remove ${removed.length} managed artifact(s) that are no longer present in the source repository?`,\n default: false\n });\n\n if (!cleanup) {\n return { installIds, removeIds };\n }\n\n return {\n installIds,\n removeIds: [...removeIds, ...removed.map((entry) => entry.id)]\n };\n}\n"],"mappings":"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","names":["path","fs","path","path","fs","fs","path","path"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-installer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install Codex skills and Claude Code skills and commands from a local repository.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"bin": {
|
|
11
|
+
"agent-installer": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsup",
|
|
15
|
+
"prepare": "pnpm build",
|
|
16
|
+
"dev": "tsx src/cli.ts",
|
|
17
|
+
"typecheck": "tsc --noEmit",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/lbacik/agent-installer.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/lbacik/agent-installer",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/lbacik/agent-installer/issues"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@inquirer/prompts": "^7.8.4",
|
|
34
|
+
"commander": "^14.0.0",
|
|
35
|
+
"picocolors": "^1.1.1",
|
|
36
|
+
"zod": "^4.0.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^24.0.13",
|
|
40
|
+
"tsup": "^8.5.0",
|
|
41
|
+
"tsx": "^4.20.3",
|
|
42
|
+
"typescript": "^5.8.3",
|
|
43
|
+
"vitest": "^3.2.4"
|
|
44
|
+
},
|
|
45
|
+
"packageManager": "pnpm@10.32.1"
|
|
46
|
+
}
|