patchwork-os 0.2.0-alpha.3 → 0.2.0-alpha.30

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 (298) hide show
  1. package/README.bridge.md +6 -0
  2. package/README.md +40 -15
  3. package/deploy/bootstrap-vps.sh +184 -0
  4. package/deploy/deploy-dashboard.sh +174 -0
  5. package/deploy/deploy-landing.sh +79 -0
  6. package/dist/activationMetrics.d.ts +67 -0
  7. package/dist/activationMetrics.js +255 -0
  8. package/dist/activationMetrics.js.map +1 -0
  9. package/dist/approvalHttp.d.ts +24 -2
  10. package/dist/approvalHttp.js +150 -10
  11. package/dist/approvalHttp.js.map +1 -1
  12. package/dist/approvalQueue.d.ts +16 -1
  13. package/dist/approvalQueue.js +44 -3
  14. package/dist/approvalQueue.js.map +1 -1
  15. package/dist/automation.d.ts +20 -0
  16. package/dist/automation.js +54 -1
  17. package/dist/automation.js.map +1 -1
  18. package/dist/bridge.d.ts +7 -0
  19. package/dist/bridge.js +225 -35
  20. package/dist/bridge.js.map +1 -1
  21. package/dist/bridgeToken.js +57 -19
  22. package/dist/bridgeToken.js.map +1 -1
  23. package/dist/ccPermissions.js +6 -4
  24. package/dist/ccPermissions.js.map +1 -1
  25. package/dist/claudeOrchestrator.d.ts +1 -1
  26. package/dist/claudeOrchestrator.js +14 -8
  27. package/dist/claudeOrchestrator.js.map +1 -1
  28. package/dist/commands/launchd.d.ts +2 -0
  29. package/dist/commands/launchd.js +94 -0
  30. package/dist/commands/launchd.js.map +1 -0
  31. package/dist/commands/recipe.d.ts +258 -0
  32. package/dist/commands/recipe.js +1130 -0
  33. package/dist/commands/recipe.js.map +1 -0
  34. package/dist/commands/recipeInstall.d.ts +72 -0
  35. package/dist/commands/recipeInstall.js +339 -0
  36. package/dist/commands/recipeInstall.js.map +1 -0
  37. package/dist/config.d.ts +14 -1
  38. package/dist/config.js +99 -8
  39. package/dist/config.js.map +1 -1
  40. package/dist/connectors/baseConnector.d.ts +117 -0
  41. package/dist/connectors/baseConnector.js +213 -0
  42. package/dist/connectors/baseConnector.js.map +1 -0
  43. package/dist/connectors/confluence.d.ts +111 -0
  44. package/dist/connectors/confluence.js +406 -0
  45. package/dist/connectors/confluence.js.map +1 -0
  46. package/dist/connectors/datadog.d.ts +116 -0
  47. package/dist/connectors/datadog.js +385 -0
  48. package/dist/connectors/datadog.js.map +1 -0
  49. package/dist/connectors/fixtureLibrary.d.ts +21 -0
  50. package/dist/connectors/fixtureLibrary.js +70 -0
  51. package/dist/connectors/fixtureLibrary.js.map +1 -0
  52. package/dist/connectors/fixtureRecorder.d.ts +1 -0
  53. package/dist/connectors/fixtureRecorder.js +35 -0
  54. package/dist/connectors/fixtureRecorder.js.map +1 -0
  55. package/dist/connectors/github.d.ts +58 -8
  56. package/dist/connectors/github.js +312 -84
  57. package/dist/connectors/github.js.map +1 -1
  58. package/dist/connectors/gmail.d.ts +4 -1
  59. package/dist/connectors/gmail.js +79 -16
  60. package/dist/connectors/gmail.js.map +1 -1
  61. package/dist/connectors/googleCalendar.d.ts +60 -0
  62. package/dist/connectors/googleCalendar.js +345 -0
  63. package/dist/connectors/googleCalendar.js.map +1 -0
  64. package/dist/connectors/hubspot.d.ts +112 -0
  65. package/dist/connectors/hubspot.js +408 -0
  66. package/dist/connectors/hubspot.js.map +1 -0
  67. package/dist/connectors/intercom.d.ts +102 -0
  68. package/dist/connectors/intercom.js +402 -0
  69. package/dist/connectors/intercom.js.map +1 -0
  70. package/dist/connectors/jira.d.ts +98 -0
  71. package/dist/connectors/jira.js +379 -0
  72. package/dist/connectors/jira.js.map +1 -0
  73. package/dist/connectors/linear.d.ts +69 -19
  74. package/dist/connectors/linear.js +170 -129
  75. package/dist/connectors/linear.js.map +1 -1
  76. package/dist/connectors/mcpClient.d.ts +56 -0
  77. package/dist/connectors/mcpClient.js +189 -0
  78. package/dist/connectors/mcpClient.js.map +1 -0
  79. package/dist/connectors/mcpOAuth.d.ts +84 -0
  80. package/dist/connectors/mcpOAuth.js +389 -0
  81. package/dist/connectors/mcpOAuth.js.map +1 -0
  82. package/dist/connectors/mockConnector.d.ts +28 -0
  83. package/dist/connectors/mockConnector.js +81 -0
  84. package/dist/connectors/mockConnector.js.map +1 -0
  85. package/dist/connectors/notion.d.ts +143 -0
  86. package/dist/connectors/notion.js +424 -0
  87. package/dist/connectors/notion.js.map +1 -0
  88. package/dist/connectors/sentry.d.ts +17 -21
  89. package/dist/connectors/sentry.js +115 -131
  90. package/dist/connectors/sentry.js.map +1 -1
  91. package/dist/connectors/slack.d.ts +50 -0
  92. package/dist/connectors/slack.js +324 -0
  93. package/dist/connectors/slack.js.map +1 -0
  94. package/dist/connectors/stripe.d.ts +116 -0
  95. package/dist/connectors/stripe.js +379 -0
  96. package/dist/connectors/stripe.js.map +1 -0
  97. package/dist/connectors/tokenStorage.d.ts +35 -0
  98. package/dist/connectors/tokenStorage.js +459 -0
  99. package/dist/connectors/tokenStorage.js.map +1 -0
  100. package/dist/connectors/zendesk.d.ts +104 -0
  101. package/dist/connectors/zendesk.js +424 -0
  102. package/dist/connectors/zendesk.js.map +1 -0
  103. package/dist/drivers/gemini/index.d.ts +5 -1
  104. package/dist/drivers/gemini/index.js +39 -5
  105. package/dist/drivers/gemini/index.js.map +1 -1
  106. package/dist/drivers/index.d.ts +5 -0
  107. package/dist/drivers/index.js +1 -1
  108. package/dist/drivers/index.js.map +1 -1
  109. package/dist/featureFlags.d.ts +73 -0
  110. package/dist/featureFlags.js +203 -0
  111. package/dist/featureFlags.js.map +1 -0
  112. package/dist/fp/automationInterpreter.js +1 -0
  113. package/dist/fp/automationInterpreter.js.map +1 -1
  114. package/dist/fp/automationProgram.d.ts +1 -1
  115. package/dist/fp/automationProgram.js.map +1 -1
  116. package/dist/fp/policyParser.js +17 -0
  117. package/dist/fp/policyParser.js.map +1 -1
  118. package/dist/index.js +621 -61
  119. package/dist/index.js.map +1 -1
  120. package/dist/installGuard.d.ts +25 -0
  121. package/dist/installGuard.js +48 -0
  122. package/dist/installGuard.js.map +1 -0
  123. package/dist/oauth.d.ts +4 -1
  124. package/dist/oauth.js +50 -14
  125. package/dist/oauth.js.map +1 -1
  126. package/dist/patchworkConfig.d.ts +9 -0
  127. package/dist/patchworkConfig.js.map +1 -1
  128. package/dist/recipes/RecipeOrchestrator.d.ts +40 -0
  129. package/dist/recipes/RecipeOrchestrator.js +51 -0
  130. package/dist/recipes/RecipeOrchestrator.js.map +1 -0
  131. package/dist/recipes/agentExecutor.d.ts +28 -0
  132. package/dist/recipes/agentExecutor.js +42 -0
  133. package/dist/recipes/agentExecutor.js.map +1 -0
  134. package/dist/recipes/chainedRunner.d.ts +140 -0
  135. package/dist/recipes/chainedRunner.js +539 -0
  136. package/dist/recipes/chainedRunner.js.map +1 -0
  137. package/dist/recipes/dependencyGraph.d.ts +39 -0
  138. package/dist/recipes/dependencyGraph.js +199 -0
  139. package/dist/recipes/dependencyGraph.js.map +1 -0
  140. package/dist/recipes/legacyRecipeCompat.d.ts +2 -0
  141. package/dist/recipes/legacyRecipeCompat.js +112 -0
  142. package/dist/recipes/legacyRecipeCompat.js.map +1 -0
  143. package/dist/recipes/manifest.d.ts +47 -0
  144. package/dist/recipes/manifest.js +141 -0
  145. package/dist/recipes/manifest.js.map +1 -0
  146. package/dist/recipes/nestedRecipeStep.d.ts +58 -0
  147. package/dist/recipes/nestedRecipeStep.js +95 -0
  148. package/dist/recipes/nestedRecipeStep.js.map +1 -0
  149. package/dist/recipes/outputRegistry.d.ts +28 -0
  150. package/dist/recipes/outputRegistry.js +52 -0
  151. package/dist/recipes/outputRegistry.js.map +1 -0
  152. package/dist/recipes/scheduler.d.ts +23 -7
  153. package/dist/recipes/scheduler.js +131 -41
  154. package/dist/recipes/scheduler.js.map +1 -1
  155. package/dist/recipes/schema.d.ts +17 -2
  156. package/dist/recipes/schemaGenerator.d.ts +28 -0
  157. package/dist/recipes/schemaGenerator.js +565 -0
  158. package/dist/recipes/schemaGenerator.js.map +1 -0
  159. package/dist/recipes/templateEngine.d.ts +62 -0
  160. package/dist/recipes/templateEngine.js +182 -0
  161. package/dist/recipes/templateEngine.js.map +1 -0
  162. package/dist/recipes/toolRegistry.d.ts +181 -0
  163. package/dist/recipes/toolRegistry.js +300 -0
  164. package/dist/recipes/toolRegistry.js.map +1 -0
  165. package/dist/recipes/tools/calendar.d.ts +6 -0
  166. package/dist/recipes/tools/calendar.js +61 -0
  167. package/dist/recipes/tools/calendar.js.map +1 -0
  168. package/dist/recipes/tools/confluence.d.ts +6 -0
  169. package/dist/recipes/tools/confluence.js +254 -0
  170. package/dist/recipes/tools/confluence.js.map +1 -0
  171. package/dist/recipes/tools/datadog.d.ts +6 -0
  172. package/dist/recipes/tools/datadog.js +239 -0
  173. package/dist/recipes/tools/datadog.js.map +1 -0
  174. package/dist/recipes/tools/diagnostics.d.ts +6 -0
  175. package/dist/recipes/tools/diagnostics.js +36 -0
  176. package/dist/recipes/tools/diagnostics.js.map +1 -0
  177. package/dist/recipes/tools/file.d.ts +6 -0
  178. package/dist/recipes/tools/file.js +170 -0
  179. package/dist/recipes/tools/file.js.map +1 -0
  180. package/dist/recipes/tools/git.d.ts +6 -0
  181. package/dist/recipes/tools/git.js +63 -0
  182. package/dist/recipes/tools/git.js.map +1 -0
  183. package/dist/recipes/tools/github.d.ts +6 -0
  184. package/dist/recipes/tools/github.js +91 -0
  185. package/dist/recipes/tools/github.js.map +1 -0
  186. package/dist/recipes/tools/gmail.d.ts +6 -0
  187. package/dist/recipes/tools/gmail.js +210 -0
  188. package/dist/recipes/tools/gmail.js.map +1 -0
  189. package/dist/recipes/tools/hubspot.d.ts +6 -0
  190. package/dist/recipes/tools/hubspot.js +232 -0
  191. package/dist/recipes/tools/hubspot.js.map +1 -0
  192. package/dist/recipes/tools/index.d.ts +22 -0
  193. package/dist/recipes/tools/index.js +25 -0
  194. package/dist/recipes/tools/index.js.map +1 -0
  195. package/dist/recipes/tools/intercom.d.ts +6 -0
  196. package/dist/recipes/tools/intercom.js +226 -0
  197. package/dist/recipes/tools/intercom.js.map +1 -0
  198. package/dist/recipes/tools/linear.d.ts +6 -0
  199. package/dist/recipes/tools/linear.js +83 -0
  200. package/dist/recipes/tools/linear.js.map +1 -0
  201. package/dist/recipes/tools/notion.d.ts +6 -0
  202. package/dist/recipes/tools/notion.js +278 -0
  203. package/dist/recipes/tools/notion.js.map +1 -0
  204. package/dist/recipes/tools/slack.d.ts +6 -0
  205. package/dist/recipes/tools/slack.js +72 -0
  206. package/dist/recipes/tools/slack.js.map +1 -0
  207. package/dist/recipes/tools/stripe.d.ts +6 -0
  208. package/dist/recipes/tools/stripe.js +265 -0
  209. package/dist/recipes/tools/stripe.js.map +1 -0
  210. package/dist/recipes/tools/zendesk.d.ts +6 -0
  211. package/dist/recipes/tools/zendesk.js +245 -0
  212. package/dist/recipes/tools/zendesk.js.map +1 -0
  213. package/dist/recipes/validation.d.ts +13 -0
  214. package/dist/recipes/validation.js +433 -0
  215. package/dist/recipes/validation.js.map +1 -0
  216. package/dist/recipes/yamlRunner.d.ts +87 -0
  217. package/dist/recipes/yamlRunner.js +693 -409
  218. package/dist/recipes/yamlRunner.js.map +1 -1
  219. package/dist/recipesHttp.d.ts +34 -6
  220. package/dist/recipesHttp.js +285 -15
  221. package/dist/recipesHttp.js.map +1 -1
  222. package/dist/riskTier.js +1 -0
  223. package/dist/riskTier.js.map +1 -1
  224. package/dist/runLog.d.ts +23 -0
  225. package/dist/runLog.js +56 -1
  226. package/dist/runLog.js.map +1 -1
  227. package/dist/schemas/dry-run-plan.v1.json +139 -0
  228. package/dist/schemas/recipe.v1.json +684 -0
  229. package/dist/server.d.ts +32 -1
  230. package/dist/server.js +980 -97
  231. package/dist/server.js.map +1 -1
  232. package/dist/streamableHttp.js +2 -0
  233. package/dist/streamableHttp.js.map +1 -1
  234. package/dist/tools/addLinearComment.d.ts +55 -0
  235. package/dist/tools/addLinearComment.js +72 -0
  236. package/dist/tools/addLinearComment.js.map +1 -0
  237. package/dist/tools/bridgeDoctor.js +2 -2
  238. package/dist/tools/bridgeDoctor.js.map +1 -1
  239. package/dist/tools/createLinearIssue.d.ts +84 -0
  240. package/dist/tools/createLinearIssue.js +146 -0
  241. package/dist/tools/createLinearIssue.js.map +1 -0
  242. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  243. package/dist/tools/fetchCalendarEvents.js +97 -0
  244. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  245. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  246. package/dist/tools/fetchGithubIssue.js +84 -0
  247. package/dist/tools/fetchGithubIssue.js.map +1 -0
  248. package/dist/tools/fetchGithubPR.d.ts +89 -0
  249. package/dist/tools/fetchGithubPR.js +96 -0
  250. package/dist/tools/fetchGithubPR.js.map +1 -0
  251. package/dist/tools/fetchSlackProfile.d.ts +43 -0
  252. package/dist/tools/fetchSlackProfile.js +46 -0
  253. package/dist/tools/fetchSlackProfile.js.map +1 -0
  254. package/dist/tools/getConnectorStatus.d.ts +58 -0
  255. package/dist/tools/getConnectorStatus.js +56 -0
  256. package/dist/tools/getConnectorStatus.js.map +1 -0
  257. package/dist/tools/github/actions.js +4 -2
  258. package/dist/tools/github/actions.js.map +1 -1
  259. package/dist/tools/github/composite.d.ts +339 -0
  260. package/dist/tools/github/composite.js +343 -0
  261. package/dist/tools/github/composite.js.map +1 -0
  262. package/dist/tools/github/index.d.ts +2 -1
  263. package/dist/tools/github/index.js +2 -1
  264. package/dist/tools/github/index.js.map +1 -1
  265. package/dist/tools/github/issues.js +8 -4
  266. package/dist/tools/github/issues.js.map +1 -1
  267. package/dist/tools/github/pr.d.ts +122 -0
  268. package/dist/tools/github/pr.js +195 -5
  269. package/dist/tools/github/pr.js.map +1 -1
  270. package/dist/tools/index.js +32 -1
  271. package/dist/tools/index.js.map +1 -1
  272. package/dist/tools/searchTools.js +1 -1
  273. package/dist/tools/searchTools.js.map +1 -1
  274. package/dist/tools/slackListChannels.d.ts +65 -0
  275. package/dist/tools/slackListChannels.js +70 -0
  276. package/dist/tools/slackListChannels.js.map +1 -0
  277. package/dist/tools/slackPostMessage.d.ts +57 -0
  278. package/dist/tools/slackPostMessage.js +77 -0
  279. package/dist/tools/slackPostMessage.js.map +1 -0
  280. package/dist/tools/testTraceToSource.js +2 -2
  281. package/dist/tools/testTraceToSource.js.map +1 -1
  282. package/dist/tools/updateLinearIssue.d.ts +89 -0
  283. package/dist/tools/updateLinearIssue.js +117 -0
  284. package/dist/tools/updateLinearIssue.js.map +1 -0
  285. package/dist/transport.d.ts +7 -1
  286. package/dist/transport.js +85 -11
  287. package/dist/transport.js.map +1 -1
  288. package/package.json +5 -2
  289. package/scripts/start-all.sh +56 -19
  290. package/templates/automation-policies/recipe-authoring.json +25 -0
  291. package/templates/automation-policy.example.json +6 -0
  292. package/templates/co.patchwork-os.bridge.plist +34 -0
  293. package/templates/recipes/ctx-loop-test.yaml +75 -0
  294. package/templates/recipes/lint-on-save.yaml +1 -2
  295. package/templates/recipes/morning-brief-slack.yaml +57 -0
  296. package/templates/recipes/morning-brief.yaml +14 -6
  297. package/templates/recipes/project-health-check.yaml +50 -0
  298. package/templates/recipes/sentry-to-linear.yaml +77 -0
package/README.bridge.md CHANGED
@@ -40,6 +40,12 @@ claude --ide
40
40
 
41
41
  > **Updating?** Use `npm install -g claude-ide-bridge@latest` — `npm update -g` may lag the registry cache after a new release.
42
42
 
43
+ > **macOS LaunchAgent users:** always install from the registry (`npm install -g patchwork-os`) or
44
+ > from a tarball (`npm pack && npm install -g patchwork-os-*.tgz`).
45
+ > Running `npm install -g .` from a repo checkout creates a symlink — the macOS sandbox
46
+ > cannot follow it, causing EPERM when launchctl starts the bridge.
47
+ > `patchwork-os launchd install` will refuse to proceed and print fix instructions.
48
+
43
49
  After `init`, type `/mcp` in Claude Code to confirm the bridge is connected. Type `/ide` to see open files, diagnostics, and editor state.
44
50
 
45
51
  > **One bridge per workspace.** Each project runs its own bridge instance on its own port. Start a separate `claude-ide-bridge --watch` in each directory.
package/README.md CHANGED
@@ -25,14 +25,16 @@ Patchwork OS watches for things that matter, acts, and asks before anything risk
25
25
  ## After init
26
26
 
27
27
  ```bash
28
- patchwork-os recipe list # see installed recipes
29
- patchwork-os recipe run daily-status # run one now
30
- patchwork-os # open terminal dashboard
28
+ patchwork-os recipe list # see installed recipes
29
+ patchwork-os recipe run daily-status # run one now
30
+ patchwork-os recipe run morning-brief --local # run with local LLM
31
+ patchwork-os tools list # browse all 140+ tools
32
+ patchwork-os # open terminal dashboard
31
33
  ```
32
34
 
33
- The oversight web UI runs at `http://localhost:3100` when the bridge is active.
35
+ The oversight web UI runs at `http://localhost:3100` when the bridge is active. The dashboard shows live sessions, pending approvals, recent recipe runs, and analytics.
34
36
 
35
- ## 5 starter recipes (no API key needed)
37
+ ## Starter recipes (no external API keys needed)
36
38
 
37
39
  | Recipe | Trigger | What it does |
38
40
  |---|---|---|
@@ -41,25 +43,48 @@ The oversight web UI runs at `http://localhost:3100` when the bridge is active.
41
43
  | `watch-failing-tests` | test run | drops triage note to inbox on failure |
42
44
  | `lint-on-save` | file save | surfaces new TS/JS diagnostics to inbox |
43
45
  | `stale-branches` | cron weekly | lists branches older than 30 days |
46
+ | `morning-brief` | cron 08:00 | commits + Linear issues + Calendar events |
47
+ | `sentry-to-linear` | manual | Sentry issue → Linear ticket (one-shot) |
44
48
 
45
- All 5 write to `~/.patchwork/inbox/` only. Nothing is sent anywhere without your approval.
49
+ Local recipes write to `~/.patchwork/inbox/` only. Connectors (Linear, Sentry, etc.) require API keys and approval-gated writes.
46
50
 
47
- ## Roadmap
51
+ ## What's working today
48
52
 
49
- | Phase | Status |
53
+ | Feature | Status |
50
54
  |---|---|
51
- | Foundationinit, recipes, terminal dashboard | **shipped (W1)** |
52
- | Connectors Gmail, calendar, Slack | W2 |
53
- | Mobile oversight approve from phone | W3 |
54
- | Community recipes + ecosystem | Q3 |
55
-
56
- ## From source
55
+ | `patchwork-init`one-command setup | **shipped** |
56
+ | Terminal dashboard (`patchwork-os`) | **shipped** |
57
+ | Web oversight UI (approvals, sessions, recipes) | **shipped** |
58
+ | Recipe runner (YAML, cron, manual, webhook) | **shipped** |
59
+ | Multi-provider LLM (Claude, Gemini, OpenAI, Grok, Ollama) | **shipped** |
60
+ | Linear connector (read + approval-gated write) | **shipped** |
61
+ | Sentry connector (fetch issues, stack traces) | **shipped** |
62
+ | Google Calendar connector (read-only) | **shipped** |
63
+ | Slack connector (post messages, list channels) | **shipped** |
64
+ | 140+ MCP tools (LSP, git, tests, diagnostics) | **shipped** |
65
+ | Cross-session memory (traces, handoff notes) | **shipped** |
66
+ | Gmail connector | W2 |
67
+ | Mobile oversight PWA | W3 |
68
+ | Community recipe marketplace | Q3 |
69
+
70
+ ## Install
71
+
72
+ **From the registry (recommended):**
73
+ ```bash
74
+ npm install -g patchwork-os
75
+ patchwork-os patchwork-init
76
+ ```
57
77
 
78
+ **From a local build (development / CI):**
58
79
  ```bash
59
80
  git clone https://github.com/Oolab-labs/patchwork-os
60
81
  cd patchwork-os
61
82
  npm install && npm run build
62
- node dist/index.js patchwork-init
83
+ # Use npm pack to create a real copy — do NOT use `npm install -g .`
84
+ # That creates a symlink which breaks the macOS LaunchAgent (EPERM at startup).
85
+ npm pack
86
+ npm install -g patchwork-os-*.tgz
87
+ patchwork-os patchwork-init
63
88
  ```
64
89
 
65
90
  ## License
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env bash
2
+ # deploy/bootstrap-vps.sh
3
+ # Full VPS bootstrap for patchworkos.com
4
+ # Run as root on a fresh Ubuntu 24.04 VPS
5
+ # Usage: bash bootstrap-vps.sh
6
+
7
+ set -euo pipefail
8
+
9
+ DOMAIN="patchworkos.com"
10
+ EMAIL="support@gigsecure.co.ke"
11
+ BRIDGE_PORT=3284
12
+ BRIDGE_USER="patchwork"
13
+ BRIDGE_HOME="/opt/patchwork"
14
+ NODE_VERSION="22"
15
+
16
+ # ── Colours ──────────────────────────────────────────────────────────────────
17
+ RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
18
+ info() { echo -e "${GREEN}[bootstrap]${NC} $*"; }
19
+ warn() { echo -e "${YELLOW}[bootstrap]${NC} $*"; }
20
+ error() { echo -e "${RED}[bootstrap]${NC} $*"; exit 1; }
21
+
22
+ # ── 1. System updates ─────────────────────────────────────────────────────────
23
+ info "Updating system packages..."
24
+ apt-get update -qq
25
+ apt-get upgrade -y -qq
26
+ apt-get install -y -qq curl wget gnupg2 ca-certificates lsb-release \
27
+ nginx certbot python3-certbot-nginx ufw git jq unzip
28
+
29
+ # ── 2. Node.js ────────────────────────────────────────────────────────────────
30
+ info "Installing Node.js ${NODE_VERSION}..."
31
+ curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - >/dev/null
32
+ apt-get install -y -qq nodejs
33
+ node --version
34
+ npm --version
35
+
36
+ # ── 3. Service user ───────────────────────────────────────────────────────────
37
+ info "Creating service user '${BRIDGE_USER}'..."
38
+ id "${BRIDGE_USER}" &>/dev/null || useradd -r -m -d "${BRIDGE_HOME}" -s /bin/bash "${BRIDGE_USER}"
39
+ mkdir -p "${BRIDGE_HOME}"
40
+ chown "${BRIDGE_USER}:${BRIDGE_USER}" "${BRIDGE_HOME}"
41
+
42
+ # ── 4. Install bridge globally ────────────────────────────────────────────────
43
+ info "Installing claude-ide-bridge from npm..."
44
+ npm install -g claude-ide-bridge 2>&1 | tail -5
45
+ BRIDGE_BIN="$(which claude-ide-bridge)"
46
+ info "Bridge binary: ${BRIDGE_BIN}"
47
+
48
+ # ── 5. Generate fixed token ───────────────────────────────────────────────────
49
+ FIXED_TOKEN="$(uuidgen | tr '[:upper:]' '[:lower:]')"
50
+ info "Generated bridge token (save this!): ${FIXED_TOKEN}"
51
+
52
+ # Persist token to a file readable only by root + patchwork user
53
+ TOKEN_FILE="/etc/patchwork/bridge-token"
54
+ mkdir -p /etc/patchwork
55
+ echo "${FIXED_TOKEN}" > "${TOKEN_FILE}"
56
+ chown root:"${BRIDGE_USER}" "${TOKEN_FILE}"
57
+ chmod 640 "${TOKEN_FILE}"
58
+
59
+ # ── 6. Systemd service ────────────────────────────────────────────────────────
60
+ info "Writing systemd service..."
61
+ cat > /etc/systemd/system/patchwork-bridge.service <<EOF
62
+ [Unit]
63
+ Description=Patchwork OS — Claude IDE Bridge
64
+ After=network-online.target
65
+ Wants=network-online.target
66
+
67
+ [Service]
68
+ Type=simple
69
+ User=${BRIDGE_USER}
70
+ Group=${BRIDGE_USER}
71
+ WorkingDirectory=${BRIDGE_HOME}
72
+ Environment=NODE_ENV=production
73
+ Environment=HOME=${BRIDGE_HOME}
74
+
75
+ ExecStart=${BRIDGE_BIN} \\
76
+ --bind 0.0.0.0 \\
77
+ --port ${BRIDGE_PORT} \\
78
+ --vps \\
79
+ --issuer-url https://${DOMAIN} \\
80
+ --cors-origin https://claude.ai \\
81
+ --cors-origin https://app.patchworkos.com \\
82
+ --fixed-token ${FIXED_TOKEN}
83
+
84
+ Restart=always
85
+ RestartSec=5
86
+ StandardOutput=journal
87
+ StandardError=journal
88
+ SyslogIdentifier=patchwork-bridge
89
+
90
+ # Hardening
91
+ NoNewPrivileges=true
92
+ ProtectSystem=strict
93
+ ProtectHome=read-only
94
+ ReadWritePaths=${BRIDGE_HOME}
95
+ PrivateTmp=true
96
+
97
+ [Install]
98
+ WantedBy=multi-user.target
99
+ EOF
100
+
101
+ systemctl daemon-reload
102
+ systemctl enable patchwork-bridge
103
+
104
+ # ── 7. UFW firewall ───────────────────────────────────────────────────────────
105
+ info "Configuring firewall..."
106
+ ufw --force reset >/dev/null
107
+ ufw default deny incoming >/dev/null
108
+ ufw default allow outgoing >/dev/null
109
+ ufw allow ssh >/dev/null
110
+ ufw allow http >/dev/null
111
+ ufw allow https >/dev/null
112
+ ufw --force enable >/dev/null
113
+ ufw status
114
+
115
+ # ── 8. Nginx config — HTTP only first (certbot needs nginx up to issue cert) ──
116
+ info "Writing nginx config (HTTP only)..."
117
+ cat > /etc/nginx/sites-available/patchworkos <<'NGINX'
118
+ server {
119
+ listen 80;
120
+ listen [::]:80;
121
+ server_name patchworkos.com www.patchworkos.com;
122
+
123
+ location /.well-known/acme-challenge/ { root /var/www/html; }
124
+
125
+ location / {
126
+ proxy_pass http://127.0.0.1:3284;
127
+ proxy_http_version 1.1;
128
+ proxy_set_header Upgrade $http_upgrade;
129
+ proxy_set_header Connection "upgrade";
130
+ proxy_set_header Host $host;
131
+ proxy_set_header X-Real-IP $remote_addr;
132
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
133
+ proxy_set_header X-Forwarded-Proto $scheme;
134
+ proxy_read_timeout 3600s;
135
+ proxy_send_timeout 3600s;
136
+ }
137
+ }
138
+ NGINX
139
+
140
+ ln -sf /etc/nginx/sites-available/patchworkos /etc/nginx/sites-enabled/patchworkos
141
+ rm -f /etc/nginx/sites-enabled/default
142
+
143
+ nginx -t
144
+ systemctl reload nginx
145
+
146
+ # ── 9. TLS certificate ────────────────────────────────────────────────────────
147
+ info "Issuing Let's Encrypt certificate for ${DOMAIN}..."
148
+ certbot --nginx \
149
+ -d "${DOMAIN}" \
150
+ -d "www.${DOMAIN}" \
151
+ --non-interactive \
152
+ --agree-tos \
153
+ --email "${EMAIL}" \
154
+ --redirect
155
+
156
+ # Certbot rewrites the nginx config with SSL — reload to apply
157
+ systemctl reload nginx
158
+
159
+ # ── 10. Start bridge ──────────────────────────────────────────────────────────
160
+ info "Starting bridge service..."
161
+ systemctl start patchwork-bridge
162
+ sleep 3
163
+ systemctl is-active patchwork-bridge && info "Bridge is running." || warn "Bridge may have failed — check: journalctl -u patchwork-bridge -n 50"
164
+
165
+ # ── 11. Print summary ─────────────────────────────────────────────────────────
166
+ echo ""
167
+ echo -e "${GREEN}═══════════════════════════════════════════════════${NC}"
168
+ echo -e "${GREEN} Patchwork OS — VPS bootstrap complete${NC}"
169
+ echo -e "${GREEN}═══════════════════════════════════════════════════${NC}"
170
+ echo ""
171
+ echo " Domain: https://${DOMAIN}"
172
+ echo " Bridge port: ${BRIDGE_PORT} (internal, nginx proxied)"
173
+ echo " Token file: ${TOKEN_FILE}"
174
+ echo " Token: ${FIXED_TOKEN}"
175
+ echo ""
176
+ echo " Service: systemctl status patchwork-bridge"
177
+ echo " Logs: journalctl -u patchwork-bridge -f"
178
+ echo " Nginx logs: tail -f /var/log/nginx/access.log"
179
+ echo ""
180
+ echo -e "${YELLOW} Save the token above — you'll need it to connect Claude Code.${NC}"
181
+ echo ""
182
+ echo " To connect Claude Code remotely:"
183
+ echo " claude mcp add patchwork https://${DOMAIN}/mcp --token ${FIXED_TOKEN}"
184
+ echo ""
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-dashboard.sh — Build dashboard locally and deploy to VPS
3
+ # Run from Mac: bash deploy/deploy-dashboard.sh
4
+ set -euo pipefail
5
+
6
+ VPS="root@185.167.97.141"
7
+ REMOTE_DIR="/opt/patchwork-dashboard"
8
+ PM2_NAME="patchwork-dashboard"
9
+ PORT=3200
10
+ NGINX_CONF="/etc/nginx/sites-available/patchworkos"
11
+ DASHBOARD_URL="https://patchworkos.com/dashboard"
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15
+ DASHBOARD_DIR="$REPO_ROOT/dashboard"
16
+
17
+ echo "==> Building dashboard..."
18
+ cd "$DASHBOARD_DIR"
19
+ npm run build
20
+
21
+ echo "==> Packaging standalone build..."
22
+ TARBALL="/tmp/patchwork-dashboard.tar.gz"
23
+ STAGE="/tmp/patchwork-dashboard-stage"
24
+ rm -rf "$STAGE" && mkdir -p "$STAGE"
25
+
26
+ # Copy standalone output
27
+ cp -r "$DASHBOARD_DIR/.next/standalone/." "$STAGE/"
28
+ # Standalone needs static assets in .next/static
29
+ mkdir -p "$STAGE/.next/static"
30
+ cp -r "$DASHBOARD_DIR/.next/static/." "$STAGE/.next/static/"
31
+ # Copy public dir if it exists
32
+ if [ -d "$DASHBOARD_DIR/public" ]; then
33
+ cp -r "$DASHBOARD_DIR/public/." "$STAGE/public/"
34
+ fi
35
+
36
+ tar -czf "$TARBALL" --no-xattrs -C "$STAGE" .
37
+
38
+ echo "==> Copying tarball to VPS..."
39
+ scp "$TARBALL" "$VPS:/tmp/patchwork-dashboard.tar.gz"
40
+
41
+ echo "==> Deploying on VPS..."
42
+ # shellcheck disable=SC2087
43
+ ssh "$VPS" bash <<'REMOTE'
44
+ set -euo pipefail
45
+ REMOTE_DIR="/opt/patchwork-dashboard"
46
+ PM2_NAME="patchwork-dashboard"
47
+ PORT=3200
48
+
49
+ # Stop existing PM2 process if running
50
+ if pm2 list | grep -q "$PM2_NAME"; then
51
+ pm2 stop "$PM2_NAME" || true
52
+ pm2 delete "$PM2_NAME" || true
53
+ fi
54
+
55
+ # Wipe and recreate deploy dir
56
+ rm -rf "$REMOTE_DIR"
57
+ mkdir -p "$REMOTE_DIR"
58
+
59
+ # Extract
60
+ tar -xzf /tmp/patchwork-dashboard.tar.gz -C "$REMOTE_DIR"
61
+ rm /tmp/patchwork-dashboard.tar.gz
62
+
63
+ # Copy static assets into standalone's expected location
64
+ mkdir -p "$REMOTE_DIR/.next"
65
+ if [ -d "$REMOTE_DIR/.next/static" ]; then
66
+ echo "static dir already in place"
67
+ else
68
+ # tar may have extracted flat; handle both layouts
69
+ if [ -d /tmp/dashboard-static ]; then
70
+ cp -r /tmp/dashboard-static "$REMOTE_DIR/.next/static"
71
+ fi
72
+ fi
73
+
74
+ # Also copy public dir if present
75
+ if [ -d "$REMOTE_DIR/public" ]; then
76
+ echo "public dir in place"
77
+ fi
78
+
79
+ # Write .env.local — secrets must be set via environment before running this script:
80
+ # PATCHWORK_BRIDGE_TOKEN, DASHBOARD_PASSWORD
81
+ # PATCHWORK_BRIDGE_TOKEN is the bridge auth token (from: patchwork print-token)
82
+ # DASHBOARD_PASSWORD protects the dashboard UI (leave blank to disable auth)
83
+ if [ -f "$REMOTE_DIR/.env.local" ]; then
84
+ echo ".env.local already exists on VPS — preserving (delete manually to reset)"
85
+ else
86
+ cat > "$REMOTE_DIR/.env.local" <<ENV
87
+ NEXT_PUBLIC_BASE_PATH=/dashboard
88
+ PATCHWORK_BRIDGE_URL=https://patchworkos.com
89
+ PATCHWORK_BRIDGE_TOKEN=${PATCHWORK_BRIDGE_TOKEN:-REPLACE_ME}
90
+ VAPID_SUBJECT=mailto:support@gigsecure.co.ke
91
+ DASHBOARD_PASSWORD=${DASHBOARD_PASSWORD:-}
92
+ ENV
93
+ chmod 600 "$REMOTE_DIR/.env.local"
94
+ echo "Wrote .env.local — review and update secrets if placeholders remain"
95
+ fi
96
+
97
+ # Install PM2 if missing
98
+ which pm2 || npm install -g pm2
99
+
100
+ # Start with PM2
101
+ cd "$REMOTE_DIR"
102
+ PORT=3200 pm2 start server.js --name "$PM2_NAME"
103
+
104
+ pm2 save
105
+ echo "PM2 started: $PM2_NAME on port $PORT"
106
+ REMOTE
107
+
108
+ echo "==> Configuring nginx..."
109
+ ssh "$VPS" bash <<'NGINX'
110
+ set -euo pipefail
111
+ NGINX_CONF="/etc/nginx/sites-available/patchworkos"
112
+
113
+ # Add SSE location block if missing
114
+ if ! grep -q "location /dashboard/api/bridge/stream" "$NGINX_CONF"; then
115
+ # Insert before the closing brace of the SSL server block
116
+ # We insert just before the last `}` that closes the server block listening on 443
117
+ python3 - "$NGINX_CONF" <<'PYEOF'
118
+ import sys, re
119
+
120
+ path = sys.argv[1]
121
+ with open(path) as f:
122
+ content = f.read()
123
+
124
+ sse_block = """
125
+ # SSE passthrough — no buffering
126
+ location /dashboard/api/bridge/stream {
127
+ proxy_pass http://127.0.0.1:3200;
128
+ proxy_http_version 1.1;
129
+ proxy_buffering off;
130
+ proxy_cache off;
131
+ proxy_read_timeout 86400s;
132
+ proxy_send_timeout 86400s;
133
+ proxy_set_header Host $host;
134
+ proxy_set_header X-Real-IP $remote_addr;
135
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
136
+ proxy_set_header X-Forwarded-Proto $scheme;
137
+ add_header X-Accel-Buffering no;
138
+ }
139
+
140
+ # Dashboard app
141
+ location /dashboard {
142
+ proxy_pass http://127.0.0.1:3200;
143
+ proxy_http_version 1.1;
144
+ proxy_set_header Host $host;
145
+ proxy_set_header X-Real-IP $remote_addr;
146
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
147
+ proxy_set_header X-Forwarded-Proto $scheme;
148
+ proxy_read_timeout 60s;
149
+ }
150
+ """
151
+
152
+ # Find the ssl/443 server block and insert before its closing brace
153
+ # Strategy: find last `}` in file and insert before it
154
+ idx = content.rfind('\n}')
155
+ if idx == -1:
156
+ print("ERROR: could not find closing brace in nginx config", file=sys.stderr)
157
+ sys.exit(1)
158
+
159
+ new_content = content[:idx] + sse_block + content[idx:]
160
+ with open(path, 'w') as f:
161
+ f.write(new_content)
162
+ print("nginx: location blocks inserted")
163
+ PYEOF
164
+ else
165
+ echo "nginx: location blocks already present, skipping"
166
+ fi
167
+
168
+ nginx -t && systemctl reload nginx
169
+ echo "nginx reloaded"
170
+ NGINX
171
+
172
+ echo ""
173
+ echo "==> Deploy complete!"
174
+ echo " Dashboard: https://patchworkos.com/dashboard"
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-landing.sh — Deploy static landing page to VPS
3
+ # Run from Mac: bash deploy/deploy-landing.sh
4
+ set -euo pipefail
5
+
6
+ VPS="root@185.167.97.141"
7
+ LANDING_DIR="/var/www/patchwork-landing"
8
+ NGINX_CONF="/etc/nginx/sites-available/patchworkos"
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
11
+
12
+ echo "==> Copying landing page to VPS..."
13
+ ssh "$VPS" "mkdir -p $LANDING_DIR"
14
+ scp "$REPO_ROOT/landing/index.html" "$VPS:$LANDING_DIR/index.html"
15
+
16
+ echo "==> Updating nginx to serve landing page at root..."
17
+ ssh "$VPS" bash <<'REMOTE'
18
+ set -euo pipefail
19
+ NGINX_CONF="/etc/nginx/sites-available/patchworkos"
20
+
21
+ # Insert landing page root location if not already present
22
+ if ! grep -q "location = /" "$NGINX_CONF"; then
23
+ python3 - "$NGINX_CONF" <<'PYEOF'
24
+ import sys, re
25
+
26
+ path = sys.argv[1]
27
+ with open(path) as f:
28
+ content = f.read()
29
+
30
+ landing_blocks = """
31
+ # Static landing page at root
32
+ location = / {
33
+ root /var/www/patchwork-landing;
34
+ try_files /index.html =404;
35
+ }
36
+
37
+ # Bridge API paths — proxy to bridge
38
+ location ~ ^/(mcp|oauth|notify|\.well-known|metrics|health|approvals|activity|sessions|traces|recipes|connectors|settings|schemas|push) {
39
+ proxy_pass http://127.0.0.1:3284;
40
+ proxy_http_version 1.1;
41
+ proxy_set_header Upgrade $http_upgrade;
42
+ proxy_set_header Connection "upgrade";
43
+ proxy_set_header Host $host;
44
+ proxy_set_header X-Real-IP $remote_addr;
45
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
46
+ proxy_set_header X-Forwarded-Proto $scheme;
47
+ proxy_read_timeout 3600s;
48
+ proxy_send_timeout 3600s;
49
+ }
50
+ """
51
+
52
+ # Insert before the SSE/dashboard blocks (before first "location /dashboard")
53
+ # or before the last closing brace if those don't exist
54
+ if 'location /dashboard' in content:
55
+ idx = content.find(' # SSE passthrough')
56
+ if idx == -1:
57
+ idx = content.find(' location /dashboard')
58
+ else:
59
+ idx = content.rfind('\n}')
60
+
61
+ if idx == -1:
62
+ idx = content.rfind('\n}')
63
+
64
+ new_content = content[:idx] + landing_blocks + content[idx:]
65
+ with open(path, 'w') as f:
66
+ f.write(new_content)
67
+ print("nginx: landing + bridge location blocks inserted")
68
+ PYEOF
69
+ else
70
+ echo "nginx: landing location already present, skipping"
71
+ fi
72
+
73
+ nginx -t && systemctl reload nginx
74
+ echo "nginx reloaded"
75
+ REMOTE
76
+
77
+ echo ""
78
+ echo "==> Deploy complete!"
79
+ echo " Landing page: https://patchworkos.com"
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Activation metrics — a small, local-only counter file that tracks how far
3
+ * along the user is in the "first success" journey. Nothing here is ever
4
+ * transmitted off-machine. The outbound usage analytics pipeline lives in
5
+ * `analyticsAggregator.ts` / `analyticsSend.ts` and is a separate concern.
6
+ *
7
+ * Intended counters:
8
+ * - installedAt first time any metric was recorded
9
+ * - firstRecipeRunAt timestamp of the first successful recipe run
10
+ * - recipeRunsTotal running count of successful recipe runs
11
+ * - recipeRunsByDay map of YYYY-MM-DD -> count, trimmed to 14 days
12
+ * - approvalsPrompted count of approval requests created
13
+ * - approvalsCompleted count of approvals approved or rejected (not timed out)
14
+ *
15
+ * These feed a single "first success in N days" KPI card on the dashboard.
16
+ *
17
+ * Opt-out: if the existing outbound analytics preference is explicitly `false`
18
+ * via `getAnalyticsPref()`, all record operations become no-ops. The user does
19
+ * not need to re-opt-out; the existing opt-out is sufficient. Reads are
20
+ * always allowed (they never leave the machine).
21
+ *
22
+ * Storage: `~/.patchwork/telemetry.json` (respects PATCHWORK_HOME override).
23
+ * File is written atomically via tmp+rename with 0o600 permissions.
24
+ */
25
+ export interface ActivationMetrics {
26
+ installedAt: number;
27
+ firstRecipeRunAt: number | null;
28
+ recipeRunsTotal: number;
29
+ recipeRunsByDay: Record<string, number>;
30
+ approvalsPrompted: number;
31
+ approvalsCompleted: number;
32
+ }
33
+ export interface ActivationSummary {
34
+ installedAt: number;
35
+ firstRecipeRunAt: number | null;
36
+ /** Milliseconds between install and first successful recipe run. Null until first success. */
37
+ timeToFirstRecipeRunMs: number | null;
38
+ /** Total successful recipe runs across all time. */
39
+ recipeRunsTotal: number;
40
+ /** Recipe runs in the last 7 days (inclusive of today). */
41
+ recipeRunsLast7Days: number;
42
+ /** Distinct calendar days with at least one recipe run in the last 7 days. */
43
+ activeDaysLast7: number;
44
+ /** Fraction of prompted approvals that were completed (approved or rejected). 0..1 or null. */
45
+ approvalCompletionRate: number | null;
46
+ approvalsPrompted: number;
47
+ approvalsCompleted: number;
48
+ }
49
+ /**
50
+ * Load metrics from disk. Returns a fresh empty record if the file is missing
51
+ * or malformed. Never throws on I/O errors.
52
+ */
53
+ export declare function loadMetrics(configDir?: string, now?: number): ActivationMetrics;
54
+ /**
55
+ * Record one successful recipe run. Sets `firstRecipeRunAt` on the first call
56
+ * and increments total + per-day counters. Trims the per-day map so the file
57
+ * stays O(14 entries).
58
+ *
59
+ * No-op if the user has explicitly opted out of analytics.
60
+ */
61
+ export declare function recordRecipeRun(configDir?: string, now?: number): void;
62
+ /** Record an approval prompt being surfaced to the user. */
63
+ export declare function recordApprovalPrompted(configDir?: string, now?: number): void;
64
+ /** Record an approval being acted on (approved or rejected — not a timeout). */
65
+ export declare function recordApprovalCompleted(configDir?: string, now?: number): void;
66
+ /** Derive a dashboard-friendly summary from a metrics record. Pure function. */
67
+ export declare function computeSummary(metrics: ActivationMetrics, now?: number): ActivationSummary;