patchwork-os 0.2.0-alpha.0 → 0.2.0-alpha.11
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 +41 -46
- package/dist/bridge.js +23 -10
- package/dist/bridge.js.map +1 -1
- package/dist/claudeDriver.d.ts +3 -1
- package/dist/claudeDriver.js +48 -0
- package/dist/claudeDriver.js.map +1 -1
- package/dist/commands/dashboard.d.ts +47 -0
- package/dist/commands/dashboard.js +319 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +5 -2
- package/dist/config.js.map +1 -1
- package/dist/connectors/github.d.ts +94 -0
- package/dist/connectors/github.js +350 -0
- package/dist/connectors/github.js.map +1 -0
- package/dist/connectors/gmail.d.ts +40 -0
- package/dist/connectors/gmail.js +304 -0
- package/dist/connectors/gmail.js.map +1 -0
- package/dist/connectors/googleCalendar.d.ts +57 -0
- package/dist/connectors/googleCalendar.js +308 -0
- package/dist/connectors/googleCalendar.js.map +1 -0
- package/dist/connectors/linear.d.ts +117 -0
- package/dist/connectors/linear.js +248 -0
- package/dist/connectors/linear.js.map +1 -0
- package/dist/connectors/mcpClient.d.ts +56 -0
- package/dist/connectors/mcpClient.js +189 -0
- package/dist/connectors/mcpClient.js.map +1 -0
- package/dist/connectors/mcpOAuth.d.ts +83 -0
- package/dist/connectors/mcpOAuth.js +363 -0
- package/dist/connectors/mcpOAuth.js.map +1 -0
- package/dist/connectors/sentry.d.ts +43 -0
- package/dist/connectors/sentry.js +197 -0
- package/dist/connectors/sentry.js.map +1 -0
- package/dist/connectors/slack.d.ts +50 -0
- package/dist/connectors/slack.js +254 -0
- package/dist/connectors/slack.js.map +1 -0
- package/dist/drivers/claude/api.d.ts +11 -0
- package/dist/drivers/claude/api.js +54 -0
- package/dist/drivers/claude/api.js.map +1 -0
- package/dist/drivers/claude/envSanitizer.d.ts +7 -0
- package/dist/drivers/claude/envSanitizer.js +18 -0
- package/dist/drivers/claude/envSanitizer.js.map +1 -0
- package/dist/drivers/claude/streamParser.d.ts +38 -0
- package/dist/drivers/claude/streamParser.js +34 -0
- package/dist/drivers/claude/streamParser.js.map +1 -0
- package/dist/drivers/claude/subprocess.d.ts +19 -0
- package/dist/drivers/claude/subprocess.js +216 -0
- package/dist/drivers/claude/subprocess.js.map +1 -0
- package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
- package/dist/drivers/claude/subprocessSettings.js +55 -0
- package/dist/drivers/claude/subprocessSettings.js.map +1 -0
- package/dist/drivers/gemini/index.d.ts +14 -0
- package/dist/drivers/gemini/index.js +176 -0
- package/dist/drivers/gemini/index.js.map +1 -0
- package/dist/drivers/grok/index.d.ts +11 -0
- package/dist/drivers/grok/index.js +22 -0
- package/dist/drivers/grok/index.js.map +1 -0
- package/dist/drivers/index.d.ts +18 -0
- package/dist/drivers/index.js +31 -0
- package/dist/drivers/index.js.map +1 -0
- package/dist/drivers/openai/index.d.ts +24 -0
- package/dist/drivers/openai/index.js +110 -0
- package/dist/drivers/openai/index.js.map +1 -0
- package/dist/drivers/types.d.ts +72 -0
- package/dist/drivers/types.js +30 -0
- package/dist/drivers/types.js.map +1 -0
- package/dist/index.js +116 -22
- package/dist/index.js.map +1 -1
- package/dist/recipes/yamlRunner.d.ts +104 -0
- package/dist/recipes/yamlRunner.js +683 -0
- package/dist/recipes/yamlRunner.js.map +1 -0
- package/dist/recipesHttp.d.ts +13 -1
- package/dist/recipesHttp.js +9 -1
- package/dist/recipesHttp.js.map +1 -1
- package/dist/runLog.d.ts +5 -0
- package/dist/runLog.js +44 -0
- package/dist/runLog.js.map +1 -1
- package/dist/server.d.ts +3 -1
- package/dist/server.js +490 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/addLinearComment.d.ts +55 -0
- package/dist/tools/addLinearComment.js +70 -0
- package/dist/tools/addLinearComment.js.map +1 -0
- package/dist/tools/createLinearIssue.d.ts +84 -0
- package/dist/tools/createLinearIssue.js +146 -0
- package/dist/tools/createLinearIssue.js.map +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +4 -1
- package/dist/tools/ctxGetTaskContext.js +45 -2
- package/dist/tools/ctxGetTaskContext.js.map +1 -1
- package/dist/tools/fetchCalendarEvents.d.ts +94 -0
- package/dist/tools/fetchCalendarEvents.js +97 -0
- package/dist/tools/fetchCalendarEvents.js.map +1 -0
- package/dist/tools/fetchGithubIssue.d.ts +80 -0
- package/dist/tools/fetchGithubIssue.js +84 -0
- package/dist/tools/fetchGithubIssue.js.map +1 -0
- package/dist/tools/fetchGithubPR.d.ts +89 -0
- package/dist/tools/fetchGithubPR.js +96 -0
- package/dist/tools/fetchGithubPR.js.map +1 -0
- package/dist/tools/fetchLinearIssue.d.ts +112 -0
- package/dist/tools/fetchLinearIssue.js +129 -0
- package/dist/tools/fetchLinearIssue.js.map +1 -0
- package/dist/tools/fetchSentryIssue.d.ts +143 -0
- package/dist/tools/fetchSentryIssue.js +150 -0
- package/dist/tools/fetchSentryIssue.js.map +1 -0
- package/dist/tools/fetchSlackProfile.d.ts +43 -0
- package/dist/tools/fetchSlackProfile.js +43 -0
- package/dist/tools/fetchSlackProfile.js.map +1 -0
- package/dist/tools/getConnectorStatus.d.ts +58 -0
- package/dist/tools/getConnectorStatus.js +56 -0
- package/dist/tools/getConnectorStatus.js.map +1 -0
- package/dist/tools/github/index.d.ts +1 -1
- package/dist/tools/github/index.js +1 -1
- package/dist/tools/github/index.js.map +1 -1
- package/dist/tools/github/pr.d.ts +122 -0
- package/dist/tools/github/pr.js +152 -0
- package/dist/tools/github/pr.js.map +1 -1
- package/dist/tools/index.js +27 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/slackListChannels.d.ts +65 -0
- package/dist/tools/slackListChannels.js +70 -0
- package/dist/tools/slackListChannels.js.map +1 -0
- package/dist/tools/slackPostMessage.d.ts +57 -0
- package/dist/tools/slackPostMessage.js +72 -0
- package/dist/tools/slackPostMessage.js.map +1 -0
- package/dist/tools/updateLinearIssue.d.ts +89 -0
- package/dist/tools/updateLinearIssue.js +103 -0
- package/dist/tools/updateLinearIssue.js.map +1 -0
- package/package.json +1 -1
- package/scripts/start-all.sh +56 -19
- package/templates/recipes/ctx-loop-test.yaml +75 -0
- package/templates/recipes/gmail-health-check.yaml +19 -0
- package/templates/recipes/inbox-triage.yaml +15 -0
- package/templates/recipes/morning-brief-slack.yaml +54 -0
- package/templates/recipes/morning-brief.yaml +72 -0
- package/templates/recipes/sentry-to-linear.yaml +77 -0
- package/templates/scheduled-tasks/morning-brief/SKILL.md +37 -0
package/dist/server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import http from "node:http";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
3
5
|
import { WebSocket, WebSocketServer as WsServer } from "ws";
|
|
4
6
|
import { routeApprovalRequest } from "./approvalHttp.js";
|
|
5
7
|
import { getApprovalQueue } from "./approvalQueue.js";
|
|
@@ -353,6 +355,80 @@ export class Server extends EventEmitter {
|
|
|
353
355
|
res.end(JSON.stringify({ ok: true, v: PACKAGE_VERSION }));
|
|
354
356
|
return;
|
|
355
357
|
}
|
|
358
|
+
// ── Connector OAuth callbacks (unauthenticated — browser redirect from vendor) ──
|
|
359
|
+
if (parsedUrl.pathname === "/connections/github/callback" &&
|
|
360
|
+
req.method === "GET") {
|
|
361
|
+
void (async () => {
|
|
362
|
+
const { handleGithubCallback } = await import("./connectors/github.js");
|
|
363
|
+
const code = parsedUrl.searchParams.get("code");
|
|
364
|
+
const state = parsedUrl.searchParams.get("state");
|
|
365
|
+
const error = parsedUrl.searchParams.get("error");
|
|
366
|
+
const result = await handleGithubCallback(code, state, error);
|
|
367
|
+
res.writeHead(result.status, {
|
|
368
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
369
|
+
});
|
|
370
|
+
res.end(result.body);
|
|
371
|
+
})();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (parsedUrl.pathname === "/connections/linear/callback" &&
|
|
375
|
+
req.method === "GET") {
|
|
376
|
+
void (async () => {
|
|
377
|
+
const { handleLinearCallback } = await import("./connectors/linear.js");
|
|
378
|
+
const code = parsedUrl.searchParams.get("code");
|
|
379
|
+
const state = parsedUrl.searchParams.get("state");
|
|
380
|
+
const error = parsedUrl.searchParams.get("error");
|
|
381
|
+
const result = await handleLinearCallback(code, state, error);
|
|
382
|
+
res.writeHead(result.status, {
|
|
383
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
384
|
+
});
|
|
385
|
+
res.end(result.body);
|
|
386
|
+
})();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (parsedUrl.pathname === "/connections/sentry/callback" &&
|
|
390
|
+
req.method === "GET") {
|
|
391
|
+
void (async () => {
|
|
392
|
+
const { handleSentryCallback } = await import("./connectors/sentry.js");
|
|
393
|
+
const code = parsedUrl.searchParams.get("code");
|
|
394
|
+
const state = parsedUrl.searchParams.get("state");
|
|
395
|
+
const error = parsedUrl.searchParams.get("error");
|
|
396
|
+
const result = await handleSentryCallback(code, state, error);
|
|
397
|
+
res.writeHead(result.status, {
|
|
398
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
399
|
+
});
|
|
400
|
+
res.end(result.body);
|
|
401
|
+
})();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (parsedUrl.pathname === "/connections/google-calendar/callback" &&
|
|
405
|
+
req.method === "GET") {
|
|
406
|
+
void (async () => {
|
|
407
|
+
const { handleCalendarCallback } = await import("./connectors/googleCalendar.js");
|
|
408
|
+
const code = parsedUrl.searchParams.get("code");
|
|
409
|
+
const state = parsedUrl.searchParams.get("state");
|
|
410
|
+
const error = parsedUrl.searchParams.get("error");
|
|
411
|
+
const result = await handleCalendarCallback(code, state, error);
|
|
412
|
+
res.writeHead(result.status, {
|
|
413
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
414
|
+
});
|
|
415
|
+
res.end(result.body);
|
|
416
|
+
})();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (parsedUrl.pathname === "/connections/slack/callback" &&
|
|
420
|
+
req.method === "GET") {
|
|
421
|
+
void (async () => {
|
|
422
|
+
const { handleSlackCallback } = await import("./connectors/slack.js");
|
|
423
|
+
const code = parsedUrl.searchParams.get("code");
|
|
424
|
+
const state = parsedUrl.searchParams.get("state");
|
|
425
|
+
const error = parsedUrl.searchParams.get("error");
|
|
426
|
+
const result = await handleSlackCallback(code, state, error);
|
|
427
|
+
res.writeHead(result.status, { "Content-Type": result.contentType ?? "application/json" });
|
|
428
|
+
res.end(result.body);
|
|
429
|
+
})();
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
356
432
|
// ── Bearer token authentication ───────────────────────────────────────
|
|
357
433
|
// All other HTTP endpoints require a valid Bearer token.
|
|
358
434
|
// Accepts either:
|
|
@@ -643,6 +719,411 @@ export class Server extends EventEmitter {
|
|
|
643
719
|
});
|
|
644
720
|
return;
|
|
645
721
|
}
|
|
722
|
+
// ── Gmail / Connections endpoints ───────────────────────────────────────
|
|
723
|
+
if (parsedUrl.pathname === "/connections" && req.method === "GET") {
|
|
724
|
+
void (async () => {
|
|
725
|
+
const { handleConnectionsList } = await import("./connectors/gmail.js");
|
|
726
|
+
const result = await handleConnectionsList();
|
|
727
|
+
res.writeHead(result.status, {
|
|
728
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
729
|
+
});
|
|
730
|
+
res.end(result.body);
|
|
731
|
+
})();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (parsedUrl.pathname === "/connections/gmail/auth" &&
|
|
735
|
+
req.method === "GET") {
|
|
736
|
+
void (async () => {
|
|
737
|
+
const { handleGmailAuthRedirect } = await import("./connectors/gmail.js");
|
|
738
|
+
const result = handleGmailAuthRedirect();
|
|
739
|
+
if (result.redirect) {
|
|
740
|
+
res.writeHead(302, { Location: result.redirect });
|
|
741
|
+
res.end();
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
res.writeHead(result.status, {
|
|
745
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
746
|
+
});
|
|
747
|
+
res.end(result.body);
|
|
748
|
+
}
|
|
749
|
+
})();
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (parsedUrl.pathname === "/connections/gmail/callback" &&
|
|
753
|
+
req.method === "GET") {
|
|
754
|
+
void (async () => {
|
|
755
|
+
const { handleGmailCallback } = await import("./connectors/gmail.js");
|
|
756
|
+
const code = parsedUrl.searchParams.get("code");
|
|
757
|
+
const state = parsedUrl.searchParams.get("state");
|
|
758
|
+
const error = parsedUrl.searchParams.get("error");
|
|
759
|
+
const result = await handleGmailCallback(code, state, error);
|
|
760
|
+
res.writeHead(result.status, {
|
|
761
|
+
"Content-Type": result.contentType ?? "text/html",
|
|
762
|
+
});
|
|
763
|
+
res.end(result.body);
|
|
764
|
+
})();
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (parsedUrl.pathname === "/connections/gmail" &&
|
|
768
|
+
req.method === "DELETE") {
|
|
769
|
+
void (async () => {
|
|
770
|
+
const { handleGmailDisconnect } = await import("./connectors/gmail.js");
|
|
771
|
+
const result = await handleGmailDisconnect();
|
|
772
|
+
res.writeHead(result.status, {
|
|
773
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
774
|
+
});
|
|
775
|
+
res.end(result.body);
|
|
776
|
+
})();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (parsedUrl.pathname === "/connections/gmail/test" &&
|
|
780
|
+
req.method === "POST") {
|
|
781
|
+
void (async () => {
|
|
782
|
+
const { handleGmailTest } = await import("./connectors/gmail.js");
|
|
783
|
+
const result = await handleGmailTest();
|
|
784
|
+
res.writeHead(result.status, {
|
|
785
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
786
|
+
});
|
|
787
|
+
res.end(result.body);
|
|
788
|
+
})();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
// ── GitHub MCP connector routes ─────────────────────────────────────
|
|
792
|
+
if (parsedUrl.pathname === "/connections/github/auth" &&
|
|
793
|
+
req.method === "GET") {
|
|
794
|
+
void (async () => {
|
|
795
|
+
const { handleGithubAuthorize } = await import("./connectors/github.js");
|
|
796
|
+
const result = await handleGithubAuthorize();
|
|
797
|
+
if (result.redirect) {
|
|
798
|
+
res.writeHead(302, { Location: result.redirect });
|
|
799
|
+
res.end();
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
res.writeHead(result.status, {
|
|
803
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
804
|
+
});
|
|
805
|
+
res.end(result.body);
|
|
806
|
+
}
|
|
807
|
+
})();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (parsedUrl.pathname === "/connections/github/test" &&
|
|
811
|
+
req.method === "POST") {
|
|
812
|
+
void (async () => {
|
|
813
|
+
const { handleGithubTest } = await import("./connectors/github.js");
|
|
814
|
+
const result = await handleGithubTest();
|
|
815
|
+
res.writeHead(result.status, {
|
|
816
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
817
|
+
});
|
|
818
|
+
res.end(result.body);
|
|
819
|
+
})();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
if (parsedUrl.pathname === "/connections/github" &&
|
|
823
|
+
req.method === "DELETE") {
|
|
824
|
+
void (async () => {
|
|
825
|
+
const { handleGithubDisconnect } = await import("./connectors/github.js");
|
|
826
|
+
const result = await handleGithubDisconnect();
|
|
827
|
+
res.writeHead(result.status, {
|
|
828
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
829
|
+
});
|
|
830
|
+
res.end(result.body);
|
|
831
|
+
})();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
// ── Sentry MCP connector routes ─────────────────────────────────────
|
|
835
|
+
if (parsedUrl.pathname === "/connections/sentry/auth" &&
|
|
836
|
+
req.method === "GET") {
|
|
837
|
+
void (async () => {
|
|
838
|
+
const { handleSentryAuthorize } = await import("./connectors/sentry.js");
|
|
839
|
+
const result = await handleSentryAuthorize();
|
|
840
|
+
if (result.redirect) {
|
|
841
|
+
res.writeHead(302, { Location: result.redirect });
|
|
842
|
+
res.end();
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
res.writeHead(result.status, {
|
|
846
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
847
|
+
});
|
|
848
|
+
res.end(result.body);
|
|
849
|
+
}
|
|
850
|
+
})();
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (parsedUrl.pathname === "/connections/sentry/callback" &&
|
|
854
|
+
req.method === "GET") {
|
|
855
|
+
void (async () => {
|
|
856
|
+
const { handleSentryCallback } = await import("./connectors/sentry.js");
|
|
857
|
+
const code = parsedUrl.searchParams.get("code");
|
|
858
|
+
const state = parsedUrl.searchParams.get("state");
|
|
859
|
+
const error = parsedUrl.searchParams.get("error");
|
|
860
|
+
const result = await handleSentryCallback(code, state, error);
|
|
861
|
+
res.writeHead(result.status, {
|
|
862
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
863
|
+
});
|
|
864
|
+
res.end(result.body);
|
|
865
|
+
})();
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
if (parsedUrl.pathname === "/connections/sentry/test" &&
|
|
869
|
+
req.method === "POST") {
|
|
870
|
+
void (async () => {
|
|
871
|
+
const { handleSentryTest } = await import("./connectors/sentry.js");
|
|
872
|
+
const result = await handleSentryTest();
|
|
873
|
+
res.writeHead(result.status, {
|
|
874
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
875
|
+
});
|
|
876
|
+
res.end(result.body);
|
|
877
|
+
})();
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (parsedUrl.pathname === "/connections/sentry" &&
|
|
881
|
+
req.method === "DELETE") {
|
|
882
|
+
void (async () => {
|
|
883
|
+
const { handleSentryDisconnect } = await import("./connectors/sentry.js");
|
|
884
|
+
const result = await handleSentryDisconnect();
|
|
885
|
+
res.writeHead(result.status, {
|
|
886
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
887
|
+
});
|
|
888
|
+
res.end(result.body);
|
|
889
|
+
})();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
// ── Linear MCP connector routes ─────────────────────────────────────
|
|
893
|
+
if (parsedUrl.pathname === "/connections/linear/auth" &&
|
|
894
|
+
req.method === "GET") {
|
|
895
|
+
void (async () => {
|
|
896
|
+
const { handleLinearAuthorize } = await import("./connectors/linear.js");
|
|
897
|
+
const result = await handleLinearAuthorize();
|
|
898
|
+
if (result.redirect) {
|
|
899
|
+
res.writeHead(302, { Location: result.redirect });
|
|
900
|
+
res.end();
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
res.writeHead(result.status, {
|
|
904
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
905
|
+
});
|
|
906
|
+
res.end(result.body);
|
|
907
|
+
}
|
|
908
|
+
})();
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
if (parsedUrl.pathname === "/connections/linear/callback" &&
|
|
912
|
+
req.method === "GET") {
|
|
913
|
+
void (async () => {
|
|
914
|
+
const { handleLinearCallback } = await import("./connectors/linear.js");
|
|
915
|
+
const code = parsedUrl.searchParams.get("code");
|
|
916
|
+
const state = parsedUrl.searchParams.get("state");
|
|
917
|
+
const error = parsedUrl.searchParams.get("error");
|
|
918
|
+
const result = await handleLinearCallback(code, state, error);
|
|
919
|
+
res.writeHead(result.status, {
|
|
920
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
921
|
+
});
|
|
922
|
+
res.end(result.body);
|
|
923
|
+
})();
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
if (parsedUrl.pathname === "/connections/linear/test" &&
|
|
927
|
+
req.method === "POST") {
|
|
928
|
+
void (async () => {
|
|
929
|
+
const { handleLinearTest } = await import("./connectors/linear.js");
|
|
930
|
+
const result = await handleLinearTest();
|
|
931
|
+
res.writeHead(result.status, {
|
|
932
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
933
|
+
});
|
|
934
|
+
res.end(result.body);
|
|
935
|
+
})();
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (parsedUrl.pathname === "/connections/linear" &&
|
|
939
|
+
req.method === "DELETE") {
|
|
940
|
+
void (async () => {
|
|
941
|
+
const { handleLinearDisconnect } = await import("./connectors/linear.js");
|
|
942
|
+
const result = await handleLinearDisconnect();
|
|
943
|
+
res.writeHead(result.status, {
|
|
944
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
945
|
+
});
|
|
946
|
+
res.end(result.body);
|
|
947
|
+
})();
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
// ── Slack connector routes ──────────────────────────────────────
|
|
951
|
+
if ((parsedUrl.pathname === "/connections/slack/auth" || parsedUrl.pathname === "/connections/slack/authorize") &&
|
|
952
|
+
req.method === "GET") {
|
|
953
|
+
const { handleSlackAuthorize } = await import("./connectors/slack.js");
|
|
954
|
+
const result = handleSlackAuthorize();
|
|
955
|
+
if (result.redirect) {
|
|
956
|
+
res.writeHead(302, { Location: result.redirect });
|
|
957
|
+
res.end();
|
|
958
|
+
}
|
|
959
|
+
else {
|
|
960
|
+
res.writeHead(result.status, { "Content-Type": result.contentType ?? "application/json" });
|
|
961
|
+
res.end(result.body);
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
if (parsedUrl.pathname === "/connections/slack/test" &&
|
|
966
|
+
req.method === "POST") {
|
|
967
|
+
void (async () => {
|
|
968
|
+
const { handleSlackTest } = await import("./connectors/slack.js");
|
|
969
|
+
const result = await handleSlackTest();
|
|
970
|
+
res.writeHead(result.status, { "Content-Type": result.contentType ?? "application/json" });
|
|
971
|
+
res.end(result.body);
|
|
972
|
+
})();
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
if (parsedUrl.pathname === "/connections/slack" &&
|
|
976
|
+
req.method === "DELETE") {
|
|
977
|
+
const { handleSlackDisconnect } = await import("./connectors/slack.js");
|
|
978
|
+
const result = handleSlackDisconnect();
|
|
979
|
+
res.writeHead(result.status, { "Content-Type": result.contentType ?? "application/json" });
|
|
980
|
+
res.end(result.body);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
// ── Google Calendar routes ──────────────────────────────────────
|
|
984
|
+
if (parsedUrl.pathname === "/connections/google-calendar/auth" &&
|
|
985
|
+
req.method === "GET") {
|
|
986
|
+
void (async () => {
|
|
987
|
+
const { handleCalendarAuthRedirect } = await import("./connectors/googleCalendar.js");
|
|
988
|
+
const result = handleCalendarAuthRedirect();
|
|
989
|
+
if (result.redirect) {
|
|
990
|
+
res.writeHead(302, { Location: result.redirect });
|
|
991
|
+
res.end();
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
res.writeHead(result.status, {
|
|
995
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
996
|
+
});
|
|
997
|
+
res.end(result.body);
|
|
998
|
+
}
|
|
999
|
+
})();
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (parsedUrl.pathname === "/connections/google-calendar/callback" &&
|
|
1003
|
+
req.method === "GET") {
|
|
1004
|
+
void (async () => {
|
|
1005
|
+
const { handleCalendarCallback } = await import("./connectors/googleCalendar.js");
|
|
1006
|
+
const code = parsedUrl.searchParams.get("code");
|
|
1007
|
+
const state = parsedUrl.searchParams.get("state");
|
|
1008
|
+
const error = parsedUrl.searchParams.get("error");
|
|
1009
|
+
const result = await handleCalendarCallback(code, state, error);
|
|
1010
|
+
res.writeHead(result.status, {
|
|
1011
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
1012
|
+
});
|
|
1013
|
+
res.end(result.body);
|
|
1014
|
+
})();
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (parsedUrl.pathname === "/connections/google-calendar/test" &&
|
|
1018
|
+
req.method === "POST") {
|
|
1019
|
+
void (async () => {
|
|
1020
|
+
const { handleCalendarTest } = await import("./connectors/googleCalendar.js");
|
|
1021
|
+
const result = await handleCalendarTest();
|
|
1022
|
+
res.writeHead(result.status, {
|
|
1023
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
1024
|
+
});
|
|
1025
|
+
res.end(result.body);
|
|
1026
|
+
})();
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (parsedUrl.pathname === "/connections/google-calendar" &&
|
|
1030
|
+
req.method === "DELETE") {
|
|
1031
|
+
void (async () => {
|
|
1032
|
+
const { handleCalendarDisconnect } = await import("./connectors/googleCalendar.js");
|
|
1033
|
+
const result = await handleCalendarDisconnect();
|
|
1034
|
+
res.writeHead(result.status, {
|
|
1035
|
+
"Content-Type": result.contentType ?? "application/json",
|
|
1036
|
+
});
|
|
1037
|
+
res.end(result.body);
|
|
1038
|
+
})();
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
// ── Inbox routes ────────────────────────────────────────────────────
|
|
1042
|
+
if (parsedUrl.pathname === "/inbox" && req.method === "GET") {
|
|
1043
|
+
void (async () => {
|
|
1044
|
+
try {
|
|
1045
|
+
const { readdir, readFile, stat } = await import("node:fs/promises");
|
|
1046
|
+
const { existsSync } = await import("node:fs");
|
|
1047
|
+
const inboxDir = path.join(os.homedir(), ".patchwork", "inbox");
|
|
1048
|
+
if (!existsSync(inboxDir)) {
|
|
1049
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1050
|
+
res.end(JSON.stringify({ items: [] }));
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const files = (await readdir(inboxDir)).filter((f) => f.endsWith(".md"));
|
|
1054
|
+
const items = await Promise.all(files.map(async (name) => {
|
|
1055
|
+
const filePath = path.join(inboxDir, name);
|
|
1056
|
+
const [content, stats] = await Promise.all([
|
|
1057
|
+
readFile(filePath, "utf8"),
|
|
1058
|
+
stat(filePath),
|
|
1059
|
+
]);
|
|
1060
|
+
const stripped = content
|
|
1061
|
+
.split("\n")
|
|
1062
|
+
.filter((l) => !l.startsWith("#"))
|
|
1063
|
+
.join("\n")
|
|
1064
|
+
.trim();
|
|
1065
|
+
return {
|
|
1066
|
+
name,
|
|
1067
|
+
path: filePath,
|
|
1068
|
+
modifiedAt: stats.mtime.toISOString(),
|
|
1069
|
+
preview: stripped.slice(0, 200),
|
|
1070
|
+
};
|
|
1071
|
+
}));
|
|
1072
|
+
items.sort((a, b) => new Date(b.modifiedAt).getTime() -
|
|
1073
|
+
new Date(a.modifiedAt).getTime());
|
|
1074
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1075
|
+
res.end(JSON.stringify({ items }));
|
|
1076
|
+
}
|
|
1077
|
+
catch (err) {
|
|
1078
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1079
|
+
res.end(JSON.stringify({
|
|
1080
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1081
|
+
}));
|
|
1082
|
+
}
|
|
1083
|
+
})();
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const inboxFileMatch = parsedUrl.pathname?.match(/^\/inbox\/([^/]+\.md)$/);
|
|
1087
|
+
if (inboxFileMatch && req.method === "GET") {
|
|
1088
|
+
void (async () => {
|
|
1089
|
+
try {
|
|
1090
|
+
const { readFile, stat } = await import("node:fs/promises");
|
|
1091
|
+
const filename = decodeURIComponent(inboxFileMatch[1] ?? "");
|
|
1092
|
+
// Prevent path traversal — filename must not contain directory separators
|
|
1093
|
+
if (filename.includes("/") || filename.includes("\\")) {
|
|
1094
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
1095
|
+
res.end(JSON.stringify({ error: "Invalid filename" }));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const filePath = path.join(os.homedir(), ".patchwork", "inbox", filename);
|
|
1099
|
+
const [content, stats] = await Promise.all([
|
|
1100
|
+
readFile(filePath, "utf8"),
|
|
1101
|
+
stat(filePath),
|
|
1102
|
+
]);
|
|
1103
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1104
|
+
res.end(JSON.stringify({
|
|
1105
|
+
name: filename,
|
|
1106
|
+
content,
|
|
1107
|
+
modifiedAt: stats.mtime.toISOString(),
|
|
1108
|
+
}));
|
|
1109
|
+
}
|
|
1110
|
+
catch (err) {
|
|
1111
|
+
const code = err.code;
|
|
1112
|
+
if (code === "ENOENT") {
|
|
1113
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
1114
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1118
|
+
res.end(JSON.stringify({
|
|
1119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1120
|
+
}));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
})();
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
// ── End inbox routes ─────────────────────────────────────────────────
|
|
646
1127
|
if (parsedUrl.pathname === "/recipes/run" && req.method === "POST") {
|
|
647
1128
|
const chunks = [];
|
|
648
1129
|
req.on("data", (c) => chunks.push(c));
|
|
@@ -652,6 +1133,11 @@ export class Server extends EventEmitter {
|
|
|
652
1133
|
const body = Buffer.concat(chunks).toString("utf-8");
|
|
653
1134
|
const parsed = JSON.parse(body || "{}");
|
|
654
1135
|
const name = parsed.name;
|
|
1136
|
+
const vars = parsed.vars &&
|
|
1137
|
+
typeof parsed.vars === "object" &&
|
|
1138
|
+
!Array.isArray(parsed.vars)
|
|
1139
|
+
? parsed.vars
|
|
1140
|
+
: undefined;
|
|
655
1141
|
if (typeof name !== "string" || !name) {
|
|
656
1142
|
res.writeHead(400, { "Content-Type": "application/json" });
|
|
657
1143
|
res.end(JSON.stringify({ ok: false, error: "name required" }));
|
|
@@ -665,7 +1151,7 @@ export class Server extends EventEmitter {
|
|
|
665
1151
|
}));
|
|
666
1152
|
return;
|
|
667
1153
|
}
|
|
668
|
-
const result = await this.runRecipeFn(name);
|
|
1154
|
+
const result = await this.runRecipeFn(name, vars);
|
|
669
1155
|
res.writeHead(result.ok ? 200 : 400, {
|
|
670
1156
|
"Content-Type": "application/json",
|
|
671
1157
|
});
|
|
@@ -759,7 +1245,7 @@ export class Server extends EventEmitter {
|
|
|
759
1245
|
const id = sessionDetailMatch[1];
|
|
760
1246
|
try {
|
|
761
1247
|
const data = this.sessionDetailFn?.(id);
|
|
762
|
-
if (!data
|
|
1248
|
+
if (!data?.summary) {
|
|
763
1249
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
764
1250
|
res.end(JSON.stringify({ error: "unknown sessionId" }));
|
|
765
1251
|
return;
|
|
@@ -1126,6 +1612,8 @@ export class Server extends EventEmitter {
|
|
|
1126
1612
|
ws.on("error", (err) => {
|
|
1127
1613
|
this.logger.error(`WebSocket client error: ${err.message}`);
|
|
1128
1614
|
});
|
|
1615
|
+
ws.remoteAddr =
|
|
1616
|
+
req.socket.remoteAddress;
|
|
1129
1617
|
this.emit("connection", ws);
|
|
1130
1618
|
});
|
|
1131
1619
|
}
|