openclaw-weiyuan-init 1.0.0 → 1.0.2
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/package.json +9 -11
- package/lib/package.json +0 -49
- package/templates/.weiyuan.template +0 -10
- package/workspace-weiyuan/.weiyuan +0 -10
- package/workspace-weiyuan/weiyuan/README.md +0 -159
- package/workspace-weiyuan/weiyuan/examples/action-task-list.json +0 -6
- package/workspace-weiyuan/weiyuan/examples/risk-report.json +0 -8
- package/workspace-weiyuan/weiyuan/examples/text-what-can-i-do.json +0 -6
- package/workspace-weiyuan/weiyuan/manifest.json +0 -45
- package/workspace-weiyuan/weiyuan/schema.json +0 -104
- package/workspace-weiyuan/weiyuan/src/cli.ts +0 -453
- package/workspace-weiyuan/weiyuan/src/cliMain.ts +0 -7
- package/workspace-weiyuan/weiyuan/src/crypto.ts +0 -14
- package/workspace-weiyuan/weiyuan/src/http.ts +0 -45
- package/workspace-weiyuan/weiyuan/src/signing.ts +0 -38
- package/workspace-weiyuan/weiyuan/src/skillAdapter.ts +0 -173
- package/workspace-weiyuan/weiyuan/src/types.ts +0 -10
- package/workspace-weiyuan/weiyuan/src/weiyuanFile.ts +0 -25
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-weiyuan-init",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "OpenClaw Weiyuan Skill 一键初始化工具",
|
|
5
5
|
"main": "bin/cli.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"openclaw-weiyuan-init": "
|
|
7
|
+
"openclaw-weiyuan-init": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"start": "node bin/cli.js init"
|
|
11
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
"start": "node bin/cli.js init"
|
|
12
11
|
},
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"lib/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"axios": "^1.6.0",
|
|
18
19
|
"adm-zip": "^0.5.10",
|
|
@@ -21,10 +22,7 @@
|
|
|
21
22
|
"fs-extra": "^11.1.1",
|
|
22
23
|
"ora": "^5.4.1"
|
|
23
24
|
},
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=14.0.0"
|
|
26
|
-
},
|
|
27
25
|
"publishConfig": {
|
|
28
26
|
"access": "public"
|
|
29
27
|
}
|
|
30
|
-
}
|
|
28
|
+
}
|
package/lib/package.json
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@weiyuan/openclaw-weiyuan-init",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "OpenClaw Weiyuan Skill 一键初始化工具 - 自动下载、解压、配置身份文件",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"openclaw-weiyuan-init": "./bin/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"scripts": {
|
|
10
|
-
"start": "node bin/cli.js init",
|
|
11
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"openclaw",
|
|
15
|
-
"weiyuan",
|
|
16
|
-
"skill",
|
|
17
|
-
"init",
|
|
18
|
-
"cli",
|
|
19
|
-
"one-click",
|
|
20
|
-
"deploy"
|
|
21
|
-
],
|
|
22
|
-
"author": "Weiyuan",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/weiyuan/openclaw-weiyuan-init.git"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=14.0.0"
|
|
30
|
-
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"axios": "^1.6.0",
|
|
33
|
-
"adm-zip": "^0.5.10",
|
|
34
|
-
"chalk": "^4.1.2",
|
|
35
|
-
"commander": "^11.1.0",
|
|
36
|
-
"fs-extra": "^11.1.1",
|
|
37
|
-
"ora": "^5.4.1"
|
|
38
|
-
},
|
|
39
|
-
"publishConfig": {
|
|
40
|
-
"access": "public"
|
|
41
|
-
},
|
|
42
|
-
"files": [
|
|
43
|
-
"bin/",
|
|
44
|
-
"lib/",
|
|
45
|
-
"templates/",
|
|
46
|
-
"index.js",
|
|
47
|
-
"README.md"
|
|
48
|
-
]
|
|
49
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "1.0",
|
|
3
|
-
"device_id": "ZHAOTY",
|
|
4
|
-
"device_name": "ZHAOTY",
|
|
5
|
-
"created_at": "2026-03-28T14:27:13.667Z",
|
|
6
|
-
"server_url": "http://121.43.119.190:8787",
|
|
7
|
-
"workspace": "C:\\pythonProject1\\openclaw-weiyuan-init\\workspace-weiyuan",
|
|
8
|
-
"skill_path": "C:\\pythonProject1\\openclaw-weiyuan-init\\workspace-weiyuan/weiyuan",
|
|
9
|
-
"note": "请根据实际情况修改此文件"
|
|
10
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# 龙虾端 weiyuan CLI(MVP)
|
|
2
|
-
|
|
3
|
-
本目录提供一个最小可用的龙虾端 CLI,用于与微元云服务进行签名通信(Ed25519)。
|
|
4
|
-
|
|
5
|
-
## 快速开始
|
|
6
|
-
|
|
7
|
-
前置:微元云服务已启动(默认 `http://127.0.0.1:8787`)。
|
|
8
|
-
|
|
9
|
-
初始化身份(生成本地 `.weiyuan`):
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
npm run weiyuan -- init --server http://127.0.0.1:8787 --out .weiyuan
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
创建项目(DNA 原文在本地输入,云端只存 dnaHash):
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm run weiyuan -- create --name "Demo" --goal "MVP" --dna "稳当第一"
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
生成邀请口令(发起人执行):
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
npm run weiyuan -- invite --project <projectId> --role member
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
加入项目(成员执行):
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
npm run weiyuan -- join --project <projectId> --code <inviteCode>
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
成员列表:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
npm run weiyuan -- member list --project <projectId>
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
任务流转:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm run weiyuan -- task create --project <projectId> --title "工具清点" --desc "检查工具"
|
|
43
|
-
npm run weiyuan -- task list --project <projectId>
|
|
44
|
-
npm run weiyuan -- task take --project <projectId> --task <taskId>
|
|
45
|
-
npm run weiyuan -- task submit --project <projectId> --task <taskId> --hash <contentHash> --type text/markdown
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
状态体检与事件同步:
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm run weiyuan -- status --project <projectId>
|
|
52
|
-
npm run weiyuan -- sync --project <projectId>
|
|
53
|
-
npm run weiyuan -- sync --project <projectId> --follow --interval 2000
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
风险上报与查询:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
npm run weiyuan -- risk report --project <projectId> --severity warn --summary "3号库信号不稳"
|
|
60
|
-
npm run weiyuan -- risk list --project <projectId>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
进化胶囊:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm run weiyuan -- capsule publish --name "Skill_Pump_Repair_V1" --kind industry --file ./capsule.bin --version v1 --dnaTags 维修,稳当第一
|
|
67
|
-
npm run weiyuan -- capsule search --query 维修
|
|
68
|
-
npm run weiyuan -- capsule pull --id <capsuleId> --out ./downloaded-capsule.bin
|
|
69
|
-
npm run weiyuan -- capsule report --id <capsuleId> --success true
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## 身份文件
|
|
73
|
-
|
|
74
|
-
默认身份文件:当前目录的 `.weiyuan`(JSON)。
|
|
75
|
-
|
|
76
|
-
## 说明
|
|
77
|
-
|
|
78
|
-
- 这是 MVP 级 CLI:聚焦可跑通闭环与协议对齐;后续可升级为 OpenClaw skill 形态。
|
|
79
|
-
- 协议与鉴权细则见:[server/PROTOCOL.md](file:///c:/Users/10123/Documents/trae_projects/sky/server/PROTOCOL.md)
|
|
80
|
-
|
|
81
|
-
## OpenClaw Skill 适配入口(MVP)
|
|
82
|
-
|
|
83
|
-
适配器命令:`npm run weiyuan:skill`
|
|
84
|
-
|
|
85
|
-
输入为 JSON(stdin),支持两种模式:
|
|
86
|
-
|
|
87
|
-
1) `action` 模式(结构化)
|
|
88
|
-
2) `text` 模式(口语意图映射)
|
|
89
|
-
|
|
90
|
-
输入/输出 schema 见:`client/skill/schema.json`
|
|
91
|
-
|
|
92
|
-
示例(action):
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
'{"action":"task.list","identity":".weiyuan.dev2","projectId":"prj_xxx"}' | npm run weiyuan:skill
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
示例(text):
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
'{"text":"我能做什么","identity":".weiyuan.dev2","projectId":"prj_xxx"}' | npm run weiyuan:skill
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
输出格式:
|
|
105
|
-
|
|
106
|
-
- 成功:`{"ok": true, "data": ...}`
|
|
107
|
-
- 失败:`{"ok": false, "errorCode": "...", "message": "...", "rawError": "..."}`
|
|
108
|
-
|
|
109
|
-
## 生成 Skill 包
|
|
110
|
-
|
|
111
|
-
构建命令:
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
npm run weiyuan:skill:build
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
输出目录:`dist/skill-package`
|
|
118
|
-
|
|
119
|
-
该目录包含:
|
|
120
|
-
|
|
121
|
-
- `manifest.json`
|
|
122
|
-
- `schema.json`
|
|
123
|
-
- `examples/*.json`
|
|
124
|
-
- 适配器与 CLI 源码(用于 OpenClaw 侧加载或二次封装)
|
|
125
|
-
|
|
126
|
-
## 版本与发布
|
|
127
|
-
|
|
128
|
-
版本递增(patch/minor/major):
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npm run weiyuan:skill:version -- patch
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
发布前校验门(manifest/schema/examples/adapter 契约):
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
npm run weiyuan:skill:validate
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
一键发布(构建 + zip + 发布说明):
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
npm run weiyuan:skill:release
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
说明:`weiyuan:skill:release` 会先执行校验门,未通过则阻断发布。
|
|
147
|
-
|
|
148
|
-
发布产物:
|
|
149
|
-
|
|
150
|
-
- `dist/releases/weiyuan-openclaw-skill-v<version>.zip`
|
|
151
|
-
- `dist/releases/RELEASE_NOTES_<version>.md`
|
|
152
|
-
|
|
153
|
-
## CI 工作流
|
|
154
|
-
|
|
155
|
-
- `skill-ci`:push/PR 自动执行 `weiyuan:skill:validate` 与 `weiyuan:skill:build`
|
|
156
|
-
- `skill-release`:手动触发,支持 patch/minor/major 递增后自动执行:
|
|
157
|
-
- 版本递增并提交 `manifest.json`
|
|
158
|
-
- 创建并推送 git tag(`v<version>`)
|
|
159
|
-
- 生成 GitHub Release 并上传 zip/release notes
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "weiyuan-openclaw-skill",
|
|
3
|
-
"displayName": "微元协作 Skill",
|
|
4
|
-
"version": "0.1.1",
|
|
5
|
-
"description": "通过结构化 action 或自然语言 text 调用微元云服务。",
|
|
6
|
-
"entry": {
|
|
7
|
-
"type": "command",
|
|
8
|
-
"command": "npm run weiyuan:skill"
|
|
9
|
-
},
|
|
10
|
-
"ioSchema": "./schema.json",
|
|
11
|
-
"capabilities": {
|
|
12
|
-
"modes": [
|
|
13
|
-
"action",
|
|
14
|
-
"text"
|
|
15
|
-
],
|
|
16
|
-
"actions": [
|
|
17
|
-
"init",
|
|
18
|
-
"create",
|
|
19
|
-
"join",
|
|
20
|
-
"task.list",
|
|
21
|
-
"task.take",
|
|
22
|
-
"task.submit",
|
|
23
|
-
"status",
|
|
24
|
-
"risk.report",
|
|
25
|
-
"capsule.search"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
"examples": [
|
|
29
|
-
"./examples/action-task-list.json",
|
|
30
|
-
"./examples/text-what-can-i-do.json",
|
|
31
|
-
"./examples/risk-report.json"
|
|
32
|
-
],
|
|
33
|
-
"output": {
|
|
34
|
-
"success": {
|
|
35
|
-
"ok": true,
|
|
36
|
-
"data": {}
|
|
37
|
-
},
|
|
38
|
-
"error": {
|
|
39
|
-
"ok": false,
|
|
40
|
-
"errorCode": "UNKNOWN_ACTION",
|
|
41
|
-
"message": "",
|
|
42
|
-
"rawError": ""
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "weiyuan-skill-schema-v1",
|
|
4
|
-
"title": "Weiyuan OpenClaw Skill IO Schema",
|
|
5
|
-
"type": "object",
|
|
6
|
-
"properties": {
|
|
7
|
-
"input": {
|
|
8
|
-
"oneOf": [
|
|
9
|
-
{
|
|
10
|
-
"title": "ActionInput",
|
|
11
|
-
"type": "object",
|
|
12
|
-
"required": ["action"],
|
|
13
|
-
"properties": {
|
|
14
|
-
"action": {
|
|
15
|
-
"type": "string",
|
|
16
|
-
"enum": [
|
|
17
|
-
"init",
|
|
18
|
-
"create",
|
|
19
|
-
"join",
|
|
20
|
-
"task.list",
|
|
21
|
-
"task.take",
|
|
22
|
-
"task.submit",
|
|
23
|
-
"status",
|
|
24
|
-
"risk.report",
|
|
25
|
-
"capsule.search"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
"identity": { "type": "string" },
|
|
29
|
-
"projectId": { "type": "string" },
|
|
30
|
-
"taskId": { "type": "string" },
|
|
31
|
-
"code": { "type": "string" },
|
|
32
|
-
"role": { "type": "string", "enum": ["founder", "member", "guest"] },
|
|
33
|
-
"summary": { "type": "string" },
|
|
34
|
-
"severity": { "type": "string", "enum": ["info", "warn", "urgent"] },
|
|
35
|
-
"capsuleId": { "type": "string" },
|
|
36
|
-
"query": { "type": "string" },
|
|
37
|
-
"hash": { "type": "string" },
|
|
38
|
-
"title": { "type": "string" }
|
|
39
|
-
},
|
|
40
|
-
"additionalProperties": false
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
"title": "TextInput",
|
|
44
|
-
"type": "object",
|
|
45
|
-
"required": ["text"],
|
|
46
|
-
"properties": {
|
|
47
|
-
"text": { "type": "string", "minLength": 1 },
|
|
48
|
-
"identity": { "type": "string" },
|
|
49
|
-
"projectId": { "type": "string" },
|
|
50
|
-
"taskId": { "type": "string" },
|
|
51
|
-
"summary": { "type": "string" },
|
|
52
|
-
"severity": { "type": "string", "enum": ["info", "warn", "urgent"] },
|
|
53
|
-
"hash": { "type": "string" },
|
|
54
|
-
"query": { "type": "string" }
|
|
55
|
-
},
|
|
56
|
-
"additionalProperties": false
|
|
57
|
-
}
|
|
58
|
-
]
|
|
59
|
-
},
|
|
60
|
-
"output": {
|
|
61
|
-
"oneOf": [
|
|
62
|
-
{
|
|
63
|
-
"title": "SuccessOutput",
|
|
64
|
-
"type": "object",
|
|
65
|
-
"required": ["ok"],
|
|
66
|
-
"properties": {
|
|
67
|
-
"ok": { "const": true },
|
|
68
|
-
"data": {}
|
|
69
|
-
},
|
|
70
|
-
"additionalProperties": true
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"title": "ErrorOutput",
|
|
74
|
-
"type": "object",
|
|
75
|
-
"required": ["ok", "errorCode", "message"],
|
|
76
|
-
"properties": {
|
|
77
|
-
"ok": { "const": false },
|
|
78
|
-
"errorCode": {
|
|
79
|
-
"type": "string",
|
|
80
|
-
"enum": [
|
|
81
|
-
"MISSING_INPUT",
|
|
82
|
-
"INVALID_JSON",
|
|
83
|
-
"UNKNOWN_ACTION",
|
|
84
|
-
"CANNOT_MAP_TEXT",
|
|
85
|
-
"MISSING_PROJECT",
|
|
86
|
-
"MISSING_TASK",
|
|
87
|
-
"MISSING_CODE",
|
|
88
|
-
"MISSING_SUMMARY",
|
|
89
|
-
"CLI_ERROR",
|
|
90
|
-
"UNKNOWN"
|
|
91
|
-
]
|
|
92
|
-
},
|
|
93
|
-
"message": { "type": "string" },
|
|
94
|
-
"rawError": { "type": "string" }
|
|
95
|
-
},
|
|
96
|
-
"additionalProperties": true
|
|
97
|
-
}
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
"required": ["input", "output"],
|
|
102
|
-
"additionalProperties": false
|
|
103
|
-
}
|
|
104
|
-
|
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises"
|
|
2
|
-
import { DEFAULT_IDENTITY_PATH, readIdentity, writeIdentity } from "./weiyuanFile"
|
|
3
|
-
import { sha256Hex } from "./crypto"
|
|
4
|
-
import { signedRequest } from "./http"
|
|
5
|
-
import { bodyHashBase64, canonicalRequest, newKeyPair, signEd25519Base64 } from "./signing"
|
|
6
|
-
|
|
7
|
-
type Argv = { _: string[]; flags: Record<string, string | boolean> }
|
|
8
|
-
|
|
9
|
-
function parseArgv(argv: string[]): Argv {
|
|
10
|
-
const out: Argv = { _: [], flags: {} }
|
|
11
|
-
for (let i = 0; i < argv.length; i++) {
|
|
12
|
-
const a = argv[i]!
|
|
13
|
-
if (a.startsWith("--")) {
|
|
14
|
-
const key = a.slice(2)
|
|
15
|
-
const next = argv[i + 1]
|
|
16
|
-
if (next && !next.startsWith("-")) {
|
|
17
|
-
out.flags[key] = next
|
|
18
|
-
i++
|
|
19
|
-
} else {
|
|
20
|
-
out.flags[key] = true
|
|
21
|
-
}
|
|
22
|
-
} else {
|
|
23
|
-
out._.push(a)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return out
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function mustString(flags: Record<string, string | boolean>, key: string): string {
|
|
30
|
-
const v = flags[key]
|
|
31
|
-
if (typeof v !== "string" || !v) throw new Error(`missing_${key}`)
|
|
32
|
-
return v
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function optString(flags: Record<string, string | boolean>, key: string): string | undefined {
|
|
36
|
-
const v = flags[key]
|
|
37
|
-
return typeof v === "string" ? v : undefined
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function print(obj: unknown): void {
|
|
41
|
-
process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function sleep(ms: number): Promise<void> {
|
|
45
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function cmdInit(args: Argv): Promise<void> {
|
|
49
|
-
const serverBaseUrl = optString(args.flags, "server") ?? "http://127.0.0.1:8787"
|
|
50
|
-
const filePath = optString(args.flags, "out") ?? DEFAULT_IDENTITY_PATH
|
|
51
|
-
const kp = newKeyPair()
|
|
52
|
-
const identityHash = sha256Hex(JSON.stringify({ lobsterId: kp.lobsterId, publicKeyBase64: kp.publicKeyBase64 }))
|
|
53
|
-
|
|
54
|
-
const body = { lobsterId: kp.lobsterId, publicKeyBase64: kp.publicKeyBase64, identityHash }
|
|
55
|
-
const timestampMs = String(Date.now())
|
|
56
|
-
const nonce = `nonce_${Math.random().toString(16).slice(2)}`
|
|
57
|
-
const bodySha = bodyHashBase64(body)
|
|
58
|
-
const canonical = canonicalRequest({
|
|
59
|
-
method: "POST",
|
|
60
|
-
pathWithQuery: "/v1/init",
|
|
61
|
-
timestampMs,
|
|
62
|
-
nonce,
|
|
63
|
-
bodySha256Base64: bodySha,
|
|
64
|
-
})
|
|
65
|
-
const signature = signEd25519Base64(canonical, kp.secretKeyBase64)
|
|
66
|
-
|
|
67
|
-
const res = await fetch(`${serverBaseUrl}/v1/init`, {
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: {
|
|
70
|
-
"Content-Type": "application/json",
|
|
71
|
-
"X-Weiyuan-Lobster-Id": kp.lobsterId,
|
|
72
|
-
"X-Weiyuan-Timestamp": timestampMs,
|
|
73
|
-
"X-Weiyuan-Nonce": nonce,
|
|
74
|
-
"X-Weiyuan-Signature": signature,
|
|
75
|
-
},
|
|
76
|
-
body: JSON.stringify(body),
|
|
77
|
-
})
|
|
78
|
-
const json = await res.json().catch(() => ({}))
|
|
79
|
-
if (res.status !== 200) throw new Error(JSON.stringify(json))
|
|
80
|
-
|
|
81
|
-
await writeIdentity(filePath, {
|
|
82
|
-
version: 1,
|
|
83
|
-
serverBaseUrl,
|
|
84
|
-
lobsterId: kp.lobsterId,
|
|
85
|
-
publicKeyBase64: kp.publicKeyBase64,
|
|
86
|
-
secretKeyBase64: kp.secretKeyBase64,
|
|
87
|
-
identityHash,
|
|
88
|
-
projectCursors: {},
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
print({ ok: true, lobsterId: kp.lobsterId, file: filePath, welcome: json.data?.welcome })
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function cmdCreateProject(args: Argv): Promise<void> {
|
|
95
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
96
|
-
const identity = await readIdentity(filePath)
|
|
97
|
-
const name = mustString(args.flags, "name")
|
|
98
|
-
const goal = optString(args.flags, "goal") ?? ""
|
|
99
|
-
const dna = mustString(args.flags, "dna")
|
|
100
|
-
const dnaHash = sha256Hex(dna)
|
|
101
|
-
|
|
102
|
-
const { status, json } = await signedRequest({
|
|
103
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
104
|
-
method: "POST",
|
|
105
|
-
pathWithQuery: "/v1/projects",
|
|
106
|
-
lobsterId: identity.lobsterId,
|
|
107
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
108
|
-
body: { name, goal, dnaHash },
|
|
109
|
-
})
|
|
110
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
111
|
-
print({ ok: true, projectId: json.data.projectId })
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function cmdInvite(args: Argv): Promise<void> {
|
|
115
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
116
|
-
const identity = await readIdentity(filePath)
|
|
117
|
-
const projectId = mustString(args.flags, "project")
|
|
118
|
-
const role = mustString(args.flags, "role")
|
|
119
|
-
const { status, json } = await signedRequest({
|
|
120
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
121
|
-
method: "POST",
|
|
122
|
-
pathWithQuery: "/v1/invites",
|
|
123
|
-
lobsterId: identity.lobsterId,
|
|
124
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
125
|
-
body: { projectId, role },
|
|
126
|
-
})
|
|
127
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
128
|
-
print({ ok: true, code: json.data.code })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function cmdJoin(args: Argv): Promise<void> {
|
|
132
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
133
|
-
const identity = await readIdentity(filePath)
|
|
134
|
-
const projectId = mustString(args.flags, "project")
|
|
135
|
-
const code = mustString(args.flags, "code")
|
|
136
|
-
const { status, json } = await signedRequest({
|
|
137
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
138
|
-
method: "POST",
|
|
139
|
-
pathWithQuery: `/v1/projects/${projectId}/join`,
|
|
140
|
-
lobsterId: identity.lobsterId,
|
|
141
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
142
|
-
body: { code },
|
|
143
|
-
})
|
|
144
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
145
|
-
print({ ok: true, welcome: json.data?.welcome })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async function cmdTaskList(args: Argv): Promise<void> {
|
|
149
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
150
|
-
const identity = await readIdentity(filePath)
|
|
151
|
-
const projectId = mustString(args.flags, "project")
|
|
152
|
-
const { status, json } = await signedRequest({
|
|
153
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
154
|
-
method: "GET",
|
|
155
|
-
pathWithQuery: `/v1/projects/${projectId}/tasks`,
|
|
156
|
-
lobsterId: identity.lobsterId,
|
|
157
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
158
|
-
})
|
|
159
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
160
|
-
print(json.data)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async function cmdTaskCreate(args: Argv): Promise<void> {
|
|
164
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
165
|
-
const identity = await readIdentity(filePath)
|
|
166
|
-
const projectId = mustString(args.flags, "project")
|
|
167
|
-
const title = mustString(args.flags, "title")
|
|
168
|
-
const desc = optString(args.flags, "desc")
|
|
169
|
-
const { status, json } = await signedRequest({
|
|
170
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
171
|
-
method: "POST",
|
|
172
|
-
pathWithQuery: `/v1/projects/${projectId}/tasks`,
|
|
173
|
-
lobsterId: identity.lobsterId,
|
|
174
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
175
|
-
body: { title, desc },
|
|
176
|
-
})
|
|
177
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
178
|
-
print({ ok: true, taskId: json.data.taskId })
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async function cmdTaskTake(args: Argv): Promise<void> {
|
|
182
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
183
|
-
const identity = await readIdentity(filePath)
|
|
184
|
-
const projectId = mustString(args.flags, "project")
|
|
185
|
-
const taskId = mustString(args.flags, "task")
|
|
186
|
-
const { status, json } = await signedRequest({
|
|
187
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
188
|
-
method: "POST",
|
|
189
|
-
pathWithQuery: `/v1/projects/${projectId}/tasks/${taskId}/take`,
|
|
190
|
-
lobsterId: identity.lobsterId,
|
|
191
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
192
|
-
body: {},
|
|
193
|
-
})
|
|
194
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
195
|
-
print({ ok: true })
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function cmdTaskAbandon(args: Argv): Promise<void> {
|
|
199
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
200
|
-
const identity = await readIdentity(filePath)
|
|
201
|
-
const projectId = mustString(args.flags, "project")
|
|
202
|
-
const taskId = mustString(args.flags, "task")
|
|
203
|
-
const { status, json } = await signedRequest({
|
|
204
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
205
|
-
method: "POST",
|
|
206
|
-
pathWithQuery: `/v1/projects/${projectId}/tasks/${taskId}/abandon`,
|
|
207
|
-
lobsterId: identity.lobsterId,
|
|
208
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
209
|
-
body: {},
|
|
210
|
-
})
|
|
211
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
212
|
-
print({ ok: true })
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function cmdTaskSubmit(args: Argv): Promise<void> {
|
|
216
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
217
|
-
const identity = await readIdentity(filePath)
|
|
218
|
-
const projectId = mustString(args.flags, "project")
|
|
219
|
-
const taskId = mustString(args.flags, "task")
|
|
220
|
-
const contentHash = mustString(args.flags, "hash")
|
|
221
|
-
const contentType = optString(args.flags, "type") ?? "text/markdown"
|
|
222
|
-
const { status, json } = await signedRequest({
|
|
223
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
224
|
-
method: "POST",
|
|
225
|
-
pathWithQuery: `/v1/projects/${projectId}/tasks/${taskId}/submit`,
|
|
226
|
-
lobsterId: identity.lobsterId,
|
|
227
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
228
|
-
body: { artifacts: [{ contentHash, contentType }] },
|
|
229
|
-
})
|
|
230
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
231
|
-
print({ ok: true })
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function cmdStatus(args: Argv): Promise<void> {
|
|
235
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
236
|
-
const identity = await readIdentity(filePath)
|
|
237
|
-
const projectId = mustString(args.flags, "project")
|
|
238
|
-
const { status, json } = await signedRequest({
|
|
239
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
240
|
-
method: "GET",
|
|
241
|
-
pathWithQuery: `/v1/projects/${projectId}/status`,
|
|
242
|
-
lobsterId: identity.lobsterId,
|
|
243
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
244
|
-
})
|
|
245
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
246
|
-
print(json.data)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
async function cmdMemberList(args: Argv): Promise<void> {
|
|
250
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
251
|
-
const identity = await readIdentity(filePath)
|
|
252
|
-
const projectId = mustString(args.flags, "project")
|
|
253
|
-
const { status, json } = await signedRequest({
|
|
254
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
255
|
-
method: "GET",
|
|
256
|
-
pathWithQuery: `/v1/projects/${projectId}/members`,
|
|
257
|
-
lobsterId: identity.lobsterId,
|
|
258
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
259
|
-
})
|
|
260
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
261
|
-
print(json.data)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async function cmdSync(args: Argv): Promise<void> {
|
|
265
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
266
|
-
const identity = await readIdentity(filePath)
|
|
267
|
-
const projectId = mustString(args.flags, "project")
|
|
268
|
-
const follow = Boolean(args.flags.follow)
|
|
269
|
-
const intervalMs = Number(optString(args.flags, "interval") ?? 2000)
|
|
270
|
-
|
|
271
|
-
const runOnce = async (): Promise<void> => {
|
|
272
|
-
const cursor = identity.projectCursors[projectId] ?? 0
|
|
273
|
-
const { status, json } = await signedRequest({
|
|
274
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
275
|
-
method: "GET",
|
|
276
|
-
pathWithQuery: `/v1/projects/${projectId}/events?cursor=${cursor}`,
|
|
277
|
-
lobsterId: identity.lobsterId,
|
|
278
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
279
|
-
})
|
|
280
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
281
|
-
identity.projectCursors[projectId] = Number(json.data.cursor ?? cursor)
|
|
282
|
-
await writeIdentity(filePath, identity)
|
|
283
|
-
print({ ok: true, cursor: identity.projectCursors[projectId], events: json.data.events })
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
await runOnce()
|
|
287
|
-
if (!follow) return
|
|
288
|
-
while (true) {
|
|
289
|
-
await sleep(intervalMs)
|
|
290
|
-
await runOnce()
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
async function cmdRiskReport(args: Argv): Promise<void> {
|
|
295
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
296
|
-
const identity = await readIdentity(filePath)
|
|
297
|
-
const projectId = mustString(args.flags, "project")
|
|
298
|
-
const severity = mustString(args.flags, "severity")
|
|
299
|
-
const summary = mustString(args.flags, "summary")
|
|
300
|
-
const taskId = optString(args.flags, "task")
|
|
301
|
-
const { status, json } = await signedRequest({
|
|
302
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
303
|
-
method: "POST",
|
|
304
|
-
pathWithQuery: `/v1/projects/${projectId}/risks`,
|
|
305
|
-
lobsterId: identity.lobsterId,
|
|
306
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
307
|
-
body: { severity, summary, taskId },
|
|
308
|
-
})
|
|
309
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
310
|
-
print({ ok: true, riskId: json.data.riskId })
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async function cmdRiskList(args: Argv): Promise<void> {
|
|
314
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
315
|
-
const identity = await readIdentity(filePath)
|
|
316
|
-
const projectId = mustString(args.flags, "project")
|
|
317
|
-
const { status, json } = await signedRequest({
|
|
318
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
319
|
-
method: "GET",
|
|
320
|
-
pathWithQuery: `/v1/projects/${projectId}/risks`,
|
|
321
|
-
lobsterId: identity.lobsterId,
|
|
322
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
323
|
-
})
|
|
324
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
325
|
-
print(json.data)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async function cmdCapsulePublish(args: Argv): Promise<void> {
|
|
329
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
330
|
-
const identity = await readIdentity(filePath)
|
|
331
|
-
const name = mustString(args.flags, "name")
|
|
332
|
-
const kind = mustString(args.flags, "kind")
|
|
333
|
-
const version = optString(args.flags, "version") ?? "v1"
|
|
334
|
-
const packagePath = mustString(args.flags, "file")
|
|
335
|
-
const bytes = await fs.readFile(packagePath)
|
|
336
|
-
const packageHash = sha256Hex(bytes)
|
|
337
|
-
const manifest = {
|
|
338
|
-
name,
|
|
339
|
-
kind,
|
|
340
|
-
version,
|
|
341
|
-
dnaTags: optString(args.flags, "dnaTags")?.split(",").map((x) => x.trim()).filter(Boolean) ?? [],
|
|
342
|
-
projectTags: optString(args.flags, "projectTags")?.split(",").map((x) => x.trim()).filter(Boolean) ?? [],
|
|
343
|
-
}
|
|
344
|
-
const manifestHash = sha256Hex(JSON.stringify(manifest))
|
|
345
|
-
const packageBytesBase64 = Buffer.from(bytes).toString("base64")
|
|
346
|
-
|
|
347
|
-
const { status, json } = await signedRequest({
|
|
348
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
349
|
-
method: "POST",
|
|
350
|
-
pathWithQuery: "/v1/capsules",
|
|
351
|
-
lobsterId: identity.lobsterId,
|
|
352
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
353
|
-
body: { ...manifest, manifestHash, packageHash, packageBytesBase64 },
|
|
354
|
-
})
|
|
355
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
356
|
-
print({ ok: true, capsuleId: json.data.capsuleId, manifestHash, packageHash })
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async function cmdCapsuleSearch(args: Argv): Promise<void> {
|
|
360
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
361
|
-
const identity = await readIdentity(filePath)
|
|
362
|
-
const query = optString(args.flags, "query") ?? ""
|
|
363
|
-
const { status, json } = await signedRequest({
|
|
364
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
365
|
-
method: "GET",
|
|
366
|
-
pathWithQuery: `/v1/capsules?query=${encodeURIComponent(query)}`,
|
|
367
|
-
lobsterId: identity.lobsterId,
|
|
368
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
369
|
-
})
|
|
370
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
371
|
-
print(json.data)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async function cmdCapsulePull(args: Argv): Promise<void> {
|
|
375
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
376
|
-
const identity = await readIdentity(filePath)
|
|
377
|
-
const capsuleId = mustString(args.flags, "id")
|
|
378
|
-
const out = optString(args.flags, "out")
|
|
379
|
-
const { status, json } = await signedRequest({
|
|
380
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
381
|
-
method: "POST",
|
|
382
|
-
pathWithQuery: `/v1/capsules/${capsuleId}/pull`,
|
|
383
|
-
lobsterId: identity.lobsterId,
|
|
384
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
385
|
-
body: {},
|
|
386
|
-
})
|
|
387
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
388
|
-
const capsule = json.data.capsule
|
|
389
|
-
if (out && capsule?.packageBytesBase64) {
|
|
390
|
-
await fs.writeFile(out, Buffer.from(capsule.packageBytesBase64, "base64"))
|
|
391
|
-
}
|
|
392
|
-
print({ ok: true, capsule })
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async function cmdCapsuleReport(args: Argv): Promise<void> {
|
|
396
|
-
const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
|
|
397
|
-
const identity = await readIdentity(filePath)
|
|
398
|
-
const capsuleId = mustString(args.flags, "id")
|
|
399
|
-
const success = (optString(args.flags, "success") ?? "true").toLowerCase() !== "false"
|
|
400
|
-
const { status, json } = await signedRequest({
|
|
401
|
-
serverBaseUrl: identity.serverBaseUrl,
|
|
402
|
-
method: "POST",
|
|
403
|
-
pathWithQuery: `/v1/capsules/${capsuleId}/report`,
|
|
404
|
-
lobsterId: identity.lobsterId,
|
|
405
|
-
secretKeyBase64: identity.secretKeyBase64,
|
|
406
|
-
body: { success },
|
|
407
|
-
})
|
|
408
|
-
if (status !== 200) throw new Error(JSON.stringify(json))
|
|
409
|
-
print({ ok: true, capsuleId })
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
export async function runCli(argv: string[]): Promise<void> {
|
|
413
|
-
const args = parseArgv(argv)
|
|
414
|
-
const [cmd, sub] = args._.length ? [args._[0], args._[1]] : [undefined, undefined]
|
|
415
|
-
|
|
416
|
-
if (!cmd) throw new Error("missing_command")
|
|
417
|
-
|
|
418
|
-
if (cmd === "init") return await cmdInit(args)
|
|
419
|
-
if (cmd === "create") return await cmdCreateProject(args)
|
|
420
|
-
if (cmd === "invite") return await cmdInvite(args)
|
|
421
|
-
if (cmd === "join") return await cmdJoin(args)
|
|
422
|
-
if (cmd === "status") return await cmdStatus(args)
|
|
423
|
-
if (cmd === "sync") return await cmdSync(args)
|
|
424
|
-
if (cmd === "member") {
|
|
425
|
-
if (sub === "list") return await cmdMemberList(args)
|
|
426
|
-
throw new Error("unknown_member_subcommand")
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (cmd === "risk") {
|
|
430
|
-
if (sub === "report") return await cmdRiskReport(args)
|
|
431
|
-
if (sub === "list") return await cmdRiskList(args)
|
|
432
|
-
throw new Error("unknown_risk_subcommand")
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (cmd === "capsule") {
|
|
436
|
-
if (sub === "publish") return await cmdCapsulePublish(args)
|
|
437
|
-
if (sub === "search") return await cmdCapsuleSearch(args)
|
|
438
|
-
if (sub === "pull") return await cmdCapsulePull(args)
|
|
439
|
-
if (sub === "report") return await cmdCapsuleReport(args)
|
|
440
|
-
throw new Error("unknown_capsule_subcommand")
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (cmd === "task") {
|
|
444
|
-
if (sub === "list") return await cmdTaskList(args)
|
|
445
|
-
if (sub === "create") return await cmdTaskCreate(args)
|
|
446
|
-
if (sub === "take") return await cmdTaskTake(args)
|
|
447
|
-
if (sub === "abandon") return await cmdTaskAbandon(args)
|
|
448
|
-
if (sub === "submit") return await cmdTaskSubmit(args)
|
|
449
|
-
throw new Error("unknown_task_subcommand")
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
throw new Error("unknown_command")
|
|
453
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import * as crypto from "node:crypto"
|
|
2
|
-
|
|
3
|
-
export function sha256Base64(input: string | Uint8Array): string {
|
|
4
|
-
const h = crypto.createHash("sha256")
|
|
5
|
-
h.update(input)
|
|
6
|
-
return h.digest("base64")
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function sha256Hex(input: string | Uint8Array): string {
|
|
10
|
-
const h = crypto.createHash("sha256")
|
|
11
|
-
h.update(input)
|
|
12
|
-
return h.digest("hex")
|
|
13
|
-
}
|
|
14
|
-
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { bodyHashBase64, canonicalRequest, signEd25519Base64 } from "./signing"
|
|
2
|
-
|
|
3
|
-
export async function signedRequest(opts: {
|
|
4
|
-
serverBaseUrl: string
|
|
5
|
-
method: "GET" | "POST"
|
|
6
|
-
pathWithQuery: string
|
|
7
|
-
lobsterId: string
|
|
8
|
-
secretKeyBase64: string
|
|
9
|
-
body?: unknown
|
|
10
|
-
}): Promise<any> {
|
|
11
|
-
const timestampMs = String(Date.now())
|
|
12
|
-
const nonce = `nonce_${Math.random().toString(16).slice(2)}`
|
|
13
|
-
const bodySha = bodyHashBase64(opts.body)
|
|
14
|
-
const canonical = canonicalRequest({
|
|
15
|
-
method: opts.method,
|
|
16
|
-
pathWithQuery: opts.pathWithQuery,
|
|
17
|
-
timestampMs,
|
|
18
|
-
nonce,
|
|
19
|
-
bodySha256Base64: bodySha,
|
|
20
|
-
})
|
|
21
|
-
const signature = signEd25519Base64(canonical, opts.secretKeyBase64)
|
|
22
|
-
|
|
23
|
-
const headers: Record<string, string> = {
|
|
24
|
-
"X-Weiyuan-Lobster-Id": opts.lobsterId,
|
|
25
|
-
"X-Weiyuan-Timestamp": timestampMs,
|
|
26
|
-
"X-Weiyuan-Nonce": nonce,
|
|
27
|
-
"X-Weiyuan-Signature": signature,
|
|
28
|
-
}
|
|
29
|
-
if (opts.body) headers["Content-Type"] = "application/json"
|
|
30
|
-
|
|
31
|
-
const res = await fetch(`${opts.serverBaseUrl}${opts.pathWithQuery}`, {
|
|
32
|
-
method: opts.method,
|
|
33
|
-
headers,
|
|
34
|
-
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
35
|
-
})
|
|
36
|
-
const text = await res.text()
|
|
37
|
-
let json: any
|
|
38
|
-
try {
|
|
39
|
-
json = JSON.parse(text)
|
|
40
|
-
} catch {
|
|
41
|
-
json = { raw: text }
|
|
42
|
-
}
|
|
43
|
-
return { status: res.status, json }
|
|
44
|
-
}
|
|
45
|
-
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import naclImport from "tweetnacl"
|
|
2
|
-
import { sha256Base64, sha256Hex } from "./crypto"
|
|
3
|
-
|
|
4
|
-
const nacl = (naclImport as unknown as any).default ?? naclImport
|
|
5
|
-
|
|
6
|
-
function b64(u8: Uint8Array): string {
|
|
7
|
-
return Buffer.from(u8).toString("base64")
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function canonicalRequest(opts: {
|
|
11
|
-
method: string
|
|
12
|
-
pathWithQuery: string
|
|
13
|
-
timestampMs: string
|
|
14
|
-
nonce: string
|
|
15
|
-
bodySha256Base64: string
|
|
16
|
-
}): string {
|
|
17
|
-
return [opts.method.toUpperCase(), opts.pathWithQuery, opts.timestampMs, opts.nonce, opts.bodySha256Base64].join("\n")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function signEd25519Base64(messageUtf8: string, secretKeyBase64: string): string {
|
|
21
|
-
const sk = Buffer.from(secretKeyBase64, "base64")
|
|
22
|
-
const sig = nacl.sign.detached(new Uint8Array(Buffer.from(messageUtf8, "utf8")), new Uint8Array(sk))
|
|
23
|
-
return b64(sig)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function newKeyPair(): { publicKeyBase64: string; secretKeyBase64: string; lobsterId: string } {
|
|
27
|
-
const kp = nacl.sign.keyPair()
|
|
28
|
-
const publicKeyBase64 = b64(kp.publicKey)
|
|
29
|
-
const secretKeyBase64 = b64(kp.secretKey)
|
|
30
|
-
const lobsterId = `lob_${sha256Hex(kp.publicKey).slice(0, 12)}`
|
|
31
|
-
return { publicKeyBase64, secretKeyBase64, lobsterId }
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function bodyHashBase64(body: unknown | undefined): string {
|
|
35
|
-
const bodyStr = body ? JSON.stringify(body) : ""
|
|
36
|
-
return sha256Base64(bodyStr)
|
|
37
|
-
}
|
|
38
|
-
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { runCli } from "./cli"
|
|
2
|
-
|
|
3
|
-
type SkillInput = {
|
|
4
|
-
action?: string
|
|
5
|
-
text?: string
|
|
6
|
-
identity?: string
|
|
7
|
-
projectId?: string
|
|
8
|
-
taskId?: string
|
|
9
|
-
code?: string
|
|
10
|
-
role?: string
|
|
11
|
-
summary?: string
|
|
12
|
-
severity?: string
|
|
13
|
-
capsuleId?: string
|
|
14
|
-
query?: string
|
|
15
|
-
hash?: string
|
|
16
|
-
title?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type SkillSuccess = {
|
|
20
|
-
ok: true
|
|
21
|
-
data: unknown
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
type SkillError = {
|
|
25
|
-
ok: false
|
|
26
|
-
errorCode: string
|
|
27
|
-
message: string
|
|
28
|
-
rawError?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function readAllStdin(): Promise<string> {
|
|
32
|
-
return new Promise((resolve) => {
|
|
33
|
-
const chunks: Buffer[] = []
|
|
34
|
-
process.stdin.on("data", (c) => chunks.push(Buffer.from(c)))
|
|
35
|
-
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function fromText(input: SkillInput): string[] {
|
|
40
|
-
const text = (input.text ?? "").trim()
|
|
41
|
-
const lower = text.toLowerCase()
|
|
42
|
-
const identity = input.identity ?? ".weiyuan"
|
|
43
|
-
|
|
44
|
-
if (!text) throw new Error("missing_text")
|
|
45
|
-
|
|
46
|
-
if (lower.includes("我能做什么") || lower.includes("任务列表") || lower.includes("还有啥活")) {
|
|
47
|
-
if (!input.projectId) throw new Error("missing_projectId")
|
|
48
|
-
return ["task", "list", "--identity", identity, "--project", input.projectId]
|
|
49
|
-
}
|
|
50
|
-
if (lower.includes("我领") || lower.includes("我来")) {
|
|
51
|
-
if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
|
|
52
|
-
return ["task", "take", "--identity", identity, "--project", input.projectId, "--task", input.taskId]
|
|
53
|
-
}
|
|
54
|
-
if (lower.includes("搞定") || lower.includes("完事") || lower.includes("看图") || lower.includes("已修好")) {
|
|
55
|
-
if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
|
|
56
|
-
const hash = input.hash ?? "hash-auto"
|
|
57
|
-
return ["task", "submit", "--identity", identity, "--project", input.projectId, "--task", input.taskId, "--hash", hash]
|
|
58
|
-
}
|
|
59
|
-
if (lower.includes("卡住") || lower.includes("帮忙")) {
|
|
60
|
-
if (!input.projectId) throw new Error("missing_projectId")
|
|
61
|
-
const summary = input.summary ?? text
|
|
62
|
-
return ["risk", "report", "--identity", identity, "--project", input.projectId, "--severity", input.severity ?? "warn", "--summary", summary]
|
|
63
|
-
}
|
|
64
|
-
if (lower.includes("进度") || lower.includes("现在怎么样")) {
|
|
65
|
-
if (!input.projectId) throw new Error("missing_projectId")
|
|
66
|
-
return ["status", "--identity", identity, "--project", input.projectId]
|
|
67
|
-
}
|
|
68
|
-
if (lower.includes("老规矩") || lower.includes("上次那套")) {
|
|
69
|
-
return ["capsule", "search", "--identity", identity, "--query", input.query ?? ""]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (input.projectId) {
|
|
73
|
-
return ["task", "list", "--identity", identity, "--project", input.projectId]
|
|
74
|
-
}
|
|
75
|
-
throw new Error("cannot_map_text_without_project_context")
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function fromAction(input: SkillInput): string[] {
|
|
79
|
-
const identity = input.identity ?? ".weiyuan"
|
|
80
|
-
switch (input.action) {
|
|
81
|
-
case "init":
|
|
82
|
-
return ["init", "--server", "http://127.0.0.1:8787", "--out", identity]
|
|
83
|
-
case "create":
|
|
84
|
-
if (!input.title) throw new Error("missing_title")
|
|
85
|
-
return ["create", "--identity", identity, "--name", input.title, "--goal", "MVP", "--dna", "稳当第一"]
|
|
86
|
-
case "join":
|
|
87
|
-
if (!input.projectId || !input.code) throw new Error("missing_projectId_or_code")
|
|
88
|
-
return ["join", "--identity", identity, "--project", input.projectId, "--code", input.code]
|
|
89
|
-
case "task.take":
|
|
90
|
-
if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
|
|
91
|
-
return ["task", "take", "--identity", identity, "--project", input.projectId, "--task", input.taskId]
|
|
92
|
-
case "task.submit":
|
|
93
|
-
if (!input.projectId || !input.taskId) throw new Error("missing_projectId_or_taskId")
|
|
94
|
-
return ["task", "submit", "--identity", identity, "--project", input.projectId, "--task", input.taskId, "--hash", input.hash ?? "hash-auto"]
|
|
95
|
-
case "task.list":
|
|
96
|
-
if (!input.projectId) throw new Error("missing_projectId")
|
|
97
|
-
return ["task", "list", "--identity", identity, "--project", input.projectId]
|
|
98
|
-
case "status":
|
|
99
|
-
if (!input.projectId) throw new Error("missing_projectId")
|
|
100
|
-
return ["status", "--identity", identity, "--project", input.projectId]
|
|
101
|
-
case "risk.report":
|
|
102
|
-
if (!input.projectId || !input.summary) throw new Error("missing_projectId_or_summary")
|
|
103
|
-
return ["risk", "report", "--identity", identity, "--project", input.projectId, "--severity", input.severity ?? "warn", "--summary", input.summary]
|
|
104
|
-
case "capsule.search":
|
|
105
|
-
return ["capsule", "search", "--identity", identity, "--query", input.query ?? ""]
|
|
106
|
-
default:
|
|
107
|
-
throw new Error("unknown_action")
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async function main(): Promise<void> {
|
|
112
|
-
const raw = await readAllStdin()
|
|
113
|
-
if (!raw.trim()) throw new Error("missing_input")
|
|
114
|
-
const input = JSON.parse(raw) as SkillInput
|
|
115
|
-
const argv = input.action ? fromAction(input) : fromText(input)
|
|
116
|
-
|
|
117
|
-
let captured = ""
|
|
118
|
-
const originalWrite = process.stdout.write.bind(process.stdout)
|
|
119
|
-
;(process.stdout as any).write = (chunk: any, ...rest: any[]) => {
|
|
120
|
-
captured += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk)
|
|
121
|
-
return true
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
await runCli(argv)
|
|
126
|
-
} finally {
|
|
127
|
-
;(process.stdout as any).write = originalWrite
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
let data: unknown = captured.trim()
|
|
131
|
-
try {
|
|
132
|
-
data = JSON.parse(captured)
|
|
133
|
-
} catch {}
|
|
134
|
-
const out: SkillSuccess = { ok: true, data }
|
|
135
|
-
process.stdout.write(`${JSON.stringify(out, null, 2)}\n`)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function mapError(err: unknown): SkillError {
|
|
139
|
-
const rawError = String(err instanceof Error ? err.message : err)
|
|
140
|
-
if (rawError.includes("missing_input")) {
|
|
141
|
-
return { ok: false, errorCode: "MISSING_INPUT", message: "Missing skill JSON input.", rawError }
|
|
142
|
-
}
|
|
143
|
-
if (rawError.includes("Unexpected token") || rawError.includes("JSON")) {
|
|
144
|
-
return { ok: false, errorCode: "INVALID_JSON", message: "Invalid JSON input.", rawError }
|
|
145
|
-
}
|
|
146
|
-
if (rawError.includes("unknown_action")) {
|
|
147
|
-
return { ok: false, errorCode: "UNKNOWN_ACTION", message: "Unknown action for skill adapter.", rawError }
|
|
148
|
-
}
|
|
149
|
-
if (rawError.includes("cannot_map_text")) {
|
|
150
|
-
return { ok: false, errorCode: "CANNOT_MAP_TEXT", message: "Cannot map text intent. Add project/task context.", rawError }
|
|
151
|
-
}
|
|
152
|
-
if (rawError.includes("missing_project")) {
|
|
153
|
-
return { ok: false, errorCode: "MISSING_PROJECT", message: "Missing projectId.", rawError }
|
|
154
|
-
}
|
|
155
|
-
if (rawError.includes("taskId")) {
|
|
156
|
-
return { ok: false, errorCode: "MISSING_TASK", message: "Missing taskId.", rawError }
|
|
157
|
-
}
|
|
158
|
-
if (rawError.includes("missing_projectId_or_code") || rawError.includes("missing_code")) {
|
|
159
|
-
return { ok: false, errorCode: "MISSING_CODE", message: "Missing invitation code.", rawError }
|
|
160
|
-
}
|
|
161
|
-
if (rawError.includes("missing_projectId_or_summary")) {
|
|
162
|
-
return { ok: false, errorCode: "MISSING_SUMMARY", message: "Missing risk summary.", rawError }
|
|
163
|
-
}
|
|
164
|
-
if (rawError.includes("unknown_") || rawError.includes("missing_")) {
|
|
165
|
-
return { ok: false, errorCode: "CLI_ERROR", message: "CLI arguments are incomplete or unsupported.", rawError }
|
|
166
|
-
}
|
|
167
|
-
return { ok: false, errorCode: "UNKNOWN", message: "Skill execution failed.", rawError }
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
main().catch((e) => {
|
|
171
|
-
process.stdout.write(`${JSON.stringify(mapError(e), null, 2)}\n`)
|
|
172
|
-
process.exit(1)
|
|
173
|
-
})
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import * as fs from "node:fs/promises"
|
|
2
|
-
import * as path from "node:path"
|
|
3
|
-
import type { WeiyuanIdentityFileV1 } from "./types"
|
|
4
|
-
|
|
5
|
-
export const DEFAULT_IDENTITY_PATH = ".weiyuan"
|
|
6
|
-
|
|
7
|
-
export async function readIdentity(filePath: string): Promise<WeiyuanIdentityFileV1> {
|
|
8
|
-
const raw = await fs.readFile(filePath, "utf8")
|
|
9
|
-
const parsed = JSON.parse(raw) as WeiyuanIdentityFileV1
|
|
10
|
-
if (parsed.version !== 1) {
|
|
11
|
-
throw new Error("unsupported_identity_version")
|
|
12
|
-
}
|
|
13
|
-
return parsed
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function writeIdentity(filePath: string, data: WeiyuanIdentityFileV1): Promise<void> {
|
|
17
|
-
await fs.mkdir(path.dirname(path.resolve(filePath)), { recursive: true })
|
|
18
|
-
const tmp = `${filePath}.tmp`
|
|
19
|
-
await fs.writeFile(tmp, JSON.stringify(data, null, 2), "utf8")
|
|
20
|
-
try {
|
|
21
|
-
await fs.unlink(filePath)
|
|
22
|
-
} catch {}
|
|
23
|
-
await fs.rename(tmp, filePath)
|
|
24
|
-
}
|
|
25
|
-
|