@trust-ethos/cli 0.0.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1791 -0
  3. package/bin/dev.cmd +3 -0
  4. package/bin/dev.js +5 -0
  5. package/bin/run.cmd +3 -0
  6. package/bin/run.js +5 -0
  7. package/dist/commands/auction/active.d.ts +10 -0
  8. package/dist/commands/auction/active.js +39 -0
  9. package/dist/commands/auction/info.d.ts +16 -0
  10. package/dist/commands/auction/info.js +46 -0
  11. package/dist/commands/auction/list.d.ts +14 -0
  12. package/dist/commands/auction/list.js +61 -0
  13. package/dist/commands/broker/info.d.ts +16 -0
  14. package/dist/commands/broker/info.js +37 -0
  15. package/dist/commands/broker/list.d.ts +15 -0
  16. package/dist/commands/broker/list.js +62 -0
  17. package/dist/commands/config/get.d.ts +9 -0
  18. package/dist/commands/config/get.js +29 -0
  19. package/dist/commands/config/path.d.ts +6 -0
  20. package/dist/commands/config/path.js +12 -0
  21. package/dist/commands/config/set.d.ts +9 -0
  22. package/dist/commands/config/set.js +28 -0
  23. package/dist/commands/listing/info.d.ts +13 -0
  24. package/dist/commands/listing/info.js +41 -0
  25. package/dist/commands/listing/list.d.ts +13 -0
  26. package/dist/commands/listing/list.js +46 -0
  27. package/dist/commands/listing/voters.d.ts +19 -0
  28. package/dist/commands/listing/voters.js +48 -0
  29. package/dist/commands/market/featured.d.ts +10 -0
  30. package/dist/commands/market/featured.js +34 -0
  31. package/dist/commands/market/holders.d.ts +14 -0
  32. package/dist/commands/market/holders.js +46 -0
  33. package/dist/commands/market/info.d.ts +14 -0
  34. package/dist/commands/market/info.js +48 -0
  35. package/dist/commands/market/list.d.ts +16 -0
  36. package/dist/commands/market/list.js +55 -0
  37. package/dist/commands/nft/list.d.ts +15 -0
  38. package/dist/commands/nft/list.js +64 -0
  39. package/dist/commands/review/info.d.ts +17 -0
  40. package/dist/commands/review/info.js +49 -0
  41. package/dist/commands/review/list.d.ts +16 -0
  42. package/dist/commands/review/list.js +68 -0
  43. package/dist/commands/review/votes.d.ts +21 -0
  44. package/dist/commands/review/votes.js +91 -0
  45. package/dist/commands/score/status.d.ts +13 -0
  46. package/dist/commands/score/status.js +54 -0
  47. package/dist/commands/slash/info.d.ts +16 -0
  48. package/dist/commands/slash/info.js +42 -0
  49. package/dist/commands/slash/list.d.ts +15 -0
  50. package/dist/commands/slash/list.js +50 -0
  51. package/dist/commands/slash/votes.d.ts +21 -0
  52. package/dist/commands/slash/votes.js +91 -0
  53. package/dist/commands/user/activity.d.ts +15 -0
  54. package/dist/commands/user/activity.js +71 -0
  55. package/dist/commands/user/info.d.ts +14 -0
  56. package/dist/commands/user/info.js +51 -0
  57. package/dist/commands/user/invitations.d.ts +16 -0
  58. package/dist/commands/user/invitations.js +73 -0
  59. package/dist/commands/user/search.d.ts +15 -0
  60. package/dist/commands/user/search.js +59 -0
  61. package/dist/commands/user/summary.d.ts +14 -0
  62. package/dist/commands/user/summary.js +134 -0
  63. package/dist/commands/validator/info.d.ts +13 -0
  64. package/dist/commands/validator/info.js +53 -0
  65. package/dist/commands/validator/list.d.ts +13 -0
  66. package/dist/commands/validator/list.js +64 -0
  67. package/dist/commands/validator/sales.d.ts +12 -0
  68. package/dist/commands/validator/sales.js +52 -0
  69. package/dist/commands/vouch/info.d.ts +17 -0
  70. package/dist/commands/vouch/info.js +53 -0
  71. package/dist/commands/vouch/list.d.ts +18 -0
  72. package/dist/commands/vouch/list.js +89 -0
  73. package/dist/commands/vouch/mutual.d.ts +15 -0
  74. package/dist/commands/vouch/mutual.js +68 -0
  75. package/dist/commands/vouch/votes.d.ts +21 -0
  76. package/dist/commands/vouch/votes.js +91 -0
  77. package/dist/commands/xp/rank.d.ts +15 -0
  78. package/dist/commands/xp/rank.js +74 -0
  79. package/dist/commands/xp/seasons.d.ts +10 -0
  80. package/dist/commands/xp/seasons.js +42 -0
  81. package/dist/hooks/init.d.ts +3 -0
  82. package/dist/hooks/init.js +40 -0
  83. package/dist/index.d.ts +1 -0
  84. package/dist/index.js +1 -0
  85. package/dist/lib/api/echo-client.d.ts +624 -0
  86. package/dist/lib/api/echo-client.js +408 -0
  87. package/dist/lib/config/index.d.ts +6 -0
  88. package/dist/lib/config/index.js +32 -0
  89. package/dist/lib/errors/cli-error.d.ts +23 -0
  90. package/dist/lib/errors/cli-error.js +57 -0
  91. package/dist/lib/formatting/colors.d.ts +13 -0
  92. package/dist/lib/formatting/colors.js +22 -0
  93. package/dist/lib/formatting/error.d.ts +1 -0
  94. package/dist/lib/formatting/error.js +64 -0
  95. package/dist/lib/formatting/output.d.ts +45 -0
  96. package/dist/lib/formatting/output.js +753 -0
  97. package/dist/lib/help.d.ts +4 -0
  98. package/dist/lib/help.js +28 -0
  99. package/dist/lib/update/index.d.ts +37 -0
  100. package/dist/lib/update/index.js +286 -0
  101. package/dist/lib/validation/userkey.d.ts +11 -0
  102. package/dist/lib/validation/userkey.js +81 -0
  103. package/oclif.manifest.json +2224 -0
  104. package/package.json +87 -0
@@ -0,0 +1,408 @@
1
+ import { APIError, NetworkError, NotFoundError } from '../errors/cli-error.js';
2
+ import { parseIdentifier } from '../validation/userkey.js';
3
+ import { loadConfig } from '../config/index.js';
4
+ export class EchoClient {
5
+ baseUrl;
6
+ debug;
7
+ constructor() {
8
+ const config = loadConfig();
9
+ this.baseUrl = config.apiUrl;
10
+ this.debug = process.env.DEBUG === 'ethos:*';
11
+ }
12
+ log(message, data) {
13
+ if (this.debug) {
14
+ console.error(`[DEBUG] ${message}`, data || '');
15
+ }
16
+ }
17
+ async request(path, resourceType, options) {
18
+ const url = `${this.baseUrl}${path}`;
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
21
+ this.log(`Fetching ${url}`);
22
+ try {
23
+ const response = await fetch(url, {
24
+ headers: {
25
+ 'Accept': 'application/json',
26
+ 'X-Ethos-Client': 'ethos-cli',
27
+ ...(options?.body ? { 'Content-Type': 'application/json' } : {}),
28
+ },
29
+ signal: controller.signal,
30
+ ...options,
31
+ });
32
+ this.log(`Response status: ${response.status}`);
33
+ if (!response.ok) {
34
+ if (response.status === 404) {
35
+ const identifier = path.split('/').pop() || 'unknown';
36
+ throw new NotFoundError(resourceType || 'Resource', decodeURIComponent(identifier));
37
+ }
38
+ let errorMessage = `API request failed with status ${response.status}`;
39
+ let errorBody;
40
+ const responseText = await response.text();
41
+ try {
42
+ errorBody = JSON.parse(responseText);
43
+ errorMessage = errorBody.message || errorBody.error || errorMessage;
44
+ }
45
+ catch {
46
+ if (responseText)
47
+ errorMessage = responseText;
48
+ }
49
+ this.log('API Error', { status: response.status, body: errorBody });
50
+ throw new APIError(errorMessage, response.status, errorBody);
51
+ }
52
+ const data = await response.json();
53
+ this.log('Response data', data);
54
+ return data;
55
+ }
56
+ catch (error) {
57
+ if (error instanceof NotFoundError || error instanceof APIError) {
58
+ throw error;
59
+ }
60
+ if (error instanceof Error) {
61
+ this.log('Network error', error.message);
62
+ if (error.name === 'AbortError') {
63
+ throw new NetworkError('Request timed out after 10 seconds', url);
64
+ }
65
+ if (error.message.includes('fetch failed') ||
66
+ error.message.includes('ECONNREFUSED') ||
67
+ error.message.includes('ENOTFOUND')) {
68
+ throw new NetworkError(`Cannot connect to API at ${this.baseUrl}`, url);
69
+ }
70
+ throw new NetworkError(error.message, url);
71
+ }
72
+ throw error;
73
+ }
74
+ finally {
75
+ clearTimeout(timeoutId);
76
+ }
77
+ }
78
+ async getUserByTwitter(username) {
79
+ return this.request(`/api/v2/user/by/x/${encodeURIComponent(username)}`, 'User');
80
+ }
81
+ async getUserByAddress(address) {
82
+ return this.request(`/api/v2/user/by/address/${encodeURIComponent(address)}`, 'User');
83
+ }
84
+ async getUserByProfileId(profileId) {
85
+ return this.request(`/api/v2/user/by/profile-id/${encodeURIComponent(profileId)}`, 'User');
86
+ }
87
+ async searchUsers(query, limit = 10) {
88
+ const params = new URLSearchParams({ query, limit: String(limit) });
89
+ return this.request(`/api/v2/users/search?${params}`, 'Users');
90
+ }
91
+ async resolveUser(identifier) {
92
+ const parsed = parseIdentifier(identifier);
93
+ return this.resolveUserFromParsed(parsed);
94
+ }
95
+ async resolveUserFromParsed(parsed) {
96
+ switch (parsed.type) {
97
+ case 'address':
98
+ return this.getUserByAddress(parsed.value);
99
+ case 'ens':
100
+ return this.resolveEnsUser(parsed.value);
101
+ case 'profileId':
102
+ return this.getUserByProfileId(parsed.value);
103
+ case 'twitter':
104
+ default:
105
+ return this.getUserByTwitter(parsed.value);
106
+ }
107
+ }
108
+ async resolveEnsUser(ensName) {
109
+ const searchResult = await this.searchUsers(ensName, 5);
110
+ const exactMatch = searchResult.values.find(u => u.displayName?.toLowerCase() === ensName.toLowerCase() ||
111
+ u.username?.toLowerCase() === ensName.toLowerCase());
112
+ if (exactMatch)
113
+ return exactMatch;
114
+ const addressMatch = searchResult.values.find(u => u.userkeys?.some(uk => uk.startsWith('address:')));
115
+ if (addressMatch)
116
+ return addressMatch;
117
+ if (searchResult.values.length > 0) {
118
+ return searchResult.values[0];
119
+ }
120
+ throw new NotFoundError('User', ensName);
121
+ }
122
+ async getXpTotal(userkey) {
123
+ return this.request(`/api/v2/xp/user/${encodeURIComponent(userkey)}`, 'XP Balance');
124
+ }
125
+ async getSeasons() {
126
+ return this.request('/api/v2/xp/seasons', 'XP Seasons');
127
+ }
128
+ async getLeaderboardRank(userkey) {
129
+ return this.request(`/api/v2/xp/user/${encodeURIComponent(userkey)}/leaderboard-rank`, 'Leaderboard Rank');
130
+ }
131
+ async getXpBySeason(userkey, seasonId) {
132
+ return this.request(`/api/v2/xp/user/${encodeURIComponent(userkey)}/season/${seasonId}`, 'XP for Season');
133
+ }
134
+ async getActivities(userkey, types = ['review', 'vouch'], limit = 10) {
135
+ const params = new URLSearchParams({ userkey, limit: String(limit) });
136
+ for (const type of types) {
137
+ params.append('activityType', type);
138
+ }
139
+ return this.request(`/api/v2/activities/userkey?${params}`, 'Activities');
140
+ }
141
+ getPrimaryUserkey(user) {
142
+ if (user.profileId) {
143
+ return `profileId:${user.profileId}`;
144
+ }
145
+ const addressKey = user.userkeys?.find(uk => uk.startsWith('address:'));
146
+ if (addressKey)
147
+ return addressKey;
148
+ return user.userkeys?.[0] || null;
149
+ }
150
+ /** @deprecated Use resolveUser instead */
151
+ async getUserByUsername(username) {
152
+ return this.getUserByTwitter(username);
153
+ }
154
+ /** @deprecated Use getXpTotal instead */
155
+ async getTotalXp(userkey) {
156
+ return this.getXpTotal(userkey);
157
+ }
158
+ async getSlashes(params = {}) {
159
+ const query = new URLSearchParams();
160
+ if (params.author)
161
+ query.set('author', params.author);
162
+ if (params.subject)
163
+ query.set('subject', params.subject);
164
+ if (params.status)
165
+ query.set('status', params.status);
166
+ if (params.limit)
167
+ query.set('limit', String(params.limit));
168
+ if (params.offset)
169
+ query.set('offset', String(params.offset));
170
+ const path = `/api/v1/slashes${query.toString() ? '?' + query.toString() : ''}`;
171
+ return this.request(path, 'Slashes');
172
+ }
173
+ async getSlashRoles(slashId, profileIds) {
174
+ const query = new URLSearchParams();
175
+ profileIds.forEach(id => query.append('profileId', String(id)));
176
+ return this.request(`/api/v1/slashes/${slashId}/roles?${query.toString()}`, 'Slash Roles');
177
+ }
178
+ async getBrokerPosts(params = {}) {
179
+ const query = new URLSearchParams();
180
+ if (params.type)
181
+ query.set('type', params.type);
182
+ if (params.search)
183
+ query.set('search', params.search);
184
+ if (params.sortBy)
185
+ query.set('sortBy', params.sortBy);
186
+ if (params.minScore)
187
+ query.set('minScore', String(params.minScore));
188
+ if (params.limit)
189
+ query.set('limit', String(params.limit));
190
+ if (params.offset)
191
+ query.set('offset', String(params.offset));
192
+ const path = `/api/v2/broker/posts${query.toString() ? '?' + query.toString() : ''}`;
193
+ return this.request(path, 'Broker Posts');
194
+ }
195
+ async getBrokerPost(id) {
196
+ return this.request(`/api/v2/broker/posts/${id}`, 'Broker Post');
197
+ }
198
+ async getBrokerPostsByAuthor(profileId, params = {}) {
199
+ const query = new URLSearchParams();
200
+ if (params.type)
201
+ query.set('type', params.type);
202
+ if (params.limit)
203
+ query.set('limit', String(params.limit));
204
+ const path = `/api/v2/broker/author/${profileId}/posts${query.toString() ? '?' + query.toString() : ''}`;
205
+ return this.request(path, 'Author Posts');
206
+ }
207
+ async getProjects(params = {}) {
208
+ const query = new URLSearchParams();
209
+ if (params.status)
210
+ params.status.forEach(s => query.append('status[]', s));
211
+ if (params.limit)
212
+ query.set('limit', String(params.limit));
213
+ if (params.offset)
214
+ query.set('offset', String(params.offset));
215
+ return this.request(`/api/v2/projects${query.toString() ? '?' + query.toString() : ''}`, 'Projects');
216
+ }
217
+ async getProjectDetails(projectId) {
218
+ return this.request(`/api/v2/projects/${projectId}/details`, 'Project');
219
+ }
220
+ async getProjectByUsername(username) {
221
+ return this.request(`/api/v2/projects/username/${encodeURIComponent(username)}`, 'Project');
222
+ }
223
+ async getProjectVoters(projectId, params = {}) {
224
+ const query = new URLSearchParams();
225
+ if (params.limit)
226
+ query.set('limit', String(params.limit));
227
+ if (params.offset)
228
+ query.set('offset', String(params.offset));
229
+ if (params.sentiment)
230
+ query.set('sentiment', params.sentiment);
231
+ return this.request(`/api/v2/projects/${projectId}/voters${query.toString() ? '?' + query.toString() : ''}`, 'Voters');
232
+ }
233
+ async getProjectTeam(projectId) {
234
+ return this.request(`/api/v2/projects/${projectId}/team`, 'Team');
235
+ }
236
+ async getNftsForUser(userkey, params = {}) {
237
+ const query = new URLSearchParams();
238
+ if (params.limit)
239
+ query.set('limit', String(params.limit));
240
+ if (params.offset)
241
+ query.set('offset', String(params.offset));
242
+ const path = `/api/v2/nfts/user/${encodeURIComponent(userkey)}${query.toString() ? '?' + query.toString() : ''}`;
243
+ return this.request(path, 'User NFTs');
244
+ }
245
+ async checkValidatorOwnership(userkey) {
246
+ return this.request(`/api/v2/nfts/user/${encodeURIComponent(userkey)}/owns-validator`, 'Validator Check');
247
+ }
248
+ async getValidatorListings(params = {}) {
249
+ const query = new URLSearchParams();
250
+ if (params.limit)
251
+ query.set('limit', String(params.limit));
252
+ if (params.offset)
253
+ query.set('offset', String(params.offset));
254
+ const path = `/api/v2/nfts/validators/listings${query.toString() ? '?' + query.toString() : ''}`;
255
+ return this.request(path, 'Validator Listings');
256
+ }
257
+ async getAuctions(params = {}) {
258
+ const query = new URLSearchParams();
259
+ if (params.limit)
260
+ query.set('limit', String(params.limit));
261
+ if (params.offset)
262
+ query.set('offset', String(params.offset));
263
+ if (params.status)
264
+ query.set('status', params.status);
265
+ const path = `/api/v2/auctions${query.toString() ? '?' + query.toString() : ''}`;
266
+ return this.request(path, 'Auctions');
267
+ }
268
+ async getActiveAuction() {
269
+ return this.request('/api/v2/auctions/active', 'Active Auction');
270
+ }
271
+ async getAuction(auctionId) {
272
+ return this.request(`/api/v2/auctions/${auctionId}`, 'Auction');
273
+ }
274
+ async getMarkets(params = {}) {
275
+ const query = new URLSearchParams();
276
+ if (params.limit)
277
+ query.set('limit', String(params.limit));
278
+ if (params.offset)
279
+ query.set('offset', String(params.offset));
280
+ if (params.orderBy)
281
+ query.set('orderBy', params.orderBy);
282
+ if (params.orderDirection)
283
+ query.set('orderDirection', params.orderDirection);
284
+ if (params.filterQuery)
285
+ query.set('filterQuery', params.filterQuery);
286
+ return this.request(`/api/v2/markets${query.toString() ? '?' + query.toString() : ''}`, 'Markets');
287
+ }
288
+ async getFeaturedMarkets() {
289
+ return this.request('/api/v2/markets/featured', 'Featured Markets');
290
+ }
291
+ async getMarketInfo(profileId) {
292
+ return this.request(`/api/v2/markets/${profileId}/info`, 'Market Info', {
293
+ method: 'POST',
294
+ body: JSON.stringify({ profileId, includeTopHolders: true, includeMarketChange: true }),
295
+ });
296
+ }
297
+ async getMarketHolders(profileId, params = {}) {
298
+ const query = new URLSearchParams();
299
+ if (params.limit)
300
+ query.set('limit', String(params.limit));
301
+ return this.request(`/api/v2/markets/${profileId}/holders${query.toString() ? '?' + query.toString() : ''}`, 'Market Holders');
302
+ }
303
+ async getMarketByTwitter(username) {
304
+ return this.request(`/api/v2/markets/users/by/x/${encodeURIComponent(username)}`, 'Market User');
305
+ }
306
+ async getScoreBreakdownByUserkey(userkey) {
307
+ const params = new URLSearchParams({ userkey });
308
+ return this.request(`/api/v1/score/userkey?${params}`, 'Score');
309
+ }
310
+ async getScoreBreakdownByAddress(address) {
311
+ const params = new URLSearchParams({ address });
312
+ return this.request(`/api/v1/score/address?${params}`, 'Score');
313
+ }
314
+ convertScoreToLevel(score) {
315
+ if (score < 800)
316
+ return 'untrusted';
317
+ if (score < 1200)
318
+ return 'questionable';
319
+ if (score < 1600)
320
+ return 'neutral';
321
+ if (score < 2000)
322
+ return 'reputable';
323
+ return 'exemplary';
324
+ }
325
+ async getScoreStatus(userkey) {
326
+ const params = new URLSearchParams({ userkey });
327
+ return this.request(`/api/v2/score/status?${params}`, 'Score Status');
328
+ }
329
+ async getVouches(params = {}) {
330
+ const body = {};
331
+ if (params.ids)
332
+ body.ids = params.ids;
333
+ if (params.subjectUserkeys)
334
+ body.subjectUserkeys = params.subjectUserkeys;
335
+ if (params.authorProfileIds)
336
+ body.authorProfileIds = params.authorProfileIds;
337
+ if (params.subjectProfileIds)
338
+ body.subjectProfileIds = params.subjectProfileIds;
339
+ if (params.archived !== undefined)
340
+ body.archived = params.archived;
341
+ if (params.limit)
342
+ body.limit = params.limit;
343
+ if (params.offset)
344
+ body.offset = params.offset;
345
+ const response = await this.request('/api/v1/vouches', 'Vouches', {
346
+ method: 'POST',
347
+ body: JSON.stringify(body),
348
+ });
349
+ return response.data;
350
+ }
351
+ async getMutualVouchers(viewerProfileId, targetProfileId, params = {}) {
352
+ const query = new URLSearchParams();
353
+ query.set('viewerProfileId', String(viewerProfileId));
354
+ query.set('targetProfileId', String(targetProfileId));
355
+ if (params.limit)
356
+ query.set('limit', String(params.limit));
357
+ return this.request(`/api/v2/vouches/mutual-vouchers?${query}`, 'Mutual Vouchers');
358
+ }
359
+ async getVotes(activityId, type, params = {}) {
360
+ const query = new URLSearchParams();
361
+ query.set('activityId', String(activityId));
362
+ query.set('type', type);
363
+ if (params.isUpvote !== undefined)
364
+ query.set('isUpvote', String(params.isUpvote));
365
+ if (params.limit)
366
+ query.set('limit', String(params.limit));
367
+ if (params.offset)
368
+ query.set('offset', String(params.offset));
369
+ return this.request(`/api/v2/votes?${query}`, 'Votes');
370
+ }
371
+ async getVoteStats(activityId, type) {
372
+ const query = new URLSearchParams();
373
+ query.set('activityId', String(activityId));
374
+ query.set('type', type);
375
+ return this.request(`/api/v2/votes/stats?${query}`, 'Vote Stats');
376
+ }
377
+ async getValidators() {
378
+ return this.request('/api/v2/xp/validators', 'Validators');
379
+ }
380
+ async getValidatorByTokenId(tokenId) {
381
+ const validators = await this.getValidators();
382
+ return validators.find(v => v.tokenId === tokenId) || null;
383
+ }
384
+ async getReview(reviewId) {
385
+ return this.request(`/api/v2/activities/review/${reviewId}`, 'Review');
386
+ }
387
+ async getReviewsForUser(userkey, params = {}) {
388
+ const query = new URLSearchParams({ userkey });
389
+ query.append('activityType', 'review');
390
+ if (params.limit)
391
+ query.set('limit', String(params.limit));
392
+ if (params.offset)
393
+ query.set('offset', String(params.offset));
394
+ return this.request(`/api/v2/activities/userkey?${query}`, 'Reviews');
395
+ }
396
+ async getInvitations(params = {}) {
397
+ const query = new URLSearchParams();
398
+ if (params.senderProfileId)
399
+ query.set('senderProfileId', String(params.senderProfileId));
400
+ if (params.status)
401
+ query.set('status', params.status);
402
+ if (params.limit)
403
+ query.set('limit', String(params.limit));
404
+ if (params.offset)
405
+ query.set('offset', String(params.offset));
406
+ return this.request(`/api/v2/invitations${query.toString() ? '?' + query.toString() : ''}`, 'Invitations');
407
+ }
408
+ }
@@ -0,0 +1,6 @@
1
+ export interface EthosConfig {
2
+ apiUrl: string;
3
+ }
4
+ export declare function loadConfig(): EthosConfig;
5
+ export declare function saveConfig(config: Partial<EthosConfig>): void;
6
+ export declare function getConfigPath(): string;
@@ -0,0 +1,32 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join, dirname } from 'path';
4
+ const DEFAULT_API_URL = 'https://api.ethos.network';
5
+ const CONFIG_PATH = join(homedir(), '.config', 'ethos', 'config.json');
6
+ export function loadConfig() {
7
+ if (!existsSync(CONFIG_PATH)) {
8
+ return { apiUrl: DEFAULT_API_URL };
9
+ }
10
+ try {
11
+ const content = readFileSync(CONFIG_PATH, 'utf-8');
12
+ const parsed = JSON.parse(content);
13
+ return {
14
+ apiUrl: parsed.apiUrl || DEFAULT_API_URL,
15
+ };
16
+ }
17
+ catch {
18
+ return { apiUrl: DEFAULT_API_URL };
19
+ }
20
+ }
21
+ export function saveConfig(config) {
22
+ const current = loadConfig();
23
+ const updated = { ...current, ...config };
24
+ const dir = dirname(CONFIG_PATH);
25
+ if (!existsSync(dir)) {
26
+ mkdirSync(dir, { recursive: true });
27
+ }
28
+ writeFileSync(CONFIG_PATH, JSON.stringify(updated, null, 2) + '\n');
29
+ }
30
+ export function getConfigPath() {
31
+ return CONFIG_PATH;
32
+ }
@@ -0,0 +1,23 @@
1
+ export declare class CLIError extends Error {
2
+ readonly code: string;
3
+ readonly suggestions: string[];
4
+ constructor(message: string, code: string, suggestions?: string[]);
5
+ }
6
+ export declare class NetworkError extends CLIError {
7
+ readonly url?: string | undefined;
8
+ readonly statusCode?: number | undefined;
9
+ constructor(message: string, url?: string | undefined, statusCode?: number | undefined);
10
+ }
11
+ export declare class NotFoundError extends CLIError {
12
+ readonly identifier: string;
13
+ constructor(resourceType: string, identifier: string);
14
+ }
15
+ export declare class ValidationError extends CLIError {
16
+ readonly field?: string | undefined;
17
+ constructor(message: string, field?: string | undefined);
18
+ }
19
+ export declare class APIError extends CLIError {
20
+ readonly statusCode?: number | undefined;
21
+ readonly response?: unknown | undefined;
22
+ constructor(message: string, statusCode?: number | undefined, response?: unknown | undefined);
23
+ }
@@ -0,0 +1,57 @@
1
+ export class CLIError extends Error {
2
+ code;
3
+ suggestions;
4
+ constructor(message, code, suggestions = []) {
5
+ super(message);
6
+ this.code = code;
7
+ this.suggestions = suggestions;
8
+ this.name = 'CLIError';
9
+ }
10
+ }
11
+ export class NetworkError extends CLIError {
12
+ url;
13
+ statusCode;
14
+ constructor(message, url, statusCode) {
15
+ super(message, 'NETWORK_ERROR', [
16
+ 'Check your internet connection',
17
+ 'Verify the API is accessible',
18
+ 'Try again in a few moments',
19
+ ]);
20
+ this.url = url;
21
+ this.statusCode = statusCode;
22
+ this.name = 'NetworkError';
23
+ }
24
+ }
25
+ export class NotFoundError extends CLIError {
26
+ identifier;
27
+ constructor(resourceType, identifier) {
28
+ super(`${resourceType} not found: ${identifier}`, 'NOT_FOUND', [
29
+ `Try: ethos user search "${identifier}"`,
30
+ 'For Twitter users, use their Twitter username (e.g., @username)',
31
+ 'For ETH addresses, use the full 0x... address',
32
+ ]);
33
+ this.name = 'NotFoundError';
34
+ this.identifier = identifier;
35
+ }
36
+ }
37
+ export class ValidationError extends CLIError {
38
+ field;
39
+ constructor(message, field) {
40
+ super(message, 'VALIDATION_ERROR', ['Check your input and try again']);
41
+ this.field = field;
42
+ this.name = 'ValidationError';
43
+ }
44
+ }
45
+ export class APIError extends CLIError {
46
+ statusCode;
47
+ response;
48
+ constructor(message, statusCode, response) {
49
+ super(message, 'API_ERROR', [
50
+ 'The API returned an error',
51
+ 'Try again or contact support if the issue persists',
52
+ ]);
53
+ this.statusCode = statusCode;
54
+ this.response = response;
55
+ this.name = 'APIError';
56
+ }
57
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Custom color utilities for Ethos CLI branding
3
+ * Uses ANSI true color (24-bit) escape codes for precise colors
4
+ */
5
+ /**
6
+ * Apply Ethos brand gray color (#C1C0B6) to text
7
+ * Uses ANSI true color escape codes (supported by most modern terminals)
8
+ */
9
+ export declare function ethosGray(text: string): string;
10
+ /**
11
+ * Apply Ethos brand gray as bold
12
+ */
13
+ export declare function ethosGrayBold(text: string): string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Custom color utilities for Ethos CLI branding
3
+ * Uses ANSI true color (24-bit) escape codes for precise colors
4
+ */
5
+ // Ethos brand color: #C1C0B6 (warm gray/beige)
6
+ // RGB: 193, 192, 182
7
+ const ETHOS_GRAY_RGB = [193, 192, 182];
8
+ /**
9
+ * Apply Ethos brand gray color (#C1C0B6) to text
10
+ * Uses ANSI true color escape codes (supported by most modern terminals)
11
+ */
12
+ export function ethosGray(text) {
13
+ const [r, g, b] = ETHOS_GRAY_RGB;
14
+ return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
15
+ }
16
+ /**
17
+ * Apply Ethos brand gray as bold
18
+ */
19
+ export function ethosGrayBold(text) {
20
+ const [r, g, b] = ETHOS_GRAY_RGB;
21
+ return `\x1b[1m\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
22
+ }
@@ -0,0 +1 @@
1
+ export declare function formatError(error: Error, verbose?: boolean): string;
@@ -0,0 +1,64 @@
1
+ import pc from 'picocolors';
2
+ import { APIError, CLIError, NetworkError, NotFoundError, ValidationError } from '../errors/cli-error.js';
3
+ export function formatError(error, verbose = false) {
4
+ const lines = [];
5
+ if (error instanceof NotFoundError) {
6
+ lines.push(pc.yellow('⚠ ') + pc.bold(error.message));
7
+ }
8
+ else if (error instanceof NetworkError) {
9
+ lines.push(pc.red('✖ ') + pc.bold('Network Error'));
10
+ lines.push(' ' + error.message);
11
+ }
12
+ else if (error instanceof ValidationError) {
13
+ lines.push(pc.yellow('⚠ ') + pc.bold('Validation Error'));
14
+ lines.push(' ' + error.message);
15
+ }
16
+ else if (error instanceof APIError) {
17
+ lines.push(pc.red('✖ ') + pc.bold('API Error'));
18
+ lines.push(' ' + error.message);
19
+ if (error.statusCode) {
20
+ lines.push(' ' + pc.dim(`Status: ${error.statusCode}`));
21
+ }
22
+ }
23
+ else {
24
+ lines.push(pc.red('✖ ') + pc.bold('Error'));
25
+ lines.push(' ' + error.message);
26
+ }
27
+ if (error instanceof CLIError && error.suggestions.length > 0) {
28
+ lines.push('');
29
+ for (const suggestion of error.suggestions) {
30
+ if (suggestion.startsWith('Try:')) {
31
+ lines.push(pc.cyan('💡 ' + suggestion));
32
+ }
33
+ else {
34
+ lines.push(pc.dim('• ' + suggestion));
35
+ }
36
+ }
37
+ }
38
+ if (verbose) {
39
+ lines.push('');
40
+ lines.push(pc.dim('Debug Information:'));
41
+ if (error instanceof NetworkError) {
42
+ if (error.url)
43
+ lines.push(pc.dim(` URL: ${error.url}`));
44
+ if (error.statusCode)
45
+ lines.push(pc.dim(` Status Code: ${error.statusCode}`));
46
+ }
47
+ if (error instanceof APIError && error.response) {
48
+ lines.push(pc.dim(' Response:'));
49
+ lines.push(pc.dim(' ' + JSON.stringify(error.response, null, 2).replace(/\n/g, '\n ')));
50
+ }
51
+ if (error.stack) {
52
+ lines.push(pc.dim(' Stack Trace:'));
53
+ const stackLines = error.stack.split('\n').slice(1);
54
+ for (const line of stackLines) {
55
+ lines.push(pc.dim(' ' + line.trim()));
56
+ }
57
+ }
58
+ }
59
+ if (!verbose && !(error instanceof NotFoundError)) {
60
+ lines.push('');
61
+ lines.push(pc.dim('Run with --verbose for more details'));
62
+ }
63
+ return lines.join('\n');
64
+ }
@@ -0,0 +1,45 @@
1
+ import type { Activity, Auction, BrokerPost, EthosUser, FeaturedMarketsResponse, InvitationWithUser, Market, MarketHolder, Project, ProjectVoter, ProjectVotersTotals, Review, ScoreResponse, ScoreStatus, Season, Slash, Validator, Vote, VoteStats, Vouch, VouchUser } from '../api/echo-client.js';
2
+ export declare function output<T>(data: T): string;
3
+ export declare function formatUser(user: EthosUser): string;
4
+ export declare function formatInvitations(invitations: InvitationWithUser[], total: number): string;
5
+ export declare function formatSeasons(seasons: Season[], currentSeason?: Season): string;
6
+ export declare function formatRank(data: {
7
+ rank: number;
8
+ totalXp?: number;
9
+ userkey?: string;
10
+ username?: string;
11
+ seasonXp?: number;
12
+ season?: number;
13
+ }): string;
14
+ export declare function formatSearchResults(results: EthosUser[]): string;
15
+ export declare function formatActivities(activities: Activity[], username?: string): string;
16
+ export declare function formatSlash(slash: Slash): string;
17
+ export declare function formatSlashes(slashes: Slash[], total: number): string;
18
+ export declare function formatReview(review: Review): string;
19
+ export declare function formatReviews(activities: Activity[], total?: number): string;
20
+ export declare function formatBrokerPost(post: BrokerPost): string;
21
+ export declare function formatBrokerPosts(posts: BrokerPost[], total: number): string;
22
+ export declare function formatListing(project: Project): string;
23
+ export declare function formatListings(projects: Project[], total: number): string;
24
+ export declare function formatListingVoters(voters: ProjectVoter[], totals: ProjectVotersTotals): string;
25
+ export declare function formatNfts(nfts: any[], total: number): string;
26
+ export declare function formatValidatorListings(listings: any[], total: number): string;
27
+ export declare function formatAuction(auction: Auction): string;
28
+ export declare function formatAuctions(auctions: Auction[], total: number): string;
29
+ export declare function formatMarket(market: Market): string;
30
+ export declare function formatMarkets(markets: Market[], total: number): string;
31
+ export declare function formatMarketHolders(holders: MarketHolder[], total: number): string;
32
+ export declare function formatFeaturedMarkets(response: FeaturedMarketsResponse): string;
33
+ export declare function formatScore(data: ScoreResponse & {
34
+ identifier?: string;
35
+ }): string;
36
+ export declare function formatScoreStatus(data: ScoreStatus & {
37
+ identifier?: string;
38
+ }): string;
39
+ export declare function formatVouch(vouch: Vouch): string;
40
+ export declare function formatVouches(vouches: Vouch[], total: number): string;
41
+ export declare function formatMutualVouchers(users: VouchUser[], total: number): string;
42
+ export declare function formatVotes(votes: Vote[], total: number, activityType?: string): string;
43
+ export declare function formatVoteStats(stats: VoteStats, activityType?: string, activityId?: number): string;
44
+ export declare function formatValidator(validator: Validator): string;
45
+ export declare function formatValidators(validators: Validator[], total: number): string;