@wenrwa/marketplace-cli 0.8.4 → 0.8.6

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 (42) hide show
  1. package/dist/__tests__/bridge-changes-report.test.js +25 -0
  2. package/dist/__tests__/bridge-changes-reporter.test.js +98 -0
  3. package/dist/__tests__/bridge-read-transcript-frame.test.js +22 -0
  4. package/dist/__tests__/bridge-save-env.test.d.ts +1 -0
  5. package/dist/__tests__/bridge-save-env.test.js +343 -0
  6. package/dist/__tests__/bridge-server-phantom-takeover.test.d.ts +1 -0
  7. package/dist/__tests__/bridge-server-phantom-takeover.test.js +84 -0
  8. package/dist/__tests__/bridge-server.test.js +9 -11
  9. package/dist/__tests__/secrets-egress.test.d.ts +1 -0
  10. package/dist/__tests__/secrets-egress.test.js +237 -0
  11. package/dist/__tests__/secrets-redact.test.d.ts +1 -0
  12. package/dist/__tests__/secrets-redact.test.js +318 -0
  13. package/dist/__tests__/term-snapshot.test.js +136 -0
  14. package/dist/bridge/changesReport.js +10 -3
  15. package/dist/bridge/changesReporter.js +0 -0
  16. package/dist/bridge/contextWriter.js +5 -1
  17. package/dist/bridge/envSaver.d.ts +63 -0
  18. package/dist/bridge/envSaver.js +256 -0
  19. package/dist/bridge/hiveopSettings.js +5 -0
  20. package/dist/bridge/launchSupervisor.js +4 -1
  21. package/dist/bridge/localContextWriter.js +6 -2
  22. package/dist/bridge/paneClient.d.ts +32 -0
  23. package/dist/bridge/paneClient.js +30 -0
  24. package/dist/bridge/realPty.js +5 -1
  25. package/dist/bridge/relayClient.d.ts +0 -18
  26. package/dist/bridge/relayClient.js +21 -6
  27. package/dist/bridge/repoEnv.d.ts +34 -0
  28. package/dist/bridge/repoEnv.js +186 -0
  29. package/dist/bridge/server.js +69 -26
  30. package/dist/bridge/sessionManager.d.ts +4 -0
  31. package/dist/bridge/sessionManager.js +28 -8
  32. package/dist/bridge/termSnapshot.js +114 -2
  33. package/dist/bridge/workGraphPointer.d.ts +25 -0
  34. package/dist/bridge/workGraphPointer.js +36 -1
  35. package/dist/commands/bridge.js +4 -0
  36. package/dist/secrets/console.d.ts +4 -0
  37. package/dist/secrets/console.js +34 -0
  38. package/dist/secrets/redact.d.ts +79 -0
  39. package/dist/secrets/redact.js +637 -0
  40. package/hooks/opencode-plugin.mjs +55 -2
  41. package/hooks/pre-tool-use.js +96 -0
  42. package/package.json +1 -1
@@ -11,11 +11,16 @@
11
11
  // session.updated (primary session, real title) -> OSC 0 pane title (oc-17), written to /dev/tty
12
12
  // because OpenCode's own title ("OpenCode" / "OC | <title>") is switched off by the launch
13
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.
14
+ // No-op without HIVEOP_ACTIVITY_FILE and WENRWA_SESSION_ID; nothing ever throws into OpenCode,
15
+ // with ONE deliberate exception: the secret guard (2026-10-17) throws from tool.execute.before
16
+ // to block a bash command carrying a literal secret (OpenCode's documented way to block a tool).
17
+ // It uses the CLI's own detector (dist/secrets/redact.js) and fails open when it cannot load it.
15
18
  // ONLY plugin functions may be exported: OpenCode loads every export as a plugin, and a helper
16
19
  // export breaks every prompt (Plugin.trigger reads hooks off its return value).
17
20
  import fs from 'node:fs'
18
21
  import path from 'node:path'
22
+ import { createRequire } from 'node:module'
23
+ import { fileURLToPath } from 'node:url'
19
24
 
20
25
  // OpenCode tool ids -> the Claude Code names the bridge/touch tracker already understand.
21
26
  const TOOL_NAME = {
@@ -154,6 +159,52 @@ function captureSessionId(event, env = process.env) {
154
159
  } catch {}
155
160
  }
156
161
 
162
+ // Secret guard: the same words the Claude Code PreToolUse hook (hooks/pre-tool-use.js) uses.
163
+ const SECRET_MESSAGE = [
164
+ 'HiveOp blocked this command: it contains a literal secret (token, key or password).',
165
+ 'Never put secret values in commands, and never echo, print or cat them.',
166
+ 'Put the value in an environment variable or the workspace .env (ask the user to add it, or use the write tool),',
167
+ 'then reference it by name, e.g. RAILWAY_TOKEN="$RAILWAY_TOKEN" railway up - and re-run.',
168
+ ].join('\n')
169
+
170
+ let secretCheck // undefined: not loaded yet; null: unavailable (fail open)
171
+ function loadSecretCheck(env) {
172
+ if (secretCheck !== undefined) return secretCheck
173
+ secretCheck = null
174
+ try {
175
+ const req = createRequire(import.meta.url)
176
+ const here = path.dirname(fileURLToPath(import.meta.url))
177
+ const prefix = env.npm_config_prefix || '/usr/local'
178
+ // HIVEOP_SECRETS_MODULE, when set, is the ONLY place looked (tests; a pinned install).
179
+ const candidates = env.HIVEOP_SECRETS_MODULE ? [env.HIVEOP_SECRETS_MODULE] : [
180
+ path.join(here, '..', 'dist', 'secrets', 'redact.js'),
181
+ path.join(prefix, 'lib', 'node_modules', '@wenrwa', 'marketplace-cli', 'dist', 'secrets', 'redact.js'),
182
+ '/usr/local/lib/node_modules/@wenrwa/marketplace-cli/dist/secrets/redact.js',
183
+ ]
184
+ for (const p of candidates) {
185
+ if (!p) continue
186
+ try {
187
+ if (!fs.existsSync(p)) continue
188
+ const m = req(p)
189
+ if (m && typeof m.containsSecret === 'function') { secretCheck = m.containsSecret; break }
190
+ } catch {}
191
+ }
192
+ } catch {}
193
+ return secretCheck
194
+ }
195
+
196
+ /** True when a bash tool call carries a literal secret. Never throws. */
197
+ function carriesSecret(toolId, args, env) {
198
+ try {
199
+ if (env.HIVEOP_SECRET_GUARD === '0') return false
200
+ if (String(toolId || '').toLowerCase() !== 'bash') return false
201
+ const cmd = args && typeof args.command === 'string' ? args.command : ''
202
+ if (!cmd) return false
203
+ const check = loadSecretCheck(env)
204
+ return !!(check && check(cmd))
205
+ } catch { return false }
206
+ }
207
+
157
208
  export const HiveOp = async () => {
158
209
  const env = process.env
159
210
  let lastKind = ''
@@ -177,7 +228,9 @@ export const HiveOp = async () => {
177
228
  } catch {}
178
229
  },
179
230
  'tool.execute.before': async (input, output) => {
180
- try { emit(toolRecord(input && input.tool, (output && output.args) || (input && input.args))) } catch {}
231
+ const args = (output && output.args) || (input && input.args)
232
+ try { emit(toolRecord(input && input.tool, args)) } catch {}
233
+ if (carriesSecret(input && input.tool, args, env)) throw new Error(SECRET_MESSAGE)
181
234
  },
182
235
  'tool.execute.after': async (input, output) => {
183
236
  try { emit(toolRecord(input && input.tool, (input && input.args) || (output && output.args))) } catch {}
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * HiveOp PreToolUse hook (Bash) — secret guard, BOTH lanes (2026-10-17).
4
+ *
5
+ * Blocks a Bash command that carries a literal secret (a token, a password, a
6
+ * NAME=value with a secret-ish name …) and tells the agent to reference it
7
+ * from the environment instead. A command that only names `$VARS` passes.
8
+ *
9
+ * The detector is the CLI's own (dist/secrets/redact.js — the same code that
10
+ * masks terminal output), so the guard and the mask agree on what a secret
11
+ * is. This file is byte-identical in cli/hooks/ (local lane, loaded via the
12
+ * bridge's --settings file) and infra/terminal-image/hooks/ (hosted lane,
13
+ * registered by entrypoint.sh); a test keeps the copies identical.
14
+ *
15
+ * FAIL OPEN: no detector, unreadable input, any error, or a slow stdin →
16
+ * exit 0 and the command runs. Output masking is the safety net behind this.
17
+ * Opt out per user with HIVEOP_SECRET_GUARD=0.
18
+ */
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const MESSAGE = [
23
+ 'HiveOp blocked this command: it contains a literal secret (token, key or password).',
24
+ 'Never put secret values in commands, and never echo, print or cat them.',
25
+ 'Put the value in an environment variable or the workspace .env (ask the user to add it, or use the Write tool),',
26
+ 'then reference it by name, e.g. RAILWAY_TOKEN="$RAILWAY_TOKEN" railway up — and re-run.',
27
+ ].join('\n');
28
+
29
+ /** Where the CLI's compiled detector lives, nearest first. HIVEOP_SECRETS_MODULE,
30
+ * when set, is the ONLY place looked (tests; a pinned install). */
31
+ function detectorCandidates(env) {
32
+ if (env.HIVEOP_SECRETS_MODULE) return [env.HIVEOP_SECRETS_MODULE];
33
+ const prefix = env.npm_config_prefix || '/usr/local';
34
+ return [
35
+ path.join(__dirname, '..', 'dist', 'secrets', 'redact.js'),
36
+ path.join(prefix, 'lib', 'node_modules', '@wenrwa', 'marketplace-cli', 'dist', 'secrets', 'redact.js'),
37
+ '/usr/local/lib/node_modules/@wenrwa/marketplace-cli/dist/secrets/redact.js',
38
+ '/usr/lib/node_modules/@wenrwa/marketplace-cli/dist/secrets/redact.js',
39
+ ];
40
+ }
41
+
42
+ function loadDetector(env) {
43
+ for (const p of detectorCandidates(env)) {
44
+ if (!p) continue;
45
+ try {
46
+ if (!fs.existsSync(p)) continue;
47
+ const mod = require(p);
48
+ if (mod && typeof mod.containsSecret === 'function') return mod.containsSecret;
49
+ } catch { /* try the next */ }
50
+ }
51
+ return null;
52
+ }
53
+
54
+ /**
55
+ * Pure decision: the hook event in, `{ block, message }` out. Never throws.
56
+ * `containsSecret` is injected so tests can pass the TypeScript detector.
57
+ */
58
+ function decide(ev, containsSecret, env) {
59
+ try {
60
+ if (!ev || typeof ev !== 'object') return { block: false };
61
+ if ((env && env.HIVEOP_SECRET_GUARD) === '0') return { block: false };
62
+ if (ev.tool_name !== 'Bash') return { block: false };
63
+ const command = ev.tool_input && typeof ev.tool_input.command === 'string' ? ev.tool_input.command : '';
64
+ if (!command || typeof containsSecret !== 'function') return { block: false };
65
+ return containsSecret(command) ? { block: true, message: MESSAGE } : { block: false };
66
+ } catch {
67
+ return { block: false };
68
+ }
69
+ }
70
+
71
+ function main() {
72
+ let input = '';
73
+ // A hook must never hang a tool call: give stdin 2s, then let it run.
74
+ const guard = setTimeout(() => process.exit(0), 2000);
75
+ process.stdin.on('data', (d) => { input += d; });
76
+ process.stdin.on('error', () => process.exit(0));
77
+ process.stdin.on('end', () => {
78
+ clearTimeout(guard);
79
+ try {
80
+ const ev = JSON.parse(input || '{}');
81
+ const verdict = decide(ev, loadDetector(process.env), process.env);
82
+ if (verdict.block) {
83
+ // Exit 2 + stderr: Claude Code blocks the call and shows the agent
84
+ // this message. It never repeats the command (or the secret).
85
+ process.stderr.write(verdict.message + '\n');
86
+ process.exit(2);
87
+ return;
88
+ }
89
+ } catch { /* fail open */ }
90
+ process.exit(0);
91
+ });
92
+ }
93
+
94
+ if (require.main === module) main();
95
+
96
+ module.exports = { decide, detectorCandidates, MESSAGE };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenrwa/marketplace-cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
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",