@aigne/cli 1.41.3 → 1.42.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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.42.0](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.41.3...cli-v1.42.0) (2025-08-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * **cli:** add searchable checkbox component with dynamic filtering ([#426](https://github.com/AIGNE-io/aigne-framework/issues/426)) ([1a76fe7](https://github.com/AIGNE-io/aigne-framework/commit/1a76fe7c2f7d91bc4041dfcd73850b39a18a036b))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **cli:** only show ascii logo on help and errors ([#425](https://github.com/AIGNE-io/aigne-framework/issues/425)) ([1279376](https://github.com/AIGNE-io/aigne-framework/commit/1279376b7ca9c1c38148dcde581ee4730771a4ad))
14
+
15
+
16
+ ### Dependencies
17
+
18
+ * The following workspace dependencies were updated
19
+ * dependencies
20
+ * @aigne/agent-library bumped to 1.21.31
21
+ * @aigne/agentic-memory bumped to 1.0.31
22
+ * @aigne/aigne-hub bumped to 0.8.1
23
+ * @aigne/core bumped to 1.57.0
24
+ * @aigne/default-memory bumped to 1.1.13
25
+ * @aigne/openai bumped to 0.13.2
26
+ * devDependencies
27
+ * @aigne/test-utils bumped to 0.5.38
28
+
3
29
  ## [1.41.3](https://github.com/AIGNE-io/aigne-framework/compare/cli-v1.41.2...cli-v1.41.3) (2025-08-27)
4
30
 
5
31
 
package/dist/cli.js CHANGED
@@ -23,6 +23,7 @@ export default createAIGNECommand({ aigneFilePath })
23
23
  if (!error) {
24
24
  yargs.showHelp();
25
25
  console.error(`\n${message}`);
26
+ process.exit(1);
26
27
  }
27
28
  })
28
29
  .parseAsync(hideBin([...process.argv.slice(0, 2), ...process.argv.slice(aigneFilePath ? 3 : 2)]))
@@ -10,10 +10,9 @@ import { createRunCommand } from "./run.js";
10
10
  import { createServeMCPCommand } from "./serve-mcp.js";
11
11
  import { createTestCommand } from "./test.js";
12
12
  export function createAIGNECommand(options) {
13
- console.log(asciiLogo);
14
13
  return yargs()
15
14
  .scriptName("aigne")
16
- .usage("CLI for AIGNE framework")
15
+ .usage(`${asciiLogo}\n$0 <command> [options]`)
17
16
  .version(AIGNE_CLI_VERSION)
18
17
  .command(createRunCommand(options))
19
18
  .command(createTestCommand(options))
@@ -26,5 +25,6 @@ export function createAIGNECommand(options) {
26
25
  .demandCommand()
27
26
  .alias("help", "h")
28
27
  .alias("version", "v")
28
+ .wrap(null)
29
29
  .strict();
30
30
  }
@@ -9,6 +9,7 @@ import * as prompts from "@inquirer/prompts";
9
9
  import chalk from "chalk";
10
10
  import { Marked } from "marked";
11
11
  import { AIGNE_HUB_CREDITS_NOT_ENOUGH_ERROR_TYPE } from "../constants.js";
12
+ import checkbox from "../utils/inquirer/checkbox.js";
12
13
  import { AIGNEListr, AIGNEListrRenderer } from "../utils/listr.js";
13
14
  import { highlightUrl } from "../utils/string-utils.js";
14
15
  import { parseDuration } from "../utils/time.js";
@@ -188,10 +189,12 @@ export class TerminalTracer {
188
189
  listr;
189
190
  proxiedPrompts = new Proxy({}, {
190
191
  get: (_target, prop) => {
191
- // biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
192
- const method = prompts[prop];
192
+ const method = prop === "checkbox"
193
+ ? checkbox
194
+ : // biome-ignore lint/performance/noDynamicNamespaceImportAccess: we need to access prompts dynamically
195
+ prompts[prop];
193
196
  if (typeof method !== "function")
194
- return undefined;
197
+ throw new Error(`Unsupported prompt method ${String(prop)}`);
195
198
  return async (config) => {
196
199
  const renderer = this.listr?.["renderer"] instanceof AIGNEListrRenderer
197
200
  ? this.listr["renderer"]
@@ -0,0 +1,55 @@
1
+ import { Separator, type Theme } from "@inquirer/core";
2
+ import type { PartialDeep } from "@inquirer/type";
3
+ type CheckboxTheme = {
4
+ icon: {
5
+ checked: string;
6
+ unchecked: string;
7
+ cursor: string;
8
+ };
9
+ style: {
10
+ disabledChoice: (text: string) => string;
11
+ renderSelectedChoices: <T>(selectedChoices: ReadonlyArray<NormalizedChoice<T>>, allChoices: ReadonlyArray<NormalizedChoice<T> | Separator>) => string;
12
+ description: (text: string) => string;
13
+ searchTerm: (text: string) => string;
14
+ };
15
+ helpMode: "always" | "never" | "auto";
16
+ };
17
+ type CheckboxShortcuts = {
18
+ all?: string | null;
19
+ invert?: string | null;
20
+ };
21
+ type Choice<Value> = {
22
+ value: Value;
23
+ name?: string;
24
+ description?: string;
25
+ short?: string;
26
+ disabled?: boolean | string;
27
+ checked?: boolean;
28
+ type?: never;
29
+ };
30
+ type NormalizedChoice<Value> = {
31
+ value: Value;
32
+ name: string;
33
+ description?: string;
34
+ short: string;
35
+ disabled: boolean | string;
36
+ checked: boolean;
37
+ };
38
+ declare const _default: <Value>(config: {
39
+ message: string;
40
+ prefix?: string | undefined;
41
+ pageSize?: number | undefined;
42
+ instructions?: string | boolean | undefined;
43
+ choices?: readonly (string | Separator)[] | readonly (Separator | Choice<Value>)[] | undefined;
44
+ source?: ((term: string | undefined, opt: {
45
+ signal: AbortSignal;
46
+ }) => readonly (string | Separator)[] | Promise<readonly (string | Separator)[]> | readonly (Separator | Choice<Value>)[] | Promise<readonly (Separator | Choice<Value>)[]>) | undefined;
47
+ loop?: boolean | undefined;
48
+ required?: boolean | undefined;
49
+ validate?: ((choices: readonly Choice<Value>[]) => boolean | string | Promise<string | boolean>) | undefined;
50
+ theme?: PartialDeep<Theme<CheckboxTheme>> | undefined;
51
+ shortcuts?: CheckboxShortcuts | undefined;
52
+ }, context?: import("@inquirer/type").Context) => Promise<Value[]> & {
53
+ cancel: () => void;
54
+ };
55
+ export default _default;
@@ -0,0 +1,306 @@
1
+ import { createPrompt, isDownKey, isEnterKey, isNumberKey, isSpaceKey, isUpKey, makeTheme, Separator, useEffect, useKeypress, useMemo, usePagination, usePrefix, useRef, useState, ValidationError, } from "@inquirer/core";
2
+ import figures from "@inquirer/figures";
3
+ import ansiEscapes from "ansi-escapes";
4
+ import colors from "yoctocolors-cjs";
5
+ const checkboxTheme = {
6
+ icon: {
7
+ checked: colors.green(figures.circleFilled),
8
+ unchecked: figures.circle,
9
+ cursor: figures.pointer,
10
+ },
11
+ style: {
12
+ disabledChoice: (text) => colors.dim(`- ${text}`),
13
+ renderSelectedChoices: (selectedChoices) => selectedChoices.map((choice) => choice.short).join(", "),
14
+ description: (text) => colors.cyan(text),
15
+ searchTerm: (text) => colors.cyan(text),
16
+ },
17
+ helpMode: "auto",
18
+ };
19
+ function isSelectable(item) {
20
+ return !Separator.isSeparator(item) && !item.disabled;
21
+ }
22
+ function isChecked(item) {
23
+ return isSelectable(item) && item.checked;
24
+ }
25
+ function toggle(item) {
26
+ return isSelectable(item) ? { ...item, checked: !item.checked } : item;
27
+ }
28
+ function check(checked) {
29
+ return (item) => isSelectable(item) ? { ...item, checked } : item;
30
+ }
31
+ function normalizeChoices(choices) {
32
+ return choices.map((choice) => {
33
+ if (Separator.isSeparator(choice))
34
+ return choice;
35
+ if (typeof choice === "string") {
36
+ return {
37
+ value: choice,
38
+ name: choice,
39
+ short: choice,
40
+ disabled: false,
41
+ checked: false,
42
+ };
43
+ }
44
+ const name = choice.name ?? String(choice.value);
45
+ const normalizedChoice = {
46
+ value: choice.value,
47
+ name,
48
+ short: choice.short ?? name,
49
+ disabled: choice.disabled ?? false,
50
+ checked: choice.checked ?? false,
51
+ };
52
+ if (choice.description) {
53
+ normalizedChoice.description = choice.description;
54
+ }
55
+ return normalizedChoice;
56
+ });
57
+ }
58
+ export default createPrompt((config, done) => {
59
+ const { instructions, pageSize = 7, loop = true, required, validate = () => true } = config;
60
+ const shortcuts = { all: "a", invert: "i", ...config.shortcuts };
61
+ const theme = makeTheme(checkboxTheme, config.theme);
62
+ const firstRender = useRef(true);
63
+ const [status, setStatus] = useState(config.source ? "loading" : "idle");
64
+ const prefix = usePrefix({ status, theme });
65
+ const [searchTerm, setSearchTerm] = useState("");
66
+ const [searchError, setSearchError] = useState();
67
+ const initialItems = config.choices ? normalizeChoices(config.choices) : [];
68
+ const initialSelectedValues = new Set(initialItems
69
+ .filter((item) => !Separator.isSeparator(item) && item.checked)
70
+ .map((item) => item.value));
71
+ const [selectedValues, setSelectedValues] = useState(initialSelectedValues);
72
+ const [items, setItems] = useState(initialItems);
73
+ useEffect(() => {
74
+ const { source } = config;
75
+ if (!source)
76
+ return;
77
+ const controller = new AbortController();
78
+ setStatus("loading");
79
+ setSearchError(undefined);
80
+ const fetchResults = async () => {
81
+ try {
82
+ const results = await source(searchTerm || undefined, {
83
+ signal: controller.signal,
84
+ });
85
+ if (!controller.signal.aborted) {
86
+ const normalizedResults = normalizeChoices(results);
87
+ // Preserve selected state from selectedValues
88
+ const itemsWithSelection = normalizedResults.map((item) => {
89
+ if (!Separator.isSeparator(item)) {
90
+ return {
91
+ ...item,
92
+ checked: selectedValues.has(item.value),
93
+ };
94
+ }
95
+ return item;
96
+ });
97
+ setItems(itemsWithSelection);
98
+ // Reset active to first selectable item after search
99
+ const firstSelectable = itemsWithSelection.findIndex(isSelectable);
100
+ if (firstSelectable >= 0) {
101
+ setActive(firstSelectable);
102
+ }
103
+ setSearchError(undefined);
104
+ setStatus("idle");
105
+ }
106
+ }
107
+ catch (error) {
108
+ if (!controller.signal.aborted && error instanceof Error) {
109
+ setSearchError(error.message);
110
+ }
111
+ }
112
+ };
113
+ void fetchResults();
114
+ return () => {
115
+ controller.abort();
116
+ };
117
+ }, [searchTerm, config.source]);
118
+ const bounds = useMemo(() => {
119
+ const first = items.findIndex(isSelectable);
120
+ const last = items.findLastIndex(isSelectable);
121
+ if (first === -1 && !config.source && status !== "loading") {
122
+ throw new ValidationError("[checkbox prompt] No selectable choices. All choices are disabled.");
123
+ }
124
+ return { first, last };
125
+ }, [items, config.source, status]);
126
+ const [active, setActive] = useState(bounds.first >= 0 ? bounds.first : 0);
127
+ const [showHelpTip, setShowHelpTip] = useState(true);
128
+ const [errorMsg, setError] = useState();
129
+ useKeypress(async (key, rl) => {
130
+ if (isEnterKey(key)) {
131
+ const selection = items.filter(isChecked);
132
+ const isValid = await validate([...selection]);
133
+ if (required && !items.some(isChecked)) {
134
+ setError("At least one choice must be selected");
135
+ }
136
+ else if (isValid === true) {
137
+ setStatus("done");
138
+ done(selection.map((choice) => choice.value));
139
+ }
140
+ else {
141
+ setError(isValid || "You must select a valid value");
142
+ }
143
+ }
144
+ else if (isUpKey(key) || isDownKey(key)) {
145
+ if (loop ||
146
+ (isUpKey(key) && active !== bounds.first) ||
147
+ (isDownKey(key) && active !== bounds.last)) {
148
+ const offset = isUpKey(key) ? -1 : 1;
149
+ let next = active;
150
+ do {
151
+ next = (next + offset + items.length) % items.length;
152
+ // biome-ignore lint/style/noNonNullAssertion: we need to access items dynamically
153
+ } while (!isSelectable(items[next]));
154
+ setActive(next);
155
+ }
156
+ }
157
+ else if (isSpaceKey(key)) {
158
+ setError(undefined);
159
+ setShowHelpTip(false);
160
+ if (config.source) {
161
+ // In search mode, prevent space from being added to search term
162
+ rl.clearLine(0);
163
+ rl.write(searchTerm); // Restore search term without the space
164
+ }
165
+ const activeItem = items[active];
166
+ if (activeItem && isSelectable(activeItem)) {
167
+ const newSelectedValues = new Set(selectedValues);
168
+ if (selectedValues.has(activeItem.value)) {
169
+ newSelectedValues.delete(activeItem.value);
170
+ }
171
+ else {
172
+ newSelectedValues.add(activeItem.value);
173
+ }
174
+ setSelectedValues(newSelectedValues);
175
+ }
176
+ setItems(items.map((choice, i) => (i === active ? toggle(choice) : choice)));
177
+ }
178
+ else if (key.name === shortcuts.all && !config.source) {
179
+ const selectAll = items.some((choice) => isSelectable(choice) && !choice.checked);
180
+ const newSelectedValues = new Set();
181
+ if (selectAll) {
182
+ items.forEach((item) => {
183
+ if (isSelectable(item)) {
184
+ newSelectedValues.add(item.value);
185
+ }
186
+ });
187
+ }
188
+ setSelectedValues(newSelectedValues);
189
+ setItems(items.map(check(selectAll)));
190
+ }
191
+ else if (key.name === shortcuts.invert && !config.source) {
192
+ const newSelectedValues = new Set();
193
+ items.forEach((item) => {
194
+ if (isSelectable(item)) {
195
+ if (!selectedValues.has(item.value)) {
196
+ newSelectedValues.add(item.value);
197
+ }
198
+ }
199
+ });
200
+ setSelectedValues(newSelectedValues);
201
+ setItems(items.map(toggle));
202
+ }
203
+ else if (isNumberKey(key) && !config.source) {
204
+ const selectedIndex = Number(key.name) - 1;
205
+ // Find the nth item (ignoring separators)
206
+ let selectableIndex = -1;
207
+ const position = items.findIndex((item) => {
208
+ if (Separator.isSeparator(item))
209
+ return false;
210
+ selectableIndex++;
211
+ return selectableIndex === selectedIndex;
212
+ });
213
+ const selectedItem = items[position];
214
+ if (selectedItem && isSelectable(selectedItem)) {
215
+ setActive(position);
216
+ const newSelectedValues = new Set(selectedValues);
217
+ if (selectedValues.has(selectedItem.value)) {
218
+ newSelectedValues.delete(selectedItem.value);
219
+ }
220
+ else {
221
+ newSelectedValues.add(selectedItem.value);
222
+ }
223
+ setSelectedValues(newSelectedValues);
224
+ setItems(items.map((choice, i) => (i === position ? toggle(choice) : choice)));
225
+ }
226
+ }
227
+ else if (config.source && !isSpaceKey(key)) {
228
+ setSearchTerm(rl.line);
229
+ }
230
+ });
231
+ const message = theme.style.message(config.message, status);
232
+ let description;
233
+ const page = usePagination({
234
+ items,
235
+ active,
236
+ renderItem({ item, isActive }) {
237
+ if (Separator.isSeparator(item)) {
238
+ return ` ${item.separator}`;
239
+ }
240
+ if (item.disabled) {
241
+ const disabledLabel = typeof item.disabled === "string" ? item.disabled : "(disabled)";
242
+ return theme.style.disabledChoice(`${item.name} ${disabledLabel}`);
243
+ }
244
+ if (isActive) {
245
+ description = item.description;
246
+ }
247
+ const checkbox = item.checked ? theme.icon.checked : theme.icon.unchecked;
248
+ const color = isActive ? theme.style.highlight : (x) => x;
249
+ const cursor = isActive ? theme.icon.cursor : " ";
250
+ return color(`${cursor}${checkbox} ${item.name}`);
251
+ },
252
+ pageSize,
253
+ loop,
254
+ });
255
+ if (status === "done") {
256
+ const selection = items.filter(isChecked);
257
+ const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
258
+ return `${prefix} ${message} ${answer}`;
259
+ }
260
+ let helpTipTop = "";
261
+ let helpTipBottom = "";
262
+ if (theme.helpMode === "always" ||
263
+ (theme.helpMode === "auto" && showHelpTip && (instructions === undefined || instructions))) {
264
+ if (typeof instructions === "string") {
265
+ helpTipTop = instructions;
266
+ }
267
+ else {
268
+ const keys = [
269
+ `${theme.style.key("space")} to select`,
270
+ !config.source && shortcuts.all ? `${theme.style.key(shortcuts.all)} to toggle all` : "",
271
+ !config.source && shortcuts.invert
272
+ ? `${theme.style.key(shortcuts.invert)} to invert selection`
273
+ : "",
274
+ `and ${theme.style.key("enter")} to proceed`,
275
+ ];
276
+ helpTipTop = ` (Press ${keys.filter((key) => key !== "").join(", ")})`;
277
+ }
278
+ if (items.length > pageSize &&
279
+ (theme.helpMode === "always" ||
280
+ (theme.helpMode === "auto" && (firstRender.current || config.source)))) {
281
+ helpTipBottom = `\n${theme.style.help("(Use arrow keys to reveal more choices)")}`;
282
+ if (!config.source) {
283
+ firstRender.current = false;
284
+ }
285
+ }
286
+ }
287
+ const choiceDescription = description ? `\n${theme.style.description(description)}` : ``;
288
+ let error = "";
289
+ if (searchError) {
290
+ error = `\n${theme.style.error(searchError)}`;
291
+ }
292
+ else if (config.source && items.length === 0 && searchTerm !== "" && status === "idle") {
293
+ error = `\n${theme.style.error("No results found")}`;
294
+ }
295
+ else if (errorMsg) {
296
+ error = `\n${theme.style.error(errorMsg)}`;
297
+ }
298
+ if (config.source) {
299
+ const searchStr = theme.style.searchTerm(searchTerm);
300
+ return [
301
+ [prefix, message, searchStr].filter(Boolean).join(" "),
302
+ `${error || page}${helpTipBottom}${choiceDescription}`,
303
+ ];
304
+ }
305
+ return `${prefix} ${message}${helpTipTop}\n${page}${helpTipBottom}${choiceDescription}${error}${ansiEscapes.cursorHide}`;
306
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/cli",
3
- "version": "1.41.3",
3
+ "version": "1.42.0",
4
4
  "description": "Your command center for agent development",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -47,12 +47,15 @@
47
47
  "dependencies": {
48
48
  "@aigne/listr2": "^1.0.10",
49
49
  "@aigne/marked-terminal": "^7.3.2",
50
- "@inquirer/prompts": "^7.6.0",
50
+ "@inquirer/core": "^10.2.0",
51
+ "@inquirer/figures": "^1.0.13",
52
+ "@inquirer/prompts": "^7.8.4",
51
53
  "@inquirer/type": "^3.0.8",
52
- "@listr2/prompt-adapter-inquirer": "^3.0.1",
54
+ "@listr2/prompt-adapter-inquirer": "^3.0.2",
53
55
  "@modelcontextprotocol/sdk": "^1.15.0",
54
56
  "@ocap/mcrypto": "^1.21.0",
55
57
  "@smithy/node-http-handler": "^4.1.0",
58
+ "ansi-escapes": "^7.0.0",
56
59
  "boxen": "^8.0.1",
57
60
  "chalk": "^5.4.1",
58
61
  "cli-table3": "^0.6.5",
@@ -62,7 +65,7 @@
62
65
  "glob": "^11.0.3",
63
66
  "gradient-string": "^3.0.0",
64
67
  "https-proxy-agent": "^7.0.6",
65
- "inquirer": "^12.7.0",
68
+ "inquirer": "^12.9.4",
66
69
  "log-update": "^6.1.0",
67
70
  "marked": "^16.0.0",
68
71
  "nunjucks": "^3.2.4",
@@ -74,14 +77,15 @@
74
77
  "wrap-ansi": "^9.0.0",
75
78
  "yaml": "^2.8.0",
76
79
  "yargs": "^18.0.0",
80
+ "yoctocolors-cjs": "^2.1.3",
77
81
  "zod": "^3.25.67",
78
- "@aigne/agent-library": "^1.21.30",
79
- "@aigne/agentic-memory": "^1.0.30",
80
- "@aigne/core": "^1.56.0",
81
- "@aigne/aigne-hub": "^0.8.0",
82
- "@aigne/default-memory": "^1.1.12",
82
+ "@aigne/agentic-memory": "^1.0.31",
83
+ "@aigne/aigne-hub": "^0.8.1",
84
+ "@aigne/agent-library": "^1.21.31",
85
+ "@aigne/core": "^1.57.0",
86
+ "@aigne/default-memory": "^1.1.13",
83
87
  "@aigne/observability-api": "^0.10.1",
84
- "@aigne/openai": "^0.13.1"
88
+ "@aigne/openai": "^0.13.2"
85
89
  },
86
90
  "devDependencies": {
87
91
  "@types/archiver": "^6.0.3",
@@ -97,7 +101,7 @@
97
101
  "rimraf": "^6.0.1",
98
102
  "typescript": "^5.8.3",
99
103
  "ufo": "^1.6.1",
100
- "@aigne/test-utils": "^0.5.37"
104
+ "@aigne/test-utils": "^0.5.38"
101
105
  },
102
106
  "scripts": {
103
107
  "lint": "tsc --noEmit",