@aigne/cli 1.33.1 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/cli.js +2 -1
- package/dist/commands/aigne.js +2 -2
- package/dist/commands/app.js +4 -1
- package/dist/commands/hub.d.ts +2 -0
- package/dist/commands/hub.js +271 -0
- package/dist/commands/run.js +33 -14
- package/dist/commands/serve-mcp.js +4 -1
- package/dist/commands/test.js +4 -1
- package/dist/tracer/terminal.d.ts +1 -1
- package/dist/tracer/terminal.js +54 -27
- package/dist/utils/load-aigne.d.ts +11 -65
- package/dist/utils/load-aigne.js +32 -266
- package/dist/utils/run-with-aigne.d.ts +3 -5
- package/dist/utils/run-with-aigne.js +17 -14
- package/dist/utils/string-utils.d.ts +1 -0
- package/dist/utils/string-utils.js +4 -0
- package/package.json +9 -11
- package/dist/commands/connect.d.ts +0 -13
- package/dist/commands/connect.js +0 -99
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.35.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.34.1...cli-v1.35.0) (2025-08-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **cli:** support aigne hub commond ([#352](https://github.com/AIGNE-io/aigne-framework/issues/352)) ([0341f19](https://github.com/AIGNE-io/aigne-framework/commit/0341f190229b42c5d2ab8a8616597359f35543a7))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @aigne/aigne-hub bumped to 0.5.0
|
|
16
|
+
|
|
17
|
+
## [1.34.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.34.0...cli-v1.34.1) (2025-08-12)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
* The following workspace dependencies were updated
|
|
23
|
+
* dependencies
|
|
24
|
+
* @aigne/agent-library bumped to 1.21.19
|
|
25
|
+
* @aigne/agentic-memory bumped to 1.0.19
|
|
26
|
+
* @aigne/aigne-hub bumped to 0.4.10
|
|
27
|
+
* @aigne/core bumped to 1.49.1
|
|
28
|
+
* @aigne/default-memory bumped to 1.1.1
|
|
29
|
+
* @aigne/openai bumped to 0.11.1
|
|
30
|
+
|
|
31
|
+
## [1.34.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.33.1...cli-v1.34.0) (2025-08-12)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
* **cli:** add retry functionality and improve error handling for AIGNE Hub ([#348](https://github.com/AIGNE-io/aigne-framework/issues/348)) ([672c93a](https://github.com/AIGNE-io/aigne-framework/commit/672c93abbba8b4b234f6d810536ff4b603a97e1e))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Bug Fixes
|
|
40
|
+
|
|
41
|
+
* **core:** examples cases that failed when using aigne-hub ([#337](https://github.com/AIGNE-io/aigne-framework/issues/337)) ([0d4a31c](https://github.com/AIGNE-io/aigne-framework/commit/0d4a31c24d9e7d26f00d1accb80719d9ad79a4c6))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Dependencies
|
|
45
|
+
|
|
46
|
+
* The following workspace dependencies were updated
|
|
47
|
+
* dependencies
|
|
48
|
+
* @aigne/agent-library bumped to 1.21.18
|
|
49
|
+
* @aigne/agentic-memory bumped to 1.0.18
|
|
50
|
+
* @aigne/aigne-hub bumped to 0.4.9
|
|
51
|
+
* @aigne/core bumped to 1.49.0
|
|
52
|
+
* @aigne/default-memory bumped to 1.1.0
|
|
53
|
+
* @aigne/openai bumped to 0.11.0
|
|
54
|
+
|
|
3
55
|
## [1.33.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.33.0...cli-v1.33.1) (2025-08-12)
|
|
4
56
|
|
|
5
57
|
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from "chalk";
|
|
|
4
4
|
import { config } from "dotenv-flow";
|
|
5
5
|
import { hideBin } from "yargs/helpers";
|
|
6
6
|
import { createAIGNECommand } from "./commands/aigne.js";
|
|
7
|
+
import { highlightUrl } from "./utils/string-utils.js";
|
|
7
8
|
config({ silent: true });
|
|
8
9
|
function getAIGNEFilePath() {
|
|
9
10
|
let file = process.argv[2];
|
|
@@ -27,6 +28,6 @@ export default createAIGNECommand({ aigneFilePath })
|
|
|
27
28
|
.parseAsync(hideBin([...process.argv.slice(0, 2), ...process.argv.slice(aigneFilePath ? 3 : 2)]))
|
|
28
29
|
.catch((error) => {
|
|
29
30
|
console.log(""); // Add an empty line for better readability
|
|
30
|
-
console.error(`${chalk.red("Error:")} ${error.message
|
|
31
|
+
console.error(`${chalk.red("Error:")} ${highlightUrl(error.message)}`);
|
|
31
32
|
process.exit(1);
|
|
32
33
|
});
|
package/dist/commands/aigne.js
CHANGED
|
@@ -3,8 +3,8 @@ import yargs from "yargs";
|
|
|
3
3
|
import { AIGNE_CLI_VERSION } from "../constants.js";
|
|
4
4
|
import { asciiLogo } from "../utils/ascii-logo.js";
|
|
5
5
|
import { createAppCommands } from "./app.js";
|
|
6
|
-
import { createConnectCommand } from "./connect.js";
|
|
7
6
|
import { createCreateCommand } from "./create.js";
|
|
7
|
+
import { createHubCommand } from "./hub.js";
|
|
8
8
|
import { createObservabilityCommand } from "./observe.js";
|
|
9
9
|
import { createRunCommand } from "./run.js";
|
|
10
10
|
import { createServeMCPCommand } from "./serve-mcp.js";
|
|
@@ -21,8 +21,8 @@ export function createAIGNECommand(options) {
|
|
|
21
21
|
.command(createCreateCommand())
|
|
22
22
|
.command(createServeMCPCommand(options))
|
|
23
23
|
.command(createObservabilityCommand())
|
|
24
|
-
.command(createConnectCommand())
|
|
25
24
|
.command(createAppCommands())
|
|
25
|
+
.command(createHubCommand())
|
|
26
26
|
.demandCommand()
|
|
27
27
|
.alias("help", "h")
|
|
28
28
|
.alias("version", "v")
|
package/dist/commands/app.js
CHANGED
|
@@ -130,7 +130,10 @@ function innerZodType(type) {
|
|
|
130
130
|
return type;
|
|
131
131
|
}
|
|
132
132
|
export async function invokeCLIAgentFromDir(options) {
|
|
133
|
-
const aigne = await loadAIGNE(
|
|
133
|
+
const aigne = await loadAIGNE({
|
|
134
|
+
path: options.dir,
|
|
135
|
+
options: { model: options.input.model },
|
|
136
|
+
});
|
|
134
137
|
try {
|
|
135
138
|
const agent = aigne.cli.agents[options.agent];
|
|
136
139
|
assert(agent, `Agent ${options.agent} not found in ${options.dir}`);
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { AIGNE_ENV_FILE, AIGNE_HUB_URL, connectToAIGNEHub, isTest } from "@aigne/aigne-hub";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import Table from "cli-table3";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
import { parse, stringify } from "yaml";
|
|
8
|
+
import { getUserInfo } from "../utils/aigne-hub-user.js";
|
|
9
|
+
const formatNumber = (balance) => {
|
|
10
|
+
const balanceNum = String(balance).split(".")[0];
|
|
11
|
+
return chalk.yellow((balanceNum || "").replace(/\B(?=(\d{3})+(?!\d))/g, ","));
|
|
12
|
+
};
|
|
13
|
+
function printHubStatus(data) {
|
|
14
|
+
const divider = "─".repeat(46);
|
|
15
|
+
console.log(chalk.bold("AIGNE Hub Connection"));
|
|
16
|
+
console.log(chalk.gray(divider));
|
|
17
|
+
console.log(`${chalk.bold("Hub:".padEnd(10))} ${data.hub}`);
|
|
18
|
+
console.log(`${chalk.bold("Status:".padEnd(10))} ${data.status === "Connected"
|
|
19
|
+
? chalk.green(`${data.status} ✅`)
|
|
20
|
+
: chalk.red(`${data.status} ❌`)}`);
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log(chalk.bold("User:"));
|
|
23
|
+
console.log(` ${chalk.bold("Name:".padEnd(8))} ${data.user.name}`);
|
|
24
|
+
console.log(` ${chalk.bold("DID:".padEnd(8))} ${data.user.did}`);
|
|
25
|
+
console.log(` ${chalk.bold("Email:".padEnd(8))} ${data.user.email}`);
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log(chalk.bold("Credits:"));
|
|
28
|
+
console.log(` ${chalk.bold("Used:".padEnd(8))} ${data.credits.used.toLocaleString()}`);
|
|
29
|
+
console.log(` ${chalk.bold("Total:".padEnd(8))} ${data.credits.total.toLocaleString()}`);
|
|
30
|
+
console.log("");
|
|
31
|
+
console.log(chalk.bold("Links:"));
|
|
32
|
+
console.log(` ${chalk.bold("Payment:".padEnd(8))} ${data.links.payment}`);
|
|
33
|
+
console.log(` ${chalk.bold("Profile:".padEnd(8))} ${data.links.profile}`);
|
|
34
|
+
}
|
|
35
|
+
async function getHubs() {
|
|
36
|
+
if (!existsSync(AIGNE_ENV_FILE)) {
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const data = await readFile(AIGNE_ENV_FILE, "utf8");
|
|
41
|
+
const envs = parse(data);
|
|
42
|
+
const statusList = [];
|
|
43
|
+
for (const [host, config] of Object.entries(envs)) {
|
|
44
|
+
if (host !== "default") {
|
|
45
|
+
statusList.push({
|
|
46
|
+
host,
|
|
47
|
+
apiUrl: config.AIGNE_HUB_API_URL,
|
|
48
|
+
apiKey: config.AIGNE_HUB_API_KEY,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return statusList;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const getDefaultHub = async () => {
|
|
59
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
60
|
+
return envs?.default?.AIGNE_HUB_API_URL || AIGNE_HUB_URL;
|
|
61
|
+
};
|
|
62
|
+
async function formatHubsList(statusList) {
|
|
63
|
+
if (statusList?.length === 0) {
|
|
64
|
+
console.log(chalk.yellow("No AIGNE Hub configured."));
|
|
65
|
+
console.log("Use 'aigne hub connect' to connect to a hub.");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const defaultHub = await getDefaultHub();
|
|
69
|
+
const table = new Table({
|
|
70
|
+
head: ["URL", "ACTIVE", "STATUS"],
|
|
71
|
+
colWidths: [50, 10, 20],
|
|
72
|
+
style: {
|
|
73
|
+
head: ["cyan"],
|
|
74
|
+
border: ["grey"],
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
console.log(chalk.blue("AIGNE Hubs:\n"));
|
|
78
|
+
for (const status of statusList) {
|
|
79
|
+
const isConnected = new URL(status.apiUrl).origin === new URL(defaultHub).origin;
|
|
80
|
+
table.push([
|
|
81
|
+
status.apiUrl,
|
|
82
|
+
isConnected ? "*" : "-",
|
|
83
|
+
isConnected ? "Connected" : "Not connected",
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
console.log(table.toString());
|
|
87
|
+
}
|
|
88
|
+
export function createHubCommand() {
|
|
89
|
+
return {
|
|
90
|
+
command: "hub <command>",
|
|
91
|
+
describe: "Manage AIGNE Hub connections",
|
|
92
|
+
builder: (yargs) => yargs
|
|
93
|
+
.command(["list", "ls"], "List all connected AIGNE Hubs", listHubs)
|
|
94
|
+
.command("connect", "Connect to an AIGNE Hub", connectHub)
|
|
95
|
+
.command("use", "Switch to a different AIGNE Hub", useHub)
|
|
96
|
+
.command(["status", "st"], "Show current active hub", showStatus)
|
|
97
|
+
.command(["remove", "rm"], "Remove a connected hub", removeHub)
|
|
98
|
+
.command(["info", "i"], "Show details of a connected hub", showInfo)
|
|
99
|
+
.demandCommand(1, "Please provide a valid hub command"),
|
|
100
|
+
handler: () => { },
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const listHubs = async () => {
|
|
104
|
+
const list = await getHubs();
|
|
105
|
+
await formatHubsList(list);
|
|
106
|
+
};
|
|
107
|
+
async function connectHub() {
|
|
108
|
+
const defaultUrl = "https://hub.aigne.io";
|
|
109
|
+
const { isOfficial } = await inquirer.prompt({
|
|
110
|
+
type: "select",
|
|
111
|
+
name: "isOfficial",
|
|
112
|
+
message: `Choose a hub to connect:`,
|
|
113
|
+
choices: [
|
|
114
|
+
{ name: `Official Hub (${defaultUrl})`, value: true },
|
|
115
|
+
{ name: `Custom Hub URL`, value: false },
|
|
116
|
+
],
|
|
117
|
+
default: true,
|
|
118
|
+
});
|
|
119
|
+
let currentUrl = defaultUrl;
|
|
120
|
+
if (!isOfficial) {
|
|
121
|
+
const { customUrl } = await inquirer.prompt({
|
|
122
|
+
type: "input",
|
|
123
|
+
name: "customUrl",
|
|
124
|
+
message: "Enter the URL of your AIGNE Hub:",
|
|
125
|
+
validate: validateUrl,
|
|
126
|
+
});
|
|
127
|
+
currentUrl = customUrl;
|
|
128
|
+
}
|
|
129
|
+
await saveAndConnect(currentUrl);
|
|
130
|
+
}
|
|
131
|
+
async function useHub() {
|
|
132
|
+
const hubs = await getHubs();
|
|
133
|
+
if (!hubs.length) {
|
|
134
|
+
console.log(chalk.yellow("No AIGNE Hub configured."));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const { hubApiKey } = await inquirer.prompt({
|
|
138
|
+
type: "select",
|
|
139
|
+
name: "hubApiKey",
|
|
140
|
+
message: `Choose a hub to switch to:`,
|
|
141
|
+
choices: hubs.map((h) => ({
|
|
142
|
+
name: new URL(h.apiUrl).origin,
|
|
143
|
+
value: h.apiUrl,
|
|
144
|
+
})),
|
|
145
|
+
});
|
|
146
|
+
await setDefaultHub(hubApiKey);
|
|
147
|
+
}
|
|
148
|
+
async function showStatus() {
|
|
149
|
+
const active = await getDefaultHub();
|
|
150
|
+
if (!active) {
|
|
151
|
+
console.log(chalk.red("No active hub."));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(`Active hub: ${active} - online`);
|
|
155
|
+
}
|
|
156
|
+
async function removeHub() {
|
|
157
|
+
const hubs = await getHubs();
|
|
158
|
+
if (!hubs.length) {
|
|
159
|
+
console.log(chalk.yellow("No AIGNE Hub configured."));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const { hubApiKey } = await inquirer.prompt({
|
|
163
|
+
type: "select",
|
|
164
|
+
name: "hubApiKey",
|
|
165
|
+
message: `Choose a hub to remove:`,
|
|
166
|
+
choices: hubs.map((h) => ({
|
|
167
|
+
name: new URL(h.apiUrl).origin,
|
|
168
|
+
value: h.apiUrl,
|
|
169
|
+
})),
|
|
170
|
+
});
|
|
171
|
+
await deleteHub(hubApiKey);
|
|
172
|
+
}
|
|
173
|
+
async function showInfo() {
|
|
174
|
+
const hubs = await getHubs();
|
|
175
|
+
if (!hubs.length) {
|
|
176
|
+
console.log(chalk.yellow("No AIGNE Hub configured."));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const { hubApiKey } = await inquirer.prompt({
|
|
180
|
+
type: "select",
|
|
181
|
+
name: "hubApiKey",
|
|
182
|
+
message: `Choose a hub to view info:`,
|
|
183
|
+
choices: hubs.map((h) => ({
|
|
184
|
+
name: new URL(h.apiUrl).origin,
|
|
185
|
+
value: h.apiUrl,
|
|
186
|
+
})),
|
|
187
|
+
});
|
|
188
|
+
await printHubDetails(hubApiKey);
|
|
189
|
+
}
|
|
190
|
+
function validateUrl(input) {
|
|
191
|
+
try {
|
|
192
|
+
const url = new URL(input);
|
|
193
|
+
return url.protocol.startsWith("http") ? true : "Must be http or https";
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return "Invalid URL";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function saveAndConnect(url) {
|
|
200
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
201
|
+
const host = new URL(url).host;
|
|
202
|
+
if (envs[host]) {
|
|
203
|
+
await setDefaultHub(envs[host]?.AIGNE_HUB_API_URL || "");
|
|
204
|
+
console.log(chalk.green(`✓ Hub ${envs[host]?.AIGNE_HUB_API_URL} connected successfully.`));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
if (isTest) {
|
|
209
|
+
writeFile(AIGNE_ENV_FILE, stringify({
|
|
210
|
+
"hub.aigne.io": {
|
|
211
|
+
AIGNE_HUB_API_KEY: "test-key",
|
|
212
|
+
AIGNE_HUB_API_URL: "https://hub.aigne.io/ai-kit",
|
|
213
|
+
},
|
|
214
|
+
default: {
|
|
215
|
+
AIGNE_HUB_API_URL: "https://hub.aigne.io/ai-kit",
|
|
216
|
+
},
|
|
217
|
+
}));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
await connectToAIGNEHub(url);
|
|
221
|
+
console.log(chalk.green(`✓ Hub ${url} connected successfully.`));
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error(chalk.red("✗ Failed to connect:"), error.message);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function setDefaultHub(url) {
|
|
228
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
229
|
+
const host = new URL(url).host;
|
|
230
|
+
if (!envs[host]) {
|
|
231
|
+
console.error(chalk.red("✗ Hub not found"));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
await writeFile(AIGNE_ENV_FILE, stringify({ ...envs, default: { AIGNE_HUB_API_URL: envs[host]?.AIGNE_HUB_API_URL } }));
|
|
235
|
+
console.log(chalk.green(`✓ Switched active hub to ${url}`));
|
|
236
|
+
}
|
|
237
|
+
async function deleteHub(url) {
|
|
238
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
239
|
+
const host = new URL(url).host;
|
|
240
|
+
delete envs[host];
|
|
241
|
+
if (envs.default?.AIGNE_HUB_API_URL && new URL(envs.default?.AIGNE_HUB_API_URL).host === host) {
|
|
242
|
+
delete envs.default;
|
|
243
|
+
}
|
|
244
|
+
await writeFile(AIGNE_ENV_FILE, stringify(envs));
|
|
245
|
+
console.log(chalk.green(`✓ Hub ${url} removed`));
|
|
246
|
+
}
|
|
247
|
+
async function printHubDetails(url) {
|
|
248
|
+
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
249
|
+
const host = new URL(url).host;
|
|
250
|
+
const userInfo = await getUserInfo({
|
|
251
|
+
baseUrl: envs[host]?.AIGNE_HUB_API_URL || "",
|
|
252
|
+
apiKey: envs[host]?.AIGNE_HUB_API_KEY || "",
|
|
253
|
+
}).catch(() => null);
|
|
254
|
+
printHubStatus({
|
|
255
|
+
hub: url,
|
|
256
|
+
status: userInfo ? "Connected" : "Not connected",
|
|
257
|
+
user: {
|
|
258
|
+
name: userInfo?.user.fullName || "",
|
|
259
|
+
did: userInfo?.user.did || "",
|
|
260
|
+
email: userInfo?.user.email || "",
|
|
261
|
+
},
|
|
262
|
+
credits: {
|
|
263
|
+
used: formatNumber(userInfo?.creditBalance?.balance || "0"),
|
|
264
|
+
total: formatNumber(userInfo?.creditBalance?.total || "0"),
|
|
265
|
+
},
|
|
266
|
+
links: {
|
|
267
|
+
payment: userInfo?.paymentLink || "",
|
|
268
|
+
profile: userInfo?.profileLink || "",
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -44,6 +44,7 @@ export function createRunCommand({ aigneFilePath, } = {}) {
|
|
|
44
44
|
if (options.logLevel)
|
|
45
45
|
logger.level = options.logLevel;
|
|
46
46
|
const { cacheDir, dir } = prepareDirs(path, options);
|
|
47
|
+
const originalLog = {};
|
|
47
48
|
const { aigne, agent } = await new Listr([
|
|
48
49
|
{
|
|
49
50
|
title: "Prepare environment",
|
|
@@ -67,24 +68,37 @@ export function createRunCommand({ aigneFilePath, } = {}) {
|
|
|
67
68
|
task: async (ctx, task) => {
|
|
68
69
|
// Load env files in the aigne directory
|
|
69
70
|
config({ path: dir, silent: true });
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
71
|
+
ctx.logs = [];
|
|
72
|
+
for (const method of ["debug", "log", "info", "warn", "error"]) {
|
|
73
|
+
originalLog[method] = console[method];
|
|
74
|
+
console[method] = (...args) => {
|
|
75
|
+
ctx.logs.push(...args);
|
|
76
|
+
task.output = args.join(" ");
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const aigne = await loadAIGNE({
|
|
80
|
+
path: dir,
|
|
81
|
+
options: {
|
|
82
|
+
...options,
|
|
83
|
+
model: options.model || process.env.MODEL,
|
|
84
|
+
aigneHubUrl: options?.aigneHubUrl,
|
|
85
|
+
},
|
|
86
|
+
actionOptions: {
|
|
87
|
+
inquirerPromptFn: (prompt) => {
|
|
88
|
+
if (prompt.type === "input") {
|
|
89
|
+
return task
|
|
90
|
+
.prompt(ListrInquirerPromptAdapter)
|
|
91
|
+
.run(inputInquirer, prompt)
|
|
92
|
+
.then((res) => ({ [prompt.name]: res }));
|
|
93
|
+
}
|
|
77
94
|
return task
|
|
78
95
|
.prompt(ListrInquirerPromptAdapter)
|
|
79
|
-
.run(
|
|
96
|
+
.run(selectInquirer, prompt)
|
|
80
97
|
.then((res) => ({ [prompt.name]: res }));
|
|
81
|
-
}
|
|
82
|
-
return task
|
|
83
|
-
.prompt(ListrInquirerPromptAdapter)
|
|
84
|
-
.run(selectInquirer, prompt)
|
|
85
|
-
.then((res) => ({ [prompt.name]: res }));
|
|
98
|
+
},
|
|
86
99
|
},
|
|
87
100
|
});
|
|
101
|
+
Object.assign(console, originalLog);
|
|
88
102
|
ctx.aigne = aigne;
|
|
89
103
|
},
|
|
90
104
|
},
|
|
@@ -118,7 +132,12 @@ ${aigne.agents.map((agent) => ` - ${agent.name}`).join("\n")}
|
|
|
118
132
|
showErrorMessage: false,
|
|
119
133
|
timer: PRESET_TIMER,
|
|
120
134
|
},
|
|
121
|
-
})
|
|
135
|
+
})
|
|
136
|
+
.run()
|
|
137
|
+
.then((ctx) => {
|
|
138
|
+
ctx.logs.forEach((log) => console.log(log));
|
|
139
|
+
return ctx;
|
|
140
|
+
});
|
|
122
141
|
assert(aigne);
|
|
123
142
|
assert(agent);
|
|
124
143
|
const input = await parseAgentInputByCommander(agent, options);
|
|
@@ -51,7 +51,10 @@ export function createServeMCPCommand({ aigneFilePath, } = {}) {
|
|
|
51
51
|
}
|
|
52
52
|
export async function serveMCPServerFromDir(options) {
|
|
53
53
|
const port = options.port || DEFAULT_PORT();
|
|
54
|
-
const aigne = await loadAIGNE(
|
|
54
|
+
const aigne = await loadAIGNE({
|
|
55
|
+
path: options.dir,
|
|
56
|
+
options: { aigneHubUrl: options.aigneHubUrl },
|
|
57
|
+
});
|
|
55
58
|
await serveMCPServer({
|
|
56
59
|
aigne,
|
|
57
60
|
host: options.host,
|
package/dist/commands/test.js
CHANGED
|
@@ -22,7 +22,10 @@ export function createTestCommand({ aigneFilePath, } = {}) {
|
|
|
22
22
|
handler: async (options) => {
|
|
23
23
|
const path = aigneFilePath || options.path;
|
|
24
24
|
const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
|
|
25
|
-
const aigne = await loadAIGNE(
|
|
25
|
+
const aigne = await loadAIGNE({
|
|
26
|
+
path: absolutePath,
|
|
27
|
+
options: { aigneHubUrl: options?.aigneHubUrl },
|
|
28
|
+
});
|
|
26
29
|
assert(aigne.rootDir);
|
|
27
30
|
spawnSync("node", ["--test"], { cwd: aigne.rootDir, stdio: "inherit" });
|
|
28
31
|
},
|
|
@@ -25,7 +25,7 @@ export declare class TerminalTracer {
|
|
|
25
25
|
usage?: boolean;
|
|
26
26
|
time?: boolean;
|
|
27
27
|
input: Message;
|
|
28
|
-
}): string
|
|
28
|
+
}): Promise<string>;
|
|
29
29
|
private marked;
|
|
30
30
|
get outputKey(): string;
|
|
31
31
|
formatRequest(agent: Agent, _context: Context, m?: Message, { running }?: {
|
package/dist/tracer/terminal.js
CHANGED
|
@@ -10,6 +10,7 @@ import * as prompts from "@inquirer/prompts";
|
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { Marked } from "marked";
|
|
12
12
|
import { AIGNEListr, AIGNEListrRenderer } from "../utils/listr.js";
|
|
13
|
+
import { highlightUrl } from "../utils/string-utils.js";
|
|
13
14
|
import { parseDuration } from "../utils/time.js";
|
|
14
15
|
export class TerminalTracer {
|
|
15
16
|
context;
|
|
@@ -28,6 +29,24 @@ export class TerminalTracer {
|
|
|
28
29
|
: undefined,
|
|
29
30
|
formatResult: (result, options) => [this.formatResult(agent, context, result, options)].filter(Boolean),
|
|
30
31
|
}, [], { concurrent: true });
|
|
32
|
+
const proxiedPrompts = new Proxy({}, {
|
|
33
|
+
get: (_target, prop) => {
|
|
34
|
+
// biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
|
|
35
|
+
const method = prompts[prop];
|
|
36
|
+
if (typeof method !== "function")
|
|
37
|
+
return undefined;
|
|
38
|
+
return async (config) => {
|
|
39
|
+
const renderer = listr["renderer"] instanceof AIGNEListrRenderer ? listr["renderer"] : undefined;
|
|
40
|
+
await renderer?.pause();
|
|
41
|
+
try {
|
|
42
|
+
return await method({ ...config });
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
await renderer?.resume();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
31
50
|
const onStart = async ({ context, agent, ...event }) => {
|
|
32
51
|
if (agent instanceof UserAgent)
|
|
33
52
|
return;
|
|
@@ -40,7 +59,7 @@ export class TerminalTracer {
|
|
|
40
59
|
};
|
|
41
60
|
this.tasks[contextId] = task;
|
|
42
61
|
const listrTask = {
|
|
43
|
-
title: this.formatTaskTitle(agent, { ...event }),
|
|
62
|
+
title: await this.formatTaskTitle(agent, { ...event }),
|
|
44
63
|
task: (ctx, taskWrapper) => {
|
|
45
64
|
const subtask = taskWrapper.newListr([{ task: () => task.promise }]);
|
|
46
65
|
task.listr.resolve({ subtask, taskWrapper, ctx });
|
|
@@ -61,28 +80,7 @@ export class TerminalTracer {
|
|
|
61
80
|
else {
|
|
62
81
|
listr.add(listrTask);
|
|
63
82
|
}
|
|
64
|
-
return {
|
|
65
|
-
options: {
|
|
66
|
-
prompts: new Proxy({}, {
|
|
67
|
-
get: (_target, prop) => {
|
|
68
|
-
// biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
|
|
69
|
-
const method = prompts[prop];
|
|
70
|
-
if (typeof method !== "function")
|
|
71
|
-
return undefined;
|
|
72
|
-
return async (config) => {
|
|
73
|
-
const renderer = listr["renderer"] instanceof AIGNEListrRenderer ? listr["renderer"] : undefined;
|
|
74
|
-
await renderer?.pause();
|
|
75
|
-
try {
|
|
76
|
-
return await method({ ...config });
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
await renderer?.resume();
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
}),
|
|
84
|
-
},
|
|
85
|
-
};
|
|
83
|
+
return { options: { prompts: proxiedPrompts } };
|
|
86
84
|
};
|
|
87
85
|
const onSuccess = async ({ context, agent, output, ...event }) => {
|
|
88
86
|
const contextId = context.id;
|
|
@@ -99,20 +97,49 @@ export class TerminalTracer {
|
|
|
99
97
|
if (model)
|
|
100
98
|
task.extraTitleMetadata.model = model;
|
|
101
99
|
}
|
|
102
|
-
taskWrapper.title = this.formatTaskTitle(agent, {
|
|
100
|
+
taskWrapper.title = await this.formatTaskTitle(agent, {
|
|
101
|
+
...event,
|
|
102
|
+
task,
|
|
103
|
+
usage: true,
|
|
104
|
+
time: true,
|
|
105
|
+
});
|
|
103
106
|
if (!parentContextId || !this.tasks[parentContextId]) {
|
|
104
107
|
Object.assign(ctx, output);
|
|
105
108
|
}
|
|
106
109
|
task.resolve();
|
|
107
110
|
};
|
|
108
111
|
const onError = async ({ context, agent, error, ...event }) => {
|
|
112
|
+
if ("type" in error && error.type === "NOT_ENOUGH") {
|
|
113
|
+
const retry = await proxiedPrompts.select({
|
|
114
|
+
message: highlightUrl(error.message),
|
|
115
|
+
choices: [
|
|
116
|
+
{
|
|
117
|
+
name: "I have bought some credits, try again",
|
|
118
|
+
value: "retry",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "Exit",
|
|
122
|
+
value: "exit",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
console.log("");
|
|
127
|
+
if (retry === "retry") {
|
|
128
|
+
return { retry: true };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
109
131
|
const contextId = context.id;
|
|
110
132
|
const task = this.tasks[contextId];
|
|
111
133
|
if (!task)
|
|
112
134
|
return;
|
|
113
135
|
task.endTime = Date.now();
|
|
114
136
|
const { taskWrapper } = await task.listr.promise;
|
|
115
|
-
taskWrapper.title = this.formatTaskTitle(agent, {
|
|
137
|
+
taskWrapper.title = await this.formatTaskTitle(agent, {
|
|
138
|
+
...event,
|
|
139
|
+
task,
|
|
140
|
+
usage: true,
|
|
141
|
+
time: true,
|
|
142
|
+
});
|
|
116
143
|
task.reject(error);
|
|
117
144
|
};
|
|
118
145
|
const result = await listr.run(() => context.invoke(agent, input, {
|
|
@@ -145,10 +172,10 @@ export class TerminalTracer {
|
|
|
145
172
|
const duration = endTime - startTime;
|
|
146
173
|
return chalk.grey(`[${parseDuration(duration)}]`);
|
|
147
174
|
}
|
|
148
|
-
formatTaskTitle(agent, { task, usage, time, input }) {
|
|
175
|
+
async formatTaskTitle(agent, { task, usage, time, input }) {
|
|
149
176
|
let title = agent.name;
|
|
150
177
|
if (agent.taskTitle) {
|
|
151
|
-
title += ` ${chalk.cyan(agent.renderTaskTitle(input))}`;
|
|
178
|
+
title += ` ${chalk.cyan(await agent.renderTaskTitle(input))}`;
|
|
152
179
|
}
|
|
153
180
|
if (usage && task?.usage)
|
|
154
181
|
title += ` ${this.formatTokenUsage(task.usage, task.extraTitleMetadata)}`;
|
|
@@ -1,72 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AIGNE } from "@aigne/core";
|
|
3
|
-
import
|
|
4
|
-
import { type RunAIGNECommandOptions } from "./run-with-aigne.js";
|
|
5
|
-
export declare const decrypt: (m: string, s: string, i: string) => string;
|
|
6
|
-
export declare const encrypt: (m: string, s: string, i: string) => string;
|
|
7
|
-
export declare const encodeEncryptionKey: (key: string) => string;
|
|
8
|
-
export declare const decodeEncryptionKey: (str: string) => Uint8Array<ArrayBuffer>;
|
|
9
|
-
export declare const AIGNE_ENV_FILE: string;
|
|
1
|
+
import type { LoadCredentialOptions, Model } from "@aigne/aigne-hub";
|
|
2
|
+
import { AIGNE, type ChatModelOptions } from "@aigne/core";
|
|
3
|
+
import type { RunAIGNECommandOptions } from "./run-with-aigne.js";
|
|
10
4
|
export interface RunOptions extends RunAIGNECommandOptions {
|
|
11
5
|
path: string;
|
|
12
6
|
entryAgent?: string;
|
|
13
7
|
cacheDir?: string;
|
|
14
8
|
aigneHubUrl?: string;
|
|
15
9
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
fetchTimeout: number;
|
|
25
|
-
}) => Promise<any>;
|
|
26
|
-
declare function baseWrapSpinner(_: string, waiting: () => Promise<FetchResult>): Promise<FetchResult>;
|
|
27
|
-
interface CreateConnectOptions {
|
|
28
|
-
connectUrl: string;
|
|
29
|
-
openPage?: (url: string) => void;
|
|
30
|
-
fetchInterval?: number;
|
|
31
|
-
retry?: number;
|
|
32
|
-
source?: string;
|
|
33
|
-
connectAction?: string;
|
|
34
|
-
appName?: string;
|
|
35
|
-
appLogo?: string;
|
|
36
|
-
wrapSpinner?: typeof baseWrapSpinner;
|
|
37
|
-
prettyUrl?: (url: string) => string;
|
|
38
|
-
closeOnSuccess?: boolean;
|
|
39
|
-
intervalFetchConfig?: (options: {
|
|
40
|
-
sessionId: string;
|
|
41
|
-
fetchInterval: number;
|
|
42
|
-
fetchTimeout: number;
|
|
43
|
-
}) => Promise<FetchResult>;
|
|
44
|
-
}
|
|
45
|
-
export declare function createConnect({ connectUrl, openPage, fetchInterval, retry, source, connectAction, wrapSpinner, closeOnSuccess, intervalFetchConfig, appName, appLogo, }: CreateConnectOptions): Promise<FetchResult>;
|
|
46
|
-
export declare const DEFAULT_URL = "https://hub.aigne.io/";
|
|
47
|
-
export declare const formatModelName: (models: ReturnType<typeof availableModels>, model: string, inquirerPrompt: typeof inquirer.prompt) => Promise<string>;
|
|
48
|
-
export declare function connectToAIGNEHub(url: string): Promise<{
|
|
49
|
-
apiKey: string;
|
|
50
|
-
url: string;
|
|
51
|
-
} | {
|
|
52
|
-
apiKey: undefined;
|
|
53
|
-
url: undefined;
|
|
54
|
-
}>;
|
|
55
|
-
export declare const checkConnectionStatus: (host: string) => Promise<{
|
|
56
|
-
apiKey: any;
|
|
57
|
-
url: string;
|
|
58
|
-
}>;
|
|
59
|
-
export declare function loadAIGNE(path: string, options?: Pick<RunOptions, "model" | "aigneHubUrl">, actionOptions?: {
|
|
60
|
-
inquirerPromptFn?: (prompt: {
|
|
61
|
-
type: string;
|
|
62
|
-
name: string;
|
|
63
|
-
message: string;
|
|
64
|
-
choices: {
|
|
65
|
-
name: string;
|
|
66
|
-
value: any;
|
|
67
|
-
}[];
|
|
68
|
-
default: any;
|
|
69
|
-
}) => Promise<any>;
|
|
70
|
-
runTest?: boolean;
|
|
10
|
+
export declare function loadAIGNE({ path, options, modelOptions, actionOptions, }: {
|
|
11
|
+
path?: string;
|
|
12
|
+
options?: Model & Pick<RunOptions, "model" | "aigneHubUrl">;
|
|
13
|
+
modelOptions?: ChatModelOptions;
|
|
14
|
+
actionOptions?: {
|
|
15
|
+
inquirerPromptFn?: LoadCredentialOptions["inquirerPromptFn"];
|
|
16
|
+
runTest?: boolean;
|
|
17
|
+
};
|
|
71
18
|
}): Promise<AIGNE<import("@aigne/core").UserContext>>;
|
|
72
|
-
export {};
|
package/dist/utils/load-aigne.js
CHANGED
|
@@ -1,288 +1,54 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { readFile
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import {
|
|
5
|
+
import { AIGNE_ENV_FILE, checkConnectionStatus, AIGNE_HUB_URL as DEFAULT_AIGNE_HUB_URL, formatModelName, loadModel, parseModelOption, } from "@aigne/aigne-hub";
|
|
6
6
|
import { AIGNE } from "@aigne/core";
|
|
7
7
|
import { loadAIGNEFile } from "@aigne/core/loader/index.js";
|
|
8
|
-
import { logger } from "@aigne/core/utils/logger.js";
|
|
9
|
-
import { AesCrypter } from "@ocap/mcrypto/lib/crypter/aes-legacy.js";
|
|
10
|
-
import crypto from "crypto";
|
|
11
8
|
import inquirer from "inquirer";
|
|
12
|
-
import open from "open";
|
|
13
|
-
import pWaitFor from "p-wait-for";
|
|
14
|
-
import { joinURL, withQuery } from "ufo";
|
|
15
9
|
import { parse, stringify } from "yaml";
|
|
16
10
|
import { availableMemories } from "../constants.js";
|
|
17
|
-
|
|
18
|
-
const aes = new AesCrypter();
|
|
19
|
-
export const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
20
|
-
export const encrypt = (m, s, i) => aes.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
21
|
-
const escapeFn = (str) => str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
22
|
-
const unescapeFn = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
23
|
-
export const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
|
|
24
|
-
export const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescapeFn(str), "base64"));
|
|
25
|
-
const TEST_ENV = process.env.CI || process.env.NODE_ENV === "test";
|
|
26
|
-
export const AIGNE_ENV_FILE = TEST_ENV
|
|
27
|
-
? join(homedir(), ".aigne", "test-aigne-hub-connected.yaml")
|
|
28
|
-
: join(homedir(), ".aigne", "aigne-hub-connected.yaml");
|
|
29
|
-
const request = async (config) => {
|
|
30
|
-
const headers = {};
|
|
31
|
-
if (config.requestCount !== undefined) {
|
|
32
|
-
headers["X-Request-Count"] = config.requestCount.toString();
|
|
33
|
-
}
|
|
34
|
-
const response = await fetch(config.url, { method: config.method || "GET", headers });
|
|
35
|
-
if (!response.ok)
|
|
36
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
37
|
-
const data = await response.json();
|
|
38
|
-
return { data };
|
|
39
|
-
};
|
|
40
|
-
const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
|
|
41
|
-
const ACCESS_KEY_PREFIX = "/api/access-key";
|
|
42
|
-
const ACCESS_KEY_SESSION_API = `${ACCESS_KEY_PREFIX}/session`;
|
|
43
|
-
export const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
|
|
44
|
-
const sessionURL = withQuery(joinURL(connectUrl, ACCESS_KEY_SESSION_API), { sid: sessionId });
|
|
45
|
-
let requestCount = 0;
|
|
46
|
-
const condition = async () => {
|
|
47
|
-
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
48
|
-
requestCount++;
|
|
49
|
-
return Boolean(session.accessKeyId && session.accessKeySecret);
|
|
50
|
-
};
|
|
51
|
-
await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
52
|
-
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
53
|
-
await request({ url: sessionURL, method: "DELETE" });
|
|
54
|
-
return {
|
|
55
|
-
...session,
|
|
56
|
-
accessKeyId: session.accessKeyId,
|
|
57
|
-
accessKeySecret: decrypt(session.accessKeySecret, session.accessKeyId, session.challenge),
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
function baseWrapSpinner(_, waiting) {
|
|
61
|
-
return Promise.resolve(waiting());
|
|
62
|
-
}
|
|
63
|
-
export async function createConnect({ connectUrl, openPage, fetchInterval = 3 * 1000, retry = 1500, source = "Blocklet CLI", connectAction = "connect-cli", wrapSpinner = baseWrapSpinner, closeOnSuccess, intervalFetchConfig, appName = "AIGNE CLI", appLogo = "https://www.aigne.io/favicon.ico?imageFilter=resize&w=32", }) {
|
|
64
|
-
try {
|
|
65
|
-
const startSessionURL = joinURL(connectUrl, ACCESS_KEY_SESSION_API);
|
|
66
|
-
const { data: session } = await request({ url: startSessionURL, method: "POST" });
|
|
67
|
-
const token = session.id;
|
|
68
|
-
const pageUrl = withQuery(joinURL(connectUrl, connectAction), {
|
|
69
|
-
__token__: encodeEncryptionKey(token),
|
|
70
|
-
source,
|
|
71
|
-
closeOnSuccess,
|
|
72
|
-
cli: true,
|
|
73
|
-
appName: ` ${appName}`,
|
|
74
|
-
appLogo,
|
|
75
|
-
});
|
|
76
|
-
openPage?.(pageUrl);
|
|
77
|
-
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
78
|
-
const checkAuthorizeStatus = intervalFetchConfig ?? fetchConfigs;
|
|
79
|
-
const authorizeStatus = await checkAuthorizeStatus({
|
|
80
|
-
connectUrl,
|
|
81
|
-
sessionId: token,
|
|
82
|
-
fetchTimeout: retry * fetchInterval,
|
|
83
|
-
fetchInterval: retry,
|
|
84
|
-
});
|
|
85
|
-
return authorizeStatus;
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
catch (e) {
|
|
89
|
-
console.error(e);
|
|
90
|
-
throw e;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
const AGENT_HUB_PROVIDER = "aignehub";
|
|
94
|
-
const DEFAULT_AIGNE_HUB_MODEL = "openai/gpt-4o";
|
|
95
|
-
const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = `${AGENT_HUB_PROVIDER}:${DEFAULT_AIGNE_HUB_MODEL}`;
|
|
96
|
-
export const DEFAULT_URL = "https://hub.aigne.io/";
|
|
97
|
-
export const formatModelName = async (models, model, inquirerPrompt) => {
|
|
98
|
-
if (!model)
|
|
99
|
-
return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
|
|
100
|
-
const { provider, name } = parseModelOption(model);
|
|
101
|
-
if (!provider) {
|
|
102
|
-
return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
|
|
103
|
-
}
|
|
104
|
-
const providerName = provider.replace(/-/g, "");
|
|
105
|
-
if (providerName.includes(AGENT_HUB_PROVIDER)) {
|
|
106
|
-
return model;
|
|
107
|
-
}
|
|
108
|
-
const m = findModel(models, providerName);
|
|
109
|
-
if (!m)
|
|
110
|
-
throw new Error(`Unsupported model: ${provider} ${name}`);
|
|
111
|
-
const apiKeyEnvName = Array.isArray(m.apiKeyEnvName) ? m.apiKeyEnvName : [m.apiKeyEnvName];
|
|
112
|
-
if (apiKeyEnvName.some((name) => name && process.env[name])) {
|
|
113
|
-
return model;
|
|
114
|
-
}
|
|
115
|
-
if (TEST_ENV) {
|
|
116
|
-
return `${AGENT_HUB_PROVIDER}:${provider}/${name}`;
|
|
117
|
-
}
|
|
118
|
-
const result = await inquirerPrompt({
|
|
119
|
-
type: "list",
|
|
120
|
-
name: "useAigneHub",
|
|
121
|
-
message: `Seems no API Key configured for ${provider}/${name}, select your preferred way to continue:`,
|
|
122
|
-
choices: [
|
|
123
|
-
{
|
|
124
|
-
name: `Connect to AIGNE Hub to use ${name} (Recommended since free credits available)`,
|
|
125
|
-
value: true,
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: `Exit and bring my owner API Key by set ${apiKeyEnvName.join(", ")}`,
|
|
129
|
-
value: false,
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
default: true,
|
|
133
|
-
});
|
|
134
|
-
if (!result.useAigneHub)
|
|
135
|
-
return model;
|
|
136
|
-
return `${AGENT_HUB_PROVIDER}:${provider}/${name}`;
|
|
137
|
-
};
|
|
138
|
-
export async function connectToAIGNEHub(url) {
|
|
139
|
-
const { origin, host } = new URL(url);
|
|
140
|
-
const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
141
|
-
const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
|
|
142
|
-
const blockletInfo = await fetch(joinURL(origin, BLOCKLET_JSON_PATH));
|
|
143
|
-
const blocklet = await blockletInfo.json();
|
|
144
|
-
const aigneHubMount = (blocklet?.componentMountPoints || []).find((m) => m.did === "z8ia3xzq2tMq8CRHfaXj1BTYJyYnEcHbqP8cJ");
|
|
145
|
-
try {
|
|
146
|
-
const result = await createConnect({
|
|
147
|
-
connectUrl: connectUrl,
|
|
148
|
-
connectAction: "gen-simple-access-key",
|
|
149
|
-
source: `@aigne/cli connect to AIGNE hub`,
|
|
150
|
-
closeOnSuccess: true,
|
|
151
|
-
openPage: (pageUrl) => open(pageUrl),
|
|
152
|
-
});
|
|
153
|
-
const accessKeyOptions = {
|
|
154
|
-
apiKey: result.accessKeySecret,
|
|
155
|
-
url: joinURL(origin, aigneHubMount?.mountPoint || ""),
|
|
156
|
-
};
|
|
157
|
-
// After redirection, write the AIGNE Hub access token
|
|
158
|
-
const aigneDir = join(homedir(), ".aigne");
|
|
159
|
-
if (!existsSync(aigneDir)) {
|
|
160
|
-
mkdirSync(aigneDir, { recursive: true });
|
|
161
|
-
}
|
|
162
|
-
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
163
|
-
await writeFile(AIGNE_ENV_FILE, stringify({
|
|
164
|
-
...envs,
|
|
165
|
-
[host]: {
|
|
166
|
-
AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
|
|
167
|
-
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
168
|
-
},
|
|
169
|
-
default: {
|
|
170
|
-
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
171
|
-
},
|
|
172
|
-
})).catch((err) => {
|
|
173
|
-
logger.error("Failed to write AIGNE Hub access token to .aigne/aigne-hub-connected.yaml", err.message);
|
|
174
|
-
throw err;
|
|
175
|
-
});
|
|
176
|
-
return accessKeyOptions;
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
logger.error("Failed to connect to AIGNE Hub", error.message);
|
|
180
|
-
return { apiKey: undefined, url: undefined };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
export const checkConnectionStatus = async (host) => {
|
|
184
|
-
// aigne-hub access token
|
|
185
|
-
if (!existsSync(AIGNE_ENV_FILE)) {
|
|
186
|
-
throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
|
|
187
|
-
}
|
|
188
|
-
const data = await readFile(AIGNE_ENV_FILE, "utf8");
|
|
189
|
-
if (!data.includes("AIGNE_HUB_API_KEY")) {
|
|
190
|
-
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
191
|
-
}
|
|
192
|
-
const envs = parse(data);
|
|
193
|
-
if (!envs[host]) {
|
|
194
|
-
throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
|
|
195
|
-
}
|
|
196
|
-
const env = envs[host];
|
|
197
|
-
if (!env.AIGNE_HUB_API_KEY) {
|
|
198
|
-
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
199
|
-
}
|
|
200
|
-
return {
|
|
201
|
-
apiKey: env.AIGNE_HUB_API_KEY,
|
|
202
|
-
url: joinURL(env.AIGNE_HUB_API_URL),
|
|
203
|
-
};
|
|
204
|
-
};
|
|
11
|
+
const isTest = process.env.CI || process.env.NODE_ENV === "test";
|
|
205
12
|
const mockInquirerPrompt = (() => Promise.resolve({ useAigneHub: true }));
|
|
206
|
-
|
|
13
|
+
async function prepareAIGNEConfig(options, inquirerPromptFn) {
|
|
207
14
|
const aigneDir = join(homedir(), ".aigne");
|
|
208
15
|
if (!existsSync(aigneDir)) {
|
|
209
16
|
mkdirSync(aigneDir, { recursive: true });
|
|
210
17
|
}
|
|
211
18
|
const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
212
|
-
const inquirerPrompt = (
|
|
213
|
-
|
|
214
|
-
const models = availableModels();
|
|
19
|
+
const inquirerPrompt = (inquirerPromptFn ?? inquirer.prompt);
|
|
20
|
+
// get aigne hub url
|
|
215
21
|
const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
|
|
216
|
-
const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL ||
|
|
217
|
-
const connectUrl = joinURL(new URL(AIGNE_HUB_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
22
|
+
const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL || DEFAULT_AIGNE_HUB_URL;
|
|
218
23
|
const { host } = new URL(AIGNE_HUB_URL);
|
|
219
|
-
const { aigne } = await loadAIGNEFile(path).catch(() => ({ aigne: null }));
|
|
220
24
|
const result = await checkConnectionStatus(host).catch(() => null);
|
|
221
25
|
const alreadyConnected = Boolean(result?.apiKey);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
26
|
+
return { AIGNE_HUB_URL, inquirerPrompt: alreadyConnected ? mockInquirerPrompt : inquirerPrompt };
|
|
27
|
+
}
|
|
28
|
+
export async function loadAIGNE({ path, options, modelOptions, actionOptions, }) {
|
|
29
|
+
const { AIGNE_HUB_URL, inquirerPrompt } = await prepareAIGNEConfig(options, actionOptions?.inquirerPromptFn);
|
|
30
|
+
const { temperature, topP, presencePenalty, frequencyPenalty } = options || {};
|
|
31
|
+
let modelName = options?.model || "";
|
|
32
|
+
if (path) {
|
|
33
|
+
const { aigne } = await loadAIGNEFile(path).catch(() => ({ aigne: null }));
|
|
34
|
+
const modelFromPath = `${aigne?.model?.provider ?? ""}:${aigne?.model?.name ?? ""}`;
|
|
35
|
+
modelName = modelName || modelFromPath;
|
|
36
|
+
}
|
|
37
|
+
// format model name
|
|
38
|
+
const formattedModelName = isTest ? modelName : await formatModelName(modelName, inquirerPrompt);
|
|
39
|
+
if (isTest && path && !actionOptions?.runTest) {
|
|
40
|
+
const model = await loadModel(parseModelOption(formattedModelName));
|
|
226
41
|
return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
|
|
227
42
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
type: "list",
|
|
238
|
-
name: "subscribe",
|
|
239
|
-
message: "No LLM API Keys or AIGNE Hub connections found. How would you like to proceed?",
|
|
240
|
-
choices: [
|
|
241
|
-
{
|
|
242
|
-
name: "Connect to the Arcblock official AIGNE Hub (recommended, free credits for new users)",
|
|
243
|
-
value: "official",
|
|
244
|
-
},
|
|
245
|
-
connectUrl.includes(DEFAULT_URL)
|
|
246
|
-
? {
|
|
247
|
-
name: "Connect to your own AIGNE Hub instance (self-hosted)",
|
|
248
|
-
value: "custom",
|
|
249
|
-
}
|
|
250
|
-
: null,
|
|
251
|
-
{
|
|
252
|
-
name: "Exit and configure my own LLM API Keys",
|
|
253
|
-
value: "manual",
|
|
254
|
-
},
|
|
255
|
-
].filter(Boolean),
|
|
256
|
-
default: "official",
|
|
257
|
-
});
|
|
258
|
-
if (subscribe === "custom") {
|
|
259
|
-
const { customUrl } = await inquirerPrompt({
|
|
260
|
-
type: "input",
|
|
261
|
-
name: "customUrl",
|
|
262
|
-
message: "Enter the URL of your AIGNE Hub:",
|
|
263
|
-
validate(input) {
|
|
264
|
-
try {
|
|
265
|
-
const url = new URL(input);
|
|
266
|
-
return url.protocol.startsWith("http")
|
|
267
|
-
? true
|
|
268
|
-
: "Must be a valid URL with http or https";
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
return "Invalid URL";
|
|
272
|
-
}
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
aigneHubUrl = customUrl;
|
|
276
|
-
}
|
|
277
|
-
else if (subscribe === "manual") {
|
|
278
|
-
console.log("You chose to configure your own LLM API Keys. Exiting...");
|
|
279
|
-
process.exit(0);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
credential = await connectToAIGNEHub(aigneHubUrl);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
43
|
+
const model = await loadModel({
|
|
44
|
+
...parseModelOption(formattedModelName),
|
|
45
|
+
temperature,
|
|
46
|
+
topP,
|
|
47
|
+
presencePenalty,
|
|
48
|
+
frequencyPenalty,
|
|
49
|
+
}, modelOptions, { aigneHubUrl: AIGNE_HUB_URL, inquirerPromptFn: actionOptions?.inquirerPromptFn });
|
|
50
|
+
if (path) {
|
|
51
|
+
return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
|
|
285
52
|
}
|
|
286
|
-
|
|
287
|
-
return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
|
|
53
|
+
return new AIGNE({ model });
|
|
288
54
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Agent, AIGNE, type ChatModelOptions, type Message } from "@aigne/core";
|
|
1
|
+
import { type Agent, type AIGNE, type ChatModelOptions, type Message } from "@aigne/core";
|
|
2
2
|
import { LogLevel } from "@aigne/core/utils/logger.js";
|
|
3
3
|
import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
|
|
4
4
|
import type { Argv } from "yargs";
|
|
@@ -41,16 +41,14 @@ export declare const createRunAIGNECommand: (yargs: Argv) => Argv<{
|
|
|
41
41
|
force: boolean;
|
|
42
42
|
} & {
|
|
43
43
|
"log-level": LogLevel;
|
|
44
|
+
} & {
|
|
45
|
+
"aigne-hub-url": string | undefined;
|
|
44
46
|
}>;
|
|
45
47
|
export declare function parseAgentInputByCommander(agent: Agent, options?: RunAIGNECommandOptions & {
|
|
46
48
|
inputKey?: string;
|
|
47
49
|
argv?: string[];
|
|
48
50
|
defaultInput?: string | Message;
|
|
49
51
|
}): Promise<Message>;
|
|
50
|
-
export declare const parseModelOption: (model?: string) => {
|
|
51
|
-
provider: string | undefined;
|
|
52
|
-
name: string | undefined;
|
|
53
|
-
};
|
|
54
52
|
export declare function runWithAIGNE(agentCreator: ((aigne: AIGNE) => PromiseOrValue<Agent>) | Agent, { argv, chatLoopOptions, modelOptions, outputKey, }?: {
|
|
55
53
|
argv?: typeof process.argv;
|
|
56
54
|
chatLoopOptions?: ChatLoopOptions;
|
|
@@ -4,8 +4,8 @@ import { dirname, isAbsolute, join } from "node:path";
|
|
|
4
4
|
import { isatty } from "node:tty";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { exists } from "@aigne/agent-library/utils/fs.js";
|
|
7
|
-
import { availableModels,
|
|
8
|
-
import { AIAgent,
|
|
7
|
+
import { availableModels, parseModelOption } from "@aigne/aigne-hub";
|
|
8
|
+
import { AIAgent, DEFAULT_OUTPUT_KEY, readAllString, UserAgent, } from "@aigne/core";
|
|
9
9
|
import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
|
|
10
10
|
import { flat, isEmpty, tryOrThrow } from "@aigne/core/utils/type-utils.js";
|
|
11
11
|
import chalk from "chalk";
|
|
@@ -14,6 +14,7 @@ import yargs from "yargs";
|
|
|
14
14
|
import { hideBin } from "yargs/helpers";
|
|
15
15
|
import { ZodError, ZodObject, z } from "zod";
|
|
16
16
|
import { TerminalTracer } from "../tracer/terminal.js";
|
|
17
|
+
import { loadAIGNE } from "./load-aigne.js";
|
|
17
18
|
import { DEFAULT_CHAT_INPUT_KEY, runChatLoopInTerminal, } from "./run-chat-loop.js";
|
|
18
19
|
export const createRunAIGNECommand = (yargs) => yargs
|
|
19
20
|
.option("chat", {
|
|
@@ -81,6 +82,10 @@ export const createRunAIGNECommand = (yargs) => yargs
|
|
|
81
82
|
type: "string",
|
|
82
83
|
default: getLevelFromEnv(logger.options.ns) || LogLevel.INFO,
|
|
83
84
|
coerce: customZodError("--log-level", (s) => z.nativeEnum(LogLevel).parse(s)),
|
|
85
|
+
})
|
|
86
|
+
.option("aigne-hub-url", {
|
|
87
|
+
describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models. ",
|
|
88
|
+
type: "string",
|
|
84
89
|
});
|
|
85
90
|
export async function parseAgentInputByCommander(agent, options = {}) {
|
|
86
91
|
const inputSchemaShape = flat(agent instanceof AIAgent ? agent.inputKey : undefined, agent.inputSchema instanceof ZodObject ? Object.keys(agent.inputSchema.shape) : []);
|
|
@@ -123,24 +128,22 @@ export async function parseAgentInputByCommander(agent, options = {}) {
|
|
|
123
128
|
}
|
|
124
129
|
return input;
|
|
125
130
|
}
|
|
126
|
-
export const parseModelOption = (model) => {
|
|
127
|
-
const { provider, name } = (model || process.env.MODEL)?.match(/(?<provider>[^:]*)(:(?<name>(\S+)))?/)?.groups ?? {};
|
|
128
|
-
return { provider, name };
|
|
129
|
-
};
|
|
130
131
|
export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoopOptions, modelOptions, outputKey, } = {}) {
|
|
131
132
|
await yargs()
|
|
132
133
|
.command("$0", "Run an agent with AIGNE", (yargs) => createRunAIGNECommand(yargs), async (options) => {
|
|
133
134
|
if (options.logLevel) {
|
|
134
135
|
logger.level = options.logLevel;
|
|
135
136
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
const aigne = await loadAIGNE({
|
|
138
|
+
options: {
|
|
139
|
+
...parseModelOption(options.model),
|
|
140
|
+
temperature: options.temperature,
|
|
141
|
+
topP: options.topP,
|
|
142
|
+
presencePenalty: options.presencePenalty,
|
|
143
|
+
frequencyPenalty: options.frequencyPenalty,
|
|
144
|
+
},
|
|
145
|
+
modelOptions,
|
|
146
|
+
});
|
|
144
147
|
try {
|
|
145
148
|
const agent = typeof agentCreator === "function" ? await agentCreator(aigne) : agentCreator;
|
|
146
149
|
const input = await parseAgentInputByCommander(agent, {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function highlightUrl(str: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Your command center for agent development",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -51,10 +51,9 @@
|
|
|
51
51
|
"@inquirer/type": "^3.0.8",
|
|
52
52
|
"@listr2/prompt-adapter-inquirer": "^3.0.1",
|
|
53
53
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
54
|
-
"@ocap/mcrypto": "^1.21.0",
|
|
55
54
|
"@smithy/node-http-handler": "^4.1.0",
|
|
56
55
|
"chalk": "^5.4.1",
|
|
57
|
-
"
|
|
56
|
+
"cli-table3": "^0.6.5",
|
|
58
57
|
"detect-port": "^2.1.0",
|
|
59
58
|
"dotenv-flow": "^4.1.0",
|
|
60
59
|
"express": "^5.1.0",
|
|
@@ -64,22 +63,21 @@
|
|
|
64
63
|
"inquirer": "^12.7.0",
|
|
65
64
|
"log-update": "^6.1.0",
|
|
66
65
|
"marked": "^16.0.0",
|
|
67
|
-
"
|
|
66
|
+
"nunjucks": "^3.2.4",
|
|
68
67
|
"openai": "^5.8.3",
|
|
69
|
-
"p-wait-for": "^5.0.2",
|
|
70
68
|
"prettier": "^3.6.2",
|
|
71
69
|
"tar": "^7.4.3",
|
|
72
70
|
"wrap-ansi": "^9.0.0",
|
|
73
71
|
"yaml": "^2.8.0",
|
|
74
72
|
"yargs": "^18.0.0",
|
|
75
73
|
"zod": "^3.25.67",
|
|
76
|
-
"@aigne/agent-library": "^1.21.
|
|
77
|
-
"@aigne/agentic-memory": "^1.0.
|
|
78
|
-
"@aigne/aigne-hub": "^0.
|
|
79
|
-
"@aigne/
|
|
74
|
+
"@aigne/agent-library": "^1.21.19",
|
|
75
|
+
"@aigne/agentic-memory": "^1.0.19",
|
|
76
|
+
"@aigne/aigne-hub": "^0.5.0",
|
|
77
|
+
"@aigne/default-memory": "^1.1.1",
|
|
80
78
|
"@aigne/observability-api": "^0.9.0",
|
|
81
|
-
"@aigne/
|
|
82
|
-
"@aigne/openai": "^0.
|
|
79
|
+
"@aigne/core": "^1.49.1",
|
|
80
|
+
"@aigne/openai": "^0.11.1"
|
|
83
81
|
},
|
|
84
82
|
"devDependencies": {
|
|
85
83
|
"@types/archiver": "^6.0.3",
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { CommandModule } from "yargs";
|
|
2
|
-
interface ConnectOptions {
|
|
3
|
-
url?: string;
|
|
4
|
-
}
|
|
5
|
-
interface StatusInfo {
|
|
6
|
-
host: string;
|
|
7
|
-
apiUrl: string;
|
|
8
|
-
apiKey: string;
|
|
9
|
-
}
|
|
10
|
-
export declare function getConnectionStatus(): Promise<StatusInfo[]>;
|
|
11
|
-
export declare function displayStatus(statusList: StatusInfo[]): Promise<void>;
|
|
12
|
-
export declare function createConnectCommand(): CommandModule<unknown, ConnectOptions>;
|
|
13
|
-
export {};
|
package/dist/commands/connect.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import { parse } from "yaml";
|
|
5
|
-
import { getUserInfo } from "../utils/aigne-hub-user.js";
|
|
6
|
-
import { AIGNE_ENV_FILE, connectToAIGNEHub, DEFAULT_URL } from "../utils/load-aigne.js";
|
|
7
|
-
const formatNumber = (balance) => {
|
|
8
|
-
const balanceNum = String(balance).split(".")[0];
|
|
9
|
-
return chalk.yellow((balanceNum || "").replace(/\B(?=(\d{3})+(?!\d))/g, ","));
|
|
10
|
-
};
|
|
11
|
-
export async function getConnectionStatus() {
|
|
12
|
-
if (!existsSync(AIGNE_ENV_FILE)) {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
try {
|
|
16
|
-
const data = await readFile(AIGNE_ENV_FILE, "utf8");
|
|
17
|
-
const envs = parse(data);
|
|
18
|
-
const statusList = [];
|
|
19
|
-
for (const [host, config] of Object.entries(envs)) {
|
|
20
|
-
statusList.push({
|
|
21
|
-
host,
|
|
22
|
-
apiUrl: config.AIGNE_HUB_API_URL,
|
|
23
|
-
apiKey: config.AIGNE_HUB_API_KEY,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
return statusList;
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export async function displayStatus(statusList) {
|
|
33
|
-
if (statusList.length === 0) {
|
|
34
|
-
console.log(chalk.yellow("No AIGNE Hub connections found."));
|
|
35
|
-
console.log("Use 'aigne connect <url>' to connect to a hub.");
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
console.log(chalk.cyan("AIGNE Hub Connection Status:\n"));
|
|
39
|
-
const defaultStatus = statusList.find((status) => status.host === "default")?.apiUrl || DEFAULT_URL;
|
|
40
|
-
for (const status of statusList.filter((status) => status.host !== "default")) {
|
|
41
|
-
const userInfo = await getUserInfo({ baseUrl: status.apiUrl, apiKey: status.apiKey }).catch((e) => {
|
|
42
|
-
console.error(e);
|
|
43
|
-
return null;
|
|
44
|
-
});
|
|
45
|
-
const isConnected = new URL(status.apiUrl).origin === new URL(defaultStatus).origin;
|
|
46
|
-
const statusIcon = isConnected ? chalk.green("✓") : chalk.red("✗");
|
|
47
|
-
const statusText = isConnected ? "Connected" : "Disconnected";
|
|
48
|
-
console.log(`${statusIcon} ${chalk.bold(status.host)}`);
|
|
49
|
-
console.log(` Status: ${statusText}`);
|
|
50
|
-
if (userInfo) {
|
|
51
|
-
console.log(` User: ${userInfo?.user.fullName}`);
|
|
52
|
-
console.log(` User DID: ${userInfo?.user.did}`);
|
|
53
|
-
if (userInfo?.user.email) {
|
|
54
|
-
console.log(` Email: ${userInfo?.user.email}`);
|
|
55
|
-
}
|
|
56
|
-
if (userInfo?.creditBalance) {
|
|
57
|
-
const balance = formatNumber(userInfo?.creditBalance?.balance);
|
|
58
|
-
const total = formatNumber(userInfo?.creditBalance?.total);
|
|
59
|
-
console.log(` Plan: ${balance} / ${total}`);
|
|
60
|
-
}
|
|
61
|
-
console.log(` Billing URL: ${userInfo?.paymentLink ? chalk.green(userInfo.paymentLink) : chalk.red("N/A")}`);
|
|
62
|
-
}
|
|
63
|
-
console.log("");
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
export function createConnectCommand() {
|
|
67
|
-
return {
|
|
68
|
-
command: "connect [url]",
|
|
69
|
-
describe: "Manage AIGNE Hub connections",
|
|
70
|
-
builder: (yargs) => {
|
|
71
|
-
return yargs
|
|
72
|
-
.positional("url", {
|
|
73
|
-
describe: "URL to the AIGNE Hub server",
|
|
74
|
-
type: "string",
|
|
75
|
-
default: "https://hub.aigne.io/",
|
|
76
|
-
})
|
|
77
|
-
.command({
|
|
78
|
-
command: "status",
|
|
79
|
-
describe: "Show current connection status",
|
|
80
|
-
handler: async () => {
|
|
81
|
-
const statusList = await getConnectionStatus();
|
|
82
|
-
await displayStatus(statusList);
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
},
|
|
86
|
-
handler: async (argv) => {
|
|
87
|
-
const url = argv.url || "https://hub.aigne.io/";
|
|
88
|
-
console.log(chalk.cyan(`Connecting to AIGNE Hub: ${url}`));
|
|
89
|
-
try {
|
|
90
|
-
await connectToAIGNEHub(url);
|
|
91
|
-
console.log(chalk.green("✓ Successfully connected to AIGNE Hub"));
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error(chalk.red("✗ Failed to connect to AIGNE Hub:"), error.message);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
}
|