@plannotator/opencode 0.17.10 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40429,11 +40429,12 @@ class OpenCodeProvider {
40429
40429
  server = null;
40430
40430
  client = null;
40431
40431
  startPromise = null;
40432
+ lastAttachError = null;
40432
40433
  constructor(config) {
40433
40434
  this.config = config;
40434
40435
  }
40435
40436
  async ensureServer() {
40436
- if (this.server && this.client)
40437
+ if (this.client)
40437
40438
  return;
40438
40439
  this.startPromise ??= this.doStart().catch((err) => {
40439
40440
  this.startPromise = null;
@@ -40442,27 +40443,71 @@ class OpenCodeProvider {
40442
40443
  return this.startPromise;
40443
40444
  }
40444
40445
  async doStart() {
40446
+ this.lastAttachError = null;
40445
40447
  const { createOpencodeServer: createOpencodeServer2, createOpencodeClient: createOpencodeClient2 } = await getSDK();
40446
- this.server = await createOpencodeServer2({
40447
- hostname: this.config.hostname ?? "127.0.0.1",
40448
- ...this.config.port != null && { port: this.config.port },
40449
- timeout: 15000
40450
- });
40448
+ const attachedClient = await this.tryAttachExistingServer(createOpencodeClient2);
40449
+ if (attachedClient) {
40450
+ this.client = attachedClient;
40451
+ return;
40452
+ }
40453
+ try {
40454
+ this.server = await createOpencodeServer2({
40455
+ hostname: this.config.hostname ?? "127.0.0.1",
40456
+ ...this.config.port != null && { port: this.config.port },
40457
+ timeout: 15000
40458
+ });
40459
+ } catch (err) {
40460
+ const spawnMessage = err instanceof Error ? err.message : String(err);
40461
+ if (this.lastAttachError) {
40462
+ throw new Error(`${this.lastAttachError}
40463
+ Fallback startup also failed: ${spawnMessage}`);
40464
+ }
40465
+ throw err;
40466
+ }
40451
40467
  this.client = createOpencodeClient2({
40452
40468
  baseUrl: this.server.url,
40453
40469
  directory: this.config.cwd ?? process.cwd()
40454
40470
  });
40455
40471
  }
40472
+ async tryAttachExistingServer(createOpencodeClient2) {
40473
+ const cwd = this.config.cwd ?? process.cwd();
40474
+ const baseUrl = `http://${this.config.hostname ?? "127.0.0.1"}:${this.config.port ?? 4096}`;
40475
+ const client3 = createOpencodeClient2({
40476
+ baseUrl,
40477
+ directory: cwd
40478
+ });
40479
+ try {
40480
+ await client3.config.get({
40481
+ throwOnError: true,
40482
+ signal: AbortSignal.timeout(1000)
40483
+ });
40484
+ return client3;
40485
+ } catch (err) {
40486
+ const message = err instanceof Error ? err.message : String(err);
40487
+ this.lastAttachError = `Failed to attach to existing OpenCode server at ${baseUrl}: ${message}`;
40488
+ return null;
40489
+ }
40490
+ }
40491
+ getClient() {
40492
+ if (!this.client) {
40493
+ throw new Error("OpenCode client is not initialized.");
40494
+ }
40495
+ return this.client;
40496
+ }
40456
40497
  async createSession(options) {
40457
40498
  await this.ensureServer();
40458
- const result = await this.client.session.create({
40499
+ const client3 = this.getClient();
40500
+ const result = await client3.session.create({
40459
40501
  query: { directory: options.cwd ?? this.config.cwd ?? process.cwd() }
40460
40502
  });
40461
40503
  const sessionData = result.data;
40504
+ if (!sessionData) {
40505
+ throw new Error("OpenCode did not return session data.");
40506
+ }
40462
40507
  const session = new OpenCodeSession({
40463
40508
  sessionId: sessionData.id,
40464
40509
  systemPrompt: buildSystemPrompt(options.context),
40465
- client: this.client,
40510
+ client: client3,
40466
40511
  model: options.model,
40467
40512
  parentSessionId: null
40468
40513
  });
@@ -40470,29 +40515,34 @@ class OpenCodeProvider {
40470
40515
  }
40471
40516
  async forkSession(options) {
40472
40517
  await this.ensureServer();
40518
+ const client3 = this.getClient();
40473
40519
  const parentId = options.context.parent?.sessionId;
40474
40520
  if (!parentId) {
40475
40521
  throw new Error("Fork requires a parent session ID.");
40476
40522
  }
40477
- const result = await this.client.session.fork({
40523
+ const result = await client3.session.fork({
40478
40524
  path: { id: parentId }
40479
40525
  });
40480
40526
  const sessionData = result.data;
40527
+ if (!sessionData) {
40528
+ throw new Error("OpenCode did not return forked session data.");
40529
+ }
40481
40530
  return new OpenCodeSession({
40482
40531
  sessionId: sessionData.id,
40483
40532
  systemPrompt: buildSystemPrompt(options.context),
40484
- client: this.client,
40533
+ client: client3,
40485
40534
  model: options.model,
40486
40535
  parentSessionId: parentId
40487
40536
  });
40488
40537
  }
40489
40538
  async resumeSession(sessionId) {
40490
40539
  await this.ensureServer();
40491
- await this.client.session.get({ path: { id: sessionId } });
40540
+ const client3 = this.getClient();
40541
+ await client3.session.get({ path: { id: sessionId } });
40492
40542
  return new OpenCodeSession({
40493
40543
  sessionId,
40494
40544
  systemPrompt: null,
40495
- client: this.client,
40545
+ client: client3,
40496
40546
  model: undefined,
40497
40547
  parentSessionId: null
40498
40548
  });
@@ -40501,26 +40551,30 @@ class OpenCodeProvider {
40501
40551
  if (this.server) {
40502
40552
  this.server.close();
40503
40553
  this.server = null;
40504
- this.client = null;
40505
- this.startPromise = null;
40506
40554
  }
40555
+ this.client = null;
40556
+ this.startPromise = null;
40507
40557
  }
40508
40558
  async fetchModels() {
40509
40559
  try {
40510
40560
  await this.ensureServer();
40511
- const result = await this.client.provider.list({
40561
+ const client3 = this.getClient();
40562
+ const result = await client3.provider.list({
40512
40563
  query: { directory: this.config.cwd ?? process.cwd() }
40513
40564
  });
40514
40565
  const data = result.data;
40515
- const connected = new Set(data.connected);
40516
- const allProviders = data.all;
40566
+ if (!data) {
40567
+ return;
40568
+ }
40569
+ const connected = new Set(data.connected ?? []);
40570
+ const allProviders = data.all ?? [];
40517
40571
  const models = [];
40518
40572
  for (const provider of allProviders) {
40519
40573
  if (!connected.has(provider.id))
40520
40574
  continue;
40521
40575
  for (const model of Object.values(provider.models)) {
40522
40576
  models.push({
40523
- id: `${model.providerID}/${model.id}`,
40577
+ id: `${provider.id}/${model.id}`,
40524
40578
  label: model.name ?? model.id
40525
40579
  });
40526
40580
  }
@@ -40828,8 +40882,8 @@ async function openEditorDiff(oldPath, newPath) {
40828
40882
 
40829
40883
  // ../../packages/server/integrations.ts
40830
40884
  var {$: $2 } = globalThis.Bun;
40831
- import { join as join2 } from "path";
40832
- import { mkdirSync, existsSync as existsSync2, statSync } from "fs";
40885
+ import { join as join3 } from "path";
40886
+ import { mkdirSync, existsSync as existsSync3, statSync } from "fs";
40833
40887
 
40834
40888
  // ../../packages/server/project.ts
40835
40889
  var {$ } = globalThis.Bun;
@@ -41007,6 +41061,202 @@ Last Edited: ${now}
41007
41061
  ---`;
41008
41062
  }
41009
41063
 
41064
+ // ../../packages/shared/resolve-file.ts
41065
+ import { homedir } from "os";
41066
+ import { isAbsolute, join as join2, resolve, win32 } from "path";
41067
+ import { existsSync as existsSync2, readdirSync } from "fs";
41068
+ var MARKDOWN_PATH_REGEX = /\.mdx?$/i;
41069
+ var WINDOWS_DRIVE_PATH_PATTERNS = [
41070
+ /^\/cygdrive\/([a-zA-Z])\/(.+)$/,
41071
+ /^\/([a-zA-Z])\/(.+)$/
41072
+ ];
41073
+ var IGNORED_DIRS = [
41074
+ "node_modules/",
41075
+ ".git/",
41076
+ "dist/",
41077
+ "build/",
41078
+ ".next/",
41079
+ "__pycache__/",
41080
+ ".obsidian/",
41081
+ ".trash/"
41082
+ ];
41083
+ function normalizeSeparators(input) {
41084
+ return input.replace(/\\/g, "/");
41085
+ }
41086
+ function stripTrailingSlashes(input) {
41087
+ return input.replace(/\/+$/, "");
41088
+ }
41089
+ function expandHomePath(input, home = homedir()) {
41090
+ if (input === "~") {
41091
+ return home;
41092
+ }
41093
+ if (input.startsWith("~/") || input.startsWith("~\\")) {
41094
+ return join2(home, input.slice(2));
41095
+ }
41096
+ return input;
41097
+ }
41098
+ function stripWrappingQuotes(input) {
41099
+ if (input.length < 2) {
41100
+ return input;
41101
+ }
41102
+ const first = input[0];
41103
+ const last = input[input.length - 1];
41104
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
41105
+ return input.slice(1, -1);
41106
+ }
41107
+ return input;
41108
+ }
41109
+ function normalizeUserPathInput(input, platform = process.platform) {
41110
+ const trimmedInput = input.trim();
41111
+ const unquotedInput = stripWrappingQuotes(trimmedInput);
41112
+ const expandedInput = expandHomePath(unquotedInput);
41113
+ if (platform !== "win32") {
41114
+ return expandedInput;
41115
+ }
41116
+ for (const pattern of WINDOWS_DRIVE_PATH_PATTERNS) {
41117
+ const match = expandedInput.match(pattern);
41118
+ if (!match) {
41119
+ continue;
41120
+ }
41121
+ const [, driveLetter, rest] = match;
41122
+ return `${driveLetter.toUpperCase()}:/${rest}`;
41123
+ }
41124
+ return expandedInput;
41125
+ }
41126
+ function isAbsoluteNormalizedUserPath(input, platform = process.platform) {
41127
+ if (hasWindowsDriveLetter(input)) {
41128
+ return true;
41129
+ }
41130
+ return platform === "win32" ? win32.isAbsolute(input) : isAbsolute(input);
41131
+ }
41132
+ function isAbsoluteUserPath(input, platform = process.platform) {
41133
+ return isAbsoluteNormalizedUserPath(normalizeUserPathInput(input, platform), platform);
41134
+ }
41135
+ function resolveUserPath(input, baseDir = process.cwd(), platform = process.platform) {
41136
+ const normalizedInput = normalizeUserPathInput(input, platform);
41137
+ if (!normalizedInput) {
41138
+ return "";
41139
+ }
41140
+ return isAbsoluteNormalizedUserPath(normalizedInput, platform) ? resolveAbsolutePath(normalizedInput, platform) : resolve(baseDir, normalizedInput);
41141
+ }
41142
+ function normalizeComparablePath(input) {
41143
+ return stripTrailingSlashes(normalizeSeparators(resolveUserPath(input)));
41144
+ }
41145
+ function isWithinProjectRoot(candidate, projectRoot) {
41146
+ const normalizedCandidate = normalizeComparablePath(candidate);
41147
+ const normalizedProjectRoot = normalizeComparablePath(projectRoot);
41148
+ return normalizedCandidate === normalizedProjectRoot || normalizedCandidate.startsWith(`${normalizedProjectRoot}/`);
41149
+ }
41150
+ function getLowercaseBasename(input) {
41151
+ const normalizedInput = normalizeSeparators(input);
41152
+ return normalizedInput.split("/").pop().toLowerCase();
41153
+ }
41154
+ function getLookupKey(input, isBareFilename) {
41155
+ return isBareFilename ? getLowercaseBasename(input) : input.toLowerCase();
41156
+ }
41157
+ function resolveAbsolutePath(input, platform = process.platform) {
41158
+ return platform === "win32" || hasWindowsDriveLetter(input) ? win32.resolve(input) : resolve(input);
41159
+ }
41160
+ function isSearchableMarkdownPath(input) {
41161
+ return MARKDOWN_PATH_REGEX.test(input.trim());
41162
+ }
41163
+ function hasWindowsDriveLetter(input) {
41164
+ return /^[a-zA-Z]:[/\\]/.test(input);
41165
+ }
41166
+ function fileExists(filePath) {
41167
+ try {
41168
+ return existsSync2(filePath);
41169
+ } catch {
41170
+ return false;
41171
+ }
41172
+ }
41173
+ function walkMarkdownFiles(dir, root, results, ignoredDirs) {
41174
+ let entries;
41175
+ try {
41176
+ entries = readdirSync(dir, { withFileTypes: true });
41177
+ } catch {
41178
+ return;
41179
+ }
41180
+ for (const entry of entries) {
41181
+ if (entry.isDirectory()) {
41182
+ if (ignoredDirs.some((d) => d === entry.name + "/"))
41183
+ continue;
41184
+ walkMarkdownFiles(join2(dir, entry.name), root, results, ignoredDirs);
41185
+ } else if (entry.isFile() && /\.mdx?$/i.test(entry.name)) {
41186
+ const relative = join2(dir, entry.name).slice(root.length + 1).replace(/\\/g, "/");
41187
+ results.push(relative);
41188
+ }
41189
+ }
41190
+ }
41191
+ function resolveMarkdownFileCore(input, projectRoot) {
41192
+ const normalizedInput = normalizeUserPathInput(input);
41193
+ const searchInput = normalizeSeparators(normalizedInput);
41194
+ const isBareFilename = !searchInput.includes("/");
41195
+ const targetLookupKey = getLookupKey(searchInput, isBareFilename);
41196
+ if (!isSearchableMarkdownPath(normalizedInput)) {
41197
+ return { kind: "not_found", input };
41198
+ }
41199
+ if (isAbsoluteNormalizedUserPath(normalizedInput)) {
41200
+ const absolutePath = resolveAbsolutePath(normalizedInput);
41201
+ if (fileExists(absolutePath)) {
41202
+ return { kind: "found", path: absolutePath };
41203
+ }
41204
+ return { kind: "not_found", input };
41205
+ }
41206
+ const fromRoot = resolve(projectRoot, searchInput);
41207
+ if (isWithinProjectRoot(fromRoot, projectRoot) && fileExists(fromRoot)) {
41208
+ return { kind: "found", path: fromRoot };
41209
+ }
41210
+ const allFiles = [];
41211
+ walkMarkdownFiles(projectRoot, projectRoot, allFiles, IGNORED_DIRS);
41212
+ const matches = [];
41213
+ for (const match of allFiles) {
41214
+ const normalizedMatch = normalizeSeparators(match);
41215
+ const matchLookupKey = getLookupKey(normalizedMatch, isBareFilename);
41216
+ if (matchLookupKey === targetLookupKey) {
41217
+ const full = resolve(projectRoot, normalizedMatch);
41218
+ if (isWithinProjectRoot(full, projectRoot)) {
41219
+ matches.push(full);
41220
+ }
41221
+ }
41222
+ }
41223
+ if (matches.length === 1) {
41224
+ return { kind: "found", path: matches[0] };
41225
+ }
41226
+ if (matches.length > 1) {
41227
+ const projectRootPrefix = `${normalizeComparablePath(projectRoot)}/`;
41228
+ const relative = matches.map((match) => normalizeComparablePath(match).replace(projectRootPrefix, ""));
41229
+ return { kind: "ambiguous", input, matches: relative };
41230
+ }
41231
+ return { kind: "not_found", input };
41232
+ }
41233
+ function resolveMarkdownFile(input, projectRoot) {
41234
+ const originalInput = input.trim();
41235
+ const unquotedInput = stripWrappingQuotes(originalInput);
41236
+ const primary = resolveMarkdownFileCore(unquotedInput, projectRoot);
41237
+ if (primary.kind === "found") {
41238
+ return primary;
41239
+ }
41240
+ if (primary.kind === "ambiguous") {
41241
+ return { ...primary, input: originalInput };
41242
+ }
41243
+ if (!unquotedInput.startsWith("@")) {
41244
+ return { kind: "not_found", input: originalInput };
41245
+ }
41246
+ const normalizedInput = unquotedInput.replace(/^@+/, "");
41247
+ if (!normalizedInput) {
41248
+ return { kind: "not_found", input: originalInput };
41249
+ }
41250
+ const fallback = resolveMarkdownFileCore(normalizedInput, projectRoot);
41251
+ if (fallback.kind === "found") {
41252
+ return fallback;
41253
+ }
41254
+ if (fallback.kind === "ambiguous") {
41255
+ return { ...fallback, input: originalInput };
41256
+ }
41257
+ return { kind: "not_found", input: originalInput };
41258
+ }
41259
+
41010
41260
  // ../../packages/server/integrations.ts
41011
41261
  async function extractTags(markdown) {
41012
41262
  const tags = new Set(["plannotator"]);
@@ -41049,12 +41299,11 @@ async function extractTags(markdown) {
41049
41299
  async function saveToObsidian(config) {
41050
41300
  try {
41051
41301
  const { vaultPath, folder, plan } = config;
41052
- let normalizedVault = vaultPath.trim();
41053
- if (normalizedVault.startsWith("~")) {
41054
- const home = process.env.HOME || process.env.USERPROFILE || "";
41055
- normalizedVault = join2(home, normalizedVault.slice(1));
41302
+ if (!vaultPath?.trim()) {
41303
+ return { success: false, error: "Vault path is required" };
41056
41304
  }
41057
- if (!existsSync2(normalizedVault)) {
41305
+ const normalizedVault = resolveUserPath(vaultPath);
41306
+ if (!existsSync3(normalizedVault)) {
41058
41307
  return {
41059
41308
  success: false,
41060
41309
  error: `Vault path does not exist: ${normalizedVault}`
@@ -41068,12 +41317,12 @@ async function saveToObsidian(config) {
41068
41317
  };
41069
41318
  }
41070
41319
  const folderName = folder.trim() || "plannotator";
41071
- const targetFolder = join2(normalizedVault, folderName);
41072
- if (!existsSync2(targetFolder)) {
41320
+ const targetFolder = join3(normalizedVault, folderName);
41321
+ if (!existsSync3(targetFolder)) {
41073
41322
  mkdirSync(targetFolder, { recursive: true });
41074
41323
  }
41075
41324
  const filename = generateFilename(plan, config.filenameFormat, config.filenameSeparator);
41076
- const filePath = join2(targetFolder, filename);
41325
+ const filePath = join3(targetFolder, filename);
41077
41326
  const tags = await extractTags(plan);
41078
41327
  const frontmatter = generateFrontmatter(tags);
41079
41328
  const content = `${frontmatter}
@@ -41129,17 +41378,16 @@ ${plan}`;
41129
41378
  }
41130
41379
 
41131
41380
  // ../../packages/shared/storage.ts
41132
- import { homedir } from "os";
41133
- import { join as join3, resolve, sep } from "path";
41134
- import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync2, readdirSync, statSync as statSync2, existsSync as existsSync3 } from "fs";
41381
+ import { homedir as homedir2 } from "os";
41382
+ import { join as join4, resolve as resolve2, sep } from "path";
41383
+ import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2, existsSync as existsSync4 } from "fs";
41135
41384
  function getPlanDir(customPath) {
41136
41385
  let planDir;
41137
- if (customPath) {
41138
- planDir = customPath.startsWith("~") ? join3(homedir(), customPath.slice(1)) : customPath;
41386
+ if (customPath?.trim()) {
41387
+ planDir = resolveUserPath(customPath);
41139
41388
  } else {
41140
- planDir = join3(homedir(), ".plannotator", "plans");
41389
+ planDir = join4(homedir2(), ".plannotator", "plans");
41141
41390
  }
41142
- planDir = resolve(planDir);
41143
41391
  mkdirSync2(planDir, { recursive: true });
41144
41392
  return planDir;
41145
41393
  }
@@ -41157,13 +41405,13 @@ function generateSlug(plan) {
41157
41405
  }
41158
41406
  function saveAnnotations(slug, annotationsContent, customPath) {
41159
41407
  const planDir = getPlanDir(customPath);
41160
- const filePath = join3(planDir, `${slug}.annotations.md`);
41408
+ const filePath = join4(planDir, `${slug}.annotations.md`);
41161
41409
  writeFileSync(filePath, annotationsContent, "utf-8");
41162
41410
  return filePath;
41163
41411
  }
41164
41412
  function saveFinalSnapshot(slug, status, plan, annotations, customPath) {
41165
41413
  const planDir = getPlanDir(customPath);
41166
- const filePath = join3(planDir, `${slug}-${status}.md`);
41414
+ const filePath = join4(planDir, `${slug}-${status}.md`);
41167
41415
  let content = plan;
41168
41416
  if (annotations && annotations !== "No changes detected.") {
41169
41417
  content += `
@@ -41198,7 +41446,7 @@ function parseArchiveFilename(filename) {
41198
41446
  function listArchivedPlans(customPath) {
41199
41447
  const planDir = getPlanDir(customPath);
41200
41448
  try {
41201
- const entries = readdirSync(planDir);
41449
+ const entries = readdirSync2(planDir);
41202
41450
  const plans = [];
41203
41451
  for (const entry of entries) {
41204
41452
  if (!entry.endsWith(".md"))
@@ -41207,7 +41455,7 @@ function listArchivedPlans(customPath) {
41207
41455
  if (!parsed)
41208
41456
  continue;
41209
41457
  try {
41210
- const stat = statSync2(join3(planDir, entry));
41458
+ const stat = statSync2(join4(planDir, entry));
41211
41459
  parsed.size = stat.size;
41212
41460
  parsed.timestamp = stat.mtime.toISOString();
41213
41461
  } catch {}
@@ -41220,7 +41468,7 @@ function listArchivedPlans(customPath) {
41220
41468
  }
41221
41469
  function readArchivedPlan(filename, customPath) {
41222
41470
  const planDir = getPlanDir(customPath);
41223
- const filePath = resolve(planDir, filename);
41471
+ const filePath = resolve2(planDir, filename);
41224
41472
  if (!filePath.startsWith(planDir + sep))
41225
41473
  return null;
41226
41474
  try {
@@ -41230,13 +41478,13 @@ function readArchivedPlan(filename, customPath) {
41230
41478
  }
41231
41479
  }
41232
41480
  function getHistoryDir(project, slug) {
41233
- const historyDir = join3(homedir(), ".plannotator", "history", project, slug);
41481
+ const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
41234
41482
  mkdirSync2(historyDir, { recursive: true });
41235
41483
  return historyDir;
41236
41484
  }
41237
41485
  function getNextVersionNumber(historyDir) {
41238
41486
  try {
41239
- const entries = readdirSync(historyDir);
41487
+ const entries = readdirSync2(historyDir);
41240
41488
  let max = 0;
41241
41489
  for (const entry of entries) {
41242
41490
  const match = entry.match(/^(\d+)\.md$/);
@@ -41255,7 +41503,7 @@ function saveToHistory(project, slug, plan) {
41255
41503
  const historyDir = getHistoryDir(project, slug);
41256
41504
  const nextVersion = getNextVersionNumber(historyDir);
41257
41505
  if (nextVersion > 1) {
41258
- const latestPath = join3(historyDir, `${String(nextVersion - 1).padStart(3, "0")}.md`);
41506
+ const latestPath = join4(historyDir, `${String(nextVersion - 1).padStart(3, "0")}.md`);
41259
41507
  try {
41260
41508
  const existing = readFileSync2(latestPath, "utf-8");
41261
41509
  if (existing === plan) {
@@ -41264,14 +41512,14 @@ function saveToHistory(project, slug, plan) {
41264
41512
  } catch {}
41265
41513
  }
41266
41514
  const fileName = `${String(nextVersion).padStart(3, "0")}.md`;
41267
- const filePath = join3(historyDir, fileName);
41515
+ const filePath = join4(historyDir, fileName);
41268
41516
  writeFileSync(filePath, plan, "utf-8");
41269
41517
  return { version: nextVersion, path: filePath, isNew: true };
41270
41518
  }
41271
41519
  function getPlanVersion(project, slug, version) {
41272
- const historyDir = join3(homedir(), ".plannotator", "history", project, slug);
41520
+ const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
41273
41521
  const fileName = `${String(version).padStart(3, "0")}.md`;
41274
- const filePath = join3(historyDir, fileName);
41522
+ const filePath = join4(historyDir, fileName);
41275
41523
  try {
41276
41524
  return readFileSync2(filePath, "utf-8");
41277
41525
  } catch {
@@ -41279,30 +41527,30 @@ function getPlanVersion(project, slug, version) {
41279
41527
  }
41280
41528
  }
41281
41529
  function getPlanVersionPath(project, slug, version) {
41282
- const historyDir = join3(homedir(), ".plannotator", "history", project, slug);
41530
+ const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
41283
41531
  const fileName = `${String(version).padStart(3, "0")}.md`;
41284
- const filePath = join3(historyDir, fileName);
41285
- return existsSync3(filePath) ? filePath : null;
41532
+ const filePath = join4(historyDir, fileName);
41533
+ return existsSync4(filePath) ? filePath : null;
41286
41534
  }
41287
41535
  function getVersionCount(project, slug) {
41288
- const historyDir = join3(homedir(), ".plannotator", "history", project, slug);
41536
+ const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
41289
41537
  try {
41290
- const entries = readdirSync(historyDir);
41538
+ const entries = readdirSync2(historyDir);
41291
41539
  return entries.filter((e) => /^\d+\.md$/.test(e)).length;
41292
41540
  } catch {
41293
41541
  return 0;
41294
41542
  }
41295
41543
  }
41296
41544
  function listVersions(project, slug) {
41297
- const historyDir = join3(homedir(), ".plannotator", "history", project, slug);
41545
+ const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
41298
41546
  try {
41299
- const entries = readdirSync(historyDir);
41547
+ const entries = readdirSync2(historyDir);
41300
41548
  const versions = [];
41301
41549
  for (const entry of entries) {
41302
41550
  const match = entry.match(/^(\d+)\.md$/);
41303
41551
  if (match) {
41304
41552
  const version = parseInt(match[1], 10);
41305
- const filePath = join3(historyDir, entry);
41553
+ const filePath = join4(historyDir, entry);
41306
41554
  try {
41307
41555
  const stat = statSync2(filePath);
41308
41556
  versions.push({ version, timestamp: stat.mtime.toISOString() });
@@ -41387,15 +41635,15 @@ async function getRepoInfo() {
41387
41635
  }
41388
41636
 
41389
41637
  // ../../packages/shared/config.ts
41390
- import { homedir as homedir2 } from "os";
41391
- import { join as join4 } from "path";
41392
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync4 } from "fs";
41638
+ import { homedir as homedir3 } from "os";
41639
+ import { join as join5 } from "path";
41640
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
41393
41641
  import { execSync } from "child_process";
41394
- var CONFIG_DIR = join4(homedir2(), ".plannotator");
41395
- var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
41642
+ var CONFIG_DIR = join5(homedir3(), ".plannotator");
41643
+ var CONFIG_PATH = join5(CONFIG_DIR, "config.json");
41396
41644
  function loadConfig() {
41397
41645
  try {
41398
- if (!existsSync4(CONFIG_PATH))
41646
+ if (!existsSync5(CONFIG_PATH))
41399
41647
  return {};
41400
41648
  const raw = readFileSync3(CONFIG_PATH, "utf-8");
41401
41649
  const parsed = JSON.parse(raw);
@@ -41542,7 +41790,7 @@ async function openBrowser(url, options) {
41542
41790
  }
41543
41791
 
41544
41792
  // ../../packages/server/image.ts
41545
- import { resolve as resolve2, join as join5 } from "path";
41793
+ import { resolve as resolve3, join as join6 } from "path";
41546
41794
  import { tmpdir } from "os";
41547
41795
  var ALLOWED_IMAGE_EXTENSIONS = new Set([
41548
41796
  "png",
@@ -41557,7 +41805,7 @@ var ALLOWED_IMAGE_EXTENSIONS = new Set([
41557
41805
  "tif",
41558
41806
  "avif"
41559
41807
  ]);
41560
- var UPLOAD_DIR = join5(tmpdir(), "plannotator");
41808
+ var UPLOAD_DIR = join6(tmpdir(), "plannotator");
41561
41809
  function getExtension(filePath) {
41562
41810
  const lastDot = filePath.lastIndexOf(".");
41563
41811
  if (lastDot === -1)
@@ -41568,7 +41816,7 @@ function hasImageExtension(filePath) {
41568
41816
  return ALLOWED_IMAGE_EXTENSIONS.has(getExtension(filePath));
41569
41817
  }
41570
41818
  function validateImagePath(rawPath) {
41571
- const resolved = resolve2(rawPath);
41819
+ const resolved = resolve3(rawPath);
41572
41820
  if (!hasImageExtension(resolved)) {
41573
41821
  return {
41574
41822
  valid: false,
@@ -41591,12 +41839,12 @@ function validateUploadExtension(fileName) {
41591
41839
  }
41592
41840
 
41593
41841
  // ../../packages/shared/draft.ts
41594
- import { homedir as homedir3 } from "os";
41595
- import { join as join6 } from "path";
41596
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, unlinkSync, existsSync as existsSync5 } from "fs";
41842
+ import { homedir as homedir4 } from "os";
41843
+ import { join as join7 } from "path";
41844
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, unlinkSync, existsSync as existsSync6 } from "fs";
41597
41845
  import { createHash } from "crypto";
41598
41846
  function getDraftDir() {
41599
- const dir = join6(homedir3(), ".plannotator", "drafts");
41847
+ const dir = join7(homedir4(), ".plannotator", "drafts");
41600
41848
  mkdirSync4(dir, { recursive: true });
41601
41849
  return dir;
41602
41850
  }
@@ -41605,12 +41853,12 @@ function contentHash(content) {
41605
41853
  }
41606
41854
  function saveDraft(key, data) {
41607
41855
  const dir = getDraftDir();
41608
- writeFileSync3(join6(dir, `${key}.json`), JSON.stringify(data), "utf-8");
41856
+ writeFileSync3(join7(dir, `${key}.json`), JSON.stringify(data), "utf-8");
41609
41857
  }
41610
41858
  function loadDraft(key) {
41611
- const filePath = join6(getDraftDir(), `${key}.json`);
41859
+ const filePath = join7(getDraftDir(), `${key}.json`);
41612
41860
  try {
41613
- if (!existsSync5(filePath))
41861
+ if (!existsSync6(filePath))
41614
41862
  return null;
41615
41863
  return JSON.parse(readFileSync4(filePath, "utf-8"));
41616
41864
  } catch {
@@ -41618,9 +41866,9 @@ function loadDraft(key) {
41618
41866
  }
41619
41867
  }
41620
41868
  function deleteDraft(key) {
41621
- const filePath = join6(getDraftDir(), `${key}.json`);
41869
+ const filePath = join7(getDraftDir(), `${key}.json`);
41622
41870
  try {
41623
- if (existsSync5(filePath))
41871
+ if (existsSync6(filePath))
41624
41872
  unlinkSync(filePath);
41625
41873
  } catch {}
41626
41874
  }
@@ -41801,180 +42049,6 @@ function buildFileTree(relativePaths) {
41801
42049
  return root;
41802
42050
  }
41803
42051
 
41804
- // ../../packages/shared/resolve-file.ts
41805
- import { isAbsolute, join as join7, resolve as resolve3, win32 } from "path";
41806
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
41807
- var MARKDOWN_PATH_REGEX = /\.mdx?$/i;
41808
- var WINDOWS_DRIVE_PATH_PATTERNS = [
41809
- /^\/cygdrive\/([a-zA-Z])\/(.+)$/,
41810
- /^\/([a-zA-Z])\/(.+)$/
41811
- ];
41812
- var IGNORED_DIRS = [
41813
- "node_modules/",
41814
- ".git/",
41815
- "dist/",
41816
- "build/",
41817
- ".next/",
41818
- "__pycache__/",
41819
- ".obsidian/",
41820
- ".trash/"
41821
- ];
41822
- function normalizeSeparators(input) {
41823
- return input.replace(/\\/g, "/");
41824
- }
41825
- function stripTrailingSlashes(input) {
41826
- return input.replace(/\/+$/, "");
41827
- }
41828
- function normalizeComparablePath(input) {
41829
- return stripTrailingSlashes(normalizeSeparators(resolve3(input)));
41830
- }
41831
- function isWithinProjectRoot(candidate, projectRoot) {
41832
- const normalizedCandidate = normalizeComparablePath(candidate);
41833
- const normalizedProjectRoot = normalizeComparablePath(projectRoot);
41834
- return normalizedCandidate === normalizedProjectRoot || normalizedCandidate.startsWith(`${normalizedProjectRoot}/`);
41835
- }
41836
- function getLowercaseBasename(input) {
41837
- const normalizedInput = normalizeSeparators(input);
41838
- return normalizedInput.split("/").pop().toLowerCase();
41839
- }
41840
- function getLookupKey(input, isBareFilename) {
41841
- return isBareFilename ? getLowercaseBasename(input) : input.toLowerCase();
41842
- }
41843
- function resolveAbsolutePath(input, platform = process.platform) {
41844
- return platform === "win32" || hasWindowsDriveLetter(input) ? win32.resolve(input) : resolve3(input);
41845
- }
41846
- function isSearchableMarkdownPath(input) {
41847
- return MARKDOWN_PATH_REGEX.test(input.trim());
41848
- }
41849
- function stripWrappingQuotes(input) {
41850
- if (input.length < 2) {
41851
- return input;
41852
- }
41853
- const first = input[0];
41854
- const last = input[input.length - 1];
41855
- if (first === '"' && last === '"' || first === "'" && last === "'") {
41856
- return input.slice(1, -1);
41857
- }
41858
- return input;
41859
- }
41860
- function hasWindowsDriveLetter(input) {
41861
- return /^[a-zA-Z]:[/\\]/.test(input);
41862
- }
41863
- function fileExists(filePath) {
41864
- try {
41865
- return existsSync6(filePath);
41866
- } catch {
41867
- return false;
41868
- }
41869
- }
41870
- function walkMarkdownFiles(dir, root, results, ignoredDirs) {
41871
- let entries;
41872
- try {
41873
- entries = readdirSync2(dir, { withFileTypes: true });
41874
- } catch {
41875
- return;
41876
- }
41877
- for (const entry of entries) {
41878
- if (entry.isDirectory()) {
41879
- if (ignoredDirs.some((d) => d === entry.name + "/"))
41880
- continue;
41881
- walkMarkdownFiles(join7(dir, entry.name), root, results, ignoredDirs);
41882
- } else if (entry.isFile() && /\.mdx?$/i.test(entry.name)) {
41883
- const relative = join7(dir, entry.name).slice(root.length + 1).replace(/\\/g, "/");
41884
- results.push(relative);
41885
- }
41886
- }
41887
- }
41888
- function normalizeMarkdownPathInput(input, platform = process.platform) {
41889
- if (platform !== "win32") {
41890
- return input;
41891
- }
41892
- for (const pattern of WINDOWS_DRIVE_PATH_PATTERNS) {
41893
- const match = input.match(pattern);
41894
- if (!match) {
41895
- continue;
41896
- }
41897
- const [, driveLetter, rest] = match;
41898
- return `${driveLetter.toUpperCase()}:/${rest}`;
41899
- }
41900
- return input;
41901
- }
41902
- function isAbsoluteMarkdownPath(input, platform = process.platform) {
41903
- const normalizedInput = normalizeMarkdownPathInput(input, platform);
41904
- if (hasWindowsDriveLetter(normalizedInput)) {
41905
- return true;
41906
- }
41907
- return platform === "win32" ? win32.isAbsolute(normalizedInput) : isAbsolute(normalizedInput);
41908
- }
41909
- function resolveMarkdownFileCore(input, projectRoot) {
41910
- const normalizedInput = normalizeMarkdownPathInput(input);
41911
- const searchInput = normalizeSeparators(normalizedInput);
41912
- const isBareFilename = !searchInput.includes("/");
41913
- const targetLookupKey = getLookupKey(searchInput, isBareFilename);
41914
- if (!isSearchableMarkdownPath(normalizedInput)) {
41915
- return { kind: "not_found", input };
41916
- }
41917
- if (isAbsoluteMarkdownPath(normalizedInput)) {
41918
- const absolutePath = resolveAbsolutePath(normalizedInput);
41919
- if (fileExists(absolutePath)) {
41920
- return { kind: "found", path: absolutePath };
41921
- }
41922
- return { kind: "not_found", input };
41923
- }
41924
- const fromRoot = resolve3(projectRoot, searchInput);
41925
- if (isWithinProjectRoot(fromRoot, projectRoot) && fileExists(fromRoot)) {
41926
- return { kind: "found", path: fromRoot };
41927
- }
41928
- const allFiles = [];
41929
- walkMarkdownFiles(projectRoot, projectRoot, allFiles, IGNORED_DIRS);
41930
- const matches = [];
41931
- for (const match of allFiles) {
41932
- const normalizedMatch = normalizeSeparators(match);
41933
- const matchLookupKey = getLookupKey(normalizedMatch, isBareFilename);
41934
- if (matchLookupKey === targetLookupKey) {
41935
- const full = resolve3(projectRoot, normalizedMatch);
41936
- if (isWithinProjectRoot(full, projectRoot)) {
41937
- matches.push(full);
41938
- }
41939
- }
41940
- }
41941
- if (matches.length === 1) {
41942
- return { kind: "found", path: matches[0] };
41943
- }
41944
- if (matches.length > 1) {
41945
- const projectRootPrefix = `${normalizeComparablePath(projectRoot)}/`;
41946
- const relative = matches.map((match) => normalizeComparablePath(match).replace(projectRootPrefix, ""));
41947
- return { kind: "ambiguous", input, matches: relative };
41948
- }
41949
- return { kind: "not_found", input };
41950
- }
41951
- function resolveMarkdownFile(input, projectRoot) {
41952
- const originalInput = input.trim();
41953
- const unquotedInput = stripWrappingQuotes(originalInput);
41954
- const primary = resolveMarkdownFileCore(unquotedInput, projectRoot);
41955
- if (primary.kind === "found") {
41956
- return primary;
41957
- }
41958
- if (primary.kind === "ambiguous") {
41959
- return { ...primary, input: originalInput };
41960
- }
41961
- if (!unquotedInput.startsWith("@")) {
41962
- return { kind: "not_found", input: originalInput };
41963
- }
41964
- const normalizedInput = unquotedInput.replace(/^@+/, "");
41965
- if (!normalizedInput) {
41966
- return { kind: "not_found", input: originalInput };
41967
- }
41968
- const fallback = resolveMarkdownFileCore(normalizedInput, projectRoot);
41969
- if (fallback.kind === "found") {
41970
- return fallback;
41971
- }
41972
- if (fallback.kind === "ambiguous") {
41973
- return { ...fallback, input: originalInput };
41974
- }
41975
- return { kind: "not_found", input: originalInput };
41976
- }
41977
-
41978
42052
  // ../../node_modules/.bun/turndown@7.2.4/node_modules/turndown/lib/turndown.es.js
41979
42053
  function extend(destination) {
41980
42054
  for (var i = 1;i < arguments.length; i++) {
@@ -42664,8 +42738,9 @@ async function handleDoc(req) {
42664
42738
  return Response.json({ error: "Missing path parameter" }, { status: 400 });
42665
42739
  }
42666
42740
  const base = url.searchParams.get("base");
42667
- if (base && !requestedPath.startsWith("/") && /\.(mdx?|html?)$/i.test(requestedPath)) {
42668
- const fromBase = resolve4(base, requestedPath);
42741
+ const resolvedBase = base ? resolveUserPath(base) : null;
42742
+ if (resolvedBase && !isAbsoluteUserPath(requestedPath) && /\.(mdx?|html?)$/i.test(requestedPath)) {
42743
+ const fromBase = resolveUserPath(requestedPath, resolvedBase);
42669
42744
  try {
42670
42745
  const file = Bun.file(fromBase);
42671
42746
  if (await file.exists()) {
@@ -42677,7 +42752,7 @@ async function handleDoc(req) {
42677
42752
  }
42678
42753
  const projectRoot = process.cwd();
42679
42754
  if (/\.html?$/i.test(requestedPath)) {
42680
- const resolvedHtml = resolve4(base || projectRoot, requestedPath);
42755
+ const resolvedHtml = resolveUserPath(requestedPath, resolvedBase || projectRoot);
42681
42756
  if (!isWithinProjectRoot(resolvedHtml, projectRoot)) {
42682
42757
  return Response.json({ error: "Access denied: path is outside project root" }, { status: 403 });
42683
42758
  }
@@ -42718,7 +42793,7 @@ async function handleObsidianFiles(req) {
42718
42793
  if (!vaultPath) {
42719
42794
  return Response.json({ error: "Missing vaultPath parameter" }, { status: 400 });
42720
42795
  }
42721
- const resolvedVault = resolve4(vaultPath);
42796
+ const resolvedVault = resolveUserPath(vaultPath);
42722
42797
  if (!existsSync7(resolvedVault) || !statSync3(resolvedVault).isDirectory()) {
42723
42798
  return Response.json({ error: "Invalid vault path" }, { status: 400 });
42724
42799
  }
@@ -42750,7 +42825,7 @@ async function handleObsidianDoc(req) {
42750
42825
  if (!/\.mdx?$/i.test(filePath)) {
42751
42826
  return Response.json({ error: "Only markdown files are supported" }, { status: 400 });
42752
42827
  }
42753
- const resolvedVault = resolve4(vaultPath);
42828
+ const resolvedVault = resolveUserPath(vaultPath);
42754
42829
  let resolvedFile = resolve4(resolvedVault, filePath);
42755
42830
  if (!existsSync7(resolvedFile) && !filePath.includes("/")) {
42756
42831
  const glob = new Bun.Glob(`**/${filePath}`);
@@ -42793,7 +42868,7 @@ async function handleFileBrowserFiles(req) {
42793
42868
  if (!dirPath) {
42794
42869
  return Response.json({ error: "Missing dirPath parameter" }, { status: 400 });
42795
42870
  }
42796
- const resolvedDir = resolve4(dirPath);
42871
+ const resolvedDir = resolveUserPath(dirPath);
42797
42872
  if (!existsSync7(resolvedDir) || !statSync3(resolvedDir).isDirectory()) {
42798
42873
  return Response.json({ error: "Invalid directory path" }, { status: 400 });
42799
42874
  }
@@ -44937,16 +45012,16 @@ function createAgentJobHandler(options) {
44937
45012
 
44938
45013
  // ../../packages/server/codex-review.ts
44939
45014
  import { join as join9 } from "path";
44940
- import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
45015
+ import { homedir as homedir5, tmpdir as tmpdir2 } from "os";
44941
45016
  import { appendFile, mkdir, unlink, writeFile, readFile } from "fs/promises";
44942
45017
  import { existsSync as existsSync8 } from "fs";
44943
45018
  var DEBUG_ENABLED = !!process.env.PLANNOTATOR_DEBUG;
44944
- var DEBUG_LOG_PATH = join9(homedir4(), ".plannotator", "codex-review-debug.log");
45019
+ var DEBUG_LOG_PATH = join9(homedir5(), ".plannotator", "codex-review-debug.log");
44945
45020
  async function debugLog(label, data) {
44946
45021
  if (!DEBUG_ENABLED)
44947
45022
  return;
44948
45023
  try {
44949
- await mkdir(join9(homedir4(), ".plannotator"), { recursive: true });
45024
+ await mkdir(join9(homedir5(), ".plannotator"), { recursive: true });
44950
45025
  const timestamp = new Date().toISOString();
44951
45026
  const line = data !== undefined ? `[${timestamp}] ${label}: ${typeof data === "string" ? data : JSON.stringify(data, null, 2)}
44952
45027
  ` : `[${timestamp}] ${label}
@@ -44995,7 +45070,7 @@ var CODEX_REVIEW_SCHEMA = JSON.stringify({
44995
45070
  required: ["findings", "overall_correctness", "overall_explanation", "overall_confidence_score"],
44996
45071
  additionalProperties: false
44997
45072
  });
44998
- var SCHEMA_DIR = join9(homedir4(), ".plannotator");
45073
+ var SCHEMA_DIR = join9(homedir5(), ".plannotator");
44999
45074
  var SCHEMA_FILE = join9(SCHEMA_DIR, "codex-review-schema.json");
45000
45075
  var schemaMaterialized = false;
45001
45076
  async function ensureSchemaFile() {
@@ -46941,7 +47016,14 @@ async function urlToMarkdown(url, options) {
46941
47016
  return { markdown: text, source: "fetch-raw" };
46942
47017
  }
46943
47018
  }
46944
- if (options.useJina && !isLocalUrl(url)) {
47019
+ const local = isLocalUrl(url);
47020
+ if (!local) {
47021
+ const negotiated = await fetchViaContentNegotiation(url);
47022
+ if (negotiated !== null) {
47023
+ return { markdown: negotiated, source: "content-negotiation" };
47024
+ }
47025
+ }
47026
+ if (options.useJina && !local) {
46945
47027
  try {
46946
47028
  const markdown2 = await fetchViaJina(url);
46947
47029
  return { markdown: markdown2, source: "jina" };
@@ -47026,6 +47108,45 @@ async function fetchRawText(url) {
47026
47108
  clearTimeout(timer);
47027
47109
  }
47028
47110
  }
47111
+ var NEGOTIATION_TIMEOUT_MS = 5000;
47112
+ async function fetchViaContentNegotiation(url) {
47113
+ const controller = new AbortController;
47114
+ const timer = setTimeout(() => controller.abort(), NEGOTIATION_TIMEOUT_MS);
47115
+ const headers = {
47116
+ "User-Agent": "Mozilla/5.0 (compatible; Plannotator/1.0; +https://plannotator.ai)",
47117
+ Accept: "text/markdown, text/html;q=0.9"
47118
+ };
47119
+ try {
47120
+ let currentUrl = url;
47121
+ let res = await fetch(currentUrl, { headers, redirect: "manual", signal: controller.signal });
47122
+ for (let i3 = 0;i3 < MAX_REDIRECTS && REDIRECT_STATUSES.has(res.status); i3++) {
47123
+ const location = res.headers.get("location");
47124
+ if (!location)
47125
+ break;
47126
+ currentUrl = new URL(location, currentUrl).href;
47127
+ if (isLocalUrl(currentUrl)) {
47128
+ res.body?.cancel();
47129
+ return null;
47130
+ }
47131
+ res.body?.cancel();
47132
+ res = await fetch(currentUrl, { headers, redirect: "manual", signal: controller.signal });
47133
+ }
47134
+ if (!res.ok) {
47135
+ res.body?.cancel();
47136
+ return null;
47137
+ }
47138
+ const ct = res.headers.get("content-type") || "";
47139
+ if (!ct.includes("text/markdown")) {
47140
+ res.body?.cancel();
47141
+ return null;
47142
+ }
47143
+ return await readBodyWithLimit(res);
47144
+ } catch {
47145
+ return null;
47146
+ } finally {
47147
+ clearTimeout(timer);
47148
+ }
47149
+ }
47029
47150
  async function fetchViaJina(url) {
47030
47151
  const cleanUrl = url.split("#")[0];
47031
47152
  const jinaUrl = `https://r.jina.ai/${cleanUrl}`;
@@ -47190,7 +47311,7 @@ Please address this feedback.`;
47190
47311
  }
47191
47312
  }
47192
47313
  async function handleAnnotateCommand(event, deps) {
47193
- const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl } = deps;
47314
+ const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl, getPasteApiUrl } = deps;
47194
47315
  const filePath = event.properties?.arguments || event.arguments || "";
47195
47316
  if (!filePath) {
47196
47317
  client3.app.log({ level: "error", message: "Usage: /plannotator-annotate <file.md | file.html | https://...>" });
@@ -47214,7 +47335,7 @@ async function handleAnnotateCommand(event, deps) {
47214
47335
  sourceInfo = filePath;
47215
47336
  } else {
47216
47337
  const projectRoot = process.cwd();
47217
- const resolvedArg = path3.resolve(projectRoot, filePath);
47338
+ const resolvedArg = resolveUserPath(filePath, projectRoot);
47218
47339
  if (/\.html?$/i.test(resolvedArg)) {
47219
47340
  let fileSize;
47220
47341
  try {
@@ -47260,6 +47381,7 @@ ${resolved.matches.map((m3) => ` ${m3}`).join(`
47260
47381
  sourceInfo,
47261
47382
  sharingEnabled: await getSharingEnabled(),
47262
47383
  shareBaseUrl: getShareBaseUrl(),
47384
+ pasteApiUrl: getPasteApiUrl(),
47263
47385
  htmlContent,
47264
47386
  onReady: handleServerReady
47265
47387
  });
@@ -47293,7 +47415,7 @@ Please address the annotation feedback above.`
47293
47415
  }
47294
47416
  }
47295
47417
  async function handleAnnotateLastCommand(event, deps) {
47296
- const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl } = deps;
47418
+ const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl, getPasteApiUrl } = deps;
47297
47419
  const sessionId = event.properties?.sessionID;
47298
47420
  if (!sessionId) {
47299
47421
  client3.app.log({ level: "error", message: "No active session." });
@@ -47329,6 +47451,7 @@ async function handleAnnotateLastCommand(event, deps) {
47329
47451
  mode: "annotate-last",
47330
47452
  sharingEnabled: await getSharingEnabled(),
47331
47453
  shareBaseUrl: getShareBaseUrl(),
47454
+ pasteApiUrl: getPasteApiUrl(),
47332
47455
  htmlContent,
47333
47456
  onReady: handleServerReady
47334
47457
  });
@@ -47341,7 +47464,7 @@ async function handleAnnotateLastCommand(event, deps) {
47341
47464
  return result.feedback || null;
47342
47465
  }
47343
47466
  async function handleArchiveCommand(event, deps) {
47344
- const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl } = deps;
47467
+ const { client: client3, htmlContent, getSharingEnabled, getShareBaseUrl, getPasteApiUrl } = deps;
47345
47468
  client3.app.log({ level: "info", message: "Opening plan archive..." });
47346
47469
  const server2 = await startPlannotatorServer({
47347
47470
  plan: "",
@@ -47349,6 +47472,7 @@ async function handleArchiveCommand(event, deps) {
47349
47472
  mode: "archive",
47350
47473
  sharingEnabled: await getSharingEnabled(),
47351
47474
  shareBaseUrl: getShareBaseUrl(),
47475
+ pasteApiUrl: getPasteApiUrl(),
47352
47476
  htmlContent,
47353
47477
  onReady: handleServerReady
47354
47478
  });
@@ -47410,14 +47534,29 @@ if (_proto?.constructor && _proto.constructor !== Response && _proto.constructor
47410
47534
  }
47411
47535
  var _planHtml = null;
47412
47536
  var _reviewHtml = null;
47537
+ function resolveBundledHtmlPath(filename) {
47538
+ const candidates = [
47539
+ path4.join(import.meta.dir, filename),
47540
+ path4.join(import.meta.dir, "..", filename)
47541
+ ];
47542
+ for (const candidate of candidates) {
47543
+ if (existsSync10(candidate)) {
47544
+ return candidate;
47545
+ }
47546
+ }
47547
+ throw new Error(`Could not find bundled HTML asset: ${filename}`);
47548
+ }
47549
+ function readBundledHtml(filename) {
47550
+ return readFileSync6(resolveBundledHtmlPath(filename), "utf-8");
47551
+ }
47413
47552
  function getPlanHtml() {
47414
47553
  if (!_planHtml)
47415
- _planHtml = readFileSync6(path4.join(import.meta.dir, "..", "plannotator.html"), "utf-8");
47554
+ _planHtml = readBundledHtml("plannotator.html");
47416
47555
  return _planHtml;
47417
47556
  }
47418
47557
  function getReviewHtml() {
47419
47558
  if (!_reviewHtml)
47420
- _reviewHtml = readFileSync6(path4.join(import.meta.dir, "..", "review-editor.html"), "utf-8");
47559
+ _reviewHtml = readBundledHtml("review-editor.html");
47421
47560
  return _reviewHtml;
47422
47561
  }
47423
47562
  var DEFAULT_PLAN_TIMEOUT_SECONDS = 345600;
@@ -47465,10 +47604,10 @@ Only write and submit a plan once you have sufficient context.
47465
47604
  - Don't end your turn without either submitting a plan or asking the user a question.`;
47466
47605
  }
47467
47606
  var PlannotatorPlugin = async (ctx) => {
47468
- Bun.file(path4.join(import.meta.dir, "..", "plannotator.html")).text().then((h) => {
47607
+ Bun.file(resolveBundledHtmlPath("plannotator.html")).text().then((h) => {
47469
47608
  _planHtml = h;
47470
47609
  });
47471
- Bun.file(path4.join(import.meta.dir, "..", "review-editor.html")).text().then((h) => {
47610
+ Bun.file(resolveBundledHtmlPath("review-editor.html")).text().then((h) => {
47472
47611
  _reviewHtml = h;
47473
47612
  });
47474
47613
  let cachedAgents = null;
@@ -47485,6 +47624,9 @@ var PlannotatorPlugin = async (ctx) => {
47485
47624
  function getShareBaseUrl() {
47486
47625
  return process.env.PLANNOTATOR_SHARE_URL || undefined;
47487
47626
  }
47627
+ function getPasteApiUrl() {
47628
+ return process.env.PLANNOTATOR_PASTE_URL || undefined;
47629
+ }
47488
47630
  function getPlanTimeoutSeconds() {
47489
47631
  const raw = process.env.PLANNOTATOR_PLAN_TIMEOUT_SECONDS?.trim();
47490
47632
  if (!raw)
@@ -47618,6 +47760,7 @@ Do NOT proceed with implementation until your plan is approved.`);
47618
47760
  reviewHtmlContent: getReviewHtml(),
47619
47761
  getSharingEnabled,
47620
47762
  getShareBaseUrl,
47763
+ getPasteApiUrl,
47621
47764
  directory: ctx.directory
47622
47765
  };
47623
47766
  const feedback = await handleAnnotateLastCommand({ properties: { sessionID: input.sessionID } }, deps);
@@ -47650,6 +47793,7 @@ Please address the annotation feedback above.`
47650
47793
  reviewHtmlContent: getReviewHtml(),
47651
47794
  getSharingEnabled,
47652
47795
  getShareBaseUrl,
47796
+ getPasteApiUrl,
47653
47797
  directory: ctx.directory
47654
47798
  };
47655
47799
  if (commandName === "plannotator-review")
@@ -47684,6 +47828,7 @@ Please address the annotation feedback above.`
47684
47828
  origin: "opencode",
47685
47829
  sharingEnabled,
47686
47830
  shareBaseUrl: getShareBaseUrl(),
47831
+ pasteApiUrl: getPasteApiUrl(),
47687
47832
  htmlContent: getPlanHtml(),
47688
47833
  opencodeClient: ctx.client,
47689
47834
  onReady: async (url, isRemote, port) => {
@@ -47708,11 +47853,6 @@ Please address the annotation feedback above.`
47708
47853
  const shouldSwitchAgent = result.agentSwitch && result.agentSwitch !== "disabled";
47709
47854
  const targetAgent = result.agentSwitch || "build";
47710
47855
  if (shouldSwitchAgent) {
47711
- try {
47712
- await ctx.client.tui.executeCommand({
47713
- body: { command: "agent_cycle" }
47714
- });
47715
- } catch {}
47716
47856
  try {
47717
47857
  await ctx.client.session.prompt({
47718
47858
  path: { id: context.sessionID },