@my-life-buddies/cli 0.28.0-dev.1 → 0.28.0-dev.2

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/README.md CHANGED
@@ -344,7 +344,9 @@ buddy-cli-sit meta update --intro "根据食材、人数和口味推荐菜谱"
344
344
  buddy-cli-sit meta update --avatar ./avatar.png
345
345
  ```
346
346
 
347
- `meta` 只读,修改时必须带 `update` 并至少提供一个字段。多个修改参数可以组合,未传字段保持原样,包括云端已有的扩展资料。文字字段不能为空,名称最多 60 字,一句话介绍最多 200 字,完整介绍最多 10000 字。头像只上传替换,不提供清除选项;支持静态 PNG、JPEG、WebP,最大 5 MiB。Server 校验图片并生成地址,处理失败时保留原头像。
347
+ `meta` 只读,修改时必须带 `update` 并至少提供一个字段。多个修改参数可以组合,未传字段保持原样,包括云端已有的扩展资料。文字字段不能为空,名称最多 60 字,一句话介绍最多 200 字,完整介绍最多 10000 字。头像只上传替换,不提供清除选项;支持静态 PNG、JPEG、WebP,原图最大 5 MiB、宽高不超过 4096 px。Server 校验图片并生成地址,处理失败时保留原头像。
348
+
349
+ 头像上传请求最多等待 5 分钟,适用于首次登记、替换头像以及同时修改文字和头像。CLI 上传前按服务端规则居中裁切至 512×512,编码为无损 WebP,保留颜色和透明度;原始文件与本地原图草稿不变;查询、仅文字更新和兼容命令清除头像仍使用 30 秒超时。
348
350
 
349
351
  默认向上寻找 `package.json.buddyId`,也可用 `--directory ./cooking-buddy` 指定工程;图片路径始终相对执行命令的当前目录。创作阶段只有身份清单时也能使用,无需安装或启动 Runtime。工程外可直接指定平台 ID:
350
352
 
package/dist/bin/core.js CHANGED
@@ -386,7 +386,8 @@ var resolveAvatarSkill = (previous, options = {}) => resolveSkillPackage(definit
386
386
 
387
387
  // packages/cli-core/src/cloud/avatar.ts
388
388
  import { open } from "node:fs/promises";
389
- import { basename } from "node:path";
389
+ import { basename, extname } from "node:path";
390
+ import sharp from "sharp";
390
391
  var MAX_BUDDY_AVATAR_BYTES = 5 * 1024 * 1024;
391
392
  async function readBuddyAvatarFile(path) {
392
393
  const file = await open(path, "r");
@@ -410,6 +411,33 @@ async function readBuddyAvatarFile(path) {
410
411
  await file.close();
411
412
  }
412
413
  }
414
+ async function prepareBuddyAvatarUpload(source) {
415
+ const input = Buffer.from(source.bytes);
416
+ if (!input.length || input.length > MAX_BUDDY_AVATAR_BYTES) throw new Error("\u5934\u50CF\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u8D85\u8FC7 5 MiB");
417
+ if (source.mediaType === "image/png") {
418
+ for (let at = 8; at + 12 <= input.length; ) {
419
+ const length = input.readUInt32BE(at);
420
+ if (input.toString("ascii", at + 4, at + 8) === "acTL") throw new Error("\u4EC5\u652F\u6301\u9759\u6001\u5934\u50CF\uFF0C\u4E0D\u652F\u6301\u52A8\u56FE");
421
+ at += length + 12;
422
+ }
423
+ }
424
+ const image = sharp(input, { failOn: "warning", limitInputPixels: 4096 * 4096 });
425
+ let meta2;
426
+ try {
427
+ meta2 = await image.metadata();
428
+ } catch {
429
+ throw new Error("\u56FE\u7247\u65E0\u6CD5\u89E3\u7801\u6216\u50CF\u7D20\u8FC7\u5927\uFF0C\u8BF7\u9009\u62E9\u5BBD\u9AD8\u4E0D\u8D85\u8FC7 4096 px \u7684\u9759\u6001 PNG\u3001JPEG\u3001WebP");
430
+ }
431
+ if (!["png", "jpeg", "webp"].includes(meta2.format ?? "")) throw new Error("\u4EC5\u652F\u6301 PNG\u3001JPEG\u3001WebP \u56FE\u7247\u6587\u4EF6");
432
+ if ((meta2.pages ?? 1) !== 1) throw new Error("\u4EC5\u652F\u6301\u9759\u6001\u5934\u50CF\uFF0C\u4E0D\u652F\u6301\u52A8\u56FE");
433
+ if (!meta2.width || !meta2.height || meta2.width > 4096 || meta2.height > 4096) throw new Error("\u5934\u50CF\u5BBD\u9AD8\u4E0D\u80FD\u8D85\u8FC7 4096 px");
434
+ try {
435
+ const bytes = await image.rotate().resize(512, 512, { fit: "cover", position: "centre" }).webp({ lossless: true }).timeout({ seconds: 10 }).toBuffer();
436
+ return { bytes, mediaType: "image/webp", fileName: `${basename(source.fileName, extname(source.fileName))}.webp` };
437
+ } catch {
438
+ throw new Error("\u5934\u50CF\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u56FE\u7247\u662F\u5426\u5B8C\u6574\uFF0C\u6216\u6362\u4E00\u5F20\u56FE\u7247\u91CD\u8BD5");
439
+ }
440
+ }
413
441
 
414
442
  // packages/cli-core/src/process-lock.ts
415
443
  import { randomUUID } from "node:crypto";
@@ -547,9 +575,10 @@ async function save(path, content) {
547
575
  });
548
576
  }
549
577
  }
550
- async function readImage(path) {
578
+ async function readImage(path, { forUpload = false } = {}) {
551
579
  try {
552
- return await readBuddyAvatarFile(path);
580
+ const source = await readBuddyAvatarFile(path);
581
+ return forUpload ? await prepareBuddyAvatarUpload(source) : source;
553
582
  } catch (error2) {
554
583
  throw new MetaPreparationError(`\u5934\u50CF\u672A\u5B8C\u6210\uFF1A${error2 instanceof Error ? error2.message : "\u65E0\u6CD5\u8BFB\u53D6\u56FE\u7247"}\u3002\u672C\u5730\u8D44\u6599\u5DF2\u4FDD\u7559\uFF0C\u5C1A\u672A\u63D0\u4EA4\u8FD9\u5F20\u5934\u50CF\u3002`);
555
584
  }
@@ -679,7 +708,7 @@ async function prepareLockedMeta(options) {
679
708
  io.write(`\u5934\u50CF\u672A\u5B8C\u6210\uFF0C\u5DF2\u4FDD\u5B58\u672C\u5730\u8D44\u6599\uFF0C\u5C1A\u672A\u521B\u5EFA\u4E91\u7AEF\u642D\u5B50\u3002\u8BF7\u8BA9 Coding Agent \u8BFB\u53D6 ${join4(folder(root), "HANDOFF.md")}\uFF1B\u6216\u4F7F\u7528 --avatar <\u56FE\u7247\u8DEF\u5F84> \u7EE7\u7EED\u3002`);
680
709
  return void 0;
681
710
  }
682
- const upload = await readImage(join4(folder(root), draft.avatarPath));
711
+ const upload = await readImage(join4(folder(root), draft.avatarPath), { forUpload: true });
683
712
  const confirmation = hash(JSON.stringify([profile.name, profile.tagline, profile.intro, hash(upload.bytes)]));
684
713
  const preview = join4(folder(root), "preview.html");
685
714
  await save(preview, `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>\u786E\u8BA4\u642D\u5B50\u8D44\u6599\u4E0E\u5934\u50CF</title><style>body{font:18px system-ui;max-width:800px;margin:48px auto;padding:24px;color:#163342}p{white-space:pre-wrap}section{display:flex;gap:24px}figure{margin:0;padding:24px;background:#eef4f8;border-radius:12px}figure.dark{background:#183040}img{width:220px;height:220px;object-fit:contain;border-radius:50%}</style><h1>${escape(profile.name)}</h1><p>${escape(profile.tagline)}</p><p>${escape(profile.intro)}</p><section><figure><img alt="\u6D45\u8272\u80CC\u666F\u5934\u50CF" src="data:${upload.mediaType};base64,${Buffer.from(upload.bytes).toString("base64")}"></figure><figure class="dark"><img alt="\u6DF1\u8272\u80CC\u666F\u5934\u50CF" src="data:${upload.mediaType};base64,${Buffer.from(upload.bytes).toString("base64")}"></figure></section><p>\u67E5\u770B\u5934\u50CF\u548C\u8D44\u6599\u540E\uFF0C\u56DE\u5230\u7EC8\u7AEF\u786E\u8BA4\u3002\u6B64\u9875\u9762\u4E0D\u4F1A\u4E0A\u4F20\u4EFB\u4F55\u5185\u5BB9\u3002</p></html>`);
@@ -3425,6 +3454,7 @@ direct \u751F\u6210\u65B0\u4E1A\u52A1\u5DE5\u5382\u524D\uFF0C\u6309\u6700\u65B0
3425
3454
  \u540C\u76EE\u5F55\u6062\u590D\u4FDD\u7559\u5DF2\u6709\u4E1A\u52A1\u6587\u4EF6\u3002create \u540E\u6309 .buddy/creator/HANDOFF.md \u7EE7\u7EED\uFF0Cdirect \u540E\u5B89\u88C5\u4F9D\u8D56\u5E76\u5B9E\u73B0\u4E1A\u52A1\u3002
3426
3455
  Creator \u6BCF\u6B21\u8FDB\u5165\u6216\u6062\u590D\u90FD\u67E5\u8BE2\u516C\u5171 npm latest\uFF1B\u6709 Creator \u8BB0\u5F55\u7684\u5B8C\u6574\u5DE5\u7A0B\u4E5F\u53EF\u6267\u884C create \u63A5\u7EED\u521B\u4F5C\uFF0C\u4E0D\u91CD\u5EFA\u4E1A\u52A1\u6587\u4EF6\u3002
3427
3456
  \u5DF2\u767B\u8BB0\u642D\u5B50\u7684\u540D\u79F0\u3001\u4ECB\u7ECD\u6216\u5934\u50CF\u7528 buddy-cli-sit meta update \u4FEE\u6539\uFF1Binit \u4E0D\u8986\u76D6\u4E91\u7AEF\u8D44\u6599\u3002
3457
+ \u5934\u50CF\u4E0A\u4F20\u8BF7\u6C42\u6700\u591A\u7B49\u5F85 5 \u5206\u949F\uFF0CCLI \u4E0A\u4F20\u524D\u5C45\u4E2D\u88C1\u5207\u81F3 512\xD7512 \u65E0\u635F WebP\uFF0C\u4FDD\u7559\u989C\u8272\u3001\u900F\u660E\u5EA6\u4E0E\u539F\u59CB\u6587\u4EF6\uFF1B\u666E\u901A\u8BF7\u6C42\u4ECD\u4E3A 30 \u79D2\u3002
3428
3458
 
3429
3459
  \u793A\u4F8B:
3430
3460
  buddy-cli-sit init --mode direct --directory ./my-buddy --name "\u505A\u996D\u642D\u5B50" --tagline "\u5E2E\u4F60\u5B89\u6392\u665A\u996D" --intro "\u6839\u636E\u73B0\u6709\u98DF\u6750\u63A8\u8350\u83DC\u8C31"
@@ -6060,7 +6090,10 @@ var HttpDeveloperPlatformClient = class {
6060
6090
  const form = new FormData();
6061
6091
  form.set("meta", JSON.stringify(request.meta));
6062
6092
  form.set("avatar", new Blob([new Uint8Array(request.avatar.bytes)], { type: request.avatar.mediaType }), request.avatar.fileName);
6063
- return buddy(await this.#request(`/cli/buddies/${encodeURIComponent(request.id)}/meta`, "PUT", { form }), true);
6093
+ return buddy(await this.#request(`/cli/buddies/${encodeURIComponent(request.id)}/meta`, "PUT", {
6094
+ form,
6095
+ timeoutMs: this.#uploadTimeoutMs
6096
+ }), true);
6064
6097
  }
6065
6098
  return buddy(await this.#request(`/cli/buddies/${encodeURIComponent(request.id)}/meta`, "PUT", {
6066
6099
  json: request.meta
@@ -6285,6 +6318,7 @@ var HELP = Object.freeze({
6285
6318
  meta \u53EA\u67E5\u770B\u4E91\u7AEF\u8D44\u6599\uFF1Bmeta update \u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A\u4FEE\u6539\u5B57\u6BB5\uFF0C\u672A\u4F20\u5B57\u6BB5\u4FDD\u6301\u539F\u6837\u3002
6286
6319
  \u6587\u5B57\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A\uFF1B\u540D\u79F0\u6700\u591A ${BUDDY_PROFILE_TEXT_LIMITS.name} \u5B57\uFF0C\u4E00\u53E5\u8BDD\u4ECB\u7ECD\u6700\u591A ${BUDDY_PROFILE_TEXT_LIMITS.tagline} \u5B57\uFF0C\u5B8C\u6574\u4ECB\u7ECD\u6700\u591A ${BUDDY_PROFILE_TEXT_LIMITS.intro} \u5B57\u3002
6287
6320
  --avatar \u4E0A\u4F20\u66FF\u6362\u5934\u50CF\uFF1A\u672C\u5730\u9759\u6001 PNG\u3001JPEG\u3001WebP\uFF0C\u6700\u5927 5 MiB\uFF1B\u4E0D\u63D0\u4F9B\u6E05\u9664\u5934\u50CF\u9009\u9879\u3002
6321
+ \u5934\u50CF\u4E0A\u4F20\u8BF7\u6C42\u6700\u591A\u7B49\u5F85 5 \u5206\u949F\uFF0CCLI \u4E0A\u4F20\u524D\u5C45\u4E2D\u88C1\u5207\u81F3 512\xD7512 \u65E0\u635F WebP\uFF0C\u4FDD\u7559\u989C\u8272\u3001\u900F\u660E\u5EA6\u4E0E\u539F\u59CB\u6587\u4EF6\uFF1B\u67E5\u8BE2\u548C\u4EC5\u6587\u5B57\u66F4\u65B0\u4ECD\u4E3A 30 \u79D2\u3002
6288
6322
  \u9ED8\u8BA4\u8BFB\u53D6\u5F53\u524D\u5DE5\u7A0B\u7684 package.json.buddyId\uFF1B--buddy-id \u4E0E --directory \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528\u3002\u56FE\u7247\u8DEF\u5F84\u76F8\u5BF9\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u3002
6289
6323
  \u9700\u8981\u5F53\u524D\u8D26\u53F7\u62E5\u6709\u8BE5\u642D\u5B50\uFF1B\u66F4\u65B0\u7ACB\u5373\u5F71\u54CD\u4E91\u7AEF\u5C55\u793A\u8D44\u6599\uFF0C\u65E0\u9700 Runtime\u3001push \u6216\u91CD\u65B0\u53D1\u5E03\u7A0B\u5E8F\u5305\u3002
6290
6324
  \u793A\u4F8B: buddy-cli-sit meta update --name "\u5DDD\u5927\u53A8" --tagline "\u5E2E\u4F60\u5B89\u6392\u6BCF\u5929\u5403\u4EC0\u4E48" --avatar ./avatar.png`,
@@ -6292,6 +6326,7 @@ meta \u53EA\u67E5\u770B\u4E91\u7AEF\u8D44\u6599\uFF1Bmeta update \u81F3\u5C11\u6
6292
6326
  \u65E7\u547D\u4EE4\u517C\u5BB9\u5165\u53E3\uFF1B\u66F4\u6362\u5934\u50CF\u63A8\u8350\u4F7F\u7528 buddy-cli-sit meta update --avatar <\u56FE\u7247\u6587\u4EF6>\u3002
6293
6327
  \u4FEE\u6539\u5F53\u524D\u9879\u76EE\u6216\u6307\u5B9A\u642D\u5B50\u7684\u4E91\u7AEF\u5934\u50CF\uFF0C\u4FDD\u7559\u5176\u4ED6\u8D44\u6599\uFF1B\u4E0D\u9700\u8981\u5B89\u88C5 Runtime \u6216\u4E0A\u4F20\u4EE3\u7801\u5305\u3002
6294
6328
  --file \u63A5\u53D7\u672C\u5730\u9759\u6001 PNG\u3001JPEG\u3001WebP \u56FE\u7247\uFF0C\u6700\u5927 5 MiB\uFF1BServer \u81EA\u52A8\u5904\u7406\u5E76\u751F\u6210\u5730\u5740\u3002
6329
+ \u5934\u50CF\u4E0A\u4F20\u8BF7\u6C42\u6700\u591A\u7B49\u5F85 5 \u5206\u949F\uFF0CCLI \u4E0A\u4F20\u524D\u5C45\u4E2D\u88C1\u5207\u81F3 512\xD7512 \u65E0\u635F WebP\uFF0C\u4FDD\u7559\u989C\u8272\u3001\u900F\u660E\u5EA6\u4E0E\u539F\u59CB\u6587\u4EF6\uFF1B\u6E05\u9664\u5934\u50CF\u4ECD\u4E3A 30 \u79D2\u3002
6295
6330
  \u9700\u8981\u5F53\u524D\u8D26\u53F7\u62E5\u6709\u8BE5\u642D\u5B50\uFF0C\u547D\u4EE4\u6267\u884C\u540E\u7ACB\u5373\u66F4\u65B0\u4E91\u7AEF\u8D44\u6599\u3002`
6296
6331
  });
6297
6332
  function parse3(args, command) {
@@ -6420,7 +6455,7 @@ async function runMeta(parsed, io, options, legacyAvatar = false) {
6420
6455
  }
6421
6456
  let upload;
6422
6457
  try {
6423
- upload = file ? await readBuddyAvatarFile(resolve16(options.cwd ?? process.cwd(), file)) : void 0;
6458
+ upload = file ? await prepareBuddyAvatarUpload(await readBuddyAvatarFile(resolve16(options.cwd ?? process.cwd(), file))) : void 0;
6424
6459
  } catch (error2) {
6425
6460
  throw new CloudCommandUsageError(error2 instanceof Error ? error2.message : "\u65E0\u6CD5\u8BFB\u53D6\u5934\u50CF\u56FE\u7247");
6426
6461
  }
@@ -6518,7 +6553,7 @@ var CLOUD_COMMAND_HELP = HELP;
6518
6553
 
6519
6554
  // packages/cli-core/src/migration/index.ts
6520
6555
  import { lstat as lstat12, open as open5, readdir as readdir6, realpath as realpath7, stat } from "node:fs/promises";
6521
- import { extname, isAbsolute as isAbsolute3, join as join22, relative, resolve as resolve17, sep, win32 as win322 } from "node:path";
6556
+ import { extname as extname2, isAbsolute as isAbsolute3, join as join22, relative, resolve as resolve17, sep, win32 as win322 } from "node:path";
6522
6557
  var DEFAULT_SKILL_SCAN_LIMITS = Object.freeze({
6523
6558
  maxFiles: 128,
6524
6559
  maxFileBytes: 256 * 1024,
@@ -6950,7 +6985,7 @@ function frontmatterEnd(lines) {
6950
6985
  return -1;
6951
6986
  }
6952
6987
  function shellLines(lines, path) {
6953
- const extension = extname(path).toLowerCase();
6988
+ const extension = extname2(path).toLowerCase();
6954
6989
  const shebang = lines[0] ?? "";
6955
6990
  if (SHELL_EXTENSIONS.has(extension) || /^#!.*(?:[/\s])(?:bash|fish|powershell|pwsh|sh|zsh)(?:\s|$)/u.test(shebang)) return lines.map(() => true);
6956
6991
  const result = lines.map(() => false);
@@ -7078,7 +7113,7 @@ function analyzeText(source, path, context) {
7078
7113
  const lines = source.split(/\r\n|\n|\r/u);
7079
7114
  const metadataEnd = frontmatterEnd(lines);
7080
7115
  const shellContext = shellLines(lines, path);
7081
- const extension = extname(path).toLowerCase();
7116
+ const extension = extname2(path).toLowerCase();
7082
7117
  const localShellVariables = /* @__PURE__ */ new Set();
7083
7118
  const localBatchVariables = /* @__PURE__ */ new Set();
7084
7119
  const localPowerShellEnvironment = /* @__PURE__ */ new Set();