@aipm-registry/cli 0.4.1 → 0.4.8

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.cjs CHANGED
@@ -6,8 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __esm = (fn, res) => function __init() {
10
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ var __esm = (fn, res, err) => function __init() {
10
+ if (err) throw err[0];
11
+ try {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ } catch (e) {
14
+ throw err = [e], e;
15
+ }
11
16
  };
12
17
  var __export = (target, all) => {
13
18
  for (var name in all)
@@ -3837,7 +3842,7 @@ var init_types = __esm({
3837
3842
  ...processCreateParams(params)
3838
3843
  });
3839
3844
  };
3840
- BRAND = Symbol("zod_brand");
3845
+ BRAND = /* @__PURE__ */ Symbol("zod_brand");
3841
3846
  ZodBranded = class extends ZodType {
3842
3847
  _parse(input2) {
3843
3848
  const { ctx } = this._processInputParams(input2);
@@ -4011,14 +4016,14 @@ var init_types = __esm({
4011
4016
  onumber = () => numberType().optional();
4012
4017
  oboolean = () => booleanType().optional();
4013
4018
  coerce = {
4014
- string: (arg) => ZodString.create({ ...arg, coerce: true }),
4015
- number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
4016
- boolean: (arg) => ZodBoolean.create({
4019
+ string: ((arg) => ZodString.create({ ...arg, coerce: true })),
4020
+ number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
4021
+ boolean: ((arg) => ZodBoolean.create({
4017
4022
  ...arg,
4018
4023
  coerce: true
4019
- }),
4020
- bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
4021
- date: (arg) => ZodDate.create({ ...arg, coerce: true })
4024
+ })),
4025
+ bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
4026
+ date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
4022
4027
  };
4023
4028
  NEVER = INVALID;
4024
4029
  }
@@ -4314,7 +4319,7 @@ var init_dist = __esm({
4314
4319
  // ../../packages/engine/dist/detect-tools.js
4315
4320
  async function pathExists(path2) {
4316
4321
  try {
4317
- await (0, import_promises.access)(path2);
4322
+ await (0, import_promises4.access)(path2);
4318
4323
  return true;
4319
4324
  } catch {
4320
4325
  return false;
@@ -4322,11 +4327,11 @@ async function pathExists(path2) {
4322
4327
  }
4323
4328
  async function detectToolsInProject(projectRoot) {
4324
4329
  const detected = [];
4325
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".cursor")))
4330
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".cursor")))
4326
4331
  detected.push("cursor");
4327
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".claude")))
4332
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".claude")))
4328
4333
  detected.push("claude");
4329
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".codex")))
4334
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".codex")))
4330
4335
  detected.push("codex");
4331
4336
  return detected;
4332
4337
  }
@@ -4337,7 +4342,6 @@ function manifestAllowsTool(manifest, tool) {
4337
4342
  }
4338
4343
  async function resolveInstallTools(options) {
4339
4344
  const { manifest } = options;
4340
- const expandedAllowed = expandTargets(manifest.targets);
4341
4345
  if (options.explicitTarget) {
4342
4346
  if (options.explicitTarget === "*") {
4343
4347
  return [...ALL_TOOLS];
@@ -4358,37 +4362,69 @@ async function resolveInstallTools(options) {
4358
4362
  const preferred = expandTargets(options.preferredTools ?? []).filter((t) => manifestAllowsTool(manifest, t));
4359
4363
  if (preferred.length > 0)
4360
4364
  return preferred;
4361
- if (manifest.targets.includes("*"))
4362
- return expandedAllowed;
4363
4365
  return [];
4364
4366
  }
4365
- var import_promises, import_node_path2;
4367
+ var import_promises4, import_node_path5;
4366
4368
  var init_detect_tools = __esm({
4367
4369
  "../../packages/engine/dist/detect-tools.js"() {
4368
4370
  "use strict";
4369
- import_promises = require("node:fs/promises");
4370
- import_node_path2 = require("node:path");
4371
+ import_promises4 = require("node:fs/promises");
4372
+ import_node_path5 = require("node:path");
4371
4373
  init_dist();
4372
4374
  }
4373
4375
  });
4374
4376
 
4375
- // ../../packages/adapter-claude/dist/index.js
4376
- var import_promises2, import_node_path3, ClaudeSkillAdapter, claudeSkillAdapter;
4377
+ // ../../packages/adapter-sdk/dist/index.js
4378
+ async function writeSkillDirectory(skillDir, input2) {
4379
+ const entryPath = (0, import_node_path6.join)(skillDir, "SKILL.md");
4380
+ const seen = /* @__PURE__ */ new Set(["skill.md"]);
4381
+ const files = (input2.supportingFiles ?? []).map((file) => {
4382
+ const normalized = (0, import_node_path6.normalize)(file.path);
4383
+ const target = (0, import_node_path6.resolve)(skillDir, normalized);
4384
+ const fromSkillDir = (0, import_node_path6.relative)(skillDir, target);
4385
+ return { file, normalized, target, fromSkillDir, key: fromSkillDir.toLowerCase() };
4386
+ });
4387
+ for (const { file, normalized, fromSkillDir, key } of files) {
4388
+ if (!normalized || normalized === "." || (0, import_node_path6.isAbsolute)(normalized) || fromSkillDir === ".." || fromSkillDir.startsWith(`..${import_node_path6.sep}`) || (0, import_node_path6.isAbsolute)(fromSkillDir) || seen.has(key)) {
4389
+ throw new Error(`Unsafe or duplicate skill supporting file path: ${file.path}`);
4390
+ }
4391
+ seen.add(key);
4392
+ }
4393
+ await (0, import_promises5.mkdir)(skillDir, { recursive: true });
4394
+ await (0, import_promises5.writeFile)(entryPath, input2.skillMarkdown, "utf8");
4395
+ const writtenPaths = [entryPath];
4396
+ for (const { file, target } of files) {
4397
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(target), { recursive: true });
4398
+ await (0, import_promises5.writeFile)(target, file.content);
4399
+ if (file.mode !== void 0)
4400
+ await (0, import_promises5.chmod)(target, file.mode & 511);
4401
+ writtenPaths.push(target);
4402
+ }
4403
+ return { writtenPaths };
4404
+ }
4405
+ var import_promises5, import_node_path6;
4377
4406
  var init_dist2 = __esm({
4407
+ "../../packages/adapter-sdk/dist/index.js"() {
4408
+ "use strict";
4409
+ import_promises5 = require("node:fs/promises");
4410
+ import_node_path6 = require("node:path");
4411
+ }
4412
+ });
4413
+
4414
+ // ../../packages/adapter-claude/dist/index.js
4415
+ var import_node_path7, ClaudeSkillAdapter, claudeSkillAdapter;
4416
+ var init_dist3 = __esm({
4378
4417
  "../../packages/adapter-claude/dist/index.js"() {
4379
4418
  "use strict";
4380
- import_promises2 = require("node:fs/promises");
4381
- import_node_path3 = require("node:path");
4419
+ import_node_path7 = require("node:path");
4420
+ init_dist2();
4382
4421
  init_dist();
4383
4422
  ClaudeSkillAdapter = class {
4384
4423
  tool = "claude";
4385
4424
  async installSkill(input2) {
4386
4425
  const short = shortNameFromScopeName(input2.packageName);
4387
- const skillDir = (0, import_node_path3.join)(input2.projectRoot, ".claude", "aipm", "skills", short);
4388
- await (0, import_promises2.mkdir)(skillDir, { recursive: true });
4389
- const filePath = (0, import_node_path3.join)(skillDir, "SKILL.md");
4390
- await (0, import_promises2.writeFile)(filePath, input2.skillMarkdown, "utf8");
4391
- return { writtenPaths: [filePath] };
4426
+ const skillDir = (0, import_node_path7.join)(input2.projectRoot, ".claude", "skills", short);
4427
+ return writeSkillDirectory(skillDir, input2);
4392
4428
  }
4393
4429
  };
4394
4430
  claudeSkillAdapter = new ClaudeSkillAdapter();
@@ -4396,21 +4432,21 @@ var init_dist2 = __esm({
4396
4432
  });
4397
4433
 
4398
4434
  // ../../packages/adapter-cursor/dist/index.js
4399
- var import_promises3, import_node_path4, CursorSkillAdapter, cursorSkillAdapter;
4400
- var init_dist3 = __esm({
4435
+ var import_promises6, import_node_path8, CursorSkillAdapter, cursorSkillAdapter;
4436
+ var init_dist4 = __esm({
4401
4437
  "../../packages/adapter-cursor/dist/index.js"() {
4402
4438
  "use strict";
4403
- import_promises3 = require("node:fs/promises");
4404
- import_node_path4 = require("node:path");
4439
+ import_promises6 = require("node:fs/promises");
4440
+ import_node_path8 = require("node:path");
4405
4441
  init_dist();
4406
4442
  CursorSkillAdapter = class {
4407
4443
  tool = "cursor";
4408
4444
  async installSkill(input2) {
4409
4445
  const short = shortNameFromScopeName(input2.packageName);
4410
- const skillsDir = (0, import_node_path4.join)(input2.projectRoot, ".cursor", "aipm", "skills");
4411
- await (0, import_promises3.mkdir)(skillsDir, { recursive: true });
4412
- const filePath = (0, import_node_path4.join)(skillsDir, `${short}.md`);
4413
- await (0, import_promises3.writeFile)(filePath, input2.skillMarkdown, "utf8");
4446
+ const skillsDir = (0, import_node_path8.join)(input2.projectRoot, ".cursor", "aipm", "skills");
4447
+ await (0, import_promises6.mkdir)(skillsDir, { recursive: true });
4448
+ const filePath = (0, import_node_path8.join)(skillsDir, `${short}.md`);
4449
+ await (0, import_promises6.writeFile)(filePath, input2.skillMarkdown, "utf8");
4414
4450
  return { writtenPaths: [filePath] };
4415
4451
  }
4416
4452
  };
@@ -4419,22 +4455,19 @@ var init_dist3 = __esm({
4419
4455
  });
4420
4456
 
4421
4457
  // ../../packages/adapter-codex/dist/index.js
4422
- var import_promises4, import_node_path5, CodexSkillAdapter, codexSkillAdapter;
4423
- var init_dist4 = __esm({
4458
+ var import_node_path9, CodexSkillAdapter, codexSkillAdapter;
4459
+ var init_dist5 = __esm({
4424
4460
  "../../packages/adapter-codex/dist/index.js"() {
4425
4461
  "use strict";
4426
- import_promises4 = require("node:fs/promises");
4427
- import_node_path5 = require("node:path");
4462
+ import_node_path9 = require("node:path");
4463
+ init_dist2();
4428
4464
  init_dist();
4429
4465
  CodexSkillAdapter = class {
4430
4466
  tool = "codex";
4431
4467
  async installSkill(input2) {
4432
4468
  const short = shortNameFromScopeName(input2.packageName);
4433
- const skillDir = (0, import_node_path5.join)(input2.projectRoot, ".agents", "skills", short);
4434
- await (0, import_promises4.mkdir)(skillDir, { recursive: true });
4435
- const filePath = (0, import_node_path5.join)(skillDir, "SKILL.md");
4436
- await (0, import_promises4.writeFile)(filePath, input2.skillMarkdown, "utf8");
4437
- return { writtenPaths: [filePath] };
4469
+ const skillDir = (0, import_node_path9.join)(input2.projectRoot, ".agents", "skills", short);
4470
+ return writeSkillDirectory(skillDir, input2);
4438
4471
  }
4439
4472
  };
4440
4473
  codexSkillAdapter = new CodexSkillAdapter();
@@ -4459,6 +4492,7 @@ async function installSkillPackage(options) {
4459
4492
  packageName: options.manifest.name,
4460
4493
  version: options.manifest.version,
4461
4494
  skillMarkdown: options.skillMarkdown,
4495
+ supportingFiles: options.supportingFiles,
4462
4496
  projectRoot: options.projectRoot
4463
4497
  });
4464
4498
  installed[tool] = result.writtenPaths;
@@ -4469,9 +4503,9 @@ var adapters;
4469
4503
  var init_install_skill = __esm({
4470
4504
  "../../packages/engine/dist/install-skill.js"() {
4471
4505
  "use strict";
4472
- init_dist2();
4473
4506
  init_dist3();
4474
4507
  init_dist4();
4508
+ init_dist5();
4475
4509
  init_detect_tools();
4476
4510
  adapters = {
4477
4511
  cursor: cursorSkillAdapter,
@@ -4488,7 +4522,7 @@ __export(dist_exports, {
4488
4522
  installSkillPackage: () => installSkillPackage,
4489
4523
  resolveInstallTools: () => resolveInstallTools
4490
4524
  });
4491
- var init_dist5 = __esm({
4525
+ var init_dist6 = __esm({
4492
4526
  "../../packages/engine/dist/index.js"() {
4493
4527
  "use strict";
4494
4528
  init_detect_tools();
@@ -7868,25 +7902,107 @@ var program = new Command();
7868
7902
  var import_node_crypto3 = require("node:crypto");
7869
7903
  var import_node_child_process4 = require("node:child_process");
7870
7904
  var import_node_http = require("node:http");
7871
- var import_promises12 = require("node:fs/promises");
7872
- var import_node_path15 = require("node:path");
7873
- var import_node_process5 = require("node:process");
7905
+ var import_promises14 = require("node:fs/promises");
7906
+ var import_node_path18 = require("node:path");
7907
+ var import_node_process6 = require("node:process");
7874
7908
  init_dist();
7875
- init_dist5();
7876
7909
 
7877
7910
  // src/pack.ts
7878
7911
  var import_node_child_process2 = require("node:child_process");
7879
7912
  var import_node_util3 = require("node:util");
7880
- var import_promises6 = require("node:fs/promises");
7881
- var import_node_path7 = require("node:path");
7913
+ var import_promises2 = require("node:fs/promises");
7914
+ var import_node_path3 = require("node:path");
7882
7915
  var import_node_os = require("node:os");
7883
7916
 
7884
7917
  // src/publish-state.ts
7885
7918
  var import_node_crypto = require("node:crypto");
7886
- var import_promises5 = require("node:fs/promises");
7887
- var import_node_path6 = require("node:path");
7919
+ var import_promises = require("node:fs/promises");
7920
+ var import_node_path2 = require("node:path");
7888
7921
  init_dist();
7889
- var STATE_PATH = (0, import_node_path6.join)(".aipm", "publish-state.json");
7922
+
7923
+ // src/ui/theme.ts
7924
+ var brand = {
7925
+ /** Primary accent — dark-mode site --accent / close to logo mint */
7926
+ accent: { r: 72, g: 185, b: 141 },
7927
+ // #48b98d
7928
+ /** Stronger / hover — dark --accent-strong */
7929
+ accentStrong: { r: 123, g: 217, b: 173 },
7930
+ // #7bd9ad
7931
+ /** Logo mint (slightly brighter) */
7932
+ mint: { r: 79, g: 196, b: 154 },
7933
+ // #4FC49A
7934
+ /** On-accent text (dark green-black) */
7935
+ onAccent: { r: 7, g: 17, b: 13 },
7936
+ // #07110d
7937
+ /** Muted secondary text */
7938
+ muted: { r: 157, g: 175, b: 170 },
7939
+ // #9dafaa
7940
+ /** Warning */
7941
+ warning: { r: 245, g: 165, b: 36 },
7942
+ // #f5a524
7943
+ /** Danger */
7944
+ danger: { r: 249, g: 112, b: 102 }
7945
+ // #f97066
7946
+ };
7947
+ function fgTruecolor({ r, g, b }) {
7948
+ return `\x1B[38;2;${r};${g};${b}m`;
7949
+ }
7950
+ function bgTruecolor({ r, g, b }) {
7951
+ return `\x1B[48;2;${r};${g};${b}m`;
7952
+ }
7953
+ function envValue(env2, key) {
7954
+ return env2[key];
7955
+ }
7956
+ function supportsColor(stream, env2 = process.env) {
7957
+ const noColor = envValue(env2, "NO_COLOR");
7958
+ if (noColor != null && noColor !== "") return false;
7959
+ const force = envValue(env2, "FORCE_COLOR");
7960
+ if (force === "0") return false;
7961
+ if (force != null && force !== "") return true;
7962
+ return Boolean(stream.isTTY);
7963
+ }
7964
+ function supportsTruecolor(stream, env2 = process.env) {
7965
+ if (!supportsColor(stream, env2)) return false;
7966
+ const force = envValue(env2, "FORCE_COLOR");
7967
+ if (force === "3") return true;
7968
+ const colorterm = (envValue(env2, "COLORTERM") ?? "").toLowerCase();
7969
+ if (colorterm.includes("truecolor") || colorterm.includes("24bit")) return true;
7970
+ return Boolean(stream.isTTY);
7971
+ }
7972
+ function createTheme(stream, env2 = process.env) {
7973
+ const color = supportsColor(stream, env2);
7974
+ const truecolor = supportsTruecolor(stream, env2);
7975
+ const accentFg = !color ? "" : truecolor ? fgTruecolor(brand.accent) : "\x1B[32m";
7976
+ const accentStrongFg = !color ? "" : truecolor ? fgTruecolor(brand.accentStrong) : "\x1B[32m";
7977
+ const mutedFg = !color ? "" : truecolor ? fgTruecolor(brand.muted) : "\x1B[2m";
7978
+ const onAccentFg = !color ? "" : truecolor ? fgTruecolor(brand.onAccent) : "\x1B[30m";
7979
+ const accentBg = !color ? "" : truecolor ? bgTruecolor(brand.accent) : "\x1B[42m";
7980
+ return {
7981
+ color,
7982
+ truecolor,
7983
+ reset: color ? "\x1B[0m" : "",
7984
+ bold: color ? "\x1B[1m" : "",
7985
+ dim: color ? "\x1B[2m" : "",
7986
+ accent: accentFg,
7987
+ accentStrong: accentStrongFg,
7988
+ muted: mutedFg,
7989
+ /** Selected row: mint background + dark text (site on-accent). */
7990
+ highlight: color ? `${accentBg}${onAccentFg}\x1B[1m` : "",
7991
+ warning: color ? truecolor ? fgTruecolor(brand.warning) : "\x1B[33m" : "",
7992
+ danger: color ? truecolor ? fgTruecolor(brand.danger) : "\x1B[31m" : ""
7993
+ };
7994
+ }
7995
+
7996
+ // src/recommend-cmd.ts
7997
+ function recommendCmd(command, stream = process.stderr, env2) {
7998
+ const quoted = `"${command}"`;
7999
+ const theme = createTheme(stream, env2);
8000
+ if (!theme.color) return quoted;
8001
+ return `${theme.accent}${quoted}${theme.reset}`;
8002
+ }
8003
+
8004
+ // src/publish-state.ts
8005
+ var STATE_PATH = (0, import_node_path2.join)(".aipm", "publish-state.json");
7890
8006
  var IGNORE_FILE = ".aipmignore";
7891
8007
  var MAX_PACKAGE_BYTES = 50 * 1024 * 1024;
7892
8008
  var EXCLUDED_SEGMENTS = /* @__PURE__ */ new Set([".aipm", ".git", "node_modules", "dist", ".next"]);
@@ -7901,11 +8017,11 @@ var SECRET_PATTERNS = [
7901
8017
  /<publishData/i
7902
8018
  ];
7903
8019
  function publishStatePath(root) {
7904
- return (0, import_node_path6.join)(root, STATE_PATH);
8020
+ return (0, import_node_path2.join)(root, STATE_PATH);
7905
8021
  }
7906
8022
  function normalizePublishPath(root, filePath) {
7907
- const abs = (0, import_node_path6.resolve)(root, filePath);
7908
- const rel = (0, import_node_path6.normalize)((0, import_node_path6.relative)(root, abs)).split(import_node_path6.sep).join("/");
8023
+ const abs = (0, import_node_path2.resolve)(root, filePath);
8024
+ const rel = (0, import_node_path2.normalize)((0, import_node_path2.relative)(root, abs)).split(import_node_path2.sep).join("/");
7909
8025
  if (!rel || rel === ".") return ".";
7910
8026
  if (rel.startsWith("../") || rel === ".." || rel.startsWith("/")) {
7911
8027
  throw new Error(`Path is outside the skill folder: ${filePath}`);
@@ -7922,7 +8038,7 @@ function isExcludedPublishPath(rel) {
7922
8038
  }
7923
8039
  async function readAipmIgnore(root) {
7924
8040
  try {
7925
- const raw = await (0, import_promises5.readFile)((0, import_node_path6.join)(root, IGNORE_FILE), "utf8");
8041
+ const raw = await (0, import_promises.readFile)((0, import_node_path2.join)(root, IGNORE_FILE), "utf8");
7926
8042
  return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
7927
8043
  } catch {
7928
8044
  return [];
@@ -7943,7 +8059,7 @@ async function isIgnoredByAipmIgnore(root, rel) {
7943
8059
  return rules.some((rule) => matchesIgnoreRule(rel, rule));
7944
8060
  }
7945
8061
  async function assertNoObviousSecrets(path2, rel) {
7946
- const data = await (0, import_promises5.readFile)(path2);
8062
+ const data = await (0, import_promises.readFile)(path2);
7947
8063
  if (data.includes(0)) return;
7948
8064
  const text = data.toString("utf8");
7949
8065
  if (SECRET_PATTERNS.some((pattern) => pattern.test(text))) {
@@ -7951,7 +8067,7 @@ async function assertNoObviousSecrets(path2, rel) {
7951
8067
  }
7952
8068
  }
7953
8069
  async function fileHash(path2) {
7954
- const data = await (0, import_promises5.readFile)(path2);
8070
+ const data = await (0, import_promises.readFile)(path2);
7955
8071
  return {
7956
8072
  hash: (0, import_node_crypto.createHash)("sha256").update(data).digest("hex"),
7957
8073
  size: data.length
@@ -7959,7 +8075,7 @@ async function fileHash(path2) {
7959
8075
  }
7960
8076
  async function readPublishState(root) {
7961
8077
  try {
7962
- const raw = await (0, import_promises5.readFile)(publishStatePath(root), "utf8");
8078
+ const raw = await (0, import_promises.readFile)(publishStatePath(root), "utf8");
7963
8079
  const parsed = JSON.parse(raw);
7964
8080
  return {
7965
8081
  schemaVersion: "0.1",
@@ -7971,18 +8087,18 @@ async function readPublishState(root) {
7971
8087
  }
7972
8088
  async function writePublishState(root, state) {
7973
8089
  const path2 = publishStatePath(root);
7974
- await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(path2), { recursive: true });
8090
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(path2), { recursive: true });
7975
8091
  const files = [...state.files].sort((a, b) => a.path.localeCompare(b.path));
7976
- await (0, import_promises5.writeFile)(path2, JSON.stringify({ schemaVersion: "0.1", files }, null, 2) + "\n", "utf8");
8092
+ await (0, import_promises.writeFile)(path2, JSON.stringify({ schemaVersion: "0.1", files }, null, 2) + "\n", "utf8");
7977
8093
  }
7978
8094
  async function expandPublishPath(root, rel) {
7979
8095
  if (isExcludedPublishPath(rel)) return [];
7980
8096
  if (await isIgnoredByAipmIgnore(root, rel)) return [];
7981
- const abs = (0, import_node_path6.join)(root, rel);
7982
- const info = await (0, import_promises5.stat)(abs);
8097
+ const abs = (0, import_node_path2.join)(root, rel);
8098
+ const info = await (0, import_promises.stat)(abs);
7983
8099
  if (info.isFile()) return [rel];
7984
8100
  if (!info.isDirectory()) return [];
7985
- const entries = await (0, import_promises5.readdir)(abs);
8101
+ const entries = await (0, import_promises.readdir)(abs);
7986
8102
  const nested = await Promise.all(
7987
8103
  entries.map((entry) => expandPublishPath(root, `${rel === "." ? "" : `${rel}/`}${entry}`))
7988
8104
  );
@@ -7998,7 +8114,7 @@ async function addPublishFiles(root, paths) {
7998
8114
  for (const rel of files) {
7999
8115
  if (isExcludedPublishPath(rel)) continue;
8000
8116
  if (await isIgnoredByAipmIgnore(root, rel)) continue;
8001
- const hashed = await fileHash((0, import_node_path6.join)(root, rel));
8117
+ const hashed = await fileHash((0, import_node_path2.join)(root, rel));
8002
8118
  byPath.set(rel, { path: rel, ...hashed });
8003
8119
  }
8004
8120
  }
@@ -8017,15 +8133,17 @@ async function removePublishFiles(root, paths) {
8017
8133
  return next;
8018
8134
  }
8019
8135
  async function resetPublishState(root) {
8020
- await (0, import_promises5.rm)(publishStatePath(root), { force: true });
8136
+ await (0, import_promises.rm)(publishStatePath(root), { force: true });
8021
8137
  }
8022
8138
  async function readManifest(root) {
8023
8139
  try {
8024
- const raw = await (0, import_promises5.readFile)((0, import_node_path6.join)(root, "aipm.manifest.json"), "utf8");
8140
+ const raw = await (0, import_promises.readFile)((0, import_node_path2.join)(root, "aipm.manifest.json"), "utf8");
8025
8141
  return PackageManifestSchema.parse(JSON.parse(raw));
8026
8142
  } catch (error) {
8027
8143
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
8028
- throw new Error("No aipm.manifest.json found. Run aipm publish init --name @org/skill.");
8144
+ throw new Error(
8145
+ `No aipm.manifest.json found. Run ${recommendCmd("aipm publish init --name <@org/skill>")}.`
8146
+ );
8029
8147
  }
8030
8148
  throw error;
8031
8149
  }
@@ -8041,7 +8159,9 @@ function installReferencedFiles(manifest) {
8041
8159
  async function validatePublishState(root) {
8042
8160
  const manifest = await readManifest(root);
8043
8161
  const state = await readPublishState(root);
8044
- if (state.files.length === 0) throw new Error("No files staged. Run aipm publish add <files...>.");
8162
+ if (state.files.length === 0) {
8163
+ throw new Error(`No files staged. Run ${recommendCmd("aipm publish add <files...>")}.`);
8164
+ }
8045
8165
  const staged = new Set(state.files.map((entry) => entry.path));
8046
8166
  if (!staged.has("aipm.manifest.json")) throw new Error("aipm.manifest.json must be staged.");
8047
8167
  if (!staged.has(manifest.entry)) throw new Error(`Manifest entry must be staged: ${manifest.entry}`);
@@ -8054,12 +8174,14 @@ async function validatePublishState(root) {
8054
8174
  if (await isIgnoredByAipmIgnore(root, entry.path)) {
8055
8175
  throw new Error(`Refusing to publish ignored path: ${entry.path}`);
8056
8176
  }
8057
- const abs = (0, import_node_path6.join)(root, entry.path);
8058
- const info = await (0, import_promises5.stat)(abs).catch(() => null);
8177
+ const abs = (0, import_node_path2.join)(root, entry.path);
8178
+ const info = await (0, import_promises.stat)(abs).catch(() => null);
8059
8179
  if (!info?.isFile()) throw new Error(`Staged file is missing: ${entry.path}`);
8060
8180
  const hashed = await fileHash(abs);
8061
8181
  if (hashed.hash !== entry.hash) {
8062
- throw new Error(`Staged file changed after add: ${entry.path}. Run aipm publish add ${entry.path}`);
8182
+ throw new Error(
8183
+ `Staged file changed after add: ${entry.path}. Run ${recommendCmd(`aipm publish add ${entry.path}`)}.`
8184
+ );
8063
8185
  }
8064
8186
  await assertNoObviousSecrets(abs, entry.path);
8065
8187
  size += hashed.size;
@@ -8071,7 +8193,7 @@ async function statusPublishState(root) {
8071
8193
  const state = await readPublishState(root);
8072
8194
  const rows = [];
8073
8195
  for (const entry of state.files) {
8074
- const current = await fileHash((0, import_node_path6.join)(root, entry.path)).catch(() => null);
8196
+ const current = await fileHash((0, import_node_path2.join)(root, entry.path)).catch(() => null);
8075
8197
  rows.push({ ...entry, changed: !current || current.hash !== entry.hash });
8076
8198
  }
8077
8199
  return rows;
@@ -8079,9 +8201,9 @@ async function statusPublishState(root) {
8079
8201
  async function copyStagedFiles(root, destination) {
8080
8202
  const state = await readPublishState(root);
8081
8203
  for (const entry of state.files) {
8082
- const target = (0, import_node_path6.join)(destination, entry.path);
8083
- await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(target), { recursive: true });
8084
- await (0, import_promises5.cp)((0, import_node_path6.join)(root, entry.path), target);
8204
+ const target = (0, import_node_path2.join)(destination, entry.path);
8205
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(target), { recursive: true });
8206
+ await (0, import_promises.cp)((0, import_node_path2.join)(root, entry.path), target);
8085
8207
  }
8086
8208
  }
8087
8209
 
@@ -8095,21 +8217,21 @@ async function packDirectory(dir) {
8095
8217
  return Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout);
8096
8218
  }
8097
8219
  async function packStagedFiles(root) {
8098
- const tempDir = await (0, import_promises6.mkdtemp)((0, import_node_path7.join)((0, import_node_os.tmpdir)(), "aipm-publish-stage-"));
8220
+ const tempDir = await (0, import_promises2.mkdtemp)((0, import_node_path3.join)((0, import_node_os.tmpdir)(), "aipm-publish-stage-"));
8099
8221
  try {
8100
8222
  await copyStagedFiles(root, tempDir);
8101
8223
  return await packDirectory(tempDir);
8102
8224
  } finally {
8103
- await (0, import_promises6.rm)(tempDir, { recursive: true, force: true });
8225
+ await (0, import_promises2.rm)(tempDir, { recursive: true, force: true });
8104
8226
  }
8105
8227
  }
8106
8228
  async function unpackTarballToDirectory(tarball) {
8107
- const { mkdtemp: mkdtemp2, writeFile: writeFile9 } = await import("node:fs/promises");
8108
- const { join: join15 } = await import("node:path");
8229
+ const { mkdtemp: mkdtemp2, writeFile: writeFile8 } = await import("node:fs/promises");
8230
+ const { join: join17 } = await import("node:path");
8109
8231
  const { tmpdir: tmpdir2 } = await import("node:os");
8110
- const tempDir = await mkdtemp2(join15(tmpdir2(), "aipm-install-"));
8111
- const tgzPath = join15(tempDir, "pkg.tgz");
8112
- await writeFile9(tgzPath, tarball);
8232
+ const tempDir = await mkdtemp2(join17(tmpdir2(), "aipm-install-"));
8233
+ const tgzPath = join17(tempDir, "pkg.tgz");
8234
+ await writeFile8(tgzPath, tarball);
8113
8235
  await execFileAsync("tar", ["-xzf", tgzPath, "-C", tempDir]);
8114
8236
  return tempDir;
8115
8237
  }
@@ -8122,7 +8244,7 @@ function registryFetchError(registry, cause) {
8122
8244
  const msg = cause instanceof Error ? cause.message : String(cause);
8123
8245
  if (msg === "fetch failed" || msg.includes("ECONNREFUSED") || msg.includes("ENOTFOUND") || msg.includes("timed out") || msg.includes("aborted")) {
8124
8246
  return new Error(
8125
- `Cannot reach registry at ${registry}. Check your connection or pass --registry <url>.`
8247
+ `Cannot reach registry at ${registry}. Check your connection or pass ${recommendCmd("--registry <url>")}.`
8126
8248
  );
8127
8249
  }
8128
8250
  return new Error(`Registry request failed (${registry}): ${msg}`);
@@ -8143,13 +8265,13 @@ function privateInstallHint(name, status, token) {
8143
8265
  if (status !== 404 || !name.startsWith("@") || token) {
8144
8266
  return `Package not found: ${name} (${status})`;
8145
8267
  }
8146
- return `Package not found: ${name} (${status}). If @org/pkg is private, run aipm login, or set AIPM_TOKEN for CI.`;
8268
+ return `Package not found: ${name} (${status}). If @org/pkg is private, run ${recommendCmd("aipm login")}, or set AIPM_TOKEN for CI.`;
8147
8269
  }
8148
8270
  function publishTokenHint(name, error) {
8149
8271
  if (error === "Publish token required") {
8150
8272
  return [
8151
8273
  `Publish token required for ${name}.`,
8152
- `Generate a fresh 5-minute publish token for ${name} from the package dashboard, then retry with AIPM_TOKEN=<token> aipm publish push --yes.`
8274
+ `Generate a fresh 5-minute publish token for ${name} from the package dashboard, then retry with ${recommendCmd("AIPM_TOKEN=<token> aipm publish push --yes")}.`
8153
8275
  ].join(" ");
8154
8276
  }
8155
8277
  if (error === "Invalid publish token") {
@@ -8170,7 +8292,7 @@ async function assertRegistryReachable(registry) {
8170
8292
  }
8171
8293
  async function publishPackage(registry, name, tarball, token) {
8172
8294
  const base = registry.replace(/\/$/, "");
8173
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions`;
8295
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions`;
8174
8296
  const form = new FormData();
8175
8297
  form.append("tarball", new Blob([tarball]), "package.tgz");
8176
8298
  const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
@@ -8187,12 +8309,12 @@ async function searchPackages(registry, query, limit = 20, token) {
8187
8309
  if (query) params.set("q", query);
8188
8310
  params.set("limit", String(limit));
8189
8311
  if (token) params.set("includePrivate", "true");
8190
- const res = await registryFetch(`${base}/v1/packages?${params}`, base, {
8312
+ const res = await registryFetch(`${base}/v1/skills?${params}`, base, {
8191
8313
  headers: registryAuthHeaders(token)
8192
8314
  });
8193
8315
  if (!res.ok) throw new Error(`Search failed: ${res.status}`);
8194
8316
  const data = await res.json();
8195
- return data.packages ?? [];
8317
+ return data.skills ?? data.packages ?? [];
8196
8318
  }
8197
8319
  async function exchangeCliAuthCode(registry, input2) {
8198
8320
  const base = registry.replace(/\/$/, "");
@@ -8241,7 +8363,7 @@ async function logoutCliAuth(registry, refreshToken) {
8241
8363
  }
8242
8364
  async function fetchPackageMetadata(registry, name, version, token) {
8243
8365
  const base = registry.replace(/\/$/, "");
8244
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions/${version}`;
8366
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions/${version}`;
8245
8367
  const res = await registryFetch(url, base, { headers: registryAuthHeaders(token) });
8246
8368
  if (!res.ok) throw new Error(privateInstallHint(`${name}@${version}`, res.status, token));
8247
8369
  const data = await res.json();
@@ -8249,7 +8371,7 @@ async function fetchPackageMetadata(registry, name, version, token) {
8249
8371
  }
8250
8372
  async function fetchPackageTarball(registry, name, version, token) {
8251
8373
  const base = registry.replace(/\/$/, "");
8252
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions/${version}/tarball`;
8374
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions/${version}/tarball`;
8253
8375
  const res = await registryFetch(url, base, { headers: registryAuthHeaders(token) });
8254
8376
  if (!res.ok) throw new Error(privateInstallHint(`${name}@${version}`, res.status, token));
8255
8377
  const ab = await res.arrayBuffer();
@@ -8257,7 +8379,7 @@ async function fetchPackageTarball(registry, name, version, token) {
8257
8379
  }
8258
8380
  async function recordPackageInstall(registry, name, token) {
8259
8381
  const base = registry.replace(/\/$/, "");
8260
- const url = `${base}/v1/packages/${encodePackageName(name)}/installs`;
8382
+ const url = `${base}/v1/skills/${encodePackageName(name)}/installs`;
8261
8383
  const res = await registryFetch(url, base, {
8262
8384
  method: "POST",
8263
8385
  headers: registryAuthHeaders(token)
@@ -8305,16 +8427,16 @@ async function publishPrompt(registry, input2, accessToken, sampleImage) {
8305
8427
  }
8306
8428
 
8307
8429
  // src/auth-store.ts
8308
- var import_promises7 = require("node:fs/promises");
8430
+ var import_promises3 = require("node:fs/promises");
8309
8431
  var import_node_os2 = require("node:os");
8310
- var import_node_path8 = require("node:path");
8311
- var AUTH_FILE = (0, import_node_path8.join)((0, import_node_os2.homedir)(), ".aipm", "auth.json");
8432
+ var import_node_path4 = require("node:path");
8433
+ var AUTH_FILE = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".aipm", "auth.json");
8312
8434
  function normalizeRegistry(registry) {
8313
8435
  return registry.replace(/\/$/, "");
8314
8436
  }
8315
8437
  async function readAuthStore() {
8316
8438
  try {
8317
- const raw = await (0, import_promises7.readFile)(AUTH_FILE, "utf8");
8439
+ const raw = await (0, import_promises3.readFile)(AUTH_FILE, "utf8");
8318
8440
  const parsed = JSON.parse(raw);
8319
8441
  return { registries: parsed.registries ?? {} };
8320
8442
  } catch {
@@ -8322,9 +8444,9 @@ async function readAuthStore() {
8322
8444
  }
8323
8445
  }
8324
8446
  async function writeAuthStore(store) {
8325
- await (0, import_promises7.mkdir)((0, import_node_path8.dirname)(AUTH_FILE), { recursive: true });
8326
- await (0, import_promises7.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8327
- await (0, import_promises7.chmod)(AUTH_FILE, 384).catch(() => void 0);
8447
+ await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(AUTH_FILE), { recursive: true });
8448
+ await (0, import_promises3.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8449
+ await (0, import_promises3.chmod)(AUTH_FILE, 384).catch(() => void 0);
8328
8450
  }
8329
8451
  async function getStoredRegistryAuth(registry) {
8330
8452
  const store = await readAuthStore();
@@ -8353,41 +8475,41 @@ function isAccessTokenFresh(auth, skewMs = 6e4) {
8353
8475
  }
8354
8476
 
8355
8477
  // src/install-one.ts
8356
- init_dist5();
8357
- var import_promises9 = require("node:fs/promises");
8358
- var import_node_path10 = require("node:path");
8478
+ init_dist6();
8479
+ var import_promises8 = require("node:fs/promises");
8480
+ var import_node_path11 = require("node:path");
8359
8481
 
8360
8482
  // src/project-files.ts
8361
- var import_promises8 = require("node:fs/promises");
8362
- var import_node_path9 = require("node:path");
8483
+ var import_promises7 = require("node:fs/promises");
8484
+ var import_node_path10 = require("node:path");
8363
8485
  init_dist();
8364
8486
  var PACKAGE_JSON = "aipm.package.json";
8365
8487
  var LOCKFILE = "aipm-lock.json";
8366
8488
  async function readProjectPackageJson(projectRoot) {
8367
8489
  try {
8368
- const raw = await (0, import_promises8.readFile)((0, import_node_path9.join)(projectRoot, PACKAGE_JSON), "utf8");
8490
+ const raw = await (0, import_promises7.readFile)((0, import_node_path10.join)(projectRoot, PACKAGE_JSON), "utf8");
8369
8491
  return ProjectPackageJsonSchema.parse(JSON.parse(raw));
8370
8492
  } catch {
8371
8493
  return null;
8372
8494
  }
8373
8495
  }
8374
8496
  async function writeProjectPackageJson(projectRoot, data) {
8375
- await (0, import_promises8.writeFile)(
8376
- (0, import_node_path9.join)(projectRoot, PACKAGE_JSON),
8497
+ await (0, import_promises7.writeFile)(
8498
+ (0, import_node_path10.join)(projectRoot, PACKAGE_JSON),
8377
8499
  JSON.stringify(data, null, 2) + "\n",
8378
8500
  "utf8"
8379
8501
  );
8380
8502
  }
8381
8503
  async function readLockfile(projectRoot) {
8382
8504
  try {
8383
- const raw = await (0, import_promises8.readFile)((0, import_node_path9.join)(projectRoot, LOCKFILE), "utf8");
8505
+ const raw = await (0, import_promises7.readFile)((0, import_node_path10.join)(projectRoot, LOCKFILE), "utf8");
8384
8506
  return LockfileSchema.parse(JSON.parse(raw));
8385
8507
  } catch {
8386
8508
  return null;
8387
8509
  }
8388
8510
  }
8389
8511
  async function writeLockfile(projectRoot, data) {
8390
- await (0, import_promises8.writeFile)((0, import_node_path9.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8512
+ await (0, import_promises7.writeFile)((0, import_node_path10.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8391
8513
  }
8392
8514
  function upsertLockEntry(lock, name, entry) {
8393
8515
  return {
@@ -8429,40 +8551,392 @@ function parseTargetsFlag(value) {
8429
8551
  return unique;
8430
8552
  }
8431
8553
 
8432
- // src/prompt.ts
8433
- var readline = __toESM(require("node:readline/promises"), 1);
8554
+ // src/ui/select.ts
8434
8555
  var import_node_process2 = require("node:process");
8435
- async function promptForTool() {
8436
- const rl = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
8556
+
8557
+ // src/ui/ansi.ts
8558
+ var ansi = {
8559
+ reset: "\x1B[0m",
8560
+ bold: "\x1B[1m",
8561
+ dim: "\x1B[2m",
8562
+ hideCursor: "\x1B[?25l",
8563
+ showCursor: "\x1B[?25h",
8564
+ clearLine: "\x1B[2K",
8565
+ cursorTo(col) {
8566
+ return `\x1B[${Math.max(1, col)}G`;
8567
+ },
8568
+ /** Move cursor up `n` lines (0 = no-op). */
8569
+ cursorUp(n) {
8570
+ return n > 0 ? `\x1B[${n}A` : "";
8571
+ }
8572
+ };
8573
+
8574
+ // src/ui/select.ts
8575
+ var SelectCancelledError = class extends Error {
8576
+ constructor(message = "Selection cancelled") {
8577
+ super(message);
8578
+ this.name = "SelectCancelledError";
8579
+ }
8580
+ };
8581
+ function assertSelectable(options) {
8582
+ if (options.length === 0) {
8583
+ throw new Error("selectPrompt requires at least one option");
8584
+ }
8585
+ }
8586
+ function wrap(open, text, reset) {
8587
+ return open ? `${open}${text}${reset}` : text;
8588
+ }
8589
+ function renderFrame(output2, opts) {
8590
+ const { message, options, index, theme } = opts;
8591
+ const lines = [];
8592
+ lines.push("");
8593
+ lines.push(
8594
+ `${wrap(theme.accent + theme.bold, "?", theme.reset)} ${wrap(theme.bold, message, theme.reset)}`
8595
+ );
8596
+ lines.push("");
8597
+ for (let i = 0; i < options.length; i++) {
8598
+ const option = options[i];
8599
+ const selected = i === index;
8600
+ const pointer = selected ? "\u276F" : " ";
8601
+ if (selected && theme.color) {
8602
+ lines.push(`${theme.highlight} ${pointer} ${option.label} ${theme.reset}`);
8603
+ if (option.hint) {
8604
+ lines.push(`${theme.highlight} ${option.hint} ${theme.reset}`);
8605
+ }
8606
+ } else if (selected) {
8607
+ lines.push(` ${pointer} ${option.label}`);
8608
+ if (option.hint) lines.push(` ${option.hint}`);
8609
+ } else {
8610
+ lines.push(` ${pointer} ${wrap(theme.muted, option.label, theme.reset)}`);
8611
+ if (option.hint) {
8612
+ lines.push(` ${wrap(theme.muted, option.hint, theme.reset)}`);
8613
+ }
8614
+ }
8615
+ lines.push("");
8616
+ }
8617
+ lines.push(wrap(theme.muted, " \u2191\u2193 navigate \xB7 enter select \xB7 esc cancel", theme.reset));
8618
+ lines.push("");
8619
+ if (opts.previous && opts.previous.lines > 0) {
8620
+ output2.write(ansi.cursorUp(opts.previous.lines));
8621
+ }
8622
+ for (const line of lines) {
8623
+ output2.write(`${ansi.clearLine}${ansi.cursorTo(1)}${line}
8624
+ `);
8625
+ }
8626
+ const prev = opts.previous?.lines ?? 0;
8627
+ for (let i = lines.length; i < prev; i++) {
8628
+ output2.write(`${ansi.clearLine}${ansi.cursorTo(1)}
8629
+ `);
8630
+ }
8631
+ if (prev > lines.length) {
8632
+ output2.write(ansi.cursorUp(prev - lines.length));
8633
+ }
8634
+ return { lines: Math.max(lines.length, prev) };
8635
+ }
8636
+ function clearFrame(output2, state) {
8637
+ if (state.lines <= 0) return;
8638
+ output2.write(ansi.cursorUp(state.lines));
8639
+ for (let i = 0; i < state.lines; i++) {
8640
+ output2.write(`${ansi.clearLine}${ansi.cursorTo(1)}
8641
+ `);
8642
+ }
8643
+ output2.write(ansi.cursorUp(state.lines));
8644
+ }
8645
+ function writeDone(output2, message, option, theme) {
8646
+ const prefix = wrap(theme.accent + theme.bold, "\u2714", theme.reset);
8647
+ const msg = wrap(theme.bold, message, theme.reset);
8648
+ const value = wrap(theme.accentStrong, option.label, theme.reset);
8649
+ output2.write("\n");
8650
+ output2.write(
8651
+ `${prefix} ${msg} ${wrap(theme.muted, "\xB7", theme.reset)} ${value}
8652
+ `
8653
+ );
8654
+ output2.write("\n");
8655
+ }
8656
+ function readKey(chunk) {
8657
+ if (chunk === "" || chunk === "\x1B") return "cancel";
8658
+ if (chunk === "\r" || chunk === "\n") return "enter";
8659
+ if (chunk === "\x1B[A" || chunk === "\x1BOA" || chunk === "k") return "up";
8660
+ if (chunk === "\x1B[B" || chunk === "\x1BOB" || chunk === "j") return "down";
8661
+ if (chunk.startsWith("\x1B")) return "ignore";
8662
+ return "ignore";
8663
+ }
8664
+ async function selectPrompt(options) {
8665
+ assertSelectable(options.options);
8666
+ const input2 = options.input ?? import_node_process2.stdin;
8667
+ const output2 = options.output ?? import_node_process2.stdout;
8668
+ const theme = createTheme(output2);
8669
+ if (!input2.isTTY || !output2.isTTY || typeof input2.setRawMode !== "function") {
8670
+ throw new Error(
8671
+ "Interactive selection requires a TTY. Pass an explicit flag (e.g. --target / --ci) or run in a terminal."
8672
+ );
8673
+ }
8674
+ const initial = Math.min(
8675
+ Math.max(options.initialIndex ?? 0, 0),
8676
+ options.options.length - 1
8677
+ );
8678
+ let index = initial;
8679
+ let frame = { lines: 0 };
8680
+ const wasRaw = input2.isRaw;
8681
+ input2.setRawMode(true);
8682
+ input2.resume();
8683
+ output2.write(ansi.hideCursor);
8684
+ const redraw = () => {
8685
+ frame = renderFrame(output2, {
8686
+ message: options.message,
8687
+ options: options.options,
8688
+ index,
8689
+ theme,
8690
+ previous: frame
8691
+ });
8692
+ };
8693
+ redraw();
8437
8694
  try {
8438
- while (true) {
8439
- const answer = await rl.question(
8440
- "Which AI tool should this skill be installed for? (cursor/claude/codex): "
8695
+ return await new Promise((resolve6, reject) => {
8696
+ const onData = (chunk) => {
8697
+ const key = readKey(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
8698
+ if (key === "ignore") return;
8699
+ if (key === "up") {
8700
+ index = (index - 1 + options.options.length) % options.options.length;
8701
+ redraw();
8702
+ return;
8703
+ }
8704
+ if (key === "down") {
8705
+ index = (index + 1) % options.options.length;
8706
+ redraw();
8707
+ return;
8708
+ }
8709
+ if (key === "cancel") {
8710
+ cleanup();
8711
+ clearFrame(output2, frame);
8712
+ reject(new SelectCancelledError());
8713
+ return;
8714
+ }
8715
+ const selected = options.options[index];
8716
+ cleanup();
8717
+ clearFrame(output2, frame);
8718
+ writeDone(output2, options.message, selected, theme);
8719
+ resolve6(selected.value);
8720
+ };
8721
+ const cleanup = () => {
8722
+ input2.off("data", onData);
8723
+ };
8724
+ input2.on("data", onData);
8725
+ });
8726
+ } finally {
8727
+ output2.write(ansi.showCursor);
8728
+ if (typeof input2.setRawMode === "function") {
8729
+ input2.setRawMode(wasRaw);
8730
+ }
8731
+ if (typeof input2.pause === "function") {
8732
+ input2.pause();
8733
+ }
8734
+ }
8735
+ }
8736
+
8737
+ // src/ui/confirm.ts
8738
+ async function confirmPrompt(options) {
8739
+ return selectPrompt({
8740
+ message: options.message,
8741
+ initialIndex: options.initialConfirm ? 0 : 1,
8742
+ input: options.input,
8743
+ output: options.output,
8744
+ options: [
8745
+ { value: true, label: options.activeLabel ?? "Yes" },
8746
+ { value: false, label: options.inactiveLabel ?? "No" }
8747
+ ]
8748
+ });
8749
+ }
8750
+
8751
+ // src/ui/note.ts
8752
+ function symbolFor(type) {
8753
+ switch (type) {
8754
+ case "success":
8755
+ return "\u2714";
8756
+ case "warn":
8757
+ return "\u26A0";
8758
+ case "error":
8759
+ return "\u2716";
8760
+ default:
8761
+ return "\u2139";
8762
+ }
8763
+ }
8764
+ function colorFor(type, theme) {
8765
+ switch (type) {
8766
+ case "success":
8767
+ return theme.accent;
8768
+ case "warn":
8769
+ return theme.warning;
8770
+ case "error":
8771
+ return theme.danger;
8772
+ default:
8773
+ return theme.accentStrong;
8774
+ }
8775
+ }
8776
+ function printNote(options) {
8777
+ const stream = options.stream ?? process.stderr;
8778
+ const theme = createTheme(stream);
8779
+ const type = options.type ?? "info";
8780
+ const symbol = symbolFor(type);
8781
+ const color = colorFor(type, theme);
8782
+ stream.write("\n");
8783
+ if (options.title) {
8784
+ stream.write(
8785
+ `${color}${theme.bold}${symbol} ${options.title}${theme.reset}
8786
+ `
8787
+ );
8788
+ stream.write(` ${theme.muted}${options.message}${theme.reset}
8789
+ `);
8790
+ } else {
8791
+ stream.write(
8792
+ `${color}${theme.bold}${symbol}${theme.reset} ${options.message}
8793
+ `
8794
+ );
8795
+ }
8796
+ stream.write("\n");
8797
+ }
8798
+
8799
+ // src/ui/spinner.ts
8800
+ var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8801
+ function createSpinner(options) {
8802
+ const stream = options.stream ?? process.stderr;
8803
+ const theme = createTheme(stream);
8804
+ const disabled = options.disabled || !stream.isTTY || !theme.color;
8805
+ let text = options.text;
8806
+ let frame = 0;
8807
+ let timer;
8808
+ let active = false;
8809
+ const clearLine = () => {
8810
+ if (!stream.isTTY) return;
8811
+ stream.write("\r\x1B[2K");
8812
+ };
8813
+ const render = () => {
8814
+ if (disabled || !active) return;
8815
+ const spin = FRAMES[frame % FRAMES.length];
8816
+ stream.write(`\r\x1B[2K${theme.accent}${spin}${theme.reset} ${text}`);
8817
+ frame += 1;
8818
+ };
8819
+ return {
8820
+ start(nextText) {
8821
+ if (nextText) text = nextText;
8822
+ if (disabled) return;
8823
+ if (active) return;
8824
+ active = true;
8825
+ render();
8826
+ timer = setInterval(render, 80);
8827
+ timer.unref?.();
8828
+ },
8829
+ update(nextText) {
8830
+ text = nextText;
8831
+ if (disabled || !active) return;
8832
+ render();
8833
+ },
8834
+ succeed(finalText = text) {
8835
+ stop();
8836
+ if (disabled) return;
8837
+ stream.write(
8838
+ `${theme.accent}${theme.bold}\u2714${theme.reset} ${finalText}
8839
+ `
8441
8840
  );
8442
- const normalized = answer.trim().toLowerCase();
8443
- if (normalized === "cursor" || normalized === "claude" || normalized === "codex") {
8444
- return normalized;
8841
+ },
8842
+ fail(finalText = text) {
8843
+ stop();
8844
+ if (disabled) {
8845
+ stream.write(`${finalText}
8846
+ `);
8847
+ return;
8445
8848
  }
8446
- console.log('Please enter "cursor", "claude", or "codex".');
8849
+ stream.write(`${theme.danger}${theme.bold}\u2716${theme.reset} ${finalText}
8850
+ `);
8851
+ },
8852
+ stop() {
8853
+ stop();
8854
+ }
8855
+ };
8856
+ function stop() {
8857
+ if (timer) {
8858
+ clearInterval(timer);
8859
+ timer = void 0;
8860
+ }
8861
+ if (active) {
8862
+ clearLine();
8863
+ active = false;
8447
8864
  }
8448
- } finally {
8449
- rl.close();
8450
8865
  }
8451
8866
  }
8452
- async function promptForConfirmation(question) {
8453
- const rl = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
8867
+
8868
+ // src/ui/recover.ts
8869
+ var import_node_process3 = require("node:process");
8870
+ function canPromptInteractively(ci) {
8871
+ if (ci) return false;
8872
+ return Boolean(import_node_process3.stdin.isTTY && import_node_process3.stdout.isTTY);
8873
+ }
8874
+ var RecoveryCancelledError = class extends Error {
8875
+ constructor(message = "Cancelled.") {
8876
+ super(message);
8877
+ this.name = "RecoveryCancelledError";
8878
+ }
8879
+ };
8880
+ async function offerChoices(options) {
8881
+ if (!canPromptInteractively(options.ci)) {
8882
+ throw new Error(options.fallbackError);
8883
+ }
8884
+ if (options.note) {
8885
+ printNote({
8886
+ type: options.note.type ?? "warn",
8887
+ title: options.note.title,
8888
+ message: options.note.message
8889
+ });
8890
+ }
8454
8891
  try {
8455
- const answer = await rl.question(`${question} (y/N): `);
8456
- const normalized = answer.trim().toLowerCase();
8457
- return normalized === "y" || normalized === "yes";
8458
- } finally {
8459
- rl.close();
8892
+ return await selectPrompt({
8893
+ message: options.message,
8894
+ options: options.choices
8895
+ });
8896
+ } catch (error) {
8897
+ if (error instanceof SelectCancelledError) {
8898
+ throw new RecoveryCancelledError();
8899
+ }
8900
+ throw error;
8460
8901
  }
8461
8902
  }
8462
8903
 
8904
+ // src/prompt.ts
8905
+ async function promptForTool(tools = ["cursor", "claude", "codex"]) {
8906
+ const options = tools.map((tool) => ({
8907
+ value: tool,
8908
+ label: tool,
8909
+ hint: tool === "cursor" ? ".cursor/" : tool === "claude" ? ".claude/" : ".agents/ (Codex)"
8910
+ }));
8911
+ return selectPrompt({
8912
+ message: "Which AI tool should this skill be installed for?",
8913
+ options
8914
+ });
8915
+ }
8916
+ async function promptForConfirmation(question) {
8917
+ return confirmPrompt({ message: question, initialConfirm: false });
8918
+ }
8919
+ async function promptForNoInitConflict() {
8920
+ return selectPrompt({
8921
+ message: "Project already has aipm.package.json. How do you want to continue?",
8922
+ options: [
8923
+ {
8924
+ value: "no-init",
8925
+ label: "Continue without project tracking",
8926
+ hint: "keep --no-init"
8927
+ },
8928
+ {
8929
+ value: "tracked",
8930
+ label: "Use normal project mode",
8931
+ hint: "drop --no-init for this run"
8932
+ }
8933
+ ]
8934
+ });
8935
+ }
8936
+
8463
8937
  // src/install-one.ts
8464
8938
  function normalizeRelativePath(path2) {
8465
- return (0, import_node_path10.normalize)(path2).split(import_node_path10.sep).join("/");
8939
+ return (0, import_node_path11.normalize)(path2).split(import_node_path11.sep).join("/");
8466
8940
  }
8467
8941
  function assertSafeRelativePath(path2) {
8468
8942
  const rel = normalizeRelativePath(path2.trim());
@@ -8473,8 +8947,8 @@ function assertSafeRelativePath(path2) {
8473
8947
  }
8474
8948
  function safeJoin(root, relPath) {
8475
8949
  const rel = assertSafeRelativePath(relPath);
8476
- const target = (0, import_node_path10.resolve)(root, rel);
8477
- const fromRoot = normalizeRelativePath((0, import_node_path10.relative)(root, target));
8950
+ const target = (0, import_node_path11.resolve)(root, rel);
8951
+ const fromRoot = normalizeRelativePath((0, import_node_path11.relative)(root, target));
8478
8952
  if (fromRoot === ".." || fromRoot.startsWith("../") || fromRoot.startsWith("/")) {
8479
8953
  throw new Error(`Unsafe install path: ${relPath}`);
8480
8954
  }
@@ -8485,33 +8959,98 @@ function packageHelperSlug(name) {
8485
8959
  return `${scope}__${pkg}`;
8486
8960
  }
8487
8961
  function helperRootFor(configRoot, packageName, version) {
8488
- const base = (0, import_node_path10.basename)(configRoot) === ".aipm" ? (0, import_node_path10.join)(configRoot, "helpers") : (0, import_node_path10.join)(configRoot, ".aipm", "helpers");
8489
- return (0, import_node_path10.join)(base, packageHelperSlug(packageName), version);
8962
+ const base = (0, import_node_path11.basename)(configRoot) === ".aipm" ? (0, import_node_path11.join)(configRoot, "helpers") : (0, import_node_path11.join)(configRoot, ".aipm", "helpers");
8963
+ return (0, import_node_path11.join)(base, packageHelperSlug(packageName), version);
8490
8964
  }
8491
8965
  async function assertSourceFile(packageRoot, relPath) {
8492
8966
  const source = safeJoin(packageRoot, relPath);
8493
- const info = await (0, import_promises9.stat)(source).catch(() => null);
8967
+ const info = await (0, import_promises8.stat)(source).catch(() => null);
8494
8968
  if (!info?.isFile()) throw new Error(`Package install file not found: ${relPath}`);
8495
8969
  return source;
8496
8970
  }
8971
+ function installReferencedFiles2(install) {
8972
+ return [
8973
+ ...(install?.mainFiles ?? []).map((file) => file.from),
8974
+ ...(install?.helperFiles ?? []).map((file) => file.from)
8975
+ ];
8976
+ }
8977
+ function isRootLicenseOrNotice(path2) {
8978
+ return !path2.includes("/") && /^(?:licen[cs]e|notice|copying)(?:$|[._-])/i.test(path2);
8979
+ }
8980
+ async function listRegularPackageFiles(root, current = root) {
8981
+ const files = [];
8982
+ for (const entry of await (0, import_promises8.readdir)(current, { withFileTypes: true })) {
8983
+ const absolute = (0, import_node_path11.join)(current, entry.name);
8984
+ if (entry.isDirectory()) {
8985
+ files.push(...await listRegularPackageFiles(root, absolute));
8986
+ } else if (entry.isFile()) {
8987
+ files.push(normalizeRelativePath((0, import_node_path11.relative)(root, absolute)));
8988
+ }
8989
+ }
8990
+ return files;
8991
+ }
8992
+ async function collectSkillSupportingFiles(packageRoot, manifest) {
8993
+ const entry = assertSafeRelativePath(manifest.entry);
8994
+ const entryDirectory = (0, import_node_path11.dirname)(entry);
8995
+ const excluded = /* @__PURE__ */ new Set([
8996
+ entry,
8997
+ "aipm.manifest.json",
8998
+ "pkg.tgz",
8999
+ ...installReferencedFiles2(manifest.install).map(assertSafeRelativePath)
9000
+ ]);
9001
+ const supportingFiles = [];
9002
+ for (const sourcePath of await listRegularPackageFiles(packageRoot)) {
9003
+ if (excluded.has(sourcePath)) continue;
9004
+ const fromEntryDirectory = normalizeRelativePath((0, import_node_path11.relative)(entryDirectory, sourcePath));
9005
+ const isEntrySibling = fromEntryDirectory !== ".." && !fromEntryDirectory.startsWith("../") && !fromEntryDirectory.startsWith("/");
9006
+ if (!isEntrySibling && !isRootLicenseOrNotice(sourcePath)) continue;
9007
+ const destinationPath = isEntrySibling ? fromEntryDirectory : (0, import_node_path11.basename)(sourcePath);
9008
+ if (destinationPath.toLowerCase() === "skill.md") {
9009
+ throw new Error(`Package supporting file conflicts with installed SKILL.md: ${sourcePath}`);
9010
+ }
9011
+ const source = safeJoin(packageRoot, sourcePath);
9012
+ supportingFiles.push({
9013
+ path: destinationPath,
9014
+ content: await (0, import_promises8.readFile)(source),
9015
+ mode: (await (0, import_promises8.stat)(source)).mode
9016
+ });
9017
+ }
9018
+ return supportingFiles.sort((a, b) => a.path.localeCompare(b.path));
9019
+ }
8497
9020
  async function copyMainFile(input2) {
8498
9021
  const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8499
9022
  const target = safeJoin(input2.installRoot, input2.file.to);
8500
- const overwrite = input2.file.overwrite ?? "fail";
8501
- const exists = await (0, import_promises9.stat)(target).catch(() => null);
9023
+ let overwrite = input2.file.overwrite ?? "fail";
9024
+ const exists = await (0, import_promises8.stat)(target).catch(() => null);
8502
9025
  if (exists) {
8503
9026
  if (overwrite === "skip") return null;
8504
- if (overwrite === "fail") throw new Error(`Install target already exists: ${target}`);
9027
+ if (overwrite === "fail") {
9028
+ overwrite = await offerChoices({
9029
+ ci: input2.ci,
9030
+ note: {
9031
+ type: "warn",
9032
+ title: "File already exists",
9033
+ message: target
9034
+ },
9035
+ message: "How do you want to continue?",
9036
+ choices: [
9037
+ { value: "replace", label: "Overwrite the existing file" },
9038
+ { value: "skip", label: "Skip this file" }
9039
+ ],
9040
+ fallbackError: `Install target already exists: ${target}`
9041
+ });
9042
+ if (overwrite === "skip") return null;
9043
+ }
8505
9044
  }
8506
- await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
8507
- await (0, import_promises9.cp)(source, target, { force: overwrite === "replace" });
9045
+ await (0, import_promises8.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
9046
+ await (0, import_promises8.cp)(source, target, { force: overwrite === "replace" });
8508
9047
  return target;
8509
9048
  }
8510
9049
  async function copyHelperFile(input2) {
8511
9050
  const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8512
- const target = safeJoin(input2.helperRoot, input2.file.to ?? (0, import_node_path10.basename)(input2.file.from));
8513
- await (0, import_promises9.mkdir)((0, import_node_path10.dirname)(target), { recursive: true });
8514
- await (0, import_promises9.cp)(source, target, { force: true });
9051
+ const target = safeJoin(input2.helperRoot, input2.file.to ?? (0, import_node_path11.basename)(input2.file.from));
9052
+ await (0, import_promises8.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
9053
+ await (0, import_promises8.cp)(source, target, { force: true });
8515
9054
  return target;
8516
9055
  }
8517
9056
  async function installPackageAssets(options) {
@@ -8522,13 +9061,14 @@ async function installPackageAssets(options) {
8522
9061
  const copied = await copyMainFile({
8523
9062
  packageRoot: options.packageRoot,
8524
9063
  installRoot: options.installRoot,
8525
- file
9064
+ file,
9065
+ ci: options.ci
8526
9066
  });
8527
9067
  if (copied) assets.main.push(copied);
8528
9068
  }
8529
9069
  const helperRoot = helperRootFor(options.configRoot, options.packageName, options.version);
8530
9070
  if ((install.helperFiles ?? []).length > 0) {
8531
- await (0, import_promises9.rm)(helperRoot, { recursive: true, force: true });
9071
+ await (0, import_promises8.rm)(helperRoot, { recursive: true, force: true });
8532
9072
  }
8533
9073
  for (const file of install.helperFiles ?? []) {
8534
9074
  assets.helper.push(await copyHelperFile({ packageRoot: options.packageRoot, helperRoot, file }));
@@ -8559,121 +9099,163 @@ function printPostInstallNotice(name, postInstall) {
8559
9099
  async function installOnePackage(options) {
8560
9100
  const configRoot = options.configRoot;
8561
9101
  const installRoot = options.installRoot ?? configRoot;
8562
- await assertRegistryReachable(options.registry);
8563
- const { manifest, integrity: remoteIntegrity, deprecated } = await fetchPackageMetadata(
8564
- options.registry,
8565
- options.name,
8566
- options.version,
8567
- options.token
8568
- );
8569
- if (deprecated) {
8570
- console.warn(
8571
- `Warning: ${options.name} is deprecated${deprecated.message ? `: ${deprecated.message}` : ""}.`
8572
- );
8573
- }
8574
- let explicitTarget = options.explicitTarget;
8575
- let preferredTools = options.project.preferredTools;
8576
- const { resolveInstallTools: resolveInstallTools2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports));
8577
- let tools = await resolveInstallTools2({
8578
- projectRoot: installRoot,
8579
- manifest,
8580
- preferredTools,
8581
- explicitTarget
9102
+ const spinner = createSpinner({
9103
+ text: `Fetching ${options.name}@${options.version}`,
9104
+ disabled: options.ci
8582
9105
  });
8583
- if (tools.length === 0) {
8584
- if (options.ci) {
8585
- throw new Error(
8586
- "No tool detected. Use --target cursor|claude|codex|* in CI mode."
8587
- );
8588
- }
8589
- const choice = await promptForTool();
8590
- explicitTarget = choice;
8591
- preferredTools = [choice];
8592
- tools = await resolveInstallTools2({
8593
- projectRoot: installRoot,
8594
- manifest,
8595
- preferredTools,
8596
- explicitTarget
8597
- });
8598
- }
8599
- const tarball = await fetchPackageTarball(
8600
- options.registry,
8601
- options.name,
8602
- options.version,
8603
- options.token
8604
- );
8605
- const packageRoot = await unpackTarballToDirectory(tarball);
9106
+ spinner.start();
8606
9107
  try {
8607
- const skillMarkdown = await (0, import_promises9.readFile)(safeJoin(packageRoot, manifest.entry), "utf8");
8608
- const result = await installSkillPackage({
9108
+ await assertRegistryReachable(options.registry);
9109
+ const { manifest, integrity: remoteIntegrity, deprecated } = await fetchPackageMetadata(
9110
+ options.registry,
9111
+ options.name,
9112
+ options.version,
9113
+ options.token
9114
+ );
9115
+ if (deprecated) {
9116
+ spinner.stop();
9117
+ printNote({
9118
+ type: "warn",
9119
+ title: "Deprecated package",
9120
+ message: `${options.name}${deprecated.message ? `: ${deprecated.message}` : ""}`
9121
+ });
9122
+ spinner.start(`Installing ${options.name}@${options.version}`);
9123
+ }
9124
+ let explicitTarget = options.explicitTarget;
9125
+ let preferredTools = options.project.preferredTools;
9126
+ const { resolveInstallTools: resolveInstallTools2 } = await Promise.resolve().then(() => (init_dist6(), dist_exports));
9127
+ let tools = await resolveInstallTools2({
8609
9128
  projectRoot: installRoot,
8610
9129
  manifest,
8611
- skillMarkdown,
8612
9130
  preferredTools,
8613
9131
  explicitTarget
8614
9132
  });
8615
- const { assets, postInstall } = await installPackageAssets({
8616
- configRoot,
8617
- installRoot,
8618
- packageRoot,
8619
- packageName: options.name,
8620
- version: options.version,
8621
- install: manifest.install
8622
- });
8623
- const lock = await readLockfile(configRoot) ?? {
8624
- schemaVersion: "0.1",
8625
- packages: {},
8626
- prompts: {}
8627
- };
8628
- const installed = {};
8629
- for (const tool of result.resolvedTools) {
8630
- const paths = result.installed[tool];
8631
- if (paths) installed[tool] = paths;
8632
- }
8633
- await writeLockfile(
8634
- configRoot,
8635
- upsertLockEntry(lock, options.name, {
8636
- version: options.version,
8637
- integrity: remoteIntegrity,
8638
- registry: options.registry,
8639
- resolvedTools: result.resolvedTools,
8640
- installed,
8641
- ...assets.main.length || assets.helper.length ? { installedAssets: assets } : {},
8642
- ...postInstall ? { postInstall } : {}
8643
- })
9133
+ if (tools.length === 0) {
9134
+ spinner.stop();
9135
+ if (options.ci) {
9136
+ throw new Error(
9137
+ "No tool detected. Use --target cursor|claude|codex|* in CI mode."
9138
+ );
9139
+ }
9140
+ printNote({
9141
+ type: "info",
9142
+ title: "No AI tool folder found",
9143
+ message: "Could not find .cursor, .claude, or .codex in this project."
9144
+ });
9145
+ const choice = await promptForTool();
9146
+ explicitTarget = choice;
9147
+ preferredTools = [choice];
9148
+ tools = await resolveInstallTools2({
9149
+ projectRoot: installRoot,
9150
+ manifest,
9151
+ preferredTools,
9152
+ explicitTarget
9153
+ });
9154
+ spinner.start(`Installing ${options.name}@${options.version}`);
9155
+ } else if (tools.length > 1 && !explicitTarget && !options.ci) {
9156
+ spinner.stop();
9157
+ printNote({
9158
+ type: "info",
9159
+ title: "Multiple AI tools found",
9160
+ message: `Detected: ${tools.join(", ")}`
9161
+ });
9162
+ const choice = await promptForTool(tools);
9163
+ explicitTarget = choice;
9164
+ preferredTools = [choice];
9165
+ tools = [choice];
9166
+ spinner.start(`Installing ${options.name}@${options.version}`);
9167
+ }
9168
+ spinner.update(`Downloading ${options.name}@${options.version}`);
9169
+ const tarball = await fetchPackageTarball(
9170
+ options.registry,
9171
+ options.name,
9172
+ options.version,
9173
+ options.token
8644
9174
  );
8645
- console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8646
- printPostInstallNotice(options.name, postInstall);
8647
- } finally {
8648
- await (0, import_promises9.rm)(packageRoot, { recursive: true, force: true });
9175
+ const packageRoot = await unpackTarballToDirectory(tarball);
9176
+ try {
9177
+ spinner.update(`Installing ${options.name}@${options.version}`);
9178
+ const skillMarkdown = await (0, import_promises8.readFile)(safeJoin(packageRoot, manifest.entry), "utf8");
9179
+ const supportingFiles = await collectSkillSupportingFiles(packageRoot, manifest);
9180
+ const result = await installSkillPackage({
9181
+ projectRoot: installRoot,
9182
+ manifest,
9183
+ skillMarkdown,
9184
+ supportingFiles,
9185
+ preferredTools,
9186
+ explicitTarget
9187
+ });
9188
+ const { assets, postInstall } = await installPackageAssets({
9189
+ configRoot,
9190
+ installRoot,
9191
+ packageRoot,
9192
+ packageName: options.name,
9193
+ version: options.version,
9194
+ install: manifest.install,
9195
+ ci: options.ci
9196
+ });
9197
+ const track = options.track !== false;
9198
+ if (track) {
9199
+ const lock = await readLockfile(configRoot) ?? {
9200
+ schemaVersion: "0.1",
9201
+ packages: {},
9202
+ prompts: {}
9203
+ };
9204
+ const installed = {};
9205
+ for (const tool of result.resolvedTools) {
9206
+ const paths = result.installed[tool];
9207
+ if (paths) installed[tool] = paths;
9208
+ }
9209
+ await writeLockfile(
9210
+ configRoot,
9211
+ upsertLockEntry(lock, options.name, {
9212
+ version: options.version,
9213
+ integrity: remoteIntegrity,
9214
+ registry: options.registry,
9215
+ resolvedTools: result.resolvedTools,
9216
+ installed,
9217
+ ...assets.main.length || assets.helper.length ? { installedAssets: assets } : {},
9218
+ ...postInstall ? { postInstall } : {}
9219
+ })
9220
+ );
9221
+ }
9222
+ spinner.stop();
9223
+ console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
9224
+ printPostInstallNotice(options.name, postInstall);
9225
+ } finally {
9226
+ await (0, import_promises8.rm)(packageRoot, { recursive: true, force: true });
9227
+ }
9228
+ } catch (error) {
9229
+ spinner.fail(`Failed to install ${options.name}@${options.version}`);
9230
+ throw error;
8649
9231
  }
8650
9232
  }
8651
9233
 
8652
9234
  // src/doctor.ts
8653
9235
  var import_node_child_process3 = require("node:child_process");
8654
- var import_promises10 = require("node:fs/promises");
8655
- var import_node_path12 = require("node:path");
9236
+ var import_promises9 = require("node:fs/promises");
9237
+ var import_node_path13 = require("node:path");
8656
9238
  var import_node_util4 = require("node:util");
8657
9239
 
8658
9240
  // src/project-root.ts
8659
9241
  var import_node_os3 = require("node:os");
8660
- var import_node_path11 = require("node:path");
8661
- var import_node_process3 = require("node:process");
9242
+ var import_node_path12 = require("node:path");
9243
+ var import_node_process4 = require("node:process");
8662
9244
  function globalConfigDir(env2 = process.env) {
8663
9245
  const fromEnv = env2.AIPM_HOME?.trim();
8664
- return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path11.join)((0, import_node_os3.homedir)(), ".aipm");
9246
+ return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path12.join)((0, import_node_os3.homedir)(), ".aipm");
8665
9247
  }
8666
9248
  function resolveConfigRoot(options = {}) {
8667
- return options.global ? globalConfigDir() : (0, import_node_process3.cwd)();
9249
+ return options.global ? globalConfigDir() : (0, import_node_process4.cwd)();
8668
9250
  }
8669
9251
  function resolveInstallRoot(options = {}) {
8670
- return options.global ? (0, import_node_os3.homedir)() : (0, import_node_process3.cwd)();
9252
+ return options.global ? (0, import_node_os3.homedir)() : (0, import_node_process4.cwd)();
8671
9253
  }
8672
9254
  function scopeLabel(options) {
8673
9255
  return options.global ? "global" : "project";
8674
9256
  }
8675
9257
  function initRequiredMessage(options) {
8676
- return options.global ? "Run aipm init -g first" : "Run aipm init first";
9258
+ return options.global ? `Run ${recommendCmd("aipm init -g")} first` : `Run ${recommendCmd("aipm init")} first`;
8677
9259
  }
8678
9260
 
8679
9261
  // src/doctor.ts
@@ -8692,11 +9274,11 @@ async function commandOutput(command, args) {
8692
9274
  }
8693
9275
  function npmGlobalBin(prefix) {
8694
9276
  if (!prefix) return null;
8695
- return process.platform === "win32" ? prefix : `${prefix}${import_node_path12.sep}bin`;
9277
+ return process.platform === "win32" ? prefix : `${prefix}${import_node_path13.sep}bin`;
8696
9278
  }
8697
9279
  function pathIncludes(pathDir) {
8698
9280
  if (!pathDir) return false;
8699
- return (process.env.PATH ?? "").split(import_node_path12.delimiter).includes(pathDir);
9281
+ return (process.env.PATH ?? "").split(import_node_path13.delimiter).includes(pathDir);
8700
9282
  }
8701
9283
  function shellPathHint(binDir) {
8702
9284
  if (!binDir) return "Run npm prefix -g and add its bin folder to PATH.";
@@ -8749,11 +9331,11 @@ async function runDoctor(options) {
8749
9331
  if (!options.publish) {
8750
9332
  const configLabel = options.global ? "Global config" : "Project config";
8751
9333
  try {
8752
- await (0, import_promises10.access)((0, import_node_path12.join)(configRoot, "aipm.package.json"));
9334
+ await (0, import_promises9.access)((0, import_node_path13.join)(configRoot, "aipm.package.json"));
8753
9335
  checks.push({
8754
9336
  name: configLabel,
8755
9337
  ok: true,
8756
- detail: `${(0, import_node_path12.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
9338
+ detail: `${(0, import_node_path13.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
8757
9339
  });
8758
9340
  } catch {
8759
9341
  checks.push({
@@ -8783,7 +9365,7 @@ async function runDoctor(options) {
8783
9365
  checks.push({
8784
9366
  name: "Publish stage",
8785
9367
  ok: rows.length > 0 && changed.length === 0,
8786
- detail: rows.length === 0 ? "No files staged. Run aipm publish add ." : changed.length > 0 ? `${changed.length} staged file(s) changed after add. Run aipm publish add . again.` : `${rows.length} file(s) staged.`
9368
+ detail: rows.length === 0 ? `No files staged. Run ${recommendCmd("aipm publish add .")}` : changed.length > 0 ? `${changed.length} staged file(s) changed after add. Run ${recommendCmd("aipm publish add .")} again.` : `${rows.length} file(s) staged.`
8787
9369
  });
8788
9370
  } catch (error) {
8789
9371
  checks.push({
@@ -8823,8 +9405,88 @@ async function runDoctor(options) {
8823
9405
 
8824
9406
  // src/prompt-install.ts
8825
9407
  var import_node_crypto2 = require("node:crypto");
8826
- var import_promises11 = require("node:fs/promises");
8827
- var import_node_path13 = require("node:path");
9408
+ var import_promises10 = require("node:fs/promises");
9409
+ var import_node_path14 = require("node:path");
9410
+
9411
+ // src/cli-recover.ts
9412
+ async function resolvePromptRequiresTracking(options) {
9413
+ const choice = await offerChoices({
9414
+ ci: options.ci,
9415
+ note: {
9416
+ type: "warn",
9417
+ title: "Prompt URLs need project tracking",
9418
+ message: "--no-init cannot manage prompts without aipm.package.json."
9419
+ },
9420
+ message: "How do you want to continue?",
9421
+ choices: [
9422
+ {
9423
+ value: "tracked",
9424
+ label: "Continue with project tracking",
9425
+ hint: "drop --no-init for this run"
9426
+ }
9427
+ ],
9428
+ fallbackError: "Prompt URLs require project tracking. Drop --no-init, or run aipm init and continue without --no-init."
9429
+ });
9430
+ return choice;
9431
+ }
9432
+ async function resolvePrivateInstallFailure(options) {
9433
+ const looksPrivate = /private|login|unauthorized|forbidden|401|403|aipm login/i.test(options.errorMessage);
9434
+ if (!looksPrivate) {
9435
+ throw new Error(options.errorMessage);
9436
+ }
9437
+ const choice = await offerChoices({
9438
+ ci: options.ci,
9439
+ note: {
9440
+ type: "error",
9441
+ title: "Package install needs authentication",
9442
+ message: options.errorMessage
9443
+ },
9444
+ message: "What do you want to do?",
9445
+ choices: [
9446
+ {
9447
+ value: "login-retry",
9448
+ label: "Log in and retry",
9449
+ hint: "aipm login"
9450
+ }
9451
+ ],
9452
+ fallbackError: options.errorMessage
9453
+ });
9454
+ return choice;
9455
+ }
9456
+ async function resolveLoginRequired(options) {
9457
+ return offerChoices({
9458
+ ci: options.ci,
9459
+ note: {
9460
+ type: "warn",
9461
+ title: "Login required",
9462
+ message: options.purpose
9463
+ },
9464
+ message: "What do you want to do?",
9465
+ choices: [{ value: "login", label: "Log in now", hint: "opens browser" }],
9466
+ fallbackError: `Run ${recommendCmd("aipm login")} first.`
9467
+ });
9468
+ }
9469
+ async function resolvePromptAliasConflict(options) {
9470
+ return offerChoices({
9471
+ ci: options.ci,
9472
+ note: {
9473
+ type: "warn",
9474
+ title: "Prompt alias already in use",
9475
+ message: `"${options.alias}" currently tracks ${options.currentUrl}`
9476
+ },
9477
+ message: "Replace it with the new prompt URL?",
9478
+ choices: [
9479
+ {
9480
+ value: "replace",
9481
+ label: "Replace and continue",
9482
+ hint: options.nextUrl
9483
+ }
9484
+ ],
9485
+ fallbackError: `Prompt alias "${options.alias}" already tracks ${options.currentUrl}. Remove it first or use a unique slug.`
9486
+ });
9487
+ }
9488
+
9489
+ // src/prompt-install.ts
8828
9490
  var PROMPT_SITE_ORIGIN = "https://www.aipm-registry.com";
8829
9491
  function parsePromptUrl(value) {
8830
9492
  let url;
@@ -8849,10 +9511,10 @@ function parsePromptUrl(value) {
8849
9511
  };
8850
9512
  }
8851
9513
  function promptDirectory(configRoot) {
8852
- return (0, import_node_path13.basename)(configRoot) === ".aipm" ? (0, import_node_path13.join)(configRoot, "prompts") : (0, import_node_path13.join)(configRoot, ".aipm", "prompts");
9514
+ return (0, import_node_path14.basename)(configRoot) === ".aipm" ? (0, import_node_path14.join)(configRoot, "prompts") : (0, import_node_path14.join)(configRoot, ".aipm", "prompts");
8853
9515
  }
8854
9516
  function promptSnapshotPath(configRoot, reference) {
8855
- return (0, import_node_path13.join)(promptDirectory(configRoot), `${reference.publisher}--${reference.slug}.md`);
9517
+ return (0, import_node_path14.join)(promptDirectory(configRoot), `${reference.publisher}--${reference.slug}.md`);
8856
9518
  }
8857
9519
  function canonicalPromptContent(prompt) {
8858
9520
  return JSON.stringify({
@@ -8911,7 +9573,7 @@ function emptyLock() {
8911
9573
  return { schemaVersion: "0.1", packages: {}, prompts: {} };
8912
9574
  }
8913
9575
  function installedRelativePath(configRoot, absolutePath) {
8914
- return (0, import_node_path13.relative)(configRoot, absolutePath).split("\\").join("/");
9576
+ return (0, import_node_path14.relative)(configRoot, absolutePath).split("\\").join("/");
8915
9577
  }
8916
9578
  async function installTrackedPrompt(input2) {
8917
9579
  const prompt = await fetchPromptDetail(
@@ -8929,18 +9591,21 @@ async function installTrackedPrompt(input2) {
8929
9591
  const contentHash = promptContentHash(prompt);
8930
9592
  const lock = await readLockfile(input2.configRoot) ?? emptyLock();
8931
9593
  const existing = lock.prompts[input2.reference.alias];
8932
- const fileExists = Boolean(await (0, import_promises11.stat)(outputPath).catch(() => null));
9594
+ const fileExists = Boolean(await (0, import_promises10.stat)(outputPath).catch(() => null));
8933
9595
  const changed = !existing || existing.contentHash !== contentHash || !fileExists;
8934
9596
  if (!input2.updateOnly || changed) {
8935
- await (0, import_promises11.mkdir)((0, import_node_path13.dirname)(outputPath), { recursive: true });
8936
- await (0, import_promises11.writeFile)(outputPath, renderPromptMarkdown(prompt, canonicalReference.url), "utf8");
9597
+ await (0, import_promises10.mkdir)((0, import_node_path14.dirname)(outputPath), { recursive: true });
9598
+ await (0, import_promises10.writeFile)(outputPath, renderPromptMarkdown(prompt, canonicalReference.url), "utf8");
8937
9599
  }
8938
9600
  if (input2.track) {
8939
9601
  const currentUrl = input2.project.prompts[input2.reference.alias];
8940
9602
  if (currentUrl && currentUrl !== canonicalReference.url) {
8941
- throw new Error(
8942
- `Prompt alias "${input2.reference.alias}" already tracks ${currentUrl}. Remove it first or use a unique slug.`
8943
- );
9603
+ await resolvePromptAliasConflict({
9604
+ ci: input2.ci,
9605
+ alias: input2.reference.alias,
9606
+ currentUrl,
9607
+ nextUrl: canonicalReference.url
9608
+ });
8944
9609
  }
8945
9610
  await writeProjectPackageJson(input2.configRoot, {
8946
9611
  ...input2.project,
@@ -8975,12 +9640,12 @@ function resolveTrackedPrompt(project, value) {
8975
9640
  return parsed ? { ...parsed, alias: value } : null;
8976
9641
  }
8977
9642
  async function readInstalledPrompt(configRoot, entry) {
8978
- return (0, import_promises11.readFile)((0, import_node_path13.join)(configRoot, entry.installedPath), "utf8");
9643
+ return (0, import_promises10.readFile)((0, import_node_path14.join)(configRoot, entry.installedPath), "utf8");
8979
9644
  }
8980
9645
  async function removeTrackedPrompt(input2) {
8981
9646
  const lock = await readLockfile(input2.configRoot);
8982
9647
  const entry = lock?.prompts[input2.reference.alias];
8983
- if (entry) await (0, import_promises11.rm)((0, import_node_path13.join)(input2.configRoot, entry.installedPath), { force: true });
9648
+ if (entry) await (0, import_promises10.rm)((0, import_node_path14.join)(input2.configRoot, entry.installedPath), { force: true });
8984
9649
  const prompts = { ...input2.project.prompts };
8985
9650
  delete prompts[input2.reference.alias];
8986
9651
  await writeProjectPackageJson(input2.configRoot, { ...input2.project, prompts });
@@ -8991,17 +9656,192 @@ async function removeTrackedPrompt(input2) {
8991
9656
  }
8992
9657
  }
8993
9658
 
9659
+ // src/remove-package.ts
9660
+ var import_promises11 = require("node:fs/promises");
9661
+ var import_node_path15 = require("node:path");
9662
+ function within(root, path2) {
9663
+ const rel = (0, import_node_path15.relative)((0, import_node_path15.resolve)(root), (0, import_node_path15.resolve)(path2));
9664
+ return rel === "" || !rel.startsWith("..") && !(0, import_node_path15.isAbsolute)(rel);
9665
+ }
9666
+ function trackedInstalledPackagePaths(entry) {
9667
+ return [
9668
+ ...Object.values(entry.installed).flat(),
9669
+ ...entry.installedAssets?.main ?? [],
9670
+ ...entry.installedAssets?.helper ?? []
9671
+ ];
9672
+ }
9673
+ async function pruneEmptyParents(path2, roots) {
9674
+ let current = (0, import_node_path15.dirname)(path2);
9675
+ while (roots.some((root) => within(root, current)) && !roots.some((root) => (0, import_node_path15.resolve)(root) === current)) {
9676
+ try {
9677
+ await (0, import_promises11.rmdir)(current);
9678
+ } catch {
9679
+ return;
9680
+ }
9681
+ current = (0, import_node_path15.dirname)(current);
9682
+ }
9683
+ }
9684
+ async function removeInstalledPackageFiles(input2) {
9685
+ const roots = [.../* @__PURE__ */ new Set([(0, import_node_path15.resolve)(input2.configRoot), (0, import_node_path15.resolve)(input2.installRoot)])];
9686
+ const realRoots = await Promise.all(roots.map((root) => (0, import_promises11.realpath)(root).catch(() => root)));
9687
+ const requestedPaths = [...new Set(trackedInstalledPackagePaths(input2.entry).map((path2) => (0, import_node_path15.resolve)(path2)))];
9688
+ const protectedPaths = new Set(
9689
+ (input2.otherEntries ?? []).flatMap(trackedInstalledPackagePaths).map((path2) => (0, import_node_path15.resolve)(path2))
9690
+ );
9691
+ const paths = requestedPaths.filter((path2) => !protectedPaths.has(path2));
9692
+ const unsafe = paths.find((path2) => !roots.some((root) => within(root, path2)));
9693
+ if (unsafe) throw new Error(`Refusing to remove an untrusted path from the lockfile: ${unsafe}`);
9694
+ for (const path2 of paths) {
9695
+ const stat6 = await (0, import_promises11.lstat)(path2).catch(() => null);
9696
+ if (!stat6 || stat6.isSymbolicLink()) continue;
9697
+ const canonicalPath = await (0, import_promises11.realpath)(path2);
9698
+ if (!realRoots.some((root) => within(root, canonicalPath))) {
9699
+ throw new Error(`Refusing to remove a path that resolves outside the install roots: ${path2}`);
9700
+ }
9701
+ }
9702
+ let removed = 0;
9703
+ for (const path2 of paths) {
9704
+ if (await (0, import_promises11.lstat)(path2).catch(() => null)) removed += 1;
9705
+ await (0, import_promises11.rm)(path2, { force: true });
9706
+ }
9707
+ for (const path2 of paths.sort((a, b) => b.length - a.length)) {
9708
+ await pruneEmptyParents(path2, roots);
9709
+ }
9710
+ return { removed, retainedShared: requestedPaths.length - paths.length };
9711
+ }
9712
+
9713
+ // src/no-init.ts
9714
+ var import_promises12 = require("node:fs/promises");
9715
+ var import_node_path16 = require("node:path");
9716
+ init_dist();
9717
+ function packageHelperSlug2(name) {
9718
+ const [scope = "", pkg = ""] = name.replace(/^@/, "").split("/");
9719
+ return `${scope}__${pkg}`;
9720
+ }
9721
+ function helpersBase(configRoot) {
9722
+ return (0, import_node_path16.basename)(configRoot) === ".aipm" ? (0, import_node_path16.join)(configRoot, "helpers") : (0, import_node_path16.join)(configRoot, ".aipm", "helpers");
9723
+ }
9724
+ async function resolveNoInitMode(options) {
9725
+ if (!options.requested) return false;
9726
+ if (!options.projectExists) return true;
9727
+ if (options.ci) {
9728
+ throw new Error(
9729
+ "Project already initialized (aipm.package.json exists). Drop --no-init, or run interactively to choose whether to continue without tracking."
9730
+ );
9731
+ }
9732
+ console.warn("Warning: aipm.package.json already exists in this project.");
9733
+ console.warn("You requested --no-init (install/manage without project tracking).");
9734
+ const choice = await promptForNoInitConflict();
9735
+ return choice === "no-init";
9736
+ }
9737
+ function discoverUntrackedPackagePaths(input2) {
9738
+ const short = shortNameFromScopeName(input2.packageName);
9739
+ const slug = packageHelperSlug2(input2.packageName);
9740
+ return [
9741
+ (0, import_node_path16.join)(input2.installRoot, ".cursor", "aipm", "skills", `${short}.md`),
9742
+ (0, import_node_path16.join)(input2.installRoot, ".claude", "skills", short),
9743
+ (0, import_node_path16.join)(input2.installRoot, ".agents", "skills", short),
9744
+ (0, import_node_path16.join)(helpersBase(input2.configRoot), slug)
9745
+ ];
9746
+ }
9747
+ async function removeUntrackedPackage(input2) {
9748
+ const candidates = discoverUntrackedPackagePaths(input2);
9749
+ const removed = [];
9750
+ for (const path2 of candidates) {
9751
+ const info = await (0, import_promises12.stat)(path2).catch(() => null);
9752
+ if (!info) continue;
9753
+ await (0, import_promises12.rm)(path2, { recursive: true, force: true });
9754
+ removed.push(path2);
9755
+ }
9756
+ if (removed.length === 0) {
9757
+ throw new Error(
9758
+ `No untracked install found for ${input2.packageName}. Nothing to remove under known tool skill paths or .aipm/helpers.`
9759
+ );
9760
+ }
9761
+ return { removed };
9762
+ }
9763
+
9764
+ // src/ensure-project.ts
9765
+ var import_promises13 = require("node:fs/promises");
9766
+ init_dist6();
9767
+ async function ensureProjectOrOffer(options) {
9768
+ if (options.project) {
9769
+ return { action: "ready", project: options.project };
9770
+ }
9771
+ const fallback = initRequiredMessage(options.scope);
9772
+ const choices = options.allowNoInit ? [
9773
+ {
9774
+ value: "init",
9775
+ label: "Initialize AiPM",
9776
+ hint: `create aipm.package.json (${scopeLabel(options.scope)})`
9777
+ },
9778
+ {
9779
+ value: "no-init",
9780
+ label: "Continue without initializing",
9781
+ hint: "one-time / no project tracking"
9782
+ }
9783
+ ] : [
9784
+ {
9785
+ value: "init",
9786
+ label: "Initialize AiPM",
9787
+ hint: `create aipm.package.json (${scopeLabel(options.scope)})`
9788
+ }
9789
+ ];
9790
+ const choice = await offerChoices({
9791
+ ci: options.ci,
9792
+ note: {
9793
+ type: "warn",
9794
+ title: "AiPM is not initialized",
9795
+ message: `No aipm.package.json found for this ${scopeLabel(options.scope)}.`
9796
+ },
9797
+ message: `How do you want to continue with aipm ${options.command}?`,
9798
+ choices: [...choices],
9799
+ fallbackError: fallback
9800
+ });
9801
+ if (choice === "no-init") {
9802
+ return { action: "no-init" };
9803
+ }
9804
+ const project = await createProjectPackageJson({
9805
+ scope: options.scope,
9806
+ registry: options.registry,
9807
+ target: options.target
9808
+ });
9809
+ return { action: "ready", project };
9810
+ }
9811
+ async function createProjectPackageJson(options) {
9812
+ const configRoot = resolveConfigRoot(options.scope);
9813
+ const installRoot = resolveInstallRoot(options.scope);
9814
+ if (options.scope.global) await (0, import_promises13.mkdir)(configRoot, { recursive: true });
9815
+ const detected = await detectToolsInProject(installRoot);
9816
+ const parsedTarget = parseTargetFlag(options.target);
9817
+ let preferredTools = parsedTarget ? [parsedTarget] : detected;
9818
+ if (!options.target && detected.length === 0) {
9819
+ const choice = await promptForTool();
9820
+ preferredTools = [choice];
9821
+ }
9822
+ const project = {
9823
+ schemaVersion: "0.1",
9824
+ registry: options.registry,
9825
+ preferredTools: preferredTools.length ? preferredTools : void 0,
9826
+ packages: {},
9827
+ prompts: {}
9828
+ };
9829
+ await writeProjectPackageJson(configRoot, project);
9830
+ console.log(`Created aipm.package.json (${scopeLabel(options.scope)}, registry: ${options.registry})`);
9831
+ return project;
9832
+ }
9833
+
8994
9834
  // src/version.ts
8995
9835
  var import_node_fs2 = require("node:fs");
8996
- var import_node_path14 = require("node:path");
9836
+ var import_node_path17 = require("node:path");
8997
9837
  function getCliVersion() {
8998
- const pkgPath = (0, import_node_path14.join)(__dirname, "..", "package.json");
9838
+ const pkgPath = (0, import_node_path17.join)(__dirname, "..", "package.json");
8999
9839
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
9000
9840
  return pkg.version;
9001
9841
  }
9002
9842
 
9003
9843
  // src/cli-update-check.ts
9004
- var import_node_process4 = require("node:process");
9844
+ var import_node_process5 = require("node:process");
9005
9845
  var CLI_PACKAGE_NAME = "@aipm-registry/cli";
9006
9846
  var CLI_UPDATE_COMMAND = `npm install -g ${CLI_PACKAGE_NAME}`;
9007
9847
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -9052,7 +9892,7 @@ function formatCliUpdateNotice(options) {
9052
9892
  return `A new AIPM CLI version is available (${currentVersion} \u2192 ${latestVersion}). Run: ${cmd}`;
9053
9893
  }
9054
9894
  function printCliUpdateNotice(options) {
9055
- const useColor2 = options.useColor ?? import_node_process4.stderr.isTTY;
9895
+ const useColor2 = options.useColor ?? import_node_process5.stderr.isTTY;
9056
9896
  const writeInfo = options.writeInfo ?? ((message2) => console.log(message2));
9057
9897
  const writeWarn = options.writeWarn ?? ((message2) => console.error(message2));
9058
9898
  const message = formatCliUpdateNotice(options);
@@ -9117,7 +9957,7 @@ function registryFromEnvOrDefault(flag) {
9117
9957
  }
9118
9958
  function packagePageUrl(packageName, version) {
9119
9959
  const [scope, name] = packageName.replace(/^@/, "").split("/");
9120
- return `${SITE_URL}/packages/${encodeURIComponent(scope ?? "")}/${encodeURIComponent(name ?? "")}/${encodeURIComponent(version)}`;
9960
+ return `${SITE_URL}/skills/${encodeURIComponent(scope ?? "")}/${encodeURIComponent(name ?? "")}/${encodeURIComponent(version)}`;
9121
9961
  }
9122
9962
  function packageBadgeMarkdown(packageName, version) {
9123
9963
  const pageUrl = packagePageUrl(packageName, version);
@@ -9137,23 +9977,23 @@ function skillFolderName(name) {
9137
9977
  return folder;
9138
9978
  }
9139
9979
  async function writeStarterFile(path2, content) {
9140
- await (0, import_promises12.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
9980
+ await (0, import_promises14.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
9141
9981
  }
9142
9982
  async function inferEntryFromSource(source, fallback) {
9143
- const sourceInfo = await (0, import_promises12.stat)(source);
9144
- if (sourceInfo.isFile()) return (0, import_node_path15.basename)(source);
9983
+ const sourceInfo = await (0, import_promises14.stat)(source);
9984
+ if (sourceInfo.isFile()) return (0, import_node_path18.basename)(source);
9145
9985
  if (!sourceInfo.isDirectory()) return fallback;
9146
- const entries = await (0, import_promises12.readdir)(source);
9986
+ const entries = await (0, import_promises14.readdir)(source);
9147
9987
  if (entries.includes(fallback)) return fallback;
9148
9988
  return entries.find((entry) => entry.endsWith(".md")) ?? entries.find((entry) => entry.endsWith(".mdc")) ?? fallback;
9149
9989
  }
9150
9990
  async function copySourceIntoSkillRoot(source, root, sourceLabel) {
9151
- const sourceInfo = await (0, import_promises12.stat)(source);
9152
- await (0, import_promises12.mkdir)(root, { recursive: true });
9991
+ const sourceInfo = await (0, import_promises14.stat)(source);
9992
+ await (0, import_promises14.mkdir)(root, { recursive: true });
9153
9993
  if (sourceInfo.isDirectory()) {
9154
- const entries = await (0, import_promises12.readdir)(source);
9994
+ const entries = await (0, import_promises14.readdir)(source);
9155
9995
  for (const entry of entries) {
9156
- await (0, import_promises12.cp)((0, import_node_path15.join)(source, entry), (0, import_node_path15.join)(root, entry), {
9996
+ await (0, import_promises14.cp)((0, import_node_path18.join)(source, entry), (0, import_node_path18.join)(root, entry), {
9157
9997
  recursive: true,
9158
9998
  force: false,
9159
9999
  errorOnExist: true
@@ -9162,7 +10002,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
9162
10002
  return;
9163
10003
  }
9164
10004
  if (sourceInfo.isFile()) {
9165
- await (0, import_promises12.cp)(source, (0, import_node_path15.join)(root, (0, import_node_path15.basename)(source)), { force: false, errorOnExist: true });
10005
+ await (0, import_promises14.cp)(source, (0, import_node_path18.join)(root, (0, import_node_path18.basename)(source)), { force: false, errorOnExist: true });
9166
10006
  return;
9167
10007
  }
9168
10008
  throw new Error(`Unsupported source path: ${sourceLabel}`);
@@ -9193,14 +10033,14 @@ function formatBytes(bytes) {
9193
10033
  }
9194
10034
  function packageDashboardUrl(name) {
9195
10035
  if (!name) return DASHBOARD_URL;
9196
- return `${DASHBOARD_URL}/packages/${name.replace(/^@/, "")}`;
10036
+ return `${DASHBOARD_URL}/skills/${name.replace(/^@/, "")}`;
9197
10037
  }
9198
10038
  function helperRootFromTrackedPath(path2) {
9199
- const parts = path2.split(import_node_path15.sep).filter(Boolean);
10039
+ const parts = path2.split(import_node_path18.sep).filter(Boolean);
9200
10040
  const helpersIndex = parts.lastIndexOf("helpers");
9201
10041
  if (helpersIndex === -1 || parts.length <= helpersIndex + 2) return null;
9202
- const prefix = path2.startsWith(import_node_path15.sep) ? import_node_path15.sep : "";
9203
- return (0, import_node_path15.join)(prefix, ...parts.slice(0, helpersIndex + 3));
10042
+ const prefix = path2.startsWith(import_node_path18.sep) ? import_node_path18.sep : "";
10043
+ return (0, import_node_path18.join)(prefix, ...parts.slice(0, helpersIndex + 3));
9204
10044
  }
9205
10045
  async function showInstalledPrompt(configRoot, packageArg) {
9206
10046
  const { name } = parsePackageArg(packageArg);
@@ -9213,7 +10053,7 @@ async function showInstalledPrompt(configRoot, packageArg) {
9213
10053
  if (entry.postInstall.status === "cleaned") {
9214
10054
  throw new Error(`Setup helper files were cleaned for ${name}. Reinstall the package to restore them.`);
9215
10055
  }
9216
- const content = await (0, import_promises12.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
10056
+ const content = await (0, import_promises14.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
9217
10057
  throw new Error(`Setup prompt file is missing: ${entry.postInstall?.promptFile}`);
9218
10058
  });
9219
10059
  console.log(`Prompt file: ${entry.postInstall.promptFile}`);
@@ -9246,9 +10086,9 @@ async function cleanupInstalledHelpers(options) {
9246
10086
  entry.installedAssets.helper.map((path2) => helperRootFromTrackedPath(path2)).filter((path2) => Boolean(path2))
9247
10087
  );
9248
10088
  if (roots.size > 0) {
9249
- for (const root of roots) await (0, import_promises12.rm)(root, { recursive: true, force: true });
10089
+ for (const root of roots) await (0, import_promises14.rm)(root, { recursive: true, force: true });
9250
10090
  } else {
9251
- for (const helperPath of entry.installedAssets.helper) await (0, import_promises12.rm)(helperPath, { force: true });
10091
+ for (const helperPath of entry.installedAssets.helper) await (0, import_promises14.rm)(helperPath, { force: true });
9252
10092
  }
9253
10093
  await writeLockfile(options.configRoot, {
9254
10094
  ...lock,
@@ -9293,20 +10133,76 @@ async function authTokenForRegistry(registry, options = {}) {
9293
10133
  return refreshed.accessToken;
9294
10134
  } catch {
9295
10135
  await clearStoredRegistryAuth(registry);
9296
- const message = `CLI login for ${registry} expired or was revoked. Run aipm login to access private packages.`;
10136
+ const message = `CLI login for ${registry} expired or was revoked. Run ${recommendCmd("aipm login")} to access private packages.`;
9297
10137
  if (options.throwOnFailure) throw new Error(message);
9298
10138
  if (!options.quiet) console.warn(`Warning: ${message}`);
9299
10139
  return void 0;
9300
10140
  }
9301
10141
  }
9302
10142
  async function tokenForRead(registry, explicitToken, options = {}) {
9303
- return explicitToken ?? import_node_process5.env.AIPM_TOKEN ?? await authTokenForRegistry(registry, options);
10143
+ return explicitToken ?? import_node_process6.env.AIPM_TOKEN ?? await authTokenForRegistry(registry, options);
10144
+ }
10145
+ async function installOnePackageWithRecovery(options) {
10146
+ try {
10147
+ await installOnePackage(options);
10148
+ } catch (error) {
10149
+ if (error instanceof RecoveryCancelledError || error instanceof Error && error.name === "SelectCancelledError") {
10150
+ throw error;
10151
+ }
10152
+ const message = error instanceof Error ? error.message : String(error);
10153
+ await resolvePrivateInstallFailure({ ci: options.ci, errorMessage: message });
10154
+ await runLoopbackLogin({
10155
+ registry: options.registry,
10156
+ siteUrl: SITE_URL,
10157
+ open: true
10158
+ });
10159
+ const token = await authTokenForRegistry(options.registry, { throwOnFailure: true });
10160
+ await installOnePackage({ ...options, token: token ?? options.token });
10161
+ }
10162
+ }
10163
+ async function addUntrackedPackage(options) {
10164
+ const { name, version: requestedVersion } = parsePackageArg(options.pkgArg);
10165
+ const token = await tokenForRead(options.registry, options.token);
10166
+ const version = requestedVersion ?? await latestVersionForPackage(options.registry, name, token);
10167
+ if (!version) throw new Error(`Specify version: ${recommendCmd("aipm add <@scope/pkg>@<version>")}`);
10168
+ const syntheticProject = {
10169
+ schemaVersion: "0.1",
10170
+ registry: options.registry,
10171
+ packages: {},
10172
+ prompts: {}
10173
+ };
10174
+ await installOnePackageWithRecovery({
10175
+ configRoot: options.configRoot,
10176
+ installRoot: options.installRoot,
10177
+ registry: options.registry,
10178
+ name,
10179
+ version: version.replace(/^\^/, ""),
10180
+ project: syntheticProject,
10181
+ explicitTarget: parseTargetFlag(options.target),
10182
+ ci: options.ci,
10183
+ token,
10184
+ track: false
10185
+ });
10186
+ try {
10187
+ await recordPackageInstall(options.registry, name, token);
10188
+ } catch (error) {
10189
+ if (!options.ci && !program2.opts().quiet) {
10190
+ const message = error instanceof Error ? error.message : String(error);
10191
+ console.warn(`Warning: could not record install count: ${message}`);
10192
+ }
10193
+ }
9304
10194
  }
9305
10195
  async function runLoopbackLogin(options) {
9306
10196
  const state = randomBase64Url(24);
9307
10197
  const verifier = randomBase64Url(48);
9308
10198
  const challenge = sha256Base64Url(verifier);
9309
10199
  const result = await new Promise((resolveLogin, rejectLogin) => {
10200
+ let settled = false;
10201
+ const settle = (fn) => {
10202
+ if (settled) return;
10203
+ settled = true;
10204
+ fn();
10205
+ };
9310
10206
  const server = (0, import_node_http.createServer)((request, response) => {
9311
10207
  const host = request.headers.host ?? "127.0.0.1";
9312
10208
  const url = new URL(request.url ?? "/", `http://${host}`);
@@ -9318,29 +10214,33 @@ async function runLoopbackLogin(options) {
9318
10214
  if (url.searchParams.get("state") !== state) {
9319
10215
  response.writeHead(400, { "content-type": "text/html" });
9320
10216
  response.end("<h1>AIPM login failed</h1><p>Invalid state. Return to the terminal and try again.</p>");
9321
- rejectLogin(new Error("Invalid CLI login state"));
9322
- server.close();
10217
+ forceCloseServer(server);
10218
+ settle(() => rejectLogin(new Error("Invalid CLI login state")));
9323
10219
  return;
9324
10220
  }
9325
10221
  const code = url.searchParams.get("code");
9326
10222
  if (!code) {
9327
10223
  response.writeHead(400, { "content-type": "text/html" });
9328
10224
  response.end("<h1>AIPM login failed</h1><p>Missing code. Return to the terminal and try again.</p>");
9329
- rejectLogin(new Error("Missing CLI authorization code"));
9330
- server.close();
10225
+ forceCloseServer(server);
10226
+ settle(() => rejectLogin(new Error("Missing CLI authorization code")));
9331
10227
  return;
9332
10228
  }
9333
10229
  response.writeHead(200, { "content-type": "text/html" });
9334
10230
  response.end("<h1>AIPM CLI is signed in</h1><p>You can close this window and return to the terminal.</p>");
9335
10231
  const address = server.address();
9336
10232
  if (!address || typeof address === "string") {
9337
- rejectLogin(new Error("Could not read CLI callback address"));
9338
- } else {
9339
- resolveLogin({ code, redirectUri: `http://127.0.0.1:${address.port}/callback` });
10233
+ forceCloseServer(server);
10234
+ settle(() => rejectLogin(new Error("Could not read CLI callback address")));
10235
+ return;
9340
10236
  }
9341
- server.close();
10237
+ forceCloseServer(server);
10238
+ settle(() => resolveLogin({ code, redirectUri: `http://127.0.0.1:${address.port}/callback` }));
10239
+ });
10240
+ server.once("error", (error) => {
10241
+ forceCloseServer(server);
10242
+ settle(() => rejectLogin(error));
9342
10243
  });
9343
- server.once("error", rejectLogin);
9344
10244
  server.listen(0, "127.0.0.1", () => {
9345
10245
  const address = server.address();
9346
10246
  const redirectUri = `http://127.0.0.1:${address.port}/callback`;
@@ -9353,10 +10253,12 @@ async function runLoopbackLogin(options) {
9353
10253
  if (options.open) void openUrl(loginUrl.toString());
9354
10254
  else console.log(`Open: ${loginUrl.toString()}`);
9355
10255
  });
9356
- server.setTimeout(5 * 60 * 1e3, () => {
9357
- rejectLogin(new Error("CLI login timed out"));
9358
- server.close();
9359
- });
10256
+ const timeout = setTimeout(() => {
10257
+ forceCloseServer(server);
10258
+ settle(() => rejectLogin(new Error("CLI login timed out")));
10259
+ }, 5 * 60 * 1e3);
10260
+ timeout.unref?.();
10261
+ server.once("close", () => clearTimeout(timeout));
9360
10262
  });
9361
10263
  const tokens = await exchangeCliAuthCode(options.registry, {
9362
10264
  code: result.code,
@@ -9375,6 +10277,12 @@ async function runLoopbackLogin(options) {
9375
10277
  console.log(`Logged in as ${label}.`);
9376
10278
  console.log(`Credentials saved to ${authFilePath()}`);
9377
10279
  }
10280
+ function forceCloseServer(server) {
10281
+ if (typeof server.closeAllConnections === "function") {
10282
+ server.closeAllConnections();
10283
+ }
10284
+ server.close();
10285
+ }
9378
10286
  function printPublishFlow() {
9379
10287
  console.log("AIPM publish flow");
9380
10288
  console.log("1. Create an account and organization in the dashboard.");
@@ -9542,9 +10450,9 @@ function buildStarterQualityMetadata(options) {
9542
10450
  }
9543
10451
  async function initSkill(opts) {
9544
10452
  if (!isValidScopeName(opts.name)) throw new Error("Invalid @scope/name");
9545
- const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path15.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
10453
+ const root = opts.here ? (0, import_node_process6.cwd)() : (0, import_node_path18.resolve)((0, import_node_process6.cwd)(), opts.dir ?? skillFolderName(opts.name));
9546
10454
  const cdTarget = opts.here ? null : opts.dir ?? skillFolderName(opts.name);
9547
- const source = opts.from ? (0, import_node_path15.resolve)(opts.from) : null;
10455
+ const source = opts.from ? (0, import_node_path18.resolve)(opts.from) : null;
9548
10456
  const entry = source ? await inferEntryFromSource(source, opts.entry) : opts.entry;
9549
10457
  const template = parseSkillTemplate(opts.template ?? "blank");
9550
10458
  const manifest = PackageManifestSchema.parse({
@@ -9565,17 +10473,17 @@ async function initSkill(opts) {
9565
10473
  if (source) {
9566
10474
  await copySourceIntoSkillRoot(source, root, source);
9567
10475
  } else {
9568
- await (0, import_promises12.mkdir)(root, { recursive: true });
10476
+ await (0, import_promises14.mkdir)(root, { recursive: true });
9569
10477
  }
9570
- await writeStarterFile((0, import_node_path15.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
10478
+ await writeStarterFile((0, import_node_path18.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9571
10479
  if (!opts.from) {
9572
10480
  await writeStarterFile(
9573
- (0, import_node_path15.join)(root, entry),
10481
+ (0, import_node_path18.join)(root, entry),
9574
10482
  SKILL_TEMPLATES[template](opts.name)
9575
10483
  );
9576
10484
  }
9577
10485
  await writeStarterFile(
9578
- (0, import_node_path15.join)(root, ".aipmignore"),
10486
+ (0, import_node_path18.join)(root, ".aipmignore"),
9579
10487
  [
9580
10488
  "# Files that should never be published with this skill",
9581
10489
  ".env*",
@@ -9593,7 +10501,7 @@ async function initSkill(opts) {
9593
10501
  ""
9594
10502
  ].join("\n")
9595
10503
  );
9596
- await (0, import_promises12.mkdir)((0, import_node_path15.join)(root, ".aipm"), { recursive: true });
10504
+ await (0, import_promises14.mkdir)((0, import_node_path18.join)(root, ".aipm"), { recursive: true });
9597
10505
  console.log(`Created ${opts.name} skill folder: ${root}`);
9598
10506
  printPublishGuide(
9599
10507
  opts.from ? `Copied ${opts.from} into ${root} and created aipm.manifest.json, .aipmignore, and the local publish state folder.` : `Created ${root} with aipm.manifest.json, ${entry}, .aipmignore, and the local publish state folder.`,
@@ -9608,24 +10516,11 @@ program2.command("init").description("Create aipm.package.json in the current pr
9608
10516
  console.log(`aipm.package.json already exists (${scopeLabel(scope)}).`);
9609
10517
  return;
9610
10518
  }
9611
- if (scope.global) await (0, import_promises12.mkdir)(configRoot, { recursive: true });
9612
- const installRoot = resolveInstallRoot(scope);
9613
- const detected = await detectToolsInProject(installRoot);
9614
- const parsedTarget = parseTargetFlag(opts.target);
9615
- let preferredTools = parsedTarget ? [parsedTarget] : detected;
9616
- if (!opts.target && detected.length === 0) {
9617
- const choice = await promptForTool();
9618
- preferredTools = [choice];
9619
- }
9620
- const registry = registryFromEnvOrDefault(opts.registry);
9621
- await writeProjectPackageJson(configRoot, {
9622
- schemaVersion: "0.1",
9623
- registry,
9624
- preferredTools: preferredTools.length ? preferredTools : void 0,
9625
- packages: {},
9626
- prompts: {}
10519
+ await createProjectPackageJson({
10520
+ scope,
10521
+ registry: registryFromEnvOrDefault(opts.registry),
10522
+ target: opts.target
9627
10523
  });
9628
- console.log(`Created aipm.package.json (${scopeLabel(scope)}, registry: ${registry})`);
9629
10524
  });
9630
10525
  program2.command("login").description("Sign in and store a local CLI session for private installs").option("--registry <url>", "Registry API base URL").option("--site <url>", "Website base URL", SITE_URL).option("--no-open", "Print the URL without opening a browser").action(async (opts) => {
9631
10526
  const registry = registryFromEnvOrDefault(opts.registry);
@@ -9680,9 +10575,9 @@ program2.command("config").description("Show resolved AIPM CLI and project confi
9680
10575
  scope: scopeLabel(scope),
9681
10576
  configRoot,
9682
10577
  installRoot,
9683
- cwd: (0, import_node_process5.cwd)(),
10578
+ cwd: (0, import_node_process6.cwd)(),
9684
10579
  registry,
9685
- envRegistry: import_node_process5.env.AIPM_REGISTRY_URL ?? import_node_process5.env.AIPM_REGISTRY ?? null,
10580
+ envRegistry: import_node_process6.env.AIPM_REGISTRY_URL ?? import_node_process6.env.AIPM_REGISTRY ?? null,
9686
10581
  hasProjectConfig: Boolean(project),
9687
10582
  projectPackages: project ? Object.keys(project.packages) : [],
9688
10583
  hasLockfile: Boolean(lock),
@@ -9711,11 +10606,58 @@ program2.command("config").description("Show resolved AIPM CLI and project confi
9711
10606
  console.log(`Auth file: ${data.authFile}`);
9712
10607
  console.log(`Publish guide: ${data.publishDoc}`);
9713
10608
  });
9714
- program2.command("add <package-or-prompt>").description("Add a skill package or an AIPM prompt URL").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--target <tool>", "cursor, claude, codex, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive; fail if prompt needed").action(async (pkgArg, opts) => {
10609
+ program2.command("add <package-or-prompt>").description("Add a skill package or an AIPM prompt URL").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--target <tool>", "cursor, claude, codex, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive; fail if prompt needed").option("--no-init", "Install without creating or updating aipm.package.json / aipm-lock.json").action(async (pkgArg, opts) => {
9715
10610
  const scope = { global: opts.global };
9716
10611
  const configRoot = resolveConfigRoot(scope);
9717
10612
  const installRoot = resolveInstallRoot(scope);
9718
10613
  let project = await readProjectPackageJson(configRoot);
10614
+ let useNoInit = await resolveNoInitMode({
10615
+ requested: opts.init === false,
10616
+ projectExists: Boolean(project),
10617
+ ci: opts.ci
10618
+ });
10619
+ if (!useNoInit && !project) {
10620
+ const ensured = await ensureProjectOrOffer({
10621
+ scope,
10622
+ project,
10623
+ ci: opts.ci,
10624
+ registry: resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY),
10625
+ target: opts.target,
10626
+ allowNoInit: true,
10627
+ command: "add"
10628
+ });
10629
+ if (ensured.action === "no-init") useNoInit = true;
10630
+ else project = ensured.project;
10631
+ }
10632
+ if (useNoInit) {
10633
+ const promptReference2 = parsePromptUrl(pkgArg);
10634
+ if (promptReference2) {
10635
+ await resolvePromptRequiresTracking({ ci: opts.ci, command: "add" });
10636
+ useNoInit = false;
10637
+ if (!project) {
10638
+ project = await createProjectPackageJson({
10639
+ scope,
10640
+ registry: resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY),
10641
+ target: opts.target
10642
+ });
10643
+ }
10644
+ } else {
10645
+ const registry2 = resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY);
10646
+ await addUntrackedPackage({
10647
+ configRoot,
10648
+ installRoot,
10649
+ registry: registry2,
10650
+ pkgArg,
10651
+ target: opts.target,
10652
+ token: opts.token,
10653
+ ci: opts.ci
10654
+ });
10655
+ if (!opts.ci && !program2.opts().quiet) {
10656
+ await notifyCliUpdateIfNeeded(CLI_VERSION);
10657
+ }
10658
+ return;
10659
+ }
10660
+ }
9719
10661
  if (!project) throw new Error(initRequiredMessage(scope));
9720
10662
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9721
10663
  const promptReference = parsePromptUrl(pkgArg);
@@ -9726,7 +10668,8 @@ program2.command("add <package-or-prompt>").description("Add a skill package or
9726
10668
  project,
9727
10669
  reference: promptReference,
9728
10670
  track: true,
9729
- recordCopy: true
10671
+ recordCopy: true,
10672
+ ci: opts.ci
9730
10673
  });
9731
10674
  console.log(`Installed prompt ${result.prompt.title} \u2192 ${result.path}`);
9732
10675
  return;
@@ -9734,13 +10677,13 @@ program2.command("add <package-or-prompt>").description("Add a skill package or
9734
10677
  const { name, version: requestedVersion } = parsePackageArg(pkgArg);
9735
10678
  const token = await tokenForRead(registry, opts.token);
9736
10679
  const version = requestedVersion ?? project.packages[name] ?? await latestVersionForPackage(registry, name, token);
9737
- if (!version) throw new Error("Specify version: aipm add @scope/pkg@1.0.0");
10680
+ if (!version) throw new Error(`Specify version: ${recommendCmd("aipm add <@scope/pkg>@<version>")}`);
9738
10681
  project = {
9739
10682
  ...project,
9740
10683
  packages: { ...project.packages, [name]: version.replace(/^\^/, "") }
9741
10684
  };
9742
10685
  await writeProjectPackageJson(configRoot, project);
9743
- await installOnePackage({
10686
+ await installOnePackageWithRecovery({
9744
10687
  configRoot,
9745
10688
  installRoot,
9746
10689
  registry,
@@ -9768,7 +10711,7 @@ program2.command("search [query]").description("Search public registry packages"
9768
10711
  const token = await tokenForRead(registry, opts.token, { quiet: opts.json });
9769
10712
  const packages = await searchPackages(registry, query, Number(opts.limit), token);
9770
10713
  if (opts.json) {
9771
- console.log(JSON.stringify({ packages }, null, 2));
10714
+ console.log(JSON.stringify({ skills: packages }, null, 2));
9772
10715
  return;
9773
10716
  }
9774
10717
  if (packages.length === 0) {
@@ -9787,13 +10730,31 @@ program2.command("install").description("Install all packages from aipm.package.
9787
10730
  const scope = { global: opts.global };
9788
10731
  const configRoot = resolveConfigRoot(scope);
9789
10732
  const installRoot = resolveInstallRoot(scope);
9790
- const project = await readProjectPackageJson(configRoot);
9791
- if (!project) throw new Error(initRequiredMessage(scope));
10733
+ let project = await readProjectPackageJson(configRoot);
10734
+ if (!project) {
10735
+ const ensured = await ensureProjectOrOffer({
10736
+ scope,
10737
+ project,
10738
+ ci: opts.ci,
10739
+ registry: resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY),
10740
+ target: opts.target,
10741
+ allowNoInit: false,
10742
+ command: "install"
10743
+ });
10744
+ if (ensured.action !== "ready") throw new Error(initRequiredMessage(scope));
10745
+ project = ensured.project;
10746
+ printNote({
10747
+ type: "info",
10748
+ title: "Initialized",
10749
+ message: "aipm.package.json is empty \u2014 add packages with aipm add, then run aipm install."
10750
+ });
10751
+ return;
10752
+ }
9792
10753
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9793
10754
  const target = parseTargetFlag(opts.target);
9794
10755
  const token = await tokenForRead(registry, opts.token);
9795
10756
  for (const [name, version] of Object.entries(project.packages)) {
9796
- await installOnePackage({
10757
+ await installOnePackageWithRecovery({
9797
10758
  configRoot,
9798
10759
  installRoot,
9799
10760
  registry,
@@ -9813,20 +10774,33 @@ program2.command("install").description("Install all packages from aipm.package.
9813
10774
  registry,
9814
10775
  project,
9815
10776
  reference: { ...reference, alias },
9816
- track: false
10777
+ track: false,
10778
+ ci: opts.ci
9817
10779
  });
9818
10780
  console.log(`Restored prompt ${result.prompt.title} \u2192 ${result.path}`);
9819
10781
  }
9820
10782
  });
9821
10783
  program2.command("show <package-or-prompt>").description("Show an installed prompt snapshot or tracked skill details").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).action(async (value, opts) => {
9822
- const configRoot = resolveConfigRoot({ global: opts.global });
9823
- const project = await readProjectPackageJson(configRoot);
9824
- if (!project) throw new Error(initRequiredMessage({ global: opts.global }));
10784
+ const scope = { global: opts.global };
10785
+ const configRoot = resolveConfigRoot(scope);
10786
+ let project = await readProjectPackageJson(configRoot);
10787
+ if (!project) {
10788
+ const ensured = await ensureProjectOrOffer({
10789
+ scope,
10790
+ project,
10791
+ ci: opts.ci,
10792
+ registry: DEFAULT_REGISTRY,
10793
+ allowNoInit: false,
10794
+ command: "show"
10795
+ });
10796
+ if (ensured.action !== "ready") throw new Error(initRequiredMessage(scope));
10797
+ project = ensured.project;
10798
+ }
9825
10799
  const reference = resolveTrackedPrompt(project, value);
9826
10800
  if (reference && project.prompts[reference.alias] === reference.url) {
9827
10801
  const lock2 = await readLockfile(configRoot);
9828
10802
  const entry2 = lock2?.prompts[reference.alias];
9829
- if (!entry2) throw new Error(`Prompt is not installed: ${reference.url}. Run aipm install.`);
10803
+ if (!entry2) throw new Error(`Prompt is not installed: ${reference.url}. Run ${recommendCmd("aipm install")}.`);
9830
10804
  console.log(await readInstalledPrompt(configRoot, entry2));
9831
10805
  return;
9832
10806
  }
@@ -9840,7 +10814,7 @@ program2.command("show-prompt <package>").description("Show the manual setup pro
9840
10814
  await showInstalledPrompt(resolveConfigRoot({ global: opts.global }), pkgArg);
9841
10815
  });
9842
10816
  function promptImageContentType(path2) {
9843
- const extension = (0, import_node_path15.extname)(path2).toLowerCase();
10817
+ const extension = (0, import_node_path18.extname)(path2).toLowerCase();
9844
10818
  if (extension === ".png") return "image/png";
9845
10819
  if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
9846
10820
  if (extension === ".webp") return "image/webp";
@@ -9849,10 +10823,18 @@ function promptImageContentType(path2) {
9849
10823
  var promptCommand = program2.command("prompt").description("Publish prompt listings from structured JSON");
9850
10824
  promptCommand.command("publish <file>").description("Publish one prompt or a batch of prompts from JSON").option("--registry <url>", "Registry API base URL").option("--yes", "Confirm public publishing without an extra reminder").action(async (file, opts) => {
9851
10825
  const registry = registryFromEnvOrDefault(opts.registry);
9852
- const accessToken = await authTokenForRegistry(registry, { throwOnFailure: true });
9853
- if (!accessToken) throw new Error("Run aipm login before publishing prompts.");
9854
- const absoluteFile = (0, import_node_path15.resolve)(file);
9855
- const parsed = JSON.parse(await (0, import_promises12.readFile)(absoluteFile, "utf8"));
10826
+ let accessToken = await authTokenForRegistry(registry, { throwOnFailure: false });
10827
+ if (!accessToken) {
10828
+ await resolveLoginRequired({
10829
+ ci: opts.ci,
10830
+ purpose: "Publishing prompts requires an AIPM login."
10831
+ });
10832
+ await runLoopbackLogin({ registry, siteUrl: SITE_URL, open: true });
10833
+ accessToken = await authTokenForRegistry(registry, { throwOnFailure: true });
10834
+ }
10835
+ if (!accessToken) throw new Error(`Run ${recommendCmd("aipm login")} before publishing prompts.`);
10836
+ const absoluteFile = (0, import_node_path18.resolve)(file);
10837
+ const parsed = JSON.parse(await (0, import_promises14.readFile)(absoluteFile, "utf8"));
9856
10838
  const rows = Array.isArray(parsed) ? parsed : "prompts" in parsed ? parsed.prompts : [parsed];
9857
10839
  if (rows.length === 0) throw new Error("Prompt publish file contains no prompts.");
9858
10840
  if (!opts.yes) {
@@ -9863,10 +10845,10 @@ promptCommand.command("publish <file>").description("Publish one prompt or a bat
9863
10845
  const { sampleImagePath, ...input2 } = row;
9864
10846
  let sampleImage;
9865
10847
  if (sampleImagePath) {
9866
- const imagePath = (0, import_node_path15.resolve)((0, import_node_path15.dirname)(absoluteFile), sampleImagePath);
10848
+ const imagePath = (0, import_node_path18.resolve)((0, import_node_path18.dirname)(absoluteFile), sampleImagePath);
9867
10849
  sampleImage = {
9868
- data: await (0, import_promises12.readFile)(imagePath),
9869
- filename: (0, import_node_path15.basename)(imagePath),
10850
+ data: await (0, import_promises14.readFile)(imagePath),
10851
+ filename: (0, import_node_path18.basename)(imagePath),
9870
10852
  contentType: promptImageContentType(imagePath)
9871
10853
  };
9872
10854
  }
@@ -9889,12 +10871,12 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
9889
10871
  publish.help();
9890
10872
  return;
9891
10873
  }
9892
- const abs = (0, import_node_path15.resolve)(dir);
9893
- const manifestRaw = await (0, import_promises12.readFile)((0, import_node_path15.join)(abs, "aipm.manifest.json"), "utf8");
10874
+ const abs = (0, import_node_path18.resolve)(dir);
10875
+ const manifestRaw = await (0, import_promises14.readFile)((0, import_node_path18.join)(abs, "aipm.manifest.json"), "utf8");
9894
10876
  const manifest = PackageManifestSchema.parse(JSON.parse(manifestRaw));
9895
10877
  const tarball = await packDirectory(abs);
9896
10878
  const registry = registryFromEnvOrDefault(opts.registry);
9897
- const result = await publishPackage(registry, manifest.name, tarball, opts.token ?? import_node_process5.env.AIPM_TOKEN);
10879
+ const result = await publishPackage(registry, manifest.name, tarball, opts.token ?? import_node_process6.env.AIPM_TOKEN);
9898
10880
  console.log(`Published ${manifest.name}@${result.version}`);
9899
10881
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
9900
10882
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
@@ -9909,7 +10891,7 @@ publish.command("import <source>").description("Create a package folder from an
9909
10891
  async (source, opts) => initSkill({ ...opts, from: source })
9910
10892
  );
9911
10893
  publish.command("add [files...]").description("Stage files for publishing").action(async (files) => {
9912
- const state = await addPublishFiles((0, import_node_process5.cwd)(), files);
10894
+ const state = await addPublishFiles((0, import_node_process6.cwd)(), files);
9913
10895
  console.log(`Staged ${state.files.length} file${state.files.length === 1 ? "" : "s"}.`);
9914
10896
  printPublishGuide(
9915
10897
  `Added ${files.length > 0 ? files.join(", ") : "."} to the publish stage, respecting .aipmignore and secret-file exclusions.`,
@@ -9940,7 +10922,7 @@ publish.command("token").description("Open the dashboard page used to generate a
9940
10922
  );
9941
10923
  });
9942
10924
  publish.command("remove <files...>").alias("rm").description("Remove files from the publish stage").action(async (files) => {
9943
- const state = await removePublishFiles((0, import_node_process5.cwd)(), files);
10925
+ const state = await removePublishFiles((0, import_node_process6.cwd)(), files);
9944
10926
  console.log(`Staged ${state.files.length} file${state.files.length === 1 ? "" : "s"}.`);
9945
10927
  printPublishGuide(
9946
10928
  `Removed ${files.join(", ")} from the publish stage.`,
@@ -9948,7 +10930,7 @@ publish.command("remove <files...>").alias("rm").description("Remove files from
9948
10930
  );
9949
10931
  });
9950
10932
  publish.command("reset").description("Clear staged publish files").action(async () => {
9951
- await resetPublishState((0, import_node_process5.cwd)());
10933
+ await resetPublishState((0, import_node_process6.cwd)());
9952
10934
  console.log("Cleared publish stage.");
9953
10935
  printPublishGuide(
9954
10936
  "Cleared all staged publish files.",
@@ -9956,7 +10938,7 @@ publish.command("reset").description("Clear staged publish files").action(async
9956
10938
  );
9957
10939
  });
9958
10940
  publish.command("status").description("Show staged publish files").option("--json", "Print machine-readable JSON").action(async (opts) => {
9959
- const rows = await statusPublishState((0, import_node_process5.cwd)());
10941
+ const rows = await statusPublishState((0, import_node_process6.cwd)());
9960
10942
  if (opts.json) {
9961
10943
  console.log(JSON.stringify({ files: rows }, null, 2));
9962
10944
  return;
@@ -9979,7 +10961,7 @@ publish.command("status").description("Show staged publish files").option("--jso
9979
10961
  );
9980
10962
  });
9981
10963
  publish.command("diff").description("Show staged files that changed since add").action(async () => {
9982
- const rows = await statusPublishState((0, import_node_process5.cwd)());
10964
+ const rows = await statusPublishState((0, import_node_process6.cwd)());
9983
10965
  const changed = rows.filter((row) => row.changed);
9984
10966
  if (changed.length === 0) {
9985
10967
  console.log("No staged files changed.");
@@ -9996,16 +10978,16 @@ publish.command("diff").description("Show staged files that changed since add").
9996
10978
  );
9997
10979
  });
9998
10980
  publish.command("validate").description("Validate staged files before publish").action(async () => {
9999
- const result = await validatePublishState((0, import_node_process5.cwd)());
10981
+ const result = await validatePublishState((0, import_node_process6.cwd)());
10000
10982
  console.log(`Valid ${result.manifest.name}@${result.manifest.version} (${result.size} bytes staged).`);
10001
10983
  printPublishGuide(
10002
10984
  `Validated ${result.manifest.name}@${result.manifest.version}; manifest, entry file, staged hashes, ignored paths, package size, and obvious secrets passed.`,
10003
10985
  "Generate a 5-minute token from the dashboard, then run AIPM_TOKEN=<token> aipm publish push."
10004
10986
  );
10005
10987
  });
10006
- publish.command("preview").description("Preview exactly what will be uploaded").option("--json", "Print machine-readable JSON").action((opts) => printPublishPreview((0, import_node_process5.cwd)(), opts.json));
10988
+ publish.command("preview").description("Preview exactly what will be uploaded").option("--json", "Print machine-readable JSON").action((opts) => printPublishPreview((0, import_node_process6.cwd)(), opts.json));
10007
10989
  publish.command("push").description("Publish staged files").option("--registry <url>", "Registry base URL").option("--token <token>", "Publish token").option("--yes", "Confirm upload without an extra reminder").option("--verbose", "Print extra diagnostic output").action(async (opts) => {
10008
- const root = (0, import_node_process5.cwd)();
10990
+ const root = (0, import_node_process6.cwd)();
10009
10991
  const { manifest } = await validatePublishState(root);
10010
10992
  const registry = registryFromEnvOrDefault(opts.registry);
10011
10993
  const tarball = await packStagedFiles(root);
@@ -10020,7 +11002,7 @@ publish.command("push").description("Publish staged files").option("--registry <
10020
11002
  console.log(`Verbose: package ${manifest.name}@${manifest.version}; tarball ${tarball.length} bytes.`);
10021
11003
  }
10022
11004
  for (const entry of staged) console.log(` ${entry.path}`);
10023
- const result = await publishPackage(registry, manifest.name, tarball, opts.token ?? import_node_process5.env.AIPM_TOKEN);
11005
+ const result = await publishPackage(registry, manifest.name, tarball, opts.token ?? import_node_process6.env.AIPM_TOKEN);
10024
11006
  console.log(`Published ${manifest.name}@${result.version}`);
10025
11007
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
10026
11008
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
@@ -10052,10 +11034,51 @@ program2.command("list").description("List installed skills and prompts from aip
10052
11034
  }
10053
11035
  }
10054
11036
  });
10055
- program2.command("remove <package-or-prompt>").alias("rm").description("Remove a tracked skill or prompt and its installed files").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).action(async (pkgArg, opts) => {
11037
+ program2.command("remove <package-or-prompt>").alias("rm").description("Remove a tracked skill or prompt and its installed files").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--no-init", "Remove by filesystem discovery without reading or writing aipm.package.json / aipm-lock.json").option("--ci", "Non-interactive; fail if --no-init conflicts with an initialized project").action(async (pkgArg, opts) => {
10056
11038
  const scope = { global: opts.global };
10057
11039
  const configRoot = resolveConfigRoot(scope);
10058
- const project = await readProjectPackageJson(configRoot);
11040
+ const installRoot = resolveInstallRoot(scope);
11041
+ let project = await readProjectPackageJson(configRoot);
11042
+ let useNoInit = await resolveNoInitMode({
11043
+ requested: opts.init === false,
11044
+ projectExists: Boolean(project),
11045
+ ci: opts.ci
11046
+ });
11047
+ if (!useNoInit && !project) {
11048
+ const ensured = await ensureProjectOrOffer({
11049
+ scope,
11050
+ project,
11051
+ ci: opts.ci,
11052
+ registry: DEFAULT_REGISTRY,
11053
+ allowNoInit: true,
11054
+ command: "remove"
11055
+ });
11056
+ if (ensured.action === "no-init") useNoInit = true;
11057
+ else project = ensured.project;
11058
+ }
11059
+ if (useNoInit) {
11060
+ if (parsePromptUrl(pkgArg)) {
11061
+ await resolvePromptRequiresTracking({ ci: opts.ci, command: "remove" });
11062
+ useNoInit = false;
11063
+ if (!project) {
11064
+ project = await createProjectPackageJson({
11065
+ scope,
11066
+ registry: DEFAULT_REGISTRY
11067
+ });
11068
+ }
11069
+ } else {
11070
+ const { name: name2 } = parsePackageArg(pkgArg);
11071
+ const removal = await removeUntrackedPackage({
11072
+ installRoot,
11073
+ configRoot,
11074
+ packageName: name2
11075
+ });
11076
+ console.log(
11077
+ `Removed ${removal.removed.length} untracked ${removal.removed.length === 1 ? "path" : "paths"} for ${name2}.`
11078
+ );
11079
+ return;
11080
+ }
11081
+ }
10059
11082
  if (!project) throw new Error(initRequiredMessage(scope));
10060
11083
  const promptReference = resolveTrackedPrompt(project, pkgArg);
10061
11084
  if (promptReference && project.prompts[promptReference.alias] === promptReference.url) {
@@ -10064,23 +11087,96 @@ program2.command("remove <package-or-prompt>").alias("rm").description("Remove a
10064
11087
  return;
10065
11088
  }
10066
11089
  const { name } = parsePackageArg(pkgArg);
11090
+ const lock = await readLockfile(configRoot);
11091
+ const lockEntry = lock?.packages[name];
11092
+ if (lockEntry) {
11093
+ const otherEntries = Object.entries(lock.packages).filter(([packageName]) => packageName !== name).map(([, entry]) => entry);
11094
+ const removal = await removeInstalledPackageFiles({
11095
+ configRoot,
11096
+ installRoot,
11097
+ entry: lockEntry,
11098
+ otherEntries
11099
+ });
11100
+ console.log(`Deleted ${removal.removed} tracked installed ${removal.removed === 1 ? "file" : "files"}.`);
11101
+ if (removal.retainedShared > 0) {
11102
+ console.log(`Retained ${removal.retainedShared} ${removal.retainedShared === 1 ? "file" : "files"} shared with another installed package.`);
11103
+ }
11104
+ }
10067
11105
  const packages = { ...project.packages };
10068
11106
  delete packages[name];
10069
11107
  await writeProjectPackageJson(configRoot, { ...project, packages });
10070
- const lock = await readLockfile(configRoot);
10071
11108
  if (lock) {
10072
11109
  const lockPackages = { ...lock.packages };
10073
11110
  delete lockPackages[name];
10074
11111
  await writeLockfile(configRoot, { ...lock, packages: lockPackages });
10075
11112
  }
10076
11113
  console.log(`Removed ${name} from AIPM ${scopeLabel(scope)} files.`);
10077
- console.log("Adapter-written files are not deleted yet; review your project before committing.");
10078
11114
  });
10079
- program2.command("update [package]").description("Update one installed package, or all installed packages, to the latest registry version").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--target <tool>", "cursor, claude, codex, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive").action(async (pkgArg, opts) => {
11115
+ program2.command("update [package]").description("Update one installed package, or all installed packages, to the latest registry version").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--target <tool>", "cursor, claude, codex, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive").option("--no-init", "Update without reading or writing aipm.package.json / aipm-lock.json (requires a package arg; always latest)").action(async (pkgArg, opts) => {
10080
11116
  const scope = { global: opts.global };
10081
11117
  const configRoot = resolveConfigRoot(scope);
10082
11118
  const installRoot = resolveInstallRoot(scope);
10083
11119
  let project = await readProjectPackageJson(configRoot);
11120
+ let useNoInit = await resolveNoInitMode({
11121
+ requested: opts.init === false,
11122
+ projectExists: Boolean(project),
11123
+ ci: opts.ci
11124
+ });
11125
+ if (!useNoInit && !project) {
11126
+ const ensured = await ensureProjectOrOffer({
11127
+ scope,
11128
+ project,
11129
+ ci: opts.ci,
11130
+ registry: resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY),
11131
+ target: opts.target,
11132
+ allowNoInit: Boolean(pkgArg) && !parsePromptUrl(pkgArg ?? ""),
11133
+ command: "update"
11134
+ });
11135
+ if (ensured.action === "no-init") useNoInit = true;
11136
+ else project = ensured.project;
11137
+ }
11138
+ if (useNoInit) {
11139
+ if (!pkgArg) {
11140
+ throw new Error(
11141
+ `Update with --no-init requires a package: ${recommendCmd("aipm update <@scope/pkg> --no-init")}`
11142
+ );
11143
+ }
11144
+ if (parsePromptUrl(pkgArg)) {
11145
+ await resolvePromptRequiresTracking({ ci: opts.ci, command: "update" });
11146
+ useNoInit = false;
11147
+ if (!project) {
11148
+ project = await createProjectPackageJson({
11149
+ scope,
11150
+ registry: resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY),
11151
+ target: opts.target
11152
+ });
11153
+ }
11154
+ } else {
11155
+ const registry2 = resolveRegistryUrl(null, opts.registry, DEFAULT_REGISTRY);
11156
+ const { name } = parsePackageArg(pkgArg);
11157
+ const token2 = await tokenForRead(registry2, opts.token);
11158
+ const version = await latestVersionForPackage(registry2, name, token2);
11159
+ const syntheticProject = {
11160
+ schemaVersion: "0.1",
11161
+ registry: registry2,
11162
+ packages: {},
11163
+ prompts: {}
11164
+ };
11165
+ await installOnePackageWithRecovery({
11166
+ configRoot,
11167
+ installRoot,
11168
+ registry: registry2,
11169
+ name,
11170
+ version,
11171
+ project: syntheticProject,
11172
+ explicitTarget: parseTargetFlag(opts.target),
11173
+ ci: opts.ci,
11174
+ token: token2,
11175
+ track: false
11176
+ });
11177
+ return;
11178
+ }
11179
+ }
10084
11180
  if (!project) throw new Error(initRequiredMessage(scope));
10085
11181
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
10086
11182
  const requestedPrompt = pkgArg ? resolveTrackedPrompt(project, pkgArg) : null;
@@ -10094,7 +11190,8 @@ program2.command("update [package]").description("Update one installed package,
10094
11190
  project,
10095
11191
  reference: requestedPrompt,
10096
11192
  track: false,
10097
- updateOnly: true
11193
+ updateOnly: true,
11194
+ ci: opts.ci
10098
11195
  });
10099
11196
  console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
10100
11197
  return;
@@ -10108,7 +11205,7 @@ program2.command("update [package]").description("Update one installed package,
10108
11205
  packages: { ...project.packages, [name]: version }
10109
11206
  };
10110
11207
  await writeProjectPackageJson(configRoot, project);
10111
- await installOnePackage({
11208
+ await installOnePackageWithRecovery({
10112
11209
  configRoot,
10113
11210
  installRoot,
10114
11211
  registry,
@@ -10130,7 +11227,8 @@ program2.command("update [package]").description("Update one installed package,
10130
11227
  project,
10131
11228
  reference: { ...reference, alias },
10132
11229
  track: false,
10133
- updateOnly: true
11230
+ updateOnly: true,
11231
+ ci: opts.ci
10134
11232
  });
10135
11233
  console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
10136
11234
  }
@@ -10141,6 +11239,10 @@ program2.command("update [package]").description("Update one installed package,
10141
11239
  });
10142
11240
  program2.command("doctor").description("Check PATH, Node, registry, project config, and publish readiness").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--json", "Print machine-readable JSON").option("--publish", "Only show publish-readiness checks").action((opts) => runDoctor(opts));
10143
11241
  program2.parseAsync(normalizedArgv).catch((err) => {
11242
+ if (err.name === "RecoveryCancelledError" || err.name === "SelectCancelledError") {
11243
+ printNote({ type: "info", title: "Cancelled", message: err.message || "Cancelled." });
11244
+ process.exit(130);
11245
+ }
10144
11246
  console.error(err.message);
10145
11247
  const message = err.message.toLowerCase();
10146
11248
  if (message.includes("token") || message.includes("unauthorized") || message.includes("forbidden")) {