@eightstate/escli 0.5.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.
Files changed (54) hide show
  1. package/CONVENTIONS.md +59 -0
  2. package/LICENSE +21 -0
  3. package/README.md +106 -0
  4. package/RELEASE-NOTES-0.5.0.md +34 -0
  5. package/dist/base-command.js +166 -0
  6. package/dist/commands/audio/get.js +39 -0
  7. package/dist/commands/audio/index.js +18 -0
  8. package/dist/commands/audio/list.js +39 -0
  9. package/dist/commands/audio/status.js +34 -0
  10. package/dist/commands/audio/transcribe.js +99 -0
  11. package/dist/commands/auth/index.js +18 -0
  12. package/dist/commands/auth/login.js +38 -0
  13. package/dist/commands/auth/logout.js +27 -0
  14. package/dist/commands/auth/profiles.js +31 -0
  15. package/dist/commands/auth/status.js +27 -0
  16. package/dist/commands/auth/switch.js +24 -0
  17. package/dist/commands/docs/fetch.js +37 -0
  18. package/dist/commands/docs/get.js +47 -0
  19. package/dist/commands/docs/index.js +18 -0
  20. package/dist/commands/docs/search.js +55 -0
  21. package/dist/commands/fetch.js +55 -0
  22. package/dist/commands/image/edit.js +59 -0
  23. package/dist/commands/image/generate.js +67 -0
  24. package/dist/commands/image/index.js +18 -0
  25. package/dist/commands/models.js +27 -0
  26. package/dist/commands/research.js +92 -0
  27. package/dist/commands/search.js +54 -0
  28. package/dist/commands/social.js +69 -0
  29. package/dist/commands/usage.js +51 -0
  30. package/dist/commands/version.js +22 -0
  31. package/dist/entry.js +120 -0
  32. package/dist/io/io.js +322 -0
  33. package/dist/lib/build-flags.js +2 -0
  34. package/dist/lib/command-metadata.js +8 -0
  35. package/dist/lib/envelope.js +28 -0
  36. package/dist/lib/escli-error.js +20 -0
  37. package/dist/lib/global-flags.js +29 -0
  38. package/dist/lib/globals.js +2 -0
  39. package/dist/lib/manifest.js +67 -0
  40. package/dist/lib/oclif-manifest-check.js +11 -0
  41. package/dist/lib/registry.js +228 -0
  42. package/dist/services/audio.js +454 -0
  43. package/dist/services/auth.js +329 -0
  44. package/dist/services/credentials.js +137 -0
  45. package/dist/services/docs.js +303 -0
  46. package/dist/services/fetch.js +197 -0
  47. package/dist/services/image.js +297 -0
  48. package/dist/services/models.js +131 -0
  49. package/dist/services/research.js +504 -0
  50. package/dist/services/search.js +195 -0
  51. package/dist/services/social.js +224 -0
  52. package/dist/services/usage.js +165 -0
  53. package/oclif.manifest.json +3377 -0
  54. package/package.json +57 -0
@@ -0,0 +1,224 @@
1
+ import { ErrorCode } from '@eightstate/contracts/errors';
2
+ import { ExitCodes } from '@eightstate/contracts/exit-codes';
3
+ import { buildUsageError } from '../lib/envelope.js';
4
+ import { EscliError } from '../lib/escli-error.js';
5
+ import { callWithRetry } from './credentials.js';
6
+ const TAVILY_API = 'https://api.tavily.com';
7
+ const DEFAULT_TIMEOUT_MS = 30_000;
8
+ const VALID_TIME_RANGES = ['day', 'week', 'month', 'year'];
9
+ const VALID_DEPTHS = ['basic', 'advanced', 'fast', 'ultra-fast'];
10
+ const PLATFORM_DOMAINS = {
11
+ reddit: ['reddit.com'],
12
+ x: ['x.com', 'twitter.com'],
13
+ twitter: ['x.com', 'twitter.com'],
14
+ tiktok: ['tiktok.com'],
15
+ linkedin: ['linkedin.com'],
16
+ instagram: ['instagram.com'],
17
+ facebook: ['facebook.com'],
18
+ youtube: ['youtube.com'],
19
+ combined: ['reddit.com', 'x.com', 'twitter.com', 'tiktok.com', 'linkedin.com', 'instagram.com', 'facebook.com'],
20
+ };
21
+ export function validTimeRanges() {
22
+ return VALID_TIME_RANGES;
23
+ }
24
+ export function validDepths() {
25
+ return VALID_DEPTHS;
26
+ }
27
+ export function validPlatforms() {
28
+ return Object.keys(PLATFORM_DOMAINS);
29
+ }
30
+ export async function searchSocial(options) {
31
+ const query = options.query.trim();
32
+ if (!query)
33
+ throw buildUsageError('search query required', ErrorCode.UsageRequired);
34
+ const domains = resolveDomains(options.platform);
35
+ const payload = await socialPayload(options, query, domains);
36
+ const data = recordValue(payload) ?? {};
37
+ const results = normalizeResults(data.results, Boolean(options.raw));
38
+ return {
39
+ answer: typeof data.answer === 'string' ? data.answer : null,
40
+ results,
41
+ platform: options.platform ?? 'combined',
42
+ query,
43
+ result_count: results.length,
44
+ credits: numberOrNull(recordValue(data.usage)?.credits),
45
+ };
46
+ }
47
+ function resolveDomains(platform) {
48
+ if (!platform)
49
+ return [...PLATFORM_DOMAINS.combined];
50
+ const domains = [];
51
+ for (const rawPlatform of platform.toLowerCase().split(',')) {
52
+ const name = rawPlatform.trim();
53
+ if (!isPlatform(name)) {
54
+ throw buildUsageError(`unknown platform: ${name}`, ErrorCode.UsageInvalid, { valid: validPlatforms() });
55
+ }
56
+ domains.push(...PLATFORM_DOMAINS[name]);
57
+ }
58
+ return [...new Set(domains)];
59
+ }
60
+ function isPlatform(value) {
61
+ return Object.hasOwn(PLATFORM_DOMAINS, value);
62
+ }
63
+ async function socialPayload(options, query, domains) {
64
+ const request = buildRequestBody(options, query, domains);
65
+ const explicitKey = process.env.TAVILY_API_KEY;
66
+ if (explicitKey)
67
+ return requestTavily(request, { apiKey: explicitKey, fingerprint: 'env' }, options.timeoutSeconds);
68
+ const response = await callWithRetry('tavily', (key) => requestTavily(request, key, options.timeoutSeconds));
69
+ if (response !== null)
70
+ return response;
71
+ throw new EscliError('no Tavily API key', {
72
+ code: ErrorCode.AuthRequired,
73
+ exitCode: ExitCodes.Auth,
74
+ remediation: { hint: 'Set TAVILY_API_KEY or authenticate with escli auth login.' },
75
+ });
76
+ }
77
+ function buildRequestBody(options, query, domains) {
78
+ const body = {
79
+ query,
80
+ include_domains: domains,
81
+ max_results: options.maxResults ?? 10,
82
+ search_depth: options.depth ?? 'advanced',
83
+ include_answer: Boolean(options.answer),
84
+ include_raw_content: Boolean(options.raw),
85
+ include_images: Boolean(options.images),
86
+ include_usage: true,
87
+ };
88
+ if (options.time)
89
+ body.time_range = options.time;
90
+ if (options.country)
91
+ body.country = options.country;
92
+ return body;
93
+ }
94
+ async function requestTavily(body, key, timeoutSeconds) {
95
+ const responseText = await requestText(`${tavilyBaseUrl()}/search`, {
96
+ method: 'POST',
97
+ headers: { 'authorization': `Bearer ${key.apiKey}`, 'content-type': 'application/json' },
98
+ body: JSON.stringify(body),
99
+ timeoutMs: timeoutMs(timeoutSeconds),
100
+ });
101
+ return parseJsonResponse(responseText);
102
+ }
103
+ async function requestText(url, options) {
104
+ const controller = new AbortController();
105
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
106
+ try {
107
+ const response = await fetch(url, {
108
+ method: options.method,
109
+ headers: options.headers,
110
+ body: options.body,
111
+ redirect: 'follow',
112
+ signal: controller.signal,
113
+ });
114
+ const text = await response.text();
115
+ if (!response.ok)
116
+ throw httpStatusError(response.status, formatHttpError(response.status, text), text);
117
+ return text;
118
+ }
119
+ catch (error) {
120
+ if (isHttpStatusError(error))
121
+ throw mapHttpStatusError(error);
122
+ if (error instanceof EscliError)
123
+ throw error;
124
+ throw mapNetworkError(error);
125
+ }
126
+ finally {
127
+ clearTimeout(timeout);
128
+ }
129
+ }
130
+ function parseJsonResponse(text) {
131
+ try {
132
+ return JSON.parse(text);
133
+ }
134
+ catch (error) {
135
+ throw new EscliError(`invalid JSON response: ${error instanceof Error ? error.message : String(error)}`, {
136
+ code: ErrorCode.GateInvalidResponse,
137
+ exitCode: ExitCodes.Error,
138
+ details: text,
139
+ });
140
+ }
141
+ }
142
+ function normalizeResults(results, includeRawContent) {
143
+ if (!Array.isArray(results))
144
+ return [];
145
+ return results
146
+ .map((item) => recordValue(item))
147
+ .filter((item) => Boolean(item))
148
+ .map((item) => ({
149
+ title: String(item.title ?? ''),
150
+ url: String(item.url ?? ''),
151
+ content: String(item.content ?? ''),
152
+ score: numberOrNull(item.score),
153
+ raw_content: includeRawContent ? stringOrNull(item.raw_content) : null,
154
+ }));
155
+ }
156
+ function tavilyBaseUrl() {
157
+ if ((typeof __ESCLI_TEST__ === 'undefined' || __ESCLI_TEST__) && process.env.ESCLI_TEST_TAVILY_URL)
158
+ return process.env.ESCLI_TEST_TAVILY_URL;
159
+ return TAVILY_API;
160
+ }
161
+ function timeoutMs(timeoutSeconds) {
162
+ const seconds = timeoutSeconds ?? DEFAULT_TIMEOUT_MS / 1000;
163
+ return Math.max(1, seconds) * 1000;
164
+ }
165
+ function httpStatusError(status, message, details) {
166
+ const error = new Error(message);
167
+ error.status = status;
168
+ error.details = details;
169
+ return error;
170
+ }
171
+ function isHttpStatusError(error) {
172
+ return error instanceof Error && typeof error.status === 'number';
173
+ }
174
+ function mapHttpStatusError(error) {
175
+ const status = error.status;
176
+ if (status === 401)
177
+ return new EscliError(error.message, { code: ErrorCode.ApiUnauthorized, details: error.details });
178
+ if (status === 403)
179
+ return new EscliError(error.message, { code: ErrorCode.ApiForbidden, details: error.details });
180
+ if (status === 404)
181
+ return new EscliError(error.message, { code: ErrorCode.ApiError, exitCode: ExitCodes.NotFound, details: error.details });
182
+ if (status === 429)
183
+ return new EscliError(error.message, { code: ErrorCode.ApiRateLimited, details: error.details });
184
+ if (status >= 500)
185
+ return new EscliError(error.message, { code: ErrorCode.ServiceUnavailable, details: error.details });
186
+ return new EscliError(error.message, { code: ErrorCode.SocialFailed, details: error.details });
187
+ }
188
+ function mapNetworkError(error) {
189
+ const isAbort = error instanceof Error && error.name === 'AbortError';
190
+ return new EscliError(isAbort ? 'network error: request timed out' : `network error: ${error instanceof Error ? error.message : String(error)}`, {
191
+ code: isAbort ? ErrorCode.NetworkTimeout : ErrorCode.NetworkError,
192
+ details: error instanceof Error ? error.message : error,
193
+ });
194
+ }
195
+ function formatHttpError(status, body) {
196
+ const parsed = parseMaybeJson(body);
197
+ const extracted = extractErrorMessage(parsed) ?? (typeof parsed === 'string' ? parsed : undefined);
198
+ return extracted ? `API error (${status}): ${extracted}` : `API error (${status})`;
199
+ }
200
+ function parseMaybeJson(value) {
201
+ try {
202
+ return JSON.parse(value);
203
+ }
204
+ catch {
205
+ return value;
206
+ }
207
+ }
208
+ function extractErrorMessage(body) {
209
+ const root = recordValue(body);
210
+ return stringValue(root?.error) ?? stringValue(root?.message) ?? stringValue(recordValue(root?.error)?.message) ?? stringValue(recordValue(root?.detail)?.error);
211
+ }
212
+ function recordValue(value) {
213
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
214
+ }
215
+ function stringValue(value) {
216
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
217
+ }
218
+ function stringOrNull(value) {
219
+ return typeof value === 'string' ? value : null;
220
+ }
221
+ function numberOrNull(value) {
222
+ return typeof value === 'number' ? value : null;
223
+ }
224
+ //# sourceMappingURL=social.js.map
@@ -0,0 +1,165 @@
1
+ import { ErrorCode } from '@eightstate/contracts/errors';
2
+ import { ExitCodes } from '@eightstate/contracts/exit-codes';
3
+ import { EscliError } from '../lib/escli-error.js';
4
+ import { resolveCliToken } from './credentials.js';
5
+ const DEFAULT_GATE_URL = 'https://internal.eightstate.co';
6
+ const DEFAULT_TIMEOUT_MS = 30_000;
7
+ export async function fetchUsage(options) {
8
+ const token = resolveCliToken();
9
+ if (!token) {
10
+ throw new EscliError('not authenticated', {
11
+ code: ErrorCode.AuthRequired,
12
+ exitCode: ExitCodes.Auth,
13
+ remediation: { command: 'escli auth login' },
14
+ });
15
+ }
16
+ const days = normalizeDays(options.days);
17
+ const service = options.service;
18
+ const mode = usageMode(options);
19
+ const request = buildUsageRequest(mode, days, service);
20
+ const data = await requestUsage(request.path, request.query, token, (options.timeoutSeconds ?? 30) * 1000);
21
+ if (mode === 'pricing')
22
+ return data;
23
+ if (mode === 'daily' || mode === 'service') {
24
+ return {
25
+ days,
26
+ service: service ?? null,
27
+ start: request.query.start,
28
+ end: request.query.end,
29
+ ...data,
30
+ };
31
+ }
32
+ return {
33
+ days,
34
+ start: request.query.start,
35
+ end: request.query.end,
36
+ ...data,
37
+ };
38
+ }
39
+ export function normalizeDays(value) {
40
+ return Math.max(1, Math.trunc(value || 30));
41
+ }
42
+ function usageMode(options) {
43
+ if (options.pricing)
44
+ return 'pricing';
45
+ if (options.daily)
46
+ return 'daily';
47
+ if (options.service)
48
+ return 'service';
49
+ return 'summary';
50
+ }
51
+ function buildUsageRequest(mode, days, service) {
52
+ if (mode === 'pricing')
53
+ return { path: '/api/analytics/pricing', query: {} };
54
+ const [start, end] = dateRange(days);
55
+ const query = { start, end };
56
+ if (service)
57
+ query.service = service;
58
+ if (mode === 'daily')
59
+ return { path: '/api/analytics/by-day', query };
60
+ if (mode === 'service')
61
+ return { path: '/api/analytics/by-service', query };
62
+ return { path: '/api/analytics/summary', query };
63
+ }
64
+ function dateRange(days) {
65
+ const end = new Date();
66
+ const start = new Date(end.getTime() - days * 24 * 60 * 60 * 1000);
67
+ return [start.toISOString(), end.toISOString()];
68
+ }
69
+ async function requestUsage(path, query, token, timeoutMs) {
70
+ const controller = new AbortController();
71
+ const timeout = setTimeout(() => controller.abort(), timeoutMs || DEFAULT_TIMEOUT_MS);
72
+ try {
73
+ const url = new URL(path, gateUrl());
74
+ for (const [key, value] of Object.entries(query))
75
+ url.searchParams.set(key, value);
76
+ const response = await fetch(url, {
77
+ method: 'GET',
78
+ headers: { 'authorization': `Bearer ${token}`, 'accept': 'application/json' },
79
+ signal: controller.signal,
80
+ });
81
+ const body = await parseUsageBody(response);
82
+ if (!response.ok)
83
+ throw mapHttpError(response.status, body);
84
+ const record = recordValue(body);
85
+ if (!record) {
86
+ throw new EscliError('invalid JSON response: response was not an object', { code: ErrorCode.GateInvalidResponse, details: body });
87
+ }
88
+ return record;
89
+ }
90
+ catch (error) {
91
+ if (error instanceof EscliError)
92
+ throw error;
93
+ throw mapNetworkError(error);
94
+ }
95
+ finally {
96
+ clearTimeout(timeout);
97
+ }
98
+ }
99
+ async function parseUsageBody(response) {
100
+ const text = await response.text();
101
+ if (!text)
102
+ return {};
103
+ try {
104
+ return JSON.parse(text);
105
+ }
106
+ catch (error) {
107
+ throw new EscliError(`invalid JSON response: ${error instanceof Error ? error.message : String(error)}`, {
108
+ code: ErrorCode.GateInvalidResponse,
109
+ details: text,
110
+ });
111
+ }
112
+ }
113
+ function mapHttpError(status, body) {
114
+ if (status === 401) {
115
+ return new EscliError('auth failed (401) — re-run: escli auth login', {
116
+ code: ErrorCode.AuthInvalid,
117
+ exitCode: ExitCodes.Auth,
118
+ details: body,
119
+ remediation: { command: 'escli auth login' },
120
+ });
121
+ }
122
+ if (status === 403) {
123
+ return new EscliError('forbidden (403)', { code: ErrorCode.AuthForbidden, exitCode: ExitCodes.Auth, details: body });
124
+ }
125
+ const message = `API error (${status}): ${extractErrorMessage(body) ?? stringifyBody(body)}`;
126
+ if (status === 404)
127
+ return new EscliError(message, { code: ErrorCode.UsageFetchFailed, exitCode: ExitCodes.NotFound, details: body });
128
+ if (status === 429)
129
+ return new EscliError(message, { code: ErrorCode.ApiRateLimited, exitCode: ExitCodes.Transient, details: body });
130
+ if (status >= 500)
131
+ return new EscliError(message, { code: ErrorCode.ServiceUnavailable, exitCode: ExitCodes.Transient, details: body });
132
+ return new EscliError(message, { code: ErrorCode.ApiError, details: body });
133
+ }
134
+ function mapNetworkError(error) {
135
+ const isAbort = error instanceof Error && error.name === 'AbortError';
136
+ return new EscliError(isAbort ? 'network error: request timed out' : `network error: ${error instanceof Error ? error.message : String(error)}`, {
137
+ code: isAbort ? ErrorCode.NetworkTimeout : ErrorCode.NetworkError,
138
+ exitCode: ExitCodes.Transient,
139
+ details: error instanceof Error ? error.message : error,
140
+ });
141
+ }
142
+ function extractErrorMessage(body) {
143
+ const root = recordValue(body);
144
+ return stringValue(root?.error) ?? stringValue(root?.message) ?? stringValue(recordValue(root?.error)?.message);
145
+ }
146
+ function stringifyBody(body) {
147
+ if (typeof body === 'string')
148
+ return body;
149
+ try {
150
+ return JSON.stringify(body);
151
+ }
152
+ catch {
153
+ return String(body);
154
+ }
155
+ }
156
+ function gateUrl() {
157
+ return process.env.ESCLI_GATE_URL ?? DEFAULT_GATE_URL;
158
+ }
159
+ function recordValue(value) {
160
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
161
+ }
162
+ function stringValue(value) {
163
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
164
+ }
165
+ //# sourceMappingURL=usage.js.map