openclaw-weiyuan-init 1.0.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.
- package/LICENSE +0 -0
- package/README.md +8 -0
- package/bin/cli.js +54 -0
- package/lib/commands.js +172 -0
- package/lib/downloader.js +43 -0
- package/lib/extractor.js +24 -0
- package/lib/identity.js +39 -0
- package/lib/package.json +49 -0
- package/lib/server.js +33 -0
- package/lib/utils.js +50 -0
- package/package.json +30 -0
- package/templates/.weiyuan.template +10 -0
- package/workspace-weiyuan/.weiyuan +10 -0
- package/workspace-weiyuan/weiyuan/README.md +159 -0
- package/workspace-weiyuan/weiyuan/examples/action-task-list.json +6 -0
- package/workspace-weiyuan/weiyuan/examples/risk-report.json +8 -0
- package/workspace-weiyuan/weiyuan/examples/text-what-can-i-do.json +6 -0
- package/workspace-weiyuan/weiyuan/manifest.json +45 -0
- package/workspace-weiyuan/weiyuan/schema.json +104 -0
- package/workspace-weiyuan/weiyuan/src/cli.ts +453 -0
- package/workspace-weiyuan/weiyuan/src/cliMain.ts +7 -0
- package/workspace-weiyuan/weiyuan/src/crypto.ts +14 -0
- package/workspace-weiyuan/weiyuan/src/http.ts +45 -0
- package/workspace-weiyuan/weiyuan/src/signing.ts +38 -0
- package/workspace-weiyuan/weiyuan/src/skillAdapter.ts +173 -0
- package/workspace-weiyuan/weiyuan/src/types.ts +10 -0
- package/workspace-weiyuan/weiyuan/src/weiyuanFile.ts +25 -0
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,453 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
|