prism-mcp-server 20.0.7 → 20.1.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/README.md +26 -0
- package/dist/boundaries/boundaries.js +2 -2
- package/dist/tools/__tests__/layer1Integration.test.js +97 -5
- package/dist/tools/behavioralVerifierHandler.js +9 -0
- package/dist/tools/prismInferHandler.js +128 -12
- package/dist/utils/entitlements.js +38 -10
- package/dist/utils/groundingVerifier.js +3 -3
- package/dist/utils/inferenceMetrics.js +19 -1
- package/dist/utils/layer1.js +57 -10
- package/dist/utils/modelPicker.js +12 -3
- package/package.json +1 -1
- package/dist/agent/agentTools.js +0 -453
- package/dist/agent/mcpBridge.js +0 -234
- package/dist/agent/platformUtils.js +0 -470
- package/dist/agent/terminalUI.js +0 -198
- package/dist/auth.js +0 -218
- package/dist/darkfactory/cloudDelegate.js +0 -173
- package/dist/plugins/pluginManager.js +0 -199
- package/dist/prism-cloud.js +0 -110
- package/dist/scm/ciPipeline.js +0 -220
- package/dist/sync/encryptedSync.js +0 -172
- package/dist/sync/synaluxProxy.js +0 -177
- package/dist/tools/adaptiveDefinitions.js +0 -148
- package/dist/tools/interactiveDefinitions.js +0 -87
- package/dist/tools/interactiveHandlers.js +0 -164
- package/dist/tools/projects.js +0 -214
- package/dist/utils/changelogGenerator.js +0 -158
- package/dist/utils/fallbackClient.js +0 -52
- package/dist/utils/memoryAttestation.js +0 -163
- package/dist/utils/rbac.js +0 -321
- package/dist/utils/tavilyApi.js +0 -70
- package/dist/vm/quotaEnforcer.js +0 -192
package/dist/utils/layer1.js
CHANGED
|
@@ -5,9 +5,14 @@
|
|
|
5
5
|
* a prompt is OBVIOUS_RESERVED, OBVIOUS_NOT_RESERVED, or UNCERTAIN.
|
|
6
6
|
*
|
|
7
7
|
* Fail-closed contract:
|
|
8
|
-
* OBVIOUS_NOT_RESERVED →
|
|
8
|
+
* OBVIOUS_NOT_RESERVED → permits local routing
|
|
9
9
|
* OBVIOUS_RESERVED → escalate to cloud
|
|
10
10
|
* UNCERTAIN → escalate to cloud (conservative)
|
|
11
|
+
* UNCERTAIN_LENGTH → §5.3: prompt too long to classify in full, but the
|
|
12
|
+
* full-text keyword floor is clean AND a head+tail
|
|
13
|
+
* excerpt classified clean — permits local routing
|
|
14
|
+
* with a distinct audit marker ("too long to classify"
|
|
15
|
+
* ≠ "semantically uncertain")
|
|
11
16
|
* ERROR → escalate to cloud (never fail-open)
|
|
12
17
|
*
|
|
13
18
|
* The prompt below is VERBATIM from §E of prism-infer-boundaries/SKILL.md.
|
|
@@ -66,7 +71,7 @@ const LAYER1_RETRY_TIMEOUT_MS = 5_000;
|
|
|
66
71
|
// Not sufficient alone (adversaries can paraphrase), but as an ERROR-path
|
|
67
72
|
// floor they block the obvious cases that padding/injection attacks
|
|
68
73
|
// would otherwise smuggle through.
|
|
69
|
-
const RESERVED_KEYWORDS = /\b(restrain\w*|seclu(?:sion|d\w*)|physical\s*holds?|containment|self[- ]?harm\w*|suicid\w*|overdos\w*|dos(?:age|ing)\s*(?:mg|schedule)|crisis\s*de[- ]?escalation|meltdown\s*management|elopement\s*incident)\b/i;
|
|
74
|
+
const RESERVED_KEYWORDS = /\b(restrain\w*|seclu(?:sion|d\w*)|physical\s*holds?|(?:prone|supine|basket|therapeutic|manual|two[- ]?person)\s+holds?|hold(?:ing)?\s+(?:the\s+)?(?:client|child|student|patient)\s+down|containment|self[- ]?harm\w*|suicid\w*|overdos\w*|dos(?:age|ing)\s*(?:mg|schedule)|crisis\s*de[- ]?escalation|meltdown\s*management|rage\s+episode|elopement\s*incident)\b/i;
|
|
70
75
|
/**
|
|
71
76
|
* Deterministic keyword check — the ERROR-path floor.
|
|
72
77
|
* Returns OBVIOUS_RESERVED if reserved vocabulary is present,
|
|
@@ -77,21 +82,54 @@ export function keywordBackstop(prompt) {
|
|
|
77
82
|
return RESERVED_KEYWORDS.test(prompt) ? "OBVIOUS_RESERVED" : "OBVIOUS_NOT_RESERVED";
|
|
78
83
|
}
|
|
79
84
|
// Over-length prompts are attacker-controlled — don't let length
|
|
80
|
-
// select the ERROR branch.
|
|
81
|
-
//
|
|
85
|
+
// select the ERROR branch. §5.3: instead of a blanket UNCERTAIN (which
|
|
86
|
+
// routed every big benign prompt to cloud-or-refuse — v1 FATAL #3), run
|
|
87
|
+
// the deterministic keyword floor over the FULL text, then classify a
|
|
88
|
+
// bounded head+tail excerpt. A clean floor + clean excerpt yields the
|
|
89
|
+
// distinct UNCERTAIN_LENGTH verdict so callers can tell "too long to
|
|
90
|
+
// classify" from "semantically uncertain".
|
|
82
91
|
const MAX_CLASSIFIER_PROMPT_LENGTH = 4_000;
|
|
92
|
+
// Excerpt budget: head + middle + tail must stay under the classifier cap
|
|
93
|
+
// with room for the LAYER1_PROMPT template. The sampled middle window
|
|
94
|
+
// narrows the region an attacker can hide paraphrased reserved content in
|
|
95
|
+
// (adversarial-review finding: keyword-free paraphrases in the unseen
|
|
96
|
+
// middle are the residual gap — the window makes padding placement
|
|
97
|
+
// harder; the full-text keyword floor remains the deterministic net).
|
|
98
|
+
const EXCERPT_HEAD_CHARS = 2_000;
|
|
99
|
+
const EXCERPT_MID_CHARS = 400;
|
|
100
|
+
const EXCERPT_TAIL_CHARS = 1_400;
|
|
101
|
+
/** Bounded head+middle+tail excerpt of an oversize prompt (§5.3). Exported for tests. */
|
|
102
|
+
export function buildOversizeExcerpt(prompt) {
|
|
103
|
+
const midStart = Math.max(EXCERPT_HEAD_CHARS, Math.floor(prompt.length / 2) - EXCERPT_MID_CHARS / 2);
|
|
104
|
+
const middle = prompt.slice(midStart, midStart + EXCERPT_MID_CHARS);
|
|
105
|
+
return (prompt.slice(0, EXCERPT_HEAD_CHARS) +
|
|
106
|
+
"\n[…]\n" +
|
|
107
|
+
middle +
|
|
108
|
+
"\n[…]\n" +
|
|
109
|
+
prompt.slice(-EXCERPT_TAIL_CHARS));
|
|
110
|
+
}
|
|
83
111
|
/**
|
|
84
112
|
* Run the Layer 1 classifier with retry on cold-model timeout.
|
|
85
113
|
* Returns a verdict; never throws.
|
|
86
114
|
*
|
|
87
115
|
* Flow: classify → if ERROR, retry once with longer timeout →
|
|
88
116
|
* if still ERROR, return ERROR (caller uses keywordBackstop).
|
|
117
|
+
*
|
|
118
|
+
* Oversize flow (§5.3): full-text keyword floor → excerpt classification →
|
|
119
|
+
* reserved/uncertain excerpt verdicts keep full reserved handling;
|
|
120
|
+
* a clean excerpt returns UNCERTAIN_LENGTH.
|
|
89
121
|
*/
|
|
90
122
|
export async function callLayer1(userPrompt, ollamaUrl, model, fetchImpl = fetch) {
|
|
91
123
|
if (!userPrompt || !userPrompt.trim())
|
|
92
124
|
return "ERROR";
|
|
93
|
-
|
|
94
|
-
|
|
125
|
+
const oversize = userPrompt.length > MAX_CLASSIFIER_PROMPT_LENGTH;
|
|
126
|
+
if (oversize && keywordBackstop(userPrompt) === "OBVIOUS_RESERVED") {
|
|
127
|
+
// The regex floor has no length limit — reserved vocabulary anywhere
|
|
128
|
+
// in the full text (including the middle the excerpt can't see)
|
|
129
|
+
// short-circuits to reserved handling.
|
|
130
|
+
return "OBVIOUS_RESERVED";
|
|
131
|
+
}
|
|
132
|
+
const classifierInput = oversize ? buildOversizeExcerpt(userPrompt) : userPrompt;
|
|
95
133
|
const classify = async (timeoutMs) => {
|
|
96
134
|
let res;
|
|
97
135
|
try {
|
|
@@ -101,7 +139,7 @@ export async function callLayer1(userPrompt, ollamaUrl, model, fetchImpl = fetch
|
|
|
101
139
|
body: JSON.stringify({
|
|
102
140
|
model,
|
|
103
141
|
messages: [
|
|
104
|
-
{ role: "user", content: LAYER1_PROMPT.replace("{prompt}",
|
|
142
|
+
{ role: "user", content: LAYER1_PROMPT.replace("{prompt}", classifierInput) },
|
|
105
143
|
],
|
|
106
144
|
stream: false,
|
|
107
145
|
think: false,
|
|
@@ -128,7 +166,16 @@ export async function callLayer1(userPrompt, ollamaUrl, model, fetchImpl = fetch
|
|
|
128
166
|
return parseLayer1(text);
|
|
129
167
|
};
|
|
130
168
|
const first = await classify(LAYER1_TIMEOUT_MS);
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
169
|
+
const verdict = first !== "ERROR" ? first : await classify(LAYER1_RETRY_TIMEOUT_MS);
|
|
170
|
+
if (!oversize)
|
|
171
|
+
return verdict;
|
|
172
|
+
// §5.3 oversize mapping:
|
|
173
|
+
// excerpt OBVIOUS_RESERVED / UNCERTAIN → keep full reserved handling
|
|
174
|
+
// excerpt OBVIOUS_NOT_RESERVED → UNCERTAIN_LENGTH (distinct verdict)
|
|
175
|
+
// excerpt ERROR → UNCERTAIN_LENGTH — the deterministic
|
|
176
|
+
// keyword floor already cleared the FULL text above, which is the same
|
|
177
|
+
// floor the normal-size ERROR path falls back to via the caller.
|
|
178
|
+
if (verdict === "OBVIOUS_RESERVED" || verdict === "UNCERTAIN")
|
|
179
|
+
return verdict;
|
|
180
|
+
return "UNCERTAIN_LENGTH";
|
|
134
181
|
}
|
|
@@ -24,12 +24,21 @@ const GB = 1024 ** 3;
|
|
|
24
24
|
/**
|
|
25
25
|
* Tier table, ordered LARGEST → SMALLEST. Picker walks this and returns
|
|
26
26
|
* the first row whose minFreeGb fits within freeBytes.
|
|
27
|
+
*
|
|
28
|
+
* ctxTokens (§5.4): aligned with the LIVE Modelfile `num_ctx` values
|
|
29
|
+
* (verified via `ollama show <tag> --modelfile` on 2026-07-20). The values
|
|
30
|
+
* are inverted from intuition — the 27b/9b Modelfiles bake num_ctx 4096
|
|
31
|
+
* while 4b/2b bake 32768. The tier walk skips tiers whose ctx cannot hold
|
|
32
|
+
* prompt + output (reason: ctx_insufficient) — Ollama otherwise silently
|
|
33
|
+
* truncates the prompt and answers from the fragment. If a Modelfile's
|
|
34
|
+
* num_ctx is ever raised, update the row here (a raise is a measured
|
|
35
|
+
* decision with KV-cache RAM costed — plan v2 §5.4).
|
|
27
36
|
*/
|
|
28
37
|
export const MODEL_TIERS = [
|
|
29
|
-
{ tag: 'prism-coder:27b', weightsGb: 16, minFreeGb: 20, ctxTokens:
|
|
30
|
-
{ tag: 'prism-coder:9b', weightsGb: 5.8, minFreeGb: 8, ctxTokens:
|
|
38
|
+
{ tag: 'prism-coder:27b', weightsGb: 16, minFreeGb: 20, ctxTokens: 4_096 },
|
|
39
|
+
{ tag: 'prism-coder:9b', weightsGb: 5.8, minFreeGb: 8, ctxTokens: 4_096 },
|
|
31
40
|
{ tag: 'prism-coder:4b', weightsGb: 3.4, minFreeGb: 5, ctxTokens: 32_768 },
|
|
32
|
-
{ tag: 'prism-coder:2b', weightsGb: 2.3, minFreeGb: 3, ctxTokens:
|
|
41
|
+
{ tag: 'prism-coder:2b', weightsGb: 2.3, minFreeGb: 3, ctxTokens: 32_768 },
|
|
33
42
|
];
|
|
34
43
|
/**
|
|
35
44
|
* True when `installed` matches `tierTag` either as a bare tag
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "20.0
|
|
3
|
+
"version": "20.1.0",
|
|
4
4
|
"mcpName": "io.github.dcostenco/prism-coder",
|
|
5
5
|
"description": "Prism Coder — Cognitive memory + tool-calling intelligence for AI agents. Mind Palace persistent memory (BFCL Gold Certified, 100% Tool-Call Accuracy, 114 Agent Skills, PHI Guard, Tier Enforcement, Prompt-Based Skill Routing, Zero-Search HDC/HRR retrieval, HRR Semantic Drift Detection across BCBA/Coding/AAC domains, HIPAA-hardened local-first storage, SLERP-optimized GRPO alignment) plus the prism-coder 1.7B–32B open-weights LLM fleet.",
|
|
6
6
|
"module": "index.ts",
|
package/dist/agent/agentTools.js
DELETED
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Tools — Local workspace tools for the Prism Agent Terminal
|
|
3
|
-
* =================================================================
|
|
4
|
-
*
|
|
5
|
-
* Provides file system, shell, and memory access tools that the AI agent
|
|
6
|
-
* can invoke during an interactive `prism prompt` session. These are
|
|
7
|
-
* declared as Gemini Function Declarations and executed locally.
|
|
8
|
-
*
|
|
9
|
-
* Mirrors the Synalux VS Code extension's local-tools.ts capabilities
|
|
10
|
-
* but runs in a terminal context (no VS Code API dependency).
|
|
11
|
-
*/
|
|
12
|
-
import * as fs from "fs";
|
|
13
|
-
import * as path from "path";
|
|
14
|
-
import { exec } from "child_process";
|
|
15
|
-
import { promisify } from "util";
|
|
16
|
-
import { SchemaType } from "@google/generative-ai";
|
|
17
|
-
import { openUrlCommand, fetchUrlCommand, listFilesCommand, searchFilesCommand, resolveCli, IS_WINDOWS, } from "./platformUtils.js";
|
|
18
|
-
const execAsync = promisify(exec);
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// Tool Declarations (Gemini Function Calling Schema)
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
export const AGENT_TOOL_DECLARATIONS = [
|
|
23
|
-
{
|
|
24
|
-
name: "read_file",
|
|
25
|
-
description: "Read the contents of a file. Use when the user asks about code, configs, or any file. Supports optional line range.",
|
|
26
|
-
parameters: {
|
|
27
|
-
type: SchemaType.OBJECT,
|
|
28
|
-
properties: {
|
|
29
|
-
path: {
|
|
30
|
-
type: SchemaType.STRING,
|
|
31
|
-
description: "Absolute or relative file path to read",
|
|
32
|
-
},
|
|
33
|
-
start_line: {
|
|
34
|
-
type: SchemaType.INTEGER,
|
|
35
|
-
description: "Optional start line (1-indexed)",
|
|
36
|
-
},
|
|
37
|
-
end_line: {
|
|
38
|
-
type: SchemaType.INTEGER,
|
|
39
|
-
description: "Optional end line (1-indexed, inclusive)",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
required: ["path"],
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
name: "list_files",
|
|
47
|
-
description: "List files and directories. Use when the user asks about project structure or wants to find files.",
|
|
48
|
-
parameters: {
|
|
49
|
-
type: SchemaType.OBJECT,
|
|
50
|
-
properties: {
|
|
51
|
-
directory: {
|
|
52
|
-
type: SchemaType.STRING,
|
|
53
|
-
description: "Directory path to list (default: current working directory)",
|
|
54
|
-
},
|
|
55
|
-
pattern: {
|
|
56
|
-
type: SchemaType.STRING,
|
|
57
|
-
description: "Optional glob pattern filter (e.g., '**/*.ts')",
|
|
58
|
-
},
|
|
59
|
-
max_depth: {
|
|
60
|
-
type: SchemaType.INTEGER,
|
|
61
|
-
description: "Maximum directory depth to traverse (default: 3)",
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: "search_files",
|
|
68
|
-
description: "Search for text or patterns across files using ripgrep. Use when the user wants to find where something is defined or used.",
|
|
69
|
-
parameters: {
|
|
70
|
-
type: SchemaType.OBJECT,
|
|
71
|
-
properties: {
|
|
72
|
-
query: {
|
|
73
|
-
type: SchemaType.STRING,
|
|
74
|
-
description: "Text or regex pattern to search for",
|
|
75
|
-
},
|
|
76
|
-
directory: {
|
|
77
|
-
type: SchemaType.STRING,
|
|
78
|
-
description: "Directory to search in (default: cwd)",
|
|
79
|
-
},
|
|
80
|
-
file_pattern: {
|
|
81
|
-
type: SchemaType.STRING,
|
|
82
|
-
description: "Glob to filter files (e.g., '*.ts')",
|
|
83
|
-
},
|
|
84
|
-
max_results: {
|
|
85
|
-
type: SchemaType.INTEGER,
|
|
86
|
-
description: "Maximum results (default: 20)",
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
required: ["query"],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: "run_command",
|
|
94
|
-
description: "Execute a shell command and return stdout/stderr. Use for running tests, builds, git commands, etc. Commands have a 30-second timeout.",
|
|
95
|
-
parameters: {
|
|
96
|
-
type: SchemaType.OBJECT,
|
|
97
|
-
properties: {
|
|
98
|
-
command: {
|
|
99
|
-
type: SchemaType.STRING,
|
|
100
|
-
description: "Shell command to execute",
|
|
101
|
-
},
|
|
102
|
-
cwd: {
|
|
103
|
-
type: SchemaType.STRING,
|
|
104
|
-
description: "Working directory (default: cwd)",
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
required: ["command"],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: "write_file",
|
|
112
|
-
description: "Create or overwrite a file with the given content. Creates parent directories if needed.",
|
|
113
|
-
parameters: {
|
|
114
|
-
type: SchemaType.OBJECT,
|
|
115
|
-
properties: {
|
|
116
|
-
path: {
|
|
117
|
-
type: SchemaType.STRING,
|
|
118
|
-
description: "File path to write to",
|
|
119
|
-
},
|
|
120
|
-
content: {
|
|
121
|
-
type: SchemaType.STRING,
|
|
122
|
-
description: "Content to write",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
required: ["path", "content"],
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "edit_file",
|
|
130
|
-
description: "Apply a targeted search-and-replace edit to a file. Use instead of write_file when making small changes.",
|
|
131
|
-
parameters: {
|
|
132
|
-
type: SchemaType.OBJECT,
|
|
133
|
-
properties: {
|
|
134
|
-
path: {
|
|
135
|
-
type: SchemaType.STRING,
|
|
136
|
-
description: "File path to edit",
|
|
137
|
-
},
|
|
138
|
-
search: {
|
|
139
|
-
type: SchemaType.STRING,
|
|
140
|
-
description: "Exact text to find and replace",
|
|
141
|
-
},
|
|
142
|
-
replace: {
|
|
143
|
-
type: SchemaType.STRING,
|
|
144
|
-
description: "Replacement text",
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
required: ["path", "search", "replace"],
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: "memory_search",
|
|
152
|
-
description: "Search the user's Prism memory (past sessions, decisions, TODOs). Use when the user asks about previous work or project history.",
|
|
153
|
-
parameters: {
|
|
154
|
-
type: SchemaType.OBJECT,
|
|
155
|
-
properties: {
|
|
156
|
-
query: {
|
|
157
|
-
type: SchemaType.STRING,
|
|
158
|
-
description: "Search query",
|
|
159
|
-
},
|
|
160
|
-
project: {
|
|
161
|
-
type: SchemaType.STRING,
|
|
162
|
-
description: "Project to search within",
|
|
163
|
-
},
|
|
164
|
-
limit: {
|
|
165
|
-
type: SchemaType.INTEGER,
|
|
166
|
-
description: "Max results (default: 5)",
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
required: ["query"],
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: "open_url",
|
|
174
|
-
description: "Open a URL in the user's default browser.",
|
|
175
|
-
parameters: {
|
|
176
|
-
type: SchemaType.OBJECT,
|
|
177
|
-
properties: {
|
|
178
|
-
url: {
|
|
179
|
-
type: SchemaType.STRING,
|
|
180
|
-
description: "URL to open",
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
required: ["url"],
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "fetch_url",
|
|
188
|
-
description: "Fetch the text content of a web page and return it. Use when the user wants you to read, summarize, or analyze a web page.",
|
|
189
|
-
parameters: {
|
|
190
|
-
type: SchemaType.OBJECT,
|
|
191
|
-
properties: {
|
|
192
|
-
url: {
|
|
193
|
-
type: SchemaType.STRING,
|
|
194
|
-
description: "The URL to fetch content from",
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
required: ["url"],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "supabase_cli",
|
|
202
|
-
description: "Execute Supabase CLI commands (e.g., status, diff, push, db pull). Always use this for Supabase database management.",
|
|
203
|
-
parameters: {
|
|
204
|
-
type: SchemaType.OBJECT,
|
|
205
|
-
properties: {
|
|
206
|
-
args: {
|
|
207
|
-
type: SchemaType.STRING,
|
|
208
|
-
description: 'The arguments to pass to the supabase CLI (e.g., "db pull", "status")',
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
required: ["args"],
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: "stripe_cli",
|
|
216
|
-
description: "Execute Stripe CLI commands (e.g., listen, trigger, login). Always use this for Stripe operations.",
|
|
217
|
-
parameters: {
|
|
218
|
-
type: SchemaType.OBJECT,
|
|
219
|
-
properties: {
|
|
220
|
-
args: {
|
|
221
|
-
type: SchemaType.STRING,
|
|
222
|
-
description: 'The arguments to pass to the stripe CLI (e.g., "listen --forward-to localhost:3000/api/webhook")',
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
required: ["args"],
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
];
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
// Tool Executor
|
|
231
|
-
// ---------------------------------------------------------------------------
|
|
232
|
-
/**
|
|
233
|
-
* Resolve a path — if relative, resolve against cwd.
|
|
234
|
-
*/
|
|
235
|
-
function resolvePath(filePath) {
|
|
236
|
-
if (path.isAbsolute(filePath))
|
|
237
|
-
return filePath;
|
|
238
|
-
return path.resolve(process.cwd(), filePath);
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Execute an agent tool and return the result as a string.
|
|
242
|
-
*/
|
|
243
|
-
export async function executeAgentTool(toolName, args, project) {
|
|
244
|
-
switch (toolName) {
|
|
245
|
-
// ─── read_file ─────────────────────────────────────────────
|
|
246
|
-
case "read_file": {
|
|
247
|
-
const filePath = resolvePath(args.path);
|
|
248
|
-
if (!fs.existsSync(filePath))
|
|
249
|
-
return `Error: File not found: ${filePath}`;
|
|
250
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
251
|
-
const lines = content.split("\n");
|
|
252
|
-
const start = Math.max(1, args.start_line || 1);
|
|
253
|
-
const end = Math.min(lines.length, args.end_line || lines.length);
|
|
254
|
-
const slice = lines.slice(start - 1, end);
|
|
255
|
-
// Add line numbers
|
|
256
|
-
const numbered = slice.map((l, i) => `${start + i}: ${l}`).join("\n");
|
|
257
|
-
return `File: ${filePath} (${lines.length} lines total, showing ${start}-${end})\n\n${numbered}`;
|
|
258
|
-
}
|
|
259
|
-
// ─── list_files ────────────────────────────────────────────
|
|
260
|
-
case "list_files": {
|
|
261
|
-
const dir = resolvePath(args.directory || ".");
|
|
262
|
-
if (!fs.existsSync(dir))
|
|
263
|
-
return `Error: Directory not found: ${dir}`;
|
|
264
|
-
const maxDepth = args.max_depth || 3;
|
|
265
|
-
const pattern = args.pattern;
|
|
266
|
-
const cmd = listFilesCommand(dir, maxDepth, pattern);
|
|
267
|
-
try {
|
|
268
|
-
const { stdout } = await execAsync(cmd, { timeout: 10000, shell: IS_WINDOWS ? "powershell.exe" : undefined });
|
|
269
|
-
const entries = stdout.trim().split("\n").filter(Boolean);
|
|
270
|
-
return `Directory: ${dir}\n${entries.length} items found:\n\n${entries.join("\n")}`;
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
// Fallback to Node.js readdir
|
|
274
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
275
|
-
const lines = entries
|
|
276
|
-
.slice(0, 50)
|
|
277
|
-
.map((e) => `${e.isDirectory() ? "📁" : "📄"} ${e.name}`);
|
|
278
|
-
return `Directory: ${dir}\n${lines.join("\n")}`;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
// ─── search_files ──────────────────────────────────────────
|
|
282
|
-
case "search_files": {
|
|
283
|
-
const query = args.query;
|
|
284
|
-
const dir = resolvePath(args.directory || ".");
|
|
285
|
-
const maxResults = args.max_results || 20;
|
|
286
|
-
const filePattern = args.file_pattern;
|
|
287
|
-
const cmd = searchFilesCommand(query, dir, maxResults, filePattern);
|
|
288
|
-
try {
|
|
289
|
-
const { stdout } = await execAsync(cmd, { timeout: 15000 });
|
|
290
|
-
if (!stdout.trim())
|
|
291
|
-
return `No matches found for "${query}" in ${dir}`;
|
|
292
|
-
return `Search: "${query}" in ${dir}\n\n${stdout.trim()}`;
|
|
293
|
-
}
|
|
294
|
-
catch {
|
|
295
|
-
return `No matches found for "${query}" in ${dir}`;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
// ─── run_command ───────────────────────────────────────────
|
|
299
|
-
case "run_command": {
|
|
300
|
-
const command = args.command;
|
|
301
|
-
const cwd = resolvePath(args.cwd || ".");
|
|
302
|
-
// Safety: block obviously dangerous commands
|
|
303
|
-
const blocked = ["rm -rf /", "mkfs", "dd if=", ":(){"];
|
|
304
|
-
if (blocked.some((b) => command.includes(b))) {
|
|
305
|
-
return "Error: Command blocked for safety reasons.";
|
|
306
|
-
}
|
|
307
|
-
try {
|
|
308
|
-
const { stdout, stderr } = await execAsync(command, {
|
|
309
|
-
cwd,
|
|
310
|
-
timeout: 30000,
|
|
311
|
-
maxBuffer: 1024 * 1024,
|
|
312
|
-
env: { ...process.env, PAGER: "cat" },
|
|
313
|
-
});
|
|
314
|
-
let result = "";
|
|
315
|
-
if (stdout.trim())
|
|
316
|
-
result += `stdout:\n${stdout.trim()}\n`;
|
|
317
|
-
if (stderr.trim())
|
|
318
|
-
result += `stderr:\n${stderr.trim()}\n`;
|
|
319
|
-
if (!result)
|
|
320
|
-
result = "(command completed with no output)";
|
|
321
|
-
return result;
|
|
322
|
-
}
|
|
323
|
-
catch (err) {
|
|
324
|
-
const e = err;
|
|
325
|
-
let msg = `Command failed: ${command}\n`;
|
|
326
|
-
if (e.stdout)
|
|
327
|
-
msg += `stdout:\n${e.stdout}\n`;
|
|
328
|
-
if (e.stderr)
|
|
329
|
-
msg += `stderr:\n${e.stderr}\n`;
|
|
330
|
-
if (e.message && !e.stderr)
|
|
331
|
-
msg += `error: ${e.message}`;
|
|
332
|
-
return msg;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
// ─── write_file ────────────────────────────────────────────
|
|
336
|
-
case "write_file": {
|
|
337
|
-
const filePath = resolvePath(args.path);
|
|
338
|
-
const content = args.content;
|
|
339
|
-
// Create parent dirs
|
|
340
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
341
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
342
|
-
return `✅ Written ${content.length} chars to ${filePath}`;
|
|
343
|
-
}
|
|
344
|
-
// ─── edit_file ─────────────────────────────────────────────
|
|
345
|
-
case "edit_file": {
|
|
346
|
-
const filePath = resolvePath(args.path);
|
|
347
|
-
if (!fs.existsSync(filePath))
|
|
348
|
-
return `Error: File not found: ${filePath}`;
|
|
349
|
-
const original = fs.readFileSync(filePath, "utf-8");
|
|
350
|
-
const search = args.search;
|
|
351
|
-
const replace = args.replace;
|
|
352
|
-
if (!original.includes(search)) {
|
|
353
|
-
return `Error: Search text not found in ${filePath}`;
|
|
354
|
-
}
|
|
355
|
-
const updated = original.replace(search, replace);
|
|
356
|
-
fs.writeFileSync(filePath, updated, "utf-8");
|
|
357
|
-
return `✅ Edited ${filePath} — replaced ${search.length} chars`;
|
|
358
|
-
}
|
|
359
|
-
// ─── memory_search ─────────────────────────────────────────
|
|
360
|
-
case "memory_search": {
|
|
361
|
-
const { knowledgeSearchHandler } = await import("../tools/graphHandlers.js");
|
|
362
|
-
const result = await knowledgeSearchHandler({
|
|
363
|
-
query: args.query,
|
|
364
|
-
project: args.project || project || "prism-mcp",
|
|
365
|
-
limit: args.limit || 5,
|
|
366
|
-
});
|
|
367
|
-
const text = result.content?.[0] && "text" in result.content[0]
|
|
368
|
-
? result.content[0].text
|
|
369
|
-
: "No results found.";
|
|
370
|
-
return text;
|
|
371
|
-
}
|
|
372
|
-
// ─── open_url ──────────────────────────────────────────────
|
|
373
|
-
case "open_url": {
|
|
374
|
-
const url = args.url;
|
|
375
|
-
try {
|
|
376
|
-
await execAsync(openUrlCommand(url));
|
|
377
|
-
return `✅ Opened ${url} in default browser`;
|
|
378
|
-
}
|
|
379
|
-
catch {
|
|
380
|
-
return `Error: Failed to open ${url}`;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// ─── fetch_url ─────────────────────────────────────────────
|
|
384
|
-
case "fetch_url": {
|
|
385
|
-
const fetchUrl = args.url;
|
|
386
|
-
if (!fetchUrl?.match(/^https?:\/\//i)) {
|
|
387
|
-
return "Error: Invalid URL. Must start with http:// or https://";
|
|
388
|
-
}
|
|
389
|
-
try {
|
|
390
|
-
const cmd = fetchUrlCommand(fetchUrl);
|
|
391
|
-
const { stdout } = await execAsync(cmd, {
|
|
392
|
-
timeout: 20000,
|
|
393
|
-
maxBuffer: 1024 * 1024,
|
|
394
|
-
shell: IS_WINDOWS ? "powershell.exe" : undefined,
|
|
395
|
-
});
|
|
396
|
-
const text = stdout.trim().slice(0, 8000);
|
|
397
|
-
return `Content from ${fetchUrl}:\n\n${text}${text.length >= 8000 ? "\n\n... (truncated)" : ""}`;
|
|
398
|
-
}
|
|
399
|
-
catch (err) {
|
|
400
|
-
const e = err;
|
|
401
|
-
return `Error: Failed to fetch ${fetchUrl} — ${e.message?.slice(0, 200) || "unknown error"}`;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// ─── supabase_cli ──────────────────────────────────────────
|
|
405
|
-
case "supabase_cli": {
|
|
406
|
-
const sbArgs = args.args;
|
|
407
|
-
const sbBin = resolveCli("supabase");
|
|
408
|
-
try {
|
|
409
|
-
const { stdout } = await execAsync(`${sbBin} ${sbArgs}`, {
|
|
410
|
-
cwd: process.cwd(),
|
|
411
|
-
timeout: 60000,
|
|
412
|
-
maxBuffer: 1024 * 512,
|
|
413
|
-
env: { ...process.env, PAGER: "cat" },
|
|
414
|
-
});
|
|
415
|
-
return `supabase ${sbArgs}:\n${stdout.trim().slice(0, 5000)}`;
|
|
416
|
-
}
|
|
417
|
-
catch (err) {
|
|
418
|
-
const e = err;
|
|
419
|
-
let msg = `supabase ${sbArgs} failed:\n`;
|
|
420
|
-
if (e.stdout)
|
|
421
|
-
msg += e.stdout.slice(0, 3000);
|
|
422
|
-
if (e.stderr)
|
|
423
|
-
msg += `\nstderr: ${e.stderr.slice(0, 1000)}`;
|
|
424
|
-
return msg;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
// ─── stripe_cli ───────────────────────────────────────────
|
|
428
|
-
case "stripe_cli": {
|
|
429
|
-
const stripeArgs = args.args;
|
|
430
|
-
const stripeBin = resolveCli("stripe");
|
|
431
|
-
try {
|
|
432
|
-
const { stdout } = await execAsync(`${stripeBin} ${stripeArgs}`, {
|
|
433
|
-
cwd: process.cwd(),
|
|
434
|
-
timeout: 60000,
|
|
435
|
-
maxBuffer: 1024 * 512,
|
|
436
|
-
env: { ...process.env, PAGER: "cat" },
|
|
437
|
-
});
|
|
438
|
-
return `stripe ${stripeArgs}:\n${stdout.trim().slice(0, 5000)}`;
|
|
439
|
-
}
|
|
440
|
-
catch (err) {
|
|
441
|
-
const e = err;
|
|
442
|
-
let msg = `stripe ${stripeArgs} failed:\n`;
|
|
443
|
-
if (e.stdout)
|
|
444
|
-
msg += e.stdout.slice(0, 3000);
|
|
445
|
-
if (e.stderr)
|
|
446
|
-
msg += `\nstderr: ${e.stderr.slice(0, 1000)}`;
|
|
447
|
-
return msg;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
default:
|
|
451
|
-
return `Error: Unknown tool \"${toolName}\"`;
|
|
452
|
-
}
|
|
453
|
-
}
|