@wenrwa/marketplace-cli 0.7.8 → 0.8.1

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/dist/__tests__/bridge-activity-tail.test.js +16 -0
  2. package/dist/__tests__/bridge-attach-paste-engine.test.d.ts +1 -0
  3. package/dist/__tests__/bridge-attach-paste-engine.test.js +108 -0
  4. package/dist/__tests__/bridge-bundle-installer-opencode.test.d.ts +1 -0
  5. package/dist/__tests__/bridge-bundle-installer-opencode.test.js +69 -0
  6. package/dist/__tests__/bridge-context-engine.test.d.ts +1 -0
  7. package/dist/__tests__/bridge-context-engine.test.js +112 -0
  8. package/dist/__tests__/bridge-context-manifest-frame.test.js +19 -0
  9. package/dist/__tests__/bridge-crew-translator.test.d.ts +1 -0
  10. package/dist/__tests__/bridge-crew-translator.test.js +103 -0
  11. package/dist/__tests__/bridge-install-bundle-frame.test.js +24 -0
  12. package/dist/__tests__/bridge-loopback-opencode-open.test.d.ts +1 -0
  13. package/dist/__tests__/bridge-loopback-opencode-open.test.js +51 -0
  14. package/dist/__tests__/bridge-opencode-launch.test.d.ts +1 -0
  15. package/dist/__tests__/bridge-opencode-launch.test.js +179 -0
  16. package/dist/__tests__/bridge-relay-pucc14.test.js +28 -0
  17. package/dist/__tests__/bridge-server.test.js +32 -0
  18. package/dist/__tests__/opencode-config-parity.test.d.ts +1 -0
  19. package/dist/__tests__/opencode-config-parity.test.js +85 -0
  20. package/dist/__tests__/opencode-plugin.test.d.ts +1 -0
  21. package/dist/__tests__/opencode-plugin.test.js +197 -0
  22. package/dist/__tests__/opencode-title.test.d.ts +1 -0
  23. package/dist/__tests__/opencode-title.test.js +99 -0
  24. package/dist/__tests__/opencode-transcript-reader.test.d.ts +1 -0
  25. package/dist/__tests__/opencode-transcript-reader.test.js +156 -0
  26. package/dist/bridge/activityTail.d.ts +7 -0
  27. package/dist/bridge/activityTail.js +1 -0
  28. package/dist/bridge/agentLaunch.d.ts +28 -0
  29. package/dist/bridge/agentLaunch.js +49 -0
  30. package/dist/bridge/attachmentWriter.d.ts +16 -0
  31. package/dist/bridge/attachmentWriter.js +24 -1
  32. package/dist/bridge/bundleInstaller.d.ts +3 -0
  33. package/dist/bridge/bundleInstaller.js +22 -8
  34. package/dist/bridge/claudeTranscriptReader.d.ts +7 -1
  35. package/dist/bridge/claudeTranscriptReader.js +12 -2
  36. package/dist/bridge/contextFile.d.ts +20 -0
  37. package/dist/bridge/contextFile.js +30 -0
  38. package/dist/bridge/contextWriter.d.ts +10 -1
  39. package/dist/bridge/contextWriter.js +29 -9
  40. package/dist/bridge/crewTranslator.d.ts +41 -0
  41. package/dist/bridge/crewTranslator.js +129 -0
  42. package/dist/bridge/launchSupervisor.d.ts +4 -0
  43. package/dist/bridge/opencodeConfig.d.ts +41 -0
  44. package/dist/bridge/opencodeConfig.js +107 -0
  45. package/dist/bridge/opencodePlugin.d.ts +13 -0
  46. package/dist/bridge/opencodePlugin.js +54 -0
  47. package/dist/bridge/opencodeTranscriptReader.d.ts +24 -0
  48. package/dist/bridge/opencodeTranscriptReader.js +178 -0
  49. package/dist/bridge/relayClient.d.ts +27 -1
  50. package/dist/bridge/relayClient.js +24 -7
  51. package/dist/bridge/screenshotAttach.d.ts +2 -0
  52. package/dist/bridge/screenshotAttach.js +3 -1
  53. package/dist/bridge/server.d.ts +6 -0
  54. package/dist/bridge/server.js +9 -1
  55. package/dist/bridge/sessionManager.d.ts +5 -0
  56. package/dist/bridge/sessionManager.js +21 -1
  57. package/dist/commands/bridge.js +36 -9
  58. package/hooks/opencode-plugin.mjs +186 -0
  59. package/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ // HiveOp OpenCode activity plugin (oc-06). Self-contained ESM: no imports beyond node:*.
2
+ // Writes the SAME JSONL lines as the Claude Code hooks (hooks/post-tool-use.js, stop.js)
3
+ // to $HIVEOP_ACTIVITY_FILE, which the bridge already tails. Copies of this file live in
4
+ // infra/terminal-image/opencode/ (hosted) and cli/hooks/ (local); a test keeps them identical.
5
+ //
6
+ // Event -> line:
7
+ // tool.execute.before / .after -> kind 'tool' (working, thinking "<Verb> <file>")
8
+ // session.idle, session.status idle -> kind 'stop' (idle, thinking '')
9
+ // permission.asked, question.asked -> kind 'needs_you' (thinking 'Waiting for you')
10
+ // permission.replied -> kind 'tool', tool '' (working, thinking 'Working')
11
+ // session.updated (primary session, real title) -> OSC 0 pane title (oc-17), written to /dev/tty
12
+ // because OpenCode's own title ("OpenCode" / "OC | <title>") is switched off by the launch
13
+ // (OPENCODE_DISABLE_TERMINAL_TITLE=1): the app's auto-title ingestion shows the title verbatim.
14
+ // No-op without HIVEOP_ACTIVITY_FILE and WENRWA_SESSION_ID; nothing ever throws into OpenCode.
15
+ // ONLY plugin functions may be exported: OpenCode loads every export as a plugin, and a helper
16
+ // export breaks every prompt (Plugin.trigger reads hooks off its return value).
17
+ import fs from 'node:fs'
18
+ import path from 'node:path'
19
+
20
+ // OpenCode tool ids -> the Claude Code names the bridge/touch tracker already understand.
21
+ const TOOL_NAME = {
22
+ read: 'Read', edit: 'Edit', write: 'Write', patch: 'MultiEdit', multiedit: 'MultiEdit', bash: 'Bash',
23
+ }
24
+ const FILE_TOOLS = new Set(['Read', 'Edit', 'Write', 'Update', 'Create', 'MultiEdit', 'NotebookEdit'])
25
+ const VERB = {
26
+ Read: 'Reading', Edit: 'Editing', Write: 'Writing', Update: 'Updating',
27
+ Create: 'Creating', MultiEdit: 'Editing', NotebookEdit: 'Editing', Bash: 'Running',
28
+ }
29
+ const MAX_BASH_PATHS = 5
30
+
31
+ // Same algorithm as hooks/post-tool-use.js extractBashPaths (kept in step by a parity test).
32
+ function extractBashPaths(command, pathExists, cwd) {
33
+ if (!command || typeof command !== 'string') return []
34
+ const tokens = command.match(/"[^"]*"|'[^']*'|[^\s;|&()<>]+/g) || []
35
+ const root = (cwd || '').replace(/\/+$/, '')
36
+ const out = []
37
+ const seen = new Set()
38
+ const bare = (s) => s.replace(/^['"]|['"]$/g, '').replace(/[),;]+$/, '')
39
+ for (let i = 1; i < tokens.length; i++) {
40
+ const prev = bare(tokens[i - 1])
41
+ if (prev === 'cd' || prev === 'pushd') continue
42
+ const t = bare(tokens[i])
43
+ if (!t || t.startsWith('-')) continue
44
+ const norm = t.replace(/\/+$/, '')
45
+ if (!norm || seen.has(norm)) continue
46
+ if (norm === '.' || (root && norm === root)) continue
47
+ let ok = false
48
+ try { ok = !!pathExists(t) } catch { ok = false }
49
+ if (!ok) continue
50
+ seen.add(norm)
51
+ out.push(t)
52
+ if (out.length >= MAX_BASH_PATHS) break
53
+ }
54
+ return out
55
+ }
56
+
57
+ const exists = (p) => { try { return fs.existsSync(p) } catch { return false } }
58
+
59
+ /** Build the 'tool' record fields for an OpenCode tool call. */
60
+ function toolRecord(toolId, args) {
61
+ const tool = typeof toolId === 'string' ? (TOOL_NAME[toolId.toLowerCase()] || toolId) : ''
62
+ const a = args && typeof args === 'object' ? args : {}
63
+ const paths = []
64
+ for (const k of ['filePath', 'file_path', 'notebook_path']) if (typeof a[k] === 'string') paths.push(a[k])
65
+ const filePaths = FILE_TOOLS.has(tool)
66
+ ? paths.filter((p, i) => p && paths.indexOf(p) === i)
67
+ : tool === 'Bash' ? extractBashPaths(a.command, exists, process.cwd()) : []
68
+ const base = filePaths.length ? String(filePaths[0]).split('/').pop() : ''
69
+ return { kind: 'tool', tool, filePaths, thinking: (VERB[tool] || 'Working') + (base ? ' ' + base : '') }
70
+ }
71
+
72
+ function append(extra, env) {
73
+ try {
74
+ const file = env.HIVEOP_ACTIVITY_FILE
75
+ const sessionId = env.WENRWA_SESSION_ID || ''
76
+ if (!file || !sessionId) return
77
+ fs.mkdirSync(path.dirname(file), { recursive: true })
78
+ fs.appendFileSync(file, JSON.stringify({ sessionId, workspaceId: env.WENRWA_WORKSPACE_ID || '', ...extra }) + '\n')
79
+ } catch {}
80
+ }
81
+
82
+ // OpenCode's placeholder titles until the title generator runs ("New session - <iso>").
83
+ const DEFAULT_TITLE = /^(New session|Child session) - \d{4}-\d{2}-\d{2}T/
84
+ const MAX_TITLE = 80
85
+
86
+ /** oc-17: the OSC 0 sequence for a session.updated event's info, or '' (sub-agent, placeholder, empty). */
87
+ function titleSequence(info) {
88
+ if (!info || info.parentID || typeof info.title !== 'string') return ''
89
+ const t = info.title.replace(/[\u0000-\u001f\u007f-\u009f]/g, ' ').replace(/\s+/g, ' ').trim()
90
+ if (!t || DEFAULT_TITLE.test(t)) return ''
91
+ return '\x1b]0;' + (t.length > MAX_TITLE ? t.slice(0, MAX_TITLE - 1) + '\u2026' : t) + '\x07'
92
+ }
93
+
94
+ let lastTitle = ''
95
+ function setPaneTitle(info, ttyPath = '/dev/tty') {
96
+ try {
97
+ const seq = titleSequence(info)
98
+ if (!seq || seq === lastTitle) return
99
+ const fd = fs.openSync(ttyPath, 'a')
100
+ try { fs.writeSync(fd, seq) } finally { fs.closeSync(fd) }
101
+ lastTitle = seq
102
+ } catch {}
103
+ }
104
+
105
+ let reportedToGateway = ''
106
+
107
+ /**
108
+ * oc-05 (hosted): POST the primary session's id to $HIVEOP_GATEWAY_URL/hiveop/session, authenticated
109
+ * by the per-agent gateway token (it already identifies the agent). Best-effort: one retry, never
110
+ * throws, no-op without gateway env. Last one wins server-side, so a fresh session after a
111
+ * "Session not found" fallback replaces the dead id.
112
+ */
113
+ async function reportSessionToGateway(id, env = process.env) {
114
+ try {
115
+ const base = String(env.HIVEOP_GATEWAY_URL || '').replace(/\/+$/, '')
116
+ const token = env.HIVEOP_GATEWAY_TOKEN || ''
117
+ if (!id || !base || !token || id === reportedToGateway) return
118
+ reportedToGateway = id // claimed up front so a burst of events posts once; released on failure
119
+ for (let attempt = 0; attempt < 2; attempt++) {
120
+ try {
121
+ const res = await fetch(`${base}/hiveop/session`, {
122
+ method: 'POST',
123
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${token}` },
124
+ body: JSON.stringify({ sessionId: id }),
125
+ signal: AbortSignal.timeout(5000),
126
+ })
127
+ if (res.ok) return
128
+ if (res.status < 500) break
129
+ } catch {}
130
+ await new Promise((r) => setTimeout(r, 1500))
131
+ }
132
+ if (reportedToGateway === id) reportedToGateway = ''
133
+ } catch {}
134
+ }
135
+
136
+ /**
137
+ * oc-05 hook point: capture the generated id of an agent's PRIMARY session so the next launch
138
+ * can `opencode -s <id>`. Sub-agent sessions (info.parentID) never overwrite it.
139
+ * Local lane: first id is written to a file under HIVEOP_OPENCODE_SESSIONS_DIR (keyed by agent).
140
+ * Hosted lane: the id is POSTed to the gateway (see reportSessionToGateway).
141
+ */
142
+ function captureSessionId(event, env = process.env) {
143
+ try {
144
+ const p = (event && event.properties) || {}
145
+ if (!event || event.type !== 'session.created' || (p.info && p.info.parentID)) return
146
+ const id = p.sessionID || (p.info && p.info.id) || ''
147
+ void reportSessionToGateway(id, env)
148
+ const agentId = env.WENRWA_AGENT_ID || ''
149
+ if (!agentId) return
150
+ const dir = env.HIVEOP_OPENCODE_SESSIONS_DIR || '/data/.hiveop/opencode-sessions'
151
+ fs.mkdirSync(dir, { recursive: true })
152
+ const f = path.join(dir, agentId)
153
+ if (!fs.existsSync(f)) fs.writeFileSync(f, id)
154
+ } catch {}
155
+ }
156
+
157
+ export const HiveOp = async () => {
158
+ const env = process.env
159
+ let lastKind = ''
160
+ const emit = (extra) => {
161
+ // session.status idle and session.idle both fire per turn end: write one stop line.
162
+ if (extra.kind === 'stop' && lastKind === 'stop') return
163
+ lastKind = extra.kind
164
+ append({ ...extra, ...(extra.kind === 'stop' ? { thinking: '' } : {}) }, env)
165
+ }
166
+ return {
167
+ event: async ({ event }) => {
168
+ try {
169
+ if (!event) return
170
+ const p = event.properties || {}
171
+ if (event.type === 'session.created') captureSessionId(event, env)
172
+ else if (event.type === 'session.updated') setPaneTitle(p.info, env.HIVEOP_TITLE_TTY || '/dev/tty')
173
+ else if (event.type === 'session.idle') emit({ kind: 'stop' })
174
+ else if (event.type === 'session.status' && p.status && p.status.type === 'idle') emit({ kind: 'stop' })
175
+ else if (event.type === 'permission.asked' || event.type === 'question.asked') emit({ kind: 'needs_you', thinking: 'Waiting for you' })
176
+ else if (event.type === 'permission.replied') emit({ kind: 'tool', tool: '', filePaths: [], thinking: 'Working' })
177
+ } catch {}
178
+ },
179
+ 'tool.execute.before': async (input, output) => {
180
+ try { emit(toolRecord(input && input.tool, (output && output.args) || (input && input.args))) } catch {}
181
+ },
182
+ 'tool.execute.after': async (input, output) => {
183
+ try { emit(toolRecord(input && input.tool, (input && input.args) || (output && output.args))) } catch {}
184
+ },
185
+ }
186
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenrwa/marketplace-cli",
3
- "version": "0.7.8",
3
+ "version": "0.8.1",
4
4
  "description": "CLI tool for the Wenrwa Agent Marketplace — monitor bounties, agents, workspaces, and treasury from your terminal",
5
5
  "author": "Wenrwa <dev@wenrwa.com>",
6
6
  "license": "MIT",