@aigne/cli 1.25.1 → 1.26.1-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.
@@ -1,9 +1,10 @@
1
- import { existsSync } from "node:fs";
2
- import { appendFile, readFile } from "node:fs/promises";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { AIGNE } from "@aigne/core";
6
- import { loadModel } from "@aigne/core/loader/index.js";
6
+ import { loadAIGNEFile, loadModel } from "@aigne/core/loader/index.js";
7
+ import { logger } from "@aigne/core/utils/logger.js";
7
8
  import { AesCrypter } from "@ocap/mcrypto/lib/crypter/aes-legacy.js";
8
9
  import crypto from "crypto";
9
10
  import inquirer from "inquirer";
@@ -14,28 +15,40 @@ import { parse, stringify } from "yaml";
14
15
  import { availableMemories, availableModels } from "../constants.js";
15
16
  import { parseModelOption } from "./run-with-aigne.js";
16
17
  const aes = new AesCrypter();
17
- const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
18
+ export const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
19
+ export const encrypt = (m, s, i) => aes.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
18
20
  const escapeFn = (str) => str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
19
- const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
21
+ const unescapeFn = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
22
+ export const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
23
+ export const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescapeFn(str), "base64"));
24
+ const TEST_ENV = process.env.CI || process.env.NODE_ENV === "test";
25
+ export const AIGNE_ENV_FILE = TEST_ENV
26
+ ? join(homedir(), ".aigne", "test-aigne-hub-connected.yaml")
27
+ : join(homedir(), ".aigne", "aigne-hub-connected.yaml");
20
28
  const request = async (config) => {
21
- const response = await fetch(config.url, { method: config.method || "GET" });
22
- if (!response.ok) {
23
- throw new Error(`HTTP error! status: ${response.status}`);
29
+ const headers = {};
30
+ if (config.requestCount !== undefined) {
31
+ headers["X-Request-Count"] = config.requestCount.toString();
24
32
  }
33
+ const response = await fetch(config.url, { method: config.method || "GET", headers });
34
+ if (!response.ok)
35
+ throw new Error(`HTTP error! status: ${response.status}`);
25
36
  const data = await response.json();
26
37
  return { data };
27
38
  };
28
39
  const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
29
40
  const ACCESS_KEY_PREFIX = "/api/access-key";
30
41
  const ACCESS_KEY_SESSION_API = `${ACCESS_KEY_PREFIX}/session`;
31
- const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
42
+ export const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
32
43
  const sessionURL = withQuery(joinURL(connectUrl, ACCESS_KEY_SESSION_API), { sid: sessionId });
44
+ let requestCount = 0;
33
45
  const condition = async () => {
34
- const { data: session } = await request({ url: sessionURL });
46
+ const { data: session } = await request({ url: sessionURL, requestCount });
47
+ requestCount++;
35
48
  return Boolean(session.accessKeyId && session.accessKeySecret);
36
49
  };
37
50
  await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
38
- const { data: session } = await request({ url: sessionURL });
51
+ const { data: session } = await request({ url: sessionURL, requestCount });
39
52
  await request({ url: sessionURL, method: "DELETE" });
40
53
  return {
41
54
  ...session,
@@ -46,7 +59,7 @@ const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout
46
59
  function baseWrapSpinner(_, waiting) {
47
60
  return Promise.resolve(waiting());
48
61
  }
49
- async function createConnect({ connectUrl, openPage, fetchInterval = 3 * 1000, retry = 1500, source = "Blocklet CLI", connectAction = "connect-cli", wrapSpinner = baseWrapSpinner, closeOnSuccess, intervalFetchConfig, }) {
62
+ export async function createConnect({ connectUrl, openPage, fetchInterval = 3 * 1000, retry = 1500, source = "Blocklet CLI", connectAction = "connect-cli", wrapSpinner = baseWrapSpinner, closeOnSuccess, intervalFetchConfig, }) {
50
63
  try {
51
64
  const startSessionURL = joinURL(connectUrl, ACCESS_KEY_SESSION_API);
52
65
  const { data: session } = await request({ url: startSessionURL, method: "POST" });
@@ -80,13 +93,11 @@ const AGENT_HUB_PROVIDER = "aignehub";
80
93
  const DEFAULT_AIGNE_HUB_MODEL = "openai/gpt-4o";
81
94
  const DEFAULT_AIGNE_HUB_PROVIDER_MODEL = `${AGENT_HUB_PROVIDER}:${DEFAULT_AIGNE_HUB_MODEL}`;
82
95
  const DEFAULT_URL = "https://hub.aigne.io/";
83
- const formatModelName = async (models, model, inquirerPrompt) => {
84
- if (process.env.NODE_ENV === "test")
85
- return model;
96
+ export const formatModelName = async (models, model, inquirerPrompt) => {
86
97
  if (!model)
87
98
  return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
88
99
  const { provider, name } = parseModelOption(model);
89
- if (!provider || !name) {
100
+ if (!provider) {
90
101
  return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
91
102
  }
92
103
  const providerName = provider.replace(/-/g, "");
@@ -99,6 +110,9 @@ const formatModelName = async (models, model, inquirerPrompt) => {
99
110
  if (m.apiKeyEnvName && process.env[m.apiKeyEnvName]) {
100
111
  return model;
101
112
  }
113
+ if (TEST_ENV) {
114
+ return `${AGENT_HUB_PROVIDER}:${provider}/${name}`;
115
+ }
102
116
  const result = await inquirerPrompt({
103
117
  type: "list",
104
118
  name: "useAigneHub",
@@ -119,88 +133,106 @@ const formatModelName = async (models, model, inquirerPrompt) => {
119
133
  return model;
120
134
  return `${AGENT_HUB_PROVIDER}:${provider}/${name}`;
121
135
  };
122
- export async function loadAIGNE(path, options, checkAuthorizeOptions) {
136
+ export async function connectToAIGNEHub(url) {
137
+ const { origin, host } = new URL(url);
138
+ const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
139
+ const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
140
+ const blockletInfo = await fetch(joinURL(origin, BLOCKLET_JSON_PATH));
141
+ const blocklet = await blockletInfo.json();
142
+ const aigneHubMount = (blocklet?.componentMountPoints || []).find((m) => m.did === "z8ia3xzq2tMq8CRHfaXj1BTYJyYnEcHbqP8cJ");
143
+ try {
144
+ const result = await createConnect({
145
+ connectUrl: connectUrl,
146
+ connectAction: "gen-simple-access-key",
147
+ source: `@aigne/cli connect to AIGNE hub`,
148
+ closeOnSuccess: true,
149
+ openPage: (pageUrl) => open(pageUrl),
150
+ });
151
+ const accessKeyOptions = {
152
+ accessKey: result.accessKeySecret,
153
+ url: joinURL(origin, aigneHubMount?.mountPoint || ""),
154
+ };
155
+ // After redirection, write the AIGNE Hub access token
156
+ const aigneDir = join(homedir(), ".aigne");
157
+ if (!existsSync(aigneDir)) {
158
+ mkdirSync(aigneDir, { recursive: true });
159
+ }
160
+ const envs = parse(await readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
161
+ await writeFile(AIGNE_ENV_FILE, stringify({
162
+ ...envs,
163
+ [host]: {
164
+ AIGNE_HUB_API_KEY: accessKeyOptions.accessKey,
165
+ AIGNE_HUB_API_URL: accessKeyOptions.url,
166
+ },
167
+ })).catch((err) => {
168
+ logger.error("Failed to write AIGNE Hub access token to .aigne/aigne-hub-connected.yaml", err.message);
169
+ throw err;
170
+ });
171
+ return accessKeyOptions;
172
+ }
173
+ catch (error) {
174
+ logger.error("Failed to connect to AIGNE Hub", error.message);
175
+ return { accessKey: undefined, url: undefined };
176
+ }
177
+ }
178
+ export async function loadAIGNE(path, options, actionOptions) {
123
179
  const models = availableModels();
124
- const AIGNE_ENV_FILE = join(homedir(), ".aigne", "aigne-hub-connected.yaml");
125
180
  const AIGNE_HUB_URL = process.env.AIGNE_HUB_API_URL || DEFAULT_URL;
126
181
  const connectUrl = joinURL(new URL(AIGNE_HUB_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
127
- const inquirerPrompt = (checkAuthorizeOptions?.inquirerPromptFn ??
182
+ const inquirerPrompt = (actionOptions?.inquirerPromptFn ??
128
183
  inquirer.prompt);
184
+ const { aigne } = await loadAIGNEFile(path).catch(() => ({ aigne: null }));
129
185
  let accessKeyOptions = {};
130
- const modelName = await formatModelName(models, options?.model || "", inquirerPrompt);
131
- if (!process.env.CI) {
132
- if ((modelName.toLocaleLowerCase() || "").includes(AGENT_HUB_PROVIDER)) {
133
- const { origin, host } = new URL(AIGNE_HUB_URL);
134
- try {
135
- // 检查 aigne-hub access token
136
- if (!existsSync(AIGNE_ENV_FILE)) {
137
- throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
138
- }
139
- const data = await readFile(AIGNE_ENV_FILE, "utf8");
140
- if (!data.includes("AIGNE_HUB_API_KEY")) {
141
- throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
142
- }
143
- const envs = parse(data);
144
- if (!envs[host]) {
145
- throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
146
- }
147
- const env = envs[host];
148
- if (!env.AIGNE_HUB_API_KEY) {
149
- throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
150
- }
151
- accessKeyOptions = {
152
- accessKey: env.AIGNE_HUB_API_KEY,
153
- url: joinURL(env.AIGNE_HUB_API_URL),
154
- };
186
+ const modelName = await formatModelName(models, options?.model || `${aigne?.model?.provider ?? ""}:${aigne?.model?.name ?? ""}`, inquirerPrompt);
187
+ if (TEST_ENV && !actionOptions?.runTest) {
188
+ const model = await loadModel(models, parseModelOption(modelName), undefined, accessKeyOptions);
189
+ return await AIGNE.load(path, { models, memories: availableMemories, model });
190
+ }
191
+ if ((modelName.toLocaleLowerCase() || "").includes(AGENT_HUB_PROVIDER)) {
192
+ const { host } = new URL(AIGNE_HUB_URL);
193
+ try {
194
+ // aigne-hub access token
195
+ if (!existsSync(AIGNE_ENV_FILE)) {
196
+ throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
197
+ }
198
+ const data = await readFile(AIGNE_ENV_FILE, "utf8");
199
+ if (!data.includes("AIGNE_HUB_API_KEY")) {
200
+ throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
201
+ }
202
+ const envs = parse(data);
203
+ if (!envs[host]) {
204
+ throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
155
205
  }
156
- catch (error) {
157
- if (error instanceof Error && error.message.includes("login first")) {
158
- // If none or invalid, prompt the user to proceed
159
- const subscribePrompt = await inquirerPrompt({
160
- type: "list",
161
- name: "subscribe",
162
- message: "No LLM API Keys or AIGNE Hub connections found, select your preferred way to continue:",
163
- choices: [
164
- {
165
- name: "Connect to AIGNE Hub with just a few clicks, free credits eligible for new users (Recommended)",
166
- value: true,
167
- },
168
- { name: "Exit and configure my own LLM API Keys", value: false },
169
- ],
170
- default: true,
171
- });
172
- if (!subscribePrompt.subscribe) {
173
- console.warn("The AIGNE Hub connection has been cancelled");
174
- process.exit(0);
175
- }
176
- const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
177
- const blockletInfo = await fetch(joinURL(origin, BLOCKLET_JSON_PATH));
178
- const blocklet = await blockletInfo.json();
179
- const aigneHubMount = (blocklet?.componentMountPoints || []).find((m) => m.did === "z8ia3xzq2tMq8CRHfaXj1BTYJyYnEcHbqP8cJ");
180
- try {
181
- const result = await createConnect({
182
- connectUrl: connectUrl,
183
- connectAction: "gen-simple-access-key",
184
- source: `@aigne/cli connect to AIGNE hub`,
185
- closeOnSuccess: true,
186
- openPage: (pageUrl) => open(pageUrl),
187
- });
188
- accessKeyOptions = {
189
- accessKey: result.accessKeySecret,
190
- url: joinURL(origin, aigneHubMount?.mountPoint || ""),
191
- };
192
- // After redirection, write the AIGNE Hub access token
193
- await appendFile(AIGNE_ENV_FILE, stringify({
194
- [host]: {
195
- AIGNE_HUB_API_KEY: accessKeyOptions.accessKey,
196
- AIGNE_HUB_API_URL: accessKeyOptions.url,
197
- },
198
- }));
199
- }
200
- catch (error) {
201
- console.error(error);
202
- }
206
+ const env = envs[host];
207
+ if (!env.AIGNE_HUB_API_KEY) {
208
+ throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
209
+ }
210
+ accessKeyOptions = {
211
+ accessKey: env.AIGNE_HUB_API_KEY,
212
+ url: joinURL(env.AIGNE_HUB_API_URL),
213
+ };
214
+ }
215
+ catch (error) {
216
+ if (error instanceof Error && error.message.includes("login first")) {
217
+ // If none or invalid, prompt the user to proceed
218
+ const subscribePrompt = await inquirerPrompt({
219
+ type: "list",
220
+ name: "subscribe",
221
+ message: "No LLM API Keys or AIGNE Hub connections found, select your preferred way to continue:",
222
+ choices: [
223
+ {
224
+ name: "Connect to AIGNE Hub with just a few clicks, free credits eligible for new users (Recommended)",
225
+ value: true,
226
+ },
227
+ { name: "Exit and configure my own LLM API Keys", value: false },
228
+ ],
229
+ default: true,
230
+ });
231
+ if (!subscribePrompt.subscribe) {
232
+ console.warn("The AIGNE Hub connection has been cancelled");
233
+ process.exit(0);
203
234
  }
235
+ accessKeyOptions = await connectToAIGNEHub(connectUrl);
204
236
  }
205
237
  }
206
238
  }
@@ -1,7 +1,7 @@
1
1
  import { type Agent, 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
- import { Command } from "commander";
4
+ import type { Argv } from "yargs";
5
5
  import { type ChatLoopOptions } from "./run-chat-loop.js";
6
6
  export interface RunAIGNECommandOptions {
7
7
  chat?: boolean;
@@ -17,7 +17,31 @@ export interface RunAIGNECommandOptions {
17
17
  logLevel?: LogLevel;
18
18
  force?: boolean;
19
19
  }
20
- export declare const createRunAIGNECommand: (name?: string) => Command;
20
+ export declare const createRunAIGNECommand: (yargs: Argv) => Argv<{
21
+ chat: boolean;
22
+ } & {
23
+ model: string | undefined;
24
+ } & {
25
+ temperature: number | undefined;
26
+ } & {
27
+ "top-p": number | undefined;
28
+ } & {
29
+ "presence-penalty": number | undefined;
30
+ } & {
31
+ "frequency-penalty": number | undefined;
32
+ } & {
33
+ input: (string | number)[] | undefined;
34
+ } & {
35
+ format: string | undefined;
36
+ } & {
37
+ output: string | undefined;
38
+ } & {
39
+ "output-key": string;
40
+ } & {
41
+ force: boolean;
42
+ } & {
43
+ "log-level": LogLevel;
44
+ }>;
21
45
  export declare function parseAgentInputByCommander(agent: Agent, options?: RunAIGNECommandOptions & {
22
46
  inputKey?: string;
23
47
  argv?: string[];
@@ -4,67 +4,90 @@ 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 { AIGNE, DEFAULT_OUTPUT_KEY, readAllString, UserAgent, } from "@aigne/core";
7
+ import { AIAgent, AIGNE, DEFAULT_OUTPUT_KEY, readAllString, UserAgent, } from "@aigne/core";
8
8
  import { loadModel } from "@aigne/core/loader/index.js";
9
9
  import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
10
- import { isEmpty, isNonNullable, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
10
+ import { flat, isEmpty, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
11
11
  import chalk from "chalk";
12
- import { Command } from "commander";
13
12
  import { parse } from "yaml";
13
+ import yargs from "yargs";
14
14
  import { ZodError, ZodObject, z } from "zod";
15
15
  import { availableModels } from "../constants.js";
16
16
  import { TerminalTracer } from "../tracer/terminal.js";
17
17
  import { DEFAULT_CHAT_INPUT_KEY, runChatLoopInTerminal, } from "./run-chat-loop.js";
18
- export const createRunAIGNECommand = (name = "run") => new Command(name)
19
- .allowUnknownOption(true)
20
- .allowExcessArguments(true)
21
- .description("Run agent with AIGNE in terminal")
22
- .option("--chat", "Run chat loop in terminal", false)
23
- .option("--model <provider[:model]>", `AI model to use in format 'provider[:model]' where model is optional. Examples: 'openai' or 'openai:gpt-4o-mini'. Available providers: ${availableModels()
24
- .map((i) => i.name.toLowerCase().replace(/ChatModel$/i, ""))
25
- .join(", ")} (default: openai)`)
26
- .option("--temperature <temperature>", "Temperature for the model (controls randomness, higher values produce more random outputs). Range: 0.0-2.0", customZodError("--temperature", (s) => z.coerce.number().min(0).max(2).parse(s)))
27
- .option("--top-p <top-p>", "Top P (nucleus sampling) parameter for the model (controls diversity). Range: 0.0-1.0", customZodError("--top-p", (s) => z.coerce.number().min(0).max(1).parse(s)))
28
- .option("--presence-penalty <presence-penalty>", "Presence penalty for the model (penalizes repeating the same tokens). Range: -2.0 to 2.0", customZodError("--presence-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)))
29
- .option("--frequency-penalty <frequency-penalty>", "Frequency penalty for the model (penalizes frequency of token usage). Range: -2.0 to 2.0", customZodError("--frequency-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)))
30
- .option("--input -i <input...>", "Input to the agent, use @<file> to read from a file")
31
- .option("--format <format>", "Input format for the agent (available: text, json, yaml default: text)")
32
- .option("--output -o <output>", "Output file to save the result (default: stdout)")
33
- .option("--output-key <output-key>", "Key in the result to save to the output file", DEFAULT_OUTPUT_KEY)
34
- .option("--force", "Truncate the output file if it exists, and create directory if the output path is not exists", false)
35
- .option("--log-level <level>", `Log level for detailed debugging information. Values: ${Object.values(LogLevel).join(", ")}`, customZodError("--log-level", (s) => z.nativeEnum(LogLevel).parse(s)), getLevelFromEnv(logger.options.ns) || LogLevel.INFO);
18
+ export const createRunAIGNECommand = (yargs) => yargs
19
+ .option("chat", {
20
+ describe: "Run chat loop in terminal",
21
+ type: "boolean",
22
+ default: false,
23
+ })
24
+ .option("model", {
25
+ describe: `AI model to use in format 'provider[:model]' where model is optional. Examples: 'openai' or 'openai:gpt-4o-mini'. Available providers: ${availableModels()
26
+ .map((i) => i.name.toLowerCase().replace(/ChatModel$/i, ""))
27
+ .join(", ")} (default: openai)`,
28
+ type: "string",
29
+ })
30
+ .option("temperature", {
31
+ describe: "Temperature for the model (controls randomness, higher values produce more random outputs). Range: 0.0-2.0",
32
+ type: "number",
33
+ coerce: customZodError("--temperature", (s) => z.coerce.number().min(0).max(2).parse(s)),
34
+ })
35
+ .option("top-p", {
36
+ describe: "Top P (nucleus sampling) parameter for the model (controls diversity). Range: 0.0-1.0",
37
+ type: "number",
38
+ coerce: customZodError("--top-p", (s) => z.coerce.number().min(0).max(1).parse(s)),
39
+ })
40
+ .option("presence-penalty", {
41
+ describe: "Presence penalty for the model (penalizes repeating the same tokens). Range: -2.0 to 2.0",
42
+ type: "number",
43
+ coerce: customZodError("--presence-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
44
+ })
45
+ .option("frequency-penalty", {
46
+ describe: "Frequency penalty for the model (penalizes frequency of token usage). Range: -2.0 to 2.0",
47
+ type: "number",
48
+ coerce: customZodError("--frequency-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
49
+ })
50
+ .option("input", {
51
+ describe: "Input to the agent, use @<file> to read from a file",
52
+ type: "array",
53
+ alias: "i",
54
+ })
55
+ .option("format", {
56
+ describe: "Input format for the agent (available: text, json, yaml default: text)",
57
+ type: "string",
58
+ })
59
+ .option("output", {
60
+ describe: "Output file to save the result (default: stdout)",
61
+ type: "string",
62
+ alias: "o",
63
+ })
64
+ .option("output-key", {
65
+ describe: "Key in the result to save to the output file",
66
+ type: "string",
67
+ default: DEFAULT_OUTPUT_KEY,
68
+ })
69
+ .option("force", {
70
+ describe: "Truncate the output file if it exists, and create directory if the output path is not exists",
71
+ type: "boolean",
72
+ default: false,
73
+ })
74
+ .option("log-level", {
75
+ describe: `Log level for detailed debugging information. Values: ${Object.values(LogLevel).join(", ")}`,
76
+ type: "string",
77
+ default: getLevelFromEnv(logger.options.ns) || LogLevel.INFO,
78
+ coerce: customZodError("--log-level", (s) => z.nativeEnum(LogLevel).parse(s)),
79
+ });
36
80
  export async function parseAgentInputByCommander(agent, options = {}) {
37
- const cmd = new Command()
38
- .description(`Run agent ${agent.name} with AIGNE`)
39
- .allowUnknownOption(true)
40
- .allowExcessArguments(true);
41
- const inputSchemaShape = agent.inputSchema instanceof ZodObject ? Object.keys(agent.inputSchema.shape) : [];
42
- for (const option of inputSchemaShape) {
43
- cmd.option(`--input-${option} <${option}>`);
44
- }
45
- const input = await new Promise((resolve, reject) => {
46
- cmd
47
- .action(async (agentInputOptions) => {
48
- try {
49
- const input = Object.fromEntries((await Promise.all(Object.entries(agentInputOptions).map(async ([key, value]) => {
50
- let k = key.replace(/^input/, "");
51
- k = k.charAt(0).toLowerCase() + k.slice(1);
52
- if (!k)
53
- return null;
54
- if (typeof value === "string" && value.startsWith("@")) {
55
- value = await readFile(value.slice(1), "utf8");
56
- }
57
- return [k, value];
58
- }))).filter(isNonNullable));
59
- resolve(input);
60
- }
61
- catch (error) {
62
- reject(error);
63
- }
64
- })
65
- .parseAsync(options.argv ?? process.argv)
66
- .catch((error) => reject(error));
67
- });
81
+ const inputSchemaShape = flat(agent instanceof AIAgent ? agent.inputKey : undefined, agent.inputSchema instanceof ZodObject ? Object.keys(agent.inputSchema.shape) : []);
82
+ const parsedInput = await yargs().parseAsync(options.argv ?? process.argv);
83
+ const input = Object.fromEntries(await Promise.all(inputSchemaShape.map(async (key) => {
84
+ const k = `input${key.charAt(0).toUpperCase()}${key.slice(1)}`;
85
+ let value = parsedInput[k];
86
+ if (typeof value === "string" && value.startsWith("@")) {
87
+ value = await readFile(value.slice(1), "utf8");
88
+ }
89
+ return [key, value];
90
+ })));
68
91
  const rawInput = options.input ||
69
92
  (isatty(process.stdin.fd) || !(await stdinHasData())
70
93
  ? null
@@ -96,14 +119,12 @@ export async function parseAgentInputByCommander(agent, options = {}) {
96
119
  return input;
97
120
  }
98
121
  export const parseModelOption = (model) => {
99
- const { provider, name } = (model || process.env.MODEL)?.match(/(?<provider>[^:]+)(:(?<name>(\S+)))?/)?.groups ?? {};
122
+ const { provider, name } = (model || process.env.MODEL)?.match(/(?<provider>[^:]*)(:(?<name>(\S+)))?/)?.groups ?? {};
100
123
  return { provider, name };
101
124
  };
102
125
  export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoopOptions, modelOptions, outputKey, } = {}) {
103
- await createRunAIGNECommand()
104
- .showHelpAfterError(true)
105
- .showSuggestionAfterError(true)
106
- .action(async (options) => {
126
+ await yargs()
127
+ .command("$0", "Run an agent with AIGNE", (yargs) => createRunAIGNECommand(yargs), async (options) => {
107
128
  if (options.logLevel) {
108
129
  logger.level = options.logLevel;
109
130
  }
@@ -134,6 +155,8 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
134
155
  await aigne.shutdown();
135
156
  }
136
157
  })
158
+ .alias("h", "help")
159
+ .alias("v", "version")
137
160
  .parseAsync(argv)
138
161
  .catch((error) => {
139
162
  console.error(`${chalk.red("Error:")} ${error.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.25.1",
3
+ "version": "1.26.1-0",
4
4
  "description": "cli for AIGNE framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,26 +24,60 @@
24
24
  "bunwrapper": "dist/bunwrapper.js"
25
25
  },
26
26
  "type": "module",
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
27
30
  "exports": {
31
+ ".": "./dist/index.js",
28
32
  "./*": "./dist/*"
29
33
  },
30
34
  "typesVersions": {
31
35
  "*": {
36
+ ".": [
37
+ "./lib/dts/index.d.ts"
38
+ ],
32
39
  "*": [
33
40
  "./dist/*"
34
41
  ]
35
42
  }
36
43
  },
44
+ "scripts": {
45
+ "lint": "tsc --noEmit",
46
+ "build": "tsc --build tsconfig.build.json",
47
+ "clean": "rimraf dist test/coverage templates/coverage",
48
+ "prepublishOnly": "run-s clean build",
49
+ "test": "run-s test:src test:templates",
50
+ "test:coverage": "run-s test:src:coverage test:templates:coverage",
51
+ "test:src": "bun --cwd test test",
52
+ "test:src:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
53
+ "test:templates": "cd templates && node --test",
54
+ "test:templates:coverage": "cd templates && mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-reporter=spec --test-reporter-destination=stdout"
55
+ },
37
56
  "dependencies": {
57
+ "@aigne/agent-library": "workspace:^",
58
+ "@aigne/agentic-memory": "workspace:^",
59
+ "@aigne/aigne-hub": "workspace:^",
60
+ "@aigne/anthropic": "workspace:^",
61
+ "@aigne/bedrock": "workspace:^",
62
+ "@aigne/core": "workspace:^",
63
+ "@aigne/deepseek": "workspace:^",
64
+ "@aigne/default-memory": "workspace:^",
65
+ "@aigne/gemini": "workspace:^",
38
66
  "@aigne/listr2": "^1.0.10",
39
67
  "@aigne/marked-terminal": "^7.3.2",
68
+ "@aigne/observability-api": "workspace:^",
69
+ "@aigne/ollama": "workspace:^",
70
+ "@aigne/open-router": "workspace:^",
71
+ "@aigne/openai": "workspace:^",
72
+ "@aigne/xai": "workspace:^",
73
+ "@blocklet/aigne-hub": "^0.2.17",
40
74
  "@inquirer/prompts": "^7.6.0",
75
+ "@inquirer/type": "^3.0.8",
41
76
  "@listr2/prompt-adapter-inquirer": "^3.0.1",
42
77
  "@modelcontextprotocol/sdk": "^1.15.0",
43
78
  "@ocap/mcrypto": "^1.21.0",
44
79
  "@smithy/node-http-handler": "^4.1.0",
45
80
  "chalk": "^5.4.1",
46
- "commander": "^14.0.0",
47
81
  "crypto": "^1.0.1",
48
82
  "detect-port": "^2.1.0",
49
83
  "dotenv-flow": "^4.1.0",
@@ -61,21 +95,8 @@
61
95
  "tar": "^7.4.3",
62
96
  "wrap-ansi": "^9.0.0",
63
97
  "yaml": "^2.8.0",
64
- "zod": "^3.25.67",
65
- "@aigne/agent-library": "^1.21.5",
66
- "@aigne/agentic-memory": "^1.0.5",
67
- "@aigne/aigne-hub": "^0.2.1",
68
- "@aigne/anthropic": "^0.10.1",
69
- "@aigne/core": "^1.38.1",
70
- "@aigne/default-memory": "^1.0.5",
71
- "@aigne/deepseek": "^0.7.5",
72
- "@aigne/bedrock": "^0.8.5",
73
- "@aigne/gemini": "^0.8.5",
74
- "@aigne/ollama": "^0.7.5",
75
- "@aigne/open-router": "^0.7.5",
76
- "@aigne/observability-api": "^0.8.1",
77
- "@aigne/openai": "^0.10.5",
78
- "@aigne/xai": "^0.7.5"
98
+ "yargs": "^18.0.0",
99
+ "zod": "^3.25.67"
79
100
  },
80
101
  "devDependencies": {
81
102
  "@types/archiver": "^6.0.3",
@@ -84,21 +105,12 @@
84
105
  "@types/glob": "^9.0.0",
85
106
  "@types/gradient-string": "^1.1.6",
86
107
  "@types/node": "^24.0.12",
108
+ "@types/yargs": "^17.0.33",
87
109
  "archiver": "^7.0.1",
110
+ "hono": "4.8.4",
88
111
  "npm-run-all": "^4.1.5",
89
112
  "rimraf": "^6.0.1",
90
113
  "typescript": "^5.8.3",
91
114
  "ufo": "^1.6.1"
92
- },
93
- "scripts": {
94
- "lint": "tsc --noEmit",
95
- "build": "tsc --build tsconfig.build.json",
96
- "clean": "rimraf dist test/coverage templates/coverage",
97
- "test": "run-s test:src test:templates",
98
- "test:coverage": "run-s test:src:coverage test:templates:coverage",
99
- "test:src": "bun --cwd test test",
100
- "test:src:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
101
- "test:templates": "cd templates && node --test",
102
- "test:templates:coverage": "cd templates && mkdir -p coverage && node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test-reporter=spec --test-reporter-destination=stdout"
103
115
  }
104
- }
116
+ }