@rishiqing/cli 0.1.3 → 0.1.5

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/README.md CHANGED
@@ -6,19 +6,7 @@
6
6
 
7
7
  面向 Agent 和开发者的日事清 CLI,由 `rishiqing.openapi.json` 驱动命令注册。它不是简单包一层 HTTP 请求,而是把“发现命令、理解参数、预览请求、执行调用”整理成一套稳定的终端接口,方便人和 AI Agent 直接操作日事清。
8
8
 
9
- [安装](#安装与快速开始) · [Agent 能力](#agent-能力) · [命令体系](#命令体系) · [进阶用法](#进阶用法) · [安全](#安全与约束) · [开发](#开发)
10
-
11
- ## 功能
12
-
13
- 当前共提供 **54** 个命令,覆盖 5 个业务域:
14
-
15
- | 类别 | 当前能力 |
16
- | --- | --- |
17
- | `workflow` | 流程应用、模板、步骤、表单、审核项、流程实例、运行中步骤流转 |
18
- | `project` | 项目、模块、卡片、项目成员 |
19
- | `task` | 创建、查询、更新、完成、删除任务,评论,执行人/负责人/参与人维护 |
20
- | `datasheet` | 数据表列表、字段、记录的查询/新增/更新/删除 |
21
- | `contacts` | 全员列表、按姓名解析 `userId` |
9
+ [安装](#安装与快速开始) · [Agent 能力](#agent-能力) · [命令体系](#命令体系) · [进阶用法](#进阶用法) · [安全](#安全与约束)
22
10
 
23
11
  ## 安装与快速开始
24
12
 
@@ -39,7 +27,7 @@
39
27
  npm install -g @rishiqing/cli
40
28
  ```
41
29
 
42
- # 安装 CLI Skill(推荐)
30
+ #### 安装 CLI Skill
43
31
  ```bash
44
32
  rsq-cli install
45
33
  ```
@@ -79,14 +67,6 @@ rsq-cli config init
79
67
  rsq-cli config path
80
68
  ```
81
69
 
82
- 设置接口域名:
83
-
84
- ```bash
85
- rsq-cli config baseurl
86
- rsq-cli config baseurl www.rishiqing.com
87
- rsq-cli config baseurl https://www.rishiqing.com
88
- ```
89
-
90
70
  ## Agent 能力
91
71
 
92
72
  `rsq-cli` 既提供内建的 agent contract,也保留了仓库内可复用的 skill。
@@ -96,6 +76,7 @@ rsq-cli config baseurl https://www.rishiqing.com
96
76
  | `describe` | 结构化输出全部命令、参数、请求体 schema、示例 |
97
77
  | `search` | 按自然语言意图检索命令 |
98
78
  | `describe-agent-contract` | 输出面向 Agent 的路由规则、领域关键词和全局约束 |
79
+ | `agent` | 面向 Agent 的包装命令;收到内联 `--body` 时会自动落盘并转成 `--body-file` 执行 |
99
80
  | `skills/rishiqing` | 识别“日事清”领域请求并路由到对应模块 |
100
81
  | `skills/rsq-workflow-createFlowApplication` | 根据流程场景自动创建流程应用,优先匹配模板,匹配不到回退到标准模式 |
101
82
 
@@ -118,7 +99,8 @@ rsq-cli task --help
118
99
  例如:
119
100
 
120
101
  ```bash
121
- rsq-cli project create --body '{"name":"新项目"}' --json
102
+ echo '{"name":"新项目"}' > request.json
103
+ rsq-cli project create --body-file request.json --json
122
104
  rsq-cli task get --task-id task_xxx --json
123
105
  rsq-cli workflow list-templates --json
124
106
  ```
@@ -128,10 +110,10 @@ rsq-cli workflow list-templates --json
128
110
  这类命令会在内部串联多个步骤,适合 Agent 直接调用:
129
111
 
130
112
  ```bash
131
- rsq-cli contacts resolve-user --user-name 张三 --json
132
- rsq-cli task set-executor-by-name --task-id task_xxx --user-name 张三 --json
133
- rsq-cli project set-member-by-name --project-id proj_xxx --user-name 李四 --json
134
- rsq-cli workflow create-audit-by-names \
113
+ rsq-cli agent contacts resolve-user --user-name 张三 --json
114
+ rsq-cli agent task set-executor-by-name --task-id task_xxx --user-name 张三 --json
115
+ rsq-cli agent project set-member-by-name --project-id proj_xxx --user-name 李四 --json
116
+ rsq-cli agent workflow create-audit-by-names \
135
117
  --flow-application-id flow_xxx \
136
118
  --step-info-id step_xxx \
137
119
  --audit-info-name 部门审批 \
@@ -156,32 +138,3 @@ rsq-cli describe-agent-contract
156
138
  本工具可被 AI Agent 调用执行真实的日事清写操作,因此请务必注意:
157
139
 
158
140
  - `X-Rsq-Api-key` 具备真实权限,不应出现在日志、截图或共享终端中
159
- - 生产环境与测试环境的 `baseUrl` 应严格区分
160
-
161
- ## 开发
162
-
163
- ### 安装依赖
164
-
165
- ```bash
166
- npm install
167
- ```
168
-
169
- ### 类型检查
170
-
171
- ```bash
172
- npm run check
173
- ```
174
-
175
- ### 构建
176
-
177
- ```bash
178
- npm run build
179
- ```
180
-
181
- ### 本地调试
182
-
183
- ```bash
184
- node dist/cli.js --help
185
- node dist/cli.js search "create task" --json
186
- node dist/cli.js describe --json
187
- ```
package/dist/cli.js CHANGED
@@ -2,14 +2,16 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { Command, InvalidArgumentError, Option } from "commander";
5
- import { readFile as readFile2 } from "fs/promises";
5
+ import { mkdtemp, readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
6
+ import os3 from "os";
7
+ import path4 from "path";
6
8
  import readline from "readline/promises";
7
9
  import { stdin, stdout } from "process";
8
10
 
9
11
  // package.json
10
12
  var package_default = {
11
13
  name: "@rishiqing/cli",
12
- version: "0.1.3",
14
+ version: "0.1.5",
13
15
  description: "Agent-oriented CLI for Rishiqing APIs",
14
16
  license: "MIT",
15
17
  type: "module",
@@ -1154,7 +1156,9 @@ function getSkillContract() {
1154
1156
  globalRules: [
1155
1157
  "\u9047\u5230\u65E5\u4E8B\u6E05\u9886\u57DF\u77ED\u8BED\u65F6\uFF0C\u5148\u8BC6\u522B\u4E3A rsq-cli \u64CD\u4F5C\u610F\u56FE\uFF0C\u4E0D\u8981\u628A\u5B83\u5F53\u6210\u6CDB\u5316\u9879\u76EE\u7BA1\u7406\u8BF7\u6C42\u3002",
1156
1158
  "\u4E0D\u786E\u5B9A\u5177\u4F53\u547D\u4EE4\u65F6\uFF0C\u5148\u8C03\u7528 rsq-cli search\uFF1B\u786E\u8BA4\u5019\u9009\u547D\u4EE4\u540E\u518D\u8C03\u7528 rsq-cli describe \u9605\u8BFB\u53C2\u6570\u548C requestBodySchema\u3002",
1159
+ "\u771F\u6B63\u6267\u884C API \u547D\u4EE4\u65F6\uFF0C\u4F18\u5148\u8D70 rsq-cli agent <module> <command> ...\uFF1Bdescribe/search \u7EE7\u7EED\u8D70\u539F\u5165\u53E3\u3002",
1157
1160
  "\u6D89\u53CA\u521B\u5EFA\u3001\u66F4\u65B0\u3001\u7ED1\u5B9A\u3001\u5206\u914D\u7B49\u5199\u64CD\u4F5C\u65F6\uFF0C\u4F18\u5148\u4F7F\u7528 --dry-run --json \u9884\u89C8\u3002",
1161
+ "\u53EA\u8981\u547D\u4EE4\u5E26 request body\uFF0C\u4F18\u5148\u628A JSON \u5199\u5165\u6587\u4EF6\u5E76\u901A\u8FC7 --body-file \u4F20\u5165\uFF0C\u4E0D\u8981\u9ED8\u8BA4\u62FC\u5185\u8054 --body\u3002",
1158
1162
  "\u6240\u6709 id \u5B57\u6BB5\u90FD\u5FC5\u987B\u4F20\u524D\u7F00+uuid\u98CE\u683C\u7684\u5B57\u7B26\u4E32\uFF0C\u4F8B\u5982 flowApp_019d6c0c2f03770b8b789ec313076e64\u3002",
1159
1163
  "\u4E0D\u8981\u4F20\u6570\u5B57\u7C7B\u578B id\uFF0C\u4E5F\u4E0D\u8981\u4F20\u7EAF\u6570\u5B57\u5B57\u7B26\u4E32 id\u3002",
1160
1164
  "\u8C03\u7528\u524D\u4F18\u5148\u9605\u8BFB\u53C2\u6570\u8BF4\u660E\u548C requestBodySchema \u4E2D\u7684\u5B57\u6BB5\u8BF4\u660E\uFF0C\u518D\u7EC4\u88C5\u53C2\u6570\u3002"
@@ -1206,6 +1210,7 @@ async function main() {
1206
1210
  registerDescribe(program);
1207
1211
  registerInstall(program);
1208
1212
  registerSearch(program);
1213
+ registerAgent(program);
1209
1214
  for (const moduleDefinition of getModuleDefinitions()) {
1210
1215
  const moduleCommand = program.command(moduleDefinition.name).description(moduleDefinition.description);
1211
1216
  for (const definition of getRegistry().filter((entry) => entry.module === moduleDefinition.name)) {
@@ -1391,7 +1396,32 @@ function registerSearch(program) {
1391
1396
  printOutput(ranked, options);
1392
1397
  });
1393
1398
  }
1394
- function registerApiCommand(parent, definition) {
1399
+ function registerAgent(program) {
1400
+ const agent = program.command("agent").description("Agent-oriented wrappers that stabilize request body handling");
1401
+ for (const moduleDefinition of getModuleDefinitions()) {
1402
+ const moduleCommand = agent.command(moduleDefinition.name).description(moduleDefinition.description);
1403
+ for (const definition of getRegistry().filter((entry) => entry.module === moduleDefinition.name)) {
1404
+ if (definition.module === "contacts" && definition.name === "resolveUser") {
1405
+ registerResolveUserCommand(moduleCommand, definition);
1406
+ continue;
1407
+ }
1408
+ if (definition.module === "task" && ["setTaskExecutorByName", "setTaskResponsibleByName", "addTaskParticipantByName"].includes(definition.name)) {
1409
+ registerTaskUserByNameCommand(moduleCommand, definition);
1410
+ continue;
1411
+ }
1412
+ if (definition.module === "project" && definition.name === "setProjectMemberByName") {
1413
+ registerProjectUserByNameCommand(moduleCommand, definition);
1414
+ continue;
1415
+ }
1416
+ if (definition.module === "workflow" && ["createAuditInfoByNames", "updateAuditInfoByNames"].includes(definition.name)) {
1417
+ registerWorkflowAuditByNamesCommand(moduleCommand, definition);
1418
+ continue;
1419
+ }
1420
+ registerApiCommand(moduleCommand, definition, "agent-wrapper");
1421
+ }
1422
+ }
1423
+ }
1424
+ function registerApiCommand(parent, definition, mode = "direct") {
1395
1425
  if (!definition.api) {
1396
1426
  throw new Error(`Missing API mapping for command: ${definition.module} ${definition.name}`);
1397
1427
  }
@@ -1410,9 +1440,15 @@ function registerApiCommand(parent, definition) {
1410
1440
  command.addOption(new Option("--body-file <path>", "Read JSON request body from a file"));
1411
1441
  }
1412
1442
  command.action(async (options) => {
1413
- const body = sanitizeRequestBody(definition, await readBody(options));
1443
+ await executeApiCommand(definition, options, mode);
1444
+ });
1445
+ }
1446
+ async function executeApiCommand(definition, options, mode) {
1447
+ const resolvedOptions = await prepareBodyOptions(options, mode);
1448
+ try {
1449
+ const body = sanitizeRequestBody(definition, await readBody(resolvedOptions));
1414
1450
  validateRequestBody(definition, body);
1415
- const request = buildRequest(definition, options, body);
1451
+ const request = buildRequest(definition, resolvedOptions, body);
1416
1452
  if (options.dryRun) {
1417
1453
  const appConfig = await readAppConfig();
1418
1454
  printOutput(
@@ -1422,7 +1458,11 @@ function registerApiCommand(parent, definition) {
1422
1458
  api: definition.api,
1423
1459
  baseUrl: appConfig.baseUrl,
1424
1460
  url: new URL(request.path, ensureTrailingSlash2(appConfig.baseUrl)).toString(),
1425
- request
1461
+ request,
1462
+ agentWrapper: mode === "agent-wrapper" ? {
1463
+ enabled: true,
1464
+ requestBodyTransport: resolvedOptions.bodyFile ? "body-file" : "none"
1465
+ } : void 0
1426
1466
  },
1427
1467
  { json: true }
1428
1468
  );
@@ -1432,7 +1472,9 @@ function registerApiCommand(parent, definition) {
1432
1472
  if (!options.quiet) {
1433
1473
  printOutput(result, options);
1434
1474
  }
1435
- });
1475
+ } finally {
1476
+ await cleanupPreparedBodyOptions(resolvedOptions);
1477
+ }
1436
1478
  }
1437
1479
  function registerResolveUserCommand(parent, definition) {
1438
1480
  const command = parent.command(definition.name).description(definition.summary);
@@ -1673,29 +1715,8 @@ function sanitizeRequestBody(definition, body) {
1673
1715
  if (body === void 0) {
1674
1716
  return void 0;
1675
1717
  }
1676
- if (definition.module === "workflow" && definition.name === "createForm") {
1677
- return sanitizeWorkflowCreateFormBody(body);
1678
- }
1679
1718
  return body;
1680
1719
  }
1681
- function sanitizeWorkflowCreateFormBody(body) {
1682
- if (!isRecord(body)) {
1683
- return body;
1684
- }
1685
- const sanitized = { ...body };
1686
- delete sanitized.formId;
1687
- if (Array.isArray(body.fields)) {
1688
- sanitized.fields = body.fields.map((field) => {
1689
- if (!isRecord(field)) {
1690
- return field;
1691
- }
1692
- const sanitizedField = { ...field };
1693
- delete sanitizedField.fieldId;
1694
- return sanitizedField;
1695
- });
1696
- }
1697
- return sanitized;
1698
- }
1699
1720
  function validateRequestBody(definition, body) {
1700
1721
  if (!definition.hasRequestBody || body === void 0 || !definition.requestBodySchema) {
1701
1722
  return;
@@ -1722,19 +1743,19 @@ function validateCustomBodyRules(definition, body, issues) {
1722
1743
  }
1723
1744
  }
1724
1745
  }
1725
- function validateValueAgainstSchema(value, schema, path4, issues) {
1746
+ function validateValueAgainstSchema(value, schema, path5, issues) {
1726
1747
  if (value === void 0 || value === null) {
1727
1748
  return;
1728
1749
  }
1729
1750
  if (schema.type === "object" && isRecord(value)) {
1730
1751
  for (const requiredKey of schema.required ?? []) {
1731
1752
  if (!(requiredKey in value)) {
1732
- issues.push(`${path4}.${requiredKey}: missing required field`);
1753
+ issues.push(`${path5}.${requiredKey}: missing required field`);
1733
1754
  }
1734
1755
  }
1735
1756
  for (const [propertyName, propertySchema] of Object.entries(schema.properties ?? {})) {
1736
1757
  if (propertyName in value) {
1737
- validateValueAgainstSchema(value[propertyName], propertySchema, `${path4}.${propertyName}`, issues);
1758
+ validateValueAgainstSchema(value[propertyName], propertySchema, `${path5}.${propertyName}`, issues);
1738
1759
  }
1739
1760
  }
1740
1761
  return;
@@ -1742,19 +1763,19 @@ function validateValueAgainstSchema(value, schema, path4, issues) {
1742
1763
  if (schema.type === "array" && Array.isArray(value)) {
1743
1764
  value.forEach((item, index) => {
1744
1765
  if (schema.items) {
1745
- validateValueAgainstSchema(item, schema.items, `${path4}[${index}]`, issues);
1766
+ validateValueAgainstSchema(item, schema.items, `${path5}[${index}]`, issues);
1746
1767
  }
1747
1768
  });
1748
1769
  return;
1749
1770
  }
1750
1771
  if (schema.type === "string" && typeof value === "string") {
1751
- validateStringByDescription(value, schema.description, path4, issues);
1772
+ validateStringByDescription(value, schema.description, path5, issues);
1752
1773
  }
1753
1774
  }
1754
- function validateIdLikeBodyValues(value, path4, issues) {
1775
+ function validateIdLikeBodyValues(value, path5, issues) {
1755
1776
  if (Array.isArray(value)) {
1756
1777
  value.forEach((item, index) => {
1757
- validateIdLikeBodyValues(item, `${path4}[${index}]`, issues);
1778
+ validateIdLikeBodyValues(item, `${path5}[${index}]`, issues);
1758
1779
  });
1759
1780
  return;
1760
1781
  }
@@ -1762,7 +1783,7 @@ function validateIdLikeBodyValues(value, path4, issues) {
1762
1783
  return;
1763
1784
  }
1764
1785
  for (const [key, fieldValue] of Object.entries(value)) {
1765
- const nextPath = `${path4}.${key}`;
1786
+ const nextPath = `${path5}.${key}`;
1766
1787
  if (looksLikeIdField2(key)) {
1767
1788
  const issue = getIdLikeValidationIssue(fieldValue, nextPath);
1768
1789
  if (issue) {
@@ -1811,7 +1832,7 @@ function getIdLikeValidationIssue(value, label) {
1811
1832
  }
1812
1833
  return void 0;
1813
1834
  }
1814
- function validateStringByDescription(value, description, path4, issues) {
1835
+ function validateStringByDescription(value, description, path5, issues) {
1815
1836
  if (!description) {
1816
1837
  return;
1817
1838
  }
@@ -1819,7 +1840,7 @@ function validateStringByDescription(value, description, path4, issues) {
1819
1840
  const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value);
1820
1841
  const isOffsetDateTime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$/.test(value);
1821
1842
  if (!isDateOnly && !isOffsetDateTime) {
1822
- issues.push(`${path4}: invalid format "${value}". Allowed formats: yyyy-MM-dd or yyyy-MM-dd'T'HH:mm:ssXXX`);
1843
+ issues.push(`${path5}: invalid format "${value}". Allowed formats: yyyy-MM-dd or yyyy-MM-dd'T'HH:mm:ssXXX`);
1823
1844
  }
1824
1845
  }
1825
1846
  }
@@ -1835,6 +1856,26 @@ async function readBody(options) {
1835
1856
  }
1836
1857
  return void 0;
1837
1858
  }
1859
+ async function prepareBodyOptions(options, mode) {
1860
+ if (mode !== "agent-wrapper" || !options.body) {
1861
+ return options;
1862
+ }
1863
+ const tempDir = await mkdtemp(path4.join(os3.tmpdir(), "rsq-cli-agent-"));
1864
+ const bodyFile = path4.join(tempDir, "request.json");
1865
+ await writeFile2(bodyFile, options.body, "utf8");
1866
+ return {
1867
+ ...options,
1868
+ body: void 0,
1869
+ bodyFile,
1870
+ __tempDir: tempDir
1871
+ };
1872
+ }
1873
+ async function cleanupPreparedBodyOptions(options) {
1874
+ if (!options.__tempDir) {
1875
+ return;
1876
+ }
1877
+ await rm(options.__tempDir, { recursive: true, force: true });
1878
+ }
1838
1879
  function optionName(parameterName) {
1839
1880
  return `--${toKebabCase(parameterName)} <value>`;
1840
1881
  }