@aigne/cli 1.52.0-beta → 1.52.0-beta.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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.52.0-beta.1](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.52.0-beta...cli-v1.52.0-beta.1) (2025-10-22)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * add validate/required support for terminal input ([#651](https://github.com/AIGNE-io/aigne-framework/issues/651)) ([3d7f94c](https://github.com/AIGNE-io/aigne-framework/commit/3d7f94c32c8ec7bebb8f71fb16ddd3dd74a2d255))
9
+ * improve model name parsing to handle complex model identifiers ([#654](https://github.com/AIGNE-io/aigne-framework/issues/654)) ([4b7faea](https://github.com/AIGNE-io/aigne-framework/commit/4b7faea97f33db34a51c49dde3d6c1cf2679f0cd))
10
+
11
+
12
+ ### Dependencies
13
+
14
+ * The following workspace dependencies were updated
15
+ * dependencies
16
+ * @aigne/afs-system-fs bumped to 1.0.2-beta.1
17
+ * @aigne/agent-library bumped to 1.21.49-beta.1
18
+ * @aigne/agentic-memory bumped to 1.0.49-beta.1
19
+ * @aigne/aigne-hub bumped to 0.10.3-beta.1
20
+ * @aigne/core bumped to 1.64.0-beta.1
21
+ * @aigne/default-memory bumped to 1.2.12-beta.1
22
+ * @aigne/openai bumped to 0.16.3-beta.1
23
+ * devDependencies
24
+ * @aigne/test-utils bumped to 0.5.56-beta.1
25
+
3
26
  ## [1.52.0-beta](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.51.0...cli-v1.52.0-beta) (2025-10-21)
4
27
 
5
28
 
@@ -1,5 +1,18 @@
1
- export declare function terminalInput(options?: {
1
+ import { render } from "ink";
2
+ export declare function terminalInput({ render: r, ...options }?: {
2
3
  message?: string;
3
4
  default?: string;
4
5
  inline?: boolean;
6
+ required?: boolean;
7
+ validate?: (input: string) => string | boolean | Promise<string | boolean>;
8
+ render?: typeof render;
5
9
  }): Promise<string>;
10
+ export declare function TerminalInput(props: {
11
+ message?: string;
12
+ default?: string;
13
+ inline?: boolean;
14
+ required?: boolean;
15
+ validate?: (input: string) => string | boolean | Promise<string | boolean>;
16
+ onSubmit: (input: string) => void;
17
+ onError: (error: Error) => void;
18
+ }): import("react/jsx-runtime").JSX.Element;
@@ -2,30 +2,37 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ExitPromptError } from "@inquirer/core";
3
3
  import chalk from "chalk";
4
4
  import { Box, render, Text, useInput } from "ink";
5
- import { useState } from "react";
5
+ import { useRef, useState } from "react";
6
6
  import { useTextBuffer } from "./text-buffer.js";
7
- export async function terminalInput(options = {}) {
7
+ export async function terminalInput({ render: r = render, ...options } = {}) {
8
8
  return new Promise((resolve, reject) => {
9
- process.addListener("SIGINT", () => {
9
+ const handleSigInt = () => {
10
10
  reject(new Error("Input aborted"));
11
- });
12
- const app = render(_jsx(Input, { ...options, onSubmit: (value) => {
11
+ };
12
+ process.addListener("SIGINT", handleSigInt);
13
+ const clean = () => process.removeListener("SIGINT", handleSigInt);
14
+ const app = r(_jsx(TerminalInput, { ...options, onSubmit: (value) => {
13
15
  app.unmount();
14
16
  resolve(value);
17
+ clean();
15
18
  }, onError: (error) => {
16
19
  app.unmount();
17
20
  reject(error);
21
+ clean();
18
22
  } }), { exitOnCtrlC: false });
19
23
  });
20
24
  }
21
- function Input(props) {
25
+ export function TerminalInput(props) {
22
26
  const buffer = useTextBuffer({
23
27
  initialText: props.default || "",
24
28
  initialCursorOffset: props.default?.length || 0,
25
29
  isValidPath: () => false,
26
30
  viewport: { width: 80, height: 1 },
27
31
  });
32
+ const textRef = useRef(buffer.text);
33
+ textRef.current = buffer.text;
28
34
  const [status, setStatus] = useState("input");
35
+ const [errorMessage, setErrorMessage] = useState();
29
36
  useInput((character, key) => {
30
37
  if (character === "c" && key.ctrl) {
31
38
  setStatus("error");
@@ -35,10 +42,37 @@ function Input(props) {
35
42
  return;
36
43
  }
37
44
  if (key.return) {
38
- setStatus("success");
39
- setTimeout(() => {
40
- props.onSubmit(buffer.text);
41
- });
45
+ const input = textRef.current || props.default || "";
46
+ setStatus("validating");
47
+ setErrorMessage(undefined);
48
+ // Handle validation
49
+ const validateInput = async () => {
50
+ try {
51
+ // Check required validation first
52
+ if (props.required && !input.trim()) {
53
+ setErrorMessage("You must provide a value");
54
+ setStatus("input");
55
+ return;
56
+ }
57
+ // Run custom validation if provided
58
+ if (props.validate) {
59
+ const result = await props.validate(input);
60
+ if (result !== true) {
61
+ setErrorMessage(typeof result === "string" ? result : "You must provide a valid value");
62
+ setStatus("input");
63
+ return;
64
+ }
65
+ }
66
+ // Validation passed
67
+ setStatus("success");
68
+ props.onSubmit(input);
69
+ }
70
+ catch (error) {
71
+ setErrorMessage(error instanceof Error ? error.message : "Validation error");
72
+ setStatus("input");
73
+ }
74
+ };
75
+ validateInput();
42
76
  return;
43
77
  }
44
78
  else if (key.backspace)
@@ -59,6 +93,7 @@ function Input(props) {
59
93
  buffer.move("end");
60
94
  else {
61
95
  buffer.handleInput({ ...key, name: character, sequence: character, paste: false });
96
+ setErrorMessage(undefined);
62
97
  }
63
98
  });
64
99
  const lines = [...buffer.lines];
@@ -72,10 +107,11 @@ function Input(props) {
72
107
  }
73
108
  const label = props.message && chalk.bold(props.message);
74
109
  const inline = props.inline !== false;
75
- return (_jsxs(Box, { flexDirection: inline ? "row" : "column", children: [_jsxs(Text, { children: [PREFIX[status], " ", !inline && label] }), _jsx(Box, { flexShrink: 1, flexGrow: 1, marginLeft: inline ? 0 : 2, marginRight: 1, children: _jsxs(Text, { children: [!!label && inline && `${label} `, lines.join("\n")] }) })] }));
110
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: inline ? "row" : "column", children: [_jsxs(Text, { children: [PREFIX[status], " ", !inline && label] }), _jsx(Box, { flexShrink: 1, flexGrow: 1, marginLeft: inline ? 0 : 2, marginRight: 1, children: _jsxs(Text, { children: [!!label && inline && `${label} `, lines.join("\n")] }) })] }), errorMessage && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "red", children: errorMessage }) }))] }));
76
111
  }
77
112
  const PREFIX = {
78
113
  input: chalk.blue("?"),
114
+ validating: chalk.yellow("⋯"),
79
115
  success: chalk.green("✔"),
80
116
  error: chalk.red("✘"),
81
117
  };
@@ -1,10 +1,6 @@
1
1
  import type { ChatModel, ChatModelInputOptions, ImageModel, ImageModelInputOptions } from "@aigne/core";
2
2
  import type { LoadCredentialOptions } from "./type.js";
3
3
  export declare function maskApiKey(apiKey?: string): string | undefined;
4
- export declare const parseModelOption: (model: string) => {
5
- provider: string | undefined;
6
- model: string | undefined;
7
- };
8
4
  export declare const formatModelName: (model: string, inquirerPrompt: NonNullable<LoadCredentialOptions["inquirerPromptFn"]>) => Promise<{
9
5
  provider: string;
10
6
  model?: string;
@@ -1,5 +1,5 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
- import { AIGNE_HUB_DEFAULT_MODEL, AIGNE_HUB_URL, findImageModel, findModel, } from "@aigne/aigne-hub";
2
+ import { AIGNE_HUB_DEFAULT_MODEL, AIGNE_HUB_URL, findImageModel, findModel, parseModel, } from "@aigne/aigne-hub";
3
3
  import { flat, omit } from "@aigne/core/utils/type-utils.js";
4
4
  import chalk from "chalk";
5
5
  import inquirer from "inquirer";
@@ -13,13 +13,8 @@ export function maskApiKey(apiKey) {
13
13
  const end = apiKey.slice(-4);
14
14
  return `${start}${"*".repeat(8)}${end}`;
15
15
  }
16
- export const parseModelOption = (model) => {
17
- model = model.replace(":", "/");
18
- const { provider, name } = model.match(/(?<provider>[^/]*)(\/(?<name>.*))?/)?.groups ?? {};
19
- return { provider: provider?.replace(/-/g, ""), model: name };
20
- };
21
16
  export const formatModelName = async (model, inquirerPrompt) => {
22
- let { provider, model: name } = parseModelOption(model);
17
+ let { provider, model: name } = parseModel(model);
23
18
  provider ||= AIGNE_HUB_PROVIDER;
24
19
  const { match, all } = findModel(provider);
25
20
  if (!match) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.52.0-beta",
3
+ "version": "1.52.0-beta.1",
4
4
  "description": "Your command center for agent development",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -89,14 +89,14 @@
89
89
  "yoctocolors-cjs": "^2.1.3",
90
90
  "zod": "^3.25.67",
91
91
  "zod-to-json-schema": "^3.24.6",
92
- "@aigne/afs-system-fs": "^1.0.2-beta",
93
- "@aigne/agentic-memory": "^1.0.49-beta",
94
- "@aigne/core": "^1.64.0-beta",
95
- "@aigne/aigne-hub": "^0.10.3-beta",
96
- "@aigne/agent-library": "^1.21.49-beta",
97
- "@aigne/default-memory": "^1.2.12-beta",
98
- "@aigne/observability-api": "^0.11.3-beta",
99
- "@aigne/openai": "^0.16.3-beta"
92
+ "@aigne/afs-system-fs": "^1.0.2-beta.1",
93
+ "@aigne/agent-library": "^1.21.49-beta.1",
94
+ "@aigne/agentic-memory": "^1.0.49-beta.1",
95
+ "@aigne/core": "^1.64.0-beta.1",
96
+ "@aigne/aigne-hub": "^0.10.3-beta.1",
97
+ "@aigne/default-memory": "^1.2.12-beta.1",
98
+ "@aigne/openai": "^0.16.3-beta.1",
99
+ "@aigne/observability-api": "^0.11.3-beta"
100
100
  },
101
101
  "devDependencies": {
102
102
  "@inquirer/testing": "^2.1.50",
@@ -109,11 +109,12 @@
109
109
  "@types/yargs": "^17.0.33",
110
110
  "archiver": "^7.0.1",
111
111
  "hono": "4.8.4",
112
+ "ink-testing-library": "^4.0.0",
112
113
  "npm-run-all": "^4.1.5",
113
114
  "rimraf": "^6.0.1",
114
115
  "typescript": "^5.9.2",
115
116
  "ufo": "^1.6.1",
116
- "@aigne/test-utils": "^0.5.56-beta"
117
+ "@aigne/test-utils": "^0.5.56-beta.1"
117
118
  },
118
119
  "scripts": {
119
120
  "lint": "tsc --noEmit",