@casatwy/deyo 0.2.1 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casatwy/deyo",
3
- "version": "0.2.1",
3
+ "version": "0.2.5",
4
4
  "description": "Deyo transcription CLI with link/file input, JSONL progress, and opt-in stable transcript streaming",
5
5
  "homepage": "https://deyo.miaobi.fun/docs",
6
6
  "license": "UNLICENSED",
@@ -8,6 +8,7 @@
8
8
  "files": [
9
9
  "deyo.mjs",
10
10
  "skill-manager.mjs",
11
+ "openclaw-skill-update.mjs",
11
12
  "README.md"
12
13
  ],
13
14
  "keywords": [
@@ -28,4 +29,4 @@
28
29
  "bin": {
29
30
  "deyo": "deyo.mjs"
30
31
  }
31
- }
32
+ }
package/skill-manager.mjs CHANGED
@@ -16,6 +16,13 @@ import { createHash, randomBytes } from 'node:crypto'
16
16
  import { homedir } from 'node:os'
17
17
  import { basename, dirname, isAbsolute, join, normalize, resolve } from 'node:path'
18
18
  import process from 'node:process'
19
+ import {
20
+ CONFIRM_LATEST_PHRASE,
21
+ enrollOpenClawAutoUpdate,
22
+ inspectOpenClawAutoUpdate,
23
+ runOpenClawConfirmedUpdate,
24
+ runOpenClawInvocationCheck,
25
+ } from './openclaw-skill-update.mjs'
19
26
 
20
27
  export const SKILL_PLATFORMS = ['codex', 'claude', 'gemini', 'openclaw']
21
28
  export const SKILL_SCOPES = ['user', 'workspace', 'global']
@@ -189,13 +196,17 @@ export function printSkillUsage(stream = process.stdout) {
189
196
  [--replace-legacy sha256:<observed-hash>]
190
197
  [--json]
191
198
  deyo skill status [--platform codex|claude|gemini|openclaw] [--json]
199
+ deyo skill update --platform openclaw [--scope workspace|global]
200
+ --confirm-latest 'update @casatwy/deyo to latest now'
192
201
 
193
202
  默认 scope:
194
203
  codex=user,claude=user,gemini=user,openclaw=global
195
204
 
196
205
  说明:
197
206
  status 只读取本地配置和 manifest,不联网、不检查更新,也不修改 cache/lockfile。
198
- install 只调用各平台原生管理器;不会用 --force、--all、固定 --ref 或 --consent。
207
+ install 只调用各平台原生管理器;OpenClaw 安装成功后登记调用时检查,不会自动更新已有安装。
208
+ update 仅用于当前用户明确同意后更新 OpenClaw;执行前会重新验证 latest,候选变化时必须重新确认。
209
+ 所有原生命令都不会使用 --force、--all、--force-install、风险确认、固定 --ref 或 --consent。
199
210
  legacy 目录只有在完整树哈希属于官方历史版本,且 --replace-legacy 精确匹配时才会备份迁移。
200
211
  `)
201
212
  }
@@ -208,6 +219,7 @@ function parseSkillCommandArgs(argv) {
208
219
  platform: '',
209
220
  scope: '',
210
221
  replaceLegacy: '',
222
+ confirmLatest: '',
211
223
  json: false,
212
224
  help: requestedAction === '-h' || requestedAction === '--help',
213
225
  }
@@ -257,13 +269,19 @@ function parseSkillCommandArgs(argv) {
257
269
  index += 1
258
270
  continue
259
271
  }
272
+ if (current === '--confirm-latest') {
273
+ markSeen(current)
274
+ result.confirmLatest = readOptionValue(current, index)
275
+ index += 1
276
+ continue
277
+ }
260
278
  throw new SkillManagerError(`未知 skill 参数:${current}`, 'unknown_argument')
261
279
  }
262
280
 
263
281
  if (!action && !result.help) {
264
282
  throw new SkillManagerError('缺少 skill 子命令 install 或 status', 'missing_action')
265
283
  }
266
- if (!['install', 'status'].includes(action) && !result.help) {
284
+ if (!['install', 'status', 'update', '_openclaw-check'].includes(action) && !result.help) {
267
285
  throw new SkillManagerError(`不支持的 skill 子命令:${action}`, 'invalid_action')
268
286
  }
269
287
  if (action === 'install' && !result.platform && !result.help) {
@@ -275,6 +293,38 @@ function parseSkillCommandArgs(argv) {
275
293
  if (action === 'status' && result.replaceLegacy) {
276
294
  throw new SkillManagerError('skill status 不接受 --replace-legacy', 'status_replace_legacy')
277
295
  }
296
+ if (action === 'status' && result.confirmLatest) {
297
+ throw new SkillManagerError('skill status 不接受 --confirm-latest', 'status_confirm_latest')
298
+ }
299
+ if (action === 'install' && result.confirmLatest) {
300
+ throw new SkillManagerError('skill install 不接受 --confirm-latest', 'install_confirm_latest')
301
+ }
302
+ if (action === 'update' && result.platform !== 'openclaw') {
303
+ throw new SkillManagerError('skill update 目前只支持 --platform openclaw', 'update_platform')
304
+ }
305
+ if (action === 'update' && result.replaceLegacy) {
306
+ throw new SkillManagerError('skill update 不接受 --replace-legacy', 'update_replace_legacy')
307
+ }
308
+ if (action === 'update' && result.json) {
309
+ throw new SkillManagerError('skill update 不接受 --json;更新输出不会写入 stdout', 'update_json')
310
+ }
311
+ if (action === 'update' && !result.confirmLatest) {
312
+ throw new SkillManagerError('skill update 必须提供 --confirm-latest', 'confirmation_required')
313
+ }
314
+ if (action === 'update' && result.confirmLatest !== CONFIRM_LATEST_PHRASE) {
315
+ throw new SkillManagerError(
316
+ `--confirm-latest 必须精确为 '${CONFIRM_LATEST_PHRASE}'`,
317
+ 'confirmation_required',
318
+ )
319
+ }
320
+ if (action === '_openclaw-check') {
321
+ if (result.platform || result.replaceLegacy || result.confirmLatest || result.json) {
322
+ throw new SkillManagerError('内部 OpenClaw 检查只接受 --scope', 'internal_check_arguments')
323
+ }
324
+ if (!result.scope) {
325
+ throw new SkillManagerError('内部 OpenClaw 检查必须提供 --scope', 'missing_scope')
326
+ }
327
+ }
278
328
 
279
329
  return result
280
330
  }
@@ -1062,7 +1112,7 @@ async function inspectOpenClawSkill(scope) {
1062
1112
  }
1063
1113
  }
1064
1114
 
1065
- async function buildOpenClawInstallPlan(scope, jsonMode) {
1115
+ async function buildOpenClawInstallPlan(scope, jsonMode, cliVersion) {
1066
1116
  const openclaw = await requireExecutable('openclaw')
1067
1117
  const skill = await inspectOpenClawSkill(scope)
1068
1118
  if (skill.managed && !skill.officialSource) {
@@ -1074,24 +1124,41 @@ async function buildOpenClawInstallPlan(scope, jsonMode) {
1074
1124
 
1075
1125
  return async () => {
1076
1126
  const scopeArgs = scope === 'global' ? ['--global'] : []
1127
+ const actions = []
1077
1128
  if (skill.managed) {
1129
+ actions.push('already_installed')
1130
+ } else {
1078
1131
  await runManager(openclaw, [
1079
- 'skills', 'update', OPENCLAW_SKILL, ...scopeArgs,
1132
+ 'skills', 'install', OPENCLAW_SKILL, ...scopeArgs,
1080
1133
  ], { interactive: true, jsonMode })
1081
- return { actions: ['skill_updated'], activation: 'invoke_again_if_updated' }
1134
+ actions.push('skill_installed')
1082
1135
  }
1083
- await runManager(openclaw, [
1084
- 'skills', 'install', OPENCLAW_SKILL, ...scopeArgs,
1085
- ], { interactive: true, jsonMode })
1086
- return { actions: ['skill_installed'], activation: 'next_invocation' }
1136
+ const installed = await inspectOpenClawSkill(scope)
1137
+ if (!installed.installed || !installed.officialSource) {
1138
+ throw new SkillManagerError(
1139
+ 'OpenClaw 原生安装已返回,但无法从当前 scope 的 origin.json 确认 @casatwy/deyo',
1140
+ 'openclaw_origin_unverified',
1141
+ )
1142
+ }
1143
+ let autoUpdate
1144
+ try {
1145
+ autoUpdate = await enrollOpenClawAutoUpdate({ scope, cliVersion })
1146
+ } catch (error) {
1147
+ throw new SkillManagerError(
1148
+ `OpenClaw Skill 已处理,但调用时更新检查登记失败:${error instanceof Error ? error.message : String(error)}`,
1149
+ 'openclaw_enrollment_failed',
1150
+ )
1151
+ }
1152
+ actions.push('invocation_update_check_enrolled')
1153
+ return { actions, activation: 'next_invocation', autoUpdate }
1087
1154
  }
1088
1155
  }
1089
1156
 
1090
- async function buildPlatformInstallPlan(platform, scope, jsonMode) {
1157
+ async function buildPlatformInstallPlan(platform, scope, jsonMode, cliVersion) {
1091
1158
  if (platform === 'codex') return await buildCodexInstallPlan(jsonMode)
1092
1159
  if (platform === 'claude') return await buildClaudeInstallPlan(scope, jsonMode)
1093
1160
  if (platform === 'gemini') return await buildGeminiInstallPlan(jsonMode)
1094
- return await buildOpenClawInstallPlan(scope, jsonMode)
1161
+ return await buildOpenClawInstallPlan(scope, jsonMode, cliVersion)
1095
1162
  }
1096
1163
 
1097
1164
  function extractTomlSection(content, sectionName) {
@@ -1187,12 +1254,19 @@ async function inspectPlatformManaged(platform, scope) {
1187
1254
  }
1188
1255
  }
1189
1256
  const skill = await inspectOpenClawSkill(scope)
1257
+ const update = await inspectOpenClawAutoUpdate({
1258
+ scope,
1259
+ ...(scope === 'workspace' ? { cwd: process.cwd() } : {}),
1260
+ })
1190
1261
  return {
1191
1262
  installed: skill.installed,
1192
1263
  version: skill.version,
1193
1264
  source: skill.origin,
1194
1265
  officialSource: skill.officialSource,
1195
- autoUpdate: 'throttled_on_invocation',
1266
+ autoUpdate: update.enrollment === 'enrolled'
1267
+ ? 'confirmation_required_on_invocation'
1268
+ : update.enrollment,
1269
+ autoUpdateState: update,
1196
1270
  installPath: skill.path,
1197
1271
  }
1198
1272
  }
@@ -1216,7 +1290,7 @@ async function getSkillStatus(platform) {
1216
1290
  }
1217
1291
  }
1218
1292
 
1219
- async function installSkill(options) {
1293
+ async function installSkill(options, cliVersion) {
1220
1294
  validatePlatform(options.platform)
1221
1295
  const scope = options.scope || getDefaultScope(options.platform)
1222
1296
  validateScope(options.platform, scope)
@@ -1224,7 +1298,7 @@ async function installSkill(options) {
1224
1298
 
1225
1299
  // All source-conflict and manager checks happen before the legacy directory
1226
1300
  // is moved or any provider configuration is mutated.
1227
- const execute = await buildPlatformInstallPlan(options.platform, scope, options.json)
1301
+ const execute = await buildPlatformInstallPlan(options.platform, scope, options.json, cliVersion)
1228
1302
  const legacy = await inspectLegacy(options.platform, scope)
1229
1303
  let migration = null
1230
1304
  try {
@@ -1290,6 +1364,7 @@ function printStatusResult(result) {
1290
1364
 
1291
1365
  export async function handleSkillCommand(argv, cliVersion) {
1292
1366
  const wantsJson = argv.includes('--json')
1367
+ const isolatesStdout = argv[1] === 'update' || argv[1] === '_openclaw-check'
1293
1368
  try {
1294
1369
  const args = parseSkillCommandArgs(argv)
1295
1370
  if (args.help) {
@@ -1299,13 +1374,32 @@ export async function handleSkillCommand(argv, cliVersion) {
1299
1374
  if (args.platform) validatePlatform(args.platform)
1300
1375
 
1301
1376
  if (args.action === 'install') {
1302
- const result = await installSkill(args)
1377
+ const result = await installSkill(args, cliVersion)
1303
1378
  const output = { cliVersion, ...result }
1304
1379
  if (args.json) process.stdout.write(`${JSON.stringify(output)}\n`)
1305
1380
  else printInstallResult(output)
1306
1381
  return
1307
1382
  }
1308
1383
 
1384
+ if (args.action === '_openclaw-check') {
1385
+ const scope = args.scope
1386
+ validateScope('openclaw', scope)
1387
+ const result = await runOpenClawInvocationCheck({ scope })
1388
+ if (result.message) process.stderr.write(`${result.message}\n`)
1389
+ return result.exitCode
1390
+ }
1391
+
1392
+ if (args.action === 'update') {
1393
+ const scope = args.scope || getDefaultScope('openclaw')
1394
+ validateScope('openclaw', scope)
1395
+ const result = await runOpenClawConfirmedUpdate({
1396
+ scope,
1397
+ confirmation: args.confirmLatest,
1398
+ })
1399
+ if (result.message) process.stderr.write(`${result.message}\n`)
1400
+ return result.exitCode
1401
+ }
1402
+
1309
1403
  const platforms = args.platform ? [args.platform] : SKILL_PLATFORMS
1310
1404
  const result = {
1311
1405
  command: 'skill.status',
@@ -1318,13 +1412,14 @@ export async function handleSkillCommand(argv, cliVersion) {
1318
1412
  }
1319
1413
  if (args.json) process.stdout.write(`${JSON.stringify(result)}\n`)
1320
1414
  else printStatusResult(result)
1415
+ return 0
1321
1416
  } catch (error) {
1322
1417
  const payload = {
1323
1418
  command: 'skill.error',
1324
1419
  error: error?.code || 'skill_error',
1325
1420
  message: error instanceof Error ? error.message : String(error),
1326
1421
  }
1327
- if (wantsJson) process.stdout.write(`${JSON.stringify(payload)}\n`)
1422
+ if (wantsJson && !isolatesStdout) process.stdout.write(`${JSON.stringify(payload)}\n`)
1328
1423
  else process.stderr.write(`Deyo Skill 操作失败:${payload.message}\n`)
1329
1424
  throw new SkillCommandReportedError(1)
1330
1425
  }