@kaleidorg/mind 0.1.0 → 0.2.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/dist/capabilities.d.ts +34 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +34 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/context/budget.d.ts +29 -0
- package/dist/context/budget.d.ts.map +1 -0
- package/dist/context/budget.js +36 -0
- package/dist/context/budget.js.map +1 -0
- package/dist/context/builder.d.ts +39 -0
- package/dist/context/builder.d.ts.map +1 -0
- package/dist/context/builder.js +77 -0
- package/dist/context/builder.js.map +1 -0
- package/dist/fastpath/fastpath.d.ts +38 -0
- package/dist/fastpath/fastpath.d.ts.map +1 -0
- package/dist/fastpath/fastpath.js +52 -0
- package/dist/fastpath/fastpath.js.map +1 -0
- package/dist/funnel.d.ts +111 -0
- package/dist/funnel.d.ts.map +1 -0
- package/dist/funnel.js +175 -0
- package/dist/funnel.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/knowledge/bitcoin-copilot.d.ts +11 -0
- package/dist/knowledge/bitcoin-copilot.d.ts.map +1 -0
- package/dist/knowledge/bitcoin-copilot.js +155 -0
- package/dist/knowledge/bitcoin-copilot.js.map +1 -0
- package/dist/knowledge/merchants.d.ts +24 -0
- package/dist/knowledge/merchants.d.ts.map +1 -0
- package/dist/knowledge/merchants.js +34 -0
- package/dist/knowledge/merchants.js.map +1 -0
- package/dist/knowledge/wallet.d.ts +34 -0
- package/dist/knowledge/wallet.d.ts.map +1 -0
- package/dist/knowledge/wallet.js +63 -0
- package/dist/knowledge/wallet.js.map +1 -0
- package/dist/memory/store.d.ts +34 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +103 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/tool.d.ts +9 -0
- package/dist/memory/tool.d.ts.map +1 -0
- package/dist/memory/tool.js +70 -0
- package/dist/memory/tool.js.map +1 -0
- package/dist/memory/types.d.ts +56 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/types.js +14 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/rag/retriever.d.ts +30 -0
- package/dist/rag/retriever.d.ts.map +1 -0
- package/dist/rag/retriever.js +72 -0
- package/dist/rag/retriever.js.map +1 -0
- package/dist/rag/tool.d.ts +15 -0
- package/dist/rag/tool.d.ts.map +1 -0
- package/dist/rag/tool.js +42 -0
- package/dist/rag/tool.js.map +1 -0
- package/dist/rag/types.d.ts +44 -0
- package/dist/rag/types.d.ts.map +1 -0
- package/dist/rag/types.js +11 -0
- package/dist/rag/types.js.map +1 -0
- package/dist/rag/vector-store.d.ts +23 -0
- package/dist/rag/vector-store.d.ts.map +1 -0
- package/dist/rag/vector-store.js +72 -0
- package/dist/rag/vector-store.js.map +1 -0
- package/dist/recipe/asset-send.d.ts +15 -0
- package/dist/recipe/asset-send.d.ts.map +1 -0
- package/dist/recipe/asset-send.js +83 -0
- package/dist/recipe/asset-send.js.map +1 -0
- package/dist/recipe/payments.d.ts +15 -0
- package/dist/recipe/payments.d.ts.map +1 -0
- package/dist/recipe/payments.js +119 -0
- package/dist/recipe/payments.js.map +1 -0
- package/dist/recipe/receive.d.ts +14 -0
- package/dist/recipe/receive.d.ts.map +1 -0
- package/dist/recipe/receive.js +109 -0
- package/dist/recipe/receive.js.map +1 -0
- package/dist/recipe/runner.d.ts +42 -0
- package/dist/recipe/runner.d.ts.map +1 -0
- package/dist/recipe/runner.js +94 -0
- package/dist/recipe/runner.js.map +1 -0
- package/dist/recipe/swap.d.ts +16 -0
- package/dist/recipe/swap.d.ts.map +1 -0
- package/dist/recipe/swap.js +73 -0
- package/dist/recipe/swap.js.map +1 -0
- package/dist/recipe/types.d.ts +71 -0
- package/dist/recipe/types.d.ts.map +1 -0
- package/dist/recipe/types.js +13 -0
- package/dist/recipe/types.js.map +1 -0
- package/dist/tools/cli.d.ts +43 -0
- package/dist/tools/cli.d.ts.map +1 -0
- package/dist/tools/cli.js +61 -0
- package/dist/tools/cli.js.map +1 -0
- package/dist/tools/mcp.d.ts +3 -2
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mcp.js +3 -2
- package/dist/tools/mcp.js.map +1 -1
- package/dist/wallet/contract.d.ts +57 -0
- package/dist/wallet/contract.d.ts.map +1 -0
- package/dist/wallet/contract.js +113 -0
- package/dist/wallet/contract.js.map +1 -0
- package/package.json +9 -5
- package/src/capabilities.ts +67 -0
- package/src/context/budget.ts +46 -0
- package/src/context/builder.ts +100 -0
- package/src/context/context.test.ts +83 -0
- package/src/fastpath/fastpath.test.ts +34 -0
- package/src/fastpath/fastpath.ts +70 -0
- package/src/funnel.test.ts +207 -0
- package/src/funnel.ts +260 -0
- package/src/index.ts +85 -0
- package/src/knowledge/bitcoin-copilot.ts +177 -0
- package/src/knowledge/knowledge.test.ts +63 -0
- package/src/knowledge/merchants.ts +49 -0
- package/src/knowledge/wallet.ts +84 -0
- package/src/memory/memory.test.ts +85 -0
- package/src/memory/store.ts +129 -0
- package/src/memory/tool.ts +76 -0
- package/src/memory/types.ts +63 -0
- package/src/rag/rag.test.ts +85 -0
- package/src/rag/retriever.ts +94 -0
- package/src/rag/tool.ts +55 -0
- package/src/rag/types.ts +49 -0
- package/src/rag/vector-store.ts +78 -0
- package/src/recipe/asset-send.ts +79 -0
- package/src/recipe/payments.ts +116 -0
- package/src/recipe/receive.ts +98 -0
- package/src/recipe/recipe.test.ts +193 -0
- package/src/recipe/runner.ts +122 -0
- package/src/recipe/swap.ts +74 -0
- package/src/recipe/types.ts +76 -0
- package/src/tools/cli.test.ts +53 -0
- package/src/tools/cli.ts +98 -0
- package/src/tools/mcp.ts +3 -2
- package/src/wallet/contract.test.ts +89 -0
- package/src/wallet/contract.ts +157 -0
package/src/tools/cli.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI tool source — the fourth tool mechanism (alongside in-process function
|
|
3
|
+
* calling, MCP, and skills). Lets the agent run shell commands, e.g. a skill's
|
|
4
|
+
* documented CLI path (`@bitrefill/cli`, `kaleido`, `git`, …).
|
|
5
|
+
*
|
|
6
|
+
* Command execution is INJECTED (`CommandRunner`) so this file has no Node
|
|
7
|
+
* dependency and stays RN-safe — a Node host provides the runner (ideally via a
|
|
8
|
+
* non-shell `execFile`-style helper); React Native simply never provides one.
|
|
9
|
+
* Guarded by a required allowlist of command prefixes, and confirmation-gated
|
|
10
|
+
* by default since it runs real commands.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { ToolDef } from '../types.js';
|
|
14
|
+
import type { ToolSource } from './source.js';
|
|
15
|
+
|
|
16
|
+
export interface CommandResult {
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
code: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Injected shell runner. The Node host supplies a safe implementation. */
|
|
23
|
+
export interface CommandRunner {
|
|
24
|
+
run(command: string, opts?: { cwd?: string; timeoutMs?: number }): Promise<CommandResult>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CliToolOptions {
|
|
28
|
+
runner: CommandRunner;
|
|
29
|
+
/**
|
|
30
|
+
* Allowed command prefixes (REQUIRED — no empty allowlist). A command runs
|
|
31
|
+
* only if it starts with one of these tokens, e.g. ['kaleido', 'git status',
|
|
32
|
+
* 'npx @bitrefill/cli'].
|
|
33
|
+
*/
|
|
34
|
+
allow: string[];
|
|
35
|
+
cwd?: string;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
/** Confirmation gate (default true — it executes real commands). */
|
|
38
|
+
requiresConfirmation?: boolean;
|
|
39
|
+
/** Tool description override (e.g. name the specific CLI). */
|
|
40
|
+
description?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const RUN = 'run_command';
|
|
44
|
+
|
|
45
|
+
/** True if `command` is permitted by the allowlist (prefix match on tokens). */
|
|
46
|
+
export function isAllowed(command: string, allow: string[]): boolean {
|
|
47
|
+
const cmd = command.trim();
|
|
48
|
+
return allow.some((prefix) => {
|
|
49
|
+
const p = prefix.trim();
|
|
50
|
+
return p.length > 0 && (cmd === p || cmd.startsWith(p + ' '));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createCliToolSource(opts: CliToolOptions): ToolSource {
|
|
55
|
+
if (!opts.allow || opts.allow.length === 0) {
|
|
56
|
+
throw new Error('createCliToolSource: a non-empty `allow` allowlist is required');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tool: ToolDef = {
|
|
60
|
+
name: RUN,
|
|
61
|
+
description:
|
|
62
|
+
opts.description ??
|
|
63
|
+
`Run an allowed shell command and return its output. Allowed commands start ` +
|
|
64
|
+
`with: ${opts.allow.join(', ')}. Use for documented CLI tools.`,
|
|
65
|
+
parameters: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
command: { type: 'string', description: 'The full command line to run' },
|
|
69
|
+
},
|
|
70
|
+
required: ['command'],
|
|
71
|
+
},
|
|
72
|
+
requiresConfirmation: opts.requiresConfirmation ?? true,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
async function execute(_name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
76
|
+
const command = String(args.command ?? '').trim();
|
|
77
|
+
if (!command) throw new Error('run_command: command is required');
|
|
78
|
+
if (!isAllowed(command, opts.allow)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`run_command: "${command.split(' ')[0]}" is not allowed. Allowed: ${opts.allow.join(', ')}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const res = await opts.runner.run(command, { cwd: opts.cwd, timeoutMs: opts.timeoutMs });
|
|
84
|
+
const out = (res.stdout || '').trim();
|
|
85
|
+
const err = (res.stderr || '').trim();
|
|
86
|
+
if (res.code !== 0) {
|
|
87
|
+
return `exit ${res.code}${err ? `\n${err}` : ''}${out ? `\n${out}` : ''}`.trim();
|
|
88
|
+
}
|
|
89
|
+
return out || '(no output)';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: 'cli',
|
|
94
|
+
listTools: () => [tool],
|
|
95
|
+
has: (name) => name === RUN,
|
|
96
|
+
execute,
|
|
97
|
+
};
|
|
98
|
+
}
|
package/src/tools/mcp.ts
CHANGED
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* file type-checks and ships even where the SDK isn't installed; constructing
|
|
14
14
|
* an McpToolSource without it throws a clear error.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* Wired end-to-end: connect() (stdio + HTTP transports), listTools() and
|
|
17
|
+
* execute() are implemented. Used by the desktop sidecar (kaleido-mcp +
|
|
18
|
+
* Bitrefill MCP) and verified against the remote Bitrefill MCP.
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import type { ToolDef } from '../types.js';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Wallet contract tests — integrity of the single source of truth. */
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import {
|
|
5
|
+
WALLET_TOOLS,
|
|
6
|
+
SPEND_TOOLS,
|
|
7
|
+
isSpendTool,
|
|
8
|
+
walletTools,
|
|
9
|
+
toToolDefs,
|
|
10
|
+
bindWalletTools,
|
|
11
|
+
getWalletTool,
|
|
12
|
+
} from './contract.js';
|
|
13
|
+
|
|
14
|
+
describe('WALLET_TOOLS contract', () => {
|
|
15
|
+
it('has unique names and object schemas', () => {
|
|
16
|
+
const names = WALLET_TOOLS.map((t) => t.name);
|
|
17
|
+
expect(new Set(names).size).toBe(names.length);
|
|
18
|
+
for (const t of WALLET_TOOLS) {
|
|
19
|
+
expect(t.name).toMatch(/^[a-z][a-z0-9_]+$/);
|
|
20
|
+
expect((t.parameters as any).type).toBe('object');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('namespaces per-layer tools and keeps core helpers unprefixed', () => {
|
|
25
|
+
for (const t of WALLET_TOOLS) {
|
|
26
|
+
if (t.layer === 'core') continue;
|
|
27
|
+
expect(t.name.startsWith(`${t.layer}_`)).toBe(true);
|
|
28
|
+
}
|
|
29
|
+
expect(getWalletTool('send_payment')!.layer).toBe('core');
|
|
30
|
+
expect(getWalletTool('resolve_contact')!.layer).toBe('core');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('spend tools are confirmation-gated; reads do not move funds', () => {
|
|
34
|
+
for (const t of WALLET_TOOLS) {
|
|
35
|
+
expect(!!t.requiresConfirmation).toBe(!!t.spend);
|
|
36
|
+
}
|
|
37
|
+
// every fund-moving tool is flagged
|
|
38
|
+
expect(isSpendTool('send_payment')).toBe(true);
|
|
39
|
+
expect(isSpendTool('rln_send_asset')).toBe(true);
|
|
40
|
+
expect(isSpendTool('execute_swap')).toBe(true);
|
|
41
|
+
expect(isSpendTool('spark_send')).toBe(true);
|
|
42
|
+
// reads are not
|
|
43
|
+
expect(isSpendTool('get_balances')).toBe(false);
|
|
44
|
+
expect(isSpendTool('get_price')).toBe(false);
|
|
45
|
+
expect([...SPEND_TOOLS].length).toBeGreaterThanOrEqual(5);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('required args declared on the actionable tools', () => {
|
|
49
|
+
expect((getWalletTool('send_payment')!.parameters as any).required).toContain('to');
|
|
50
|
+
expect((getWalletTool('fiat_to_sats')!.parameters as any).required).toEqual(['amount', 'currency']);
|
|
51
|
+
expect((getWalletTool('rln_create_rgb_invoice')!.parameters as any).required).toEqual(['asset', 'amount']);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('selectors', () => {
|
|
56
|
+
it('walletTools filters by layer + always includes core unless disabled', () => {
|
|
57
|
+
const spark = walletTools({ layers: ['spark'] });
|
|
58
|
+
expect(spark.some((t) => t.name === 'spark_send')).toBe(true);
|
|
59
|
+
expect(spark.some((t) => t.layer === 'core')).toBe(true); // core included by default
|
|
60
|
+
expect(spark.some((t) => t.layer === 'rln')).toBe(false);
|
|
61
|
+
|
|
62
|
+
const noCore = walletTools({ layers: ['spark'], includeCore: false });
|
|
63
|
+
expect(noCore.every((t) => t.layer === 'spark')).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('toToolDefs strips metadata but keeps requiresConfirmation', () => {
|
|
67
|
+
const defs = toToolDefs(walletTools({ layers: ['spark'] }));
|
|
68
|
+
const send = defs.find((d) => d.name === 'spark_send')!;
|
|
69
|
+
expect(send.requiresConfirmation).toBe(true);
|
|
70
|
+
expect('layer' in (send as any)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('bindWalletTools', () => {
|
|
75
|
+
it('binds handlers → an InProcessToolSource with spend flags preserved', async () => {
|
|
76
|
+
const handler = vi.fn(async () => ({ ok: true }));
|
|
77
|
+
const src = bindWalletTools({ spark_get_balance: handler, spark_send: handler }, { layers: ['spark'], includeCore: false, allowMissing: true });
|
|
78
|
+
const tools = src.listTools();
|
|
79
|
+
expect(tools.map((t) => t.name).sort()).toEqual(['spark_get_balance', 'spark_send']);
|
|
80
|
+
expect(tools.find((t) => t.name === 'spark_send')!.requiresConfirmation).toBe(true);
|
|
81
|
+
expect(await src.execute('spark_get_balance', {})).toEqual({ ok: true });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('throws on a missing handler unless allowMissing', () => {
|
|
85
|
+
expect(() => bindWalletTools({}, { layers: ['spark'], includeCore: false })).toThrow(/no handler/);
|
|
86
|
+
const src = bindWalletTools({ spark_get_balance: async () => 1 }, { layers: ['spark'], includeCore: false, allowMissing: true });
|
|
87
|
+
expect(src.listTools().map((t) => t.name)).toEqual(['spark_get_balance']);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical multi-L2 wallet tool contract — the single source of truth for
|
|
3
|
+
* KaleidoMind's wallet tools (names + JSON schemas + spend flags).
|
|
4
|
+
*
|
|
5
|
+
* Every surface implements THESE EXACT tools, only the transport differs:
|
|
6
|
+
* - mobile → in-process handlers over the WDK adapters (`bindWalletTools`)
|
|
7
|
+
* - desktop → kaleido-mcp (tools namespaced per layer) + a `kaleido` CLI
|
|
8
|
+
* - eval → stub handlers
|
|
9
|
+
*
|
|
10
|
+
* Because the schemas are identical everywhere, skills are portable and the
|
|
11
|
+
* model comparison is honest. Tools are namespaced per layer (`spark_*`,
|
|
12
|
+
* `rln_*`, `arkade_*`, `liquid_*`); cross-cutting router/helpers are unprefixed.
|
|
13
|
+
*
|
|
14
|
+
* Spend tools (move funds) carry `spend: true` → `requiresConfirmation: true`,
|
|
15
|
+
* so the Engine always pauses for the host's confirm gate before executing.
|
|
16
|
+
*
|
|
17
|
+
* Pure data — no deps, RN-safe.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ToolDef } from '../types.js';
|
|
21
|
+
import { InProcessToolSource } from '../tools/in-process.js';
|
|
22
|
+
import type { InProcessTool } from '../tools/in-process.js';
|
|
23
|
+
|
|
24
|
+
export type WalletLayer = 'spark' | 'rln' | 'arkade' | 'liquid' | 'core';
|
|
25
|
+
|
|
26
|
+
export interface WalletToolDef extends ToolDef {
|
|
27
|
+
/** Which L2 (or 'core' for cross-cutting router/helpers). */
|
|
28
|
+
layer: WalletLayer;
|
|
29
|
+
/** Moves funds → confirmation-gated. */
|
|
30
|
+
spend?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type Props = Record<string, { type: string; description?: string; enum?: string[] }>;
|
|
34
|
+
|
|
35
|
+
function t(
|
|
36
|
+
layer: WalletLayer,
|
|
37
|
+
name: string,
|
|
38
|
+
description: string,
|
|
39
|
+
properties: Props = {},
|
|
40
|
+
required: string[] = [],
|
|
41
|
+
spend = false,
|
|
42
|
+
): WalletToolDef {
|
|
43
|
+
return {
|
|
44
|
+
layer,
|
|
45
|
+
name,
|
|
46
|
+
description,
|
|
47
|
+
spend,
|
|
48
|
+
requiresConfirmation: spend,
|
|
49
|
+
parameters: { type: 'object', properties, required },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sats = { type: 'number', description: 'Amount in satoshis' } as const;
|
|
54
|
+
const asset = { type: 'string', description: "Asset ticker, e.g. 'USDT', 'XAUT', 'BTC'" } as const;
|
|
55
|
+
|
|
56
|
+
/** The full contract. Keep descriptions terse — small models read every word. */
|
|
57
|
+
export const WALLET_TOOLS: WalletToolDef[] = [
|
|
58
|
+
// ── Spark ──────────────────────────────────────────────────────────────
|
|
59
|
+
t('spark', 'spark_get_balance', 'Get the Spark wallet BTC balance.'),
|
|
60
|
+
t('spark', 'spark_get_address', 'Get a Spark deposit address to receive BTC.'),
|
|
61
|
+
t('spark', 'spark_create_invoice', 'Create a Spark Lightning invoice to receive BTC.', { amount_sats: sats }),
|
|
62
|
+
t('spark', 'spark_send', 'Send BTC from Spark to an address or invoice.', { amount_sats: sats, to: { type: 'string', description: 'Address or invoice' } }, ['amount_sats', 'to'], true),
|
|
63
|
+
|
|
64
|
+
// ── RLN / RGB ──────────────────────────────────────────────────────────
|
|
65
|
+
t('rln', 'rln_get_balances', 'Get RLN node balances (BTC + RGB assets).'),
|
|
66
|
+
t('rln', 'rln_get_node_info', 'Get RLN node status and sync state.'),
|
|
67
|
+
t('rln', 'rln_list_channels', 'List the RLN node Lightning channels.'),
|
|
68
|
+
t('rln', 'rln_create_ln_invoice', 'Create a Lightning (BTC) invoice on the RLN node.', { amount_sats: sats }),
|
|
69
|
+
t('rln', 'rln_create_rgb_invoice', 'Create an RGB asset invoice to receive an asset (e.g. USDT).', { asset, amount: { type: 'number', description: 'Asset amount' } }, ['asset', 'amount']),
|
|
70
|
+
t('rln', 'rln_pay_invoice', 'Pay a Lightning invoice from the RLN node.', { invoice: { type: 'string' } }, ['invoice'], true),
|
|
71
|
+
t('rln', 'rln_send_asset', 'Send an RGB asset (e.g. USDT) to a recipient.', { asset, amount: { type: 'number' }, to: { type: 'string' } }, ['asset', 'amount', 'to'], true),
|
|
72
|
+
|
|
73
|
+
// ── Arkade ─────────────────────────────────────────────────────────────
|
|
74
|
+
t('arkade', 'arkade_get_balance', 'Get the Arkade wallet balance.'),
|
|
75
|
+
t('arkade', 'arkade_get_address', 'Get an Arkade address to receive funds.'),
|
|
76
|
+
t('arkade', 'arkade_send', 'Send BTC from Arkade to a recipient.', { amount_sats: sats, to: { type: 'string' } }, ['amount_sats', 'to'], true),
|
|
77
|
+
|
|
78
|
+
// ── Liquid (later) ─────────────────────────────────────────────────────
|
|
79
|
+
t('liquid', 'liquid_get_balance', 'Get the Liquid wallet balance (L-BTC + assets).'),
|
|
80
|
+
t('liquid', 'liquid_create_invoice', 'Create a Liquid invoice/address to receive (L-BTC or L-USDt).', { asset, amount: { type: 'number' } }),
|
|
81
|
+
t('liquid', 'liquid_send', 'Send a Liquid asset (L-BTC or L-USDt) to a recipient.', { asset, amount: { type: 'number' }, to: { type: 'string' } }, ['asset', 'amount', 'to'], true),
|
|
82
|
+
|
|
83
|
+
// ── Core: router + helpers ─────────────────────────────────────────────
|
|
84
|
+
t('core', 'get_balances', 'Get balances across all layers (or one layer).', { layer: { type: 'string', enum: ['spark', 'rln', 'arkade', 'liquid'], description: 'Optional: a single layer' } }),
|
|
85
|
+
t('core', 'resolve_contact', 'Resolve a contact name to a Lightning address / Nostr / preferred rail.', { name: { type: 'string', description: 'Contact name, e.g. "bob"' } }, ['name']),
|
|
86
|
+
t('core', 'get_price', 'Get the current price of an asset, optionally in a fiat currency.', { asset, fiat: { type: 'string', description: "Fiat code, e.g. 'EUR', 'USD'" } }),
|
|
87
|
+
t('core', 'fiat_to_sats', 'Convert a fiat amount to satoshis at the current rate.', { amount: { type: 'number' }, currency: { type: 'string', description: "Fiat code, e.g. 'EUR'" } }, ['amount', 'currency']),
|
|
88
|
+
t('core', 'get_swap_quote', 'Quote a swap between two assets.', { from_asset: asset, to_asset: asset, amount: { type: 'number' } }, ['from_asset', 'to_asset', 'amount']),
|
|
89
|
+
t('core', 'execute_swap', 'Execute a previously quoted swap.', { quote_id: { type: 'string' }, from_asset: asset, to_asset: asset, amount: { type: 'number' } }, [], true),
|
|
90
|
+
// The high-level entry a skill prefers — picks the rail for the asset, or uses `layer`.
|
|
91
|
+
t('core', 'send_payment', 'Send a payment, automatically choosing the best layer for the asset (or use `layer`).', { asset, amount_sats: sats, to: { type: 'string', description: 'Contact, address, or invoice' }, layer: { type: 'string', enum: ['spark', 'rln', 'arkade', 'liquid'] } }, ['to'], true),
|
|
92
|
+
// High-level receive — picks the right invoice/address tool for the asset/layer.
|
|
93
|
+
t('core', 'create_invoice', 'Create an invoice or address to receive funds, choosing the rail for the asset (or use `layer`). Omit amount for an any-amount invoice.', { asset, amount: { type: 'number', description: 'Amount (sats for BTC, asset units otherwise) — optional' }, layer: { type: 'string', enum: ['spark', 'rln', 'arkade', 'liquid'] } }),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
// ── Selectors ───────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
export const WALLET_LAYERS: WalletLayer[] = ['spark', 'rln', 'arkade', 'liquid', 'core'];
|
|
99
|
+
|
|
100
|
+
/** Names of all spend (fund-moving) tools — these are confirmation-gated. */
|
|
101
|
+
export const SPEND_TOOLS: ReadonlySet<string> = new Set(WALLET_TOOLS.filter((x) => x.spend).map((x) => x.name));
|
|
102
|
+
|
|
103
|
+
export function isSpendTool(name: string): boolean {
|
|
104
|
+
return SPEND_TOOLS.has(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getWalletTool(name: string): WalletToolDef | undefined {
|
|
108
|
+
return WALLET_TOOLS.find((x) => x.name === name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Pick the contract tools for the given layers (core helpers included by default). */
|
|
112
|
+
export function walletTools(opts: { layers?: WalletLayer[]; includeCore?: boolean } = {}): WalletToolDef[] {
|
|
113
|
+
const layers = new Set(opts.layers ?? (['spark', 'rln', 'arkade', 'liquid'] as WalletLayer[]));
|
|
114
|
+
if (opts.includeCore !== false) layers.add('core');
|
|
115
|
+
return WALLET_TOOLS.filter((x) => layers.has(x.layer));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Strip to plain ToolDefs (drop the layer/spend metadata). */
|
|
119
|
+
export function toToolDefs(tools: WalletToolDef[]): ToolDef[] {
|
|
120
|
+
return tools.map(({ name, description, parameters, requiresConfirmation }) => ({ name, description, parameters, requiresConfirmation }));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** A handler bound to one contract tool. */
|
|
124
|
+
export type WalletHandler = (args: Record<string, unknown>) => Promise<unknown>;
|
|
125
|
+
|
|
126
|
+
export interface BindWalletOptions {
|
|
127
|
+
layers?: WalletLayer[];
|
|
128
|
+
includeCore?: boolean;
|
|
129
|
+
/** Skip tools that have no handler instead of throwing (default false). */
|
|
130
|
+
allowMissing?: boolean;
|
|
131
|
+
id?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Bind contract tools to in-process handlers → an InProcessToolSource. The
|
|
136
|
+
* mobile (and eval) binding: pass a map of `{ toolName: handler }` and you get a
|
|
137
|
+
* ToolSource implementing the canonical schemas with spend flags preserved.
|
|
138
|
+
*/
|
|
139
|
+
export function bindWalletTools(handlers: Record<string, WalletHandler>, opts: BindWalletOptions = {}): InProcessToolSource {
|
|
140
|
+
const tools = walletTools(opts);
|
|
141
|
+
const bound: InProcessTool[] = [];
|
|
142
|
+
for (const def of tools) {
|
|
143
|
+
const handler = handlers[def.name];
|
|
144
|
+
if (!handler) {
|
|
145
|
+
if (opts.allowMissing) continue;
|
|
146
|
+
throw new Error(`bindWalletTools: no handler for "${def.name}"`);
|
|
147
|
+
}
|
|
148
|
+
bound.push({
|
|
149
|
+
name: def.name,
|
|
150
|
+
description: def.description,
|
|
151
|
+
parameters: def.parameters,
|
|
152
|
+
requiresConfirmation: def.requiresConfirmation,
|
|
153
|
+
handler,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return new InProcessToolSource(opts.id ?? 'wallet', bound);
|
|
157
|
+
}
|