ide-agents 0.1.1 → 0.3.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 +22 -17
- package/dist/adapters/create.d.ts +9 -0
- package/dist/adapters/create.js +33 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +16 -0
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.js +1 -0
- package/dist/apply.js +38 -11
- package/dist/config.js +48 -13
- package/dist/git.d.ts +1 -0
- package/dist/git.js +41 -1
- package/dist/gitignore.d.ts +5 -0
- package/dist/gitignore.js +105 -0
- package/dist/ides.d.ts +5 -0
- package/dist/ides.js +52 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +10 -0
- package/dist/scan.js +58 -29
- package/dist/server.js +111 -6
- package/dist/targets.d.ts +2 -1
- package/dist/targets.js +24 -6
- package/dist/template.d.ts +12 -0
- package/dist/template.js +86 -0
- package/dist/types.d.ts +16 -2
- package/dist/types.js +1 -1
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/package.json +17 -9
- package/template/.agents/AGENTS.md +51 -0
- package/template/.claude/CLAUDE.md +37 -0
- package/template/.cursor/rules/agents.mdc +72 -0
- package/template/.cursor/rules/repo-structure.mdc +46 -0
- package/template/.cursor/rules/scripts.mdc +101 -0
- package/template/README.md +60 -0
- package/template/agents/oracle.md +38 -0
- package/template/skills/hello/SKILL.md +43 -0
- package/template/skills/hello/scripts/now.mjs +49 -0
- package/web/dist/assets/index-BSBPsDYY.css +1 -0
- package/web/dist/assets/index-C-SOo38e.js +51 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-D7KhBlEO.js +0 -124
- package/web/dist/assets/index-JihkyerF.css +0 -1
package/dist/scan.js
CHANGED
|
@@ -15,7 +15,7 @@ function parseAllowedScope(data) {
|
|
|
15
15
|
if (scope === "global" || scope === "project" || scope === "any") {
|
|
16
16
|
return scope;
|
|
17
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return "any";
|
|
19
19
|
}
|
|
20
20
|
function parseAgentDescription(content, fallbackName) {
|
|
21
21
|
const parsed = matter(content);
|
|
@@ -37,45 +37,74 @@ function parseAgentDescription(content, fallbackName) {
|
|
|
37
37
|
}
|
|
38
38
|
return fallbackName;
|
|
39
39
|
}
|
|
40
|
-
async function
|
|
41
|
-
const
|
|
42
|
-
|
|
40
|
+
async function parseSkillDirectory(skillDir, dirName) {
|
|
41
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
42
|
+
const hasSkillMd = await pathExists(skillMdPath);
|
|
43
|
+
let name = dirName;
|
|
44
|
+
let description = "";
|
|
45
|
+
let allowedScope = "any";
|
|
46
|
+
if (hasSkillMd) {
|
|
47
|
+
const raw = await readFile(skillMdPath, "utf8");
|
|
48
|
+
const parsed = matter(raw);
|
|
49
|
+
if (typeof parsed.data.name === "string" && parsed.data.name.trim()) {
|
|
50
|
+
name = parsed.data.name.trim();
|
|
51
|
+
}
|
|
52
|
+
if (typeof parsed.data.description === "string") {
|
|
53
|
+
description = parsed.data.description.trim();
|
|
54
|
+
}
|
|
55
|
+
allowedScope = parseAllowedScope(parsed.data);
|
|
56
|
+
}
|
|
57
|
+
return { id: dirName, name, description, hasSkillMd, allowedScope };
|
|
58
|
+
}
|
|
59
|
+
function toSkillArtifact(sourcePath, parsed) {
|
|
60
|
+
return {
|
|
61
|
+
id: parsed.id,
|
|
62
|
+
kind: "skill",
|
|
63
|
+
sourcePath,
|
|
64
|
+
name: parsed.name,
|
|
65
|
+
description: parsed.description,
|
|
66
|
+
hasSkillMd: parsed.hasSkillMd,
|
|
67
|
+
allowedScope: parsed.allowedScope,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async function scanSkillsInDirectory(parentDir, sourcePathPrefix) {
|
|
71
|
+
if (!(await pathExists(parentDir))) {
|
|
43
72
|
return [];
|
|
44
73
|
}
|
|
45
|
-
const entries = await readdir(
|
|
74
|
+
const entries = await readdir(parentDir, { withFileTypes: true });
|
|
46
75
|
const artifacts = [];
|
|
47
76
|
for (const entry of entries) {
|
|
48
77
|
if (!entry.isDirectory())
|
|
49
78
|
continue;
|
|
50
|
-
|
|
79
|
+
if (entry.name.startsWith("."))
|
|
80
|
+
continue;
|
|
81
|
+
const skillDir = path.join(parentDir, entry.name);
|
|
51
82
|
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
let description = "";
|
|
55
|
-
let allowedScope = null;
|
|
56
|
-
if (hasSkillMd) {
|
|
57
|
-
const raw = await readFile(skillMdPath, "utf8");
|
|
58
|
-
const parsed = matter(raw);
|
|
59
|
-
if (typeof parsed.data.name === "string" && parsed.data.name.trim()) {
|
|
60
|
-
name = parsed.data.name.trim();
|
|
61
|
-
}
|
|
62
|
-
if (typeof parsed.data.description === "string") {
|
|
63
|
-
description = parsed.data.description.trim();
|
|
64
|
-
}
|
|
65
|
-
allowedScope = parseAllowedScope(parsed.data);
|
|
83
|
+
if (!(await pathExists(skillMdPath))) {
|
|
84
|
+
continue;
|
|
66
85
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
description,
|
|
73
|
-
hasSkillMd,
|
|
74
|
-
allowedScope,
|
|
75
|
-
});
|
|
86
|
+
const parsed = await parseSkillDirectory(skillDir, entry.name);
|
|
87
|
+
const relativeSource = sourcePathPrefix
|
|
88
|
+
? path.join(sourcePathPrefix, entry.name)
|
|
89
|
+
: entry.name;
|
|
90
|
+
artifacts.push(toSkillArtifact(relativeSource, parsed));
|
|
76
91
|
}
|
|
77
92
|
return artifacts.sort((a, b) => a.id.localeCompare(b.id));
|
|
78
93
|
}
|
|
94
|
+
async function scanSkillsNested(repoPath) {
|
|
95
|
+
return scanSkillsInDirectory(path.join(repoPath, "skills"), "skills");
|
|
96
|
+
}
|
|
97
|
+
/** Repos like bluriesophos/cursorskills: `<repo>/<skill-name>/SKILL.md` at root. */
|
|
98
|
+
async function scanSkillsFlat(repoPath) {
|
|
99
|
+
return scanSkillsInDirectory(repoPath, "");
|
|
100
|
+
}
|
|
101
|
+
async function scanSkills(repoPath) {
|
|
102
|
+
const nested = await scanSkillsNested(repoPath);
|
|
103
|
+
if (nested.length > 0) {
|
|
104
|
+
return nested;
|
|
105
|
+
}
|
|
106
|
+
return scanSkillsFlat(repoPath);
|
|
107
|
+
}
|
|
79
108
|
async function scanAgents(repoPath) {
|
|
80
109
|
const agentsDir = path.join(repoPath, "agents");
|
|
81
110
|
if (!(await pathExists(agentsDir))) {
|
package/dist/server.js
CHANGED
|
@@ -3,11 +3,13 @@ import fastifyStatic from "@fastify/static";
|
|
|
3
3
|
import { v4 as uuidv4 } from "uuid";
|
|
4
4
|
import { addRecentProject, readConfig, writeConfig, } from "./config.js";
|
|
5
5
|
import { cloneRepo, fetchRepo, getGitStatusWithoutFetch, pullRepo, } from "./git.js";
|
|
6
|
+
import { bootstrapEmptyRepo } from "./template.js";
|
|
6
7
|
import { applyInstallations } from "./apply.js";
|
|
7
8
|
import { scanRepoArtifacts } from "./scan.js";
|
|
8
9
|
import { getArtifactTargets } from "./targets.js";
|
|
9
|
-
import { getIdeAgentsHome, getRepoPath, getWebDistDir, slugFromUrl, } from "./paths.js";
|
|
10
|
-
import {
|
|
10
|
+
import { expandUserPath, getIdeAgentsHome, getRepoPath, getWebDistDir, slugFromUrl, } from "./paths.js";
|
|
11
|
+
import { defaultAdapterFromIdes, getDefaultIdes } from "./ides.js";
|
|
12
|
+
import { PACKAGE_VERSION as VERSION } from "./version.js";
|
|
11
13
|
export async function createServer(options) {
|
|
12
14
|
const app = Fastify({ logger: false });
|
|
13
15
|
app.setErrorHandler((error, _request, reply) => {
|
|
@@ -19,21 +21,70 @@ export async function createServer(options) {
|
|
|
19
21
|
const config = await readConfig();
|
|
20
22
|
return {
|
|
21
23
|
home: getIdeAgentsHome(),
|
|
22
|
-
adapter: config.adapter,
|
|
23
24
|
port: options.port,
|
|
24
25
|
version: VERSION,
|
|
25
26
|
defaultProjectPath: options.launchCwd ?? null,
|
|
27
|
+
ides: config.ides,
|
|
26
28
|
};
|
|
27
29
|
});
|
|
30
|
+
app.get("/api/settings", async () => {
|
|
31
|
+
const config = await readConfig();
|
|
32
|
+
return {
|
|
33
|
+
ides: config.ides,
|
|
34
|
+
defaults: getDefaultIdes(),
|
|
35
|
+
home: getIdeAgentsHome(),
|
|
36
|
+
version: VERSION,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
app.put("/api/settings", async (request, reply) => {
|
|
40
|
+
const { ides } = request.body ?? {};
|
|
41
|
+
if (!ides) {
|
|
42
|
+
return reply.status(400).send({ error: "ides is required" });
|
|
43
|
+
}
|
|
44
|
+
for (const key of ["cursor", "claude", "codex"]) {
|
|
45
|
+
const entry = ides[key];
|
|
46
|
+
if (!entry || typeof entry.configPath !== "string" || !entry.configPath.trim()) {
|
|
47
|
+
return reply.status(400).send({ error: `Invalid config for ${key}` });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const normalized = {
|
|
51
|
+
cursor: {
|
|
52
|
+
enabled: Boolean(ides.cursor.enabled),
|
|
53
|
+
configPath: expandUserPath(ides.cursor.configPath),
|
|
54
|
+
},
|
|
55
|
+
claude: {
|
|
56
|
+
enabled: Boolean(ides.claude.enabled),
|
|
57
|
+
configPath: expandUserPath(ides.claude.configPath),
|
|
58
|
+
},
|
|
59
|
+
codex: {
|
|
60
|
+
enabled: Boolean(ides.codex.enabled),
|
|
61
|
+
configPath: expandUserPath(ides.codex.configPath),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
if (!normalized.cursor.enabled &&
|
|
65
|
+
!normalized.claude.enabled &&
|
|
66
|
+
!normalized.codex.enabled) {
|
|
67
|
+
return reply.status(400).send({ error: "At least one IDE must be enabled" });
|
|
68
|
+
}
|
|
69
|
+
const config = await readConfig();
|
|
70
|
+
config.ides = normalized;
|
|
71
|
+
config.adapter = defaultAdapterFromIdes(config.ides);
|
|
72
|
+
await writeConfig(config);
|
|
73
|
+
return { ides: config.ides };
|
|
74
|
+
});
|
|
28
75
|
app.get("/api/repos", async () => {
|
|
29
76
|
const config = await readConfig();
|
|
30
77
|
const repos = [];
|
|
31
78
|
for (const repo of config.repos) {
|
|
32
79
|
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
80
|
+
const repoPath = getRepoPath(repo.slug);
|
|
81
|
+
const artifacts = await scanRepoArtifacts(repoPath);
|
|
33
82
|
repos.push({
|
|
34
83
|
...repo,
|
|
35
|
-
localPath:
|
|
84
|
+
localPath: repoPath,
|
|
36
85
|
git,
|
|
86
|
+
skillCount: artifacts.filter((a) => a.kind === "skill").length,
|
|
87
|
+
agentCount: artifacts.filter((a) => a.kind === "agent").length,
|
|
37
88
|
});
|
|
38
89
|
}
|
|
39
90
|
return { repos };
|
|
@@ -49,14 +100,16 @@ export async function createServer(options) {
|
|
|
49
100
|
if (config.repos.some((r) => r.id === repoId)) {
|
|
50
101
|
return reply.status(409).send({ error: `Repository id already exists: ${repoId}` });
|
|
51
102
|
}
|
|
103
|
+
let repoPath;
|
|
52
104
|
try {
|
|
53
|
-
await cloneRepo(url.trim(), slug, ref);
|
|
105
|
+
repoPath = await cloneRepo(url.trim(), slug, ref);
|
|
54
106
|
}
|
|
55
107
|
catch (err) {
|
|
56
108
|
return reply.status(400).send({
|
|
57
109
|
error: err instanceof Error ? err.message : String(err),
|
|
58
110
|
});
|
|
59
111
|
}
|
|
112
|
+
const bootstrap = await bootstrapEmptyRepo(repoPath, ref);
|
|
60
113
|
const repo = { id: repoId, url: url.trim(), ref, slug };
|
|
61
114
|
config.repos.push(repo);
|
|
62
115
|
await writeConfig(config);
|
|
@@ -66,7 +119,10 @@ export async function createServer(options) {
|
|
|
66
119
|
...repo,
|
|
67
120
|
localPath: getRepoPath(slug),
|
|
68
121
|
git,
|
|
122
|
+
skillCount: bootstrap.skillCount,
|
|
123
|
+
agentCount: bootstrap.agentCount,
|
|
69
124
|
},
|
|
125
|
+
bootstrap,
|
|
70
126
|
};
|
|
71
127
|
});
|
|
72
128
|
app.delete("/api/repos/:id", async (request, reply) => {
|
|
@@ -80,6 +136,38 @@ export async function createServer(options) {
|
|
|
80
136
|
await writeConfig(config);
|
|
81
137
|
return { ok: true };
|
|
82
138
|
});
|
|
139
|
+
app.post("/api/repos/:id/bootstrap", async (request, reply) => {
|
|
140
|
+
const config = await readConfig();
|
|
141
|
+
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
142
|
+
if (!repo) {
|
|
143
|
+
return reply.status(404).send({ error: "Repository not found" });
|
|
144
|
+
}
|
|
145
|
+
const repoPath = getRepoPath(repo.slug);
|
|
146
|
+
try {
|
|
147
|
+
const bootstrap = await bootstrapEmptyRepo(repoPath, repo.ref);
|
|
148
|
+
if (!bootstrap.applied) {
|
|
149
|
+
return reply.status(409).send({
|
|
150
|
+
error: "Repository is not empty — bootstrap skipped",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
154
|
+
return {
|
|
155
|
+
repo: {
|
|
156
|
+
...repo,
|
|
157
|
+
localPath: repoPath,
|
|
158
|
+
git,
|
|
159
|
+
skillCount: bootstrap.skillCount,
|
|
160
|
+
agentCount: bootstrap.agentCount,
|
|
161
|
+
},
|
|
162
|
+
bootstrap,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
return reply.status(400).send({
|
|
167
|
+
error: err instanceof Error ? err.message : String(err),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
83
171
|
app.post("/api/repos/:id/fetch", async (request, reply) => {
|
|
84
172
|
const config = await readConfig();
|
|
85
173
|
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
@@ -97,6 +185,23 @@ export async function createServer(options) {
|
|
|
97
185
|
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
98
186
|
return { git };
|
|
99
187
|
});
|
|
188
|
+
app.post("/api/repos/:id/check-updates", async (request, reply) => {
|
|
189
|
+
const config = await readConfig();
|
|
190
|
+
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
191
|
+
if (!repo) {
|
|
192
|
+
return reply.status(404).send({ error: "Repository not found" });
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
await fetchRepo(repo.slug);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return reply.status(400).send({
|
|
199
|
+
error: err instanceof Error ? err.message : String(err),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
203
|
+
return { git };
|
|
204
|
+
});
|
|
100
205
|
app.post("/api/repos/:id/pull", async (request, reply) => {
|
|
101
206
|
const config = await readConfig();
|
|
102
207
|
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
@@ -124,7 +229,7 @@ export async function createServer(options) {
|
|
|
124
229
|
const scanned = await scanRepoArtifacts(getRepoPath(repo.slug));
|
|
125
230
|
const artifacts = await Promise.all(scanned.map(async (artifact) => ({
|
|
126
231
|
...artifact,
|
|
127
|
-
targets: await getArtifactTargets(artifact, projectPath, config
|
|
232
|
+
targets: await getArtifactTargets(artifact, projectPath, config),
|
|
128
233
|
})));
|
|
129
234
|
return { artifacts };
|
|
130
235
|
});
|
package/dist/targets.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
import type { IdeAgentsConfig } from "./types.js";
|
|
1
2
|
import type { Artifact, ArtifactTargets, Installation, TargetStatus } from "./types.js";
|
|
2
3
|
export declare function getTargetStatus(targetPath: string): Promise<TargetStatus>;
|
|
3
|
-
export declare function getArtifactTargets(artifact: Pick<Artifact, "kind" | "id">, projectPath: string | null,
|
|
4
|
+
export declare function getArtifactTargets(artifact: Pick<Artifact, "kind" | "id">, projectPath: string | null, config: IdeAgentsConfig): Promise<ArtifactTargets>;
|
|
4
5
|
export declare function installationStub(artifact: Pick<Artifact, "kind" | "id" | "sourcePath">, projectPath: string | null): Pick<Installation, "kind" | "targetName" | "sourcePath" | "projectPath">;
|
package/dist/targets.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstat } from "node:fs/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { getEnabledAdapters } from "./adapters/index.js";
|
|
3
3
|
export async function getTargetStatus(targetPath) {
|
|
4
4
|
try {
|
|
5
5
|
const stats = await lstat(targetPath);
|
|
@@ -18,17 +18,35 @@ export async function getTargetStatus(targetPath) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
function mergeTargetStatus(current, next) {
|
|
22
|
+
return {
|
|
23
|
+
exists: current.exists || next.exists,
|
|
24
|
+
isSymlink: current.isSymlink || next.isSymlink,
|
|
25
|
+
blocked: current.blocked || next.blocked,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function getArtifactTargets(artifact, projectPath, config) {
|
|
29
|
+
const adapters = getEnabledAdapters(config);
|
|
23
30
|
const installation = {
|
|
24
31
|
kind: artifact.kind,
|
|
25
32
|
targetName: artifact.id,
|
|
26
33
|
projectPath,
|
|
27
34
|
};
|
|
28
|
-
|
|
35
|
+
let global = {
|
|
36
|
+
exists: false,
|
|
37
|
+
isSymlink: false,
|
|
38
|
+
blocked: false,
|
|
39
|
+
};
|
|
29
40
|
let project = null;
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
for (const adapter of adapters) {
|
|
42
|
+
const globalStatus = await getTargetStatus(adapter.getGlobalTargetPath(installation));
|
|
43
|
+
global = mergeTargetStatus(global, globalStatus);
|
|
44
|
+
if (projectPath) {
|
|
45
|
+
const projectStatus = await getTargetStatus(adapter.getProjectTargetPath(installation));
|
|
46
|
+
project = project
|
|
47
|
+
? mergeTargetStatus(project, projectStatus)
|
|
48
|
+
: projectStatus;
|
|
49
|
+
}
|
|
32
50
|
}
|
|
33
51
|
return { global, project };
|
|
34
52
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface BootstrapResult {
|
|
2
|
+
applied: boolean;
|
|
3
|
+
pushed: boolean;
|
|
4
|
+
pushError?: string;
|
|
5
|
+
skillCount: number;
|
|
6
|
+
agentCount: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function getTemplateDir(): string;
|
|
9
|
+
/** True when the clone has no installable artifacts and no custom content. */
|
|
10
|
+
export declare function isEmptySkillRepo(repoPath: string): Promise<boolean>;
|
|
11
|
+
export declare function applyTemplateToRepo(repoPath: string): Promise<void>;
|
|
12
|
+
export declare function bootstrapEmptyRepo(repoPath: string, ref: string): Promise<BootstrapResult>;
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { cp, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { commitAndPushRepo } from "./git.js";
|
|
5
|
+
import { scanRepoArtifacts } from "./scan.js";
|
|
6
|
+
const GITHUB_INIT_FILES = new Set([
|
|
7
|
+
"README.md",
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"LICENSE.md",
|
|
10
|
+
"LICENSE.txt",
|
|
11
|
+
".gitignore",
|
|
12
|
+
]);
|
|
13
|
+
export function getTemplateDir() {
|
|
14
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
return path.resolve(moduleDir, "../template");
|
|
16
|
+
}
|
|
17
|
+
async function pathExists(filePath) {
|
|
18
|
+
try {
|
|
19
|
+
await stat(filePath);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** True when the clone has no installable artifacts and no custom content. */
|
|
27
|
+
export async function isEmptySkillRepo(repoPath) {
|
|
28
|
+
const artifacts = await scanRepoArtifacts(repoPath);
|
|
29
|
+
if (artifacts.length > 0) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const entries = await readdir(repoPath, { withFileTypes: true });
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entry.name === ".git") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (!GITHUB_INIT_FILES.has(entry.name)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
export async function applyTemplateToRepo(repoPath) {
|
|
47
|
+
const templateDir = getTemplateDir();
|
|
48
|
+
if (!(await pathExists(templateDir))) {
|
|
49
|
+
throw new Error(`Template directory not found: ${templateDir}`);
|
|
50
|
+
}
|
|
51
|
+
const entries = await readdir(templateDir, { withFileTypes: true });
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const src = path.join(templateDir, entry.name);
|
|
54
|
+
const dest = path.join(repoPath, entry.name);
|
|
55
|
+
await cp(src, dest, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function bootstrapEmptyRepo(repoPath, ref) {
|
|
59
|
+
if (!(await isEmptySkillRepo(repoPath))) {
|
|
60
|
+
const artifacts = await scanRepoArtifacts(repoPath);
|
|
61
|
+
return {
|
|
62
|
+
applied: false,
|
|
63
|
+
pushed: false,
|
|
64
|
+
skillCount: artifacts.filter((a) => a.kind === "skill").length,
|
|
65
|
+
agentCount: artifacts.filter((a) => a.kind === "agent").length,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
await applyTemplateToRepo(repoPath);
|
|
69
|
+
let pushed = false;
|
|
70
|
+
let pushError;
|
|
71
|
+
try {
|
|
72
|
+
await commitAndPushRepo(repoPath, ref);
|
|
73
|
+
pushed = true;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
pushError = err instanceof Error ? err.message : String(err);
|
|
77
|
+
}
|
|
78
|
+
const artifacts = await scanRepoArtifacts(repoPath);
|
|
79
|
+
return {
|
|
80
|
+
applied: true,
|
|
81
|
+
pushed,
|
|
82
|
+
pushError,
|
|
83
|
+
skillCount: artifacts.filter((a) => a.kind === "skill").length,
|
|
84
|
+
agentCount: artifacts.filter((a) => a.kind === "agent").length,
|
|
85
|
+
};
|
|
86
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export type ArtifactKind = "skill" | "agent";
|
|
2
2
|
export type ArtifactAllowedScope = "global" | "project" | "any";
|
|
3
|
-
export type
|
|
3
|
+
export type IdeId = "cursor" | "claude" | "codex";
|
|
4
|
+
/** @deprecated use IdeId */
|
|
5
|
+
export type AdapterId = IdeId;
|
|
6
|
+
export interface IdeToolConfig {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
configPath: string;
|
|
9
|
+
}
|
|
10
|
+
export interface IdesConfig {
|
|
11
|
+
cursor: IdeToolConfig;
|
|
12
|
+
claude: IdeToolConfig;
|
|
13
|
+
codex: IdeToolConfig;
|
|
14
|
+
}
|
|
4
15
|
export interface ServerConfig {
|
|
5
16
|
port: number;
|
|
6
17
|
}
|
|
@@ -23,7 +34,9 @@ export interface Installation {
|
|
|
23
34
|
}
|
|
24
35
|
export interface IdeAgentsConfig {
|
|
25
36
|
version: 1;
|
|
37
|
+
/** @deprecated kept for migration; use ides */
|
|
26
38
|
adapter: AdapterId;
|
|
39
|
+
ides: IdesConfig;
|
|
27
40
|
server: ServerConfig;
|
|
28
41
|
repos: RepoConfig[];
|
|
29
42
|
installations: Installation[];
|
|
@@ -61,6 +74,8 @@ export interface GitStatus {
|
|
|
61
74
|
export interface RepoWithStatus extends RepoConfig {
|
|
62
75
|
localPath: string;
|
|
63
76
|
git: GitStatus;
|
|
77
|
+
skillCount: number;
|
|
78
|
+
agentCount: number;
|
|
64
79
|
}
|
|
65
80
|
export interface ApplyResultItem {
|
|
66
81
|
path: string;
|
|
@@ -70,6 +85,5 @@ export interface ApplyResultItem {
|
|
|
70
85
|
export interface ApplyResult {
|
|
71
86
|
results: ApplyResultItem[];
|
|
72
87
|
}
|
|
73
|
-
export declare const PACKAGE_VERSION = "0.1.0";
|
|
74
88
|
/** @deprecated legacy config field */
|
|
75
89
|
export type Scope = "global" | "project" | "off";
|
package/dist/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PACKAGE_VERSION: string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version } = require(path.join(fileURLToPath(new URL(".", import.meta.url)), "../package.json"));
|
|
6
|
+
export const PACKAGE_VERSION = version;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ide-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Local admin for IDE agents and skills from git repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"template",
|
|
11
12
|
"web/dist",
|
|
12
13
|
"LICENSE",
|
|
13
14
|
"README.md"
|
|
@@ -23,7 +24,9 @@
|
|
|
23
24
|
"start": "node dist/cli.js",
|
|
24
25
|
"docs:install": "npm install --prefix docs",
|
|
25
26
|
"docs:start": "npm --prefix docs start",
|
|
26
|
-
"docs:build": "npm --prefix docs run build"
|
|
27
|
+
"docs:build": "npm --prefix docs run build",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"lint:fix": "eslint . --fix"
|
|
27
30
|
},
|
|
28
31
|
"engines": {
|
|
29
32
|
"node": ">=20"
|
|
@@ -51,27 +54,32 @@
|
|
|
51
54
|
},
|
|
52
55
|
"dependencies": {
|
|
53
56
|
"@fastify/static": "^8.1.1",
|
|
54
|
-
"fastify": "^5.
|
|
57
|
+
"fastify": "^5.8.5",
|
|
55
58
|
"gray-matter": "^4.0.3",
|
|
56
59
|
"uuid": "^11.1.0"
|
|
57
60
|
},
|
|
58
61
|
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^10.0.1",
|
|
59
63
|
"@mantine/core": "^9.2.2",
|
|
60
64
|
"@mantine/hooks": "^9.2.2",
|
|
61
65
|
"@tabler/icons-react": "^3.44.0",
|
|
62
66
|
"@types/node": "^22.13.10",
|
|
63
|
-
"@types/react": "^19.
|
|
64
|
-
"@types/react-dom": "^19.
|
|
65
|
-
"@vitejs/plugin-react": "^
|
|
67
|
+
"@types/react": "^19.2.15",
|
|
68
|
+
"@types/react-dom": "^19.2.3",
|
|
69
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
66
70
|
"concurrently": "^9.1.2",
|
|
71
|
+
"eslint": "^10.4.1",
|
|
72
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
73
|
+
"globals": "^16.5.0",
|
|
67
74
|
"postcss": "^8.5.15",
|
|
68
75
|
"postcss-preset-mantine": "^1.18.0",
|
|
69
76
|
"postcss-simple-vars": "^7.0.1",
|
|
70
|
-
"react": "^19.
|
|
71
|
-
"react-dom": "^19.
|
|
77
|
+
"react": "^19.2.6",
|
|
78
|
+
"react-dom": "^19.2.6",
|
|
72
79
|
"react-router-dom": "^7.3.0",
|
|
73
80
|
"tsx": "^4.19.3",
|
|
74
81
|
"typescript": "^5.8.2",
|
|
75
|
-
"
|
|
82
|
+
"typescript-eslint": "^8.60.0",
|
|
83
|
+
"vite": "^8.0.14"
|
|
76
84
|
}
|
|
77
85
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Codex — repository guide
|
|
2
|
+
|
|
3
|
+
This repository holds **Agent Skills** and **agents** consumed by ide-agents.
|
|
4
|
+
|
|
5
|
+
## Expected layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
skills/<skill-id>/
|
|
9
|
+
├── SKILL.md
|
|
10
|
+
├── scripts/ # optional — *.mjs generators (stdlib, ESM)
|
|
11
|
+
└── assets/ # optional — JSON config for scripts
|
|
12
|
+
agents/<agent-id>.md
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## SKILL.md
|
|
16
|
+
|
|
17
|
+
Required frontmatter:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
---
|
|
21
|
+
name: skill-id
|
|
22
|
+
description: Short summary for the UI.
|
|
23
|
+
scope: any
|
|
24
|
+
---
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Project installs symlink into `<project>/.agents/skills/<name>`.
|
|
28
|
+
|
|
29
|
+
## Agents
|
|
30
|
+
|
|
31
|
+
Markdown files in `agents/<agent-id>.md`. Required frontmatter:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
---
|
|
35
|
+
name: agent-id # must match filename stem
|
|
36
|
+
description: When the IDE should delegate to this subagent.
|
|
37
|
+
scope: any # optional — ide-agents install toggles
|
|
38
|
+
---
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Project installs use `.agents/agents/<name>.md`.
|
|
42
|
+
|
|
43
|
+
Agents define **role and workflow** only. Repeatable generators (stack detection, audits, structured reports) go in `skills/<skill-id>/scripts/` — agents invoke `node <SKILL_DIR>/scripts/….mjs` instead of improvising the same logic in chat. Pattern: [repo-audit-skills](https://github.com/sergeychernov/repo-audit-skills).
|
|
44
|
+
|
|
45
|
+
## Editing rules
|
|
46
|
+
|
|
47
|
+
- Do not flatten skills to repo root unless you intentionally use the flat layout (ide-agents detects nested `skills/` first).
|
|
48
|
+
- Preserve skill folder names — they become installation ids.
|
|
49
|
+
- Push to git after changes so remote catalogs stay in sync.
|
|
50
|
+
|
|
51
|
+
See `README.md` for usage with ide-agents.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Claude Code — repository guide
|
|
2
|
+
|
|
3
|
+
This repo is a **skills and agents catalog** for ide-agents.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
- `skills/<name>/SKILL.md` — skills with YAML frontmatter (`name`, `description`, `scope`)
|
|
8
|
+
- `skills/<name>/scripts/` — optional Node `.mjs` generators (stable scans, JSON reports)
|
|
9
|
+
- `skills/<name>/assets/` — optional JSON/markers scripts read
|
|
10
|
+
- `agents/<name>.md` — subagent orchestrators; frontmatter `name`, `description`, optional `scope`
|
|
11
|
+
|
|
12
|
+
## Agents and scripts
|
|
13
|
+
|
|
14
|
+
Do **not** implement multi-step repo scanners or report generators inline in agent markdown. Put them in `skills/<skill-id>/scripts/` and have agents run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node <SKILL_DIR>/scripts/<script>.mjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then read stdout (`--json`) or documented output files. See repo-audit-skills for reference (`detect-stack.mjs`, `run-audit.mjs`).
|
|
21
|
+
|
|
22
|
+
## When editing
|
|
23
|
+
|
|
24
|
+
- Keep one skill per folder; the folder name is the skill id.
|
|
25
|
+
- Use `scope: any` unless the skill must be global-only or project-only.
|
|
26
|
+
- Write instructions in the skill body, not only in frontmatter.
|
|
27
|
+
- Commit meaningful changes; ide-agents symlinks from the cloned copy under `~/.ide-agents/repos/`.
|
|
28
|
+
|
|
29
|
+
## Scope values
|
|
30
|
+
|
|
31
|
+
| Value | Meaning |
|
|
32
|
+
|-------|---------|
|
|
33
|
+
| `global` | Install only to user config (`~/.claude/`) |
|
|
34
|
+
| `project` | Install only to project `.claude/` |
|
|
35
|
+
| `any` | User chooses global or project in the UI |
|
|
36
|
+
|
|
37
|
+
See `README.md` for the full catalog overview.
|