@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/index.js
CHANGED
|
@@ -10982,7 +10982,103 @@ var coerce = {
|
|
|
10982
10982
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
10983
10983
|
};
|
|
10984
10984
|
var NEVER = INVALID;
|
|
10985
|
+
// src/redaction.ts
|
|
10986
|
+
var REDACTED_VALUE = "[redacted]";
|
|
10987
|
+
var SENSITIVE_KEY_PATTERN = /(password|passwd|token|credential|private[_-]?key|privateKey|api[_-]?key|github.*key|pem|secret)/i;
|
|
10988
|
+
var SECRET_REFERENCE_KEY_PATTERN = /(secret(ref(erence)?|key)?|secretRef|secretKey)$/i;
|
|
10989
|
+
var SENSITIVE_VALUE_PATTERNS = [
|
|
10990
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
10991
|
+
/\bghp_[A-Za-z0-9_]{20,}\b/,
|
|
10992
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
10993
|
+
/\bxox[baprs]-[A-Za-z0-9-]{20,}\b/,
|
|
10994
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
10995
|
+
/\bsk-[A-Za-z0-9_-]{20,}\b/
|
|
10996
|
+
];
|
|
10997
|
+
function isSensitiveKey(key) {
|
|
10998
|
+
return SENSITIVE_KEY_PATTERN.test(key);
|
|
10999
|
+
}
|
|
11000
|
+
function isSecretReferenceKey(key) {
|
|
11001
|
+
return SECRET_REFERENCE_KEY_PATTERN.test(key);
|
|
11002
|
+
}
|
|
11003
|
+
function looksSensitiveString(value) {
|
|
11004
|
+
return SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(value));
|
|
11005
|
+
}
|
|
11006
|
+
function isRecord(value) {
|
|
11007
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11008
|
+
}
|
|
11009
|
+
function redactPath(value) {
|
|
11010
|
+
return value.replace(/\/home\/[^/\s]+/g, "/home/<user>").replace(/\/Users\/[^/\s]+/g, "/Users/<user>").replace(/[A-Za-z]:\\Users\\[^\\\s]+/g, "C:\\Users\\<user>");
|
|
11011
|
+
}
|
|
11012
|
+
function redactPrivateRef(value) {
|
|
11013
|
+
const trimmed = value.trim();
|
|
11014
|
+
const scheme = trimmed.match(/^([a-z][a-z0-9+.-]*:\/\/)/i);
|
|
11015
|
+
if (scheme)
|
|
11016
|
+
return `${scheme[1]}<redacted>`;
|
|
11017
|
+
const colon = trimmed.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
11018
|
+
if (colon)
|
|
11019
|
+
return `${colon[1]}:<redacted>`;
|
|
11020
|
+
return "<private-manifest-ref:redacted>";
|
|
11021
|
+
}
|
|
11022
|
+
function redactIdentifier(value) {
|
|
11023
|
+
return value.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 80) || "adapter";
|
|
11024
|
+
}
|
|
11025
|
+
function redactSensitiveValue(value, key = "") {
|
|
11026
|
+
if (typeof value === "string") {
|
|
11027
|
+
if (isSensitiveKey(key) && !(isSecretReferenceKey(key) && !looksSensitiveString(value))) {
|
|
11028
|
+
return REDACTED_VALUE;
|
|
11029
|
+
}
|
|
11030
|
+
if (looksSensitiveString(value))
|
|
11031
|
+
return REDACTED_VALUE;
|
|
11032
|
+
return redactPath(value);
|
|
11033
|
+
}
|
|
11034
|
+
if (Array.isArray(value)) {
|
|
11035
|
+
return value.map((entry) => redactSensitiveValue(entry, key));
|
|
11036
|
+
}
|
|
11037
|
+
if (isRecord(value)) {
|
|
11038
|
+
const redacted = {};
|
|
11039
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
11040
|
+
redacted[entryKey] = redactSensitiveValue(entryValue, entryKey);
|
|
11041
|
+
}
|
|
11042
|
+
return redacted;
|
|
11043
|
+
}
|
|
11044
|
+
return value;
|
|
11045
|
+
}
|
|
11046
|
+
function publicMetadataKeys(metadata) {
|
|
11047
|
+
return Object.keys(metadata ?? {}).filter((key) => !isSensitiveKey(key)).sort();
|
|
11048
|
+
}
|
|
11049
|
+
function redactMetadata(metadata) {
|
|
11050
|
+
return redactSensitiveValue(metadata ?? {});
|
|
11051
|
+
}
|
|
11052
|
+
function redactManifestForDiagnostics(machine) {
|
|
11053
|
+
const metadata = redactMetadata(machine.metadata);
|
|
11054
|
+
for (const key of ["user", "username", "login"]) {
|
|
11055
|
+
if (typeof metadata[key] === "string")
|
|
11056
|
+
metadata[key] = REDACTED_VALUE;
|
|
11057
|
+
}
|
|
11058
|
+
return {
|
|
11059
|
+
id: machine.id,
|
|
11060
|
+
hostname: machine.hostname ? REDACTED_VALUE : undefined,
|
|
11061
|
+
sshAddress: machine.sshAddress ? REDACTED_VALUE : undefined,
|
|
11062
|
+
tailscaleName: machine.tailscaleName ? REDACTED_VALUE : undefined,
|
|
11063
|
+
platform: machine.platform,
|
|
11064
|
+
connection: machine.connection,
|
|
11065
|
+
workspacePath: redactPath(machine.workspacePath),
|
|
11066
|
+
bunPath: machine.bunPath ? redactPath(machine.bunPath) : undefined,
|
|
11067
|
+
tags: machine.tags ?? [],
|
|
11068
|
+
metadata,
|
|
11069
|
+
packages: machine.packages?.map((pkg) => ({ ...pkg })),
|
|
11070
|
+
apps: machine.apps?.map((app) => ({ ...app })),
|
|
11071
|
+
files: machine.files?.map((file) => ({
|
|
11072
|
+
...file,
|
|
11073
|
+
source: redactPath(file.source),
|
|
11074
|
+
target: redactPath(file.target)
|
|
11075
|
+
}))
|
|
11076
|
+
};
|
|
11077
|
+
}
|
|
11078
|
+
|
|
10985
11079
|
// src/manifests.ts
|
|
11080
|
+
var PRIVATE_MANIFEST_REF_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_REF";
|
|
11081
|
+
var PRIVATE_MANIFEST_BACKEND_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_BACKEND";
|
|
10986
11082
|
var packageSchema = exports_external.object({
|
|
10987
11083
|
name: exports_external.string(),
|
|
10988
11084
|
manager: exports_external.enum(["bun", "brew", "apt", "custom"]).optional(),
|
|
@@ -11035,6 +11131,42 @@ function normalizePlatform() {
|
|
|
11035
11131
|
function normalizeMachines(machines) {
|
|
11036
11132
|
return [...machines].sort((left, right) => left.id.localeCompare(right.id));
|
|
11037
11133
|
}
|
|
11134
|
+
function inferPrivateBackend(rawRef, explicitBackend) {
|
|
11135
|
+
if (explicitBackend?.trim())
|
|
11136
|
+
return explicitBackend.trim();
|
|
11137
|
+
const scheme = rawRef.trim().match(/^([a-z][a-z0-9+.-]*)(?::\/\/|:)/i);
|
|
11138
|
+
return scheme?.[1] ?? null;
|
|
11139
|
+
}
|
|
11140
|
+
function fileSourceRef(path) {
|
|
11141
|
+
return {
|
|
11142
|
+
kind: "file",
|
|
11143
|
+
ref: redactPath(path),
|
|
11144
|
+
backend: "file",
|
|
11145
|
+
private: false,
|
|
11146
|
+
publicSafe: true
|
|
11147
|
+
};
|
|
11148
|
+
}
|
|
11149
|
+
function privateSourceRef(rawRef, backend) {
|
|
11150
|
+
return {
|
|
11151
|
+
kind: "private-ref",
|
|
11152
|
+
ref: redactPrivateRef(rawRef),
|
|
11153
|
+
backend: inferPrivateBackend(rawRef, backend),
|
|
11154
|
+
private: true,
|
|
11155
|
+
publicSafe: true
|
|
11156
|
+
};
|
|
11157
|
+
}
|
|
11158
|
+
function privateRefFromOptions(options) {
|
|
11159
|
+
const env = options.env ?? process.env;
|
|
11160
|
+
return options.privateRef?.trim() || env[PRIVATE_MANIFEST_REF_ENV]?.trim() || env["MACHINES_PRIVATE_MANIFEST_REF"]?.trim() || null;
|
|
11161
|
+
}
|
|
11162
|
+
function getManifestSourceRef(options = {}) {
|
|
11163
|
+
const rawPrivateRef = privateRefFromOptions(options);
|
|
11164
|
+
if (rawPrivateRef) {
|
|
11165
|
+
const env = options.env ?? process.env;
|
|
11166
|
+
return privateSourceRef(rawPrivateRef, options.privateBackend ?? env[PRIVATE_MANIFEST_BACKEND_ENV]);
|
|
11167
|
+
}
|
|
11168
|
+
return fileSourceRef(options.path ?? getManifestPath());
|
|
11169
|
+
}
|
|
11038
11170
|
function getDefaultManifest() {
|
|
11039
11171
|
return {
|
|
11040
11172
|
version: 1,
|
|
@@ -11049,6 +11181,53 @@ function readManifest(path = getManifestPath()) {
|
|
|
11049
11181
|
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
11050
11182
|
return fleetSchema.parse(raw);
|
|
11051
11183
|
}
|
|
11184
|
+
function readManifestWithSource(options = {}) {
|
|
11185
|
+
const path = options.path ?? getManifestPath();
|
|
11186
|
+
const source = getManifestSourceRef(options);
|
|
11187
|
+
const warnings = [];
|
|
11188
|
+
if (source.kind === "private-ref") {
|
|
11189
|
+
const rawRef = privateRefFromOptions(options);
|
|
11190
|
+
if (rawRef && options.adapter) {
|
|
11191
|
+
try {
|
|
11192
|
+
const manifest2 = options.adapter.readManifest({ source, rawRef });
|
|
11193
|
+
if (manifest2) {
|
|
11194
|
+
return {
|
|
11195
|
+
manifest: fleetSchema.parse(manifest2),
|
|
11196
|
+
info: {
|
|
11197
|
+
source,
|
|
11198
|
+
loadedFrom: "private-ref",
|
|
11199
|
+
warnings
|
|
11200
|
+
}
|
|
11201
|
+
};
|
|
11202
|
+
}
|
|
11203
|
+
warnings.push(`private_manifest_adapter_empty:${redactIdentifier(options.adapter.id)}`);
|
|
11204
|
+
} catch (error) {
|
|
11205
|
+
warnings.push(`private_manifest_adapter_failed:${redactIdentifier(options.adapter.id)}`);
|
|
11206
|
+
}
|
|
11207
|
+
} else {
|
|
11208
|
+
warnings.push("private_manifest_ref_without_adapter");
|
|
11209
|
+
}
|
|
11210
|
+
const fallbackSource = fileSourceRef(path);
|
|
11211
|
+
const manifest = readManifest(path);
|
|
11212
|
+
return {
|
|
11213
|
+
manifest,
|
|
11214
|
+
info: {
|
|
11215
|
+
source,
|
|
11216
|
+
loadedFrom: existsSync2(path) ? "fallback" : "default",
|
|
11217
|
+
fallbackSource,
|
|
11218
|
+
warnings
|
|
11219
|
+
}
|
|
11220
|
+
};
|
|
11221
|
+
}
|
|
11222
|
+
return {
|
|
11223
|
+
manifest: readManifest(path),
|
|
11224
|
+
info: {
|
|
11225
|
+
source,
|
|
11226
|
+
loadedFrom: existsSync2(path) ? "file" : "default",
|
|
11227
|
+
warnings
|
|
11228
|
+
}
|
|
11229
|
+
};
|
|
11230
|
+
}
|
|
11052
11231
|
function validateManifest(path = getManifestPath()) {
|
|
11053
11232
|
return readManifest(path);
|
|
11054
11233
|
}
|
|
@@ -11287,6 +11466,19 @@ function manifestHostReachable(target) {
|
|
|
11287
11466
|
return null;
|
|
11288
11467
|
return overrides.has(target);
|
|
11289
11468
|
}
|
|
11469
|
+
function userFromSshAddress(address) {
|
|
11470
|
+
if (!address)
|
|
11471
|
+
return null;
|
|
11472
|
+
const at = address.indexOf("@");
|
|
11473
|
+
if (at <= 0)
|
|
11474
|
+
return null;
|
|
11475
|
+
return address.slice(0, at);
|
|
11476
|
+
}
|
|
11477
|
+
function commandTargetForRoute(target, user) {
|
|
11478
|
+
if (target.kind === "local" || target.target.includes("@") || !user)
|
|
11479
|
+
return target.target;
|
|
11480
|
+
return `${user}@${target.target}`;
|
|
11481
|
+
}
|
|
11290
11482
|
function routeHints(input) {
|
|
11291
11483
|
const hints = [];
|
|
11292
11484
|
if (input.machineId === input.localMachineId) {
|
|
@@ -11337,6 +11529,7 @@ function buildEntry(input) {
|
|
|
11337
11529
|
});
|
|
11338
11530
|
const selectedRoute = selectRouteHint(hints);
|
|
11339
11531
|
const route = selectedRoute?.kind === "ssh" ? "ssh" : selectedRoute?.kind ?? "unknown";
|
|
11532
|
+
const routeUser = userFromSshAddress(manifest?.sshAddress) ?? (typeof manifest?.metadata?.user === "string" ? manifest.metadata.user : null);
|
|
11340
11533
|
return {
|
|
11341
11534
|
machine_id: input.machineId,
|
|
11342
11535
|
hostname: manifest?.hostname ?? peer?.HostName ?? null,
|
|
@@ -11357,11 +11550,11 @@ function buildEntry(input) {
|
|
|
11357
11550
|
ssh: {
|
|
11358
11551
|
address: manifest?.sshAddress ?? null,
|
|
11359
11552
|
route,
|
|
11360
|
-
command_target: selectedRoute
|
|
11553
|
+
command_target: selectedRoute ? commandTargetForRoute(selectedRoute, routeUser) : null
|
|
11361
11554
|
},
|
|
11362
11555
|
route_hints: hints,
|
|
11363
11556
|
tags: manifest?.tags ?? [],
|
|
11364
|
-
metadata: manifest?.metadata
|
|
11557
|
+
metadata: redactMetadata(manifest?.metadata)
|
|
11365
11558
|
};
|
|
11366
11559
|
}
|
|
11367
11560
|
function discoverMachineTopology(options = {}) {
|
|
@@ -11596,6 +11789,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11596
11789
|
const local = route === "local" || machine.machine_id === topology.local_machine_id;
|
|
11597
11790
|
const confidence = routeConfidence({ machine, hint: selectedHint, matchedBy });
|
|
11598
11791
|
const ok = Boolean(selectedHint?.target);
|
|
11792
|
+
const commandTarget = selectedHint ? commandTargetForRoute(selectedHint, userFromSshAddress(machine.ssh.address) ?? machine.user) : null;
|
|
11599
11793
|
return {
|
|
11600
11794
|
schema_version: MACHINES_CONSUMER_CONTRACT_VERSION,
|
|
11601
11795
|
package: topology.package,
|
|
@@ -11606,7 +11800,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11606
11800
|
route,
|
|
11607
11801
|
source: route,
|
|
11608
11802
|
target: selectedHint?.target ?? null,
|
|
11609
|
-
command_target:
|
|
11803
|
+
command_target: commandTarget,
|
|
11610
11804
|
confidence,
|
|
11611
11805
|
local,
|
|
11612
11806
|
evidence: {
|
|
@@ -11629,7 +11823,7 @@ function resolveMachineRoute(machineId, options = {}) {
|
|
|
11629
11823
|
warnings
|
|
11630
11824
|
};
|
|
11631
11825
|
}
|
|
11632
|
-
function
|
|
11826
|
+
function isRecord2(value) {
|
|
11633
11827
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
11634
11828
|
}
|
|
11635
11829
|
function metadataString(metadata, keys) {
|
|
@@ -11659,13 +11853,13 @@ function metadataStringArray(metadata, keys) {
|
|
|
11659
11853
|
function readMappedPath(input) {
|
|
11660
11854
|
for (const containerName of input.containers) {
|
|
11661
11855
|
const container = input.metadata[containerName];
|
|
11662
|
-
if (!
|
|
11856
|
+
if (!isRecord2(container))
|
|
11663
11857
|
continue;
|
|
11664
11858
|
for (const key of input.keys) {
|
|
11665
11859
|
const value = container[key];
|
|
11666
11860
|
if (typeof value === "string" && value.trim())
|
|
11667
11861
|
return value.trim();
|
|
11668
|
-
if (
|
|
11862
|
+
if (isRecord2(value)) {
|
|
11669
11863
|
const path = metadataString(value, ["path", "root", "workspacePath", "workspace_path"]);
|
|
11670
11864
|
if (path)
|
|
11671
11865
|
return path;
|
|
@@ -11919,7 +12113,7 @@ function primaryMachine(machine, projectId, primaryMachineId) {
|
|
|
11919
12113
|
return machine.tags.includes("primary");
|
|
11920
12114
|
}
|
|
11921
12115
|
function metadataKeysForDiagnostics(metadata) {
|
|
11922
|
-
return
|
|
12116
|
+
return publicMetadataKeys(metadata);
|
|
11923
12117
|
}
|
|
11924
12118
|
function resolveMachineWorkspace(options) {
|
|
11925
12119
|
const now = options.now ?? new Date;
|
|
@@ -12153,7 +12347,7 @@ function resolveSshTarget(machineId, options = {}) {
|
|
|
12153
12347
|
}
|
|
12154
12348
|
return {
|
|
12155
12349
|
machineId: resolved.machine_id ?? machineId,
|
|
12156
|
-
target: resolved.target,
|
|
12350
|
+
target: resolved.command_target ?? resolved.target,
|
|
12157
12351
|
route: resolved.route,
|
|
12158
12352
|
confidence: resolved.confidence,
|
|
12159
12353
|
warnings: resolved.warnings
|
|
@@ -12844,12 +13038,28 @@ function renderDomainMapping(domain) {
|
|
|
12844
13038
|
};
|
|
12845
13039
|
}
|
|
12846
13040
|
// src/commands/doctor.ts
|
|
12847
|
-
|
|
12848
|
-
|
|
13041
|
+
var DOCTOR_OPTIONAL_ADAPTER_DOMAINS = ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
|
|
13042
|
+
function makeCheck2(id, status, summary, detail, extra = {}) {
|
|
13043
|
+
const { data, ...rest } = extra;
|
|
13044
|
+
return {
|
|
13045
|
+
...rest,
|
|
13046
|
+
id,
|
|
13047
|
+
status,
|
|
13048
|
+
summary,
|
|
13049
|
+
detail,
|
|
13050
|
+
data: data ? redactSensitiveValue(data) : undefined
|
|
13051
|
+
};
|
|
12849
13052
|
}
|
|
12850
13053
|
function parseKeyValueOutput(stdout) {
|
|
12851
|
-
|
|
12852
|
-
|
|
13054
|
+
const result = {};
|
|
13055
|
+
for (const line of stdout.trim().split(`
|
|
13056
|
+
`)) {
|
|
13057
|
+
const index = line.indexOf("=");
|
|
13058
|
+
if (index <= 0)
|
|
13059
|
+
continue;
|
|
13060
|
+
result[line.slice(0, index)] = line.slice(index + 1);
|
|
13061
|
+
}
|
|
13062
|
+
return result;
|
|
12853
13063
|
}
|
|
12854
13064
|
function buildDoctorCommand() {
|
|
12855
13065
|
return [
|
|
@@ -12857,9 +13067,11 @@ function buildDoctorCommand() {
|
|
|
12857
13067
|
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
12858
13068
|
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
12859
13069
|
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
13070
|
+
`printf 'data_dir=%s\\n' "$data_dir"`,
|
|
12860
13071
|
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
12861
13072
|
`printf 'db_path=%s\\n' "$db_path"`,
|
|
12862
13073
|
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
13074
|
+
`printf 'data_dir_exists=%s\\n' "$(test -d "$data_dir" && printf yes || printf no)"`,
|
|
12863
13075
|
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
12864
13076
|
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
12865
13077
|
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
@@ -12870,28 +13082,115 @@ function buildDoctorCommand() {
|
|
|
12870
13082
|
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
12871
13083
|
].join("; ");
|
|
12872
13084
|
}
|
|
12873
|
-
function
|
|
12874
|
-
|
|
13085
|
+
function fallbackAdapterCheck(domain) {
|
|
13086
|
+
return makeCheck2(`${domain}-adapter`, "ok", `Optional ${domain} adapter`, `No ${domain} adapter configured; skipped optional private integration check.`, {
|
|
13087
|
+
optional: true,
|
|
13088
|
+
source: "open-machines",
|
|
13089
|
+
data: { configured: false, fallback: true }
|
|
13090
|
+
});
|
|
13091
|
+
}
|
|
13092
|
+
function sanitizeAdapterCheck(check, domain, adapterId) {
|
|
13093
|
+
const safeAdapterId = redactIdentifier(adapterId);
|
|
13094
|
+
return makeCheck2(check.id.startsWith(`${domain}-`) || check.id.startsWith(`${domain}:`) ? check.id : `${domain}:${check.id}`, check.status, check.summary, String(redactSensitiveValue(check.detail)), {
|
|
13095
|
+
...check,
|
|
13096
|
+
optional: check.optional ?? true,
|
|
13097
|
+
source: check.source ? String(redactSensitiveValue(check.source)) : `adapter:${safeAdapterId}`,
|
|
13098
|
+
data: check.data ? redactSensitiveValue(check.data) : undefined
|
|
13099
|
+
});
|
|
13100
|
+
}
|
|
13101
|
+
function runOptionalAdapterChecks(context, adapters) {
|
|
13102
|
+
const checks = [];
|
|
13103
|
+
for (const domain of DOCTOR_OPTIONAL_ADAPTER_DOMAINS) {
|
|
13104
|
+
const adapter2 = adapters.find((candidate) => candidate.checks?.[domain]);
|
|
13105
|
+
const hook = adapter2?.checks?.[domain];
|
|
13106
|
+
if (!adapter2 || !hook) {
|
|
13107
|
+
checks.push(fallbackAdapterCheck(domain));
|
|
13108
|
+
continue;
|
|
13109
|
+
}
|
|
13110
|
+
try {
|
|
13111
|
+
const result = hook(context);
|
|
13112
|
+
const domainChecks = Array.isArray(result) ? result : result ? [result] : [fallbackAdapterCheck(domain)];
|
|
13113
|
+
checks.push(...domainChecks.map((check) => sanitizeAdapterCheck(check, domain, adapter2.id)));
|
|
13114
|
+
} catch {
|
|
13115
|
+
const safeAdapterId = redactIdentifier(adapter2.id);
|
|
13116
|
+
checks.push(makeCheck2(`${domain}-adapter`, "warn", `Optional ${domain} adapter failed`, "Adapter failed; details are intentionally hidden to avoid leaking private refs or credentials.", {
|
|
13117
|
+
optional: true,
|
|
13118
|
+
source: `adapter:${safeAdapterId}`,
|
|
13119
|
+
data: { adapter: safeAdapterId, fallback: true }
|
|
13120
|
+
}));
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
return checks;
|
|
13124
|
+
}
|
|
13125
|
+
function runDoctor(machineId = getLocalMachineId(), options = {}) {
|
|
13126
|
+
const now = options.now ?? new Date;
|
|
13127
|
+
const { manifest, info: manifestSource } = readManifestWithSource({ adapter: options.manifestAdapter ?? null });
|
|
12875
13128
|
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
12876
13129
|
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
12877
13130
|
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
13131
|
+
const optionalAdapterChecks = options.includeOptionalAdapters === false ? [] : runOptionalAdapterChecks({
|
|
13132
|
+
machineId,
|
|
13133
|
+
manifest,
|
|
13134
|
+
manifestSource,
|
|
13135
|
+
commandDetails: details,
|
|
13136
|
+
now
|
|
13137
|
+
}, options.adapters ?? []);
|
|
12878
13138
|
const checks = [
|
|
12879
|
-
makeCheck2("manifest-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
13139
|
+
makeCheck2("manifest-source", manifestSource.warnings.length > 0 ? "warn" : "ok", "Manifest source boundary", `${manifestSource.source.kind}:${manifestSource.source.ref} loaded from ${manifestSource.loadedFrom}`, {
|
|
13140
|
+
data: {
|
|
13141
|
+
source: manifestSource.source,
|
|
13142
|
+
loadedFrom: manifestSource.loadedFrom,
|
|
13143
|
+
fallbackSource: manifestSource.fallbackSource,
|
|
13144
|
+
warnings: manifestSource.warnings
|
|
13145
|
+
},
|
|
13146
|
+
remediation: manifestSource.warnings.length > 0 ? ["Provide a private manifest adapter or unset the private manifest ref to use the local manifest only."] : undefined
|
|
13147
|
+
}),
|
|
13148
|
+
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}`, {
|
|
13149
|
+
data: {
|
|
13150
|
+
declared: Boolean(machineInManifest),
|
|
13151
|
+
machine: machineInManifest ? redactManifestForDiagnostics(machineInManifest) : null
|
|
13152
|
+
}
|
|
13153
|
+
}),
|
|
13154
|
+
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"}`, {
|
|
13155
|
+
data: {
|
|
13156
|
+
path: redactPath(details["data_dir"] || "unknown"),
|
|
13157
|
+
exists: details["data_dir_exists"] === "yes"
|
|
13158
|
+
}
|
|
13159
|
+
}),
|
|
13160
|
+
makeCheck2("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${redactPath(details["manifest_path"] || "unknown")} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13161
|
+
data: {
|
|
13162
|
+
path: redactPath(details["manifest_path"] || "unknown"),
|
|
13163
|
+
exists: details["manifest_exists"] === "yes"
|
|
13164
|
+
}
|
|
13165
|
+
}),
|
|
13166
|
+
makeCheck2("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${redactPath(details["db_path"] || "unknown")} ${details["db_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13167
|
+
data: {
|
|
13168
|
+
path: redactPath(details["db_path"] || "unknown"),
|
|
13169
|
+
exists: details["db_exists"] === "yes"
|
|
13170
|
+
}
|
|
13171
|
+
}),
|
|
13172
|
+
makeCheck2("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${redactPath(details["notifications_path"] || "unknown")} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`, {
|
|
13173
|
+
data: {
|
|
13174
|
+
path: redactPath(details["notifications_path"] || "unknown"),
|
|
13175
|
+
exists: details["notifications_exists"] === "yes"
|
|
13176
|
+
}
|
|
13177
|
+
}),
|
|
12883
13178
|
makeCheck2("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
12884
13179
|
makeCheck2("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
12885
13180
|
makeCheck2("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
12886
13181
|
makeCheck2("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
12887
|
-
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
13182
|
+
makeCheck2("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing"),
|
|
13183
|
+
...optionalAdapterChecks
|
|
12888
13184
|
];
|
|
12889
13185
|
return {
|
|
12890
13186
|
machineId,
|
|
12891
13187
|
source: commandChecks.source,
|
|
12892
|
-
|
|
12893
|
-
|
|
12894
|
-
|
|
13188
|
+
schemaVersion: 1,
|
|
13189
|
+
generatedAt: now.toISOString(),
|
|
13190
|
+
manifestSource,
|
|
13191
|
+
manifestPath: details["manifest_path"] ? redactPath(details["manifest_path"]) : undefined,
|
|
13192
|
+
dbPath: details["db_path"] ? redactPath(details["db_path"]) : undefined,
|
|
13193
|
+
notificationsPath: details["notifications_path"] ? redactPath(details["notifications_path"]) : undefined,
|
|
12895
13194
|
checks
|
|
12896
13195
|
};
|
|
12897
13196
|
}
|
|
@@ -14305,20 +14604,20 @@ function runSync(machineId, options = {}, runner = runMachineCommand) {
|
|
|
14305
14604
|
return summary;
|
|
14306
14605
|
}
|
|
14307
14606
|
// src/commands/workspace.ts
|
|
14308
|
-
function
|
|
14607
|
+
function isRecord3(value) {
|
|
14309
14608
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
14310
14609
|
}
|
|
14311
14610
|
function cloneMetadata(metadata) {
|
|
14312
|
-
return
|
|
14611
|
+
return isRecord3(metadata) ? { ...metadata } : {};
|
|
14313
14612
|
}
|
|
14314
14613
|
function mappedPath(metadata, field, key) {
|
|
14315
14614
|
const container = metadata[field];
|
|
14316
|
-
if (!
|
|
14615
|
+
if (!isRecord3(container))
|
|
14317
14616
|
return null;
|
|
14318
14617
|
const value = container[key];
|
|
14319
14618
|
if (typeof value === "string" && value.trim())
|
|
14320
14619
|
return value.trim();
|
|
14321
|
-
if (
|
|
14620
|
+
if (isRecord3(value)) {
|
|
14322
14621
|
const nested = value["path"] ?? value["root"] ?? value["workspacePath"] ?? value["workspace_path"];
|
|
14323
14622
|
if (typeof nested === "string" && nested.trim())
|
|
14324
14623
|
return nested.trim();
|
|
@@ -14327,7 +14626,7 @@ function mappedPath(metadata, field, key) {
|
|
|
14327
14626
|
}
|
|
14328
14627
|
function writeMappedPath(metadata, field, key, path) {
|
|
14329
14628
|
const existing = metadata[field];
|
|
14330
|
-
const container =
|
|
14629
|
+
const container = isRecord3(existing) ? { ...existing } : {};
|
|
14331
14630
|
container[key] = path;
|
|
14332
14631
|
metadata[field] = container;
|
|
14333
14632
|
}
|
|
@@ -23681,10 +23980,18 @@ export {
|
|
|
23681
23980
|
renderDomainMapping,
|
|
23682
23981
|
renderDashboardHtml,
|
|
23683
23982
|
removeNotificationChannel,
|
|
23983
|
+
redactSensitiveValue,
|
|
23984
|
+
redactPrivateRef,
|
|
23985
|
+
redactPath,
|
|
23986
|
+
redactMetadata,
|
|
23987
|
+
redactManifestForDiagnostics,
|
|
23988
|
+
redactIdentifier,
|
|
23684
23989
|
recordSyncRun,
|
|
23685
23990
|
recordSetupRun,
|
|
23686
23991
|
readNotificationConfig,
|
|
23992
|
+
readManifestWithSource,
|
|
23687
23993
|
readManifest,
|
|
23994
|
+
publicMetadataKeys,
|
|
23688
23995
|
probeTmuxPane,
|
|
23689
23996
|
parseStorageTables,
|
|
23690
23997
|
parsePortOutput,
|
|
@@ -23702,6 +24009,7 @@ export {
|
|
|
23702
24009
|
listHeartbeats,
|
|
23703
24010
|
listDomainMappings,
|
|
23704
24011
|
listApps,
|
|
24012
|
+
isSensitiveKey,
|
|
23705
24013
|
getSyncMetaAll,
|
|
23706
24014
|
getStorageStatus,
|
|
23707
24015
|
getStoragePg,
|
|
@@ -23713,6 +24021,7 @@ export {
|
|
|
23713
24021
|
getServeInfo,
|
|
23714
24022
|
getPackageVersion,
|
|
23715
24023
|
getNotificationsPath,
|
|
24024
|
+
getManifestSourceRef,
|
|
23716
24025
|
getManifestPath,
|
|
23717
24026
|
getManifestMachine,
|
|
23718
24027
|
getMachinesConsumerCapabilities,
|
|
@@ -23764,6 +24073,9 @@ export {
|
|
|
23764
24073
|
STORAGE_MODE_ENV,
|
|
23765
24074
|
STORAGE_DATABASE_ENV,
|
|
23766
24075
|
SCREEN_SECRET_NAMESPACE_ENV,
|
|
24076
|
+
REDACTED_VALUE,
|
|
24077
|
+
PRIVATE_MANIFEST_REF_ENV,
|
|
24078
|
+
PRIVATE_MANIFEST_BACKEND_ENV,
|
|
23767
24079
|
MACHINE_MCP_TOOL_NAMES,
|
|
23768
24080
|
MACHINES_STORAGE_TABLES,
|
|
23769
24081
|
MACHINES_STORAGE_MODE_FALLBACK_ENV,
|
|
@@ -23782,6 +24094,7 @@ export {
|
|
|
23782
24094
|
MACHINES_BACKUP_PREFIX_ENV,
|
|
23783
24095
|
MACHINES_BACKUP_BUCKET_FALLBACK_ENV,
|
|
23784
24096
|
MACHINES_BACKUP_BUCKET_ENV,
|
|
24097
|
+
DOCTOR_OPTIONAL_ADAPTER_DOMAINS,
|
|
23785
24098
|
DEFAULT_SCREEN_SECRET_NAMESPACE,
|
|
23786
24099
|
DEFAULT_MACHINE_RESOLVER_TTL_MS,
|
|
23787
24100
|
DEFAULT_BACKUP_PREFIX,
|
package/dist/manifests.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import type { FleetManifest, MachineManifest } from "./types.js";
|
|
2
|
+
import type { FleetManifest, MachineManifest, ManifestLoadInfo, ManifestSourceRef } from "./types.js";
|
|
3
|
+
export declare const PRIVATE_MANIFEST_REF_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_REF";
|
|
4
|
+
export declare const PRIVATE_MANIFEST_BACKEND_ENV = "HASNA_MACHINES_PRIVATE_MANIFEST_BACKEND";
|
|
5
|
+
export interface ManifestSourceAdapter {
|
|
6
|
+
id: string;
|
|
7
|
+
readManifest(input: {
|
|
8
|
+
source: ManifestSourceRef;
|
|
9
|
+
rawRef: string;
|
|
10
|
+
}): FleetManifest | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
export interface ReadManifestWithSourceOptions {
|
|
13
|
+
path?: string;
|
|
14
|
+
env?: NodeJS.ProcessEnv;
|
|
15
|
+
privateRef?: string;
|
|
16
|
+
privateBackend?: string;
|
|
17
|
+
adapter?: ManifestSourceAdapter | null;
|
|
18
|
+
}
|
|
3
19
|
export declare const machineSchema: z.ZodObject<{
|
|
4
20
|
id: z.ZodString;
|
|
5
21
|
hostname: z.ZodOptional<z.ZodString>;
|
|
@@ -270,8 +286,13 @@ export declare const fleetSchema: z.ZodObject<{
|
|
|
270
286
|
version: 1;
|
|
271
287
|
generatedAt?: string | undefined;
|
|
272
288
|
}>;
|
|
289
|
+
export declare function getManifestSourceRef(options?: ReadManifestWithSourceOptions): ManifestSourceRef;
|
|
273
290
|
export declare function getDefaultManifest(): FleetManifest;
|
|
274
291
|
export declare function readManifest(path?: string): FleetManifest;
|
|
292
|
+
export declare function readManifestWithSource(options?: ReadManifestWithSourceOptions): {
|
|
293
|
+
manifest: FleetManifest;
|
|
294
|
+
info: ManifestLoadInfo;
|
|
295
|
+
};
|
|
275
296
|
export declare function validateManifest(path?: string): FleetManifest;
|
|
276
297
|
export declare function writeManifest(manifest: FleetManifest, path?: string): string;
|
|
277
298
|
export declare function getManifestMachine(machineId: string, path?: string): MachineManifest | null;
|
package/dist/manifests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"manifests.d.ts","sourceRoot":"","sources":["../src/manifests.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEtG,eAAO,MAAM,wBAAwB,wCAAwC,CAAC;AAC9E,eAAO,MAAM,4BAA4B,4CAA4C,CAAC;AAEtF,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC;CACtG;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI,CAAC;CACxC;AAoBD,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAcxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAItB,CAAC;AAsDH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,6BAAkC,GAAG,iBAAiB,CAOnG;AAED,wBAAgB,kBAAkB,IAAI,aAAa,CAMlD;AAED,wBAAgB,YAAY,CAAC,IAAI,SAAoB,GAAG,aAAa,CAMpE;AAED,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,6BAAkC,GAAG;IAAE,QAAQ,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAiDvI;AAED,wBAAgB,gBAAgB,CAAC,IAAI,SAAoB,GAAG,aAAa,CAExE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,IAAI,SAAoB,GAAG,MAAM,CAWvF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,SAAoB,GAAG,eAAe,GAAG,IAAI,CAEtG;AAED,wBAAgB,4BAA4B,IAAI,eAAe,CAiB9D"}
|