agendex-cli 1.0.0 → 1.2.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 +3 -0
- package/dist/cli.js +234 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,9 @@ agendex configure # Select which agents/adapters to index
|
|
|
23
23
|
agendex start # Start daemon (backgrounds itself)
|
|
24
24
|
agendex stop # Stop the running daemon
|
|
25
25
|
agendex sync # One-shot scan + sync to cloud
|
|
26
|
+
agendex upload <path> # Upload a single Markdown plan file to the cloud
|
|
27
|
+
agendex upload <path> --agent <name> # Override the uploaded plan's agent label
|
|
28
|
+
agendex upload <path> --open # Open the uploaded plan in the browser after upload
|
|
26
29
|
agendex cleanup # Interactively remove cloud daemons
|
|
27
30
|
agendex cleanup --stale # Auto-remove all stale daemons
|
|
28
31
|
agendex status # Show config state, daemon status, uptime & hostname
|
package/dist/cli.js
CHANGED
|
@@ -1059,8 +1059,8 @@ var init_cleanup = __esm(() => {
|
|
|
1059
1059
|
|
|
1060
1060
|
// src/cli.ts
|
|
1061
1061
|
import { spawn as spawn4 } from "node:child_process";
|
|
1062
|
-
import { existsSync as
|
|
1063
|
-
import { resolve as
|
|
1062
|
+
import { existsSync as existsSync13, statSync as statSync2, writeSync } from "node:fs";
|
|
1063
|
+
import { resolve as resolve10 } from "node:path";
|
|
1064
1064
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
1065
1065
|
|
|
1066
1066
|
// ../shared/src/adapters/catalog.ts
|
|
@@ -1924,6 +1924,19 @@ function parseDate2(value) {
|
|
|
1924
1924
|
return;
|
|
1925
1925
|
return date;
|
|
1926
1926
|
}
|
|
1927
|
+
function liveSessionIdentity(input) {
|
|
1928
|
+
if (input.sourcePlanPath) {
|
|
1929
|
+
return hashPath(`plannotator-live:${resolve2(input.sourcePlanPath)}`);
|
|
1930
|
+
}
|
|
1931
|
+
if (input.session.reviewId) {
|
|
1932
|
+
return hashPath(`plannotator-live-review:${input.session.reviewId}`);
|
|
1933
|
+
}
|
|
1934
|
+
const project = input.session.project ?? input.planResponse.versionInfo?.project ?? input.planResponse.projectRoot;
|
|
1935
|
+
if (project && input.session.label) {
|
|
1936
|
+
return hashPath(`plannotator-live:${project}:${input.session.label}:${input.mode}`);
|
|
1937
|
+
}
|
|
1938
|
+
return hashPath(input.sessionPath);
|
|
1939
|
+
}
|
|
1927
1940
|
function metadataRecord(metadata) {
|
|
1928
1941
|
return {
|
|
1929
1942
|
source: "plannotator",
|
|
@@ -2090,7 +2103,13 @@ async function parseLiveSession(filePath) {
|
|
|
2090
2103
|
};
|
|
2091
2104
|
return [
|
|
2092
2105
|
{
|
|
2093
|
-
id:
|
|
2106
|
+
id: liveSessionIdentity({
|
|
2107
|
+
sessionPath: filePath,
|
|
2108
|
+
session,
|
|
2109
|
+
planResponse,
|
|
2110
|
+
mode: responseMode,
|
|
2111
|
+
sourcePlanPath
|
|
2112
|
+
}),
|
|
2094
2113
|
agent: origin ?? "plannotator",
|
|
2095
2114
|
title: extractTitle5(planResponse.plan, session.label ?? filePath),
|
|
2096
2115
|
content: planResponse.plan,
|
|
@@ -2182,6 +2201,12 @@ var plannotatorAdapter = {
|
|
|
2182
2201
|
return false;
|
|
2183
2202
|
if (!isSafePlannotatorUrl(metadata.url))
|
|
2184
2203
|
return false;
|
|
2204
|
+
if (payload.action === "approve") {
|
|
2205
|
+
return await postJson(apiUrl(metadata.url, "/api/approve"), {
|
|
2206
|
+
...payload.feedback.trim() && { feedback: payload.feedback.trim() },
|
|
2207
|
+
planSave: { enabled: true }
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2185
2210
|
const feedback = formatWritebackFeedback(plan, payload);
|
|
2186
2211
|
if (metadata.mode === "review" || metadata.mode === "annotate") {
|
|
2187
2212
|
return await postJson(apiUrl(metadata.url, "/api/feedback"), {
|
|
@@ -2797,6 +2822,7 @@ function normalizeStoredConfig(raw) {
|
|
|
2797
2822
|
const token = typeof raw.token === "string" && raw.token.trim() ? raw.token : undefined;
|
|
2798
2823
|
const cloudToken = typeof raw.cloudToken === "string" && raw.cloudToken.trim() ? raw.cloudToken : undefined;
|
|
2799
2824
|
const convexUrl = typeof raw.convexUrl === "string" && raw.convexUrl.trim() ? raw.convexUrl : undefined;
|
|
2825
|
+
const siteUrl = typeof raw.siteUrl === "string" && raw.siteUrl.trim() ? raw.siteUrl : undefined;
|
|
2800
2826
|
const deviceId = typeof raw.deviceId === "string" && raw.deviceId.trim() ? raw.deviceId : undefined;
|
|
2801
2827
|
const collectLocalIpAddress = typeof raw.collectLocalIpAddress === "boolean" ? raw.collectLocalIpAddress : undefined;
|
|
2802
2828
|
return {
|
|
@@ -2804,6 +2830,7 @@ function normalizeStoredConfig(raw) {
|
|
|
2804
2830
|
token,
|
|
2805
2831
|
cloudToken,
|
|
2806
2832
|
convexUrl,
|
|
2833
|
+
siteUrl,
|
|
2807
2834
|
deviceId,
|
|
2808
2835
|
collectLocalIpAddress,
|
|
2809
2836
|
enabledAdapters: normalizeAdapterIds(raw.enabledAdapters),
|
|
@@ -2820,6 +2847,7 @@ function saveConfig(config) {
|
|
|
2820
2847
|
token: config.token,
|
|
2821
2848
|
cloudToken: config.cloudToken,
|
|
2822
2849
|
convexUrl: config.convexUrl,
|
|
2850
|
+
siteUrl: config.siteUrl,
|
|
2823
2851
|
deviceId: config.deviceId,
|
|
2824
2852
|
collectLocalIpAddress: config.collectLocalIpAddress,
|
|
2825
2853
|
enabledAdapters: sanitizeEnabledAdapterIds(config.enabledAdapters),
|
|
@@ -2898,6 +2926,7 @@ async function loadOrInitConfig(options = {}) {
|
|
|
2898
2926
|
token: tokenFromEnv ? existing?.token : currentToken,
|
|
2899
2927
|
cloudToken: existing?.cloudToken,
|
|
2900
2928
|
convexUrl: existing?.convexUrl,
|
|
2929
|
+
siteUrl: existing?.siteUrl,
|
|
2901
2930
|
deviceId,
|
|
2902
2931
|
enabledAdapters,
|
|
2903
2932
|
customPlanDirs: existing?.customPlanDirs ?? []
|
|
@@ -3581,7 +3610,13 @@ async function requestChanges(id, payload) {
|
|
|
3581
3610
|
return false;
|
|
3582
3611
|
const ok = await adapter.requestChanges(plan, payload);
|
|
3583
3612
|
if (ok) {
|
|
3584
|
-
const
|
|
3613
|
+
const writebackAt = Date.now();
|
|
3614
|
+
const plannotator = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? {
|
|
3615
|
+
...plan.metadata.plannotator,
|
|
3616
|
+
...payload.action === "approve" ? { status: "approved" } : {},
|
|
3617
|
+
lastWritebackStatus: "sent",
|
|
3618
|
+
lastWritebackAt: writebackAt
|
|
3619
|
+
} : undefined;
|
|
3585
3620
|
if (plannotator)
|
|
3586
3621
|
plan.metadata = { ...plan.metadata, plannotator };
|
|
3587
3622
|
plan.updatedAt = new Date;
|
|
@@ -3839,7 +3874,8 @@ function parseSyncSuccess(body) {
|
|
|
3839
3874
|
return {
|
|
3840
3875
|
ok: true,
|
|
3841
3876
|
skippedLowValue: result.skippedLowValue === true,
|
|
3842
|
-
deleted: result.deleted === true
|
|
3877
|
+
deleted: result.deleted === true,
|
|
3878
|
+
...typeof result.planId === "string" && { planId: result.planId }
|
|
3843
3879
|
};
|
|
3844
3880
|
} catch {
|
|
3845
3881
|
return { ok: true };
|
|
@@ -3874,7 +3910,7 @@ async function syncPlan(plan) {
|
|
|
3874
3910
|
}
|
|
3875
3911
|
}
|
|
3876
3912
|
if (res.status < 200 || res.status >= 300) {
|
|
3877
|
-
return { ok: false, error: `${res.status}: ${res.body}` };
|
|
3913
|
+
return { ok: false, status: res.status, error: `${res.status}: ${res.body}` };
|
|
3878
3914
|
}
|
|
3879
3915
|
return parseSyncSuccess(res.body);
|
|
3880
3916
|
}
|
|
@@ -4160,6 +4196,12 @@ function getDefaultSiteUrl() {
|
|
|
4160
4196
|
return process.env.AGENDEX_SITE_URL;
|
|
4161
4197
|
return isDevMode() ? DEV_SITE_URL : PROD_SITE_URL;
|
|
4162
4198
|
}
|
|
4199
|
+
function getSiteUrl() {
|
|
4200
|
+
const config = loadConfig();
|
|
4201
|
+
if (config?.siteUrl)
|
|
4202
|
+
return config.siteUrl;
|
|
4203
|
+
return getDefaultSiteUrl();
|
|
4204
|
+
}
|
|
4163
4205
|
function launchBrowser(url, label) {
|
|
4164
4206
|
console.log(`[agendex] Opening ${label}...`);
|
|
4165
4207
|
console.log(`[agendex] If it doesn't open, visit: ${url}`);
|
|
@@ -4172,15 +4214,16 @@ function launchBrowser(url, label) {
|
|
|
4172
4214
|
async function login(siteUrlOverride) {
|
|
4173
4215
|
const { port, result } = await startCallbackServer();
|
|
4174
4216
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
4175
|
-
const
|
|
4217
|
+
const existing = loadConfig();
|
|
4218
|
+
const siteUrl = siteUrlOverride ?? existing?.siteUrl ?? getDefaultSiteUrl();
|
|
4176
4219
|
const authUrl = `${siteUrl}/auth/cli?callback=${encodeURIComponent(callbackUrl)}`;
|
|
4177
4220
|
launchBrowser(authUrl, "browser for authentication");
|
|
4178
4221
|
const callback = await result;
|
|
4179
|
-
const existing = loadConfig();
|
|
4180
4222
|
const config = {
|
|
4181
4223
|
configVersion: 3,
|
|
4182
4224
|
cloudToken: callback.token,
|
|
4183
4225
|
convexUrl: callback.convexUrl,
|
|
4226
|
+
siteUrl,
|
|
4184
4227
|
enabledAdapters: existing?.enabledAdapters ?? [],
|
|
4185
4228
|
customPlanDirs: existing?.customPlanDirs ?? [],
|
|
4186
4229
|
...existing?.token ? { token: existing.token } : {},
|
|
@@ -4201,7 +4244,8 @@ function logout() {
|
|
|
4201
4244
|
enabledAdapters: existing.enabledAdapters,
|
|
4202
4245
|
customPlanDirs: existing.customPlanDirs,
|
|
4203
4246
|
...existing.token ? { token: existing.token } : {},
|
|
4204
|
-
...existing.deviceId ? { deviceId: existing.deviceId } : {}
|
|
4247
|
+
...existing.deviceId ? { deviceId: existing.deviceId } : {},
|
|
4248
|
+
...existing.siteUrl ? { siteUrl: existing.siteUrl } : {}
|
|
4205
4249
|
};
|
|
4206
4250
|
saveConfig(config);
|
|
4207
4251
|
console.log("[agendex] Logged out. Cloud token removed.");
|
|
@@ -4345,7 +4389,7 @@ function spawnBrowser(command, args, options = {}) {
|
|
|
4345
4389
|
// src/daemon.ts
|
|
4346
4390
|
import { spawn as spawn2 } from "node:child_process";
|
|
4347
4391
|
import { hostname as osHostname2 } from "node:os";
|
|
4348
|
-
import { resolve as
|
|
4392
|
+
import { resolve as resolve7 } from "node:path";
|
|
4349
4393
|
import { fileURLToPath } from "node:url";
|
|
4350
4394
|
|
|
4351
4395
|
// src/adapters.ts
|
|
@@ -4490,6 +4534,7 @@ function isIpv4Address(address) {
|
|
|
4490
4534
|
}
|
|
4491
4535
|
|
|
4492
4536
|
// src/payload.ts
|
|
4537
|
+
import { basename as basename6, resolve as resolve6 } from "node:path";
|
|
4493
4538
|
var SYNC_METADATA_KEY = "agendexSync";
|
|
4494
4539
|
function isRecord4(value) {
|
|
4495
4540
|
return typeof value === "object" && value !== null;
|
|
@@ -4508,6 +4553,36 @@ function withSyncDeviceMetadata(metadata, deviceId, hostname2, ipAddress) {
|
|
|
4508
4553
|
}
|
|
4509
4554
|
};
|
|
4510
4555
|
}
|
|
4556
|
+
function parseUploadFile(filePath, content, agentOverride) {
|
|
4557
|
+
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
|
|
4558
|
+
const override = agentOverride?.trim();
|
|
4559
|
+
let agent = override || "uploaded";
|
|
4560
|
+
if (!override && fmMatch) {
|
|
4561
|
+
const agentLine = fmMatch[1]?.match(/^agent:\s*(.+)$/m);
|
|
4562
|
+
if (agentLine?.[1])
|
|
4563
|
+
agent = agentLine[1].trim();
|
|
4564
|
+
}
|
|
4565
|
+
const body = fmMatch ? content.slice(fmMatch[0].length) : content;
|
|
4566
|
+
const titleMatch = body.match(/^#\s+(.+)/m);
|
|
4567
|
+
const title = titleMatch?.[1]?.trim() || basename6(filePath).replace(/\.md$/i, "") || "Untitled";
|
|
4568
|
+
return { title, agent, body };
|
|
4569
|
+
}
|
|
4570
|
+
function fileToSyncPayload(filePath, content, options = {}) {
|
|
4571
|
+
const absolutePath = resolve6(filePath);
|
|
4572
|
+
const { title, agent, body } = parseUploadFile(absolutePath, content, options.agentOverride);
|
|
4573
|
+
const now = Date.now();
|
|
4574
|
+
return {
|
|
4575
|
+
localPlanId: hashPath(absolutePath),
|
|
4576
|
+
agent,
|
|
4577
|
+
title,
|
|
4578
|
+
content: body,
|
|
4579
|
+
format: "md",
|
|
4580
|
+
filePath: absolutePath,
|
|
4581
|
+
metadata: withSyncDeviceMetadata({ uploaded: true, userCreated: true }, options.deviceId, options.hostname, options.ipAddress),
|
|
4582
|
+
createdAt: options.createdAt ?? now,
|
|
4583
|
+
updatedAt: options.updatedAt ?? now
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4511
4586
|
function planToSyncPayload(plan, deviceId, hostname2, ipAddress) {
|
|
4512
4587
|
return {
|
|
4513
4588
|
localPlanId: plan.id,
|
|
@@ -4635,7 +4710,7 @@ var RESTART_WINDOW_MS = 60000;
|
|
|
4635
4710
|
var RESTART_DELAY_MS = 5000;
|
|
4636
4711
|
var PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS = 15000;
|
|
4637
4712
|
var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
|
|
4638
|
-
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the
|
|
4713
|
+
var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the write-back payload.";
|
|
4639
4714
|
var PLANNOTATOR_LIVENESS_SWEEP_INTERVAL_MS = 20000;
|
|
4640
4715
|
function isRecord5(value) {
|
|
4641
4716
|
return typeof value === "object" && value !== null;
|
|
@@ -4814,6 +4889,7 @@ async function runWorker() {
|
|
|
4814
4889
|
return;
|
|
4815
4890
|
}
|
|
4816
4891
|
const ok = await requestChanges(job.localPlanId, {
|
|
4892
|
+
action: job.action,
|
|
4817
4893
|
feedback: job.feedback,
|
|
4818
4894
|
revisedContent: job.revisedContent,
|
|
4819
4895
|
annotations: job.annotations,
|
|
@@ -4824,7 +4900,11 @@ async function runWorker() {
|
|
|
4824
4900
|
if (ok) {
|
|
4825
4901
|
const updatedPlan = getById(job.localPlanId);
|
|
4826
4902
|
if (updatedPlan) {
|
|
4827
|
-
|
|
4903
|
+
const updatedPayload = planToSyncPayload(updatedPlan, config.deviceId, hostname2, await getSyncIpAddress());
|
|
4904
|
+
syncQueue.push(updatedPayload);
|
|
4905
|
+
if (isLivePlannotatorPayload(updatedPayload)) {
|
|
4906
|
+
liveSessions.set(updatedPayload.localPlanId, updatedPayload);
|
|
4907
|
+
}
|
|
4828
4908
|
}
|
|
4829
4909
|
pendingWritebackReports.set(job._id, "sent");
|
|
4830
4910
|
persistPendingWritebackReports();
|
|
@@ -4945,17 +5025,21 @@ async function startSupervisor() {
|
|
|
4945
5025
|
};
|
|
4946
5026
|
process.on("SIGTERM", shutdown);
|
|
4947
5027
|
process.on("SIGINT", shutdown);
|
|
4948
|
-
const scriptPath =
|
|
5028
|
+
const scriptPath = resolve7(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
|
|
4949
5029
|
const restartTimes = [];
|
|
4950
5030
|
while (!stopping) {
|
|
4951
|
-
|
|
4952
|
-
|
|
5031
|
+
const workerArgs = [scriptPath, "start", "--worker"];
|
|
5032
|
+
if (isDevMode())
|
|
5033
|
+
workerArgs.push("--dev");
|
|
5034
|
+
workerProc = spawn2(process.execPath, workerArgs, {
|
|
5035
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
5036
|
+
env: { ...process.env, ...isDevMode() ? { AGENDEX_DEV: "1" } : {} }
|
|
4953
5037
|
});
|
|
4954
|
-
const exitCode = await new Promise((
|
|
4955
|
-
workerProc?.once("exit", (code) =>
|
|
5038
|
+
const exitCode = await new Promise((resolve8) => {
|
|
5039
|
+
workerProc?.once("exit", (code) => resolve8(code));
|
|
4956
5040
|
workerProc?.once("error", (error) => {
|
|
4957
5041
|
console.error("[agendex] failed to spawn worker:", error);
|
|
4958
|
-
|
|
5042
|
+
resolve8(1);
|
|
4959
5043
|
});
|
|
4960
5044
|
});
|
|
4961
5045
|
workerProc = null;
|
|
@@ -4981,7 +5065,7 @@ async function startSupervisor() {
|
|
|
4981
5065
|
import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
|
|
4982
5066
|
import { mkdir as mkdir2, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
4983
5067
|
import { homedir as homedir10 } from "node:os";
|
|
4984
|
-
import { dirname as dirname4, join as join14, resolve as
|
|
5068
|
+
import { dirname as dirname4, join as join14, resolve as resolve8 } from "node:path";
|
|
4985
5069
|
var SUPPORTED_AGENTS = ["claude-code", "codex", "pi"];
|
|
4986
5070
|
var MANAGED_MARKER = "agendex-plan-review";
|
|
4987
5071
|
var HOOK_TIMEOUT_SECONDS = 345600;
|
|
@@ -4993,7 +5077,7 @@ function commandFor(cliEntry, agent) {
|
|
|
4993
5077
|
return `${shellQuote(process.execPath)} ${shellQuote(cliEntry)} review-plan --hook --agent ${agent}`;
|
|
4994
5078
|
}
|
|
4995
5079
|
function scopeRoot(scope) {
|
|
4996
|
-
return scope === "repo" ?
|
|
5080
|
+
return scope === "repo" ? resolve8(process.env.PWD || process.cwd()) : homedir10();
|
|
4997
5081
|
}
|
|
4998
5082
|
function hooksJsonPath(agent, scope) {
|
|
4999
5083
|
const root = scopeRoot(scope);
|
|
@@ -5318,7 +5402,7 @@ async function runHooksCommand(args, cliEntry) {
|
|
|
5318
5402
|
for (const agent of agents) {
|
|
5319
5403
|
if (agent === "claude-code")
|
|
5320
5404
|
printClaudePreviewWarning(dryRun);
|
|
5321
|
-
const path = await installAgent(agent, scope,
|
|
5405
|
+
const path = await installAgent(agent, scope, resolve8(cliEntry), dryRun);
|
|
5322
5406
|
console.log(`[agendex] ${dryRun ? "would install" : "installed"} ${agent} hook: ${path}`);
|
|
5323
5407
|
}
|
|
5324
5408
|
return 0;
|
|
@@ -5408,7 +5492,7 @@ async function syncAll(force = false) {
|
|
|
5408
5492
|
// src/upgrade.ts
|
|
5409
5493
|
import { spawn as spawn3, spawnSync } from "node:child_process";
|
|
5410
5494
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
5411
|
-
import { dirname as dirname5, resolve as
|
|
5495
|
+
import { dirname as dirname5, resolve as resolve9, sep as sep4 } from "node:path";
|
|
5412
5496
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5413
5497
|
|
|
5414
5498
|
// src/version.ts
|
|
@@ -5418,7 +5502,7 @@ import { join as join15 } from "node:path";
|
|
|
5418
5502
|
// package.json
|
|
5419
5503
|
var package_default = {
|
|
5420
5504
|
name: "agendex-cli",
|
|
5421
|
-
version: "1.
|
|
5505
|
+
version: "1.2.0",
|
|
5422
5506
|
description: "Agendex CLI for login, sync, and daemon workflows",
|
|
5423
5507
|
homepage: "https://github.com/Tyru5/Agendex#readme",
|
|
5424
5508
|
bugs: {
|
|
@@ -5541,9 +5625,9 @@ var PACKAGE_NAME = "agendex-cli";
|
|
|
5541
5625
|
var moduleDir = dirname5(fileURLToPath2(import.meta.url));
|
|
5542
5626
|
function getPackageRoot() {
|
|
5543
5627
|
try {
|
|
5544
|
-
return realpathSync2(
|
|
5628
|
+
return realpathSync2(resolve9(moduleDir, ".."));
|
|
5545
5629
|
} catch {
|
|
5546
|
-
return
|
|
5630
|
+
return resolve9(moduleDir, "..");
|
|
5547
5631
|
}
|
|
5548
5632
|
}
|
|
5549
5633
|
function detectPackageManager(packageRoot) {
|
|
@@ -5725,9 +5809,120 @@ async function runUpgrade(opts) {
|
|
|
5725
5809
|
});
|
|
5726
5810
|
}
|
|
5727
5811
|
|
|
5812
|
+
// src/upload.ts
|
|
5813
|
+
import { readFile as readFile8, stat as stat8 } from "node:fs/promises";
|
|
5814
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
5815
|
+
import { hostname as osHostname4 } from "node:os";
|
|
5816
|
+
function resolveAgentOverride(args) {
|
|
5817
|
+
const idx = args.indexOf("--agent");
|
|
5818
|
+
if (idx === -1)
|
|
5819
|
+
return;
|
|
5820
|
+
const value = args[idx + 1];
|
|
5821
|
+
if (value === undefined || value.startsWith("--"))
|
|
5822
|
+
return "missing";
|
|
5823
|
+
return value;
|
|
5824
|
+
}
|
|
5825
|
+
function resolvePathArg(args) {
|
|
5826
|
+
for (let i = 0;i < args.length; i++) {
|
|
5827
|
+
const a = args[i];
|
|
5828
|
+
if (a === undefined)
|
|
5829
|
+
continue;
|
|
5830
|
+
if (a === "upload" || a === "--dev" || a === "--open")
|
|
5831
|
+
continue;
|
|
5832
|
+
if (a === "--agent") {
|
|
5833
|
+
i++;
|
|
5834
|
+
continue;
|
|
5835
|
+
}
|
|
5836
|
+
if (a.startsWith("--"))
|
|
5837
|
+
continue;
|
|
5838
|
+
return a;
|
|
5839
|
+
}
|
|
5840
|
+
return;
|
|
5841
|
+
}
|
|
5842
|
+
async function runUpload(args, deps) {
|
|
5843
|
+
const syncPlanFn = deps?.syncPlan ?? syncPlan;
|
|
5844
|
+
const log = deps?.log ?? ((m) => console.log(m));
|
|
5845
|
+
const error = deps?.error ?? ((m) => console.error(m));
|
|
5846
|
+
const openBrowser2 = deps?.openBrowser ?? launchBrowser;
|
|
5847
|
+
const pathArg = resolvePathArg(args);
|
|
5848
|
+
if (!pathArg || !pathArg.trim()) {
|
|
5849
|
+
error("[agendex] usage: agendex upload <path> [--agent <name>] [--open]");
|
|
5850
|
+
return 1;
|
|
5851
|
+
}
|
|
5852
|
+
const absolutePath = resolveCustomPlanDirPath(pathArg);
|
|
5853
|
+
if (!existsSync12(absolutePath)) {
|
|
5854
|
+
error(`[agendex] path does not exist: ${absolutePath}`);
|
|
5855
|
+
return 1;
|
|
5856
|
+
}
|
|
5857
|
+
let stats;
|
|
5858
|
+
try {
|
|
5859
|
+
stats = await stat8(absolutePath);
|
|
5860
|
+
} catch (err) {
|
|
5861
|
+
error(`[agendex] could not read path: ${err instanceof Error ? err.message : String(err)}`);
|
|
5862
|
+
return 1;
|
|
5863
|
+
}
|
|
5864
|
+
if (stats.isDirectory()) {
|
|
5865
|
+
error(`[agendex] path is a directory, expected a single file: ${absolutePath}`);
|
|
5866
|
+
return 1;
|
|
5867
|
+
}
|
|
5868
|
+
if (!/\.md$/i.test(absolutePath)) {
|
|
5869
|
+
error(`[agendex] only Markdown (.md) files are supported: ${absolutePath}`);
|
|
5870
|
+
return 1;
|
|
5871
|
+
}
|
|
5872
|
+
const config = loadConfig();
|
|
5873
|
+
if (!config?.cloudToken || !config?.convexUrl) {
|
|
5874
|
+
error("[agendex] not logged in. Run `agendex login` first.");
|
|
5875
|
+
return 1;
|
|
5876
|
+
}
|
|
5877
|
+
let content;
|
|
5878
|
+
try {
|
|
5879
|
+
content = await readFile8(absolutePath, "utf-8");
|
|
5880
|
+
} catch (err) {
|
|
5881
|
+
error(`[agendex] could not read file: ${err instanceof Error ? err.message : String(err)}`);
|
|
5882
|
+
return 1;
|
|
5883
|
+
}
|
|
5884
|
+
const agentOverride = resolveAgentOverride(args);
|
|
5885
|
+
if (agentOverride === "missing") {
|
|
5886
|
+
error("[agendex] usage: agendex upload <path> [--agent <name>] [--open]");
|
|
5887
|
+
error("[agendex] --agent requires a name");
|
|
5888
|
+
return 1;
|
|
5889
|
+
}
|
|
5890
|
+
const ipAddress = await shouldIncludeLocalIpAddressInSync() ? getLocalIpAddress() : undefined;
|
|
5891
|
+
const payload = fileToSyncPayload(absolutePath, content, {
|
|
5892
|
+
agentOverride,
|
|
5893
|
+
deviceId: config.deviceId ?? loadOrCreateDeviceId(),
|
|
5894
|
+
hostname: osHostname4(),
|
|
5895
|
+
ipAddress,
|
|
5896
|
+
createdAt: stats.birthtime.getTime(),
|
|
5897
|
+
updatedAt: stats.mtime.getTime()
|
|
5898
|
+
});
|
|
5899
|
+
const result = await syncPlanFn(payload);
|
|
5900
|
+
if (!result.ok) {
|
|
5901
|
+
if (result.status === 403) {
|
|
5902
|
+
error(`[agendex] ${result.error ?? "Cloud Pro subscription required"}`);
|
|
5903
|
+
error(`[agendex] View plans and pricing: ${getSiteUrl().replace(/\/$/, "")}/#pricing`);
|
|
5904
|
+
return 1;
|
|
5905
|
+
}
|
|
5906
|
+
error(`[agendex] upload failed: ${result.error ?? "unknown error"}`);
|
|
5907
|
+
return 1;
|
|
5908
|
+
}
|
|
5909
|
+
if (result.skippedLowValue) {
|
|
5910
|
+
log(`[agendex] "${payload.title}" was skipped as a low-value plan and was not stored in the cloud.`);
|
|
5911
|
+
return 0;
|
|
5912
|
+
}
|
|
5913
|
+
const site = getSiteUrl().replace(/\/$/, "");
|
|
5914
|
+
const planUrl = result.planId ? `${site}/dashboard?plan=${encodeURIComponent(result.planId)}` : `${site}/dashboard`;
|
|
5915
|
+
log(`[agendex] uploaded "${payload.title}"`);
|
|
5916
|
+
log(`[agendex] ${planUrl}`);
|
|
5917
|
+
if (args.includes("--open")) {
|
|
5918
|
+
openBrowser2(planUrl, "uploaded plan in your browser");
|
|
5919
|
+
}
|
|
5920
|
+
return 0;
|
|
5921
|
+
}
|
|
5922
|
+
|
|
5728
5923
|
// src/web.ts
|
|
5729
5924
|
async function openAgendexWeb(siteUrlOverride) {
|
|
5730
|
-
const base = siteUrlOverride ??
|
|
5925
|
+
const base = siteUrlOverride ?? getSiteUrl();
|
|
5731
5926
|
const url = base.replace(/\/$/, "");
|
|
5732
5927
|
launchBrowser(url, "Agendex in your browser");
|
|
5733
5928
|
}
|
|
@@ -5762,7 +5957,7 @@ function firstCommandToken(argv) {
|
|
|
5762
5957
|
return;
|
|
5763
5958
|
}
|
|
5764
5959
|
var command = firstCommandToken(args) ?? "start";
|
|
5765
|
-
var cliEntry =
|
|
5960
|
+
var cliEntry = resolve10(process.argv[1] ?? fileURLToPath3(import.meta.url));
|
|
5766
5961
|
async function main() {
|
|
5767
5962
|
const isInternal = args.includes("--daemon") || args.includes("--worker");
|
|
5768
5963
|
if (command === "--version" || command === "-v") {
|
|
@@ -5782,6 +5977,7 @@ async function main() {
|
|
|
5782
5977
|
"add-dir",
|
|
5783
5978
|
"remove-dir",
|
|
5784
5979
|
"list-dirs",
|
|
5980
|
+
"upload",
|
|
5785
5981
|
"upgrade",
|
|
5786
5982
|
"help",
|
|
5787
5983
|
"--help",
|
|
@@ -5882,6 +6078,9 @@ async function main() {
|
|
|
5882
6078
|
await syncAll(force);
|
|
5883
6079
|
return 0;
|
|
5884
6080
|
}
|
|
6081
|
+
case "upload": {
|
|
6082
|
+
return await runUpload(args);
|
|
6083
|
+
}
|
|
5885
6084
|
case "hooks": {
|
|
5886
6085
|
return await runHooksCommand(args, cliEntry);
|
|
5887
6086
|
}
|
|
@@ -5970,7 +6169,7 @@ async function main() {
|
|
|
5970
6169
|
return 1;
|
|
5971
6170
|
}
|
|
5972
6171
|
const resolved = resolveCustomPlanDirPath(dirPath);
|
|
5973
|
-
if (!
|
|
6172
|
+
if (!existsSync13(resolved)) {
|
|
5974
6173
|
writeStderr(`[agendex] path does not exist: ${resolved}`);
|
|
5975
6174
|
return 1;
|
|
5976
6175
|
}
|
|
@@ -5989,7 +6188,7 @@ async function main() {
|
|
|
5989
6188
|
const { request } = await import("node:http");
|
|
5990
6189
|
const body = JSON.stringify({ path: resolved });
|
|
5991
6190
|
try {
|
|
5992
|
-
const res = await new Promise((
|
|
6191
|
+
const res = await new Promise((resolve11, reject) => {
|
|
5993
6192
|
const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
|
|
5994
6193
|
method: "POST",
|
|
5995
6194
|
headers: {
|
|
@@ -6003,7 +6202,7 @@ async function main() {
|
|
|
6003
6202
|
res2.on("data", (chunk) => {
|
|
6004
6203
|
data += chunk;
|
|
6005
6204
|
});
|
|
6006
|
-
res2.on("end", () =>
|
|
6205
|
+
res2.on("end", () => resolve11({ status: res2.statusCode ?? 0, body: data }));
|
|
6007
6206
|
res2.on("error", reject);
|
|
6008
6207
|
});
|
|
6009
6208
|
req.on("error", reject);
|
|
@@ -6163,6 +6362,9 @@ Usage:
|
|
|
6163
6362
|
agendex list-dirs List custom plan directories
|
|
6164
6363
|
agendex sync One-shot scan + sync to cloud (skips unchanged plans)
|
|
6165
6364
|
agendex sync --force Re-sync all plans, ignoring cache
|
|
6365
|
+
agendex upload <path> Upload a single Markdown plan file to the cloud
|
|
6366
|
+
agendex upload <path> --agent <name> Override the uploaded plan's agent label
|
|
6367
|
+
agendex upload <path> --open Open the uploaded plan in the browser after upload
|
|
6166
6368
|
agendex cleanup Interactively remove cloud daemons
|
|
6167
6369
|
agendex cleanup --stale Auto-remove all stale daemons
|
|
6168
6370
|
agendex status Show current config state + daemon status
|
|
@@ -6196,13 +6398,13 @@ function flushStream(stream) {
|
|
|
6196
6398
|
if (stream.destroyed || !stream.writable) {
|
|
6197
6399
|
return Promise.resolve();
|
|
6198
6400
|
}
|
|
6199
|
-
return new Promise((
|
|
6401
|
+
return new Promise((resolve11, reject) => {
|
|
6200
6402
|
stream.write("", (error) => {
|
|
6201
6403
|
if (error) {
|
|
6202
6404
|
reject(error);
|
|
6203
6405
|
return;
|
|
6204
6406
|
}
|
|
6205
|
-
|
|
6407
|
+
resolve11();
|
|
6206
6408
|
});
|
|
6207
6409
|
});
|
|
6208
6410
|
}
|