ur-agent 1.12.2 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.13.0
4
+
5
+ ### Fixed
6
+ - **Image paste hint.** When the clipboard holds no image, the image paste
7
+ shortcut (`ctrl+v`, `alt+v` on Windows) no longer shows a circular "use
8
+ ctrl+v to paste images" message — the very key that was just pressed. It now
9
+ tells you to copy an image (e.g. a screenshot) first, then press the shortcut
10
+ to paste it. SSH sessions keep the existing `scp` hint.
11
+
12
+ ## 1.12.3
13
+
14
+ ### Added
15
+ - **Agent feature expansion commands.** Added `ur agent-features`,
16
+ `ur agent-templates`, `ur automation`, `ur agent-task`, `ur model-doctor`,
17
+ `ur semantic-memory`, `ur claim-ledger`, and `ur browser-qa` so the agent
18
+ platform roadmap is visible and executable from both CLI and slash command
19
+ surfaces.
20
+ - **Opt-in A2A task server.** `ur a2a serve` now exposes loopback Agent Card,
21
+ health, and dry-run task endpoints from the launcher, with off-loopback binds
22
+ requiring a bearer token.
23
+ - **Project scaffolds and examples.** `ur agent-features init` creates reusable
24
+ project assets for agents, automations, GitHub workflow entrypoints, A2A,
25
+ memory, provenance, and browser QA, with `examples/agent_features.md`
26
+ documenting the workflow.
27
+
28
+ ### Fixed
29
+ - **Agent template typo safety.** `ur agent-templates install <name>` now
30
+ rejects unknown template names instead of interpreting a misspelling as
31
+ "install all templates."
32
+ - **Ollama model inspection request.** `ur model-doctor` now uses the preferred
33
+ `/api/show` request body key (`model`) when inspecting local Ollama models.
34
+
35
+ ### Verified
36
+ - Added focused Bun tests for feature scaffolds, template installation,
37
+ automations, PR dry-run generation, local memory/provenance/browser QA
38
+ commands, and the model-doctor Ollama request body.
39
+
3
40
  ## 1.12.2
4
41
 
5
42
  ### Changed
package/README.md CHANGED
@@ -104,9 +104,10 @@ ur plugin --help
104
104
  - [Usage Guide](docs/USAGE.md)
105
105
  - [Configuration](docs/CONFIGURATION.md)
106
106
  - [Agent Trend Coverage](docs/AGENT_TRENDS.md)
107
+ - [Agent Feature Expansion](docs/AGENT_FEATURES.md)
107
108
  - [Development Guide](docs/DEVELOPMENT.md)
108
109
 
109
- The `examples/` directory also contains prompt examples for coding, research, browser, image, video, MCP, memory, and agent-trend workflows.
110
+ The `examples/` directory also contains prompt examples for coding, research, browser, image, video, MCP, memory, agent-trend, and agent-feature workflows.
110
111
 
111
112
  ## License And Responsibility
112
113
 
package/bin/ur.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process'
3
3
  import { existsSync, readFileSync } from 'node:fs'
4
+ import { createServer } from 'node:http'
4
5
  import { dirname, resolve } from 'node:path'
5
6
  import { fileURLToPath } from 'node:url'
6
7
 
@@ -38,6 +39,196 @@ const bun = process.env.BUN_BIN || process.env.BUN_EXECUTABLE || 'bun'
38
39
  const ollamaModel =
39
40
  process.env.OLLAMA_MODEL || process.env.UR_MODEL
40
41
  const userArgs = process.argv.slice(2)
42
+
43
+ function argValue(flag, fallback) {
44
+ const index = userArgs.indexOf(flag)
45
+ return index === -1 ? fallback : (userArgs[index + 1] ?? fallback)
46
+ }
47
+
48
+ function isLoopback(host) {
49
+ return host === '127.0.0.1' || host === 'localhost' || host === '::1'
50
+ }
51
+
52
+ function sendJson(res, status, body) {
53
+ res.writeHead(status, { 'content-type': 'application/json' })
54
+ res.end(JSON.stringify(body, null, 2))
55
+ }
56
+
57
+ function readJson(req) {
58
+ return new Promise(resolve => {
59
+ const chunks = []
60
+ req.on('data', chunk => chunks.push(Buffer.from(chunk)))
61
+ req.on('end', () => {
62
+ try {
63
+ resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}'))
64
+ } catch {
65
+ resolve(null)
66
+ }
67
+ })
68
+ req.on('error', () => resolve(null))
69
+ })
70
+ }
71
+
72
+ function buildAgentCard(baseUrl) {
73
+ return {
74
+ protocolVersion: '0.3.0',
75
+ name: 'UR Agent',
76
+ description:
77
+ 'Local-first terminal coding agent powered through the local Ollama app, with MCP tools, custom agents, browser workflows, memory, verifier gates, and permission controls.',
78
+ url: `${baseUrl}/a2a`,
79
+ version,
80
+ documentationUrl:
81
+ 'https://github.com/Maitham16/UR-mapek/blob/master/docs/AGENT_TRENDS.md',
82
+ capabilities: {
83
+ streaming: true,
84
+ pushNotifications: false,
85
+ stateTransitionHistory: true,
86
+ },
87
+ defaultInputModes: ['text/plain', 'text/markdown', 'application/json'],
88
+ defaultOutputModes: ['text/plain', 'text/markdown', 'application/json'],
89
+ provider: {
90
+ organization: 'Maitham Al-rubaye',
91
+ url: 'https://github.com/Maitham16/UR-mapek',
92
+ },
93
+ skills: [
94
+ {
95
+ id: 'coding-agent',
96
+ name: 'Coding Agent',
97
+ description:
98
+ 'Read, edit, test, verify, and explain code inside a local workspace with permission controls.',
99
+ tags: ['coding', 'terminal', 'verification'],
100
+ examples: [
101
+ 'Fix this failing test and run the relevant checks.',
102
+ 'Review the current diff for behavioral regressions.',
103
+ ],
104
+ inputModes: ['text/plain', 'text/markdown'],
105
+ outputModes: ['text/plain', 'text/markdown'],
106
+ },
107
+ ],
108
+ }
109
+ }
110
+
111
+ function runAgentPrompt(prompt) {
112
+ const childArgs = existsSync(bundledEntrypoint)
113
+ ? [bundledEntrypoint, '-p', '--output-format', 'json', prompt]
114
+ : [
115
+ 'run',
116
+ '--preload',
117
+ preload,
118
+ '--define',
119
+ defineMacro('MACRO.VERSION', version),
120
+ '--define',
121
+ defineMacro('MACRO.BUILD_TIME', ''),
122
+ '--define',
123
+ defineMacro('MACRO.PACKAGE_URL', packageName),
124
+ '--define',
125
+ defineMacro('MACRO.NATIVE_PACKAGE_URL', undefined),
126
+ '--define',
127
+ defineMacro('MACRO.FEEDBACK_CHANNEL', issuesUrl),
128
+ '--define',
129
+ defineMacro('MACRO.ISSUES_EXPLAINER', `file an issue at ${issuesUrl}`),
130
+ '--define',
131
+ defineMacro('MACRO.VERSION_CHANGELOG', ''),
132
+ entrypoint,
133
+ '-p',
134
+ '--output-format',
135
+ 'json',
136
+ prompt,
137
+ ]
138
+
139
+ return new Promise(resolve => {
140
+ const child = spawn(bun, childArgs, {
141
+ cwd: process.cwd(),
142
+ env: {
143
+ ...process.env,
144
+ ...(ollamaModel ? { OLLAMA_MODEL: ollamaModel } : {}),
145
+ },
146
+ stdio: ['ignore', 'pipe', 'pipe'],
147
+ })
148
+ const stdout = []
149
+ const stderr = []
150
+ child.stdout.on('data', chunk => stdout.push(Buffer.from(chunk)))
151
+ child.stderr.on('data', chunk => stderr.push(Buffer.from(chunk)))
152
+ child.on('error', error => {
153
+ resolve({ code: 1, stdout: '', stderr: error.message })
154
+ })
155
+ child.on('exit', code => {
156
+ resolve({
157
+ code: code ?? 1,
158
+ stdout: Buffer.concat(stdout).toString('utf8'),
159
+ stderr: Buffer.concat(stderr).toString('utf8'),
160
+ })
161
+ })
162
+ })
163
+ }
164
+
165
+ function runA2AServer() {
166
+ const host = argValue('--host', '127.0.0.1')
167
+ const port = Number(argValue('--port', '8765'))
168
+ const token = argValue('--token')
169
+ const dryRun = userArgs.includes('--dry-run')
170
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
171
+ console.error(`Invalid --port value: ${argValue('--port')}`)
172
+ process.exit(1)
173
+ }
174
+ if (!isLoopback(host) && !token) {
175
+ console.error('Refusing to bind a2a server off-loopback without --token')
176
+ process.exit(1)
177
+ }
178
+
179
+ const baseUrl = `http://${host}:${port}`
180
+ const server = createServer(async (req, res) => {
181
+ const url = new URL(req.url ?? '/', baseUrl)
182
+ if (req.method === 'GET' && url.pathname === '/healthz') {
183
+ sendJson(res, 200, { ok: true })
184
+ return
185
+ }
186
+ if (
187
+ req.method === 'GET' &&
188
+ (url.pathname === '/.well-known/agent-card.json' ||
189
+ url.pathname === '/agent-card.json')
190
+ ) {
191
+ sendJson(res, 200, buildAgentCard(baseUrl))
192
+ return
193
+ }
194
+ if (req.method === 'POST' && url.pathname === '/a2a/tasks') {
195
+ if (token && req.headers.authorization !== `Bearer ${token}`) {
196
+ sendJson(res, 401, { error: 'unauthorized' })
197
+ return
198
+ }
199
+ const body = await readJson(req)
200
+ const prompt =
201
+ body && typeof body.prompt === 'string' ? body.prompt.trim() : ''
202
+ if (!prompt) {
203
+ sendJson(res, 400, { error: 'missing prompt' })
204
+ return
205
+ }
206
+ const command = [bun, bundledEntrypoint, '-p', '--output-format', 'json', prompt]
207
+ if (dryRun) {
208
+ sendJson(res, 200, { dryRun: true, command })
209
+ return
210
+ }
211
+ const result = await runAgentPrompt(prompt)
212
+ sendJson(res, result.code === 0 ? 200 : 500, result)
213
+ return
214
+ }
215
+ sendJson(res, 404, { error: 'not found' })
216
+ })
217
+ server.listen(port, host, () => {
218
+ const actual = server.address()
219
+ const actualPort = actual && typeof actual === 'object' ? actual.port : port
220
+ console.log(`A2A server listening on http://${host}:${actualPort}`)
221
+ })
222
+ }
223
+
224
+ if (
225
+ userArgs[0] === 'a2a' &&
226
+ userArgs[1] === 'serve' &&
227
+ !userArgs.includes('--help') &&
228
+ !userArgs.includes('-h')
229
+ ) {
230
+ runA2AServer()
231
+ } else {
41
232
  const args =
42
233
  existsSync(bundledEntrypoint)
43
234
  ? [bundledEntrypoint, ...userArgs]
@@ -92,3 +283,4 @@ child.on('exit', (code, signal) => {
92
283
 
93
284
  process.exit(code ?? 1)
94
285
  })
286
+ }