@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 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.0...HEAD
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. **Enforced:** invalid local input exits 2 and is not sent;
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
@@ -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
  }
@@ -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 fetch(`${u.origin}/status?token=${token}`, { signal: AbortSignal.timeout(2_000) });
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());
@@ -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 fetch(url, { method: "HEAD", signal: AbortSignal.timeout(3_000) });
75
+ const res = await httpFetch(url, { method: "HEAD", timeoutMs: 3_000 });
75
76
  return res.ok;
76
77
  }
77
78
  catch {
@@ -254,7 +254,7 @@ export async function mcpStatusAction() {
254
254
  */
255
255
  export async function pingEndpoint(endpoint) {
256
256
  try {
257
- const res = await fetch(endpoint, {
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
- signal: AbortSignal.timeout(8_000),
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 fetch(`http://127.0.0.1:${rec.port}/status?token=${rec.token}`, {
60
- signal: AbortSignal.timeout(1_500),
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 from search. ${EXAMPLE_PUBLISHED_TOOL} is a published tool whose schema includes ticker.`,
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.",
@@ -2,6 +2,7 @@ export type RouterKind = "search" | "schema" | "quote" | "call";
2
2
  export interface RouterIoOptions {
3
3
  input?: string;
4
4
  file?: string;
5
+ sessionId?: string;
5
6
  json?: boolean;
6
7
  limit?: string;
7
8
  provider?: string[];
@@ -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) {
@@ -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;
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.6.0";
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
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.6.0";
1
+ export const VERSION = "0.6.2";
2
2
  /** Root of the platform. Per-surface bases are derived in api.ts#resolveBases. */
3
3
  export const BASE_URL = "https://api.aisa.one";
4
4
  export const ENV_VAR_NAME = "AISA_API_KEY";
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)));
@@ -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
@@ -29,6 +29,7 @@ export interface BalanceResponse {
29
29
  currency: string;
30
30
  account_balance_micros_usd: number;
31
31
  available_balance_micros_usd: number;
32
+ go_to_market_balance_micros_usd: number;
32
33
  api_key: {
33
34
  unlimited: boolean;
34
35
  remaining_micros_usd: number;
@@ -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
@@ -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 fetch(`https://registry.npmjs.org/${PACKAGE}/latest`, {
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.0",
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",