@aion0/forge 0.5.43 → 0.5.44

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 (62) hide show
  1. package/RELEASE_NOTES.md +14 -5
  2. package/components/Dashboard.tsx +14 -0
  3. package/intellij-plugin/README.md +53 -0
  4. package/intellij-plugin/build.gradle.kts +60 -0
  5. package/intellij-plugin/gradle/gradle-daemon-jvm.properties +12 -0
  6. package/intellij-plugin/gradle.properties +9 -0
  7. package/intellij-plugin/publish.sh +78 -0
  8. package/intellij-plugin/settings.gradle.kts +7 -0
  9. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +49 -0
  10. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +18 -0
  11. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +13 -0
  12. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +26 -0
  13. package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +115 -0
  14. package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +31 -0
  15. package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +95 -0
  16. package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +81 -0
  17. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +99 -0
  18. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +94 -0
  19. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +27 -0
  20. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +176 -0
  21. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +48 -0
  22. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +226 -0
  23. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +309 -0
  24. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +33 -0
  25. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +166 -0
  26. package/intellij-plugin/src/main/resources/META-INF/plugin.xml +88 -0
  27. package/intellij-plugin/src/main/resources/icons/forge.svg +3 -0
  28. package/lib/agents/index.ts +1 -1
  29. package/lib/help-docs/00-overview.md +1 -0
  30. package/lib/help-docs/13-ide-plugins.md +90 -0
  31. package/lib/help-docs/CLAUDE.md +3 -0
  32. package/package.json +1 -1
  33. package/vscode-extension/.vscodeignore +11 -0
  34. package/vscode-extension/README.md +48 -0
  35. package/vscode-extension/media/icon.png +0 -0
  36. package/vscode-extension/media/icon.svg +3 -0
  37. package/vscode-extension/package-lock.json +4046 -0
  38. package/vscode-extension/package.json +514 -0
  39. package/vscode-extension/publish.sh +49 -0
  40. package/vscode-extension/src/api/client.ts +217 -0
  41. package/vscode-extension/src/auth/auth.ts +32 -0
  42. package/vscode-extension/src/commands/auth.ts +44 -0
  43. package/vscode-extension/src/commands/connection.ts +113 -0
  44. package/vscode-extension/src/commands/docs.ts +40 -0
  45. package/vscode-extension/src/commands/pipeline.ts +103 -0
  46. package/vscode-extension/src/commands/server.ts +50 -0
  47. package/vscode-extension/src/commands/smith.ts +112 -0
  48. package/vscode-extension/src/commands/task.ts +43 -0
  49. package/vscode-extension/src/commands/terminal.ts +279 -0
  50. package/vscode-extension/src/commands/workspace.ts +138 -0
  51. package/vscode-extension/src/connection/manager.ts +80 -0
  52. package/vscode-extension/src/docs/fs-provider.ts +94 -0
  53. package/vscode-extension/src/docs/result-provider.ts +33 -0
  54. package/vscode-extension/src/docs/transport.ts +22 -0
  55. package/vscode-extension/src/extension.ts +314 -0
  56. package/vscode-extension/src/statusbar.ts +70 -0
  57. package/vscode-extension/src/terminal/pseudoterm.ts +123 -0
  58. package/vscode-extension/src/views/docs.ts +145 -0
  59. package/vscode-extension/src/views/pipelines.ts +222 -0
  60. package/vscode-extension/src/views/terminals.ts +91 -0
  61. package/vscode-extension/src/views/workspaces.ts +243 -0
  62. package/vscode-extension/tsconfig.json +16 -0
@@ -0,0 +1,166 @@
1
+ package com.aion0.forge.ui.toolwindow
2
+
3
+ import com.aion0.forge.api.ForgeClient
4
+ import com.intellij.icons.AllIcons
5
+ import com.intellij.openapi.actionSystem.AnAction
6
+ import com.intellij.openapi.actionSystem.AnActionEvent
7
+ import com.intellij.openapi.project.Project
8
+ import com.intellij.openapi.ui.Messages
9
+ import org.jetbrains.plugins.terminal.ShellTerminalWidget
10
+ import org.jetbrains.plugins.terminal.TerminalView
11
+ import javax.swing.tree.DefaultMutableTreeNode
12
+
13
+ class WorkspacesView(project: Project) : ForgeTreeView(project) {
14
+
15
+ /** Cache of open smith terminals keyed by tmux session name. Re-clicking a smith
16
+ * focuses the existing terminal tab instead of spawning a duplicate. */
17
+ private val openedTerminals = mutableMapOf<String, ShellTerminalWidget>()
18
+
19
+ override fun rootLabel() = "workspaces"
20
+
21
+ override fun reload(): List<DefaultMutableTreeNode> {
22
+ val r = ForgeClient.get().request("/api/workspace")
23
+ if (r.status == 401 || r.status == 403) return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("🔑 Tools → Forge: Login")))
24
+ if (!r.ok || r.data == null || !r.data.isJsonArray) {
25
+ return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("⚠ ${r.error ?: "Not connected"}")))
26
+ }
27
+ val arr = r.data.asJsonArray
28
+ if (arr.size() == 0) return listOf(DefaultMutableTreeNode(TreeNodeData.Hint("No workspaces yet")))
29
+ val sorted = arr.toList().sortedBy { it.asJsonObject.get("projectName")?.asString ?: "" }
30
+ return sorted.mapNotNull { el ->
31
+ val ws = el.asJsonObject
32
+ val id = ws.get("id")?.asString ?: return@mapNotNull null
33
+ val name = ws.get("projectName")?.asString ?: id
34
+ val ar = ForgeClient.get().request("/api/workspace/$id/agents")
35
+ val agents = ar.data?.asJsonObject?.getAsJsonArray("agents")
36
+ val states = ar.data?.asJsonObject?.getAsJsonObject("states")
37
+ val daemon = ar.data?.asJsonObject?.get("daemonActive")?.asBoolean ?: false
38
+ val mark = if (daemon) "🟢" else "○"
39
+ val node = DefaultMutableTreeNode(TreeNodeData.Workspace("$mark $name (${agents?.size() ?: 0} smiths)", id, name, daemon))
40
+ agents?.forEach { aEl ->
41
+ val a = aEl.asJsonObject
42
+ val aId = a.get("id")?.asString ?: return@forEach
43
+ val label = a.get("label")?.asString ?: aId
44
+ val icon = a.get("icon")?.asString ?: "🤖"
45
+ val s = states?.getAsJsonObject(aId)
46
+ val task = s?.get("taskStatus")?.asString ?: "idle"
47
+ val smith = s?.get("smithStatus")?.asString ?: "down"
48
+ val paused = s?.get("paused")?.asBoolean == true
49
+ val tmux = s?.get("tmuxSession")?.asString
50
+ val statusEmoji = when {
51
+ paused -> "⏸"
52
+ smith == "down" -> "○"
53
+ smith == "starting"-> "◐"
54
+ task == "running" -> "▶"
55
+ task == "failed" -> "✕"
56
+ task == "done" -> "✓"
57
+ else -> "·"
58
+ }
59
+ node.add(DefaultMutableTreeNode(
60
+ TreeNodeData.Smith("$statusEmoji $icon $label ($task)", id, aId, label, task, paused, tmux),
61
+ ))
62
+ }
63
+ node
64
+ }
65
+ }
66
+
67
+ override fun onDoubleClick(data: TreeNodeData, node: DefaultMutableTreeNode) {
68
+ when (data) {
69
+ is TreeNodeData.Smith -> openSmithTerminal(data)
70
+ else -> {}
71
+ }
72
+ }
73
+
74
+ override fun contextActions(data: TreeNodeData, node: DefaultMutableTreeNode): List<AnAction> = when (data) {
75
+ is TreeNodeData.Workspace -> if (data.daemonActive) {
76
+ listOf(
77
+ act("Stop Daemon", AllIcons.Actions.Suspend) { confirmAndDoWs(data.workspaceId, "stop_daemon", "Stop the workspace daemon? Running smiths will be terminated.") },
78
+ act("Restart Daemon",AllIcons.Actions.Restart) { runApi(project, "Restart daemon", { restartDaemon(data.workspaceId) }) { refresh() } },
79
+ )
80
+ } else {
81
+ listOf(
82
+ act("Start Daemon", AllIcons.Actions.Execute) { runApi(project, "Start daemon", { wsAction(data.workspaceId, "start_daemon") }) { refresh() } },
83
+ )
84
+ }
85
+ is TreeNodeData.Smith -> buildList {
86
+ add(act("Open Terminal", AllIcons.Debugger.Console) { openSmithTerminal(data) })
87
+ add(act("Send Message", AllIcons.General.Balloon) { promptSendMessage(data) })
88
+ if (data.paused) {
89
+ add(act("Resume", AllIcons.Actions.Execute) { runApi(project, "Resume ${data.agentLabel}", { wsAction(data.workspaceId, "resume", mapOf("agentId" to data.agentId)) }) { refresh() } })
90
+ } else {
91
+ add(act("Pause", AllIcons.Actions.Suspend) { runApi(project, "Pause ${data.agentLabel}", { wsAction(data.workspaceId, "pause", mapOf("agentId" to data.agentId)) }) { refresh() } })
92
+ }
93
+ if (data.taskStatus == "running") {
94
+ add(act("Mark Done", null) { runApi(project, "Mark done", { wsAction(data.workspaceId, "mark_done", mapOf("agentId" to data.agentId, "notify" to true)) }) { refresh() } })
95
+ add(act("Mark Failed", null) { runApi(project, "Mark failed", { wsAction(data.workspaceId, "mark_failed", mapOf("agentId" to data.agentId, "notify" to true)) }) { refresh() } })
96
+ add(act("Mark Idle", null) { runApi(project, "Mark idle", { wsAction(data.workspaceId, "mark_done", mapOf("agentId" to data.agentId, "notify" to false)) }) { refresh() } })
97
+ }
98
+ if (data.taskStatus == "failed") {
99
+ add(act("Retry", AllIcons.Actions.Refresh) { runApi(project, "Retry ${data.agentLabel}", { wsAction(data.workspaceId, "retry", mapOf("agentId" to data.agentId)) }) { refresh() } })
100
+ }
101
+ }
102
+ else -> emptyList()
103
+ }
104
+
105
+ private fun confirmAndDoWs(wsId: String, action: String, prompt: String) {
106
+ val r = Messages.showYesNoDialog(project, prompt, "Forge", Messages.getQuestionIcon())
107
+ if (r == Messages.YES) runApi(project, action, { wsAction(wsId, action) }) { refresh() }
108
+ }
109
+
110
+ private fun restartDaemon(wsId: String): com.aion0.forge.api.ApiResult {
111
+ val stop = wsAction(wsId, "stop_daemon")
112
+ if (!stop.ok) return stop
113
+ Thread.sleep(800)
114
+ return wsAction(wsId, "start_daemon")
115
+ }
116
+
117
+ /** Open an IDE terminal and run `tmux attach -t <session>` — works for
118
+ * local forge. Remote forge would need a WebSocket-bridged JediTerm
119
+ * session (TODO). Reuses an existing terminal tab if one was already
120
+ * opened for this smith. */
121
+ private fun openSmithTerminal(smith: TreeNodeData.Smith) {
122
+ val res = ForgeClient.get().request(
123
+ "/api/workspace/${smith.workspaceId}/agents",
124
+ method = "POST",
125
+ body = mapOf("action" to "open_terminal", "agentId" to smith.agentId),
126
+ )
127
+ val tmux = res.data?.asJsonObject?.get("tmuxSession")?.asString ?: smith.tmuxSession
128
+ if (tmux.isNullOrBlank()) {
129
+ notify(project, "Forge: smith ${smith.agentLabel} has no tmux session yet — start the daemon first.", com.intellij.notification.NotificationType.WARNING)
130
+ return
131
+ }
132
+ val existing = openedTerminals[tmux]
133
+ if (existing != null && !com.intellij.openapi.util.Disposer.isDisposed(existing)) {
134
+ // Surface the existing tab in the Terminal tool window.
135
+ val tw = com.intellij.openapi.wm.ToolWindowManager.getInstance(project).getToolWindow("Terminal")
136
+ tw?.activate(null)
137
+ existing.requestFocusInWindow()
138
+ return
139
+ }
140
+ val terminalView = TerminalView.getInstance(project)
141
+ val widget = terminalView.createLocalShellWidget(project.basePath ?: System.getProperty("user.home"), "forge: ${smith.agentLabel}")
142
+ openedTerminals[tmux] = widget
143
+ com.intellij.openapi.util.Disposer.register(widget) { openedTerminals.remove(tmux) }
144
+ // Force UTF-8 + 256-color terminfo so the tmux UI doesn't render as garbled
145
+ // box-drawing characters in JediTerm. `-u` opts into UTF-8, `-2` forces 256-color.
146
+ widget.executeCommand("TERM=xterm-256color tmux -2 -u attach -t \"$tmux\" || TERM=xterm-256color tmux -2 -u new -A -s \"$tmux\"")
147
+ }
148
+
149
+ private fun promptSendMessage(smith: TreeNodeData.Smith) {
150
+ val text = Messages.showMultilineInputDialog(
151
+ project,
152
+ "Send message to ${smith.agentLabel}",
153
+ "Forge: Send Message",
154
+ "",
155
+ null, null,
156
+ ) ?: return
157
+ if (text.isBlank()) return
158
+ runApi(project, "Send message to ${smith.agentLabel}",
159
+ { wsAction(smith.workspaceId, "message", mapOf("agentId" to smith.agentId, "content" to text)) },
160
+ ) { refresh() }
161
+ }
162
+
163
+ private fun act(name: String, icon: javax.swing.Icon?, run: () -> Unit) = object : AnAction(name, null, icon) {
164
+ override fun actionPerformed(e: AnActionEvent) = run()
165
+ }
166
+ }
@@ -0,0 +1,88 @@
1
+ <idea-plugin>
2
+ <id>com.aion0.forge</id>
3
+ <name>Forge Vibe Coding</name>
4
+ <version>0.1.0</version>
5
+ <vendor email="liuzhen1984@gmail.com" url="https://github.com/aiwatching/forge">aion0</vendor>
6
+
7
+ <description><![CDATA[
8
+ <p>Drive your <a href="https://github.com/aiwatching/forge">Forge</a> instance from
9
+ the IDE — workspaces, agents, terminals, pipelines and docs in one tool window.</p>
10
+
11
+ <p><b>⚠ Requires Forge running locally.</b> Install once:</p>
12
+ <pre>npm install -g @aion0/forge
13
+ forge server start</pre>
14
+ <p>Default port <code>8403</code>. Configure remote forges under
15
+ <b>Settings → Tools → Forge</b>.</p>
16
+
17
+ <h3>Features</h3>
18
+ <ul>
19
+ <li>Workspaces — daemon control, multi-agent smiths, tmux attach, pause/resume/retry</li>
20
+ <li>Terminals — per-project claude sessions, double-click to resume, right-click → Open With for any agent</li>
21
+ <li>Pipelines — bindings + run details with per-node prompt / result / diff viewer</li>
22
+ <li>Docs — open files in editor, "open terminal here"</li>
23
+ <li>Multi-connection — local + remote forges with status-bar switcher</li>
24
+ </ul>
25
+ ]]></description>
26
+
27
+ <change-notes><![CDATA[
28
+ <h3>0.1.0</h3>
29
+ <ul>
30
+ <li>Initial scaffold: tool window shell, settings page, login flow, multi-connection support.</li>
31
+ </ul>
32
+ ]]></change-notes>
33
+
34
+ <depends>com.intellij.modules.platform</depends>
35
+ <depends>org.jetbrains.plugins.terminal</depends>
36
+
37
+ <extensions defaultExtensionNs="com.intellij">
38
+ <!-- Persisted application-level state (connections + active connection). -->
39
+ <applicationService serviceImplementation="com.aion0.forge.connection.ConnectionManager"/>
40
+ <applicationService serviceImplementation="com.aion0.forge.api.ForgeClient"/>
41
+ <applicationService serviceImplementation="com.aion0.forge.auth.Auth"/>
42
+
43
+ <!-- Tool window: lives on the right side, contains tabs for each section. -->
44
+ <toolWindow
45
+ id="Forge"
46
+ anchor="left"
47
+ secondary="true"
48
+ icon="/icons/forge.svg"
49
+ factoryClass="com.aion0.forge.ui.toolwindow.ForgeToolWindowFactory"/>
50
+
51
+ <!-- Settings page (Preferences → Tools → Forge). -->
52
+ <applicationConfigurable
53
+ parentId="tools"
54
+ instance="com.aion0.forge.settings.ForgeConfigurable"
55
+ id="com.aion0.forge.settings.ForgeConfigurable"
56
+ displayName="Forge"/>
57
+
58
+ <!-- Status bar widget — connection state + click-to-switch. -->
59
+ <statusBarWidgetFactory
60
+ id="com.aion0.forge.statusbar"
61
+ implementation="com.aion0.forge.ui.toolwindow.ForgeStatusBarWidgetFactory"
62
+ order="last"/>
63
+
64
+ <notificationGroup id="Forge" displayType="BALLOON"/>
65
+ </extensions>
66
+
67
+ <actions>
68
+ <group id="com.aion0.forge.actions" text="Forge" popup="true">
69
+ <action id="com.aion0.forge.action.LoginAction"
70
+ class="com.aion0.forge.action.LoginAction"
71
+ text="Forge: Login"
72
+ description="Authenticate against the active Forge server."/>
73
+ <action id="com.aion0.forge.action.LogoutAction"
74
+ class="com.aion0.forge.action.LogoutAction"
75
+ text="Forge: Logout"
76
+ description="Clear the saved token for the active connection."/>
77
+ <action id="com.aion0.forge.action.SwitchConnectionAction"
78
+ class="com.aion0.forge.action.SwitchConnectionAction"
79
+ text="Forge: Switch Connection…"
80
+ description="Pick a different Forge server."/>
81
+ <action id="com.aion0.forge.action.OpenWebUIAction"
82
+ class="com.aion0.forge.action.OpenWebUIAction"
83
+ text="Forge: Open Web UI"
84
+ description="Open the Forge web UI in the default browser."/>
85
+ <add-to-group group-id="ToolsMenu" anchor="last"/>
86
+ </group>
87
+ </actions>
88
+ </idea-plugin>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="currentColor">
2
+ <path d="M12 2L3 7v6c0 5 3.5 9.5 9 11 5.5-1.5 9-6 9-11V7l-9-5zm0 4.5l5 2.8v3.4l-5-2.8-5 2.8V9.3l5-2.8zm-5 6.4l5 2.8v5.6c-3.4-1.4-5-4.6-5-7.6v-.8zm10 .8c0 3-1.6 6.2-5 7.6v-5.6l5-2.8v.8z"/>
3
+ </svg>
@@ -209,7 +209,7 @@ export function resolveTerminalLaunch(agentId?: string): TerminalLaunchInfo {
209
209
 
210
210
  // Determine CLI command and capabilities from cliType
211
211
  const cliMap: Record<string, { cmd: string; session: boolean; resume: string }> = {
212
- 'claude-code': { cmd: 'claude', session: true, resume: '-c' },
212
+ 'claude-code': { cmd: 'claude', session: true, resume: '--resume' },
213
213
  'codex': { cmd: 'codex', session: false, resume: '' },
214
214
  'aider': { cmd: 'aider', session: false, resume: '' },
215
215
  'generic': { cmd: agentCfg.path || agentId || 'claude', session: false, resume: '' },
@@ -17,6 +17,7 @@ Forge is a self-hosted Vibe Coding platform for Claude Code. It provides a brows
17
17
  | **Remote Access** | One-click Cloudflare tunnel for remote browsing |
18
18
  | **GitHub Issue Auto-fix** | Scan issues, auto-fix, create PRs |
19
19
  | **Memory (optional)** | Code graph + knowledge via `@aion0/temper` MCP server |
20
+ | **IDE Plugins** | First-party VSCode extension and IntelliJ plugin — drive workspaces, agents, terminals, pipelines and docs from inside the editor (see `13-ide-plugins.md`) |
20
21
 
21
22
  ## Quick Start
22
23
 
@@ -0,0 +1,90 @@
1
+ # IDE Plugins
2
+
3
+ Forge ships with two first-party IDE plugins that mirror the web UI's feature set so you can drive workspaces, agent terminals, pipelines and docs without leaving the editor. Both are **thin clients** — they require a Forge server running locally (or reachable over a tunnel).
4
+
5
+ | | VSCode extension | IntelliJ plugin |
6
+ |---|---|---|
7
+ | Marketplace ID | `aion0.forge-vibecoding` | `Forge Vibe Coding` (`com.aion0.forge`) |
8
+ | Install | VSCode → Extensions → search `Forge Vibe Coding` | IDE → Settings → Plugins → Marketplace → search `Forge Vibe Coding` |
9
+ | Source | `vscode-extension/` | `intellij-plugin/` |
10
+ | Min IDE | VSCode ≥ 1.80 | IntelliJ Platform ≥ 2024.1 (build 241) |
11
+
12
+ ## Prerequisites
13
+
14
+ The plugins do **not** ship Forge — install it once first:
15
+
16
+ ```bash
17
+ npm install -g @aion0/forge
18
+ forge server start
19
+ ```
20
+
21
+ Default port `8403`. The plugins auto-detect `http://localhost:8403`. For remote forges (over Cloudflare tunnel or LAN), add a connection in plugin settings (see below).
22
+
23
+ ## Tool Window / Sidebar Layout
24
+
25
+ Both plugins expose four tabs:
26
+
27
+ | Tab | Shows | Right-click actions | Double-click |
28
+ |---|---|---|---|
29
+ | **Workspaces** | Forge workspaces with daemon status (🟢/○) and per-smith status emoji (▶ running / ⏸ paused / ✓ done / ✕ failed / ◐ starting) | Workspace: start / stop / restart daemon. Smith: open terminal, send message, pause/resume, mark done/failed/idle, retry. | Smith → attach to its tmux session in an IDE terminal |
30
+ | **Terminals** | Each forge project as a folder with its claude sessions (★ = bound/pinned default). | Project: **Open With ▸** submenu of every configured agent (claude/codex/aider/...) — fresh launch; New Session… (pick agent); Plain Terminal Here. Session: Resume; Resume With… submenu; Pin as Default Session. | Session row resumes that exact session via `claude --resume <id>` |
31
+ | **Pipelines** | Forge projects with their pipeline bindings (⚙ enabled / ⊘ disabled) and recent runs (▶/✓/✕/⊘). | Project: Add Pipeline…; Binding: Trigger Now, Enable/Disable, Remove; Run: Show Nodes; Node: Show Result. | Binding: trigger; Run: expand to see nodes; Node: open prompt/result/diff/log as a markdown buffer |
32
+ | **Docs** | Configured doc roots → file/dir tree. | Dir: Open Terminal Here (runs claude); File: Open. | File: open in IDE editor |
33
+
34
+ ## Multi-Connection (Local + Remote forges)
35
+
36
+ Both plugins support multiple Forge servers — useful when you have one Forge running locally for `~/.forge` and a second running on an office Mac mini exposed via tunnel.
37
+
38
+ **VSCode**: edit `forge.connections` in settings.json:
39
+ ```json
40
+ {
41
+ "forge.connections": [
42
+ { "name": "Local", "serverUrl": "http://localhost:8403", "terminalUrl": "ws://localhost:8404" },
43
+ { "name": "Office", "serverUrl": "https://forge-office.trycloudflare.com", "terminalUrl": "wss://forge-office.trycloudflare.com" }
44
+ ],
45
+ "forge.activeConnection": "Local"
46
+ }
47
+ ```
48
+ Click the status-bar entry (bottom right, `Forge: <name>`) to switch.
49
+
50
+ **IntelliJ**: Settings → Tools → Forge → Connections list. Status-bar widget toggles active connection.
51
+
52
+ Tokens are stored per-connection: VSCode uses `SecretStorage`, IntelliJ uses `PasswordSafe`. `Forge: Login` (command palette / Tools menu) prompts for the admin password and caches the bearer token.
53
+
54
+ ## How agent terminals work
55
+
56
+ When you double-click a session row or pick `Open With ▸ <agent>`:
57
+
58
+ 1. Plugin calls `GET /api/agents?resolve=<agentId>` → gets `cliCmd`, `cliType`, `supportsSession`, `env`, `model`.
59
+ 2. For specific-session resume: `claude --resume <sid>` (forced regardless of API's `resumeFlag` — `-c` is wrong for specific resume).
60
+ 3. Profile env vars are forwarded to the spawned process.
61
+ 4. `model` is passed as `--model <name>` for claude-code agents (so a "sonnet" profile actually runs sonnet).
62
+ 5. **IntelliJ** spawns the agent CLI directly as the pty's primary process (`LocalTerminalDirectRunner` subclass with `enableShellIntegration = false` + `configureStartupOptions` override) — no shell, no `executeCommand` race. The user's login shell (`$SHELL -l`) is wrapped around it so `.zprofile` / `.bash_profile` is sourced for PATH.
63
+ 6. **VSCode** uses the existing terminal-server WebSocket (`forge.terminalUrl`) to attach.
64
+
65
+ Workspace smith terminals attach to a pre-existing tmux session via `tmux -2 -u attach -t <session>` (UTF-8 + 256-color forced, otherwise JediTerm/xterm.js render boxes wrong).
66
+
67
+ ## Releasing new versions (maintainers)
68
+
69
+ Each plugin has a `publish.sh` in its directory:
70
+
71
+ ```bash
72
+ # VSCode — needs `vsce login aion0` cached, or $VSCE_PAT env var
73
+ cd vscode-extension
74
+ ./publish.sh patch # bump 0.2.x → 0.2.(x+1) and publish
75
+
76
+ # IntelliJ — needs $JETBRAINS_MARKETPLACE_TOKEN env var
77
+ cd intellij-plugin
78
+ ./publish.sh patch
79
+ ```
80
+
81
+ VSCode goes live in 1–2 minutes. IntelliJ goes through human moderation: 1–3 business days for the first publish, hours-to-1-day for subsequent updates of an already-approved plugin.
82
+
83
+ ## Troubleshooting
84
+
85
+ - **"Not connected" / "401" in tree** — Forge server isn't running or the token expired. Run `Forge: Login` (or `forge server start` if the server is down).
86
+ - **Terminal opens but agent CLI fails** ("`claude: command not found`") — IDE inherited a stripped PATH. The IntelliJ plugin spawns under `$SHELL -l -c …` to source `.zprofile`/`.bash_profile`; if your PATH is set in `.zshrc` only (not `.zprofile`), move it.
87
+ - **Smith terminal shows garbled output** — make sure tmux ≥ 3.0 and the IDE terminal supports UTF-8 + 256-color. The plugin already passes `tmux -2 -u attach`; if it still looks broken, check `$LANG` is `*.UTF-8`.
88
+ - **Picked a session, claude opens a different one** — Forge ≤ 0.5.43 had `resume: '-c'` hardcoded for claude (which is `--continue`, zero-arg). Upgrade Forge or pull the latest `lib/agents/index.ts`.
89
+ - **JetBrains plugin install fails with "incompatible build"** — verify your IDE is build 241+ (Help → About → Build #). The plugin's `sinceBuild = 241`.
90
+ - **Tree keeps collapsing folders I expanded** — fixed in IntelliJ plugin v0.1.17 (full path expansion is preserved across the 5-second poll, not just top-level).
@@ -43,6 +43,7 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
43
43
  | `10-troubleshooting.md` | Common issues and solutions |
44
44
  | `11-workspace.md` | Workspace (Forge Smiths) — multi-agent orchestration, daemon, message bus, profiles |
45
45
  | `12-usage.md` | Token usage analytics — charts, heatmap, cost estimation, by model/project/source |
46
+ | `13-ide-plugins.md` | VSCode extension + IntelliJ plugin — install, tabs, multi-connection, agent terminal launching |
46
47
 
47
48
  ## Matching questions to docs
48
49
 
@@ -64,3 +65,5 @@ The token is valid for 24 hours. Store it in a variable and reuse for all API ca
64
65
  - Usage/cost/tokens/spending/billing/analytics → `12-usage.md`
65
66
  - Terminal dock/float/mouse toggle/reconnect → `07-projects.md` + `11-workspace.md`
66
67
  - Sidebar collapse/project tabs/favorites → `07-projects.md`
68
+ - VSCode/IntelliJ/IDE plugin/extension/marketplace → `13-ide-plugins.md`
69
+ - vsce/vsix/JetBrains marketplace publish → `13-ide-plugins.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.5.43",
3
+ "version": "0.5.44",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -0,0 +1,11 @@
1
+ .vscode/**
2
+ .vscode-test/**
3
+ src/**
4
+ .gitignore
5
+ .yarnrc
6
+ vsc-extension-quickstart.md
7
+ tsconfig.json
8
+ **/.eslintrc.json
9
+ **/*.map
10
+ **/*.ts
11
+ package-lock.json
@@ -0,0 +1,48 @@
1
+ # Forge VSCode Extension
2
+
3
+ Native VSCode integration for [Forge](https://github.com/aiwatching/forge).
4
+
5
+ ## What it does
6
+
7
+ - **Sidebar** — workspaces, smiths, active terminals, and background tasks at a glance
8
+ - **Native terminals** — attach to or create Forge tmux sessions in VSCode's terminal panel (uses VSCode's own Pseudoterminal API; sessions show up next to your regular terminals)
9
+ - **Send selection** — pipe selected code to a running Forge terminal
10
+ - **Notifications** — smith bell events surface as VSCode notifications
11
+ - **Status bar** — connection indicator + quick command access
12
+
13
+ ## Setup
14
+
15
+ 1. Install Forge CLI: `npm install -g @aion0/forge`
16
+ 2. Start the server: `forge server start`
17
+ 3. Install this extension (VSCode → "Install from VSIX..." or marketplace)
18
+ 4. Cmd/Ctrl+Shift+P → `Forge: Login` → enter your admin password
19
+
20
+ The token is stored in VSCode's SecretStorage and reused across sessions.
21
+
22
+ ## Settings
23
+
24
+ | Key | Default | Description |
25
+ |-----|---------|-------------|
26
+ | `forge.serverUrl` | `http://localhost:8403` | Forge HTTP server |
27
+ | `forge.terminalUrl` | `ws://localhost:8404` | Terminal WebSocket |
28
+ | `forge.autoStart` | `false` | Spawn `forge server start` on activation if unreachable |
29
+ | `forge.notifications.enabled` | `true` | Smith-bell → VSCode notification |
30
+ | `forge.refreshInterval` | `5` | Tree refresh interval (seconds) |
31
+
32
+ ## Local install
33
+
34
+ ```bash
35
+ cd vscode-extension
36
+ npm install
37
+ npm run package # produces forge-vscode.vsix
38
+ code --install-extension forge-vscode.vsix
39
+ ```
40
+
41
+ ## Develop
42
+
43
+ ```bash
44
+ cd vscode-extension
45
+ npm install
46
+ npm run watch # in one terminal
47
+ # In VSCode: F5 to launch Extension Development Host
48
+ ```
Binary file
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
2
+ <path d="M12 2L3 7v6c0 5 3.5 9.5 9 11 5.5-1.5 9-6 9-11V7l-9-5zm0 4.5l5 2.8v3.4l-5-2.8-5 2.8V9.3l5-2.8zm-5 6.4l5 2.8v5.6c-3.4-1.4-5-4.6-5-7.6v-.8zm10 .8c0 3-1.6 6.2-5 7.6v-5.6l5-2.8v.8z"/>
3
+ </svg>