cssprobe-cli 0.2.25 → 0.3.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 +22 -0
- package/README.md +33 -1
- package/dist/cli/commands.js +26 -0
- package/dist/cli/program.js +59 -152
- package/dist/cli/session-ops.d.ts +43 -0
- package/dist/cli/session-ops.js +163 -0
- package/dist/help.json +22 -1
- package/dist/installer/index.d.ts +2 -0
- package/dist/installer/index.js +6 -1
- package/dist/installer/mcp.d.ts +14 -0
- package/dist/installer/mcp.js +274 -0
- package/dist/installer/toml.d.ts +20 -0
- package/dist/installer/toml.js +98 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.js +174 -0
- package/dist/mcp/server-instructions.d.ts +1 -0
- package/dist/mcp/server-instructions.js +27 -0
- package/dist/mcp/tools.d.ts +261 -0
- package/dist/mcp/tools.js +238 -0
- package/llms.txt +15 -0
- package/package.json +5 -2
package/dist/installer/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveTargetFlag = exports.listTargetIds = exports.getTarget = exports.ALL_TARGETS = exports.buildPitfallsMd = exports.buildSkillMd = void 0;
|
|
3
|
+
exports.MCP_SERVER_NAME = exports.listMcpClientIds = exports.uninstallMcpConfig = exports.installMcpConfig = exports.resolveTargetFlag = exports.listTargetIds = exports.getTarget = exports.ALL_TARGETS = exports.buildPitfallsMd = exports.buildSkillMd = void 0;
|
|
4
4
|
var skill_template_1 = require("./skill-template");
|
|
5
5
|
Object.defineProperty(exports, "buildSkillMd", { enumerable: true, get: function () { return skill_template_1.buildSkillMd; } });
|
|
6
6
|
Object.defineProperty(exports, "buildPitfallsMd", { enumerable: true, get: function () { return skill_template_1.buildPitfallsMd; } });
|
|
@@ -9,4 +9,9 @@ Object.defineProperty(exports, "ALL_TARGETS", { enumerable: true, get: function
|
|
|
9
9
|
Object.defineProperty(exports, "getTarget", { enumerable: true, get: function () { return registry_1.getTarget; } });
|
|
10
10
|
Object.defineProperty(exports, "listTargetIds", { enumerable: true, get: function () { return registry_1.listTargetIds; } });
|
|
11
11
|
Object.defineProperty(exports, "resolveTargetFlag", { enumerable: true, get: function () { return registry_1.resolveTargetFlag; } });
|
|
12
|
+
var mcp_1 = require("./mcp");
|
|
13
|
+
Object.defineProperty(exports, "installMcpConfig", { enumerable: true, get: function () { return mcp_1.installMcpConfig; } });
|
|
14
|
+
Object.defineProperty(exports, "uninstallMcpConfig", { enumerable: true, get: function () { return mcp_1.uninstallMcpConfig; } });
|
|
15
|
+
Object.defineProperty(exports, "listMcpClientIds", { enumerable: true, get: function () { return mcp_1.listMcpClientIds; } });
|
|
16
|
+
Object.defineProperty(exports, "MCP_SERVER_NAME", { enumerable: true, get: function () { return mcp_1.MCP_SERVER_NAME; } });
|
|
12
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Location, TargetId } from './targets/types';
|
|
2
|
+
export declare const MCP_SERVER_NAME = "cssprobe-cli";
|
|
3
|
+
export interface McpFileResult {
|
|
4
|
+
path: string;
|
|
5
|
+
action: 'created' | 'updated' | 'unchanged' | 'removed' | 'not-found' | 'kept';
|
|
6
|
+
agent: string;
|
|
7
|
+
}
|
|
8
|
+
export interface McpCommandResult {
|
|
9
|
+
files: McpFileResult[];
|
|
10
|
+
notes: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function installMcpConfig(loc: Location, targetFlag?: string): McpCommandResult;
|
|
13
|
+
export declare function uninstallMcpConfig(loc: Location, targetFlag?: string): McpCommandResult;
|
|
14
|
+
export declare function listMcpClientIds(): TargetId[];
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MCP_SERVER_NAME = void 0;
|
|
37
|
+
exports.installMcpConfig = installMcpConfig;
|
|
38
|
+
exports.uninstallMcpConfig = uninstallMcpConfig;
|
|
39
|
+
exports.listMcpClientIds = listMcpClientIds;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const shared_1 = require("./targets/shared");
|
|
44
|
+
const toml_1 = require("./toml");
|
|
45
|
+
exports.MCP_SERVER_NAME = 'cssprobe-cli';
|
|
46
|
+
const jsonEntry = () => ({ type: 'stdio', command: exports.MCP_SERVER_NAME, args: ['mcp'] });
|
|
47
|
+
const opencodeEntry = () => ({ type: 'local', command: [exports.MCP_SERVER_NAME, 'mcp'], enabled: true });
|
|
48
|
+
function home(...parts) {
|
|
49
|
+
return path.join(os.homedir(), ...parts);
|
|
50
|
+
}
|
|
51
|
+
function dirExists(p) {
|
|
52
|
+
try {
|
|
53
|
+
return fs.statSync(p).isDirectory();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function fileExists(p) {
|
|
60
|
+
try {
|
|
61
|
+
return fs.statSync(p).isFile();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function opencodeGlobalPath() {
|
|
68
|
+
const newStyleDir = home('.opencode');
|
|
69
|
+
if (dirExists(newStyleDir) || fileExists(path.join(newStyleDir, 'opencode.json'))) {
|
|
70
|
+
return path.join(newStyleDir, 'opencode.json');
|
|
71
|
+
}
|
|
72
|
+
return home('.config', 'opencode', 'opencode.json');
|
|
73
|
+
}
|
|
74
|
+
const CLIENTS = [
|
|
75
|
+
{
|
|
76
|
+
id: 'claude',
|
|
77
|
+
displayName: 'Claude Code',
|
|
78
|
+
format: 'json',
|
|
79
|
+
supportsLocal: true,
|
|
80
|
+
configPath: (loc) => (loc === 'global' ? home('.claude.json') : path.join(process.cwd(), '.mcp.json')),
|
|
81
|
+
detect: (loc) => (loc === 'global' ? fileExists(home('.claude.json')) || dirExists(home('.claude')) : dirExists(path.join(process.cwd(), '.claude'))),
|
|
82
|
+
serversKey: 'mcpServers',
|
|
83
|
+
buildEntry: jsonEntry,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'cursor',
|
|
87
|
+
displayName: 'Cursor',
|
|
88
|
+
format: 'json',
|
|
89
|
+
supportsLocal: true,
|
|
90
|
+
configPath: (loc) => (loc === 'global' ? home('.cursor', 'mcp.json') : path.join(process.cwd(), '.cursor', 'mcp.json')),
|
|
91
|
+
detect: (loc) => dirExists(loc === 'global' ? home('.cursor') : path.join(process.cwd(), '.cursor')),
|
|
92
|
+
serversKey: 'mcpServers',
|
|
93
|
+
buildEntry: jsonEntry,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'gemini',
|
|
97
|
+
displayName: 'Gemini CLI',
|
|
98
|
+
format: 'json',
|
|
99
|
+
supportsLocal: false,
|
|
100
|
+
configPath: () => home('.gemini', 'mcp_settings.json'),
|
|
101
|
+
detect: () => dirExists(home('.gemini')),
|
|
102
|
+
serversKey: 'mcpServers',
|
|
103
|
+
buildEntry: jsonEntry,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'hermes',
|
|
107
|
+
displayName: 'Hermes',
|
|
108
|
+
format: 'json',
|
|
109
|
+
supportsLocal: false,
|
|
110
|
+
configPath: () => home('.hermes', 'mcp.json'),
|
|
111
|
+
detect: () => dirExists(home('.hermes')),
|
|
112
|
+
serversKey: 'mcpServers',
|
|
113
|
+
buildEntry: jsonEntry,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'opencode',
|
|
117
|
+
displayName: 'opencode',
|
|
118
|
+
format: 'json',
|
|
119
|
+
supportsLocal: true,
|
|
120
|
+
configPath: (loc) => (loc === 'global' ? opencodeGlobalPath() : path.join(process.cwd(), 'opencode.json')),
|
|
121
|
+
detect: (loc) => dirExists(loc === 'global' ? home('.opencode') : path.join(process.cwd(), '.opencode')) || (loc === 'global' && dirExists(home('.config', 'opencode'))),
|
|
122
|
+
serversKey: 'mcp',
|
|
123
|
+
buildEntry: opencodeEntry,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'codex',
|
|
127
|
+
displayName: 'Codex CLI',
|
|
128
|
+
format: 'toml',
|
|
129
|
+
supportsLocal: false,
|
|
130
|
+
configPath: () => home('.codex', 'config.toml'),
|
|
131
|
+
detect: () => dirExists(home('.codex')),
|
|
132
|
+
table: `mcp_servers.${exports.MCP_SERVER_NAME}`,
|
|
133
|
+
buildBlock: () => (0, toml_1.buildTomlTable)(`mcp_servers.${exports.MCP_SERVER_NAME}`, { command: exports.MCP_SERVER_NAME, args: ['mcp'] }),
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
function resolveClients(targetFlag, loc) {
|
|
137
|
+
const notes = [];
|
|
138
|
+
const allowed = CLIENTS.filter((c) => loc === 'global' || c.supportsLocal);
|
|
139
|
+
if (targetFlag === 'none')
|
|
140
|
+
return { clients: [], notes };
|
|
141
|
+
if (targetFlag === 'all') {
|
|
142
|
+
if (loc === 'local')
|
|
143
|
+
notes.push('Local location only applies to claude / cursor / opencode; other clients are global-only.');
|
|
144
|
+
return { clients: [...allowed], notes };
|
|
145
|
+
}
|
|
146
|
+
if (targetFlag === 'auto') {
|
|
147
|
+
const detected = allowed.filter((c) => c.detect(loc));
|
|
148
|
+
if (detected.length === 0) {
|
|
149
|
+
notes.push('No MCP client configs detected. Use --target=all or --target=<id,...> to force.');
|
|
150
|
+
}
|
|
151
|
+
return { clients: detected, notes };
|
|
152
|
+
}
|
|
153
|
+
const ids = targetFlag.split(',').map((s) => s.trim()).filter(Boolean);
|
|
154
|
+
const resolved = [];
|
|
155
|
+
const unknown = [];
|
|
156
|
+
for (const id of ids) {
|
|
157
|
+
const client = CLIENTS.find((c) => c.id === id);
|
|
158
|
+
if (!client) {
|
|
159
|
+
unknown.push(id);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (loc === 'local' && !client.supportsLocal) {
|
|
163
|
+
notes.push(`${client.displayName} has no project-local MCP config; skipped.`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
resolved.push(client);
|
|
167
|
+
}
|
|
168
|
+
if (unknown.length > 0) {
|
|
169
|
+
const known = CLIENTS.map((c) => c.id).join(', ');
|
|
170
|
+
throw new Error(`Unknown --target id(s): ${unknown.join(', ')}. Known: ${known}, plus 'auto' / 'all' / 'none'.`);
|
|
171
|
+
}
|
|
172
|
+
return { clients: resolved, notes };
|
|
173
|
+
}
|
|
174
|
+
function installJson(client, loc) {
|
|
175
|
+
const file = client.configPath(loc);
|
|
176
|
+
if (!fileExists(file)) {
|
|
177
|
+
const data = {};
|
|
178
|
+
data[client.serversKey] = { [exports.MCP_SERVER_NAME]: client.buildEntry() };
|
|
179
|
+
(0, shared_1.atomicWriteFileSync)(file, JSON.stringify(data, null, 2) + '\n');
|
|
180
|
+
return { result: { path: file, action: 'created', agent: client.displayName } };
|
|
181
|
+
}
|
|
182
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
183
|
+
let data;
|
|
184
|
+
try {
|
|
185
|
+
data = JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return {
|
|
189
|
+
result: { path: file, action: 'kept', agent: client.displayName },
|
|
190
|
+
note: `${file} is not valid JSON — skipped (left untouched).`,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const before = JSON.stringify(data[client.serversKey]?.[exports.MCP_SERVER_NAME] ?? null);
|
|
194
|
+
const after = JSON.stringify(client.buildEntry());
|
|
195
|
+
if (before === after) {
|
|
196
|
+
return { result: { path: file, action: 'unchanged', agent: client.displayName } };
|
|
197
|
+
}
|
|
198
|
+
if (!data[client.serversKey])
|
|
199
|
+
data[client.serversKey] = {};
|
|
200
|
+
data[client.serversKey][exports.MCP_SERVER_NAME] = client.buildEntry();
|
|
201
|
+
(0, shared_1.atomicWriteFileSync)(file, JSON.stringify(data, null, 2) + '\n');
|
|
202
|
+
return { result: { path: file, action: 'updated', agent: client.displayName } };
|
|
203
|
+
}
|
|
204
|
+
function uninstallJson(client, loc) {
|
|
205
|
+
const file = client.configPath(loc);
|
|
206
|
+
if (!fileExists(file)) {
|
|
207
|
+
return { result: { path: file, action: 'not-found', agent: client.displayName } };
|
|
208
|
+
}
|
|
209
|
+
const raw = fs.readFileSync(file, 'utf-8');
|
|
210
|
+
let data;
|
|
211
|
+
try {
|
|
212
|
+
data = JSON.parse(raw);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return {
|
|
216
|
+
result: { path: file, action: 'kept', agent: client.displayName },
|
|
217
|
+
note: `${file} is not valid JSON — skipped (left untouched).`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (!data[client.serversKey]?.[exports.MCP_SERVER_NAME]) {
|
|
221
|
+
return { result: { path: file, action: 'not-found', agent: client.displayName } };
|
|
222
|
+
}
|
|
223
|
+
delete data[client.serversKey][exports.MCP_SERVER_NAME];
|
|
224
|
+
if (Object.keys(data[client.serversKey]).length === 0)
|
|
225
|
+
delete data[client.serversKey];
|
|
226
|
+
(0, shared_1.atomicWriteFileSync)(file, JSON.stringify(data, null, 2) + '\n');
|
|
227
|
+
return { result: { path: file, action: 'removed', agent: client.displayName } };
|
|
228
|
+
}
|
|
229
|
+
function installToml(client, loc) {
|
|
230
|
+
const file = client.configPath(loc);
|
|
231
|
+
const existing = fileExists(file) ? fs.readFileSync(file, 'utf-8') : '';
|
|
232
|
+
const { content, action } = (0, toml_1.upsertTomlTable)(existing, client.table, client.buildBlock());
|
|
233
|
+
if (action === 'unchanged') {
|
|
234
|
+
return { result: { path: file, action: 'unchanged', agent: client.displayName } };
|
|
235
|
+
}
|
|
236
|
+
(0, shared_1.atomicWriteFileSync)(file, content);
|
|
237
|
+
return { result: { path: file, action: action === 'inserted' ? 'created' : 'updated', agent: client.displayName } };
|
|
238
|
+
}
|
|
239
|
+
function uninstallToml(client, loc) {
|
|
240
|
+
const file = client.configPath(loc);
|
|
241
|
+
if (!fileExists(file)) {
|
|
242
|
+
return { result: { path: file, action: 'not-found', agent: client.displayName } };
|
|
243
|
+
}
|
|
244
|
+
const existing = fs.readFileSync(file, 'utf-8');
|
|
245
|
+
const { content, action } = (0, toml_1.removeTomlTable)(existing, client.table);
|
|
246
|
+
if (action === 'not-found') {
|
|
247
|
+
return { result: { path: file, action: 'not-found', agent: client.displayName } };
|
|
248
|
+
}
|
|
249
|
+
(0, shared_1.atomicWriteFileSync)(file, content);
|
|
250
|
+
return { result: { path: file, action: 'removed', agent: client.displayName } };
|
|
251
|
+
}
|
|
252
|
+
function run(mode, loc, targetFlag) {
|
|
253
|
+
const { clients, notes } = resolveClients(targetFlag, loc);
|
|
254
|
+
const files = [];
|
|
255
|
+
for (const client of clients) {
|
|
256
|
+
const handled = mode === 'install'
|
|
257
|
+
? (client.format === 'json' ? installJson(client, loc) : installToml(client, loc))
|
|
258
|
+
: (client.format === 'json' ? uninstallJson(client, loc) : uninstallToml(client, loc));
|
|
259
|
+
files.push(handled.result);
|
|
260
|
+
if (handled.note)
|
|
261
|
+
notes.push(handled.note);
|
|
262
|
+
}
|
|
263
|
+
return { files, notes };
|
|
264
|
+
}
|
|
265
|
+
function installMcpConfig(loc, targetFlag = 'auto') {
|
|
266
|
+
return run('install', loc, targetFlag);
|
|
267
|
+
}
|
|
268
|
+
function uninstallMcpConfig(loc, targetFlag = 'auto') {
|
|
269
|
+
return run('uninstall', loc, targetFlag);
|
|
270
|
+
}
|
|
271
|
+
function listMcpClientIds() {
|
|
272
|
+
return CLIENTS.map((c) => c.id);
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny TOML helpers — just enough to inject / replace / remove a single
|
|
3
|
+
* dotted-key table block (`[mcp_servers.cssprobe-cli]`) inside an existing
|
|
4
|
+
* `~/.codex/config.toml`. We deliberately do NOT try to be a general TOML
|
|
5
|
+
* parser/serializer.
|
|
6
|
+
*
|
|
7
|
+
* Strategy: treat the file as text. Find the header line, splice it (and the
|
|
8
|
+
* lines that follow it until the next `[...]` header or EOF) in or out.
|
|
9
|
+
* Everything outside that block is preserved verbatim.
|
|
10
|
+
*/
|
|
11
|
+
export declare function serializeTomlTableBody(values: Record<string, string | string[]>): string;
|
|
12
|
+
export declare function buildTomlTable(header: string, values: Record<string, string | string[]>): string;
|
|
13
|
+
export declare function upsertTomlTable(fileContent: string, header: string, block: string): {
|
|
14
|
+
content: string;
|
|
15
|
+
action: 'inserted' | 'replaced' | 'unchanged';
|
|
16
|
+
};
|
|
17
|
+
export declare function removeTomlTable(fileContent: string, header: string): {
|
|
18
|
+
content: string;
|
|
19
|
+
action: 'removed' | 'not-found';
|
|
20
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tiny TOML helpers — just enough to inject / replace / remove a single
|
|
4
|
+
* dotted-key table block (`[mcp_servers.cssprobe-cli]`) inside an existing
|
|
5
|
+
* `~/.codex/config.toml`. We deliberately do NOT try to be a general TOML
|
|
6
|
+
* parser/serializer.
|
|
7
|
+
*
|
|
8
|
+
* Strategy: treat the file as text. Find the header line, splice it (and the
|
|
9
|
+
* lines that follow it until the next `[...]` header or EOF) in or out.
|
|
10
|
+
* Everything outside that block is preserved verbatim.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.serializeTomlTableBody = serializeTomlTableBody;
|
|
14
|
+
exports.buildTomlTable = buildTomlTable;
|
|
15
|
+
exports.upsertTomlTable = upsertTomlTable;
|
|
16
|
+
exports.removeTomlTable = removeTomlTable;
|
|
17
|
+
function serializeTomlTableBody(values) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
for (const [key, value] of Object.entries(values)) {
|
|
20
|
+
if (typeof value === 'string') {
|
|
21
|
+
lines.push(`${key} = ${quoteString(value)}`);
|
|
22
|
+
}
|
|
23
|
+
else if (Array.isArray(value) && value.every((v) => typeof v === 'string')) {
|
|
24
|
+
const parts = value.map(quoteString).join(', ');
|
|
25
|
+
lines.push(`${key} = [${parts}]`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new Error(`Unsupported TOML value type for key "${key}"`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return lines.join('\n');
|
|
32
|
+
}
|
|
33
|
+
function quoteString(s) {
|
|
34
|
+
return '"' + s.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
35
|
+
}
|
|
36
|
+
function buildTomlTable(header, values) {
|
|
37
|
+
return `[${header}]\n${serializeTomlTableBody(values)}`;
|
|
38
|
+
}
|
|
39
|
+
function upsertTomlTable(fileContent, header, block) {
|
|
40
|
+
const headerLine = `[${header}]`;
|
|
41
|
+
const headerIdx = findHeaderIndex(fileContent, headerLine);
|
|
42
|
+
if (headerIdx === -1) {
|
|
43
|
+
const trimmed = fileContent.trimEnd();
|
|
44
|
+
const sep = trimmed.length > 0 ? '\n\n' : '';
|
|
45
|
+
return {
|
|
46
|
+
content: trimmed + sep + block + '\n',
|
|
47
|
+
action: 'inserted',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const blockEnd = findNextTableHeader(fileContent, headerIdx + headerLine.length);
|
|
51
|
+
const existingBlock = fileContent.substring(headerIdx, blockEnd).replace(/\n+$/, '');
|
|
52
|
+
if (existingBlock === block) {
|
|
53
|
+
return { content: fileContent, action: 'unchanged' };
|
|
54
|
+
}
|
|
55
|
+
const before = fileContent.substring(0, headerIdx);
|
|
56
|
+
const after = fileContent.substring(blockEnd);
|
|
57
|
+
const beforeClean = before.replace(/\n+$/, '');
|
|
58
|
+
const afterClean = after.replace(/^\n+/, '');
|
|
59
|
+
const sepBefore = beforeClean.length > 0 ? '\n\n' : '';
|
|
60
|
+
const sepAfter = afterClean.length > 0 ? '\n\n' : '\n';
|
|
61
|
+
return {
|
|
62
|
+
content: beforeClean + sepBefore + block + sepAfter + afterClean,
|
|
63
|
+
action: 'replaced',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function removeTomlTable(fileContent, header) {
|
|
67
|
+
const headerLine = `[${header}]`;
|
|
68
|
+
const headerIdx = findHeaderIndex(fileContent, headerLine);
|
|
69
|
+
if (headerIdx === -1)
|
|
70
|
+
return { content: fileContent, action: 'not-found' };
|
|
71
|
+
const blockEnd = findNextTableHeader(fileContent, headerIdx + headerLine.length);
|
|
72
|
+
const before = fileContent.substring(0, headerIdx).replace(/\n+$/, '');
|
|
73
|
+
const after = fileContent.substring(blockEnd).replace(/^\n+/, '');
|
|
74
|
+
const joined = before + (before && after ? '\n\n' : '') + after;
|
|
75
|
+
return { content: joined, action: 'removed' };
|
|
76
|
+
}
|
|
77
|
+
function findHeaderIndex(content, headerLine) {
|
|
78
|
+
if (content.startsWith(headerLine))
|
|
79
|
+
return 0;
|
|
80
|
+
const needle = '\n' + headerLine;
|
|
81
|
+
const idx = content.indexOf(needle);
|
|
82
|
+
return idx === -1 ? -1 : idx + 1;
|
|
83
|
+
}
|
|
84
|
+
function findNextTableHeader(content, from) {
|
|
85
|
+
let i = from;
|
|
86
|
+
while (i < content.length) {
|
|
87
|
+
const nlIdx = content.indexOf('\n[', i);
|
|
88
|
+
if (nlIdx === -1)
|
|
89
|
+
return content.length;
|
|
90
|
+
if (content[nlIdx + 2] === '[') {
|
|
91
|
+
i = nlIdx + 2;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
return nlIdx + 1;
|
|
95
|
+
}
|
|
96
|
+
return content.length;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=toml.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class MCPServer {
|
|
2
|
+
private rl;
|
|
3
|
+
private handler;
|
|
4
|
+
private version;
|
|
5
|
+
private pending;
|
|
6
|
+
private closing;
|
|
7
|
+
private queue;
|
|
8
|
+
constructor();
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
handleMessage(message: Record<string, unknown>): Promise<Record<string, unknown> | undefined>;
|
|
11
|
+
private handleToolCall;
|
|
12
|
+
private executeTool;
|
|
13
|
+
private send;
|
|
14
|
+
}
|
|
15
|
+
export declare function runMcpServer(): Promise<void>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MCPServer = void 0;
|
|
37
|
+
exports.runMcpServer = runMcpServer;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const readline = __importStar(require("readline"));
|
|
41
|
+
const server_instructions_1 = require("./server-instructions");
|
|
42
|
+
const tools_1 = require("./tools");
|
|
43
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
44
|
+
class MCPServer {
|
|
45
|
+
rl = null;
|
|
46
|
+
handler = new tools_1.MCPToolHandler();
|
|
47
|
+
version;
|
|
48
|
+
pending = 0;
|
|
49
|
+
closing = false;
|
|
50
|
+
queue = Promise.resolve();
|
|
51
|
+
constructor() {
|
|
52
|
+
try {
|
|
53
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'package.json'), 'utf-8'));
|
|
54
|
+
this.version = pkg.version || '0.0.0';
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
this.version = '0.0.0';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
this.rl = readline.createInterface({
|
|
62
|
+
input: process.stdin,
|
|
63
|
+
output: process.stdout,
|
|
64
|
+
terminal: false,
|
|
65
|
+
});
|
|
66
|
+
process.stderr.write('cssprobe-cli MCP server started\n');
|
|
67
|
+
// Requests are serialized: tool calls share one browser session, so an
|
|
68
|
+
// inspect racing an in-flight open must be queued, not run concurrently.
|
|
69
|
+
this.rl.on('line', (line) => {
|
|
70
|
+
this.pending++;
|
|
71
|
+
this.queue = this.queue.then(async () => {
|
|
72
|
+
try {
|
|
73
|
+
const message = JSON.parse(line);
|
|
74
|
+
const response = await this.handleMessage(message);
|
|
75
|
+
if (response)
|
|
76
|
+
this.send(response);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore malformed lines
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
this.pending--;
|
|
83
|
+
if (this.closing && this.pending === 0)
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
// Exit only after in-flight requests drain, so a client that closes
|
|
89
|
+
// stdin immediately after sending still receives its responses.
|
|
90
|
+
this.rl.on('close', () => {
|
|
91
|
+
this.closing = true;
|
|
92
|
+
if (this.pending === 0)
|
|
93
|
+
process.exit(0);
|
|
94
|
+
});
|
|
95
|
+
process.on('SIGTERM', () => process.exit(0));
|
|
96
|
+
process.on('SIGINT', () => process.exit(0));
|
|
97
|
+
}
|
|
98
|
+
async handleMessage(message) {
|
|
99
|
+
const method = message['method'];
|
|
100
|
+
const id = message['id'];
|
|
101
|
+
if (!method)
|
|
102
|
+
return undefined;
|
|
103
|
+
// Notifications (no id) never receive a response.
|
|
104
|
+
if (id === undefined)
|
|
105
|
+
return undefined;
|
|
106
|
+
switch (method) {
|
|
107
|
+
case 'initialize':
|
|
108
|
+
return {
|
|
109
|
+
jsonrpc: '2.0',
|
|
110
|
+
id,
|
|
111
|
+
result: {
|
|
112
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
113
|
+
capabilities: { tools: {} },
|
|
114
|
+
serverInfo: { name: 'cssprobe-cli', version: this.version },
|
|
115
|
+
instructions: server_instructions_1.SERVER_INSTRUCTIONS,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
case 'ping':
|
|
119
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
120
|
+
case 'tools/list':
|
|
121
|
+
return { jsonrpc: '2.0', id, result: { tools: (0, tools_1.getToolDefinitions)() } };
|
|
122
|
+
case 'tools/call':
|
|
123
|
+
return this.handleToolCall(message, id);
|
|
124
|
+
default:
|
|
125
|
+
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async handleToolCall(message, id) {
|
|
129
|
+
const params = message['params'];
|
|
130
|
+
const toolName = params?.['name'];
|
|
131
|
+
const args = params?.['arguments'] ?? {};
|
|
132
|
+
if (!toolName) {
|
|
133
|
+
return { jsonrpc: '2.0', id, error: { code: -32602, message: 'Missing tool name' } };
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const text = await this.executeTool(toolName, args);
|
|
137
|
+
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }] } };
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
141
|
+
return {
|
|
142
|
+
jsonrpc: '2.0',
|
|
143
|
+
id,
|
|
144
|
+
result: {
|
|
145
|
+
content: [{ type: 'text', text: `cssprobe-cli internal error: ${msg}\n\nThis is an unexpected error — please report it.` }],
|
|
146
|
+
isError: true,
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
executeTool(name, args) {
|
|
152
|
+
switch (name) {
|
|
153
|
+
case 'cssprobe_open': return this.handler.open(args);
|
|
154
|
+
case 'cssprobe_inspect': return this.handler.inspect(args);
|
|
155
|
+
case 'cssprobe_tree': return this.handler.tree(args);
|
|
156
|
+
case 'cssprobe_layout': return this.handler.layout(args);
|
|
157
|
+
case 'cssprobe_findings': return this.handler.findings(args);
|
|
158
|
+
case 'cssprobe_eval': return this.handler.evalExpression(args);
|
|
159
|
+
case 'cssprobe_screenshot': return this.handler.screenshot(args);
|
|
160
|
+
case 'cssprobe_inject_css': return this.handler.injectCss(args);
|
|
161
|
+
case 'cssprobe_close': return this.handler.close(args);
|
|
162
|
+
case 'cssprobe_status': return this.handler.status();
|
|
163
|
+
default: return Promise.resolve(`Unknown tool: ${name}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
send(message) {
|
|
167
|
+
process.stdout.write(JSON.stringify(message) + '\n');
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.MCPServer = MCPServer;
|
|
171
|
+
async function runMcpServer() {
|
|
172
|
+
await new MCPServer().run();
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SERVER_INSTRUCTIONS = "cssprobe-cli \u2014 runtime CSS probe for AI agents.\n\nIt opens a real browser session and answers questions that static code cannot:\nwhy an element overflows, where its scroll chain goes, what it actually renders\nas. Findings carry a confidence level (DEFINITE / INDEFINITE / UNVERIFIABLE) \u2014\nreport UNVERIFIABLE items as \"needs verification\", never as conclusions.\n\nTypical workflow:\n1. cssprobe_open \u2014 start a session (optionally with a URL; headed: true when a\n login is needed, then ask the user to sign in manually).\n2. cssprobe_inspect / cssprobe_layout / cssprobe_findings \u2014 query a selector.\n inspect returns DOM tree + layout + findings; layout is the ASCII diagram;\n findings lists only issues/warnings/errors.\n3. cssprobe_eval / cssprobe_inject_css \u2014 probe or patch the live page.\n4. cssprobe_screenshot \u2014 capture the current page to a PNG file.\n5. cssprobe_close \u2014 stop the session when done.\n\nNotes:\n- One shared \"default\" session: open once, run many queries, close at the end.\n- Any command without an open session returns guidance instead of failing.\n- Pair with cssgraph (static CSS knowledge graph MCP) for the full picture:\n cssgraph finds where styles come from, cssprobe shows what they render as.\n- Prefer narrow selectors (\".card > .title\") to keep output small.";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SERVER_INSTRUCTIONS = void 0;
|
|
4
|
+
exports.SERVER_INSTRUCTIONS = `cssprobe-cli — runtime CSS probe for AI agents.
|
|
5
|
+
|
|
6
|
+
It opens a real browser session and answers questions that static code cannot:
|
|
7
|
+
why an element overflows, where its scroll chain goes, what it actually renders
|
|
8
|
+
as. Findings carry a confidence level (DEFINITE / INDEFINITE / UNVERIFIABLE) —
|
|
9
|
+
report UNVERIFIABLE items as "needs verification", never as conclusions.
|
|
10
|
+
|
|
11
|
+
Typical workflow:
|
|
12
|
+
1. cssprobe_open — start a session (optionally with a URL; headed: true when a
|
|
13
|
+
login is needed, then ask the user to sign in manually).
|
|
14
|
+
2. cssprobe_inspect / cssprobe_layout / cssprobe_findings — query a selector.
|
|
15
|
+
inspect returns DOM tree + layout + findings; layout is the ASCII diagram;
|
|
16
|
+
findings lists only issues/warnings/errors.
|
|
17
|
+
3. cssprobe_eval / cssprobe_inject_css — probe or patch the live page.
|
|
18
|
+
4. cssprobe_screenshot — capture the current page to a PNG file.
|
|
19
|
+
5. cssprobe_close — stop the session when done.
|
|
20
|
+
|
|
21
|
+
Notes:
|
|
22
|
+
- One shared "default" session: open once, run many queries, close at the end.
|
|
23
|
+
- Any command without an open session returns guidance instead of failing.
|
|
24
|
+
- Pair with cssgraph (static CSS knowledge graph MCP) for the full picture:
|
|
25
|
+
cssgraph finds where styles come from, cssprobe shows what they render as.
|
|
26
|
+
- Prefer narrow selectors (".card > .title") to keep output small.`;
|
|
27
|
+
//# sourceMappingURL=server-instructions.js.map
|