@aisa-one/cli 0.2.1 → 0.2.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 +11 -1
- package/README.md +2 -1
- package/dist/commands/account.d.ts +4 -1
- package/dist/commands/account.js +33 -5
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/index.js +2 -1
- package/dist/types.d.ts +8 -1
- package/package.json +1 -1
- package/tests/account.test.ts +89 -0
- package/dist/agent/env-setup.d.ts +0 -5
- package/dist/agent/env-setup.js +0 -49
- package/dist/agent/index-client.d.ts +0 -7
- package/dist/agent/index-client.js +0 -22
- package/dist/agent/installer.d.ts +0 -19
- package/dist/agent/installer.js +0 -70
- package/dist/agent/setup-python.d.ts +0 -15
- package/dist/agent/setup-python.js +0 -51
- package/dist/commands/agent.d.ts +0 -37
- package/dist/commands/agent.js +0 -129
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.2] — 2026-08-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`aisa balance` now queries the live account wallet through the API-key
|
|
15
|
+
balance endpoint.** It reports both the account balance and the amount
|
|
16
|
+
available to the current API key, with `--json` support for automation.
|
|
17
|
+
|
|
10
18
|
### Fixed
|
|
11
19
|
|
|
12
20
|
- **Skills registry now points at the canonical `agent-skills` repo.** The
|
|
@@ -58,5 +66,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
58
66
|
- Config commands (`aisa config get|set|list|reset`) and auth
|
|
59
67
|
(`aisa login|logout|whoami`).
|
|
60
68
|
|
|
61
|
-
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.2.
|
|
69
|
+
[Unreleased]: https://github.com/AIsa-team/cli/compare/v0.2.2...HEAD
|
|
70
|
+
[0.2.2]: https://github.com/AIsa-team/cli/compare/v0.2.1...v0.2.2
|
|
71
|
+
[0.2.1]: https://github.com/AIsa-team/cli/releases/tag/v0.2.1
|
|
62
72
|
[0.2.0]: https://github.com/AIsa-team/cli/releases/tag/v0.2.0
|
package/README.md
CHANGED
|
@@ -115,7 +115,8 @@ aisa video status <task-id> # check status
|
|
|
115
115
|
### Account
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
aisa balance #
|
|
118
|
+
aisa balance # Show wallet and API key credit balance
|
|
119
|
+
aisa balance --json # Output the balance response as JSON
|
|
119
120
|
aisa usage --limit 20 # 🚧 WIP
|
|
120
121
|
```
|
|
121
122
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function formatMicrosUSD(micros: number | string | bigint): string;
|
|
2
|
+
export declare function balanceAction(options?: {
|
|
3
|
+
json?: boolean;
|
|
4
|
+
}): Promise<void>;
|
|
2
5
|
export declare function usageAction(_options: {
|
|
3
6
|
limit?: string;
|
|
4
7
|
days?: string;
|
package/dist/commands/account.js
CHANGED
|
@@ -1,10 +1,38 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { requireApiKey } from "../config.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import { apiRequest } from "../api.js";
|
|
4
|
+
import { formatJson, hint } from "../utils/display.js";
|
|
5
|
+
export function formatMicrosUSD(micros) {
|
|
6
|
+
const value = typeof micros === "bigint" ? micros : BigInt(micros);
|
|
7
|
+
const negative = value < 0n;
|
|
8
|
+
let absolute = negative ? -value : value;
|
|
9
|
+
let cents = (absolute % 1000000n + 5000n) / 10000n;
|
|
10
|
+
let dollars = absolute / 1000000n;
|
|
11
|
+
if (cents === 100n) {
|
|
12
|
+
dollars += 1n;
|
|
13
|
+
cents = 0n;
|
|
14
|
+
}
|
|
15
|
+
return `${negative ? "-" : ""}$${dollars.toString()}.${cents.toString().padStart(2, "0")}`;
|
|
16
|
+
}
|
|
17
|
+
export async function balanceAction(options = {}) {
|
|
18
|
+
const key = requireApiKey();
|
|
19
|
+
const res = await apiRequest(key, "credits/balance");
|
|
20
|
+
if (!res.success || !res.data) {
|
|
21
|
+
throw new Error(res.error || "Failed to fetch balance");
|
|
22
|
+
}
|
|
23
|
+
if (options.json) {
|
|
24
|
+
console.log(formatJson(res.data));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const balance = res.data;
|
|
28
|
+
console.log(` Balance: ${formatMicrosUSD(balance.account_balance_micros_usd)} ${balance.currency}`);
|
|
29
|
+
console.log(` Available with key: ${formatMicrosUSD(balance.available_balance_micros_usd)} ${balance.currency}`);
|
|
30
|
+
if (balance.api_key.unlimited) {
|
|
31
|
+
console.log(" API key limit: Unlimited");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
console.log(` API key limit: ${formatMicrosUSD(balance.api_key.remaining_micros_usd)} remaining`);
|
|
35
|
+
}
|
|
8
36
|
}
|
|
9
37
|
export async function usageAction(_options) {
|
|
10
38
|
requireApiKey();
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.2.
|
|
1
|
+
export declare const VERSION = "0.2.2";
|
|
2
2
|
export declare const BASE_URL = "https://api.aisa.one";
|
|
3
3
|
export declare const CLI_BASE_URL = "https://api.aisa.one/v1";
|
|
4
4
|
export declare const APIS_BASE_URL = "https://api.aisa.one/apis/v1";
|
package/dist/constants.js
CHANGED
package/dist/index.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -26,8 +26,15 @@ export interface Model {
|
|
|
26
26
|
supported_endpoint_types?: string[] | null;
|
|
27
27
|
}
|
|
28
28
|
export interface BalanceResponse {
|
|
29
|
-
balance: number;
|
|
30
29
|
currency: string;
|
|
30
|
+
account_balance_micros_usd: number;
|
|
31
|
+
available_balance_micros_usd: number;
|
|
32
|
+
api_key: {
|
|
33
|
+
unlimited: boolean;
|
|
34
|
+
remaining_micros_usd: number;
|
|
35
|
+
used_micros_usd: number;
|
|
36
|
+
};
|
|
37
|
+
as_of: string;
|
|
31
38
|
}
|
|
32
39
|
export interface UsageRecord {
|
|
33
40
|
timestamp: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import { balanceAction, formatMicrosUSD } from "../src/commands/account.js";
|
|
3
|
+
|
|
4
|
+
const balance = {
|
|
5
|
+
currency: "USD",
|
|
6
|
+
account_balance_micros_usd: 2_500_000,
|
|
7
|
+
available_balance_micros_usd: 1_000_000,
|
|
8
|
+
api_key: {
|
|
9
|
+
unlimited: false,
|
|
10
|
+
remaining_micros_usd: 1_000_000,
|
|
11
|
+
used_micros_usd: 300_000,
|
|
12
|
+
},
|
|
13
|
+
as_of: "2026-08-03T12:00:00Z",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
describe("balanceAction", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
process.env.AISA_API_KEY = "test-balance-key";
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
delete process.env.AISA_API_KEY;
|
|
23
|
+
vi.unstubAllGlobals();
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("formats microdollars without floating-point drift", () => {
|
|
28
|
+
expect(formatMicrosUSD(2_500_000)).toBe("$2.50");
|
|
29
|
+
expect(formatMicrosUSD(2_505_001)).toBe("$2.51");
|
|
30
|
+
expect(formatMicrosUSD(1_999_999)).toBe("$2.00");
|
|
31
|
+
expect(formatMicrosUSD(-1_500_000)).toBe("-$1.50");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("requests the balance endpoint with the API key and renders the summary", async () => {
|
|
35
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify(balance), { status: 200 }));
|
|
36
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
37
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
38
|
+
|
|
39
|
+
await balanceAction();
|
|
40
|
+
|
|
41
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
42
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
43
|
+
expect(url).toBe("https://api.aisa.one/v1/credits/balance");
|
|
44
|
+
expect((init?.headers as Record<string, string>).Authorization).toBe("Bearer test-balance-key");
|
|
45
|
+
expect(log.mock.calls.map(([line]) => line).join("\n")).toContain("Balance:");
|
|
46
|
+
expect(log.mock.calls.map(([line]) => line).join("\n")).toContain("$2.50 USD");
|
|
47
|
+
expect(log.mock.calls.map(([line]) => line).join("\n")).toContain("$1.00 USD");
|
|
48
|
+
expect(log.mock.calls.map(([line]) => line).join("\n")).not.toContain("test-balance-key");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("renders unlimited keys", async () => {
|
|
52
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({
|
|
53
|
+
...balance,
|
|
54
|
+
api_key: { ...balance.api_key, unlimited: true },
|
|
55
|
+
}), { status: 200 }));
|
|
56
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
57
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
58
|
+
|
|
59
|
+
await balanceAction();
|
|
60
|
+
|
|
61
|
+
expect(log.mock.calls.map(([line]) => line).join("\n")).toContain("API key limit: Unlimited");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("supports raw JSON output", async () => {
|
|
65
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify(balance), { status: 200 }));
|
|
66
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
67
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
68
|
+
|
|
69
|
+
await balanceAction({ json: true });
|
|
70
|
+
|
|
71
|
+
expect(JSON.parse(log.mock.calls[0][0] as string)).toEqual(balance);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it.each([401, 403, 404, 500])("raises API errors for HTTP %s", async (status) => {
|
|
75
|
+
const fetchMock = vi.fn(async () => new Response(JSON.stringify({ error: "balance unavailable" }), { status }));
|
|
76
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
77
|
+
|
|
78
|
+
await expect(balanceAction()).rejects.toThrow(`${status}: balance unavailable`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("raises network errors", async () => {
|
|
82
|
+
const fetchMock = vi.fn(async () => {
|
|
83
|
+
throw new Error("connection refused");
|
|
84
|
+
});
|
|
85
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
86
|
+
|
|
87
|
+
await expect(balanceAction()).rejects.toThrow("connection refused");
|
|
88
|
+
});
|
|
89
|
+
});
|
package/dist/agent/env-setup.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { getApiKey } from "../config.js";
|
|
2
|
-
function parseEnvText(text) {
|
|
3
|
-
const map = new Map();
|
|
4
|
-
for (const line of (text ?? "").split("\n")) {
|
|
5
|
-
const m = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/);
|
|
6
|
-
if (m && m[2] !== "")
|
|
7
|
-
map.set(m[1], m[2]);
|
|
8
|
-
}
|
|
9
|
-
return map;
|
|
10
|
-
}
|
|
11
|
-
export async function collectEnv(manifest, existingEnvText, deps = {}) {
|
|
12
|
-
const prompt = deps.prompt ?? (async () => "");
|
|
13
|
-
const existing = parseEnvText(existingEnvText);
|
|
14
|
-
const values = new Map(existing);
|
|
15
|
-
// .env is user keys only. System defaults travel in the artifact's
|
|
16
|
-
// agent.json (render reads them; the installer passes them as process
|
|
17
|
-
// env for old artifacts). Legacy installs seeded system vars into .env
|
|
18
|
-
// — strip them, or updates would freeze them forever (HERMES_HOME even
|
|
19
|
-
// overrides hermes' --profile switch).
|
|
20
|
-
for (const legacy of ["HERMES_HOME", "PROFILE_ID", "MODEL_DEFAULT", "MODEL_PROVIDER"])
|
|
21
|
-
values.delete(legacy);
|
|
22
|
-
for (const v of manifest.env.required) {
|
|
23
|
-
if (values.has(v.name))
|
|
24
|
-
continue;
|
|
25
|
-
let val = process.env[v.name] || "";
|
|
26
|
-
if (!val && v.name === "AISA_API_KEY")
|
|
27
|
-
val = getApiKey() ?? "";
|
|
28
|
-
if (!val)
|
|
29
|
-
val = await prompt(`${v.name} (${v.description}): `);
|
|
30
|
-
if (!val)
|
|
31
|
-
throw new Error(`missing required ${v.name} — set it in your environment` +
|
|
32
|
-
(v.name === "AISA_API_KEY" ? ` or run "aisa login --key <key>"` : ""));
|
|
33
|
-
values.set(v.name, val);
|
|
34
|
-
}
|
|
35
|
-
const lines = [
|
|
36
|
-
`# ${manifest.name} — profile environment (managed by aisa agent install)`,
|
|
37
|
-
];
|
|
38
|
-
for (const [k, v] of values)
|
|
39
|
-
lines.push(`${k}=${v}`);
|
|
40
|
-
for (const v of manifest.env.optional) {
|
|
41
|
-
if (values.has(v.name))
|
|
42
|
-
continue;
|
|
43
|
-
if (process.env[v.name])
|
|
44
|
-
lines.push(`${v.name}=${process.env[v.name]}`);
|
|
45
|
-
else
|
|
46
|
-
lines.push(`# ${v.name}= (${v.description}${v.degrade ? `; degrade: ${v.degrade}` : ""})`);
|
|
47
|
-
}
|
|
48
|
-
return lines.join("\n") + "\n";
|
|
49
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type AgentIndex } from "@aisa-one/agent-spec";
|
|
2
|
-
export declare const DEFAULT_INDEX_URL = "https://raw.githubusercontent.com/AIsa-team/agent-index/main/index.json";
|
|
3
|
-
export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
4
|
-
export declare function indexUrl(): string;
|
|
5
|
-
export declare function fetchIndex(fetchImpl?: FetchLike): Promise<AgentIndex>;
|
|
6
|
-
export declare function requireAgent(index: AgentIndex, id: string): AgentIndex["agents"][string];
|
|
7
|
-
export declare function siblingAssetUrl(artifactUrl: string, filename: string): string;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { parseIndex } from "@aisa-one/agent-spec";
|
|
2
|
-
export const DEFAULT_INDEX_URL = "https://raw.githubusercontent.com/AIsa-team/agent-index/main/index.json";
|
|
3
|
-
export function indexUrl() {
|
|
4
|
-
return process.env.AISA_AGENT_INDEX_URL || DEFAULT_INDEX_URL;
|
|
5
|
-
}
|
|
6
|
-
export async function fetchIndex(fetchImpl = fetch) {
|
|
7
|
-
const res = await fetchImpl(indexUrl());
|
|
8
|
-
if (!res.ok)
|
|
9
|
-
throw new Error(`failed to fetch agent index (${res.status}): ${indexUrl()}`);
|
|
10
|
-
return parseIndex(await res.text());
|
|
11
|
-
}
|
|
12
|
-
export function requireAgent(index, id) {
|
|
13
|
-
const entry = index.agents[id];
|
|
14
|
-
if (!entry) {
|
|
15
|
-
const available = Object.keys(index.agents).sort().join(", ") || "(none)";
|
|
16
|
-
throw new Error(`unknown agent "${id}"\navailable agents: ${available}`);
|
|
17
|
-
}
|
|
18
|
-
return entry;
|
|
19
|
-
}
|
|
20
|
-
export function siblingAssetUrl(artifactUrl, filename) {
|
|
21
|
-
return artifactUrl.replace(/[^/]+$/, filename);
|
|
22
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type AgentManifest } from "@aisa-one/agent-spec";
|
|
2
|
-
import type { FetchLike } from "./index-client.js";
|
|
3
|
-
export interface InstalledMarker {
|
|
4
|
-
id: string;
|
|
5
|
-
version: string;
|
|
6
|
-
target: "hermes";
|
|
7
|
-
pinned: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare function hermesRoot(): string;
|
|
10
|
-
export declare function profileDir(id: string): string;
|
|
11
|
-
export declare function downloadArtifact(url: string, sha256: string, fetchImpl?: FetchLike): Promise<Buffer>;
|
|
12
|
-
export declare function extractBundle(buf: Buffer, destDir: string): Promise<void>;
|
|
13
|
-
export declare function readBundleManifest(destDir: string): Promise<AgentManifest>;
|
|
14
|
-
export declare function writeMarker(destDir: string, m: InstalledMarker): Promise<void>;
|
|
15
|
-
export declare function readMarker(dir: string): Promise<InstalledMarker | null>;
|
|
16
|
-
export declare function listInstalled(): Promise<{
|
|
17
|
-
dir: string;
|
|
18
|
-
marker: InstalledMarker;
|
|
19
|
-
}[]>;
|
package/dist/agent/installer.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile, readdir, rename, rm, mkdtemp } from "node:fs/promises";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { createHash } from "node:crypto";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import * as tar from "tar";
|
|
7
|
-
import { parseManifest } from "@aisa-one/agent-spec";
|
|
8
|
-
import { stringify as yamlStringify } from "yaml";
|
|
9
|
-
import { expandHome } from "../utils/file.js";
|
|
10
|
-
const MARKER = ".agentspec.json";
|
|
11
|
-
export function hermesRoot() {
|
|
12
|
-
return expandHome(process.env.HERMES_HOME || "~/.hermes");
|
|
13
|
-
}
|
|
14
|
-
export function profileDir(id) {
|
|
15
|
-
return join(hermesRoot(), "profiles", id);
|
|
16
|
-
}
|
|
17
|
-
export async function downloadArtifact(url, sha256, fetchImpl = fetch) {
|
|
18
|
-
const res = await fetchImpl(url);
|
|
19
|
-
if (!res.ok)
|
|
20
|
-
throw new Error(`download failed (${res.status}): ${url}`);
|
|
21
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
22
|
-
const actual = createHash("sha256").update(buf).digest("hex");
|
|
23
|
-
if (actual !== sha256)
|
|
24
|
-
throw new Error(`sha256 mismatch for ${url}\n expected ${sha256}\n actual ${actual}`);
|
|
25
|
-
return buf;
|
|
26
|
-
}
|
|
27
|
-
export async function extractBundle(buf, destDir) {
|
|
28
|
-
const tmp = await mkdtemp(join(tmpdir(), "aisa-agent-"));
|
|
29
|
-
const tarPath = join(tmp, "bundle.tar.gz");
|
|
30
|
-
await writeFile(tarPath, buf);
|
|
31
|
-
await mkdir(destDir, { recursive: true });
|
|
32
|
-
await tar.x({ file: tarPath, cwd: destDir });
|
|
33
|
-
// promote profile/ contents to the root (hermes profile layout)
|
|
34
|
-
const profileSub = join(destDir, "profile");
|
|
35
|
-
if (existsSync(profileSub)) {
|
|
36
|
-
for (const name of await readdir(profileSub)) {
|
|
37
|
-
await rm(join(destDir, name), { recursive: true, force: true });
|
|
38
|
-
await rename(join(profileSub, name), join(destDir, name));
|
|
39
|
-
}
|
|
40
|
-
await rm(profileSub, { recursive: true, force: true });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
export async function readBundleManifest(destDir) {
|
|
44
|
-
const raw = JSON.parse(await readFile(join(destDir, "agent.json"), "utf8"));
|
|
45
|
-
return parseManifest(yamlStringify(raw)); // 复用 schema 校验(YAML 是 JSON 超集)
|
|
46
|
-
}
|
|
47
|
-
export async function writeMarker(destDir, m) {
|
|
48
|
-
await writeFile(join(destDir, MARKER), JSON.stringify(m, null, 2) + "\n");
|
|
49
|
-
}
|
|
50
|
-
export async function readMarker(dir) {
|
|
51
|
-
try {
|
|
52
|
-
return JSON.parse(await readFile(join(dir, MARKER), "utf8"));
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
export async function listInstalled() {
|
|
59
|
-
const root = join(hermesRoot(), "profiles");
|
|
60
|
-
if (!existsSync(root))
|
|
61
|
-
return [];
|
|
62
|
-
const out = [];
|
|
63
|
-
for (const name of await readdir(root)) {
|
|
64
|
-
const dir = join(root, name);
|
|
65
|
-
const marker = await readMarker(dir);
|
|
66
|
-
if (marker)
|
|
67
|
-
out.push({ dir, marker });
|
|
68
|
-
}
|
|
69
|
-
return out.sort((a, b) => a.marker.id.localeCompare(b.marker.id));
|
|
70
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { AgentManifest } from "@aisa-one/agent-spec";
|
|
2
|
-
export type Exec = (cmd: string, args: string[]) => Promise<{
|
|
3
|
-
code: number;
|
|
4
|
-
stdout: string;
|
|
5
|
-
stderr: string;
|
|
6
|
-
}>;
|
|
7
|
-
export declare const SKIP_OPTIONAL_ENV = "AGENT_SKIP_OPTIONAL_SETUP";
|
|
8
|
-
export declare const SETUP_PYTHON_ENV = "AGENT_SETUP_PYTHON";
|
|
9
|
-
export interface SetupResult {
|
|
10
|
-
name: string;
|
|
11
|
-
status: "ok" | "skipped" | "failed";
|
|
12
|
-
}
|
|
13
|
-
/** Build the venvs declared in manifest.setup.python and point their env vars
|
|
14
|
-
* at the venv interpreters (written into <profileDir>/.env). */
|
|
15
|
-
export declare function runPythonSetup(manifest: AgentManifest, profileDir: string, exec: Exec): Promise<SetupResult[]>;
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
export const SKIP_OPTIONAL_ENV = "AGENT_SKIP_OPTIONAL_SETUP";
|
|
5
|
-
// venv 基础解释器覆盖(如系统 python3 过旧时指向 python3.12)
|
|
6
|
-
export const SETUP_PYTHON_ENV = "AGENT_SETUP_PYTHON";
|
|
7
|
-
async function upsertEnvLine(dir, key, value) {
|
|
8
|
-
const envPath = join(dir, ".env");
|
|
9
|
-
const text = existsSync(envPath) ? await readFile(envPath, "utf8") : "";
|
|
10
|
-
const lines = text.split("\n").filter((l) => !l.startsWith(`${key}=`));
|
|
11
|
-
while (lines.length && lines[lines.length - 1] === "")
|
|
12
|
-
lines.pop();
|
|
13
|
-
lines.push(`${key}=${value}`);
|
|
14
|
-
await writeFile(envPath, lines.join("\n") + "\n");
|
|
15
|
-
}
|
|
16
|
-
/** Build the venvs declared in manifest.setup.python and point their env vars
|
|
17
|
-
* at the venv interpreters (written into <profileDir>/.env). */
|
|
18
|
-
export async function runPythonSetup(manifest, profileDir, exec) {
|
|
19
|
-
const skipOptional = process.env[SKIP_OPTIONAL_ENV] === "1";
|
|
20
|
-
const results = [];
|
|
21
|
-
for (const s of manifest.setup.python) {
|
|
22
|
-
if (s.optional && skipOptional) {
|
|
23
|
-
console.log(`setup ${s.name}: optional, skipped (${SKIP_OPTIONAL_ENV}=1)`);
|
|
24
|
-
results.push({ name: s.name, status: "skipped" });
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const venvDir = join(profileDir, ".venvs", s.name);
|
|
28
|
-
const venvPython = join(venvDir, "bin", "python");
|
|
29
|
-
const reqPath = join(profileDir, s.requirements);
|
|
30
|
-
const basePython = process.env[SETUP_PYTHON_ENV] || "python3";
|
|
31
|
-
console.log(`setup ${s.name}: building venv (${s.requirements})…`);
|
|
32
|
-
const created = await exec(basePython, ["-m", "venv", venvDir]);
|
|
33
|
-
const installed = created.code === 0
|
|
34
|
-
? await exec(venvPython, ["-m", "pip", "install", "-r", reqPath])
|
|
35
|
-
: created;
|
|
36
|
-
if (installed.code !== 0) {
|
|
37
|
-
const detail = installed.stderr.slice(-500);
|
|
38
|
-
if (!s.optional)
|
|
39
|
-
throw new Error(`setup ${s.name} failed (required):\n${detail}`);
|
|
40
|
-
console.log(`setup ${s.name}: failed, degrading (optional). ${s.description ?? ""}`);
|
|
41
|
-
if (detail.trim())
|
|
42
|
-
console.log(` reason: ${detail.trim().split("\n").slice(-3).join(" | ")}`);
|
|
43
|
-
results.push({ name: s.name, status: "failed" });
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
await upsertEnvLine(profileDir, s.env, venvPython);
|
|
47
|
-
console.log(`setup ${s.name}: ok -> ${s.env}=${venvPython}`);
|
|
48
|
-
results.push({ name: s.name, status: "ok" });
|
|
49
|
-
}
|
|
50
|
-
return results;
|
|
51
|
-
}
|
package/dist/commands/agent.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { type FetchLike } from "../agent/index-client.js";
|
|
2
|
-
import { type Prompt } from "../agent/env-setup.js";
|
|
3
|
-
import type { AgentIndex } from "@aisa-one/agent-spec";
|
|
4
|
-
export type Exec = (cmd: string, args: string[]) => Promise<{
|
|
5
|
-
code: number;
|
|
6
|
-
stdout: string;
|
|
7
|
-
stderr: string;
|
|
8
|
-
}>;
|
|
9
|
-
export declare function agentListAction(_opts: Record<string, never>, deps?: {
|
|
10
|
-
fetchImpl?: FetchLike;
|
|
11
|
-
}): Promise<AgentIndex>;
|
|
12
|
-
export declare function agentInfoAction(id: string, deps?: {
|
|
13
|
-
fetchImpl?: FetchLike;
|
|
14
|
-
}): Promise<AgentIndex["agents"][string]>;
|
|
15
|
-
export declare function agentInstallAction(id: string, opts: {
|
|
16
|
-
version?: string;
|
|
17
|
-
runtime?: string;
|
|
18
|
-
}, deps?: {
|
|
19
|
-
fetchImpl?: FetchLike;
|
|
20
|
-
prompt?: Prompt;
|
|
21
|
-
exec?: Exec;
|
|
22
|
-
}): Promise<void>;
|
|
23
|
-
export declare function agentUpdateAction(id: string | undefined, _opts: Record<string, never>, deps?: {
|
|
24
|
-
fetchImpl?: FetchLike;
|
|
25
|
-
prompt?: Prompt;
|
|
26
|
-
exec?: Exec;
|
|
27
|
-
}): Promise<{
|
|
28
|
-
id: string;
|
|
29
|
-
from: string;
|
|
30
|
-
to: string;
|
|
31
|
-
status: "updated" | "up-to-date" | "pinned";
|
|
32
|
-
}[]>;
|
|
33
|
-
export declare function agentGuideAction(id: string, opts: {
|
|
34
|
-
md?: boolean;
|
|
35
|
-
}, deps?: {
|
|
36
|
-
fetchImpl?: FetchLike;
|
|
37
|
-
}): Promise<string>;
|
package/dist/commands/agent.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
6
|
-
import { table, success, hint, truncate } from "../utils/display.js";
|
|
7
|
-
import { fetchIndex, requireAgent, siblingAssetUrl } from "../agent/index-client.js";
|
|
8
|
-
import { hermesRoot, profileDir, downloadArtifact, extractBundle, readBundleManifest, writeMarker, listInstalled, } from "../agent/installer.js";
|
|
9
|
-
import { collectEnv } from "../agent/env-setup.js";
|
|
10
|
-
import { runPythonSetup } from "../agent/setup-python.js";
|
|
11
|
-
const realExec = (cmd, args) => new Promise((resolve) => {
|
|
12
|
-
execFile(cmd, args, (err, stdout, stderr) => resolve({ code: err ? 1 : 0, stdout: String(stdout), stderr: String(stderr) }));
|
|
13
|
-
});
|
|
14
|
-
export async function agentListAction(_opts, deps = {}) {
|
|
15
|
-
const index = await fetchIndex(deps.fetchImpl);
|
|
16
|
-
const rows = Object.entries(index.agents).sort(([a], [b]) => a.localeCompare(b))
|
|
17
|
-
.map(([id, e]) => [id, e.name, e.latest, truncate(e.description, 60)]);
|
|
18
|
-
console.log(table(["ID", "NAME", "LATEST", "DESCRIPTION"], rows));
|
|
19
|
-
hint(`install one with: aisa agent install <id>`);
|
|
20
|
-
return index;
|
|
21
|
-
}
|
|
22
|
-
export async function agentInfoAction(id, deps = {}) {
|
|
23
|
-
const index = await fetchIndex(deps.fetchImpl);
|
|
24
|
-
const e = requireAgent(index, id);
|
|
25
|
-
console.log(`${chalk.bold(e.name)} (${id})`);
|
|
26
|
-
console.log(` ${e.description}`);
|
|
27
|
-
console.log(` repo: https://github.com/${e.repo}`);
|
|
28
|
-
console.log(` latest: ${e.latest}`);
|
|
29
|
-
for (const [v, spec] of Object.entries(e.versions))
|
|
30
|
-
console.log(` ${v}: targets ${Object.keys(spec.targets).join(", ")}`);
|
|
31
|
-
hint(`install with: aisa agent install ${id}`);
|
|
32
|
-
return e;
|
|
33
|
-
}
|
|
34
|
-
export async function agentInstallAction(id, opts, deps = {}) {
|
|
35
|
-
const runtime = opts.runtime ?? "hermes";
|
|
36
|
-
if (runtime !== "hermes")
|
|
37
|
-
throw new Error(`runtime "${runtime}" not supported yet — v1 supports: hermes`);
|
|
38
|
-
if (!existsSync(hermesRoot()))
|
|
39
|
-
throw new Error(`hermes not found at ${hermesRoot()} — install it first:\n` +
|
|
40
|
-
` curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash`);
|
|
41
|
-
const index = await fetchIndex(deps.fetchImpl);
|
|
42
|
-
const entry = requireAgent(index, id);
|
|
43
|
-
const version = opts.version ?? entry.latest;
|
|
44
|
-
const versionSpec = entry.versions[version];
|
|
45
|
-
if (!versionSpec)
|
|
46
|
-
throw new Error(`version ${version} not found for ${id} — available: ${Object.keys(entry.versions).join(", ")}`);
|
|
47
|
-
const artifact = versionSpec.targets[runtime];
|
|
48
|
-
if (!artifact)
|
|
49
|
-
throw new Error(`no ${runtime} artifact for ${id}@${version}`);
|
|
50
|
-
const dir = profileDir(id);
|
|
51
|
-
const existingEnv = existsSync(join(dir, ".env"))
|
|
52
|
-
? await readFile(join(dir, ".env"), "utf8") : null;
|
|
53
|
-
const buf = await downloadArtifact(artifact.url, artifact.sha256, deps.fetchImpl);
|
|
54
|
-
await extractBundle(buf, dir);
|
|
55
|
-
const manifest = await readBundleManifest(dir);
|
|
56
|
-
const envText = await collectEnv(manifest, existingEnv, { prompt: deps.prompt });
|
|
57
|
-
await writeFile(join(dir, ".env"), envText);
|
|
58
|
-
const exec = deps.exec ?? realExec;
|
|
59
|
-
// system vars go to render as process env, never into .env — new render.sh
|
|
60
|
-
// reads them from the bundled agent.json itself, old artifacts (≤1.0.4)
|
|
61
|
-
// still require them in the environment
|
|
62
|
-
const r = await exec("env", [
|
|
63
|
-
`PROFILE_ID=${manifest.id}`,
|
|
64
|
-
`MODEL_DEFAULT=${manifest.models.default}`,
|
|
65
|
-
`MODEL_PROVIDER=${manifest.models.provider}`,
|
|
66
|
-
"bash", join(dir, "scripts", "render.sh"), dir,
|
|
67
|
-
]);
|
|
68
|
-
if (r.code !== 0)
|
|
69
|
-
throw new Error(`render failed: ${r.stderr}`);
|
|
70
|
-
await runPythonSetup(manifest, dir, exec);
|
|
71
|
-
await writeMarker(dir, {
|
|
72
|
-
id, version, target: "hermes",
|
|
73
|
-
pinned: !!opts.version || manifest.update.channel === "pinned",
|
|
74
|
-
});
|
|
75
|
-
success(`installed ${entry.name} ${version} -> ${dir}`);
|
|
76
|
-
hint(`start it with: hermes --profile ${id}`);
|
|
77
|
-
}
|
|
78
|
-
export async function agentUpdateAction(id, _opts, deps = {}) {
|
|
79
|
-
const installed = await listInstalled();
|
|
80
|
-
const targets = id ? installed.filter((i) => i.marker.id === id) : installed;
|
|
81
|
-
if (id && targets.length === 0)
|
|
82
|
-
throw new Error(`agent "${id}" is not installed (no .agentspec.json marker found)`);
|
|
83
|
-
if (targets.length === 0) {
|
|
84
|
-
hint("no installed agents found");
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
const index = await fetchIndex(deps.fetchImpl);
|
|
88
|
-
const results = [];
|
|
89
|
-
for (const { marker } of targets) {
|
|
90
|
-
if (marker.pinned) {
|
|
91
|
-
console.log(`${marker.id}: pinned at ${marker.version} — skipping`);
|
|
92
|
-
results.push({ id: marker.id, from: marker.version, to: marker.version, status: "pinned" });
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
const entry = index.agents[marker.id];
|
|
96
|
-
if (!entry || entry.latest === marker.version) {
|
|
97
|
-
results.push({ id: marker.id, from: marker.version, to: marker.version, status: "up-to-date" });
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
await agentInstallAction(marker.id, {}, deps); // install 已保留 .env、更新 marker
|
|
101
|
-
results.push({ id: marker.id, from: marker.version, to: entry.latest, status: "updated" });
|
|
102
|
-
}
|
|
103
|
-
for (const r of results)
|
|
104
|
-
if (r.status === "updated")
|
|
105
|
-
success(`${r.id}: ${r.from} -> ${r.to}`);
|
|
106
|
-
return results;
|
|
107
|
-
}
|
|
108
|
-
export async function agentGuideAction(id, opts, deps = {}) {
|
|
109
|
-
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
110
|
-
const index = await fetchIndex(deps.fetchImpl);
|
|
111
|
-
const entry = requireAgent(index, id);
|
|
112
|
-
const artifact = entry.versions[entry.latest]?.targets["hermes"];
|
|
113
|
-
if (!artifact)
|
|
114
|
-
throw new Error(`no hermes artifact for ${id}@${entry.latest}`);
|
|
115
|
-
// 优先读索引携带的资产 URL(2026-07-15 起 publish 写入);老条目回落到
|
|
116
|
-
// legacy 裸文件名拼接,保证旧版本 Release 仍可用
|
|
117
|
-
const url = opts.md
|
|
118
|
-
? (artifact.installMd ?? siblingAssetUrl(artifact.url, "INSTALL.md"))
|
|
119
|
-
: (artifact.guidePrompt ?? siblingAssetUrl(artifact.url, "guide-prompt.txt"));
|
|
120
|
-
const filename = url.split("/").pop();
|
|
121
|
-
const res = await fetchImpl(url);
|
|
122
|
-
if (!res.ok)
|
|
123
|
-
throw new Error(`failed to fetch ${filename} (${res.status}): ${url}`);
|
|
124
|
-
const text = await res.text();
|
|
125
|
-
console.log(text);
|
|
126
|
-
if (!opts.md)
|
|
127
|
-
hint("paste the text above into your local AI agent (Claude Code, hermes, …)");
|
|
128
|
-
return text;
|
|
129
|
-
}
|