agendex-cli 0.15.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 +457 -51
- 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
|
@@ -1058,10 +1058,10 @@ var init_cleanup = __esm(() => {
|
|
|
1058
1058
|
});
|
|
1059
1059
|
|
|
1060
1060
|
// src/cli.ts
|
|
1061
|
-
import { spawn as
|
|
1061
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
1062
1062
|
import { existsSync as existsSync11, statSync as statSync2, writeSync } from "node:fs";
|
|
1063
|
-
import { resolve as
|
|
1064
|
-
import { fileURLToPath as
|
|
1063
|
+
import { resolve as resolve8 } from "node:path";
|
|
1064
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1065
1065
|
|
|
1066
1066
|
// ../shared/src/adapters/catalog.ts
|
|
1067
1067
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -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,42 @@ 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
|
+
}
|
|
3988
|
+
var REQUEST_TIMEOUT_MS2 = Number.parseInt(process.env.AGENDEX_HTTP_TIMEOUT_MS ?? "", 10) || 1e4;
|
|
3949
3989
|
function requestText(urlString, options) {
|
|
3950
3990
|
const url = new URL(urlString);
|
|
3951
3991
|
const request = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
@@ -3957,7 +3997,8 @@ function requestText(urlString, options) {
|
|
|
3957
3997
|
const req = request(url, {
|
|
3958
3998
|
agent: false,
|
|
3959
3999
|
headers,
|
|
3960
|
-
method: options.method
|
|
4000
|
+
method: options.method,
|
|
4001
|
+
timeout: REQUEST_TIMEOUT_MS2
|
|
3961
4002
|
}, (res) => {
|
|
3962
4003
|
let body = "";
|
|
3963
4004
|
res.setEncoding("utf8");
|
|
@@ -3973,6 +4014,9 @@ function requestText(urlString, options) {
|
|
|
3973
4014
|
res.on("error", reject);
|
|
3974
4015
|
});
|
|
3975
4016
|
req.on("error", reject);
|
|
4017
|
+
req.on("timeout", () => {
|
|
4018
|
+
req.destroy(new Error(`Request to ${url.host} timed out after ${REQUEST_TIMEOUT_MS2}ms`));
|
|
4019
|
+
});
|
|
3976
4020
|
if (options.body) {
|
|
3977
4021
|
req.write(options.body);
|
|
3978
4022
|
}
|
|
@@ -4107,7 +4151,7 @@ async function deleteDaemons(deviceIds) {
|
|
|
4107
4151
|
import { spawn } from "node:child_process";
|
|
4108
4152
|
import { createServer } from "node:http";
|
|
4109
4153
|
var PROD_SITE_URL = "https://app.agendex.dev";
|
|
4110
|
-
var DEV_SITE_URL = "http://app.agendex.
|
|
4154
|
+
var DEV_SITE_URL = "http://app.agendex.localhost:5174";
|
|
4111
4155
|
function getDefaultSiteUrl() {
|
|
4112
4156
|
if (process.env.AGENDEX_SITE_URL)
|
|
4113
4157
|
return process.env.AGENDEX_SITE_URL;
|
|
@@ -4306,6 +4350,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
4306
4350
|
|
|
4307
4351
|
// src/daemon.ts
|
|
4308
4352
|
import { spawn as spawn2 } from "node:child_process";
|
|
4353
|
+
import { hostname as osHostname2 } from "node:os";
|
|
4309
4354
|
import { resolve as resolve6 } from "node:path";
|
|
4310
4355
|
import { fileURLToPath } from "node:url";
|
|
4311
4356
|
|
|
@@ -4335,24 +4380,141 @@ function resolveCliAdapterIds(config) {
|
|
|
4335
4380
|
return ids;
|
|
4336
4381
|
}
|
|
4337
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
|
+
|
|
4338
4498
|
// src/payload.ts
|
|
4339
4499
|
var SYNC_METADATA_KEY = "agendexSync";
|
|
4340
4500
|
function isRecord4(value) {
|
|
4341
4501
|
return typeof value === "object" && value !== null;
|
|
4342
4502
|
}
|
|
4343
|
-
function withSyncDeviceMetadata(metadata, deviceId) {
|
|
4344
|
-
if (!deviceId)
|
|
4503
|
+
function withSyncDeviceMetadata(metadata, deviceId, hostname2, ipAddress) {
|
|
4504
|
+
if (!deviceId && !hostname2 && !ipAddress)
|
|
4345
4505
|
return metadata;
|
|
4346
4506
|
const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
|
|
4347
4507
|
return {
|
|
4348
4508
|
...metadata,
|
|
4349
4509
|
[SYNC_METADATA_KEY]: {
|
|
4350
4510
|
...existing,
|
|
4351
|
-
deviceId
|
|
4511
|
+
...deviceId !== undefined && { deviceId },
|
|
4512
|
+
...hostname2 !== undefined && { hostname: hostname2 },
|
|
4513
|
+
...ipAddress !== undefined && { ipAddress }
|
|
4352
4514
|
}
|
|
4353
4515
|
};
|
|
4354
4516
|
}
|
|
4355
|
-
function planToSyncPayload(plan, deviceId) {
|
|
4517
|
+
function planToSyncPayload(plan, deviceId, hostname2, ipAddress) {
|
|
4356
4518
|
return {
|
|
4357
4519
|
localPlanId: plan.id,
|
|
4358
4520
|
agent: plan.agent,
|
|
@@ -4361,7 +4523,7 @@ function planToSyncPayload(plan, deviceId) {
|
|
|
4361
4523
|
format: plan.format,
|
|
4362
4524
|
filePath: plan.filePath,
|
|
4363
4525
|
workspace: plan.workspace,
|
|
4364
|
-
metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
|
|
4526
|
+
metadata: withSyncDeviceMetadata(plan.metadata, deviceId, hostname2, ipAddress),
|
|
4365
4527
|
createdAt: plan.createdAt.getTime(),
|
|
4366
4528
|
updatedAt: plan.updatedAt.getTime()
|
|
4367
4529
|
};
|
|
@@ -4415,6 +4577,16 @@ function computePayloadHash(payload) {
|
|
|
4415
4577
|
return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
|
|
4416
4578
|
}
|
|
4417
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
|
+
|
|
4418
4590
|
// src/writeback-delivery-cache.ts
|
|
4419
4591
|
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4420
4592
|
import { join as join13 } from "node:path";
|
|
@@ -4472,15 +4644,25 @@ var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
|
4472
4644
|
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
|
|
4473
4645
|
async function runWorker() {
|
|
4474
4646
|
const config = await loadOrInitConfig();
|
|
4647
|
+
const hostname2 = osHostname2();
|
|
4475
4648
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4476
4649
|
const adapters = resolveAdapters(adapterIds);
|
|
4477
4650
|
setActiveAdapters(adapters);
|
|
4478
|
-
console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
|
|
4479
|
-
await sendHeartbeat();
|
|
4480
4651
|
const syncCache = loadSyncCache();
|
|
4481
4652
|
const syncQueue = [];
|
|
4482
4653
|
const pendingWritebackReports = loadPendingWritebackReports();
|
|
4483
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());
|
|
4484
4666
|
async function tryRefreshToken() {
|
|
4485
4667
|
const cfg = loadConfig();
|
|
4486
4668
|
if (!cfg?.cloudToken || !cfg.convexUrl)
|
|
@@ -4595,8 +4777,9 @@ async function runWorker() {
|
|
|
4595
4777
|
});
|
|
4596
4778
|
if (ok) {
|
|
4597
4779
|
const updatedPlan = getById(job.localPlanId);
|
|
4598
|
-
if (updatedPlan)
|
|
4599
|
-
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
|
|
4780
|
+
if (updatedPlan) {
|
|
4781
|
+
syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId, hostname2, await getSyncIpAddress()));
|
|
4782
|
+
}
|
|
4600
4783
|
pendingWritebackReports.set(job._id, "sent");
|
|
4601
4784
|
persistPendingWritebackReports();
|
|
4602
4785
|
await reportPendingWriteback(job._id);
|
|
@@ -4636,8 +4819,9 @@ async function runWorker() {
|
|
|
4636
4819
|
let initialSkipped = 0;
|
|
4637
4820
|
let initialQueuedSyncable = 0;
|
|
4638
4821
|
let initialQueuedLowValue = 0;
|
|
4822
|
+
const initialIpAddress = await getSyncIpAddress();
|
|
4639
4823
|
for (const plan of plans) {
|
|
4640
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4824
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, initialIpAddress);
|
|
4641
4825
|
const hash = computePayloadHash(payload);
|
|
4642
4826
|
if (syncCache[plan.id] === hash) {
|
|
4643
4827
|
initialSkipped++;
|
|
@@ -4659,16 +4843,25 @@ async function runWorker() {
|
|
|
4659
4843
|
const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
|
|
4660
4844
|
console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
|
|
4661
4845
|
await processSyncQueue();
|
|
4662
|
-
setInterval(() =>
|
|
4846
|
+
setInterval(() => {
|
|
4847
|
+
(async () => {
|
|
4848
|
+
await sendHeartbeat(await getSyncIpAddress());
|
|
4849
|
+
})().catch(() => {});
|
|
4850
|
+
}, CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
|
|
4663
4851
|
if (shouldEnablePlannotatorSync(config)) {
|
|
4664
4852
|
setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
|
|
4665
4853
|
pollPlannotatorWritebacks();
|
|
4666
4854
|
}
|
|
4667
4855
|
startWatching((changedPlans) => {
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
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
|
+
});
|
|
4672
4865
|
});
|
|
4673
4866
|
console.log(`[agendex] daemon running. Watching for file changes...`);
|
|
4674
4867
|
async function gracefulShutdown() {
|
|
@@ -4729,8 +4922,11 @@ async function startSupervisor() {
|
|
|
4729
4922
|
}
|
|
4730
4923
|
|
|
4731
4924
|
// src/sync.ts
|
|
4925
|
+
import { hostname as osHostname3 } from "node:os";
|
|
4732
4926
|
async function syncAll(force = false) {
|
|
4733
4927
|
const config = await loadOrInitConfig();
|
|
4928
|
+
const hostname2 = osHostname3();
|
|
4929
|
+
const ipAddress = await shouldIncludeLocalIpAddressInSync() ? getLocalIpAddress() : undefined;
|
|
4734
4930
|
const adapterIds = resolveCliAdapterIds(config);
|
|
4735
4931
|
const adapters = resolveAdapters(adapterIds);
|
|
4736
4932
|
setActiveAdapters(adapters);
|
|
@@ -4750,7 +4946,7 @@ async function syncAll(force = false) {
|
|
|
4750
4946
|
let failed = 0;
|
|
4751
4947
|
for (const plan of [...syncablePlans, ...lowValuePlans]) {
|
|
4752
4948
|
activePlanIds.add(plan.id);
|
|
4753
|
-
const payload = planToSyncPayload(plan, config.deviceId);
|
|
4949
|
+
const payload = planToSyncPayload(plan, config.deviceId, hostname2, ipAddress);
|
|
4754
4950
|
const hash = computePayloadHash(payload);
|
|
4755
4951
|
if (!force && cache[plan.id] === hash) {
|
|
4756
4952
|
skipped++;
|
|
@@ -4780,6 +4976,12 @@ async function syncAll(force = false) {
|
|
|
4780
4976
|
console.log(`[agendex] Sync complete: ${synced} synced${lowValueSuffix}, ${skipped} unchanged, ${failed} failed`);
|
|
4781
4977
|
}
|
|
4782
4978
|
|
|
4979
|
+
// src/upgrade.ts
|
|
4980
|
+
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
4981
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
4982
|
+
import { dirname as dirname4, resolve as resolve7, sep as sep4 } from "node:path";
|
|
4983
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
4984
|
+
|
|
4783
4985
|
// src/version.ts
|
|
4784
4986
|
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4785
4987
|
import { tmpdir } from "node:os";
|
|
@@ -4787,19 +4989,18 @@ import { join as join14 } from "node:path";
|
|
|
4787
4989
|
// package.json
|
|
4788
4990
|
var package_default = {
|
|
4789
4991
|
name: "agendex-cli",
|
|
4790
|
-
version: "0.
|
|
4992
|
+
version: "0.17.0",
|
|
4791
4993
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
4792
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",
|
|
4793
4999
|
repository: {
|
|
4794
5000
|
type: "git",
|
|
4795
5001
|
url: "git+https://github.com/Tyru5/Agendex.git",
|
|
4796
5002
|
directory: "packages/cli"
|
|
4797
5003
|
},
|
|
4798
|
-
bugs: {
|
|
4799
|
-
url: "https://github.com/Tyru5/Agendex/issues"
|
|
4800
|
-
},
|
|
4801
|
-
type: "module",
|
|
4802
|
-
license: "AGPL-3.0-only",
|
|
4803
5004
|
bin: {
|
|
4804
5005
|
agendex: "./dist/cli.js"
|
|
4805
5006
|
},
|
|
@@ -4808,12 +5009,10 @@ var package_default = {
|
|
|
4808
5009
|
"README.md",
|
|
4809
5010
|
"LICENSE"
|
|
4810
5011
|
],
|
|
5012
|
+
type: "module",
|
|
4811
5013
|
publishConfig: {
|
|
4812
5014
|
access: "public"
|
|
4813
5015
|
},
|
|
4814
|
-
engines: {
|
|
4815
|
-
node: ">=20"
|
|
4816
|
-
},
|
|
4817
5016
|
scripts: {
|
|
4818
5017
|
build: "node ./scripts/build-release.mjs --dist-only",
|
|
4819
5018
|
"build:release": "node ./scripts/build-release.mjs",
|
|
@@ -4826,6 +5025,9 @@ var package_default = {
|
|
|
4826
5025
|
devDependencies: {
|
|
4827
5026
|
"@agendex/shared": "workspace:*",
|
|
4828
5027
|
"@types/bun": "^1.3.9"
|
|
5028
|
+
},
|
|
5029
|
+
engines: {
|
|
5030
|
+
node: ">=20"
|
|
4829
5031
|
}
|
|
4830
5032
|
};
|
|
4831
5033
|
|
|
@@ -4851,11 +5053,13 @@ function writeCache(result) {
|
|
|
4851
5053
|
writeFileSync5(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
|
|
4852
5054
|
} catch {}
|
|
4853
5055
|
}
|
|
4854
|
-
async function checkForUpdate() {
|
|
5056
|
+
async function checkForUpdate(options = {}) {
|
|
4855
5057
|
const current = CLI_VERSION;
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
5058
|
+
if (!options.forceRefresh) {
|
|
5059
|
+
const cached = readCache(current);
|
|
5060
|
+
if (cached)
|
|
5061
|
+
return cached;
|
|
5062
|
+
}
|
|
4859
5063
|
try {
|
|
4860
5064
|
const controller = new AbortController;
|
|
4861
5065
|
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
@@ -4863,21 +5067,27 @@ async function checkForUpdate() {
|
|
|
4863
5067
|
signal: controller.signal
|
|
4864
5068
|
}).finally(() => clearTimeout(timeout));
|
|
4865
5069
|
if (!res.ok) {
|
|
4866
|
-
return { updateAvailable: false, current, latest: current };
|
|
5070
|
+
return { checked: false, updateAvailable: false, current, latest: current };
|
|
4867
5071
|
}
|
|
4868
5072
|
const data = await res.json();
|
|
4869
5073
|
const latest = data.version;
|
|
4870
|
-
const result = {
|
|
5074
|
+
const result = {
|
|
5075
|
+
checked: true,
|
|
5076
|
+
updateAvailable: isNewer(latest, current),
|
|
5077
|
+
current,
|
|
5078
|
+
latest
|
|
5079
|
+
};
|
|
4871
5080
|
writeCache(result);
|
|
4872
5081
|
return result;
|
|
4873
5082
|
} catch {
|
|
4874
|
-
return { updateAvailable: false, current, latest: current };
|
|
5083
|
+
return { checked: false, updateAvailable: false, current, latest: current };
|
|
4875
5084
|
}
|
|
4876
5085
|
}
|
|
4877
5086
|
function normalizeResult(result, current) {
|
|
4878
5087
|
if (typeof result.latest !== "string")
|
|
4879
5088
|
return null;
|
|
4880
5089
|
return {
|
|
5090
|
+
checked: true,
|
|
4881
5091
|
updateAvailable: isNewer(result.latest, current),
|
|
4882
5092
|
current,
|
|
4883
5093
|
latest: result.latest
|
|
@@ -4897,6 +5107,195 @@ function isNewer(latest, current) {
|
|
|
4897
5107
|
return false;
|
|
4898
5108
|
}
|
|
4899
5109
|
|
|
5110
|
+
// src/upgrade.ts
|
|
5111
|
+
var PACKAGE_NAME = "agendex-cli";
|
|
5112
|
+
var moduleDir = dirname4(fileURLToPath2(import.meta.url));
|
|
5113
|
+
function getPackageRoot() {
|
|
5114
|
+
try {
|
|
5115
|
+
return realpathSync2(resolve7(moduleDir, ".."));
|
|
5116
|
+
} catch {
|
|
5117
|
+
return resolve7(moduleDir, "..");
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
function detectPackageManager(packageRoot) {
|
|
5121
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
5122
|
+
const execpath = process.env.npm_execpath ?? "";
|
|
5123
|
+
if (userAgent.startsWith("bun/") || execpath.includes("bun"))
|
|
5124
|
+
return "bun";
|
|
5125
|
+
if (userAgent.startsWith("pnpm/") || execpath.includes("pnpm"))
|
|
5126
|
+
return "pnpm";
|
|
5127
|
+
if (userAgent.startsWith("yarn/") || execpath.includes("yarn"))
|
|
5128
|
+
return "yarn";
|
|
5129
|
+
const lower = packageRoot.toLowerCase();
|
|
5130
|
+
if (lower.includes(`${sep4}.bun${sep4}`) || lower.includes("/.bun/"))
|
|
5131
|
+
return "bun";
|
|
5132
|
+
if (lower.includes(`${sep4}pnpm${sep4}`) || lower.includes("/pnpm/"))
|
|
5133
|
+
return "pnpm";
|
|
5134
|
+
if (lower.includes(`${sep4}yarn${sep4}`) || lower.includes("/yarn/"))
|
|
5135
|
+
return "yarn";
|
|
5136
|
+
if (typeof globalThis.Bun !== "undefined" || process.versions.bun) {
|
|
5137
|
+
return "bun";
|
|
5138
|
+
}
|
|
5139
|
+
return "npm";
|
|
5140
|
+
}
|
|
5141
|
+
function readYarnVersion() {
|
|
5142
|
+
const result = spawnSync("yarn", ["--version"], {
|
|
5143
|
+
encoding: "utf8",
|
|
5144
|
+
shell: process.platform === "win32"
|
|
5145
|
+
});
|
|
5146
|
+
if (result.error || result.status !== 0)
|
|
5147
|
+
return null;
|
|
5148
|
+
return result.stdout.trim() || null;
|
|
5149
|
+
}
|
|
5150
|
+
function parseMajorVersion(version) {
|
|
5151
|
+
const match = version.match(/^(\d+)/);
|
|
5152
|
+
if (!match)
|
|
5153
|
+
return null;
|
|
5154
|
+
const major = Number(match[1]);
|
|
5155
|
+
return Number.isFinite(major) ? major : null;
|
|
5156
|
+
}
|
|
5157
|
+
function buildGlobalInstallCommand(pm) {
|
|
5158
|
+
const pkgSpec = `${PACKAGE_NAME}@latest`;
|
|
5159
|
+
switch (pm) {
|
|
5160
|
+
case "bun":
|
|
5161
|
+
return {
|
|
5162
|
+
supported: true,
|
|
5163
|
+
command: { bin: "bun", args: ["add", "-g", pkgSpec], display: `bun add -g ${pkgSpec}` }
|
|
5164
|
+
};
|
|
5165
|
+
case "pnpm":
|
|
5166
|
+
return {
|
|
5167
|
+
supported: true,
|
|
5168
|
+
command: {
|
|
5169
|
+
bin: "pnpm",
|
|
5170
|
+
args: ["add", "-g", pkgSpec],
|
|
5171
|
+
display: `pnpm add -g ${pkgSpec}`
|
|
5172
|
+
}
|
|
5173
|
+
};
|
|
5174
|
+
case "yarn": {
|
|
5175
|
+
const yarnVersion = readYarnVersion();
|
|
5176
|
+
const yarnMajorVersion = yarnVersion ? parseMajorVersion(yarnVersion) : null;
|
|
5177
|
+
if (yarnMajorVersion !== null && yarnMajorVersion >= 2) {
|
|
5178
|
+
return {
|
|
5179
|
+
supported: false,
|
|
5180
|
+
reason: `automatic upgrade with Yarn only supports Yarn Classic (v1); detected Yarn v${yarnVersion}.`,
|
|
5181
|
+
manualCommand: `npm install -g ${pkgSpec}`
|
|
5182
|
+
};
|
|
5183
|
+
}
|
|
5184
|
+
return {
|
|
5185
|
+
supported: true,
|
|
5186
|
+
command: {
|
|
5187
|
+
bin: "yarn",
|
|
5188
|
+
args: ["global", "add", pkgSpec],
|
|
5189
|
+
display: `yarn global add ${pkgSpec}`
|
|
5190
|
+
}
|
|
5191
|
+
};
|
|
5192
|
+
}
|
|
5193
|
+
default:
|
|
5194
|
+
return {
|
|
5195
|
+
supported: true,
|
|
5196
|
+
command: {
|
|
5197
|
+
bin: "npm",
|
|
5198
|
+
args: ["install", "-g", pkgSpec],
|
|
5199
|
+
display: `npm install -g ${pkgSpec}`
|
|
5200
|
+
}
|
|
5201
|
+
};
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
function isLikelyGlobalInstall(packageRoot) {
|
|
5205
|
+
const normalized = packageRoot.replace(/\\/g, "/");
|
|
5206
|
+
if (!normalized.includes("/node_modules/")) {
|
|
5207
|
+
return false;
|
|
5208
|
+
}
|
|
5209
|
+
const globalMarkers = [
|
|
5210
|
+
"/lib/node_modules/",
|
|
5211
|
+
"/pnpm/global/",
|
|
5212
|
+
"/yarn/global/",
|
|
5213
|
+
"/.bun/install/global/",
|
|
5214
|
+
"/.bun/install/",
|
|
5215
|
+
"/.config/yarn/global/",
|
|
5216
|
+
"/AppData/Roaming/npm/",
|
|
5217
|
+
"/AppData/Local/Yarn/",
|
|
5218
|
+
"/AppData/Local/pnpm/"
|
|
5219
|
+
];
|
|
5220
|
+
return globalMarkers.some((marker) => normalized.includes(marker));
|
|
5221
|
+
}
|
|
5222
|
+
async function runUpgrade(opts) {
|
|
5223
|
+
const packageRoot = getPackageRoot();
|
|
5224
|
+
const pm = detectPackageManager(packageRoot);
|
|
5225
|
+
const isGlobal = isLikelyGlobalInstall(packageRoot);
|
|
5226
|
+
if (!isGlobal) {
|
|
5227
|
+
process.stderr.write(`[agendex] this CLI appears to be running from a local checkout or linked install:
|
|
5228
|
+
` + `[agendex] ${packageRoot}
|
|
5229
|
+
` + `[agendex] automatic upgrade only supports global installs.
|
|
5230
|
+
` + `[agendex] reinstall globally (e.g. \`npm install -g ${PACKAGE_NAME}@latest\`) or update the source repo manually.
|
|
5231
|
+
`);
|
|
5232
|
+
return 1;
|
|
5233
|
+
}
|
|
5234
|
+
const { checked, updateAvailable, current, latest } = await checkForUpdate({
|
|
5235
|
+
forceRefresh: true
|
|
5236
|
+
});
|
|
5237
|
+
if (checked && !updateAvailable && !opts.force) {
|
|
5238
|
+
process.stdout.write(`[agendex] already up to date (v${current})
|
|
5239
|
+
`);
|
|
5240
|
+
return 0;
|
|
5241
|
+
}
|
|
5242
|
+
if (!checked) {
|
|
5243
|
+
process.stderr.write(`[agendex] could not verify the latest version; attempting upgrade anyway...
|
|
5244
|
+
`);
|
|
5245
|
+
}
|
|
5246
|
+
const commandResult = buildGlobalInstallCommand(pm);
|
|
5247
|
+
if (!commandResult.supported) {
|
|
5248
|
+
process.stderr.write(`[agendex] ${commandResult.reason}
|
|
5249
|
+
`);
|
|
5250
|
+
process.stderr.write(`[agendex] install the latest CLI manually, e.g. \`${commandResult.manualCommand}\`.
|
|
5251
|
+
`);
|
|
5252
|
+
return 1;
|
|
5253
|
+
}
|
|
5254
|
+
const cmd = commandResult.command;
|
|
5255
|
+
if (checked && updateAvailable) {
|
|
5256
|
+
process.stdout.write(`[agendex] upgrading: v${current} → v${latest}
|
|
5257
|
+
`);
|
|
5258
|
+
} else if (opts.force) {
|
|
5259
|
+
process.stdout.write(`[agendex] reinstalling v${CLI_VERSION} (forced)
|
|
5260
|
+
`);
|
|
5261
|
+
}
|
|
5262
|
+
process.stdout.write(`[agendex] running: ${cmd.display}
|
|
5263
|
+
`);
|
|
5264
|
+
return await new Promise((resolveExit) => {
|
|
5265
|
+
const child = spawn3(cmd.bin, cmd.args, {
|
|
5266
|
+
stdio: "inherit",
|
|
5267
|
+
shell: process.platform === "win32",
|
|
5268
|
+
env: process.env
|
|
5269
|
+
});
|
|
5270
|
+
let didError = false;
|
|
5271
|
+
child.on("error", (err) => {
|
|
5272
|
+
didError = true;
|
|
5273
|
+
process.stderr.write(`[agendex] failed to run ${cmd.bin}: ${err instanceof Error ? err.message : String(err)}
|
|
5274
|
+
`);
|
|
5275
|
+
process.stderr.write(`[agendex] you can run it manually: ${cmd.display}
|
|
5276
|
+
`);
|
|
5277
|
+
resolveExit(1);
|
|
5278
|
+
});
|
|
5279
|
+
child.on("close", (code) => {
|
|
5280
|
+
if (didError)
|
|
5281
|
+
return;
|
|
5282
|
+
if (code === 0) {
|
|
5283
|
+
process.stdout.write(`[agendex] upgrade complete.
|
|
5284
|
+
`);
|
|
5285
|
+
process.stdout.write(`[agendex] note: if the daemon is running, restart it: \`agendex stop && agendex start\`
|
|
5286
|
+
`);
|
|
5287
|
+
resolveExit(0);
|
|
5288
|
+
return;
|
|
5289
|
+
}
|
|
5290
|
+
process.stderr.write(`[agendex] upgrade failed with exit code ${code ?? "unknown"}
|
|
5291
|
+
`);
|
|
5292
|
+
process.stderr.write(`[agendex] you can run it manually: ${cmd.display}
|
|
5293
|
+
`);
|
|
5294
|
+
resolveExit(code ?? 1);
|
|
5295
|
+
});
|
|
5296
|
+
});
|
|
5297
|
+
}
|
|
5298
|
+
|
|
4900
5299
|
// src/web.ts
|
|
4901
5300
|
async function openAgendexWeb(siteUrlOverride) {
|
|
4902
5301
|
const base = siteUrlOverride ?? getDefaultSiteUrl();
|
|
@@ -4934,7 +5333,7 @@ function firstCommandToken(argv) {
|
|
|
4934
5333
|
return;
|
|
4935
5334
|
}
|
|
4936
5335
|
var command = firstCommandToken(args) ?? "start";
|
|
4937
|
-
var cliEntry =
|
|
5336
|
+
var cliEntry = resolve8(process.argv[1] ?? fileURLToPath3(import.meta.url));
|
|
4938
5337
|
async function main() {
|
|
4939
5338
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
4940
5339
|
if (command === "--version" || command === "-v") {
|
|
@@ -4952,16 +5351,16 @@ async function main() {
|
|
|
4952
5351
|
"add-dir",
|
|
4953
5352
|
"remove-dir",
|
|
4954
5353
|
"list-dirs",
|
|
5354
|
+
"upgrade",
|
|
4955
5355
|
"help",
|
|
4956
5356
|
"--help",
|
|
4957
5357
|
"-h"
|
|
4958
5358
|
].includes(command);
|
|
4959
5359
|
if (!isInternal && !isPassthrough) {
|
|
4960
|
-
const { updateAvailable, current, latest } = await checkForUpdate();
|
|
4961
|
-
if (updateAvailable) {
|
|
4962
|
-
writeStderr(`[agendex] update
|
|
4963
|
-
writeStderr(`[agendex] run:
|
|
4964
|
-
return 1;
|
|
5360
|
+
const { checked, updateAvailable, current, latest } = await checkForUpdate();
|
|
5361
|
+
if (checked && updateAvailable) {
|
|
5362
|
+
writeStderr(`[agendex] update available: v${current} → v${latest}`);
|
|
5363
|
+
writeStderr(`[agendex] run: agendex upgrade`);
|
|
4965
5364
|
}
|
|
4966
5365
|
}
|
|
4967
5366
|
switch (command) {
|
|
@@ -5000,7 +5399,7 @@ async function main() {
|
|
|
5000
5399
|
const daemonArgs = [cliEntry, "start", "--daemon"];
|
|
5001
5400
|
if (devFlag)
|
|
5002
5401
|
daemonArgs.push("--dev");
|
|
5003
|
-
const child =
|
|
5402
|
+
const child = spawn4(process.execPath, daemonArgs, {
|
|
5004
5403
|
detached: true,
|
|
5005
5404
|
stdio: "ignore",
|
|
5006
5405
|
env: { ...process.env, ...devFlag ? { AGENDEX_DEV: "1" } : {} }
|
|
@@ -5153,7 +5552,7 @@ async function main() {
|
|
|
5153
5552
|
const { request } = await import("node:http");
|
|
5154
5553
|
const body = JSON.stringify({ path: resolved });
|
|
5155
5554
|
try {
|
|
5156
|
-
const res = await new Promise((
|
|
5555
|
+
const res = await new Promise((resolve9, reject) => {
|
|
5157
5556
|
const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
|
|
5158
5557
|
method: "POST",
|
|
5159
5558
|
headers: {
|
|
@@ -5167,7 +5566,7 @@ async function main() {
|
|
|
5167
5566
|
res2.on("data", (chunk) => {
|
|
5168
5567
|
data += chunk;
|
|
5169
5568
|
});
|
|
5170
|
-
res2.on("end", () =>
|
|
5569
|
+
res2.on("end", () => resolve9({ status: res2.statusCode ?? 0, body: data }));
|
|
5171
5570
|
res2.on("error", reject);
|
|
5172
5571
|
});
|
|
5173
5572
|
req.on("error", reject);
|
|
@@ -5277,8 +5676,10 @@ async function main() {
|
|
|
5277
5676
|
const uptimeStr = device.startedAtMs != null ? formatDuration(now - device.startedAtMs) : "~";
|
|
5278
5677
|
const pidStr = device.pid != null ? String(device.pid) : "~";
|
|
5279
5678
|
const hostnameStr = device.hostname ?? "~";
|
|
5679
|
+
const ipStr = device.ipAddress ?? "~";
|
|
5280
5680
|
const isLocal = localDeviceId && device.deviceId === localDeviceId;
|
|
5281
5681
|
writeStdout(`- hostname: ${hostnameStr}${isLocal ? " (this machine)" : ""}
|
|
5682
|
+
ip: ${ipStr}
|
|
5282
5683
|
pid: ${pidStr}
|
|
5283
5684
|
uptime: ${uptimeStr}
|
|
5284
5685
|
status: ${status}`);
|
|
@@ -5295,6 +5696,9 @@ async function main() {
|
|
|
5295
5696
|
}
|
|
5296
5697
|
return 0;
|
|
5297
5698
|
}
|
|
5699
|
+
case "upgrade": {
|
|
5700
|
+
return await runUpgrade({ force: args.includes("--force") });
|
|
5701
|
+
}
|
|
5298
5702
|
case "help":
|
|
5299
5703
|
case "--help":
|
|
5300
5704
|
case "-h": {
|
|
@@ -5321,6 +5725,8 @@ Usage:
|
|
|
5321
5725
|
agendex cleanup Interactively remove cloud daemons
|
|
5322
5726
|
agendex cleanup --stale Auto-remove all stale daemons
|
|
5323
5727
|
agendex status Show current config state + daemon status
|
|
5728
|
+
agendex upgrade Upgrade the globally installed CLI to the latest version
|
|
5729
|
+
agendex upgrade --force Reinstall latest even if already up to date
|
|
5324
5730
|
agendex help Show this help message
|
|
5325
5731
|
agendex --version Print CLI version
|
|
5326
5732
|
agendex -v Print CLI version
|
|
@@ -5349,13 +5755,13 @@ function flushStream(stream) {
|
|
|
5349
5755
|
if (stream.destroyed || !stream.writable) {
|
|
5350
5756
|
return Promise.resolve();
|
|
5351
5757
|
}
|
|
5352
|
-
return new Promise((
|
|
5758
|
+
return new Promise((resolve9, reject) => {
|
|
5353
5759
|
stream.write("", (error) => {
|
|
5354
5760
|
if (error) {
|
|
5355
5761
|
reject(error);
|
|
5356
5762
|
return;
|
|
5357
5763
|
}
|
|
5358
|
-
|
|
5764
|
+
resolve9();
|
|
5359
5765
|
});
|
|
5360
5766
|
});
|
|
5361
5767
|
}
|