@claude-flow/cli 3.39.3 → 3.41.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.
@@ -0,0 +1,96 @@
1
+ // ADR-125 precedence: the tool arg `gatewayUrl` (fed by `ruflo federation --gateway`)
2
+ // takes precedence over the RUFLO_X_GATEWAY_URL env var, which precedes the default.
3
+ const GATEWAY = (override) => ((typeof override === 'string' && override) || process.env.RUFLO_X_GATEWAY_URL || 'https://x.ruv.io').replace(/\/$/, '');
4
+ const gatewayArg = { gatewayUrl: { type: 'string', description: 'Gateway base URL; takes precedence over RUFLO_X_GATEWAY_URL (default https://x.ruv.io).' } };
5
+ const TIMEOUT_MS = 25_000;
6
+ /** Minimal MCP-over-Streamable-HTTP client: POST JSON-RPC, parse the SSE `data:` frame. */
7
+ async function gatewayRpc(method, params, gatewayUrl) {
8
+ const res = await fetch(`${GATEWAY(gatewayUrl)}/mcp`, {
9
+ method: 'POST',
10
+ headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream' },
11
+ body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params }),
12
+ signal: AbortSignal.timeout(TIMEOUT_MS),
13
+ });
14
+ const text = await res.text();
15
+ const line = text.split('\n').find((l) => l.startsWith('data:'));
16
+ const payload = JSON.parse(line ? line.slice(5) : text);
17
+ if (payload.error)
18
+ throw new Error(`x.ruv.io: ${payload.error.message ?? 'rpc error'}`);
19
+ return payload.result;
20
+ }
21
+ async function gatewayTool(name, args) {
22
+ const { gatewayUrl, ...rest } = args;
23
+ const r = (await gatewayRpc('tools/call', { name, arguments: rest }, gatewayUrl));
24
+ const text = r.content?.[0]?.text ?? '{}';
25
+ const parsed = JSON.parse(text);
26
+ if (r.isError || parsed.error)
27
+ throw new Error(String(parsed.error ?? 'gateway tool error'));
28
+ return parsed;
29
+ }
30
+ async function gatewayResource(uri, gatewayUrl) {
31
+ const r = (await gatewayRpc('resources/read', { uri }, gatewayUrl));
32
+ return JSON.parse(r.contents?.[0]?.text ?? '{}');
33
+ }
34
+ // Credential: intentionally env-only (a secret must never be a CLI flag — it would land in
35
+ // shell history / process lists). Registered in scripts/audit-env-var-precedence.mjs.
36
+ const adminToken = () => process.env.RUFLO_X_ADMIN_TOKEN;
37
+ export const xFederationTools = [
38
+ {
39
+ name: 'x_federation_sync',
40
+ description: 'Fetch recent signature-verified coordination messages from the open x.ruv.io swarm federation (Nostr, #t=ruflo-swarm). Use when you need to see what other ruflo nodes across the internet have posted (PeerHello/Status/Task/Result/Claim*). Reading the relay directly is wrong because you would have to do NIP-42 auth yourself; the gateway does it and only returns events whose signatures verify.',
41
+ inputSchema: { type: 'object', properties: { ...gatewayArg, sinceSeconds: { type: 'number', description: 'Look-back window (default 3600).' }, limit: { type: 'number', description: 'Max messages (default 100).' }, type: { type: 'string', description: 'Optional message type filter, e.g. Task.' } } },
42
+ handler: async (input) => gatewayTool('federation_sync', input),
43
+ },
44
+ {
45
+ name: 'x_federation_roster',
46
+ description: 'List nodes currently announcing themselves on the open swarm (recent PeerHello events) via the ruv://swarm/roster resource. Use when you need to know who is online across the federation before assigning work. Grepping sync output by hand is wrong because the roster resource already de-duplicates by pubkey and carries lastSeen.',
47
+ inputSchema: { type: 'object', properties: { ...gatewayArg } },
48
+ handler: async (input) => gatewayResource('ruv://swarm/roster', input.gatewayUrl),
49
+ },
50
+ {
51
+ name: 'x_federation_claims',
52
+ description: 'Return the current owner-per-resource work-claims ledger for the open swarm (ruv://claims/board). Use when you are about to start shared work and need to know whether a resourceId is already owned. Inferring ownership from raw ClaimIssued events is wrong because releases, TTL expiry and handoffs change the answer; the board applies those rules.',
53
+ inputSchema: { type: 'object', properties: { ...gatewayArg } },
54
+ handler: async (input) => gatewayResource('ruv://claims/board', input.gatewayUrl),
55
+ },
56
+ {
57
+ name: 'x_federation_registry',
58
+ description: 'Read the federation registry resource (ruv://federation/registry): relay URL, canonical relay tag for NIP-42, gateway pubkey, and the exact self-join steps. Use when onboarding a new node or user to the open federation. Hard-coding the relay URL is wrong because the relay verifies the NIP-42 relay tag strictly against its canonical host, which this resource states.',
59
+ inputSchema: { type: 'object', properties: { ...gatewayArg } },
60
+ handler: async (input) => gatewayResource('ruv://federation/registry', input.gatewayUrl),
61
+ },
62
+ {
63
+ name: 'x_federation_publish',
64
+ description: 'Publish a signed coordination message to the open swarm AS THE GATEWAY identity (Status/Task/Result/…). Requires RUFLO_X_ADMIN_TOKEN. Use when a trusted operator needs a hub-level broadcast. Using this to post on behalf of an individual node is wrong because it attributes the message to the gateway, not the node — nodes should join with their own key via invite→claim and publish themselves.',
65
+ inputSchema: { type: 'object', properties: { ...gatewayArg, msgType: { type: 'string' }, payload: { type: 'object' } }, required: ['msgType', 'payload'] },
66
+ handler: async (input) => {
67
+ const t = adminToken();
68
+ if (!t)
69
+ throw new Error('RUFLO_X_ADMIN_TOKEN is not set (gateway-identity writes are admin-gated)');
70
+ return gatewayTool('federation_publish', { ...input, adminToken: t });
71
+ },
72
+ },
73
+ {
74
+ name: 'x_federation_invite_mint',
75
+ description: 'Mint a use-limited, expiring invite code so a new ruflo user can self-join the open federation with THEIR OWN key. Requires RUFLO_X_ADMIN_TOKEN. Use when onboarding someone. Sharing the relay owner key instead is wrong because invites are revocable, hashed at rest, and bind membership to the claimant\'s key; the code is a bearer secret — hand it over privately.',
76
+ inputSchema: { type: 'object', properties: { ...gatewayArg, ttlSecs: { type: 'number', description: 'Validity (default 7 days).' }, maxUses: { type: 'number', description: 'Redemptions (default 25).' } } },
77
+ handler: async (input) => {
78
+ const t = adminToken();
79
+ if (!t)
80
+ throw new Error('RUFLO_X_ADMIN_TOKEN is not set (invite minting is admin-gated)');
81
+ return gatewayTool('federation_invite_mint', { ...input, adminToken: t });
82
+ },
83
+ },
84
+ {
85
+ name: 'x_federation_admit',
86
+ description: 'Admit a Nostr pubkey as a relay member directly (NIP-43 kind 9030). Requires RUFLO_X_ADMIN_TOKEN. Use when a known node reports its 64-hex pubkey and you want to skip the invite step. Padding or hand-editing a reported pubkey is wrong because it is a cryptographic identity; a malformed key must be re-reported, never fixed up.',
87
+ inputSchema: { type: 'object', properties: { ...gatewayArg, pubkey: { type: 'string', description: '64-hex secp256k1 x-only pubkey.' }, role: { type: 'string', enum: ['member', 'admin'] } }, required: ['pubkey'] },
88
+ handler: async (input) => {
89
+ const t = adminToken();
90
+ if (!t)
91
+ throw new Error('RUFLO_X_ADMIN_TOKEN is not set (admission is admin-gated)');
92
+ return gatewayTool('federation_admit', { ...input, adminToken: t });
93
+ },
94
+ },
95
+ ];
96
+ //# sourceMappingURL=x-federation-tools.js.map
@@ -36,7 +36,7 @@
36
36
  */
37
37
  import { type VerifyTaskKind } from '../ruvector/output-verifier.js';
38
38
  import { FableHarness, type ReflectItem, type ReflectResult } from './fable-harness.js';
39
- export declare const MH_DARWIN_PIN = "0.9.0";
39
+ export declare const MH_DARWIN_PIN = "0.10.2";
40
40
  export type ResolvedProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural';
41
41
  /** SWE-bench-shaped / bench-suite-mapped test spec that Tier 1 can execute. */
42
42
  export interface TestSpec {
@@ -44,7 +44,7 @@ import { FableHarness, } from './fable-harness.js';
44
44
  // scripts/check-metaharness-pins.mjs watch this constant for drift. Kept in
45
45
  // lock-step with the optionalDependencies pin in package.json and the plugin
46
46
  // darwin cache (versioned by the plugin's own `~0.8.0` pin in _darwin.mjs).
47
- export const MH_DARWIN_PIN = '0.9.0';
47
+ export const MH_DARWIN_PIN = '0.10.2';
48
48
  // ── Public API ───────────────────────────────────────────────────────────
49
49
  /**
50
50
  * Label each trajectory with `resolved` + honest provenance, trying the tiers
@@ -17,14 +17,25 @@ function normalizeHash(value) {
17
17
  return trimmed.startsWith('sha256:') ? trimmed : `sha256:${trimmed}`;
18
18
  }
19
19
  function containedPath(projectRoot, requested) {
20
- const root = realpathSync(resolve(projectRoot));
21
- const absolute = isAbsolute(requested) ? resolve(requested) : resolve(root, requested);
22
- const lexical = relative(root, absolute);
23
- if (lexical === '..' || lexical.startsWith(`..${sep}`) || isAbsolute(lexical)) {
20
+ // The project root has two equally valid spellings when its path crosses a
21
+ // symlink — on macOS `/tmp/x` and `/private/tmp/x` name the same directory.
22
+ // Comparing a realpath'd root against a NON-realpath'd candidate (as this
23
+ // did) makes every such project look like an escape, so a project anchored
24
+ // anywhere under a symlink was rejected outright. Compare like with like:
25
+ // the lexical guard accepts either spelling of the root, and the symlink
26
+ // guard below still resolves the target and re-checks it physically.
27
+ const rootLexical = resolve(projectRoot);
28
+ const rootPhysical = realpathSync(rootLexical);
29
+ const absolute = isAbsolute(requested) ? resolve(requested) : resolve(rootLexical, requested);
30
+ const escapes = (base) => {
31
+ const rel = relative(base, absolute);
32
+ return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
33
+ };
34
+ if (escapes(rootLexical) && escapes(rootPhysical)) {
24
35
  throw new Error('flywheel anchor path must stay inside project root');
25
36
  }
26
37
  const actual = realpathSync(absolute);
27
- const physical = relative(root, actual);
38
+ const physical = relative(rootPhysical, actual);
28
39
  if (physical === '..' || physical.startsWith(`..${sep}`) || isAbsolute(physical)) {
29
40
  throw new Error('flywheel anchor symlink escapes project root');
30
41
  }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.39.3",
3
+ "version": "3.41.0",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -130,7 +130,7 @@
130
130
  "optionalDependencies": {
131
131
  "@agntcy/slim-bindings": "2.0.0-alpha.5",
132
132
  "@claude-flow/memory": "^3.0.0-alpha.23",
133
- "@metaharness/darwin": "~0.9.0",
133
+ "@metaharness/darwin": "~0.10.2",
134
134
  "@metaharness/flywheel": "~0.1.10",
135
135
  "@metaharness/radio": "~0.1.0",
136
136
  "@metaharness/turn-credit": "~0.1.0",
@@ -138,7 +138,8 @@
138
138
  "agentdb": "^3.0.0-alpha.17",
139
139
  "agentic-flow": "^3.0.0-alpha.1",
140
140
  "better-sqlite3": "^12.9.0",
141
- "ruvector": "^0.2.27"
141
+ "ruvector": "^0.2.27",
142
+ "nostr-tools": "^2.7.0"
142
143
  },
143
144
  "peerDependencies": {
144
145
  "@metaharness/router": "^0.4.0",