@aigne/cli 1.49.2-beta.1 → 1.50.0-beta.2

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,33 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.50.0-beta.2](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.50.0-beta.1...cli-v1.50.0-beta.2) (2025-09-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * add multiline support for prompts.input ([#570](https://github.com/AIGNE-io/aigne-framework/issues/570)) ([520d985](https://github.com/AIGNE-io/aigne-framework/commit/520d9859770cc553b551a4a58c7e392b39f53b37))
9
+
10
+ ## [1.50.0-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.49.2-beta.1...cli-v1.50.0-beta.1) (2025-09-30)
11
+
12
+
13
+ ### Features
14
+
15
+ * **cli:** support define nested commands for sub apps ([#568](https://github.com/AIGNE-io/aigne-framework/issues/568)) ([0693b80](https://github.com/AIGNE-io/aigne-framework/commit/0693b807e0f8d335010e6ad00763b07cf095e65b))
16
+
17
+
18
+ ### Dependencies
19
+
20
+ * The following workspace dependencies were updated
21
+ * dependencies
22
+ * @aigne/agent-library bumped to 1.21.47-beta.1
23
+ * @aigne/agentic-memory bumped to 1.0.47-beta.1
24
+ * @aigne/aigne-hub bumped to 0.10.1-beta.1
25
+ * @aigne/core bumped to 1.62.0-beta
26
+ * @aigne/default-memory bumped to 1.2.10-beta.1
27
+ * @aigne/openai bumped to 0.16.1-beta.1
28
+ * devDependencies
29
+ * @aigne/test-utils bumped to 0.5.54-beta.1
30
+
3
31
  ## [1.49.2-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.49.2-beta...cli-v1.49.2-beta.1) (2025-09-29)
4
32
 
5
33
 
@@ -1,5 +1,4 @@
1
- import yargs from "yargs";
2
1
  export declare function createAIGNECommand(options?: {
3
2
  argv?: string[];
4
3
  aigneFilePath?: string;
5
- }): yargs.Argv<{}>;
4
+ }): import("yargs").Argv<{}>;
@@ -1,5 +1,5 @@
1
1
  import type { CommandModule } from "yargs";
2
- import { type AgentInChildProcess, type LoadAIGNEInChildProcessResult } from "../utils/workers/run-aigne-in-child-process.js";
2
+ import { type AgentInChildProcess, type CLIAgentInChildProcess, type LoadAIGNEInChildProcessResult } from "../utils/workers/run-aigne-in-child-process.js";
3
3
  import { type AgentRunCommonOptions } from "../utils/yargs.js";
4
4
  export declare function createAppCommands({ argv }?: {
5
5
  argv?: string[];
@@ -8,6 +8,11 @@ export declare const agentCommandModule: ({ dir, agent, }: {
8
8
  dir: string;
9
9
  agent: AgentInChildProcess;
10
10
  }) => CommandModule<unknown, AgentRunCommonOptions>;
11
+ export declare const cliAgentCommandModule: ({ dir, parent, cliAgent, }: {
12
+ dir: string;
13
+ parent?: string[];
14
+ cliAgent: CLIAgentInChildProcess;
15
+ }) => CommandModule<unknown, AgentRunCommonOptions>;
11
16
  interface LoadApplicationOptions {
12
17
  packageName: string;
13
18
  dir: string;
@@ -1,3 +1,4 @@
1
+ import assert from "node:assert";
1
2
  import { spawn } from "node:child_process";
2
3
  import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
4
  import { homedir } from "node:os";
@@ -53,8 +54,8 @@ export function createAppCommands({ argv } = {}) {
53
54
  command: "$0",
54
55
  });
55
56
  }
56
- for (const agent of aigne.cli?.agents ?? []) {
57
- y.command(agentCommandModule({ dir, agent }));
57
+ for (const cliAgent of aigne.cli?.agents ?? []) {
58
+ y.command(cliAgentCommandModule({ dir, cliAgent }));
58
59
  }
59
60
  y.option("model", {
60
61
  type: "string",
@@ -152,6 +153,42 @@ export const agentCommandModule = ({ dir, agent, }) => {
152
153
  },
153
154
  };
154
155
  };
156
+ export const cliAgentCommandModule = ({ dir, parent, cliAgent, }) => {
157
+ const { agent, agents } = cliAgent;
158
+ const name = cliAgent.name || agent?.name;
159
+ assert(name, "CLI agent must have a name");
160
+ return {
161
+ command: name,
162
+ aliases: cliAgent.alias || agent?.alias || [],
163
+ describe: cliAgent.description || agent?.description || "",
164
+ builder: async (yargs) => {
165
+ if (agent) {
166
+ withAgentInputSchema(yargs, { inputSchema: jsonSchemaToZod(agent.inputSchema) });
167
+ }
168
+ if (agents?.length) {
169
+ for (const cmd of agents) {
170
+ yargs.command(cliAgentCommandModule({ dir, parent: (parent ?? []).concat(name), cliAgent: cmd }));
171
+ }
172
+ }
173
+ if (!agent)
174
+ yargs.demandCommand();
175
+ return yargs;
176
+ },
177
+ handler: async (options) => {
178
+ if (!agent)
179
+ throw new Error("CLI agent is not defined");
180
+ if (options.logLevel)
181
+ logger.level = options.logLevel;
182
+ await runAIGNEInChildProcess("invokeCLIAgentFromDir", {
183
+ dir,
184
+ parent,
185
+ agent: name,
186
+ input: options,
187
+ });
188
+ process.exit(0);
189
+ },
190
+ };
191
+ };
155
192
  export async function loadApplication(options) {
156
193
  const { dir, packageName } = options;
157
194
  const check = await checkInstallation(dir);
@@ -1,5 +1,6 @@
1
1
  import { isAbsolute, resolve } from "node:path";
2
2
  import { exists } from "@aigne/agent-library/utils/fs.js";
3
+ import { findCliAgent } from "@aigne/core/utils/agent-utils.js";
3
4
  import { z } from "zod";
4
5
  import { runEvaluationPipeline } from "../utils/evaluation/core.js";
5
6
  import { FileDataset } from "../utils/evaluation/dataset.js";
@@ -70,7 +71,7 @@ export function createEvalCommand({ aigneFilePath, } = {}) {
70
71
  const { chat } = aigne.cli;
71
72
  const agent = chat && chat.name === entryAgent
72
73
  ? chat
73
- : aigne.cli.agents[entryAgent] ||
74
+ : findCliAgent(aigne.cli, "*", entryAgent) ||
74
75
  aigne.agents[entryAgent] ||
75
76
  aigne.skills[entryAgent] ||
76
77
  aigne.mcpServer.agents[entryAgent];
@@ -80,7 +81,7 @@ export function createEvalCommand({ aigneFilePath, } = {}) {
80
81
  let evaluatorAgent;
81
82
  if (evaluatorName) {
82
83
  evaluatorAgent =
83
- aigne.cli.agents[evaluatorName] ||
84
+ findCliAgent(aigne.cli, "*", evaluatorName) ||
84
85
  aigne.agents[evaluatorName] ||
85
86
  aigne.skills[evaluatorName] ||
86
87
  aigne.mcpServer.agents[evaluatorName];
@@ -2,6 +2,7 @@ import { cp, mkdir, rm } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { isAbsolute, join, resolve } from "node:path";
4
4
  import { exists } from "@aigne/agent-library/utils/fs.js";
5
+ import { mapCliAgent } from "@aigne/core/utils/agent-utils.js";
5
6
  import { flat, isNonNullable } from "@aigne/core/utils/type-utils.js";
6
7
  import { Listr, PRESET_TIMER } from "@aigne/listr2";
7
8
  import { config } from "dotenv-flow";
@@ -11,7 +12,7 @@ import { downloadAndExtract } from "../utils/download.js";
11
12
  import { loadAIGNE } from "../utils/load-aigne.js";
12
13
  import { isUrl } from "../utils/url.js";
13
14
  import { serializeAgent } from "../utils/workers/run-aigne-in-child-process.js";
14
- import { agentCommandModule } from "./app.js";
15
+ import { agentCommandModule, cliAgentCommandModule } from "./app.js";
15
16
  export function createRunCommand({ aigneFilePath, } = {}) {
16
17
  return {
17
18
  command: "run [path] [entry-agent]",
@@ -47,10 +48,16 @@ export function createRunCommand({ aigneFilePath, } = {}) {
47
48
  });
48
49
  }
49
50
  // Allow user to run all of agents in the AIGNE instances
50
- const allAgents = flat(aigne.cli.agents, aigne.agents, aigne.skills, aigne.cli.chat, aigne.mcpServer.agents);
51
+ const allAgents = flat(aigne.agents, aigne.skills, aigne.cli.chat, aigne.mcpServer.agents);
51
52
  for (const agent of allAgents) {
52
53
  subYargs.command(agentCommandModule({ dir: path, agent: serializeAgent(agent) }));
53
54
  }
55
+ for (const cliAgent of aigne.cli.agents ?? []) {
56
+ subYargs.command(cliAgentCommandModule({
57
+ dir: path,
58
+ cliAgent: mapCliAgent(cliAgent, (a) => (a ? serializeAgent(a) : undefined)),
59
+ }));
60
+ }
54
61
  const argv = process.argv.slice(aigneFilePath ? 3 : 2);
55
62
  if (argv[0] === "run")
56
63
  argv.shift(); // remove 'run' command
@@ -62,8 +69,9 @@ export function createRunCommand({ aigneFilePath, } = {}) {
62
69
  if (argv[0] === "--entry-agent")
63
70
  argv.shift();
64
71
  const firstAgent = aigne.agents[0]?.name;
65
- if (!options.entryAgent && firstAgent)
72
+ if (!options.entryAgent && firstAgent && !argv.some((i) => ["-h", "--help"].includes(i))) {
66
73
  argv.unshift(firstAgent);
74
+ }
67
75
  await subYargs
68
76
  .strict()
69
77
  .demandCommand()
@@ -12,6 +12,7 @@ import terminalImage from "terminal-image";
12
12
  import terminalLink from "terminal-link";
13
13
  import { withProtocol } from "ufo";
14
14
  import { AIGNE_HUB_CREDITS_NOT_ENOUGH_ERROR_TYPE } from "../constants.js";
15
+ import { terminalInput } from "../ui/utils/terminal-input.js";
15
16
  import checkbox from "../utils/inquirer/checkbox.js";
16
17
  import { AIGNEListr, AIGNEListrRenderer } from "../utils/listr.js";
17
18
  import { highlightUrl } from "../utils/string-utils.js";
@@ -195,8 +196,10 @@ export class TerminalTracer {
195
196
  get: (_target, prop) => {
196
197
  const method = prop === "checkbox"
197
198
  ? checkbox
198
- : // biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
199
- prompts[prop];
199
+ : prop === "input"
200
+ ? terminalInput
201
+ : // biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
202
+ prompts[prop];
200
203
  if (typeof method !== "function")
201
204
  throw new Error(`Unsupported prompt method ${String(prop)}`);
202
205
  return async (config) => {
@@ -0,0 +1,2 @@
1
+ export declare class SIGINTError extends Error {
2
+ }
@@ -0,0 +1,2 @@
1
+ export class SIGINTError extends Error {
2
+ }
@@ -0,0 +1,4 @@
1
+ export declare function terminalInput(options?: {
2
+ message?: string;
3
+ default?: string;
4
+ }): Promise<string>;
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import chalk from "chalk";
3
+ import { render, Text, useInput } from "ink";
4
+ import { SIGINTError } from "./error.js";
5
+ import { useTextBuffer } from "./text-buffer.js";
6
+ export async function terminalInput(options = {}) {
7
+ return new Promise((resolve, reject) => {
8
+ process.addListener("SIGINT", () => {
9
+ reject(new Error("Input aborted"));
10
+ });
11
+ const app = render(_jsx(Input, { ...options, onSubmit: (value) => {
12
+ app.clear();
13
+ app.unmount();
14
+ console.log(`${options.message} ${value}`); // echo the input back to terminal
15
+ resolve(value);
16
+ }, onError: (error) => {
17
+ app.unmount();
18
+ reject(error);
19
+ } }), { exitOnCtrlC: false });
20
+ });
21
+ }
22
+ function Input(props) {
23
+ const buffer = useTextBuffer({
24
+ initialText: props.default || "",
25
+ isValidPath: () => false,
26
+ viewport: { width: 80, height: 1 },
27
+ });
28
+ useInput((character, key) => {
29
+ if (character === "c" && key.ctrl) {
30
+ props.onError(new SIGINTError("Input aborted by user"));
31
+ return;
32
+ }
33
+ if (key.return) {
34
+ props.onSubmit(buffer.text);
35
+ return;
36
+ }
37
+ else if (key.backspace)
38
+ buffer.backspace();
39
+ else if (key.delete)
40
+ buffer.backspace();
41
+ else if (key.downArrow)
42
+ buffer.move("down");
43
+ else if (key.upArrow)
44
+ buffer.move("up");
45
+ else if (key.leftArrow)
46
+ buffer.move("left");
47
+ else if (key.rightArrow)
48
+ buffer.move("right");
49
+ else if (character === "a" && key.ctrl)
50
+ buffer.move("home");
51
+ else if (character === "e" && key.ctrl)
52
+ buffer.move("end");
53
+ else {
54
+ buffer.handleInput({ ...key, name: character, sequence: character, paste: false });
55
+ }
56
+ });
57
+ const lines = [...buffer.lines];
58
+ const [row, col] = buffer.cursor;
59
+ const currentLine = lines[row] || "";
60
+ lines[row] =
61
+ currentLine.slice(0, col) + chalk.inverse(currentLine[col] || " ") + currentLine.slice(col + 1);
62
+ return (_jsxs(Text, { children: [props.message, " ", lines.join("\n")] }));
63
+ }
@@ -0,0 +1,87 @@
1
+ export type Direction = "left" | "right" | "up" | "down" | "wordLeft" | "wordRight" | "home" | "end";
2
+ interface Viewport {
3
+ height: number;
4
+ width: number;
5
+ }
6
+ interface UseTextBufferProps {
7
+ initialText?: string;
8
+ initialCursorOffset?: number;
9
+ viewport: Viewport;
10
+ onChange?: (text: string) => void;
11
+ isValidPath: (path: string) => boolean;
12
+ shellModeActive?: boolean;
13
+ }
14
+ export declare function useTextBuffer({ initialText, initialCursorOffset, viewport, onChange, isValidPath, shellModeActive, }: UseTextBufferProps): TextBuffer;
15
+ export interface TextBuffer {
16
+ lines: string[];
17
+ text: string;
18
+ cursor: [number, number];
19
+ /**
20
+ * When the user moves the caret vertically we try to keep their original
21
+ * horizontal column even when passing through shorter lines. We remember
22
+ * that *preferred* column in this field while the user is still travelling
23
+ * vertically. Any explicit horizontal movement resets the preference.
24
+ */
25
+ preferredCol: number | null;
26
+ selectionAnchor: [number, number] | null;
27
+ allVisualLines: string[];
28
+ viewportVisualLines: string[];
29
+ visualCursor: [number, number];
30
+ visualScrollRow: number;
31
+ /**
32
+ * For each visual line (by absolute index in allVisualLines) provides a tuple
33
+ * [logicalLineIndex, startColInLogical] that maps where that visual line
34
+ * begins within the logical buffer. Indices are code-point based.
35
+ */
36
+ visualToLogicalMap: Array<[number, number]>;
37
+ /**
38
+ * Replaces the entire buffer content with the provided text.
39
+ * The operation is undoable.
40
+ */
41
+ setText: (text: string) => void;
42
+ /**
43
+ * Insert a single character or string without newlines.
44
+ */
45
+ insert: (ch: string, opts?: {
46
+ paste?: boolean;
47
+ }) => void;
48
+ newline: () => void;
49
+ backspace: () => void;
50
+ del: () => void;
51
+ move: (dir: Direction) => void;
52
+ undo: () => void;
53
+ redo: () => void;
54
+ /**
55
+ * Delete the word to the *left* of the caret, mirroring common
56
+ * Ctrl/Alt+Backspace behaviour in editors & terminals. Both the adjacent
57
+ * whitespace *and* the word characters immediately preceding the caret are
58
+ * removed. If the caret is already at column‑0 this becomes a no-op.
59
+ */
60
+ deleteWordLeft: () => void;
61
+ /**
62
+ * Delete the word to the *right* of the caret, akin to many editors'
63
+ * Ctrl/Alt+Delete shortcut. Removes any whitespace/punctuation that
64
+ * follows the caret and the next contiguous run of word characters.
65
+ */
66
+ deleteWordRight: () => void;
67
+ /**
68
+ * Deletes text from the cursor to the end of the current line.
69
+ */
70
+ killLineRight: () => void;
71
+ /**
72
+ * Deletes text from the start of the current line to the cursor.
73
+ */
74
+ killLineLeft: () => void;
75
+ /**
76
+ * High level "handleInput" – receives what Ink gives us.
77
+ */
78
+ handleInput: (key: {
79
+ name: string;
80
+ ctrl: boolean;
81
+ meta: boolean;
82
+ shift: boolean;
83
+ paste: boolean;
84
+ sequence: string;
85
+ }) => void;
86
+ }
87
+ export {};