@hasna/machines 0.0.34 → 0.0.35
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 +17 -0
- package/dist/cli/index.js +310 -24
- package/dist/commands/doctor.d.ts +23 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/consumer.js +188 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +322 -24
- package/dist/manifests.d.ts +22 -1
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +303 -19
- package/dist/redaction.d.ts +11 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/topology.d.ts.map +1 -1
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/consumer-conformance.mjs +3 -2
package/README.md
CHANGED
|
@@ -36,9 +36,18 @@ machines manifest validate
|
|
|
36
36
|
machines manifest list
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
Public packages should keep private fleet state behind an opaque source/ref
|
|
40
|
+
boundary. `HASNA_MACHINES_PRIVATE_MANIFEST_REF` (or
|
|
41
|
+
`MACHINES_PRIVATE_MANIFEST_REF`) may point at a private backend, but
|
|
42
|
+
open-machines only reports the redacted ref and falls back to the local
|
|
43
|
+
`machines.json` unless a caller supplies a manifest adapter. The adapter
|
|
44
|
+
contract is backend-agnostic and lives in the package root exports; it does not
|
|
45
|
+
pull in secrets managers, storage SDKs, or org-specific fleet internals.
|
|
46
|
+
|
|
39
47
|
## Provision and reconcile
|
|
40
48
|
|
|
41
49
|
```bash
|
|
50
|
+
machines setup --machine linux-dev-01
|
|
42
51
|
machines setup --machine linux-dev-01 --json
|
|
43
52
|
machines setup --machine linux-dev-01 --apply --yes
|
|
44
53
|
machines sync --machine linux-dev-01 --json
|
|
@@ -47,6 +56,14 @@ machines doctor --machine linux-dev-01
|
|
|
47
56
|
machines self-test
|
|
48
57
|
```
|
|
49
58
|
|
|
59
|
+
`machines setup` is a dry-run plan by default. The generated playbook favors
|
|
60
|
+
idempotent operations (`mkdir -p`, command-existence guards, package-manager
|
|
61
|
+
installs) and only executes when both `--apply` and `--yes` are provided.
|
|
62
|
+
`doctor --json` includes public-safe source/ref diagnostics plus optional
|
|
63
|
+
adapter hook results for secrets, configs, monitors, repos, MCPs, and shield
|
|
64
|
+
checks. When no adapter is configured, those checks report a skipped fallback
|
|
65
|
+
instead of importing private dependencies.
|
|
66
|
+
|
|
50
67
|
## Topology SDK
|
|
51
68
|
|
|
52
69
|
`@hasna/machines` exposes a compact consumer SDK for other open-core packages
|
package/dist/cli/index.js
CHANGED
|
@@ -7092,6 +7092,104 @@ var coerce = {
|
|
|
7092
7092
|
var NEVER = INVALID;
|
|
7093
7093
|
// src/manifests.ts
|
|
7094
7094
|
init_paths();
|
|
7095
|
+
|
|
7096
|
+
// src/redaction.ts
|
|
7097
|
+
var REDACTED_VALUE = "[redacted]";
|
|
7098
|
+
var SENSITIVE_KEY_PATTERN = /(password|passwd|token|credential|private[_-]?key|privateKey|api[_-]?key|github.*key|pem|secret)/i;
|
|
7099
|
+
var SECRET_REFERENCE_KEY_PATTERN = /(secret(ref(erence)?|key)?|secretRef|secretKey)$/i;
|
|
7100
|
+
var SENSITIVE_VALUE_PATTERNS = [
|
|
7101
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
7102
|
+
/\bghp_[A-Za-z0-9_]{20,}\b/,
|
|
7103
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
7104
|
+
/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/,
|
|
7105
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
7106
|
+
/\bsk-[A-Za-z0-9_-]{20,}\b/
|
|
7107
|
+
];
|
|
7108
|
+
function isSensitiveKey(key) {
|
|
7109
|
+
return SENSITIVE_KEY_PATTERN.test(key);
|
|
7110
|
+
}
|
|
7111
|
+
function isSecretReferenceKey(key) {
|
|
7112
|
+
return SECRET_REFERENCE_KEY_PATTERN.test(key);
|
|
7113
|
+
}
|
|
7114
|
+
function looksSensitiveString(value) {
|
|
7115
|
+
return SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(value));
|
|
7116
|
+
}
|
|
7117
|
+
function isRecord(value) {
|
|
7118
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7119
|
+
}
|
|
7120
|
+
function redactPath(value) {
|
|
7121
|
+
return value.replace(/\/home\/[^/\s]+/g, "/home/<user>").replace(/\/Users\/[^/\s]+/g, "/Users/<user>").replace(/[A-Za-z]:\\Users\\[^\\\s]+/g, "C:\\Users\\<user>");
|
|
7122
|
+
}
|
|
7123
|
+
function redactPrivateRef(value) {
|
|
7124
|
+
const trimmed = value.trim();
|
|
7125
|
+
const scheme = trimmed.match(/^([a-z][a-z0-9+.-]*:\/\/)/i);
|
|
7126
|
+
if (scheme)
|
|
7127
|
+
return `${scheme[1]}<redacted>`;
|
|
7128
|
+
const colon = trimmed.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
7129
|
+
if (colon)
|
|
7130
|
+
return `${colon[1]}:<redacted>`;
|
|
7131
|
+
return "<private-manifest-ref:redacted>";
|
|
7132
|
+
}
|
|
7133
|
+
function redactIdentifier(value) {
|
|
7134
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80) || "adapter";
|
|
7135
|
+
}
|
|
7136
|
+
function redactSensitiveValue(value, key = "") {
|
|
7137
|
+
if (typeof value === "string") {
|
|
7138
|
+
if (isSensitiveKey(key) && !(isSecretReferenceKey(key) && !looksSensitiveString(value))) {
|
|
7139
|
+
return REDACTED_VALUE;
|
|
7140
|
+
}
|
|
7141
|
+
if (looksSensitiveString(value))
|
|
7142
|
+
return REDACTED_VALUE;
|
|
7143
|
+
return redactPath(value);
|
|
7144
|
+
}
|
|
7145
|
+
if (Array.isArray(value)) {
|
|
7146
|
+
return value.map((entry) => redactSensitiveValue(entry, key));
|
|
7147
|
+
}
|
|
7148
|
+
if (isRecord(value)) {
|
|
7149
|
+
const redacted = {};
|
|
7150
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
7151
|
+
redacted[entryKey] = redactSensitiveValue(entryValue, entryKey);
|
|
7152
|
+
}
|
|
7153
|
+
return redacted;
|
|
7154
|
+
}
|
|
7155
|
+
return value;
|
|
7156
|
+
}
|
|
7157
|
+
function publicMetadataKeys(metadata) {
|
|
7158
|
+
return Object.keys(metadata ?? {}).filter((key) => !isSensitiveKey(key)).sort();
|
|
7159
|
+
}
|
|
7160
|
+
function redactMetadata(metadata) {
|
|
7161
|
+
return redactSensitiveValue(metadata ?? {});
|
|
7162
|
+
}
|
|
7163
|
+
function redactManifestForDiagnostics(machine) {
|
|
7164
|
+
const metadata = redactMetadata(machine.metadata);
|
|
7165
|
+
for (const key of ["user", "username", "login"]) {
|
|
7166
|
+
if (typeof metadata[key] === "string")
|
|
7167
|
+
metadata[key] = REDACTED_VALUE;
|
|
7168
|
+
}
|
|
7169
|
+
return {
|
|
7170
|
+
id: machine.id,
|
|
7171
|
+
hostname: machine.hostname ? REDACTED_VALUE : undefined,
|
|
7172
|
+
sshAddress: machine.sshAddress ? REDACTED_VALUE : undefined,
|
|
7173
|
+
tailscaleName: machine.tailscaleName ? REDACTED_VALUE : undefined,
|
|
7174
|
+
platform: machine.platform,
|
|
7175
|
+
connection: machine.connection,
|
|
7176
|
+
workspacePath: redactPath(machine.workspacePath),
|
|
7177
|
+
bunPath: machine.bunPath ? redactPath(machine.bunPath) : undefined,
|
|
7178
|
+
tags: machine.tags ?? [],
|
|
7179
|
+
metadata,
|
|
7180
|
+
packages: machine.packages?.map((pkg) => ({ ...pkg })),
|
|
7181
|
+
apps: machine.apps?.map((app) => ({ ...app })),
|
|
7182
|
+
files: machine.files?.map((file) => ({
|
|
7183
|
+
...file,
|
|
7184
|
+
source: redactPath(file.source),
|
|
7185
|
+
target: redactPath(file.target)
|
|
7186
|
+
}))
|
|
7187
|
+
};
|
|
7188
|
+
}
|
|
7189
|
+
|
|
7190
|
+
// src/manifests.ts
|
|
7191
|
+
var PRIVATE_MANIFEST_REF_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_REF";
|
|
7192
|
+
var PRIVATE_MANIFEST_BACKEND_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_BACKEND";
|
|
7095
7193
|
var packageSchema = exports_external.object({
|
|
7096
7194
|
name: exports_external.string(),
|
|
7097
7195
|
manager: exports_external.enum(["bun", "brew", "apt", "custom"]).optional(),
|
|
@@ -7144,6 +7242,42 @@ function normalizePlatform() {
|
|
|
7144
7242
|
function normalizeMachines(machines) {
|
|
7145
7243
|
return [...machines].sort((left, right) => left.id.localeCompare(right.id));
|
|
7146
7244
|
}
|
|
7245
|
+
function inferPrivateBackend(rawRef, explicitBackend) {
|
|
7246
|
+
if (explicitBackend?.trim())
|
|
7247
|
+
return explicitBackend.trim();
|
|
7248
|
+
const scheme = rawRef.trim().match(/^([a-z][a-z0-9+.-]*)(?::\/\/|:)/i);
|
|
7249
|
+
return scheme?.[1] ?? null;
|
|
7250
|
+
}
|
|
7251
|
+
function fileSourceRef(path) {
|
|
7252
|
+
return {
|
|
7253
|
+
kind: "file",
|
|
7254
|
+
ref: redactPath(path),
|
|
7255
|
+
backend: "file",
|
|
7256
|
+
private: false,
|
|
7257
|
+
publicSafe: true
|
|
7258
|
+
};
|
|
7259
|
+
}
|
|
7260
|
+
function privateSourceRef(rawRef, backend) {
|
|
7261
|
+
return {
|
|
7262
|
+
kind: "private-ref",
|
|
7263
|
+
ref: redactPrivateRef(rawRef),
|
|
7264
|
+
backend: inferPrivateBackend(rawRef, backend),
|
|
7265
|
+
private: true,
|
|
7266
|
+
publicSafe: true
|
|
7267
|
+
};
|
|
7268
|
+
}
|
|
7269
|
+
function privateRefFromOptions(options) {
|
|
7270
|
+
const env2 = options.env ?? process.env;
|
|
7271
|
+
return options.privateRef?.trim() || env2[PRIVATE_MANIFEST_REF_ENV]?.trim() || env2["MACHINES_PRIVATE_MANIFEST_REF"]?.trim() || null;
|
|
7272
|
+
}
|
|
7273
|
+
function getManifestSourceRef(options = {}) {
|
|
7274
|
+
const rawPrivateRef = privateRefFromOptions(options);
|
|
7275
|
+
if (rawPrivateRef) {
|
|
7276
|
+
const env2 = options.env ?? process.env;
|
|
7277
|
+
return privateSourceRef(rawPrivateRef, options.privateBackend ?? env2[PRIVATE_MANIFEST_BACKEND_ENV]);
|
|
7278
|
+
}
|
|
7279
|
+
return fileSourceRef(options.path ?? getManifestPath());
|
|
7280
|
+
}
|
|
7147
7281
|
function getDefaultManifest() {
|
|
7148
7282
|
return {
|
|
7149
7283
|
version: 1,
|
|
@@ -7158,6 +7292,53 @@ function readManifest(path = getManifestPath()) {
|
|
|
7158
7292
|
const raw = JSON.parse(readFileSync2(path, "utf8"));
|
|
7159
7293
|
return fleetSchema.parse(raw);
|
|
7160
7294
|
}
|
|
7295
|
+
function readManifestWithSource(options = {}) {
|
|
7296
|
+
const path = options.path ?? getManifestPath();
|
|
7297
|
+
const source = getManifestSourceRef(options);
|
|
7298
|
+
const warnings = [];
|
|
7299
|
+
if (source.kind === "private-ref") {
|
|
7300
|
+
const rawRef = privateRefFromOptions(options);
|
|
7301
|
+
if (rawRef && options.adapter) {
|
|
7302
|
+
try {
|
|
7303
|
+
const manifest2 = options.adapter.readManifest({ source, rawRef });
|
|
7304
|
+
if (manifest2) {
|
|
7305
|
+
return {
|
|
7306
|
+
manifest: fleetSchema.parse(manifest2),
|
|
7307
|
+
info: {
|
|
7308
|
+
source,
|
|
7309
|
+
loadedFrom: "private-ref",
|
|
7310
|
+
warnings
|
|
7311
|
+
}
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
warnings.push(`private_manifest_adapter_empty:${redactIdentifier(options.adapter.id)}`);
|
|
7315
|
+
} catch (error) {
|
|
7316
|
+
warnings.push(`private_manifest_adapter_failed:${redactIdentifier(options.adapter.id)}`);
|
|
7317
|
+
}
|
|
7318
|
+
} else {
|
|
7319
|
+
warnings.push("private_manifest_ref_without_adapter");
|
|
7320
|
+
}
|
|
7321
|
+
const fallbackSource = fileSourceRef(path);
|
|
7322
|
+
const manifest = readManifest(path);
|
|
7323
|
+
return {
|
|
7324
|
+
manifest,
|
|
7325
|
+
info: {
|
|
7326
|
+
source,
|
|
7327
|
+
loadedFrom: existsSync3(path) ? "fallback" : "default",
|
|
7328
|
+
fallbackSource,
|
|
7329
|
+
warnings
|
|
7330
|
+
}
|
|
7331
|
+
};
|
|
7332
|
+
}
|
|
7333
|
+
return {
|
|
7334
|
+
manifest: readManifest(path),
|
|
7335
|
+
info: {
|
|
7336
|
+
source,
|
|
7337
|
+
loadedFrom: existsSync3(path) ? "file" : "default",
|
|
7338
|
+
warnings
|
|
7339
|
+
}
|
|
7340
|
+
};
|
|
7341
|
+
}
|
|
7161
7342
|
function validateManifest(path = getManifestPath()) {
|
|
7162
7343
|
return readManifest(path);
|
|
7163
7344
|
}
|
|
@@ -7444,7 +7625,7 @@ function buildEntry(input) {
|
|
|
7444
7625
|
},
|
|
7445
7626
|
route_hints: hints,
|
|
7446
7627
|
tags: manifest?.tags ?? [],
|
|
7447
|
-
metadata: manifest?.metadata
|
|
7628
|
+
metadata: redactMetadata(manifest?.metadata)
|
|
7448
7629
|
};
|
|
7449
7630
|
}
|
|
7450
7631
|
function discoverMachineTopology(options = {}) {
|
|
@@ -7682,7 +7863,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
7682
7863
|
warnings
|
|
7683
7864
|
};
|
|
7684
7865
|
}
|
|
7685
|
-
function
|
|
7866
|
+
function isRecord2(value) {
|
|
7686
7867
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7687
7868
|
}
|
|
7688
7869
|
function metadataString(metadata, keys) {
|
|
@@ -7712,13 +7893,13 @@ function metadataStringArray(metadata, keys) {
|
|
|
7712
7893
|
function readMappedPath(input) {
|
|
7713
7894
|
for (const containerName of input.containers) {
|
|
7714
7895
|
const container = input.metadata[containerName];
|
|
7715
|
-
if (!
|
|
7896
|
+
if (!isRecord2(container))
|
|
7716
7897
|
continue;
|
|
7717
7898
|
for (const key of input.keys) {
|
|
7718
7899
|
const value = container[key];
|
|
7719
7900
|
if (typeof value === "string" && value.trim())
|
|
7720
7901
|
return value.trim();
|
|
7721
|
-
if (
|
|
7902
|
+
if (isRecord2(value)) {
|
|
7722
7903
|
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
7723
7904
|
if (path)
|
|
7724
7905
|
return path;
|
|
@@ -7972,7 +8153,7 @@ function primaryMachine(machine, projectId, primaryMachineId) {
|
|
|
7972
8153
|
return machine.tags.includes("primary");
|
|
7973
8154
|
}
|
|
7974
8155
|
function metadataKeysForDiagnostics(metadata) {
|
|
7975
|
-
return
|
|
8156
|
+
return publicMetadataKeys(metadata);
|
|
7976
8157
|
}
|
|
7977
8158
|
function resolveMachineWorkspace(options) {
|
|
7978
8159
|
const now = options.now ?? new Date;
|
|
@@ -9578,20 +9759,20 @@ function getStatus() {
|
|
|
9578
9759
|
|
|
9579
9760
|
// src/commands/workspace.ts
|
|
9580
9761
|
init_paths();
|
|
9581
|
-
function
|
|
9762
|
+
function isRecord3(value) {
|
|
9582
9763
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9583
9764
|
}
|
|
9584
9765
|
function cloneMetadata(metadata) {
|
|
9585
|
-
return
|
|
9766
|
+
return isRecord3(metadata) ? { ...metadata } : {};
|
|
9586
9767
|
}
|
|
9587
9768
|
function mappedPath(metadata, field, key) {
|
|
9588
9769
|
const container = metadata[field];
|
|
9589
|
-
if (!
|
|
9770
|
+
if (!isRecord3(container))
|
|
9590
9771
|
return null;
|
|
9591
9772
|
const value = container[key];
|
|
9592
9773
|
if (typeof value === "string" && value.trim())
|
|
9593
9774
|
return value.trim();
|
|
9594
|
-
if (
|
|
9775
|
+
if (isRecord3(value)) {
|
|
9595
9776
|
const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
|
|
9596
9777
|
if (typeof nested === "string" && nested.trim())
|
|
9597
9778
|
return nested.trim();
|
|
@@ -9600,7 +9781,7 @@ function mappedPath(metadata, field, key) {
|
|
|
9600
9781
|
}
|
|
9601
9782
|
function writeMappedPath(metadata, field, key, path) {
|
|
9602
9783
|
const existing = metadata[field];
|
|
9603
|
-
const container =
|
|
9784
|
+
const container = isRecord3(existing) ? { ...existing } : {};
|
|
9604
9785
|
container[key] = path;
|
|
9605
9786
|
metadata[field] = container;
|
|
9606
9787
|
}
|
|
@@ -9970,12 +10151,28 @@ function checkMachineCompatibility(options = {}) {
|
|
|
9970
10151
|
|
|
9971
10152
|
// src/commands/doctor.ts
|
|
9972
10153
|
init_db();
|
|
9973
|
-
|
|
9974
|
-
|
|
10154
|
+
var DOCTOR_OPTIONAL_ADAPTER_DOMAINS = ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
|
|
10155
|
+
function makeCheck2(id, status, summary, detail, extra = {}) {
|
|
10156
|
+
const { data, ...rest } = extra;
|
|
10157
|
+
return {
|
|
10158
|
+
...rest,
|
|
10159
|
+
id,
|
|
10160
|
+
status,
|
|
10161
|
+
summary,
|
|
10162
|
+
detail,
|
|
10163
|
+
data: data ? redactSensitiveValue(data) : undefined
|
|
10164
|
+
};
|
|
9975
10165
|
}
|
|
9976
10166
|
function parseKeyValueOutput(stdout) {
|
|
9977
|
-
|
|
9978
|
-
|
|
10167
|
+
const result = {};
|
|
10168
|
+
for (const line of stdout.trim().split(`
|
|
10169
|
+
`)) {
|
|
10170
|
+
const index = line.indexOf("=");
|
|
10171
|
+
if (index <= 0)
|
|
10172
|
+
continue;
|
|
10173
|
+
result[line.slice(0, index)] = line.slice(index + 1);
|
|
10174
|
+
}
|
|
10175
|
+
return result;
|
|
9979
10176
|
}
|
|
9980
10177
|
function buildDoctorCommand() {
|
|
9981
10178
|
return [
|
|
@@ -9983,9 +10180,11 @@ function buildDoctorCommand() {
|
|
|
9983
10180
|
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
9984
10181
|
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
9985
10182
|
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
10183
|
+
`printf 'data_dir=%s\\n' "$data_dir"`,
|
|
9986
10184
|
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
9987
10185
|
`printf 'db_path=%s\\n' "$db_path"`,
|
|
9988
10186
|
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
10187
|
+
`printf 'data_dir_exists=%s\\n' "$(test -d "$data_dir" && printf yes || printf no)"`,
|
|
9989
10188
|
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
9990
10189
|
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
9991
10190
|
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
@@ -9996,28 +10195,115 @@ function buildDoctorCommand() {
|
|
|
9996
10195
|
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
9997
10196
|
].join("; ");
|
|
9998
10197
|
}
|
|
9999
|
-
function
|
|
10000
|
-
|
|
10198
|
+
function fallbackAdapterCheck(domain) {
|
|
10199
|
+
return makeCheck2(`${domain}-adapter`, "ok", `Optional ${domain} adapter`, `No ${domain} adapter configured; skipped optional private integration check.`, {
|
|
10200
|
+
optional: true,
|
|
10201
|
+
source: "open-machines",
|
|
10202
|
+
data: { configured: false, fallback: true }
|
|
10203
|
+
});
|
|
10204
|
+
}
|
|
10205
|
+
function sanitizeAdapterCheck(check, domain, adapterId) {
|
|
10206
|
+
const safeAdapterId = redactIdentifier(adapterId);
|
|
10207
|
+
return makeCheck2(check.id.startsWith(`${domain}-`) || check.id.startsWith(`${domain}:`) ? check.id : `${domain}:${check.id}`, check.status, check.summary, String(redactSensitiveValue(check.detail)), {
|
|
10208
|
+
...check,
|
|
10209
|
+
optional: check.optional ?? true,
|
|
10210
|
+
source: check.source ? String(redactSensitiveValue(check.source)) : `adapter:${safeAdapterId}`,
|
|
10211
|
+
data: check.data ? redactSensitiveValue(check.data) : undefined
|
|
10212
|
+
});
|
|
10213
|
+
}
|
|
10214
|
+
function runOptionalAdapterChecks(context, adapters) {
|
|
10215
|
+
const checks = [];
|
|
10216
|
+
for (const domain of DOCTOR_OPTIONAL_ADAPTER_DOMAINS) {
|
|
10217
|
+
const adapter2 = adapters.find((candidate) => candidate.checks?.[domain]);
|
|
10218
|
+
const hook = adapter2?.checks?.[domain];
|
|
10219
|
+
if (!adapter2 || !hook) {
|
|
10220
|
+
checks.push(fallbackAdapterCheck(domain));
|
|
10221
|
+
continue;
|
|
10222
|
+
}
|
|
10223
|
+
try {
|
|
10224
|
+
const result = hook(context);
|
|
10225
|
+
const domainChecks = Array.isArray(result) ? result : result ? [result] : [fallbackAdapterCheck(domain)];
|
|
10226
|
+
checks.push(...domainChecks.map((check) => sanitizeAdapterCheck(check, domain, adapter2.id)));
|
|
10227
|
+
} catch {
|
|
10228
|
+
const safeAdapterId = redactIdentifier(adapter2.id);
|
|
10229
|
+
checks.push(makeCheck2(`${domain}-adapter`, "warn", `Optional ${domain} adapter failed`, "Adapter failed; details are intentionally hidden to avoid leaking private refs or credentials.", {
|
|
10230
|
+
optional: true,
|
|
10231
|
+
source: `adapter:${safeAdapterId}`,
|
|
10232
|
+
data: { adapter: safeAdapterId, fallback: true }
|
|
10233
|
+
}));
|
|
10234
|
+
}
|
|
10235
|
+
}
|
|
10236
|
+
return checks;
|
|
10237
|
+
}
|
|
10238
|
+
function runDoctor(machineId = getLocalMachineId(), options = {}) {
|
|
10239
|
+
const now = options.now ?? new Date;
|
|
10240
|
+
const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
|
|
10001
10241
|
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
10002
10242
|
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
10003
10243
|
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
10244
|
+
const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
|
|
10245
|
+
machineId,
|
|
10246
|
+
manifest,
|
|
10247
|
+
manifestSource,
|
|
10248
|
+
commandDetails: details,
|
|
10249
|
+
now
|
|
10250
|
+
}, options.adapters ?? []);
|
|
10004
10251
|
const checks = [
|
|
10005
|
-
makeCheck2("manifest-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10252
|
+
makeCheck2("manifest-source", manifestSource.warnings.length > 0 ? "warn" : "ok", "Manifest source boundary", `${manifestSource.source.kind}:${manifestSource.source.ref} loaded from ${manifestSource.loadedFrom}`, {
|
|
10253
|
+
data: {
|
|
10254
|
+
source: manifestSource.source,
|
|
10255
|
+
loadedFrom: manifestSource.loadedFrom,
|
|
10256
|
+
fallbackSource: manifestSource.fallbackSource,
|
|
10257
|
+
warnings: manifestSource.warnings
|
|
10258
|
+
},
|
|
10259
|
+
remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
|
|
10260
|
+
}),
|
|
10261
|
+
makeCheck2("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(redactManifestForDiagnostics(machineInManifest)) : `No manifest entry for ${machineId}`, {
|
|
10262
|
+
data: {
|
|
10263
|
+
declared: Boolean(machineInManifest),
|
|
10264
|
+
machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
|
|
10265
|
+
}
|
|
10266
|
+
}),
|
|
10267
|
+
makeCheck2("data-dir", details["data_dir_exists"] === "yes" ? "ok" : "warn", "Data directory check", `${redactPath(details["data_dir"] || "unknown")} ${details["data_dir_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
10268
|
+
data: {
|
|
10269
|
+
path: redactPath(details["data_dir"] || "unknown"),
|
|
10270
|
+
exists: details["data_dir_exists"] === "yes"
|
|
10271
|
+
}
|
|
10272
|
+
}),
|
|
10273
|
+
makeCheck2("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${redactPath(details["manifest_path"] || "unknown")} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
10274
|
+
data: {
|
|
10275
|
+
path: redactPath(details["manifest_path"] || "unknown"),
|
|
10276
|
+
exists: details["manifest_exists"] === "yes"
|
|
10277
|
+
}
|
|
10278
|
+
}),
|
|
10279
|
+
makeCheck2("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${redactPath(details["db_path"] || "unknown")} ${details["db_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
10280
|
+
data: {
|
|
10281
|
+
path: redactPath(details["db_path"] || "unknown"),
|
|
10282
|
+
exists: details["db_exists"] === "yes"
|
|
10283
|
+
}
|
|
10284
|
+
}),
|
|
10285
|
+
makeCheck2("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${redactPath(details["notifications_path"] || "unknown")} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
10286
|
+
data: {
|
|
10287
|
+
path: redactPath(details["notifications_path"] || "unknown"),
|
|
10288
|
+
exists: details["notifications_exists"] === "yes"
|
|
10289
|
+
}
|
|
10290
|
+
}),
|
|
10009
10291
|
makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
10010
10292
|
makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
10011
10293
|
makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
10012
10294
|
makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
10013
|
-
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
10295
|
+
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
|
|
10296
|
+
...optionalAdapterChecks
|
|
10014
10297
|
];
|
|
10015
10298
|
return {
|
|
10016
10299
|
machineId,
|
|
10017
10300
|
source: commandChecks.source,
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10301
|
+
schemaVersion: 1,
|
|
10302
|
+
generatedAt: now.toISOString(),
|
|
10303
|
+
manifestSource,
|
|
10304
|
+
manifestPath: details["manifest_path"] ? redactPath(details["manifest_path"]) : undefined,
|
|
10305
|
+
dbPath: details["db_path"] ? redactPath(details["db_path"]) : undefined,
|
|
10306
|
+
notificationsPath: details["notifications_path"] ? redactPath(details["notifications_path"]) : undefined,
|
|
10021
10307
|
checks
|
|
10022
10308
|
};
|
|
10023
10309
|
}
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
-
import type
|
|
2
|
-
|
|
1
|
+
import { type ManifestSourceAdapter } from "../manifests.js";
|
|
2
|
+
import type { DoctorCheck, DoctorReport, FleetManifest, ManifestLoadInfo } from "../types.js";
|
|
3
|
+
export declare const DOCTOR_OPTIONAL_ADAPTER_DOMAINS: readonly ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
|
|
4
|
+
export type DoctorOptionalAdapterDomain = typeof DOCTOR_OPTIONAL_ADAPTER_DOMAINS[number];
|
|
5
|
+
export interface DoctorAdapterContext {
|
|
6
|
+
machineId: string;
|
|
7
|
+
manifest: FleetManifest;
|
|
8
|
+
manifestSource: ManifestLoadInfo;
|
|
9
|
+
commandDetails: Record<string, string>;
|
|
10
|
+
now: Date;
|
|
11
|
+
}
|
|
12
|
+
export type DoctorAdapterHook = (context: DoctorAdapterContext) => DoctorCheck | DoctorCheck[] | null | undefined;
|
|
13
|
+
export interface DoctorAdapter {
|
|
14
|
+
id: string;
|
|
15
|
+
checks?: Partial<Record<DoctorOptionalAdapterDomain, DoctorAdapterHook>>;
|
|
16
|
+
}
|
|
17
|
+
export interface DoctorOptions {
|
|
18
|
+
now?: Date;
|
|
19
|
+
manifestAdapter?: ManifestSourceAdapter | null;
|
|
20
|
+
adapters?: DoctorAdapter[];
|
|
21
|
+
includeOptionalAdapters?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function runDoctor(machineId?: string, options?: DoctorOptions): DoctorReport;
|
|
3
24
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGrF,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE9F,eAAO,MAAM,+BAA+B,uEAAwE,CAAC;AAErH,MAAM,MAAM,2BAA2B,GAAG,OAAO,+BAA+B,CAAC,MAAM,CAAC,CAAC;AAEzF,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,gBAAgB,CAAC;IACjC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,GAAG,EAAE,IAAI,CAAC;CACX;AAED,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,WAAW,GAAG,WAAW,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;AAElH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,2BAA2B,EAAE,iBAAiB,CAAC,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,eAAe,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC;AAkHD,wBAAgB,SAAS,CAAC,SAAS,SAAsB,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CA0IpG"}
|