@agentskit/cli 0.13.9 → 0.13.11

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