lace-mcp 0.0.2-alpha.3 → 0.0.2-alpha.5

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 (2) hide show
  1. package/dist/cli.js +82 -58
  2. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -256,17 +256,37 @@ import { execSync as execSync2 } from "node:child_process";
256
256
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
257
257
  import { homedir as homedir2 } from "node:os";
258
258
  import { join as join3, resolve } from "node:path";
259
+ import { createInterface } from "node:readline";
259
260
  function parseFlags(args) {
260
261
  const flags = {};
261
262
  for (let i = 0; i < args.length; i++) {
262
263
  if (args[i] === "--token" && args[i + 1]) flags.token = args[++i];
263
264
  else if (args[i] === "--org" && args[i + 1]) flags.org = args[++i];
264
265
  else if (args[i] === "--api-base" && args[i + 1]) flags.apiBase = args[++i];
265
- }
266
- return flags;
266
+ else if (args[i] === "--scope" && args[i + 1]) flags.scope = args[++i];
267
+ }
268
+ const scope = SCOPES.includes(flags.scope) ? flags.scope : void 0;
269
+ return { ...flags, scope };
270
+ }
271
+ function promptScope(target) {
272
+ const available = target === "claude" ? SCOPES : ["user", "project"];
273
+ return new Promise((promptResolve) => {
274
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
275
+ console.log("\n Where should Lace be installed?\n");
276
+ for (let i = 0; i < available.length; i++) {
277
+ console.log(` ${i + 1}. ${SCOPE_LABELS[available[i]]}`);
278
+ }
279
+ rl.question("\n > ", (answer) => {
280
+ rl.close();
281
+ const idx = parseInt(answer.trim(), 10) - 1;
282
+ const scope = available[idx] ?? available[0];
283
+ if (!available[idx]) console.log(` Defaulting to ${scope}`);
284
+ console.log("");
285
+ promptResolve(scope);
286
+ });
287
+ });
267
288
  }
268
- async function resolveCredentials(args) {
269
- const flags = parseFlags(args);
289
+ async function resolveCredentials(flags) {
270
290
  if (flags.token && flags.org) {
271
291
  return { apiBase: flags.apiBase ?? DEFAULT_API_BASE, token: flags.token, orgId: flags.org };
272
292
  }
@@ -280,12 +300,19 @@ function credentialsToEnv(creds) {
280
300
  };
281
301
  }
282
302
  function extractSkillVersion(content) {
283
- const match = content.match(/<!-- lace-mcp-skill-version: (\d+) -->/);
303
+ const match = content.match(/lace-mcp-skill-version: (\d+)/);
284
304
  return match ? parseInt(match[1], 10) : 0;
285
305
  }
286
- function prependVersion(content) {
287
- return `<!-- lace-mcp-skill-version: ${SKILL_VERSION} -->
288
- ${content}`;
306
+ function injectVersion(content) {
307
+ if (content.startsWith("---\n")) {
308
+ return content.replace(/^---\n/, `---
309
+ lace-mcp-skill-version: ${SKILL_VERSION}
310
+ `);
311
+ }
312
+ return `${content.trimEnd()}
313
+
314
+ <!-- lace-mcp-skill-version: ${SKILL_VERSION} -->
315
+ `;
289
316
  }
290
317
  function loadSkillContent() {
291
318
  const skillPath = join3(resolve(import.meta.dirname, ".."), "SKILL.md");
@@ -331,64 +358,48 @@ alwaysApply: false
331
358
 
332
359
  ${body}`;
333
360
  }
334
- function installClaudeSkill() {
361
+ function installSkillFile(dir, filename, label, transform2) {
335
362
  const content = loadSkillContent();
336
363
  if (!content) return;
337
- const skillDir = join3(homedir2(), ".claude", "skills", "lace");
338
- const skillPath = join3(skillDir, "SKILL.md");
339
- if (existsSync2(skillPath)) {
340
- const existing = readFileSync2(skillPath, "utf8");
364
+ const filePath = join3(dir, filename);
365
+ if (existsSync2(filePath)) {
366
+ const existing = readFileSync2(filePath, "utf8");
341
367
  if (extractSkillVersion(existing) >= SKILL_VERSION) {
342
- console.log(" \u2713 /lace skill already up to date");
368
+ console.log(` \u2713 ${label} already up to date`);
343
369
  return;
344
370
  }
345
371
  }
346
- mkdirSync2(skillDir, { recursive: true });
347
- writeFileSync2(skillPath, prependVersion(content.trimStart()));
348
- console.log(` \u2713 /lace skill written to ${skillPath}`);
372
+ mkdirSync2(dir, { recursive: true });
373
+ writeFileSync2(filePath, injectVersion(transform2(content)));
374
+ console.log(` \u2713 ${label} written to ${filePath}`);
375
+ }
376
+ function installClaudeSkill() {
377
+ installSkillFile(join3(homedir2(), ".claude", "skills", "lace"), "SKILL.md", "/lace skill", (c) => c.trimStart());
349
378
  }
350
379
  function installCursorRule() {
351
- const content = loadSkillContent();
352
- if (!content) return;
353
- const rulesDir = join3(homedir2(), ".cursor", "rules");
354
- const rulePath = join3(rulesDir, "lace.mdc");
355
- if (existsSync2(rulePath)) {
356
- const existing = readFileSync2(rulePath, "utf8");
357
- if (extractSkillVersion(existing) >= SKILL_VERSION) {
358
- console.log(" \u2713 Cursor rule already up to date");
359
- return;
360
- }
361
- }
362
- mkdirSync2(rulesDir, { recursive: true });
363
- writeFileSync2(rulePath, prependVersion(buildCursorRule(extractSkillBody(content))));
364
- console.log(` \u2713 Cursor rule written to ${rulePath}`);
380
+ installSkillFile(
381
+ join3(homedir2(), ".cursor", "rules"),
382
+ "lace.mdc",
383
+ "Cursor rule",
384
+ (c) => buildCursorRule(extractSkillBody(c))
385
+ );
365
386
  }
366
387
  function installCodexSkill() {
367
- const content = loadSkillContent();
368
- if (!content) return;
369
- const skillDir = join3(homedir2(), ".agents", "skills", "lace");
370
- const skillPath = join3(skillDir, "SKILL.md");
371
- if (existsSync2(skillPath)) {
372
- const existing = readFileSync2(skillPath, "utf8");
373
- if (extractSkillVersion(existing) >= SKILL_VERSION) {
374
- console.log(" \u2713 Codex skill already up to date");
375
- return;
376
- }
377
- }
378
- mkdirSync2(skillDir, { recursive: true });
379
- writeFileSync2(skillPath, prependVersion(extractSkillBody(content)));
380
- console.log(` \u2713 Codex skill written to ${skillPath}`);
388
+ installSkillFile(join3(homedir2(), ".agents", "skills", "lace"), "SKILL.md", "Codex skill", extractSkillBody);
381
389
  }
382
390
  function mcpArgs(editor) {
383
391
  return ["-y", `${PACKAGE_NAME}@latest`, "mcp", "--editor", editor];
384
392
  }
385
- function installClaude(env) {
393
+ function installClaude(env, scope = "user") {
386
394
  try {
387
- const envFlags = Object.entries(env).map(([key, value]) => `--env ${key}=${value}`).join(" ");
388
- execSync2(`claude mcp add --transport stdio --scope user ${envFlags} lace -- npx ${mcpArgs("claude").join(" ")}`, {
389
- stdio: "inherit"
390
- });
391
- console.log(" \u2713 MCP server registered via claude CLI");
395
+ const envFlags = Object.entries(env).map(([key, value]) => `-e ${key}=${value}`).join(" ");
396
+ execSync2(
397
+ `claude mcp add --transport stdio --scope ${scope} ${envFlags} lace -- npx ${mcpArgs("claude").join(" ")}`,
398
+ {
399
+ stdio: "inherit"
400
+ }
401
+ );
402
+ console.log(` \u2713 MCP server registered via claude CLI (scope: ${scope})`);
392
403
  } catch {
393
404
  const configPath = getEditorConfigPath("claude");
394
405
  writeJsonMcpEntry(configPath, { command: "npx", args: mcpArgs("claude"), env });
@@ -401,8 +412,12 @@ function installClaudeDesktop(env) {
401
412
  writeJsonMcpEntry(configPath, { command: npx, args: mcpArgs("claude-desktop"), env });
402
413
  installClaudeSkill();
403
414
  }
404
- function installCursor(env) {
405
- const configPath = getEditorConfigPath("cursor");
415
+ function getCursorConfigPath(scope) {
416
+ if (scope === "project") return join3(process.cwd(), ".cursor", "mcp.json");
417
+ return getEditorConfigPath("cursor");
418
+ }
419
+ function installCursor(env, scope = "user") {
420
+ const configPath = getCursorConfigPath(scope);
406
421
  writeJsonMcpEntry(configPath, { command: "npx", args: mcpArgs("cursor"), env });
407
422
  installCursorRule();
408
423
  }
@@ -428,20 +443,23 @@ async function main() {
428
443
  const args = process.argv.slice(2);
429
444
  const target = args[0] === "install" ? args[1] : args[0];
430
445
  if (!target || !TARGETS.includes(target)) {
431
- console.error(`Usage: npx ${PACKAGE_NAME} install <${TARGETS.join("|")}>`);
446
+ console.error(`Usage: npx ${PACKAGE_NAME} install <${TARGETS.join("|")}> [--scope user|project|local]`);
432
447
  process.exit(1);
433
448
  }
449
+ const flags = parseFlags(args);
434
450
  console.log(`
435
451
  Installing Lace MCP for ${target}...`);
436
- const credentials = await resolveCredentials(args);
452
+ const credentials = await resolveCredentials(flags);
437
453
  const env = credentialsToEnv(credentials);
438
- if (target === "claude") installClaude(env);
454
+ const scopeTargets = ["claude", "cursor"];
455
+ const scope = flags.scope ?? (scopeTargets.includes(target) ? await promptScope(target) : "user");
456
+ if (target === "claude") installClaude(env, scope);
439
457
  else if (target === "claude-desktop") installClaudeDesktop(env);
440
- else if (target === "cursor") installCursor(env);
458
+ else if (target === "cursor") installCursor(env, scope);
441
459
  else installCodex(env);
442
460
  console.log('\nDone. Start a new session and say "implement the Lace decision" or type /lace.\n');
443
461
  }
444
- var TARGETS, PACKAGE_NAME, SKILL_VERSION;
462
+ var TARGETS, PACKAGE_NAME, SCOPES, SCOPE_LABELS, SKILL_VERSION;
445
463
  var init_install = __esm({
446
464
  "src/install.ts"() {
447
465
  "use strict";
@@ -450,6 +468,12 @@ var init_install = __esm({
450
468
  init_paths();
451
469
  TARGETS = ["claude", "claude-desktop", "cursor", "codex"];
452
470
  PACKAGE_NAME = "lace-mcp";
471
+ SCOPES = ["user", "project", "local"];
472
+ SCOPE_LABELS = {
473
+ user: "User \u2014 available in all projects",
474
+ project: "Project \u2014 this project only (shared via .mcp.json)",
475
+ local: "Local \u2014 this machine only (not shared)"
476
+ };
453
477
  SKILL_VERSION = 1;
454
478
  }
455
479
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lace-mcp",
3
- "version": "0.0.2-alpha.3",
3
+ "version": "0.0.2-alpha.5",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist/cli.js",
@@ -27,6 +27,7 @@
27
27
  "install-claude-desktop": "tsx src/cli.ts install claude-desktop",
28
28
  "install-cursor": "tsx src/cli.ts install cursor",
29
29
  "install-codex": "tsx src/cli.ts install codex",
30
- "build": "rm -rf dist && esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js"
30
+ "build": "rm -rf dist && esbuild src/cli.ts --bundle --platform=node --format=esm --outfile=dist/cli.js",
31
+ "build:mcpb": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=mcpb/server/index.js && npx @anthropic-ai/mcpb pack mcpb dist/lace.mcpb"
31
32
  }
32
33
  }