@aisa-one/cli 0.6.1 → 0.6.3
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/CHANGELOG.md +30 -1
- package/README.md +32 -4
- package/dist/catalog.d.ts +4 -3
- package/dist/catalog.js +6 -2
- package/dist/commands/api.js +36 -10
- package/dist/commands/chat.js +2 -1
- package/dist/commands/connect.js +1 -1
- package/dist/commands/install.js +3 -2
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/serve-results.js +3 -2
- package/dist/commands/skills.js +2 -2
- package/dist/commands/tool-help.js +2 -2
- package/dist/commands/tool-input.d.ts +1 -0
- package/dist/commands/tool-input.js +32 -5
- package/dist/commands/tools.js +3 -0
- package/dist/config.js +2 -2
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +3 -1
- package/dist/index.js +9 -4
- package/dist/skills-registry.js +0 -1
- package/dist/utils/http.d.ts +1 -0
- package/dist/utils/http.js +10 -1
- package/dist/utils/update-check.js +2 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.3] — 2026-09-30
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Default `aisa chat` to `deepseek-v4.1-flash` for new configurations and
|
|
15
|
+
when no model is configured. Preserve saved `defaultModel` and `--model` overrides.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Display metered provider and endpoint catalog pricing as `usage-based` in
|
|
20
|
+
`api list` and `api show`, rather than treating `normal: 0` as free.
|
|
21
|
+
- Label the catalog price column `PRICING` and show usage-based details as
|
|
22
|
+
`Pricing: usage-based (cost varies)`, with guidance to request a quote.
|
|
23
|
+
- Include `pricing_note` with quote guidance on metered providers and endpoints
|
|
24
|
+
in catalog JSON, preserving the original pricing metadata and list structure.
|
|
25
|
+
|
|
26
|
+
## [0.6.2] — 2026-09-23
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Carry Router session IDs through CLI tool commands.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Send `User-Agent: aisa-cli` on outbound HTTP requests.
|
|
35
|
+
|
|
10
36
|
## [0.6.1] — 2026-09-11
|
|
11
37
|
|
|
12
38
|
### Changed
|
|
@@ -451,7 +477,10 @@ supports today; nothing here depends on a backend change.
|
|
|
451
477
|
- Config commands (`aisa config get|set|list|reset`) and auth
|
|
452
478
|
(`aisa login|logout|whoami`).
|
|
453
479
|
|
|
454
|
-
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.
|
|
480
|
+
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.3...HEAD
|
|
481
|
+
[0.6.3]: https://github.com/AIsa-team/cli/compare/v0.6.2...v0.6.3
|
|
482
|
+
[0.6.2]: https://github.com/AIsa-team/cli/compare/v0.6.1...v0.6.2
|
|
483
|
+
[0.6.1]: https://github.com/AIsa-team/cli/compare/v0.6.0...v0.6.1
|
|
455
484
|
[0.6.0]: https://github.com/AIsa-team/cli/compare/v0.5.2...v0.6.0
|
|
456
485
|
[0.5.2]: https://github.com/AIsa-team/cli/compare/v0.5.1...v0.5.2
|
|
457
486
|
[0.5.1]: https://github.com/AIsa-team/cli/compare/v0.5.0...v0.5.1
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ aisa quote --input '{"calls":[{"call_id":"c1","tool":"get_financial_company_fact
|
|
|
31
31
|
|
|
32
32
|
`aisa login` opens a browser, signs you in, and stores OAuth tokens. You do not
|
|
33
33
|
need to create or paste a key from the console. For CI or scripts, set
|
|
34
|
-
`AISA_API_KEY` or run `aisa login --key <key>`. New accounts receive $
|
|
34
|
+
`AISA_API_KEY` or run `aisa login --key <key>`. New accounts receive $1 in
|
|
35
35
|
free credits.
|
|
36
36
|
|
|
37
37
|
This first block does not run `aisa chat` or `aisa call`. Quote is a price
|
|
@@ -63,7 +63,13 @@ and `aisa manifest search` / `schema` / `quote` / `call` expose `mcp`, `auth`,
|
|
|
63
63
|
|
|
64
64
|
Recommended sequence: discover a tool → `aisa schema` when
|
|
65
65
|
`has_full_schema=false` → `aisa quote` → `aisa call`. Quote and call share
|
|
66
|
-
one request shape.
|
|
66
|
+
one request shape. The first search may omit `session_id`; the Router returns
|
|
67
|
+
one. Reuse that ID for every later search, schema, quote, and call in the same
|
|
68
|
+
task, using `--session-id <id>` or a top-level `session_id` in `--input`/`-f`
|
|
69
|
+
JSON. The CLI does not save it between commands. For related quote/call
|
|
70
|
+
requests, also carry the Search `search_id`; it identifies the search, while
|
|
71
|
+
`session_id` groups the task. Standalone commands may omit either ID.
|
|
72
|
+
**Enforced:** invalid local input exits 2 and is not sent;
|
|
67
73
|
quote and call refuse to run without an OAuth session or static AIsa API key. **Not
|
|
68
74
|
enforced:** the CLI does not record quotes, approvals, or budget caps and
|
|
69
75
|
does not reject an unquoted call. **Instruction:** do not execute unquoted
|
|
@@ -98,6 +104,21 @@ aisa call --input '{"calls":[{"call_id":"c1","tool":"get_financial_company_facts
|
|
|
98
104
|
aisa call -f - --json < request.json
|
|
99
105
|
```
|
|
100
106
|
|
|
107
|
+
For a multi-step task, copy the actual `session_id` from the first search
|
|
108
|
+
response (shown in normal output or `--json`) and pass it on later commands:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
aisa search "company facts"
|
|
112
|
+
aisa search "company revenue" --session-id "$SESSION_ID"
|
|
113
|
+
aisa schema get_financial_company_facts --session-id "$SESSION_ID"
|
|
114
|
+
aisa quote --input '{"search_id":"SEARCH_ID","calls":[{"call_id":"c1","tool":"get_financial_company_facts","arguments":{"ticker":"AAPL"}}]}' --session-id "$SESSION_ID" --json
|
|
115
|
+
aisa call --input '{"search_id":"SEARCH_ID","calls":[{"call_id":"c1","tool":"get_financial_company_facts","arguments":{"ticker":"AAPL"}}]}' --session-id "$SESSION_ID" --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Set `SESSION_ID` and replace `SEARCH_ID` with values returned by your search;
|
|
119
|
+
quote the same calls before an authorized call. If JSON already contains
|
|
120
|
+
`session_id`, omit `--session-id` to avoid conflicting sources.
|
|
121
|
+
|
|
101
122
|
`--json` keeps large integer tokens. Diagnostics go to stderr. Exit `2` means
|
|
102
123
|
local input was invalid and nothing was sent; `1` is transport, auth, or an
|
|
103
124
|
HTTP error; `3` means the Router returned a batch with at least one failed
|
|
@@ -142,6 +163,13 @@ A provider **id is not always its URL slug** — `brave-search` serves
|
|
|
142
163
|
`/apis/v1/brave/...`, and `api show` prints the catalog path. The catalog
|
|
143
164
|
reports every method as `GET`; treat that as advisory.
|
|
144
165
|
|
|
166
|
+
Prices with `type: metered` display as `usage-based` (billing depends on usage),
|
|
167
|
+
even when the catalog returns `normal: 0`. Details show `usage-based (cost varies)`.
|
|
168
|
+
Use `aisa quote` for a request-specific estimate; JSON keeps the original pricing metadata.
|
|
169
|
+
In `--json` output, metered providers and endpoints also include `pricing_note`
|
|
170
|
+
with quote guidance, including endpoints nested in provider details. The list
|
|
171
|
+
remains an array, and the original `pricing` fields are unchanged.
|
|
172
|
+
|
|
145
173
|
The catalog is cached in `~/.aisa/cache` (override with `AISA_CACHE_DIR`). Pass
|
|
146
174
|
`--refresh` to any command to bypass it, or `aisa cache clear`.
|
|
147
175
|
|
|
@@ -151,7 +179,7 @@ GPT, Claude, Gemini, DeepSeek, Kimi, GLM, Qwen and the rest behind one
|
|
|
151
179
|
OpenAI-compatible endpoint.
|
|
152
180
|
|
|
153
181
|
```bash
|
|
154
|
-
aisa chat "your message" --model
|
|
182
|
+
aisa chat "your message" --model deepseek-v4.1-flash
|
|
155
183
|
aisa chat "explain this" --model claude-opus-4-6
|
|
156
184
|
aisa chat "respond in JSON" --model gemini-2.5-pro --json
|
|
157
185
|
echo "summarize this" | aisa chat # pipe support
|
|
@@ -319,7 +347,7 @@ aisa config reset
|
|
|
319
347
|
```
|
|
320
348
|
|
|
321
349
|
Settings:
|
|
322
|
-
- `defaultModel` — default model for `aisa chat` (default: `
|
|
350
|
+
- `defaultModel` — default model for `aisa chat` (default: `deepseek-v4.1-flash`). Existing saved model settings are preserved.
|
|
323
351
|
- `baseUrl` — platform root; the LLM (`/v1`), integration (`/apis/v1`), and
|
|
324
352
|
catalog bases are all derived from it
|
|
325
353
|
- `routerUrl` — Tool Router origin (default `https://tools.aisa.one`,
|
package/dist/catalog.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export interface CatalogPricing {
|
|
13
13
|
type: string;
|
|
14
|
-
/** Dollars per request, as a float — not micros. */
|
|
14
|
+
/** Dollars per request, as a float — not micros. Metered prices may use 0 as a placeholder. */
|
|
15
15
|
normal: number;
|
|
16
16
|
}
|
|
17
17
|
export interface CatalogProvider {
|
|
@@ -66,8 +66,9 @@ export declare function toRunPath(path: string): string;
|
|
|
66
66
|
*/
|
|
67
67
|
export declare function runSlugOf(detail: CatalogDetail): string | undefined;
|
|
68
68
|
/**
|
|
69
|
-
* Format
|
|
69
|
+
* Format catalog pricing, keeping metered billing distinct from a free request.
|
|
70
|
+
* Fixed prices span $0.000001 to $0.08, so a fixed
|
|
70
71
|
* number of decimals either rounds the cheap endpoints to $0.00 or pads the
|
|
71
72
|
* rest with noise — keep two significant digits instead.
|
|
72
73
|
*/
|
|
73
|
-
export declare function formatPrice(
|
|
74
|
+
export declare function formatPrice(pricing?: CatalogPricing): string;
|
package/dist/catalog.js
CHANGED
|
@@ -62,11 +62,15 @@ export function runSlugOf(detail) {
|
|
|
62
62
|
return first ? toRunPath(first.path).split("/")[0] : undefined;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Format
|
|
65
|
+
* Format catalog pricing, keeping metered billing distinct from a free request.
|
|
66
|
+
* Fixed prices span $0.000001 to $0.08, so a fixed
|
|
66
67
|
* number of decimals either rounds the cheap endpoints to $0.00 or pads the
|
|
67
68
|
* rest with noise — keep two significant digits instead.
|
|
68
69
|
*/
|
|
69
|
-
export function formatPrice(
|
|
70
|
+
export function formatPrice(pricing) {
|
|
71
|
+
if (pricing?.type === "metered")
|
|
72
|
+
return "usage-based";
|
|
73
|
+
const usd = pricing?.normal;
|
|
70
74
|
if (usd == null)
|
|
71
75
|
return "—";
|
|
72
76
|
if (usd === 0)
|
package/dist/commands/api.js
CHANGED
|
@@ -4,6 +4,21 @@ import { error, formatJson, hint, table, truncate } from "../utils/display.js";
|
|
|
4
4
|
import { API_CATEGORIES, categoryOf } from "../constants.js";
|
|
5
5
|
import { getProviders, getProviderDetail, getHealth, flatEndpoints, toRunPath, runSlugOf, formatPrice, } from "../catalog.js";
|
|
6
6
|
const DEFAULT_ENDPOINT_LIMIT = 40;
|
|
7
|
+
const CATALOG_HINT = "Catalog paths and prices are browsing metadata. Use aisa schema for parameters and aisa quote for a request-specific estimate.";
|
|
8
|
+
const USAGE_BASED_PRICING_NOTE = "Usage-based billing. Use aisa quote for a request-specific estimate.";
|
|
9
|
+
function withPricingNote(item) {
|
|
10
|
+
return item.pricing?.type === "metered"
|
|
11
|
+
? { ...item, pricing_note: USAGE_BASED_PRICING_NOTE }
|
|
12
|
+
: item;
|
|
13
|
+
}
|
|
14
|
+
function pricingDescription(pricing, from = false) {
|
|
15
|
+
const price = formatPrice(pricing);
|
|
16
|
+
if (pricing?.type === "metered")
|
|
17
|
+
return `${price} (cost varies)`;
|
|
18
|
+
if (pricing?.normal == null)
|
|
19
|
+
return price;
|
|
20
|
+
return `${from && pricing.normal > 0 ? "from " : ""}${price} per request`;
|
|
21
|
+
}
|
|
7
22
|
function statusBadge(status) {
|
|
8
23
|
switch (status) {
|
|
9
24
|
case "healthy":
|
|
@@ -41,13 +56,13 @@ export async function apiListAction(options) {
|
|
|
41
56
|
}
|
|
42
57
|
if (options.json) {
|
|
43
58
|
console.log(formatJson(providers.map((p) => ({
|
|
44
|
-
...p,
|
|
59
|
+
...withPricingNote(p),
|
|
45
60
|
category: categoryOf(p.id),
|
|
46
61
|
...(options.health ? { health: healthById.get(p.id)?.status } : {}),
|
|
47
62
|
}))));
|
|
48
63
|
return;
|
|
49
64
|
}
|
|
50
|
-
const headers = ["API", "CATEGORY", "ENDPOINTS", "
|
|
65
|
+
const headers = ["API", "CATEGORY", "ENDPOINTS", "PRICING", "UPDATED"];
|
|
51
66
|
if (options.health)
|
|
52
67
|
headers.push("STATUS");
|
|
53
68
|
const rows = providers.map((p) => {
|
|
@@ -55,7 +70,9 @@ export async function apiListAction(options) {
|
|
|
55
70
|
p.id,
|
|
56
71
|
categoryOf(p.id),
|
|
57
72
|
String(p.endpoint_count),
|
|
58
|
-
|
|
73
|
+
p.pricing?.type !== "metered" && p.pricing?.normal != null && p.pricing.normal > 0
|
|
74
|
+
? `from ${formatPrice(p.pricing)}`
|
|
75
|
+
: formatPrice(p.pricing),
|
|
59
76
|
shortDate(p.updated_at),
|
|
60
77
|
];
|
|
61
78
|
if (options.health)
|
|
@@ -68,7 +85,7 @@ export async function apiListAction(options) {
|
|
|
68
85
|
console.log();
|
|
69
86
|
console.log(chalk.gray(` ${providers.length} APIs · ${total} endpoints`));
|
|
70
87
|
hint("Details: aisa api show <api>");
|
|
71
|
-
hint(
|
|
88
|
+
hint(CATALOG_HINT);
|
|
72
89
|
}
|
|
73
90
|
/** Resolve a provider by id, or by the URL slug its endpoints are served under. */
|
|
74
91
|
async function findProvider(idOrSlug, refresh) {
|
|
@@ -93,13 +110,13 @@ function printEndpointDetail(detail, endpoint) {
|
|
|
93
110
|
console.log(` ${endpoint.description}`);
|
|
94
111
|
console.log(`\n Path: ${endpoint.path}`);
|
|
95
112
|
console.log(` Provider: ${detail.id}`);
|
|
96
|
-
console.log(`
|
|
113
|
+
console.log(` Pricing: ${pricingDescription(endpoint.pricing)}`);
|
|
97
114
|
const params = [...runPath.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
|
|
98
115
|
if (params.length > 0) {
|
|
99
116
|
console.log(` Path params: ${params.join(", ")}`);
|
|
100
117
|
}
|
|
101
118
|
console.log();
|
|
102
|
-
hint(
|
|
119
|
+
hint(CATALOG_HINT);
|
|
103
120
|
}
|
|
104
121
|
export async function apiShowAction(idOrSlug, path, options = {}) {
|
|
105
122
|
const spinner = ora(`Loading ${idOrSlug}...`).start();
|
|
@@ -140,21 +157,30 @@ export async function apiShowAction(idOrSlug, path, options = {}) {
|
|
|
140
157
|
return;
|
|
141
158
|
}
|
|
142
159
|
if (options.json) {
|
|
143
|
-
console.log(formatJson(matches[0]));
|
|
160
|
+
console.log(formatJson(withPricingNote(matches[0])));
|
|
144
161
|
return;
|
|
145
162
|
}
|
|
146
163
|
printEndpointDetail(detail, matches[0]);
|
|
147
164
|
return;
|
|
148
165
|
}
|
|
149
166
|
if (options.json) {
|
|
150
|
-
console.log(formatJson(
|
|
167
|
+
console.log(formatJson({
|
|
168
|
+
...withPricingNote(detail),
|
|
169
|
+
...(detail.endpoint_groups ? {
|
|
170
|
+
endpoint_groups: detail.endpoint_groups.map((group) => ({
|
|
171
|
+
...group,
|
|
172
|
+
...(group.endpoints ? { endpoints: group.endpoints.map(withPricingNote) } : {}),
|
|
173
|
+
})),
|
|
174
|
+
} : {}),
|
|
175
|
+
}));
|
|
151
176
|
return;
|
|
152
177
|
}
|
|
153
178
|
const health = options.health
|
|
154
179
|
? (await getHealth({ refresh: options.refresh })).find((h) => h.id === detail.id)
|
|
155
180
|
: undefined;
|
|
156
181
|
console.log(`\n ${chalk.cyan.bold(detail.id)} ${chalk.gray(`(${categoryOf(detail.id)})`)}`);
|
|
157
|
-
console.log(` ${detail.endpoint_count} endpoints
|
|
182
|
+
console.log(` ${detail.endpoint_count} endpoints`);
|
|
183
|
+
console.log(` Pricing: ${pricingDescription(detail.pricing, true)}`);
|
|
158
184
|
console.log(` Updated ${shortDate(detail.updated_at)}`);
|
|
159
185
|
if (health) {
|
|
160
186
|
// Health is tracked per provider, not per endpoint — the per-endpoint
|
|
@@ -185,6 +211,6 @@ export async function apiShowAction(idOrSlug, path, options = {}) {
|
|
|
185
211
|
if (runSlugOf(detail)) {
|
|
186
212
|
console.log();
|
|
187
213
|
hint(`Detail: aisa api show ${detail.id} <path>`);
|
|
188
|
-
hint(
|
|
214
|
+
hint(CATALOG_HINT);
|
|
189
215
|
}
|
|
190
216
|
}
|
package/dist/commands/chat.js
CHANGED
|
@@ -4,6 +4,7 @@ import { requireAccessToken, getConfig } from "../config.js";
|
|
|
4
4
|
import { apiRequest, apiRequestRaw } from "../api.js";
|
|
5
5
|
import { error } from "../utils/display.js";
|
|
6
6
|
import { handleSSEStream } from "../utils/streaming.js";
|
|
7
|
+
import { DEFAULT_CHAT_MODEL } from "../constants.js";
|
|
7
8
|
export async function chatAction(message, options) {
|
|
8
9
|
const key = await requireAccessToken();
|
|
9
10
|
// Read from stdin if no message provided
|
|
@@ -23,7 +24,7 @@ export async function chatAction(message, options) {
|
|
|
23
24
|
error("Empty message");
|
|
24
25
|
process.exit(1);
|
|
25
26
|
}
|
|
26
|
-
const model = options.model || getConfig("defaultModel") ||
|
|
27
|
+
const model = options.model || getConfig("defaultModel") || DEFAULT_CHAT_MODEL;
|
|
27
28
|
const stream = options.stream !== false; // default true
|
|
28
29
|
const messages = [];
|
|
29
30
|
if (options.system) {
|
package/dist/commands/connect.js
CHANGED
|
@@ -1935,7 +1935,7 @@ async function probeRun(url) {
|
|
|
1935
1935
|
const token = u.searchParams.get("token") ?? "";
|
|
1936
1936
|
if (!token)
|
|
1937
1937
|
return { ours: false };
|
|
1938
|
-
const res = await
|
|
1938
|
+
const res = await httpFetch(`${u.origin}/status?token=${token}`, { timeoutMs: 2_000 });
|
|
1939
1939
|
if (!res.ok)
|
|
1940
1940
|
return { ours: false };
|
|
1941
1941
|
const data = (await res.json());
|
package/dist/commands/install.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { accessSync, constants, existsSync } from "node:fs";
|
|
2
2
|
import { runShell, runSync, QUICK_TIMEOUT_MS } from "../utils/exec.js";
|
|
3
|
+
import { httpFetch } from "../utils/http.js";
|
|
3
4
|
export const INSTALLERS = {
|
|
4
5
|
"claude-code": {
|
|
5
6
|
id: "claude-code",
|
|
@@ -10,7 +11,7 @@ export const INSTALLERS = {
|
|
|
10
11
|
// what makes the mirror path below work where claude.ai is unreachable.
|
|
11
12
|
// Curl gets its own timeouts: a blackholed connection must fail in
|
|
12
13
|
// seconds, not sit until the overall install deadline.
|
|
13
|
-
command: "curl -fsSL --connect-timeout 10 --max-time 180 https://claude.ai/install.sh | bash",
|
|
14
|
+
command: "curl -fsSL -A aisa-cli --connect-timeout 10 --max-time 180 https://claude.ai/install.sh | bash",
|
|
14
15
|
installDir: "~/.local/bin",
|
|
15
16
|
npmPackage: "@anthropic-ai/claude-code",
|
|
16
17
|
probeUrl: "https://claude.ai/install.sh",
|
|
@@ -71,7 +72,7 @@ export async function pickNpmChannel(pkg, probe = headOk, userRegistry = npmConf
|
|
|
71
72
|
}
|
|
72
73
|
async function headOk(url) {
|
|
73
74
|
try {
|
|
74
|
-
const res = await
|
|
75
|
+
const res = await httpFetch(url, { method: "HEAD", timeoutMs: 3_000 });
|
|
75
76
|
return res.ok;
|
|
76
77
|
}
|
|
77
78
|
catch {
|
package/dist/commands/mcp.js
CHANGED
|
@@ -254,7 +254,7 @@ export async function mcpStatusAction() {
|
|
|
254
254
|
*/
|
|
255
255
|
export async function pingEndpoint(endpoint) {
|
|
256
256
|
try {
|
|
257
|
-
const res = await
|
|
257
|
+
const res = await httpFetch(endpoint, {
|
|
258
258
|
method: "POST",
|
|
259
259
|
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
260
260
|
body: JSON.stringify({
|
|
@@ -267,7 +267,7 @@ export async function pingEndpoint(endpoint) {
|
|
|
267
267
|
clientInfo: { name: "aisa-cli", version: "status" },
|
|
268
268
|
},
|
|
269
269
|
}),
|
|
270
|
-
|
|
270
|
+
timeoutMs: 8_000,
|
|
271
271
|
});
|
|
272
272
|
if (res.status === 200)
|
|
273
273
|
return `${chalk.green("✓")} reachable (open)`;
|
|
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { readFileSync, writeFileSync, unlinkSync, mkdtempSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { httpFetch } from "../utils/http.js";
|
|
6
7
|
import { renderT2Page } from "./connect-t2.js";
|
|
7
8
|
import { isInstalled } from "./install.js";
|
|
8
9
|
import { renderDone } from "./connect.js";
|
|
@@ -56,8 +57,8 @@ export async function closeStaleResults() {
|
|
|
56
57
|
// crash may belong to something else entirely by now. Answering on that
|
|
57
58
|
// port with that token is the only proof that counts.
|
|
58
59
|
try {
|
|
59
|
-
const res = await
|
|
60
|
-
|
|
60
|
+
const res = await httpFetch(`http://127.0.0.1:${rec.port}/status?token=${rec.token}`, {
|
|
61
|
+
timeoutMs: 1_500,
|
|
61
62
|
});
|
|
62
63
|
if (!res.ok)
|
|
63
64
|
return forget();
|
package/dist/commands/skills.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { error, success, hint, truncate } from "../utils/display.js";
|
|
6
6
|
import { expandHome, ensureDir, writeSkillFiles, removeDir, detectAgents } from "../utils/file.js";
|
|
7
|
-
import { AGENT_DIRS } from "../constants.js";
|
|
7
|
+
import { AGENT_DIRS, DEFAULT_CHAT_MODEL } from "../constants.js";
|
|
8
8
|
import { SKILLS_REPO, getSkillIndex, listSkills, resolveSlug, leafName, fetchSkillFiles, fetchSkillMarkdown, parseSkillFrontmatter, } from "../skills-registry.js";
|
|
9
9
|
// --- Skill Templates (for init) ---
|
|
10
10
|
// Domain labels may stay; runnable steps use retained commands only.
|
|
@@ -74,7 +74,7 @@ export AISA_API_KEY=sk-your-key
|
|
|
74
74
|
## Chat Completion
|
|
75
75
|
|
|
76
76
|
\`\`\`bash
|
|
77
|
-
aisa chat "Your question here" --model
|
|
77
|
+
aisa chat "Your question here" --model ${DEFAULT_CHAT_MODEL}
|
|
78
78
|
\`\`\`
|
|
79
79
|
|
|
80
80
|
## Streaming
|
|
@@ -29,7 +29,7 @@ export const ROUTER_EXITS = {
|
|
|
29
29
|
"1": "transport, auth, or HTTP error",
|
|
30
30
|
"3": "HTTP 200 with any failed batch item",
|
|
31
31
|
};
|
|
32
|
-
const FLOW = "Recommended sequence: aisa search → aisa schema when has_full_schema=false → aisa quote → aisa call.";
|
|
32
|
+
const FLOW = "Recommended sequence: aisa search → aisa schema when has_full_schema=false → aisa quote → aisa call. First search may omit session_id; reuse the returned session_id for every later search, schema, quote, and call in the same task. Pass it as --session-id or as a top-level JSON field. The CLI does not save sessions between commands. Keep search_id from search on related quote and call requests.";
|
|
33
33
|
const INLINE = "--input is inline JSON text (no file required). Shell examples are POSIX sh with single-quoted --input; apostrophes are written as '\\'' so $(), backticks, and other expansions stay literal. -f FILE and -f - remain available.";
|
|
34
34
|
const JSON_CONTRACT = "--json writes the unmodified application body, including MCP identifiers and numeric tokens. Human output maps only AISA_SEARCH_TOOL, AISA_BATCH_GET_SCHEMA, AISA_BATCH_QUOTE, and AISA_BATCH_USE to aisa search / schema / quote / call.";
|
|
35
35
|
const KEY_RESOLUTION = "Prefer aisa login (stores a CLI key). Resolution: AISA_API_KEY, then ~/.aisa/key, then legacy login. CI: AISA_API_KEY or aisa login --key.";
|
|
@@ -60,7 +60,7 @@ export function routerContract(kind) {
|
|
|
60
60
|
case "schema":
|
|
61
61
|
return {
|
|
62
62
|
auth: "optional",
|
|
63
|
-
flow: `Use after search when has_full_schema=false. Pass exact tool names
|
|
63
|
+
flow: `Use after search when has_full_schema=false. Pass exact tool names and reuse the returned session_id via --session-id or a top-level JSON field. The CLI does not save sessions between commands. ${EXAMPLE_PUBLISHED_TOOL} is a published tool whose schema includes ticker.`,
|
|
64
64
|
enforced: [ENFORCED_OPTIONAL],
|
|
65
65
|
safety: [
|
|
66
66
|
"Instruction: Do not invent tool names. Do not guess arguments; read the returned schema.",
|
|
@@ -8,6 +8,8 @@ export async function prepareRouterRequest(kind, operands, options) {
|
|
|
8
8
|
const hasInput = options.input !== undefined;
|
|
9
9
|
const hasFile = options.file !== undefined;
|
|
10
10
|
const hasOperands = operands.length > 0;
|
|
11
|
+
if (options.sessionId === "")
|
|
12
|
+
throw usageError("--session-id must not be empty");
|
|
11
13
|
if (hasInput && hasFile) {
|
|
12
14
|
throw usageError("Use either --input or -f/--file, not both");
|
|
13
15
|
}
|
|
@@ -18,10 +20,10 @@ export async function prepareRouterRequest(kind, operands, options) {
|
|
|
18
20
|
throw usageError("Do not combine --input/-f with field flags; put every field in the JSON request");
|
|
19
21
|
}
|
|
20
22
|
if (hasInput) {
|
|
21
|
-
return fromEnvelope(kind, options.input, " --input");
|
|
23
|
+
return withSessionId(kind, fromEnvelope(kind, options.input, " --input"), options.sessionId);
|
|
22
24
|
}
|
|
23
25
|
if (hasFile) {
|
|
24
|
-
return fromEnvelope(kind, await readRequestFile(options.file), ` -f ${options.file}`);
|
|
26
|
+
return withSessionId(kind, fromEnvelope(kind, await readRequestFile(options.file), ` -f ${options.file}`), options.sessionId);
|
|
25
27
|
}
|
|
26
28
|
if (kind === "search") {
|
|
27
29
|
return buildSearch(operands, options);
|
|
@@ -68,6 +70,20 @@ function fromEnvelope(kind, raw, source) {
|
|
|
68
70
|
// Send the original text so numeric tokens in arguments are not rewritten.
|
|
69
71
|
return { body: text, value };
|
|
70
72
|
}
|
|
73
|
+
/** Add the flag without parsing and rewriting numeric tokens in the raw JSON body. */
|
|
74
|
+
function withSessionId(kind, prepared, sessionId) {
|
|
75
|
+
if (sessionId === undefined)
|
|
76
|
+
return prepared;
|
|
77
|
+
validateSessionId(sessionId);
|
|
78
|
+
if (prepared.value.session_id !== undefined) {
|
|
79
|
+
throw usageError("session_id is already present in the JSON request; use either it or --session-id");
|
|
80
|
+
}
|
|
81
|
+
const value = { ...prepared.value, session_id: sessionId };
|
|
82
|
+
validateEnvelope(kind, value);
|
|
83
|
+
const separator = Object.keys(prepared.value).length > 0 ? "," : "";
|
|
84
|
+
const body = `${prepared.body.slice(0, -1)}${separator}"session_id":${JSON.stringify(sessionId)}}`;
|
|
85
|
+
return { body, value };
|
|
86
|
+
}
|
|
71
87
|
function buildSearch(operands, options) {
|
|
72
88
|
if (operands.length !== 1 || !operands[0].trim()) {
|
|
73
89
|
throw usageError("search requires a query, --input '<json>', or -f FILE");
|
|
@@ -79,6 +95,8 @@ function buildSearch(operands, options) {
|
|
|
79
95
|
if (options.provider && options.provider.length > 0) {
|
|
80
96
|
request.provider_filters = options.provider;
|
|
81
97
|
}
|
|
98
|
+
if (options.sessionId !== undefined)
|
|
99
|
+
request.session_id = options.sessionId;
|
|
82
100
|
validateEnvelope("search", request);
|
|
83
101
|
return { body: JSON.stringify(request), value: request };
|
|
84
102
|
}
|
|
@@ -98,6 +116,8 @@ function buildSchema(operands, options) {
|
|
|
98
116
|
if (options.includeArgumentsSchema === false && options.includeResponseSchema !== true) {
|
|
99
117
|
throw usageError("At least one of arguments or response schema must be requested");
|
|
100
118
|
}
|
|
119
|
+
if (options.sessionId !== undefined)
|
|
120
|
+
request.session_id = options.sessionId;
|
|
101
121
|
validateEnvelope("schema", request);
|
|
102
122
|
return { body: JSON.stringify(request), value: request };
|
|
103
123
|
}
|
|
@@ -111,11 +131,13 @@ function parseLimit(raw) {
|
|
|
111
131
|
}
|
|
112
132
|
return n;
|
|
113
133
|
}
|
|
114
|
-
const SEARCH_KEYS = ["query", "known_fields", "provider_filters", "limit"];
|
|
115
|
-
const SCHEMA_KEYS = ["tools", "include_arguments_schema", "include_response_schema"];
|
|
116
|
-
const BATCH_KEYS = ["search_id", "calls"];
|
|
134
|
+
const SEARCH_KEYS = ["query", "known_fields", "provider_filters", "limit", "session_id"];
|
|
135
|
+
const SCHEMA_KEYS = ["tools", "include_arguments_schema", "include_response_schema", "session_id"];
|
|
136
|
+
const BATCH_KEYS = ["search_id", "calls", "session_id"];
|
|
117
137
|
const CALL_KEYS = ["call_id", "tool", "arguments"];
|
|
118
138
|
export function validateEnvelope(kind, value) {
|
|
139
|
+
if (value.session_id !== undefined)
|
|
140
|
+
validateSessionId(value.session_id);
|
|
119
141
|
if (kind === "search") {
|
|
120
142
|
rejectUnknownKeys(value, SEARCH_KEYS, "search request");
|
|
121
143
|
if (typeof value.query !== "string" || value.query.trim().length === 0) {
|
|
@@ -197,6 +219,11 @@ export function validateEnvelope(kind, value) {
|
|
|
197
219
|
throw usageError("call_id values must be unique within the batch");
|
|
198
220
|
}
|
|
199
221
|
}
|
|
222
|
+
function validateSessionId(value) {
|
|
223
|
+
if (typeof value !== "string" || (value !== "" && !/^[A-Za-z0-9_-]{1,128}$/.test(value))) {
|
|
224
|
+
throw usageError("session_id must be a string of at most 128 letters, digits, underscores, or hyphens");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
200
227
|
function rejectUnknownKeys(value, allowed, label) {
|
|
201
228
|
const extra = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
202
229
|
if (extra.length > 0) {
|
package/dist/commands/tools.js
CHANGED
|
@@ -127,6 +127,9 @@ function renderHuman(kind, raw) {
|
|
|
127
127
|
console.log(raw);
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
+
if (typeof value.session_id === "string") {
|
|
131
|
+
console.log(`\n ${chalk.gray("session_id")} ${value.session_id}`);
|
|
132
|
+
}
|
|
130
133
|
if (kind === "search") {
|
|
131
134
|
renderSearch(value);
|
|
132
135
|
return;
|
package/dist/config.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import lockfile from "proper-lockfile";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
7
|
import { httpFetch } from "./utils/http.js";
|
|
8
|
-
import { AUTH_SERVER, MISSING_API_KEY_GUIDANCE, ENV_VAR_NAME } from "./constants.js";
|
|
8
|
+
import { AUTH_SERVER, MISSING_API_KEY_GUIDANCE, ENV_VAR_NAME, DEFAULT_CHAT_MODEL } from "./constants.js";
|
|
9
9
|
// tokens.json is authoritative; conf is a write-only compatibility mirror.
|
|
10
10
|
const aisaDir = () => join(homedir(), ".aisa");
|
|
11
11
|
const keyFile = () => join(aisaDir(), "key");
|
|
@@ -118,7 +118,7 @@ const config = new Conf({
|
|
|
118
118
|
schema: {
|
|
119
119
|
tokens: { type: "object", default: {} },
|
|
120
120
|
apiKey: { type: "string", default: "" },
|
|
121
|
-
defaultModel: { type: "string", default:
|
|
121
|
+
defaultModel: { type: "string", default: DEFAULT_CHAT_MODEL },
|
|
122
122
|
baseUrl: { type: "string", default: "https://api.aisa.one/v1" },
|
|
123
123
|
routerUrl: { type: "string", default: "" },
|
|
124
124
|
outputFormat: { type: "string", default: "text" },
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.3";
|
|
2
|
+
/** Default for chat when no model is configured or supplied explicitly. */
|
|
3
|
+
export declare const DEFAULT_CHAT_MODEL = "deepseek-v4.1-flash";
|
|
2
4
|
/** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
|
|
3
5
|
export declare const BASE_URL = "https://api.aisa.one";
|
|
4
6
|
export declare const ENV_VAR_NAME = "AISA_API_KEY";
|
package/dist/constants.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export const VERSION = "0.6.
|
|
1
|
+
export const VERSION = "0.6.3";
|
|
2
|
+
/** Default for chat when no model is configured or supplied explicitly. */
|
|
3
|
+
export const DEFAULT_CHAT_MODEL = "deepseek-v4.1-flash";
|
|
2
4
|
/** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
|
|
3
5
|
export const BASE_URL = "https://api.aisa.one";
|
|
4
6
|
export const ENV_VAR_NAME = "AISA_API_KEY";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, Option } from "commander";
|
|
3
|
-
import { VERSION } from "./constants.js";
|
|
3
|
+
import { VERSION, DEFAULT_CHAT_MODEL } from "./constants.js";
|
|
4
4
|
import { CliError } from "./cli-error.js";
|
|
5
5
|
// Auth
|
|
6
6
|
import { loginAction, logoutAction, whoamiAction } from "./commands/auth.js";
|
|
@@ -58,6 +58,7 @@ function routerIo(opts) {
|
|
|
58
58
|
return {
|
|
59
59
|
input: opts.input,
|
|
60
60
|
file: opts.file,
|
|
61
|
+
sessionId: opts.sessionId,
|
|
61
62
|
json: Boolean(opts.json),
|
|
62
63
|
limit: opts.limit,
|
|
63
64
|
provider: opts.provider,
|
|
@@ -111,6 +112,7 @@ program
|
|
|
111
112
|
.option("-f, --file <path>", "Read JSON request from file, or - for stdin")
|
|
112
113
|
.option("--limit <n>", "Max additional discovery results (1-20)")
|
|
113
114
|
.option("--provider <id>", "Exact catalog provider key (repeatable)", collectProvider, [])
|
|
115
|
+
.option("--session-id <id>", "Reuse a session_id returned by an earlier search in this task")
|
|
114
116
|
.option("--json", "Write the unmodified application response to stdout (MCP identifiers unchanged)")
|
|
115
117
|
.addHelpText("after", searchHelpAfter())
|
|
116
118
|
.action((query, opts) => wrap(searchAction)(query, routerIo(opts)));
|
|
@@ -121,6 +123,7 @@ program
|
|
|
121
123
|
.option("-f, --file <path>", "Read JSON request from file, or - for stdin")
|
|
122
124
|
.option("--no-arguments-schema", "Omit arguments_schema (at least one schema type is required)")
|
|
123
125
|
.option("--include-response-schema", "Include response_schema")
|
|
126
|
+
.option("--session-id <id>", "Reuse the session_id returned by search for this task")
|
|
124
127
|
.option("--json", "Write the unmodified application response to stdout (MCP identifiers unchanged)")
|
|
125
128
|
.addHelpText("after", schemaHelpAfter())
|
|
126
129
|
.action((tools, opts) => wrap(schemaAction)(tools, routerIo(opts)));
|
|
@@ -129,6 +132,7 @@ program
|
|
|
129
132
|
.description("Quote published tools via the Tool Router without executing them (MCP AISA_BATCH_QUOTE)")
|
|
130
133
|
.option("--input <json>", "Inline JSON request body, same shape as call (no file required)")
|
|
131
134
|
.option("-f, --file <path>", "Read JSON request from file, or - for stdin")
|
|
135
|
+
.option("--session-id <id>", "Reuse the session_id returned by search for this task")
|
|
132
136
|
.option("--json", "Write the unmodified application response to stdout (MCP identifiers unchanged)")
|
|
133
137
|
.addHelpText("after", quoteHelpAfter())
|
|
134
138
|
.action((opts) => wrap(quoteAction)(routerIo(opts)));
|
|
@@ -137,6 +141,7 @@ program
|
|
|
137
141
|
.description("Execute published tools via the Tool Router (MCP AISA_BATCH_USE)")
|
|
138
142
|
.option("--input <json>", "Inline JSON request body, same shape as quote (no file required)")
|
|
139
143
|
.option("-f, --file <path>", "Read JSON request from file, or - for stdin")
|
|
144
|
+
.option("--session-id <id>", "Reuse the session_id returned by search for this task")
|
|
140
145
|
.option("--json", "Write the unmodified application response to stdout (MCP identifiers unchanged)")
|
|
141
146
|
.addHelpText("after", callHelpAfter())
|
|
142
147
|
.action((opts) => wrap(callAction)(routerIo(opts)));
|
|
@@ -147,7 +152,7 @@ api
|
|
|
147
152
|
.description("List available APIs")
|
|
148
153
|
.option("--category <cat>", "Filter by category (client-side grouping): finance, search, social, productivity, other")
|
|
149
154
|
.option("--health", "Include provider health status")
|
|
150
|
-
.option("--json", "Output
|
|
155
|
+
.option("--json", "Output catalog JSON with pricing guidance")
|
|
151
156
|
.option("--refresh", "Bypass the cached catalog")
|
|
152
157
|
.action(wrap(apiListAction));
|
|
153
158
|
api
|
|
@@ -156,14 +161,14 @@ api
|
|
|
156
161
|
.option("--all", "Show every endpoint instead of the first 40")
|
|
157
162
|
.option("--group", "Group by the provider's raw endpoint groups")
|
|
158
163
|
.option("--health", "Include provider health status")
|
|
159
|
-
.option("--json", "Output
|
|
164
|
+
.option("--json", "Output catalog JSON with pricing guidance")
|
|
160
165
|
.option("--refresh", "Bypass the cached catalog")
|
|
161
166
|
.action(wrap(apiShowAction));
|
|
162
167
|
// ── Chat (LLM Gateway) ──
|
|
163
168
|
program
|
|
164
169
|
.command("chat [message]")
|
|
165
170
|
.description("Chat with AI models via the AIsa gateway")
|
|
166
|
-
.option("--model <model>",
|
|
171
|
+
.option("--model <model>", `Model ID (default: ${DEFAULT_CHAT_MODEL})`)
|
|
167
172
|
.option("--system <prompt>", "System prompt")
|
|
168
173
|
.option("--no-stream", "Disable streaming")
|
|
169
174
|
.option("--json", "Output raw JSON response")
|
package/dist/skills-registry.js
CHANGED
|
@@ -21,7 +21,6 @@ const FETCH_CONCURRENCY = 8;
|
|
|
21
21
|
function ghHeaders(etag) {
|
|
22
22
|
const headers = {
|
|
23
23
|
Accept: "application/vnd.github+json",
|
|
24
|
-
"User-Agent": "aisa-cli",
|
|
25
24
|
};
|
|
26
25
|
// Raises the rate limit from 60/hr to 5000/hr when the user has one set.
|
|
27
26
|
const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
package/dist/utils/http.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ export declare const MAX_ATTEMPTS = 3;
|
|
|
35
35
|
export declare const INFO_TIMEOUT_MS = 10000;
|
|
36
36
|
/** A normal authenticated API call. */
|
|
37
37
|
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
38
|
+
export declare const USER_AGENT = "aisa-cli";
|
|
38
39
|
export interface HttpOptions extends RequestInit {
|
|
39
40
|
timeoutMs?: number;
|
|
40
41
|
/** Opt in to retries. See the note above: this must mean "safe to send
|
package/dist/utils/http.js
CHANGED
|
@@ -35,6 +35,7 @@ export const MAX_ATTEMPTS = 3;
|
|
|
35
35
|
export const INFO_TIMEOUT_MS = 10_000;
|
|
36
36
|
/** A normal authenticated API call. */
|
|
37
37
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
38
|
+
export const USER_AGENT = "aisa-cli";
|
|
38
39
|
const BASE_BACKOFF_MS = 300;
|
|
39
40
|
/** Agents fan out many calls at once; identical backoff would resynchronise
|
|
40
41
|
* them into a second wave against a gateway that is already struggling. */
|
|
@@ -75,10 +76,18 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
75
76
|
export async function httpFetch(url, options = {}) {
|
|
76
77
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, idempotent = false, maxAttempts, ...init } = options;
|
|
77
78
|
const attempts = idempotent ? (maxAttempts ?? MAX_ATTEMPTS) : 1;
|
|
79
|
+
const headers = init.headers instanceof Headers || Array.isArray(init.headers)
|
|
80
|
+
? Object.fromEntries(new Headers(init.headers))
|
|
81
|
+
: { ...(init.headers ?? {}) };
|
|
82
|
+
for (const name of Object.keys(headers)) {
|
|
83
|
+
if (name.toLowerCase() === "user-agent")
|
|
84
|
+
delete headers[name];
|
|
85
|
+
}
|
|
86
|
+
headers["User-Agent"] = USER_AGENT;
|
|
78
87
|
let lastError;
|
|
79
88
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
80
89
|
try {
|
|
81
|
-
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
90
|
+
const res = await fetch(url, { ...init, headers, signal: AbortSignal.timeout(timeoutMs) });
|
|
82
91
|
if (attempt === attempts || !RETRYABLE_STATUS.has(res.status))
|
|
83
92
|
return res;
|
|
84
93
|
const after = retryAfterMs(res);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readCache, writeCache } from "../cache.js";
|
|
2
2
|
import { VERSION } from "../constants.js";
|
|
3
3
|
import { detectInstall } from "./install-method.js";
|
|
4
|
+
import { httpFetch } from "./http.js";
|
|
4
5
|
/**
|
|
5
6
|
* Throttled, best-effort check for a newer published version.
|
|
6
7
|
*
|
|
@@ -32,9 +33,7 @@ export function isNewer(candidate, current) {
|
|
|
32
33
|
return false;
|
|
33
34
|
}
|
|
34
35
|
async function defaultFetchLatest() {
|
|
35
|
-
const res = await
|
|
36
|
-
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
37
|
-
});
|
|
36
|
+
const res = await httpFetch(`https://registry.npmjs.org/${PACKAGE}/latest`, { timeoutMs: FETCH_TIMEOUT_MS });
|
|
38
37
|
if (!res.ok)
|
|
39
38
|
return undefined;
|
|
40
39
|
const json = (await res.json());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aisa-one/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "CLI for the AIsa unified AI infrastructure platform - one API key for 80+ LLMs and 900+ endpoints across finance, search, social, and video APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|