ur-agent 1.44.10 → 1.45.3

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.
@@ -71,12 +71,16 @@ ur auth gemini
71
71
  ur auth antigravity
72
72
  ur config set provider ollama
73
73
  ur config set provider openai-compatible
74
- ur config set model qwen3-coder:480b-cloud
74
+ ur config set model qwen2.5-coder:7b
75
75
  ur config set base_url http://localhost:11434/v1
76
76
  ur config set provider.fallback ollama
77
77
  ur upgrade
78
78
  ```
79
79
 
80
+ The optional `provider.fallback` value is used only to print an explicit
81
+ recovery recommendation in provider diagnostics. It never silently or
82
+ automatically changes the active provider.
83
+
80
84
  ## v1.25.x Additions
81
85
 
82
86
  | Addition | Surface | What it adds |
@@ -135,8 +139,8 @@ components instead of one giant prompt.
135
139
 
136
140
  | Addition | Surface | What it adds |
137
141
  | --- | --- | --- |
138
- | Agent skill runner | `src/services/agents/agentSkillRunner.ts` | Reusable wrapper around `startBackgroundTask({ worktree: true, pr: true })` that polls to completion and returns a PR-style summary. |
139
- | Worktree slash skills | `/debug-v2`, `/refactor`, `/paper-implementation`, `/benchmark`, `/security-review`, `/dockerize`, `/latex-paper` | Bundled slash skills that expand into prompts for isolated worktree work with clean commits and PR output. |
142
+ | Agent skill runner | `src/services/agents/agentSkillRunner.ts` | Reusable isolated-worktree wrapper that polls to completion; PR creation is available only with explicit `createPr: true`. |
143
+ | Worktree slash skills | `/debug-v2`, `/refactor`, `/paper-implementation`, `/benchmark`, `/security-review`, `/dockerize`, `/latex-paper`, `/batch` | Bundled slash skills keep changes local, run focused checks, ask before the final full suite, and never publish automatically. |
140
144
  | Agent templates | `ur agent-templates install` | Adds `debug-v2`, `refactor`, `paper-implementation`, `benchmark`, `security-review`, `dockerize`, and `latex-paper` reusable agent templates under `.ur/agents/`. |
141
145
  | Worktree command | `ur worktree list\|status\|clean` | Inspect and clean up UR agent worktrees created by `ur bg` or slash skills. |
142
146
 
@@ -269,7 +273,7 @@ runners, so the core logic is deterministic and unit-tested offline.
269
273
  | Spec-driven development | `ur spec init\|generate\|approve\|run\|status` + `.ur/specs/` | requirements -> design -> tasks documents and a phase/approval record; executes the Spec Kit / Kiro `- [ ] T1: ...` task list one task at a time, checking off each PASS |
270
274
  | In-loop model escalation | `ur escalate plan\|run\|oracle\|policy` + `.ur/escalation.json` | capability-aware fast/oracle tiers from `model-doctor`; routine work runs fast and auto-escalates hard/failed work to the strong model; `oracle` is a one-shot second opinion |
271
275
  | Best-of-N judging | `ur arena "<task>" [--agents N] [--apply]` | runs N agents on one task in isolated worktrees, scores diffs with the self-review gate + verdict/diff heuristics, surfaces (optionally applies) the winner |
272
- | Self-healing CI loop | `ur ci-loop [--command ...] [--commit] [--push]` | run -> on failure summarize -> fix agent -> re-run, bounded by retries; commits/pushes are self-review gated; `--from-log` seeds the first failure |
276
+ | Self-healing CI loop | `ur ci-loop [--command ...] [--cwd ...] [--commit] [--push]` | run -> on failure summarize -> fix agent -> re-run, bounded by retries; no-test failures stop immediately with cwd guidance; commits/pushes are self-review gated; `--from-log` seeds the first failure |
273
277
  | Verifiable artifacts | `ur artifacts add\|capture-diff\|capture-tests\|approve\|reject` + `.ur/artifacts/` | reviewable deliverables with pending/approved/rejected status and threaded feedback; threads into the provenance stack (`claim-ledger`, `trace`, `evidence`) |
274
278
 
275
279
  ### Commands
@@ -58,6 +58,10 @@ ur provider select-model <provider> <model> --json
58
58
  ur config set base_url <url>
59
59
  ```
60
60
 
61
+ `provider.fallback` is diagnostic recovery metadata, not automatic routing.
62
+ When the active provider fails, `ur provider doctor` shows the configured
63
+ recovery command; changing providers remains an explicit user action.
64
+
61
65
  Provider values accept canonical IDs and common aliases. Examples:
62
66
  `openai-api`, `anthropic-api`, `gemini-api`, `openrouter`, `ollama`,
63
67
  `lmstudio`, `LM Studio`, `llama.cpp`, `vllm`, and the subscription CLI
@@ -88,6 +92,7 @@ it in the environment when you explicitly choose API mode:
88
92
 
89
93
  ```sh
90
94
  OPENAI_API_KEY=...
95
+ OPENAI_COMPATIBLE_API_KEY=...
91
96
  ANTHROPIC_API_KEY=...
92
97
  GEMINI_API_KEY=...
93
98
  OPENROUTER_API_KEY=...
@@ -117,8 +122,8 @@ ur --settings '{"ollama":{"host":"http://192.168.1.50:11434"}}'
117
122
  Model selection environment variables still work the same way:
118
123
 
119
124
  ```sh
120
- OLLAMA_MODEL=qwen3-coder:480b-cloud
121
- UR_MODEL=qwen3-coder:480b-cloud
125
+ OLLAMA_MODEL=qwen2.5-coder:7b
126
+ UR_MODEL=qwen2.5-coder:7b
122
127
  ```
123
128
 
124
129
  `OLLAMA_MODEL` selects the model name and takes precedence over `UR_MODEL` only
package/docs/IDE.md CHANGED
@@ -4,8 +4,7 @@ UR's professional IDE integration is the **UR Inline Diffs** VS Code extension,
4
4
  bundled in this repository (`extensions/vscode-ur-inline-diffs`). It gives VS
5
5
  Code, Cursor, and Windsurf a chat panel, inline diff review, an actions panel,
6
6
  an agent status card, a searchable command palette, and an agent options
7
- panel — all driven by the same `ur` CLI you already use from a terminal, with
8
- no separate server process and no new network surface.
7
+ panel — all driven by the same `ur` CLI you already use from a terminal.
9
8
 
10
9
  Other editors connect through different mechanisms, chosen per editor and
11
10
  stated honestly — nothing here claims support that does not exist:
@@ -19,12 +18,12 @@ stated honestly — nothing here claims support that does not exist:
19
18
  Neovim clients. This remains a separate transport from the VS Code
20
19
  extension's chat bridge below; the two are independent and neither depends
21
20
  on the other.
22
- - **Manual** — no auto-config; install a plugin and connect via `/ide`.
23
- **JetBrains is not implemented in this repository.** UR can detect whether
24
- a plugin named `ur-jetbrains-plugin` is installed
25
- (`src/utils/jetbrains.ts`), but this repo does not build, ship, or publish
26
- that plugin. Nothing in this document should be read as "install our
27
- JetBrains plugin today" — there isn't one yet.
21
+ - **JetBrains plugin** — the experimental bundled plugin under
22
+ `extensions/jetbrains-ur` connects to `ur acp serve` on loopback. It uses
23
+ JSON-RPC `initialize`, `session/new`, and `session/prompt` calls at `/acp`,
24
+ preserves a project-scoped session, and performs network work off the IDE UI
25
+ thread. It is installable from its `buildPlugin` zip; marketplace publishing
26
+ is not claimed.
28
27
 
29
28
  ## Supported targets
30
29
 
@@ -34,7 +33,7 @@ stated honestly — nothing here claims support that does not exist:
34
33
  | Cursor | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
35
34
  | Windsurf | UR Inline Diffs extension (VS Code fork) | `.vscode/settings.json` | Same extension as VS Code. |
36
35
  | Zed | stdio ACP | `.zed/settings.json` | Real Agent Client Protocol over stdio. |
37
- | JetBrains | not implemented | none | Detection code exists; no plugin ships from this repo. Use the CLI directly in the meantime. |
36
+ | JetBrains | bundled experimental plugin + HTTP ACP | manual zip install | Project-scoped JSON-RPC session and Send Selection action; synchronous ACP task result, not token streaming. |
38
37
  | Neovim | stdio ACP | snippet | Requires a third-party ACP client plugin. |
39
38
  | Generic ACP | stdio ACP / HTTP | snippet | `ur acp stdio` (native ACP) or `ur acp serve` (HTTP JSON-RPC; not a streaming transport — see Known limitations). |
40
39
 
@@ -128,7 +127,9 @@ rather than leaving it hanging.
128
127
  `.ur/ide/chat/` in the workspace (a manifest plus one file per session).
129
128
  **UR: Open Chat** can resume a previous session — using the CLI's own
130
129
  `--resume` — or start a new one; sessions survive closing and reopening VS
131
- Code.
130
+ Code. In multi-root workspaces the chat binds to the folder containing the
131
+ active editor, and stale callbacks from a canceled turn cannot mutate a newer
132
+ chat.
132
133
 
133
134
  **File and selection context.** **UR: Add Current File to Chat** and
134
135
  **UR: Add Selection to Chat** attach the active file or selection as explicit
@@ -200,8 +201,9 @@ success it did not observe.
200
201
 
201
202
  ## Known limitations
202
203
 
203
- - **JetBrains is not implemented.** No JetBrains plugin ships from this
204
- repository; see "Supported targets" above.
204
+ - **JetBrains integration is experimental and non-streaming.** It uses the
205
+ loopback HTTP ACP server and returns the completed synchronous task output;
206
+ it does not yet render token or tool-call streams.
205
207
  - **The lockfile/MCP IDE bridge is Phase 2 and not part of this MVP.** The
206
208
  CLI already implements the *client* half of a lockfile-discovered MCP
207
209
  connection to a running IDE (`src/utils/ide.ts`), used by the terminal
@@ -186,6 +186,13 @@ ur ci-loop --command "bun test" --max-attempts 3
186
186
  ur test-first detect
187
187
  ```
188
188
 
189
+ If the runner says "No tests found", check the working directory printed by
190
+ the result and run from the test root or pass it explicitly:
191
+
192
+ ```sh
193
+ ur ci-loop --command "bun test" --cwd ./packages/app --max-attempts 3
194
+ ```
195
+
189
196
  ## Integrations
190
197
 
191
198
  ### Plugin fails to load
package/docs/USAGE.md CHANGED
@@ -13,7 +13,7 @@ Use interactive mode for iterative coding, debugging, research, and repository e
13
13
  Useful options:
14
14
 
15
15
  ```sh
16
- ur --model qwen3-coder:480b-cloud
16
+ ur --model qwen2.5-coder:7b
17
17
  ur --add-dir ../other-project
18
18
  ur --permission-mode ask
19
19
  ur --continue
@@ -63,7 +63,7 @@ Ollama router choose from the models exposed by your local Ollama app.
63
63
  You can also choose the model for a single session:
64
64
 
65
65
  ```sh
66
- ur --model qwen3-coder:480b-cloud
66
+ ur --model qwen2.5-coder:7b
67
67
  ur --model qwen2.5-coder:latest
68
68
  ```
69
69
 
@@ -108,6 +108,10 @@ ur config set base_url <url>
108
108
  ur config set provider.fallback ollama
109
109
  ```
110
110
 
111
+ `provider.fallback` only controls the recovery suggestion printed by provider
112
+ diagnostics. UR does not switch or retry across providers automatically; use
113
+ `ur config set provider <id>` after reviewing the failure.
114
+
111
115
  In the interactive app, `/model` chooses a provider first and then a model from
112
116
  that provider only. The saved pair controls the runtime backend for the next
113
117
  agent request. There is no cross-provider fallback: OpenAI API does not fall
@@ -287,7 +287,7 @@ ur escalate run "refactor the cache layer" --force-oracle --dry-run
287
287
  ur test-first --dry-run
288
288
  ur safety check --command "rm -rf build"
289
289
  ur context-pack scan
290
- ur ci-loop --command "bun test" --dry-run
290
+ ur ci-loop --command "bun test" --cwd . --dry-run
291
291
  ur artifacts capture-tests --command "bun test"
292
292
  ```
293
293
 
package/docs/providers.md CHANGED
@@ -36,6 +36,7 @@ multimodal input, external CLI boundary, and sandbox scope:
36
36
  | Claude API | API | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:anthropic` | `ANTHROPIC_API_KEY` |
37
37
  | Gemini API | API | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:gemini` | `GEMINI_API_KEY` |
38
38
  | OpenRouter | API/router | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `api:openrouter` | `OPENROUTER_API_KEY` |
39
+ | OpenAI-compatible | server/API | UR-native | no | yes | yes | endpoint-dependent | UR Bash/File sandbox | `openai-compatible` | optional `OPENAI_COMPATIBLE_API_KEY`; never reuses `OPENAI_API_KEY` |
39
40
  | Ollama | local | UR-native | no | yes | yes | yes* | UR Bash/File sandbox | `ollama` | localhost Ollama runtime |
40
41
  | LM Studio | local/server | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `openai-compatible:lmstudio` | local OpenAI-compatible server |
41
42
  | llama.cpp | local/server | UR-native | no | yes | yes | yes | UR Bash/File sandbox | `openai-compatible:llama.cpp` | local OpenAI-compatible server |
@@ -106,6 +107,10 @@ ur config set base_url <url>
106
107
  ur config set provider.fallback ollama
107
108
  ```
108
109
 
110
+ The fallback setting is a recovery hint for `ur provider doctor`; it does not
111
+ route a failed request to another provider. Review the failure and use
112
+ `ur config set provider <id>` to switch explicitly.
113
+
109
114
  ## Provider-scoped model selection
110
115
 
111
116
  UR-Nexus shows providers first, then only models available for the selected provider. This prevents incompatible model/provider pairs and keeps API-key, local/server, subscription, and external app bridge model lists separate. The generic `subscription` entry has no models unless a real independent subscription runtime is configured; UR does not list fake subscription models.
@@ -185,7 +190,7 @@ After selecting a model, the confirmation shows:
185
190
 
186
191
  → Step 2: Select model
187
192
  llama3 · discovered from Ollama · live
188
- qwen3-coder:480b-cloud · discovered from Ollama · live
193
+ qwen2.5-coder:7b · discovered from Ollama · live
189
194
 
190
195
  → Select: llama3
191
196
 
@@ -401,6 +406,7 @@ API providers require explicit user selection and environment keys:
401
406
 
402
407
  ```sh
403
408
  OPENAI_API_KEY=...
409
+ OPENAI_COMPATIBLE_API_KEY=...
404
410
  ANTHROPIC_API_KEY=...
405
411
  GEMINI_API_KEY=...
406
412
  OPENROUTER_API_KEY=...
@@ -414,6 +420,10 @@ ur config set base_url http://localhost:1234/v1
414
420
  ur config set model local-model-name
415
421
  ```
416
422
 
423
+ `OPENAI_COMPATIBLE_API_KEY` is intentionally separate from
424
+ `OPENAI_API_KEY`; selecting an arbitrary compatible base URL never forwards
425
+ the OpenAI credential to that host.
426
+
417
427
  Local/server providers use their normal endpoints:
418
428
 
419
429
  - Ollama: `http://localhost:11434`
@@ -44,7 +44,7 @@
44
44
  <main id="content" class="content">
45
45
  <header class="topbar">
46
46
  <div>
47
- <p class="eyebrow">Version 1.44.10</p>
47
+ <p class="eyebrow">Version 1.45.3</p>
48
48
  <h1>UR-Nexus Documentation</h1>
49
49
  <p class="lead">A practical, tutorial-style reference for installing, configuring, automating, extending, and operating UR-Nexus.</p>
50
50
  </div>
@@ -0,0 +1,22 @@
1
+ # UR for JetBrains (experimental scaffold)
2
+
3
+ Connects JetBrains IDEs to a running UR-Nexus agent over the local ACP server
4
+ (`ur acp serve --port 9100`). Builds with the IntelliJ Platform Gradle Plugin 2.x (verified: `gradle buildPlugin` produces `build/distributions/jetbrains-ur-<version>.zip` against IDEA 2024.2). Install via Settings → Plugins → ⚙ → Install Plugin from Disk. Mirrors what `extensions/vscode-ur-inline-diffs` does for VS Code.
5
+
6
+ ## What it wires up
7
+
8
+ - A tool window ("UR Agent") that checks the loopback ACP endpoint without
9
+ blocking the IDE event thread.
10
+ - An action (Tools → UR: Send Selection) that posts the current selection as
11
+ a prompt to a project-scoped JSON-RPC session at `/acp`. The current server
12
+ returns a completed synchronous task result; token streaming is not claimed.
13
+
14
+ ## Build
15
+
16
+ ```
17
+ cd extensions/jetbrains-ur
18
+ JAVA_HOME=$(brew --prefix openjdk@17) gradle buildPlugin # → build/distributions/*.zip
19
+ ```
20
+
21
+ The heavy lifting stays in UR's ACP server — the plugin is intentionally a
22
+ thin client, so protocol changes land on the CLI side.
@@ -0,0 +1,34 @@
1
+ plugins {
2
+ id("java")
3
+ id("org.jetbrains.kotlin.jvm") version "2.0.21"
4
+ id("org.jetbrains.intellij.platform") version "2.7.2"
5
+ }
6
+
7
+ group = "dev.urnexus"
8
+ version = "1.45.3"
9
+
10
+ repositories {
11
+ mavenCentral()
12
+ intellijPlatform {
13
+ defaultRepositories()
14
+ }
15
+ }
16
+
17
+ dependencies {
18
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
19
+ intellijPlatform {
20
+ intellijIdeaCommunity("2024.2.3")
21
+ }
22
+ }
23
+
24
+ intellijPlatform {
25
+ pluginConfiguration {
26
+ ideaVersion {
27
+ sinceBuild.set("242")
28
+ }
29
+ }
30
+ }
31
+
32
+ kotlin {
33
+ jvmToolchain(17)
34
+ }
@@ -0,0 +1 @@
1
+ rootProject.name = "jetbrains-ur"
@@ -0,0 +1,28 @@
1
+ package dev.urnexus
2
+
3
+ import com.intellij.openapi.actionSystem.AnAction
4
+ import com.intellij.openapi.actionSystem.AnActionEvent
5
+ import com.intellij.openapi.actionSystem.CommonDataKeys
6
+ import com.intellij.openapi.ui.Messages
7
+ import com.intellij.openapi.progress.ProgressIndicator
8
+ import com.intellij.openapi.progress.ProgressManager
9
+ import com.intellij.openapi.progress.Task
10
+ import com.intellij.openapi.application.ApplicationManager
11
+
12
+ class SendSelectionAction : AnAction() {
13
+ override fun actionPerformed(e: AnActionEvent) {
14
+ val editor = e.getData(CommonDataKeys.EDITOR) ?: return
15
+ val selection = editor.selectionModel.selectedText ?: return
16
+ val project = e.project ?: return
17
+ val client = project.getService(UrAcpClient::class.java)
18
+ ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Sending selection to UR", true) {
19
+ override fun run(indicator: ProgressIndicator) {
20
+ val result = runCatching { client.sendPrompt(selection) }
21
+ ApplicationManager.getApplication().invokeLater {
22
+ result.onSuccess { Messages.showInfoMessage(project, it.take(2000), "UR Agent") }
23
+ .onFailure { Messages.showErrorDialog(project, it.message ?: it.toString(), "UR Agent") }
24
+ }
25
+ }
26
+ })
27
+ }
28
+ }
@@ -0,0 +1,97 @@
1
+ package dev.urnexus
2
+
3
+ import com.intellij.openapi.components.Service
4
+ import com.intellij.openapi.project.Project
5
+ import java.net.URI
6
+ import java.net.http.HttpClient
7
+ import java.net.http.HttpRequest
8
+ import java.net.http.HttpResponse
9
+ import java.time.Duration
10
+ import java.util.concurrent.atomic.AtomicLong
11
+ import kotlinx.serialization.json.Json
12
+ import kotlinx.serialization.json.JsonElement
13
+ import kotlinx.serialization.json.JsonObject
14
+ import kotlinx.serialization.json.JsonPrimitive
15
+ import kotlinx.serialization.json.buildJsonObject
16
+ import kotlinx.serialization.json.jsonObject
17
+ import kotlinx.serialization.json.jsonPrimitive
18
+
19
+ /** Project-scoped JSON-RPC client for UR's HTTP ACP endpoint. */
20
+ @Service(Service.Level.PROJECT)
21
+ class UrAcpClient(private val project: Project) {
22
+ private val baseUrl = "http://127.0.0.1:9100"
23
+ private val http = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build()
24
+ private val ids = AtomicLong()
25
+ private val json = Json { ignoreUnknownKeys = true }
26
+ @Volatile private var initialized = false
27
+ @Volatile private var sessionId: String? = null
28
+
29
+ fun health(): Boolean = try {
30
+ val req = HttpRequest.newBuilder(URI.create("$baseUrl/healthz"))
31
+ .timeout(Duration.ofSeconds(5)).GET().build()
32
+ http.send(req, HttpResponse.BodyHandlers.ofString()).statusCode() == 200
33
+ } catch (_: Exception) { false }
34
+
35
+ @Synchronized
36
+ fun sendPrompt(prompt: String): String {
37
+ require(prompt.isNotBlank()) { "Prompt cannot be empty." }
38
+ ensureSession()
39
+ val result = rpc("session/prompt", buildJsonObject {
40
+ put("sessionId", JsonPrimitive(sessionId!!))
41
+ put("prompt", JsonPrimitive(prompt))
42
+ put("mode", JsonPrimitive("sync"))
43
+ }).jsonObject
44
+ val task = result["task"]?.jsonObject ?: error("ACP response did not include a task.")
45
+ val status = task["status"]?.jsonPrimitive?.content ?: "unknown"
46
+ val output = task["result"]?.jsonObject?.get("stdout")?.jsonPrimitive?.content
47
+ val failure = task["result"]?.jsonObject?.get("stderr")?.jsonPrimitive?.content
48
+ return output?.takeIf { it.isNotBlank() }
49
+ ?: failure?.takeIf { it.isNotBlank() }
50
+ ?: "UR task $status (${task["id"]?.jsonPrimitive?.content ?: "unknown id"})"
51
+ }
52
+
53
+ @Synchronized
54
+ fun closeSession() {
55
+ val active = sessionId ?: return
56
+ try { rpc("session/cancel", buildJsonObject { put("sessionId", JsonPrimitive(active)) }) }
57
+ finally { sessionId = null }
58
+ }
59
+
60
+ private fun ensureSession() {
61
+ if (!initialized) {
62
+ rpc("initialize")
63
+ initialized = true
64
+ }
65
+ if (sessionId == null) {
66
+ val params = project.basePath?.let { cwd ->
67
+ buildJsonObject { put("cwd", JsonPrimitive(cwd)) }
68
+ }
69
+ sessionId = rpc("session/new", params).jsonObject["sessionId"]?.jsonPrimitive?.content
70
+ ?: error("ACP session/new response did not include sessionId.")
71
+ }
72
+ }
73
+
74
+ private fun rpc(method: String, params: JsonObject? = null): JsonElement {
75
+ val requestId = ids.incrementAndGet()
76
+ val body = buildJsonObject {
77
+ put("jsonrpc", JsonPrimitive("2.0"))
78
+ put("id", JsonPrimitive(requestId))
79
+ put("method", JsonPrimitive(method))
80
+ if (params != null) put("params", params)
81
+ }
82
+ val request = HttpRequest.newBuilder(URI.create("$baseUrl/acp"))
83
+ .timeout(Duration.ofMinutes(31))
84
+ .header("content-type", "application/json")
85
+ .POST(HttpRequest.BodyPublishers.ofString(body.toString()))
86
+ .build()
87
+ val response = http.send(request, HttpResponse.BodyHandlers.ofString())
88
+ if (response.statusCode() !in 200..299) {
89
+ error("ACP HTTP ${response.statusCode()}: ${response.body().take(500)}")
90
+ }
91
+ val payload = json.parseToJsonElement(response.body()).jsonObject
92
+ payload["error"]?.jsonObject?.let { rpcError ->
93
+ error("ACP ${rpcError["code"]?.jsonPrimitive?.content}: ${rpcError["message"]?.jsonPrimitive?.content}")
94
+ }
95
+ return payload["result"] ?: error("ACP response did not include a result.")
96
+ }
97
+ }
@@ -0,0 +1,27 @@
1
+ package dev.urnexus
2
+
3
+ import com.intellij.openapi.project.Project
4
+ import com.intellij.openapi.wm.ToolWindow
5
+ import com.intellij.openapi.wm.ToolWindowFactory
6
+ import com.intellij.ui.components.JBScrollPane
7
+ import com.intellij.ui.content.ContentFactory
8
+ import javax.swing.JTextArea
9
+ import com.intellij.openapi.application.ApplicationManager
10
+
11
+ class UrToolWindowFactory : ToolWindowFactory {
12
+ override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
13
+ val client = project.getService(UrAcpClient::class.java)
14
+ val text = JTextArea()
15
+ text.isEditable = false
16
+ text.text = "Checking UR ACP server..."
17
+ val content = ContentFactory.getInstance().createContent(JBScrollPane(text), "", false)
18
+ toolWindow.contentManager.addContent(content)
19
+ ApplicationManager.getApplication().executeOnPooledThread {
20
+ val status = if (client.health())
21
+ "Connected to UR ACP server (127.0.0.1:9100)."
22
+ else
23
+ "UR ACP server not running.\nStart it with: ur acp serve --port 9100"
24
+ ApplicationManager.getApplication().invokeLater { text.text = status }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,17 @@
1
+ <idea-plugin>
2
+ <id>dev.urnexus.jetbrains</id>
3
+ <name>UR Agent</name>
4
+ <vendor url="https://github.com/Maitham16/UR">UR-Nexus</vendor>
5
+ <description>Connects JetBrains IDEs to a local UR-Nexus agent over ACP (ur acp serve).</description>
6
+ <depends>com.intellij.modules.platform</depends>
7
+ <extensions defaultExtensionNs="com.intellij">
8
+ <toolWindow id="UR Agent" anchor="right"
9
+ factoryClass="dev.urnexus.UrToolWindowFactory"/>
10
+ </extensions>
11
+ <actions>
12
+ <action id="UR.SendSelection" class="dev.urnexus.SendSelectionAction"
13
+ text="UR: Send Selection" description="Send the current selection to the UR agent">
14
+ <add-to-group group-id="ToolsMenu" anchor="last"/>
15
+ </action>
16
+ </actions>
17
+ </idea-plugin>