@yinuo-ngm/mcp-server 0.1.2 → 0.1.4
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 +191 -208
- package/lib/audit/audit-event.d.ts +14 -0
- package/lib/audit/audit-event.js +2 -0
- package/lib/audit/audit-log.service.d.ts +7 -0
- package/lib/audit/audit-log.service.js +187 -0
- package/lib/audit/redact.d.ts +3 -0
- package/lib/audit/redact.js +28 -0
- package/lib/catalog/capabilities/blocked-local-actions.d.ts +1 -0
- package/lib/catalog/capabilities/blocked-local-actions.js +18 -0
- package/lib/catalog/capabilities/frontend-standard.d.ts +2 -0
- package/lib/catalog/capabilities/frontend-standard.js +36 -0
- package/lib/catalog/capabilities/hub-v2.d.ts +2 -0
- package/lib/catalog/capabilities/hub-v2.js +34 -0
- package/lib/catalog/capabilities/nginx.d.ts +2 -0
- package/lib/catalog/capabilities/nginx.js +23 -0
- package/lib/catalog/capabilities/project.d.ts +2 -0
- package/lib/catalog/capabilities/project.js +23 -0
- package/lib/catalog/capabilities/router.d.ts +2 -0
- package/lib/catalog/capabilities/router.js +11 -0
- package/lib/catalog/capabilities/runtime.d.ts +2 -0
- package/lib/catalog/capabilities/runtime.js +17 -0
- package/lib/catalog/capabilities/workspace.d.ts +2 -0
- package/lib/catalog/capabilities/workspace.js +23 -0
- package/lib/catalog/helpers.d.ts +3 -0
- package/lib/catalog/helpers.js +42 -0
- package/lib/catalog/index.d.ts +4 -0
- package/lib/catalog/index.js +23 -0
- package/lib/catalog/tools/frontend-standard.d.ts +2 -0
- package/lib/catalog/tools/frontend-standard.js +166 -0
- package/lib/catalog/tools/hub-v2-api.d.ts +2 -0
- package/lib/catalog/tools/hub-v2-api.js +124 -0
- package/lib/catalog/tools/hub-v2-docs.d.ts +2 -0
- package/lib/catalog/tools/hub-v2-docs.js +40 -0
- package/lib/catalog/tools/nginx.d.ts +2 -0
- package/lib/catalog/tools/nginx.js +82 -0
- package/lib/catalog/tools/project.d.ts +2 -0
- package/lib/catalog/tools/project.js +124 -0
- package/lib/catalog/tools/router.d.ts +2 -0
- package/lib/catalog/tools/router.js +26 -0
- package/lib/catalog/tools/runtime.d.ts +2 -0
- package/lib/catalog/tools/runtime.js +40 -0
- package/lib/catalog/tools/workspace.d.ts +2 -0
- package/lib/catalog/tools/workspace.js +75 -0
- package/lib/catalog/types.d.ts +15 -0
- package/lib/catalog/types.js +2 -0
- package/lib/context/create-tool-context.js +11 -10
- package/lib/context/local-server-client.d.ts +2 -0
- package/lib/context/local-server-client.js +174 -0
- package/lib/context/tool-context.d.ts +36 -0
- package/lib/doctor.d.ts +8 -0
- package/lib/doctor.js +194 -0
- package/lib/errors/error-codes.d.ts +12 -0
- package/lib/errors/error-codes.js +14 -0
- package/lib/errors/mcp-tool-error.d.ts +8 -0
- package/lib/errors/mcp-tool-error.js +14 -0
- package/lib/filesystem/project-files.d.ts +18 -0
- package/lib/filesystem/project-files.js +112 -0
- package/lib/git/local-git-read-service.d.ts +2 -0
- package/lib/git/local-git-read-service.js +96 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +4 -0
- package/lib/policy/assert-tool-policy.js +10 -1
- package/lib/register-tools.js +67 -10
- package/lib/registry/tool-names.d.ts +95 -0
- package/lib/registry/tool-names.js +97 -0
- package/lib/services/path-guard.service.d.ts +4 -0
- package/lib/services/path-guard.service.js +75 -0
- package/lib/services/permission.service.d.ts +5 -0
- package/lib/services/permission.service.js +38 -0
- package/lib/services/project-resolver.service.d.ts +32 -0
- package/lib/services/project-resolver.service.js +95 -0
- package/lib/standard/frontend-standard.default.d.ts +2 -0
- package/lib/standard/frontend-standard.default.js +51 -0
- package/lib/standard/frontend-standard.schema.d.ts +196 -0
- package/lib/standard/frontend-standard.schema.js +61 -0
- package/lib/standard/frontend-standard.service.d.ts +79 -0
- package/lib/standard/frontend-standard.service.js +115 -0
- package/lib/standard/project-scan.d.ts +9 -0
- package/lib/standard/project-scan.js +91 -0
- package/lib/standard/validators/angular-structure.validator.d.ts +4 -0
- package/lib/standard/validators/angular-structure.validator.js +75 -0
- package/lib/standard/validators/component.validator.d.ts +4 -0
- package/lib/standard/validators/component.validator.js +94 -0
- package/lib/standard/validators/git.validator.d.ts +8 -0
- package/lib/standard/validators/git.validator.js +32 -0
- package/lib/standard/validators/review.validator.d.ts +15 -0
- package/lib/standard/validators/review.validator.js +67 -0
- package/lib/standard/validators/test.validator.d.ts +19 -0
- package/lib/standard/validators/test.validator.js +89 -0
- package/lib/tool-catalog.d.ts +2 -0
- package/lib/tool-catalog.js +6 -0
- package/lib/tools/angular/angular-standard.tools.d.ts +2 -0
- package/lib/tools/angular/angular-standard.tools.js +53 -0
- package/lib/tools/angular/index.d.ts +1 -0
- package/lib/tools/angular/index.js +5 -0
- package/lib/tools/capability.tools.d.ts +2 -0
- package/lib/tools/capability.tools.js +205 -0
- package/lib/tools/controlled/index.d.ts +2 -0
- package/lib/tools/controlled/index.js +13 -0
- package/lib/tools/controlled/local-server.d.ts +6 -0
- package/lib/tools/controlled/local-server.js +17 -0
- package/lib/tools/controlled/operation-policy.d.ts +22 -0
- package/lib/tools/controlled/operation-policy.js +50 -0
- package/lib/tools/controlled/operation-result.d.ts +30 -0
- package/lib/tools/controlled/operation-result.js +33 -0
- package/lib/tools/controlled/schemas.d.ts +159 -0
- package/lib/tools/controlled/schemas.js +49 -0
- package/lib/tools/controlled.tools.d.ts +1 -0
- package/lib/tools/controlled.tools.js +5 -0
- package/lib/tools/file-write.tools.d.ts +2 -0
- package/lib/tools/file-write.tools.js +70 -0
- package/lib/tools/git.tools.js +109 -8
- package/lib/tools/hub-v2/client.d.ts +6 -1
- package/lib/tools/hub-v2/client.js +15 -0
- package/lib/tools/hub-v2/config/config-paths.d.ts +2 -0
- package/lib/tools/hub-v2/config/config-paths.js +17 -0
- package/lib/tools/hub-v2/config/env.d.ts +1 -0
- package/lib/tools/hub-v2/config/env.js +12 -0
- package/lib/tools/hub-v2/config/index.d.ts +8 -0
- package/lib/tools/hub-v2/config/index.js +18 -0
- package/lib/tools/hub-v2/config/jsonc.d.ts +5 -0
- package/lib/tools/hub-v2/config/jsonc.js +86 -0
- package/lib/tools/hub-v2/config/load-config.d.ts +18 -0
- package/lib/tools/hub-v2/config/load-config.js +93 -0
- package/lib/tools/hub-v2/config/project-selector.d.ts +5 -0
- package/lib/tools/hub-v2/config/project-selector.js +92 -0
- package/lib/tools/hub-v2/config/resolve-context.d.ts +13 -0
- package/lib/tools/hub-v2/config/resolve-context.js +33 -0
- package/lib/tools/hub-v2/docs.tools.js +138 -4
- package/lib/tools/hub-v2/index.js +2 -0
- package/lib/tools/hub-v2/issues-workflow.tools.d.ts +2 -0
- package/lib/tools/hub-v2/issues-workflow.tools.js +199 -0
- package/lib/tools/hub-v2/issues.tools.js +96 -6
- package/lib/tools/hub-v2/projects.tools.js +16 -3
- package/lib/tools/hub-v2/raw.d.ts +8 -0
- package/lib/tools/hub-v2/raw.js +33 -0
- package/lib/tools/hub-v2/rd.tools.js +167 -8
- package/lib/tools/hub-v2/schemas.d.ts +668 -71
- package/lib/tools/hub-v2/schemas.js +152 -1
- package/lib/tools/hub-v2/upload.tools.js +53 -5
- package/lib/tools/index.d.ts +1 -0
- package/lib/tools/index.js +22 -0
- package/lib/tools/log.tools.js +33 -6
- package/lib/tools/nginx/index.d.ts +1 -0
- package/lib/tools/nginx/index.js +5 -0
- package/lib/tools/nginx/nginx-control.tools.d.ts +2 -0
- package/lib/tools/nginx/nginx-control.tools.js +133 -0
- package/lib/tools/nginx/nginx-proxy.d.ts +24 -0
- package/lib/tools/nginx/nginx-proxy.js +154 -0
- package/lib/tools/nginx.tools.d.ts +2 -0
- package/lib/tools/nginx.tools.js +111 -0
- package/lib/tools/project/index.d.ts +2 -0
- package/lib/tools/project/index.js +7 -0
- package/lib/tools/project/launch-status.d.ts +10 -0
- package/lib/tools/project/launch-status.js +78 -0
- package/lib/tools/project/local-diagnostics.d.ts +19 -0
- package/lib/tools/project/local-diagnostics.js +97 -0
- package/lib/tools/project/observe-redaction.d.ts +3 -0
- package/lib/tools/project/observe-redaction.js +25 -0
- package/lib/tools/project/observe-runtime.d.ts +72 -0
- package/lib/tools/project/observe-runtime.js +147 -0
- package/lib/tools/project/project-control.tools.d.ts +2 -0
- package/lib/tools/project/project-control.tools.js +216 -0
- package/lib/tools/project/project-observe.tools.d.ts +2 -0
- package/lib/tools/project/project-observe.tools.js +191 -0
- package/lib/tools/project/runtime-config.d.ts +7 -0
- package/lib/tools/project/runtime-config.js +50 -0
- package/lib/tools/project-observe.tools.d.ts +1 -0
- package/lib/tools/project-observe.tools.js +5 -0
- package/lib/tools/project.tools.d.ts +8 -0
- package/lib/tools/project.tools.js +97 -6
- package/lib/tools/proxy.tools.js +4 -4
- package/lib/tools/review/index.d.ts +1 -0
- package/lib/tools/review/index.js +5 -0
- package/lib/tools/review/review.tools.d.ts +2 -0
- package/lib/tools/review/review.tools.js +152 -0
- package/lib/tools/runtime/index.d.ts +1 -0
- package/lib/tools/runtime/index.js +5 -0
- package/lib/tools/runtime/runtime-control.tools.d.ts +2 -0
- package/lib/tools/runtime/runtime-control.tools.js +89 -0
- package/lib/tools/runtime.tools.js +41 -4
- package/lib/tools/standard/index.d.ts +1 -0
- package/lib/tools/standard/index.js +5 -0
- package/lib/tools/standard/standard.tools.d.ts +2 -0
- package/lib/tools/standard/standard.tools.js +91 -0
- package/lib/tools/task.tools.js +44 -9
- package/lib/tools/test/index.d.ts +1 -0
- package/lib/tools/test/index.js +5 -0
- package/lib/tools/test/test-standard.tools.d.ts +2 -0
- package/lib/tools/test/test-standard.tools.js +51 -0
- package/lib/tools/tool-catalog.d.ts +2 -0
- package/lib/tools/tool-catalog.js +7 -0
- package/lib/tools/workflow/frontend-workflow.tools.d.ts +2 -0
- package/lib/tools/workflow/frontend-workflow.tools.js +364 -0
- package/lib/tools/workflow/index.d.ts +1 -0
- package/lib/tools/workflow/index.js +5 -0
- package/lib/tools/workspace-package.d.ts +22 -0
- package/lib/tools/workspace-package.js +130 -0
- package/lib/tools/workspace.tools.d.ts +7 -0
- package/lib/tools/workspace.tools.js +336 -0
- package/lib/utils/errors.js +6 -1
- package/lib/utils/result.d.ts +9 -0
- package/lib/utils/result.js +9 -0
- package/lib/workflow/frontend-task.schema.d.ts +83 -0
- package/lib/workflow/frontend-task.schema.js +25 -0
- package/lib/workflow/frontend-task.service.d.ts +57 -0
- package/lib/workflow/frontend-task.service.js +195 -0
- package/lib/workflow/workflow-status.d.ts +2 -0
- package/lib/workflow/workflow-status.js +14 -0
- package/lib/workflow/workflow-transition.d.ts +9 -0
- package/lib/workflow/workflow-transition.js +38 -0
- package/package.json +5 -3
- package/lib/tools/hub-v2/config.d.ts +0 -34
- package/lib/tools/hub-v2/config.js +0 -297
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeProxyRequest = normalizeProxyRequest;
|
|
4
|
+
exports.sanitizeNginxServerForAgent = sanitizeNginxServerForAgent;
|
|
5
|
+
exports.sanitizeNginxProxyRequestForAgent = sanitizeNginxProxyRequestForAgent;
|
|
6
|
+
exports.rollbackNginxProxySave = rollbackNginxProxySave;
|
|
7
|
+
function validateProxyTarget(target) {
|
|
8
|
+
if (/[\r\n;{}]/.test(target) || target.includes("`") || target.includes("$(")) {
|
|
9
|
+
throw new Error("target contains unsafe characters");
|
|
10
|
+
}
|
|
11
|
+
let url;
|
|
12
|
+
try {
|
|
13
|
+
url = new URL(target);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error("target must be a valid URL");
|
|
17
|
+
}
|
|
18
|
+
const allowed = new Set(["http:", "https:", "ws:", "wss:"]);
|
|
19
|
+
if (!allowed.has(url.protocol))
|
|
20
|
+
throw new Error("target protocol must be http, https, ws, or wss");
|
|
21
|
+
if (!url.hostname)
|
|
22
|
+
throw new Error("target hostname is required");
|
|
23
|
+
return url;
|
|
24
|
+
}
|
|
25
|
+
function assertSafeNginxToken(label, value) {
|
|
26
|
+
const normalized = value.trim();
|
|
27
|
+
if (!normalized)
|
|
28
|
+
throw new Error(`${label} cannot be empty`);
|
|
29
|
+
if (normalized !== value || /[\r\n;{}]/.test(normalized) || normalized.includes("`") || normalized.includes("$(") || normalized.includes("${")) {
|
|
30
|
+
throw new Error(`${label} contains unsafe Nginx control characters`);
|
|
31
|
+
}
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function validatePortText(label, value) {
|
|
35
|
+
const port = Number(value);
|
|
36
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
37
|
+
throw new Error(`${label} must use a port between 1 and 65535`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateListenValue(value) {
|
|
41
|
+
const listen = assertSafeNginxToken("listen", value);
|
|
42
|
+
if (/\s/.test(listen))
|
|
43
|
+
throw new Error("listen must be a port or host:port without whitespace");
|
|
44
|
+
if (/^\d{1,5}$/.test(listen)) {
|
|
45
|
+
validatePortText("listen", listen);
|
|
46
|
+
return listen;
|
|
47
|
+
}
|
|
48
|
+
const hostPort = listen.match(/^(localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[[0-9a-fA-F:.]+\]|[A-Za-z0-9.-]+):(\d{1,5})$/);
|
|
49
|
+
if (!hostPort)
|
|
50
|
+
throw new Error("listen must be a port or host:port");
|
|
51
|
+
validatePortText("listen", hostPort[2]);
|
|
52
|
+
return listen;
|
|
53
|
+
}
|
|
54
|
+
function validateDomainValue(value) {
|
|
55
|
+
const domain = assertSafeNginxToken("domain", value);
|
|
56
|
+
if (/[\s/\\]/.test(domain))
|
|
57
|
+
throw new Error("domain must not contain whitespace or path separators");
|
|
58
|
+
return domain;
|
|
59
|
+
}
|
|
60
|
+
function validateLocationPath(value) {
|
|
61
|
+
const locationPath = assertSafeNginxToken("locationPath", value);
|
|
62
|
+
if (!locationPath.startsWith("/"))
|
|
63
|
+
throw new Error("locationPath must start with /");
|
|
64
|
+
if (/\s/.test(locationPath))
|
|
65
|
+
throw new Error("locationPath must not contain whitespace");
|
|
66
|
+
return locationPath;
|
|
67
|
+
}
|
|
68
|
+
function normalizeProxyRequest(args, existing) {
|
|
69
|
+
const target = validateProxyTarget(args.target).toString();
|
|
70
|
+
const domains = args.domains ?? existing?.domains;
|
|
71
|
+
const listen = args.listen ?? existing?.listen;
|
|
72
|
+
const name = args.name ?? existing?.name ?? domains?.[0];
|
|
73
|
+
if (!name)
|
|
74
|
+
throw new Error("name is required when creating a new proxy server");
|
|
75
|
+
if (!domains?.length)
|
|
76
|
+
throw new Error("domains is required when creating a new proxy server");
|
|
77
|
+
if (!listen?.length)
|
|
78
|
+
throw new Error("listen is required when creating a new proxy server");
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
listen: listen.map((item) => validateListenValue(item)),
|
|
82
|
+
domains: domains.map((item) => validateDomainValue(item)),
|
|
83
|
+
enabled: args.enabled ?? existing?.enabled ?? true,
|
|
84
|
+
protocol: existing?.ssl ? "https" : "http",
|
|
85
|
+
ssl: existing?.ssl ?? false,
|
|
86
|
+
sslCert: existing?.sslCert,
|
|
87
|
+
sslKey: existing?.sslKey,
|
|
88
|
+
root: existing?.root,
|
|
89
|
+
index: existing?.index,
|
|
90
|
+
extraConfig: existing?.extraConfig,
|
|
91
|
+
locations: [{ path: validateLocationPath(args.locationPath ?? "/"), proxyPass: target }],
|
|
92
|
+
createdBy: existing?.createdBy ?? "ngm-mcp",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function sanitizeNginxServerForAgent(server) {
|
|
96
|
+
if (!server)
|
|
97
|
+
return server;
|
|
98
|
+
return {
|
|
99
|
+
id: server.id,
|
|
100
|
+
name: server.name,
|
|
101
|
+
listen: server.listen,
|
|
102
|
+
domains: server.domains,
|
|
103
|
+
enabled: server.enabled,
|
|
104
|
+
ssl: Boolean(server.ssl),
|
|
105
|
+
locations: Array.isArray(server.locations)
|
|
106
|
+
? server.locations.map((item) => ({
|
|
107
|
+
path: item?.path,
|
|
108
|
+
proxyPass: item?.proxyPass,
|
|
109
|
+
}))
|
|
110
|
+
: undefined,
|
|
111
|
+
sslCert: server.sslCert ? "[REDACTED_PATH]" : undefined,
|
|
112
|
+
sslKey: server.sslKey ? "[REDACTED_PATH]" : undefined,
|
|
113
|
+
extraConfig: server.extraConfig ? "[REDACTED]" : undefined,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function sanitizeNginxProxyRequestForAgent(request) {
|
|
117
|
+
return sanitizeNginxServerForAgent({
|
|
118
|
+
...request,
|
|
119
|
+
id: undefined,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async function rollbackNginxProxySave(context, mode, serverId, saved, existing) {
|
|
123
|
+
const nginx = context.services.core.nginx;
|
|
124
|
+
try {
|
|
125
|
+
if (mode === "create") {
|
|
126
|
+
const savedId = typeof saved?.id === "string" ? saved.id : undefined;
|
|
127
|
+
if (!savedId)
|
|
128
|
+
return { status: "not_possible", reason: "created server id was not returned" };
|
|
129
|
+
const result = await nginx.server.deleteServer(savedId);
|
|
130
|
+
const validation = await nginx.config.validateConfig();
|
|
131
|
+
return {
|
|
132
|
+
status: validation.valid ? "rolled_back" : "failed",
|
|
133
|
+
action: "delete-created-server",
|
|
134
|
+
result,
|
|
135
|
+
validation,
|
|
136
|
+
...(validation.valid ? {} : { reason: "Nginx config is still invalid after rollback" }),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
if (!serverId || !existing)
|
|
140
|
+
return { status: "not_possible", reason: "original server snapshot is unavailable" };
|
|
141
|
+
const restored = await nginx.server.updateServer(serverId, existing);
|
|
142
|
+
const validation = await nginx.config.validateConfig();
|
|
143
|
+
return {
|
|
144
|
+
status: validation.valid ? "rolled_back" : "failed",
|
|
145
|
+
action: "restore-previous-server",
|
|
146
|
+
server: sanitizeNginxServerForAgent(restored),
|
|
147
|
+
validation,
|
|
148
|
+
...(validation.valid ? {} : { reason: "Nginx config is still invalid after rollback" }),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return { status: "failed", reason: error instanceof Error ? error.message : String(error) };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nginxTools = nginxTools;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const result_1 = require("../utils/result");
|
|
6
|
+
const nginxServerGetSchema = zod_1.z.object({
|
|
7
|
+
id: zod_1.z.string().trim().min(1),
|
|
8
|
+
}).strict();
|
|
9
|
+
const nginxValidateSchema = zod_1.z.object({
|
|
10
|
+
configText: zod_1.z.string().optional(),
|
|
11
|
+
}).strict();
|
|
12
|
+
const nginxLogsTailSchema = zod_1.z.object({
|
|
13
|
+
type: zod_1.z.enum(["access", "error"]).optional(),
|
|
14
|
+
tail: zod_1.z.number().int().min(1).max(1000).optional(),
|
|
15
|
+
}).strict();
|
|
16
|
+
function clampTail(value) {
|
|
17
|
+
return Math.min(Math.max(value ?? 100, 1), 1000);
|
|
18
|
+
}
|
|
19
|
+
function nginxTools() {
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
name: "ngm_nginx_status",
|
|
23
|
+
description: "Read local ng-manager Nginx binding and process status without starting, stopping, or reloading it.",
|
|
24
|
+
riskLevel: "read",
|
|
25
|
+
inputSchema: zod_1.z.object({}).strict(),
|
|
26
|
+
async handler(_args, context) {
|
|
27
|
+
const nginx = context.services.core.nginx;
|
|
28
|
+
const instance = nginx.service.getInstance();
|
|
29
|
+
const status = await nginx.service.getStatus().catch((error) => ({
|
|
30
|
+
isRunning: false,
|
|
31
|
+
error: error instanceof Error ? error.message : String(error),
|
|
32
|
+
}));
|
|
33
|
+
return (0, result_1.ok)("ngm_nginx_status", {
|
|
34
|
+
instance,
|
|
35
|
+
status,
|
|
36
|
+
lastConfigAppliedAt: nginx.service.getLastConfigAppliedAt(),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "ngm_nginx_servers_list",
|
|
42
|
+
description: "List local ng-manager Nginx server blocks without changing Nginx config.",
|
|
43
|
+
riskLevel: "read",
|
|
44
|
+
inputSchema: zod_1.z.object({}).strict(),
|
|
45
|
+
async handler(_args, context) {
|
|
46
|
+
const servers = await context.services.core.nginx.server.getAllServers();
|
|
47
|
+
return (0, result_1.ok)("ngm_nginx_servers_list", servers);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "ngm_nginx_server_get",
|
|
52
|
+
description: "Get one local ng-manager Nginx server block by id.",
|
|
53
|
+
riskLevel: "read",
|
|
54
|
+
inputSchema: nginxServerGetSchema,
|
|
55
|
+
async handler(args, context) {
|
|
56
|
+
const server = await context.services.core.nginx.server.getServer(args.id);
|
|
57
|
+
if (!server) {
|
|
58
|
+
throw new Error(`Nginx server not found: ${args.id}`);
|
|
59
|
+
}
|
|
60
|
+
return (0, result_1.ok)("ngm_nginx_server_get", server);
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "ngm_nginx_upstreams_list",
|
|
65
|
+
description: "List local ng-manager Nginx upstream definitions without saving changes.",
|
|
66
|
+
riskLevel: "read",
|
|
67
|
+
inputSchema: zod_1.z.object({}).strict(),
|
|
68
|
+
async handler(_args, context) {
|
|
69
|
+
const upstreams = await context.services.core.nginx.module.getUpstreams();
|
|
70
|
+
return (0, result_1.ok)("ngm_nginx_upstreams_list", upstreams);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "ngm_nginx_config_validate",
|
|
75
|
+
description: "Validate the current or supplied local ng-manager Nginx config without reload.",
|
|
76
|
+
riskLevel: "read",
|
|
77
|
+
inputSchema: nginxValidateSchema,
|
|
78
|
+
async handler(args, context) {
|
|
79
|
+
const validation = await context.services.core.nginx.config.validateConfig(args.configText);
|
|
80
|
+
return (0, result_1.ok)("ngm_nginx_config_validate", validation);
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "ngm_nginx_config_get_main",
|
|
85
|
+
description: "Read local ng-manager Nginx main config metadata and content without writing it.",
|
|
86
|
+
riskLevel: "read",
|
|
87
|
+
inputSchema: zod_1.z.object({}).strict(),
|
|
88
|
+
async handler(_args, context) {
|
|
89
|
+
const config = await context.services.core.nginx.config.readMainConfig();
|
|
90
|
+
return (0, result_1.ok)("ngm_nginx_config_get_main", config);
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "ngm_nginx_logs_tail",
|
|
95
|
+
description: "Read recent local ng-manager Nginx access or error log lines.",
|
|
96
|
+
riskLevel: "read",
|
|
97
|
+
inputSchema: nginxLogsTailSchema,
|
|
98
|
+
async handler(args, context) {
|
|
99
|
+
const type = args.type ?? "error";
|
|
100
|
+
const tail = clampTail(args.tail);
|
|
101
|
+
const lines = await context.services.core.nginx.log.readLogTail(type, tail);
|
|
102
|
+
return (0, result_1.ok)("ngm_nginx_logs_tail", {
|
|
103
|
+
type,
|
|
104
|
+
tail,
|
|
105
|
+
logPath: context.services.core.nginx.log.getLogFilePath(type),
|
|
106
|
+
lines,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectObserveTools = exports.projectControlTools = void 0;
|
|
4
|
+
var project_control_tools_1 = require("./project-control.tools");
|
|
5
|
+
Object.defineProperty(exports, "projectControlTools", { enumerable: true, get: function () { return project_control_tools_1.projectControlTools; } });
|
|
6
|
+
var project_observe_tools_1 = require("./project-observe.tools");
|
|
7
|
+
Object.defineProperty(exports, "projectObserveTools", { enumerable: true, get: function () { return project_observe_tools_1.projectObserveTools; } });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolContext } from "../../context/tool-context";
|
|
2
|
+
export type LaunchStatus = "ready" | "running" | "failed" | "success" | "stopped" | "unknown";
|
|
3
|
+
export declare function runtimeStatus(runtime: unknown): string | undefined;
|
|
4
|
+
export declare function runtimeRunId(runtime: unknown): string | undefined;
|
|
5
|
+
export declare function observeLaunch(context: ToolContext, taskId: string, initialRuntime: unknown, waitMs: number): Promise<{
|
|
6
|
+
status: LaunchStatus;
|
|
7
|
+
observedForMs: number;
|
|
8
|
+
message: string;
|
|
9
|
+
runtime: unknown;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runtimeStatus = runtimeStatus;
|
|
4
|
+
exports.runtimeRunId = runtimeRunId;
|
|
5
|
+
exports.observeLaunch = observeLaunch;
|
|
6
|
+
const local_server_1 = require("../controlled/local-server");
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
function runtimeStatus(runtime) {
|
|
11
|
+
return typeof runtime?.status === "string"
|
|
12
|
+
? (runtime.status)
|
|
13
|
+
: undefined;
|
|
14
|
+
}
|
|
15
|
+
function runtimeRunId(runtime) {
|
|
16
|
+
return typeof runtime?.runId === "string"
|
|
17
|
+
? (runtime.runId)
|
|
18
|
+
: undefined;
|
|
19
|
+
}
|
|
20
|
+
function launchStatusFromRuntime(runtime) {
|
|
21
|
+
const status = runtimeStatus(runtime);
|
|
22
|
+
if (status === "failed")
|
|
23
|
+
return "failed";
|
|
24
|
+
if (status === "success")
|
|
25
|
+
return "success";
|
|
26
|
+
if (status === "stopped")
|
|
27
|
+
return "stopped";
|
|
28
|
+
if (status === "running") {
|
|
29
|
+
return runtime?.readyAt ? "ready" : "running";
|
|
30
|
+
}
|
|
31
|
+
return "unknown";
|
|
32
|
+
}
|
|
33
|
+
function launchMessage(status, observedForMs) {
|
|
34
|
+
switch (status) {
|
|
35
|
+
case "ready":
|
|
36
|
+
return "Task is running and emitted a readiness signal.";
|
|
37
|
+
case "running":
|
|
38
|
+
return `Task is still running after ${observedForMs}ms; no failure was observed yet.`;
|
|
39
|
+
case "failed":
|
|
40
|
+
return "Task failed during the observation window.";
|
|
41
|
+
case "success":
|
|
42
|
+
return "Task exited successfully during the observation window.";
|
|
43
|
+
case "stopped":
|
|
44
|
+
return "Task stopped during the observation window.";
|
|
45
|
+
default:
|
|
46
|
+
return "Task status could not be confirmed.";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function getTaskStatus(context, taskId) {
|
|
50
|
+
const { server } = await (0, local_server_1.requireLocalServer)(context);
|
|
51
|
+
if (server)
|
|
52
|
+
return server.getTaskStatus(taskId);
|
|
53
|
+
throw new Error("ng-manager local server is unavailable");
|
|
54
|
+
}
|
|
55
|
+
async function observeLaunch(context, taskId, initialRuntime, waitMs) {
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
let runtime = initialRuntime;
|
|
58
|
+
let status = launchStatusFromRuntime(runtime);
|
|
59
|
+
while (Date.now() - start < waitMs) {
|
|
60
|
+
if (status !== "running")
|
|
61
|
+
break;
|
|
62
|
+
await sleep(Math.min(250, Math.max(waitMs - (Date.now() - start), 0)));
|
|
63
|
+
try {
|
|
64
|
+
runtime = await getTaskStatus(context, taskId);
|
|
65
|
+
status = launchStatusFromRuntime(runtime);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const observedForMs = Date.now() - start;
|
|
72
|
+
return {
|
|
73
|
+
status,
|
|
74
|
+
observedForMs,
|
|
75
|
+
message: launchMessage(status, observedForMs),
|
|
76
|
+
runtime,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare function portCheck(host: string, port: number, timeoutMs: number): Promise<{
|
|
2
|
+
status: "listening" | "unavailable" | "unknown";
|
|
3
|
+
responseTimeMs: number;
|
|
4
|
+
error?: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function normalizeLocalHost(hostname: string): {
|
|
7
|
+
allowed: boolean;
|
|
8
|
+
connectHost: string;
|
|
9
|
+
reason?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function normalizeLocalUrl(input: URL): {
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
url: URL;
|
|
14
|
+
reason?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function headersObject(headers: Headers): Record<string, string>;
|
|
17
|
+
export declare function readBodyPreview(response: Response, maxChars: number): Promise<string | undefined>;
|
|
18
|
+
export declare function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response>;
|
|
19
|
+
export declare function redactHeaders(headers: Record<string, string> | undefined): Record<string, string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.portCheck = portCheck;
|
|
7
|
+
exports.normalizeLocalHost = normalizeLocalHost;
|
|
8
|
+
exports.normalizeLocalUrl = normalizeLocalUrl;
|
|
9
|
+
exports.headersObject = headersObject;
|
|
10
|
+
exports.readBodyPreview = readBodyPreview;
|
|
11
|
+
exports.fetchWithTimeout = fetchWithTimeout;
|
|
12
|
+
exports.redactHeaders = redactHeaders;
|
|
13
|
+
const net_1 = __importDefault(require("net"));
|
|
14
|
+
const observe_redaction_1 = require("./observe-redaction");
|
|
15
|
+
function portCheck(host, port, timeoutMs) {
|
|
16
|
+
const startedAt = Date.now();
|
|
17
|
+
return new Promise((resolve) => {
|
|
18
|
+
const socket = new net_1.default.Socket();
|
|
19
|
+
let settled = false;
|
|
20
|
+
function finish(status, error) {
|
|
21
|
+
if (settled)
|
|
22
|
+
return;
|
|
23
|
+
settled = true;
|
|
24
|
+
socket.destroy();
|
|
25
|
+
resolve({ status, responseTimeMs: Date.now() - startedAt, ...(error ? { error } : {}) });
|
|
26
|
+
}
|
|
27
|
+
socket.setTimeout(timeoutMs);
|
|
28
|
+
socket.once("connect", () => finish("listening"));
|
|
29
|
+
socket.once("timeout", () => finish("unknown", "timeout"));
|
|
30
|
+
socket.once("error", (error) => {
|
|
31
|
+
const code = typeof error?.code === "string" ? error.code : "";
|
|
32
|
+
finish(code === "ECONNREFUSED" ? "unavailable" : "unknown", code || error?.message || "connect error");
|
|
33
|
+
});
|
|
34
|
+
socket.connect(port, host);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function normalizeLocalHost(hostname) {
|
|
38
|
+
const value = hostname.trim().toLowerCase();
|
|
39
|
+
if (value === "localhost" || value === "127.0.0.1" || value === "::ffff:127.0.0.1")
|
|
40
|
+
return { allowed: true, connectHost: "127.0.0.1" };
|
|
41
|
+
if (value === "::1" || value === "[::1]")
|
|
42
|
+
return { allowed: true, connectHost: "::1" };
|
|
43
|
+
if (value === "0.0.0.0")
|
|
44
|
+
return { allowed: true, connectHost: "127.0.0.1" };
|
|
45
|
+
if (value === "::" || value === "[::]")
|
|
46
|
+
return { allowed: true, connectHost: "::1" };
|
|
47
|
+
return { allowed: false, connectHost: hostname, reason: "only localhost, loopback, or wildcard local addresses are allowed" };
|
|
48
|
+
}
|
|
49
|
+
function normalizeLocalUrl(input) {
|
|
50
|
+
const normalized = normalizeLocalHost(input.hostname);
|
|
51
|
+
if (!normalized.allowed)
|
|
52
|
+
return { allowed: false, url: input, reason: normalized.reason };
|
|
53
|
+
const url = new URL(input.toString());
|
|
54
|
+
url.hostname = normalized.connectHost;
|
|
55
|
+
return { allowed: true, url };
|
|
56
|
+
}
|
|
57
|
+
function headersObject(headers) {
|
|
58
|
+
const out = {};
|
|
59
|
+
headers.forEach((value, key) => {
|
|
60
|
+
out[key] = observe_redaction_1.SENSITIVE_KEY_RE.test(key) ? "[REDACTED]" : (0, observe_redaction_1.redactText)(value);
|
|
61
|
+
});
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
async function readBodyPreview(response, maxChars) {
|
|
65
|
+
if (!response.body)
|
|
66
|
+
return undefined;
|
|
67
|
+
const reader = response.body.getReader();
|
|
68
|
+
const decoder = new TextDecoder();
|
|
69
|
+
let text = "";
|
|
70
|
+
try {
|
|
71
|
+
while (text.length < maxChars) {
|
|
72
|
+
const { done, value } = await reader.read();
|
|
73
|
+
if (done)
|
|
74
|
+
break;
|
|
75
|
+
text += decoder.decode(value, { stream: true });
|
|
76
|
+
if (text.length >= maxChars)
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
reader.cancel().catch(() => undefined);
|
|
82
|
+
}
|
|
83
|
+
return (0, observe_redaction_1.redactText)(text.slice(0, maxChars));
|
|
84
|
+
}
|
|
85
|
+
async function fetchWithTimeout(url, init, timeoutMs) {
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
88
|
+
try {
|
|
89
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearTimeout(timer);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function redactHeaders(headers) {
|
|
96
|
+
return (0, observe_redaction_1.redactValue)(headers ?? {});
|
|
97
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SENSITIVE_KEY_RE = void 0;
|
|
4
|
+
exports.redactText = redactText;
|
|
5
|
+
exports.redactValue = redactValue;
|
|
6
|
+
exports.SENSITIVE_KEY_RE = /(authorization|token|password|passwd|secret|api[-_]?key|access[-_]?token|refresh[-_]?token)/i;
|
|
7
|
+
function redactText(value) {
|
|
8
|
+
return value
|
|
9
|
+
.replace(/(authorization\s*[:=]\s*)(bearer\s+)?[^\s,;]+/gi, "$1[REDACTED]")
|
|
10
|
+
.replace(/((?:token|password|passwd|secret|api[-_]?key|access[-_]?token|refresh[-_]?token)\s*[:=]\s*)("[^"]*"|'[^']*'|[^\s,;&]+)/gi, "$1[REDACTED]")
|
|
11
|
+
.replace(/([?&](?:token|password|passwd|secret|api[-_]?key|access[-_]?token|refresh[-_]?token)=)[^&\s]+/gi, "$1[REDACTED]");
|
|
12
|
+
}
|
|
13
|
+
function redactValue(value) {
|
|
14
|
+
if (typeof value === "string")
|
|
15
|
+
return redactText(value);
|
|
16
|
+
if (Array.isArray(value))
|
|
17
|
+
return value.map(redactValue);
|
|
18
|
+
if (!value || typeof value !== "object")
|
|
19
|
+
return value;
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [key, item] of Object.entries(value)) {
|
|
22
|
+
out[key] = exports.SENSITIVE_KEY_RE.test(key) ? "[REDACTED]" : redactValue(item);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { LocalServerAvailability, ToolContext } from "../../context/tool-context";
|
|
2
|
+
export type TaskRowLike = {
|
|
3
|
+
spec?: Record<string, any>;
|
|
4
|
+
runtime?: Record<string, any>;
|
|
5
|
+
};
|
|
6
|
+
export type RuntimeLike = Record<string, any>;
|
|
7
|
+
export declare function localServerAvailability(context: ToolContext): Promise<LocalServerAvailability>;
|
|
8
|
+
export declare function runtimeSummary(runtime?: RuntimeLike): {
|
|
9
|
+
taskId: any;
|
|
10
|
+
runId: any;
|
|
11
|
+
status: any;
|
|
12
|
+
pid: any;
|
|
13
|
+
pidExists: boolean | null;
|
|
14
|
+
startedAt: any;
|
|
15
|
+
stoppedAt: any;
|
|
16
|
+
lastOutputAt: any;
|
|
17
|
+
readyAt: any;
|
|
18
|
+
exitCode: any;
|
|
19
|
+
signal: any;
|
|
20
|
+
lastError: any;
|
|
21
|
+
urls: any[];
|
|
22
|
+
} | undefined;
|
|
23
|
+
export declare function taskSummary(rowOrRuntime: TaskRowLike | RuntimeLike): {
|
|
24
|
+
taskId: any;
|
|
25
|
+
projectId: any;
|
|
26
|
+
projectPath: any;
|
|
27
|
+
scriptName: any;
|
|
28
|
+
status: any;
|
|
29
|
+
pid: any;
|
|
30
|
+
startTime: any;
|
|
31
|
+
runtime: {
|
|
32
|
+
taskId: any;
|
|
33
|
+
runId: any;
|
|
34
|
+
status: any;
|
|
35
|
+
pid: any;
|
|
36
|
+
pidExists: boolean | null;
|
|
37
|
+
startedAt: any;
|
|
38
|
+
stoppedAt: any;
|
|
39
|
+
lastOutputAt: any;
|
|
40
|
+
readyAt: any;
|
|
41
|
+
exitCode: any;
|
|
42
|
+
signal: any;
|
|
43
|
+
lastError: any;
|
|
44
|
+
urls: any[];
|
|
45
|
+
} | undefined;
|
|
46
|
+
ports: ({
|
|
47
|
+
url: string;
|
|
48
|
+
host: string;
|
|
49
|
+
port: number;
|
|
50
|
+
protocol: string;
|
|
51
|
+
} | null)[];
|
|
52
|
+
};
|
|
53
|
+
export declare function latestTimestamp(runtime?: RuntimeLike): number | undefined;
|
|
54
|
+
export declare function taskStatus(context: ToolContext, taskId: string): Promise<{
|
|
55
|
+
runtime: RuntimeLike | null;
|
|
56
|
+
controlPlane: string;
|
|
57
|
+
localServer: LocalServerAvailability;
|
|
58
|
+
}>;
|
|
59
|
+
export declare function listProjectRows(context: ToolContext, projectId: string, availability: LocalServerAvailability): Promise<{
|
|
60
|
+
rows: TaskRowLike[];
|
|
61
|
+
controlPlane: string;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function activeTasks(context: ToolContext, availability: LocalServerAvailability): Promise<{
|
|
64
|
+
tasks: RuntimeLike[];
|
|
65
|
+
controlPlane: string;
|
|
66
|
+
}>;
|
|
67
|
+
export declare function fitLogLines(lines: unknown[], maxChars: number): {
|
|
68
|
+
lines: unknown[];
|
|
69
|
+
returned: number;
|
|
70
|
+
omitted: number;
|
|
71
|
+
maxChars: number;
|
|
72
|
+
};
|