@aisa-one/cli 0.3.0 → 0.5.1

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 (103) hide show
  1. package/CHANGELOG.md +165 -1
  2. package/LICENSE +21 -0
  3. package/README.md +164 -170
  4. package/dist/api.d.ts +10 -0
  5. package/dist/api.js +14 -3
  6. package/dist/catalog.d.ts +1 -18
  7. package/dist/catalog.js +1 -24
  8. package/dist/cli-error.d.ts +17 -0
  9. package/dist/cli-error.js +25 -0
  10. package/dist/cli-guidance.d.ts +14 -0
  11. package/dist/cli-guidance.js +55 -0
  12. package/dist/commands/account.d.ts +17 -0
  13. package/dist/commands/account.js +45 -1
  14. package/dist/commands/api.d.ts +0 -11
  15. package/dist/commands/api.js +6 -175
  16. package/dist/commands/auth.d.ts +2 -1
  17. package/dist/commands/auth.js +45 -11
  18. package/dist/commands/brand-logos.d.ts +6 -0
  19. package/dist/commands/brand-logos.js +21 -0
  20. package/dist/commands/configCmd.js +8 -3
  21. package/dist/commands/connect-shared.d.ts +136 -0
  22. package/dist/commands/connect-shared.js +143 -0
  23. package/dist/commands/connect-t2.d.ts +18 -0
  24. package/dist/commands/connect-t2.js +1599 -0
  25. package/dist/commands/connect-terminal.d.ts +65 -0
  26. package/dist/commands/connect-terminal.js +822 -0
  27. package/dist/commands/connect.d.ts +31 -0
  28. package/dist/commands/connect.js +3181 -0
  29. package/dist/commands/flow.d.ts +685 -0
  30. package/dist/commands/flow.js +697 -0
  31. package/dist/commands/install.d.ts +91 -0
  32. package/dist/commands/install.js +258 -0
  33. package/dist/commands/llm-config.d.ts +103 -0
  34. package/dist/commands/llm-config.js +452 -0
  35. package/dist/commands/manifest.d.ts +68 -0
  36. package/dist/commands/manifest.js +90 -0
  37. package/dist/commands/mcp.d.ts +59 -0
  38. package/dist/commands/mcp.js +122 -44
  39. package/dist/commands/oauth-login.d.ts +33 -0
  40. package/dist/commands/oauth-login.js +333 -0
  41. package/dist/commands/prompt.d.ts +96 -0
  42. package/dist/commands/prompt.js +397 -0
  43. package/dist/commands/serve-results.d.ts +61 -0
  44. package/dist/commands/serve-results.js +245 -0
  45. package/dist/commands/serve-signin.d.ts +28 -0
  46. package/dist/commands/serve-signin.js +90 -0
  47. package/dist/commands/signin-page.d.ts +49 -0
  48. package/dist/commands/signin-page.js +250 -0
  49. package/dist/commands/skills.js +87 -50
  50. package/dist/commands/tool-help.d.ts +69 -0
  51. package/dist/commands/tool-help.js +189 -0
  52. package/dist/commands/tool-input.d.ts +22 -0
  53. package/dist/commands/tool-input.js +220 -0
  54. package/dist/commands/tools.d.ts +12 -0
  55. package/dist/commands/tools.js +306 -0
  56. package/dist/commands/update.d.ts +11 -0
  57. package/dist/commands/update.js +92 -0
  58. package/dist/commands/vscode.d.ts +65 -0
  59. package/dist/commands/vscode.js +173 -0
  60. package/dist/commands/wrappers.d.ts +23 -0
  61. package/dist/commands/wrappers.js +102 -0
  62. package/dist/completion.js +3 -80
  63. package/dist/config.d.ts +3 -0
  64. package/dist/config.js +80 -3
  65. package/dist/constants.d.ts +51 -38
  66. package/dist/constants.js +53 -109
  67. package/dist/index.js +152 -355
  68. package/dist/json-text.d.ts +12 -0
  69. package/dist/json-text.js +60 -0
  70. package/dist/router.d.ts +39 -0
  71. package/dist/router.js +59 -0
  72. package/dist/skills-registry.js +12 -3
  73. package/dist/utils/banner.d.ts +9 -0
  74. package/dist/utils/banner.js +41 -0
  75. package/dist/utils/browser.d.ts +19 -0
  76. package/dist/utils/browser.js +31 -0
  77. package/dist/utils/exec.d.ts +43 -0
  78. package/dist/utils/exec.js +61 -0
  79. package/dist/utils/http.d.ts +53 -0
  80. package/dist/utils/http.js +124 -0
  81. package/dist/utils/install-method.d.ts +36 -0
  82. package/dist/utils/install-method.js +48 -0
  83. package/dist/utils/journal.d.ts +57 -0
  84. package/dist/utils/journal.js +188 -0
  85. package/dist/utils/key-check.d.ts +20 -0
  86. package/dist/utils/key-check.js +24 -0
  87. package/dist/utils/update-check.d.ts +23 -0
  88. package/dist/utils/update-check.js +116 -0
  89. package/dist/utils/width.d.ts +32 -0
  90. package/dist/utils/width.js +73 -0
  91. package/package.json +8 -4
  92. package/vscode-extension/README.md +6 -0
  93. package/vscode-extension/aisa-vscode.vsix +0 -0
  94. package/dist/commands/finance.d.ts +0 -17
  95. package/dist/commands/finance.js +0 -342
  96. package/dist/commands/run.d.ts +0 -28
  97. package/dist/commands/run.js +0 -182
  98. package/dist/commands/search.d.ts +0 -9
  99. package/dist/commands/search.js +0 -81
  100. package/dist/commands/twitter.d.ts +0 -138
  101. package/dist/commands/twitter.js +0 -374
  102. package/dist/commands/video.d.ts +0 -49
  103. package/dist/commands/video.js +0 -311
@@ -1,342 +0,0 @@
1
- import ora from "ora";
2
- import chalk from "chalk";
3
- import { requireApiKey } from "../config.js";
4
- import { apiRequest, cacheScope } from "../api.js";
5
- import { error, formatJson, hint } from "../utils/display.js";
6
- import { COINGECKO_IDS } from "../constants.js";
7
- import { readCache, writeCache } from "../cache.js";
8
- const coinListCacheKey = () => `coingecko/${cacheScope()}/coins-list.json`;
9
- const COIN_LIST_TTL_MS = 7 * 24 * 60 * 60 * 1000;
10
- const FIELD_ENDPOINTS = {
11
- info: "financial/company/facts",
12
- estimates: "financial/analyst-estimates",
13
- financials: "financial/financials",
14
- filings: "financial/filings",
15
- insider: "financial/insider-trades",
16
- institutional: "financial/institutional-ownership",
17
- news: "financial/news",
18
- };
19
- export async function stockAction(symbol, options) {
20
- const key = requireApiKey();
21
- const field = options.field || "info";
22
- const ticker = symbol.toUpperCase();
23
- // Default: show a summary of company info + estimates + news
24
- if (!options.field) {
25
- const spinner = ora(`Fetching ${ticker}...`).start();
26
- const [infoRes, estRes, newsRes] = await Promise.all([
27
- apiRequest(key, "financial/company/facts", { query: { ticker }, domain: true }),
28
- apiRequest(key, "financial/analyst-estimates", { query: { ticker }, domain: true }),
29
- apiRequest(key, "financial/news", { query: { ticker, limit: "3" }, domain: true }),
30
- ]);
31
- spinner.stop();
32
- if (options.raw) {
33
- console.log(JSON.stringify({ info: infoRes.data, estimates: estRes.data, news: newsRes.data }));
34
- return;
35
- }
36
- // Company info
37
- const facts = infoRes.data?.company_facts;
38
- if (facts) {
39
- console.log(`\n ${chalk.cyan.bold(facts.name || ticker)} ${chalk.gray(`(${facts.ticker || ticker})`)}`);
40
- const details = [facts.exchange, facts.sector, facts.industry].filter(Boolean).join(" · ");
41
- if (details)
42
- console.log(` ${chalk.gray(details)}`);
43
- if (facts.location)
44
- console.log(` ${chalk.gray(facts.location)}`);
45
- }
46
- else {
47
- console.log(`\n ${chalk.cyan.bold(ticker)}`);
48
- }
49
- // Analyst estimates
50
- const estimates = estRes.data?.analyst_estimates;
51
- if (estimates && estimates.length > 0) {
52
- console.log(`\n ${chalk.white.bold("Analyst Estimates")}`);
53
- for (const e of estimates.slice(0, 3)) {
54
- const period = e.fiscal_period?.split("T")[0] || e.period || "";
55
- const eps = e.earnings_per_share != null ? `EPS $${e.earnings_per_share.toFixed(2)}` : "";
56
- const rev = e.revenue != null ? `Rev $${(e.revenue / 1e9).toFixed(1)}B` : "";
57
- console.log(` ${chalk.gray(period)} ${eps} ${rev}`);
58
- }
59
- }
60
- // Latest news
61
- const news = newsRes.data?.news;
62
- if (news && news.length > 0) {
63
- console.log(`\n ${chalk.white.bold("Latest News")}`);
64
- for (const n of news) {
65
- const date = n.date ? new Date(n.date).toLocaleDateString() : "";
66
- console.log(` ${chalk.gray(date)} ${n.title}`);
67
- }
68
- }
69
- console.log();
70
- return;
71
- }
72
- // Specific field
73
- const endpoint = FIELD_ENDPOINTS[field];
74
- if (!endpoint) {
75
- error(`Unknown field: ${field}. Valid: ${Object.keys(FIELD_ENDPOINTS).join(", ")}`);
76
- return;
77
- }
78
- const spinner = ora(`Fetching ${field} for ${ticker}...`).start();
79
- const res = await apiRequest(key, endpoint, {
80
- query: { ticker },
81
- domain: true,
82
- });
83
- if (!res.success) {
84
- spinner.fail(`Failed to fetch ${field}`);
85
- error(res.error || "Unknown error");
86
- return;
87
- }
88
- spinner.stop();
89
- if (options.raw) {
90
- console.log(JSON.stringify(res.data));
91
- }
92
- else {
93
- console.log(formatJson(res.data));
94
- }
95
- }
96
- const PERIOD_DAYS = { "1d": 1, "7d": 7, "30d": 30, "90d": 90, "1y": 365 };
97
- /**
98
- * Resolve a user-typed symbol to a CoinGecko coin id: alias table first, then
99
- * the full coin list (cached, ~18k entries). Ambiguous symbols are reported
100
- * rather than guessed — picking one would silently price the wrong token.
101
- */
102
- async function resolveCoinId(key, symbol) {
103
- const lower = symbol.toLowerCase();
104
- if (COINGECKO_IDS[lower])
105
- return COINGECKO_IDS[lower];
106
- const cached = readCache(coinListCacheKey());
107
- let coins = cached?.fresh ? cached.data : undefined;
108
- if (!coins) {
109
- const res = await apiRequest(key, "coingecko/coins/list", { domain: true });
110
- if (res.success && Array.isArray(res.data)) {
111
- coins = res.data;
112
- writeCache(coinListCacheKey(), coins, COIN_LIST_TTL_MS);
113
- }
114
- else {
115
- coins = cached?.data;
116
- }
117
- }
118
- if (!coins)
119
- throw new Error(`Cannot resolve "${symbol}" — pass --id <coingecko-id>`);
120
- // An exact id match is unambiguous, so accept it before considering symbols.
121
- if (coins.some((c) => c.id === lower))
122
- return lower;
123
- const matches = coins.filter((c) => c.symbol.toLowerCase() === lower);
124
- if (matches.length === 1)
125
- return matches[0].id;
126
- if (matches.length > 1) {
127
- const shown = matches.slice(0, 8).map((c) => `${c.id} (${c.name})`);
128
- throw new Error(`"${symbol}" matches ${matches.length} coins — pick one with --id:\n ${shown.join("\n ")}`);
129
- }
130
- throw new Error(`Unknown coin "${symbol}" — pass --id <coingecko-id>`);
131
- }
132
- function formatUsd(value) {
133
- if (value >= 1)
134
- return `$${value.toLocaleString("en-US", { maximumFractionDigits: 2 })}`;
135
- return `$${value.toPrecision(4)}`;
136
- }
137
- function formatBig(value) {
138
- if (value >= 1e12)
139
- return `$${(value / 1e12).toFixed(2)}T`;
140
- if (value >= 1e9)
141
- return `$${(value / 1e9).toFixed(2)}B`;
142
- if (value >= 1e6)
143
- return `$${(value / 1e6).toFixed(2)}M`;
144
- return `$${value.toFixed(0)}`;
145
- }
146
- export async function cryptoAction(symbol, options) {
147
- const key = requireApiKey();
148
- // The financial provider's crypto endpoints return 403 for standard keys;
149
- // coingecko covers the same ground and is cheaper, so it is the default.
150
- if (options.source === "financial") {
151
- return cryptoViaFinancial(key, symbol, options);
152
- }
153
- const spinner = ora(`Fetching ${symbol.toUpperCase()}...`).start();
154
- let coinId;
155
- try {
156
- coinId = options.id || (await resolveCoinId(key, symbol));
157
- }
158
- catch (err) {
159
- spinner.stop();
160
- error(err.message);
161
- return;
162
- }
163
- const historical = options.period && options.period !== "current";
164
- const endpoint = historical
165
- ? `coingecko/coins/${encodeURIComponent(coinId)}/market_chart`
166
- : "coingecko/simple/price";
167
- const query = historical
168
- ? { vs_currency: "usd", days: String(PERIOD_DAYS[options.period] || 30) }
169
- : {
170
- ids: coinId,
171
- vs_currencies: "usd",
172
- include_market_cap: "true",
173
- include_24hr_vol: "true",
174
- include_24hr_change: "true",
175
- include_last_updated_at: "true",
176
- };
177
- const res = await apiRequest(key, endpoint, { query, domain: true });
178
- if (!res.success) {
179
- spinner.fail("Failed to fetch crypto price");
180
- error(res.error || "Unknown error");
181
- return;
182
- }
183
- spinner.stop();
184
- if (options.raw) {
185
- console.log(JSON.stringify(res.data));
186
- return;
187
- }
188
- if (historical) {
189
- const prices = res.data.prices || [];
190
- if (prices.length === 0) {
191
- console.log(formatJson(res.data));
192
- return;
193
- }
194
- const first = prices[0][1];
195
- const last = prices[prices.length - 1][1];
196
- const change = ((last - first) / first) * 100;
197
- console.log(`\n ${chalk.cyan.bold(coinId)} ${chalk.gray(`· ${options.period}`)}`);
198
- console.log(` Now: ${formatUsd(last)}`);
199
- console.log(` Start: ${formatUsd(first)}`);
200
- console.log(` Change: ${change >= 0 ? chalk.green(`+${change.toFixed(2)}%`) : chalk.red(`${change.toFixed(2)}%`)}`);
201
- console.log(chalk.gray(` ${prices.length} data points`));
202
- console.log();
203
- return;
204
- }
205
- const snapshot = res.data[coinId];
206
- if (!snapshot) {
207
- console.log(formatJson(res.data));
208
- return;
209
- }
210
- const change = snapshot.usd_24h_change;
211
- console.log(`\n ${chalk.cyan.bold(coinId)} ${chalk.gray(`(${symbol.toUpperCase()})`)}`);
212
- console.log(` Price: ${formatUsd(snapshot.usd)}`);
213
- if (change != null) {
214
- console.log(` 24h: ${change >= 0 ? chalk.green(`+${change.toFixed(2)}%`) : chalk.red(`${change.toFixed(2)}%`)}`);
215
- }
216
- if (snapshot.usd_market_cap != null)
217
- console.log(` Mkt cap: ${formatBig(snapshot.usd_market_cap)}`);
218
- if (snapshot.usd_24h_vol != null)
219
- console.log(` 24h vol: ${formatBig(snapshot.usd_24h_vol)}`);
220
- console.log();
221
- }
222
- /** Legacy path, kept behind --source for accounts that do have crypto access. */
223
- async function cryptoViaFinancial(key, symbol, options) {
224
- const spinner = ora(`Fetching ${symbol.toUpperCase()}...`).start();
225
- const ticker = symbol.toUpperCase().includes("-") ? symbol.toUpperCase() : `${symbol.toUpperCase()}-USD`;
226
- const query = { ticker };
227
- let endpoint;
228
- if (!options.period || options.period === "current") {
229
- endpoint = "financial/crypto/prices/snapshot";
230
- }
231
- else {
232
- endpoint = "financial/crypto/prices";
233
- const start = new Date();
234
- start.setDate(start.getDate() - (PERIOD_DAYS[options.period] || 30));
235
- query.interval = "day";
236
- query.interval_multiplier = "1";
237
- query.start_date = start.toISOString().split("T")[0];
238
- query.end_date = new Date().toISOString().split("T")[0];
239
- }
240
- const res = await apiRequest(key, endpoint, { query, domain: true });
241
- if (!res.success) {
242
- spinner.fail("Failed to fetch crypto price");
243
- error(res.error || "Unknown error");
244
- if ((res.error || "").includes("403")) {
245
- hint("Crypto access is restricted on this provider — omit --source to use coingecko");
246
- }
247
- return;
248
- }
249
- spinner.stop();
250
- console.log(options.raw ? JSON.stringify(res.data) : formatJson(res.data));
251
- }
252
- /**
253
- * The screener matches sectors against GICS names, so the short forms people
254
- * naturally type ("Technology", "Healthcare") silently return zero results.
255
- */
256
- const GICS_SECTORS = [
257
- "Energy",
258
- "Materials",
259
- "Industrials",
260
- "Consumer Discretionary",
261
- "Consumer Staples",
262
- "Health Care",
263
- "Financials",
264
- "Information Technology",
265
- "Communication Services",
266
- "Utilities",
267
- "Real Estate",
268
- ];
269
- const SECTOR_ALIASES = {
270
- tech: "Information Technology",
271
- technology: "Information Technology",
272
- it: "Information Technology",
273
- healthcare: "Health Care",
274
- health: "Health Care",
275
- finance: "Financials",
276
- financial: "Financials",
277
- telecom: "Communication Services",
278
- communications: "Communication Services",
279
- consumer: "Consumer Discretionary",
280
- staples: "Consumer Staples",
281
- discretionary: "Consumer Discretionary",
282
- realestate: "Real Estate",
283
- utility: "Utilities",
284
- };
285
- function normalizeSector(input) {
286
- const exact = GICS_SECTORS.find((s) => s.toLowerCase() === input.toLowerCase());
287
- if (exact)
288
- return exact;
289
- return SECTOR_ALIASES[input.toLowerCase().replace(/[\s_-]/g, "")] || input;
290
- }
291
- /** Parse a `field:operator:value` filter, e.g. `market_cap:gt:1e12`. */
292
- function parseFilter(raw) {
293
- const parts = raw.split(":");
294
- if (parts.length < 3) {
295
- throw new Error(`Invalid filter "${raw}". Use field:operator:value, e.g. market_cap:gt:1000000000`);
296
- }
297
- const [field, operator, ...rest] = parts;
298
- const value = rest.join(":");
299
- const num = Number(value);
300
- return { field, operator, value: value !== "" && !Number.isNaN(num) ? num : value };
301
- }
302
- export async function screenerAction(options) {
303
- const key = requireApiKey();
304
- const filters = [];
305
- if (options.sector) {
306
- filters.push({ field: "sector", operator: "eq", value: normalizeSector(options.sector) });
307
- }
308
- if (options.minMarketCap) {
309
- filters.push({ field: "market_cap", operator: "gt", value: Number(options.minMarketCap) });
310
- }
311
- for (const raw of options.filter || []) {
312
- filters.push(parseFilter(raw));
313
- }
314
- // The upstream rejects an empty filter set outright, so give unfiltered runs
315
- // a broad default rather than surfacing "Please include filters".
316
- if (filters.length === 0) {
317
- filters.push({ field: "market_cap", operator: "gt", value: 1_000_000_000 });
318
- hint("No filters given — defaulting to market cap > $1B");
319
- }
320
- const spinner = ora("Running stock screener...").start();
321
- const body = { filters };
322
- if (options.limit)
323
- body.limit = parseInt(options.limit);
324
- const res = await apiRequest(key, "financial/financials/search/screener", { method: "POST", body, domain: true });
325
- if (!res.success) {
326
- spinner.fail("Screener failed");
327
- error(res.error || "Unknown error");
328
- return;
329
- }
330
- spinner.stop();
331
- if (options.raw) {
332
- console.log(JSON.stringify(res.data));
333
- return;
334
- }
335
- const results = res.data?.results;
336
- if (Array.isArray(results) && results.length === 0 && options.sector) {
337
- console.log(" No matches.");
338
- hint(`Sector must be a GICS name: ${GICS_SECTORS.join(", ")}`);
339
- return;
340
- }
341
- console.log(formatJson(res.data));
342
- }
@@ -1,28 +0,0 @@
1
- export interface RunTarget {
2
- /** Which base URL to use: the LLM gateway (/v1) or integration APIs (/apis/v1). */
3
- base: "llm" | "domain";
4
- /** Path relative to that base, with no leading slash. */
5
- endpoint: string;
6
- }
7
- /**
8
- * Decide which base URL a `run` invocation targets.
9
- *
10
- * Integration APIs are the default: there are ~29 providers and the set grows
11
- * without a CLI release, so anything not recognised as an LLM gateway route
12
- * goes to /apis/v1. See LLM_ROUTE_PREFIXES for why the whitelist sits on that
13
- * side.
14
- */
15
- export declare function resolveRunTarget(slug: string, path: string, options?: {
16
- llm?: boolean;
17
- domain?: boolean;
18
- }): RunTarget;
19
- export declare function runAction(slug: string, path: string, options: {
20
- q?: string[];
21
- d?: string;
22
- method?: string;
23
- raw?: boolean;
24
- stream?: boolean;
25
- llm?: boolean;
26
- domain?: boolean;
27
- showCost?: boolean;
28
- }): Promise<void>;
@@ -1,182 +0,0 @@
1
- import chalk from "chalk";
2
- import ora from "ora";
3
- import { requireApiKey } from "../config.js";
4
- import { apiRequest, apiRequestRaw, parseCost } from "../api.js";
5
- import { error, formatJson, hint } from "../utils/display.js";
6
- import { handleSSEStream } from "../utils/streaming.js";
7
- import { LLM_ROUTE_PREFIXES } from "../constants.js";
8
- import { getProviders } from "../catalog.js";
9
- /**
10
- * Decide which base URL a `run` invocation targets.
11
- *
12
- * Integration APIs are the default: there are ~29 providers and the set grows
13
- * without a CLI release, so anything not recognised as an LLM gateway route
14
- * goes to /apis/v1. See LLM_ROUTE_PREFIXES for why the whitelist sits on that
15
- * side.
16
- */
17
- export function resolveRunTarget(slug, path, options = {}) {
18
- const cleanSlug = slug.replace(/^\/+/, "").replace(/\/+$/, "");
19
- const cleanPath = path.replace(/^\/+/, "");
20
- const endpoint = cleanPath ? `${cleanSlug}/${cleanPath}` : cleanSlug;
21
- let base;
22
- if (options.llm) {
23
- base = "llm";
24
- }
25
- else if (options.domain) {
26
- base = "domain";
27
- }
28
- else {
29
- base = LLM_ROUTE_PREFIXES.includes(cleanSlug.split("/")[0]) ? "llm" : "domain";
30
- }
31
- return { base, endpoint };
32
- }
33
- function editDistance(a, b) {
34
- const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
35
- for (let i = 1; i <= a.length; i++) {
36
- let diag = prev[0];
37
- prev[0] = i;
38
- for (let j = 1; j <= b.length; j++) {
39
- const temp = prev[j];
40
- prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
41
- diag = temp;
42
- }
43
- }
44
- return prev[b.length];
45
- }
46
- /**
47
- * Turn a 404 into a usable next step. This runs only on failure, so the catalog
48
- * stays a diagnostic aid rather than something `run` depends on to work.
49
- */
50
- async function suggestSlug(slug) {
51
- try {
52
- const providers = await getProviders();
53
- const ids = providers.map((p) => p.id);
54
- const near = ids
55
- .map((id) => ({ id, d: editDistance(slug.toLowerCase(), id.toLowerCase()) }))
56
- .filter((c) => c.d <= 3 || c.id.includes(slug) || slug.includes(c.id))
57
- .sort((a, b) => a.d - b.d)
58
- .slice(0, 3)
59
- .map((c) => c.id);
60
- if (near.length > 0) {
61
- hint(`Did you mean: ${near.join(", ")}?`);
62
- }
63
- hint("List APIs: aisa api list");
64
- }
65
- catch {
66
- // catalog unavailable — the error above is enough
67
- }
68
- }
69
- /**
70
- * Report what a call cost, on stderr so `--raw | jq` stays a clean pipe.
71
- *
72
- * Only metered routes report an amount; plain integration responses carry a
73
- * price key and the LLM gateway sends no billing headers at all. Say so rather
74
- * than printing nothing, so an empty report is not read as "this was free".
75
- */
76
- function reportCost(cost) {
77
- const dim = (label, value) => console.error(chalk.gray(` ${label.padEnd(12)} ${value}`));
78
- if (!cost) {
79
- console.error(chalk.gray(" cost not reported by this route"));
80
- return;
81
- }
82
- if (cost.priceUsd)
83
- dim("cost", `$${cost.priceUsd} USD`);
84
- if (cost.accountedCredits) {
85
- const estimate = cost.estimatedCredits && cost.estimatedCredits !== cost.accountedCredits
86
- ? ` (estimated ${cost.estimatedCredits})`
87
- : "";
88
- dim("credits", `${cost.accountedCredits}${estimate}`);
89
- }
90
- if (cost.creditModel)
91
- dim("credit model", cost.creditModel);
92
- if (cost.pricingStrategy) {
93
- const version = cost.pricingVersion ? ` v${cost.pricingVersion}` : "";
94
- dim("pricing", `${cost.pricingStrategy}${version}`);
95
- }
96
- if (cost.priceKey)
97
- dim("price key", cost.priceKey);
98
- if (cost.requestId)
99
- dim("request id", cost.requestId);
100
- if (!cost.priceUsd) {
101
- console.error(chalk.gray(" no amount reported — check `aisa account balance` for actual spend"));
102
- }
103
- }
104
- export async function runAction(slug, path, options) {
105
- const key = requireApiKey();
106
- // Parse query params
107
- const query = {};
108
- if (options.q) {
109
- for (const q of options.q) {
110
- const pairs = q.split("&");
111
- for (const pair of pairs) {
112
- const eqIdx = pair.indexOf("=");
113
- if (eqIdx > 0) {
114
- query[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
115
- }
116
- }
117
- }
118
- }
119
- // Parse body
120
- let body;
121
- if (options.d) {
122
- try {
123
- body = JSON.parse(options.d);
124
- }
125
- catch {
126
- error("Invalid JSON body. Use -d '{\"key\": \"value\"}'");
127
- process.exit(1);
128
- }
129
- }
130
- // Detect method
131
- const method = (options.method || (body ? "POST" : "GET")).toUpperCase();
132
- const { base, endpoint } = resolveRunTarget(slug, path, options);
133
- const isDomain = base === "domain";
134
- // Streaming mode
135
- if (options.stream) {
136
- const res = await apiRequestRaw(key, endpoint, {
137
- method,
138
- query: Object.keys(query).length > 0 ? query : undefined,
139
- body,
140
- headers: { Accept: "text/event-stream" },
141
- domain: isDomain,
142
- });
143
- if (!res.ok) {
144
- const text = await res.text();
145
- error(`${res.status}: ${text}`);
146
- if (options.showCost)
147
- reportCost(parseCost(res));
148
- return;
149
- }
150
- await handleSSEStream(res, (token) => process.stdout.write(token), () => console.log());
151
- if (options.showCost)
152
- reportCost(parseCost(res));
153
- return;
154
- }
155
- // Normal mode
156
- const spinner = ora(`Calling ${slug} ${path}...`).start();
157
- const res = await apiRequest(key, endpoint, {
158
- method,
159
- query: Object.keys(query).length > 0 ? query : undefined,
160
- body,
161
- domain: isDomain,
162
- });
163
- if (!res.success) {
164
- spinner.fail("API call failed");
165
- error(res.error || "Unknown error");
166
- if (isDomain && (res.error || "").includes("api endpoint not found")) {
167
- await suggestSlug(slug);
168
- }
169
- if (options.showCost)
170
- reportCost(res.cost);
171
- return;
172
- }
173
- spinner.stop();
174
- if (options.raw) {
175
- console.log(JSON.stringify(res.data));
176
- }
177
- else {
178
- console.log(formatJson(res.data));
179
- }
180
- if (options.showCost)
181
- reportCost(res.cost);
182
- }
@@ -1,9 +0,0 @@
1
- export declare function webSearchAction(query: string, options: {
2
- type?: string;
3
- limit?: string;
4
- raw?: boolean;
5
- }): Promise<void>;
6
- export declare function scholarAction(query: string, options: {
7
- limit?: string;
8
- raw?: boolean;
9
- }): Promise<void>;
@@ -1,81 +0,0 @@
1
- import ora from "ora";
2
- import chalk from "chalk";
3
- import { requireApiKey } from "../config.js";
4
- import { apiRequest } from "../api.js";
5
- import { error, formatJson, hint } from "../utils/display.js";
6
- export async function webSearchAction(query, options) {
7
- const key = requireApiKey();
8
- const type = options.type || "tavily";
9
- const spinner = ora(`Searching (${type})...`).start();
10
- const endpointMap = {
11
- smart: { endpoint: "search/smart", method: "GET", paramStyle: "query" },
12
- full: { endpoint: "search/full", method: "GET", paramStyle: "query" },
13
- youtube: { endpoint: "youtube/search", method: "GET", paramStyle: "query" },
14
- scholar: { endpoint: "scholar/search/scholar", method: "POST", paramStyle: "query" },
15
- tavily: { endpoint: "tavily/search", method: "POST", paramStyle: "body" },
16
- };
17
- const config = endpointMap[type];
18
- if (!config) {
19
- spinner.fail(`Unknown search type: ${type}`);
20
- error("Valid types: tavily, youtube, scholar, smart, full");
21
- return;
22
- }
23
- // Build request options
24
- const reqOpts = {
25
- method: config.method,
26
- domain: true,
27
- };
28
- if (config.paramStyle === "query") {
29
- // GET-style search endpoints use 'q' for smart/full/youtube, 'query' for scholar
30
- const qParam = type === "scholar" ? "query" : "q";
31
- reqOpts.query = { [qParam]: query };
32
- if (options.limit) {
33
- reqOpts.query[type === "scholar" ? "max_num_results" : "count"] = options.limit;
34
- }
35
- }
36
- else {
37
- // Tavily uses POST body
38
- reqOpts.body = {
39
- query,
40
- ...(options.limit ? { max_results: parseInt(options.limit) } : {}),
41
- };
42
- }
43
- const res = await apiRequest(key, config.endpoint, reqOpts);
44
- if (!res.success) {
45
- spinner.fail("Search failed");
46
- error(res.error || "Unknown error");
47
- // smart/full currently proxy to an upstream that 404s for every parameter
48
- // shape; steer users to a working backend rather than to parameter tweaks.
49
- if (type === "smart" || type === "full") {
50
- hint(`The '${type}' backend is currently degraded — try --type tavily`);
51
- }
52
- return;
53
- }
54
- spinner.stop();
55
- if (options.raw) {
56
- console.log(JSON.stringify(res.data));
57
- }
58
- else {
59
- // Format search results nicely
60
- const data = res.data;
61
- // Smart/Full search format
62
- if (data?.webPages && typeof data.webPages === "object") {
63
- const pages = data.webPages;
64
- if (pages.value) {
65
- for (const result of pages.value) {
66
- console.log(`\n ${chalk.cyan.bold(result.name)}`);
67
- console.log(` ${chalk.gray(result.url)}`);
68
- if (result.snippet)
69
- console.log(` ${result.snippet}`);
70
- }
71
- console.log(chalk.gray(`\n ${pages.value.length} results`));
72
- return;
73
- }
74
- }
75
- // Fallback to generic JSON display
76
- console.log(formatJson(res.data));
77
- }
78
- }
79
- export async function scholarAction(query, options) {
80
- return webSearchAction(query, { type: "scholar", ...options });
81
- }