@contextstream/mcp-server 0.3.19 → 0.3.21

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 (3) hide show
  1. package/README.md +79 -334
  2. package/dist/index.js +984 -230
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4611,7 +4611,9 @@ var CacheTTL = {
4611
4611
  // Search results - cache for 60 seconds
4612
4612
  SEARCH: 60 * 1e3,
4613
4613
  // User preferences - cache for 5 minutes
4614
- USER_PREFS: 5 * 60 * 1e3
4614
+ USER_PREFS: 5 * 60 * 1e3,
4615
+ // Credits/plan - cache briefly to reflect upgrades quickly
4616
+ CREDIT_BALANCE: 60 * 1e3
4615
4617
  };
4616
4618
  var CacheKeys = {
4617
4619
  workspace: (id) => `workspace:${id}`,
@@ -4620,7 +4622,8 @@ var CacheKeys = {
4620
4622
  projectList: (workspaceId) => `projects:${workspaceId}`,
4621
4623
  sessionInit: (workspaceId, projectId) => `session_init:${workspaceId || ""}:${projectId || ""}`,
4622
4624
  memoryEvents: (workspaceId) => `memory:${workspaceId}`,
4623
- search: (query, workspaceId) => `search:${workspaceId || ""}:${query}`
4625
+ search: (query, workspaceId) => `search:${workspaceId || ""}:${query}`,
4626
+ creditBalance: () => "credits:balance"
4624
4627
  };
4625
4628
  var globalCache = new MemoryCache();
4626
4629
 
@@ -4666,6 +4669,25 @@ var ContextStreamClient = class {
4666
4669
  me() {
4667
4670
  return request(this.config, "/auth/me");
4668
4671
  }
4672
+ // Credits / Billing (used for plan gating)
4673
+ async getCreditBalance() {
4674
+ const cacheKey = CacheKeys.creditBalance();
4675
+ const cached = globalCache.get(cacheKey);
4676
+ if (cached) return cached;
4677
+ const result = await request(this.config, "/credits/balance", { method: "GET" });
4678
+ const data = result && typeof result === "object" && "data" in result && result.data ? result.data : result;
4679
+ globalCache.set(cacheKey, data, CacheTTL.CREDIT_BALANCE);
4680
+ return data;
4681
+ }
4682
+ async getPlanName() {
4683
+ try {
4684
+ const balance = await this.getCreditBalance();
4685
+ const planName = balance?.plan?.name;
4686
+ return typeof planName === "string" ? planName.toLowerCase() : null;
4687
+ } catch {
4688
+ return null;
4689
+ }
4690
+ }
4669
4691
  // Workspaces & Projects
4670
4692
  listWorkspaces(params) {
4671
4693
  const query = new URLSearchParams();
@@ -5053,22 +5075,15 @@ var ContextStreamClient = class {
5053
5075
  return context;
5054
5076
  }
5055
5077
  } else {
5056
- const newWorkspace = await this.createWorkspace({
5057
- name: folderName || "My Workspace",
5058
- description: `Workspace created for ${rootPath}`,
5059
- visibility: "private"
5060
- });
5061
- if (newWorkspace.id) {
5062
- workspaceId = newWorkspace.id;
5063
- workspaceName = newWorkspace.name;
5064
- context.workspace_source = "auto_created";
5065
- context.workspace_created = true;
5066
- writeLocalConfig(rootPath, {
5067
- workspace_id: newWorkspace.id,
5068
- workspace_name: newWorkspace.name,
5069
- associated_at: (/* @__PURE__ */ new Date()).toISOString()
5070
- });
5071
- }
5078
+ const folderDisplayName = rootPath?.split("/").pop() || "this folder";
5079
+ context.status = "requires_workspace_name";
5080
+ context.workspace_source = "none_found";
5081
+ context.ide_roots = ideRoots;
5082
+ context.folder_name = folderDisplayName;
5083
+ context.folder_path = rootPath;
5084
+ context.suggested_project_name = folderDisplayName;
5085
+ context.message = `No workspaces found for this account. Ask the user for a name for a new workspace, then create a project for "${folderDisplayName}".`;
5086
+ return context;
5072
5087
  }
5073
5088
  } catch (e) {
5074
5089
  context.workspace_error = String(e);
@@ -6237,6 +6252,8 @@ You have access to ContextStream MCP tools for persistent memory and context.
6237
6252
  | **1st message** | \`session_init(folder_path="...", context_hint="<user's message>")\` |
6238
6253
  | **2nd+ messages** | \`context_smart(user_message="<user's message>")\` |
6239
6254
  | **After completing task** | \`session_capture(...)\` - MUST capture decisions/insights |
6255
+ | **User frustration/correction** | \`session_capture_lesson(...)\` - MUST capture lessons |
6256
+ | **Command/tool error + fix** | \`session_capture_lesson(...)\` - MUST capture lessons |
6240
6257
 
6241
6258
  **NO EXCEPTIONS.** Do not skip even if you think you have enough context.
6242
6259
 
@@ -6292,6 +6309,52 @@ You have access to ContextStream MCP tools for persistent memory and context.
6292
6309
 
6293
6310
  ---
6294
6311
 
6312
+ ### Lesson Capture (MANDATORY)
6313
+
6314
+ When:
6315
+ 1. **Expresses frustration** (caps, profanity, "COME ON", "WTF", repeated corrections)
6316
+ 2. **Corrects you** ("No, you should...", "That's wrong", "Fix this")
6317
+ 3. **Points out a mistake** (broken code, wrong approach, production issue)
6318
+ 4. **A command/tool call fails and you learn the correct fix** (even if the user didn\u2019t explicitly correct you)
6319
+
6320
+ You MUST immediately call \`session_capture_lesson\` with:
6321
+
6322
+ | Field | Description | Example |
6323
+ |-------|-------------|---------|
6324
+ | \`title\` | What to remember | "Verify assets in git before pushing" |
6325
+ | \`severity\` | \`critical\`/\`high\`/\`medium\`/\`low\` | \`critical\` for production issues |
6326
+ | \`category\` | \`workflow\`/\`code_quality\`/\`verification\`/\`communication\`/\`project_specific\` | \`workflow\` |
6327
+ | \`trigger\` | What action caused the problem | "Pushed code referencing images without committing them" |
6328
+ | \`impact\` | What went wrong | "Production 404 errors - broken landing page" |
6329
+ | \`prevention\` | How to prevent in future | "Run git status to check untracked files before pushing" |
6330
+ | \`keywords\` | Keywords for matching | \`["git", "images", "assets", "push"]\` |
6331
+
6332
+ **Example call:**
6333
+ \`\`\`json
6334
+ {
6335
+ "title": "Always verify assets in git before pushing code references",
6336
+ "severity": "critical",
6337
+ "category": "workflow",
6338
+ "trigger": "Pushed code referencing /screenshots/*.png without committing images",
6339
+ "impact": "Production 404 errors - broken landing page",
6340
+ "prevention": "Run 'git status' to check untracked files before pushing code that references static assets",
6341
+ "keywords": ["git", "images", "assets", "push", "404", "static"]
6342
+ }
6343
+ \`\`\`
6344
+
6345
+ **Why this matters:**
6346
+ - Lessons are surfaced automatically in \`session_init\` and \`context_smart\`
6347
+ - Future sessions will warn you before repeating the same mistake
6348
+ - This prevents production issues and user frustration
6349
+
6350
+ **Severity guide:**
6351
+ - \`critical\`: Production outages, data loss, security issues
6352
+ - \`high\`: Breaking changes, significant user impact
6353
+ - \`medium\`: Workflow inefficiencies, minor bugs
6354
+ - \`low\`: Style/preference corrections
6355
+
6356
+ ---
6357
+
6295
6358
  ### Quick Examples
6296
6359
 
6297
6360
  \`\`\`
@@ -6407,6 +6470,39 @@ function toStructured(data) {
6407
6470
  return void 0;
6408
6471
  }
6409
6472
  function registerTools(server, client, sessionManager) {
6473
+ const upgradeUrl2 = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
6474
+ const defaultProTools = /* @__PURE__ */ new Set([
6475
+ // AI endpoints (typically paid/credit-metered)
6476
+ "ai_context",
6477
+ "ai_enhanced_context",
6478
+ "ai_context_budget",
6479
+ "ai_embeddings",
6480
+ "ai_plan",
6481
+ "ai_tasks"
6482
+ ]);
6483
+ const proTools = (() => {
6484
+ const raw = process.env.CONTEXTSTREAM_PRO_TOOLS;
6485
+ if (!raw) return defaultProTools;
6486
+ const parsed = raw.split(",").map((t) => t.trim()).filter(Boolean);
6487
+ return parsed.length > 0 ? new Set(parsed) : defaultProTools;
6488
+ })();
6489
+ function getToolAccessTier(toolName) {
6490
+ return proTools.has(toolName) ? "pro" : "free";
6491
+ }
6492
+ function getToolAccessLabel(toolName) {
6493
+ return getToolAccessTier(toolName) === "pro" ? "PRO" : "Free";
6494
+ }
6495
+ async function gateIfProTool(toolName) {
6496
+ if (getToolAccessTier(toolName) !== "pro") return null;
6497
+ const planName = await client.getPlanName();
6498
+ if (planName !== "free") return null;
6499
+ return errorResult(
6500
+ [
6501
+ `Access denied: \`${toolName}\` requires ContextStream PRO.`,
6502
+ `Upgrade: ${upgradeUrl2}`
6503
+ ].join("\n")
6504
+ );
6505
+ }
6410
6506
  function wrapWithAutoContext(toolName, handler) {
6411
6507
  if (!sessionManager) {
6412
6508
  return handler;
@@ -6435,8 +6531,18 @@ function registerTools(server, client, sessionManager) {
6435
6531
  };
6436
6532
  }
6437
6533
  function registerTool(name, config, handler) {
6534
+ const accessLabel = getToolAccessLabel(name);
6535
+ const labeledConfig = {
6536
+ ...config,
6537
+ title: `${config.title} (${accessLabel})`,
6538
+ description: `${config.description}
6539
+
6540
+ Access: ${accessLabel}${accessLabel === "PRO" ? ` (upgrade: ${upgradeUrl2})` : ""}`
6541
+ };
6438
6542
  const safeHandler = async (input) => {
6439
6543
  try {
6544
+ const gated = await gateIfProTool(name);
6545
+ if (gated) return gated;
6440
6546
  return await handler(input);
6441
6547
  } catch (error) {
6442
6548
  const errorMessage = error?.message || String(error);
@@ -6450,10 +6556,26 @@ function registerTools(server, client, sessionManager) {
6450
6556
  };
6451
6557
  server.registerTool(
6452
6558
  name,
6453
- config,
6559
+ labeledConfig,
6454
6560
  wrapWithAutoContext(name, safeHandler)
6455
6561
  );
6456
6562
  }
6563
+ function errorResult(text) {
6564
+ return {
6565
+ content: [{ type: "text", text }],
6566
+ isError: true
6567
+ };
6568
+ }
6569
+ function resolveWorkspaceId(explicitWorkspaceId) {
6570
+ if (explicitWorkspaceId) return explicitWorkspaceId;
6571
+ const ctx = sessionManager?.getContext();
6572
+ return typeof ctx?.workspace_id === "string" ? ctx.workspace_id : void 0;
6573
+ }
6574
+ function resolveProjectId(explicitProjectId) {
6575
+ if (explicitProjectId) return explicitProjectId;
6576
+ const ctx = sessionManager?.getContext();
6577
+ return typeof ctx?.project_id === "string" ? ctx.project_id : void 0;
6578
+ }
6457
6579
  registerTool(
6458
6580
  "auth_me",
6459
6581
  {
@@ -6590,10 +6712,14 @@ function registerTools(server, client, sessionManager) {
6590
6712
  {
6591
6713
  title: "Index project",
6592
6714
  description: "Trigger indexing for a project",
6593
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
6715
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6594
6716
  },
6595
6717
  async (input) => {
6596
- const result = await client.indexProject(input.project_id);
6718
+ const projectId = resolveProjectId(input.project_id);
6719
+ if (!projectId) {
6720
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
6721
+ }
6722
+ const result = await client.indexProject(projectId);
6597
6723
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6598
6724
  }
6599
6725
  );
@@ -6943,10 +7069,14 @@ function registerTools(server, client, sessionManager) {
6943
7069
  {
6944
7070
  title: "Get project",
6945
7071
  description: "Get project details by ID",
6946
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7072
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6947
7073
  },
6948
7074
  async (input) => {
6949
- const result = await client.getProject(input.project_id);
7075
+ const projectId = resolveProjectId(input.project_id);
7076
+ if (!projectId) {
7077
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7078
+ }
7079
+ const result = await client.getProject(projectId);
6950
7080
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6951
7081
  }
6952
7082
  );
@@ -6955,10 +7085,14 @@ function registerTools(server, client, sessionManager) {
6955
7085
  {
6956
7086
  title: "Project overview",
6957
7087
  description: "Get project overview with summary information",
6958
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7088
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6959
7089
  },
6960
7090
  async (input) => {
6961
- const result = await client.projectOverview(input.project_id);
7091
+ const projectId = resolveProjectId(input.project_id);
7092
+ if (!projectId) {
7093
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7094
+ }
7095
+ const result = await client.projectOverview(projectId);
6962
7096
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6963
7097
  }
6964
7098
  );
@@ -6967,10 +7101,14 @@ function registerTools(server, client, sessionManager) {
6967
7101
  {
6968
7102
  title: "Project statistics",
6969
7103
  description: "Get project statistics (files, lines, complexity)",
6970
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7104
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6971
7105
  },
6972
7106
  async (input) => {
6973
- const result = await client.projectStatistics(input.project_id);
7107
+ const projectId = resolveProjectId(input.project_id);
7108
+ if (!projectId) {
7109
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7110
+ }
7111
+ const result = await client.projectStatistics(projectId);
6974
7112
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6975
7113
  }
6976
7114
  );
@@ -6979,10 +7117,14 @@ function registerTools(server, client, sessionManager) {
6979
7117
  {
6980
7118
  title: "List project files",
6981
7119
  description: "List all indexed files in a project",
6982
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7120
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6983
7121
  },
6984
7122
  async (input) => {
6985
- const result = await client.projectFiles(input.project_id);
7123
+ const projectId = resolveProjectId(input.project_id);
7124
+ if (!projectId) {
7125
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7126
+ }
7127
+ const result = await client.projectFiles(projectId);
6986
7128
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6987
7129
  }
6988
7130
  );
@@ -6991,10 +7133,14 @@ function registerTools(server, client, sessionManager) {
6991
7133
  {
6992
7134
  title: "Index status",
6993
7135
  description: "Get project indexing status",
6994
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7136
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
6995
7137
  },
6996
7138
  async (input) => {
6997
- const result = await client.projectIndexStatus(input.project_id);
7139
+ const projectId = resolveProjectId(input.project_id);
7140
+ if (!projectId) {
7141
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7142
+ }
7143
+ const result = await client.projectIndexStatus(projectId);
6998
7144
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
6999
7145
  }
7000
7146
  );
@@ -7006,18 +7152,22 @@ function registerTools(server, client, sessionManager) {
7006
7152
  This indexes your entire project by reading files in batches.
7007
7153
  Automatically detects code files and skips ignored directories like node_modules, target, dist, etc.`,
7008
7154
  inputSchema: external_exports.object({
7009
- project_id: external_exports.string().uuid().describe("Project to ingest files into"),
7155
+ project_id: external_exports.string().uuid().optional().describe("Project to ingest files into (defaults to current session project)"),
7010
7156
  path: external_exports.string().describe("Local directory path to read files from")
7011
7157
  })
7012
7158
  },
7013
7159
  async (input) => {
7160
+ const projectId = resolveProjectId(input.project_id);
7161
+ if (!projectId) {
7162
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7163
+ }
7014
7164
  (async () => {
7015
7165
  try {
7016
7166
  let totalIndexed = 0;
7017
7167
  let batchCount = 0;
7018
- console.error(`[ContextStream] Starting background ingestion for project ${input.project_id} from ${input.path}`);
7168
+ console.error(`[ContextStream] Starting background ingestion for project ${projectId} from ${input.path}`);
7019
7169
  for await (const batch of readAllFilesInBatches(input.path, { batchSize: 50 })) {
7020
- const result = await client.ingestFiles(input.project_id, batch);
7170
+ const result = await client.ingestFiles(projectId, batch);
7021
7171
  totalIndexed += result.data?.files_indexed ?? batch.length;
7022
7172
  batchCount++;
7023
7173
  }
@@ -7029,7 +7179,7 @@ Automatically detects code files and skips ignored directories like node_modules
7029
7179
  const summary = {
7030
7180
  status: "started",
7031
7181
  message: "Ingestion running in background",
7032
- project_id: input.project_id,
7182
+ project_id: projectId,
7033
7183
  path: input.path,
7034
7184
  note: "Use 'projects_index_status' to monitor progress."
7035
7185
  };
@@ -7047,10 +7197,14 @@ Automatically detects code files and skips ignored directories like node_modules
7047
7197
  {
7048
7198
  title: "Get workspace",
7049
7199
  description: "Get workspace details by ID",
7050
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7200
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7051
7201
  },
7052
7202
  async (input) => {
7053
- const result = await client.getWorkspace(input.workspace_id);
7203
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7204
+ if (!workspaceId) {
7205
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7206
+ }
7207
+ const result = await client.getWorkspace(workspaceId);
7054
7208
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7055
7209
  }
7056
7210
  );
@@ -7059,10 +7213,14 @@ Automatically detects code files and skips ignored directories like node_modules
7059
7213
  {
7060
7214
  title: "Workspace overview",
7061
7215
  description: "Get workspace overview with summary information",
7062
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7216
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7063
7217
  },
7064
7218
  async (input) => {
7065
- const result = await client.workspaceOverview(input.workspace_id);
7219
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7220
+ if (!workspaceId) {
7221
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7222
+ }
7223
+ const result = await client.workspaceOverview(workspaceId);
7066
7224
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7067
7225
  }
7068
7226
  );
@@ -7071,10 +7229,14 @@ Automatically detects code files and skips ignored directories like node_modules
7071
7229
  {
7072
7230
  title: "Workspace analytics",
7073
7231
  description: "Get workspace usage analytics",
7074
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7232
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7075
7233
  },
7076
7234
  async (input) => {
7077
- const result = await client.workspaceAnalytics(input.workspace_id);
7235
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7236
+ if (!workspaceId) {
7237
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7238
+ }
7239
+ const result = await client.workspaceAnalytics(workspaceId);
7078
7240
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7079
7241
  }
7080
7242
  );
@@ -7083,10 +7245,14 @@ Automatically detects code files and skips ignored directories like node_modules
7083
7245
  {
7084
7246
  title: "Workspace content",
7085
7247
  description: "List content in a workspace",
7086
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7248
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7087
7249
  },
7088
7250
  async (input) => {
7089
- const result = await client.workspaceContent(input.workspace_id);
7251
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7252
+ if (!workspaceId) {
7253
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7254
+ }
7255
+ const result = await client.workspaceContent(workspaceId);
7090
7256
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7091
7257
  }
7092
7258
  );
@@ -7208,10 +7374,14 @@ Automatically detects code files and skips ignored directories like node_modules
7208
7374
  {
7209
7375
  title: "Memory timeline",
7210
7376
  description: "Get chronological timeline of memory events for a workspace",
7211
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7377
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7212
7378
  },
7213
7379
  async (input) => {
7214
- const result = await client.memoryTimeline(input.workspace_id);
7380
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7381
+ if (!workspaceId) {
7382
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7383
+ }
7384
+ const result = await client.memoryTimeline(workspaceId);
7215
7385
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7216
7386
  }
7217
7387
  );
@@ -7220,10 +7390,14 @@ Automatically detects code files and skips ignored directories like node_modules
7220
7390
  {
7221
7391
  title: "Memory summary",
7222
7392
  description: "Get condensed summary of workspace memory",
7223
- inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid() })
7393
+ inputSchema: external_exports.object({ workspace_id: external_exports.string().uuid().optional() })
7224
7394
  },
7225
7395
  async (input) => {
7226
- const result = await client.memorySummary(input.workspace_id);
7396
+ const workspaceId = resolveWorkspaceId(input.workspace_id);
7397
+ if (!workspaceId) {
7398
+ return errorResult("Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly.");
7399
+ }
7400
+ const result = await client.memorySummary(workspaceId);
7227
7401
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7228
7402
  }
7229
7403
  );
@@ -7232,10 +7406,14 @@ Automatically detects code files and skips ignored directories like node_modules
7232
7406
  {
7233
7407
  title: "Find circular dependencies",
7234
7408
  description: "Detect circular dependencies in project code",
7235
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7409
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
7236
7410
  },
7237
7411
  async (input) => {
7238
- const result = await client.findCircularDependencies(input.project_id);
7412
+ const projectId = resolveProjectId(input.project_id);
7413
+ if (!projectId) {
7414
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7415
+ }
7416
+ const result = await client.findCircularDependencies(projectId);
7239
7417
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7240
7418
  }
7241
7419
  );
@@ -7244,10 +7422,14 @@ Automatically detects code files and skips ignored directories like node_modules
7244
7422
  {
7245
7423
  title: "Find unused code",
7246
7424
  description: "Detect unused code in project",
7247
- inputSchema: external_exports.object({ project_id: external_exports.string().uuid() })
7425
+ inputSchema: external_exports.object({ project_id: external_exports.string().uuid().optional() })
7248
7426
  },
7249
7427
  async (input) => {
7250
- const result = await client.findUnusedCode(input.project_id);
7428
+ const projectId = resolveProjectId(input.project_id);
7429
+ if (!projectId) {
7430
+ return errorResult("Error: project_id is required. Please call session_init first or provide project_id explicitly.");
7431
+ }
7432
+ const result = await client.findUnusedCode(projectId);
7251
7433
  return { content: [{ type: "text", text: formatContent(result) }], structuredContent: toStructured(result) };
7252
7434
  }
7253
7435
  );
@@ -7285,7 +7467,7 @@ Automatically detects code files and skips ignored directories like node_modules
7285
7467
  title: "Initialize conversation session",
7286
7468
  description: `Initialize a new conversation session and automatically retrieve relevant context.
7287
7469
  This is the FIRST tool AI assistants should call when starting a conversation.
7288
- Returns: workspace info, project info, recent memory, recent decisions, and relevant context.
7470
+ Returns: workspace info, project info, recent memory, recent decisions, relevant context, and high-priority lessons.
7289
7471
  Automatically detects the IDE workspace/project path and can auto-index code.
7290
7472
 
7291
7473
  IMPORTANT: Pass the user's FIRST MESSAGE as context_hint to get semantically relevant context!
@@ -7392,6 +7574,117 @@ Optionally generates AI editor rules for automatic ContextStream usage.`,
7392
7574
  return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
7393
7575
  }
7394
7576
  );
7577
+ registerTool(
7578
+ "workspace_bootstrap",
7579
+ {
7580
+ title: "Create workspace + project from folder",
7581
+ description: `Create a new workspace (user-provided name) and onboard the current folder as a project.
7582
+ This is useful when session_init returns status='requires_workspace_name' (no workspaces exist yet) or when you want to create a new workspace for a repo.
7583
+
7584
+ Behavior:
7585
+ - Creates a workspace with the given name
7586
+ - Associates the folder to that workspace (writes .contextstream/config.json)
7587
+ - Initializes a session for the folder, which creates the project (folder name) and starts indexing (if enabled)`,
7588
+ inputSchema: external_exports.object({
7589
+ workspace_name: external_exports.string().min(1).describe("Name for the new workspace (ask the user)"),
7590
+ folder_path: external_exports.string().optional().describe("Absolute folder path (defaults to IDE root/cwd)"),
7591
+ description: external_exports.string().optional().describe("Optional workspace description"),
7592
+ visibility: external_exports.enum(["private", "public"]).optional().describe("Workspace visibility (default: private)"),
7593
+ create_parent_mapping: external_exports.boolean().optional().describe("Also create a parent folder mapping (e.g., /dev/company/* -> workspace)"),
7594
+ generate_editor_rules: external_exports.boolean().optional().describe("Generate AI editor rules in the folder for automatic ContextStream usage"),
7595
+ context_hint: external_exports.string().optional().describe("Optional context hint for session initialization"),
7596
+ auto_index: external_exports.boolean().optional().describe("Automatically create and index project from folder (default: true)")
7597
+ })
7598
+ },
7599
+ async (input) => {
7600
+ let folderPath = input.folder_path;
7601
+ if (!folderPath) {
7602
+ try {
7603
+ const rootsResponse = await server.server.listRoots();
7604
+ if (rootsResponse?.roots && rootsResponse.roots.length > 0) {
7605
+ folderPath = rootsResponse.roots[0].uri.replace("file://", "");
7606
+ }
7607
+ } catch {
7608
+ }
7609
+ }
7610
+ if (!folderPath) {
7611
+ folderPath = process.cwd();
7612
+ }
7613
+ if (!folderPath) {
7614
+ return errorResult("Error: folder_path is required. Provide folder_path or run from a project directory.");
7615
+ }
7616
+ const folderName = folderPath.split("/").pop() || "My Project";
7617
+ const newWorkspace = await client.createWorkspace({
7618
+ name: input.workspace_name,
7619
+ description: input.description || `Workspace created for ${folderPath}`,
7620
+ visibility: input.visibility || "private"
7621
+ });
7622
+ if (!newWorkspace?.id) {
7623
+ return errorResult("Error: failed to create workspace.");
7624
+ }
7625
+ const associateResult = await client.associateWorkspace({
7626
+ folder_path: folderPath,
7627
+ workspace_id: newWorkspace.id,
7628
+ workspace_name: newWorkspace.name || input.workspace_name,
7629
+ create_parent_mapping: input.create_parent_mapping
7630
+ });
7631
+ let rulesGenerated = [];
7632
+ if (input.generate_editor_rules) {
7633
+ const fs3 = await import("fs");
7634
+ const path3 = await import("path");
7635
+ for (const editor of getAvailableEditors()) {
7636
+ const rule = generateRuleContent(editor, {
7637
+ workspaceName: newWorkspace.name || input.workspace_name,
7638
+ workspaceId: newWorkspace.id
7639
+ });
7640
+ if (!rule) continue;
7641
+ const filePath = path3.join(folderPath, rule.filename);
7642
+ try {
7643
+ let existingContent = "";
7644
+ try {
7645
+ existingContent = fs3.readFileSync(filePath, "utf-8");
7646
+ } catch {
7647
+ }
7648
+ if (!existingContent) {
7649
+ fs3.writeFileSync(filePath, rule.content);
7650
+ rulesGenerated.push(rule.filename);
7651
+ } else if (!existingContent.includes("ContextStream Integration")) {
7652
+ fs3.writeFileSync(filePath, existingContent + "\n\n" + rule.content);
7653
+ rulesGenerated.push(rule.filename + " (appended)");
7654
+ }
7655
+ } catch {
7656
+ }
7657
+ }
7658
+ }
7659
+ const session = await client.initSession(
7660
+ {
7661
+ workspace_id: newWorkspace.id,
7662
+ context_hint: input.context_hint,
7663
+ include_recent_memory: true,
7664
+ include_decisions: true,
7665
+ auto_index: input.auto_index
7666
+ },
7667
+ [folderPath]
7668
+ );
7669
+ if (sessionManager) {
7670
+ sessionManager.markInitialized(session);
7671
+ }
7672
+ const response = {
7673
+ ...session,
7674
+ bootstrap: {
7675
+ folder_path: folderPath,
7676
+ project_name: folderName,
7677
+ workspace: {
7678
+ id: newWorkspace.id,
7679
+ name: newWorkspace.name || input.workspace_name
7680
+ },
7681
+ association: associateResult,
7682
+ editor_rules_generated: rulesGenerated.length > 0 ? rulesGenerated : void 0
7683
+ }
7684
+ };
7685
+ return { content: [{ type: "text", text: formatContent(response) }], structuredContent: toStructured(response) };
7686
+ }
7687
+ );
7395
7688
  registerTool(
7396
7689
  "session_capture",
7397
7690
  {
@@ -7578,7 +7871,7 @@ Returns lessons filtered by:
7578
7871
  limit: input.limit * 2
7579
7872
  // Fetch more to filter
7580
7873
  });
7581
- let lessons = (searchResult?.results || []).filter((item) => {
7874
+ let lessons = (searchResult.results || []).filter((item) => {
7582
7875
  const tags = item.metadata?.tags || [];
7583
7876
  const isLesson = tags.includes("lesson");
7584
7877
  if (!isLesson) return false;
@@ -8032,7 +8325,7 @@ Format options:
8032
8325
  - 'readable': Line-separated with labels
8033
8326
  - 'structured': JSON-like grouped format
8034
8327
 
8035
- Type codes: W=Workspace, P=Project, D=Decision, M=Memory, I=Insight, T=Task
8328
+ Type codes: W=Workspace, P=Project, D=Decision, M=Memory, I=Insight, T=Task, L=Lesson
8036
8329
 
8037
8330
  Example usage:
8038
8331
  1. User asks "how should I implement auth?"
@@ -8123,32 +8416,47 @@ function registerResources(server, client, apiUrl) {
8123
8416
  }
8124
8417
 
8125
8418
  // src/prompts.ts
8419
+ var ID_NOTES = [
8420
+ "Notes:",
8421
+ "- If ContextStream is not initialized in this conversation, call `session_init` first (omit ids).",
8422
+ "- Do not ask me for `workspace_id`/`project_id` \u2014 use session defaults or IDs returned by `session_init`.",
8423
+ "- Prefer omitting IDs in tool calls when the tool supports defaults."
8424
+ ];
8425
+ var upgradeUrl = process.env.CONTEXTSTREAM_UPGRADE_URL || "https://contextstream.io/pricing";
8426
+ var proPrompts = /* @__PURE__ */ new Set([
8427
+ "build-context",
8428
+ "generate-plan",
8429
+ "generate-tasks",
8430
+ "token-budget-context"
8431
+ ]);
8432
+ function promptAccessLabel(promptName) {
8433
+ return proPrompts.has(promptName) ? "PRO" : "Free";
8434
+ }
8126
8435
  function registerPrompts(server) {
8127
8436
  server.registerPrompt(
8128
8437
  "explore-codebase",
8129
8438
  {
8130
- title: "Explore Codebase",
8131
- description: "Get an overview of a project codebase structure and key components",
8132
- argsSchema: {
8133
- project_id: external_exports.string().uuid().describe("Project ID to explore"),
8134
- focus_area: external_exports.string().optional().describe('Optional area to focus on (e.g., "authentication", "api routes")')
8135
- }
8439
+ title: `Explore Codebase (${promptAccessLabel("explore-codebase")})`,
8440
+ description: "Get an overview of a project codebase structure and key components"
8136
8441
  },
8137
- async (args) => ({
8442
+ async () => ({
8138
8443
  messages: [
8139
8444
  {
8140
8445
  role: "user",
8141
8446
  content: {
8142
8447
  type: "text",
8143
- text: `I want to understand the codebase for project ${args.project_id}${args.focus_area ? ` with focus on ${args.focus_area}` : ""}.
8144
-
8145
- Please help me by:
8146
- 1. First, use \`projects.overview\` to get the project summary
8147
- 2. Use \`projects.files\` to list the key files
8148
- 3. Use \`search.semantic\` to find relevant code${args.focus_area ? ` related to "${args.focus_area}"` : ""}
8149
- 4. Summarize the architecture and key patterns you observe
8150
-
8151
- Provide a clear, structured overview that helps me navigate this codebase effectively.`
8448
+ text: [
8449
+ "I want to understand the current codebase.",
8450
+ "",
8451
+ ...ID_NOTES,
8452
+ "",
8453
+ "Please help me by:",
8454
+ "1. Use `projects_overview` to get a project summary (use session defaults; only pass `project_id` if required).",
8455
+ "2. Use `projects_files` to identify key entry points.",
8456
+ "3. If a focus area is clear from our conversation, prioritize it; otherwise ask me what to focus on.",
8457
+ "4. Use `search_semantic` (and optionally `search_hybrid`) to find the most relevant files.",
8458
+ "5. Summarize the architecture, major modules, and where to start editing."
8459
+ ].join("\n")
8152
8460
  }
8153
8461
  }
8154
8462
  ]
@@ -8157,37 +8465,32 @@ Provide a clear, structured overview that helps me navigate this codebase effect
8157
8465
  server.registerPrompt(
8158
8466
  "capture-decision",
8159
8467
  {
8160
- title: "Capture Decision",
8161
- description: "Document an architectural or technical decision in workspace memory",
8162
- argsSchema: {
8163
- workspace_id: external_exports.string().uuid().describe("Workspace ID"),
8164
- decision_title: external_exports.string().describe("Brief title of the decision"),
8165
- context: external_exports.string().describe("What prompted this decision"),
8166
- decision: external_exports.string().describe("The decision made"),
8167
- consequences: external_exports.string().optional().describe("Expected consequences or tradeoffs")
8168
- }
8468
+ title: `Capture Decision (${promptAccessLabel("capture-decision")})`,
8469
+ description: "Document an architectural or technical decision in workspace memory"
8169
8470
  },
8170
- async (args) => ({
8471
+ async () => ({
8171
8472
  messages: [
8172
8473
  {
8173
8474
  role: "user",
8174
8475
  content: {
8175
8476
  type: "text",
8176
- text: `Please document the following decision in workspace memory:
8177
-
8178
- **Title:** ${args.decision_title}
8179
- **Context:** ${args.context}
8180
- **Decision:** ${args.decision}
8181
- ${args.consequences ? `**Consequences:** ${args.consequences}` : ""}
8182
-
8183
- Use \`memory.create_event\` with:
8184
- - event_type: "decision"
8185
- - workspace_id: "${args.workspace_id}"
8186
- - title: The decision title
8187
- - content: A well-formatted ADR (Architecture Decision Record) with context, decision, and consequences
8188
- - metadata: Include relevant tags and status
8189
-
8190
- After creating, confirm the decision was recorded and summarize it.`
8477
+ text: [
8478
+ "Please capture an architectural/technical decision in ContextStream memory.",
8479
+ "",
8480
+ ...ID_NOTES,
8481
+ "",
8482
+ "Instructions:",
8483
+ "- If the decision is already described in this conversation, extract: title, context, decision, consequences/tradeoffs.",
8484
+ "- If anything is missing, ask me 1\u20133 quick questions to fill the gaps.",
8485
+ "- Then call `session_capture` with:",
8486
+ ' - event_type: "decision"',
8487
+ " - title: (short ADR title)",
8488
+ " - content: a well-formatted ADR (Context, Decision, Consequences)",
8489
+ ' - tags: include relevant tags (e.g., "adr", "architecture")',
8490
+ ' - importance: "high"',
8491
+ "",
8492
+ "After capturing, confirm what was saved."
8493
+ ].join("\n")
8191
8494
  }
8192
8495
  }
8193
8496
  ]
@@ -8196,35 +8499,35 @@ After creating, confirm the decision was recorded and summarize it.`
8196
8499
  server.registerPrompt(
8197
8500
  "review-context",
8198
8501
  {
8199
- title: "Code Review Context",
8200
- description: "Build context for reviewing code changes",
8201
- argsSchema: {
8202
- project_id: external_exports.string().uuid().describe("Project ID"),
8203
- file_paths: external_exports.string().describe("Comma-separated file paths being changed"),
8204
- change_description: external_exports.string().describe("Brief description of the changes")
8205
- }
8502
+ title: `Code Review Context (${promptAccessLabel("review-context")})`,
8503
+ description: "Build context for reviewing code changes"
8206
8504
  },
8207
- async (args) => ({
8505
+ async () => ({
8208
8506
  messages: [
8209
8507
  {
8210
8508
  role: "user",
8211
8509
  content: {
8212
8510
  type: "text",
8213
- text: `I need context to review changes in these files: ${args.file_paths}
8214
-
8215
- Change description: ${args.change_description}
8216
-
8217
- Please help me understand the impact by:
8218
- 1. Use \`graph.dependencies\` to find what depends on these files
8219
- 2. Use \`graph.impact\` to analyze potential impact
8220
- 3. Use \`memory.search\` to find related decisions or notes about these areas
8221
- 4. Use \`search.semantic\` to find related code patterns
8222
-
8223
- Provide:
8224
- - Summary of what these files do
8225
- - What other parts of the codebase might be affected
8226
- - Any relevant past decisions or context from memory
8227
- - Potential risks or areas to focus the review on`
8511
+ text: [
8512
+ "I need context to review a set of code changes.",
8513
+ "",
8514
+ ...ID_NOTES,
8515
+ "",
8516
+ "First:",
8517
+ "- If file paths and a short change description are not already in this conversation, ask me for them.",
8518
+ "",
8519
+ "Then build review context by:",
8520
+ "1. Using `graph_dependencies` to find what depends on the changed areas.",
8521
+ "2. Using `graph_impact` to assess potential blast radius.",
8522
+ "3. Using `memory_search` to find related decisions/notes.",
8523
+ "4. Using `search_semantic` to find related code patterns.",
8524
+ "",
8525
+ "Provide:",
8526
+ "- What the files/components do",
8527
+ "- What might be affected",
8528
+ "- Relevant prior decisions/lessons",
8529
+ "- Review checklist + risks to focus on"
8530
+ ].join("\n")
8228
8531
  }
8229
8532
  }
8230
8533
  ]
@@ -8233,36 +8536,36 @@ Provide:
8233
8536
  server.registerPrompt(
8234
8537
  "investigate-bug",
8235
8538
  {
8236
- title: "Investigate Bug",
8237
- description: "Build context for debugging an issue",
8238
- argsSchema: {
8239
- project_id: external_exports.string().uuid().describe("Project ID"),
8240
- error_message: external_exports.string().describe("Error message or symptom"),
8241
- affected_area: external_exports.string().optional().describe("Known affected area or component")
8242
- }
8539
+ title: `Investigate Bug (${promptAccessLabel("investigate-bug")})`,
8540
+ description: "Build context for debugging an issue"
8243
8541
  },
8244
- async (args) => ({
8542
+ async () => ({
8245
8543
  messages: [
8246
8544
  {
8247
8545
  role: "user",
8248
8546
  content: {
8249
8547
  type: "text",
8250
- text: `I'm investigating a bug:
8251
-
8252
- **Error/Symptom:** ${args.error_message}
8253
- ${args.affected_area ? `**Affected Area:** ${args.affected_area}` : ""}
8254
-
8255
- Please help me investigate by:
8256
- 1. Use \`search.semantic\` to find code related to this error
8257
- 2. Use \`search.pattern\` to find where similar errors are thrown
8258
- 3. Use \`graph.call_path\` to trace call flows if we identify key functions
8259
- 4. Use \`memory.search\` to check if this issue has been encountered before
8260
-
8261
- Provide:
8262
- - Likely locations where this error originates
8263
- - Call flow analysis
8264
- - Any related past issues from memory
8265
- - Suggested debugging approach`
8548
+ text: [
8549
+ "I want help investigating a bug.",
8550
+ "",
8551
+ ...ID_NOTES,
8552
+ "",
8553
+ "First:",
8554
+ "- If the error/symptom is not already stated, ask me for the exact error message and what I expected vs what happened.",
8555
+ "- If an affected area/component is not known, ask me where I noticed it.",
8556
+ "",
8557
+ "Then:",
8558
+ "1. Use `search_semantic` to find code related to the error/symptom.",
8559
+ "2. Use `search_pattern` to locate where similar errors are thrown or logged.",
8560
+ "3. If you identify key functions, use `graph_call_path` to trace call flows.",
8561
+ "4. Use `memory_search` to check if we have prior notes/bugs about this area.",
8562
+ "",
8563
+ "Return:",
8564
+ "- Likely origin locations",
8565
+ "- Call flow (if found)",
8566
+ "- Related past context",
8567
+ "- Suggested debugging steps"
8568
+ ].join("\n")
8266
8569
  }
8267
8570
  }
8268
8571
  ]
@@ -8271,32 +8574,32 @@ Provide:
8271
8574
  server.registerPrompt(
8272
8575
  "explore-knowledge",
8273
8576
  {
8274
- title: "Explore Knowledge Graph",
8275
- description: "Navigate and understand the knowledge graph for a workspace",
8276
- argsSchema: {
8277
- workspace_id: external_exports.string().uuid().describe("Workspace ID"),
8278
- starting_topic: external_exports.string().optional().describe("Topic to start exploration from")
8279
- }
8577
+ title: `Explore Knowledge Graph (${promptAccessLabel("explore-knowledge")})`,
8578
+ description: "Navigate and understand the knowledge graph for a workspace"
8280
8579
  },
8281
- async (args) => ({
8580
+ async () => ({
8282
8581
  messages: [
8283
8582
  {
8284
8583
  role: "user",
8285
8584
  content: {
8286
8585
  type: "text",
8287
- text: `Help me explore the knowledge captured in workspace ${args.workspace_id}${args.starting_topic ? ` starting from "${args.starting_topic}"` : ""}.
8288
-
8289
- Please:
8290
- 1. Use \`memory.list_nodes\` to see available knowledge nodes
8291
- 2. Use \`memory.decisions\` to see decision history
8292
- 3. ${args.starting_topic ? `Use \`memory.search\` to find nodes related to "${args.starting_topic}"` : "Use `memory.summary` to get an overview"}
8293
- 4. Use \`graph.related\` to explore connections between nodes
8294
-
8295
- Provide:
8296
- - Overview of knowledge captured
8297
- - Key themes and topics
8298
- - Important decisions and their rationale
8299
- - Connections between different pieces of knowledge`
8586
+ text: [
8587
+ "Help me explore the knowledge captured in this workspace.",
8588
+ "",
8589
+ ...ID_NOTES,
8590
+ "",
8591
+ "Approach:",
8592
+ "1. Use `memory_summary` for a high-level overview.",
8593
+ "2. Use `memory_decisions` to see decision history (titles + a few key details).",
8594
+ "3. Use `memory_list_nodes` to see available knowledge nodes.",
8595
+ "4. If a starting topic is clear from the conversation, use `memory_search` for it.",
8596
+ "5. Use `graph_related` on the most relevant nodes to expand connections.",
8597
+ "",
8598
+ "Provide:",
8599
+ "- Key themes and topics",
8600
+ "- Important decisions + rationale",
8601
+ "- Suggested \u201Cnext nodes\u201D to explore"
8602
+ ].join("\n")
8300
8603
  }
8301
8604
  }
8302
8605
  ]
@@ -8305,37 +8608,37 @@ Provide:
8305
8608
  server.registerPrompt(
8306
8609
  "onboard-to-project",
8307
8610
  {
8308
- title: "Project Onboarding",
8309
- description: "Generate onboarding context for a new team member",
8310
- argsSchema: {
8311
- project_id: external_exports.string().uuid().describe("Project ID"),
8312
- workspace_id: external_exports.string().uuid().describe("Workspace ID"),
8313
- role: external_exports.string().optional().describe('Role of the person being onboarded (e.g., "backend developer", "frontend developer")')
8314
- }
8611
+ title: `Project Onboarding (${promptAccessLabel("onboard-to-project")})`,
8612
+ description: "Generate onboarding context for a new team member"
8315
8613
  },
8316
- async (args) => ({
8614
+ async () => ({
8317
8615
  messages: [
8318
8616
  {
8319
8617
  role: "user",
8320
8618
  content: {
8321
8619
  type: "text",
8322
- text: `Create an onboarding guide for a new team member joining this project.
8323
- ${args.role ? `They will be working as a ${args.role}.` : ""}
8324
-
8325
- Please gather comprehensive context:
8326
- 1. Use \`projects.overview\` and \`projects.statistics\` for project summary
8327
- 2. Use \`projects.files\` to identify key entry points
8328
- 3. Use \`memory.timeline\` to see recent activity and changes
8329
- 4. Use \`memory.decisions\` to understand key architectural choices
8330
- 5. Use \`search.semantic\` to find documentation and READMEs
8331
-
8332
- Provide an onboarding guide that includes:
8333
- - Project overview and purpose
8334
- - Technology stack and architecture
8335
- - Key files and entry points${args.role ? ` relevant to ${args.role}` : ""}
8336
- - Important decisions and their rationale
8337
- - Recent changes and current focus areas
8338
- - Getting started steps`
8620
+ text: [
8621
+ "Create an onboarding guide for a new team member joining this project.",
8622
+ "",
8623
+ ...ID_NOTES,
8624
+ "",
8625
+ "First:",
8626
+ "- If the role is not specified, ask me what role they are onboarding into (backend, frontend, fullstack, etc.).",
8627
+ "",
8628
+ "Gather context:",
8629
+ "1. Use `projects_overview` and `projects_statistics` for project summary.",
8630
+ "2. Use `projects_files` to identify key entry points.",
8631
+ "3. Use `memory_timeline` and `memory_decisions` to understand recent changes and architectural choices.",
8632
+ "4. Use `search_semantic` to find READMEs/docs/setup instructions.",
8633
+ "",
8634
+ "Output:",
8635
+ "- Project overview and purpose",
8636
+ "- Tech stack + architecture map",
8637
+ "- Key files/entry points relevant to the role",
8638
+ "- Important decisions + rationale",
8639
+ "- Recent changes/current focus",
8640
+ "- Step-by-step getting started"
8641
+ ].join("\n")
8339
8642
  }
8340
8643
  }
8341
8644
  ]
@@ -8344,33 +8647,30 @@ Provide an onboarding guide that includes:
8344
8647
  server.registerPrompt(
8345
8648
  "analyze-refactoring",
8346
8649
  {
8347
- title: "Refactoring Analysis",
8348
- description: "Analyze a codebase for refactoring opportunities",
8349
- argsSchema: {
8350
- project_id: external_exports.string().uuid().describe("Project ID"),
8351
- target_area: external_exports.string().optional().describe("Specific area to analyze")
8352
- }
8650
+ title: `Refactoring Analysis (${promptAccessLabel("analyze-refactoring")})`,
8651
+ description: "Analyze a codebase for refactoring opportunities"
8353
8652
  },
8354
- async (args) => ({
8653
+ async () => ({
8355
8654
  messages: [
8356
8655
  {
8357
8656
  role: "user",
8358
8657
  content: {
8359
8658
  type: "text",
8360
- text: `Analyze the codebase for refactoring opportunities${args.target_area ? ` in ${args.target_area}` : ""}.
8361
-
8362
- Please investigate:
8363
- 1. Use \`graph.circular_dependencies\` to find circular dependencies
8364
- 2. Use \`graph.unused_code\` to find dead code
8365
- 3. Use \`search.pattern\` to find code duplication patterns
8366
- 4. Use \`projects.statistics\` to identify complex areas
8367
-
8368
- Provide:
8369
- - Circular dependencies that should be broken
8370
- - Unused code that can be removed
8371
- - Duplicate patterns that could be consolidated
8372
- - High complexity areas that need simplification
8373
- - Prioritized refactoring recommendations`
8659
+ text: [
8660
+ "Analyze the codebase for refactoring opportunities.",
8661
+ "",
8662
+ ...ID_NOTES,
8663
+ "",
8664
+ "If a target area is obvious from our conversation, focus there; otherwise ask me what area to analyze.",
8665
+ "",
8666
+ "Please investigate:",
8667
+ "1. `graph_circular_dependencies` (circular deps to break)",
8668
+ "2. `graph_unused_code` (dead code to remove)",
8669
+ "3. `search_pattern` (duplication patterns)",
8670
+ "4. `projects_statistics` (high complexity hotspots)",
8671
+ "",
8672
+ "Provide a prioritized list with quick wins vs deeper refactors."
8673
+ ].join("\n")
8374
8674
  }
8375
8675
  }
8376
8676
  ]
@@ -8379,34 +8679,452 @@ Provide:
8379
8679
  server.registerPrompt(
8380
8680
  "build-context",
8381
8681
  {
8382
- title: "Build LLM Context",
8383
- description: "Build comprehensive context for an LLM task",
8384
- argsSchema: {
8385
- query: external_exports.string().describe("What you need context for"),
8386
- workspace_id: external_exports.string().uuid().optional().describe("Workspace ID"),
8387
- project_id: external_exports.string().uuid().optional().describe("Project ID"),
8388
- include_memory: external_exports.string().optional().describe('Include memory/decisions ("true" or "false")')
8389
- }
8682
+ title: `Build LLM Context (${promptAccessLabel("build-context")})`,
8683
+ description: "Build comprehensive context for an LLM task"
8390
8684
  },
8391
- async (args) => ({
8685
+ async () => ({
8392
8686
  messages: [
8393
8687
  {
8394
8688
  role: "user",
8395
8689
  content: {
8396
8690
  type: "text",
8397
- text: `Build comprehensive context for the following task:
8398
-
8399
- **Query:** ${args.query}
8400
-
8401
- Please use \`ai.enhanced_context\` with:
8402
- - query: "${args.query}"
8403
- ${args.workspace_id ? `- workspace_id: "${args.workspace_id}"` : ""}
8404
- ${args.project_id ? `- project_id: "${args.project_id}"` : ""}
8405
- - include_code: true
8406
- - include_docs: true
8407
- - include_memory: ${args.include_memory ?? true}
8408
-
8409
- Then synthesize the retrieved context into a coherent briefing that will help with the task.`
8691
+ text: [
8692
+ "Build comprehensive context for the task we are working on.",
8693
+ "",
8694
+ `Access: ${promptAccessLabel("build-context")}${promptAccessLabel("build-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
8695
+ "",
8696
+ ...ID_NOTES,
8697
+ "",
8698
+ "First:",
8699
+ "- If the \u201Cquery\u201D is clear from the latest user request, use that.",
8700
+ "- Otherwise ask me: \u201CWhat do you need context for?\u201D",
8701
+ "",
8702
+ "Then:",
8703
+ "- Call `ai_enhanced_context` with include_code=true, include_docs=true, include_memory=true (omit IDs unless required).",
8704
+ "- Synthesize the returned context into a short briefing with links/file paths and key decisions/risks."
8705
+ ].join("\n")
8706
+ }
8707
+ }
8708
+ ]
8709
+ })
8710
+ );
8711
+ server.registerPrompt(
8712
+ "smart-search",
8713
+ {
8714
+ title: `Smart Search (${promptAccessLabel("smart-search")})`,
8715
+ description: "Search across memory, decisions, and code for a query"
8716
+ },
8717
+ async () => ({
8718
+ messages: [
8719
+ {
8720
+ role: "user",
8721
+ content: {
8722
+ type: "text",
8723
+ text: [
8724
+ "Find the most relevant context for what I am asking about.",
8725
+ "",
8726
+ ...ID_NOTES,
8727
+ "",
8728
+ "First:",
8729
+ "- If a query is clear from the conversation, use it.",
8730
+ "- Otherwise ask me what I want to find.",
8731
+ "",
8732
+ "Then:",
8733
+ "1. Use `session_smart_search` for the query.",
8734
+ "2. If results are thin, follow up with `search_hybrid` and `memory_search`.",
8735
+ "3. Return the top results with file paths/links and a short synthesis."
8736
+ ].join("\n")
8737
+ }
8738
+ }
8739
+ ]
8740
+ })
8741
+ );
8742
+ server.registerPrompt(
8743
+ "recall-context",
8744
+ {
8745
+ title: `Recall Context (${promptAccessLabel("recall-context")})`,
8746
+ description: "Retrieve relevant past decisions and memory for a query"
8747
+ },
8748
+ async () => ({
8749
+ messages: [
8750
+ {
8751
+ role: "user",
8752
+ content: {
8753
+ type: "text",
8754
+ text: [
8755
+ "Recall relevant past context (decisions, notes, lessons) for what I am asking about.",
8756
+ "",
8757
+ ...ID_NOTES,
8758
+ "",
8759
+ "First:",
8760
+ "- If a recall query is clear from the conversation, use it.",
8761
+ "- Otherwise ask me what topic I want to recall.",
8762
+ "",
8763
+ "Then:",
8764
+ "- Use `session_recall` with the query (omit IDs unless required).",
8765
+ "- Summarize the key points and any relevant decisions/lessons."
8766
+ ].join("\n")
8767
+ }
8768
+ }
8769
+ ]
8770
+ })
8771
+ );
8772
+ server.registerPrompt(
8773
+ "session-summary",
8774
+ {
8775
+ title: `Session Summary (${promptAccessLabel("session-summary")})`,
8776
+ description: "Get a compact summary of workspace/project context"
8777
+ },
8778
+ async () => ({
8779
+ messages: [
8780
+ {
8781
+ role: "user",
8782
+ content: {
8783
+ type: "text",
8784
+ text: [
8785
+ "Generate a compact, token-efficient summary of the current workspace/project context.",
8786
+ "",
8787
+ ...ID_NOTES,
8788
+ "",
8789
+ "Use `session_summary` (default max_tokens=500 unless I specify otherwise).",
8790
+ "Then list:",
8791
+ "- Top decisions (titles only)",
8792
+ "- Any high-priority lessons to watch for"
8793
+ ].join("\n")
8794
+ }
8795
+ }
8796
+ ]
8797
+ })
8798
+ );
8799
+ server.registerPrompt(
8800
+ "capture-lesson",
8801
+ {
8802
+ title: `Capture Lesson (${promptAccessLabel("capture-lesson")})`,
8803
+ description: "Record a lesson learned from an error or correction"
8804
+ },
8805
+ async () => ({
8806
+ messages: [
8807
+ {
8808
+ role: "user",
8809
+ content: {
8810
+ type: "text",
8811
+ text: [
8812
+ "Capture a lesson learned so it is surfaced in future sessions.",
8813
+ "",
8814
+ ...ID_NOTES,
8815
+ "",
8816
+ "If the lesson details are not fully present in the conversation, ask me for:",
8817
+ "- title (what to remember)",
8818
+ "- severity (low|medium|high|critical, default medium)",
8819
+ "- category (workflow|code_quality|verification|communication|project_specific)",
8820
+ "- trigger (what caused it)",
8821
+ "- impact (what went wrong)",
8822
+ "- prevention (how to prevent it)",
8823
+ "- keywords (optional)",
8824
+ "",
8825
+ "Then call `session_capture_lesson` with those fields and confirm it was saved."
8826
+ ].join("\n")
8827
+ }
8828
+ }
8829
+ ]
8830
+ })
8831
+ );
8832
+ server.registerPrompt(
8833
+ "capture-preference",
8834
+ {
8835
+ title: `Capture Preference (${promptAccessLabel("capture-preference")})`,
8836
+ description: "Save a user preference to memory"
8837
+ },
8838
+ async () => ({
8839
+ messages: [
8840
+ {
8841
+ role: "user",
8842
+ content: {
8843
+ type: "text",
8844
+ text: [
8845
+ "Save a user preference to ContextStream memory.",
8846
+ "",
8847
+ ...ID_NOTES,
8848
+ "",
8849
+ "If the preference is not explicit in the conversation, ask me what to remember.",
8850
+ "",
8851
+ "Then call `session_capture` with:",
8852
+ '- event_type: "preference"',
8853
+ "- title: (short title)",
8854
+ "- content: (preference text)",
8855
+ '- importance: "medium"'
8856
+ ].join("\n")
8857
+ }
8858
+ }
8859
+ ]
8860
+ })
8861
+ );
8862
+ server.registerPrompt(
8863
+ "capture-task",
8864
+ {
8865
+ title: `Capture Task (${promptAccessLabel("capture-task")})`,
8866
+ description: "Capture an action item into memory"
8867
+ },
8868
+ async () => ({
8869
+ messages: [
8870
+ {
8871
+ role: "user",
8872
+ content: {
8873
+ type: "text",
8874
+ text: [
8875
+ "Capture an action item into ContextStream memory.",
8876
+ "",
8877
+ ...ID_NOTES,
8878
+ "",
8879
+ "If the task is not explicit in the conversation, ask me what to capture.",
8880
+ "",
8881
+ "Then call `session_capture` with:",
8882
+ '- event_type: "task"',
8883
+ "- title: (short title)",
8884
+ "- content: (task details)",
8885
+ '- importance: "medium"'
8886
+ ].join("\n")
8887
+ }
8888
+ }
8889
+ ]
8890
+ })
8891
+ );
8892
+ server.registerPrompt(
8893
+ "capture-bug",
8894
+ {
8895
+ title: `Capture Bug (${promptAccessLabel("capture-bug")})`,
8896
+ description: "Capture a bug report into workspace memory"
8897
+ },
8898
+ async () => ({
8899
+ messages: [
8900
+ {
8901
+ role: "user",
8902
+ content: {
8903
+ type: "text",
8904
+ text: [
8905
+ "Capture a bug report in ContextStream memory.",
8906
+ "",
8907
+ ...ID_NOTES,
8908
+ "",
8909
+ "If details are missing, ask me for:",
8910
+ "- title",
8911
+ "- description",
8912
+ "- steps to reproduce (optional)",
8913
+ "- expected behavior (optional)",
8914
+ "- actual behavior (optional)",
8915
+ "",
8916
+ "Then call `session_capture` with:",
8917
+ '- event_type: "bug"',
8918
+ "- title: (bug title)",
8919
+ "- content: a well-formatted bug report (include all provided details)",
8920
+ "- tags: component/area tags",
8921
+ '- importance: "high"'
8922
+ ].join("\n")
8923
+ }
8924
+ }
8925
+ ]
8926
+ })
8927
+ );
8928
+ server.registerPrompt(
8929
+ "capture-feature",
8930
+ {
8931
+ title: `Capture Feature (${promptAccessLabel("capture-feature")})`,
8932
+ description: "Capture a feature request into workspace memory"
8933
+ },
8934
+ async () => ({
8935
+ messages: [
8936
+ {
8937
+ role: "user",
8938
+ content: {
8939
+ type: "text",
8940
+ text: [
8941
+ "Capture a feature request in ContextStream memory.",
8942
+ "",
8943
+ ...ID_NOTES,
8944
+ "",
8945
+ "If details are missing, ask me for:",
8946
+ "- title",
8947
+ "- description",
8948
+ "- rationale (optional)",
8949
+ "- acceptance criteria (optional)",
8950
+ "",
8951
+ "Then call `session_capture` with:",
8952
+ '- event_type: "feature"',
8953
+ "- title: (feature title)",
8954
+ "- content: a well-formatted feature request",
8955
+ "- tags: component/area tags",
8956
+ '- importance: "medium"'
8957
+ ].join("\n")
8958
+ }
8959
+ }
8960
+ ]
8961
+ })
8962
+ );
8963
+ server.registerPrompt(
8964
+ "generate-plan",
8965
+ {
8966
+ title: `Generate Plan (${promptAccessLabel("generate-plan")})`,
8967
+ description: "Generate a development plan from a description"
8968
+ },
8969
+ async () => ({
8970
+ messages: [
8971
+ {
8972
+ role: "user",
8973
+ content: {
8974
+ type: "text",
8975
+ text: [
8976
+ "Generate a development plan for what I am trying to build/fix.",
8977
+ "",
8978
+ `Access: ${promptAccessLabel("generate-plan")}${promptAccessLabel("generate-plan") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
8979
+ "",
8980
+ ...ID_NOTES,
8981
+ "",
8982
+ "Use the most recent user request as the plan description. If unclear, ask me for a one-paragraph description.",
8983
+ "",
8984
+ "Then call `ai_plan` and present the plan as an ordered list with milestones and risks."
8985
+ ].join("\n")
8986
+ }
8987
+ }
8988
+ ]
8989
+ })
8990
+ );
8991
+ server.registerPrompt(
8992
+ "generate-tasks",
8993
+ {
8994
+ title: `Generate Tasks (${promptAccessLabel("generate-tasks")})`,
8995
+ description: "Generate actionable tasks from a plan or description"
8996
+ },
8997
+ async () => ({
8998
+ messages: [
8999
+ {
9000
+ role: "user",
9001
+ content: {
9002
+ type: "text",
9003
+ text: [
9004
+ "Generate actionable tasks for the work we are discussing.",
9005
+ "",
9006
+ `Access: ${promptAccessLabel("generate-tasks")}${promptAccessLabel("generate-tasks") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
9007
+ "",
9008
+ ...ID_NOTES,
9009
+ "",
9010
+ "If a plan_id exists in the conversation, use it. Otherwise use the latest user request as the description.",
9011
+ "If granularity is not specified, default to medium.",
9012
+ "",
9013
+ "Call `ai_tasks` and return a checklist of tasks with acceptance criteria for each."
9014
+ ].join("\n")
9015
+ }
9016
+ }
9017
+ ]
9018
+ })
9019
+ );
9020
+ server.registerPrompt(
9021
+ "token-budget-context",
9022
+ {
9023
+ title: `Token-Budget Context (${promptAccessLabel("token-budget-context")})`,
9024
+ description: "Get the most relevant context that fits within a token budget"
9025
+ },
9026
+ async () => ({
9027
+ messages: [
9028
+ {
9029
+ role: "user",
9030
+ content: {
9031
+ type: "text",
9032
+ text: [
9033
+ "Build the most relevant context that fits within a token budget.",
9034
+ "",
9035
+ `Access: ${promptAccessLabel("token-budget-context")}${promptAccessLabel("token-budget-context") === "PRO" ? ` (upgrade: ${upgradeUrl})` : ""}`,
9036
+ "",
9037
+ ...ID_NOTES,
9038
+ "",
9039
+ "First:",
9040
+ "- If a query is clear from the conversation, use it; otherwise ask me for a query.",
9041
+ "- If max_tokens is not specified, ask me for a token budget (e.g., 500/1000/2000).",
9042
+ "",
9043
+ "Then call `ai_context_budget` and return the packed context plus a short note about what was included/excluded."
9044
+ ].join("\n")
9045
+ }
9046
+ }
9047
+ ]
9048
+ })
9049
+ );
9050
+ server.registerPrompt(
9051
+ "find-todos",
9052
+ {
9053
+ title: `Find TODOs (${promptAccessLabel("find-todos")})`,
9054
+ description: "Scan the codebase for TODO/FIXME/HACK notes and summarize"
9055
+ },
9056
+ async () => ({
9057
+ messages: [
9058
+ {
9059
+ role: "user",
9060
+ content: {
9061
+ type: "text",
9062
+ text: [
9063
+ "Scan the codebase for TODO/FIXME/HACK notes and summarize them.",
9064
+ "",
9065
+ ...ID_NOTES,
9066
+ "",
9067
+ "Use `search_pattern` with query `TODO|FIXME|HACK` (or a pattern inferred from the conversation).",
9068
+ "Group results by file path, summarize themes, and propose a small prioritized cleanup list."
9069
+ ].join("\n")
9070
+ }
9071
+ }
9072
+ ]
9073
+ })
9074
+ );
9075
+ server.registerPrompt(
9076
+ "generate-editor-rules",
9077
+ {
9078
+ title: `Generate Editor Rules (${promptAccessLabel("generate-editor-rules")})`,
9079
+ description: "Generate ContextStream AI rule files for your editor"
9080
+ },
9081
+ async () => ({
9082
+ messages: [
9083
+ {
9084
+ role: "user",
9085
+ content: {
9086
+ type: "text",
9087
+ text: [
9088
+ "Generate ContextStream AI rule files for my editor.",
9089
+ "",
9090
+ ...ID_NOTES,
9091
+ "",
9092
+ "First:",
9093
+ "- If you can infer the project folder path from the environment/IDE roots, use it.",
9094
+ "- Otherwise ask me for an absolute folder path.",
9095
+ "- Ask which editor(s) (windsurf,cursor,cline,kilo,roo,claude,aider) or default to all.",
9096
+ "",
9097
+ "Then call `generate_editor_rules` and confirm which files were created/updated."
9098
+ ].join("\n")
9099
+ }
9100
+ }
9101
+ ]
9102
+ })
9103
+ );
9104
+ server.registerPrompt(
9105
+ "index-local-repo",
9106
+ {
9107
+ title: `Index Local Repo (${promptAccessLabel("index-local-repo")})`,
9108
+ description: "Ingest local files into ContextStream for indexing/search"
9109
+ },
9110
+ async () => ({
9111
+ messages: [
9112
+ {
9113
+ role: "user",
9114
+ content: {
9115
+ type: "text",
9116
+ text: [
9117
+ "Ingest local files into ContextStream for indexing/search.",
9118
+ "",
9119
+ ...ID_NOTES,
9120
+ "",
9121
+ "First:",
9122
+ "- Ask me for the local directory path to ingest if it is not already specified.",
9123
+ "",
9124
+ "Then:",
9125
+ "- Call `projects_ingest_local` with the path (use session defaults for project, or the `project_id` returned by `session_init`).",
9126
+ "- Explain how to monitor progress via `projects_index_status`."
9127
+ ].join("\n")
8410
9128
  }
8411
9129
  }
8412
9130
  ]
@@ -8587,6 +9305,25 @@ var SessionManager = class {
8587
9305
  parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
8588
9306
  parts.push("\u{1F9E0} AUTO-CONTEXT LOADED (ContextStream)");
8589
9307
  parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
9308
+ if (context.status === "requires_workspace_name") {
9309
+ parts.push("");
9310
+ parts.push("\u26A0\uFE0F NO WORKSPACE FOUND");
9311
+ parts.push(`Folder: ${context.folder_name || "unknown"}`);
9312
+ parts.push("");
9313
+ parts.push("Please ask the user for a name for the new workspace.");
9314
+ parts.push("Then create a project for this folder.");
9315
+ parts.push("");
9316
+ parts.push("Recommended: call `workspace_bootstrap` with:");
9317
+ if (typeof context.folder_path === "string") {
9318
+ parts.push(` - folder_path: ${context.folder_path}`);
9319
+ } else {
9320
+ parts.push(" - folder_path: (your repo folder path)");
9321
+ }
9322
+ parts.push(' - workspace_name: "<user-provided name>"');
9323
+ parts.push("");
9324
+ parts.push("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
9325
+ return parts.join("\n");
9326
+ }
8590
9327
  if (context.status === "requires_workspace_selection") {
8591
9328
  parts.push("");
8592
9329
  parts.push("\u26A0\uFE0F NEW FOLDER DETECTED");
@@ -8643,6 +9380,21 @@ var SessionManager = class {
8643
9380
  parts.push(` \u2022 [${type}] ${title}`);
8644
9381
  });
8645
9382
  }
9383
+ const lessonsWarning = typeof context.lessons_warning === "string" ? context.lessons_warning : void 0;
9384
+ const lessons = Array.isArray(context.lessons) ? context.lessons : [];
9385
+ if (lessonsWarning || lessons.length > 0) {
9386
+ parts.push("");
9387
+ parts.push("\u26A0\uFE0F Lessons (review before changes):");
9388
+ if (lessonsWarning) {
9389
+ parts.push(` ${lessonsWarning}`);
9390
+ }
9391
+ lessons.slice(0, 3).forEach((l) => {
9392
+ const title = l.title || "Lesson";
9393
+ const severity = l.severity || "unknown";
9394
+ parts.push(` \u2022 [${severity}] ${title}`);
9395
+ });
9396
+ parts.push(' Use session_get_lessons(query="...") for details.');
9397
+ }
8646
9398
  parts.push("");
8647
9399
  if (context.ide_roots && context.ide_roots.length > 0) {
8648
9400
  const roots = context.ide_roots;
@@ -8705,6 +9457,8 @@ Environment variables:
8705
9457
  CONTEXTSTREAM_JWT JWT for authentication (alternative to API key)
8706
9458
  CONTEXTSTREAM_WORKSPACE_ID Optional default workspace ID
8707
9459
  CONTEXTSTREAM_PROJECT_ID Optional default project ID
9460
+ CONTEXTSTREAM_PRO_TOOLS Optional comma-separated PRO tool names (default: AI tools)
9461
+ CONTEXTSTREAM_UPGRADE_URL Optional upgrade URL shown for PRO tools on Free plan
8708
9462
 
8709
9463
  Examples:
8710
9464
  CONTEXTSTREAM_API_URL="https://api.contextstream.io" \\