@hightop/cli 0.1.3 → 0.1.5

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 CHANGED
@@ -85,6 +85,8 @@ Examples:
85
85
  hightop self --pretty
86
86
  hightop balances --pretty
87
87
  hightop operations get <operation-id> --include-onchain --pretty
88
+ hightop borrow collateral-options --pretty
89
+ hightop borrow collateral add --asset <option-id> --amount-usd 25 --simulate --pretty
88
90
  hightop borrow repay --asset GREEN --amount-usd 25 --simulate --pretty
89
91
  hightop one-off-payments create --to 0x... --asset USDC --amount-usd 25 --simulate --pretty
90
92
  hightop webhooks create --url https://example.com/hook --event-types payment.executed,webhook.test --pretty
@@ -94,6 +96,8 @@ hightop raw GET /v1/agent/openapi.json --pretty
94
96
 
95
97
  The generated command layer supports all catalogued `/v1/agent/*` endpoints. CLI flags are generated from endpoint query/body field names by converting underscores to dashes, for example `amount_usd` becomes `--amount-usd`.
96
98
 
99
+ For collateral, fetch `hightop borrow collateral-options` first and pass the returned option `id` as `--asset` to `hightop borrow collateral add`. Vault-backed options use the vault contract address as the option id.
100
+
97
101
  For complex bodies, pass a JSON object:
98
102
 
99
103
  ```sh
package/dist/cli.js CHANGED
@@ -2,6 +2,8 @@ import { readFileSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { agentApiEndpoints, buildAgentApiPath, createIdempotencyKey, HightopAgentClient, HightopAgentOperationWaitTimeoutError, HightopAgentSDKError, } from '@hightop/sdk';
5
+ import { buildCliEndpointCommands, commandDisplayName, endpointCanSimulate as commandEndpointCanSimulate, endpointCanWait, KNOWN_BOOLEAN_REQUEST_FIELDS, requestFieldNames, } from './commandMetadata.js';
6
+ import { SIMULATABLE_ENDPOINT_KEYS } from './simulatableEndpointKeys.js';
5
7
  class CliUsageError extends Error {
6
8
  constructor(message) {
7
9
  super(message);
@@ -22,18 +24,6 @@ class CliWriteError extends Error {
22
24
  this.response = input.response;
23
25
  }
24
26
  }
25
- const KNOWN_BOOLEAN_REQUEST_FIELDS = [
26
- 'allow_conversion',
27
- 'allow_partial',
28
- 'enabled',
29
- 'move_all',
30
- 'remove_all',
31
- 'repay_all',
32
- 'to_best_available',
33
- 'use_available_cash_first',
34
- 'use_best_available',
35
- 'withdraw_all',
36
- ];
37
27
  const BOOLEAN_REQUEST_FIELDS = new Set(KNOWN_BOOLEAN_REQUEST_FIELDS);
38
28
  const NULLABLE_REQUEST_FIELDS = new Set(['body', 'description', 'note']);
39
29
  const BOOLEAN_FLAGS = new Set([
@@ -48,24 +38,6 @@ const BOOLEAN_FLAGS = new Set([
48
38
  ]);
49
39
  const PACKAGE_JSON_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
50
40
  const PUBLIC_NO_AUTH_CLI_ENDPOINT_KEYS = new Set(['GET /v1/agent/openapi.json', 'GET /v1/agent/capabilities.json']);
51
- // Mirrors SIMULATED_WRITE_ROUTES in src/backend/agentApi/simulate.ts.
52
- const SIMULATABLE_ENDPOINT_KEYS = new Set([
53
- 'POST /v1/agent/payments',
54
- 'POST /v1/agent/one-off-payments',
55
- 'POST /v1/agent/conversions/quote',
56
- 'POST /v1/agent/conversions',
57
- 'POST /v1/agent/earn/deposit',
58
- 'POST /v1/agent/earn/withdraw',
59
- 'POST /v1/agent/earn/move',
60
- 'POST /v1/agent/earn/rewards/claim',
61
- 'POST /v1/agent/borrow',
62
- 'POST /v1/agent/borrow/repay',
63
- 'POST /v1/agent/borrow/deleverage',
64
- 'POST /v1/agent/borrow/collateral/add',
65
- 'POST /v1/agent/borrow/collateral/remove',
66
- 'POST /v1/agent/withdrawals/to-bank',
67
- 'POST /v1/agent/withdrawals/to-crypto',
68
- ]);
69
41
  function readCliVersion() {
70
42
  return JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8')).version;
71
43
  }
@@ -118,125 +90,11 @@ function hasFlag(parsed, name) {
118
90
  function isRecord(value) {
119
91
  return typeof value === 'object' && value !== null && !Array.isArray(value);
120
92
  }
121
- function endpointSegments(endpoint) {
122
- return endpoint.path
123
- .replace(/^\/v1\/agent\/?/, '')
124
- .split('/')
125
- .filter(Boolean);
126
- }
127
- function endpointPathParamsAfterCommand(endpoint) {
128
- return endpoint.pathParams.filter((param) => !endpoint.queryParams.includes(param) && !endpoint.bodyParams.includes(param));
129
- }
130
- function endpointCanWait(endpoint) {
131
- return ['AgentApiWriteResponse', 'AgentApiX402SignResponse', 'AgentApiX402PurchaseResponse'].includes(endpoint.responseType ?? '');
132
- }
93
+ const CLI_ENDPOINT_COMMANDS = buildCliEndpointCommands(agentApiEndpoints, {
94
+ simulatableEndpointKeys: SIMULATABLE_ENDPOINT_KEYS,
95
+ });
133
96
  function endpointCanSimulate(endpoint) {
134
- return SIMULATABLE_ENDPOINT_KEYS.has(endpoint.key);
135
- }
136
- function commandUsage(commandPath, endpoint, pathParamsAfterCommand) {
137
- const pathSuffix = pathParamsAfterCommand.map((param) => ` <${param}>`).join('');
138
- const flags = [...endpoint.queryParams, ...endpoint.bodyParams]
139
- .filter((param) => !endpoint.pathParams.includes(param))
140
- .map((param) => ` [--${param.replace(/_/g, '-')} <value>]`)
141
- .join('');
142
- const waitFlag = endpointCanWait(endpoint) ? ' [--wait]' : '';
143
- const simulateFlag = endpointCanSimulate(endpoint) ? ' [--simulate]' : '';
144
- const submitFlags = endpoint.idempotencyRequired ? ` [--idempotency-key <key>]${waitFlag}${simulateFlag}` : '';
145
- return `hightop ${commandPath.join(' ')}${pathSuffix}${flags}${submitFlags}`;
146
- }
147
- function commandFromEndpoint(endpoint) {
148
- const segments = endpointSegments(endpoint);
149
- const pathParamsAfterCommand = endpointPathParamsAfterCommand(endpoint);
150
- const staticSegments = segments.filter((segment) => !segment.startsWith('{'));
151
- if (endpoint.path === '/v1/agent/capabilities.json') {
152
- return { commandPath: ['capabilities', 'json'], pathParamsAfterCommand };
153
- }
154
- if (endpoint.method === 'GET' && endpoint.pathParams.length === 0) {
155
- return { commandPath: staticSegments, pathParamsAfterCommand };
156
- }
157
- if (endpoint.method === 'GET' && endpoint.pathParams.length > 0) {
158
- const tail = staticSegments.slice(1);
159
- if (tail.length > 0) {
160
- return { commandPath: [staticSegments[0], ...tail], pathParamsAfterCommand };
161
- }
162
- return { commandPath: [staticSegments[0], 'get'], pathParamsAfterCommand };
163
- }
164
- if (endpoint.method === 'POST') {
165
- if (endpoint.pathParams.length > 0) {
166
- return { commandPath: staticSegments, pathParamsAfterCommand };
167
- }
168
- if (staticSegments.length === 1) {
169
- if (staticSegments[0] === 'conversions') {
170
- return { commandPath: ['conversions', 'execute'], pathParamsAfterCommand };
171
- }
172
- return { commandPath: [staticSegments[0], 'create'], pathParamsAfterCommand };
173
- }
174
- return { commandPath: staticSegments, pathParamsAfterCommand };
175
- }
176
- if (endpoint.method === 'PATCH') {
177
- return { commandPath: [staticSegments[0], 'update'], pathParamsAfterCommand };
178
- }
179
- if (endpoint.method === 'DELETE') {
180
- return { commandPath: [staticSegments[0], 'delete'], pathParamsAfterCommand };
181
- }
182
- return { commandPath: staticSegments, pathParamsAfterCommand };
183
- }
184
- function buildCliEndpointCommands() {
185
- const commands = agentApiEndpoints
186
- .filter((endpoint) => endpoint.key !== 'POST /v1/agent/simulate')
187
- .flatMap((endpoint) => {
188
- const primary = commandFromEndpoint(endpoint);
189
- const aliases = [primary];
190
- if (endpoint.key === 'GET /v1/agent/self/usage') {
191
- aliases.push({ commandPath: ['usage'], pathParamsAfterCommand: [] });
192
- }
193
- if (endpoint.method === 'GET' &&
194
- endpoint.pathParams.length === 0 &&
195
- endpoint.queryParams.some((param) => param === 'cursor' || param === 'limit')) {
196
- aliases.push({ commandPath: [...primary.commandPath, 'list'], pathParamsAfterCommand: [] });
197
- }
198
- return aliases.map((alias) => ({
199
- commandPath: alias.commandPath,
200
- endpoint,
201
- pathParamsAfterCommand: alias.pathParamsAfterCommand,
202
- usage: commandUsage(alias.commandPath, endpoint, alias.pathParamsAfterCommand),
203
- }));
204
- });
205
- commands.push({
206
- commandPath: ['openapi'],
207
- endpoint: {
208
- bodyParams: [],
209
- bodySchema: null,
210
- description: 'Return the public Agent API OpenAPI document.',
211
- idempotencyRequired: false,
212
- key: 'GET /v1/agent/openapi.json',
213
- method: 'GET',
214
- path: '/v1/agent/openapi.json',
215
- pathParams: [],
216
- queryParams: [],
217
- requestSchema: null,
218
- requestType: null,
219
- responseSchema: null,
220
- responseType: null,
221
- routeClass: 'read',
222
- },
223
- pathParamsAfterCommand: [],
224
- usage: 'hightop openapi',
225
- });
226
- const seen = new Set();
227
- return commands.filter((command) => {
228
- const key = command.commandPath.join(' ');
229
- if (seen.has(key)) {
230
- return false;
231
- }
232
- seen.add(key);
233
- return true;
234
- });
235
- }
236
- const CLI_ENDPOINT_COMMANDS = buildCliEndpointCommands();
237
- function commandDisplayName(command) {
238
- const suffix = command.pathParamsAfterCommand.map((param) => ` <${param}>`).join('');
239
- return `${command.commandPath.join(' ')}${suffix}`;
97
+ return commandEndpointCanSimulate(endpoint, SIMULATABLE_ENDPOINT_KEYS);
240
98
  }
241
99
  function formatGlobalHelp() {
242
100
  const commandLines = CLI_ENDPOINT_COMMANDS.map((command) => ({
@@ -292,7 +150,7 @@ function formatCommandHelp(commandPath) {
292
150
  const exact = matches.find((command) => command.commandPath.length === commandPath.length);
293
151
  if (exact && matches.length === 1) {
294
152
  const endpoint = exact.endpoint;
295
- const requestFlags = [...endpoint.queryParams, ...endpoint.bodyParams].filter((param) => !endpoint.pathParams.includes(param));
153
+ const requestFlags = requestFieldNames(endpoint);
296
154
  const flagLines = requestFlags.length > 0
297
155
  ? `\nRequest flags:\n${requestFlags.map((param) => ` --${param.replace(/_/g, '-')} <value>`).join('\n')}\n`
298
156
  : '';
@@ -0,0 +1,37 @@
1
+ export type CliEndpointMetadata = {
2
+ readonly key: string;
3
+ readonly method: string;
4
+ readonly path: string;
5
+ readonly pathParams: readonly string[];
6
+ readonly queryParams: readonly string[];
7
+ readonly bodyParams: readonly string[];
8
+ readonly requestSchema: string | null;
9
+ readonly bodySchema: string | null;
10
+ readonly responseSchema: string | null;
11
+ readonly requestType: string | null;
12
+ readonly responseType: string | null;
13
+ readonly idempotencyRequired: boolean;
14
+ readonly routeClass: string;
15
+ readonly description: string;
16
+ };
17
+ export type CliEndpointCommand = {
18
+ commandPath: string[];
19
+ endpoint: CliEndpointMetadata;
20
+ pathParamsAfterCommand: string[];
21
+ requestFlags: string[];
22
+ usage: string;
23
+ canSimulate: boolean;
24
+ canWait: boolean;
25
+ };
26
+ type BuildCliEndpointCommandOptions = {
27
+ simulatableEndpointKeys: ReadonlySet<string>;
28
+ };
29
+ export declare const KNOWN_BOOLEAN_REQUEST_FIELDS: readonly ["allow_conversion", "allow_partial", "enabled", "move_all", "remove_all", "repay_all", "to_best_available", "use_available_cash_first", "use_best_available", "withdraw_all"];
30
+ export declare function flagNameForField(field: string): string;
31
+ export declare function requestFieldNames(endpoint: CliEndpointMetadata): string[];
32
+ export declare function requestFlags(endpoint: CliEndpointMetadata): string[];
33
+ export declare function endpointCanWait(endpoint: CliEndpointMetadata): boolean;
34
+ export declare function endpointCanSimulate(endpoint: CliEndpointMetadata, simulatableEndpointKeys: ReadonlySet<string>): boolean;
35
+ export declare function commandDisplayName(command: Pick<CliEndpointCommand, 'commandPath' | 'pathParamsAfterCommand'>): string;
36
+ export declare function buildCliEndpointCommands(metadata: readonly CliEndpointMetadata[], options: BuildCliEndpointCommandOptions): CliEndpointCommand[];
37
+ export {};
@@ -0,0 +1,151 @@
1
+ export const KNOWN_BOOLEAN_REQUEST_FIELDS = [
2
+ 'allow_conversion',
3
+ 'allow_partial',
4
+ 'enabled',
5
+ 'move_all',
6
+ 'remove_all',
7
+ 'repay_all',
8
+ 'to_best_available',
9
+ 'use_available_cash_first',
10
+ 'use_best_available',
11
+ 'withdraw_all',
12
+ ];
13
+ export function flagNameForField(field) {
14
+ return field.replace(/_/g, '-');
15
+ }
16
+ export function requestFieldNames(endpoint) {
17
+ return [...endpoint.queryParams, ...endpoint.bodyParams].filter((param) => !endpoint.pathParams.includes(param));
18
+ }
19
+ export function requestFlags(endpoint) {
20
+ return requestFieldNames(endpoint).map((param) => `--${flagNameForField(param)}`);
21
+ }
22
+ export function endpointCanWait(endpoint) {
23
+ return ['AgentApiWriteResponse', 'AgentApiX402SignResponse', 'AgentApiX402PurchaseResponse'].includes(endpoint.responseType ?? '');
24
+ }
25
+ export function endpointCanSimulate(endpoint, simulatableEndpointKeys) {
26
+ return simulatableEndpointKeys.has(endpoint.key);
27
+ }
28
+ export function commandDisplayName(command) {
29
+ const suffix = command.pathParamsAfterCommand.map((param) => ` <${param}>`).join('');
30
+ return `${command.commandPath.join(' ')}${suffix}`;
31
+ }
32
+ function endpointSegments(endpoint) {
33
+ return endpoint.path
34
+ .replace(/^\/v1\/agent\/?/, '')
35
+ .split('/')
36
+ .filter(Boolean);
37
+ }
38
+ function endpointPathParamsAfterCommand(endpoint) {
39
+ return endpoint.pathParams.filter((param) => !endpoint.queryParams.includes(param) && !endpoint.bodyParams.includes(param));
40
+ }
41
+ function commandUsage(commandPath, endpoint, pathParamsAfterCommand, options) {
42
+ const pathSuffix = pathParamsAfterCommand.map((param) => ` <${param}>`).join('');
43
+ const flags = requestFlags(endpoint)
44
+ .map((flag) => ` [${flag} <value>]`)
45
+ .join('');
46
+ const waitFlag = endpointCanWait(endpoint) ? ' [--wait]' : '';
47
+ const simulateFlag = endpointCanSimulate(endpoint, options.simulatableEndpointKeys) ? ' [--simulate]' : '';
48
+ const submitFlags = endpoint.idempotencyRequired ? ` [--idempotency-key <key>]${waitFlag}${simulateFlag}` : '';
49
+ return `hightop ${commandPath.join(' ')}${pathSuffix}${flags}${submitFlags}`;
50
+ }
51
+ function commandFromEndpoint(endpoint) {
52
+ const segments = endpointSegments(endpoint);
53
+ const pathParamsAfterCommand = endpointPathParamsAfterCommand(endpoint);
54
+ const staticSegments = segments.filter((segment) => !segment.startsWith('{'));
55
+ if (endpoint.path === '/v1/agent/capabilities.json') {
56
+ return { commandPath: ['capabilities', 'json'], pathParamsAfterCommand };
57
+ }
58
+ if (endpoint.method === 'GET' && endpoint.pathParams.length === 0) {
59
+ return { commandPath: staticSegments, pathParamsAfterCommand };
60
+ }
61
+ if (endpoint.method === 'GET' && endpoint.pathParams.length > 0) {
62
+ const tail = staticSegments.slice(1);
63
+ if (tail.length > 0) {
64
+ return { commandPath: [staticSegments[0], ...tail], pathParamsAfterCommand };
65
+ }
66
+ return { commandPath: [staticSegments[0], 'get'], pathParamsAfterCommand };
67
+ }
68
+ if (endpoint.method === 'POST') {
69
+ if (endpoint.pathParams.length > 0) {
70
+ return { commandPath: staticSegments, pathParamsAfterCommand };
71
+ }
72
+ if (staticSegments.length === 1) {
73
+ if (staticSegments[0] === 'conversions') {
74
+ return { commandPath: ['conversions', 'execute'], pathParamsAfterCommand };
75
+ }
76
+ return { commandPath: [staticSegments[0], 'create'], pathParamsAfterCommand };
77
+ }
78
+ return { commandPath: staticSegments, pathParamsAfterCommand };
79
+ }
80
+ if (endpoint.method === 'PATCH') {
81
+ return { commandPath: [staticSegments[0], 'update'], pathParamsAfterCommand };
82
+ }
83
+ if (endpoint.method === 'DELETE') {
84
+ return { commandPath: [staticSegments[0], 'delete'], pathParamsAfterCommand };
85
+ }
86
+ return { commandPath: staticSegments, pathParamsAfterCommand };
87
+ }
88
+ function openApiEndpoint() {
89
+ return {
90
+ bodyParams: [],
91
+ bodySchema: null,
92
+ description: 'Return the public Agent API OpenAPI document.',
93
+ idempotencyRequired: false,
94
+ key: 'GET /v1/agent/openapi.json',
95
+ method: 'GET',
96
+ path: '/v1/agent/openapi.json',
97
+ pathParams: [],
98
+ queryParams: [],
99
+ requestSchema: null,
100
+ requestType: null,
101
+ responseSchema: null,
102
+ responseType: null,
103
+ routeClass: 'read',
104
+ };
105
+ }
106
+ export function buildCliEndpointCommands(metadata, options) {
107
+ const commands = metadata
108
+ .filter((endpoint) => endpoint.key !== 'POST /v1/agent/simulate')
109
+ .flatMap((endpoint) => {
110
+ const primary = commandFromEndpoint(endpoint);
111
+ const aliases = [primary];
112
+ if (endpoint.key === 'GET /v1/agent/self/usage') {
113
+ aliases.push({ commandPath: ['usage'], pathParamsAfterCommand: [] });
114
+ }
115
+ if (endpoint.method === 'GET' &&
116
+ endpoint.pathParams.length === 0 &&
117
+ endpoint.queryParams.some((param) => param === 'cursor' || param === 'limit')) {
118
+ aliases.push({ commandPath: [...primary.commandPath, 'list'], pathParamsAfterCommand: [] });
119
+ }
120
+ return aliases.map((alias) => ({
121
+ commandPath: alias.commandPath,
122
+ endpoint,
123
+ pathParamsAfterCommand: alias.pathParamsAfterCommand,
124
+ requestFlags: requestFlags(endpoint),
125
+ usage: commandUsage(alias.commandPath, endpoint, alias.pathParamsAfterCommand, options),
126
+ canSimulate: endpointCanSimulate(endpoint, options.simulatableEndpointKeys),
127
+ canWait: endpointCanWait(endpoint),
128
+ }));
129
+ });
130
+ const openapi = openApiEndpoint();
131
+ commands.push({
132
+ commandPath: ['openapi'],
133
+ endpoint: openapi,
134
+ pathParamsAfterCommand: [],
135
+ requestFlags: [],
136
+ usage: 'hightop openapi',
137
+ canSimulate: false,
138
+ canWait: false,
139
+ });
140
+ const seen = new Set();
141
+ return commands
142
+ .filter((command) => {
143
+ const key = command.commandPath.join(' ');
144
+ if (seen.has(key)) {
145
+ return false;
146
+ }
147
+ seen.add(key);
148
+ return true;
149
+ })
150
+ .sort((a, b) => commandDisplayName(a).localeCompare(commandDisplayName(b)));
151
+ }
@@ -0,0 +1 @@
1
+ export declare const SIMULATABLE_ENDPOINT_KEYS: ReadonlySet<string>;
@@ -0,0 +1,23 @@
1
+ // CLI-supported write routes that may be routed through POST /v1/agent/simulate.
2
+ // Keep this in sync with compatible entries in src/backend/agentApi/simulate.ts.
3
+ const SIMULATABLE_ENDPOINT_KEY_VALUES = [
4
+ 'POST /v1/agent/payments',
5
+ 'POST /v1/agent/one-off-payments',
6
+ 'POST /v1/agent/conversions/quote',
7
+ 'POST /v1/agent/conversions',
8
+ 'POST /v1/agent/earn/deposit',
9
+ 'POST /v1/agent/earn/withdraw',
10
+ 'POST /v1/agent/earn/move',
11
+ 'POST /v1/agent/earn/rewards/claim',
12
+ 'POST /v1/agent/borrow',
13
+ 'POST /v1/agent/borrow/repay',
14
+ 'POST /v1/agent/borrow/deleverage',
15
+ 'POST /v1/agent/borrow/collateral/add',
16
+ 'POST /v1/agent/borrow/collateral/remove',
17
+ 'POST /v1/agent/withdrawals/to-bank',
18
+ 'POST /v1/agent/withdrawals/to-crypto',
19
+ 'POST /v1/agent/trusted-destinations/{id}/confirm',
20
+ 'POST /v1/agent/trusted-destinations/{id}/cancel',
21
+ 'DELETE /v1/agent/trusted-destinations/{id}',
22
+ ];
23
+ export const SIMULATABLE_ENDPOINT_KEYS = new Set(SIMULATABLE_ENDPOINT_KEY_VALUES);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hightop/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,6 +41,6 @@
41
41
  "test": "vitest run --config vitest.config.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@hightop/sdk": "0.1.3"
44
+ "@hightop/sdk": "0.1.5"
45
45
  }
46
46
  }