@hightop/cli 0.1.3 → 0.1.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.
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.4",
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.4"
45
45
  }
46
46
  }