@aigne/cli 1.37.0 → 1.38.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 CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.38.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.37.1...cli-v1.38.0) (2025-08-20)
4
+
5
+
6
+ ### Features
7
+
8
+ * add ImageModel/ImageAgent support ([#383](https://github.com/AIGNE-io/aigne-framework/issues/383)) ([96a2093](https://github.com/AIGNE-io/aigne-framework/commit/96a209368d91d98f47db6de1e404640368a86fa8))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @aigne/agent-library bumped to 1.21.24
16
+ * @aigne/agentic-memory bumped to 1.0.24
17
+ * @aigne/aigne-hub bumped to 0.6.6
18
+ * @aigne/core bumped to 1.53.0
19
+ * @aigne/default-memory bumped to 1.1.6
20
+ * @aigne/openai bumped to 0.12.0
21
+
22
+ ## [1.37.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.37.0...cli-v1.37.1) (2025-08-20)
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @aigne/agent-library bumped to 1.21.23
30
+ * @aigne/agentic-memory bumped to 1.0.23
31
+ * @aigne/aigne-hub bumped to 0.6.5
32
+ * @aigne/core bumped to 1.52.0
33
+ * @aigne/default-memory bumped to 1.1.5
34
+ * @aigne/observability-api bumped to 0.9.1
35
+ * @aigne/openai bumped to 0.11.5
36
+
3
37
  ## [1.37.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.36.4...cli-v1.37.0) (2025-08-18)
4
38
 
5
39
 
@@ -3,7 +3,6 @@ import { spawn } from "node:child_process";
3
3
  import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { join } from "node:path";
6
- import { loadModel } from "@aigne/aigne-hub";
7
6
  import { AIGNE } from "@aigne/core";
8
7
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
9
8
  import { joinURL } from "ufo";
@@ -95,7 +94,7 @@ const agentCommandModule = ({ dir, agent, }) => {
95
94
  export async function invokeCLIAgentFromDir(options) {
96
95
  const aigne = await loadAIGNE({
97
96
  path: options.dir,
98
- options: { model: options.input.model },
97
+ modelOptions: { model: options.input.model },
99
98
  });
100
99
  try {
101
100
  const agent = aigne.cli.agents[options.agent];
@@ -113,7 +112,7 @@ export async function loadApplication({ name, dir, forceUpgrade = false, }) {
113
112
  const check = forceUpgrade ? undefined : await isInstallationAvailable(dir);
114
113
  if (check?.available) {
115
114
  return {
116
- aigne: await AIGNE.load(dir, { loadModel }),
115
+ aigne: await AIGNE.load(dir),
117
116
  dir,
118
117
  version: check.version,
119
118
  isCache: true,
@@ -156,7 +155,7 @@ export async function loadApplication({ name, dir, forceUpgrade = false, }) {
156
155
  },
157
156
  }).run();
158
157
  return {
159
- aigne: await AIGNE.load(dir, { loadModel }),
158
+ aigne: await AIGNE.load(dir),
160
159
  dir,
161
160
  version: result.version,
162
161
  };
@@ -1,10 +1,12 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
- import { AIGNE_ENV_FILE, AIGNE_HUB_URL, connectToAIGNEHub, isTest } from "@aigne/aigne-hub";
3
+ import { AIGNE_HUB_URL } from "@aigne/aigne-hub";
4
4
  import chalk from "chalk";
5
5
  import Table from "cli-table3";
6
6
  import inquirer from "inquirer";
7
7
  import { parse, stringify } from "yaml";
8
+ import { AIGNE_ENV_FILE, isTest } from "../utils/aigne-hub/constants.js";
9
+ import { connectToAIGNEHub } from "../utils/aigne-hub/credential.js";
8
10
  import { getUserInfo } from "../utils/aigne-hub-user.js";
9
11
  import { getUrlOrigin } from "../utils/get-url-origin.js";
10
12
  const formatNumber = (balance) => {
@@ -78,12 +78,10 @@ export function createRunCommand({ aigneFilePath, } = {}) {
78
78
  }
79
79
  const aigne = await loadAIGNE({
80
80
  path: dir,
81
- options: {
81
+ modelOptions: {
82
82
  ...options,
83
83
  model: options.model || process.env.MODEL,
84
84
  aigneHubUrl: options?.aigneHubUrl,
85
- },
86
- actionOptions: {
87
85
  inquirerPromptFn: (prompt) => {
88
86
  if (prompt.type === "input") {
89
87
  return task
@@ -53,7 +53,7 @@ export async function serveMCPServerFromDir(options) {
53
53
  const port = options.port || DEFAULT_PORT();
54
54
  const aigne = await loadAIGNE({
55
55
  path: options.dir,
56
- options: { aigneHubUrl: options.aigneHubUrl },
56
+ modelOptions: { aigneHubUrl: options.aigneHubUrl },
57
57
  });
58
58
  await serveMCPServer({
59
59
  aigne,
@@ -24,7 +24,7 @@ export function createTestCommand({ aigneFilePath, } = {}) {
24
24
  const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
25
25
  const aigne = await loadAIGNE({
26
26
  path: absolutePath,
27
- options: { aigneHubUrl: options?.aigneHubUrl },
27
+ modelOptions: { aigneHubUrl: options?.aigneHubUrl },
28
28
  });
29
29
  assert(aigne.rootDir);
30
30
  spawnSync("node", ["--test"], { cwd: aigne.rootDir, stdio: "inherit" });
@@ -0,0 +1,6 @@
1
+ export declare const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
2
+ export declare const ACCESS_KEY_SESSION_API = "/api/access-key/session";
3
+ export declare const AIGNE_HUB_PROVIDER = "aignehub";
4
+ export declare const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = "aignehub:openai/gpt-5-mini";
5
+ export declare const isTest: string | boolean;
6
+ export declare const AIGNE_ENV_FILE: string;
@@ -0,0 +1,12 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ const ACCESS_KEY_PREFIX = "/api/access-key";
4
+ export const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
5
+ export const ACCESS_KEY_SESSION_API = `${ACCESS_KEY_PREFIX}/session`;
6
+ const DEFAULT_AIGNE_HUB_MODEL = "openai/gpt-5-mini";
7
+ export const AIGNE_HUB_PROVIDER = "aignehub";
8
+ export const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = `${AIGNE_HUB_PROVIDER}:${DEFAULT_AIGNE_HUB_MODEL}`;
9
+ export const isTest = process.env.CI || process.env.NODE_ENV === "test";
10
+ const TEST_AIGNE_ENV_FILE = join(homedir(), ".aigne", "test-aigne-hub-connected.yaml");
11
+ const PROD_AIGNE_ENV_FILE = join(homedir(), ".aigne", "aigne-hub-connected.yaml");
12
+ export const AIGNE_ENV_FILE = isTest ? TEST_AIGNE_ENV_FILE : PROD_AIGNE_ENV_FILE;
@@ -0,0 +1,23 @@
1
+ import type { CreateConnectOptions, FetchResult, LoadCredentialOptions } from "./type.js";
2
+ export declare const fetchConfigs: ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }: {
3
+ connectUrl: string;
4
+ sessionId: string;
5
+ fetchInterval: number;
6
+ fetchTimeout: number;
7
+ }) => Promise<any>;
8
+ export declare function createConnect({ connectUrl, openPage, fetchInterval, retry, source, connectAction, wrapSpinner, closeOnSuccess, intervalFetchConfig, appName, appLogo, }: CreateConnectOptions): Promise<FetchResult>;
9
+ export declare function connectToAIGNEHub(url: string): Promise<{
10
+ apiKey: string;
11
+ url: string;
12
+ } | {
13
+ apiKey: undefined;
14
+ url: undefined;
15
+ }>;
16
+ export declare const checkConnectionStatus: (host: string) => Promise<{
17
+ apiKey: any;
18
+ url: any;
19
+ }>;
20
+ export declare function loadAIGNEHubCredential(options?: LoadCredentialOptions): Promise<{
21
+ apiKey?: string;
22
+ url?: string;
23
+ } | undefined>;
@@ -0,0 +1,206 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { AIGNE_HUB_BLOCKLET_DID, AIGNE_HUB_URL, getAIGNEHubMountPoint } from "@aigne/aigne-hub";
6
+ import { logger } from "@aigne/core/utils/logger.js";
7
+ import inquirer from "inquirer";
8
+ import open from "open";
9
+ import pWaitFor from "p-wait-for";
10
+ import { joinURL, withQuery } from "ufo";
11
+ import { parse, stringify } from "yaml";
12
+ import { ACCESS_KEY_SESSION_API, AIGNE_ENV_FILE, isTest, WELLKNOWN_SERVICE_PATH_PREFIX, } from "./constants.js";
13
+ import { decrypt, encodeEncryptionKey } from "./crypto.js";
14
+ const request = async (config) => {
15
+ const headers = {};
16
+ if (config.requestCount !== undefined) {
17
+ headers["X-Request-Count"] = config.requestCount.toString();
18
+ }
19
+ const response = await fetch(config.url, { method: config.method || "GET", headers });
20
+ if (!response.ok)
21
+ throw new Error(`HTTP error! status: ${response.status}`);
22
+ const data = await response.json();
23
+ return { data };
24
+ };
25
+ export const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
26
+ const sessionURL = withQuery(joinURL(connectUrl, ACCESS_KEY_SESSION_API), { sid: sessionId });
27
+ let requestCount = 0;
28
+ const condition = async () => {
29
+ const { data: session } = await request({ url: sessionURL, requestCount });
30
+ requestCount++;
31
+ return Boolean(session.accessKeyId && session.accessKeySecret);
32
+ };
33
+ await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
34
+ const { data: session } = await request({ url: sessionURL, requestCount });
35
+ await request({ url: sessionURL, method: "DELETE" });
36
+ return {
37
+ ...session,
38
+ accessKeyId: session.accessKeyId,
39
+ accessKeySecret: decrypt(session.accessKeySecret, session.accessKeyId, session.challenge),
40
+ };
41
+ };
42
+ function baseWrapSpinner(_, waiting) {
43
+ return Promise.resolve(waiting());
44
+ }
45
+ 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", }) {
46
+ const startSessionURL = joinURL(connectUrl, ACCESS_KEY_SESSION_API);
47
+ const { data: session } = await request({ url: startSessionURL, method: "POST" });
48
+ const token = session.id;
49
+ const pageUrl = withQuery(joinURL(connectUrl, connectAction), {
50
+ __token__: encodeEncryptionKey(token),
51
+ source,
52
+ closeOnSuccess,
53
+ cli: true,
54
+ appName: ` ${appName}`,
55
+ appLogo,
56
+ });
57
+ openPage?.(pageUrl);
58
+ return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
59
+ const checkAuthorizeStatus = intervalFetchConfig ?? fetchConfigs;
60
+ const authorizeStatus = await checkAuthorizeStatus({
61
+ connectUrl,
62
+ sessionId: token,
63
+ fetchTimeout: retry * fetchInterval,
64
+ fetchInterval: retry,
65
+ });
66
+ return authorizeStatus;
67
+ });
68
+ }
69
+ export async function connectToAIGNEHub(url) {
70
+ const { origin, host } = new URL(url);
71
+ const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
72
+ const apiUrl = await getAIGNEHubMountPoint(url, AIGNE_HUB_BLOCKLET_DID);
73
+ try {
74
+ const openFn = isTest ? () => { } : open;
75
+ const result = await createConnect({
76
+ connectUrl: connectUrl,
77
+ connectAction: "gen-simple-access-key",
78
+ source: `@aigne/cli connect to AIGNE hub`,
79
+ closeOnSuccess: true,
80
+ openPage: (pageUrl) => openFn(pageUrl),
81
+ });
82
+ const accessKeyOptions = {
83
+ apiKey: result.accessKeySecret,
84
+ url: apiUrl,
85
+ };
86
+ // After redirection, write the AIGNE Hub access token
87
+ const aigneDir = join(homedir(), ".aigne");
88
+ if (!existsSync(aigneDir)) {
89
+ mkdirSync(aigneDir, { recursive: true });
90
+ }
91
+ const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
92
+ await writeFile(AIGNE_ENV_FILE, stringify({
93
+ ...envs,
94
+ [host]: {
95
+ AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
96
+ AIGNE_HUB_API_URL: accessKeyOptions.url,
97
+ },
98
+ default: {
99
+ AIGNE_HUB_API_URL: accessKeyOptions.url,
100
+ },
101
+ }));
102
+ return accessKeyOptions;
103
+ }
104
+ catch (error) {
105
+ logger.error("Failed to connect to AIGNE Hub", error.message);
106
+ return { apiKey: undefined, url: undefined };
107
+ }
108
+ }
109
+ export const checkConnectionStatus = async (host) => {
110
+ // aigne-hub access token
111
+ if (!existsSync(AIGNE_ENV_FILE)) {
112
+ throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
113
+ }
114
+ const data = await readFile(AIGNE_ENV_FILE, "utf8");
115
+ if (!data.includes("AIGNE_HUB_API_KEY")) {
116
+ throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
117
+ }
118
+ const envs = parse(data);
119
+ if (!envs[host]) {
120
+ throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
121
+ }
122
+ const env = envs[host];
123
+ if (!env.AIGNE_HUB_API_KEY) {
124
+ throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
125
+ }
126
+ return {
127
+ apiKey: env.AIGNE_HUB_API_KEY,
128
+ url: env.AIGNE_HUB_API_URL,
129
+ };
130
+ };
131
+ export async function loadAIGNEHubCredential(options) {
132
+ const isBlocklet = process.env.BLOCKLET_AIGNE_API_URL && process.env.BLOCKLET_AIGNE_API_PROVIDER;
133
+ if (isBlocklet)
134
+ return undefined;
135
+ const aigneDir = join(homedir(), ".aigne");
136
+ if (!existsSync(aigneDir)) {
137
+ mkdirSync(aigneDir, { recursive: true });
138
+ }
139
+ const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
140
+ const inquirerPrompt = (options?.inquirerPromptFn ?? inquirer.prompt);
141
+ const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
142
+ const url = configUrl || envs?.default?.AIGNE_HUB_API_URL || AIGNE_HUB_URL;
143
+ const connectUrl = joinURL(new URL(url).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
144
+ const { host } = new URL(url);
145
+ let credential = {};
146
+ try {
147
+ credential = await checkConnectionStatus(host);
148
+ }
149
+ catch (error) {
150
+ if (error instanceof Error && error.message.includes("login first")) {
151
+ let aigneHubUrl = connectUrl;
152
+ if (!configUrl) {
153
+ const { subscribe } = await inquirerPrompt({
154
+ type: "list",
155
+ name: "subscribe",
156
+ message: "No LLM API Keys or AIGNE Hub connections found. How would you like to proceed?",
157
+ choices: [
158
+ {
159
+ name: "Connect to the Arcblock official AIGNE Hub (recommended, free credits for new users)",
160
+ value: "official",
161
+ },
162
+ connectUrl.includes(AIGNE_HUB_URL)
163
+ ? {
164
+ name: "Connect to your own AIGNE Hub instance (self-hosted)",
165
+ value: "custom",
166
+ }
167
+ : null,
168
+ {
169
+ name: "Exit and configure my own LLM API Keys",
170
+ value: "manual",
171
+ },
172
+ ].filter(Boolean),
173
+ default: "official",
174
+ });
175
+ if (subscribe === "custom") {
176
+ const { customUrl } = await inquirerPrompt({
177
+ type: "input",
178
+ name: "customUrl",
179
+ message: "Enter the URL of your AIGNE Hub:",
180
+ validate(input) {
181
+ try {
182
+ const url = new URL(input);
183
+ return url.protocol.startsWith("http")
184
+ ? true
185
+ : "Must be a valid URL with http or https";
186
+ }
187
+ catch {
188
+ return "Invalid URL";
189
+ }
190
+ },
191
+ });
192
+ aigneHubUrl = customUrl;
193
+ }
194
+ else if (subscribe === "manual") {
195
+ console.log("You chose to configure your own LLM API Keys. Exiting...");
196
+ process.exit(0);
197
+ }
198
+ }
199
+ credential = await connectToAIGNEHub(aigneHubUrl);
200
+ }
201
+ else {
202
+ throw error;
203
+ }
204
+ }
205
+ return credential;
206
+ }
@@ -0,0 +1,4 @@
1
+ export declare const decrypt: (m: string, s: string, i: string) => string;
2
+ export declare const encrypt: (m: string, s: string, i: string) => string;
3
+ export declare const encodeEncryptionKey: (key: string) => string;
4
+ export declare const decodeEncryptionKey: (str: string) => Uint8Array<ArrayBuffer>;
@@ -0,0 +1,9 @@
1
+ import crypto from "node:crypto";
2
+ import { AesCrypter } from "@ocap/mcrypto/lib/crypter/aes-legacy.js";
3
+ const aes = new AesCrypter();
4
+ export const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
5
+ export const encrypt = (m, s, i) => aes.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
6
+ const escapeFn = (str) => str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
7
+ const unescapeFn = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
8
+ export const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
9
+ export const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescapeFn(str), "base64"));
@@ -0,0 +1,12 @@
1
+ import type { ChatModel, ChatModelOptions } from "@aigne/core";
2
+ import type { LoadCredentialOptions } from "./type.js";
3
+ export declare function maskApiKey(apiKey?: string): string | undefined;
4
+ export declare const parseModelOption: (model: string) => {
5
+ provider: string | undefined;
6
+ model: string | undefined;
7
+ };
8
+ export declare const formatModelName: (model: string, inquirerPrompt: NonNullable<LoadCredentialOptions["inquirerPromptFn"]>) => Promise<{
9
+ provider: string;
10
+ model?: string;
11
+ }>;
12
+ export declare function loadChatModel(options?: ChatModelOptions & LoadCredentialOptions): Promise<ChatModel>;
@@ -0,0 +1,75 @@
1
+ import { AIGNE_HUB_DEFAULT_MODEL, findModel } from "@aigne/aigne-hub";
2
+ import { flat } from "@aigne/core/utils/type-utils.js";
3
+ import chalk from "chalk";
4
+ import inquirer from "inquirer";
5
+ import { AIGNE_HUB_PROVIDER } from "./constants.js";
6
+ import { loadAIGNEHubCredential } from "./credential.js";
7
+ export function maskApiKey(apiKey) {
8
+ if (!apiKey || apiKey.length <= 8)
9
+ return apiKey;
10
+ const start = apiKey.slice(0, 4);
11
+ const end = apiKey.slice(-4);
12
+ return `${start}${"*".repeat(8)}${end}`;
13
+ }
14
+ export const parseModelOption = (model) => {
15
+ const { provider, name } = model.match(/(?<provider>[^:]*)(:(?<name>.*))?/)?.groups ?? {};
16
+ return { provider: provider?.replace(/-/g, ""), model: name };
17
+ };
18
+ export const formatModelName = async (model, inquirerPrompt) => {
19
+ let { provider, model: name } = parseModelOption(model);
20
+ provider ||= AIGNE_HUB_PROVIDER;
21
+ const { match, all } = findModel(provider);
22
+ if (!match)
23
+ throw new Error(`Unsupported model: ${provider}/${name}, available providers: ${all.map((m) => m.name).join(", ")}`);
24
+ if (provider.includes(AIGNE_HUB_PROVIDER)) {
25
+ return { provider, model: name || AIGNE_HUB_DEFAULT_MODEL };
26
+ }
27
+ const requireEnvs = flat(match.apiKeyEnvName);
28
+ if (requireEnvs.some((name) => name && process.env[name])) {
29
+ return { provider, model: name };
30
+ }
31
+ const result = await inquirerPrompt({
32
+ type: "list",
33
+ name: "useAigneHub",
34
+ message: `Seems no API Key configured for ${provider}/${name}, select your preferred way to continue:`,
35
+ choices: [
36
+ {
37
+ name: `Connect to AIGNE Hub to use ${name} (Recommended since free credits available)`,
38
+ value: true,
39
+ },
40
+ {
41
+ name: `Exit and bring my owner API Key by set ${requireEnvs.join(", ")}`,
42
+ value: false,
43
+ },
44
+ ],
45
+ default: true,
46
+ });
47
+ if (!result.useAigneHub) {
48
+ console.log(chalk.yellow(`You can use command "export ${requireEnvs[0]}=xxx" to set API Key in your shell. Or you can set environment variables in .env file.`));
49
+ process.exit(0);
50
+ }
51
+ return { provider: AIGNE_HUB_PROVIDER, model: `${provider}/${name}` };
52
+ };
53
+ export async function loadChatModel(options) {
54
+ const { provider, model } = await formatModelName(options?.model || process.env.MODEL || "", options?.inquirerPromptFn ??
55
+ inquirer.prompt);
56
+ const params = {
57
+ model,
58
+ temperature: options?.temperature,
59
+ topP: options?.topP,
60
+ frequencyPenalty: options?.frequencyPenalty,
61
+ presencePenalty: options?.presencePenalty,
62
+ };
63
+ const { match, all } = findModel(provider);
64
+ if (!match) {
65
+ throw new Error(`Unsupported model provider ${provider}, available providers: ${all.map((m) => m.name).join(", ")}`);
66
+ }
67
+ const credential = provider.toLowerCase().includes(AIGNE_HUB_PROVIDER)
68
+ ? await loadAIGNEHubCredential(options)
69
+ : undefined;
70
+ return match.create({
71
+ ...credential,
72
+ model: params.model,
73
+ modelOptions: { ...params },
74
+ });
75
+ }
@@ -0,0 +1,38 @@
1
+ type InquirerPromptFn = (prompt: {
2
+ type: string;
3
+ name: string;
4
+ message: string;
5
+ choices: {
6
+ name: string;
7
+ value: any;
8
+ }[];
9
+ default: any;
10
+ }) => Promise<any>;
11
+ export type LoadCredentialOptions = {
12
+ aigneHubUrl?: string;
13
+ inquirerPromptFn?: InquirerPromptFn;
14
+ };
15
+ export type FetchResult = {
16
+ accessKeyId: string;
17
+ accessKeySecret: string;
18
+ };
19
+ export type BaseWrapSpinner = (_: string, waiting: () => Promise<FetchResult>) => Promise<FetchResult>;
20
+ export interface CreateConnectOptions {
21
+ connectUrl: string;
22
+ openPage?: (url: string) => void;
23
+ fetchInterval?: number;
24
+ retry?: number;
25
+ source?: string;
26
+ connectAction?: string;
27
+ appName?: string;
28
+ appLogo?: string;
29
+ wrapSpinner?: BaseWrapSpinner;
30
+ prettyUrl?: (url: string) => string;
31
+ closeOnSuccess?: boolean;
32
+ intervalFetchConfig?: (options: {
33
+ sessionId: string;
34
+ fetchInterval: number;
35
+ fetchTimeout: number;
36
+ }) => Promise<FetchResult>;
37
+ }
38
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,5 @@
1
- import type { LoadCredentialOptions, Model } from "@aigne/aigne-hub";
2
1
  import { AIGNE, type ChatModelOptions } from "@aigne/core";
2
+ import type { LoadCredentialOptions } from "./aigne-hub/type.js";
3
3
  import type { RunAIGNECommandOptions } from "./run-with-aigne.js";
4
4
  export interface RunOptions extends RunAIGNECommandOptions {
5
5
  path: string;
@@ -7,12 +7,7 @@ export interface RunOptions extends RunAIGNECommandOptions {
7
7
  cacheDir?: string;
8
8
  aigneHubUrl?: string;
9
9
  }
10
- export declare function loadAIGNE({ path, options, modelOptions, actionOptions, }: {
10
+ export declare function loadAIGNE({ path, modelOptions, }: {
11
11
  path?: string;
12
- options?: Model & Pick<RunOptions, "model" | "aigneHubUrl">;
13
- modelOptions?: ChatModelOptions;
14
- actionOptions?: {
15
- inquirerPromptFn?: LoadCredentialOptions["inquirerPromptFn"];
16
- runTest?: boolean;
17
- };
12
+ modelOptions?: ChatModelOptions & LoadCredentialOptions;
18
13
  }): Promise<AIGNE<import("@aigne/core").UserContext>>;
@@ -1,24 +1,17 @@
1
- import { existsSync, mkdirSync } from "node:fs";
2
- import { readFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { join } from "node:path";
5
- import { AIGNE_ENV_FILE, checkConnectionStatus, AIGNE_HUB_URL as DEFAULT_AIGNE_HUB_URL, formatModelName, loadModel, maskApiKey, parseModelOption, } from "@aigne/aigne-hub";
6
1
  import { AIGNE } from "@aigne/core";
7
- import { loadAIGNEFile } from "@aigne/core/loader/index.js";
2
+ import { isNil, omitBy } from "@aigne/core/utils/type-utils.js";
3
+ import { OpenAIImageModel } from "@aigne/openai";
8
4
  import boxen from "boxen";
9
5
  import chalk from "chalk";
10
- import inquirer from "inquirer";
11
- import { parse, stringify } from "yaml";
12
6
  import { availableMemories } from "../constants.js";
7
+ import { loadChatModel, maskApiKey } from "./aigne-hub/model.js";
13
8
  import { getUrlOrigin } from "./get-url-origin.js";
14
- const isTest = process.env.CI || process.env.NODE_ENV === "test";
15
- const mockInquirerPrompt = (() => Promise.resolve({ useAigneHub: true }));
16
9
  let printed = false;
17
10
  async function printChatModelInfoBox(model) {
18
11
  if (printed)
19
12
  return;
20
13
  printed = true;
21
- const credential = await model.getCredential();
14
+ const credential = await model.credential;
22
15
  const lines = [`${chalk.cyan("Provider")}: ${chalk.green(model.name.replace("ChatModel", ""))}`];
23
16
  if (credential?.model) {
24
17
  lines.push(`${chalk.cyan("Model")}: ${chalk.green(credential?.model)}`);
@@ -32,49 +25,22 @@ async function printChatModelInfoBox(model) {
32
25
  console.log(boxen(lines.join("\n"), { padding: 1, borderStyle: "classic", borderColor: "cyan" }));
33
26
  console.log("");
34
27
  }
35
- async function prepareAIGNEConfig(options, inquirerPromptFn) {
36
- const aigneDir = join(homedir(), ".aigne");
37
- if (!existsSync(aigneDir)) {
38
- mkdirSync(aigneDir, { recursive: true });
39
- }
40
- const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
41
- const inquirerPrompt = (inquirerPromptFn ?? inquirer.prompt);
42
- // get aigne hub url
43
- const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
44
- const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL || DEFAULT_AIGNE_HUB_URL;
45
- const { host } = new URL(AIGNE_HUB_URL);
46
- const result = await checkConnectionStatus(host).catch(() => null);
47
- const alreadyConnected = Boolean(result?.apiKey);
48
- return { AIGNE_HUB_URL, inquirerPrompt: alreadyConnected ? mockInquirerPrompt : inquirerPrompt };
49
- }
50
- export async function loadAIGNE({ path, options, modelOptions, actionOptions, }) {
51
- const { AIGNE_HUB_URL, inquirerPrompt } = await prepareAIGNEConfig(options, actionOptions?.inquirerPromptFn);
52
- const { temperature, topP, presencePenalty, frequencyPenalty } = options || {};
53
- let modelName = options?.model || "";
28
+ export async function loadAIGNE({ path, modelOptions, }) {
29
+ let aigne;
54
30
  if (path) {
55
- const { aigne } = await loadAIGNEFile(path).catch(() => ({ aigne: null }));
56
- const modelFromPath = `${aigne?.model?.provider ?? ""}:${aigne?.model?.name ?? ""}`;
57
- modelName = modelName || modelFromPath;
31
+ aigne = await AIGNE.load(path, {
32
+ memories: availableMemories,
33
+ model: (options) => loadChatModel({ ...options, ...omitBy(modelOptions ?? {}, (v) => isNil(v)) }),
34
+ imageModel: () => new OpenAIImageModel(),
35
+ });
58
36
  }
59
- // format model name
60
- const formattedModelName = isTest ? modelName : await formatModelName(modelName, inquirerPrompt);
61
- if (isTest && path && !actionOptions?.runTest) {
62
- const model = await loadModel(parseModelOption(formattedModelName));
63
- return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
37
+ else {
38
+ const chatModel = await loadChatModel({ ...modelOptions });
39
+ aigne = new AIGNE({ model: chatModel });
64
40
  }
65
41
  console.log(`${chalk.grey("TIPS:")} run ${chalk.cyan("aigne observe")} to start the observability server.\n`);
66
- const model = await loadModel({
67
- ...parseModelOption(formattedModelName),
68
- temperature,
69
- topP,
70
- presencePenalty,
71
- frequencyPenalty,
72
- }, modelOptions, { aigneHubUrl: AIGNE_HUB_URL, inquirerPromptFn: actionOptions?.inquirerPromptFn });
73
- if (model) {
74
- await printChatModelInfoBox(model);
75
- }
76
- if (path) {
77
- return await AIGNE.load(path, { loadModel, memories: availableMemories, model });
42
+ if (aigne.model) {
43
+ await printChatModelInfoBox(aigne.model);
78
44
  }
79
- return new AIGNE({ model });
45
+ return aigne;
80
46
  }
@@ -2,10 +2,10 @@ import { mkdir, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, isAbsolute, join } from "node:path";
3
3
  import { isatty } from "node:tty";
4
4
  import { exists } from "@aigne/agent-library/utils/fs.js";
5
- import { availableModels, parseModelOption } from "@aigne/aigne-hub";
5
+ import { availableModels } from "@aigne/aigne-hub";
6
6
  import { DEFAULT_OUTPUT_KEY, UserAgent, } from "@aigne/core";
7
7
  import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
8
- import { isEmpty, tryOrThrow } from "@aigne/core/utils/type-utils.js";
8
+ import { isEmpty, isNil, omitBy, pick, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
9
9
  import chalk from "chalk";
10
10
  import yargs from "yargs";
11
11
  import { hideBin } from "yargs/helpers";
@@ -106,14 +106,10 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
106
106
  logger.level = options.logLevel;
107
107
  }
108
108
  const aigne = await loadAIGNE({
109
- options: {
110
- ...parseModelOption(options.model),
111
- temperature: options.temperature,
112
- topP: options.topP,
113
- presencePenalty: options.presencePenalty,
114
- frequencyPenalty: options.frequencyPenalty,
109
+ modelOptions: {
110
+ ...modelOptions,
111
+ ...omitBy(pick(options, "model", "temperature", "topP", "presencePenalty", "frequencyPenalty"), (v) => isNil(v)),
115
112
  },
116
- modelOptions,
117
113
  });
118
114
  try {
119
115
  const agent = typeof agentCreator === "function" ? await agentCreator(aigne) : agentCreator;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.37.0",
3
+ "version": "1.38.0",
4
4
  "description": "Your command center for agent development",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -51,6 +51,7 @@
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",
54
55
  "@smithy/node-http-handler": "^4.1.0",
55
56
  "boxen": "^8.0.1",
56
57
  "chalk": "^5.4.1",
@@ -65,20 +66,22 @@
65
66
  "log-update": "^6.1.0",
66
67
  "marked": "^16.0.0",
67
68
  "nunjucks": "^3.2.4",
69
+ "open": "^10.2.0",
68
70
  "openai": "^5.8.3",
71
+ "p-wait-for": "^5.0.2",
69
72
  "prettier": "^3.6.2",
70
73
  "tar": "^7.4.3",
71
74
  "wrap-ansi": "^9.0.0",
72
75
  "yaml": "^2.8.0",
73
76
  "yargs": "^18.0.0",
74
77
  "zod": "^3.25.67",
75
- "@aigne/agent-library": "^1.21.22",
76
- "@aigne/aigne-hub": "^0.6.4",
77
- "@aigne/agentic-memory": "^1.0.22",
78
- "@aigne/core": "^1.51.0",
79
- "@aigne/default-memory": "^1.1.4",
80
- "@aigne/observability-api": "^0.9.0",
81
- "@aigne/openai": "^0.11.4"
78
+ "@aigne/agent-library": "^1.21.24",
79
+ "@aigne/agentic-memory": "^1.0.24",
80
+ "@aigne/aigne-hub": "^0.6.6",
81
+ "@aigne/core": "^1.53.0",
82
+ "@aigne/observability-api": "^0.9.1",
83
+ "@aigne/default-memory": "^1.1.6",
84
+ "@aigne/openai": "^0.12.0"
82
85
  },
83
86
  "devDependencies": {
84
87
  "@types/archiver": "^6.0.3",