@csmedeiros/codemax 1.0.3 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +4 -20
  2. package/launcher.js +28 -0
  3. package/package.json +15 -121
  4. package/LICENSE +0 -21
  5. package/dist/commands/cursorRules.d.ts +0 -15
  6. package/dist/commands/cursorRules.js +0 -118
  7. package/dist/commands/slashCommands.d.ts +0 -36
  8. package/dist/commands/slashCommands.js +0 -236
  9. package/dist/configuration/configManager.d.ts +0 -32
  10. package/dist/configuration/configManager.js +0 -71
  11. package/dist/configuration/modelContextWindows.d.ts +0 -3
  12. package/dist/configuration/modelContextWindows.js +0 -13
  13. package/dist/conversation/agentGraph.d.ts +0 -203
  14. package/dist/conversation/agentGraph.js +0 -433
  15. package/dist/conversation/agentTurn.d.ts +0 -40
  16. package/dist/conversation/agentTurn.js +0 -252
  17. package/dist/conversation/chatHistory.d.ts +0 -24
  18. package/dist/conversation/chatHistory.js +0 -251
  19. package/dist/conversation/compactionUtils.d.ts +0 -17
  20. package/dist/conversation/compactionUtils.js +0 -57
  21. package/dist/conversation/prompts/planPrompt.d.ts +0 -1
  22. package/dist/conversation/prompts/planPrompt.js +0 -12
  23. package/dist/conversation/prompts/systemPrompt.d.ts +0 -29
  24. package/dist/conversation/prompts/systemPrompt.js +0 -149
  25. package/dist/entry/cli.d.ts +0 -2
  26. package/dist/entry/cli.js +0 -24
  27. package/dist/observability/langfuseTracing.d.ts +0 -5
  28. package/dist/observability/langfuseTracing.js +0 -76
  29. package/dist/shared/types.d.ts +0 -25
  30. package/dist/shared/types.js +0 -1
  31. package/dist/terminal/app.d.ts +0 -2
  32. package/dist/terminal/app.js +0 -1236
  33. package/dist/terminal/components.d.ts +0 -18
  34. package/dist/terminal/components.js +0 -43
  35. package/dist/terminal/markdown.d.ts +0 -4
  36. package/dist/terminal/markdown.js +0 -47
  37. package/dist/terminal/screens/compactionSettings.d.ts +0 -6
  38. package/dist/terminal/screens/compactionSettings.js +0 -66
  39. package/dist/terminal/screens/modelSettings.d.ts +0 -10
  40. package/dist/terminal/screens/modelSettings.js +0 -76
  41. package/dist/terminal/textField.d.ts +0 -7
  42. package/dist/terminal/textField.js +0 -136
  43. package/dist/terminal/theme.d.ts +0 -23
  44. package/dist/terminal/theme.js +0 -23
  45. package/dist/tooling/mcpConfig.d.ts +0 -40
  46. package/dist/tooling/mcpConfig.js +0 -49
  47. package/dist/tooling/planControlChannel.d.ts +0 -2
  48. package/dist/tooling/planControlChannel.js +0 -21
  49. package/dist/tooling/toolConfig.d.ts +0 -42
  50. package/dist/tooling/toolConfig.js +0 -138
  51. package/dist/tooling/toolUiCallback.d.ts +0 -21
  52. package/dist/tooling/toolUiCallback.js +0 -268
  53. package/dist/tooling/tools.d.ts +0 -216
  54. package/dist/tooling/tools.js +0 -614
@@ -1,18 +0,0 @@
1
- import React from 'react';
2
- export type Todo = {
3
- id: string;
4
- task: string;
5
- status: 'pending' | 'in_progress' | 'completed';
6
- };
7
- export declare function truncateToWidth(text: string, width: number): string;
8
- export declare function TodoPanel({ todos, width }: {
9
- todos: Todo[];
10
- width: number;
11
- }): React.JSX.Element | null;
12
- export declare function SlashCommandModal({ suggestions, selectedIndex, }: {
13
- suggestions: ReadonlyArray<{
14
- name: string;
15
- description: string;
16
- }>;
17
- selectedIndex: number;
18
- }): React.JSX.Element | null;
@@ -1,43 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { THEME, DASHED_BORDER } from './theme.js';
4
- export function truncateToWidth(text, width) {
5
- if (width <= 0)
6
- return '';
7
- if (text.length <= width)
8
- return text;
9
- if (width === 1)
10
- return '…';
11
- return `${text.slice(0, Math.max(0, width - 1))}…`;
12
- }
13
- export function TodoPanel({ todos, width }) {
14
- if (todos.length === 0)
15
- return null;
16
- const statusMarkers = {
17
- pending: '○',
18
- in_progress: '◌',
19
- completed: '●',
20
- };
21
- return (React.createElement(Box, { flexDirection: "column", borderStyle: DASHED_BORDER, borderColor: THEME.copper, paddingX: 2, paddingY: 1, width: width, marginBottom: 1 },
22
- React.createElement(Text, { color: THEME.copper, bold: true }, "Current Tasks"),
23
- todos.map((todo, i) => (React.createElement(Box, { key: i },
24
- React.createElement(Text, { color: todo.status === 'completed' ? THEME.muted : THEME.fg },
25
- statusMarkers[todo.status] || '•',
26
- ' ',
27
- truncateToWidth(todo.task, width - 10)))))));
28
- }
29
- export function SlashCommandModal({ suggestions, selectedIndex, }) {
30
- if (!suggestions.length)
31
- return null;
32
- const maxNameLen = Math.max(0, ...suggestions.map(s => s.name.length));
33
- return (React.createElement(Box, { flexDirection: "column", paddingX: 1 },
34
- suggestions.map((cmd, i) => {
35
- const namePadded = `/${cmd.name}`.padEnd(maxNameLen + 2);
36
- const isSelected = i === selectedIndex;
37
- return (React.createElement(Box, { key: cmd.name },
38
- React.createElement(Text, { inverse: isSelected, color: isSelected ? undefined : THEME.copper }, namePadded),
39
- React.createElement(Text, { inverse: isSelected, color: isSelected ? undefined : THEME.muted }, cmd.description)));
40
- }),
41
- React.createElement(Box, null,
42
- React.createElement(Text, { color: THEME.muted }, " \u2193\u2191 navigate \u21B5 select Esc dismiss"))));
43
- }
@@ -1,4 +0,0 @@
1
- /**
2
- * Markdown → ANSI para o terminal (bold, listas, blocos de código com highlight, etc.).
3
- */
4
- export declare function renderAssistantMarkdown(source: string): string;
@@ -1,47 +0,0 @@
1
- import { marked } from 'marked';
2
- import { markedTerminal } from 'marked-terminal';
3
- import { appendFileSync } from 'node:fs';
4
- import { getConfig } from '../configuration/configManager.js';
5
- let configured = false;
6
- function debugLog(message) {
7
- if (!getConfig().debugEvents)
8
- return;
9
- const ts = new Date().toISOString();
10
- const line = `[CodeMax][markdown][${ts}] ${message}\n`;
11
- try {
12
- const filePath = (process.env['CODEMAX_DEBUG_LOG_FILE'] ?? '').trim() ||
13
- '/tmp/codemax-debug.log';
14
- appendFileSync(filePath, line, { encoding: 'utf8' });
15
- }
16
- catch {
17
- // ignore
18
- }
19
- }
20
- /**
21
- * Markdown → ANSI para o terminal (bold, listas, blocos de código com highlight, etc.).
22
- */
23
- export function renderAssistantMarkdown(source) {
24
- const width = Math.max(40, Math.min(120, process.stdout.columns ?? 80));
25
- if (!configured) {
26
- marked.use(markedTerminal({
27
- width,
28
- reflowText: true,
29
- emoji: true,
30
- /** Sem isto, headings aparecem como `## Título` em texto; queremos só estilo ANSI. */
31
- showSectionPrefix: false,
32
- }));
33
- configured = true;
34
- }
35
- const text = source.trimEnd();
36
- if (!text)
37
- return '';
38
- try {
39
- const out = marked.parse(text, { async: false });
40
- debugLog(`parsed len=${out.length}`);
41
- return out.endsWith('\n') ? out : `${out}\n`;
42
- }
43
- catch (error) {
44
- debugLog(`parse error: ${error}`);
45
- return `${text}\n`;
46
- }
47
- }
@@ -1,6 +0,0 @@
1
- import React from 'react';
2
- export declare function CompactionSettingsScreen({ width, onSave, onCancel, }: {
3
- width: number;
4
- onSave: (pct: number) => void;
5
- onCancel: () => void;
6
- }): React.JSX.Element;
@@ -1,66 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { getConfig, updateConfig } from '../../configuration/configManager.js';
4
- import { THEME, DASHED_BORDER } from '../theme.js';
5
- import { getContextWindow } from '../../configuration/modelContextWindows.js';
6
- const STEP = 10;
7
- function clampPct(v) {
8
- if (v < 0)
9
- return 0;
10
- if (v > 100)
11
- return 100;
12
- return Math.round(v / STEP) * STEP;
13
- }
14
- export function CompactionSettingsScreen({ width, onSave, onCancel, }) {
15
- const initial = getConfig();
16
- const [pct, setPct] = useState(clampPct(Math.round(initial.compactionThreshold * 100)));
17
- const window = getContextWindow(initial.modelName);
18
- useInput((_input, key) => {
19
- if (key.escape) {
20
- onCancel();
21
- return;
22
- }
23
- if (key.return) {
24
- updateConfig({ compactionThreshold: pct / 100 });
25
- onSave(pct);
26
- return;
27
- }
28
- if (key.upArrow || key.rightArrow) {
29
- setPct(p => clampPct(p + STEP));
30
- return;
31
- }
32
- if (key.downArrow || key.leftArrow) {
33
- setPct(p => clampPct(p - STEP));
34
- return;
35
- }
36
- });
37
- const triggerAt = Math.floor((pct / 100) * window).toLocaleString();
38
- const bars = Math.round(pct / STEP);
39
- const meter = '█'.repeat(bars) + '░'.repeat(10 - bars);
40
- return (React.createElement(Box, { flexDirection: "column", width: width, borderStyle: DASHED_BORDER, borderColor: THEME.copper, paddingX: 2, paddingY: 1 },
41
- React.createElement(Box, { marginBottom: 1 },
42
- React.createElement(Text, { color: THEME.copper, bold: true }, "Compaction Threshold")),
43
- React.createElement(Box, null,
44
- React.createElement(Text, { color: THEME.green },
45
- '> ',
46
- "Threshold: "),
47
- React.createElement(Text, { color: THEME.fg, bold: true },
48
- pct,
49
- "%"),
50
- React.createElement(Text, { color: THEME.muted }, ` [${meter}]`)),
51
- React.createElement(Box, { marginTop: 1 },
52
- React.createElement(Text, { color: THEME.muted },
53
- "Model: ",
54
- initial.modelName,
55
- " \u00B7 context window:",
56
- ' ',
57
- window.toLocaleString(),
58
- " tokens")),
59
- React.createElement(Box, null,
60
- React.createElement(Text, { color: THEME.muted },
61
- "Auto-compaction triggers at ~",
62
- triggerAt,
63
- " tokens used.")),
64
- React.createElement(Box, { marginTop: 2 },
65
- React.createElement(Text, { color: THEME.muted }, "Use \u2191/\u2192 to increase by 10%, \u2193/\u2190 to decrease. Enter saves, Esc cancels."))));
66
- }
@@ -1,10 +0,0 @@
1
- import React from 'react';
2
- export declare function ModelSettingsScreen({ width, onSave, onCancel, }: {
3
- width: number;
4
- onSave: (cfg: {
5
- modelName: string;
6
- baseURL: string;
7
- apiKey: string;
8
- }) => void;
9
- onCancel: () => void;
10
- }): React.JSX.Element;
@@ -1,76 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Box, Text, useInput } from 'ink';
3
- import { getConfig } from '../../configuration/configManager.js';
4
- import { THEME, DASHED_BORDER } from '../theme.js';
5
- import { TextField } from '../textField.js';
6
- // ink-text-input does not handle bracketed paste: pasted chunks arrive with the
7
- // terminal's start/end markers (\x1b[200~ ... \x1b[201~). Ink strips the leading
8
- // \x1b, so we see a literal '[200~' prefix and a '\x1b[201~' suffix embedded in
9
- // the value. Strip those markers (and any stray control/escape bytes) on every
10
- // change so the saved value is clean.
11
- function sanitizePasteMarkers(value) {
12
- return (value
13
- // \x1b[200~ / \x1b[201~ with or without the (Ink-stripped) leading ESC,
14
- // and the 8-bit CSI form \x9b.
15
- // eslint-disable-next-line no-control-regex
16
- .replace(/(?:\x1b|\x9b)?\[20[01]~/g, '')
17
- // Any remaining control characters (stray ESC, etc.).
18
- // eslint-disable-next-line no-control-regex
19
- .replace(/[\x00-\x1f\x7f-\x9f]/g, ''));
20
- }
21
- export function ModelSettingsScreen({ width, onSave, onCancel, }) {
22
- const initial = getConfig();
23
- const [modelName, setModelName] = useState(initial.modelName);
24
- const [baseURL, setBaseURL] = useState(initial.baseURL);
25
- const [apiKey, setApiKey] = useState(initial.apiKey);
26
- const [focusIdx, setFocusIdx] = useState(0);
27
- useInput((_input, key) => {
28
- if (key.escape) {
29
- onCancel();
30
- return;
31
- }
32
- if (key.return) {
33
- onSave({
34
- modelName: sanitizePasteMarkers(modelName),
35
- baseURL: sanitizePasteMarkers(baseURL),
36
- apiKey: sanitizePasteMarkers(apiKey),
37
- });
38
- return;
39
- }
40
- if (key.upArrow) {
41
- setFocusIdx(i => Math.max(0, i - 1));
42
- return;
43
- }
44
- if (key.downArrow) {
45
- setFocusIdx(i => Math.min(2, i + 1));
46
- return;
47
- }
48
- if (key.tab) {
49
- setFocusIdx(i => (i + 1) % 3);
50
- return;
51
- }
52
- });
53
- return (React.createElement(Box, { flexDirection: "column", width: width, borderStyle: DASHED_BORDER, borderColor: THEME.copper, paddingX: 2, paddingY: 1 },
54
- React.createElement(Box, { marginBottom: 1 },
55
- React.createElement(Text, { color: THEME.copper, bold: true }, "CodeMax Model Configuration")),
56
- React.createElement(Box, null,
57
- React.createElement(Text, { color: focusIdx === 0 ? THEME.green : THEME.muted },
58
- focusIdx === 0 ? '> ' : ' ',
59
- "Model Name:",
60
- ' '),
61
- React.createElement(TextField, { focus: focusIdx === 0, value: modelName, onChange: setModelName })),
62
- React.createElement(Box, null,
63
- React.createElement(Text, { color: focusIdx === 1 ? THEME.green : THEME.muted },
64
- focusIdx === 1 ? '> ' : ' ',
65
- "Base URL:",
66
- ' '),
67
- React.createElement(TextField, { focus: focusIdx === 1, value: baseURL, onChange: setBaseURL })),
68
- React.createElement(Box, null,
69
- React.createElement(Text, { color: focusIdx === 2 ? THEME.green : THEME.muted },
70
- focusIdx === 2 ? '> ' : ' ',
71
- "API Key:",
72
- ' '),
73
- React.createElement(TextField, { focus: focusIdx === 2, value: apiKey, onChange: setApiKey, mask: "*" })),
74
- React.createElement(Box, { marginTop: 2 },
75
- React.createElement(Text, { color: THEME.muted }, "Use \u2191/\u2193 arrows to navigate. Press Enter to Save. Press Esc to Cancel."))));
76
- }
@@ -1,7 +0,0 @@
1
- import React from 'react';
2
- export declare function TextField({ focus, value, onChange, mask, }: {
3
- focus: boolean;
4
- value: string;
5
- onChange: (value: string) => void;
6
- mask?: string;
7
- }): React.JSX.Element;
@@ -1,136 +0,0 @@
1
- import React, { useEffect, useRef, useState } from 'react';
2
- import { Text, useInput } from 'ink';
3
- import chalk from 'chalk';
4
- // Escape-sequence residuals that Ink leaves in `input` after stripping the
5
- // leading \x1b. These are key events, never printable text — never insert them.
6
- const ESCAPE_RESIDUALS = new Set([
7
- '[13;2u',
8
- 'OM',
9
- '[27;2;13~',
10
- '[1;3D',
11
- '[1;3C',
12
- '[200~',
13
- ]);
14
- // A single-line text input that mirrors the main prompt's editing behavior:
15
- // plain backspace, Alt/Option+Backspace and Ctrl+W word-delete, Alt+Left/Right
16
- // word motion, and bracketed paste. Replaces ink-text-input, which only
17
- // supports single-character backspace.
18
- export function TextField({ focus, value, onChange, mask, }) {
19
- const [cursor, setCursor] = useState(value.length);
20
- const swallowNextDeleteRef = useRef(false);
21
- const inPasteRef = useRef(false);
22
- const pasteBufferRef = useRef('');
23
- // Keep the cursor within bounds when the value changes externally.
24
- useEffect(() => {
25
- setCursor(c => Math.min(c, value.length));
26
- }, [value.length]);
27
- useInput((input, key) => {
28
- // Bracketed paste: Ink strips the leading \x1b but inner \x1b chars remain.
29
- // Full chunk arrives as '[200~<content>\x1b[201~' in a single call.
30
- if (input.startsWith('[200~') || inPasteRef.current) {
31
- if (!inPasteRef.current) {
32
- inPasteRef.current = true;
33
- pasteBufferRef.current = input.slice(5); // strip '[200~'
34
- }
35
- else {
36
- pasteBufferRef.current += input;
37
- }
38
- const endIdx = pasteBufferRef.current.indexOf('\x1b[201~');
39
- if (endIdx !== -1) {
40
- const pasted = pasteBufferRef.current
41
- .slice(0, endIdx)
42
- // eslint-disable-next-line no-control-regex
43
- .replace(/[\x00-\x1f\x7f-\x9f]/g, '');
44
- inPasteRef.current = false;
45
- pasteBufferRef.current = '';
46
- onChange(value.slice(0, cursor) + pasted + value.slice(cursor));
47
- setCursor(c => c + pasted.length);
48
- }
49
- return;
50
- }
51
- if (key.leftArrow && !key.meta) {
52
- setCursor(c => Math.max(0, c - 1));
53
- return;
54
- }
55
- if (key.rightArrow && !key.meta) {
56
- setCursor(c => Math.min(value.length, c + 1));
57
- return;
58
- }
59
- // Alt+Left / Alt+Right: jump by word (macOS style).
60
- if (input === '[1;3D' || (key.meta && input === 'b')) {
61
- setCursor(c => {
62
- let i = c - 1;
63
- while (i > 0 && /\s/.test(value[i - 1]))
64
- i--;
65
- while (i > 0 && !/\s/.test(value[i - 1]))
66
- i--;
67
- return i;
68
- });
69
- return;
70
- }
71
- if (input === '[1;3C' || (key.meta && input === 'f')) {
72
- setCursor(c => {
73
- let i = c;
74
- while (i < value.length && /\s/.test(value[i]))
75
- i++;
76
- while (i < value.length && !/\s/.test(value[i]))
77
- i++;
78
- return i;
79
- });
80
- return;
81
- }
82
- // Alt+Backspace / Ctrl+W: delete word to the left.
83
- // macOS Terminal: \x1b+w → key.meta=true, input='w'.
84
- // VS Code terminal: Option+Backspace → two events: Ctrl+W then Delete.
85
- // The Delete that follows Ctrl+W must be swallowed; track it with a ref.
86
- if (swallowNextDeleteRef.current && key.delete && !key.meta && !key.ctrl) {
87
- swallowNextDeleteRef.current = false;
88
- return;
89
- }
90
- if ((key.meta && input === 'w') ||
91
- (key.meta && key.backspace) ||
92
- (key.meta && key.delete) ||
93
- (key.ctrl && input === 'w')) {
94
- swallowNextDeleteRef.current = key.ctrl && input === 'w';
95
- if (cursor <= 0)
96
- return;
97
- let i = cursor - 1;
98
- while (i > 0 && /\s/.test(value[i - 1]))
99
- i--;
100
- while (i > 0 && !/\s/.test(value[i - 1]))
101
- i--;
102
- onChange(value.slice(0, i) + value.slice(cursor));
103
- setCursor(i);
104
- return;
105
- }
106
- if (key.backspace || key.delete) {
107
- if (cursor <= 0)
108
- return;
109
- onChange(value.slice(0, cursor - 1) + value.slice(cursor));
110
- setCursor(c => Math.max(0, c - 1));
111
- return;
112
- }
113
- // Ignore unhandled ctrl/meta combos — they are not printable text.
114
- if (key.ctrl || key.meta)
115
- return;
116
- // Printable characters (Ink already filters escape sequences for us).
117
- const isPaste = input.length > 1 && !ESCAPE_RESIDUALS.has(input);
118
- if (input && (input.length === 1 || isPaste)) {
119
- const clean = input.length > 1
120
- ? // eslint-disable-next-line no-control-regex
121
- input.replace(/[\x00-\x1f\x7f-\x9f]/g, '')
122
- : input;
123
- if (!clean)
124
- return;
125
- onChange(value.slice(0, cursor) + clean + value.slice(cursor));
126
- setCursor(c => c + clean.length);
127
- }
128
- }, { isActive: focus });
129
- const shown = mask ? mask.repeat(value.length) : value;
130
- if (!focus) {
131
- return React.createElement(Text, null, shown);
132
- }
133
- const left = shown.slice(0, cursor);
134
- const right = shown.slice(cursor);
135
- return (React.createElement(Text, null, left + chalk.inverse(right.length ? right[0] : ' ') + right.slice(1)));
136
- }
@@ -1,23 +0,0 @@
1
- export declare const THEME: {
2
- readonly bg: "#14181c";
3
- readonly fg: "#e6e6e6";
4
- readonly muted: "#9aa3aa";
5
- readonly copper: "#e09a7a";
6
- readonly green: "#3ddc84";
7
- readonly diffAdd: "#233d2e";
8
- readonly diffRemove: "#3d2323";
9
- readonly diffAddFg: "#3ddc84";
10
- readonly diffRemoveFg: "#e06c75";
11
- };
12
- export declare const DASHED_BORDER: {
13
- readonly topLeft: "┌";
14
- readonly topRight: "┐";
15
- readonly bottomLeft: "└";
16
- readonly bottomRight: "┘";
17
- readonly horizontal: "┄";
18
- readonly vertical: "┆";
19
- readonly top: "┄";
20
- readonly bottom: "┄";
21
- readonly left: "┆";
22
- readonly right: "┆";
23
- };
@@ -1,23 +0,0 @@
1
- export const THEME = {
2
- bg: '#14181c',
3
- fg: '#e6e6e6',
4
- muted: '#9aa3aa',
5
- copper: '#e09a7a',
6
- green: '#3ddc84',
7
- diffAdd: '#233d2e',
8
- diffRemove: '#3d2323',
9
- diffAddFg: '#3ddc84',
10
- diffRemoveFg: '#e06c75',
11
- };
12
- export const DASHED_BORDER = {
13
- topLeft: '┌',
14
- topRight: '┐',
15
- bottomLeft: '└',
16
- bottomRight: '┘',
17
- horizontal: '┄',
18
- vertical: '┆',
19
- top: '┄',
20
- bottom: '┄',
21
- left: '┆',
22
- right: '┆',
23
- };
@@ -1,40 +0,0 @@
1
- import { z } from 'zod';
2
- declare const McpServerSchema: z.ZodUnion<[z.ZodObject<{
3
- url: z.ZodString;
4
- transport: z.ZodOptional<z.ZodEnum<["http", "sse"]>>;
5
- headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
6
- }, "strip", z.ZodTypeAny, {
7
- url: string;
8
- transport?: "http" | "sse" | undefined;
9
- headers?: Record<string, string> | undefined;
10
- }, {
11
- url: string;
12
- transport?: "http" | "sse" | undefined;
13
- headers?: Record<string, string> | undefined;
14
- }>, z.ZodObject<{
15
- command: z.ZodString;
16
- args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
17
- env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
18
- transport: z.ZodOptional<z.ZodLiteral<"stdio">>;
19
- }, "strip", z.ZodTypeAny, {
20
- command: string;
21
- transport?: "stdio" | undefined;
22
- args?: string[] | undefined;
23
- env?: Record<string, string> | undefined;
24
- }, {
25
- command: string;
26
- transport?: "stdio" | undefined;
27
- args?: string[] | undefined;
28
- env?: Record<string, string> | undefined;
29
- }>]>;
30
- export type McpServerConfig = z.infer<typeof McpServerSchema>;
31
- export type McpServersMap = Record<string, McpServerConfig>;
32
- export type McpServerStatus = {
33
- name: string;
34
- config: McpServerConfig;
35
- status: 'connected' | 'unauthenticated' | 'unavailable';
36
- toolCount: number;
37
- error?: string;
38
- };
39
- export declare function loadMcpServers(): McpServersMap;
40
- export {};
@@ -1,49 +0,0 @@
1
- import { existsSync, readFileSync } from 'node:fs';
2
- import { homedir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { z } from 'zod';
5
- const HttpServerSchema = z.object({
6
- url: z.string(),
7
- transport: z.enum(['http', 'sse']).optional(),
8
- headers: z.record(z.string()).optional(),
9
- });
10
- const StdioServerSchema = z.object({
11
- command: z.string(),
12
- args: z.array(z.string()).optional(),
13
- env: z.record(z.string()).optional(),
14
- transport: z.literal('stdio').optional(),
15
- });
16
- const McpServerSchema = z.union([HttpServerSchema, StdioServerSchema]);
17
- const McpConfigSchema = z.object({
18
- mcpServers: z.record(McpServerSchema).default({}),
19
- });
20
- function loadMcpConfigFile(filePath) {
21
- if (!existsSync(filePath))
22
- return {};
23
- try {
24
- const raw = JSON.parse(readFileSync(filePath, 'utf8'));
25
- const parsed = McpConfigSchema.safeParse(raw);
26
- if (!parsed.success) {
27
- process.stderr.write(`[CodeMax] Invalid MCP config at ${filePath}: ${parsed.error.message}\n`);
28
- return {};
29
- }
30
- return parsed.data.mcpServers;
31
- }
32
- catch (e) {
33
- const msg = e instanceof Error ? e.message : String(e);
34
- process.stderr.write(`[CodeMax] Failed to read MCP config at ${filePath}: ${msg}\n`);
35
- return {};
36
- }
37
- }
38
- export function loadMcpServers() {
39
- const homePath = join(homedir(), '.codemax', 'mcp.json');
40
- const cwdPath = join(process.cwd(), '.codemax', 'mcp.json');
41
- const homeServers = loadMcpConfigFile(homePath);
42
- const cwdServers = loadMcpConfigFile(cwdPath);
43
- const merged = { ...homeServers, ...cwdServers };
44
- if (Object.keys(merged).length > 0) {
45
- process.stderr.write(`[CodeMax] MCP config: found servers: ${Object.keys(merged).join(', ')}\n`);
46
- }
47
- // cwd takes precedence over home on key collision
48
- return merged;
49
- }
@@ -1,2 +0,0 @@
1
- export declare function registerPlanNonce(nonce: string): void;
2
- export declare function consumePlanNonce(nonce: string): boolean;
@@ -1,21 +0,0 @@
1
- /**
2
- * In-process registry of valid plan-control nonces.
3
- *
4
- * The plan feedback HTTP server (tools.ts) writes control markers to stdout in
5
- * the form `Plan accepted: <nonce>:<name>`. The TUI (terminal/app.tsx) scrapes
6
- * stdout but must NOT act on a marker unless its <nonce> was registered here by
7
- * a real server in THIS process. This prevents arbitrary tool/model output that
8
- * merely contains the marker string from triggering auto-mode or file reads.
9
- *
10
- * Nonces are single-use: consuming one removes it from the registry.
11
- */
12
- const validNonces = new Set();
13
- export function registerPlanNonce(nonce) {
14
- validNonces.add(nonce);
15
- }
16
- export function consumePlanNonce(nonce) {
17
- if (!validNonces.has(nonce))
18
- return false;
19
- validNonces.delete(nonce);
20
- return true;
21
- }
@@ -1,42 +0,0 @@
1
- export type ToolUiConfig = {
2
- /** Printed when the tool is called. */
3
- onCallTemplate: string;
4
- /** Printed when the tool returns successfully. */
5
- onResultTemplate: string;
6
- /** Printed when the tool errors (optional; toolUiCallback has its own default). */
7
- onErrorTemplate?: string;
8
- /** Max chars shown for `input_preview` / `output_preview`. */
9
- maxInputPreview: number;
10
- maxOutputPreview: number;
11
- /** Extra computed placeholders available to templates. */
12
- placeholders?: Record<string, (vars: ToolUiVars) => string>;
13
- };
14
- export type ToolUiVars = {
15
- tool_name: string;
16
- tool_call_id: string;
17
- run_id: string;
18
- input_raw: string;
19
- output_raw: string;
20
- input_preview: string;
21
- output_preview: string;
22
- file_path: string;
23
- file_name: string;
24
- command: string;
25
- cwd: string;
26
- };
27
- export declare function toolNameFromSerialized(tool: {
28
- id?: string[];
29
- kwargs?: Record<string, unknown>;
30
- }, runName: string): string;
31
- export declare function buildToolUiVars(args: {
32
- toolName: string;
33
- input: string;
34
- output: string;
35
- toolCallId?: string;
36
- runId: string;
37
- maxInputPreview: number;
38
- maxOutputPreview: number;
39
- placeholders?: Record<string, (vars: ToolUiVars) => string>;
40
- }): ToolUiVars & Record<string, string>;
41
- export declare function renderToolTemplate(template: string, vars: Record<string, string>): string;
42
- export declare function resolveToolUi(toolName: string): ToolUiConfig;