@aigne/cli 1.11.7 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.12.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.11.9...cli-v1.12.0) (2025-06-20)
4
+
5
+
6
+ ### Features
7
+
8
+ * **cli:** support pass named input to agent by --input-xxx ([#167](https://github.com/AIGNE-io/aigne-framework/issues/167)) ([cda5bb6](https://github.com/AIGNE-io/aigne-framework/commit/cda5bb6baab680787de1a042664fe34c17a84bb1))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @aigne/agent-library bumped to 1.14.0
16
+ * @aigne/anthropic bumped to 0.3.3
17
+ * @aigne/bedrock bumped to 0.3.3
18
+ * @aigne/core bumped to 1.21.0
19
+ * @aigne/deepseek bumped to 0.3.3
20
+ * @aigne/gemini bumped to 0.3.3
21
+ * @aigne/ollama bumped to 0.3.3
22
+ * @aigne/open-router bumped to 0.3.3
23
+ * @aigne/openai bumped to 0.3.3
24
+ * @aigne/xai bumped to 0.3.3
25
+
26
+ ## [1.11.9](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.11.8...cli-v1.11.9) (2025-06-19)
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * use `inputKey` instead of implicit $message for AIAgent ([#165](https://github.com/AIGNE-io/aigne-framework/issues/165)) ([8b6e589](https://github.com/AIGNE-io/aigne-framework/commit/8b6e5896bba8209fd2eecb0f5b9263618bffdaf8))
32
+
33
+
34
+ ### Dependencies
35
+
36
+ * The following workspace dependencies were updated
37
+ * dependencies
38
+ * @aigne/agent-library bumped to 1.13.2
39
+ * @aigne/anthropic bumped to 0.3.2
40
+ * @aigne/bedrock bumped to 0.3.2
41
+ * @aigne/core bumped to 1.20.1
42
+ * @aigne/deepseek bumped to 0.3.2
43
+ * @aigne/gemini bumped to 0.3.2
44
+ * @aigne/ollama bumped to 0.3.2
45
+ * @aigne/open-router bumped to 0.3.2
46
+ * @aigne/openai bumped to 0.3.2
47
+ * @aigne/xai bumped to 0.3.2
48
+
49
+ ## [1.11.8](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.11.7...cli-v1.11.8) (2025-06-17)
50
+
51
+
52
+ ### Dependencies
53
+
54
+ * The following workspace dependencies were updated
55
+ * dependencies
56
+ * @aigne/agent-library bumped to 1.13.1
57
+ * @aigne/anthropic bumped to 0.3.1
58
+ * @aigne/bedrock bumped to 0.3.1
59
+ * @aigne/core bumped to 1.20.0
60
+ * @aigne/deepseek bumped to 0.3.1
61
+ * @aigne/gemini bumped to 0.3.1
62
+ * @aigne/ollama bumped to 0.3.1
63
+ * @aigne/open-router bumped to 0.3.1
64
+ * @aigne/openai bumped to 0.3.1
65
+ * @aigne/xai bumped to 0.3.1
66
+
3
67
  ## [1.11.7](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.11.6...cli-v1.11.7) (2025-06-16)
4
68
 
5
69
 
@@ -10,14 +10,15 @@ import { Listr, PRESET_TIMER } from "@aigne/listr2";
10
10
  import { availableMemories, availableModels } from "../constants.js";
11
11
  import { isV1Package, toAIGNEPackage } from "../utils/agent-v1.js";
12
12
  import { downloadAndExtract } from "../utils/download.js";
13
- import { createRunAIGNECommand, parseModelOption, runAgentWithAIGNE, } from "../utils/run-with-aigne.js";
13
+ import { createRunAIGNECommand, parseAgentInputByCommander, parseModelOption, runAgentWithAIGNE, } from "../utils/run-with-aigne.js";
14
14
  export function createRunCommand() {
15
15
  return createRunAIGNECommand()
16
16
  .description("Run AIGNE from the specified agent")
17
- .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
17
+ .option("--url, --path <path_or_url>", "Path to the agents directory or URL to aigne project", ".")
18
18
  .option("--entry-agent <entry-agent>", "Name of the agent to run (defaults to the first agent found)")
19
19
  .option("--cache-dir <dir>", "Directory to download the package to (defaults to the ~/.aigne/xxx)")
20
- .action(async (path, options) => {
20
+ .action(async (options) => {
21
+ const { path } = options;
21
22
  if (options.logLevel)
22
23
  logger.level = options.logLevel;
23
24
  const { cacheDir, dir } = prepareDirs(path, options);
@@ -78,8 +79,9 @@ export function createRunCommand() {
78
79
  }).run();
79
80
  assert(aigne);
80
81
  assert(agent);
82
+ const input = await parseAgentInputByCommander(agent, options);
81
83
  try {
82
- await runAgentWithAIGNE(aigne, agent, { ...options });
84
+ await runAgentWithAIGNE(aigne, agent, { ...options, input });
83
85
  }
84
86
  finally {
85
87
  await aigne.shutdown();
@@ -4,6 +4,7 @@ import { type Listr } from "@aigne/listr2";
4
4
  import { type AIGNEListrTaskWrapper } from "../utils/listr.js";
5
5
  export interface TerminalTracerOptions {
6
6
  printRequest?: boolean;
7
+ outputKey?: string;
7
8
  }
8
9
  export declare class TerminalTracer {
9
10
  readonly context: Context;
@@ -24,8 +25,9 @@ export declare class TerminalTracer {
24
25
  time?: boolean;
25
26
  }): string;
26
27
  private marked;
27
- formatRequest(_context: Context, m?: Message): string | undefined;
28
- formatResult(context: Context, m?: Message): string;
28
+ get outputKey(): string;
29
+ formatRequest(agent: Agent, _context: Context, m?: Message): string | undefined;
30
+ formatResult(agent: Agent, context: Context, m?: Message): string;
29
31
  }
30
32
  type Task = ReturnType<typeof promiseWithResolvers<void>> & {
31
33
  listr: ReturnType<typeof promiseWithResolvers<{
@@ -1,9 +1,9 @@
1
1
  import { EOL } from "node:os";
2
2
  import { inspect } from "node:util";
3
- import { ChatModel, MESSAGE_KEY, } from "@aigne/core";
3
+ import { AIAgent, ChatModel, DEFAULT_OUTPUT_KEY, } from "@aigne/core";
4
4
  import { LogLevel, logger } from "@aigne/core/utils/logger.js";
5
5
  import { promiseWithResolvers } from "@aigne/core/utils/promise.js";
6
- import { omitBy } from "@aigne/core/utils/type-utils.js";
6
+ import { omit } from "@aigne/core/utils/type-utils.js";
7
7
  import { figures } from "@aigne/listr2";
8
8
  import { markedTerminal } from "@aigne/marked-terminal";
9
9
  import chalk from "chalk";
@@ -21,8 +21,8 @@ export class TerminalTracer {
21
21
  async run(agent, input) {
22
22
  const context = this.context.newContext({ reset: true });
23
23
  const listr = new AIGNEListr({
24
- formatRequest: () => this.options.printRequest ? this.formatRequest(context, input) : undefined,
25
- formatResult: (result) => [this.formatResult(context, result)].filter(Boolean),
24
+ formatRequest: () => this.options.printRequest ? this.formatRequest(agent, context, input) : undefined,
25
+ formatResult: (result) => [this.formatResult(agent, context, result)].filter(Boolean),
26
26
  }, [], { concurrent: true });
27
27
  const onAgentStarted = async ({ contextId, parentContextId, agent, timestamp, }) => {
28
28
  const task = {
@@ -121,27 +121,44 @@ export class TerminalTracer {
121
121
  title += ` ${this.formatTimeUsage(task.startTime, task.endTime)}`;
122
122
  return title;
123
123
  }
124
- marked = new Marked().use(markedTerminal({ forceHyperLink: false }));
125
- formatRequest(_context, m = {}) {
124
+ marked = new Marked().use({
125
+ // marked-terminal does not support code block meta, so we need to strip it
126
+ walkTokens: (token) => {
127
+ if (token.type === "code") {
128
+ if (typeof token.lang === "string") {
129
+ token.lang = token.lang.trim().split(/\s+/)[0];
130
+ }
131
+ }
132
+ },
133
+ }, markedTerminal({ forceHyperLink: false }));
134
+ get outputKey() {
135
+ return this.options.outputKey || DEFAULT_OUTPUT_KEY;
136
+ }
137
+ formatRequest(agent, _context, m = {}) {
126
138
  if (!logger.enabled(LogLevel.INFO))
127
139
  return;
128
140
  const prefix = `${chalk.grey(figures.pointer)} 💬 `;
129
- const msg = m[MESSAGE_KEY];
130
- const message = omitBy(m, (_, k) => k === MESSAGE_KEY);
141
+ const inputKey = agent instanceof AIAgent ? agent.inputKey : undefined;
142
+ const msg = inputKey ? m[inputKey] : undefined;
143
+ const message = inputKey ? omit(m, inputKey) : m;
131
144
  const text = msg && typeof msg === "string" ? this.marked.parse(msg, { async: false }).trim() : undefined;
132
145
  const json = Object.keys(message).length > 0 ? inspect(message, { colors: true }) : undefined;
133
146
  return [prefix, [text, json].filter(Boolean).join(EOL)].join(" ");
134
147
  }
135
- formatResult(context, m = {}) {
148
+ formatResult(agent, context, m = {}) {
149
+ const { isTTY } = process.stdout;
150
+ const outputKey = agent instanceof AIAgent ? agent.outputKey : undefined;
136
151
  const prefix = logger.enabled(LogLevel.INFO)
137
152
  ? `${chalk.grey(figures.tick)} 🤖 ${this.formatTokenUsage(context.usage)}`
138
153
  : null;
139
- const msg = m[MESSAGE_KEY];
140
- const message = omitBy(m, (_, k) => k === MESSAGE_KEY);
141
- const text = msg && typeof msg === "string" ? this.marked.parse(msg, { async: false }).trim() : undefined;
142
- const json = Object.keys(message).length > 0
143
- ? inspect(message, { colors: process.stdout.isTTY })
154
+ const msg = outputKey ? m[outputKey] : undefined;
155
+ const message = outputKey ? omit(m, outputKey) : m;
156
+ const text = msg && typeof msg === "string"
157
+ ? isTTY
158
+ ? this.marked.parse(msg, { async: false }).trim()
159
+ : msg
144
160
  : undefined;
161
+ const json = Object.keys(message).length > 0 ? inspect(message, { colors: isTTY }) : undefined;
145
162
  return [prefix, text, json].filter(Boolean).join(EOL);
146
163
  }
147
164
  }
@@ -1,4 +1,5 @@
1
- import { type Message, type UserAgent } from "@aigne/core";
1
+ import type { Message, UserAgent } from "@aigne/core";
2
+ export declare const DEFAULT_CHAT_INPUT_KEY = "message";
2
3
  export interface ChatLoopOptions {
3
4
  initialCall?: Message | string;
4
5
  welcome?: string;
@@ -6,4 +7,4 @@ export interface ChatLoopOptions {
6
7
  inputKey?: string;
7
8
  skipLoop?: boolean;
8
9
  }
9
- export declare function runChatLoopInTerminal(userAgent: UserAgent, options?: ChatLoopOptions): Promise<void>;
10
+ export declare function runChatLoopInTerminal(userAgent: UserAgent<any, any>, options?: ChatLoopOptions): Promise<void>;
@@ -1,6 +1,6 @@
1
- import { createMessage } from "@aigne/core";
2
1
  import inquirer from "inquirer";
3
2
  import { TerminalTracer } from "../tracer/terminal.js";
3
+ export const DEFAULT_CHAT_INPUT_KEY = "message";
4
4
  export async function runChatLoopInTerminal(userAgent, options = {}) {
5
5
  const { initialCall = process.env.INITIAL_CALL, skipLoop = process.env.SKIP_LOOP === "true" } = options;
6
6
  let prompt;
@@ -43,9 +43,7 @@ export async function runChatLoopInTerminal(userAgent, options = {}) {
43
43
  }
44
44
  async function callAgent(userAgent, input, options) {
45
45
  const tracer = new TerminalTracer(userAgent.context);
46
- await tracer.run(userAgent, options.inputKey && typeof input === "string"
47
- ? { [options.inputKey]: input }
48
- : createMessage(input));
46
+ await tracer.run(userAgent, typeof input === "string" ? { [options.inputKey || DEFAULT_CHAT_INPUT_KEY]: input } : input);
49
47
  }
50
48
  const COMMANDS = {
51
49
  "/exit": () => ({ exit: true }),
@@ -1,4 +1,4 @@
1
- import { AIGNE, type Agent, type ChatModelOptions } from "@aigne/core";
1
+ import { AIGNE, type Agent, type ChatModelOptions, type Message } from "@aigne/core";
2
2
  import { LogLevel } from "@aigne/core/utils/logger.js";
3
3
  import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
4
4
  import { Command } from "commander";
@@ -10,23 +10,32 @@ export interface RunAIGNECommandOptions {
10
10
  topP?: number;
11
11
  presencePenalty?: number;
12
12
  frequencyPenalty?: number;
13
- input?: string;
13
+ input?: string[];
14
+ format?: "text" | "json" | "yaml";
15
+ output?: string;
14
16
  logLevel?: LogLevel;
17
+ force?: boolean;
15
18
  }
16
19
  export declare const createRunAIGNECommand: (name?: string) => Command;
20
+ export declare function parseAgentInputByCommander(agent: Agent, options?: RunAIGNECommandOptions & {
21
+ inputKey?: string;
22
+ argv?: string[];
23
+ }): Promise<Message>;
17
24
  export declare const parseModelOption: (model?: string) => {
18
25
  provider: string | undefined;
19
26
  name: string | undefined;
20
27
  };
21
- export declare function runWithAIGNE(agentCreator: ((aigne: AIGNE) => PromiseOrValue<Agent>) | Agent, { argv, chatLoopOptions, modelOptions, }?: {
28
+ export declare function runWithAIGNE(agentCreator: ((aigne: AIGNE) => PromiseOrValue<Agent>) | Agent, { argv, chatLoopOptions, modelOptions, outputKey, }?: {
22
29
  argv?: typeof process.argv;
23
30
  chatLoopOptions?: ChatLoopOptions;
24
31
  modelOptions?: ChatModelOptions;
32
+ outputKey?: string;
25
33
  }): Promise<void>;
26
- export declare function runAgentWithAIGNE(aigne: AIGNE, agent: Agent, { chatLoopOptions, modelOptions, ...options }?: {
34
+ export declare function runAgentWithAIGNE(aigne: AIGNE, agent: Agent, { outputKey, chatLoopOptions, modelOptions, ...options }?: {
35
+ outputKey?: string;
27
36
  chatLoopOptions?: ChatLoopOptions;
28
37
  modelOptions?: ChatModelOptions;
29
- } & RunAIGNECommandOptions): Promise<{
30
- result: import("@aigne/core").Message;
31
- context: import("@aigne/core").Context<import("@aigne/core").UserContext>;
38
+ input?: Message;
39
+ } & Omit<RunAIGNECommandOptions, "input">): Promise<{
40
+ result: Message;
32
41
  } | undefined>;
@@ -1,18 +1,24 @@
1
+ import assert from "node:assert";
1
2
  import { fstat } from "node:fs";
3
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
4
+ import { dirname, isAbsolute, join } from "node:path";
2
5
  import { isatty } from "node:tty";
3
6
  import { promisify } from "node:util";
4
- import { AIGNE, UserAgent, createMessage } from "@aigne/core";
7
+ import { exists } from "@aigne/agent-library/utils/fs.js";
8
+ import { AIGNE, DEFAULT_OUTPUT_KEY, UserAgent, readAllString, } from "@aigne/core";
5
9
  import { loadModel } from "@aigne/core/loader/index.js";
6
10
  import { LogLevel, getLevelFromEnv, logger } from "@aigne/core/utils/logger.js";
7
- import { readAllString } from "@aigne/core/utils/stream-utils.js";
8
- import { tryOrThrow } from "@aigne/core/utils/type-utils.js";
11
+ import { isEmpty, isNonNullable, tryOrThrow, } from "@aigne/core/utils/type-utils.js";
9
12
  import { Command } from "commander";
10
13
  import PrettyError from "pretty-error";
11
- import { ZodError, z } from "zod";
14
+ import { parse } from "yaml";
15
+ import { ZodError, ZodObject, z } from "zod";
12
16
  import { availableModels } from "../constants.js";
13
17
  import { TerminalTracer } from "../tracer/terminal.js";
14
- import { runChatLoopInTerminal } from "./run-chat-loop.js";
18
+ import { DEFAULT_CHAT_INPUT_KEY, runChatLoopInTerminal, } from "./run-chat-loop.js";
15
19
  export const createRunAIGNECommand = (name = "run") => new Command(name)
20
+ .allowUnknownOption(true)
21
+ .allowExcessArguments(true)
16
22
  .description("Run agent with AIGNE in terminal")
17
23
  .option("--chat", "Run chat loop in terminal", false)
18
24
  .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.map((i) => i.name.toLowerCase().replace(/ChatModel$/i, "")).join(", ")} (default: openai)`)
@@ -20,13 +26,73 @@ export const createRunAIGNECommand = (name = "run") => new Command(name)
20
26
  .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)))
21
27
  .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)))
22
28
  .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)))
23
- .option("--input -i <input>", "Input to the agent")
29
+ .option("--input -i <input...>", "Input to the agent, use @<file> to read from a file")
30
+ .option("--format <format>", "Input format for the agent (available: text, json, yaml default: text)")
31
+ .option("--output -o <output>", "Output file to save the result (default: stdout)")
32
+ .option("--output-key <output-key>", "Key in the result to save to the output file", DEFAULT_OUTPUT_KEY)
33
+ .option("--force", "Truncate the output file if it exists, and create directory if the output path is not exists", false)
24
34
  .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);
35
+ export async function parseAgentInputByCommander(agent, options = {}) {
36
+ const cmd = new Command()
37
+ .description(`Run agent ${agent.name} with AIGNE`)
38
+ .allowUnknownOption(true)
39
+ .allowExcessArguments(true);
40
+ const inputSchemaShape = agent.inputSchema instanceof ZodObject ? Object.keys(agent.inputSchema.shape) : [];
41
+ for (const option of inputSchemaShape) {
42
+ cmd.option(`--input-${option} <${option}>`);
43
+ }
44
+ const input = await new Promise((resolve, reject) => {
45
+ cmd
46
+ .action(async (agentInputOptions) => {
47
+ try {
48
+ const input = Object.fromEntries((await Promise.all(Object.entries(agentInputOptions).map(async ([key, value]) => {
49
+ let k = key.replace(/^input/, "");
50
+ k = k.charAt(0).toLowerCase() + k.slice(1);
51
+ if (!k)
52
+ return null;
53
+ if (typeof value === "string" && value.startsWith("@")) {
54
+ value = await readFile(value.slice(1), "utf8");
55
+ }
56
+ return [k, value];
57
+ }))).filter(isNonNullable));
58
+ resolve(input);
59
+ }
60
+ catch (error) {
61
+ reject(error);
62
+ }
63
+ })
64
+ .parseAsync(options.argv ?? process.argv)
65
+ .catch((error) => reject(error));
66
+ });
67
+ const rawInput = options.input ||
68
+ (isatty(process.stdin.fd) || !(await stdinHasData())
69
+ ? null
70
+ : [await readAllString(process.stdin)]);
71
+ if (rawInput?.length) {
72
+ for (let raw of rawInput) {
73
+ if (raw.startsWith("@")) {
74
+ raw = await readFile(raw.slice(1), "utf8");
75
+ }
76
+ if (options.format === "json") {
77
+ Object.assign(input, JSON.parse(raw));
78
+ }
79
+ else if (options.format === "yaml") {
80
+ Object.assign(input, parse(raw));
81
+ }
82
+ else {
83
+ Object.assign(input, typeof options.inputKey === "string"
84
+ ? { [options.inputKey]: raw }
85
+ : { [DEFAULT_CHAT_INPUT_KEY]: raw });
86
+ }
87
+ }
88
+ }
89
+ return input;
90
+ }
25
91
  export const parseModelOption = (model) => {
26
92
  const { provider, name } = model?.match(/(?<provider>[^:]+)(:(?<name>(\S+)))?/)?.groups ?? {};
27
93
  return { provider, name };
28
94
  };
29
- export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoopOptions, modelOptions, } = {}) {
95
+ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoopOptions, modelOptions, outputKey, } = {}) {
30
96
  await createRunAIGNECommand()
31
97
  .showHelpAfterError(true)
32
98
  .showSuggestionAfterError(true)
@@ -44,7 +110,20 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
44
110
  const aigne = new AIGNE({ model });
45
111
  try {
46
112
  const agent = typeof agentCreator === "function" ? await agentCreator(aigne) : agentCreator;
47
- await runAgentWithAIGNE(aigne, agent, { ...options, chatLoopOptions, modelOptions });
113
+ const input = await parseAgentInputByCommander(agent, {
114
+ ...options,
115
+ inputKey: chatLoopOptions?.inputKey,
116
+ });
117
+ if (isEmpty(input)) {
118
+ Object.assign(input, chatLoopOptions?.initialCall || chatLoopOptions?.defaultQuestion);
119
+ }
120
+ await runAgentWithAIGNE(aigne, agent, {
121
+ ...options,
122
+ outputKey,
123
+ chatLoopOptions,
124
+ modelOptions,
125
+ input,
126
+ });
48
127
  }
49
128
  finally {
50
129
  await aigne.shutdown();
@@ -59,7 +138,24 @@ export async function runWithAIGNE(agentCreator, { argv = process.argv, chatLoop
59
138
  function customZodError(label, fn) {
60
139
  return ((...args) => tryOrThrow(() => fn(...args), (e) => new Error(`${label} ${e instanceof ZodError ? e.issues[0]?.message : e.message}`)));
61
140
  }
62
- export async function runAgentWithAIGNE(aigne, agent, { chatLoopOptions, modelOptions, ...options } = {}) {
141
+ export async function runAgentWithAIGNE(aigne, agent, { outputKey, chatLoopOptions, modelOptions, ...options } = {}) {
142
+ if (options.output) {
143
+ const outputPath = isAbsolute(options.output)
144
+ ? options.output
145
+ : join(process.cwd(), options.output);
146
+ if (await exists(outputPath)) {
147
+ const s = await stat(outputPath);
148
+ if (!s.isFile())
149
+ throw new Error(`Output path ${outputPath} is not a file`);
150
+ if (s.size > 0 && !options.force) {
151
+ throw new Error(`Output file ${outputPath} already exists. Use --force to overwrite.`);
152
+ }
153
+ }
154
+ else {
155
+ await mkdir(dirname(outputPath), { recursive: true });
156
+ }
157
+ await writeFile(outputPath, "", "utf8");
158
+ }
63
159
  if (options.chat) {
64
160
  if (!isatty(process.stdout.fd)) {
65
161
  throw new Error("--chat mode requires a TTY terminal");
@@ -70,19 +166,20 @@ export async function runAgentWithAIGNE(aigne, agent, { chatLoopOptions, modelOp
70
166
  });
71
167
  return;
72
168
  }
73
- const input = options.input ||
74
- (isatty(process.stdin.fd) || !(await stdinHasData())
75
- ? null
76
- : await readAllString(process.stdin)) ||
77
- chatLoopOptions?.initialCall ||
78
- chatLoopOptions?.defaultQuestion ||
79
- {};
80
169
  const tracer = new TerminalTracer(aigne.newContext(), {
81
170
  printRequest: logger.enabled(LogLevel.INFO),
171
+ outputKey,
82
172
  });
83
- return await tracer.run(agent, chatLoopOptions?.inputKey && typeof input === "string"
84
- ? { [chatLoopOptions.inputKey]: input }
85
- : createMessage(input));
173
+ assert(options.input);
174
+ const { result } = await tracer.run(agent, options.input);
175
+ if (options.output) {
176
+ const message = result[outputKey || DEFAULT_OUTPUT_KEY];
177
+ const content = typeof message === "string" ? message : JSON.stringify(result, null, 2);
178
+ const path = isAbsolute(options.output) ? options.output : join(process.cwd(), options.output);
179
+ await mkdir(dirname(path), { recursive: true });
180
+ await writeFile(path, content, "utf8");
181
+ }
182
+ return { result };
86
183
  }
87
184
  async function stdinHasData() {
88
185
  const stats = await promisify(fstat)(0);
@@ -1,4 +1,4 @@
1
- import { getMessage } from "@aigne/core";
1
+ import { AIAgent } from "@aigne/core";
2
2
  import { promiseWithResolvers } from "@aigne/core/utils/promise.js";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -82,7 +82,8 @@ export function createMcpServer(aigne) {
82
82
  content: [
83
83
  {
84
84
  type: "text",
85
- text: getMessage(result) || JSON.stringify(result),
85
+ text: (agent instanceof AIAgent && result[agent.outputKey]) ||
86
+ JSON.stringify(result),
86
87
  },
87
88
  ],
88
89
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.11.7",
3
+ "version": "1.12.0",
4
4
  "description": "cli for AIGNE framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -49,17 +49,18 @@
49
49
  "pretty-error": "^4.0.0",
50
50
  "tar": "^7.4.3",
51
51
  "wrap-ansi": "^9.0.0",
52
+ "yaml": "^2.7.1",
52
53
  "zod": "^3.24.4",
53
- "@aigne/agent-library": "^1.13.0",
54
- "@aigne/bedrock": "^0.3.0",
55
- "@aigne/core": "^1.19.0",
56
- "@aigne/deepseek": "^0.3.0",
57
- "@aigne/open-router": "^0.3.0",
58
- "@aigne/anthropic": "^0.3.0",
59
- "@aigne/ollama": "^0.3.0",
60
- "@aigne/openai": "^0.3.0",
61
- "@aigne/gemini": "^0.3.0",
62
- "@aigne/xai": "^0.3.0"
54
+ "@aigne/agent-library": "^1.14.0",
55
+ "@aigne/anthropic": "^0.3.3",
56
+ "@aigne/bedrock": "^0.3.3",
57
+ "@aigne/deepseek": "^0.3.3",
58
+ "@aigne/core": "^1.21.0",
59
+ "@aigne/ollama": "^0.3.3",
60
+ "@aigne/gemini": "^0.3.3",
61
+ "@aigne/open-router": "^0.3.3",
62
+ "@aigne/xai": "^0.3.3",
63
+ "@aigne/openai": "^0.3.3"
63
64
  },
64
65
  "devDependencies": {
65
66
  "@types/archiver": "^6.0.3",
@@ -3,6 +3,7 @@ description: Chat agent
3
3
  instructions: |
4
4
  You are a helpful assistant that can answer questions and provide information on a wide range of topics.
5
5
  Your goal is to assist users in finding the information they need and to engage in friendly conversation.
6
+ input_key: message
6
7
  memory: true
7
8
  tools:
8
9
  - sandbox.js