artifacty 0.10.0 → 0.10.2
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/docs/integrations.md +20 -0
- package/package.json +1 -1
- package/src/cli.js +35 -23
- package/src/lib/installer.js +109 -18
package/docs/integrations.md
CHANGED
|
@@ -130,9 +130,29 @@ their password before they can continue to `/account` and issue MCP/API tokens.
|
|
|
130
130
|
- `--api-token <token>` is written into the generated MCP environment for bridge mode. For central servers, prefer a personal token issued from `/account`.
|
|
131
131
|
- `--url <url>` remains a browser-link override and does not enable central MCP by itself.
|
|
132
132
|
- `--timeout <ms>` adjusts Codex `startup_timeout_sec` and Gemini `timeout`. It does not change Claude Code startup behavior; set `MCP_TIMEOUT` before launching Claude Code if you need a larger value there.
|
|
133
|
+
- Do not use `artifacty install all --config <path>`. Each MCP client uses a
|
|
134
|
+
different config file shape, so shared config overrides are rejected. Use
|
|
135
|
+
`artifacty install <agent> --config <path>` only when targeting one agent.
|
|
136
|
+
- JSON-based installers preserve unrelated servers and replace only the
|
|
137
|
+
`artifacty` entry. If `mcpServers` or `servers` already exists but is not a
|
|
138
|
+
JSON object, Artifacty stops with an error instead of rewriting the file.
|
|
133
139
|
- `check` starts the local MCP server and verifies required tools, resources, and prompts through MCP discovery methods.
|
|
134
140
|
- `doctor` combines MCP discovery with runtime, storage, server, and service diagnostics.
|
|
135
141
|
|
|
142
|
+
Codex troubleshooting:
|
|
143
|
+
|
|
144
|
+
- If Codex reports a duplicate `mcp_servers.artifacty.env` or `artifacty.env`
|
|
145
|
+
key, update Artifacty and rerun the Codex installer. Older installs could
|
|
146
|
+
leave a legacy `[mcp_servers.artifacty.env]` child table behind while adding a
|
|
147
|
+
new inline `env = { ... }` value.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm install -g artifacty@latest
|
|
151
|
+
artifacty install codex \
|
|
152
|
+
--mcp-url http://10.0.0.50:8787/mcp \
|
|
153
|
+
--api-token "$ARTIFACTY_PERSONAL_TOKEN"
|
|
154
|
+
```
|
|
155
|
+
|
|
136
156
|
## Client Compatibility Matrix
|
|
137
157
|
|
|
138
158
|
| Client | Config shape | Scope | Timeout behavior | Restart requirement | Notes |
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -2,42 +2,24 @@
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
archiveArtifact,
|
|
7
|
-
checkStoreIntegrity,
|
|
8
|
-
createArtifact,
|
|
9
|
-
createStore,
|
|
10
|
-
getArtifact,
|
|
11
|
-
importUsersFromCsv,
|
|
12
|
-
listAuditEvents,
|
|
13
|
-
listArtifactsPage,
|
|
14
|
-
rebuildSearchIndex,
|
|
15
|
-
restoreArtifact,
|
|
16
|
-
updateArtifact
|
|
17
|
-
} from "./lib/storage.js";
|
|
18
|
-
import { exportStore, importStore, defaultBackupPath } from "./lib/backup.js";
|
|
19
|
-
import { convertAgentArtifact } from "./lib/converters.js";
|
|
20
|
-
import { checkMcpTools } from "./lib/check.js";
|
|
21
|
-
import { installAgent } from "./lib/installer.js";
|
|
22
|
-
import { serviceCommand } from "./lib/service.js";
|
|
23
|
-
import { backgroundStatus, startBackgroundServer, stopBackgroundServer } from "./lib/background.js";
|
|
24
|
-
import { resolvePublicBaseUrl } from "./lib/server-state.js";
|
|
25
5
|
import { generateToken } from "./lib/token.js";
|
|
26
|
-
import { runDoctor } from "./lib/doctor.js";
|
|
27
|
-
import { startServer } from "./server.js";
|
|
28
6
|
|
|
29
7
|
const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
30
8
|
|
|
31
9
|
async function main() {
|
|
32
10
|
const [command, ...args] = process.argv.slice(2);
|
|
33
11
|
const options = parseArgs(args);
|
|
34
|
-
const store = createStore({ home: options.home });
|
|
35
12
|
|
|
36
13
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
37
14
|
printHelp();
|
|
38
15
|
return;
|
|
39
16
|
}
|
|
40
17
|
|
|
18
|
+
if (command === "version" || command === "--version" || command === "-v") {
|
|
19
|
+
process.stdout.write(`${await packageVersion()}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
41
23
|
if (command === "token" || command === "generate-token") {
|
|
42
24
|
const token = generateToken(options);
|
|
43
25
|
if (options.raw) {
|
|
@@ -48,6 +30,29 @@ async function main() {
|
|
|
48
30
|
return;
|
|
49
31
|
}
|
|
50
32
|
|
|
33
|
+
const {
|
|
34
|
+
archiveArtifact,
|
|
35
|
+
checkStoreIntegrity,
|
|
36
|
+
createArtifact,
|
|
37
|
+
createStore,
|
|
38
|
+
getArtifact,
|
|
39
|
+
importUsersFromCsv,
|
|
40
|
+
listAuditEvents,
|
|
41
|
+
listArtifactsPage,
|
|
42
|
+
rebuildSearchIndex,
|
|
43
|
+
restoreArtifact,
|
|
44
|
+
updateArtifact
|
|
45
|
+
} = await import("./lib/storage.js");
|
|
46
|
+
const { exportStore, importStore, defaultBackupPath } = await import("./lib/backup.js");
|
|
47
|
+
const { convertAgentArtifact } = await import("./lib/converters.js");
|
|
48
|
+
const { checkMcpTools } = await import("./lib/check.js");
|
|
49
|
+
const { installAgent } = await import("./lib/installer.js");
|
|
50
|
+
const { serviceCommand } = await import("./lib/service.js");
|
|
51
|
+
const { backgroundStatus, startBackgroundServer, stopBackgroundServer } = await import("./lib/background.js");
|
|
52
|
+
const { runDoctor } = await import("./lib/doctor.js");
|
|
53
|
+
const { startServer } = await import("./server.js");
|
|
54
|
+
const store = createStore({ home: options.home });
|
|
55
|
+
|
|
51
56
|
if (command === "serve") {
|
|
52
57
|
if (options.detach && options.foreground) {
|
|
53
58
|
throw new Error("Use either --foreground or --detach, not both");
|
|
@@ -412,6 +417,7 @@ function shouldReadFileAsBase64(options, filePath) {
|
|
|
412
417
|
}
|
|
413
418
|
|
|
414
419
|
async function withUrls(store, artifact) {
|
|
420
|
+
const { resolvePublicBaseUrl } = await import("./lib/server-state.js");
|
|
415
421
|
const publicBaseUrl = await resolvePublicBaseUrl(store);
|
|
416
422
|
return {
|
|
417
423
|
...artifact,
|
|
@@ -442,10 +448,16 @@ function printJson(data) {
|
|
|
442
448
|
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
443
449
|
}
|
|
444
450
|
|
|
451
|
+
async function packageVersion() {
|
|
452
|
+
const packageJson = JSON.parse(await readFile(path.join(PACKAGE_ROOT, "package.json"), "utf8"));
|
|
453
|
+
return packageJson.version;
|
|
454
|
+
}
|
|
455
|
+
|
|
445
456
|
function printHelp() {
|
|
446
457
|
process.stdout.write(`Artifacty
|
|
447
458
|
|
|
448
459
|
Usage:
|
|
460
|
+
artifacty --version
|
|
449
461
|
artifacty token [--bytes 32] [--raw]
|
|
450
462
|
artifacty serve [--host 127.0.0.1] [--port 8787] [--home ~/.artifacty] [--api-token token] [--generate-token] [--bytes 32] [--mcp-http] [--foreground]
|
|
451
463
|
artifacty serve --foreground [--generate-token]
|
package/src/lib/installer.js
CHANGED
|
@@ -14,6 +14,9 @@ export async function installAgent(agent, options = {}) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (normalized === "all") {
|
|
17
|
+
if (options.configPath) {
|
|
18
|
+
throw new Error("install all does not support --config because each MCP client uses a different config file. Run install per agent when overriding config paths.");
|
|
19
|
+
}
|
|
17
20
|
const results = [];
|
|
18
21
|
for (const target of INSTALL_TARGETS) {
|
|
19
22
|
results.push(await installAgent(target, options));
|
|
@@ -73,11 +76,12 @@ export function createMcpServerConfig(options = {}) {
|
|
|
73
76
|
export async function installClaude(options = {}) {
|
|
74
77
|
const projectDir = path.resolve(options.projectDir || process.cwd());
|
|
75
78
|
const targetPath = path.resolve(options.configPath || path.join(projectDir, ".mcp.json"));
|
|
76
|
-
const existing = await
|
|
79
|
+
const existing = await readJsonConfigFile(targetPath);
|
|
80
|
+
const mcpServers = jsonObjectSection(existing, "mcpServers", targetPath);
|
|
77
81
|
const next = {
|
|
78
82
|
...existing,
|
|
79
83
|
mcpServers: {
|
|
80
|
-
...
|
|
84
|
+
...mcpServers,
|
|
81
85
|
artifacty: createMcpServerConfig(options)
|
|
82
86
|
}
|
|
83
87
|
};
|
|
@@ -93,11 +97,12 @@ export async function installClaude(options = {}) {
|
|
|
93
97
|
export async function installGemini(options = {}) {
|
|
94
98
|
const projectDir = path.resolve(options.projectDir || process.cwd());
|
|
95
99
|
const targetPath = path.resolve(options.configPath || path.join(projectDir, ".gemini", "settings.json"));
|
|
96
|
-
const existing = await
|
|
100
|
+
const existing = await readJsonConfigFile(targetPath);
|
|
101
|
+
const mcpServers = jsonObjectSection(existing, "mcpServers", targetPath);
|
|
97
102
|
const next = {
|
|
98
103
|
...existing,
|
|
99
104
|
mcpServers: {
|
|
100
|
-
...
|
|
105
|
+
...mcpServers,
|
|
101
106
|
artifacty: {
|
|
102
107
|
...createMcpServerConfig(options),
|
|
103
108
|
timeout: normalizeTimeoutMs(options.timeout),
|
|
@@ -117,11 +122,12 @@ export async function installGemini(options = {}) {
|
|
|
117
122
|
export async function installCopilot(options = {}) {
|
|
118
123
|
const projectDir = path.resolve(options.projectDir || process.cwd());
|
|
119
124
|
const targetPath = path.resolve(options.configPath || path.join(projectDir, ".vscode", "mcp.json"));
|
|
120
|
-
const existing = await
|
|
125
|
+
const existing = await readJsonConfigFile(targetPath);
|
|
126
|
+
const servers = jsonObjectSection(existing, "servers", targetPath);
|
|
121
127
|
const next = {
|
|
122
128
|
...existing,
|
|
123
129
|
servers: {
|
|
124
|
-
...
|
|
130
|
+
...servers,
|
|
125
131
|
artifacty: {
|
|
126
132
|
type: "stdio",
|
|
127
133
|
...createMcpServerConfig(options)
|
|
@@ -140,11 +146,12 @@ export async function installCopilot(options = {}) {
|
|
|
140
146
|
export async function installCursor(options = {}) {
|
|
141
147
|
const projectDir = path.resolve(options.projectDir || process.cwd());
|
|
142
148
|
const targetPath = path.resolve(options.configPath || path.join(projectDir, ".cursor", "mcp.json"));
|
|
143
|
-
const existing = await
|
|
149
|
+
const existing = await readJsonConfigFile(targetPath);
|
|
150
|
+
const mcpServers = jsonObjectSection(existing, "mcpServers", targetPath);
|
|
144
151
|
const next = {
|
|
145
152
|
...existing,
|
|
146
153
|
mcpServers: {
|
|
147
|
-
...
|
|
154
|
+
...mcpServers,
|
|
148
155
|
artifacty: createMcpServerConfig(options)
|
|
149
156
|
}
|
|
150
157
|
};
|
|
@@ -190,11 +197,35 @@ export function codexTomlBlock(config, options = {}) {
|
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
export function replaceTomlBlock(existing, dottedName, block) {
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
200
|
+
const trimmedBlock = block.trimEnd();
|
|
201
|
+
const lines = existing.replace(/\r\n/g, "\n").split("\n");
|
|
202
|
+
const target = normalizeTomlDottedName(dottedName);
|
|
203
|
+
const start = lines.findIndex((line) => normalizeTomlDottedName(parseTomlTableHeader(line)) === target);
|
|
204
|
+
|
|
205
|
+
if (start !== -1) {
|
|
206
|
+
let end = lines.length;
|
|
207
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
208
|
+
const tableName = normalizeTomlDottedName(parseTomlTableHeader(lines[index]));
|
|
209
|
+
if (!tableName) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (tableName === target || tableName.startsWith(`${target}.`)) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
end = index;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const prefix = lines.slice(0, start).join("\n").trimEnd();
|
|
220
|
+
const suffix = lines.slice(end).join("\n").trimStart();
|
|
221
|
+
return [
|
|
222
|
+
prefix,
|
|
223
|
+
trimmedBlock,
|
|
224
|
+
suffix
|
|
225
|
+
].filter(Boolean).join("\n").trimEnd() + "\n";
|
|
197
226
|
}
|
|
227
|
+
|
|
228
|
+
const trimmed = existing.trimEnd();
|
|
198
229
|
return `${trimmed}${trimmed ? "\n\n" : ""}${block}`;
|
|
199
230
|
}
|
|
200
231
|
|
|
@@ -216,20 +247,25 @@ async function writeInstallFile({ agent, path: targetPath, dryRun, content }) {
|
|
|
216
247
|
};
|
|
217
248
|
}
|
|
218
249
|
|
|
219
|
-
async function
|
|
250
|
+
async function readJsonConfigFile(filePath) {
|
|
220
251
|
if (!existsSync(filePath)) {
|
|
221
|
-
return
|
|
252
|
+
return {};
|
|
222
253
|
}
|
|
223
254
|
|
|
224
255
|
const raw = await readTextFile(filePath, "");
|
|
225
256
|
if (!raw.trim()) {
|
|
226
|
-
return
|
|
257
|
+
return {};
|
|
227
258
|
}
|
|
259
|
+
let parsed;
|
|
228
260
|
try {
|
|
229
|
-
|
|
261
|
+
parsed = JSON.parse(raw);
|
|
230
262
|
} catch (error) {
|
|
231
263
|
throw new Error(`Failed to parse ${filePath}: ${error.message}`);
|
|
232
264
|
}
|
|
265
|
+
if (!isPlainObject(parsed)) {
|
|
266
|
+
throw new Error(`Invalid MCP config at ${filePath}: expected a JSON object`);
|
|
267
|
+
}
|
|
268
|
+
return parsed;
|
|
233
269
|
}
|
|
234
270
|
|
|
235
271
|
async function readTextFile(filePath, fallback) {
|
|
@@ -247,6 +283,20 @@ function normalizeAgent(agent) {
|
|
|
247
283
|
return normalized;
|
|
248
284
|
}
|
|
249
285
|
|
|
286
|
+
function jsonObjectSection(config, key, filePath) {
|
|
287
|
+
if (config[key] === undefined) {
|
|
288
|
+
return {};
|
|
289
|
+
}
|
|
290
|
+
if (!isPlainObject(config[key])) {
|
|
291
|
+
throw new Error(`Invalid MCP config at ${filePath}: ${key} must be a JSON object`);
|
|
292
|
+
}
|
|
293
|
+
return config[key];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function isPlainObject(value) {
|
|
297
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
298
|
+
}
|
|
299
|
+
|
|
250
300
|
function normalizeTimeoutMs(value) {
|
|
251
301
|
const timeout = Number(value ?? DEFAULT_MCP_TIMEOUT_MS);
|
|
252
302
|
return Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_MCP_TIMEOUT_MS;
|
|
@@ -276,6 +326,47 @@ function quoteTomlString(value) {
|
|
|
276
326
|
return JSON.stringify(String(value));
|
|
277
327
|
}
|
|
278
328
|
|
|
279
|
-
function
|
|
280
|
-
|
|
329
|
+
function parseTomlTableHeader(line) {
|
|
330
|
+
const match = /^\s*\[([^\][\r\n]+)]\s*(?:#.*)?$/.exec(line);
|
|
331
|
+
return match ? match[1] : "";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function normalizeTomlDottedName(value) {
|
|
335
|
+
return splitTomlDottedName(value).join(".");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function splitTomlDottedName(value) {
|
|
339
|
+
const parts = [];
|
|
340
|
+
let current = "";
|
|
341
|
+
let quoted = false;
|
|
342
|
+
let quote = "";
|
|
343
|
+
let escaped = false;
|
|
344
|
+
for (const char of String(value || "").trim()) {
|
|
345
|
+
if (quoted) {
|
|
346
|
+
if (escaped) {
|
|
347
|
+
current += char;
|
|
348
|
+
escaped = false;
|
|
349
|
+
} else if (char === "\\" && quote === "\"") {
|
|
350
|
+
escaped = true;
|
|
351
|
+
} else if (char === quote) {
|
|
352
|
+
quoted = false;
|
|
353
|
+
} else {
|
|
354
|
+
current += char;
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (char === "\"" || char === "'") {
|
|
359
|
+
quoted = true;
|
|
360
|
+
quote = char;
|
|
361
|
+
} else if (char === ".") {
|
|
362
|
+
parts.push(current.trim());
|
|
363
|
+
current = "";
|
|
364
|
+
} else if (!/\s/.test(char)) {
|
|
365
|
+
current += char;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (current || parts.length > 0) {
|
|
369
|
+
parts.push(current.trim());
|
|
370
|
+
}
|
|
371
|
+
return parts.filter(Boolean);
|
|
281
372
|
}
|