@ai-setting/roy-agent-cli 1.5.84 → 1.5.85

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.
@@ -7320,7 +7320,7 @@ var require_dist = __commonJS((exports) => {
7320
7320
  var require_package = __commonJS((exports, module) => {
7321
7321
  module.exports = {
7322
7322
  name: "@ai-setting/roy-agent-cli",
7323
- version: "1.5.84",
7323
+ version: "1.5.85",
7324
7324
  type: "module",
7325
7325
  description: "CLI for roy-agent - Non-interactive command execution",
7326
7326
  main: "./dist/index.js",
@@ -9255,8 +9255,16 @@ class InputHandler {
9255
9255
  buffer = [];
9256
9256
  static USER_PROMPT = "❯ ";
9257
9257
  static CONTINUATION_PROMPT = "... ";
9258
+ static filterControlChars(input) {
9259
+ if (!input)
9260
+ return "";
9261
+ return input.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
9262
+ }
9258
9263
  pushLine(line) {
9259
- this.buffer.push(line);
9264
+ if (line == null) {
9265
+ return;
9266
+ }
9267
+ this.buffer.push(InputHandler.filterControlChars(line));
9260
9268
  }
9261
9269
  getBuffer() {
9262
9270
  return this.buffer.join(`
@@ -9341,6 +9349,7 @@ class REPL {
9341
9349
  isShuttingDown = false;
9342
9350
  env = null;
9343
9351
  inputHandler;
9352
+ keypressHandler;
9344
9353
  constructor(options, env) {
9345
9354
  this.options = options;
9346
9355
  this.env = env ?? null;
@@ -9361,6 +9370,17 @@ class REPL {
9361
9370
  return;
9362
9371
  }
9363
9372
  this.isShuttingDown = true;
9373
+ if (process.stdin.isTTY) {
9374
+ try {
9375
+ process.stdin.setRawMode?.(false);
9376
+ } catch {}
9377
+ }
9378
+ if (this.keypressHandler) {
9379
+ try {
9380
+ process.stdin.removeListener("keypress", this.keypressHandler);
9381
+ } catch {}
9382
+ this.keypressHandler = undefined;
9383
+ }
9364
9384
  this.rl.close();
9365
9385
  await this.options.onShutdown();
9366
9386
  }
@@ -9428,7 +9448,9 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9428
9448
  if (process.stdin.isTTY) {
9429
9449
  process.stdin.setRawMode?.(true);
9430
9450
  }
9431
- process.stdin.on("keypress", (str, key) => {
9451
+ this.keypressHandler = (str, key) => {
9452
+ if (this.isShuttingDown)
9453
+ return;
9432
9454
  if (key.meta && key.name === "return") {
9433
9455
  readline2.moveCursor(process.stdout, 0, -1);
9434
9456
  readline2.clearLine(process.stdout, 0);
@@ -9438,7 +9460,8 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9438
9460
  if (key.name === "escape") {
9439
9461
  this.handleEscKey();
9440
9462
  }
9441
- });
9463
+ };
9464
+ process.stdin.on("keypress", this.keypressHandler);
9442
9465
  return new Promise((resolve) => {
9443
9466
  this.rl.on("line", async (line) => {
9444
9467
  await this.handleLine(line.trim());
@@ -9452,6 +9475,9 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9452
9475
  });
9453
9476
  }
9454
9477
  async handleLine(line) {
9478
+ if (this.isShuttingDown) {
9479
+ return;
9480
+ }
9455
9481
  if (line.startsWith("/") && this.inputHandler.getBuffer() === "") {
9456
9482
  await this.handleCommand(line.slice(1));
9457
9483
  this.inputHandler.reset();
@@ -17002,7 +17028,7 @@ var WorkflowListCommand = {
17002
17028
  describe: "列出所有已注册的 Workflow",
17003
17029
  builder: (yargs) => yargs.option("tag", {
17004
17030
  alias: "t",
17005
- describe: "按标签筛选",
17031
+ describe: "按标签筛选(可多次 --tag ai --tag cli 表示 AND)",
17006
17032
  type: "string"
17007
17033
  }).option("task-id", {
17008
17034
  alias: "i",
@@ -17022,6 +17048,27 @@ var WorkflowListCommand = {
17022
17048
  describe: "分页偏移",
17023
17049
  type: "number",
17024
17050
  default: 0
17051
+ }).option("page", {
17052
+ describe: "页码(1-based,与 --page-size 配合;与 --offset 互斥)",
17053
+ type: "number",
17054
+ default: 1
17055
+ }).option("page-size", {
17056
+ describe: "每页大小(与 --page 配合;等价于 --limit)",
17057
+ type: "number"
17058
+ }).check((argv) => {
17059
+ if (typeof argv.page === "number" && argv.page < 1) {
17060
+ throw new Error("--page must be >= 1");
17061
+ }
17062
+ if (typeof argv.pageSize === "number" && argv.pageSize < 1) {
17063
+ throw new Error("--page-size must be >= 1");
17064
+ }
17065
+ if (typeof argv.limit === "number" && argv.limit < 1) {
17066
+ throw new Error("--limit must be >= 1");
17067
+ }
17068
+ if (typeof argv.offset === "number" && argv.offset < 0) {
17069
+ throw new Error("--offset must be >= 0");
17070
+ }
17071
+ return true;
17025
17072
  }).option("json", {
17026
17073
  alias: "j",
17027
17074
  describe: "JSON 格式输出",
@@ -17056,12 +17103,20 @@ var WorkflowListCommand = {
17056
17103
  output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
17057
17104
  process.exit(1);
17058
17105
  }
17106
+ const tagsArg = args.tag;
17107
+ const tagsList = Array.isArray(tagsArg) ? tagsArg : tagsArg ? [tagsArg] : [];
17108
+ const pageSize = typeof args.pageSize === "number" ? args.pageSize : typeof args.limit === "number" ? args.limit : 50;
17109
+ const page = typeof args.page === "number" ? args.page : 1;
17110
+ const effectiveOffset = (page - 1) * pageSize;
17111
+ const userOffset = typeof args.offset === "number" ? args.offset : 0;
17112
+ const finalOffset = page > 1 ? effectiveOffset : userOffset;
17059
17113
  const tool = createWorkflowListTool(service);
17060
17114
  const result = await tool.execute({
17061
- tag: args.tag,
17062
- search: args.search,
17063
- limit: args.limit,
17064
- offset: args.offset
17115
+ tag: tagsList.length === 1 ? tagsList[0] : undefined,
17116
+ tags: tagsList.length > 1 ? tagsList : undefined,
17117
+ search: args.search !== undefined && typeof args.search === "string" ? args.search : undefined,
17118
+ limit: pageSize,
17119
+ offset: finalOffset
17065
17120
  }, {});
17066
17121
  if (!result.success) {
17067
17122
  output.error(result.error || "Failed to list workflows");
@@ -17069,6 +17124,8 @@ var WorkflowListCommand = {
17069
17124
  }
17070
17125
  const data = JSON.parse(result.output);
17071
17126
  const workflows = data.workflows || [];
17127
+ const availableTags = data.available_tags || [];
17128
+ const total = typeof data.total === "number" ? data.total : workflows.length;
17072
17129
  if (args.json) {
17073
17130
  output.json({
17074
17131
  workflows: workflows.map((w) => ({
@@ -17079,7 +17136,12 @@ var WorkflowListCommand = {
17079
17136
  createdAt: w.created_at,
17080
17137
  updatedAt: w.created_at
17081
17138
  })),
17082
- total: data.total || workflows.length
17139
+ available_tags: availableTags,
17140
+ total,
17141
+ count: workflows.length,
17142
+ limit: pageSize,
17143
+ offset: finalOffset,
17144
+ page: page > 1 ? page : Math.floor(finalOffset / pageSize) + 1
17083
17145
  });
17084
17146
  } else {
17085
17147
  const renderWorkflows = workflows.map((w) => ({
@@ -17094,8 +17156,25 @@ var WorkflowListCommand = {
17094
17156
  metadata: {}
17095
17157
  }));
17096
17158
  output.log(renderWorkflowList(renderWorkflows));
17097
- output.log(chalk61.green(`
17098
- ${workflows.length} Workflow`));
17159
+ if (total > workflows.length) {
17160
+ const start = finalOffset + 1;
17161
+ const end = finalOffset + workflows.length;
17162
+ output.log(chalk61.green(`
17163
+ ✅ 显示 ${start}-${end} / 共 ${total} 个 Workflow`));
17164
+ const totalPages = Math.ceil(total / pageSize);
17165
+ const currentPage = Math.floor(finalOffset / pageSize) + 1;
17166
+ if (totalPages > 1) {
17167
+ output.log(chalk61.cyan(`\uD83D\uDCC4 第 ${currentPage} / ${totalPages} 页`));
17168
+ }
17169
+ } else {
17170
+ output.log(chalk61.green(`
17171
+ ✅ 共 ${total} 个 Workflow`));
17172
+ }
17173
+ if (availableTags.length > 0) {
17174
+ const tagsLine = availableTags.map((t) => `${t.name}(${t.count})`).join(", ");
17175
+ output.log(chalk61.cyan(`
17176
+ \uD83C\uDFF7️ Available tags: ${tagsLine}`));
17177
+ }
17099
17178
  }
17100
17179
  } catch (error) {
17101
17180
  output.error(`Failed to list workflows: ${error}`);
@@ -17763,8 +17842,9 @@ var WorkflowAddCommand = {
17763
17842
  type: "string"
17764
17843
  }).option("tags", {
17765
17844
  alias: "t",
17766
- describe: "标签(逗号分隔)",
17767
- type: "string"
17845
+ describe: 'Tags (1-3 required). Select from existing tag pool when possible. Run "roy-agent workflow tag list" first to see available tags. Supports both --tags "a,b,c" and --tags a --tags b --tags c forms.',
17846
+ type: "array",
17847
+ string: true
17768
17848
  }).option("task-id", {
17769
17849
  alias: "i",
17770
17850
  describe: "关联任务 ID",
@@ -17791,6 +17871,29 @@ var WorkflowAddCommand = {
17791
17871
  const output = new OutputService;
17792
17872
  const envService = new EnvironmentService(output);
17793
17873
  try {
17874
+ let parseTags = function(input) {
17875
+ if (input === undefined || input === null)
17876
+ return;
17877
+ const list = [];
17878
+ if (Array.isArray(input)) {
17879
+ for (const item of input) {
17880
+ if (typeof item === "string") {
17881
+ for (const t of item.split(",")) {
17882
+ const trimmed = t.trim();
17883
+ if (trimmed)
17884
+ list.push(trimmed);
17885
+ }
17886
+ }
17887
+ }
17888
+ } else if (typeof input === "string") {
17889
+ for (const t of input.split(",")) {
17890
+ const trimmed = t.trim();
17891
+ if (trimmed)
17892
+ list.push(trimmed);
17893
+ }
17894
+ }
17895
+ return list.length > 0 ? list : undefined;
17896
+ };
17794
17897
  await envService.create({ configPath: a.config });
17795
17898
  const env = envService.getEnvironment();
17796
17899
  if (!env) {
@@ -17902,7 +18005,19 @@ Agent output:
17902
18005
  output.error('Usage: roy-agent workflow add --file <path> | --desc "<description>"');
17903
18006
  process.exit(1);
17904
18007
  }
17905
- const tags = a.tags ? a.tags.split(",").map((t) => t.trim()) : undefined;
18008
+ const tags = parseTags(a.tags);
18009
+ if (!tags || tags.length === 0) {
18010
+ output.error("❌ --tags is required.");
18011
+ output.error(' Please provide 1-3 tags using --tags "tag1,tag2,tag3"');
18012
+ output.error(' Example: roy-agent workflow add --file my.yaml --tags "ai,ml,etl"');
18013
+ process.exit(1);
18014
+ }
18015
+ if (tags.length > 3) {
18016
+ output.error(`❌ Too many tags: received ${tags.length}, maximum is 3.`);
18017
+ output.error(' Please reduce to 1-3 tags using --tags "tag1,tag2,tag3"');
18018
+ output.error(' Example: roy-agent workflow add --file my.yaml --tags "ai,ml,etl"');
18019
+ process.exit(1);
18020
+ }
17906
18021
  const workflow = await service.createWorkflow(definition, {
17907
18022
  tags,
17908
18023
  taskId: a.taskId,
@@ -19140,11 +19255,412 @@ Error: ${error.message}`));
19140
19255
  }
19141
19256
  };
19142
19257
 
19258
+ // src/commands/workflow/commands/search.ts
19259
+ import chalk71 from "chalk";
19260
+ import { wrapFunction as wrapFunction3 } from "@ai-setting/roy-agent-core";
19261
+ var WorkflowSearchCommand = {
19262
+ command: "search",
19263
+ describe: "Search workflows by keyword and/or tags (multiple tags use AND filter). v2: returns available_tags.",
19264
+ builder: (yargs) => yargs.option("keyword", {
19265
+ alias: "k",
19266
+ describe: "Search keyword in name and description",
19267
+ type: "string"
19268
+ }).option("tag", {
19269
+ alias: "t",
19270
+ describe: "Filter by tag (can be specified multiple times for AND filter)",
19271
+ type: "array",
19272
+ string: true
19273
+ }).option("limit", {
19274
+ alias: "l",
19275
+ describe: "Maximum number of results",
19276
+ type: "number",
19277
+ default: 50
19278
+ }).option("offset", {
19279
+ alias: "o",
19280
+ describe: "Pagination offset",
19281
+ type: "number",
19282
+ default: 0
19283
+ }).option("json", {
19284
+ alias: "j",
19285
+ describe: "Output as JSON",
19286
+ type: "boolean",
19287
+ default: false
19288
+ }).option("yaml", {
19289
+ alias: "y",
19290
+ describe: "Output as YAML",
19291
+ type: "boolean",
19292
+ default: false
19293
+ }).option("config", {
19294
+ describe: "Config file path",
19295
+ type: "string"
19296
+ }).check((argv) => {
19297
+ const hasKeyword = typeof argv.keyword === "string" && argv.keyword.length > 0;
19298
+ const hasTags = Array.isArray(argv.tag) && argv.tag.length > 0;
19299
+ if (!hasKeyword && !hasTags) {
19300
+ throw new Error("At least one of --keyword or --tag must be provided");
19301
+ }
19302
+ if (Array.isArray(argv.tag)) {
19303
+ for (const t of argv.tag) {
19304
+ if (typeof t !== "string" || t.length === 0) {
19305
+ throw new Error("Empty tag values are not allowed");
19306
+ }
19307
+ if (t.length > 50) {
19308
+ throw new Error(`Tag "${t}" exceeds 50 characters`);
19309
+ }
19310
+ if (t.includes(",")) {
19311
+ throw new Error(`Tag "${t}" contains comma; pass them as separate --tags`);
19312
+ }
19313
+ }
19314
+ }
19315
+ return true;
19316
+ }),
19317
+ async handler(args) {
19318
+ const a = args;
19319
+ const output = new OutputService;
19320
+ const envService = new EnvironmentService(output);
19321
+ try {
19322
+ await runWorkflowSearch({
19323
+ keyword: a.keyword,
19324
+ tags: a.tag,
19325
+ limit: a.limit,
19326
+ offset: a.offset,
19327
+ asJson: a.json,
19328
+ asYaml: a.yaml,
19329
+ configPath: a.config,
19330
+ output,
19331
+ envService
19332
+ });
19333
+ } catch (error) {
19334
+ output.error(`Failed to search workflows: ${error}`);
19335
+ process.exit(1);
19336
+ } finally {
19337
+ await envService.dispose();
19338
+ }
19339
+ }
19340
+ };
19341
+ var _runWorkflowSearchImpl = async function(opts) {
19342
+ const { keyword, tags, limit, offset, asJson, asYaml, configPath, output, envService } = opts;
19343
+ await envService.create({ configPath });
19344
+ const env = envService.getEnvironment();
19345
+ if (!env) {
19346
+ output.error("Failed to create environment");
19347
+ process.exit(1);
19348
+ }
19349
+ const workflowComponent = env.getComponent("workflow");
19350
+ if (!workflowComponent) {
19351
+ output.error("WorkflowComponent not available");
19352
+ process.exit(1);
19353
+ }
19354
+ const service = workflowComponent.getService();
19355
+ if (!service) {
19356
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19357
+ process.exit(1);
19358
+ }
19359
+ const useWithTags = typeof service.searchWorkflowsWithTags === "function";
19360
+ const result = useWithTags ? await service.searchWorkflowsWithTags({
19361
+ keyword,
19362
+ tags,
19363
+ limit,
19364
+ offset
19365
+ }) : {
19366
+ workflows: service.searchWorkflows({ keyword, tags, limit, offset }),
19367
+ available_tags: []
19368
+ };
19369
+ const workflows = result.workflows ?? [];
19370
+ const availableTags = result.available_tags ?? [];
19371
+ if (asJson) {
19372
+ output.json({
19373
+ workflows: workflows.map((w) => ({
19374
+ name: w.name,
19375
+ version: w.version,
19376
+ description: w.description,
19377
+ tags: w.tags,
19378
+ createdAt: w.createdAt,
19379
+ updatedAt: w.updatedAt
19380
+ })),
19381
+ available_tags: availableTags,
19382
+ total: workflows.length,
19383
+ keyword,
19384
+ tags
19385
+ });
19386
+ return;
19387
+ }
19388
+ if (asYaml) {
19389
+ const yaml = await Promise.resolve().then(() => __toESM(require_dist(), 1));
19390
+ output.log(yaml.stringify({
19391
+ workflows: workflows.map((w) => ({
19392
+ name: w.name,
19393
+ version: w.version,
19394
+ description: w.description,
19395
+ tags: w.tags
19396
+ })),
19397
+ available_tags: availableTags,
19398
+ total: workflows.length,
19399
+ keyword,
19400
+ tags
19401
+ }));
19402
+ return;
19403
+ }
19404
+ const renderWorkflows = workflows.map((w) => ({
19405
+ id: w.name,
19406
+ name: w.name,
19407
+ version: w.version,
19408
+ description: w.description,
19409
+ tags: w.tags || [],
19410
+ createdAt: w.createdAt ? new Date(w.createdAt) : new Date,
19411
+ updatedAt: w.updatedAt ? new Date(w.updatedAt) : new Date,
19412
+ definition: { nodes: [], config: {}, inputs: {} },
19413
+ metadata: {}
19414
+ }));
19415
+ if (renderWorkflows.length === 0) {
19416
+ output.log(chalk71.yellow("\uD83D\uDD0D No workflows matched the search criteria."));
19417
+ output.log(chalk71.gray(` keyword: ${keyword || "(none)"}`));
19418
+ output.log(chalk71.gray(` tags: ${tags && tags.length > 0 ? tags.join(", ") : "(none)"}`));
19419
+ } else {
19420
+ output.log(chalk71.bold(`\uD83D\uDD0D Search results (${renderWorkflows.length}):`));
19421
+ output.log(renderWorkflowList(renderWorkflows));
19422
+ output.log(chalk71.green(`
19423
+ ✅ Found ${renderWorkflows.length} workflow(s)`));
19424
+ }
19425
+ if (availableTags.length > 0) {
19426
+ output.log("");
19427
+ output.log(chalk71.bold("\uD83D\uDCDA Available tags (sorted by usage_count):"));
19428
+ for (const t of availableTags) {
19429
+ const name = t.name ?? t.tag;
19430
+ const count = t.count ?? t.usage_count;
19431
+ output.log(chalk71.gray(` - ${name} (${count})`));
19432
+ }
19433
+ }
19434
+ };
19435
+ var runWorkflowSearch = wrapFunction3(_runWorkflowSearchImpl, "workflow.cli.search", {
19436
+ recordParams: true,
19437
+ recordResult: false,
19438
+ log: false
19439
+ });
19440
+
19441
+ // src/commands/workflow/commands/tag.ts
19442
+ import { wrapFunction as wrapFunction4 } from "@ai-setting/roy-agent-core";
19443
+ import chalk72 from "chalk";
19444
+ var WorkflowTagListCommand = {
19445
+ command: "list",
19446
+ describe: "List all workflow tags from the workflow_tags table (sorted by usage_count DESC by default). Use this BEFORE `workflow add` to discover existing tags and reuse them for semantic consistency.",
19447
+ builder: (yargs) => yargs.option("sort-by", {
19448
+ describe: "Sort field",
19449
+ choices: ["usage_count", "name", "created_at"],
19450
+ default: "usage_count"
19451
+ }).option("order", {
19452
+ describe: "Sort order",
19453
+ choices: ["asc", "desc"],
19454
+ default: "desc"
19455
+ }).option("limit", {
19456
+ describe: "Max number of tags to return",
19457
+ type: "number",
19458
+ default: 100
19459
+ }).option("json", {
19460
+ alias: "j",
19461
+ describe: "Output as JSON",
19462
+ type: "boolean",
19463
+ default: false
19464
+ }).option("config", {
19465
+ describe: "Config file path",
19466
+ type: "string"
19467
+ }),
19468
+ async handler(args) {
19469
+ const a = args;
19470
+ const output = new OutputService;
19471
+ const envService = new EnvironmentService(output);
19472
+ try {
19473
+ await runWorkflowTagList({
19474
+ sortBy: a.sortBy,
19475
+ order: a.order,
19476
+ limit: a.limit,
19477
+ asJson: a.json,
19478
+ configPath: a.config,
19479
+ output,
19480
+ envService
19481
+ });
19482
+ } catch (error) {
19483
+ output.error(`Failed to list workflow tags: ${error}`);
19484
+ process.exit(1);
19485
+ } finally {
19486
+ await envService.dispose();
19487
+ }
19488
+ }
19489
+ };
19490
+ var WorkflowTagGetCommand = {
19491
+ command: "get <name>",
19492
+ describe: "Get details of a single workflow tag (usage_count, created_at, updated_at). Returns error if tag not in the workflow_tags table.",
19493
+ builder: (yargs) => yargs.positional("name", {
19494
+ describe: "Tag name",
19495
+ type: "string"
19496
+ }).option("json", {
19497
+ alias: "j",
19498
+ describe: "Output as JSON",
19499
+ type: "boolean",
19500
+ default: false
19501
+ }).option("config", {
19502
+ describe: "Config file path",
19503
+ type: "string"
19504
+ }).check((argv) => {
19505
+ if (typeof argv.name !== "string" || argv.name.length === 0) {
19506
+ throw new Error("Tag name is required");
19507
+ }
19508
+ if (argv.name.length > 50) {
19509
+ throw new Error(`Tag name exceeds 50 characters`);
19510
+ }
19511
+ return true;
19512
+ }),
19513
+ async handler(args) {
19514
+ const a = args;
19515
+ const output = new OutputService;
19516
+ const envService = new EnvironmentService(output);
19517
+ try {
19518
+ await runWorkflowTagGet({
19519
+ name: a.name,
19520
+ asJson: a.json,
19521
+ configPath: a.config,
19522
+ output,
19523
+ envService
19524
+ });
19525
+ } catch (error) {
19526
+ output.error(`Failed to get tag: ${error}`);
19527
+ process.exit(1);
19528
+ } finally {
19529
+ await envService.dispose();
19530
+ }
19531
+ }
19532
+ };
19533
+ var _runWorkflowTagListImpl = async function(opts) {
19534
+ const { output, envService } = opts;
19535
+ await envService.create({ configPath: opts.configPath });
19536
+ const env = envService.getEnvironment();
19537
+ if (!env) {
19538
+ output.error("Failed to create environment");
19539
+ process.exit(1);
19540
+ }
19541
+ const workflowComponent = env.getComponent("workflow");
19542
+ if (!workflowComponent) {
19543
+ output.error("WorkflowComponent not available");
19544
+ process.exit(1);
19545
+ }
19546
+ const service = workflowComponent.getService();
19547
+ if (!service) {
19548
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19549
+ process.exit(1);
19550
+ }
19551
+ const result = await callListTags(service, {
19552
+ sortBy: opts.sortBy ?? "usage_count",
19553
+ order: opts.order ?? "desc",
19554
+ limit: opts.limit ?? 100
19555
+ });
19556
+ if (opts.asJson) {
19557
+ output.json({
19558
+ tags: result.tags,
19559
+ total: result.total
19560
+ });
19561
+ return;
19562
+ }
19563
+ if (result.tags.length === 0) {
19564
+ output.log(chalk72.yellow("\uD83C\uDFF7️ No tags in the workflow_tags table yet."));
19565
+ output.log(chalk72.gray(" Tags are created automatically when you run `workflow add --tags <name>`."));
19566
+ return;
19567
+ }
19568
+ output.log(chalk72.bold(`\uD83C\uDFF7️ Workflow tags (${result.total}):`));
19569
+ output.log(chalk72.gray(` ${padRight("NAME", 24)}${padRight("USAGE", 8)}${padRight("CREATED", 22)}`));
19570
+ for (const t of result.tags) {
19571
+ const line = ` ${padRight(t.name, 24)}${padRight(String(t.usage_count), 8)}${padRight(formatDate2(t.created_at), 22)}`;
19572
+ output.log(line);
19573
+ }
19574
+ output.log(chalk72.gray(`
19575
+ \uD83D\uDCA1 Tip: when adding a new workflow, prefer tags from this list to ensure semantic consistency.`));
19576
+ };
19577
+ var _runWorkflowTagGetImpl = async function(opts) {
19578
+ const { output, envService } = opts;
19579
+ await envService.create({ configPath: opts.configPath });
19580
+ const env = envService.getEnvironment();
19581
+ if (!env) {
19582
+ output.error("Failed to create environment");
19583
+ process.exit(1);
19584
+ }
19585
+ const workflowComponent = env.getComponent("workflow");
19586
+ if (!workflowComponent) {
19587
+ output.error("WorkflowComponent not available");
19588
+ process.exit(1);
19589
+ }
19590
+ const service = workflowComponent.getService();
19591
+ if (!service) {
19592
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19593
+ process.exit(1);
19594
+ }
19595
+ const tag = callGetTag(service, opts.name);
19596
+ if (!tag) {
19597
+ if (opts.asJson) {
19598
+ output.json({ error: "Tag not found", name: opts.name });
19599
+ } else {
19600
+ output.error(`Tag "${opts.name}" not found in the workflow_tags table.`);
19601
+ output.log(chalk72.gray(`Tip: run \`roy-agent workflow tag list\` to see available tags.`));
19602
+ }
19603
+ process.exitCode = 1;
19604
+ return;
19605
+ }
19606
+ if (opts.asJson) {
19607
+ output.json(tag);
19608
+ return;
19609
+ }
19610
+ output.log(chalk72.bold(`\uD83C\uDFF7️ Tag: ${tag.name}`));
19611
+ output.log(chalk72.gray(` usage_count: ${tag.usage_count}`));
19612
+ output.log(chalk72.gray(` created_at: ${tag.created_at}`));
19613
+ output.log(chalk72.gray(` updated_at: ${tag.updated_at}`));
19614
+ };
19615
+ async function callListTags(service, options) {
19616
+ if (typeof service.listTags !== "function") {
19617
+ return { tags: [], total: 0 };
19618
+ }
19619
+ return await service.listTags(options);
19620
+ }
19621
+ function callGetTag(service, name) {
19622
+ if (typeof service.getTag !== "function") {
19623
+ return;
19624
+ }
19625
+ return service.getTag(name);
19626
+ }
19627
+ function padRight(str, width) {
19628
+ if (str.length >= width)
19629
+ return str.slice(0, width - 1) + " ";
19630
+ return str + " ".repeat(width - str.length);
19631
+ }
19632
+ function formatDate2(iso) {
19633
+ try {
19634
+ const d = new Date(iso);
19635
+ if (isNaN(d.getTime()))
19636
+ return iso;
19637
+ return d.toISOString().replace("T", " ").slice(0, 19);
19638
+ } catch {
19639
+ return iso;
19640
+ }
19641
+ }
19642
+ var runWorkflowTagList = wrapFunction4(_runWorkflowTagListImpl, "workflow.cli.tag.list", {
19643
+ recordParams: true,
19644
+ recordResult: false,
19645
+ log: false
19646
+ });
19647
+ var runWorkflowTagGet = wrapFunction4(_runWorkflowTagGetImpl, "workflow.cli.tag.get", {
19648
+ recordParams: true,
19649
+ recordResult: false,
19650
+ log: false
19651
+ });
19652
+
19143
19653
  // src/commands/workflow/index.ts
19654
+ var WorkflowTagCommand = {
19655
+ command: "tag",
19656
+ describe: "Manage workflow tags — list and get from the workflow_tags table. Use BEFORE `workflow add` to discover existing tags.",
19657
+ builder: (yargs) => yargs.command(WorkflowTagListCommand).command(WorkflowTagGetCommand).demandCommand(1, "请指定 tag 子命令:list 或 get <name>").help(),
19658
+ handler() {}
19659
+ };
19144
19660
  var WorkflowCommand = {
19145
19661
  command: "workflow",
19146
19662
  describe: "Workflow DAG management and execution - create, run, and monitor workflows",
19147
- builder: (yargs) => yargs.command(WorkflowListCommand).command(WorkflowAddCommand).command(WorkflowNodesCommand).command(WorkflowValidateCommand).command(WorkflowGetCommand).command(WorkflowUpdateCommand).command(WorkflowRemoveCommand).command(WorkflowRunCommand).command(WorkflowStopCommand).command(WorkflowStatusCommand).demandCommand(1, "请指定子命令:list、add、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流").help(),
19663
+ builder: (yargs) => yargs.command(WorkflowListCommand).command(WorkflowAddCommand).command(WorkflowSearchCommand).command(WorkflowTagCommand).command(WorkflowNodesCommand).command(WorkflowValidateCommand).command(WorkflowGetCommand).command(WorkflowUpdateCommand).command(WorkflowRemoveCommand).command(WorkflowRunCommand).command(WorkflowStopCommand).command(WorkflowStatusCommand).demandCommand(1, "请指定子命令:list、add、search、tag、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流").help(),
19148
19664
  handler() {}
19149
19665
  };
19150
19666
 
package/dist/index.js CHANGED
@@ -7319,7 +7319,7 @@ var require_dist = __commonJS((exports) => {
7319
7319
  var require_package = __commonJS((exports, module) => {
7320
7320
  module.exports = {
7321
7321
  name: "@ai-setting/roy-agent-cli",
7322
- version: "1.5.84",
7322
+ version: "1.5.85",
7323
7323
  type: "module",
7324
7324
  description: "CLI for roy-agent - Non-interactive command execution",
7325
7325
  main: "./dist/index.js",
@@ -9254,8 +9254,16 @@ class InputHandler {
9254
9254
  buffer = [];
9255
9255
  static USER_PROMPT = "❯ ";
9256
9256
  static CONTINUATION_PROMPT = "... ";
9257
+ static filterControlChars(input) {
9258
+ if (!input)
9259
+ return "";
9260
+ return input.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
9261
+ }
9257
9262
  pushLine(line) {
9258
- this.buffer.push(line);
9263
+ if (line == null) {
9264
+ return;
9265
+ }
9266
+ this.buffer.push(InputHandler.filterControlChars(line));
9259
9267
  }
9260
9268
  getBuffer() {
9261
9269
  return this.buffer.join(`
@@ -9340,6 +9348,7 @@ class REPL {
9340
9348
  isShuttingDown = false;
9341
9349
  env = null;
9342
9350
  inputHandler;
9351
+ keypressHandler;
9343
9352
  constructor(options, env) {
9344
9353
  this.options = options;
9345
9354
  this.env = env ?? null;
@@ -9360,6 +9369,17 @@ class REPL {
9360
9369
  return;
9361
9370
  }
9362
9371
  this.isShuttingDown = true;
9372
+ if (process.stdin.isTTY) {
9373
+ try {
9374
+ process.stdin.setRawMode?.(false);
9375
+ } catch {}
9376
+ }
9377
+ if (this.keypressHandler) {
9378
+ try {
9379
+ process.stdin.removeListener("keypress", this.keypressHandler);
9380
+ } catch {}
9381
+ this.keypressHandler = undefined;
9382
+ }
9363
9383
  this.rl.close();
9364
9384
  await this.options.onShutdown();
9365
9385
  }
@@ -9427,7 +9447,9 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9427
9447
  if (process.stdin.isTTY) {
9428
9448
  process.stdin.setRawMode?.(true);
9429
9449
  }
9430
- process.stdin.on("keypress", (str, key) => {
9450
+ this.keypressHandler = (str, key) => {
9451
+ if (this.isShuttingDown)
9452
+ return;
9431
9453
  if (key.meta && key.name === "return") {
9432
9454
  readline2.moveCursor(process.stdout, 0, -1);
9433
9455
  readline2.clearLine(process.stdout, 0);
@@ -9437,7 +9459,8 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9437
9459
  if (key.name === "escape") {
9438
9460
  this.handleEscKey();
9439
9461
  }
9440
- });
9462
+ };
9463
+ process.stdin.on("keypress", this.keypressHandler);
9441
9464
  return new Promise((resolve) => {
9442
9465
  this.rl.on("line", async (line) => {
9443
9466
  await this.handleLine(line.trim());
@@ -9451,6 +9474,9 @@ ${COLORS.system("[没有正在执行的请求]")}`);
9451
9474
  });
9452
9475
  }
9453
9476
  async handleLine(line) {
9477
+ if (this.isShuttingDown) {
9478
+ return;
9479
+ }
9454
9480
  if (line.startsWith("/") && this.inputHandler.getBuffer() === "") {
9455
9481
  await this.handleCommand(line.slice(1));
9456
9482
  this.inputHandler.reset();
@@ -17001,7 +17027,7 @@ var WorkflowListCommand = {
17001
17027
  describe: "列出所有已注册的 Workflow",
17002
17028
  builder: (yargs) => yargs.option("tag", {
17003
17029
  alias: "t",
17004
- describe: "按标签筛选",
17030
+ describe: "按标签筛选(可多次 --tag ai --tag cli 表示 AND)",
17005
17031
  type: "string"
17006
17032
  }).option("task-id", {
17007
17033
  alias: "i",
@@ -17021,6 +17047,27 @@ var WorkflowListCommand = {
17021
17047
  describe: "分页偏移",
17022
17048
  type: "number",
17023
17049
  default: 0
17050
+ }).option("page", {
17051
+ describe: "页码(1-based,与 --page-size 配合;与 --offset 互斥)",
17052
+ type: "number",
17053
+ default: 1
17054
+ }).option("page-size", {
17055
+ describe: "每页大小(与 --page 配合;等价于 --limit)",
17056
+ type: "number"
17057
+ }).check((argv) => {
17058
+ if (typeof argv.page === "number" && argv.page < 1) {
17059
+ throw new Error("--page must be >= 1");
17060
+ }
17061
+ if (typeof argv.pageSize === "number" && argv.pageSize < 1) {
17062
+ throw new Error("--page-size must be >= 1");
17063
+ }
17064
+ if (typeof argv.limit === "number" && argv.limit < 1) {
17065
+ throw new Error("--limit must be >= 1");
17066
+ }
17067
+ if (typeof argv.offset === "number" && argv.offset < 0) {
17068
+ throw new Error("--offset must be >= 0");
17069
+ }
17070
+ return true;
17024
17071
  }).option("json", {
17025
17072
  alias: "j",
17026
17073
  describe: "JSON 格式输出",
@@ -17055,12 +17102,20 @@ var WorkflowListCommand = {
17055
17102
  output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
17056
17103
  process.exit(1);
17057
17104
  }
17105
+ const tagsArg = args.tag;
17106
+ const tagsList = Array.isArray(tagsArg) ? tagsArg : tagsArg ? [tagsArg] : [];
17107
+ const pageSize = typeof args.pageSize === "number" ? args.pageSize : typeof args.limit === "number" ? args.limit : 50;
17108
+ const page = typeof args.page === "number" ? args.page : 1;
17109
+ const effectiveOffset = (page - 1) * pageSize;
17110
+ const userOffset = typeof args.offset === "number" ? args.offset : 0;
17111
+ const finalOffset = page > 1 ? effectiveOffset : userOffset;
17058
17112
  const tool = createWorkflowListTool(service);
17059
17113
  const result = await tool.execute({
17060
- tag: args.tag,
17061
- search: args.search,
17062
- limit: args.limit,
17063
- offset: args.offset
17114
+ tag: tagsList.length === 1 ? tagsList[0] : undefined,
17115
+ tags: tagsList.length > 1 ? tagsList : undefined,
17116
+ search: args.search !== undefined && typeof args.search === "string" ? args.search : undefined,
17117
+ limit: pageSize,
17118
+ offset: finalOffset
17064
17119
  }, {});
17065
17120
  if (!result.success) {
17066
17121
  output.error(result.error || "Failed to list workflows");
@@ -17068,6 +17123,8 @@ var WorkflowListCommand = {
17068
17123
  }
17069
17124
  const data = JSON.parse(result.output);
17070
17125
  const workflows = data.workflows || [];
17126
+ const availableTags = data.available_tags || [];
17127
+ const total = typeof data.total === "number" ? data.total : workflows.length;
17071
17128
  if (args.json) {
17072
17129
  output.json({
17073
17130
  workflows: workflows.map((w) => ({
@@ -17078,7 +17135,12 @@ var WorkflowListCommand = {
17078
17135
  createdAt: w.created_at,
17079
17136
  updatedAt: w.created_at
17080
17137
  })),
17081
- total: data.total || workflows.length
17138
+ available_tags: availableTags,
17139
+ total,
17140
+ count: workflows.length,
17141
+ limit: pageSize,
17142
+ offset: finalOffset,
17143
+ page: page > 1 ? page : Math.floor(finalOffset / pageSize) + 1
17082
17144
  });
17083
17145
  } else {
17084
17146
  const renderWorkflows = workflows.map((w) => ({
@@ -17093,8 +17155,25 @@ var WorkflowListCommand = {
17093
17155
  metadata: {}
17094
17156
  }));
17095
17157
  output.log(renderWorkflowList(renderWorkflows));
17096
- output.log(chalk61.green(`
17097
- ${workflows.length} Workflow`));
17158
+ if (total > workflows.length) {
17159
+ const start = finalOffset + 1;
17160
+ const end = finalOffset + workflows.length;
17161
+ output.log(chalk61.green(`
17162
+ ✅ 显示 ${start}-${end} / 共 ${total} 个 Workflow`));
17163
+ const totalPages = Math.ceil(total / pageSize);
17164
+ const currentPage = Math.floor(finalOffset / pageSize) + 1;
17165
+ if (totalPages > 1) {
17166
+ output.log(chalk61.cyan(`\uD83D\uDCC4 第 ${currentPage} / ${totalPages} 页`));
17167
+ }
17168
+ } else {
17169
+ output.log(chalk61.green(`
17170
+ ✅ 共 ${total} 个 Workflow`));
17171
+ }
17172
+ if (availableTags.length > 0) {
17173
+ const tagsLine = availableTags.map((t) => `${t.name}(${t.count})`).join(", ");
17174
+ output.log(chalk61.cyan(`
17175
+ \uD83C\uDFF7️ Available tags: ${tagsLine}`));
17176
+ }
17098
17177
  }
17099
17178
  } catch (error) {
17100
17179
  output.error(`Failed to list workflows: ${error}`);
@@ -17762,8 +17841,9 @@ var WorkflowAddCommand = {
17762
17841
  type: "string"
17763
17842
  }).option("tags", {
17764
17843
  alias: "t",
17765
- describe: "标签(逗号分隔)",
17766
- type: "string"
17844
+ describe: 'Tags (1-3 required). Select from existing tag pool when possible. Run "roy-agent workflow tag list" first to see available tags. Supports both --tags "a,b,c" and --tags a --tags b --tags c forms.',
17845
+ type: "array",
17846
+ string: true
17767
17847
  }).option("task-id", {
17768
17848
  alias: "i",
17769
17849
  describe: "关联任务 ID",
@@ -17790,6 +17870,29 @@ var WorkflowAddCommand = {
17790
17870
  const output = new OutputService;
17791
17871
  const envService = new EnvironmentService(output);
17792
17872
  try {
17873
+ let parseTags = function(input) {
17874
+ if (input === undefined || input === null)
17875
+ return;
17876
+ const list = [];
17877
+ if (Array.isArray(input)) {
17878
+ for (const item of input) {
17879
+ if (typeof item === "string") {
17880
+ for (const t of item.split(",")) {
17881
+ const trimmed = t.trim();
17882
+ if (trimmed)
17883
+ list.push(trimmed);
17884
+ }
17885
+ }
17886
+ }
17887
+ } else if (typeof input === "string") {
17888
+ for (const t of input.split(",")) {
17889
+ const trimmed = t.trim();
17890
+ if (trimmed)
17891
+ list.push(trimmed);
17892
+ }
17893
+ }
17894
+ return list.length > 0 ? list : undefined;
17895
+ };
17793
17896
  await envService.create({ configPath: a.config });
17794
17897
  const env = envService.getEnvironment();
17795
17898
  if (!env) {
@@ -17901,7 +18004,19 @@ Agent output:
17901
18004
  output.error('Usage: roy-agent workflow add --file <path> | --desc "<description>"');
17902
18005
  process.exit(1);
17903
18006
  }
17904
- const tags = a.tags ? a.tags.split(",").map((t) => t.trim()) : undefined;
18007
+ const tags = parseTags(a.tags);
18008
+ if (!tags || tags.length === 0) {
18009
+ output.error("❌ --tags is required.");
18010
+ output.error(' Please provide 1-3 tags using --tags "tag1,tag2,tag3"');
18011
+ output.error(' Example: roy-agent workflow add --file my.yaml --tags "ai,ml,etl"');
18012
+ process.exit(1);
18013
+ }
18014
+ if (tags.length > 3) {
18015
+ output.error(`❌ Too many tags: received ${tags.length}, maximum is 3.`);
18016
+ output.error(' Please reduce to 1-3 tags using --tags "tag1,tag2,tag3"');
18017
+ output.error(' Example: roy-agent workflow add --file my.yaml --tags "ai,ml,etl"');
18018
+ process.exit(1);
18019
+ }
17905
18020
  const workflow = await service.createWorkflow(definition, {
17906
18021
  tags,
17907
18022
  taskId: a.taskId,
@@ -19139,11 +19254,412 @@ Error: ${error.message}`));
19139
19254
  }
19140
19255
  };
19141
19256
 
19257
+ // src/commands/workflow/commands/search.ts
19258
+ import chalk71 from "chalk";
19259
+ import { wrapFunction as wrapFunction3 } from "@ai-setting/roy-agent-core";
19260
+ var WorkflowSearchCommand = {
19261
+ command: "search",
19262
+ describe: "Search workflows by keyword and/or tags (multiple tags use AND filter). v2: returns available_tags.",
19263
+ builder: (yargs) => yargs.option("keyword", {
19264
+ alias: "k",
19265
+ describe: "Search keyword in name and description",
19266
+ type: "string"
19267
+ }).option("tag", {
19268
+ alias: "t",
19269
+ describe: "Filter by tag (can be specified multiple times for AND filter)",
19270
+ type: "array",
19271
+ string: true
19272
+ }).option("limit", {
19273
+ alias: "l",
19274
+ describe: "Maximum number of results",
19275
+ type: "number",
19276
+ default: 50
19277
+ }).option("offset", {
19278
+ alias: "o",
19279
+ describe: "Pagination offset",
19280
+ type: "number",
19281
+ default: 0
19282
+ }).option("json", {
19283
+ alias: "j",
19284
+ describe: "Output as JSON",
19285
+ type: "boolean",
19286
+ default: false
19287
+ }).option("yaml", {
19288
+ alias: "y",
19289
+ describe: "Output as YAML",
19290
+ type: "boolean",
19291
+ default: false
19292
+ }).option("config", {
19293
+ describe: "Config file path",
19294
+ type: "string"
19295
+ }).check((argv) => {
19296
+ const hasKeyword = typeof argv.keyword === "string" && argv.keyword.length > 0;
19297
+ const hasTags = Array.isArray(argv.tag) && argv.tag.length > 0;
19298
+ if (!hasKeyword && !hasTags) {
19299
+ throw new Error("At least one of --keyword or --tag must be provided");
19300
+ }
19301
+ if (Array.isArray(argv.tag)) {
19302
+ for (const t of argv.tag) {
19303
+ if (typeof t !== "string" || t.length === 0) {
19304
+ throw new Error("Empty tag values are not allowed");
19305
+ }
19306
+ if (t.length > 50) {
19307
+ throw new Error(`Tag "${t}" exceeds 50 characters`);
19308
+ }
19309
+ if (t.includes(",")) {
19310
+ throw new Error(`Tag "${t}" contains comma; pass them as separate --tags`);
19311
+ }
19312
+ }
19313
+ }
19314
+ return true;
19315
+ }),
19316
+ async handler(args) {
19317
+ const a = args;
19318
+ const output = new OutputService;
19319
+ const envService = new EnvironmentService(output);
19320
+ try {
19321
+ await runWorkflowSearch({
19322
+ keyword: a.keyword,
19323
+ tags: a.tag,
19324
+ limit: a.limit,
19325
+ offset: a.offset,
19326
+ asJson: a.json,
19327
+ asYaml: a.yaml,
19328
+ configPath: a.config,
19329
+ output,
19330
+ envService
19331
+ });
19332
+ } catch (error) {
19333
+ output.error(`Failed to search workflows: ${error}`);
19334
+ process.exit(1);
19335
+ } finally {
19336
+ await envService.dispose();
19337
+ }
19338
+ }
19339
+ };
19340
+ var _runWorkflowSearchImpl = async function(opts) {
19341
+ const { keyword, tags, limit, offset, asJson, asYaml, configPath, output, envService } = opts;
19342
+ await envService.create({ configPath });
19343
+ const env = envService.getEnvironment();
19344
+ if (!env) {
19345
+ output.error("Failed to create environment");
19346
+ process.exit(1);
19347
+ }
19348
+ const workflowComponent = env.getComponent("workflow");
19349
+ if (!workflowComponent) {
19350
+ output.error("WorkflowComponent not available");
19351
+ process.exit(1);
19352
+ }
19353
+ const service = workflowComponent.getService();
19354
+ if (!service) {
19355
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19356
+ process.exit(1);
19357
+ }
19358
+ const useWithTags = typeof service.searchWorkflowsWithTags === "function";
19359
+ const result = useWithTags ? await service.searchWorkflowsWithTags({
19360
+ keyword,
19361
+ tags,
19362
+ limit,
19363
+ offset
19364
+ }) : {
19365
+ workflows: service.searchWorkflows({ keyword, tags, limit, offset }),
19366
+ available_tags: []
19367
+ };
19368
+ const workflows = result.workflows ?? [];
19369
+ const availableTags = result.available_tags ?? [];
19370
+ if (asJson) {
19371
+ output.json({
19372
+ workflows: workflows.map((w) => ({
19373
+ name: w.name,
19374
+ version: w.version,
19375
+ description: w.description,
19376
+ tags: w.tags,
19377
+ createdAt: w.createdAt,
19378
+ updatedAt: w.updatedAt
19379
+ })),
19380
+ available_tags: availableTags,
19381
+ total: workflows.length,
19382
+ keyword,
19383
+ tags
19384
+ });
19385
+ return;
19386
+ }
19387
+ if (asYaml) {
19388
+ const yaml = await Promise.resolve().then(() => __toESM(require_dist(), 1));
19389
+ output.log(yaml.stringify({
19390
+ workflows: workflows.map((w) => ({
19391
+ name: w.name,
19392
+ version: w.version,
19393
+ description: w.description,
19394
+ tags: w.tags
19395
+ })),
19396
+ available_tags: availableTags,
19397
+ total: workflows.length,
19398
+ keyword,
19399
+ tags
19400
+ }));
19401
+ return;
19402
+ }
19403
+ const renderWorkflows = workflows.map((w) => ({
19404
+ id: w.name,
19405
+ name: w.name,
19406
+ version: w.version,
19407
+ description: w.description,
19408
+ tags: w.tags || [],
19409
+ createdAt: w.createdAt ? new Date(w.createdAt) : new Date,
19410
+ updatedAt: w.updatedAt ? new Date(w.updatedAt) : new Date,
19411
+ definition: { nodes: [], config: {}, inputs: {} },
19412
+ metadata: {}
19413
+ }));
19414
+ if (renderWorkflows.length === 0) {
19415
+ output.log(chalk71.yellow("\uD83D\uDD0D No workflows matched the search criteria."));
19416
+ output.log(chalk71.gray(` keyword: ${keyword || "(none)"}`));
19417
+ output.log(chalk71.gray(` tags: ${tags && tags.length > 0 ? tags.join(", ") : "(none)"}`));
19418
+ } else {
19419
+ output.log(chalk71.bold(`\uD83D\uDD0D Search results (${renderWorkflows.length}):`));
19420
+ output.log(renderWorkflowList(renderWorkflows));
19421
+ output.log(chalk71.green(`
19422
+ ✅ Found ${renderWorkflows.length} workflow(s)`));
19423
+ }
19424
+ if (availableTags.length > 0) {
19425
+ output.log("");
19426
+ output.log(chalk71.bold("\uD83D\uDCDA Available tags (sorted by usage_count):"));
19427
+ for (const t of availableTags) {
19428
+ const name = t.name ?? t.tag;
19429
+ const count = t.count ?? t.usage_count;
19430
+ output.log(chalk71.gray(` - ${name} (${count})`));
19431
+ }
19432
+ }
19433
+ };
19434
+ var runWorkflowSearch = wrapFunction3(_runWorkflowSearchImpl, "workflow.cli.search", {
19435
+ recordParams: true,
19436
+ recordResult: false,
19437
+ log: false
19438
+ });
19439
+
19440
+ // src/commands/workflow/commands/tag.ts
19441
+ import { wrapFunction as wrapFunction4 } from "@ai-setting/roy-agent-core";
19442
+ import chalk72 from "chalk";
19443
+ var WorkflowTagListCommand = {
19444
+ command: "list",
19445
+ describe: "List all workflow tags from the workflow_tags table (sorted by usage_count DESC by default). Use this BEFORE `workflow add` to discover existing tags and reuse them for semantic consistency.",
19446
+ builder: (yargs) => yargs.option("sort-by", {
19447
+ describe: "Sort field",
19448
+ choices: ["usage_count", "name", "created_at"],
19449
+ default: "usage_count"
19450
+ }).option("order", {
19451
+ describe: "Sort order",
19452
+ choices: ["asc", "desc"],
19453
+ default: "desc"
19454
+ }).option("limit", {
19455
+ describe: "Max number of tags to return",
19456
+ type: "number",
19457
+ default: 100
19458
+ }).option("json", {
19459
+ alias: "j",
19460
+ describe: "Output as JSON",
19461
+ type: "boolean",
19462
+ default: false
19463
+ }).option("config", {
19464
+ describe: "Config file path",
19465
+ type: "string"
19466
+ }),
19467
+ async handler(args) {
19468
+ const a = args;
19469
+ const output = new OutputService;
19470
+ const envService = new EnvironmentService(output);
19471
+ try {
19472
+ await runWorkflowTagList({
19473
+ sortBy: a.sortBy,
19474
+ order: a.order,
19475
+ limit: a.limit,
19476
+ asJson: a.json,
19477
+ configPath: a.config,
19478
+ output,
19479
+ envService
19480
+ });
19481
+ } catch (error) {
19482
+ output.error(`Failed to list workflow tags: ${error}`);
19483
+ process.exit(1);
19484
+ } finally {
19485
+ await envService.dispose();
19486
+ }
19487
+ }
19488
+ };
19489
+ var WorkflowTagGetCommand = {
19490
+ command: "get <name>",
19491
+ describe: "Get details of a single workflow tag (usage_count, created_at, updated_at). Returns error if tag not in the workflow_tags table.",
19492
+ builder: (yargs) => yargs.positional("name", {
19493
+ describe: "Tag name",
19494
+ type: "string"
19495
+ }).option("json", {
19496
+ alias: "j",
19497
+ describe: "Output as JSON",
19498
+ type: "boolean",
19499
+ default: false
19500
+ }).option("config", {
19501
+ describe: "Config file path",
19502
+ type: "string"
19503
+ }).check((argv) => {
19504
+ if (typeof argv.name !== "string" || argv.name.length === 0) {
19505
+ throw new Error("Tag name is required");
19506
+ }
19507
+ if (argv.name.length > 50) {
19508
+ throw new Error(`Tag name exceeds 50 characters`);
19509
+ }
19510
+ return true;
19511
+ }),
19512
+ async handler(args) {
19513
+ const a = args;
19514
+ const output = new OutputService;
19515
+ const envService = new EnvironmentService(output);
19516
+ try {
19517
+ await runWorkflowTagGet({
19518
+ name: a.name,
19519
+ asJson: a.json,
19520
+ configPath: a.config,
19521
+ output,
19522
+ envService
19523
+ });
19524
+ } catch (error) {
19525
+ output.error(`Failed to get tag: ${error}`);
19526
+ process.exit(1);
19527
+ } finally {
19528
+ await envService.dispose();
19529
+ }
19530
+ }
19531
+ };
19532
+ var _runWorkflowTagListImpl = async function(opts) {
19533
+ const { output, envService } = opts;
19534
+ await envService.create({ configPath: opts.configPath });
19535
+ const env = envService.getEnvironment();
19536
+ if (!env) {
19537
+ output.error("Failed to create environment");
19538
+ process.exit(1);
19539
+ }
19540
+ const workflowComponent = env.getComponent("workflow");
19541
+ if (!workflowComponent) {
19542
+ output.error("WorkflowComponent not available");
19543
+ process.exit(1);
19544
+ }
19545
+ const service = workflowComponent.getService();
19546
+ if (!service) {
19547
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19548
+ process.exit(1);
19549
+ }
19550
+ const result = await callListTags(service, {
19551
+ sortBy: opts.sortBy ?? "usage_count",
19552
+ order: opts.order ?? "desc",
19553
+ limit: opts.limit ?? 100
19554
+ });
19555
+ if (opts.asJson) {
19556
+ output.json({
19557
+ tags: result.tags,
19558
+ total: result.total
19559
+ });
19560
+ return;
19561
+ }
19562
+ if (result.tags.length === 0) {
19563
+ output.log(chalk72.yellow("\uD83C\uDFF7️ No tags in the workflow_tags table yet."));
19564
+ output.log(chalk72.gray(" Tags are created automatically when you run `workflow add --tags <name>`."));
19565
+ return;
19566
+ }
19567
+ output.log(chalk72.bold(`\uD83C\uDFF7️ Workflow tags (${result.total}):`));
19568
+ output.log(chalk72.gray(` ${padRight("NAME", 24)}${padRight("USAGE", 8)}${padRight("CREATED", 22)}`));
19569
+ for (const t of result.tags) {
19570
+ const line = ` ${padRight(t.name, 24)}${padRight(String(t.usage_count), 8)}${padRight(formatDate2(t.created_at), 22)}`;
19571
+ output.log(line);
19572
+ }
19573
+ output.log(chalk72.gray(`
19574
+ \uD83D\uDCA1 Tip: when adding a new workflow, prefer tags from this list to ensure semantic consistency.`));
19575
+ };
19576
+ var _runWorkflowTagGetImpl = async function(opts) {
19577
+ const { output, envService } = opts;
19578
+ await envService.create({ configPath: opts.configPath });
19579
+ const env = envService.getEnvironment();
19580
+ if (!env) {
19581
+ output.error("Failed to create environment");
19582
+ process.exit(1);
19583
+ }
19584
+ const workflowComponent = env.getComponent("workflow");
19585
+ if (!workflowComponent) {
19586
+ output.error("WorkflowComponent not available");
19587
+ process.exit(1);
19588
+ }
19589
+ const service = workflowComponent.getService();
19590
+ if (!service) {
19591
+ output.error('WorkflowService not initialized. Please run "roy-agent workflow init" first.');
19592
+ process.exit(1);
19593
+ }
19594
+ const tag = callGetTag(service, opts.name);
19595
+ if (!tag) {
19596
+ if (opts.asJson) {
19597
+ output.json({ error: "Tag not found", name: opts.name });
19598
+ } else {
19599
+ output.error(`Tag "${opts.name}" not found in the workflow_tags table.`);
19600
+ output.log(chalk72.gray(`Tip: run \`roy-agent workflow tag list\` to see available tags.`));
19601
+ }
19602
+ process.exitCode = 1;
19603
+ return;
19604
+ }
19605
+ if (opts.asJson) {
19606
+ output.json(tag);
19607
+ return;
19608
+ }
19609
+ output.log(chalk72.bold(`\uD83C\uDFF7️ Tag: ${tag.name}`));
19610
+ output.log(chalk72.gray(` usage_count: ${tag.usage_count}`));
19611
+ output.log(chalk72.gray(` created_at: ${tag.created_at}`));
19612
+ output.log(chalk72.gray(` updated_at: ${tag.updated_at}`));
19613
+ };
19614
+ async function callListTags(service, options) {
19615
+ if (typeof service.listTags !== "function") {
19616
+ return { tags: [], total: 0 };
19617
+ }
19618
+ return await service.listTags(options);
19619
+ }
19620
+ function callGetTag(service, name) {
19621
+ if (typeof service.getTag !== "function") {
19622
+ return;
19623
+ }
19624
+ return service.getTag(name);
19625
+ }
19626
+ function padRight(str, width) {
19627
+ if (str.length >= width)
19628
+ return str.slice(0, width - 1) + " ";
19629
+ return str + " ".repeat(width - str.length);
19630
+ }
19631
+ function formatDate2(iso) {
19632
+ try {
19633
+ const d = new Date(iso);
19634
+ if (isNaN(d.getTime()))
19635
+ return iso;
19636
+ return d.toISOString().replace("T", " ").slice(0, 19);
19637
+ } catch {
19638
+ return iso;
19639
+ }
19640
+ }
19641
+ var runWorkflowTagList = wrapFunction4(_runWorkflowTagListImpl, "workflow.cli.tag.list", {
19642
+ recordParams: true,
19643
+ recordResult: false,
19644
+ log: false
19645
+ });
19646
+ var runWorkflowTagGet = wrapFunction4(_runWorkflowTagGetImpl, "workflow.cli.tag.get", {
19647
+ recordParams: true,
19648
+ recordResult: false,
19649
+ log: false
19650
+ });
19651
+
19142
19652
  // src/commands/workflow/index.ts
19653
+ var WorkflowTagCommand = {
19654
+ command: "tag",
19655
+ describe: "Manage workflow tags — list and get from the workflow_tags table. Use BEFORE `workflow add` to discover existing tags.",
19656
+ builder: (yargs) => yargs.command(WorkflowTagListCommand).command(WorkflowTagGetCommand).demandCommand(1, "请指定 tag 子命令:list 或 get <name>").help(),
19657
+ handler() {}
19658
+ };
19143
19659
  var WorkflowCommand = {
19144
19660
  command: "workflow",
19145
19661
  describe: "Workflow DAG management and execution - create, run, and monitor workflows",
19146
- builder: (yargs) => yargs.command(WorkflowListCommand).command(WorkflowAddCommand).command(WorkflowNodesCommand).command(WorkflowValidateCommand).command(WorkflowGetCommand).command(WorkflowUpdateCommand).command(WorkflowRemoveCommand).command(WorkflowRunCommand).command(WorkflowStopCommand).command(WorkflowStatusCommand).demandCommand(1, "请指定子命令:list、add、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流").help(),
19662
+ builder: (yargs) => yargs.command(WorkflowListCommand).command(WorkflowAddCommand).command(WorkflowSearchCommand).command(WorkflowTagCommand).command(WorkflowNodesCommand).command(WorkflowValidateCommand).command(WorkflowGetCommand).command(WorkflowUpdateCommand).command(WorkflowRemoveCommand).command(WorkflowRunCommand).command(WorkflowStopCommand).command(WorkflowStatusCommand).demandCommand(1, "请指定子命令:list、add、search、tag、validate、nodes、get、update、remove、run、stop、status\n\nResume: 使用 `workflow run -s <sessionId>` 恢复暂停的工作流").help(),
19147
19663
  handler() {}
19148
19664
  };
19149
19665
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-agent-cli",
3
- "version": "1.5.84",
3
+ "version": "1.5.85",
4
4
  "type": "module",
5
5
  "description": "CLI for roy-agent - Non-interactive command execution",
6
6
  "main": "./dist/index.js",