@aipm-registry/cli 0.3.1 → 0.3.4

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
@@ -8046,6 +8106,22 @@ function privateInstallHint(name, status, token) {
8046
8106
  }
8047
8107
  return `Package not found: ${name} (${status}). If @org/pkg is private, run aipm login, or set AIPM_TOKEN for CI.`;
8048
8108
  }
8109
+ function publishTokenHint(name, error) {
8110
+ if (error === "Publish token required") {
8111
+ return [
8112
+ `Publish token required for ${name}.`,
8113
+ `Generate a fresh 5-minute publish token for ${name} from the package dashboard, then retry with AIPM_TOKEN=<token> aipm publish push --yes.`
8114
+ ].join(" ");
8115
+ }
8116
+ if (error === "Invalid publish token") {
8117
+ return [
8118
+ `Publish token was rejected for ${name}.`,
8119
+ `Generate a fresh 5-minute token for this exact package name, make sure aipm.manifest.json still says "${name}", and confirm your account can publish to that reserved package.`,
8120
+ "If you recently renamed the package or generated the token from another package page, create a new token from the matching package dashboard."
8121
+ ].join(" ");
8122
+ }
8123
+ return error;
8124
+ }
8049
8125
  async function assertRegistryReachable(registry) {
8050
8126
  const base = registry.replace(/\/$/, "");
8051
8127
  const res = await registryFetch(`${base}/health`, base);
@@ -8062,7 +8138,7 @@ async function publishPackage(registry, name, tarball, token) {
8062
8138
  const res = await registryFetch(url, base, { method: "POST", body: form, headers });
8063
8139
  if (!res.ok) {
8064
8140
  const err = await res.json().catch(() => ({}));
8065
- throw new Error(err.error ?? `Publish failed: ${res.status}`);
8141
+ throw new Error(err.error ? publishTokenHint(name, err.error) : `Publish failed: ${res.status}`);
8066
8142
  }
8067
8143
  return res.json();
8068
8144
  }
@@ -8155,7 +8231,7 @@ async function recordPackageInstall(registry, name, token) {
8155
8231
  }
8156
8232
 
8157
8233
  // src/auth-store.ts
8158
- var import_promises7 = require("node:fs/promises");
8234
+ var import_promises6 = require("node:fs/promises");
8159
8235
  var import_node_os2 = require("node:os");
8160
8236
  var import_node_path7 = require("node:path");
8161
8237
  var AUTH_FILE = (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".aipm", "auth.json");
@@ -8164,7 +8240,7 @@ function normalizeRegistry(registry) {
8164
8240
  }
8165
8241
  async function readAuthStore() {
8166
8242
  try {
8167
- const raw = await (0, import_promises7.readFile)(AUTH_FILE, "utf8");
8243
+ const raw = await (0, import_promises6.readFile)(AUTH_FILE, "utf8");
8168
8244
  const parsed = JSON.parse(raw);
8169
8245
  return { registries: parsed.registries ?? {} };
8170
8246
  } catch {
@@ -8172,9 +8248,9 @@ async function readAuthStore() {
8172
8248
  }
8173
8249
  }
8174
8250
  async function writeAuthStore(store) {
8175
- await (0, import_promises7.mkdir)((0, import_node_path7.dirname)(AUTH_FILE), { recursive: true });
8176
- await (0, import_promises7.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8177
- await (0, import_promises7.chmod)(AUTH_FILE, 384).catch(() => void 0);
8251
+ await (0, import_promises6.mkdir)((0, import_node_path7.dirname)(AUTH_FILE), { recursive: true });
8252
+ await (0, import_promises6.writeFile)(AUTH_FILE, JSON.stringify(store, null, 2) + "\n", "utf8");
8253
+ await (0, import_promises6.chmod)(AUTH_FILE, 384).catch(() => void 0);
8178
8254
  }
8179
8255
  async function getStoredRegistryAuth(registry) {
8180
8256
  const store = await readAuthStore();
@@ -8204,23 +8280,25 @@ function isAccessTokenFresh(auth, skewMs = 6e4) {
8204
8280
 
8205
8281
  // src/install-one.ts
8206
8282
  init_dist4();
8283
+ var import_promises8 = require("node:fs/promises");
8284
+ var import_node_path9 = require("node:path");
8207
8285
 
8208
8286
  // src/project-files.ts
8209
- var import_promises8 = require("node:fs/promises");
8287
+ var import_promises7 = require("node:fs/promises");
8210
8288
  var import_node_path8 = require("node:path");
8211
8289
  init_dist();
8212
8290
  var PACKAGE_JSON = "aipm.package.json";
8213
8291
  var LOCKFILE = "aipm-lock.json";
8214
8292
  async function readProjectPackageJson(projectRoot) {
8215
8293
  try {
8216
- const raw = await (0, import_promises8.readFile)((0, import_node_path8.join)(projectRoot, PACKAGE_JSON), "utf8");
8294
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(projectRoot, PACKAGE_JSON), "utf8");
8217
8295
  return ProjectPackageJsonSchema.parse(JSON.parse(raw));
8218
8296
  } catch {
8219
8297
  return null;
8220
8298
  }
8221
8299
  }
8222
8300
  async function writeProjectPackageJson(projectRoot, data) {
8223
- await (0, import_promises8.writeFile)(
8301
+ await (0, import_promises7.writeFile)(
8224
8302
  (0, import_node_path8.join)(projectRoot, PACKAGE_JSON),
8225
8303
  JSON.stringify(data, null, 2) + "\n",
8226
8304
  "utf8"
@@ -8228,14 +8306,14 @@ async function writeProjectPackageJson(projectRoot, data) {
8228
8306
  }
8229
8307
  async function readLockfile(projectRoot) {
8230
8308
  try {
8231
- const raw = await (0, import_promises8.readFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), "utf8");
8309
+ const raw = await (0, import_promises7.readFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), "utf8");
8232
8310
  return LockfileSchema.parse(JSON.parse(raw));
8233
8311
  } catch {
8234
8312
  return null;
8235
8313
  }
8236
8314
  }
8237
8315
  async function writeLockfile(projectRoot, data) {
8238
- await (0, import_promises8.writeFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8316
+ await (0, import_promises7.writeFile)((0, import_node_path8.join)(projectRoot, LOCKFILE), JSON.stringify(data, null, 2) + "\n", "utf8");
8239
8317
  }
8240
8318
  function upsertLockEntry(lock, name, entry) {
8241
8319
  return {
@@ -8289,8 +8367,113 @@ async function promptForTool() {
8289
8367
  rl.close();
8290
8368
  }
8291
8369
  }
8370
+ async function promptForConfirmation(question) {
8371
+ const rl = readline.createInterface({ input: import_node_process2.stdin, output: import_node_process2.stdout });
8372
+ try {
8373
+ const answer = await rl.question(`${question} (y/N): `);
8374
+ const normalized = answer.trim().toLowerCase();
8375
+ return normalized === "y" || normalized === "yes";
8376
+ } finally {
8377
+ rl.close();
8378
+ }
8379
+ }
8292
8380
 
8293
8381
  // src/install-one.ts
8382
+ function normalizeRelativePath(path2) {
8383
+ return (0, import_node_path9.normalize)(path2).split(import_node_path9.sep).join("/");
8384
+ }
8385
+ function assertSafeRelativePath(path2) {
8386
+ const rel = normalizeRelativePath(path2.trim());
8387
+ if (!rel || rel === "." || rel.startsWith("/") || rel === ".." || rel.startsWith("../") || rel.includes("/../")) {
8388
+ throw new Error(`Unsafe install path: ${path2}`);
8389
+ }
8390
+ return rel;
8391
+ }
8392
+ function safeJoin(root, relPath) {
8393
+ const rel = assertSafeRelativePath(relPath);
8394
+ const target = (0, import_node_path9.resolve)(root, rel);
8395
+ const fromRoot = normalizeRelativePath((0, import_node_path9.relative)(root, target));
8396
+ if (fromRoot === ".." || fromRoot.startsWith("../") || fromRoot.startsWith("/")) {
8397
+ throw new Error(`Unsafe install path: ${relPath}`);
8398
+ }
8399
+ return target;
8400
+ }
8401
+ function packageHelperSlug(name) {
8402
+ const [scope = "", pkg = ""] = name.replace(/^@/, "").split("/");
8403
+ return `${scope}__${pkg}`;
8404
+ }
8405
+ function helperRootFor(configRoot, packageName, version) {
8406
+ const base = (0, import_node_path9.basename)(configRoot) === ".aipm" ? (0, import_node_path9.join)(configRoot, "helpers") : (0, import_node_path9.join)(configRoot, ".aipm", "helpers");
8407
+ return (0, import_node_path9.join)(base, packageHelperSlug(packageName), version);
8408
+ }
8409
+ async function assertSourceFile(packageRoot, relPath) {
8410
+ const source = safeJoin(packageRoot, relPath);
8411
+ const info = await (0, import_promises8.stat)(source).catch(() => null);
8412
+ if (!info?.isFile()) throw new Error(`Package install file not found: ${relPath}`);
8413
+ return source;
8414
+ }
8415
+ async function copyMainFile(input2) {
8416
+ const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8417
+ const target = safeJoin(input2.installRoot, input2.file.to);
8418
+ const overwrite = input2.file.overwrite ?? "fail";
8419
+ const exists = await (0, import_promises8.stat)(target).catch(() => null);
8420
+ if (exists) {
8421
+ if (overwrite === "skip") return null;
8422
+ if (overwrite === "fail") throw new Error(`Install target already exists: ${target}`);
8423
+ }
8424
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
8425
+ await (0, import_promises8.cp)(source, target, { force: overwrite === "replace" });
8426
+ return target;
8427
+ }
8428
+ async function copyHelperFile(input2) {
8429
+ const source = await assertSourceFile(input2.packageRoot, input2.file.from);
8430
+ const target = safeJoin(input2.helperRoot, input2.file.to ?? (0, import_node_path9.basename)(input2.file.from));
8431
+ await (0, import_promises8.mkdir)((0, import_node_path9.dirname)(target), { recursive: true });
8432
+ await (0, import_promises8.cp)(source, target, { force: true });
8433
+ return target;
8434
+ }
8435
+ async function installPackageAssets(options) {
8436
+ const assets = { main: [], helper: [] };
8437
+ const install = options.install;
8438
+ if (!install) return { assets };
8439
+ for (const file of install.mainFiles ?? []) {
8440
+ const copied = await copyMainFile({
8441
+ packageRoot: options.packageRoot,
8442
+ installRoot: options.installRoot,
8443
+ file
8444
+ });
8445
+ if (copied) assets.main.push(copied);
8446
+ }
8447
+ const helperRoot = helperRootFor(options.configRoot, options.packageName, options.version);
8448
+ if ((install.helperFiles ?? []).length > 0) {
8449
+ await (0, import_promises8.rm)(helperRoot, { recursive: true, force: true });
8450
+ }
8451
+ for (const file of install.helperFiles ?? []) {
8452
+ assets.helper.push(await copyHelperFile({ packageRoot: options.packageRoot, helperRoot, file }));
8453
+ }
8454
+ if (!install.postInstall) return { assets };
8455
+ const promptFile = safeJoin(helperRoot, install.postInstall.promptFile);
8456
+ if (!assets.helper.includes(promptFile)) {
8457
+ throw new Error(`Post-install prompt was not installed: ${install.postInstall.promptFile}`);
8458
+ }
8459
+ return {
8460
+ assets,
8461
+ postInstall: {
8462
+ mode: "manual_prompt",
8463
+ status: "pending",
8464
+ promptFile,
8465
+ cleanup: install.postInstall.cleanup ?? "manual"
8466
+ }
8467
+ };
8468
+ }
8469
+ function printPostInstallNotice(name, postInstall) {
8470
+ if (!postInstall || postInstall.mode !== "manual_prompt") return;
8471
+ console.log("");
8472
+ console.log("This package requires AI-assisted setup.");
8473
+ console.log(`Prompt saved to: ${postInstall.promptFile}`);
8474
+ console.log(`Run: aipm show-prompt ${name}`);
8475
+ console.log(`After setup, run: aipm cleanup ${name}`);
8476
+ }
8294
8477
  async function installOnePackage(options) {
8295
8478
  const configRoot = options.configRoot;
8296
8479
  const installRoot = options.installRoot ?? configRoot;
@@ -8337,49 +8520,65 @@ async function installOnePackage(options) {
8337
8520
  options.version,
8338
8521
  options.token
8339
8522
  );
8340
- const skillMarkdown = await unpackTarballToBuffer(tarball, manifest.entry);
8341
- const result = await installSkillPackage({
8342
- projectRoot: installRoot,
8343
- manifest,
8344
- skillMarkdown,
8345
- preferredTools,
8346
- explicitTarget
8347
- });
8348
- const lock = await readLockfile(configRoot) ?? {
8349
- schemaVersion: "0.1",
8350
- packages: {}
8351
- };
8352
- const installed = {};
8353
- for (const tool of result.resolvedTools) {
8354
- const paths = result.installed[tool];
8355
- if (paths) installed[tool] = paths;
8356
- }
8357
- await writeLockfile(
8358
- configRoot,
8359
- upsertLockEntry(lock, options.name, {
8523
+ const packageRoot = await unpackTarballToDirectory(tarball);
8524
+ try {
8525
+ const skillMarkdown = await (0, import_promises8.readFile)(safeJoin(packageRoot, manifest.entry), "utf8");
8526
+ const result = await installSkillPackage({
8527
+ projectRoot: installRoot,
8528
+ manifest,
8529
+ skillMarkdown,
8530
+ preferredTools,
8531
+ explicitTarget
8532
+ });
8533
+ const { assets, postInstall } = await installPackageAssets({
8534
+ configRoot,
8535
+ installRoot,
8536
+ packageRoot,
8537
+ packageName: options.name,
8360
8538
  version: options.version,
8361
- integrity: remoteIntegrity,
8362
- registry: options.registry,
8363
- resolvedTools: result.resolvedTools,
8364
- installed
8365
- })
8366
- );
8367
- console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8539
+ install: manifest.install
8540
+ });
8541
+ const lock = await readLockfile(configRoot) ?? {
8542
+ schemaVersion: "0.1",
8543
+ packages: {}
8544
+ };
8545
+ const installed = {};
8546
+ for (const tool of result.resolvedTools) {
8547
+ const paths = result.installed[tool];
8548
+ if (paths) installed[tool] = paths;
8549
+ }
8550
+ await writeLockfile(
8551
+ configRoot,
8552
+ upsertLockEntry(lock, options.name, {
8553
+ version: options.version,
8554
+ integrity: remoteIntegrity,
8555
+ registry: options.registry,
8556
+ resolvedTools: result.resolvedTools,
8557
+ installed,
8558
+ ...assets.main.length || assets.helper.length ? { installedAssets: assets } : {},
8559
+ ...postInstall ? { postInstall } : {}
8560
+ })
8561
+ );
8562
+ console.log(`Installed ${options.name}@${options.version} \u2192 ${result.resolvedTools.join(", ")}`);
8563
+ printPostInstallNotice(options.name, postInstall);
8564
+ } finally {
8565
+ await (0, import_promises8.rm)(packageRoot, { recursive: true, force: true });
8566
+ }
8368
8567
  }
8369
8568
 
8370
8569
  // src/doctor.ts
8371
8570
  var import_node_child_process3 = require("node:child_process");
8372
8571
  var import_promises9 = require("node:fs/promises");
8373
- var import_node_path10 = require("node:path");
8572
+ var import_node_path11 = require("node:path");
8374
8573
  var import_node_util4 = require("node:util");
8375
8574
 
8376
8575
  // src/project-root.ts
8377
8576
  var import_node_os3 = require("node:os");
8378
- var import_node_path9 = require("node:path");
8577
+ var import_node_path10 = require("node:path");
8379
8578
  var import_node_process3 = require("node:process");
8380
8579
  function globalConfigDir(env2 = process.env) {
8381
8580
  const fromEnv = env2.AIPM_HOME?.trim();
8382
- return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".aipm");
8581
+ return fromEnv ? fromEnv.replace(/\/$/, "") : (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".aipm");
8383
8582
  }
8384
8583
  function resolveConfigRoot(options = {}) {
8385
8584
  return options.global ? globalConfigDir() : (0, import_node_process3.cwd)();
@@ -8410,11 +8609,11 @@ async function commandOutput(command, args) {
8410
8609
  }
8411
8610
  function npmGlobalBin(prefix) {
8412
8611
  if (!prefix) return null;
8413
- return process.platform === "win32" ? prefix : `${prefix}${import_node_path10.sep}bin`;
8612
+ return process.platform === "win32" ? prefix : `${prefix}${import_node_path11.sep}bin`;
8414
8613
  }
8415
8614
  function pathIncludes(pathDir) {
8416
8615
  if (!pathDir) return false;
8417
- return (process.env.PATH ?? "").split(import_node_path10.delimiter).includes(pathDir);
8616
+ return (process.env.PATH ?? "").split(import_node_path11.delimiter).includes(pathDir);
8418
8617
  }
8419
8618
  function shellPathHint(binDir) {
8420
8619
  if (!binDir) return "Run npm prefix -g and add its bin folder to PATH.";
@@ -8467,11 +8666,11 @@ async function runDoctor(options) {
8467
8666
  if (!options.publish) {
8468
8667
  const configLabel = options.global ? "Global config" : "Project config";
8469
8668
  try {
8470
- await (0, import_promises9.access)((0, import_node_path10.join)(configRoot, "aipm.package.json"));
8669
+ await (0, import_promises9.access)((0, import_node_path11.join)(configRoot, "aipm.package.json"));
8471
8670
  checks.push({
8472
8671
  name: configLabel,
8473
8672
  ok: true,
8474
- detail: `${(0, import_node_path10.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
8673
+ detail: `${(0, import_node_path11.join)(configRoot, "aipm.package.json")} exists (${scopeLabel(scope)}).`
8475
8674
  });
8476
8675
  } catch {
8477
8676
  checks.push({
@@ -8541,9 +8740,9 @@ async function runDoctor(options) {
8541
8740
 
8542
8741
  // src/version.ts
8543
8742
  var import_node_fs2 = require("node:fs");
8544
- var import_node_path11 = require("node:path");
8743
+ var import_node_path12 = require("node:path");
8545
8744
  function getCliVersion() {
8546
- const pkgPath = (0, import_node_path11.join)(__dirname, "..", "package.json");
8745
+ const pkgPath = (0, import_node_path12.join)(__dirname, "..", "package.json");
8547
8746
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
8548
8747
  return pkg.version;
8549
8748
  }
@@ -8682,7 +8881,7 @@ async function writeStarterFile(path2, content) {
8682
8881
  }
8683
8882
  async function inferEntryFromSource(source, fallback) {
8684
8883
  const sourceInfo = await (0, import_promises10.stat)(source);
8685
- if (sourceInfo.isFile()) return (0, import_node_path12.basename)(source);
8884
+ if (sourceInfo.isFile()) return (0, import_node_path13.basename)(source);
8686
8885
  if (!sourceInfo.isDirectory()) return fallback;
8687
8886
  const entries = await (0, import_promises10.readdir)(source);
8688
8887
  if (entries.includes(fallback)) return fallback;
@@ -8694,7 +8893,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8694
8893
  if (sourceInfo.isDirectory()) {
8695
8894
  const entries = await (0, import_promises10.readdir)(source);
8696
8895
  for (const entry of entries) {
8697
- await (0, import_promises10.cp)((0, import_node_path12.join)(source, entry), (0, import_node_path12.join)(root, entry), {
8896
+ await (0, import_promises10.cp)((0, import_node_path13.join)(source, entry), (0, import_node_path13.join)(root, entry), {
8698
8897
  recursive: true,
8699
8898
  force: false,
8700
8899
  errorOnExist: true
@@ -8703,7 +8902,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8703
8902
  return;
8704
8903
  }
8705
8904
  if (sourceInfo.isFile()) {
8706
- await (0, import_promises10.cp)(source, (0, import_node_path12.join)(root, (0, import_node_path12.basename)(source)), { force: false, errorOnExist: true });
8905
+ await (0, import_promises10.cp)(source, (0, import_node_path13.join)(root, (0, import_node_path13.basename)(source)), { force: false, errorOnExist: true });
8707
8906
  return;
8708
8907
  }
8709
8908
  throw new Error(`Unsupported source path: ${sourceLabel}`);
@@ -8736,6 +8935,73 @@ function packageDashboardUrl(name) {
8736
8935
  if (!name) return DASHBOARD_URL;
8737
8936
  return `${DASHBOARD_URL}/packages/${name.replace(/^@/, "")}`;
8738
8937
  }
8938
+ function helperRootFromTrackedPath(path2) {
8939
+ const parts = path2.split(import_node_path13.sep).filter(Boolean);
8940
+ const helpersIndex = parts.lastIndexOf("helpers");
8941
+ if (helpersIndex === -1 || parts.length <= helpersIndex + 2) return null;
8942
+ const prefix = path2.startsWith(import_node_path13.sep) ? import_node_path13.sep : "";
8943
+ return (0, import_node_path13.join)(prefix, ...parts.slice(0, helpersIndex + 3));
8944
+ }
8945
+ async function showInstalledPrompt(configRoot, packageArg) {
8946
+ const { name } = parsePackageArg(packageArg);
8947
+ const lock = await readLockfile(configRoot);
8948
+ const entry = lock?.packages[name];
8949
+ if (!entry) throw new Error(`Package is not installed: ${name}`);
8950
+ if (!entry.postInstall || entry.postInstall.mode !== "manual_prompt") {
8951
+ throw new Error(`Package has no setup prompt: ${name}`);
8952
+ }
8953
+ if (entry.postInstall.status === "cleaned") {
8954
+ throw new Error(`Setup helper files were cleaned for ${name}. Reinstall the package to restore them.`);
8955
+ }
8956
+ const content = await (0, import_promises10.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
8957
+ throw new Error(`Setup prompt file is missing: ${entry.postInstall?.promptFile}`);
8958
+ });
8959
+ console.log(`Prompt file: ${entry.postInstall.promptFile}`);
8960
+ console.log("");
8961
+ console.log(content.trimEnd());
8962
+ }
8963
+ async function cleanupInstalledHelpers(options) {
8964
+ const { name } = parsePackageArg(options.packageArg);
8965
+ const lock = await readLockfile(options.configRoot);
8966
+ const entry = lock?.packages[name];
8967
+ if (!lock || !entry) throw new Error(`Package is not installed: ${name}`);
8968
+ if (!entry.installedAssets?.helper?.length) {
8969
+ throw new Error(`Package has no helper files to clean: ${name}`);
8970
+ }
8971
+ if (!entry.postInstall) {
8972
+ throw new Error(`Package has no post-install helper state: ${name}`);
8973
+ }
8974
+ if (entry.postInstall.status === "cleaned") {
8975
+ console.log(`Helper files already cleaned for ${name}.`);
8976
+ return;
8977
+ }
8978
+ if (!options.yes) {
8979
+ const confirmed = await promptForConfirmation(`Delete tracked helper files for ${name}?`);
8980
+ if (!confirmed) {
8981
+ console.log("Cleanup cancelled.");
8982
+ return;
8983
+ }
8984
+ }
8985
+ const roots = new Set(
8986
+ entry.installedAssets.helper.map((path2) => helperRootFromTrackedPath(path2)).filter((path2) => Boolean(path2))
8987
+ );
8988
+ if (roots.size > 0) {
8989
+ for (const root of roots) await (0, import_promises10.rm)(root, { recursive: true, force: true });
8990
+ } else {
8991
+ for (const helperPath of entry.installedAssets.helper) await (0, import_promises10.rm)(helperPath, { force: true });
8992
+ }
8993
+ await writeLockfile(options.configRoot, {
8994
+ ...lock,
8995
+ packages: {
8996
+ ...lock.packages,
8997
+ [name]: {
8998
+ ...entry,
8999
+ postInstall: { ...entry.postInstall, status: "cleaned" }
9000
+ }
9001
+ }
9002
+ });
9003
+ console.log(`Cleaned helper files for ${name}.`);
9004
+ }
8739
9005
  async function openUrl(url) {
8740
9006
  const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
8741
9007
  const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
@@ -8874,6 +9140,11 @@ async function printPublishPreview(root, json) {
8874
9140
  description: manifest.description,
8875
9141
  targets: manifest.targets,
8876
9142
  entry: manifest.entry,
9143
+ install: manifest.install ? {
9144
+ mainFiles: manifest.install.mainFiles ?? [],
9145
+ helperFiles: manifest.install.helperFiles ?? [],
9146
+ postInstall: manifest.install.postInstall ?? null
9147
+ } : null,
8877
9148
  files: rows.map((row) => ({ path: row.path, size: row.size, changed: row.changed })),
8878
9149
  totalBytes: size,
8879
9150
  warnings: [
@@ -8891,6 +9162,13 @@ async function printPublishPreview(root, json) {
8891
9162
  console.log(`Description: ${preview.description}`);
8892
9163
  console.log(`Targets: ${preview.targets.join(", ")}`);
8893
9164
  console.log(`Entry: ${preview.entry}`);
9165
+ if (preview.install) {
9166
+ console.log(`Main files: ${preview.install.mainFiles.length}`);
9167
+ console.log(`Helper files: ${preview.install.helperFiles.length}`);
9168
+ if (preview.install.postInstall) {
9169
+ console.log(`Post-install: ${preview.install.postInstall.mode} (${preview.install.postInstall.promptFile})`);
9170
+ }
9171
+ }
8894
9172
  console.log(`Total: ${preview.files.length} file${preview.files.length === 1 ? "" : "s"}, ${preview.totalBytes} bytes`);
8895
9173
  for (const file of preview.files) {
8896
9174
  console.log(`${file.changed ? "modified" : "staged"} ${file.path} (${file.size} bytes)`);
@@ -9004,9 +9282,9 @@ function buildStarterQualityMetadata(options) {
9004
9282
  }
9005
9283
  async function initSkill(opts) {
9006
9284
  if (!isValidScopeName(opts.name)) throw new Error("Invalid @scope/name");
9007
- const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path12.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
9285
+ const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path13.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
9008
9286
  const cdTarget = opts.here ? null : opts.dir ?? skillFolderName(opts.name);
9009
- const source = opts.from ? (0, import_node_path12.resolve)(opts.from) : null;
9287
+ const source = opts.from ? (0, import_node_path13.resolve)(opts.from) : null;
9010
9288
  const entry = source ? await inferEntryFromSource(source, opts.entry) : opts.entry;
9011
9289
  const template = parseSkillTemplate(opts.template ?? "blank");
9012
9290
  const manifest = PackageManifestSchema.parse({
@@ -9029,15 +9307,15 @@ async function initSkill(opts) {
9029
9307
  } else {
9030
9308
  await (0, import_promises10.mkdir)(root, { recursive: true });
9031
9309
  }
9032
- await writeStarterFile((0, import_node_path12.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9310
+ await writeStarterFile((0, import_node_path13.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9033
9311
  if (!opts.from) {
9034
9312
  await writeStarterFile(
9035
- (0, import_node_path12.join)(root, entry),
9313
+ (0, import_node_path13.join)(root, entry),
9036
9314
  SKILL_TEMPLATES[template](opts.name)
9037
9315
  );
9038
9316
  }
9039
9317
  await writeStarterFile(
9040
- (0, import_node_path12.join)(root, ".aipmignore"),
9318
+ (0, import_node_path13.join)(root, ".aipmignore"),
9041
9319
  [
9042
9320
  "# Files that should never be published with this skill",
9043
9321
  ".env*",
@@ -9055,7 +9333,7 @@ async function initSkill(opts) {
9055
9333
  ""
9056
9334
  ].join("\n")
9057
9335
  );
9058
- await (0, import_promises10.mkdir)((0, import_node_path12.join)(root, ".aipm"), { recursive: true });
9336
+ await (0, import_promises10.mkdir)((0, import_node_path13.join)(root, ".aipm"), { recursive: true });
9059
9337
  console.log(`Created ${opts.name} skill folder: ${root}`);
9060
9338
  printPublishGuide(
9061
9339
  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.`,
@@ -9254,6 +9532,16 @@ program2.command("install").description("Install all packages from aipm.package.
9254
9532
  });
9255
9533
  }
9256
9534
  });
9535
+ 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) => {
9536
+ await showInstalledPrompt(resolveConfigRoot({ global: opts.global }), pkgArg);
9537
+ });
9538
+ 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) => {
9539
+ await cleanupInstalledHelpers({
9540
+ configRoot: resolveConfigRoot({ global: opts.global }),
9541
+ packageArg: pkgArg,
9542
+ yes: opts.yes
9543
+ });
9544
+ });
9257
9545
  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(
9258
9546
  async (opts) => initSkill(opts)
9259
9547
  );
@@ -9262,8 +9550,8 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
9262
9550
  publish.help();
9263
9551
  return;
9264
9552
  }
9265
- const abs = (0, import_node_path12.resolve)(dir);
9266
- const manifestRaw = await (0, import_promises10.readFile)((0, import_node_path12.join)(abs, "aipm.manifest.json"), "utf8");
9553
+ const abs = (0, import_node_path13.resolve)(dir);
9554
+ const manifestRaw = await (0, import_promises10.readFile)((0, import_node_path13.join)(abs, "aipm.manifest.json"), "utf8");
9267
9555
  const manifest = PackageManifestSchema.parse(JSON.parse(manifestRaw));
9268
9556
  const tarball = await packDirectory(abs);
9269
9557
  const registry = registryFromEnvOrDefault(opts.registry);
@@ -9376,17 +9664,21 @@ publish.command("validate").description("Validate staged files before publish").
9376
9664
  );
9377
9665
  });
9378
9666
  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));
9379
- 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").action(async (opts) => {
9667
+ 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) => {
9380
9668
  const root = (0, import_node_process5.cwd)();
9381
9669
  const { manifest } = await validatePublishState(root);
9382
9670
  const registry = registryFromEnvOrDefault(opts.registry);
9383
9671
  const tarball = await packStagedFiles(root);
9384
9672
  const staged = await statusPublishState(root);
9673
+ const verbose = Boolean(opts.verbose || program2.opts().verbose);
9385
9674
  if (!opts.yes) {
9386
9675
  console.log("Reminder: this publishes an immutable public package version.");
9387
9676
  console.log("Use --yes to skip this reminder in automation.");
9388
9677
  }
9389
9678
  console.log(`Uploading ${staged.length} file${staged.length === 1 ? "" : "s"} to ${registry}.`);
9679
+ if (verbose) {
9680
+ console.log(`Verbose: package ${manifest.name}@${manifest.version}; tarball ${tarball.length} bytes.`);
9681
+ }
9390
9682
  for (const entry of staged) console.log(` ${entry.path}`);
9391
9683
  const result = await publishPackage(registry, manifest.name, tarball, opts.token ?? import_node_process5.env.AIPM_TOKEN);
9392
9684
  console.log(`Published ${manifest.name}@${result.version}`);