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.
Files changed (136) hide show
  1. package/README.md +41 -46
  2. package/dist/bridge.js +23 -10
  3. package/dist/bridge.js.map +1 -1
  4. package/dist/claudeDriver.d.ts +3 -1
  5. package/dist/claudeDriver.js +48 -0
  6. package/dist/claudeDriver.js.map +1 -1
  7. package/dist/commands/dashboard.d.ts +47 -0
  8. package/dist/commands/dashboard.js +319 -0
  9. package/dist/commands/dashboard.js.map +1 -0
  10. package/dist/config.d.ts +2 -2
  11. package/dist/config.js +5 -2
  12. package/dist/config.js.map +1 -1
  13. package/dist/connectors/github.d.ts +94 -0
  14. package/dist/connectors/github.js +350 -0
  15. package/dist/connectors/github.js.map +1 -0
  16. package/dist/connectors/gmail.d.ts +40 -0
  17. package/dist/connectors/gmail.js +304 -0
  18. package/dist/connectors/gmail.js.map +1 -0
  19. package/dist/connectors/googleCalendar.d.ts +57 -0
  20. package/dist/connectors/googleCalendar.js +308 -0
  21. package/dist/connectors/googleCalendar.js.map +1 -0
  22. package/dist/connectors/linear.d.ts +117 -0
  23. package/dist/connectors/linear.js +248 -0
  24. package/dist/connectors/linear.js.map +1 -0
  25. package/dist/connectors/mcpClient.d.ts +56 -0
  26. package/dist/connectors/mcpClient.js +189 -0
  27. package/dist/connectors/mcpClient.js.map +1 -0
  28. package/dist/connectors/mcpOAuth.d.ts +83 -0
  29. package/dist/connectors/mcpOAuth.js +363 -0
  30. package/dist/connectors/mcpOAuth.js.map +1 -0
  31. package/dist/connectors/sentry.d.ts +43 -0
  32. package/dist/connectors/sentry.js +197 -0
  33. package/dist/connectors/sentry.js.map +1 -0
  34. package/dist/connectors/slack.d.ts +50 -0
  35. package/dist/connectors/slack.js +254 -0
  36. package/dist/connectors/slack.js.map +1 -0
  37. package/dist/drivers/claude/api.d.ts +11 -0
  38. package/dist/drivers/claude/api.js +54 -0
  39. package/dist/drivers/claude/api.js.map +1 -0
  40. package/dist/drivers/claude/envSanitizer.d.ts +7 -0
  41. package/dist/drivers/claude/envSanitizer.js +18 -0
  42. package/dist/drivers/claude/envSanitizer.js.map +1 -0
  43. package/dist/drivers/claude/streamParser.d.ts +38 -0
  44. package/dist/drivers/claude/streamParser.js +34 -0
  45. package/dist/drivers/claude/streamParser.js.map +1 -0
  46. package/dist/drivers/claude/subprocess.d.ts +19 -0
  47. package/dist/drivers/claude/subprocess.js +216 -0
  48. package/dist/drivers/claude/subprocess.js.map +1 -0
  49. package/dist/drivers/claude/subprocessSettings.d.ts +9 -0
  50. package/dist/drivers/claude/subprocessSettings.js +55 -0
  51. package/dist/drivers/claude/subprocessSettings.js.map +1 -0
  52. package/dist/drivers/gemini/index.d.ts +14 -0
  53. package/dist/drivers/gemini/index.js +176 -0
  54. package/dist/drivers/gemini/index.js.map +1 -0
  55. package/dist/drivers/grok/index.d.ts +11 -0
  56. package/dist/drivers/grok/index.js +22 -0
  57. package/dist/drivers/grok/index.js.map +1 -0
  58. package/dist/drivers/index.d.ts +18 -0
  59. package/dist/drivers/index.js +31 -0
  60. package/dist/drivers/index.js.map +1 -0
  61. package/dist/drivers/openai/index.d.ts +24 -0
  62. package/dist/drivers/openai/index.js +110 -0
  63. package/dist/drivers/openai/index.js.map +1 -0
  64. package/dist/drivers/types.d.ts +72 -0
  65. package/dist/drivers/types.js +30 -0
  66. package/dist/drivers/types.js.map +1 -0
  67. package/dist/index.js +116 -22
  68. package/dist/index.js.map +1 -1
  69. package/dist/recipes/yamlRunner.d.ts +104 -0
  70. package/dist/recipes/yamlRunner.js +683 -0
  71. package/dist/recipes/yamlRunner.js.map +1 -0
  72. package/dist/recipesHttp.d.ts +13 -1
  73. package/dist/recipesHttp.js +9 -1
  74. package/dist/recipesHttp.js.map +1 -1
  75. package/dist/runLog.d.ts +5 -0
  76. package/dist/runLog.js +44 -0
  77. package/dist/runLog.js.map +1 -1
  78. package/dist/server.d.ts +3 -1
  79. package/dist/server.js +490 -2
  80. package/dist/server.js.map +1 -1
  81. package/dist/tools/addLinearComment.d.ts +55 -0
  82. package/dist/tools/addLinearComment.js +70 -0
  83. package/dist/tools/addLinearComment.js.map +1 -0
  84. package/dist/tools/createLinearIssue.d.ts +84 -0
  85. package/dist/tools/createLinearIssue.js +146 -0
  86. package/dist/tools/createLinearIssue.js.map +1 -0
  87. package/dist/tools/ctxGetTaskContext.d.ts +4 -1
  88. package/dist/tools/ctxGetTaskContext.js +45 -2
  89. package/dist/tools/ctxGetTaskContext.js.map +1 -1
  90. package/dist/tools/fetchCalendarEvents.d.ts +94 -0
  91. package/dist/tools/fetchCalendarEvents.js +97 -0
  92. package/dist/tools/fetchCalendarEvents.js.map +1 -0
  93. package/dist/tools/fetchGithubIssue.d.ts +80 -0
  94. package/dist/tools/fetchGithubIssue.js +84 -0
  95. package/dist/tools/fetchGithubIssue.js.map +1 -0
  96. package/dist/tools/fetchGithubPR.d.ts +89 -0
  97. package/dist/tools/fetchGithubPR.js +96 -0
  98. package/dist/tools/fetchGithubPR.js.map +1 -0
  99. package/dist/tools/fetchLinearIssue.d.ts +112 -0
  100. package/dist/tools/fetchLinearIssue.js +129 -0
  101. package/dist/tools/fetchLinearIssue.js.map +1 -0
  102. package/dist/tools/fetchSentryIssue.d.ts +143 -0
  103. package/dist/tools/fetchSentryIssue.js +150 -0
  104. package/dist/tools/fetchSentryIssue.js.map +1 -0
  105. package/dist/tools/fetchSlackProfile.d.ts +43 -0
  106. package/dist/tools/fetchSlackProfile.js +43 -0
  107. package/dist/tools/fetchSlackProfile.js.map +1 -0
  108. package/dist/tools/getConnectorStatus.d.ts +58 -0
  109. package/dist/tools/getConnectorStatus.js +56 -0
  110. package/dist/tools/getConnectorStatus.js.map +1 -0
  111. package/dist/tools/github/index.d.ts +1 -1
  112. package/dist/tools/github/index.js +1 -1
  113. package/dist/tools/github/index.js.map +1 -1
  114. package/dist/tools/github/pr.d.ts +122 -0
  115. package/dist/tools/github/pr.js +152 -0
  116. package/dist/tools/github/pr.js.map +1 -1
  117. package/dist/tools/index.js +27 -1
  118. package/dist/tools/index.js.map +1 -1
  119. package/dist/tools/slackListChannels.d.ts +65 -0
  120. package/dist/tools/slackListChannels.js +70 -0
  121. package/dist/tools/slackListChannels.js.map +1 -0
  122. package/dist/tools/slackPostMessage.d.ts +57 -0
  123. package/dist/tools/slackPostMessage.js +72 -0
  124. package/dist/tools/slackPostMessage.js.map +1 -0
  125. package/dist/tools/updateLinearIssue.d.ts +89 -0
  126. package/dist/tools/updateLinearIssue.js +103 -0
  127. package/dist/tools/updateLinearIssue.js.map +1 -0
  128. package/package.json +1 -1
  129. package/scripts/start-all.sh +56 -19
  130. package/templates/recipes/ctx-loop-test.yaml +75 -0
  131. package/templates/recipes/gmail-health-check.yaml +19 -0
  132. package/templates/recipes/inbox-triage.yaml +15 -0
  133. package/templates/recipes/morning-brief-slack.yaml +54 -0
  134. package/templates/recipes/morning-brief.yaml +72 -0
  135. package/templates/recipes/sentry-to-linear.yaml +77 -0
  136. package/templates/scheduled-tasks/morning-brief/SKILL.md +37 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchwork-os",
3
- "version": "0.2.0-alpha.0",
3
+ "version": "0.2.0-alpha.11",
4
4
  "description": "Patchwork OS — proactive, multi-model AI teammate built on the Claude IDE Bridge. One agent, any model, works while you're away.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
- # Full orchestrator for bridge + claude + remote-control.
3
- # Manages all three in tmux panes with health monitoring.
2
+ # Full orchestrator for bridge + claude + remote-control + dashboard.
3
+ # Manages all processes in tmux panes with health monitoring.
4
4
  #
5
5
  # Pane layout:
6
6
  # 0 — health monitor (orchestrator)
@@ -8,7 +8,8 @@
8
8
  # 2 — claude --ide (Claude Code CLI, connects to bridge for IDE tools)
9
9
  # 3 — claude remote-control --spawn=session (exposes the workspace to claude.ai;
10
10
  # spawned sessions auto-discover the bridge via ~/.claude/ide/*.lock)
11
- # 4 — SSH reverse tunnel to VPS (optional, enabled with --vps <user@host:port>)
11
+ # 4 — Patchwork dashboard (Next.js, http://localhost:3200)
12
+ # 5 — SSH reverse tunnel to VPS (optional, enabled with --vps <user@host:port>)
12
13
  # Forwards VPS_PORT on the remote host back to the local bridge port.
13
14
  # Allows claude.ai integrations to use a static VPS URL instead of a
14
15
  # rotating remote-control session URL.
@@ -20,6 +21,8 @@
20
21
  # Options:
21
22
  # --workspace <path> Directory to open in Claude (default: current directory)
22
23
  # --full Register all ~95 bridge tools (git, terminal, file ops, HTTP, GitHub).
24
+ # --no-dashboard Skip starting the Patchwork dashboard (pane 4).
25
+ # --dashboard-port <N> Dashboard port (default: 3200).
23
26
  # Default is slim mode (27 IDE-exclusive tools). Add --full if your
24
27
  # workflow requires git/terminal/file tools.
25
28
  # --notify <topic> Push notifications via ntfy.sh when remote-control connects or
@@ -48,6 +51,8 @@ NTFY_TOPIC=""
48
51
  IDE_NAME=""
49
52
  VPS=""
50
53
  FULL_MODE=""
54
+ NO_DASHBOARD=""
55
+ DASHBOARD_PORT="3200"
51
56
  AUTOMATION_POLICY=""
52
57
  CLAUDE_DRIVER="subprocess"
53
58
  BRIDGE_READY_TIMEOUT="${BRIDGE_READY_TIMEOUT:-30}"
@@ -64,6 +69,8 @@ while [[ $# -gt 0 ]]; do
64
69
  --ide) IDE_NAME="$2"; shift 2 ;;
65
70
  --vps) VPS="$2"; shift 2 ;;
66
71
  --full) FULL_MODE="--full"; shift ;;
72
+ --no-dashboard) NO_DASHBOARD=1; shift ;;
73
+ --dashboard-port) DASHBOARD_PORT="$2"; shift 2 ;;
67
74
  --automation-policy) AUTOMATION_POLICY="$2"; shift 2 ;;
68
75
  --claude-driver) CLAUDE_DRIVER="$2"; shift 2 ;;
69
76
  *) echo "Unknown option: $1" >&2; exit 1 ;;
@@ -90,23 +97,31 @@ fi
90
97
 
91
98
  # --- Bridge conflict check ---
92
99
  # Detect an already-running bridge instance and abort early with a clear message.
93
- # Iterates lock files safely (no xargs injection); filters to isBridge:true + live PID.
100
+ # Validates: isBridge:true + live PID + process is actually a bridge (not Windsurf reusing PID)
101
+ # by checking whether the lock file's port has an active listener.
94
102
  while IFS= read -r lock; do
95
103
  [[ -f "$lock" ]] || continue
96
- bridge_pid=$(python3 -c "
104
+ lock_info=$(python3 -c "
97
105
  import json, sys
98
106
  try:
99
107
  d = json.load(open(sys.argv[1]))
100
108
  if d.get('isBridge') is True:
101
- print(d.get('pid', ''))
109
+ print(d.get('pid', ''), d.get('port', ''))
102
110
  except Exception:
103
111
  pass
104
112
  " "$lock" 2>/dev/null || true)
113
+ bridge_pid="${lock_info%% *}"
114
+ bridge_port="${lock_info##* }"
105
115
  if [[ -n "$bridge_pid" ]] && kill -0 "$bridge_pid" 2>/dev/null; then
106
- echo "Error: bridge already running (PID $bridge_pid, lock: $(basename "$lock"))." >&2
107
- echo " Stop it first: kill $bridge_pid" >&2
108
- echo " Or kill the tmux session: tmux kill-session -t claude-all" >&2
109
- exit 1
116
+ # Cross-check: verify a process is actually listening on the lock's port.
117
+ # Windsurf may reuse PIDs from stale locks — if nothing listens on that port,
118
+ # the bridge is not actually running.
119
+ if [[ -n "$bridge_port" ]] && lsof -i ":${bridge_port}" 2>/dev/null | grep -q LISTEN; then
120
+ echo "Error: bridge already running (PID $bridge_pid, port $bridge_port, lock: $(basename "$lock"))." >&2
121
+ echo " Stop it first: kill $bridge_pid" >&2
122
+ echo " Or kill the tmux session: tmux kill-session -t claude-all" >&2
123
+ exit 1
124
+ fi
110
125
  fi
111
126
  done < <(ls ~/.claude/ide/*.lock 2>/dev/null)
112
127
 
@@ -141,6 +156,7 @@ echo "=== Claude IDE Bridge Full Orchestrator ==="
141
156
  echo " Ctrl+C in any pane — stops that process"
142
157
  echo " Ctrl+B then D (press Ctrl+B, release, then press D) — detach (keeps running)"
143
158
  echo " tmux kill-session -t $SESSION — stop everything"
159
+ [[ -z "$NO_DASHBOARD" ]] && echo " Dashboard: http://localhost:${DASHBOARD_PORT}"
144
160
  [[ -n "$NTFY_TOPIC" ]] && echo " Push notifications: ntfy.sh/$NTFY_TOPIC"
145
161
  if [[ -z "$FULL_MODE" ]]; then
146
162
  echo ""
@@ -276,11 +292,12 @@ while true; do
276
292
  sleep \$delay
277
293
  done'"
278
294
 
279
- # --- Create additional panes (pane 0 = orchestrator, 1 = bridge, 2 = claude, 3 = remote-control, 4 = ssh tunnel) ---
295
+ # --- Create additional panes (pane 0 = orchestrator, 1 = bridge, 2 = claude, 3 = remote-control, 4 = dashboard, 5 = ssh tunnel) ---
280
296
  tmux split-window -v -t "$SESSION" # pane 1 for bridge
281
297
  tmux split-window -v -t "$SESSION" # pane 2 for claude --ide
282
298
  tmux split-window -v -t "$SESSION" # pane 3 for claude remote-control
283
- [[ -n "$TUNNEL_CMD" ]] && tmux split-window -v -t "$SESSION" # pane 4 for ssh tunnel (optional)
299
+ [[ -z "$NO_DASHBOARD" ]] && tmux split-window -v -t "$SESSION" # pane 4 for dashboard (optional)
300
+ [[ -n "$TUNNEL_CMD" ]] && tmux split-window -v -t "$SESSION" # pane 5 for ssh tunnel (optional)
284
301
  tmux select-layout -t "$SESSION" even-vertical
285
302
 
286
303
  # --- Helper: wait for a NEW lock file (ignores pre-existing ones) ---
@@ -359,10 +376,20 @@ sleep 3
359
376
  # Pane 3: Remote control with auto-restart loop
360
377
  tmux send-keys -t "${SESSION}:0.3" "$REMOTE_CMD" Enter
361
378
 
362
- # Pane 4: SSH reverse tunnel (only if --vps was set)
379
+ # Pane 4: Dashboard (only if not --no-dashboard and dashboard dir exists)
380
+ DASHBOARD_DIR="$BRIDGE_DIR/dashboard"
381
+ if [[ -z "$NO_DASHBOARD" ]] && [[ -d "$DASHBOARD_DIR" ]]; then
382
+ BRIDGE_PORT=$(basename "$LOCK_FILE" .lock)
383
+ DASHBOARD_CMD="cd $(printf '%q' "$DASHBOARD_DIR") && PATCHWORK_BRIDGE_PORT=${BRIDGE_PORT} npm run dev"
384
+ notify "Starting dashboard on http://localhost:${DASHBOARD_PORT}"
385
+ tmux send-keys -t "${SESSION}:0.4" "$DASHBOARD_CMD" Enter
386
+ fi
387
+
388
+ # Pane 5: SSH reverse tunnel (only if --vps was set)
363
389
  if [[ -n "$TUNNEL_CMD" ]]; then
364
390
  notify "Starting SSH tunnel to ${VPS}..."
365
- tmux send-keys -t "${SESSION}:0.4" "$TUNNEL_CMD" Enter
391
+ TUNNEL_PANE=$([[ -z "$NO_DASHBOARD" ]] && echo 5 || echo 4)
392
+ tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE}" "$TUNNEL_CMD" Enter
366
393
  fi
367
394
 
368
395
  # --- Health monitor (runs in pane 0 — the orchestrator pane) ---
@@ -377,10 +404,12 @@ cleanup() {
377
404
  tmux send-keys -t "${SESSION}:0.1" C-c 2>/dev/null
378
405
  tmux send-keys -t "${SESSION}:0.2" C-c 2>/dev/null
379
406
  tmux send-keys -t "${SESSION}:0.3" C-c 2>/dev/null
380
- [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c 2>/dev/null
407
+ [[ -z "$NO_DASHBOARD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c 2>/dev/null
408
+ [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" C-c 2>/dev/null
381
409
  # Wait up to 5s for panes to exit gracefully, then force-kill
382
410
  local panes=(1 2 3)
383
- [[ -n "$TUNNEL_CMD" ]] && panes=(1 2 3 4)
411
+ [[ -z "$NO_DASHBOARD" ]] && panes=(1 2 3 4)
412
+ [[ -n "$TUNNEL_CMD" ]] && panes=("${panes[@]}" "${TUNNEL_PANE:-4}")
384
413
  for _ in $(seq 1 5); do
385
414
  sleep 1
386
415
  all_idle=true
@@ -396,7 +425,8 @@ cleanup() {
396
425
  done
397
426
  sleep 1
398
427
  local rpanes=(3 2 1)
399
- [[ -n "$TUNNEL_CMD" ]] && rpanes=(4 3 2 1)
428
+ [[ -z "$NO_DASHBOARD" ]] && rpanes=(4 3 2 1)
429
+ [[ -n "$TUNNEL_CMD" ]] && rpanes=("${TUNNEL_PANE:-4}" "${rpanes[@]}")
400
430
  for pane in "${rpanes[@]}"; do
401
431
  tmux kill-pane -t "${SESSION}:0.${pane}" 2>/dev/null || true
402
432
  done
@@ -413,7 +443,8 @@ while true; do
413
443
  tmux send-keys -t "${SESSION}:0.1" C-c
414
444
  tmux send-keys -t "${SESSION}:0.2" C-c
415
445
  tmux send-keys -t "${SESSION}:0.3" C-c
416
- [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c
446
+ [[ -z "$NO_DASHBOARD" ]] && tmux send-keys -t "${SESSION}:0.4" C-c
447
+ [[ -n "$TUNNEL_CMD" ]] && tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" C-c
417
448
  # Wait for bridge (pane 1) and claude (pane 2) to actually stop
418
449
  for _ in $(seq 1 10); do
419
450
  pane1_cmd=$(tmux display-message -t "${SESSION}:0.1" -p '#{pane_current_command}' 2>/dev/null || echo "")
@@ -476,9 +507,15 @@ while true; do
476
507
  sleep 3
477
508
  # Restart remote-control
478
509
  tmux send-keys -t "${SESSION}:0.3" "$REMOTE_CMD" Enter
510
+ # Restart dashboard with updated bridge port
511
+ if [[ -z "$NO_DASHBOARD" ]] && [[ -d "$DASHBOARD_DIR" ]]; then
512
+ NEW_BRIDGE_PORT=$(basename "$LOCK_FILE" .lock)
513
+ DASHBOARD_CMD="cd $(printf '%q' "$DASHBOARD_DIR") && PATCHWORK_BRIDGE_PORT=${NEW_BRIDGE_PORT} npm run dev"
514
+ tmux send-keys -t "${SESSION}:0.4" "$DASHBOARD_CMD" Enter
515
+ fi
479
516
  # Restart SSH tunnel (port may have changed — tunnel cmd reads lock file dynamically)
480
517
  if [[ -n "$TUNNEL_CMD" ]]; then
481
- tmux send-keys -t "${SESSION}:0.4" "$TUNNEL_CMD" Enter
518
+ tmux send-keys -t "${SESSION}:0.${TUNNEL_PANE:-4}" "$TUNNEL_CMD" Enter
482
519
  fi
483
520
  notify "All processes restarted"
484
521
  else
@@ -0,0 +1,75 @@
1
+ name: ctx-loop-test
2
+ description: |
3
+ End-to-end test of the agent-read loop. An agent writes a decision trace with
4
+ ctxSaveTrace, then reads it back with ctxQueryTraces, and confirms the trace
5
+ appears in the result. Output lands in ~/.patchwork/inbox/ for manual inspection.
6
+ Run this after deploying a new bridge build to confirm the memory moat is live.
7
+
8
+ NOTE: requires --claude-driver subprocess and a running bridge with HTTP port
9
+ so the agent subprocess has MCP access. Running via `patchwork recipe run`
10
+ (local yamlRunner) will fail at Step 1 — the local claude -p subprocess has
11
+ no bridge MCP context. Trigger from the dashboard Recipes page instead.
12
+ trigger:
13
+ type: manual
14
+ steps:
15
+ - tool: git.log_since
16
+ since: 1h
17
+ into: recent_commits
18
+
19
+ - agent:
20
+ prompt: |
21
+ You are validating the Patchwork cross-session memory loop end-to-end.
22
+
23
+ RECENT COMMITS (context only):
24
+ {{recent_commits}}
25
+
26
+ Perform the following steps IN ORDER. Stop immediately if any step fails and
27
+ report the failure clearly in your output.
28
+
29
+ ## Step 1 — Write a trace
30
+ Call ctxSaveTrace with these exact values:
31
+ ref: "ctx-loop-test-{{date}}"
32
+ problem: "Validating that ctxSaveTrace persists traces readable by future sessions"
33
+ solution: "ctxSaveTrace writes to decision_traces.jsonl; ctxQueryTraces reads it back"
34
+ tags: ["loop-test", "dogfood"]
35
+
36
+ Record the returned `seq` number.
37
+
38
+ ## Step 2 — Read it back
39
+ Call ctxQueryTraces with:
40
+ traceType: "decision"
41
+ limit: 5
42
+
43
+ Check that the trace you just wrote (ref "ctx-loop-test-{{date}}") appears in
44
+ the results. If it does, the loop is confirmed closed.
45
+
46
+ ## Step 3 — Write your report
47
+ Output a markdown block in this exact format:
48
+
49
+ ```
50
+ # ctx-loop-test — {{date}} {{time}}
51
+
52
+ ## Result: PASS / FAIL
53
+
54
+ ### Step 1 — ctxSaveTrace
55
+ - seq: <returned seq>
56
+ - ref: ctx-loop-test-{{date}}
57
+ - Status: OK / ERROR: <message>
58
+
59
+ ### Step 2 — ctxQueryTraces
60
+ - Traces returned: <count>
61
+ - Test trace found: yes / no
62
+ - Status: OK / ERROR: <message>
63
+
64
+ ### Verdict
65
+ <one sentence summary>
66
+ ```
67
+
68
+ Output ONLY the markdown block. No preamble.
69
+ driver: claude-code
70
+ model: claude-haiku-4-5-20251001
71
+ into: report
72
+
73
+ - tool: file.write
74
+ path: ~/.patchwork/inbox/ctx-loop-test-{{date}}.md
75
+ content: "{{report}}\n"
@@ -0,0 +1,19 @@
1
+ name: gmail-health-check
2
+ description: Verify Gmail connector is working and report inbox stats.
3
+ trigger:
4
+ type: manual
5
+ steps:
6
+ - tool: gmail.fetch_unread
7
+ since: 7d
8
+ max: 5
9
+ into: recent
10
+ - tool: gmail.search
11
+ query: "is:unread"
12
+ max: 1
13
+ into: unread_check
14
+ - tool: file.write
15
+ path: ~/.patchwork/inbox/gmail-health-{{date}}.md
16
+ content: |
17
+ # Gmail health check — {{date}} {{time}}
18
+ Unread (last 7d): {{recent.count}}
19
+ Total unread check: {{unread_check.count}}
@@ -0,0 +1,15 @@
1
+ name: inbox-triage
2
+ description: Fetch unread emails from last 24h and write a prioritised triage summary.
3
+ trigger:
4
+ type: cron
5
+ at: "0 8 * * 1-5"
6
+ steps:
7
+ - tool: gmail.fetch_unread
8
+ since: 24h
9
+ max: 50
10
+ into: messages
11
+ - tool: file.write
12
+ path: ~/.patchwork/inbox/triage-{{date}}.md
13
+ content: |
14
+ # Inbox triage — {{date}} {{time}}
15
+ Unread messages: {{messages.count}}
@@ -0,0 +1,54 @@
1
+ name: morning-brief-slack
2
+ description: Daily morning brief combining calendar, GitHub PRs, and Linear issues — posted to Slack.
3
+ trigger:
4
+ type: cron
5
+ at: "0 8 * * 1-5"
6
+ steps:
7
+ - tool: github.list_prs
8
+ author: "@me"
9
+ max: 10
10
+ into: prs
11
+ - tool: linear.list_issues
12
+ assignee: "@me"
13
+ state: "started,unstarted"
14
+ max: 15
15
+ into: linear_issues
16
+ - tool: calendar.list_events
17
+ days_ahead: 1
18
+ max: 10
19
+ into: calendar
20
+ - agent:
21
+ prompt: |
22
+ You are a personal assistant writing a concise morning brief. Use ONLY the data provided below — do not call any tools or fetch additional information.
23
+
24
+ OPEN PULL REQUESTS (authored by me):
25
+ {{prs}}
26
+
27
+ LINEAR ISSUES (assigned to me, in progress or unstarted):
28
+ {{linear_issues}}
29
+
30
+ TODAY'S CALENDAR EVENTS:
31
+ {{calendar}}
32
+
33
+ Write a short morning brief for Slack (plain text, no markdown headers, use emoji for sections):
34
+
35
+ 📅 *Calendar* — list today's meetings with time; write "No meetings today" if empty or error
36
+ 🔀 *Pull Requests* — list open PRs needing attention; omit if empty
37
+ 📋 *Linear* — list in-progress and unstarted issues by priority; omit if empty or error
38
+
39
+ Keep it under 20 lines. Be direct.
40
+ driver: claude-code
41
+ model: claude-haiku-4-5-20251001
42
+ into: brief
43
+ - tool: file.write
44
+ path: ~/.patchwork/inbox/morning-brief-{{date}}.md
45
+ content: |
46
+ # Morning brief — {{date}}
47
+
48
+ {{brief}}
49
+ - tool: slack.post_message
50
+ channel: general
51
+ text: |
52
+ *Morning Brief — {{date}}*
53
+
54
+ {{brief}}
@@ -0,0 +1,72 @@
1
+ name: morning-brief
2
+ description: Daily morning brief combining unread emails, calendar, recent git activity, and open GitHub issues.
3
+ trigger:
4
+ type: cron
5
+ at: "0 8 * * 1-5"
6
+ steps:
7
+ - tool: gmail.fetch_unread
8
+ since: 24h
9
+ max: 30
10
+ into: messages
11
+ - tool: git.log_since
12
+ since: 24h
13
+ into: commits
14
+ - tool: github.list_issues
15
+ assignee: "@me"
16
+ max: 10
17
+ into: issues
18
+ - tool: github.list_prs
19
+ author: "@me"
20
+ max: 10
21
+ into: prs
22
+ - tool: linear.list_issues
23
+ assignee: "@me"
24
+ state: "started,unstarted"
25
+ max: 15
26
+ into: linear_issues
27
+ - tool: calendar.list_events
28
+ days_ahead: 1
29
+ max: 10
30
+ into: calendar
31
+ - agent:
32
+ prompt: |
33
+ You are a personal assistant writing a concise morning brief. Use ONLY the data provided below — do not call any tools or fetch additional information.
34
+
35
+ UNREAD EMAILS ({{messages.count}} total):
36
+ <untrusted_data>
37
+ {{messages.json}}
38
+ </untrusted_data>
39
+
40
+ RECENT GIT COMMITS (last 24h):
41
+ {{commits}}
42
+
43
+ OPEN GITHUB ISSUES (assigned to me):
44
+ {{issues}}
45
+
46
+ OPEN PULL REQUESTS (authored by me):
47
+ {{prs}}
48
+
49
+ LINEAR ISSUES (assigned to me, in progress or unstarted):
50
+ {{linear_issues}}
51
+
52
+ TODAY'S CALENDAR EVENTS:
53
+ {{calendar}}
54
+
55
+ Write a morning brief with these sections:
56
+ 1. **Email triage** — list emails needing action today (sender, subject, one-line summary)
57
+ 2. **FYI emails** — informational only, no action needed
58
+ 3. **Calendar** — list today's meetings with time and attendees; omit if calendar returned an error or no events
59
+ 4. **Code activity** — summarize the RECENT GIT COMMITS above; if the list is empty write "No commits in the last 24 hours"
60
+ 5. **GitHub** — list open issues and PRs needing attention; omit this section entirely if both lists are empty
61
+ 6. **Linear** — list in-progress and unstarted issues by priority; omit this section entirely if the list is empty or returned an error
62
+
63
+ Be concise. Skip newsletters and automated notifications.
64
+ driver: claude-code
65
+ model: claude-haiku-4-5-20251001
66
+ into: brief
67
+ - tool: file.write
68
+ path: ~/.patchwork/inbox/morning-brief-{{date}}.md
69
+ content: |
70
+ # Morning brief — {{date}}
71
+
72
+ {{brief}}
@@ -0,0 +1,77 @@
1
+ name: sentry-to-linear
2
+ description: |
3
+ Fetch a Sentry issue, enrich it with git blame, and create a triage-ready
4
+ Linear ticket. Requires both Sentry and Linear connectors connected.
5
+ Run ad-hoc or trigger via webhook / automation policy.
6
+
7
+ # Example manual trigger:
8
+ # patchwork-os run-recipe sentry-to-linear --var SENTRY_ISSUE_ID=12345678
9
+ #
10
+ # Example automation trigger (add to ~/.patchwork/automation-policy.json):
11
+ # "onFileSave": { ... } — or fire from a Sentry webhook handler
12
+
13
+ trigger:
14
+ type: manual
15
+ vars:
16
+ - name: SENTRY_ISSUE_ID
17
+ description: "Sentry issue ID or URL (e.g. '12345678' or 'https://sentry.io/...')"
18
+ required: true
19
+ - name: LINEAR_TEAM_KEY
20
+ description: "Linear team key to create the issue in (e.g. 'ENG'). Defaults to first team."
21
+ required: false
22
+ - name: LINEAR_PRIORITY
23
+ description: "Linear priority: 1=urgent, 2=high, 3=medium, 4=low (default: 2)"
24
+ required: false
25
+ default: "2"
26
+
27
+ steps:
28
+ - agent:
29
+ prompt: |
30
+ You are a triage assistant. Your job is to:
31
+ 1. Fetch the Sentry issue using fetchSentryIssue with issueId "{{SENTRY_ISSUE_ID}}"
32
+ 2. Create a Linear issue using createLinearIssue with the data you received
33
+
34
+ For the Linear issue:
35
+ - title: Use the Sentry issue title verbatim
36
+ - description: Write a structured Markdown body with these sections:
37
+ ## Error
38
+ (one-line error type + message from the Sentry title)
39
+
40
+ ## Stack trace
41
+ (the top 5 frames from the stackTrace field, formatted as a code block)
42
+
43
+ ## Suspect commit
44
+ (if topSuspect is present: sha, author, date, subject — otherwise write "No suspect commit identified")
45
+
46
+ ## Confidence
47
+ (the confidence field value + a one-sentence explanation of what it means)
48
+
49
+ ## Sentry link
50
+ (a link back to the Sentry issue — construct from the issueId: https://sentry.io/issues/{{SENTRY_ISSUE_ID}}/)
51
+
52
+ - teamKey: "{{LINEAR_TEAM_KEY}}" (pass as-is; if blank the tool picks the first team)
53
+ - priority: {{LINEAR_PRIORITY}} (as an integer)
54
+ - labelNames: ["bug"]
55
+
56
+ After creating the ticket, respond with ONLY a JSON object:
57
+ {
58
+ "sentryIssueId": "<the issueId from fetchSentryIssue>",
59
+ "sentryTitle": "<the title>",
60
+ "linearIdentifier": "<e.g. ENG-101>",
61
+ "linearUrl": "<the url from createLinearIssue>",
62
+ "confidence": "<high|medium|low>",
63
+ "suspectCommit": "<sha or null>"
64
+ }
65
+
66
+ If either tool returns an error (sentryConnected: false or linearConnected: false),
67
+ respond with the same JSON shape but set linearUrl to "" and add an "error" key
68
+ explaining which connector is not connected.
69
+ driver: claude-code
70
+ model: claude-haiku-4-5-20251001
71
+ into: result
72
+ - tool: file.write
73
+ path: ~/.patchwork/inbox/sentry-to-linear-{{date}}.md
74
+ content: |
75
+ # Sentry → Linear triage ({{date}})
76
+
77
+ {{result}}
@@ -0,0 +1,37 @@
1
+ ## Installation
2
+
3
+ Copy this template to your Claude Desktop scheduled-tasks directory:
4
+
5
+ ```bash
6
+ mkdir -p ~/.claude/scheduled-tasks/morning-brief
7
+ cp /path/to/patchwork-os/templates/scheduled-tasks/morning-brief/SKILL.md \
8
+ ~/.claude/scheduled-tasks/morning-brief/SKILL.md
9
+ ```
10
+
11
+ Then restart Claude Desktop. Configure the schedule (recommended: weekdays 8am) in Claude Desktop settings under "Scheduled Tasks".
12
+
13
+ > Patchwork OS must be installed: `npm install -g patchwork-os`
14
+ > Gmail must be connected via the dashboard Connections page before this fires.
15
+
16
+ ---
17
+ ---
18
+ name: morning-brief
19
+ description: Run the Patchwork OS morning-brief recipe — fetches unread Gmail, recent git commits, and writes a brief to the Inbox.
20
+ schedule: "0 8 * * 1-5"
21
+ ---
22
+
23
+ # Morning Brief
24
+
25
+ Run the morning-brief recipe and deliver results to the Patchwork OS Inbox.
26
+
27
+ ## Steps
28
+
29
+ 1. Run `Bash` with command: `patchwork-os recipe run morning-brief`
30
+ 2. Confirm the output file was written to `~/.patchwork/inbox/`
31
+ 3. Report the file path and a one-line summary of what was written
32
+
33
+ ## Guidelines
34
+
35
+ - If Gmail returns 0 emails, still write the brief with the git activity section
36
+ - If the recipe fails, report the error clearly — do not retry automatically
37
+ - Keep the confirmation message under 3 lines