@aion0/forge 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/.forge/mcp.json +8 -0
  2. package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/01-settings.md +5 -5
  3. package/.forge/worktrees/pipeline-0a33c50d/lib/help-docs/07-projects.md +1 -1
  4. package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/01-settings.md +5 -5
  5. package/.forge/worktrees/pipeline-2ba01c10/lib/help-docs/07-projects.md +1 -1
  6. package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/01-settings.md +5 -5
  7. package/.forge/worktrees/pipeline-3156a8b3/lib/help-docs/07-projects.md +1 -1
  8. package/.forge/worktrees/pipeline-316c6574/lib/help-docs/01-settings.md +5 -5
  9. package/.forge/worktrees/pipeline-316c6574/lib/help-docs/07-projects.md +1 -1
  10. package/.forge/worktrees/pipeline-44a94121/lib/help-docs/01-settings.md +5 -5
  11. package/.forge/worktrees/pipeline-44a94121/lib/help-docs/07-projects.md +1 -1
  12. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/01-settings.md +5 -5
  13. package/.forge/worktrees/pipeline-4dd8dc2d/lib/help-docs/07-projects.md +1 -1
  14. package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/01-settings.md +5 -5
  15. package/.forge/worktrees/pipeline-d1757a50/lib/help-docs/07-projects.md +1 -1
  16. package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/01-settings.md +5 -5
  17. package/.forge/worktrees/pipeline-d59c2fe2/lib/help-docs/07-projects.md +1 -1
  18. package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/01-settings.md +5 -5
  19. package/.forge/worktrees/pipeline-d6a6ef23/lib/help-docs/07-projects.md +1 -1
  20. package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/01-settings.md +5 -5
  21. package/.forge/worktrees/pipeline-e7f78b7a/lib/help-docs/07-projects.md +1 -1
  22. package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/01-settings.md +5 -5
  23. package/.forge/worktrees/pipeline-e97c13c7/lib/help-docs/07-projects.md +1 -1
  24. package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/01-settings.md +5 -5
  25. package/.forge/worktrees/pipeline-ecd7cb0f/lib/help-docs/07-projects.md +1 -1
  26. package/CLAUDE.md +2 -2
  27. package/RELEASE_NOTES.md +101 -5
  28. package/app/api/auth/check/route.ts +18 -0
  29. package/app/api/browser-bridge/route.ts +70 -0
  30. package/app/api/chat/sessions/[id]/events/route.ts +17 -0
  31. package/app/api/chat/sessions/[id]/fork/route.ts +15 -0
  32. package/app/api/chat/sessions/[id]/messages/route.ts +21 -0
  33. package/app/api/chat/sessions/[id]/route.ts +23 -0
  34. package/app/api/chat/sessions/route.ts +12 -0
  35. package/app/api/chat/temper-ping/route.ts +18 -0
  36. package/app/api/chat-proxy/[...path]/route.ts +83 -0
  37. package/app/api/connector-tool/route.ts +38 -0
  38. package/app/api/connectors/[id]/settings/route.ts +112 -0
  39. package/app/api/connectors/route.ts +108 -0
  40. package/app/api/health/tools/route.ts +14 -0
  41. package/app/api/issue-scanner-gitlab/route.ts +95 -0
  42. package/app/api/jobs/[id]/reset_dedup/route.ts +15 -0
  43. package/app/api/jobs/[id]/route.ts +31 -0
  44. package/app/api/jobs/[id]/run/route.ts +44 -0
  45. package/app/api/jobs/[id]/runs/[runId]/route.ts +15 -0
  46. package/app/api/jobs/[id]/runs/route.ts +15 -0
  47. package/app/api/jobs/preview/route.ts +193 -0
  48. package/app/api/jobs/route.ts +36 -0
  49. package/app/api/notify/test/route.ts +39 -7
  50. package/app/api/pipelines/[id]/route.ts +10 -1
  51. package/app/api/pipelines/route.ts +16 -2
  52. package/app/api/plugins/route.ts +40 -8
  53. package/app/api/project-sessions/route.ts +50 -10
  54. package/app/api/settings/route.ts +13 -0
  55. package/app/chat/page.tsx +531 -0
  56. package/bin/forge-server.mjs +3 -1
  57. package/cli/chat.ts +283 -0
  58. package/cli/jobs.ts +176 -0
  59. package/cli/mw.ts +28 -1
  60. package/cli/worktree.ts +245 -0
  61. package/components/ConnectorsPanel.tsx +275 -0
  62. package/components/Dashboard.tsx +90 -37
  63. package/components/JobsView.tsx +361 -0
  64. package/components/LogViewer.tsx +12 -2
  65. package/components/PipelineView.tsx +275 -56
  66. package/components/PluginsPanel.tsx +3 -1
  67. package/components/SettingsModal.tsx +229 -40
  68. package/components/SkillsPanel.tsx +12 -4
  69. package/components/TerminalLauncher.tsx +3 -1
  70. package/components/WebTerminal.tsx +32 -9
  71. package/components/WorkspaceView.tsx +18 -10
  72. package/docs/Connector-DeclarativeExtract-Handoff.md +471 -0
  73. package/docs/Connector-DeclarativeExtract-Spec.md +364 -0
  74. package/docs/Implementation-Plan-Browser-Agent.md +487 -0
  75. package/docs/Jobs-Design.md +240 -0
  76. package/docs/LOCAL-DEPLOY.md +3 -3
  77. package/docs/RFC-Browser-Connectors.md +509 -0
  78. package/lib/agents/index.ts +44 -6
  79. package/lib/agents/types.ts +1 -1
  80. package/lib/browser-bridge-standalone.ts +317 -0
  81. package/lib/builtin-plugins/github-api.yaml +93 -0
  82. package/lib/builtin-plugins/gitlab.yaml +860 -0
  83. package/lib/builtin-plugins/mantis.probe.js +176 -0
  84. package/lib/builtin-plugins/mantis.yaml +964 -0
  85. package/lib/builtin-plugins/pmdb.yaml +178 -0
  86. package/lib/builtin-plugins/teams.yaml +913 -0
  87. package/lib/chat/__test__/smoke.ts +30 -0
  88. package/lib/chat/agent-loop.ts +523 -0
  89. package/lib/chat/bridge-client.ts +59 -0
  90. package/lib/chat/llm/anthropic.ts +99 -0
  91. package/lib/chat/llm/index.ts +20 -0
  92. package/lib/chat/llm/openai.ts +215 -0
  93. package/lib/chat/llm/types.ts +42 -0
  94. package/lib/chat/local-memory.ts +300 -0
  95. package/lib/chat/memory-store.ts +87 -0
  96. package/lib/chat/memory-tools.ts +157 -0
  97. package/lib/chat/protocols/http.ts +118 -0
  98. package/lib/chat/protocols/shell.ts +101 -0
  99. package/lib/chat/proxy.ts +51 -0
  100. package/lib/chat/session-store.ts +272 -0
  101. package/lib/chat/telegram-bridge.ts +276 -0
  102. package/lib/chat/temper.ts +281 -0
  103. package/lib/chat/tool-dispatcher.ts +190 -0
  104. package/lib/chat/types.ts +50 -0
  105. package/lib/chat-standalone.ts +286 -0
  106. package/lib/crypto.ts +1 -1
  107. package/lib/health.ts +131 -0
  108. package/lib/help-docs/00-overview.md +2 -1
  109. package/lib/help-docs/01-settings.md +46 -25
  110. package/lib/help-docs/07-projects.md +1 -1
  111. package/lib/help-docs/10-troubleshooting.md +10 -2
  112. package/lib/help-docs/16-gitlab-autofix.md +114 -0
  113. package/lib/help-docs/17-connectors.md +322 -0
  114. package/lib/help-docs/18-chrome-mcp.md +134 -0
  115. package/lib/help-docs/19-jobs.md +140 -0
  116. package/lib/help-docs/20-mantis-bug-fix.md +115 -0
  117. package/lib/help-docs/CLAUDE.md +10 -0
  118. package/lib/init.ts +137 -50
  119. package/lib/iso-time.ts +30 -0
  120. package/lib/issue-scanner-gitlab.ts +281 -0
  121. package/lib/jobs/dispatcher.ts +217 -0
  122. package/lib/jobs/scheduler.ts +334 -0
  123. package/lib/jobs/store.ts +319 -0
  124. package/lib/jobs/types.ts +117 -0
  125. package/lib/pipeline-scheduler.ts +1 -6
  126. package/lib/pipeline.ts +790 -10
  127. package/lib/plugins/registry.ts +133 -8
  128. package/lib/plugins/templates.ts +83 -0
  129. package/lib/plugins/types.ts +140 -1
  130. package/lib/session-watcher.ts +36 -10
  131. package/lib/settings.ts +65 -33
  132. package/lib/skills.ts +3 -1
  133. package/lib/task-manager.ts +50 -22
  134. package/lib/telegram-bot.ts +71 -0
  135. package/lib/terminal-standalone.ts +58 -36
  136. package/lib/workspace/orchestrator.ts +1 -0
  137. package/middleware.ts +10 -0
  138. package/package.json +3 -2
  139. package/scripts/bench/README.md +1 -1
  140. package/scripts/bench/tasks/01-text-utils/validator.sh +1 -1
  141. package/scripts/bench/tasks/02-pagination/setup.sh +1 -1
  142. package/scripts/bench/tasks/02-pagination/validator.sh +1 -1
  143. package/scripts/bench/tasks/03-bug-fix/setup.sh +1 -1
  144. package/scripts/bench/tasks/03-bug-fix/validator.sh +1 -1
  145. package/src/core/db/database.ts +21 -12
@@ -0,0 +1,114 @@
1
+ # GitLab Issue Auto-fix
2
+
3
+ Scan **self-hosted GitLab issues**, resolve base branch from issue metadata,
4
+ create a per-issue **worktree**, run Claude on the issue (with Premium epic
5
+ context + downloaded image attachments), and open an MR — all automated.
6
+
7
+ Mirrors the existing GitHub flow (`issue-fix-and-review`) but built around
8
+ the `glab` CLI and adapted for Premium epic links + image uploads.
9
+
10
+ ## Setup
11
+
12
+ ### 1. Install + auth glab
13
+
14
+ ```bash
15
+ brew install glab
16
+ glab auth login --hostname <your-gitlab.example.com>
17
+ ```
18
+
19
+ GitLab host is auto-detected per project from the git remote, so you only
20
+ need to authenticate once per host. Self-hosted is fully supported.
21
+
22
+ ### 2. Bind the pipeline to your project
23
+
24
+ Project tab → **Pipelines** → `+ Add` → `gitlab-issue-fix-and-review`.
25
+
26
+ In the binding form set:
27
+ - **Labels** (optional) — only process issues matching these labels
28
+ - **Assignee filter** — `me` (default), specific username, or empty
29
+ - **Base branch rule** — how to derive target branch from issue:
30
+ - `milestone` (default): `release/<milestone-title-slug>`
31
+ - `label:<prefix>`: pick first label starting with `<prefix>`, strip it
32
+ (e.g. `label:branch:` → label `branch:hotfix/4.5` → base `hotfix/4.5`)
33
+ - `desc`: regex match `^Base: <branch>` in issue description
34
+ - `default`: just `main` (or repo default)
35
+ - **Base branch override** — string; bypasses the rule entirely
36
+ - **MR title template** — vars: `{issue_id}`, `{issue_title}`.
37
+ Default: `Fix #{issue_id}: {issue_title}`
38
+ - **MR body template** — vars: `{issue_id}`, `{issue_url}`, `{summary}`.
39
+ Default closes the issue and inlines Claude's summary.
40
+ - **Schedule** — interval in minutes (`0` = manual only)
41
+
42
+ ### 3. Triggering
43
+
44
+ Three ways:
45
+
46
+ | Mode | How |
47
+ |---|---|
48
+ | Manual single-issue | `Run` button in Pipelines tab → enter `issue_id` |
49
+ | Manual scan-all | `Scan` button → fetches assigned issues, fans out, dedups |
50
+ | Scheduled | Set interval > 0 in config; runs on Forge boot + every N min |
51
+
52
+ ## Pipeline nodes
53
+
54
+ ```
55
+ resolve → parse host + project path from origin
56
+ fetch-issue → glab issue view → JSON (title, description, labels, milestone, epic.iid)
57
+ fetch-extras → glab api → comments JSON + epic JSON (Premium)
58
+ resolve-base-branch → apply rule from config
59
+ worktree-setup → git worktree add .forge/worktrees/issue-<id>
60
+ download-attachments → curl GitLab /uploads/.../filename into .attachments/
61
+ fix-code → claude in worktree, prompt = issue + comments + epic + images
62
+ push-and-mr → git push + glab mr create
63
+ notify-issue → glab issue note → "🤖 Forge opened MR: <url>"
64
+ ```
65
+
66
+ Pipeline output: an MR URL on the issue + linked from comments.
67
+
68
+ ## Storage
69
+
70
+ Per-project config + dedup tracking lives in `workflow.db`:
71
+
72
+ - `issue_autofix_gitlab_config` — projectPath → config
73
+ - `issue_autofix_gitlab_processed` — projectPath × issueIid → status + MR
74
+
75
+ Already-processed issues are skipped in scan mode. Use `reset` to re-trigger,
76
+ or `retry` with extra context to nudge Claude in a different direction.
77
+
78
+ ## Image attachments
79
+
80
+ GitLab uploads (`/uploads/<32-hex>/<file>`) in issue descriptions are
81
+ auto-downloaded to `<worktree>/.attachments/`. Claude can `Read` those
82
+ files. PNG/JPG/PDF supported via Claude Code's image reader.
83
+
84
+ The download uses the same PAT that glab already has authenticated (so
85
+ no extra credential setup). Falls back to anonymous HTTPS if the upload
86
+ happens to be public.
87
+
88
+ ## Premium: Epic context
89
+
90
+ If the issue links to an epic (`issue.epic.iid` present), the pipeline:
91
+ 1. Fetches the epic via `glab api groups/<id>/epics/<iid>`
92
+ 2. Includes epic description + acceptance criteria in Claude's prompt
93
+ 3. Claude can read the full JSON dump file path from `fetch-extras`
94
+
95
+ Free-tier instances just skip this step — no error.
96
+
97
+ ## Authentication notes
98
+
99
+ `glab auth login` stores a token at `~/.config/glab-cli/config.yml`. Forge
100
+ inherits the env; the spawned `glab` invocations from the pipeline use the
101
+ same credentials. **No env vars needed** unless you want to override per-
102
+ session.
103
+
104
+ For headless / CI mode use `GITLAB_TOKEN` env var — `glab` picks it up too.
105
+
106
+ ## Troubleshooting
107
+
108
+ | Symptom | Cause + fix |
109
+ |---|---|
110
+ | `ERROR: glab CLI not installed` | `brew install glab` (or apt) and `glab auth login` |
111
+ | `glab CLI not authenticated` | Token expired or wrong host. `glab auth login --hostname <host>` |
112
+ | MR push fails with "main branch protected" | Configure GitLab to allow force-push to `fix/*` branches OR change pipeline to drop `--force-with-lease` |
113
+ | Base branch resolves wrong | Switch `baseBranchRule` to `desc` and add `Base: <branch>` to the issue description as a forcing override |
114
+ | `403 on attachment fetch` | Some self-hosted instances require token even for public-link uploads. Make sure glab is authenticated to the same host that issued the upload URL |
@@ -0,0 +1,322 @@
1
+ # Connectors
2
+
3
+ **Connectors** are Forge plugins of `category: connector` that expose tool
4
+ schemas + **the extraction scripts that implement them** to the **Forge
5
+ browser extension**. The extension is a generic runner: it doesn't know
6
+ about Mantis or GitLab specifically — it loads the manifest, finds/opens
7
+ the right tab, and executes whatever script the manifest ships.
8
+
9
+ > **Architectural principle**: adding a new connector should be a
10
+ > Forge-only change. No browser extension release required.
11
+ >
12
+ > See `docs/Connector-DeclarativeExtract-Spec.md` for the full spec of
13
+ > the manifest schema and runtime contract.
14
+
15
+ ## Execution model: DOM extraction, not REST
16
+
17
+ **Browser-side connectors do NOT call REST APIs.** They navigate the
18
+ user's tabs and parse rendered HTML via `chrome.scripting.executeScript`.
19
+ This is the whole point of routing through a browser extension — reuse
20
+ the user's already-authenticated UI session, with zero token management.
21
+
22
+ Why this matters in practice:
23
+
24
+ | System | REST API needs | Browser DOM needs |
25
+ |---|---|---|
26
+ | MantisBT | Per-user API token (`Authorization` header) | Already-logged-in PHPSESSID cookie ✅ |
27
+ | GitLab | Personal Access Token | Session cookie on `/-/issues/` etc. ✅ |
28
+ | JIRA Server | PAT or basic auth | Session cookie ✅ |
29
+ | Teams web | MSAL bearer token | Session cookie ✅ |
30
+
31
+ If a connector hit the REST API path, every user would need to mint and
32
+ manage a token per system. That defeats the extension's value
33
+ proposition. **Token-based fallback is opt-in per connector, not the
34
+ default code path.**
35
+
36
+ This means:
37
+ - **No PAT / API tokens to manage** — connector reuses the user's logged-in browser session.
38
+ - **Data never flows through Forge** — Forge only ships the manifest + extraction script. The LLM in the extension calls the tool, the extension scrapes in the user's tab, the LLM sees the result.
39
+ - Forge's role: **discovery + settings sync + script delivery**.
40
+ - Trade-off: site redesigns break scripts. Bump the manifest `version` and update the `script` block — users get the fix on next refresh, no extension release.
41
+
42
+ ## Architecture
43
+
44
+ ```
45
+ Forge Extension User's tabs
46
+ ───── ───────── ───────────
47
+ mantis.yaml ┌──────────────┐
48
+ tools: Generic runner │ Mantis tab │
49
+ list_my_bugs: ─────────▶ ① fetch manifest │ (logged in) │
50
+ page: { url, ... } ② render templates │ │
51
+ script: | ③ acquire tab matching │ #bug_list │
52
+ const list = ... host_match, navigate │ ◀── runs │
53
+ return { bugs, ... } to page.url the script
54
+ ④ executeScript(script) from the
55
+ in the tab manifest
56
+ ⑤ return JSON to LLM │ │
57
+ └──────────────┘
58
+ ```
59
+
60
+ The extension has **no per-connector code**. Adding GitLab means dropping
61
+ a `gitlab.yaml` next to `mantis.yaml` — extension auto-discovers it.
62
+
63
+ ## Plugin manifest format
64
+
65
+ A connector plugin is a YAML in `lib/builtin-plugins/<id>.yaml` (or
66
+ `~/.forge/plugins/<id>/plugin.yaml`):
67
+
68
+ ```yaml
69
+ id: mantis
70
+ name: MantisBT
71
+ icon: "🐞"
72
+ version: "0.2.0"
73
+ category: connector # ← marks this as a connector
74
+ mode: browser-side # server-side | browser-side | hybrid
75
+
76
+ # Per-user settings (rendered as a form in the extension)
77
+ settings:
78
+ base_url:
79
+ type: string
80
+ label: Mantis base URL
81
+ required: true
82
+
83
+ # Plugin-level: where the extension finds / opens an authenticated tab.
84
+ # Chrome match pattern; {settings.*} expanded at API response time.
85
+ host_match: "{base_url}/*"
86
+
87
+ # Substring detected after navigation → tells the runner "user not logged in".
88
+ login_redirect: "/login_page.php"
89
+
90
+ tools:
91
+ list_my_bugs:
92
+ description: "List bugs assigned to me."
93
+ parameters:
94
+ status: { type: select, options: ["open", "closed", "all"], default: "open" }
95
+ limit: { type: number, default: 50 }
96
+
97
+ # Page to navigate to (or stay on, if on_target matches current URL).
98
+ page:
99
+ url: "{base_url}/view_all_bug_page.php"
100
+ on_target: "/view_all_bug_page.php"
101
+
102
+ # Function BODY. Implicit `args` parameter. Returns JSON-serializable.
103
+ # Runs IN THE USER'S TAB (page context). No closures over Forge / extension.
104
+ script: |
105
+ const { status, limit } = args;
106
+ const list = document.querySelector('#bug_list');
107
+ if (!list) return { bugs: [], total: 0, _error: '#bug_list not found' };
108
+ // ... extract rows, filter, return
109
+ return { bugs, total };
110
+
111
+ add_comment:
112
+ destructive: true # ← extension prompts user before running
113
+ description: "Add a note to a bug."
114
+ parameters:
115
+ bug_id: { type: number, required: true }
116
+ text: { type: string, required: true }
117
+ page:
118
+ url: "{base_url}/bug_view_page.php?bug_id={args.bug_id}"
119
+ script: |
120
+ // Form-submit or fetch() with same-origin cookies
121
+ ...
122
+ ```
123
+
124
+ **Template variables**:
125
+ - `{base_url}` / `{settings.<name>}` → expanded server-side from saved settings
126
+ - `{args.<name>}` → expanded by the extension at run time from the LLM's tool input
127
+
128
+ **Script contract**:
129
+ - Receives `args` (the LLM's parameters)
130
+ - Returns a JSON-serializable value (no DOM nodes, no functions)
131
+ - Has access to `document`, `fetch`, `URL`, etc. (page context)
132
+ - Can call same-origin `fetch()` — cookies auto-attached
133
+ - Must be self-contained — no closures over the manifest's surroundings
134
+ - Errors thrown are caught by the runner and returned as tool errors
135
+
136
+ See `docs/Connector-DeclarativeExtract-Spec.md` for the complete contract.
137
+
138
+ ### 1 plugin = 1 connector (default)
139
+
140
+ The above shape is the **default 1:1 case**. For same-vendor suites with
141
+ shared auth (Atlassian, Google Workspace, M365), use the `connectors[]`
142
+ escape hatch — one plugin declares multiple connector entries that share
143
+ the user's OAuth/SSO:
144
+
145
+ ```yaml
146
+ id: atlassian-suite
147
+ category: connector
148
+ mode: hybrid
149
+ connectors:
150
+ - id: jira
151
+ host_match: "*://*.atlassian.net/*"
152
+ tools: { ... }
153
+ - id: confluence
154
+ host_match: "*://*.atlassian.net/wiki/*"
155
+ tools: { ... }
156
+ ```
157
+
158
+ Most plugins should stay 1:1. Use 1:N only for genuine shared-auth suites.
159
+
160
+ ## HTTP API
161
+
162
+ All endpoints require `X-Forge-Token` header (obtain via `POST /api/auth/verify`).
163
+
164
+ ### `GET /api/connectors`
165
+ List connector plugins (installed + available). For installed connectors,
166
+ `page.url` / `host_match` etc. have `{base_url}` / `{settings.*}` expanded
167
+ from the user's saved settings; `{args.*}` is left literal.
168
+
169
+ ```json
170
+ {
171
+ "connectors": [
172
+ {
173
+ "plugin_id": "mantis",
174
+ "name": "MantisBT",
175
+ "icon": "🐞",
176
+ "version": "0.2.0",
177
+ "mode": "browser-side",
178
+ "installed": true,
179
+ "host_match": "https://mantis.acme.com/*",
180
+ "login_redirect": "/login_page.php",
181
+ "entries": [
182
+ {
183
+ "id": "mantis",
184
+ "tools": {
185
+ "list_my_bugs": {
186
+ "description": "...",
187
+ "parameters": {...},
188
+ "page": { "url": "https://mantis.acme.com/view_all_bug_page.php", "on_target": "/view_all_bug_page.php" },
189
+ "script": "const { status, limit } = args; ..."
190
+ }
191
+ },
192
+ "settings": { "base_url": {...} }
193
+ }
194
+ ]
195
+ }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ Query params:
201
+ - `installed=true` → only plugins the user has configured
202
+ - `id=<plugin_id>` → single connector detail (alternative to a path param)
203
+
204
+ ### `GET /api/connectors?id=<id>` / `GET /api/connectors/<id>`
205
+ Single connector detail. Same shape as a list entry, wrapped in `{ connector: ... }`.
206
+
207
+ ### `GET /api/connectors/<id>/settings`
208
+ Read the user's saved settings for a connector.
209
+
210
+ ```json
211
+ {
212
+ "settings": { "base_url": "https://mantis.acme.com", "default_project": "Web" },
213
+ "schema": { "base_url": {...}, "default_project": {...} },
214
+ "installed": true
215
+ }
216
+ ```
217
+
218
+ `schema` is the merged settings schema across all entries (1:N). `settings`
219
+ includes any defaults declared in the schema, overlaid with the user's
220
+ saved values.
221
+
222
+ ### `POST /api/connectors/<id>/settings`
223
+ Save settings. Body: `{ settings: { base_url: "..." } }` (or the object directly).
224
+
225
+ First save = creates the install record. Subsequent saves update it.
226
+
227
+ ```json
228
+ { "ok": true, "settings": { "base_url": "..." } }
229
+ ```
230
+
231
+ ## Built-in connectors
232
+
233
+ | ID | Description |
234
+ |---|---|
235
+ | `mantis` | MantisBT — list/get/search bugs, add comments. Self-hosted. |
236
+ | `gitlab-browser` | GitLab — issues, MRs, pipelines via browser session. gitlab.com + self-hosted. |
237
+
238
+ The CLI-backed `glab` GitLab integration in `gitlab-issue-fix-and-review`
239
+ is **separate** — that runs server-side as a pipeline. The browser
240
+ connector is for the extension's interactive use.
241
+
242
+ ## Writing a new connector
243
+
244
+ 1. Drop a YAML in `~/.forge/plugins/<id>/plugin.yaml` with `category: connector`.
245
+ 2. Define `settings` (e.g. `base_url`) the user must provide.
246
+ 3. Define `host_match` and `login_redirect` at the plugin level.
247
+ 4. For each tool: schema (`parameters`), `page` block, `script` body.
248
+ 5. **Use DOM extraction**, not REST API calls. `script` runs in the tab,
249
+ has same-origin cookies via `fetch()`, can read the DOM directly.
250
+ 6. Use `mantis.probe.js`-style helper scripts at dev time to discover
251
+ stable selectors; paste the resulting selectors into your `script`.
252
+ 7. Restart Forge — extension picks it up via `GET /api/connectors` on
253
+ next refresh. **No extension code changes needed.**
254
+
255
+ ### When REST is unavoidable
256
+
257
+ Some sites are pure SPAs that virtualize lists, render via canvas, or
258
+ require many interactions to expose data. In those rare cases, add an
259
+ optional `api_token` setting (`type: secret`) and call `fetch()` with
260
+ the token from within `script`. Default code path stays DOM; token is
261
+ the fallback the user explicitly accepts.
262
+
263
+ ## Server-side protocols (http, shell)
264
+
265
+ When a site has a clean REST API and you don't need a browser tab at
266
+ all, declare `protocol: http` on the tool. Forge issues the request
267
+ server-side and returns the body to the LLM. Same for `protocol:
268
+ shell` — Forge spawns a process with an explicit arg array (no
269
+ `shell:true`, so templated values cannot inject metacharacters).
270
+
271
+ ```yaml
272
+ # lib/builtin-plugins/github-api.yaml — see file for full example
273
+ tools:
274
+ get_repo:
275
+ protocol: http
276
+ parameters:
277
+ repo: { type: string, required: true }
278
+ request:
279
+ method: GET
280
+ url: 'https://api.github.com/repos/{args.repo}'
281
+ headers:
282
+ Accept: 'application/vnd.github+json'
283
+ Authorization: 'Bearer {settings.token}'
284
+
285
+ git_log:
286
+ protocol: shell
287
+ parameters:
288
+ repo: { type: string, required: true }
289
+ n: { type: number }
290
+ command: ['git', '-C', '{args.repo}', 'log', '-n', '{args.n}', '--oneline']
291
+ timeout_ms: 5000
292
+ ```
293
+
294
+ Templates `{base_url}`, `{settings.*}`, `{args.*}` all expand at
295
+ dispatch time. For `http`, body can be a string or a JSON object; for
296
+ `shell`, every arg is templated independently so an arg with spaces or
297
+ quotes stays a single literal arg.
298
+
299
+ Response body / stdout is truncated to ~8 KB for the LLM context;
300
+ default timeout 30 s, max 5 min. `protocol: browser` (the default)
301
+ keeps using the extension runner; `http` and `shell` run entirely on
302
+ Forge (chat-standalone, port 8408).
303
+
304
+ **Safety:** `protocol: shell` lets a YAML pick any binary on PATH —
305
+ review at install time. There is no auto allow-list in v1.
306
+
307
+ ### Iterating selectors
308
+
309
+ If a connector breaks after a site redesign, **only the YAML's `script`
310
+ body needs to change**. Bump `version`, edit, restart Forge — extension
311
+ users pick up the fix on their next refresh.
312
+
313
+ ## Troubleshooting
314
+
315
+ | Symptom | Cause + fix |
316
+ |---|---|
317
+ | Extension shows "no connectors" | Token expired — call `POST /api/auth/verify` to refresh |
318
+ | Tool not appearing for LLM | Plugin loaded but `category` missing, OR `script` missing for the tool |
319
+ | Settings POST returns 500 | Plugin id not found (typo?) or `plugin-configs.json` not writable |
320
+ | Tool returns "login required" | `login_redirect` substring matched the tab URL after nav — user needs to log in to that site |
321
+ | Script throws but error opaque | Wrap script body in try/catch and `return { _error: e.message }`; the runner catches uncaught throws but caller-visible message is cleaner |
322
+ | Strict-CSP site (github.com etc.) refuses `new Function` | Page CSP blocks dynamic eval. For those sites, write a server-side connector (REST + token) instead of browser-side. Document the limitation per connector. |
@@ -0,0 +1,134 @@
1
+ # Chrome MCP Setup
2
+
3
+ Connect Forge's Claude Code sessions to a running Chrome instance via
4
+ **chrome-devtools-mcp**, so the agent can navigate pages, query the
5
+ DOM, and execute scripts against the user's real browser session.
6
+
7
+ This is the development-time path for authoring browser connectors —
8
+ the agent writes a connector handler, runs it against a real Mantis /
9
+ GitLab / etc. page, sees the actual DOM output, and iterates.
10
+
11
+ ## How it fits
12
+
13
+ ```
14
+ Forge Claude Code session
15
+
16
+ │ MCP (stdio)
17
+
18
+ chrome-devtools-mcp ──CDP──▶ Chrome (user's real instance)
19
+
20
+
21
+ Mantis / GitLab / any logged-in tab
22
+ ```
23
+
24
+ ## One-time setup
25
+
26
+ ### 1. Start Chrome with remote debugging enabled
27
+
28
+ Quit all Chrome windows first (so the new instance can take the debug
29
+ port).
30
+
31
+ **macOS:**
32
+ ```bash
33
+ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
34
+ --remote-debugging-port=9222 \
35
+ --user-data-dir="$HOME/Library/Application Support/Google/Chrome"
36
+ ```
37
+
38
+ The `--user-data-dir` argument keeps your normal profile (and all your
39
+ logins) — drop it only if you want a fresh profile.
40
+
41
+ **Linux:**
42
+ ```bash
43
+ google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.config/google-chrome"
44
+ ```
45
+
46
+ **Windows:**
47
+ ```powershell
48
+ "C:\Program Files\Google\Chrome\Application\chrome.exe" `
49
+ --remote-debugging-port=9222 `
50
+ --user-data-dir="$env:LOCALAPPDATA\Google\Chrome\User Data"
51
+ ```
52
+
53
+ Verify it worked: open `http://localhost:9222/json/version` in the new
54
+ Chrome — you should see a JSON page with `Browser`, `Protocol-Version`, etc.
55
+
56
+ ### 2. Add the MCP server to Forge settings
57
+
58
+ Settings → MCP Servers → Add. Fill in:
59
+
60
+ | Field | Value |
61
+ |---|---|
62
+ | Name | `chrome` |
63
+ | Type | `stdio` |
64
+ | Command | `npx` |
65
+ | Args | `["-y", "chrome-devtools-mcp"]` |
66
+ | Env | (leave empty — defaults to localhost:9222) |
67
+
68
+ Save. Forge will now merge this into every project's `.mcp.json` next
69
+ time a session is created or refreshed.
70
+
71
+ Equivalent `settings.yaml` snippet:
72
+
73
+ ```yaml
74
+ mcpServers:
75
+ chrome:
76
+ type: stdio
77
+ command: npx
78
+ args:
79
+ - "-y"
80
+ - "chrome-devtools-mcp"
81
+ ```
82
+
83
+ ### 3. Open a Forge project / workspace
84
+
85
+ When Claude Code launches in the project, it'll auto-discover the new
86
+ MCP server (the `.mcp.json` Forge writes now has both `forge` and
87
+ `chrome` entries). The agent will see browser tools like
88
+ `chrome_navigate`, `chrome_query_selector`, `chrome_evaluate`, etc.
89
+
90
+ Verify in a session: ask the agent "what MCP tools do you have?" — it
91
+ should list chrome_* tools alongside Forge tools.
92
+
93
+ ## Using it for connector development
94
+
95
+ Workflow inside a Forge workspace:
96
+
97
+ 1. Ask the agent: *"Develop a Mantis list_my_bugs connector. Target
98
+ page is https://mantis.forge.com/view_all_bug_page.php."*
99
+ 2. Agent navigates the Chrome tab there via `chrome_navigate`.
100
+ 3. Agent queries the DOM via `chrome_query_selector` /
101
+ `chrome_evaluate` to understand the page structure.
102
+ 4. Agent writes the handler code (`lib/builtin-plugins/mantis.handler.js`
103
+ or wherever your code-publishing scheme lands).
104
+ 5. Agent runs the candidate code in the page via `chrome_evaluate`,
105
+ sees real output, iterates.
106
+ 6. When done, agent commits the connector via normal git flow / publishes
107
+ per the connector marketplace flow.
108
+
109
+ No copy-paste loop. No probe scripts. Real DOM at every step.
110
+
111
+ ## Cleanup
112
+
113
+ To stop using Chrome MCP:
114
+
115
+ 1. Remove the `chrome` entry from Settings → MCP Servers.
116
+ 2. Forge regenerates `.mcp.json` on the next session creation.
117
+ 3. Optionally close the debug-enabled Chrome and restart normally.
118
+
119
+ ## Troubleshooting
120
+
121
+ | Symptom | Cause + fix |
122
+ |---|---|
123
+ | Agent says it has no chrome_* tools | Forge hasn't written the new `.mcp.json` yet — open the project tab in Forge to trigger `ensureMcpConfig`. Or just delete the project's `.mcp.json` and reopen. |
124
+ | `chrome_navigate` errors with "no debug target" | Chrome isn't running with `--remote-debugging-port=9222`. Verify `curl localhost:9222/json/version`. |
125
+ | Multiple Chrome processes | Quit all Chrome windows first, then start with the debug flag. macOS app launches and bare-binary launches don't share the port. |
126
+ | Login redirect on every nav | You're using `--user-data-dir=/tmp` or a fresh profile — point it at your real one to reuse cookies. |
127
+ | Enterprise / SSO restrictions | Some corporate MDM disables CDP. There's no workaround in those environments — fall back to the in-extension probe loop. |
128
+
129
+ ## Security note
130
+
131
+ Connecting to Chrome via CDP means anything able to reach
132
+ `localhost:9222` can drive your browser. Only enable on machines where
133
+ you trust everything local. Don't expose the debug port over the
134
+ network.