notoken-core 1.5.1 → 2.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/chat-responses.json +767 -0
- package/config/concept-clusters.json +31 -0
- package/config/entities.json +93 -0
- package/config/image-prompts.json +20 -0
- package/config/intent-vectors.json +1 -0
- package/config/intents.json +5023 -65
- package/config/ollama-models.json +193 -0
- package/config/rules.json +32 -1
- package/dist/automation/discordPatchright.d.ts +35 -0
- package/dist/automation/discordPatchright.js +424 -0
- package/dist/automation/discordSetup.d.ts +31 -0
- package/dist/automation/discordSetup.js +338 -0
- package/dist/conversation/coreference.js +44 -4
- package/dist/conversation/pendingActions.d.ts +55 -0
- package/dist/conversation/pendingActions.js +127 -0
- package/dist/conversation/store.d.ts +72 -0
- package/dist/conversation/store.js +140 -1
- package/dist/conversation/topicTracker.d.ts +36 -0
- package/dist/conversation/topicTracker.js +141 -0
- package/dist/execution/ssh.d.ts +42 -1
- package/dist/execution/ssh.js +532 -3
- package/dist/handlers/executor.js +3981 -16
- package/dist/index.d.ts +25 -3
- package/dist/index.js +36 -2
- package/dist/nlp/batchParser.d.ts +30 -0
- package/dist/nlp/batchParser.js +77 -0
- package/dist/nlp/conceptExpansion.d.ts +54 -0
- package/dist/nlp/conceptExpansion.js +136 -0
- package/dist/nlp/conceptRouter.d.ts +49 -0
- package/dist/nlp/conceptRouter.js +302 -0
- package/dist/nlp/confidenceCalibrator.d.ts +62 -0
- package/dist/nlp/confidenceCalibrator.js +116 -0
- package/dist/nlp/correctionLearner.d.ts +45 -0
- package/dist/nlp/correctionLearner.js +207 -0
- package/dist/nlp/entitySpellCorrect.d.ts +35 -0
- package/dist/nlp/entitySpellCorrect.js +141 -0
- package/dist/nlp/knowledgeGraph.d.ts +70 -0
- package/dist/nlp/knowledgeGraph.js +380 -0
- package/dist/nlp/llmFallback.js +28 -1
- package/dist/nlp/multiClassifier.js +91 -6
- package/dist/nlp/multiIntent.d.ts +43 -0
- package/dist/nlp/multiIntent.js +154 -0
- package/dist/nlp/parseIntent.d.ts +6 -1
- package/dist/nlp/parseIntent.js +180 -5
- package/dist/nlp/ruleParser.js +315 -0
- package/dist/nlp/semanticSimilarity.d.ts +30 -0
- package/dist/nlp/semanticSimilarity.js +174 -0
- package/dist/nlp/vocabularyBuilder.d.ts +43 -0
- package/dist/nlp/vocabularyBuilder.js +224 -0
- package/dist/nlp/wikidata.d.ts +49 -0
- package/dist/nlp/wikidata.js +228 -0
- package/dist/policy/confirm.d.ts +10 -0
- package/dist/policy/confirm.js +39 -0
- package/dist/policy/safety.js +6 -4
- package/dist/utils/aliases.d.ts +5 -0
- package/dist/utils/aliases.js +39 -0
- package/dist/utils/analysis.js +71 -15
- package/dist/utils/browser.d.ts +64 -0
- package/dist/utils/browser.js +364 -0
- package/dist/utils/commandHistory.d.ts +20 -0
- package/dist/utils/commandHistory.js +108 -0
- package/dist/utils/completer.d.ts +17 -0
- package/dist/utils/completer.js +79 -0
- package/dist/utils/config.js +32 -2
- package/dist/utils/dbQuery.d.ts +25 -0
- package/dist/utils/dbQuery.js +248 -0
- package/dist/utils/discordDiag.d.ts +35 -0
- package/dist/utils/discordDiag.js +826 -0
- package/dist/utils/diskCleanup.d.ts +36 -0
- package/dist/utils/diskCleanup.js +775 -0
- package/dist/utils/entityResolver.d.ts +107 -0
- package/dist/utils/entityResolver.js +468 -0
- package/dist/utils/imageGen.d.ts +92 -0
- package/dist/utils/imageGen.js +2031 -0
- package/dist/utils/installTracker.d.ts +57 -0
- package/dist/utils/installTracker.js +160 -0
- package/dist/utils/multiExec.d.ts +21 -0
- package/dist/utils/multiExec.js +141 -0
- package/dist/utils/openclawDiag.d.ts +29 -0
- package/dist/utils/openclawDiag.js +1035 -0
- package/dist/utils/output.js +4 -0
- package/dist/utils/platform.js +2 -1
- package/dist/utils/progressReporter.d.ts +50 -0
- package/dist/utils/progressReporter.js +58 -0
- package/dist/utils/projectDetect.d.ts +44 -0
- package/dist/utils/projectDetect.js +319 -0
- package/dist/utils/projectScanner.d.ts +44 -0
- package/dist/utils/projectScanner.js +312 -0
- package/dist/utils/shellCompat.d.ts +78 -0
- package/dist/utils/shellCompat.js +186 -0
- package/dist/utils/smartArchive.d.ts +16 -0
- package/dist/utils/smartArchive.js +172 -0
- package/dist/utils/smartRetry.d.ts +26 -0
- package/dist/utils/smartRetry.js +114 -0
- package/dist/utils/updater.d.ts +1 -0
- package/dist/utils/updater.js +1 -1
- package/dist/utils/version.d.ts +20 -0
- package/dist/utils/version.js +212 -0
- package/package.json +6 -3
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project scanner.
|
|
3
|
+
*
|
|
4
|
+
* Scans a directory tree to find and describe software projects.
|
|
5
|
+
* Also lists directory contents with rich output.
|
|
6
|
+
*
|
|
7
|
+
* Used by:
|
|
8
|
+
* "what projects do I have here?"
|
|
9
|
+
* "where are my files?"
|
|
10
|
+
* "what's in this folder?"
|
|
11
|
+
*/
|
|
12
|
+
import { readdirSync, statSync, existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { resolve, basename, relative, extname } from "node:path";
|
|
14
|
+
const c = {
|
|
15
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
16
|
+
green: "\x1b[32m", yellow: "\x1b[33m", red: "\x1b[31m",
|
|
17
|
+
cyan: "\x1b[36m", magenta: "\x1b[35m", blue: "\x1b[34m",
|
|
18
|
+
};
|
|
19
|
+
const SIGNATURES = [
|
|
20
|
+
{
|
|
21
|
+
type: "Next.js", marker: "next.config",
|
|
22
|
+
parseInfo: (dir) => parsePackageJson(dir, "Next.js"),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "Nuxt.js", marker: "nuxt.config",
|
|
26
|
+
parseInfo: (dir) => parsePackageJson(dir, "Nuxt.js"),
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: "SvelteKit", marker: "svelte.config.js",
|
|
30
|
+
parseInfo: (dir) => parsePackageJson(dir, "SvelteKit"),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: "React", marker: "src/App.tsx",
|
|
34
|
+
parseInfo: (dir) => parsePackageJson(dir, "React"),
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: "Electron", marker: "electron-builder",
|
|
38
|
+
parseInfo: (dir) => parsePackageJson(dir, "Electron"),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "Node.js", marker: "package.json",
|
|
42
|
+
parseInfo: (dir) => parsePackageJson(dir, "Node.js"),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "Python", marker: "requirements.txt",
|
|
46
|
+
parseInfo: (dir) => {
|
|
47
|
+
try {
|
|
48
|
+
const reqs = readFileSync(resolve(dir, "requirements.txt"), "utf-8");
|
|
49
|
+
return { deps: reqs.split("\n").filter(l => l.trim() && !l.startsWith("#")).length };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{ type: "Python", marker: "pyproject.toml" },
|
|
57
|
+
{ type: "Django", marker: "manage.py" },
|
|
58
|
+
{ type: "Go", marker: "go.mod" },
|
|
59
|
+
{ type: "Rust", marker: "Cargo.toml" },
|
|
60
|
+
{ type: "Java/Maven", marker: "pom.xml" },
|
|
61
|
+
{ type: "Java/Gradle", marker: "build.gradle" },
|
|
62
|
+
{ type: "PHP", marker: "composer.json" },
|
|
63
|
+
{ type: "Laravel", marker: "artisan" },
|
|
64
|
+
{ type: "WordPress", marker: "wp-config.php" },
|
|
65
|
+
{ type: "Ruby", marker: "Gemfile" },
|
|
66
|
+
{ type: ".NET", marker: "*.csproj" },
|
|
67
|
+
{ type: "Terraform", marker: "main.tf" },
|
|
68
|
+
{ type: "Docker", marker: "Dockerfile" },
|
|
69
|
+
];
|
|
70
|
+
function parsePackageJson(dir, defaultType) {
|
|
71
|
+
try {
|
|
72
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
|
|
73
|
+
const depCount = Object.keys(pkg.dependencies ?? {}).length + Object.keys(pkg.devDependencies ?? {}).length;
|
|
74
|
+
const scripts = Object.keys(pkg.scripts ?? {}).slice(0, 8);
|
|
75
|
+
// Detect more specific type from deps
|
|
76
|
+
let type = defaultType;
|
|
77
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
78
|
+
if (allDeps["next"])
|
|
79
|
+
type = "Next.js";
|
|
80
|
+
else if (allDeps["nuxt"])
|
|
81
|
+
type = "Nuxt.js";
|
|
82
|
+
else if (allDeps["svelte"])
|
|
83
|
+
type = "SvelteKit";
|
|
84
|
+
else if (allDeps["electron"])
|
|
85
|
+
type = "Electron";
|
|
86
|
+
else if (allDeps["react"])
|
|
87
|
+
type = "React";
|
|
88
|
+
else if (allDeps["vue"])
|
|
89
|
+
type = "Vue.js";
|
|
90
|
+
else if (allDeps["@angular/core"])
|
|
91
|
+
type = "Angular";
|
|
92
|
+
else if (allDeps["express"])
|
|
93
|
+
type = "Express";
|
|
94
|
+
else if (allDeps["fastify"])
|
|
95
|
+
type = "Fastify";
|
|
96
|
+
return {
|
|
97
|
+
name: pkg.name,
|
|
98
|
+
type,
|
|
99
|
+
description: pkg.description,
|
|
100
|
+
version: pkg.version,
|
|
101
|
+
deps: depCount,
|
|
102
|
+
scripts,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return {};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ─── Scanning ──────────────────────────────────────────────────────────────
|
|
110
|
+
const SKIP_DIRS = new Set([
|
|
111
|
+
"node_modules", ".git", ".next", ".nuxt", "__pycache__",
|
|
112
|
+
"venv", ".venv", "target", "dist", "build", ".cache",
|
|
113
|
+
"vendor", "obj", "bin", ".terraform", ".svelte-kit",
|
|
114
|
+
]);
|
|
115
|
+
/**
|
|
116
|
+
* Scan for projects in a directory (recursive, up to maxDepth).
|
|
117
|
+
*/
|
|
118
|
+
export function scanProjects(rootPath, maxDepth = 3) {
|
|
119
|
+
const root = resolve(rootPath);
|
|
120
|
+
if (!existsSync(root))
|
|
121
|
+
return [];
|
|
122
|
+
const projects = [];
|
|
123
|
+
const seen = new Set();
|
|
124
|
+
function walk(dir, depth) {
|
|
125
|
+
if (depth > maxDepth)
|
|
126
|
+
return;
|
|
127
|
+
let entries;
|
|
128
|
+
try {
|
|
129
|
+
entries = readdirSync(dir);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Check each signature against this directory
|
|
135
|
+
for (const sig of SIGNATURES) {
|
|
136
|
+
const markerFile = sig.marker;
|
|
137
|
+
let matched = false;
|
|
138
|
+
if (markerFile.startsWith("*")) {
|
|
139
|
+
const ext = markerFile.slice(1);
|
|
140
|
+
matched = entries.some(e => e.endsWith(ext));
|
|
141
|
+
}
|
|
142
|
+
else if (markerFile.includes("/")) {
|
|
143
|
+
matched = existsSync(resolve(dir, markerFile));
|
|
144
|
+
}
|
|
145
|
+
else if (markerFile === "electron-builder") {
|
|
146
|
+
// Special: check package.json for electron-builder
|
|
147
|
+
try {
|
|
148
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
|
|
149
|
+
matched = !!(pkg.devDependencies?.["electron-builder"] || pkg.build?.appId);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
matched = false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Check exact filename or prefix match (e.g. "next.config" matches next.config.js/ts/mjs)
|
|
157
|
+
if (markerFile.includes(".")) {
|
|
158
|
+
matched = entries.includes(markerFile);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
matched = entries.some(e => e.startsWith(markerFile + ".") || e === markerFile);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (matched && !seen.has(dir)) {
|
|
165
|
+
seen.add(dir);
|
|
166
|
+
const info = {
|
|
167
|
+
path: dir,
|
|
168
|
+
name: basename(dir),
|
|
169
|
+
type: sig.type,
|
|
170
|
+
indicators: [markerFile],
|
|
171
|
+
...sig.parseInfo?.(dir),
|
|
172
|
+
};
|
|
173
|
+
projects.push(info);
|
|
174
|
+
break; // One detection per directory
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Recurse into subdirectories
|
|
178
|
+
for (const entry of entries) {
|
|
179
|
+
if (SKIP_DIRS.has(entry) || entry.startsWith("."))
|
|
180
|
+
continue;
|
|
181
|
+
const full = resolve(dir, entry);
|
|
182
|
+
try {
|
|
183
|
+
if (statSync(full).isDirectory()) {
|
|
184
|
+
walk(full, depth + 1);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
walk(root, 0);
|
|
191
|
+
return projects;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Summarize a directory — files, projects, sizes.
|
|
195
|
+
*/
|
|
196
|
+
export function summarizeDirectory(dirPath) {
|
|
197
|
+
const root = resolve(dirPath);
|
|
198
|
+
let totalFiles = 0;
|
|
199
|
+
let totalDirs = 0;
|
|
200
|
+
let totalSize = 0;
|
|
201
|
+
const fileCounts = {};
|
|
202
|
+
const largestFiles = [];
|
|
203
|
+
const notable = [];
|
|
204
|
+
const NOTABLE = new Set([
|
|
205
|
+
"README.md", "LICENSE", "Dockerfile", "docker-compose.yml",
|
|
206
|
+
".env", "Makefile", ".gitignore", "package.json", "tsconfig.json",
|
|
207
|
+
"requirements.txt", "go.mod", "Cargo.toml",
|
|
208
|
+
]);
|
|
209
|
+
try {
|
|
210
|
+
const entries = readdirSync(root);
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
const full = resolve(root, entry);
|
|
213
|
+
try {
|
|
214
|
+
const stat = statSync(full);
|
|
215
|
+
if (stat.isDirectory()) {
|
|
216
|
+
totalDirs++;
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
totalFiles++;
|
|
220
|
+
totalSize += stat.size;
|
|
221
|
+
const ext = extname(entry).toLowerCase() || "(no ext)";
|
|
222
|
+
fileCounts[ext] = (fileCounts[ext] ?? 0) + 1;
|
|
223
|
+
largestFiles.push({ name: entry, size: stat.size });
|
|
224
|
+
}
|
|
225
|
+
if (NOTABLE.has(entry))
|
|
226
|
+
notable.push(entry);
|
|
227
|
+
}
|
|
228
|
+
catch { }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch { }
|
|
232
|
+
largestFiles.sort((a, b) => b.size - a.size);
|
|
233
|
+
const projects = scanProjects(root, 2);
|
|
234
|
+
return {
|
|
235
|
+
path: root,
|
|
236
|
+
totalFiles,
|
|
237
|
+
totalDirs,
|
|
238
|
+
projects,
|
|
239
|
+
fileCounts,
|
|
240
|
+
largestFiles: largestFiles.slice(0, 5),
|
|
241
|
+
notable,
|
|
242
|
+
totalSize,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// ─── Formatting ────────────────────────────────────────────────────────────
|
|
246
|
+
function formatSize(bytes) {
|
|
247
|
+
if (bytes < 1024)
|
|
248
|
+
return `${bytes}B`;
|
|
249
|
+
if (bytes < 1048576)
|
|
250
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
251
|
+
if (bytes < 1073741824)
|
|
252
|
+
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
253
|
+
return `${(bytes / 1073741824).toFixed(1)}GB`;
|
|
254
|
+
}
|
|
255
|
+
export function formatProjectList(projects, rootPath) {
|
|
256
|
+
if (projects.length === 0) {
|
|
257
|
+
return `${c.dim}No projects found in ${rootPath}${c.reset}`;
|
|
258
|
+
}
|
|
259
|
+
const lines = [];
|
|
260
|
+
const root = resolve(rootPath);
|
|
261
|
+
lines.push(`${c.bold}Projects in ${root}${c.reset}\n`);
|
|
262
|
+
for (const p of projects) {
|
|
263
|
+
const rel = relative(root, p.path) || ".";
|
|
264
|
+
const ver = p.version ? ` ${c.dim}v${p.version}${c.reset}` : "";
|
|
265
|
+
const deps = p.deps ? ` ${c.dim}(${p.deps} deps)${c.reset}` : "";
|
|
266
|
+
lines.push(` ${c.magenta}${p.type}${c.reset} ${c.bold}${p.name}${c.reset}${ver}${deps}`);
|
|
267
|
+
if (p.description)
|
|
268
|
+
lines.push(` ${c.dim}${p.description}${c.reset}`);
|
|
269
|
+
if (rel !== ".")
|
|
270
|
+
lines.push(` ${c.dim}${rel}/${c.reset}`);
|
|
271
|
+
if (p.scripts && p.scripts.length > 0) {
|
|
272
|
+
lines.push(` ${c.dim}scripts: ${p.scripts.join(", ")}${c.reset}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
lines.push(`\n ${c.dim}${projects.length} project(s) found${c.reset}`);
|
|
276
|
+
return lines.join("\n");
|
|
277
|
+
}
|
|
278
|
+
export function formatDirSummary(summary) {
|
|
279
|
+
const lines = [];
|
|
280
|
+
lines.push(`${c.bold}${summary.path}${c.reset}\n`);
|
|
281
|
+
lines.push(` ${c.cyan}${summary.totalFiles}${c.reset} file(s), ${c.cyan}${summary.totalDirs}${c.reset} director${summary.totalDirs === 1 ? "y" : "ies"} — ${formatSize(summary.totalSize)} total`);
|
|
282
|
+
// File type breakdown
|
|
283
|
+
const sorted = Object.entries(summary.fileCounts).sort((a, b) => b[1] - a[1]).slice(0, 8);
|
|
284
|
+
if (sorted.length > 0) {
|
|
285
|
+
lines.push(`\n ${c.bold}File types:${c.reset}`);
|
|
286
|
+
for (const [ext, count] of sorted) {
|
|
287
|
+
const bar = "█".repeat(Math.min(15, Math.round((count / summary.totalFiles) * 15)));
|
|
288
|
+
lines.push(` ${c.dim}${bar}${c.reset} ${ext}: ${count}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Largest files
|
|
292
|
+
if (summary.largestFiles.length > 0) {
|
|
293
|
+
lines.push(`\n ${c.bold}Largest files:${c.reset}`);
|
|
294
|
+
for (const f of summary.largestFiles) {
|
|
295
|
+
lines.push(` ${formatSize(f.size).padStart(8)} ${f.name}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Notable files
|
|
299
|
+
if (summary.notable.length > 0) {
|
|
300
|
+
lines.push(`\n ${c.bold}Notable:${c.reset} ${summary.notable.join(", ")}`);
|
|
301
|
+
}
|
|
302
|
+
// Projects
|
|
303
|
+
if (summary.projects.length > 0) {
|
|
304
|
+
lines.push(`\n ${c.bold}Projects detected:${c.reset}`);
|
|
305
|
+
for (const p of summary.projects) {
|
|
306
|
+
const rel = relative(summary.path, p.path) || ".";
|
|
307
|
+
const ver = p.version ? ` v${p.version}` : "";
|
|
308
|
+
lines.push(` ${c.magenta}${p.type}${c.reset} ${p.name}${c.dim}${ver}${c.reset}${rel !== "." ? ` ${c.dim}(${rel}/)${c.reset}` : ""}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return lines.join("\n");
|
|
312
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell compatibility layer.
|
|
3
|
+
*
|
|
4
|
+
* Abstracts platform differences so commands work on:
|
|
5
|
+
* - Linux (bash)
|
|
6
|
+
* - macOS (zsh/bash)
|
|
7
|
+
* - Windows (cmd.exe / PowerShell)
|
|
8
|
+
* - WSL (bash, but with /mnt/ Windows drives)
|
|
9
|
+
*
|
|
10
|
+
* Use these helpers instead of raw execSync with bash-isms.
|
|
11
|
+
*/
|
|
12
|
+
declare const isWin: boolean;
|
|
13
|
+
declare const isWSL: boolean;
|
|
14
|
+
export { isWin, isWSL };
|
|
15
|
+
/**
|
|
16
|
+
* Check if a command exists on this platform.
|
|
17
|
+
*/
|
|
18
|
+
export declare function commandExists(cmd: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Run a command and get output. Returns null on failure.
|
|
21
|
+
* Handles stderr redirection cross-platform.
|
|
22
|
+
*/
|
|
23
|
+
export declare function tryExec(cmd: string, timeout?: number): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* Get the temp directory.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getTempDir(): string;
|
|
28
|
+
/**
|
|
29
|
+
* Generate a timestamp string (replaces `$(date +%Y%m%d-%H%M%S)` in shell).
|
|
30
|
+
*/
|
|
31
|
+
export declare function timestamp(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Get file size in bytes. Returns 0 if file doesn't exist.
|
|
34
|
+
*/
|
|
35
|
+
export declare function fileSize(path: string): number;
|
|
36
|
+
/**
|
|
37
|
+
* Count lines in a file without shell commands.
|
|
38
|
+
*/
|
|
39
|
+
export declare function lineCount(path: string): number;
|
|
40
|
+
/**
|
|
41
|
+
* Run a command with proper shell for this platform.
|
|
42
|
+
*/
|
|
43
|
+
export declare function shellExec(cmd: string, opts?: {
|
|
44
|
+
cwd?: string;
|
|
45
|
+
timeout?: number;
|
|
46
|
+
stdio?: "inherit" | "pipe";
|
|
47
|
+
}): string;
|
|
48
|
+
/**
|
|
49
|
+
* Build a command that works on both Unix and Windows.
|
|
50
|
+
* Handles: stderr redirection, path separators, etc.
|
|
51
|
+
*/
|
|
52
|
+
export declare function crossPlatformCmd(unixCmd: string, windowsCmd?: string): string;
|
|
53
|
+
/**
|
|
54
|
+
* Redirect stderr to null, cross-platform.
|
|
55
|
+
* Unix: 2>/dev/null
|
|
56
|
+
* Windows: 2>NUL
|
|
57
|
+
*/
|
|
58
|
+
export declare function silenceStderr(cmd: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Get the home directory path.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getHome(): string;
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a path that works on this platform.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolvePath(...parts: string[]): string;
|
|
67
|
+
/**
|
|
68
|
+
* Check if running as root/admin.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isAdmin(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Get the package install command for this platform.
|
|
73
|
+
*/
|
|
74
|
+
export declare function getSystemInstallCmd(pkg: string): string;
|
|
75
|
+
/**
|
|
76
|
+
* Platform info for display.
|
|
77
|
+
*/
|
|
78
|
+
export declare function getPlatformSummary(): string;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell compatibility layer.
|
|
3
|
+
*
|
|
4
|
+
* Abstracts platform differences so commands work on:
|
|
5
|
+
* - Linux (bash)
|
|
6
|
+
* - macOS (zsh/bash)
|
|
7
|
+
* - Windows (cmd.exe / PowerShell)
|
|
8
|
+
* - WSL (bash, but with /mnt/ Windows drives)
|
|
9
|
+
*
|
|
10
|
+
* Use these helpers instead of raw execSync with bash-isms.
|
|
11
|
+
*/
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
import { platform as osPlatform, tmpdir, homedir } from "node:os";
|
|
14
|
+
import { statSync, readFileSync } from "node:fs";
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
const isWin = osPlatform() === "win32";
|
|
17
|
+
const isWSL = (() => {
|
|
18
|
+
try {
|
|
19
|
+
return !!execSync("grep -qi microsoft /proc/version && echo wsl", {
|
|
20
|
+
encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 2000,
|
|
21
|
+
}).trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
})();
|
|
27
|
+
export { isWin, isWSL };
|
|
28
|
+
/**
|
|
29
|
+
* Check if a command exists on this platform.
|
|
30
|
+
*/
|
|
31
|
+
export function commandExists(cmd) {
|
|
32
|
+
try {
|
|
33
|
+
if (isWin) {
|
|
34
|
+
execSync(`where ${cmd}`, { stdio: "pipe", timeout: 5000 });
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
execSync(`command -v ${cmd}`, { stdio: "pipe", timeout: 5000 });
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Run a command and get output. Returns null on failure.
|
|
47
|
+
* Handles stderr redirection cross-platform.
|
|
48
|
+
*/
|
|
49
|
+
export function tryExec(cmd, timeout = 5000) {
|
|
50
|
+
try {
|
|
51
|
+
const result = execSync(cmd, {
|
|
52
|
+
encoding: "utf-8",
|
|
53
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
54
|
+
timeout,
|
|
55
|
+
...(isWin ? { shell: "cmd.exe" } : {}),
|
|
56
|
+
});
|
|
57
|
+
return result.trim() || null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get the temp directory.
|
|
65
|
+
*/
|
|
66
|
+
export function getTempDir() {
|
|
67
|
+
return tmpdir();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Generate a timestamp string (replaces `$(date +%Y%m%d-%H%M%S)` in shell).
|
|
71
|
+
*/
|
|
72
|
+
export function timestamp() {
|
|
73
|
+
const d = new Date();
|
|
74
|
+
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, "0")}${String(d.getDate()).padStart(2, "0")}-${String(d.getHours()).padStart(2, "0")}${String(d.getMinutes()).padStart(2, "0")}${String(d.getSeconds()).padStart(2, "0")}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get file size in bytes. Returns 0 if file doesn't exist.
|
|
78
|
+
*/
|
|
79
|
+
export function fileSize(path) {
|
|
80
|
+
try {
|
|
81
|
+
return statSync(path).size;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Count lines in a file without shell commands.
|
|
89
|
+
*/
|
|
90
|
+
export function lineCount(path) {
|
|
91
|
+
try {
|
|
92
|
+
return readFileSync(path, "utf-8").split("\n").length;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Run a command with proper shell for this platform.
|
|
100
|
+
*/
|
|
101
|
+
export function shellExec(cmd, opts = {}) {
|
|
102
|
+
const execOpts = {
|
|
103
|
+
encoding: "utf-8",
|
|
104
|
+
timeout: opts.timeout ?? 30000,
|
|
105
|
+
stdio: opts.stdio === "pipe" ? ["pipe", "pipe", "pipe"] : "inherit",
|
|
106
|
+
};
|
|
107
|
+
if (opts.cwd)
|
|
108
|
+
execOpts.cwd = opts.cwd;
|
|
109
|
+
if (isWin)
|
|
110
|
+
execOpts.shell = "cmd.exe";
|
|
111
|
+
return execSync(cmd, execOpts);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a command that works on both Unix and Windows.
|
|
115
|
+
* Handles: stderr redirection, path separators, etc.
|
|
116
|
+
*/
|
|
117
|
+
export function crossPlatformCmd(unixCmd, windowsCmd) {
|
|
118
|
+
if (isWin && windowsCmd)
|
|
119
|
+
return windowsCmd;
|
|
120
|
+
return unixCmd;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Redirect stderr to null, cross-platform.
|
|
124
|
+
* Unix: 2>/dev/null
|
|
125
|
+
* Windows: 2>NUL
|
|
126
|
+
*/
|
|
127
|
+
export function silenceStderr(cmd) {
|
|
128
|
+
if (isWin)
|
|
129
|
+
return `${cmd} 2>NUL`;
|
|
130
|
+
return `${cmd} 2>/dev/null`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the home directory path.
|
|
134
|
+
*/
|
|
135
|
+
export function getHome() {
|
|
136
|
+
return homedir();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolve a path that works on this platform.
|
|
140
|
+
*/
|
|
141
|
+
export function resolvePath(...parts) {
|
|
142
|
+
return resolve(...parts);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if running as root/admin.
|
|
146
|
+
*/
|
|
147
|
+
export function isAdmin() {
|
|
148
|
+
if (isWin) {
|
|
149
|
+
return !!tryExec("net session 2>NUL");
|
|
150
|
+
}
|
|
151
|
+
return process.getuid?.() === 0;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the package install command for this platform.
|
|
155
|
+
*/
|
|
156
|
+
export function getSystemInstallCmd(pkg) {
|
|
157
|
+
if (isWin) {
|
|
158
|
+
if (commandExists("winget"))
|
|
159
|
+
return `winget install ${pkg} --accept-source-agreements --accept-package-agreements -h`;
|
|
160
|
+
if (commandExists("choco"))
|
|
161
|
+
return `choco install ${pkg} -y`;
|
|
162
|
+
return `echo "Please install ${pkg} manually"`;
|
|
163
|
+
}
|
|
164
|
+
if (commandExists("apt-get"))
|
|
165
|
+
return `sudo apt-get install -y ${pkg}`;
|
|
166
|
+
if (commandExists("dnf"))
|
|
167
|
+
return `sudo dnf install -y ${pkg}`;
|
|
168
|
+
if (commandExists("brew"))
|
|
169
|
+
return `brew install ${pkg}`;
|
|
170
|
+
if (commandExists("apk"))
|
|
171
|
+
return `apk add ${pkg}`;
|
|
172
|
+
return `echo "Please install ${pkg} manually"`;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Platform info for display.
|
|
176
|
+
*/
|
|
177
|
+
export function getPlatformSummary() {
|
|
178
|
+
const os = osPlatform();
|
|
179
|
+
if (isWSL)
|
|
180
|
+
return "WSL (Windows Subsystem for Linux)";
|
|
181
|
+
if (os === "win32")
|
|
182
|
+
return "Windows";
|
|
183
|
+
if (os === "darwin")
|
|
184
|
+
return "macOS";
|
|
185
|
+
return "Linux";
|
|
186
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart archive creation.
|
|
3
|
+
*
|
|
4
|
+
* Auto-excludes heavy/non-essential directories (node_modules, .git, etc.)
|
|
5
|
+
* unless the user explicitly requests them. Checks disk space before archiving.
|
|
6
|
+
*/
|
|
7
|
+
export interface ArchiveOptions {
|
|
8
|
+
source: string;
|
|
9
|
+
destination?: string;
|
|
10
|
+
includeAll?: boolean;
|
|
11
|
+
excludes?: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Smart archive: checks space, shows what will be excluded, asks before creating.
|
|
15
|
+
*/
|
|
16
|
+
export declare function smartArchive(options: ArchiveOptions): Promise<string>;
|