notoken-core 1.6.0 → 1.8.1
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/ascii-art.json +12 -0
- package/config/chat-responses.json +1019 -0
- package/config/cheat-sheets.json +94 -0
- package/config/concept-clusters.json +31 -0
- package/config/daily-tips.json +105 -0
- package/config/entities.json +93 -0
- package/config/history-today.json +9762 -0
- package/config/image-prompts.json +20 -0
- package/config/intent-vectors.json +1 -0
- package/config/intents.json +5749 -85
- package/config/ollama-models.json +193 -0
- package/config/rules.json +32 -1
- package/config/startup-quotes.json +45 -0
- package/dist/automation/discordPatchright.d.ts +35 -0
- package/dist/automation/discordPatchright.js +437 -0
- package/dist/automation/discordSetup.d.ts +31 -0
- package/dist/automation/discordSetup.js +338 -0
- package/dist/automation/smAutomation.d.ts +82 -0
- package/dist/automation/smAutomation.js +448 -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 +538 -3
- package/dist/handlers/executor.d.ts +2 -0
- package/dist/handlers/executor.js +4669 -31
- package/dist/index.d.ts +39 -5
- package/dist/index.js +56 -4
- 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.d.ts +47 -0
- package/dist/nlp/llmFallback.js +175 -36
- package/dist/nlp/llmParser.d.ts +5 -1
- package/dist/nlp/llmParser.js +43 -24
- 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 +199 -6
- package/dist/nlp/ruleParser.js +348 -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/types/intent.d.ts +8 -0
- package/dist/types/intent.js +1 -0
- package/dist/utils/achievements.d.ts +38 -0
- package/dist/utils/achievements.js +126 -0
- 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/bookmarks.d.ts +13 -0
- package/dist/utils/bookmarks.js +51 -0
- 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/devTools.d.ts +35 -0
- package/dist/utils/devTools.js +95 -0
- package/dist/utils/discordDiag.d.ts +35 -0
- package/dist/utils/discordDiag.js +834 -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 +127 -0
- package/dist/utils/openclawDiag.js +1535 -0
- package/dist/utils/openclawLogParser.d.ts +65 -0
- package/dist/utils/openclawLogParser.js +168 -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/snippets.d.ts +13 -0
- package/dist/utils/snippets.js +53 -0
- package/dist/utils/stabilityMatrixManager.d.ts +80 -0
- package/dist/utils/stabilityMatrixManager.js +268 -0
- package/dist/utils/teachMode.d.ts +41 -0
- package/dist/utils/teachMode.js +100 -0
- package/dist/utils/timer.d.ts +22 -0
- package/dist/utils/timer.js +52 -0
- package/dist/utils/updater.d.ts +1 -0
- package/dist/utils/updater.js +1 -1
- package/dist/utils/userContext.d.ts +57 -0
- package/dist/utils/userContext.js +133 -0
- package/dist/utils/version.d.ts +20 -0
- package/dist/utils/version.js +212 -0
- package/package.json +6 -3
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart project detection and package manager resolution.
|
|
3
|
+
*
|
|
4
|
+
* Detects the project type in a directory and returns the correct
|
|
5
|
+
* install/update/build commands for that project's ecosystem.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { detectLocalPlatform } from "./platform.js";
|
|
10
|
+
const c = {
|
|
11
|
+
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
|
12
|
+
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m", magenta: "\x1b[35m",
|
|
13
|
+
};
|
|
14
|
+
/** Check which files exist in a directory. */
|
|
15
|
+
function has(dir, ...files) {
|
|
16
|
+
return files.some((f) => existsSync(resolve(dir, f)));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Detect all projects in a directory.
|
|
20
|
+
* Returns them in priority order (most specific first).
|
|
21
|
+
*/
|
|
22
|
+
export function detectProjects(dir = process.cwd()) {
|
|
23
|
+
const projects = [];
|
|
24
|
+
// ── Node.js ecosystem ──
|
|
25
|
+
if (has(dir, "package.json")) {
|
|
26
|
+
// Detect which package manager
|
|
27
|
+
let pm = "npm";
|
|
28
|
+
let installCmd = "npm install";
|
|
29
|
+
let updateCmd = "npm update";
|
|
30
|
+
let lockFile;
|
|
31
|
+
if (has(dir, "bun.lockb", "bun.lock")) {
|
|
32
|
+
pm = "bun";
|
|
33
|
+
installCmd = "bun install";
|
|
34
|
+
updateCmd = "bun update";
|
|
35
|
+
lockFile = "bun.lockb";
|
|
36
|
+
}
|
|
37
|
+
else if (has(dir, "pnpm-lock.yaml")) {
|
|
38
|
+
pm = "pnpm";
|
|
39
|
+
installCmd = "pnpm install";
|
|
40
|
+
updateCmd = "pnpm update";
|
|
41
|
+
lockFile = "pnpm-lock.yaml";
|
|
42
|
+
}
|
|
43
|
+
else if (has(dir, "yarn.lock")) {
|
|
44
|
+
pm = "yarn";
|
|
45
|
+
installCmd = "yarn install";
|
|
46
|
+
updateCmd = "yarn upgrade";
|
|
47
|
+
lockFile = "yarn.lock";
|
|
48
|
+
}
|
|
49
|
+
else if (has(dir, "package-lock.json")) {
|
|
50
|
+
lockFile = "package-lock.json";
|
|
51
|
+
}
|
|
52
|
+
// Detect framework for type label
|
|
53
|
+
let type = "Node.js";
|
|
54
|
+
if (has(dir, "next.config.js", "next.config.ts", "next.config.mjs"))
|
|
55
|
+
type = "Next.js";
|
|
56
|
+
else if (has(dir, "nuxt.config.ts", "nuxt.config.js"))
|
|
57
|
+
type = "Nuxt.js";
|
|
58
|
+
else if (has(dir, "svelte.config.js"))
|
|
59
|
+
type = "SvelteKit";
|
|
60
|
+
else if (has(dir, "remix.config.js"))
|
|
61
|
+
type = "Remix";
|
|
62
|
+
else if (has(dir, "astro.config.mjs"))
|
|
63
|
+
type = "Astro";
|
|
64
|
+
else if (has(dir, "angular.json"))
|
|
65
|
+
type = "Angular";
|
|
66
|
+
else if (has(dir, "vue.config.js"))
|
|
67
|
+
type = "Vue.js";
|
|
68
|
+
else if (has(dir, "tsconfig.json"))
|
|
69
|
+
type = "TypeScript/Node.js";
|
|
70
|
+
const buildCmd = has(dir, "tsconfig.json") ? `${pm} run build` : undefined;
|
|
71
|
+
projects.push({ type, packageManager: pm, installCmd, updateCmd, buildCmd, lockFile, configFile: "package.json" });
|
|
72
|
+
}
|
|
73
|
+
// ── Python ecosystem ──
|
|
74
|
+
if (has(dir, "pyproject.toml")) {
|
|
75
|
+
if (has(dir, "poetry.lock")) {
|
|
76
|
+
projects.push({ type: "Python (Poetry)", packageManager: "poetry", installCmd: "poetry install", updateCmd: "poetry update", configFile: "pyproject.toml", lockFile: "poetry.lock" });
|
|
77
|
+
}
|
|
78
|
+
else if (has(dir, "uv.lock")) {
|
|
79
|
+
projects.push({ type: "Python (uv)", packageManager: "uv", installCmd: "uv sync", updateCmd: "uv lock --upgrade && uv sync", configFile: "pyproject.toml", lockFile: "uv.lock" });
|
|
80
|
+
}
|
|
81
|
+
else if (has(dir, "pdm.lock")) {
|
|
82
|
+
projects.push({ type: "Python (pdm)", packageManager: "pdm", installCmd: "pdm install", updateCmd: "pdm update", configFile: "pyproject.toml", lockFile: "pdm.lock" });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
projects.push({ type: "Python", packageManager: "pip", installCmd: "pip install -e .", updateCmd: "pip install -e . --upgrade", configFile: "pyproject.toml" });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (has(dir, "Pipfile")) {
|
|
89
|
+
projects.push({ type: "Python (Pipenv)", packageManager: "pipenv", installCmd: "pipenv install", updateCmd: "pipenv update", configFile: "Pipfile", lockFile: "Pipfile.lock" });
|
|
90
|
+
}
|
|
91
|
+
else if (has(dir, "requirements.txt")) {
|
|
92
|
+
projects.push({ type: "Python", packageManager: "pip", installCmd: "pip install -r requirements.txt", updateCmd: "pip install -r requirements.txt --upgrade", configFile: "requirements.txt" });
|
|
93
|
+
}
|
|
94
|
+
// ── Go ──
|
|
95
|
+
if (has(dir, "go.mod")) {
|
|
96
|
+
projects.push({ type: "Go", packageManager: "go", installCmd: "go mod download", updateCmd: "go get -u ./... && go mod tidy", configFile: "go.mod", lockFile: "go.sum" });
|
|
97
|
+
}
|
|
98
|
+
// ── Rust ──
|
|
99
|
+
if (has(dir, "Cargo.toml")) {
|
|
100
|
+
projects.push({ type: "Rust", packageManager: "cargo", installCmd: "cargo build", updateCmd: "cargo update", buildCmd: "cargo build --release", configFile: "Cargo.toml", lockFile: "Cargo.lock" });
|
|
101
|
+
}
|
|
102
|
+
// ── Ruby ──
|
|
103
|
+
if (has(dir, "Gemfile")) {
|
|
104
|
+
projects.push({ type: "Ruby", packageManager: "bundler", installCmd: "bundle install", updateCmd: "bundle update", configFile: "Gemfile", lockFile: "Gemfile.lock" });
|
|
105
|
+
}
|
|
106
|
+
// ── PHP ──
|
|
107
|
+
if (has(dir, "composer.json")) {
|
|
108
|
+
projects.push({ type: "PHP (Composer)", packageManager: "composer", installCmd: "composer install", updateCmd: "composer update", configFile: "composer.json", lockFile: "composer.lock" });
|
|
109
|
+
}
|
|
110
|
+
// ── .NET ──
|
|
111
|
+
const csproj = ["*.csproj", "*.fsproj"].some(() => {
|
|
112
|
+
try {
|
|
113
|
+
const { execSync } = require("node:child_process");
|
|
114
|
+
return execSync(`ls ${dir}/*.csproj ${dir}/*.fsproj 2>/dev/null`, { encoding: "utf-8" }).trim().length > 0;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (has(dir, "*.sln") || csproj) {
|
|
121
|
+
projects.push({ type: ".NET", packageManager: "dotnet", installCmd: "dotnet restore", updateCmd: "dotnet restore", buildCmd: "dotnet build", configFile: "*.csproj" });
|
|
122
|
+
}
|
|
123
|
+
// ── Java ──
|
|
124
|
+
if (has(dir, "pom.xml")) {
|
|
125
|
+
projects.push({ type: "Java (Maven)", packageManager: "maven", installCmd: "mvn install", updateCmd: "mvn versions:use-latest-versions", buildCmd: "mvn package", configFile: "pom.xml" });
|
|
126
|
+
}
|
|
127
|
+
else if (has(dir, "build.gradle", "build.gradle.kts")) {
|
|
128
|
+
projects.push({ type: "Java (Gradle)", packageManager: "gradle", installCmd: "./gradlew build", updateCmd: "./gradlew dependencies --write-locks", buildCmd: "./gradlew build", configFile: "build.gradle" });
|
|
129
|
+
}
|
|
130
|
+
return projects;
|
|
131
|
+
}
|
|
132
|
+
/** Format project detection results for display. */
|
|
133
|
+
export function formatProjectDetection(projects) {
|
|
134
|
+
if (projects.length === 0) {
|
|
135
|
+
return `${c.dim}No recognized projects in current directory.${c.reset}`;
|
|
136
|
+
}
|
|
137
|
+
const lines = [];
|
|
138
|
+
lines.push(`\n${c.bold}${c.cyan}── Detected Projects ──${c.reset}\n`);
|
|
139
|
+
for (const p of projects) {
|
|
140
|
+
const lockLabel = p.lockFile && existsSync(resolve(process.cwd(), p.lockFile))
|
|
141
|
+
? `${c.green}✓${c.reset} ${p.lockFile}`
|
|
142
|
+
: `${c.yellow}⚠ no lock file${c.reset}`;
|
|
143
|
+
lines.push(` ${c.magenta}${c.bold}${p.type}${c.reset} ${c.dim}(${p.packageManager})${c.reset} ${lockLabel}`);
|
|
144
|
+
lines.push(` Install: ${c.cyan}${p.installCmd}${c.reset}`);
|
|
145
|
+
lines.push(` Update: ${c.cyan}${p.updateCmd}${c.reset}`);
|
|
146
|
+
if (p.buildCmd) {
|
|
147
|
+
lines.push(` Build: ${c.cyan}${p.buildCmd}${c.reset}`);
|
|
148
|
+
}
|
|
149
|
+
lines.push("");
|
|
150
|
+
}
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
/** Get the install command for the primary project in cwd. */
|
|
154
|
+
export function getProjectInstallCmd(dir) {
|
|
155
|
+
const projects = detectProjects(dir);
|
|
156
|
+
return projects.length > 0 ? projects[0].installCmd : null;
|
|
157
|
+
}
|
|
158
|
+
/** Get the update command for the primary project in cwd. */
|
|
159
|
+
export function getProjectUpdateCmd(dir) {
|
|
160
|
+
const projects = detectProjects(dir);
|
|
161
|
+
return projects.length > 0 ? projects[0].updateCmd : null;
|
|
162
|
+
}
|
|
163
|
+
/** Read package.json or composer.json and extract scripts, deps, and metadata. */
|
|
164
|
+
export function readProjectConfig(dir = process.cwd()) {
|
|
165
|
+
// Try package.json first (Node.js)
|
|
166
|
+
if (existsSync(resolve(dir, "package.json"))) {
|
|
167
|
+
return readPackageJson(dir);
|
|
168
|
+
}
|
|
169
|
+
// Try composer.json (PHP)
|
|
170
|
+
if (existsSync(resolve(dir, "composer.json"))) {
|
|
171
|
+
return readComposerJson(dir);
|
|
172
|
+
}
|
|
173
|
+
// Try pyproject.toml scripts
|
|
174
|
+
if (existsSync(resolve(dir, "pyproject.toml"))) {
|
|
175
|
+
return readPyprojectScripts(dir);
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function readPackageJson(dir) {
|
|
180
|
+
try {
|
|
181
|
+
const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf-8"));
|
|
182
|
+
let pm = "npm";
|
|
183
|
+
if (existsSync(resolve(dir, "bun.lockb")) || existsSync(resolve(dir, "bun.lock")))
|
|
184
|
+
pm = "bun";
|
|
185
|
+
else if (existsSync(resolve(dir, "pnpm-lock.yaml")))
|
|
186
|
+
pm = "pnpm";
|
|
187
|
+
else if (existsSync(resolve(dir, "yarn.lock")))
|
|
188
|
+
pm = "yarn";
|
|
189
|
+
return {
|
|
190
|
+
packageManager: pm,
|
|
191
|
+
scripts: pkg.scripts ?? {},
|
|
192
|
+
dependencies: Object.keys(pkg.dependencies ?? {}),
|
|
193
|
+
devDependencies: Object.keys(pkg.devDependencies ?? {}),
|
|
194
|
+
name: pkg.name ?? "unknown",
|
|
195
|
+
version: pkg.version ?? "0.0.0",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function readComposerJson(dir) {
|
|
203
|
+
try {
|
|
204
|
+
const composer = JSON.parse(readFileSync(resolve(dir, "composer.json"), "utf-8"));
|
|
205
|
+
return {
|
|
206
|
+
packageManager: "composer",
|
|
207
|
+
scripts: composer.scripts ?? {},
|
|
208
|
+
dependencies: Object.keys(composer.require ?? {}),
|
|
209
|
+
devDependencies: Object.keys(composer["require-dev"] ?? {}),
|
|
210
|
+
name: composer.name ?? "unknown",
|
|
211
|
+
version: composer.version ?? "0.0.0",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function readPyprojectScripts(dir) {
|
|
219
|
+
try {
|
|
220
|
+
const content = readFileSync(resolve(dir, "pyproject.toml"), "utf-8");
|
|
221
|
+
// Basic TOML script extraction — look for [tool.poetry.scripts] or [project.scripts]
|
|
222
|
+
const scripts = {};
|
|
223
|
+
const scriptMatch = content.match(/\[(?:tool\.poetry\.scripts|project\.scripts)\]\n([\s\S]*?)(?:\n\[|$)/);
|
|
224
|
+
if (scriptMatch) {
|
|
225
|
+
for (const line of scriptMatch[1].split("\n")) {
|
|
226
|
+
const kv = line.match(/^(\w[\w-]*)\s*=\s*"([^"]+)"/);
|
|
227
|
+
if (kv)
|
|
228
|
+
scripts[kv[1]] = kv[2];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const pm = existsSync(resolve(dir, "poetry.lock")) ? "poetry"
|
|
232
|
+
: existsSync(resolve(dir, "uv.lock")) ? "uv"
|
|
233
|
+
: "pip";
|
|
234
|
+
// Extract project name
|
|
235
|
+
const nameMatch = content.match(/name\s*=\s*"([^"]+)"/);
|
|
236
|
+
const versionMatch = content.match(/version\s*=\s*"([^"]+)"/);
|
|
237
|
+
return {
|
|
238
|
+
packageManager: pm,
|
|
239
|
+
scripts,
|
|
240
|
+
dependencies: [],
|
|
241
|
+
devDependencies: [],
|
|
242
|
+
name: nameMatch?.[1] ?? "unknown",
|
|
243
|
+
version: versionMatch?.[1] ?? "0.0.0",
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** Format package.json scripts for display. */
|
|
251
|
+
export function formatPackageScripts(info) {
|
|
252
|
+
const lines = [];
|
|
253
|
+
const scripts = Object.entries(info.scripts);
|
|
254
|
+
lines.push(`\n${c.bold}${c.cyan}── ${info.name}@${info.version} ──${c.reset}\n`);
|
|
255
|
+
lines.push(` ${c.bold}Package manager:${c.reset} ${info.packageManager}`);
|
|
256
|
+
if (scripts.length > 0) {
|
|
257
|
+
lines.push(`\n ${c.bold}Available scripts:${c.reset}`);
|
|
258
|
+
for (const [name, cmd] of scripts) {
|
|
259
|
+
const runCmd = info.packageManager === "npm" ? `npm run ${name}` : `${info.packageManager} ${name}`;
|
|
260
|
+
lines.push(` ${c.cyan}${runCmd.padEnd(30)}${c.reset} ${c.dim}→ ${cmd}${c.reset}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
lines.push(` ${c.dim}No scripts defined.${c.reset}`);
|
|
265
|
+
}
|
|
266
|
+
if (info.dependencies.length > 0) {
|
|
267
|
+
lines.push(`\n ${c.bold}Dependencies:${c.reset} ${c.dim}(${info.dependencies.length})${c.reset}`);
|
|
268
|
+
const shown = info.dependencies.slice(0, 10);
|
|
269
|
+
lines.push(` ${c.dim}${shown.join(", ")}${info.dependencies.length > 10 ? ` +${info.dependencies.length - 10} more` : ""}${c.reset}`);
|
|
270
|
+
}
|
|
271
|
+
if (info.devDependencies.length > 0) {
|
|
272
|
+
lines.push(` ${c.bold}Dev dependencies:${c.reset} ${c.dim}(${info.devDependencies.length})${c.reset}`);
|
|
273
|
+
const shown = info.devDependencies.slice(0, 10);
|
|
274
|
+
lines.push(` ${c.dim}${shown.join(", ")}${info.devDependencies.length > 10 ? ` +${info.devDependencies.length - 10} more` : ""}${c.reset}`);
|
|
275
|
+
}
|
|
276
|
+
return lines.join("\n");
|
|
277
|
+
}
|
|
278
|
+
/** Build the run command prefix for a package manager. */
|
|
279
|
+
function runPrefix(pm, script) {
|
|
280
|
+
switch (pm) {
|
|
281
|
+
case "npm": return `npm run ${script}`;
|
|
282
|
+
case "composer": return `composer ${script}`;
|
|
283
|
+
case "poetry": return `poetry run ${script}`;
|
|
284
|
+
case "uv": return `uv run ${script}`;
|
|
285
|
+
default: return `${pm} ${script}`;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/** Get the run command for a named script. */
|
|
289
|
+
export function getScriptRunCmd(scriptName, dir) {
|
|
290
|
+
const info = readProjectConfig(dir);
|
|
291
|
+
if (!info)
|
|
292
|
+
return null;
|
|
293
|
+
// Exact match
|
|
294
|
+
if (info.scripts[scriptName]) {
|
|
295
|
+
return runPrefix(info.packageManager, scriptName);
|
|
296
|
+
}
|
|
297
|
+
// Fuzzy match — "dev" matches "dev", "start:dev", etc.
|
|
298
|
+
const fuzzy = Object.keys(info.scripts).find((s) => s === scriptName || s.includes(scriptName) || scriptName.includes(s));
|
|
299
|
+
if (fuzzy) {
|
|
300
|
+
return runPrefix(info.packageManager, fuzzy);
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get the system-level update command based on the detected platform.
|
|
306
|
+
*/
|
|
307
|
+
export function getSystemUpdateCmd() {
|
|
308
|
+
const plat = detectLocalPlatform();
|
|
309
|
+
switch (plat.packageManager) {
|
|
310
|
+
case "apt": return "sudo apt-get update && sudo apt-get upgrade -y";
|
|
311
|
+
case "dnf": return "sudo dnf upgrade -y";
|
|
312
|
+
case "yum": return "sudo yum update -y";
|
|
313
|
+
case "pacman": return "sudo pacman -Syu --noconfirm";
|
|
314
|
+
case "apk": return "sudo apk update && sudo apk upgrade";
|
|
315
|
+
case "brew": return "brew update && brew upgrade";
|
|
316
|
+
case "choco": return "choco upgrade all -y";
|
|
317
|
+
default: return "echo 'Unknown package manager — update manually'";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
export interface ProjectInfo {
|
|
13
|
+
path: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
deps?: number;
|
|
19
|
+
scripts?: string[];
|
|
20
|
+
indicators: string[];
|
|
21
|
+
}
|
|
22
|
+
export interface DirSummary {
|
|
23
|
+
path: string;
|
|
24
|
+
totalFiles: number;
|
|
25
|
+
totalDirs: number;
|
|
26
|
+
projects: ProjectInfo[];
|
|
27
|
+
fileCounts: Record<string, number>;
|
|
28
|
+
largestFiles: Array<{
|
|
29
|
+
name: string;
|
|
30
|
+
size: number;
|
|
31
|
+
}>;
|
|
32
|
+
notable: string[];
|
|
33
|
+
totalSize: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Scan for projects in a directory (recursive, up to maxDepth).
|
|
37
|
+
*/
|
|
38
|
+
export declare function scanProjects(rootPath: string, maxDepth?: number): ProjectInfo[];
|
|
39
|
+
/**
|
|
40
|
+
* Summarize a directory — files, projects, sizes.
|
|
41
|
+
*/
|
|
42
|
+
export declare function summarizeDirectory(dirPath: string): DirSummary;
|
|
43
|
+
export declare function formatProjectList(projects: ProjectInfo[], rootPath: string): string;
|
|
44
|
+
export declare function formatDirSummary(summary: DirSummary): string;
|
|
@@ -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
|
+
}
|