@ilya-lesikov/pi-pi 0.1.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/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR/MR provider for Node.js runtime.
|
|
3
|
+
* Node.js PRRuntime + bound dispatch functions from shared pr-provider.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type PRMetadata,
|
|
10
|
+
type PRRef,
|
|
11
|
+
type PRReviewFileComment,
|
|
12
|
+
type PRRuntime,
|
|
13
|
+
type PRStackTree,
|
|
14
|
+
type PRListItem,
|
|
15
|
+
parsePRUrl as parsePRUrlCore,
|
|
16
|
+
} from "../generated/pr-types.js";
|
|
17
|
+
import {
|
|
18
|
+
checkAuth as checkAuthCore,
|
|
19
|
+
fetchPRContext as fetchPRContextCore,
|
|
20
|
+
fetchPR as fetchPRCore,
|
|
21
|
+
fetchPRFileContent as fetchPRFileContentCore,
|
|
22
|
+
fetchPRViewedFiles as fetchPRViewedFilesCore,
|
|
23
|
+
fetchPRStack as fetchPRStackCore,
|
|
24
|
+
fetchPRList as fetchPRListCore,
|
|
25
|
+
getUser as getUserCore,
|
|
26
|
+
markPRFilesViewed as markPRFilesViewedCore,
|
|
27
|
+
submitPRReview as submitPRReviewCore,
|
|
28
|
+
} from "../generated/pr-provider.js";
|
|
29
|
+
|
|
30
|
+
const prRuntime: PRRuntime = {
|
|
31
|
+
async runCommand(cmd, args) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const proc = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
34
|
+
let stdout = "";
|
|
35
|
+
let stderr = "";
|
|
36
|
+
proc.stdout?.on("data", (chunk: Buffer) => {
|
|
37
|
+
stdout += chunk.toString();
|
|
38
|
+
});
|
|
39
|
+
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
40
|
+
stderr += chunk.toString();
|
|
41
|
+
});
|
|
42
|
+
proc.on("error", reject);
|
|
43
|
+
proc.on("close", (exitCode) => {
|
|
44
|
+
resolve({ stdout, stderr, exitCode: exitCode ?? 1 });
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
async runCommandWithInput(cmd, args, input) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const proc = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
51
|
+
let stdout = "";
|
|
52
|
+
let stderr = "";
|
|
53
|
+
proc.stdout?.on("data", (chunk: Buffer) => {
|
|
54
|
+
stdout += chunk.toString();
|
|
55
|
+
});
|
|
56
|
+
proc.stderr?.on("data", (chunk: Buffer) => {
|
|
57
|
+
stderr += chunk.toString();
|
|
58
|
+
});
|
|
59
|
+
proc.on("error", reject);
|
|
60
|
+
proc.on("close", (exitCode) => {
|
|
61
|
+
resolve({ stdout, stderr, exitCode: exitCode ?? 1 });
|
|
62
|
+
});
|
|
63
|
+
proc.stdin?.write(input);
|
|
64
|
+
proc.stdin?.end();
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const parsePRUrl = parsePRUrlCore;
|
|
70
|
+
export function checkPRAuth(ref: PRRef) {
|
|
71
|
+
return checkAuthCore(prRuntime, ref);
|
|
72
|
+
}
|
|
73
|
+
export function getPRUser(ref: PRRef) {
|
|
74
|
+
return getUserCore(prRuntime, ref);
|
|
75
|
+
}
|
|
76
|
+
export function fetchPR(ref: PRRef) {
|
|
77
|
+
return fetchPRCore(prRuntime, ref);
|
|
78
|
+
}
|
|
79
|
+
export function fetchPRContext(ref: PRRef) {
|
|
80
|
+
return fetchPRContextCore(prRuntime, ref);
|
|
81
|
+
}
|
|
82
|
+
export function fetchPRFileContent(ref: PRRef, sha: string, filePath: string) {
|
|
83
|
+
return fetchPRFileContentCore(prRuntime, ref, sha, filePath);
|
|
84
|
+
}
|
|
85
|
+
export function submitPRReview(
|
|
86
|
+
ref: PRRef,
|
|
87
|
+
headSha: string,
|
|
88
|
+
action: "approve" | "comment",
|
|
89
|
+
body: string,
|
|
90
|
+
fileComments: PRReviewFileComment[],
|
|
91
|
+
) {
|
|
92
|
+
return submitPRReviewCore(
|
|
93
|
+
prRuntime,
|
|
94
|
+
ref,
|
|
95
|
+
headSha,
|
|
96
|
+
action,
|
|
97
|
+
body,
|
|
98
|
+
fileComments,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function fetchPRViewedFiles(ref: PRRef): Promise<Record<string, boolean>> {
|
|
103
|
+
return fetchPRViewedFilesCore(prRuntime, ref);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function markPRFilesViewed(
|
|
107
|
+
ref: PRRef,
|
|
108
|
+
prNodeId: string,
|
|
109
|
+
filePaths: string[],
|
|
110
|
+
viewed: boolean,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
return markPRFilesViewedCore(prRuntime, ref, prNodeId, filePaths, viewed);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function fetchPRStack(
|
|
116
|
+
ref: PRRef,
|
|
117
|
+
metadata: PRMetadata,
|
|
118
|
+
): Promise<PRStackTree | null> {
|
|
119
|
+
return fetchPRStackCore(prRuntime, ref, metadata);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function fetchPRList(
|
|
123
|
+
ref: PRRef,
|
|
124
|
+
): Promise<PRListItem[]> {
|
|
125
|
+
return fetchPRListCore(prRuntime, ref);
|
|
126
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project detection — repo info, project name, remote URL parsing.
|
|
3
|
+
* detectProjectName, getRepoInfo, parseRemoteUrl
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
import { sanitizeTag } from "../generated/project.js";
|
|
9
|
+
import { parseRemoteUrl, getDirName } from "../generated/repo.js";
|
|
10
|
+
|
|
11
|
+
/** Run a git command and return stdout (empty string on error). */
|
|
12
|
+
function git(cmd: string): string {
|
|
13
|
+
try {
|
|
14
|
+
return execSync(`git ${cmd}`, {
|
|
15
|
+
encoding: "utf-8",
|
|
16
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
17
|
+
}).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function detectProjectName(): string {
|
|
24
|
+
try {
|
|
25
|
+
const toplevel = execSync("git rev-parse --show-toplevel", {
|
|
26
|
+
encoding: "utf-8",
|
|
27
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
28
|
+
}).trim();
|
|
29
|
+
const name = basename(toplevel);
|
|
30
|
+
return sanitizeTag(name) ?? "_unknown";
|
|
31
|
+
} catch {
|
|
32
|
+
// Not a git repo — fall back to cwd
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const name = basename(process.cwd());
|
|
36
|
+
return sanitizeTag(name) ?? "_unknown";
|
|
37
|
+
} catch {
|
|
38
|
+
return "_unknown";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getRepoInfo(): { display: string; branch?: string } | null {
|
|
43
|
+
const branch = git("rev-parse --abbrev-ref HEAD");
|
|
44
|
+
const safeBranch = branch && branch !== "HEAD" ? branch : undefined;
|
|
45
|
+
|
|
46
|
+
const originUrl = git("remote get-url origin");
|
|
47
|
+
const orgRepo = parseRemoteUrl(originUrl);
|
|
48
|
+
if (orgRepo) {
|
|
49
|
+
return { display: orgRepo, branch: safeBranch };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const topLevel = git("rev-parse --show-toplevel");
|
|
53
|
+
const repoName = getDirName(topLevel);
|
|
54
|
+
if (repoName) {
|
|
55
|
+
return { display: repoName, branch: safeBranch };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cwdName = getDirName(process.cwd());
|
|
59
|
+
if (cwdName) {
|
|
60
|
+
return { display: cwdName };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document and reference handlers (Node.js equivalents of packages/server/reference-handlers.ts).
|
|
3
|
+
* VaultNode, buildFileTree, walkMarkdownFiles, handleDocRequest,
|
|
4
|
+
* detectObsidianVaults, handleObsidian*, handleFileBrowserRequest
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
statSync,
|
|
12
|
+
type Dirent,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import type { ServerResponse } from "node:http";
|
|
15
|
+
import { join, resolve as resolvePath } from "node:path";
|
|
16
|
+
|
|
17
|
+
import { json, parseBody } from "./helpers";
|
|
18
|
+
import type { IncomingMessage } from "node:http";
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
type VaultNode,
|
|
22
|
+
buildFileTree,
|
|
23
|
+
FILE_BROWSER_EXCLUDED,
|
|
24
|
+
} from "../generated/reference-common.js";
|
|
25
|
+
import { detectObsidianVaults } from "../generated/integrations-common.js";
|
|
26
|
+
import {
|
|
27
|
+
isAbsoluteUserPath,
|
|
28
|
+
isCodeFilePath,
|
|
29
|
+
resolveCodeFile,
|
|
30
|
+
resolveMarkdownFile,
|
|
31
|
+
resolveUserPath,
|
|
32
|
+
isWithinProjectRoot,
|
|
33
|
+
warmFileListCache,
|
|
34
|
+
} from "../generated/resolve-file.js";
|
|
35
|
+
import { parseCodePath } from "../generated/code-file.js";
|
|
36
|
+
import { htmlToMarkdown } from "../generated/html-to-markdown.js";
|
|
37
|
+
import { preloadFile } from "@pierre/diffs/ssr";
|
|
38
|
+
|
|
39
|
+
type Res = ServerResponse;
|
|
40
|
+
|
|
41
|
+
/** Recursively walk a directory collecting files by extension, skipping ignored dirs. */
|
|
42
|
+
function walkMarkdownFiles(dir: string, root: string, results: string[], extensions: RegExp = /\.(mdx?|html?)$/i): void {
|
|
43
|
+
let entries: Dirent[];
|
|
44
|
+
try {
|
|
45
|
+
entries = readdirSync(dir, { withFileTypes: true }) as Dirent[];
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
if (FILE_BROWSER_EXCLUDED.includes(entry.name + "/")) continue;
|
|
52
|
+
walkMarkdownFiles(join(dir, entry.name), root, results, extensions);
|
|
53
|
+
} else if (entry.isFile() && extensions.test(entry.name)) {
|
|
54
|
+
const relative = join(dir, entry.name)
|
|
55
|
+
.slice(root.length + 1)
|
|
56
|
+
.replace(/\\/g, "/");
|
|
57
|
+
results.push(relative);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Serve a linked markdown document. Uses shared resolveMarkdownFile for parity with Bun server. */
|
|
63
|
+
export async function handleDocRequest(res: Res, url: URL): Promise<void> {
|
|
64
|
+
const requestedPath = url.searchParams.get("path");
|
|
65
|
+
if (!requestedPath) {
|
|
66
|
+
json(res, { error: "Missing path parameter" }, 400);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Side-channel: warm the code-file walk so /api/doc/exists POSTs land warm.
|
|
71
|
+
void warmFileListCache(process.cwd(), "code");
|
|
72
|
+
|
|
73
|
+
// Try resolving relative to base directory first (used by annotate mode).
|
|
74
|
+
// No isWithinProjectRoot check here — intentional, matches pre-existing
|
|
75
|
+
// markdown behavior. The base param is set server-side by the annotate
|
|
76
|
+
// server (see serverAnnotate.ts /api/doc route). The standalone HTML
|
|
77
|
+
// block below (no base) retains its cwd-based containment check.
|
|
78
|
+
const base = url.searchParams.get("base");
|
|
79
|
+
const resolvedBase = base ? resolveUserPath(base) : null;
|
|
80
|
+
if (
|
|
81
|
+
resolvedBase &&
|
|
82
|
+
!isAbsoluteUserPath(requestedPath) &&
|
|
83
|
+
/\.(mdx?|html?)$/i.test(requestedPath)
|
|
84
|
+
) {
|
|
85
|
+
const fromBase = resolveUserPath(requestedPath, resolvedBase);
|
|
86
|
+
try {
|
|
87
|
+
if (existsSync(fromBase)) {
|
|
88
|
+
const raw = readFileSync(fromBase, "utf-8");
|
|
89
|
+
const isHtml = /\.html?$/i.test(requestedPath);
|
|
90
|
+
const markdown = isHtml ? htmlToMarkdown(raw) : raw;
|
|
91
|
+
json(res, { markdown, filepath: fromBase, isConverted: isHtml });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
/* fall through to standard resolution */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// HTML files: resolve directly (not via resolveMarkdownFile which only handles .md/.mdx)
|
|
100
|
+
const projectRoot = process.cwd();
|
|
101
|
+
if (/\.html?$/i.test(requestedPath)) {
|
|
102
|
+
const resolvedHtml = resolveUserPath(requestedPath, resolvedBase || projectRoot);
|
|
103
|
+
if (!isWithinProjectRoot(resolvedHtml, projectRoot)) {
|
|
104
|
+
json(res, { error: "Access denied: path is outside project root" }, 403);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
if (existsSync(resolvedHtml)) {
|
|
109
|
+
const html = readFileSync(resolvedHtml, "utf-8");
|
|
110
|
+
json(res, { markdown: htmlToMarkdown(html), filepath: resolvedHtml, isConverted: true });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
} catch { /* fall through to 404 */ }
|
|
114
|
+
json(res, { error: `File not found: ${requestedPath}` }, 404);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Code files: try literal resolve first; on miss, fall back to smart resolver.
|
|
119
|
+
if (isCodeFilePath(requestedPath)) {
|
|
120
|
+
const parsed = parseCodePath(requestedPath);
|
|
121
|
+
const cleanPath = parsed.filePath;
|
|
122
|
+
const literalPath = resolveUserPath(cleanPath, resolvedBase || projectRoot);
|
|
123
|
+
const literalAllowed = resolvedBase || isWithinProjectRoot(literalPath, projectRoot);
|
|
124
|
+
|
|
125
|
+
let resolvedCode: string | null = null;
|
|
126
|
+
if (literalAllowed && existsSync(literalPath)) {
|
|
127
|
+
resolvedCode = literalPath;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!resolvedCode) {
|
|
131
|
+
const result = await resolveCodeFile(cleanPath, projectRoot);
|
|
132
|
+
if (result.kind === "found") {
|
|
133
|
+
resolvedCode = result.path;
|
|
134
|
+
} else if (result.kind === "ambiguous") {
|
|
135
|
+
const prefix = `${projectRoot}/`;
|
|
136
|
+
const relative = result.matches.map((m: string) =>
|
|
137
|
+
m.startsWith(prefix) ? m.slice(prefix.length) : m,
|
|
138
|
+
);
|
|
139
|
+
json(res, { error: `Ambiguous path '${requestedPath}'`, matches: relative }, 400);
|
|
140
|
+
return;
|
|
141
|
+
} else {
|
|
142
|
+
json(res, { error: `File not found: ${requestedPath}` }, 404);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!isWithinProjectRoot(resolvedCode, projectRoot)) {
|
|
146
|
+
json(res, { error: "Access denied: path is outside project root" }, 403);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const stat = statSync(resolvedCode);
|
|
153
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
154
|
+
json(res, { error: "File too large (max 2MB)" }, 413);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const contents = readFileSync(resolvedCode, "utf-8");
|
|
158
|
+
const displayName = resolvedCode.split("/").pop() || resolvedCode;
|
|
159
|
+
let prerenderedHTML: string | undefined;
|
|
160
|
+
try {
|
|
161
|
+
const result = await preloadFile({
|
|
162
|
+
file: { name: displayName, contents },
|
|
163
|
+
options: { disableFileHeader: true },
|
|
164
|
+
});
|
|
165
|
+
prerenderedHTML = result.prerenderedHTML;
|
|
166
|
+
} catch {
|
|
167
|
+
// Fall back to client-side rendering
|
|
168
|
+
}
|
|
169
|
+
json(res, { codeFile: true, contents, filepath: resolvedCode, prerenderedHTML, line: parsed.line, lineEnd: parsed.lineEnd });
|
|
170
|
+
return;
|
|
171
|
+
} catch {
|
|
172
|
+
json(res, { error: `File not found: ${requestedPath}` }, 404);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = resolveMarkdownFile(requestedPath, projectRoot);
|
|
178
|
+
|
|
179
|
+
if (result.kind === "ambiguous") {
|
|
180
|
+
json(
|
|
181
|
+
res,
|
|
182
|
+
{
|
|
183
|
+
error: `Ambiguous filename '${result.input}': found ${result.matches.length} matches`,
|
|
184
|
+
matches: result.matches,
|
|
185
|
+
},
|
|
186
|
+
400,
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (result.kind === "not_found" || result.kind === "unavailable") {
|
|
192
|
+
json(res, { error: `File not found: ${result.input}` }, 404);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const markdown = readFileSync(result.path, "utf-8");
|
|
198
|
+
json(res, { markdown, filepath: result.path });
|
|
199
|
+
} catch {
|
|
200
|
+
json(res, { error: "Failed to read file" }, 500);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Batch existence check for code-file paths the renderer wants to linkify.
|
|
206
|
+
* POST /api/doc/exists with { paths: string[] }.
|
|
207
|
+
*
|
|
208
|
+
* TODO(security): see packages/server/reference-handlers.ts handleDocExists —
|
|
209
|
+
* both absolute paths in `paths[]` AND the `base` field are honored verbatim
|
|
210
|
+
* with no project-root containment check, leaking file existence back to the
|
|
211
|
+
* caller. Fix in lockstep with the Bun handler.
|
|
212
|
+
*/
|
|
213
|
+
export async function handleDocExistsRequest(res: Res, req: IncomingMessage): Promise<void> {
|
|
214
|
+
const body = await parseBody(req);
|
|
215
|
+
const paths = (body as { paths?: unknown }).paths;
|
|
216
|
+
if (!Array.isArray(paths) || !paths.every((p) => typeof p === "string")) {
|
|
217
|
+
json(res, { error: "Expected { paths: string[] }" }, 400);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (paths.length > 500) {
|
|
221
|
+
json(res, { error: "Too many paths (max 500)" }, 400);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const baseRaw = (body as { base?: unknown }).base;
|
|
225
|
+
const baseDir = typeof baseRaw === "string" && baseRaw.length > 0
|
|
226
|
+
? resolveUserPath(baseRaw)
|
|
227
|
+
: undefined;
|
|
228
|
+
|
|
229
|
+
const projectRoot = process.cwd();
|
|
230
|
+
const results: Record<
|
|
231
|
+
string,
|
|
232
|
+
| { status: "found"; resolved: string }
|
|
233
|
+
| { status: "ambiguous"; matches: string[] }
|
|
234
|
+
| { status: "missing" }
|
|
235
|
+
| { status: "unavailable" }
|
|
236
|
+
> = {};
|
|
237
|
+
|
|
238
|
+
await Promise.all(
|
|
239
|
+
(paths as string[]).map(async (p) => {
|
|
240
|
+
const cleanP = parseCodePath(p).filePath;
|
|
241
|
+
const r = await resolveCodeFile(cleanP, projectRoot, baseDir);
|
|
242
|
+
if (r.kind === "found") {
|
|
243
|
+
results[p] = { status: "found", resolved: r.path };
|
|
244
|
+
} else if (r.kind === "ambiguous") {
|
|
245
|
+
const prefix = `${projectRoot}/`;
|
|
246
|
+
results[p] = {
|
|
247
|
+
status: "ambiguous",
|
|
248
|
+
matches: r.matches.map((m: string) => (m.startsWith(prefix) ? m.slice(prefix.length) : m)),
|
|
249
|
+
};
|
|
250
|
+
} else if (r.kind === "unavailable") {
|
|
251
|
+
results[p] = { status: "unavailable" };
|
|
252
|
+
} else {
|
|
253
|
+
results[p] = { status: "missing" };
|
|
254
|
+
}
|
|
255
|
+
}),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
json(res, { results });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function handleObsidianVaultsRequest(res: Res): void {
|
|
262
|
+
json(res, { vaults: detectObsidianVaults() });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function handleObsidianFilesRequest(res: Res, url: URL): void {
|
|
266
|
+
const vaultPath = url.searchParams.get("vaultPath");
|
|
267
|
+
if (!vaultPath) {
|
|
268
|
+
json(res, { error: "Missing vaultPath parameter" }, 400);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const resolvedVault = resolveUserPath(vaultPath);
|
|
272
|
+
if (!existsSync(resolvedVault) || !statSync(resolvedVault).isDirectory()) {
|
|
273
|
+
json(res, { error: "Invalid vault path" }, 400);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const files: string[] = [];
|
|
278
|
+
walkMarkdownFiles(resolvedVault, resolvedVault, files, /\.mdx?$/i);
|
|
279
|
+
files.sort();
|
|
280
|
+
json(res, { tree: buildFileTree(files) });
|
|
281
|
+
} catch {
|
|
282
|
+
json(res, { error: "Failed to list vault files" }, 500);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function handleObsidianDocRequest(res: Res, url: URL): void {
|
|
287
|
+
const vaultPath = url.searchParams.get("vaultPath");
|
|
288
|
+
const filePath = url.searchParams.get("path");
|
|
289
|
+
if (!vaultPath || !filePath) {
|
|
290
|
+
json(res, { error: "Missing vaultPath or path parameter" }, 400);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!/\.mdx?$/i.test(filePath)) {
|
|
294
|
+
json(res, { error: "Only markdown files are supported" }, 400);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const resolvedVault = resolveUserPath(vaultPath);
|
|
298
|
+
let resolvedFile = resolvePath(resolvedVault, filePath);
|
|
299
|
+
|
|
300
|
+
// Bare filename search within vault
|
|
301
|
+
if (!existsSync(resolvedFile) && !filePath.includes("/")) {
|
|
302
|
+
const files: string[] = [];
|
|
303
|
+
walkMarkdownFiles(resolvedVault, resolvedVault, files, /\.mdx?$/i);
|
|
304
|
+
const matches = files.filter(
|
|
305
|
+
(f) => f.split("/").pop()!.toLowerCase() === filePath.toLowerCase(),
|
|
306
|
+
);
|
|
307
|
+
if (matches.length === 1) {
|
|
308
|
+
resolvedFile = resolvePath(resolvedVault, matches[0]);
|
|
309
|
+
} else if (matches.length > 1) {
|
|
310
|
+
json(
|
|
311
|
+
res,
|
|
312
|
+
{
|
|
313
|
+
error: `Ambiguous filename '${filePath}': found ${matches.length} matches`,
|
|
314
|
+
matches,
|
|
315
|
+
},
|
|
316
|
+
400,
|
|
317
|
+
);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Security: must be within vault
|
|
323
|
+
if (
|
|
324
|
+
!resolvedFile.startsWith(resolvedVault + "/") &&
|
|
325
|
+
resolvedFile !== resolvedVault
|
|
326
|
+
) {
|
|
327
|
+
json(res, { error: "Access denied: path is outside vault" }, 403);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!existsSync(resolvedFile)) {
|
|
332
|
+
json(res, { error: `File not found: ${filePath}` }, 404);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
const markdown = readFileSync(resolvedFile, "utf-8");
|
|
337
|
+
json(res, { markdown, filepath: resolvedFile });
|
|
338
|
+
} catch {
|
|
339
|
+
json(res, { error: "Failed to read file" }, 500);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function handleFileBrowserRequest(res: Res, url: URL): void {
|
|
344
|
+
const dirPath = url.searchParams.get("dirPath");
|
|
345
|
+
if (!dirPath) {
|
|
346
|
+
json(res, { error: "Missing dirPath parameter" }, 400);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const resolvedDir = resolveUserPath(dirPath);
|
|
350
|
+
if (!existsSync(resolvedDir) || !statSync(resolvedDir).isDirectory()) {
|
|
351
|
+
json(res, { error: "Invalid directory path" }, 400);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
const files: string[] = [];
|
|
356
|
+
walkMarkdownFiles(resolvedDir, resolvedDir, files);
|
|
357
|
+
files.sort();
|
|
358
|
+
json(res, { tree: buildFileTree(files) });
|
|
359
|
+
} catch {
|
|
360
|
+
json(res, { error: "Failed to list directory files" }, 500);
|
|
361
|
+
}
|
|
362
|
+
}
|