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,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart file finder.
|
|
3
|
+
*
|
|
4
|
+
* When a user asks about a specific file or config, searches known locations
|
|
5
|
+
* by file type, service, and name. Works locally or remotely via SSH.
|
|
6
|
+
*
|
|
7
|
+
* Reads locations from config/file-hints.json so users can extend it.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { runRemoteCommand, runLocalCommand } from "../execution/ssh.js";
|
|
12
|
+
import { CONFIG_DIR } from "../utils/paths.js";
|
|
13
|
+
const HINTS_FILE = resolve(CONFIG_DIR, "file-hints.json");
|
|
14
|
+
let cachedHints = null;
|
|
15
|
+
function loadHints() {
|
|
16
|
+
if (cachedHints)
|
|
17
|
+
return cachedHints;
|
|
18
|
+
const raw = JSON.parse(readFileSync(HINTS_FILE, "utf-8"));
|
|
19
|
+
delete raw._description;
|
|
20
|
+
cachedHints = raw;
|
|
21
|
+
return cachedHints;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Known file locations by category.
|
|
25
|
+
* These are the standard/common paths on Linux systems.
|
|
26
|
+
*/
|
|
27
|
+
export const KNOWN_LOCATIONS = {
|
|
28
|
+
// Nginx
|
|
29
|
+
nginx: [
|
|
30
|
+
{ path: "/etc/nginx/nginx.conf", description: "Nginx main config" },
|
|
31
|
+
{ path: "/etc/nginx/conf.d/", description: "Nginx config directory" },
|
|
32
|
+
{ path: "/etc/nginx/sites-available/", description: "Nginx sites available" },
|
|
33
|
+
{ path: "/etc/nginx/sites-enabled/", description: "Nginx sites enabled" },
|
|
34
|
+
{ path: "/var/log/nginx/access.log", description: "Nginx access log" },
|
|
35
|
+
{ path: "/var/log/nginx/error.log", description: "Nginx error log" },
|
|
36
|
+
{ path: "/usr/share/nginx/html/", description: "Nginx default webroot" },
|
|
37
|
+
],
|
|
38
|
+
// Apache
|
|
39
|
+
apache: [
|
|
40
|
+
{ path: "/etc/apache2/apache2.conf", description: "Apache main config (Debian)" },
|
|
41
|
+
{ path: "/etc/httpd/conf/httpd.conf", description: "Apache main config (RHEL)" },
|
|
42
|
+
{ path: "/etc/apache2/sites-available/", description: "Apache sites available" },
|
|
43
|
+
{ path: "/etc/apache2/sites-enabled/", description: "Apache sites enabled" },
|
|
44
|
+
{ path: "/etc/httpd/conf.d/", description: "Apache config directory (RHEL)" },
|
|
45
|
+
{ path: "/var/log/apache2/access.log", description: "Apache access log (Debian)" },
|
|
46
|
+
{ path: "/var/log/apache2/error.log", description: "Apache error log (Debian)" },
|
|
47
|
+
{ path: "/var/log/httpd/access_log", description: "Apache access log (RHEL)" },
|
|
48
|
+
{ path: "/var/log/httpd/error_log", description: "Apache error log (RHEL)" },
|
|
49
|
+
{ path: "/var/www/html/", description: "Apache default webroot" },
|
|
50
|
+
],
|
|
51
|
+
// System auth
|
|
52
|
+
auth: [
|
|
53
|
+
{ path: "/etc/passwd", description: "User accounts" },
|
|
54
|
+
{ path: "/etc/shadow", description: "Password hashes" },
|
|
55
|
+
{ path: "/etc/group", description: "Group definitions" },
|
|
56
|
+
{ path: "/etc/sudoers", description: "Sudo rules" },
|
|
57
|
+
{ path: "/etc/sudoers.d/", description: "Sudo rules directory" },
|
|
58
|
+
{ path: "/etc/ssh/sshd_config", description: "SSH daemon config" },
|
|
59
|
+
{ path: "/etc/pam.d/", description: "PAM config directory" },
|
|
60
|
+
],
|
|
61
|
+
// SSL/TLS
|
|
62
|
+
ssl: [
|
|
63
|
+
{ path: "/etc/ssl/certs/", description: "System SSL certificates" },
|
|
64
|
+
{ path: "/etc/ssl/private/", description: "Private keys" },
|
|
65
|
+
{ path: "/etc/letsencrypt/live/", description: "Let's Encrypt certificates" },
|
|
66
|
+
{ path: "/etc/pki/tls/certs/", description: "PKI certificates (RHEL)" },
|
|
67
|
+
],
|
|
68
|
+
// System
|
|
69
|
+
system: [
|
|
70
|
+
{ path: "/etc/hostname", description: "Hostname" },
|
|
71
|
+
{ path: "/etc/hosts", description: "Hosts file" },
|
|
72
|
+
{ path: "/etc/resolv.conf", description: "DNS resolver config" },
|
|
73
|
+
{ path: "/etc/fstab", description: "Filesystem mounts" },
|
|
74
|
+
{ path: "/etc/crontab", description: "System crontab" },
|
|
75
|
+
{ path: "/etc/environment", description: "System environment variables" },
|
|
76
|
+
{ path: "/etc/sysctl.conf", description: "Kernel parameters" },
|
|
77
|
+
],
|
|
78
|
+
// Network
|
|
79
|
+
network: [
|
|
80
|
+
{ path: "/etc/network/interfaces", description: "Network interfaces (Debian)" },
|
|
81
|
+
{ path: "/etc/sysconfig/network-scripts/", description: "Network scripts (RHEL)" },
|
|
82
|
+
{ path: "/etc/netplan/", description: "Netplan config (Ubuntu)" },
|
|
83
|
+
{ path: "/etc/iptables/rules.v4", description: "iptables rules" },
|
|
84
|
+
{ path: "/etc/firewalld/", description: "Firewalld config" },
|
|
85
|
+
],
|
|
86
|
+
// Database
|
|
87
|
+
postgres: [
|
|
88
|
+
{ path: "/etc/postgresql/", description: "PostgreSQL config directory" },
|
|
89
|
+
{ path: "/var/lib/postgresql/", description: "PostgreSQL data" },
|
|
90
|
+
{ path: "/var/log/postgresql/", description: "PostgreSQL logs" },
|
|
91
|
+
],
|
|
92
|
+
mysql: [
|
|
93
|
+
{ path: "/etc/mysql/my.cnf", description: "MySQL main config" },
|
|
94
|
+
{ path: "/etc/mysql/mysql.conf.d/", description: "MySQL config directory" },
|
|
95
|
+
{ path: "/var/log/mysql/", description: "MySQL logs" },
|
|
96
|
+
],
|
|
97
|
+
redis: [
|
|
98
|
+
{ path: "/etc/redis/redis.conf", description: "Redis config" },
|
|
99
|
+
{ path: "/var/log/redis/", description: "Redis logs" },
|
|
100
|
+
],
|
|
101
|
+
// Application
|
|
102
|
+
env: [
|
|
103
|
+
{ path: ".env", description: "Local .env file" },
|
|
104
|
+
{ path: ".env.local", description: "Local overrides" },
|
|
105
|
+
{ path: ".env.production", description: "Production env" },
|
|
106
|
+
{ path: ".env.staging", description: "Staging env" },
|
|
107
|
+
{ path: ".env.development", description: "Development env" },
|
|
108
|
+
],
|
|
109
|
+
// Docker
|
|
110
|
+
docker: [
|
|
111
|
+
{ path: "/etc/docker/daemon.json", description: "Docker daemon config" },
|
|
112
|
+
{ path: "docker-compose.yml", description: "Docker Compose config" },
|
|
113
|
+
{ path: "docker-compose.yaml", description: "Docker Compose config" },
|
|
114
|
+
{ path: "Dockerfile", description: "Dockerfile" },
|
|
115
|
+
],
|
|
116
|
+
// Systemd
|
|
117
|
+
systemd: [
|
|
118
|
+
{ path: "/etc/systemd/system/", description: "Systemd unit files" },
|
|
119
|
+
{ path: "/usr/lib/systemd/system/", description: "System unit files" },
|
|
120
|
+
{ path: "/var/log/journal/", description: "Systemd journal logs" },
|
|
121
|
+
],
|
|
122
|
+
// Logs
|
|
123
|
+
logs: [
|
|
124
|
+
{ path: "/var/log/syslog", description: "System log (Debian)" },
|
|
125
|
+
{ path: "/var/log/messages", description: "System log (RHEL)" },
|
|
126
|
+
{ path: "/var/log/auth.log", description: "Auth log (Debian)" },
|
|
127
|
+
{ path: "/var/log/secure", description: "Auth log (RHEL)" },
|
|
128
|
+
{ path: "/var/log/kern.log", description: "Kernel log" },
|
|
129
|
+
{ path: "/var/log/dmesg", description: "Boot messages" },
|
|
130
|
+
],
|
|
131
|
+
};
|
|
132
|
+
// Aliases for category lookup
|
|
133
|
+
const CATEGORY_ALIASES = {
|
|
134
|
+
httpd: "apache", http: "apache", web: "nginx",
|
|
135
|
+
pg: "postgres", postgresql: "postgres", database: "postgres", db: "postgres",
|
|
136
|
+
cache: "redis",
|
|
137
|
+
ssh: "auth", users: "auth", passwd: "auth", shadow: "auth",
|
|
138
|
+
certs: "ssl", certificates: "ssl", tls: "ssl", "lets-encrypt": "ssl", letsencrypt: "ssl",
|
|
139
|
+
firewall: "network", iptables: "network", netplan: "network",
|
|
140
|
+
compose: "docker", container: "docker",
|
|
141
|
+
journal: "systemd", services: "systemd",
|
|
142
|
+
syslog: "logs", "auth.log": "logs",
|
|
143
|
+
dotenv: "env", environment: "env",
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Find files for a given query (service name, file type, or filename).
|
|
147
|
+
* Reads from config/file-hints.json.
|
|
148
|
+
*/
|
|
149
|
+
export function findKnownLocations(query) {
|
|
150
|
+
const lower = query.toLowerCase();
|
|
151
|
+
const hints = loadHints();
|
|
152
|
+
// Find the matching category
|
|
153
|
+
let category;
|
|
154
|
+
// Direct name match
|
|
155
|
+
if (hints[lower])
|
|
156
|
+
category = hints[lower];
|
|
157
|
+
// Alias match
|
|
158
|
+
if (!category) {
|
|
159
|
+
for (const cat of Object.values(hints)) {
|
|
160
|
+
if (cat.aliases?.some((a) => a === lower)) {
|
|
161
|
+
category = cat;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (category) {
|
|
167
|
+
const results = [];
|
|
168
|
+
for (const group of [category.configs, category.logs, category.data]) {
|
|
169
|
+
if (group)
|
|
170
|
+
results.push(...group);
|
|
171
|
+
}
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
// Fuzzy search across all categories
|
|
175
|
+
const results = [];
|
|
176
|
+
for (const cat of Object.values(hints)) {
|
|
177
|
+
for (const group of [cat.configs, cat.logs, cat.data]) {
|
|
178
|
+
if (!group)
|
|
179
|
+
continue;
|
|
180
|
+
for (const loc of group) {
|
|
181
|
+
if (loc.path.toLowerCase().includes(lower) || loc.description.toLowerCase().includes(lower)) {
|
|
182
|
+
results.push(loc);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get the recommended parser type for a service.
|
|
191
|
+
*/
|
|
192
|
+
export function getParserForService(service) {
|
|
193
|
+
const hints = loadHints();
|
|
194
|
+
const cat = hints[service.toLowerCase()];
|
|
195
|
+
return cat?.parser ?? "text";
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Search for a file on a remote server.
|
|
199
|
+
*/
|
|
200
|
+
export async function searchRemoteFile(query, environment, execution = "remote") {
|
|
201
|
+
const run = execution === "local" ? runLocalCommand : (cmd) => runRemoteCommand(environment, cmd);
|
|
202
|
+
// First check known locations
|
|
203
|
+
const known = findKnownLocations(query);
|
|
204
|
+
const knownPaths = known.map((k) => k.path);
|
|
205
|
+
// Check which known paths exist
|
|
206
|
+
if (knownPaths.length > 0) {
|
|
207
|
+
const checkCmd = knownPaths.map((p) => `test -e ${p} && echo ${p}`).join("; ");
|
|
208
|
+
try {
|
|
209
|
+
const result = (await run(checkCmd)).trim();
|
|
210
|
+
if (result)
|
|
211
|
+
return result.split("\n").filter(Boolean);
|
|
212
|
+
}
|
|
213
|
+
catch { }
|
|
214
|
+
}
|
|
215
|
+
// Fall back to find
|
|
216
|
+
const searchDirs = ["/etc", "/var/log", "/var/www", "/srv", "/opt", "/home"];
|
|
217
|
+
const sanitized = query.replace(/[^a-zA-Z0-9._*\-]/g, "");
|
|
218
|
+
const findCmd = `find ${searchDirs.join(" ")} -maxdepth 4 -iname '*${sanitized}*' 2>/dev/null | head -20`;
|
|
219
|
+
try {
|
|
220
|
+
const result = (await run(findCmd)).trim();
|
|
221
|
+
return result ? result.split("\n").filter(Boolean) : [];
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File parser system.
|
|
3
|
+
*
|
|
4
|
+
* Detects file type and delegates to the right parser.
|
|
5
|
+
* Supports: passwd, shadow, .env, yaml, json, generic text.
|
|
6
|
+
*/
|
|
7
|
+
export type FileType = "passwd" | "shadow" | "env" | "yaml" | "json" | "nginx" | "apache" | "bind" | "text" | "unknown";
|
|
8
|
+
export interface ParsedFile {
|
|
9
|
+
path: string;
|
|
10
|
+
type: FileType;
|
|
11
|
+
raw: string;
|
|
12
|
+
data: unknown;
|
|
13
|
+
summary: string;
|
|
14
|
+
entries?: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detect file type from path and content.
|
|
18
|
+
*/
|
|
19
|
+
export declare function detectFileType(filePath: string, content?: string): FileType;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a file and return structured data.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseFile(filePath: string): ParsedFile;
|
|
24
|
+
/**
|
|
25
|
+
* Format a parsed file for display.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatParsedFile(parsed: ParsedFile): string;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File parser system.
|
|
3
|
+
*
|
|
4
|
+
* Detects file type and delegates to the right parser.
|
|
5
|
+
* Supports: passwd, shadow, .env, yaml, json, generic text.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { extname, basename } from "node:path";
|
|
9
|
+
import { parsePasswd } from "./passwd.js";
|
|
10
|
+
import { parseShadow } from "./shadow.js";
|
|
11
|
+
import { parseEnvFile } from "./envFile.js";
|
|
12
|
+
import { parseYaml } from "./yamlParser.js";
|
|
13
|
+
import { parseJson } from "./jsonParser.js";
|
|
14
|
+
import { parseNginx, formatNginxSummary } from "./nginxParser.js";
|
|
15
|
+
import { parseApache, formatApacheSummary } from "./apacheParser.js";
|
|
16
|
+
import { parseZoneFile, formatZoneSummary } from "./bindParser.js";
|
|
17
|
+
/**
|
|
18
|
+
* Detect file type from path and content.
|
|
19
|
+
*/
|
|
20
|
+
export function detectFileType(filePath, content) {
|
|
21
|
+
const name = basename(filePath);
|
|
22
|
+
const ext = extname(filePath).toLowerCase();
|
|
23
|
+
if (name === "passwd" || filePath.includes("/etc/passwd"))
|
|
24
|
+
return "passwd";
|
|
25
|
+
if (name === "shadow" || filePath.includes("/etc/shadow"))
|
|
26
|
+
return "shadow";
|
|
27
|
+
if (name === ".env" || name.startsWith(".env.") || ext === ".env")
|
|
28
|
+
return "env";
|
|
29
|
+
if (ext === ".yml" || ext === ".yaml")
|
|
30
|
+
return "yaml";
|
|
31
|
+
if (ext === ".json")
|
|
32
|
+
return "json";
|
|
33
|
+
if (filePath.includes("nginx") && (ext === ".conf" || ext === ""))
|
|
34
|
+
return "nginx";
|
|
35
|
+
if ((filePath.includes("apache") || filePath.includes("httpd")) && (ext === ".conf" || ext === ""))
|
|
36
|
+
return "apache";
|
|
37
|
+
if ((filePath.includes("bind") || filePath.includes("named") || filePath.includes("/zones/")) && ext === ".zone")
|
|
38
|
+
return "bind";
|
|
39
|
+
if (filePath.includes("db.") && filePath.includes("/bind"))
|
|
40
|
+
return "bind";
|
|
41
|
+
// Content-based detection
|
|
42
|
+
if (content) {
|
|
43
|
+
const firstLine = content.split("\n")[0] ?? "";
|
|
44
|
+
// passwd format: user:x:uid:gid:...
|
|
45
|
+
if (/^\w+:[x*!]:?\d+:\d+:/.test(firstLine))
|
|
46
|
+
return "passwd";
|
|
47
|
+
// shadow format: user:$hash:...
|
|
48
|
+
if (/^\w+:[\$!*]/.test(firstLine))
|
|
49
|
+
return "shadow";
|
|
50
|
+
// env format: KEY=VALUE
|
|
51
|
+
if (/^[A-Z_][A-Z0-9_]*=/.test(firstLine))
|
|
52
|
+
return "env";
|
|
53
|
+
// json
|
|
54
|
+
if (firstLine.trim().startsWith("{") || firstLine.trim().startsWith("["))
|
|
55
|
+
return "json";
|
|
56
|
+
// yaml
|
|
57
|
+
if (firstLine.trim().startsWith("---") || /^\w+:\s/.test(firstLine))
|
|
58
|
+
return "yaml";
|
|
59
|
+
}
|
|
60
|
+
return "text";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Parse a file and return structured data.
|
|
64
|
+
*/
|
|
65
|
+
export function parseFile(filePath) {
|
|
66
|
+
if (!existsSync(filePath)) {
|
|
67
|
+
return { path: filePath, type: "unknown", raw: "", data: null, summary: `File not found: ${filePath}` };
|
|
68
|
+
}
|
|
69
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
70
|
+
const type = detectFileType(filePath, raw);
|
|
71
|
+
switch (type) {
|
|
72
|
+
case "passwd": {
|
|
73
|
+
const entries = parsePasswd(raw);
|
|
74
|
+
return {
|
|
75
|
+
path: filePath, type, raw, data: entries,
|
|
76
|
+
summary: `${entries.length} user(s)`,
|
|
77
|
+
entries: entries.length,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
case "shadow": {
|
|
81
|
+
const entries = parseShadow(raw);
|
|
82
|
+
return {
|
|
83
|
+
path: filePath, type, raw, data: entries,
|
|
84
|
+
summary: `${entries.length} shadow entries`,
|
|
85
|
+
entries: entries.length,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case "env": {
|
|
89
|
+
const entries = parseEnvFile(raw);
|
|
90
|
+
return {
|
|
91
|
+
path: filePath, type, raw, data: entries,
|
|
92
|
+
summary: `${entries.length} variable(s)`,
|
|
93
|
+
entries: entries.length,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
case "yaml": {
|
|
97
|
+
const data = parseYaml(raw);
|
|
98
|
+
const keys = typeof data === "object" && data ? Object.keys(data) : [];
|
|
99
|
+
return {
|
|
100
|
+
path: filePath, type, raw, data,
|
|
101
|
+
summary: `YAML with ${keys.length} top-level key(s)`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
case "json": {
|
|
105
|
+
const data = parseJson(raw);
|
|
106
|
+
const keys = typeof data === "object" && data && !Array.isArray(data) ? Object.keys(data) : [];
|
|
107
|
+
const count = Array.isArray(data) ? data.length : keys.length;
|
|
108
|
+
return {
|
|
109
|
+
path: filePath, type, raw, data,
|
|
110
|
+
summary: `JSON with ${count} ${Array.isArray(data) ? "item(s)" : "key(s)"}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
case "nginx": {
|
|
114
|
+
const config = parseNginx(raw);
|
|
115
|
+
return {
|
|
116
|
+
path: filePath, type, raw, data: config,
|
|
117
|
+
summary: `Nginx config with ${config.servers.length} server block(s)`,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
case "apache": {
|
|
121
|
+
const config = parseApache(raw);
|
|
122
|
+
return {
|
|
123
|
+
path: filePath, type, raw, data: config,
|
|
124
|
+
summary: `Apache config with ${config.vhosts.length} VirtualHost(s)`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case "bind": {
|
|
128
|
+
const zoneData = parseZoneFile(raw);
|
|
129
|
+
return {
|
|
130
|
+
path: filePath, type, raw, data: zoneData,
|
|
131
|
+
summary: `DNS zone with ${zoneData.records.length} record(s)`,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
return {
|
|
136
|
+
path: filePath, type: "text", raw, data: raw,
|
|
137
|
+
summary: `${raw.split("\n").length} line(s)`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Format a parsed file for display.
|
|
143
|
+
*/
|
|
144
|
+
export function formatParsedFile(parsed) {
|
|
145
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", yellow: "\x1b[33m" };
|
|
146
|
+
const lines = [];
|
|
147
|
+
lines.push(`${c.bold}${parsed.path}${c.reset} (${parsed.type}) — ${parsed.summary}`);
|
|
148
|
+
lines.push("");
|
|
149
|
+
switch (parsed.type) {
|
|
150
|
+
case "passwd": {
|
|
151
|
+
const entries = parsed.data;
|
|
152
|
+
for (const e of entries.slice(0, 20)) {
|
|
153
|
+
lines.push(` ${c.cyan}${e.username}${c.reset} uid=${e.uid} gid=${e.gid} home=${e.home} shell=${e.shell}`);
|
|
154
|
+
}
|
|
155
|
+
if (entries.length > 20)
|
|
156
|
+
lines.push(` ${c.dim}... and ${entries.length - 20} more${c.reset}`);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
case "shadow": {
|
|
160
|
+
const entries = parsed.data;
|
|
161
|
+
for (const e of entries.slice(0, 20)) {
|
|
162
|
+
const status = e.locked ? "locked" : e.hasPassword ? "has password" : "no password";
|
|
163
|
+
lines.push(` ${c.cyan}${e.username}${c.reset} ${status} lastChanged=${e.lastChanged ?? "?"}`);
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
case "env": {
|
|
168
|
+
const entries = parsed.data;
|
|
169
|
+
for (const e of entries) {
|
|
170
|
+
const val = e.isSecret ? `${c.yellow}****${c.reset}` : e.value;
|
|
171
|
+
lines.push(` ${c.cyan}${e.key}${c.reset}=${val}`);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "nginx":
|
|
176
|
+
lines.push(formatNginxSummary(parsed.data));
|
|
177
|
+
break;
|
|
178
|
+
case "apache":
|
|
179
|
+
lines.push(formatApacheSummary(parsed.data));
|
|
180
|
+
break;
|
|
181
|
+
case "bind":
|
|
182
|
+
lines.push(formatZoneSummary(parsed.data));
|
|
183
|
+
break;
|
|
184
|
+
case "yaml":
|
|
185
|
+
case "json":
|
|
186
|
+
lines.push(JSON.stringify(parsed.data, null, 2).split("\n").slice(0, 30).join("\n"));
|
|
187
|
+
break;
|
|
188
|
+
default:
|
|
189
|
+
lines.push(parsed.raw.split("\n").slice(0, 20).join("\n"));
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
return lines.join("\n");
|
|
193
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON parser with path-based access.
|
|
3
|
+
*/
|
|
4
|
+
export declare function parseJson(content: string): unknown;
|
|
5
|
+
/**
|
|
6
|
+
* Get a nested value using dot-notation or bracket path.
|
|
7
|
+
* e.g., getJsonValue(data, "users[0].name")
|
|
8
|
+
*/
|
|
9
|
+
export declare function getJsonValue(data: unknown, path: string): unknown;
|
|
10
|
+
/**
|
|
11
|
+
* Search JSON for keys matching a pattern.
|
|
12
|
+
*/
|
|
13
|
+
export declare function searchJsonKeys(data: unknown, pattern: string, prefix?: string): Array<{
|
|
14
|
+
path: string;
|
|
15
|
+
value: unknown;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON parser with path-based access.
|
|
3
|
+
*/
|
|
4
|
+
export function parseJson(content) {
|
|
5
|
+
return JSON.parse(content);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Get a nested value using dot-notation or bracket path.
|
|
9
|
+
* e.g., getJsonValue(data, "users[0].name")
|
|
10
|
+
*/
|
|
11
|
+
export function getJsonValue(data, path) {
|
|
12
|
+
const parts = path.match(/[^.[\]]+/g);
|
|
13
|
+
if (!parts)
|
|
14
|
+
return undefined;
|
|
15
|
+
let current = data;
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(current)) {
|
|
21
|
+
const idx = Number(part);
|
|
22
|
+
if (isNaN(idx))
|
|
23
|
+
return undefined;
|
|
24
|
+
current = current[idx];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
current = current[part];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return current;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Search JSON for keys matching a pattern.
|
|
34
|
+
*/
|
|
35
|
+
export function searchJsonKeys(data, pattern, prefix = "") {
|
|
36
|
+
const results = [];
|
|
37
|
+
const lower = pattern.toLowerCase();
|
|
38
|
+
if (data === null || data === undefined || typeof data !== "object")
|
|
39
|
+
return results;
|
|
40
|
+
if (Array.isArray(data)) {
|
|
41
|
+
for (let i = 0; i < data.length; i++) {
|
|
42
|
+
results.push(...searchJsonKeys(data[i], pattern, `${prefix}[${i}]`));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
for (const [key, value] of Object.entries(data)) {
|
|
47
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
48
|
+
if (key.toLowerCase().includes(lower)) {
|
|
49
|
+
results.push({ path: fullPath, value });
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "object" && value !== null) {
|
|
52
|
+
results.push(...searchJsonKeys(value, pattern, fullPath));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
export interface NginxDirective {
|
|
8
|
+
name: string;
|
|
9
|
+
args: string[];
|
|
10
|
+
line: number;
|
|
11
|
+
}
|
|
12
|
+
export interface NginxBlock {
|
|
13
|
+
type: string;
|
|
14
|
+
args: string[];
|
|
15
|
+
directives: NginxDirective[];
|
|
16
|
+
blocks: NginxBlock[];
|
|
17
|
+
line: number;
|
|
18
|
+
}
|
|
19
|
+
export interface NginxConfig {
|
|
20
|
+
directives: NginxDirective[];
|
|
21
|
+
blocks: NginxBlock[];
|
|
22
|
+
servers: NginxServerBlock[];
|
|
23
|
+
}
|
|
24
|
+
export interface NginxServerBlock {
|
|
25
|
+
listen: string[];
|
|
26
|
+
serverName: string[];
|
|
27
|
+
root?: string;
|
|
28
|
+
index?: string;
|
|
29
|
+
locations: Array<{
|
|
30
|
+
path: string;
|
|
31
|
+
proxyPass?: string;
|
|
32
|
+
root?: string;
|
|
33
|
+
tryFiles?: string;
|
|
34
|
+
directives: NginxDirective[];
|
|
35
|
+
}>;
|
|
36
|
+
ssl: boolean;
|
|
37
|
+
sslCertificate?: string;
|
|
38
|
+
sslCertificateKey?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Parse nginx config content.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseNginx(content: string): NginxConfig;
|
|
44
|
+
/**
|
|
45
|
+
* Format an nginx config summary for display.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatNginxSummary(config: NginxConfig): string;
|