@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.
Files changed (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Editor annotation handler (in-memory store for VS Code integration).
3
+ * EditorAnnotation type, createEditorAnnotationHandler
4
+ */
5
+
6
+ import { randomUUID } from "node:crypto";
7
+ import type { IncomingMessage } from "node:http";
8
+ import { json, parseBody } from "./helpers";
9
+
10
+ interface EditorAnnotation {
11
+ id: string;
12
+ filePath: string;
13
+ selectedText: string;
14
+ lineStart: number;
15
+ lineEnd: number;
16
+ comment?: string;
17
+ createdAt: number;
18
+ }
19
+
20
+ export function createEditorAnnotationHandler() {
21
+ const annotations: EditorAnnotation[] = [];
22
+
23
+ return {
24
+ async handle(
25
+ req: IncomingMessage,
26
+ res: import("node:http").ServerResponse,
27
+ url: URL,
28
+ ): Promise<boolean> {
29
+ if (url.pathname === "/api/editor-annotations" && req.method === "GET") {
30
+ json(res, { annotations });
31
+ return true;
32
+ }
33
+
34
+ if (url.pathname === "/api/editor-annotation" && req.method === "POST") {
35
+ try {
36
+ const body = await parseBody(req);
37
+ if (
38
+ !body.filePath ||
39
+ !body.selectedText ||
40
+ !body.lineStart ||
41
+ !body.lineEnd
42
+ ) {
43
+ json(res, { error: "Missing required fields" }, 400);
44
+ return true;
45
+ }
46
+
47
+ const annotation: EditorAnnotation = {
48
+ id: randomUUID(),
49
+ filePath: String(body.filePath),
50
+ selectedText: String(body.selectedText),
51
+ lineStart: Number(body.lineStart),
52
+ lineEnd: Number(body.lineEnd),
53
+ comment: typeof body.comment === "string" ? body.comment : undefined,
54
+ createdAt: Date.now(),
55
+ };
56
+
57
+ annotations.push(annotation);
58
+ json(res, { id: annotation.id });
59
+ } catch {
60
+ json(res, { error: "Invalid JSON" }, 400);
61
+ }
62
+ return true;
63
+ }
64
+
65
+ if (
66
+ url.pathname === "/api/editor-annotation" &&
67
+ req.method === "DELETE"
68
+ ) {
69
+ const id = url.searchParams.get("id");
70
+ if (!id) {
71
+ json(res, { error: "Missing id parameter" }, 400);
72
+ return true;
73
+ }
74
+ const idx = annotations.findIndex((annotation) => annotation.id === id);
75
+ if (idx !== -1) {
76
+ annotations.splice(idx, 1);
77
+ }
78
+ json(res, { ok: true });
79
+ return true;
80
+ }
81
+
82
+ return false;
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * External Annotations — Pi (node:http) server handler.
3
+ *
4
+ * Thin HTTP adapter over the shared annotation store. Mirrors the Bun
5
+ * handler at packages/server/external-annotations.ts but uses node:http
6
+ * IncomingMessage/ServerResponse + res.write() for SSE.
7
+ */
8
+
9
+ import type { IncomingMessage, ServerResponse } from "node:http";
10
+ import {
11
+ createAnnotationStore,
12
+ transformPlanInput,
13
+ transformReviewInput,
14
+ serializeSSEEvent,
15
+ HEARTBEAT_COMMENT,
16
+ HEARTBEAT_INTERVAL_MS,
17
+ type StorableAnnotation,
18
+ type ExternalAnnotationEvent,
19
+ } from "../generated/external-annotation.js";
20
+ import { json, parseBody } from "./helpers.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Route prefix
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const BASE = "/api/external-annotations";
27
+ const STREAM = `${BASE}/stream`;
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Factory
31
+ // ---------------------------------------------------------------------------
32
+
33
+ export function createExternalAnnotationHandler(mode: "plan" | "review") {
34
+ const store = createAnnotationStore<StorableAnnotation>();
35
+ const subscribers = new Set<ServerResponse>();
36
+ const transform = mode === "plan" ? transformPlanInput : transformReviewInput;
37
+
38
+ // Wire store mutations → SSE broadcast
39
+ store.onMutation((event: ExternalAnnotationEvent<StorableAnnotation>) => {
40
+ const data = serializeSSEEvent(event);
41
+ for (const res of subscribers) {
42
+ try {
43
+ res.write(data);
44
+ } catch {
45
+ // Response closed — clean up
46
+ subscribers.delete(res);
47
+ }
48
+ }
49
+ });
50
+
51
+ return {
52
+ /** Push annotations directly into the store (bypasses HTTP, reuses same validation). */
53
+ addAnnotations(body: unknown): { ids: string[] } | { error: string } {
54
+ const parsed = transform(body);
55
+ if ("error" in parsed) return { error: parsed.error };
56
+ const created = store.add(parsed.annotations);
57
+ return { ids: created.map((a: { id: string }) => a.id) };
58
+ },
59
+
60
+ async handle(
61
+ req: IncomingMessage,
62
+ res: ServerResponse,
63
+ url: URL,
64
+ ): Promise<boolean> {
65
+ // --- SSE stream ---
66
+ if (url.pathname === STREAM && req.method === "GET") {
67
+ res.writeHead(200, {
68
+ "Content-Type": "text/event-stream",
69
+ "Cache-Control": "no-cache",
70
+ Connection: "keep-alive",
71
+ });
72
+
73
+ // Disable idle timeout for SSE connections
74
+ res.setTimeout(0);
75
+
76
+ // Send current state as snapshot
77
+ const snapshot: ExternalAnnotationEvent<StorableAnnotation> = {
78
+ type: "snapshot",
79
+ annotations: store.getAll(),
80
+ };
81
+ res.write(serializeSSEEvent(snapshot));
82
+
83
+ subscribers.add(res);
84
+
85
+ // Heartbeat to keep connection alive
86
+ const heartbeatTimer = setInterval(() => {
87
+ try {
88
+ res.write(HEARTBEAT_COMMENT);
89
+ } catch {
90
+ clearInterval(heartbeatTimer);
91
+ subscribers.delete(res);
92
+ }
93
+ }, HEARTBEAT_INTERVAL_MS);
94
+
95
+ // Clean up on disconnect
96
+ res.on("close", () => {
97
+ clearInterval(heartbeatTimer);
98
+ subscribers.delete(res);
99
+ });
100
+
101
+ // Don't end the response — SSE stays open
102
+ return true;
103
+ }
104
+
105
+ // --- GET snapshot (polling fallback) ---
106
+ if (url.pathname === BASE && req.method === "GET") {
107
+ const since = url.searchParams.get("since");
108
+ if (since !== null) {
109
+ const sinceVersion = parseInt(since, 10);
110
+ if (!isNaN(sinceVersion) && sinceVersion === store.version) {
111
+ res.writeHead(304);
112
+ res.end();
113
+ return true;
114
+ }
115
+ }
116
+ json(res, {
117
+ annotations: store.getAll(),
118
+ version: store.version,
119
+ });
120
+ return true;
121
+ }
122
+
123
+ // --- POST (add single or batch) ---
124
+ if (url.pathname === BASE && req.method === "POST") {
125
+ try {
126
+ const body = await parseBody(req);
127
+ const parsed = transform(body);
128
+
129
+ if ("error" in parsed) {
130
+ json(res, { error: parsed.error }, 400);
131
+ return true;
132
+ }
133
+
134
+ const created = store.add(parsed.annotations);
135
+ json(res, { ids: created.map((a: StorableAnnotation) => a.id) }, 201);
136
+ } catch {
137
+ json(res, { error: "Invalid JSON" }, 400);
138
+ }
139
+ return true;
140
+ }
141
+
142
+ // --- PATCH (update fields on a single annotation) ---
143
+ if (url.pathname === BASE && req.method === "PATCH") {
144
+ const id = url.searchParams.get("id");
145
+ if (!id) {
146
+ json(res, { error: "Missing ?id parameter" }, 400);
147
+ return true;
148
+ }
149
+ try {
150
+ const body = await parseBody(req);
151
+ const updated = store.update(id, body as Partial<StorableAnnotation>);
152
+ if (!updated) {
153
+ json(res, { error: "Not found" }, 404);
154
+ return true;
155
+ }
156
+ json(res, { annotation: updated });
157
+ } catch {
158
+ json(res, { error: "Invalid JSON" }, 400);
159
+ }
160
+ return true;
161
+ }
162
+
163
+ // --- DELETE (by id, by source, or clear all) ---
164
+ if (url.pathname === BASE && req.method === "DELETE") {
165
+ const id = url.searchParams.get("id");
166
+ const source = url.searchParams.get("source");
167
+
168
+ if (id) {
169
+ store.remove(id);
170
+ json(res, { ok: true });
171
+ return true;
172
+ }
173
+
174
+ if (source) {
175
+ const count = store.clearBySource(source);
176
+ json(res, { ok: true, removed: count });
177
+ return true;
178
+ }
179
+
180
+ const count = store.clearAll();
181
+ json(res, { ok: true, removed: count });
182
+ return true;
183
+ }
184
+
185
+ // Not handled — pass through
186
+ return false;
187
+ },
188
+ };
189
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Shared request handlers reused across plan, review, and annotate servers.
3
+ * handleImageRequest, handleUploadRequest, handleDraftRequest, handleFavicon
4
+ */
5
+
6
+ import { randomUUID } from "node:crypto";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
8
+ import type { IncomingMessage } from "node:http";
9
+ import { tmpdir } from "node:os";
10
+ import { join, resolve as resolvePath } from "node:path";
11
+ import { saveDraft, loadDraft, deleteDraft } from "../generated/draft.js";
12
+ import { FAVICON_SVG } from "../generated/favicon.js";
13
+
14
+ import { json, parseBody, send, toWebRequest } from "./helpers";
15
+
16
+ type Res = import("node:http").ServerResponse;
17
+
18
+ const ALLOWED_IMAGE_EXTENSIONS = new Set([
19
+ "png",
20
+ "jpg",
21
+ "jpeg",
22
+ "gif",
23
+ "webp",
24
+ "svg",
25
+ "bmp",
26
+ "ico",
27
+ "tiff",
28
+ "tif",
29
+ "avif",
30
+ ]);
31
+
32
+ const IMAGE_CONTENT_TYPES: Record<string, string> = {
33
+ png: "image/png",
34
+ jpg: "image/jpeg",
35
+ jpeg: "image/jpeg",
36
+ gif: "image/gif",
37
+ webp: "image/webp",
38
+ svg: "image/svg+xml",
39
+ bmp: "image/bmp",
40
+ ico: "image/x-icon",
41
+ tiff: "image/tiff",
42
+ tif: "image/tiff",
43
+ avif: "image/avif",
44
+ };
45
+
46
+ const UPLOAD_DIR = join(tmpdir(), "plannotator");
47
+
48
+ function getExtension(filePath: string): string {
49
+ const lastDot = filePath.lastIndexOf(".");
50
+ if (lastDot === -1) return "";
51
+ return filePath.slice(lastDot + 1).toLowerCase();
52
+ }
53
+
54
+ function validateImagePath(rawPath: string): {
55
+ valid: boolean;
56
+ resolved: string;
57
+ error?: string;
58
+ } {
59
+ const resolved = resolvePath(rawPath);
60
+ const ext = getExtension(resolved);
61
+
62
+ if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
63
+ return {
64
+ valid: false,
65
+ resolved,
66
+ error: "Path does not point to a supported image file",
67
+ };
68
+ }
69
+
70
+ return { valid: true, resolved };
71
+ }
72
+
73
+ function validateUploadExtension(fileName: string): {
74
+ valid: boolean;
75
+ ext: string;
76
+ error?: string;
77
+ } {
78
+ const ext = getExtension(fileName) || "png";
79
+ if (!ALLOWED_IMAGE_EXTENSIONS.has(ext)) {
80
+ return {
81
+ valid: false,
82
+ ext,
83
+ error: `File extension ".${ext}" is not a supported image type`,
84
+ };
85
+ }
86
+
87
+ return { valid: true, ext };
88
+ }
89
+
90
+ function getImageContentType(filePath: string): string {
91
+ return (
92
+ IMAGE_CONTENT_TYPES[getExtension(filePath)] || "application/octet-stream"
93
+ );
94
+ }
95
+
96
+ export function handleImageRequest(res: Res, url: URL): void {
97
+ const imagePath = url.searchParams.get("path");
98
+ if (!imagePath) {
99
+ send(res, "Missing path parameter", 400, { "Content-Type": "text/plain" });
100
+ return;
101
+ }
102
+
103
+ const tryServePath = (candidate: string): boolean => {
104
+ const validation = validateImagePath(candidate);
105
+ if (!validation.valid) return false;
106
+ try {
107
+ if (!existsSync(validation.resolved)) return false;
108
+ const data = readFileSync(validation.resolved);
109
+ send(res, data, 200, {
110
+ "Content-Type": getImageContentType(validation.resolved),
111
+ });
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ };
117
+
118
+ if (tryServePath(imagePath)) return;
119
+
120
+ const base = url.searchParams.get("base");
121
+ if (
122
+ base &&
123
+ !imagePath.startsWith("/") &&
124
+ tryServePath(resolvePath(base, imagePath))
125
+ ) {
126
+ return;
127
+ }
128
+
129
+ const validation = validateImagePath(imagePath);
130
+ if (!validation.valid) {
131
+ send(res, validation.error || "Invalid image path", 403, {
132
+ "Content-Type": "text/plain",
133
+ });
134
+ return;
135
+ }
136
+
137
+ send(res, "File not found", 404, { "Content-Type": "text/plain" });
138
+ }
139
+
140
+ export async function handleUploadRequest(
141
+ req: IncomingMessage,
142
+ res: Res,
143
+ ): Promise<void> {
144
+ try {
145
+ const request = toWebRequest(req);
146
+ const formData = await request.formData();
147
+ const file = formData.get("file");
148
+ if (
149
+ !file ||
150
+ typeof file !== "object" ||
151
+ !("arrayBuffer" in file) ||
152
+ !("name" in file)
153
+ ) {
154
+ json(res, { error: "No file provided" }, 400);
155
+ return;
156
+ }
157
+
158
+ const upload = file as File;
159
+ const extResult = validateUploadExtension(upload.name);
160
+ if (!extResult.valid) {
161
+ json(res, { error: extResult.error }, 400);
162
+ return;
163
+ }
164
+
165
+ mkdirSync(UPLOAD_DIR, { recursive: true });
166
+ const tempPath = join(UPLOAD_DIR, `${randomUUID()}.${extResult.ext}`);
167
+ const bytes = Buffer.from(await upload.arrayBuffer());
168
+ writeFileSync(tempPath, bytes);
169
+ json(res, { path: tempPath, originalName: upload.name });
170
+ } catch (err) {
171
+ const message = err instanceof Error ? err.message : "Upload failed";
172
+ json(res, { error: message }, 500);
173
+ }
174
+ }
175
+
176
+ export function handleDraftRequest(
177
+ req: IncomingMessage,
178
+ res: Res,
179
+ draftKey: string,
180
+ ): Promise<void> | void {
181
+ if (req.method === "POST") {
182
+ return parseBody(req)
183
+ .then((body) => {
184
+ saveDraft(draftKey, body);
185
+ json(res, { ok: true });
186
+ })
187
+ .catch((err: unknown) => {
188
+ const message = err instanceof Error ? err.message : "Failed to save draft";
189
+ console.error(`[draft] save failed: ${message}`);
190
+ json(res, { error: message }, 500);
191
+ });
192
+ } else if (req.method === "DELETE") {
193
+ deleteDraft(draftKey);
194
+ json(res, { ok: true });
195
+ } else {
196
+ const draft = loadDraft(draftKey);
197
+ if (!draft) {
198
+ json(res, { found: false }, 404);
199
+ return;
200
+ }
201
+ json(res, draft);
202
+ }
203
+ }
204
+
205
+ export function handleFavicon(res: Res): void {
206
+ send(res, FAVICON_SVG, 200, {
207
+ "Content-Type": "image/svg+xml",
208
+ "Cache-Control": "public, max-age=86400",
209
+ });
210
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Core HTTP helpers for Pi extension servers.
3
+ * parseBody, json, html, send, toWebRequest
4
+ */
5
+
6
+ import type { IncomingMessage } from "node:http";
7
+ import { Readable } from "node:stream";
8
+
9
+ export function parseBody(
10
+ req: IncomingMessage,
11
+ ): Promise<Record<string, unknown>> {
12
+ return new Promise((resolve) => {
13
+ let data = "";
14
+ req.on("data", (chunk: string) => (data += chunk));
15
+ req.on("end", () => {
16
+ try {
17
+ resolve(JSON.parse(data));
18
+ } catch {
19
+ resolve({});
20
+ }
21
+ });
22
+ });
23
+ }
24
+
25
+ export function json(
26
+ res: import("node:http").ServerResponse,
27
+ data: unknown,
28
+ status = 200,
29
+ ): void {
30
+ res.writeHead(status, { "Content-Type": "application/json" });
31
+ res.end(JSON.stringify(data));
32
+ }
33
+
34
+ export function html(
35
+ res: import("node:http").ServerResponse,
36
+ content: string,
37
+ ): void {
38
+ res.writeHead(200, { "Content-Type": "text/html" });
39
+ res.end(content);
40
+ }
41
+
42
+ export function send(
43
+ res: import("node:http").ServerResponse,
44
+ body: string | Buffer,
45
+ status = 200,
46
+ headers: Record<string, string> = {},
47
+ ): void {
48
+ res.writeHead(status, headers);
49
+ res.end(body);
50
+ }
51
+
52
+ export function requestUrl(req: IncomingMessage): URL {
53
+ return new URL(req.url ?? "/", "http://localhost");
54
+ }
55
+
56
+ export function toWebRequest(req: IncomingMessage): Request {
57
+ const headers = new Headers();
58
+ for (const [key, value] of Object.entries(req.headers)) {
59
+ if (value === undefined) continue;
60
+ if (Array.isArray(value)) {
61
+ for (const item of value) headers.append(key, item);
62
+ } else {
63
+ headers.set(key, value);
64
+ }
65
+ }
66
+
67
+ const init: RequestInit & { duplex?: "half" } = {
68
+ method: req.method,
69
+ headers,
70
+ };
71
+
72
+ if (req.method !== "GET" && req.method !== "HEAD") {
73
+ init.body = Readable.toWeb(req) as unknown as BodyInit;
74
+ init.duplex = "half";
75
+ }
76
+
77
+ return new Request(`http://localhost${req.url ?? "/"}`, init);
78
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * IDE integration — open plan diffs in VS Code.
3
+ * Node.js equivalent of packages/server/ide.ts.
4
+ */
5
+
6
+ import { spawn } from "node:child_process";
7
+
8
+ /** Open two files in VS Code's diff viewer. Node.js equivalent of packages/server/ide.ts */
9
+ export function openEditorDiff(
10
+ oldPath: string,
11
+ newPath: string,
12
+ ): Promise<{ ok: true } | { error: string }> {
13
+ return new Promise((resolve) => {
14
+ const proc = spawn("code", ["--diff", oldPath, newPath], {
15
+ stdio: ["ignore", "ignore", "pipe"],
16
+ });
17
+ let stderr = "";
18
+ proc.stderr?.on("data", (chunk: Buffer) => {
19
+ stderr += chunk.toString();
20
+ });
21
+ proc.on("error", (err) => {
22
+ if (err.message.includes("ENOENT")) {
23
+ resolve({
24
+ error:
25
+ "VS Code CLI not found. Run 'Shell Command: Install code command in PATH' from the VS Code command palette.",
26
+ });
27
+ } else {
28
+ resolve({ error: err.message });
29
+ }
30
+ });
31
+ proc.on("close", (code) => {
32
+ if (code !== 0) {
33
+ if (stderr.includes("not found") || stderr.includes("ENOENT")) {
34
+ resolve({
35
+ error:
36
+ "VS Code CLI not found. Run 'Shell Command: Install code command in PATH' from the VS Code command palette.",
37
+ });
38
+ } else {
39
+ resolve({ error: `code --diff exited with ${code}: ${stderr}` });
40
+ }
41
+ } else {
42
+ resolve({ ok: true });
43
+ }
44
+ });
45
+ });
46
+ }