@chat-js/cli 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/index.js +216 -171
  2. package/package.json +1 -1
  3. package/templates/chat-app/CHANGELOG.md +19 -0
  4. package/templates/chat-app/app/(chat)/actions.ts +9 -9
  5. package/templates/chat-app/app/(chat)/api/chat/prepare/route.ts +94 -0
  6. package/templates/chat-app/app/(chat)/api/chat/route.ts +97 -14
  7. package/templates/chat-app/chat.config.ts +144 -156
  8. package/templates/chat-app/components/chat-sync.tsx +6 -3
  9. package/templates/chat-app/components/feedback-actions.tsx +7 -3
  10. package/templates/chat-app/components/message-editor.tsx +8 -3
  11. package/templates/chat-app/components/message-siblings.tsx +14 -1
  12. package/templates/chat-app/components/model-selector.tsx +669 -407
  13. package/templates/chat-app/components/multimodal-input.tsx +252 -18
  14. package/templates/chat-app/components/parallel-response-cards.tsx +157 -0
  15. package/templates/chat-app/components/part/text-message-part.tsx +9 -5
  16. package/templates/chat-app/components/retry-button.tsx +25 -8
  17. package/templates/chat-app/components/user-message.tsx +136 -125
  18. package/templates/chat-app/hooks/chat-sync-hooks.ts +11 -0
  19. package/templates/chat-app/hooks/use-navigate-to-message.ts +39 -0
  20. package/templates/chat-app/lib/ai/gateway-model-defaults.ts +154 -100
  21. package/templates/chat-app/lib/ai/gateways/openrouter-gateway.ts +2 -2
  22. package/templates/chat-app/lib/ai/tools/generate-image.ts +9 -2
  23. package/templates/chat-app/lib/ai/tools/generate-video.ts +3 -0
  24. package/templates/chat-app/lib/ai/types.ts +74 -3
  25. package/templates/chat-app/lib/config-schema.ts +131 -132
  26. package/templates/chat-app/lib/config.ts +2 -2
  27. package/templates/chat-app/lib/db/migrations/0044_gray_red_shift.sql +5 -0
  28. package/templates/chat-app/lib/db/migrations/meta/0044_snapshot.json +1567 -0
  29. package/templates/chat-app/lib/db/migrations/meta/_journal.json +8 -1
  30. package/templates/chat-app/lib/db/queries.ts +84 -4
  31. package/templates/chat-app/lib/db/schema.ts +4 -1
  32. package/templates/chat-app/lib/message-conversion.ts +14 -2
  33. package/templates/chat-app/lib/stores/hooks-threads.ts +37 -1
  34. package/templates/chat-app/lib/stores/with-threads.test.ts +137 -0
  35. package/templates/chat-app/lib/stores/with-threads.ts +157 -4
  36. package/templates/chat-app/lib/thread-utils.ts +23 -2
  37. package/templates/chat-app/package.json +1 -1
  38. package/templates/chat-app/providers/chat-input-provider.tsx +40 -2
  39. package/templates/chat-app/scripts/db-branch-delete.sh +7 -1
  40. package/templates/chat-app/scripts/db-branch-use.sh +7 -1
  41. package/templates/chat-app/scripts/with-db.sh +7 -1
  42. package/templates/chat-app/vitest.config.ts +2 -0
package/dist/index.js CHANGED
@@ -3914,7 +3914,7 @@ var {
3914
3914
  // package.json
3915
3915
  var package_default = {
3916
3916
  name: "@chat-js/cli",
3917
- version: "0.2.1",
3917
+ version: "0.4.0",
3918
3918
  description: "CLI for creating and extending ChatJS apps",
3919
3919
  license: "Apache-2.0",
3920
3920
  repository: {
@@ -17611,6 +17611,71 @@ var add = new Command().name("add").description("add a component or feature to a
17611
17611
  }
17612
17612
  });
17613
17613
 
17614
+ // src/commands/config.ts
17615
+ import { spawn } from "node:child_process";
17616
+ import path from "node:path";
17617
+
17618
+ // src/utils/get-package-manager.ts
17619
+ function inferPackageManager() {
17620
+ const ua = process.env.npm_config_user_agent ?? "";
17621
+ if (ua.startsWith("pnpm/"))
17622
+ return "pnpm";
17623
+ if (ua.startsWith("yarn/"))
17624
+ return "yarn";
17625
+ if (ua.startsWith("npm/"))
17626
+ return "npm";
17627
+ if (ua.startsWith("bun/"))
17628
+ return "bun";
17629
+ return "bun";
17630
+ }
17631
+
17632
+ // src/commands/config.ts
17633
+ var EVAL_SCRIPT = `
17634
+ import userConfig from "./chat.config.ts";
17635
+ import { applyDefaults } from "./lib/config-schema";
17636
+ console.log(JSON.stringify(applyDefaults(userConfig), null, 2));
17637
+ `;
17638
+ function getTsEvalCommand(pm) {
17639
+ switch (pm) {
17640
+ case "bun":
17641
+ return ["bun", ["--eval", EVAL_SCRIPT]];
17642
+ case "pnpm":
17643
+ return ["pnpm", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
17644
+ case "yarn":
17645
+ return ["yarn", ["dlx", "tsx", "--eval", EVAL_SCRIPT]];
17646
+ default:
17647
+ return ["npx", ["tsx", "--eval", EVAL_SCRIPT]];
17648
+ }
17649
+ }
17650
+ 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) => {
17651
+ try {
17652
+ const cwd = path.resolve(opts.cwd);
17653
+ const pm = inferPackageManager();
17654
+ const [cmd, args] = getTsEvalCommand(pm);
17655
+ await new Promise((resolve, reject) => {
17656
+ const child = spawn(cmd, args, {
17657
+ cwd,
17658
+ stdio: ["ignore", "inherit", "pipe"]
17659
+ });
17660
+ const stderr = [];
17661
+ child.stderr?.on("data", (data) => stderr.push(String(data)));
17662
+ child.on("error", (err) => {
17663
+ reject(new Error(`Could not spawn ${cmd}. Make sure ${pm} is installed.
17664
+ ${err.message}`));
17665
+ });
17666
+ child.on("close", (code) => {
17667
+ if (code === 0)
17668
+ resolve();
17669
+ else
17670
+ reject(new Error(`Failed to resolve config:
17671
+ ${stderr.join("").trim()}`));
17672
+ });
17673
+ });
17674
+ } catch (error48) {
17675
+ handleError(error48);
17676
+ }
17677
+ });
17678
+
17614
17679
  // src/commands/create.ts
17615
17680
  import { readFile, writeFile } from "node:fs/promises";
17616
17681
  import { join as join2, resolve as resolve2 } from "node:path";
@@ -18661,7 +18726,7 @@ ${l}
18661
18726
  } }).prompt();
18662
18727
 
18663
18728
  // ../../apps/chat/lib/ai/gateway-model-defaults.ts
18664
- var multiProviderDefaults = {
18729
+ var vercelDefaults = {
18665
18730
  providerOrder: ["openai", "google", "anthropic"],
18666
18731
  disabledModels: [],
18667
18732
  curatedDefaults: [
@@ -18684,36 +18749,66 @@ var multiProviderDefaults = {
18684
18749
  chatImageCompatible: "openai/gpt-4o-mini"
18685
18750
  },
18686
18751
  tools: {
18687
- webSearch: {
18688
- enabled: false
18689
- },
18690
- urlRetrieval: {
18691
- enabled: false
18692
- },
18693
- codeExecution: {
18694
- enabled: false
18695
- },
18696
- mcp: {
18697
- enabled: false
18698
- },
18752
+ webSearch: { enabled: false },
18753
+ urlRetrieval: { enabled: false },
18754
+ codeExecution: { enabled: false },
18755
+ mcp: { enabled: false },
18699
18756
  followupSuggestions: {
18700
18757
  enabled: false,
18701
18758
  default: "google/gemini-2.5-flash-lite"
18702
18759
  },
18703
- text: {
18704
- polish: "openai/gpt-5-mini"
18705
- },
18706
- sheet: {
18707
- format: "openai/gpt-5-mini",
18708
- analyze: "openai/gpt-5-mini"
18709
- },
18710
- code: {
18711
- edits: "openai/gpt-5-mini"
18712
- },
18713
- image: {
18760
+ text: { polish: "openai/gpt-5-mini" },
18761
+ sheet: { format: "openai/gpt-5-mini", analyze: "openai/gpt-5-mini" },
18762
+ code: { edits: "openai/gpt-5-mini" },
18763
+ image: { enabled: false, default: "google/gemini-3-pro-image" },
18764
+ video: { enabled: false, default: "xai/grok-imagine-video" },
18765
+ deepResearch: {
18714
18766
  enabled: false,
18715
- default: "google/gemini-3-pro-image"
18767
+ defaultModel: "google/gemini-2.5-flash-lite",
18768
+ finalReportModel: "google/gemini-3-flash",
18769
+ allowClarification: true,
18770
+ maxResearcherIterations: 1,
18771
+ maxConcurrentResearchUnits: 2,
18772
+ maxSearchQueries: 2
18773
+ }
18774
+ }
18775
+ };
18776
+ var openrouterDefaults = {
18777
+ providerOrder: ["openai", "google", "anthropic"],
18778
+ disabledModels: [],
18779
+ curatedDefaults: [
18780
+ "openai/gpt-5-nano",
18781
+ "openai/gpt-5-mini",
18782
+ "openai/gpt-5.2",
18783
+ "openai/gpt-5.2-chat",
18784
+ "google/gemini-2.5-flash-lite",
18785
+ "google/gemini-3-flash",
18786
+ "google/gemini-3-pro-preview",
18787
+ "anthropic/claude-sonnet-4.5",
18788
+ "anthropic/claude-opus-4.5",
18789
+ "xai/grok-4"
18790
+ ],
18791
+ anonymousModels: ["google/gemini-2.5-flash-lite", "openai/gpt-5-nano"],
18792
+ workflows: {
18793
+ chat: "openai/gpt-5-mini",
18794
+ title: "openai/gpt-5-nano",
18795
+ pdf: "openai/gpt-5-mini",
18796
+ chatImageCompatible: "openai/gpt-4o-mini"
18797
+ },
18798
+ tools: {
18799
+ webSearch: { enabled: false },
18800
+ urlRetrieval: { enabled: false },
18801
+ codeExecution: { enabled: false },
18802
+ mcp: { enabled: false },
18803
+ followupSuggestions: {
18804
+ enabled: false,
18805
+ default: "google/gemini-2.5-flash-lite"
18716
18806
  },
18807
+ text: { polish: "openai/gpt-5-mini" },
18808
+ sheet: { format: "openai/gpt-5-mini", analyze: "openai/gpt-5-mini" },
18809
+ code: { edits: "openai/gpt-5-mini" },
18810
+ image: { enabled: false },
18811
+ video: { enabled: false },
18717
18812
  deepResearch: {
18718
18813
  enabled: false,
18719
18814
  defaultModel: "google/gemini-2.5-flash-lite",
@@ -18725,7 +18820,7 @@ var multiProviderDefaults = {
18725
18820
  }
18726
18821
  }
18727
18822
  };
18728
- var openaiOnlyDefaults = {
18823
+ var openaiDefaults = {
18729
18824
  providerOrder: ["openai"],
18730
18825
  disabledModels: [],
18731
18826
  curatedDefaults: [
@@ -18742,36 +18837,54 @@ var openaiOnlyDefaults = {
18742
18837
  chatImageCompatible: "gpt-4o-mini"
18743
18838
  },
18744
18839
  tools: {
18745
- webSearch: {
18746
- enabled: false
18747
- },
18748
- urlRetrieval: {
18749
- enabled: false
18750
- },
18751
- codeExecution: {
18752
- enabled: false
18753
- },
18754
- mcp: {
18755
- enabled: false
18756
- },
18757
- followupSuggestions: {
18758
- enabled: false,
18759
- default: "gpt-5-nano"
18760
- },
18761
- text: {
18762
- polish: "gpt-5-mini"
18763
- },
18764
- sheet: {
18765
- format: "gpt-5-mini",
18766
- analyze: "gpt-5-mini"
18767
- },
18768
- code: {
18769
- edits: "gpt-5-mini"
18770
- },
18771
- image: {
18840
+ webSearch: { enabled: false },
18841
+ urlRetrieval: { enabled: false },
18842
+ codeExecution: { enabled: false },
18843
+ mcp: { enabled: false },
18844
+ followupSuggestions: { enabled: false, default: "gpt-5-nano" },
18845
+ text: { polish: "gpt-5-mini" },
18846
+ sheet: { format: "gpt-5-mini", analyze: "gpt-5-mini" },
18847
+ code: { edits: "gpt-5-mini" },
18848
+ image: { enabled: false },
18849
+ video: { enabled: false },
18850
+ deepResearch: {
18772
18851
  enabled: false,
18773
- default: "gpt-image-1"
18774
- },
18852
+ defaultModel: "gpt-5-nano",
18853
+ finalReportModel: "gpt-5-mini",
18854
+ allowClarification: true,
18855
+ maxResearcherIterations: 1,
18856
+ maxConcurrentResearchUnits: 2,
18857
+ maxSearchQueries: 2
18858
+ }
18859
+ }
18860
+ };
18861
+ var openaiCompatibleDefaults = {
18862
+ providerOrder: ["openai"],
18863
+ disabledModels: [],
18864
+ curatedDefaults: [
18865
+ "gpt-5-nano",
18866
+ "gpt-5-mini",
18867
+ "gpt-5.2",
18868
+ "gpt-5.2-chat-latest"
18869
+ ],
18870
+ anonymousModels: ["gpt-5-nano"],
18871
+ workflows: {
18872
+ chat: "gpt-5-mini",
18873
+ title: "gpt-5-nano",
18874
+ pdf: "gpt-5-mini",
18875
+ chatImageCompatible: "gpt-4o-mini"
18876
+ },
18877
+ tools: {
18878
+ webSearch: { enabled: false },
18879
+ urlRetrieval: { enabled: false },
18880
+ codeExecution: { enabled: false },
18881
+ mcp: { enabled: false },
18882
+ followupSuggestions: { enabled: false, default: "gpt-5-nano" },
18883
+ text: { polish: "gpt-5-mini" },
18884
+ sheet: { format: "gpt-5-mini", analyze: "gpt-5-mini" },
18885
+ code: { edits: "gpt-5-mini" },
18886
+ image: { enabled: false },
18887
+ video: { enabled: false },
18775
18888
  deepResearch: {
18776
18889
  enabled: false,
18777
18890
  defaultModel: "gpt-5-nano",
@@ -18784,10 +18897,10 @@ var openaiOnlyDefaults = {
18784
18897
  }
18785
18898
  };
18786
18899
  var GATEWAY_MODEL_DEFAULTS = {
18787
- vercel: multiProviderDefaults,
18788
- openrouter: multiProviderDefaults,
18789
- openai: openaiOnlyDefaults,
18790
- "openai-compatible": openaiOnlyDefaults
18900
+ vercel: vercelDefaults,
18901
+ openrouter: openrouterDefaults,
18902
+ openai: openaiDefaults,
18903
+ "openai-compatible": openaiCompatibleDefaults
18791
18904
  };
18792
18905
 
18793
18906
  // ../../apps/chat/lib/config-schema.ts
@@ -18850,14 +18963,26 @@ function createAiSchema(g) {
18850
18963
  code: exports_external.object({
18851
18964
  edits: gatewayModelId()
18852
18965
  }),
18853
- image: exports_external.object({
18854
- enabled: exports_external.boolean(),
18855
- default: gatewayImageModelId()
18856
- }),
18857
- video: exports_external.object({
18858
- enabled: exports_external.boolean(),
18859
- default: gatewayVideoModelId()
18860
- }),
18966
+ image: exports_external.discriminatedUnion("enabled", [
18967
+ exports_external.object({
18968
+ enabled: exports_external.literal(true),
18969
+ default: gatewayImageModelId()
18970
+ }),
18971
+ exports_external.object({
18972
+ enabled: exports_external.literal(false),
18973
+ default: gatewayImageModelId().optional()
18974
+ })
18975
+ ]),
18976
+ video: exports_external.discriminatedUnion("enabled", [
18977
+ exports_external.object({
18978
+ enabled: exports_external.literal(true),
18979
+ default: gatewayVideoModelId()
18980
+ }),
18981
+ exports_external.object({
18982
+ enabled: exports_external.literal(false),
18983
+ default: gatewayVideoModelId().optional()
18984
+ })
18985
+ ]),
18861
18986
  deepResearch: deepResearchToolConfigSchema.extend({
18862
18987
  enabled: exports_external.boolean(),
18863
18988
  defaultModel: gatewayModelId(),
@@ -18879,72 +19004,7 @@ var aiConfigSchema = exports_external.discriminatedUnion("gateway", [
18879
19004
  gatewaySchemaMap["openai-compatible"]
18880
19005
  ]).default({
18881
19006
  gateway: DEFAULT_GATEWAY,
18882
- providerOrder: ["openai", "google", "anthropic"],
18883
- disabledModels: [],
18884
- curatedDefaults: [
18885
- "openai/gpt-5-nano",
18886
- "openai/gpt-5-mini",
18887
- "openai/gpt-5.2",
18888
- "openai/gpt-5.2-chat",
18889
- "google/gemini-2.5-flash-lite",
18890
- "google/gemini-3-flash",
18891
- "google/gemini-3-pro-preview",
18892
- "anthropic/claude-sonnet-4.5",
18893
- "anthropic/claude-opus-4.5",
18894
- "xai/grok-4"
18895
- ],
18896
- anonymousModels: ["google/gemini-2.5-flash-lite", "openai/gpt-5-nano"],
18897
- workflows: {
18898
- chat: "openai/gpt-5-mini",
18899
- title: "openai/gpt-5-nano",
18900
- pdf: "openai/gpt-5-mini",
18901
- chatImageCompatible: "openai/gpt-4o-mini"
18902
- },
18903
- tools: {
18904
- webSearch: {
18905
- enabled: false
18906
- },
18907
- urlRetrieval: {
18908
- enabled: false
18909
- },
18910
- codeExecution: {
18911
- enabled: false
18912
- },
18913
- mcp: {
18914
- enabled: false
18915
- },
18916
- followupSuggestions: {
18917
- enabled: false,
18918
- default: "google/gemini-2.5-flash-lite"
18919
- },
18920
- text: {
18921
- polish: "openai/gpt-5-mini"
18922
- },
18923
- sheet: {
18924
- format: "openai/gpt-5-mini",
18925
- analyze: "openai/gpt-5-mini"
18926
- },
18927
- code: {
18928
- edits: "openai/gpt-5-mini"
18929
- },
18930
- image: {
18931
- enabled: false,
18932
- default: "google/gemini-3-pro-image"
18933
- },
18934
- video: {
18935
- enabled: false,
18936
- default: "xai/grok-imagine-video"
18937
- },
18938
- deepResearch: {
18939
- enabled: false,
18940
- defaultModel: "google/gemini-2.5-flash-lite",
18941
- finalReportModel: "google/gemini-3-flash",
18942
- allowClarification: true,
18943
- maxResearcherIterations: 1,
18944
- maxConcurrentResearchUnits: 2,
18945
- maxSearchQueries: 2
18946
- }
18947
- }
19007
+ ...GATEWAY_MODEL_DEFAULTS[DEFAULT_GATEWAY]
18948
19008
  });
18949
19009
  var pricingConfigSchema = exports_external.object({
18950
19010
  currency: exports_external.string().optional(),
@@ -18991,9 +19051,11 @@ var attachmentsConfigSchema = exports_external.object({
18991
19051
  }
18992
19052
  });
18993
19053
  var featuresConfigSchema = exports_external.object({
18994
- attachments: exports_external.boolean().describe("File attachments (requires BLOB_READ_WRITE_TOKEN)")
19054
+ attachments: exports_external.boolean().describe("File attachments (requires BLOB_READ_WRITE_TOKEN)"),
19055
+ parallelResponses: exports_external.boolean().default(true).describe("Send one message to multiple models simultaneously")
18995
19056
  }).default({
18996
- attachments: false
19057
+ attachments: false,
19058
+ parallelResponses: true
18997
19059
  });
18998
19060
  var authenticationConfigSchema = exports_external.object({
18999
19061
  google: exports_external.boolean().describe("Google OAuth (requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET)"),
@@ -19074,8 +19136,8 @@ function extractDescriptions(schema, prefix = "", result = new Map) {
19074
19136
  if (unwrapped instanceof exports_external.ZodObject) {
19075
19137
  const shape = unwrapped._zod.def.shape;
19076
19138
  for (const [key, propSchema] of Object.entries(shape)) {
19077
- const path = prefix ? `${prefix}.${key}` : key;
19078
- extractDescriptions(propSchema, path, result);
19139
+ const path2 = prefix ? `${prefix}.${key}` : key;
19140
+ extractDescriptions(propSchema, path2, result);
19079
19141
  }
19080
19142
  }
19081
19143
  return result;
@@ -19117,11 +19179,11 @@ ${spaces}}`;
19117
19179
  function generateConfig(obj, indent, pathPrefix, descs) {
19118
19180
  const spaces = " ".repeat(indent);
19119
19181
  return Object.entries(obj).map(([key, value]) => {
19120
- const path = pathPrefix ? `${pathPrefix}.${key}` : key;
19121
- const desc = descs.get(path);
19182
+ const path2 = pathPrefix ? `${pathPrefix}.${key}` : key;
19183
+ const desc = descs.get(path2);
19122
19184
  const comment = desc ? ` // ${desc}` : "";
19123
19185
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
19124
- const nested = generateConfig(value, indent + 1, path, descs);
19186
+ const nested = generateConfig(value, indent + 1, path2, descs);
19125
19187
  return `${spaces}${formatKey(key)}: {
19126
19188
  ${nested}
19127
19189
  ${spaces}},`;
@@ -19131,7 +19193,6 @@ ${spaces}},`;
19131
19193
  `);
19132
19194
  }
19133
19195
  function buildConfigTs(input) {
19134
- const modelDefaults = GATEWAY_MODEL_DEFAULTS[input.gateway];
19135
19196
  const fullConfig = {
19136
19197
  appPrefix: input.appPrefix,
19137
19198
  appName: input.appName,
@@ -19160,7 +19221,7 @@ function buildConfigTs(input) {
19160
19221
  terms: { title: "Terms of Service" }
19161
19222
  },
19162
19223
  authentication: input.auth,
19163
- models: { gateway: input.gateway, ...modelDefaults },
19224
+ ai: { gateway: input.gateway },
19164
19225
  anonymous: {
19165
19226
  credits: 10,
19166
19227
  availableTools: [],
@@ -19177,15 +19238,9 @@ function buildConfigTs(input) {
19177
19238
  "image/jpeg": [".jpg", ".jpeg"],
19178
19239
  "application/pdf": [".pdf"]
19179
19240
  }
19180
- },
19181
- deepResearch: {
19182
- allowClarification: true,
19183
- maxResearcherIterations: 1,
19184
- maxConcurrentResearchUnits: 2,
19185
- maxSearchQueries: 2
19186
19241
  }
19187
19242
  };
19188
- return `import type { ConfigInput } from "@/lib/config-schema";
19243
+ return `import { defineConfig } from "@/lib/config-schema";
19189
19244
 
19190
19245
  /**
19191
19246
  * ChatJS Configuration
@@ -19193,9 +19248,9 @@ function buildConfigTs(input) {
19193
19248
  * Edit this file to customize your app.
19194
19249
  * @see https://chatjs.dev/docs/reference/config
19195
19250
  */
19196
- const config: ConfigInput = {
19251
+ const config = defineConfig({
19197
19252
  ${generateConfig(fullConfig, 1, "", descriptions)}
19198
- };
19253
+ });
19199
19254
 
19200
19255
  export default config;
19201
19256
  `;
@@ -19299,7 +19354,8 @@ var FEATURE_KEYS = [
19299
19354
  "mcp",
19300
19355
  "imageGeneration",
19301
19356
  "attachments",
19302
- "followupSuggestions"
19357
+ "followupSuggestions",
19358
+ "parallelResponses"
19303
19359
  ];
19304
19360
 
19305
19361
  // src/helpers/env-checklist.ts
@@ -19374,7 +19430,8 @@ var FEATURE_DEFAULTS = {
19374
19430
  mcp: false,
19375
19431
  imageGeneration: false,
19376
19432
  attachments: false,
19377
- followupSuggestions: true
19433
+ followupSuggestions: true,
19434
+ parallelResponses: true
19378
19435
  };
19379
19436
  var AUTH_DEFAULTS = {
19380
19437
  google: false,
@@ -19389,7 +19446,8 @@ var FEATURE_LABELS = {
19389
19446
  mcp: "MCP Tool Servers",
19390
19447
  imageGeneration: "Image Generation",
19391
19448
  attachments: "File Attachments",
19392
- followupSuggestions: "Follow-up Suggestions"
19449
+ followupSuggestions: "Follow-up Suggestions",
19450
+ parallelResponses: "Parallel Responses"
19393
19451
  };
19394
19452
  var AUTH_LABELS = {
19395
19453
  google: "Google OAuth",
@@ -19514,10 +19572,10 @@ import { dirname, join, resolve } from "node:path";
19514
19572
  import { fileURLToPath } from "node:url";
19515
19573
 
19516
19574
  // src/utils/run-command.ts
19517
- import { spawn } from "node:child_process";
19575
+ import { spawn as spawn2 } from "node:child_process";
19518
19576
  async function runCommand(command, args, cwd) {
19519
19577
  await new Promise((resolvePromise, rejectPromise) => {
19520
- const child = spawn(command, args, { cwd, stdio: "pipe" });
19578
+ const child = spawn2(command, args, { cwd, stdio: "pipe" });
19521
19579
  const stderr = [];
19522
19580
  child.stderr?.on("data", (data) => {
19523
19581
  stderr.push(String(data));
@@ -19552,20 +19610,6 @@ async function scaffoldFromGit(url2, destination) {
19552
19610
  await rm(join(destination, ".git"), { recursive: true, force: true });
19553
19611
  }
19554
19612
 
19555
- // src/utils/get-package-manager.ts
19556
- function inferPackageManager() {
19557
- const ua = process.env.npm_config_user_agent ?? "";
19558
- if (ua.startsWith("pnpm/"))
19559
- return "pnpm";
19560
- if (ua.startsWith("yarn/"))
19561
- return "yarn";
19562
- if (ua.startsWith("npm/"))
19563
- return "npm";
19564
- if (ua.startsWith("bun/"))
19565
- return "bun";
19566
- return "bun";
19567
- }
19568
-
19569
19613
  // ../../node_modules/ora/index.js
19570
19614
  import process10 from "node:process";
19571
19615
 
@@ -21056,4 +21100,5 @@ process.on("SIGTERM", () => process.exit(0));
21056
21100
  var program2 = new Command().name("chat-js").description("ChatJS CLI").version(package_default.version, "-v, --version", "display the version number");
21057
21101
  program2.addCommand(create, { isDefault: true });
21058
21102
  program2.addCommand(add);
21103
+ program2.addCommand(config2);
21059
21104
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-js/cli",
3
- "version": "0.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for creating and extending ChatJS apps",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,19 @@
1
+ # @chatjs/chat
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#94](https://github.com/FranciscoMoretti/chat-js/pull/94) [`2a8a7cc`](https://github.com/FranciscoMoretti/chat-js/commit/2a8a7cc2b0649bd73e41999dbf0528a21e8065be) Thanks [@FranciscoMoretti](https://github.com/FranciscoMoretti)! - ## Config defaults & `defineConfig` helper
8
+
9
+ ### New features
10
+
11
+ - **`defineConfig()` helper** — new type-safe wrapper for `chat.config.ts`. The gateway type is inferred from `ai.gateway`, so autocomplete and type errors are scoped to the model IDs available in the chosen gateway. Replace `satisfies ConfigInput` with `defineConfig({...})`.
12
+ - **Gateway-specific defaults** — all AI config fields (models, tools, workflows) are now optional. Omitted fields are automatically filled from per-gateway defaults at runtime via `applyDefaults()`. Only `ai.gateway` is required.
13
+ - **`chatjs config` CLI command** — new command that prints the fully-resolved configuration for the current project, applying all defaults. Useful for debugging and verifying your setup.
14
+ - **Separate defaults per gateway** — `vercel`, `openrouter`, `openai`, and `openai-compatible` each have their own typed defaults (`ModelDefaultsFor<G>`), ensuring model IDs are validated against the correct gateway's model registry.
15
+ - **Stricter image/video tool schemas** — `tools.image` and `tools.video` now use a discriminated union: `enabled: true` requires a `default` model, while `enabled: false` makes it optional.
16
+
17
+ ### Breaking changes
18
+
19
+ None — existing configs using `satisfies ConfigInput` continue to work. Migrating to `defineConfig()` is recommended for better DX but not required.
@@ -6,13 +6,13 @@ import type { ChatMessage } from "@/lib/ai/types";
6
6
  import { config } from "@/lib/config";
7
7
 
8
8
  export async function generateTitleFromUserMessage({
9
- message,
9
+ message,
10
10
  }: {
11
- message: ChatMessage;
11
+ message: ChatMessage;
12
12
  }) {
13
- const { text: title } = await generateText({
14
- model: await getLanguageModel(config.ai.workflows.title),
15
- system: `Generate a concise title for a chat conversation based on the user's first message.
13
+ const { text: title } = await generateText({
14
+ model: await getLanguageModel(config.ai.workflows.title),
15
+ system: `Generate a concise title for a chat conversation based on the user's first message.
16
16
 
17
17
  Rules (strictly follow all):
18
18
  - Maximum 40 characters — hard limit, never exceed this
@@ -21,9 +21,9 @@ Rules (strictly follow all):
21
21
  - No filler words like "How to" or "Question about"
22
22
  - Use title case
23
23
  - Return ONLY the title, nothing else`,
24
- prompt: JSON.stringify(message),
25
- experimental_telemetry: { isEnabled: true },
26
- });
24
+ prompt: JSON.stringify(message),
25
+ experimental_telemetry: { isEnabled: true },
26
+ });
27
27
 
28
- return title;
28
+ return title;
29
29
  }