@cursor/july 0.1.18 → 0.1.19
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/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.CrsWMchO.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BnHRjfoe.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.CNCzfUKC.js → VPLocalSearchBox.D1JqzSh8.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CwKvMSWv.js → theme.DaBvZYwl.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-DRjS5klQ.js → index-C2SU2xV5.js} +23 -23
- package/dist/playground/assets/index-CidizGZv.css +1 -0
- package/dist/playground/index.html +2 -2
- package/package.json +1 -1
- 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-TCDs-UhZ.css +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install-cursor-skills.d.ts","sourceRoot":"","sources":["../../src/internal/install-cursor-skills.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,+EAA+E;AAC/E,eAAO,MAAM,uBAAuB,aAAa,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,GAAE,MAAsC,GAC3C,MAAM,CAGR;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CACrC,UAAU,GAAE,MAA2B,GACtC,MAAM,CAER;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,EACpC,UAAU,GAAE,MAA2B,GACtC,MAAM,CAER;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,WAAW,GAAE,MAAyB,GACrC,OAAO,CAAC,YAAY,EAAE,CAAC,CA2BzB;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,CAAC,EAAE;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAY1B;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,SAAS,YAAY,EAAE,EAC/B,UAAU,GAAE,MAA2B,GACtC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"install-cursor-skills.d.ts","sourceRoot":"","sources":["../../src/internal/install-cursor-skills.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,12 +18,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
18
18
|
import { access, cp, mkdir, readdir, readFile, rm, writeFile, } 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
|
/** Monorepo-only skill; excluded from the published package. */
|
|
24
24
|
const SKIP_SKILL_DIRS = new Set(["operate"]);
|
|
25
25
|
/** Category folder under ~/.cursor/skills (Cursor discovers nested skills). */
|
|
26
26
|
export const CURSOR_SKILLS_NAMESPACE = "agentkit";
|
|
27
|
+
/** How long the `init` offer waits for an answer before skipping the install. */
|
|
28
|
+
export const SKILL_PROMPT_TIMEOUT_MS = 45000;
|
|
27
29
|
export function cursorSkillsHome(home) {
|
|
28
30
|
var _a;
|
|
29
31
|
if (home === void 0) { home = (_a = process.env.HOME) !== null && _a !== void 0 ? _a : homedir(); }
|
|
@@ -105,21 +107,9 @@ export function installCursorSkills(skills_1) {
|
|
|
105
107
|
return __awaiter(this, arguments, void 0, function* (skills, skillsHome = cursorSkillsHome()) {
|
|
106
108
|
const namespaceDir = agentkitCursorSkillsDir(skillsHome);
|
|
107
109
|
yield mkdir(namespaceDir, { recursive: true });
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const entries = yield readdir(namespaceDir, { withFileTypes: true });
|
|
112
|
-
existingLeaves = entries
|
|
113
|
-
.filter((entry) => entry.isDirectory())
|
|
114
|
-
.map((entry) => entry.name);
|
|
115
|
-
}
|
|
116
|
-
catch (_a) {
|
|
117
|
-
existingLeaves = [];
|
|
118
|
-
}
|
|
119
|
-
for (const leaf of existingLeaves) {
|
|
120
|
-
if (!shippedLeaves.has(leaf)) {
|
|
121
|
-
yield rm(join(namespaceDir, leaf), { recursive: true, force: true });
|
|
122
|
-
}
|
|
110
|
+
const removed = yield orphanLeaves(namespaceDir, skills);
|
|
111
|
+
for (const leaf of removed) {
|
|
112
|
+
yield rm(join(namespaceDir, leaf), { recursive: true, force: true });
|
|
123
113
|
}
|
|
124
114
|
const installed = [];
|
|
125
115
|
for (const skill of skills) {
|
|
@@ -133,7 +123,23 @@ export function installCursorSkills(skills_1) {
|
|
|
133
123
|
}
|
|
134
124
|
installed.push(skill.leafDir);
|
|
135
125
|
}
|
|
136
|
-
return installed;
|
|
126
|
+
return { installed, removed };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** Installed leaf dirs the package no longer ships. Installing deletes these. */
|
|
130
|
+
function orphanLeaves(namespaceDir, skills) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
const shipped = new Set(skills.map((skill) => skill.leafDir));
|
|
133
|
+
let entries;
|
|
134
|
+
try {
|
|
135
|
+
entries = yield readdir(namespaceDir, { withFileTypes: true });
|
|
136
|
+
}
|
|
137
|
+
catch (_a) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
return entries
|
|
141
|
+
.filter((entry) => entry.isDirectory() && !shipped.has(entry.name))
|
|
142
|
+
.map((entry) => entry.name);
|
|
137
143
|
});
|
|
138
144
|
}
|
|
139
145
|
/** Drop monorepo `paths` globs and fix package doc links for ~/.cursor installs. */
|
|
@@ -166,48 +172,88 @@ function stripPathsFromFrontmatter(frontmatter) {
|
|
|
166
172
|
}
|
|
167
173
|
return out.join("\n");
|
|
168
174
|
}
|
|
175
|
+
/** What an install would do, for the offer and for `install-skills`. */
|
|
176
|
+
export function planSkillInstall(options) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
var _a;
|
|
179
|
+
const skillsHome = (_a = options === null || options === void 0 ? void 0 : options.skillsHome) !== null && _a !== void 0 ? _a : cursorSkillsHome();
|
|
180
|
+
const directory = agentkitCursorSkillsDir(skillsHome);
|
|
181
|
+
const shipped = yield listShippedPackageSkills(options === null || options === void 0 ? void 0 : options.packageRoot);
|
|
182
|
+
const missing = yield findMissingCursorSkills({
|
|
183
|
+
packageRoot: options === null || options === void 0 ? void 0 : options.packageRoot,
|
|
184
|
+
skillsHome,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
shipped,
|
|
188
|
+
skillsHome,
|
|
189
|
+
directory,
|
|
190
|
+
leaves: shipped.map((skill) => skill.leafDir),
|
|
191
|
+
firstInstall: missing.length === shipped.length,
|
|
192
|
+
orphans: yield orphanLeaves(directory, shipped),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
/** `Installed 9 agentkit skills`, what it dropped, and where it went. */
|
|
197
|
+
export function formatSkillInstall(plan, result) {
|
|
198
|
+
const noun = result.installed.length === 1 ? "skill" : "skills";
|
|
199
|
+
const verb = plan.firstInstall ? "Installed" : "Updated";
|
|
200
|
+
const lines = [`${verb} ${result.installed.length} agentkit ${noun}`];
|
|
201
|
+
if (result.removed.length > 0) {
|
|
202
|
+
lines.push(` removed: ${result.removed.join(", ")}`);
|
|
203
|
+
}
|
|
204
|
+
lines.push(` location: ${plan.directory}`);
|
|
205
|
+
return `${lines.join("\n")}\n`;
|
|
206
|
+
}
|
|
169
207
|
/**
|
|
170
208
|
* On a TTY, offer to install/update every shipped package skill into
|
|
171
209
|
* ~/.cursor/skills/agentkit (always overwriting). No-op when non-interactive
|
|
172
|
-
* or the package ships no skills.
|
|
210
|
+
* or the package ships no skills. An unanswered prompt skips the install
|
|
211
|
+
* rather than blocking the caller — see `SKILL_PROMPT_TIMEOUT_MS`.
|
|
173
212
|
*/
|
|
174
213
|
export function offerInstallCursorSkills() {
|
|
175
214
|
return __awaiter(this, arguments, void 0, function* (options = {}) {
|
|
176
|
-
var _a, _b, _c
|
|
215
|
+
var _a, _b, _c;
|
|
177
216
|
const out = (_a = options.out) !== null && _a !== void 0 ? _a : ((text) => process.stdout.write(text));
|
|
178
217
|
const interactive = (_b = options.interactive) !== null && _b !== void 0 ? _b : (process.stdin.isTTY === true && process.stdout.isTTY === true);
|
|
179
|
-
|
|
180
|
-
if (shipped.length === 0) {
|
|
218
|
+
if (!interactive) {
|
|
181
219
|
return { installed: [], skipped: true };
|
|
182
220
|
}
|
|
183
|
-
const
|
|
184
|
-
const targetDir = agentkitCursorSkillsDir(home);
|
|
185
|
-
const missing = yield findMissingCursorSkills({
|
|
221
|
+
const plan = yield planSkillInstall({
|
|
186
222
|
packageRoot: options.packageRoot,
|
|
187
|
-
skillsHome:
|
|
223
|
+
skillsHome: options.skillsHome,
|
|
188
224
|
});
|
|
189
|
-
|
|
190
|
-
const noun = shipped.length === 1 ? "skill" : "skills";
|
|
191
|
-
const verb = missing.length === shipped.length ? "Install" : "Update";
|
|
192
|
-
const question = `${verb} ${shipped.length} agentkit ${noun} in ${targetDir}?`;
|
|
193
|
-
if (!interactive) {
|
|
225
|
+
if (plan.shipped.length === 0) {
|
|
194
226
|
return { installed: [], skipped: true };
|
|
195
227
|
}
|
|
228
|
+
const noun = plan.shipped.length === 1 ? "skill" : "skills";
|
|
229
|
+
const verb = plan.firstInstall ? "Install" : "Update";
|
|
196
230
|
const c = stdoutPalette();
|
|
197
|
-
out(`${
|
|
198
|
-
out(c.dim(` ${
|
|
199
|
-
if (
|
|
231
|
+
out(`${verb} ${plan.shipped.length} agentkit ${noun} in ${plan.directory}?\n`);
|
|
232
|
+
out(c.dim(` ${plan.leaves.join(", ")}\n`));
|
|
233
|
+
if (!plan.firstInstall) {
|
|
200
234
|
out(c.dim(" (overwrites existing copies with the package version)\n"));
|
|
201
235
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
if (plan.orphans.length > 0) {
|
|
237
|
+
out(c.dim(` (deletes ${plan.orphans.join(", ")})\n`));
|
|
238
|
+
}
|
|
239
|
+
const confirm = (_c = options.confirm) !== null && _c !== void 0 ? _c : ((q) => promptYesNo(q, {
|
|
240
|
+
defaultYes: true,
|
|
241
|
+
out,
|
|
242
|
+
timeoutMs: SKILL_PROMPT_TIMEOUT_MS,
|
|
243
|
+
}));
|
|
244
|
+
const accepted = yield confirm(`${verb} now?`);
|
|
245
|
+
if (accepted !== true) {
|
|
246
|
+
// `undefined` is the prompt timing out or stdin closing; both mean nobody
|
|
247
|
+
// said yes, so do not claim a duration neither case is guaranteed to have.
|
|
248
|
+
const why = accepted === undefined
|
|
249
|
+
? "No answer — skipped the skill install."
|
|
250
|
+
: "Skipped skill install.";
|
|
251
|
+
out(c.dim(`${why}\nInstall later: ${CLI_COMMAND_NAME} install-skills (${plan.directory})\n\n`));
|
|
206
252
|
return { installed: [], skipped: true };
|
|
207
253
|
}
|
|
208
|
-
const
|
|
209
|
-
out(`${
|
|
210
|
-
return { installed, skipped: false };
|
|
254
|
+
const result = yield installCursorSkills(plan.shipped, plan.skillsHome);
|
|
255
|
+
out(`${formatSkillInstall(plan, result)}\n`);
|
|
256
|
+
return { installed: result.installed, skipped: false };
|
|
211
257
|
});
|
|
212
258
|
}
|
|
213
259
|
function parseSkillName(skillMd) {
|
|
@@ -228,14 +274,24 @@ function parseSkillName(skillMd) {
|
|
|
228
274
|
// Strip optional quotes around the YAML scalar.
|
|
229
275
|
return raw.replace(/^["']|["']$/g, "");
|
|
230
276
|
}
|
|
231
|
-
|
|
277
|
+
/**
|
|
278
|
+
* Ask a yes/no question. Resolves `undefined` when the answer never comes —
|
|
279
|
+
* the prompt timed out, or stdin closed on an empty line.
|
|
280
|
+
*/
|
|
281
|
+
export function promptYesNo(question, options) {
|
|
232
282
|
return __awaiter(this, void 0, void 0, function* () {
|
|
283
|
+
var _a;
|
|
233
284
|
const hint = options.defaultYes ? "Y/n" : "y/N";
|
|
285
|
+
const input = (_a = options.input) !== null && _a !== void 0 ? _a : process.stdin;
|
|
234
286
|
options.out(`${question} [${hint}] `);
|
|
235
|
-
|
|
236
|
-
const line = yield readOneLine(
|
|
237
|
-
|
|
238
|
-
if (line === undefined
|
|
287
|
+
input.resume();
|
|
288
|
+
const line = yield readOneLine(input, options.timeoutMs);
|
|
289
|
+
input.pause();
|
|
290
|
+
if (line === undefined) {
|
|
291
|
+
options.out("\n");
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
if (line.trim() === "") {
|
|
239
295
|
return options.defaultYes;
|
|
240
296
|
}
|
|
241
297
|
const answer = line.trim().toLowerCase();
|
|
@@ -248,9 +304,11 @@ function promptYesNo(question, options) {
|
|
|
248
304
|
return options.defaultYes;
|
|
249
305
|
});
|
|
250
306
|
}
|
|
251
|
-
|
|
307
|
+
/** The line, or `undefined` when the stream ends or the clock runs out first. */
|
|
308
|
+
function readOneLine(stream, timeoutMs) {
|
|
252
309
|
return new Promise((resolve) => {
|
|
253
310
|
let buffer = "";
|
|
311
|
+
let timer;
|
|
254
312
|
const onData = (chunk) => {
|
|
255
313
|
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
256
314
|
const newline = buffer.indexOf("\n");
|
|
@@ -260,15 +318,25 @@ function readOneLine(stream) {
|
|
|
260
318
|
cleanup();
|
|
261
319
|
resolve(buffer.slice(0, newline).replace(/\r$/, ""));
|
|
262
320
|
};
|
|
263
|
-
|
|
321
|
+
// EOF and the timeout both land here. A tty holds a half-typed line until
|
|
322
|
+
// Enter, so a non-empty buffer only ever happens on piped input.
|
|
323
|
+
const giveUp = () => {
|
|
264
324
|
cleanup();
|
|
265
325
|
resolve(buffer === "" ? undefined : buffer.replace(/\r$/, ""));
|
|
266
326
|
};
|
|
267
327
|
const cleanup = () => {
|
|
328
|
+
if (timer !== undefined) {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
}
|
|
268
331
|
stream.off("data", onData);
|
|
269
|
-
stream.off("end",
|
|
332
|
+
stream.off("end", giveUp);
|
|
270
333
|
};
|
|
271
334
|
stream.on("data", onData);
|
|
272
|
-
stream.on("end",
|
|
335
|
+
stream.on("end", giveUp);
|
|
336
|
+
if (timeoutMs !== undefined && timeoutMs > 0) {
|
|
337
|
+
timer = setTimeout(giveUp, timeoutMs);
|
|
338
|
+
// Never keep the process alive purely to wait for an answer.
|
|
339
|
+
timer.unref();
|
|
340
|
+
}
|
|
273
341
|
});
|
|
274
342
|
}
|