@aigne/cli 1.31.0 → 1.32.1

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.32.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.32.0...cli-v1.32.1) (2025-08-08)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @aigne/agent-library bumped to 1.21.15
11
+ * @aigne/agentic-memory bumped to 1.0.15
12
+ * @aigne/aigne-hub bumped to 0.4.6
13
+ * @aigne/core bumped to 1.46.1
14
+ * @aigne/default-memory bumped to 1.0.15
15
+ * @aigne/openai bumped to 0.10.15
16
+
17
+ ## [1.32.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.31.0...cli-v1.32.0) (2025-08-07)
18
+
19
+
20
+ ### Features
21
+
22
+ * **cli:** support config custom AIGNE Hub service URL ([#330](https://github.com/AIGNE-io/aigne-framework/issues/330)) ([21d30c8](https://github.com/AIGNE-io/aigne-framework/commit/21d30c8c75d9f27cb257d92434ba63e38e06f468))
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **cli:** properly handle boolean and number types in agent options ([#331](https://github.com/AIGNE-io/aigne-framework/issues/331)) ([c9f4209](https://github.com/AIGNE-io/aigne-framework/commit/c9f4209ec1b236bc54e8aaef0b960e10a380e375))
28
+
3
29
  ## [1.31.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.30.4...cli-v1.31.0) (2025-08-06)
4
30
 
5
31
 
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
  <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/packages/cli/logo.svg" media="(prefers-color-scheme: light)">
7
7
  <img src="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/packages/cli/logo.svg" alt="AIGNE Logo" width="400" />
8
8
  </picture>
9
+
10
+ <center>Your command center for agent development</center>
9
11
  </p>
10
12
 
11
13
  [![GitHub star chart](https://img.shields.io/github/stars/AIGNE-io/aigne-framework?style=flat-square)](https://star-history.com/#AIGNE-io/aigne-framework)
@@ -20,6 +22,12 @@ Command-line tool for [AIGNE Framework](https://github.com/AIGNE-io/aigne-framew
20
22
 
21
23
  `@aigne/cli` is the official command-line tool for [AIGNE Framework](https://github.com/AIGNE-io/aigne-framework), designed to simplify the development, testing, and deployment processes for AIGNE applications. It provides a series of useful commands to help developers quickly create projects, run agents, test code, and deploy applications.
22
24
 
25
+ <picture>
26
+ <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/assets/aigne-cli-dark.png" media="(prefers-color-scheme: dark)">
27
+ <source srcset="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/assets/aigne-cli.png" media="(prefers-color-scheme: light)">
28
+ <img src="https://raw.githubusercontent.com/AIGNE-io/aigne-framework/main/aigne-cli.png" alt="AIGNE Arch" />
29
+ </picture>
30
+
23
31
  ## Features
24
32
 
25
33
  * **Project Creation**: Quickly create new AIGNE projects with predefined file structures and configurations
@@ -10,7 +10,7 @@ import { pick } from "@aigne/core/utils/type-utils.js";
10
10
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
11
11
  import { joinURL } from "ufo";
12
12
  import { parse } from "yaml";
13
- import { ZodObject, ZodString } from "zod";
13
+ import { ZodBoolean, ZodNumber, ZodObject, ZodString, ZodType } from "zod";
14
14
  import { downloadAndExtract } from "../utils/download.js";
15
15
  import { loadAIGNE } from "../utils/load-aigne.js";
16
16
  import { runAgentWithAIGNE, stdinHasData } from "../utils/run-with-aigne.js";
@@ -19,7 +19,7 @@ const NPM_PACKAGE_CACHE_TIME_MS = 1000 * 60 * 60 * 24; // 1 day
19
19
  const builtinApps = [
20
20
  {
21
21
  name: "doc-smith",
22
- describe: "Generate professional documents by doc-smith",
22
+ describe: "Generate and maintain project docs — powered by agents.",
23
23
  aliases: ["docsmith", "doc"],
24
24
  },
25
25
  ];
@@ -92,9 +92,14 @@ const agentCommandModule = ({ dir, agent, }) => {
92
92
  describe: agent.description || "",
93
93
  builder: (yargs) => {
94
94
  for (const [option, config] of Object.entries(inputSchema)) {
95
+ const innerType = innerZodType(config);
95
96
  yargs.option(option, {
96
97
  // TODO: support more types
97
- type: "string",
98
+ type: innerType instanceof ZodBoolean
99
+ ? "boolean"
100
+ : innerType instanceof ZodNumber
101
+ ? "number"
102
+ : "string",
98
103
  description: config.description,
99
104
  });
100
105
  if (!(config.isNullable() || config.isOptional())) {
@@ -118,6 +123,12 @@ const agentCommandModule = ({ dir, agent, }) => {
118
123
  },
119
124
  };
120
125
  };
126
+ function innerZodType(type) {
127
+ if ("innerType" in type._def && type._def.innerType instanceof ZodType) {
128
+ return innerZodType(type._def.innerType);
129
+ }
130
+ return type;
131
+ }
121
132
  export async function invokeCLIAgentFromDir(options) {
122
133
  const aigne = await loadAIGNE(options.dir, { model: options.input.model });
123
134
  try {
@@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
3
3
  import chalk from "chalk";
4
4
  import { parse } from "yaml";
5
5
  import { getUserInfo } from "../utils/aigne-hub-user.js";
6
- import { AIGNE_ENV_FILE, connectToAIGNEHub } from "../utils/load-aigne.js";
6
+ import { AIGNE_ENV_FILE, connectToAIGNEHub, DEFAULT_URL } from "../utils/load-aigne.js";
7
7
  const formatNumber = (balance) => {
8
8
  const balanceNum = String(balance).split(".")[0];
9
9
  return chalk.yellow((balanceNum || "").replace(/\B(?=(\d{3})+(?!\d))/g, ","));
@@ -36,16 +36,15 @@ export async function displayStatus(statusList) {
36
36
  return;
37
37
  }
38
38
  console.log(chalk.blue("AIGNE Hub Connection Status:\n"));
39
- for (const status of statusList) {
40
- const userInfo = await getUserInfo({
41
- baseUrl: status.apiUrl,
42
- accessKey: status.apiKey,
43
- }).catch((e) => {
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) => {
44
42
  console.error(e);
45
43
  return null;
46
44
  });
47
- const statusIcon = userInfo ? chalk.green("✓") : chalk.red("✗");
48
- const statusText = userInfo ? "Connected" : "Disconnected";
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";
49
48
  console.log(`${statusIcon} ${chalk.bold(status.host)}`);
50
49
  console.log(` Status: ${statusText}`);
51
50
  if (userInfo) {
@@ -5,7 +5,7 @@ import { isAbsolute, join, resolve } from "node:path";
5
5
  import { logger } from "@aigne/core/utils/logger.js";
6
6
  import { isNonNullable } from "@aigne/core/utils/type-utils.js";
7
7
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
8
- import { select } from "@inquirer/prompts";
8
+ import { input as inputInquirer, select as selectInquirer } from "@inquirer/prompts";
9
9
  import { ListrInquirerPromptAdapter } from "@listr2/prompt-adapter-inquirer";
10
10
  import { config } from "dotenv-flow";
11
11
  import { isV1Package, toAIGNEPackage } from "../utils/agent-v1.js";
@@ -31,6 +31,10 @@ export function createRunCommand({ aigneFilePath, } = {}) {
31
31
  .option("cache-dir", {
32
32
  describe: "Directory to download the package to (defaults to the ~/.aigne/xxx)",
33
33
  type: "string",
34
+ })
35
+ .option("aigne-hub-url", {
36
+ describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models. ",
37
+ type: "string",
34
38
  })
35
39
  .strict(false);
36
40
  },
@@ -63,11 +67,21 @@ export function createRunCommand({ aigneFilePath, } = {}) {
63
67
  task: async (ctx, task) => {
64
68
  // Load env files in the aigne directory
65
69
  config({ path: dir, silent: true });
66
- const aigne = await loadAIGNE(dir, { ...options, model: options.model || process.env.MODEL }, {
70
+ const aigne = await loadAIGNE(dir, {
71
+ ...options,
72
+ model: options.model || process.env.MODEL,
73
+ aigneHubUrl: options?.aigneHubUrl,
74
+ }, {
67
75
  inquirerPromptFn: (prompt) => {
76
+ if (prompt.type === "input") {
77
+ return task
78
+ .prompt(ListrInquirerPromptAdapter)
79
+ .run(inputInquirer, prompt)
80
+ .then((res) => ({ [prompt.name]: res }));
81
+ }
68
82
  return task
69
83
  .prompt(ListrInquirerPromptAdapter)
70
- .run(select, prompt)
84
+ .run(selectInquirer, prompt)
71
85
  .then((res) => ({ [prompt.name]: res }));
72
86
  },
73
87
  });
@@ -14,5 +14,6 @@ export declare function serveMCPServerFromDir(options: {
14
14
  host: string;
15
15
  port?: number;
16
16
  pathname: string;
17
+ aigneHubUrl?: string;
17
18
  }): Promise<void>;
18
19
  export {};
@@ -36,6 +36,10 @@ export function createServeMCPCommand({ aigneFilePath, } = {}) {
36
36
  describe: "Pathname to the service",
37
37
  type: "string",
38
38
  default: "/mcp",
39
+ })
40
+ .option("aigne-hub-url", {
41
+ describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models. ",
42
+ type: "string",
39
43
  });
40
44
  },
41
45
  handler: async (options) => {
@@ -47,7 +51,7 @@ export function createServeMCPCommand({ aigneFilePath, } = {}) {
47
51
  }
48
52
  export async function serveMCPServerFromDir(options) {
49
53
  const port = options.port || DEFAULT_PORT();
50
- const aigne = await loadAIGNE(options.dir);
54
+ const aigne = await loadAIGNE(options.dir, { aigneHubUrl: options.aigneHubUrl });
51
55
  await serveMCPServer({
52
56
  aigne,
53
57
  host: options.host,
@@ -1,6 +1,7 @@
1
1
  import type { CommandModule } from "yargs";
2
2
  interface TestOptions {
3
3
  path: string;
4
+ aigneHubUrl?: string;
4
5
  }
5
6
  export declare function createTestCommand({ aigneFilePath, }?: {
6
7
  aigneFilePath?: string;
@@ -7,17 +7,22 @@ export function createTestCommand({ aigneFilePath, } = {}) {
7
7
  command: "test",
8
8
  describe: "Run tests in the specified agents directory",
9
9
  builder: (yargs) => {
10
- return yargs.option("path", {
10
+ return yargs
11
+ .option("path", {
11
12
  describe: "Path to the agents directory or URL to aigne project",
12
13
  type: "string",
13
14
  default: ".",
14
15
  alias: ["url"],
16
+ })
17
+ .option("aigne-hub-url", {
18
+ describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models. ",
19
+ type: "string",
15
20
  });
16
21
  },
17
22
  handler: async (options) => {
18
23
  const path = aigneFilePath || options.path;
19
24
  const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
20
- const aigne = await loadAIGNE(absolutePath);
25
+ const aigne = await loadAIGNE(absolutePath, { aigneHubUrl: options?.aigneHubUrl });
21
26
  assert(aigne.rootDir);
22
27
  spawnSync("node", ["--test"], { cwd: aigne.rootDir, stdio: "inherit" });
23
28
  },
@@ -10,7 +10,7 @@ export interface UserInfoResult {
10
10
  paymentLink: string | null;
11
11
  profileLink: string;
12
12
  }
13
- export declare function getUserInfo({ baseUrl, accessKey, }: {
13
+ export declare function getUserInfo({ baseUrl, apiKey, }: {
14
14
  baseUrl: string;
15
- accessKey: string;
15
+ apiKey: string;
16
16
  }): Promise<UserInfoResult>;
@@ -1,8 +1,8 @@
1
1
  import { joinURL } from "ufo";
2
- export async function getUserInfo({ baseUrl, accessKey, }) {
2
+ export async function getUserInfo({ baseUrl, apiKey, }) {
3
3
  const response = await fetch(joinURL(baseUrl, "/api/user/info"), {
4
4
  headers: {
5
- Authorization: `Bearer ${accessKey}`,
5
+ Authorization: `Bearer ${apiKey}`,
6
6
  },
7
7
  });
8
8
  if (!response.ok)
@@ -11,6 +11,7 @@ export interface RunOptions extends RunAIGNECommandOptions {
11
11
  path: string;
12
12
  entryAgent?: string;
13
13
  cacheDir?: string;
14
+ aigneHubUrl?: string;
14
15
  }
15
16
  type FetchResult = {
16
17
  accessKeyId: string;
@@ -42,19 +43,20 @@ interface CreateConnectOptions {
42
43
  }) => Promise<FetchResult>;
43
44
  }
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/";
45
47
  export declare const formatModelName: (models: ReturnType<typeof availableModels>, model: string, inquirerPrompt: typeof inquirer.prompt) => Promise<string>;
46
48
  export declare function connectToAIGNEHub(url: string): Promise<{
47
- accessKey: string;
49
+ apiKey: string;
48
50
  url: string;
49
51
  } | {
50
- accessKey: undefined;
52
+ apiKey: undefined;
51
53
  url: undefined;
52
54
  }>;
53
55
  export declare const checkConnectionStatus: (host: string) => Promise<{
54
56
  apiKey: any;
55
57
  url: string;
56
58
  }>;
57
- export declare function loadAIGNE(path: string, options?: Pick<RunOptions, "model">, actionOptions?: {
59
+ export declare function loadAIGNE(path: string, options?: Pick<RunOptions, "model" | "aigneHubUrl">, actionOptions?: {
58
60
  inquirerPromptFn?: (prompt: {
59
61
  type: string;
60
62
  name: string;
@@ -93,7 +93,7 @@ export async function createConnect({ connectUrl, openPage, fetchInterval = 3 *
93
93
  const AGENT_HUB_PROVIDER = "aignehub";
94
94
  const DEFAULT_AIGNE_HUB_MODEL = "openai/gpt-4o";
95
95
  const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = `${AGENT_HUB_PROVIDER}:${DEFAULT_AIGNE_HUB_MODEL}`;
96
- const DEFAULT_URL = "https://hub.aigne.io/";
96
+ export const DEFAULT_URL = "https://hub.aigne.io/";
97
97
  export const formatModelName = async (models, model, inquirerPrompt) => {
98
98
  if (!model)
99
99
  return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
@@ -151,7 +151,7 @@ export async function connectToAIGNEHub(url) {
151
151
  openPage: (pageUrl) => open(pageUrl),
152
152
  });
153
153
  const accessKeyOptions = {
154
- accessKey: result.accessKeySecret,
154
+ apiKey: result.accessKeySecret,
155
155
  url: joinURL(origin, aigneHubMount?.mountPoint || ""),
156
156
  };
157
157
  // After redirection, write the AIGNE Hub access token
@@ -163,7 +163,10 @@ export async function connectToAIGNEHub(url) {
163
163
  await writeFile(AIGNE_ENV_FILE, stringify({
164
164
  ...envs,
165
165
  [host]: {
166
- AIGNE_HUB_API_KEY: accessKeyOptions.accessKey,
166
+ AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
167
+ AIGNE_HUB_API_URL: accessKeyOptions.url,
168
+ },
169
+ default: {
167
170
  AIGNE_HUB_API_URL: accessKeyOptions.url,
168
171
  },
169
172
  })).catch((err) => {
@@ -174,7 +177,7 @@ export async function connectToAIGNEHub(url) {
174
177
  }
175
178
  catch (error) {
176
179
  logger.error("Failed to connect to AIGNE Hub", error.message);
177
- return { accessKey: undefined, url: undefined };
180
+ return { apiKey: undefined, url: undefined };
178
181
  }
179
182
  }
180
183
  export const checkConnectionStatus = async (host) => {
@@ -201,11 +204,17 @@ export const checkConnectionStatus = async (host) => {
201
204
  };
202
205
  const mockInquirerPrompt = (() => Promise.resolve({ useAigneHub: true }));
203
206
  export async function loadAIGNE(path, options, actionOptions) {
204
- const models = availableModels();
205
- const AIGNE_HUB_URL = process.env.AIGNE_HUB_API_URL || DEFAULT_URL;
206
- const connectUrl = joinURL(new URL(AIGNE_HUB_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
207
+ const aigneDir = join(homedir(), ".aigne");
208
+ if (!existsSync(aigneDir)) {
209
+ mkdirSync(aigneDir, { recursive: true });
210
+ }
211
+ const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
207
212
  const inquirerPrompt = (actionOptions?.inquirerPromptFn ??
208
213
  inquirer.prompt);
214
+ const models = availableModels();
215
+ const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
216
+ const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL || DEFAULT_URL;
217
+ const connectUrl = joinURL(new URL(AIGNE_HUB_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
209
218
  const { host } = new URL(AIGNE_HUB_URL);
210
219
  const { aigne } = await loadAIGNEFile(path).catch(() => ({ aigne: null }));
211
220
  const result = await checkConnectionStatus(host).catch(() => null);
@@ -222,25 +231,55 @@ export async function loadAIGNE(path, options, actionOptions) {
222
231
  }
223
232
  catch (error) {
224
233
  if (error instanceof Error && error.message.includes("login first")) {
225
- // If none or invalid, prompt the user to proceed
226
- const subscribePrompt = await inquirerPrompt({
227
- type: "list",
228
- name: "subscribe",
229
- message: "No LLM API Keys or AIGNE Hub connections found, select your preferred way to continue:",
230
- choices: [
231
- {
232
- name: "Connect to AIGNE Hub with just a few clicks, free credits eligible for new users (Recommended)",
233
- value: true,
234
- },
235
- { name: "Exit and configure my own LLM API Keys", value: false },
236
- ],
237
- default: true,
238
- });
239
- if (!subscribePrompt.subscribe) {
240
- console.warn("The AIGNE Hub connection has been cancelled");
241
- process.exit(0);
234
+ let aigneHubUrl = connectUrl;
235
+ if (!configUrl) {
236
+ const { subscribe } = await inquirerPrompt({
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
+ }
242
281
  }
243
- credential = await connectToAIGNEHub(connectUrl);
282
+ credential = await connectToAIGNEHub(aigneHubUrl);
244
283
  }
245
284
  }
246
285
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.31.0",
4
- "description": "Command-line interface for AIGNE Framework - A functional AI application development framework for building scalable AI-powered applications with workflow patterns, MCP protocol integration, and multi-model support",
3
+ "version": "1.32.1",
4
+ "description": "Your command center for agent development",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -74,13 +74,13 @@
74
74
  "yaml": "^2.8.0",
75
75
  "yargs": "^18.0.0",
76
76
  "zod": "^3.25.67",
77
- "@aigne/agent-library": "^1.21.14",
78
- "@aigne/agentic-memory": "^1.0.14",
79
- "@aigne/aigne-hub": "^0.4.5",
80
- "@aigne/core": "^1.46.0",
81
- "@aigne/default-memory": "^1.0.14",
77
+ "@aigne/agentic-memory": "^1.0.15",
78
+ "@aigne/agent-library": "^1.21.15",
79
+ "@aigne/core": "^1.46.1",
80
+ "@aigne/aigne-hub": "^0.4.6",
81
+ "@aigne/default-memory": "^1.0.15",
82
82
  "@aigne/observability-api": "^0.9.0",
83
- "@aigne/openai": "^0.10.14"
83
+ "@aigne/openai": "^0.10.15"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@types/archiver": "^6.0.3",