@aion0/forge 0.5.44 → 0.5.46
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.
- package/RELEASE_NOTES.md +3 -15
- package/package.json +1 -1
- package/tsconfig.json +3 -1
- package/intellij-plugin/README.md +0 -53
- package/intellij-plugin/build.gradle.kts +0 -60
- package/intellij-plugin/gradle/gradle-daemon-jvm.properties +0 -12
- package/intellij-plugin/gradle.properties +0 -9
- package/intellij-plugin/publish.sh +0 -78
- package/intellij-plugin/settings.gradle.kts +0 -7
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LoginAction.kt +0 -49
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/LogoutAction.kt +0 -18
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/OpenWebUIAction.kt +0 -13
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/action/SwitchConnectionAction.kt +0 -26
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/api/ForgeClient.kt +0 -115
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/auth/Auth.kt +0 -31
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/connection/ConnectionManager.kt +0 -95
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/settings/ForgeConfigurable.kt +0 -81
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/DocsView.kt +0 -99
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeStatusBarWidgetFactory.kt +0 -94
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeToolWindowFactory.kt +0 -27
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/ForgeTreeView.kt +0 -176
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/Helpers.kt +0 -48
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/PipelinesView.kt +0 -226
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TerminalsView.kt +0 -309
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/TreeNodeData.kt +0 -33
- package/intellij-plugin/src/main/kotlin/com/aion0/forge/ui/toolwindow/WorkspacesView.kt +0 -166
- package/intellij-plugin/src/main/resources/META-INF/plugin.xml +0 -88
- package/intellij-plugin/src/main/resources/icons/forge.svg +0 -3
- package/vscode-extension/.vscodeignore +0 -11
- package/vscode-extension/README.md +0 -48
- package/vscode-extension/media/icon.png +0 -0
- package/vscode-extension/media/icon.svg +0 -3
- package/vscode-extension/package-lock.json +0 -4046
- package/vscode-extension/package.json +0 -514
- package/vscode-extension/publish.sh +0 -49
- package/vscode-extension/src/api/client.ts +0 -217
- package/vscode-extension/src/auth/auth.ts +0 -32
- package/vscode-extension/src/commands/auth.ts +0 -44
- package/vscode-extension/src/commands/connection.ts +0 -113
- package/vscode-extension/src/commands/docs.ts +0 -40
- package/vscode-extension/src/commands/pipeline.ts +0 -103
- package/vscode-extension/src/commands/server.ts +0 -50
- package/vscode-extension/src/commands/smith.ts +0 -112
- package/vscode-extension/src/commands/task.ts +0 -43
- package/vscode-extension/src/commands/terminal.ts +0 -279
- package/vscode-extension/src/commands/workspace.ts +0 -138
- package/vscode-extension/src/connection/manager.ts +0 -80
- package/vscode-extension/src/docs/fs-provider.ts +0 -94
- package/vscode-extension/src/docs/result-provider.ts +0 -33
- package/vscode-extension/src/docs/transport.ts +0 -22
- package/vscode-extension/src/extension.ts +0 -314
- package/vscode-extension/src/statusbar.ts +0 -70
- package/vscode-extension/src/terminal/pseudoterm.ts +0 -123
- package/vscode-extension/src/views/docs.ts +0 -145
- package/vscode-extension/src/views/pipelines.ts +0 -222
- package/vscode-extension/src/views/terminals.ts +0 -91
- package/vscode-extension/src/views/workspaces.ts +0 -243
- package/vscode-extension/tsconfig.json +0 -16
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
# Forge v0.5.
|
|
1
|
+
# Forge v0.5.46
|
|
2
2
|
|
|
3
3
|
Released: 2026-04-26
|
|
4
4
|
|
|
5
|
-
## Changes since v0.5.
|
|
5
|
+
## Changes since v0.5.45
|
|
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
|
-
|
|
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.45...v0.5.46
|
package/package.json
CHANGED
package/tsconfig.json
CHANGED
|
@@ -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,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
|
-
}
|