mstro-app 0.3.1 → 0.3.5

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 (36) hide show
  1. package/README.md +3 -19
  2. package/bin/mstro.js +15 -177
  3. package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
  4. package/dist/server/mcp/bouncer-integration.js +43 -135
  5. package/dist/server/mcp/bouncer-integration.js.map +1 -1
  6. package/dist/server/services/platform.d.ts.map +1 -1
  7. package/dist/server/services/platform.js +2 -13
  8. package/dist/server/services/platform.js.map +1 -1
  9. package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
  10. package/dist/server/services/websocket/file-explorer-handlers.js +17 -1
  11. package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
  12. package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
  13. package/dist/server/services/websocket/file-utils.js +26 -20
  14. package/dist/server/services/websocket/file-utils.js.map +1 -1
  15. package/dist/server/services/websocket/types.d.ts +1 -1
  16. package/dist/server/services/websocket/types.d.ts.map +1 -1
  17. package/dist/server/utils/paths.d.ts +0 -12
  18. package/dist/server/utils/paths.d.ts.map +1 -1
  19. package/dist/server/utils/paths.js +0 -12
  20. package/dist/server/utils/paths.js.map +1 -1
  21. package/package.json +1 -2
  22. package/server/README.md +0 -1
  23. package/server/mcp/README.md +0 -5
  24. package/server/mcp/bouncer-integration.ts +55 -210
  25. package/server/services/platform.ts +2 -12
  26. package/server/services/websocket/file-explorer-handlers.ts +16 -1
  27. package/server/services/websocket/file-utils.ts +29 -24
  28. package/server/services/websocket/types.ts +1 -0
  29. package/server/utils/paths.ts +0 -14
  30. package/bin/configure-claude.js +0 -298
  31. package/dist/server/mcp/bouncer-cli.d.ts +0 -3
  32. package/dist/server/mcp/bouncer-cli.d.ts.map +0 -1
  33. package/dist/server/mcp/bouncer-cli.js +0 -138
  34. package/dist/server/mcp/bouncer-cli.js.map +0 -1
  35. package/hooks/bouncer.sh +0 -159
  36. package/server/mcp/bouncer-cli.ts +0 -180
@@ -1,180 +0,0 @@
1
- #!/usr/bin/env node
2
- // Copyright (c) 2025-present Mstro, Inc. All rights reserved.
3
- // Licensed under the MIT License. See LICENSE file for details.
4
-
5
- /**
6
- * Bouncer CLI - Shell-callable wrapper for Mstro security bouncer
7
- *
8
- * This CLI reads Claude Code hook input from stdin and returns a security decision.
9
- * It's designed to be called from bouncer.sh.
10
- *
11
- * Input (stdin): Claude Code PreToolUse hook JSON payload
12
- * Output (stdout): JSON decision { decision: "allow"|"deny", reason: string }
13
- *
14
- * The hook payload includes conversation context that we pass to the bouncer
15
- * so it can make context-aware decisions.
16
- */
17
-
18
- import { type BouncerReviewRequest, reviewOperation } from './bouncer-integration.js';
19
-
20
- interface HookInput {
21
- // Tool identification (mstro: toolName, Claude Code: tool_name)
22
- tool_name?: string;
23
- toolName?: string;
24
- // Tool parameters (mstro: input/toolInput, Claude Code: tool_input)
25
- input?: Record<string, unknown>;
26
- toolInput?: Record<string, unknown>;
27
- tool_input?: Record<string, unknown>;
28
- // Claude Code hook metadata
29
- hook_event_name?: string;
30
- transcript_path?: string;
31
- permission_mode?: string;
32
- cwd?: string;
33
- // Mstro conversation context
34
- session_id?: string;
35
- conversation?: {
36
- messages?: Array<{
37
- role: 'user' | 'assistant';
38
- content: string;
39
- }>;
40
- last_user_message?: string;
41
- };
42
- // Common fields
43
- tool_use_id?: string;
44
- working_directory?: string;
45
- }
46
-
47
- /**
48
- * Read all data from stdin (Node.js compatible)
49
- */
50
- async function readStdin(): Promise<string> {
51
- return new Promise((resolve, reject) => {
52
- const chunks: Buffer[] = [];
53
- process.stdin.on('data', (chunk) => chunks.push(chunk));
54
- process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8').trim()));
55
- process.stdin.on('error', reject);
56
- });
57
- }
58
-
59
- function buildOperationString(toolName: string, toolInput: Record<string, unknown>): string {
60
- if (toolName === 'Bash' && toolInput.command) {
61
- return `${toolName}: ${toolInput.command}`;
62
- }
63
- if (['Write', 'Edit', 'Read'].includes(toolName)) {
64
- const filePath = toolInput.file_path || toolInput.filePath || toolInput.path;
65
- return filePath ? `${toolName}: ${filePath}` : `${toolName}: ${JSON.stringify(toolInput)}`;
66
- }
67
- return `${toolName}: ${JSON.stringify(toolInput)}`;
68
- }
69
-
70
- /**
71
- * Detect whether the caller is Claude Code (vs mstro).
72
- * Claude Code includes hook_event_name in its payload.
73
- */
74
- function isClaudeCodeHook(hookInput: HookInput): boolean {
75
- return hookInput.hook_event_name === 'PreToolUse';
76
- }
77
-
78
- /**
79
- * Format a bouncer decision for the calling system.
80
- * Claude Code expects: { hookSpecificOutput: { permissionDecision, ... } }
81
- * Mstro expects: { decision, reason, confidence, threatLevel, alternative }
82
- */
83
- function formatDecisionOutput(
84
- decision: { decision: string; reasoning: string; confidence?: number; threatLevel?: string; alternative?: string },
85
- claudeCode: boolean
86
- ): string {
87
- const mappedDecision = decision.decision === 'deny' ? 'deny' : 'allow';
88
- if (claudeCode) {
89
- return JSON.stringify({
90
- hookSpecificOutput: {
91
- hookEventName: 'PreToolUse',
92
- permissionDecision: mappedDecision,
93
- permissionDecisionReason: decision.reasoning,
94
- },
95
- });
96
- }
97
- return JSON.stringify({
98
- decision: mappedDecision,
99
- reason: decision.reasoning,
100
- confidence: decision.confidence,
101
- threatLevel: decision.threatLevel,
102
- alternative: decision.alternative,
103
- });
104
- }
105
-
106
- function formatSimpleOutput(d: 'allow' | 'deny', reason: string, claudeCode: boolean): string {
107
- if (claudeCode) {
108
- return JSON.stringify({
109
- hookSpecificOutput: {
110
- hookEventName: 'PreToolUse',
111
- permissionDecision: d,
112
- permissionDecisionReason: reason,
113
- },
114
- });
115
- }
116
- return JSON.stringify({ decision: d, reason });
117
- }
118
-
119
- function extractConversationContext(hookInput: HookInput): string | undefined {
120
- const lastUserMessage = hookInput.conversation?.last_user_message;
121
- if (lastUserMessage) return `User's request: "${lastUserMessage}"`;
122
-
123
- const recentMessages = hookInput.conversation?.messages?.slice(-5);
124
- if (recentMessages?.length) {
125
- return `Recent conversation:\n${recentMessages.map(m => `${m.role}: ${m.content}`).join('\n')}`;
126
- }
127
- return undefined;
128
- }
129
-
130
- async function main() {
131
- const inputStr = await readStdin();
132
-
133
- if (!inputStr) {
134
- // Can't detect caller without input — output both-compatible allow
135
- console.log(JSON.stringify({ decision: 'allow', reason: 'Empty input, allowing' }));
136
- process.exit(0);
137
- }
138
-
139
- let hookInput: HookInput;
140
- try {
141
- hookInput = JSON.parse(inputStr);
142
- } catch (e) {
143
- console.error('[bouncer-cli] Failed to parse input JSON:', e);
144
- console.log(JSON.stringify({ decision: 'allow', reason: 'Invalid JSON input, allowing' }));
145
- process.exit(0);
146
- }
147
-
148
- const claudeCode = isClaudeCodeHook(hookInput);
149
- const toolName = hookInput.tool_name || hookInput.toolName || 'unknown';
150
- // Claude Code: tool_input, mstro: input/toolInput
151
- const toolInput = hookInput.tool_input || hookInput.input || hookInput.toolInput || {};
152
- const userRequestContext = extractConversationContext(hookInput);
153
- const lastUserMessage = hookInput.conversation?.last_user_message;
154
- const recentMessages = hookInput.conversation?.messages?.slice(-5);
155
-
156
- const bouncerRequest: BouncerReviewRequest = {
157
- operation: buildOperationString(toolName, toolInput),
158
- context: {
159
- purpose: userRequestContext || 'Tool use request from Claude',
160
- // Claude Code: cwd, mstro: working_directory
161
- workingDirectory: hookInput.cwd || hookInput.working_directory || process.cwd(),
162
- toolName,
163
- toolInput,
164
- userRequest: lastUserMessage,
165
- conversationHistory: recentMessages?.map(m => `${m.role}: ${m.content}`),
166
- sessionId: hookInput.session_id,
167
- },
168
- };
169
-
170
- try {
171
- const decision = await reviewOperation(bouncerRequest);
172
- console.log(formatDecisionOutput(decision, claudeCode));
173
- } catch (error: unknown) {
174
- const message = error instanceof Error ? error.message : String(error);
175
- console.error('[bouncer-cli] Error:', message);
176
- console.log(formatSimpleOutput('allow', `Bouncer error: ${message}. Allowing to avoid blocking.`, claudeCode));
177
- }
178
- }
179
-
180
- main();