@42ailab/42plugin 0.2.14 → 0.2.16
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 +17 -262
- package/bin/42plugin +97 -0
- package/package.json +13 -44
- package/dist/npm/cli.js +0 -606
package/README.md
CHANGED
|
@@ -1,276 +1,31 @@
|
|
|
1
1
|
# 42plugin CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AI 插件管理工具,用于 Claude Code 插件的搜索、安装、发布。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 前提条件
|
|
8
|
-
|
|
9
|
-
- 需要安装 [Bun](https://bun.sh)(用于运行与构建)
|
|
10
|
-
- Node.js/npm 非必需,依赖通过 `bun install` 处理
|
|
11
|
-
|
|
12
|
-
## 快速开始
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
# 安装依赖
|
|
16
|
-
bun install
|
|
17
|
-
|
|
18
|
-
# 开发运行
|
|
19
|
-
bun run dev -- <command> [options]
|
|
20
|
-
|
|
21
|
-
# 或直接运行
|
|
22
|
-
bun run src/index.ts <command> [options]
|
|
23
|
-
|
|
24
|
-
# 构建跨平台可执行文件
|
|
25
|
-
bun run build
|
|
26
|
-
# 输出位于 dist/ 下
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## 技术栈
|
|
30
|
-
|
|
31
|
-
| 技术 | 版本 | 说明 |
|
|
32
|
-
|------|------|------|
|
|
33
|
-
| TypeScript | 5.7.0 | 类型安全 |
|
|
34
|
-
| Bun | 1.0+ | 运行时和包管理 |
|
|
35
|
-
| Commander.js | 13.0.0 | CLI 框架 |
|
|
36
|
-
| @libsql/client | 0.14.0 | SQLite 数据库 |
|
|
37
|
-
| better-auth | 1.4.6 | 认证客户端 |
|
|
38
|
-
| chalk | 5.4.1 | 终端颜色 |
|
|
39
|
-
| ora | 8.1.1 | 加载动画 |
|
|
40
|
-
|
|
41
|
-
## 目录结构
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
42plugin-cli/
|
|
45
|
-
├── src/
|
|
46
|
-
│ ├── index.ts # 入口文件
|
|
47
|
-
│ ├── cli.ts # 命令定义
|
|
48
|
-
│ ├── config.ts # 配置管理
|
|
49
|
-
│ ├── commands/ # CLI 子命令
|
|
50
|
-
│ │ ├── auth.ts # 登录/授权/登出
|
|
51
|
-
│ │ ├── search.ts # 搜索
|
|
52
|
-
│ │ ├── install.ts # 安装
|
|
53
|
-
│ │ ├── list.ts # 查看已安装
|
|
54
|
-
│ │ ├── uninstall.ts # 卸载
|
|
55
|
-
│ │ ├── version.ts # 版本
|
|
56
|
-
│ │ └── publish.ts # 发布(开发中)
|
|
57
|
-
│ ├── services/ # 业务逻辑
|
|
58
|
-
│ │ ├── api.ts # API 客户端
|
|
59
|
-
│ │ ├── auth.ts # 凭证管理
|
|
60
|
-
│ │ ├── download.ts # 下载处理
|
|
61
|
-
│ │ ├── cache.ts # 缓存管理
|
|
62
|
-
│ │ ├── link.ts # 符号链接
|
|
63
|
-
│ │ └── project.ts # 项目注册
|
|
64
|
-
│ ├── db/
|
|
65
|
-
│ │ └── client.ts # 数据库客户端
|
|
66
|
-
│ ├── types/ # 类型定义
|
|
67
|
-
│ └── utils/ # 工具函数
|
|
68
|
-
├── tests/ # 测试用例
|
|
69
|
-
└── scripts/ # 构建脚本
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## CLI 命令
|
|
73
|
-
|
|
74
|
-
### 认证
|
|
75
|
-
|
|
76
|
-
使用 Better Auth Device Authorization Flow (RFC 8628):
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
42plugin auth # 浏览器授权登录
|
|
80
|
-
42plugin auth --status # 查看登录状态
|
|
81
|
-
42plugin auth --logout # 登出并清除本地凭证
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 搜索
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
42plugin search <关键词> [--type skill|agent|command|hook|kit] [--limit 20] [--json]
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### 安装
|
|
91
|
-
|
|
92
|
-
安装格式:
|
|
93
|
-
|
|
94
|
-
| 格式 | 说明 |
|
|
95
|
-
|------|------|
|
|
96
|
-
| `author/name` | 安装插件 |
|
|
97
|
-
| `author/kit/slug` | 安装套包 |
|
|
98
|
-
|
|
99
|
-
```bash
|
|
100
|
-
# 安装插件
|
|
101
|
-
42plugin install alice/smart-reviewer
|
|
102
|
-
|
|
103
|
-
# 安装套包
|
|
104
|
-
42plugin install user/kit/tools # 仅安装必选项
|
|
105
|
-
42plugin install user/kit/tools --optional # 包含可选项
|
|
106
|
-
|
|
107
|
-
# 安装某个插件所属的整个套包
|
|
108
|
-
42plugin install alice/smart-reviewer -k
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
选项:
|
|
112
|
-
- `-g, --global`:安装到全局目录 (~/.42plugin/global/)
|
|
113
|
-
- `-k, --kit`:安装该插件所属的整个套包
|
|
114
|
-
- `--optional`:安装套包时包含可选项
|
|
115
|
-
- `--force`:强制重新下载
|
|
116
|
-
- `--no-cache`:跳过缓存命中检查
|
|
117
|
-
- `--from <source>`:指定来源套件
|
|
118
|
-
|
|
119
|
-
### 管理
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# 查看插件
|
|
123
|
-
42plugin list # 当前项目已安装的插件
|
|
124
|
-
42plugin list --global # 全局安装的插件 (~/.claude/)
|
|
125
|
-
42plugin list --all # 所有项目 + 全局的插件
|
|
126
|
-
42plugin list --type skill --json # 筛选类型,输出 JSON
|
|
127
|
-
|
|
128
|
-
# 卸载插件
|
|
129
|
-
42plugin uninstall <full_name> # 从当前项目卸载
|
|
130
|
-
42plugin uninstall <full_name> --global # 从全局目录卸载
|
|
131
|
-
42plugin uninstall <full_name> --purge # 卸载并清除下载缓存
|
|
132
|
-
|
|
133
|
-
# 版本
|
|
134
|
-
42plugin version
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 发布(开发中)
|
|
5
|
+
## 安装
|
|
138
6
|
|
|
139
7
|
```bash
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
- 用户必须登录
|
|
146
|
-
- 用户必须验证手机号
|
|
147
|
-
- 用户必须设置 username
|
|
148
|
-
|
|
149
|
-
## 数据存储
|
|
150
|
-
|
|
151
|
-
| 类型 | 路径 | 说明 |
|
|
152
|
-
|------|------|------|
|
|
153
|
-
| 数据库 | `~/.42plugin/local.db` | 本地 SQLite(Windows: `%APPDATA%/42plugin/`) |
|
|
154
|
-
| 下载缓存 | `~/.42plugin/cache/` | 插件原始文件缓存 |
|
|
155
|
-
| 登录凭证 | `~/.42plugin/secrets.json` | 权限 600 |
|
|
156
|
-
| 全局安装 | `~/.claude/` | 对所有项目生效,Claude Code 可识别 |
|
|
157
|
-
| 项目安装 | `./.claude/` | 仅对当前项目生效 |
|
|
158
|
-
|
|
159
|
-
### 全局 vs 项目安装
|
|
160
|
-
|
|
8
|
+
npm install -g @42ailab/42plugin
|
|
9
|
+
# 或
|
|
10
|
+
bun add -g @42ailab/42plugin
|
|
11
|
+
# 或
|
|
12
|
+
brew install 42ailab/42plugin/42plugin
|
|
161
13
|
```
|
|
162
|
-
~/.claude/ # 全局目录,对所有项目生效
|
|
163
|
-
├── skills/
|
|
164
|
-
├── agents/
|
|
165
|
-
└── ...
|
|
166
14
|
|
|
167
|
-
|
|
168
|
-
├── skills/
|
|
169
|
-
├── agents/
|
|
170
|
-
└── ...
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
- **全局安装** (`--global`):插件安装到 `~/.claude/`,所有项目都能使用
|
|
174
|
-
- **项目安装**(默认):插件安装到当前项目的 `.claude/`,仅当前项目可用
|
|
175
|
-
|
|
176
|
-
## 本地数据库表(SQLite)
|
|
177
|
-
|
|
178
|
-
| 表名 | 说明 |
|
|
179
|
-
|------|------|
|
|
180
|
-
| `projects` | 本地项目注册(id, path, name, registered_at, last_used_at)|
|
|
181
|
-
| `plugin_cache` | 插件下载缓存(full_name, type, version, cache_path, checksum)|
|
|
182
|
-
| `project_plugins` | 项目已安装的插件(project_id, full_name, type, version, link_path, source)|
|
|
183
|
-
| `config` | 配置项(key-value)|
|
|
184
|
-
|
|
185
|
-
注意:这些表仅在 SQLite 模式下创建,PostgreSQL 模式会跳过以避免污染云端数据库。
|
|
186
|
-
|
|
187
|
-
## 环境变量
|
|
15
|
+
## 使用
|
|
188
16
|
|
|
189
17
|
```bash
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
#
|
|
195
|
-
DEBUG=true
|
|
196
|
-
|
|
197
|
-
# 数据库(可选,使用 PostgreSQL 替代默认 SQLite)
|
|
198
|
-
CLI_DB_DRIVER=postgres
|
|
199
|
-
CLI_DATABASE_URL=postgresql://user@localhost:5432/42plugin_local
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## 认证流程
|
|
203
|
-
|
|
204
|
-
```
|
|
205
|
-
CLI API Browser
|
|
206
|
-
│ │ │
|
|
207
|
-
│─────POST /api/auth/device/code─────>│ │
|
|
208
|
-
│<────{device_code, user_code}────────│ │
|
|
209
|
-
│ │ │
|
|
210
|
-
│ 显示 user_code 和 verification_uri │ │
|
|
211
|
-
│───────────────────────────────────────────────────────────>│
|
|
212
|
-
│ │ 用户输入 user_code 并授权 │
|
|
213
|
-
│ │<──────────────────────────────────│
|
|
214
|
-
│ │ │
|
|
215
|
-
│─────POST /api/auth/device/token──>│ │
|
|
216
|
-
│ (轮询,5s 间隔) │ │
|
|
217
|
-
│<────{access_token, user}─────────│ │
|
|
218
|
-
│ │ │
|
|
219
|
-
│ 保存到 secrets.json │ │
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
## 安装流程
|
|
223
|
-
|
|
18
|
+
42plugin search <keyword> # 搜索插件
|
|
19
|
+
42plugin install <plugin> # 安装插件
|
|
20
|
+
42plugin list # 查看已安装
|
|
21
|
+
42plugin publish # 发布插件
|
|
22
|
+
42plugin --help # 查看帮助
|
|
224
23
|
```
|
|
225
|
-
1. 解析安装目标格式(utils/target.ts)
|
|
226
|
-
- author/name → TargetType.Plugin
|
|
227
|
-
- author/kit/slug → TargetType.Kit
|
|
228
24
|
|
|
229
|
-
|
|
230
|
-
- GET /cli/v1/download/plugin/{author}/{name}
|
|
231
|
-
- GET /cli/v1/download/kit/{username}/{slug}
|
|
25
|
+
## 文档
|
|
232
26
|
|
|
233
|
-
|
|
27
|
+
https://docs.42plugin.com
|
|
234
28
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
5. 验证 SHA256 校验和
|
|
238
|
-
|
|
239
|
-
6. 解压到缓存目录 (~/.42plugin/cache)
|
|
240
|
-
|
|
241
|
-
7. 创建符号链接到项目 .claude/ 目录
|
|
242
|
-
|
|
243
|
-
8. 记录到本地数据库(project_plugins 表)
|
|
244
|
-
```
|
|
29
|
+
## License
|
|
245
30
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
### 认证 & 用户
|
|
249
|
-
|
|
250
|
-
| 端点 | 方法 | 说明 |
|
|
251
|
-
|------|------|------|
|
|
252
|
-
| `/api/auth/device/code` | POST | 请求 device code |
|
|
253
|
-
| `/api/auth/device/token` | POST | 轮询 token |
|
|
254
|
-
| `/api/auth/get-session` | GET | 获取用户会话 |
|
|
255
|
-
| `/api/user/installed` | POST | 同步安装记录 |
|
|
256
|
-
| `/api/user/installed/{author}/{name}` | DELETE | 同步卸载记录 |
|
|
257
|
-
|
|
258
|
-
### CLI 专用 API(稳定契约)
|
|
259
|
-
|
|
260
|
-
| 端点 | 方法 | 说明 |
|
|
261
|
-
|------|------|------|
|
|
262
|
-
| `/cli/v1/download/plugin/{author}/{name}` | GET | 获取插件下载信息 |
|
|
263
|
-
| `/cli/v1/download/kit/{username}/{slug}` | GET | 获取套包下载信息 |
|
|
264
|
-
| `/cli/v1/search?q=` | GET | 搜索插件 |
|
|
265
|
-
| `/cli/v1/session` | GET | 会话验证 |
|
|
266
|
-
|
|
267
|
-
> CLI 专用端点直接返回 camelCase 格式,扁平化结构,独立版本控制。
|
|
268
|
-
|
|
269
|
-
## 开发命令
|
|
270
|
-
|
|
271
|
-
```bash
|
|
272
|
-
bun test # 运行测试
|
|
273
|
-
bun run lint # ESLint
|
|
274
|
-
bun run format # Prettier
|
|
275
|
-
bun run typecheck # TypeScript 类型检查
|
|
276
|
-
```
|
|
31
|
+
MIT
|
package/bin/42plugin
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 42plugin CLI 启动脚本
|
|
5
|
+
*
|
|
6
|
+
* 查找并执行对应平台的二进制文件
|
|
7
|
+
* 参考 esbuild、biome 的实现方式
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { spawnSync } from 'node:child_process';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
import { join, dirname } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
|
|
17
|
+
// 平台映射
|
|
18
|
+
const PLATFORMS = {
|
|
19
|
+
'darwin-arm64': '@42ailab/42plugin-darwin-arm64',
|
|
20
|
+
'darwin-x64': '@42ailab/42plugin-darwin-x64',
|
|
21
|
+
'linux-arm64': '@42ailab/42plugin-linux-arm64',
|
|
22
|
+
'linux-x64': '@42ailab/42plugin-linux-x64',
|
|
23
|
+
'win32-x64': '@42ailab/42plugin-win32-x64',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// 获取当前平台标识
|
|
27
|
+
function getPlatformKey() {
|
|
28
|
+
const platform = process.platform;
|
|
29
|
+
const arch = process.arch;
|
|
30
|
+
|
|
31
|
+
// x86_64 在 Node.js 中是 x64
|
|
32
|
+
const normalizedArch = arch === 'x64' ? 'x64' : arch;
|
|
33
|
+
|
|
34
|
+
return `${platform}-${normalizedArch}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 查找二进制文件路径
|
|
38
|
+
function findBinary() {
|
|
39
|
+
const platformKey = getPlatformKey();
|
|
40
|
+
const packageName = PLATFORMS[platformKey];
|
|
41
|
+
|
|
42
|
+
if (!packageName) {
|
|
43
|
+
console.error(`错误: 不支持的平台 ${platformKey}`);
|
|
44
|
+
console.error(`支持的平台: ${Object.keys(PLATFORMS).join(', ')}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 可能的二进制文件名
|
|
49
|
+
const binaryName = process.platform === 'win32' ? '42plugin.exe' : '42plugin';
|
|
50
|
+
|
|
51
|
+
// 尝试多个可能的路径
|
|
52
|
+
const possiblePaths = [
|
|
53
|
+
// 1. 作为依赖安装时的路径 (node_modules/@42ailab/42plugin-xxx/bin/42plugin)
|
|
54
|
+
join(__dirname, '..', '..', packageName, 'bin', binaryName),
|
|
55
|
+
// 2. 全局安装时的路径
|
|
56
|
+
join(__dirname, '..', 'node_modules', packageName, 'bin', binaryName),
|
|
57
|
+
// 3. pnpm 的路径
|
|
58
|
+
join(__dirname, '..', '..', '.pnpm', 'node_modules', packageName, 'bin', binaryName),
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const binPath of possiblePaths) {
|
|
62
|
+
if (existsSync(binPath)) {
|
|
63
|
+
return binPath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 找不到二进制文件
|
|
68
|
+
console.error(`错误: 找不到 ${platformKey} 平台的二进制文件`);
|
|
69
|
+
console.error(`请确保已安装: ${packageName}`);
|
|
70
|
+
console.error('');
|
|
71
|
+
console.error('尝试重新安装:');
|
|
72
|
+
console.error(' npm install -g @42ailab/42plugin');
|
|
73
|
+
console.error('');
|
|
74
|
+
console.error('或使用 Homebrew (macOS/Linux):');
|
|
75
|
+
console.error(' brew install 42ailab/42plugin/42plugin');
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 执行二进制文件
|
|
80
|
+
function run() {
|
|
81
|
+
const binPath = findBinary();
|
|
82
|
+
const args = process.argv.slice(2);
|
|
83
|
+
|
|
84
|
+
const result = spawnSync(binPath, args, {
|
|
85
|
+
stdio: 'inherit',
|
|
86
|
+
shell: false,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (result.error) {
|
|
90
|
+
console.error(`执行失败: ${result.error.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
process.exit(result.status ?? 0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
run();
|
package/package.json
CHANGED
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@42ailab/42plugin",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.16",
|
|
4
|
+
"description": "42plugin CLI - AI 插件管理工具",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/npm/cli.js",
|
|
7
6
|
"bin": {
|
|
8
|
-
"42plugin": "
|
|
7
|
+
"42plugin": "bin/42plugin"
|
|
9
8
|
},
|
|
10
9
|
"files": [
|
|
11
|
-
"
|
|
12
|
-
"README.md"
|
|
10
|
+
"bin"
|
|
13
11
|
],
|
|
14
|
-
"scripts": {
|
|
15
|
-
"dev": "bun run --env-file=../../.env --env-file=.env src/index.ts",
|
|
16
|
-
"build": "bun run --env-file=../../.env --env-file=.env scripts/build.ts",
|
|
17
|
-
"build:npm": "bun run scripts/build-npm.ts",
|
|
18
|
-
"prepublishOnly": "bun run build:npm",
|
|
19
|
-
"test": "bun test",
|
|
20
|
-
"typecheck": "tsc --noEmit",
|
|
21
|
-
"lint": "cd ../.. && biome lint src/42plugin-cli/src",
|
|
22
|
-
"format": "cd ../.. && biome format src/42plugin-cli/src",
|
|
23
|
-
"check": "cd ../.. && biome check src/42plugin-cli/src",
|
|
24
|
-
"check:fix": "cd ../.. && biome check --write src/42plugin-cli/src"
|
|
25
|
-
},
|
|
12
|
+
"scripts": {},
|
|
26
13
|
"keywords": [
|
|
27
14
|
"claude",
|
|
28
15
|
"claude-code",
|
|
@@ -40,33 +27,15 @@
|
|
|
40
27
|
"bugs": {
|
|
41
28
|
"url": "https://github.com/42ailab/42plugin/issues"
|
|
42
29
|
},
|
|
43
|
-
"homepage": "https://
|
|
30
|
+
"homepage": "https://42plugin.com",
|
|
44
31
|
"engines": {
|
|
45
|
-
"
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@inquirer/prompts": "^8.1.0",
|
|
49
|
-
"better-auth": "^1.4.6",
|
|
50
|
-
"chalk": "^5.4.1",
|
|
51
|
-
"cli-progress": "^3.12.0",
|
|
52
|
-
"commander": "^13.0.0",
|
|
53
|
-
"gray-matter": "^4.0.3",
|
|
54
|
-
"nanoid": "^5.0.9",
|
|
55
|
-
"open": "^10.1.0",
|
|
56
|
-
"ora": "^8.1.1",
|
|
57
|
-
"pg": "^8.12.0",
|
|
58
|
-
"prompts": "^2.4.2",
|
|
59
|
-
"proper-lockfile": "^4.1.2",
|
|
60
|
-
"tar": "^7.4.3"
|
|
32
|
+
"node": ">=18.0.0"
|
|
61
33
|
},
|
|
62
|
-
"
|
|
63
|
-
"@
|
|
64
|
-
"@
|
|
65
|
-
"@
|
|
66
|
-
"@
|
|
67
|
-
"@
|
|
68
|
-
"@types/proper-lockfile": "^4.1.4",
|
|
69
|
-
"@types/tar": "^6.1.13",
|
|
70
|
-
"typescript": "^5.7.0"
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"@42ailab/42plugin-darwin-arm64": "0.2.16",
|
|
36
|
+
"@42ailab/42plugin-darwin-x64": "0.2.16",
|
|
37
|
+
"@42ailab/42plugin-linux-arm64": "0.2.16",
|
|
38
|
+
"@42ailab/42plugin-linux-x64": "0.2.16",
|
|
39
|
+
"@42ailab/42plugin-win32-x64": "0.2.16"
|
|
71
40
|
}
|
|
72
41
|
}
|