@techdigger/humanode-agentlink-cli 0.3.2 → 0.3.4

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.
@@ -15,5 +15,5 @@ export interface AuthorizeLinkOverrides {
15
15
  export interface AuthorizeLinkDeps {
16
16
  createAgentLinkConsent: typeof createAgentLinkConsent;
17
17
  }
18
- export declare function parseAuthorizeLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: AuthorizeLinkOverrides): AuthorizeLinkOptions;
18
+ export declare function parseAuthorizeLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: AuthorizeLinkOverrides): Promise<AuthorizeLinkOptions>;
19
19
  export declare function runAuthorizeLink(options: AuthorizeLinkOptions, deps?: Partial<AuthorizeLinkDeps>): Promise<AgentLinkConsentOutput>;
@@ -1,13 +1,27 @@
1
+ import { isAddress } from 'viem';
1
2
  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 = {}) {
3
+ import { CliError, assertNoExtraPositionals, createConsolePrompter, defaultDeadline, parseFlags, parseFutureDeadline, parseRequiredAddress, resolveNetwork, resolvePrivateKey, resolveRegistryAddress, resolveRpcUrl, } from '../lib/core.js';
4
+ export async function parseAuthorizeLinkOptions(argv, env, now, overrides = {}) {
4
5
  const parsed = parseFlags(argv);
5
6
  assertNoExtraPositionals('authorize-link', parsed.positionals);
7
+ let ownerValue = parsed.values.owner;
8
+ if (!ownerValue) {
9
+ const prompter = createConsolePrompter();
10
+ try {
11
+ ownerValue = await prompter.text({ message: 'Owner wallet address (biomapped)' });
12
+ }
13
+ finally {
14
+ prompter.close();
15
+ }
16
+ }
17
+ if (!isAddress(ownerValue)) {
18
+ throw new CliError(`Invalid --owner. Expected a checksummed or lowercase EVM address.`);
19
+ }
6
20
  const network = resolveNetwork(parsed.values.network, env);
7
21
  return {
8
- owner: parseRequiredAddress('owner', parsed.values.owner),
22
+ owner: ownerValue,
9
23
  registry: resolveRegistryAddress(parsed.values.registry, env, network),
10
- deadline: parseFutureDeadline(parsed.values.deadline, now),
24
+ deadline: parsed.values.deadline ? parseFutureDeadline(parsed.values.deadline, now) : defaultDeadline(now, 1),
11
25
  network,
12
26
  privateKey: resolvePrivateKey(parsed.values['private-key'], env, overrides.privateKey),
13
27
  rpcUrl: resolveRpcUrl(parsed.values['rpc-url'], env),
@@ -1,8 +1,7 @@
1
1
  import path from 'node:path';
2
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';
3
+ import { CliError, NETWORKS, PACKAGE_MANAGERS, TEMPLATE_NAMES, createConsolePrompter, ensureTargetDirectoryIsWritable, getPackageManagerInstallCommand, getPackageManagerRunCommand, installProjectDependencies, parseAddressOrPlaceholder, parseFlags, resolveHumanodePackageSpecs, resolveNetwork, resolvePackageManager, resolvePackageSource, resolveTemplateName, toPackageName, writeGeneratedFiles, } from '../lib/core.js';
4
4
  const DEFAULT_PROJECT_DIR = 'biomapper-agent';
5
- const DEFAULT_REGISTRY = '0xYourRegistryDeployment';
6
5
  export function parseInitOptions(argv, env) {
7
6
  const parsed = parseFlags(argv);
8
7
  if (parsed.positionals.length > 1) {
@@ -52,10 +51,11 @@ export async function runInit(options, depsInput = {}) {
52
51
  ],
53
52
  defaultValue: resolveNetwork(undefined, env),
54
53
  }));
55
- const registry = await resolveInteractiveValue(options.registry, options.yes, () => env.BIOMAPPER_REGISTRY ?? DEFAULT_REGISTRY, async () => parseAddressOrPlaceholder('registry', await getPrompter().text({
54
+ const networkDefaultRegistry = NETWORKS[network].defaultRegistry;
55
+ const registry = await resolveInteractiveValue(options.registry, options.yes, () => env.BIOMAPPER_REGISTRY ?? networkDefaultRegistry, async () => parseAddressOrPlaceholder('registry', await getPrompter().text({
56
56
  message: 'Biomapper registry address',
57
- defaultValue: env.BIOMAPPER_REGISTRY ?? DEFAULT_REGISTRY,
58
- }), DEFAULT_REGISTRY));
57
+ defaultValue: env.BIOMAPPER_REGISTRY ?? networkDefaultRegistry,
58
+ }), networkDefaultRegistry));
59
59
  const packageManager = await resolveInteractiveValue(options.packageManager, options.yes, () => 'npm', () => getPrompter().select({
60
60
  message: 'Package manager',
61
61
  options: PACKAGE_MANAGERS.map(value => ({ value, label: value })),
@@ -33,6 +33,6 @@ export interface LinkDeps {
33
33
  buildLinkUrl: (baseUrl: string, session: AgentLinkSession) => string;
34
34
  openUrl: (url: string) => Promise<void>;
35
35
  }
36
- export declare function parseLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: LinkOptionOverrides): LinkOptions;
36
+ export declare function parseLinkOptions(argv: string[], env: EnvSource, now: Date, overrides?: LinkOptionOverrides): Promise<LinkOptions>;
37
37
  export declare function runLink(options: LinkOptions, deps?: Partial<LinkDeps>): Promise<LinkResult>;
38
38
  export declare function formatLinkOutput(result: LinkResult, json?: boolean): string;
@@ -1,11 +1,25 @@
1
+ import { isAddress } from 'viem';
1
2
  import { buildEmbeddedHostedLinkUrl, createAgentLinkConsent, createLinkSession, } from '@techdigger/humanode-agentlink';
2
- import { CliError, NETWORKS, assertNoExtraPositionals, defaultDeadline, formatJson, parseFlags, parseFutureDeadline, parseRequiredAddress, resolveLinkerUrl, resolveNetwork, resolvePrivateKey, resolveRegistryAddress, resolveRpcUrl, openUrlInBrowser, } from '../lib/core.js';
3
- export function parseLinkOptions(argv, env, now, overrides = {}) {
3
+ import { CliError, NETWORKS, assertNoExtraPositionals, createConsolePrompter, defaultDeadline, formatJson, parseFlags, parseFutureDeadline, resolveLinkerUrl, resolveNetwork, resolvePrivateKey, resolveRegistryAddress, resolveRpcUrl, openUrlInBrowser, } from '../lib/core.js';
4
+ export async function parseLinkOptions(argv, env, now, overrides = {}) {
4
5
  const parsed = parseFlags(argv);
5
6
  assertNoExtraPositionals('link', parsed.positionals);
7
+ let ownerValue = parsed.values.owner;
8
+ if (!ownerValue) {
9
+ const prompter = createConsolePrompter();
10
+ try {
11
+ ownerValue = await prompter.text({ message: 'Owner wallet address (biomapped)' });
12
+ }
13
+ finally {
14
+ prompter.close();
15
+ }
16
+ }
17
+ if (!isAddress(ownerValue)) {
18
+ throw new CliError(`Invalid --owner. Expected a checksummed or lowercase EVM address.`);
19
+ }
6
20
  const network = resolveNetwork(parsed.values.network, env);
7
21
  return {
8
- owner: parseRequiredAddress('owner', parsed.values.owner),
22
+ owner: ownerValue,
9
23
  registry: resolveRegistryAddress(parsed.values.registry, env, network),
10
24
  network,
11
25
  deadline: parsed.values.deadline ? parseFutureDeadline(parsed.values.deadline, now) : defaultDeadline(now),
@@ -0,0 +1,23 @@
1
+ import type { Hex } from 'viem';
2
+ import { type EnvSource, type NetworkName } from '../lib/core.js';
3
+ export interface RequestOptions {
4
+ url: string;
5
+ method: string;
6
+ network: NetworkName;
7
+ count: number;
8
+ json: boolean;
9
+ privateKey: Hex;
10
+ }
11
+ export interface RequestResult {
12
+ request: number;
13
+ status: number;
14
+ body: string;
15
+ }
16
+ export interface RequestDeps {
17
+ fetchImpl: typeof fetch;
18
+ }
19
+ export declare function parseRequestOptions(argv: string[], env: EnvSource, overrides?: {
20
+ privateKey?: string;
21
+ }): RequestOptions;
22
+ export declare function runRequest(options: RequestOptions, deps?: Partial<RequestDeps>): Promise<RequestResult[]>;
23
+ export declare function formatRequestOutput(results: RequestResult[], json: boolean): string;
@@ -0,0 +1,89 @@
1
+ import { SiweMessage } from 'siwe';
2
+ import { privateKeyToAccount } from 'viem/accounts';
3
+ import { CliError, NETWORKS, assertNoExtraPositionals, parseFlags, resolveNetwork, resolvePrivateKey, } from '../lib/core.js';
4
+ export function parseRequestOptions(argv, env, overrides = {}) {
5
+ const parsed = parseFlags(argv);
6
+ if (parsed.positionals.length === 0) {
7
+ throw new CliError('Missing URL. Usage: agentlink request <url> [options]');
8
+ }
9
+ assertNoExtraPositionals('request', parsed.positionals.slice(1));
10
+ const url = parsed.positionals[0];
11
+ try {
12
+ new URL(url);
13
+ }
14
+ catch {
15
+ throw new CliError(`Invalid URL: "${url}"`);
16
+ }
17
+ const countRaw = parsed.values.count;
18
+ const count = countRaw ? parseInt(countRaw, 10) : 1;
19
+ if (isNaN(count) || count < 1) {
20
+ throw new CliError(`--count must be a positive integer, got "${countRaw}"`);
21
+ }
22
+ const network = resolveNetwork(parsed.values.network, env);
23
+ return {
24
+ url,
25
+ method: (parsed.values.method ?? 'GET').toUpperCase(),
26
+ network,
27
+ count,
28
+ json: parsed.flags.has('json'),
29
+ privateKey: resolvePrivateKey(parsed.values['private-key'], env, overrides.privateKey),
30
+ };
31
+ }
32
+ async function buildAgentLinkHeader(privateKey, url, network) {
33
+ const account = privateKeyToAccount(privateKey);
34
+ const parsed = new URL(url);
35
+ const chainId = NETWORKS[network].chainId;
36
+ const nonce = Math.random().toString(36).slice(2) + Date.now().toString(36);
37
+ const issuedAt = new Date().toISOString();
38
+ const siwe = new SiweMessage({
39
+ domain: parsed.hostname,
40
+ address: account.address,
41
+ uri: url,
42
+ version: '1',
43
+ chainId,
44
+ nonce,
45
+ issuedAt,
46
+ statement: 'Agentlink access request',
47
+ });
48
+ const message = siwe.prepareMessage();
49
+ const signature = await account.signMessage({ message });
50
+ const payload = {
51
+ domain: parsed.hostname,
52
+ address: account.address,
53
+ statement: 'Agentlink access request',
54
+ uri: url,
55
+ version: '1',
56
+ chainId: `eip155:${chainId}`,
57
+ type: 'eip191',
58
+ nonce,
59
+ issuedAt,
60
+ signature,
61
+ };
62
+ return Buffer.from(JSON.stringify(payload)).toString('base64');
63
+ }
64
+ export async function runRequest(options, deps = {}) {
65
+ const fetchImpl = deps.fetchImpl ?? fetch;
66
+ const results = [];
67
+ for (let i = 1; i <= options.count; i++) {
68
+ const agentlinkHeader = await buildAgentLinkHeader(options.privateKey, options.url, options.network);
69
+ let response;
70
+ try {
71
+ response = await fetchImpl(new Request(options.url, {
72
+ method: options.method,
73
+ headers: { agentlink: agentlinkHeader },
74
+ }));
75
+ }
76
+ catch (error) {
77
+ throw new CliError(`Request ${i} failed: ${error instanceof Error ? error.message : String(error)}`);
78
+ }
79
+ const body = await response.text();
80
+ results.push({ request: i, status: response.status, body });
81
+ }
82
+ return results;
83
+ }
84
+ export function formatRequestOutput(results, json) {
85
+ if (json) {
86
+ return JSON.stringify(results, null, 2);
87
+ }
88
+ return results.map(r => `Request ${r.request} ${r.status} ${r.body.slice(0, 200)}`).join('\n');
89
+ }
package/dist/help.d.ts CHANGED
@@ -3,4 +3,5 @@ export declare function getKeystoreHelpText(): string;
3
3
  export declare function getAuthorizeLinkHelpText(): string;
4
4
  export declare function getStatusHelpText(): string;
5
5
  export declare function getLinkHelpText(): string;
6
+ export declare function getRequestHelpText(): string;
6
7
  export declare function getInitHelpText(): string;
package/dist/help.js CHANGED
@@ -7,6 +7,7 @@ Commands:
7
7
  link Generate consent and open a linker URL for owner approval
8
8
  authorize-link Generate a one-time EIP-712 consent blob for manual linking
9
9
  status Check whether an agent is linked and active
10
+ request Send a signed agentlink request to a protected endpoint
10
11
 
11
12
  Flags:
12
13
  --help, -h Show this help text
@@ -108,6 +109,32 @@ Options:
108
109
  --help Show this help message
109
110
  `;
110
111
  }
112
+ export function getRequestHelpText() {
113
+ return `Usage:
114
+ agentlink request <url> [options]
115
+
116
+ Description:
117
+ Send a signed agentlink request to a URL. Builds a SIWE payload signed with
118
+ your agent wallet, attaches it as the "agentlink" header, and prints the
119
+ response. Use --count to repeat the request (e.g. to test free-trial limits).
120
+
121
+ Options:
122
+ --method HTTP method (default: GET)
123
+ --count Number of times to send the request (default: 1)
124
+ --network base | base-sepolia (default: BIOMAPPER_NETWORK or base-sepolia)
125
+ --private-key-stdin Read the agent private key from stdin
126
+ --private-key Dev-only: pass key directly as 0x-prefixed hex
127
+ --json Output results as JSON
128
+ --help Show this help message
129
+
130
+ Key resolution order: keystore (default) → --private-key-stdin → --private-key → AGENT_PRIVATE_KEY env
131
+
132
+ Examples:
133
+ agentlink request http://localhost:3000/data
134
+ agentlink request http://75.119.141.48:3000/data --count 4
135
+ agentlink request https://api.example.com/resource --method POST --network base
136
+ `;
137
+ }
111
138
  export function getInitHelpText() {
112
139
  return `Usage:
113
140
  agentlink init [dir] [options]
package/dist/index.d.ts CHANGED
@@ -13,5 +13,6 @@ export interface CliRuntime {
13
13
  openUrl: (url: string) => Promise<void>;
14
14
  readStdin: () => Promise<string>;
15
15
  readPassword: (prompt: string) => Promise<string>;
16
+ fetchImpl: typeof fetch;
16
17
  }
17
18
  export declare function runCli(argv?: string[], overrides?: Partial<CliRuntime>): Promise<number>;
package/dist/index.js CHANGED
@@ -6,10 +6,11 @@ import { formatInitOutput, parseInitOptions, runInit } from './commands/init.js'
6
6
  import { formatLinkOutput, parseLinkOptions, runLink } from './commands/link.js';
7
7
  import { formatStatusOutput, parseStatusOptions, runStatus } from './commands/status.js';
8
8
  import { parseAuthorizeLinkOptions, runAuthorizeLink } from './commands/authorize-link.js';
9
+ import { formatRequestOutput, parseRequestOptions, runRequest } from './commands/request.js';
9
10
  import { runKeystoreAddress, runKeystoreDelete, runKeystoreSet } from './commands/keystore.js';
10
11
  import { CliError, formatJson, loadEnvFiles, openUrlInBrowser, parseFlags, readAllFromStdin, readPassword } from './lib/core.js';
11
12
  import { decryptKeystore, keystoreExists, keystorePath } from './lib/keystore.js';
12
- import { getAuthorizeLinkHelpText, getInitHelpText, getKeystoreHelpText, getLinkHelpText, getRootHelpText, getStatusHelpText, } from './help.js';
13
+ import { getAuthorizeLinkHelpText, getInitHelpText, getKeystoreHelpText, getLinkHelpText, getRequestHelpText, getRootHelpText, getStatusHelpText, } from './help.js';
13
14
  import { VERSION } from './version.js';
14
15
  import { buildEmbeddedHostedLinkUrl, createAgentLinkConsent, createBiomapperQueryClient, createLinkSession, } from '@techdigger/humanode-agentlink';
15
16
  function writeLine(write, text) {
@@ -34,6 +35,7 @@ export async function runCli(argv = process.argv.slice(2), overrides = {}) {
34
35
  openUrl: overrides.openUrl ?? openUrlInBrowser,
35
36
  readStdin: overrides.readStdin ?? readAllFromStdin,
36
37
  readPassword: overrides.readPassword ?? readPassword,
38
+ fetchImpl: overrides.fetchImpl ?? fetch,
37
39
  };
38
40
  const [command, ...args] = argv;
39
41
  try {
@@ -83,7 +85,7 @@ export async function runCli(argv = process.argv.slice(2), overrides = {}) {
83
85
  return 0;
84
86
  }
85
87
  const authorizePrivateKey = await readPrivateKeyFromStdin(args, runtime);
86
- writeLine(runtime.stdout, formatJson(await runAuthorizeLink(parseAuthorizeLinkOptions(args, runtime.env, runtime.now, {
88
+ writeLine(runtime.stdout, formatJson(await runAuthorizeLink(await parseAuthorizeLinkOptions(args, runtime.env, runtime.now, {
87
89
  privateKey: authorizePrivateKey,
88
90
  }), {
89
91
  createAgentLinkConsent: runtime.createAgentLinkConsent,
@@ -136,7 +138,7 @@ export async function runCli(argv = process.argv.slice(2), overrides = {}) {
136
138
  return 0;
137
139
  }
138
140
  const linkPrivateKey = await readPrivateKeyFromStdin(args, runtime);
139
- const options = parseLinkOptions(args, runtime.env, runtime.now, {
141
+ const options = await parseLinkOptions(args, runtime.env, runtime.now, {
140
142
  privateKey: linkPrivateKey,
141
143
  });
142
144
  const result = await runLink(options, {
@@ -183,6 +185,17 @@ export async function runCli(argv = process.argv.slice(2), overrides = {}) {
183
185
  }, runtime.env);
184
186
  return 0;
185
187
  }
188
+ case 'request': {
189
+ if (args.includes('--help') || args.includes('-h')) {
190
+ writeLine(runtime.stdout, getRequestHelpText());
191
+ return 0;
192
+ }
193
+ const requestPrivateKey = await readPrivateKeyFromStdin(args, runtime);
194
+ const requestOptions = parseRequestOptions(args, runtime.env, { privateKey: requestPrivateKey });
195
+ const requestResults = await runRequest(requestOptions, { fetchImpl: runtime.fetchImpl });
196
+ writeLine(runtime.stdout, formatRequestOutput(requestResults, requestOptions.json));
197
+ return 0;
198
+ }
186
199
  default:
187
200
  throw new CliError(`Unknown command "${command}". Run "agentlink --help" for available commands.`);
188
201
  }
@@ -4,11 +4,13 @@ export declare const NETWORKS: {
4
4
  readonly label: "Base mainnet";
5
5
  readonly biomapperAppUrl: string;
6
6
  readonly defaultRegistry: Address;
7
+ readonly chainId: 8453;
7
8
  };
8
9
  readonly 'base-sepolia': {
9
10
  readonly label: "Base Sepolia";
10
11
  readonly biomapperAppUrl: string;
11
12
  readonly defaultRegistry: Address;
13
+ readonly chainId: 84532;
12
14
  };
13
15
  };
14
16
  export type NetworkName = keyof typeof NETWORKS;
package/dist/lib/core.js CHANGED
@@ -12,11 +12,13 @@ export const NETWORKS = {
12
12
  label: 'Base mainnet',
13
13
  biomapperAppUrl: BIOMAPPER_APP_URLS.base,
14
14
  defaultRegistry: '0x', // populated at mainnet launch
15
+ chainId: 8453,
15
16
  },
16
17
  'base-sepolia': {
17
18
  label: 'Base Sepolia',
18
19
  biomapperAppUrl: BIOMAPPER_APP_URLS['base-sepolia'],
19
20
  defaultRegistry: '0xc0fb26BaACe7E1BCb3aFFD547AD5f2cAc4A4F51b',
21
+ chainId: 84532,
20
22
  },
21
23
  };
22
24
  export const TEMPLATE_NAMES = ['langchain', 'vercel-ai-sdk', 'mcp'];
package/dist/templates.js CHANGED
@@ -7,16 +7,11 @@ const MCP_SDK_VERSION = '^1.12.1';
7
7
  const DOTENV_VERSION = '^17.3.1';
8
8
  const TSX_VERSION = '^4.19.4';
9
9
  const TYPESCRIPT_VERSION = '^5.8.3';
10
- function buildCommonEnv(input, includeOpenAI) {
10
+ function buildCommonEnv(input, _includeOpenAI) {
11
11
  const lines = [
12
- `AGENT_PRIVATE_KEY=0xyouragentprivatekey`,
13
12
  `BIOMAPPER_NETWORK=${input.network}`,
14
13
  `BIOMAPPER_REGISTRY=${input.registry}`,
15
- `BIOMAPPER_LINKER_URL=http://localhost:5173/`,
16
14
  ];
17
- if (includeOpenAI) {
18
- lines.unshift('OPENAI_API_KEY=your-openai-api-key', 'OPENAI_MODEL=gpt-4.1-mini');
19
- }
20
15
  return `${lines.join('\n')}\n`;
21
16
  }
22
17
  function buildGitIgnore() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techdigger/humanode-agentlink-cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Scaffold, link, and inspect Biomapper-ready agent projects.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,8 +23,9 @@
23
23
  "cli": "tsx src/index.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@techdigger/humanode-agentlink": "^0.2.0",
26
+ "@techdigger/humanode-agentlink": "^0.3.0",
27
27
  "posthog-node": "^5.21.2",
28
+ "siwe": "^2.3.2",
28
29
  "viem": "^2.46.2"
29
30
  },
30
31
  "keywords": [