@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 +9 -56
- package/dist/cli.js +79 -38
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/rishiqing.openapi.json +2 -18
- package/skills/rishiqing/SKILL.md +13 -3
- package/skills/rsq-workflow-createFlowApplication/SKILL.md +7 -5
- package/skills/rsq-workflow-createFlowApplication/references/api-sequence.md +8 -8
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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(`${
|
|
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, `${
|
|
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, `${
|
|
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,
|
|
1772
|
+
validateStringByDescription(value, schema.description, path5, issues);
|
|
1752
1773
|
}
|
|
1753
1774
|
}
|
|
1754
|
-
function validateIdLikeBodyValues(value,
|
|
1775
|
+
function validateIdLikeBodyValues(value, path5, issues) {
|
|
1755
1776
|
if (Array.isArray(value)) {
|
|
1756
1777
|
value.forEach((item, index) => {
|
|
1757
|
-
validateIdLikeBodyValues(item, `${
|
|
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 = `${
|
|
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,
|
|
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(`${
|
|
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
|
}
|