androjack-mcp 1.3.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/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- package/.github/pull_request_template.md +16 -0
- package/CONTRIBUTING.md +27 -0
- package/LICENSE +21 -0
- package/README.md +592 -0
- package/SECURITY.md +26 -0
- package/assets/AndroJack banner.png +0 -0
- package/assets/killer_argument.png +0 -0
- package/build/constants.js +412 -0
- package/build/http-server.js +163 -0
- package/build/http.js +151 -0
- package/build/index.js +553 -0
- package/build/install.js +379 -0
- package/build/logger.js +57 -0
- package/build/tools/api-level.js +170 -0
- package/build/tools/api36-compliance.js +282 -0
- package/build/tools/architecture.js +75 -0
- package/build/tools/build-publish.js +362 -0
- package/build/tools/component.js +90 -0
- package/build/tools/debugger.js +82 -0
- package/build/tools/gradle.js +234 -0
- package/build/tools/kmp.js +348 -0
- package/build/tools/kotlin-patterns.js +500 -0
- package/build/tools/large-screen.js +366 -0
- package/build/tools/m3-expressive.js +447 -0
- package/build/tools/navigation3.js +331 -0
- package/build/tools/ondevice-ai.js +283 -0
- package/build/tools/permissions.js +404 -0
- package/build/tools/play-policy.js +221 -0
- package/build/tools/scalability.js +621 -0
- package/build/tools/search.js +89 -0
- package/build/tools/testing.js +439 -0
- package/build/tools/wear.js +337 -0
- package/build/tools/xr.js +274 -0
- package/config/antigravity_mcp.json +32 -0
- package/config/claude_desktop_config.json +17 -0
- package/config/cursor_mcp.json +21 -0
- package/config/jetbrains_mcp.json +28 -0
- package/config/kiro_mcp.json +40 -0
- package/config/vscode_mcp.json +24 -0
- package/config/windsurf_mcp.json +18 -0
- package/package.json +51 -0
- package/src/constants.ts +436 -0
- package/src/http-server.ts +186 -0
- package/src/http.ts +190 -0
- package/src/index.ts +702 -0
- package/src/install.ts +441 -0
- package/src/logger.ts +67 -0
- package/src/tools/api-level.ts +198 -0
- package/src/tools/api36-compliance.ts +289 -0
- package/src/tools/architecture.ts +94 -0
- package/src/tools/build-publish.ts +379 -0
- package/src/tools/component.ts +106 -0
- package/src/tools/debugger.ts +111 -0
- package/src/tools/gradle.ts +288 -0
- package/src/tools/kmp.ts +352 -0
- package/src/tools/kotlin-patterns.ts +534 -0
- package/src/tools/large-screen.ts +391 -0
- package/src/tools/m3-expressive.ts +473 -0
- package/src/tools/navigation3.ts +338 -0
- package/src/tools/ondevice-ai.ts +287 -0
- package/src/tools/permissions.ts +445 -0
- package/src/tools/play-policy.ts +229 -0
- package/src/tools/scalability.ts +646 -0
- package/src/tools/search.ts +112 -0
- package/src/tools/testing.ts +460 -0
- package/src/tools/wear.ts +343 -0
- package/src/tools/xr.ts +278 -0
- package/tsconfig.json +17 -0
package/src/install.ts
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AndroJack MCP – Smart Installer
|
|
4
|
+
*
|
|
5
|
+
* Detects OS, installed IDEs, and config file locations automatically.
|
|
6
|
+
* Supports both automated (--auto) and guided interactive installation.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx androjack-mcp install → interactive guided mode
|
|
10
|
+
* npx androjack-mcp install --auto → auto-detect and install to all found IDEs
|
|
11
|
+
* npx androjack-mcp install --ide cursor → target a specific IDE
|
|
12
|
+
* npx androjack-mcp install --list → list all supported IDEs and their status
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as fs from "fs";
|
|
16
|
+
import * as path from "path";
|
|
17
|
+
import * as os from "os";
|
|
18
|
+
import * as readline from "readline";
|
|
19
|
+
|
|
20
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
interface IdeTarget {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
configPaths: string[]; // ordered by preference
|
|
26
|
+
configKey: "mcpServers" | "servers"; // JSON structure key
|
|
27
|
+
format: "standard" | "vscode";
|
|
28
|
+
oneClickUrl?: string;
|
|
29
|
+
notes?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface InstallResult {
|
|
33
|
+
ide: string;
|
|
34
|
+
success: boolean;
|
|
35
|
+
path?: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── AndroJack server config block (reused for all IDEs) ───────────────────
|
|
40
|
+
|
|
41
|
+
const SERVER_CONFIG_STANDARD = {
|
|
42
|
+
command: "npx",
|
|
43
|
+
args: ["-y", "androjack-mcp"],
|
|
44
|
+
env: {},
|
|
45
|
+
autoApprove: [],
|
|
46
|
+
disabled: false,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const SERVER_CONFIG_VSCODE = {
|
|
50
|
+
type: "stdio",
|
|
51
|
+
command: "npx",
|
|
52
|
+
args: ["-y", "androjack-mcp"],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// ── IDE Definitions ─────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const HOME = os.homedir();
|
|
58
|
+
const PLATFORM = process.platform; // darwin | linux | win32
|
|
59
|
+
|
|
60
|
+
function getConfigPaths(platform: string): IdeTarget[] {
|
|
61
|
+
const appdata = process.env.APPDATA ?? path.join(HOME, "AppData", "Roaming");
|
|
62
|
+
const localappdata = process.env.LOCALAPPDATA ?? path.join(HOME, "AppData", "Local");
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
// ── Claude Desktop ──────────────────────────────────────────────────────
|
|
66
|
+
{
|
|
67
|
+
id: "claude",
|
|
68
|
+
name: "Claude Desktop",
|
|
69
|
+
configPaths:
|
|
70
|
+
platform === "darwin"
|
|
71
|
+
? [path.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json")]
|
|
72
|
+
: platform === "win32"
|
|
73
|
+
? [path.join(appdata, "Claude", "claude_desktop_config.json")]
|
|
74
|
+
: [path.join(HOME, ".config", "Claude", "claude_desktop_config.json")],
|
|
75
|
+
configKey: "mcpServers",
|
|
76
|
+
format: "standard",
|
|
77
|
+
notes: "Restart Claude Desktop after install. Look for 🔨 in chat input.",
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// ── Cursor IDE ──────────────────────────────────────────────────────────
|
|
81
|
+
{
|
|
82
|
+
id: "cursor",
|
|
83
|
+
name: "Cursor",
|
|
84
|
+
configPaths: [
|
|
85
|
+
path.join(process.cwd(), ".cursor", "mcp.json"), // project-level (preferred)
|
|
86
|
+
path.join(HOME, ".cursor", "mcp.json"), // global
|
|
87
|
+
],
|
|
88
|
+
configKey: "mcpServers",
|
|
89
|
+
format: "standard",
|
|
90
|
+
notes: "Check Settings → MCP for green dot confirmation.",
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// ── Windsurf (Codeium) ──────────────────────────────────────────────────
|
|
94
|
+
{
|
|
95
|
+
id: "windsurf",
|
|
96
|
+
name: "Windsurf",
|
|
97
|
+
configPaths: [
|
|
98
|
+
path.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
99
|
+
path.join(HOME, ".windsurf", "mcp_config.json"),
|
|
100
|
+
],
|
|
101
|
+
configKey: "mcpServers",
|
|
102
|
+
format: "standard",
|
|
103
|
+
notes: "Restart Windsurf → Cascade panel shows AndroJack tools.",
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// ── VS Code (GitHub Copilot) ─────────────────────────────────────────────
|
|
107
|
+
{
|
|
108
|
+
id: "vscode",
|
|
109
|
+
name: "VS Code (GitHub Copilot)",
|
|
110
|
+
configPaths: [
|
|
111
|
+
path.join(process.cwd(), ".vscode", "mcp.json"), // workspace (preferred)
|
|
112
|
+
...(platform === "darwin"
|
|
113
|
+
? [path.join(HOME, "Library", "Application Support", "Code", "User", "settings.json")]
|
|
114
|
+
: platform === "win32"
|
|
115
|
+
? [path.join(appdata, "Code", "User", "settings.json")]
|
|
116
|
+
: [path.join(HOME, ".config", "Code", "User", "settings.json")]),
|
|
117
|
+
],
|
|
118
|
+
configKey: "servers",
|
|
119
|
+
format: "vscode",
|
|
120
|
+
notes: "VS Code 1.99+ required. Copilot Chat → Agent mode to access tools.",
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
// ── AWS Kiro IDE ─────────────────────────────────────────────────────────
|
|
124
|
+
{
|
|
125
|
+
id: "kiro",
|
|
126
|
+
name: "AWS Kiro",
|
|
127
|
+
configPaths: [
|
|
128
|
+
path.join(process.cwd(), ".kiro", "settings", "mcp.json"), // project-level
|
|
129
|
+
path.join(HOME, ".kiro", "settings", "mcp.json"), // global
|
|
130
|
+
],
|
|
131
|
+
configKey: "mcpServers",
|
|
132
|
+
format: "standard",
|
|
133
|
+
oneClickUrl: (() => {
|
|
134
|
+
const name = encodeURIComponent("androjack");
|
|
135
|
+
const config = encodeURIComponent(
|
|
136
|
+
JSON.stringify({ command: "npx", args: ["-y", "androjack-mcp"], disabled: false, autoApprove: [] })
|
|
137
|
+
);
|
|
138
|
+
return `https://kiro.dev/launch/mcp/add?name=${name}&config=${config}`;
|
|
139
|
+
})(),
|
|
140
|
+
notes: "Or use the one-click Kiro install link in the README.",
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ── Google Antigravity IDE (standalone, launched Nov 18 2025 with Gemini 3) ────
|
|
144
|
+
// NOT Firebase Studio / Project IDX — those are separate Google products.
|
|
145
|
+
// Confirmed config path from real usage: ~/.gemini/antigravity/mcp_config.json
|
|
146
|
+
{
|
|
147
|
+
id: "antigravity",
|
|
148
|
+
name: "Google Antigravity IDE",
|
|
149
|
+
configPaths: [
|
|
150
|
+
path.join(HOME, ".gemini", "antigravity", "mcp_config.json"),
|
|
151
|
+
],
|
|
152
|
+
configKey: "mcpServers",
|
|
153
|
+
format: "standard",
|
|
154
|
+
notes:
|
|
155
|
+
"After saving: Antigravity Agent pane → '...' → MCP Servers → Manage → Refresh.",
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
// ── JetBrains (Android Studio / IntelliJ) ────────────────────────────────
|
|
159
|
+
{
|
|
160
|
+
id: "jetbrains",
|
|
161
|
+
name: "JetBrains AI Assistant (Android Studio / IntelliJ)",
|
|
162
|
+
configPaths: [
|
|
163
|
+
// JetBrains stores MCP config inside IDE-version-specific dirs
|
|
164
|
+
...(platform === "darwin"
|
|
165
|
+
? [
|
|
166
|
+
path.join(HOME, "Library", "Application Support", "JetBrains", "AndroidStudio2024.3", "mcp.json"),
|
|
167
|
+
path.join(HOME, "Library", "Application Support", "JetBrains", "IdeaIC2024.3", "mcp.json"),
|
|
168
|
+
]
|
|
169
|
+
: platform === "win32"
|
|
170
|
+
? [
|
|
171
|
+
path.join(appdata, "JetBrains", "AndroidStudio2024.3", "mcp.json"),
|
|
172
|
+
path.join(appdata, "JetBrains", "IdeaIC2024.3", "mcp.json"),
|
|
173
|
+
]
|
|
174
|
+
: [
|
|
175
|
+
path.join(HOME, ".config", "JetBrains", "AndroidStudio2024.3", "mcp.json"),
|
|
176
|
+
path.join(HOME, ".config", "JetBrains", "IdeaIC2024.3", "mcp.json"),
|
|
177
|
+
]),
|
|
178
|
+
],
|
|
179
|
+
configKey: "mcpServers",
|
|
180
|
+
format: "standard",
|
|
181
|
+
notes:
|
|
182
|
+
"Or add manually: Android Studio → Settings → Tools → AI Assistant → MCP Servers → +",
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Config helpers ──────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
function buildConfig(target: IdeTarget): Record<string, unknown> {
|
|
190
|
+
if (target.format === "vscode") {
|
|
191
|
+
return { servers: { androjack: SERVER_CONFIG_VSCODE } };
|
|
192
|
+
}
|
|
193
|
+
return { mcpServers: { androjack: SERVER_CONFIG_STANDARD } };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mergeConfig(existing: Record<string, unknown>, target: IdeTarget): Record<string, unknown> {
|
|
197
|
+
const key = target.configKey as string;
|
|
198
|
+
const serverBlock =
|
|
199
|
+
target.format === "vscode" ? SERVER_CONFIG_VSCODE : SERVER_CONFIG_STANDARD;
|
|
200
|
+
|
|
201
|
+
const existingBlock = (existing[key] as Record<string, unknown>) ?? {};
|
|
202
|
+
return {
|
|
203
|
+
...existing,
|
|
204
|
+
[key]: { ...existingBlock, androjack: serverBlock },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function installToPath(configPath: string, target: IdeTarget): InstallResult {
|
|
209
|
+
try {
|
|
210
|
+
const dir = path.dirname(configPath);
|
|
211
|
+
|
|
212
|
+
// Create directory if needed
|
|
213
|
+
if (!fs.existsSync(dir)) {
|
|
214
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let finalConfig: Record<string, unknown>;
|
|
218
|
+
|
|
219
|
+
if (fs.existsSync(configPath)) {
|
|
220
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
221
|
+
let existing: Record<string, unknown> = {};
|
|
222
|
+
try {
|
|
223
|
+
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
224
|
+
} catch {
|
|
225
|
+
// Corrupted JSON — back it up and overwrite
|
|
226
|
+
fs.writeFileSync(configPath + ".backup", raw);
|
|
227
|
+
}
|
|
228
|
+
finalConfig = mergeConfig(existing, target);
|
|
229
|
+
} else {
|
|
230
|
+
finalConfig = buildConfig(target);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2) + "\n", "utf-8");
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
ide: target.name,
|
|
237
|
+
success: true,
|
|
238
|
+
path: configPath,
|
|
239
|
+
message: `✅ Installed to ${configPath}`,
|
|
240
|
+
};
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return {
|
|
243
|
+
ide: target.name,
|
|
244
|
+
success: false,
|
|
245
|
+
message: `❌ Failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── IDE Detection ────────────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
function detectInstalledIdes(targets: IdeTarget[]): IdeTarget[] {
|
|
253
|
+
return targets.filter((target) => {
|
|
254
|
+
// Check if any of the config's parent dirs exist (suggests IDE is installed)
|
|
255
|
+
return target.configPaths.some((p) => fs.existsSync(path.dirname(path.dirname(p))));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function alreadyInstalled(target: IdeTarget): string | null {
|
|
260
|
+
for (const p of target.configPaths) {
|
|
261
|
+
if (!fs.existsSync(p)) continue;
|
|
262
|
+
try {
|
|
263
|
+
const json = JSON.parse(fs.readFileSync(p, "utf-8")) as Record<string, unknown>;
|
|
264
|
+
const key = target.configKey as string;
|
|
265
|
+
const servers = json[key] as Record<string, unknown> | undefined;
|
|
266
|
+
if (servers?.["androjack"]) return p;
|
|
267
|
+
} catch {
|
|
268
|
+
// ignore
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ── Output helpers ────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
const RESET = "\x1b[0m";
|
|
277
|
+
const BOLD = "\x1b[1m";
|
|
278
|
+
const GREEN = "\x1b[32m";
|
|
279
|
+
const YELLOW = "\x1b[33m";
|
|
280
|
+
const CYAN = "\x1b[36m";
|
|
281
|
+
const DIM = "\x1b[2m";
|
|
282
|
+
|
|
283
|
+
function banner(): void {
|
|
284
|
+
console.log(`
|
|
285
|
+
${BOLD}${CYAN}╔══════════════════════════════════════════════════╗
|
|
286
|
+
║ 🤖 AndroJack MCP — Smart Installer ║
|
|
287
|
+
║ The Jack of All Android Trades ║
|
|
288
|
+
╚══════════════════════════════════════════════════╝${RESET}
|
|
289
|
+
`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function printStatus(targets: IdeTarget[]): void {
|
|
293
|
+
console.log(`${BOLD}IDE Installation Status:${RESET}\n`);
|
|
294
|
+
for (const t of targets) {
|
|
295
|
+
const installed = alreadyInstalled(t);
|
|
296
|
+
const icon = installed ? `${GREEN}✓${RESET}` : `${DIM}○${RESET}`;
|
|
297
|
+
const status = installed ? `${GREEN}installed${RESET} → ${DIM}${installed}${RESET}` : `${DIM}not installed${RESET}`;
|
|
298
|
+
console.log(` ${icon} ${BOLD}${t.name}${RESET} — ${status}`);
|
|
299
|
+
}
|
|
300
|
+
console.log();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── Prompt helper ────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
async function prompt(question: string): Promise<string> {
|
|
306
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
307
|
+
return new Promise((resolve) => rl.question(question, (ans) => { rl.close(); resolve(ans.trim()); }));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ── Install to best path for a target ───────────────────────────────────────
|
|
311
|
+
|
|
312
|
+
function installTarget(target: IdeTarget): InstallResult {
|
|
313
|
+
// For project-level configs, use first path. For global, prefer existing or first.
|
|
314
|
+
const existingPath = target.configPaths.find((p) => fs.existsSync(p));
|
|
315
|
+
const chosenPath = existingPath ?? target.configPaths[0];
|
|
316
|
+
return installToPath(chosenPath, target);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ── Main entry point ─────────────────────────────────────────────────────────
|
|
320
|
+
|
|
321
|
+
async function main(): Promise<void> {
|
|
322
|
+
const args = process.argv.slice(2);
|
|
323
|
+
const subcommand = args[0];
|
|
324
|
+
|
|
325
|
+
if (!subcommand || subcommand === "install") {
|
|
326
|
+
banner();
|
|
327
|
+
const targets = getConfigPaths(PLATFORM);
|
|
328
|
+
const autoFlag = args.includes("--auto");
|
|
329
|
+
const listFlag = args.includes("--list");
|
|
330
|
+
const ideFlag = args.find((a) => a.startsWith("--ide="))?.split("=")[1] ?? null;
|
|
331
|
+
|
|
332
|
+
// ── --list ──────────────────────────────────────────────────────────────
|
|
333
|
+
if (listFlag) {
|
|
334
|
+
printStatus(targets);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ── --ide=<id> ──────────────────────────────────────────────────────────
|
|
339
|
+
if (ideFlag) {
|
|
340
|
+
const target = targets.find((t) => t.id === ideFlag);
|
|
341
|
+
if (!target) {
|
|
342
|
+
console.error(`❌ Unknown IDE: "${ideFlag}". Supported: ${targets.map((t) => t.id).join(", ")}`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
const result = installTarget(target);
|
|
346
|
+
console.log(result.message);
|
|
347
|
+
if (result.success && target.notes) console.log(` ${DIM}→ ${target.notes}${RESET}`);
|
|
348
|
+
if (target.oneClickUrl) console.log(` ${YELLOW}One-click install: ${target.oneClickUrl}${RESET}`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ── --auto ──────────────────────────────────────────────────────────────
|
|
353
|
+
if (autoFlag) {
|
|
354
|
+
console.log(`${BOLD}Auto-detecting installed IDEs on ${PLATFORM}...${RESET}\n`);
|
|
355
|
+
const detected = detectInstalledIdes(targets);
|
|
356
|
+
|
|
357
|
+
if (detected.length === 0) {
|
|
358
|
+
console.log(`${YELLOW}No IDEs auto-detected. Run without --auto for guided install.${RESET}`);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(`Found: ${detected.map((t) => t.name).join(", ")}\n`);
|
|
363
|
+
const results: InstallResult[] = [];
|
|
364
|
+
|
|
365
|
+
for (const target of detected) {
|
|
366
|
+
const existing = alreadyInstalled(target);
|
|
367
|
+
if (existing) {
|
|
368
|
+
console.log(`${GREEN}⏭ ${target.name}${RESET} — already installed at ${DIM}${existing}${RESET}`);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const result = installTarget(target);
|
|
372
|
+
results.push(result);
|
|
373
|
+
console.log(result.message);
|
|
374
|
+
if (result.success && target.notes) console.log(` ${DIM}→ ${target.notes}${RESET}`);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const successes = results.filter((r) => r.success).length;
|
|
378
|
+
console.log(`\n${BOLD}${GREEN}Done. ${successes} new installation(s) completed.${RESET}`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ── Interactive guided mode ─────────────────────────────────────────────
|
|
383
|
+
console.log(`${BOLD}Platform detected:${RESET} ${PLATFORM}\n`);
|
|
384
|
+
printStatus(targets);
|
|
385
|
+
|
|
386
|
+
console.log(`${BOLD}Select installation mode:${RESET}`);
|
|
387
|
+
console.log(` ${CYAN}1${RESET} Auto-install to all detected IDEs`);
|
|
388
|
+
console.log(` ${CYAN}2${RESET} Choose specific IDEs`);
|
|
389
|
+
console.log(` ${CYAN}3${RESET} Show manual config snippets`);
|
|
390
|
+
console.log(` ${CYAN}q${RESET} Quit\n`);
|
|
391
|
+
|
|
392
|
+
const choice = await prompt("Your choice: ");
|
|
393
|
+
|
|
394
|
+
if (choice === "1") {
|
|
395
|
+
const detected = detectInstalledIdes(targets);
|
|
396
|
+
for (const target of detected) {
|
|
397
|
+
const result = installTarget(target);
|
|
398
|
+
console.log(result.message);
|
|
399
|
+
if (result.success && target.notes) console.log(` ${DIM}→ ${target.notes}${RESET}`);
|
|
400
|
+
}
|
|
401
|
+
} else if (choice === "2") {
|
|
402
|
+
for (let i = 0; i < targets.length; i++) {
|
|
403
|
+
const t = targets[i];
|
|
404
|
+
const installed = alreadyInstalled(t);
|
|
405
|
+
const status = installed ? `${GREEN}(already installed)${RESET}` : "";
|
|
406
|
+
console.log(` ${CYAN}${i + 1}${RESET} ${t.name} ${status}`);
|
|
407
|
+
}
|
|
408
|
+
const input = await prompt("\nEnter numbers (e.g. 1 3 5) or 'all': ");
|
|
409
|
+
const selected =
|
|
410
|
+
input.trim() === "all"
|
|
411
|
+
? targets
|
|
412
|
+
: input
|
|
413
|
+
.split(/\s+/)
|
|
414
|
+
.map((n) => targets[parseInt(n, 10) - 1])
|
|
415
|
+
.filter(Boolean);
|
|
416
|
+
|
|
417
|
+
for (const target of selected) {
|
|
418
|
+
const result = installTarget(target);
|
|
419
|
+
console.log(result.message);
|
|
420
|
+
if (result.success && target.notes) console.log(` ${DIM}→ ${target.notes}${RESET}`);
|
|
421
|
+
if (target.oneClickUrl) console.log(` ${YELLOW}One-click: ${target.oneClickUrl}${RESET}`);
|
|
422
|
+
}
|
|
423
|
+
} else if (choice === "3") {
|
|
424
|
+
const snippet = {
|
|
425
|
+
mcpServers: {
|
|
426
|
+
androjack: SERVER_CONFIG_STANDARD,
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
console.log(`\n${BOLD}Paste this into your IDE's MCP config file:${RESET}\n`);
|
|
430
|
+
console.log(JSON.stringify(snippet, null, 2));
|
|
431
|
+
console.log(`\n${DIM}For VS Code .vscode/mcp.json, use the "servers" key instead of "mcpServers".${RESET}`);
|
|
432
|
+
} else {
|
|
433
|
+
console.log("Exiting.");
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
main().catch((err) => {
|
|
439
|
+
console.error("Installer error:", err);
|
|
440
|
+
process.exit(1);
|
|
441
|
+
});
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AndroJack MCP — Production Logger
|
|
3
|
+
*
|
|
4
|
+
* All output goes to stderr. stdout is reserved exclusively for the MCP
|
|
5
|
+
* JSON-RPC protocol — writing anything else there corrupts the transport.
|
|
6
|
+
*
|
|
7
|
+
* Log levels follow the standard severity ladder. In production (NODE_ENV=production)
|
|
8
|
+
* only WARN and above are emitted to reduce noise; in development all levels print.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
12
|
+
|
|
13
|
+
const LEVELS: Record<LogLevel, number> = {
|
|
14
|
+
debug: 0,
|
|
15
|
+
info: 1,
|
|
16
|
+
warn: 2,
|
|
17
|
+
error: 3,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const ENV_LEVEL = (process.env.LOG_LEVEL ?? (
|
|
21
|
+
process.env.NODE_ENV === "production" ? "warn" : "debug"
|
|
22
|
+
)) as LogLevel;
|
|
23
|
+
|
|
24
|
+
const MIN_LEVEL = LEVELS[ENV_LEVEL] ?? LEVELS.debug;
|
|
25
|
+
|
|
26
|
+
function emit(level: LogLevel, message: string, meta?: Record<string, unknown>): void {
|
|
27
|
+
if (LEVELS[level] < MIN_LEVEL) return;
|
|
28
|
+
|
|
29
|
+
const entry: Record<string, unknown> = {
|
|
30
|
+
ts: new Date().toISOString(),
|
|
31
|
+
level,
|
|
32
|
+
msg: message,
|
|
33
|
+
};
|
|
34
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
35
|
+
entry.meta = meta;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// JSON lines format — easy to parse with any log aggregator (Datadog, Loki, CloudWatch)
|
|
39
|
+
process.stderr.write(JSON.stringify(entry) + "\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const logger = {
|
|
43
|
+
debug: (msg: string, meta?: Record<string, unknown>) => emit("debug", msg, meta),
|
|
44
|
+
info: (msg: string, meta?: Record<string, unknown>) => emit("info", msg, meta),
|
|
45
|
+
warn: (msg: string, meta?: Record<string, unknown>) => emit("warn", msg, meta),
|
|
46
|
+
error: (msg: string, meta?: Record<string, unknown>) => emit("error", msg, meta),
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Wraps an async tool handler with timing + error telemetry.
|
|
50
|
+
* Usage: const result = await logger.timed("tool_name", () => myTool(args))
|
|
51
|
+
*/
|
|
52
|
+
async timed<T>(toolName: string, fn: () => Promise<T>): Promise<T> {
|
|
53
|
+
const start = Date.now();
|
|
54
|
+
try {
|
|
55
|
+
const result = await fn();
|
|
56
|
+
emit("info", "tool_call_ok", { tool: toolName, ms: Date.now() - start });
|
|
57
|
+
return result;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
emit("error", "tool_call_error", {
|
|
60
|
+
tool: toolName,
|
|
61
|
+
ms: Date.now() - start,
|
|
62
|
+
error: err instanceof Error ? err.message : String(err),
|
|
63
|
+
});
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
};
|