@aipm-registry/cli 0.3.4 → 0.4.0

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
@@ -4237,7 +4237,7 @@ var init_manifest = __esm({
4237
4237
  });
4238
4238
 
4239
4239
  // ../../packages/schemas/dist/package-json.js
4240
- var scopedPackageKey, ProjectPackageJsonSchema;
4240
+ var scopedPackageKey, promptAliasKey, ProjectPackageJsonSchema;
4241
4241
  var init_package_json = __esm({
4242
4242
  "../../packages/schemas/dist/package-json.js"() {
4243
4243
  "use strict";
@@ -4245,17 +4245,19 @@ var init_package_json = __esm({
4245
4245
  init_scope_name();
4246
4246
  init_manifest();
4247
4247
  scopedPackageKey = external_exports.string().regex(SCOPE_NAME_REGEX);
4248
+ promptAliasKey = external_exports.string().trim().min(1).max(120);
4248
4249
  ProjectPackageJsonSchema = external_exports.object({
4249
4250
  schemaVersion: external_exports.literal("0.1"),
4250
4251
  registry: external_exports.string().url(),
4251
4252
  preferredTools: external_exports.array(AiToolSchema).optional(),
4252
- packages: external_exports.record(scopedPackageKey, external_exports.string().min(1))
4253
+ packages: external_exports.record(scopedPackageKey, external_exports.string().min(1)),
4254
+ prompts: external_exports.record(promptAliasKey, external_exports.string().url()).default({})
4253
4255
  });
4254
4256
  }
4255
4257
  });
4256
4258
 
4257
4259
  // ../../packages/schemas/dist/lockfile.js
4258
- var scopedPackageKey2, InstalledPathsSchema, LockfilePackageEntrySchema, LockfileSchema;
4260
+ var scopedPackageKey2, InstalledPathsSchema, LockfilePromptEntrySchema, LockfilePackageEntrySchema, LockfileSchema;
4259
4261
  var init_lockfile = __esm({
4260
4262
  "../../packages/schemas/dist/lockfile.js"() {
4261
4263
  "use strict";
@@ -4264,6 +4266,15 @@ var init_lockfile = __esm({
4264
4266
  init_manifest();
4265
4267
  scopedPackageKey2 = external_exports.string().regex(SCOPE_NAME_REGEX);
4266
4268
  InstalledPathsSchema = external_exports.record(AiToolSchema, external_exports.array(external_exports.string()));
4269
+ LockfilePromptEntrySchema = external_exports.object({
4270
+ id: external_exports.string().min(1),
4271
+ url: external_exports.string().url(),
4272
+ publisher: external_exports.string().min(1),
4273
+ slug: external_exports.string().min(1),
4274
+ contentHash: external_exports.string().min(1),
4275
+ updatedAt: external_exports.string().min(1),
4276
+ installedPath: external_exports.string().min(1)
4277
+ });
4267
4278
  LockfilePackageEntrySchema = external_exports.object({
4268
4279
  version: external_exports.string(),
4269
4280
  integrity: external_exports.string(),
@@ -4283,7 +4294,8 @@ var init_lockfile = __esm({
4283
4294
  });
4284
4295
  LockfileSchema = external_exports.object({
4285
4296
  schemaVersion: external_exports.literal("0.1"),
4286
- packages: external_exports.record(scopedPackageKey2, LockfilePackageEntrySchema)
4297
+ packages: external_exports.record(scopedPackageKey2, LockfilePackageEntrySchema),
4298
+ prompts: external_exports.record(external_exports.string().min(1), LockfilePromptEntrySchema).default({})
4287
4299
  });
4288
4300
  }
4289
4301
  });
@@ -7826,11 +7838,11 @@ function useColor() {
7826
7838
  var program = new Command();
7827
7839
 
7828
7840
  // src/bin.ts
7829
- var import_node_crypto2 = require("node:crypto");
7841
+ var import_node_crypto3 = require("node:crypto");
7830
7842
  var import_node_child_process4 = require("node:child_process");
7831
7843
  var import_node_http = require("node:http");
7832
- var import_promises10 = require("node:fs/promises");
7833
- var import_node_path13 = require("node:path");
7844
+ var import_promises11 = require("node:fs/promises");
7845
+ var import_node_path14 = require("node:path");
7834
7846
  var import_node_process5 = require("node:process");
7835
7847
  init_dist();
7836
7848
  init_dist4();
@@ -8065,12 +8077,12 @@ async function packStagedFiles(root) {
8065
8077
  }
8066
8078
  }
8067
8079
  async function unpackTarballToDirectory(tarball) {
8068
- const { mkdtemp: mkdtemp2, writeFile: writeFile7 } = await import("node:fs/promises");
8069
- const { join: join13 } = await import("node:path");
8080
+ const { mkdtemp: mkdtemp2, writeFile: writeFile8 } = await import("node:fs/promises");
8081
+ const { join: join14 } = await import("node:path");
8070
8082
  const { tmpdir: tmpdir2 } = await import("node:os");
8071
- const tempDir = await mkdtemp2(join13(tmpdir2(), "aipm-install-"));
8072
- const tgzPath = join13(tempDir, "pkg.tgz");
8073
- await writeFile7(tgzPath, tarball);
8083
+ const tempDir = await mkdtemp2(join14(tmpdir2(), "aipm-install-"));
8084
+ const tgzPath = join14(tempDir, "pkg.tgz");
8085
+ await writeFile8(tgzPath, tarball);
8074
8086
  await execFileAsync("tar", ["-xzf", tgzPath, "-C", tempDir]);
8075
8087
  return tempDir;
8076
8088
  }
@@ -8229,6 +8241,41 @@ async function recordPackageInstall(registry, name, token) {
8229
8241
  }
8230
8242
  return res.json();
8231
8243
  }
8244
+ async function fetchPromptDetail(registry, publisher, slug) {
8245
+ const base = registry.replace(/\/$/, "");
8246
+ const url = `${base}/v1/prompts/${encodeURIComponent(publisher)}/${encodeURIComponent(slug)}`;
8247
+ const res = await registryFetch(url, base);
8248
+ if (!res.ok) throw new Error(`Prompt not found: @${publisher}/${slug} (${res.status})`);
8249
+ return res.json();
8250
+ }
8251
+ async function recordPromptCopy(registry, publisher, slug) {
8252
+ const base = registry.replace(/\/$/, "");
8253
+ const url = `${base}/v1/prompts/${encodeURIComponent(publisher)}/${encodeURIComponent(slug)}/copy`;
8254
+ const res = await registryFetch(url, base, { method: "POST" });
8255
+ if (!res.ok) throw new Error(`Prompt copy recording failed: ${res.status}`);
8256
+ }
8257
+ async function publishPrompt(registry, input2, accessToken, sampleImage) {
8258
+ const base = registry.replace(/\/$/, "");
8259
+ const form = new FormData();
8260
+ form.append("data", JSON.stringify(input2));
8261
+ if (sampleImage) {
8262
+ form.append(
8263
+ "sampleImage",
8264
+ new Blob([sampleImage.data], { type: sampleImage.contentType }),
8265
+ sampleImage.filename
8266
+ );
8267
+ }
8268
+ const res = await registryFetch(`${base}/v1/prompts`, base, {
8269
+ method: "POST",
8270
+ headers: registryAuthHeaders(accessToken),
8271
+ body: form
8272
+ });
8273
+ if (!res.ok) {
8274
+ const error = await res.json().catch(() => ({}));
8275
+ throw new Error(error.error ?? `Prompt publish failed: ${res.status}`);
8276
+ }
8277
+ return res.json();
8278
+ }
8232
8279
 
8233
8280
  // src/auth-store.ts
8234
8281
  var import_promises6 = require("node:fs/promises");
@@ -8321,6 +8368,12 @@ function upsertLockEntry(lock, name, entry) {
8321
8368
  packages: { ...lock.packages, [name]: entry }
8322
8369
  };
8323
8370
  }
8371
+ function upsertPromptLockEntry(lock, name, entry) {
8372
+ return {
8373
+ ...lock,
8374
+ prompts: { ...lock.prompts, [name]: entry }
8375
+ };
8376
+ }
8324
8377
  function resolveRegistryUrl(project, flag, fallback) {
8325
8378
  const fromEnv = process.env.AIPM_REGISTRY_URL ?? process.env.AIPM_REGISTRY;
8326
8379
  if (flag) return flag.replace(/\/$/, "");
@@ -8540,7 +8593,8 @@ async function installOnePackage(options) {
8540
8593
  });
8541
8594
  const lock = await readLockfile(configRoot) ?? {
8542
8595
  schemaVersion: "0.1",
8543
- packages: {}
8596
+ packages: {},
8597
+ prompts: {}
8544
8598
  };
8545
8599
  const installed = {};
8546
8600
  for (const tool of result.resolvedTools) {
@@ -8738,11 +8792,181 @@ async function runDoctor(options) {
8738
8792
  }
8739
8793
  }
8740
8794
 
8795
+ // src/prompt-install.ts
8796
+ var import_node_crypto2 = require("node:crypto");
8797
+ var import_promises10 = require("node:fs/promises");
8798
+ var import_node_path12 = require("node:path");
8799
+ var PROMPT_SITE_ORIGIN = "https://www.aipm-registry.com";
8800
+ function parsePromptUrl(value) {
8801
+ let url;
8802
+ try {
8803
+ url = new URL(value);
8804
+ } catch {
8805
+ return null;
8806
+ }
8807
+ if (url.origin !== PROMPT_SITE_ORIGIN) return null;
8808
+ const parts = url.pathname.split("/").filter(Boolean);
8809
+ if (parts.length !== 3 || parts[0] !== "prompts") return null;
8810
+ const publisher = decodeURIComponent(parts[1] ?? "").trim().toLowerCase();
8811
+ const slug = decodeURIComponent(parts[2] ?? "").trim().toLowerCase();
8812
+ if (!publisher || !slug || !/^[a-z0-9][a-z0-9-]*$/.test(publisher) || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
8813
+ return null;
8814
+ }
8815
+ return {
8816
+ url: `${PROMPT_SITE_ORIGIN}/prompts/${encodeURIComponent(publisher)}/${encodeURIComponent(slug)}`,
8817
+ publisher,
8818
+ slug,
8819
+ alias: slug
8820
+ };
8821
+ }
8822
+ function promptDirectory(configRoot) {
8823
+ return (0, import_node_path12.basename)(configRoot) === ".aipm" ? (0, import_node_path12.join)(configRoot, "prompts") : (0, import_node_path12.join)(configRoot, ".aipm", "prompts");
8824
+ }
8825
+ function promptSnapshotPath(configRoot, reference) {
8826
+ return (0, import_node_path12.join)(promptDirectory(configRoot), `${reference.publisher}--${reference.slug}.md`);
8827
+ }
8828
+ function canonicalPromptContent(prompt) {
8829
+ return JSON.stringify({
8830
+ promptText: prompt.promptText,
8831
+ variables: prompt.variables,
8832
+ exampleInput: prompt.exampleInput,
8833
+ exampleOutput: prompt.exampleOutput,
8834
+ usageNotes: prompt.usageNotes,
8835
+ license: prompt.license
8836
+ });
8837
+ }
8838
+ function promptContentHash(prompt) {
8839
+ return (0, import_node_crypto2.createHash)("sha256").update(canonicalPromptContent(prompt)).digest("hex");
8840
+ }
8841
+ function section(title, value) {
8842
+ return value?.trim() ? `
8843
+ ## ${title}
8844
+
8845
+ ${value.trim()}
8846
+ ` : "";
8847
+ }
8848
+ function renderPromptMarkdown(prompt, sourceUrl) {
8849
+ const variables = prompt.variables.length ? `
8850
+ ## Variables
8851
+
8852
+ ${prompt.variables.map(
8853
+ (variable) => `- \`{{${variable.name}}}\`${variable.required ? " \u2014 required" : " \u2014 optional"}: ${variable.description}${variable.example ? ` Example: ${variable.example}` : ""}`
8854
+ ).join("\n")}
8855
+ ` : "";
8856
+ const publisher = prompt.publisher.org?.name ?? prompt.publisher.user.name ?? `@${prompt.publisher.scope}`;
8857
+ return [
8858
+ `# ${prompt.title}`,
8859
+ "",
8860
+ prompt.summary,
8861
+ "",
8862
+ `- Source: ${sourceUrl}`,
8863
+ `- Publisher: ${publisher} (@${prompt.publisher.scope})`,
8864
+ `- Category: ${prompt.category}`,
8865
+ `- Inputs: ${prompt.inputTypes.join(", ") || "none"}`,
8866
+ `- Outputs: ${prompt.outputTypes.join(", ") || "none"}`,
8867
+ `- Effort: ${prompt.effort}`,
8868
+ `- Language: ${prompt.language}`,
8869
+ `- License: ${prompt.license}`,
8870
+ `- Updated: ${prompt.updatedAt}`,
8871
+ "",
8872
+ "## Prompt",
8873
+ "",
8874
+ prompt.promptText.trim(),
8875
+ variables,
8876
+ section("Example input", prompt.exampleInput),
8877
+ section("Example output", prompt.exampleOutput),
8878
+ section("Usage notes", prompt.usageNotes)
8879
+ ].join("\n").replace(/\n{3,}/g, "\n\n").trimEnd().concat("\n");
8880
+ }
8881
+ function emptyLock() {
8882
+ return { schemaVersion: "0.1", packages: {}, prompts: {} };
8883
+ }
8884
+ function installedRelativePath(configRoot, absolutePath) {
8885
+ return (0, import_node_path12.relative)(configRoot, absolutePath).split("\\").join("/");
8886
+ }
8887
+ async function installTrackedPrompt(input2) {
8888
+ const prompt = await fetchPromptDetail(
8889
+ input2.registry,
8890
+ input2.reference.publisher,
8891
+ input2.reference.slug
8892
+ );
8893
+ const canonicalReference = {
8894
+ ...input2.reference,
8895
+ publisher: prompt.publisher.scope,
8896
+ slug: prompt.slug,
8897
+ url: `${PROMPT_SITE_ORIGIN}/prompts/${encodeURIComponent(prompt.publisher.scope)}/${encodeURIComponent(prompt.slug)}`
8898
+ };
8899
+ const outputPath = promptSnapshotPath(input2.configRoot, canonicalReference);
8900
+ const contentHash = promptContentHash(prompt);
8901
+ const lock = await readLockfile(input2.configRoot) ?? emptyLock();
8902
+ const existing = lock.prompts[input2.reference.alias];
8903
+ const fileExists = Boolean(await (0, import_promises10.stat)(outputPath).catch(() => null));
8904
+ const changed = !existing || existing.contentHash !== contentHash || !fileExists;
8905
+ if (!input2.updateOnly || changed) {
8906
+ await (0, import_promises10.mkdir)((0, import_node_path12.dirname)(outputPath), { recursive: true });
8907
+ await (0, import_promises10.writeFile)(outputPath, renderPromptMarkdown(prompt, canonicalReference.url), "utf8");
8908
+ }
8909
+ if (input2.track) {
8910
+ const currentUrl = input2.project.prompts[input2.reference.alias];
8911
+ if (currentUrl && currentUrl !== canonicalReference.url) {
8912
+ throw new Error(
8913
+ `Prompt alias "${input2.reference.alias}" already tracks ${currentUrl}. Remove it first or use a unique slug.`
8914
+ );
8915
+ }
8916
+ await writeProjectPackageJson(input2.configRoot, {
8917
+ ...input2.project,
8918
+ prompts: { ...input2.project.prompts, [input2.reference.alias]: canonicalReference.url }
8919
+ });
8920
+ }
8921
+ const entry = {
8922
+ id: prompt.id,
8923
+ url: canonicalReference.url,
8924
+ publisher: canonicalReference.publisher,
8925
+ slug: canonicalReference.slug,
8926
+ contentHash,
8927
+ updatedAt: prompt.updatedAt,
8928
+ installedPath: installedRelativePath(input2.configRoot, outputPath)
8929
+ };
8930
+ await writeLockfile(input2.configRoot, upsertPromptLockEntry(lock, input2.reference.alias, entry));
8931
+ if (input2.recordCopy) {
8932
+ await recordPromptCopy(input2.registry, canonicalReference.publisher, canonicalReference.slug).catch(
8933
+ () => void 0
8934
+ );
8935
+ }
8936
+ return { changed, path: outputPath, prompt };
8937
+ }
8938
+ function resolveTrackedPrompt(project, value) {
8939
+ const direct = parsePromptUrl(value);
8940
+ if (direct) {
8941
+ const match = Object.entries(project.prompts).find(([, url2]) => url2 === direct.url);
8942
+ return match ? { ...direct, alias: match[0] } : direct;
8943
+ }
8944
+ const url = project.prompts[value];
8945
+ const parsed = url ? parsePromptUrl(url) : null;
8946
+ return parsed ? { ...parsed, alias: value } : null;
8947
+ }
8948
+ async function readInstalledPrompt(configRoot, entry) {
8949
+ return (0, import_promises10.readFile)((0, import_node_path12.join)(configRoot, entry.installedPath), "utf8");
8950
+ }
8951
+ async function removeTrackedPrompt(input2) {
8952
+ const lock = await readLockfile(input2.configRoot);
8953
+ const entry = lock?.prompts[input2.reference.alias];
8954
+ if (entry) await (0, import_promises10.rm)((0, import_node_path12.join)(input2.configRoot, entry.installedPath), { force: true });
8955
+ const prompts = { ...input2.project.prompts };
8956
+ delete prompts[input2.reference.alias];
8957
+ await writeProjectPackageJson(input2.configRoot, { ...input2.project, prompts });
8958
+ if (lock) {
8959
+ const lockedPrompts = { ...lock.prompts };
8960
+ delete lockedPrompts[input2.reference.alias];
8961
+ await writeLockfile(input2.configRoot, { ...lock, prompts: lockedPrompts });
8962
+ }
8963
+ }
8964
+
8741
8965
  // src/version.ts
8742
8966
  var import_node_fs2 = require("node:fs");
8743
- var import_node_path12 = require("node:path");
8967
+ var import_node_path13 = require("node:path");
8744
8968
  function getCliVersion() {
8745
- const pkgPath = (0, import_node_path12.join)(__dirname, "..", "package.json");
8969
+ const pkgPath = (0, import_node_path13.join)(__dirname, "..", "package.json");
8746
8970
  const pkg = JSON.parse((0, import_node_fs2.readFileSync)(pkgPath, "utf8"));
8747
8971
  return pkg.version;
8748
8972
  }
@@ -8831,11 +9055,14 @@ async function notifyCliUpdateIfNeeded(currentVersion, options) {
8831
9055
  var CLI_VERSION = getCliVersion();
8832
9056
  var program2 = new Command();
8833
9057
  var DEFAULT_REGISTRY = "https://api.aipm-registry.com";
8834
- var SITE_URL = "https://aipm-registry.com";
8835
- var PUBLISH_DOC_URL = "https://aipm-registry.com/publish";
8836
- var DASHBOARD_URL = "https://aipm-registry.com/dashboard";
9058
+ var SITE_URL = "https://www.aipm-registry.com";
9059
+ var PUBLISH_DOC_URL = `${SITE_URL}/publish`;
9060
+ var DASHBOARD_URL = `${SITE_URL}/dashboard`;
8837
9061
  var GLOBAL_OPTION = "-g, --global";
8838
9062
  var GLOBAL_OPTION_DESC = "Use global config (~/.aipm) and install skills under your home directory";
9063
+ var normalizedArgv = process.argv.map(
9064
+ (arg) => arg === "-prompt" ? "--prompt" : arg === "-skill" ? "--skill" : arg
9065
+ );
8839
9066
  program2.name("aipm").description("AI package manager").enablePositionalOptions().version(CLI_VERSION, "-v, --version", "output the current version").option("--verbose", "Print extra diagnostic output").option("--quiet", "Reduce non-essential output").addHelpText(
8840
9067
  "after",
8841
9068
  `
@@ -8863,6 +9090,10 @@ function packagePageUrl(packageName, version) {
8863
9090
  const [scope, name] = packageName.replace(/^@/, "").split("/");
8864
9091
  return `${SITE_URL}/packages/${encodeURIComponent(scope ?? "")}/${encodeURIComponent(name ?? "")}/${encodeURIComponent(version)}`;
8865
9092
  }
9093
+ function packageBadgeMarkdown(packageName, version) {
9094
+ const pageUrl = packagePageUrl(packageName, version);
9095
+ return `[![Install with AIPM](${SITE_URL}/install-with-aipm.svg)](${pageUrl})`;
9096
+ }
8866
9097
  function parsePackageArg(value) {
8867
9098
  const match = value.match(/^(@[^@]+)(?:@(.+))?$/);
8868
9099
  if (!match) throw new Error("Package must be @scope/name or @scope/name@version");
@@ -8877,23 +9108,23 @@ function skillFolderName(name) {
8877
9108
  return folder;
8878
9109
  }
8879
9110
  async function writeStarterFile(path2, content) {
8880
- await (0, import_promises10.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
9111
+ await (0, import_promises11.writeFile)(path2, content, { encoding: "utf8", flag: "wx" });
8881
9112
  }
8882
9113
  async function inferEntryFromSource(source, fallback) {
8883
- const sourceInfo = await (0, import_promises10.stat)(source);
8884
- if (sourceInfo.isFile()) return (0, import_node_path13.basename)(source);
9114
+ const sourceInfo = await (0, import_promises11.stat)(source);
9115
+ if (sourceInfo.isFile()) return (0, import_node_path14.basename)(source);
8885
9116
  if (!sourceInfo.isDirectory()) return fallback;
8886
- const entries = await (0, import_promises10.readdir)(source);
9117
+ const entries = await (0, import_promises11.readdir)(source);
8887
9118
  if (entries.includes(fallback)) return fallback;
8888
9119
  return entries.find((entry) => entry.endsWith(".md")) ?? entries.find((entry) => entry.endsWith(".mdc")) ?? fallback;
8889
9120
  }
8890
9121
  async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8891
- const sourceInfo = await (0, import_promises10.stat)(source);
8892
- await (0, import_promises10.mkdir)(root, { recursive: true });
9122
+ const sourceInfo = await (0, import_promises11.stat)(source);
9123
+ await (0, import_promises11.mkdir)(root, { recursive: true });
8893
9124
  if (sourceInfo.isDirectory()) {
8894
- const entries = await (0, import_promises10.readdir)(source);
9125
+ const entries = await (0, import_promises11.readdir)(source);
8895
9126
  for (const entry of entries) {
8896
- await (0, import_promises10.cp)((0, import_node_path13.join)(source, entry), (0, import_node_path13.join)(root, entry), {
9127
+ await (0, import_promises11.cp)((0, import_node_path14.join)(source, entry), (0, import_node_path14.join)(root, entry), {
8897
9128
  recursive: true,
8898
9129
  force: false,
8899
9130
  errorOnExist: true
@@ -8902,7 +9133,7 @@ async function copySourceIntoSkillRoot(source, root, sourceLabel) {
8902
9133
  return;
8903
9134
  }
8904
9135
  if (sourceInfo.isFile()) {
8905
- await (0, import_promises10.cp)(source, (0, import_node_path13.join)(root, (0, import_node_path13.basename)(source)), { force: false, errorOnExist: true });
9136
+ await (0, import_promises11.cp)(source, (0, import_node_path14.join)(root, (0, import_node_path14.basename)(source)), { force: false, errorOnExist: true });
8906
9137
  return;
8907
9138
  }
8908
9139
  throw new Error(`Unsupported source path: ${sourceLabel}`);
@@ -8936,11 +9167,11 @@ function packageDashboardUrl(name) {
8936
9167
  return `${DASHBOARD_URL}/packages/${name.replace(/^@/, "")}`;
8937
9168
  }
8938
9169
  function helperRootFromTrackedPath(path2) {
8939
- const parts = path2.split(import_node_path13.sep).filter(Boolean);
9170
+ const parts = path2.split(import_node_path14.sep).filter(Boolean);
8940
9171
  const helpersIndex = parts.lastIndexOf("helpers");
8941
9172
  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));
9173
+ const prefix = path2.startsWith(import_node_path14.sep) ? import_node_path14.sep : "";
9174
+ return (0, import_node_path14.join)(prefix, ...parts.slice(0, helpersIndex + 3));
8944
9175
  }
8945
9176
  async function showInstalledPrompt(configRoot, packageArg) {
8946
9177
  const { name } = parsePackageArg(packageArg);
@@ -8953,7 +9184,7 @@ async function showInstalledPrompt(configRoot, packageArg) {
8953
9184
  if (entry.postInstall.status === "cleaned") {
8954
9185
  throw new Error(`Setup helper files were cleaned for ${name}. Reinstall the package to restore them.`);
8955
9186
  }
8956
- const content = await (0, import_promises10.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
9187
+ const content = await (0, import_promises11.readFile)(entry.postInstall.promptFile, "utf8").catch(() => {
8957
9188
  throw new Error(`Setup prompt file is missing: ${entry.postInstall?.promptFile}`);
8958
9189
  });
8959
9190
  console.log(`Prompt file: ${entry.postInstall.promptFile}`);
@@ -8986,9 +9217,9 @@ async function cleanupInstalledHelpers(options) {
8986
9217
  entry.installedAssets.helper.map((path2) => helperRootFromTrackedPath(path2)).filter((path2) => Boolean(path2))
8987
9218
  );
8988
9219
  if (roots.size > 0) {
8989
- for (const root of roots) await (0, import_promises10.rm)(root, { recursive: true, force: true });
9220
+ for (const root of roots) await (0, import_promises11.rm)(root, { recursive: true, force: true });
8990
9221
  } else {
8991
- for (const helperPath of entry.installedAssets.helper) await (0, import_promises10.rm)(helperPath, { force: true });
9222
+ for (const helperPath of entry.installedAssets.helper) await (0, import_promises11.rm)(helperPath, { force: true });
8992
9223
  }
8993
9224
  await writeLockfile(options.configRoot, {
8994
9225
  ...lock,
@@ -9011,10 +9242,10 @@ async function openUrl(url) {
9011
9242
  console.log(`Open: ${url}`);
9012
9243
  }
9013
9244
  function randomBase64Url(bytes = 32) {
9014
- return (0, import_node_crypto2.randomBytes)(bytes).toString("base64url");
9245
+ return (0, import_node_crypto3.randomBytes)(bytes).toString("base64url");
9015
9246
  }
9016
9247
  function sha256Base64Url(value) {
9017
- return (0, import_node_crypto2.createHash)("sha256").update(value).digest("base64url");
9248
+ return (0, import_node_crypto3.createHash)("sha256").update(value).digest("base64url");
9018
9249
  }
9019
9250
  function deviceName() {
9020
9251
  return `AIPM CLI on ${process.platform}`;
@@ -9282,9 +9513,9 @@ function buildStarterQualityMetadata(options) {
9282
9513
  }
9283
9514
  async function initSkill(opts) {
9284
9515
  if (!isValidScopeName(opts.name)) throw new Error("Invalid @scope/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));
9516
+ const root = opts.here ? (0, import_node_process5.cwd)() : (0, import_node_path14.resolve)((0, import_node_process5.cwd)(), opts.dir ?? skillFolderName(opts.name));
9286
9517
  const cdTarget = opts.here ? null : opts.dir ?? skillFolderName(opts.name);
9287
- const source = opts.from ? (0, import_node_path13.resolve)(opts.from) : null;
9518
+ const source = opts.from ? (0, import_node_path14.resolve)(opts.from) : null;
9288
9519
  const entry = source ? await inferEntryFromSource(source, opts.entry) : opts.entry;
9289
9520
  const template = parseSkillTemplate(opts.template ?? "blank");
9290
9521
  const manifest = PackageManifestSchema.parse({
@@ -9305,17 +9536,17 @@ async function initSkill(opts) {
9305
9536
  if (source) {
9306
9537
  await copySourceIntoSkillRoot(source, root, source);
9307
9538
  } else {
9308
- await (0, import_promises10.mkdir)(root, { recursive: true });
9539
+ await (0, import_promises11.mkdir)(root, { recursive: true });
9309
9540
  }
9310
- await writeStarterFile((0, import_node_path13.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9541
+ await writeStarterFile((0, import_node_path14.join)(root, "aipm.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
9311
9542
  if (!opts.from) {
9312
9543
  await writeStarterFile(
9313
- (0, import_node_path13.join)(root, entry),
9544
+ (0, import_node_path14.join)(root, entry),
9314
9545
  SKILL_TEMPLATES[template](opts.name)
9315
9546
  );
9316
9547
  }
9317
9548
  await writeStarterFile(
9318
- (0, import_node_path13.join)(root, ".aipmignore"),
9549
+ (0, import_node_path14.join)(root, ".aipmignore"),
9319
9550
  [
9320
9551
  "# Files that should never be published with this skill",
9321
9552
  ".env*",
@@ -9333,7 +9564,7 @@ async function initSkill(opts) {
9333
9564
  ""
9334
9565
  ].join("\n")
9335
9566
  );
9336
- await (0, import_promises10.mkdir)((0, import_node_path13.join)(root, ".aipm"), { recursive: true });
9567
+ await (0, import_promises11.mkdir)((0, import_node_path14.join)(root, ".aipm"), { recursive: true });
9337
9568
  console.log(`Created ${opts.name} skill folder: ${root}`);
9338
9569
  printPublishGuide(
9339
9570
  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.`,
@@ -9348,7 +9579,7 @@ program2.command("init").description("Create aipm.package.json in the current pr
9348
9579
  console.log(`aipm.package.json already exists (${scopeLabel(scope)}).`);
9349
9580
  return;
9350
9581
  }
9351
- if (scope.global) await (0, import_promises10.mkdir)(configRoot, { recursive: true });
9582
+ if (scope.global) await (0, import_promises11.mkdir)(configRoot, { recursive: true });
9352
9583
  const installRoot = resolveInstallRoot(scope);
9353
9584
  const detected = await detectToolsInProject(installRoot);
9354
9585
  const parsedTarget = parseTargetFlag(opts.target);
@@ -9362,7 +9593,8 @@ program2.command("init").description("Create aipm.package.json in the current pr
9362
9593
  schemaVersion: "0.1",
9363
9594
  registry,
9364
9595
  preferredTools: preferredTools.length ? preferredTools : void 0,
9365
- packages: {}
9596
+ packages: {},
9597
+ prompts: {}
9366
9598
  });
9367
9599
  console.log(`Created aipm.package.json (${scopeLabel(scope)}, registry: ${registry})`);
9368
9600
  });
@@ -9450,14 +9682,27 @@ program2.command("config").description("Show resolved AIPM CLI and project confi
9450
9682
  console.log(`Auth file: ${data.authFile}`);
9451
9683
  console.log(`Publish guide: ${data.publishDoc}`);
9452
9684
  });
9453
- program2.command("add <package>").description("Add and install a package @scope/name[@version]").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("--registry <url>", "Registry base URL").option("--target <tool>", "cursor, claude, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive; fail if prompt needed").action(async (pkgArg, opts) => {
9454
- const { name, version: requestedVersion } = parsePackageArg(pkgArg);
9685
+ 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, or *").option("--token <token>", "Install token for private packages").option("--ci", "Non-interactive; fail if prompt needed").action(async (pkgArg, opts) => {
9455
9686
  const scope = { global: opts.global };
9456
9687
  const configRoot = resolveConfigRoot(scope);
9457
9688
  const installRoot = resolveInstallRoot(scope);
9458
9689
  let project = await readProjectPackageJson(configRoot);
9459
9690
  if (!project) throw new Error(initRequiredMessage(scope));
9460
9691
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9692
+ const promptReference = parsePromptUrl(pkgArg);
9693
+ if (promptReference) {
9694
+ const result = await installTrackedPrompt({
9695
+ configRoot,
9696
+ registry,
9697
+ project,
9698
+ reference: promptReference,
9699
+ track: true,
9700
+ recordCopy: true
9701
+ });
9702
+ console.log(`Installed prompt ${result.prompt.title} \u2192 ${result.path}`);
9703
+ return;
9704
+ }
9705
+ const { name, version: requestedVersion } = parsePackageArg(pkgArg);
9461
9706
  const token = await tokenForRead(registry, opts.token);
9462
9707
  const version = requestedVersion ?? project.packages[name] ?? await latestVersionForPackage(registry, name, token);
9463
9708
  if (!version) throw new Error("Specify version: aipm add @scope/pkg@1.0.0");
@@ -9531,10 +9776,75 @@ program2.command("install").description("Install all packages from aipm.package.
9531
9776
  token
9532
9777
  });
9533
9778
  }
9779
+ for (const [alias, url] of Object.entries(project.prompts)) {
9780
+ const reference = parsePromptUrl(url);
9781
+ if (!reference) throw new Error(`Invalid tracked prompt URL for ${alias}: ${url}`);
9782
+ const result = await installTrackedPrompt({
9783
+ configRoot,
9784
+ registry,
9785
+ project,
9786
+ reference: { ...reference, alias },
9787
+ track: false
9788
+ });
9789
+ console.log(`Restored prompt ${result.prompt.title} \u2192 ${result.path}`);
9790
+ }
9791
+ });
9792
+ 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) => {
9793
+ const configRoot = resolveConfigRoot({ global: opts.global });
9794
+ const project = await readProjectPackageJson(configRoot);
9795
+ if (!project) throw new Error(initRequiredMessage({ global: opts.global }));
9796
+ const reference = resolveTrackedPrompt(project, value);
9797
+ if (reference && project.prompts[reference.alias] === reference.url) {
9798
+ const lock2 = await readLockfile(configRoot);
9799
+ const entry2 = lock2?.prompts[reference.alias];
9800
+ if (!entry2) throw new Error(`Prompt is not installed: ${reference.url}. Run aipm install.`);
9801
+ console.log(await readInstalledPrompt(configRoot, entry2));
9802
+ return;
9803
+ }
9804
+ const { name } = parsePackageArg(value);
9805
+ const lock = await readLockfile(configRoot);
9806
+ const entry = lock?.packages[name];
9807
+ if (!entry) throw new Error(`Package is not installed: ${name}`);
9808
+ console.log(`${name}@${entry.version} [${entry.resolvedTools.join(", ")}]`);
9534
9809
  });
9535
9810
  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
9811
  await showInstalledPrompt(resolveConfigRoot({ global: opts.global }), pkgArg);
9537
9812
  });
9813
+ function promptImageContentType(path2) {
9814
+ const extension = (0, import_node_path14.extname)(path2).toLowerCase();
9815
+ if (extension === ".png") return "image/png";
9816
+ if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
9817
+ if (extension === ".webp") return "image/webp";
9818
+ throw new Error(`Unsupported prompt sample image: ${path2}`);
9819
+ }
9820
+ var promptCommand = program2.command("prompt").description("Publish prompt listings from structured JSON");
9821
+ 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) => {
9822
+ const registry = registryFromEnvOrDefault(opts.registry);
9823
+ const accessToken = await authTokenForRegistry(registry, { throwOnFailure: true });
9824
+ if (!accessToken) throw new Error("Run aipm login before publishing prompts.");
9825
+ const absoluteFile = (0, import_node_path14.resolve)(file);
9826
+ const parsed = JSON.parse(await (0, import_promises11.readFile)(absoluteFile, "utf8"));
9827
+ const rows = Array.isArray(parsed) ? parsed : "prompts" in parsed ? parsed.prompts : [parsed];
9828
+ if (rows.length === 0) throw new Error("Prompt publish file contains no prompts.");
9829
+ if (!opts.yes) {
9830
+ console.log(`Reminder: this will publish ${rows.length} public prompt${rows.length === 1 ? "" : "s"}.`);
9831
+ console.log("Use --yes to skip this reminder in automation.");
9832
+ }
9833
+ for (const row of rows) {
9834
+ const { sampleImagePath, ...input2 } = row;
9835
+ let sampleImage;
9836
+ if (sampleImagePath) {
9837
+ const imagePath = (0, import_node_path14.resolve)((0, import_node_path14.dirname)(absoluteFile), sampleImagePath);
9838
+ sampleImage = {
9839
+ data: await (0, import_promises11.readFile)(imagePath),
9840
+ filename: (0, import_node_path14.basename)(imagePath),
9841
+ contentType: promptImageContentType(imagePath)
9842
+ };
9843
+ }
9844
+ const saved = await publishPrompt(registry, input2, accessToken, sampleImage);
9845
+ console.log(`Published ${saved.title} \u2192 ${SITE_URL}${saved.path}`);
9846
+ }
9847
+ });
9538
9848
  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
9849
  await cleanupInstalledHelpers({
9540
9850
  configRoot: resolveConfigRoot({ global: opts.global }),
@@ -9550,8 +9860,8 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
9550
9860
  publish.help();
9551
9861
  return;
9552
9862
  }
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");
9863
+ const abs = (0, import_node_path14.resolve)(dir);
9864
+ const manifestRaw = await (0, import_promises11.readFile)((0, import_node_path14.join)(abs, "aipm.manifest.json"), "utf8");
9555
9865
  const manifest = PackageManifestSchema.parse(JSON.parse(manifestRaw));
9556
9866
  const tarball = await packDirectory(abs);
9557
9867
  const registry = registryFromEnvOrDefault(opts.registry);
@@ -9559,6 +9869,7 @@ var publish = program2.command("publish [dir]").description("Stage, validate, an
9559
9869
  console.log(`Published ${manifest.name}@${result.version}`);
9560
9870
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
9561
9871
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
9872
+ console.log(`README badge: ${packageBadgeMarkdown(manifest.name, result.version)}`);
9562
9873
  printPublishGuide(
9563
9874
  `Published ${manifest.name}@${result.version} from ${abs}.`,
9564
9875
  "Share the install command or open the package page to confirm the listing."
@@ -9684,28 +9995,46 @@ publish.command("push").description("Publish staged files").option("--registry <
9684
9995
  console.log(`Published ${manifest.name}@${result.version}`);
9685
9996
  console.log(`View: ${packagePageUrl(manifest.name, result.version)}`);
9686
9997
  console.log(`Install: aipm add ${manifest.name}@${result.version} --target ${manifest.targets[0]} --ci`);
9998
+ console.log(`README badge: ${packageBadgeMarkdown(manifest.name, result.version)}`);
9687
9999
  printPublishGuide(
9688
10000
  `Published ${manifest.name}@${result.version} to ${registry}.`,
9689
10001
  "Open the registry page, test the install command in a clean project, and share the package link."
9690
10002
  );
9691
10003
  });
9692
- program2.command("list").description("List packages from aipm-lock.json").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).action(async (opts) => {
10004
+ program2.command("list").description("List installed skills and prompts from aipm-lock.json").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).option("-p, --prompt", "List prompts only").option("-s, --skill", "List skills only").action(async (opts) => {
9693
10005
  const configRoot = resolveConfigRoot({ global: opts.global });
9694
10006
  const lock = await readLockfile(configRoot);
9695
- if (!lock || Object.keys(lock.packages).length === 0) {
9696
- console.log("No packages installed.");
10007
+ const showSkills = !opts.prompt || Boolean(opts.skill);
10008
+ const showPrompts = !opts.skill || Boolean(opts.prompt);
10009
+ const skillRows = lock ? Object.entries(lock.packages) : [];
10010
+ const promptRows = lock ? Object.entries(lock.prompts) : [];
10011
+ if ((!showSkills || skillRows.length === 0) && (!showPrompts || promptRows.length === 0)) {
10012
+ console.log(opts.prompt && !opts.skill ? "No prompts installed." : opts.skill && !opts.prompt ? "No skills installed." : "No skills or prompts installed.");
9697
10013
  return;
9698
10014
  }
9699
- for (const [name, entry] of Object.entries(lock.packages)) {
9700
- console.log(`${name}@${entry.version} [${entry.resolvedTools.join(", ")}]`);
10015
+ if (showSkills) {
10016
+ for (const [name, entry] of skillRows) {
10017
+ console.log(`skill ${name}@${entry.version} [${entry.resolvedTools.join(", ")}]`);
10018
+ }
10019
+ }
10020
+ if (showPrompts) {
10021
+ for (const [name, entry] of promptRows) {
10022
+ console.log(`prompt ${name} ${entry.url} ${entry.installedPath}`);
10023
+ }
9701
10024
  }
9702
10025
  });
9703
- program2.command("remove <package>").alias("rm").description("Remove a package from aipm.package.json and aipm-lock.json").option(GLOBAL_OPTION, GLOBAL_OPTION_DESC).action(async (pkgArg, opts) => {
9704
- const { name } = parsePackageArg(pkgArg);
10026
+ 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) => {
9705
10027
  const scope = { global: opts.global };
9706
10028
  const configRoot = resolveConfigRoot(scope);
9707
10029
  const project = await readProjectPackageJson(configRoot);
9708
10030
  if (!project) throw new Error(initRequiredMessage(scope));
10031
+ const promptReference = resolveTrackedPrompt(project, pkgArg);
10032
+ if (promptReference && project.prompts[promptReference.alias] === promptReference.url) {
10033
+ await removeTrackedPrompt({ configRoot, project, reference: promptReference });
10034
+ console.log(`Removed prompt ${promptReference.url} and its local snapshot.`);
10035
+ return;
10036
+ }
10037
+ const { name } = parsePackageArg(pkgArg);
9709
10038
  const packages = { ...project.packages };
9710
10039
  delete packages[name];
9711
10040
  await writeProjectPackageJson(configRoot, { ...project, packages });
@@ -9725,12 +10054,24 @@ program2.command("update [package]").description("Update one installed package,
9725
10054
  let project = await readProjectPackageJson(configRoot);
9726
10055
  if (!project) throw new Error(initRequiredMessage(scope));
9727
10056
  const registry = resolveRegistryUrl(project, opts.registry, DEFAULT_REGISTRY);
9728
- const token = await tokenForRead(registry, opts.token);
9729
- const names = pkgArg ? [parsePackageArg(pkgArg).name] : Object.keys(project.packages);
9730
- if (names.length === 0) {
9731
- console.log("No packages configured.");
10057
+ const requestedPrompt = pkgArg ? resolveTrackedPrompt(project, pkgArg) : null;
10058
+ if (requestedPrompt) {
10059
+ if (project.prompts[requestedPrompt.alias] !== requestedPrompt.url) {
10060
+ throw new Error(`Prompt is not tracked: ${requestedPrompt.url}`);
10061
+ }
10062
+ const result = await installTrackedPrompt({
10063
+ configRoot,
10064
+ registry,
10065
+ project,
10066
+ reference: requestedPrompt,
10067
+ track: false,
10068
+ updateOnly: true
10069
+ });
10070
+ console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
9732
10071
  return;
9733
10072
  }
10073
+ const token = await tokenForRead(registry, opts.token);
10074
+ const names = pkgArg ? [parsePackageArg(pkgArg).name] : Object.keys(project.packages);
9734
10075
  for (const name of names) {
9735
10076
  const version = await latestVersionForPackage(registry, name, token);
9736
10077
  project = {
@@ -9750,9 +10091,27 @@ program2.command("update [package]").description("Update one installed package,
9750
10091
  token
9751
10092
  });
9752
10093
  }
10094
+ if (!pkgArg) {
10095
+ for (const [alias, url] of Object.entries(project.prompts)) {
10096
+ const reference = parsePromptUrl(url);
10097
+ if (!reference) throw new Error(`Invalid tracked prompt URL for ${alias}: ${url}`);
10098
+ const result = await installTrackedPrompt({
10099
+ configRoot,
10100
+ registry,
10101
+ project,
10102
+ reference: { ...reference, alias },
10103
+ track: false,
10104
+ updateOnly: true
10105
+ });
10106
+ console.log(result.changed ? `Updated prompt ${result.prompt.title} \u2192 ${result.path}` : `Prompt unchanged: ${result.prompt.title}`);
10107
+ }
10108
+ }
10109
+ if (names.length === 0 && Object.keys(project.prompts).length === 0) {
10110
+ console.log("No skills or prompts configured.");
10111
+ }
9753
10112
  });
9754
10113
  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));
9755
- program2.parseAsync(process.argv).catch((err) => {
10114
+ program2.parseAsync(normalizedArgv).catch((err) => {
9756
10115
  console.error(err.message);
9757
10116
  const message = err.message.toLowerCase();
9758
10117
  if (message.includes("token") || message.includes("unauthorized") || message.includes("forbidden")) {