@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 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).
@@ -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;