@aigne/cli 1.0.0-9 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.0.0...cli-v1.1.0) (2025-04-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * 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))
9
+ * 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 {};
@@ -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;
@@ -19,15 +19,13 @@ export function createRunCommand() {
19
19
  for (const agent of engine.agents) {
20
20
  console.log(`- ${agent.name}`);
21
21
  }
22
- process.exit(1);
22
+ throw new Error(`Agent "${options.agent}" not found`);
23
23
  }
24
24
  }
25
25
  else {
26
26
  agent = engine.agents[0];
27
- if (!agent) {
28
- console.error("No agents found in the specified path.");
29
- process.exit(1);
30
- }
27
+ if (!agent)
28
+ throw new Error("No agents found in the specified path");
31
29
  }
32
30
  const user = engine.call(agent);
33
31
  await runChatLoopInTerminal(user, {});
@@ -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 @@
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,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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.0.0-9",
3
+ "version": "1.1.0",
4
4
  "description": "cli for AIGNE framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,24 +22,49 @@
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
+ "@modelcontextprotocol/sdk": "^1.8.0",
38
+ "chalk": "^5.4.1",
26
39
  "commander": "^13.1.0",
40
+ "express": "^5.1.0",
41
+ "gradient-string": "^3.0.0",
27
42
  "inquirer": "^12.5.0",
28
- "openai": "^4.89.1",
29
- "@aigne/core": "^1.5.0"
43
+ "openai": "^4.91.1",
44
+ "zod": "^3.24.2",
45
+ "@aigne/core": "^1.6.0"
30
46
  },
31
47
  "devDependencies": {
32
- "@types/bun": "^1.2.6",
33
- "@types/node": "^22.13.14",
48
+ "@types/bun": "^1.2.8",
49
+ "@types/express": "^5.0.1",
50
+ "@types/gradient-string": "^1.1.6",
51
+ "@types/node": "^22.14.0",
52
+ "detect-port": "^2.1.0",
34
53
  "npm-run-all": "^4.1.5",
35
54
  "rimraf": "^6.0.1",
36
- "typescript": "^5.8.2"
55
+ "typescript": "^5.8.2",
56
+ "ufo": "^1.6.0",
57
+ "@aigne/test-utils": "^1.0.0"
37
58
  },
38
59
  "scripts": {
39
60
  "lint": "tsc --noEmit",
40
61
  "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"
62
+ "clean": "rimraf dist test/coverage templates/coverage",
63
+ "test": "run-s test:src test:templates",
64
+ "test:coverage": "run-s test:src:coverage test:templates:coverage",
65
+ "test:src": "bun --cwd test test",
66
+ "test:src:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
67
+ "test:templates": "cd templates && node --test",
68
+ "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
69
  }
45
70
  }
@@ -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", "."]