@hasna/machines 0.0.33 → 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 +53 -36
- package/dist/cli/index.js +332 -29
- package/dist/commands/doctor.d.ts +23 -2
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/heal-daemon.d.ts.map +1 -1
- package/dist/consumer.js +206 -12
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +340 -27
- package/dist/manifests.d.ts +22 -1
- package/dist/manifests.d.ts.map +1 -1
- package/dist/mcp/index.js +321 -22
- 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/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
|
}
|
|
@@ -7356,6 +7537,19 @@ function manifestHostReachable(target) {
|
|
|
7356
7537
|
return null;
|
|
7357
7538
|
return overrides.has(target);
|
|
7358
7539
|
}
|
|
7540
|
+
function userFromSshAddress(address) {
|
|
7541
|
+
if (!address)
|
|
7542
|
+
return null;
|
|
7543
|
+
const at = address.indexOf("@");
|
|
7544
|
+
if (at <= 0)
|
|
7545
|
+
return null;
|
|
7546
|
+
return address.slice(0, at);
|
|
7547
|
+
}
|
|
7548
|
+
function commandTargetForRoute(target, user) {
|
|
7549
|
+
if (target.kind === "local" || target.target.includes("@") || !user)
|
|
7550
|
+
return target.target;
|
|
7551
|
+
return `${user}@${target.target}`;
|
|
7552
|
+
}
|
|
7359
7553
|
function routeHints(input) {
|
|
7360
7554
|
const hints = [];
|
|
7361
7555
|
if (input.machineId === input.localMachineId) {
|
|
@@ -7406,6 +7600,7 @@ function buildEntry(input) {
|
|
|
7406
7600
|
});
|
|
7407
7601
|
const selectedRoute = selectRouteHint(hints);
|
|
7408
7602
|
const route = selectedRoute?.kind === "ssh" ? "ssh" : selectedRoute?.kind ?? "unknown";
|
|
7603
|
+
const routeUser = userFromSshAddress(manifest?.sshAddress) ?? (typeof manifest?.metadata?.user === "string" ? manifest.metadata.user : null);
|
|
7409
7604
|
return {
|
|
7410
7605
|
machine_id: input.machineId,
|
|
7411
7606
|
hostname: manifest?.hostname ?? peer?.HostName ?? null,
|
|
@@ -7426,11 +7621,11 @@ function buildEntry(input) {
|
|
|
7426
7621
|
ssh: {
|
|
7427
7622
|
address: manifest?.sshAddress ?? null,
|
|
7428
7623
|
route,
|
|
7429
|
-
command_target: selectedRoute
|
|
7624
|
+
command_target: selectedRoute ? commandTargetForRoute(selectedRoute, routeUser) : null
|
|
7430
7625
|
},
|
|
7431
7626
|
route_hints: hints,
|
|
7432
7627
|
tags: manifest?.tags ?? [],
|
|
7433
|
-
metadata: manifest?.metadata
|
|
7628
|
+
metadata: redactMetadata(manifest?.metadata)
|
|
7434
7629
|
};
|
|
7435
7630
|
}
|
|
7436
7631
|
function discoverMachineTopology(options = {}) {
|
|
@@ -7634,6 +7829,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
7634
7829
|
const local = route === "local" || machine.machine_id === topology.local_machine_id;
|
|
7635
7830
|
const confidence = routeConfidence({ machine, hint: selectedHint, matchedBy });
|
|
7636
7831
|
const ok = Boolean(selectedHint?.target);
|
|
7832
|
+
const commandTarget = selectedHint ? commandTargetForRoute(selectedHint, userFromSshAddress(machine.ssh.address) ?? machine.user) : null;
|
|
7637
7833
|
return {
|
|
7638
7834
|
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
7639
7835
|
package: topology.package,
|
|
@@ -7644,7 +7840,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
7644
7840
|
route,
|
|
7645
7841
|
source: route,
|
|
7646
7842
|
target: selectedHint?.target ?? null,
|
|
7647
|
-
command_target:
|
|
7843
|
+
command_target: commandTarget,
|
|
7648
7844
|
confidence,
|
|
7649
7845
|
local,
|
|
7650
7846
|
evidence: {
|
|
@@ -7667,7 +7863,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
7667
7863
|
warnings
|
|
7668
7864
|
};
|
|
7669
7865
|
}
|
|
7670
|
-
function
|
|
7866
|
+
function isRecord2(value) {
|
|
7671
7867
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
7672
7868
|
}
|
|
7673
7869
|
function metadataString(metadata, keys) {
|
|
@@ -7697,13 +7893,13 @@ function metadataStringArray(metadata, keys) {
|
|
|
7697
7893
|
function readMappedPath(input) {
|
|
7698
7894
|
for (const containerName of input.containers) {
|
|
7699
7895
|
const container = input.metadata[containerName];
|
|
7700
|
-
if (!
|
|
7896
|
+
if (!isRecord2(container))
|
|
7701
7897
|
continue;
|
|
7702
7898
|
for (const key of input.keys) {
|
|
7703
7899
|
const value = container[key];
|
|
7704
7900
|
if (typeof value === "string" && value.trim())
|
|
7705
7901
|
return value.trim();
|
|
7706
|
-
if (
|
|
7902
|
+
if (isRecord2(value)) {
|
|
7707
7903
|
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
7708
7904
|
if (path)
|
|
7709
7905
|
return path;
|
|
@@ -7957,7 +8153,7 @@ function primaryMachine(machine, projectId, primaryMachineId) {
|
|
|
7957
8153
|
return machine.tags.includes("primary");
|
|
7958
8154
|
}
|
|
7959
8155
|
function metadataKeysForDiagnostics(metadata) {
|
|
7960
|
-
return
|
|
8156
|
+
return publicMetadataKeys(metadata);
|
|
7961
8157
|
}
|
|
7962
8158
|
function resolveMachineWorkspace(options) {
|
|
7963
8159
|
const now = options.now ?? new Date;
|
|
@@ -8102,7 +8298,7 @@ function resolveSshTarget(machineId, options = {}) {
|
|
|
8102
8298
|
}
|
|
8103
8299
|
return {
|
|
8104
8300
|
machineId: resolved.machine_id ?? machineId,
|
|
8105
|
-
target: resolved.target,
|
|
8301
|
+
target: resolved.command_target ?? resolved.target,
|
|
8106
8302
|
route: resolved.route,
|
|
8107
8303
|
confidence: resolved.confidence,
|
|
8108
8304
|
warnings: resolved.warnings
|
|
@@ -9563,20 +9759,20 @@ function getStatus() {
|
|
|
9563
9759
|
|
|
9564
9760
|
// src/commands/workspace.ts
|
|
9565
9761
|
init_paths();
|
|
9566
|
-
function
|
|
9762
|
+
function isRecord3(value) {
|
|
9567
9763
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9568
9764
|
}
|
|
9569
9765
|
function cloneMetadata(metadata) {
|
|
9570
|
-
return
|
|
9766
|
+
return isRecord3(metadata) ? { ...metadata } : {};
|
|
9571
9767
|
}
|
|
9572
9768
|
function mappedPath(metadata, field, key) {
|
|
9573
9769
|
const container = metadata[field];
|
|
9574
|
-
if (!
|
|
9770
|
+
if (!isRecord3(container))
|
|
9575
9771
|
return null;
|
|
9576
9772
|
const value = container[key];
|
|
9577
9773
|
if (typeof value === "string" && value.trim())
|
|
9578
9774
|
return value.trim();
|
|
9579
|
-
if (
|
|
9775
|
+
if (isRecord3(value)) {
|
|
9580
9776
|
const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
|
|
9581
9777
|
if (typeof nested === "string" && nested.trim())
|
|
9582
9778
|
return nested.trim();
|
|
@@ -9585,7 +9781,7 @@ function mappedPath(metadata, field, key) {
|
|
|
9585
9781
|
}
|
|
9586
9782
|
function writeMappedPath(metadata, field, key, path) {
|
|
9587
9783
|
const existing = metadata[field];
|
|
9588
|
-
const container =
|
|
9784
|
+
const container = isRecord3(existing) ? { ...existing } : {};
|
|
9589
9785
|
container[key] = path;
|
|
9590
9786
|
metadata[field] = container;
|
|
9591
9787
|
}
|
|
@@ -9955,12 +10151,28 @@ function checkMachineCompatibility(options = {}) {
|
|
|
9955
10151
|
|
|
9956
10152
|
// src/commands/doctor.ts
|
|
9957
10153
|
init_db();
|
|
9958
|
-
|
|
9959
|
-
|
|
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
|
+
};
|
|
9960
10165
|
}
|
|
9961
10166
|
function parseKeyValueOutput(stdout) {
|
|
9962
|
-
|
|
9963
|
-
|
|
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;
|
|
9964
10176
|
}
|
|
9965
10177
|
function buildDoctorCommand() {
|
|
9966
10178
|
return [
|
|
@@ -9968,9 +10180,11 @@ function buildDoctorCommand() {
|
|
|
9968
10180
|
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
9969
10181
|
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
9970
10182
|
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
10183
|
+
`printf 'data_dir=%s\\n' "$data_dir"`,
|
|
9971
10184
|
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
9972
10185
|
`printf 'db_path=%s\\n' "$db_path"`,
|
|
9973
10186
|
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
10187
|
+
`printf 'data_dir_exists=%s\\n' "$(test -d "$data_dir" && printf yes || printf no)"`,
|
|
9974
10188
|
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
9975
10189
|
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
9976
10190
|
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
@@ -9981,28 +10195,115 @@ function buildDoctorCommand() {
|
|
|
9981
10195
|
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
9982
10196
|
].join("; ");
|
|
9983
10197
|
}
|
|
9984
|
-
function
|
|
9985
|
-
|
|
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 });
|
|
9986
10241
|
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
9987
10242
|
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
9988
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 ?? []);
|
|
9989
10251
|
const checks = [
|
|
9990
|
-
makeCheck2("manifest-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
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
|
+
}),
|
|
9994
10291
|
makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
9995
10292
|
makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
9996
10293
|
makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
9997
10294
|
makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
9998
|
-
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
|
|
9999
10297
|
];
|
|
10000
10298
|
return {
|
|
10001
10299
|
machineId,
|
|
10002
10300
|
source: commandChecks.source,
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
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,
|
|
10006
10307
|
checks
|
|
10007
10308
|
};
|
|
10008
10309
|
}
|
|
@@ -11261,8 +11562,10 @@ function binPath() {
|
|
|
11261
11562
|
candidates.push(which);
|
|
11262
11563
|
if (process.argv[1])
|
|
11263
11564
|
candidates.push(process.argv[1]);
|
|
11264
|
-
const home = process.env["HOME"]
|
|
11265
|
-
|
|
11565
|
+
const home = process.env["HOME"];
|
|
11566
|
+
if (home)
|
|
11567
|
+
candidates.push(`${home}/.bun/bin/machines`);
|
|
11568
|
+
candidates.push("/root/.bun/bin/machines", "/usr/local/bin/machines");
|
|
11266
11569
|
for (const c of candidates) {
|
|
11267
11570
|
if (c && existsSync10(c))
|
|
11268
11571
|
return c;
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heal-daemon.d.ts","sourceRoot":"","sources":["../../src/commands/heal-daemon.ts"],"names":[],"mappings":"AAUA,OAAO,EAWL,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAsC/F;AAwBD,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAOzE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAiBtC;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAyB7D;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAejD;
|
|
1
|
+
{"version":3,"file":"heal-daemon.d.ts","sourceRoot":"","sources":["../../src/commands/heal-daemon.ts"],"names":[],"mappings":"AAUA,OAAO,EAWL,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAMnB,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,cAAc,CAsC/F;AAwBD,wBAAgB,cAAc,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAOzE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAiBtC;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAyB7D;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,IAAI,MAAM,EAAE,CAejD;AAqBD,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CA0B7C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAW/C;AAED,wBAAgB,iBAAiB,IAAI;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAM7F"}
|