@hasna/machines 0.0.1 → 0.0.3
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 +33 -8
- package/dist/agent/index.js +9 -5
- package/dist/cli/index.js +732 -158
- package/dist/cli-utils.d.ts +7 -0
- package/dist/cli-utils.d.ts.map +1 -0
- package/dist/commands/apps.d.ts +3 -1
- package/dist/commands/apps.d.ts.map +1 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/install-claude.d.ts +3 -1
- package/dist/commands/install-claude.d.ts.map +1 -1
- package/dist/commands/notifications.d.ts +5 -2
- package/dist/commands/notifications.d.ts.map +1 -1
- package/dist/commands/self-test.d.ts +3 -0
- package/dist/commands/self-test.d.ts.map +1 -0
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +668 -311
- package/dist/mcp/index.js +2055 -1747
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/remote.d.ts +9 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -16,9 +16,9 @@ var __export = (target, all) => {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
// src/mcp/index.ts
|
|
19
|
-
import { readFileSync as
|
|
20
|
-
import { dirname as
|
|
21
|
-
import { fileURLToPath } from "url";
|
|
19
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
20
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
21
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
22
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
23
|
|
|
24
24
|
// src/mcp/server.ts
|
|
@@ -4197,1240 +4197,769 @@ function detectCurrentMachineManifest() {
|
|
|
4197
4197
|
};
|
|
4198
4198
|
}
|
|
4199
4199
|
|
|
4200
|
-
// src/
|
|
4201
|
-
|
|
4202
|
-
|
|
4200
|
+
// src/remote.ts
|
|
4201
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
4202
|
+
|
|
4203
|
+
// src/db.ts
|
|
4204
|
+
import { hostname as hostname2 } from "os";
|
|
4205
|
+
|
|
4206
|
+
// node_modules/@hasna/cloud/dist/index.js
|
|
4207
|
+
import { createRequire } from "module";
|
|
4208
|
+
import { Database } from "bun:sqlite";
|
|
4209
|
+
import {
|
|
4210
|
+
existsSync as existsSync3,
|
|
4211
|
+
mkdirSync as mkdirSync2,
|
|
4212
|
+
readdirSync,
|
|
4213
|
+
copyFileSync
|
|
4214
|
+
} from "fs";
|
|
4215
|
+
import { homedir as homedir3 } from "os";
|
|
4216
|
+
import { join as join3, relative } from "path";
|
|
4217
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4218
|
+
import { homedir as homedir22 } from "os";
|
|
4219
|
+
import { join as join22 } from "path";
|
|
4220
|
+
import { readdirSync as readdirSync2, existsSync as existsSync32 } from "fs";
|
|
4221
|
+
import { join as join32 } from "path";
|
|
4222
|
+
import { homedir as homedir32 } from "os";
|
|
4223
|
+
import { homedir as homedir4 } from "os";
|
|
4224
|
+
import { join as join4 } from "path";
|
|
4225
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
4226
|
+
import { homedir as homedir5, platform as platform2 } from "os";
|
|
4227
|
+
var __create = Object.create;
|
|
4228
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4229
|
+
var __defProp2 = Object.defineProperty;
|
|
4230
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4231
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4232
|
+
function __accessProp(key) {
|
|
4233
|
+
return this[key];
|
|
4203
4234
|
}
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
return `brew install ${packageName}`;
|
|
4214
|
-
}
|
|
4215
|
-
if (machine.platform === "windows") {
|
|
4216
|
-
return `winget install ${packageName}`;
|
|
4235
|
+
var __toESMCache_node;
|
|
4236
|
+
var __toESMCache_esm;
|
|
4237
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
4238
|
+
var canCache = mod != null && typeof mod === "object";
|
|
4239
|
+
if (canCache) {
|
|
4240
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
4241
|
+
var cached = cache.get(mod);
|
|
4242
|
+
if (cached)
|
|
4243
|
+
return cached;
|
|
4217
4244
|
}
|
|
4218
|
-
|
|
4219
|
-
}
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
function
|
|
4233
|
-
|
|
4234
|
-
return {
|
|
4235
|
-
machineId: machine.id,
|
|
4236
|
-
apps: machine.apps || []
|
|
4237
|
-
};
|
|
4238
|
-
}
|
|
4239
|
-
function buildAppsPlan(machineId) {
|
|
4240
|
-
const machine = resolveMachine(machineId);
|
|
4241
|
-
return {
|
|
4242
|
-
machineId: machine.id,
|
|
4243
|
-
mode: "plan",
|
|
4244
|
-
steps: buildAppSteps(machine),
|
|
4245
|
-
executed: 0
|
|
4246
|
-
};
|
|
4245
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
4246
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target;
|
|
4247
|
+
for (let key of __getOwnPropNames(mod))
|
|
4248
|
+
if (!__hasOwnProp.call(to, key))
|
|
4249
|
+
__defProp2(to, key, {
|
|
4250
|
+
get: __accessProp.bind(mod, key),
|
|
4251
|
+
enumerable: true
|
|
4252
|
+
});
|
|
4253
|
+
if (canCache)
|
|
4254
|
+
cache.set(mod, to);
|
|
4255
|
+
return to;
|
|
4256
|
+
};
|
|
4257
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4258
|
+
var __returnValue2 = (v) => v;
|
|
4259
|
+
function __exportSetter2(name, newValue) {
|
|
4260
|
+
this[name] = __returnValue2.bind(null, newValue);
|
|
4247
4261
|
}
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
let executed = 0;
|
|
4256
|
-
for (const step of plan.steps) {
|
|
4257
|
-
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
4258
|
-
stdout: "pipe",
|
|
4259
|
-
stderr: "pipe",
|
|
4260
|
-
env: process.env
|
|
4262
|
+
var __export2 = (target, all) => {
|
|
4263
|
+
for (var name in all)
|
|
4264
|
+
__defProp2(target, name, {
|
|
4265
|
+
get: all[name],
|
|
4266
|
+
enumerable: true,
|
|
4267
|
+
configurable: true,
|
|
4268
|
+
set: __exportSetter2.bind(all, name)
|
|
4261
4269
|
});
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
machineId: plan.machineId,
|
|
4269
|
-
mode: "apply",
|
|
4270
|
-
steps: plan.steps,
|
|
4271
|
-
executed
|
|
4270
|
+
};
|
|
4271
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4272
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4273
|
+
var require_postgres_array = __commonJS((exports) => {
|
|
4274
|
+
exports.parse = function(source, transform) {
|
|
4275
|
+
return new ArrayParser(source, transform).parse();
|
|
4272
4276
|
};
|
|
4273
|
-
}
|
|
4274
4277
|
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
}
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4278
|
+
class ArrayParser {
|
|
4279
|
+
constructor(source, transform) {
|
|
4280
|
+
this.source = source;
|
|
4281
|
+
this.transform = transform || identity;
|
|
4282
|
+
this.position = 0;
|
|
4283
|
+
this.entries = [];
|
|
4284
|
+
this.recorded = [];
|
|
4285
|
+
this.dimension = 0;
|
|
4286
|
+
}
|
|
4287
|
+
isEof() {
|
|
4288
|
+
return this.position >= this.source.length;
|
|
4289
|
+
}
|
|
4290
|
+
nextCharacter() {
|
|
4291
|
+
var character = this.source[this.position++];
|
|
4292
|
+
if (character === "\\") {
|
|
4293
|
+
return {
|
|
4294
|
+
value: this.source[this.position++],
|
|
4295
|
+
escaped: true
|
|
4296
|
+
};
|
|
4297
|
+
}
|
|
4298
|
+
return {
|
|
4299
|
+
value: character,
|
|
4300
|
+
escaped: false
|
|
4301
|
+
};
|
|
4302
|
+
}
|
|
4303
|
+
record(character) {
|
|
4304
|
+
this.recorded.push(character);
|
|
4305
|
+
}
|
|
4306
|
+
newEntry(includeEmpty) {
|
|
4307
|
+
var entry;
|
|
4308
|
+
if (this.recorded.length > 0 || includeEmpty) {
|
|
4309
|
+
entry = this.recorded.join("");
|
|
4310
|
+
if (entry === "NULL" && !includeEmpty) {
|
|
4311
|
+
entry = null;
|
|
4312
|
+
}
|
|
4313
|
+
if (entry !== null)
|
|
4314
|
+
entry = this.transform(entry);
|
|
4315
|
+
this.entries.push(entry);
|
|
4316
|
+
this.recorded = [];
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
consumeDimensions() {
|
|
4320
|
+
if (this.source[0] === "[") {
|
|
4321
|
+
while (!this.isEof()) {
|
|
4322
|
+
var char = this.nextCharacter();
|
|
4323
|
+
if (char.value === "=")
|
|
4324
|
+
break;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
}
|
|
4328
|
+
parse(nested) {
|
|
4329
|
+
var character, parser, quote2;
|
|
4330
|
+
this.consumeDimensions();
|
|
4331
|
+
while (!this.isEof()) {
|
|
4332
|
+
character = this.nextCharacter();
|
|
4333
|
+
if (character.value === "{" && !quote2) {
|
|
4334
|
+
this.dimension++;
|
|
4335
|
+
if (this.dimension > 1) {
|
|
4336
|
+
parser = new ArrayParser(this.source.substr(this.position - 1), this.transform);
|
|
4337
|
+
this.entries.push(parser.parse(true));
|
|
4338
|
+
this.position += parser.position - 2;
|
|
4339
|
+
}
|
|
4340
|
+
} else if (character.value === "}" && !quote2) {
|
|
4341
|
+
this.dimension--;
|
|
4342
|
+
if (!this.dimension) {
|
|
4343
|
+
this.newEntry();
|
|
4344
|
+
if (nested)
|
|
4345
|
+
return this.entries;
|
|
4346
|
+
}
|
|
4347
|
+
} else if (character.value === '"' && !character.escaped) {
|
|
4348
|
+
if (quote2)
|
|
4349
|
+
this.newEntry(true);
|
|
4350
|
+
quote2 = !quote2;
|
|
4351
|
+
} else if (character.value === "," && !quote2) {
|
|
4352
|
+
this.newEntry();
|
|
4353
|
+
} else {
|
|
4354
|
+
this.record(character.value);
|
|
4355
|
+
}
|
|
4356
|
+
}
|
|
4357
|
+
if (this.dimension !== 0) {
|
|
4358
|
+
throw new Error("array dimension not balanced");
|
|
4359
|
+
}
|
|
4360
|
+
return this.entries;
|
|
4361
|
+
}
|
|
4287
4362
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
const keyPath = join3(certDir(), `${primary}-key.pem`);
|
|
4291
|
-
const steps = [];
|
|
4292
|
-
if (platform2() === "darwin") {
|
|
4293
|
-
steps.push({
|
|
4294
|
-
id: "mkcert-install-macos",
|
|
4295
|
-
title: "Install mkcert on macOS",
|
|
4296
|
-
command: "brew install mkcert nss",
|
|
4297
|
-
manager: "brew"
|
|
4298
|
-
});
|
|
4299
|
-
} else {
|
|
4300
|
-
steps.push({
|
|
4301
|
-
id: "mkcert-install-linux",
|
|
4302
|
-
title: "Install mkcert on Linux",
|
|
4303
|
-
command: "sudo apt-get update && sudo apt-get install -y mkcert libnss3-tools",
|
|
4304
|
-
manager: "apt",
|
|
4305
|
-
privileged: true
|
|
4306
|
-
});
|
|
4363
|
+
function identity(value) {
|
|
4364
|
+
return value;
|
|
4307
4365
|
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
}
|
|
4325
|
-
|
|
4326
|
-
function
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4366
|
+
});
|
|
4367
|
+
var require_arrayParser = __commonJS((exports, module) => {
|
|
4368
|
+
var array = require_postgres_array();
|
|
4369
|
+
module.exports = {
|
|
4370
|
+
create: function(source, transform) {
|
|
4371
|
+
return {
|
|
4372
|
+
parse: function() {
|
|
4373
|
+
return array.parse(source, transform);
|
|
4374
|
+
}
|
|
4375
|
+
};
|
|
4376
|
+
}
|
|
4377
|
+
};
|
|
4378
|
+
});
|
|
4379
|
+
var require_postgres_date = __commonJS((exports, module) => {
|
|
4380
|
+
var DATE_TIME = /(\d{1,})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})(\.\d{1,})?.*?( BC)?$/;
|
|
4381
|
+
var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
|
|
4382
|
+
var TIME_ZONE = /([Z+-])(\d{2})?:?(\d{2})?:?(\d{2})?/;
|
|
4383
|
+
var INFINITY = /^-?infinity$/;
|
|
4384
|
+
module.exports = function parseDate(isoDate) {
|
|
4385
|
+
if (INFINITY.test(isoDate)) {
|
|
4386
|
+
return Number(isoDate.replace("i", "I"));
|
|
4387
|
+
}
|
|
4388
|
+
var matches = DATE_TIME.exec(isoDate);
|
|
4389
|
+
if (!matches) {
|
|
4390
|
+
return getDate(isoDate) || null;
|
|
4391
|
+
}
|
|
4392
|
+
var isBC = !!matches[8];
|
|
4393
|
+
var year = parseInt(matches[1], 10);
|
|
4394
|
+
if (isBC) {
|
|
4395
|
+
year = bcYearToNegativeYear(year);
|
|
4396
|
+
}
|
|
4397
|
+
var month = parseInt(matches[2], 10) - 1;
|
|
4398
|
+
var day = matches[3];
|
|
4399
|
+
var hour = parseInt(matches[4], 10);
|
|
4400
|
+
var minute = parseInt(matches[5], 10);
|
|
4401
|
+
var second = parseInt(matches[6], 10);
|
|
4402
|
+
var ms = matches[7];
|
|
4403
|
+
ms = ms ? 1000 * parseFloat(ms) : 0;
|
|
4404
|
+
var date;
|
|
4405
|
+
var offset = timeZoneOffset(isoDate);
|
|
4406
|
+
if (offset != null) {
|
|
4407
|
+
date = new Date(Date.UTC(year, month, day, hour, minute, second, ms));
|
|
4408
|
+
if (is0To99(year)) {
|
|
4409
|
+
date.setUTCFullYear(year);
|
|
4410
|
+
}
|
|
4411
|
+
if (offset !== 0) {
|
|
4412
|
+
date.setTime(date.getTime() - offset);
|
|
4413
|
+
}
|
|
4414
|
+
} else {
|
|
4415
|
+
date = new Date(year, month, day, hour, minute, second, ms);
|
|
4416
|
+
if (is0To99(year)) {
|
|
4417
|
+
date.setFullYear(year);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
return date;
|
|
4421
|
+
};
|
|
4422
|
+
function getDate(isoDate) {
|
|
4423
|
+
var matches = DATE.exec(isoDate);
|
|
4424
|
+
if (!matches) {
|
|
4425
|
+
return;
|
|
4426
|
+
}
|
|
4427
|
+
var year = parseInt(matches[1], 10);
|
|
4428
|
+
var isBC = !!matches[4];
|
|
4429
|
+
if (isBC) {
|
|
4430
|
+
year = bcYearToNegativeYear(year);
|
|
4431
|
+
}
|
|
4432
|
+
var month = parseInt(matches[2], 10) - 1;
|
|
4433
|
+
var day = matches[3];
|
|
4434
|
+
var date = new Date(year, month, day);
|
|
4435
|
+
if (is0To99(year)) {
|
|
4436
|
+
date.setFullYear(year);
|
|
4437
|
+
}
|
|
4438
|
+
return date;
|
|
4332
4439
|
}
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
stdout: "pipe",
|
|
4337
|
-
stderr: "pipe",
|
|
4338
|
-
env: process.env
|
|
4339
|
-
});
|
|
4340
|
-
if (result.exitCode !== 0) {
|
|
4341
|
-
throw new Error(`Certificate step failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
4440
|
+
function timeZoneOffset(isoDate) {
|
|
4441
|
+
if (isoDate.endsWith("+00")) {
|
|
4442
|
+
return 0;
|
|
4342
4443
|
}
|
|
4343
|
-
|
|
4444
|
+
var zone = TIME_ZONE.exec(isoDate.split(" ")[1]);
|
|
4445
|
+
if (!zone)
|
|
4446
|
+
return;
|
|
4447
|
+
var type = zone[1];
|
|
4448
|
+
if (type === "Z") {
|
|
4449
|
+
return 0;
|
|
4450
|
+
}
|
|
4451
|
+
var sign = type === "-" ? -1 : 1;
|
|
4452
|
+
var offset = parseInt(zone[2], 10) * 3600 + parseInt(zone[3] || 0, 10) * 60 + parseInt(zone[4] || 0, 10);
|
|
4453
|
+
return offset * sign * 1000;
|
|
4344
4454
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
mode: "apply",
|
|
4348
|
-
steps: plan.steps,
|
|
4349
|
-
executed
|
|
4350
|
-
};
|
|
4351
|
-
}
|
|
4352
|
-
|
|
4353
|
-
// src/commands/dns.ts
|
|
4354
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4355
|
-
import { join as join4 } from "path";
|
|
4356
|
-
function getDnsPath() {
|
|
4357
|
-
return join4(getDataDir(), "dns.json");
|
|
4358
|
-
}
|
|
4359
|
-
function readMappings() {
|
|
4360
|
-
const path = getDnsPath();
|
|
4361
|
-
if (!existsSync3(path))
|
|
4362
|
-
return [];
|
|
4363
|
-
return JSON.parse(readFileSync2(path, "utf8"));
|
|
4364
|
-
}
|
|
4365
|
-
function writeMappings(mappings) {
|
|
4366
|
-
const path = getDnsPath();
|
|
4367
|
-
ensureParentDir(path);
|
|
4368
|
-
writeFileSync2(path, `${JSON.stringify(mappings, null, 2)}
|
|
4369
|
-
`, "utf8");
|
|
4370
|
-
return path;
|
|
4371
|
-
}
|
|
4372
|
-
function addDomainMapping(domain, port, targetHost = "127.0.0.1") {
|
|
4373
|
-
const mappings = readMappings().filter((entry) => entry.domain !== domain);
|
|
4374
|
-
mappings.push({ domain, port, targetHost });
|
|
4375
|
-
writeMappings(mappings);
|
|
4376
|
-
return mappings.sort((left, right) => left.domain.localeCompare(right.domain));
|
|
4377
|
-
}
|
|
4378
|
-
function listDomainMappings() {
|
|
4379
|
-
return readMappings().sort((left, right) => left.domain.localeCompare(right.domain));
|
|
4380
|
-
}
|
|
4381
|
-
function renderDomainMapping(domain) {
|
|
4382
|
-
const entry = readMappings().find((mapping) => mapping.domain === domain);
|
|
4383
|
-
if (!entry) {
|
|
4384
|
-
throw new Error(`Domain mapping not found: ${domain}`);
|
|
4455
|
+
function bcYearToNegativeYear(year) {
|
|
4456
|
+
return -(year - 1);
|
|
4385
4457
|
}
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
caddySnippet: `${entry.domain} {
|
|
4389
|
-
reverse_proxy 127.0.0.1:${entry.port}
|
|
4390
|
-
tls ${join4(getDataDir(), "certs", `${entry.domain}.pem`)} ${join4(getDataDir(), "certs", `${entry.domain}-key.pem`)}
|
|
4391
|
-
}`,
|
|
4392
|
-
certPath: join4(getDataDir(), "certs", `${entry.domain}.pem`),
|
|
4393
|
-
keyPath: join4(getDataDir(), "certs", `${entry.domain}-key.pem`)
|
|
4394
|
-
};
|
|
4395
|
-
}
|
|
4396
|
-
|
|
4397
|
-
// src/commands/diff.ts
|
|
4398
|
-
function packageNames(machine) {
|
|
4399
|
-
return (machine.packages || []).map((pkg) => pkg.name).sort();
|
|
4400
|
-
}
|
|
4401
|
-
function fileTargets(machine) {
|
|
4402
|
-
return (machine.files || []).map((file) => `${file.source}->${file.target}`).sort();
|
|
4403
|
-
}
|
|
4404
|
-
function diffMachines(leftMachineId, rightMachineId) {
|
|
4405
|
-
const left = getManifestMachine(leftMachineId);
|
|
4406
|
-
if (!left) {
|
|
4407
|
-
throw new Error(`Machine not found in manifest: ${leftMachineId}`);
|
|
4458
|
+
function is0To99(num) {
|
|
4459
|
+
return num >= 0 && num < 100;
|
|
4408
4460
|
}
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4461
|
+
});
|
|
4462
|
+
var require_mutable = __commonJS((exports, module) => {
|
|
4463
|
+
module.exports = extend;
|
|
4464
|
+
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
|
4465
|
+
function extend(target) {
|
|
4466
|
+
for (var i = 1;i < arguments.length; i++) {
|
|
4467
|
+
var source = arguments[i];
|
|
4468
|
+
for (var key in source) {
|
|
4469
|
+
if (hasOwnProperty.call(source, key)) {
|
|
4470
|
+
target[key] = source[key];
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
return target;
|
|
4412
4475
|
}
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
const rightPackages = new Set(packageNames(right));
|
|
4421
|
-
const leftFiles = new Set(fileTargets(left));
|
|
4422
|
-
const rightFiles = new Set(fileTargets(right));
|
|
4423
|
-
return {
|
|
4424
|
-
leftMachineId: left.id,
|
|
4425
|
-
rightMachineId: right.id,
|
|
4426
|
-
changedFields,
|
|
4427
|
-
missingPackages: {
|
|
4428
|
-
leftOnly: [...leftPackages].filter((pkg) => !rightPackages.has(pkg)),
|
|
4429
|
-
rightOnly: [...rightPackages].filter((pkg) => !leftPackages.has(pkg))
|
|
4430
|
-
},
|
|
4431
|
-
missingFiles: {
|
|
4432
|
-
leftOnly: [...leftFiles].filter((file) => !rightFiles.has(file)),
|
|
4433
|
-
rightOnly: [...rightFiles].filter((file) => !leftFiles.has(file))
|
|
4476
|
+
});
|
|
4477
|
+
var require_postgres_interval = __commonJS((exports, module) => {
|
|
4478
|
+
var extend = require_mutable();
|
|
4479
|
+
module.exports = PostgresInterval;
|
|
4480
|
+
function PostgresInterval(raw) {
|
|
4481
|
+
if (!(this instanceof PostgresInterval)) {
|
|
4482
|
+
return new PostgresInterval(raw);
|
|
4434
4483
|
}
|
|
4435
|
-
|
|
4436
|
-
}
|
|
4437
|
-
|
|
4438
|
-
// src/commands/install-claude.ts
|
|
4439
|
-
var AI_CLI_PACKAGES = {
|
|
4440
|
-
claude: "@anthropic-ai/claude-code",
|
|
4441
|
-
codex: "@openai/codex",
|
|
4442
|
-
gemini: "@google/gemini-cli"
|
|
4443
|
-
};
|
|
4444
|
-
function normalizeTools(tools) {
|
|
4445
|
-
if (!tools || tools.length === 0) {
|
|
4446
|
-
return ["claude", "codex", "gemini"];
|
|
4484
|
+
extend(this, parse(raw));
|
|
4447
4485
|
}
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4486
|
+
var properties = ["seconds", "minutes", "hours", "days", "months", "years"];
|
|
4487
|
+
PostgresInterval.prototype.toPostgres = function() {
|
|
4488
|
+
var filtered = properties.filter(this.hasOwnProperty, this);
|
|
4489
|
+
if (this.milliseconds && filtered.indexOf("seconds") < 0) {
|
|
4490
|
+
filtered.push("seconds");
|
|
4451
4491
|
}
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
}));
|
|
4462
|
-
}
|
|
4463
|
-
function buildClaudeInstallPlan(machineId, tools) {
|
|
4464
|
-
const machine = (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
4465
|
-
return {
|
|
4466
|
-
machineId: machine.id,
|
|
4467
|
-
mode: "plan",
|
|
4468
|
-
steps: buildInstallSteps(machine, tools),
|
|
4469
|
-
executed: 0
|
|
4492
|
+
if (filtered.length === 0)
|
|
4493
|
+
return "0";
|
|
4494
|
+
return filtered.map(function(property) {
|
|
4495
|
+
var value = this[property] || 0;
|
|
4496
|
+
if (property === "seconds" && this.milliseconds) {
|
|
4497
|
+
value = (value + this.milliseconds / 1000).toFixed(6).replace(/\.?0+$/, "");
|
|
4498
|
+
}
|
|
4499
|
+
return value + " " + property;
|
|
4500
|
+
}, this).join(" ");
|
|
4470
4501
|
};
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
}
|
|
4479
|
-
let executed = 0;
|
|
4480
|
-
for (const step of plan.steps) {
|
|
4481
|
-
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
4482
|
-
stdout: "pipe",
|
|
4483
|
-
stderr: "pipe",
|
|
4484
|
-
env: process.env
|
|
4485
|
-
});
|
|
4486
|
-
if (result.exitCode !== 0) {
|
|
4487
|
-
throw new Error(`AI CLI install failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
4488
|
-
}
|
|
4489
|
-
executed += 1;
|
|
4490
|
-
}
|
|
4491
|
-
return {
|
|
4492
|
-
machineId: plan.machineId,
|
|
4493
|
-
mode: "apply",
|
|
4494
|
-
steps: plan.steps,
|
|
4495
|
-
executed
|
|
4502
|
+
var propertiesISOEquivalent = {
|
|
4503
|
+
years: "Y",
|
|
4504
|
+
months: "M",
|
|
4505
|
+
days: "D",
|
|
4506
|
+
hours: "H",
|
|
4507
|
+
minutes: "M",
|
|
4508
|
+
seconds: "S"
|
|
4496
4509
|
};
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
return
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
manager: "brew"
|
|
4510
|
+
var dateProperties = ["years", "months", "days"];
|
|
4511
|
+
var timeProperties = ["hours", "minutes", "seconds"];
|
|
4512
|
+
PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = function() {
|
|
4513
|
+
var datePart = dateProperties.map(buildProperty, this).join("");
|
|
4514
|
+
var timePart = timeProperties.map(buildProperty, this).join("");
|
|
4515
|
+
return "P" + datePart + "T" + timePart;
|
|
4516
|
+
function buildProperty(property) {
|
|
4517
|
+
var value = this[property] || 0;
|
|
4518
|
+
if (property === "seconds" && this.milliseconds) {
|
|
4519
|
+
value = (value + this.milliseconds / 1000).toFixed(6).replace(/0+$/, "");
|
|
4508
4520
|
}
|
|
4509
|
-
|
|
4521
|
+
return value + propertiesISOEquivalent[property];
|
|
4522
|
+
}
|
|
4523
|
+
};
|
|
4524
|
+
var NUMBER = "([+-]?\\d+)";
|
|
4525
|
+
var YEAR = NUMBER + "\\s+years?";
|
|
4526
|
+
var MONTH = NUMBER + "\\s+mons?";
|
|
4527
|
+
var DAY = NUMBER + "\\s+days?";
|
|
4528
|
+
var TIME = "([+-])?([\\d]*):(\\d\\d):(\\d\\d)\\.?(\\d{1,6})?";
|
|
4529
|
+
var INTERVAL = new RegExp([YEAR, MONTH, DAY, TIME].map(function(regexString) {
|
|
4530
|
+
return "(" + regexString + ")?";
|
|
4531
|
+
}).join("\\s*"));
|
|
4532
|
+
var positions = {
|
|
4533
|
+
years: 2,
|
|
4534
|
+
months: 4,
|
|
4535
|
+
days: 6,
|
|
4536
|
+
hours: 9,
|
|
4537
|
+
minutes: 10,
|
|
4538
|
+
seconds: 11,
|
|
4539
|
+
milliseconds: 12
|
|
4540
|
+
};
|
|
4541
|
+
var negatives = ["hours", "minutes", "seconds", "milliseconds"];
|
|
4542
|
+
function parseMilliseconds(fraction) {
|
|
4543
|
+
var microseconds = fraction + "000000".slice(fraction.length);
|
|
4544
|
+
return parseInt(microseconds, 10) / 1000;
|
|
4510
4545
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
{
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4546
|
+
function parse(interval) {
|
|
4547
|
+
if (!interval)
|
|
4548
|
+
return {};
|
|
4549
|
+
var matches = INTERVAL.exec(interval);
|
|
4550
|
+
var isNegative = matches[8] === "-";
|
|
4551
|
+
return Object.keys(positions).reduce(function(parsed, property) {
|
|
4552
|
+
var position = positions[property];
|
|
4553
|
+
var value = matches[position];
|
|
4554
|
+
if (!value)
|
|
4555
|
+
return parsed;
|
|
4556
|
+
value = property === "milliseconds" ? parseMilliseconds(value) : parseInt(value, 10);
|
|
4557
|
+
if (!value)
|
|
4558
|
+
return parsed;
|
|
4559
|
+
if (isNegative && ~negatives.indexOf(property)) {
|
|
4560
|
+
value *= -1;
|
|
4518
4561
|
}
|
|
4519
|
-
|
|
4562
|
+
parsed[property] = value;
|
|
4563
|
+
return parsed;
|
|
4564
|
+
}, {});
|
|
4520
4565
|
}
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
privileged: true
|
|
4566
|
+
});
|
|
4567
|
+
var require_postgres_bytea = __commonJS((exports, module) => {
|
|
4568
|
+
var bufferFrom = Buffer.from || Buffer;
|
|
4569
|
+
module.exports = function parseBytea(input) {
|
|
4570
|
+
if (/^\\x/.test(input)) {
|
|
4571
|
+
return bufferFrom(input.substr(2), "hex");
|
|
4528
4572
|
}
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
}
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
stdout: "pipe",
|
|
4551
|
-
stderr: "pipe",
|
|
4552
|
-
env: process.env
|
|
4553
|
-
});
|
|
4554
|
-
if (result.exitCode !== 0) {
|
|
4555
|
-
throw new Error(`Tailscale install failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
4573
|
+
var output = "";
|
|
4574
|
+
var i = 0;
|
|
4575
|
+
while (i < input.length) {
|
|
4576
|
+
if (input[i] !== "\\") {
|
|
4577
|
+
output += input[i];
|
|
4578
|
+
++i;
|
|
4579
|
+
} else {
|
|
4580
|
+
if (/[0-7]{3}/.test(input.substr(i + 1, 3))) {
|
|
4581
|
+
output += String.fromCharCode(parseInt(input.substr(i + 1, 3), 8));
|
|
4582
|
+
i += 4;
|
|
4583
|
+
} else {
|
|
4584
|
+
var backslashes = 1;
|
|
4585
|
+
while (i + backslashes < input.length && input[i + backslashes] === "\\") {
|
|
4586
|
+
backslashes++;
|
|
4587
|
+
}
|
|
4588
|
+
for (var k = 0;k < Math.floor(backslashes / 2); ++k) {
|
|
4589
|
+
output += "\\";
|
|
4590
|
+
}
|
|
4591
|
+
i += Math.floor(backslashes / 2) * 2;
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4556
4594
|
}
|
|
4557
|
-
|
|
4558
|
-
}
|
|
4559
|
-
return {
|
|
4560
|
-
machineId: plan.machineId,
|
|
4561
|
-
mode: "apply",
|
|
4562
|
-
steps: plan.steps,
|
|
4563
|
-
executed
|
|
4595
|
+
return bufferFrom(output, "binary");
|
|
4564
4596
|
};
|
|
4565
|
-
}
|
|
4566
|
-
|
|
4567
|
-
// src/commands/notifications.ts
|
|
4568
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4569
|
-
var notificationChannelSchema = exports_external.object({
|
|
4570
|
-
id: exports_external.string(),
|
|
4571
|
-
type: exports_external.enum(["email", "webhook", "command"]),
|
|
4572
|
-
target: exports_external.string(),
|
|
4573
|
-
events: exports_external.array(exports_external.string()),
|
|
4574
|
-
enabled: exports_external.boolean()
|
|
4575
|
-
});
|
|
4576
|
-
var notificationConfigSchema = exports_external.object({
|
|
4577
|
-
version: exports_external.literal(1),
|
|
4578
|
-
updatedAt: exports_external.string().optional(),
|
|
4579
|
-
channels: exports_external.array(notificationChannelSchema)
|
|
4580
4597
|
});
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
return getDefaultNotificationConfig();
|
|
4598
|
+
var require_textParsers = __commonJS((exports, module) => {
|
|
4599
|
+
var array = require_postgres_array();
|
|
4600
|
+
var arrayParser = require_arrayParser();
|
|
4601
|
+
var parseDate = require_postgres_date();
|
|
4602
|
+
var parseInterval = require_postgres_interval();
|
|
4603
|
+
var parseByteA = require_postgres_bytea();
|
|
4604
|
+
function allowNull(fn) {
|
|
4605
|
+
return function nullAllowed(value) {
|
|
4606
|
+
if (value === null)
|
|
4607
|
+
return value;
|
|
4608
|
+
return fn(value);
|
|
4609
|
+
};
|
|
4594
4610
|
}
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
const nextConfig = {
|
|
4600
|
-
version: 1,
|
|
4601
|
-
updatedAt: new Date().toISOString(),
|
|
4602
|
-
channels: sortChannels(config.channels)
|
|
4603
|
-
};
|
|
4604
|
-
writeFileSync3(path, `${JSON.stringify(nextConfig, null, 2)}
|
|
4605
|
-
`, "utf8");
|
|
4606
|
-
return nextConfig;
|
|
4607
|
-
}
|
|
4608
|
-
function listNotificationChannels() {
|
|
4609
|
-
return readNotificationConfig();
|
|
4610
|
-
}
|
|
4611
|
-
function addNotificationChannel(channel) {
|
|
4612
|
-
const config = readNotificationConfig();
|
|
4613
|
-
const channels = config.channels.filter((entry) => entry.id !== channel.id);
|
|
4614
|
-
channels.push({
|
|
4615
|
-
...channel,
|
|
4616
|
-
events: [...new Set(channel.events)]
|
|
4617
|
-
});
|
|
4618
|
-
return writeNotificationConfig({ ...config, channels });
|
|
4619
|
-
}
|
|
4620
|
-
function removeNotificationChannel(channelId) {
|
|
4621
|
-
const config = readNotificationConfig();
|
|
4622
|
-
return writeNotificationConfig({
|
|
4623
|
-
...config,
|
|
4624
|
-
channels: config.channels.filter((channel) => channel.id !== channelId)
|
|
4625
|
-
});
|
|
4626
|
-
}
|
|
4627
|
-
function buildNotificationPreview(channel, event, message) {
|
|
4628
|
-
if (channel.type === "email") {
|
|
4629
|
-
return `send email to ${channel.target}: [${event}] ${message}`;
|
|
4611
|
+
function parseBool(value) {
|
|
4612
|
+
if (value === null)
|
|
4613
|
+
return value;
|
|
4614
|
+
return value === "TRUE" || value === "t" || value === "true" || value === "y" || value === "yes" || value === "on" || value === "1";
|
|
4630
4615
|
}
|
|
4631
|
-
|
|
4632
|
-
|
|
4616
|
+
function parseBoolArray(value) {
|
|
4617
|
+
if (!value)
|
|
4618
|
+
return null;
|
|
4619
|
+
return array.parse(value, parseBool);
|
|
4633
4620
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
4637
|
-
const channel = readNotificationConfig().channels.find((entry) => entry.id === channelId);
|
|
4638
|
-
if (!channel) {
|
|
4639
|
-
throw new Error(`Notification channel not found: ${channelId}`);
|
|
4621
|
+
function parseBaseTenInt(string) {
|
|
4622
|
+
return parseInt(string, 10);
|
|
4640
4623
|
}
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
mode: "plan",
|
|
4646
|
-
delivered: false,
|
|
4647
|
-
preview
|
|
4648
|
-
};
|
|
4624
|
+
function parseIntegerArray(value) {
|
|
4625
|
+
if (!value)
|
|
4626
|
+
return null;
|
|
4627
|
+
return array.parse(value, allowNull(parseBaseTenInt));
|
|
4649
4628
|
}
|
|
4650
|
-
|
|
4651
|
-
|
|
4629
|
+
function parseBigIntegerArray(value) {
|
|
4630
|
+
if (!value)
|
|
4631
|
+
return null;
|
|
4632
|
+
return array.parse(value, allowNull(function(entry) {
|
|
4633
|
+
return parseBigInteger(entry).trim();
|
|
4634
|
+
}));
|
|
4652
4635
|
}
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
stderr: "pipe",
|
|
4657
|
-
env: process.env
|
|
4658
|
-
});
|
|
4659
|
-
if (result.exitCode !== 0) {
|
|
4660
|
-
throw new Error(`Notification command failed (${channel.id}): ${result.stderr.toString().trim()}`);
|
|
4636
|
+
var parsePointArray = function(value) {
|
|
4637
|
+
if (!value) {
|
|
4638
|
+
return null;
|
|
4661
4639
|
}
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
preview
|
|
4668
|
-
};
|
|
4669
|
-
}
|
|
4670
|
-
|
|
4671
|
-
// src/commands/ports.ts
|
|
4672
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
4673
|
-
|
|
4674
|
-
// src/db.ts
|
|
4675
|
-
import { hostname as hostname2 } from "os";
|
|
4676
|
-
|
|
4677
|
-
// node_modules/@hasna/cloud/dist/index.js
|
|
4678
|
-
import { createRequire } from "module";
|
|
4679
|
-
import { Database } from "bun:sqlite";
|
|
4680
|
-
import {
|
|
4681
|
-
existsSync as existsSync5,
|
|
4682
|
-
mkdirSync as mkdirSync2,
|
|
4683
|
-
readdirSync,
|
|
4684
|
-
copyFileSync
|
|
4685
|
-
} from "fs";
|
|
4686
|
-
import { homedir as homedir4 } from "os";
|
|
4687
|
-
import { join as join5, relative } from "path";
|
|
4688
|
-
import { existsSync as existsSync22, mkdirSync as mkdirSync22, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
4689
|
-
import { homedir as homedir22 } from "os";
|
|
4690
|
-
import { join as join22 } from "path";
|
|
4691
|
-
import { readdirSync as readdirSync2, existsSync as existsSync32 } from "fs";
|
|
4692
|
-
import { join as join32 } from "path";
|
|
4693
|
-
import { homedir as homedir32 } from "os";
|
|
4694
|
-
import { homedir as homedir42 } from "os";
|
|
4695
|
-
import { join as join42 } from "path";
|
|
4696
|
-
import { join as join6, dirname as dirname3 } from "path";
|
|
4697
|
-
import { homedir as homedir5, platform as platform3 } from "os";
|
|
4698
|
-
var __create = Object.create;
|
|
4699
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
4700
|
-
var __defProp2 = Object.defineProperty;
|
|
4701
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4702
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
4703
|
-
function __accessProp(key) {
|
|
4704
|
-
return this[key];
|
|
4705
|
-
}
|
|
4706
|
-
var __toESMCache_node;
|
|
4707
|
-
var __toESMCache_esm;
|
|
4708
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
4709
|
-
var canCache = mod != null && typeof mod === "object";
|
|
4710
|
-
if (canCache) {
|
|
4711
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
4712
|
-
var cached = cache.get(mod);
|
|
4713
|
-
if (cached)
|
|
4714
|
-
return cached;
|
|
4715
|
-
}
|
|
4716
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
4717
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target;
|
|
4718
|
-
for (let key of __getOwnPropNames(mod))
|
|
4719
|
-
if (!__hasOwnProp.call(to, key))
|
|
4720
|
-
__defProp2(to, key, {
|
|
4721
|
-
get: __accessProp.bind(mod, key),
|
|
4722
|
-
enumerable: true
|
|
4723
|
-
});
|
|
4724
|
-
if (canCache)
|
|
4725
|
-
cache.set(mod, to);
|
|
4726
|
-
return to;
|
|
4727
|
-
};
|
|
4728
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
4729
|
-
var __returnValue2 = (v) => v;
|
|
4730
|
-
function __exportSetter2(name, newValue) {
|
|
4731
|
-
this[name] = __returnValue2.bind(null, newValue);
|
|
4732
|
-
}
|
|
4733
|
-
var __export2 = (target, all) => {
|
|
4734
|
-
for (var name in all)
|
|
4735
|
-
__defProp2(target, name, {
|
|
4736
|
-
get: all[name],
|
|
4737
|
-
enumerable: true,
|
|
4738
|
-
configurable: true,
|
|
4739
|
-
set: __exportSetter2.bind(all, name)
|
|
4640
|
+
var p = arrayParser.create(value, function(entry) {
|
|
4641
|
+
if (entry !== null) {
|
|
4642
|
+
entry = parsePoint(entry);
|
|
4643
|
+
}
|
|
4644
|
+
return entry;
|
|
4740
4645
|
});
|
|
4741
|
-
|
|
4742
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
4743
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4744
|
-
var require_postgres_array = __commonJS((exports) => {
|
|
4745
|
-
exports.parse = function(source, transform) {
|
|
4746
|
-
return new ArrayParser(source, transform).parse();
|
|
4646
|
+
return p.parse();
|
|
4747
4647
|
};
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
this.source = source;
|
|
4752
|
-
this.transform = transform || identity;
|
|
4753
|
-
this.position = 0;
|
|
4754
|
-
this.entries = [];
|
|
4755
|
-
this.recorded = [];
|
|
4756
|
-
this.dimension = 0;
|
|
4757
|
-
}
|
|
4758
|
-
isEof() {
|
|
4759
|
-
return this.position >= this.source.length;
|
|
4648
|
+
var parseFloatArray = function(value) {
|
|
4649
|
+
if (!value) {
|
|
4650
|
+
return null;
|
|
4760
4651
|
}
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
return {
|
|
4765
|
-
value: this.source[this.position++],
|
|
4766
|
-
escaped: true
|
|
4767
|
-
};
|
|
4652
|
+
var p = arrayParser.create(value, function(entry) {
|
|
4653
|
+
if (entry !== null) {
|
|
4654
|
+
entry = parseFloat(entry);
|
|
4768
4655
|
}
|
|
4769
|
-
return
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4656
|
+
return entry;
|
|
4657
|
+
});
|
|
4658
|
+
return p.parse();
|
|
4659
|
+
};
|
|
4660
|
+
var parseStringArray = function(value) {
|
|
4661
|
+
if (!value) {
|
|
4662
|
+
return null;
|
|
4773
4663
|
}
|
|
4774
|
-
|
|
4775
|
-
|
|
4664
|
+
var p = arrayParser.create(value);
|
|
4665
|
+
return p.parse();
|
|
4666
|
+
};
|
|
4667
|
+
var parseDateArray = function(value) {
|
|
4668
|
+
if (!value) {
|
|
4669
|
+
return null;
|
|
4776
4670
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
entry = this.recorded.join("");
|
|
4781
|
-
if (entry === "NULL" && !includeEmpty) {
|
|
4782
|
-
entry = null;
|
|
4783
|
-
}
|
|
4784
|
-
if (entry !== null)
|
|
4785
|
-
entry = this.transform(entry);
|
|
4786
|
-
this.entries.push(entry);
|
|
4787
|
-
this.recorded = [];
|
|
4671
|
+
var p = arrayParser.create(value, function(entry) {
|
|
4672
|
+
if (entry !== null) {
|
|
4673
|
+
entry = parseDate(entry);
|
|
4788
4674
|
}
|
|
4675
|
+
return entry;
|
|
4676
|
+
});
|
|
4677
|
+
return p.parse();
|
|
4678
|
+
};
|
|
4679
|
+
var parseIntervalArray = function(value) {
|
|
4680
|
+
if (!value) {
|
|
4681
|
+
return null;
|
|
4789
4682
|
}
|
|
4790
|
-
|
|
4791
|
-
if (
|
|
4792
|
-
|
|
4793
|
-
var char = this.nextCharacter();
|
|
4794
|
-
if (char.value === "=")
|
|
4795
|
-
break;
|
|
4796
|
-
}
|
|
4683
|
+
var p = arrayParser.create(value, function(entry) {
|
|
4684
|
+
if (entry !== null) {
|
|
4685
|
+
entry = parseInterval(entry);
|
|
4797
4686
|
}
|
|
4687
|
+
return entry;
|
|
4688
|
+
});
|
|
4689
|
+
return p.parse();
|
|
4690
|
+
};
|
|
4691
|
+
var parseByteAArray = function(value) {
|
|
4692
|
+
if (!value) {
|
|
4693
|
+
return null;
|
|
4798
4694
|
}
|
|
4799
|
-
parse(
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
this.entries.push(parser.parse(true));
|
|
4809
|
-
this.position += parser.position - 2;
|
|
4810
|
-
}
|
|
4811
|
-
} else if (character.value === "}" && !quote3) {
|
|
4812
|
-
this.dimension--;
|
|
4813
|
-
if (!this.dimension) {
|
|
4814
|
-
this.newEntry();
|
|
4815
|
-
if (nested)
|
|
4816
|
-
return this.entries;
|
|
4817
|
-
}
|
|
4818
|
-
} else if (character.value === '"' && !character.escaped) {
|
|
4819
|
-
if (quote3)
|
|
4820
|
-
this.newEntry(true);
|
|
4821
|
-
quote3 = !quote3;
|
|
4822
|
-
} else if (character.value === "," && !quote3) {
|
|
4823
|
-
this.newEntry();
|
|
4824
|
-
} else {
|
|
4825
|
-
this.record(character.value);
|
|
4826
|
-
}
|
|
4827
|
-
}
|
|
4828
|
-
if (this.dimension !== 0) {
|
|
4829
|
-
throw new Error("array dimension not balanced");
|
|
4830
|
-
}
|
|
4831
|
-
return this.entries;
|
|
4695
|
+
return array.parse(value, allowNull(parseByteA));
|
|
4696
|
+
};
|
|
4697
|
+
var parseInteger = function(value) {
|
|
4698
|
+
return parseInt(value, 10);
|
|
4699
|
+
};
|
|
4700
|
+
var parseBigInteger = function(value) {
|
|
4701
|
+
var valStr = String(value);
|
|
4702
|
+
if (/^\d+$/.test(valStr)) {
|
|
4703
|
+
return valStr;
|
|
4832
4704
|
}
|
|
4833
|
-
}
|
|
4834
|
-
function identity(value) {
|
|
4835
4705
|
return value;
|
|
4836
|
-
}
|
|
4837
|
-
});
|
|
4838
|
-
var require_arrayParser = __commonJS((exports, module) => {
|
|
4839
|
-
var array = require_postgres_array();
|
|
4840
|
-
module.exports = {
|
|
4841
|
-
create: function(source, transform) {
|
|
4842
|
-
return {
|
|
4843
|
-
parse: function() {
|
|
4844
|
-
return array.parse(source, transform);
|
|
4845
|
-
}
|
|
4846
|
-
};
|
|
4847
|
-
}
|
|
4848
4706
|
};
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
var DATE = /^(\d{1,})-(\d{2})-(\d{2})( BC)?$/;
|
|
4853
|
-
var TIME_ZONE = /([Z+-])(\d{2})?:?(\d{2})?:?(\d{2})?/;
|
|
4854
|
-
var INFINITY = /^-?infinity$/;
|
|
4855
|
-
module.exports = function parseDate(isoDate) {
|
|
4856
|
-
if (INFINITY.test(isoDate)) {
|
|
4857
|
-
return Number(isoDate.replace("i", "I"));
|
|
4707
|
+
var parseJsonArray = function(value) {
|
|
4708
|
+
if (!value) {
|
|
4709
|
+
return null;
|
|
4858
4710
|
}
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4711
|
+
return array.parse(value, allowNull(JSON.parse));
|
|
4712
|
+
};
|
|
4713
|
+
var parsePoint = function(value) {
|
|
4714
|
+
if (value[0] !== "(") {
|
|
4715
|
+
return null;
|
|
4862
4716
|
}
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4717
|
+
value = value.substring(1, value.length - 1).split(",");
|
|
4718
|
+
return {
|
|
4719
|
+
x: parseFloat(value[0]),
|
|
4720
|
+
y: parseFloat(value[1])
|
|
4721
|
+
};
|
|
4722
|
+
};
|
|
4723
|
+
var parseCircle = function(value) {
|
|
4724
|
+
if (value[0] !== "<" && value[1] !== "(") {
|
|
4725
|
+
return null;
|
|
4867
4726
|
}
|
|
4868
|
-
var
|
|
4869
|
-
var
|
|
4870
|
-
var
|
|
4871
|
-
var
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
ms = ms ? 1000 * parseFloat(ms) : 0;
|
|
4875
|
-
var date;
|
|
4876
|
-
var offset = timeZoneOffset(isoDate);
|
|
4877
|
-
if (offset != null) {
|
|
4878
|
-
date = new Date(Date.UTC(year, month, day, hour, minute, second, ms));
|
|
4879
|
-
if (is0To99(year)) {
|
|
4880
|
-
date.setUTCFullYear(year);
|
|
4727
|
+
var point = "(";
|
|
4728
|
+
var radius = "";
|
|
4729
|
+
var pointParsed = false;
|
|
4730
|
+
for (var i = 2;i < value.length - 1; i++) {
|
|
4731
|
+
if (!pointParsed) {
|
|
4732
|
+
point += value[i];
|
|
4881
4733
|
}
|
|
4882
|
-
if (
|
|
4883
|
-
|
|
4734
|
+
if (value[i] === ")") {
|
|
4735
|
+
pointParsed = true;
|
|
4736
|
+
continue;
|
|
4737
|
+
} else if (!pointParsed) {
|
|
4738
|
+
continue;
|
|
4884
4739
|
}
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
if (is0To99(year)) {
|
|
4888
|
-
date.setFullYear(year);
|
|
4740
|
+
if (value[i] === ",") {
|
|
4741
|
+
continue;
|
|
4889
4742
|
}
|
|
4743
|
+
radius += value[i];
|
|
4890
4744
|
}
|
|
4891
|
-
|
|
4745
|
+
var result = parsePoint(point);
|
|
4746
|
+
result.radius = parseFloat(radius);
|
|
4747
|
+
return result;
|
|
4892
4748
|
};
|
|
4893
|
-
function
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4749
|
+
var init = function(register) {
|
|
4750
|
+
register(20, parseBigInteger);
|
|
4751
|
+
register(21, parseInteger);
|
|
4752
|
+
register(23, parseInteger);
|
|
4753
|
+
register(26, parseInteger);
|
|
4754
|
+
register(700, parseFloat);
|
|
4755
|
+
register(701, parseFloat);
|
|
4756
|
+
register(16, parseBool);
|
|
4757
|
+
register(1082, parseDate);
|
|
4758
|
+
register(1114, parseDate);
|
|
4759
|
+
register(1184, parseDate);
|
|
4760
|
+
register(600, parsePoint);
|
|
4761
|
+
register(651, parseStringArray);
|
|
4762
|
+
register(718, parseCircle);
|
|
4763
|
+
register(1000, parseBoolArray);
|
|
4764
|
+
register(1001, parseByteAArray);
|
|
4765
|
+
register(1005, parseIntegerArray);
|
|
4766
|
+
register(1007, parseIntegerArray);
|
|
4767
|
+
register(1028, parseIntegerArray);
|
|
4768
|
+
register(1016, parseBigIntegerArray);
|
|
4769
|
+
register(1017, parsePointArray);
|
|
4770
|
+
register(1021, parseFloatArray);
|
|
4771
|
+
register(1022, parseFloatArray);
|
|
4772
|
+
register(1231, parseFloatArray);
|
|
4773
|
+
register(1014, parseStringArray);
|
|
4774
|
+
register(1015, parseStringArray);
|
|
4775
|
+
register(1008, parseStringArray);
|
|
4776
|
+
register(1009, parseStringArray);
|
|
4777
|
+
register(1040, parseStringArray);
|
|
4778
|
+
register(1041, parseStringArray);
|
|
4779
|
+
register(1115, parseDateArray);
|
|
4780
|
+
register(1182, parseDateArray);
|
|
4781
|
+
register(1185, parseDateArray);
|
|
4782
|
+
register(1186, parseInterval);
|
|
4783
|
+
register(1187, parseIntervalArray);
|
|
4784
|
+
register(17, parseByteA);
|
|
4785
|
+
register(114, JSON.parse.bind(JSON));
|
|
4786
|
+
register(3802, JSON.parse.bind(JSON));
|
|
4787
|
+
register(199, parseJsonArray);
|
|
4788
|
+
register(3807, parseJsonArray);
|
|
4789
|
+
register(3907, parseStringArray);
|
|
4790
|
+
register(2951, parseStringArray);
|
|
4791
|
+
register(791, parseStringArray);
|
|
4792
|
+
register(1183, parseStringArray);
|
|
4793
|
+
register(1270, parseStringArray);
|
|
4794
|
+
};
|
|
4795
|
+
module.exports = {
|
|
4796
|
+
init
|
|
4797
|
+
};
|
|
4798
|
+
});
|
|
4799
|
+
var require_pg_int8 = __commonJS((exports, module) => {
|
|
4800
|
+
var BASE = 1e6;
|
|
4801
|
+
function readInt8(buffer) {
|
|
4802
|
+
var high = buffer.readInt32BE(0);
|
|
4803
|
+
var low = buffer.readUInt32BE(4);
|
|
4804
|
+
var sign = "";
|
|
4805
|
+
if (high < 0) {
|
|
4806
|
+
high = ~high + (low === 0);
|
|
4807
|
+
low = ~low + 1 >>> 0;
|
|
4808
|
+
sign = "-";
|
|
4897
4809
|
}
|
|
4898
|
-
var
|
|
4899
|
-
var
|
|
4900
|
-
|
|
4901
|
-
|
|
4810
|
+
var result = "";
|
|
4811
|
+
var carry;
|
|
4812
|
+
var t;
|
|
4813
|
+
var digits;
|
|
4814
|
+
var pad;
|
|
4815
|
+
var l;
|
|
4816
|
+
var i;
|
|
4817
|
+
{
|
|
4818
|
+
carry = high % BASE;
|
|
4819
|
+
high = high / BASE >>> 0;
|
|
4820
|
+
t = 4294967296 * carry + low;
|
|
4821
|
+
low = t / BASE >>> 0;
|
|
4822
|
+
digits = "" + (t - BASE * low);
|
|
4823
|
+
if (low === 0 && high === 0) {
|
|
4824
|
+
return sign + digits + result;
|
|
4825
|
+
}
|
|
4826
|
+
pad = "";
|
|
4827
|
+
l = 6 - digits.length;
|
|
4828
|
+
for (i = 0;i < l; i++) {
|
|
4829
|
+
pad += "0";
|
|
4830
|
+
}
|
|
4831
|
+
result = pad + digits + result;
|
|
4902
4832
|
}
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4833
|
+
{
|
|
4834
|
+
carry = high % BASE;
|
|
4835
|
+
high = high / BASE >>> 0;
|
|
4836
|
+
t = 4294967296 * carry + low;
|
|
4837
|
+
low = t / BASE >>> 0;
|
|
4838
|
+
digits = "" + (t - BASE * low);
|
|
4839
|
+
if (low === 0 && high === 0) {
|
|
4840
|
+
return sign + digits + result;
|
|
4841
|
+
}
|
|
4842
|
+
pad = "";
|
|
4843
|
+
l = 6 - digits.length;
|
|
4844
|
+
for (i = 0;i < l; i++) {
|
|
4845
|
+
pad += "0";
|
|
4846
|
+
}
|
|
4847
|
+
result = pad + digits + result;
|
|
4908
4848
|
}
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4849
|
+
{
|
|
4850
|
+
carry = high % BASE;
|
|
4851
|
+
high = high / BASE >>> 0;
|
|
4852
|
+
t = 4294967296 * carry + low;
|
|
4853
|
+
low = t / BASE >>> 0;
|
|
4854
|
+
digits = "" + (t - BASE * low);
|
|
4855
|
+
if (low === 0 && high === 0) {
|
|
4856
|
+
return sign + digits + result;
|
|
4857
|
+
}
|
|
4858
|
+
pad = "";
|
|
4859
|
+
l = 6 - digits.length;
|
|
4860
|
+
for (i = 0;i < l; i++) {
|
|
4861
|
+
pad += "0";
|
|
4862
|
+
}
|
|
4863
|
+
result = pad + digits + result;
|
|
4914
4864
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
return 0;
|
|
4865
|
+
{
|
|
4866
|
+
carry = high % BASE;
|
|
4867
|
+
t = 4294967296 * carry + low;
|
|
4868
|
+
digits = "" + t % BASE;
|
|
4869
|
+
return sign + digits + result;
|
|
4921
4870
|
}
|
|
4922
|
-
var sign = type === "-" ? -1 : 1;
|
|
4923
|
-
var offset = parseInt(zone[2], 10) * 3600 + parseInt(zone[3] || 0, 10) * 60 + parseInt(zone[4] || 0, 10);
|
|
4924
|
-
return offset * sign * 1000;
|
|
4925
|
-
}
|
|
4926
|
-
function bcYearToNegativeYear(year) {
|
|
4927
|
-
return -(year - 1);
|
|
4928
|
-
}
|
|
4929
|
-
function is0To99(num) {
|
|
4930
|
-
return num >= 0 && num < 100;
|
|
4931
4871
|
}
|
|
4872
|
+
module.exports = readInt8;
|
|
4932
4873
|
});
|
|
4933
|
-
var
|
|
4934
|
-
|
|
4935
|
-
var
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4874
|
+
var require_binaryParsers = __commonJS((exports, module) => {
|
|
4875
|
+
var parseInt64 = require_pg_int8();
|
|
4876
|
+
var parseBits = function(data, bits, offset, invert, callback) {
|
|
4877
|
+
offset = offset || 0;
|
|
4878
|
+
invert = invert || false;
|
|
4879
|
+
callback = callback || function(lastValue, newValue, bits2) {
|
|
4880
|
+
return lastValue * Math.pow(2, bits2) + newValue;
|
|
4881
|
+
};
|
|
4882
|
+
var offsetBytes = offset >> 3;
|
|
4883
|
+
var inv = function(value) {
|
|
4884
|
+
if (invert) {
|
|
4885
|
+
return ~value & 255;
|
|
4943
4886
|
}
|
|
4887
|
+
return value;
|
|
4888
|
+
};
|
|
4889
|
+
var mask = 255;
|
|
4890
|
+
var firstBits = 8 - offset % 8;
|
|
4891
|
+
if (bits < firstBits) {
|
|
4892
|
+
mask = 255 << 8 - bits & 255;
|
|
4893
|
+
firstBits = bits;
|
|
4944
4894
|
}
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
});
|
|
4948
|
-
var require_postgres_interval = __commonJS((exports, module) => {
|
|
4949
|
-
var extend = require_mutable();
|
|
4950
|
-
module.exports = PostgresInterval;
|
|
4951
|
-
function PostgresInterval(raw) {
|
|
4952
|
-
if (!(this instanceof PostgresInterval)) {
|
|
4953
|
-
return new PostgresInterval(raw);
|
|
4895
|
+
if (offset) {
|
|
4896
|
+
mask = mask >> offset % 8;
|
|
4954
4897
|
}
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
PostgresInterval.prototype.toPostgres = function() {
|
|
4959
|
-
var filtered = properties.filter(this.hasOwnProperty, this);
|
|
4960
|
-
if (this.milliseconds && filtered.indexOf("seconds") < 0) {
|
|
4961
|
-
filtered.push("seconds");
|
|
4898
|
+
var result = 0;
|
|
4899
|
+
if (offset % 8 + bits >= 8) {
|
|
4900
|
+
result = callback(0, inv(data[offsetBytes]) & mask, firstBits);
|
|
4962
4901
|
}
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
var value = this[property] || 0;
|
|
4967
|
-
if (property === "seconds" && this.milliseconds) {
|
|
4968
|
-
value = (value + this.milliseconds / 1000).toFixed(6).replace(/\.?0+$/, "");
|
|
4969
|
-
}
|
|
4970
|
-
return value + " " + property;
|
|
4971
|
-
}, this).join(" ");
|
|
4972
|
-
};
|
|
4973
|
-
var propertiesISOEquivalent = {
|
|
4974
|
-
years: "Y",
|
|
4975
|
-
months: "M",
|
|
4976
|
-
days: "D",
|
|
4977
|
-
hours: "H",
|
|
4978
|
-
minutes: "M",
|
|
4979
|
-
seconds: "S"
|
|
4980
|
-
};
|
|
4981
|
-
var dateProperties = ["years", "months", "days"];
|
|
4982
|
-
var timeProperties = ["hours", "minutes", "seconds"];
|
|
4983
|
-
PostgresInterval.prototype.toISOString = PostgresInterval.prototype.toISO = function() {
|
|
4984
|
-
var datePart = dateProperties.map(buildProperty, this).join("");
|
|
4985
|
-
var timePart = timeProperties.map(buildProperty, this).join("");
|
|
4986
|
-
return "P" + datePart + "T" + timePart;
|
|
4987
|
-
function buildProperty(property) {
|
|
4988
|
-
var value = this[property] || 0;
|
|
4989
|
-
if (property === "seconds" && this.milliseconds) {
|
|
4990
|
-
value = (value + this.milliseconds / 1000).toFixed(6).replace(/0+$/, "");
|
|
4991
|
-
}
|
|
4992
|
-
return value + propertiesISOEquivalent[property];
|
|
4902
|
+
var bytes = bits + offset >> 3;
|
|
4903
|
+
for (var i = offsetBytes + 1;i < bytes; i++) {
|
|
4904
|
+
result = callback(result, inv(data[i]), 8);
|
|
4993
4905
|
}
|
|
4906
|
+
var lastBits = (bits + offset) % 8;
|
|
4907
|
+
if (lastBits > 0) {
|
|
4908
|
+
result = callback(result, inv(data[bytes]) >> 8 - lastBits, lastBits);
|
|
4909
|
+
}
|
|
4910
|
+
return result;
|
|
4994
4911
|
};
|
|
4995
|
-
var
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
return "(" + regexString + ")?";
|
|
5002
|
-
}).join("\\s*"));
|
|
5003
|
-
var positions = {
|
|
5004
|
-
years: 2,
|
|
5005
|
-
months: 4,
|
|
5006
|
-
days: 6,
|
|
5007
|
-
hours: 9,
|
|
5008
|
-
minutes: 10,
|
|
5009
|
-
seconds: 11,
|
|
5010
|
-
milliseconds: 12
|
|
5011
|
-
};
|
|
5012
|
-
var negatives = ["hours", "minutes", "seconds", "milliseconds"];
|
|
5013
|
-
function parseMilliseconds(fraction) {
|
|
5014
|
-
var microseconds = fraction + "000000".slice(fraction.length);
|
|
5015
|
-
return parseInt(microseconds, 10) / 1000;
|
|
5016
|
-
}
|
|
5017
|
-
function parse(interval) {
|
|
5018
|
-
if (!interval)
|
|
5019
|
-
return {};
|
|
5020
|
-
var matches = INTERVAL.exec(interval);
|
|
5021
|
-
var isNegative = matches[8] === "-";
|
|
5022
|
-
return Object.keys(positions).reduce(function(parsed, property) {
|
|
5023
|
-
var position = positions[property];
|
|
5024
|
-
var value = matches[position];
|
|
5025
|
-
if (!value)
|
|
5026
|
-
return parsed;
|
|
5027
|
-
value = property === "milliseconds" ? parseMilliseconds(value) : parseInt(value, 10);
|
|
5028
|
-
if (!value)
|
|
5029
|
-
return parsed;
|
|
5030
|
-
if (isNegative && ~negatives.indexOf(property)) {
|
|
5031
|
-
value *= -1;
|
|
5032
|
-
}
|
|
5033
|
-
parsed[property] = value;
|
|
5034
|
-
return parsed;
|
|
5035
|
-
}, {});
|
|
5036
|
-
}
|
|
5037
|
-
});
|
|
5038
|
-
var require_postgres_bytea = __commonJS((exports, module) => {
|
|
5039
|
-
var bufferFrom = Buffer.from || Buffer;
|
|
5040
|
-
module.exports = function parseBytea(input) {
|
|
5041
|
-
if (/^\\x/.test(input)) {
|
|
5042
|
-
return bufferFrom(input.substr(2), "hex");
|
|
4912
|
+
var parseFloatFromBits = function(data, precisionBits, exponentBits) {
|
|
4913
|
+
var bias = Math.pow(2, exponentBits - 1) - 1;
|
|
4914
|
+
var sign = parseBits(data, 1);
|
|
4915
|
+
var exponent = parseBits(data, exponentBits, 1);
|
|
4916
|
+
if (exponent === 0) {
|
|
4917
|
+
return 0;
|
|
5043
4918
|
}
|
|
5044
|
-
var
|
|
5045
|
-
var
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
if (
|
|
5052
|
-
|
|
5053
|
-
i += 4;
|
|
5054
|
-
} else {
|
|
5055
|
-
var backslashes = 1;
|
|
5056
|
-
while (i + backslashes < input.length && input[i + backslashes] === "\\") {
|
|
5057
|
-
backslashes++;
|
|
5058
|
-
}
|
|
5059
|
-
for (var k = 0;k < Math.floor(backslashes / 2); ++k) {
|
|
5060
|
-
output += "\\";
|
|
5061
|
-
}
|
|
5062
|
-
i += Math.floor(backslashes / 2) * 2;
|
|
4919
|
+
var precisionBitsCounter = 1;
|
|
4920
|
+
var parsePrecisionBits = function(lastValue, newValue, bits) {
|
|
4921
|
+
if (lastValue === 0) {
|
|
4922
|
+
lastValue = 1;
|
|
4923
|
+
}
|
|
4924
|
+
for (var i = 1;i <= bits; i++) {
|
|
4925
|
+
precisionBitsCounter /= 2;
|
|
4926
|
+
if ((newValue & 1 << bits - i) > 0) {
|
|
4927
|
+
lastValue += precisionBitsCounter;
|
|
5063
4928
|
}
|
|
5064
4929
|
}
|
|
5065
|
-
|
|
5066
|
-
return bufferFrom(output, "binary");
|
|
5067
|
-
};
|
|
5068
|
-
});
|
|
5069
|
-
var require_textParsers = __commonJS((exports, module) => {
|
|
5070
|
-
var array = require_postgres_array();
|
|
5071
|
-
var arrayParser = require_arrayParser();
|
|
5072
|
-
var parseDate = require_postgres_date();
|
|
5073
|
-
var parseInterval = require_postgres_interval();
|
|
5074
|
-
var parseByteA = require_postgres_bytea();
|
|
5075
|
-
function allowNull(fn) {
|
|
5076
|
-
return function nullAllowed(value) {
|
|
5077
|
-
if (value === null)
|
|
5078
|
-
return value;
|
|
5079
|
-
return fn(value);
|
|
4930
|
+
return lastValue;
|
|
5080
4931
|
};
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
return value === "TRUE" || value === "t" || value === "true" || value === "y" || value === "yes" || value === "on" || value === "1";
|
|
5086
|
-
}
|
|
5087
|
-
function parseBoolArray(value) {
|
|
5088
|
-
if (!value)
|
|
5089
|
-
return null;
|
|
5090
|
-
return array.parse(value, parseBool);
|
|
5091
|
-
}
|
|
5092
|
-
function parseBaseTenInt(string) {
|
|
5093
|
-
return parseInt(string, 10);
|
|
5094
|
-
}
|
|
5095
|
-
function parseIntegerArray(value) {
|
|
5096
|
-
if (!value)
|
|
5097
|
-
return null;
|
|
5098
|
-
return array.parse(value, allowNull(parseBaseTenInt));
|
|
5099
|
-
}
|
|
5100
|
-
function parseBigIntegerArray(value) {
|
|
5101
|
-
if (!value)
|
|
5102
|
-
return null;
|
|
5103
|
-
return array.parse(value, allowNull(function(entry) {
|
|
5104
|
-
return parseBigInteger(entry).trim();
|
|
5105
|
-
}));
|
|
5106
|
-
}
|
|
5107
|
-
var parsePointArray = function(value) {
|
|
5108
|
-
if (!value) {
|
|
5109
|
-
return null;
|
|
5110
|
-
}
|
|
5111
|
-
var p = arrayParser.create(value, function(entry) {
|
|
5112
|
-
if (entry !== null) {
|
|
5113
|
-
entry = parsePoint(entry);
|
|
5114
|
-
}
|
|
5115
|
-
return entry;
|
|
5116
|
-
});
|
|
5117
|
-
return p.parse();
|
|
5118
|
-
};
|
|
5119
|
-
var parseFloatArray = function(value) {
|
|
5120
|
-
if (!value) {
|
|
5121
|
-
return null;
|
|
5122
|
-
}
|
|
5123
|
-
var p = arrayParser.create(value, function(entry) {
|
|
5124
|
-
if (entry !== null) {
|
|
5125
|
-
entry = parseFloat(entry);
|
|
4932
|
+
var mantissa = parseBits(data, precisionBits, exponentBits + 1, false, parsePrecisionBits);
|
|
4933
|
+
if (exponent == Math.pow(2, exponentBits + 1) - 1) {
|
|
4934
|
+
if (mantissa === 0) {
|
|
4935
|
+
return sign === 0 ? Infinity : -Infinity;
|
|
5126
4936
|
}
|
|
5127
|
-
return
|
|
5128
|
-
});
|
|
5129
|
-
return p.parse();
|
|
5130
|
-
};
|
|
5131
|
-
var parseStringArray = function(value) {
|
|
5132
|
-
if (!value) {
|
|
5133
|
-
return null;
|
|
5134
|
-
}
|
|
5135
|
-
var p = arrayParser.create(value);
|
|
5136
|
-
return p.parse();
|
|
5137
|
-
};
|
|
5138
|
-
var parseDateArray = function(value) {
|
|
5139
|
-
if (!value) {
|
|
5140
|
-
return null;
|
|
4937
|
+
return NaN;
|
|
5141
4938
|
}
|
|
5142
|
-
|
|
5143
|
-
if (entry !== null) {
|
|
5144
|
-
entry = parseDate(entry);
|
|
5145
|
-
}
|
|
5146
|
-
return entry;
|
|
5147
|
-
});
|
|
5148
|
-
return p.parse();
|
|
4939
|
+
return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa;
|
|
5149
4940
|
};
|
|
5150
|
-
var
|
|
5151
|
-
if (
|
|
5152
|
-
return
|
|
4941
|
+
var parseInt16 = function(value) {
|
|
4942
|
+
if (parseBits(value, 1) == 1) {
|
|
4943
|
+
return -1 * (parseBits(value, 15, 1, true) + 1);
|
|
5153
4944
|
}
|
|
5154
|
-
|
|
5155
|
-
if (entry !== null) {
|
|
5156
|
-
entry = parseInterval(entry);
|
|
5157
|
-
}
|
|
5158
|
-
return entry;
|
|
5159
|
-
});
|
|
5160
|
-
return p.parse();
|
|
4945
|
+
return parseBits(value, 15, 1);
|
|
5161
4946
|
};
|
|
5162
|
-
var
|
|
5163
|
-
if (
|
|
5164
|
-
return
|
|
4947
|
+
var parseInt32 = function(value) {
|
|
4948
|
+
if (parseBits(value, 1) == 1) {
|
|
4949
|
+
return -1 * (parseBits(value, 31, 1, true) + 1);
|
|
5165
4950
|
}
|
|
5166
|
-
return
|
|
4951
|
+
return parseBits(value, 31, 1);
|
|
5167
4952
|
};
|
|
5168
|
-
var
|
|
5169
|
-
return
|
|
4953
|
+
var parseFloat32 = function(value) {
|
|
4954
|
+
return parseFloatFromBits(value, 23, 8);
|
|
5170
4955
|
};
|
|
5171
|
-
var
|
|
5172
|
-
|
|
5173
|
-
if (/^\d+$/.test(valStr)) {
|
|
5174
|
-
return valStr;
|
|
5175
|
-
}
|
|
5176
|
-
return value;
|
|
4956
|
+
var parseFloat64 = function(value) {
|
|
4957
|
+
return parseFloatFromBits(value, 52, 11);
|
|
5177
4958
|
};
|
|
5178
|
-
var
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
return array.parse(value, allowNull(JSON.parse));
|
|
5183
|
-
};
|
|
5184
|
-
var parsePoint = function(value) {
|
|
5185
|
-
if (value[0] !== "(") {
|
|
5186
|
-
return null;
|
|
5187
|
-
}
|
|
5188
|
-
value = value.substring(1, value.length - 1).split(",");
|
|
5189
|
-
return {
|
|
5190
|
-
x: parseFloat(value[0]),
|
|
5191
|
-
y: parseFloat(value[1])
|
|
5192
|
-
};
|
|
5193
|
-
};
|
|
5194
|
-
var parseCircle = function(value) {
|
|
5195
|
-
if (value[0] !== "<" && value[1] !== "(") {
|
|
5196
|
-
return null;
|
|
5197
|
-
}
|
|
5198
|
-
var point = "(";
|
|
5199
|
-
var radius = "";
|
|
5200
|
-
var pointParsed = false;
|
|
5201
|
-
for (var i = 2;i < value.length - 1; i++) {
|
|
5202
|
-
if (!pointParsed) {
|
|
5203
|
-
point += value[i];
|
|
5204
|
-
}
|
|
5205
|
-
if (value[i] === ")") {
|
|
5206
|
-
pointParsed = true;
|
|
5207
|
-
continue;
|
|
5208
|
-
} else if (!pointParsed) {
|
|
5209
|
-
continue;
|
|
5210
|
-
}
|
|
5211
|
-
if (value[i] === ",") {
|
|
5212
|
-
continue;
|
|
5213
|
-
}
|
|
5214
|
-
radius += value[i];
|
|
5215
|
-
}
|
|
5216
|
-
var result = parsePoint(point);
|
|
5217
|
-
result.radius = parseFloat(radius);
|
|
5218
|
-
return result;
|
|
5219
|
-
};
|
|
5220
|
-
var init = function(register) {
|
|
5221
|
-
register(20, parseBigInteger);
|
|
5222
|
-
register(21, parseInteger);
|
|
5223
|
-
register(23, parseInteger);
|
|
5224
|
-
register(26, parseInteger);
|
|
5225
|
-
register(700, parseFloat);
|
|
5226
|
-
register(701, parseFloat);
|
|
5227
|
-
register(16, parseBool);
|
|
5228
|
-
register(1082, parseDate);
|
|
5229
|
-
register(1114, parseDate);
|
|
5230
|
-
register(1184, parseDate);
|
|
5231
|
-
register(600, parsePoint);
|
|
5232
|
-
register(651, parseStringArray);
|
|
5233
|
-
register(718, parseCircle);
|
|
5234
|
-
register(1000, parseBoolArray);
|
|
5235
|
-
register(1001, parseByteAArray);
|
|
5236
|
-
register(1005, parseIntegerArray);
|
|
5237
|
-
register(1007, parseIntegerArray);
|
|
5238
|
-
register(1028, parseIntegerArray);
|
|
5239
|
-
register(1016, parseBigIntegerArray);
|
|
5240
|
-
register(1017, parsePointArray);
|
|
5241
|
-
register(1021, parseFloatArray);
|
|
5242
|
-
register(1022, parseFloatArray);
|
|
5243
|
-
register(1231, parseFloatArray);
|
|
5244
|
-
register(1014, parseStringArray);
|
|
5245
|
-
register(1015, parseStringArray);
|
|
5246
|
-
register(1008, parseStringArray);
|
|
5247
|
-
register(1009, parseStringArray);
|
|
5248
|
-
register(1040, parseStringArray);
|
|
5249
|
-
register(1041, parseStringArray);
|
|
5250
|
-
register(1115, parseDateArray);
|
|
5251
|
-
register(1182, parseDateArray);
|
|
5252
|
-
register(1185, parseDateArray);
|
|
5253
|
-
register(1186, parseInterval);
|
|
5254
|
-
register(1187, parseIntervalArray);
|
|
5255
|
-
register(17, parseByteA);
|
|
5256
|
-
register(114, JSON.parse.bind(JSON));
|
|
5257
|
-
register(3802, JSON.parse.bind(JSON));
|
|
5258
|
-
register(199, parseJsonArray);
|
|
5259
|
-
register(3807, parseJsonArray);
|
|
5260
|
-
register(3907, parseStringArray);
|
|
5261
|
-
register(2951, parseStringArray);
|
|
5262
|
-
register(791, parseStringArray);
|
|
5263
|
-
register(1183, parseStringArray);
|
|
5264
|
-
register(1270, parseStringArray);
|
|
5265
|
-
};
|
|
5266
|
-
module.exports = {
|
|
5267
|
-
init
|
|
5268
|
-
};
|
|
5269
|
-
});
|
|
5270
|
-
var require_pg_int8 = __commonJS((exports, module) => {
|
|
5271
|
-
var BASE = 1e6;
|
|
5272
|
-
function readInt8(buffer) {
|
|
5273
|
-
var high = buffer.readInt32BE(0);
|
|
5274
|
-
var low = buffer.readUInt32BE(4);
|
|
5275
|
-
var sign = "";
|
|
5276
|
-
if (high < 0) {
|
|
5277
|
-
high = ~high + (low === 0);
|
|
5278
|
-
low = ~low + 1 >>> 0;
|
|
5279
|
-
sign = "-";
|
|
5280
|
-
}
|
|
5281
|
-
var result = "";
|
|
5282
|
-
var carry;
|
|
5283
|
-
var t;
|
|
5284
|
-
var digits;
|
|
5285
|
-
var pad;
|
|
5286
|
-
var l;
|
|
5287
|
-
var i;
|
|
5288
|
-
{
|
|
5289
|
-
carry = high % BASE;
|
|
5290
|
-
high = high / BASE >>> 0;
|
|
5291
|
-
t = 4294967296 * carry + low;
|
|
5292
|
-
low = t / BASE >>> 0;
|
|
5293
|
-
digits = "" + (t - BASE * low);
|
|
5294
|
-
if (low === 0 && high === 0) {
|
|
5295
|
-
return sign + digits + result;
|
|
5296
|
-
}
|
|
5297
|
-
pad = "";
|
|
5298
|
-
l = 6 - digits.length;
|
|
5299
|
-
for (i = 0;i < l; i++) {
|
|
5300
|
-
pad += "0";
|
|
5301
|
-
}
|
|
5302
|
-
result = pad + digits + result;
|
|
5303
|
-
}
|
|
5304
|
-
{
|
|
5305
|
-
carry = high % BASE;
|
|
5306
|
-
high = high / BASE >>> 0;
|
|
5307
|
-
t = 4294967296 * carry + low;
|
|
5308
|
-
low = t / BASE >>> 0;
|
|
5309
|
-
digits = "" + (t - BASE * low);
|
|
5310
|
-
if (low === 0 && high === 0) {
|
|
5311
|
-
return sign + digits + result;
|
|
5312
|
-
}
|
|
5313
|
-
pad = "";
|
|
5314
|
-
l = 6 - digits.length;
|
|
5315
|
-
for (i = 0;i < l; i++) {
|
|
5316
|
-
pad += "0";
|
|
5317
|
-
}
|
|
5318
|
-
result = pad + digits + result;
|
|
5319
|
-
}
|
|
5320
|
-
{
|
|
5321
|
-
carry = high % BASE;
|
|
5322
|
-
high = high / BASE >>> 0;
|
|
5323
|
-
t = 4294967296 * carry + low;
|
|
5324
|
-
low = t / BASE >>> 0;
|
|
5325
|
-
digits = "" + (t - BASE * low);
|
|
5326
|
-
if (low === 0 && high === 0) {
|
|
5327
|
-
return sign + digits + result;
|
|
5328
|
-
}
|
|
5329
|
-
pad = "";
|
|
5330
|
-
l = 6 - digits.length;
|
|
5331
|
-
for (i = 0;i < l; i++) {
|
|
5332
|
-
pad += "0";
|
|
5333
|
-
}
|
|
5334
|
-
result = pad + digits + result;
|
|
5335
|
-
}
|
|
5336
|
-
{
|
|
5337
|
-
carry = high % BASE;
|
|
5338
|
-
t = 4294967296 * carry + low;
|
|
5339
|
-
digits = "" + t % BASE;
|
|
5340
|
-
return sign + digits + result;
|
|
5341
|
-
}
|
|
5342
|
-
}
|
|
5343
|
-
module.exports = readInt8;
|
|
5344
|
-
});
|
|
5345
|
-
var require_binaryParsers = __commonJS((exports, module) => {
|
|
5346
|
-
var parseInt64 = require_pg_int8();
|
|
5347
|
-
var parseBits = function(data, bits, offset, invert, callback) {
|
|
5348
|
-
offset = offset || 0;
|
|
5349
|
-
invert = invert || false;
|
|
5350
|
-
callback = callback || function(lastValue, newValue, bits2) {
|
|
5351
|
-
return lastValue * Math.pow(2, bits2) + newValue;
|
|
5352
|
-
};
|
|
5353
|
-
var offsetBytes = offset >> 3;
|
|
5354
|
-
var inv = function(value) {
|
|
5355
|
-
if (invert) {
|
|
5356
|
-
return ~value & 255;
|
|
5357
|
-
}
|
|
5358
|
-
return value;
|
|
5359
|
-
};
|
|
5360
|
-
var mask = 255;
|
|
5361
|
-
var firstBits = 8 - offset % 8;
|
|
5362
|
-
if (bits < firstBits) {
|
|
5363
|
-
mask = 255 << 8 - bits & 255;
|
|
5364
|
-
firstBits = bits;
|
|
5365
|
-
}
|
|
5366
|
-
if (offset) {
|
|
5367
|
-
mask = mask >> offset % 8;
|
|
5368
|
-
}
|
|
5369
|
-
var result = 0;
|
|
5370
|
-
if (offset % 8 + bits >= 8) {
|
|
5371
|
-
result = callback(0, inv(data[offsetBytes]) & mask, firstBits);
|
|
5372
|
-
}
|
|
5373
|
-
var bytes = bits + offset >> 3;
|
|
5374
|
-
for (var i = offsetBytes + 1;i < bytes; i++) {
|
|
5375
|
-
result = callback(result, inv(data[i]), 8);
|
|
5376
|
-
}
|
|
5377
|
-
var lastBits = (bits + offset) % 8;
|
|
5378
|
-
if (lastBits > 0) {
|
|
5379
|
-
result = callback(result, inv(data[bytes]) >> 8 - lastBits, lastBits);
|
|
5380
|
-
}
|
|
5381
|
-
return result;
|
|
5382
|
-
};
|
|
5383
|
-
var parseFloatFromBits = function(data, precisionBits, exponentBits) {
|
|
5384
|
-
var bias = Math.pow(2, exponentBits - 1) - 1;
|
|
5385
|
-
var sign = parseBits(data, 1);
|
|
5386
|
-
var exponent = parseBits(data, exponentBits, 1);
|
|
5387
|
-
if (exponent === 0) {
|
|
5388
|
-
return 0;
|
|
5389
|
-
}
|
|
5390
|
-
var precisionBitsCounter = 1;
|
|
5391
|
-
var parsePrecisionBits = function(lastValue, newValue, bits) {
|
|
5392
|
-
if (lastValue === 0) {
|
|
5393
|
-
lastValue = 1;
|
|
5394
|
-
}
|
|
5395
|
-
for (var i = 1;i <= bits; i++) {
|
|
5396
|
-
precisionBitsCounter /= 2;
|
|
5397
|
-
if ((newValue & 1 << bits - i) > 0) {
|
|
5398
|
-
lastValue += precisionBitsCounter;
|
|
5399
|
-
}
|
|
5400
|
-
}
|
|
5401
|
-
return lastValue;
|
|
5402
|
-
};
|
|
5403
|
-
var mantissa = parseBits(data, precisionBits, exponentBits + 1, false, parsePrecisionBits);
|
|
5404
|
-
if (exponent == Math.pow(2, exponentBits + 1) - 1) {
|
|
5405
|
-
if (mantissa === 0) {
|
|
5406
|
-
return sign === 0 ? Infinity : -Infinity;
|
|
5407
|
-
}
|
|
5408
|
-
return NaN;
|
|
5409
|
-
}
|
|
5410
|
-
return (sign === 0 ? 1 : -1) * Math.pow(2, exponent - bias) * mantissa;
|
|
5411
|
-
};
|
|
5412
|
-
var parseInt16 = function(value) {
|
|
5413
|
-
if (parseBits(value, 1) == 1) {
|
|
5414
|
-
return -1 * (parseBits(value, 15, 1, true) + 1);
|
|
5415
|
-
}
|
|
5416
|
-
return parseBits(value, 15, 1);
|
|
5417
|
-
};
|
|
5418
|
-
var parseInt32 = function(value) {
|
|
5419
|
-
if (parseBits(value, 1) == 1) {
|
|
5420
|
-
return -1 * (parseBits(value, 31, 1, true) + 1);
|
|
5421
|
-
}
|
|
5422
|
-
return parseBits(value, 31, 1);
|
|
5423
|
-
};
|
|
5424
|
-
var parseFloat32 = function(value) {
|
|
5425
|
-
return parseFloatFromBits(value, 23, 8);
|
|
5426
|
-
};
|
|
5427
|
-
var parseFloat64 = function(value) {
|
|
5428
|
-
return parseFloatFromBits(value, 52, 11);
|
|
5429
|
-
};
|
|
5430
|
-
var parseNumeric = function(value) {
|
|
5431
|
-
var sign = parseBits(value, 16, 32);
|
|
5432
|
-
if (sign == 49152) {
|
|
5433
|
-
return NaN;
|
|
4959
|
+
var parseNumeric = function(value) {
|
|
4960
|
+
var sign = parseBits(value, 16, 32);
|
|
4961
|
+
if (sign == 49152) {
|
|
4962
|
+
return NaN;
|
|
5434
4963
|
}
|
|
5435
4964
|
var weight = Math.pow(1e4, parseBits(value, 16, 16));
|
|
5436
4965
|
var result = 0;
|
|
@@ -13858,473 +13387,1267 @@ var init_zod = __esm(() => {
|
|
|
13858
13387
|
init_external();
|
|
13859
13388
|
});
|
|
13860
13389
|
function getDataDir2(serviceName) {
|
|
13861
|
-
const dir =
|
|
13390
|
+
const dir = join3(HASNA_DIR, serviceName);
|
|
13862
13391
|
mkdirSync2(dir, { recursive: true });
|
|
13863
13392
|
return dir;
|
|
13864
13393
|
}
|
|
13865
|
-
function getDbPath2(serviceName) {
|
|
13866
|
-
const dir = getDataDir2(serviceName);
|
|
13867
|
-
return
|
|
13394
|
+
function getDbPath2(serviceName) {
|
|
13395
|
+
const dir = getDataDir2(serviceName);
|
|
13396
|
+
return join3(dir, `${serviceName}.db`);
|
|
13397
|
+
}
|
|
13398
|
+
var HASNA_DIR;
|
|
13399
|
+
var init_dotfile = __esm(() => {
|
|
13400
|
+
HASNA_DIR = join3(homedir3(), ".hasna");
|
|
13401
|
+
});
|
|
13402
|
+
var exports_config = {};
|
|
13403
|
+
__export2(exports_config, {
|
|
13404
|
+
saveCloudConfig: () => saveCloudConfig,
|
|
13405
|
+
getConnectionString: () => getConnectionString,
|
|
13406
|
+
getConfigPath: () => getConfigPath,
|
|
13407
|
+
getConfigDir: () => getConfigDir,
|
|
13408
|
+
getCloudConfig: () => getCloudConfig,
|
|
13409
|
+
createDatabase: () => createDatabase,
|
|
13410
|
+
CloudConfigSchema: () => CloudConfigSchema
|
|
13411
|
+
});
|
|
13412
|
+
function getConfigDir() {
|
|
13413
|
+
return CONFIG_DIR;
|
|
13414
|
+
}
|
|
13415
|
+
function getConfigPath() {
|
|
13416
|
+
return CONFIG_PATH;
|
|
13417
|
+
}
|
|
13418
|
+
function getCloudConfig() {
|
|
13419
|
+
if (!existsSync22(CONFIG_PATH)) {
|
|
13420
|
+
return CloudConfigSchema.parse({});
|
|
13421
|
+
}
|
|
13422
|
+
try {
|
|
13423
|
+
const raw = readFileSync2(CONFIG_PATH, "utf-8");
|
|
13424
|
+
return CloudConfigSchema.parse(JSON.parse(raw));
|
|
13425
|
+
} catch {
|
|
13426
|
+
return CloudConfigSchema.parse({});
|
|
13427
|
+
}
|
|
13428
|
+
}
|
|
13429
|
+
function saveCloudConfig(config) {
|
|
13430
|
+
mkdirSync22(CONFIG_DIR, { recursive: true });
|
|
13431
|
+
writeFileSync2(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
13432
|
+
`, "utf-8");
|
|
13433
|
+
}
|
|
13434
|
+
function getConnectionString(dbName) {
|
|
13435
|
+
const config = getCloudConfig();
|
|
13436
|
+
const { host, port, username, password_env, ssl } = config.rds;
|
|
13437
|
+
if (!host || !username) {
|
|
13438
|
+
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
13439
|
+
}
|
|
13440
|
+
const password = process.env[password_env];
|
|
13441
|
+
if (password === undefined || password === "") {
|
|
13442
|
+
throw new Error(`RDS password not set. Export ${password_env} in your shell or add it to ~/.secrets/hasna/rds/live.env`);
|
|
13443
|
+
}
|
|
13444
|
+
const sslParam = ssl ? "?sslmode=require" : "";
|
|
13445
|
+
return `postgres://${username}:${encodeURIComponent(password)}@${host}:${port}/${dbName}${sslParam}`;
|
|
13446
|
+
}
|
|
13447
|
+
function createDatabase(options) {
|
|
13448
|
+
const config = getCloudConfig();
|
|
13449
|
+
const mode = options.mode ?? config.mode;
|
|
13450
|
+
if (mode === "cloud") {
|
|
13451
|
+
const connStr = options.pgConnectionString ?? getConnectionString(options.service);
|
|
13452
|
+
return new PgAdapter(connStr);
|
|
13453
|
+
}
|
|
13454
|
+
const dbPath = options.sqlitePath ?? getDbPath2(options.service);
|
|
13455
|
+
return new SqliteAdapter(dbPath);
|
|
13456
|
+
}
|
|
13457
|
+
var CloudConfigSchema;
|
|
13458
|
+
var CONFIG_DIR;
|
|
13459
|
+
var CONFIG_PATH;
|
|
13460
|
+
var init_config = __esm(() => {
|
|
13461
|
+
init_zod();
|
|
13462
|
+
init_adapter();
|
|
13463
|
+
init_dotfile();
|
|
13464
|
+
CloudConfigSchema = exports_external2.object({
|
|
13465
|
+
rds: exports_external2.object({
|
|
13466
|
+
host: exports_external2.string().default(""),
|
|
13467
|
+
port: exports_external2.number().default(5432),
|
|
13468
|
+
username: exports_external2.string().default(""),
|
|
13469
|
+
password_env: exports_external2.string().default("HASNA_RDS_PASSWORD"),
|
|
13470
|
+
ssl: exports_external2.boolean().default(true)
|
|
13471
|
+
}).default({}),
|
|
13472
|
+
mode: exports_external2.enum(["local", "cloud", "hybrid"]).default("hybrid"),
|
|
13473
|
+
auto_sync_interval_minutes: exports_external2.number().default(0),
|
|
13474
|
+
feedback_endpoint: exports_external2.string().default("https://feedback.hasna.com/api/v1/feedback"),
|
|
13475
|
+
sync: exports_external2.object({
|
|
13476
|
+
schedule_minutes: exports_external2.number().default(0)
|
|
13477
|
+
}).default({})
|
|
13478
|
+
});
|
|
13479
|
+
CONFIG_DIR = join22(homedir22(), ".hasna", "cloud");
|
|
13480
|
+
CONFIG_PATH = join22(CONFIG_DIR, "config.json");
|
|
13481
|
+
});
|
|
13482
|
+
var exports_discover = {};
|
|
13483
|
+
__export2(exports_discover, {
|
|
13484
|
+
isSyncExcludedTable: () => isSyncExcludedTable,
|
|
13485
|
+
getServiceDbPath: () => getServiceDbPath,
|
|
13486
|
+
discoverSyncableServices: () => discoverSyncableServices,
|
|
13487
|
+
discoverServices: () => discoverServices,
|
|
13488
|
+
SYNC_EXCLUDED_TABLE_PATTERNS: () => SYNC_EXCLUDED_TABLE_PATTERNS,
|
|
13489
|
+
KNOWN_PG_SERVICES: () => KNOWN_PG_SERVICES
|
|
13490
|
+
});
|
|
13491
|
+
function isSyncExcludedTable(table) {
|
|
13492
|
+
return SYNC_EXCLUDED_TABLE_PATTERNS.some((p) => p.test(table));
|
|
13493
|
+
}
|
|
13494
|
+
function discoverServices() {
|
|
13495
|
+
const dataDir = join32(homedir32(), ".hasna");
|
|
13496
|
+
if (!existsSync32(dataDir))
|
|
13497
|
+
return [];
|
|
13498
|
+
try {
|
|
13499
|
+
const entries = readdirSync2(dataDir, { withFileTypes: true });
|
|
13500
|
+
return entries.filter((e) => {
|
|
13501
|
+
if (!e.isDirectory())
|
|
13502
|
+
return false;
|
|
13503
|
+
if (e.name === "cloud" || e.name.startsWith("."))
|
|
13504
|
+
return false;
|
|
13505
|
+
return true;
|
|
13506
|
+
}).map((e) => e.name).sort();
|
|
13507
|
+
} catch {
|
|
13508
|
+
return [];
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
function discoverSyncableServices() {
|
|
13512
|
+
const local = discoverServices();
|
|
13513
|
+
const pgSet = new Set(KNOWN_PG_SERVICES);
|
|
13514
|
+
return local.filter((s) => pgSet.has(s));
|
|
13515
|
+
}
|
|
13516
|
+
function getServiceDbPath(service) {
|
|
13517
|
+
const dataDir = join32(homedir32(), ".hasna", service);
|
|
13518
|
+
if (!existsSync32(dataDir))
|
|
13519
|
+
return null;
|
|
13520
|
+
const candidates = [
|
|
13521
|
+
join32(dataDir, `${service}.db`),
|
|
13522
|
+
join32(dataDir, "data.db"),
|
|
13523
|
+
join32(dataDir, "database.db")
|
|
13524
|
+
];
|
|
13525
|
+
try {
|
|
13526
|
+
const files = readdirSync2(dataDir);
|
|
13527
|
+
for (const f of files) {
|
|
13528
|
+
if (f.endsWith(".db") && !f.endsWith("-wal") && !f.endsWith("-shm")) {
|
|
13529
|
+
candidates.push(join32(dataDir, f));
|
|
13530
|
+
}
|
|
13531
|
+
}
|
|
13532
|
+
} catch {}
|
|
13533
|
+
for (const p of candidates) {
|
|
13534
|
+
if (existsSync32(p))
|
|
13535
|
+
return p;
|
|
13536
|
+
}
|
|
13537
|
+
return null;
|
|
13538
|
+
}
|
|
13539
|
+
var KNOWN_PG_SERVICES;
|
|
13540
|
+
var SYNC_EXCLUDED_TABLE_PATTERNS;
|
|
13541
|
+
var init_discover = __esm(() => {
|
|
13542
|
+
KNOWN_PG_SERVICES = [
|
|
13543
|
+
"assistants",
|
|
13544
|
+
"attachments",
|
|
13545
|
+
"brains",
|
|
13546
|
+
"configs",
|
|
13547
|
+
"connectors",
|
|
13548
|
+
"contacts",
|
|
13549
|
+
"context",
|
|
13550
|
+
"conversations",
|
|
13551
|
+
"crawl",
|
|
13552
|
+
"deployment",
|
|
13553
|
+
"economy",
|
|
13554
|
+
"emails",
|
|
13555
|
+
"files",
|
|
13556
|
+
"hooks",
|
|
13557
|
+
"implementations",
|
|
13558
|
+
"logs",
|
|
13559
|
+
"mcps",
|
|
13560
|
+
"mementos",
|
|
13561
|
+
"microservices",
|
|
13562
|
+
"predictor",
|
|
13563
|
+
"prompts",
|
|
13564
|
+
"recordings",
|
|
13565
|
+
"researcher",
|
|
13566
|
+
"sandboxes",
|
|
13567
|
+
"search",
|
|
13568
|
+
"secrets",
|
|
13569
|
+
"sessions",
|
|
13570
|
+
"signatures",
|
|
13571
|
+
"skills",
|
|
13572
|
+
"telephony",
|
|
13573
|
+
"terminal",
|
|
13574
|
+
"testers",
|
|
13575
|
+
"tickets",
|
|
13576
|
+
"todos",
|
|
13577
|
+
"wallets"
|
|
13578
|
+
];
|
|
13579
|
+
SYNC_EXCLUDED_TABLE_PATTERNS = [
|
|
13580
|
+
/^sqlite_/,
|
|
13581
|
+
/_fts$/,
|
|
13582
|
+
/_fts_/,
|
|
13583
|
+
/^_sync_/,
|
|
13584
|
+
/^_pg_migrations$/
|
|
13585
|
+
];
|
|
13586
|
+
});
|
|
13587
|
+
init_adapter();
|
|
13588
|
+
init_config();
|
|
13589
|
+
init_config();
|
|
13590
|
+
init_dotfile();
|
|
13591
|
+
|
|
13592
|
+
class SyncProgressTracker {
|
|
13593
|
+
db;
|
|
13594
|
+
progress = new Map;
|
|
13595
|
+
startTimes = new Map;
|
|
13596
|
+
callback;
|
|
13597
|
+
constructor(db, callback) {
|
|
13598
|
+
this.db = db;
|
|
13599
|
+
this.callback = callback;
|
|
13600
|
+
this.ensureResumeTable();
|
|
13601
|
+
}
|
|
13602
|
+
ensureResumeTable() {
|
|
13603
|
+
this.db.exec(`
|
|
13604
|
+
CREATE TABLE IF NOT EXISTS _sync_resume (
|
|
13605
|
+
table_name TEXT PRIMARY KEY,
|
|
13606
|
+
last_row_id TEXT,
|
|
13607
|
+
direction TEXT,
|
|
13608
|
+
started_at TEXT,
|
|
13609
|
+
status TEXT DEFAULT 'in_progress'
|
|
13610
|
+
)
|
|
13611
|
+
`);
|
|
13612
|
+
}
|
|
13613
|
+
start(table, total, direction) {
|
|
13614
|
+
const resumed = this.canResume(table);
|
|
13615
|
+
const now = Date.now();
|
|
13616
|
+
this.startTimes.set(table, now);
|
|
13617
|
+
const status = resumed ? "resumed" : "in_progress";
|
|
13618
|
+
const info = {
|
|
13619
|
+
table,
|
|
13620
|
+
total,
|
|
13621
|
+
done: 0,
|
|
13622
|
+
percent: 0,
|
|
13623
|
+
elapsed_ms: 0,
|
|
13624
|
+
eta_ms: 0,
|
|
13625
|
+
status
|
|
13626
|
+
};
|
|
13627
|
+
this.progress.set(table, info);
|
|
13628
|
+
this.db.run(`INSERT INTO _sync_resume (table_name, last_row_id, direction, started_at, status)
|
|
13629
|
+
VALUES (?, ?, ?, datetime('now'), ?)
|
|
13630
|
+
ON CONFLICT (table_name) DO UPDATE SET
|
|
13631
|
+
direction = excluded.direction,
|
|
13632
|
+
started_at = datetime('now'),
|
|
13633
|
+
status = excluded.status`, table, "", direction, status);
|
|
13634
|
+
this.notify(table);
|
|
13635
|
+
}
|
|
13636
|
+
update(table, done, lastRowId) {
|
|
13637
|
+
const info = this.progress.get(table);
|
|
13638
|
+
if (!info)
|
|
13639
|
+
return;
|
|
13640
|
+
const startTime = this.startTimes.get(table) ?? Date.now();
|
|
13641
|
+
const elapsed = Date.now() - startTime;
|
|
13642
|
+
const rate = done > 0 ? elapsed / done : 0;
|
|
13643
|
+
const remaining = info.total - done;
|
|
13644
|
+
const eta = remaining > 0 ? Math.round(rate * remaining) : 0;
|
|
13645
|
+
info.done = done;
|
|
13646
|
+
info.percent = info.total > 0 ? Math.round(done / info.total * 100) : 0;
|
|
13647
|
+
info.elapsed_ms = elapsed;
|
|
13648
|
+
info.eta_ms = eta;
|
|
13649
|
+
info.status = "in_progress";
|
|
13650
|
+
this.db.run(`UPDATE _sync_resume SET last_row_id = ?, status = 'in_progress' WHERE table_name = ?`, lastRowId, table);
|
|
13651
|
+
this.notify(table);
|
|
13652
|
+
}
|
|
13653
|
+
markComplete(table) {
|
|
13654
|
+
const info = this.progress.get(table);
|
|
13655
|
+
if (info) {
|
|
13656
|
+
const startTime = this.startTimes.get(table) ?? Date.now();
|
|
13657
|
+
info.elapsed_ms = Date.now() - startTime;
|
|
13658
|
+
info.done = info.total;
|
|
13659
|
+
info.percent = 100;
|
|
13660
|
+
info.eta_ms = 0;
|
|
13661
|
+
info.status = "completed";
|
|
13662
|
+
this.notify(table);
|
|
13663
|
+
}
|
|
13664
|
+
this.db.run(`UPDATE _sync_resume SET status = 'completed' WHERE table_name = ?`, table);
|
|
13665
|
+
}
|
|
13666
|
+
markFailed(table, _error) {
|
|
13667
|
+
const info = this.progress.get(table);
|
|
13668
|
+
if (info) {
|
|
13669
|
+
const startTime = this.startTimes.get(table) ?? Date.now();
|
|
13670
|
+
info.elapsed_ms = Date.now() - startTime;
|
|
13671
|
+
info.status = "failed";
|
|
13672
|
+
this.notify(table);
|
|
13673
|
+
}
|
|
13674
|
+
this.db.run(`UPDATE _sync_resume SET status = 'failed' WHERE table_name = ?`, table);
|
|
13675
|
+
}
|
|
13676
|
+
canResume(table) {
|
|
13677
|
+
const row = this.db.get(`SELECT status FROM _sync_resume WHERE table_name = ?`, table);
|
|
13678
|
+
if (!row)
|
|
13679
|
+
return false;
|
|
13680
|
+
return row.status === "in_progress" || row.status === "resumed";
|
|
13681
|
+
}
|
|
13682
|
+
getResumePoint(table) {
|
|
13683
|
+
const row = this.db.get(`SELECT table_name, last_row_id, direction, started_at, status FROM _sync_resume WHERE table_name = ?`, table);
|
|
13684
|
+
if (!row)
|
|
13685
|
+
return null;
|
|
13686
|
+
if (row.status !== "in_progress" && row.status !== "resumed")
|
|
13687
|
+
return null;
|
|
13688
|
+
return row;
|
|
13689
|
+
}
|
|
13690
|
+
clearResume(table) {
|
|
13691
|
+
this.db.run(`DELETE FROM _sync_resume WHERE table_name = ?`, table);
|
|
13692
|
+
this.progress.delete(table);
|
|
13693
|
+
this.startTimes.delete(table);
|
|
13694
|
+
}
|
|
13695
|
+
getProgress(table) {
|
|
13696
|
+
return this.progress.get(table) ?? null;
|
|
13697
|
+
}
|
|
13698
|
+
getAllProgress() {
|
|
13699
|
+
return Array.from(this.progress.values());
|
|
13700
|
+
}
|
|
13701
|
+
listResumeRecords() {
|
|
13702
|
+
return this.db.all(`SELECT table_name, last_row_id, direction, started_at, status FROM _sync_resume ORDER BY started_at DESC`);
|
|
13703
|
+
}
|
|
13704
|
+
notify(table) {
|
|
13705
|
+
const info = this.progress.get(table);
|
|
13706
|
+
if (info && this.callback) {
|
|
13707
|
+
this.callback({ ...info });
|
|
13708
|
+
}
|
|
13709
|
+
}
|
|
13710
|
+
}
|
|
13711
|
+
init_adapter();
|
|
13712
|
+
init_config();
|
|
13713
|
+
init_discover();
|
|
13714
|
+
var AUTO_SYNC_CONFIG_PATH = join4(homedir4(), ".hasna", "cloud", "config.json");
|
|
13715
|
+
init_config();
|
|
13716
|
+
init_adapter();
|
|
13717
|
+
init_dotfile();
|
|
13718
|
+
init_config();
|
|
13719
|
+
var CONFIG_DIR2 = join6(homedir5(), ".hasna", "cloud");
|
|
13720
|
+
init_adapter();
|
|
13721
|
+
init_config();
|
|
13722
|
+
init_discover();
|
|
13723
|
+
init_zod();
|
|
13724
|
+
init_config();
|
|
13725
|
+
init_dotfile();
|
|
13726
|
+
init_adapter();
|
|
13727
|
+
init_config();
|
|
13728
|
+
init_dotfile();
|
|
13729
|
+
init_adapter();
|
|
13730
|
+
|
|
13731
|
+
// src/db.ts
|
|
13732
|
+
var adapter = null;
|
|
13733
|
+
function createTables(db) {
|
|
13734
|
+
db.exec(`
|
|
13735
|
+
CREATE TABLE IF NOT EXISTS agent_heartbeats (
|
|
13736
|
+
machine_id TEXT NOT NULL,
|
|
13737
|
+
pid INTEGER NOT NULL,
|
|
13738
|
+
status TEXT NOT NULL,
|
|
13739
|
+
updated_at TEXT NOT NULL,
|
|
13740
|
+
PRIMARY KEY (machine_id, pid)
|
|
13741
|
+
)
|
|
13742
|
+
`);
|
|
13743
|
+
db.exec(`
|
|
13744
|
+
CREATE TABLE IF NOT EXISTS setup_runs (
|
|
13745
|
+
id TEXT PRIMARY KEY,
|
|
13746
|
+
machine_id TEXT NOT NULL,
|
|
13747
|
+
status TEXT NOT NULL,
|
|
13748
|
+
details_json TEXT NOT NULL DEFAULT '[]',
|
|
13749
|
+
created_at TEXT NOT NULL,
|
|
13750
|
+
updated_at TEXT NOT NULL
|
|
13751
|
+
)
|
|
13752
|
+
`);
|
|
13753
|
+
db.exec(`
|
|
13754
|
+
CREATE TABLE IF NOT EXISTS sync_runs (
|
|
13755
|
+
id TEXT PRIMARY KEY,
|
|
13756
|
+
machine_id TEXT NOT NULL,
|
|
13757
|
+
status TEXT NOT NULL,
|
|
13758
|
+
actions_json TEXT NOT NULL DEFAULT '[]',
|
|
13759
|
+
created_at TEXT NOT NULL,
|
|
13760
|
+
updated_at TEXT NOT NULL
|
|
13761
|
+
)
|
|
13762
|
+
`);
|
|
13763
|
+
}
|
|
13764
|
+
function getAdapter(path = getDbPath()) {
|
|
13765
|
+
if (path === ":memory:") {
|
|
13766
|
+
const memoryAdapter = new SqliteAdapter(path);
|
|
13767
|
+
createTables(memoryAdapter.raw);
|
|
13768
|
+
return memoryAdapter;
|
|
13769
|
+
}
|
|
13770
|
+
if (!adapter) {
|
|
13771
|
+
ensureParentDir(path);
|
|
13772
|
+
adapter = new SqliteAdapter(path);
|
|
13773
|
+
createTables(adapter.raw);
|
|
13774
|
+
}
|
|
13775
|
+
return adapter;
|
|
13776
|
+
}
|
|
13777
|
+
function getDb(path = getDbPath()) {
|
|
13778
|
+
return getAdapter(path).raw;
|
|
13779
|
+
}
|
|
13780
|
+
function getLocalMachineId() {
|
|
13781
|
+
return process.env["HASNA_MACHINES_MACHINE_ID"] || hostname2();
|
|
13782
|
+
}
|
|
13783
|
+
function listHeartbeats(machineId) {
|
|
13784
|
+
const db = getDb();
|
|
13785
|
+
if (machineId) {
|
|
13786
|
+
return db.query(`SELECT machine_id, pid, status, updated_at
|
|
13787
|
+
FROM agent_heartbeats
|
|
13788
|
+
WHERE machine_id = ?
|
|
13789
|
+
ORDER BY updated_at DESC`).all(machineId);
|
|
13790
|
+
}
|
|
13791
|
+
return db.query(`SELECT machine_id, pid, status, updated_at
|
|
13792
|
+
FROM agent_heartbeats
|
|
13793
|
+
ORDER BY updated_at DESC`).all();
|
|
13794
|
+
}
|
|
13795
|
+
function countRuns(table) {
|
|
13796
|
+
const db = getDb();
|
|
13797
|
+
const row = db.query(`SELECT COUNT(*) as count FROM ${table}`).get();
|
|
13798
|
+
return row.count;
|
|
13799
|
+
}
|
|
13800
|
+
function recordSetupRun(machineId, status, details) {
|
|
13801
|
+
const db = getDb();
|
|
13802
|
+
const now = new Date().toISOString();
|
|
13803
|
+
db.query(`INSERT INTO setup_runs (id, machine_id, status, details_json, created_at, updated_at)
|
|
13804
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(details), now, now);
|
|
13805
|
+
}
|
|
13806
|
+
function recordSyncRun(machineId, status, actions) {
|
|
13807
|
+
const db = getDb();
|
|
13808
|
+
const now = new Date().toISOString();
|
|
13809
|
+
db.query(`INSERT INTO sync_runs (id, machine_id, status, actions_json, created_at, updated_at)
|
|
13810
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(actions), now, now);
|
|
13811
|
+
}
|
|
13812
|
+
|
|
13813
|
+
// src/commands/ssh.ts
|
|
13814
|
+
import { spawnSync } from "child_process";
|
|
13815
|
+
function envReachableHosts() {
|
|
13816
|
+
const raw = process.env["HASNA_MACHINES_REACHABLE_HOSTS"];
|
|
13817
|
+
return new Set((raw || "").split(",").map((value) => value.trim()).filter(Boolean));
|
|
13818
|
+
}
|
|
13819
|
+
function isReachable(host) {
|
|
13820
|
+
const overrides = envReachableHosts();
|
|
13821
|
+
if (overrides.size > 0) {
|
|
13822
|
+
return overrides.has(host);
|
|
13823
|
+
}
|
|
13824
|
+
const probe = spawnSync("bash", ["-lc", `getent hosts ${host} >/dev/null 2>&1 || ping -c 1 -W 1 ${host} >/dev/null 2>&1`], {
|
|
13825
|
+
stdio: "ignore"
|
|
13826
|
+
});
|
|
13827
|
+
return probe.status === 0;
|
|
13828
|
+
}
|
|
13829
|
+
function resolveSshTarget(machineId) {
|
|
13830
|
+
const machine = getManifestMachine(machineId);
|
|
13831
|
+
if (!machine) {
|
|
13832
|
+
throw new Error(`Machine not found in manifest: ${machineId}`);
|
|
13833
|
+
}
|
|
13834
|
+
const current = detectCurrentMachineManifest();
|
|
13835
|
+
if (machine.id === current.id) {
|
|
13836
|
+
return {
|
|
13837
|
+
machineId,
|
|
13838
|
+
target: "localhost",
|
|
13839
|
+
route: "local"
|
|
13840
|
+
};
|
|
13841
|
+
}
|
|
13842
|
+
const lanTarget = machine.sshAddress || machine.hostname || machine.id;
|
|
13843
|
+
const tailscaleTarget = machine.tailscaleName || machine.hostname || machine.id;
|
|
13844
|
+
const route = isReachable(lanTarget) ? "lan" : "tailscale";
|
|
13845
|
+
return {
|
|
13846
|
+
machineId,
|
|
13847
|
+
target: route === "lan" ? lanTarget : tailscaleTarget,
|
|
13848
|
+
route
|
|
13849
|
+
};
|
|
13850
|
+
}
|
|
13851
|
+
function buildSshCommand(machineId, remoteCommand) {
|
|
13852
|
+
const resolved = resolveSshTarget(machineId);
|
|
13853
|
+
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
13868
13854
|
}
|
|
13869
|
-
|
|
13870
|
-
|
|
13871
|
-
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
13876
|
-
|
|
13877
|
-
|
|
13878
|
-
|
|
13879
|
-
|
|
13880
|
-
|
|
13881
|
-
|
|
13882
|
-
|
|
13883
|
-
|
|
13884
|
-
|
|
13855
|
+
|
|
13856
|
+
// src/remote.ts
|
|
13857
|
+
function runMachineCommand(machineId, command) {
|
|
13858
|
+
const localMachineId = getLocalMachineId();
|
|
13859
|
+
const isLocal = machineId === localMachineId;
|
|
13860
|
+
const route = isLocal ? "local" : resolveSshTarget(machineId).route;
|
|
13861
|
+
const shellCommand = isLocal ? command : buildSshCommand(machineId, command);
|
|
13862
|
+
const result = spawnSync2("bash", ["-lc", shellCommand], {
|
|
13863
|
+
encoding: "utf8",
|
|
13864
|
+
env: process.env
|
|
13865
|
+
});
|
|
13866
|
+
return {
|
|
13867
|
+
machineId,
|
|
13868
|
+
source: route,
|
|
13869
|
+
stdout: result.stdout || "",
|
|
13870
|
+
stderr: result.stderr || "",
|
|
13871
|
+
exitCode: result.status ?? 1
|
|
13872
|
+
};
|
|
13885
13873
|
}
|
|
13886
|
-
|
|
13887
|
-
|
|
13874
|
+
|
|
13875
|
+
// src/commands/apps.ts
|
|
13876
|
+
function getPackageName(app) {
|
|
13877
|
+
return app.packageName || app.name;
|
|
13888
13878
|
}
|
|
13889
|
-
function
|
|
13890
|
-
if (
|
|
13891
|
-
return
|
|
13879
|
+
function getAppManager(machine, app) {
|
|
13880
|
+
if (app.manager)
|
|
13881
|
+
return app.manager;
|
|
13882
|
+
if (machine.platform === "macos")
|
|
13883
|
+
return "brew";
|
|
13884
|
+
if (machine.platform === "windows")
|
|
13885
|
+
return "winget";
|
|
13886
|
+
return "apt";
|
|
13887
|
+
}
|
|
13888
|
+
function shellQuote(value) {
|
|
13889
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
13890
|
+
}
|
|
13891
|
+
function buildAppCommand(machine, app) {
|
|
13892
|
+
const packageName = getPackageName(app);
|
|
13893
|
+
const manager = getAppManager(machine, app);
|
|
13894
|
+
if (manager === "custom") {
|
|
13895
|
+
return packageName;
|
|
13892
13896
|
}
|
|
13893
|
-
|
|
13894
|
-
|
|
13895
|
-
|
|
13896
|
-
|
|
13897
|
-
return
|
|
13897
|
+
if (machine.platform === "macos") {
|
|
13898
|
+
if (manager === "cask") {
|
|
13899
|
+
return `brew install --cask ${packageName}`;
|
|
13900
|
+
}
|
|
13901
|
+
return `brew install ${packageName}`;
|
|
13898
13902
|
}
|
|
13903
|
+
if (machine.platform === "windows") {
|
|
13904
|
+
return `winget install ${packageName}`;
|
|
13905
|
+
}
|
|
13906
|
+
return `sudo apt-get install -y ${packageName}`;
|
|
13899
13907
|
}
|
|
13900
|
-
function
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
}
|
|
13905
|
-
function getConnectionString(dbName) {
|
|
13906
|
-
const config = getCloudConfig();
|
|
13907
|
-
const { host, port, username, password_env, ssl } = config.rds;
|
|
13908
|
-
if (!host || !username) {
|
|
13909
|
-
throw new Error("Cloud RDS not configured. Run `cloud setup` to configure.");
|
|
13908
|
+
function buildAppProbeCommand(machine, app) {
|
|
13909
|
+
const packageName = shellQuote(getPackageName(app));
|
|
13910
|
+
const manager = getAppManager(machine, app);
|
|
13911
|
+
if (manager === "custom") {
|
|
13912
|
+
return `if command -v ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=custom\\n'; else printf 'installed=0\\n'; fi`;
|
|
13910
13913
|
}
|
|
13911
|
-
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
+
if (machine.platform === "macos") {
|
|
13915
|
+
if (manager === "cask") {
|
|
13916
|
+
return `if brew list --cask ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
13917
|
+
}
|
|
13918
|
+
return `if brew list --versions ${packageName} >/dev/null 2>&1; then printf 'installed=1\\nversion='; brew list --versions ${packageName} | awk '{print $2}'; printf '\\n'; else printf 'installed=0\\n'; fi`;
|
|
13914
13919
|
}
|
|
13915
|
-
|
|
13916
|
-
|
|
13920
|
+
if (machine.platform === "windows") {
|
|
13921
|
+
return `if winget list --id ${packageName} --exact >/dev/null 2>&1; then printf 'installed=1\\nversion=installed\\n'; else printf 'installed=0\\n'; fi`;
|
|
13922
|
+
}
|
|
13923
|
+
return `if dpkg-query -W -f='${"${Version}"}' ${packageName} >/tmp/machines-app-version 2>/dev/null; then printf 'installed=1\\nversion='; cat /tmp/machines-app-version; printf '\\n'; rm -f /tmp/machines-app-version; else printf 'installed=0\\n'; fi`;
|
|
13917
13924
|
}
|
|
13918
|
-
function
|
|
13919
|
-
|
|
13920
|
-
|
|
13921
|
-
|
|
13922
|
-
|
|
13923
|
-
|
|
13925
|
+
function buildAppSteps(machine) {
|
|
13926
|
+
return (machine.apps || []).map((app) => ({
|
|
13927
|
+
id: `app-${app.name}`,
|
|
13928
|
+
title: `Install ${app.name} on ${machine.id}`,
|
|
13929
|
+
command: buildAppCommand(machine, app),
|
|
13930
|
+
manager: getAppManager(machine, app) === "custom" ? "custom" : machine.platform === "macos" ? "brew" : machine.platform === "windows" ? "custom" : "apt",
|
|
13931
|
+
privileged: machine.platform === "linux"
|
|
13932
|
+
}));
|
|
13933
|
+
}
|
|
13934
|
+
function resolveMachine(machineId) {
|
|
13935
|
+
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
13936
|
+
}
|
|
13937
|
+
function parseProbeOutput(app, machine, stdout) {
|
|
13938
|
+
const lines = stdout.trim().split(`
|
|
13939
|
+
`).filter(Boolean);
|
|
13940
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
13941
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
13942
|
+
return {
|
|
13943
|
+
name: app.name,
|
|
13944
|
+
packageName: getPackageName(app),
|
|
13945
|
+
manager: getAppManager(machine, app),
|
|
13946
|
+
installed: installedLine === "installed=1",
|
|
13947
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
13948
|
+
};
|
|
13949
|
+
}
|
|
13950
|
+
function listApps(machineId) {
|
|
13951
|
+
const machine = resolveMachine(machineId);
|
|
13952
|
+
return {
|
|
13953
|
+
machineId: machine.id,
|
|
13954
|
+
apps: machine.apps || []
|
|
13955
|
+
};
|
|
13956
|
+
}
|
|
13957
|
+
function buildAppsPlan(machineId) {
|
|
13958
|
+
const machine = resolveMachine(machineId);
|
|
13959
|
+
return {
|
|
13960
|
+
machineId: machine.id,
|
|
13961
|
+
mode: "plan",
|
|
13962
|
+
steps: buildAppSteps(machine),
|
|
13963
|
+
executed: 0
|
|
13964
|
+
};
|
|
13965
|
+
}
|
|
13966
|
+
function getAppsStatus(machineId) {
|
|
13967
|
+
const machine = resolveMachine(machineId);
|
|
13968
|
+
const apps = (machine.apps || []).map((app) => {
|
|
13969
|
+
const probe = runMachineCommand(machine.id, buildAppProbeCommand(machine, app));
|
|
13970
|
+
return parseProbeOutput(app, machine, probe.stdout);
|
|
13971
|
+
});
|
|
13972
|
+
return {
|
|
13973
|
+
machineId: machine.id,
|
|
13974
|
+
source: apps.length > 0 ? runMachineCommand(machine.id, "true").source : machine.id === detectCurrentMachineManifest().id ? "local" : runMachineCommand(machine.id, "true").source,
|
|
13975
|
+
apps
|
|
13976
|
+
};
|
|
13977
|
+
}
|
|
13978
|
+
function diffApps(machineId) {
|
|
13979
|
+
const status = getAppsStatus(machineId);
|
|
13980
|
+
return {
|
|
13981
|
+
...status,
|
|
13982
|
+
missing: status.apps.filter((app) => !app.installed).map((app) => app.name),
|
|
13983
|
+
installed: status.apps.filter((app) => app.installed).map((app) => app.name)
|
|
13984
|
+
};
|
|
13985
|
+
}
|
|
13986
|
+
function runAppsInstall(machineId, options = {}) {
|
|
13987
|
+
const plan = buildAppsPlan(machineId);
|
|
13988
|
+
if (!options.apply)
|
|
13989
|
+
return plan;
|
|
13990
|
+
if (!options.yes) {
|
|
13991
|
+
throw new Error("App installation requires --yes.");
|
|
13924
13992
|
}
|
|
13925
|
-
|
|
13926
|
-
|
|
13993
|
+
let executed = 0;
|
|
13994
|
+
for (const step of plan.steps) {
|
|
13995
|
+
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
13996
|
+
stdout: "pipe",
|
|
13997
|
+
stderr: "pipe",
|
|
13998
|
+
env: process.env
|
|
13999
|
+
});
|
|
14000
|
+
if (result.exitCode !== 0) {
|
|
14001
|
+
throw new Error(`App install failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
14002
|
+
}
|
|
14003
|
+
executed += 1;
|
|
14004
|
+
}
|
|
14005
|
+
return {
|
|
14006
|
+
machineId: plan.machineId,
|
|
14007
|
+
mode: "apply",
|
|
14008
|
+
steps: plan.steps,
|
|
14009
|
+
executed
|
|
14010
|
+
};
|
|
13927
14011
|
}
|
|
13928
|
-
|
|
13929
|
-
|
|
13930
|
-
|
|
13931
|
-
|
|
13932
|
-
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
13943
|
-
|
|
13944
|
-
|
|
13945
|
-
|
|
13946
|
-
|
|
13947
|
-
|
|
13948
|
-
|
|
14012
|
+
|
|
14013
|
+
// src/commands/cert.ts
|
|
14014
|
+
import { homedir as homedir6, platform as platform3 } from "os";
|
|
14015
|
+
import { join as join5 } from "path";
|
|
14016
|
+
function quote2(value) {
|
|
14017
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
14018
|
+
}
|
|
14019
|
+
function certDir() {
|
|
14020
|
+
return join5(homedir6(), ".hasna", "machines", "certs");
|
|
14021
|
+
}
|
|
14022
|
+
function buildCertPlan(domains) {
|
|
14023
|
+
if (domains.length === 0) {
|
|
14024
|
+
throw new Error("At least one domain is required.");
|
|
14025
|
+
}
|
|
14026
|
+
const primary = domains[0];
|
|
14027
|
+
const certPath = join5(certDir(), `${primary}.pem`);
|
|
14028
|
+
const keyPath = join5(certDir(), `${primary}-key.pem`);
|
|
14029
|
+
const steps = [];
|
|
14030
|
+
if (platform3() === "darwin") {
|
|
14031
|
+
steps.push({
|
|
14032
|
+
id: "mkcert-install-macos",
|
|
14033
|
+
title: "Install mkcert on macOS",
|
|
14034
|
+
command: "brew install mkcert nss",
|
|
14035
|
+
manager: "brew"
|
|
14036
|
+
});
|
|
14037
|
+
} else {
|
|
14038
|
+
steps.push({
|
|
14039
|
+
id: "mkcert-install-linux",
|
|
14040
|
+
title: "Install mkcert on Linux",
|
|
14041
|
+
command: "sudo apt-get update && sudo apt-get install -y mkcert libnss3-tools",
|
|
14042
|
+
manager: "apt",
|
|
14043
|
+
privileged: true
|
|
14044
|
+
});
|
|
14045
|
+
}
|
|
14046
|
+
steps.push({
|
|
14047
|
+
id: "mkcert-local-ca",
|
|
14048
|
+
title: "Install local mkcert CA",
|
|
14049
|
+
command: "mkcert -install",
|
|
14050
|
+
manager: "custom"
|
|
14051
|
+
}, {
|
|
14052
|
+
id: "mkcert-issue",
|
|
14053
|
+
title: `Issue certificate for ${domains.join(", ")}`,
|
|
14054
|
+
command: `mkdir -p ${quote2(certDir())} && mkcert -cert-file ${quote2(certPath)} -key-file ${quote2(keyPath)} ${domains.map((domain) => quote2(domain)).join(" ")}`,
|
|
14055
|
+
manager: "custom"
|
|
13949
14056
|
});
|
|
13950
|
-
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
14057
|
+
return {
|
|
14058
|
+
machineId: process.env["HASNA_MACHINES_MACHINE_ID"] || "local",
|
|
14059
|
+
mode: "plan",
|
|
14060
|
+
steps,
|
|
14061
|
+
executed: 0
|
|
14062
|
+
};
|
|
14063
|
+
}
|
|
14064
|
+
function runCertPlan(domains, options = {}) {
|
|
14065
|
+
const plan = buildCertPlan(domains);
|
|
14066
|
+
if (!options.apply)
|
|
14067
|
+
return plan;
|
|
14068
|
+
if (!options.yes) {
|
|
14069
|
+
throw new Error("Certificate generation requires --yes.");
|
|
14070
|
+
}
|
|
14071
|
+
let executed = 0;
|
|
14072
|
+
for (const step of plan.steps) {
|
|
14073
|
+
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
14074
|
+
stdout: "pipe",
|
|
14075
|
+
stderr: "pipe",
|
|
14076
|
+
env: process.env
|
|
14077
|
+
});
|
|
14078
|
+
if (result.exitCode !== 0) {
|
|
14079
|
+
throw new Error(`Certificate step failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
14080
|
+
}
|
|
14081
|
+
executed += 1;
|
|
14082
|
+
}
|
|
14083
|
+
return {
|
|
14084
|
+
machineId: plan.machineId,
|
|
14085
|
+
mode: "apply",
|
|
14086
|
+
steps: plan.steps,
|
|
14087
|
+
executed
|
|
14088
|
+
};
|
|
14089
|
+
}
|
|
14090
|
+
|
|
14091
|
+
// src/commands/dns.ts
|
|
14092
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
14093
|
+
import { join as join7 } from "path";
|
|
14094
|
+
function getDnsPath() {
|
|
14095
|
+
return join7(getDataDir(), "dns.json");
|
|
13964
14096
|
}
|
|
13965
|
-
function
|
|
13966
|
-
const
|
|
13967
|
-
if (!
|
|
13968
|
-
return [];
|
|
13969
|
-
try {
|
|
13970
|
-
const entries = readdirSync2(dataDir, { withFileTypes: true });
|
|
13971
|
-
return entries.filter((e) => {
|
|
13972
|
-
if (!e.isDirectory())
|
|
13973
|
-
return false;
|
|
13974
|
-
if (e.name === "cloud" || e.name.startsWith("."))
|
|
13975
|
-
return false;
|
|
13976
|
-
return true;
|
|
13977
|
-
}).map((e) => e.name).sort();
|
|
13978
|
-
} catch {
|
|
14097
|
+
function readMappings() {
|
|
14098
|
+
const path = getDnsPath();
|
|
14099
|
+
if (!existsSync4(path))
|
|
13979
14100
|
return [];
|
|
13980
|
-
|
|
14101
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
13981
14102
|
}
|
|
13982
|
-
function
|
|
13983
|
-
const
|
|
13984
|
-
|
|
13985
|
-
|
|
14103
|
+
function writeMappings(mappings) {
|
|
14104
|
+
const path = getDnsPath();
|
|
14105
|
+
ensureParentDir(path);
|
|
14106
|
+
writeFileSync3(path, `${JSON.stringify(mappings, null, 2)}
|
|
14107
|
+
`, "utf8");
|
|
14108
|
+
return path;
|
|
13986
14109
|
}
|
|
13987
|
-
function
|
|
13988
|
-
const
|
|
13989
|
-
|
|
13990
|
-
|
|
13991
|
-
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
13997
|
-
|
|
13998
|
-
|
|
13999
|
-
|
|
14000
|
-
candidates.push(join32(dataDir, f));
|
|
14001
|
-
}
|
|
14002
|
-
}
|
|
14003
|
-
} catch {}
|
|
14004
|
-
for (const p of candidates) {
|
|
14005
|
-
if (existsSync32(p))
|
|
14006
|
-
return p;
|
|
14110
|
+
function addDomainMapping(domain, port, targetHost = "127.0.0.1") {
|
|
14111
|
+
const mappings = readMappings().filter((entry) => entry.domain !== domain);
|
|
14112
|
+
mappings.push({ domain, port, targetHost });
|
|
14113
|
+
writeMappings(mappings);
|
|
14114
|
+
return mappings.sort((left, right) => left.domain.localeCompare(right.domain));
|
|
14115
|
+
}
|
|
14116
|
+
function listDomainMappings() {
|
|
14117
|
+
return readMappings().sort((left, right) => left.domain.localeCompare(right.domain));
|
|
14118
|
+
}
|
|
14119
|
+
function renderDomainMapping(domain) {
|
|
14120
|
+
const entry = readMappings().find((mapping) => mapping.domain === domain);
|
|
14121
|
+
if (!entry) {
|
|
14122
|
+
throw new Error(`Domain mapping not found: ${domain}`);
|
|
14007
14123
|
}
|
|
14008
|
-
return
|
|
14124
|
+
return {
|
|
14125
|
+
hostsEntry: `${entry.targetHost} ${entry.domain}`,
|
|
14126
|
+
caddySnippet: `${entry.domain} {
|
|
14127
|
+
reverse_proxy 127.0.0.1:${entry.port}
|
|
14128
|
+
tls ${join7(getDataDir(), "certs", `${entry.domain}.pem`)} ${join7(getDataDir(), "certs", `${entry.domain}-key.pem`)}
|
|
14129
|
+
}`,
|
|
14130
|
+
certPath: join7(getDataDir(), "certs", `${entry.domain}.pem`),
|
|
14131
|
+
keyPath: join7(getDataDir(), "certs", `${entry.domain}-key.pem`)
|
|
14132
|
+
};
|
|
14009
14133
|
}
|
|
14010
|
-
var KNOWN_PG_SERVICES;
|
|
14011
|
-
var SYNC_EXCLUDED_TABLE_PATTERNS;
|
|
14012
|
-
var init_discover = __esm(() => {
|
|
14013
|
-
KNOWN_PG_SERVICES = [
|
|
14014
|
-
"assistants",
|
|
14015
|
-
"attachments",
|
|
14016
|
-
"brains",
|
|
14017
|
-
"configs",
|
|
14018
|
-
"connectors",
|
|
14019
|
-
"contacts",
|
|
14020
|
-
"context",
|
|
14021
|
-
"conversations",
|
|
14022
|
-
"crawl",
|
|
14023
|
-
"deployment",
|
|
14024
|
-
"economy",
|
|
14025
|
-
"emails",
|
|
14026
|
-
"files",
|
|
14027
|
-
"hooks",
|
|
14028
|
-
"implementations",
|
|
14029
|
-
"logs",
|
|
14030
|
-
"mcps",
|
|
14031
|
-
"mementos",
|
|
14032
|
-
"microservices",
|
|
14033
|
-
"predictor",
|
|
14034
|
-
"prompts",
|
|
14035
|
-
"recordings",
|
|
14036
|
-
"researcher",
|
|
14037
|
-
"sandboxes",
|
|
14038
|
-
"search",
|
|
14039
|
-
"secrets",
|
|
14040
|
-
"sessions",
|
|
14041
|
-
"signatures",
|
|
14042
|
-
"skills",
|
|
14043
|
-
"telephony",
|
|
14044
|
-
"terminal",
|
|
14045
|
-
"testers",
|
|
14046
|
-
"tickets",
|
|
14047
|
-
"todos",
|
|
14048
|
-
"wallets"
|
|
14049
|
-
];
|
|
14050
|
-
SYNC_EXCLUDED_TABLE_PATTERNS = [
|
|
14051
|
-
/^sqlite_/,
|
|
14052
|
-
/_fts$/,
|
|
14053
|
-
/_fts_/,
|
|
14054
|
-
/^_sync_/,
|
|
14055
|
-
/^_pg_migrations$/
|
|
14056
|
-
];
|
|
14057
|
-
});
|
|
14058
|
-
init_adapter();
|
|
14059
|
-
init_config();
|
|
14060
|
-
init_config();
|
|
14061
|
-
init_dotfile();
|
|
14062
14134
|
|
|
14063
|
-
|
|
14064
|
-
|
|
14065
|
-
|
|
14066
|
-
|
|
14067
|
-
|
|
14068
|
-
|
|
14069
|
-
|
|
14070
|
-
|
|
14071
|
-
|
|
14135
|
+
// src/commands/diff.ts
|
|
14136
|
+
function packageNames(machine) {
|
|
14137
|
+
return (machine.packages || []).map((pkg) => pkg.name).sort();
|
|
14138
|
+
}
|
|
14139
|
+
function fileTargets(machine) {
|
|
14140
|
+
return (machine.files || []).map((file) => `${file.source}->${file.target}`).sort();
|
|
14141
|
+
}
|
|
14142
|
+
function diffMachines(leftMachineId, rightMachineId) {
|
|
14143
|
+
const left = getManifestMachine(leftMachineId);
|
|
14144
|
+
if (!left) {
|
|
14145
|
+
throw new Error(`Machine not found in manifest: ${leftMachineId}`);
|
|
14072
14146
|
}
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
table_name TEXT PRIMARY KEY,
|
|
14077
|
-
last_row_id TEXT,
|
|
14078
|
-
direction TEXT,
|
|
14079
|
-
started_at TEXT,
|
|
14080
|
-
status TEXT DEFAULT 'in_progress'
|
|
14081
|
-
)
|
|
14082
|
-
`);
|
|
14147
|
+
const right = rightMachineId ? getManifestMachine(rightMachineId) : detectCurrentMachineManifest();
|
|
14148
|
+
if (!right) {
|
|
14149
|
+
throw new Error(`Machine not found in manifest: ${rightMachineId}`);
|
|
14083
14150
|
}
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14151
|
+
const changedFields = [
|
|
14152
|
+
left.platform !== right.platform ? "platform" : null,
|
|
14153
|
+
left.connection !== right.connection ? "connection" : null,
|
|
14154
|
+
left.workspacePath !== right.workspacePath ? "workspacePath" : null,
|
|
14155
|
+
left.bunPath !== right.bunPath ? "bunPath" : null
|
|
14156
|
+
].filter(Boolean);
|
|
14157
|
+
const leftPackages = new Set(packageNames(left));
|
|
14158
|
+
const rightPackages = new Set(packageNames(right));
|
|
14159
|
+
const leftFiles = new Set(fileTargets(left));
|
|
14160
|
+
const rightFiles = new Set(fileTargets(right));
|
|
14161
|
+
return {
|
|
14162
|
+
leftMachineId: left.id,
|
|
14163
|
+
rightMachineId: right.id,
|
|
14164
|
+
changedFields,
|
|
14165
|
+
missingPackages: {
|
|
14166
|
+
leftOnly: [...leftPackages].filter((pkg) => !rightPackages.has(pkg)),
|
|
14167
|
+
rightOnly: [...rightPackages].filter((pkg) => !leftPackages.has(pkg))
|
|
14168
|
+
},
|
|
14169
|
+
missingFiles: {
|
|
14170
|
+
leftOnly: [...leftFiles].filter((file) => !rightFiles.has(file)),
|
|
14171
|
+
rightOnly: [...rightFiles].filter((file) => !leftFiles.has(file))
|
|
14172
|
+
}
|
|
14173
|
+
};
|
|
14174
|
+
}
|
|
14175
|
+
|
|
14176
|
+
// src/commands/doctor.ts
|
|
14177
|
+
function makeCheck(id, status, summary, detail) {
|
|
14178
|
+
return { id, status, summary, detail };
|
|
14179
|
+
}
|
|
14180
|
+
function parseKeyValueOutput(stdout) {
|
|
14181
|
+
return Object.fromEntries(stdout.trim().split(`
|
|
14182
|
+
`).map((line) => line.split("=")).filter((parts) => parts.length === 2).map(([key, value]) => [key, value]));
|
|
14183
|
+
}
|
|
14184
|
+
function buildDoctorCommand() {
|
|
14185
|
+
return [
|
|
14186
|
+
'data_dir="${HASNA_MACHINES_DIR:-$HOME/.hasna/machines}"',
|
|
14187
|
+
'manifest_path="${HASNA_MACHINES_MANIFEST_PATH:-$data_dir/machines.json}"',
|
|
14188
|
+
'db_path="${HASNA_MACHINES_DB_PATH:-$data_dir/machines.db}"',
|
|
14189
|
+
'notifications_path="${HASNA_MACHINES_NOTIFICATIONS_PATH:-$data_dir/notifications.json}"',
|
|
14190
|
+
`printf 'manifest_path=%s\\n' "$manifest_path"`,
|
|
14191
|
+
`printf 'db_path=%s\\n' "$db_path"`,
|
|
14192
|
+
`printf 'notifications_path=%s\\n' "$notifications_path"`,
|
|
14193
|
+
`printf 'manifest_exists=%s\\n' "$(test -e "$manifest_path" && printf yes || printf no)"`,
|
|
14194
|
+
`printf 'db_exists=%s\\n' "$(test -e "$db_path" && printf yes || printf no)"`,
|
|
14195
|
+
`printf 'notifications_exists=%s\\n' "$(test -e "$notifications_path" && printf yes || printf no)"`,
|
|
14196
|
+
`printf 'bun=%s\\n' "$(bun --version 2>/dev/null || printf missing)"`,
|
|
14197
|
+
`printf 'ssh=%s\\n' "$(command -v ssh >/dev/null 2>&1 && printf ok || printf missing)"`,
|
|
14198
|
+
`printf 'machines=%s\\n' "$(command -v machines 2>/dev/null || printf missing)"`,
|
|
14199
|
+
`printf 'machines_agent=%s\\n' "$(command -v machines-agent 2>/dev/null || printf missing)"`,
|
|
14200
|
+
`printf 'machines_mcp=%s\\n' "$(command -v machines-mcp 2>/dev/null || printf missing)"`
|
|
14201
|
+
].join("; ");
|
|
14202
|
+
}
|
|
14203
|
+
function runDoctor(machineId = getLocalMachineId()) {
|
|
14204
|
+
const manifest = readManifest();
|
|
14205
|
+
const commandChecks = runMachineCommand(machineId, buildDoctorCommand());
|
|
14206
|
+
const details = parseKeyValueOutput(commandChecks.stdout);
|
|
14207
|
+
const machineInManifest = manifest.machines.find((machine) => machine.id === machineId);
|
|
14208
|
+
const checks = [
|
|
14209
|
+
makeCheck("manifest-entry", machineInManifest ? "ok" : "warn", machineInManifest ? "Machine exists in manifest" : "Machine missing from manifest", machineInManifest ? JSON.stringify(machineInManifest) : `No manifest entry for ${machineId}`),
|
|
14210
|
+
makeCheck("manifest-path", details["manifest_exists"] === "yes" ? "ok" : "warn", "Manifest path check", `${details["manifest_path"] || "unknown"} ${details["manifest_exists"] === "yes" ? "exists" : "missing"}`),
|
|
14211
|
+
makeCheck("db-path", details["db_exists"] === "yes" ? "ok" : "warn", "DB path check", `${details["db_path"] || "unknown"} ${details["db_exists"] === "yes" ? "exists" : "missing"}`),
|
|
14212
|
+
makeCheck("notifications-path", details["notifications_exists"] === "yes" ? "ok" : "warn", "Notifications path check", `${details["notifications_path"] || "unknown"} ${details["notifications_exists"] === "yes" ? "exists" : "missing"}`),
|
|
14213
|
+
makeCheck("bun", details["bun"] && details["bun"] !== "missing" ? "ok" : "fail", "Bun availability", details["bun"] || "missing"),
|
|
14214
|
+
makeCheck("machines-cli", details["machines"] && details["machines"] !== "missing" ? "ok" : "warn", "machines CLI availability", details["machines"] || "missing"),
|
|
14215
|
+
makeCheck("machines-agent-cli", details["machines_agent"] && details["machines_agent"] !== "missing" ? "ok" : "warn", "machines-agent availability", details["machines_agent"] || "missing"),
|
|
14216
|
+
makeCheck("machines-mcp-cli", details["machines_mcp"] && details["machines_mcp"] !== "missing" ? "ok" : "warn", "machines-mcp availability", details["machines_mcp"] || "missing"),
|
|
14217
|
+
makeCheck("ssh", details["ssh"] === "ok" ? "ok" : "warn", "SSH availability", details["ssh"] || "missing")
|
|
14218
|
+
];
|
|
14219
|
+
return {
|
|
14220
|
+
machineId,
|
|
14221
|
+
source: commandChecks.source,
|
|
14222
|
+
manifestPath: details["manifest_path"],
|
|
14223
|
+
dbPath: details["db_path"],
|
|
14224
|
+
notificationsPath: details["notifications_path"],
|
|
14225
|
+
checks
|
|
14226
|
+
};
|
|
14227
|
+
}
|
|
14228
|
+
|
|
14229
|
+
// src/commands/install-claude.ts
|
|
14230
|
+
var AI_CLI_PACKAGES = {
|
|
14231
|
+
claude: "@anthropic-ai/claude-code",
|
|
14232
|
+
codex: "@openai/codex",
|
|
14233
|
+
gemini: "@google/gemini-cli"
|
|
14234
|
+
};
|
|
14235
|
+
function getToolBinary(tool) {
|
|
14236
|
+
if (tool === "claude")
|
|
14237
|
+
return process.env["HASNA_MACHINES_CLAUDE_BINARY"] || "claude";
|
|
14238
|
+
if (tool === "codex")
|
|
14239
|
+
return process.env["HASNA_MACHINES_CODEX_BINARY"] || "codex";
|
|
14240
|
+
return process.env["HASNA_MACHINES_GEMINI_BINARY"] || "gemini";
|
|
14241
|
+
}
|
|
14242
|
+
function normalizeTools(tools) {
|
|
14243
|
+
if (!tools || tools.length === 0) {
|
|
14244
|
+
return ["claude", "codex", "gemini"];
|
|
14106
14245
|
}
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14246
|
+
return [...new Set(tools)].map((tool) => {
|
|
14247
|
+
if (!(tool in AI_CLI_PACKAGES)) {
|
|
14248
|
+
throw new Error(`Unsupported AI CLI tool: ${tool}`);
|
|
14249
|
+
}
|
|
14250
|
+
return tool;
|
|
14251
|
+
});
|
|
14252
|
+
}
|
|
14253
|
+
function buildInstallSteps(machine, tools) {
|
|
14254
|
+
return normalizeTools(tools).map((tool) => ({
|
|
14255
|
+
id: `install-${tool}`,
|
|
14256
|
+
title: `Install or update ${tool} CLI on ${machine.id}`,
|
|
14257
|
+
command: `bun install -g ${AI_CLI_PACKAGES[tool]}`,
|
|
14258
|
+
manager: "bun"
|
|
14259
|
+
}));
|
|
14260
|
+
}
|
|
14261
|
+
function resolveMachine2(machineId) {
|
|
14262
|
+
return (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
14263
|
+
}
|
|
14264
|
+
function buildProbeCommand(tool) {
|
|
14265
|
+
const binary = getToolBinary(tool);
|
|
14266
|
+
return `if command -v ${binary} >/dev/null 2>&1; then printf 'installed=1\\nversion='; ${binary} --version 2>/dev/null | head -n 1; printf '\\n'; else printf 'installed=0\\n'; fi`;
|
|
14267
|
+
}
|
|
14268
|
+
function parseProbe(tool, stdout) {
|
|
14269
|
+
const lines = stdout.trim().split(`
|
|
14270
|
+
`).filter(Boolean);
|
|
14271
|
+
const installedLine = lines.find((line) => line.startsWith("installed="));
|
|
14272
|
+
const versionLine = lines.find((line) => line.startsWith("version="));
|
|
14273
|
+
return {
|
|
14274
|
+
tool,
|
|
14275
|
+
packageName: AI_CLI_PACKAGES[tool],
|
|
14276
|
+
installed: installedLine === "installed=1",
|
|
14277
|
+
version: versionLine?.slice("version=".length) || undefined
|
|
14278
|
+
};
|
|
14279
|
+
}
|
|
14280
|
+
function buildClaudeInstallPlan(machineId, tools) {
|
|
14281
|
+
const machine = resolveMachine2(machineId);
|
|
14282
|
+
return {
|
|
14283
|
+
machineId: machine.id,
|
|
14284
|
+
mode: "plan",
|
|
14285
|
+
steps: buildInstallSteps(machine, tools),
|
|
14286
|
+
executed: 0
|
|
14287
|
+
};
|
|
14288
|
+
}
|
|
14289
|
+
function getClaudeCliStatus(machineId, tools) {
|
|
14290
|
+
const machine = resolveMachine2(machineId);
|
|
14291
|
+
const normalizedTools = normalizeTools(tools);
|
|
14292
|
+
const route = runMachineCommand(machine.id, "true").source;
|
|
14293
|
+
return {
|
|
14294
|
+
machineId: machine.id,
|
|
14295
|
+
source: route,
|
|
14296
|
+
tools: normalizedTools.map((tool) => parseProbe(tool, runMachineCommand(machine.id, buildProbeCommand(tool)).stdout))
|
|
14297
|
+
};
|
|
14298
|
+
}
|
|
14299
|
+
function diffClaudeCli(machineId, tools) {
|
|
14300
|
+
const status = getClaudeCliStatus(machineId, tools);
|
|
14301
|
+
return {
|
|
14302
|
+
...status,
|
|
14303
|
+
missing: status.tools.filter((tool) => !tool.installed).map((tool) => tool.tool),
|
|
14304
|
+
installed: status.tools.filter((tool) => tool.installed).map((tool) => tool.tool)
|
|
14305
|
+
};
|
|
14306
|
+
}
|
|
14307
|
+
function runClaudeInstall(machineId, tools, options = {}) {
|
|
14308
|
+
const plan = buildClaudeInstallPlan(machineId, tools);
|
|
14309
|
+
if (!options.apply)
|
|
14310
|
+
return plan;
|
|
14311
|
+
if (!options.yes) {
|
|
14312
|
+
throw new Error("Claude CLI installation requires --yes.");
|
|
14123
14313
|
}
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
this.notify(table);
|
|
14314
|
+
let executed = 0;
|
|
14315
|
+
for (const step of plan.steps) {
|
|
14316
|
+
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
14317
|
+
stdout: "pipe",
|
|
14318
|
+
stderr: "pipe",
|
|
14319
|
+
env: process.env
|
|
14320
|
+
});
|
|
14321
|
+
if (result.exitCode !== 0) {
|
|
14322
|
+
throw new Error(`AI CLI install failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
14134
14323
|
}
|
|
14135
|
-
|
|
14324
|
+
executed += 1;
|
|
14136
14325
|
}
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
|
|
14326
|
+
return {
|
|
14327
|
+
machineId: plan.machineId,
|
|
14328
|
+
mode: "apply",
|
|
14329
|
+
steps: plan.steps,
|
|
14330
|
+
executed
|
|
14331
|
+
};
|
|
14332
|
+
}
|
|
14333
|
+
|
|
14334
|
+
// src/commands/install-tailscale.ts
|
|
14335
|
+
function buildInstallSteps2(machine) {
|
|
14336
|
+
if (machine.platform === "macos") {
|
|
14337
|
+
return [
|
|
14338
|
+
{
|
|
14339
|
+
id: "tailscale-brew",
|
|
14340
|
+
title: "Install Tailscale via Homebrew",
|
|
14341
|
+
command: "brew install --cask tailscale",
|
|
14342
|
+
manager: "brew"
|
|
14343
|
+
}
|
|
14344
|
+
];
|
|
14146
14345
|
}
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
-
|
|
14151
|
-
|
|
14346
|
+
if (machine.platform === "windows") {
|
|
14347
|
+
return [
|
|
14348
|
+
{
|
|
14349
|
+
id: "tailscale-winget",
|
|
14350
|
+
title: "Install Tailscale via winget",
|
|
14351
|
+
command: "winget install Tailscale.Tailscale",
|
|
14352
|
+
manager: "custom"
|
|
14353
|
+
}
|
|
14354
|
+
];
|
|
14152
14355
|
}
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
|
|
14156
|
-
|
|
14157
|
-
|
|
14158
|
-
|
|
14159
|
-
|
|
14356
|
+
return [
|
|
14357
|
+
{
|
|
14358
|
+
id: "tailscale-linux",
|
|
14359
|
+
title: "Install Tailscale on Linux",
|
|
14360
|
+
command: "curl -fsSL https://tailscale.com/install.sh | sh",
|
|
14361
|
+
manager: "custom",
|
|
14362
|
+
privileged: true
|
|
14363
|
+
}
|
|
14364
|
+
];
|
|
14365
|
+
}
|
|
14366
|
+
function buildTailscaleInstallPlan(machineId) {
|
|
14367
|
+
const machine = (machineId ? getManifestMachine(machineId) : null) || detectCurrentMachineManifest();
|
|
14368
|
+
return {
|
|
14369
|
+
machineId: machine.id,
|
|
14370
|
+
mode: "plan",
|
|
14371
|
+
steps: buildInstallSteps2(machine),
|
|
14372
|
+
executed: 0
|
|
14373
|
+
};
|
|
14374
|
+
}
|
|
14375
|
+
function runTailscaleInstall(machineId, options = {}) {
|
|
14376
|
+
const plan = buildTailscaleInstallPlan(machineId);
|
|
14377
|
+
if (!options.apply)
|
|
14378
|
+
return plan;
|
|
14379
|
+
if (!options.yes) {
|
|
14380
|
+
throw new Error("Tailscale install requires --yes.");
|
|
14160
14381
|
}
|
|
14161
|
-
|
|
14162
|
-
|
|
14163
|
-
|
|
14164
|
-
|
|
14382
|
+
let executed = 0;
|
|
14383
|
+
for (const step of plan.steps) {
|
|
14384
|
+
const result = Bun.spawnSync(["bash", "-lc", step.command], {
|
|
14385
|
+
stdout: "pipe",
|
|
14386
|
+
stderr: "pipe",
|
|
14387
|
+
env: process.env
|
|
14388
|
+
});
|
|
14389
|
+
if (result.exitCode !== 0) {
|
|
14390
|
+
throw new Error(`Tailscale install failed (${step.id}): ${result.stderr.toString().trim()}`);
|
|
14391
|
+
}
|
|
14392
|
+
executed += 1;
|
|
14165
14393
|
}
|
|
14166
|
-
|
|
14167
|
-
|
|
14394
|
+
return {
|
|
14395
|
+
machineId: plan.machineId,
|
|
14396
|
+
mode: "apply",
|
|
14397
|
+
steps: plan.steps,
|
|
14398
|
+
executed
|
|
14399
|
+
};
|
|
14400
|
+
}
|
|
14401
|
+
|
|
14402
|
+
// src/commands/notifications.ts
|
|
14403
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
14404
|
+
var notificationChannelSchema = exports_external.object({
|
|
14405
|
+
id: exports_external.string(),
|
|
14406
|
+
type: exports_external.enum(["email", "webhook", "command"]),
|
|
14407
|
+
target: exports_external.string(),
|
|
14408
|
+
events: exports_external.array(exports_external.string()),
|
|
14409
|
+
enabled: exports_external.boolean()
|
|
14410
|
+
});
|
|
14411
|
+
var notificationConfigSchema = exports_external.object({
|
|
14412
|
+
version: exports_external.literal(1),
|
|
14413
|
+
updatedAt: exports_external.string().optional(),
|
|
14414
|
+
channels: exports_external.array(notificationChannelSchema)
|
|
14415
|
+
});
|
|
14416
|
+
function sortChannels(channels) {
|
|
14417
|
+
return [...channels].sort((left, right) => left.id.localeCompare(right.id));
|
|
14418
|
+
}
|
|
14419
|
+
function shellQuote2(value) {
|
|
14420
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
14421
|
+
}
|
|
14422
|
+
function hasCommand(binary) {
|
|
14423
|
+
const result = Bun.spawnSync(["bash", "-lc", `command -v ${binary} >/dev/null 2>&1`], {
|
|
14424
|
+
stdout: "ignore",
|
|
14425
|
+
stderr: "ignore",
|
|
14426
|
+
env: process.env
|
|
14427
|
+
});
|
|
14428
|
+
return result.exitCode === 0;
|
|
14429
|
+
}
|
|
14430
|
+
function buildNotificationPreview(channel, event, message) {
|
|
14431
|
+
if (channel.type === "email") {
|
|
14432
|
+
return `send email to ${channel.target}: [${event}] ${message}`;
|
|
14168
14433
|
}
|
|
14169
|
-
|
|
14170
|
-
return
|
|
14434
|
+
if (channel.type === "webhook") {
|
|
14435
|
+
return `POST ${channel.target} with payload {"event":"${event}","message":"${message}"}`;
|
|
14171
14436
|
}
|
|
14172
|
-
|
|
14173
|
-
|
|
14437
|
+
return `${channel.target} --event ${event} --message ${JSON.stringify(message)}`;
|
|
14438
|
+
}
|
|
14439
|
+
async function dispatchEmail(channel, event, message) {
|
|
14440
|
+
const subject = `[${event}] machines notification`;
|
|
14441
|
+
const body = `To: ${channel.target}
|
|
14442
|
+
Subject: ${subject}
|
|
14443
|
+
Content-Type: text/plain; charset=utf-8
|
|
14444
|
+
|
|
14445
|
+
${message}
|
|
14446
|
+
`;
|
|
14447
|
+
if (hasCommand("sendmail")) {
|
|
14448
|
+
const result = Bun.spawnSync(["bash", "-lc", "sendmail -t"], {
|
|
14449
|
+
stdin: new TextEncoder().encode(body),
|
|
14450
|
+
stdout: "pipe",
|
|
14451
|
+
stderr: "pipe",
|
|
14452
|
+
env: process.env
|
|
14453
|
+
});
|
|
14454
|
+
if (result.exitCode !== 0) {
|
|
14455
|
+
throw new Error(result.stderr.toString().trim() || `sendmail exited with ${result.exitCode}`);
|
|
14456
|
+
}
|
|
14457
|
+
return {
|
|
14458
|
+
channelId: channel.id,
|
|
14459
|
+
event,
|
|
14460
|
+
delivered: true,
|
|
14461
|
+
transport: channel.type,
|
|
14462
|
+
detail: `Delivered via sendmail to ${channel.target}`
|
|
14463
|
+
};
|
|
14174
14464
|
}
|
|
14175
|
-
|
|
14176
|
-
const
|
|
14177
|
-
|
|
14178
|
-
|
|
14465
|
+
if (hasCommand("mail")) {
|
|
14466
|
+
const command = `printf %s ${shellQuote2(message)} | mail -s ${shellQuote2(subject)} ${shellQuote2(channel.target)}`;
|
|
14467
|
+
const result = Bun.spawnSync(["bash", "-lc", command], {
|
|
14468
|
+
stdout: "pipe",
|
|
14469
|
+
stderr: "pipe",
|
|
14470
|
+
env: process.env
|
|
14471
|
+
});
|
|
14472
|
+
if (result.exitCode !== 0) {
|
|
14473
|
+
throw new Error(result.stderr.toString().trim() || `mail exited with ${result.exitCode}`);
|
|
14179
14474
|
}
|
|
14475
|
+
return {
|
|
14476
|
+
channelId: channel.id,
|
|
14477
|
+
event,
|
|
14478
|
+
delivered: true,
|
|
14479
|
+
transport: channel.type,
|
|
14480
|
+
detail: `Delivered via mail to ${channel.target}`
|
|
14481
|
+
};
|
|
14180
14482
|
}
|
|
14483
|
+
throw new Error("No local email transport available. Install sendmail or mail.");
|
|
14181
14484
|
}
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
init_config();
|
|
14199
|
-
init_dotfile();
|
|
14200
|
-
init_adapter();
|
|
14201
|
-
|
|
14202
|
-
// src/db.ts
|
|
14203
|
-
var adapter = null;
|
|
14204
|
-
function createTables(db) {
|
|
14205
|
-
db.exec(`
|
|
14206
|
-
CREATE TABLE IF NOT EXISTS agent_heartbeats (
|
|
14207
|
-
machine_id TEXT NOT NULL,
|
|
14208
|
-
pid INTEGER NOT NULL,
|
|
14209
|
-
status TEXT NOT NULL,
|
|
14210
|
-
updated_at TEXT NOT NULL,
|
|
14211
|
-
PRIMARY KEY (machine_id, pid)
|
|
14212
|
-
)
|
|
14213
|
-
`);
|
|
14214
|
-
db.exec(`
|
|
14215
|
-
CREATE TABLE IF NOT EXISTS setup_runs (
|
|
14216
|
-
id TEXT PRIMARY KEY,
|
|
14217
|
-
machine_id TEXT NOT NULL,
|
|
14218
|
-
status TEXT NOT NULL,
|
|
14219
|
-
details_json TEXT NOT NULL DEFAULT '[]',
|
|
14220
|
-
created_at TEXT NOT NULL,
|
|
14221
|
-
updated_at TEXT NOT NULL
|
|
14222
|
-
)
|
|
14223
|
-
`);
|
|
14224
|
-
db.exec(`
|
|
14225
|
-
CREATE TABLE IF NOT EXISTS sync_runs (
|
|
14226
|
-
id TEXT PRIMARY KEY,
|
|
14227
|
-
machine_id TEXT NOT NULL,
|
|
14228
|
-
status TEXT NOT NULL,
|
|
14229
|
-
actions_json TEXT NOT NULL DEFAULT '[]',
|
|
14230
|
-
created_at TEXT NOT NULL,
|
|
14231
|
-
updated_at TEXT NOT NULL
|
|
14232
|
-
)
|
|
14233
|
-
`);
|
|
14234
|
-
}
|
|
14235
|
-
function getAdapter(path = getDbPath()) {
|
|
14236
|
-
if (path === ":memory:") {
|
|
14237
|
-
const memoryAdapter = new SqliteAdapter(path);
|
|
14238
|
-
createTables(memoryAdapter.raw);
|
|
14239
|
-
return memoryAdapter;
|
|
14240
|
-
}
|
|
14241
|
-
if (!adapter) {
|
|
14242
|
-
ensureParentDir(path);
|
|
14243
|
-
adapter = new SqliteAdapter(path);
|
|
14244
|
-
createTables(adapter.raw);
|
|
14485
|
+
async function dispatchWebhook(channel, event, message) {
|
|
14486
|
+
const response = await fetch(channel.target, {
|
|
14487
|
+
method: "POST",
|
|
14488
|
+
headers: {
|
|
14489
|
+
"content-type": "application/json"
|
|
14490
|
+
},
|
|
14491
|
+
body: JSON.stringify({
|
|
14492
|
+
channelId: channel.id,
|
|
14493
|
+
event,
|
|
14494
|
+
message,
|
|
14495
|
+
sentAt: new Date().toISOString()
|
|
14496
|
+
})
|
|
14497
|
+
});
|
|
14498
|
+
if (!response.ok) {
|
|
14499
|
+
const text = await response.text();
|
|
14500
|
+
throw new Error(`Webhook responded ${response.status}: ${text || response.statusText}`);
|
|
14245
14501
|
}
|
|
14246
|
-
return
|
|
14502
|
+
return {
|
|
14503
|
+
channelId: channel.id,
|
|
14504
|
+
event,
|
|
14505
|
+
delivered: true,
|
|
14506
|
+
transport: channel.type,
|
|
14507
|
+
detail: `Webhook accepted with HTTP ${response.status}`
|
|
14508
|
+
};
|
|
14509
|
+
}
|
|
14510
|
+
async function dispatchCommand(channel, event, message) {
|
|
14511
|
+
const result = Bun.spawnSync(["bash", "-lc", channel.target], {
|
|
14512
|
+
stdout: "pipe",
|
|
14513
|
+
stderr: "pipe",
|
|
14514
|
+
env: {
|
|
14515
|
+
...process.env,
|
|
14516
|
+
HASNA_MACHINES_NOTIFICATION_CHANNEL: channel.id,
|
|
14517
|
+
HASNA_MACHINES_NOTIFICATION_EVENT: event,
|
|
14518
|
+
HASNA_MACHINES_NOTIFICATION_MESSAGE: message
|
|
14519
|
+
}
|
|
14520
|
+
});
|
|
14521
|
+
if (result.exitCode !== 0) {
|
|
14522
|
+
throw new Error(result.stderr.toString().trim() || `command exited with ${result.exitCode}`);
|
|
14523
|
+
}
|
|
14524
|
+
const stdout = result.stdout.toString().trim();
|
|
14525
|
+
return {
|
|
14526
|
+
channelId: channel.id,
|
|
14527
|
+
event,
|
|
14528
|
+
delivered: true,
|
|
14529
|
+
transport: channel.type,
|
|
14530
|
+
detail: stdout || "Command completed successfully"
|
|
14531
|
+
};
|
|
14247
14532
|
}
|
|
14248
|
-
function
|
|
14249
|
-
|
|
14533
|
+
async function dispatchChannel(channel, event, message) {
|
|
14534
|
+
if (!channel.enabled) {
|
|
14535
|
+
return {
|
|
14536
|
+
channelId: channel.id,
|
|
14537
|
+
event,
|
|
14538
|
+
delivered: false,
|
|
14539
|
+
transport: channel.type,
|
|
14540
|
+
detail: "Channel is disabled"
|
|
14541
|
+
};
|
|
14542
|
+
}
|
|
14543
|
+
if (channel.type === "email") {
|
|
14544
|
+
return dispatchEmail(channel, event, message);
|
|
14545
|
+
}
|
|
14546
|
+
if (channel.type === "webhook") {
|
|
14547
|
+
return dispatchWebhook(channel, event, message);
|
|
14548
|
+
}
|
|
14549
|
+
return dispatchCommand(channel, event, message);
|
|
14250
14550
|
}
|
|
14251
|
-
function
|
|
14252
|
-
return
|
|
14551
|
+
function getDefaultNotificationConfig() {
|
|
14552
|
+
return {
|
|
14553
|
+
version: 1,
|
|
14554
|
+
updatedAt: new Date().toISOString(),
|
|
14555
|
+
channels: []
|
|
14556
|
+
};
|
|
14253
14557
|
}
|
|
14254
|
-
function
|
|
14255
|
-
|
|
14256
|
-
|
|
14257
|
-
return db.query(`SELECT machine_id, pid, status, updated_at
|
|
14258
|
-
FROM agent_heartbeats
|
|
14259
|
-
WHERE machine_id = ?
|
|
14260
|
-
ORDER BY updated_at DESC`).all(machineId);
|
|
14558
|
+
function readNotificationConfig(path = getNotificationsPath()) {
|
|
14559
|
+
if (!existsSync5(path)) {
|
|
14560
|
+
return getDefaultNotificationConfig();
|
|
14261
14561
|
}
|
|
14262
|
-
return
|
|
14263
|
-
FROM agent_heartbeats
|
|
14264
|
-
ORDER BY updated_at DESC`).all();
|
|
14562
|
+
return notificationConfigSchema.parse(JSON.parse(readFileSync4(path, "utf8")));
|
|
14265
14563
|
}
|
|
14266
|
-
function
|
|
14267
|
-
|
|
14268
|
-
const
|
|
14269
|
-
|
|
14564
|
+
function writeNotificationConfig(config, path = getNotificationsPath()) {
|
|
14565
|
+
ensureParentDir(path);
|
|
14566
|
+
const nextConfig = {
|
|
14567
|
+
version: 1,
|
|
14568
|
+
updatedAt: new Date().toISOString(),
|
|
14569
|
+
channels: sortChannels(config.channels)
|
|
14570
|
+
};
|
|
14571
|
+
writeFileSync4(path, `${JSON.stringify(nextConfig, null, 2)}
|
|
14572
|
+
`, "utf8");
|
|
14573
|
+
return nextConfig;
|
|
14270
14574
|
}
|
|
14271
|
-
function
|
|
14272
|
-
|
|
14273
|
-
const now = new Date().toISOString();
|
|
14274
|
-
db.query(`INSERT INTO setup_runs (id, machine_id, status, details_json, created_at, updated_at)
|
|
14275
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(crypto.randomUUID(), machineId, status, JSON.stringify(details), now, now);
|
|
14575
|
+
function listNotificationChannels() {
|
|
14576
|
+
return readNotificationConfig();
|
|
14276
14577
|
}
|
|
14277
|
-
function
|
|
14278
|
-
const
|
|
14279
|
-
const
|
|
14280
|
-
|
|
14281
|
-
|
|
14578
|
+
function addNotificationChannel(channel) {
|
|
14579
|
+
const config = readNotificationConfig();
|
|
14580
|
+
const channels = config.channels.filter((entry) => entry.id !== channel.id);
|
|
14581
|
+
channels.push({
|
|
14582
|
+
...channel,
|
|
14583
|
+
events: [...new Set(channel.events)]
|
|
14584
|
+
});
|
|
14585
|
+
return writeNotificationConfig({ ...config, channels });
|
|
14282
14586
|
}
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
14587
|
+
function removeNotificationChannel(channelId) {
|
|
14588
|
+
const config = readNotificationConfig();
|
|
14589
|
+
return writeNotificationConfig({
|
|
14590
|
+
...config,
|
|
14591
|
+
channels: config.channels.filter((channel) => channel.id !== channelId)
|
|
14592
|
+
});
|
|
14289
14593
|
}
|
|
14290
|
-
function
|
|
14291
|
-
const
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
stdio: "ignore"
|
|
14594
|
+
async function dispatchNotificationEvent(event, message, options = {}) {
|
|
14595
|
+
const channels = readNotificationConfig().channels.filter((channel) => {
|
|
14596
|
+
if (options.channelId && channel.id !== options.channelId) {
|
|
14597
|
+
return false;
|
|
14598
|
+
}
|
|
14599
|
+
return channel.events.includes(event) || event === "manual.test";
|
|
14297
14600
|
});
|
|
14298
|
-
|
|
14601
|
+
const deliveries = [];
|
|
14602
|
+
for (const channel of channels) {
|
|
14603
|
+
try {
|
|
14604
|
+
deliveries.push(await dispatchChannel(channel, event, message));
|
|
14605
|
+
} catch (error) {
|
|
14606
|
+
deliveries.push({
|
|
14607
|
+
channelId: channel.id,
|
|
14608
|
+
event,
|
|
14609
|
+
delivered: false,
|
|
14610
|
+
transport: channel.type,
|
|
14611
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
14612
|
+
});
|
|
14613
|
+
}
|
|
14614
|
+
}
|
|
14615
|
+
return {
|
|
14616
|
+
event,
|
|
14617
|
+
message,
|
|
14618
|
+
deliveries
|
|
14619
|
+
};
|
|
14299
14620
|
}
|
|
14300
|
-
function
|
|
14301
|
-
const
|
|
14302
|
-
if (!
|
|
14303
|
-
throw new Error(`
|
|
14621
|
+
async function testNotificationChannel(channelId, event = "manual.test", message = "machines notification test", options = {}) {
|
|
14622
|
+
const channel = readNotificationConfig().channels.find((entry) => entry.id === channelId);
|
|
14623
|
+
if (!channel) {
|
|
14624
|
+
throw new Error(`Notification channel not found: ${channelId}`);
|
|
14304
14625
|
}
|
|
14305
|
-
const
|
|
14306
|
-
if (
|
|
14626
|
+
const preview = buildNotificationPreview(channel, event, message);
|
|
14627
|
+
if (!options.apply) {
|
|
14307
14628
|
return {
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14629
|
+
channelId,
|
|
14630
|
+
mode: "plan",
|
|
14631
|
+
delivered: false,
|
|
14632
|
+
preview,
|
|
14633
|
+
detail: "Preview only"
|
|
14311
14634
|
};
|
|
14312
14635
|
}
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14636
|
+
if (!options.yes) {
|
|
14637
|
+
throw new Error("Notification test execution requires --yes.");
|
|
14638
|
+
}
|
|
14639
|
+
const delivery = await dispatchChannel(channel, event, message);
|
|
14316
14640
|
return {
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14641
|
+
channelId,
|
|
14642
|
+
mode: "apply",
|
|
14643
|
+
delivered: delivery.delivered,
|
|
14644
|
+
preview,
|
|
14645
|
+
detail: delivery.detail
|
|
14320
14646
|
};
|
|
14321
14647
|
}
|
|
14322
|
-
function buildSshCommand(machineId, remoteCommand) {
|
|
14323
|
-
const resolved = resolveSshTarget(machineId);
|
|
14324
|
-
return remoteCommand ? `ssh ${resolved.target} ${JSON.stringify(remoteCommand)}` : `ssh ${resolved.target}`;
|
|
14325
|
-
}
|
|
14326
14648
|
|
|
14327
14649
|
// src/commands/ports.ts
|
|
14650
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
14328
14651
|
function parseSsOutput(output) {
|
|
14329
14652
|
return output.trim().split(`
|
|
14330
14653
|
`).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
@@ -14366,7 +14689,7 @@ function listPorts(machineId) {
|
|
|
14366
14689
|
const isLocal = targetMachineId === getLocalMachineId();
|
|
14367
14690
|
const localCommand = "if command -v ss >/dev/null 2>&1; then ss -ltnpH; else lsof -nP -iTCP -sTCP:LISTEN; fi";
|
|
14368
14691
|
const command = isLocal ? localCommand : buildSshCommand(targetMachineId, localCommand);
|
|
14369
|
-
const result =
|
|
14692
|
+
const result = spawnSync3("bash", ["-lc", command], { encoding: "utf8" });
|
|
14370
14693
|
if (result.status !== 0) {
|
|
14371
14694
|
throw new Error(result.stderr || `Failed to list ports for ${targetMachineId}`);
|
|
14372
14695
|
}
|
|
@@ -14377,6 +14700,55 @@ function listPorts(machineId) {
|
|
|
14377
14700
|
};
|
|
14378
14701
|
}
|
|
14379
14702
|
|
|
14703
|
+
// src/commands/manifest.ts
|
|
14704
|
+
function manifestList() {
|
|
14705
|
+
return readManifest();
|
|
14706
|
+
}
|
|
14707
|
+
function manifestAdd(machine) {
|
|
14708
|
+
const manifest = readManifest();
|
|
14709
|
+
const nextMachines = manifest.machines.filter((entry) => entry.id !== machine.id);
|
|
14710
|
+
nextMachines.push(machine);
|
|
14711
|
+
const nextManifest = { ...manifest, machines: nextMachines };
|
|
14712
|
+
writeManifest(nextManifest);
|
|
14713
|
+
return nextManifest;
|
|
14714
|
+
}
|
|
14715
|
+
function manifestBootstrapCurrentMachine() {
|
|
14716
|
+
return manifestAdd(detectCurrentMachineManifest());
|
|
14717
|
+
}
|
|
14718
|
+
function manifestGet(machineId) {
|
|
14719
|
+
return getManifestMachine(machineId);
|
|
14720
|
+
}
|
|
14721
|
+
function manifestRemove(machineId) {
|
|
14722
|
+
const manifest = readManifest();
|
|
14723
|
+
const nextManifest = {
|
|
14724
|
+
...manifest,
|
|
14725
|
+
machines: manifest.machines.filter((machine) => machine.id !== machineId)
|
|
14726
|
+
};
|
|
14727
|
+
writeManifest(nextManifest);
|
|
14728
|
+
return nextManifest;
|
|
14729
|
+
}
|
|
14730
|
+
function manifestValidate() {
|
|
14731
|
+
return validateManifest(getManifestPath());
|
|
14732
|
+
}
|
|
14733
|
+
|
|
14734
|
+
// src/version.ts
|
|
14735
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
14736
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
14737
|
+
import { fileURLToPath } from "url";
|
|
14738
|
+
function getPackageVersion() {
|
|
14739
|
+
try {
|
|
14740
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
14741
|
+
const candidates = [join8(here, "..", "package.json"), join8(here, "..", "..", "package.json")];
|
|
14742
|
+
const pkgPath = candidates.find((candidate) => existsSync6(candidate));
|
|
14743
|
+
if (!pkgPath) {
|
|
14744
|
+
return "0.0.0";
|
|
14745
|
+
}
|
|
14746
|
+
return JSON.parse(readFileSync5(pkgPath, "utf8")).version || "0.0.0";
|
|
14747
|
+
} catch {
|
|
14748
|
+
return "0.0.0";
|
|
14749
|
+
}
|
|
14750
|
+
}
|
|
14751
|
+
|
|
14380
14752
|
// src/commands/status.ts
|
|
14381
14753
|
function getStatus() {
|
|
14382
14754
|
const manifest = readManifest();
|
|
@@ -14409,35 +14781,34 @@ function getStatus() {
|
|
|
14409
14781
|
};
|
|
14410
14782
|
}
|
|
14411
14783
|
|
|
14412
|
-
// src/commands/
|
|
14413
|
-
function
|
|
14414
|
-
return
|
|
14415
|
-
}
|
|
14416
|
-
function manifestAdd(machine) {
|
|
14417
|
-
const manifest = readManifest();
|
|
14418
|
-
const nextMachines = manifest.machines.filter((entry) => entry.id !== machine.id);
|
|
14419
|
-
nextMachines.push(machine);
|
|
14420
|
-
const nextManifest = { ...manifest, machines: nextMachines };
|
|
14421
|
-
writeManifest(nextManifest);
|
|
14422
|
-
return nextManifest;
|
|
14423
|
-
}
|
|
14424
|
-
function manifestBootstrapCurrentMachine() {
|
|
14425
|
-
return manifestAdd(detectCurrentMachineManifest());
|
|
14426
|
-
}
|
|
14427
|
-
function manifestGet(machineId) {
|
|
14428
|
-
return getManifestMachine(machineId);
|
|
14784
|
+
// src/commands/self-test.ts
|
|
14785
|
+
function check(id, status, summary, detail) {
|
|
14786
|
+
return { id, status, summary, detail };
|
|
14429
14787
|
}
|
|
14430
|
-
function
|
|
14431
|
-
const
|
|
14432
|
-
const
|
|
14433
|
-
|
|
14434
|
-
|
|
14788
|
+
function runSelfTest() {
|
|
14789
|
+
const version = getPackageVersion();
|
|
14790
|
+
const status = getStatus();
|
|
14791
|
+
const doctor = runDoctor();
|
|
14792
|
+
const serveInfo = getServeInfo();
|
|
14793
|
+
const html = renderDashboardHtml();
|
|
14794
|
+
const notifications = listNotificationChannels();
|
|
14795
|
+
const apps = listApps(status.machineId);
|
|
14796
|
+
const appsDiff = diffApps(status.machineId);
|
|
14797
|
+
const cliPlan = buildClaudeInstallPlan(status.machineId);
|
|
14798
|
+
return {
|
|
14799
|
+
machineId: getLocalMachineId(),
|
|
14800
|
+
checks: [
|
|
14801
|
+
check("package-version", version === "0.0.0" ? "fail" : "ok", "Package version resolves", version),
|
|
14802
|
+
check("status", "ok", "Status loads", JSON.stringify({ machines: status.manifestMachineCount, heartbeats: status.heartbeatCount })),
|
|
14803
|
+
check("doctor", doctor.checks.some((entry) => entry.status === "fail") ? "warn" : "ok", "Doctor completed", `${doctor.checks.length} checks`),
|
|
14804
|
+
check("serve-info", "ok", "Dashboard info renders", `${serveInfo.url} routes=${serveInfo.routes.length}`),
|
|
14805
|
+
check("dashboard-html", html.includes("Machines Dashboard") ? "ok" : "fail", "Dashboard HTML renders", html.slice(0, 80)),
|
|
14806
|
+
check("notifications", "ok", "Notifications config loads", `${notifications.channels.length} channels`),
|
|
14807
|
+
check("apps", "ok", "Apps manifest loads", `${apps.apps.length} apps`),
|
|
14808
|
+
check("apps-diff", appsDiff.missing.length === 0 ? "ok" : "warn", "Apps diff computed", `missing=${appsDiff.missing.length} installed=${appsDiff.installed.length}`),
|
|
14809
|
+
check("install-claude-plan", cliPlan.steps.length > 0 ? "ok" : "warn", "Install plan renders", `${cliPlan.steps.length} steps`)
|
|
14810
|
+
]
|
|
14435
14811
|
};
|
|
14436
|
-
writeManifest(nextManifest);
|
|
14437
|
-
return nextManifest;
|
|
14438
|
-
}
|
|
14439
|
-
function manifestValidate() {
|
|
14440
|
-
return validateManifest(getManifestPath());
|
|
14441
14812
|
}
|
|
14442
14813
|
|
|
14443
14814
|
// src/commands/serve.ts
|
|
@@ -14451,13 +14822,27 @@ function getServeInfo(options = {}) {
|
|
|
14451
14822
|
host,
|
|
14452
14823
|
port,
|
|
14453
14824
|
url: `http://${host}:${port}`,
|
|
14454
|
-
routes: [
|
|
14825
|
+
routes: [
|
|
14826
|
+
"/",
|
|
14827
|
+
"/health",
|
|
14828
|
+
"/api/status",
|
|
14829
|
+
"/api/manifest",
|
|
14830
|
+
"/api/notifications",
|
|
14831
|
+
"/api/doctor",
|
|
14832
|
+
"/api/self-test",
|
|
14833
|
+
"/api/apps/status",
|
|
14834
|
+
"/api/apps/diff",
|
|
14835
|
+
"/api/install-claude/status",
|
|
14836
|
+
"/api/install-claude/diff",
|
|
14837
|
+
"/api/notifications/test"
|
|
14838
|
+
]
|
|
14455
14839
|
};
|
|
14456
14840
|
}
|
|
14457
14841
|
function renderDashboardHtml() {
|
|
14458
14842
|
const status = getStatus();
|
|
14459
14843
|
const manifest = manifestList();
|
|
14460
14844
|
const notifications = listNotificationChannels();
|
|
14845
|
+
const doctor = runDoctor();
|
|
14461
14846
|
return `<!doctype html>
|
|
14462
14847
|
<html lang="en">
|
|
14463
14848
|
<head>
|
|
@@ -14473,12 +14858,14 @@ function renderDashboardHtml() {
|
|
|
14473
14858
|
.card { background: #121933; border: 1px solid #243057; border-radius: 16px; padding: 20px; }
|
|
14474
14859
|
.stat { font-size: 32px; font-weight: 700; margin-top: 8px; }
|
|
14475
14860
|
table { width: 100%; border-collapse: collapse; }
|
|
14476
|
-
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; }
|
|
14861
|
+
th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid #243057; vertical-align: top; }
|
|
14477
14862
|
code { color: #9ed0ff; }
|
|
14478
14863
|
.badge { display: inline-block; border-radius: 999px; padding: 4px 10px; font-size: 12px; }
|
|
14479
|
-
.online { background: #12351f; color: #74f0a7; }
|
|
14480
|
-
.offline { background: #3b1a1a; color: #ff8c8c; }
|
|
14481
|
-
.unknown { background: #2f2b16; color: #ffd76a; }
|
|
14864
|
+
.online, .ok { background: #12351f; color: #74f0a7; }
|
|
14865
|
+
.offline, .fail { background: #3b1a1a; color: #ff8c8c; }
|
|
14866
|
+
.unknown, .warn { background: #2f2b16; color: #ffd76a; }
|
|
14867
|
+
ul { margin: 8px 0 0; padding-left: 18px; }
|
|
14868
|
+
.muted { color: #9fb0d9; }
|
|
14482
14869
|
</style>
|
|
14483
14870
|
</head>
|
|
14484
14871
|
<body>
|
|
@@ -14488,6 +14875,7 @@ function renderDashboardHtml() {
|
|
|
14488
14875
|
<section class="card"><div>Manifest machines</div><div class="stat">${status.manifestMachineCount}</div></section>
|
|
14489
14876
|
<section class="card"><div>Heartbeats</div><div class="stat">${status.heartbeatCount}</div></section>
|
|
14490
14877
|
<section class="card"><div>Notification channels</div><div class="stat">${notifications.channels.length}</div></section>
|
|
14878
|
+
<section class="card"><div>Doctor warnings</div><div class="stat">${doctor.checks.filter((entry) => entry.status !== "ok").length}</div></section>
|
|
14491
14879
|
</div>
|
|
14492
14880
|
|
|
14493
14881
|
<section class="card" style="margin-top:16px">
|
|
@@ -14505,6 +14893,25 @@ function renderDashboardHtml() {
|
|
|
14505
14893
|
</table>
|
|
14506
14894
|
</section>
|
|
14507
14895
|
|
|
14896
|
+
<section class="card" style="margin-top:16px">
|
|
14897
|
+
<h2>Doctor</h2>
|
|
14898
|
+
<table>
|
|
14899
|
+
<thead><tr><th>Check</th><th>Status</th><th>Detail</th></tr></thead>
|
|
14900
|
+
<tbody>
|
|
14901
|
+
${doctor.checks.map((entry) => `<tr>
|
|
14902
|
+
<td>${escapeHtml(entry.summary)}</td>
|
|
14903
|
+
<td><span class="badge ${escapeHtml(entry.status)}">${escapeHtml(entry.status)}</span></td>
|
|
14904
|
+
<td class="muted">${escapeHtml(entry.detail)}</td>
|
|
14905
|
+
</tr>`).join("")}
|
|
14906
|
+
</tbody>
|
|
14907
|
+
</table>
|
|
14908
|
+
</section>
|
|
14909
|
+
|
|
14910
|
+
<section class="card" style="margin-top:16px">
|
|
14911
|
+
<h2>Self Test</h2>
|
|
14912
|
+
<p class="muted">Use <code>/api/self-test</code> for the full smoke-check payload.</p>
|
|
14913
|
+
</section>
|
|
14914
|
+
|
|
14508
14915
|
<section class="card" style="margin-top:16px">
|
|
14509
14916
|
<h2>Manifest</h2>
|
|
14510
14917
|
<pre>${escapeHtml(JSON.stringify(manifest, null, 2))}</pre>
|
|
@@ -14630,7 +15037,7 @@ function runSetup(machineId, options = {}) {
|
|
|
14630
15037
|
}
|
|
14631
15038
|
|
|
14632
15039
|
// src/commands/sync.ts
|
|
14633
|
-
import { existsSync as
|
|
15040
|
+
import { existsSync as existsSync7, lstatSync, readFileSync as readFileSync6, symlinkSync, copyFileSync as copyFileSync2 } from "fs";
|
|
14634
15041
|
function quote4(value) {
|
|
14635
15042
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
14636
15043
|
}
|
|
@@ -14661,12 +15068,12 @@ function packageInstallCommand(machine, packageName, manager = machine.platform
|
|
|
14661
15068
|
function detectPackageActions(machine) {
|
|
14662
15069
|
return (machine.packages || []).map((pkg, index) => {
|
|
14663
15070
|
const manager = pkg.manager || (machine.platform === "macos" ? "brew" : "apt");
|
|
14664
|
-
const
|
|
15071
|
+
const check2 = Bun.spawnSync(["bash", "-lc", packageCheckCommand(machine, pkg.name, manager)], {
|
|
14665
15072
|
stdout: "ignore",
|
|
14666
15073
|
stderr: "ignore",
|
|
14667
15074
|
env: process.env
|
|
14668
15075
|
});
|
|
14669
|
-
const installed =
|
|
15076
|
+
const installed = check2.exitCode === 0;
|
|
14670
15077
|
return {
|
|
14671
15078
|
id: `package-${index + 1}`,
|
|
14672
15079
|
title: `${installed ? "Package present" : "Install package"} ${pkg.name}`,
|
|
@@ -14678,15 +15085,15 @@ function detectPackageActions(machine) {
|
|
|
14678
15085
|
}
|
|
14679
15086
|
function detectFileActions(machine) {
|
|
14680
15087
|
return (machine.files || []).map((file, index) => {
|
|
14681
|
-
const sourceExists =
|
|
14682
|
-
const targetExists =
|
|
15088
|
+
const sourceExists = existsSync7(file.source);
|
|
15089
|
+
const targetExists = existsSync7(file.target);
|
|
14683
15090
|
let status = "missing";
|
|
14684
15091
|
if (sourceExists && targetExists) {
|
|
14685
15092
|
if (file.mode === "symlink") {
|
|
14686
15093
|
status = lstatSync(file.target).isSymbolicLink() ? "ok" : "drifted";
|
|
14687
15094
|
} else {
|
|
14688
|
-
const source =
|
|
14689
|
-
const target =
|
|
15095
|
+
const source = readFileSync6(file.source, "utf8");
|
|
15096
|
+
const target = readFileSync6(file.target, "utf8");
|
|
14690
15097
|
status = source === target ? "ok" : "drifted";
|
|
14691
15098
|
}
|
|
14692
15099
|
}
|
|
@@ -14793,25 +15200,19 @@ function getAgentStatus(machineId = getLocalMachineId()) {
|
|
|
14793
15200
|
|
|
14794
15201
|
// src/mcp/server.ts
|
|
14795
15202
|
function createMcpServer(version) {
|
|
14796
|
-
const server = new McpServer({
|
|
14797
|
-
name: "machines",
|
|
14798
|
-
version
|
|
14799
|
-
});
|
|
15203
|
+
const server = new McpServer({ name: "machines", version });
|
|
14800
15204
|
server.tool("machines_status", "Return local machine fleet status paths and machine identity.", {}, async () => ({
|
|
14801
15205
|
content: [{ type: "text", text: JSON.stringify(getStatus(), null, 2) }]
|
|
14802
15206
|
}));
|
|
14803
|
-
server.tool("
|
|
14804
|
-
|
|
14805
|
-
|
|
14806
|
-
server.tool("machines_apps_plan", "Preview app install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14807
|
-
content: [{ type: "text", text: JSON.stringify(buildAppsPlan(machine_id), null, 2) }]
|
|
14808
|
-
}));
|
|
14809
|
-
server.tool("machines_apps_apply", "Install manifest-managed apps for a machine.", {
|
|
14810
|
-
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14811
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14812
|
-
}, async ({ machine_id, yes }) => ({
|
|
14813
|
-
content: [{ type: "text", text: JSON.stringify(runAppsInstall(machine_id, { apply: true, yes }), null, 2) }]
|
|
15207
|
+
server.tool("machines_doctor", "Run machine preflight checks.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(runDoctor(machine_id), null, 2) }] }));
|
|
15208
|
+
server.tool("machines_self_test", "Run local package smoke checks.", {}, async () => ({
|
|
15209
|
+
content: [{ type: "text", text: JSON.stringify(runSelfTest(), null, 2) }]
|
|
14814
15210
|
}));
|
|
15211
|
+
server.tool("machines_apps_list", "List manifest-managed apps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(listApps(machine_id), null, 2) }] }));
|
|
15212
|
+
server.tool("machines_apps_status", "Check installed state for manifest-managed apps.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(getAppsStatus(machine_id), null, 2) }] }));
|
|
15213
|
+
server.tool("machines_apps_diff", "Show missing and installed manifest-managed apps.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(diffApps(machine_id), null, 2) }] }));
|
|
15214
|
+
server.tool("machines_apps_plan", "Preview app install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildAppsPlan(machine_id), null, 2) }] }));
|
|
15215
|
+
server.tool("machines_apps_apply", "Install manifest-managed apps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runAppsInstall(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
14815
15216
|
server.tool("machines_manifest", "Read the current fleet manifest.", {}, async () => ({
|
|
14816
15217
|
content: [{ type: "text", text: JSON.stringify(manifestList(), null, 2) }]
|
|
14817
15218
|
}));
|
|
@@ -14821,45 +15222,33 @@ function createMcpServer(version) {
|
|
|
14821
15222
|
server.tool("machines_manifest_bootstrap", "Detect and upsert the current machine into the fleet manifest.", {}, async () => ({
|
|
14822
15223
|
content: [{ type: "text", text: JSON.stringify(manifestBootstrapCurrentMachine(), null, 2) }]
|
|
14823
15224
|
}));
|
|
14824
|
-
server.tool("machines_manifest_get", "Read a single machine from the fleet manifest.", { machine_id: exports_external.string().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14825
|
-
|
|
14826
|
-
}));
|
|
14827
|
-
server.tool("machines_manifest_remove", "Remove a single machine from the fleet manifest.", { machine_id: exports_external.string().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14828
|
-
content: [{ type: "text", text: JSON.stringify(manifestRemove(machine_id), null, 2) }]
|
|
14829
|
-
}));
|
|
15225
|
+
server.tool("machines_manifest_get", "Read a single machine from the fleet manifest.", { machine_id: exports_external.string().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(manifestGet(machine_id), null, 2) }] }));
|
|
15226
|
+
server.tool("machines_manifest_remove", "Remove a single machine from the fleet manifest.", { machine_id: exports_external.string().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(manifestRemove(machine_id), null, 2) }] }));
|
|
14830
15227
|
server.tool("machines_agent_status", "List current machine agent heartbeats.", {}, async () => ({
|
|
14831
15228
|
content: [{ type: "text", text: JSON.stringify(getAgentStatus(), null, 2) }]
|
|
14832
15229
|
}));
|
|
14833
|
-
server.tool("machines_setup_preview", "Preview setup actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14834
|
-
|
|
14835
|
-
}));
|
|
14836
|
-
server.tool("
|
|
14837
|
-
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14838
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14839
|
-
}, async ({ machine_id, yes }) => ({
|
|
14840
|
-
content: [{ type: "text", text: JSON.stringify(runSetup(machine_id, { apply: true, yes }), null, 2) }]
|
|
14841
|
-
}));
|
|
14842
|
-
server.tool("machines_sync_preview", "Preview sync actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14843
|
-
content: [{ type: "text", text: JSON.stringify(buildSyncPlan(machine_id), null, 2) }]
|
|
14844
|
-
}));
|
|
14845
|
-
server.tool("machines_sync_apply", "Execute sync actions for a machine.", {
|
|
14846
|
-
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14847
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14848
|
-
}, async ({ machine_id, yes }) => ({
|
|
14849
|
-
content: [{ type: "text", text: JSON.stringify(runSync(machine_id, { apply: true, yes }), null, 2) }]
|
|
14850
|
-
}));
|
|
15230
|
+
server.tool("machines_setup_preview", "Preview setup actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildSetupPlan(machine_id), null, 2) }] }));
|
|
15231
|
+
server.tool("machines_setup_apply", "Execute setup actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSetup(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
15232
|
+
server.tool("machines_sync_preview", "Preview sync actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildSyncPlan(machine_id), null, 2) }] }));
|
|
15233
|
+
server.tool("machines_sync_apply", "Execute sync actions for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runSync(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
14851
15234
|
server.tool("machines_diff", "Show manifest differences between two machines.", {
|
|
14852
15235
|
left_machine_id: exports_external.string().describe("Left machine identifier"),
|
|
14853
15236
|
right_machine_id: exports_external.string().optional().describe("Right machine identifier")
|
|
14854
15237
|
}, async ({ left_machine_id, right_machine_id }) => ({
|
|
14855
15238
|
content: [{ type: "text", text: JSON.stringify(diffMachines(left_machine_id, right_machine_id), null, 2) }]
|
|
14856
15239
|
}));
|
|
15240
|
+
server.tool("machines_install_claude_status", "Check installed state for Claude, Codex, and Gemini CLIs.", {
|
|
15241
|
+
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
15242
|
+
tools: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
15243
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(getClaudeCliStatus(machine_id, tools), null, 2) }] }));
|
|
15244
|
+
server.tool("machines_install_claude_diff", "Show missing and installed Claude, Codex, and Gemini CLIs.", {
|
|
15245
|
+
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
15246
|
+
tools: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to inspect")
|
|
15247
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(diffClaudeCli(machine_id, tools), null, 2) }] }));
|
|
14857
15248
|
server.tool("machines_install_claude_preview", "Preview Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
14858
15249
|
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14859
15250
|
tools: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to install")
|
|
14860
|
-
}, async ({ machine_id, tools }) => ({
|
|
14861
|
-
content: [{ type: "text", text: JSON.stringify(buildClaudeInstallPlan(machine_id, tools), null, 2) }]
|
|
14862
|
-
}));
|
|
15251
|
+
}, async ({ machine_id, tools }) => ({ content: [{ type: "text", text: JSON.stringify(buildClaudeInstallPlan(machine_id, tools), null, 2) }] }));
|
|
14863
15252
|
server.tool("machines_install_claude_apply", "Execute Claude, Codex, and Gemini CLI install steps for a machine.", {
|
|
14864
15253
|
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14865
15254
|
tools: exports_external.array(exports_external.enum(["claude", "codex", "gemini"])).optional().describe("AI CLIs to install"),
|
|
@@ -14867,73 +15256,21 @@ function createMcpServer(version) {
|
|
|
14867
15256
|
}, async ({ machine_id, tools, yes }) => ({
|
|
14868
15257
|
content: [{ type: "text", text: JSON.stringify(runClaudeInstall(machine_id, tools, { apply: true, yes }), null, 2) }]
|
|
14869
15258
|
}));
|
|
14870
|
-
server.tool("machines_install_tailscale_preview", "Preview Tailscale install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14871
|
-
|
|
14872
|
-
})
|
|
14873
|
-
|
|
14874
|
-
machine_id: exports_external.string().optional().describe("Machine identifier"),
|
|
14875
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14876
|
-
}, async ({ machine_id, yes }) => ({
|
|
14877
|
-
content: [{ type: "text", text: JSON.stringify(runTailscaleInstall(machine_id, { apply: true, yes }), null, 2) }]
|
|
14878
|
-
}));
|
|
14879
|
-
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", {
|
|
14880
|
-
machine_id: exports_external.string().describe("Machine identifier"),
|
|
14881
|
-
remote_command: exports_external.string().optional().describe("Optional remote command")
|
|
14882
|
-
}, async ({ machine_id, remote_command }) => ({
|
|
14883
|
-
content: [
|
|
14884
|
-
{
|
|
14885
|
-
type: "text",
|
|
14886
|
-
text: JSON.stringify({
|
|
14887
|
-
resolved: resolveSshTarget(machine_id),
|
|
14888
|
-
command: buildSshCommand(machine_id, remote_command)
|
|
14889
|
-
}, null, 2)
|
|
14890
|
-
}
|
|
14891
|
-
]
|
|
15259
|
+
server.tool("machines_install_tailscale_preview", "Preview Tailscale install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({ content: [{ type: "text", text: JSON.stringify(buildTailscaleInstallPlan(machine_id), null, 2) }] }));
|
|
15260
|
+
server.tool("machines_install_tailscale_apply", "Execute Tailscale install steps for a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ machine_id, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runTailscaleInstall(machine_id, { apply: true, yes }), null, 2) }] }));
|
|
15261
|
+
server.tool("machines_ssh_resolve", "Resolve the best SSH route for a machine.", { machine_id: exports_external.string().describe("Machine identifier"), remote_command: exports_external.string().optional().describe("Optional remote command") }, async ({ machine_id, remote_command }) => ({
|
|
15262
|
+
content: [{ type: "text", text: JSON.stringify({ resolved: resolveSshTarget(machine_id), command: buildSshCommand(machine_id, remote_command) }, null, 2) }]
|
|
14892
15263
|
}));
|
|
14893
|
-
server.tool("machines_ports", "List listening ports on a machine.", {
|
|
14894
|
-
machine_id: exports_external.string().optional().describe("Machine identifier")
|
|
14895
|
-
}, async ({ machine_id }) => ({
|
|
15264
|
+
server.tool("machines_ports", "List listening ports on a machine.", { machine_id: exports_external.string().optional().describe("Machine identifier") }, async ({ machine_id }) => ({
|
|
14896
15265
|
content: [{ type: "text", text: JSON.stringify(listPorts(machine_id), null, 2) }]
|
|
14897
15266
|
}));
|
|
14898
|
-
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", {
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
}, async ({
|
|
14902
|
-
|
|
14903
|
-
}));
|
|
14904
|
-
server.tool("
|
|
14905
|
-
bucket: exports_external.string().describe("S3 bucket name"),
|
|
14906
|
-
prefix: exports_external.string().optional().describe("S3 key prefix"),
|
|
14907
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14908
|
-
}, async ({ bucket, prefix, yes }) => ({
|
|
14909
|
-
content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }]
|
|
14910
|
-
}));
|
|
14911
|
-
server.tool("machines_cert_preview", "Preview mkcert steps for one or more domains.", {
|
|
14912
|
-
domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for")
|
|
14913
|
-
}, async ({ domains }) => ({
|
|
14914
|
-
content: [{ type: "text", text: JSON.stringify(buildCertPlan(domains), null, 2) }]
|
|
14915
|
-
}));
|
|
14916
|
-
server.tool("machines_cert_apply", "Execute mkcert steps for one or more domains.", {
|
|
14917
|
-
domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for"),
|
|
14918
|
-
yes: exports_external.boolean().describe("Confirmation flag for execution")
|
|
14919
|
-
}, async ({ domains, yes }) => ({
|
|
14920
|
-
content: [{ type: "text", text: JSON.stringify(runCertPlan(domains, { apply: true, yes }), null, 2) }]
|
|
14921
|
-
}));
|
|
14922
|
-
server.tool("machines_dns_add", "Add or replace a local domain mapping.", {
|
|
14923
|
-
domain: exports_external.string().describe("Domain name"),
|
|
14924
|
-
port: exports_external.number().describe("Target port"),
|
|
14925
|
-
target_host: exports_external.string().optional().describe("Target host")
|
|
14926
|
-
}, async ({ domain, port, target_host }) => ({
|
|
14927
|
-
content: [{ type: "text", text: JSON.stringify(addDomainMapping(domain, port, target_host), null, 2) }]
|
|
14928
|
-
}));
|
|
14929
|
-
server.tool("machines_dns_list", "List local domain mappings.", {}, async () => ({
|
|
14930
|
-
content: [{ type: "text", text: JSON.stringify(listDomainMappings(), null, 2) }]
|
|
14931
|
-
}));
|
|
14932
|
-
server.tool("machines_dns_render", "Render hosts/proxy configuration for a domain.", {
|
|
14933
|
-
domain: exports_external.string().describe("Domain name")
|
|
14934
|
-
}, async ({ domain }) => ({
|
|
14935
|
-
content: [{ type: "text", text: JSON.stringify(renderDomainMapping(domain), null, 2) }]
|
|
14936
|
-
}));
|
|
15267
|
+
server.tool("machines_backup_preview", "Preview backup steps for the current machine.", { bucket: exports_external.string().describe("S3 bucket name"), prefix: exports_external.string().optional().describe("S3 key prefix") }, async ({ bucket, prefix }) => ({ content: [{ type: "text", text: JSON.stringify(buildBackupPlan(bucket, prefix), null, 2) }] }));
|
|
15268
|
+
server.tool("machines_backup_apply", "Execute backup steps for the current machine.", { bucket: exports_external.string().describe("S3 bucket name"), prefix: exports_external.string().optional().describe("S3 key prefix"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ bucket, prefix, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runBackup(bucket, prefix, { apply: true, yes }), null, 2) }] }));
|
|
15269
|
+
server.tool("machines_cert_preview", "Preview mkcert steps for one or more domains.", { domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for") }, async ({ domains }) => ({ content: [{ type: "text", text: JSON.stringify(buildCertPlan(domains), null, 2) }] }));
|
|
15270
|
+
server.tool("machines_cert_apply", "Execute mkcert steps for one or more domains.", { domains: exports_external.array(exports_external.string()).describe("Domains to issue certificates for"), yes: exports_external.boolean().describe("Confirmation flag for execution") }, async ({ domains, yes }) => ({ content: [{ type: "text", text: JSON.stringify(runCertPlan(domains, { apply: true, yes }), null, 2) }] }));
|
|
15271
|
+
server.tool("machines_dns_add", "Add or replace a local domain mapping.", { domain: exports_external.string().describe("Domain name"), port: exports_external.number().describe("Target port"), target_host: exports_external.string().optional().describe("Target host") }, async ({ domain, port, target_host }) => ({ content: [{ type: "text", text: JSON.stringify(addDomainMapping(domain, port, target_host), null, 2) }] }));
|
|
15272
|
+
server.tool("machines_dns_list", "List local domain mappings.", {}, async () => ({ content: [{ type: "text", text: JSON.stringify(listDomainMappings(), null, 2) }] }));
|
|
15273
|
+
server.tool("machines_dns_render", "Render hosts/proxy configuration for a domain.", { domain: exports_external.string().describe("Domain name") }, async ({ domain }) => ({ content: [{ type: "text", text: JSON.stringify(renderDomainMapping(domain), null, 2) }] }));
|
|
14937
15274
|
server.tool("machines_notifications_add", "Add or replace a notification channel.", {
|
|
14938
15275
|
channel_id: exports_external.string().describe("Channel identifier"),
|
|
14939
15276
|
type: exports_external.enum(["email", "webhook", "command"]).describe("Notification transport"),
|
|
@@ -14941,46 +15278,17 @@ function createMcpServer(version) {
|
|
|
14941
15278
|
events: exports_external.array(exports_external.string()).describe("Events routed to this channel"),
|
|
14942
15279
|
enabled: exports_external.boolean().optional().describe("Whether the channel is enabled")
|
|
14943
15280
|
}, async ({ channel_id, type, target, events, enabled }) => ({
|
|
14944
|
-
content: [
|
|
14945
|
-
{
|
|
14946
|
-
type: "text",
|
|
14947
|
-
text: JSON.stringify(addNotificationChannel({
|
|
14948
|
-
id: channel_id,
|
|
14949
|
-
type,
|
|
14950
|
-
target,
|
|
14951
|
-
events,
|
|
14952
|
-
enabled: enabled ?? true
|
|
14953
|
-
}), null, 2)
|
|
14954
|
-
}
|
|
14955
|
-
]
|
|
15281
|
+
content: [{ type: "text", text: JSON.stringify(addNotificationChannel({ id: channel_id, type, target, events, enabled: enabled ?? true }), null, 2) }]
|
|
14956
15282
|
}));
|
|
14957
15283
|
server.tool("machines_notifications_list", "List notification channels.", {}, async () => ({
|
|
14958
15284
|
content: [{ type: "text", text: JSON.stringify(listNotificationChannels(), null, 2) }]
|
|
14959
15285
|
}));
|
|
14960
|
-
server.tool("machines_notifications_test", "Preview or execute a notification test.", {
|
|
14961
|
-
|
|
14962
|
-
event: exports_external.string().optional().describe("Event name"),
|
|
14963
|
-
message: exports_external.string().optional().describe("Message body"),
|
|
14964
|
-
yes: exports_external.boolean().optional().describe("Execute the test when true")
|
|
14965
|
-
}, async ({ channel_id, event, message, yes }) => ({
|
|
14966
|
-
content: [
|
|
14967
|
-
{
|
|
14968
|
-
type: "text",
|
|
14969
|
-
text: JSON.stringify(testNotificationChannel(channel_id, event, message, { apply: Boolean(yes), yes }), null, 2)
|
|
14970
|
-
}
|
|
14971
|
-
]
|
|
14972
|
-
}));
|
|
14973
|
-
server.tool("machines_notifications_remove", "Remove a notification channel.", {
|
|
14974
|
-
channel_id: exports_external.string().describe("Channel identifier")
|
|
14975
|
-
}, async ({ channel_id }) => ({
|
|
14976
|
-
content: [{ type: "text", text: JSON.stringify(removeNotificationChannel(channel_id), null, 2) }]
|
|
14977
|
-
}));
|
|
14978
|
-
server.tool("machines_serve_info", "Preview the dashboard server bind address and routes.", {
|
|
14979
|
-
host: exports_external.string().optional().describe("Host interface"),
|
|
14980
|
-
port: exports_external.number().optional().describe("Port number")
|
|
14981
|
-
}, async ({ host, port }) => ({
|
|
14982
|
-
content: [{ type: "text", text: JSON.stringify(getServeInfo({ host, port }), null, 2) }]
|
|
15286
|
+
server.tool("machines_notifications_test", "Preview or execute a notification test.", { channel_id: exports_external.string().describe("Channel identifier"), event: exports_external.string().optional().describe("Event name"), message: exports_external.string().optional().describe("Message body"), yes: exports_external.boolean().optional().describe("Execute the test when true") }, async ({ channel_id, event, message, yes }) => ({
|
|
15287
|
+
content: [{ type: "text", text: JSON.stringify(await testNotificationChannel(channel_id, event, message, { apply: Boolean(yes), yes }), null, 2) }]
|
|
14983
15288
|
}));
|
|
15289
|
+
server.tool("machines_notifications_dispatch", "Dispatch an event to matching notification channels.", { event: exports_external.string().describe("Event name"), message: exports_external.string().describe("Message body"), channel_id: exports_external.string().optional().describe("Limit delivery to one channel") }, async ({ event, message, channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(await dispatchNotificationEvent(event, message, { channelId: channel_id }), null, 2) }] }));
|
|
15290
|
+
server.tool("machines_notifications_remove", "Remove a notification channel.", { channel_id: exports_external.string().describe("Channel identifier") }, async ({ channel_id }) => ({ content: [{ type: "text", text: JSON.stringify(removeNotificationChannel(channel_id), null, 2) }] }));
|
|
15291
|
+
server.tool("machines_serve_info", "Preview the dashboard server bind address and routes.", { host: exports_external.string().optional().describe("Host interface"), port: exports_external.number().optional().describe("Port number") }, async ({ host, port }) => ({ content: [{ type: "text", text: JSON.stringify(getServeInfo({ host, port }), null, 2) }] }));
|
|
14984
15292
|
server.tool("machines_serve_dashboard", "Render the current dashboard HTML.", {}, async () => ({
|
|
14985
15293
|
content: [{ type: "text", text: renderDashboardHtml() }]
|
|
14986
15294
|
}));
|
|
@@ -14990,8 +15298,8 @@ function createMcpServer(version) {
|
|
|
14990
15298
|
// src/mcp/index.ts
|
|
14991
15299
|
function getPkgVersion() {
|
|
14992
15300
|
try {
|
|
14993
|
-
const pkgPath =
|
|
14994
|
-
return JSON.parse(
|
|
15301
|
+
const pkgPath = join9(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
15302
|
+
return JSON.parse(readFileSync7(pkgPath, "utf8")).version || "0.0.0";
|
|
14995
15303
|
} catch {
|
|
14996
15304
|
return "0.0.0";
|
|
14997
15305
|
}
|