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.
Files changed (42) hide show
  1. package/README.md +22 -17
  2. package/dist/adapters/create.d.ts +9 -0
  3. package/dist/adapters/create.js +33 -0
  4. package/dist/adapters/index.d.ts +6 -0
  5. package/dist/adapters/index.js +16 -0
  6. package/dist/adapters/types.d.ts +7 -0
  7. package/dist/adapters/types.js +1 -0
  8. package/dist/apply.js +38 -11
  9. package/dist/config.js +48 -13
  10. package/dist/git.d.ts +1 -0
  11. package/dist/git.js +41 -1
  12. package/dist/gitignore.d.ts +5 -0
  13. package/dist/gitignore.js +105 -0
  14. package/dist/ides.d.ts +5 -0
  15. package/dist/ides.js +52 -0
  16. package/dist/paths.d.ts +1 -0
  17. package/dist/paths.js +10 -0
  18. package/dist/scan.js +58 -29
  19. package/dist/server.js +111 -6
  20. package/dist/targets.d.ts +2 -1
  21. package/dist/targets.js +24 -6
  22. package/dist/template.d.ts +12 -0
  23. package/dist/template.js +86 -0
  24. package/dist/types.d.ts +16 -2
  25. package/dist/types.js +1 -1
  26. package/dist/version.d.ts +1 -0
  27. package/dist/version.js +6 -0
  28. package/package.json +17 -9
  29. package/template/.agents/AGENTS.md +51 -0
  30. package/template/.claude/CLAUDE.md +37 -0
  31. package/template/.cursor/rules/agents.mdc +72 -0
  32. package/template/.cursor/rules/repo-structure.mdc +46 -0
  33. package/template/.cursor/rules/scripts.mdc +101 -0
  34. package/template/README.md +60 -0
  35. package/template/agents/oracle.md +38 -0
  36. package/template/skills/hello/SKILL.md +43 -0
  37. package/template/skills/hello/scripts/now.mjs +49 -0
  38. package/web/dist/assets/index-BSBPsDYY.css +1 -0
  39. package/web/dist/assets/index-C-SOo38e.js +51 -0
  40. package/web/dist/index.html +2 -2
  41. package/web/dist/assets/index-D7KhBlEO.js +0 -124
  42. 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 null;
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 scanSkills(repoPath) {
41
- const skillsDir = path.join(repoPath, "skills");
42
- if (!(await pathExists(skillsDir))) {
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(skillsDir, { withFileTypes: true });
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
- const skillDir = path.join(skillsDir, entry.name);
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
- const hasSkillMd = await pathExists(skillMdPath);
53
- let name = entry.name;
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
- artifacts.push({
68
- id: entry.name,
69
- kind: "skill",
70
- sourcePath: path.join("skills", entry.name),
71
- name,
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 { PACKAGE_VERSION as VERSION } from "./types.js";
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: getRepoPath(repo.slug),
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.adapter),
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, adapterId: string): Promise<ArtifactTargets>;
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 { getAdapter } from "./adapters/cursor.js";
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
- export async function getArtifactTargets(artifact, projectPath, adapterId) {
22
- const adapter = getAdapter(adapterId);
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
- const global = await getTargetStatus(adapter.getGlobalTargetPath(installation));
35
+ let global = {
36
+ exists: false,
37
+ isSymlink: false,
38
+ blocked: false,
39
+ };
29
40
  let project = null;
30
- if (projectPath) {
31
- project = await getTargetStatus(adapter.getProjectTargetPath(installation));
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>;
@@ -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 AdapterId = "cursor";
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 const PACKAGE_VERSION = "0.1.0";
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const PACKAGE_VERSION: string;
@@ -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.1.1",
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.2.1",
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.0.10",
64
- "@types/react-dom": "^19.0.4",
65
- "@vitejs/plugin-react": "^4.3.4",
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.0.0",
71
- "react-dom": "^19.0.0",
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
- "vite": "^6.2.1"
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.