claude-threads 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +473 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/dist/changelog.d.ts +20 -0
- package/dist/changelog.js +134 -0
- package/dist/claude/cli.d.ts +42 -0
- package/dist/claude/cli.js +173 -0
- package/dist/claude/session.d.ts +256 -0
- package/dist/claude/session.js +1964 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +94 -0
- package/dist/git/worktree.d.ts +50 -0
- package/dist/git/worktree.js +228 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +371 -0
- package/dist/logo.d.ts +31 -0
- package/dist/logo.js +57 -0
- package/dist/mattermost/api.d.ts +85 -0
- package/dist/mattermost/api.js +124 -0
- package/dist/mattermost/api.test.d.ts +1 -0
- package/dist/mattermost/api.test.js +319 -0
- package/dist/mattermost/client.d.ts +56 -0
- package/dist/mattermost/client.js +321 -0
- package/dist/mattermost/emoji.d.ts +43 -0
- package/dist/mattermost/emoji.js +65 -0
- package/dist/mattermost/emoji.test.d.ts +1 -0
- package/dist/mattermost/emoji.test.js +131 -0
- package/dist/mattermost/types.d.ts +71 -0
- package/dist/mattermost/types.js +1 -0
- package/dist/mcp/permission-server.d.ts +2 -0
- package/dist/mcp/permission-server.js +201 -0
- package/dist/onboarding.d.ts +1 -0
- package/dist/onboarding.js +116 -0
- package/dist/persistence/session-store.d.ts +65 -0
- package/dist/persistence/session-store.js +127 -0
- package/dist/update-notifier.d.ts +3 -0
- package/dist/update-notifier.js +31 -0
- package/dist/utils/logger.d.ts +34 -0
- package/dist/utils/logger.js +42 -0
- package/dist/utils/logger.test.d.ts +1 -0
- package/dist/utils/logger.test.js +121 -0
- package/dist/utils/tool-formatter.d.ts +56 -0
- package/dist/utils/tool-formatter.js +247 -0
- package/dist/utils/tool-formatter.test.d.ts +1 -0
- package/dist/utils/tool-formatter.test.js +357 -0
- package/package.json +85 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree information for a session
|
|
3
|
+
*/
|
|
4
|
+
export interface WorktreeInfo {
|
|
5
|
+
repoRoot: string;
|
|
6
|
+
worktreePath: string;
|
|
7
|
+
branch: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Persisted session state for resuming after bot restart
|
|
11
|
+
*/
|
|
12
|
+
export interface PersistedSession {
|
|
13
|
+
threadId: string;
|
|
14
|
+
claudeSessionId: string;
|
|
15
|
+
startedBy: string;
|
|
16
|
+
startedAt: string;
|
|
17
|
+
sessionNumber: number;
|
|
18
|
+
workingDir: string;
|
|
19
|
+
sessionAllowedUsers: string[];
|
|
20
|
+
forceInteractivePermissions: boolean;
|
|
21
|
+
sessionStartPostId: string | null;
|
|
22
|
+
tasksPostId: string | null;
|
|
23
|
+
lastActivityAt: string;
|
|
24
|
+
planApproved: boolean;
|
|
25
|
+
worktreeInfo?: WorktreeInfo;
|
|
26
|
+
pendingWorktreePrompt?: boolean;
|
|
27
|
+
worktreePromptDisabled?: boolean;
|
|
28
|
+
queuedPrompt?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* SessionStore - Persistence layer for session state
|
|
32
|
+
* Stores session data as JSON file for resume after restart
|
|
33
|
+
*/
|
|
34
|
+
export declare class SessionStore {
|
|
35
|
+
private debug;
|
|
36
|
+
constructor();
|
|
37
|
+
/**
|
|
38
|
+
* Load all persisted sessions
|
|
39
|
+
*/
|
|
40
|
+
load(): Map<string, PersistedSession>;
|
|
41
|
+
/**
|
|
42
|
+
* Save a session (creates or updates)
|
|
43
|
+
*/
|
|
44
|
+
save(threadId: string, session: PersistedSession): void;
|
|
45
|
+
/**
|
|
46
|
+
* Remove a session
|
|
47
|
+
*/
|
|
48
|
+
remove(threadId: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Remove sessions older than maxAgeMs
|
|
51
|
+
*/
|
|
52
|
+
cleanStale(maxAgeMs: number): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Clear all sessions
|
|
55
|
+
*/
|
|
56
|
+
clear(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Load raw data from file
|
|
59
|
+
*/
|
|
60
|
+
private loadRaw;
|
|
61
|
+
/**
|
|
62
|
+
* Write data atomically (write to temp file, then rename)
|
|
63
|
+
*/
|
|
64
|
+
private writeAtomic;
|
|
65
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
const STORE_VERSION = 1;
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.config', 'claude-threads');
|
|
6
|
+
const SESSIONS_FILE = join(CONFIG_DIR, 'sessions.json');
|
|
7
|
+
/**
|
|
8
|
+
* SessionStore - Persistence layer for session state
|
|
9
|
+
* Stores session data as JSON file for resume after restart
|
|
10
|
+
*/
|
|
11
|
+
export class SessionStore {
|
|
12
|
+
debug = process.env.DEBUG === '1' || process.argv.includes('--debug');
|
|
13
|
+
constructor() {
|
|
14
|
+
// Ensure config directory exists
|
|
15
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
16
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load all persisted sessions
|
|
21
|
+
*/
|
|
22
|
+
load() {
|
|
23
|
+
const sessions = new Map();
|
|
24
|
+
if (!existsSync(SESSIONS_FILE)) {
|
|
25
|
+
if (this.debug)
|
|
26
|
+
console.log(' [persist] No sessions file found');
|
|
27
|
+
return sessions;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const data = JSON.parse(readFileSync(SESSIONS_FILE, 'utf-8'));
|
|
31
|
+
// Version check for future migrations
|
|
32
|
+
if (data.version !== STORE_VERSION) {
|
|
33
|
+
console.warn(` [persist] Sessions file version mismatch (${data.version} vs ${STORE_VERSION}), starting fresh`);
|
|
34
|
+
return sessions;
|
|
35
|
+
}
|
|
36
|
+
for (const [threadId, session] of Object.entries(data.sessions)) {
|
|
37
|
+
sessions.set(threadId, session);
|
|
38
|
+
}
|
|
39
|
+
if (this.debug) {
|
|
40
|
+
console.log(` [persist] Loaded ${sessions.size} session(s)`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
console.error(' [persist] Failed to load sessions:', err);
|
|
45
|
+
}
|
|
46
|
+
return sessions;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Save a session (creates or updates)
|
|
50
|
+
*/
|
|
51
|
+
save(threadId, session) {
|
|
52
|
+
const data = this.loadRaw();
|
|
53
|
+
data.sessions[threadId] = session;
|
|
54
|
+
this.writeAtomic(data);
|
|
55
|
+
if (this.debug) {
|
|
56
|
+
const shortId = threadId.substring(0, 8);
|
|
57
|
+
console.log(` [persist] Saved session ${shortId}...`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove a session
|
|
62
|
+
*/
|
|
63
|
+
remove(threadId) {
|
|
64
|
+
const data = this.loadRaw();
|
|
65
|
+
if (data.sessions[threadId]) {
|
|
66
|
+
delete data.sessions[threadId];
|
|
67
|
+
this.writeAtomic(data);
|
|
68
|
+
if (this.debug) {
|
|
69
|
+
const shortId = threadId.substring(0, 8);
|
|
70
|
+
console.log(` [persist] Removed session ${shortId}...`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Remove sessions older than maxAgeMs
|
|
76
|
+
*/
|
|
77
|
+
cleanStale(maxAgeMs) {
|
|
78
|
+
const data = this.loadRaw();
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const staleIds = [];
|
|
81
|
+
for (const [threadId, session] of Object.entries(data.sessions)) {
|
|
82
|
+
const lastActivity = new Date(session.lastActivityAt).getTime();
|
|
83
|
+
if (now - lastActivity > maxAgeMs) {
|
|
84
|
+
staleIds.push(threadId);
|
|
85
|
+
delete data.sessions[threadId];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (staleIds.length > 0) {
|
|
89
|
+
this.writeAtomic(data);
|
|
90
|
+
if (this.debug) {
|
|
91
|
+
console.log(` [persist] Cleaned ${staleIds.length} stale session(s)`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return staleIds;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all sessions
|
|
98
|
+
*/
|
|
99
|
+
clear() {
|
|
100
|
+
this.writeAtomic({ version: STORE_VERSION, sessions: {} });
|
|
101
|
+
if (this.debug) {
|
|
102
|
+
console.log(' [persist] Cleared all sessions');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Load raw data from file
|
|
107
|
+
*/
|
|
108
|
+
loadRaw() {
|
|
109
|
+
if (!existsSync(SESSIONS_FILE)) {
|
|
110
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(readFileSync(SESSIONS_FILE, 'utf-8'));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { version: STORE_VERSION, sessions: {} };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Write data atomically (write to temp file, then rename)
|
|
121
|
+
*/
|
|
122
|
+
writeAtomic(data) {
|
|
123
|
+
const tempFile = `${SESSIONS_FILE}.tmp`;
|
|
124
|
+
writeFileSync(tempFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
125
|
+
renameSync(tempFile, SESSIONS_FILE);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import updateNotifier from 'update-notifier';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
6
|
+
let cachedUpdateInfo;
|
|
7
|
+
export function checkForUpdates() {
|
|
8
|
+
if (process.env.NO_UPDATE_NOTIFIER)
|
|
9
|
+
return;
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
12
|
+
const notifier = updateNotifier({
|
|
13
|
+
pkg,
|
|
14
|
+
updateCheckInterval: 1000 * 60 * 30, // Check every 30 minutes
|
|
15
|
+
});
|
|
16
|
+
// Cache for Mattermost notifications
|
|
17
|
+
cachedUpdateInfo = notifier.update;
|
|
18
|
+
// Show CLI notification
|
|
19
|
+
notifier.notify({
|
|
20
|
+
message: `Update available: {currentVersion} → {latestVersion}
|
|
21
|
+
Run: npm install -g claude-threads`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Silently fail - update checking is not critical
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Returns update info if available, for posting to Mattermost
|
|
29
|
+
export function getUpdateInfo() {
|
|
30
|
+
return cachedUpdateInfo;
|
|
31
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple debug logging utility
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent logging across the codebase with:
|
|
5
|
+
* - Configurable prefix for different components
|
|
6
|
+
* - DEBUG environment variable check
|
|
7
|
+
* - stdout vs stderr routing option
|
|
8
|
+
*/
|
|
9
|
+
export interface Logger {
|
|
10
|
+
/** Log a debug message (only when DEBUG=1) */
|
|
11
|
+
debug: (msg: string) => void;
|
|
12
|
+
/** Log an info message (always shown) */
|
|
13
|
+
info: (msg: string) => void;
|
|
14
|
+
/** Log an error message (always shown, to stderr) */
|
|
15
|
+
error: (msg: string) => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a logger with a specific prefix
|
|
19
|
+
*
|
|
20
|
+
* @param prefix - Prefix to add to all messages (e.g., '[MCP]', '[ws]')
|
|
21
|
+
* @param useStderr - If true, use stderr for all output (default: false)
|
|
22
|
+
* @returns Logger object with debug, info, and error methods
|
|
23
|
+
*/
|
|
24
|
+
export declare function createLogger(prefix: string, useStderr?: boolean): Logger;
|
|
25
|
+
/**
|
|
26
|
+
* Pre-configured logger for MCP permission server
|
|
27
|
+
* Uses stderr (required for MCP stdio communication)
|
|
28
|
+
*/
|
|
29
|
+
export declare const mcpLogger: Logger;
|
|
30
|
+
/**
|
|
31
|
+
* Pre-configured logger for WebSocket client
|
|
32
|
+
* Uses stdout with indentation for visual hierarchy
|
|
33
|
+
*/
|
|
34
|
+
export declare const wsLogger: Logger;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple debug logging utility
|
|
3
|
+
*
|
|
4
|
+
* Provides consistent logging across the codebase with:
|
|
5
|
+
* - Configurable prefix for different components
|
|
6
|
+
* - DEBUG environment variable check
|
|
7
|
+
* - stdout vs stderr routing option
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Create a logger with a specific prefix
|
|
11
|
+
*
|
|
12
|
+
* @param prefix - Prefix to add to all messages (e.g., '[MCP]', '[ws]')
|
|
13
|
+
* @param useStderr - If true, use stderr for all output (default: false)
|
|
14
|
+
* @returns Logger object with debug, info, and error methods
|
|
15
|
+
*/
|
|
16
|
+
export function createLogger(prefix, useStderr = false) {
|
|
17
|
+
const isDebug = () => process.env.DEBUG === '1';
|
|
18
|
+
const log = useStderr ? console.error : console.log;
|
|
19
|
+
return {
|
|
20
|
+
debug: (msg) => {
|
|
21
|
+
if (isDebug()) {
|
|
22
|
+
log(`${prefix} ${msg}`);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
info: (msg) => {
|
|
26
|
+
log(`${prefix} ${msg}`);
|
|
27
|
+
},
|
|
28
|
+
error: (msg) => {
|
|
29
|
+
console.error(`${prefix} ${msg}`);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Pre-configured logger for MCP permission server
|
|
35
|
+
* Uses stderr (required for MCP stdio communication)
|
|
36
|
+
*/
|
|
37
|
+
export const mcpLogger = createLogger('[MCP]', true);
|
|
38
|
+
/**
|
|
39
|
+
* Pre-configured logger for WebSocket client
|
|
40
|
+
* Uses stdout with indentation for visual hierarchy
|
|
41
|
+
*/
|
|
42
|
+
export const wsLogger = createLogger(' [ws]', false);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { createLogger, mcpLogger, wsLogger } from './logger.js';
|
|
3
|
+
describe('createLogger', () => {
|
|
4
|
+
let consoleLogSpy;
|
|
5
|
+
let consoleErrorSpy;
|
|
6
|
+
const originalEnv = process.env.DEBUG;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
9
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
10
|
+
delete process.env.DEBUG;
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
consoleLogSpy.mockRestore();
|
|
14
|
+
consoleErrorSpy.mockRestore();
|
|
15
|
+
if (originalEnv !== undefined) {
|
|
16
|
+
process.env.DEBUG = originalEnv;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
delete process.env.DEBUG;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
describe('debug', () => {
|
|
23
|
+
it('does not log when DEBUG is not set', () => {
|
|
24
|
+
const logger = createLogger('[test]');
|
|
25
|
+
logger.debug('test message');
|
|
26
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
27
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
28
|
+
});
|
|
29
|
+
it('logs to stdout when DEBUG=1 and useStderr=false', () => {
|
|
30
|
+
process.env.DEBUG = '1';
|
|
31
|
+
const logger = createLogger('[test]');
|
|
32
|
+
logger.debug('test message');
|
|
33
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[test] test message');
|
|
34
|
+
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
it('logs to stderr when DEBUG=1 and useStderr=true', () => {
|
|
37
|
+
process.env.DEBUG = '1';
|
|
38
|
+
const logger = createLogger('[test]', true);
|
|
39
|
+
logger.debug('test message');
|
|
40
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('[test] test message');
|
|
41
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
42
|
+
});
|
|
43
|
+
it('does not log when DEBUG is set to something other than 1', () => {
|
|
44
|
+
process.env.DEBUG = 'true';
|
|
45
|
+
const logger = createLogger('[test]');
|
|
46
|
+
logger.debug('test message');
|
|
47
|
+
expect(consoleLogSpy).not.toHaveBeenCalled();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('info', () => {
|
|
51
|
+
it('always logs to stdout when useStderr=false', () => {
|
|
52
|
+
const logger = createLogger('[test]');
|
|
53
|
+
logger.info('info message');
|
|
54
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[test] info message');
|
|
55
|
+
});
|
|
56
|
+
it('logs to stderr when useStderr=true', () => {
|
|
57
|
+
const logger = createLogger('[test]', true);
|
|
58
|
+
logger.info('info message');
|
|
59
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('[test] info message');
|
|
60
|
+
});
|
|
61
|
+
it('logs even when DEBUG is not set', () => {
|
|
62
|
+
const logger = createLogger('[test]');
|
|
63
|
+
logger.info('info message');
|
|
64
|
+
expect(consoleLogSpy).toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('error', () => {
|
|
68
|
+
it('always logs to stderr', () => {
|
|
69
|
+
const logger = createLogger('[test]');
|
|
70
|
+
logger.error('error message');
|
|
71
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('[test] error message');
|
|
72
|
+
});
|
|
73
|
+
it('logs to stderr even when useStderr=false', () => {
|
|
74
|
+
const logger = createLogger('[test]', false);
|
|
75
|
+
logger.error('error message');
|
|
76
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('[test] error message');
|
|
77
|
+
});
|
|
78
|
+
it('logs even when DEBUG is not set', () => {
|
|
79
|
+
const logger = createLogger('[test]');
|
|
80
|
+
logger.error('error message');
|
|
81
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('prefix formatting', () => {
|
|
85
|
+
it('includes prefix in debug messages', () => {
|
|
86
|
+
process.env.DEBUG = '1';
|
|
87
|
+
const logger = createLogger('[MyPrefix]');
|
|
88
|
+
logger.debug('my message');
|
|
89
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[MyPrefix] my message');
|
|
90
|
+
});
|
|
91
|
+
it('includes prefix in info messages', () => {
|
|
92
|
+
const logger = createLogger('[MyPrefix]');
|
|
93
|
+
logger.info('my message');
|
|
94
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('[MyPrefix] my message');
|
|
95
|
+
});
|
|
96
|
+
it('includes prefix in error messages', () => {
|
|
97
|
+
const logger = createLogger('[MyPrefix]');
|
|
98
|
+
logger.error('my message');
|
|
99
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('[MyPrefix] my message');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('pre-configured loggers', () => {
|
|
104
|
+
// Note: mcpLogger and wsLogger are module-level singletons created at import time.
|
|
105
|
+
// Since they capture console.log/error at creation, we test their behavior by
|
|
106
|
+
// verifying they have the expected interface and configuration.
|
|
107
|
+
describe('mcpLogger', () => {
|
|
108
|
+
it('has debug, info, and error methods', () => {
|
|
109
|
+
expect(typeof mcpLogger.debug).toBe('function');
|
|
110
|
+
expect(typeof mcpLogger.info).toBe('function');
|
|
111
|
+
expect(typeof mcpLogger.error).toBe('function');
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('wsLogger', () => {
|
|
115
|
+
it('has debug, info, and error methods', () => {
|
|
116
|
+
expect(typeof wsLogger.debug).toBe('function');
|
|
117
|
+
expect(typeof wsLogger.info).toBe('function');
|
|
118
|
+
expect(typeof wsLogger.error).toBe('function');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool formatting utilities for displaying Claude tool calls in Mattermost
|
|
3
|
+
*
|
|
4
|
+
* This module provides shared formatting logic used by both:
|
|
5
|
+
* - src/claude/session.ts (main bot)
|
|
6
|
+
* - src/mcp/permission-server.ts (MCP permission handler)
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolInput {
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface FormatOptions {
|
|
12
|
+
/** Include detailed previews (diffs, file content). Default: false */
|
|
13
|
+
detailed?: boolean;
|
|
14
|
+
/** Max command length for Bash. Default: 50 */
|
|
15
|
+
maxCommandLength?: number;
|
|
16
|
+
/** Max path display length. Default: 60 */
|
|
17
|
+
maxPathLength?: number;
|
|
18
|
+
/** Max lines to show in previews. Default: 20 for diff, 6 for content */
|
|
19
|
+
maxPreviewLines?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Shorten a file path for display by replacing home directory with ~
|
|
23
|
+
*/
|
|
24
|
+
export declare function shortenPath(path: string, homeDir?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a tool name is an MCP tool and extract server/tool parts
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseMcpToolName(toolName: string): {
|
|
29
|
+
server: string;
|
|
30
|
+
tool: string;
|
|
31
|
+
} | null;
|
|
32
|
+
/**
|
|
33
|
+
* Format a tool use for display in Mattermost
|
|
34
|
+
*
|
|
35
|
+
* @param toolName - The name of the tool being called
|
|
36
|
+
* @param input - The tool input parameters
|
|
37
|
+
* @param options - Formatting options
|
|
38
|
+
* @returns Formatted string or null if the tool should not be displayed
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatToolUse(toolName: string, input: ToolInput, options?: FormatOptions): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Format tool info for permission prompts (simpler format)
|
|
43
|
+
*
|
|
44
|
+
* @param toolName - The name of the tool
|
|
45
|
+
* @param input - The tool input parameters
|
|
46
|
+
* @returns Formatted string for permission prompts
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatToolForPermission(toolName: string, input: ToolInput): string;
|
|
49
|
+
/**
|
|
50
|
+
* Format Claude in Chrome tool calls
|
|
51
|
+
*
|
|
52
|
+
* @param tool - The Chrome tool name (after mcp__claude-in-chrome__)
|
|
53
|
+
* @param input - The tool input parameters
|
|
54
|
+
* @returns Formatted string for display
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatChromeToolUse(tool: string, input: ToolInput): string;
|