openclaw-weiyuan-init 1.0.116 → 1.0.118

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/lib/downloader.js CHANGED
@@ -12,6 +12,44 @@ function toZipFilename(downloadUrl) {
12
12
  return parts[parts.length - 1] || 'weiyuan-openclaw-skill.zip';
13
13
  }
14
14
 
15
+ function parseVersionFromZipFilename(zipFilename) {
16
+ const match = /-v(\d+\.\d+\.\d+)\.zip$/i.exec(String(zipFilename || '').trim());
17
+ return match ? match[1] : '';
18
+ }
19
+
20
+ function buildFallbackReleaseNotes(version) {
21
+ const normalizedVersion = String(version || '').trim();
22
+ if (!normalizedVersion) return null;
23
+ return {
24
+ version: normalizedVersion,
25
+ summary: '本次更新已完成发布,请先阅读必读文件后再执行命令。',
26
+ changes: [
27
+ '已同步最新微元系统运行时与规则文件。',
28
+ '执行任何命令前,请先阅读 CHANGELOG_OPENCLAW.md、release-notes/latest.json、docs/OPENCLAW_WEIYUAN_CLI_QUICKSTART.md、docs/WEIYUAN_AGENT_RULES.md。'
29
+ ],
30
+ releasedAt: new Date().toISOString()
31
+ };
32
+ }
33
+
34
+ function normalizeReleaseNotesForResolvedVersion(releaseNotes, resolvedVersion) {
35
+ const version = String(resolvedVersion || '').trim();
36
+ if (!version) return releaseNotes || null;
37
+ const fallback = buildFallbackReleaseNotes(version);
38
+ if (!releaseNotes || typeof releaseNotes !== 'object') return fallback;
39
+ const notesVersion = String(releaseNotes.version || '').trim();
40
+ const summary = String(releaseNotes.summary || '').trim();
41
+ const changes = Array.isArray(releaseNotes.changes)
42
+ ? releaseNotes.changes.map((item) => String(item || '').trim()).filter(Boolean)
43
+ : [];
44
+ return {
45
+ version,
46
+ summary: summary || fallback.summary,
47
+ changes: changes.length > 0 ? changes : fallback.changes,
48
+ releasedAt: String(releaseNotes.releasedAt || '').trim() || fallback.releasedAt,
49
+ sourceVersion: notesVersion || undefined
50
+ };
51
+ }
52
+
15
53
  async function fetchLatestReleaseNotes(base, nonce) {
16
54
  const notesUrl = `${base}/RELEASE_NOTES_LATEST.json?nocache=${nonce}`;
17
55
  try {
@@ -34,7 +72,16 @@ async function fetchLatestReleaseNotes(base, nonce) {
34
72
 
35
73
  async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = null }) {
36
74
  if (downloadUrl) {
37
- return { downloadUrl, zipFilename: toZipFilename(downloadUrl), version: null, sha256: '', from: 'download_url', releaseNotes: null };
75
+ const zipFilename = toZipFilename(downloadUrl);
76
+ const version = parseVersionFromZipFilename(zipFilename);
77
+ return {
78
+ downloadUrl,
79
+ zipFilename,
80
+ version: version || null,
81
+ sha256: '',
82
+ from: 'download_url',
83
+ releaseNotes: normalizeReleaseNotesForResolvedVersion(null, version)
84
+ };
38
85
  }
39
86
 
40
87
  const base = trimSlash(upgradeBaseUrl);
@@ -52,7 +99,14 @@ async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = nul
52
99
  if (spinner) {
53
100
  spinner.text = `已解析最新版本: v${version}`;
54
101
  }
55
- return { downloadUrl: resolvedDownloadUrl, zipFilename, version, sha256, from: 'latest_json', releaseNotes };
102
+ return {
103
+ downloadUrl: resolvedDownloadUrl,
104
+ zipFilename,
105
+ version,
106
+ sha256,
107
+ from: 'latest_json',
108
+ releaseNotes: normalizeReleaseNotesForResolvedVersion(releaseNotes, version)
109
+ };
56
110
  }
57
111
  } catch (_e) {
58
112
  // fallback to txt
@@ -66,7 +120,14 @@ async function resolvePackageSource({ downloadUrl, upgradeBaseUrl, spinner = nul
66
120
  if (spinner) {
67
121
  spinner.text = `已解析最新版本: v${version}`;
68
122
  }
69
- return { downloadUrl: resolvedDownloadUrl, zipFilename, version, sha256: '', from: 'latest_version', releaseNotes };
123
+ return {
124
+ downloadUrl: resolvedDownloadUrl,
125
+ zipFilename,
126
+ version,
127
+ sha256: '',
128
+ from: 'latest_version',
129
+ releaseNotes: normalizeReleaseNotesForResolvedVersion(releaseNotes, version)
130
+ };
70
131
  }
71
132
 
72
133
  async function downloadFile(url, outputPath, spinner = null, expectedSha256 = '') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-weiyuan-init",
3
- "version": "1.0.116",
3
+ "version": "1.0.118",
4
4
  "description": "OpenClaw Weiyuan Skill 一键初始化工具",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -145,7 +145,7 @@ npm run weiyuan -- doc sync-check --project <projectId>
145
145
 
146
146
  说明:
147
147
 
148
- - 文件上传不再通过 CLI 执行,请直接在项目看板中点击上传按钮。
148
+ - 文件上传已恢复 CLI 直传能力,可使用 `doc upload --project <projectId> --file <路径>`;未显式提供 `--name` 或 `--type` 时会自动补默认值。
149
149
  - 文件删除保留 CLI 能力,智能体 CLI 与小精灵 CLI 都支持删除文件。
150
150
 
151
151
  Todo 行动清单:
@@ -803,7 +803,7 @@ async function repairIdentityForCurrentBinding(args: Argv): Promise<boolean> {
803
803
  JSON.stringify(nextJoinedProjectIds) !== JSON.stringify(identity.joinedProjectIds ?? []) ||
804
804
  nextCurrentProjectId !== String(identity.currentProjectId || "").trim() ||
805
805
  JSON.stringify(nextProjectCursors) !== JSON.stringify(identity.projectCursors ?? {})
806
- if (!changed) return false
806
+ if (!changed) return true
807
807
  await backupCurrentIdentityIfExists(filePath)
808
808
  await writeIdentity(filePath, nextIdentity)
809
809
  return true
@@ -2137,25 +2137,57 @@ async function cmdTaskList(args: Argv): Promise<void> {
2137
2137
  throw new Error(JSON.stringify(json))
2138
2138
  }
2139
2139
  const includeArchived = optBoolean(args.flags, "includeArchived", false)
2140
+ const includeContainers = optBoolean(args.flags, "includeContainers", false)
2140
2141
  const originalTasks = Array.isArray((json.data as any)?.tasks) ? ((json.data as any).tasks as any[]) : []
2141
- const tasks = includeArchived ? originalTasks : originalTasks.filter((t) => String(t?.status ?? "") !== "archived")
2142
- const doneCount = tasks.filter((t) => String(t?.status ?? "").toLowerCase() === "done").length
2143
- const takenCount = tasks.filter((t) => String(t?.status ?? "").toLowerCase() === "taken").length
2144
- const openCount = tasks.filter((t) => String(t?.status ?? "").toLowerCase() === "open").length
2145
- const myTakenCount = tasks.filter((t) => String(t?.status ?? "").toLowerCase() === "taken" && isTaskMine(t, identity.lobsterId)).length
2142
+ const archivedFilteredCount = includeArchived
2143
+ ? 0
2144
+ : originalTasks.filter((t) => String(t?.status ?? "").trim().toLowerCase() === "archived").length
2145
+ const tasksAfterArchivePolicy = includeArchived ? originalTasks : originalTasks.filter((t) => String(t?.status ?? "") !== "archived")
2146
+ const taskById = buildTaskListTaskMap(tasksAfterArchivePolicy)
2147
+ const now = Date.now()
2148
+ const executionTasks = tasksAfterArchivePolicy
2149
+ .filter((t) => !isContainerTaskLike(t))
2150
+ .map((t) => {
2151
+ const dependencyReady = isTaskDependencyReadyForList(t, taskById)
2152
+ const taskBoardBucket = classifyTaskBoardBucketForList({ ...t, dependencyReady }, taskById, now)
2153
+ return {
2154
+ ...t,
2155
+ dependencyReady,
2156
+ claimedByMe: isTaskMine(t, identity.lobsterId),
2157
+ taskBoardBucket,
2158
+ }
2159
+ })
2160
+ const containerTasks = tasksAfterArchivePolicy.filter((t) => isContainerTaskLike(t))
2161
+ const tasks = includeContainers ? tasksAfterArchivePolicy : executionTasks
2162
+ const doneCount = executionTasks.filter((t) => t.taskBoardBucket === "done").length
2163
+ const focusCount = executionTasks.filter((t) => t.taskBoardBucket === "focus").length
2164
+ const waitingCount = executionTasks.filter((t) => t.taskBoardBucket === "waiting").length
2165
+ const unclaimedCount = executionTasks.filter((t) => t.taskBoardBucket === "unclaimed").length
2166
+ const myTakenCount = executionTasks.filter((t) => ["focus", "waiting"].includes(String(t.taskBoardBucket || "")) && t.claimedByMe === true).length
2167
+ const takenCount = executionTasks.filter((t) => String(t?.status ?? "").toLowerCase() === "taken").length
2168
+ const openCount = executionTasks.filter((t) => String(t?.status ?? "").toLowerCase() === "open").length
2146
2169
  print({
2147
2170
  ...(json.data as Record<string, unknown>),
2148
2171
  projectId,
2149
2172
  projectName: projectRef.projectName ?? (json.data as any)?.projectName,
2150
2173
  tasks,
2174
+ executionTasks,
2175
+ containerTasks: includeContainers ? containerTasks : undefined,
2151
2176
  summary: {
2152
2177
  done: doneCount,
2178
+ focus: focusCount,
2179
+ waiting: waitingCount,
2180
+ unclaimed: unclaimedCount,
2181
+ mineTaken: myTakenCount,
2182
+ },
2183
+ rawStatusSummary: {
2153
2184
  taken: takenCount,
2154
2185
  open: openCount,
2155
- mineTaken: myTakenCount,
2156
2186
  },
2187
+ listView: includeContainers ? "all_tasks" : "execution_tasks_only",
2157
2188
  listPolicy: includeArchived ? "all" : "exclude_archived",
2158
- archivedFilteredCount: includeArchived ? 0 : Math.max(0, originalTasks.length - tasks.length),
2189
+ archivedFilteredCount,
2190
+ containerExcludedCount: includeContainers ? 0 : containerTasks.length,
2159
2191
  nextSuggestions: [
2160
2192
  "如需查看剩余空间,执行:project storage-remaining",
2161
2193
  "可继续上传文档或更新任务进度",
@@ -4093,6 +4125,7 @@ function buildTodoCliView(
4093
4125
  const base = {
4094
4126
  ok: true,
4095
4127
  mode,
4128
+ fetchedAt: now,
4096
4129
  count: enriched.length,
4097
4130
  activeCount: enriched.filter((row) => String(row.status || "") !== "done").length,
4098
4131
  todayItems,
@@ -4125,6 +4158,85 @@ function buildTodoCliView(
4125
4158
  return base
4126
4159
  }
4127
4160
 
4161
+ function buildMemberListCliView(raw: unknown, viewerLobsterId: string): Record<string, unknown> {
4162
+ const source = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {}
4163
+ const members = Array.isArray(source.members) ? (source.members as Array<Record<string, unknown>>) : []
4164
+ const enriched = members.map((row) => {
4165
+ const lobsterId = String(row.lobsterId ?? "").trim()
4166
+ const role = String(row.role ?? "member").trim().toLowerCase()
4167
+ const memberType = String(row.memberType ?? "lobster").trim().toLowerCase()
4168
+ return {
4169
+ ...row,
4170
+ lobsterId,
4171
+ role,
4172
+ memberType,
4173
+ isMe: lobsterId === viewerLobsterId,
4174
+ joinedAtText: typeof row.joinedAt === "number" && Number.isFinite(row.joinedAt) ? new Date(Number(row.joinedAt)).toLocaleString() : "",
4175
+ }
4176
+ })
4177
+ const summary = {
4178
+ members: enriched.length,
4179
+ founder: enriched.filter((row) => row.role === "founder").length,
4180
+ leader: enriched.filter((row) => row.role === "leader").length,
4181
+ member: enriched.filter((row) => row.role === "member").length,
4182
+ autoAgent: enriched.filter((row) => row.memberType === "lobster").length,
4183
+ manualMember: enriched.filter((row) => row.memberType === "member").length,
4184
+ }
4185
+ return {
4186
+ ...source,
4187
+ fetchedAt: Date.now(),
4188
+ members: enriched,
4189
+ summary,
4190
+ }
4191
+ }
4192
+
4193
+ function buildDocListCliView(raw: unknown): Record<string, unknown> {
4194
+ const source = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : {}
4195
+ const docs = Array.isArray(source.docs) ? (source.docs as Array<Record<string, unknown>>) : []
4196
+ const enriched = docs.map((row) => {
4197
+ const fileSize = Number(row.fileSize ?? 0)
4198
+ const updatedAt = Number(row.updatedAt ?? 0)
4199
+ return {
4200
+ ...row,
4201
+ fileSize,
4202
+ updatedAt,
4203
+ updatedAtText: updatedAt > 0 ? new Date(updatedAt).toLocaleString() : "",
4204
+ }
4205
+ })
4206
+ const totalBytes = enriched.reduce((sum, row) => sum + (Number.isFinite(Number(row.fileSize ?? 0)) ? Number(row.fileSize ?? 0) : 0), 0)
4207
+ const summary = {
4208
+ docs: enriched.length,
4209
+ totalBytes,
4210
+ latestUpdatedAt: enriched.reduce((max, row) => Math.max(max, Number(row.updatedAt ?? 0) || 0), 0),
4211
+ }
4212
+ return {
4213
+ ...source,
4214
+ fetchedAt: Date.now(),
4215
+ docs: enriched,
4216
+ summary: {
4217
+ ...summary,
4218
+ latestUpdatedAtText: summary.latestUpdatedAt > 0 ? new Date(summary.latestUpdatedAt).toLocaleString() : "",
4219
+ },
4220
+ }
4221
+ }
4222
+
4223
+ function normalizeTodoMutationCliResult(raw: unknown): Record<string, unknown> | unknown {
4224
+ const source = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : undefined
4225
+ if (!source) return raw
4226
+ if (source.currentTodoPlan || Array.isArray(source.items)) {
4227
+ const view = buildTodoCliView(source, "plan")
4228
+ return {
4229
+ ...source,
4230
+ ...view,
4231
+ fetchedAt: Date.now(),
4232
+ }
4233
+ }
4234
+ return {
4235
+ ...source,
4236
+ fetchedAt: Date.now(),
4237
+ }
4238
+ }
4239
+
4128
4240
  async function cmdTodoList(
4129
4241
  args: Argv,
4130
4242
  mode: "list" | "plan" | "today" | "tomorrow" | "calendar" = "list"
@@ -4183,7 +4295,7 @@ async function cmdTodoUpsert(args: Argv): Promise<void> {
4183
4295
  body,
4184
4296
  })
4185
4297
  if (status === 200) {
4186
- print(json.data)
4298
+ print(normalizeTodoMutationCliResult(json.data))
4187
4299
  return
4188
4300
  }
4189
4301
  if (!isTodoRestRouteMissing(status, json)) throw new Error(JSON.stringify(json))
@@ -4192,7 +4304,7 @@ async function cmdTodoUpsert(args: Argv): Promise<void> {
4192
4304
  buildPanelStyleCommand("todo", "upsert", body),
4193
4305
  optString(args.flags, "password")
4194
4306
  )
4195
- print(data)
4307
+ print(normalizeTodoMutationCliResult(data))
4196
4308
  }
4197
4309
 
4198
4310
  async function cmdTodoComplete(args: Argv): Promise<void> {
@@ -4215,7 +4327,7 @@ async function cmdTodoComplete(args: Argv): Promise<void> {
4215
4327
  body,
4216
4328
  })
4217
4329
  if (status === 200) {
4218
- print(json.data)
4330
+ print(normalizeTodoMutationCliResult(json.data))
4219
4331
  return
4220
4332
  }
4221
4333
  if (!isTodoRestRouteMissing(status, json)) throw new Error(JSON.stringify(json))
@@ -4224,7 +4336,7 @@ async function cmdTodoComplete(args: Argv): Promise<void> {
4224
4336
  buildPanelStyleCommand("todo", "complete", body),
4225
4337
  optString(args.flags, "password")
4226
4338
  )
4227
- print(data)
4339
+ print(normalizeTodoMutationCliResult(data))
4228
4340
  }
4229
4341
 
4230
4342
  async function cmdTodoDelete(args: Argv): Promise<void> {
@@ -4247,7 +4359,7 @@ async function cmdTodoDelete(args: Argv): Promise<void> {
4247
4359
  body,
4248
4360
  })
4249
4361
  if (status === 200) {
4250
- print(json.data)
4362
+ print(normalizeTodoMutationCliResult(json.data))
4251
4363
  return
4252
4364
  }
4253
4365
  if (!isTodoRestRouteMissing(status, json)) throw new Error(JSON.stringify(json))
@@ -4256,7 +4368,7 @@ async function cmdTodoDelete(args: Argv): Promise<void> {
4256
4368
  buildPanelStyleCommand("todo", "delete", body),
4257
4369
  optString(args.flags, "password")
4258
4370
  )
4259
- print(data)
4371
+ print(normalizeTodoMutationCliResult(data))
4260
4372
  }
4261
4373
 
4262
4374
  async function cmdTodoRegenerate(args: Argv): Promise<void> {
@@ -4341,7 +4453,7 @@ async function cmdMemberList(args: Argv): Promise<void> {
4341
4453
  secretKeyBase64: identity.secretKeyBase64,
4342
4454
  })
4343
4455
  if (status !== 200) throw new Error(JSON.stringify(json))
4344
- print(json.data)
4456
+ print(buildMemberListCliView(json.data, identity.lobsterId))
4345
4457
  }
4346
4458
 
4347
4459
  async function cmdMemberAddWeb(args: Argv): Promise<void> {
@@ -4955,6 +5067,32 @@ function parseIssueAttachmentPaths(raw?: string): string[] {
4955
5067
  .slice(0, ISSUE_ATTACHMENT_MAX_FILES)
4956
5068
  }
4957
5069
 
5070
+ const DOC_UPLOAD_MIME_MAP: Record<string, string> = {
5071
+ ".txt": "text/plain",
5072
+ ".md": "text/markdown",
5073
+ ".markdown": "text/markdown",
5074
+ ".json": "application/json",
5075
+ ".csv": "text/csv",
5076
+ ".pdf": "application/pdf",
5077
+ ".zip": "application/zip",
5078
+ ".doc": "application/msword",
5079
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
5080
+ ".xls": "application/vnd.ms-excel",
5081
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5082
+ ".ppt": "application/vnd.ms-powerpoint",
5083
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
5084
+ ".png": "image/png",
5085
+ ".jpg": "image/jpeg",
5086
+ ".jpeg": "image/jpeg",
5087
+ ".gif": "image/gif",
5088
+ ".webp": "image/webp",
5089
+ }
5090
+
5091
+ function guessDocMimeType(filePath: string): string {
5092
+ const ext = path.extname(String(filePath || "")).toLowerCase()
5093
+ return DOC_UPLOAD_MIME_MAP[ext] ?? "application/octet-stream"
5094
+ }
5095
+
4958
5096
  async function loadIssueAttachments(attachmentPath?: string): Promise<Array<{ fileName: string; mimeType: string; contentBase64: string }>> {
4959
5097
  const paths = parseIssueAttachmentPaths(attachmentPath)
4960
5098
  if (!paths.length) return []
@@ -5292,10 +5430,73 @@ function parseProjectScope(flags: Record<string, string | boolean>, allProjects:
5292
5430
  function isTaskMine(task: any, lobsterId: string): boolean {
5293
5431
  if (!task || typeof task !== "object") return false
5294
5432
  if (task.claimedByMe === true || task.mine === true) return true
5295
- const assignee = String(task.assigneeLobsterId ?? task.ownerLobsterId ?? task.claimedBy ?? "")
5433
+ const assignee = String(task.assigneeLobsterId ?? "")
5296
5434
  return assignee.length > 0 && assignee === lobsterId
5297
5435
  }
5298
5436
 
5437
+ function isContainerTaskLike(task: any): boolean {
5438
+ return String(task?.taskKind ?? "").trim().toLowerCase() === "container"
5439
+ }
5440
+
5441
+ function normalizeTaskListStatus(task: any): "todo" | "in_progress" | "done" | "archived" | "blocked" {
5442
+ const raw = String(task?.status ?? "").trim().toLowerCase()
5443
+ if (raw === "done") return "done"
5444
+ if (raw === "archived") return "archived"
5445
+ if (raw === "blocked") return "blocked"
5446
+ if (raw === "taken" || raw === "review") return "in_progress"
5447
+ return "todo"
5448
+ }
5449
+
5450
+ function buildTaskListTaskMap(tasks: any[]): Map<string, any> {
5451
+ return new Map(
5452
+ tasks
5453
+ .map((task) => [String(task?.taskId ?? "").trim(), task] as const)
5454
+ .filter(([taskId]) => Boolean(taskId))
5455
+ )
5456
+ }
5457
+
5458
+ function isTaskDependencyReadyForList(task: any, taskById: Map<string, any>): boolean {
5459
+ if (typeof task?.dependencyReady === "boolean") return Boolean(task.dependencyReady)
5460
+ const deps = Array.isArray(task?.dependsOnTaskIds) ? task.dependsOnTaskIds.map((x: any) => String(x ?? "").trim()).filter(Boolean) : []
5461
+ if (!deps.length) return true
5462
+ return deps.every((depId) => {
5463
+ const dep = taskById.get(depId)
5464
+ if (!dep) return false
5465
+ if (isContainerTaskLike(dep)) {
5466
+ const childExecution = Array.from(taskById.values()).filter(
5467
+ (x) =>
5468
+ !isContainerTaskLike(x) &&
5469
+ String(x?.status ?? "").trim().toLowerCase() !== "archived" &&
5470
+ String(x?.parentContainerId ?? "").trim() === depId
5471
+ )
5472
+ if (!childExecution.length) return normalizeTaskListStatus(dep) === "done"
5473
+ return childExecution.every((x) => normalizeTaskListStatus(x) === "done")
5474
+ }
5475
+ return normalizeTaskListStatus(dep) === "done"
5476
+ })
5477
+ }
5478
+
5479
+ function isTaskActivatedForList(task: any, taskById: Map<string, any>, now = Date.now()): boolean {
5480
+ const status = normalizeTaskListStatus(task)
5481
+ if (status === "done" || status === "archived") return false
5482
+ const manualFocus = task?.showInFocus === true
5483
+ const depReady = isTaskDependencyReadyForList(task, taskById)
5484
+ const plannedStartAt = Number(task?.plannedStartAt ?? 0)
5485
+ const hasDeps = Array.isArray(task?.dependsOnTaskIds) && task.dependsOnTaskIds.length > 0
5486
+ const timeReady = hasDeps || !(plannedStartAt > 0) || plannedStartAt <= now
5487
+ if (manualFocus) return depReady
5488
+ return depReady && timeReady
5489
+ }
5490
+
5491
+ function classifyTaskBoardBucketForList(task: any, taskById: Map<string, any>, now = Date.now()): "focus" | "waiting" | "unclaimed" | "done" | "archived" {
5492
+ const status = normalizeTaskListStatus(task)
5493
+ if (status === "done") return "done"
5494
+ if (status === "archived") return "archived"
5495
+ const hasAssignee = Boolean(String(task?.assigneeLobsterId ?? "").trim())
5496
+ if (!hasAssignee) return "unclaimed"
5497
+ return isTaskActivatedForList(task, taskById, now) ? "focus" : "waiting"
5498
+ }
5499
+
5299
5500
  async function cmdTaskListCross(args: Argv): Promise<void> {
5300
5501
  const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
5301
5502
  const identity = await readIdentity(filePath)
@@ -5581,12 +5782,97 @@ async function cmdDocList(args: Argv): Promise<void> {
5581
5782
  secretKeyBase64: identity.secretKeyBase64,
5582
5783
  })
5583
5784
  if (status !== 200) throw new Error(JSON.stringify(json))
5584
- print(json.data)
5785
+ print(buildDocListCliView(json.data))
5585
5786
  }
5586
5787
 
5587
5788
  async function cmdDocCreate(args: Argv): Promise<void> {
5588
- void args
5589
- throw new Error("doc_upload_board_only")
5789
+ const filePath = optString(args.flags, "identity") ?? DEFAULT_IDENTITY_PATH
5790
+ const identity = await readIdentity(filePath)
5791
+ const resolved = await resolveProjectRef(args.flags, identity)
5792
+ const interactive = Boolean(args.flags.interactive || args.flags.prompt)
5793
+ const sourceFileRaw = interactive
5794
+ ? await askRequired("请选择要上传的文件路径: ", optString(args.flags, "file"))
5795
+ : mustString(args.flags, "file")
5796
+ const sourceFile = path.resolve(sourceFileRaw)
5797
+ let stat
5798
+ try {
5799
+ stat = await fs.stat(sourceFile)
5800
+ } catch {
5801
+ throw new Error(`invalid_doc_file_path: ${sourceFileRaw}`)
5802
+ }
5803
+ if (!stat.isFile()) throw new Error(`invalid_doc_file_path: ${sourceFileRaw}`)
5804
+ const bytes = await fs.readFile(sourceFile)
5805
+ const uploadNameFallback = path.basename(sourceFile)
5806
+ const uploadTypeFallback = guessDocMimeType(sourceFile)
5807
+ const docName = interactive
5808
+ ? await askRequired("请输入上传后显示的文件名: ", optString(args.flags, "name") ?? optString(args.flags, "title") ?? uploadNameFallback)
5809
+ : String(optString(args.flags, "name") ?? optString(args.flags, "title") ?? uploadNameFallback).trim()
5810
+ const docType = interactive
5811
+ ? await askRequired("请输入文件类型(可留空走自动识别): ", optString(args.flags, "type") ?? optString(args.flags, "docType") ?? uploadTypeFallback)
5812
+ : String(optString(args.flags, "type") ?? optString(args.flags, "docType") ?? uploadTypeFallback).trim()
5813
+ const fileSize = bytes.length
5814
+ if (fileSize > MAX_SHARED_DOC_FILE_BYTES) {
5815
+ throw new Error("doc_file_too_large: 单文件超过 10MB,请压缩后重试。")
5816
+ }
5817
+ const contentHash = sha256Hex(bytes)
5818
+ const storageType = optString(args.flags, "storageType")
5819
+ const storageProvider = optString(args.flags, "storageProvider")
5820
+ const storagePath = optString(args.flags, "storagePath") ?? sourceFile
5821
+ const tags = String(optString(args.flags, "tags") ?? "")
5822
+ .split(",")
5823
+ .map((x) => x.trim())
5824
+ .filter(Boolean)
5825
+ const relatedTaskIds = String(optString(args.flags, "tasks") ?? optString(args.flags, "task") ?? "")
5826
+ .split(",")
5827
+ .map((x) => x.trim())
5828
+ .filter(Boolean)
5829
+ const version = optString(args.flags, "version") ?? "v1"
5830
+ const description = optString(args.flags, "desc") ?? optString(args.flags, "description")
5831
+ const changeSummary = optString(args.flags, "summary")
5832
+ const idempotencyKey = createRequestId(`doc_upload_${path.basename(sourceFile)}`)
5833
+ const { status, json } = await signedRequest({
5834
+ serverBaseUrl: identity.serverBaseUrl,
5835
+ method: "POST",
5836
+ pathWithQuery: `/v1/projects/${resolved.projectId}/docs`,
5837
+ lobsterId: identity.lobsterId,
5838
+ secretKeyBase64: identity.secretKeyBase64,
5839
+ idempotencyKey,
5840
+ body: {
5841
+ docName,
5842
+ docType,
5843
+ fileSize,
5844
+ contentHash,
5845
+ contentBase64: bytes.toString("base64"),
5846
+ storageLocation: { type: storageType ?? undefined, provider: storageProvider, path: storagePath },
5847
+ version,
5848
+ relatedTaskIds,
5849
+ tags,
5850
+ description,
5851
+ changeSummary,
5852
+ },
5853
+ })
5854
+ if (status !== 200) {
5855
+ const msg = String((json as any)?.msg ?? (json as any)?.message ?? "")
5856
+ if (status === 409 && msg.includes("storage_quota_exceeded")) {
5857
+ throw new Error("storage_quota_exceeded: 存储空间已达上限,请联系 founder 升级容量后再上传。")
5858
+ }
5859
+ if (status === 409 && msg === "invalid_storage_profile") {
5860
+ throw new Error("invalid_storage_profile: 当前项目存储配置无效,请联系 founder 检查项目存储设置。")
5861
+ }
5862
+ throw new Error(JSON.stringify(json))
5863
+ }
5864
+ print({
5865
+ ok: true,
5866
+ projectId: resolved.projectId,
5867
+ projectName: resolved.projectName ?? "",
5868
+ docName,
5869
+ docType,
5870
+ sourceFile,
5871
+ fileSize,
5872
+ contentHash,
5873
+ uploadedAt: Date.now(),
5874
+ ...(json.data && typeof json.data === "object" ? (json.data as Record<string, unknown>) : {}),
5875
+ })
5590
5876
  }
5591
5877
 
5592
5878
  async function cmdDocDetail(args: Argv): Promise<void> {
@@ -18,7 +18,9 @@ function toUserError(raw: string): string {
18
18
  if (msg.includes("interactive_input_required")) return "必填内容不能为空,请补充参数后重试。"
19
19
  if (msg.includes("missing_project_name")) return "项目名不能为空,请通过 --name 提供有效项目名。"
20
20
  if (msg.includes("project_name_too_long")) return "项目名不能超过80个字符,请缩短后重试。"
21
- if (msg.includes("doc_upload_board_only")) return "文件上传已改为只支持在项目看板里点击上传按钮完成,CLI 不再执行上传。"
21
+ if (msg.includes("doc_upload_board_only")) return "当前运行时版本过旧,文件上传仍被禁用。请先更新微元系统后再试。"
22
+ if (msg.includes("invalid_doc_file_path")) return "上传文件路径不存在,或该路径不是一个可读取的文件。请检查 --file 后重试。"
23
+ if (msg.includes("invalid_storage_profile")) return "当前项目的存储配置异常,暂时无法上传文件。请联系项目 founder 检查存储设置。"
22
24
  if (msg.includes("invalid_project_name")) return "项目名仅支持中文/英文/数字/空格及 -_.()·,且不能包含HTML标签。"
23
25
  if (msg.includes("task_title_contains_html")) return "任务标题不能包含HTML标签(如 <script>)。"
24
26
  if (msg.includes("task_title_too_long")) return "任务标题不能超过120个字符,请缩短后重试。"
@@ -129,14 +129,20 @@ const DEFAULT_MICRO_RULES: MicroRulesConfig = {
129
129
  commandList: [
130
130
  "检查项目",
131
131
  "查看任务",
132
+ "查看成员",
133
+ "查看文件",
132
134
  "打开看板",
133
135
  "打开驾驶舱",
134
136
  "项目列表",
135
137
  "任务列表",
138
+ "成员列表",
139
+ "文件列表",
136
140
  "创建项目",
137
141
  "创建任务",
138
142
  "认领任务",
139
143
  "完成任务",
144
+ "新增待办",
145
+ "删除待办",
140
146
  "项目状态",
141
147
  "微元回复用简洁模式",
142
148
  "微元回复用详细模式",
@@ -153,7 +159,7 @@ const DEFAULT_MICRO_RULES: MicroRulesConfig = {
153
159
  "通知中心",
154
160
  "生成邀请码",
155
161
  ],
156
- keywords: ["微元", "项目", "任务", "待办", "计划", "看板", "备份", "风险", "同步", "存储", "文档", "胶囊"],
162
+ keywords: ["微元", "项目", "任务", "待办", "计划", "看板", "成员", "文件", "备份", "风险", "同步", "存储", "文档", "胶囊"],
157
163
  detailTriggers: ["详细", "详情", "全部", "原始", "完整", "debug", "调试", "json", "id", "地址"],
158
164
  hardRequirements: [
159
165
  "微元场景回复必须带【微元协作】前缀。",
@@ -356,6 +362,90 @@ function extractNicknameFromText(text: string): string | undefined {
356
362
  return v || undefined
357
363
  }
358
364
 
365
+ function extractMemberProfileFieldFromText(
366
+ text: string,
367
+ field: "roleTitle" | "specialties" | "experienceSummary" | "strengthsSummary" | "taskPreferenceTags"
368
+ ): string | undefined {
369
+ const patterns: RegExp[] =
370
+ field === "roleTitle"
371
+ ? [
372
+ /(?:设置|修改|更改|更新)(?:我的)?(?:团队)?角色(?:与能力)?(?:为|叫|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
373
+ /(?:我的)?(?:团队)?角色(?:是|为|叫|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
374
+ ]
375
+ : field === "specialties"
376
+ ? [
377
+ /(?:设置|修改|更改|更新)(?:我的)?(?:擅长方向|擅长|专长|特长方向)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
378
+ /(?:我的)?(?:擅长方向|擅长|专长|特长方向)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
379
+ ]
380
+ : field === "experienceSummary"
381
+ ? [
382
+ /(?:设置|修改|更改|更新)(?:我的)?(?:经历概述|经历简介|经验概述|经验简介|经历)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
383
+ /(?:我的)?(?:经历概述|经历简介|经验概述|经验简介|经历)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
384
+ ]
385
+ : field === "strengthsSummary"
386
+ ? [
387
+ /(?:设置|修改|更改|更新)(?:我的)?(?:特长概述|优势概述|优势简介|特长简介|特长)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
388
+ /(?:我的)?(?:特长概述|优势概述|优势简介|特长简介|特长)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
389
+ ]
390
+ : [
391
+ /(?:设置|修改|更改|更新)(?:我的)?(?:偏好任务标签|任务偏好标签|偏好标签|任务标签)(?:为|成)?\s*[“"']?([^”"'\n]+)[”"']?$/u,
392
+ /(?:我的)?(?:偏好任务标签|任务偏好标签|偏好标签|任务标签)(?:是|为|改成|改为)\s*[“"']?([^”"'\n]+)[”"']?$/u,
393
+ ]
394
+ for (const pattern of patterns) {
395
+ const value = String(text.match(pattern)?.[1] ?? "").trim()
396
+ if (value) return value
397
+ }
398
+ return undefined
399
+ }
400
+
401
+ function hasMemberProfileCardIntent(text: string): boolean {
402
+ const compact = text.toLowerCase().replace(/\s+/g, "")
403
+ return (
404
+ compact.includes("成员角色卡片") ||
405
+ compact.includes("角色卡片") ||
406
+ compact.includes("成员卡片") ||
407
+ compact.includes("编辑成员信息") ||
408
+ compact.includes("修改成员信息") ||
409
+ compact.includes("设置角色卡片") ||
410
+ compact.includes("角色与能力") ||
411
+ compact.includes("我的角色") ||
412
+ compact.includes("修改角色") ||
413
+ compact.includes("更新角色") ||
414
+ compact.includes("团队角色") ||
415
+ compact.includes("擅长方向") ||
416
+ compact.includes("我的擅长") ||
417
+ compact.includes("我的专长") ||
418
+ compact.includes("经历概述") ||
419
+ compact.includes("经历简介") ||
420
+ compact.includes("经验概述") ||
421
+ compact.includes("我的经历") ||
422
+ compact.includes("特长概述") ||
423
+ compact.includes("优势概述") ||
424
+ compact.includes("我的特长") ||
425
+ compact.includes("我的优势") ||
426
+ compact.includes("偏好任务标签") ||
427
+ compact.includes("任务偏好标签")
428
+ )
429
+ }
430
+
431
+ function extractMemberRemarkIntent(text: string): { target?: string; remark?: string; clear?: boolean } | undefined {
432
+ const trimmed = String(text || "").trim()
433
+ if (!trimmed) return undefined
434
+ const clearMatch =
435
+ trimmed.match(/(?:清空|删除|移除|去掉)(?:我对)?\s*([^,,\s]+)\s*(?:的)?备注$/u) ??
436
+ trimmed.match(/(?:把)?\s*([^,,\s]+)\s*(?:的)?备注(?:清空|删除|移除|去掉)$/u)
437
+ if (clearMatch?.[1]) {
438
+ return { target: clearMatch[1].trim(), clear: true, remark: "-" }
439
+ }
440
+ const saveMatch =
441
+ trimmed.match(/(?:给|帮我给|把)?\s*([^,,\s]+)\s*(?:的)?备注(?:为|成)?\s*([^\n]+)$/u) ??
442
+ trimmed.match(/(?:把)?\s*([^,,\s]+)\s*(?:备注为|备注成)\s*([^\n]+)$/u)
443
+ if (saveMatch?.[1]) {
444
+ return { target: saveMatch[1].trim(), remark: String(saveMatch[2] ?? "").trim() }
445
+ }
446
+ return undefined
447
+ }
448
+
359
449
  function extractProjectNameForCreate(text: string): string | undefined {
360
450
  const m =
361
451
  text.match(/(?:创建|新建)(?:一个|个)?[“"'《【((]?\s*([^”"'》】))\n]+?项目)\s*(?:吧|呀|呢|的)?$/) ??
@@ -674,9 +764,9 @@ async function syncRulesFromSource(identityPath: string, keepMode: boolean): Pro
674
764
  function isMicroSceneText(text: string, rules: MicroRulesConfig): boolean {
675
765
  const lower = text.toLowerCase()
676
766
  if (/(团队负载|工作负载|团队负荷|团队工作量|谁最忙|team workload|workload)/i.test(lower)) return true
677
- if (/(网页成员|添加成员|新增成员|加入成员)/i.test(lower)) return true
767
+ if (/(添加成员|新增成员|加入成员|普通成员|成员列表|成员情况|项目成员|文件列表|文件情况|项目文件|项目资料)/i.test(lower)) return true
678
768
  if (
679
- /(我的项目|我有哪些项目|看看项目|看看我的项目|项目列表|查项目|创建项目|新建项目|我的任务|我有哪些任务|看看任务|看看我的任务|任务列表|查任务|创建任务|新建任务|我的待办|看看待办|看看我的待办|待办列表|今日待办|明日计划|明天计划|未来事项|打开看板|项目状态)/i.test(
769
+ /(我的项目|我有哪些项目|看看项目|看看我的项目|项目列表|查项目|创建项目|新建项目|我的任务|我有哪些任务|看看任务|看看我的任务|任务列表|查任务|创建任务|新建任务|我的待办|看看待办|看看我的待办|待办列表|今日待办|明日计划|明天计划|未来事项|打开看板|项目状态|成员列表|成员情况|项目成员|文件列表|文件情况|项目文件|项目资料)/i.test(
680
770
  text
681
771
  )
682
772
  ) {
@@ -1315,6 +1405,18 @@ function shouldCreateTodoFromText(lower: string): boolean {
1315
1405
  )
1316
1406
  }
1317
1407
 
1408
+ function shouldDeleteTodoFromText(lower: string): boolean {
1409
+ if (!lower) return false
1410
+ if (!(lower.includes("待办") || lower.includes("计划") || lower.includes("提醒"))) return false
1411
+ return (
1412
+ lower.includes("删除") ||
1413
+ lower.includes("删掉") ||
1414
+ lower.includes("去掉") ||
1415
+ lower.includes("移除") ||
1416
+ lower.includes("取消")
1417
+ )
1418
+ }
1419
+
1318
1420
  function inferTodoWhenFromText(text: string): { when?: string; bucket?: string } {
1319
1421
  const lower = text.toLowerCase()
1320
1422
  if (lower.includes("明天下午")) return { when: "tomorrow 15:00", bucket: "tomorrow" }
@@ -1329,11 +1431,24 @@ function inferTodoWhenFromText(text: string): { when?: string; bucket?: string }
1329
1431
  }
1330
1432
 
1331
1433
  function buildTodoActionTextFromText(text: string): string {
1332
- const trimmed = String(text || "").trim().replace(/[。!!]+$/g, "")
1434
+ const trimmed = String(text || "")
1435
+ .trim()
1436
+ .replace(/[。!!]+$/g, "")
1437
+ .replace(/^(提醒我|提醒一下|安排个待办|加个待办|记个待办|记一下|记得)\s*/u, "")
1333
1438
  if (!trimmed) return "新的待办"
1334
1439
  return trimmed
1335
1440
  }
1336
1441
 
1442
+ function buildTodoDeleteTargetFromText(text: string): string {
1443
+ const trimmed = String(text || "")
1444
+ .trim()
1445
+ .replace(/[。!!]+$/g, "")
1446
+ .replace(/^(请)?(帮我)?(把|将)?(这个|这条)?(待办|计划|提醒)?(删除|删掉|去掉|移除|取消)\s*/u, "")
1447
+ .replace(/^(删除|删掉|去掉|移除|取消)(这个|这条)?(待办|计划|提醒)?\s*/u, "")
1448
+ .trim()
1449
+ return trimmed || "当前待办"
1450
+ }
1451
+
1337
1452
  function isRiskActionKey(actionKey: string): boolean {
1338
1453
  return /(delete|reject|fail|rollback|conflict|error|forbidden|denied)/i.test(actionKey)
1339
1454
  }
@@ -1730,22 +1845,33 @@ function fromText(input: SkillInput): string[] {
1730
1845
  if (!nickname) return ["member-profile-edit", "--identity", identity, "--project", input.projectId]
1731
1846
  return ["member-profile-set", "--identity", identity, "--project", input.projectId, "--nickname", nickname]
1732
1847
  }
1733
- if (
1734
- compact.includes("成员角色卡片") ||
1735
- compact.includes("角色卡片") ||
1736
- compact.includes("成员卡片") ||
1737
- compact.includes("编辑成员信息") ||
1738
- compact.includes("修改成员信息") ||
1739
- compact.includes("设置角色卡片")
1740
- ) {
1848
+ if (hasMemberProfileCardIntent(text)) {
1741
1849
  if (!input.projectId) return ["context", "candidates", "--identity", identity]
1742
- return ["member-profile-edit", "--identity", identity, "--project", input.projectId]
1850
+ const roleTitle = extractMemberProfileFieldFromText(text, "roleTitle")
1851
+ const specialties = extractMemberProfileFieldFromText(text, "specialties")
1852
+ const experienceSummary = extractMemberProfileFieldFromText(text, "experienceSummary")
1853
+ const strengthsSummary = extractMemberProfileFieldFromText(text, "strengthsSummary")
1854
+ const taskPreferenceTags = extractMemberProfileFieldFromText(text, "taskPreferenceTags")
1855
+ const argv = ["member-profile-edit", "--identity", identity, "--project", input.projectId]
1856
+ if (roleTitle) argv.push("--roleTitle", roleTitle)
1857
+ if (specialties) argv.push("--specialties", specialties)
1858
+ if (experienceSummary) argv.push("--experienceSummary", experienceSummary)
1859
+ if (strengthsSummary) argv.push("--strengthsSummary", strengthsSummary)
1860
+ if (taskPreferenceTags) argv.push("--taskPreferenceTags", taskPreferenceTags)
1861
+ return argv
1743
1862
  }
1744
- if (compact.includes("成员备注") || compact.includes("备注成员") || compact.includes("给成员备注") || compact.includes("修改备注")) {
1863
+ if (
1864
+ compact.includes("成员备注") ||
1865
+ compact.includes("备注成员") ||
1866
+ compact.includes("给成员备注") ||
1867
+ compact.includes("修改备注") ||
1868
+ compact.includes("清空备注") ||
1869
+ compact.includes("删除备注")
1870
+ ) {
1745
1871
  if (!input.projectId) return ["context", "candidates", "--identity", identity]
1746
- const m = text.match(/(?:给|帮我给)?\s*([^,,\s]+)\s*(?:备注为|备注成|备注)\s*([^\n]+)$/)
1747
- const target = (m?.[1] ?? "").trim()
1748
- const remark = (m?.[2] ?? "").trim()
1872
+ const remarkIntent = extractMemberRemarkIntent(text)
1873
+ const target = String(remarkIntent?.target ?? "").trim()
1874
+ const remark = String(remarkIntent?.remark ?? "").trim()
1749
1875
  if (target && remark) {
1750
1876
  return ["panel", "member-remark", "--identity", identity, "--project", input.projectId, "--target", target, "--remark", remark]
1751
1877
  }
@@ -1953,6 +2079,8 @@ function fromText(input: SkillInput): string[] {
1953
2079
  throw new Error("confirm_required_doc_delete")
1954
2080
  }
1955
2081
  if (
2082
+ lower.includes("添加普通成员") ||
2083
+ lower.includes("新增普通成员") ||
1956
2084
  lower.includes("添加网页成员") ||
1957
2085
  lower.includes("新增网页成员") ||
1958
2086
  lower.includes("添加成员") ||
@@ -1961,14 +2089,14 @@ function fromText(input: SkillInput): string[] {
1961
2089
  ) {
1962
2090
  if (!input.projectId) return ["context", "candidates", "--identity", identity]
1963
2091
  const extractedName =
1964
- text.match(/(?:添加|新增|加入)(?:网页)?成员(?:[::\s]*|为|叫|是)?[“"']?([^”"'\n]+)[”"']?$/)?.[1]?.trim() ??
2092
+ text.match(/(?:添加|新增|加入)(?:网页|普通)?成员(?:[::\s]*|为|叫|是)?[“"']?([^”"'\n]+)[”"']?$/)?.[1]?.trim() ??
1965
2093
  text.match(/成员(?:名称|名字|昵称)?(?:为|叫|是)\s*[“"']?([^”"'\n]+)[”"']?$/)?.[1]?.trim() ??
1966
2094
  ""
1967
2095
  const memberName = String(input.memberName ?? input.title ?? extractedName).trim()
1968
2096
  if (memberName) {
1969
- return ["member", "add-web", "--identity", identity, "--project", input.projectId, "--name", memberName]
2097
+ return ["member", "add", "--identity", identity, "--project", input.projectId, "--name", memberName]
1970
2098
  }
1971
- return ["member", "add-web", "--identity", identity, "--project", input.projectId, "--interactive"]
2099
+ return ["member", "add", "--identity", identity, "--project", input.projectId, "--interactive"]
1972
2100
  }
1973
2101
  if (/移除.*成员|踢出.*成员|删除.*成员/.test(lower)) {
1974
2102
  if (!input.projectId) return ["context", "candidates", "--identity", identity]
@@ -2119,6 +2247,33 @@ function fromText(input: SkillInput): string[] {
2119
2247
  if (lower.includes("我的待办") || lower.includes("看看待办") || lower.includes("看看我的待办") || lower.includes("待办列表") || lower.includes("todo列表")) {
2120
2248
  return ["todo", "list", "--identity", identity]
2121
2249
  }
2250
+ if (
2251
+ lower.includes("成员列表") ||
2252
+ lower.includes("成员情况") ||
2253
+ lower.includes("项目成员") ||
2254
+ lower.includes("看看成员") ||
2255
+ lower.includes("查看成员")
2256
+ ) {
2257
+ if (!input.projectId) return ["context", "candidates", "--identity", identity]
2258
+ return ["member", "list", "--identity", identity, "--project", input.projectId]
2259
+ }
2260
+ if (
2261
+ lower.includes("文件列表") ||
2262
+ lower.includes("文件情况") ||
2263
+ lower.includes("项目文件") ||
2264
+ lower.includes("项目资料") ||
2265
+ lower.includes("文档列表") ||
2266
+ lower.includes("看看文件") ||
2267
+ lower.includes("查看文件") ||
2268
+ lower.includes("看看资料")
2269
+ ) {
2270
+ if (!input.projectId) return ["context", "candidates", "--identity", identity]
2271
+ return ["doc", "list", "--identity", identity, "--project", input.projectId]
2272
+ }
2273
+ if (shouldDeleteTodoFromText(lower)) {
2274
+ const actionText = buildTodoDeleteTargetFromText(text)
2275
+ return ["todo", "delete", "--identity", identity, "--actionText", actionText]
2276
+ }
2122
2277
  if (shouldCreateTodoFromText(lower)) {
2123
2278
  const todoTime = inferTodoWhenFromText(text)
2124
2279
  const actionText = buildTodoActionTextFromText(text)
@@ -2325,8 +2480,20 @@ function fromText(input: SkillInput): string[] {
2325
2480
  }
2326
2481
  if (lower.includes("上传文件") || lower.includes("上传文档") || lower.includes("新增文档")) {
2327
2482
  if (!input.projectId) return ["context", "candidates", "--identity", identity]
2328
- if (!input.file || !input.title || !input.docType) return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive"]
2329
- return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--name", input.title, "--type", input.docType, "--file", input.file]
2483
+ if (!input.file) return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive"]
2484
+ return [
2485
+ "doc",
2486
+ "upload",
2487
+ "--identity",
2488
+ identity,
2489
+ "--project",
2490
+ input.projectId,
2491
+ "--file",
2492
+ input.file,
2493
+ ...(input.title ? ["--name", input.title] : []),
2494
+ ...(input.docType ? ["--type", input.docType] : []),
2495
+ ...(input.desc ? ["--desc", input.desc] : []),
2496
+ ]
2330
2497
  }
2331
2498
  if (lower.includes("老规矩") || lower.includes("上次那套")) {
2332
2499
  if (input.projectId) return ["capsule", "recommend", "--identity", identity, "--project", input.projectId, "--query", input.query ?? ""]
@@ -2517,15 +2684,22 @@ function fromAction(input: SkillInput): string[] {
2517
2684
  case "account.invite-join-template":
2518
2685
  case "micro.invite-join-template":
2519
2686
  return ["account", "invite-join-template", "--identity", identity]
2687
+ case "member.add":
2688
+ case "member.add-member":
2689
+ case "member.add_member":
2520
2690
  case "member.add-web":
2521
2691
  case "member.add_web":
2692
+ case "project.member-add":
2693
+ case "project.member_add":
2694
+ case "project.member-add-member":
2695
+ case "project.member_add_member":
2522
2696
  case "project.member-add-web":
2523
2697
  case "project.member_add_web":
2524
2698
  if (!input.projectId) throw new Error("missing_projectId")
2525
2699
  {
2526
2700
  const memberName = String(input.memberName ?? input.title ?? input.summary ?? "").trim()
2527
- if (!memberName) throw new Error("web_member_name_required")
2528
- return ["member", "add-web", "--identity", identity, "--project", input.projectId, "--name", memberName]
2701
+ if (!memberName) throw new Error("member_name_required")
2702
+ return ["member", "add", "--identity", identity, "--project", input.projectId, "--name", memberName]
2529
2703
  }
2530
2704
  case "member.remove":
2531
2705
  if (!input.projectId) throw new Error("missing_projectId")
@@ -2592,8 +2766,8 @@ function fromAction(input: SkillInput): string[] {
2592
2766
  ]
2593
2767
  case "doc.upload":
2594
2768
  if (!input.projectId) throw new Error("missing_projectId")
2595
- if (!input.file || !input.title || !input.docType) {
2596
- return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive", ...(input.title ? ["--name", input.title] : []), ...(input.docType ? ["--type", input.docType] : []), ...(input.file ? ["--file", input.file] : [])]
2769
+ if (!input.file) {
2770
+ return ["doc", "upload", "--identity", identity, "--project", input.projectId, "--interactive", ...(input.title ? ["--name", input.title] : []), ...(input.docType ? ["--type", input.docType] : [])]
2597
2771
  }
2598
2772
  return [
2599
2773
  "doc",
@@ -2602,12 +2776,10 @@ function fromAction(input: SkillInput): string[] {
2602
2776
  identity,
2603
2777
  "--project",
2604
2778
  input.projectId,
2605
- "--name",
2606
- input.title,
2607
- "--type",
2608
- input.docType,
2609
2779
  "--file",
2610
2780
  input.file,
2781
+ ...(input.title ? ["--name", input.title] : []),
2782
+ ...(input.docType ? ["--type", input.docType] : []),
2611
2783
  ...(input.desc ? ["--desc", input.desc] : []),
2612
2784
  ]
2613
2785
  case "doc.delete":
@@ -4573,11 +4745,11 @@ function mapError(err: unknown, detailed: boolean): SkillError {
4573
4745
  if (rawError.includes("storage_quota_exceeded")) {
4574
4746
  return { ok: false, errorCode: "CLI_ERROR", message: "存储空间已满,请联系 founder 升级容量。", ...(detailed ? { rawError } : {}) }
4575
4747
  }
4576
- if (rawError.includes("web_member_name_required")) {
4577
- return { ok: false, errorCode: "CLI_ERROR", message: "请先告诉我网页成员的名称。", ...(detailed ? { rawError } : {}) }
4748
+ if (rawError.includes("member_name_required") || rawError.includes("web_member_name_required")) {
4749
+ return { ok: false, errorCode: "CLI_ERROR", message: "请先告诉我成员的名称。", ...(detailed ? { rawError } : {}) }
4578
4750
  }
4579
- if (rawError.includes("invalid_web_member_role_supported_member_only")) {
4580
- return { ok: false, errorCode: "CLI_ERROR", message: "网页成员当前仅支持 member 角色。", ...(detailed ? { rawError } : {}) }
4751
+ if (rawError.includes("invalid_member_role_supported_member_only") || rawError.includes("invalid_web_member_role_supported_member_only")) {
4752
+ return { ok: false, errorCode: "CLI_ERROR", message: "普通成员当前仅支持 member 角色。", ...(detailed ? { rawError } : {}) }
4581
4753
  }
4582
4754
  if (rawError.includes("doc_file_too_large")) {
4583
4755
  return { ok: false, errorCode: "CLI_ERROR", message: "文件上传失败:单文件超过 10MB,请压缩后重试。", ...(detailed ? { rawError } : {}) }