@agentskit/cli 0.13.9 → 0.13.12

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
@@ -154,7 +154,7 @@ function createDemoAdapter(provider, model) {
154
154
  ].join(" ");
155
155
  for (const chunk of reply.match(/.{1,18}/g) ?? []) {
156
156
  if (cancelled) return;
157
- await new Promise((resolve6) => setTimeout(resolve6, 35));
157
+ await new Promise((resolve7) => setTimeout(resolve7, 35));
158
158
  yield { type: "text", content: chunk };
159
159
  }
160
160
  yield { type: "done" };
@@ -4036,11 +4036,11 @@ function scaffoldAgent(schema) {
4036
4036
  }
4037
4037
  async function writeScaffold(files, outDir, opts = {}) {
4038
4038
  const { writeFile: writeFile5, mkdir: mkdir5, access } = await import('fs/promises');
4039
- const { dirname: dirname4, join: join8 } = await import('path');
4039
+ const { dirname: dirname5, join: join11 } = await import('path');
4040
4040
  const written = [];
4041
4041
  for (const f of files) {
4042
- const full = join8(outDir, f.path);
4043
- await mkdir5(dirname4(full), { recursive: true });
4042
+ const full = join11(outDir, f.path);
4043
+ await mkdir5(dirname5(full), { recursive: true });
4044
4044
  if (!opts.overwrite) {
4045
4045
  try {
4046
4046
  await access(full);
@@ -4709,8 +4709,22 @@ async function defaultExists(path5) {
4709
4709
  return false;
4710
4710
  }
4711
4711
  }
4712
+ function assertInstallable(agent) {
4713
+ if (agent.status === "draft" || agent.installable === false) {
4714
+ throw new Error(
4715
+ `"${agent.id}" is a catalog draft \u2014 not installable yet. Browse the roadmap at https://registry.agentskit.io/r/catalog.json`
4716
+ );
4717
+ }
4718
+ if (agent.status === "alpha") {
4719
+ console.warn(`[agentskit] "${agent.id}" is alpha \u2014 experimental; review before production use.`);
4720
+ }
4721
+ if (agent.status === "deprecated") {
4722
+ throw new Error(`"${agent.id}" is deprecated and no longer installable from the registry.`);
4723
+ }
4724
+ }
4712
4725
  async function addAgent(id, options = {}) {
4713
4726
  const agent = await fetchAgent(id, options);
4727
+ assertInstallable(agent);
4714
4728
  const baseDir = options.outDir ?? "agents";
4715
4729
  const targetDir = path.join(baseDir, id);
4716
4730
  const exists = options.existsImpl ?? defaultExists;
@@ -4782,14 +4796,767 @@ async function diffAgent(id, options = {}) {
4782
4796
  }
4783
4797
  return { agent, targetDir, files };
4784
4798
  }
4799
+ function allDeps(pkg) {
4800
+ if (!pkg) return {};
4801
+ return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
4802
+ }
4803
+ function readJson(fs, path5) {
4804
+ const raw = fs.readFile(path5);
4805
+ if (raw == null) return null;
4806
+ try {
4807
+ return JSON.parse(raw);
4808
+ } catch {
4809
+ return null;
4810
+ }
4811
+ }
4812
+ function detectPackageManager(fs, root) {
4813
+ if (fs.exists(path.join(root, "pnpm-lock.yaml"))) return "pnpm";
4814
+ if (fs.exists(path.join(root, "bun.lockb")) || fs.exists(path.join(root, "bun.lock"))) return "bun";
4815
+ if (fs.exists(path.join(root, "yarn.lock"))) return "yarn";
4816
+ return "npm";
4817
+ }
4818
+ function detectUiBinding(deps) {
4819
+ const has = (name) => name in deps;
4820
+ if (has("react-native") || has("expo")) return "react-native";
4821
+ if (has("@angular/core")) return "angular";
4822
+ if (has("svelte")) return "svelte";
4823
+ if (has("nuxt") || has("vue")) return "vue";
4824
+ if (has("solid-js")) return "solid";
4825
+ if (has("ink")) return "ink";
4826
+ if (has("react")) return "react";
4827
+ return "unknown";
4828
+ }
4829
+ function detectMetaFramework(fs, root, deps, uiBinding) {
4830
+ const has = (name) => name in deps;
4831
+ const hasPrefix = (prefix) => Object.keys(deps).some((d) => d.startsWith(prefix));
4832
+ if (has("next")) {
4833
+ if (fs.exists(path.join(root, "app")) || fs.exists(path.join(root, "src/app"))) return "next-app";
4834
+ return "next-pages";
4835
+ }
4836
+ if (hasPrefix("@remix-run/")) return "remix";
4837
+ if (has("@tanstack/react-start") || has("@tanstack/start")) return "tanstack-start";
4838
+ if (has("@sveltejs/kit")) return "sveltekit";
4839
+ if (has("nuxt")) return "nuxt";
4840
+ if (has("astro")) return "astro";
4841
+ if (uiBinding === "angular") {
4842
+ return has("@angular/ssr") || has("@angular/platform-server") ? "angular-ssr" : "angular-spa";
4843
+ }
4844
+ if (has("expo")) return "expo";
4845
+ if (has("react-scripts")) return "cra";
4846
+ if (has("vite")) return "vite";
4847
+ if (uiBinding === "ink") return "node";
4848
+ if (uiBinding === "unknown") return "unknown";
4849
+ return "none";
4850
+ }
4851
+ function detectImportAlias(fs, root) {
4852
+ const tsconfig = readJson(
4853
+ fs,
4854
+ path.join(root, "tsconfig.json")
4855
+ );
4856
+ const paths = tsconfig?.compilerOptions?.paths;
4857
+ if (!paths) return null;
4858
+ for (const key of Object.keys(paths)) {
4859
+ if (key.endsWith("/*")) return key.slice(0, -2);
4860
+ }
4861
+ return null;
4862
+ }
4863
+ var CSS_ENTRY_CANDIDATES = [
4864
+ "app/globals.css",
4865
+ "app/global.css",
4866
+ "src/app/globals.css",
4867
+ "src/index.css",
4868
+ "src/styles/globals.css",
4869
+ "styles/globals.css",
4870
+ "src/app.css",
4871
+ "src/styles.css"
4872
+ ];
4873
+ function detectStyling(fs, root, deps) {
4874
+ const tailwind = "tailwindcss" in deps || fs.exists(path.join(root, "tailwind.config.ts")) || fs.exists(path.join(root, "tailwind.config.js")) || fs.exists(path.join(root, "tailwind.config.cjs")) || fs.exists(path.join(root, "tailwind.config.mjs"));
4875
+ let cssEntry = null;
4876
+ for (const candidate of CSS_ENTRY_CANDIDATES) {
4877
+ if (fs.exists(path.join(root, candidate))) {
4878
+ cssEntry = candidate;
4879
+ break;
4880
+ }
4881
+ }
4882
+ return { mode: tailwind ? "tailwind-preset" : "data-attrs-only", cssEntry };
4883
+ }
4884
+ function detectMonorepo(fs, root) {
4885
+ if (fs.exists(path.join(root, "pnpm-workspace.yaml"))) return { tool: "pnpm", root };
4886
+ if (fs.exists(path.join(root, "turbo.json"))) return { tool: "turbo", root };
4887
+ if (fs.exists(path.join(root, "nx.json"))) return { tool: "nx", root };
4888
+ return null;
4889
+ }
4890
+ function scanProject(fs, root = ".") {
4891
+ const pkg = readJson(fs, path.join(root, "package.json"));
4892
+ const deps = allDeps(pkg);
4893
+ const uiBinding = detectUiBinding(deps);
4894
+ const metaFramework = detectMetaFramework(fs, root, deps, uiBinding);
4895
+ const srcDir = fs.exists(path.join(root, "src")) ? "src" : null;
4896
+ return {
4897
+ uiBinding,
4898
+ metaFramework,
4899
+ packageManager: detectPackageManager(fs, root),
4900
+ typescript: fs.exists(path.join(root, "tsconfig.json")),
4901
+ srcDir,
4902
+ importAlias: detectImportAlias(fs, root),
4903
+ styling: detectStyling(fs, root, deps),
4904
+ monorepo: detectMonorepo(fs, root)
4905
+ };
4906
+ }
4907
+ var CONFIG_PATH = ".agentskit/components.json";
4908
+ var SCHEMA_URL = "https://raw.githubusercontent.com/AgentsKit-io/agentskit-registry/v1/schema/components.json";
4909
+ var DEFAULT_REGISTRY = "https://registry.agentskit.io";
4910
+ var SERVER_DIR_BY_META = {
4911
+ "next-app": "app/api",
4912
+ "next-pages": "pages/api",
4913
+ remix: "app/routes",
4914
+ "tanstack-start": "app/routes",
4915
+ astro: "src/pages/api",
4916
+ sveltekit: "src/routes/api",
4917
+ nuxt: "server/api",
4918
+ "angular-ssr": "src/server",
4919
+ expo: "server",
4920
+ vite: "server",
4921
+ cra: "server",
4922
+ "angular-spa": "server",
4923
+ node: "server",
4924
+ none: "server"
4925
+ };
4926
+ function joinPath(base, sub) {
4927
+ return base ? `${base}/${sub}` : sub;
4928
+ }
4929
+ function buildConfig(scan) {
4930
+ const uiBinding = scan.uiBinding === "unknown" ? "react" : scan.uiBinding;
4931
+ const metaFramework = scan.metaFramework === "unknown" ? "none" : scan.metaFramework;
4932
+ const srcBase = scan.srcDir ?? "";
4933
+ const componentsPath = joinPath(srcBase, "components");
4934
+ const libPath = joinPath(srcBase, "lib");
4935
+ const rawServer = SERVER_DIR_BY_META[metaFramework];
4936
+ const serverPath = srcBase && (metaFramework === "next-app" || metaFramework === "next-pages") && !rawServer.startsWith("src/") ? joinPath(srcBase, rawServer) : rawServer;
4937
+ const alias = scan.importAlias;
4938
+ return {
4939
+ $schema: SCHEMA_URL,
4940
+ schemaVersion: 1,
4941
+ uiBinding,
4942
+ metaFramework,
4943
+ typescript: scan.typescript,
4944
+ styling: {
4945
+ mode: scan.styling.mode,
4946
+ css: scan.styling.cssEntry ?? "app/globals.css",
4947
+ tailwindConfig: null
4948
+ },
4949
+ aliases: {
4950
+ // components + lib are import-aliased when the project has one; the server
4951
+ // route is a framework-routed filesystem location, so it mirrors its path.
4952
+ components: alias ? `${alias}/components` : `./${componentsPath}`,
4953
+ lib: alias ? `${alias}/lib` : `./${libPath}`,
4954
+ server: serverPath
4955
+ },
4956
+ paths: {
4957
+ root: scan.monorepo?.root ?? ".",
4958
+ components: componentsPath,
4959
+ lib: libPath,
4960
+ server: serverPath
4961
+ },
4962
+ registries: { default: DEFAULT_REGISTRY }
4963
+ };
4964
+ }
4965
+ function serializeConfig(config) {
4966
+ return `${JSON.stringify(config, null, 2)}
4967
+ `;
4968
+ }
4969
+ function parseConfig(raw) {
4970
+ let data;
4971
+ try {
4972
+ data = JSON.parse(raw);
4973
+ } catch {
4974
+ return null;
4975
+ }
4976
+ if (!data || typeof data !== "object") return null;
4977
+ const c = data;
4978
+ if (typeof c.schemaVersion !== "number" || !c.uiBinding || !c.metaFramework || !c.registries) {
4979
+ return null;
4980
+ }
4981
+ return c;
4982
+ }
4983
+ function readConfig(io, root = ".") {
4984
+ const raw = io.read(path.join(root, CONFIG_PATH));
4985
+ return raw == null ? null : parseConfig(raw);
4986
+ }
4987
+ function writeConfig(io, config, root = ".") {
4988
+ const path5 = path.join(root, CONFIG_PATH);
4989
+ io.write(path5, serializeConfig(config));
4990
+ return path5;
4991
+ }
4992
+ function resolveConfig(io, scan, root = ".") {
4993
+ const existing = readConfig(io, root);
4994
+ if (existing) return { config: existing, fromDisk: true };
4995
+ return { config: buildConfig(scan), fromDisk: false };
4996
+ }
4997
+ var IntegrityError = class extends Error {
4998
+ constructor(message) {
4999
+ super(message);
5000
+ this.name = "IntegrityError";
5001
+ }
5002
+ };
5003
+ function isSafeRegistryPath(rel) {
5004
+ if (rel === "" || path.isAbsolute(rel)) return false;
5005
+ const segments = rel.split(/[/\\]/);
5006
+ for (const s of segments) {
5007
+ if (s === "" || s === "." || s === "..") return false;
5008
+ }
5009
+ return true;
5010
+ }
5011
+ function assertContained(targetDir, rel) {
5012
+ if (!isSafeRegistryPath(rel)) {
5013
+ throw new IntegrityError(`unsafe registry path rejected: ${JSON.stringify(rel)}`);
5014
+ }
5015
+ const base = path.resolve(targetDir);
5016
+ const dest = path.resolve(base, rel);
5017
+ if (dest !== base && !dest.startsWith(base + path.sep)) {
5018
+ throw new IntegrityError(`path escapes the target directory: ${JSON.stringify(rel)}`);
5019
+ }
5020
+ return dest;
5021
+ }
5022
+ function commitFiles(fs, targetDir, files, options = {}) {
5023
+ const planned = files.map((f) => ({ dest: assertContained(targetDir, f.path), content: f.content, rel: f.path }));
5024
+ if (!options.force) {
5025
+ const conflicts = planned.filter((p) => fs.exists(p.dest)).map((p) => p.rel);
5026
+ if (conflicts.length > 0) {
5027
+ throw new IntegrityError(
5028
+ `refusing to overwrite ${conflicts.length} existing file(s) (use --force): ${conflicts.join(", ")}`
5029
+ );
5030
+ }
5031
+ }
5032
+ const written = [];
5033
+ try {
5034
+ for (const p of planned) {
5035
+ fs.write(p.dest, p.content);
5036
+ written.push(p.dest);
5037
+ }
5038
+ } catch (err) {
5039
+ for (const dest of written.reverse()) {
5040
+ try {
5041
+ fs.remove(dest);
5042
+ } catch {
5043
+ }
5044
+ }
5045
+ const message = err instanceof Error ? err.message : String(err);
5046
+ throw new IntegrityError(`install failed and was rolled back (${written.length} file(s) removed): ${message}`);
5047
+ }
5048
+ return { written };
5049
+ }
5050
+
5051
+ // src/components/fetch.ts
5052
+ var SUPPORTED_SCHEMA_VERSION = 1;
5053
+ var DEFAULT_REGISTRY2 = "https://registry.agentskit.io";
5054
+ function resolveIdentifier(identifier, config, options = {}) {
5055
+ const registries = config?.registries ?? {};
5056
+ if (/^https?:\/\//.test(identifier)) {
5057
+ const m = identifier.match(/^(.*?)\/r\/([^/]+?)(?:\.json)?$/);
5058
+ if (m) return { base: m[1], itemId: m[2] };
5059
+ const url = new URL(identifier);
5060
+ const id = url.pathname.split("/").filter(Boolean).pop()?.replace(/\.json$/, "") ?? identifier;
5061
+ return { base: `${url.origin}`, itemId: id };
5062
+ }
5063
+ if (identifier.startsWith("@")) {
5064
+ const slash = identifier.indexOf("/");
5065
+ if (slash < 0) throw new IntegrityError(`invalid namespaced identifier: ${identifier}`);
5066
+ const org = identifier.slice(1, slash);
5067
+ const name = identifier.slice(slash + 1);
5068
+ const base2 = registries[org];
5069
+ if (!base2) throw new IntegrityError(`unknown registry namespace "@${org}" \u2014 add it to components.json registries`);
5070
+ return { base: base2, itemId: name };
5071
+ }
5072
+ const base = options.registryBase ?? registries.default ?? DEFAULT_REGISTRY2;
5073
+ return { base, itemId: identifier };
5074
+ }
5075
+ function resolveAuthHeader(base, config, env = {}) {
5076
+ const map = config?.registryAuth;
5077
+ if (!map) return {};
5078
+ for (const [registryBase, envVar] of Object.entries(map)) {
5079
+ if (base.startsWith(registryBase)) {
5080
+ const token = env[envVar];
5081
+ if (token) return { authorization: `Bearer ${token}` };
5082
+ }
5083
+ }
5084
+ return {};
5085
+ }
5086
+ function sha256Hex(content) {
5087
+ return crypto.createHash("sha256").update(content, "utf8").digest("hex");
5088
+ }
5089
+ function verifyChecksums(files) {
5090
+ const bad = files.filter((f) => sha256Hex(f.content) !== f.sha256).map((f) => f.path);
5091
+ if (bad.length > 0) {
5092
+ throw new IntegrityError(`checksum mismatch \u2014 install aborted for ${bad.length} file(s): ${bad.join(", ")}`);
5093
+ }
5094
+ }
5095
+ function manifestUrl(ref) {
5096
+ return `${ref.base}/r/${ref.itemId}.json`;
5097
+ }
5098
+ function fileUrl(ref, path5) {
5099
+ return `${ref.base}/r/${ref.itemId}/${path5}`;
5100
+ }
5101
+ async function getText2(fetchImpl, url, headers) {
5102
+ const res = await fetchImpl(url, { headers });
5103
+ if (!res.ok) throw new IntegrityError(`fetch failed (${res.status}) for ${url}`);
5104
+ return res.text();
5105
+ }
5106
+ async function fetchManifest(identifier, options = {}) {
5107
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
5108
+ const ref = resolveIdentifier(identifier, options.config, options);
5109
+ const headers = resolveAuthHeader(ref.base, options.config, options.env);
5110
+ const raw = await getText2(fetchImpl, manifestUrl(ref), headers);
5111
+ if (options.signatureVerifier) {
5112
+ const sig = await getText2(fetchImpl, `${manifestUrl(ref)}.minisig`, headers);
5113
+ const ok = await options.signatureVerifier(raw, sig);
5114
+ if (!ok) throw new IntegrityError(`manifest signature verification failed for ${ref.itemId}`);
5115
+ }
5116
+ let parsed;
5117
+ try {
5118
+ parsed = JSON.parse(raw);
5119
+ } catch {
5120
+ throw new IntegrityError(`manifest is not valid JSON for ${ref.itemId}`);
5121
+ }
5122
+ const component = parsed;
5123
+ if (component?.kind !== "component") {
5124
+ throw new IntegrityError(`"${ref.itemId}" is not a component (kind=${String(component?.kind)})`);
5125
+ }
5126
+ if (typeof component.schemaVersion !== "number" || component.schemaVersion > SUPPORTED_SCHEMA_VERSION) {
5127
+ throw new IntegrityError(
5128
+ `"${ref.itemId}" needs a newer CLI (schemaVersion ${component.schemaVersion} > ${SUPPORTED_SCHEMA_VERSION})`
5129
+ );
5130
+ }
5131
+ return { ref, component };
5132
+ }
5133
+ async function fetchPortFiles(ref, port, options = {}) {
5134
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
5135
+ const headers = resolveAuthHeader(ref.base, options.config, options.env);
5136
+ const resolved = await Promise.all(
5137
+ port.files.map(async (f) => {
5138
+ const content = f.content ?? await getText2(fetchImpl, fileUrl(ref, f.path), headers);
5139
+ return { path: f.path, content, sha256: f.sha256 };
5140
+ })
5141
+ );
5142
+ verifyChecksums(resolved);
5143
+ return resolved.map((f) => ({ path: f.path, content: f.content }));
5144
+ }
5145
+
5146
+ // src/components/validate.ts
5147
+ var NO_NODE_SERVER = /* @__PURE__ */ new Set([
5148
+ "expo",
5149
+ "vite",
5150
+ "cra",
5151
+ "angular-spa",
5152
+ "none"
5153
+ ]);
5154
+ var SERVERLESS_CAPABLE = /* @__PURE__ */ new Set([
5155
+ "next-app",
5156
+ "next-pages",
5157
+ "remix",
5158
+ "tanstack-start",
5159
+ "sveltekit",
5160
+ "nuxt"
5161
+ ]);
5162
+ function parseVersion(input2) {
5163
+ const cleaned = input2.trim().replace(/^[v=]+/, "").split(/[-+]/)[0] ?? "";
5164
+ const parts = cleaned.split(".");
5165
+ if (parts.length === 0 || parts[0] === "") return null;
5166
+ const nums = parts.map((p) => Number.parseInt(p, 10));
5167
+ if (nums.some((n) => Number.isNaN(n))) return null;
5168
+ return { major: nums[0] ?? 0, minor: nums[1] ?? 0, patch: nums[2] ?? 0 };
5169
+ }
5170
+ function cmp(a, b) {
5171
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
5172
+ }
5173
+ function satisfiesRange(version, range) {
5174
+ const r = range.trim();
5175
+ if (r === "" || r === "*" || r === "x" || r.startsWith("workspace:")) return true;
5176
+ if (r.includes("||")) return r.split("||").some((part) => satisfiesRange(version, part));
5177
+ const v = parseVersion(version);
5178
+ if (!v) return true;
5179
+ const m = r.match(/^(>=|<=|>|<|=|\^|~)?\s*(.+)$/);
5180
+ if (!m) return true;
5181
+ const op = m[1] ?? "=";
5182
+ const target = parseVersion(m[2]);
5183
+ if (!target) return true;
5184
+ switch (op) {
5185
+ case ">=":
5186
+ return cmp(v, target) >= 0;
5187
+ case ">":
5188
+ return cmp(v, target) > 0;
5189
+ case "<=":
5190
+ return cmp(v, target) <= 0;
5191
+ case "<":
5192
+ return cmp(v, target) < 0;
5193
+ case "^": {
5194
+ if (cmp(v, target) < 0) return false;
5195
+ if (target.major > 0) return v.major === target.major;
5196
+ if (target.minor > 0) return v.major === 0 && v.minor === target.minor;
5197
+ return v.major === 0 && v.minor === 0 && v.patch === target.patch;
5198
+ }
5199
+ case "~":
5200
+ return v.major === target.major && v.minor === target.minor && cmp(v, target) >= 0;
5201
+ default:
5202
+ return cmp(v, target) === 0;
5203
+ }
5204
+ }
5205
+ function validateInstall(input2) {
5206
+ const { scan, component, installed = {} } = input2;
5207
+ const issues = [];
5208
+ const err = (code, message) => issues.push({ severity: "error", code, message });
5209
+ const warn = (code, message) => issues.push({ severity: "warning", code, message });
5210
+ const supported = Object.keys(component.ports);
5211
+ if (scan.uiBinding === "unknown") {
5212
+ err("framework-unknown", `Could not detect the UI framework. ${component.id} supports: ${supported.join(", ")}.`);
5213
+ return { ok: false, issues };
5214
+ }
5215
+ const port = component.ports[scan.uiBinding];
5216
+ if (!port) {
5217
+ err(
5218
+ "binding-unsupported",
5219
+ `${component.id} has no "${scan.uiBinding}" port. Supported bindings: ${supported.join(", ")}.`
5220
+ );
5221
+ return { ok: false, issues };
5222
+ }
5223
+ const pairShipped = component.frameworks.some(
5224
+ (t) => t.uiBinding === scan.uiBinding && (scan.metaFramework === "unknown" || t.metaFramework === scan.metaFramework)
5225
+ );
5226
+ if (scan.metaFramework !== "unknown" && !pairShipped) {
5227
+ err(
5228
+ "target-unsupported",
5229
+ `${component.id} does not ship a ${scan.uiBinding} \xD7 ${scan.metaFramework} target.`
5230
+ );
5231
+ }
5232
+ const peerRanges = { ...component.peerRanges, ...port.peerRanges };
5233
+ for (const [name, range] of Object.entries(peerRanges)) {
5234
+ const have = installed[name];
5235
+ if (have == null) continue;
5236
+ if (!satisfiesRange(have, range)) {
5237
+ err("peer-mismatch", `${name}@${have} does not satisfy required ${range}.`);
5238
+ }
5239
+ }
5240
+ const server = port.server;
5241
+ if (server) {
5242
+ const noServer = NO_NODE_SERVER.has(scan.metaFramework);
5243
+ if (server.runtimeRequirement === "nodejs" && noServer && server.delivery === "bundled") {
5244
+ err(
5245
+ "runtime-unsatisfiable",
5246
+ `${scan.metaFramework} has no Node server, but this port needs a bundled Node handler. Use a hosted endpoint instead.`
5247
+ );
5248
+ }
5249
+ if (server.embeddingBackend === "onnx-node" && noServer) {
5250
+ err(
5251
+ "embedding-unsatisfiable",
5252
+ `The onnx-node embedder needs a Node runtime; ${scan.metaFramework} can't run it. Use a BYO/api-remote embedder.`
5253
+ );
5254
+ }
5255
+ if (noServer && server.delivery === "bundled") {
5256
+ for (const e of component.env ?? []) {
5257
+ if (e.scope === "server" && e.required) {
5258
+ err("env-client-leak", `Server-only env "${e.name}" can't be bundled into a client-only ${scan.metaFramework} target.`);
5259
+ }
5260
+ }
5261
+ }
5262
+ if (server.rateLimitBackend === "memory" && SERVERLESS_CAPABLE.has(scan.metaFramework)) {
5263
+ warn(
5264
+ "ratelimit-memory",
5265
+ `In-memory rate-limit is per-instance and ineffective on serverless ${scan.metaFramework}. Set UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN for a durable limiter.`
5266
+ );
5267
+ }
5268
+ }
5269
+ if (port.language === "ts" && !scan.typescript) {
5270
+ err("language-ts-only", `${component.id}'s ${scan.uiBinding} port is TypeScript-only, but this is a JavaScript project.`);
5271
+ }
5272
+ if (port.stylingMode === "tailwind-preset" && scan.styling.mode !== "tailwind-preset") {
5273
+ warn("styling-tailwind", `This port expects Tailwind, which wasn't detected. The component still renders via its --ak-* tokens; wire the preset manually.`);
5274
+ }
5275
+ return { ok: !issues.some((i) => i.severity === "error"), port, issues };
5276
+ }
5277
+
5278
+ // src/components/marker.ts
5279
+ function assertUniquePaths(files) {
5280
+ const seen = /* @__PURE__ */ new Set();
5281
+ for (const f of files) {
5282
+ if (seen.has(f.path)) {
5283
+ throw new IntegrityError(`duplicate file path in install marker: ${JSON.stringify(f.path)}`);
5284
+ }
5285
+ seen.add(f.path);
5286
+ }
5287
+ }
5288
+ function sameKey(entry, id, installPath) {
5289
+ return entry.id === id && entry.installPath === installPath;
5290
+ }
5291
+ function upsertInstalled(config, entry) {
5292
+ const current = config.installed ?? [];
5293
+ const idx = current.findIndex((e) => sameKey(e, entry.id, entry.installPath));
5294
+ const installed = idx === -1 ? [...current, entry] : current.map((e, i) => i === idx ? entry : e);
5295
+ return { ...config, installed };
5296
+ }
5297
+ function buildInstalledComponent(args) {
5298
+ assertUniquePaths(args.files);
5299
+ const files = {};
5300
+ for (const f of args.files) {
5301
+ files[f.path] = { sha: sha256Hex(f.content), installedAt: args.now };
5302
+ }
5303
+ return {
5304
+ id: args.id,
5305
+ kind: "component",
5306
+ framework: args.framework,
5307
+ installPath: args.installPath,
5308
+ ref: args.ref,
5309
+ version: args.version,
5310
+ files
5311
+ };
5312
+ }
5313
+ function canonicalize(value) {
5314
+ if (Array.isArray(value)) return value.map(canonicalize);
5315
+ if (value !== null && typeof value === "object") {
5316
+ const sorted = {};
5317
+ for (const key of Object.keys(value).sort()) {
5318
+ sorted[key] = canonicalize(value[key]);
5319
+ }
5320
+ return sorted;
5321
+ }
5322
+ return value;
5323
+ }
5324
+ function canonicalJson(value) {
5325
+ return JSON.stringify(canonicalize(value));
5326
+ }
5327
+ function hashEntry(entry) {
5328
+ return sha256Hex(canonicalJson(entry));
5329
+ }
5330
+ function appendAudit(prevEntries, entry) {
5331
+ const prev = prevEntries.length > 0 ? prevEntries[prevEntries.length - 1] : void 0;
5332
+ const prevEntryHash = prev ? hashEntry(prev) : "";
5333
+ return { ...entry, prevEntryHash };
5334
+ }
5335
+ function serializeAuditLog(entries) {
5336
+ if (entries.length === 0) return "";
5337
+ return `${entries.map((e) => JSON.stringify(e)).join("\n")}
5338
+ `;
5339
+ }
5340
+ function parseAuditLog(raw) {
5341
+ const out = [];
5342
+ for (const line of raw.split("\n")) {
5343
+ const trimmed = line.trim();
5344
+ if (trimmed === "") continue;
5345
+ let parsed;
5346
+ try {
5347
+ parsed = JSON.parse(trimmed);
5348
+ } catch {
5349
+ continue;
5350
+ }
5351
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5352
+ out.push(parsed);
5353
+ }
5354
+ }
5355
+ return out;
5356
+ }
5357
+
5358
+ // src/components/flow.ts
5359
+ var AUDIT_PATH = ".agentskit/install-log.jsonl";
5360
+ function installedVersions(scanFs, root) {
5361
+ const raw = scanFs.readFile(path.join(root, "package.json"));
5362
+ if (raw == null) return {};
5363
+ try {
5364
+ const pkg = JSON.parse(raw);
5365
+ return { ...pkg.peerDependencies, ...pkg.devDependencies, ...pkg.dependencies };
5366
+ } catch {
5367
+ return {};
5368
+ }
5369
+ }
5370
+ async function addComponent(options, deps) {
5371
+ const root = options.root ?? ".";
5372
+ const scan = scanProject(deps.scanFs, root);
5373
+ const { config, fromDisk } = resolveConfig(deps.io, scan, root);
5374
+ const { ref, component } = await fetchManifest(options.identifier, {
5375
+ fetchImpl: deps.fetchImpl,
5376
+ env: deps.env,
5377
+ config,
5378
+ registryBase: options.registryBase,
5379
+ signatureVerifier: options.signatureVerifier
5380
+ });
5381
+ const validation = validateInstall({
5382
+ scan,
5383
+ component,
5384
+ installed: installedVersions(deps.scanFs, root)
5385
+ });
5386
+ if (!validation.ok || !validation.port) {
5387
+ const errors = validation.issues.filter((i) => i.severity === "error").map((i) => i.message);
5388
+ throw new IntegrityError(`cannot install ${component.id}: ${errors.join("; ") || "no compatible port"}`);
5389
+ }
5390
+ const port = validation.port;
5391
+ const files = await fetchPortFiles(ref, port, { fetchImpl: deps.fetchImpl, env: deps.env, config });
5392
+ const installPath = options.outDir ?? port.defaultTarget;
5393
+ const { written } = commitFiles(deps.writeFs, installPath, files, { force: options.force });
5394
+ const framework = {
5395
+ uiBinding: port.uiBinding,
5396
+ metaFramework: scan.metaFramework === "unknown" ? config.metaFramework : scan.metaFramework
5397
+ };
5398
+ const now = deps.now();
5399
+ const marker = buildInstalledComponent({
5400
+ id: component.id,
5401
+ framework,
5402
+ installPath,
5403
+ ref: ref.base,
5404
+ version: component.version,
5405
+ files,
5406
+ now
5407
+ });
5408
+ writeConfig(deps.io, upsertInstalled(config, marker), root);
5409
+ const auditPath = path.join(root, AUDIT_PATH);
5410
+ const prev = parseAuditLog(deps.io.read(auditPath) ?? "");
5411
+ const entry = appendAudit(prev, {
5412
+ schemaVersion: 1,
5413
+ eventType: "install",
5414
+ id: component.id,
5415
+ version: component.version,
5416
+ ref: ref.base,
5417
+ files: files.map((f) => ({ path: f.path, sha256: marker.files[f.path].sha })),
5418
+ manifestSigRef: options.signatureVerifier ? `${ref.itemId}.minisig` : "",
5419
+ timestamp: now
5420
+ });
5421
+ deps.io.write(auditPath, serializeAuditLog([...prev, entry]));
5422
+ return {
5423
+ id: component.id,
5424
+ version: component.version,
5425
+ installPath,
5426
+ framework,
5427
+ written,
5428
+ packages: [...component.packages, ...port.packages ?? []],
5429
+ env: component.env ?? [],
5430
+ warnings: validation.issues.filter((i) => i.severity === "warning"),
5431
+ configFromDisk: fromDisk
5432
+ };
5433
+ }
5434
+ function nodeScanFs() {
5435
+ return {
5436
+ readFile(path5) {
5437
+ try {
5438
+ return fs.readFileSync(path5, "utf8");
5439
+ } catch {
5440
+ return null;
5441
+ }
5442
+ },
5443
+ exists(path5) {
5444
+ return fs.existsSync(path5);
5445
+ }
5446
+ };
5447
+ }
5448
+ function nodeWriteFs() {
5449
+ return {
5450
+ exists(path5) {
5451
+ return fs.existsSync(path5);
5452
+ },
5453
+ write(path5, content) {
5454
+ fs.mkdirSync(path.dirname(path5), { recursive: true });
5455
+ fs.writeFileSync(path5, content, "utf8");
5456
+ },
5457
+ remove(path5) {
5458
+ fs.rmSync(path5, { force: true });
5459
+ }
5460
+ };
5461
+ }
5462
+ function nodeConfigIo() {
5463
+ return {
5464
+ read(path5) {
5465
+ try {
5466
+ return fs.readFileSync(path5, "utf8");
5467
+ } catch {
5468
+ return null;
5469
+ }
5470
+ },
5471
+ write(path5, content) {
5472
+ fs.mkdirSync(path.dirname(path5), { recursive: true });
5473
+ fs.writeFileSync(path5, content, "utf8");
5474
+ }
5475
+ };
5476
+ }
5477
+ var nodeFetch = async (url, init) => {
5478
+ const res = await globalThis.fetch(url, { headers: init?.headers });
5479
+ return {
5480
+ ok: res.ok,
5481
+ status: res.status,
5482
+ text: () => res.text()
5483
+ };
5484
+ };
5485
+
5486
+ // src/commands/add-component.ts
5487
+ function isNotAComponent(err) {
5488
+ return err instanceof IntegrityError && /fetch failed|is not a component/i.test(err.message);
5489
+ }
5490
+ function printReport(result) {
5491
+ const { id, version, installPath, framework, written, packages, env, warnings, configFromDisk } = result;
5492
+ const out = process.stdout;
5493
+ out.write(`
5494
+ \u2713 Installed ${id}@${version} \u2192 ${installPath}/ (${framework.uiBinding} \xD7 ${framework.metaFramework})
5495
+ `);
5496
+ out.write(` wrote ${written.length} file${written.length === 1 ? "" : "s"}
5497
+ `);
5498
+ if (packages.length > 0) {
5499
+ out.write(`
5500
+ Install the packages it composes:
5501
+ npm install ${packages.join(" ")}
5502
+ `);
5503
+ }
5504
+ const required = env.filter((e) => e.required);
5505
+ if (required.length > 0) {
5506
+ out.write(`
5507
+ Required environment:
5508
+ `);
5509
+ for (const e of required) out.write(` ${e.name} \u2014 ${e.description}
5510
+ `);
5511
+ }
5512
+ if (warnings.length > 0) {
5513
+ out.write(`
5514
+ Warnings:
5515
+ `);
5516
+ for (const w of warnings) out.write(` \u26A0 ${w.message}
5517
+ `);
5518
+ }
5519
+ if (!configFromDisk) {
5520
+ out.write(`
5521
+ Tip: run \`agentskit init\` to commit .agentskit/components.json and skip the scan next time.
5522
+ `);
5523
+ }
5524
+ out.write("\n");
5525
+ }
5526
+ async function tryComponentAdd(name, options) {
5527
+ try {
5528
+ const result = await addComponent(
5529
+ { identifier: name, outDir: options.out, force: options.force === true, registryBase: options.registry },
5530
+ {
5531
+ scanFs: nodeScanFs(),
5532
+ io: nodeConfigIo(),
5533
+ writeFs: nodeWriteFs(),
5534
+ fetchImpl: nodeFetch,
5535
+ now: () => (/* @__PURE__ */ new Date()).toISOString(),
5536
+ env: process.env
5537
+ }
5538
+ );
5539
+ printReport(result);
5540
+ return "installed";
5541
+ } catch (err) {
5542
+ if (isNotAComponent(err)) return "not-a-component";
5543
+ throw err;
5544
+ }
5545
+ }
4785
5546
 
4786
5547
  // src/commands/add.ts
4787
5548
  function registerAddCommand(program) {
4788
- program.command("add <agent>").description(
4789
- 'Add a ready-made agent from the AgentsKit registry (registry.agentskit.io). Copies the agent source into your project \u2014 you own the code. With --run, also executes it. e.g. `agentskit add research` or `agentskit add legal-contract-reviewer --run "review this NDA\u2026" --provider ollama`.'
4790
- ).option("--out <dir>", "Directory to write the agent into (default: ./agents)").option("-f, --force", "Overwrite existing files").option("--run <task>", "Run the agent on this task right after adding it").option("--provider <provider>", "Provider for --run (openai, anthropic, gemini, ollama, demo)", "demo").option("--model <model>", "Model for --run").option("--api-key <key>", "API key for --run (else read from the provider env var)").action(
5549
+ program.command("add <name>").description(
5550
+ 'Add a ready-made agent or UI component from the AgentsKit registry (registry.agentskit.io). Auto-detects which: a component is scanned/validated against your project and installed transactionally; an agent is copied into ./agents. You own the code. e.g. `agentskit add docs-chat` (component) or `agentskit add research --run "\u2026"` (agent).'
5551
+ ).option("--out <dir>", "Directory to install into (component: the port dir; agent: ./agents)").option("-f, --force", "Overwrite existing files").option("--registry <url>", "Registry base URL override (component install)").option("--run <task>", "Run the agent on this task right after adding it").option("--provider <provider>", "Provider for --run (openai, anthropic, gemini, ollama, demo)", "demo").option("--model <model>", "Model for --run").option("--api-key <key>", "API key for --run (else read from the provider env var)").action(
4791
5552
  async (agent, options) => {
4792
5553
  try {
5554
+ const outcome = await tryComponentAdd(agent, {
5555
+ out: options.out,
5556
+ force: options.force,
5557
+ registry: options.registry
5558
+ });
5559
+ if (outcome === "installed") return;
4793
5560
  const result = await addAgent(agent, { outDir: options.out, force: options.force === true });
4794
5561
  process.stdout.write(`
4795
5562
  Added "${result.agent.title}" \u2192 ${result.targetDir}/