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,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized path resolution.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for all directory paths used by mycli.
|
|
5
|
+
* Supports three modes:
|
|
6
|
+
* 1. Development (tsx): src/utils/paths.ts → resolve("../..")
|
|
7
|
+
* 2. npm package: dist/utils/paths.js → resolve("../..")
|
|
8
|
+
* 3. SEA binary: embedded assets, writable dirs in ~/.mycli/
|
|
9
|
+
*
|
|
10
|
+
* Writable directories (data, logs) always go to ~/.mycli/ so they
|
|
11
|
+
* work in all modes. Config is read-only and ships with the package.
|
|
12
|
+
*/
|
|
13
|
+
import { resolve, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
/** Whether running as a Node.js Single Executable Application */
|
|
20
|
+
export function isSEA() {
|
|
21
|
+
try {
|
|
22
|
+
// node:sea module only exists when running as a SEA binary
|
|
23
|
+
// Use globalThis to check for the injected fuse
|
|
24
|
+
return !!globalThis.__sea_resources__;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Package root: two levels up from dist/utils/ or src/utils/ */
|
|
31
|
+
export const PACKAGE_ROOT = resolve(__dirname, "../..");
|
|
32
|
+
/** Read-only config directory (ships with the package) */
|
|
33
|
+
export const CONFIG_DIR = resolve(PACKAGE_ROOT, "config");
|
|
34
|
+
/** User data root — writable, lives in home directory */
|
|
35
|
+
export const USER_HOME = resolve(process.env.MYCLI_DATA_DIR ?? resolve(homedir(), ".mycli"));
|
|
36
|
+
/** Writable data directory (history, sessions) */
|
|
37
|
+
export const DATA_DIR = resolve(USER_HOME, "data");
|
|
38
|
+
/** Writable logs directory (failures, uncertainty) */
|
|
39
|
+
export const LOG_DIR = resolve(USER_HOME, "logs");
|
|
40
|
+
/** Ensure writable directories exist */
|
|
41
|
+
export function ensureUserDirs() {
|
|
42
|
+
for (const dir of [USER_HOME, DATA_DIR, LOG_DIR]) {
|
|
43
|
+
if (!existsSync(dir)) {
|
|
44
|
+
mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File permissions module.
|
|
3
|
+
*
|
|
4
|
+
* Understands Unix permission model:
|
|
5
|
+
* - Octal (755, 644, 600)
|
|
6
|
+
* - Symbolic (rwxr-xr-x, u+x, g-w)
|
|
7
|
+
* - Ownership (user:group)
|
|
8
|
+
* - Special bits (setuid, setgid, sticky)
|
|
9
|
+
* - Access checks (can I read/write/execute this?)
|
|
10
|
+
*
|
|
11
|
+
* Works locally via stat, remotely via SSH.
|
|
12
|
+
*/
|
|
13
|
+
export interface FilePermissions {
|
|
14
|
+
path: string;
|
|
15
|
+
exists: boolean;
|
|
16
|
+
mode: string;
|
|
17
|
+
octal: string;
|
|
18
|
+
owner: string;
|
|
19
|
+
group: string;
|
|
20
|
+
size: number;
|
|
21
|
+
type: "file" | "directory" | "symlink" | "other";
|
|
22
|
+
readable: boolean;
|
|
23
|
+
writable: boolean;
|
|
24
|
+
executable: boolean;
|
|
25
|
+
setuid: boolean;
|
|
26
|
+
setgid: boolean;
|
|
27
|
+
sticky: boolean;
|
|
28
|
+
humanReadable: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Get permissions for a local file.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getLocalPermissions(filePath: string): FilePermissions;
|
|
34
|
+
/**
|
|
35
|
+
* Get permissions for a remote file via SSH.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getRemotePermissions(filePath: string, environment: string): Promise<FilePermissions>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if we have the needed access before an operation.
|
|
40
|
+
* Returns an error message if access is denied, or null if OK.
|
|
41
|
+
*/
|
|
42
|
+
export declare function checkAccessForIntent(intent: string, perms: FilePermissions): string | null;
|
|
43
|
+
/**
|
|
44
|
+
* Parse a permission change request from natural language.
|
|
45
|
+
*
|
|
46
|
+
* Understands:
|
|
47
|
+
* "make readable" → chmod a+r
|
|
48
|
+
* "make executable" → chmod +x
|
|
49
|
+
* "set 755" → chmod 755
|
|
50
|
+
* "set read only" → chmod 444
|
|
51
|
+
* "owner only" → chmod 700
|
|
52
|
+
* "give group write" → chmod g+w
|
|
53
|
+
* "remove others execute" → chmod o-x
|
|
54
|
+
* "secure" → chmod 600
|
|
55
|
+
* "world readable" → chmod 644
|
|
56
|
+
*/
|
|
57
|
+
export declare function parsePermissionRequest(text: string): {
|
|
58
|
+
mode: string;
|
|
59
|
+
explanation: string;
|
|
60
|
+
} | null;
|
|
61
|
+
/**
|
|
62
|
+
* Format full permissions display.
|
|
63
|
+
*/
|
|
64
|
+
export declare function formatPermissionsDisplay(perms: FilePermissions): string;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File permissions module.
|
|
3
|
+
*
|
|
4
|
+
* Understands Unix permission model:
|
|
5
|
+
* - Octal (755, 644, 600)
|
|
6
|
+
* - Symbolic (rwxr-xr-x, u+x, g-w)
|
|
7
|
+
* - Ownership (user:group)
|
|
8
|
+
* - Special bits (setuid, setgid, sticky)
|
|
9
|
+
* - Access checks (can I read/write/execute this?)
|
|
10
|
+
*
|
|
11
|
+
* Works locally via stat, remotely via SSH.
|
|
12
|
+
*/
|
|
13
|
+
import { statSync, accessSync, constants } from "node:fs";
|
|
14
|
+
import { runRemoteCommand } from "../execution/ssh.js";
|
|
15
|
+
/**
|
|
16
|
+
* Get permissions for a local file.
|
|
17
|
+
*/
|
|
18
|
+
export function getLocalPermissions(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const stat = statSync(filePath);
|
|
21
|
+
const mode = stat.mode;
|
|
22
|
+
const octal = (mode & 0o7777).toString(8).padStart(4, "0");
|
|
23
|
+
const symbolic = modeToSymbolic(mode);
|
|
24
|
+
let readable = false;
|
|
25
|
+
let writable = false;
|
|
26
|
+
let executable = false;
|
|
27
|
+
try {
|
|
28
|
+
accessSync(filePath, constants.R_OK);
|
|
29
|
+
readable = true;
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
try {
|
|
33
|
+
accessSync(filePath, constants.W_OK);
|
|
34
|
+
writable = true;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
try {
|
|
38
|
+
accessSync(filePath, constants.X_OK);
|
|
39
|
+
executable = true;
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
return {
|
|
43
|
+
path: filePath,
|
|
44
|
+
exists: true,
|
|
45
|
+
mode: symbolic,
|
|
46
|
+
octal,
|
|
47
|
+
owner: String(stat.uid),
|
|
48
|
+
group: String(stat.gid),
|
|
49
|
+
size: stat.size,
|
|
50
|
+
type: stat.isDirectory() ? "directory" : stat.isSymbolicLink() ? "symlink" : stat.isFile() ? "file" : "other",
|
|
51
|
+
readable,
|
|
52
|
+
writable,
|
|
53
|
+
executable,
|
|
54
|
+
setuid: !!(mode & 0o4000),
|
|
55
|
+
setgid: !!(mode & 0o2000),
|
|
56
|
+
sticky: !!(mode & 0o1000),
|
|
57
|
+
humanReadable: formatPermissions({ octal, mode: symbolic, owner: String(stat.uid), group: String(stat.gid), readable, writable, executable }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return {
|
|
62
|
+
path: filePath,
|
|
63
|
+
exists: false,
|
|
64
|
+
mode: "",
|
|
65
|
+
octal: "",
|
|
66
|
+
owner: "",
|
|
67
|
+
group: "",
|
|
68
|
+
size: 0,
|
|
69
|
+
type: "other",
|
|
70
|
+
readable: false,
|
|
71
|
+
writable: false,
|
|
72
|
+
executable: false,
|
|
73
|
+
setuid: false,
|
|
74
|
+
setgid: false,
|
|
75
|
+
sticky: false,
|
|
76
|
+
humanReadable: "File not found",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get permissions for a remote file via SSH.
|
|
82
|
+
*/
|
|
83
|
+
export async function getRemotePermissions(filePath, environment) {
|
|
84
|
+
try {
|
|
85
|
+
const result = await runRemoteCommand(environment, `stat -c '%A %a %U %G %s %F' ${filePath} 2>/dev/null || echo 'NOT_FOUND'`);
|
|
86
|
+
if (result.trim() === "NOT_FOUND") {
|
|
87
|
+
return {
|
|
88
|
+
path: filePath, exists: false, mode: "", octal: "", owner: "", group: "",
|
|
89
|
+
size: 0, type: "other", readable: false, writable: false, executable: false,
|
|
90
|
+
setuid: false, setgid: false, sticky: false, humanReadable: "File not found on remote",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const parts = result.trim().split(" ");
|
|
94
|
+
const symbolic = parts[0] ?? "";
|
|
95
|
+
const octal = parts[1] ?? "";
|
|
96
|
+
const owner = parts[2] ?? "";
|
|
97
|
+
const group = parts[3] ?? "";
|
|
98
|
+
const size = Number(parts[4]) || 0;
|
|
99
|
+
const fileType = parts.slice(5).join(" ").toLowerCase();
|
|
100
|
+
// Check access
|
|
101
|
+
const accessCheck = await runRemoteCommand(environment, `test -r ${filePath} && echo R || echo -; test -w ${filePath} && echo W || echo -; test -x ${filePath} && echo X || echo -`);
|
|
102
|
+
const accessParts = accessCheck.trim().split("\n");
|
|
103
|
+
const readable = accessParts[0] === "R";
|
|
104
|
+
const writable = accessParts[1] === "W";
|
|
105
|
+
const executable = accessParts[2] === "X";
|
|
106
|
+
const type = fileType.includes("directory") ? "directory" :
|
|
107
|
+
fileType.includes("symbolic") ? "symlink" :
|
|
108
|
+
fileType.includes("regular") ? "file" : "other";
|
|
109
|
+
return {
|
|
110
|
+
path: filePath,
|
|
111
|
+
exists: true,
|
|
112
|
+
mode: symbolic,
|
|
113
|
+
octal,
|
|
114
|
+
owner,
|
|
115
|
+
group,
|
|
116
|
+
size,
|
|
117
|
+
type,
|
|
118
|
+
readable,
|
|
119
|
+
writable,
|
|
120
|
+
executable,
|
|
121
|
+
setuid: symbolic.includes("s") && symbolic[3] === "s",
|
|
122
|
+
setgid: symbolic.includes("s") && symbolic[6] === "s",
|
|
123
|
+
sticky: symbolic.includes("t"),
|
|
124
|
+
humanReadable: formatPermissions({ octal, mode: symbolic, owner, group, readable, writable, executable }),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
return {
|
|
129
|
+
path: filePath, exists: false, mode: "", octal: "", owner: "", group: "",
|
|
130
|
+
size: 0, type: "other", readable: false, writable: false, executable: false,
|
|
131
|
+
setuid: false, setgid: false, sticky: false,
|
|
132
|
+
humanReadable: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if we have the needed access before an operation.
|
|
138
|
+
* Returns an error message if access is denied, or null if OK.
|
|
139
|
+
*/
|
|
140
|
+
export function checkAccessForIntent(intent, perms) {
|
|
141
|
+
if (!perms.exists)
|
|
142
|
+
return `File not found: ${perms.path}`;
|
|
143
|
+
switch (intent) {
|
|
144
|
+
case "file.parse":
|
|
145
|
+
case "env.get":
|
|
146
|
+
case "logs.tail":
|
|
147
|
+
case "logs.search":
|
|
148
|
+
case "archive.list":
|
|
149
|
+
if (!perms.readable)
|
|
150
|
+
return `Permission denied: cannot read ${perms.path} (owner: ${perms.owner}, mode: ${perms.octal})`;
|
|
151
|
+
break;
|
|
152
|
+
case "files.copy":
|
|
153
|
+
case "files.move":
|
|
154
|
+
case "files.remove":
|
|
155
|
+
case "env.set":
|
|
156
|
+
if (!perms.writable)
|
|
157
|
+
return `Permission denied: cannot write ${perms.path} (owner: ${perms.owner}, mode: ${perms.octal}). Try with sudo?`;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Parse a permission change request from natural language.
|
|
164
|
+
*
|
|
165
|
+
* Understands:
|
|
166
|
+
* "make readable" → chmod a+r
|
|
167
|
+
* "make executable" → chmod +x
|
|
168
|
+
* "set 755" → chmod 755
|
|
169
|
+
* "set read only" → chmod 444
|
|
170
|
+
* "owner only" → chmod 700
|
|
171
|
+
* "give group write" → chmod g+w
|
|
172
|
+
* "remove others execute" → chmod o-x
|
|
173
|
+
* "secure" → chmod 600
|
|
174
|
+
* "world readable" → chmod 644
|
|
175
|
+
*/
|
|
176
|
+
export function parsePermissionRequest(text) {
|
|
177
|
+
const lower = text.toLowerCase();
|
|
178
|
+
// Octal directly specified
|
|
179
|
+
const octalMatch = lower.match(/\b([0-7]{3,4})\b/);
|
|
180
|
+
if (octalMatch) {
|
|
181
|
+
return { mode: octalMatch[1], explanation: `Set permissions to ${octalMatch[1]} (${octalToSymbolic(octalMatch[1])})` };
|
|
182
|
+
}
|
|
183
|
+
// Named presets
|
|
184
|
+
const presets = {
|
|
185
|
+
"executable": { mode: "+x", explanation: "Add execute permission for all" },
|
|
186
|
+
"make executable": { mode: "+x", explanation: "Add execute permission for all" },
|
|
187
|
+
"read only": { mode: "444", explanation: "Read-only for everyone" },
|
|
188
|
+
"readonly": { mode: "444", explanation: "Read-only for everyone" },
|
|
189
|
+
"secure": { mode: "600", explanation: "Read/write for owner only (secure)" },
|
|
190
|
+
"private": { mode: "600", explanation: "Read/write for owner only" },
|
|
191
|
+
"owner only": { mode: "700", explanation: "Full access for owner only" },
|
|
192
|
+
"world readable": { mode: "644", explanation: "Owner read/write, others read" },
|
|
193
|
+
"world writable": { mode: "666", explanation: "Read/write for everyone (dangerous!)" },
|
|
194
|
+
"script": { mode: "755", explanation: "Owner full, others read/execute (typical for scripts)" },
|
|
195
|
+
"config": { mode: "644", explanation: "Owner read/write, others read (typical for configs)" },
|
|
196
|
+
"secret": { mode: "600", explanation: "Owner read/write only (for secrets, keys)" },
|
|
197
|
+
"key": { mode: "600", explanation: "Owner read/write only (for SSH keys)" },
|
|
198
|
+
"web": { mode: "755", explanation: "Owner full, web-server readable" },
|
|
199
|
+
"www-data": { mode: "755", explanation: "Owner full, web-server readable" },
|
|
200
|
+
"no access": { mode: "000", explanation: "No access for anyone" },
|
|
201
|
+
"full access": { mode: "777", explanation: "Full access for everyone (dangerous!)" },
|
|
202
|
+
};
|
|
203
|
+
for (const [key, value] of Object.entries(presets)) {
|
|
204
|
+
if (lower.includes(key))
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
// Symbolic patterns
|
|
208
|
+
if (lower.includes("give") || lower.includes("add")) {
|
|
209
|
+
if (lower.includes("read"))
|
|
210
|
+
return { mode: "+r", explanation: "Add read permission" };
|
|
211
|
+
if (lower.includes("write"))
|
|
212
|
+
return { mode: "+w", explanation: "Add write permission" };
|
|
213
|
+
if (lower.includes("execute") || lower.includes("exec"))
|
|
214
|
+
return { mode: "+x", explanation: "Add execute permission" };
|
|
215
|
+
}
|
|
216
|
+
if (lower.includes("remove") || lower.includes("take away") || lower.includes("deny")) {
|
|
217
|
+
if (lower.includes("read"))
|
|
218
|
+
return { mode: "-r", explanation: "Remove read permission" };
|
|
219
|
+
if (lower.includes("write"))
|
|
220
|
+
return { mode: "-w", explanation: "Remove write permission" };
|
|
221
|
+
if (lower.includes("execute") || lower.includes("exec"))
|
|
222
|
+
return { mode: "-x", explanation: "Remove execute permission" };
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
227
|
+
function modeToSymbolic(mode) {
|
|
228
|
+
const types = { 0o140000: "s", 0o120000: "l", 0o100000: "-", 0o040000: "d", 0o060000: "b", 0o020000: "c", 0o010000: "p" };
|
|
229
|
+
const fileType = Object.entries(types).find(([mask]) => (mode & Number(mask)) === Number(mask));
|
|
230
|
+
const prefix = fileType?.[1] ?? "?";
|
|
231
|
+
const perms = (m, shift, special, specialChar) => {
|
|
232
|
+
const r = (m >> (shift + 2)) & 1 ? "r" : "-";
|
|
233
|
+
const w = (m >> (shift + 1)) & 1 ? "w" : "-";
|
|
234
|
+
const x = (m >> shift) & 1;
|
|
235
|
+
const s = (m & special) !== 0;
|
|
236
|
+
const xChar = s ? (x ? specialChar : specialChar.toUpperCase()) : (x ? "x" : "-");
|
|
237
|
+
return r + w + xChar;
|
|
238
|
+
};
|
|
239
|
+
return prefix +
|
|
240
|
+
perms(mode, 6, 0o4000, "s") +
|
|
241
|
+
perms(mode, 3, 0o2000, "s") +
|
|
242
|
+
perms(mode, 0, 0o1000, "t");
|
|
243
|
+
}
|
|
244
|
+
function octalToSymbolic(octal) {
|
|
245
|
+
const num = parseInt(octal, 8);
|
|
246
|
+
return modeToSymbolic(0o100000 | num).slice(1); // strip file type prefix
|
|
247
|
+
}
|
|
248
|
+
function formatPermissions(info) {
|
|
249
|
+
const access = [];
|
|
250
|
+
if (info.readable)
|
|
251
|
+
access.push("read");
|
|
252
|
+
if (info.writable)
|
|
253
|
+
access.push("write");
|
|
254
|
+
if (info.executable)
|
|
255
|
+
access.push("execute");
|
|
256
|
+
return `${info.mode} (${info.octal}) owner=${info.owner} group=${info.group} | You can: ${access.join(", ") || "nothing"}`;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Format full permissions display.
|
|
260
|
+
*/
|
|
261
|
+
export function formatPermissionsDisplay(perms) {
|
|
262
|
+
const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m" };
|
|
263
|
+
if (!perms.exists)
|
|
264
|
+
return `${c.red}Not found: ${perms.path}${c.reset}`;
|
|
265
|
+
const access = [
|
|
266
|
+
perms.readable ? `${c.green}read${c.reset}` : `${c.red}no read${c.reset}`,
|
|
267
|
+
perms.writable ? `${c.green}write${c.reset}` : `${c.red}no write${c.reset}`,
|
|
268
|
+
perms.executable ? `${c.green}execute${c.reset}` : `${c.dim}no execute${c.reset}`,
|
|
269
|
+
].join(", ");
|
|
270
|
+
const special = [];
|
|
271
|
+
if (perms.setuid)
|
|
272
|
+
special.push(`${c.yellow}SETUID${c.reset}`);
|
|
273
|
+
if (perms.setgid)
|
|
274
|
+
special.push(`${c.yellow}SETGID${c.reset}`);
|
|
275
|
+
if (perms.sticky)
|
|
276
|
+
special.push(`${c.yellow}STICKY${c.reset}`);
|
|
277
|
+
const lines = [
|
|
278
|
+
`${c.bold}${perms.path}${c.reset}`,
|
|
279
|
+
` Type: ${perms.type}`,
|
|
280
|
+
` Permissions: ${perms.mode} (${perms.octal})`,
|
|
281
|
+
` Owner: ${perms.owner}:${perms.group}`,
|
|
282
|
+
` Size: ${formatSize(perms.size)}`,
|
|
283
|
+
` Your access: ${access}`,
|
|
284
|
+
];
|
|
285
|
+
if (special.length > 0) {
|
|
286
|
+
lines.push(` Special: ${special.join(", ")}`);
|
|
287
|
+
}
|
|
288
|
+
return lines.join("\n");
|
|
289
|
+
}
|
|
290
|
+
function formatSize(bytes) {
|
|
291
|
+
if (bytes < 1024)
|
|
292
|
+
return `${bytes} B`;
|
|
293
|
+
if (bytes < 1048576)
|
|
294
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
295
|
+
if (bytes < 1073741824)
|
|
296
|
+
return `${(bytes / 1048576).toFixed(1)} MB`;
|
|
297
|
+
return `${(bytes / 1073741824).toFixed(1)} GB`;
|
|
298
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OS/distro/platform detection.
|
|
3
|
+
*
|
|
4
|
+
* Detects:
|
|
5
|
+
* - Linux distro (Ubuntu, Debian, CentOS, RHEL, Fedora, Arch, Alpine, Amazon Linux)
|
|
6
|
+
* - macOS
|
|
7
|
+
* - Windows (PowerShell, cmd, WSL)
|
|
8
|
+
* - Package manager (apt, dnf, yum, pacman, apk, brew, choco)
|
|
9
|
+
* - Init system (systemd, sysvinit, openrc)
|
|
10
|
+
* - Shell (bash, zsh, fish, powershell, cmd)
|
|
11
|
+
*
|
|
12
|
+
* Works locally and remotely via SSH.
|
|
13
|
+
*/
|
|
14
|
+
export interface PlatformInfo {
|
|
15
|
+
os: "linux" | "darwin" | "windows" | "unknown";
|
|
16
|
+
distro: string;
|
|
17
|
+
distroVersion: string;
|
|
18
|
+
distroFamily: "debian" | "rhel" | "arch" | "alpine" | "macos" | "windows" | "unknown";
|
|
19
|
+
kernel: string;
|
|
20
|
+
isWSL: boolean;
|
|
21
|
+
shell: string;
|
|
22
|
+
packageManager: "apt" | "dnf" | "yum" | "pacman" | "apk" | "brew" | "choco" | "unknown";
|
|
23
|
+
initSystem: "systemd" | "sysvinit" | "openrc" | "launchd" | "unknown";
|
|
24
|
+
arch: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Detect the local platform.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectLocalPlatform(): PlatformInfo;
|
|
30
|
+
/**
|
|
31
|
+
* Detect platform on a remote host via SSH.
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectRemotePlatform(environment: string): Promise<PlatformInfo>;
|
|
34
|
+
/**
|
|
35
|
+
* Get the correct install command for the detected platform.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getInstallCommand(pkg: string, platform: PlatformInfo): string;
|
|
38
|
+
/**
|
|
39
|
+
* Get the correct service management command.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getServiceCommand(action: "start" | "stop" | "restart" | "status", service: string, platform: PlatformInfo): string;
|
|
42
|
+
/**
|
|
43
|
+
* Map common command names to package names per distro family.
|
|
44
|
+
*/
|
|
45
|
+
export declare const COMMAND_TO_PACKAGE: Record<string, Record<string, string>>;
|
|
46
|
+
/**
|
|
47
|
+
* Get the package name for a command on the detected platform.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getPackageForCommand(command: string, platform: PlatformInfo): string | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Format platform info for display.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatPlatform(info: PlatformInfo): string;
|