@aigne/cli 1.0.0-9 → 1.2.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 ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## [1.2.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.1.0...cli-v1.2.0) (2025-04-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * add TerminalTracer for better UX in terminal ([#56](https://github.com/AIGNE-io/aigne-framework/issues/56)) ([9875a5d](https://github.com/AIGNE-io/aigne-framework/commit/9875a5d46abb55073340ffae841fed6bd6b83ff4))
9
+ * **cli:** support run agents from remote URL ([#60](https://github.com/AIGNE-io/aigne-framework/issues/60)) ([5f49920](https://github.com/AIGNE-io/aigne-framework/commit/5f4992089d36f9e780ba55a912a1d35508cad28e))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * remove usage of new Node.js exists API for compatibility ([#57](https://github.com/AIGNE-io/aigne-framework/issues/57)) ([c10cc08](https://github.com/AIGNE-io/aigne-framework/commit/c10cc086d8ecd0744f38cdb1367d4c8816b723b3))
15
+
16
+ ## [1.1.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.0.0...cli-v1.1.0) (2025-04-08)
17
+
18
+
19
+ ### Features
20
+
21
+ * add `serve` command for @aigne/cli ([#54](https://github.com/AIGNE-io/aigne-framework/issues/54)) ([1cca843](https://github.com/AIGNE-io/aigne-framework/commit/1cca843f1760abe832b6651108fa858130f47355))
22
+ * add agent library support ([#51](https://github.com/AIGNE-io/aigne-framework/issues/51)) ([1f0d34d](https://github.com/AIGNE-io/aigne-framework/commit/1f0d34ddda3154283a4bc958ddb9b68b4ac106b0))
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js CHANGED
@@ -1,3 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import PrettyError from "pretty-error";
2
3
  import { createAIGNECommand } from "./commands/aigne.js";
3
- createAIGNECommand().parse();
4
+ createAIGNECommand()
5
+ .parseAsync()
6
+ .catch((error) => {
7
+ console.error(new PrettyError().render(error));
8
+ process.exit(1);
9
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createAIGNECommand(): Command;
@@ -1,16 +1,20 @@
1
1
  import { Command } from "commander";
2
- import { version } from "../../package.json" assert { type: "json" };
2
+ import pkg from "../../package.json" with { type: "json" };
3
+ import { asciiLogo } from "../utils/ascii-logo.js";
3
4
  import { createCreateCommand } from "./create.js";
4
5
  import { createRunCommand } from "./run.js";
6
+ import { createServeCommand } from "./serve.js";
5
7
  import { createTestCommand } from "./test.js";
6
8
  export function createAIGNECommand() {
9
+ console.log(asciiLogo);
7
10
  return new Command()
8
11
  .name("aigne")
9
12
  .description("CLI for AIGNE framework")
10
- .version(version)
13
+ .version(pkg.version)
11
14
  .addCommand(createRunCommand())
12
15
  .addCommand(createTestCommand())
13
16
  .addCommand(createCreateCommand())
17
+ .addCommand(createServeCommand())
14
18
  .showHelpAfterError(true)
15
19
  .showSuggestionAfterError(true);
16
20
  }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createCreateCommand(): Command;
@@ -1,21 +1,21 @@
1
1
  import { existsSync, mkdirSync, readdirSync } from "node:fs";
2
2
  import { cp } from "node:fs/promises";
3
- import { isAbsolute, join, resolve } from "node:path";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { Command } from "commander";
5
5
  import inquirer from "inquirer";
6
6
  export function createCreateCommand() {
7
7
  return new Command("create")
8
8
  .description("Create a new aigne project with agent config files")
9
9
  .argument("[path]", "Path to create the project directory", ".")
10
- .action(async (path) => {
11
- let projectPath = path;
12
- if (projectPath === ".") {
10
+ .action(async (_path) => {
11
+ let path = _path;
12
+ if (path === ".") {
13
13
  const answers = await inquirer.prompt([
14
14
  {
15
15
  type: "input",
16
16
  name: "projectName",
17
17
  message: "Project name:",
18
- default: path !== "." ? path : "my-aigne-project",
18
+ default: _path !== "." ? _path : "my-aigne-project",
19
19
  validate: (input) => {
20
20
  if (input.trim() === "")
21
21
  return "Project name cannot be empty.";
@@ -23,16 +23,16 @@ export function createCreateCommand() {
23
23
  },
24
24
  },
25
25
  ]);
26
- projectPath = answers.projectName;
26
+ path = answers.projectName;
27
27
  }
28
- const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
29
- const isPathNotEmpty = existsSync(absolutePath) && readdirSync(absolutePath).length > 0;
28
+ path = isAbsolute(path) ? path : resolve(process.cwd(), path);
29
+ const isPathNotEmpty = existsSync(path) && readdirSync(path).length > 0;
30
30
  if (isPathNotEmpty) {
31
31
  const answers = await inquirer.prompt([
32
32
  {
33
33
  type: "confirm",
34
34
  name: "overwrite",
35
- message: `The directory "${absolutePath}" is not empty. Do you want to remove its contents?`,
35
+ message: `The directory "${path}" is not empty. Do you want to remove its contents?`,
36
36
  default: false,
37
37
  },
38
38
  ]);
@@ -51,18 +51,18 @@ export function createCreateCommand() {
51
51
  default: "default",
52
52
  },
53
53
  ]);
54
- mkdirSync(absolutePath, { recursive: true });
54
+ mkdirSync(path, { recursive: true });
55
55
  const templatePath = join(import.meta.dirname, "../../templates", template);
56
56
  if (!existsSync(templatePath))
57
57
  throw new Error(`Template "${template}" not found.`);
58
58
  const files = readdirSync(templatePath);
59
59
  for (const file of files) {
60
60
  const source = join(templatePath, file);
61
- const destination = join(absolutePath, file);
61
+ const destination = join(path, file);
62
62
  await cp(source, destination, { recursive: true, force: true });
63
63
  }
64
64
  console.log("\n✅ Aigne project created successfully!");
65
- console.log(`\nTo use your new agent, run:\n cd ${path} && npx -y aigne run`);
65
+ console.log(`\nTo use your new agent, run:\n cd ${relative(process.cwd(), path)} && aigne run`);
66
66
  })
67
67
  .showHelpAfterError(true)
68
68
  .showSuggestionAfterError(true);
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createRunCommand(): Command;
@@ -1,37 +1,57 @@
1
- import { isAbsolute, resolve } from "node:path";
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, rm } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { isAbsolute, join, resolve } from "node:path";
2
5
  import { ExecutionEngine } from "@aigne/core";
3
- import { runChatLoopInTerminal } from "@aigne/core/utils/run-chat-loop.js";
4
6
  import { Command } from "commander";
7
+ import { downloadAndExtract } from "../utils/download.js";
8
+ import { runChatLoopInTerminal } from "../utils/run-chat-loop.js";
5
9
  export function createRunCommand() {
6
10
  return new Command("run")
7
11
  .description("Run a chat loop with the specified agent")
8
- .argument("[path]", "Path to the agents directory", ".")
12
+ .argument("[path]", "Path to the agents directory or URL to aigne project", ".")
9
13
  .option("--agent <agent>", "Name of the agent to use (defaults to the first agent found)")
10
14
  .action(async (path, options) => {
11
- const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
12
- const engine = await ExecutionEngine.load({ path: absolutePath });
13
- let agent;
14
- if (options.agent) {
15
- agent = engine.agents[options.agent];
16
- if (!agent) {
17
- console.error(`Agent "${options.agent}" not found.`);
18
- console.log("Available agents:");
19
- for (const agent of engine.agents) {
20
- console.log(`- ${agent.name}`);
21
- }
22
- process.exit(1);
23
- }
15
+ if (path.startsWith("http")) {
16
+ await downloadAndRunPackage(path, options);
17
+ return;
24
18
  }
25
- else {
26
- agent = engine.agents[0];
27
- if (!agent) {
28
- console.error("No agents found in the specified path.");
29
- process.exit(1);
30
- }
31
- }
32
- const user = engine.call(agent);
33
- await runChatLoopInTerminal(user, {});
19
+ const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
20
+ await runEngine(path, absolutePath, options);
34
21
  })
35
22
  .showHelpAfterError(true)
36
23
  .showSuggestionAfterError(true);
37
24
  }
25
+ async function runEngine(originalPath, path, options) {
26
+ const engine = await ExecutionEngine.load({ path });
27
+ let agent;
28
+ if (options.agent) {
29
+ agent = engine.agents[options.agent];
30
+ if (!agent) {
31
+ console.error(`Agent "${options.agent}" not found in ${originalPath}`);
32
+ console.log("Available agents:");
33
+ for (const agent of engine.agents) {
34
+ console.log(`- ${agent.name}`);
35
+ }
36
+ throw new Error(`Agent "${options.agent}" not found in ${originalPath}`);
37
+ }
38
+ }
39
+ else {
40
+ agent = engine.agents[0];
41
+ if (!agent)
42
+ throw new Error(`No agents found in ${originalPath}`);
43
+ }
44
+ const user = engine.call(agent);
45
+ await runChatLoopInTerminal(user, {});
46
+ }
47
+ async function downloadAndRunPackage(url, options) {
48
+ const dir = join(tmpdir(), randomUUID());
49
+ try {
50
+ await mkdir(dir, { recursive: true });
51
+ await downloadAndExtract(url, dir);
52
+ await runEngine(url, dir, options);
53
+ }
54
+ finally {
55
+ await rm(dir, { recursive: true, force: true });
56
+ }
57
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createServeCommand(): Command;
@@ -0,0 +1,32 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import { ExecutionEngine } from "@aigne/core";
3
+ import { tryOrThrow } from "@aigne/core/utils/type-utils.js";
4
+ import { Command } from "commander";
5
+ import { serveMCPServer } from "../utils/serve-mcp.js";
6
+ const DEFAULT_PORT = () => tryOrThrow(() => {
7
+ const { PORT } = process.env;
8
+ if (!PORT)
9
+ return 3000;
10
+ const port = Number.parseInt(PORT);
11
+ if (!port || !Number.isInteger(port))
12
+ throw new Error(`Invalid PORT: ${PORT}`);
13
+ return port;
14
+ }, (error) => new Error(`parse PORT error ${error.message}`));
15
+ export function createServeCommand() {
16
+ return new Command("serve")
17
+ .description("Serve the agents in the specified directory as a MCP server")
18
+ .argument("[path]", "Path to the agents directory", ".")
19
+ .option("--mcp", "Serve the agents as a MCP server")
20
+ .option("--port <port>", "Port to run the MCP server on", (s) => Number.parseInt(s))
21
+ .action(async (path, options) => {
22
+ const absolutePath = isAbsolute(path) ? path : resolve(process.cwd(), path);
23
+ const port = options.port || DEFAULT_PORT();
24
+ const engine = await ExecutionEngine.load({ path: absolutePath });
25
+ if (options.mcp)
26
+ await serveMCPServer({ engine, port });
27
+ else
28
+ throw new Error("Default server is not supported yet. Please use --mcp option");
29
+ })
30
+ .showHelpAfterError(true)
31
+ .showSuggestionAfterError(true);
32
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createTestCommand(): Command;
@@ -0,0 +1,52 @@
1
+ import { type Agent, type Context, type Message } from "@aigne/core";
2
+ import type { ContextUsage } from "@aigne/core/execution-engine/usage";
3
+ import { type DefaultRenderer, Listr, type ListrRenderer, type ListrTaskWrapper } from "@aigne/listr2";
4
+ export interface TerminalTracerOptions {
5
+ verbose?: boolean;
6
+ }
7
+ export declare class TerminalTracer {
8
+ readonly context: Context;
9
+ readonly options: TerminalTracerOptions;
10
+ constructor(context: Context, options?: TerminalTracerOptions);
11
+ private spinner;
12
+ private tasks;
13
+ run(agent: Agent, input: Message): Promise<{
14
+ result: any;
15
+ context: Context;
16
+ }>;
17
+ protected newListr(): MyListr;
18
+ formatAgentStartedOutput(agent: Agent, data: Message): string;
19
+ formatAgentSucceedOutput(agent: Agent, data: Message): string;
20
+ formatAgentFailedOutput(agent: Agent, data: Error): string;
21
+ formatMessage(data: unknown): string;
22
+ formatTokenUsage(usage: Partial<ContextUsage>, extra?: {
23
+ [key: string]: string;
24
+ }): string;
25
+ formatTimeUsage(startTime: number, endTime: number): string;
26
+ formatTaskTitle(agent: Agent, { task, usage, time }?: {
27
+ task?: Task;
28
+ usage?: boolean;
29
+ time?: boolean;
30
+ }): string;
31
+ }
32
+ type Task = ReturnType<typeof Promise.withResolvers<void>> & {
33
+ listr: ReturnType<typeof Promise.withResolvers<{
34
+ ctx: object;
35
+ subtask: Listr;
36
+ taskWrapper: ListrTaskWrapper<unknown, typeof DefaultRenderer, typeof ListrRenderer>;
37
+ }>>;
38
+ startTime?: number;
39
+ endTime?: number;
40
+ usage?: Partial<ContextUsage>;
41
+ extraTitleMetadata?: {
42
+ [key: string]: string;
43
+ };
44
+ };
45
+ declare class MyListr extends Listr {
46
+ private taskPromise;
47
+ private isTaskPromiseResolved;
48
+ resolveWaitingTask(): void;
49
+ add(...args: Parameters<Listr["add"]>): ReturnType<Listr["add"]>;
50
+ waitTaskAndRun(ctx?: unknown): Promise<any>;
51
+ }
52
+ export {};
@@ -0,0 +1,179 @@
1
+ import { inspect } from "node:util";
2
+ import { ChatModel, } from "@aigne/core";
3
+ import { Listr, ListrDefaultRendererLogLevels, Spinner, figures, } from "@aigne/listr2";
4
+ import chalk from "chalk";
5
+ import { z } from "zod";
6
+ import { parseDuration } from "../utils/time.js";
7
+ const DEBUG_DEPTH = z.number().int().default(2).safeParse(Number(process.env.DEBUG_DEPTH)).data;
8
+ export class TerminalTracer {
9
+ context;
10
+ options;
11
+ constructor(context, options = {}) {
12
+ this.context = context;
13
+ this.options = options;
14
+ }
15
+ spinner = new Spinner();
16
+ tasks = {};
17
+ async run(agent, input) {
18
+ try {
19
+ this.spinner.start();
20
+ const context = this.context.newContext({ reset: true });
21
+ const listr = this.newListr();
22
+ context.on("agentStarted", async ({ contextId, parentContextId, agent, input, timestamp }) => {
23
+ const task = {
24
+ ...Promise.withResolvers(),
25
+ listr: Promise.withResolvers(),
26
+ startTime: timestamp,
27
+ };
28
+ this.tasks[contextId] = task;
29
+ const listrTask = {
30
+ title: this.formatTaskTitle(agent),
31
+ task: (ctx, taskWrapper) => {
32
+ const subtask = taskWrapper.newListr([{ task: () => task.promise }]);
33
+ task.listr.resolve({ subtask, taskWrapper, ctx });
34
+ return subtask;
35
+ },
36
+ rendererOptions: {
37
+ persistentOutput: true,
38
+ outputBar: Number.POSITIVE_INFINITY,
39
+ bottomBar: Number.POSITIVE_INFINITY,
40
+ },
41
+ };
42
+ const parentTask = parentContextId ? this.tasks[parentContextId] : undefined;
43
+ if (parentTask) {
44
+ parentTask.listr.promise.then(({ subtask }) => {
45
+ subtask.add(listrTask);
46
+ });
47
+ }
48
+ else {
49
+ listr.add(listrTask);
50
+ }
51
+ const { taskWrapper } = await task.listr.promise;
52
+ if (this.options.verbose) {
53
+ taskWrapper.output = this.formatAgentStartedOutput(agent, input);
54
+ }
55
+ });
56
+ context.on("agentSucceed", async ({ agent, contextId, parentContextId, output, timestamp }) => {
57
+ const task = this.tasks[contextId];
58
+ if (!task)
59
+ return;
60
+ task.endTime = timestamp;
61
+ const { taskWrapper, ctx } = await task.listr.promise;
62
+ if (agent instanceof ChatModel) {
63
+ const { usage, model } = output;
64
+ task.usage = usage;
65
+ task.extraTitleMetadata ??= {};
66
+ if (model)
67
+ task.extraTitleMetadata.model = model;
68
+ }
69
+ taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
70
+ if (this.options.verbose) {
71
+ taskWrapper.output = this.formatAgentSucceedOutput(agent, output);
72
+ }
73
+ if (!parentContextId || !this.tasks[parentContextId]) {
74
+ Object.assign(ctx, output);
75
+ }
76
+ task.resolve();
77
+ });
78
+ context.on("agentFailed", async ({ agent, contextId, error, timestamp }) => {
79
+ const task = this.tasks[contextId];
80
+ if (!task)
81
+ return;
82
+ task.endTime = timestamp;
83
+ const { taskWrapper } = await task.listr.promise;
84
+ taskWrapper.title = this.formatTaskTitle(agent, { task, usage: true, time: true });
85
+ taskWrapper.output = this.formatAgentFailedOutput(agent, error);
86
+ task.reject(error);
87
+ });
88
+ const [result] = await Promise.all([
89
+ listr.waitTaskAndRun(),
90
+ context.call(agent, input).finally(() => {
91
+ listr.resolveWaitingTask();
92
+ }),
93
+ ]);
94
+ return { result, context };
95
+ }
96
+ finally {
97
+ this.spinner.stop();
98
+ }
99
+ }
100
+ newListr() {
101
+ return new MyListr([], {
102
+ concurrent: true,
103
+ rendererOptions: {
104
+ collapseSubtasks: false,
105
+ writeBottomBarDirectly: true,
106
+ icon: {
107
+ [ListrDefaultRendererLogLevels.PENDING]: () => this.spinner.fetch(),
108
+ [ListrDefaultRendererLogLevels.OUTPUT_WITH_BOTTOMBAR]: "",
109
+ },
110
+ },
111
+ });
112
+ }
113
+ formatAgentStartedOutput(agent, data) {
114
+ return `\
115
+ ${chalk.grey(figures.pointer)} call agent ${agent.name} started with input:
116
+ ${this.formatMessage(data)}`;
117
+ }
118
+ formatAgentSucceedOutput(agent, data) {
119
+ return `\
120
+ ${chalk.grey(figures.tick)} call agent ${agent.name} succeed with output:
121
+ ${this.formatMessage(data)}`;
122
+ }
123
+ formatAgentFailedOutput(agent, data) {
124
+ return `\
125
+ ${chalk.grey(figures.cross)} call agent ${agent.name} failed with error:
126
+ ${this.formatMessage(data)}`;
127
+ }
128
+ formatMessage(data) {
129
+ return inspect(data, { colors: true, depth: DEBUG_DEPTH });
130
+ }
131
+ formatTokenUsage(usage, extra) {
132
+ const items = [
133
+ [chalk.yellow(usage.inputTokens), chalk.grey("input tokens")],
134
+ [chalk.cyan(usage.outputTokens), chalk.grey("output tokens")],
135
+ usage.agentCalls ? [chalk.magenta(usage.agentCalls), chalk.grey("agent calls")] : undefined,
136
+ ];
137
+ const content = items.filter((i) => !!i).map((i) => i.join(" "));
138
+ if (extra) {
139
+ content.unshift(...Object.entries(extra)
140
+ .filter(([k, v]) => k && v)
141
+ .map(([k, v]) => `${chalk.grey(k)}: ${v}`));
142
+ }
143
+ return `${chalk.grey("(")}${content.join(chalk.green(", "))}${chalk.grey(")")}`;
144
+ }
145
+ formatTimeUsage(startTime, endTime) {
146
+ const duration = endTime - startTime;
147
+ return chalk.grey(`[${parseDuration(duration)}]`);
148
+ }
149
+ formatTaskTitle(agent, { task, usage, time } = {}) {
150
+ let title = `call agent ${agent.name}`;
151
+ if (usage && task?.usage)
152
+ title += ` ${this.formatTokenUsage(task.usage, task.extraTitleMetadata)}`;
153
+ if (time && task?.startTime && task.endTime)
154
+ title += ` ${this.formatTimeUsage(task.startTime, task.endTime)}`;
155
+ return title;
156
+ }
157
+ }
158
+ class MyListr extends Listr {
159
+ taskPromise = Promise.withResolvers();
160
+ isTaskPromiseResolved = false;
161
+ resolveWaitingTask() {
162
+ if (!this.isTaskPromiseResolved) {
163
+ this.taskPromise.resolve();
164
+ this.isTaskPromiseResolved = true;
165
+ }
166
+ }
167
+ add(...args) {
168
+ const result = super.add(...args);
169
+ this.resolveWaitingTask();
170
+ return result;
171
+ }
172
+ async waitTaskAndRun(ctx) {
173
+ if (!this.tasks.length)
174
+ await this.taskPromise.promise;
175
+ if (!this.tasks.length)
176
+ return ctx;
177
+ return super.run(ctx);
178
+ }
179
+ }
@@ -0,0 +1 @@
1
+ export declare const asciiLogo: string;
@@ -0,0 +1,18 @@
1
+ import chalk from "chalk";
2
+ import gradient from "gradient-string";
3
+ import pkg from "../../package.json" with { type: "json" };
4
+ const modernGradient = gradient(["#4facfe", "#7367f0", "#f86aad"]);
5
+ const logo = `
6
+ _ ___ ____ _ _ _____
7
+ / \\ |_ _/ ___| \\ | | ____|
8
+ / _ \\ | | | _| \\| | _|
9
+ / ___ \\ | | |_| | |\\ | |___
10
+ /_/ \\_\\___\\____|_| \\_|_____|
11
+ `;
12
+ const frameworkInfo = `v${pkg.version}`;
13
+ const logoLines = logo.split("\n");
14
+ const maxLength = Math.max(...logoLines.filter((line) => line.trim()).map((line) => line.length));
15
+ const versionText = frameworkInfo;
16
+ const padding = Math.floor((maxLength - versionText.length) / 2);
17
+ const centeredVersion = " ".repeat(padding) + versionText;
18
+ export const asciiLogo = `${modernGradient(logo)}\n${chalk.cyan(centeredVersion)}\n\n`;
@@ -0,0 +1 @@
1
+ export declare function downloadAndExtract(url: string, dir: string): Promise<void>;
@@ -0,0 +1,21 @@
1
+ import { Readable } from "node:stream";
2
+ import { finished } from "node:stream/promises";
3
+ import { x } from "tar";
4
+ export async function downloadAndExtract(url, dir) {
5
+ const response = await fetch(url).catch((error) => {
6
+ throw new Error(`Failed to download package from ${url}: ${error.message}`);
7
+ });
8
+ if (!response.ok) {
9
+ throw new Error(`Failed to download package from ${url}: ${response.statusText}`);
10
+ }
11
+ if (!response.body) {
12
+ throw new Error(`Failed to download package from ${url}: Unexpected to get empty response`);
13
+ }
14
+ try {
15
+ await finished(Readable.fromWeb(response.body).pipe(x({ C: dir })));
16
+ }
17
+ catch (error) {
18
+ error.message = `Failed to extract package from ${url}: ${error.message}`;
19
+ throw error;
20
+ }
21
+ }
@@ -0,0 +1,9 @@
1
+ import { type Message, type UserAgent as input } from "@aigne/core";
2
+ export interface ChatLoopOptions {
3
+ initialCall?: Message | string;
4
+ welcome?: string;
5
+ defaultQuestion?: string;
6
+ inputKey?: string;
7
+ verbose?: boolean;
8
+ }
9
+ export declare function runChatLoopInTerminal(userAgent: input, options?: ChatLoopOptions): Promise<void>;
@@ -0,0 +1,73 @@
1
+ import { inspect } from "node:util";
2
+ import { MESSAGE_KEY, createMessage } from "@aigne/core";
3
+ import { logger } from "@aigne/core/utils/logger.js";
4
+ import { figures } from "@aigne/listr2";
5
+ import chalk from "chalk";
6
+ import inquirer from "inquirer";
7
+ import { TerminalTracer } from "../tracer/terminal.js";
8
+ export async function runChatLoopInTerminal(userAgent, options = {}) {
9
+ options.verbose ??= logger.enabled("aigne:core");
10
+ // Disable the logger, use TerminalTracer instead
11
+ logger.disable();
12
+ let prompt;
13
+ if (options?.welcome)
14
+ console.log(options.welcome);
15
+ if (options?.initialCall) {
16
+ await callAgent(userAgent, options.initialCall, { ...options });
17
+ }
18
+ for (let i = 0;; i++) {
19
+ prompt = inquirer.prompt([
20
+ {
21
+ type: "input",
22
+ name: "question",
23
+ message: "💬",
24
+ default: i === 0 ? options?.defaultQuestion : undefined,
25
+ },
26
+ ]);
27
+ let question;
28
+ try {
29
+ question = (await prompt).question;
30
+ }
31
+ catch {
32
+ // ignore abort error from inquirer
33
+ }
34
+ if (!question?.trim())
35
+ continue;
36
+ const cmd = COMMANDS[question.trim()];
37
+ if (cmd) {
38
+ const result = cmd();
39
+ if (result.message)
40
+ console.log(result.message);
41
+ if (result?.exit)
42
+ break;
43
+ continue;
44
+ }
45
+ await callAgent(userAgent, question, { ...options });
46
+ }
47
+ }
48
+ async function callAgent(userAgent, input, options) {
49
+ const tracer = new TerminalTracer(userAgent.context, { verbose: options.verbose });
50
+ const { result, context } = await tracer.run(userAgent, options.inputKey && typeof input === "string"
51
+ ? { [options.inputKey]: input }
52
+ : createMessage(input));
53
+ console.log(`
54
+ ${chalk.grey(figures.tick)} 💬 ${inspect(input, { colors: true })}
55
+ ${chalk.grey(figures.tick)} 🤖 ${tracer.formatTokenUsage(context.usage)}
56
+ ${formatAIResponse(result)}
57
+ `);
58
+ }
59
+ const COMMANDS = {
60
+ "/exit": () => ({ exit: true }),
61
+ "/help": () => ({
62
+ message: `\
63
+ Commands:
64
+ /exit - exit the chat loop
65
+ /help - show this help message
66
+ `,
67
+ }),
68
+ };
69
+ function formatAIResponse({ [MESSAGE_KEY]: msg, ...message } = {}) {
70
+ const text = msg && typeof msg === "string" ? msg : undefined;
71
+ const json = Object.keys(message).length > 0 ? inspect(message, { colors: true }) : undefined;
72
+ return [text, json].filter(Boolean).join("\n");
73
+ }
@@ -0,0 +1,5 @@
1
+ import { type ExecutionEngine } from "@aigne/core";
2
+ export declare function serveMCPServer({ engine, port }: {
3
+ engine: ExecutionEngine;
4
+ port: number;
5
+ }): Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
@@ -0,0 +1,71 @@
1
+ import { getMessage } from "@aigne/core";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ import express from "express";
5
+ import { ZodObject } from "zod";
6
+ export async function serveMCPServer({ engine, port }) {
7
+ const server = new McpServer({
8
+ name: engine.name || "aigne-mcp-server",
9
+ version: "1.0.0",
10
+ }, {
11
+ capabilities: { tools: {} },
12
+ instructions: engine.description,
13
+ });
14
+ for (const agent of engine.agents) {
15
+ const schema = agent.inputSchema;
16
+ if (!(schema instanceof ZodObject))
17
+ throw new Error("Agent input schema must be a ZodObject");
18
+ server.tool(agent.name, agent.description || "", schema.shape, async (input) => {
19
+ const result = await engine.call(agent, input);
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: getMessage(result) || JSON.stringify(result),
25
+ },
26
+ ],
27
+ };
28
+ });
29
+ }
30
+ const app = express();
31
+ const transports = {};
32
+ app.get("/sse", async (req, res) => {
33
+ const transport = new SSEServerTransport("/messages", res);
34
+ transports[transport.sessionId] = transport;
35
+ req.on("close", () => {
36
+ delete transports[transport.sessionId];
37
+ });
38
+ await server.connect(transport);
39
+ });
40
+ app.post("/messages", async (req, res) => {
41
+ const sessionId = req.query.sessionId;
42
+ const transport = transports[sessionId];
43
+ if (transport) {
44
+ await transport.handlePostMessage(req, res);
45
+ }
46
+ else {
47
+ throw new HttpError(400, "No transport found for sessionId");
48
+ }
49
+ });
50
+ app.use(((error, _req, res, _next) => {
51
+ console.error("handle route error", { error });
52
+ res
53
+ .status(error instanceof HttpError ? error.status : 500)
54
+ .json({ error: { message: error.message } });
55
+ }));
56
+ const { promise, resolve, reject } = Promise.withResolvers();
57
+ const httpServer = app.listen(port, (error) => {
58
+ if (error)
59
+ reject(error);
60
+ resolve();
61
+ });
62
+ await promise;
63
+ return httpServer;
64
+ }
65
+ class HttpError extends Error {
66
+ status;
67
+ constructor(status, message) {
68
+ super(message);
69
+ this.status = status;
70
+ }
71
+ }
@@ -0,0 +1 @@
1
+ export declare function parseDuration(duration: number): string;
@@ -0,0 +1,12 @@
1
+ export function parseDuration(duration) {
2
+ const milliseconds = duration % 1000;
3
+ const seconds = Math.floor(duration / 1000);
4
+ const minutes = Math.floor(seconds / 60);
5
+ const ms = Math.round(milliseconds / 10)
6
+ .toString()
7
+ .padStart(2, "0");
8
+ const s = `${Number.parseFloat(`${seconds % 60}.${ms}`)}s`;
9
+ if (minutes === 0)
10
+ return s;
11
+ return `${minutes}m${s}`;
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.0.0-9",
3
+ "version": "1.2.0",
4
4
  "description": "cli for AIGNE framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,24 +22,53 @@
22
22
  "bin": {
23
23
  "aigne": "dist/cli.js"
24
24
  },
25
+ "type": "module",
26
+ "exports": {
27
+ "./*": "./dist/*"
28
+ },
29
+ "typesVersions": {
30
+ "*": {
31
+ "*": [
32
+ "./dist/*"
33
+ ]
34
+ }
35
+ },
25
36
  "dependencies": {
37
+ "@aigne/listr2": "^1.0.7",
38
+ "@modelcontextprotocol/sdk": "^1.9.0",
39
+ "chalk": "^5.4.1",
26
40
  "commander": "^13.1.0",
27
- "inquirer": "^12.5.0",
28
- "openai": "^4.89.1",
29
- "@aigne/core": "^1.5.0"
41
+ "express": "^5.1.0",
42
+ "gradient-string": "^3.0.0",
43
+ "inquirer": "^12.5.2",
44
+ "openai": "^4.93.0",
45
+ "pretty-error": "^4.0.0",
46
+ "tar": "^7.4.3",
47
+ "zod": "^3.24.2",
48
+ "@aigne/core": "^1.7.0"
30
49
  },
31
50
  "devDependencies": {
32
- "@types/bun": "^1.2.6",
33
- "@types/node": "^22.13.14",
51
+ "@types/archiver": "^6.0.3",
52
+ "@types/bun": "^1.2.9",
53
+ "@types/express": "^5.0.1",
54
+ "@types/gradient-string": "^1.1.6",
55
+ "@types/node": "^22.14.0",
56
+ "archiver": "^7.0.1",
57
+ "detect-port": "^2.1.0",
34
58
  "npm-run-all": "^4.1.5",
35
59
  "rimraf": "^6.0.1",
36
- "typescript": "^5.8.2"
60
+ "typescript": "^5.8.3",
61
+ "ufo": "^1.6.1"
37
62
  },
38
63
  "scripts": {
39
64
  "lint": "tsc --noEmit",
40
65
  "build": "tsc --build tsconfig.build.json",
41
- "clean": "rimraf dist coverage",
42
- "test": "bun test",
43
- "test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text"
66
+ "clean": "rimraf dist test/coverage templates/coverage",
67
+ "test": "run-s test:src test:templates",
68
+ "test:coverage": "run-s test:src:coverage test:templates:coverage",
69
+ "test:src": "bun --cwd test test",
70
+ "test:src:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
71
+ "test:templates": "cd templates && node --test",
72
+ "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"
44
73
  }
45
74
  }
@@ -1,5 +1,9 @@
1
1
  chat_model:
2
+ provider: openai
2
3
  name: gpt-4o-mini
3
4
  temperature: 0.8
4
5
  agents:
5
6
  - chat.yaml
7
+ tools:
8
+ - sandbox.js
9
+ - filesystem.yaml
@@ -0,0 +1,3 @@
1
+ type: mcp
2
+ command: npx
3
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "."]