@aipm-registry/cli 0.3.0 → 0.3.2

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
@@ -4160,7 +4160,19 @@ function expandTargets(targets) {
4160
4160
  return [...ALL_TOOLS];
4161
4161
  return targets.filter((t) => t !== "*");
4162
4162
  }
4163
- var AiToolSchema, ALL_TOOLS, PackageExampleSchema, PackageManifestSchema;
4163
+ function isSafeRelativePath(value) {
4164
+ const trimmed = value.trim();
4165
+ if (!trimmed || trimmed.startsWith("/") || trimmed.startsWith("\\") || /^[A-Za-z]:/.test(trimmed)) {
4166
+ return false;
4167
+ }
4168
+ const normalized = trimmed.replace(/\\/g, "/");
4169
+ return normalized.split("/").filter(Boolean).every((segment) => segment !== "." && segment !== "..");
4170
+ }
4171
+ function basename(value) {
4172
+ const parts = value.replace(/\\/g, "/").split("/").filter(Boolean);
4173
+ return parts[parts.length - 1] ?? value;
4174
+ }
4175
+ var AiToolSchema, ALL_TOOLS, PackageExampleSchema, unsafePathMessage, InstallPathSchema, InstallOverwriteSchema, PackageInstallSchema, PackageManifestSchema;
4164
4176
  var init_manifest = __esm({
4165
4177
  "../../packages/schemas/dist/manifest.js"() {
4166
4178
  "use strict";
@@ -4173,6 +4185,36 @@ var init_manifest = __esm({
4173
4185
  description: external_exports.string().trim().min(1).max(240).optional(),
4174
4186
  prompt: external_exports.string().trim().min(1).max(1e3)
4175
4187
  });
4188
+ unsafePathMessage = "path must be relative and must not contain .. segments";
4189
+ InstallPathSchema = external_exports.string().trim().min(1).refine(isSafeRelativePath, unsafePathMessage);
4190
+ InstallOverwriteSchema = external_exports.enum(["fail", "skip", "replace"]);
4191
+ PackageInstallSchema = external_exports.object({
4192
+ mainFiles: external_exports.array(external_exports.object({
4193
+ from: InstallPathSchema,
4194
+ to: InstallPathSchema,
4195
+ overwrite: InstallOverwriteSchema.optional()
4196
+ })).optional(),
4197
+ helperFiles: external_exports.array(external_exports.object({
4198
+ from: InstallPathSchema,
4199
+ to: InstallPathSchema.optional()
4200
+ })).optional(),
4201
+ postInstall: external_exports.object({
4202
+ mode: external_exports.literal("manual_prompt"),
4203
+ promptFile: InstallPathSchema,
4204
+ cleanup: external_exports.enum(["manual", "after_user_confirmation"]).optional()
4205
+ }).optional()
4206
+ }).superRefine((install, ctx) => {
4207
+ if (!install.postInstall)
4208
+ return;
4209
+ const helperTargets = new Set((install.helperFiles ?? []).map((file) => file.to ?? basename(file.from)));
4210
+ if (!helperTargets.has(install.postInstall.promptFile)) {
4211
+ ctx.addIssue({
4212
+ code: external_exports.ZodIssueCode.custom,
4213
+ path: ["postInstall", "promptFile"],
4214
+ message: "postInstall.promptFile must point to an installed helper file"
4215
+ });
4216
+ }
4217
+ });
4176
4218
  PackageManifestSchema = external_exports.object({
4177
4219
  schemaVersion: external_exports.literal("0.1"),
4178
4220
  name: external_exports.string().regex(SCOPE_NAME_REGEX, "name must be @scope/name"),
@@ -4183,11 +4225,13 @@ var init_manifest = __esm({
4183
4225
  targets: external_exports.array(AiToolSchema).min(1),
4184
4226
  license: external_exports.string().optional(),
4185
4227
  usage: external_exports.string().trim().min(1).max(2e3).optional(),
4228
+ agentDescription: external_exports.string().trim().min(1).max(12e3).optional(),
4186
4229
  tags: external_exports.array(external_exports.string().trim().min(1).max(40)).max(12).optional(),
4187
4230
  categories: external_exports.array(external_exports.string().trim().min(1).max(40)).max(4).optional(),
4188
4231
  sourceUrl: external_exports.string().trim().url().max(500).optional(),
4189
4232
  examples: external_exports.array(PackageExampleSchema).max(5).optional(),
4190
- releaseNotes: external_exports.string().trim().min(1).max(2e3).optional()
4233
+ releaseNotes: external_exports.string().trim().min(1).max(2e3).optional(),
4234
+ install: PackageInstallSchema.optional()
4191
4235
  });
4192
4236
  }
4193
4237
  });
@@ -4225,7 +4269,17 @@ var init_lockfile = __esm({
4225
4269
  integrity: external_exports.string(),
4226
4270
  registry: external_exports.string().url(),
4227
4271
  resolvedTools: external_exports.array(AiToolSchema),
4228
- installed: InstalledPathsSchema
4272
+ installed: InstalledPathsSchema,
4273
+ installedAssets: external_exports.object({
4274
+ main: external_exports.array(external_exports.string()),
4275
+ helper: external_exports.array(external_exports.string())
4276
+ }).optional(),
4277
+ postInstall: external_exports.object({
4278
+ mode: external_exports.literal("manual_prompt"),
4279
+ status: external_exports.enum(["pending", "cleaned"]),
4280
+ promptFile: external_exports.string(),
4281
+ cleanup: external_exports.enum(["manual", "after_user_confirmation"])
4282
+ }).optional()
4229
4283
  });
4230
4284
  LockfileSchema = external_exports.object({
4231
4285
  schemaVersion: external_exports.literal("0.1"),
@@ -7776,16 +7830,15 @@ var import_node_crypto2 = require("node:crypto");
7776
7830
  var import_node_child_process4 = require("node:child_process");
7777
7831
  var import_node_http = require("node:http");
7778
7832
  var import_promises10 = require("node:fs/promises");
7779
- var import_node_path12 = require("node:path");
7833
+ var import_node_path13 = require("node:path");
7780
7834
  var import_node_process5 = require("node:process");
7781
7835
  init_dist();
7782
7836
  init_dist4();
7783
7837
 
7784
7838
  // src/pack.ts
7785
7839
  var import_node_child_process2 = require("node:child_process");
7786
- var import_promises5 = require("node:fs/promises");
7787
7840
  var import_node_util3 = require("node:util");
7788
- var import_promises6 = require("node:fs/promises");
7841
+ var import_promises5 = require("node:fs/promises");
7789
7842
  var import_node_path6 = require("node:path");
7790
7843
  var import_node_os = require("node:os");
7791
7844
 
@@ -7938,6 +7991,14 @@ async function readManifest(root) {
7938
7991
  throw error;
7939
7992
  }
7940
7993
  }
7994
+ function installReferencedFiles(manifest) {
7995
+ const install = manifest.install;
7996
+ if (!install) return [];
7997
+ return [
7998
+ ...(install.mainFiles ?? []).map((file) => file.from),
7999
+ ...(install.helperFiles ?? []).map((file) => file.from)
8000
+ ];
8001
+ }
7941
8002
  async function validatePublishState(root) {
7942
8003
  const manifest = await readManifest(root);
7943
8004
  const state = await readPublishState(root);
@@ -7945,6 +8006,9 @@ async function validatePublishState(root) {
7945
8006
  const staged = new Set(state.files.map((entry) => entry.path));
7946
8007
  if (!staged.has("aipm.manifest.json")) throw new Error("aipm.manifest.json must be staged.");
7947
8008
  if (!staged.has(manifest.entry)) throw new Error(`Manifest entry must be staged: ${manifest.entry}`);
8009
+ for (const file of installReferencedFiles(manifest)) {
8010
+ if (!staged.has(file)) throw new Error(`Manifest install file must be staged: ${file}`);
8011
+ }
7948
8012
  let size = 0;
7949
8013
  for (const entry of state.files) {
7950
8014
  if (isExcludedPublishPath(entry.path)) throw new Error(`Refusing to publish excluded path: ${entry.path}`);
@@ -7992,27 +8056,23 @@ async function packDirectory(dir) {
7992
8056
  return Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout);
7993
8057
  }
7994
8058
  async function packStagedFiles(root) {
7995
- const tempDir = await (0, import_promises6.mkdtemp)((0, import_node_path6.join)((0, import_node_os.tmpdir)(), "aipm-publish-stage-"));
8059
+ const tempDir = await (0, import_promises5.mkdtemp)((0, import_node_path6.join)((0, import_node_os.tmpdir)(), "aipm-publish-stage-"));
7996
8060
  try {
7997
8061
  await copyStagedFiles(root, tempDir);
7998
8062
  return await packDirectory(tempDir);
7999
8063
  } finally {
8000
- await (0, import_promises6.rm)(tempDir, { recursive: true, force: true });
8064
+ await (0, import_promises5.rm)(tempDir, { recursive: true, force: true });
8001
8065
  }
8002
8066
  }
8003
- async function unpackTarballToBuffer(tarball, entry) {
8004
- const { mkdtemp: mkdtemp2, rm: rm3, writeFile: writeFile7 } = await import("node:fs/promises");
8005
- const { join: join12 } = await import("node:path");
8067
+ async function unpackTarballToDirectory(tarball) {
8068
+ const { mkdtemp: mkdtemp2, writeFile: writeFile7 } = await import("node:fs/promises");
8069
+ const { join: join13 } = await import("node:path");
8006
8070
  const { tmpdir: tmpdir2 } = await import("node:os");
8007
- const tempDir = await mkdtemp2(join12(tmpdir2(), "aipm-install-"));
8008
- const tgzPath = join12(tempDir, "pkg.tgz");
8009
- try {
8010
- await writeFile7(tgzPath, tarball);
8011
- await execFileAsync("tar", ["-xzf", tgzPath, "-C", tempDir]);
8012
- return (0, import_promises5.readFile)(join12(tempDir, entry), "utf8");
8013
- } finally {
8014
- await rm3(tempDir, { recursive: true, force: true });
8015
- }
8071
+ const tempDir = await mkdtemp2(join13(tmpdir2(), "aipm-install-"));
8072
+ const tgzPath = join13(tempDir, "pkg.tgz");
8073
+ await writeFile7(tgzPath, tarball);
8074
+ await execFileAsync("tar", ["-xzf", tgzPath, "-C", tempDir]);
8075
+ return tempDir;
8016
8076
  }
8017
8077
 
8018
8078
  // src/registry-client.ts
@@ -8071,6 +8131,7 @@ async function searchPackages(registry, query, limit = 20, token) {
8071
8131
  const params = new URLSearchParams();
8072
8132
  if (query) params.set("q", query);
8073
8133
  params.set("limit", String(limit));
8134
+ if (token) params.set("includePrivate", "true");
8074
8135
  const res = await registryFetch(`${base}/v1/packages?${params}`, base, {
8075
8136
  headers: registryAuthHeaders(token)
8076
8137
  });
@@ -8154,7 +8215,7 @@ async function recordPackageInstall(registry, name, token) {
8154
8215
  }
8155
8216
 
8156
8217
  // src/auth-store.ts
8157
- var import_promises7 = require("node:fs/promises");
8218
+ var import_promises6 = require("node:fs/promises");
8158
8219
  var import_node_os2 = require("node:os");
8159
8220
  var import_node_path7 = require("node:path");
8160
8221
  var AUTH_FILE = (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".aipm", "auth.json");
@@ -8163,7 +8224,7 @@ function normalizeRegistry(registry) {
8163
8224
  }
8164
8225
  async function readAuthStore() {
8165
8226
  try {
8166
- const raw = await (0, import_promises7.readFile)(AUTH_FILE, "utf8");
8227
+ const raw = await (0, import_promises6.readFile)(AUTH_FILE, "utf8");
8167
8228
  const parsed = JSON.parse(raw);
8168
8229
  return { registries: parsed.registries ?? {} };
8169
8230
  } catch {
@@ -8171,9 +8232,9 @@ async function readAuthStore() {
8171
8232
  }
8172
8233
  }
8173
8234
  async function writeAuthStore(store) {
8174
- await (0, import_promises7.mkdir)((0, import_node_path7.dirname)(AUTH_FILE), { recursive: true });
8175
- await (0, import_promises7.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8176
- await (0, import_promises7.chmod)(AUTH_FILE, 384).catch(() => void 0);
8235
+ await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(AUTH_FILE), { recursive: true });
8236
+ await (0, import_promises6.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8237
+ await (0, import_promises6.chmod)(AUTH_FILE, 384).catch(() => void 0);
8177
8238
  }
8178
8239
  async function getStoredRegistryAuth(registry) {
8179
8240
  const store = await readAuthStore();
@@ -8203,23 +8264,25 @@ function isAccessTokenFresh(auth, skewMs = 6e4) {
8203
8264
 
8204
8265
  // src/install-one.ts
8205
8266
  init_dist4();
8267
+ var import_promises8 = require("node:fs/promises");
8268
+ var import_node_path9 = require("node:path");
8206
8269
 
8207
8270
  // src/project-files.ts
8208
- var import_promises8 = require("node:fs/promises");
8271
+ var import_promises7 = require("node:fs/promises");
8209
8272
  var import_node_path8 = require("node:path");
8210
8273
  init_dist();
8211
8274
  var PACKAGE_JSON = "aipm.package.json";
8212
8275
  var LOCKFILE = "aipm-lock.json";
8213
8276
  async function readProjectPackageJson(projectRoot) {
8214
8277
  try {
8215
- const raw = await (0, import_promises8.readFile)((0, import_node_path8.join)(projectRoot, PACKAGE_JSON), "utf8");
8278
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(projectRoot, PACKAGE_JSON), "utf8");
8216
8279
  return ProjectPackageJsonSchema.parse(JSON.parse(raw));
8217
8280
  } catch {
8218
8281
  return null;
8219
8282
  }
8220
8283
  }
8221
8284
  async function writeProjectPackageJson(projectRoot, data) {
8222
- await (0, import_promises8.writeFile)(
8285
+ await (0, import_promises7.writeFile)(
8223
8286
  (0, import_node_path8.join)(projectRoot, PACKAGE_JSON),
8224
8287
  JSON.stringify(data, null, 2) + "\n",
8225
8288
  "utf8"
@@ -8227,14 +8290,14 @@ async function writeProjectPackageJson(projectRoot, data) {
8227
8290
  }
8228
8291
  async function readLockfile(projectRoot) {
8229
8292
  try {
8230
- const raw = await (0, import_promises8.readFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), "utf8");
8293
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), "utf8");
8231
8294
  return LockfileSchema.parse(JSON.parse(raw));
8232
8295
  } catch {
8233
8296
  return null;
8234
8297
  }
8235
8298
  }
8236
8299
  async function writeLockfile(projectRoot, data) {
8237
- await (0, import_promises8.writeFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8300
+ await (0, import_promises7.writeFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8238
8301
  }
8239
8302
  function upsertLockEntry(lock, name, entry) {
8240
8303
  return {
@@ -8288,8 +8351,113 @@ async function promptForTool() {
8288
8351
  rl.close();
8289
8352
  }
8290
8353
  }
8354
+ async function promptForConfirmation(question) {
8355
+ const rl = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
8356
+ try {
8357
+ const answer = await rl.question(`${question} (y/N): `);
8358
+ const normalized = answer.trim().toLowerCase();
8359
+ return normalized === "y" || normalized === "yes";
8360
+ } finally {
8361
+ rl.close();
8362
+ }
8363
+ }
8291
8364
 
8292
8365
  // src/install-one.ts
8366
+ function normalizeRelativePath(path2) {
8367
+ return (0, import_node_path9.normalize)(path2).split(import_node_path9.sep).join("/");
8368
+ }
8369
+ function assertSafeRelativePath(path2) {
8370
+ const rel = normalizeRelativePath(path2.trim());
8371
+ if (!rel || rel === "." || rel.startsWith("/") || rel === ".." || rel.startsWith("../") || rel.includes("/../")) {
8372
+ throw new Error(`Unsafe install path: ${path2}`);
8373
+ }
8374
+ return rel;
8375
+ }
8376
+ function safeJoin(root, relPath) {
8377
+ const rel = assertSafeRelativePath(relPath);
8378
+ const target = (0, import_node_path9.resolve)(root, rel);
8379
+ const fromRoot = normalizeRelativePath((0, import_node_path9.relative)(root, target));
8380
+ if (fromRoot === ".." || fromRoot.startsWith("../") || fromRoot.startsWith("/")) {
8381
+ throw new Error(`Unsafe install path: ${relPath}`);
8382
+ }
8383
+ return target;
8384
+ }
8385
+ function packageHelperSlug(name) {
8386
+ const [scope = "", pkg = ""] = name.replace(/^@/, "").split("/");
8387
+ return `${scope}__${pkg}`;
8388
+ }
8389
+ function helperRootFor(configRoot, packageName, version) {
8390
+ const base = (0, import_node_path9.basename)(configRoot) === ".aipm" ? (0, import_node_path9.join)(configRoot, "helpers") : (0, import_node_path9.join)(configRoot, ".aipm", "helpers");
8391
+ return (0, import_node_path9.join)(base, packageHelperSlug(packageName), version);
8392
+ }
8393
+ async function assertSourceFile(packageRoot, relPath) {
8394
+ const source = safeJoin(packageRoot, relPath);
8395
+ const info = await (0, import_promises8.stat)(source).catch(() => null);
8396
+ if (!info?.isFile()) throw new Error(`Package install file not found: ${relPath}`);
8397
+ return source;
8398
+ }
8399
+ async function copyMainFile(input2) {
8400
+ const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8401
+ const target = safeJoin(input2.installRoot, input2.file.to);
8402
+ const overwrite = input2.file.overwrite ?? "fail";
8403
+ const exists = await (0, import_promises8.stat)(target).catch(() => null);
8404
+ if (exists) {
8405
+ if (overwrite === "skip") return null;
8406
+ if (overwrite === "fail") throw new Error(`Install target already exists: ${target}`);
8407
+ }
8408
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
8409
+ await (0, import_promises8.cp)(source, target, { force: overwrite === "replace" });
8410
+ return target;
8411
+ }
8412
+ async function copyHelperFile(input2) {
8413
+ const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8414
+ const target = safeJoin(input2.helperRoot, input2.file.to ?? (0, import_node_path9.basename)(input2.file.from));
8415
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
8416
+ await (0, import_promises8.cp)(source, target, { force: true });
8417
+ return target;
8418
+ }
8419
+ async function installPackageAssets(options) {
8420
+ const assets = { main: [], helper: [] };
8421
+ const install = options.install;
8422
+ if (!install) return { assets };
8423
+ for (const file of install.mainFiles ?? []) {
8424
+ const copied = await copyMainFile({
8425
+ packageRoot: options.packageRoot,
8426
+ installRoot: options.installRoot,
8427
+ file
8428
+ });
8429
+ if (copied) assets.main.push(copied);
8430
+ }
8431
+ const helperRoot = helperRootFor(options.configRoot, options.packageName, options.version);
8432
+ if ((install.helperFiles ?? []).length > 0) {
8433
+ await (0, import_promises8.rm)(helperRoot, { recursive: true, force: true });
8434
+ }
8435
+ for (const file of install.helperFiles ?? []) {
8436
+ assets.helper.push(await copyHelperFile({ packageRoot: options.packageRoot, helperRoot, file }));
8437
+ }
8438
+ if (!install.postInstall) return { assets };
8439
+ const promptFile = safeJoin(helperRoot, install.postInstall.promptFile);
8440
+ if (!assets.helper.includes(promptFile)) {
8441
+ throw new Error(`Post-install prompt was not installed: ${install.postInstall.promptFile}`);
8442
+ }
8443
+ return {
8444
+ assets,
8445
+ postInstall: {
8446
+ mode: "manual_prompt",
8447
+ status: "pending",
8448
+ promptFile,
8449
+ cleanup: install.postInstall.cleanup ?? "manual"
8450
+ }
8451
+ };
8452
+ }
8453
+ function printPostInstallNotice(name, postInstall) {
8454
+ if (!postInstall || postInstall.mode !== "manual_prompt") return;
8455
+ console.log("");
8456
+ console.log("This package requires AI-assisted setup.");
8457
+ console.log(`Prompt saved to: ${postInstall.promptFile}`);
8458
+ console.log(`Run: aipm show-prompt ${name}`);
8459
+ console.log(`After setup, run: aipm cleanup ${name}`);
8460
+ }
8293
8461
  async function installOnePackage(options) {
8294
8462
  const configRoot = options.configRoot;
8295
8463
  const installRoot = options.installRoot ?? configRoot;
@@ -8336,49 +8504,65 @@ async function installOnePackage(options) {
8336
8504
  options.version,
8337
8505
  options.token
8338
8506
  );
8339
- const skillMarkdown = await unpackTarballToBuffer(tarball, manifest.entry);
8340
- const result = await installSkillPackage({
8341
- projectRoot: installRoot,
8342
- manifest,
8343
- skillMarkdown,
8344
- preferredTools,
8345
- explicitTarget
8346
- });
8347
- const lock = await readLockfile(configRoot) ?? {
8348
- schemaVersion: "0.1",
8349
- packages: {}
8350
- };
8351
- const installed = {};
8352
- for (const tool of result.resolvedTools) {
8353
- const paths = result.installed[tool];
8354
- if (paths) installed[tool] = paths;
8355
- }
8356
- await writeLockfile(
8357
- configRoot,
8358
- upsertLockEntry(lock, options.name, {
8507
+ const packageRoot = await unpackTarballToDirectory(tarball);
8508
+ try {
8509
+ const skillMarkdown = await (0, import_promises8.readFile)(safeJoin(packageRoot, manifest.entry), "utf8");
8510
+ const result = await installSkillPackage({
8511
+ projectRoot: installRoot,
8512
+ manifest,
8513
+ skillMarkdown,
8514
+ preferredTools,
8515
+ explicitTarget
8516
+ });
8517
+ const { assets, postInstall } = await installPackageAssets({
8518
+ configRoot,
8519
+ installRoot,
8520
+ packageRoot,
8521
+ packageName: options.name,
8359
8522
  version: options.version,
8360
- integrity: remoteIntegrity,
8361
- registry: options.registry,
8362
- resolvedTools: result.resolvedTools,
8363
- installed
8364
- })
8365
- );
8366
- console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8523
+ install: manifest.install
8524
+ });
8525
+ const lock = await readLockfile(configRoot) ?? {
8526
+ schemaVersion: "0.1",
8527
+ packages: {}
8528
+ };
8529
+ const installed = {};
8530
+ for (const tool of result.resolvedTools) {
8531
+ const paths = result.installed[tool];
8532
+ if (paths) installed[tool] = paths;
8533
+ }
8534
+ await writeLockfile(
8535
+ configRoot,
8536
+ upsertLockEntry(lock, options.name, {
8537
+ version: options.version,
8538
+ integrity: remoteIntegrity,
8539
+ registry: options.registry,
8540
+ resolvedTools: result.resolvedTools,
8541
+ installed,
8542
+ ...assets.main.length || assets.helper.length ? { installedAssets: assets } : {},
8543
+ ...postInstall ? { postInstall } : {}
8544
+ })
8545
+ );
8546
+ console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8547
+ printPostInstallNotice(options.name, postInstall);
8548
+ } finally {
8549
+ await (0, import_promises8.rm)(packageRoot, { recursive: true, force: true });
8550
+ }
8367
8551
  }
8368
8552
 
8369
8553
  // src/doctor.ts
8370
8554
  var import_node_child_process3 = require("node:child_process");
8371
8555
  var import_promises9 = require("node:fs/promises");
8372
- var import_node_path10 = require("node:path");
8556
+ var import_node_path11 = require("node:path");
8373
8557
  var import_node_util4 = require("node:util");
8374
8558
 
8375
8559
  // src/project-root.ts
8376
8560
  var import_node_os3 = require("node:os");
8377
- var import_node_path9 = require("node:path");
8561
+ var import_node_path10 = require("node:path");
8378
8562
  var import_node_process3 = require("node:process");
8379
8563
  function globalConfigDir(env2 = process.env) {
8380
8564
  const fromEnv = env2.AIPM_HOME?.trim();
8381
- return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".aipm");
8565
+ return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".aipm");
8382
8566
  }
8383
8567
  function resolveConfigRoot(options = {}) {
8384
8568
  return options.global ? globalConfigDir() : (0, import_node_process3.cwd)();
@@ -8409,11 +8593,11 @@ async function commandOutput(command, args) {
8409
8593
  }
8410
8594
  function npmGlobalBin(prefix) {
8411
8595
  if (!prefix) return null;
8412
- return process.platform === "win32" ? prefix : `${prefix}${import_node_path10.sep}bin`;
8596
+ return process.platform === "win32" ? prefix : `${prefix}${import_node_path11.sep}bin`;
8413
8597
  }
8414
8598
  function pathIncludes(pathDir) {
8415
8599
  if (!pathDir) return false;
8416
- return (process.env.PATH ?? "").split(import_node_path10.delimiter).includes(pathDir);
8600
+ return (process.env.PATH ?? "").split(import_node_path11.delimiter).includes(pathDir);
8417
8601
  }
8418
8602
  function shellPathHint(binDir) {
8419
8603
  if (!binDir) return "Run npm prefix -g and add its bin folder to PATH.";
@@ -8466,11 +8650,11 @@ async function runDoctor(options) {
8466
8650
  if (!options.publish) {
8467
8651
  const configLabel = options.global ? "Global config" : "Project config";
8468
8652
  try {
8469
- await (0, import_promises9.access)((0, import_node_path10.join)(configRoot, "aipm.package.json"));
8653
+ await (0, import_promises9.access)((0, import_node_path11.join)(configRoot, "aipm.package.json"));
8470
8654
  checks.push({
8471
8655
  name: configLabel,
8472
8656
  ok: true,
8473
- detail: `${(0, import_node_path10.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
8657
+ detail: `${(0, import_node_path11.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
8474
8658
  });
8475
8659
  } catch {
8476
8660
  checks.push({
@@ -8540,9 +8724,9 @@ async function runDoctor(options) {
8540
8724
 
8541
8725
  // src/version.ts
8542
8726
  var import_node_fs2 = require("node:fs");
8543
- var import_node_path11 = require("node:path");
8727
+ var import_node_path12 = require("node:path");
8544
8728
  function getCliVersion() {
8545
- const pkgPath = (0, import_node_path11.join)(__dirname, "..", "package.json");
8729
+ const pkgPath = (0, import_node_path12.join)(__dirname, "..", "package.json");
8546
8730
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
8547
8731
  return pkg.version;
8548
8732
  }
@@ -8681,7 +8865,7 @@ async function writeStarterFile(path2, content) {
8681
8865
  }
8682
8866
  async function inferEntryFromSource(source, fallback) {
8683
8867
  const sourceInfo = await (0, import_promises10.stat)(source);
8684
- if (sourceInfo.isFile()) return (0, import_node_path12.basename)(source);
8868
+ if (sourceInfo.isFile()) return (0, import_node_path13.basename)(source);
8685
8869
  if (!sourceInfo.isDirectory()) return fallback;
8686
8870
  const entries = await (0, import_promises10.readdir)(source);
8687
8871
  if (entries.includes(fallback)) return fallback;
@@ -8693,7 +8877,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8693
8877
  if (sourceInfo.isDirectory()) {
8694
8878
  const entries = await (0, import_promises10.readdir)(source);
8695
8879
  for (const entry of entries) {
8696
- await (0, import_promises10.cp)((0, import_node_path12.join)(source, entry), (0, import_node_path12.join)(root, entry), {
8880
+ await (0, import_promises10.cp)((0, import_node_path13.join)(source, entry), (0, import_node_path13.join)(root, entry), {
8697
8881
  recursive: true,
8698
8882
  force: false,
8699
8883
  errorOnExist: true
@@ -8702,7 +8886,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8702
8886
  return;
8703
8887
  }
8704
8888
  if (sourceInfo.isFile()) {
8705
- await (0, import_promises10.cp)(source, (0, import_node_path12.join)(root, (0, import_node_path12.basename)(source)), { force: false, errorOnExist: true });
8889
+ await (0, import_promises10.cp)(source, (0, import_node_path13.join)(root, (0, import_node_path13.basename)(source)), { force: false, errorOnExist: true });
8706
8890
  return;
8707
8891
  }
8708
8892
  throw new Error(`Unsupported source path: ${sourceLabel}`);
@@ -8735,6 +8919,73 @@ function packageDashboardUrl(name) {
8735
8919
  if (!name) return DASHBOARD_URL;
8736
8920
  return `${DASHBOARD_URL}/packages/${name.replace(/^@/, "")}`;
8737
8921
  }
8922
+ function helperRootFromTrackedPath(path2) {
8923
+ const parts = path2.split(import_node_path13.sep).filter(Boolean);
8924
+ const helpersIndex = parts.lastIndexOf("helpers");
8925
+ if (helpersIndex === -1 || parts.length <= helpersIndex + 2) return null;
8926
+ const prefix = path2.startsWith(import_node_path13.sep) ? import_node_path13.sep : "";
8927
+ return (0, import_node_path13.join)(prefix, ...parts.slice(0, helpersIndex + 3));
8928
+ }
8929
+ async function showInstalledPrompt(configRoot, packageArg) {
8930
+ const { name } = parsePackageArg(packageArg);
8931
+ const lock = await readLockfile(configRoot);
8932
+ const entry = lock?.packages[name];
8933
+ if (!entry) throw new Error(`Package is not installed: ${name}`);
8934
+ if (!entry.postInstall || entry.postInstall.mode !== "manual_prompt") {
8935
+ throw new Error(`Package has no setup prompt: ${name}`);
8936
+ }
8937
+ if (entry.postInstall.status === "cleaned") {
8938
+ throw new Error(`Setup helper files were cleaned for ${name}. Reinstall the package to restore them.`);
8939
+ }
8940
+ const content = await (0, import_promises10.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
8941
+ throw new Error(`Setup prompt file is missing: ${entry.postInstall?.promptFile}`);
8942
+ });
8943
+ console.log(`Prompt file: ${entry.postInstall.promptFile}`);
8944
+ console.log("");
8945
+ console.log(content.trimEnd());
8946
+ }
8947
+ async function cleanupInstalledHelpers(options) {
8948
+ const { name } = parsePackageArg(options.packageArg);
8949
+ const lock = await readLockfile(options.configRoot);
8950
+ const entry = lock?.packages[name];
8951
+ if (!lock || !entry) throw new Error(`Package is not installed: ${name}`);
8952
+ if (!entry.installedAssets?.helper?.length) {
8953
+ throw new Error(`Package has no helper files to clean: ${name}`);
8954
+ }
8955
+ if (!entry.postInstall) {
8956
+ throw new Error(`Package has no post-install helper state: ${name}`);
8957
+ }
8958
+ if (entry.postInstall.status === "cleaned") {
8959
+ console.log(`Helper files already cleaned for ${name}.`);
8960
+ return;
8961
+ }
8962
+ if (!options.yes) {
8963
+ const confirmed = await promptForConfirmation(`Delete tracked helper files for ${name}?`);
8964
+ if (!confirmed) {
8965
+ console.log("Cleanup cancelled.");
8966
+ return;
8967
+ }
8968
+ }
8969
+ const roots = new Set(
8970
+ entry.installedAssets.helper.map((path2) => helperRootFromTrackedPath(path2)).filter((path2) => Boolean(path2))
8971
+ );
8972
+ if (roots.size > 0) {
8973
+ for (const root of roots) await (0, import_promises10.rm)(root, { recursive: true, force: true });
8974
+ } else {
8975
+ for (const helperPath of entry.installedAssets.helper) await (0, import_promises10.rm)(helperPath, { force: true });
8976
+ }
8977
+ await writeLockfile(options.configRoot, {
8978
+ ...lock,
8979
+ packages: {
8980
+ ...lock.packages,
8981
+ [name]: {
8982
+ ...entry,
8983
+ postInstall: { ...entry.postInstall, status: "cleaned" }
8984
+ }
8985
+ }
8986
+ });
8987
+ console.log(`Cleaned helper files for ${name}.`);
8988
+ }
8738
8989
  async function openUrl(url) {
8739
8990
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
8740
8991
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
@@ -8873,6 +9124,11 @@ async function printPublishPreview(root, json) {
8873
9124
  description: manifest.description,
8874
9125
  targets: manifest.targets,
8875
9126
  entry: manifest.entry,
9127
+ install: manifest.install ? {
9128
+ mainFiles: manifest.install.mainFiles ?? [],
9129
+ helperFiles: manifest.install.helperFiles ?? [],
9130
+ postInstall: manifest.install.postInstall ?? null
9131
+ } : null,
8876
9132
  files: rows.map((row) => ({ path: row.path, size: row.size, changed: row.changed })),
8877
9133
  totalBytes: size,
8878
9134
  warnings: [
@@ -8890,6 +9146,13 @@ async function printPublishPreview(root, json) {
8890
9146
  console.log(`Description: ${preview.description}`);
8891
9147
  console.log(`Targets: ${preview.targets.join(", ")}`);
8892
9148
  console.log(`Entry: ${preview.entry}`);
9149
+ if (preview.install) {
9150
+ console.log(`Main files: ${preview.install.mainFiles.length}`);
9151
+ console.log(`Helper files: ${preview.install.helperFiles.length}`);
9152
+ if (preview.install.postInstall) {
9153
+ console.log(`Post-install: ${preview.install.postInstall.mode} (${preview.install.postInstall.promptFile})`);
9154
+ }
9155
+ }
8893
9156
  console.log(`Total: ${preview.files.length} file${preview.files.length === 1 ? "" : "s"}, ${preview.totalBytes} bytes`);
8894
9157
  for (const file of preview.files) {
8895
9158
  console.log(`${file.changed ? "modified" : "staged"} ${file.path} (${file.size} bytes)`);
@@ -9003,9 +9266,9 @@ function buildStarterQualityMetadata(options) {
9003
9266
  }
9004
9267
  async function initSkill(opts) {
9005
9268
  if (!isValidScopeName(opts.name)) throw new Error("Invalid @scope/name");
9006
- const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path12.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
9269
+ const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path13.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
9007
9270
  const cdTarget = opts.here ? null : opts.dir ?? skillFolderName(opts.name);
9008
- const source = opts.from ? (0, import_node_path12.resolve)(opts.from) : null;
9271
+ const source = opts.from ? (0, import_node_path13.resolve)(opts.from) : null;
9009
9272
  const entry = source ? await inferEntryFromSource(source, opts.entry) : opts.entry;
9010
9273
  const template = parseSkillTemplate(opts.template ?? "blank");
9011
9274
  const manifest = PackageManifestSchema.parse({
@@ -9028,15 +9291,15 @@ async function initSkill(opts) {
9028
9291
  } else {
9029
9292
  await (0, import_promises10.mkdir)(root, { recursive: true });
9030
9293
  }
9031
- await writeStarterFile((0, import_node_path12.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9294
+ await writeStarterFile((0, import_node_path13.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9032
9295
  if (!opts.from) {
9033
9296
  await writeStarterFile(
9034
- (0, import_node_path12.join)(root, entry),
9297
+ (0, import_node_path13.join)(root, entry),
9035
9298
  SKILL_TEMPLATES[template](opts.name)
9036
9299
  );
9037
9300
  }
9038
9301
  await writeStarterFile(
9039
- (0, import_node_path12.join)(root, ".aipmignore"),
9302
+ (0, import_node_path13.join)(root, ".aipmignore"),
9040
9303
  [
9041
9304
  "# Files that should never be published with this skill",
9042
9305
  ".env*",
@@ -9054,7 +9317,7 @@ async function initSkill(opts) {
9054
9317
  ""
9055
9318
  ].join("\n")
9056
9319
  );
9057
- await (0, import_promises10.mkdir)((0, import_node_path12.join)(root, ".aipm"), { recursive: true });
9320
+ await (0, import_promises10.mkdir)((0, import_node_path13.join)(root, ".aipm"), { recursive: true });
9058
9321
  console.log(`Created ${opts.name} skill folder: ${root}`);
9059
9322
  printPublishGuide(
9060
9323
  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.`,
@@ -9253,6 +9516,16 @@ program2.command("install").description("Install all packages from aipm.package.
9253
9516
  });
9254
9517
  }
9255
9518
  });
9519
+ program2.command("show-prompt <package>").description("Show the manual setup prompt installed with a package").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).action(async (pkgArg, opts) => {
9520
+ await showInstalledPrompt(resolveConfigRoot({ global: opts.global }), pkgArg);
9521
+ });
9522
+ program2.command("cleanup <package>").description("Delete temporary helper files installed with a package").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--yes", "Delete helper files without asking for confirmation").action(async (pkgArg, opts) => {
9523
+ await cleanupInstalledHelpers({
9524
+ configRoot: resolveConfigRoot({ global: opts.global }),
9525
+ packageArg: pkgArg,
9526
+ yes: opts.yes
9527
+ });
9528
+ });
9256
9529
  program2.command("skill").description("Create and manage local skill packages").command("init").description("Create aipm.manifest.json and SKILL.md").requiredOption("--name <name>", "Package name, e.g. @org/skill").option("--version <version>", "Initial version", "1.0.0").option("--description <description>", "Skill description", "AIPM skill").option("--targets <targets>", "Comma-separated targets", "cursor").option("--entry <entry>", "Entry file", "SKILL.md").option("--template <template>", "Starter SKILL.md template: blank, code-review, issue-summary, release-notes", "blank").option("--dir <dir>", "Create files in this folder").option("--here", "Create files in the current folder instead of a skill-named folder").option("--from <path>", "Copy an existing AI-tool skill file or folder into the package folder").action(
9257
9530
  async (opts) => initSkill(opts)
9258
9531
  );
@@ -9261,8 +9534,8 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
9261
9534
  publish.help();
9262
9535
  return;
9263
9536
  }
9264
- const abs = (0, import_node_path12.resolve)(dir);
9265
- const manifestRaw = await (0, import_promises10.readFile)((0, import_node_path12.join)(abs, "aipm.manifest.json"), "utf8");
9537
+ const abs = (0, import_node_path13.resolve)(dir);
9538
+ const manifestRaw = await (0, import_promises10.readFile)((0, import_node_path13.join)(abs, "aipm.manifest.json"), "utf8");
9266
9539
  const manifest = PackageManifestSchema.parse(JSON.parse(manifestRaw));
9267
9540
  const tarball = await packDirectory(abs);
9268
9541
  const registry = registryFromEnvOrDefault(opts.registry);