@techdigger/humanode-agentlink-cli 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/README.md +93 -0
- package/REGISTRATION.md +302 -0
- package/dist/commands/authorize-link.d.ts +19 -0
- package/dist/commands/authorize-link.js +30 -0
- package/dist/commands/init.d.ts +33 -0
- package/dist/commands/init.js +127 -0
- package/dist/commands/link.d.ts +38 -0
- package/dist/commands/link.js +82 -0
- package/dist/commands/status.d.ts +30 -0
- package/dist/commands/status.js +68 -0
- package/dist/help.d.ts +5 -0
- package/dist/help.js +103 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +185 -0
- package/dist/lib/core.d.ts +92 -0
- package/dist/lib/core.js +435 -0
- package/dist/lib/telemetry.d.ts +13 -0
- package/dist/lib/telemetry.js +123 -0
- package/dist/templates.d.ts +12 -0
- package/dist/templates.js +281 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @techdigger/humanode-agentlink-cli
|
|
2
|
+
|
|
3
|
+
Scaffold, link, and inspect Biomapper-ready agent projects.
|
|
4
|
+
|
|
5
|
+
`agentlink` is the default entrypoint for agent developers:
|
|
6
|
+
|
|
7
|
+
- `agentlink init` scaffolds a starter for LangChain, Vercel AI SDK, or MCP
|
|
8
|
+
- `agentlink link` generates agent consent, builds a self-contained local/dev linker URL, and can open it in your browser
|
|
9
|
+
- `agentlink status` checks whether an agent is linked and active
|
|
10
|
+
- `agentlink authorize-link` remains available for lower-level manual linking flows
|
|
11
|
+
|
|
12
|
+
Generated starters also include local `link`, `status`, and `authorize-link` package scripts, so you can run them from inside the scaffolded project after installing dependencies.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx @techdigger/humanode-agentlink-cli init my-agent
|
|
18
|
+
cd my-agent
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Fill in `.env.local` for local development, then generate a linker URL for the owner wallet:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
agentlink link --owner 0xOwner --open
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
After the owner completes linking in the browser, check the result:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
agentlink status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `agentlink init`
|
|
36
|
+
|
|
37
|
+
Scaffold a TypeScript starter for:
|
|
38
|
+
|
|
39
|
+
- `langchain`
|
|
40
|
+
- `vercel-ai-sdk`
|
|
41
|
+
- `mcp`
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
agentlink init my-agent --template langchain --network base-sepolia --registry 0xRegistry
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
When you scaffold from this repo checkout, `init` automatically uses local `file:` dependencies for the Biomapper packages. Add `--package-source npm` to force npm package versions instead.
|
|
50
|
+
|
|
51
|
+
### `agentlink link`
|
|
52
|
+
|
|
53
|
+
Generate consent, create a self-contained local/dev linker URL, and optionally open it:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
agentlink link --owner 0xOwner --registry 0xRegistry --network base-sepolia --open
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The CLI does not submit `linkAgent(...)` itself. The owner still completes the on-chain transaction in the linker UI.
|
|
60
|
+
|
|
61
|
+
`agentlink link` is the local/self-contained flow. Hosted production linkers should create and store sessions server-side with `buildHostedLinkUrl(...)` and the same-origin session lookup endpoint instead of embedding the full session in the URL.
|
|
62
|
+
|
|
63
|
+
Prefer `--private-key-stdin` or an external secret manager for production secret handling. `AGENT_PRIVATE_KEY` and `.env.local` are convenient local/dev defaults. `--private-key` remains available for compatibility, but it is best treated as a dev-only shortcut because argv can leak into shell history and process inspection.
|
|
64
|
+
|
|
65
|
+
### `agentlink status`
|
|
66
|
+
|
|
67
|
+
Check whether the current agent is linked and active:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
agentlink status --registry 0xRegistry --network base-sepolia
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If `AGENT_PRIVATE_KEY` is set, the agent address is derived automatically. Otherwise, pass `--agent 0xAgent` or pipe the key with `--private-key-stdin`.
|
|
74
|
+
|
|
75
|
+
### `agentlink authorize-link`
|
|
76
|
+
|
|
77
|
+
Generate the raw one-time EIP-712 consent blob for manual or platform-managed flows:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
agentlink authorize-link --owner 0xOwner --registry 0xRegistry --deadline 1735689600 --network base-sepolia
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Environment
|
|
84
|
+
|
|
85
|
+
`agentlink` automatically loads `.env` and `.env.local`.
|
|
86
|
+
|
|
87
|
+
- `AGENT_PRIVATE_KEY` for local/dev flows
|
|
88
|
+
- `BIOMAPPER_NETWORK`
|
|
89
|
+
- `BIOMAPPER_REGISTRY`
|
|
90
|
+
- `BIOMAPPER_RPC_URL`
|
|
91
|
+
- `BIOMAPPER_LINKER_URL`
|
|
92
|
+
|
|
93
|
+
For the full flow, examples, and hosted-link details, see the [Agent Developer Guide](./REGISTRATION.md).
|
package/REGISTRATION.md
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Biomapper Link — Agent Developer Guide
|
|
2
|
+
|
|
3
|
+
Link your AI agent to a biomapped human and unlock free or discounted access to x402 APIs.
|
|
4
|
+
|
|
5
|
+
## What linking does
|
|
6
|
+
|
|
7
|
+
When your agent is linked to a biomapped owner, platforms using Biomapper Link can identify your agent as acting on behalf of a real human. This unlocks better pricing: free tiers, free trials, or discounts that are unavailable to unlinked agents.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- An agent EVM private key
|
|
12
|
+
- A `BiomapperAgentRegistry` address on Base or Base Sepolia
|
|
13
|
+
- An owner wallet that is biomapped, or can complete Biomapper in the linker flow
|
|
14
|
+
- Node.js and npm, pnpm, yarn, or bun
|
|
15
|
+
|
|
16
|
+
## Happy path
|
|
17
|
+
|
|
18
|
+
### Step 1: Scaffold an agent starter
|
|
19
|
+
|
|
20
|
+
Create a starter for LangChain, Vercel AI SDK, or MCP:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @techdigger/humanode-agentlink-cli init my-agent
|
|
24
|
+
cd my-agent
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Or choose a template explicitly:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
agentlink init my-agent --template langchain
|
|
31
|
+
agentlink init my-agent --template vercel-ai-sdk
|
|
32
|
+
agentlink init my-agent --template mcp
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The generated project includes `.env.local`, a runnable `src/index.ts`, and a README with template-specific instructions.
|
|
36
|
+
It also includes local `link`, `status`, and `authorize-link` package scripts backed by `@techdigger/humanode-agentlink-cli`.
|
|
37
|
+
|
|
38
|
+
### Step 2: Fill in `.env.local`
|
|
39
|
+
|
|
40
|
+
The CLI and generated starters read these values automatically:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
AGENT_PRIVATE_KEY=0x...
|
|
44
|
+
BIOMAPPER_NETWORK=base-sepolia
|
|
45
|
+
BIOMAPPER_REGISTRY=0xYourRegistry
|
|
46
|
+
BIOMAPPER_LINKER_URL=http://localhost:5173/
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
LangChain and Vercel AI SDK starters also include `OPENAI_API_KEY`.
|
|
50
|
+
|
|
51
|
+
This `.env.local` pattern is convenient for local development. For production operators, prefer stdin or an external secret manager over long-lived private keys in argv or checked-in deployment files.
|
|
52
|
+
|
|
53
|
+
When you scaffold from this repo checkout, `agentlink init` automatically points the generated project at local `file:` dependencies for the Biomapper packages. Add `--package-source npm` if you want npm version ranges instead.
|
|
54
|
+
|
|
55
|
+
### Step 3: Generate a linker URL
|
|
56
|
+
|
|
57
|
+
Use the guided `link` command to generate agent consent and embed it in a self-contained local linker URL:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
agentlink link --owner 0xOwner --open
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
By default, the CLI:
|
|
64
|
+
|
|
65
|
+
- derives the agent address from `AGENT_PRIVATE_KEY`
|
|
66
|
+
- uses `BIOMAPPER_NETWORK` or `base-sepolia`
|
|
67
|
+
- uses `BIOMAPPER_REGISTRY`
|
|
68
|
+
- uses `BIOMAPPER_LINKER_URL` or `http://localhost:5173/`
|
|
69
|
+
- creates a consent and link session that expire in 24 hours
|
|
70
|
+
|
|
71
|
+
Add `--json` if you want the raw consent, session payload, and final URL for automation.
|
|
72
|
+
|
|
73
|
+
This CLI flow is intended for local/dev use. Hosted production linkers should create server-stored sessions and emit canonical `buildHostedLinkUrl(...)` URLs instead of embedding the full session payload in the URL.
|
|
74
|
+
|
|
75
|
+
Important: the CLI does not submit `linkAgent(...)` itself. It prepares the agent-side consent and hands the owner off to the linker UI.
|
|
76
|
+
|
|
77
|
+
### Step 4: Complete linking in the linker
|
|
78
|
+
|
|
79
|
+
If you used `--open`, your browser opens automatically. Otherwise open the printed linker URL manually.
|
|
80
|
+
|
|
81
|
+
In the linker:
|
|
82
|
+
|
|
83
|
+
1. Connect the owner wallet
|
|
84
|
+
2. Review the pre-filled network, registry, and agent details from the hosted link
|
|
85
|
+
3. If needed, complete Biomapper verification for the current generation
|
|
86
|
+
4. Approve `linkAgent(...)`
|
|
87
|
+
|
|
88
|
+
If the owner is not biomapped in the current generation, the linker points them to the correct Biomapper app:
|
|
89
|
+
|
|
90
|
+
- Base mainnet: `https://mainnet.biomapper.hmnd.app/`
|
|
91
|
+
- Base Sepolia: `https://testnet5.biomapper.hmnd.app/`
|
|
92
|
+
|
|
93
|
+
### Step 5: Verify status
|
|
94
|
+
|
|
95
|
+
Check whether the agent is linked and currently active:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
agentlink status
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or pass flags explicitly:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
agentlink status --registry 0xRegistry --network base-sepolia --agent 0xAgent
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Possible results:
|
|
108
|
+
|
|
109
|
+
- `Not linked`: no owner is linked to the agent
|
|
110
|
+
- `Inactive`: the agent is linked, but the owner is not biomapped in the current generation
|
|
111
|
+
- `Active`: the agent is linked and the owner is biomapped in the current generation
|
|
112
|
+
|
|
113
|
+
Use `--json` for machine-readable output.
|
|
114
|
+
|
|
115
|
+
## Manual consent flow
|
|
116
|
+
|
|
117
|
+
If you do not want the CLI to create a link session URL, use the lower-level `authorize-link` command:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
agentlink authorize-link \
|
|
121
|
+
--owner 0xOwner \
|
|
122
|
+
--registry 0xRegistry \
|
|
123
|
+
--deadline 1735689600 \
|
|
124
|
+
--network base-sepolia
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This outputs the raw EIP-712 consent blob:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"type": "biomapper-agent-link",
|
|
132
|
+
"network": "base-sepolia",
|
|
133
|
+
"chainId": 84532,
|
|
134
|
+
"agent": "0xAgent",
|
|
135
|
+
"owner": "0xOwner",
|
|
136
|
+
"registry": "0xRegistry",
|
|
137
|
+
"nonce": "0",
|
|
138
|
+
"deadline": "1735689600",
|
|
139
|
+
"typedData": { "...": "..." },
|
|
140
|
+
"signature": "0x..."
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use this for advanced or fully custom platform flows. The public linker no longer expects end users to paste this JSON manually.
|
|
145
|
+
|
|
146
|
+
## Programmatic consent
|
|
147
|
+
|
|
148
|
+
If your platform manages agent wallets, generate consent directly from code:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { createAgentLinkConsent } from '@techdigger/humanode-agentlink';
|
|
152
|
+
|
|
153
|
+
const consent = await createAgentLinkConsent({
|
|
154
|
+
owner: '0xOwner',
|
|
155
|
+
registry: '0xYourRegistry',
|
|
156
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
157
|
+
network: 'base-sepolia',
|
|
158
|
+
privateKey: process.env.AGENT_PRIVATE_KEY as `0x${string}`,
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
For production code, prefer injecting the signer secret from a secret manager or stdin-equivalent runtime input rather than normalizing long-lived keys in argv.
|
|
163
|
+
|
|
164
|
+
The output matches the CLI JSON shape and can be passed into hosted link sessions.
|
|
165
|
+
|
|
166
|
+
## Hosted link sessions
|
|
167
|
+
|
|
168
|
+
Platforms can build pre-configured link URLs that skip manual setup:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { buildHostedLinkUrl, createLinkSession, validateLinkSession } from '@techdigger/humanode-agentlink';
|
|
172
|
+
|
|
173
|
+
const session = await createLinkSession(
|
|
174
|
+
{
|
|
175
|
+
platformAccountId: 'acct_123',
|
|
176
|
+
network: 'base-sepolia',
|
|
177
|
+
registry: '0xRegistry',
|
|
178
|
+
redirectUrl: 'https://platform.example.com/complete',
|
|
179
|
+
branding: { platformName: 'Atlas Cloud' },
|
|
180
|
+
consent,
|
|
181
|
+
},
|
|
182
|
+
{ store }
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const url = buildHostedLinkUrl('https://link.example.com/', session);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`buildHostedLinkUrl(...)` now emits a canonical `?sessionId=...` URL. Serve that session back to the hosted linker from the same origin with `GET /.well-known/agentlink/link-sessions/:sessionId`, returning `200 { session }` or `404/410 { error }`.
|
|
189
|
+
|
|
190
|
+
Validate stored sessions before returning them:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const stored = await store.get(sessionId);
|
|
194
|
+
const validated = validateLinkSession(stored, {
|
|
195
|
+
allowedRedirectOrigins: ['https://platform.example.com'],
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Use `buildHostedLinkUrl(...)` for hosted production linkers. For local static or demo flows, use `buildEmbeddedHostedLinkUrl(...)` instead so the full session stays self-contained in the URL.
|
|
200
|
+
|
|
201
|
+
## Programmatic status checks
|
|
202
|
+
|
|
203
|
+
For applications and agents, use the shared read-only query client:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { createBiomapperQueryClient } from '@techdigger/humanode-agentlink';
|
|
207
|
+
|
|
208
|
+
const biomapper = createBiomapperQueryClient({
|
|
209
|
+
network: 'base-sepolia',
|
|
210
|
+
registryAddress: '0xRegistry',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const status = await biomapper.checkAgentStatus({
|
|
214
|
+
agentAddress: '0xAgent',
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Generation-aware behavior
|
|
219
|
+
|
|
220
|
+
- Links persist across Biomapper generations
|
|
221
|
+
- `active` is derived from the current generation at query time
|
|
222
|
+
- If the same owner wallet becomes biomapped again later, the link reactivates automatically
|
|
223
|
+
- If the owner switches wallets, a new link is required
|
|
224
|
+
|
|
225
|
+
## CLI reference
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
agentlink <command> [options]
|
|
229
|
+
|
|
230
|
+
Commands:
|
|
231
|
+
init Scaffold a Biomapper-ready agent starter
|
|
232
|
+
link Generate consent and open a self-contained local/dev linker URL
|
|
233
|
+
authorize-link Generate a one-time EIP-712 consent blob for manual linking
|
|
234
|
+
status Check whether an agent is linked and active
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### `init`
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
agentlink init [dir] [options]
|
|
241
|
+
|
|
242
|
+
Options:
|
|
243
|
+
--template langchain | vercel-ai-sdk | mcp
|
|
244
|
+
--network base | base-sepolia (default: BIOMAPPER_NETWORK or base-sepolia)
|
|
245
|
+
--registry BiomapperAgentRegistry address (falls back to BIOMAPPER_REGISTRY)
|
|
246
|
+
--package-manager npm | pnpm | yarn | bun (default: npm)
|
|
247
|
+
--package-source npm | local (default: local when running from this repo, otherwise npm)
|
|
248
|
+
--install Install dependencies after writing files
|
|
249
|
+
--yes Use defaults and skip prompts
|
|
250
|
+
--help Show help
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### `link`
|
|
254
|
+
|
|
255
|
+
```text
|
|
256
|
+
agentlink link --owner <address> [options]
|
|
257
|
+
|
|
258
|
+
Options:
|
|
259
|
+
--owner Biomapped owner wallet address
|
|
260
|
+
--registry BiomapperAgentRegistry address (falls back to BIOMAPPER_REGISTRY)
|
|
261
|
+
--network base | base-sepolia (default: BIOMAPPER_NETWORK or base-sepolia)
|
|
262
|
+
--deadline Unix timestamp in seconds for signature and session expiry
|
|
263
|
+
--private-key Insecure/dev-only agent private key (falls back to AGENT_PRIVATE_KEY)
|
|
264
|
+
--private-key-stdin Read the agent private key from stdin instead of argv
|
|
265
|
+
--rpc-url Optional RPC URL override
|
|
266
|
+
--linker-url Linker base URL (default: BIOMAPPER_LINKER_URL or http://localhost:5173/)
|
|
267
|
+
--open Open the linker URL in the default browser
|
|
268
|
+
--json Output consent, session, and linker URL as JSON
|
|
269
|
+
--help Show help
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### `authorize-link`
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
agentlink authorize-link --owner <address> [options]
|
|
276
|
+
|
|
277
|
+
Options:
|
|
278
|
+
--owner Biomapped owner wallet address
|
|
279
|
+
--registry BiomapperAgentRegistry address (falls back to BIOMAPPER_REGISTRY)
|
|
280
|
+
--deadline Unix timestamp in seconds for signature expiry
|
|
281
|
+
--network base | base-sepolia (default: BIOMAPPER_NETWORK or base-sepolia)
|
|
282
|
+
--private-key Insecure/dev-only agent private key (falls back to AGENT_PRIVATE_KEY)
|
|
283
|
+
--private-key-stdin Read the agent private key from stdin instead of argv
|
|
284
|
+
--rpc-url Optional RPC URL override
|
|
285
|
+
--help Show help
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### `status`
|
|
289
|
+
|
|
290
|
+
```text
|
|
291
|
+
agentlink status [options]
|
|
292
|
+
|
|
293
|
+
Options:
|
|
294
|
+
--registry BiomapperAgentRegistry address (falls back to BIOMAPPER_REGISTRY)
|
|
295
|
+
--agent Agent wallet address. If omitted, derived from the provided private key input
|
|
296
|
+
--network base | base-sepolia (default: BIOMAPPER_NETWORK or base-sepolia)
|
|
297
|
+
--private-key Insecure/dev-only agent private key used to derive the agent address
|
|
298
|
+
--private-key-stdin Read the agent private key from stdin instead of argv
|
|
299
|
+
--rpc-url Optional RPC URL override
|
|
300
|
+
--json Output JSON instead of human-readable text
|
|
301
|
+
--help Show help
|
|
302
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem';
|
|
2
|
+
import { createAgentLinkConsent, type AgentLinkConsentOutput } from '@techdigger/humanode-agentlink';
|
|
3
|
+
import { type EnvSource, type NetworkName } from '../lib/core.js';
|
|
4
|
+
export interface AuthorizeLinkOptions {
|
|
5
|
+
owner: Address;
|
|
6
|
+
registry: Address;
|
|
7
|
+
deadline: bigint;
|
|
8
|
+
network: NetworkName;
|
|
9
|
+
privateKey: Hex;
|
|
10
|
+
rpcUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AuthorizeLinkOverrides {
|
|
13
|
+
privateKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthorizeLinkDeps {
|
|
16
|
+
createAgentLinkConsent: typeof createAgentLinkConsent;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseAuthorizeLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: AuthorizeLinkOverrides): AuthorizeLinkOptions;
|
|
19
|
+
export declare function runAuthorizeLink(options: AuthorizeLinkOptions, deps?: Partial<AuthorizeLinkDeps>): Promise<AgentLinkConsentOutput>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createAgentLinkConsent } from '@techdigger/humanode-agentlink';
|
|
2
|
+
import { CliError, assertNoExtraPositionals, parseFlags, parseFutureDeadline, parseRequiredAddress, resolveNetwork, resolvePrivateKey, resolveRegistryAddress, resolveRpcUrl, } from '../lib/core.js';
|
|
3
|
+
export function parseAuthorizeLinkOptions(argv, env, now, overrides = {}) {
|
|
4
|
+
const parsed = parseFlags(argv);
|
|
5
|
+
assertNoExtraPositionals('authorize-link', parsed.positionals);
|
|
6
|
+
return {
|
|
7
|
+
owner: parseRequiredAddress('owner', parsed.values.owner),
|
|
8
|
+
registry: resolveRegistryAddress(parsed.values.registry, env),
|
|
9
|
+
deadline: parseFutureDeadline(parsed.values.deadline, now),
|
|
10
|
+
network: resolveNetwork(parsed.values.network, env),
|
|
11
|
+
privateKey: resolvePrivateKey(parsed.values['private-key'], env, overrides.privateKey),
|
|
12
|
+
rpcUrl: resolveRpcUrl(parsed.values['rpc-url'], env),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export async function runAuthorizeLink(options, deps = {}) {
|
|
16
|
+
const createConsent = deps.createAgentLinkConsent ?? createAgentLinkConsent;
|
|
17
|
+
try {
|
|
18
|
+
return await createConsent({
|
|
19
|
+
owner: options.owner,
|
|
20
|
+
registry: options.registry,
|
|
21
|
+
deadline: options.deadline,
|
|
22
|
+
network: options.network,
|
|
23
|
+
privateKey: options.privateKey,
|
|
24
|
+
rpcUrl: options.rpcUrl,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
throw new CliError(error instanceof Error ? error.message : String(error));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { installProjectDependencies, type EnvSource, type GeneratedFile, type NetworkName, type PackageManager, type PackageSourceName, type Prompter, type TemplateName } from '../lib/core.js';
|
|
2
|
+
export interface InitOptions {
|
|
3
|
+
directory?: string;
|
|
4
|
+
template?: TemplateName;
|
|
5
|
+
network?: NetworkName;
|
|
6
|
+
registry?: string;
|
|
7
|
+
packageManager?: PackageManager;
|
|
8
|
+
packageSource?: PackageSourceName;
|
|
9
|
+
install: boolean;
|
|
10
|
+
yes: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface InitResult {
|
|
13
|
+
targetDir: string;
|
|
14
|
+
projectName: string;
|
|
15
|
+
packageName: string;
|
|
16
|
+
template: TemplateName;
|
|
17
|
+
network: NetworkName;
|
|
18
|
+
registry: string;
|
|
19
|
+
packageManager: PackageManager;
|
|
20
|
+
packageSource: PackageSourceName;
|
|
21
|
+
install: boolean;
|
|
22
|
+
installed: boolean;
|
|
23
|
+
files: GeneratedFile[];
|
|
24
|
+
}
|
|
25
|
+
export interface InitDeps {
|
|
26
|
+
cwd: string;
|
|
27
|
+
env: EnvSource;
|
|
28
|
+
prompter: Prompter;
|
|
29
|
+
installProjectDependencies: typeof installProjectDependencies;
|
|
30
|
+
}
|
|
31
|
+
export declare function parseInitOptions(argv: string[], env: EnvSource): InitOptions;
|
|
32
|
+
export declare function runInit(options: InitOptions, depsInput?: Partial<InitDeps>): Promise<InitResult>;
|
|
33
|
+
export declare function formatInitOutput(result: InitResult, cwd: string): string;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { buildTemplateFiles } from '../templates.js';
|
|
3
|
+
import { CliError, PACKAGE_MANAGERS, TEMPLATE_NAMES, createConsolePrompter, ensureTargetDirectoryIsWritable, getPackageManagerInstallCommand, getPackageManagerRunCommand, installProjectDependencies, parseAddressOrPlaceholder, parseFlags, resolveHumanodePackageSpecs, resolveNetwork, resolvePackageManager, resolvePackageSource, resolveTemplateName, toPackageName, writeGeneratedFiles, } from '../lib/core.js';
|
|
4
|
+
const DEFAULT_PROJECT_DIR = 'biomapper-agent';
|
|
5
|
+
const DEFAULT_REGISTRY = '0xYourRegistryDeployment';
|
|
6
|
+
export function parseInitOptions(argv, env) {
|
|
7
|
+
const parsed = parseFlags(argv);
|
|
8
|
+
if (parsed.positionals.length > 1) {
|
|
9
|
+
throw new CliError(`Unexpected arguments for init: ${parsed.positionals.slice(1).join(', ')}`);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
directory: parsed.positionals[0],
|
|
13
|
+
template: resolveTemplateName(parsed.values.template),
|
|
14
|
+
network: parsed.values.network ? resolveNetwork(parsed.values.network, env) : undefined,
|
|
15
|
+
registry: parsed.values.registry ?? env.BIOMAPPER_REGISTRY,
|
|
16
|
+
packageManager: resolvePackageManager(parsed.values['package-manager']),
|
|
17
|
+
packageSource: resolvePackageSource(parsed.values['package-source']),
|
|
18
|
+
install: parsed.flags.has('install'),
|
|
19
|
+
yes: parsed.flags.has('yes'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async function resolveInteractiveValue(current, yes, resolveDefault, resolvePrompt) {
|
|
23
|
+
if (current !== undefined)
|
|
24
|
+
return current;
|
|
25
|
+
if (yes)
|
|
26
|
+
return resolveDefault();
|
|
27
|
+
return resolvePrompt();
|
|
28
|
+
}
|
|
29
|
+
export async function runInit(options, depsInput = {}) {
|
|
30
|
+
const cwd = depsInput.cwd ?? process.cwd();
|
|
31
|
+
const env = depsInput.env ?? process.env;
|
|
32
|
+
const installDependencies = depsInput.installProjectDependencies ?? installProjectDependencies;
|
|
33
|
+
const prompter = depsInput.prompter ?? createConsolePrompter();
|
|
34
|
+
try {
|
|
35
|
+
const rawDirectory = await resolveInteractiveValue(options.directory, options.yes, () => DEFAULT_PROJECT_DIR, () => prompter.text({
|
|
36
|
+
message: 'Project directory',
|
|
37
|
+
defaultValue: DEFAULT_PROJECT_DIR,
|
|
38
|
+
}));
|
|
39
|
+
const targetDir = path.resolve(cwd, rawDirectory);
|
|
40
|
+
await ensureTargetDirectoryIsWritable(targetDir);
|
|
41
|
+
const template = await resolveInteractiveValue(options.template, options.yes, () => 'langchain', () => prompter.select({
|
|
42
|
+
message: 'Starter template',
|
|
43
|
+
options: TEMPLATE_NAMES.map(value => ({ value, label: value })),
|
|
44
|
+
defaultValue: 'langchain',
|
|
45
|
+
}));
|
|
46
|
+
const network = await resolveInteractiveValue(options.network, options.yes, () => resolveNetwork(undefined, env), () => prompter.select({
|
|
47
|
+
message: 'Biomapper network',
|
|
48
|
+
options: [
|
|
49
|
+
{ value: 'base-sepolia', label: 'base-sepolia' },
|
|
50
|
+
{ value: 'base', label: 'base' },
|
|
51
|
+
],
|
|
52
|
+
defaultValue: resolveNetwork(undefined, env),
|
|
53
|
+
}));
|
|
54
|
+
const registry = await resolveInteractiveValue(options.registry, options.yes, () => env.BIOMAPPER_REGISTRY ?? DEFAULT_REGISTRY, async () => parseAddressOrPlaceholder('registry', await prompter.text({
|
|
55
|
+
message: 'Biomapper registry address',
|
|
56
|
+
defaultValue: env.BIOMAPPER_REGISTRY ?? DEFAULT_REGISTRY,
|
|
57
|
+
}), DEFAULT_REGISTRY));
|
|
58
|
+
const packageManager = await resolveInteractiveValue(options.packageManager, options.yes, () => 'npm', () => prompter.select({
|
|
59
|
+
message: 'Package manager',
|
|
60
|
+
options: PACKAGE_MANAGERS.map(value => ({ value, label: value })),
|
|
61
|
+
defaultValue: 'npm',
|
|
62
|
+
}));
|
|
63
|
+
const packageSource = options.packageSource ?? (await resolveHumanodePackageSpecs(targetDir)).packageSource;
|
|
64
|
+
const shouldInstall = options.install
|
|
65
|
+
? true
|
|
66
|
+
: options.yes
|
|
67
|
+
? false
|
|
68
|
+
: await prompter.confirm({
|
|
69
|
+
message: 'Install dependencies now',
|
|
70
|
+
defaultValue: true,
|
|
71
|
+
});
|
|
72
|
+
const packageName = toPackageName(path.basename(targetDir));
|
|
73
|
+
const projectName = path.basename(targetDir);
|
|
74
|
+
const resolvedPackages = await resolveHumanodePackageSpecs(targetDir, packageSource);
|
|
75
|
+
const files = buildTemplateFiles({
|
|
76
|
+
projectName,
|
|
77
|
+
packageName,
|
|
78
|
+
template,
|
|
79
|
+
network,
|
|
80
|
+
registry,
|
|
81
|
+
packageManager,
|
|
82
|
+
packageSource: resolvedPackages.packageSource,
|
|
83
|
+
humanodePackages: resolvedPackages.packages,
|
|
84
|
+
});
|
|
85
|
+
await writeGeneratedFiles(targetDir, files);
|
|
86
|
+
if (shouldInstall) {
|
|
87
|
+
await installDependencies(packageManager, targetDir);
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
targetDir,
|
|
91
|
+
projectName,
|
|
92
|
+
packageName,
|
|
93
|
+
template,
|
|
94
|
+
network,
|
|
95
|
+
registry,
|
|
96
|
+
packageManager,
|
|
97
|
+
packageSource: resolvedPackages.packageSource,
|
|
98
|
+
install: shouldInstall,
|
|
99
|
+
installed: shouldInstall,
|
|
100
|
+
files,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
if (!depsInput.prompter) {
|
|
105
|
+
prompter?.close();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export function formatInitOutput(result, cwd) {
|
|
110
|
+
const relativeDir = path.relative(cwd, result.targetDir) || '.';
|
|
111
|
+
const lines = [
|
|
112
|
+
`Scaffolded ${result.template} starter in ${relativeDir}`,
|
|
113
|
+
`Network: ${result.network}`,
|
|
114
|
+
`Registry: ${result.registry}`,
|
|
115
|
+
`Package source: ${result.packageSource}`,
|
|
116
|
+
'',
|
|
117
|
+
'Next steps:',
|
|
118
|
+
];
|
|
119
|
+
if (relativeDir !== '.') {
|
|
120
|
+
lines.push(` cd ${relativeDir}`);
|
|
121
|
+
}
|
|
122
|
+
if (!result.installed) {
|
|
123
|
+
lines.push(` ${getPackageManagerInstallCommand(result.packageManager)}`);
|
|
124
|
+
}
|
|
125
|
+
lines.push(' Fill in .env.local', ` ${getPackageManagerRunCommand(result.packageManager, 'dev')}`, ` ${getPackageManagerRunCommand(result.packageManager, 'link')} -- --owner 0xOwner --open`, ` ${getPackageManagerRunCommand(result.packageManager, 'status')}`);
|
|
126
|
+
return lines.join('\n');
|
|
127
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Address, Hex } from 'viem';
|
|
2
|
+
import { createAgentLinkConsent, createLinkSession, type AgentLinkConsentOutput, type AgentLinkSession } from '@techdigger/humanode-agentlink';
|
|
3
|
+
import { type EnvSource, type NetworkName } from '../lib/core.js';
|
|
4
|
+
export interface LinkOptions {
|
|
5
|
+
owner: Address;
|
|
6
|
+
registry: Address;
|
|
7
|
+
network: NetworkName;
|
|
8
|
+
deadline: bigint;
|
|
9
|
+
privateKey: Hex;
|
|
10
|
+
rpcUrl?: string;
|
|
11
|
+
linkerUrl: string;
|
|
12
|
+
open: boolean;
|
|
13
|
+
json: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface LinkOptionOverrides {
|
|
16
|
+
privateKey?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface LinkResult {
|
|
19
|
+
agent: Address;
|
|
20
|
+
owner: Address;
|
|
21
|
+
network: NetworkName;
|
|
22
|
+
registry: Address;
|
|
23
|
+
deadline: string;
|
|
24
|
+
linkerUrl: string;
|
|
25
|
+
opened: boolean;
|
|
26
|
+
consent: AgentLinkConsentOutput;
|
|
27
|
+
session: AgentLinkSession;
|
|
28
|
+
}
|
|
29
|
+
export interface LinkDeps {
|
|
30
|
+
now: Date;
|
|
31
|
+
createAgentLinkConsent: typeof createAgentLinkConsent;
|
|
32
|
+
createLinkSession: typeof createLinkSession;
|
|
33
|
+
buildLinkUrl: (baseUrl: string, session: AgentLinkSession) => string;
|
|
34
|
+
openUrl: (url: string) => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export declare function parseLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: LinkOptionOverrides): LinkOptions;
|
|
37
|
+
export declare function runLink(options: LinkOptions, deps?: Partial<LinkDeps>): Promise<LinkResult>;
|
|
38
|
+
export declare function formatLinkOutput(result: LinkResult, json?: boolean): string;
|