@wdyy/skills 0.1.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.
Files changed (37) hide show
  1. package/.well-known/skills/index.json +24 -0
  2. package/.well-known/skills/wdyy-database-standard/SKILL.md +72 -0
  3. package/.well-known/skills/wdyy-database-standard/agents/openai.yaml +4 -0
  4. package/.well-known/skills/wdyy-database-standard/reference/database-rules.md +65 -0
  5. package/.well-known/skills/wdyy-database-standard/scripts/apply-migrations.sh +68 -0
  6. package/.well-known/skills/wdyy-database-standard/scripts/apply-migrations.test.mjs +47 -0
  7. package/.well-known/skills/wdyy-database-standard/scripts/validate-migration-layout.mjs +35 -0
  8. package/.well-known/skills/wdyy-database-standard/scripts/validate-migration-layout.test.mjs +73 -0
  9. package/.well-known/skills/wdyy-database-standard/scripts/validate-table-design.mjs +49 -0
  10. package/.well-known/skills/wdyy-database-standard/templates/table-design.template.md +44 -0
  11. package/.well-known/skills/wdyy-deployment-standard/SKILL.md +68 -0
  12. package/.well-known/skills/wdyy-deployment-standard/agents/openai.yaml +4 -0
  13. package/.well-known/skills/wdyy-deployment-standard/reference/linux-deployment-rules.md +9 -0
  14. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.mjs +48 -0
  15. package/.well-known/skills/wdyy-deployment-standard/scripts/validate-deployment-package.test.mjs +72 -0
  16. package/.well-known/skills/wdyy-deployment-standard/templates/Dockerfile.template +15 -0
  17. package/.well-known/skills/wdyy-deployment-standard/templates/deploy.sh.template +171 -0
  18. package/.well-known/skills/wdyy-deployment-standard/templates/docker-compose.blue-green.yml +34 -0
  19. package/.well-known/skills/wdyy-deployment-standard/templates/nginx-upstream.template.conf +26 -0
  20. package/.well-known/skills/wdyy-internal-api-standard/SKILL.md +60 -0
  21. package/.well-known/skills/wdyy-internal-api-standard/agents/openai.yaml +4 -0
  22. package/.well-known/skills/wdyy-internal-api-standard/reference/internal-api-rules.md +5 -0
  23. package/.well-known/skills/wdyy-internal-api-standard/scripts/check-raw-http-calls.mjs +10 -0
  24. package/.well-known/skills/wdyy-internal-api-standard/templates/api-client.template.ts +20 -0
  25. package/.well-known/skills/wdyy-internal-api-standard/templates/api-error.template.ts +7 -0
  26. package/.well-known/skills/wdyy-internal-api-standard/templates/api-mock.template.ts +8 -0
  27. package/.well-known/skills/wdyy-logging-standard/SKILL.md +65 -0
  28. package/.well-known/skills/wdyy-logging-standard/agents/openai.yaml +4 -0
  29. package/.well-known/skills/wdyy-logging-standard/reference/logging-rules.md +12 -0
  30. package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.mjs +36 -0
  31. package/.well-known/skills/wdyy-logging-standard/scripts/validate-log-entry.test.mjs +133 -0
  32. package/.well-known/skills/wdyy-logging-standard/templates/frontend-error-report.template.ts +22 -0
  33. package/.well-known/skills/wdyy-logging-standard/templates/logger.template.ts +74 -0
  34. package/README.md +65 -0
  35. package/bin/wdyy.js +6 -0
  36. package/lib/wdyy-cli.js +124 -0
  37. package/package.json +24 -0
@@ -0,0 +1,133 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { afterEach, test } from 'node:test';
6
+ import { spawnSync } from 'node:child_process';
7
+
8
+ const validator = new URL('./validate-log-entry.mjs', import.meta.url);
9
+ const temporaryDirectories = [];
10
+
11
+ afterEach(async () => {
12
+ await Promise.all(
13
+ temporaryDirectories.splice(0).map((directory) =>
14
+ rm(directory, { force: true, recursive: true }),
15
+ ),
16
+ );
17
+ });
18
+
19
+ async function validate(entry) {
20
+ const directory = await mkdtemp(join(tmpdir(), 'log-entry-'));
21
+ temporaryDirectories.push(directory);
22
+ const input = join(directory, 'entry.json');
23
+ await writeFile(input, JSON.stringify(entry));
24
+ return spawnSync(process.execPath, [validator.pathname, input], { encoding: 'utf8' });
25
+ }
26
+
27
+ test('蓝绿实例日志必须包含 instanceId', async () => {
28
+ const result = await validate({
29
+ timestamp: '2026-07-09 08:00:00',
30
+ level: 'info',
31
+ service: 'backend',
32
+ instanceId: 'blue',
33
+ env: 'production',
34
+ message: 'request completed',
35
+ });
36
+
37
+ assert.equal(result.status, 0, result.stderr);
38
+ });
39
+
40
+ test('缺少 instanceId 时明确失败', async () => {
41
+ const result = await validate({
42
+ timestamp: '2026-07-09 08:00:00',
43
+ level: 'info',
44
+ service: 'backend',
45
+ env: 'production',
46
+ message: 'request completed',
47
+ });
48
+
49
+ assert.notEqual(result.status, 0);
50
+ assert.match(result.stderr, /instanceId/);
51
+ });
52
+
53
+ test('HTTP 状态码与 result 不一致时明确失败', async () => {
54
+ const result = await validate({
55
+ timestamp: '2026-07-09 08:00:00',
56
+ level: 'info',
57
+ service: 'backend',
58
+ instanceId: 'blue',
59
+ env: 'production',
60
+ message: 'request completed',
61
+ statusCode: 201,
62
+ result: 'failure',
63
+ });
64
+
65
+ assert.notEqual(result.status, 0);
66
+ assert.match(result.stderr, /result/);
67
+ });
68
+
69
+ test('每个敏感字段出现在日志中时都明确失败', async () => {
70
+ for (const key of ['password', 'token', 'secret', 'authorization', 'databaseUrl', 'idCard', 'bankCard']) {
71
+ const result = await validate({
72
+ timestamp: '2026-07-09 08:00:00',
73
+ level: 'info',
74
+ service: 'backend',
75
+ instanceId: 'blue',
76
+ env: 'production',
77
+ message: 'request completed',
78
+ body: { [key]: 'not-allowed' },
79
+ });
80
+
81
+ assert.notEqual(result.status, 0, key);
82
+ assert.match(result.stderr, /Sensitive/, key);
83
+ }
84
+ });
85
+
86
+ test('HTTP 成功结果与截断数组可通过验证', async () => {
87
+ const result = await validate({
88
+ timestamp: '2026-07-09 08:00:00',
89
+ level: 'info',
90
+ service: 'backend',
91
+ instanceId: 'blue',
92
+ env: 'production',
93
+ message: 'request completed',
94
+ query: { page: '1' },
95
+ body: {
96
+ user: 'operator',
97
+ permissions: [...Array.from({ length: 500 }, (_, index) => index), '[TRUNCATED 1 ITEMS]'],
98
+ },
99
+ statusCode: 201,
100
+ result: 'success',
101
+ });
102
+
103
+ assert.equal(result.status, 0, result.stderr);
104
+ });
105
+
106
+ test('超过 500 项且没有截断标记时明确失败', async () => {
107
+ const result = await validate({
108
+ timestamp: '2026-07-09 08:00:00',
109
+ level: 'info',
110
+ service: 'backend',
111
+ instanceId: 'blue',
112
+ env: 'production',
113
+ message: 'request completed',
114
+ body: { items: Array.from({ length: 501 }, (_, index) => index) },
115
+ });
116
+
117
+ assert.notEqual(result.status, 0);
118
+ assert.match(result.stderr, /truncation/);
119
+ });
120
+
121
+ test('UTC ISO timestamp 时明确失败', async () => {
122
+ const result = await validate({
123
+ timestamp: '2026-07-09T00:00:00.000Z',
124
+ level: 'info',
125
+ service: 'backend',
126
+ instanceId: 'blue',
127
+ env: 'production',
128
+ message: 'request completed',
129
+ });
130
+
131
+ assert.notEqual(result.status, 0);
132
+ assert.match(result.stderr, /timestamp/);
133
+ });
@@ -0,0 +1,22 @@
1
+ type FrontendErrorReport = {
2
+ message: string;
3
+ stack?: string;
4
+ path: string;
5
+ traceId?: string;
6
+ occurredAt: string;
7
+ };
8
+
9
+ export async function reportFrontendError(report: FrontendErrorReport): Promise<void> {
10
+ const payload = {
11
+ ...report,
12
+ message: report.message.slice(0, 500),
13
+ stack: report.stack?.slice(0, 2000),
14
+ };
15
+ const response = await fetch('/api/client-errors', {
16
+ method: 'POST',
17
+ headers: { 'Content-Type': 'application/json' },
18
+ credentials: 'same-origin',
19
+ body: JSON.stringify(payload),
20
+ });
21
+ if (!response.ok) throw new Error(`Frontend error report failed: ${response.status}`);
22
+ }
@@ -0,0 +1,74 @@
1
+ export type LogContext = {
2
+ traceId?: string;
3
+ userId?: string;
4
+ method?: string;
5
+ path?: string;
6
+ statusCode?: number;
7
+ query?: Record<string, unknown>;
8
+ body?: unknown;
9
+ durationMs?: number;
10
+ errorCode?: string;
11
+ params?: Record<string, unknown>;
12
+ };
13
+
14
+ export type LogEntry = LogContext & {
15
+ timestamp: string;
16
+ level: 'debug' | 'info' | 'warn' | 'error';
17
+ service: string;
18
+ instanceId: string;
19
+ env: string;
20
+ message: string;
21
+ result?: 'success' | 'failure';
22
+ };
23
+
24
+ const sensitiveKeys = new Set([
25
+ 'password', 'token', 'secret', 'authorization', 'databaseurl', 'idcard', 'bankcard',
26
+ ]);
27
+
28
+ export const redact = (value: unknown): unknown => {
29
+ if (Array.isArray(value)) {
30
+ const items = value.slice(0, 500).map(redact);
31
+ return value.length > 500
32
+ ? [...items, '[TRUNCATED ' + (value.length - 500) + ' ITEMS]']
33
+ : items;
34
+ }
35
+ if (!value || typeof value !== 'object') return value;
36
+ return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => (
37
+ sensitiveKeys.has(key.toLowerCase()) ? [] : [[key, redact(item)]]
38
+ )));
39
+ };
40
+
41
+ const pad = (value: number) => String(value).padStart(2, '0');
42
+
43
+ export const formatLocalTimestamp = (date = new Date()): string => (
44
+ [date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate())].join('-')
45
+ + ' '
46
+ + [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())].join(':')
47
+ );
48
+
49
+ export const createLogFileName = (date = new Date()): string => (
50
+ formatLocalTimestamp(date).replace(' ', '_').replaceAll(':', '-') + '.log'
51
+ );
52
+
53
+ const resultForStatusCode = (statusCode: number): LogEntry['result'] => {
54
+ if (statusCode >= 100 && statusCode <= 399) return 'success';
55
+ if (statusCode >= 400 && statusCode <= 599) return 'failure';
56
+ return undefined;
57
+ };
58
+
59
+ export const toLogEntry = (level: LogEntry['level'], message: string, context: LogContext): LogEntry => {
60
+ const sanitizedContext = redact(context) as LogContext;
61
+ const result = sanitizedContext.statusCode === undefined
62
+ ? undefined
63
+ : resultForStatusCode(sanitizedContext.statusCode);
64
+ return {
65
+ timestamp: formatLocalTimestamp(),
66
+ level,
67
+ service: process.env.SERVICE_NAME ?? 'backend',
68
+ instanceId: process.env.INSTANCE_ID ?? 'local',
69
+ env: process.env.NODE_ENV ?? 'development',
70
+ message,
71
+ ...sanitizedContext,
72
+ ...(result ? { result } : {}),
73
+ };
74
+ };
package/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # wdyy_skills
2
+
3
+ 面向企业内部软件开发的精简 Agent Skill 仓库。开发流程采用 [OpenSpec](https://github.com/Fission-AI/OpenSpec);本仓库只保留技术栈以外、需要独立专业约束与可执行验证的企业规则。
4
+
5
+ ## 体系结构
6
+
7
+ ```text
8
+ AGENTS.md 全局基线、技术栈、OpenSpec 流程与 Skill 路由
9
+ OpenSpec 单项变更的需求、设计、任务、实施与归档
10
+ .well-known/skills/wdyy-* 数据库、内部接口、日志、部署的专业规则
11
+ ```
12
+
13
+ 当前 `.well-known/skills/index.json` 登记 4 个企业 Skill。
14
+
15
+ | Skill | 职责 |
16
+ |---|---|
17
+ | `wdyy-database-standard` | PostgreSQL 18 数据库设计、版本化迁移、数据兼容性与 DDL 校验。 |
18
+ | `wdyy-internal-api-standard` | 内部 REST 调用的认证、traceId、超时、受控重试、错误映射与响应校验。 |
19
+ | `wdyy-logging-standard` | 结构化日志、敏感字段移除、traceId、前端异常上报与日志轮转。 |
20
+ | `wdyy-deployment-standard` | 人工 SCP 交付、版本化前端资源、Docker 蓝绿发布与回滚。 |
21
+
22
+ ## 使用方式
23
+
24
+ 安装全局包后,在目标项目根目录执行:
25
+
26
+ ```bash
27
+ npm install -g @wdyy/skills
28
+ wdyy init
29
+ ```
30
+
31
+ `wdyy init` 自动执行 `openspec init`,将 4 个受管理 Skill 安装到 `.agents/skills/`。若目标项目已有 `AGENTS.md`,原文件保持不变,命令将生成 `AGENTS_new.md`;每次运行都会覆盖更新 `.agents/skills/wdyy-*`,但不修改同级自定义 Skill。
32
+
33
+ 中大型变更先使用 `/opsx:explore` 或 `/opsx:propose <change-name>`,在方案确认后使用 `/opsx:apply`;完成验证后使用 `/opsx:archive`。涉及数据库、内部 API、日志、部署时,按 `AGENTS.md` 的路由加载相应企业 Skill。配置仅从环境变量读取,不得提交秘密或生产环境 `.env`。
34
+
35
+ ## 固定技术栈
36
+
37
+ - Node.js 24、pnpm workspace、TypeScript
38
+ - Vue 3、Vite、Pinia、Vue Router、Element Plus
39
+ - NestJS、PostgreSQL 18
40
+ - Redis、RabbitMQ 仅在工程师明确确认后启用
41
+
42
+ ## 仓库维护
43
+
44
+ - 所有 Skill 位于 `.well-known/skills`。
45
+ - 新增、更新、删除 Skill 后必须同步更新 `.well-known/skills/index.json`。
46
+ - 自定义企业 Skill 的目录名和 frontmatter `name` 必须以 `wdyy-` 开头且保持一致。
47
+ - 只修改 `wdyy-*` Skill;详细且易变的规则应放在对应的 `reference/`,可重复校验应放在 `scripts/`。
48
+
49
+ ## 名称迁移
50
+
51
+ | 原名称 | 新名称 |
52
+ |---|---|
53
+ | `enterprise-database-standard` | `wdyy-database-standard` |
54
+ | `enterprise-internal-api-standard` | `wdyy-internal-api-standard` |
55
+ | `enterprise-logging-standard` | `wdyy-logging-standard` |
56
+ | `enterprise-deployment-standard` | `wdyy-deployment-standard` |
57
+
58
+ 旧名称不再作为本仓库提供的 Skill 身份。请将项目规则与手动调用更新为相应的 `wdyy-*` 名称。
59
+
60
+ ## 验证
61
+
62
+ ```bash
63
+ node --input-type=module -e "import fs from 'node:fs'; const x = JSON.parse(fs.readFileSync('.well-known/skills/index.json', 'utf8')); for (const s of x.skills) for (const f of s.files) fs.accessSync('.well-known/skills/' + s.name + '/' + f)"
64
+ node --test $(find .well-known/skills -path '*/scripts/*.test.mjs' -print)
65
+ ```
package/bin/wdyy.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../lib/wdyy-cli.js';
4
+
5
+ const exitCode = await run(process.argv.slice(2));
6
+ process.exitCode = exitCode;
@@ -0,0 +1,124 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { access, cp, mkdir, mkdtemp, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
7
+ const managedSkillNames = [
8
+ 'wdyy-database-standard',
9
+ 'wdyy-internal-api-standard',
10
+ 'wdyy-logging-standard',
11
+ 'wdyy-deployment-standard'
12
+ ];
13
+
14
+ const generatedAgentsContent = `# 企业开发规则
15
+
16
+ 本项目使用 wdyy 企业 Skill。新增、更新、删除受管理 Skill 后,必须同步核对项目内 Skill 清单;Skill 目录名必须与 \`SKILL.md\` frontmatter 的 \`name\` 一致。
17
+
18
+ ## 固定技术栈
19
+
20
+ - Node.js 24、pnpm workspace、TypeScript。
21
+ - 前端:Vue 3、Vite、Pinia、Vue Router、Element Plus。
22
+ - 后端:NestJS。
23
+ - 数据库:PostgreSQL 18。
24
+ - Redis、RabbitMQ 仅在工程师明确确认后引入。
25
+
26
+ 默认项目目录为 \`apps/frontend\`、\`apps/backend\`、\`packages\`、\`database/migrations\`、\`deploy/docker\`、\`deploy/nginx\`、\`docs\`、\`scripts\`、\`tests\`。配置仅从环境变量读取;不得提交秘密或生产环境 \`.env\`。
27
+
28
+ ## OpenSpec 工作流
29
+
30
+ 功能新增、跨模块变更、数据模型变更、重构和生产发布必须使用 OpenSpec:不明确时先执行 \`explore\`;明确需求时执行 \`propose <change-name>\`;涉及业务、接口、数据库、部署或不可逆决定时,必须等待工程师确认方案后执行 \`apply\`;完成实现与验证后执行 \`/opsx:archive\`。
31
+
32
+ ## Skill 路由
33
+
34
+ | 场景 | 必须使用的 Skill |
35
+ |---|---|
36
+ | PostgreSQL 表、DDL、迁移、数据兼容性 | \`wdyy-database-standard\` |
37
+ | 服务间 REST 调用、认证、超时、重试、错误映射 | \`wdyy-internal-api-standard\` |
38
+ | 结构化日志、traceId、脱敏、前端异常上报 | \`wdyy-logging-standard\` |
39
+ | Linux 发布、Docker 蓝绿、Nginx 切流、回滚 | \`wdyy-deployment-standard\` |
40
+
41
+ ## 通用边界
42
+
43
+ - 项目根目录应提供真实可执行的 \`pnpm dev\`、\`pnpm build\`、\`pnpm test\`、\`pnpm lint\` 命令;\`pnpm dev\` 不得启动或管理数据库、缓存或消息队列。
44
+ - 代码、配置、迁移、测试与部署文件必须以实际实现为准;不得用模板虚构已实现能力。
45
+ - 任何破坏性数据库操作、生产发布、引入基础设施或新外部依赖,必须在 OpenSpec 方案中说明影响并获得工程师确认。
46
+ - 不得静默吞错、跳过验证、以兜底逻辑掩盖未决问题。
47
+ `;
48
+
49
+ function printHelp() {
50
+ console.log('用法: wdyy init');
51
+ console.log('在当前项目生成 AGENTS 规则、安装 wdyy Skill,并执行 openspec init。');
52
+ }
53
+
54
+ async function installManagedSkills(targetRoot, writtenPaths) {
55
+ const skillsRoot = join(targetRoot, '.agents', 'skills');
56
+ await mkdir(skillsRoot, { recursive: true });
57
+ const temporaryRoot = await mkdtemp(join(skillsRoot, '.wdyy-skills-'));
58
+
59
+ try {
60
+ for (const skillName of managedSkillNames) {
61
+ const source = join(packageRoot, '.well-known', 'skills', skillName);
62
+ const prepared = join(temporaryRoot, skillName);
63
+ await cp(source, prepared, { recursive: true, force: true, errorOnExist: false });
64
+ }
65
+
66
+ for (const skillName of managedSkillNames) {
67
+ const destination = join(skillsRoot, skillName);
68
+ await rm(destination, { recursive: true, force: true });
69
+ await rename(join(temporaryRoot, skillName), destination);
70
+ writtenPaths.push(destination);
71
+ console.log(`已更新 Skill: ${destination}`);
72
+ }
73
+ } finally {
74
+ await rm(temporaryRoot, { recursive: true, force: true });
75
+ }
76
+ }
77
+
78
+ async function writeAgentsFile(targetRoot, writtenPaths) {
79
+ const agentsPath = join(targetRoot, 'AGENTS.md');
80
+ let destination = agentsPath;
81
+ try {
82
+ await access(agentsPath);
83
+ destination = join(targetRoot, 'AGENTS_new.md');
84
+ } catch (error) {
85
+ if (error.code !== 'ENOENT') throw error;
86
+ }
87
+ await writeFile(destination, generatedAgentsContent, 'utf8');
88
+ writtenPaths.push(destination);
89
+ console.log(`已写入项目规则: ${destination}`);
90
+ }
91
+
92
+ function initializeOpenSpec(targetRoot, writtenPaths) {
93
+ const result = spawnSync('openspec', ['init'], { cwd: targetRoot, stdio: 'inherit' });
94
+ if (result.error || result.status !== 0) {
95
+ const detail = result.error?.message ?? `退出码 ${result.status ?? 1}`;
96
+ console.error(`OpenSpec 初始化失败: ${detail}`);
97
+ console.error(`已写入路径:\n${writtenPaths.map((path) => `- ${path}`).join('\n')}`);
98
+ return result.status ?? 1;
99
+ }
100
+
101
+ console.log('OpenSpec 初始化完成。');
102
+ return 0;
103
+ }
104
+
105
+ export async function run(argumentsList) {
106
+ if (argumentsList.length === 1 && argumentsList[0] === 'init') {
107
+ const writtenPaths = [];
108
+ try {
109
+ await writeAgentsFile(process.cwd(), writtenPaths);
110
+ await installManagedSkills(process.cwd(), writtenPaths);
111
+ const openSpecExitCode = initializeOpenSpec(process.cwd(), writtenPaths);
112
+ if (openSpecExitCode !== 0) return openSpecExitCode;
113
+ console.log('wdyy 初始化完成。');
114
+ return 0;
115
+ } catch (error) {
116
+ console.error(`wdyy 初始化失败: ${error.message}`);
117
+ if (writtenPaths.length > 0) console.error(`已写入路径:\n${writtenPaths.map((path) => `- ${path}`).join('\n')}`);
118
+ return 1;
119
+ }
120
+ }
121
+
122
+ printHelp();
123
+ return argumentsList.length === 1 && ['--help', '-h'].includes(argumentsList[0]) ? 0 : 1;
124
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@wdyy/skills",
3
+ "version": "0.1.0",
4
+ "description": "企业内部开发 Skill 与项目初始化命令",
5
+ "type": "module",
6
+ "bin": {
7
+ "wdyy": "bin/wdyy.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "lib",
12
+ ".well-known/skills",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=24"
17
+ },
18
+ "scripts": {
19
+ "test": "node --test tests/*.test.mjs",
20
+ "lint": "node --check bin/wdyy.js && node --check lib/*.js",
21
+ "build": "node --check bin/wdyy.js && node --check lib/*.js",
22
+ "dev": "node bin/wdyy.js --help"
23
+ }
24
+ }