@aigne/cli 1.43.1 → 1.44.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,54 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.44.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.44.0...cli-v1.44.1) (2025-09-05)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * should not return local path from aigne hub service ([#460](https://github.com/AIGNE-io/aigne-framework/issues/460)) ([c959717](https://github.com/AIGNE-io/aigne-framework/commit/c95971774f7e84dbeb3313f60b3e6464e2bb22e4))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @aigne/agent-library bumped to 1.21.38
16
+ * @aigne/agentic-memory bumped to 1.0.38
17
+ * @aigne/aigne-hub bumped to 0.8.8
18
+ * @aigne/core bumped to 1.58.1
19
+ * @aigne/default-memory bumped to 1.2.1
20
+ * @aigne/openai bumped to 0.14.1
21
+ * devDependencies
22
+ * @aigne/test-utils bumped to 0.5.45
23
+
24
+ ## [1.44.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.43.1...cli-v1.44.0) (2025-09-05)
25
+
26
+
27
+ ### Features
28
+
29
+ * add modalities support for chat model ([#454](https://github.com/AIGNE-io/aigne-framework/issues/454)) ([70d1bf6](https://github.com/AIGNE-io/aigne-framework/commit/70d1bf631f4e711235d89c6df8ee210a19179b30))
30
+ * **cli:** unify `run` and apps command arguments ([#436](https://github.com/AIGNE-io/aigne-framework/issues/436)) ([9c6b632](https://github.com/AIGNE-io/aigne-framework/commit/9c6b6323f8cfc2afe632d8ae392eab446981fc64))
31
+
32
+
33
+ ### Bug Fixes
34
+
35
+ * add file protocol to local file links in terminal tracer ([#455](https://github.com/AIGNE-io/aigne-framework/issues/455)) ([14890f9](https://github.com/AIGNE-io/aigne-framework/commit/14890f9ead679f38a7cc0b1ff31a97d2fe9056cb))
36
+
37
+
38
+ ### Dependencies
39
+
40
+ * The following workspace dependencies were updated
41
+ * dependencies
42
+ * @aigne/agent-library bumped to 1.21.37
43
+ * @aigne/agentic-memory bumped to 1.0.37
44
+ * @aigne/aigne-hub bumped to 0.8.7
45
+ * @aigne/core bumped to 1.58.0
46
+ * @aigne/default-memory bumped to 1.2.0
47
+ * @aigne/observability-api bumped to 0.10.2
48
+ * @aigne/openai bumped to 0.14.0
49
+ * devDependencies
50
+ * @aigne/test-utils bumped to 0.5.44
51
+
3
52
  ## [1.43.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.43.0...cli-v1.43.1) (2025-09-01)
4
53
 
5
54
 
package/dist/cli.js CHANGED
@@ -2,7 +2,6 @@
2
2
  import { existsSync, realpathSync, statSync } from "node:fs";
3
3
  import chalk from "chalk";
4
4
  import { config } from "dotenv-flow";
5
- import { hideBin } from "yargs/helpers";
6
5
  import { createAIGNECommand } from "./commands/aigne.js";
7
6
  import { highlightUrl } from "./utils/string-utils.js";
8
7
  config({ silent: true });
@@ -26,7 +25,7 @@ export default createAIGNECommand({ aigneFilePath })
26
25
  process.exit(1);
27
26
  }
28
27
  })
29
- .parseAsync(hideBin([...process.argv.slice(0, 2), ...process.argv.slice(aigneFilePath ? 3 : 2)]))
28
+ .parseAsync(process.argv.slice(aigneFilePath ? 3 : 2))
30
29
  .catch((error) => {
31
30
  console.log(""); // Add an empty line for better readability
32
31
  console.error(`${chalk.red("Error:")} ${highlightUrl(error.message)}`);
@@ -1,14 +1,15 @@
1
- import { AIGNE, type Message } from "@aigne/core";
1
+ import { type Agent, AIGNE, type Message } from "@aigne/core";
2
2
  import type { CommandModule } from "yargs";
3
+ import { type AgentRunCommonOptions } from "../utils/yargs.js";
3
4
  export declare function createAppCommands(): CommandModule[];
5
+ export declare const agentCommandModule: ({ dir, agent, }: {
6
+ dir: string;
7
+ agent: Agent;
8
+ }) => CommandModule<unknown, AgentRunCommonOptions>;
4
9
  export declare function invokeCLIAgentFromDir(options: {
5
10
  dir: string;
6
11
  agent: string;
7
- input: Message & {
8
- input?: string[];
9
- format?: "yaml" | "json";
10
- model?: string;
11
- };
12
+ input: Message & AgentRunCommonOptions;
12
13
  }): Promise<void>;
13
14
  export declare function loadApplication({ name, dir, forceUpgrade, }: {
14
15
  name: string;
@@ -4,12 +4,13 @@ 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
6
  import { AIGNE } from "@aigne/core";
7
+ import { logger } from "@aigne/core/utils/logger.js";
7
8
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
8
9
  import { joinURL } from "ufo";
9
10
  import { downloadAndExtract } from "../utils/download.js";
10
11
  import { loadAIGNE } from "../utils/load-aigne.js";
11
12
  import { runAgentWithAIGNE } from "../utils/run-with-aigne.js";
12
- import { parseAgentInput, withAgentInputSchema } from "../utils/yargs.js";
13
+ import { parseAgentInput, withAgentInputSchema, } from "../utils/yargs.js";
13
14
  import { serveMCPServerFromDir } from "./serve-mcp.js";
14
15
  const NPM_PACKAGE_CACHE_TIME_MS = 1000 * 60 * 60 * 24; // 1 day
15
16
  const builtinApps = [
@@ -36,7 +37,7 @@ export function createAppCommands() {
36
37
  if (aigne.cli.chat) {
37
38
  yargs.command({ ...agentCommandModule({ dir, agent: aigne.cli.chat }), command: "$0" });
38
39
  }
39
- for (const agent of aigne.cli?.agents ?? []) {
40
+ for (const agent of aigne.cli.agents) {
40
41
  yargs.command(agentCommandModule({ dir, agent }));
41
42
  }
42
43
  yargs.version(`${app.name} v${version}`).alias("version", "v");
@@ -83,28 +84,39 @@ const upgradeCommandModule = ({ name, dir, isLatest, version, }) => ({
83
84
  console.log(`\n✅ ${name} is already at the latest version (${version})`);
84
85
  },
85
86
  });
86
- const agentCommandModule = ({ dir, agent, }) => {
87
+ export const agentCommandModule = ({ dir, agent, }) => {
87
88
  return {
88
89
  command: agent.name,
89
90
  aliases: agent.alias || [],
90
91
  describe: agent.description || "",
91
92
  builder: async (yargs) => withAgentInputSchema(yargs, agent),
92
- handler: async (input) => {
93
- await invokeCLIAgentFromDir({ dir, agent: agent.name, input });
93
+ handler: async (options) => {
94
+ if (options.logLevel)
95
+ logger.level = options.logLevel;
96
+ await invokeCLIAgentFromDir({ dir, agent: agent.name, input: options });
94
97
  },
95
98
  };
96
99
  };
97
100
  export async function invokeCLIAgentFromDir(options) {
98
101
  const aigne = await loadAIGNE({
99
102
  path: options.dir,
100
- modelOptions: { model: options.input.model },
103
+ modelOptions: options.input,
101
104
  });
102
105
  try {
103
- const { chat, agents } = aigne.cli;
104
- const agent = chat && chat.name === options.agent ? chat : agents[options.agent];
106
+ const { chat } = aigne.cli;
107
+ const agent = chat && chat.name === options.agent
108
+ ? chat
109
+ : aigne.cli.agents[options.agent] ||
110
+ aigne.agents[options.agent] ||
111
+ aigne.skills[options.agent] ||
112
+ aigne.mcpServer.agents[options.agent];
105
113
  assert(agent, `Agent ${options.agent} not found in ${options.dir}`);
106
114
  const input = await parseAgentInput(options.input, agent);
107
- await runAgentWithAIGNE(aigne, agent, { input, chat: agent === chat });
115
+ await runAgentWithAIGNE(aigne, agent, {
116
+ ...options.input,
117
+ input,
118
+ chat: agent === chat || options.input.chat,
119
+ });
108
120
  }
109
121
  finally {
110
122
  await aigne.shutdown();
@@ -1,4 +1,6 @@
1
1
  import type { CommandModule } from "yargs";
2
2
  export declare function createRunCommand({ aigneFilePath, }?: {
3
3
  aigneFilePath?: string;
4
- }): CommandModule;
4
+ }): CommandModule<unknown, {
5
+ path?: string;
6
+ }>;
@@ -1,155 +1,97 @@
1
- import assert from "node:assert";
2
1
  import { cp, mkdir, rm } from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import { isAbsolute, join, resolve } from "node:path";
5
- import { logger } from "@aigne/core/utils/logger.js";
6
- import { isNonNullable } from "@aigne/core/utils/type-utils.js";
4
+ import { flat, isNonNullable } from "@aigne/core/utils/type-utils.js";
7
5
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
8
- import { input as inputInquirer, select as selectInquirer } from "@inquirer/prompts";
9
- import { ListrInquirerPromptAdapter } from "@listr2/prompt-adapter-inquirer";
10
6
  import { config } from "dotenv-flow";
7
+ import yargs from "yargs";
11
8
  import { isV1Package, toAIGNEPackage } from "../utils/agent-v1.js";
12
9
  import { downloadAndExtract } from "../utils/download.js";
13
10
  import { loadAIGNE } from "../utils/load-aigne.js";
14
- import { createRunAIGNECommand, parseAgentInputByCommander, runAgentWithAIGNE, } from "../utils/run-with-aigne.js";
11
+ import { agentCommandModule } from "./app.js";
15
12
  export function createRunCommand({ aigneFilePath, } = {}) {
16
13
  return {
17
14
  command: "run [path]",
18
- describe: "Run AIGNE from the specified agent",
19
- builder: (yargs) => {
20
- return createRunAIGNECommand(yargs)
15
+ describe: "Run AIGNE for the specified path",
16
+ builder: async (yargs) => {
17
+ return yargs
21
18
  .positional("path", {
22
- describe: "Path to the agents directory or URL to aigne project",
23
19
  type: "string",
20
+ describe: "Path to the agents directory or URL to an aigne project",
24
21
  default: ".",
25
- alias: ["url"],
26
- })
27
- .option("entry-agent", {
28
- describe: "Name of the agent to run (defaults to the first agent found)",
29
- type: "string",
30
- })
31
- .option("cache-dir", {
32
- describe: "Directory to download the package to (defaults to the ~/.aigne/xxx)",
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",
38
22
  })
23
+ .help(false)
24
+ .version(false)
39
25
  .strict(false);
40
26
  },
41
- handler: async (argv) => {
42
- const options = argv;
43
- const path = aigneFilePath || options.path;
44
- if (options.logLevel)
45
- logger.level = options.logLevel;
46
- const { cacheDir, dir } = prepareDirs(path, options);
47
- const originalLog = {};
48
- const { aigne, agent } = await new Listr([
49
- {
50
- title: "Prepare environment",
51
- task: (_, task) => {
52
- if (cacheDir) {
53
- return task.newListr([
54
- {
55
- title: "Download package",
56
- task: () => downloadPackage(path, cacheDir),
57
- },
58
- {
59
- title: "Extract package",
60
- task: () => extractPackage(cacheDir, dir),
61
- },
62
- ]);
63
- }
64
- },
65
- },
66
- {
67
- title: "Initialize AIGNE",
68
- task: async (ctx, task) => {
69
- // Load env files in the aigne directory
70
- config({ path: dir, silent: true });
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
- try {
80
- const aigne = await loadAIGNE({
81
- path: dir,
82
- modelOptions: {
83
- ...options,
84
- inquirerPromptFn: (prompt) => {
85
- if (prompt.type === "input") {
86
- return task
87
- .prompt(ListrInquirerPromptAdapter)
88
- .run(inputInquirer, prompt)
89
- .then((res) => ({ [prompt.name]: res }));
90
- }
91
- return task
92
- .prompt(ListrInquirerPromptAdapter)
93
- .run(selectInquirer, prompt)
94
- .then((res) => ({ [prompt.name]: res }));
95
- },
96
- },
97
- });
98
- ctx.aigne = aigne;
99
- }
100
- finally {
101
- Object.assign(console, originalLog);
102
- }
103
- },
104
- },
105
- {
106
- task: (ctx) => {
107
- const { aigne } = ctx;
108
- assert(aigne);
109
- let entryAgent;
110
- if (options.entryAgent) {
111
- entryAgent = aigne.agents[options.entryAgent];
112
- if (!entryAgent) {
113
- throw new Error(`\
114
- Agent "${options.entryAgent}" not found in ${aigne.rootDir}
115
-
116
- Available agents:
117
- ${aigne.agents.map((agent) => ` - ${agent.name}`).join("\n")}
118
- `);
119
- }
120
- }
121
- else {
122
- entryAgent = aigne.agents[0];
123
- if (!entryAgent)
124
- throw new Error(`No any agent found in ${aigne.rootDir}`);
125
- }
126
- ctx.agent = entryAgent;
127
- },
128
- },
129
- ], {
130
- rendererOptions: {
131
- collapseSubtasks: false,
132
- showErrorMessage: false,
133
- timer: PRESET_TIMER,
134
- },
135
- })
136
- .run()
137
- .then((ctx) => {
138
- ctx.logs.forEach((log) => console.log(log));
139
- return ctx;
140
- });
141
- assert(aigne);
142
- assert(agent);
143
- const input = await parseAgentInputByCommander(agent, options);
144
- try {
145
- await runAgentWithAIGNE(aigne, agent, { ...options, input });
27
+ handler: async (options) => {
28
+ const p = aigneFilePath || options.path;
29
+ if (!p)
30
+ throw new Error("No path specified");
31
+ const { aigne, path } = await loadApplication(p);
32
+ const subYargs = yargs().scriptName("").usage("aigne run <path> <agent> [...options]");
33
+ if (aigne.cli.chat) {
34
+ subYargs.command({
35
+ ...agentCommandModule({ dir: path, agent: aigne.cli.chat }),
36
+ command: "$0",
37
+ });
146
38
  }
147
- finally {
148
- await aigne.shutdown();
39
+ // Allow user to run all of agents in the AIGNE instances
40
+ for (const agent of flat(aigne.cli.agents, aigne.agents, aigne.skills, aigne.cli.chat, aigne.mcpServer.agents)) {
41
+ subYargs.command(agentCommandModule({ dir: path, agent }));
149
42
  }
43
+ const argv = process.argv.slice(aigneFilePath ? 3 : 2);
44
+ if (argv[0] === "run")
45
+ argv.shift(); // remove 'run' command
46
+ // For compatibility with old `run` command like: `aigne run --path /xx/xx --entry-agent xx --xx`
47
+ if (argv[0] === "--path" || argv[0] === "--url")
48
+ argv.shift(); // remove --path flag
49
+ if (argv[0] === options.path)
50
+ argv.shift(); // remove path/url args
51
+ if (argv[0] === "--entry-agent")
52
+ argv.shift();
53
+ await subYargs
54
+ .strict()
55
+ .demandCommand()
56
+ .alias("h", "help")
57
+ .alias("v", "version")
58
+ .fail((message, error, yargs) => {
59
+ // We catch all errors below, here just print the help message non-error case like demandCommand
60
+ if (!error) {
61
+ yargs.showHelp();
62
+ console.error(`\n${message}`);
63
+ process.exit(1);
64
+ }
65
+ })
66
+ .parseAsync(argv);
150
67
  },
151
68
  };
152
69
  }
70
+ async function loadApplication(path) {
71
+ const { cacheDir, dir } = prepareDirs(path);
72
+ if (cacheDir) {
73
+ await new Listr([
74
+ {
75
+ title: "Download package",
76
+ task: () => downloadPackage(path, cacheDir),
77
+ },
78
+ {
79
+ title: "Extract package",
80
+ task: () => extractPackage(cacheDir, dir),
81
+ },
82
+ ], {
83
+ rendererOptions: {
84
+ collapseSubtasks: false,
85
+ showErrorMessage: false,
86
+ timer: PRESET_TIMER,
87
+ },
88
+ }).run();
89
+ }
90
+ // Load env files in the aigne directory
91
+ config({ path: dir, silent: true });
92
+ const aigne = await loadAIGNE({ path: dir });
93
+ return { aigne, path: dir };
94
+ }
153
95
  async function downloadPackage(url, cacheDir) {
154
96
  await rm(cacheDir, { recursive: true, force: true });
155
97
  await mkdir(cacheDir, { recursive: true });
@@ -164,18 +106,12 @@ async function extractPackage(cacheDir, dir) {
164
106
  await cp(cacheDir, dir, { recursive: true, force: true });
165
107
  }
166
108
  }
167
- function prepareDirs(path, options) {
109
+ function prepareDirs(path) {
168
110
  let dir;
169
111
  let cacheDir;
170
112
  if (!path.startsWith("http")) {
171
113
  dir = isAbsolute(path) ? path : resolve(process.cwd(), path);
172
114
  }
173
- else if (options.cacheDir) {
174
- dir = isAbsolute(options.cacheDir)
175
- ? options.cacheDir
176
- : resolve(process.cwd(), options.cacheDir);
177
- cacheDir = join(dir, ".download");
178
- }
179
115
  else {
180
116
  dir = getLocalPackagePathFromUrl(path);
181
117
  cacheDir = getLocalPackagePathFromUrl(path, { subdir: ".download" });
@@ -5,6 +5,7 @@ import { type Listr } from "@aigne/listr2";
5
5
  import { type AIGNEListrTaskWrapper } from "../utils/listr.js";
6
6
  export interface TerminalTracerOptions {
7
7
  outputKey?: string;
8
+ fileOutputKey?: string;
8
9
  }
9
10
  export declare class TerminalTracer {
10
11
  readonly context: Context;
@@ -31,12 +32,15 @@ export declare class TerminalTracer {
31
32
  }): Promise<string>;
32
33
  private marked;
33
34
  get outputKey(): string;
35
+ get dataOutputKey(): string;
34
36
  formatRequest(agent: Agent, _context: Context, m?: Message, { running }?: {
35
37
  running?: boolean | undefined;
36
38
  }): string | undefined;
37
- formatResult(agent: Agent, context: Context, m?: Message, { running }?: {
39
+ formatResult(agent: Agent, context: Context, m?: Message, { running, renderImage }?: {
38
40
  running?: boolean | undefined;
39
- }): string;
41
+ renderImage?: boolean | undefined;
42
+ }): string | Promise<string>;
43
+ formatResultData(output: Message): Promise<string | undefined>;
40
44
  protected runningInspectOptions: InspectOptions;
41
45
  }
42
46
  type Task = ReturnType<typeof promiseWithResolvers<void>> & {
@@ -1,6 +1,6 @@
1
1
  import { EOL } from "node:os";
2
2
  import { inspect } from "node:util";
3
- import { AIAgent, ChatModel, DEFAULT_OUTPUT_KEY, mergeContextUsage, newEmptyContextUsage, UserAgent, } from "@aigne/core";
3
+ import { AIAgent, ChatModel, DEFAULT_FILE_OUTPUT_KEY, DEFAULT_OUTPUT_KEY, mergeContextUsage, newEmptyContextUsage, UserAgent, } from "@aigne/core";
4
4
  import { promiseWithResolvers } from "@aigne/core/utils/promise.js";
5
5
  import { flat, omit } from "@aigne/core/utils/type-utils.js";
6
6
  import { figures } from "@aigne/listr2";
@@ -8,6 +8,9 @@ import { markedTerminal } from "@aigne/marked-terminal";
8
8
  import * as prompts from "@inquirer/prompts";
9
9
  import chalk from "chalk";
10
10
  import { Marked } from "marked";
11
+ import terminalImage from "terminal-image";
12
+ import terminalLink from "terminal-link";
13
+ import { withProtocol } from "ufo";
11
14
  import { AIGNE_HUB_CREDITS_NOT_ENOUGH_ERROR_TYPE } from "../constants.js";
12
15
  import checkbox from "../utils/inquirer/checkbox.js";
13
16
  import { AIGNEListr, AIGNEListrRenderer } from "../utils/listr.js";
@@ -27,7 +30,7 @@ export class TerminalTracer {
27
30
  const context = this.context.newContext({ reset: true });
28
31
  const listr = new AIGNEListr({
29
32
  formatRequest: (options) => this.formatRequest(agent, context, input, options),
30
- formatResult: (result, options) => [this.formatResult(agent, context, result, options)].filter(Boolean),
33
+ formatResult: (result, options) => this.formatResult(agent, context, result, options),
31
34
  }, [], { concurrent: true, exitOnError: false });
32
35
  this.listr = listr;
33
36
  const collapsedMap = new Map();
@@ -228,7 +231,9 @@ export class TerminalTracer {
228
231
  });
229
232
  return retry;
230
233
  })();
231
- return this.buyCreditsPromptPromise.finally(() => {
234
+ return this.buyCreditsPromptPromise
235
+ .catch(() => "exit")
236
+ .finally(() => {
232
237
  // Clear the promise so that we can show the prompt again if needed
233
238
  this.buyCreditsPromptPromise = undefined;
234
239
  });
@@ -282,6 +287,9 @@ export class TerminalTracer {
282
287
  get outputKey() {
283
288
  return this.options.outputKey || DEFAULT_OUTPUT_KEY;
284
289
  }
290
+ get dataOutputKey() {
291
+ return this.options.fileOutputKey || DEFAULT_FILE_OUTPUT_KEY;
292
+ }
285
293
  formatRequest(agent, _context, m = {}, { running = false } = {}) {
286
294
  const prefix = `${chalk.grey(figures.pointer)} 💬 `;
287
295
  const inputKey = agent instanceof AIAgent ? agent.inputKey : undefined;
@@ -296,12 +304,12 @@ export class TerminalTracer {
296
304
  return undefined;
297
305
  return `${prefix}${r}`;
298
306
  }
299
- formatResult(agent, context, m = {}, { running = false } = {}) {
307
+ formatResult(agent, context, m = {}, { running = false, renderImage = false } = {}) {
300
308
  const { isTTY } = process.stdout;
301
309
  const outputKey = this.outputKey || (agent instanceof AIAgent ? agent.outputKey : undefined);
302
310
  const prefix = `${chalk.grey(figures.tick)} 🤖 ${this.formatTokenUsage(context.usage)}`;
303
311
  const msg = outputKey ? m[outputKey] : undefined;
304
- const message = outputKey ? omit(m, outputKey) : m;
312
+ const message = outputKey ? omit(m, outputKey, this.dataOutputKey) : m;
305
313
  const text = msg && typeof msg === "string"
306
314
  ? isTTY
307
315
  ? this.marked.parse(msg, { async: false }).trim()
@@ -310,8 +318,43 @@ export class TerminalTracer {
310
318
  const json = Object.keys(message).length > 0
311
319
  ? inspect(message, { colors: isTTY, ...(running ? this.runningInspectOptions : undefined) })
312
320
  : undefined;
321
+ if (renderImage) {
322
+ return this.formatResultData(m).then((images) => {
323
+ return [prefix, text, images, json].filter(Boolean).join(EOL.repeat(2));
324
+ });
325
+ }
313
326
  return [prefix, text, json].filter(Boolean).join(EOL.repeat(2));
314
327
  }
328
+ async formatResultData(output) {
329
+ const data = output[this.dataOutputKey];
330
+ if (!Array.isArray(data))
331
+ return;
332
+ const options = {
333
+ height: 30,
334
+ };
335
+ return (await Promise.all(data.map(async (item) => {
336
+ const image = item.type === "local"
337
+ ? await terminalImage.file(item.path, options)
338
+ : item.type === "file"
339
+ ? await terminalImage.buffer(Buffer.from(item.data, "base64"), options)
340
+ : undefined;
341
+ const link = item.type === "local"
342
+ ? withProtocol(item.path, "file://")
343
+ : item.type === "url"
344
+ ? item.url
345
+ : undefined;
346
+ const text = [
347
+ link ? chalk.cyan(terminalLink(link, link)) : undefined,
348
+ item.filename,
349
+ item.mimeType ? chalk.gray(item.mimeType) : undefined,
350
+ ]
351
+ .filter(Boolean)
352
+ .join(" ");
353
+ return [image, text].filter(Boolean).join(EOL);
354
+ })))
355
+ .filter(Boolean)
356
+ .join(EOL);
357
+ }
315
358
  runningInspectOptions = {
316
359
  maxArrayLength: 3,
317
360
  maxStringLength: 200,
@@ -18,12 +18,16 @@ export async function toAIGNEPackage(src, dst) {
18
18
  name: "gpt-4o-mini", // TODO: get from config
19
19
  },
20
20
  agents: [],
21
+ cli: {
22
+ agents: [],
23
+ },
21
24
  };
22
25
  for (const agent of definition.agents) {
23
26
  const { content } = await assistantToAIGNEV2(agent, definition);
24
27
  const filename = getAgentFilename(agent);
25
28
  await writeFile(join(dst, filename), content);
26
29
  aigne.agents.push(filename);
30
+ aigne.cli.agents.push(filename);
27
31
  }
28
32
  await writeFile(join(dst, "aigne.yaml"), stringify(aigne));
29
33
  }
@@ -1,5 +1,5 @@
1
1
  import { AIGNE_HUB_DEFAULT_MODEL, findModel } from "@aigne/aigne-hub";
2
- import { flat } from "@aigne/core/utils/type-utils.js";
2
+ import { flat, pick } from "@aigne/core/utils/type-utils.js";
3
3
  import chalk from "chalk";
4
4
  import inquirer from "inquirer";
5
5
  import { AIGNE_HUB_PROVIDER } from "./constants.js";
@@ -55,10 +55,13 @@ export async function loadChatModel(options) {
55
55
  inquirer.prompt);
56
56
  const params = {
57
57
  model,
58
- temperature: options?.temperature,
59
- topP: options?.topP,
60
- frequencyPenalty: options?.frequencyPenalty,
61
- presencePenalty: options?.presencePenalty,
58
+ ...pick(options ?? {}, [
59
+ "modalities",
60
+ "temperature",
61
+ "topP",
62
+ "frequencyPenalty",
63
+ "presencePenalty",
64
+ ]),
62
65
  };
63
66
  const { match, all } = findModel(provider);
64
67
  if (!match) {
@@ -8,7 +8,8 @@ export declare class AIGNEListr extends Listr<object, typeof AIGNEListrRenderer,
8
8
  formatRequest: () => string | undefined;
9
9
  formatResult: (result: Message, options?: {
10
10
  running?: boolean;
11
- }) => string[];
11
+ renderImage?: boolean;
12
+ }) => string | Promise<string>;
12
13
  };
13
14
  private result;
14
15
  private error?;
@@ -18,7 +19,8 @@ export declare class AIGNEListr extends Listr<object, typeof AIGNEListrRenderer,
18
19
  formatRequest: () => string | undefined;
19
20
  formatResult: (result: Message, options?: {
20
21
  running?: boolean;
21
- }) => string[];
22
+ renderImage?: boolean;
23
+ }) => string | Promise<string>;
22
24
  }, ...[task, options, parentTask]: ConstructorParameters<typeof Listr<object, typeof AIGNEListrRenderer, typeof AIGNEListrFallbackRenderer>>);
23
25
  run(stream: () => PromiseOrValue<AgentResponseStream<Message>>): Promise<Message>;
24
26
  private extractStream;
@@ -16,7 +16,12 @@ export class AIGNEListr extends Listr {
16
16
  return this.logs.splice(0);
17
17
  },
18
18
  getBottomBarLogs: (options) => {
19
- return this.myOptions.formatResult(this.result, options);
19
+ if (!options?.running)
20
+ return [];
21
+ const r = this.myOptions.formatResult(this.result);
22
+ if (typeof r !== "string")
23
+ throw new Error("Must return a string result for running task");
24
+ return [r];
20
25
  },
21
26
  };
22
27
  super(task, {
@@ -71,11 +76,13 @@ export class AIGNEListr extends Listr {
71
76
  }
72
77
  const _stream = await stream();
73
78
  this.add({ task: () => this.extractStream(_stream) });
74
- return await super.run().then(() => {
79
+ const result = await super.run().then(() => {
75
80
  if (this.error)
76
81
  throw this.error;
77
82
  return { ...this.result };
78
83
  });
84
+ console.log(await this.myOptions.formatResult(this.result, { running: false, renderImage: true }));
85
+ return result;
79
86
  }
80
87
  finally {
81
88
  logger.logMessage = originalLog;
@@ -1,7 +1,7 @@
1
1
  import { AIGNE, type ChatModelOptions } from "@aigne/core";
2
2
  import type { LoadCredentialOptions } from "./aigne-hub/type.js";
3
- import type { RunAIGNECommandOptions } from "./run-with-aigne.js";
4
- export interface RunOptions extends RunAIGNECommandOptions {
3
+ import type { AgentRunCommonOptions } from "./yargs.js";
4
+ export interface RunOptions extends AgentRunCommonOptions {
5
5
  path: string;
6
6
  entryAgent?: string;
7
7
  cacheDir?: string;
@@ -1,10 +1,13 @@
1
- import type { Message, UserAgent } from "@aigne/core";
1
+ import { type Message, type UserAgent } from "@aigne/core";
2
2
  export declare const DEFAULT_CHAT_INPUT_KEY = "message";
3
3
  export interface ChatLoopOptions {
4
4
  initialCall?: Message | string;
5
5
  welcome?: string;
6
6
  defaultQuestion?: string;
7
7
  inputKey?: string;
8
+ fileInputKey?: string;
8
9
  outputKey?: string;
10
+ dataOutputKey?: string;
11
+ input?: Message;
9
12
  }
10
13
  export declare function runChatLoopInTerminal(userAgent: UserAgent<any, any>, options?: ChatLoopOptions): Promise<void>;
@@ -1,3 +1,7 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename } from "node:path";
3
+ import { ChatModel } from "@aigne/core";
4
+ import { isNonNullable, omit } from "@aigne/core/utils/type-utils.js";
1
5
  import inquirer from "inquirer";
2
6
  import { TerminalTracer } from "../tracer/terminal.js";
3
7
  export const DEFAULT_CHAT_INPUT_KEY = "message";
@@ -7,7 +11,10 @@ export async function runChatLoopInTerminal(userAgent, options = {}) {
7
11
  if (options?.welcome)
8
12
  console.log(options.welcome);
9
13
  if (initialCall) {
10
- await callAgent(userAgent, initialCall, { ...options });
14
+ await callAgent(userAgent, initialCall, options);
15
+ if (options.input && options.fileInputKey) {
16
+ options.input = omit(options.input, options.fileInputKey);
17
+ }
11
18
  }
12
19
  for (let i = 0;; i++) {
13
20
  prompt = inquirer.prompt([
@@ -36,12 +43,50 @@ export async function runChatLoopInTerminal(userAgent, options = {}) {
36
43
  break;
37
44
  continue;
38
45
  }
39
- await callAgent(userAgent, question, { ...options });
46
+ const input = {};
47
+ if (options.fileInputKey) {
48
+ const { message, files } = await extractFilesFromQuestion(question);
49
+ input[options.inputKey || DEFAULT_CHAT_INPUT_KEY] = message;
50
+ input[options.fileInputKey] = files;
51
+ }
52
+ else {
53
+ input[options.inputKey || DEFAULT_CHAT_INPUT_KEY] = question;
54
+ }
55
+ await callAgent(userAgent, input, options);
56
+ if (options.input && options.fileInputKey) {
57
+ options.input = omit(options.input, options.fileInputKey);
58
+ }
59
+ }
60
+ }
61
+ async function extractFilesFromQuestion(question) {
62
+ const fileRegex = /@\S+/g;
63
+ const paths = question.match(fileRegex) || [];
64
+ const files = (await Promise.all(paths.map(async (path) => {
65
+ const p = path.slice(1);
66
+ const filename = basename(p);
67
+ const data = await readFile(p, "base64").catch((error) => {
68
+ if (error.code === "ENOENT")
69
+ return null;
70
+ throw error;
71
+ });
72
+ if (!data)
73
+ return;
74
+ return {
75
+ path,
76
+ file: { type: "file", data, filename, mimeType: ChatModel.getMimeType(filename) },
77
+ };
78
+ }))).filter(isNonNullable);
79
+ // Remove file paths from question
80
+ for (const { path } of files) {
81
+ question = question.replaceAll(path, "");
40
82
  }
83
+ return { message: question, files: files.map((i) => i.file) };
41
84
  }
42
85
  async function callAgent(userAgent, input, options) {
43
86
  const tracer = new TerminalTracer(userAgent.context, options);
44
- await tracer.run(userAgent, typeof input === "string" ? { [options.inputKey || DEFAULT_CHAT_INPUT_KEY]: input } : input);
87
+ await tracer.run(userAgent, typeof input === "string"
88
+ ? { ...options.input, [options.inputKey || DEFAULT_CHAT_INPUT_KEY]: input }
89
+ : { ...options.input, ...input });
45
90
  }
46
91
  const COMMANDS = {
47
92
  "/exit": () => ({ exit: true }),
@@ -1,50 +1,8 @@
1
1
  import { type Agent, type AIGNE, type ChatModelOptions, type Message } from "@aigne/core";
2
- import { LogLevel } from "@aigne/core/utils/logger.js";
3
2
  import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
4
- import type { Argv } from "yargs";
5
3
  import { type ChatLoopOptions } from "./run-chat-loop.js";
6
- export interface RunAIGNECommandOptions {
7
- chat?: boolean;
8
- model?: string;
9
- temperature?: number;
10
- topP?: number;
11
- presencePenalty?: number;
12
- frequencyPenalty?: number;
13
- input?: string[];
14
- format?: "text" | "json" | "yaml";
15
- output?: string;
16
- outputKey?: string;
17
- logLevel?: LogLevel;
18
- force?: boolean;
19
- }
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
- } & {
45
- "aigne-hub-url": string | undefined;
46
- }>;
47
- export declare function parseAgentInputByCommander(agent: Agent, options?: RunAIGNECommandOptions & {
4
+ import { type AgentRunCommonOptions } from "./yargs.js";
5
+ export declare function parseAgentInputByCommander(agent: Agent, options?: AgentRunCommonOptions & {
48
6
  inputKey?: string;
49
7
  argv?: string[];
50
8
  defaultInput?: string | Message;
@@ -55,11 +13,11 @@ export declare function runWithAIGNE(agentCreator: ((aigne: AIGNE) => PromiseOrV
55
13
  modelOptions?: ChatModelOptions;
56
14
  outputKey?: string;
57
15
  }): Promise<void>;
58
- export declare function runAgentWithAIGNE(aigne: AIGNE, agent: Agent, { outputKey, chatLoopOptions, modelOptions, ...options }?: {
16
+ export declare function runAgentWithAIGNE(aigne: AIGNE, agent: Agent, { outputKey, fileOutputKey, chatLoopOptions, ...options }?: {
59
17
  outputKey?: string;
18
+ fileOutputKey?: string;
60
19
  chatLoopOptions?: ChatLoopOptions;
61
- modelOptions?: ChatModelOptions;
62
20
  input?: Message;
63
- } & Omit<RunAIGNECommandOptions, "input">): Promise<{
21
+ } & Omit<AgentRunCommonOptions, "input">): Promise<{
64
22
  result: Message;
65
23
  } | undefined>;
@@ -2,89 +2,16 @@ 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 } from "@aigne/aigne-hub";
6
- import { DEFAULT_OUTPUT_KEY, UserAgent, } from "@aigne/core";
7
- import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
8
- import { isEmpty, isNil, omitBy, pick, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
5
+ import { AIAgent, DEFAULT_OUTPUT_KEY, UserAgent, } from "@aigne/core";
6
+ import { logger } from "@aigne/core/utils/logger.js";
7
+ import { isEmpty, isNil, omitBy, pick } from "@aigne/core/utils/type-utils.js";
9
8
  import chalk from "chalk";
10
9
  import yargs from "yargs";
11
10
  import { hideBin } from "yargs/helpers";
12
- import { ZodError, z } from "zod";
13
11
  import { TerminalTracer } from "../tracer/terminal.js";
14
12
  import { loadAIGNE } from "./load-aigne.js";
15
13
  import { DEFAULT_CHAT_INPUT_KEY, runChatLoopInTerminal, } from "./run-chat-loop.js";
16
- import { parseAgentInput, withAgentInputSchema } from "./yargs.js";
17
- export const createRunAIGNECommand = (yargs) => yargs
18
- .option("chat", {
19
- describe: "Run chat loop in terminal",
20
- type: "boolean",
21
- default: false,
22
- })
23
- .option("model", {
24
- describe: `AI model to use in format 'provider[:model]' where model is optional. Examples: 'openai' or 'openai:gpt-4o-mini'. Available providers: ${availableModels()
25
- .map((i) => {
26
- if (typeof i.name === "string") {
27
- return i.name.toLowerCase().replace(/ChatModel$/i, "");
28
- }
29
- return i.name.map((n) => n.toLowerCase().replace(/ChatModel$/i, ""));
30
- })
31
- .join(", ")} (default: openai)`,
32
- type: "string",
33
- })
34
- .option("temperature", {
35
- describe: "Temperature for the model (controls randomness, higher values produce more random outputs). Range: 0.0-2.0",
36
- type: "number",
37
- coerce: customZodError("--temperature", (s) => z.coerce.number().min(0).max(2).parse(s)),
38
- })
39
- .option("top-p", {
40
- describe: "Top P (nucleus sampling) parameter for the model (controls diversity). Range: 0.0-1.0",
41
- type: "number",
42
- coerce: customZodError("--top-p", (s) => z.coerce.number().min(0).max(1).parse(s)),
43
- })
44
- .option("presence-penalty", {
45
- describe: "Presence penalty for the model (penalizes repeating the same tokens). Range: -2.0 to 2.0",
46
- type: "number",
47
- coerce: customZodError("--presence-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
48
- })
49
- .option("frequency-penalty", {
50
- describe: "Frequency penalty for the model (penalizes frequency of token usage). Range: -2.0 to 2.0",
51
- type: "number",
52
- coerce: customZodError("--frequency-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
53
- })
54
- .option("input", {
55
- describe: "Input to the agent, use @<file> to read from a file",
56
- type: "array",
57
- alias: "i",
58
- })
59
- .option("format", {
60
- describe: "Input format for the agent (available: text, json, yaml default: text)",
61
- type: "string",
62
- })
63
- .option("output", {
64
- describe: "Output file to save the result (default: stdout)",
65
- type: "string",
66
- alias: "o",
67
- })
68
- .option("output-key", {
69
- describe: "Key in the result to save to the output file",
70
- type: "string",
71
- default: DEFAULT_OUTPUT_KEY,
72
- })
73
- .option("force", {
74
- describe: "Truncate the output file if it exists, and create directory if the output path is not exists",
75
- type: "boolean",
76
- default: false,
77
- })
78
- .option("log-level", {
79
- describe: `Log level for detailed debugging information. Values: ${Object.values(LogLevel).join(", ")}`,
80
- type: "string",
81
- default: getLevelFromEnv(logger.options.ns) || LogLevel.SILENT,
82
- coerce: customZodError("--log-level", (s) => z.nativeEnum(LogLevel).parse(s)),
83
- })
84
- .option("aigne-hub-url", {
85
- describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models. ",
86
- type: "string",
87
- });
14
+ import { parseAgentInput, withAgentInputSchema, withRunAgentCommonOptions, } from "./yargs.js";
88
15
  export async function parseAgentInputByCommander(agent, options = {}) {
89
16
  const args = await withAgentInputSchema(yargs(), agent)
90
17
  .showHelpOnFail(false)
@@ -94,14 +21,18 @@ export async function parseAgentInputByCommander(agent, options = {}) {
94
21
  if (isEmpty(input)) {
95
22
  const defaultInput = options.defaultInput || process.env.INITIAL_CALL;
96
23
  Object.assign(input, typeof defaultInput === "string"
97
- ? { [options?.inputKey || DEFAULT_CHAT_INPUT_KEY]: defaultInput }
24
+ ? {
25
+ [options?.inputKey ||
26
+ (agent instanceof AIAgent ? agent.inputKey : undefined) ||
27
+ DEFAULT_CHAT_INPUT_KEY]: defaultInput,
28
+ }
98
29
  : defaultInput);
99
30
  }
100
31
  return input;
101
32
  }
102
33
  export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoopOptions, modelOptions, outputKey, } = {}) {
103
34
  await yargs()
104
- .command("$0", "Run an agent with AIGNE", (yargs) => createRunAIGNECommand(yargs), async (options) => {
35
+ .command("$0", "Execute an AI agent using the AIGNE framework with specified configuration", (yargs) => withRunAgentCommonOptions(yargs), async (options) => {
105
36
  if (options.logLevel) {
106
37
  logger.level = options.logLevel;
107
38
  }
@@ -122,7 +53,6 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
122
53
  ...options,
123
54
  outputKey: outputKey || options.outputKey,
124
55
  chatLoopOptions,
125
- modelOptions,
126
56
  input,
127
57
  });
128
58
  }
@@ -132,16 +62,19 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
132
62
  })
133
63
  .alias("h", "help")
134
64
  .alias("v", "version")
65
+ .fail((message, error, yargs) => {
66
+ if (!error)
67
+ yargs.showHelp();
68
+ console.error(`\n${message || error?.message}`);
69
+ process.exit(1);
70
+ })
135
71
  .parseAsync(hideBin(argv))
136
72
  .catch((error) => {
137
73
  console.error(`${chalk.red("Error:")} ${error.message}`);
138
74
  process.exit(1);
139
75
  });
140
76
  }
141
- function customZodError(label, fn) {
142
- return ((...args) => tryOrThrow(() => fn(...args), (e) => new Error(`${label} ${e instanceof ZodError ? e.issues[0]?.message : e.message}`)));
143
- }
144
- export async function runAgentWithAIGNE(aigne, agent, { outputKey, chatLoopOptions, modelOptions, ...options } = {}) {
77
+ export async function runAgentWithAIGNE(aigne, agent, { outputKey, fileOutputKey, chatLoopOptions, ...options } = {}) {
145
78
  if (options.output) {
146
79
  const outputPath = isAbsolute(options.output)
147
80
  ? options.output
@@ -167,10 +100,12 @@ export async function runAgentWithAIGNE(aigne, agent, { outputKey, chatLoopOptio
167
100
  await runChatLoopInTerminal(userAgent, {
168
101
  ...chatLoopOptions,
169
102
  outputKey,
103
+ fileInputKey: agent instanceof AIAgent ? agent.fileInputKey : undefined,
104
+ input: options.input,
170
105
  });
171
106
  return;
172
107
  }
173
- const tracer = new TerminalTracer(aigne.newContext(), { outputKey });
108
+ const tracer = new TerminalTracer(aigne.newContext(), { outputKey, fileOutputKey });
174
109
  const { result } = await tracer.run(agent, options.input ?? {});
175
110
  if (options.output) {
176
111
  const message = result[outputKey || DEFAULT_OUTPUT_KEY];
@@ -1,6 +1,47 @@
1
1
  import { type Agent, type Message } from "@aigne/core";
2
+ import { LogLevel } from "@aigne/core/utils/logger.js";
2
3
  import type { Argv } from "yargs";
3
4
  import { ZodType } from "zod";
5
+ export type InferArgv<T> = T extends Argv<infer U> ? U : never;
6
+ export declare const withRunAgentCommonOptions: (yargs: Argv) => Argv<{
7
+ chat: boolean;
8
+ } & {
9
+ model: string | undefined;
10
+ } & {
11
+ temperature: number | undefined;
12
+ } & {
13
+ "top-p": number | undefined;
14
+ } & {
15
+ "presence-penalty": number | undefined;
16
+ } & {
17
+ "frequency-penalty": number | undefined;
18
+ } & {
19
+ input: string[] | undefined;
20
+ } & {
21
+ "input-file": string[] | undefined;
22
+ } & {
23
+ format: string | undefined;
24
+ } & {
25
+ output: string | undefined;
26
+ } & {
27
+ "output-key": string;
28
+ } & {
29
+ force: boolean;
30
+ } & {
31
+ "log-level": LogLevel;
32
+ } & {
33
+ "aigne-hub-url": string | undefined;
34
+ }>;
35
+ type _AgentRunCommonOptions = Partial<InferArgv<ReturnType<typeof withAgentInputSchema>>>;
36
+ /** Convert literal string types like 'foo-bar' to 'FooBar' */
37
+ type PascalCase<S extends string> = string extends S ? string : S extends `${infer T}-${infer U}` ? `${Capitalize<T>}${PascalCase<U>}` : Capitalize<S>;
38
+ /** Convert literal string types like 'foo-bar' to 'fooBar' */
39
+ type CamelCase<S extends string> = string extends S ? string : S extends `${infer T}-${infer U}` ? `${T}${PascalCase<U>}` : S;
40
+ /** Convert literal string types like 'foo-bar' to 'fooBar', allowing all `PropertyKey` types */
41
+ type CamelCaseKey<K extends PropertyKey> = K extends string ? Exclude<CamelCase<K>, ""> : K;
42
+ export type AgentRunCommonOptions = {
43
+ [key in keyof _AgentRunCommonOptions as CamelCaseKey<key>]: _AgentRunCommonOptions[key];
44
+ };
4
45
  export declare function inferZodType(type: ZodType, opts?: {
5
46
  array?: boolean;
6
47
  optional?: boolean;
@@ -10,11 +51,34 @@ export declare function inferZodType(type: ZodType, opts?: {
10
51
  optional?: boolean;
11
52
  };
12
53
  export declare function withAgentInputSchema(yargs: Argv, agent: Agent): Argv<{
13
- input?: string[];
14
- format?: "json" | "yaml";
54
+ chat: boolean;
55
+ } & {
56
+ model: string | undefined;
57
+ } & {
58
+ temperature: number | undefined;
59
+ } & {
60
+ "top-p": number | undefined;
61
+ } & {
62
+ "presence-penalty": number | undefined;
63
+ } & {
64
+ "frequency-penalty": number | undefined;
65
+ } & {
66
+ input: string[] | undefined;
67
+ } & {
68
+ "input-file": string[] | undefined;
69
+ } & {
70
+ format: string | undefined;
71
+ } & {
72
+ output: string | undefined;
73
+ } & {
74
+ "output-key": string;
75
+ } & {
76
+ force: boolean;
77
+ } & {
78
+ "log-level": LogLevel;
79
+ } & {
80
+ "aigne-hub-url": string | undefined;
15
81
  }>;
16
- export declare function parseAgentInput(i: Message & {
17
- input?: string[];
18
- format?: "json" | "yaml";
19
- }, agent: Agent): Promise<any>;
82
+ export declare function parseAgentInput(i: Message & AgentRunCommonOptions, agent: Agent): Promise<any>;
20
83
  export declare function stdinHasData(): Promise<boolean>;
84
+ export {};
@@ -1,12 +1,99 @@
1
1
  import { fstat } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
- import { extname } from "node:path";
3
+ import { basename, extname } from "node:path";
4
4
  import { isatty } from "node:tty";
5
5
  import { promisify } from "node:util";
6
- import { AIAgent, readAllString } from "@aigne/core";
7
- import { pick } from "@aigne/core/utils/type-utils.js";
6
+ import { availableModels } from "@aigne/aigne-hub";
7
+ import { AIAgent, ChatModel, DEFAULT_OUTPUT_KEY, readAllString, } from "@aigne/core";
8
+ import { getLevelFromEnv, LogLevel, logger } from "@aigne/core/utils/logger.js";
9
+ import { pick, tryOrThrow } from "@aigne/core/utils/type-utils.js";
8
10
  import { parse } from "yaml";
9
- import { ZodAny, ZodArray, ZodBoolean, ZodNumber, ZodObject, ZodString, ZodType, ZodUnknown, } from "zod";
11
+ import z, { ZodAny, ZodArray, ZodBoolean, ZodError, ZodNumber, ZodObject, ZodString, ZodType, ZodUnknown, } from "zod";
12
+ const MODEL_OPTIONS_GROUP_NAME = "Model Options";
13
+ export const withRunAgentCommonOptions = (yargs) => yargs
14
+ .option("chat", {
15
+ describe: "Run chat loop in terminal",
16
+ type: "boolean",
17
+ default: false,
18
+ })
19
+ .option("model", {
20
+ group: MODEL_OPTIONS_GROUP_NAME,
21
+ describe: `AI model to use in format 'provider[:model]' where model is optional. Examples: 'openai' or 'openai:gpt-4o-mini'. Available providers: ${availableModels()
22
+ .map((i) => {
23
+ if (typeof i.name === "string") {
24
+ return i.name.toLowerCase().replace(/ChatModel$/i, "");
25
+ }
26
+ return i.name.map((n) => n.toLowerCase().replace(/ChatModel$/i, ""));
27
+ })
28
+ .join(", ")} (default: openai)`,
29
+ type: "string",
30
+ })
31
+ .option("temperature", {
32
+ group: MODEL_OPTIONS_GROUP_NAME,
33
+ describe: "Temperature for the model (controls randomness, higher values produce more random outputs). Range: 0.0-2.0",
34
+ type: "number",
35
+ coerce: customZodError("--temperature", (s) => z.coerce.number().min(0).max(2).parse(s)),
36
+ })
37
+ .option("top-p", {
38
+ group: MODEL_OPTIONS_GROUP_NAME,
39
+ describe: "Top P (nucleus sampling) parameter for the model (controls diversity). Range: 0.0-1.0",
40
+ type: "number",
41
+ coerce: customZodError("--top-p", (s) => z.coerce.number().min(0).max(1).parse(s)),
42
+ })
43
+ .option("presence-penalty", {
44
+ group: MODEL_OPTIONS_GROUP_NAME,
45
+ describe: "Presence penalty for the model (penalizes repeating the same tokens). Range: -2.0 to 2.0",
46
+ type: "number",
47
+ coerce: customZodError("--presence-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
48
+ })
49
+ .option("frequency-penalty", {
50
+ group: MODEL_OPTIONS_GROUP_NAME,
51
+ describe: "Frequency penalty for the model (penalizes frequency of token usage). Range: -2.0 to 2.0",
52
+ type: "number",
53
+ coerce: customZodError("--frequency-penalty", (s) => z.coerce.number().min(-2).max(2).parse(s)),
54
+ })
55
+ .option("input", {
56
+ describe: "Input to the agent, use @<file> to read from a file",
57
+ type: "string",
58
+ array: true,
59
+ alias: "i",
60
+ })
61
+ .option("input-file", {
62
+ describe: "Input files to the agent",
63
+ type: "string",
64
+ array: true,
65
+ })
66
+ .option("format", {
67
+ describe: "Input format for the agent (available: text, json, yaml default: text)",
68
+ type: "string",
69
+ choices: ["text", "json", "yaml"],
70
+ })
71
+ .option("output", {
72
+ describe: "Output file to save the result (default: stdout)",
73
+ type: "string",
74
+ alias: "o",
75
+ })
76
+ .option("output-key", {
77
+ describe: "Key in the result to save to the output file",
78
+ type: "string",
79
+ default: DEFAULT_OUTPUT_KEY,
80
+ })
81
+ .option("force", {
82
+ describe: "Truncate the output file if it exists, and create directory if the output path does not exists",
83
+ type: "boolean",
84
+ default: false,
85
+ })
86
+ .option("log-level", {
87
+ describe: `Log level for detailed debugging information. Values: ${Object.values(LogLevel).join(", ")}`,
88
+ type: "string",
89
+ default: getLevelFromEnv(logger.options.ns) || LogLevel.SILENT,
90
+ coerce: customZodError("--log-level", (s) => z.nativeEnum(LogLevel).parse(s)),
91
+ })
92
+ .option("aigne-hub-url", {
93
+ group: MODEL_OPTIONS_GROUP_NAME,
94
+ describe: "Custom AIGNE Hub service URL. Used to fetch remote agent definitions or models.",
95
+ type: "string",
96
+ });
10
97
  export function inferZodType(type, opts = {}) {
11
98
  if (type instanceof ZodUnknown || type instanceof ZodAny) {
12
99
  return { type: "string", optional: true };
@@ -30,6 +117,7 @@ export function withAgentInputSchema(yargs, agent) {
30
117
  for (const [option, config] of Object.entries(inputSchema)) {
31
118
  const type = inferZodType(config);
32
119
  yargs.option(option, {
120
+ group: "Agent Parameters",
33
121
  type: type.type,
34
122
  description: config.description,
35
123
  array: type.array,
@@ -38,18 +126,7 @@ export function withAgentInputSchema(yargs, agent) {
38
126
  yargs.demandOption(option);
39
127
  }
40
128
  }
41
- return yargs
42
- .option("input", {
43
- type: "string",
44
- array: true,
45
- description: "Input to the agent, use @<file> to read from a file",
46
- alias: ["i"],
47
- })
48
- .option("format", {
49
- type: "string",
50
- description: 'Input format, can be "json" or "yaml"',
51
- choices: ["json", "yaml"],
52
- });
129
+ return withRunAgentCommonOptions(yargs);
53
130
  }
54
131
  export async function parseAgentInput(i, agent) {
55
132
  const inputSchema = agent.inputSchema instanceof ZodObject ? agent.inputSchema.shape : {};
@@ -62,6 +139,16 @@ export async function parseAgentInput(i, agent) {
62
139
  }
63
140
  return [key, val];
64
141
  })));
142
+ if (agent instanceof AIAgent && agent.fileInputKey) {
143
+ const files = [];
144
+ for (const file of i.inputFile ?? []) {
145
+ const raw = await readFile(file.replace(/^@/, ""), "base64");
146
+ const filename = basename(file);
147
+ const mimeType = ChatModel.getMimeType(filename) || "application/octet-stream";
148
+ files.push({ type: "file", data: raw, filename, mimeType });
149
+ }
150
+ Object.assign(input, { [agent.fileInputKey]: files });
151
+ }
65
152
  const rawInput = i.input ||
66
153
  (isatty(process.stdin.fd) || !(await stdinHasData())
67
154
  ? null
@@ -105,3 +192,6 @@ export async function stdinHasData() {
105
192
  const stats = await promisify(fstat)(0);
106
193
  return stats.isFIFO() || stats.isFile();
107
194
  }
195
+ function customZodError(label, fn) {
196
+ return ((...args) => tryOrThrow(() => fn(...args), (e) => new Error(`${label} ${e instanceof ZodError ? e.issues[0]?.message : e.message}`)));
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.43.1",
3
+ "version": "1.44.1",
4
4
  "description": "Your command center for agent development",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -74,18 +74,20 @@
74
74
  "p-wait-for": "^5.0.2",
75
75
  "prettier": "^3.6.2",
76
76
  "tar": "^7.4.3",
77
+ "terminal-image": "^3.1.1",
78
+ "terminal-link": "^4.0.0",
77
79
  "wrap-ansi": "^9.0.0",
78
80
  "yaml": "^2.8.0",
79
81
  "yargs": "^18.0.0",
80
82
  "yoctocolors-cjs": "^2.1.3",
81
83
  "zod": "^3.25.67",
82
- "@aigne/agent-library": "^1.21.36",
83
- "@aigne/agentic-memory": "^1.0.36",
84
- "@aigne/aigne-hub": "^0.8.6",
85
- "@aigne/core": "^1.57.5",
86
- "@aigne/default-memory": "^1.1.18",
87
- "@aigne/observability-api": "^0.10.1",
88
- "@aigne/openai": "^0.13.7"
84
+ "@aigne/agentic-memory": "^1.0.38",
85
+ "@aigne/agent-library": "^1.21.38",
86
+ "@aigne/aigne-hub": "^0.8.8",
87
+ "@aigne/core": "^1.58.1",
88
+ "@aigne/default-memory": "^1.2.1",
89
+ "@aigne/openai": "^0.14.1",
90
+ "@aigne/observability-api": "^0.10.2"
89
91
  },
90
92
  "devDependencies": {
91
93
  "@inquirer/testing": "^2.1.49",
@@ -102,7 +104,7 @@
102
104
  "rimraf": "^6.0.1",
103
105
  "typescript": "^5.8.3",
104
106
  "ufo": "^1.6.1",
105
- "@aigne/test-utils": "^0.5.43"
107
+ "@aigne/test-utils": "^0.5.45"
106
108
  },
107
109
  "scripts": {
108
110
  "lint": "tsc --noEmit",