@atlascompiler/mcp 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/LICENSE +21 -0
- package/README.md +365 -0
- package/dist/chunk-QNNAZCBS.js +1164 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +612 -0
- package/dist/index.d.ts +595 -0
- package/dist/index.js +84 -0
- package/package.json +48 -0
- package/skills/atlas/SKILL.md +193 -0
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createAtlasMcpServer
|
|
4
|
+
} from "./chunk-QNNAZCBS.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
8
|
+
|
|
9
|
+
// src/auth/store.ts
|
|
10
|
+
import * as fs from "fs";
|
|
11
|
+
import * as os from "os";
|
|
12
|
+
import * as path from "path";
|
|
13
|
+
var FileCredentialStore = class {
|
|
14
|
+
filePath;
|
|
15
|
+
constructor(customPath) {
|
|
16
|
+
if (customPath) {
|
|
17
|
+
this.filePath = customPath;
|
|
18
|
+
} else {
|
|
19
|
+
const home = os.homedir();
|
|
20
|
+
this.filePath = path.join(home, ".atlas", "credentials.json");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
getPath() {
|
|
24
|
+
return this.filePath;
|
|
25
|
+
}
|
|
26
|
+
async getApiKey() {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(this.filePath)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const raw = fs.readFileSync(this.filePath, "utf8");
|
|
32
|
+
if (!raw || !raw.trim()) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
if (typeof parsed?.apiKey === "string" && parsed.apiKey.trim().length > 0) {
|
|
37
|
+
return parsed.apiKey.trim();
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async saveApiKey(apiKey) {
|
|
45
|
+
const trimmed = apiKey.trim();
|
|
46
|
+
if (!trimmed) {
|
|
47
|
+
throw new Error("Cannot save an empty API key.");
|
|
48
|
+
}
|
|
49
|
+
const dir = path.dirname(this.filePath);
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
52
|
+
}
|
|
53
|
+
if (process.platform !== "win32") {
|
|
54
|
+
try {
|
|
55
|
+
fs.chmodSync(dir, 448);
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
let createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(this.filePath)) {
|
|
62
|
+
const raw = fs.readFileSync(this.filePath, "utf8");
|
|
63
|
+
const parsed = JSON.parse(raw);
|
|
64
|
+
if (parsed?.createdAt) {
|
|
65
|
+
createdAt = parsed.createdAt;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
const payload = {
|
|
71
|
+
apiKey: trimmed,
|
|
72
|
+
createdAt,
|
|
73
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
74
|
+
};
|
|
75
|
+
const tempFile = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
76
|
+
fs.writeFileSync(tempFile, `${JSON.stringify(payload, null, 2)}
|
|
77
|
+
`, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
mode: 384
|
|
80
|
+
});
|
|
81
|
+
if (process.platform !== "win32") {
|
|
82
|
+
try {
|
|
83
|
+
fs.chmodSync(tempFile, 384);
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
fs.renameSync(tempFile, this.filePath);
|
|
88
|
+
}
|
|
89
|
+
async deleteApiKey() {
|
|
90
|
+
try {
|
|
91
|
+
if (fs.existsSync(this.filePath)) {
|
|
92
|
+
fs.unlinkSync(this.filePath);
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err?.code !== "ENOENT") {
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// src/auth/resolve.ts
|
|
103
|
+
async function resolveAtlasApiKey(options = {}) {
|
|
104
|
+
const result = await resolveAtlasApiKeyWithSource(options);
|
|
105
|
+
return result.apiKey;
|
|
106
|
+
}
|
|
107
|
+
async function resolveAtlasApiKeyWithSource(options = {}) {
|
|
108
|
+
const env = options.env ?? process.env;
|
|
109
|
+
const envKey = env.ATLAS_API_KEY?.trim();
|
|
110
|
+
if (envKey) {
|
|
111
|
+
return {
|
|
112
|
+
apiKey: envKey,
|
|
113
|
+
source: "env",
|
|
114
|
+
location: "process.env.ATLAS_API_KEY"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const store = options.store ?? new FileCredentialStore();
|
|
118
|
+
const storedKey = await store.getApiKey();
|
|
119
|
+
if (storedKey) {
|
|
120
|
+
return {
|
|
121
|
+
apiKey: storedKey,
|
|
122
|
+
source: "store",
|
|
123
|
+
location: store.getPath()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
apiKey: null,
|
|
128
|
+
source: "none"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/auth/wizard.ts
|
|
133
|
+
import * as fs2 from "fs";
|
|
134
|
+
import * as os2 from "os";
|
|
135
|
+
import * as path2 from "path";
|
|
136
|
+
import * as readline from "readline/promises";
|
|
137
|
+
import { fileURLToPath } from "url";
|
|
138
|
+
var API_KEY_REGEX = /^(atlas_[A-Za-z0-9_-]{43}|ak_(live|test)_[0-9A-Za-z]{32,64})$/;
|
|
139
|
+
function validateApiKey(key) {
|
|
140
|
+
return API_KEY_REGEX.test(key.trim());
|
|
141
|
+
}
|
|
142
|
+
function maskApiKey(key) {
|
|
143
|
+
const trimmed = key.trim();
|
|
144
|
+
if (trimmed.length <= 10) {
|
|
145
|
+
return "***";
|
|
146
|
+
}
|
|
147
|
+
return `${trimmed.slice(0, 8)}...${trimmed.slice(-4)}`;
|
|
148
|
+
}
|
|
149
|
+
function getDetectedEditorConfigs(homeDir = os2.homedir(), projectDir = process.cwd()) {
|
|
150
|
+
const targets = [];
|
|
151
|
+
const platform = process.platform;
|
|
152
|
+
let claudeConfigPath;
|
|
153
|
+
if (platform === "darwin") {
|
|
154
|
+
claudeConfigPath = path2.join(
|
|
155
|
+
homeDir,
|
|
156
|
+
"Library",
|
|
157
|
+
"Application Support",
|
|
158
|
+
"Claude",
|
|
159
|
+
"claude_desktop_config.json"
|
|
160
|
+
);
|
|
161
|
+
} else if (platform === "win32") {
|
|
162
|
+
const appData = process.env.APPDATA || path2.join(homeDir, "AppData", "Roaming");
|
|
163
|
+
claudeConfigPath = path2.join(appData, "Claude", "claude_desktop_config.json");
|
|
164
|
+
} else {
|
|
165
|
+
claudeConfigPath = path2.join(homeDir, ".config", "Claude", "claude_desktop_config.json");
|
|
166
|
+
}
|
|
167
|
+
targets.push({
|
|
168
|
+
name: "Claude Desktop",
|
|
169
|
+
configPath: claudeConfigPath,
|
|
170
|
+
exists: fs2.existsSync(claudeConfigPath),
|
|
171
|
+
scope: "global",
|
|
172
|
+
format: "standard"
|
|
173
|
+
});
|
|
174
|
+
const cursorGlobalPath = path2.join(homeDir, ".cursor", "mcp.json");
|
|
175
|
+
targets.push({
|
|
176
|
+
name: "Cursor (Global)",
|
|
177
|
+
configPath: cursorGlobalPath,
|
|
178
|
+
exists: fs2.existsSync(cursorGlobalPath),
|
|
179
|
+
scope: "global",
|
|
180
|
+
format: "standard"
|
|
181
|
+
});
|
|
182
|
+
const windsurfGlobalPath = path2.join(homeDir, ".codeium", "windsurf", "mcp_config.json");
|
|
183
|
+
targets.push({
|
|
184
|
+
name: "Windsurf (Global)",
|
|
185
|
+
configPath: windsurfGlobalPath,
|
|
186
|
+
exists: fs2.existsSync(windsurfGlobalPath),
|
|
187
|
+
scope: "global",
|
|
188
|
+
format: "standard"
|
|
189
|
+
});
|
|
190
|
+
const cursorProjectPath = path2.join(projectDir, ".cursor", "mcp.json");
|
|
191
|
+
targets.push({
|
|
192
|
+
name: "Cursor (Project)",
|
|
193
|
+
configPath: cursorProjectPath,
|
|
194
|
+
exists: fs2.existsSync(cursorProjectPath),
|
|
195
|
+
scope: "project",
|
|
196
|
+
format: "standard"
|
|
197
|
+
});
|
|
198
|
+
const vscodeProjectPath = path2.join(projectDir, ".vscode", "mcp.json");
|
|
199
|
+
targets.push({
|
|
200
|
+
name: "VS Code (Project)",
|
|
201
|
+
configPath: vscodeProjectPath,
|
|
202
|
+
exists: fs2.existsSync(vscodeProjectPath),
|
|
203
|
+
scope: "project",
|
|
204
|
+
format: "vscode"
|
|
205
|
+
});
|
|
206
|
+
return targets;
|
|
207
|
+
}
|
|
208
|
+
function mergeMcpConfig(configPath, serverName = "atlas", format = "standard") {
|
|
209
|
+
let existingJson = {};
|
|
210
|
+
if (fs2.existsSync(configPath)) {
|
|
211
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
212
|
+
if (raw.trim()) {
|
|
213
|
+
try {
|
|
214
|
+
existingJson = JSON.parse(raw);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Failed to parse existing config at ${configPath}: ${msg}. Manual inspection required to avoid overwriting invalid JSON.`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const rootKey = format === "vscode" ? "servers" : "mcpServers";
|
|
224
|
+
const existingServers = existingJson[rootKey] ?? {};
|
|
225
|
+
const previousServers = Object.keys(existingServers);
|
|
226
|
+
const updatedServers = {
|
|
227
|
+
...existingServers,
|
|
228
|
+
[serverName]: {
|
|
229
|
+
command: "npx",
|
|
230
|
+
args: ["-y", "@atlascompiler/mcp"]
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
const updatedConfig = {
|
|
234
|
+
...existingJson,
|
|
235
|
+
[rootKey]: updatedServers
|
|
236
|
+
};
|
|
237
|
+
const dir = path2.dirname(configPath);
|
|
238
|
+
if (!fs2.existsSync(dir)) {
|
|
239
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
const tempPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
242
|
+
fs2.writeFileSync(tempPath, `${JSON.stringify(updatedConfig, null, 2)}
|
|
243
|
+
`, "utf8");
|
|
244
|
+
fs2.renameSync(tempPath, configPath);
|
|
245
|
+
return {
|
|
246
|
+
previousServers,
|
|
247
|
+
updatedServers: Object.keys(updatedServers)
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function getBundledSkillContent() {
|
|
251
|
+
if (typeof import.meta.url !== "string" || !import.meta.url.startsWith("file:")) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"Unable to resolve package directory: import.meta.url is not a valid file URL."
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
const currentDir = path2.dirname(fileURLToPath(import.meta.url));
|
|
257
|
+
const candidates = [
|
|
258
|
+
path2.resolve(currentDir, "../skills/atlas/SKILL.md"),
|
|
259
|
+
path2.resolve(currentDir, "../../skills/atlas/SKILL.md"),
|
|
260
|
+
path2.resolve(currentDir, "../../../skills/atlas/SKILL.md")
|
|
261
|
+
];
|
|
262
|
+
for (const candidate of candidates) {
|
|
263
|
+
if (fs2.existsSync(candidate)) {
|
|
264
|
+
return fs2.readFileSync(candidate, "utf8");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
throw new Error("Bundled Atlas SKILL.md could not be found.");
|
|
268
|
+
}
|
|
269
|
+
function installAtlasSkill(targetBaseDir = process.cwd(), isGlobal = false) {
|
|
270
|
+
const content = getBundledSkillContent();
|
|
271
|
+
const targetDir = isGlobal ? path2.join(os2.homedir(), ".agents", "skills", "atlas") : path2.join(targetBaseDir, ".agents", "skills", "atlas");
|
|
272
|
+
const targetFile = path2.join(targetDir, "SKILL.md");
|
|
273
|
+
const isNew = !fs2.existsSync(targetFile);
|
|
274
|
+
if (!fs2.existsSync(targetDir)) {
|
|
275
|
+
fs2.mkdirSync(targetDir, { recursive: true });
|
|
276
|
+
}
|
|
277
|
+
const tempFile = `${targetFile}.${process.pid}.${Date.now()}.tmp`;
|
|
278
|
+
fs2.writeFileSync(tempFile, content, "utf8");
|
|
279
|
+
fs2.renameSync(tempFile, targetFile);
|
|
280
|
+
return { targetPath: targetFile, isNew };
|
|
281
|
+
}
|
|
282
|
+
async function probeAtlasApiKey(apiKey, baseUrl = "https://api.atlas-compiler.com") {
|
|
283
|
+
try {
|
|
284
|
+
const controller = new AbortController();
|
|
285
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
286
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
287
|
+
method: "GET",
|
|
288
|
+
headers: {
|
|
289
|
+
Authorization: `Bearer ${apiKey}`,
|
|
290
|
+
"User-Agent": "Atlas-MCP-Init-Probe/1.0"
|
|
291
|
+
},
|
|
292
|
+
signal: controller.signal
|
|
293
|
+
}).finally(() => clearTimeout(timer));
|
|
294
|
+
if (res.ok) {
|
|
295
|
+
return { success: true, message: "Atlas API probe succeeded." };
|
|
296
|
+
}
|
|
297
|
+
if (res.status === 401 || res.status === 403) {
|
|
298
|
+
return {
|
|
299
|
+
success: false,
|
|
300
|
+
message: "Atlas API rejected key: Invalid or unauthorized credentials."
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
message: `Atlas API returned HTTP ${res.status}; key format is valid.`
|
|
306
|
+
};
|
|
307
|
+
} catch (err) {
|
|
308
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
message: `Could not reach Atlas API (${msg}); proceeding with format validation.`
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function promptForApiKey(rl, store, initialKey) {
|
|
316
|
+
if (initialKey?.trim()) {
|
|
317
|
+
return initialKey.trim();
|
|
318
|
+
}
|
|
319
|
+
const existingKey = await store.getApiKey();
|
|
320
|
+
if (existingKey) {
|
|
321
|
+
console.log(`Found existing credentials at: ${store.getPath()}`);
|
|
322
|
+
console.log(`Active Key: ${maskApiKey(existingKey)}`);
|
|
323
|
+
const replaceAns = await rl.question(
|
|
324
|
+
"Do you want to overwrite this key with a new one? [y/N]: "
|
|
325
|
+
);
|
|
326
|
+
if (!replaceAns.trim().toLowerCase().startsWith("y")) {
|
|
327
|
+
return existingKey;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
while (true) {
|
|
331
|
+
const input = await rl.question("Enter your Atlas API Key (atlas_... or ak_live_...): ");
|
|
332
|
+
const trimmed = input.trim();
|
|
333
|
+
if (!trimmed) {
|
|
334
|
+
console.log("API key cannot be empty. Please try again.");
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (!validateApiKey(trimmed)) {
|
|
338
|
+
console.log(
|
|
339
|
+
"Invalid API key format. Expected format: atlas_<43 chars> or ak_live_<32-64 chars>."
|
|
340
|
+
);
|
|
341
|
+
const retry = await rl.question("Try again? [Y/n]: ");
|
|
342
|
+
if (retry.trim().toLowerCase().startsWith("n")) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
return trimmed;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
async function probeApiKeyWithConfirmation(rl, apiKey) {
|
|
351
|
+
process.stdout.write("Probing Atlas endpoint... ");
|
|
352
|
+
const probeResult = await probeAtlasApiKey(apiKey);
|
|
353
|
+
console.log(probeResult.success ? "OK" : "Warning");
|
|
354
|
+
if (!probeResult.success) {
|
|
355
|
+
console.warn(`[!] ${probeResult.message}`);
|
|
356
|
+
const cont = await rl.question("Save key anyway? [Y/n]: ");
|
|
357
|
+
if (cont.trim().toLowerCase().startsWith("n")) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
async function selectScope(rl, preselected) {
|
|
364
|
+
if (preselected) {
|
|
365
|
+
return preselected;
|
|
366
|
+
}
|
|
367
|
+
console.log("\n--- MCP Editor Configuration ---");
|
|
368
|
+
console.log("Where would you like to configure Atlas MCP?");
|
|
369
|
+
console.log(" 1) Global / User-Level (Claude Desktop, ~/.cursor/mcp.json, Windsurf) [Default]");
|
|
370
|
+
console.log(
|
|
371
|
+
" 2) Project-Level (Configure for this repository: .cursor/mcp.json, .vscode/mcp.json)"
|
|
372
|
+
);
|
|
373
|
+
console.log(" 3) Both Global & Project-Level");
|
|
374
|
+
console.log(" 4) Skip editor configuration");
|
|
375
|
+
const answer = await rl.question("Select scope [1-4] (Default: 1): ");
|
|
376
|
+
const trimmed = answer.trim().toLowerCase();
|
|
377
|
+
if (trimmed === "2" || trimmed.startsWith("p")) return "project";
|
|
378
|
+
if (trimmed === "3" || trimmed.startsWith("b")) return "both";
|
|
379
|
+
if (trimmed === "4" || trimmed.startsWith("s")) return "skip";
|
|
380
|
+
return "global";
|
|
381
|
+
}
|
|
382
|
+
async function configureSingleEditor(rl, editor) {
|
|
383
|
+
const scopeTag = editor.scope === "global" ? "[Global]" : "[Project]";
|
|
384
|
+
const promptText = editor.exists ? `Configure Atlas MCP in ${editor.name} ${scopeTag} (${editor.configPath})? [Y/n]: ` : `Initialize Atlas MCP in ${editor.name} ${scopeTag} (${editor.configPath})? [Y/n]: `;
|
|
385
|
+
const answer = await rl.question(promptText);
|
|
386
|
+
if (answer.trim() && !answer.trim().toLowerCase().startsWith("y")) {
|
|
387
|
+
console.log(` Skipped ${editor.name}.`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const res = mergeMcpConfig(editor.configPath, "atlas", editor.format);
|
|
392
|
+
console.log(
|
|
393
|
+
` \u2713 Configured ${editor.name}. Active MCP servers: [${res.updatedServers.join(", ")}]`
|
|
394
|
+
);
|
|
395
|
+
} catch (err) {
|
|
396
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
397
|
+
console.error(` \u2717 Failed to configure ${editor.name}: ${msg}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function configureEditors(rl, scope, projectDir) {
|
|
401
|
+
if (scope === "skip") {
|
|
402
|
+
console.log("Skipped editor configuration.");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
console.log("\n--- Editor & Agent Integration ---");
|
|
406
|
+
const allEditors = getDetectedEditorConfigs(os2.homedir(), projectDir);
|
|
407
|
+
const targets = allEditors.filter((editor) => {
|
|
408
|
+
if (scope === "both") return true;
|
|
409
|
+
return editor.scope === scope;
|
|
410
|
+
});
|
|
411
|
+
for (const editor of targets) {
|
|
412
|
+
await configureSingleEditor(rl, editor);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
async function promptSkillInstallation(rl, projectDir = process.cwd()) {
|
|
416
|
+
console.log("\n--- Agent Skill Installation ---");
|
|
417
|
+
console.log("Install the canonical Atlas Agent Skill (.agents/skills/atlas/SKILL.md)?");
|
|
418
|
+
console.log(" 1) In this project (.agents/skills/atlas/SKILL.md) [Default]");
|
|
419
|
+
console.log(" 2) Globally for all projects (~/.agents/skills/atlas/SKILL.md)");
|
|
420
|
+
console.log(" 3) Skip skill installation");
|
|
421
|
+
const ans = await rl.question("Select option [1-3] (Default: 1): ");
|
|
422
|
+
const trimmed = ans.trim();
|
|
423
|
+
if (trimmed === "2" || trimmed.toLowerCase().startsWith("g")) {
|
|
424
|
+
try {
|
|
425
|
+
const res = installAtlasSkill(projectDir, true);
|
|
426
|
+
console.log(` \u2713 Installed Atlas Agent Skill globally to: ${res.targetPath}`);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
429
|
+
console.error(` \u2717 Failed to install global Agent Skill: ${msg}`);
|
|
430
|
+
}
|
|
431
|
+
} else if (trimmed === "3" || trimmed.toLowerCase().startsWith("s")) {
|
|
432
|
+
console.log(" Skipped Agent Skill installation.");
|
|
433
|
+
} else {
|
|
434
|
+
try {
|
|
435
|
+
const res = installAtlasSkill(projectDir, false);
|
|
436
|
+
console.log(` \u2713 Installed Atlas Agent Skill to: ${res.targetPath}`);
|
|
437
|
+
} catch (err) {
|
|
438
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
439
|
+
console.error(` \u2717 Failed to install Agent Skill: ${msg}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async function runSetupWizard(options = {}) {
|
|
444
|
+
const store = options.store ?? new FileCredentialStore();
|
|
445
|
+
const rl = options.rlInterface ?? readline.createInterface({
|
|
446
|
+
input: process.stdin,
|
|
447
|
+
output: process.stdout
|
|
448
|
+
});
|
|
449
|
+
const shouldCloseRl = !options.rlInterface;
|
|
450
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
451
|
+
try {
|
|
452
|
+
console.log("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
|
|
453
|
+
console.log("\u2551 Atlas MCP - Zero-Friction Credential Setup \u2551");
|
|
454
|
+
console.log("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n");
|
|
455
|
+
const apiKey = await promptForApiKey(rl, store, options.apiKey);
|
|
456
|
+
if (!apiKey) {
|
|
457
|
+
console.log("Setup aborted.");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (!options.skipProbe) {
|
|
461
|
+
const confirmed = await probeApiKeyWithConfirmation(rl, apiKey);
|
|
462
|
+
if (!confirmed) {
|
|
463
|
+
console.log("Setup cancelled.");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
await store.saveApiKey(apiKey);
|
|
468
|
+
console.log(`
|
|
469
|
+
\u2713 Securely saved credentials to: ${store.getPath()}`);
|
|
470
|
+
const scope = await selectScope(rl, options.scope);
|
|
471
|
+
await configureEditors(rl, scope, projectDir);
|
|
472
|
+
await promptSkillInstallation(rl, projectDir);
|
|
473
|
+
console.log("\n\u2728 Atlas MCP setup completed successfully!");
|
|
474
|
+
console.log("You can now run:");
|
|
475
|
+
console.log(" npx -y @atlascompiler/mcp");
|
|
476
|
+
console.log("or use Atlas tools directly in your AI assistant!\n");
|
|
477
|
+
} finally {
|
|
478
|
+
if (shouldCloseRl) {
|
|
479
|
+
rl.close();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// src/cli.ts
|
|
485
|
+
try {
|
|
486
|
+
if (typeof process.loadEnvFile === "function") {
|
|
487
|
+
process.loadEnvFile();
|
|
488
|
+
}
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
var cliArgs = process.argv.slice(2);
|
|
492
|
+
for (const arg of cliArgs) {
|
|
493
|
+
if (arg.startsWith("--api-key") || arg.startsWith("-k")) {
|
|
494
|
+
console.error(
|
|
495
|
+
"Security Error: Passing API keys via CLI flags (--api-key) is prohibited.\nPlease supply your credentials via ~/.atlas/credentials.json (run: atlas-mcp init) or the ATLAS_API_KEY environment variable."
|
|
496
|
+
);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
var firstArg = cliArgs[0]?.toLowerCase();
|
|
501
|
+
async function handleInit() {
|
|
502
|
+
let scope;
|
|
503
|
+
if (cliArgs.includes("--global") || cliArgs.includes("-g")) {
|
|
504
|
+
scope = "global";
|
|
505
|
+
} else if (cliArgs.includes("--project") || cliArgs.includes("-p")) {
|
|
506
|
+
scope = "project";
|
|
507
|
+
} else if (cliArgs.includes("--both")) {
|
|
508
|
+
scope = "both";
|
|
509
|
+
} else if (cliArgs.includes("--skip-editors")) {
|
|
510
|
+
scope = "skip";
|
|
511
|
+
}
|
|
512
|
+
const skipProbe = cliArgs.includes("--skip-probe");
|
|
513
|
+
await runSetupWizard({ scope, skipProbe });
|
|
514
|
+
}
|
|
515
|
+
async function handleWhoami() {
|
|
516
|
+
const result = await resolveAtlasApiKeyWithSource();
|
|
517
|
+
if (result.apiKey) {
|
|
518
|
+
console.log("Atlas Credentials:");
|
|
519
|
+
console.log(` Source: ${result.source} (${result.location})`);
|
|
520
|
+
console.log(` API Key: ${maskApiKey(result.apiKey)}`);
|
|
521
|
+
} else {
|
|
522
|
+
console.log("No Atlas credentials found.");
|
|
523
|
+
console.log(
|
|
524
|
+
"Run `atlas-mcp init` to configure credentials or set ATLAS_API_KEY in your environment."
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async function handleLogout() {
|
|
529
|
+
const store = new FileCredentialStore();
|
|
530
|
+
await store.deleteApiKey();
|
|
531
|
+
console.log(`Cleared stored credentials from ${store.getPath()}.`);
|
|
532
|
+
}
|
|
533
|
+
function handleSkillInstall() {
|
|
534
|
+
const isGlobal = cliArgs.includes("--global") || cliArgs.includes("-g");
|
|
535
|
+
try {
|
|
536
|
+
const result = installAtlasSkill(process.cwd(), isGlobal);
|
|
537
|
+
console.log(`\u2713 Atlas Agent Skill installed to: ${result.targetPath}`);
|
|
538
|
+
console.log(
|
|
539
|
+
"AI agents (Claude Code, Cursor, Antigravity) in this environment will now discover and use Atlas tools."
|
|
540
|
+
);
|
|
541
|
+
} catch (err) {
|
|
542
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
543
|
+
console.error(`Failed to install Agent Skill: ${msg}`);
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function printHelp() {
|
|
548
|
+
console.log(`
|
|
549
|
+
Atlas Model Context Protocol (MCP) CLI
|
|
550
|
+
|
|
551
|
+
Usage:
|
|
552
|
+
atlas-mcp [command] [options]
|
|
553
|
+
|
|
554
|
+
Commands:
|
|
555
|
+
(default) Start the MCP stdio server for Claude, Cursor, and other MCP clients
|
|
556
|
+
init | login | setup Interactive credential setup wizard and editor configuration
|
|
557
|
+
--global, -g Configure user-level editor settings (Claude, ~/.cursor, Windsurf)
|
|
558
|
+
--project, -p Configure project-level editor settings (.cursor/mcp.json, .vscode/mcp.json)
|
|
559
|
+
--both Configure both user-level and project-level editor settings
|
|
560
|
+
--skip-editors Skip editor configuration during setup
|
|
561
|
+
--skip-probe Skip network probe verification during setup
|
|
562
|
+
skill [--global, -g] Install the canonical Atlas Agent Skill into .agents/skills/atlas/SKILL.md
|
|
563
|
+
whoami Check currently active credentials and source
|
|
564
|
+
logout Clear stored credentials from ~/.atlas/credentials.json
|
|
565
|
+
help, -h, --help Show this help message
|
|
566
|
+
|
|
567
|
+
Environment Variables:
|
|
568
|
+
ATLAS_API_KEY Overrides stored credentials for CI/CD or ephemeral environments
|
|
569
|
+
ATLAS_BASE_URL Atlas API endpoint (default: https://api.atlas-compiler.com)
|
|
570
|
+
`);
|
|
571
|
+
}
|
|
572
|
+
async function main() {
|
|
573
|
+
if (firstArg === "init" || firstArg === "login" || firstArg === "setup") {
|
|
574
|
+
await handleInit();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (firstArg === "skill" || firstArg === "install-skill") {
|
|
578
|
+
handleSkillInstall();
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (firstArg === "whoami") {
|
|
582
|
+
await handleWhoami();
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (firstArg === "logout") {
|
|
586
|
+
await handleLogout();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
if (firstArg === "help" || firstArg === "--help" || firstArg === "-h") {
|
|
590
|
+
printHelp();
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const apiKey = await resolveAtlasApiKey();
|
|
594
|
+
if (!apiKey) {
|
|
595
|
+
console.error(
|
|
596
|
+
"[atlas-mcp] Notice: No Atlas API key found in ATLAS_API_KEY or ~/.atlas/credentials.json.\n[atlas-mcp] Execution calls against Atlas API will require valid credentials. Run `atlas-mcp init` to set up."
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
const baseUrl = process.env.ATLAS_BASE_URL ?? "https://api.atlas-compiler.com";
|
|
600
|
+
const server = createAtlasMcpServer({
|
|
601
|
+
context: {
|
|
602
|
+
apiKey: apiKey ?? void 0,
|
|
603
|
+
baseUrl
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
const transport = new StdioServerTransport();
|
|
607
|
+
await server.connect(transport);
|
|
608
|
+
}
|
|
609
|
+
main().catch((err) => {
|
|
610
|
+
console.error("Fatal error in Atlas MCP CLI:", err);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
});
|