ide-agents 0.1.0 → 0.2.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 +26 -12
- 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.js +17 -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 +72 -5
- package/dist/targets.d.ts +2 -1
- package/dist/targets.js +24 -6
- 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 +11 -11
- package/web/dist/assets/index-BSBPsDYY.css +1 -0
- package/web/dist/assets/index-C1Mi5f8W.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
|
@@ -6,8 +6,9 @@ import { cloneRepo, fetchRepo, getGitStatusWithoutFetch, pullRepo, } from "./git
|
|
|
6
6
|
import { applyInstallations } from "./apply.js";
|
|
7
7
|
import { scanRepoArtifacts } from "./scan.js";
|
|
8
8
|
import { getArtifactTargets } from "./targets.js";
|
|
9
|
-
import { getIdeAgentsHome, getRepoPath, getWebDistDir, slugFromUrl, } from "./paths.js";
|
|
10
|
-
import {
|
|
9
|
+
import { expandUserPath, getIdeAgentsHome, getRepoPath, getWebDistDir, slugFromUrl, } from "./paths.js";
|
|
10
|
+
import { defaultAdapterFromIdes, getDefaultIdes } from "./ides.js";
|
|
11
|
+
import { PACKAGE_VERSION as VERSION } from "./version.js";
|
|
11
12
|
export async function createServer(options) {
|
|
12
13
|
const app = Fastify({ logger: false });
|
|
13
14
|
app.setErrorHandler((error, _request, reply) => {
|
|
@@ -19,21 +20,70 @@ export async function createServer(options) {
|
|
|
19
20
|
const config = await readConfig();
|
|
20
21
|
return {
|
|
21
22
|
home: getIdeAgentsHome(),
|
|
22
|
-
adapter: config.adapter,
|
|
23
23
|
port: options.port,
|
|
24
24
|
version: VERSION,
|
|
25
25
|
defaultProjectPath: options.launchCwd ?? null,
|
|
26
|
+
ides: config.ides,
|
|
26
27
|
};
|
|
27
28
|
});
|
|
29
|
+
app.get("/api/settings", async () => {
|
|
30
|
+
const config = await readConfig();
|
|
31
|
+
return {
|
|
32
|
+
ides: config.ides,
|
|
33
|
+
defaults: getDefaultIdes(),
|
|
34
|
+
home: getIdeAgentsHome(),
|
|
35
|
+
version: VERSION,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
app.put("/api/settings", async (request, reply) => {
|
|
39
|
+
const { ides } = request.body ?? {};
|
|
40
|
+
if (!ides) {
|
|
41
|
+
return reply.status(400).send({ error: "ides is required" });
|
|
42
|
+
}
|
|
43
|
+
for (const key of ["cursor", "claude", "codex"]) {
|
|
44
|
+
const entry = ides[key];
|
|
45
|
+
if (!entry || typeof entry.configPath !== "string" || !entry.configPath.trim()) {
|
|
46
|
+
return reply.status(400).send({ error: `Invalid config for ${key}` });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const normalized = {
|
|
50
|
+
cursor: {
|
|
51
|
+
enabled: Boolean(ides.cursor.enabled),
|
|
52
|
+
configPath: expandUserPath(ides.cursor.configPath),
|
|
53
|
+
},
|
|
54
|
+
claude: {
|
|
55
|
+
enabled: Boolean(ides.claude.enabled),
|
|
56
|
+
configPath: expandUserPath(ides.claude.configPath),
|
|
57
|
+
},
|
|
58
|
+
codex: {
|
|
59
|
+
enabled: Boolean(ides.codex.enabled),
|
|
60
|
+
configPath: expandUserPath(ides.codex.configPath),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
if (!normalized.cursor.enabled &&
|
|
64
|
+
!normalized.claude.enabled &&
|
|
65
|
+
!normalized.codex.enabled) {
|
|
66
|
+
return reply.status(400).send({ error: "At least one IDE must be enabled" });
|
|
67
|
+
}
|
|
68
|
+
const config = await readConfig();
|
|
69
|
+
config.ides = normalized;
|
|
70
|
+
config.adapter = defaultAdapterFromIdes(config.ides);
|
|
71
|
+
await writeConfig(config);
|
|
72
|
+
return { ides: config.ides };
|
|
73
|
+
});
|
|
28
74
|
app.get("/api/repos", async () => {
|
|
29
75
|
const config = await readConfig();
|
|
30
76
|
const repos = [];
|
|
31
77
|
for (const repo of config.repos) {
|
|
32
78
|
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
79
|
+
const repoPath = getRepoPath(repo.slug);
|
|
80
|
+
const artifacts = await scanRepoArtifacts(repoPath);
|
|
33
81
|
repos.push({
|
|
34
82
|
...repo,
|
|
35
|
-
localPath:
|
|
83
|
+
localPath: repoPath,
|
|
36
84
|
git,
|
|
85
|
+
skillCount: artifacts.filter((a) => a.kind === "skill").length,
|
|
86
|
+
agentCount: artifacts.filter((a) => a.kind === "agent").length,
|
|
37
87
|
});
|
|
38
88
|
}
|
|
39
89
|
return { repos };
|
|
@@ -97,6 +147,23 @@ export async function createServer(options) {
|
|
|
97
147
|
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
98
148
|
return { git };
|
|
99
149
|
});
|
|
150
|
+
app.post("/api/repos/:id/check-updates", async (request, reply) => {
|
|
151
|
+
const config = await readConfig();
|
|
152
|
+
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
153
|
+
if (!repo) {
|
|
154
|
+
return reply.status(404).send({ error: "Repository not found" });
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
await fetchRepo(repo.slug);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return reply.status(400).send({
|
|
161
|
+
error: err instanceof Error ? err.message : String(err),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
165
|
+
return { git };
|
|
166
|
+
});
|
|
100
167
|
app.post("/api/repos/:id/pull", async (request, reply) => {
|
|
101
168
|
const config = await readConfig();
|
|
102
169
|
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
@@ -124,7 +191,7 @@ export async function createServer(options) {
|
|
|
124
191
|
const scanned = await scanRepoArtifacts(getRepoPath(repo.slug));
|
|
125
192
|
const artifacts = await Promise.all(scanned.map(async (artifact) => ({
|
|
126
193
|
...artifact,
|
|
127
|
-
targets: await getArtifactTargets(artifact, projectPath, config
|
|
194
|
+
targets: await getArtifactTargets(artifact, projectPath, config),
|
|
128
195
|
})));
|
|
129
196
|
return { artifacts };
|
|
130
197
|
});
|
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
|
}
|
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.2.0",
|
|
4
4
|
"description": "Local admin for IDE agents and skills from git repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/sergeychernov/
|
|
46
|
+
"url": "git+https://github.com/sergeychernov/ide-agents.git"
|
|
47
47
|
},
|
|
48
|
-
"homepage": "https://
|
|
48
|
+
"homepage": "https://ide-agents.vercel.app/",
|
|
49
49
|
"bugs": {
|
|
50
|
-
"url": "https://github.com/sergeychernov/
|
|
50
|
+
"url": "https://github.com/sergeychernov/ide-agents/issues"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@fastify/static": "^8.1.1",
|
|
54
|
-
"fastify": "^5.
|
|
54
|
+
"fastify": "^5.8.5",
|
|
55
55
|
"gray-matter": "^4.0.3",
|
|
56
56
|
"uuid": "^11.1.0"
|
|
57
57
|
},
|
|
@@ -60,18 +60,18 @@
|
|
|
60
60
|
"@mantine/hooks": "^9.2.2",
|
|
61
61
|
"@tabler/icons-react": "^3.44.0",
|
|
62
62
|
"@types/node": "^22.13.10",
|
|
63
|
-
"@types/react": "^19.
|
|
64
|
-
"@types/react-dom": "^19.
|
|
65
|
-
"@vitejs/plugin-react": "^
|
|
63
|
+
"@types/react": "^19.2.15",
|
|
64
|
+
"@types/react-dom": "^19.2.3",
|
|
65
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
66
66
|
"concurrently": "^9.1.2",
|
|
67
67
|
"postcss": "^8.5.15",
|
|
68
68
|
"postcss-preset-mantine": "^1.18.0",
|
|
69
69
|
"postcss-simple-vars": "^7.0.1",
|
|
70
|
-
"react": "^19.
|
|
71
|
-
"react-dom": "^19.
|
|
70
|
+
"react": "^19.2.6",
|
|
71
|
+
"react-dom": "^19.2.6",
|
|
72
72
|
"react-router-dom": "^7.3.0",
|
|
73
73
|
"tsx": "^4.19.3",
|
|
74
74
|
"typescript": "^5.8.2",
|
|
75
|
-
"vite": "^
|
|
75
|
+
"vite": "^8.0.14"
|
|
76
76
|
}
|
|
77
77
|
}
|