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
|
+
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
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
|