@aisa-one/cli 0.0.1 → 0.1.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.
- package/README.md +203 -0
- package/dist/api.d.ts +11 -0
- package/dist/api.js +61 -0
- package/dist/commands/account.d.ts +5 -0
- package/dist/commands/account.js +47 -0
- package/dist/commands/api.d.ts +10 -0
- package/dist/commands/api.js +116 -0
- package/dist/commands/auth.d.ts +5 -0
- package/dist/commands/auth.js +29 -0
- package/dist/commands/chat.d.ts +8 -0
- package/dist/commands/chat.js +78 -0
- package/dist/commands/configCmd.d.ts +4 -0
- package/dist/commands/configCmd.js +36 -0
- package/dist/commands/finance.d.ts +13 -0
- package/dist/commands/finance.js +85 -0
- package/dist/commands/mcp.d.ts +4 -0
- package/dist/commands/mcp.js +67 -0
- package/dist/commands/models.d.ts +4 -0
- package/dist/commands/models.js +65 -0
- package/dist/commands/run.d.ts +7 -0
- package/dist/commands/run.js +70 -0
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.js +43 -0
- package/dist/commands/skills.d.ts +22 -0
- package/dist/commands/skills.js +555 -0
- package/dist/commands/twitter.d.ts +14 -0
- package/dist/commands/twitter.js +85 -0
- package/dist/commands/video.d.ts +9 -0
- package/dist/commands/video.js +73 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +56 -0
- package/dist/constants.d.ts +14 -0
- package/dist/constants.js +43 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +291 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.js +1 -0
- package/dist/utils/display.d.ts +8 -0
- package/dist/utils/display.js +33 -0
- package/dist/utils/file.d.ts +12 -0
- package/dist/utils/file.js +70 -0
- package/dist/utils/streaming.d.ts +5 -0
- package/dist/utils/streaming.js +37 -0
- package/package.json +53 -5
- package/index.js +0 -3
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @aisa-one/cli
|
|
2
|
+
|
|
3
|
+
CLI for the [AISA](https://aisa.one) unified AI infrastructure platform. Access 70+ LLMs, web search, financial data, Twitter/X, and video generation APIs — all from one command line.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @aisa-one/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Authenticate
|
|
15
|
+
aisa login --key sk-your-api-key
|
|
16
|
+
|
|
17
|
+
# Chat with any LLM
|
|
18
|
+
aisa chat "Explain quantum computing" --model claude-opus-4-6
|
|
19
|
+
|
|
20
|
+
# Search the web
|
|
21
|
+
aisa web-search "latest AI research"
|
|
22
|
+
|
|
23
|
+
# Look up stock data
|
|
24
|
+
aisa stock AAPL
|
|
25
|
+
|
|
26
|
+
# Post a tweet
|
|
27
|
+
aisa tweet "Hello from AISA CLI!"
|
|
28
|
+
|
|
29
|
+
# Generate a video
|
|
30
|
+
aisa video create "A cat playing piano" --wait
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Get your API key at [aisa.one/dashboard](https://aisa.one/dashboard). New accounts receive $5 in free credits.
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
### Authentication
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
aisa login --key <key> # Store API key (also reads AISA_API_KEY env var)
|
|
41
|
+
aisa logout # Remove stored key
|
|
42
|
+
aisa whoami # Show auth status
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### LLM Gateway
|
|
46
|
+
|
|
47
|
+
Chat with 70+ models (GPT, Claude, Gemini, Qwen, Deepseek, Grok) through a single OpenAI-compatible endpoint.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
aisa chat "your message" --model gpt-4.1
|
|
51
|
+
aisa chat "explain this" --model claude-opus-4-6 --stream
|
|
52
|
+
aisa chat "respond in JSON" --model gemini-2.5-pro --json
|
|
53
|
+
echo "summarize this" | aisa chat # pipe support
|
|
54
|
+
|
|
55
|
+
aisa models # list all models
|
|
56
|
+
aisa models --provider anthropic # filter by provider
|
|
57
|
+
aisa models show gpt-4.1 # model details + pricing
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### API Discovery & Execution
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
aisa api list # list all API endpoints
|
|
64
|
+
aisa api list --category finance # filter by category
|
|
65
|
+
aisa api search "email finder" # natural language search
|
|
66
|
+
aisa api show finance /stock/price # endpoint details + params
|
|
67
|
+
aisa api code finance /stock/price --lang python # generate code snippet
|
|
68
|
+
|
|
69
|
+
# Execute any API
|
|
70
|
+
aisa run finance /stock/price -q "symbol=AAPL"
|
|
71
|
+
aisa run tavily /search -d '{"query": "AI news"}'
|
|
72
|
+
aisa run twitter /tweet/advanced-search -q "query=AI agents" --raw
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Web Search
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
aisa web-search "query" # smart search (default)
|
|
79
|
+
aisa web-search "query" --type full # full-text search
|
|
80
|
+
aisa web-search "query" --type youtube # YouTube search
|
|
81
|
+
aisa web-search "query" --type tavily # Tavily deep search
|
|
82
|
+
aisa scholar "transformer architecture" # academic papers
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Finance
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
aisa stock AAPL # stock price
|
|
89
|
+
aisa stock MSFT --field earnings # earnings data
|
|
90
|
+
aisa stock TSLA --field filings # SEC filings
|
|
91
|
+
aisa stock NVDA --field insider # insider trades
|
|
92
|
+
aisa crypto BTC # crypto price
|
|
93
|
+
aisa crypto ETH --period 30d # historical
|
|
94
|
+
aisa screener --sector Technology # stock screener
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Twitter/X
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
aisa tweet "Hello world!" # post a tweet
|
|
101
|
+
aisa tweet "Reply" --reply-to 123456 # reply
|
|
102
|
+
aisa twitter search "AI agents" --limit 20 # search tweets
|
|
103
|
+
aisa twitter user elonmusk # user profile
|
|
104
|
+
aisa twitter trends # trending topics
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Video Generation
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
aisa video create "A sunset timelapse" # create task
|
|
111
|
+
aisa video create "Dancing robot" --wait # wait for result
|
|
112
|
+
aisa video status <task-id> # check status
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Account
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
aisa balance # credit balance
|
|
119
|
+
aisa usage --limit 20 # usage history
|
|
120
|
+
aisa usage --days 7 # last 7 days
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Skills
|
|
124
|
+
|
|
125
|
+
Skills are markdown files that teach AI coding agents (Claude Code, Cursor, Copilot, etc.) how to use AISA APIs.
|
|
126
|
+
|
|
127
|
+
### Browse & Install
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
aisa skills list # browse skills
|
|
131
|
+
aisa skills search "financial analysis" # search
|
|
132
|
+
aisa skills show aisa-team/finance-analyst # details
|
|
133
|
+
aisa skills add aisa-team/finance-analyst # install to all agents
|
|
134
|
+
aisa skills add aisa-team/llm-assistant --agent claude # specific agent
|
|
135
|
+
aisa skills remove aisa-team/finance-analyst # uninstall
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Skills install to agent directories automatically:
|
|
139
|
+
|
|
140
|
+
| Agent | Directory |
|
|
141
|
+
|-------|-----------|
|
|
142
|
+
| Claude Code | `~/.claude/skills/` |
|
|
143
|
+
| Cursor | `~/.cursor/skills/` |
|
|
144
|
+
| GitHub Copilot | `~/.github/skills/` |
|
|
145
|
+
| Windsurf | `~/.codeium/windsurf/skills/` |
|
|
146
|
+
| Codex | `~/.agents/skills/` |
|
|
147
|
+
| Gemini | `~/.gemini/skills/` |
|
|
148
|
+
| OpenClaw | `~/.openclaw/skills/` |
|
|
149
|
+
|
|
150
|
+
### Create & Publish
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
aisa skills init my-skill # create from default template
|
|
154
|
+
aisa skills init my-skill --template finance # finance template
|
|
155
|
+
aisa skills init my-skill --template llm # LLM template
|
|
156
|
+
aisa skills submit ./my-skill # submit to AISA
|
|
157
|
+
aisa skills push owner/my-skill # push updates
|
|
158
|
+
aisa skills request-verification owner/my-skill # request review
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Available templates: `default`, `llm`, `search`, `finance`, `twitter`, `video`
|
|
162
|
+
|
|
163
|
+
## MCP Server
|
|
164
|
+
|
|
165
|
+
Auto-configure AISA's MCP server for your AI agents:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
aisa mcp setup # configure all detected agents
|
|
169
|
+
aisa mcp setup --agent cursor # Cursor only
|
|
170
|
+
aisa mcp setup --agent claude-desktop # Claude Desktop only
|
|
171
|
+
aisa mcp status # check configuration
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
aisa config set defaultModel claude-opus-4-6 # change default model
|
|
178
|
+
aisa config get defaultModel # read a value
|
|
179
|
+
aisa config list # show all settings
|
|
180
|
+
aisa config reset # reset to defaults
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Settings:
|
|
184
|
+
- `defaultModel` — default model for `aisa chat` (default: `gpt-4.1`)
|
|
185
|
+
- `baseUrl` — API base URL (default: `https://api.aisa.one`)
|
|
186
|
+
- `outputFormat` — output format: `text` or `json`
|
|
187
|
+
|
|
188
|
+
Environment variable `AISA_API_KEY` takes precedence over stored key.
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
git clone https://github.com/AIsa-team/cli.git
|
|
194
|
+
cd cli
|
|
195
|
+
npm install
|
|
196
|
+
npm run build # compile TypeScript
|
|
197
|
+
npm run dev # watch mode
|
|
198
|
+
npm test # run tests
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type Response } from "node-fetch";
|
|
2
|
+
import type { ApiResponse } from "./types.js";
|
|
3
|
+
export interface RequestOptions {
|
|
4
|
+
method?: "GET" | "POST" | "PUT" | "DELETE";
|
|
5
|
+
query?: Record<string, string>;
|
|
6
|
+
body?: unknown;
|
|
7
|
+
stream?: boolean;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
export declare function apiRequest<T = unknown>(apiKey: string, endpoint: string, options?: RequestOptions): Promise<ApiResponse<T>>;
|
|
11
|
+
export declare function apiRequestRaw(apiKey: string, endpoint: string, options?: RequestOptions): Promise<Response>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
import { CLI_BASE_URL } from "./constants.js";
|
|
3
|
+
import { getConfig } from "./config.js";
|
|
4
|
+
function getBaseUrl() {
|
|
5
|
+
const custom = getConfig("baseUrl");
|
|
6
|
+
return custom || CLI_BASE_URL;
|
|
7
|
+
}
|
|
8
|
+
export async function apiRequest(apiKey, endpoint, options = {}) {
|
|
9
|
+
const { method = "GET", query, body, headers: extraHeaders } = options;
|
|
10
|
+
const baseUrl = getBaseUrl();
|
|
11
|
+
let url = `${baseUrl}/${endpoint}`;
|
|
12
|
+
if (query) {
|
|
13
|
+
const params = new URLSearchParams(query);
|
|
14
|
+
url += `?${params.toString()}`;
|
|
15
|
+
}
|
|
16
|
+
const headers = {
|
|
17
|
+
Authorization: `Bearer ${apiKey}`,
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
"x-aisa-source": "cli",
|
|
20
|
+
...extraHeaders,
|
|
21
|
+
};
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
method,
|
|
24
|
+
headers,
|
|
25
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const text = await res.text();
|
|
29
|
+
let errorMsg;
|
|
30
|
+
try {
|
|
31
|
+
const json = JSON.parse(text);
|
|
32
|
+
errorMsg = json.error?.message || json.error || json.message || text;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
errorMsg = text;
|
|
36
|
+
}
|
|
37
|
+
return { success: false, error: `${res.status}: ${errorMsg}` };
|
|
38
|
+
}
|
|
39
|
+
const data = (await res.json());
|
|
40
|
+
return { success: true, data };
|
|
41
|
+
}
|
|
42
|
+
export async function apiRequestRaw(apiKey, endpoint, options = {}) {
|
|
43
|
+
const { method = "GET", query, body, headers: extraHeaders } = options;
|
|
44
|
+
const baseUrl = getBaseUrl();
|
|
45
|
+
let url = `${baseUrl}/${endpoint}`;
|
|
46
|
+
if (query) {
|
|
47
|
+
const params = new URLSearchParams(query);
|
|
48
|
+
url += `?${params.toString()}`;
|
|
49
|
+
}
|
|
50
|
+
const headers = {
|
|
51
|
+
Authorization: `Bearer ${apiKey}`,
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
"x-aisa-source": "cli",
|
|
54
|
+
...extraHeaders,
|
|
55
|
+
};
|
|
56
|
+
return fetch(url, {
|
|
57
|
+
method,
|
|
58
|
+
headers,
|
|
59
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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, table } from "../utils/display.js";
|
|
6
|
+
export async function balanceAction() {
|
|
7
|
+
const key = requireApiKey();
|
|
8
|
+
const spinner = ora("Fetching balance...").start();
|
|
9
|
+
const res = await apiRequest(key, "credits/balance");
|
|
10
|
+
if (!res.success || !res.data) {
|
|
11
|
+
spinner.fail("Failed to fetch balance");
|
|
12
|
+
error(res.error || "Unknown error");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
spinner.stop();
|
|
16
|
+
console.log(` Balance: ${chalk.green("$" + res.data.balance.toFixed(2))} ${res.data.currency}`);
|
|
17
|
+
console.log(chalk.gray(" Add credits at https://aisa.one/dashboard/wallet"));
|
|
18
|
+
}
|
|
19
|
+
export async function usageAction(options) {
|
|
20
|
+
const key = requireApiKey();
|
|
21
|
+
const spinner = ora("Fetching usage...").start();
|
|
22
|
+
const query = {};
|
|
23
|
+
if (options.limit)
|
|
24
|
+
query.limit = options.limit;
|
|
25
|
+
if (options.days)
|
|
26
|
+
query.days = options.days;
|
|
27
|
+
const res = await apiRequest(key, "credits/usage", { query });
|
|
28
|
+
if (!res.success || !res.data) {
|
|
29
|
+
spinner.fail("Failed to fetch usage");
|
|
30
|
+
error(res.error || "Unknown error");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
spinner.stop();
|
|
34
|
+
const { records } = res.data;
|
|
35
|
+
if (records.length === 0) {
|
|
36
|
+
console.log(" No usage records found.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const rows = records.map((r) => [
|
|
40
|
+
new Date(r.timestamp).toLocaleDateString(),
|
|
41
|
+
r.api,
|
|
42
|
+
r.endpoint,
|
|
43
|
+
`$${r.cost.toFixed(4)}`,
|
|
44
|
+
r.status,
|
|
45
|
+
]);
|
|
46
|
+
console.log(table(["Date", "API", "Endpoint", "Cost", "Status"], rows));
|
|
47
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function apiListAction(options: {
|
|
2
|
+
category?: string;
|
|
3
|
+
}): Promise<void>;
|
|
4
|
+
export declare function apiSearchAction(query: string, options: {
|
|
5
|
+
limit?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function apiShowAction(slug: string, path?: string): Promise<void>;
|
|
8
|
+
export declare function apiCodeAction(slug: string, path: string, options: {
|
|
9
|
+
lang?: string;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,116 @@
|
|
|
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, badge, table, hint, truncate } from "../utils/display.js";
|
|
6
|
+
export async function apiListAction(options) {
|
|
7
|
+
const key = requireApiKey();
|
|
8
|
+
const spinner = ora("Listing APIs...").start();
|
|
9
|
+
const query = {};
|
|
10
|
+
if (options.category)
|
|
11
|
+
query.category = options.category;
|
|
12
|
+
const res = await apiRequest(key, "cli/apis", { query });
|
|
13
|
+
if (!res.success || !res.data) {
|
|
14
|
+
spinner.fail("Failed to list APIs");
|
|
15
|
+
error(res.error || "Unknown error");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
spinner.stop();
|
|
19
|
+
const { apis } = res.data;
|
|
20
|
+
if (apis.length === 0) {
|
|
21
|
+
console.log(" No APIs found.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const api of apis) {
|
|
25
|
+
console.log(`\n ${chalk.cyan.bold(api.name)} ${chalk.gray(api.slug)} ${badge(api.category)}`);
|
|
26
|
+
console.log(` ${chalk.gray(api.description)}`);
|
|
27
|
+
const shown = api.endpoints.slice(0, 3);
|
|
28
|
+
for (const ep of shown) {
|
|
29
|
+
console.log(` ${chalk.yellow(ep.method.padEnd(6))} ${ep.path} ${chalk.gray("- " + truncate(ep.description, 50))}`);
|
|
30
|
+
}
|
|
31
|
+
if (api.endpoints.length > 3) {
|
|
32
|
+
hint(`+${api.endpoints.length - 3} more endpoints`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.gray("\n Run 'aisa api show <slug>' for endpoint details"));
|
|
36
|
+
}
|
|
37
|
+
export async function apiSearchAction(query, options) {
|
|
38
|
+
const key = requireApiKey();
|
|
39
|
+
const spinner = ora(`Searching "${query}"...`).start();
|
|
40
|
+
const res = await apiRequest(key, "cli/apis/search", {
|
|
41
|
+
method: "POST",
|
|
42
|
+
body: { query, limit: parseInt(options.limit || "10") },
|
|
43
|
+
});
|
|
44
|
+
if (!res.success || !res.data) {
|
|
45
|
+
spinner.fail("Search failed");
|
|
46
|
+
error(res.error || "Unknown error");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
spinner.stop();
|
|
50
|
+
const { apis } = res.data;
|
|
51
|
+
if (apis.length === 0) {
|
|
52
|
+
console.log(` No APIs found for "${query}".`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const api of apis) {
|
|
56
|
+
console.log(`\n ${chalk.cyan.bold(api.name)} ${chalk.gray(api.slug)} ${badge(api.category)}`);
|
|
57
|
+
console.log(` ${chalk.gray(api.description)}`);
|
|
58
|
+
for (const ep of api.endpoints.slice(0, 2)) {
|
|
59
|
+
console.log(` ${chalk.yellow(ep.method.padEnd(6))} ${ep.path}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
console.log(chalk.gray("\n Run 'aisa run <slug> <path>' to execute"));
|
|
63
|
+
}
|
|
64
|
+
export async function apiShowAction(slug, path) {
|
|
65
|
+
const key = requireApiKey();
|
|
66
|
+
const spinner = ora(`Loading ${slug}${path ? " " + path : ""}...`).start();
|
|
67
|
+
const endpoint = path ? `cli/apis/${slug}${path}` : `cli/apis/${slug}`;
|
|
68
|
+
const res = await apiRequest(key, endpoint);
|
|
69
|
+
if (!res.success || !res.data) {
|
|
70
|
+
spinner.fail("Failed to load API details");
|
|
71
|
+
error(res.error || "Unknown error");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
spinner.stop();
|
|
75
|
+
const data = res.data;
|
|
76
|
+
if ("endpoints" in data) {
|
|
77
|
+
// ApiGroup
|
|
78
|
+
console.log(`\n ${chalk.cyan.bold(data.name)} ${badge(data.category)}`);
|
|
79
|
+
console.log(` ${data.description}\n`);
|
|
80
|
+
for (const ep of data.endpoints) {
|
|
81
|
+
console.log(` ${chalk.yellow(ep.method.padEnd(6))} ${ep.path}`);
|
|
82
|
+
console.log(` ${chalk.gray(ep.description)}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// ApiEndpoint with parameters
|
|
87
|
+
console.log(`\n ${chalk.yellow(data.method)} ${data.path}`);
|
|
88
|
+
console.log(` ${data.description}\n`);
|
|
89
|
+
if (data.parameters && data.parameters.length > 0) {
|
|
90
|
+
const rows = data.parameters.map((p) => [
|
|
91
|
+
p.name,
|
|
92
|
+
p.type,
|
|
93
|
+
p.required ? chalk.red("required") : "optional",
|
|
94
|
+
truncate(p.description, 40),
|
|
95
|
+
]);
|
|
96
|
+
console.log(table(["Name", "Type", "Required", "Description"], rows));
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.gray(`\n Example: aisa run ${slug} ${data.path} -q "param=value"`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export async function apiCodeAction(slug, path, options) {
|
|
102
|
+
const key = requireApiKey();
|
|
103
|
+
const lang = options.lang || "typescript";
|
|
104
|
+
const spinner = ora(`Generating ${lang} code...`).start();
|
|
105
|
+
const res = await apiRequest(key, "cli/apis/code", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
body: { slug, path, language: lang },
|
|
108
|
+
});
|
|
109
|
+
if (!res.success || !res.data) {
|
|
110
|
+
spinner.fail("Failed to generate code");
|
|
111
|
+
error(res.error || "Unknown error");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
spinner.stop();
|
|
115
|
+
console.log(`\n${res.data.code}`);
|
|
116
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { setApiKey, clearApiKey, getApiKey, getKeySource, maskKey } from "../config.js";
|
|
3
|
+
import { success, error, info } from "../utils/display.js";
|
|
4
|
+
import { ENV_VAR_NAME } from "../constants.js";
|
|
5
|
+
export function loginAction(options) {
|
|
6
|
+
const key = options.key || process.env[ENV_VAR_NAME];
|
|
7
|
+
if (!key) {
|
|
8
|
+
error(`Provide a key with --key or set ${ENV_VAR_NAME}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
setApiKey(key);
|
|
12
|
+
success(`Authenticated: ${maskKey(key)}`);
|
|
13
|
+
console.log(chalk.gray(" Get your API key at https://aisa.one/dashboard"));
|
|
14
|
+
}
|
|
15
|
+
export function logoutAction() {
|
|
16
|
+
clearApiKey();
|
|
17
|
+
success("Logged out. API key removed.");
|
|
18
|
+
}
|
|
19
|
+
export function whoamiAction() {
|
|
20
|
+
const key = getApiKey();
|
|
21
|
+
const source = getKeySource();
|
|
22
|
+
if (!key) {
|
|
23
|
+
info("Not authenticated.");
|
|
24
|
+
console.log(chalk.gray(` Run "aisa login --key <key>" or set ${ENV_VAR_NAME}`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log(` Key: ${maskKey(key)}`);
|
|
28
|
+
console.log(` Source: ${source === "env" ? `${ENV_VAR_NAME} env var` : "stored config"}`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import ora from "ora";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { requireApiKey, getConfig } from "../config.js";
|
|
4
|
+
import { apiRequest, apiRequestRaw } from "../api.js";
|
|
5
|
+
import { error } from "../utils/display.js";
|
|
6
|
+
import { handleSSEStream } from "../utils/streaming.js";
|
|
7
|
+
export async function chatAction(message, options) {
|
|
8
|
+
const key = requireApiKey();
|
|
9
|
+
// Read from stdin if no message provided
|
|
10
|
+
let text = message;
|
|
11
|
+
if (!text) {
|
|
12
|
+
if (process.stdin.isTTY) {
|
|
13
|
+
error("Provide a message or pipe input: echo 'hello' | aisa chat");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for await (const chunk of process.stdin) {
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
}
|
|
20
|
+
text = Buffer.concat(chunks).toString("utf-8").trim();
|
|
21
|
+
}
|
|
22
|
+
if (!text) {
|
|
23
|
+
error("Empty message");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
const model = options.model || getConfig("defaultModel") || "gpt-4.1";
|
|
27
|
+
const stream = options.stream !== false; // default true
|
|
28
|
+
const messages = [];
|
|
29
|
+
if (options.system) {
|
|
30
|
+
messages.push({ role: "system", content: options.system });
|
|
31
|
+
}
|
|
32
|
+
messages.push({ role: "user", content: text });
|
|
33
|
+
const body = { model, messages, stream };
|
|
34
|
+
if (options.maxTokens)
|
|
35
|
+
body.max_tokens = parseInt(options.maxTokens);
|
|
36
|
+
if (options.temperature)
|
|
37
|
+
body.temperature = parseFloat(options.temperature);
|
|
38
|
+
if (stream) {
|
|
39
|
+
const res = await apiRequestRaw(key, "chat/completions", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
body,
|
|
42
|
+
headers: { Accept: "text/event-stream" },
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const text = await res.text();
|
|
46
|
+
error(`${res.status}: ${text}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await handleSSEStream(res, (token) => process.stdout.write(token), (usage) => {
|
|
50
|
+
console.log();
|
|
51
|
+
if (usage) {
|
|
52
|
+
console.log(chalk.gray(` ${model} · ${usage.prompt_tokens} in / ${usage.completion_tokens} out`));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const spinner = ora(`${model}...`).start();
|
|
58
|
+
const res = await apiRequest(key, "chat/completions", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body,
|
|
61
|
+
});
|
|
62
|
+
if (!res.success || !res.data) {
|
|
63
|
+
spinner.fail("Chat failed");
|
|
64
|
+
error(res.error || "Unknown error");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
spinner.stop();
|
|
68
|
+
if (options.json) {
|
|
69
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(res.data.choices[0].message.content);
|
|
73
|
+
if (res.data.usage) {
|
|
74
|
+
console.log(chalk.gray(`\n ${model} · ${res.data.usage.prompt_tokens} in / ${res.data.usage.completion_tokens} out`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { getConfig, setConfig, listConfig, resetConfig } from "../config.js";
|
|
3
|
+
import { success, info } from "../utils/display.js";
|
|
4
|
+
const VALID_KEYS = ["defaultModel", "baseUrl", "outputFormat"];
|
|
5
|
+
export function configSetAction(key, value) {
|
|
6
|
+
if (!VALID_KEYS.includes(key)) {
|
|
7
|
+
console.error(`Unknown config key: ${key}`);
|
|
8
|
+
console.error(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
setConfig(key, value);
|
|
12
|
+
success(`${key} = ${value}`);
|
|
13
|
+
}
|
|
14
|
+
export function configGetAction(key) {
|
|
15
|
+
const value = getConfig(key);
|
|
16
|
+
if (value === undefined) {
|
|
17
|
+
info(`${key} is not set.`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(` ${key} = ${value}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function configListAction() {
|
|
24
|
+
const all = listConfig();
|
|
25
|
+
const display = { ...all };
|
|
26
|
+
if (display.apiKey) {
|
|
27
|
+
display.apiKey = "****";
|
|
28
|
+
}
|
|
29
|
+
for (const [k, v] of Object.entries(display)) {
|
|
30
|
+
console.log(` ${chalk.cyan(k)} = ${v}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function configResetAction() {
|
|
34
|
+
resetConfig();
|
|
35
|
+
success("Config reset to defaults.");
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function stockAction(symbol: string, options: {
|
|
2
|
+
field?: string;
|
|
3
|
+
raw?: boolean;
|
|
4
|
+
}): Promise<void>;
|
|
5
|
+
export declare function cryptoAction(symbol: string, options: {
|
|
6
|
+
period?: string;
|
|
7
|
+
raw?: boolean;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function screenerAction(options: {
|
|
10
|
+
sector?: string;
|
|
11
|
+
limit?: string;
|
|
12
|
+
raw?: boolean;
|
|
13
|
+
}): Promise<void>;
|