@chat-js/cli 0.6.2 → 0.6.3

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/index.js CHANGED
@@ -3844,7 +3844,7 @@ var {
3844
3844
  // package.json
3845
3845
  var package_default = {
3846
3846
  name: "@chat-js/cli",
3847
- version: "0.6.2",
3847
+ version: "0.6.3",
3848
3848
  description: "CLI for creating and extending ChatJS apps",
3849
3849
  license: "Apache-2.0",
3850
3850
  repository: {
@@ -3895,8 +3895,8 @@ var package_default = {
3895
3895
  };
3896
3896
 
3897
3897
  // src/commands/add.ts
3898
- import fs4 from "node:fs/promises";
3899
- import path6 from "node:path";
3898
+ import fs6 from "node:fs/promises";
3899
+ import path8 from "node:path";
3900
3900
 
3901
3901
  // ../../node_modules/.bun/@clack+core@1.2.0/node_modules/@clack/core/dist/index.mjs
3902
3902
  import { styleText as y } from "node:util";
@@ -5184,9 +5184,96 @@ ${c2}
5184
5184
  }
5185
5185
  } }).prompt();
5186
5186
 
5187
- // src/registry/fetch.ts
5188
- import fs from "node:fs/promises";
5187
+ // src/utils/get-config.ts
5188
+ import { spawn } from "node:child_process";
5189
+ import path2 from "node:path";
5190
+
5191
+ // src/utils/get-package-manager.ts
5192
+ import fs from "node:fs";
5189
5193
  import path from "node:path";
5194
+ function inferPackageManager(cwd = process.cwd()) {
5195
+ let currentDir = path.resolve(cwd);
5196
+ while (true) {
5197
+ if (fs.existsSync(path.join(currentDir, "pnpm-lock.yaml")))
5198
+ return "pnpm";
5199
+ if (fs.existsSync(path.join(currentDir, "yarn.lock")))
5200
+ return "yarn";
5201
+ if (fs.existsSync(path.join(currentDir, "package-lock.json")))
5202
+ return "npm";
5203
+ if (fs.existsSync(path.join(currentDir, "bun.lock")) || fs.existsSync(path.join(currentDir, "bun.lockb"))) {
5204
+ return "bun";
5205
+ }
5206
+ const parentDir = path.dirname(currentDir);
5207
+ if (parentDir === currentDir) {
5208
+ break;
5209
+ }
5210
+ currentDir = parentDir;
5211
+ }
5212
+ const ua = process.env.npm_config_user_agent ?? "";
5213
+ if (ua.startsWith("pnpm/"))
5214
+ return "pnpm";
5215
+ if (ua.startsWith("yarn/"))
5216
+ return "yarn";
5217
+ if (ua.startsWith("npm/"))
5218
+ return "npm";
5219
+ if (ua.startsWith("bun/"))
5220
+ return "bun";
5221
+ return "npm";
5222
+ }
5223
+
5224
+ // src/utils/get-config.ts
5225
+ var EVAL_SCRIPT = `
5226
+ import userConfig from "./chat.config.ts";
5227
+ import { applyDefaults } from "./lib/config-schema";
5228
+ process.stdout.write(JSON.stringify(applyDefaults(userConfig)));
5229
+ `;
5230
+ function getTsEvalCommand(pm) {
5231
+ switch (pm) {
5232
+ case "bun":
5233
+ return ["bun", ["--eval", EVAL_SCRIPT]];
5234
+ case "pnpm":
5235
+ return ["pnpm", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
5236
+ case "yarn":
5237
+ return ["yarn", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
5238
+ default:
5239
+ return ["npx", ["tsx", "--eval", EVAL_SCRIPT]];
5240
+ }
5241
+ }
5242
+ async function loadProjectConfig(cwd) {
5243
+ const pm = inferPackageManager(cwd);
5244
+ const [cmd, args] = getTsEvalCommand(pm);
5245
+ const stdout = await new Promise((resolve, reject) => {
5246
+ const child = spawn(cmd, args, {
5247
+ cwd,
5248
+ stdio: ["ignore", "pipe", "pipe"]
5249
+ });
5250
+ const out = [];
5251
+ const err = [];
5252
+ child.stdout?.on("data", (d3) => out.push(String(d3)));
5253
+ child.stderr?.on("data", (d3) => err.push(String(d3)));
5254
+ child.on("error", (e) => reject(new Error(`Could not spawn ${cmd}. Make sure ${pm} is installed.
5255
+ ${e.message}`)));
5256
+ child.on("close", (code) => {
5257
+ if (code === 0)
5258
+ resolve(out.join(""));
5259
+ else
5260
+ reject(new Error(`Failed to load config:
5261
+ ${err.join("").trim()}`));
5262
+ });
5263
+ });
5264
+ const parsed = JSON.parse(stdout);
5265
+ return {
5266
+ paths: {
5267
+ tools: parsed?.paths?.tools ?? "@/tools/chatjs"
5268
+ }
5269
+ };
5270
+ }
5271
+ function resolveToolsPath(alias, cwd) {
5272
+ if (alias.startsWith("@/")) {
5273
+ return path2.join(cwd, alias.slice(2));
5274
+ }
5275
+ return path2.resolve(cwd, alias);
5276
+ }
5190
5277
 
5191
5278
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
5192
5279
  var exports_external = {};
@@ -5953,10 +6040,10 @@ function mergeDefs(...defs) {
5953
6040
  function cloneDef(schema) {
5954
6041
  return mergeDefs(schema._zod.def);
5955
6042
  }
5956
- function getElementAtPath(obj, path) {
5957
- if (!path)
6043
+ function getElementAtPath(obj, path3) {
6044
+ if (!path3)
5958
6045
  return obj;
5959
- return path.reduce((acc, key) => acc?.[key], obj);
6046
+ return path3.reduce((acc, key) => acc?.[key], obj);
5960
6047
  }
5961
6048
  function promiseAllObject(promisesObj) {
5962
6049
  const keys = Object.keys(promisesObj);
@@ -6337,11 +6424,11 @@ function aborted(x, startIndex = 0) {
6337
6424
  }
6338
6425
  return false;
6339
6426
  }
6340
- function prefixIssues(path, issues) {
6427
+ function prefixIssues(path3, issues) {
6341
6428
  return issues.map((iss) => {
6342
6429
  var _a;
6343
6430
  (_a = iss).path ?? (_a.path = []);
6344
- iss.path.unshift(path);
6431
+ iss.path.unshift(path3);
6345
6432
  return iss;
6346
6433
  });
6347
6434
  }
@@ -6524,7 +6611,7 @@ function formatError(error, mapper = (issue2) => issue2.message) {
6524
6611
  }
6525
6612
  function treeifyError(error, mapper = (issue2) => issue2.message) {
6526
6613
  const result = { errors: [] };
6527
- const processError = (error2, path = []) => {
6614
+ const processError = (error2, path3 = []) => {
6528
6615
  var _a, _b;
6529
6616
  for (const issue2 of error2.issues) {
6530
6617
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -6534,7 +6621,7 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
6534
6621
  } else if (issue2.code === "invalid_element") {
6535
6622
  processError({ issues: issue2.issues }, issue2.path);
6536
6623
  } else {
6537
- const fullpath = [...path, ...issue2.path];
6624
+ const fullpath = [...path3, ...issue2.path];
6538
6625
  if (fullpath.length === 0) {
6539
6626
  result.errors.push(mapper(issue2));
6540
6627
  continue;
@@ -6566,8 +6653,8 @@ function treeifyError(error, mapper = (issue2) => issue2.message) {
6566
6653
  }
6567
6654
  function toDotPath(_path) {
6568
6655
  const segs = [];
6569
- const path = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6570
- for (const seg of path) {
6656
+ const path3 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6657
+ for (const seg of path3) {
6571
6658
  if (typeof seg === "number")
6572
6659
  segs.push(`[${seg}]`);
6573
6660
  else if (typeof seg === "symbol")
@@ -18314,13 +18401,13 @@ function resolveRef(ref, ctx) {
18314
18401
  if (!ref.startsWith("#")) {
18315
18402
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
18316
18403
  }
18317
- const path = ref.slice(1).split("/").filter(Boolean);
18318
- if (path.length === 0) {
18404
+ const path3 = ref.slice(1).split("/").filter(Boolean);
18405
+ if (path3.length === 0) {
18319
18406
  return ctx.rootSchema;
18320
18407
  }
18321
18408
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
18322
- if (path[0] === defsKey) {
18323
- const key = path[1];
18409
+ if (path3[0] === defsKey) {
18410
+ const key = path3[1];
18324
18411
  if (!key || !ctx.defs[key]) {
18325
18412
  throw new Error(`Reference not found: ${ref}`);
18326
18413
  }
@@ -18720,173 +18807,6 @@ function date4(params) {
18720
18807
 
18721
18808
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
18722
18809
  config(en_default());
18723
- // src/registry/schema.ts
18724
- var envRequirementSchema = exports_external.object({
18725
- description: exports_external.string().optional(),
18726
- options: exports_external.array(exports_external.array(exports_external.string()))
18727
- });
18728
- var registryToolFileSchema = exports_external.object({
18729
- path: exports_external.string(),
18730
- content: exports_external.string(),
18731
- type: exports_external.enum(["tool", "renderer", "lib", "component", "hook"]),
18732
- target: exports_external.string()
18733
- });
18734
- var registryToolItemSchema = exports_external.object({
18735
- name: exports_external.string(),
18736
- description: exports_external.string().optional(),
18737
- dependencies: exports_external.array(exports_external.string()).optional(),
18738
- devDependencies: exports_external.array(exports_external.string()).optional(),
18739
- registryDependencies: exports_external.array(exports_external.string()).optional(),
18740
- envRequirements: exports_external.array(envRequirementSchema).optional(),
18741
- files: exports_external.array(registryToolFileSchema)
18742
- });
18743
-
18744
- // src/registry/fetch.ts
18745
- var DEFAULT_REGISTRY_URL = "https://registry.chatjs.dev/items/{name}.json";
18746
- function getRegistryUrl(override) {
18747
- return override ?? process.env.CHATJS_REGISTRY_URL ?? DEFAULT_REGISTRY_URL;
18748
- }
18749
- async function fetchRegistryItem(name, registryUrl) {
18750
- const template = getRegistryUrl(registryUrl);
18751
- const resolved = template.replace("{name}", encodeURIComponent(name));
18752
- const isLocalPath = resolved.startsWith(".") || path.isAbsolute(resolved);
18753
- const filePath = resolved.startsWith(".") ? path.resolve(process.cwd(), resolved) : resolved;
18754
- let raw;
18755
- if (isLocalPath) {
18756
- const content = await fs.readFile(filePath, "utf8").catch(() => {
18757
- throw new Error(`Tool "${name}" not found at ${filePath}`);
18758
- });
18759
- raw = JSON.parse(content);
18760
- } else {
18761
- const res = await fetch(filePath).catch(() => {
18762
- throw new Error(`Could not reach registry. Check your internet connection.`);
18763
- });
18764
- if (res.status === 404)
18765
- throw new Error(`Tool "${name}" not found in registry.`);
18766
- if (!res.ok)
18767
- throw new Error(`Registry fetch failed: ${res.status} ${res.statusText}`);
18768
- raw = await res.json();
18769
- }
18770
- return registryToolItemSchema.parse(raw);
18771
- }
18772
-
18773
- // src/registry/resolve.ts
18774
- async function resolveRegistryItems(names, registryUrl) {
18775
- const seen = new Set;
18776
- const items = [];
18777
- async function visit(name) {
18778
- if (seen.has(name)) {
18779
- return;
18780
- }
18781
- seen.add(name);
18782
- const item = await fetchRegistryItem(name, registryUrl);
18783
- for (const dependency of item.registryDependencies ?? []) {
18784
- await visit(dependency);
18785
- }
18786
- items.push(item);
18787
- }
18788
- for (const name of names) {
18789
- await visit(name);
18790
- }
18791
- return {
18792
- items,
18793
- dependencies: Array.from(new Set(items.flatMap((item) => item.dependencies ?? []))),
18794
- devDependencies: Array.from(new Set(items.flatMap((item) => item.devDependencies ?? []))),
18795
- files: items.flatMap((item) => item.files)
18796
- };
18797
- }
18798
-
18799
- // src/utils/get-config.ts
18800
- import { spawn } from "node:child_process";
18801
- import path3 from "node:path";
18802
-
18803
- // src/utils/get-package-manager.ts
18804
- import fs2 from "node:fs";
18805
- import path2 from "node:path";
18806
- function inferPackageManager(cwd = process.cwd()) {
18807
- let currentDir = path2.resolve(cwd);
18808
- while (true) {
18809
- if (fs2.existsSync(path2.join(currentDir, "pnpm-lock.yaml")))
18810
- return "pnpm";
18811
- if (fs2.existsSync(path2.join(currentDir, "yarn.lock")))
18812
- return "yarn";
18813
- if (fs2.existsSync(path2.join(currentDir, "package-lock.json")))
18814
- return "npm";
18815
- if (fs2.existsSync(path2.join(currentDir, "bun.lock")) || fs2.existsSync(path2.join(currentDir, "bun.lockb"))) {
18816
- return "bun";
18817
- }
18818
- const parentDir = path2.dirname(currentDir);
18819
- if (parentDir === currentDir) {
18820
- break;
18821
- }
18822
- currentDir = parentDir;
18823
- }
18824
- const ua = process.env.npm_config_user_agent ?? "";
18825
- if (ua.startsWith("pnpm/"))
18826
- return "pnpm";
18827
- if (ua.startsWith("yarn/"))
18828
- return "yarn";
18829
- if (ua.startsWith("npm/"))
18830
- return "npm";
18831
- if (ua.startsWith("bun/"))
18832
- return "bun";
18833
- return "npm";
18834
- }
18835
-
18836
- // src/utils/get-config.ts
18837
- var EVAL_SCRIPT = `
18838
- import userConfig from "./chat.config.ts";
18839
- import { applyDefaults } from "./lib/config-schema";
18840
- process.stdout.write(JSON.stringify(applyDefaults(userConfig)));
18841
- `;
18842
- function getTsEvalCommand(pm) {
18843
- switch (pm) {
18844
- case "bun":
18845
- return ["bun", ["--eval", EVAL_SCRIPT]];
18846
- case "pnpm":
18847
- return ["pnpm", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
18848
- case "yarn":
18849
- return ["yarn", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
18850
- default:
18851
- return ["npx", ["tsx", "--eval", EVAL_SCRIPT]];
18852
- }
18853
- }
18854
- async function loadProjectConfig(cwd) {
18855
- const pm = inferPackageManager(cwd);
18856
- const [cmd, args] = getTsEvalCommand(pm);
18857
- const stdout = await new Promise((resolve, reject) => {
18858
- const child = spawn(cmd, args, {
18859
- cwd,
18860
- stdio: ["ignore", "pipe", "pipe"]
18861
- });
18862
- const out = [];
18863
- const err = [];
18864
- child.stdout?.on("data", (d3) => out.push(String(d3)));
18865
- child.stderr?.on("data", (d3) => err.push(String(d3)));
18866
- child.on("error", (e) => reject(new Error(`Could not spawn ${cmd}. Make sure ${pm} is installed.
18867
- ${e.message}`)));
18868
- child.on("close", (code) => {
18869
- if (code === 0)
18870
- resolve(out.join(""));
18871
- else
18872
- reject(new Error(`Failed to load config:
18873
- ${err.join("").trim()}`));
18874
- });
18875
- });
18876
- const parsed = JSON.parse(stdout);
18877
- return {
18878
- paths: {
18879
- tools: parsed?.paths?.tools ?? "@/tools/chatjs"
18880
- }
18881
- };
18882
- }
18883
- function resolveToolsPath(alias, cwd) {
18884
- if (alias.startsWith("@/")) {
18885
- return path3.join(cwd, alias.slice(2));
18886
- }
18887
- return path3.resolve(cwd, alias);
18888
- }
18889
-
18890
18810
  // ../../node_modules/.bun/kleur@4.1.5/node_modules/kleur/colors.mjs
18891
18811
  var FORCE_COLOR;
18892
18812
  var NODE_DISABLE_COLORS;
@@ -18993,6 +18913,118 @@ function handleError(error48) {
18993
18913
  process.exit(1);
18994
18914
  }
18995
18915
 
18916
+ // src/utils/install-registry-tools.ts
18917
+ import fs5 from "node:fs/promises";
18918
+ import path7 from "node:path";
18919
+
18920
+ // src/registry/fetch.ts
18921
+ import fs2 from "node:fs/promises";
18922
+ import path3 from "node:path";
18923
+
18924
+ // src/registry/schema.ts
18925
+ var envRequirementSchema = exports_external.object({
18926
+ description: exports_external.string().optional(),
18927
+ options: exports_external.array(exports_external.array(exports_external.string()))
18928
+ });
18929
+ var registryToolFileSchema = exports_external.object({
18930
+ path: exports_external.string(),
18931
+ content: exports_external.string(),
18932
+ type: exports_external.enum(["tool", "renderer", "lib", "component", "hook"]),
18933
+ target: exports_external.string()
18934
+ });
18935
+ var registryToolItemSchema = exports_external.object({
18936
+ name: exports_external.string(),
18937
+ description: exports_external.string().optional(),
18938
+ hidden: exports_external.boolean().optional(),
18939
+ dependencies: exports_external.array(exports_external.string()).optional(),
18940
+ devDependencies: exports_external.array(exports_external.string()).optional(),
18941
+ registryDependencies: exports_external.array(exports_external.string()).optional(),
18942
+ envRequirements: exports_external.array(envRequirementSchema).optional(),
18943
+ files: exports_external.array(registryToolFileSchema)
18944
+ });
18945
+ var registryIndexItemSchema = exports_external.object({
18946
+ name: exports_external.string(),
18947
+ description: exports_external.string().optional(),
18948
+ hidden: exports_external.boolean().optional()
18949
+ });
18950
+
18951
+ // src/registry/fetch.ts
18952
+ var DEFAULT_REGISTRY_URL = "https://unpkg.com/@chat-js/registry@0/items/{name}.json";
18953
+ function getRegistryUrl(override) {
18954
+ return override ?? process.env.CHATJS_REGISTRY_URL ?? DEFAULT_REGISTRY_URL;
18955
+ }
18956
+ function getRegistryIndexUrl(registryUrl) {
18957
+ const template = getRegistryUrl(registryUrl);
18958
+ if (template.includes("{name}")) {
18959
+ return template.replace(/(\{name\}\.json|\{name\})$/, "index.json");
18960
+ }
18961
+ if (template.startsWith(".") || path3.isAbsolute(template)) {
18962
+ return path3.join(path3.dirname(template), "index.json");
18963
+ }
18964
+ return new URL("../index.json", template).toString();
18965
+ }
18966
+ async function fetchJson(source) {
18967
+ const isLocalPath = source.startsWith(".") || path3.isAbsolute(source);
18968
+ const filePath = source.startsWith(".") ? path3.resolve(process.cwd(), source) : source;
18969
+ if (isLocalPath) {
18970
+ const content = await fs2.readFile(filePath, "utf8").catch(() => {
18971
+ throw new Error(`Registry resource not found at ${filePath}`);
18972
+ });
18973
+ return JSON.parse(content);
18974
+ }
18975
+ const res = await fetch(filePath).catch(() => {
18976
+ throw new Error(`Could not reach registry. Check your internet connection.`);
18977
+ });
18978
+ if (res.status === 404) {
18979
+ throw new Error(`Registry resource not found: ${filePath}`);
18980
+ }
18981
+ if (!res.ok) {
18982
+ throw new Error(`Registry fetch failed: ${res.status} ${res.statusText}`);
18983
+ }
18984
+ return res.json();
18985
+ }
18986
+ async function fetchRegistryIndex(registryUrl) {
18987
+ const raw = await fetchJson(getRegistryIndexUrl(registryUrl));
18988
+ return registryIndexItemSchema.array().parse(raw);
18989
+ }
18990
+ async function fetchRegistryItem(name, registryUrl) {
18991
+ const template = getRegistryUrl(registryUrl);
18992
+ const resolved = template.replace("{name}", encodeURIComponent(name));
18993
+ const raw = await fetchJson(resolved).catch((error48) => {
18994
+ if (error48 instanceof Error && error48.message.startsWith("Registry resource not found")) {
18995
+ throw new Error(`Tool "${name}" not found in registry.`);
18996
+ }
18997
+ throw error48;
18998
+ });
18999
+ return registryToolItemSchema.parse(raw);
19000
+ }
19001
+
19002
+ // src/registry/resolve.ts
19003
+ async function resolveRegistryItems(names, registryUrl) {
19004
+ const seen = new Set;
19005
+ const items = [];
19006
+ async function visit(name) {
19007
+ if (seen.has(name)) {
19008
+ return;
19009
+ }
19010
+ seen.add(name);
19011
+ const item = await fetchRegistryItem(name, registryUrl);
19012
+ for (const dependency of item.registryDependencies ?? []) {
19013
+ await visit(dependency);
19014
+ }
19015
+ items.push(item);
19016
+ }
19017
+ for (const name of names) {
19018
+ await visit(name);
19019
+ }
19020
+ return {
19021
+ items,
19022
+ dependencies: Array.from(new Set(items.flatMap((item) => item.dependencies ?? []))),
19023
+ devDependencies: Array.from(new Set(items.flatMap((item) => item.devDependencies ?? []))),
19024
+ files: items.flatMap((item) => item.files)
19025
+ };
19026
+ }
19027
+
18996
19028
  // src/utils/inject-tool.ts
18997
19029
  var MARKERS = {
18998
19030
  toolImports: {
@@ -19085,6 +19117,10 @@ function createEmptyUiTemplate() {
19085
19117
  `;
19086
19118
  }
19087
19119
 
19120
+ // src/utils/install-deps.ts
19121
+ import fs3 from "node:fs/promises";
19122
+ import path4 from "node:path";
19123
+
19088
19124
  // src/utils/run-command.ts
19089
19125
  import { spawn as spawn2 } from "node:child_process";
19090
19126
  async function runCommand(command, args, cwd) {
@@ -19106,13 +19142,31 @@ ${stderr.join("")}`.trim()));
19106
19142
  }
19107
19143
 
19108
19144
  // src/utils/install-deps.ts
19109
- async function installDependencies(deps, devDeps, cwd) {
19145
+ async function updatePackageJsonDependencies(deps, devDeps, cwd) {
19146
+ const packageJsonPath = path4.join(cwd, "package.json");
19147
+ const packageJson = JSON.parse(await fs3.readFile(packageJsonPath, "utf8"));
19148
+ packageJson.dependencies ??= {};
19149
+ packageJson.devDependencies ??= {};
19150
+ for (const dependency of deps) {
19151
+ packageJson.dependencies[dependency] ??= "latest";
19152
+ }
19153
+ for (const dependency of devDeps) {
19154
+ packageJson.devDependencies[dependency] ??= "latest";
19155
+ }
19156
+ await fs3.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
19157
+ `);
19158
+ }
19159
+ async function installDependencies(deps, devDeps, cwd, installNow = true, packageManager) {
19110
19160
  const dependencies = Array.from(new Set(deps));
19111
19161
  const developmentDependencies = Array.from(new Set(devDeps));
19112
19162
  if (!dependencies.length && !developmentDependencies.length) {
19113
19163
  return;
19114
19164
  }
19115
- const pm = inferPackageManager(cwd);
19165
+ if (!installNow) {
19166
+ await updatePackageJsonDependencies(dependencies, developmentDependencies, cwd);
19167
+ return;
19168
+ }
19169
+ const pm = packageManager ?? inferPackageManager(cwd);
19116
19170
  if (dependencies.length) {
19117
19171
  const args = pm === "yarn" ? ["add", ...dependencies] : ["add", ...dependencies];
19118
19172
  await runCommand(pm, args, cwd);
@@ -20499,11 +20553,11 @@ function spinner(text, options) {
20499
20553
  }
20500
20554
 
20501
20555
  // src/utils/write-files.ts
20502
- import fs3 from "node:fs/promises";
20503
- import path5 from "node:path";
20556
+ import fs4 from "node:fs/promises";
20557
+ import path6 from "node:path";
20504
20558
 
20505
20559
  // src/utils/is-safe-target.ts
20506
- import path4 from "node:path";
20560
+ import path5 from "node:path";
20507
20561
  function isSafeTarget(targetPath, root) {
20508
20562
  if (targetPath.includes("\x00")) {
20509
20563
  return false;
@@ -20518,8 +20572,8 @@ function isSafeTarget(targetPath, root) {
20518
20572
  } catch {
20519
20573
  return false;
20520
20574
  }
20521
- const normalizedTarget = path4.normalize(decodedPath.replace(/\\/g, "/"));
20522
- const normalizedRoot = path4.normalize(root);
20575
+ const normalizedTarget = path5.normalize(decodedPath.replace(/\\/g, "/"));
20576
+ const normalizedRoot = path5.normalize(root);
20523
20577
  const targetSegments = decodedPath.replace(/\\/g, "/").split("/").filter(Boolean);
20524
20578
  const normalizedSegments = normalizedTarget.split(/[\\/]+/).filter(Boolean);
20525
20579
  if (targetSegments.includes("..") || normalizedSegments.includes("..")) {
@@ -20528,8 +20582,8 @@ function isSafeTarget(targetPath, root) {
20528
20582
  if (/^[a-zA-Z]:[\\/]/.test(decodedPath)) {
20529
20583
  return false;
20530
20584
  }
20531
- const resolvedPath = path4.isAbsolute(normalizedTarget) ? normalizedTarget : path4.resolve(normalizedRoot, normalizedTarget);
20532
- return resolvedPath === normalizedRoot || resolvedPath.startsWith(`${normalizedRoot}${path4.sep}`);
20585
+ const resolvedPath = path5.isAbsolute(normalizedTarget) ? normalizedTarget : path5.resolve(normalizedRoot, normalizedTarget);
20586
+ return resolvedPath === normalizedRoot || resolvedPath.startsWith(`${normalizedRoot}${path5.sep}`);
20533
20587
  }
20534
20588
 
20535
20589
  // src/utils/write-files.ts
@@ -20537,14 +20591,14 @@ function rewriteToolkitImports(content, toolsAlias) {
20537
20591
  return content.replace(/(["'])@toolkit\/(lib|components|hooks)\/([^"'`]+)\1/g, (_match, quote, kind, rest) => `${quote}${toolsAlias}/_shared/${kind}/${rest}${quote}`);
20538
20592
  }
20539
20593
  async function assertNoSymlinkTraversal(dest, toolsDir) {
20540
- const relativeParent = path5.relative(toolsDir, path5.dirname(dest));
20541
- const segments = relativeParent.split(path5.sep).filter((segment) => segment.length > 0);
20594
+ const relativeParent = path6.relative(toolsDir, path6.dirname(dest));
20595
+ const segments = relativeParent.split(path6.sep).filter((segment) => segment.length > 0);
20542
20596
  let currentPath = toolsDir;
20543
20597
  for (const segment of segments) {
20544
- currentPath = path5.join(currentPath, segment);
20545
- const stat = await fs3.lstat(currentPath).catch(() => null);
20598
+ currentPath = path6.join(currentPath, segment);
20599
+ const stat = await fs4.lstat(currentPath).catch(() => null);
20546
20600
  if (stat?.isSymbolicLink()) {
20547
- throw new Error(`Refusing to write through symlinked path "${path5.relative(toolsDir, currentPath)}"`);
20601
+ throw new Error(`Refusing to write through symlinked path "${path6.relative(toolsDir, currentPath)}"`);
20548
20602
  }
20549
20603
  }
20550
20604
  }
@@ -20559,45 +20613,166 @@ async function writeToolFiles(files, {
20559
20613
  if (!isSafeTarget(file2.target, toolsDir)) {
20560
20614
  throw new Error(`Refusing to write "${file2.target}" outside the tools directory`);
20561
20615
  }
20562
- const dest = path5.resolve(toolsDir, file2.target);
20616
+ const dest = path6.resolve(toolsDir, file2.target);
20563
20617
  await assertNoSymlinkTraversal(dest, toolsDir);
20564
- const exists = await fs3.access(dest).then(() => true).catch(() => false);
20618
+ const exists = await fs4.access(dest).then(() => true).catch(() => false);
20565
20619
  if (exists && !overwrite) {
20566
20620
  existing.push(dest);
20567
20621
  continue;
20568
20622
  }
20569
- await fs3.mkdir(path5.dirname(dest), { recursive: true });
20570
- const realToolsDir = await fs3.realpath(toolsDir).catch(() => path5.resolve(toolsDir));
20571
- const realParentDir = await fs3.realpath(path5.dirname(dest));
20572
- if (realParentDir !== realToolsDir && !realParentDir.startsWith(`${realToolsDir}${path5.sep}`)) {
20623
+ await fs4.mkdir(path6.dirname(dest), { recursive: true });
20624
+ const realToolsDir = await fs4.realpath(toolsDir).catch(() => path6.resolve(toolsDir));
20625
+ const realParentDir = await fs4.realpath(path6.dirname(dest));
20626
+ if (realParentDir !== realToolsDir && !realParentDir.startsWith(`${realToolsDir}${path6.sep}`)) {
20573
20627
  throw new Error(`Refusing to write "${file2.target}" outside the tools directory`);
20574
20628
  }
20575
20629
  if (exists) {
20576
- const stat = await fs3.lstat(dest);
20630
+ const stat = await fs4.lstat(dest);
20577
20631
  if (stat.isSymbolicLink()) {
20578
20632
  throw new Error(`Refusing to overwrite symlinked file "${file2.target}"`);
20579
20633
  }
20580
20634
  }
20581
20635
  const content = dest.endsWith(".ts") || dest.endsWith(".tsx") || dest.endsWith(".js") ? rewriteToolkitImports(file2.content, toolsAlias) : file2.content;
20582
- await fs3.writeFile(dest, content, "utf8");
20636
+ await fs4.writeFile(dest, content, "utf8");
20583
20637
  written.push(dest);
20584
20638
  }
20585
20639
  return { written, existing };
20586
20640
  }
20587
20641
 
20588
- // src/commands/add.ts
20642
+ // src/utils/install-registry-tools.ts
20643
+ function isRequirementSatisfied(requirement, env2) {
20644
+ return requirement.options.some((option) => option.every((name) => Boolean(env2[name])));
20645
+ }
20589
20646
  function formatRequirementDescription(requirement) {
20590
20647
  return requirement.description ?? requirement.options.map((option) => option.join(" + ")).join(" or ");
20591
20648
  }
20649
+ async function installRegistryTools({
20650
+ tools,
20651
+ cwd,
20652
+ toolsDir,
20653
+ toolsAlias,
20654
+ overwrite = false,
20655
+ registryUrl,
20656
+ installDependenciesNow = true,
20657
+ packageManager,
20658
+ confirmOverwrite
20659
+ }) {
20660
+ if (tools.length === 0) {
20661
+ return;
20662
+ }
20663
+ const toolsIndexPath = path7.join(toolsDir, "tools.ts");
20664
+ const uiIndexPath = path7.join(toolsDir, "ui.ts");
20665
+ const fetchSpinner = spinner(tools.length === 1 ? `Fetching ${tools[0]}...` : "Fetching tools...");
20666
+ fetchSpinner.start();
20667
+ let resolution;
20668
+ try {
20669
+ resolution = await resolveRegistryItems(tools, registryUrl);
20670
+ fetchSpinner.succeed(tools.length === 1 ? `Fetched ${tools[0]}` : `Fetched ${tools.length} tools`);
20671
+ } catch (err) {
20672
+ fetchSpinner.fail("Failed to fetch tools");
20673
+ throw err;
20674
+ }
20675
+ const filesToWrite = resolution.files;
20676
+ const dependencies = resolution.dependencies;
20677
+ const devDependencies = resolution.devDependencies;
20678
+ const missingEnvRequirements = resolution.items.flatMap((item) => (item.envRequirements ?? []).filter((requirement) => !isRequirementSatisfied(requirement, process.env)).map((requirement) => ({
20679
+ tool: item.name,
20680
+ requirement: formatRequirementDescription(requirement)
20681
+ })));
20682
+ let effectiveOverwrite = overwrite;
20683
+ const writeSpinner = spinner("Writing files...");
20684
+ writeSpinner.start();
20685
+ const initialWrite = await writeToolFiles(filesToWrite, {
20686
+ overwrite: effectiveOverwrite,
20687
+ toolsDir,
20688
+ toolsAlias
20689
+ });
20690
+ if (initialWrite.existing.length > 0 && !effectiveOverwrite) {
20691
+ writeSpinner.stop();
20692
+ const shouldOverwrite = confirmOverwrite ? await confirmOverwrite(initialWrite.existing) : false;
20693
+ if (!shouldOverwrite) {
20694
+ throw new Error(`Tool install would overwrite existing files. Re-run with overwrite enabled.`);
20695
+ }
20696
+ effectiveOverwrite = true;
20697
+ }
20698
+ let writtenFiles = initialWrite.written;
20699
+ if (effectiveOverwrite && initialWrite.existing.length > 0) {
20700
+ writeSpinner.start();
20701
+ const secondWrite = await writeToolFiles(filesToWrite, {
20702
+ overwrite: true,
20703
+ toolsDir,
20704
+ toolsAlias
20705
+ });
20706
+ writtenFiles = secondWrite.written;
20707
+ }
20708
+ writeSpinner.succeed(writtenFiles.length > 0 ? `Wrote ${writtenFiles.map((file2) => path7.relative(cwd, file2)).join(", ")}` : `No file changes needed for ${tools.join(", ")}`);
20709
+ if (dependencies.length > 0 || devDependencies.length > 0) {
20710
+ const depsSpinner = spinner(installDependenciesNow ? "Installing dependencies..." : "Updating package.json dependencies...");
20711
+ depsSpinner.start();
20712
+ try {
20713
+ await installDependencies(dependencies, devDependencies, cwd, installDependenciesNow, packageManager);
20714
+ const installed = [
20715
+ ...dependencies,
20716
+ ...devDependencies.map((dep) => `${dep} (dev)`)
20717
+ ];
20718
+ depsSpinner.succeed(`${installDependenciesNow ? "Installed" : "Recorded"}: ${installed.join(", ")}`);
20719
+ } catch (err) {
20720
+ depsSpinner.fail(installDependenciesNow ? "Failed to install dependencies" : "Failed to update package.json dependencies");
20721
+ throw err;
20722
+ }
20723
+ }
20724
+ if (missingEnvRequirements.length > 0) {
20725
+ const details = missingEnvRequirements.map(({ tool, requirement }) => `${tool}: ${requirement}`).join(", ");
20726
+ O2.warn(`Missing env vars for installed tools: ${details}`);
20727
+ }
20728
+ const injectSpinner = spinner("Updating tool registry index...");
20729
+ injectSpinner.start();
20730
+ try {
20731
+ let toolsSource;
20732
+ let uiSource;
20733
+ try {
20734
+ toolsSource = await fs5.readFile(toolsIndexPath, "utf8");
20735
+ } catch {
20736
+ toolsSource = createEmptyToolsTemplate();
20737
+ }
20738
+ try {
20739
+ uiSource = await fs5.readFile(uiIndexPath, "utf8");
20740
+ } catch {
20741
+ uiSource = createEmptyUiTemplate();
20742
+ }
20743
+ let updated = { toolsSource, uiSource };
20744
+ for (const name of tools) {
20745
+ const mainItem = resolution.items.find((item) => item.name === name);
20746
+ const shouldInject = mainItem?.files.some((file2) => file2.type === "tool" || file2.type === "renderer") ?? false;
20747
+ if (!shouldInject)
20748
+ continue;
20749
+ updated = injectTool({
20750
+ toolsSource: updated.toolsSource,
20751
+ uiSource: updated.uiSource,
20752
+ name,
20753
+ toolsAlias
20754
+ });
20755
+ }
20756
+ await fs5.mkdir(path7.dirname(toolsIndexPath), { recursive: true });
20757
+ await fs5.writeFile(toolsIndexPath, updated.toolsSource, "utf8");
20758
+ await fs5.writeFile(uiIndexPath, updated.uiSource, "utf8");
20759
+ injectSpinner.succeed("Updated tool registry index");
20760
+ } catch (err) {
20761
+ injectSpinner.fail("Failed to update tool registry index");
20762
+ throw err;
20763
+ }
20764
+ }
20765
+
20766
+ // src/commands/add.ts
20592
20767
  var add = new Command().name("add").description("add a tool to an existing ChatJS project").argument("[tools...]", "tool names to add (e.g. word-count)").option("-y, --yes", "skip confirmation prompt", false).option("-o, --overwrite", "overwrite existing files without prompting", false).option("-c, --cwd <cwd>", "the working directory (defaults to current directory)", process.cwd()).option("-r, --registry <url>", "registry URL or local path template (e.g. ./packages/registry/items/{name}.json)").action(async (tools, opts) => {
20593
20768
  try {
20594
- const cwd = path6.resolve(opts.cwd);
20769
+ const cwd = path8.resolve(opts.cwd);
20595
20770
  if (tools.length === 0) {
20596
20771
  O2.error("Please specify one or more tool names, e.g. chatjs add word-count");
20597
20772
  process.exit(1);
20598
20773
  }
20599
20774
  try {
20600
- await fs4.stat(path6.join(cwd, "chat.config.ts"));
20775
+ await fs6.stat(path8.join(cwd, "chat.config.ts"));
20601
20776
  } catch {
20602
20777
  O2.error("No chat.config.ts found. Run `chat-js create` first or specify --cwd.");
20603
20778
  process.exit(1);
@@ -20614,129 +20789,29 @@ var add = new Command().name("add").description("add a tool to an existing ChatJ
20614
20789
  throw err;
20615
20790
  }
20616
20791
  const toolsDir = resolveToolsPath(config2.paths.tools, cwd);
20617
- const toolsIndexPath = path6.join(toolsDir, "tools.ts");
20618
- const uiIndexPath = path6.join(toolsDir, "ui.ts");
20619
20792
  if (!opts.yes) {
20620
20793
  const answer = await ot2({
20621
- message: `Install ${tools.join(", ")} into ${path6.relative(process.cwd(), toolsDir)}/?`
20794
+ message: `Install ${tools.join(", ")} into ${path8.relative(process.cwd(), toolsDir)}/?`
20622
20795
  });
20623
20796
  if (q(answer) || !answer) {
20624
20797
  gt("Cancelled.");
20625
20798
  process.exit(0);
20626
20799
  }
20627
20800
  }
20628
- const processedItems = new Set;
20629
- for (const name of tools) {
20630
- O2.step(`Installing ${name}`);
20631
- const fetchSpinner = spinner(`Fetching ${name}...`);
20632
- fetchSpinner.start();
20633
- let resolution;
20634
- try {
20635
- resolution = await resolveRegistryItems([name], opts.registry);
20636
- fetchSpinner.succeed(`Fetched ${name}`);
20637
- } catch (err) {
20638
- fetchSpinner.fail(`Failed to fetch ${name}`);
20639
- throw err;
20640
- }
20641
- const itemsToInstall = resolution.items.filter((item) => {
20642
- if (processedItems.has(item.name)) {
20643
- return false;
20644
- }
20645
- processedItems.add(item.name);
20646
- return true;
20647
- });
20648
- const filesToWrite = itemsToInstall.flatMap((item) => item.files);
20649
- const dependencies = Array.from(new Set(itemsToInstall.flatMap((item) => item.dependencies ?? [])));
20650
- const devDependencies = Array.from(new Set(itemsToInstall.flatMap((item) => item.devDependencies ?? [])));
20651
- const requiredEnvRequirements = itemsToInstall.flatMap((item) => (item.envRequirements ?? []).map((requirement) => ({
20652
- tool: item.name,
20653
- requirement: formatRequirementDescription(requirement)
20654
- })));
20655
- const mainItem = resolution.items.find((item) => item.name === name);
20656
- const overwrite = opts.overwrite;
20657
- try {
20658
- const writeSpinner = spinner("Writing files...");
20659
- writeSpinner.start();
20660
- const { written, existing } = await writeToolFiles(filesToWrite, {
20661
- overwrite,
20662
- toolsDir,
20663
- toolsAlias: config2.paths.tools
20801
+ await installRegistryTools({
20802
+ tools,
20803
+ cwd,
20804
+ toolsDir,
20805
+ toolsAlias: config2.paths.tools,
20806
+ overwrite: opts.overwrite,
20807
+ registryUrl: opts.registry,
20808
+ confirmOverwrite: async (existingFiles) => {
20809
+ const answer = await ot2({
20810
+ message: `${existingFiles.map((file2) => path8.relative(cwd, file2)).join(", ")} already exist. Overwrite?`
20664
20811
  });
20665
- writeSpinner.stop();
20666
- if (existing.length > 0 && !overwrite) {
20667
- const answer = await ot2({
20668
- message: `${existing.map((f) => path6.relative(cwd, f)).join(", ")} already exist. Overwrite?`
20669
- });
20670
- if (q(answer) || !answer) {
20671
- O2.warn(`Kept existing files for ${name}; installed remaining new files`);
20672
- } else {
20673
- const { written: rest } = await writeToolFiles(filesToWrite, {
20674
- overwrite: true,
20675
- toolsDir,
20676
- toolsAlias: config2.paths.tools
20677
- });
20678
- written.push(...rest);
20679
- }
20680
- }
20681
- if (written.length > 0) {
20682
- O2.step(written.map((f) => path6.relative(cwd, f)).join(", "));
20683
- } else {
20684
- O2.step(`No file changes needed for ${name}`);
20685
- }
20686
- } catch (err) {
20687
- O2.error("Failed to write files");
20688
- throw err;
20812
+ return !(q(answer) || !answer);
20689
20813
  }
20690
- if (dependencies.length > 0 || devDependencies.length > 0) {
20691
- const depsSpinner = spinner("Installing dependencies...");
20692
- depsSpinner.start();
20693
- try {
20694
- await installDependencies(dependencies, devDependencies, cwd);
20695
- const installed = [
20696
- ...dependencies,
20697
- ...devDependencies.map((dep) => `${dep} (dev)`)
20698
- ];
20699
- depsSpinner.succeed(`Installed: ${installed.join(", ")}`);
20700
- } catch (err) {
20701
- depsSpinner.fail("Failed to install dependencies");
20702
- throw err;
20703
- }
20704
- }
20705
- if (requiredEnvRequirements.length > 0) {
20706
- const details = requiredEnvRequirements.map(({ tool, requirement }) => `${tool}: ${requirement}`).join(", ");
20707
- O2.warn(`Required env vars for installed tools: ${details}`);
20708
- }
20709
- const injectSpinner = spinner("Updating tool registry index...");
20710
- injectSpinner.start();
20711
- try {
20712
- let toolsSource;
20713
- let uiSource;
20714
- try {
20715
- toolsSource = await fs4.readFile(toolsIndexPath, "utf8");
20716
- } catch {
20717
- toolsSource = createEmptyToolsTemplate();
20718
- }
20719
- try {
20720
- uiSource = await fs4.readFile(uiIndexPath, "utf8");
20721
- } catch {
20722
- uiSource = createEmptyUiTemplate();
20723
- }
20724
- const shouldInject = mainItem?.files.some((file2) => file2.type === "tool" || file2.type === "renderer") ?? false;
20725
- const updated = shouldInject ? injectTool({
20726
- toolsSource,
20727
- uiSource,
20728
- name,
20729
- toolsAlias: config2.paths.tools
20730
- }) : { toolsSource, uiSource };
20731
- await fs4.mkdir(path6.dirname(toolsIndexPath), { recursive: true });
20732
- await fs4.writeFile(toolsIndexPath, updated.toolsSource, "utf8");
20733
- await fs4.writeFile(uiIndexPath, updated.uiSource, "utf8");
20734
- injectSpinner.succeed("Updated tool registry index");
20735
- } catch (err) {
20736
- injectSpinner.fail("Failed to update tool registry index");
20737
- throw err;
20738
- }
20739
- }
20814
+ });
20740
20815
  gt(`Done! ${tools.length === 1 ? `"${tools[0]}"` : `${tools.length} tools`} installed successfully.`);
20741
20816
  } catch (error48) {
20742
20817
  handleError(error48);
@@ -20745,7 +20820,7 @@ var add = new Command().name("add").description("add a tool to an existing ChatJ
20745
20820
 
20746
20821
  // src/commands/config.ts
20747
20822
  import { spawn as spawn3 } from "node:child_process";
20748
- import path7 from "node:path";
20823
+ import path9 from "node:path";
20749
20824
  var EVAL_SCRIPT2 = `
20750
20825
  import userConfig from "./chat.config.ts";
20751
20826
  import { applyDefaults } from "./lib/config-schema";
@@ -20765,7 +20840,7 @@ function getTsEvalCommand2(pm) {
20765
20840
  }
20766
20841
  var config2 = new Command().name("config").description("print the resolved configuration for the current ChatJS project").option("-c, --cwd <cwd>", "the working directory (defaults to current directory)", process.cwd()).action(async (opts) => {
20767
20842
  try {
20768
- const cwd = path7.resolve(opts.cwd);
20843
+ const cwd = path9.resolve(opts.cwd);
20769
20844
  const pm = inferPackageManager();
20770
20845
  const [cmd, args] = getTsEvalCommand2(pm);
20771
20846
  await new Promise((resolve, reject) => {
@@ -20812,6 +20887,16 @@ var DESCRIPTIONS = new Map([
20812
20887
  "authentication.vercel",
20813
20888
  "Vercel OAuth (requires VERCEL_APP_CLIENT_ID + VERCEL_APP_CLIENT_SECRET)"
20814
20889
  ],
20890
+ ["ai.tools.mcp.enabled", "Requires MCP_ENCRYPTION_KEY"],
20891
+ ["ai.tools.documents.enabled", "Document create/edit/review support"],
20892
+ ["ai.tools.webSearch.enabled", "Requires TAVILY_API_KEY or FIRECRAWL_API_KEY"],
20893
+ ["ai.tools.urlRetrieval.enabled", "Requires FIRECRAWL_API_KEY"],
20894
+ [
20895
+ "ai.tools.codeExecution.enabled",
20896
+ "Requires Vercel sandbox credentials outside Vercel"
20897
+ ],
20898
+ ["ai.tools.image.enabled", "Requires BLOB_READ_WRITE_TOKEN"],
20899
+ ["ai.tools.deepResearch.enabled", "Requires web search access"],
20815
20900
  [
20816
20901
  "paths.tools",
20817
20902
  "Import alias for the installable tools registry index and tool files"
@@ -20853,14 +20938,14 @@ ${spaces}}`;
20853
20938
  function generateConfig(obj, indent, pathPrefix) {
20854
20939
  const spaces = " ".repeat(indent);
20855
20940
  return Object.entries(obj).map(([key, value]) => {
20856
- const path8 = pathPrefix ? `${pathPrefix}.${key}` : key;
20857
- const desc = DESCRIPTIONS.get(path8);
20941
+ const path10 = pathPrefix ? `${pathPrefix}.${key}` : key;
20942
+ const desc = DESCRIPTIONS.get(path10);
20858
20943
  const comment = desc ? ` // ${desc}` : "";
20859
20944
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
20860
- const nested = generateConfig(value, indent + 1, path8);
20945
+ const nested = generateConfig(value, indent + 1, path10);
20861
20946
  return `${spaces}${formatKey(key)}: {
20862
20947
  ${nested}
20863
- ${spaces}},`;
20948
+ ${spaces}},${comment}`;
20864
20949
  }
20865
20950
  return `${spaces}${formatKey(key)}: ${formatValue(value, indent)},${comment}`;
20866
20951
  }).join(`
@@ -20884,7 +20969,10 @@ function buildConfigTs(input) {
20884
20969
  aiProviders: ["OpenAI", "Anthropic", "Google"],
20885
20970
  paymentProcessors: []
20886
20971
  },
20887
- features: input.features,
20972
+ features: {
20973
+ attachments: input.coreFeatures.attachments,
20974
+ parallelResponses: input.coreFeatures.parallelResponses
20975
+ },
20888
20976
  legal: {
20889
20977
  minimumAge: 13,
20890
20978
  governingLaw: "United States",
@@ -20898,7 +20986,39 @@ function buildConfigTs(input) {
20898
20986
  desktopApp: {
20899
20987
  enabled: input.withElectron
20900
20988
  },
20901
- ai: { gateway: input.gateway },
20989
+ ai: {
20990
+ gateway: input.gateway,
20991
+ tools: {
20992
+ mcp: {
20993
+ enabled: input.coreFeatures.mcp
20994
+ },
20995
+ followupSuggestions: {
20996
+ enabled: input.coreFeatures.followupSuggestions
20997
+ },
20998
+ documents: {
20999
+ enabled: input.coreFeatures.documents,
21000
+ types: input.documentTypes
21001
+ },
21002
+ webSearch: {
21003
+ enabled: input.builtInTools.webSearch
21004
+ },
21005
+ urlRetrieval: {
21006
+ enabled: input.builtInTools.urlRetrieval
21007
+ },
21008
+ deepResearch: {
21009
+ enabled: input.builtInTools.deepResearch
21010
+ },
21011
+ codeExecution: {
21012
+ enabled: input.builtInTools.codeExecution
21013
+ },
21014
+ image: {
21015
+ enabled: input.builtInTools.imageGeneration
21016
+ },
21017
+ video: {
21018
+ enabled: input.builtInTools.videoGeneration
21019
+ }
21020
+ }
21021
+ },
20902
21022
  paths: {
20903
21023
  tools: "@/tools/chatjs"
20904
21024
  },
@@ -20973,7 +21093,17 @@ var gatewayEnvRequirements = {
20973
21093
  description: "OPENAI_COMPATIBLE_BASE_URL + OPENAI_COMPATIBLE_API_KEY"
20974
21094
  }
20975
21095
  };
20976
- var featureEnvRequirements = {
21096
+ var coreFeatureEnvRequirements = {
21097
+ mcp: {
21098
+ options: [["MCP_ENCRYPTION_KEY"]],
21099
+ description: "MCP_ENCRYPTION_KEY"
21100
+ },
21101
+ attachments: {
21102
+ options: [["BLOB_READ_WRITE_TOKEN"]],
21103
+ description: "BLOB_READ_WRITE_TOKEN"
21104
+ }
21105
+ };
21106
+ var builtInToolEnvRequirements = {
20977
21107
  webSearch: {
20978
21108
  options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
20979
21109
  description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
@@ -20986,24 +21116,20 @@ var featureEnvRequirements = {
20986
21116
  options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
20987
21117
  description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
20988
21118
  },
20989
- mcp: {
20990
- options: [["MCP_ENCRYPTION_KEY"]],
20991
- description: "MCP_ENCRYPTION_KEY"
21119
+ codeExecution: {
21120
+ options: [
21121
+ ["VERCEL_OIDC_TOKEN"],
21122
+ ["VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", "VERCEL_TOKEN"]
21123
+ ],
21124
+ description: "VERCEL_OIDC_TOKEN or VERCEL_TEAM_ID + VERCEL_PROJECT_ID + VERCEL_TOKEN"
20992
21125
  },
20993
21126
  imageGeneration: {
20994
21127
  options: [["BLOB_READ_WRITE_TOKEN"]],
20995
21128
  description: "BLOB_READ_WRITE_TOKEN"
20996
21129
  },
20997
- attachments: {
21130
+ videoGeneration: {
20998
21131
  options: [["BLOB_READ_WRITE_TOKEN"]],
20999
21132
  description: "BLOB_READ_WRITE_TOKEN"
21000
- },
21001
- sandbox: {
21002
- options: [
21003
- ["VERCEL_OIDC_TOKEN"],
21004
- ["VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", "VERCEL_TOKEN"]
21005
- ],
21006
- description: "VERCEL_OIDC_TOKEN or VERCEL_TEAM_ID + VERCEL_PROJECT_ID + VERCEL_TOKEN"
21007
21133
  }
21008
21134
  };
21009
21135
  var authEnvRequirements = {
@@ -21052,16 +21178,21 @@ var GATEWAYS = [
21052
21178
  "openai-compatible"
21053
21179
  ];
21054
21180
  var AUTH_PROVIDERS = ["google", "github", "vercel"];
21055
- var FEATURE_KEYS = [
21056
- "sandbox",
21181
+ var CORE_FEATURE_KEYS = [
21182
+ "attachments",
21183
+ "parallelResponses",
21184
+ "documents",
21185
+ "mcp",
21186
+ "followupSuggestions"
21187
+ ];
21188
+ var DOCUMENT_TYPE_KEYS = ["text", "code", "sheet"];
21189
+ var BUILT_IN_TOOL_KEYS = [
21057
21190
  "webSearch",
21058
21191
  "urlRetrieval",
21059
21192
  "deepResearch",
21060
- "mcp",
21193
+ "codeExecution",
21061
21194
  "imageGeneration",
21062
- "attachments",
21063
- "followupSuggestions",
21064
- "parallelResponses"
21195
+ "videoGeneration"
21065
21196
  ];
21066
21197
 
21067
21198
  // src/helpers/env-checklist.ts
@@ -21095,10 +21226,21 @@ function collectEnvChecklist(input) {
21095
21226
  entries.push(...gwEntries);
21096
21227
  const featureItems = [];
21097
21228
  const seen = new Set;
21098
- for (const feature of FEATURE_KEYS) {
21099
- if (!input.features[feature])
21229
+ for (const feature of CORE_FEATURE_KEYS) {
21230
+ if (!input.coreFeatures[feature])
21231
+ continue;
21232
+ const requirement = coreFeatureEnvRequirements[feature];
21233
+ if (!requirement)
21234
+ continue;
21235
+ if (seen.has(requirement.description))
21100
21236
  continue;
21101
- const requirement = featureEnvRequirements[feature];
21237
+ seen.add(requirement.description);
21238
+ featureItems.push(...requirementToEntries(requirement));
21239
+ }
21240
+ for (const tool of BUILT_IN_TOOL_KEYS) {
21241
+ if (!input.builtInTools[tool])
21242
+ continue;
21243
+ const requirement = builtInToolEnvRequirements[tool];
21102
21244
  if (!requirement)
21103
21245
  continue;
21104
21246
  if (seen.has(requirement.description))
@@ -21118,32 +21260,63 @@ function collectEnvChecklist(input) {
21118
21260
  }
21119
21261
 
21120
21262
  // src/helpers/prompts.ts
21121
- var FEATURE_DEFAULTS = {
21122
- sandbox: false,
21263
+ var CORE_FEATURE_DEFAULTS = {
21264
+ attachments: false,
21265
+ parallelResponses: true,
21266
+ documents: true,
21267
+ mcp: false,
21268
+ followupSuggestions: true
21269
+ };
21270
+ var DOCUMENT_TYPE_DEFAULTS = {
21271
+ text: true,
21272
+ code: true,
21273
+ sheet: true
21274
+ };
21275
+ var BUILT_IN_TOOL_DEFAULTS = {
21123
21276
  webSearch: false,
21124
21277
  urlRetrieval: false,
21125
21278
  deepResearch: false,
21126
- mcp: false,
21279
+ codeExecution: false,
21127
21280
  imageGeneration: false,
21128
- attachments: false,
21129
- followupSuggestions: true,
21130
- parallelResponses: true
21281
+ videoGeneration: false
21131
21282
  };
21132
21283
  var AUTH_DEFAULTS = {
21133
21284
  google: false,
21134
21285
  github: true,
21135
21286
  vercel: false
21136
21287
  };
21137
- var FEATURE_LABELS = {
21138
- sandbox: "Code Sandbox",
21288
+ var CORE_FEATURE_LABELS = {
21289
+ attachments: "Attachments",
21290
+ parallelResponses: "Parallel Responses",
21291
+ documents: "Documents",
21292
+ mcp: "MCP Tool Servers",
21293
+ followupSuggestions: "Follow-up Suggestions"
21294
+ };
21295
+ var DOCUMENT_TYPE_LABELS = {
21296
+ text: "Text Documents",
21297
+ code: "Code Documents",
21298
+ sheet: "Spreadsheet Documents"
21299
+ };
21300
+ var DOCUMENT_TYPE_HINTS = {
21301
+ text: "Notes, guides, markdown, and long-form writing",
21302
+ code: "Code files and snippets",
21303
+ sheet: "CSV-based tables and structured data"
21304
+ };
21305
+ var BUILT_IN_TOOL_LABELS = {
21139
21306
  webSearch: "Web Search",
21140
21307
  urlRetrieval: "URL Retrieval",
21141
21308
  deepResearch: "Deep Research",
21142
- mcp: "MCP Tool Servers",
21309
+ codeExecution: "Code Sandbox",
21143
21310
  imageGeneration: "Image Generation",
21144
- attachments: "File Attachments",
21145
- followupSuggestions: "Follow-up Suggestions",
21146
- parallelResponses: "Parallel Responses"
21311
+ videoGeneration: "Video Generation"
21312
+ };
21313
+ var BUILT_IN_TOOL_HINTS = {
21314
+ webSearch: "Search the web from chat",
21315
+ urlRetrieval: "Fetch structured content from a specific URL",
21316
+ deepResearch: "Run multi-step web research and generate reports",
21317
+ codeExecution: "Execute code in a sandboxed environment",
21318
+ imageGeneration: "Generate images inside chat",
21319
+ videoGeneration: "Generate videos inside chat"
21147
21320
  };
21148
21321
  var AUTH_LABELS = {
21149
21322
  google: "Google OAuth",
@@ -21159,9 +21332,13 @@ function handleCancel(value) {
21159
21332
  function toKebabCase(value) {
21160
21333
  return (value ?? "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
21161
21334
  }
21335
+ function toSelectionRecord(keys, selected) {
21336
+ return Object.fromEntries(keys.map((key) => [key, selected.includes(key)]));
21337
+ }
21162
21338
  async function promptProjectName(targetArg, skipPrompt) {
21163
- if (skipPrompt)
21339
+ if (skipPrompt) {
21164
21340
  return toKebabCase(targetArg ?? "my-chat-app") || "my-chat-app";
21341
+ }
21165
21342
  const name = await Ot({
21166
21343
  message: "What is your project named?",
21167
21344
  initialValue: targetArg ?? "my-chat-app",
@@ -21189,32 +21366,76 @@ async function promptGateway(skipPrompt) {
21189
21366
  handleCancel(gateway);
21190
21367
  return gateway;
21191
21368
  }
21192
- async function promptFeatures(skipPrompt) {
21369
+ async function promptCoreFeatures(skipPrompt) {
21193
21370
  if (skipPrompt)
21194
- return { ...FEATURE_DEFAULTS };
21195
- const defaultFeatures = FEATURE_KEYS.filter((key) => FEATURE_DEFAULTS[key]);
21196
- const selectedFeatures = await yt({
21197
- message: `Which ${highlighter.info("features")} would you like to enable? ${highlighter.dim("(space to toggle, enter to submit)")}`,
21198
- options: FEATURE_KEYS.map((key) => ({
21371
+ return { ...CORE_FEATURE_DEFAULTS };
21372
+ const selected = await yt({
21373
+ message: `Which ${highlighter.info("core features")} would you like to enable? ${highlighter.dim("(space to toggle, enter to submit)")}`,
21374
+ options: CORE_FEATURE_KEYS.map((key) => ({
21199
21375
  value: key,
21200
- label: FEATURE_LABELS[key],
21201
- hint: featureEnvRequirements[key]?.description
21376
+ label: CORE_FEATURE_LABELS[key],
21377
+ hint: key === "documents" ? "Create, edit, and review documents in chat" : coreFeatureEnvRequirements[key]?.description
21202
21378
  })),
21203
- initialValues: defaultFeatures,
21379
+ initialValues: CORE_FEATURE_KEYS.filter((key) => CORE_FEATURE_DEFAULTS[key]),
21204
21380
  required: false
21205
21381
  });
21206
- handleCancel(selectedFeatures);
21207
- const features = { ...FEATURE_DEFAULTS };
21208
- for (const key of FEATURE_KEYS) {
21209
- features[key] = false;
21382
+ handleCancel(selected);
21383
+ return toSelectionRecord(CORE_FEATURE_KEYS, selected);
21384
+ }
21385
+ async function promptDocumentTypes(skipPrompt, documentsEnabled) {
21386
+ if (!documentsEnabled) {
21387
+ return toSelectionRecord(DOCUMENT_TYPE_KEYS, []);
21210
21388
  }
21211
- for (const key of selectedFeatures) {
21212
- features[key] = true;
21389
+ if (skipPrompt)
21390
+ return { ...DOCUMENT_TYPE_DEFAULTS };
21391
+ const selected = await yt({
21392
+ message: `Which ${highlighter.info("document types")} would you like to enable? ${highlighter.dim("(space to toggle, enter to submit)")}`,
21393
+ options: DOCUMENT_TYPE_KEYS.map((key) => ({
21394
+ value: key,
21395
+ label: DOCUMENT_TYPE_LABELS[key],
21396
+ hint: DOCUMENT_TYPE_HINTS[key]
21397
+ })),
21398
+ initialValues: DOCUMENT_TYPE_KEYS.filter((key) => DOCUMENT_TYPE_DEFAULTS[key]),
21399
+ required: false
21400
+ });
21401
+ handleCancel(selected);
21402
+ return toSelectionRecord(DOCUMENT_TYPE_KEYS, selected);
21403
+ }
21404
+ async function promptAssistantTools(registryItems, skipPrompt) {
21405
+ const installableItems = registryItems.filter((item) => !item.hidden);
21406
+ if (skipPrompt) {
21407
+ return {
21408
+ builtInTools: { ...BUILT_IN_TOOL_DEFAULTS },
21409
+ installableTools: []
21410
+ };
21213
21411
  }
21214
- if (features.deepResearch) {
21215
- features.webSearch = true;
21412
+ const selected = await yt({
21413
+ message: `Which ${highlighter.info("assistant tools")} would you like to enable? ${highlighter.dim("(space to toggle, enter to submit)")}`,
21414
+ options: [
21415
+ ...BUILT_IN_TOOL_KEYS.map((key) => ({
21416
+ value: key,
21417
+ label: BUILT_IN_TOOL_LABELS[key],
21418
+ hint: builtInToolEnvRequirements[key]?.description ?? BUILT_IN_TOOL_HINTS[key]
21419
+ })),
21420
+ ...installableItems.map((item) => ({
21421
+ value: item.name,
21422
+ label: item.name,
21423
+ hint: item.description
21424
+ }))
21425
+ ],
21426
+ initialValues: BUILT_IN_TOOL_KEYS.filter((key) => BUILT_IN_TOOL_DEFAULTS[key]),
21427
+ required: false
21428
+ });
21429
+ handleCancel(selected);
21430
+ const selectedValues = selected;
21431
+ const builtInTools = toSelectionRecord(BUILT_IN_TOOL_KEYS, selectedValues.filter((value) => BUILT_IN_TOOL_KEYS.includes(value)));
21432
+ if (builtInTools.deepResearch) {
21433
+ builtInTools.webSearch = true;
21216
21434
  }
21217
- return features;
21435
+ return {
21436
+ builtInTools,
21437
+ installableTools: selectedValues.filter((value) => !BUILT_IN_TOOL_KEYS.includes(value))
21438
+ };
21218
21439
  }
21219
21440
  async function promptAuth(skipPrompt) {
21220
21441
  if (skipPrompt)
@@ -21238,15 +21459,7 @@ async function promptAuth(skipPrompt) {
21238
21459
  logger.warn("At least one auth provider is required. Please select one.");
21239
21460
  }
21240
21461
  }
21241
- const auth = {
21242
- google: false,
21243
- github: false,
21244
- vercel: false
21245
- };
21246
- for (const p2 of selectedProviders) {
21247
- auth[p2] = true;
21248
- }
21249
- return auth;
21462
+ return toSelectionRecord(AUTH_PROVIDERS, selectedProviders);
21250
21463
  }
21251
21464
  async function promptElectron(skipPrompt, explicitChoice) {
21252
21465
  if (typeof explicitChoice === "boolean") {
@@ -21527,7 +21740,7 @@ async function applyElectronTemplateSourceTransforms(destination) {
21527
21740
  await replaceInFile(tsconfigPath, [['"../chat/*"', '"../*"']]);
21528
21741
  const packageJsonPath = join(destination, "package.json");
21529
21742
  await replaceInFile(packageJsonPath, [
21530
- ['"name": "@chatjs/electron"', '"name": "__PROJECT_NAME__-electron"'],
21743
+ ['"name": "@chat-js/electron"', '"name": "__PROJECT_NAME__-electron"'],
21531
21744
  [
21532
21745
  '"url": "https://github.com/FranciscoMoretti/chat-js.git"',
21533
21746
  '"url": "https://github.com/__GITHUB_OWNER__/__GITHUB_REPO__.git"'
@@ -21720,9 +21933,10 @@ var createOptionsSchema = exports_external.object({
21720
21933
  install: exports_external.boolean(),
21721
21934
  electron: exports_external.boolean().optional(),
21722
21935
  fromGit: exports_external.string().optional(),
21936
+ registry: exports_external.string().optional(),
21723
21937
  packageManager: exports_external.enum(["bun", "npm", "pnpm", "yarn"]).optional()
21724
21938
  });
21725
- var create = new Command().name("create").description("scaffold a new ChatJS chat application").argument("[directory]", "target directory for the project").option("-y, --yes", "skip prompts and use defaults", false).option("--no-install", "skip dependency installation").option("--electron", "include the Electron desktop app").option("--no-electron", "do not include the Electron desktop app").option("--package-manager <manager>", "package manager for install + next steps (bun, npm, pnpm, yarn)").option("--from-git <url>", "clone from a git repository instead of the built-in scaffold").action(async (directory, opts) => {
21939
+ var create = new Command().name("create").description("scaffold a new ChatJS chat application").argument("[directory]", "target directory for the project").option("-y, --yes", "skip prompts and use defaults", false).option("--no-install", "skip dependency installation").option("--electron", "include the Electron desktop app").option("--no-electron", "do not include the Electron desktop app").option("-r, --registry <url>", "registry URL or local path template (e.g. ./packages/registry/items/{name}.json)").option("--package-manager <manager>", "package manager for install + next steps (bun, npm, pnpm, yarn)").option("--from-git <url>", "clone from a git repository instead of the built-in scaffold").action(async (directory, opts) => {
21726
21940
  try {
21727
21941
  const options = createOptionsSchema.parse({
21728
21942
  target: directory,
@@ -21733,16 +21947,29 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
21733
21947
  mt("Create ChatJS App");
21734
21948
  }
21735
21949
  const initialTarget = resolveCreateTarget(options.target);
21736
- const promptedProjectName = await promptProjectName(initialTarget.projectName, options.yes);
21737
- const projectName = promptedProjectName;
21950
+ const projectName = await promptProjectName(initialTarget.projectName, options.yes);
21738
21951
  const targetDir = options.target ? initialTarget.targetDir : resolve2(process.cwd(), projectName);
21739
21952
  const displayPath = options.target ? initialTarget.displayPath : projectName;
21740
21953
  await ensureTargetEmpty(targetDir);
21741
- const appName = projectName.split("-").map((w3) => w3.charAt(0).toUpperCase() + w3.slice(1)).join(" ");
21954
+ const appName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
21742
21955
  const appPrefix = projectName;
21743
21956
  const appUrl = "http://localhost:3000";
21744
21957
  const gateway = await promptGateway(options.yes);
21745
- const features = await promptFeatures(options.yes);
21958
+ const coreFeatures = await promptCoreFeatures(options.yes);
21959
+ const documentTypes = await promptDocumentTypes(options.yes, coreFeatures.documents);
21960
+ let registryItems = [];
21961
+ if (!options.yes) {
21962
+ const registrySpinner = spinner("Loading installable tools...");
21963
+ registrySpinner.start();
21964
+ try {
21965
+ registryItems = await fetchRegistryIndex(options.registry);
21966
+ registrySpinner.succeed("Installable tools loaded.");
21967
+ } catch (error48) {
21968
+ registrySpinner.fail("Could not load installable tools.");
21969
+ logger.warn(error48 instanceof Error ? error48.message : "Continuing with built-in tools only.");
21970
+ }
21971
+ }
21972
+ const assistantTools = await promptAssistantTools(registryItems, options.yes);
21746
21973
  const auth = await promptAuth(options.yes);
21747
21974
  const withElectron = await promptElectron(options.yes, options.electron);
21748
21975
  logger.break();
@@ -21777,7 +22004,9 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
21777
22004
  appUrl,
21778
22005
  withElectron,
21779
22006
  gateway,
21780
- features,
22007
+ coreFeatures,
22008
+ documentTypes,
22009
+ builtInTools: assistantTools.builtInTools,
21781
22010
  auth
21782
22011
  });
21783
22012
  await writeFile2(join2(targetDir, "chat.config.ts"), configSource);
@@ -21787,6 +22016,18 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
21787
22016
  throw error48;
21788
22017
  }
21789
22018
  const installNow = !options.install ? false : await promptInstall(packageManager, options.yes);
22019
+ if (assistantTools.installableTools.length > 0) {
22020
+ const toolsDir = resolveToolsPath("@/tools/chatjs", targetDir);
22021
+ await installRegistryTools({
22022
+ tools: assistantTools.installableTools,
22023
+ cwd: targetDir,
22024
+ toolsDir,
22025
+ toolsAlias: "@/tools/chatjs",
22026
+ registryUrl: options.registry,
22027
+ installDependenciesNow: false,
22028
+ packageManager
22029
+ });
22030
+ }
21790
22031
  if (installNow) {
21791
22032
  const installSpinner = spinner(`Installing dependencies with ${highlighter.info(packageManager)}...`).start();
21792
22033
  try {
@@ -21797,7 +22038,12 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
21797
22038
  throw error48;
21798
22039
  }
21799
22040
  }
21800
- const envEntries = collectEnvChecklist({ gateway, features, auth });
22041
+ const envEntries = collectEnvChecklist({
22042
+ gateway,
22043
+ coreFeatures,
22044
+ builtInTools: assistantTools.builtInTools,
22045
+ auth
22046
+ });
21801
22047
  gt("Your ChatJS app is ready!");
21802
22048
  logger.info("Next steps:");
21803
22049
  logger.break();