ide-agents 0.2.0 → 0.4.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 +1 -7
- package/dist/cli.js +10 -0
- package/dist/git.d.ts +1 -0
- package/dist/git.js +24 -0
- package/dist/npmUpdate.d.ts +12 -0
- package/dist/npmUpdate.js +102 -0
- package/dist/server.js +44 -1
- package/dist/template.d.ts +12 -0
- package/dist/template.js +86 -0
- package/package.json +10 -2
- 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-DMOyV7Gu.js +51 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-C1Mi5f8W.js +0 -51
package/README.md
CHANGED
|
@@ -121,13 +121,7 @@ npm run docs:install
|
|
|
121
121
|
npm run docs:start # http://localhost:3000
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
cd fixtures/sample-repo && git init && git add . && git commit -m "init"
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Then add `file://$(pwd)` in the UI.
|
|
124
|
+
For local testing, add a `file://` URL to any git repo with skills/agents (see [docs](https://ide-agents.vercel.app/docs/source-repos#local-testing)).
|
|
131
125
|
|
|
132
126
|
## Data layout
|
|
133
127
|
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { readConfig } from "./config.js";
|
|
6
|
+
import { checkNpmUpdate, formatCliUpdateMessage } from "./npmUpdate.js";
|
|
6
7
|
import { startServer } from "./server.js";
|
|
7
8
|
function parseArgs(argv) {
|
|
8
9
|
const options = {};
|
|
@@ -11,6 +12,9 @@ function parseArgs(argv) {
|
|
|
11
12
|
if (arg === "--no-open") {
|
|
12
13
|
options.noOpen = true;
|
|
13
14
|
}
|
|
15
|
+
else if (arg === "--no-update-check") {
|
|
16
|
+
options.noUpdateCheck = true;
|
|
17
|
+
}
|
|
14
18
|
else if (arg === "--port" && argv[i + 1]) {
|
|
15
19
|
options.port = Number.parseInt(argv[i + 1], 10);
|
|
16
20
|
i++;
|
|
@@ -59,6 +63,12 @@ async function main() {
|
|
|
59
63
|
const requestedPort = options.port ?? config.server.port;
|
|
60
64
|
const port = await findAvailablePort(requestedPort, host);
|
|
61
65
|
const url = `http://${host}:${port}`;
|
|
66
|
+
if (!options.noUpdateCheck) {
|
|
67
|
+
const updateMessage = formatCliUpdateMessage(await checkNpmUpdate());
|
|
68
|
+
if (updateMessage) {
|
|
69
|
+
console.log(updateMessage);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
const app = await startServer({
|
|
63
73
|
port,
|
|
64
74
|
host,
|
package/dist/git.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare function cloneRepo(url: string, slug: string, ref: string): Promi
|
|
|
3
3
|
export declare function fetchRepo(slug: string): Promise<void>;
|
|
4
4
|
export declare function pullRepo(slug: string): Promise<void>;
|
|
5
5
|
export declare function getGitStatus(slug: string, ref: string): Promise<GitStatus>;
|
|
6
|
+
export declare function commitAndPushRepo(cwd: string, ref: string): Promise<void>;
|
|
6
7
|
export declare function getGitStatusWithoutFetch(slug: string, ref: string): Promise<GitStatus>;
|
package/dist/git.js
CHANGED
|
@@ -130,6 +130,30 @@ export async function getGitStatus(slug, ref) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
+
export async function commitAndPushRepo(cwd, ref) {
|
|
134
|
+
if (!(await isGitRepo(cwd))) {
|
|
135
|
+
throw new Error(`Not a git repository: ${cwd}`);
|
|
136
|
+
}
|
|
137
|
+
const status = await runGit(cwd, ["status", "--porcelain"]);
|
|
138
|
+
if (!status.stdout.trim()) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
await runGit(cwd, ["add", "-A"]);
|
|
142
|
+
await runGit(cwd, [
|
|
143
|
+
"commit",
|
|
144
|
+
"-m",
|
|
145
|
+
"chore: bootstrap skills catalog from ide-agents template",
|
|
146
|
+
]);
|
|
147
|
+
try {
|
|
148
|
+
await runGit(cwd, ["push", "origin", ref]);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
152
|
+
throw new Error(`Commit succeeded but push failed: ${message}`, {
|
|
153
|
+
cause: err,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
133
157
|
export async function getGitStatusWithoutFetch(slug, ref) {
|
|
134
158
|
const cwd = getRepoPath(slug);
|
|
135
159
|
if (!(await isGitRepo(cwd))) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const NPM_PACKAGE_NAME = "ide-agents";
|
|
2
|
+
export interface NpmUpdateInfo {
|
|
3
|
+
current: string;
|
|
4
|
+
latest: string | null;
|
|
5
|
+
updateAvailable: boolean;
|
|
6
|
+
installCommand: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function isNewerVersion(latest: string, current: string): boolean;
|
|
9
|
+
export declare function checkNpmUpdate(options?: {
|
|
10
|
+
skipCache?: boolean;
|
|
11
|
+
}): Promise<NpmUpdateInfo>;
|
|
12
|
+
export declare function formatCliUpdateMessage(info: NpmUpdateInfo): string;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureIdeAgentsHome } from "./config.js";
|
|
4
|
+
import { getIdeAgentsHome } from "./paths.js";
|
|
5
|
+
import { PACKAGE_VERSION } from "./version.js";
|
|
6
|
+
export const NPM_PACKAGE_NAME = "ide-agents";
|
|
7
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${NPM_PACKAGE_NAME}/latest`;
|
|
8
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
const FETCH_TIMEOUT_MS = 3000;
|
|
10
|
+
function getCachePath() {
|
|
11
|
+
return path.join(getIdeAgentsHome(), "npm-update-check.json");
|
|
12
|
+
}
|
|
13
|
+
function parseVersion(version) {
|
|
14
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(version.trim());
|
|
15
|
+
if (!match) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
19
|
+
}
|
|
20
|
+
export function isNewerVersion(latest, current) {
|
|
21
|
+
const a = parseVersion(latest);
|
|
22
|
+
const b = parseVersion(current);
|
|
23
|
+
if (!a || !b) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
for (let i = 0; i < 3; i++) {
|
|
27
|
+
if (a[i] > b[i]) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (a[i] < b[i]) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
async function readCache() {
|
|
37
|
+
try {
|
|
38
|
+
const raw = await readFile(getCachePath(), "utf8");
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
if (typeof parsed.checkedAt !== "number" ||
|
|
41
|
+
(parsed.latest !== null && typeof parsed.latest !== "string")) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return parsed;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function writeCache(latest) {
|
|
51
|
+
await ensureIdeAgentsHome();
|
|
52
|
+
const cache = { checkedAt: Date.now(), latest };
|
|
53
|
+
await writeFile(getCachePath(), `${JSON.stringify(cache, null, 2)}\n`, "utf8");
|
|
54
|
+
}
|
|
55
|
+
async function fetchLatestVersion() {
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(REGISTRY_URL, {
|
|
58
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
59
|
+
headers: { Accept: "application/json" },
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const data = (await res.json());
|
|
65
|
+
return typeof data.version === "string" ? data.version : null;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function resolveLatestVersion(skipCache) {
|
|
72
|
+
if (!skipCache) {
|
|
73
|
+
const cache = await readCache();
|
|
74
|
+
if (cache && Date.now() - cache.checkedAt < CACHE_TTL_MS) {
|
|
75
|
+
return cache.latest;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const latest = await fetchLatestVersion();
|
|
79
|
+
await writeCache(latest);
|
|
80
|
+
return latest;
|
|
81
|
+
}
|
|
82
|
+
export async function checkNpmUpdate(options) {
|
|
83
|
+
const current = PACKAGE_VERSION;
|
|
84
|
+
const installCommand = `npm i -g ${NPM_PACKAGE_NAME}@latest`;
|
|
85
|
+
const latest = await resolveLatestVersion(options?.skipCache ?? false);
|
|
86
|
+
const updateAvailable = latest !== null && isNewerVersion(latest, current);
|
|
87
|
+
return {
|
|
88
|
+
current,
|
|
89
|
+
latest,
|
|
90
|
+
updateAvailable,
|
|
91
|
+
installCommand,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function formatCliUpdateMessage(info) {
|
|
95
|
+
if (!info.updateAvailable || !info.latest) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
return [
|
|
99
|
+
`ide-agents v${info.current} — доступна v${info.latest}`,
|
|
100
|
+
` ${info.installCommand}`,
|
|
101
|
+
].join("\n");
|
|
102
|
+
}
|
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
10
|
import { expandUserPath, getIdeAgentsHome, getRepoPath, getWebDistDir, slugFromUrl, } from "./paths.js";
|
|
10
11
|
import { defaultAdapterFromIdes, getDefaultIdes } from "./ides.js";
|
|
12
|
+
import { checkNpmUpdate } from "./npmUpdate.js";
|
|
11
13
|
import { PACKAGE_VERSION as VERSION } from "./version.js";
|
|
12
14
|
export async function createServer(options) {
|
|
13
15
|
const app = Fastify({ logger: false });
|
|
@@ -18,21 +20,25 @@ export async function createServer(options) {
|
|
|
18
20
|
});
|
|
19
21
|
app.get("/api/status", async () => {
|
|
20
22
|
const config = await readConfig();
|
|
23
|
+
const npmUpdate = await checkNpmUpdate();
|
|
21
24
|
return {
|
|
22
25
|
home: getIdeAgentsHome(),
|
|
23
26
|
port: options.port,
|
|
24
27
|
version: VERSION,
|
|
28
|
+
npmUpdate,
|
|
25
29
|
defaultProjectPath: options.launchCwd ?? null,
|
|
26
30
|
ides: config.ides,
|
|
27
31
|
};
|
|
28
32
|
});
|
|
29
33
|
app.get("/api/settings", async () => {
|
|
30
34
|
const config = await readConfig();
|
|
35
|
+
const npmUpdate = await checkNpmUpdate();
|
|
31
36
|
return {
|
|
32
37
|
ides: config.ides,
|
|
33
38
|
defaults: getDefaultIdes(),
|
|
34
39
|
home: getIdeAgentsHome(),
|
|
35
40
|
version: VERSION,
|
|
41
|
+
npmUpdate,
|
|
36
42
|
};
|
|
37
43
|
});
|
|
38
44
|
app.put("/api/settings", async (request, reply) => {
|
|
@@ -99,14 +105,16 @@ export async function createServer(options) {
|
|
|
99
105
|
if (config.repos.some((r) => r.id === repoId)) {
|
|
100
106
|
return reply.status(409).send({ error: `Repository id already exists: ${repoId}` });
|
|
101
107
|
}
|
|
108
|
+
let repoPath;
|
|
102
109
|
try {
|
|
103
|
-
await cloneRepo(url.trim(), slug, ref);
|
|
110
|
+
repoPath = await cloneRepo(url.trim(), slug, ref);
|
|
104
111
|
}
|
|
105
112
|
catch (err) {
|
|
106
113
|
return reply.status(400).send({
|
|
107
114
|
error: err instanceof Error ? err.message : String(err),
|
|
108
115
|
});
|
|
109
116
|
}
|
|
117
|
+
const bootstrap = await bootstrapEmptyRepo(repoPath, ref);
|
|
110
118
|
const repo = { id: repoId, url: url.trim(), ref, slug };
|
|
111
119
|
config.repos.push(repo);
|
|
112
120
|
await writeConfig(config);
|
|
@@ -116,7 +124,10 @@ export async function createServer(options) {
|
|
|
116
124
|
...repo,
|
|
117
125
|
localPath: getRepoPath(slug),
|
|
118
126
|
git,
|
|
127
|
+
skillCount: bootstrap.skillCount,
|
|
128
|
+
agentCount: bootstrap.agentCount,
|
|
119
129
|
},
|
|
130
|
+
bootstrap,
|
|
120
131
|
};
|
|
121
132
|
});
|
|
122
133
|
app.delete("/api/repos/:id", async (request, reply) => {
|
|
@@ -130,6 +141,38 @@ export async function createServer(options) {
|
|
|
130
141
|
await writeConfig(config);
|
|
131
142
|
return { ok: true };
|
|
132
143
|
});
|
|
144
|
+
app.post("/api/repos/:id/bootstrap", async (request, reply) => {
|
|
145
|
+
const config = await readConfig();
|
|
146
|
+
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
147
|
+
if (!repo) {
|
|
148
|
+
return reply.status(404).send({ error: "Repository not found" });
|
|
149
|
+
}
|
|
150
|
+
const repoPath = getRepoPath(repo.slug);
|
|
151
|
+
try {
|
|
152
|
+
const bootstrap = await bootstrapEmptyRepo(repoPath, repo.ref);
|
|
153
|
+
if (!bootstrap.applied) {
|
|
154
|
+
return reply.status(409).send({
|
|
155
|
+
error: "Repository is not empty — bootstrap skipped",
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const git = await getGitStatusWithoutFetch(repo.slug, repo.ref);
|
|
159
|
+
return {
|
|
160
|
+
repo: {
|
|
161
|
+
...repo,
|
|
162
|
+
localPath: repoPath,
|
|
163
|
+
git,
|
|
164
|
+
skillCount: bootstrap.skillCount,
|
|
165
|
+
agentCount: bootstrap.agentCount,
|
|
166
|
+
},
|
|
167
|
+
bootstrap,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return reply.status(400).send({
|
|
172
|
+
error: err instanceof Error ? err.message : String(err),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
133
176
|
app.post("/api/repos/:id/fetch", async (request, reply) => {
|
|
134
177
|
const config = await readConfig();
|
|
135
178
|
const repo = config.repos.find((r) => r.id === request.params.id);
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ide-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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"
|
|
@@ -56,6 +59,7 @@
|
|
|
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",
|
|
@@ -64,6 +68,9 @@
|
|
|
64
68
|
"@types/react-dom": "^19.2.3",
|
|
65
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",
|
|
@@ -72,6 +79,7 @@
|
|
|
72
79
|
"react-router-dom": "^7.3.0",
|
|
73
80
|
"tsx": "^4.19.3",
|
|
74
81
|
"typescript": "^5.8.2",
|
|
82
|
+
"typescript-eslint": "^8.60.0",
|
|
75
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.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Agent authoring — orchestration, reports, and script-backed generators
|
|
3
|
+
globs: agents/**/*.md
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Agent authoring
|
|
8
|
+
|
|
9
|
+
Agents in `agents/*.md` are **orchestrators**: they define role, workflow, and output format. They are not a place for ad-hoc code generation.
|
|
10
|
+
|
|
11
|
+
## Generators → `scripts/`
|
|
12
|
+
|
|
13
|
+
For anything that must be **stable, repeatable, and testable** (scanners, profile builders, report writers, stack detectors), put logic in bundled scripts under the **related skill folder**:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
skills/<skill-id>/
|
|
17
|
+
├── SKILL.md
|
|
18
|
+
├── scripts/ # node *.mjs — run these, do not re-implement in chat
|
|
19
|
+
└── assets/ # optional JSON/markers the scripts read
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Pattern from [repo-audit-skills](https://github.com/sergeychernov/repo-audit-skills):
|
|
23
|
+
|
|
24
|
+
- `skills/audit-init/scripts/detect-stack.mjs` — read-only scan, writes structured JSON
|
|
25
|
+
- `skills/audit-debt/scripts/run-audit.mjs` — reads profile, runs checks, writes report
|
|
26
|
+
|
|
27
|
+
### Why agents call scripts
|
|
28
|
+
|
|
29
|
+
| In chat (avoid) | In `scripts/` (prefer) |
|
|
30
|
+
|-----------------|------------------------|
|
|
31
|
+
| Re-parsing repo layout each turn | Same repo state → same output |
|
|
32
|
+
| Drifting flags and file paths | Documented CLI in script header |
|
|
33
|
+
| Large inline bash one-liners | `#!/usr/bin/env node`, ESM `.mjs` |
|
|
34
|
+
|
|
35
|
+
Agent body should tell the model to **resolve `<SKILL_DIR>`**, run `node <SKILL_DIR>/scripts/<name>.mjs`, then **read the artifact** (stdout JSON or a known output path). See `scripts.mdc` for script conventions.
|
|
36
|
+
|
|
37
|
+
## Agent file format
|
|
38
|
+
|
|
39
|
+
YAML frontmatter (filename stem must match `name`, e.g. `agents/oracle.md` → `name: oracle`):
|
|
40
|
+
|
|
41
|
+
```yaml
|
|
42
|
+
---
|
|
43
|
+
name: my-agent # required — lowercase id; Cursor subagent identity (/my-agent)
|
|
44
|
+
description: When to delegate to this subagent. Be specific — the IDE uses this for auto-delegation.
|
|
45
|
+
scope: any # optional — ide-agents only: global | project | any (default any)
|
|
46
|
+
---
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
| Field | Required | Used by |
|
|
50
|
+
|-------|----------|---------|
|
|
51
|
+
| `name` | yes | Cursor / Claude / Codex subagent id (defaults to filename if omitted — prefer explicit) |
|
|
52
|
+
| `description` | yes | IDE delegation — when the main agent should hand off to this subagent |
|
|
53
|
+
| `scope` | no | ide-agents UI — which install toggles are enabled |
|
|
54
|
+
|
|
55
|
+
Subagents are **not** skills: they live in `agents/*.md`, install to `~/.cursor/agents/`, and are invoked by name in Agent mode (e.g. `/oracle …` or “use the oracle subagent”), not via the `/` skill picker.
|
|
56
|
+
|
|
57
|
+
Body structure:
|
|
58
|
+
|
|
59
|
+
1. **Role** — one paragraph
|
|
60
|
+
2. **Inputs** — what the user or upstream skill must provide
|
|
61
|
+
3. **Workflow** — numbered steps; step that runs a script must include the exact `node …mjs` command
|
|
62
|
+
4. **Output contract** — fixed sections (e.g. summary, findings, gaps); use consistent severity markers if reporting issues
|
|
63
|
+
|
|
64
|
+
## Do not
|
|
65
|
+
|
|
66
|
+
- Embed multi-step filesystem scans or JSON schema logic in the agent markdown
|
|
67
|
+
- Write into the target project except paths documented by the skill (e.g. `.audit/`)
|
|
68
|
+
- Duplicate script behavior in prose — link to `SKILL.md` quick start instead
|
|
69
|
+
|
|
70
|
+
## Shared helpers
|
|
71
|
+
|
|
72
|
+
Cross-skill utilities may live in `skills/_shared/` (e.g. `generated-by.mjs`). Import from scripts via relative paths; agents reference the skill that owns the script.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Skills and agents repository layout for ide-agents catalogs
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Repository structure
|
|
7
|
+
|
|
8
|
+
This repository is an **ide-agents skill catalog** — not an application codebase.
|
|
9
|
+
|
|
10
|
+
## Directories
|
|
11
|
+
|
|
12
|
+
| Path | Purpose |
|
|
13
|
+
|------|---------|
|
|
14
|
+
| `skills/<id>/SKILL.md` | Installable skills (required `SKILL.md` per folder) |
|
|
15
|
+
| `skills/<id>/scripts/` | Optional bundled generators (`*.mjs`) — see `scripts.mdc` |
|
|
16
|
+
| `skills/<id>/assets/` | Optional JSON/markers read by scripts |
|
|
17
|
+
| `skills/_shared/` | Optional cross-skill helpers (imported by scripts) |
|
|
18
|
+
| `agents/<id>.md` | Subagent orchestrators — call scripts, do not re-implement generators |
|
|
19
|
+
| `.cursor/rules/` | Cursor project rules (this file) |
|
|
20
|
+
|
|
21
|
+
## SKILL.md conventions
|
|
22
|
+
|
|
23
|
+
- YAML frontmatter: `name`, `description`, `scope` (`global`, `project`, or `any`).
|
|
24
|
+
- Body: instructions the IDE agent follows when the skill is invoked.
|
|
25
|
+
- One skill per directory under `skills/`.
|
|
26
|
+
|
|
27
|
+
## Agents
|
|
28
|
+
|
|
29
|
+
- One markdown file per agent in `agents/` (e.g. `agents/oracle.md`).
|
|
30
|
+
- YAML frontmatter: `name`, `description`, optional `scope` (`global`, `project`, or `any`).
|
|
31
|
+
- `name` must match the filename stem; used by Cursor/Claude/Codex as the subagent id.
|
|
32
|
+
- Agents **orchestrate**; deterministic generators live in `skills/<id>/scripts/` (see `agents.mdc`).
|
|
33
|
+
|
|
34
|
+
## Scripts (generators)
|
|
35
|
+
|
|
36
|
+
Stable scanners and report writers belong in `skills/<skill-id>/scripts/`, not inline in agent prompts. Follow `scripts.mdc` (ESM `.mjs`, stdlib only, `--json` / `--dry-run`, resolve paths via `import.meta.url`).
|
|
37
|
+
|
|
38
|
+
## Do not
|
|
39
|
+
|
|
40
|
+
- Put application source code here unless it supports the catalog itself.
|
|
41
|
+
- Rename skill folders casually — `artifactId` in ide-agents matches the directory name.
|
|
42
|
+
- Remove `SKILL.md` from a skill folder (scan ignores directories without it).
|
|
43
|
+
|
|
44
|
+
## Installing
|
|
45
|
+
|
|
46
|
+
Use [ide-agents](https://github.com/sergeychernov/ide-agents): add this repo, then toggle **Global** or **Project** on Skills / Agents pages.
|