@aipm-registry/cli 0.4.3 → 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
@@ -4319,7 +4319,7 @@ var init_dist = __esm({
4319
4319
  // ../../packages/engine/dist/detect-tools.js
4320
4320
  async function pathExists(path2) {
4321
4321
  try {
4322
- await (0, import_promises.access)(path2);
4322
+ await (0, import_promises4.access)(path2);
4323
4323
  return true;
4324
4324
  } catch {
4325
4325
  return false;
@@ -4327,11 +4327,11 @@ async function pathExists(path2) {
4327
4327
  }
4328
4328
  async function detectToolsInProject(projectRoot) {
4329
4329
  const detected = [];
4330
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".cursor")))
4330
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".cursor")))
4331
4331
  detected.push("cursor");
4332
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".claude")))
4332
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".claude")))
4333
4333
  detected.push("claude");
4334
- if (await pathExists((0, import_node_path2.join)(projectRoot, ".codex")))
4334
+ if (await pathExists((0, import_node_path5.join)(projectRoot, ".codex")))
4335
4335
  detected.push("codex");
4336
4336
  return detected;
4337
4337
  }
@@ -4342,7 +4342,6 @@ function manifestAllowsTool(manifest, tool) {
4342
4342
  }
4343
4343
  async function resolveInstallTools(options) {
4344
4344
  const { manifest } = options;
4345
- const expandedAllowed = expandTargets(manifest.targets);
4346
4345
  if (options.explicitTarget) {
4347
4346
  if (options.explicitTarget === "*") {
4348
4347
  return [...ALL_TOOLS];
@@ -4363,70 +4362,68 @@ async function resolveInstallTools(options) {
4363
4362
  const preferred = expandTargets(options.preferredTools ?? []).filter((t) => manifestAllowsTool(manifest, t));
4364
4363
  if (preferred.length > 0)
4365
4364
  return preferred;
4366
- if (manifest.targets.includes("*"))
4367
- return expandedAllowed;
4368
4365
  return [];
4369
4366
  }
4370
- var import_promises, import_node_path2;
4367
+ var import_promises4, import_node_path5;
4371
4368
  var init_detect_tools = __esm({
4372
4369
  "../../packages/engine/dist/detect-tools.js"() {
4373
4370
  "use strict";
4374
- import_promises = require("node:fs/promises");
4375
- import_node_path2 = require("node:path");
4371
+ import_promises4 = require("node:fs/promises");
4372
+ import_node_path5 = require("node:path");
4376
4373
  init_dist();
4377
4374
  }
4378
4375
  });
4379
4376
 
4380
4377
  // ../../packages/adapter-sdk/dist/index.js
4381
4378
  async function writeSkillDirectory(skillDir, input2) {
4382
- const entryPath = (0, import_node_path3.join)(skillDir, "SKILL.md");
4379
+ const entryPath = (0, import_node_path6.join)(skillDir, "SKILL.md");
4383
4380
  const seen = /* @__PURE__ */ new Set(["skill.md"]);
4384
4381
  const files = (input2.supportingFiles ?? []).map((file) => {
4385
- const normalized = (0, import_node_path3.normalize)(file.path);
4386
- const target = (0, import_node_path3.resolve)(skillDir, normalized);
4387
- const fromSkillDir = (0, import_node_path3.relative)(skillDir, target);
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);
4388
4385
  return { file, normalized, target, fromSkillDir, key: fromSkillDir.toLowerCase() };
4389
4386
  });
4390
4387
  for (const { file, normalized, fromSkillDir, key } of files) {
4391
- if (!normalized || normalized === "." || (0, import_node_path3.isAbsolute)(normalized) || fromSkillDir === ".." || fromSkillDir.startsWith(`..${import_node_path3.sep}`) || (0, import_node_path3.isAbsolute)(fromSkillDir) || seen.has(key)) {
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)) {
4392
4389
  throw new Error(`Unsafe or duplicate skill supporting file path: ${file.path}`);
4393
4390
  }
4394
4391
  seen.add(key);
4395
4392
  }
4396
- await (0, import_promises2.mkdir)(skillDir, { recursive: true });
4397
- await (0, import_promises2.writeFile)(entryPath, input2.skillMarkdown, "utf8");
4393
+ await (0, import_promises5.mkdir)(skillDir, { recursive: true });
4394
+ await (0, import_promises5.writeFile)(entryPath, input2.skillMarkdown, "utf8");
4398
4395
  const writtenPaths = [entryPath];
4399
4396
  for (const { file, target } of files) {
4400
- await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(target), { recursive: true });
4401
- await (0, import_promises2.writeFile)(target, file.content);
4397
+ await (0, import_promises5.mkdir)((0, import_node_path6.dirname)(target), { recursive: true });
4398
+ await (0, import_promises5.writeFile)(target, file.content);
4402
4399
  if (file.mode !== void 0)
4403
- await (0, import_promises2.chmod)(target, file.mode & 511);
4400
+ await (0, import_promises5.chmod)(target, file.mode & 511);
4404
4401
  writtenPaths.push(target);
4405
4402
  }
4406
4403
  return { writtenPaths };
4407
4404
  }
4408
- var import_promises2, import_node_path3;
4405
+ var import_promises5, import_node_path6;
4409
4406
  var init_dist2 = __esm({
4410
4407
  "../../packages/adapter-sdk/dist/index.js"() {
4411
4408
  "use strict";
4412
- import_promises2 = require("node:fs/promises");
4413
- import_node_path3 = require("node:path");
4409
+ import_promises5 = require("node:fs/promises");
4410
+ import_node_path6 = require("node:path");
4414
4411
  }
4415
4412
  });
4416
4413
 
4417
4414
  // ../../packages/adapter-claude/dist/index.js
4418
- var import_node_path4, ClaudeSkillAdapter, claudeSkillAdapter;
4415
+ var import_node_path7, ClaudeSkillAdapter, claudeSkillAdapter;
4419
4416
  var init_dist3 = __esm({
4420
4417
  "../../packages/adapter-claude/dist/index.js"() {
4421
4418
  "use strict";
4422
- import_node_path4 = require("node:path");
4419
+ import_node_path7 = require("node:path");
4423
4420
  init_dist2();
4424
4421
  init_dist();
4425
4422
  ClaudeSkillAdapter = class {
4426
4423
  tool = "claude";
4427
4424
  async installSkill(input2) {
4428
4425
  const short = shortNameFromScopeName(input2.packageName);
4429
- const skillDir = (0, import_node_path4.join)(input2.projectRoot, ".claude", "skills", short);
4426
+ const skillDir = (0, import_node_path7.join)(input2.projectRoot, ".claude", "skills", short);
4430
4427
  return writeSkillDirectory(skillDir, input2);
4431
4428
  }
4432
4429
  };
@@ -4435,21 +4432,21 @@ var init_dist3 = __esm({
4435
4432
  });
4436
4433
 
4437
4434
  // ../../packages/adapter-cursor/dist/index.js
4438
- var import_promises3, import_node_path5, CursorSkillAdapter, cursorSkillAdapter;
4435
+ var import_promises6, import_node_path8, CursorSkillAdapter, cursorSkillAdapter;
4439
4436
  var init_dist4 = __esm({
4440
4437
  "../../packages/adapter-cursor/dist/index.js"() {
4441
4438
  "use strict";
4442
- import_promises3 = require("node:fs/promises");
4443
- import_node_path5 = require("node:path");
4439
+ import_promises6 = require("node:fs/promises");
4440
+ import_node_path8 = require("node:path");
4444
4441
  init_dist();
4445
4442
  CursorSkillAdapter = class {
4446
4443
  tool = "cursor";
4447
4444
  async installSkill(input2) {
4448
4445
  const short = shortNameFromScopeName(input2.packageName);
4449
- const skillsDir = (0, import_node_path5.join)(input2.projectRoot, ".cursor", "aipm", "skills");
4450
- await (0, import_promises3.mkdir)(skillsDir, { recursive: true });
4451
- const filePath = (0, import_node_path5.join)(skillsDir, `${short}.md`);
4452
- 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");
4453
4450
  return { writtenPaths: [filePath] };
4454
4451
  }
4455
4452
  };
@@ -4458,18 +4455,18 @@ var init_dist4 = __esm({
4458
4455
  });
4459
4456
 
4460
4457
  // ../../packages/adapter-codex/dist/index.js
4461
- var import_node_path6, CodexSkillAdapter, codexSkillAdapter;
4458
+ var import_node_path9, CodexSkillAdapter, codexSkillAdapter;
4462
4459
  var init_dist5 = __esm({
4463
4460
  "../../packages/adapter-codex/dist/index.js"() {
4464
4461
  "use strict";
4465
- import_node_path6 = require("node:path");
4462
+ import_node_path9 = require("node:path");
4466
4463
  init_dist2();
4467
4464
  init_dist();
4468
4465
  CodexSkillAdapter = class {
4469
4466
  tool = "codex";
4470
4467
  async installSkill(input2) {
4471
4468
  const short = shortNameFromScopeName(input2.packageName);
4472
- const skillDir = (0, import_node_path6.join)(input2.projectRoot, ".agents", "skills", short);
4469
+ const skillDir = (0, import_node_path9.join)(input2.projectRoot, ".agents", "skills", short);
4473
4470
  return writeSkillDirectory(skillDir, input2);
4474
4471
  }
4475
4472
  };
@@ -7905,34 +7902,107 @@ var program = new Command();
7905
7902
  var import_node_crypto3 = require("node:crypto");
7906
7903
  var import_node_child_process4 = require("node:child_process");
7907
7904
  var import_node_http = require("node:http");
7908
- var import_promises12 = require("node:fs/promises");
7909
- var import_node_path17 = require("node:path");
7910
- 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");
7911
7908
  init_dist();
7912
- init_dist6();
7913
7909
 
7914
7910
  // src/pack.ts
7915
7911
  var import_node_child_process2 = require("node:child_process");
7916
7912
  var import_node_util3 = require("node:util");
7917
- var import_promises5 = require("node:fs/promises");
7918
- var import_node_path8 = require("node:path");
7913
+ var import_promises2 = require("node:fs/promises");
7914
+ var import_node_path3 = require("node:path");
7919
7915
  var import_node_os = require("node:os");
7920
7916
 
7921
7917
  // src/publish-state.ts
7922
7918
  var import_node_crypto = require("node:crypto");
7923
- var import_promises4 = require("node:fs/promises");
7924
- var import_node_path7 = require("node:path");
7919
+ var import_promises = require("node:fs/promises");
7920
+ var import_node_path2 = require("node:path");
7925
7921
  init_dist();
7926
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
+
7927
7996
  // src/recommend-cmd.ts
7928
- function recommendCmd(command, stream = process.stderr) {
7997
+ function recommendCmd(command, stream = process.stderr, env2) {
7929
7998
  const quoted = `"${command}"`;
7930
- if (!stream.isTTY) return quoted;
7931
- return `\x1B[36m${quoted}\x1B[39m`;
7999
+ const theme = createTheme(stream, env2);
8000
+ if (!theme.color) return quoted;
8001
+ return `${theme.accent}${quoted}${theme.reset}`;
7932
8002
  }
7933
8003
 
7934
8004
  // src/publish-state.ts
7935
- var STATE_PATH = (0, import_node_path7.join)(".aipm", "publish-state.json");
8005
+ var STATE_PATH = (0, import_node_path2.join)(".aipm", "publish-state.json");
7936
8006
  var IGNORE_FILE = ".aipmignore";
7937
8007
  var MAX_PACKAGE_BYTES = 50 * 1024 * 1024;
7938
8008
  var EXCLUDED_SEGMENTS = /* @__PURE__ */ new Set([".aipm", ".git", "node_modules", "dist", ".next"]);
@@ -7947,11 +8017,11 @@ var SECRET_PATTERNS = [
7947
8017
  /<publishData/i
7948
8018
  ];
7949
8019
  function publishStatePath(root) {
7950
- return (0, import_node_path7.join)(root, STATE_PATH);
8020
+ return (0, import_node_path2.join)(root, STATE_PATH);
7951
8021
  }
7952
8022
  function normalizePublishPath(root, filePath) {
7953
- const abs = (0, import_node_path7.resolve)(root, filePath);
7954
- const rel = (0, import_node_path7.normalize)((0, import_node_path7.relative)(root, abs)).split(import_node_path7.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("/");
7955
8025
  if (!rel || rel === ".") return ".";
7956
8026
  if (rel.startsWith("../") || rel === ".." || rel.startsWith("/")) {
7957
8027
  throw new Error(`Path is outside the skill folder: ${filePath}`);
@@ -7968,7 +8038,7 @@ function isExcludedPublishPath(rel) {
7968
8038
  }
7969
8039
  async function readAipmIgnore(root) {
7970
8040
  try {
7971
- const raw = await (0, import_promises4.readFile)((0, import_node_path7.join)(root, IGNORE_FILE), "utf8");
8041
+ const raw = await (0, import_promises.readFile)((0, import_node_path2.join)(root, IGNORE_FILE), "utf8");
7972
8042
  return raw.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
7973
8043
  } catch {
7974
8044
  return [];
@@ -7989,7 +8059,7 @@ async function isIgnoredByAipmIgnore(root, rel) {
7989
8059
  return rules.some((rule) => matchesIgnoreRule(rel, rule));
7990
8060
  }
7991
8061
  async function assertNoObviousSecrets(path2, rel) {
7992
- const data = await (0, import_promises4.readFile)(path2);
8062
+ const data = await (0, import_promises.readFile)(path2);
7993
8063
  if (data.includes(0)) return;
7994
8064
  const text = data.toString("utf8");
7995
8065
  if (SECRET_PATTERNS.some((pattern) => pattern.test(text))) {
@@ -7997,7 +8067,7 @@ async function assertNoObviousSecrets(path2, rel) {
7997
8067
  }
7998
8068
  }
7999
8069
  async function fileHash(path2) {
8000
- const data = await (0, import_promises4.readFile)(path2);
8070
+ const data = await (0, import_promises.readFile)(path2);
8001
8071
  return {
8002
8072
  hash: (0, import_node_crypto.createHash)("sha256").update(data).digest("hex"),
8003
8073
  size: data.length
@@ -8005,7 +8075,7 @@ async function fileHash(path2) {
8005
8075
  }
8006
8076
  async function readPublishState(root) {
8007
8077
  try {
8008
- const raw = await (0, import_promises4.readFile)(publishStatePath(root), "utf8");
8078
+ const raw = await (0, import_promises.readFile)(publishStatePath(root), "utf8");
8009
8079
  const parsed = JSON.parse(raw);
8010
8080
  return {
8011
8081
  schemaVersion: "0.1",
@@ -8017,18 +8087,18 @@ async function readPublishState(root) {
8017
8087
  }
8018
8088
  async function writePublishState(root, state) {
8019
8089
  const path2 = publishStatePath(root);
8020
- await (0, import_promises4.mkdir)((0, import_node_path7.dirname)(path2), { recursive: true });
8090
+ await (0, import_promises.mkdir)((0, import_node_path2.dirname)(path2), { recursive: true });
8021
8091
  const files = [...state.files].sort((a, b) => a.path.localeCompare(b.path));
8022
- await (0, import_promises4.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");
8023
8093
  }
8024
8094
  async function expandPublishPath(root, rel) {
8025
8095
  if (isExcludedPublishPath(rel)) return [];
8026
8096
  if (await isIgnoredByAipmIgnore(root, rel)) return [];
8027
- const abs = (0, import_node_path7.join)(root, rel);
8028
- const info = await (0, import_promises4.stat)(abs);
8097
+ const abs = (0, import_node_path2.join)(root, rel);
8098
+ const info = await (0, import_promises.stat)(abs);
8029
8099
  if (info.isFile()) return [rel];
8030
8100
  if (!info.isDirectory()) return [];
8031
- const entries = await (0, import_promises4.readdir)(abs);
8101
+ const entries = await (0, import_promises.readdir)(abs);
8032
8102
  const nested = await Promise.all(
8033
8103
  entries.map((entry) => expandPublishPath(root, `${rel === "." ? "" : `${rel}/`}${entry}`))
8034
8104
  );
@@ -8044,7 +8114,7 @@ async function addPublishFiles(root, paths) {
8044
8114
  for (const rel of files) {
8045
8115
  if (isExcludedPublishPath(rel)) continue;
8046
8116
  if (await isIgnoredByAipmIgnore(root, rel)) continue;
8047
- const hashed = await fileHash((0, import_node_path7.join)(root, rel));
8117
+ const hashed = await fileHash((0, import_node_path2.join)(root, rel));
8048
8118
  byPath.set(rel, { path: rel, ...hashed });
8049
8119
  }
8050
8120
  }
@@ -8063,11 +8133,11 @@ async function removePublishFiles(root, paths) {
8063
8133
  return next;
8064
8134
  }
8065
8135
  async function resetPublishState(root) {
8066
- await (0, import_promises4.rm)(publishStatePath(root), { force: true });
8136
+ await (0, import_promises.rm)(publishStatePath(root), { force: true });
8067
8137
  }
8068
8138
  async function readManifest(root) {
8069
8139
  try {
8070
- const raw = await (0, import_promises4.readFile)((0, import_node_path7.join)(root, "aipm.manifest.json"), "utf8");
8140
+ const raw = await (0, import_promises.readFile)((0, import_node_path2.join)(root, "aipm.manifest.json"), "utf8");
8071
8141
  return PackageManifestSchema.parse(JSON.parse(raw));
8072
8142
  } catch (error) {
8073
8143
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -8104,8 +8174,8 @@ async function validatePublishState(root) {
8104
8174
  if (await isIgnoredByAipmIgnore(root, entry.path)) {
8105
8175
  throw new Error(`Refusing to publish ignored path: ${entry.path}`);
8106
8176
  }
8107
- const abs = (0, import_node_path7.join)(root, entry.path);
8108
- const info = await (0, import_promises4.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);
8109
8179
  if (!info?.isFile()) throw new Error(`Staged file is missing: ${entry.path}`);
8110
8180
  const hashed = await fileHash(abs);
8111
8181
  if (hashed.hash !== entry.hash) {
@@ -8123,7 +8193,7 @@ async function statusPublishState(root) {
8123
8193
  const state = await readPublishState(root);
8124
8194
  const rows = [];
8125
8195
  for (const entry of state.files) {
8126
- const current = await fileHash((0, import_node_path7.join)(root, entry.path)).catch(() => null);
8196
+ const current = await fileHash((0, import_node_path2.join)(root, entry.path)).catch(() => null);
8127
8197
  rows.push({ ...entry, changed: !current || current.hash !== entry.hash });
8128
8198
  }
8129
8199
  return rows;
@@ -8131,9 +8201,9 @@ async function statusPublishState(root) {
8131
8201
  async function copyStagedFiles(root, destination) {
8132
8202
  const state = await readPublishState(root);
8133
8203
  for (const entry of state.files) {
8134
- const target = (0, import_node_path7.join)(destination, entry.path);
8135
- await (0, import_promises4.mkdir)((0, import_node_path7.dirname)(target), { recursive: true });
8136
- await (0, import_promises4.cp)((0, import_node_path7.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);
8137
8207
  }
8138
8208
  }
8139
8209
 
@@ -8147,20 +8217,20 @@ async function packDirectory(dir) {
8147
8217
  return Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout);
8148
8218
  }
8149
8219
  async function packStagedFiles(root) {
8150
- const tempDir = await (0, import_promises5.mkdtemp)((0, import_node_path8.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-"));
8151
8221
  try {
8152
8222
  await copyStagedFiles(root, tempDir);
8153
8223
  return await packDirectory(tempDir);
8154
8224
  } finally {
8155
- await (0, import_promises5.rm)(tempDir, { recursive: true, force: true });
8225
+ await (0, import_promises2.rm)(tempDir, { recursive: true, force: true });
8156
8226
  }
8157
8227
  }
8158
8228
  async function unpackTarballToDirectory(tarball) {
8159
8229
  const { mkdtemp: mkdtemp2, writeFile: writeFile8 } = await import("node:fs/promises");
8160
- const { join: join16 } = await import("node:path");
8230
+ const { join: join17 } = await import("node:path");
8161
8231
  const { tmpdir: tmpdir2 } = await import("node:os");
8162
- const tempDir = await mkdtemp2(join16(tmpdir2(), "aipm-install-"));
8163
- const tgzPath = join16(tempDir, "pkg.tgz");
8232
+ const tempDir = await mkdtemp2(join17(tmpdir2(), "aipm-install-"));
8233
+ const tgzPath = join17(tempDir, "pkg.tgz");
8164
8234
  await writeFile8(tgzPath, tarball);
8165
8235
  await execFileAsync("tar", ["-xzf", tgzPath, "-C", tempDir]);
8166
8236
  return tempDir;
@@ -8222,7 +8292,7 @@ async function assertRegistryReachable(registry) {
8222
8292
  }
8223
8293
  async function publishPackage(registry, name, tarball, token) {
8224
8294
  const base = registry.replace(/\/$/, "");
8225
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions`;
8295
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions`;
8226
8296
  const form = new FormData();
8227
8297
  form.append("tarball", new Blob([tarball]), "package.tgz");
8228
8298
  const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
@@ -8239,12 +8309,12 @@ async function searchPackages(registry, query, limit = 20, token) {
8239
8309
  if (query) params.set("q", query);
8240
8310
  params.set("limit", String(limit));
8241
8311
  if (token) params.set("includePrivate", "true");
8242
- const res = await registryFetch(`${base}/v1/packages?${params}`, base, {
8312
+ const res = await registryFetch(`${base}/v1/skills?${params}`, base, {
8243
8313
  headers: registryAuthHeaders(token)
8244
8314
  });
8245
8315
  if (!res.ok) throw new Error(`Search failed: ${res.status}`);
8246
8316
  const data = await res.json();
8247
- return data.packages ?? [];
8317
+ return data.skills ?? data.packages ?? [];
8248
8318
  }
8249
8319
  async function exchangeCliAuthCode(registry, input2) {
8250
8320
  const base = registry.replace(/\/$/, "");
@@ -8293,7 +8363,7 @@ async function logoutCliAuth(registry, refreshToken) {
8293
8363
  }
8294
8364
  async function fetchPackageMetadata(registry, name, version, token) {
8295
8365
  const base = registry.replace(/\/$/, "");
8296
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions/${version}`;
8366
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions/${version}`;
8297
8367
  const res = await registryFetch(url, base, { headers: registryAuthHeaders(token) });
8298
8368
  if (!res.ok) throw new Error(privateInstallHint(`${name}@${version}`, res.status, token));
8299
8369
  const data = await res.json();
@@ -8301,7 +8371,7 @@ async function fetchPackageMetadata(registry, name, version, token) {
8301
8371
  }
8302
8372
  async function fetchPackageTarball(registry, name, version, token) {
8303
8373
  const base = registry.replace(/\/$/, "");
8304
- const url = `${base}/v1/packages/${encodePackageName(name)}/versions/${version}/tarball`;
8374
+ const url = `${base}/v1/skills/${encodePackageName(name)}/versions/${version}/tarball`;
8305
8375
  const res = await registryFetch(url, base, { headers: registryAuthHeaders(token) });
8306
8376
  if (!res.ok) throw new Error(privateInstallHint(`${name}@${version}`, res.status, token));
8307
8377
  const ab = await res.arrayBuffer();
@@ -8309,7 +8379,7 @@ async function fetchPackageTarball(registry, name, version, token) {
8309
8379
  }
8310
8380
  async function recordPackageInstall(registry, name, token) {
8311
8381
  const base = registry.replace(/\/$/, "");
8312
- const url = `${base}/v1/packages/${encodePackageName(name)}/installs`;
8382
+ const url = `${base}/v1/skills/${encodePackageName(name)}/installs`;
8313
8383
  const res = await registryFetch(url, base, {
8314
8384
  method: "POST",
8315
8385
  headers: registryAuthHeaders(token)
@@ -8357,16 +8427,16 @@ async function publishPrompt(registry, input2, accessToken, sampleImage) {
8357
8427
  }
8358
8428
 
8359
8429
  // src/auth-store.ts
8360
- var import_promises6 = require("node:fs/promises");
8430
+ var import_promises3 = require("node:fs/promises");
8361
8431
  var import_node_os2 = require("node:os");
8362
- var import_node_path9 = require("node:path");
8363
- var AUTH_FILE = (0, import_node_path9.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");
8364
8434
  function normalizeRegistry(registry) {
8365
8435
  return registry.replace(/\/$/, "");
8366
8436
  }
8367
8437
  async function readAuthStore() {
8368
8438
  try {
8369
- const raw = await (0, import_promises6.readFile)(AUTH_FILE, "utf8");
8439
+ const raw = await (0, import_promises3.readFile)(AUTH_FILE, "utf8");
8370
8440
  const parsed = JSON.parse(raw);
8371
8441
  return { registries: parsed.registries ?? {} };
8372
8442
  } catch {
@@ -8374,9 +8444,9 @@ async function readAuthStore() {
8374
8444
  }
8375
8445
  }
8376
8446
  async function writeAuthStore(store) {
8377
- await (0, import_promises6.mkdir)((0, import_node_path9.dirname)(AUTH_FILE), { recursive: true });
8378
- await (0, import_promises6.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8379
- await (0, import_promises6.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);
8380
8450
  }
8381
8451
  async function getStoredRegistryAuth(registry) {
8382
8452
  const store = await readAuthStore();
@@ -8481,37 +8551,389 @@ function parseTargetsFlag(value) {
8481
8551
  return unique;
8482
8552
  }
8483
8553
 
8484
- // src/prompt.ts
8485
- var readline = __toESM(require("node:readline/promises"), 1);
8554
+ // src/ui/select.ts
8486
8555
  var import_node_process2 = require("node:process");
8487
- async function promptForTool() {
8488
- 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();
8489
8694
  try {
8490
- while (true) {
8491
- const answer = await rl.question(
8492
- "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
+ `
8493
8840
  );
8494
- const normalized = answer.trim().toLowerCase();
8495
- if (normalized === "cursor" || normalized === "claude" || normalized === "codex") {
8496
- return normalized;
8841
+ },
8842
+ fail(finalText = text) {
8843
+ stop();
8844
+ if (disabled) {
8845
+ stream.write(`${finalText}
8846
+ `);
8847
+ return;
8497
8848
  }
8498
- 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;
8499
8864
  }
8500
- } finally {
8501
- rl.close();
8502
8865
  }
8503
8866
  }
8504
- async function promptForConfirmation(question) {
8505
- 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
+ }
8506
8891
  try {
8507
- const answer = await rl.question(`${question} (y/N): `);
8508
- const normalized = answer.trim().toLowerCase();
8509
- return normalized === "y" || normalized === "yes";
8510
- } finally {
8511
- 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;
8512
8901
  }
8513
8902
  }
8514
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
+
8515
8937
  // src/install-one.ts
8516
8938
  function normalizeRelativePath(path2) {
8517
8939
  return (0, import_node_path11.normalize)(path2).split(import_node_path11.sep).join("/");
@@ -8598,11 +9020,27 @@ async function collectSkillSupportingFiles(packageRoot, manifest) {
8598
9020
  async function copyMainFile(input2) {
8599
9021
  const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8600
9022
  const target = safeJoin(input2.installRoot, input2.file.to);
8601
- const overwrite = input2.file.overwrite ?? "fail";
9023
+ let overwrite = input2.file.overwrite ?? "fail";
8602
9024
  const exists = await (0, import_promises8.stat)(target).catch(() => null);
8603
9025
  if (exists) {
8604
9026
  if (overwrite === "skip") return null;
8605
- 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
+ }
8606
9044
  }
8607
9045
  await (0, import_promises8.mkdir)((0, import_node_path11.dirname)(target), { recursive: true });
8608
9046
  await (0, import_promises8.cp)(source, target, { force: overwrite === "replace" });
@@ -8623,7 +9061,8 @@ async function installPackageAssets(options) {
8623
9061
  const copied = await copyMainFile({
8624
9062
  packageRoot: options.packageRoot,
8625
9063
  installRoot: options.installRoot,
8626
- file
9064
+ file,
9065
+ ci: options.ci
8627
9066
  });
8628
9067
  if (copied) assets.main.push(copied);
8629
9068
  }
@@ -8660,95 +9099,135 @@ function printPostInstallNotice(name, postInstall) {
8660
9099
  async function installOnePackage(options) {
8661
9100
  const configRoot = options.configRoot;
8662
9101
  const installRoot = options.installRoot ?? configRoot;
8663
- await assertRegistryReachable(options.registry);
8664
- const { manifest, integrity: remoteIntegrity, deprecated } = await fetchPackageMetadata(
8665
- options.registry,
8666
- options.name,
8667
- options.version,
8668
- options.token
8669
- );
8670
- if (deprecated) {
8671
- console.warn(
8672
- `Warning: ${options.name} is deprecated${deprecated.message ? `: ${deprecated.message}` : ""}.`
8673
- );
8674
- }
8675
- let explicitTarget = options.explicitTarget;
8676
- let preferredTools = options.project.preferredTools;
8677
- const { resolveInstallTools: resolveInstallTools2 } = await Promise.resolve().then(() => (init_dist6(), dist_exports));
8678
- let tools = await resolveInstallTools2({
8679
- projectRoot: installRoot,
8680
- manifest,
8681
- preferredTools,
8682
- explicitTarget
9102
+ const spinner = createSpinner({
9103
+ text: `Fetching ${options.name}@${options.version}`,
9104
+ disabled: options.ci
8683
9105
  });
8684
- if (tools.length === 0) {
8685
- if (options.ci) {
8686
- throw new Error(
8687
- "No tool detected. Use --target cursor|claude|codex|* in CI mode."
8688
- );
8689
- }
8690
- const choice = await promptForTool();
8691
- explicitTarget = choice;
8692
- preferredTools = [choice];
8693
- tools = await resolveInstallTools2({
8694
- projectRoot: installRoot,
8695
- manifest,
8696
- preferredTools,
8697
- explicitTarget
8698
- });
8699
- }
8700
- const tarball = await fetchPackageTarball(
8701
- options.registry,
8702
- options.name,
8703
- options.version,
8704
- options.token
8705
- );
8706
- const packageRoot = await unpackTarballToDirectory(tarball);
9106
+ spinner.start();
8707
9107
  try {
8708
- const skillMarkdown = await (0, import_promises8.readFile)(safeJoin(packageRoot, manifest.entry), "utf8");
8709
- const supportingFiles = await collectSkillSupportingFiles(packageRoot, manifest);
8710
- 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({
8711
9128
  projectRoot: installRoot,
8712
9129
  manifest,
8713
- skillMarkdown,
8714
- supportingFiles,
8715
9130
  preferredTools,
8716
9131
  explicitTarget
8717
9132
  });
8718
- const { assets, postInstall } = await installPackageAssets({
8719
- configRoot,
8720
- installRoot,
8721
- packageRoot,
8722
- packageName: options.name,
8723
- version: options.version,
8724
- install: manifest.install
8725
- });
8726
- const lock = await readLockfile(configRoot) ?? {
8727
- schemaVersion: "0.1",
8728
- packages: {},
8729
- prompts: {}
8730
- };
8731
- const installed = {};
8732
- for (const tool of result.resolvedTools) {
8733
- const paths = result.installed[tool];
8734
- if (paths) installed[tool] = paths;
8735
- }
8736
- await writeLockfile(
8737
- configRoot,
8738
- upsertLockEntry(lock, options.name, {
8739
- version: options.version,
8740
- integrity: remoteIntegrity,
8741
- registry: options.registry,
8742
- resolvedTools: result.resolvedTools,
8743
- installed,
8744
- ...assets.main.length || assets.helper.length ? { installedAssets: assets } : {},
8745
- ...postInstall ? { postInstall } : {}
8746
- })
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
8747
9174
  );
8748
- console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8749
- printPostInstallNotice(options.name, postInstall);
8750
- } finally {
8751
- await (0, import_promises8.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;
8752
9231
  }
8753
9232
  }
8754
9233
 
@@ -8761,16 +9240,16 @@ var import_node_util4 = require("node:util");
8761
9240
  // src/project-root.ts
8762
9241
  var import_node_os3 = require("node:os");
8763
9242
  var import_node_path12 = require("node:path");
8764
- var import_node_process3 = require("node:process");
9243
+ var import_node_process4 = require("node:process");
8765
9244
  function globalConfigDir(env2 = process.env) {
8766
9245
  const fromEnv = env2.AIPM_HOME?.trim();
8767
9246
  return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path12.join)((0, import_node_os3.homedir)(), ".aipm");
8768
9247
  }
8769
9248
  function resolveConfigRoot(options = {}) {
8770
- return options.global ? globalConfigDir() : (0, import_node_process3.cwd)();
9249
+ return options.global ? globalConfigDir() : (0, import_node_process4.cwd)();
8771
9250
  }
8772
9251
  function resolveInstallRoot(options = {}) {
8773
- 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)();
8774
9253
  }
8775
9254
  function scopeLabel(options) {
8776
9255
  return options.global ? "global" : "project";
@@ -8928,6 +9407,86 @@ async function runDoctor(options) {
8928
9407
  var import_node_crypto2 = require("node:crypto");
8929
9408
  var import_promises10 = require("node:fs/promises");
8930
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
8931
9490
  var PROMPT_SITE_ORIGIN = "https://www.aipm-registry.com";
8932
9491
  function parsePromptUrl(value) {
8933
9492
  let url;
@@ -9041,9 +9600,12 @@ async function installTrackedPrompt(input2) {
9041
9600
  if (input2.track) {
9042
9601
  const currentUrl = input2.project.prompts[input2.reference.alias];
9043
9602
  if (currentUrl && currentUrl !== canonicalReference.url) {
9044
- throw new Error(
9045
- `Prompt alias "${input2.reference.alias}" already tracks ${currentUrl}. Remove it first or use a unique slug.`
9046
- );
9603
+ await resolvePromptAliasConflict({
9604
+ ci: input2.ci,
9605
+ alias: input2.reference.alias,
9606
+ currentUrl,
9607
+ nextUrl: canonicalReference.url
9608
+ });
9047
9609
  }
9048
9610
  await writeProjectPackageJson(input2.configRoot, {
9049
9611
  ...input2.project,
@@ -9130,8 +9692,8 @@ async function removeInstalledPackageFiles(input2) {
9130
9692
  const unsafe = paths.find((path2) => !roots.some((root) => within(root, path2)));
9131
9693
  if (unsafe) throw new Error(`Refusing to remove an untrusted path from the lockfile: ${unsafe}`);
9132
9694
  for (const path2 of paths) {
9133
- const stat5 = await (0, import_promises11.lstat)(path2).catch(() => null);
9134
- if (!stat5 || stat5.isSymbolicLink()) continue;
9695
+ const stat6 = await (0, import_promises11.lstat)(path2).catch(() => null);
9696
+ if (!stat6 || stat6.isSymbolicLink()) continue;
9135
9697
  const canonicalPath = await (0, import_promises11.realpath)(path2);
9136
9698
  if (!realRoots.some((root) => within(root, canonicalPath))) {
9137
9699
  throw new Error(`Refusing to remove a path that resolves outside the install roots: ${path2}`);
@@ -9148,17 +9710,138 @@ async function removeInstalledPackageFiles(input2) {
9148
9710
  return { removed, retainedShared: requestedPaths.length - paths.length };
9149
9711
  }
9150
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
+
9151
9834
  // src/version.ts
9152
9835
  var import_node_fs2 = require("node:fs");
9153
- var import_node_path16 = require("node:path");
9836
+ var import_node_path17 = require("node:path");
9154
9837
  function getCliVersion() {
9155
- const pkgPath = (0, import_node_path16.join)(__dirname, "..", "package.json");
9838
+ const pkgPath = (0, import_node_path17.join)(__dirname, "..", "package.json");
9156
9839
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
9157
9840
  return pkg.version;
9158
9841
  }
9159
9842
 
9160
9843
  // src/cli-update-check.ts
9161
- var import_node_process4 = require("node:process");
9844
+ var import_node_process5 = require("node:process");
9162
9845
  var CLI_PACKAGE_NAME = "@aipm-registry/cli";
9163
9846
  var CLI_UPDATE_COMMAND = `npm install -g ${CLI_PACKAGE_NAME}`;
9164
9847
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -9209,7 +9892,7 @@ function formatCliUpdateNotice(options) {
9209
9892
  return `A new AIPM CLI version is available (${currentVersion} \u2192 ${latestVersion}). Run: ${cmd}`;
9210
9893
  }
9211
9894
  function printCliUpdateNotice(options) {
9212
- const useColor2 = options.useColor ?? import_node_process4.stderr.isTTY;
9895
+ const useColor2 = options.useColor ?? import_node_process5.stderr.isTTY;
9213
9896
  const writeInfo = options.writeInfo ?? ((message2) => console.log(message2));
9214
9897
  const writeWarn = options.writeWarn ?? ((message2) => console.error(message2));
9215
9898
  const message = formatCliUpdateNotice(options);
@@ -9274,7 +9957,7 @@ function registryFromEnvOrDefault(flag) {
9274
9957
  }
9275
9958
  function packagePageUrl(packageName, version) {
9276
9959
  const [scope, name] = packageName.replace(/^@/, "").split("/");
9277
- return `${SITE_URL}/packages/${encodeURIComponent(scope ?? "")}/${encodeURIComponent(name ?? "")}/${encodeURIComponent(version)}`;
9960
+ return `${SITE_URL}/skills/${encodeURIComponent(scope ?? "")}/${encodeURIComponent(name ?? "")}/${encodeURIComponent(version)}`;
9278
9961
  }
9279
9962
  function packageBadgeMarkdown(packageName, version) {
9280
9963
  const pageUrl = packagePageUrl(packageName, version);
@@ -9294,23 +9977,23 @@ function skillFolderName(name) {
9294
9977
  return folder;
9295
9978
  }
9296
9979
  async function writeStarterFile(path2, content) {
9297
- await (0, import_promises12.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
9980
+ await (0, import_promises14.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
9298
9981
  }
9299
9982
  async function inferEntryFromSource(source, fallback) {
9300
- const sourceInfo = await (0, import_promises12.stat)(source);
9301
- if (sourceInfo.isFile()) return (0, import_node_path17.basename)(source);
9983
+ const sourceInfo = await (0, import_promises14.stat)(source);
9984
+ if (sourceInfo.isFile()) return (0, import_node_path18.basename)(source);
9302
9985
  if (!sourceInfo.isDirectory()) return fallback;
9303
- const entries = await (0, import_promises12.readdir)(source);
9986
+ const entries = await (0, import_promises14.readdir)(source);
9304
9987
  if (entries.includes(fallback)) return fallback;
9305
9988
  return entries.find((entry) => entry.endsWith(".md")) ?? entries.find((entry) => entry.endsWith(".mdc")) ?? fallback;
9306
9989
  }
9307
9990
  async function copySourceIntoSkillRoot(source, root, sourceLabel) {
9308
- const sourceInfo = await (0, import_promises12.stat)(source);
9309
- 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 });
9310
9993
  if (sourceInfo.isDirectory()) {
9311
- const entries = await (0, import_promises12.readdir)(source);
9994
+ const entries = await (0, import_promises14.readdir)(source);
9312
9995
  for (const entry of entries) {
9313
- await (0, import_promises12.cp)((0, import_node_path17.join)(source, entry), (0, import_node_path17.join)(root, entry), {
9996
+ await (0, import_promises14.cp)((0, import_node_path18.join)(source, entry), (0, import_node_path18.join)(root, entry), {
9314
9997
  recursive: true,
9315
9998
  force: false,
9316
9999
  errorOnExist: true
@@ -9319,7 +10002,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
9319
10002
  return;
9320
10003
  }
9321
10004
  if (sourceInfo.isFile()) {
9322
- await (0, import_promises12.cp)(source, (0, import_node_path17.join)(root, (0, import_node_path17.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 });
9323
10006
  return;
9324
10007
  }
9325
10008
  throw new Error(`Unsupported source path: ${sourceLabel}`);
@@ -9350,14 +10033,14 @@ function formatBytes(bytes) {
9350
10033
  }
9351
10034
  function packageDashboardUrl(name) {
9352
10035
  if (!name) return DASHBOARD_URL;
9353
- return `${DASHBOARD_URL}/packages/${name.replace(/^@/, "")}`;
10036
+ return `${DASHBOARD_URL}/skills/${name.replace(/^@/, "")}`;
9354
10037
  }
9355
10038
  function helperRootFromTrackedPath(path2) {
9356
- const parts = path2.split(import_node_path17.sep).filter(Boolean);
10039
+ const parts = path2.split(import_node_path18.sep).filter(Boolean);
9357
10040
  const helpersIndex = parts.lastIndexOf("helpers");
9358
10041
  if (helpersIndex === -1 || parts.length <= helpersIndex + 2) return null;
9359
- const prefix = path2.startsWith(import_node_path17.sep) ? import_node_path17.sep : "";
9360
- return (0, import_node_path17.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));
9361
10044
  }
9362
10045
  async function showInstalledPrompt(configRoot, packageArg) {
9363
10046
  const { name } = parsePackageArg(packageArg);
@@ -9370,7 +10053,7 @@ async function showInstalledPrompt(configRoot, packageArg) {
9370
10053
  if (entry.postInstall.status === "cleaned") {
9371
10054
  throw new Error(`Setup helper files were cleaned for ${name}. Reinstall the package to restore them.`);
9372
10055
  }
9373
- 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(() => {
9374
10057
  throw new Error(`Setup prompt file is missing: ${entry.postInstall?.promptFile}`);
9375
10058
  });
9376
10059
  console.log(`Prompt file: ${entry.postInstall.promptFile}`);
@@ -9403,9 +10086,9 @@ async function cleanupInstalledHelpers(options) {
9403
10086
  entry.installedAssets.helper.map((path2) => helperRootFromTrackedPath(path2)).filter((path2) => Boolean(path2))
9404
10087
  );
9405
10088
  if (roots.size > 0) {
9406
- 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 });
9407
10090
  } else {
9408
- 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 });
9409
10092
  }
9410
10093
  await writeLockfile(options.configRoot, {
9411
10094
  ...lock,
@@ -9457,13 +10140,69 @@ async function authTokenForRegistry(registry, options = {}) {
9457
10140
  }
9458
10141
  }
9459
10142
  async function tokenForRead(registry, explicitToken, options = {}) {
9460
- 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
+ }
9461
10194
  }
9462
10195
  async function runLoopbackLogin(options) {
9463
10196
  const state = randomBase64Url(24);
9464
10197
  const verifier = randomBase64Url(48);
9465
10198
  const challenge = sha256Base64Url(verifier);
9466
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
+ };
9467
10206
  const server = (0, import_node_http.createServer)((request, response) => {
9468
10207
  const host = request.headers.host ?? "127.0.0.1";
9469
10208
  const url = new URL(request.url ?? "/", `http://${host}`);
@@ -9475,29 +10214,33 @@ async function runLoopbackLogin(options) {
9475
10214
  if (url.searchParams.get("state") !== state) {
9476
10215
  response.writeHead(400, { "content-type": "text/html" });
9477
10216
  response.end("<h1>AIPM login failed</h1><p>Invalid state. Return to the terminal and try again.</p>");
9478
- rejectLogin(new Error("Invalid CLI login state"));
9479
- server.close();
10217
+ forceCloseServer(server);
10218
+ settle(() => rejectLogin(new Error("Invalid CLI login state")));
9480
10219
  return;
9481
10220
  }
9482
10221
  const code = url.searchParams.get("code");
9483
10222
  if (!code) {
9484
10223
  response.writeHead(400, { "content-type": "text/html" });
9485
10224
  response.end("<h1>AIPM login failed</h1><p>Missing code. Return to the terminal and try again.</p>");
9486
- rejectLogin(new Error("Missing CLI authorization code"));
9487
- server.close();
10225
+ forceCloseServer(server);
10226
+ settle(() => rejectLogin(new Error("Missing CLI authorization code")));
9488
10227
  return;
9489
10228
  }
9490
10229
  response.writeHead(200, { "content-type": "text/html" });
9491
10230
  response.end("<h1>AIPM CLI is signed in</h1><p>You can close this window and return to the terminal.</p>");
9492
10231
  const address = server.address();
9493
10232
  if (!address || typeof address === "string") {
9494
- rejectLogin(new Error("Could not read CLI callback address"));
9495
- } else {
9496
- 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;
9497
10236
  }
9498
- 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));
9499
10243
  });
9500
- server.once("error", rejectLogin);
9501
10244
  server.listen(0, "127.0.0.1", () => {
9502
10245
  const address = server.address();
9503
10246
  const redirectUri = `http://127.0.0.1:${address.port}/callback`;
@@ -9510,10 +10253,12 @@ async function runLoopbackLogin(options) {
9510
10253
  if (options.open) void openUrl(loginUrl.toString());
9511
10254
  else console.log(`Open: ${loginUrl.toString()}`);
9512
10255
  });
9513
- server.setTimeout(5 * 60 * 1e3, () => {
9514
- rejectLogin(new Error("CLI login timed out"));
9515
- server.close();
9516
- });
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));
9517
10262
  });
9518
10263
  const tokens = await exchangeCliAuthCode(options.registry, {
9519
10264
  code: result.code,
@@ -9532,6 +10277,12 @@ async function runLoopbackLogin(options) {
9532
10277
  console.log(`Logged in as ${label}.`);
9533
10278
  console.log(`Credentials saved to ${authFilePath()}`);
9534
10279
  }
10280
+ function forceCloseServer(server) {
10281
+ if (typeof server.closeAllConnections === "function") {
10282
+ server.closeAllConnections();
10283
+ }
10284
+ server.close();
10285
+ }
9535
10286
  function printPublishFlow() {
9536
10287
  console.log("AIPM publish flow");
9537
10288
  console.log("1. Create an account and organization in the dashboard.");
@@ -9699,9 +10450,9 @@ function buildStarterQualityMetadata(options) {
9699
10450
  }
9700
10451
  async function initSkill(opts) {
9701
10452
  if (!isValidScopeName(opts.name)) throw new Error("Invalid @scope/name");
9702
- const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path17.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));
9703
10454
  const cdTarget = opts.here ? null : opts.dir ?? skillFolderName(opts.name);
9704
- const source = opts.from ? (0, import_node_path17.resolve)(opts.from) : null;
10455
+ const source = opts.from ? (0, import_node_path18.resolve)(opts.from) : null;
9705
10456
  const entry = source ? await inferEntryFromSource(source, opts.entry) : opts.entry;
9706
10457
  const template = parseSkillTemplate(opts.template ?? "blank");
9707
10458
  const manifest = PackageManifestSchema.parse({
@@ -9722,17 +10473,17 @@ async function initSkill(opts) {
9722
10473
  if (source) {
9723
10474
  await copySourceIntoSkillRoot(source, root, source);
9724
10475
  } else {
9725
- await (0, import_promises12.mkdir)(root, { recursive: true });
10476
+ await (0, import_promises14.mkdir)(root, { recursive: true });
9726
10477
  }
9727
- await writeStarterFile((0, import_node_path17.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");
9728
10479
  if (!opts.from) {
9729
10480
  await writeStarterFile(
9730
- (0, import_node_path17.join)(root, entry),
10481
+ (0, import_node_path18.join)(root, entry),
9731
10482
  SKILL_TEMPLATES[template](opts.name)
9732
10483
  );
9733
10484
  }
9734
10485
  await writeStarterFile(
9735
- (0, import_node_path17.join)(root, ".aipmignore"),
10486
+ (0, import_node_path18.join)(root, ".aipmignore"),
9736
10487
  [
9737
10488
  "# Files that should never be published with this skill",
9738
10489
  ".env*",
@@ -9750,7 +10501,7 @@ async function initSkill(opts) {
9750
10501
  ""
9751
10502
  ].join("\n")
9752
10503
  );
9753
- await (0, import_promises12.mkdir)((0, import_node_path17.join)(root, ".aipm"), { recursive: true });
10504
+ await (0, import_promises14.mkdir)((0, import_node_path18.join)(root, ".aipm"), { recursive: true });
9754
10505
  console.log(`Created ${opts.name} skill folder: ${root}`);
9755
10506
  printPublishGuide(
9756
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.`,
@@ -9765,24 +10516,11 @@ program2.command("init").description("Create aipm.package.json in the current pr
9765
10516
  console.log(`aipm.package.json already exists (${scopeLabel(scope)}).`);
9766
10517
  return;
9767
10518
  }
9768
- if (scope.global) await (0, import_promises12.mkdir)(configRoot, { recursive: true });
9769
- const installRoot = resolveInstallRoot(scope);
9770
- const detected = await detectToolsInProject(installRoot);
9771
- const parsedTarget = parseTargetFlag(opts.target);
9772
- let preferredTools = parsedTarget ? [parsedTarget] : detected;
9773
- if (!opts.target && detected.length === 0) {
9774
- const choice = await promptForTool();
9775
- preferredTools = [choice];
9776
- }
9777
- const registry = registryFromEnvOrDefault(opts.registry);
9778
- await writeProjectPackageJson(configRoot, {
9779
- schemaVersion: "0.1",
9780
- registry,
9781
- preferredTools: preferredTools.length ? preferredTools : void 0,
9782
- packages: {},
9783
- prompts: {}
10519
+ await createProjectPackageJson({
10520
+ scope,
10521
+ registry: registryFromEnvOrDefault(opts.registry),
10522
+ target: opts.target
9784
10523
  });
9785
- console.log(`Created aipm.package.json (${scopeLabel(scope)}, registry: ${registry})`);
9786
10524
  });
9787
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) => {
9788
10526
  const registry = registryFromEnvOrDefault(opts.registry);
@@ -9837,9 +10575,9 @@ program2.command("config").description("Show resolved AIPM CLI and project confi
9837
10575
  scope: scopeLabel(scope),
9838
10576
  configRoot,
9839
10577
  installRoot,
9840
- cwd: (0, import_node_process5.cwd)(),
10578
+ cwd: (0, import_node_process6.cwd)(),
9841
10579
  registry,
9842
- 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,
9843
10581
  hasProjectConfig: Boolean(project),
9844
10582
  projectPackages: project ? Object.keys(project.packages) : [],
9845
10583
  hasLockfile: Boolean(lock),
@@ -9868,11 +10606,58 @@ program2.command("config").description("Show resolved AIPM CLI and project confi
9868
10606
  console.log(`Auth file: ${data.authFile}`);
9869
10607
  console.log(`Publish guide: ${data.publishDoc}`);
9870
10608
  });
9871
- 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) => {
9872
10610
  const scope = { global: opts.global };
9873
10611
  const configRoot = resolveConfigRoot(scope);
9874
10612
  const installRoot = resolveInstallRoot(scope);
9875
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
+ }
9876
10661
  if (!project) throw new Error(initRequiredMessage(scope));
9877
10662
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9878
10663
  const promptReference = parsePromptUrl(pkgArg);
@@ -9883,7 +10668,8 @@ program2.command("add <package-or-prompt>").description("Add a skill package or
9883
10668
  project,
9884
10669
  reference: promptReference,
9885
10670
  track: true,
9886
- recordCopy: true
10671
+ recordCopy: true,
10672
+ ci: opts.ci
9887
10673
  });
9888
10674
  console.log(`Installed prompt ${result.prompt.title} \u2192 ${result.path}`);
9889
10675
  return;
@@ -9897,7 +10683,7 @@ program2.command("add <package-or-prompt>").description("Add a skill package or
9897
10683
  packages: { ...project.packages, [name]: version.replace(/^\^/, "") }
9898
10684
  };
9899
10685
  await writeProjectPackageJson(configRoot, project);
9900
- await installOnePackage({
10686
+ await installOnePackageWithRecovery({
9901
10687
  configRoot,
9902
10688
  installRoot,
9903
10689
  registry,
@@ -9925,7 +10711,7 @@ program2.command("search [query]").description("Search public registry packages"
9925
10711
  const token = await tokenForRead(registry, opts.token, { quiet: opts.json });
9926
10712
  const packages = await searchPackages(registry, query, Number(opts.limit), token);
9927
10713
  if (opts.json) {
9928
- console.log(JSON.stringify({ packages }, null, 2));
10714
+ console.log(JSON.stringify({ skills: packages }, null, 2));
9929
10715
  return;
9930
10716
  }
9931
10717
  if (packages.length === 0) {
@@ -9944,13 +10730,31 @@ program2.command("install").description("Install all packages from aipm.package.
9944
10730
  const scope = { global: opts.global };
9945
10731
  const configRoot = resolveConfigRoot(scope);
9946
10732
  const installRoot = resolveInstallRoot(scope);
9947
- const project = await readProjectPackageJson(configRoot);
9948
- 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
+ }
9949
10753
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9950
10754
  const target = parseTargetFlag(opts.target);
9951
10755
  const token = await tokenForRead(registry, opts.token);
9952
10756
  for (const [name, version] of Object.entries(project.packages)) {
9953
- await installOnePackage({
10757
+ await installOnePackageWithRecovery({
9954
10758
  configRoot,
9955
10759
  installRoot,
9956
10760
  registry,
@@ -9970,15 +10774,28 @@ program2.command("install").description("Install all packages from aipm.package.
9970
10774
  registry,
9971
10775
  project,
9972
10776
  reference: { ...reference, alias },
9973
- track: false
10777
+ track: false,
10778
+ ci: opts.ci
9974
10779
  });
9975
10780
  console.log(`Restored prompt ${result.prompt.title} \u2192 ${result.path}`);
9976
10781
  }
9977
10782
  });
9978
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) => {
9979
- const configRoot = resolveConfigRoot({ global: opts.global });
9980
- const project = await readProjectPackageJson(configRoot);
9981
- 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
+ }
9982
10799
  const reference = resolveTrackedPrompt(project, value);
9983
10800
  if (reference && project.prompts[reference.alias] === reference.url) {
9984
10801
  const lock2 = await readLockfile(configRoot);
@@ -9997,7 +10814,7 @@ program2.command("show-prompt <package>").description("Show the manual setup pro
9997
10814
  await showInstalledPrompt(resolveConfigRoot({ global: opts.global }), pkgArg);
9998
10815
  });
9999
10816
  function promptImageContentType(path2) {
10000
- const extension = (0, import_node_path17.extname)(path2).toLowerCase();
10817
+ const extension = (0, import_node_path18.extname)(path2).toLowerCase();
10001
10818
  if (extension === ".png") return "image/png";
10002
10819
  if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
10003
10820
  if (extension === ".webp") return "image/webp";
@@ -10006,10 +10823,18 @@ function promptImageContentType(path2) {
10006
10823
  var promptCommand = program2.command("prompt").description("Publish prompt listings from structured JSON");
10007
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) => {
10008
10825
  const registry = registryFromEnvOrDefault(opts.registry);
10009
- const accessToken = await authTokenForRegistry(registry, { throwOnFailure: true });
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
+ }
10010
10835
  if (!accessToken) throw new Error(`Run ${recommendCmd("aipm login")} before publishing prompts.`);
10011
- const absoluteFile = (0, import_node_path17.resolve)(file);
10012
- const parsed = JSON.parse(await (0, import_promises12.readFile)(absoluteFile, "utf8"));
10836
+ const absoluteFile = (0, import_node_path18.resolve)(file);
10837
+ const parsed = JSON.parse(await (0, import_promises14.readFile)(absoluteFile, "utf8"));
10013
10838
  const rows = Array.isArray(parsed) ? parsed : "prompts" in parsed ? parsed.prompts : [parsed];
10014
10839
  if (rows.length === 0) throw new Error("Prompt publish file contains no prompts.");
10015
10840
  if (!opts.yes) {
@@ -10020,10 +10845,10 @@ promptCommand.command("publish <file>").description("Publish one prompt or a bat
10020
10845
  const { sampleImagePath, ...input2 } = row;
10021
10846
  let sampleImage;
10022
10847
  if (sampleImagePath) {
10023
- const imagePath = (0, import_node_path17.resolve)((0, import_node_path17.dirname)(absoluteFile), sampleImagePath);
10848
+ const imagePath = (0, import_node_path18.resolve)((0, import_node_path18.dirname)(absoluteFile), sampleImagePath);
10024
10849
  sampleImage = {
10025
- data: await (0, import_promises12.readFile)(imagePath),
10026
- filename: (0, import_node_path17.basename)(imagePath),
10850
+ data: await (0, import_promises14.readFile)(imagePath),
10851
+ filename: (0, import_node_path18.basename)(imagePath),
10027
10852
  contentType: promptImageContentType(imagePath)
10028
10853
  };
10029
10854
  }
@@ -10046,12 +10871,12 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
10046
10871
  publish.help();
10047
10872
  return;
10048
10873
  }
10049
- const abs = (0, import_node_path17.resolve)(dir);
10050
- const manifestRaw = await (0, import_promises12.readFile)((0, import_node_path17.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");
10051
10876
  const manifest = PackageManifestSchema.parse(JSON.parse(manifestRaw));
10052
10877
  const tarball = await packDirectory(abs);
10053
10878
  const registry = registryFromEnvOrDefault(opts.registry);
10054
- 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);
10055
10880
  console.log(`Published ${manifest.name}@${result.version}`);
10056
10881
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
10057
10882
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
@@ -10066,7 +10891,7 @@ publish.command("import <source>").description("Create a package folder from an
10066
10891
  async (source, opts) => initSkill({ ...opts, from: source })
10067
10892
  );
10068
10893
  publish.command("add [files...]").description("Stage files for publishing").action(async (files) => {
10069
- const state = await addPublishFiles((0, import_node_process5.cwd)(), files);
10894
+ const state = await addPublishFiles((0, import_node_process6.cwd)(), files);
10070
10895
  console.log(`Staged ${state.files.length} file${state.files.length === 1 ? "" : "s"}.`);
10071
10896
  printPublishGuide(
10072
10897
  `Added ${files.length > 0 ? files.join(", ") : "."} to the publish stage, respecting .aipmignore and secret-file exclusions.`,
@@ -10097,7 +10922,7 @@ publish.command("token").description("Open the dashboard page used to generate a
10097
10922
  );
10098
10923
  });
10099
10924
  publish.command("remove <files...>").alias("rm").description("Remove files from the publish stage").action(async (files) => {
10100
- const state = await removePublishFiles((0, import_node_process5.cwd)(), files);
10925
+ const state = await removePublishFiles((0, import_node_process6.cwd)(), files);
10101
10926
  console.log(`Staged ${state.files.length} file${state.files.length === 1 ? "" : "s"}.`);
10102
10927
  printPublishGuide(
10103
10928
  `Removed ${files.join(", ")} from the publish stage.`,
@@ -10105,7 +10930,7 @@ publish.command("remove <files...>").alias("rm").description("Remove files from
10105
10930
  );
10106
10931
  });
10107
10932
  publish.command("reset").description("Clear staged publish files").action(async () => {
10108
- await resetPublishState((0, import_node_process5.cwd)());
10933
+ await resetPublishState((0, import_node_process6.cwd)());
10109
10934
  console.log("Cleared publish stage.");
10110
10935
  printPublishGuide(
10111
10936
  "Cleared all staged publish files.",
@@ -10113,7 +10938,7 @@ publish.command("reset").description("Clear staged publish files").action(async
10113
10938
  );
10114
10939
  });
10115
10940
  publish.command("status").description("Show staged publish files").option("--json", "Print machine-readable JSON").action(async (opts) => {
10116
- const rows = await statusPublishState((0, import_node_process5.cwd)());
10941
+ const rows = await statusPublishState((0, import_node_process6.cwd)());
10117
10942
  if (opts.json) {
10118
10943
  console.log(JSON.stringify({ files: rows }, null, 2));
10119
10944
  return;
@@ -10136,7 +10961,7 @@ publish.command("status").description("Show staged publish files").option("--jso
10136
10961
  );
10137
10962
  });
10138
10963
  publish.command("diff").description("Show staged files that changed since add").action(async () => {
10139
- const rows = await statusPublishState((0, import_node_process5.cwd)());
10964
+ const rows = await statusPublishState((0, import_node_process6.cwd)());
10140
10965
  const changed = rows.filter((row) => row.changed);
10141
10966
  if (changed.length === 0) {
10142
10967
  console.log("No staged files changed.");
@@ -10153,16 +10978,16 @@ publish.command("diff").description("Show staged files that changed since add").
10153
10978
  );
10154
10979
  });
10155
10980
  publish.command("validate").description("Validate staged files before publish").action(async () => {
10156
- const result = await validatePublishState((0, import_node_process5.cwd)());
10981
+ const result = await validatePublishState((0, import_node_process6.cwd)());
10157
10982
  console.log(`Valid ${result.manifest.name}@${result.manifest.version} (${result.size} bytes staged).`);
10158
10983
  printPublishGuide(
10159
10984
  `Validated ${result.manifest.name}@${result.manifest.version}; manifest, entry file, staged hashes, ignored paths, package size, and obvious secrets passed.`,
10160
10985
  "Generate a 5-minute token from the dashboard, then run AIPM_TOKEN=<token> aipm publish push."
10161
10986
  );
10162
10987
  });
10163
- 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));
10164
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) => {
10165
- const root = (0, import_node_process5.cwd)();
10990
+ const root = (0, import_node_process6.cwd)();
10166
10991
  const { manifest } = await validatePublishState(root);
10167
10992
  const registry = registryFromEnvOrDefault(opts.registry);
10168
10993
  const tarball = await packStagedFiles(root);
@@ -10177,7 +11002,7 @@ publish.command("push").description("Publish staged files").option("--registry <
10177
11002
  console.log(`Verbose: package ${manifest.name}@${manifest.version}; tarball ${tarball.length} bytes.`);
10178
11003
  }
10179
11004
  for (const entry of staged) console.log(` ${entry.path}`);
10180
- 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);
10181
11006
  console.log(`Published ${manifest.name}@${result.version}`);
10182
11007
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
10183
11008
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
@@ -10209,11 +11034,51 @@ program2.command("list").description("List installed skills and prompts from aip
10209
11034
  }
10210
11035
  }
10211
11036
  });
10212
- 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) => {
10213
11038
  const scope = { global: opts.global };
10214
11039
  const configRoot = resolveConfigRoot(scope);
10215
11040
  const installRoot = resolveInstallRoot(scope);
10216
- const project = await readProjectPackageJson(configRoot);
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
+ }
10217
11082
  if (!project) throw new Error(initRequiredMessage(scope));
10218
11083
  const promptReference = resolveTrackedPrompt(project, pkgArg);
10219
11084
  if (promptReference && project.prompts[promptReference.alias] === promptReference.url) {
@@ -10247,11 +11112,71 @@ program2.command("remove <package-or-prompt>").alias("rm").description("Remove a
10247
11112
  }
10248
11113
  console.log(`Removed ${name} from AIPM ${scopeLabel(scope)} files.`);
10249
11114
  });
10250
- 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) => {
10251
11116
  const scope = { global: opts.global };
10252
11117
  const configRoot = resolveConfigRoot(scope);
10253
11118
  const installRoot = resolveInstallRoot(scope);
10254
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
+ }
10255
11180
  if (!project) throw new Error(initRequiredMessage(scope));
10256
11181
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
10257
11182
  const requestedPrompt = pkgArg ? resolveTrackedPrompt(project, pkgArg) : null;
@@ -10265,7 +11190,8 @@ program2.command("update [package]").description("Update one installed package,
10265
11190
  project,
10266
11191
  reference: requestedPrompt,
10267
11192
  track: false,
10268
- updateOnly: true
11193
+ updateOnly: true,
11194
+ ci: opts.ci
10269
11195
  });
10270
11196
  console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
10271
11197
  return;
@@ -10279,7 +11205,7 @@ program2.command("update [package]").description("Update one installed package,
10279
11205
  packages: { ...project.packages, [name]: version }
10280
11206
  };
10281
11207
  await writeProjectPackageJson(configRoot, project);
10282
- await installOnePackage({
11208
+ await installOnePackageWithRecovery({
10283
11209
  configRoot,
10284
11210
  installRoot,
10285
11211
  registry,
@@ -10301,7 +11227,8 @@ program2.command("update [package]").description("Update one installed package,
10301
11227
  project,
10302
11228
  reference: { ...reference, alias },
10303
11229
  track: false,
10304
- updateOnly: true
11230
+ updateOnly: true,
11231
+ ci: opts.ci
10305
11232
  });
10306
11233
  console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
10307
11234
  }
@@ -10312,6 +11239,10 @@ program2.command("update [package]").description("Update one installed package,
10312
11239
  });
10313
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));
10314
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
+ }
10315
11246
  console.error(err.message);
10316
11247
  const message = err.message.toLowerCase();
10317
11248
  if (message.includes("token") || message.includes("unauthorized") || message.includes("forbidden")) {