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,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Apache config parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses httpd.conf / apache2.conf and vhost configs.
|
|
5
|
+
* Handles: <VirtualHost>, <Directory>, <Location>, directives.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Parse Apache config content.
|
|
9
|
+
*/
|
|
10
|
+
export function parseApache(content) {
|
|
11
|
+
const lines = content.split("\n");
|
|
12
|
+
const { directives, blocks } = parseApacheBlock(lines, 0, lines.length);
|
|
13
|
+
const vhosts = extractVHosts(blocks);
|
|
14
|
+
return { directives, blocks, vhosts };
|
|
15
|
+
}
|
|
16
|
+
function parseApacheBlock(lines, start, end) {
|
|
17
|
+
const directives = [];
|
|
18
|
+
const blocks = [];
|
|
19
|
+
let i = start;
|
|
20
|
+
while (i < end) {
|
|
21
|
+
const line = lines[i].trim();
|
|
22
|
+
if (!line || line.startsWith("#")) {
|
|
23
|
+
i++;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Block opening: <VirtualHost *:80>, <Directory /var/www>
|
|
27
|
+
const openMatch = line.match(/^<(\w+)\s*(.*?)>$/);
|
|
28
|
+
if (openMatch) {
|
|
29
|
+
const tag = openMatch[1];
|
|
30
|
+
const args = openMatch[2] ?? "";
|
|
31
|
+
const closeTag = `</${tag}>`;
|
|
32
|
+
// Find matching closing tag
|
|
33
|
+
let j = i + 1;
|
|
34
|
+
let depth = 1;
|
|
35
|
+
while (j < end && depth > 0) {
|
|
36
|
+
const l = lines[j].trim();
|
|
37
|
+
if (l.match(new RegExp(`^<${tag}\\b`)))
|
|
38
|
+
depth++;
|
|
39
|
+
if (l === closeTag || l.toLowerCase() === closeTag.toLowerCase())
|
|
40
|
+
depth--;
|
|
41
|
+
if (depth > 0)
|
|
42
|
+
j++;
|
|
43
|
+
}
|
|
44
|
+
const inner = parseApacheBlock(lines, i + 1, j);
|
|
45
|
+
blocks.push({
|
|
46
|
+
tag,
|
|
47
|
+
args,
|
|
48
|
+
directives: inner.directives,
|
|
49
|
+
blocks: inner.blocks,
|
|
50
|
+
line: i + 1,
|
|
51
|
+
});
|
|
52
|
+
i = j + 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Closing tag (handled by the block parser above)
|
|
56
|
+
if (line.startsWith("</")) {
|
|
57
|
+
i++;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Directive: ServerName example.com
|
|
61
|
+
const parts = line.match(/^(\S+)\s+(.*)/);
|
|
62
|
+
if (parts) {
|
|
63
|
+
directives.push({ name: parts[1], value: parts[2], line: i + 1 });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
directives.push({ name: line, value: "", line: i + 1 });
|
|
67
|
+
}
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
return { directives, blocks };
|
|
71
|
+
}
|
|
72
|
+
function extractVHosts(blocks) {
|
|
73
|
+
return blocks
|
|
74
|
+
.filter((b) => b.tag.toLowerCase() === "virtualhost")
|
|
75
|
+
.map((block) => {
|
|
76
|
+
const vhost = {
|
|
77
|
+
address: block.args,
|
|
78
|
+
serverAlias: [],
|
|
79
|
+
ssl: false,
|
|
80
|
+
locations: [],
|
|
81
|
+
directories: [],
|
|
82
|
+
};
|
|
83
|
+
for (const d of block.directives) {
|
|
84
|
+
switch (d.name.toLowerCase()) {
|
|
85
|
+
case "servername":
|
|
86
|
+
vhost.serverName = d.value;
|
|
87
|
+
break;
|
|
88
|
+
case "serveralias":
|
|
89
|
+
vhost.serverAlias.push(...d.value.split(/\s+/));
|
|
90
|
+
break;
|
|
91
|
+
case "documentroot":
|
|
92
|
+
vhost.documentRoot = d.value;
|
|
93
|
+
break;
|
|
94
|
+
case "errorlog":
|
|
95
|
+
vhost.errorLog = d.value;
|
|
96
|
+
break;
|
|
97
|
+
case "customlog":
|
|
98
|
+
vhost.customLog = d.value.split(/\s+/)[0];
|
|
99
|
+
break;
|
|
100
|
+
case "sslengine":
|
|
101
|
+
vhost.ssl = d.value.toLowerCase() === "on";
|
|
102
|
+
break;
|
|
103
|
+
case "sslcertificatefile":
|
|
104
|
+
vhost.sslCertificate = d.value;
|
|
105
|
+
vhost.ssl = true;
|
|
106
|
+
break;
|
|
107
|
+
case "sslcertificatekeyfile":
|
|
108
|
+
vhost.sslCertificateKey = d.value;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const sub of block.blocks) {
|
|
113
|
+
const tag = sub.tag.toLowerCase();
|
|
114
|
+
if (tag === "location") {
|
|
115
|
+
vhost.locations.push({ path: sub.args, directives: sub.directives });
|
|
116
|
+
}
|
|
117
|
+
if (tag === "directory") {
|
|
118
|
+
vhost.directories.push({ path: sub.args, directives: sub.directives });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return vhost;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format an Apache config summary for display.
|
|
126
|
+
*/
|
|
127
|
+
export function formatApacheSummary(config) {
|
|
128
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", yellow: "\x1b[33m" };
|
|
129
|
+
const lines = [];
|
|
130
|
+
lines.push(`${c.bold}Apache Config Summary${c.reset}`);
|
|
131
|
+
lines.push(` ${config.vhosts.length} VirtualHost(s)\n`);
|
|
132
|
+
for (const vh of config.vhosts) {
|
|
133
|
+
const sslLabel = vh.ssl ? ` ${c.green}[SSL]${c.reset}` : "";
|
|
134
|
+
lines.push(` ${c.cyan}<VirtualHost ${vh.address}>${c.reset}${sslLabel}`);
|
|
135
|
+
if (vh.serverName)
|
|
136
|
+
lines.push(` ServerName: ${c.bold}${vh.serverName}${c.reset}`);
|
|
137
|
+
if (vh.serverAlias.length)
|
|
138
|
+
lines.push(` ServerAlias: ${vh.serverAlias.join(", ")}`);
|
|
139
|
+
if (vh.documentRoot)
|
|
140
|
+
lines.push(` DocumentRoot: ${vh.documentRoot}`);
|
|
141
|
+
if (vh.errorLog)
|
|
142
|
+
lines.push(` ErrorLog: ${vh.errorLog}`);
|
|
143
|
+
for (const loc of vh.locations) {
|
|
144
|
+
lines.push(` ${c.dim}<Location ${loc.path}>${c.reset}`);
|
|
145
|
+
}
|
|
146
|
+
for (const dir of vh.directories) {
|
|
147
|
+
lines.push(` ${c.dim}<Directory ${dir.path}>${c.reset}`);
|
|
148
|
+
}
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BIND zone file parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses DNS zone files and extracts:
|
|
5
|
+
* - SOA record (primary NS, admin, serial, timers)
|
|
6
|
+
* - A, AAAA, CNAME, MX, NS, TXT, SRV, PTR records
|
|
7
|
+
* - $TTL, $ORIGIN directives
|
|
8
|
+
*/
|
|
9
|
+
export interface DnsRecord {
|
|
10
|
+
name: string;
|
|
11
|
+
ttl?: number;
|
|
12
|
+
class: string;
|
|
13
|
+
type: string;
|
|
14
|
+
data: string;
|
|
15
|
+
priority?: number;
|
|
16
|
+
line: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SoaRecord {
|
|
19
|
+
primaryNS: string;
|
|
20
|
+
adminEmail: string;
|
|
21
|
+
serial: number;
|
|
22
|
+
refresh: number;
|
|
23
|
+
retry: number;
|
|
24
|
+
expire: number;
|
|
25
|
+
minimum: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ZoneFile {
|
|
28
|
+
origin?: string;
|
|
29
|
+
defaultTTL?: number;
|
|
30
|
+
soa?: SoaRecord;
|
|
31
|
+
records: DnsRecord[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a BIND zone file.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseZoneFile(content: string): ZoneFile;
|
|
37
|
+
/**
|
|
38
|
+
* Format a zone file summary for display.
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatZoneSummary(zone: ZoneFile): string;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BIND zone file parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses DNS zone files and extracts:
|
|
5
|
+
* - SOA record (primary NS, admin, serial, timers)
|
|
6
|
+
* - A, AAAA, CNAME, MX, NS, TXT, SRV, PTR records
|
|
7
|
+
* - $TTL, $ORIGIN directives
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Parse a BIND zone file.
|
|
11
|
+
*/
|
|
12
|
+
export function parseZoneFile(content) {
|
|
13
|
+
const lines = content.split("\n");
|
|
14
|
+
const zone = { records: [] };
|
|
15
|
+
let currentName = "@";
|
|
16
|
+
let inSOA = false;
|
|
17
|
+
let soaBuffer = "";
|
|
18
|
+
for (let i = 0; i < lines.length; i++) {
|
|
19
|
+
let line = lines[i];
|
|
20
|
+
// Strip comments
|
|
21
|
+
const commentIdx = line.indexOf(";");
|
|
22
|
+
if (commentIdx !== -1)
|
|
23
|
+
line = line.slice(0, commentIdx);
|
|
24
|
+
line = line.trimEnd();
|
|
25
|
+
if (!line.trim())
|
|
26
|
+
continue;
|
|
27
|
+
// $ORIGIN directive
|
|
28
|
+
const originMatch = line.match(/^\$ORIGIN\s+(\S+)/i);
|
|
29
|
+
if (originMatch) {
|
|
30
|
+
zone.origin = originMatch[1];
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
// $TTL directive
|
|
34
|
+
const ttlMatch = line.match(/^\$TTL\s+(\S+)/i);
|
|
35
|
+
if (ttlMatch) {
|
|
36
|
+
zone.defaultTTL = parseTTL(ttlMatch[1]);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// SOA record (may span multiple lines)
|
|
40
|
+
if (line.toUpperCase().includes("SOA") && !inSOA) {
|
|
41
|
+
inSOA = true;
|
|
42
|
+
soaBuffer = line;
|
|
43
|
+
if (line.includes(")")) {
|
|
44
|
+
zone.soa = parseSOA(soaBuffer) ?? undefined;
|
|
45
|
+
inSOA = false;
|
|
46
|
+
soaBuffer = "";
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inSOA) {
|
|
51
|
+
soaBuffer += " " + line.trim();
|
|
52
|
+
if (line.includes(")")) {
|
|
53
|
+
zone.soa = parseSOA(soaBuffer) ?? undefined;
|
|
54
|
+
inSOA = false;
|
|
55
|
+
soaBuffer = "";
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Regular records
|
|
60
|
+
const record = parseRecord(line, currentName, i + 1);
|
|
61
|
+
if (record) {
|
|
62
|
+
if (record.name !== "")
|
|
63
|
+
currentName = record.name;
|
|
64
|
+
else
|
|
65
|
+
record.name = currentName;
|
|
66
|
+
zone.records.push(record);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return zone;
|
|
70
|
+
}
|
|
71
|
+
function parseRecord(line, currentName, lineNum) {
|
|
72
|
+
const parts = line.trim().split(/\s+/);
|
|
73
|
+
if (parts.length < 2)
|
|
74
|
+
return null;
|
|
75
|
+
let idx = 0;
|
|
76
|
+
let name = "";
|
|
77
|
+
let ttl;
|
|
78
|
+
let cls = "IN";
|
|
79
|
+
let type = "";
|
|
80
|
+
let data = "";
|
|
81
|
+
// First token: name or empty (continuation of previous name)
|
|
82
|
+
if (line.match(/^\s/)) {
|
|
83
|
+
name = "";
|
|
84
|
+
idx = 0;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
name = parts[0];
|
|
88
|
+
idx = 1;
|
|
89
|
+
}
|
|
90
|
+
// Optional TTL (number)
|
|
91
|
+
if (idx < parts.length && /^\d+[smhdwSMHDW]?$/.test(parts[idx])) {
|
|
92
|
+
ttl = parseTTL(parts[idx]);
|
|
93
|
+
idx++;
|
|
94
|
+
}
|
|
95
|
+
// Optional class (IN, CH, HS)
|
|
96
|
+
if (idx < parts.length && /^(IN|CH|HS)$/i.test(parts[idx])) {
|
|
97
|
+
cls = parts[idx].toUpperCase();
|
|
98
|
+
idx++;
|
|
99
|
+
}
|
|
100
|
+
// Type
|
|
101
|
+
if (idx < parts.length) {
|
|
102
|
+
type = parts[idx].toUpperCase();
|
|
103
|
+
idx++;
|
|
104
|
+
}
|
|
105
|
+
if (!type || type === "SOA")
|
|
106
|
+
return null;
|
|
107
|
+
// Data (rest of line)
|
|
108
|
+
data = parts.slice(idx).join(" ");
|
|
109
|
+
// Extract priority for MX and SRV
|
|
110
|
+
let priority;
|
|
111
|
+
if ((type === "MX" || type === "SRV") && /^\d+/.test(data)) {
|
|
112
|
+
const pMatch = data.match(/^(\d+)\s+(.*)/);
|
|
113
|
+
if (pMatch) {
|
|
114
|
+
priority = Number(pMatch[1]);
|
|
115
|
+
data = pMatch[2];
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { name: name || currentName, ttl, class: cls, type, data, priority, line: lineNum };
|
|
119
|
+
}
|
|
120
|
+
function parseSOA(raw) {
|
|
121
|
+
// Extract the parenthesized section
|
|
122
|
+
const parts = raw.replace(/[()]/g, " ").split(/\s+/).filter(Boolean);
|
|
123
|
+
// Find SOA keyword position
|
|
124
|
+
const soaIdx = parts.findIndex((p) => p.toUpperCase() === "SOA");
|
|
125
|
+
if (soaIdx === -1)
|
|
126
|
+
return null;
|
|
127
|
+
const afterSOA = parts.slice(soaIdx + 1);
|
|
128
|
+
if (afterSOA.length < 7)
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
primaryNS: afterSOA[0],
|
|
132
|
+
adminEmail: afterSOA[1],
|
|
133
|
+
serial: Number(afterSOA[2]) || 0,
|
|
134
|
+
refresh: parseTTL(afterSOA[3]),
|
|
135
|
+
retry: parseTTL(afterSOA[4]),
|
|
136
|
+
expire: parseTTL(afterSOA[5]),
|
|
137
|
+
minimum: parseTTL(afterSOA[6]),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function parseTTL(value) {
|
|
141
|
+
const num = parseInt(value, 10);
|
|
142
|
+
if (isNaN(num))
|
|
143
|
+
return 0;
|
|
144
|
+
const suffix = value.slice(String(num).length).toLowerCase();
|
|
145
|
+
switch (suffix) {
|
|
146
|
+
case "s": return num;
|
|
147
|
+
case "m": return num * 60;
|
|
148
|
+
case "h": return num * 3600;
|
|
149
|
+
case "d": return num * 86400;
|
|
150
|
+
case "w": return num * 604800;
|
|
151
|
+
default: return num;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Format a zone file summary for display.
|
|
156
|
+
*/
|
|
157
|
+
export function formatZoneSummary(zone) {
|
|
158
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m" };
|
|
159
|
+
const lines = [];
|
|
160
|
+
lines.push(`${c.bold}DNS Zone File${c.reset}`);
|
|
161
|
+
if (zone.origin)
|
|
162
|
+
lines.push(` Origin: ${zone.origin}`);
|
|
163
|
+
if (zone.defaultTTL)
|
|
164
|
+
lines.push(` Default TTL: ${zone.defaultTTL}s`);
|
|
165
|
+
if (zone.soa) {
|
|
166
|
+
lines.push(`\n ${c.cyan}SOA:${c.reset}`);
|
|
167
|
+
lines.push(` Primary NS: ${zone.soa.primaryNS}`);
|
|
168
|
+
lines.push(` Admin: ${zone.soa.adminEmail}`);
|
|
169
|
+
lines.push(` Serial: ${zone.soa.serial}`);
|
|
170
|
+
}
|
|
171
|
+
// Group by type
|
|
172
|
+
const byType = new Map();
|
|
173
|
+
for (const r of zone.records) {
|
|
174
|
+
const list = byType.get(r.type) ?? [];
|
|
175
|
+
list.push(r);
|
|
176
|
+
byType.set(r.type, list);
|
|
177
|
+
}
|
|
178
|
+
for (const [type, records] of byType) {
|
|
179
|
+
lines.push(`\n ${c.cyan}${type} records (${records.length}):${c.reset}`);
|
|
180
|
+
for (const r of records.slice(0, 20)) {
|
|
181
|
+
const pri = r.priority !== undefined ? ` [pri=${r.priority}]` : "";
|
|
182
|
+
const ttl = r.ttl !== undefined ? ` TTL=${r.ttl}` : "";
|
|
183
|
+
lines.push(` ${r.name.padEnd(20)} → ${r.data}${pri}${c.dim}${ttl}${c.reset}`);
|
|
184
|
+
}
|
|
185
|
+
if (records.length > 20)
|
|
186
|
+
lines.push(` ${c.dim}... and ${records.length - 20} more${c.reset}`);
|
|
187
|
+
}
|
|
188
|
+
return lines.join("\n");
|
|
189
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .env file parser and writer.
|
|
3
|
+
*
|
|
4
|
+
* Reads KEY=VALUE pairs, detects secrets, supports comments.
|
|
5
|
+
*/
|
|
6
|
+
export interface EnvEntry {
|
|
7
|
+
key: string;
|
|
8
|
+
value: string;
|
|
9
|
+
comment?: string;
|
|
10
|
+
isSecret: boolean;
|
|
11
|
+
line: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse .env file content into entries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseEnvFile(content: string): EnvEntry[];
|
|
17
|
+
/**
|
|
18
|
+
* Get the value of a specific key.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getEnvValue(entries: EnvEntry[], key: string): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Set or update a key in env entries. Returns new content string.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setEnvValue(content: string, key: string, value: string, comment?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Read, modify, and write back an .env file.
|
|
27
|
+
*/
|
|
28
|
+
export declare function updateEnvFile(filePath: string, key: string, value: string, comment?: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Generate a smart variable name based on purpose and topic.
|
|
31
|
+
*
|
|
32
|
+
* Examples:
|
|
33
|
+
* ("database", "password", "staging") → DB_STAGING_PASSWORD
|
|
34
|
+
* ("api", "key", "prod") → API_PROD_KEY
|
|
35
|
+
* ("redis", "url", undefined) → REDIS_URL
|
|
36
|
+
* ("smtp", "password", "prod") → SMTP_PROD_PASSWORD
|
|
37
|
+
* ("aws", "secret", "production") → AWS_PROD_SECRET
|
|
38
|
+
*/
|
|
39
|
+
export declare function generateEnvName(topic: string, purpose: string, environment?: string): string;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .env file parser and writer.
|
|
3
|
+
*
|
|
4
|
+
* Reads KEY=VALUE pairs, detects secrets, supports comments.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
7
|
+
// Keys that likely contain secrets
|
|
8
|
+
const SECRET_KEY_PATTERNS = [
|
|
9
|
+
/password/i, /passwd/i, /secret/i, /token/i, /api[_-]?key/i,
|
|
10
|
+
/private[_-]?key/i, /auth/i, /credential/i, /access[_-]?key/i,
|
|
11
|
+
/connection[_-]?string/i, /db[_-]?pass/i,
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Parse .env file content into entries.
|
|
15
|
+
*/
|
|
16
|
+
export function parseEnvFile(content) {
|
|
17
|
+
const entries = [];
|
|
18
|
+
const lines = content.split("\n");
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const line = lines[i].trim();
|
|
21
|
+
if (!line || line.startsWith("#"))
|
|
22
|
+
continue;
|
|
23
|
+
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
|
|
24
|
+
if (!match)
|
|
25
|
+
continue;
|
|
26
|
+
const key = match[1];
|
|
27
|
+
let value = match[2];
|
|
28
|
+
// Strip surrounding quotes
|
|
29
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
30
|
+
value = value.slice(1, -1);
|
|
31
|
+
}
|
|
32
|
+
// Strip inline comment
|
|
33
|
+
let comment;
|
|
34
|
+
const commentMatch = value.match(/\s+#\s*(.*)$/);
|
|
35
|
+
if (commentMatch && !value.startsWith('"')) {
|
|
36
|
+
comment = commentMatch[1];
|
|
37
|
+
value = value.slice(0, value.indexOf(commentMatch[0]));
|
|
38
|
+
}
|
|
39
|
+
const isSecret = SECRET_KEY_PATTERNS.some((p) => p.test(key));
|
|
40
|
+
entries.push({ key, value, comment, isSecret, line: i + 1 });
|
|
41
|
+
}
|
|
42
|
+
return entries;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the value of a specific key.
|
|
46
|
+
*/
|
|
47
|
+
export function getEnvValue(entries, key) {
|
|
48
|
+
return entries.find((e) => e.key === key)?.value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Set or update a key in env entries. Returns new content string.
|
|
52
|
+
*/
|
|
53
|
+
export function setEnvValue(content, key, value, comment) {
|
|
54
|
+
const lines = content.split("\n");
|
|
55
|
+
const pattern = new RegExp(`^${key}=`);
|
|
56
|
+
const newLine = comment ? `${key}=${value} # ${comment}` : `${key}=${value}`;
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
if (pattern.test(lines[i].trim())) {
|
|
59
|
+
lines[i] = newLine;
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Key not found — append
|
|
64
|
+
lines.push(newLine);
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read, modify, and write back an .env file.
|
|
69
|
+
*/
|
|
70
|
+
export function updateEnvFile(filePath, key, value, comment) {
|
|
71
|
+
const content = existsSync(filePath) ? readFileSync(filePath, "utf-8") : "";
|
|
72
|
+
const updated = setEnvValue(content, key, value, comment);
|
|
73
|
+
writeFileSync(filePath, updated);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate a smart variable name based on purpose and topic.
|
|
77
|
+
*
|
|
78
|
+
* Examples:
|
|
79
|
+
* ("database", "password", "staging") → DB_STAGING_PASSWORD
|
|
80
|
+
* ("api", "key", "prod") → API_PROD_KEY
|
|
81
|
+
* ("redis", "url", undefined) → REDIS_URL
|
|
82
|
+
* ("smtp", "password", "prod") → SMTP_PROD_PASSWORD
|
|
83
|
+
* ("aws", "secret", "production") → AWS_PROD_SECRET
|
|
84
|
+
*/
|
|
85
|
+
export function generateEnvName(topic, purpose, environment) {
|
|
86
|
+
const parts = [];
|
|
87
|
+
// Topic aliases
|
|
88
|
+
const topicMap = {
|
|
89
|
+
database: "DB", db: "DB", postgres: "DB", postgresql: "DB", mysql: "DB", mongo: "DB",
|
|
90
|
+
redis: "REDIS", cache: "REDIS",
|
|
91
|
+
api: "API", rest: "API",
|
|
92
|
+
smtp: "SMTP", email: "SMTP", mail: "SMTP",
|
|
93
|
+
aws: "AWS", s3: "AWS_S3", gcp: "GCP", azure: "AZURE",
|
|
94
|
+
jwt: "JWT", auth: "AUTH", oauth: "OAUTH",
|
|
95
|
+
stripe: "STRIPE", twilio: "TWILIO", sendgrid: "SENDGRID",
|
|
96
|
+
github: "GITHUB", gitlab: "GITLAB",
|
|
97
|
+
docker: "DOCKER", k8s: "K8S", kubernetes: "K8S",
|
|
98
|
+
sentry: "SENTRY", datadog: "DATADOG",
|
|
99
|
+
app: "APP", server: "SERVER", node: "NODE",
|
|
100
|
+
};
|
|
101
|
+
parts.push(topicMap[topic.toLowerCase()] ?? topic.toUpperCase().replace(/[^A-Z0-9]/g, "_"));
|
|
102
|
+
// Environment in the middle
|
|
103
|
+
if (environment) {
|
|
104
|
+
const envMap = {
|
|
105
|
+
production: "PROD", prod: "PROD",
|
|
106
|
+
staging: "STAGING", stage: "STAGING",
|
|
107
|
+
development: "DEV", dev: "DEV",
|
|
108
|
+
test: "TEST", local: "LOCAL",
|
|
109
|
+
};
|
|
110
|
+
parts.push(envMap[environment.toLowerCase()] ?? environment.toUpperCase());
|
|
111
|
+
}
|
|
112
|
+
// Purpose aliases
|
|
113
|
+
const purposeMap = {
|
|
114
|
+
password: "PASSWORD", pass: "PASSWORD", passwd: "PASSWORD",
|
|
115
|
+
key: "KEY", apikey: "KEY", "api-key": "KEY", "api_key": "KEY",
|
|
116
|
+
secret: "SECRET", "secret-key": "SECRET_KEY",
|
|
117
|
+
token: "TOKEN", "access-token": "ACCESS_TOKEN",
|
|
118
|
+
url: "URL", uri: "URL", endpoint: "URL", host: "HOST",
|
|
119
|
+
port: "PORT",
|
|
120
|
+
user: "USER", username: "USER",
|
|
121
|
+
name: "NAME",
|
|
122
|
+
connection: "CONNECTION_STRING", "connection-string": "CONNECTION_STRING",
|
|
123
|
+
region: "REGION",
|
|
124
|
+
bucket: "BUCKET",
|
|
125
|
+
};
|
|
126
|
+
parts.push(purposeMap[purpose.toLowerCase()] ?? purpose.toUpperCase().replace(/[^A-Z0-9]/g, "_"));
|
|
127
|
+
return parts.join("_");
|
|
128
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
export interface FileLocation {
|
|
10
|
+
path: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Known file locations by category.
|
|
15
|
+
* These are the standard/common paths on Linux systems.
|
|
16
|
+
*/
|
|
17
|
+
export declare const KNOWN_LOCATIONS: Record<string, FileLocation[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Find files for a given query (service name, file type, or filename).
|
|
20
|
+
* Reads from config/file-hints.json.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findKnownLocations(query: string): FileLocation[];
|
|
23
|
+
/**
|
|
24
|
+
* Get the recommended parser type for a service.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getParserForService(service: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Search for a file on a remote server.
|
|
29
|
+
*/
|
|
30
|
+
export declare function searchRemoteFile(query: string, environment: string, execution?: "remote" | "local"): Promise<string[]>;
|