@aion0/forge 0.5.44 → 0.5.47

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 (59) hide show
  1. package/RELEASE_NOTES.md +3 -15
  2. package/middleware.ts +3 -1
  3. package/package.json +1 -1
  4. package/tsconfig.json +3 -1
  5. package/intellij-plugin/README.md +0 -53
  6. package/intellij-plugin/build.gradle.kts +0 -60
  7. package/intellij-plugin/gradle/gradle-daemon-jvm.properties +0 -12
  8. package/intellij-plugin/gradle.properties +0 -9
  9. package/intellij-plugin/publish.sh +0 -78
  10. package/intellij-plugin/settings.gradle.kts +0 -7
  11. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +0 -49
  12. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +0 -18
  13. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +0 -13
  14. package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +0 -26
  15. package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +0 -115
  16. package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +0 -31
  17. package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +0 -95
  18. package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +0 -81
  19. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +0 -99
  20. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +0 -94
  21. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +0 -27
  22. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +0 -176
  23. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +0 -48
  24. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +0 -226
  25. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +0 -309
  26. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +0 -33
  27. package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +0 -166
  28. package/intellij-plugin/src/main/resources/META-INF/plugin.xml +0 -88
  29. package/intellij-plugin/src/main/resources/icons/forge.svg +0 -3
  30. package/vscode-extension/.vscodeignore +0 -11
  31. package/vscode-extension/README.md +0 -48
  32. package/vscode-extension/media/icon.png +0 -0
  33. package/vscode-extension/media/icon.svg +0 -3
  34. package/vscode-extension/package-lock.json +0 -4046
  35. package/vscode-extension/package.json +0 -514
  36. package/vscode-extension/publish.sh +0 -49
  37. package/vscode-extension/src/api/client.ts +0 -217
  38. package/vscode-extension/src/auth/auth.ts +0 -32
  39. package/vscode-extension/src/commands/auth.ts +0 -44
  40. package/vscode-extension/src/commands/connection.ts +0 -113
  41. package/vscode-extension/src/commands/docs.ts +0 -40
  42. package/vscode-extension/src/commands/pipeline.ts +0 -103
  43. package/vscode-extension/src/commands/server.ts +0 -50
  44. package/vscode-extension/src/commands/smith.ts +0 -112
  45. package/vscode-extension/src/commands/task.ts +0 -43
  46. package/vscode-extension/src/commands/terminal.ts +0 -279
  47. package/vscode-extension/src/commands/workspace.ts +0 -138
  48. package/vscode-extension/src/connection/manager.ts +0 -80
  49. package/vscode-extension/src/docs/fs-provider.ts +0 -94
  50. package/vscode-extension/src/docs/result-provider.ts +0 -33
  51. package/vscode-extension/src/docs/transport.ts +0 -22
  52. package/vscode-extension/src/extension.ts +0 -314
  53. package/vscode-extension/src/statusbar.ts +0 -70
  54. package/vscode-extension/src/terminal/pseudoterm.ts +0 -123
  55. package/vscode-extension/src/views/docs.ts +0 -145
  56. package/vscode-extension/src/views/pipelines.ts +0 -222
  57. package/vscode-extension/src/views/terminals.ts +0 -91
  58. package/vscode-extension/src/views/workspaces.ts +0 -243
  59. package/vscode-extension/tsconfig.json +0 -16
package/RELEASE_NOTES.md CHANGED
@@ -1,20 +1,8 @@
1
- # Forge v0.5.44
1
+ # Forge v0.5.47
2
2
 
3
3
  Released: 2026-04-26
4
4
 
5
- ## Changes since v0.5.43
5
+ ## Changes since v0.5.46
6
6
 
7
- ### Features
8
- - feat: IntelliJ plugin — Forge tool window + agent terminal launcher
9
- - feat: vscode extension scaffold — workspace/terminal/task views + native session picker
10
7
 
11
- ### Documentation
12
- - docs: add 13-ide-plugins.md covering VSCode + IntelliJ plugins
13
- - feat(vscode-ext): connections, pipelines, docs, deep-link to web UI
14
-
15
- ### Other
16
- - feat(vscode-ext): connections, pipelines, docs, deep-link to web UI
17
- - feat(vscode-ext): workspace bootstrap, daemon control, smith click + actions
18
-
19
-
20
- **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.43...v0.5.44
8
+ **Full Changelog**: https://github.com/aiwatching/forge/compare/v0.5.46...v0.5.47
package/middleware.ts CHANGED
@@ -9,10 +9,12 @@ export function middleware(req: NextRequest) {
9
9
 
10
10
  const { pathname } = req.nextUrl;
11
11
 
12
- // Allow auth endpoints and static assets without login
12
+ // Allow auth endpoints, version probe (used by IDE plugins to detect a live
13
+ // server before prompting for password), and static assets without login.
13
14
  if (
14
15
  pathname.startsWith('/login') ||
15
16
  pathname.startsWith('/api/auth') ||
17
+ pathname === '/api/version' ||
16
18
  pathname.startsWith('/api/telegram') ||
17
19
  (pathname.startsWith('/api/workspace') && (pathname.endsWith('/smith') || pathname === '/api/workspace')) ||
18
20
  pathname.startsWith('/_next') ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aion0/forge",
3
- "version": "0.5.44",
3
+ "version": "0.5.47",
4
4
  "description": "Unified AI workflow platform — multi-model task orchestration, persistent sessions, web terminal, remote access",
5
5
  "type": "module",
6
6
  "scripts": {
package/tsconfig.json CHANGED
@@ -37,6 +37,8 @@
37
37
  ".next/dev/dev/types/**/*.ts"
38
38
  ],
39
39
  "exclude": [
40
- "node_modules"
40
+ "node_modules",
41
+ "vscode-extension",
42
+ "intellij-plugin"
41
43
  ]
42
44
  }
@@ -1,53 +0,0 @@
1
- # Forge IntelliJ Plugin
2
-
3
- Native IntelliJ IDEA / JetBrains IDE integration for [Forge](https://github.com/aiwatching/forge).
4
- Mirrors (in progress) the feature set of the VSCode extension.
5
-
6
- ## Status — v0.1.0 (scaffold)
7
-
8
- - [x] Project structure (Gradle Kotlin DSL + IntelliJ Platform Plugin v1.17 + Kotlin 1.9)
9
- - [x] `plugin.xml` — registers tool window, settings page, status bar widget, actions
10
- - [x] Multi-connection support — persisted to `forge.xml` application state
11
- - [x] Auth via PasswordSafe (per-connection token)
12
- - [x] HTTP client (`java.net.http.HttpClient` + Gson)
13
- - [x] Status bar widget — shows connection name + connectivity, click switches
14
- - [x] Settings UI — edit connections, pick active
15
- - [x] Login / Logout / Switch Connection / Open Web UI actions
16
-
17
- ## Coming next
18
-
19
- - [ ] Tool window tabs:
20
- - [ ] Workspaces — projects, smiths, daemon control, Inbox/Log expansion
21
- - [ ] Terminals — list active forge tmux sessions, attach
22
- - [ ] Pipelines — project-bound bindings, recent runs, node detail markdown
23
- - [ ] Docs — file tree (local file:// or remote forge-docs:// VFS)
24
- - [ ] Smith terminal attach (custom JediTerm session over forge WebSocket)
25
- - [ ] Pipeline node result viewer
26
- - [ ] Workspace bootstrap from current project root
27
- - [ ] Send selection to forge terminal
28
-
29
- ## Build
30
-
31
- ```bash
32
- cd intellij-plugin
33
- ./gradlew buildPlugin # produces build/distributions/forge-intellij-0.1.0.zip
34
- ./gradlew runIde # launches a sandbox IntelliJ with the plugin loaded
35
- ```
36
-
37
- ## Install locally
38
-
39
- After `buildPlugin`:
40
- 1. JetBrains IDE → Settings → Plugins → ⚙ → "Install Plugin from Disk…"
41
- 2. Pick `build/distributions/forge-intellij-0.1.0.zip`
42
- 3. Restart IDE
43
- 4. View → Tool Windows → Forge
44
-
45
- Then **Tools → Forge: Login** to authenticate against the active connection.
46
-
47
- ## Settings
48
-
49
- Settings → Tools → Forge:
50
- - Connections table (add / edit / remove)
51
- - Active connection name
52
-
53
- Tokens are stored separately in IntelliJ's PasswordSafe — clear via **Tools → Forge: Logout**.
@@ -1,60 +0,0 @@
1
- plugins {
2
- id("org.jetbrains.kotlin.jvm") version "2.0.21"
3
- id("org.jetbrains.intellij.platform") version "2.15.0"
4
- }
5
-
6
- group = "com.aion0.forge"
7
- version = "0.1.19"
8
-
9
- repositories {
10
- mavenCentral()
11
- intellijPlatform {
12
- defaultRepositories()
13
- }
14
- }
15
-
16
- dependencies {
17
- intellijPlatform {
18
- intellijIdeaCommunity("2024.1")
19
- // Bundled terminal plugin — needed for TerminalView (smith terminal attach).
20
- bundledPlugin("org.jetbrains.plugins.terminal")
21
- }
22
- }
23
-
24
- kotlin {
25
- jvmToolchain(17)
26
- }
27
-
28
- // Force every Kotlin compile task (and its worker JVM) to run on JDK 17
29
- // instead of the host JDK (25 on this machine, which crashes Kotlin's
30
- // internal version parser).
31
- val jdk17Launcher = javaToolchains.launcherFor {
32
- languageVersion = JavaLanguageVersion.of(17)
33
- }
34
- tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
35
- kotlinJavaToolchain.toolchain.use(jdk17Launcher)
36
- }
37
-
38
- intellijPlatform {
39
- buildSearchableOptions = false
40
-
41
- pluginConfiguration {
42
- version = "0.1.19"
43
- ideaVersion {
44
- sinceBuild = "241"
45
- // Don't pin untilBuild — keeps the plugin compatible with newer
46
- // IDEs until the platform actually breaks something.
47
- untilBuild = provider { null }
48
- }
49
- }
50
-
51
- // `gradle publishPlugin` uploads to the JetBrains Marketplace.
52
- // Get a permanent token at https://plugins.jetbrains.com/author/me/tokens
53
- // and export it as JETBRAINS_MARKETPLACE_TOKEN before running.
54
- publishing {
55
- token = providers.environmentVariable("JETBRAINS_MARKETPLACE_TOKEN")
56
- // Use "default" for stable releases. Switch to "beta" / "eap" for
57
- // pre-releases (users opt in via the IDE plugin settings).
58
- channels = listOf("default")
59
- }
60
- }
@@ -1,12 +0,0 @@
1
- #This file is generated by updateDaemonJvm
2
- toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/40b4344c056b4284246d176d9701577f/redirect
3
- toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/466f185020ff3c07ab32696653613a8d/redirect
4
- toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/40b4344c056b4284246d176d9701577f/redirect
5
- toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/466f185020ff3c07ab32696653613a8d/redirect
6
- toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1050b2216f8beaaecc1289b17d30b586/redirect
7
- toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/2208feeb3d4e12f412e9a450db1a842a/redirect
8
- toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/40b4344c056b4284246d176d9701577f/redirect
9
- toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/466f185020ff3c07ab32696653613a8d/redirect
10
- toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3676ee7aa5095d7f22645eb0f22ca159/redirect
11
- toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/69a793dd932268c7d1ae9d8b855de8ed/redirect
12
- toolchainVersion=17
@@ -1,9 +0,0 @@
1
- kotlin.stdlib.default.dependency=false
2
- org.gradle.jvmargs=-Xmx2g -XX:+UseG1GC
3
- org.gradle.caching=true
4
- org.gradle.parallel=true
5
- # Force Gradle itself to use the toolchain-provisioned JDK 17 instead of the
6
- # host JDK (which is 25 on this machine and breaks the Kotlin compiler).
7
- org.gradle.java.installations.auto-download=true
8
- # Don't let the Kotlin daemon silently fall back to the host JVM (25).
9
- kotlin.daemon.useFallbackStrategy=false
@@ -1,78 +0,0 @@
1
- #!/bin/bash
2
- # Publish the Forge IntelliJ plugin to the JetBrains Marketplace.
3
- #
4
- # Usage:
5
- # ./publish.sh # publish current version in build.gradle.kts
6
- # ./publish.sh patch # bump patch (0.1.19 → 0.1.20) + publish
7
- # ./publish.sh minor # bump minor (0.1.x → 0.2.0) + publish
8
- # ./publish.sh major # bump major (0.x.x → 1.0.0) + publish
9
- # ./publish.sh 0.5.0 # publish exact version
10
- #
11
- # Auth: get a permanent token at https://plugins.jetbrains.com/author/me/tokens
12
- # and export it as JETBRAINS_MARKETPLACE_TOKEN. The first publish must go
13
- # through the JetBrains Marketplace upload form for moderation; subsequent
14
- # updates can use this script.
15
- #
16
- # Requires JDK 17 — Gradle's Kotlin compiler crashes on the host JDK 25.
17
-
18
- set -euo pipefail
19
- cd "$(dirname "$0")"
20
-
21
- if [[ -z "${JETBRAINS_MARKETPLACE_TOKEN:-}" ]]; then
22
- echo "ERROR: set JETBRAINS_MARKETPLACE_TOKEN env var first." >&2
23
- echo " Get one at https://plugins.jetbrains.com/author/me/tokens" >&2
24
- exit 1
25
- fi
26
-
27
- ARG="${1:-}"
28
-
29
- bump_version() {
30
- local kind="$1"
31
- local current
32
- current=$(grep -E '^version = "' build.gradle.kts | head -1 | sed -E 's/^version = "([^"]+)".*/\1/')
33
- local new
34
- case "$kind" in
35
- patch|minor|major)
36
- local major minor patch
37
- IFS='.' read -r major minor patch <<<"$current"
38
- case "$kind" in
39
- patch) patch=$((patch + 1)) ;;
40
- minor) minor=$((minor + 1)); patch=0 ;;
41
- major) major=$((major + 1)); minor=0; patch=0 ;;
42
- esac
43
- new="${major}.${minor}.${patch}"
44
- ;;
45
- *)
46
- new="$kind"
47
- ;;
48
- esac
49
- echo "→ Bumping $current → $new"
50
- # Replace both `version = "..."` (top-level) and `version = "..."` (in pluginConfiguration).
51
- sed -i.bak -E "s/version = \"$current\"/version = \"$new\"/g" build.gradle.kts
52
- rm -f build.gradle.kts.bak
53
- }
54
-
55
- case "$ARG" in
56
- "")
57
- ;;
58
- patch|minor|major)
59
- bump_version "$ARG"
60
- ;;
61
- *)
62
- if [[ "$ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
63
- bump_version "$ARG"
64
- else
65
- echo "Unknown argument: $ARG" >&2
66
- echo "Usage: $0 [patch|minor|major|x.y.z]" >&2
67
- exit 1
68
- fi
69
- ;;
70
- esac
71
-
72
- VERSION=$(grep -E '^version = "' build.gradle.kts | head -1 | sed -E 's/^version = "([^"]+)".*/\1/')
73
- echo "→ Publishing version $VERSION to JetBrains Marketplace…"
74
-
75
- JAVA_HOME="$(/usr/libexec/java_home -v 17)" gradle publishPlugin
76
-
77
- echo "✓ Published Forge Vibe Coding v$VERSION"
78
- echo "→ https://plugins.jetbrains.com/author/me/plugins"
@@ -1,7 +0,0 @@
1
- // Foojay resolver auto-downloads JDK 17 if the host doesn't have one,
2
- // so users don't need to install Java separately.
3
- plugins {
4
- id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
5
- }
6
-
7
- rootProject.name = "forge-intellij"
@@ -1,49 +0,0 @@
1
- package com.aion0.forge.action
2
-
3
- import com.aion0.forge.api.ForgeClient
4
- import com.aion0.forge.connection.ConnectionManager
5
- import com.intellij.notification.NotificationGroupManager
6
- import com.intellij.notification.NotificationType
7
- import com.intellij.openapi.actionSystem.AnAction
8
- import com.intellij.openapi.actionSystem.AnActionEvent
9
- import com.intellij.openapi.application.ApplicationManager
10
- import com.intellij.openapi.progress.ProgressIndicator
11
- import com.intellij.openapi.progress.Task
12
- import com.intellij.openapi.ui.Messages
13
-
14
- class LoginAction : AnAction() {
15
- override fun actionPerformed(e: AnActionEvent) {
16
- val project = e.project
17
- val active = ConnectionManager.get().active()
18
- val pw = Messages.showPasswordDialog(
19
- project,
20
- "Admin password for ${active.name} (${active.serverUrl})",
21
- "Forge Login",
22
- null,
23
- ) ?: return
24
-
25
- ProgressManager().queue(project, "Forge: signing in") {
26
- val res = ForgeClient.get().login(pw)
27
- ApplicationManager.getApplication().invokeLater {
28
- val group = NotificationGroupManager.getInstance().getNotificationGroup("Forge")
29
- if (res.ok) {
30
- group.createNotification("Forge: logged in to ${active.name}", NotificationType.INFORMATION)
31
- .notify(project)
32
- } else {
33
- group.createNotification("Forge login failed: ${res.error}", NotificationType.ERROR)
34
- .notify(project)
35
- }
36
- }
37
- }
38
- }
39
- }
40
-
41
- private class ProgressManager {
42
- fun queue(project: com.intellij.openapi.project.Project?, title: String, work: () -> Unit) {
43
- com.intellij.openapi.progress.ProgressManager.getInstance().run(
44
- object : Task.Backgroundable(project, title, false) {
45
- override fun run(indicator: ProgressIndicator) { work() }
46
- },
47
- )
48
- }
49
- }
@@ -1,18 +0,0 @@
1
- package com.aion0.forge.action
2
-
3
- import com.aion0.forge.api.ForgeClient
4
- import com.aion0.forge.connection.ConnectionManager
5
- import com.intellij.notification.NotificationGroupManager
6
- import com.intellij.notification.NotificationType
7
- import com.intellij.openapi.actionSystem.AnAction
8
- import com.intellij.openapi.actionSystem.AnActionEvent
9
-
10
- class LogoutAction : AnAction() {
11
- override fun actionPerformed(e: AnActionEvent) {
12
- val active = ConnectionManager.get().active()
13
- ForgeClient.get().logout()
14
- NotificationGroupManager.getInstance().getNotificationGroup("Forge")
15
- .createNotification("Forge: logged out of ${active.name}", NotificationType.INFORMATION)
16
- .notify(e.project)
17
- }
18
- }
@@ -1,13 +0,0 @@
1
- package com.aion0.forge.action
2
-
3
- import com.aion0.forge.connection.ConnectionManager
4
- import com.intellij.ide.BrowserUtil
5
- import com.intellij.openapi.actionSystem.AnAction
6
- import com.intellij.openapi.actionSystem.AnActionEvent
7
-
8
- class OpenWebUIAction : AnAction() {
9
- override fun actionPerformed(e: AnActionEvent) {
10
- val url = ConnectionManager.get().active().serverUrl
11
- BrowserUtil.browse(url)
12
- }
13
- }
@@ -1,26 +0,0 @@
1
- package com.aion0.forge.action
2
-
3
- import com.aion0.forge.connection.ConnectionManager
4
- import com.intellij.openapi.actionSystem.AnAction
5
- import com.intellij.openapi.actionSystem.AnActionEvent
6
- import com.intellij.openapi.ui.popup.JBPopupFactory
7
- import com.intellij.openapi.ui.popup.PopupStep
8
- import com.intellij.openapi.ui.popup.util.BaseListPopupStep
9
-
10
- class SwitchConnectionAction : AnAction() {
11
- override fun actionPerformed(e: AnActionEvent) {
12
- val mgr = ConnectionManager.get()
13
- val items = mgr.list().map { it.name }
14
- if (items.isEmpty()) return
15
- val active = mgr.active().name
16
- val popup = JBPopupFactory.getInstance()
17
- .createListPopup(object : BaseListPopupStep<String>("Switch Forge Connection", items) {
18
- override fun getDefaultOptionIndex(): Int = items.indexOf(active).coerceAtLeast(0)
19
- override fun onChosen(selectedValue: String, finalChoice: Boolean): PopupStep<*>? {
20
- mgr.setActive(selectedValue)
21
- return PopupStep.FINAL_CHOICE
22
- }
23
- })
24
- popup.showInBestPositionFor(e.dataContext)
25
- }
26
- }
@@ -1,115 +0,0 @@
1
- package com.aion0.forge.api
2
-
3
- import com.aion0.forge.auth.Auth
4
- import com.aion0.forge.connection.ConnectionManager
5
- import com.google.gson.Gson
6
- import com.google.gson.JsonElement
7
- import com.intellij.openapi.application.ApplicationManager
8
- import com.intellij.openapi.components.Service
9
- import java.net.URI
10
- import java.net.http.HttpClient
11
- import java.net.http.HttpRequest
12
- import java.net.http.HttpResponse
13
- import java.time.Duration
14
-
15
- data class ApiResult(
16
- val ok: Boolean,
17
- val status: Int,
18
- val data: JsonElement? = null,
19
- val error: String? = null,
20
- )
21
-
22
- /** Lightweight HTTP wrapper around forge's REST API. Uses java.net.http for
23
- * zero extra deps; Gson is bundled in the IntelliJ Platform. */
24
- @Service
25
- class ForgeClient {
26
- private val http: HttpClient = HttpClient.newBuilder()
27
- .connectTimeout(Duration.ofSeconds(5))
28
- // Force HTTP/1.1. forge's Next.js server doesn't always handle the
29
- // HTTP/2 upgrade probe cleanly, which manifests as
30
- // "http/1.1 header parser received no byte".
31
- .version(HttpClient.Version.HTTP_1_1)
32
- .build()
33
- private val gson = Gson()
34
-
35
- fun activeName(): String = ConnectionManager.get().active().name
36
- fun baseUrl(): String = ConnectionManager.get().active().serverUrl
37
- fun terminalUrl(): String = ConnectionManager.get().active().terminalUrl
38
-
39
- /** Verify password against the active connection's `/api/auth/verify`.
40
- * On success, persists the returned token in PasswordSafe. */
41
- fun login(password: String): ApiResult {
42
- val body = gson.toJson(mapOf("password" to password))
43
- val req = HttpRequest.newBuilder(URI.create("${baseUrl()}/api/auth/verify"))
44
- .header("Content-Type", "application/json")
45
- .timeout(Duration.ofSeconds(10))
46
- .POST(HttpRequest.BodyPublishers.ofString(body))
47
- .build()
48
- return runCatching {
49
- val res = http.send(req, HttpResponse.BodyHandlers.ofString())
50
- val parsed = res.body().takeIf { it.isNotEmpty() }?.let { gson.fromJson(it, JsonElement::class.java) }
51
- if (res.statusCode() in 200..299 && parsed?.asJsonObject?.get("token") != null) {
52
- val token = parsed.asJsonObject.get("token").asString
53
- Auth.get().setToken(activeName(), token)
54
- ApiResult(true, res.statusCode(), parsed)
55
- } else {
56
- val err = parsed?.asJsonObject?.get("error")?.asString ?: "HTTP ${res.statusCode()}"
57
- ApiResult(false, res.statusCode(), parsed, err)
58
- }
59
- }.getOrElse { ApiResult(false, 0, error = it.message ?: "network error") }
60
- }
61
-
62
- fun logout() {
63
- Auth.get().clearToken(activeName())
64
- }
65
-
66
- /** Quick liveness probe — does NOT require auth. */
67
- fun ping(): Boolean = runCatching {
68
- val req = HttpRequest.newBuilder(URI.create("${baseUrl()}/api/version"))
69
- .timeout(Duration.ofSeconds(2))
70
- .GET().build()
71
- http.send(req, HttpResponse.BodyHandlers.discarding()).statusCode() in 200..299
72
- }.getOrDefault(false)
73
-
74
- /** Generic request — returns `ApiResult` with parsed JSON body. */
75
- fun request(path: String, method: String = "GET", body: Any? = null): ApiResult {
76
- val url = "${baseUrl()}$path"
77
- return runCatching {
78
- val token = Auth.get().getToken(activeName())
79
- val builder = HttpRequest.newBuilder(URI.create(url))
80
- .header("Content-Type", "application/json")
81
- .header("Accept", "application/json")
82
- .timeout(Duration.ofSeconds(15))
83
- if (token != null) builder.header("X-Forge-Token", token)
84
- when (method.uppercase()) {
85
- "GET" -> builder.GET()
86
- "DELETE" -> builder.DELETE()
87
- "POST" -> builder.POST(jsonBody(body))
88
- "PUT" -> builder.PUT(jsonBody(body))
89
- else -> builder.method(method, jsonBody(body))
90
- }
91
- val res = http.send(builder.build(), HttpResponse.BodyHandlers.ofString())
92
- val text = res.body()
93
- val data = if (text.isNullOrBlank()) null
94
- else runCatching { gson.fromJson(text, JsonElement::class.java) }.getOrNull()
95
- if (res.statusCode() in 200..299) {
96
- ApiResult(true, res.statusCode(), data)
97
- } else {
98
- val err = data?.asJsonObject?.get("error")?.asString ?: "HTTP ${res.statusCode()}"
99
- ApiResult(false, res.statusCode(), data, "$err ($method $url)")
100
- }
101
- }.getOrElse {
102
- ApiResult(false, 0, error = "${it.javaClass.simpleName}: ${it.message ?: "network error"} ($method $url)")
103
- }
104
- }
105
-
106
- private fun jsonBody(body: Any?): HttpRequest.BodyPublisher =
107
- if (body == null) HttpRequest.BodyPublishers.noBody()
108
- else HttpRequest.BodyPublishers.ofString(gson.toJson(body))
109
-
110
- companion object {
111
- @JvmStatic
112
- fun get(): ForgeClient =
113
- ApplicationManager.getApplication().getService(ForgeClient::class.java)
114
- }
115
- }
@@ -1,31 +0,0 @@
1
- package com.aion0.forge.auth
2
-
3
- import com.intellij.credentialStore.CredentialAttributes
4
- import com.intellij.credentialStore.Credentials
5
- import com.intellij.ide.passwordSafe.PasswordSafe
6
- import com.intellij.openapi.application.ApplicationManager
7
- import com.intellij.openapi.components.Service
8
-
9
- /** Per-connection token storage backed by IntelliJ's PasswordSafe (system
10
- * keychain on macOS, KWallet/libsecret on Linux, Windows Credential Manager
11
- * on Windows). Key shape: `forge.token.<connectionName>`. */
12
- @Service
13
- class Auth {
14
- fun getToken(connectionName: String): String? =
15
- PasswordSafe.instance.getPassword(attrs(connectionName))
16
-
17
- fun setToken(connectionName: String, token: String) {
18
- PasswordSafe.instance.set(attrs(connectionName), Credentials("forge", token))
19
- }
20
-
21
- fun clearToken(connectionName: String) {
22
- PasswordSafe.instance.set(attrs(connectionName), null)
23
- }
24
-
25
- private fun attrs(name: String) = CredentialAttributes("forge.token.$name", "forge")
26
-
27
- companion object {
28
- @JvmStatic
29
- fun get(): Auth = ApplicationManager.getApplication().getService(Auth::class.java)
30
- }
31
- }
@@ -1,95 +0,0 @@
1
- package com.aion0.forge.connection
2
-
3
- import com.intellij.openapi.application.ApplicationManager
4
- import com.intellij.openapi.components.PersistentStateComponent
5
- import com.intellij.openapi.components.Service
6
- import com.intellij.openapi.components.State
7
- import com.intellij.openapi.components.Storage
8
- import com.intellij.util.messages.Topic
9
-
10
- data class ForgeConnection(
11
- var name: String = "Local",
12
- var serverUrl: String = "http://localhost:8403",
13
- var terminalUrl: String = "ws://localhost:8404",
14
- )
15
-
16
- data class ConnectionState(
17
- var connections: MutableList<ForgeConnection> = mutableListOf(
18
- ForgeConnection(),
19
- ),
20
- var activeName: String = "Local",
21
- )
22
-
23
- interface ConnectionListener {
24
- fun onConnectionChanged(active: ForgeConnection)
25
-
26
- companion object {
27
- val TOPIC: Topic<ConnectionListener> =
28
- Topic.create("Forge connection changed", ConnectionListener::class.java)
29
- }
30
- }
31
-
32
- /** Multi-connection registry, persisted to the application-level XML state.
33
- * Mirrors the VSCode extension's `forge.connections` + `forge.activeConnection`. */
34
- @Service
35
- @State(name = "ForgeConnections", storages = [Storage("forge.xml")])
36
- class ConnectionManager : PersistentStateComponent<ConnectionState> {
37
- private var state = ConnectionState()
38
-
39
- override fun getState(): ConnectionState = state
40
-
41
- override fun loadState(s: ConnectionState) {
42
- state = s
43
- if (state.connections.isEmpty()) state.connections = mutableListOf(ForgeConnection())
44
- }
45
-
46
- fun list(): List<ForgeConnection> = state.connections.toList()
47
-
48
- fun active(): ForgeConnection =
49
- state.connections.firstOrNull { it.name == state.activeName }
50
- ?: state.connections.first()
51
-
52
- fun setActive(name: String) {
53
- if (state.connections.none { it.name == name }) return
54
- state.activeName = name
55
- notifyChanged()
56
- }
57
-
58
- fun add(c: ForgeConnection) {
59
- require(state.connections.none { it.name == c.name }) {
60
- "A connection named \"${c.name}\" already exists"
61
- }
62
- state.connections.add(c)
63
- notifyChanged()
64
- }
65
-
66
- fun remove(name: String) {
67
- if (state.connections.size <= 1) return
68
- state.connections.removeAll { it.name == name }
69
- if (state.activeName == name) {
70
- state.activeName = state.connections.firstOrNull()?.name ?: "Local"
71
- }
72
- notifyChanged()
73
- }
74
-
75
- fun replaceAll(connections: List<ForgeConnection>, activeName: String) {
76
- if (connections.isEmpty()) return
77
- state.connections = connections.toMutableList()
78
- state.activeName = if (connections.any { it.name == activeName }) activeName
79
- else connections.first().name
80
- notifyChanged()
81
- }
82
-
83
- private fun notifyChanged() {
84
- ApplicationManager.getApplication()
85
- .messageBus
86
- .syncPublisher(ConnectionListener.TOPIC)
87
- .onConnectionChanged(active())
88
- }
89
-
90
- companion object {
91
- @JvmStatic
92
- fun get(): ConnectionManager =
93
- ApplicationManager.getApplication().getService(ConnectionManager::class.java)
94
- }
95
- }