artifacty 0.7.0 → 0.8.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/README.md +8 -0
- package/docs/integrations.md +10 -0
- package/docs/release-checklist.md +7 -0
- package/package.json +1 -1
- package/src/cli.js +24 -1
- package/src/lib/doctor.js +214 -0
package/README.md
CHANGED
|
@@ -82,6 +82,13 @@ artifacty install all
|
|
|
82
82
|
artifacty check
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Run diagnostics for the local runtime, store, server, service definitions, and MCP discovery:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
artifacty doctor
|
|
89
|
+
artifacty doctor --skip-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
85
92
|
Use `artifacty install codex --timeout 30000` or
|
|
86
93
|
`artifacty install gemini --timeout 30000` to tune supported MCP client timeouts.
|
|
87
94
|
|
|
@@ -190,6 +197,7 @@ Operational commands:
|
|
|
190
197
|
|
|
191
198
|
```bash
|
|
192
199
|
artifacty audit --limit 20
|
|
200
|
+
artifacty doctor
|
|
193
201
|
artifacty index rebuild
|
|
194
202
|
artifacty integrity
|
|
195
203
|
artifacty backup
|
package/docs/integrations.md
CHANGED
|
@@ -37,6 +37,15 @@ The lifecycle commands are intended to be cross-platform:
|
|
|
37
37
|
- Windows: `start` hides the child console window, and `stop` uses `taskkill /PID <pid> /T`, falling back to `/F` when Windows requires forceful termination. `--force` uses `/F` immediately.
|
|
38
38
|
- All platforms: `status` combines the managed pid file with the HTTP `/health` endpoint, so a stale pid alone is not reported as healthy.
|
|
39
39
|
|
|
40
|
+
Run diagnostics when setup behaves unexpectedly:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node src/cli.js doctor
|
|
44
|
+
node src/cli.js doctor --skip-mcp
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`doctor` checks Node version support, local exposure settings, store integrity, managed server health, service definition rendering, and MCP discovery. A stopped server is reported as a warning, not a failure.
|
|
48
|
+
|
|
40
49
|
For login/startup persistence, use the operating system's service manager. Artifacty's `service` command can generate macOS LaunchAgent, Linux systemd user-unit, and Windows Task Scheduler definitions.
|
|
41
50
|
|
|
42
51
|
Create artifacts directly in the browser at `http://127.0.0.1:8787/new`.
|
|
@@ -85,6 +94,7 @@ node src/cli.js check
|
|
|
85
94
|
- `--dry-run` returns the generated config without writing it.
|
|
86
95
|
- `--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.
|
|
87
96
|
- `check` starts the local MCP server and verifies required tools, resources, and prompts through MCP discovery methods.
|
|
97
|
+
- `doctor` combines MCP discovery with runtime, storage, server, and service diagnostics.
|
|
88
98
|
|
|
89
99
|
## Client Compatibility Matrix
|
|
90
100
|
|
|
@@ -10,6 +10,12 @@ npm run release:check
|
|
|
10
10
|
|
|
11
11
|
This runs syntax checks, the full Node test suite, and a local smoke test that starts the HTTP server with token auth enabled, creates an artifact, verifies secret blocking, reads audit logs, writes a backup, and checks MCP tool/resource/prompt discovery.
|
|
12
12
|
|
|
13
|
+
Run the local diagnostics pass:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
artifacty doctor
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
## Packaging
|
|
14
20
|
|
|
15
21
|
- Confirm `package.json` `version` and `files` are intentional.
|
|
@@ -47,4 +53,5 @@ This runs syntax checks, the full Node test suite, and a local smoke test that s
|
|
|
47
53
|
|
|
48
54
|
- Export a backup before upgrades: `artifacty backup`.
|
|
49
55
|
- Confirm `artifacty audit --limit 20` shows recent create/update/read/archive events.
|
|
56
|
+
- Confirm `artifacty doctor` reports no failures. A stopped server warning is acceptable when intentionally checking an offline store.
|
|
50
57
|
- For background service installs, dry-run first: `artifacty service install --dry-run`. Use `--platform macos|linux|windows` to review another OS definition.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ import { serviceCommand } from "./lib/service.js";
|
|
|
22
22
|
import { backgroundStatus, startBackgroundServer, stopBackgroundServer } from "./lib/background.js";
|
|
23
23
|
import { resolvePublicBaseUrl } from "./lib/server-state.js";
|
|
24
24
|
import { generateToken } from "./lib/token.js";
|
|
25
|
+
import { runDoctor } from "./lib/doctor.js";
|
|
25
26
|
import { startServer } from "./server.js";
|
|
26
27
|
|
|
27
28
|
const PACKAGE_ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
@@ -105,6 +106,27 @@ async function main() {
|
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
if (command === "doctor") {
|
|
110
|
+
const result = await runDoctor({
|
|
111
|
+
packageRoot: PACKAGE_ROOT,
|
|
112
|
+
serverPath: options.serverPath,
|
|
113
|
+
url: options.url,
|
|
114
|
+
home: options.home,
|
|
115
|
+
host: options.host,
|
|
116
|
+
port: options.port,
|
|
117
|
+
apiToken: options.apiToken,
|
|
118
|
+
shareMode: options.shareMode,
|
|
119
|
+
allowSecrets: options.allowSecrets,
|
|
120
|
+
timeout: options.timeout,
|
|
121
|
+
skipMcp: options.skipMcp
|
|
122
|
+
});
|
|
123
|
+
printJson(result);
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
108
130
|
if (command === "publish") {
|
|
109
131
|
const content = await readContent(options);
|
|
110
132
|
const artifact = await createArtifact(store, {
|
|
@@ -324,7 +346,7 @@ function parseArgs(args) {
|
|
|
324
346
|
}
|
|
325
347
|
|
|
326
348
|
const key = arg.slice(2);
|
|
327
|
-
if (key === "raw" || key === "dry-run" || key === "trust" || key === "include-archived" || key === "allow-secrets" || key === "generate-token" || key === "detach" || key === "foreground" || key === "force") {
|
|
349
|
+
if (key === "raw" || key === "dry-run" || key === "trust" || key === "include-archived" || key === "allow-secrets" || key === "generate-token" || key === "detach" || key === "foreground" || key === "force" || key === "skip-mcp") {
|
|
328
350
|
options[toCamelCase(key)] = true;
|
|
329
351
|
continue;
|
|
330
352
|
}
|
|
@@ -412,6 +434,7 @@ Usage:
|
|
|
412
434
|
artifacty start [--host 127.0.0.1] [--port 8787] [--home ~/.artifacty] [--api-token token] [--generate-token] [--timeout 30000]
|
|
413
435
|
artifacty status [--home ~/.artifacty]
|
|
414
436
|
artifacty stop [--home ~/.artifacty] [--timeout 30000] [--force]
|
|
437
|
+
artifacty doctor [--home ~/.artifacty] [--skip-mcp] [--timeout 5000]
|
|
415
438
|
artifacty publish --title <title> (--file <path> | --content <text>) [--format html|markdown|text|json|code|svg|mermaid|react] [--source agent] [--tag tag]
|
|
416
439
|
artifacty import --agent claude|codex|gemini|copilot|cursor|auto (--file <path> | --content <text>) [--title <title>] [--format html|markdown|text|json|code|svg|mermaid|react] [--tag tag]
|
|
417
440
|
artifacty install claude|codex|gemini|copilot|cursor|all [--dry-run] [--config <path>] [--server-path <path>] [--url http://127.0.0.1:8787] [--timeout 30000]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { arch, platform } from "node:os";
|
|
4
|
+
import { backgroundStatus } from "./background.js";
|
|
5
|
+
import { checkMcpTools } from "./check.js";
|
|
6
|
+
import { securityConfig, validateServerExposure, exposureWarning } from "./security.js";
|
|
7
|
+
import { serviceCommand } from "./service.js";
|
|
8
|
+
import { checkStoreIntegrity, createStore } from "./storage.js";
|
|
9
|
+
|
|
10
|
+
const MIN_NODE_VERSION = "22.5.0";
|
|
11
|
+
|
|
12
|
+
export async function runDoctor(options = {}) {
|
|
13
|
+
const packageRoot = path.resolve(options.packageRoot || process.cwd());
|
|
14
|
+
const store = createStore({ home: options.home });
|
|
15
|
+
const checks = [];
|
|
16
|
+
|
|
17
|
+
await collect(checks, "runtime", () => runtimeCheck(packageRoot));
|
|
18
|
+
await collect(checks, "security", () => securityCheck(options));
|
|
19
|
+
await collect(checks, "storage", () => storageCheck(store));
|
|
20
|
+
await collect(checks, "server", () => serverCheck(store));
|
|
21
|
+
await collect(checks, "service", () => serviceCheck(options));
|
|
22
|
+
if (options.skipMcp) {
|
|
23
|
+
checks.push({
|
|
24
|
+
name: "mcp",
|
|
25
|
+
status: "skip",
|
|
26
|
+
message: "MCP discovery skipped by --skip-mcp"
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
await collect(checks, "mcp", () => mcpCheck(packageRoot, options));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const failures = checks.filter((check) => check.status === "fail");
|
|
33
|
+
const warnings = checks.filter((check) => check.status === "warn");
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
ok: failures.length === 0,
|
|
37
|
+
checkedAt: new Date().toISOString(),
|
|
38
|
+
version: await packageVersion(packageRoot),
|
|
39
|
+
home: store.home,
|
|
40
|
+
platform: process.platform,
|
|
41
|
+
checks,
|
|
42
|
+
failures: failures.map(({ name, message }) => ({ name, message })),
|
|
43
|
+
warnings: warnings.map(({ name, message }) => ({ name, message }))
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function collect(checks, name, fn) {
|
|
48
|
+
try {
|
|
49
|
+
checks.push({ name, ...(await fn()) });
|
|
50
|
+
} catch (error) {
|
|
51
|
+
checks.push({
|
|
52
|
+
name,
|
|
53
|
+
status: "fail",
|
|
54
|
+
message: error.message,
|
|
55
|
+
error: {
|
|
56
|
+
name: error.name,
|
|
57
|
+
code: error.code
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runtimeCheck(packageRoot) {
|
|
64
|
+
const version = await packageVersion(packageRoot);
|
|
65
|
+
const supported = compareVersions(process.versions.node, MIN_NODE_VERSION) >= 0;
|
|
66
|
+
return {
|
|
67
|
+
status: supported ? "pass" : "fail",
|
|
68
|
+
message: supported
|
|
69
|
+
? `Node ${process.versions.node} satisfies >=${MIN_NODE_VERSION}`
|
|
70
|
+
: `Node ${process.versions.node} is below required >=${MIN_NODE_VERSION}`,
|
|
71
|
+
data: {
|
|
72
|
+
artifactyVersion: version,
|
|
73
|
+
nodeVersion: process.versions.node,
|
|
74
|
+
requiredNodeVersion: `>=${MIN_NODE_VERSION}`,
|
|
75
|
+
platform: platform(),
|
|
76
|
+
arch: arch()
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function securityCheck(options = {}) {
|
|
82
|
+
const host = options.host || process.env.ARTIFACTY_HOST || "127.0.0.1";
|
|
83
|
+
const config = securityConfig(options);
|
|
84
|
+
validateServerExposure({ host, config });
|
|
85
|
+
const warning = exposureWarning({ host, config });
|
|
86
|
+
if (warning) {
|
|
87
|
+
return {
|
|
88
|
+
status: "warn",
|
|
89
|
+
message: warning,
|
|
90
|
+
data: {
|
|
91
|
+
host,
|
|
92
|
+
shareMode: config.shareMode,
|
|
93
|
+
hasApiToken: Boolean(config.apiToken)
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
status: "pass",
|
|
99
|
+
message: "Server exposure settings are local-first",
|
|
100
|
+
data: {
|
|
101
|
+
host,
|
|
102
|
+
shareMode: config.shareMode,
|
|
103
|
+
hasApiToken: Boolean(config.apiToken)
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function storageCheck(store) {
|
|
109
|
+
const integrity = await checkStoreIntegrity(store);
|
|
110
|
+
return {
|
|
111
|
+
status: integrity.ok ? "pass" : "fail",
|
|
112
|
+
message: integrity.ok
|
|
113
|
+
? `Store is consistent with ${integrity.artifactCount} artifacts and ${integrity.versionCount} versions`
|
|
114
|
+
: "Store integrity check found missing, changed, orphaned, or inconsistent files",
|
|
115
|
+
data: integrity
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function serverCheck(store) {
|
|
120
|
+
const status = await backgroundStatus({ home: store.home });
|
|
121
|
+
if (status.running) {
|
|
122
|
+
return {
|
|
123
|
+
status: "pass",
|
|
124
|
+
message: `Managed server is healthy at ${status.url}`,
|
|
125
|
+
data: status
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (status.processRunning || status.pidFileExists) {
|
|
129
|
+
return {
|
|
130
|
+
status: "fail",
|
|
131
|
+
message: "Recorded server process or pid file exists but health check is not passing",
|
|
132
|
+
data: status
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
status: "warn",
|
|
137
|
+
message: "No managed Artifacty server is currently running",
|
|
138
|
+
data: status
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function serviceCheck(options = {}) {
|
|
143
|
+
const definitions = [];
|
|
144
|
+
for (const action of ["plist", "unit", "task"]) {
|
|
145
|
+
const result = await serviceCommand(action, {
|
|
146
|
+
projectDir: options.packageRoot || process.cwd(),
|
|
147
|
+
serverPath: options.serverPath,
|
|
148
|
+
host: options.host,
|
|
149
|
+
port: options.port,
|
|
150
|
+
home: options.home,
|
|
151
|
+
apiToken: options.apiToken,
|
|
152
|
+
shareMode: options.shareMode,
|
|
153
|
+
allowSecrets: options.allowSecrets,
|
|
154
|
+
dryRun: true
|
|
155
|
+
});
|
|
156
|
+
definitions.push({
|
|
157
|
+
action,
|
|
158
|
+
platform: result.platform,
|
|
159
|
+
path: result.path,
|
|
160
|
+
contentBytes: Buffer.byteLength(result.content || "", "utf8")
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
status: "pass",
|
|
165
|
+
message: "Service definitions render for macOS, Linux, and Windows",
|
|
166
|
+
data: { definitions }
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function mcpCheck(packageRoot, options = {}) {
|
|
171
|
+
const result = await checkMcpTools({
|
|
172
|
+
projectDir: packageRoot,
|
|
173
|
+
serverPath: options.serverPath,
|
|
174
|
+
url: options.url,
|
|
175
|
+
home: options.home,
|
|
176
|
+
timeout: options.timeout
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
status: result.ok ? "pass" : "fail",
|
|
180
|
+
message: result.ok
|
|
181
|
+
? `MCP discovery found ${result.toolCount} tools, ${result.resourceCount} resources, and ${result.promptCount} prompts`
|
|
182
|
+
: "MCP discovery is missing required tools, resources, or prompts",
|
|
183
|
+
data: result
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function packageVersion(packageRoot) {
|
|
188
|
+
const file = path.join(packageRoot, "package.json");
|
|
189
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
190
|
+
return parsed.version;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function compareVersions(left, right) {
|
|
194
|
+
const leftParts = versionParts(left);
|
|
195
|
+
const rightParts = versionParts(right);
|
|
196
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
197
|
+
const leftPart = leftParts[index] || 0;
|
|
198
|
+
const rightPart = rightParts[index] || 0;
|
|
199
|
+
if (leftPart > rightPart) {
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
if (leftPart < rightPart) {
|
|
203
|
+
return -1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function versionParts(value) {
|
|
210
|
+
return String(value)
|
|
211
|
+
.split(".")
|
|
212
|
+
.map((part) => Number.parseInt(part, 10))
|
|
213
|
+
.filter((part) => Number.isFinite(part));
|
|
214
|
+
}
|