@contextstream/mcp-server 0.4.49 → 0.4.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -335,7 +335,7 @@ var require_ignore = __commonJS({
335
335
  // path matching.
336
336
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
337
337
  // @returns {TestResult} true if a file is ignored
338
- test(path13, checkUnignored, mode) {
338
+ test(path21, checkUnignored, mode) {
339
339
  let ignored = false;
340
340
  let unignored = false;
341
341
  let matchedRule;
@@ -344,7 +344,7 @@ var require_ignore = __commonJS({
344
344
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
345
345
  return;
346
346
  }
347
- const matched = rule[mode].test(path13);
347
+ const matched = rule[mode].test(path21);
348
348
  if (!matched) {
349
349
  return;
350
350
  }
@@ -365,17 +365,17 @@ var require_ignore = __commonJS({
365
365
  var throwError = (message, Ctor) => {
366
366
  throw new Ctor(message);
367
367
  };
368
- var checkPath = (path13, originalPath, doThrow) => {
369
- if (!isString(path13)) {
368
+ var checkPath = (path21, originalPath, doThrow) => {
369
+ if (!isString(path21)) {
370
370
  return doThrow(
371
371
  `path must be a string, but got \`${originalPath}\``,
372
372
  TypeError
373
373
  );
374
374
  }
375
- if (!path13) {
375
+ if (!path21) {
376
376
  return doThrow(`path must not be empty`, TypeError);
377
377
  }
378
- if (checkPath.isNotRelative(path13)) {
378
+ if (checkPath.isNotRelative(path21)) {
379
379
  const r = "`path.relative()`d";
380
380
  return doThrow(
381
381
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -384,7 +384,7 @@ var require_ignore = __commonJS({
384
384
  }
385
385
  return true;
386
386
  };
387
- var isNotRelative = (path13) => REGEX_TEST_INVALID_PATH.test(path13);
387
+ var isNotRelative = (path21) => REGEX_TEST_INVALID_PATH.test(path21);
388
388
  checkPath.isNotRelative = isNotRelative;
389
389
  checkPath.convert = (p) => p;
390
390
  var Ignore2 = class {
@@ -414,19 +414,19 @@ var require_ignore = __commonJS({
414
414
  }
415
415
  // @returns {TestResult}
416
416
  _test(originalPath, cache, checkUnignored, slices) {
417
- const path13 = originalPath && checkPath.convert(originalPath);
417
+ const path21 = originalPath && checkPath.convert(originalPath);
418
418
  checkPath(
419
- path13,
419
+ path21,
420
420
  originalPath,
421
421
  this._strictPathCheck ? throwError : RETURN_FALSE
422
422
  );
423
- return this._t(path13, cache, checkUnignored, slices);
423
+ return this._t(path21, cache, checkUnignored, slices);
424
424
  }
425
- checkIgnore(path13) {
426
- if (!REGEX_TEST_TRAILING_SLASH.test(path13)) {
427
- return this.test(path13);
425
+ checkIgnore(path21) {
426
+ if (!REGEX_TEST_TRAILING_SLASH.test(path21)) {
427
+ return this.test(path21);
428
428
  }
429
- const slices = path13.split(SLASH).filter(Boolean);
429
+ const slices = path21.split(SLASH).filter(Boolean);
430
430
  slices.pop();
431
431
  if (slices.length) {
432
432
  const parent = this._t(
@@ -439,18 +439,18 @@ var require_ignore = __commonJS({
439
439
  return parent;
440
440
  }
441
441
  }
442
- return this._rules.test(path13, false, MODE_CHECK_IGNORE);
442
+ return this._rules.test(path21, false, MODE_CHECK_IGNORE);
443
443
  }
444
- _t(path13, cache, checkUnignored, slices) {
445
- if (path13 in cache) {
446
- return cache[path13];
444
+ _t(path21, cache, checkUnignored, slices) {
445
+ if (path21 in cache) {
446
+ return cache[path21];
447
447
  }
448
448
  if (!slices) {
449
- slices = path13.split(SLASH).filter(Boolean);
449
+ slices = path21.split(SLASH).filter(Boolean);
450
450
  }
451
451
  slices.pop();
452
452
  if (!slices.length) {
453
- return cache[path13] = this._rules.test(path13, checkUnignored, MODE_IGNORE);
453
+ return cache[path21] = this._rules.test(path21, checkUnignored, MODE_IGNORE);
454
454
  }
455
455
  const parent = this._t(
456
456
  slices.join(SLASH) + SLASH,
@@ -458,29 +458,29 @@ var require_ignore = __commonJS({
458
458
  checkUnignored,
459
459
  slices
460
460
  );
461
- return cache[path13] = parent.ignored ? parent : this._rules.test(path13, checkUnignored, MODE_IGNORE);
461
+ return cache[path21] = parent.ignored ? parent : this._rules.test(path21, checkUnignored, MODE_IGNORE);
462
462
  }
463
- ignores(path13) {
464
- return this._test(path13, this._ignoreCache, false).ignored;
463
+ ignores(path21) {
464
+ return this._test(path21, this._ignoreCache, false).ignored;
465
465
  }
466
466
  createFilter() {
467
- return (path13) => !this.ignores(path13);
467
+ return (path21) => !this.ignores(path21);
468
468
  }
469
469
  filter(paths) {
470
470
  return makeArray(paths).filter(this.createFilter());
471
471
  }
472
472
  // @returns {TestResult}
473
- test(path13) {
474
- return this._test(path13, this._testCache, true);
473
+ test(path21) {
474
+ return this._test(path21, this._testCache, true);
475
475
  }
476
476
  };
477
477
  var factory = (options) => new Ignore2(options);
478
- var isPathValid = (path13) => checkPath(path13 && checkPath.convert(path13), path13, RETURN_FALSE);
478
+ var isPathValid = (path21) => checkPath(path21 && checkPath.convert(path21), path21, RETURN_FALSE);
479
479
  var setupWindows = () => {
480
480
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
481
481
  checkPath.convert = makePosix;
482
482
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
483
- checkPath.isNotRelative = (path13) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path13) || isNotRelative(path13);
483
+ checkPath.isNotRelative = (path21) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path21) || isNotRelative(path21);
484
484
  };
485
485
  if (
486
486
  // Detect `process` so that it can run in browsers.
@@ -514,6 +514,7 @@ __export(hooks_config_exports, {
514
514
  getClineHooksDir: () => getClineHooksDir,
515
515
  getCursorHooksConfigPath: () => getCursorHooksConfigPath,
516
516
  getCursorHooksDir: () => getCursorHooksDir,
517
+ getHookCommand: () => getHookCommand,
517
518
  getHooksDir: () => getHooksDir,
518
519
  getIndexStatusPath: () => getIndexStatusPath,
519
520
  getKiloCodeHooksDir: () => getKiloCodeHooksDir,
@@ -539,6 +540,19 @@ __export(hooks_config_exports, {
539
540
  import * as fs4 from "node:fs/promises";
540
541
  import * as path5 from "node:path";
541
542
  import { homedir as homedir2 } from "node:os";
543
+ import { fileURLToPath } from "node:url";
544
+ function getHookCommand(hookName) {
545
+ try {
546
+ const __dirname = path5.dirname(fileURLToPath(import.meta.url));
547
+ const indexPath = path5.join(__dirname, "index.js");
548
+ const fs20 = __require("node:fs");
549
+ if (fs20.existsSync(indexPath)) {
550
+ return `node ${indexPath} hook ${hookName}`;
551
+ }
552
+ } catch {
553
+ }
554
+ return `npx @contextstream/mcp-server hook ${hookName}`;
555
+ }
542
556
  function getClaudeSettingsPath(scope, projectPath) {
543
557
  if (scope === "user") {
544
558
  return path5.join(homedir2(), ".claude", "settings.json");
@@ -558,19 +572,31 @@ function buildHooksConfig(options) {
558
572
  hooks: [
559
573
  {
560
574
  type: "command",
561
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
575
+ command: getHookCommand("user-prompt-submit"),
562
576
  timeout: 5
563
577
  }
564
578
  ]
565
579
  }
566
580
  ];
581
+ if (options?.includeOnSaveIntent !== false) {
582
+ userPromptHooks.push({
583
+ matcher: "*",
584
+ hooks: [
585
+ {
586
+ type: "command",
587
+ command: getHookCommand("on-save-intent"),
588
+ timeout: 5
589
+ }
590
+ ]
591
+ });
592
+ }
567
593
  if (options?.includeMediaAware !== false) {
568
594
  userPromptHooks.push({
569
595
  matcher: "*",
570
596
  hooks: [
571
597
  {
572
598
  type: "command",
573
- command: "npx @contextstream/mcp-server hook media-aware",
599
+ command: getHookCommand("media-aware"),
574
600
  timeout: 5
575
601
  }
576
602
  ]
@@ -583,7 +609,7 @@ function buildHooksConfig(options) {
583
609
  hooks: [
584
610
  {
585
611
  type: "command",
586
- command: "npx @contextstream/mcp-server hook pre-tool-use",
612
+ command: getHookCommand("pre-tool-use"),
587
613
  timeout: 5
588
614
  }
589
615
  ]
@@ -594,12 +620,39 @@ function buildHooksConfig(options) {
594
620
  if (options?.includePreCompact !== false) {
595
621
  config.PreCompact = [
596
622
  {
597
- // Match both manual (/compact) and automatic compaction
598
623
  matcher: "*",
599
624
  hooks: [
600
625
  {
601
626
  type: "command",
602
- command: "npx @contextstream/mcp-server hook pre-compact",
627
+ command: getHookCommand("pre-compact"),
628
+ timeout: 10
629
+ }
630
+ ]
631
+ }
632
+ ];
633
+ }
634
+ if (options?.includeSessionInit !== false) {
635
+ config.SessionStart = [
636
+ {
637
+ matcher: "*",
638
+ hooks: [
639
+ {
640
+ type: "command",
641
+ command: getHookCommand("session-init"),
642
+ timeout: 10
643
+ }
644
+ ]
645
+ }
646
+ ];
647
+ }
648
+ if (options?.includeSessionEnd !== false) {
649
+ config.Stop = [
650
+ {
651
+ matcher: "*",
652
+ hooks: [
653
+ {
654
+ type: "command",
655
+ command: getHookCommand("session-end"),
603
656
  timeout: 10
604
657
  }
605
658
  ]
@@ -613,7 +666,7 @@ function buildHooksConfig(options) {
613
666
  hooks: [
614
667
  {
615
668
  type: "command",
616
- command: "npx @contextstream/mcp-server hook post-write",
669
+ command: getHookCommand("post-write"),
617
670
  timeout: 10
618
671
  }
619
672
  ]
@@ -625,12 +678,60 @@ function buildHooksConfig(options) {
625
678
  hooks: [
626
679
  {
627
680
  type: "command",
628
- command: "npx @contextstream/mcp-server hook auto-rules",
681
+ command: getHookCommand("auto-rules"),
629
682
  timeout: 15
630
683
  }
631
684
  ]
632
685
  });
633
686
  }
687
+ if (options?.includeOnBash !== false) {
688
+ postToolUseHooks.push({
689
+ matcher: "Bash",
690
+ hooks: [
691
+ {
692
+ type: "command",
693
+ command: getHookCommand("on-bash"),
694
+ timeout: 5
695
+ }
696
+ ]
697
+ });
698
+ }
699
+ if (options?.includeOnTask !== false) {
700
+ postToolUseHooks.push({
701
+ matcher: "Task",
702
+ hooks: [
703
+ {
704
+ type: "command",
705
+ command: getHookCommand("on-task"),
706
+ timeout: 5
707
+ }
708
+ ]
709
+ });
710
+ }
711
+ if (options?.includeOnRead !== false) {
712
+ postToolUseHooks.push({
713
+ matcher: "Read|Glob|Grep",
714
+ hooks: [
715
+ {
716
+ type: "command",
717
+ command: getHookCommand("on-read"),
718
+ timeout: 5
719
+ }
720
+ ]
721
+ });
722
+ }
723
+ if (options?.includeOnWeb !== false) {
724
+ postToolUseHooks.push({
725
+ matcher: "WebFetch|WebSearch",
726
+ hooks: [
727
+ {
728
+ type: "command",
729
+ command: getHookCommand("on-web"),
730
+ timeout: 5
731
+ }
732
+ ]
733
+ });
734
+ }
634
735
  if (postToolUseHooks.length > 0) {
635
736
  config.PostToolUse = postToolUseHooks;
636
737
  }
@@ -640,17 +741,17 @@ async function installHookScripts(options) {
640
741
  const hooksDir = getHooksDir();
641
742
  await fs4.mkdir(hooksDir, { recursive: true });
642
743
  const result = {
643
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
644
- userPrompt: "npx @contextstream/mcp-server hook user-prompt-submit"
744
+ preToolUse: getHookCommand("pre-tool-use"),
745
+ userPrompt: getHookCommand("user-prompt-submit")
645
746
  };
646
747
  if (options?.includePreCompact !== false) {
647
- result.preCompact = "npx @contextstream/mcp-server hook pre-compact";
748
+ result.preCompact = getHookCommand("pre-compact");
648
749
  }
649
750
  if (options?.includeMediaAware !== false) {
650
- result.mediaAware = "npx @contextstream/mcp-server hook media-aware";
751
+ result.mediaAware = getHookCommand("media-aware");
651
752
  }
652
753
  if (options?.includeAutoRules !== false) {
653
- result.autoRules = "npx @contextstream/mcp-server hook auto-rules";
754
+ result.autoRules = getHookCommand("auto-rules");
654
755
  }
655
756
  return result;
656
757
  }
@@ -686,20 +787,20 @@ function mergeHooksIntoSettings(existingSettings, newHooks) {
686
787
  async function installClaudeCodeHooks(options) {
687
788
  const result = { scripts: [], settings: [] };
688
789
  result.scripts.push(
689
- "npx @contextstream/mcp-server hook pre-tool-use",
690
- "npx @contextstream/mcp-server hook user-prompt-submit"
790
+ getHookCommand("pre-tool-use"),
791
+ getHookCommand("user-prompt-submit")
691
792
  );
692
793
  if (options.includePreCompact !== false) {
693
- result.scripts.push("npx @contextstream/mcp-server hook pre-compact");
794
+ result.scripts.push(getHookCommand("pre-compact"));
694
795
  }
695
796
  if (options.includeMediaAware !== false) {
696
- result.scripts.push("npx @contextstream/mcp-server hook media-aware");
797
+ result.scripts.push(getHookCommand("media-aware"));
697
798
  }
698
799
  if (options.includePostWrite !== false) {
699
- result.scripts.push("npx @contextstream/mcp-server hook post-write");
800
+ result.scripts.push(getHookCommand("post-write"));
700
801
  }
701
802
  if (options.includeAutoRules !== false) {
702
- result.scripts.push("npx @contextstream/mcp-server hook auto-rules");
803
+ result.scripts.push(getHookCommand("auto-rules"));
703
804
  }
704
805
  const hooksConfig = buildHooksConfig({
705
806
  includePreCompact: options.includePreCompact,
@@ -977,6 +1078,8 @@ async function installCursorHookScripts(options) {
977
1078
  };
978
1079
  const filteredPreToolUse = filterContextStreamHooks(existingConfig.hooks.preToolUse);
979
1080
  const filteredBeforeSubmit = filterContextStreamHooks(existingConfig.hooks.beforeSubmitPrompt);
1081
+ const preToolUseCommand = getHookCommand("pre-tool-use");
1082
+ const userPromptCommand = getHookCommand("user-prompt-submit");
980
1083
  const config = {
981
1084
  version: 1,
982
1085
  hooks: {
@@ -984,7 +1087,7 @@ async function installCursorHookScripts(options) {
984
1087
  preToolUse: [
985
1088
  ...filteredPreToolUse,
986
1089
  {
987
- command: "npx @contextstream/mcp-server hook pre-tool-use",
1090
+ command: preToolUseCommand,
988
1091
  type: "command",
989
1092
  timeout: 5,
990
1093
  matcher: { tool_name: "Glob|Grep|search_files|list_files|ripgrep" }
@@ -993,7 +1096,7 @@ async function installCursorHookScripts(options) {
993
1096
  beforeSubmitPrompt: [
994
1097
  ...filteredBeforeSubmit,
995
1098
  {
996
- command: "npx @contextstream/mcp-server hook user-prompt-submit",
1099
+ command: userPromptCommand,
997
1100
  type: "command",
998
1101
  timeout: 5
999
1102
  }
@@ -1003,8 +1106,8 @@ async function installCursorHookScripts(options) {
1003
1106
  await writeCursorHooksConfig(config, options.scope, options.projectPath);
1004
1107
  const configPath = getCursorHooksConfigPath(options.scope, options.projectPath);
1005
1108
  return {
1006
- preToolUse: "npx @contextstream/mcp-server hook pre-tool-use",
1007
- beforeSubmitPrompt: "npx @contextstream/mcp-server hook user-prompt-submit",
1109
+ preToolUse: preToolUseCommand,
1110
+ beforeSubmitPrompt: userPromptCommand,
1008
1111
  config: configPath
1009
1112
  };
1010
1113
  }
@@ -1813,11 +1916,13 @@ esac
1813
1916
 
1814
1917
  exit 0
1815
1918
  `;
1816
- CLINE_HOOK_WRAPPER = (hookName) => `#!/bin/bash
1919
+ CLINE_HOOK_WRAPPER = (hookName) => {
1920
+ const command = getHookCommand(hookName);
1921
+ return `#!/bin/bash
1817
1922
  # ContextStream ${hookName} Hook Wrapper for Cline/Roo/Kilo Code
1818
- # Calls the Node.js hook via npx
1819
- exec npx @contextstream/mcp-server hook ${hookName}
1923
+ exec ${command}
1820
1924
  `;
1925
+ };
1821
1926
  CURSOR_PRETOOLUSE_HOOK_SCRIPT = `#!/usr/bin/env python3
1822
1927
  """
1823
1928
  ContextStream PreToolUse Hook for Cursor
@@ -2825,6 +2930,9 @@ function parseTranscript(transcriptPath) {
2825
2930
  const activeFiles = /* @__PURE__ */ new Set();
2826
2931
  const recentMessages = [];
2827
2932
  const toolCalls = [];
2933
+ const messages = [];
2934
+ let startedAt = (/* @__PURE__ */ new Date()).toISOString();
2935
+ let firstTimestamp = true;
2828
2936
  try {
2829
2937
  const content = fs10.readFileSync(transcriptPath, "utf-8");
2830
2938
  const lines = content.split("\n");
@@ -2833,6 +2941,11 @@ function parseTranscript(transcriptPath) {
2833
2941
  try {
2834
2942
  const entry = JSON.parse(line);
2835
2943
  const msgType = entry.type || "";
2944
+ const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
2945
+ if (firstTimestamp && entry.timestamp) {
2946
+ startedAt = entry.timestamp;
2947
+ firstTimestamp = false;
2948
+ }
2836
2949
  if (msgType === "tool_use") {
2837
2950
  const toolName = entry.name || "";
2838
2951
  const toolInput = entry.input || {};
@@ -2848,11 +2961,40 @@ function parseTranscript(transcriptPath) {
2848
2961
  activeFiles.add(`[glob:${pattern}]`);
2849
2962
  }
2850
2963
  }
2851
- }
2852
- if (msgType === "assistant" && entry.content) {
2853
- const content2 = entry.content;
2854
- if (typeof content2 === "string" && content2.length > 50) {
2855
- recentMessages.push(content2.slice(0, 500));
2964
+ messages.push({
2965
+ role: "assistant",
2966
+ content: `[Tool: ${toolName}]`,
2967
+ timestamp,
2968
+ tool_calls: { name: toolName, input: toolInput }
2969
+ });
2970
+ } else if (msgType === "tool_result") {
2971
+ const resultContent = typeof entry.content === "string" ? entry.content.slice(0, 2e3) : JSON.stringify(entry.content || {}).slice(0, 2e3);
2972
+ messages.push({
2973
+ role: "tool",
2974
+ content: resultContent,
2975
+ timestamp,
2976
+ tool_results: { name: entry.name }
2977
+ });
2978
+ } else if (msgType === "user" || entry.role === "user") {
2979
+ const userContent = typeof entry.content === "string" ? entry.content : "";
2980
+ if (userContent) {
2981
+ messages.push({
2982
+ role: "user",
2983
+ content: userContent,
2984
+ timestamp
2985
+ });
2986
+ }
2987
+ } else if (msgType === "assistant" || entry.role === "assistant") {
2988
+ const assistantContent = typeof entry.content === "string" ? entry.content : "";
2989
+ if (assistantContent) {
2990
+ messages.push({
2991
+ role: "assistant",
2992
+ content: assistantContent,
2993
+ timestamp
2994
+ });
2995
+ if (assistantContent.length > 50) {
2996
+ recentMessages.push(assistantContent.slice(0, 500));
2997
+ }
2856
2998
  }
2857
2999
  }
2858
3000
  } catch {
@@ -2865,10 +3007,56 @@ function parseTranscript(transcriptPath) {
2865
3007
  activeFiles: Array.from(activeFiles).slice(-20),
2866
3008
  // Last 20 files
2867
3009
  toolCallCount: toolCalls.length,
2868
- messageCount: recentMessages.length,
2869
- lastTools: toolCalls.slice(-10).map((t) => t.name)
3010
+ messageCount: messages.length,
3011
+ lastTools: toolCalls.slice(-10).map((t) => t.name),
2870
3012
  // Last 10 tool names
3013
+ messages,
3014
+ startedAt
3015
+ };
3016
+ }
3017
+ async function saveFullTranscript(sessionId, transcriptData, trigger) {
3018
+ if (!API_KEY2) {
3019
+ return { success: false, message: "No API key configured" };
3020
+ }
3021
+ if (transcriptData.messages.length === 0) {
3022
+ return { success: false, message: "No messages to save" };
3023
+ }
3024
+ const payload = {
3025
+ session_id: sessionId,
3026
+ messages: transcriptData.messages,
3027
+ started_at: transcriptData.startedAt,
3028
+ source_type: "pre_compact",
3029
+ title: `Pre-compaction save (${trigger})`,
3030
+ metadata: {
3031
+ trigger,
3032
+ active_files: transcriptData.activeFiles,
3033
+ tool_call_count: transcriptData.toolCallCount
3034
+ },
3035
+ tags: ["pre_compaction", trigger]
2871
3036
  };
3037
+ if (WORKSPACE_ID) {
3038
+ payload.workspace_id = WORKSPACE_ID;
3039
+ }
3040
+ try {
3041
+ const controller = new AbortController();
3042
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
3043
+ const response = await fetch(`${API_URL2}/api/v1/transcripts`, {
3044
+ method: "POST",
3045
+ headers: {
3046
+ "Content-Type": "application/json",
3047
+ "X-API-Key": API_KEY2
3048
+ },
3049
+ body: JSON.stringify(payload),
3050
+ signal: controller.signal
3051
+ });
3052
+ clearTimeout(timeoutId);
3053
+ if (response.ok) {
3054
+ return { success: true, message: `Transcript saved (${transcriptData.messages.length} messages)` };
3055
+ }
3056
+ return { success: false, message: `API error: ${response.status}` };
3057
+ } catch (error) {
3058
+ return { success: false, message: String(error) };
3059
+ }
2872
3060
  }
2873
3061
  async function saveSnapshot(sessionId, transcriptData, trigger) {
2874
3062
  if (!API_KEY2) {
@@ -2950,13 +3138,19 @@ async function runPreCompactHook() {
2950
3138
  }
2951
3139
  let autoSaveStatus = "";
2952
3140
  if (AUTO_SAVE && API_KEY2) {
2953
- const { success, message } = await saveSnapshot(sessionId, transcriptData, trigger);
2954
- if (success) {
3141
+ const transcriptResult = await saveFullTranscript(sessionId, transcriptData, trigger);
3142
+ if (transcriptResult.success) {
2955
3143
  autoSaveStatus = `
2956
- [ContextStream: Auto-saved snapshot with ${transcriptData.activeFiles.length} active files]`;
3144
+ [ContextStream: ${transcriptResult.message}]`;
2957
3145
  } else {
2958
- autoSaveStatus = `
3146
+ const { success, message } = await saveSnapshot(sessionId, transcriptData, trigger);
3147
+ if (success) {
3148
+ autoSaveStatus = `
3149
+ [ContextStream: Auto-saved snapshot with ${transcriptData.activeFiles.length} active files (transcript save failed: ${transcriptResult.message})]`;
3150
+ } else {
3151
+ autoSaveStatus = `
2959
3152
  [ContextStream: Auto-save failed - ${message}]`;
3153
+ }
2960
3154
  }
2961
3155
  }
2962
3156
  const filesList = transcriptData.activeFiles.slice(0, 5).join(", ") || "none detected";
@@ -3103,46 +3297,1689 @@ async function runAutoRulesHook() {
3103
3297
  if (!inputData.trim()) {
3104
3298
  process.exit(0);
3105
3299
  }
3106
- let input;
3300
+ let input;
3301
+ try {
3302
+ input = JSON.parse(inputData);
3303
+ } catch {
3304
+ process.exit(0);
3305
+ }
3306
+ const toolName = input.tool_name || input.toolName || "";
3307
+ const isContextTool = toolName.includes("init") || toolName.includes("context") || toolName.includes("session_init") || toolName.includes("context_smart");
3308
+ if (!isContextTool) {
3309
+ process.exit(0);
3310
+ }
3311
+ const cwd = extractCwd3(input);
3312
+ const pythonHooks = detectPythonHooks(cwd);
3313
+ const hasPythonHooksToUpgrade = pythonHooks.global || pythonHooks.project;
3314
+ const rulesNotice = extractRulesNotice(input);
3315
+ const rulesNeedUpdate = rulesNotice && rulesNotice.status !== "current";
3316
+ if (!hasPythonHooksToUpgrade && !rulesNeedUpdate) {
3317
+ process.exit(0);
3318
+ }
3319
+ const folderPath = rulesNotice?.update_args?.folder_path || cwd;
3320
+ try {
3321
+ await upgradeHooksForFolder(folderPath);
3322
+ markAsRan();
3323
+ } catch {
3324
+ }
3325
+ process.exit(0);
3326
+ }
3327
+ var API_URL3, API_KEY3, ENABLED6, MARKER_FILE, COOLDOWN_MS, isDirectRun6;
3328
+ var init_auto_rules = __esm({
3329
+ "src/hooks/auto-rules.ts"() {
3330
+ "use strict";
3331
+ API_URL3 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3332
+ API_KEY3 = process.env.CONTEXTSTREAM_API_KEY || "";
3333
+ ENABLED6 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
3334
+ MARKER_FILE = path12.join(homedir9(), ".contextstream", ".auto-rules-ran");
3335
+ COOLDOWN_MS = 4 * 60 * 60 * 1e3;
3336
+ isDirectRun6 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
3337
+ if (isDirectRun6) {
3338
+ runAutoRulesHook().catch(() => process.exit(0));
3339
+ }
3340
+ }
3341
+ });
3342
+
3343
+ // src/hooks/post-compact.ts
3344
+ var post_compact_exports = {};
3345
+ __export(post_compact_exports, {
3346
+ runPostCompactHook: () => runPostCompactHook
3347
+ });
3348
+ import * as fs12 from "node:fs";
3349
+ import * as path13 from "node:path";
3350
+ import { homedir as homedir10 } from "node:os";
3351
+ function loadConfigFromMcpJson2(cwd) {
3352
+ let searchDir = path13.resolve(cwd);
3353
+ for (let i = 0; i < 5; i++) {
3354
+ if (!API_KEY4) {
3355
+ const mcpPath = path13.join(searchDir, ".mcp.json");
3356
+ if (fs12.existsSync(mcpPath)) {
3357
+ try {
3358
+ const content = fs12.readFileSync(mcpPath, "utf-8");
3359
+ const config = JSON.parse(content);
3360
+ const csEnv = config.mcpServers?.contextstream?.env;
3361
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3362
+ API_KEY4 = csEnv.CONTEXTSTREAM_API_KEY;
3363
+ }
3364
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3365
+ API_URL4 = csEnv.CONTEXTSTREAM_API_URL;
3366
+ }
3367
+ } catch {
3368
+ }
3369
+ }
3370
+ }
3371
+ if (!WORKSPACE_ID2) {
3372
+ const csConfigPath = path13.join(searchDir, ".contextstream", "config.json");
3373
+ if (fs12.existsSync(csConfigPath)) {
3374
+ try {
3375
+ const content = fs12.readFileSync(csConfigPath, "utf-8");
3376
+ const csConfig = JSON.parse(content);
3377
+ if (csConfig.workspace_id) {
3378
+ WORKSPACE_ID2 = csConfig.workspace_id;
3379
+ }
3380
+ } catch {
3381
+ }
3382
+ }
3383
+ }
3384
+ const parentDir = path13.dirname(searchDir);
3385
+ if (parentDir === searchDir) break;
3386
+ searchDir = parentDir;
3387
+ }
3388
+ if (!API_KEY4) {
3389
+ const homeMcpPath = path13.join(homedir10(), ".mcp.json");
3390
+ if (fs12.existsSync(homeMcpPath)) {
3391
+ try {
3392
+ const content = fs12.readFileSync(homeMcpPath, "utf-8");
3393
+ const config = JSON.parse(content);
3394
+ const csEnv = config.mcpServers?.contextstream?.env;
3395
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3396
+ API_KEY4 = csEnv.CONTEXTSTREAM_API_KEY;
3397
+ }
3398
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3399
+ API_URL4 = csEnv.CONTEXTSTREAM_API_URL;
3400
+ }
3401
+ } catch {
3402
+ }
3403
+ }
3404
+ }
3405
+ }
3406
+ async function fetchLastTranscript(sessionId) {
3407
+ if (!API_KEY4) {
3408
+ return null;
3409
+ }
3410
+ try {
3411
+ const controller = new AbortController();
3412
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
3413
+ const url = new URL(`${API_URL4}/api/v1/transcripts`);
3414
+ url.searchParams.set("session_id", sessionId);
3415
+ url.searchParams.set("limit", "1");
3416
+ url.searchParams.set("sort", "created_at:desc");
3417
+ if (WORKSPACE_ID2) {
3418
+ url.searchParams.set("workspace_id", WORKSPACE_ID2);
3419
+ }
3420
+ const response = await fetch(url.toString(), {
3421
+ method: "GET",
3422
+ headers: {
3423
+ "X-API-Key": API_KEY4
3424
+ },
3425
+ signal: controller.signal
3426
+ });
3427
+ clearTimeout(timeoutId);
3428
+ if (response.ok) {
3429
+ const data = await response.json();
3430
+ if (data.transcripts && data.transcripts.length > 0) {
3431
+ return data.transcripts[0];
3432
+ }
3433
+ }
3434
+ return null;
3435
+ } catch {
3436
+ return null;
3437
+ }
3438
+ }
3439
+ function formatTranscriptSummary(transcript) {
3440
+ const messages = transcript.messages || [];
3441
+ const activeFiles = transcript.metadata?.active_files || [];
3442
+ const toolCallCount = transcript.metadata?.tool_call_count || 0;
3443
+ const userMessages = messages.filter((m) => m.role === "user").slice(-3).map((m) => `- "${m.content.slice(0, 100)}${m.content.length > 100 ? "..." : ""}"`).join("\n");
3444
+ const lastAssistant = messages.filter((m) => m.role === "assistant" && !m.content.startsWith("[Tool:")).slice(-1)[0];
3445
+ const lastWork = lastAssistant ? lastAssistant.content.slice(0, 300) + (lastAssistant.content.length > 300 ? "..." : "") : "None recorded";
3446
+ return `## Pre-Compaction State Restored
3447
+
3448
+ ### Active Files (${activeFiles.length})
3449
+ ${activeFiles.slice(0, 10).map((f) => `- ${f}`).join("\n") || "None tracked"}
3450
+
3451
+ ### Recent User Requests
3452
+ ${userMessages || "None recorded"}
3453
+
3454
+ ### Last Work in Progress
3455
+ ${lastWork}
3456
+
3457
+ ### Session Stats
3458
+ - Tool calls: ${toolCallCount}
3459
+ - Messages: ${messages.length}
3460
+ - Saved at: ${transcript.created_at}`;
3461
+ }
3462
+ async function runPostCompactHook() {
3463
+ if (!ENABLED7) {
3464
+ process.exit(0);
3465
+ }
3466
+ let inputData = "";
3467
+ for await (const chunk of process.stdin) {
3468
+ inputData += chunk;
3469
+ }
3470
+ if (!inputData.trim()) {
3471
+ process.exit(0);
3472
+ }
3473
+ let input;
3474
+ try {
3475
+ input = JSON.parse(inputData);
3476
+ } catch {
3477
+ process.exit(0);
3478
+ }
3479
+ const cwd = input.cwd || process.cwd();
3480
+ loadConfigFromMcpJson2(cwd);
3481
+ const sessionId = input.session_id || "";
3482
+ let restoredContext = "";
3483
+ if (sessionId && API_KEY4) {
3484
+ const transcript = await fetchLastTranscript(sessionId);
3485
+ if (transcript) {
3486
+ restoredContext = formatTranscriptSummary(transcript);
3487
+ }
3488
+ }
3489
+ const context = `[POST-COMPACTION - Context Restored]
3490
+
3491
+ ${restoredContext || "No saved state found. Starting fresh."}
3492
+
3493
+ **IMPORTANT:** Call \`mcp__contextstream__context(user_message="resuming after compaction")\` to get full context and any pending tasks.
3494
+
3495
+ The conversation was compacted to save memory. The above summary was automatically restored from ContextStream.`;
3496
+ console.log(
3497
+ JSON.stringify({
3498
+ hookSpecificOutput: {
3499
+ hookEventName: "PostCompact",
3500
+ additionalContext: context
3501
+ }
3502
+ })
3503
+ );
3504
+ process.exit(0);
3505
+ }
3506
+ var ENABLED7, API_URL4, API_KEY4, WORKSPACE_ID2, isDirectRun7;
3507
+ var init_post_compact = __esm({
3508
+ "src/hooks/post-compact.ts"() {
3509
+ "use strict";
3510
+ ENABLED7 = process.env.CONTEXTSTREAM_POSTCOMPACT_ENABLED !== "false";
3511
+ API_URL4 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3512
+ API_KEY4 = process.env.CONTEXTSTREAM_API_KEY || "";
3513
+ WORKSPACE_ID2 = null;
3514
+ isDirectRun7 = process.argv[1]?.includes("post-compact") || process.argv[2] === "post-compact";
3515
+ if (isDirectRun7) {
3516
+ runPostCompactHook().catch(() => process.exit(0));
3517
+ }
3518
+ }
3519
+ });
3520
+
3521
+ // src/hooks/on-bash.ts
3522
+ var on_bash_exports = {};
3523
+ __export(on_bash_exports, {
3524
+ runOnBashHook: () => runOnBashHook
3525
+ });
3526
+ import * as fs13 from "node:fs";
3527
+ import * as path14 from "node:path";
3528
+ import { homedir as homedir11 } from "node:os";
3529
+ function loadConfigFromMcpJson3(cwd) {
3530
+ let searchDir = path14.resolve(cwd);
3531
+ for (let i = 0; i < 5; i++) {
3532
+ if (!API_KEY5) {
3533
+ const mcpPath = path14.join(searchDir, ".mcp.json");
3534
+ if (fs13.existsSync(mcpPath)) {
3535
+ try {
3536
+ const content = fs13.readFileSync(mcpPath, "utf-8");
3537
+ const config = JSON.parse(content);
3538
+ const csEnv = config.mcpServers?.contextstream?.env;
3539
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3540
+ API_KEY5 = csEnv.CONTEXTSTREAM_API_KEY;
3541
+ }
3542
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3543
+ API_URL5 = csEnv.CONTEXTSTREAM_API_URL;
3544
+ }
3545
+ } catch {
3546
+ }
3547
+ }
3548
+ }
3549
+ if (!WORKSPACE_ID3) {
3550
+ const csConfigPath = path14.join(searchDir, ".contextstream", "config.json");
3551
+ if (fs13.existsSync(csConfigPath)) {
3552
+ try {
3553
+ const content = fs13.readFileSync(csConfigPath, "utf-8");
3554
+ const csConfig = JSON.parse(content);
3555
+ if (csConfig.workspace_id) {
3556
+ WORKSPACE_ID3 = csConfig.workspace_id;
3557
+ }
3558
+ } catch {
3559
+ }
3560
+ }
3561
+ }
3562
+ const parentDir = path14.dirname(searchDir);
3563
+ if (parentDir === searchDir) break;
3564
+ searchDir = parentDir;
3565
+ }
3566
+ if (!API_KEY5) {
3567
+ const homeMcpPath = path14.join(homedir11(), ".mcp.json");
3568
+ if (fs13.existsSync(homeMcpPath)) {
3569
+ try {
3570
+ const content = fs13.readFileSync(homeMcpPath, "utf-8");
3571
+ const config = JSON.parse(content);
3572
+ const csEnv = config.mcpServers?.contextstream?.env;
3573
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3574
+ API_KEY5 = csEnv.CONTEXTSTREAM_API_KEY;
3575
+ }
3576
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3577
+ API_URL5 = csEnv.CONTEXTSTREAM_API_URL;
3578
+ }
3579
+ } catch {
3580
+ }
3581
+ }
3582
+ }
3583
+ }
3584
+ async function captureCommand(command, output, exitCode, isError, sessionId) {
3585
+ if (!API_KEY5) return;
3586
+ const payload = {
3587
+ event_type: isError ? "bash_error" : "bash_command",
3588
+ title: isError ? `Bash Error: ${command.slice(0, 50)}...` : `Command: ${command.slice(0, 50)}...`,
3589
+ content: JSON.stringify({
3590
+ command,
3591
+ output: output.slice(0, 2e3),
3592
+ exit_code: exitCode,
3593
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3594
+ }),
3595
+ importance: isError ? "high" : "low",
3596
+ tags: isError ? ["bash", "error", "command"] : ["bash", "command"],
3597
+ source_type: "hook",
3598
+ session_id: sessionId
3599
+ };
3600
+ if (WORKSPACE_ID3) {
3601
+ payload.workspace_id = WORKSPACE_ID3;
3602
+ }
3603
+ try {
3604
+ const controller = new AbortController();
3605
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
3606
+ await fetch(`${API_URL5}/api/v1/memory/events`, {
3607
+ method: "POST",
3608
+ headers: {
3609
+ "Content-Type": "application/json",
3610
+ "X-API-Key": API_KEY5
3611
+ },
3612
+ body: JSON.stringify(payload),
3613
+ signal: controller.signal
3614
+ });
3615
+ clearTimeout(timeoutId);
3616
+ } catch {
3617
+ }
3618
+ }
3619
+ async function suggestLesson(command, error) {
3620
+ const errorPatterns = [
3621
+ {
3622
+ pattern: /command not found/i,
3623
+ lesson: `The command "${command.split(" ")[0]}" is not installed. Check if the package needs to be installed first.`
3624
+ },
3625
+ {
3626
+ pattern: /permission denied/i,
3627
+ lesson: "Permission denied. May need sudo or to check file permissions."
3628
+ },
3629
+ {
3630
+ pattern: /no such file or directory/i,
3631
+ lesson: "Path does not exist. Verify the file/directory path before running commands."
3632
+ },
3633
+ {
3634
+ pattern: /EADDRINUSE|address already in use/i,
3635
+ lesson: "Port is already in use. Kill the existing process or use a different port."
3636
+ },
3637
+ {
3638
+ pattern: /npm ERR!|ERESOLVE/i,
3639
+ lesson: "npm dependency conflict. Try `npm install --legacy-peer-deps` or check package versions."
3640
+ },
3641
+ {
3642
+ pattern: /ENOENT.*package\.json/i,
3643
+ lesson: "No package.json found. Make sure you're in the right directory or run `npm init`."
3644
+ },
3645
+ {
3646
+ pattern: /git.*not a git repository/i,
3647
+ lesson: "Not in a git repository. Run `git init` or navigate to a git repo."
3648
+ }
3649
+ ];
3650
+ for (const { pattern, lesson } of errorPatterns) {
3651
+ if (pattern.test(error)) {
3652
+ return lesson;
3653
+ }
3654
+ }
3655
+ return null;
3656
+ }
3657
+ async function runOnBashHook() {
3658
+ if (!ENABLED8) {
3659
+ process.exit(0);
3660
+ }
3661
+ let inputData = "";
3662
+ for await (const chunk of process.stdin) {
3663
+ inputData += chunk;
3664
+ }
3665
+ if (!inputData.trim()) {
3666
+ process.exit(0);
3667
+ }
3668
+ let input;
3669
+ try {
3670
+ input = JSON.parse(inputData);
3671
+ } catch {
3672
+ process.exit(0);
3673
+ }
3674
+ if (input.tool_name !== "Bash") {
3675
+ process.exit(0);
3676
+ }
3677
+ const cwd = input.cwd || process.cwd();
3678
+ loadConfigFromMcpJson3(cwd);
3679
+ const command = input.tool_input?.command || "";
3680
+ const output = input.tool_result?.output || input.tool_result?.error || "";
3681
+ const exitCode = input.tool_result?.exit_code ?? 0;
3682
+ const sessionId = input.session_id || "unknown";
3683
+ const isError = exitCode !== 0 || !!input.tool_result?.error;
3684
+ captureCommand(command, output, exitCode, isError, sessionId).catch(() => {
3685
+ });
3686
+ if (isError) {
3687
+ const lesson = await suggestLesson(command, output);
3688
+ if (lesson) {
3689
+ console.log(
3690
+ JSON.stringify({
3691
+ hookSpecificOutput: {
3692
+ hookEventName: "PostToolUse",
3693
+ additionalContext: `[ContextStream Insight] ${lesson}`
3694
+ }
3695
+ })
3696
+ );
3697
+ process.exit(0);
3698
+ }
3699
+ }
3700
+ process.exit(0);
3701
+ }
3702
+ var ENABLED8, API_URL5, API_KEY5, WORKSPACE_ID3, isDirectRun8;
3703
+ var init_on_bash = __esm({
3704
+ "src/hooks/on-bash.ts"() {
3705
+ "use strict";
3706
+ ENABLED8 = process.env.CONTEXTSTREAM_BASH_HOOK_ENABLED !== "false";
3707
+ API_URL5 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3708
+ API_KEY5 = process.env.CONTEXTSTREAM_API_KEY || "";
3709
+ WORKSPACE_ID3 = null;
3710
+ isDirectRun8 = process.argv[1]?.includes("on-bash") || process.argv[2] === "on-bash";
3711
+ if (isDirectRun8) {
3712
+ runOnBashHook().catch(() => process.exit(0));
3713
+ }
3714
+ }
3715
+ });
3716
+
3717
+ // src/hooks/on-task.ts
3718
+ var on_task_exports = {};
3719
+ __export(on_task_exports, {
3720
+ runOnTaskHook: () => runOnTaskHook
3721
+ });
3722
+ import * as fs14 from "node:fs";
3723
+ import * as path15 from "node:path";
3724
+ import { homedir as homedir12 } from "node:os";
3725
+ function loadConfigFromMcpJson4(cwd) {
3726
+ let searchDir = path15.resolve(cwd);
3727
+ for (let i = 0; i < 5; i++) {
3728
+ if (!API_KEY6) {
3729
+ const mcpPath = path15.join(searchDir, ".mcp.json");
3730
+ if (fs14.existsSync(mcpPath)) {
3731
+ try {
3732
+ const content = fs14.readFileSync(mcpPath, "utf-8");
3733
+ const config = JSON.parse(content);
3734
+ const csEnv = config.mcpServers?.contextstream?.env;
3735
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3736
+ API_KEY6 = csEnv.CONTEXTSTREAM_API_KEY;
3737
+ }
3738
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3739
+ API_URL6 = csEnv.CONTEXTSTREAM_API_URL;
3740
+ }
3741
+ } catch {
3742
+ }
3743
+ }
3744
+ }
3745
+ if (!WORKSPACE_ID4) {
3746
+ const csConfigPath = path15.join(searchDir, ".contextstream", "config.json");
3747
+ if (fs14.existsSync(csConfigPath)) {
3748
+ try {
3749
+ const content = fs14.readFileSync(csConfigPath, "utf-8");
3750
+ const csConfig = JSON.parse(content);
3751
+ if (csConfig.workspace_id) {
3752
+ WORKSPACE_ID4 = csConfig.workspace_id;
3753
+ }
3754
+ } catch {
3755
+ }
3756
+ }
3757
+ }
3758
+ const parentDir = path15.dirname(searchDir);
3759
+ if (parentDir === searchDir) break;
3760
+ searchDir = parentDir;
3761
+ }
3762
+ if (!API_KEY6) {
3763
+ const homeMcpPath = path15.join(homedir12(), ".mcp.json");
3764
+ if (fs14.existsSync(homeMcpPath)) {
3765
+ try {
3766
+ const content = fs14.readFileSync(homeMcpPath, "utf-8");
3767
+ const config = JSON.parse(content);
3768
+ const csEnv = config.mcpServers?.contextstream?.env;
3769
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3770
+ API_KEY6 = csEnv.CONTEXTSTREAM_API_KEY;
3771
+ }
3772
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3773
+ API_URL6 = csEnv.CONTEXTSTREAM_API_URL;
3774
+ }
3775
+ } catch {
3776
+ }
3777
+ }
3778
+ }
3779
+ }
3780
+ async function captureTaskInvocation(description, prompt, agentType, result, sessionId) {
3781
+ if (!API_KEY6) return;
3782
+ const payload = {
3783
+ event_type: "task_agent",
3784
+ title: `Agent: ${agentType} - ${description}`,
3785
+ content: JSON.stringify({
3786
+ description,
3787
+ prompt: prompt.slice(0, 1e3),
3788
+ agent_type: agentType,
3789
+ result: result.slice(0, 2e3),
3790
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3791
+ }),
3792
+ importance: "medium",
3793
+ tags: ["task", "agent", agentType.toLowerCase()],
3794
+ source_type: "hook",
3795
+ session_id: sessionId
3796
+ };
3797
+ if (WORKSPACE_ID4) {
3798
+ payload.workspace_id = WORKSPACE_ID4;
3799
+ }
3800
+ try {
3801
+ const controller = new AbortController();
3802
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
3803
+ await fetch(`${API_URL6}/api/v1/memory/events`, {
3804
+ method: "POST",
3805
+ headers: {
3806
+ "Content-Type": "application/json",
3807
+ "X-API-Key": API_KEY6
3808
+ },
3809
+ body: JSON.stringify(payload),
3810
+ signal: controller.signal
3811
+ });
3812
+ clearTimeout(timeoutId);
3813
+ } catch {
3814
+ }
3815
+ }
3816
+ async function runOnTaskHook() {
3817
+ if (!ENABLED9) {
3818
+ process.exit(0);
3819
+ }
3820
+ let inputData = "";
3821
+ for await (const chunk of process.stdin) {
3822
+ inputData += chunk;
3823
+ }
3824
+ if (!inputData.trim()) {
3825
+ process.exit(0);
3826
+ }
3827
+ let input;
3828
+ try {
3829
+ input = JSON.parse(inputData);
3830
+ } catch {
3831
+ process.exit(0);
3832
+ }
3833
+ if (input.tool_name !== "Task") {
3834
+ process.exit(0);
3835
+ }
3836
+ const cwd = input.cwd || process.cwd();
3837
+ loadConfigFromMcpJson4(cwd);
3838
+ const description = input.tool_input?.description || "Unknown task";
3839
+ const prompt = input.tool_input?.prompt || "";
3840
+ const agentType = input.tool_input?.subagent_type || "general-purpose";
3841
+ const result = input.tool_result?.output || "";
3842
+ const sessionId = input.session_id || "unknown";
3843
+ captureTaskInvocation(description, prompt, agentType, result, sessionId).catch(() => {
3844
+ });
3845
+ process.exit(0);
3846
+ }
3847
+ var ENABLED9, API_URL6, API_KEY6, WORKSPACE_ID4, isDirectRun9;
3848
+ var init_on_task = __esm({
3849
+ "src/hooks/on-task.ts"() {
3850
+ "use strict";
3851
+ ENABLED9 = process.env.CONTEXTSTREAM_TASK_HOOK_ENABLED !== "false";
3852
+ API_URL6 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3853
+ API_KEY6 = process.env.CONTEXTSTREAM_API_KEY || "";
3854
+ WORKSPACE_ID4 = null;
3855
+ isDirectRun9 = process.argv[1]?.includes("on-task") || process.argv[2] === "on-task";
3856
+ if (isDirectRun9) {
3857
+ runOnTaskHook().catch(() => process.exit(0));
3858
+ }
3859
+ }
3860
+ });
3861
+
3862
+ // src/hooks/on-read.ts
3863
+ var on_read_exports = {};
3864
+ __export(on_read_exports, {
3865
+ runOnReadHook: () => runOnReadHook
3866
+ });
3867
+ import * as fs15 from "node:fs";
3868
+ import * as path16 from "node:path";
3869
+ import { homedir as homedir13 } from "node:os";
3870
+ function loadConfigFromMcpJson5(cwd) {
3871
+ let searchDir = path16.resolve(cwd);
3872
+ for (let i = 0; i < 5; i++) {
3873
+ if (!API_KEY7) {
3874
+ const mcpPath = path16.join(searchDir, ".mcp.json");
3875
+ if (fs15.existsSync(mcpPath)) {
3876
+ try {
3877
+ const content = fs15.readFileSync(mcpPath, "utf-8");
3878
+ const config = JSON.parse(content);
3879
+ const csEnv = config.mcpServers?.contextstream?.env;
3880
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3881
+ API_KEY7 = csEnv.CONTEXTSTREAM_API_KEY;
3882
+ }
3883
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3884
+ API_URL7 = csEnv.CONTEXTSTREAM_API_URL;
3885
+ }
3886
+ } catch {
3887
+ }
3888
+ }
3889
+ }
3890
+ if (!WORKSPACE_ID5) {
3891
+ const csConfigPath = path16.join(searchDir, ".contextstream", "config.json");
3892
+ if (fs15.existsSync(csConfigPath)) {
3893
+ try {
3894
+ const content = fs15.readFileSync(csConfigPath, "utf-8");
3895
+ const csConfig = JSON.parse(content);
3896
+ if (csConfig.workspace_id) {
3897
+ WORKSPACE_ID5 = csConfig.workspace_id;
3898
+ }
3899
+ } catch {
3900
+ }
3901
+ }
3902
+ }
3903
+ const parentDir = path16.dirname(searchDir);
3904
+ if (parentDir === searchDir) break;
3905
+ searchDir = parentDir;
3906
+ }
3907
+ if (!API_KEY7) {
3908
+ const homeMcpPath = path16.join(homedir13(), ".mcp.json");
3909
+ if (fs15.existsSync(homeMcpPath)) {
3910
+ try {
3911
+ const content = fs15.readFileSync(homeMcpPath, "utf-8");
3912
+ const config = JSON.parse(content);
3913
+ const csEnv = config.mcpServers?.contextstream?.env;
3914
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
3915
+ API_KEY7 = csEnv.CONTEXTSTREAM_API_KEY;
3916
+ }
3917
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
3918
+ API_URL7 = csEnv.CONTEXTSTREAM_API_URL;
3919
+ }
3920
+ } catch {
3921
+ }
3922
+ }
3923
+ }
3924
+ }
3925
+ async function captureExploration(toolName, target, resultSummary, sessionId) {
3926
+ if (!API_KEY7) return;
3927
+ const cacheKey = `${toolName}:${target}`;
3928
+ if (recentCaptures.has(cacheKey)) {
3929
+ return;
3930
+ }
3931
+ recentCaptures.add(cacheKey);
3932
+ setTimeout(() => recentCaptures.delete(cacheKey), CAPTURE_WINDOW_MS);
3933
+ const payload = {
3934
+ event_type: "file_exploration",
3935
+ title: `${toolName}: ${target.slice(0, 50)}`,
3936
+ content: JSON.stringify({
3937
+ tool: toolName,
3938
+ target,
3939
+ result_summary: resultSummary.slice(0, 500),
3940
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3941
+ }),
3942
+ importance: "low",
3943
+ tags: ["exploration", toolName.toLowerCase()],
3944
+ source_type: "hook",
3945
+ session_id: sessionId
3946
+ };
3947
+ if (WORKSPACE_ID5) {
3948
+ payload.workspace_id = WORKSPACE_ID5;
3949
+ }
3950
+ try {
3951
+ const controller = new AbortController();
3952
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
3953
+ await fetch(`${API_URL7}/api/v1/memory/events`, {
3954
+ method: "POST",
3955
+ headers: {
3956
+ "Content-Type": "application/json",
3957
+ "X-API-Key": API_KEY7
3958
+ },
3959
+ body: JSON.stringify(payload),
3960
+ signal: controller.signal
3961
+ });
3962
+ clearTimeout(timeoutId);
3963
+ } catch {
3964
+ }
3965
+ }
3966
+ async function runOnReadHook() {
3967
+ if (!ENABLED10) {
3968
+ process.exit(0);
3969
+ }
3970
+ let inputData = "";
3971
+ for await (const chunk of process.stdin) {
3972
+ inputData += chunk;
3973
+ }
3974
+ if (!inputData.trim()) {
3975
+ process.exit(0);
3976
+ }
3977
+ let input;
3978
+ try {
3979
+ input = JSON.parse(inputData);
3980
+ } catch {
3981
+ process.exit(0);
3982
+ }
3983
+ const toolName = input.tool_name || "";
3984
+ if (!["Read", "Glob", "Grep"].includes(toolName)) {
3985
+ process.exit(0);
3986
+ }
3987
+ const cwd = input.cwd || process.cwd();
3988
+ loadConfigFromMcpJson5(cwd);
3989
+ const sessionId = input.session_id || "unknown";
3990
+ let target = "";
3991
+ let resultSummary = "";
3992
+ switch (toolName) {
3993
+ case "Read":
3994
+ target = input.tool_input?.file_path || "";
3995
+ resultSummary = `Read file: ${target}`;
3996
+ break;
3997
+ case "Glob":
3998
+ target = input.tool_input?.pattern || "";
3999
+ const globFiles = input.tool_result?.files || [];
4000
+ resultSummary = `Found ${globFiles.length} files matching ${target}`;
4001
+ break;
4002
+ case "Grep":
4003
+ target = input.tool_input?.pattern || "";
4004
+ const matches = input.tool_result?.matches || 0;
4005
+ resultSummary = `Found ${matches} matches for "${target}"`;
4006
+ break;
4007
+ }
4008
+ if (target) {
4009
+ captureExploration(toolName, target, resultSummary, sessionId).catch(() => {
4010
+ });
4011
+ }
4012
+ process.exit(0);
4013
+ }
4014
+ var ENABLED10, API_URL7, API_KEY7, WORKSPACE_ID5, recentCaptures, CAPTURE_WINDOW_MS, isDirectRun10;
4015
+ var init_on_read = __esm({
4016
+ "src/hooks/on-read.ts"() {
4017
+ "use strict";
4018
+ ENABLED10 = process.env.CONTEXTSTREAM_READ_HOOK_ENABLED !== "false";
4019
+ API_URL7 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4020
+ API_KEY7 = process.env.CONTEXTSTREAM_API_KEY || "";
4021
+ WORKSPACE_ID5 = null;
4022
+ recentCaptures = /* @__PURE__ */ new Set();
4023
+ CAPTURE_WINDOW_MS = 6e4;
4024
+ isDirectRun10 = process.argv[1]?.includes("on-read") || process.argv[2] === "on-read";
4025
+ if (isDirectRun10) {
4026
+ runOnReadHook().catch(() => process.exit(0));
4027
+ }
4028
+ }
4029
+ });
4030
+
4031
+ // src/hooks/on-web.ts
4032
+ var on_web_exports = {};
4033
+ __export(on_web_exports, {
4034
+ runOnWebHook: () => runOnWebHook
4035
+ });
4036
+ import * as fs16 from "node:fs";
4037
+ import * as path17 from "node:path";
4038
+ import { homedir as homedir14 } from "node:os";
4039
+ function loadConfigFromMcpJson6(cwd) {
4040
+ let searchDir = path17.resolve(cwd);
4041
+ for (let i = 0; i < 5; i++) {
4042
+ if (!API_KEY8) {
4043
+ const mcpPath = path17.join(searchDir, ".mcp.json");
4044
+ if (fs16.existsSync(mcpPath)) {
4045
+ try {
4046
+ const content = fs16.readFileSync(mcpPath, "utf-8");
4047
+ const config = JSON.parse(content);
4048
+ const csEnv = config.mcpServers?.contextstream?.env;
4049
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4050
+ API_KEY8 = csEnv.CONTEXTSTREAM_API_KEY;
4051
+ }
4052
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4053
+ API_URL8 = csEnv.CONTEXTSTREAM_API_URL;
4054
+ }
4055
+ } catch {
4056
+ }
4057
+ }
4058
+ }
4059
+ if (!WORKSPACE_ID6) {
4060
+ const csConfigPath = path17.join(searchDir, ".contextstream", "config.json");
4061
+ if (fs16.existsSync(csConfigPath)) {
4062
+ try {
4063
+ const content = fs16.readFileSync(csConfigPath, "utf-8");
4064
+ const csConfig = JSON.parse(content);
4065
+ if (csConfig.workspace_id) {
4066
+ WORKSPACE_ID6 = csConfig.workspace_id;
4067
+ }
4068
+ } catch {
4069
+ }
4070
+ }
4071
+ }
4072
+ const parentDir = path17.dirname(searchDir);
4073
+ if (parentDir === searchDir) break;
4074
+ searchDir = parentDir;
4075
+ }
4076
+ if (!API_KEY8) {
4077
+ const homeMcpPath = path17.join(homedir14(), ".mcp.json");
4078
+ if (fs16.existsSync(homeMcpPath)) {
4079
+ try {
4080
+ const content = fs16.readFileSync(homeMcpPath, "utf-8");
4081
+ const config = JSON.parse(content);
4082
+ const csEnv = config.mcpServers?.contextstream?.env;
4083
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4084
+ API_KEY8 = csEnv.CONTEXTSTREAM_API_KEY;
4085
+ }
4086
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4087
+ API_URL8 = csEnv.CONTEXTSTREAM_API_URL;
4088
+ }
4089
+ } catch {
4090
+ }
4091
+ }
4092
+ }
4093
+ }
4094
+ async function captureWebResearch(toolName, target, summary, sessionId) {
4095
+ if (!API_KEY8) return;
4096
+ const payload = {
4097
+ event_type: "web_research",
4098
+ title: `${toolName}: ${target.slice(0, 60)}`,
4099
+ content: JSON.stringify({
4100
+ tool: toolName,
4101
+ target,
4102
+ summary: summary.slice(0, 1e3),
4103
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4104
+ }),
4105
+ importance: "medium",
4106
+ tags: ["research", "web", toolName.toLowerCase()],
4107
+ source_type: "hook",
4108
+ session_id: sessionId
4109
+ };
4110
+ if (WORKSPACE_ID6) {
4111
+ payload.workspace_id = WORKSPACE_ID6;
4112
+ }
4113
+ try {
4114
+ const controller = new AbortController();
4115
+ const timeoutId = setTimeout(() => controller.abort(), 3e3);
4116
+ await fetch(`${API_URL8}/api/v1/memory/events`, {
4117
+ method: "POST",
4118
+ headers: {
4119
+ "Content-Type": "application/json",
4120
+ "X-API-Key": API_KEY8
4121
+ },
4122
+ body: JSON.stringify(payload),
4123
+ signal: controller.signal
4124
+ });
4125
+ clearTimeout(timeoutId);
4126
+ } catch {
4127
+ }
4128
+ }
4129
+ async function runOnWebHook() {
4130
+ if (!ENABLED11) {
4131
+ process.exit(0);
4132
+ }
4133
+ let inputData = "";
4134
+ for await (const chunk of process.stdin) {
4135
+ inputData += chunk;
4136
+ }
4137
+ if (!inputData.trim()) {
4138
+ process.exit(0);
4139
+ }
4140
+ let input;
4141
+ try {
4142
+ input = JSON.parse(inputData);
4143
+ } catch {
4144
+ process.exit(0);
4145
+ }
4146
+ const toolName = input.tool_name || "";
4147
+ if (!["WebFetch", "WebSearch"].includes(toolName)) {
4148
+ process.exit(0);
4149
+ }
4150
+ const cwd = input.cwd || process.cwd();
4151
+ loadConfigFromMcpJson6(cwd);
4152
+ const sessionId = input.session_id || "unknown";
4153
+ let target = "";
4154
+ let summary = "";
4155
+ switch (toolName) {
4156
+ case "WebFetch":
4157
+ target = input.tool_input?.url || "";
4158
+ const prompt = input.tool_input?.prompt || "fetched content";
4159
+ const content = input.tool_result?.output || input.tool_result?.content || "";
4160
+ summary = `Fetched ${target} (${prompt}): ${content.slice(0, 300)}`;
4161
+ break;
4162
+ case "WebSearch":
4163
+ target = input.tool_input?.query || "";
4164
+ const results = input.tool_result?.results || [];
4165
+ const topResults = results.slice(0, 3).map((r) => `- ${r.title}: ${r.url}`).join("\n");
4166
+ summary = `Search: "${target}"
4167
+ Top results:
4168
+ ${topResults}`;
4169
+ break;
4170
+ }
4171
+ if (target) {
4172
+ captureWebResearch(toolName, target, summary, sessionId).catch(() => {
4173
+ });
4174
+ }
4175
+ process.exit(0);
4176
+ }
4177
+ var ENABLED11, API_URL8, API_KEY8, WORKSPACE_ID6, isDirectRun11;
4178
+ var init_on_web = __esm({
4179
+ "src/hooks/on-web.ts"() {
4180
+ "use strict";
4181
+ ENABLED11 = process.env.CONTEXTSTREAM_WEB_HOOK_ENABLED !== "false";
4182
+ API_URL8 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4183
+ API_KEY8 = process.env.CONTEXTSTREAM_API_KEY || "";
4184
+ WORKSPACE_ID6 = null;
4185
+ isDirectRun11 = process.argv[1]?.includes("on-web") || process.argv[2] === "on-web";
4186
+ if (isDirectRun11) {
4187
+ runOnWebHook().catch(() => process.exit(0));
4188
+ }
4189
+ }
4190
+ });
4191
+
4192
+ // src/hooks/session-init.ts
4193
+ var session_init_exports = {};
4194
+ __export(session_init_exports, {
4195
+ runSessionInitHook: () => runSessionInitHook
4196
+ });
4197
+ import * as fs17 from "node:fs";
4198
+ import * as path18 from "node:path";
4199
+ import { homedir as homedir15 } from "node:os";
4200
+ function loadConfigFromMcpJson7(cwd) {
4201
+ let searchDir = path18.resolve(cwd);
4202
+ for (let i = 0; i < 5; i++) {
4203
+ if (!API_KEY9) {
4204
+ const mcpPath = path18.join(searchDir, ".mcp.json");
4205
+ if (fs17.existsSync(mcpPath)) {
4206
+ try {
4207
+ const content = fs17.readFileSync(mcpPath, "utf-8");
4208
+ const config = JSON.parse(content);
4209
+ const csEnv = config.mcpServers?.contextstream?.env;
4210
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4211
+ API_KEY9 = csEnv.CONTEXTSTREAM_API_KEY;
4212
+ }
4213
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4214
+ API_URL9 = csEnv.CONTEXTSTREAM_API_URL;
4215
+ }
4216
+ if (csEnv?.CONTEXTSTREAM_WORKSPACE_ID) {
4217
+ WORKSPACE_ID7 = csEnv.CONTEXTSTREAM_WORKSPACE_ID;
4218
+ }
4219
+ } catch {
4220
+ }
4221
+ }
4222
+ }
4223
+ if (!WORKSPACE_ID7 || !PROJECT_ID) {
4224
+ const csConfigPath = path18.join(searchDir, ".contextstream", "config.json");
4225
+ if (fs17.existsSync(csConfigPath)) {
4226
+ try {
4227
+ const content = fs17.readFileSync(csConfigPath, "utf-8");
4228
+ const csConfig = JSON.parse(content);
4229
+ if (csConfig.workspace_id && !WORKSPACE_ID7) {
4230
+ WORKSPACE_ID7 = csConfig.workspace_id;
4231
+ }
4232
+ if (csConfig.project_id && !PROJECT_ID) {
4233
+ PROJECT_ID = csConfig.project_id;
4234
+ }
4235
+ } catch {
4236
+ }
4237
+ }
4238
+ }
4239
+ const parentDir = path18.dirname(searchDir);
4240
+ if (parentDir === searchDir) break;
4241
+ searchDir = parentDir;
4242
+ }
4243
+ if (!API_KEY9) {
4244
+ const homeMcpPath = path18.join(homedir15(), ".mcp.json");
4245
+ if (fs17.existsSync(homeMcpPath)) {
4246
+ try {
4247
+ const content = fs17.readFileSync(homeMcpPath, "utf-8");
4248
+ const config = JSON.parse(content);
4249
+ const csEnv = config.mcpServers?.contextstream?.env;
4250
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4251
+ API_KEY9 = csEnv.CONTEXTSTREAM_API_KEY;
4252
+ }
4253
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4254
+ API_URL9 = csEnv.CONTEXTSTREAM_API_URL;
4255
+ }
4256
+ } catch {
4257
+ }
4258
+ }
4259
+ }
4260
+ }
4261
+ async function fetchSessionContext() {
4262
+ if (!API_KEY9) return null;
4263
+ try {
4264
+ const controller = new AbortController();
4265
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
4266
+ const url = new URL(`${API_URL9}/api/v1/context`);
4267
+ if (WORKSPACE_ID7) url.searchParams.set("workspace_id", WORKSPACE_ID7);
4268
+ if (PROJECT_ID) url.searchParams.set("project_id", PROJECT_ID);
4269
+ url.searchParams.set("include_rules", "true");
4270
+ url.searchParams.set("include_lessons", "true");
4271
+ url.searchParams.set("include_decisions", "true");
4272
+ url.searchParams.set("include_plans", "true");
4273
+ url.searchParams.set("limit", "5");
4274
+ const response = await fetch(url.toString(), {
4275
+ method: "GET",
4276
+ headers: {
4277
+ "X-API-Key": API_KEY9
4278
+ },
4279
+ signal: controller.signal
4280
+ });
4281
+ clearTimeout(timeoutId);
4282
+ if (response.ok) {
4283
+ return await response.json();
4284
+ }
4285
+ return null;
4286
+ } catch {
4287
+ return null;
4288
+ }
4289
+ }
4290
+ function formatContext(ctx) {
4291
+ if (!ctx) {
4292
+ return `[ContextStream Session Start]
4293
+
4294
+ No stored context found. Call \`mcp__contextstream__context(user_message="starting new session")\` to initialize.`;
4295
+ }
4296
+ const parts = ["[ContextStream Session Start]"];
4297
+ if (ctx.lessons && ctx.lessons.length > 0) {
4298
+ parts.push("\n## \u26A0\uFE0F Lessons from Past Mistakes");
4299
+ for (const lesson of ctx.lessons.slice(0, 3)) {
4300
+ parts.push(`- **${lesson.title}**: ${lesson.prevention}`);
4301
+ }
4302
+ }
4303
+ if (ctx.active_plans && ctx.active_plans.length > 0) {
4304
+ parts.push("\n## \u{1F4CB} Active Plans");
4305
+ for (const plan of ctx.active_plans.slice(0, 3)) {
4306
+ parts.push(`- ${plan.title} (${plan.status})`);
4307
+ }
4308
+ }
4309
+ if (ctx.pending_tasks && ctx.pending_tasks.length > 0) {
4310
+ parts.push("\n## \u2705 Pending Tasks");
4311
+ for (const task of ctx.pending_tasks.slice(0, 5)) {
4312
+ parts.push(`- ${task.title}`);
4313
+ }
4314
+ }
4315
+ if (ctx.recent_decisions && ctx.recent_decisions.length > 0) {
4316
+ parts.push("\n## \u{1F4DD} Recent Decisions");
4317
+ for (const decision of ctx.recent_decisions.slice(0, 3)) {
4318
+ parts.push(`- **${decision.title}**`);
4319
+ }
4320
+ }
4321
+ parts.push("\n---");
4322
+ parts.push('Call `mcp__contextstream__context(user_message="...")` for task-specific context.');
4323
+ return parts.join("\n");
4324
+ }
4325
+ async function runSessionInitHook() {
4326
+ if (!ENABLED12) {
4327
+ process.exit(0);
4328
+ }
4329
+ let inputData = "";
4330
+ for await (const chunk of process.stdin) {
4331
+ inputData += chunk;
4332
+ }
4333
+ if (!inputData.trim()) {
4334
+ process.exit(0);
4335
+ }
4336
+ let input;
4337
+ try {
4338
+ input = JSON.parse(inputData);
4339
+ } catch {
4340
+ process.exit(0);
4341
+ }
4342
+ const cwd = input.cwd || process.cwd();
4343
+ loadConfigFromMcpJson7(cwd);
4344
+ const context = await fetchSessionContext();
4345
+ const formattedContext = formatContext(context);
4346
+ console.log(
4347
+ JSON.stringify({
4348
+ hookSpecificOutput: {
4349
+ hookEventName: "SessionStart",
4350
+ additionalContext: formattedContext
4351
+ }
4352
+ })
4353
+ );
4354
+ process.exit(0);
4355
+ }
4356
+ var ENABLED12, API_URL9, API_KEY9, WORKSPACE_ID7, PROJECT_ID, isDirectRun12;
4357
+ var init_session_init = __esm({
4358
+ "src/hooks/session-init.ts"() {
4359
+ "use strict";
4360
+ ENABLED12 = process.env.CONTEXTSTREAM_SESSION_INIT_ENABLED !== "false";
4361
+ API_URL9 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4362
+ API_KEY9 = process.env.CONTEXTSTREAM_API_KEY || "";
4363
+ WORKSPACE_ID7 = null;
4364
+ PROJECT_ID = null;
4365
+ isDirectRun12 = process.argv[1]?.includes("session-init") || process.argv[2] === "session-init";
4366
+ if (isDirectRun12) {
4367
+ runSessionInitHook().catch(() => process.exit(0));
4368
+ }
4369
+ }
4370
+ });
4371
+
4372
+ // src/hooks/session-end.ts
4373
+ var session_end_exports = {};
4374
+ __export(session_end_exports, {
4375
+ runSessionEndHook: () => runSessionEndHook
4376
+ });
4377
+ import * as fs18 from "node:fs";
4378
+ import * as path19 from "node:path";
4379
+ import { homedir as homedir16 } from "node:os";
4380
+ function loadConfigFromMcpJson8(cwd) {
4381
+ let searchDir = path19.resolve(cwd);
4382
+ for (let i = 0; i < 5; i++) {
4383
+ if (!API_KEY10) {
4384
+ const mcpPath = path19.join(searchDir, ".mcp.json");
4385
+ if (fs18.existsSync(mcpPath)) {
4386
+ try {
4387
+ const content = fs18.readFileSync(mcpPath, "utf-8");
4388
+ const config = JSON.parse(content);
4389
+ const csEnv = config.mcpServers?.contextstream?.env;
4390
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4391
+ API_KEY10 = csEnv.CONTEXTSTREAM_API_KEY;
4392
+ }
4393
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4394
+ API_URL10 = csEnv.CONTEXTSTREAM_API_URL;
4395
+ }
4396
+ } catch {
4397
+ }
4398
+ }
4399
+ }
4400
+ if (!WORKSPACE_ID8) {
4401
+ const csConfigPath = path19.join(searchDir, ".contextstream", "config.json");
4402
+ if (fs18.existsSync(csConfigPath)) {
4403
+ try {
4404
+ const content = fs18.readFileSync(csConfigPath, "utf-8");
4405
+ const csConfig = JSON.parse(content);
4406
+ if (csConfig.workspace_id) {
4407
+ WORKSPACE_ID8 = csConfig.workspace_id;
4408
+ }
4409
+ } catch {
4410
+ }
4411
+ }
4412
+ }
4413
+ const parentDir = path19.dirname(searchDir);
4414
+ if (parentDir === searchDir) break;
4415
+ searchDir = parentDir;
4416
+ }
4417
+ if (!API_KEY10) {
4418
+ const homeMcpPath = path19.join(homedir16(), ".mcp.json");
4419
+ if (fs18.existsSync(homeMcpPath)) {
4420
+ try {
4421
+ const content = fs18.readFileSync(homeMcpPath, "utf-8");
4422
+ const config = JSON.parse(content);
4423
+ const csEnv = config.mcpServers?.contextstream?.env;
4424
+ if (csEnv?.CONTEXTSTREAM_API_KEY) {
4425
+ API_KEY10 = csEnv.CONTEXTSTREAM_API_KEY;
4426
+ }
4427
+ if (csEnv?.CONTEXTSTREAM_API_URL) {
4428
+ API_URL10 = csEnv.CONTEXTSTREAM_API_URL;
4429
+ }
4430
+ } catch {
4431
+ }
4432
+ }
4433
+ }
4434
+ }
4435
+ function parseTranscriptStats(transcriptPath) {
4436
+ const stats = {
4437
+ messageCount: 0,
4438
+ toolCallCount: 0,
4439
+ duration: 0,
4440
+ filesModified: []
4441
+ };
4442
+ if (!transcriptPath || !fs18.existsSync(transcriptPath)) {
4443
+ return stats;
4444
+ }
4445
+ try {
4446
+ const content = fs18.readFileSync(transcriptPath, "utf-8");
4447
+ const lines = content.split("\n");
4448
+ let firstTimestamp = null;
4449
+ let lastTimestamp = null;
4450
+ const modifiedFiles = /* @__PURE__ */ new Set();
4451
+ for (const line of lines) {
4452
+ if (!line.trim()) continue;
4453
+ try {
4454
+ const entry = JSON.parse(line);
4455
+ if (entry.type === "user" || entry.type === "assistant") {
4456
+ stats.messageCount++;
4457
+ } else if (entry.type === "tool_use") {
4458
+ stats.toolCallCount++;
4459
+ if (["Write", "Edit", "NotebookEdit"].includes(entry.name || "")) {
4460
+ const filePath = entry.input?.file_path;
4461
+ if (filePath) {
4462
+ modifiedFiles.add(filePath);
4463
+ }
4464
+ }
4465
+ }
4466
+ if (entry.timestamp) {
4467
+ const ts = new Date(entry.timestamp);
4468
+ if (!firstTimestamp || ts < firstTimestamp) {
4469
+ firstTimestamp = ts;
4470
+ }
4471
+ if (!lastTimestamp || ts > lastTimestamp) {
4472
+ lastTimestamp = ts;
4473
+ }
4474
+ }
4475
+ } catch {
4476
+ continue;
4477
+ }
4478
+ }
4479
+ if (firstTimestamp && lastTimestamp) {
4480
+ stats.duration = Math.round((lastTimestamp.getTime() - firstTimestamp.getTime()) / 1e3);
4481
+ }
4482
+ stats.filesModified = Array.from(modifiedFiles);
4483
+ } catch {
4484
+ }
4485
+ return stats;
4486
+ }
4487
+ async function finalizeSession(sessionId, stats, reason) {
4488
+ if (!API_KEY10) return;
4489
+ const payload = {
4490
+ event_type: "session_end",
4491
+ title: `Session Ended: ${reason}`,
4492
+ content: JSON.stringify({
4493
+ session_id: sessionId,
4494
+ reason,
4495
+ stats: {
4496
+ messages: stats.messageCount,
4497
+ tool_calls: stats.toolCallCount,
4498
+ duration_seconds: stats.duration,
4499
+ files_modified: stats.filesModified.length
4500
+ },
4501
+ files_modified: stats.filesModified.slice(0, 20),
4502
+ ended_at: (/* @__PURE__ */ new Date()).toISOString()
4503
+ }),
4504
+ importance: "low",
4505
+ tags: ["session", "end", reason],
4506
+ source_type: "hook",
4507
+ session_id: sessionId
4508
+ };
4509
+ if (WORKSPACE_ID8) {
4510
+ payload.workspace_id = WORKSPACE_ID8;
4511
+ }
4512
+ try {
4513
+ const controller = new AbortController();
4514
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
4515
+ await fetch(`${API_URL10}/api/v1/memory/events`, {
4516
+ method: "POST",
4517
+ headers: {
4518
+ "Content-Type": "application/json",
4519
+ "X-API-Key": API_KEY10
4520
+ },
4521
+ body: JSON.stringify(payload),
4522
+ signal: controller.signal
4523
+ });
4524
+ clearTimeout(timeoutId);
4525
+ } catch {
4526
+ }
4527
+ }
4528
+ async function runSessionEndHook() {
4529
+ if (!ENABLED13) {
4530
+ process.exit(0);
4531
+ }
4532
+ let inputData = "";
4533
+ for await (const chunk of process.stdin) {
4534
+ inputData += chunk;
4535
+ }
4536
+ if (!inputData.trim()) {
4537
+ process.exit(0);
4538
+ }
4539
+ let input;
4540
+ try {
4541
+ input = JSON.parse(inputData);
4542
+ } catch {
4543
+ process.exit(0);
4544
+ }
4545
+ const cwd = input.cwd || process.cwd();
4546
+ loadConfigFromMcpJson8(cwd);
4547
+ const sessionId = input.session_id || "unknown";
4548
+ const transcriptPath = input.transcript_path || "";
4549
+ const reason = input.reason || "user_exit";
4550
+ const stats = parseTranscriptStats(transcriptPath);
4551
+ await finalizeSession(sessionId, stats, reason);
4552
+ process.exit(0);
4553
+ }
4554
+ var ENABLED13, API_URL10, API_KEY10, WORKSPACE_ID8, isDirectRun13;
4555
+ var init_session_end = __esm({
4556
+ "src/hooks/session-end.ts"() {
4557
+ "use strict";
4558
+ ENABLED13 = process.env.CONTEXTSTREAM_SESSION_END_ENABLED !== "false";
4559
+ API_URL10 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
4560
+ API_KEY10 = process.env.CONTEXTSTREAM_API_KEY || "";
4561
+ WORKSPACE_ID8 = null;
4562
+ isDirectRun13 = process.argv[1]?.includes("session-end") || process.argv[2] === "session-end";
4563
+ if (isDirectRun13) {
4564
+ runSessionEndHook().catch(() => process.exit(0));
4565
+ }
4566
+ }
4567
+ });
4568
+
4569
+ // src/hooks/on-save-intent.ts
4570
+ var on_save_intent_exports = {};
4571
+ __export(on_save_intent_exports, {
4572
+ runOnSaveIntentHook: () => runOnSaveIntentHook
4573
+ });
4574
+ function detectsSaveIntent(text) {
4575
+ const hasSaveIntent = SAVE_PATTERNS.some((p) => p.test(text));
4576
+ const isLocalFile = LOCAL_FILE_PATTERNS.some((p) => p.test(text));
4577
+ return { hasSaveIntent, isLocalFile };
4578
+ }
4579
+ async function runOnSaveIntentHook() {
4580
+ if (!ENABLED14) {
4581
+ process.exit(0);
4582
+ }
4583
+ let inputData = "";
4584
+ for await (const chunk of process.stdin) {
4585
+ inputData += chunk;
4586
+ }
4587
+ if (!inputData.trim()) {
4588
+ process.exit(0);
4589
+ }
4590
+ let input;
4591
+ try {
4592
+ input = JSON.parse(inputData);
4593
+ } catch {
4594
+ process.exit(0);
4595
+ }
4596
+ let prompt = input.prompt || "";
4597
+ if (!prompt && input.session?.messages) {
4598
+ for (const msg of [...input.session.messages].reverse()) {
4599
+ if (msg.role === "user") {
4600
+ if (typeof msg.content === "string") {
4601
+ prompt = msg.content;
4602
+ } else if (Array.isArray(msg.content)) {
4603
+ for (const block of msg.content) {
4604
+ if (block.type === "text" && block.text) {
4605
+ prompt = block.text;
4606
+ break;
4607
+ }
4608
+ }
4609
+ }
4610
+ break;
4611
+ }
4612
+ }
4613
+ }
4614
+ if (!prompt) {
4615
+ process.exit(0);
4616
+ }
4617
+ const { hasSaveIntent, isLocalFile } = detectsSaveIntent(prompt);
4618
+ if (hasSaveIntent || isLocalFile) {
4619
+ console.log(
4620
+ JSON.stringify({
4621
+ hookSpecificOutput: {
4622
+ hookEventName: "UserPromptSubmit",
4623
+ additionalContext: SAVE_GUIDANCE
4624
+ }
4625
+ })
4626
+ );
4627
+ }
4628
+ process.exit(0);
4629
+ }
4630
+ var ENABLED14, SAVE_PATTERNS, LOCAL_FILE_PATTERNS, SAVE_GUIDANCE, isDirectRun14;
4631
+ var init_on_save_intent = __esm({
4632
+ "src/hooks/on-save-intent.ts"() {
4633
+ "use strict";
4634
+ ENABLED14 = process.env.CONTEXTSTREAM_SAVE_INTENT_ENABLED !== "false";
4635
+ SAVE_PATTERNS = [
4636
+ // Direct save requests
4637
+ /\b(save|store|record|capture|log|document|write down|note down|keep track)\b.*\b(this|that|it|the)\b/i,
4638
+ /\b(save|store|record|capture|log)\b.*\b(to|in|for)\b.*\b(contextstream|memory|later|reference|future)\b/i,
4639
+ // Document creation
4640
+ /\b(create|make|write|draft)\b.*\b(a|the)\b.*\b(document|doc|note|summary|report|spec|design)\b/i,
4641
+ /\b(document|summarize|write up)\b.*\b(this|that|the|our)\b.*\b(decision|discussion|conversation|meeting|finding)\b/i,
4642
+ // Memory/reference requests
4643
+ /\b(remember|don't forget|keep in mind|note that|important to remember)\b/i,
4644
+ /\bfor\s+(future|later)\s+reference\b/i,
4645
+ /\b(add|put)\s+(this|it|that)\s+(to|in)\s+(memory|notes|docs)\b/i,
4646
+ // Decision tracking
4647
+ /\b(we\s+)?(decided|agreed|concluded|determined)\b.*\b(to|that)\b/i,
4648
+ /\blet('s|s)\s+document\b/i,
4649
+ /\bsave\s+(this|the)\s+(decision|choice|approach)\b/i,
4650
+ // Implementation/design docs
4651
+ /\b(implementation|design|architecture|spec)\s+(doc|document|plan)\b/i,
4652
+ /\bwrite\s+(the|a|an)\s+.*(md|markdown|readme)\b/i
4653
+ ];
4654
+ LOCAL_FILE_PATTERNS = [
4655
+ /\b(save|write|create)\s+(it|this|the\s+\w+)\s+(to|in|as)\s+[./~]/i,
4656
+ /\b(save|write)\s+to\s+.*(\.md|\.txt|\.json|docs\/|notes\/)/i,
4657
+ /\bcreate\s+(a|the)\s+file\b/i
4658
+ ];
4659
+ SAVE_GUIDANCE = `[CONTEXTSTREAM DOCUMENT STORAGE]
4660
+ The user wants to save/store content. Use ContextStream instead of local files:
4661
+
4662
+ **For decisions/notes:**
4663
+ \`\`\`
4664
+ mcp__contextstream__session(
4665
+ action="capture",
4666
+ event_type="decision|note|insight",
4667
+ title="...",
4668
+ content="...",
4669
+ importance="high|medium|low"
4670
+ )
4671
+ \`\`\`
4672
+
4673
+ **For documents/specs:**
4674
+ \`\`\`
4675
+ mcp__contextstream__docs(
4676
+ action="create",
4677
+ title="...",
4678
+ content="...",
4679
+ doc_type="implementation|design|spec|guide"
4680
+ )
4681
+ \`\`\`
4682
+
4683
+ **For plans:**
4684
+ \`\`\`
4685
+ mcp__contextstream__session(
4686
+ action="capture_plan",
4687
+ title="...",
4688
+ steps=[...]
4689
+ )
4690
+ \`\`\`
4691
+
4692
+ **Why ContextStream?**
4693
+ - Persists across sessions (local files don't)
4694
+ - Searchable and retrievable
4695
+ - Shows up in context automatically
4696
+ - Can be shared with team
4697
+
4698
+ Only save to local files if user explicitly requests a specific file path.
4699
+ [END GUIDANCE]`;
4700
+ isDirectRun14 = process.argv[1]?.includes("on-save-intent") || process.argv[2] === "on-save-intent";
4701
+ if (isDirectRun14) {
4702
+ runOnSaveIntentHook().catch(() => process.exit(0));
4703
+ }
4704
+ }
4705
+ });
4706
+
4707
+ // src/verify-key.ts
4708
+ var verify_key_exports = {};
4709
+ __export(verify_key_exports, {
4710
+ loadApiKey: () => loadApiKey,
4711
+ maskApiKey: () => maskApiKey2,
4712
+ runVerifyKey: () => runVerifyKey,
4713
+ validateApiKey: () => validateApiKey
4714
+ });
4715
+ import * as fs19 from "node:fs";
4716
+ import * as path20 from "node:path";
4717
+ import { homedir as homedir17 } from "node:os";
4718
+ function maskApiKey2(key) {
4719
+ if (!key || key.length < 10) return "***";
4720
+ const prefix = key.slice(0, 6);
4721
+ const suffix = key.slice(-4);
4722
+ return `${prefix}...${suffix}`;
4723
+ }
4724
+ function extractFromMcpConfig(config) {
4725
+ if (!config.mcpServers) return {};
4726
+ const priorityNames = ["contextstream", "ContextStream", "context-stream"];
4727
+ for (const name of priorityNames) {
4728
+ const server = config.mcpServers[name];
4729
+ if (server?.env?.CONTEXTSTREAM_API_KEY) {
4730
+ return {
4731
+ apiKey: server.env.CONTEXTSTREAM_API_KEY,
4732
+ apiUrl: server.env.CONTEXTSTREAM_API_URL
4733
+ };
4734
+ }
4735
+ }
4736
+ for (const [, server] of Object.entries(config.mcpServers)) {
4737
+ if (server?.env?.CONTEXTSTREAM_API_KEY) {
4738
+ return {
4739
+ apiKey: server.env.CONTEXTSTREAM_API_KEY,
4740
+ apiUrl: server.env.CONTEXTSTREAM_API_URL
4741
+ };
4742
+ }
4743
+ }
4744
+ return {};
4745
+ }
4746
+ function getClaudeDesktopConfigPath() {
4747
+ const platform = process.platform;
4748
+ if (platform === "darwin") {
4749
+ return path20.join(homedir17(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
4750
+ } else if (platform === "win32") {
4751
+ return path20.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
4752
+ } else {
4753
+ return path20.join(homedir17(), ".config", "Claude", "claude_desktop_config.json");
4754
+ }
4755
+ }
4756
+ function loadApiKey() {
4757
+ let apiKey = null;
4758
+ let apiUrl = "https://api.contextstream.io";
4759
+ let source = "none";
4760
+ if (process.env.CONTEXTSTREAM_API_KEY) {
4761
+ apiKey = process.env.CONTEXTSTREAM_API_KEY;
4762
+ source = "environment";
4763
+ if (process.env.CONTEXTSTREAM_API_URL) {
4764
+ apiUrl = process.env.CONTEXTSTREAM_API_URL;
4765
+ }
4766
+ return { apiKey, apiUrl, source };
4767
+ }
4768
+ let searchDir = process.cwd();
4769
+ for (let i = 0; i < 5; i++) {
4770
+ const projectMcpPath = path20.join(searchDir, ".mcp.json");
4771
+ if (fs19.existsSync(projectMcpPath)) {
4772
+ try {
4773
+ const content = fs19.readFileSync(projectMcpPath, "utf-8");
4774
+ const config = JSON.parse(content);
4775
+ const extracted = extractFromMcpConfig(config);
4776
+ if (extracted.apiKey) {
4777
+ apiKey = extracted.apiKey;
4778
+ source = `${projectMcpPath}`;
4779
+ if (extracted.apiUrl) {
4780
+ apiUrl = extracted.apiUrl;
4781
+ }
4782
+ return { apiKey, apiUrl, source };
4783
+ }
4784
+ } catch {
4785
+ }
4786
+ }
4787
+ const parentDir = path20.dirname(searchDir);
4788
+ if (parentDir === searchDir) break;
4789
+ searchDir = parentDir;
4790
+ }
4791
+ const globalMcpPath = path20.join(homedir17(), ".mcp.json");
4792
+ if (fs19.existsSync(globalMcpPath)) {
4793
+ try {
4794
+ const content = fs19.readFileSync(globalMcpPath, "utf-8");
4795
+ const config = JSON.parse(content);
4796
+ const extracted = extractFromMcpConfig(config);
4797
+ if (extracted.apiKey) {
4798
+ apiKey = extracted.apiKey;
4799
+ source = "~/.mcp.json";
4800
+ if (extracted.apiUrl) {
4801
+ apiUrl = extracted.apiUrl;
4802
+ }
4803
+ return { apiKey, apiUrl, source };
4804
+ }
4805
+ } catch {
4806
+ }
4807
+ }
4808
+ const cursorPaths = [
4809
+ path20.join(process.cwd(), ".cursor", "mcp.json"),
4810
+ path20.join(homedir17(), ".cursor", "mcp.json")
4811
+ ];
4812
+ for (const cursorPath of cursorPaths) {
4813
+ if (fs19.existsSync(cursorPath)) {
4814
+ try {
4815
+ const content = fs19.readFileSync(cursorPath, "utf-8");
4816
+ const config = JSON.parse(content);
4817
+ const extracted = extractFromMcpConfig(config);
4818
+ if (extracted.apiKey) {
4819
+ apiKey = extracted.apiKey;
4820
+ source = cursorPath;
4821
+ if (extracted.apiUrl) {
4822
+ apiUrl = extracted.apiUrl;
4823
+ }
4824
+ return { apiKey, apiUrl, source };
4825
+ }
4826
+ } catch {
4827
+ }
4828
+ }
4829
+ }
4830
+ const claudeDesktopPath = getClaudeDesktopConfigPath();
4831
+ if (fs19.existsSync(claudeDesktopPath)) {
4832
+ try {
4833
+ const content = fs19.readFileSync(claudeDesktopPath, "utf-8");
4834
+ const config = JSON.parse(content);
4835
+ const extracted = extractFromMcpConfig(config);
4836
+ if (extracted.apiKey) {
4837
+ apiKey = extracted.apiKey;
4838
+ source = claudeDesktopPath;
4839
+ if (extracted.apiUrl) {
4840
+ apiUrl = extracted.apiUrl;
4841
+ }
4842
+ return { apiKey, apiUrl, source };
4843
+ }
4844
+ } catch {
4845
+ }
4846
+ }
4847
+ const vscodePaths = [
4848
+ path20.join(homedir17(), ".vscode", "mcp.json"),
4849
+ path20.join(homedir17(), ".codeium", "windsurf", "mcp_config.json"),
4850
+ path20.join(homedir17(), ".continue", "config.json")
4851
+ ];
4852
+ for (const vsPath of vscodePaths) {
4853
+ if (fs19.existsSync(vsPath)) {
4854
+ try {
4855
+ const content = fs19.readFileSync(vsPath, "utf-8");
4856
+ const config = JSON.parse(content);
4857
+ const extracted = extractFromMcpConfig(config);
4858
+ if (extracted.apiKey) {
4859
+ apiKey = extracted.apiKey;
4860
+ source = vsPath;
4861
+ if (extracted.apiUrl) {
4862
+ apiUrl = extracted.apiUrl;
4863
+ }
4864
+ return { apiKey, apiUrl, source };
4865
+ }
4866
+ } catch {
4867
+ }
4868
+ }
4869
+ }
4870
+ const credentialsPath = path20.join(homedir17(), ".contextstream", "credentials.json");
4871
+ if (fs19.existsSync(credentialsPath)) {
4872
+ try {
4873
+ const content = fs19.readFileSync(credentialsPath, "utf-8");
4874
+ const creds = JSON.parse(content);
4875
+ if (creds.api_key) {
4876
+ apiKey = creds.api_key;
4877
+ source = "~/.contextstream/credentials.json";
4878
+ if (creds.api_url) {
4879
+ apiUrl = creds.api_url;
4880
+ }
4881
+ return { apiKey, apiUrl, source };
4882
+ }
4883
+ } catch {
4884
+ }
4885
+ }
4886
+ return { apiKey, apiUrl, source };
4887
+ }
4888
+ async function validateApiKey(apiKey, apiUrl) {
3107
4889
  try {
3108
- input = JSON.parse(inputData);
3109
- } catch {
3110
- process.exit(0);
3111
- }
3112
- const toolName = input.tool_name || input.toolName || "";
3113
- const isContextTool = toolName.includes("init") || toolName.includes("context") || toolName.includes("session_init") || toolName.includes("context_smart");
3114
- if (!isContextTool) {
3115
- process.exit(0);
4890
+ const controller = new AbortController();
4891
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
4892
+ const response = await fetch(`${apiUrl}/api/v1/auth/me`, {
4893
+ method: "GET",
4894
+ headers: {
4895
+ "X-API-Key": apiKey
4896
+ },
4897
+ signal: controller.signal
4898
+ });
4899
+ clearTimeout(timeoutId);
4900
+ if (response.ok) {
4901
+ const data = await response.json();
4902
+ return {
4903
+ valid: true,
4904
+ masked_key: maskApiKey2(apiKey),
4905
+ email: data.email,
4906
+ name: data.name || data.full_name,
4907
+ plan: data.plan_name || data.plan || "free",
4908
+ workspace_name: data.workspace?.name
4909
+ };
4910
+ } else if (response.status === 401) {
4911
+ return {
4912
+ valid: false,
4913
+ masked_key: maskApiKey2(apiKey),
4914
+ error: "Invalid or expired API key"
4915
+ };
4916
+ } else {
4917
+ return {
4918
+ valid: false,
4919
+ masked_key: maskApiKey2(apiKey),
4920
+ error: `API error: ${response.status}`
4921
+ };
4922
+ }
4923
+ } catch (error) {
4924
+ return {
4925
+ valid: false,
4926
+ masked_key: maskApiKey2(apiKey),
4927
+ error: `Connection error: ${error instanceof Error ? error.message : String(error)}`
4928
+ };
3116
4929
  }
3117
- const cwd = extractCwd3(input);
3118
- const pythonHooks = detectPythonHooks(cwd);
3119
- const hasPythonHooksToUpgrade = pythonHooks.global || pythonHooks.project;
3120
- const rulesNotice = extractRulesNotice(input);
3121
- const rulesNeedUpdate = rulesNotice && rulesNotice.status !== "current";
3122
- if (!hasPythonHooksToUpgrade && !rulesNeedUpdate) {
3123
- process.exit(0);
4930
+ }
4931
+ async function runVerifyKey(outputJson) {
4932
+ const { apiKey, apiUrl, source } = loadApiKey();
4933
+ if (!apiKey) {
4934
+ const result2 = {
4935
+ valid: false,
4936
+ masked_key: "",
4937
+ error: "No API key found. Run 'contextstream-mcp setup' to configure."
4938
+ };
4939
+ if (outputJson) {
4940
+ console.log(JSON.stringify(result2));
4941
+ } else {
4942
+ console.log("\u274C No API key found");
4943
+ console.log(" Run 'contextstream-mcp setup' to configure your API key.");
4944
+ }
4945
+ return result2;
3124
4946
  }
3125
- const folderPath = rulesNotice?.update_args?.folder_path || cwd;
3126
- try {
3127
- await upgradeHooksForFolder(folderPath);
3128
- markAsRan();
3129
- } catch {
4947
+ const result = await validateApiKey(apiKey, apiUrl);
4948
+ if (outputJson) {
4949
+ console.log(JSON.stringify({ ...result, source }));
4950
+ } else {
4951
+ console.log("");
4952
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
4953
+ console.log(" ContextStream API Key");
4954
+ console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
4955
+ console.log("");
4956
+ console.log(` Key: ${result.masked_key}`);
4957
+ console.log(` Source: ${source}`);
4958
+ if (result.valid) {
4959
+ console.log(` Status: \u2713 Valid`);
4960
+ if (result.email) {
4961
+ console.log(` Account: ${result.email}`);
4962
+ }
4963
+ if (result.name) {
4964
+ console.log(` Name: ${result.name}`);
4965
+ }
4966
+ if (result.plan) {
4967
+ console.log(` Plan: ${result.plan}`);
4968
+ }
4969
+ if (result.workspace_name) {
4970
+ console.log(` Workspace: ${result.workspace_name}`);
4971
+ }
4972
+ } else {
4973
+ console.log(` Status: \u2717 Invalid`);
4974
+ console.log(` Error: ${result.error}`);
4975
+ }
4976
+ console.log("");
3130
4977
  }
3131
- process.exit(0);
4978
+ return result;
3132
4979
  }
3133
- var API_URL3, API_KEY3, ENABLED6, MARKER_FILE, COOLDOWN_MS, isDirectRun6;
3134
- var init_auto_rules = __esm({
3135
- "src/hooks/auto-rules.ts"() {
4980
+ var init_verify_key = __esm({
4981
+ "src/verify-key.ts"() {
3136
4982
  "use strict";
3137
- API_URL3 = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
3138
- API_KEY3 = process.env.CONTEXTSTREAM_API_KEY || "";
3139
- ENABLED6 = process.env.CONTEXTSTREAM_AUTO_RULES !== "false";
3140
- MARKER_FILE = path12.join(homedir9(), ".contextstream", ".auto-rules-ran");
3141
- COOLDOWN_MS = 4 * 60 * 60 * 1e3;
3142
- isDirectRun6 = process.argv[1]?.includes("auto-rules") || process.argv[2] === "auto-rules";
3143
- if (isDirectRun6) {
3144
- runAutoRulesHook().catch(() => process.exit(0));
3145
- }
3146
4983
  }
3147
4984
  });
3148
4985
 
@@ -3628,8 +5465,8 @@ function getErrorMap() {
3628
5465
 
3629
5466
  // node_modules/zod/v3/helpers/parseUtil.js
3630
5467
  var makeIssue = (params) => {
3631
- const { data, path: path13, errorMaps, issueData } = params;
3632
- const fullPath = [...path13, ...issueData.path || []];
5468
+ const { data, path: path21, errorMaps, issueData } = params;
5469
+ const fullPath = [...path21, ...issueData.path || []];
3633
5470
  const fullIssue = {
3634
5471
  ...issueData,
3635
5472
  path: fullPath
@@ -3745,11 +5582,11 @@ var errorUtil;
3745
5582
 
3746
5583
  // node_modules/zod/v3/types.js
3747
5584
  var ParseInputLazyPath = class {
3748
- constructor(parent, value, path13, key) {
5585
+ constructor(parent, value, path21, key) {
3749
5586
  this._cachedPath = [];
3750
5587
  this.parent = parent;
3751
5588
  this.data = value;
3752
- this._path = path13;
5589
+ this._path = path21;
3753
5590
  this._key = key;
3754
5591
  }
3755
5592
  get path() {
@@ -7199,6 +9036,9 @@ import { join } from "path";
7199
9036
  var UPGRADE_COMMAND = "npm install -g @contextstream/mcp-server@latest";
7200
9037
  var NPM_LATEST_URL = "https://registry.npmjs.org/@contextstream/mcp-server/latest";
7201
9038
  function getVersion() {
9039
+ if (typeof __CONTEXTSTREAM_VERSION__ !== "undefined" && __CONTEXTSTREAM_VERSION__) {
9040
+ return __CONTEXTSTREAM_VERSION__;
9041
+ }
7202
9042
  try {
7203
9043
  const require2 = createRequire(import.meta.url);
7204
9044
  const pkg = require2("../package.json");
@@ -7482,14 +9322,14 @@ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504])
7482
9322
  var MAX_RETRIES = 3;
7483
9323
  var BASE_DELAY = 1e3;
7484
9324
  async function sleep(ms) {
7485
- return new Promise((resolve8) => setTimeout(resolve8, ms));
9325
+ return new Promise((resolve15) => setTimeout(resolve15, ms));
7486
9326
  }
7487
- async function request(config, path13, options = {}) {
9327
+ async function request(config, path21, options = {}) {
7488
9328
  const { apiUrl, userAgent } = config;
7489
9329
  const authOverride = getAuthOverride();
7490
9330
  const apiKey = authOverride?.apiKey ?? config.apiKey;
7491
9331
  const jwt = authOverride?.jwt ?? config.jwt;
7492
- const apiPath = path13.startsWith("/api/") ? path13 : `/api/v1${path13}`;
9332
+ const apiPath = path21.startsWith("/api/") ? path21 : `/api/v1${path21}`;
7493
9333
  const url = `${apiUrl.replace(/\/$/, "")}${apiPath}`;
7494
9334
  const maxRetries = options.retries ?? MAX_RETRIES;
7495
9335
  const baseDelay = options.retryDelay ?? BASE_DELAY;
@@ -7635,9 +9475,9 @@ function extractErrorCode(payload) {
7635
9475
  if (typeof payload.code === "string" && payload.code.trim()) return payload.code.trim();
7636
9476
  return null;
7637
9477
  }
7638
- function detectIntegrationProvider(path13) {
7639
- if (/\/github(\/|$)/i.test(path13)) return "github";
7640
- if (/\/slack(\/|$)/i.test(path13)) return "slack";
9478
+ function detectIntegrationProvider(path21) {
9479
+ if (/\/github(\/|$)/i.test(path21)) return "github";
9480
+ if (/\/slack(\/|$)/i.test(path21)) return "slack";
7641
9481
  return null;
7642
9482
  }
7643
9483
  function rewriteNotFoundMessage(input) {
@@ -8290,10 +10130,10 @@ var PROJECT_MARKERS = [
8290
10130
  ];
8291
10131
  function isMultiProjectFolder(folderPath) {
8292
10132
  try {
8293
- const fs12 = __require("fs");
10133
+ const fs20 = __require("fs");
8294
10134
  const pathModule = __require("path");
8295
- const rootHasGit = fs12.existsSync(pathModule.join(folderPath, ".git"));
8296
- const entries = fs12.readdirSync(folderPath, { withFileTypes: true });
10135
+ const rootHasGit = fs20.existsSync(pathModule.join(folderPath, ".git"));
10136
+ const entries = fs20.readdirSync(folderPath, { withFileTypes: true });
8297
10137
  const subdirs = entries.filter(
8298
10138
  (e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules"
8299
10139
  );
@@ -8301,7 +10141,7 @@ function isMultiProjectFolder(folderPath) {
8301
10141
  for (const subdir of subdirs) {
8302
10142
  const subdirPath = pathModule.join(folderPath, subdir.name);
8303
10143
  for (const marker of PROJECT_MARKERS) {
8304
- if (fs12.existsSync(pathModule.join(subdirPath, marker))) {
10144
+ if (fs20.existsSync(pathModule.join(subdirPath, marker))) {
8305
10145
  projectSubdirs.push(subdir.name);
8306
10146
  break;
8307
10147
  }
@@ -9935,6 +11775,35 @@ var ContextStreamClient = class {
9935
11775
  }
9936
11776
  });
9937
11777
  }
11778
+ /**
11779
+ * Capture a memory event with a direct event_type.
11780
+ * Used for auto-save session snapshots and other system events.
11781
+ */
11782
+ async captureMemoryEvent(params) {
11783
+ const withDefaults = this.withDefaults(params);
11784
+ const metadata = {
11785
+ ...params.metadata || {}
11786
+ };
11787
+ if (params.tags && params.tags.length > 0) {
11788
+ metadata.tags = params.tags;
11789
+ }
11790
+ if (!metadata.captured_at) {
11791
+ metadata.captured_at = (/* @__PURE__ */ new Date()).toISOString();
11792
+ }
11793
+ if (!metadata.source) {
11794
+ metadata.source = "mcp_auto_capture";
11795
+ }
11796
+ return this.createMemoryEvent({
11797
+ workspace_id: withDefaults.workspace_id,
11798
+ project_id: withDefaults.project_id,
11799
+ event_type: params.event_type,
11800
+ title: params.title,
11801
+ content: params.content,
11802
+ provenance: params.provenance,
11803
+ code_refs: params.code_refs,
11804
+ metadata
11805
+ });
11806
+ }
9938
11807
  submitContextFeedback(body) {
9939
11808
  return request(this.config, "/context/smart/feedback", { body: this.withDefaults(body) });
9940
11809
  }
@@ -10339,9 +12208,9 @@ var ContextStreamClient = class {
10339
12208
  candidateParts.push("## Relevant Code\n");
10340
12209
  currentChars += 18;
10341
12210
  const codeEntries = code.results.map((c) => {
10342
- const path13 = c.file_path || "file";
12211
+ const path21 = c.file_path || "file";
10343
12212
  const content = c.content?.slice(0, 150) || "";
10344
- return { path: path13, entry: `\u2022 ${path13}: ${content}...
12213
+ return { path: path21, entry: `\u2022 ${path21}: ${content}...
10345
12214
  ` };
10346
12215
  });
10347
12216
  for (const c of codeEntries) {
@@ -10502,7 +12371,11 @@ var ContextStreamClient = class {
10502
12371
  notice_inline: false,
10503
12372
  // Session token tracking for context pressure
10504
12373
  ...params.session_tokens !== void 0 && { session_tokens: params.session_tokens },
10505
- ...params.context_threshold !== void 0 && { context_threshold: params.context_threshold }
12374
+ ...params.context_threshold !== void 0 && { context_threshold: params.context_threshold },
12375
+ // Transcript save parameters
12376
+ ...params.save_exchange !== void 0 && { save_exchange: params.save_exchange },
12377
+ ...params.session_id !== void 0 && { session_id: params.session_id },
12378
+ ...params.client_name !== void 0 && { client_name: params.client_name }
10506
12379
  }
10507
12380
  });
10508
12381
  const data = unwrapApiResponse(apiResult);
@@ -12064,6 +13937,95 @@ ${context}`;
12064
13937
  uuidSchema.parse(params.doc_id);
12065
13938
  return request(this.config, `/docs/${params.doc_id}`, { method: "DELETE" });
12066
13939
  }
13940
+ // -------------------------------------------------------------------------
13941
+ // Transcript methods (conversation session storage)
13942
+ // -------------------------------------------------------------------------
13943
+ /**
13944
+ * List transcripts for a workspace/project
13945
+ */
13946
+ async listTranscripts(params) {
13947
+ const withDefaults = this.withDefaults(params || {});
13948
+ const query = new URLSearchParams();
13949
+ if (withDefaults.workspace_id) query.set("workspace_id", withDefaults.workspace_id);
13950
+ if (withDefaults.project_id) query.set("project_id", withDefaults.project_id);
13951
+ if (params?.session_id) query.set("session_id", params.session_id);
13952
+ if (params?.client_name) query.set("client_name", params.client_name);
13953
+ if (params?.started_after) query.set("started_after", params.started_after);
13954
+ if (params?.started_before) query.set("started_before", params.started_before);
13955
+ if (params?.limit) query.set("per_page", String(params.limit));
13956
+ if (params?.page) query.set("page", String(params.page));
13957
+ if (params?.per_page) query.set("per_page", String(params.per_page));
13958
+ const suffix = query.toString() ? `?${query.toString()}` : "";
13959
+ return request(this.config, `/transcripts${suffix}`, { method: "GET" });
13960
+ }
13961
+ /**
13962
+ * Get a specific transcript by ID
13963
+ */
13964
+ async getTranscript(transcript_id) {
13965
+ uuidSchema.parse(transcript_id);
13966
+ return request(this.config, `/transcripts/${transcript_id}`, { method: "GET" });
13967
+ }
13968
+ /**
13969
+ * Search transcripts by content
13970
+ */
13971
+ async searchTranscripts(params) {
13972
+ const withDefaults = this.withDefaults(params);
13973
+ const queryParams = new URLSearchParams();
13974
+ if (withDefaults.workspace_id) queryParams.set("workspace_id", withDefaults.workspace_id);
13975
+ queryParams.set("query", params.query);
13976
+ if (params.limit) queryParams.set("limit", String(params.limit));
13977
+ return request(this.config, `/transcripts/search?${queryParams.toString()}`, { method: "GET" });
13978
+ }
13979
+ /**
13980
+ * Delete a transcript
13981
+ */
13982
+ async deleteTranscript(transcript_id) {
13983
+ uuidSchema.parse(transcript_id);
13984
+ return request(this.config, `/transcripts/${transcript_id}`, { method: "DELETE" });
13985
+ }
13986
+ // -------------------------------------------------------------------------
13987
+ // Suggested Rules methods (ML-generated rule suggestions)
13988
+ // -------------------------------------------------------------------------
13989
+ /**
13990
+ * List suggested rules
13991
+ */
13992
+ async listSuggestedRules(params) {
13993
+ const withDefaults = this.withDefaults(params || {});
13994
+ const queryParams = new URLSearchParams();
13995
+ if (withDefaults.workspace_id) queryParams.set("workspace_id", withDefaults.workspace_id);
13996
+ if (params?.status) queryParams.set("status", params.status);
13997
+ if (params?.source_type) queryParams.set("source_type", params.source_type);
13998
+ if (params?.min_confidence) queryParams.set("min_confidence", String(params.min_confidence));
13999
+ if (params?.limit) queryParams.set("limit", String(params.limit));
14000
+ if (params?.offset) queryParams.set("offset", String(params.offset));
14001
+ return request(this.config, `/suggested-rules?${queryParams.toString()}`, { method: "GET" });
14002
+ }
14003
+ /**
14004
+ * Get pending suggested rules count
14005
+ */
14006
+ async getSuggestedRulesPendingCount(params) {
14007
+ const withDefaults = this.withDefaults(params || {});
14008
+ const queryParams = new URLSearchParams();
14009
+ if (withDefaults.workspace_id) queryParams.set("workspace_id", withDefaults.workspace_id);
14010
+ return request(this.config, `/suggested-rules/pending-count?${queryParams.toString()}`, { method: "GET" });
14011
+ }
14012
+ /**
14013
+ * Get suggested rules feedback stats
14014
+ */
14015
+ async getSuggestedRulesStats(params) {
14016
+ const withDefaults = this.withDefaults(params || {});
14017
+ const queryParams = new URLSearchParams();
14018
+ if (withDefaults.workspace_id) queryParams.set("workspace_id", withDefaults.workspace_id);
14019
+ return request(this.config, `/suggested-rules/stats?${queryParams.toString()}`, { method: "GET" });
14020
+ }
14021
+ /**
14022
+ * Perform action on suggested rule (accept/reject/modify)
14023
+ */
14024
+ async suggestedRuleAction(params) {
14025
+ uuidSchema.parse(params.rule_id);
14026
+ const { rule_id, ...body } = params;
14027
+ return request(this.config, `/suggested-rules/${rule_id}/action`, { method: "POST", body });
14028
+ }
12067
14029
  };
12068
14030
 
12069
14031
  // src/tools.ts
@@ -13110,111 +15072,84 @@ function trackToolTokenSavings(client, tool, contextText, params, extraMetadata)
13110
15072
  }
13111
15073
  }
13112
15074
 
13113
- // src/educational-microcopy.ts
13114
- var SESSION_INIT_TIPS = [
13115
- "AI work that doesn't disappear.",
13116
- "Every conversation builds context. Every artifact persists.",
13117
- "Your AI remembers. Now you can see what it knows.",
13118
- "Context that survives sessions.",
13119
- "The bridge between AI conversations and human artifacts."
13120
- ];
15075
+ // src/microcopy.ts
13121
15076
  function getSessionInitTip(sessionId) {
13122
- const index = sessionId ? Math.abs(hashString(sessionId)) % SESSION_INIT_TIPS.length : Math.floor(Math.random() * SESSION_INIT_TIPS.length);
13123
- return SESSION_INIT_TIPS[index];
13124
- }
13125
- var CAPTURE_HINTS = {
13126
- // Core event types
13127
- decision: "Future you will thank present you.",
13128
- preference: "Noted. This will inform future suggestions.",
13129
- insight: "Captured for future reference.",
13130
- note: "Saved. Won't disappear when the chat does.",
13131
- implementation: "Implementation recorded.",
13132
- task: "Task tracked.",
13133
- bug: "Bug logged for tracking.",
13134
- feature: "Feature request captured.",
13135
- plan: "This plan will be here when you come back.",
13136
- correction: "Correction noted. Learning from this.",
13137
- lesson: "Learn once, remember forever.",
13138
- warning: "Warning logged.",
13139
- frustration: "Feedback captured. We're listening.",
13140
- conversation: "Conversation preserved.",
13141
- session_snapshot: "Session state saved. Ready to resume anytime."
13142
- };
15077
+ return `Session ${sessionId.slice(0, 8)}... initialized. Use context(user_message="...") to get relevant context.`;
15078
+ }
13143
15079
  function getCaptureHint(eventType) {
13144
- return CAPTURE_HINTS[eventType] || "Captured. This will survive when the chat disappears.";
13145
- }
13146
- var EMPTY_STATE_HINTS = {
13147
- // Plans
13148
- list_plans: "No plans yet. Plans live here, not in chat transcripts.",
13149
- get_plan: "Plan not found. Create one to track implementation across sessions.",
13150
- // Memory & Search
13151
- recall: "Nothing found yet. Context builds over time.",
13152
- search: "No matches. Try a different query or let context accumulate.",
13153
- list_events: "No events yet. Start capturing decisions and insights.",
13154
- decisions: "No decisions captured yet. Record the 'why' behind your choices.",
13155
- timeline: "Timeline empty. Events will appear as you work.",
13156
- // Lessons
13157
- get_lessons: "No lessons yet. Capture mistakes to learn from them.",
13158
- // Tasks
13159
- list_tasks: "No tasks yet. Break plans into trackable tasks.",
13160
- // Todos
13161
- list_todos: "No todos yet. Action items will appear here.",
13162
- // Diagrams
13163
- list_diagrams: "No diagrams yet. AI-generated diagrams persist here.",
13164
- // Docs
13165
- list_docs: "No docs yet. Turn AI explanations into shareable documentation.",
13166
- // Reminders
13167
- list_reminders: "No reminders set. Schedule follow-ups that won't be forgotten.",
13168
- // Generic
13169
- default: "Nothing yet. Start a conversation\u2014context builds over time."
13170
- };
13171
- function getEmptyStateHint(action) {
13172
- return EMPTY_STATE_HINTS[action] || EMPTY_STATE_HINTS.default;
15080
+ switch (eventType) {
15081
+ case "decision":
15082
+ return "Decision captured. It will surface in future context() calls when relevant.";
15083
+ case "preference":
15084
+ return "Preference saved. Future sessions will respect this preference.";
15085
+ case "insight":
15086
+ return "Insight captured. Use recall() to retrieve it later.";
15087
+ case "task":
15088
+ return "Task captured. Use list_tasks() to view all tasks.";
15089
+ case "lesson":
15090
+ return "Lesson saved. It will warn you before similar mistakes.";
15091
+ case "session_snapshot":
15092
+ return "Session state saved. Use restore_context() after compaction.";
15093
+ default:
15094
+ return "Event captured. Use recall() to retrieve related events.";
15095
+ }
15096
+ }
15097
+ function getEmptyStateHint(operation) {
15098
+ switch (operation) {
15099
+ case "get_lessons":
15100
+ return "No lessons found. Lessons are captured when mistakes occur.";
15101
+ case "recall":
15102
+ return "No memories found. Use capture() or remember() to save context.";
15103
+ case "list_plans":
15104
+ return "No plans found. Use capture_plan() to create an implementation plan.";
15105
+ case "list_events":
15106
+ return "No events found. Events are captured as you work.";
15107
+ case "list_tasks":
15108
+ return "No tasks found. Use create_task() to add tasks.";
15109
+ case "list_todos":
15110
+ return "No todos found. Use create_todo() to add quick todos.";
15111
+ case "list_diagrams":
15112
+ return "No diagrams found. Use create_diagram() to save a Mermaid diagram.";
15113
+ case "list_docs":
15114
+ return "No docs found. Use create_doc() to save documentation.";
15115
+ default:
15116
+ return "No results found.";
15117
+ }
13173
15118
  }
13174
- var POST_COMPACT_HINTS = {
13175
- restored: "Picked up where you left off. Session ended, memory didn't.",
13176
- restored_with_session: (sessionId) => `Restored from session ${sessionId}. Session ended, memory didn't.`,
13177
- no_snapshot: "No prior session found. Fresh start\u2014context will build as you work.",
13178
- failed: "Couldn't restore previous session. Use context to retrieve what you need."
13179
- };
13180
- var PLAN_HINTS = {
13181
- created: "Plan saved. It will be here when you come back.",
13182
- activated: "Plan is now active. Track progress across sessions.",
13183
- completed: "Plan completed. The journey is preserved for future reference.",
13184
- abandoned: "Plan archived. Abandoned plans still teach.",
13185
- updated: "Plan updated. Changes are preserved."
13186
- };
13187
15119
  function getPlanStatusHint(status) {
13188
- const statusMap = {
13189
- draft: PLAN_HINTS.created,
13190
- active: PLAN_HINTS.activated,
13191
- completed: PLAN_HINTS.completed,
13192
- abandoned: PLAN_HINTS.abandoned,
13193
- archived: PLAN_HINTS.abandoned
13194
- };
13195
- return statusMap[status] || PLAN_HINTS.updated;
15120
+ switch (status) {
15121
+ case "draft":
15122
+ return "Plan saved as draft. Update status to 'active' when ready.";
15123
+ case "active":
15124
+ return "Plan is now active. Create tasks to track implementation.";
15125
+ case "completed":
15126
+ return "Plan completed. Great work!";
15127
+ case "archived":
15128
+ return "Plan archived. It will still appear in searches.";
15129
+ case "abandoned":
15130
+ return "Plan abandoned. Consider capturing lessons learned.";
15131
+ default:
15132
+ return "Plan updated. Changes are preserved.";
15133
+ }
13196
15134
  }
13197
- var TASK_HINTS = {
13198
- created: "Task tracked. It won't disappear when the chat does.",
13199
- completed: "Task done. Progress is preserved.",
13200
- blocked: "Task blocked. Capture what's blocking for future reference.",
13201
- cancelled: "Task cancelled. The history remains."
15135
+ var POST_COMPACT_HINTS = {
15136
+ restored: "Context restored from pre-compaction snapshot.",
15137
+ restored_with_session: (sessionId) => `Context restored from session ${sessionId.slice(0, 8)}... snapshot.`,
15138
+ no_snapshot: "No snapshot found. Session state may be incomplete.",
15139
+ failed: "Failed to restore context. Try recall() to find relevant memories."
13202
15140
  };
13203
15141
  var INTEGRATION_HINTS = {
13204
- not_connected: "Connect integrations to sync context where your team already works.",
13205
- slack: "Context from Slack conversations, connected.",
13206
- github: "Issues and PRs linked to the decisions behind them.",
13207
- notion: "Decisions synced to where your team documents."
15142
+ connected: "Integration connected and syncing.",
15143
+ not_connected: "Integration not connected. Connect at:",
15144
+ sync_in_progress: "Sync in progress. Results may be incomplete.",
15145
+ sync_complete: "Sync complete. All data is current."
15146
+ };
15147
+ var TASK_HINTS = {
15148
+ created: "Task created. Use update_task() to change status.",
15149
+ completed: "Task completed. Well done!",
15150
+ blocked: "Task blocked. Add blocked_reason for context.",
15151
+ linked_to_plan: "Task linked to plan. Progress will be tracked."
13208
15152
  };
13209
- function hashString(str) {
13210
- let hash = 0;
13211
- for (let i = 0; i < str.length; i++) {
13212
- const char = str.charCodeAt(i);
13213
- hash = (hash << 5) - hash + char;
13214
- hash = hash & hash;
13215
- }
13216
- return hash;
13217
- }
13218
15153
 
13219
15154
  // src/tools.ts
13220
15155
  var LOG_LEVEL = (process.env.CONTEXTSTREAM_LOG_LEVEL || "normal").toLowerCase();
@@ -13985,9 +15920,9 @@ function humanizeKey(raw) {
13985
15920
  const withSpaces = raw.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ");
13986
15921
  return withSpaces.toLowerCase();
13987
15922
  }
13988
- function buildParamDescription(key, path13) {
15923
+ function buildParamDescription(key, path21) {
13989
15924
  const normalized = key in DEFAULT_PARAM_DESCRIPTIONS ? key : key.toLowerCase();
13990
- const parent = path13[path13.length - 1];
15925
+ const parent = path21[path21.length - 1];
13991
15926
  if (parent === "target") {
13992
15927
  if (key === "id") return "Target identifier (module path, function id, etc.).";
13993
15928
  if (key === "type") return "Target type (module, file, function, type, variable).";
@@ -14018,7 +15953,7 @@ function getDescription(schema) {
14018
15953
  if (def?.description && def.description.trim()) return def.description;
14019
15954
  return void 0;
14020
15955
  }
14021
- function applyParamDescriptions(schema, path13 = []) {
15956
+ function applyParamDescriptions(schema, path21 = []) {
14022
15957
  if (!(schema instanceof external_exports.ZodObject)) {
14023
15958
  return schema;
14024
15959
  }
@@ -14029,7 +15964,7 @@ function applyParamDescriptions(schema, path13 = []) {
14029
15964
  let nextField = field;
14030
15965
  const existingDescription = getDescription(field);
14031
15966
  if (field instanceof external_exports.ZodObject) {
14032
- const nested = applyParamDescriptions(field, [...path13, key]);
15967
+ const nested = applyParamDescriptions(field, [...path21, key]);
14033
15968
  if (nested !== field) {
14034
15969
  nextField = nested;
14035
15970
  changed = true;
@@ -14041,7 +15976,7 @@ function applyParamDescriptions(schema, path13 = []) {
14041
15976
  changed = true;
14042
15977
  }
14043
15978
  } else {
14044
- nextField = nextField.describe(buildParamDescription(key, path13));
15979
+ nextField = nextField.describe(buildParamDescription(key, path21));
14045
15980
  changed = true;
14046
15981
  }
14047
15982
  nextShape[key] = nextField;
@@ -14880,7 +16815,10 @@ function registerTools(server, client, sessionManager) {
14880
16815
  "notion_stats",
14881
16816
  "notion_activity",
14882
16817
  "notion_knowledge",
14883
- "notion_summary"
16818
+ "notion_summary",
16819
+ // Media operations (credit-metered)
16820
+ "media_index",
16821
+ "media_search"
14884
16822
  ]);
14885
16823
  const proTools = (() => {
14886
16824
  const raw = process.env.CONTEXTSTREAM_PRO_TOOLS;
@@ -14958,10 +16896,12 @@ function registerTools(server, client, sessionManager) {
14958
16896
  function isTeamPlanCached() {
14959
16897
  return teamStatus.checked ? teamStatus.isTeamPlan : false;
14960
16898
  }
14961
- let integrationStatus = { checked: false, slack: false, github: false, notion: false };
16899
+ const INTEGRATION_CACHE_TTL_MS = 5 * 60 * 1e3;
16900
+ let integrationStatus = { checked: false, checkedAt: 0, slack: false, github: false, notion: false };
14962
16901
  let toolsListChangedNotified = false;
14963
16902
  async function checkIntegrationStatus(workspaceId) {
14964
- if (integrationStatus.checked && integrationStatus.workspaceId === workspaceId) {
16903
+ const cacheAge = Date.now() - integrationStatus.checkedAt;
16904
+ if (integrationStatus.checked && integrationStatus.workspaceId === workspaceId && cacheAge < INTEGRATION_CACHE_TTL_MS) {
14965
16905
  return { slack: integrationStatus.slack, github: integrationStatus.github, notion: integrationStatus.notion };
14966
16906
  }
14967
16907
  if (!workspaceId) {
@@ -14969,17 +16909,19 @@ function registerTools(server, client, sessionManager) {
14969
16909
  }
14970
16910
  try {
14971
16911
  const status = await client.integrationsStatus({ workspace_id: workspaceId });
16912
+ const isConnectedStatus = (s) => s === "connected" || s === "syncing";
14972
16913
  const slackConnected = status?.some(
14973
- (s) => s.provider === "slack" && s.status === "connected"
16914
+ (s) => s.provider === "slack" && isConnectedStatus(s.status)
14974
16915
  ) ?? false;
14975
16916
  const githubConnected = status?.some(
14976
- (s) => s.provider === "github" && s.status === "connected"
16917
+ (s) => s.provider === "github" && isConnectedStatus(s.status)
14977
16918
  ) ?? false;
14978
16919
  const notionConnected = status?.some(
14979
- (s) => s.provider === "notion" && s.status === "connected"
16920
+ (s) => s.provider === "notion" && isConnectedStatus(s.status)
14980
16921
  ) ?? false;
14981
16922
  integrationStatus = {
14982
16923
  checked: true,
16924
+ checkedAt: Date.now(),
14983
16925
  slack: slackConnected,
14984
16926
  github: githubConnected,
14985
16927
  notion: notionConnected,
@@ -14998,6 +16940,7 @@ function registerTools(server, client, sessionManager) {
14998
16940
  const hadNotion = integrationStatus.notion;
14999
16941
  integrationStatus = {
15000
16942
  checked: true,
16943
+ checkedAt: Date.now(),
15001
16944
  slack: status.slack,
15002
16945
  github: status.github,
15003
16946
  notion: status.notion,
@@ -18577,7 +20520,10 @@ This saves ~80% tokens compared to including full chat history.`,
18577
20520
  mode: external_exports.enum(["standard", "pack"]).optional().describe("Context pack mode (default: pack when enabled)"),
18578
20521
  distill: external_exports.boolean().optional().describe("Use distillation for context pack (default: true)"),
18579
20522
  session_tokens: external_exports.number().optional().describe("Cumulative session token count for context pressure calculation"),
18580
- context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)")
20523
+ context_threshold: external_exports.number().optional().describe("Custom context window threshold (defaults to 70k)"),
20524
+ save_exchange: external_exports.boolean().optional().describe("Save this exchange to the transcript for later search (background task)"),
20525
+ session_id: external_exports.string().optional().describe("Session ID for transcript association (required if save_exchange is true)"),
20526
+ client_name: external_exports.string().optional().describe("Client name for transcript metadata (e.g., 'claude', 'cursor')")
18581
20527
  })
18582
20528
  },
18583
20529
  async (input) => {
@@ -18653,6 +20599,14 @@ This saves ~80% tokens compared to including full chat history.`,
18653
20599
  logDebug(`Failed to restore post-compact context: ${err}`);
18654
20600
  }
18655
20601
  }
20602
+ let sessionId = input.session_id;
20603
+ if (!sessionId && sessionManager && input.save_exchange) {
20604
+ sessionId = sessionManager.getSessionId();
20605
+ }
20606
+ let clientName = input.client_name;
20607
+ if (!clientName && detectedClientInfo) {
20608
+ clientName = detectedClientInfo.name;
20609
+ }
18656
20610
  const result = await client.getSmartContext({
18657
20611
  user_message: input.user_message,
18658
20612
  workspace_id: workspaceId,
@@ -18662,7 +20616,10 @@ This saves ~80% tokens compared to including full chat history.`,
18662
20616
  mode: input.mode,
18663
20617
  distill: input.distill,
18664
20618
  session_tokens: sessionTokens,
18665
- context_threshold: contextThreshold
20619
+ context_threshold: contextThreshold,
20620
+ save_exchange: input.save_exchange,
20621
+ session_id: sessionId,
20622
+ client_name: clientName
18666
20623
  });
18667
20624
  if (sessionManager && result.token_estimate) {
18668
20625
  sessionManager.addTokens(result.token_estimate);
@@ -19597,14 +21554,15 @@ Use this to verify integrations are healthy and syncing properly.`,
19597
21554
  }
19598
21555
  const result = await client.integrationsStatus({ workspace_id: workspaceId });
19599
21556
  if (AUTO_HIDE_INTEGRATIONS) {
21557
+ const isConnectedStatus = (s) => s === "connected" || s === "syncing";
19600
21558
  const slackConnected = result?.some(
19601
- (s) => s.provider === "slack" && s.status === "connected"
21559
+ (s) => s.provider === "slack" && isConnectedStatus(s.status)
19602
21560
  ) ?? false;
19603
21561
  const githubConnected = result?.some(
19604
- (s) => s.provider === "github" && s.status === "connected"
21562
+ (s) => s.provider === "github" && isConnectedStatus(s.status)
19605
21563
  ) ?? false;
19606
21564
  const notionConnected = result?.some(
19607
- (s) => s.provider === "notion" && s.status === "connected"
21565
+ (s) => s.provider === "notion" && isConnectedStatus(s.status)
19608
21566
  ) ?? false;
19609
21567
  updateIntegrationStatus({ slack: slackConnected, github: githubConnected, notion: notionConnected }, workspaceId);
19610
21568
  }
@@ -19959,7 +21917,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
19959
21917
  "session",
19960
21918
  {
19961
21919
  title: "Session",
19962
- description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans). Team actions (team plans only): team_decisions (team-wide decisions), team_lessons (team-wide lessons), team_plans (plans across team workspaces).`,
21920
+ description: `Session management operations. Actions: capture (save decision/insight), capture_lesson (save lesson from mistake), get_lessons (retrieve lessons), recall (natural language recall), remember (quick save), user_context (get preferences), summary (workspace summary), compress (compress chat), delta (changes since timestamp), smart_search (context-enriched search), decision_trace (trace decision provenance), restore_context (restore state after compaction). Plan actions: capture_plan (save implementation plan), get_plan (retrieve plan with tasks), update_plan (modify plan), list_plans (list all plans). Suggested rules actions: list_suggested_rules (view ML-generated rule suggestions), suggested_rule_action (accept/reject/modify a suggestion), suggested_rules_stats (view ML accuracy stats). Team actions (team plans only): team_decisions (team-wide decisions), team_lessons (team-wide lessons), team_plans (plans across team workspaces).`,
19963
21921
  inputSchema: external_exports.object({
19964
21922
  action: external_exports.enum([
19965
21923
  "capture",
@@ -19983,7 +21941,11 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
19983
21941
  // Team actions (team plans only)
19984
21942
  "team_decisions",
19985
21943
  "team_lessons",
19986
- "team_plans"
21944
+ "team_plans",
21945
+ // Suggested rules actions (ML-generated)
21946
+ "list_suggested_rules",
21947
+ "suggested_rule_action",
21948
+ "suggested_rules_stats"
19987
21949
  ]).describe("Action to perform"),
19988
21950
  workspace_id: external_exports.string().uuid().optional(),
19989
21951
  project_id: external_exports.string().uuid().optional(),
@@ -20060,7 +22022,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
20060
22022
  is_personal: external_exports.boolean().optional().describe("Mark plan as personal (only visible to creator). For capture_plan/list_plans."),
20061
22023
  // Restore context params
20062
22024
  snapshot_id: external_exports.string().uuid().optional().describe("Specific snapshot ID to restore (defaults to most recent)"),
20063
- max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)")
22025
+ max_snapshots: external_exports.number().optional().default(1).describe("Number of recent snapshots to consider (default: 1)"),
22026
+ // Suggested rules params
22027
+ rule_id: external_exports.string().uuid().optional().describe("Suggested rule ID for actions"),
22028
+ rule_action: external_exports.enum(["accept", "reject", "modify"]).optional().describe("Action to perform on suggested rule"),
22029
+ modified_keywords: external_exports.array(external_exports.string()).optional().describe("Modified keywords when action is modify"),
22030
+ modified_instruction: external_exports.string().optional().describe("Modified instruction when action is modify"),
22031
+ min_confidence: external_exports.number().optional().describe("Minimum confidence threshold for listing rules")
20064
22032
  })
20065
22033
  },
20066
22034
  async (input) => {
@@ -20636,6 +22604,81 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
20636
22604
  ]
20637
22605
  };
20638
22606
  }
22607
+ case "list_suggested_rules": {
22608
+ const result = await client.listSuggestedRules({
22609
+ workspace_id: workspaceId,
22610
+ status: input.status,
22611
+ min_confidence: input.min_confidence,
22612
+ limit: input.limit
22613
+ });
22614
+ const rules = result?.data?.items || result?.items || [];
22615
+ if (rules.length === 0) {
22616
+ return {
22617
+ content: [
22618
+ {
22619
+ type: "text",
22620
+ text: formatContent({
22621
+ suggested_rules: [],
22622
+ hint: "No pending rule suggestions. The ML system learns from your lessons and will suggest rules when patterns are detected."
22623
+ })
22624
+ }
22625
+ ]
22626
+ };
22627
+ }
22628
+ return {
22629
+ content: [
22630
+ {
22631
+ type: "text",
22632
+ text: formatContent({
22633
+ suggested_rules: rules,
22634
+ total: result?.data?.total || rules.length,
22635
+ hint: "Use suggested_rule_action to accept, reject, or modify these suggestions."
22636
+ })
22637
+ }
22638
+ ]
22639
+ };
22640
+ }
22641
+ case "suggested_rule_action": {
22642
+ if (!input.rule_id || !input.rule_action) {
22643
+ return errorResult("suggested_rule_action requires: rule_id, rule_action (accept/reject/modify)");
22644
+ }
22645
+ const result = await client.suggestedRuleAction({
22646
+ rule_id: input.rule_id,
22647
+ action: input.rule_action,
22648
+ modified_keywords: input.modified_keywords,
22649
+ modified_instruction: input.modified_instruction
22650
+ });
22651
+ const actionVerb = input.rule_action === "accept" ? "accepted" : input.rule_action === "reject" ? "rejected" : "modified";
22652
+ return {
22653
+ content: [
22654
+ {
22655
+ type: "text",
22656
+ text: formatContent({
22657
+ success: true,
22658
+ message: `Rule ${actionVerb} successfully`,
22659
+ rule: result?.data || result,
22660
+ hint: input.rule_action === "accept" ? "This rule will now be applied to future context() calls." : input.rule_action === "reject" ? "This pattern will have reduced confidence for future suggestions." : "The modified rule will be applied to future context() calls."
22661
+ })
22662
+ }
22663
+ ]
22664
+ };
22665
+ }
22666
+ case "suggested_rules_stats": {
22667
+ const result = await client.getSuggestedRulesStats({
22668
+ workspace_id: workspaceId
22669
+ });
22670
+ return {
22671
+ content: [
22672
+ {
22673
+ type: "text",
22674
+ text: formatContent({
22675
+ stats: result?.data || result,
22676
+ hint: "These stats show ML vs Grok accuracy. The blend weight auto-adjusts based on these metrics."
22677
+ })
22678
+ }
22679
+ ]
22680
+ };
22681
+ }
20639
22682
  default:
20640
22683
  return errorResult(`Unknown action: ${input.action}`);
20641
22684
  }
@@ -20645,7 +22688,7 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
20645
22688
  "memory",
20646
22689
  {
20647
22690
  title: "Memory",
20648
- description: `Memory operations for events and nodes. Event actions: create_event, get_event, update_event, delete_event, list_events, distill_event, import_batch (bulk import array of events). Node actions: create_node, get_node, update_node, delete_node, list_nodes, supersede_node. Query actions: search, decisions, timeline, summary. Task actions: create_task (create task, optionally linked to plan), get_task, update_task (can link/unlink task to plan via plan_id), delete_task, list_tasks, reorder_tasks. Todo actions: create_todo, list_todos, get_todo, update_todo, delete_todo, complete_todo. Diagram actions: create_diagram, list_diagrams, get_diagram, update_diagram, delete_diagram. Doc actions: create_doc, list_docs, get_doc, update_doc, delete_doc, create_roadmap. Team actions (team plans only): team_tasks, team_todos, team_diagrams, team_docs.`,
22691
+ description: `Memory operations for events and nodes. Event actions: create_event, get_event, update_event, delete_event, list_events, distill_event, import_batch (bulk import array of events). Node actions: create_node, get_node, update_node, delete_node, list_nodes, supersede_node. Query actions: search, decisions, timeline, summary. Task actions: create_task (create task, optionally linked to plan), get_task, update_task (can link/unlink task to plan via plan_id), delete_task, list_tasks, reorder_tasks. Todo actions: create_todo, list_todos, get_todo, update_todo, delete_todo, complete_todo. Diagram actions: create_diagram, list_diagrams, get_diagram, update_diagram, delete_diagram. Doc actions: create_doc, list_docs, get_doc, update_doc, delete_doc, create_roadmap. Transcript actions: list_transcripts (list saved conversations), get_transcript (get full transcript by ID), search_transcripts (semantic search across conversations), delete_transcript. Team actions (team plans only): team_tasks, team_todos, team_diagrams, team_docs.`,
20649
22692
  inputSchema: external_exports.object({
20650
22693
  action: external_exports.enum([
20651
22694
  "create_event",
@@ -20693,6 +22736,11 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
20693
22736
  "update_doc",
20694
22737
  "delete_doc",
20695
22738
  "create_roadmap",
22739
+ // Transcript actions
22740
+ "list_transcripts",
22741
+ "get_transcript",
22742
+ "search_transcripts",
22743
+ "delete_transcript",
20696
22744
  // Team actions
20697
22745
  "team_tasks",
20698
22746
  "team_todos",
@@ -20798,7 +22846,13 @@ Output formats: full (default, includes content), paths (file paths only - 80% t
20798
22846
  })
20799
22847
  ).optional().describe("Milestones for create_roadmap action"),
20800
22848
  // Personal items param
20801
- is_personal: external_exports.boolean().optional().describe("Mark as personal (only visible to creator). For create/list actions on todos, diagrams, docs.")
22849
+ is_personal: external_exports.boolean().optional().describe("Mark as personal (only visible to creator). For create/list actions on todos, diagrams, docs."),
22850
+ // Transcript params
22851
+ transcript_id: external_exports.string().uuid().optional().describe("Transcript ID for get_transcript/delete_transcript"),
22852
+ session_id: external_exports.string().optional().describe("Session ID filter for list_transcripts"),
22853
+ client_name: external_exports.string().optional().describe("Client name filter for list_transcripts (e.g., 'claude', 'cursor')"),
22854
+ started_after: external_exports.string().optional().describe("ISO timestamp - filter transcripts started after this time"),
22855
+ started_before: external_exports.string().optional().describe("ISO timestamp - filter transcripts started before this time")
20802
22856
  })
20803
22857
  },
20804
22858
  async (input) => {
@@ -21572,6 +23626,54 @@ ${formatContent(result)}`
21572
23626
  ]
21573
23627
  };
21574
23628
  }
23629
+ // Transcript actions
23630
+ case "list_transcripts": {
23631
+ const result = await client.listTranscripts({
23632
+ workspace_id: workspaceId,
23633
+ project_id: projectId,
23634
+ session_id: input.session_id,
23635
+ client_name: input.client_name,
23636
+ started_after: input.started_after,
23637
+ started_before: input.started_before,
23638
+ limit: input.limit
23639
+ });
23640
+ const transcripts = result?.data?.items || result?.items || result?.data || [];
23641
+ const resultWithHint = Array.isArray(transcripts) && transcripts.length === 0 ? { ...result, hint: "No transcripts found. Enable save_exchange in context() calls to save conversations." } : result;
23642
+ return {
23643
+ content: [{ type: "text", text: formatContent(resultWithHint) }]
23644
+ };
23645
+ }
23646
+ case "get_transcript": {
23647
+ if (!input.transcript_id) {
23648
+ return errorResult("get_transcript requires: transcript_id");
23649
+ }
23650
+ const result = await client.getTranscript(input.transcript_id);
23651
+ return {
23652
+ content: [{ type: "text", text: formatContent(result) }]
23653
+ };
23654
+ }
23655
+ case "search_transcripts": {
23656
+ if (!input.query) {
23657
+ return errorResult("search_transcripts requires: query");
23658
+ }
23659
+ const result = await client.searchTranscripts({
23660
+ workspace_id: workspaceId,
23661
+ query: input.query,
23662
+ limit: input.limit
23663
+ });
23664
+ return {
23665
+ content: [{ type: "text", text: formatContent(result) }]
23666
+ };
23667
+ }
23668
+ case "delete_transcript": {
23669
+ if (!input.transcript_id) {
23670
+ return errorResult("delete_transcript requires: transcript_id");
23671
+ }
23672
+ const result = await client.deleteTranscript(input.transcript_id);
23673
+ return {
23674
+ content: [{ type: "text", text: formatContent(result) }]
23675
+ };
23676
+ }
21575
23677
  default:
21576
23678
  return errorResult(`Unknown action: ${input.action}`);
21577
23679
  }
@@ -22860,6 +24962,8 @@ Example workflow:
22860
24962
  const projectId = resolveProjectId(input.project_id);
22861
24963
  switch (input.action) {
22862
24964
  case "index": {
24965
+ const indexGate = await gateIfProTool("media_index");
24966
+ if (indexGate) return indexGate;
22863
24967
  if (!input.file_path && !input.external_url) {
22864
24968
  return errorResult("index requires: file_path or external_url");
22865
24969
  }
@@ -22869,13 +24973,13 @@ Example workflow:
22869
24973
  );
22870
24974
  }
22871
24975
  if (input.file_path) {
22872
- const fs12 = await import("fs/promises");
24976
+ const fs20 = await import("fs/promises");
22873
24977
  const pathModule = await import("path");
22874
24978
  const filePath = input.file_path.startsWith("~") ? input.file_path.replace("~", process.env.HOME || "") : input.file_path;
22875
24979
  const resolvedPath = pathModule.resolve(filePath);
22876
24980
  let fileStats;
22877
24981
  try {
22878
- fileStats = await fs12.stat(resolvedPath);
24982
+ fileStats = await fs20.stat(resolvedPath);
22879
24983
  } catch {
22880
24984
  return errorResult(`File not found: ${resolvedPath}`);
22881
24985
  }
@@ -22922,7 +25026,7 @@ Example workflow:
22922
25026
  mime_type: mimeType,
22923
25027
  tags: input.tags
22924
25028
  });
22925
- const fileBuffer = await fs12.readFile(resolvedPath);
25029
+ const fileBuffer = await fs20.readFile(resolvedPath);
22926
25030
  const uploadResponse = await fetch(uploadInit.upload_url, {
22927
25031
  method: "PUT",
22928
25032
  headers: uploadInit.headers,
@@ -23024,6 +25128,8 @@ Created: ${content.created_at}`
23024
25128
  }
23025
25129
  }
23026
25130
  case "search": {
25131
+ const searchGate = await gateIfProTool("media_search");
25132
+ if (searchGate) return searchGate;
23027
25133
  if (!input.query) {
23028
25134
  return errorResult("search requires: query");
23029
25135
  }
@@ -24244,6 +26350,7 @@ function registerPrompts(server) {
24244
26350
  }
24245
26351
 
24246
26352
  // src/session-manager.ts
26353
+ import { randomUUID as randomUUID2 } from "crypto";
24247
26354
  var SessionManager = class _SessionManager {
24248
26355
  constructor(server, client) {
24249
26356
  this.server = server;
@@ -24275,12 +26382,20 @@ var SessionManager = class _SessionManager {
24275
26382
  this.lastHighPressureAt = null;
24276
26383
  this.lastHighPressureTokens = 0;
24277
26384
  this.postCompactRestoreCompleted = false;
26385
+ this.sessionId = `mcp-${randomUUID2()}`;
24278
26386
  }
24279
26387
  static {
24280
26388
  // Each conversation turn typically includes: user message (~500), AI response (~1500),
24281
26389
  // system prompt overhead (~500), and reasoning (~1500). Conservative estimate: 3000/turn
24282
26390
  this.TOKENS_PER_TURN_ESTIMATE = 3e3;
24283
26391
  }
26392
+ /**
26393
+ * Get the unique session ID for this MCP connection.
26394
+ * Used for transcript saving and session association.
26395
+ */
26396
+ getSessionId() {
26397
+ return this.sessionId;
26398
+ }
24284
26399
  /**
24285
26400
  * Check if session has been auto-initialized
24286
26401
  */
@@ -24318,8 +26433,8 @@ var SessionManager = class _SessionManager {
24318
26433
  /**
24319
26434
  * Set the folder path hint (can be passed from tools that know the workspace path)
24320
26435
  */
24321
- setFolderPath(path13) {
24322
- this.folderPath = path13;
26436
+ setFolderPath(path21) {
26437
+ this.folderPath = path21;
24323
26438
  }
24324
26439
  /**
24325
26440
  * Mark that context_smart has been called in this session.
@@ -24509,7 +26624,7 @@ var SessionManager = class _SessionManager {
24509
26624
  }
24510
26625
  if (this.ideRoots.length === 0) {
24511
26626
  const cwd = process.cwd();
24512
- const fs12 = await import("fs");
26627
+ const fs20 = await import("fs");
24513
26628
  const projectIndicators = [
24514
26629
  ".git",
24515
26630
  "package.json",
@@ -24519,7 +26634,7 @@ var SessionManager = class _SessionManager {
24519
26634
  ];
24520
26635
  const hasProjectIndicator = projectIndicators.some((f) => {
24521
26636
  try {
24522
- return fs12.existsSync(`${cwd}/${f}`);
26637
+ return fs20.existsSync(`${cwd}/${f}`);
24523
26638
  } catch {
24524
26639
  return false;
24525
26640
  }
@@ -24815,7 +26930,7 @@ var SessionManager = class _SessionManager {
24815
26930
 
24816
26931
  // src/http-gateway.ts
24817
26932
  import { createServer } from "node:http";
24818
- import { randomUUID as randomUUID2 } from "node:crypto";
26933
+ import { randomUUID as randomUUID3 } from "node:crypto";
24819
26934
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
24820
26935
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24821
26936
  var HOST = process.env.MCP_HTTP_HOST || "0.0.0.0";
@@ -24992,7 +27107,7 @@ async function createSession() {
24992
27107
  registerPrompts(server);
24993
27108
  }
24994
27109
  const transport = new StreamableHTTPServerTransport({
24995
- sessionIdGenerator: () => randomUUID2(),
27110
+ sessionIdGenerator: () => randomUUID3(),
24996
27111
  enableJsonResponse: ENABLE_JSON_RESPONSE,
24997
27112
  onsessionclosed: (sessionId) => {
24998
27113
  sessions.delete(sessionId);
@@ -25096,9 +27211,9 @@ async function runHttpGateway() {
25096
27211
  }
25097
27212
 
25098
27213
  // src/index.ts
25099
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
25100
- import { homedir as homedir10 } from "os";
25101
- import { join as join14 } from "path";
27214
+ import { existsSync as existsSync16, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
27215
+ import { homedir as homedir18 } from "os";
27216
+ import { join as join22 } from "path";
25102
27217
 
25103
27218
  // src/setup.ts
25104
27219
  import * as fs7 from "node:fs/promises";
@@ -25928,10 +28043,10 @@ Code: ${device.user_code}`);
25928
28043
  if (poll && poll.status === "pending") {
25929
28044
  const intervalSeconds = typeof poll.interval === "number" ? poll.interval : 5;
25930
28045
  const waitMs = Math.max(1, intervalSeconds) * 1e3;
25931
- await new Promise((resolve8) => setTimeout(resolve8, waitMs));
28046
+ await new Promise((resolve15) => setTimeout(resolve15, waitMs));
25932
28047
  continue;
25933
28048
  }
25934
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
28049
+ await new Promise((resolve15) => setTimeout(resolve15, 1e3));
25935
28050
  }
25936
28051
  if (!accessToken) {
25937
28052
  throw new Error(
@@ -26496,12 +28611,12 @@ Applying to ${projects.length} project(s)...`);
26496
28611
  // src/index.ts
26497
28612
  var ENABLE_PROMPTS2 = (process.env.CONTEXTSTREAM_ENABLE_PROMPTS || "true").toLowerCase() !== "false";
26498
28613
  function showFirstRunMessage() {
26499
- const configDir = join14(homedir10(), ".contextstream");
26500
- const starShownFile = join14(configDir, ".star-shown");
26501
- if (existsSync8(starShownFile)) {
28614
+ const configDir = join22(homedir18(), ".contextstream");
28615
+ const starShownFile = join22(configDir, ".star-shown");
28616
+ if (existsSync16(starShownFile)) {
26502
28617
  return;
26503
28618
  }
26504
- if (!existsSync8(configDir)) {
28619
+ if (!existsSync16(configDir)) {
26505
28620
  try {
26506
28621
  mkdirSync5(configDir, { recursive: true });
26507
28622
  } catch {
@@ -26532,13 +28647,26 @@ Usage:
26532
28647
 
26533
28648
  Commands:
26534
28649
  setup Interactive onboarding wizard (rules + workspace mapping)
28650
+ verify-key [--json] Verify API key and show account info
28651
+ update-hooks [flags] Update hooks for all editors (Claude, Cursor, Cline, Roo, Kilo)
28652
+ --scope=global Install hooks globally (default)
28653
+ --scope=project, -p Install hooks for current project only
28654
+ --path=/path Specify project path (implies --scope=project)
26535
28655
  http Run HTTP MCP gateway (streamable HTTP transport)
26536
28656
  hook pre-tool-use PreToolUse hook - blocks discovery tools, redirects to ContextStream
26537
28657
  hook user-prompt-submit UserPromptSubmit hook - injects ContextStream rules reminder
26538
28658
  hook media-aware Media-aware hook - detects media prompts, injects media tool guidance
26539
28659
  hook pre-compact PreCompact hook - saves conversation state before compaction
28660
+ hook post-compact PostCompact hook - restores context after compaction
26540
28661
  hook post-write PostToolUse hook - real-time file indexing after Edit/Write
26541
28662
  hook auto-rules PostToolUse hook - auto-updates rules when behind (silent)
28663
+ hook on-bash PostToolUse hook - captures bash commands, learns from errors
28664
+ hook on-task PostToolUse hook - tracks Task agent work
28665
+ hook on-read PostToolUse hook - tracks file exploration (Read/Glob/Grep)
28666
+ hook on-web PostToolUse hook - captures web research (WebFetch/WebSearch)
28667
+ hook session-init SessionStart hook - full context injection on session start
28668
+ hook session-end Stop hook - finalizes session, saves state
28669
+ hook on-save-intent UserPromptSubmit hook - redirects doc saves to ContextStream
26542
28670
 
26543
28671
  Environment variables:
26544
28672
  CONTEXTSTREAM_API_URL Base API URL (e.g. https://api.contextstream.io)
@@ -26647,12 +28775,90 @@ async function main() {
26647
28775
  await runAutoRulesHook2();
26648
28776
  return;
26649
28777
  }
28778
+ case "post-compact": {
28779
+ const { runPostCompactHook: runPostCompactHook2 } = await Promise.resolve().then(() => (init_post_compact(), post_compact_exports));
28780
+ await runPostCompactHook2();
28781
+ return;
28782
+ }
28783
+ case "on-bash": {
28784
+ const { runOnBashHook: runOnBashHook2 } = await Promise.resolve().then(() => (init_on_bash(), on_bash_exports));
28785
+ await runOnBashHook2();
28786
+ return;
28787
+ }
28788
+ case "on-task": {
28789
+ const { runOnTaskHook: runOnTaskHook2 } = await Promise.resolve().then(() => (init_on_task(), on_task_exports));
28790
+ await runOnTaskHook2();
28791
+ return;
28792
+ }
28793
+ case "on-read": {
28794
+ const { runOnReadHook: runOnReadHook2 } = await Promise.resolve().then(() => (init_on_read(), on_read_exports));
28795
+ await runOnReadHook2();
28796
+ return;
28797
+ }
28798
+ case "on-web": {
28799
+ const { runOnWebHook: runOnWebHook2 } = await Promise.resolve().then(() => (init_on_web(), on_web_exports));
28800
+ await runOnWebHook2();
28801
+ return;
28802
+ }
28803
+ case "session-init": {
28804
+ const { runSessionInitHook: runSessionInitHook2 } = await Promise.resolve().then(() => (init_session_init(), session_init_exports));
28805
+ await runSessionInitHook2();
28806
+ return;
28807
+ }
28808
+ case "session-end": {
28809
+ const { runSessionEndHook: runSessionEndHook2 } = await Promise.resolve().then(() => (init_session_end(), session_end_exports));
28810
+ await runSessionEndHook2();
28811
+ return;
28812
+ }
28813
+ case "on-save-intent": {
28814
+ const { runOnSaveIntentHook: runOnSaveIntentHook2 } = await Promise.resolve().then(() => (init_on_save_intent(), on_save_intent_exports));
28815
+ await runOnSaveIntentHook2();
28816
+ return;
28817
+ }
26650
28818
  default:
26651
28819
  console.error(`Unknown hook: ${hookName}`);
26652
- console.error("Available hooks: pre-tool-use, user-prompt-submit, media-aware, pre-compact, post-write, auto-rules");
28820
+ console.error("Available hooks: pre-tool-use, user-prompt-submit, media-aware, pre-compact, post-compact, post-write, auto-rules, on-bash, on-task, on-read, on-web, session-init, session-end, on-save-intent");
26653
28821
  process.exit(1);
26654
28822
  }
26655
28823
  }
28824
+ if (args[0] === "verify-key") {
28825
+ const { runVerifyKey: runVerifyKey2 } = await Promise.resolve().then(() => (init_verify_key(), verify_key_exports));
28826
+ const outputJson = args.includes("--json");
28827
+ const result = await runVerifyKey2(outputJson);
28828
+ process.exit(result.valid ? 0 : 1);
28829
+ }
28830
+ if (args[0] === "update-hooks") {
28831
+ const { installAllEditorHooks: installAllEditorHooks2 } = await Promise.resolve().then(() => (init_hooks_config(), hooks_config_exports));
28832
+ let scope = "global";
28833
+ let projectPath;
28834
+ for (const arg of args.slice(1)) {
28835
+ if (arg === "--scope=project" || arg === "-p") {
28836
+ scope = "project";
28837
+ projectPath = projectPath || process.cwd();
28838
+ } else if (arg === "--scope=global" || arg === "-g") {
28839
+ scope = "global";
28840
+ } else if (arg.startsWith("--path=")) {
28841
+ projectPath = arg.replace("--path=", "");
28842
+ scope = "project";
28843
+ }
28844
+ }
28845
+ const scopeLabel = scope === "project" ? `project (${projectPath || process.cwd()})` : "global";
28846
+ console.error(`Updating hooks for all editors (${scopeLabel})...`);
28847
+ try {
28848
+ const results = await installAllEditorHooks2({
28849
+ scope,
28850
+ projectPath: scope === "project" ? projectPath || process.cwd() : void 0
28851
+ });
28852
+ for (const result of results) {
28853
+ console.error(`\u2713 ${result.editor}: ${result.installed.length} hooks installed`);
28854
+ }
28855
+ console.error("\u2713 Hooks updated successfully");
28856
+ } catch (error) {
28857
+ console.error("Failed to update hooks:", error);
28858
+ process.exit(1);
28859
+ }
28860
+ return;
28861
+ }
26656
28862
  if (!process.env.CONTEXTSTREAM_API_KEY && !process.env.CONTEXTSTREAM_JWT) {
26657
28863
  const saved = await readSavedCredentials();
26658
28864
  if (saved) {