agendex-cli 0.16.0 → 0.18.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 +243 -59
- 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;
|
|
@@ -4137,12 +4176,12 @@ async function login(siteUrlOverride) {
|
|
|
4137
4176
|
const existing = loadConfig();
|
|
4138
4177
|
const config = {
|
|
4139
4178
|
configVersion: 3,
|
|
4140
|
-
token: existing?.token,
|
|
4141
4179
|
cloudToken: callback.token,
|
|
4142
4180
|
convexUrl: callback.convexUrl,
|
|
4143
|
-
deviceId: existing?.deviceId,
|
|
4144
4181
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
4145
|
-
customPlanDirs: existing?.customPlanDirs ?? []
|
|
4182
|
+
customPlanDirs: existing?.customPlanDirs ?? [],
|
|
4183
|
+
...existing?.token ? { token: existing.token } : {},
|
|
4184
|
+
...existing?.deviceId ? { deviceId: existing.deviceId } : {}
|
|
4146
4185
|
};
|
|
4147
4186
|
saveConfig(config);
|
|
4148
4187
|
console.log(`[agendex] Logged in successfully!`);
|
|
@@ -4156,12 +4195,10 @@ function logout() {
|
|
|
4156
4195
|
}
|
|
4157
4196
|
const config = {
|
|
4158
4197
|
configVersion: 3,
|
|
4159
|
-
token: existing.token,
|
|
4160
|
-
cloudToken: undefined,
|
|
4161
|
-
convexUrl: undefined,
|
|
4162
|
-
deviceId: existing.deviceId,
|
|
4163
4198
|
enabledAdapters: existing.enabledAdapters,
|
|
4164
|
-
customPlanDirs: existing.customPlanDirs
|
|
4199
|
+
customPlanDirs: existing.customPlanDirs,
|
|
4200
|
+
...existing.token ? { token: existing.token } : {},
|
|
4201
|
+
...existing.deviceId ? { deviceId: existing.deviceId } : {}
|
|
4165
4202
|
};
|
|
4166
4203
|
saveConfig(config);
|
|
4167
4204
|
console.log("[agendex] Logged out. Cloud token removed.");
|
|
@@ -4251,38 +4288,31 @@ async function startCallbackServer() {
|
|
|
4251
4288
|
};
|
|
4252
4289
|
}
|
|
4253
4290
|
function callbackPage(success) {
|
|
4254
|
-
const title = success ? "
|
|
4255
|
-
const message = success ? "
|
|
4256
|
-
const icon = success ? '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:32px;height:32px;color:#22c55e"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width:32px;height:32px;color:#ef4444"><path stroke-linecap="round" stroke-linejoin="round" d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>';
|
|
4291
|
+
const title = success ? "Signed in" : "Sign in failed";
|
|
4292
|
+
const message = success ? "Return to your terminal." : "Run agendex login again.";
|
|
4257
4293
|
return `<!DOCTYPE html>
|
|
4258
4294
|
<html lang="en">
|
|
4259
4295
|
<head>
|
|
4260
4296
|
<meta charset="utf-8"/>
|
|
4261
4297
|
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
4262
|
-
<title>${title}
|
|
4298
|
+
<title>${title} | Agendex</title>
|
|
4263
4299
|
<style>
|
|
4264
|
-
*{
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
}
|
|
4271
|
-
|
|
4272
|
-
.card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:40px 48px;text-align:center;max-width:400px;width:100%;box-shadow:0 2px 16px rgba(0,0,0,0.04)}
|
|
4273
|
-
.icon{margin-bottom:16px;display:flex;justify-content:center}
|
|
4274
|
-
h1{font-size:18px;font-weight:600;letter-spacing:-0.02em;margin-bottom:8px}
|
|
4275
|
-
p{font-size:13px;color:var(--secondary);line-height:1.5}
|
|
4276
|
-
.brand{margin-top:24px;font-size:11px;color:var(--tertiary);letter-spacing:0.04em;font-weight:500}
|
|
4300
|
+
*{box-sizing:border-box}
|
|
4301
|
+
:root{color-scheme:dark light;--bg:oklch(13% 0.018 180);--text:oklch(91% 0.012 125);--muted:oklch(58% 0.018 160);--accent:oklch(90% 0.23 125);--err:oklch(64% 0.2 25)}
|
|
4302
|
+
@media(prefers-color-scheme:light){:root{--bg:oklch(97% 0.014 125);--text:oklch(18% 0.016 135);--muted:oklch(48% 0.018 155)}}
|
|
4303
|
+
body{margin:0;min-height:100vh;background:var(--bg);color:var(--text);font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;display:grid;place-items:center;padding:32px;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
|
4304
|
+
main{width:min(100%,340px)}
|
|
4305
|
+
h1{font-size:21px;font-weight:560;line-height:1.25;letter-spacing:-.02em;margin:0}
|
|
4306
|
+
p{font-size:15px;line-height:1.5;color:var(--muted);margin:9px 0 0}
|
|
4307
|
+
.brand{font-family:'SF Mono','JetBrains Mono','Fira Code',ui-monospace,monospace;font-size:12px;line-height:1;color:var(--accent);margin-top:42px;letter-spacing:.02em}
|
|
4277
4308
|
</style>
|
|
4278
4309
|
</head>
|
|
4279
4310
|
<body>
|
|
4280
|
-
<
|
|
4281
|
-
<
|
|
4282
|
-
<h1>${title}</h1>
|
|
4311
|
+
<main aria-labelledby="callback-title">
|
|
4312
|
+
<h1 id="callback-title">${title}</h1>
|
|
4283
4313
|
<p>${message}</p>
|
|
4284
|
-
<div class="brand">
|
|
4285
|
-
</
|
|
4314
|
+
<div class="brand">agendex</div>
|
|
4315
|
+
</main>
|
|
4286
4316
|
</body>
|
|
4287
4317
|
</html>`;
|
|
4288
4318
|
}
|
|
@@ -4311,6 +4341,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
4311
4341
|
|
|
4312
4342
|
// src/daemon.ts
|
|
4313
4343
|
import { spawn as spawn2 } from "node:child_process";
|
|
4344
|
+
import { hostname as osHostname2 } from "node:os";
|
|
4314
4345
|
import { resolve as resolve6 } from "node:path";
|
|
4315
4346
|
import { fileURLToPath } from "node:url";
|
|
4316
4347
|
|
|
@@ -4340,24 +4371,141 @@ function resolveCliAdapterIds(config) {
|
|
|
4340
4371
|
return ids;
|
|
4341
4372
|
}
|
|
4342
4373
|
|
|
4374
|
+
// src/network.ts
|
|
4375
|
+
import { execFileSync } from "node:child_process";
|
|
4376
|
+
import { networkInterfaces } from "node:os";
|
|
4377
|
+
import { platform } from "node:process";
|
|
4378
|
+
var DISABLE_LOCAL_IP_ENV = "AGENDEX_DISABLE_LOCAL_IP";
|
|
4379
|
+
function shouldCollectLocalIpAddress(env = process.env) {
|
|
4380
|
+
const value = env[DISABLE_LOCAL_IP_ENV]?.trim().toLowerCase();
|
|
4381
|
+
return !["1", "true", "yes", "on"].includes(value ?? "");
|
|
4382
|
+
}
|
|
4383
|
+
function getLocalIpAddress(options = {}) {
|
|
4384
|
+
const interfaces = options.interfaces ?? networkInterfaces();
|
|
4385
|
+
const route = options.defaultRoute === undefined ? getDefaultIpv4Route() : options.defaultRoute;
|
|
4386
|
+
const routedAddress = route ? getAddressForRoute(interfaces, route) : undefined;
|
|
4387
|
+
return routedAddress ?? findFirstAddress(interfaces, "IPv4") ?? findFirstAddress(interfaces, "IPv6");
|
|
4388
|
+
}
|
|
4389
|
+
function getAddressForRoute(interfaces, route) {
|
|
4390
|
+
if (route.sourceAddress && isUsableIpv4Address(route.sourceAddress))
|
|
4391
|
+
return route.sourceAddress;
|
|
4392
|
+
if (route.interfaceName)
|
|
4393
|
+
return findFirstAddress(interfaces, "IPv4", route.interfaceName);
|
|
4394
|
+
return;
|
|
4395
|
+
}
|
|
4396
|
+
function findFirstAddress(interfaces, family, interfaceName) {
|
|
4397
|
+
const entries = interfaceName ? [[interfaceName, interfaces[interfaceName]]] : Object.entries(interfaces).sort(([left], [right]) => left.localeCompare(right));
|
|
4398
|
+
for (const [, addrs] of entries) {
|
|
4399
|
+
if (!addrs)
|
|
4400
|
+
continue;
|
|
4401
|
+
for (const addr of addrs) {
|
|
4402
|
+
if (addr.internal)
|
|
4403
|
+
continue;
|
|
4404
|
+
if (addr.family === family)
|
|
4405
|
+
return addr.address;
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
return;
|
|
4409
|
+
}
|
|
4410
|
+
function getDefaultIpv4Route() {
|
|
4411
|
+
switch (platform) {
|
|
4412
|
+
case "linux":
|
|
4413
|
+
return parseLinuxRoute(readRouteCommand("ip", ["route", "get", "1.1.1.1"]));
|
|
4414
|
+
case "darwin":
|
|
4415
|
+
case "freebsd":
|
|
4416
|
+
case "netbsd":
|
|
4417
|
+
case "openbsd":
|
|
4418
|
+
return parseBsdRoute(readRouteCommand("route", ["-n", "get", "1.1.1.1"]));
|
|
4419
|
+
case "win32":
|
|
4420
|
+
return getWindowsDefaultRouteAddress();
|
|
4421
|
+
default:
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4424
|
+
}
|
|
4425
|
+
function readRouteCommand(command, args) {
|
|
4426
|
+
try {
|
|
4427
|
+
return execFileSync(command, args, {
|
|
4428
|
+
encoding: "utf8",
|
|
4429
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
4430
|
+
timeout: 1000
|
|
4431
|
+
});
|
|
4432
|
+
} catch {
|
|
4433
|
+
return;
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
function parseLinuxRoute(output) {
|
|
4437
|
+
if (!output)
|
|
4438
|
+
return;
|
|
4439
|
+
return buildRoute({
|
|
4440
|
+
interfaceName: output.match(/\bdev\s+(\S+)/)?.[1],
|
|
4441
|
+
sourceAddress: output.match(/\bsrc\s+(\d{1,3}(?:\.\d{1,3}){3})\b/)?.[1]
|
|
4442
|
+
});
|
|
4443
|
+
}
|
|
4444
|
+
function parseBsdRoute(output) {
|
|
4445
|
+
if (!output)
|
|
4446
|
+
return;
|
|
4447
|
+
return buildRoute({
|
|
4448
|
+
interfaceName: output.match(/^\s*interface:\s+(\S+)/m)?.[1]
|
|
4449
|
+
});
|
|
4450
|
+
}
|
|
4451
|
+
function getWindowsDefaultRouteAddress() {
|
|
4452
|
+
const script = [
|
|
4453
|
+
"$route = Get-NetRoute -DestinationPrefix '0.0.0.0/0'",
|
|
4454
|
+
"| Sort-Object -Property @{ Expression = { $_.RouteMetric + $_.InterfaceMetric } }",
|
|
4455
|
+
"| Select-Object -First 1;",
|
|
4456
|
+
"if ($route) {",
|
|
4457
|
+
"Get-NetIPAddress -AddressFamily IPv4 -InterfaceIndex $route.InterfaceIndex",
|
|
4458
|
+
"| Where-Object { $_.IPAddress -notlike '169.254.*' }",
|
|
4459
|
+
"| Select-Object -First 1 -ExpandProperty IPAddress",
|
|
4460
|
+
"}"
|
|
4461
|
+
].join(" ");
|
|
4462
|
+
const output = readRouteCommand("powershell.exe", [
|
|
4463
|
+
"-NoProfile",
|
|
4464
|
+
"-NonInteractive",
|
|
4465
|
+
"-Command",
|
|
4466
|
+
script
|
|
4467
|
+
]);
|
|
4468
|
+
const sourceAddress = output?.split(/\r?\n/).map((line) => line.trim()).find(isUsableIpv4Address);
|
|
4469
|
+
return buildRoute({ sourceAddress });
|
|
4470
|
+
}
|
|
4471
|
+
function buildRoute(route) {
|
|
4472
|
+
return route.interfaceName || route.sourceAddress ? route : undefined;
|
|
4473
|
+
}
|
|
4474
|
+
function isUsableIpv4Address(address) {
|
|
4475
|
+
if (!isIpv4Address(address))
|
|
4476
|
+
return false;
|
|
4477
|
+
return address !== "0.0.0.0" && !address.startsWith("127.");
|
|
4478
|
+
}
|
|
4479
|
+
function isIpv4Address(address) {
|
|
4480
|
+
const octets = address.split(".");
|
|
4481
|
+
return octets.length === 4 && octets.every((octet) => {
|
|
4482
|
+
if (!/^\d+$/.test(octet))
|
|
4483
|
+
return false;
|
|
4484
|
+
const value = Number(octet);
|
|
4485
|
+
return value >= 0 && value <= 255;
|
|
4486
|
+
});
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4343
4489
|
// src/payload.ts
|
|
4344
4490
|
var SYNC_METADATA_KEY = "agendexSync";
|
|
4345
4491
|
function isRecord4(value) {
|
|
4346
4492
|
return typeof value === "object" && value !== null;
|
|
4347
4493
|
}
|
|
4348
|
-
function withSyncDeviceMetadata(metadata, deviceId) {
|
|
4349
|
-
if (!deviceId)
|
|
4494
|
+
function withSyncDeviceMetadata(metadata, deviceId, hostname2, ipAddress) {
|
|
4495
|
+
if (!deviceId && !hostname2 && !ipAddress)
|
|
4350
4496
|
return metadata;
|
|
4351
4497
|
const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
|
|
4352
4498
|
return {
|
|
4353
4499
|
...metadata,
|
|
4354
4500
|
[SYNC_METADATA_KEY]: {
|
|
4355
4501
|
...existing,
|
|
4356
|
-
deviceId
|
|
4502
|
+
...deviceId !== undefined && { deviceId },
|
|
4503
|
+
...hostname2 !== undefined && { hostname: hostname2 },
|
|
4504
|
+
...ipAddress !== undefined && { ipAddress }
|
|
4357
4505
|
}
|
|
4358
4506
|
};
|
|
4359
4507
|
}
|
|
4360
|
-
function planToSyncPayload(plan, deviceId) {
|
|
4508
|
+
function planToSyncPayload(plan, deviceId, hostname2, ipAddress) {
|
|
4361
4509
|
return {
|
|
4362
4510
|
localPlanId: plan.id,
|
|
4363
4511
|
agent: plan.agent,
|
|
@@ -4366,7 +4514,7 @@ function planToSyncPayload(plan, deviceId) {
|
|
|
4366
4514
|
format: plan.format,
|
|
4367
4515
|
filePath: plan.filePath,
|
|
4368
4516
|
workspace: plan.workspace,
|
|
4369
|
-
metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
|
|
4517
|
+
metadata: withSyncDeviceMetadata(plan.metadata, deviceId, hostname2, ipAddress),
|
|
4370
4518
|
createdAt: plan.createdAt.getTime(),
|
|
4371
4519
|
updatedAt: plan.updatedAt.getTime()
|
|
4372
4520
|
};
|
|
@@ -4420,6 +4568,16 @@ function computePayloadHash(payload) {
|
|
|
4420
4568
|
return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
4421
4569
|
}
|
|
4422
4570
|
|
|
4571
|
+
// src/sync-privacy.ts
|
|
4572
|
+
async function shouldIncludeLocalIpAddressInSync() {
|
|
4573
|
+
if (!shouldCollectLocalIpAddress())
|
|
4574
|
+
return false;
|
|
4575
|
+
const prefs = await fetchCliPreferences();
|
|
4576
|
+
if (prefs)
|
|
4577
|
+
return prefs.collectLocalIpAddress;
|
|
4578
|
+
return loadConfig()?.collectLocalIpAddress ?? true;
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4423
4581
|
// src/writeback-delivery-cache.ts
|
|
4424
4582
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4425
4583
|
import { join as join13 } from "node:path";
|
|
@@ -4477,15 +4635,25 @@ var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
|
4477
4635
|
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
|
|
4478
4636
|
async function runWorker() {
|
|
4479
4637
|
const config = await loadOrInitConfig();
|
|
4638
|
+
const hostname2 = osHostname2();
|
|
4480
4639
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4481
4640
|
const adapters = resolveAdapters(adapterIds);
|
|
4482
4641
|
setActiveAdapters(adapters);
|
|
4483
|
-
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
4484
|
-
await sendHeartbeat();
|
|
4485
4642
|
const syncCache = loadSyncCache();
|
|
4486
4643
|
const syncQueue = [];
|
|
4487
4644
|
const pendingWritebackReports = loadPendingWritebackReports();
|
|
4488
4645
|
let syncing = false;
|
|
4646
|
+
let cachedIpAddress;
|
|
4647
|
+
async function getSyncIpAddress() {
|
|
4648
|
+
if (!await shouldIncludeLocalIpAddressInSync()) {
|
|
4649
|
+
cachedIpAddress = undefined;
|
|
4650
|
+
return;
|
|
4651
|
+
}
|
|
4652
|
+
cachedIpAddress ??= getLocalIpAddress();
|
|
4653
|
+
return cachedIpAddress;
|
|
4654
|
+
}
|
|
4655
|
+
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
4656
|
+
await sendHeartbeat(await getSyncIpAddress());
|
|
4489
4657
|
async function tryRefreshToken() {
|
|
4490
4658
|
const cfg = loadConfig();
|
|
4491
4659
|
if (!cfg?.cloudToken || !cfg.convexUrl)
|
|
@@ -4600,8 +4768,9 @@ async function runWorker() {
|
|
|
4600
4768
|
});
|
|
4601
4769
|
if (ok) {
|
|
4602
4770
|
const updatedPlan = getById(job.localPlanId);
|
|
4603
|
-
if (updatedPlan)
|
|
4604
|
-
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
|
|
4771
|
+
if (updatedPlan) {
|
|
4772
|
+
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId, hostname2, await getSyncIpAddress()));
|
|
4773
|
+
}
|
|
4605
4774
|
pendingWritebackReports.set(job._id, "sent");
|
|
4606
4775
|
persistPendingWritebackReports();
|
|
4607
4776
|
await reportPendingWriteback(job._id);
|
|
@@ -4641,8 +4810,9 @@ async function runWorker() {
|
|
|
4641
4810
|
let initialSkipped = 0;
|
|
4642
4811
|
let initialQueuedSyncable = 0;
|
|
4643
4812
|
let initialQueuedLowValue = 0;
|
|
4813
|
+
const initialIpAddress = await getSyncIpAddress();
|
|
4644
4814
|
for (const plan of plans) {
|
|
4645
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4815
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, initialIpAddress);
|
|
4646
4816
|
const hash = computePayloadHash(payload);
|
|
4647
4817
|
if (syncCache[plan.id] === hash) {
|
|
4648
4818
|
initialSkipped++;
|
|
@@ -4664,16 +4834,25 @@ async function runWorker() {
|
|
|
4664
4834
|
const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
|
|
4665
4835
|
console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
|
|
4666
4836
|
await processSyncQueue();
|
|
4667
|
-
setInterval(() =>
|
|
4837
|
+
setInterval(() => {
|
|
4838
|
+
(async () => {
|
|
4839
|
+
await sendHeartbeat(await getSyncIpAddress());
|
|
4840
|
+
})().catch(() => {});
|
|
4841
|
+
}, CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
4668
4842
|
if (shouldEnablePlannotatorSync(config)) {
|
|
4669
4843
|
setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
|
|
4670
4844
|
pollPlannotatorWritebacks();
|
|
4671
4845
|
}
|
|
4672
4846
|
startWatching((changedPlans) => {
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4847
|
+
(async () => {
|
|
4848
|
+
const ipAddress = await getSyncIpAddress();
|
|
4849
|
+
for (const plan of changedPlans) {
|
|
4850
|
+
syncQueue.push(planToSyncPayload(plan, config.deviceId, hostname2, ipAddress));
|
|
4851
|
+
}
|
|
4852
|
+
processSyncQueue();
|
|
4853
|
+
})().catch((err) => {
|
|
4854
|
+
console.error("[agendex] failed to queue changed plans:", err);
|
|
4855
|
+
});
|
|
4677
4856
|
});
|
|
4678
4857
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
4679
4858
|
async function gracefulShutdown() {
|
|
@@ -4734,8 +4913,11 @@ async function startSupervisor() {
|
|
|
4734
4913
|
}
|
|
4735
4914
|
|
|
4736
4915
|
// src/sync.ts
|
|
4916
|
+
import { hostname as osHostname3 } from "node:os";
|
|
4737
4917
|
async function syncAll(force = false) {
|
|
4738
4918
|
const config = await loadOrInitConfig();
|
|
4919
|
+
const hostname2 = osHostname3();
|
|
4920
|
+
const ipAddress = await shouldIncludeLocalIpAddressInSync() ? getLocalIpAddress() : undefined;
|
|
4739
4921
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4740
4922
|
const adapters = resolveAdapters(adapterIds);
|
|
4741
4923
|
setActiveAdapters(adapters);
|
|
@@ -4755,7 +4937,7 @@ async function syncAll(force = false) {
|
|
|
4755
4937
|
let failed = 0;
|
|
4756
4938
|
for (const plan of [...syncablePlans, ...lowValuePlans]) {
|
|
4757
4939
|
activePlanIds.add(plan.id);
|
|
4758
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4940
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, ipAddress);
|
|
4759
4941
|
const hash = computePayloadHash(payload);
|
|
4760
4942
|
if (!force && cache[plan.id] === hash) {
|
|
4761
4943
|
skipped++;
|
|
@@ -4798,19 +4980,18 @@ import { join as join14 } from "node:path";
|
|
|
4798
4980
|
// package.json
|
|
4799
4981
|
var package_default = {
|
|
4800
4982
|
name: "agendex-cli",
|
|
4801
|
-
version: "0.
|
|
4983
|
+
version: "0.18.0",
|
|
4802
4984
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
4803
4985
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
4986
|
+
bugs: {
|
|
4987
|
+
url: "https://github.com/Tyru5/Agendex/issues"
|
|
4988
|
+
},
|
|
4989
|
+
license: "AGPL-3.0-only",
|
|
4804
4990
|
repository: {
|
|
4805
4991
|
type: "git",
|
|
4806
4992
|
url: "git+https://github.com/Tyru5/Agendex.git",
|
|
4807
4993
|
directory: "packages/cli"
|
|
4808
4994
|
},
|
|
4809
|
-
bugs: {
|
|
4810
|
-
url: "https://github.com/Tyru5/Agendex/issues"
|
|
4811
|
-
},
|
|
4812
|
-
type: "module",
|
|
4813
|
-
license: "AGPL-3.0-only",
|
|
4814
4995
|
bin: {
|
|
4815
4996
|
agendex: "./dist/cli.js"
|
|
4816
4997
|
},
|
|
@@ -4819,12 +5000,10 @@ var package_default = {
|
|
|
4819
5000
|
"README.md",
|
|
4820
5001
|
"LICENSE"
|
|
4821
5002
|
],
|
|
5003
|
+
type: "module",
|
|
4822
5004
|
publishConfig: {
|
|
4823
5005
|
access: "public"
|
|
4824
5006
|
},
|
|
4825
|
-
engines: {
|
|
4826
|
-
node: ">=20"
|
|
4827
|
-
},
|
|
4828
5007
|
scripts: {
|
|
4829
5008
|
build: "node ./scripts/build-release.mjs --dist-only",
|
|
4830
5009
|
"build:release": "node ./scripts/build-release.mjs",
|
|
@@ -4837,6 +5016,9 @@ var package_default = {
|
|
|
4837
5016
|
devDependencies: {
|
|
4838
5017
|
"@agendex/shared": "workspace:*",
|
|
4839
5018
|
"@types/bun": "^1.3.9"
|
|
5019
|
+
},
|
|
5020
|
+
engines: {
|
|
5021
|
+
node: ">=20"
|
|
4840
5022
|
}
|
|
4841
5023
|
};
|
|
4842
5024
|
|
|
@@ -5485,8 +5667,10 @@ async function main() {
|
|
|
5485
5667
|
const uptimeStr = device.startedAtMs != null ? formatDuration(now - device.startedAtMs) : "~";
|
|
5486
5668
|
const pidStr = device.pid != null ? String(device.pid) : "~";
|
|
5487
5669
|
const hostnameStr = device.hostname ?? "~";
|
|
5670
|
+
const ipStr = device.ipAddress ?? "~";
|
|
5488
5671
|
const isLocal = localDeviceId && device.deviceId === localDeviceId;
|
|
5489
5672
|
writeStdout(`- hostname: ${hostnameStr}${isLocal ? " (this machine)" : ""}
|
|
5673
|
+
ip: ${ipStr}
|
|
5490
5674
|
pid: ${pidStr}
|
|
5491
5675
|
uptime: ${uptimeStr}
|
|
5492
5676
|
status: ${status}`);
|