@chat-js/cli 0.1.1 → 0.1.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/README.md CHANGED
@@ -5,11 +5,15 @@ CLI to scaffold and extend ChatJS apps.
5
5
  ## Usage
6
6
 
7
7
  ```bash
8
- npx @chat-js/cli@latest create my-app
8
+ npx @chat-js/cli@latest
9
9
  ```
10
10
 
11
- After install, the binary is available as `chat-js`:
11
+ Or with the command alias:
12
12
 
13
13
  ```bash
14
- chat-js create my-app
14
+ npx @chat-js/cli@latest create
15
15
  ```
16
+
17
+ After install, the binary is:
18
+
19
+ - `chat-js`
package/dist/index.js CHANGED
@@ -3914,17 +3914,17 @@ var {
3914
3914
  // package.json
3915
3915
  var package_default = {
3916
3916
  name: "@chat-js/cli",
3917
- version: "0.1.1",
3917
+ version: "0.1.3",
3918
3918
  description: "CLI for creating and extending ChatJS apps",
3919
3919
  license: "Apache-2.0",
3920
3920
  repository: {
3921
3921
  type: "git",
3922
- url: "https://github.com/FranciscoMoretti/chatjs.git",
3922
+ url: "https://github.com/franciscomoretti/chat.js.git",
3923
3923
  directory: "packages/cli"
3924
3924
  },
3925
- homepage: "https://github.com/FranciscoMoretti/chatjs/tree/main/packages/cli",
3925
+ homepage: "https://github.com/franciscomoretti/chat.js/tree/main/packages/cli",
3926
3926
  bugs: {
3927
- url: "https://github.com/FranciscoMoretti/chatjs/issues"
3927
+ url: "https://github.com/franciscomoretti/chat.js/issues"
3928
3928
  },
3929
3929
  keywords: [
3930
3930
  "chatjs",
@@ -18660,105 +18660,6 @@ ${l}
18660
18660
  }
18661
18661
  } }).prompt();
18662
18662
 
18663
- // ../../apps/chat/lib/config-requirements.ts
18664
- var gatewayEnvRequirements = {
18665
- openrouter: {
18666
- options: [["OPENROUTER_API_KEY"]],
18667
- description: "OPENROUTER_API_KEY"
18668
- },
18669
- openai: {
18670
- options: [["OPENAI_API_KEY"]],
18671
- description: "OPENAI_API_KEY"
18672
- },
18673
- vercel: {
18674
- options: [["AI_GATEWAY_API_KEY"], ["VERCEL_OIDC_TOKEN"]],
18675
- description: "AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN"
18676
- },
18677
- "openai-compatible": {
18678
- options: [["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY"]],
18679
- description: "OPENAI_COMPATIBLE_BASE_URL, OPENAI_COMPATIBLE_API_KEY"
18680
- }
18681
- };
18682
- var featureEnvRequirements = {
18683
- webSearch: {
18684
- options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
18685
- description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
18686
- },
18687
- deepResearch: {
18688
- options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
18689
- description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
18690
- },
18691
- urlRetrieval: {
18692
- options: [["FIRECRAWL_API_KEY"]],
18693
- description: "FIRECRAWL_API_KEY"
18694
- },
18695
- mcp: {
18696
- options: [["MCP_ENCRYPTION_KEY"]],
18697
- description: "MCP_ENCRYPTION_KEY"
18698
- },
18699
- sandbox: {
18700
- options: [
18701
- ["VERCEL_OIDC_TOKEN"],
18702
- ["VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", "VERCEL_TOKEN"]
18703
- ],
18704
- description: "VERCEL_OIDC_TOKEN (auto on Vercel) or VERCEL_TEAM_ID + VERCEL_PROJECT_ID + VERCEL_TOKEN"
18705
- },
18706
- imageGeneration: {
18707
- options: [["BLOB_READ_WRITE_TOKEN"]],
18708
- description: "BLOB_READ_WRITE_TOKEN"
18709
- },
18710
- attachments: {
18711
- options: [["BLOB_READ_WRITE_TOKEN"]],
18712
- description: "BLOB_READ_WRITE_TOKEN"
18713
- }
18714
- };
18715
- var authEnvRequirements = {
18716
- google: {
18717
- options: [["AUTH_GOOGLE_ID", "AUTH_GOOGLE_SECRET"]],
18718
- description: "AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET"
18719
- },
18720
- github: {
18721
- options: [["AUTH_GITHUB_ID", "AUTH_GITHUB_SECRET"]],
18722
- description: "AUTH_GITHUB_ID, AUTH_GITHUB_SECRET"
18723
- },
18724
- vercel: {
18725
- options: [["VERCEL_APP_CLIENT_ID", "VERCEL_APP_CLIENT_SECRET"]],
18726
- description: "VERCEL_APP_CLIENT_ID, VERCEL_APP_CLIENT_SECRET"
18727
- }
18728
- };
18729
-
18730
- // src/types.ts
18731
- var FEATURE_KEYS = [
18732
- "sandbox",
18733
- "webSearch",
18734
- "urlRetrieval",
18735
- "deepResearch",
18736
- "mcp",
18737
- "imageGeneration",
18738
- "attachments",
18739
- "followupSuggestions"
18740
- ];
18741
-
18742
- // src/helpers/env-checklist.ts
18743
- function collectEnvChecklist(input) {
18744
- const requirements = new Set;
18745
- requirements.add(gatewayEnvRequirements[input.gateway].description);
18746
- for (const feature of FEATURE_KEYS) {
18747
- if (!input.features[feature])
18748
- continue;
18749
- const requirement = featureEnvRequirements[feature];
18750
- if (requirement) {
18751
- requirements.add(requirement.description);
18752
- }
18753
- }
18754
- for (const provider of Object.keys(authEnvRequirements)) {
18755
- if (!input.auth[provider])
18756
- continue;
18757
- requirements.add(authEnvRequirements[provider].description);
18758
- }
18759
- return [...requirements].sort();
18760
- }
18761
-
18762
18663
  // ../../apps/chat/lib/ai/gateway-model-defaults.ts
18763
18664
  var multiProviderDefaults = {
18764
18665
  providerOrder: ["openai", "google", "anthropic"],
@@ -18994,7 +18895,6 @@ var authenticationConfigSchema = exports_external.object({
18994
18895
  vercel: false
18995
18896
  });
18996
18897
  var configSchema = exports_external.object({
18997
- githubUrl: exports_external.url().default("https://github.com/your-username/your-repo"),
18998
18898
  appPrefix: exports_external.string().default("chatjs"),
18999
18899
  appName: exports_external.string().default("My AI Chat"),
19000
18900
  appTitle: exports_external.string().optional().describe("Browser tab title (defaults to appName)"),
@@ -19124,7 +19024,6 @@ ${spaces}},`;
19124
19024
  function buildConfigTs(input) {
19125
19025
  const modelDefaults = GATEWAY_MODEL_DEFAULTS[input.gateway];
19126
19026
  const fullConfig = {
19127
- githubUrl: input.githubUrl,
19128
19027
  appPrefix: input.appPrefix,
19129
19028
  appName: input.appName,
19130
19029
  appDescription: "AI chat powered by ChatJS",
@@ -19211,6 +19110,179 @@ async function ensureTargetEmpty(targetDir) {
19211
19110
  }
19212
19111
  }
19213
19112
 
19113
+ // ../../apps/chat/lib/config-requirements.ts
19114
+ var gatewayEnvRequirements = {
19115
+ openrouter: {
19116
+ options: [["OPENROUTER_API_KEY"]],
19117
+ description: "OPENROUTER_API_KEY"
19118
+ },
19119
+ openai: {
19120
+ options: [["OPENAI_API_KEY"]],
19121
+ description: "OPENAI_API_KEY"
19122
+ },
19123
+ vercel: {
19124
+ options: [["AI_GATEWAY_API_KEY"], ["VERCEL_OIDC_TOKEN"]],
19125
+ description: "AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN"
19126
+ },
19127
+ "openai-compatible": {
19128
+ options: [["OPENAI_COMPATIBLE_BASE_URL", "OPENAI_COMPATIBLE_API_KEY"]],
19129
+ description: "OPENAI_COMPATIBLE_BASE_URL, OPENAI_COMPATIBLE_API_KEY"
19130
+ }
19131
+ };
19132
+ var featureEnvRequirements = {
19133
+ webSearch: {
19134
+ options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
19135
+ description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
19136
+ },
19137
+ deepResearch: {
19138
+ options: [["TAVILY_API_KEY"], ["FIRECRAWL_API_KEY"]],
19139
+ description: "TAVILY_API_KEY or FIRECRAWL_API_KEY"
19140
+ },
19141
+ urlRetrieval: {
19142
+ options: [["FIRECRAWL_API_KEY"]],
19143
+ description: "FIRECRAWL_API_KEY"
19144
+ },
19145
+ mcp: {
19146
+ options: [["MCP_ENCRYPTION_KEY"]],
19147
+ description: "MCP_ENCRYPTION_KEY"
19148
+ },
19149
+ sandbox: {
19150
+ options: [
19151
+ ["VERCEL_OIDC_TOKEN"],
19152
+ ["VERCEL_TEAM_ID", "VERCEL_PROJECT_ID", "VERCEL_TOKEN"]
19153
+ ],
19154
+ description: "VERCEL_OIDC_TOKEN (auto on Vercel) or VERCEL_TEAM_ID + VERCEL_PROJECT_ID + VERCEL_TOKEN"
19155
+ },
19156
+ imageGeneration: {
19157
+ options: [["BLOB_READ_WRITE_TOKEN"]],
19158
+ description: "BLOB_READ_WRITE_TOKEN"
19159
+ },
19160
+ attachments: {
19161
+ options: [["BLOB_READ_WRITE_TOKEN"]],
19162
+ description: "BLOB_READ_WRITE_TOKEN"
19163
+ }
19164
+ };
19165
+ var authEnvRequirements = {
19166
+ google: {
19167
+ options: [["AUTH_GOOGLE_ID", "AUTH_GOOGLE_SECRET"]],
19168
+ description: "AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET"
19169
+ },
19170
+ github: {
19171
+ options: [["AUTH_GITHUB_ID", "AUTH_GITHUB_SECRET"]],
19172
+ description: "AUTH_GITHUB_ID, AUTH_GITHUB_SECRET"
19173
+ },
19174
+ vercel: {
19175
+ options: [["VERCEL_APP_CLIENT_ID", "VERCEL_APP_CLIENT_SECRET"]],
19176
+ description: "VERCEL_APP_CLIENT_ID, VERCEL_APP_CLIENT_SECRET"
19177
+ }
19178
+ };
19179
+
19180
+ // ../../apps/chat/lib/env-schema.ts
19181
+ var serverEnvSchema = {
19182
+ DATABASE_URL: exports_external.string().min(1).describe("Postgres connection string"),
19183
+ AUTH_SECRET: exports_external.string().min(1).describe("NextAuth.js secret for signing session tokens"),
19184
+ BLOB_READ_WRITE_TOKEN: exports_external.string().optional().describe("Vercel Blob storage token for file uploads"),
19185
+ AUTH_GOOGLE_ID: exports_external.string().optional().describe("Google OAuth client ID"),
19186
+ AUTH_GOOGLE_SECRET: exports_external.string().optional().describe("Google OAuth client secret"),
19187
+ AUTH_GITHUB_ID: exports_external.string().optional().describe("GitHub OAuth app client ID"),
19188
+ AUTH_GITHUB_SECRET: exports_external.string().optional().describe("GitHub OAuth app client secret"),
19189
+ VERCEL_APP_CLIENT_ID: exports_external.string().optional().describe("Vercel OAuth integration client ID"),
19190
+ VERCEL_APP_CLIENT_SECRET: exports_external.string().optional().describe("Vercel OAuth integration client secret"),
19191
+ AI_GATEWAY_API_KEY: exports_external.string().optional().describe("Vercel AI Gateway API key"),
19192
+ VERCEL_OIDC_TOKEN: exports_external.string().optional().describe("Vercel OIDC token (auto-set on Vercel deployments)"),
19193
+ OPENROUTER_API_KEY: exports_external.string().optional().describe("OpenRouter API key"),
19194
+ OPENAI_COMPATIBLE_BASE_URL: exports_external.string().url().optional().describe("Base URL for OpenAI-compatible provider"),
19195
+ OPENAI_COMPATIBLE_API_KEY: exports_external.string().optional().describe("API key for OpenAI-compatible provider"),
19196
+ OPENAI_API_KEY: exports_external.string().optional().describe("OpenAI API key"),
19197
+ CRON_SECRET: exports_external.string().optional().describe("Secret for cleanup cron job endpoint"),
19198
+ REDIS_URL: exports_external.string().optional().describe("Redis URL for resumable streams"),
19199
+ TAVILY_API_KEY: exports_external.string().optional().describe("Tavily API key for web search"),
19200
+ EXA_API_KEY: exports_external.string().optional().describe("Exa API key for web search"),
19201
+ FIRECRAWL_API_KEY: exports_external.string().optional().describe("Firecrawl API key for web search and URL retrieval"),
19202
+ MCP_ENCRYPTION_KEY: exports_external.union([exports_external.string().length(44), exports_external.literal("")]).optional().describe("Encryption key for MCP server credentials (base64, 44 chars)"),
19203
+ VERCEL_TEAM_ID: exports_external.string().optional().describe("Vercel team ID for sandbox (non-Vercel deployments)"),
19204
+ VERCEL_PROJECT_ID: exports_external.string().optional().describe("Vercel project ID for sandbox (non-Vercel deployments)"),
19205
+ VERCEL_TOKEN: exports_external.string().optional().describe("Vercel API token for sandbox (non-Vercel deployments)"),
19206
+ VERCEL_SANDBOX_RUNTIME: exports_external.string().optional().describe("Vercel sandbox runtime identifier"),
19207
+ APP_URL: exports_external.url().optional().describe("App URL for non-Vercel deployments (full URL including https://)"),
19208
+ VERCEL_URL: exports_external.string().optional().describe("Auto-set by Vercel platform")
19209
+ };
19210
+
19211
+ // src/types.ts
19212
+ var FEATURE_KEYS = [
19213
+ "sandbox",
19214
+ "webSearch",
19215
+ "urlRetrieval",
19216
+ "deepResearch",
19217
+ "mcp",
19218
+ "imageGeneration",
19219
+ "attachments",
19220
+ "followupSuggestions"
19221
+ ];
19222
+
19223
+ // src/helpers/env-checklist.ts
19224
+ function extractEnvDescriptions() {
19225
+ const result = new Map;
19226
+ for (const [key, schema] of Object.entries(serverEnvSchema)) {
19227
+ const desc = schema.description;
19228
+ if (desc) {
19229
+ result.set(key, desc);
19230
+ }
19231
+ }
19232
+ return result;
19233
+ }
19234
+ var envDescriptions = extractEnvDescriptions();
19235
+ function requirementToEntries(requirement) {
19236
+ const oneOfGroup = requirement.options.length > 1 ? requirement.options.map((group) => group.map(String).join("+")).join("|") : undefined;
19237
+ return requirement.options.map((group) => {
19238
+ const description = group.map((v) => {
19239
+ const varName = String(v);
19240
+ return envDescriptions.get(varName) ?? varName;
19241
+ }).join(", ");
19242
+ return {
19243
+ vars: group.map(String).join(" + "),
19244
+ description: description || requirement.description,
19245
+ oneOfGroup
19246
+ };
19247
+ });
19248
+ }
19249
+ function collectEnvChecklist(input) {
19250
+ const entries = [];
19251
+ entries.push({
19252
+ vars: "AUTH_SECRET",
19253
+ description: envDescriptions.get("AUTH_SECRET") ?? "AUTH_SECRET"
19254
+ });
19255
+ entries.push({
19256
+ vars: "DATABASE_URL",
19257
+ description: envDescriptions.get("DATABASE_URL") ?? "DATABASE_URL"
19258
+ });
19259
+ const gwReq = gatewayEnvRequirements[input.gateway];
19260
+ const gwEntries = requirementToEntries(gwReq);
19261
+ entries.push(...gwEntries);
19262
+ const featureItems = [];
19263
+ const seen = new Set;
19264
+ for (const feature of FEATURE_KEYS) {
19265
+ if (!input.features[feature])
19266
+ continue;
19267
+ const requirement = featureEnvRequirements[feature];
19268
+ if (!requirement)
19269
+ continue;
19270
+ if (seen.has(requirement.description))
19271
+ continue;
19272
+ seen.add(requirement.description);
19273
+ featureItems.push(...requirementToEntries(requirement));
19274
+ }
19275
+ entries.push(...featureItems);
19276
+ const authItems = [];
19277
+ for (const provider of Object.keys(authEnvRequirements)) {
19278
+ if (!input.auth[provider])
19279
+ continue;
19280
+ authItems.push(...requirementToEntries(authEnvRequirements[provider]));
19281
+ }
19282
+ entries.push(...authItems);
19283
+ return entries;
19284
+ }
19285
+
19214
19286
  // src/helpers/prompts.ts
19215
19287
  var FEATURE_DEFAULTS = {
19216
19288
  sandbox: false,
@@ -19266,42 +19338,6 @@ async function promptProjectName(targetArg, skipPrompt) {
19266
19338
  handleCancel(name);
19267
19339
  return toKebabCase(name) || "my-chat-app";
19268
19340
  }
19269
- async function promptAppDetails(skipPrompt) {
19270
- if (skipPrompt) {
19271
- return {
19272
- appName: "My Chat App",
19273
- appPrefix: "chat",
19274
- appUrl: "http://localhost:3000",
19275
- githubUrl: "https://github.com/your-org/your-repo"
19276
- };
19277
- }
19278
- const appName = await Ze({
19279
- message: `What is the ${highlighter.info("display name")} of your app?`,
19280
- initialValue: "My Chat App"
19281
- });
19282
- handleCancel(appName);
19283
- const appPrefix = await Ze({
19284
- message: `What ${highlighter.info("prefix")} should be used?`,
19285
- initialValue: toKebabCase(appName) || "chat"
19286
- });
19287
- handleCancel(appPrefix);
19288
- const appUrl = await Ze({
19289
- message: `What is your ${highlighter.info("app URL")}?`,
19290
- initialValue: "http://localhost:3000"
19291
- });
19292
- handleCancel(appUrl);
19293
- const githubUrl = await Ze({
19294
- message: `What is your ${highlighter.info("GitHub repository URL")}?`,
19295
- initialValue: "https://github.com/your-org/your-repo"
19296
- });
19297
- handleCancel(githubUrl);
19298
- return {
19299
- appName: appName || "My Chat App",
19300
- appPrefix: appPrefix || "chat",
19301
- appUrl: appUrl || "http://localhost:3000",
19302
- githubUrl: githubUrl || "https://github.com/your-org/your-repo"
19303
- };
19304
- }
19305
19341
  async function promptGateway(skipPrompt) {
19306
19342
  if (skipPrompt)
19307
19343
  return "vercel";
@@ -20751,6 +20787,24 @@ function spinner(text, options) {
20751
20787
  }
20752
20788
 
20753
20789
  // src/commands/create.ts
20790
+ function printEnvChecklist(entries) {
20791
+ logger.info("Required for your configuration:");
20792
+ logger.break();
20793
+ for (let i = 0;i < entries.length; i += 1) {
20794
+ const entry = entries[i];
20795
+ if (!entry.oneOfGroup) {
20796
+ logger.log(` ${highlighter.warn("*")} ${highlighter.warn(entry.vars)} ${highlighter.dim(`- ${entry.description}`)}`);
20797
+ continue;
20798
+ }
20799
+ logger.log(` ${highlighter.warn("*")} ${highlighter.dim("One of:")}`);
20800
+ while (i < entries.length && entries[i].oneOfGroup === entry.oneOfGroup) {
20801
+ const option = entries[i];
20802
+ logger.log(` ${highlighter.warn("*")} ${highlighter.warn(option.vars)} ${highlighter.dim(`- ${option.description}`)}`);
20803
+ i += 1;
20804
+ }
20805
+ i -= 1;
20806
+ }
20807
+ }
20754
20808
  var createOptionsSchema = exports_external.object({
20755
20809
  target: exports_external.string().optional(),
20756
20810
  yes: exports_external.boolean(),
@@ -20769,11 +20823,13 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
20769
20823
  }
20770
20824
  const projectName = await promptProjectName(options.target, options.yes);
20771
20825
  const targetDir = resolve2(process.cwd(), projectName);
20772
- const { appName, appPrefix, appUrl, githubUrl } = await promptAppDetails(options.yes);
20826
+ await ensureTargetEmpty(targetDir);
20827
+ const appName = projectName.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
20828
+ const appPrefix = projectName;
20829
+ const appUrl = "http://localhost:3000";
20773
20830
  const gateway = await promptGateway(options.yes);
20774
20831
  const features = await promptFeatures(options.yes);
20775
20832
  const auth = await promptAuth(options.yes);
20776
- await ensureTargetEmpty(targetDir);
20777
20833
  logger.break();
20778
20834
  const scaffoldSpinner = spinner("Scaffolding project...").start();
20779
20835
  try {
@@ -20798,7 +20854,6 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
20798
20854
  appName,
20799
20855
  appPrefix,
20800
20856
  appUrl,
20801
- githubUrl,
20802
20857
  gateway,
20803
20858
  features,
20804
20859
  auth
@@ -20820,19 +20875,22 @@ var create = new Command().name("create").description("scaffold a new ChatJS cha
20820
20875
  throw error48;
20821
20876
  }
20822
20877
  }
20823
- const envChecklist = collectEnvChecklist({ gateway, features, auth });
20878
+ const envEntries = collectEnvChecklist({ gateway, features, auth });
20824
20879
  Le("Your ChatJS app is ready!");
20825
20880
  logger.info("Next steps:");
20826
- logger.log(` cd ${highlighter.info(projectName)}`);
20827
- logger.log(` ${highlighter.info(`${packageManager} run dev`)}`);
20828
20881
  logger.break();
20829
- if (envChecklist.length > 0) {
20830
- logger.info("Required environment variables:");
20831
- for (const item of envChecklist) {
20832
- logger.log(` ${highlighter.warn("*")} ${item}`);
20833
- }
20834
- logger.break();
20882
+ logger.log(` ${highlighter.dim("1.")} cd ${highlighter.info(projectName)}`);
20883
+ logger.log(` ${highlighter.dim("2.")} Copy ${highlighter.info(".env.example")} to ${highlighter.info(".env.local")} and fill in the values below`);
20884
+ if (!installNow) {
20885
+ logger.log(` ${highlighter.dim("3.")} ${highlighter.info(`${packageManager} install`)}`);
20886
+ logger.log(` ${highlighter.dim("4.")} ${highlighter.info(`${packageManager} run db:push`)}`);
20887
+ logger.log(` ${highlighter.dim("5.")} ${highlighter.info(`${packageManager} run dev`)}`);
20888
+ } else {
20889
+ logger.log(` ${highlighter.dim("3.")} ${highlighter.info(`${packageManager} run db:push`)}`);
20890
+ logger.log(` ${highlighter.dim("4.")} ${highlighter.info(`${packageManager} run dev`)}`);
20835
20891
  }
20892
+ logger.break();
20893
+ printEnvChecklist(envEntries);
20836
20894
  } catch (error48) {
20837
20895
  handleError(error48);
20838
20896
  }
package/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@chat-js/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for creating and extending ChatJS apps",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/FranciscoMoretti/chatjs.git",
8
+ "url": "https://github.com/franciscomoretti/chat.js.git",
9
9
  "directory": "packages/cli"
10
10
  },
11
- "homepage": "https://github.com/FranciscoMoretti/chatjs/tree/main/packages/cli",
11
+ "homepage": "https://github.com/franciscomoretti/chat.js/tree/main/packages/cli",
12
12
  "bugs": {
13
- "url": "https://github.com/FranciscoMoretti/chatjs/issues"
13
+ "url": "https://github.com/franciscomoretti/chat.js/issues"
14
14
  },
15
15
  "keywords": [
16
16
  "chatjs",
@@ -30,11 +30,18 @@ async function serializeSignedCookie(
30
30
  const signedValue = encodeURIComponent(`${value}.${base64Sig}`);
31
31
 
32
32
  let cookie = `${name}=${signedValue}`;
33
- if (opt.path) cookie += `; Path=${opt.path}`;
34
- if (opt.expires) cookie += `; Expires=${opt.expires.toUTCString()}`;
35
- if (opt.httpOnly) cookie += "; HttpOnly";
36
- if (opt.sameSite)
33
+ if (opt.path) {
34
+ cookie += `; Path=${opt.path}`;
35
+ }
36
+ if (opt.expires) {
37
+ cookie += `; Expires=${opt.expires.toUTCString()}`;
38
+ }
39
+ if (opt.httpOnly) {
40
+ cookie += "; HttpOnly";
41
+ }
42
+ if (opt.sameSite) {
37
43
  cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
44
+ }
38
45
  return cookie;
39
46
  }
40
47
 
@@ -0,0 +1,26 @@
1
+ {
2
+ "extends": ["ultracite/core", "ultracite/react", "ultracite/next"],
3
+ "files": {
4
+ "includes": [
5
+ "**/*",
6
+ "!.next",
7
+ "!.next",
8
+ "!.devtools",
9
+ "!.devtools",
10
+ "!components/ui",
11
+ "!components/ai-elements",
12
+ "!components/streamdown",
13
+ "!components/stick-to-bottom",
14
+ "!lib/utils.ts",
15
+ "!hooks/use-mobile.ts"
16
+ ]
17
+ },
18
+ "linter": {
19
+ "rules": {
20
+ "suspicious": {
21
+ /* Needs more work to fix */
22
+ "noExplicitAny": "off"
23
+ }
24
+ }
25
+ }
26
+ }
@@ -9,7 +9,6 @@ const isProd = process.env.NODE_ENV === "production";
9
9
  * @see https://chatjs.dev/docs/reference/config
10
10
  */
11
11
  const config = {
12
- githubUrl: "https://github.com/franciscomoretti/chatjs",
13
12
  appPrefix: "chatjs",
14
13
  appName: "ChatJS",
15
14
  appTitle: "ChatJS - The prod ready AI chat app",
@@ -35,9 +35,6 @@ export function ChatSync({
35
35
 
36
36
  const lastMessage = threadInitialMessages.at(-1);
37
37
  const isLastMessagePartial = !!lastMessage?.metadata?.activeStreamId;
38
- const transportFetch = Object.assign(fetchWithErrorHandlers, {
39
- preconnect: fetch.preconnect,
40
- }) satisfies typeof fetch;
41
38
 
42
39
  // Backstop: if we remount ChatSync (e.g. threadEpoch changes), ensure the prior
43
40
  // in-flight stream is aborted and we don't replay old deltas.
@@ -65,7 +62,8 @@ export function ChatSync({
65
62
  resume: isLastMessagePartial,
66
63
  transport: new DefaultChatTransport({
67
64
  api: "/api/chat",
68
- fetch: transportFetch,
65
+ // @ts-expect-error CI has a stricter global fetch type (includes preconnect).
66
+ fetch: fetchWithErrorHandlers,
69
67
  prepareSendMessagesRequest({ messages, id: requestId, body }) {
70
68
  setAutoResume(true);
71
69
 
@@ -63,20 +63,28 @@ function FollowUpSuggestions({
63
63
  <div className={cn("mt-2 mb-2 flex flex-col gap-2", className)}>
64
64
  <div className="font-medium text-muted-foreground text-xs">Related</div>
65
65
  <Suggestions className="gap-1.5">
66
- {suggestions.map((s) => (
67
- <Suggestion
68
- className="h-7 text-muted-foreground hover:text-foreground"
69
- key={s}
70
- onClick={handleClick}
71
- size="sm"
72
- suggestion={s}
73
- type="button"
74
- variant="ghost"
75
- >
76
- {s}
77
- <PlusIcon className="size-3 opacity-70" />
78
- </Suggestion>
79
- ))}
66
+ {(() => {
67
+ const seen = new Map<string, number>();
68
+ return suggestions.map((s) => {
69
+ const count = seen.get(s) ?? 0;
70
+ seen.set(s, count + 1);
71
+ const key = count === 0 ? s : `${s}-${count}`;
72
+ return (
73
+ <Suggestion
74
+ className="h-7 text-muted-foreground hover:text-foreground"
75
+ key={key}
76
+ onClick={handleClick}
77
+ size="sm"
78
+ suggestion={s}
79
+ type="button"
80
+ variant="ghost"
81
+ >
82
+ {s}
83
+ <PlusIcon className="size-3 opacity-70" />
84
+ </Suggestion>
85
+ );
86
+ });
87
+ })()}
80
88
  </Suggestions>
81
89
  </div>
82
90
  );
@@ -3,14 +3,12 @@
3
3
  import { LogIn } from "lucide-react";
4
4
  import { useRouter } from "next/navigation";
5
5
  import { memo } from "react";
6
- import { GitIcon } from "@/components/icons";
7
6
  import { Button } from "@/components/ui/button";
8
7
  import {
9
8
  Tooltip,
10
9
  TooltipContent,
11
10
  TooltipTrigger,
12
11
  } from "@/components/ui/tooltip";
13
- import { config } from "@/lib/config";
14
12
  import { useSession } from "@/providers/session-provider";
15
13
 
16
14
  function PureHeaderActions() {
@@ -39,16 +37,6 @@ function PureHeaderActions() {
39
37
  <TooltipContent>Sign in to your account</TooltipContent>
40
38
  </Tooltip>
41
39
  )}
42
- <Button asChild size="icon" type="button" variant="ghost">
43
- <a
44
- className="flex items-center justify-center"
45
- href={config.githubUrl}
46
- rel="noopener noreferrer"
47
- target="_blank"
48
- >
49
- <GitIcon size={20} />
50
- </a>
51
- </Button>
52
40
  </div>
53
41
  );
54
42
  }
@@ -1,5 +1,5 @@
1
- import { cn } from "@/lib/utils";
2
1
  import { Loader2 } from "lucide-react";
2
+ import { cn } from "@/lib/utils";
3
3
 
4
4
  type ImageEditorProps = {
5
5
  title: string;
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { cn } from "@/lib/utils";
4
3
  import { format, isWithinInterval } from "date-fns";
5
4
  import { useIsMobile } from "@/hooks/use-mobile";
6
5
  import type { WeatherAtLocation } from "@/lib/ai/tools/get-weather";
7
6
  import type { ChatMessage } from "@/lib/ai/types";
7
+ import { cn } from "@/lib/utils";
8
8
 
9
9
  const SAMPLE = {
10
10
  latitude: 37.763_283,