notoken-core 1.0.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/config/file-hints.json +255 -0
- package/config/hosts.json +14 -0
- package/config/intents.json +3920 -0
- package/config/playbooks.json +112 -0
- package/config/rules.json +100 -0
- package/dist/agents/agentSpawner.d.ts +56 -0
- package/dist/agents/agentSpawner.js +180 -0
- package/dist/agents/planner.d.ts +40 -0
- package/dist/agents/planner.js +175 -0
- package/dist/agents/playbookRunner.d.ts +45 -0
- package/dist/agents/playbookRunner.js +120 -0
- package/dist/agents/taskRunner.d.ts +61 -0
- package/dist/agents/taskRunner.js +142 -0
- package/dist/context/history.d.ts +36 -0
- package/dist/context/history.js +115 -0
- package/dist/conversation/coreference.d.ts +27 -0
- package/dist/conversation/coreference.js +147 -0
- package/dist/conversation/secrets.d.ts +43 -0
- package/dist/conversation/secrets.js +129 -0
- package/dist/conversation/store.d.ts +94 -0
- package/dist/conversation/store.js +184 -0
- package/dist/execution/git.d.ts +11 -0
- package/dist/execution/git.js +146 -0
- package/dist/execution/ssh.d.ts +2 -0
- package/dist/execution/ssh.js +17 -0
- package/dist/handlers/executor.d.ts +8 -0
- package/dist/handlers/executor.js +216 -0
- package/dist/healing/claudeHealer.d.ts +17 -0
- package/dist/healing/claudeHealer.js +300 -0
- package/dist/healing/patchPromoter.d.ts +25 -0
- package/dist/healing/patchPromoter.js +118 -0
- package/dist/healing/ruleBuilder.d.ts +5 -0
- package/dist/healing/ruleBuilder.js +111 -0
- package/dist/healing/ruleRepairer.d.ts +8 -0
- package/dist/healing/ruleRepairer.js +29 -0
- package/dist/healing/ruleValidator.d.ts +22 -0
- package/dist/healing/ruleValidator.js +145 -0
- package/dist/healing/runHealer.d.ts +11 -0
- package/dist/healing/runHealer.js +74 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +62 -0
- package/dist/intents/catalog.d.ts +4 -0
- package/dist/intents/catalog.js +7 -0
- package/dist/nlp/disambiguate.d.ts +2 -0
- package/dist/nlp/disambiguate.js +46 -0
- package/dist/nlp/fuzzyResolver.d.ts +14 -0
- package/dist/nlp/fuzzyResolver.js +108 -0
- package/dist/nlp/llmFallback.d.ts +63 -0
- package/dist/nlp/llmFallback.js +338 -0
- package/dist/nlp/llmParser.d.ts +8 -0
- package/dist/nlp/llmParser.js +118 -0
- package/dist/nlp/multiClassifier.d.ts +39 -0
- package/dist/nlp/multiClassifier.js +181 -0
- package/dist/nlp/parseIntent.d.ts +2 -0
- package/dist/nlp/parseIntent.js +34 -0
- package/dist/nlp/ruleParser.d.ts +2 -0
- package/dist/nlp/ruleParser.js +234 -0
- package/dist/nlp/semantic.d.ts +104 -0
- package/dist/nlp/semantic.js +419 -0
- package/dist/nlp/uncertainty.d.ts +42 -0
- package/dist/nlp/uncertainty.js +103 -0
- package/dist/parsers/apacheParser.d.ts +50 -0
- package/dist/parsers/apacheParser.js +152 -0
- package/dist/parsers/bindParser.d.ts +40 -0
- package/dist/parsers/bindParser.js +189 -0
- package/dist/parsers/envFile.d.ts +39 -0
- package/dist/parsers/envFile.js +128 -0
- package/dist/parsers/fileFinder.d.ts +30 -0
- package/dist/parsers/fileFinder.js +226 -0
- package/dist/parsers/index.d.ts +27 -0
- package/dist/parsers/index.js +193 -0
- package/dist/parsers/jsonParser.d.ts +16 -0
- package/dist/parsers/jsonParser.js +57 -0
- package/dist/parsers/nginxParser.d.ts +47 -0
- package/dist/parsers/nginxParser.js +161 -0
- package/dist/parsers/passwd.d.ts +25 -0
- package/dist/parsers/passwd.js +41 -0
- package/dist/parsers/shadow.d.ts +23 -0
- package/dist/parsers/shadow.js +50 -0
- package/dist/parsers/yamlParser.d.ts +13 -0
- package/dist/parsers/yamlParser.js +54 -0
- package/dist/policy/confirm.d.ts +2 -0
- package/dist/policy/confirm.js +29 -0
- package/dist/policy/safety.d.ts +4 -0
- package/dist/policy/safety.js +32 -0
- package/dist/types/intent.d.ts +205 -0
- package/dist/types/intent.js +32 -0
- package/dist/types/rules.d.ts +237 -0
- package/dist/types/rules.js +50 -0
- package/dist/utils/analysis.d.ts +25 -0
- package/dist/utils/analysis.js +307 -0
- package/dist/utils/autoBackup.d.ts +43 -0
- package/dist/utils/autoBackup.js +144 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.js +32 -0
- package/dist/utils/dirAnalysis.d.ts +23 -0
- package/dist/utils/dirAnalysis.js +192 -0
- package/dist/utils/explain.d.ts +8 -0
- package/dist/utils/explain.js +145 -0
- package/dist/utils/logger.d.ts +5 -0
- package/dist/utils/logger.js +29 -0
- package/dist/utils/output.d.ts +2 -0
- package/dist/utils/output.js +26 -0
- package/dist/utils/paths.d.ts +26 -0
- package/dist/utils/paths.js +47 -0
- package/dist/utils/permissions.d.ts +64 -0
- package/dist/utils/permissions.js +298 -0
- package/dist/utils/platform.d.ts +53 -0
- package/dist/utils/platform.js +253 -0
- package/dist/utils/smartFile.d.ts +29 -0
- package/dist/utils/smartFile.js +188 -0
- package/dist/utils/spinner.d.ts +53 -0
- package/dist/utils/spinner.js +140 -0
- package/dist/utils/verbose.d.ts +27 -0
- package/dist/utils/verbose.js +131 -0
- package/dist/utils/wslPaths.d.ts +31 -0
- package/dist/utils/wslPaths.js +145 -0
- package/package.json +39 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nginx config parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses nginx.conf and site configs into structured blocks.
|
|
5
|
+
* Handles: server blocks, location blocks, upstream, directives.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse nginx config content.
|
|
9
|
+
*/
|
|
10
|
+
export function parseNginx(content) {
|
|
11
|
+
const lines = content.split("\n");
|
|
12
|
+
const { directives, blocks } = parseBlock(lines, 0, lines.length);
|
|
13
|
+
const servers = extractServers(blocks);
|
|
14
|
+
return { directives, blocks, servers };
|
|
15
|
+
}
|
|
16
|
+
function parseBlock(lines, start, end) {
|
|
17
|
+
const directives = [];
|
|
18
|
+
const blocks = [];
|
|
19
|
+
let i = start;
|
|
20
|
+
while (i < end) {
|
|
21
|
+
const line = lines[i].trim();
|
|
22
|
+
// Skip comments and empty lines
|
|
23
|
+
if (!line || line.startsWith("#")) {
|
|
24
|
+
i++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
// Block opening: "server {", "location /api {"
|
|
28
|
+
if (line.includes("{")) {
|
|
29
|
+
const beforeBrace = line.split("{")[0].trim();
|
|
30
|
+
const parts = beforeBrace.split(/\s+/);
|
|
31
|
+
const blockType = parts[0] ?? "";
|
|
32
|
+
const blockArgs = parts.slice(1);
|
|
33
|
+
// Find matching closing brace
|
|
34
|
+
let depth = 1;
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
while (j < end && depth > 0) {
|
|
37
|
+
const l = lines[j].trim();
|
|
38
|
+
for (const ch of l) {
|
|
39
|
+
if (ch === "{")
|
|
40
|
+
depth++;
|
|
41
|
+
if (ch === "}")
|
|
42
|
+
depth--;
|
|
43
|
+
}
|
|
44
|
+
if (depth > 0)
|
|
45
|
+
j++;
|
|
46
|
+
else
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
// Recursively parse the block content
|
|
50
|
+
const inner = parseBlock(lines, i + 1, j);
|
|
51
|
+
blocks.push({
|
|
52
|
+
type: blockType,
|
|
53
|
+
args: blockArgs,
|
|
54
|
+
directives: inner.directives,
|
|
55
|
+
blocks: inner.blocks,
|
|
56
|
+
line: i + 1,
|
|
57
|
+
});
|
|
58
|
+
i = j + 1;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Closing brace (shouldn't happen at this level but handle gracefully)
|
|
62
|
+
if (line === "}") {
|
|
63
|
+
i++;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Directive: "worker_processes auto;"
|
|
67
|
+
if (line.endsWith(";")) {
|
|
68
|
+
const stripped = line.slice(0, -1).trim();
|
|
69
|
+
const parts = stripped.split(/\s+/);
|
|
70
|
+
directives.push({
|
|
71
|
+
name: parts[0] ?? "",
|
|
72
|
+
args: parts.slice(1),
|
|
73
|
+
line: i + 1,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
return { directives, blocks };
|
|
79
|
+
}
|
|
80
|
+
function extractServers(blocks) {
|
|
81
|
+
const servers = [];
|
|
82
|
+
for (const block of blocks) {
|
|
83
|
+
if (block.type === "http") {
|
|
84
|
+
servers.push(...extractServers(block.blocks));
|
|
85
|
+
}
|
|
86
|
+
if (block.type === "server") {
|
|
87
|
+
const server = {
|
|
88
|
+
listen: [],
|
|
89
|
+
serverName: [],
|
|
90
|
+
locations: [],
|
|
91
|
+
ssl: false,
|
|
92
|
+
};
|
|
93
|
+
for (const d of block.directives) {
|
|
94
|
+
switch (d.name) {
|
|
95
|
+
case "listen":
|
|
96
|
+
server.listen.push(d.args.join(" "));
|
|
97
|
+
if (d.args.some((a) => a === "ssl"))
|
|
98
|
+
server.ssl = true;
|
|
99
|
+
break;
|
|
100
|
+
case "server_name":
|
|
101
|
+
server.serverName.push(...d.args);
|
|
102
|
+
break;
|
|
103
|
+
case "root":
|
|
104
|
+
server.root = d.args[0];
|
|
105
|
+
break;
|
|
106
|
+
case "index":
|
|
107
|
+
server.index = d.args.join(" ");
|
|
108
|
+
break;
|
|
109
|
+
case "ssl_certificate":
|
|
110
|
+
server.sslCertificate = d.args[0];
|
|
111
|
+
server.ssl = true;
|
|
112
|
+
break;
|
|
113
|
+
case "ssl_certificate_key":
|
|
114
|
+
server.sslCertificateKey = d.args[0];
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const loc of block.blocks.filter((b) => b.type === "location")) {
|
|
119
|
+
const location = {
|
|
120
|
+
path: loc.args.join(" "),
|
|
121
|
+
directives: loc.directives,
|
|
122
|
+
};
|
|
123
|
+
for (const d of loc.directives) {
|
|
124
|
+
if (d.name === "proxy_pass")
|
|
125
|
+
location.proxyPass = d.args[0];
|
|
126
|
+
if (d.name === "root")
|
|
127
|
+
location.root = d.args[0];
|
|
128
|
+
if (d.name === "try_files")
|
|
129
|
+
location.tryFiles = d.args.join(" ");
|
|
130
|
+
}
|
|
131
|
+
server.locations.push(location);
|
|
132
|
+
}
|
|
133
|
+
servers.push(server);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return servers;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Format an nginx config summary for display.
|
|
140
|
+
*/
|
|
141
|
+
export function formatNginxSummary(config) {
|
|
142
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m" };
|
|
143
|
+
const lines = [];
|
|
144
|
+
lines.push(`${c.bold}Nginx Config Summary${c.reset}`);
|
|
145
|
+
lines.push(` ${config.servers.length} server block(s)\n`);
|
|
146
|
+
for (const server of config.servers) {
|
|
147
|
+
const names = server.serverName.join(", ") || "(default)";
|
|
148
|
+
const listen = server.listen.join(", ");
|
|
149
|
+
const sslLabel = server.ssl ? ` ${c.green}[SSL]${c.reset}` : "";
|
|
150
|
+
lines.push(` ${c.cyan}server${c.reset} ${c.bold}${names}${c.reset}${sslLabel}`);
|
|
151
|
+
lines.push(` listen: ${listen}`);
|
|
152
|
+
if (server.root)
|
|
153
|
+
lines.push(` root: ${server.root}`);
|
|
154
|
+
for (const loc of server.locations) {
|
|
155
|
+
const proxy = loc.proxyPass ? ` → ${c.yellow}${loc.proxyPass}${c.reset}` : "";
|
|
156
|
+
lines.push(` ${c.dim}location${c.reset} ${loc.path}${proxy}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push("");
|
|
159
|
+
}
|
|
160
|
+
return lines.join("\n");
|
|
161
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /etc/passwd parser.
|
|
3
|
+
*
|
|
4
|
+
* Format: username:password:uid:gid:gecos:home:shell
|
|
5
|
+
*/
|
|
6
|
+
export interface PasswdEntry {
|
|
7
|
+
username: string;
|
|
8
|
+
hasPassword: boolean;
|
|
9
|
+
uid: number;
|
|
10
|
+
gid: number;
|
|
11
|
+
gecos: string;
|
|
12
|
+
home: string;
|
|
13
|
+
shell: string;
|
|
14
|
+
isSystem: boolean;
|
|
15
|
+
isLoginUser: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function parsePasswd(content: string): PasswdEntry[];
|
|
18
|
+
/**
|
|
19
|
+
* Get only real login users (non-system, has a login shell).
|
|
20
|
+
*/
|
|
21
|
+
export declare function getLoginUsers(entries: PasswdEntry[]): PasswdEntry[];
|
|
22
|
+
/**
|
|
23
|
+
* Find a user by username.
|
|
24
|
+
*/
|
|
25
|
+
export declare function findUser(entries: PasswdEntry[], username: string): PasswdEntry | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /etc/passwd parser.
|
|
3
|
+
*
|
|
4
|
+
* Format: username:password:uid:gid:gecos:home:shell
|
|
5
|
+
*/
|
|
6
|
+
export function parsePasswd(content) {
|
|
7
|
+
return content
|
|
8
|
+
.split("\n")
|
|
9
|
+
.filter((line) => line.trim() && !line.startsWith("#"))
|
|
10
|
+
.map((line) => {
|
|
11
|
+
const parts = line.split(":");
|
|
12
|
+
if (parts.length < 7)
|
|
13
|
+
return null;
|
|
14
|
+
const uid = Number(parts[2]);
|
|
15
|
+
const shell = parts[6] ?? "";
|
|
16
|
+
return {
|
|
17
|
+
username: parts[0],
|
|
18
|
+
hasPassword: parts[1] !== "x" && parts[1] !== "*" && parts[1] !== "!",
|
|
19
|
+
uid,
|
|
20
|
+
gid: Number(parts[3]),
|
|
21
|
+
gecos: parts[4] ?? "",
|
|
22
|
+
home: parts[5] ?? "",
|
|
23
|
+
shell,
|
|
24
|
+
isSystem: uid < 1000 && uid !== 0,
|
|
25
|
+
isLoginUser: !shell.includes("nologin") && !shell.includes("false") && shell !== "/bin/sync",
|
|
26
|
+
};
|
|
27
|
+
})
|
|
28
|
+
.filter((e) => e !== null);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get only real login users (non-system, has a login shell).
|
|
32
|
+
*/
|
|
33
|
+
export function getLoginUsers(entries) {
|
|
34
|
+
return entries.filter((e) => e.isLoginUser && !e.isSystem);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Find a user by username.
|
|
38
|
+
*/
|
|
39
|
+
export function findUser(entries, username) {
|
|
40
|
+
return entries.find((e) => e.username === username);
|
|
41
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /etc/shadow parser.
|
|
3
|
+
*
|
|
4
|
+
* Format: username:password:lastchanged:min:max:warn:inactive:expire:reserved
|
|
5
|
+
*
|
|
6
|
+
* SECURITY: Never log or store actual password hashes.
|
|
7
|
+
* This parser only extracts metadata, not the hash itself.
|
|
8
|
+
*/
|
|
9
|
+
export interface ShadowEntry {
|
|
10
|
+
username: string;
|
|
11
|
+
hasPassword: boolean;
|
|
12
|
+
locked: boolean;
|
|
13
|
+
/** Days since epoch of last password change */
|
|
14
|
+
lastChanged: number | null;
|
|
15
|
+
minDays: number | null;
|
|
16
|
+
maxDays: number | null;
|
|
17
|
+
warnDays: number | null;
|
|
18
|
+
inactiveDays: number | null;
|
|
19
|
+
expireDate: number | null;
|
|
20
|
+
/** Password hash algorithm (1=MD5, 5=SHA256, 6=SHA512, y=yescrypt) */
|
|
21
|
+
algorithm: string | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function parseShadow(content: string): ShadowEntry[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /etc/shadow parser.
|
|
3
|
+
*
|
|
4
|
+
* Format: username:password:lastchanged:min:max:warn:inactive:expire:reserved
|
|
5
|
+
*
|
|
6
|
+
* SECURITY: Never log or store actual password hashes.
|
|
7
|
+
* This parser only extracts metadata, not the hash itself.
|
|
8
|
+
*/
|
|
9
|
+
export function parseShadow(content) {
|
|
10
|
+
return content
|
|
11
|
+
.split("\n")
|
|
12
|
+
.filter((line) => line.trim() && !line.startsWith("#"))
|
|
13
|
+
.map((line) => {
|
|
14
|
+
const parts = line.split(":");
|
|
15
|
+
if (parts.length < 2)
|
|
16
|
+
return null;
|
|
17
|
+
const passwordField = parts[1] ?? "";
|
|
18
|
+
const locked = passwordField.startsWith("!") || passwordField.startsWith("*");
|
|
19
|
+
const hasPassword = passwordField.length > 1 && !locked && passwordField !== "!!" && passwordField !== "";
|
|
20
|
+
// Extract algorithm from $id$salt$hash format
|
|
21
|
+
let algorithm = null;
|
|
22
|
+
if (passwordField.startsWith("$")) {
|
|
23
|
+
const algoMatch = passwordField.match(/^\$(\w+)\$/);
|
|
24
|
+
if (algoMatch) {
|
|
25
|
+
const algoMap = {
|
|
26
|
+
"1": "MD5",
|
|
27
|
+
"5": "SHA-256",
|
|
28
|
+
"6": "SHA-512",
|
|
29
|
+
"y": "yescrypt",
|
|
30
|
+
"2a": "bcrypt",
|
|
31
|
+
"2b": "bcrypt",
|
|
32
|
+
};
|
|
33
|
+
algorithm = algoMap[algoMatch[1]] ?? algoMatch[1];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
username: parts[0],
|
|
38
|
+
hasPassword,
|
|
39
|
+
locked,
|
|
40
|
+
lastChanged: parts[2] ? Number(parts[2]) || null : null,
|
|
41
|
+
minDays: parts[3] ? Number(parts[3]) || null : null,
|
|
42
|
+
maxDays: parts[4] ? Number(parts[4]) || null : null,
|
|
43
|
+
warnDays: parts[5] ? Number(parts[5]) || null : null,
|
|
44
|
+
inactiveDays: parts[6] ? Number(parts[6]) || null : null,
|
|
45
|
+
expireDate: parts[7] ? Number(parts[7]) || null : null,
|
|
46
|
+
algorithm,
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
.filter((e) => e !== null);
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse YAML content into a JS object.
|
|
3
|
+
*/
|
|
4
|
+
export declare function parseYaml(content: string): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Get a nested value from parsed YAML using dot-notation path.
|
|
7
|
+
* e.g., getYamlValue(data, "server.port") → 8080
|
|
8
|
+
*/
|
|
9
|
+
export declare function getYamlValue(data: unknown, path: string): unknown;
|
|
10
|
+
/**
|
|
11
|
+
* List all leaf paths in a YAML structure.
|
|
12
|
+
*/
|
|
13
|
+
export declare function listYamlPaths(data: unknown, prefix?: string): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import YAML from "yaml";
|
|
2
|
+
/**
|
|
3
|
+
* Parse YAML content into a JS object.
|
|
4
|
+
*/
|
|
5
|
+
export function parseYaml(content) {
|
|
6
|
+
return YAML.parse(content);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get a nested value from parsed YAML using dot-notation path.
|
|
10
|
+
* e.g., getYamlValue(data, "server.port") → 8080
|
|
11
|
+
*/
|
|
12
|
+
export function getYamlValue(data, path) {
|
|
13
|
+
const parts = path.split(".");
|
|
14
|
+
let current = data;
|
|
15
|
+
for (const part of parts) {
|
|
16
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
current = current[part];
|
|
20
|
+
}
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* List all leaf paths in a YAML structure.
|
|
25
|
+
*/
|
|
26
|
+
export function listYamlPaths(data, prefix = "") {
|
|
27
|
+
const paths = [];
|
|
28
|
+
if (data === null || data === undefined || typeof data !== "object") {
|
|
29
|
+
return paths;
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(data)) {
|
|
32
|
+
for (let i = 0; i < data.length; i++) {
|
|
33
|
+
const key = prefix ? `${prefix}[${i}]` : `[${i}]`;
|
|
34
|
+
if (typeof data[i] === "object" && data[i] !== null) {
|
|
35
|
+
paths.push(...listYamlPaths(data[i], key));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
paths.push(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
for (const [key, value] of Object.entries(data)) {
|
|
44
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
45
|
+
if (typeof value === "object" && value !== null) {
|
|
46
|
+
paths.push(...listYamlPaths(value, fullKey));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
paths.push(fullKey);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return paths;
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
export async function askForConfirmation(message) {
|
|
4
|
+
const rl = readline.createInterface({ input, output });
|
|
5
|
+
try {
|
|
6
|
+
const answer = await rl.question(`${message} [y/N] `);
|
|
7
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
8
|
+
}
|
|
9
|
+
finally {
|
|
10
|
+
rl.close();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function askForChoice(message, choices) {
|
|
14
|
+
const rl = readline.createInterface({ input, output });
|
|
15
|
+
try {
|
|
16
|
+
console.log(message);
|
|
17
|
+
for (let i = 0; i < choices.length; i++) {
|
|
18
|
+
console.log(` ${i + 1}. ${choices[i]}`);
|
|
19
|
+
}
|
|
20
|
+
const answer = await rl.question("Choose (number): ");
|
|
21
|
+
const idx = Number(answer.trim()) - 1;
|
|
22
|
+
if (idx >= 0 && idx < choices.length)
|
|
23
|
+
return choices[idx];
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
rl.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DynamicIntent } from "../types/intent.js";
|
|
2
|
+
export declare function validateIntent(intent: DynamicIntent): string[];
|
|
3
|
+
export declare function isDangerous(intent: DynamicIntent): boolean;
|
|
4
|
+
export declare function getRiskLevel(intent: DynamicIntent): "low" | "medium" | "high";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getIntentDef } from "../utils/config.js";
|
|
2
|
+
export function validateIntent(intent) {
|
|
3
|
+
const def = getIntentDef(intent.intent);
|
|
4
|
+
if (!def)
|
|
5
|
+
return [];
|
|
6
|
+
const errors = [];
|
|
7
|
+
for (const [fieldName, fieldDef] of Object.entries(def.fields)) {
|
|
8
|
+
if (fieldDef.required && intent.fields[fieldName] === undefined) {
|
|
9
|
+
errors.push(`Missing required field: ${fieldName}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// Check allowlist if defined
|
|
13
|
+
if (def.allowlist && def.allowlist.length > 0) {
|
|
14
|
+
for (const [fieldName, fieldDef] of Object.entries(def.fields)) {
|
|
15
|
+
if (fieldDef.type === "service" && intent.fields[fieldName]) {
|
|
16
|
+
const value = intent.fields[fieldName];
|
|
17
|
+
if (!def.allowlist.includes(value)) {
|
|
18
|
+
errors.push(`${fieldName} "${value}" is not in the allowlist: ${def.allowlist.join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return errors;
|
|
24
|
+
}
|
|
25
|
+
export function isDangerous(intent) {
|
|
26
|
+
const def = getIntentDef(intent.intent);
|
|
27
|
+
return def?.requiresConfirmation ?? false;
|
|
28
|
+
}
|
|
29
|
+
export function getRiskLevel(intent) {
|
|
30
|
+
const def = getIntentDef(intent.intent);
|
|
31
|
+
return def?.riskLevel ?? "low";
|
|
32
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const EnvironmentName: z.ZodEnum<["local", "dev", "staging", "prod"]>;
|
|
3
|
+
export type EnvironmentName = z.infer<typeof EnvironmentName>;
|
|
4
|
+
export declare const DynamicIntent: z.ZodObject<{
|
|
5
|
+
intent: z.ZodString;
|
|
6
|
+
confidence: z.ZodNumber;
|
|
7
|
+
rawText: z.ZodString;
|
|
8
|
+
fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
intent: string;
|
|
11
|
+
confidence: number;
|
|
12
|
+
rawText: string;
|
|
13
|
+
fields: Record<string, unknown>;
|
|
14
|
+
}, {
|
|
15
|
+
intent: string;
|
|
16
|
+
confidence: number;
|
|
17
|
+
rawText: string;
|
|
18
|
+
fields: Record<string, unknown>;
|
|
19
|
+
}>;
|
|
20
|
+
export type DynamicIntent = z.infer<typeof DynamicIntent>;
|
|
21
|
+
export interface ParsedCommand {
|
|
22
|
+
intent: DynamicIntent;
|
|
23
|
+
missingFields: string[];
|
|
24
|
+
ambiguousFields: Array<{
|
|
25
|
+
field: string;
|
|
26
|
+
candidates: string[];
|
|
27
|
+
}>;
|
|
28
|
+
needsClarification: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare const FieldDef: z.ZodObject<{
|
|
31
|
+
type: z.ZodEnum<["string", "number", "service", "environment", "branch"]>;
|
|
32
|
+
required: z.ZodBoolean;
|
|
33
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
36
|
+
required: boolean;
|
|
37
|
+
default?: unknown;
|
|
38
|
+
}, {
|
|
39
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
40
|
+
required: boolean;
|
|
41
|
+
default?: unknown;
|
|
42
|
+
}>;
|
|
43
|
+
export type FieldDef = z.infer<typeof FieldDef>;
|
|
44
|
+
export declare const IntentDef: z.ZodObject<{
|
|
45
|
+
name: z.ZodString;
|
|
46
|
+
description: z.ZodString;
|
|
47
|
+
synonyms: z.ZodArray<z.ZodString, "many">;
|
|
48
|
+
fields: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
49
|
+
type: z.ZodEnum<["string", "number", "service", "environment", "branch"]>;
|
|
50
|
+
required: z.ZodBoolean;
|
|
51
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
54
|
+
required: boolean;
|
|
55
|
+
default?: unknown;
|
|
56
|
+
}, {
|
|
57
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
58
|
+
required: boolean;
|
|
59
|
+
default?: unknown;
|
|
60
|
+
}>>;
|
|
61
|
+
command: z.ZodString;
|
|
62
|
+
execution: z.ZodEnum<["remote", "local"]>;
|
|
63
|
+
requiresConfirmation: z.ZodBoolean;
|
|
64
|
+
riskLevel: z.ZodEnum<["low", "medium", "high"]>;
|
|
65
|
+
allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
66
|
+
logPaths: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
67
|
+
fuzzyResolve: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
68
|
+
examples: z.ZodArray<z.ZodString, "many">;
|
|
69
|
+
}, "strip", z.ZodTypeAny, {
|
|
70
|
+
fields: Record<string, {
|
|
71
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
72
|
+
required: boolean;
|
|
73
|
+
default?: unknown;
|
|
74
|
+
}>;
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
synonyms: string[];
|
|
78
|
+
command: string;
|
|
79
|
+
execution: "local" | "remote";
|
|
80
|
+
requiresConfirmation: boolean;
|
|
81
|
+
riskLevel: "low" | "medium" | "high";
|
|
82
|
+
examples: string[];
|
|
83
|
+
allowlist?: string[] | undefined;
|
|
84
|
+
logPaths?: Record<string, string> | undefined;
|
|
85
|
+
fuzzyResolve?: string[] | undefined;
|
|
86
|
+
}, {
|
|
87
|
+
fields: Record<string, {
|
|
88
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
89
|
+
required: boolean;
|
|
90
|
+
default?: unknown;
|
|
91
|
+
}>;
|
|
92
|
+
name: string;
|
|
93
|
+
description: string;
|
|
94
|
+
synonyms: string[];
|
|
95
|
+
command: string;
|
|
96
|
+
execution: "local" | "remote";
|
|
97
|
+
requiresConfirmation: boolean;
|
|
98
|
+
riskLevel: "low" | "medium" | "high";
|
|
99
|
+
examples: string[];
|
|
100
|
+
allowlist?: string[] | undefined;
|
|
101
|
+
logPaths?: Record<string, string> | undefined;
|
|
102
|
+
fuzzyResolve?: string[] | undefined;
|
|
103
|
+
}>;
|
|
104
|
+
export type IntentDef = z.infer<typeof IntentDef>;
|
|
105
|
+
export declare const IntentsConfig: z.ZodObject<{
|
|
106
|
+
intents: z.ZodArray<z.ZodObject<{
|
|
107
|
+
name: z.ZodString;
|
|
108
|
+
description: z.ZodString;
|
|
109
|
+
synonyms: z.ZodArray<z.ZodString, "many">;
|
|
110
|
+
fields: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
111
|
+
type: z.ZodEnum<["string", "number", "service", "environment", "branch"]>;
|
|
112
|
+
required: z.ZodBoolean;
|
|
113
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
116
|
+
required: boolean;
|
|
117
|
+
default?: unknown;
|
|
118
|
+
}, {
|
|
119
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
120
|
+
required: boolean;
|
|
121
|
+
default?: unknown;
|
|
122
|
+
}>>;
|
|
123
|
+
command: z.ZodString;
|
|
124
|
+
execution: z.ZodEnum<["remote", "local"]>;
|
|
125
|
+
requiresConfirmation: z.ZodBoolean;
|
|
126
|
+
riskLevel: z.ZodEnum<["low", "medium", "high"]>;
|
|
127
|
+
allowlist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
128
|
+
logPaths: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
129
|
+
fuzzyResolve: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
130
|
+
examples: z.ZodArray<z.ZodString, "many">;
|
|
131
|
+
}, "strip", z.ZodTypeAny, {
|
|
132
|
+
fields: Record<string, {
|
|
133
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
134
|
+
required: boolean;
|
|
135
|
+
default?: unknown;
|
|
136
|
+
}>;
|
|
137
|
+
name: string;
|
|
138
|
+
description: string;
|
|
139
|
+
synonyms: string[];
|
|
140
|
+
command: string;
|
|
141
|
+
execution: "local" | "remote";
|
|
142
|
+
requiresConfirmation: boolean;
|
|
143
|
+
riskLevel: "low" | "medium" | "high";
|
|
144
|
+
examples: string[];
|
|
145
|
+
allowlist?: string[] | undefined;
|
|
146
|
+
logPaths?: Record<string, string> | undefined;
|
|
147
|
+
fuzzyResolve?: string[] | undefined;
|
|
148
|
+
}, {
|
|
149
|
+
fields: Record<string, {
|
|
150
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
151
|
+
required: boolean;
|
|
152
|
+
default?: unknown;
|
|
153
|
+
}>;
|
|
154
|
+
name: string;
|
|
155
|
+
description: string;
|
|
156
|
+
synonyms: string[];
|
|
157
|
+
command: string;
|
|
158
|
+
execution: "local" | "remote";
|
|
159
|
+
requiresConfirmation: boolean;
|
|
160
|
+
riskLevel: "low" | "medium" | "high";
|
|
161
|
+
examples: string[];
|
|
162
|
+
allowlist?: string[] | undefined;
|
|
163
|
+
logPaths?: Record<string, string> | undefined;
|
|
164
|
+
fuzzyResolve?: string[] | undefined;
|
|
165
|
+
}>, "many">;
|
|
166
|
+
}, "strip", z.ZodTypeAny, {
|
|
167
|
+
intents: {
|
|
168
|
+
fields: Record<string, {
|
|
169
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
170
|
+
required: boolean;
|
|
171
|
+
default?: unknown;
|
|
172
|
+
}>;
|
|
173
|
+
name: string;
|
|
174
|
+
description: string;
|
|
175
|
+
synonyms: string[];
|
|
176
|
+
command: string;
|
|
177
|
+
execution: "local" | "remote";
|
|
178
|
+
requiresConfirmation: boolean;
|
|
179
|
+
riskLevel: "low" | "medium" | "high";
|
|
180
|
+
examples: string[];
|
|
181
|
+
allowlist?: string[] | undefined;
|
|
182
|
+
logPaths?: Record<string, string> | undefined;
|
|
183
|
+
fuzzyResolve?: string[] | undefined;
|
|
184
|
+
}[];
|
|
185
|
+
}, {
|
|
186
|
+
intents: {
|
|
187
|
+
fields: Record<string, {
|
|
188
|
+
type: "string" | "number" | "service" | "environment" | "branch";
|
|
189
|
+
required: boolean;
|
|
190
|
+
default?: unknown;
|
|
191
|
+
}>;
|
|
192
|
+
name: string;
|
|
193
|
+
description: string;
|
|
194
|
+
synonyms: string[];
|
|
195
|
+
command: string;
|
|
196
|
+
execution: "local" | "remote";
|
|
197
|
+
requiresConfirmation: boolean;
|
|
198
|
+
riskLevel: "low" | "medium" | "high";
|
|
199
|
+
examples: string[];
|
|
200
|
+
allowlist?: string[] | undefined;
|
|
201
|
+
logPaths?: Record<string, string> | undefined;
|
|
202
|
+
fuzzyResolve?: string[] | undefined;
|
|
203
|
+
}[];
|
|
204
|
+
}>;
|
|
205
|
+
export type IntentsConfig = z.infer<typeof IntentsConfig>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const EnvironmentName = z.enum(["local", "dev", "staging", "prod"]);
|
|
3
|
+
// Dynamic intent — fields come from config, not hardcoded schemas
|
|
4
|
+
export const DynamicIntent = z.object({
|
|
5
|
+
intent: z.string(),
|
|
6
|
+
confidence: z.number().min(0).max(1),
|
|
7
|
+
rawText: z.string(),
|
|
8
|
+
fields: z.record(z.unknown()),
|
|
9
|
+
});
|
|
10
|
+
// Intent definition loaded from config/intents.json
|
|
11
|
+
export const FieldDef = z.object({
|
|
12
|
+
type: z.enum(["string", "number", "service", "environment", "branch"]),
|
|
13
|
+
required: z.boolean(),
|
|
14
|
+
default: z.unknown().optional(),
|
|
15
|
+
});
|
|
16
|
+
export const IntentDef = z.object({
|
|
17
|
+
name: z.string(),
|
|
18
|
+
description: z.string(),
|
|
19
|
+
synonyms: z.array(z.string()),
|
|
20
|
+
fields: z.record(FieldDef),
|
|
21
|
+
command: z.string(),
|
|
22
|
+
execution: z.enum(["remote", "local"]),
|
|
23
|
+
requiresConfirmation: z.boolean(),
|
|
24
|
+
riskLevel: z.enum(["low", "medium", "high"]),
|
|
25
|
+
allowlist: z.array(z.string()).optional(),
|
|
26
|
+
logPaths: z.record(z.string()).optional(),
|
|
27
|
+
fuzzyResolve: z.array(z.string()).optional(),
|
|
28
|
+
examples: z.array(z.string()),
|
|
29
|
+
});
|
|
30
|
+
export const IntentsConfig = z.object({
|
|
31
|
+
intents: z.array(IntentDef),
|
|
32
|
+
});
|