@aisa-one/cli 0.6.0 → 0.6.2
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 +20 -1
- package/README.md +22 -1
- package/dist/commands/account.js +1 -0
- 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/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/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.js +5 -0
- package/dist/skills-registry.js +0 -1
- package/dist/types.d.ts +1 -0
- 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,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.2] — 2026-09-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Carry Router session IDs through CLI tool commands.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Send `User-Agent: aisa-cli` on outbound HTTP requests.
|
|
19
|
+
|
|
20
|
+
## [0.6.1] — 2026-09-11
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Show the GTM balance separately in `aisa balance`. The account and
|
|
25
|
+
key-available totals now reflect the API's combined PAYG and GTM balances.
|
|
26
|
+
|
|
10
27
|
## [0.6.0] — 2026-09-10
|
|
11
28
|
|
|
12
29
|
### Changed
|
|
@@ -444,7 +461,9 @@ supports today; nothing here depends on a backend change.
|
|
|
444
461
|
- Config commands (`aisa config get|set|list|reset`) and auth
|
|
445
462
|
(`aisa login|logout|whoami`).
|
|
446
463
|
|
|
447
|
-
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.
|
|
464
|
+
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.6.2...HEAD
|
|
465
|
+
[0.6.2]: https://github.com/AIsa-team/cli/compare/v0.6.1...v0.6.2
|
|
466
|
+
[0.6.1]: https://github.com/AIsa-team/cli/compare/v0.6.0...v0.6.1
|
|
448
467
|
[0.6.0]: https://github.com/AIsa-team/cli/compare/v0.5.2...v0.6.0
|
|
449
468
|
[0.5.2]: https://github.com/AIsa-team/cli/compare/v0.5.1...v0.5.2
|
|
450
469
|
[0.5.1]: https://github.com/AIsa-team/cli/compare/v0.5.0...v0.5.1
|
package/README.md
CHANGED
|
@@ -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
|
package/dist/commands/account.js
CHANGED
|
@@ -29,6 +29,7 @@ export async function balanceAction(options = {}) {
|
|
|
29
29
|
const balance = res.data;
|
|
30
30
|
console.log(` Balance: ${formatMicrosUSD(balance.account_balance_micros_usd)} ${balance.currency}`);
|
|
31
31
|
console.log(` Available with key: ${formatMicrosUSD(balance.available_balance_micros_usd)} ${balance.currency}`);
|
|
32
|
+
console.log(` GTM balance: ${formatMicrosUSD(balance.go_to_market_balance_micros_usd)} ${balance.currency}`);
|
|
32
33
|
if (balance.api_key.unlimited) {
|
|
33
34
|
console.log(" API key limit: Unlimited");
|
|
34
35
|
}
|
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();
|
|
@@ -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/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.2";
|
|
2
2
|
/** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
|
|
3
3
|
export declare const BASE_URL = "https://api.aisa.one";
|
|
4
4
|
export declare const ENV_VAR_NAME = "AISA_API_KEY";
|
package/dist/constants.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -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)));
|
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/types.d.ts
CHANGED
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.2",
|
|
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",
|