cortico-world-hands 0.20.2 → 0.20.3

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.
Files changed (2) hide show
  1. package/dist/index.js +183 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -233,9 +233,9 @@ var HANDS_LOOK_CONFIG_GROUP = {
233
233
  };
234
234
 
235
235
  // src/world.ts
236
- import { writeFileSync as writeFileSync2 } from "node:fs";
236
+ import { writeFileSync as writeFileSync4 } from "node:fs";
237
237
  import { fileURLToPath } from "node:url";
238
- import { join as join2 } from "node:path";
238
+ import { join as join4 } from "node:path";
239
239
 
240
240
  // src/policy.ts
241
241
  var PASS_RULES = [
@@ -513,9 +513,167 @@ var Honcho = class {
513
513
  }
514
514
  };
515
515
 
516
- // src/stores.ts
517
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
516
+ // src/sync.ts
517
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
518
518
  import { join } from "node:path";
519
+ var START = "<!--yixin:sync:start-->";
520
+ var END = "<!--yixin:sync:end-->";
521
+ function readMaybe(path) {
522
+ try {
523
+ return existsSync(path) ? readFileSync(path, "utf8") : "";
524
+ } catch {
525
+ return "";
526
+ }
527
+ }
528
+ function mergeMemorySection(file, rows) {
529
+ const body = rows.filter((r) => r && r.key && r.value).map((r) => `- **${r.key}**\uFF1A${r.value.replace(/\s+/g, " ").trim()}`).join("\n");
530
+ const section = `${START}
531
+ ## \u4ECE\u61FF\u5FC3\u540C\u6B65\u8FC7\u6765\u7684\u8BB0\u5FC6\uFF08${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}\uFF09
532
+ ${body}
533
+ ${END}`;
534
+ const old = readMaybe(file);
535
+ let next;
536
+ if (old.includes(START) && old.includes(END)) {
537
+ next = old.slice(0, old.indexOf(START)) + section + old.slice(old.indexOf(END) + END.length);
538
+ } else {
539
+ next = (old ? old.trimEnd() + "\n\n" : "") + section + "\n";
540
+ }
541
+ if (next === old) return { lines: rows.length, wrote: false };
542
+ writeFileSync(file, next);
543
+ return { lines: rows.length, wrote: true };
544
+ }
545
+ function readPersona(dir) {
546
+ for (const name of ["CONSTITUTION.md", "constitution.md"]) {
547
+ const text = readMaybe(join(dir, name));
548
+ if (text.trim()) return text;
549
+ }
550
+ return "";
551
+ }
552
+
553
+ // src/skin.ts
554
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, unlinkSync, writeFileSync as writeFileSync2, copyFileSync } from "node:fs";
555
+ import { dirname, join as join2, resolve } from "node:path";
556
+ function findWhaleDir(roots) {
557
+ for (const root of roots) {
558
+ const candidates = [
559
+ join2(root, "web", "whale"),
560
+ join2(root, "pet", "web", "whale"),
561
+ join2(root, "vendor", "pet", "web", "whale")
562
+ ];
563
+ for (const dir of candidates) {
564
+ if (existsSync2(join2(dir, "model.json"))) return dir;
565
+ }
566
+ }
567
+ return null;
568
+ }
569
+ async function fetchBytes(url) {
570
+ const res = await fetch(url);
571
+ if (!res.ok) throw new Error(`\u4E0B\u8F7D\u5931\u8D25 ${res.status} ${url}`);
572
+ return Buffer.from(await res.arrayBuffer());
573
+ }
574
+ async function installSkinPack(baseUrl, roots, log) {
575
+ const base = baseUrl.replace(/\/+$/, "");
576
+ let manifest;
577
+ try {
578
+ manifest = JSON.parse((await fetchBytes(`${base}/manifest.json`)).toString("utf8"));
579
+ } catch (err) {
580
+ return { ok: false, detail: `\u62FF\u4E0D\u5230 manifest\uFF1A${err.message}` };
581
+ }
582
+ if (!manifest?.id || !Array.isArray(manifest.files)) {
583
+ return { ok: false, detail: "manifest \u683C\u5F0F\u4E0D\u5BF9\uFF1A\u8981 id \u548C files" };
584
+ }
585
+ const whaleDir = findWhaleDir(roots);
586
+ if (!whaleDir) {
587
+ return { ok: false, detail: `\u6CA1\u627E\u5230 web/whale/model.json\uFF0C\u627E\u8FC7\uFF1A${roots.join(" / ")}` };
588
+ }
589
+ try {
590
+ for (const file of manifest.files) {
591
+ const target = resolve(whaleDir, file.path);
592
+ if (!target.startsWith(resolve(whaleDir))) {
593
+ return { ok: false, detail: `\u8DEF\u5F84\u8D8A\u754C\uFF1A${file.path}` };
594
+ }
595
+ mkdirSync(dirname(target), { recursive: true });
596
+ writeFileSync2(target, await fetchBytes(`${base}/${file.path}`));
597
+ }
598
+ const modelPath = join2(whaleDir, "model.json");
599
+ const model = JSON.parse(readFileSync2(modelPath, "utf8"));
600
+ const outfits = Array.isArray(model.outfits) ? model.outfits : [];
601
+ const idx = outfits.findIndex((o) => o?.id === manifest.id);
602
+ if (idx >= 0) outfits[idx] = manifest.outfit;
603
+ else outfits.push(manifest.outfit);
604
+ model.outfits = outfits;
605
+ copyFileSync(modelPath, `${modelPath}.bak-${Date.now()}`);
606
+ writeFileSync2(modelPath, JSON.stringify(model, null, 2));
607
+ log?.info(`\u76AE\u80A4\u88C5\u597D\u4E86\uFF1A${manifest.id}\uFF08web \u76EE\u5F55 ${whaleDir}\uFF09`);
608
+ return { ok: true, detail: `\u88C5\u597D\u4E86\u300C${manifest.name || manifest.id}\u300D\uFF0C\u5171 ${manifest.files.length} \u4E2A\u6587\u4EF6`, webDir: whaleDir };
609
+ } catch (err) {
610
+ log?.warn(`\u76AE\u80A4\u5B89\u88C5\u5931\u8D25\uFF1A${err.message}`);
611
+ return { ok: false, detail: `\u88C5\u5230\u4E00\u534A\u5931\u8D25\uFF1A${err.message}` };
612
+ }
613
+ }
614
+ function cleanMedia(mediaDir, opts = {}) {
615
+ const keepDays = opts.keepDays ?? 1;
616
+ const maxMB = opts.maxMB ?? 0;
617
+ const out = { removed: 0, freedMB: 0, kept: 0, dir: mediaDir };
618
+ let names = [];
619
+ try {
620
+ names = readdirSync(mediaDir);
621
+ } catch {
622
+ return out;
623
+ }
624
+ const files = [];
625
+ for (const name of names) {
626
+ const full = join2(mediaDir, name);
627
+ try {
628
+ const st = statSync(full);
629
+ if (!st.isFile()) continue;
630
+ if (!/\.(jpg|jpeg|png|webp)$/i.test(name)) continue;
631
+ files.push({ name, full, size: st.size, mtime: st.mtimeMs });
632
+ } catch {
633
+ }
634
+ }
635
+ const cutoff = Date.now() - keepDays * 864e5;
636
+ let totalMB = files.reduce((sum, f) => sum + f.size, 0) / 1024 / 1024;
637
+ for (const f of files) {
638
+ if (f.mtime >= cutoff) continue;
639
+ if (!opts.dryRun) {
640
+ try {
641
+ unlinkSync(f.full);
642
+ } catch {
643
+ continue;
644
+ }
645
+ }
646
+ out.removed += 1;
647
+ out.freedMB += f.size / 1024 / 1024;
648
+ totalMB -= f.size / 1024 / 1024;
649
+ }
650
+ if (maxMB > 0 && totalMB > maxMB) {
651
+ const rest = files.filter((f) => f.mtime >= cutoff).sort((a, b) => a.mtime - b.mtime);
652
+ for (const f of rest) {
653
+ if (totalMB <= maxMB) break;
654
+ if (!opts.dryRun) {
655
+ try {
656
+ unlinkSync(f.full);
657
+ } catch {
658
+ continue;
659
+ }
660
+ }
661
+ out.removed += 1;
662
+ out.freedMB += f.size / 1024 / 1024;
663
+ totalMB -= f.size / 1024 / 1024;
664
+ }
665
+ }
666
+ try {
667
+ out.kept = readdirSync(mediaDir).filter((n) => /\.(jpg|jpeg|png|webp)$/i.test(n)).length;
668
+ } catch {
669
+ }
670
+ out.freedMB = Math.round(out.freedMB * 10) / 10;
671
+ return out;
672
+ }
673
+
674
+ // src/stores.ts
675
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
676
+ import { join as join3 } from "node:path";
519
677
  var JsonStore = class {
520
678
  file;
521
679
  fallback;
@@ -523,16 +681,16 @@ var JsonStore = class {
523
681
  constructor(dir, name, fallback) {
524
682
  this.fallback = fallback;
525
683
  try {
526
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
684
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
527
685
  } catch {
528
686
  }
529
- this.file = join(dir, name);
687
+ this.file = join3(dir, name);
530
688
  }
531
689
  read() {
532
690
  if (this.cached) return this.cached;
533
691
  try {
534
- if (existsSync(this.file)) {
535
- const raw = JSON.parse(readFileSync(this.file, "utf8"));
692
+ if (existsSync3(this.file)) {
693
+ const raw = JSON.parse(readFileSync3(this.file, "utf8"));
536
694
  this.cached = raw ?? this.fallback;
537
695
  return this.cached;
538
696
  }
@@ -544,7 +702,7 @@ var JsonStore = class {
544
702
  write(value) {
545
703
  this.cached = value;
546
704
  try {
547
- writeFileSync(this.file, JSON.stringify(value, null, 2));
705
+ writeFileSync3(this.file, JSON.stringify(value, null, 2));
548
706
  } catch {
549
707
  }
550
708
  }
@@ -552,7 +710,7 @@ var JsonStore = class {
552
710
 
553
711
  // src/hands.ts
554
712
  import { execFile } from "node:child_process";
555
- import { readFileSync as readFileSync2 } from "node:fs";
713
+ import { readFileSync as readFileSync4 } from "node:fs";
556
714
  import { get as httpGet, request as httpRequest } from "node:http";
557
715
  var ACTION_TIMEOUT_MS = 6e3;
558
716
  var PROBE_TTL_MS = 2e4;
@@ -562,12 +720,12 @@ var MAX_GESTURE_MS = 2e4;
562
720
  var HandError = class extends Error {
563
721
  };
564
722
  function get(port, path, timeoutMs) {
565
- return new Promise((resolve, reject) => {
723
+ return new Promise((resolve2, reject) => {
566
724
  const req = httpGet({ host: "127.0.0.1", port, path, timeout: timeoutMs }, (res) => {
567
725
  const chunks = [];
568
726
  res.on("data", (chunk) => chunks.push(chunk));
569
727
  res.on("end", () => {
570
- resolve({
728
+ resolve2({
571
729
  status: res.statusCode ?? 0,
572
730
  body: Buffer.concat(chunks),
573
731
  header: (name) => {
@@ -582,7 +740,7 @@ function get(port, path, timeoutMs) {
582
740
  });
583
741
  }
584
742
  async function post(port, path, body, timeoutMs) {
585
- return new Promise((resolve, reject) => {
743
+ return new Promise((resolve2, reject) => {
586
744
  const data = Buffer.from(body, "utf8");
587
745
  const req = httpRequest(
588
746
  {
@@ -597,7 +755,7 @@ async function post(port, path, body, timeoutMs) {
597
755
  const chunks = [];
598
756
  res.on("data", (chunk) => chunks.push(chunk));
599
757
  res.on("end", () => {
600
- resolve({
758
+ resolve2({
601
759
  status: res.statusCode ?? 0,
602
760
  body: Buffer.concat(chunks),
603
761
  header: (name) => {
@@ -690,10 +848,10 @@ var RootActuator = class {
690
848
  this.log = log;
691
849
  }
692
850
  run(argv, timeoutMs = ACTION_TIMEOUT_MS) {
693
- return new Promise((resolve) => {
851
+ return new Promise((resolve2) => {
694
852
  execFile(argv[0], argv.slice(1), { timeout: timeoutMs, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
695
853
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
696
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
854
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
697
855
  });
698
856
  });
699
857
  }
@@ -739,10 +897,10 @@ var ShellActuator = class {
739
897
  if (!template) return { ok: false, detail: "shell \u901A\u9053\u6CA1\u914D\u6A21\u677F" };
740
898
  if (!template.includes("{cmd}")) return { ok: false, detail: "shell \u6A21\u677F\u91CC\u6CA1\u6709 {cmd}\uFF0C\u4E0D\u77E5\u9053\u628A\u547D\u4EE4\u653E\u54EA" };
741
899
  const probe = template.replace(/\{cmd\}/g, "id");
742
- const r = await new Promise((resolve) => {
900
+ const r = await new Promise((resolve2) => {
743
901
  execFile("sh", ["-c", probe], { timeout: 8e3, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
744
902
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
745
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
903
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
746
904
  });
747
905
  });
748
906
  if (r.code !== 0) return { ok: false, detail: flat(r.err || r.out, 140) || "\u6A21\u677F\u8DD1\u4E0D\u8D77\u6765" };
@@ -751,10 +909,10 @@ var ShellActuator = class {
751
909
  }
752
910
  async apply(command) {
753
911
  const argv = this.argv(command);
754
- const r = await new Promise((resolve) => {
912
+ const r = await new Promise((resolve2) => {
755
913
  execFile(argv[0], argv.slice(1), { timeout: ACTION_TIMEOUT_MS, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
756
914
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
757
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
915
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
758
916
  });
759
917
  });
760
918
  if (r.code !== 0) throw new HandError(`\u901A\u9053\u547D\u4EE4\u6CA1\u6210\u529F\uFF08${flat(r.err || r.out, 160) || `\u9000\u51FA\u7801 ${r.code}`}\uFF09\u3002`);
@@ -953,7 +1111,7 @@ var Hands = class {
953
1111
  if (Date.now() - this.tokenCache.at < 3e4) return this.tokenCache.value;
954
1112
  let value = "";
955
1113
  try {
956
- const text = readFileSync2(file, "utf8").trim();
1114
+ const text = readFileSync4(file, "utf8").trim();
957
1115
  if (text && text.length <= 200) value = text;
958
1116
  } catch {
959
1117
  value = "";
@@ -2085,8 +2243,8 @@ var HandsWorld = class {
2085
2243
  try {
2086
2244
  const old = readPersona(dir);
2087
2245
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2088
- if (old) writeFileSync2(join2(dir, `CONSTITUTION.bak.${stamp}.md`), old);
2089
- writeFileSync2(join2(dir, "CONSTITUTION.md"), content);
2246
+ if (old) writeFileSync4(join4(dir, `CONSTITUTION.bak.${stamp}.md`), old);
2247
+ writeFileSync4(join4(dir, "CONSTITUTION.md"), content);
2090
2248
  return `\u4EBA\u683C\u6362\u597D\u4E86\uFF08\u65E7\u7248\u5907\u4EFD\u6210 CONSTITUTION.bak.${stamp}.md\uFF09`;
2091
2249
  } catch (err) {
2092
2250
  return failed("\u5199\u4E0D\u8FDB\u53BB\uFF1A" + String(err).slice(0, 120));
@@ -2101,7 +2259,7 @@ var HandsWorld = class {
2101
2259
  try {
2102
2260
  const rows = await this.hands.memorySearch();
2103
2261
  lines = rows.length;
2104
- const target = join2(dir, "\u8BB0\u5FC6\u5E93.md");
2262
+ const target = join4(dir, "\u8BB0\u5FC6\u5E93.md");
2105
2263
  const r = mergeMemorySection(target, rows);
2106
2264
  if (r.wrote) wrote.push("\u8BB0\u5FC6\u5E93.md");
2107
2265
  const persona = readPersona(dir);
@@ -2272,7 +2430,7 @@ ${lines.join("\n")}
2272
2430
  mediaDir() {
2273
2431
  const base = this.opts.dataDir;
2274
2432
  if (!base) return null;
2275
- const dir = base.endsWith("/data") ? join2(base, "media") : join2(base, "data", "media");
2433
+ const dir = base.endsWith("/data") ? join4(base, "media") : join4(base, "data", "media");
2276
2434
  return dir;
2277
2435
  }
2278
2436
  /** 钩子 5:QQ 在后台收到的消息,原样递给她;谁来的一眼能看清。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.20.2",
3
+ "version": "0.20.3",
4
4
  "type": "module",
5
5
  "description": "Cortico World: hands for the desktop pet — looks at the phone screen, then taps, swipes, pinches, drags, types and presses keys through whatever actuator the device actually has",
6
6
  "license": "MIT",