@atlisp/mcp 1.8.19 → 1.8.21
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/dist/atlisp-mcp.js +2032 -1365
- package/dist/cad-worker.js +6 -5
- package/dist/config.js +34 -0
- package/dist/handlers/cad-handlers.js +15 -0
- package/dist/handlers/draw-handlers.js +41 -0
- package/dist/handlers/lint-handlers.js +64 -0
- package/dist/handlers/resource-defs.js +2 -0
- package/dist/handlers/resource-handlers.js +92 -23
- package/dist/lisp-security.js +66 -29
- package/dist/pipelines/analyze-and-report.json +16 -0
- package/dist/pipelines/batch-export-pdfs.json +11 -0
- package/dist/pipelines/batch-layer-move.json +19 -0
- package/dist/pipelines/export-current-drawing.json +18 -0
- package/dist/pipelines/purge-and-save.json +16 -0
- package/dist/prompts/definitions/analyze-drawing.json +6 -0
- package/dist/prompts/definitions/batch-draw-lines.json +14 -0
- package/dist/prompts/definitions/coding-conventions.json +11 -0
- package/dist/prompts/definitions/color-convert.json +30 -0
- package/dist/prompts/definitions/curve-analysis.json +26 -0
- package/dist/prompts/definitions/excel-report.json +9 -0
- package/dist/prompts/definitions/export-entities.json +17 -0
- package/dist/prompts/definitions/generate-dimension.json +8 -0
- package/dist/prompts/definitions/geometry-calc.json +9 -0
- package/dist/prompts/definitions/json-exchange.json +22 -0
- package/dist/prompts/definitions/manage-blocks.json +28 -0
- package/dist/prompts/definitions/manage-layers.json +35 -0
- package/dist/prompts/definitions/pickset-filter.json +31 -0
- package/dist/prompts/definitions/string-process.json +40 -0
- package/dist/prompts/definitions/test-autolisp.json +33 -0
- package/dist/prompts/definitions/text-process.json +26 -0
- package/dist/prompts/definitions/workflow-3d-model.json +24 -0
- package/dist/prompts/definitions/workflow-batch-operations.json +24 -0
- package/dist/prompts/definitions/workflow-block-manage.json +32 -0
- package/dist/prompts/definitions/workflow-drawing-export.json +23 -0
- package/dist/prompts/definitions/workflow-entity-analysis.json +23 -0
- package/dist/prompts/definitions/workflow-layer-management.json +38 -0
- package/dist/prompts/definitions/workflow-plot-publish.json +28 -0
- package/dist/prompts/definitions/workflow-sheetset.json +29 -0
- package/dist/prompts/definitions/workflow-text-process.json +29 -0
- package/dist/prompts/definitions/workflow-xref-manage.json +28 -0
- package/package.json +4 -2
package/dist/cad-worker.js
CHANGED
|
@@ -496,8 +496,6 @@ async function waitForIdle(platform) {
|
|
|
496
496
|
return false;
|
|
497
497
|
}
|
|
498
498
|
|
|
499
|
-
export { checkParens } from './lisp-security.js';
|
|
500
|
-
|
|
501
499
|
async function connectToPlatform(targetPlatform) {
|
|
502
500
|
return new Promise((resolve, reject) => {
|
|
503
501
|
cadConnectByName(targetPlatform, (e, r) => {
|
|
@@ -581,9 +579,12 @@ async function handleMessage(msg) {
|
|
|
581
579
|
if (!parenCheck.valid) {
|
|
582
580
|
return { success: false, error: `括号错误: ${parenCheck.error} (位置 ${parenCheck.pos})` };
|
|
583
581
|
}
|
|
584
|
-
const
|
|
585
|
-
if (
|
|
586
|
-
|
|
582
|
+
const securityLevel = process.env.SECURITY_LEVEL || 'strict';
|
|
583
|
+
if (securityLevel !== 'relaxed') {
|
|
584
|
+
const injectionCheck = validateLispCodeWorker(msg.code);
|
|
585
|
+
if (!injectionCheck.valid) {
|
|
586
|
+
return { success: false, error: `安全限制: ${injectionCheck.error}` };
|
|
587
|
+
}
|
|
587
588
|
}
|
|
588
589
|
if (msg.code.length > 255) {
|
|
589
590
|
const cacheEnabled = process.env.LONG_CODE_CACHE === '1';
|
package/dist/config.js
CHANGED
|
@@ -34,6 +34,11 @@ const envSchema = z.object({
|
|
|
34
34
|
LLM_MAX_TOKENS: z.string().optional().default('4096'),
|
|
35
35
|
LLM_TEMPERATURE: z.string().optional().default('0.7'),
|
|
36
36
|
WORKER_POOL_SIZE: z.string().optional().default('2'),
|
|
37
|
+
LINT_PRESET: z.string().optional().default(''),
|
|
38
|
+
LINT_CONFIG_PATH: z.string().optional().default(''),
|
|
39
|
+
LINT_LEVEL: z.string().optional().default(''),
|
|
40
|
+
NO_LINT: z.string().optional().default(''),
|
|
41
|
+
SECURITY_LEVEL: z.string().optional().default('strict'),
|
|
37
42
|
});
|
|
38
43
|
|
|
39
44
|
const configSchema = z.object({
|
|
@@ -73,6 +78,11 @@ const configSchema = z.object({
|
|
|
73
78
|
llmTemperature: z.number().optional(),
|
|
74
79
|
maxPoolSize: z.number().optional(),
|
|
75
80
|
workerPoolSize: z.number().optional(),
|
|
81
|
+
lintPreset: z.string().optional(),
|
|
82
|
+
lintConfigPath: z.string().optional(),
|
|
83
|
+
lintLevel: z.string().optional(),
|
|
84
|
+
noLint: z.boolean().optional(),
|
|
85
|
+
securityLevel: z.enum(['strict', 'standard', 'relaxed']).optional(),
|
|
76
86
|
});
|
|
77
87
|
|
|
78
88
|
function parseArgs() {
|
|
@@ -103,6 +113,20 @@ function parseArgs() {
|
|
|
103
113
|
i++;
|
|
104
114
|
} else if (args[i] === '--debug') {
|
|
105
115
|
result.debug = true;
|
|
116
|
+
} else if (args[i] === '--lint-preset' && args[i + 1]) {
|
|
117
|
+
result.lintPreset = args[i + 1];
|
|
118
|
+
i++;
|
|
119
|
+
} else if (args[i] === '--lint-config' && args[i + 1]) {
|
|
120
|
+
result.lintConfigPath = args[i + 1];
|
|
121
|
+
i++;
|
|
122
|
+
} else if (args[i] === '--lint-level' && args[i + 1]) {
|
|
123
|
+
result.lintLevel = args[i + 1];
|
|
124
|
+
i++;
|
|
125
|
+
} else if (args[i] === '--no-lint' || args[i] === '--lint-off') {
|
|
126
|
+
result.noLint = true;
|
|
127
|
+
} else if (args[i] === '--security-level' && args[i + 1]) {
|
|
128
|
+
result.securityLevel = args[i + 1];
|
|
129
|
+
i++;
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
return result;
|
|
@@ -174,8 +198,18 @@ const config = {
|
|
|
174
198
|
llmTemperature: fileConfig?.llmTemperature || parseFloat(env.LLM_TEMPERATURE),
|
|
175
199
|
maxPoolSize: fileConfig?.maxPoolSize || 3,
|
|
176
200
|
workerPoolSize: fileConfig?.workerPoolSize || parseInt(env.WORKER_POOL_SIZE, 10),
|
|
201
|
+
lintPreset: fileConfig?.lintPreset || cliArgs.lintPreset || env.LINT_PRESET || undefined,
|
|
202
|
+
lintConfigPath: fileConfig?.lintConfigPath || cliArgs.lintConfigPath || env.LINT_CONFIG_PATH || undefined,
|
|
203
|
+
lintLevel: fileConfig?.lintLevel || cliArgs.lintLevel || env.LINT_LEVEL || undefined,
|
|
204
|
+
noLint: fileConfig?.noLint ?? cliArgs.noLint ?? (env.NO_LINT === 'true'),
|
|
205
|
+
securityLevel: fileConfig?.securityLevel ?? cliArgs.securityLevel ?? (env.SECURITY_LEVEL || 'strict'),
|
|
177
206
|
};
|
|
178
207
|
|
|
208
|
+
if (config.lintConfigPath) process.env.LINT_CONFIG_PATH = config.lintConfigPath;
|
|
209
|
+
if (config.lintPreset) process.env.LINT_PRESET = config.lintPreset;
|
|
210
|
+
if (config.lintLevel) process.env.LINT_LEVEL = config.lintLevel;
|
|
211
|
+
if (config.noLint) process.env.NO_LINT = 'true';
|
|
212
|
+
|
|
179
213
|
export function validateConfig() {
|
|
180
214
|
const errors = [];
|
|
181
215
|
if (config.transport === 'http' || config.transport === 'ws') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cad } from '../cad.js';
|
|
2
2
|
import { log } from '../logger.js';
|
|
3
3
|
import { withCadConnection, mcpSuccess, mcpError, escapeLispString } from '../handler-utils.js';
|
|
4
|
+
import { analyzeLispCode } from '../lint-analyzer.js';
|
|
4
5
|
|
|
5
6
|
export async function connectCad(platform = null) {
|
|
6
7
|
log(`connect_cad called${platform ? ` (target: ${platform})` : ''}, cad.connected before: ${cad.connected}`);
|
|
@@ -23,6 +24,13 @@ export async function connectCad(platform = null) {
|
|
|
23
24
|
export const evalLisp = withCadConnection(async (code, withResult = false, encoding = null) => {
|
|
24
25
|
const trimmed = (code || '').trim();
|
|
25
26
|
if (!trimmed) return mcpSuccess('命令为空,未发送');
|
|
27
|
+
|
|
28
|
+
const analysis = analyzeLispCode(trimmed);
|
|
29
|
+
if (!analysis.valid) {
|
|
30
|
+
const detail = analysis.errors.map(e => ` L${e.line}: [${e.rule}] ${e.message}`).join('\n');
|
|
31
|
+
return mcpError(`代码存在安全问题:\n${detail}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
if (withResult) {
|
|
27
35
|
const result = await cad.sendCommandWithResult(trimmed, encoding);
|
|
28
36
|
if (result !== null) {
|
|
@@ -42,6 +50,13 @@ export const getCadInfo = withCadConnection(async () => {
|
|
|
42
50
|
export const atCommand = withCadConnection(async (command) => {
|
|
43
51
|
const trimmed = (command || '').trim();
|
|
44
52
|
if (!trimmed) return mcpSuccess('命令为空,未发送');
|
|
53
|
+
|
|
54
|
+
const analysis = analyzeLispCode(trimmed);
|
|
55
|
+
if (!analysis.valid) {
|
|
56
|
+
const detail = analysis.errors.map(e => ` L${e.line}: [${e.rule}] ${e.message}`).join('\n');
|
|
57
|
+
return mcpError(`代码存在安全问题:\n${detail}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
await cad.sendCommand(trimmed + '\n');
|
|
46
61
|
return mcpSuccess('已发送命令');
|
|
47
62
|
});
|
|
@@ -85,3 +85,44 @@ export const drawHatch = withCadConnection(async (points, pattern) => {
|
|
|
85
85
|
await cad.sendCommand(`(command "_.HATCH" "${escapeLispString(pat)}" "" "")`);
|
|
86
86
|
return mcpSuccess(`已创建图案填充: ${pat}`);
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
export const drawHelix = withCadConnection(async (center, topRadius, bottomRadius, height, turns = 3) => {
|
|
90
|
+
await cad.sendCommand(`(command "_.HELIX" "${escapeLispString(center)}" ${bottomRadius} ${topRadius} ${height} ${turns})`);
|
|
91
|
+
return mcpSuccess(`已创建螺旋: 中心 ${center},半径 ${bottomRadius}→${topRadius},高度 ${height},${turns} 圈`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
export const drawDonut = withCadConnection(async (center, innerRadius, outerRadius) => {
|
|
95
|
+
await cad.sendCommand(`(command "_.DONUT" ${innerRadius} ${outerRadius} "${escapeLispString(center)}" "")`);
|
|
96
|
+
return mcpSuccess(`已绘制圆环: 中心 ${center},内径 ${innerRadius},外径 ${outerRadius}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
export const draw2dSolid = withCadConnection(async (p1, p2, p3, p4) => {
|
|
100
|
+
await cad.sendCommand(`(command "_.SOLID" "${escapeLispString(p1)}" "${escapeLispString(p2)}" "${escapeLispString(p3)}" "${escapeLispString(p4)}" "")`);
|
|
101
|
+
return mcpSuccess(`已创建 2D 实体: ${p1} ${p2} ${p3} ${p4}`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
export const drawXline = withCadConnection(async (point, direction) => {
|
|
105
|
+
await cad.sendCommand(`(command "_.XLINE" "${escapeLispString(point)}" "${escapeLispString(direction)}")`);
|
|
106
|
+
return mcpSuccess(`已创建构造线: 经过 ${point},方向 ${direction}`);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const drawRay = withCadConnection(async (point, direction) => {
|
|
110
|
+
await cad.sendCommand(`(command "_.RAY" "${escapeLispString(point)}" "${escapeLispString(direction)}")`);
|
|
111
|
+
return mcpSuccess(`已创建射线: 起点 ${point},方向 ${direction}`);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export const drawLeader = withCadConnection(async (points, annotation) => {
|
|
115
|
+
const pts = points.split(/\s+/).filter(Boolean);
|
|
116
|
+
const cmds = pts.map(p => `"${escapeLispString(p)}"`).join(' ');
|
|
117
|
+
if (annotation) {
|
|
118
|
+
await cad.sendCommand(`(command "_.LEADER" ${cmds} "" "${escapeLispString(annotation)}" "")`);
|
|
119
|
+
} else {
|
|
120
|
+
await cad.sendCommand(`(command "_.LEADER" ${cmds} "" "" "")`);
|
|
121
|
+
}
|
|
122
|
+
return mcpSuccess(`已创建引线标注: ${points}`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
export const drawTable = withCadConnection(async (insertionPoint, rows, columns, rowHeight = 10, colWidth = 20) => {
|
|
126
|
+
await cad.sendCommand(`(command "_.TABLE" "${escapeLispString(insertionPoint)}" ${columns} ${rows} ${rowHeight} ${colWidth})`);
|
|
127
|
+
return mcpSuccess(`已创建表格: ${insertionPoint},${rows}×${columns}`);
|
|
128
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { analyzeLispCode } from '../lint-analyzer.js';
|
|
2
|
+
import { formatCode } from '@atlisp/lint/dist/formatter.js';
|
|
3
|
+
import { lintProject } from '@atlisp/lint/dist/project.js';
|
|
4
|
+
import { RULES } from '@atlisp/lint/dist/rules.js';
|
|
5
|
+
import { loadConfig } from '@atlisp/lint/dist/config.js';
|
|
6
|
+
|
|
7
|
+
function getConfig(input) {
|
|
8
|
+
const base = loadConfig();
|
|
9
|
+
if (input?.checks) Object.assign(base.checks, input.checks);
|
|
10
|
+
if (input?.dangerous_calls) Object.assign(base.dangerous_calls, input.dangerous_calls);
|
|
11
|
+
return base;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function analyzeLispCodeHandler(args) {
|
|
15
|
+
const { code, config } = args || {};
|
|
16
|
+
if (!code || !code.trim()) {
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: 'text', text: JSON.stringify({ valid: true, issues: [], summary: '代码为空,无需分析' }) }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const result = analyzeLispCode(code, { lintConfig: config });
|
|
22
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function formatLispCodeHandler(args) {
|
|
26
|
+
const { code, tabWidth } = args || {};
|
|
27
|
+
if (!code) {
|
|
28
|
+
return { content: [{ type: 'text', text: '需要 code 参数' }], isError: true };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const formatted = formatCode(code, { tabWidth: tabWidth || 2 });
|
|
32
|
+
return { content: [{ type: 'text', text: formatted }] };
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return { content: [{ type: 'text', text: `格式化失败: ${e.message}` }], isError: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function listLintRulesHandler() {
|
|
39
|
+
try {
|
|
40
|
+
const list = RULES.map(r => ({
|
|
41
|
+
rule: r.name,
|
|
42
|
+
severity: r.severity || 'warn',
|
|
43
|
+
description: r.description || '',
|
|
44
|
+
fixable: !!r.fix,
|
|
45
|
+
}));
|
|
46
|
+
return { content: [{ type: 'text', text: JSON.stringify(list, null, 2) }] };
|
|
47
|
+
} catch (e) {
|
|
48
|
+
return { content: [{ type: 'text', text: `获取规则列表失败: ${e.message}` }], isError: true };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function lintProjectHandler(args) {
|
|
53
|
+
const { directory, config: configInput } = args || {};
|
|
54
|
+
if (!directory) {
|
|
55
|
+
return { content: [{ type: 'text', text: '需要 directory 参数' }], isError: true };
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const lintConfig = getConfig(configInput);
|
|
59
|
+
const results = await lintProject(directory, lintConfig);
|
|
60
|
+
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
61
|
+
} catch (e) {
|
|
62
|
+
return { content: [{ type: 'text', text: `项目分析失败: ${e.message}` }], isError: true };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -20,6 +20,8 @@ export const RESOURCES = [
|
|
|
20
20
|
{ uri: 'atlisp://platforms', name: 'Platforms', description: '支持的平台信息', mimeType: 'application/json', subscribable: false },
|
|
21
21
|
{ uri: 'atlisp://standards/drafting', name: 'Drafting Standards', description: 'CAD 制图规范', mimeType: 'application/json', subscribable: false },
|
|
22
22
|
{ uri: 'atlisp://standards/coding', name: 'Coding Standards', description: '@lisp 编码规范', mimeType: 'application/json', subscribable: false },
|
|
23
|
+
{ uri: 'atlisp://dwg/context', name: 'DWG Context', description: '当前图纸上下文摘要。返回文档名/路径/布局/UCS/图层列表/实体统计/系统变量组合信息,帮助快速了解当前 CAD 状态', mimeType: 'application/json', subscribable: true },
|
|
24
|
+
{ uri: 'atlisp://tools/relationships', name: 'Tool Relationships', description: '工具依赖关系图。描述工具间的调用链关系(如 select_entities → batch_delete),帮助理解工具组合使用方式', mimeType: 'application/json', subscribable: false },
|
|
23
25
|
];
|
|
24
26
|
|
|
25
27
|
export const TBL_INFO = {
|
|
@@ -1047,6 +1047,90 @@ async function getCadEvents(params) {
|
|
|
1047
1047
|
* Fetches a lightweight snapshot of current CAD state.
|
|
1048
1048
|
* Returns a flat object with entity count, layer names, DWG info, etc.
|
|
1049
1049
|
*/
|
|
1050
|
+
async function getDwgContext() {
|
|
1051
|
+
const snapshot = await getCadSnapshot();
|
|
1052
|
+
if (!snapshot || !snapshot.cadConnected) {
|
|
1053
|
+
return { connected: false, message: 'CAD 未连接' };
|
|
1054
|
+
}
|
|
1055
|
+
try {
|
|
1056
|
+
const code = `(progn
|
|
1057
|
+
(vl-load-com)
|
|
1058
|
+
(setq ctx nil)
|
|
1059
|
+
(setq ctx (cons (cons "layout" (getvar "ctab")) ctx))
|
|
1060
|
+
(setq ctx (cons (cons "ucs" (getvar "ucsname")) ctx))
|
|
1061
|
+
(setq ctx (cons (cons "insunits" (getvar "insunits")) ctx))
|
|
1062
|
+
(setq ctx (cons (cons "ltscale" (getvar "ltscale")) ctx))
|
|
1063
|
+
(setq ctx (cons (cons "limmin" (vl-princ-to-string (getvar "limmin"))) ctx))
|
|
1064
|
+
(setq ctx (cons (cons "limmax" (vl-princ-to-string (getvar "limmax"))) ctx))
|
|
1065
|
+
(setq ctx (cons (cons "extmin" (vl-princ-to-string (getvar "extmin"))) ctx))
|
|
1066
|
+
(setq ctx (cons (cons "extmax" (vl-princ-to-string (getvar "extmax"))) ctx))
|
|
1067
|
+
(vl-prin1-to-string ctx))`;
|
|
1068
|
+
const resultStr = await cad.sendCommandWithResult(code);
|
|
1069
|
+
const parsed = parseLispRecursive(resultStr);
|
|
1070
|
+
return {
|
|
1071
|
+
connected: true,
|
|
1072
|
+
docName: snapshot.dwgName,
|
|
1073
|
+
docPath: snapshot.dwgPath,
|
|
1074
|
+
platform: snapshot.platform,
|
|
1075
|
+
entityCount: snapshot.entityCount,
|
|
1076
|
+
layerCount: snapshot.layerCount,
|
|
1077
|
+
layerNames: snapshot.layerNames,
|
|
1078
|
+
extra: parsed && Array.isArray(parsed) ? Object.fromEntries(parsed) : {},
|
|
1079
|
+
};
|
|
1080
|
+
} catch {
|
|
1081
|
+
return {
|
|
1082
|
+
connected: true,
|
|
1083
|
+
docName: snapshot.dwgName,
|
|
1084
|
+
docPath: snapshot.dwgPath,
|
|
1085
|
+
platform: snapshot.platform,
|
|
1086
|
+
entityCount: snapshot.entityCount,
|
|
1087
|
+
layerCount: snapshot.layerCount,
|
|
1088
|
+
layerNames: snapshot.layerNames,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
function getToolRelationships() {
|
|
1094
|
+
return {
|
|
1095
|
+
selection: {
|
|
1096
|
+
tools: ['select_entities', 'select_all', 'select_last', 'select_by_color', 'find_entities_in_window', 'find_entities_near_point'],
|
|
1097
|
+
next: ['batch_delete', 'batch_move', 'batch_copy', 'batch_set_layer', 'batch_set_color', 'batch_set_linetype', 'batch_rotate', 'batch_scale', 'batch_mirror', 'batch_explode', 'batch_match_properties'],
|
|
1098
|
+
},
|
|
1099
|
+
drawing: {
|
|
1100
|
+
tools: ['draw_line', 'draw_circle', 'draw_arc', 'draw_rectangle', 'draw_polygon', 'draw_polyline', 'draw_spline', 'draw_text', 'draw_mtext', 'draw_ellipse'],
|
|
1101
|
+
next: ['get_entity', 'set_entity', 'batch_match_properties', 'copy_entity', 'move_entity', 'rotate_entity', 'scale_entity'],
|
|
1102
|
+
},
|
|
1103
|
+
editing: {
|
|
1104
|
+
tools: ['trim_entity', 'extend_entity', 'fillet', 'chamfer', 'break_entity', 'join_entities', 'stretch', 'offset_entity', 'mirror_entity'],
|
|
1105
|
+
next: ['get_entity', 'set_entity'],
|
|
1106
|
+
},
|
|
1107
|
+
batch_ops: {
|
|
1108
|
+
tools: ['batch_copy', 'batch_move', 'batch_delete', 'batch_set_layer', 'batch_set_color', 'batch_explode', 'batch_rotate', 'batch_scale'],
|
|
1109
|
+
previous: ['select_entities', 'select_all'],
|
|
1110
|
+
next: ['save_dwg'],
|
|
1111
|
+
},
|
|
1112
|
+
export: {
|
|
1113
|
+
tools: ['export_pdf', 'export_dxf', 'export_dwf', 'export_svg', 'export_stl', 'export_bmp'],
|
|
1114
|
+
previous: ['open_dwg', 'new_document', 'save_dwg'],
|
|
1115
|
+
next: [],
|
|
1116
|
+
},
|
|
1117
|
+
layers: {
|
|
1118
|
+
tools: ['create_layer', 'set_layer', 'layer_isolate', 'layer_freeze_all', 'layer_thaw_all', 'layer_lock_all', 'layer_unlock_all', 'layer_merge'],
|
|
1119
|
+
next: ['set_current_layer', 'batch_set_layer'],
|
|
1120
|
+
},
|
|
1121
|
+
blocks: {
|
|
1122
|
+
tools: ['get_blocks', 'insert_block', 'explode_block', 'get_block_attributes', 'set_block_attribute', 'get_dynamic_block_properties'],
|
|
1123
|
+
previous: ['select_entities'],
|
|
1124
|
+
next: ['save_dwg'],
|
|
1125
|
+
},
|
|
1126
|
+
analysis: {
|
|
1127
|
+
tools: ['analyze_lisp_code', 'stream_entities', 'stream_text', 'stream_block_refs', 'get_entity_info', 'get_bounding_box', 'measure_distance', 'measure_area'],
|
|
1128
|
+
previous: [],
|
|
1129
|
+
next: [],
|
|
1130
|
+
},
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1050
1134
|
async function getCadSnapshot() {
|
|
1051
1135
|
const cached = getCached('cad:snapshot');
|
|
1052
1136
|
if (cached) return cached;
|
|
@@ -1198,45 +1282,26 @@ export async function listResources(subscribedUris = []) {
|
|
|
1198
1282
|
};
|
|
1199
1283
|
|
|
1200
1284
|
if (hasLive) {
|
|
1201
|
-
// Enrich resource descriptors with live CAD state
|
|
1202
1285
|
switch (r.uri) {
|
|
1203
1286
|
case 'atlisp://cad/info':
|
|
1204
1287
|
entry.description = `CAD 连接信息 (${snapshot.platform || '未知平台'}${snapshot.dwgName ? ', ' + snapshot.dwgName : ''})`;
|
|
1205
|
-
entry.annotations = {
|
|
1206
|
-
platform: snapshot.platform,
|
|
1207
|
-
dwgName: snapshot.dwgName,
|
|
1208
|
-
cadConnected: true,
|
|
1209
|
-
};
|
|
1210
1288
|
break;
|
|
1211
|
-
|
|
1212
1289
|
case 'atlisp://dwg/entities':
|
|
1213
1290
|
entry.description = `图元列表。${snapshot.entityCount} 个实体,${snapshot.layerCount} 个图层。支持 ?type=LINE&layer=0&bbox=0,0,100,100`;
|
|
1214
|
-
entry.size = snapshot.entityCount;
|
|
1215
|
-
entry.annotations = {
|
|
1216
|
-
entityCount: snapshot.entityCount,
|
|
1217
|
-
layerCount: snapshot.layerCount,
|
|
1218
|
-
};
|
|
1219
1291
|
break;
|
|
1220
|
-
|
|
1221
1292
|
case 'atlisp://dwg/name':
|
|
1222
1293
|
entry.description = `当前 DWG 文件名: ${snapshot.dwgName || '(未命名)'}`;
|
|
1223
|
-
entry.annotations = { name: snapshot.dwgName };
|
|
1224
1294
|
break;
|
|
1225
|
-
|
|
1226
1295
|
case 'atlisp://dwg/path':
|
|
1227
|
-
entry.description = snapshot.dwgPath
|
|
1228
|
-
? `文件完整路径: ${snapshot.dwgPath}`
|
|
1229
|
-
: '文件完整路径';
|
|
1230
|
-
entry.annotations = { path: snapshot.dwgPath };
|
|
1296
|
+
entry.description = snapshot.dwgPath ? `文件完整路径: ${snapshot.dwgPath}` : '文件完整路径';
|
|
1231
1297
|
break;
|
|
1232
|
-
|
|
1233
1298
|
case 'atlisp://dwg/tbl':
|
|
1234
1299
|
entry.description = `CAD 表数据 (${snapshot.layerCount} 个图层)。支持路径方式 atlisp://dwg/tbl/layer`;
|
|
1235
|
-
entry.annotations = { layerCount: snapshot.layerCount };
|
|
1236
1300
|
break;
|
|
1237
|
-
|
|
1238
|
-
|
|
1301
|
+
case 'atlisp://dwg/context':
|
|
1302
|
+
entry.description = `当前图纸上下文: ${snapshot.dwgName || '(未命名)'},${snapshot.entityCount} 实体,${snapshot.layerCount} 图层`;
|
|
1239
1303
|
break;
|
|
1304
|
+
default: break;
|
|
1240
1305
|
}
|
|
1241
1306
|
}
|
|
1242
1307
|
|
|
@@ -1316,6 +1381,10 @@ export async function readResource(uri) {
|
|
|
1316
1381
|
return getCodingStandards();
|
|
1317
1382
|
case 'atlisp://cad/events':
|
|
1318
1383
|
return await getCadEvents(params);
|
|
1384
|
+
case 'atlisp://dwg/context':
|
|
1385
|
+
return await getDwgContext();
|
|
1386
|
+
case 'atlisp://tools/relationships':
|
|
1387
|
+
return getToolRelationships();
|
|
1319
1388
|
default:
|
|
1320
1389
|
throw new Error(`资源不存在: ${uri}`);
|
|
1321
1390
|
}
|
package/dist/lisp-security.js
CHANGED
|
@@ -1,53 +1,90 @@
|
|
|
1
1
|
import { log, error as logError } from './logger.js';
|
|
2
|
+
import config, { onConfigChange } from './config.js';
|
|
2
3
|
|
|
3
4
|
export const THREAT_BLOCK = 'block';
|
|
4
5
|
export const THREAT_WARN = 'warn';
|
|
5
6
|
export const THREAT_ALLOW = 'allow';
|
|
6
7
|
|
|
8
|
+
export const SECURITY_LEVELS = ['strict', 'standard', 'relaxed'];
|
|
9
|
+
|
|
10
|
+
const CVT = {
|
|
11
|
+
strict: THREAT_BLOCK,
|
|
12
|
+
standard: THREAT_WARN,
|
|
13
|
+
relaxed: THREAT_ALLOW,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getLevel() {
|
|
17
|
+
const level = config?.securityLevel || 'strict';
|
|
18
|
+
return CVT[level] || THREAT_BLOCK;
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export const SECURITY_RULES = [
|
|
8
|
-
{ re: /\
|
|
9
|
-
{ re: /\
|
|
10
|
-
{ re: /\
|
|
11
|
-
{ re: /\
|
|
12
|
-
{ re: /\
|
|
13
|
-
{ re: /\
|
|
14
|
-
{ re: /\
|
|
15
|
-
{ re: /\
|
|
16
|
-
{ re: /\
|
|
17
|
-
{ re: /\
|
|
18
|
-
{ re: /\
|
|
19
|
-
{ re: /\
|
|
20
|
-
{ re: /\
|
|
21
|
-
{ re: /\bvl-
|
|
22
|
-
{ re: /\
|
|
23
|
-
{ re: /\
|
|
24
|
-
{ re: /\
|
|
25
|
-
{ re: /\
|
|
26
|
-
{ re: /\
|
|
27
|
-
{ re: /\
|
|
28
|
-
{ re: /\bvla-
|
|
22
|
+
{ re: /\bstartapp\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 startapp 函数' },
|
|
23
|
+
{ re: /\bvl-bt\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-bt 函数' },
|
|
24
|
+
{ re: /\bvlax-import-type-library\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止导入类型库' },
|
|
25
|
+
{ re: /\bdos_command_string\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 dos_command_string' },
|
|
26
|
+
{ re: /\beval\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 eval 函数' },
|
|
27
|
+
{ re: /\bcommand\s*["\s]*[_(]?\s*["']?\.?\s*_?(?:shell|quit|exit|close|open|new|recover|audit|purge|wblock|export)/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用危险 CAD 命令' },
|
|
28
|
+
{ re: /\bload\s*[(\s"]/i, baseSeverity: THREAT_BLOCK, msg: 'load 函数可能加载外部代码' },
|
|
29
|
+
{ re: /\b(?:read-line|read-char|write-line|write-char)\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '文件 I/O 操作可能影响系统' },
|
|
30
|
+
{ re: /\bvl-file-copy\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件复制' },
|
|
31
|
+
{ re: /\bvl-file-delete\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件删除' },
|
|
32
|
+
{ re: /\bvl-file-rename\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止文件重命名' },
|
|
33
|
+
{ re: /\bvl-registry-write[\s\)]/i, baseSeverity: THREAT_BLOCK, msg: 'vl-registry-write 会修改注册表' },
|
|
34
|
+
{ re: /\bvl-exit-with-value\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-value' },
|
|
35
|
+
{ re: /\bvl-exit-with-error\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 vl-exit-with-error' },
|
|
36
|
+
{ re: /\bvlax-add-cmd\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止注册新命令' },
|
|
37
|
+
{ re: /\bvla-Delete\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接删除 vla 对象' },
|
|
38
|
+
{ re: /\bvla-Save\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止直接保存文档' },
|
|
39
|
+
{ re: /\bdirectory\s*[(\s]/i, baseSeverity: THREAT_BLOCK, msg: '禁止使用 directory 函数' },
|
|
40
|
+
{ re: /\bvla-deletefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止删除文件' },
|
|
41
|
+
{ re: /\bvla-copyfile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止复制文件' },
|
|
42
|
+
{ re: /\bvla-movefile\b/i, baseSeverity: THREAT_BLOCK, msg: '禁止移动文件' },
|
|
29
43
|
];
|
|
30
44
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
let _cachedLevel = null;
|
|
46
|
+
|
|
47
|
+
export function refreshConfig() {
|
|
48
|
+
_cachedLevel = null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
onConfigChange(() => { _cachedLevel = null; });
|
|
53
|
+
} catch (_) {}
|
|
54
|
+
|
|
55
|
+
function resolveSeverity(rule) {
|
|
56
|
+
if (_cachedLevel) return _cachedLevel;
|
|
57
|
+
_cachedLevel = getLevel();
|
|
58
|
+
return _cachedLevel;
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
export function validateLispCodeWorker(code) {
|
|
37
62
|
const clean = stripStringsAndComments(code);
|
|
38
|
-
|
|
63
|
+
const effectiveSeverity = resolveSeverity();
|
|
64
|
+
|
|
65
|
+
for (const rule of SECURITY_RULES) {
|
|
66
|
+
const re = rule.re;
|
|
39
67
|
if (re.test(clean)) {
|
|
40
|
-
if (
|
|
41
|
-
return { valid: false, error: msg };
|
|
68
|
+
if (effectiveSeverity === THREAT_BLOCK) {
|
|
69
|
+
return { valid: false, error: rule.msg };
|
|
42
70
|
}
|
|
43
|
-
if (
|
|
44
|
-
log('WARN: ' + msg);
|
|
71
|
+
if (effectiveSeverity === THREAT_WARN) {
|
|
72
|
+
log('WARN: ' + rule.msg);
|
|
45
73
|
}
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
return { valid: true };
|
|
49
77
|
}
|
|
50
78
|
|
|
79
|
+
export function getEffectiveSeverity() {
|
|
80
|
+
return resolveSeverity();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function stripStringsAndComments(code) {
|
|
84
|
+
const withoutStrings = code.replace(/"([^"\\]*(?:\\.[^"\\]*)*)"/g, '');
|
|
85
|
+
return withoutStrings.replace(/;.*$/gm, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
51
88
|
export function checkParens(code) {
|
|
52
89
|
let depth = 0;
|
|
53
90
|
let inString = false;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "analyze-and-report",
|
|
3
|
+
"description": "分析当前图纸实体并生成统计报告。先获取所有实体,再逐个获取详细信息",
|
|
4
|
+
"steps": [
|
|
5
|
+
{
|
|
6
|
+
"name": "entities",
|
|
7
|
+
"tool": "stream_entities",
|
|
8
|
+
"args": { "chunk": 500, "offset": 0 }
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "info",
|
|
12
|
+
"tool": "get_cad_info",
|
|
13
|
+
"args": {}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "batch-layer-move",
|
|
3
|
+
"description": "按条件选择实体并移动到目标图层。先选择指定类型/图层的实体,再移动到新图层",
|
|
4
|
+
"steps": [
|
|
5
|
+
{
|
|
6
|
+
"name": "selection",
|
|
7
|
+
"tool": "select_entities",
|
|
8
|
+
"args": { "filter": { "type": "LINE", "layer": "0" } }
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "move",
|
|
12
|
+
"tool": "batch_set_layer",
|
|
13
|
+
"args": {
|
|
14
|
+
"handles": "$ref:selection.result",
|
|
15
|
+
"layerName": "TargetLayer"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "export-current-drawing",
|
|
3
|
+
"description": "导出当前图纸为 PDF。先获取图纸信息,再执行导出",
|
|
4
|
+
"steps": [
|
|
5
|
+
{
|
|
6
|
+
"name": "info",
|
|
7
|
+
"tool": "get_cad_info",
|
|
8
|
+
"args": {}
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"name": "export",
|
|
12
|
+
"tool": "export_pdf",
|
|
13
|
+
"args": {
|
|
14
|
+
"outputPath": "$ref:info.docName.pdf"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "batch-draw-lines",
|
|
3
|
+
"description": "批量绘制线段",
|
|
4
|
+
"arguments": [
|
|
5
|
+
{ "name": "count", "description": "线段数量", "required": true, "default": 5 },
|
|
6
|
+
{ "name": "length", "description": "线段长度", "required": false, "default": 1000 }
|
|
7
|
+
],
|
|
8
|
+
"templates": {
|
|
9
|
+
"default": {
|
|
10
|
+
"code": "(defun c:batch-lines (/ i)\n (setq i 0)\n (repeat {{count}}\n (command \"_.line\" (list (* i {{length}}) 0) (list (* (1+ i) {{length}}) 0) \"\")\n (setq i (1+ i))\n )\n (princ)\n)\n(c:batch-lines)",
|
|
11
|
+
"description": "批量绘制 {{count}} 条长度为 {{length}} 的水平线段:\n\n此代码将绘制 {{count}} 条首尾相连的水平线段。"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coding-conventions",
|
|
3
|
+
"description": "AutoLISP 编码规范",
|
|
4
|
+
"arguments": [],
|
|
5
|
+
"templates": {
|
|
6
|
+
"default": {
|
|
7
|
+
"code": "",
|
|
8
|
+
"description": "## AutoLISP 编码规范\n\n### 禁止作为变量名的内置函数\n\n以下 AutoLISP 内置函数不可用作变量名,否则会覆盖内置函数导致程序错误:\n\n| 变量名 | 内置函数 | 说明 |\n|--------|----------|------|\n| `angle` | `(angle pt1 pt2)` | 严禁使用!获取两点连线与X轴夹角 |\n| `length` | `(length list)` | 获取列表长度 |\n| `type` | `(type symbol)` | 获取数据类型 |\n| `assoc` | `(assoc key alist)` | 关联表查找 |\n| `member` | `(member item list)` | 成员检测 |\n| `subst` | `(subst new old list)` | 替换列表元素 |\n| `append` | `(append list ...)` | 合并列表 |\n| `reverse` | `(reverse list)` | 反转列表 |\n| `apply` | `(apply fn list)` | 应用函数 |\n| `mapcar` | `(mapcar fn list...)` | 映射函数 |\n\n### 正确做法\n\n```lisp\n;; 错误:覆盖了内置 angle 函数\n(setq angle 45)\n\n;; 正确:使用有意义的变量名\n(setq rot-angle 45)\n(setq rotation 45)\n(setq ang 45)\n```"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|