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.
@@ -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,10 @@
1
+ export type WeiyuanIdentityFileV1 = {
2
+ version: 1
3
+ serverBaseUrl: string
4
+ lobsterId: string
5
+ publicKeyBase64: string
6
+ secretKeyBase64: string
7
+ identityHash: string
8
+ projectCursors: Record<string, number>
9
+ }
10
+
@@ -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
+