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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +234 -32
  3. 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 existsSync12, statSync as statSync2, writeSync } from "node:fs";
1063
- import { resolve as resolve9 } from "node:path";
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: hashPath(filePath),
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 plannotator = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? { ...plan.metadata.plannotator, lastWritebackStatus: "sent", lastWritebackAt: Date.now() } : undefined;
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 siteUrl = siteUrlOverride ?? getDefaultSiteUrl();
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 resolve6 } from "node:path";
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 request-changes payload.";
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
- syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId, hostname2, await getSyncIpAddress()));
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 = resolve6(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
5028
+ const scriptPath = resolve7(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
4949
5029
  const restartTimes = [];
4950
5030
  while (!stopping) {
4951
- workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
4952
- stdio: ["ignore", "inherit", "inherit"]
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((resolve7) => {
4955
- workerProc?.once("exit", (code) => resolve7(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
- resolve7(1);
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 resolve7 } from "node:path";
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" ? resolve7(process.env.PWD || process.cwd()) : homedir10();
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, resolve7(cliEntry), dryRun);
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 resolve8, sep as sep4 } from "node:path";
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.0.0",
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(resolve8(moduleDir, ".."));
5628
+ return realpathSync2(resolve9(moduleDir, ".."));
5545
5629
  } catch {
5546
- return resolve8(moduleDir, "..");
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 ?? getDefaultSiteUrl();
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 = resolve9(process.argv[1] ?? fileURLToPath3(import.meta.url));
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 (!existsSync12(resolved)) {
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((resolve10, reject) => {
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", () => resolve10({ status: res2.statusCode ?? 0, body: data }));
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((resolve10, reject) => {
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
- resolve10();
6407
+ resolve11();
6206
6408
  });
6207
6409
  });
6208
6410
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agendex-cli",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Agendex CLI for login, sync, and daemon workflows",
5
5
  "homepage": "https://github.com/Tyru5/Agendex#readme",
6
6
  "repository": {