@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/README.md +1 -0
- package/dist/index.js +410 -270
- package/package.json +3 -3
- package/plannotator.html +438 -418
- package/review-editor.html +3 -3
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.
|
|
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
|
-
|
|
40447
|
-
|
|
40448
|
-
|
|
40449
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
40516
|
-
|
|
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: `${
|
|
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
|
|
40832
|
-
import { mkdirSync, existsSync as
|
|
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
|
-
|
|
41053
|
-
|
|
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
|
-
|
|
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 =
|
|
41072
|
-
if (!
|
|
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 =
|
|
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
|
|
41134
|
-
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync2, readdirSync, statSync as statSync2, existsSync as
|
|
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 =
|
|
41386
|
+
if (customPath?.trim()) {
|
|
41387
|
+
planDir = resolveUserPath(customPath);
|
|
41139
41388
|
} else {
|
|
41140
|
-
planDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
41520
|
+
const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
|
|
41273
41521
|
const fileName = `${String(version).padStart(3, "0")}.md`;
|
|
41274
|
-
const filePath =
|
|
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 =
|
|
41530
|
+
const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
|
|
41283
41531
|
const fileName = `${String(version).padStart(3, "0")}.md`;
|
|
41284
|
-
const filePath =
|
|
41285
|
-
return
|
|
41532
|
+
const filePath = join4(historyDir, fileName);
|
|
41533
|
+
return existsSync4(filePath) ? filePath : null;
|
|
41286
41534
|
}
|
|
41287
41535
|
function getVersionCount(project, slug) {
|
|
41288
|
-
const historyDir =
|
|
41536
|
+
const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
|
|
41289
41537
|
try {
|
|
41290
|
-
const entries =
|
|
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 =
|
|
41545
|
+
const historyDir = join4(homedir2(), ".plannotator", "history", project, slug);
|
|
41298
41546
|
try {
|
|
41299
|
-
const entries =
|
|
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 =
|
|
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
|
|
41391
|
-
import { join as
|
|
41392
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, existsSync as
|
|
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 =
|
|
41395
|
-
var CONFIG_PATH =
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
41595
|
-
import { join as
|
|
41596
|
-
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3, readFileSync as readFileSync4, unlinkSync, existsSync as
|
|
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 =
|
|
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(
|
|
41856
|
+
writeFileSync3(join7(dir, `${key}.json`), JSON.stringify(data), "utf-8");
|
|
41609
41857
|
}
|
|
41610
41858
|
function loadDraft(key) {
|
|
41611
|
-
const filePath =
|
|
41859
|
+
const filePath = join7(getDraftDir(), `${key}.json`);
|
|
41612
41860
|
try {
|
|
41613
|
-
if (!
|
|
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 =
|
|
41869
|
+
const filePath = join7(getDraftDir(), `${key}.json`);
|
|
41622
41870
|
try {
|
|
41623
|
-
if (
|
|
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
|
-
|
|
42668
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
47554
|
+
_planHtml = readBundledHtml("plannotator.html");
|
|
47416
47555
|
return _planHtml;
|
|
47417
47556
|
}
|
|
47418
47557
|
function getReviewHtml() {
|
|
47419
47558
|
if (!_reviewHtml)
|
|
47420
|
-
_reviewHtml =
|
|
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(
|
|
47607
|
+
Bun.file(resolveBundledHtmlPath("plannotator.html")).text().then((h) => {
|
|
47469
47608
|
_planHtml = h;
|
|
47470
47609
|
});
|
|
47471
|
-
Bun.file(
|
|
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 },
|