agendex-cli 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/cli.js +222 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,6 +57,16 @@ AGENDEX_DEV=1 agendex sync
|
|
|
57
57
|
|
|
58
58
|
In dev mode the default OAuth site (when you do not pass `--url` and do not set `AGENDEX_SITE_URL`) points at the local EE app URL used for development.
|
|
59
59
|
|
|
60
|
+
## Sync Provenance
|
|
61
|
+
|
|
62
|
+
`agendex sync` and the daemon include sync provenance in cloud payload metadata so the web app can show where a plan was synced from. This includes the device ID, hostname, and the host machine's local IP address when one is available.
|
|
63
|
+
|
|
64
|
+
You can disable local IP address collection from Account settings in the cloud app. Managed or non-interactive environments can also omit the local IP address from sync payloads by setting:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
AGENDEX_DISABLE_LOCAL_IP=1 agendex sync
|
|
68
|
+
```
|
|
69
|
+
|
|
60
70
|
## Daemon Cleanup
|
|
61
71
|
|
|
62
72
|
`agendex cleanup` manages registered daemon devices in the cloud.
|
package/dist/cli.js
CHANGED
|
@@ -2797,12 +2797,14 @@ function normalizeStoredConfig(raw) {
|
|
|
2797
2797
|
const cloudToken = typeof raw.cloudToken === "string" && raw.cloudToken.trim() ? raw.cloudToken : undefined;
|
|
2798
2798
|
const convexUrl = typeof raw.convexUrl === "string" && raw.convexUrl.trim() ? raw.convexUrl : undefined;
|
|
2799
2799
|
const deviceId = typeof raw.deviceId === "string" && raw.deviceId.trim() ? raw.deviceId : undefined;
|
|
2800
|
+
const collectLocalIpAddress = typeof raw.collectLocalIpAddress === "boolean" ? raw.collectLocalIpAddress : undefined;
|
|
2800
2801
|
return {
|
|
2801
2802
|
configVersion: 3,
|
|
2802
2803
|
token,
|
|
2803
2804
|
cloudToken,
|
|
2804
2805
|
convexUrl,
|
|
2805
2806
|
deviceId,
|
|
2807
|
+
collectLocalIpAddress,
|
|
2806
2808
|
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters),
|
|
2807
2809
|
customPlanDirs: normalizeCustomPlanDirs(raw.customPlanDirs)
|
|
2808
2810
|
};
|
|
@@ -2818,6 +2820,7 @@ function saveConfig(config) {
|
|
|
2818
2820
|
cloudToken: config.cloudToken,
|
|
2819
2821
|
convexUrl: config.convexUrl,
|
|
2820
2822
|
deviceId: config.deviceId,
|
|
2823
|
+
collectLocalIpAddress: config.collectLocalIpAddress,
|
|
2821
2824
|
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters),
|
|
2822
2825
|
customPlanDirs: normalizeCustomPlanDirs(config.customPlanDirs)
|
|
2823
2826
|
};
|
|
@@ -3150,7 +3153,7 @@ function looksLikeExecutionReport(normalized) {
|
|
|
3150
3153
|
const hasPastCompletion = /\b(?:fixed|pushed|committed|completed|done|implemented|updated|changed|patched|merged|deployed|passed|failed|resolved|reverted)\b/i.test(normalized);
|
|
3151
3154
|
const hasReportSection = /^\s*(?:summary|result|results|changes|verification|status)\s*:/im.test(normalized);
|
|
3152
3155
|
const hasReviewReportMarker = /\b(?:review findings?|review issues?|review comments?)\b/i.test(normalized);
|
|
3153
|
-
const hasCommandMarker = /::[a-z0-9_-]+(?:\{|\[|\s*$)/im.test(normalized) || /`[^`]*(?:bun|npm|pnpm|yarn|git|tsc|biome)[^`]*`/i.test(normalized) || /\b(?:git\s+(?:stage|commit|push|status)|bunx?\s+|npm\s+|pnpm\s+|yarn\s+)\b/i.test(normalized);
|
|
3156
|
+
const hasCommandMarker = /::[a-z0-9_-]+(?:\{|\[|\s*$)/im.test(normalized) || /`[^`]*(?:bun|npm|pnpm|yarn|git|tsc|oxfmt|oxlint|biome)[^`]*`/i.test(normalized) || /\b(?:git\s+(?:stage|commit|push|status)|bunx?\s+|npm\s+|pnpm\s+|yarn\s+)\b/i.test(normalized);
|
|
3154
3157
|
return hasPastCompletion && (hasReportSection || hasCommandMarker || hasReviewReportMarker);
|
|
3155
3158
|
}
|
|
3156
3159
|
function lowValueAssessment(reasons, signals) {
|
|
@@ -3882,7 +3885,7 @@ async function refreshStoredToken(currentToken, convexUrl) {
|
|
|
3882
3885
|
}
|
|
3883
3886
|
return refreshed.token;
|
|
3884
3887
|
}
|
|
3885
|
-
async function sendHeartbeat() {
|
|
3888
|
+
async function sendHeartbeat(ipAddress) {
|
|
3886
3889
|
try {
|
|
3887
3890
|
const { token, convexUrl } = getCloudConfig();
|
|
3888
3891
|
const pidInfo = readPidInfo();
|
|
@@ -3891,7 +3894,8 @@ async function sendHeartbeat() {
|
|
|
3891
3894
|
deviceId: cachedDeviceId,
|
|
3892
3895
|
hostname: pidInfo?.hostname ?? osHostname(),
|
|
3893
3896
|
startedAtMs: pidInfo?.startedAtMs,
|
|
3894
|
-
pid: pidInfo?.pid
|
|
3897
|
+
pid: pidInfo?.pid,
|
|
3898
|
+
ipAddress: ipAddress ?? null
|
|
3895
3899
|
});
|
|
3896
3900
|
let activeToken = token;
|
|
3897
3901
|
let res = await requestText(`${convexUrl}/api/cli/heartbeat`, {
|
|
@@ -3946,6 +3950,41 @@ async function refreshToken(currentToken, convexUrl) {
|
|
|
3946
3950
|
return null;
|
|
3947
3951
|
return { token: body.token, expiresAt: body.expiresAt ?? 0 };
|
|
3948
3952
|
}
|
|
3953
|
+
async function fetchCliPreferences() {
|
|
3954
|
+
try {
|
|
3955
|
+
const { token, convexUrl } = getCloudConfig();
|
|
3956
|
+
let activeToken = token;
|
|
3957
|
+
let res = await requestText(`${convexUrl}/api/cli/preferences`, {
|
|
3958
|
+
method: "GET",
|
|
3959
|
+
headers: authHeaders(activeToken)
|
|
3960
|
+
});
|
|
3961
|
+
if (res.status === 401) {
|
|
3962
|
+
const refreshed = await refreshStoredToken(activeToken, convexUrl);
|
|
3963
|
+
if (refreshed) {
|
|
3964
|
+
activeToken = refreshed;
|
|
3965
|
+
res = await requestText(`${convexUrl}/api/cli/preferences`, {
|
|
3966
|
+
method: "GET",
|
|
3967
|
+
headers: authHeaders(activeToken)
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
if (res.status < 200 || res.status >= 300)
|
|
3972
|
+
return null;
|
|
3973
|
+
const body = JSON.parse(res.body);
|
|
3974
|
+
if (typeof body.collectLocalIpAddress !== "boolean")
|
|
3975
|
+
return null;
|
|
3976
|
+
const config = loadConfig();
|
|
3977
|
+
if (config) {
|
|
3978
|
+
saveConfig({
|
|
3979
|
+
...config,
|
|
3980
|
+
collectLocalIpAddress: body.collectLocalIpAddress
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
return { collectLocalIpAddress: body.collectLocalIpAddress };
|
|
3984
|
+
} catch {
|
|
3985
|
+
return null;
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3949
3988
|
var REQUEST_TIMEOUT_MS2 = Number.parseInt(process.env.AGENDEX_HTTP_TIMEOUT_MS ?? "", 10) || 1e4;
|
|
3950
3989
|
function requestText(urlString, options) {
|
|
3951
3990
|
const url = new URL(urlString);
|
|
@@ -4112,7 +4151,7 @@ async function deleteDaemons(deviceIds) {
|
|
|
4112
4151
|
import { spawn } from "node:child_process";
|
|
4113
4152
|
import { createServer } from "node:http";
|
|
4114
4153
|
var PROD_SITE_URL = "https://app.agendex.dev";
|
|
4115
|
-
var DEV_SITE_URL = "http://app.agendex.
|
|
4154
|
+
var DEV_SITE_URL = "http://app.agendex.localhost:5174";
|
|
4116
4155
|
function getDefaultSiteUrl() {
|
|
4117
4156
|
if (process.env.AGENDEX_SITE_URL)
|
|
4118
4157
|
return process.env.AGENDEX_SITE_URL;
|
|
@@ -4311,6 +4350,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
4311
4350
|
|
|
4312
4351
|
// src/daemon.ts
|
|
4313
4352
|
import { spawn as spawn2 } from "node:child_process";
|
|
4353
|
+
import { hostname as osHostname2 } from "node:os";
|
|
4314
4354
|
import { resolve as resolve6 } from "node:path";
|
|
4315
4355
|
import { fileURLToPath } from "node:url";
|
|
4316
4356
|
|
|
@@ -4340,24 +4380,141 @@ function resolveCliAdapterIds(config) {
|
|
|
4340
4380
|
return ids;
|
|
4341
4381
|
}
|
|
4342
4382
|
|
|
4383
|
+
// src/network.ts
|
|
4384
|
+
import { execFileSync } from "node:child_process";
|
|
4385
|
+
import { networkInterfaces } from "node:os";
|
|
4386
|
+
import { platform } from "node:process";
|
|
4387
|
+
var DISABLE_LOCAL_IP_ENV = "AGENDEX_DISABLE_LOCAL_IP";
|
|
4388
|
+
function shouldCollectLocalIpAddress(env = process.env) {
|
|
4389
|
+
const value = env[DISABLE_LOCAL_IP_ENV]?.trim().toLowerCase();
|
|
4390
|
+
return !["1", "true", "yes", "on"].includes(value ?? "");
|
|
4391
|
+
}
|
|
4392
|
+
function getLocalIpAddress(options = {}) {
|
|
4393
|
+
const interfaces = options.interfaces ?? networkInterfaces();
|
|
4394
|
+
const route = options.defaultRoute === undefined ? getDefaultIpv4Route() : options.defaultRoute;
|
|
4395
|
+
const routedAddress = route ? getAddressForRoute(interfaces, route) : undefined;
|
|
4396
|
+
return routedAddress ?? findFirstAddress(interfaces, "IPv4") ?? findFirstAddress(interfaces, "IPv6");
|
|
4397
|
+
}
|
|
4398
|
+
function getAddressForRoute(interfaces, route) {
|
|
4399
|
+
if (route.sourceAddress && isUsableIpv4Address(route.sourceAddress))
|
|
4400
|
+
return route.sourceAddress;
|
|
4401
|
+
if (route.interfaceName)
|
|
4402
|
+
return findFirstAddress(interfaces, "IPv4", route.interfaceName);
|
|
4403
|
+
return;
|
|
4404
|
+
}
|
|
4405
|
+
function findFirstAddress(interfaces, family, interfaceName) {
|
|
4406
|
+
const entries = interfaceName ? [[interfaceName, interfaces[interfaceName]]] : Object.entries(interfaces).sort(([left], [right]) => left.localeCompare(right));
|
|
4407
|
+
for (const [, addrs] of entries) {
|
|
4408
|
+
if (!addrs)
|
|
4409
|
+
continue;
|
|
4410
|
+
for (const addr of addrs) {
|
|
4411
|
+
if (addr.internal)
|
|
4412
|
+
continue;
|
|
4413
|
+
if (addr.family === family)
|
|
4414
|
+
return addr.address;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
4419
|
+
function getDefaultIpv4Route() {
|
|
4420
|
+
switch (platform) {
|
|
4421
|
+
case "linux":
|
|
4422
|
+
return parseLinuxRoute(readRouteCommand("ip", ["route", "get", "1.1.1.1"]));
|
|
4423
|
+
case "darwin":
|
|
4424
|
+
case "freebsd":
|
|
4425
|
+
case "netbsd":
|
|
4426
|
+
case "openbsd":
|
|
4427
|
+
return parseBsdRoute(readRouteCommand("route", ["-n", "get", "1.1.1.1"]));
|
|
4428
|
+
case "win32":
|
|
4429
|
+
return getWindowsDefaultRouteAddress();
|
|
4430
|
+
default:
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
function readRouteCommand(command, args) {
|
|
4435
|
+
try {
|
|
4436
|
+
return execFileSync(command, args, {
|
|
4437
|
+
encoding: "utf8",
|
|
4438
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4439
|
+
timeout: 1000
|
|
4440
|
+
});
|
|
4441
|
+
} catch {
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
function parseLinuxRoute(output) {
|
|
4446
|
+
if (!output)
|
|
4447
|
+
return;
|
|
4448
|
+
return buildRoute({
|
|
4449
|
+
interfaceName: output.match(/\bdev\s+(\S+)/)?.[1],
|
|
4450
|
+
sourceAddress: output.match(/\bsrc\s+(\d{1,3}(?:\.\d{1,3}){3})\b/)?.[1]
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
function parseBsdRoute(output) {
|
|
4454
|
+
if (!output)
|
|
4455
|
+
return;
|
|
4456
|
+
return buildRoute({
|
|
4457
|
+
interfaceName: output.match(/^\s*interface:\s+(\S+)/m)?.[1]
|
|
4458
|
+
});
|
|
4459
|
+
}
|
|
4460
|
+
function getWindowsDefaultRouteAddress() {
|
|
4461
|
+
const script = [
|
|
4462
|
+
"$route = Get-NetRoute -DestinationPrefix '0.0.0.0/0'",
|
|
4463
|
+
"| Sort-Object -Property @{ Expression = { $_.RouteMetric + $_.InterfaceMetric } }",
|
|
4464
|
+
"| Select-Object -First 1;",
|
|
4465
|
+
"if ($route) {",
|
|
4466
|
+
"Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $route.InterfaceIndex",
|
|
4467
|
+
"| Where-Object { $_.IPAddress -notlike '169.254.*' }",
|
|
4468
|
+
"| Select-Object -First 1 -ExpandProperty IPAddress",
|
|
4469
|
+
"}"
|
|
4470
|
+
].join(" ");
|
|
4471
|
+
const output = readRouteCommand("powershell.exe", [
|
|
4472
|
+
"-NoProfile",
|
|
4473
|
+
"-NonInteractive",
|
|
4474
|
+
"-Command",
|
|
4475
|
+
script
|
|
4476
|
+
]);
|
|
4477
|
+
const sourceAddress = output?.split(/\r?\n/).map((line) => line.trim()).find(isUsableIpv4Address);
|
|
4478
|
+
return buildRoute({ sourceAddress });
|
|
4479
|
+
}
|
|
4480
|
+
function buildRoute(route) {
|
|
4481
|
+
return route.interfaceName || route.sourceAddress ? route : undefined;
|
|
4482
|
+
}
|
|
4483
|
+
function isUsableIpv4Address(address) {
|
|
4484
|
+
if (!isIpv4Address(address))
|
|
4485
|
+
return false;
|
|
4486
|
+
return address !== "0.0.0.0" && !address.startsWith("127.");
|
|
4487
|
+
}
|
|
4488
|
+
function isIpv4Address(address) {
|
|
4489
|
+
const octets = address.split(".");
|
|
4490
|
+
return octets.length === 4 && octets.every((octet) => {
|
|
4491
|
+
if (!/^\d+$/.test(octet))
|
|
4492
|
+
return false;
|
|
4493
|
+
const value = Number(octet);
|
|
4494
|
+
return value >= 0 && value <= 255;
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4343
4498
|
// src/payload.ts
|
|
4344
4499
|
var SYNC_METADATA_KEY = "agendexSync";
|
|
4345
4500
|
function isRecord4(value) {
|
|
4346
4501
|
return typeof value === "object" && value !== null;
|
|
4347
4502
|
}
|
|
4348
|
-
function withSyncDeviceMetadata(metadata, deviceId) {
|
|
4349
|
-
if (!deviceId)
|
|
4503
|
+
function withSyncDeviceMetadata(metadata, deviceId, hostname2, ipAddress) {
|
|
4504
|
+
if (!deviceId && !hostname2 && !ipAddress)
|
|
4350
4505
|
return metadata;
|
|
4351
4506
|
const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
|
|
4352
4507
|
return {
|
|
4353
4508
|
...metadata,
|
|
4354
4509
|
[SYNC_METADATA_KEY]: {
|
|
4355
4510
|
...existing,
|
|
4356
|
-
deviceId
|
|
4511
|
+
...deviceId !== undefined && { deviceId },
|
|
4512
|
+
...hostname2 !== undefined && { hostname: hostname2 },
|
|
4513
|
+
...ipAddress !== undefined && { ipAddress }
|
|
4357
4514
|
}
|
|
4358
4515
|
};
|
|
4359
4516
|
}
|
|
4360
|
-
function planToSyncPayload(plan, deviceId) {
|
|
4517
|
+
function planToSyncPayload(plan, deviceId, hostname2, ipAddress) {
|
|
4361
4518
|
return {
|
|
4362
4519
|
localPlanId: plan.id,
|
|
4363
4520
|
agent: plan.agent,
|
|
@@ -4366,7 +4523,7 @@ function planToSyncPayload(plan, deviceId) {
|
|
|
4366
4523
|
format: plan.format,
|
|
4367
4524
|
filePath: plan.filePath,
|
|
4368
4525
|
workspace: plan.workspace,
|
|
4369
|
-
metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
|
|
4526
|
+
metadata: withSyncDeviceMetadata(plan.metadata, deviceId, hostname2, ipAddress),
|
|
4370
4527
|
createdAt: plan.createdAt.getTime(),
|
|
4371
4528
|
updatedAt: plan.updatedAt.getTime()
|
|
4372
4529
|
};
|
|
@@ -4420,6 +4577,16 @@ function computePayloadHash(payload) {
|
|
|
4420
4577
|
return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
4421
4578
|
}
|
|
4422
4579
|
|
|
4580
|
+
// src/sync-privacy.ts
|
|
4581
|
+
async function shouldIncludeLocalIpAddressInSync() {
|
|
4582
|
+
if (!shouldCollectLocalIpAddress())
|
|
4583
|
+
return false;
|
|
4584
|
+
const prefs = await fetchCliPreferences();
|
|
4585
|
+
if (prefs)
|
|
4586
|
+
return prefs.collectLocalIpAddress;
|
|
4587
|
+
return loadConfig()?.collectLocalIpAddress ?? true;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4423
4590
|
// src/writeback-delivery-cache.ts
|
|
4424
4591
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4425
4592
|
import { join as join13 } from "node:path";
|
|
@@ -4477,15 +4644,25 @@ var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
|
4477
4644
|
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
|
|
4478
4645
|
async function runWorker() {
|
|
4479
4646
|
const config = await loadOrInitConfig();
|
|
4647
|
+
const hostname2 = osHostname2();
|
|
4480
4648
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4481
4649
|
const adapters = resolveAdapters(adapterIds);
|
|
4482
4650
|
setActiveAdapters(adapters);
|
|
4483
|
-
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
4484
|
-
await sendHeartbeat();
|
|
4485
4651
|
const syncCache = loadSyncCache();
|
|
4486
4652
|
const syncQueue = [];
|
|
4487
4653
|
const pendingWritebackReports = loadPendingWritebackReports();
|
|
4488
4654
|
let syncing = false;
|
|
4655
|
+
let cachedIpAddress;
|
|
4656
|
+
async function getSyncIpAddress() {
|
|
4657
|
+
if (!await shouldIncludeLocalIpAddressInSync()) {
|
|
4658
|
+
cachedIpAddress = undefined;
|
|
4659
|
+
return;
|
|
4660
|
+
}
|
|
4661
|
+
cachedIpAddress ??= getLocalIpAddress();
|
|
4662
|
+
return cachedIpAddress;
|
|
4663
|
+
}
|
|
4664
|
+
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
4665
|
+
await sendHeartbeat(await getSyncIpAddress());
|
|
4489
4666
|
async function tryRefreshToken() {
|
|
4490
4667
|
const cfg = loadConfig();
|
|
4491
4668
|
if (!cfg?.cloudToken || !cfg.convexUrl)
|
|
@@ -4600,8 +4777,9 @@ async function runWorker() {
|
|
|
4600
4777
|
});
|
|
4601
4778
|
if (ok) {
|
|
4602
4779
|
const updatedPlan = getById(job.localPlanId);
|
|
4603
|
-
if (updatedPlan)
|
|
4604
|
-
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
|
|
4780
|
+
if (updatedPlan) {
|
|
4781
|
+
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId, hostname2, await getSyncIpAddress()));
|
|
4782
|
+
}
|
|
4605
4783
|
pendingWritebackReports.set(job._id, "sent");
|
|
4606
4784
|
persistPendingWritebackReports();
|
|
4607
4785
|
await reportPendingWriteback(job._id);
|
|
@@ -4641,8 +4819,9 @@ async function runWorker() {
|
|
|
4641
4819
|
let initialSkipped = 0;
|
|
4642
4820
|
let initialQueuedSyncable = 0;
|
|
4643
4821
|
let initialQueuedLowValue = 0;
|
|
4822
|
+
const initialIpAddress = await getSyncIpAddress();
|
|
4644
4823
|
for (const plan of plans) {
|
|
4645
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4824
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, initialIpAddress);
|
|
4646
4825
|
const hash = computePayloadHash(payload);
|
|
4647
4826
|
if (syncCache[plan.id] === hash) {
|
|
4648
4827
|
initialSkipped++;
|
|
@@ -4664,16 +4843,25 @@ async function runWorker() {
|
|
|
4664
4843
|
const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
|
|
4665
4844
|
console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
|
|
4666
4845
|
await processSyncQueue();
|
|
4667
|
-
setInterval(() =>
|
|
4846
|
+
setInterval(() => {
|
|
4847
|
+
(async () => {
|
|
4848
|
+
await sendHeartbeat(await getSyncIpAddress());
|
|
4849
|
+
})().catch(() => {});
|
|
4850
|
+
}, CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
4668
4851
|
if (shouldEnablePlannotatorSync(config)) {
|
|
4669
4852
|
setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
|
|
4670
4853
|
pollPlannotatorWritebacks();
|
|
4671
4854
|
}
|
|
4672
4855
|
startWatching((changedPlans) => {
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4856
|
+
(async () => {
|
|
4857
|
+
const ipAddress = await getSyncIpAddress();
|
|
4858
|
+
for (const plan of changedPlans) {
|
|
4859
|
+
syncQueue.push(planToSyncPayload(plan, config.deviceId, hostname2, ipAddress));
|
|
4860
|
+
}
|
|
4861
|
+
processSyncQueue();
|
|
4862
|
+
})().catch((err) => {
|
|
4863
|
+
console.error("[agendex] failed to queue changed plans:", err);
|
|
4864
|
+
});
|
|
4677
4865
|
});
|
|
4678
4866
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
4679
4867
|
async function gracefulShutdown() {
|
|
@@ -4734,8 +4922,11 @@ async function startSupervisor() {
|
|
|
4734
4922
|
}
|
|
4735
4923
|
|
|
4736
4924
|
// src/sync.ts
|
|
4925
|
+
import { hostname as osHostname3 } from "node:os";
|
|
4737
4926
|
async function syncAll(force = false) {
|
|
4738
4927
|
const config = await loadOrInitConfig();
|
|
4928
|
+
const hostname2 = osHostname3();
|
|
4929
|
+
const ipAddress = await shouldIncludeLocalIpAddressInSync() ? getLocalIpAddress() : undefined;
|
|
4739
4930
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4740
4931
|
const adapters = resolveAdapters(adapterIds);
|
|
4741
4932
|
setActiveAdapters(adapters);
|
|
@@ -4755,7 +4946,7 @@ async function syncAll(force = false) {
|
|
|
4755
4946
|
let failed = 0;
|
|
4756
4947
|
for (const plan of [...syncablePlans, ...lowValuePlans]) {
|
|
4757
4948
|
activePlanIds.add(plan.id);
|
|
4758
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4949
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, ipAddress);
|
|
4759
4950
|
const hash = computePayloadHash(payload);
|
|
4760
4951
|
if (!force && cache[plan.id] === hash) {
|
|
4761
4952
|
skipped++;
|
|
@@ -4798,19 +4989,18 @@ import { join as join14 } from "node:path";
|
|
|
4798
4989
|
// package.json
|
|
4799
4990
|
var package_default = {
|
|
4800
4991
|
name: "agendex-cli",
|
|
4801
|
-
version: "0.
|
|
4992
|
+
version: "0.17.0",
|
|
4802
4993
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
4803
4994
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
4995
|
+
bugs: {
|
|
4996
|
+
url: "https://github.com/Tyru5/Agendex/issues"
|
|
4997
|
+
},
|
|
4998
|
+
license: "AGPL-3.0-only",
|
|
4804
4999
|
repository: {
|
|
4805
5000
|
type: "git",
|
|
4806
5001
|
url: "git+https://github.com/Tyru5/Agendex.git",
|
|
4807
5002
|
directory: "packages/cli"
|
|
4808
5003
|
},
|
|
4809
|
-
bugs: {
|
|
4810
|
-
url: "https://github.com/Tyru5/Agendex/issues"
|
|
4811
|
-
},
|
|
4812
|
-
type: "module",
|
|
4813
|
-
license: "AGPL-3.0-only",
|
|
4814
5004
|
bin: {
|
|
4815
5005
|
agendex: "./dist/cli.js"
|
|
4816
5006
|
},
|
|
@@ -4819,12 +5009,10 @@ var package_default = {
|
|
|
4819
5009
|
"README.md",
|
|
4820
5010
|
"LICENSE"
|
|
4821
5011
|
],
|
|
5012
|
+
type: "module",
|
|
4822
5013
|
publishConfig: {
|
|
4823
5014
|
access: "public"
|
|
4824
5015
|
},
|
|
4825
|
-
engines: {
|
|
4826
|
-
node: ">=20"
|
|
4827
|
-
},
|
|
4828
5016
|
scripts: {
|
|
4829
5017
|
build: "node ./scripts/build-release.mjs --dist-only",
|
|
4830
5018
|
"build:release": "node ./scripts/build-release.mjs",
|
|
@@ -4837,6 +5025,9 @@ var package_default = {
|
|
|
4837
5025
|
devDependencies: {
|
|
4838
5026
|
"@agendex/shared": "workspace:*",
|
|
4839
5027
|
"@types/bun": "^1.3.9"
|
|
5028
|
+
},
|
|
5029
|
+
engines: {
|
|
5030
|
+
node: ">=20"
|
|
4840
5031
|
}
|
|
4841
5032
|
};
|
|
4842
5033
|
|
|
@@ -5485,8 +5676,10 @@ async function main() {
|
|
|
5485
5676
|
const uptimeStr = device.startedAtMs != null ? formatDuration(now - device.startedAtMs) : "~";
|
|
5486
5677
|
const pidStr = device.pid != null ? String(device.pid) : "~";
|
|
5487
5678
|
const hostnameStr = device.hostname ?? "~";
|
|
5679
|
+
const ipStr = device.ipAddress ?? "~";
|
|
5488
5680
|
const isLocal = localDeviceId && device.deviceId === localDeviceId;
|
|
5489
5681
|
writeStdout(`- hostname: ${hostnameStr}${isLocal ? " (this machine)" : ""}
|
|
5682
|
+
ip: ${ipStr}
|
|
5490
5683
|
pid: ${pidStr}
|
|
5491
5684
|
uptime: ${uptimeStr}
|
|
5492
5685
|
status: ${status}`);
|