@cursor/july 0.1.18 → 0.1.20
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/AGENTS.md +10 -0
- package/dist/bin/agent-serve.js +20 -3
- package/dist/docs/404.html +1 -1
- package/dist/docs/ab.html +2 -2
- package/dist/docs/assets/{app.BR5EdVSq.js → app.T-5ImnM3.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CvejISXF.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CNCzfUKC.js → VPLocalSearchBox.OMGMWH6H.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CwKvMSWv.js → theme.DLg4yB4D.js} +2 -2
- package/dist/docs/building-with-agents.html +2 -2
- package/dist/docs/concepts.html +2 -2
- package/dist/docs/deployment.html +2 -2
- package/dist/docs/evals.html +2 -2
- package/dist/docs/example-agents/approval-buddy.html +2 -2
- package/dist/docs/example-agents/benny.html +2 -2
- package/dist/docs/example-agents/bugbot.html +2 -2
- package/dist/docs/example-agents/codebase-wiki.html +2 -2
- package/dist/docs/example-agents/codeowners-review.html +2 -2
- package/dist/docs/example-agents/concierge.html +2 -2
- package/dist/docs/example-agents/fsd.html +2 -2
- package/dist/docs/example-agents/index.html +2 -2
- package/dist/docs/example-agents/knowledge-base.html +2 -2
- package/dist/docs/example-agents/oncall.html +2 -2
- package/dist/docs/example-agents/security-reviewer.html +2 -2
- package/dist/docs/example-agents/slack-agent.html +2 -2
- package/dist/docs/example-agents/weather-agent.html +2 -2
- package/dist/docs/guides/agent-to-agent.html +2 -2
- package/dist/docs/guides/cloud-runtime.html +2 -2
- package/dist/docs/guides/github.html +2 -2
- package/dist/docs/guides/human-in-the-loop.html +2 -2
- package/dist/docs/guides/mcp-oauth.html +2 -2
- package/dist/docs/guides/slack.html +2 -2
- package/dist/docs/guides/webhooks.html +2 -2
- package/dist/docs/hillclimbing.html +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/reference/agent-config.html +2 -2
- package/dist/docs/reference/channels.html +2 -2
- package/dist/docs/reference/cli.html +2 -2
- package/dist/docs/reference/connections.html +2 -2
- package/dist/docs/reference/hooks.html +2 -2
- package/dist/docs/reference/http-api.html +2 -2
- package/dist/docs/reference/instructions.html +2 -2
- package/dist/docs/reference/playground.html +2 -2
- package/dist/docs/reference/project-layout.html +2 -2
- package/dist/docs/reference/prompt.html +2 -2
- package/dist/docs/reference/schedules.html +2 -2
- package/dist/docs/reference/sessions.html +2 -2
- package/dist/docs/reference/skills.html +2 -2
- package/dist/docs/reference/subagents.html +2 -2
- package/dist/docs/reference/tools.html +2 -2
- package/dist/docs/scaffolding-agents.html +2 -2
- package/dist/docs/storage.html +2 -2
- package/dist/docs/troubleshooting.html +2 -2
- package/dist/internal/cli-skills.d.ts +19 -0
- package/dist/internal/cli-skills.d.ts.map +1 -0
- package/dist/internal/cli-skills.js +72 -0
- package/dist/internal/install-cursor-skills.d.ts +46 -4
- package/dist/internal/install-cursor-skills.d.ts.map +1 -1
- package/dist/internal/install-cursor-skills.js +117 -49
- package/dist/playground/assets/index-CidizGZv.css +1 -0
- package/dist/playground/assets/index-CuiAKZaP.js +85 -0
- package/dist/playground/index.html +2 -2
- package/package.json +2 -2
- package/skills/create-agent/SKILL.md +36 -4
- package/skills/evals/SKILL.md +3 -0
- package/skills/framework-map/SKILL.md +14 -0
- package/skills/github/SKILL.md +6 -1
- package/skills/hillclimb/SKILL.md +28 -7
- package/src/bin/agent-serve.ts +21 -3
- package/src/internal/cli-skills.ts +95 -0
- package/src/internal/install-cursor-skills.ts +168 -60
- package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +0 -1
- package/dist/playground/assets/index-DRjS5klQ.js +0 -85
- package/dist/playground/assets/index-TCDs-UhZ.css +0 -1
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "node:fs/promises";
|
|
19
19
|
import { homedir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
-
import { packageRootDir } from "./distribution.js";
|
|
21
|
+
import { CLI_COMMAND_NAME, packageRootDir } from "./distribution.js";
|
|
22
22
|
import { stdoutPalette } from "./terminal-style.js";
|
|
23
23
|
|
|
24
24
|
/** Monorepo-only skill; excluded from the published package. */
|
|
@@ -27,6 +27,9 @@ const SKIP_SKILL_DIRS = new Set(["operate"]);
|
|
|
27
27
|
/** Category folder under ~/.cursor/skills (Cursor discovers nested skills). */
|
|
28
28
|
export const CURSOR_SKILLS_NAMESPACE = "agentkit";
|
|
29
29
|
|
|
30
|
+
/** How long the `init` offer waits for an answer before skipping the install. */
|
|
31
|
+
export const SKILL_PROMPT_TIMEOUT_MS = 45_000;
|
|
32
|
+
|
|
30
33
|
export interface PackageSkill {
|
|
31
34
|
/** Frontmatter `name` (e.g. agentkit-create-agent). */
|
|
32
35
|
name: string;
|
|
@@ -115,6 +118,13 @@ export async function findMissingCursorSkills(options?: {
|
|
|
115
118
|
return missing;
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
export interface SkillInstallResult {
|
|
122
|
+
/** Leaf dirs written. */
|
|
123
|
+
installed: string[];
|
|
124
|
+
/** Leaf dirs deleted because the package no longer ships them. */
|
|
125
|
+
removed: string[];
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
/**
|
|
119
129
|
* Copy each skill directory into `skillsHome/agentkit/<leafDir>/`.
|
|
120
130
|
* Always overwrites so a package upgrade refreshes local Cursor skills.
|
|
@@ -123,24 +133,13 @@ export async function findMissingCursorSkills(options?: {
|
|
|
123
133
|
export async function installCursorSkills(
|
|
124
134
|
skills: readonly PackageSkill[],
|
|
125
135
|
skillsHome: string = cursorSkillsHome()
|
|
126
|
-
): Promise<
|
|
136
|
+
): Promise<SkillInstallResult> {
|
|
127
137
|
const namespaceDir = agentkitCursorSkillsDir(skillsHome);
|
|
128
138
|
await mkdir(namespaceDir, { recursive: true });
|
|
129
139
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const entries = await readdir(namespaceDir, { withFileTypes: true });
|
|
134
|
-
existingLeaves = entries
|
|
135
|
-
.filter((entry) => entry.isDirectory())
|
|
136
|
-
.map((entry) => entry.name);
|
|
137
|
-
} catch {
|
|
138
|
-
existingLeaves = [];
|
|
139
|
-
}
|
|
140
|
-
for (const leaf of existingLeaves) {
|
|
141
|
-
if (!shippedLeaves.has(leaf)) {
|
|
142
|
-
await rm(join(namespaceDir, leaf), { recursive: true, force: true });
|
|
143
|
-
}
|
|
140
|
+
const removed = await orphanLeaves(namespaceDir, skills);
|
|
141
|
+
for (const leaf of removed) {
|
|
142
|
+
await rm(join(namespaceDir, leaf), { recursive: true, force: true });
|
|
144
143
|
}
|
|
145
144
|
|
|
146
145
|
const installed: string[] = [];
|
|
@@ -155,7 +154,24 @@ export async function installCursorSkills(
|
|
|
155
154
|
}
|
|
156
155
|
installed.push(skill.leafDir);
|
|
157
156
|
}
|
|
158
|
-
return installed;
|
|
157
|
+
return { installed, removed };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Installed leaf dirs the package no longer ships. Installing deletes these. */
|
|
161
|
+
async function orphanLeaves(
|
|
162
|
+
namespaceDir: string,
|
|
163
|
+
skills: readonly PackageSkill[]
|
|
164
|
+
): Promise<string[]> {
|
|
165
|
+
const shipped = new Set(skills.map((skill) => skill.leafDir));
|
|
166
|
+
let entries: Array<{ name: string; isDirectory: () => boolean }>;
|
|
167
|
+
try {
|
|
168
|
+
entries = await readdir(namespaceDir, { withFileTypes: true });
|
|
169
|
+
} catch {
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
return entries
|
|
173
|
+
.filter((entry) => entry.isDirectory() && !shipped.has(entry.name))
|
|
174
|
+
.map((entry) => entry.name);
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
/** Drop monorepo `paths` globs and fix package doc links for ~/.cursor installs. */
|
|
@@ -191,20 +207,72 @@ function stripPathsFromFrontmatter(frontmatter: string): string {
|
|
|
191
207
|
return out.join("\n");
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
export interface SkillInstallPlan {
|
|
211
|
+
shipped: PackageSkill[];
|
|
212
|
+
/** Resolved `~/.cursor/skills`, to install into. */
|
|
213
|
+
skillsHome: string;
|
|
214
|
+
/** Install target: `<skillsHome>/agentkit`. */
|
|
215
|
+
directory: string;
|
|
216
|
+
/** Leaf dirs the package ships. */
|
|
217
|
+
leaves: string[];
|
|
218
|
+
/** Nothing installed yet, so this is a fresh install rather than a refresh. */
|
|
219
|
+
firstInstall: boolean;
|
|
220
|
+
/** Installed leaf dirs the package dropped; installing deletes them. */
|
|
221
|
+
orphans: string[];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** What an install would do, for the offer and for `install-skills`. */
|
|
225
|
+
export async function planSkillInstall(options?: {
|
|
226
|
+
packageRoot?: string;
|
|
227
|
+
skillsHome?: string;
|
|
228
|
+
}): Promise<SkillInstallPlan> {
|
|
229
|
+
const skillsHome = options?.skillsHome ?? cursorSkillsHome();
|
|
230
|
+
const directory = agentkitCursorSkillsDir(skillsHome);
|
|
231
|
+
const shipped = await listShippedPackageSkills(options?.packageRoot);
|
|
232
|
+
const missing = await findMissingCursorSkills({
|
|
233
|
+
packageRoot: options?.packageRoot,
|
|
234
|
+
skillsHome,
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
shipped,
|
|
238
|
+
skillsHome,
|
|
239
|
+
directory,
|
|
240
|
+
leaves: shipped.map((skill) => skill.leafDir),
|
|
241
|
+
firstInstall: missing.length === shipped.length,
|
|
242
|
+
orphans: await orphanLeaves(directory, shipped),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** `Installed 9 agentkit skills`, what it dropped, and where it went. */
|
|
247
|
+
export function formatSkillInstall(
|
|
248
|
+
plan: SkillInstallPlan,
|
|
249
|
+
result: SkillInstallResult
|
|
250
|
+
): string {
|
|
251
|
+
const noun = result.installed.length === 1 ? "skill" : "skills";
|
|
252
|
+
const verb = plan.firstInstall ? "Installed" : "Updated";
|
|
253
|
+
const lines = [`${verb} ${result.installed.length} agentkit ${noun}`];
|
|
254
|
+
if (result.removed.length > 0) {
|
|
255
|
+
lines.push(` removed: ${result.removed.join(", ")}`);
|
|
256
|
+
}
|
|
257
|
+
lines.push(` location: ${plan.directory}`);
|
|
258
|
+
return `${lines.join("\n")}\n`;
|
|
259
|
+
}
|
|
260
|
+
|
|
194
261
|
export interface OfferInstallCursorSkillsOptions {
|
|
195
262
|
/** Skip when false (non-TTY / `--json`). */
|
|
196
263
|
interactive?: boolean;
|
|
197
264
|
packageRoot?: string;
|
|
198
265
|
skillsHome?: string;
|
|
199
|
-
/** Injected yes/no for tests. */
|
|
200
|
-
confirm?: (question: string) => Promise<boolean>;
|
|
266
|
+
/** Injected yes/no for tests. `undefined` means the prompt went unanswered. */
|
|
267
|
+
confirm?: (question: string) => Promise<boolean | undefined>;
|
|
201
268
|
out?: (text: string) => void;
|
|
202
269
|
}
|
|
203
270
|
|
|
204
271
|
/**
|
|
205
272
|
* On a TTY, offer to install/update every shipped package skill into
|
|
206
273
|
* ~/.cursor/skills/agentkit (always overwriting). No-op when non-interactive
|
|
207
|
-
* or the package ships no skills.
|
|
274
|
+
* or the package ships no skills. An unanswered prompt skips the install
|
|
275
|
+
* rather than blocking the caller — see `SKILL_PROMPT_TIMEOUT_MS`.
|
|
208
276
|
*/
|
|
209
277
|
export async function offerInstallCursorSkills(
|
|
210
278
|
options: OfferInstallCursorSkillsOptions = {}
|
|
@@ -213,48 +281,60 @@ export async function offerInstallCursorSkills(
|
|
|
213
281
|
const interactive =
|
|
214
282
|
options.interactive ??
|
|
215
283
|
(process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
216
|
-
|
|
217
|
-
if (shipped.length === 0) {
|
|
284
|
+
if (!interactive) {
|
|
218
285
|
return { installed: [], skipped: true };
|
|
219
286
|
}
|
|
220
287
|
|
|
221
|
-
const
|
|
222
|
-
const targetDir = agentkitCursorSkillsDir(home);
|
|
223
|
-
const missing = await findMissingCursorSkills({
|
|
288
|
+
const plan = await planSkillInstall({
|
|
224
289
|
packageRoot: options.packageRoot,
|
|
225
|
-
skillsHome:
|
|
290
|
+
skillsHome: options.skillsHome,
|
|
226
291
|
});
|
|
227
|
-
|
|
228
|
-
const noun = shipped.length === 1 ? "skill" : "skills";
|
|
229
|
-
const verb = missing.length === shipped.length ? "Install" : "Update";
|
|
230
|
-
const question = `${verb} ${shipped.length} agentkit ${noun} in ${targetDir}?`;
|
|
231
|
-
|
|
232
|
-
if (!interactive) {
|
|
292
|
+
if (plan.shipped.length === 0) {
|
|
233
293
|
return { installed: [], skipped: true };
|
|
234
294
|
}
|
|
235
295
|
|
|
296
|
+
const noun = plan.shipped.length === 1 ? "skill" : "skills";
|
|
297
|
+
const verb = plan.firstInstall ? "Install" : "Update";
|
|
236
298
|
const c = stdoutPalette();
|
|
237
|
-
out(
|
|
238
|
-
|
|
239
|
-
|
|
299
|
+
out(
|
|
300
|
+
`${verb} ${plan.shipped.length} agentkit ${noun} in ${plan.directory}?\n`
|
|
301
|
+
);
|
|
302
|
+
out(c.dim(` ${plan.leaves.join(", ")}\n`));
|
|
303
|
+
if (!plan.firstInstall) {
|
|
240
304
|
out(c.dim(" (overwrites existing copies with the package version)\n"));
|
|
241
305
|
}
|
|
306
|
+
if (plan.orphans.length > 0) {
|
|
307
|
+
out(c.dim(` (deletes ${plan.orphans.join(", ")})\n`));
|
|
308
|
+
}
|
|
242
309
|
|
|
243
310
|
const confirm =
|
|
244
|
-
options.confirm ??
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
311
|
+
options.confirm ??
|
|
312
|
+
((q) =>
|
|
313
|
+
promptYesNo(q, {
|
|
314
|
+
defaultYes: true,
|
|
315
|
+
out,
|
|
316
|
+
timeoutMs: SKILL_PROMPT_TIMEOUT_MS,
|
|
317
|
+
}));
|
|
318
|
+
const accepted = await confirm(`${verb} now?`);
|
|
319
|
+
|
|
320
|
+
if (accepted !== true) {
|
|
321
|
+
// `undefined` is the prompt timing out or stdin closing; both mean nobody
|
|
322
|
+
// said yes, so do not claim a duration neither case is guaranteed to have.
|
|
323
|
+
const why =
|
|
324
|
+
accepted === undefined
|
|
325
|
+
? "No answer — skipped the skill install."
|
|
326
|
+
: "Skipped skill install.";
|
|
327
|
+
out(
|
|
328
|
+
c.dim(
|
|
329
|
+
`${why}\nInstall later: ${CLI_COMMAND_NAME} install-skills (${plan.directory})\n\n`
|
|
330
|
+
)
|
|
331
|
+
);
|
|
250
332
|
return { installed: [], skipped: true };
|
|
251
333
|
}
|
|
252
334
|
|
|
253
|
-
const
|
|
254
|
-
out(
|
|
255
|
-
|
|
256
|
-
);
|
|
257
|
-
return { installed, skipped: false };
|
|
335
|
+
const result = await installCursorSkills(plan.shipped, plan.skillsHome);
|
|
336
|
+
out(`${formatSkillInstall(plan, result)}\n`);
|
|
337
|
+
return { installed: result.installed, skipped: false };
|
|
258
338
|
}
|
|
259
339
|
|
|
260
340
|
function parseSkillName(skillMd: string): string | undefined {
|
|
@@ -274,19 +354,34 @@ function parseSkillName(skillMd: string): string | undefined {
|
|
|
274
354
|
return raw.replace(/^["']|["']$/g, "");
|
|
275
355
|
}
|
|
276
356
|
|
|
277
|
-
|
|
357
|
+
export interface PromptYesNoOptions {
|
|
358
|
+
defaultYes: boolean;
|
|
359
|
+
out: (text: string) => void;
|
|
360
|
+
/** Give up after this long; `0` (or omitted) waits forever. */
|
|
361
|
+
timeoutMs?: number;
|
|
362
|
+
/** Answer source; defaults to `process.stdin`. */
|
|
363
|
+
input?: NodeJS.ReadableStream;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Ask a yes/no question. Resolves `undefined` when the answer never comes —
|
|
368
|
+
* the prompt timed out, or stdin closed on an empty line.
|
|
369
|
+
*/
|
|
370
|
+
export async function promptYesNo(
|
|
278
371
|
question: string,
|
|
279
|
-
options:
|
|
280
|
-
|
|
281
|
-
out: (text: string) => void;
|
|
282
|
-
}
|
|
283
|
-
): Promise<boolean> {
|
|
372
|
+
options: PromptYesNoOptions
|
|
373
|
+
): Promise<boolean | undefined> {
|
|
284
374
|
const hint = options.defaultYes ? "Y/n" : "y/N";
|
|
375
|
+
const input = options.input ?? process.stdin;
|
|
285
376
|
options.out(`${question} [${hint}] `);
|
|
286
|
-
|
|
287
|
-
const line = await readOneLine(
|
|
288
|
-
|
|
289
|
-
if (line === undefined
|
|
377
|
+
input.resume();
|
|
378
|
+
const line = await readOneLine(input, options.timeoutMs);
|
|
379
|
+
input.pause();
|
|
380
|
+
if (line === undefined) {
|
|
381
|
+
options.out("\n");
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
if (line.trim() === "") {
|
|
290
385
|
return options.defaultYes;
|
|
291
386
|
}
|
|
292
387
|
const answer = line.trim().toLowerCase();
|
|
@@ -299,11 +394,14 @@ async function promptYesNo(
|
|
|
299
394
|
return options.defaultYes;
|
|
300
395
|
}
|
|
301
396
|
|
|
397
|
+
/** The line, or `undefined` when the stream ends or the clock runs out first. */
|
|
302
398
|
function readOneLine(
|
|
303
|
-
stream: NodeJS.ReadableStream
|
|
399
|
+
stream: NodeJS.ReadableStream,
|
|
400
|
+
timeoutMs?: number
|
|
304
401
|
): Promise<string | undefined> {
|
|
305
402
|
return new Promise((resolve) => {
|
|
306
403
|
let buffer = "";
|
|
404
|
+
let timer: NodeJS.Timeout | undefined;
|
|
307
405
|
const onData = (chunk: string | Buffer): void => {
|
|
308
406
|
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
309
407
|
const newline = buffer.indexOf("\n");
|
|
@@ -313,15 +411,25 @@ function readOneLine(
|
|
|
313
411
|
cleanup();
|
|
314
412
|
resolve(buffer.slice(0, newline).replace(/\r$/, ""));
|
|
315
413
|
};
|
|
316
|
-
|
|
414
|
+
// EOF and the timeout both land here. A tty holds a half-typed line until
|
|
415
|
+
// Enter, so a non-empty buffer only ever happens on piped input.
|
|
416
|
+
const giveUp = (): void => {
|
|
317
417
|
cleanup();
|
|
318
418
|
resolve(buffer === "" ? undefined : buffer.replace(/\r$/, ""));
|
|
319
419
|
};
|
|
320
420
|
const cleanup = (): void => {
|
|
421
|
+
if (timer !== undefined) {
|
|
422
|
+
clearTimeout(timer);
|
|
423
|
+
}
|
|
321
424
|
stream.off("data", onData);
|
|
322
|
-
stream.off("end",
|
|
425
|
+
stream.off("end", giveUp);
|
|
323
426
|
};
|
|
324
427
|
stream.on("data", onData);
|
|
325
|
-
stream.on("end",
|
|
428
|
+
stream.on("end", giveUp);
|
|
429
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
430
|
+
timer = setTimeout(giveUp, timeoutMs);
|
|
431
|
+
// Never keep the process alive purely to wait for an answer.
|
|
432
|
+
timer.unref();
|
|
433
|
+
}
|
|
326
434
|
});
|
|
327
435
|
}
|