@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,317 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ /**
3
+ * Browser Bridge — WebSocket control plane for Forge ↔ Extension.
4
+ *
5
+ * Runs as an independent process (like terminal/workspace standalones).
6
+ * Exposes:
7
+ * - HTTP on $BRIDGE_PORT/api/* — push events, RPC dispatch, status.
8
+ * - WebSocket on $BRIDGE_PORT/ws — long-lived bidirectional channel
9
+ * used by the extension service worker.
10
+ *
11
+ * Auth model (post-Phase 4):
12
+ * The bridge accepts the user's existing Forge token (X-Forge-Token,
13
+ * issued by /api/auth/verify against the admin password). On every
14
+ * `hello`, the bridge validates the token via /api/auth/check on
15
+ * Next.js. There's no separate pairing code or long-lived bridge
16
+ * token — extensions reuse the credential they already have.
17
+ *
18
+ * Protocol (newline-delimited JSON frames):
19
+ *
20
+ * Extension → Forge:
21
+ * { type: 'hello', forge_token, version? }
22
+ * { type: 'rpc_result', id, ok, value, error? }
23
+ * { type: 'event', name, payload }
24
+ * { type: 'ping' }
25
+ *
26
+ * Forge → Extension:
27
+ * { type: 'hello_ok', ext_id }
28
+ * { type: 'hello_error', message }
29
+ * { type: 'rpc_request', id, method, params }
30
+ * { type: 'push', topic, payload }
31
+ * { type: 'pong' }
32
+ *
33
+ * RPC `method` examples (extension implements these):
34
+ * browser.execute_script { tabId, world, scriptBody, args }
35
+ * browser.list_tabs { url? }
36
+ * browser.navigate { tabId, url }
37
+ * browser.get_tab { tabId }
38
+ *
39
+ * Usage: npx tsx lib/browser-bridge-standalone.ts [--forge-port=8403]
40
+ */
41
+
42
+ import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
43
+ import { WebSocket, WebSocketServer } from 'ws';
44
+ import { randomUUID, createHash } from 'node:crypto';
45
+
46
+ // ─── Config ───────────────────────────────────────────────
47
+
48
+ const PORT = Number(process.env.BRIDGE_PORT) || 8407;
49
+ const FORGE_PORT = Number(process.env.PORT) || 8403;
50
+ const RPC_TIMEOUT_MS = 60_000;
51
+ const TOKEN_CACHE_TTL_MS = 60_000;
52
+
53
+ // ─── Forge-token validation (with short-lived cache) ──────
54
+
55
+ const tokenCache = new Map<string, number>(); // token → expires_at
56
+
57
+ async function isForgeTokenValid(token: string): Promise<boolean> {
58
+ if (!token) return false;
59
+ const cached = tokenCache.get(token);
60
+ if (cached && cached > Date.now()) return true;
61
+ try {
62
+ const res = await fetch(`http://127.0.0.1:${FORGE_PORT}/api/auth/check`, {
63
+ method: 'POST',
64
+ headers: { 'x-forge-token': token, 'content-type': 'application/json' },
65
+ body: '{}',
66
+ });
67
+ if (res.ok) {
68
+ tokenCache.set(token, Date.now() + TOKEN_CACHE_TTL_MS);
69
+ return true;
70
+ }
71
+ tokenCache.delete(token);
72
+ return false;
73
+ } catch (e) {
74
+ console.warn('[bridge] token check failed:', (e as Error).message);
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // Synthetic ext_id derived from the token so reconnects keep stable
80
+ // identity (and so the side panel UI can show "connected as <hash>").
81
+ function extIdForToken(token: string): string {
82
+ const h = createHash('sha256').update(token).digest('hex');
83
+ return 'tok-' + h.slice(0, 12);
84
+ }
85
+
86
+ // ─── Connected extensions ─────────────────────────────────
87
+
88
+ interface ExtClient {
89
+ ext_id: string;
90
+ ws: WebSocket;
91
+ connected_at: number;
92
+ name?: string;
93
+ }
94
+
95
+ const clients = new Map<string, ExtClient>(); // ext_id → client
96
+
97
+ function pickAnyClient(): ExtClient | null {
98
+ let best: ExtClient | null = null;
99
+ for (const c of clients.values()) {
100
+ if (!best || c.connected_at > best.connected_at) best = c;
101
+ }
102
+ return best;
103
+ }
104
+
105
+ // ─── RPC tracking (Forge → Extension) ─────────────────────
106
+
107
+ interface PendingRpc {
108
+ resolve: (value: unknown) => void;
109
+ reject: (err: Error) => void;
110
+ timer: NodeJS.Timeout;
111
+ }
112
+
113
+ const pendingRpcs = new Map<string, PendingRpc>(); // rpc_id → callbacks
114
+
115
+ function callExtension(method: string, params: unknown): Promise<unknown> {
116
+ const client = pickAnyClient();
117
+ if (!client) {
118
+ return Promise.reject(new Error('No extension connected to the bridge.'));
119
+ }
120
+ const id = randomUUID();
121
+ return new Promise<unknown>((resolve, reject) => {
122
+ const timer = setTimeout(() => {
123
+ pendingRpcs.delete(id);
124
+ reject(new Error(`RPC ${method} timed out after ${RPC_TIMEOUT_MS / 1000}s`));
125
+ }, RPC_TIMEOUT_MS);
126
+ pendingRpcs.set(id, { resolve, reject, timer });
127
+ client.ws.send(JSON.stringify({ type: 'rpc_request', id, method, params }));
128
+ });
129
+ }
130
+
131
+ // ─── WebSocket handlers ───────────────────────────────────
132
+
133
+ function handleFrame(ws: WebSocket, raw: string): void {
134
+ let frame: any;
135
+ try { frame = JSON.parse(raw); }
136
+ catch {
137
+ ws.send(JSON.stringify({ type: 'error', message: 'invalid JSON' }));
138
+ return;
139
+ }
140
+
141
+ switch (frame.type) {
142
+ case 'hello':
143
+ void handleHello(ws, frame);
144
+ break;
145
+ case 'rpc_result':
146
+ handleRpcResult(frame);
147
+ break;
148
+ case 'event':
149
+ handleEvent(frame);
150
+ break;
151
+ case 'ping':
152
+ ws.send(JSON.stringify({ type: 'pong' }));
153
+ break;
154
+ default:
155
+ ws.send(JSON.stringify({ type: 'error', message: 'unknown frame type: ' + frame.type }));
156
+ }
157
+ }
158
+
159
+ async function handleHello(ws: WebSocket, frame: { forge_token?: string; name?: string; version?: string }): Promise<void> {
160
+ const token = String(frame.forge_token || '').trim();
161
+ if (!token) {
162
+ ws.send(JSON.stringify({ type: 'hello_error', message: 'forge_token required' }));
163
+ try { ws.close(4001, 'unauthorized'); } catch {}
164
+ return;
165
+ }
166
+ const ok = await isForgeTokenValid(token);
167
+ if (!ok) {
168
+ ws.send(JSON.stringify({ type: 'hello_error', message: 'token invalid or expired — log in to Forge to refresh' }));
169
+ try { ws.close(4001, 'unauthorized'); } catch {}
170
+ return;
171
+ }
172
+ const ext_id = extIdForToken(token);
173
+ attachClient(ws, ext_id, frame.name);
174
+ ws.send(JSON.stringify({ type: 'hello_ok', ext_id }));
175
+ console.log(`[bridge] Extension connected: ${ext_id} (${frame.name || 'unnamed'})`);
176
+ }
177
+
178
+ function attachClient(ws: WebSocket, ext_id: string, name?: string): void {
179
+ const prev = clients.get(ext_id);
180
+ if (prev && prev.ws !== ws) {
181
+ try { prev.ws.close(4000, 'replaced'); } catch {}
182
+ }
183
+ clients.set(ext_id, { ext_id, ws, connected_at: Date.now(), name });
184
+ ws.on('close', () => {
185
+ const current = clients.get(ext_id);
186
+ if (current && current.ws === ws) clients.delete(ext_id);
187
+ console.log(`[bridge] Extension disconnected: ${ext_id}`);
188
+ });
189
+ }
190
+
191
+ function handleRpcResult(frame: { id: string; ok: boolean; value?: unknown; error?: string }): void {
192
+ const pending = pendingRpcs.get(frame.id);
193
+ if (!pending) return;
194
+ pendingRpcs.delete(frame.id);
195
+ clearTimeout(pending.timer);
196
+ if (frame.ok) pending.resolve(frame.value);
197
+ else pending.reject(new Error(frame.error || 'rpc failed without message'));
198
+ }
199
+
200
+ function handleEvent(frame: { name: string; payload: unknown }): void {
201
+ // Reserved for extension-initiated push (tab changed, side panel opened, etc.)
202
+ console.log(`[bridge] Event from extension: ${frame.name}`, frame.payload);
203
+ }
204
+
205
+ // ─── HTTP API ─────────────────────────────────────────────
206
+
207
+ async function readBody(req: IncomingMessage): Promise<string> {
208
+ return new Promise((resolve, reject) => {
209
+ const chunks: Buffer[] = [];
210
+ req.on('data', (c) => chunks.push(c));
211
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
212
+ req.on('error', reject);
213
+ });
214
+ }
215
+
216
+ function sendJson(res: ServerResponse, status: number, body: unknown): void {
217
+ res.statusCode = status;
218
+ res.setHeader('content-type', 'application/json');
219
+ res.setHeader('access-control-allow-origin', '*');
220
+ res.setHeader('access-control-allow-headers', 'content-type');
221
+ res.end(JSON.stringify(body));
222
+ }
223
+
224
+ async function handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void> {
225
+ if (req.method === 'OPTIONS') {
226
+ res.setHeader('access-control-allow-origin', '*');
227
+ res.setHeader('access-control-allow-methods', 'GET,POST,OPTIONS');
228
+ res.setHeader('access-control-allow-headers', 'content-type');
229
+ res.statusCode = 204;
230
+ res.end();
231
+ return;
232
+ }
233
+
234
+ const url = new URL(req.url || '/', 'http://localhost');
235
+
236
+ if (req.method === 'GET' && url.pathname === '/api/status') {
237
+ return sendJson(res, 200, {
238
+ port: PORT,
239
+ forge_port: FORGE_PORT,
240
+ connected_extensions: clients.size,
241
+ pending_rpcs: pendingRpcs.size,
242
+ cached_tokens: tokenCache.size,
243
+ uptime_seconds: Math.floor((Date.now() - startTime) / 1000),
244
+ });
245
+ }
246
+
247
+ // POST /api/rpc — Forge internals call this to dispatch an RPC to the extension
248
+ if (req.method === 'POST' && url.pathname === '/api/rpc') {
249
+ try {
250
+ const body = JSON.parse(await readBody(req));
251
+ const value = await callExtension(body.method, body.params);
252
+ return sendJson(res, 200, { ok: true, value });
253
+ } catch (e) {
254
+ return sendJson(res, 200, { ok: false, error: (e as Error).message });
255
+ }
256
+ }
257
+
258
+ // POST /api/push — push event to all connected extensions
259
+ if (req.method === 'POST' && url.pathname === '/api/push') {
260
+ try {
261
+ const body = JSON.parse(await readBody(req));
262
+ const frame = JSON.stringify({ type: 'push', topic: body.topic, payload: body.payload });
263
+ let count = 0;
264
+ for (const client of clients.values()) {
265
+ try { client.ws.send(frame); count++; } catch {}
266
+ }
267
+ return sendJson(res, 200, { ok: true, delivered_to: count });
268
+ } catch (e) {
269
+ return sendJson(res, 400, { ok: false, error: (e as Error).message });
270
+ }
271
+ }
272
+
273
+ sendJson(res, 404, { error: 'not found' });
274
+ }
275
+
276
+ // ─── Boot ─────────────────────────────────────────────────
277
+
278
+ const startTime = Date.now();
279
+ const httpServer = createServer((req, res) => {
280
+ handleHttp(req, res).catch((err) => {
281
+ console.error('[bridge] http error:', err);
282
+ sendJson(res, 500, { error: 'internal' });
283
+ });
284
+ });
285
+
286
+ const wss = new WebSocketServer({ noServer: true });
287
+ wss.on('connection', (ws) => {
288
+ ws.on('message', (data) => handleFrame(ws, data.toString('utf-8')));
289
+ ws.on('error', (err) => console.warn('[bridge] ws error:', err.message));
290
+ });
291
+
292
+ httpServer.on('upgrade', (req, socket, head) => {
293
+ const url = new URL(req.url || '/', 'http://localhost');
294
+ if (url.pathname !== '/ws') {
295
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
296
+ socket.destroy();
297
+ return;
298
+ }
299
+ wss.handleUpgrade(req, socket, head, (ws) => {
300
+ wss.emit('connection', ws, req);
301
+ });
302
+ });
303
+
304
+ httpServer.listen(PORT, () => {
305
+ console.log(`[bridge] Browser bridge listening on http://localhost:${PORT} (ws on /ws)`);
306
+ });
307
+
308
+ function shutdown(): void {
309
+ console.log('[bridge] shutting down');
310
+ for (const c of clients.values()) {
311
+ try { c.ws.close(1001, 'server shutting down'); } catch {}
312
+ }
313
+ httpServer.close(() => process.exit(0));
314
+ setTimeout(() => process.exit(0), 2000).unref();
315
+ }
316
+ process.on('SIGTERM', shutdown);
317
+ process.on('SIGINT', shutdown);
@@ -0,0 +1,93 @@
1
+ id: github-api
2
+ name: GitHub API
3
+ icon: 🐙
4
+ version: 0.1.0
5
+ author: forge
6
+ description: |
7
+ GitHub REST API connector — runs entirely server-side (no browser tab
8
+ needed). Set settings.token to a personal access token for private
9
+ repos / higher rate limits; public-only endpoints work unauthenticated.
10
+
11
+ Demonstrates the `protocol: http` runtime: the connector declares the
12
+ request shape and Forge issues it from the server. Templates expand
13
+ {settings.*} (user-supplied) and {args.*} (LLM-supplied) into url,
14
+ headers, query, and body.
15
+
16
+ category: connector
17
+ mode: server-side
18
+
19
+ settings:
20
+ token:
21
+ type: string
22
+ label: GitHub token (optional)
23
+ description: Personal access token. Leave empty for public endpoints.
24
+ secret: true
25
+
26
+ tools:
27
+ get_repo:
28
+ description: |
29
+ Fetch metadata for a GitHub repository (description, stars, default
30
+ branch, etc.).
31
+ protocol: http
32
+ parameters:
33
+ repo:
34
+ type: string
35
+ required: true
36
+ description: 'Repository in "owner/name" form (e.g. "anthropics/anthropic-sdk-python").'
37
+ request:
38
+ method: GET
39
+ url: 'https://api.github.com/repos/{args.repo}'
40
+ headers:
41
+ Accept: 'application/vnd.github+json'
42
+ Authorization: 'Bearer {settings.token}'
43
+ User-Agent: 'forge-github-connector'
44
+
45
+ list_issues:
46
+ description: |
47
+ List issues in a GitHub repository. Default returns open issues
48
+ (most recent first). Use state="closed" or state="all" to widen.
49
+ protocol: http
50
+ parameters:
51
+ repo:
52
+ type: string
53
+ required: true
54
+ description: 'Repository in "owner/name" form.'
55
+ state:
56
+ type: string
57
+ description: 'open | closed | all (default: open).'
58
+ per_page:
59
+ type: number
60
+ description: 'Results per page (1–100, default 30).'
61
+ request:
62
+ method: GET
63
+ url: 'https://api.github.com/repos/{args.repo}/issues'
64
+ headers:
65
+ Accept: 'application/vnd.github+json'
66
+ Authorization: 'Bearer {settings.token}'
67
+ User-Agent: 'forge-github-connector'
68
+ query:
69
+ state: '{args.state}'
70
+ per_page: '{args.per_page}'
71
+
72
+ search_repos:
73
+ description: |
74
+ Search GitHub repositories by keyword. Sorted by stars desc by
75
+ default. Use q to pass GitHub search qualifiers
76
+ (e.g. "language:go forks:>100 created:>2024").
77
+ protocol: http
78
+ parameters:
79
+ q:
80
+ type: string
81
+ required: true
82
+ description: 'Search query (GitHub search syntax).'
83
+ request:
84
+ method: GET
85
+ url: 'https://api.github.com/search/repositories'
86
+ headers:
87
+ Accept: 'application/vnd.github+json'
88
+ Authorization: 'Bearer {settings.token}'
89
+ User-Agent: 'forge-github-connector'
90
+ query:
91
+ q: '{args.q}'
92
+ sort: stars
93
+ order: desc