cortico-world-hands 0.20.1 → 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 +195 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -125,6 +125,18 @@ var HANDS_CONFIG_GROUP = {
125
125
  description: '\u4F8B\u5982 rish -c "{cmd}" \u6216 adb -s 127.0.0.1:5555 shell "{cmd}"\u3002{cmd} \u4F1A\u6362\u6210\u4E00\u6761\u5DF2\u7ECF\u6821\u9A8C\u8FC7\u7684 input \u547D\u4EE4\u3002',
126
126
  "x-hot": false
127
127
  },
128
+ [`${K}.skinPackUrl`]: {
129
+ type: "string",
130
+ title: "\u670D\u88C5\u5305\u5730\u5740",
131
+ description: "\u586B\u4E86\u5C31\u5728\u4E16\u754C\u542F\u52A8\u65F6\u81EA\u52A8\u88C5\uFF08\u672C\u5730 HTTP \u4E5F\u884C\uFF0C\u6BD4\u5982 http://127.0.0.1:18080\uFF09\u3002",
132
+ "x-hot": true
133
+ },
134
+ [`${K}.qqOwner`]: {
135
+ type: "string",
136
+ title: "\u4E3B\u4EBA\u7684 QQ \u540D\u5B57",
137
+ description: "\u7528\u6765\u533A\u5206\u300C\u4E3B\u4EBA\u7684\u6307\u4EE4\u300D\u548C\u300C\u522B\u4EBA\u7684\u6D88\u606F\u300D\u3002",
138
+ "x-hot": true
139
+ },
128
140
  [`${K}.token`]: {
129
141
  type: "string",
130
142
  title: "\u624B\u52BF\u4EE4\u724C",
@@ -221,9 +233,9 @@ var HANDS_LOOK_CONFIG_GROUP = {
221
233
  };
222
234
 
223
235
  // src/world.ts
224
- import { writeFileSync as writeFileSync2 } from "node:fs";
236
+ import { writeFileSync as writeFileSync4 } from "node:fs";
225
237
  import { fileURLToPath } from "node:url";
226
- import { join as join2 } from "node:path";
238
+ import { join as join4 } from "node:path";
227
239
 
228
240
  // src/policy.ts
229
241
  var PASS_RULES = [
@@ -501,9 +513,167 @@ var Honcho = class {
501
513
  }
502
514
  };
503
515
 
504
- // src/stores.ts
505
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
516
+ // src/sync.ts
517
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
506
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";
507
677
  var JsonStore = class {
508
678
  file;
509
679
  fallback;
@@ -511,16 +681,16 @@ var JsonStore = class {
511
681
  constructor(dir, name, fallback) {
512
682
  this.fallback = fallback;
513
683
  try {
514
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
684
+ if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
515
685
  } catch {
516
686
  }
517
- this.file = join(dir, name);
687
+ this.file = join3(dir, name);
518
688
  }
519
689
  read() {
520
690
  if (this.cached) return this.cached;
521
691
  try {
522
- if (existsSync(this.file)) {
523
- const raw = JSON.parse(readFileSync(this.file, "utf8"));
692
+ if (existsSync3(this.file)) {
693
+ const raw = JSON.parse(readFileSync3(this.file, "utf8"));
524
694
  this.cached = raw ?? this.fallback;
525
695
  return this.cached;
526
696
  }
@@ -532,7 +702,7 @@ var JsonStore = class {
532
702
  write(value) {
533
703
  this.cached = value;
534
704
  try {
535
- writeFileSync(this.file, JSON.stringify(value, null, 2));
705
+ writeFileSync3(this.file, JSON.stringify(value, null, 2));
536
706
  } catch {
537
707
  }
538
708
  }
@@ -540,7 +710,7 @@ var JsonStore = class {
540
710
 
541
711
  // src/hands.ts
542
712
  import { execFile } from "node:child_process";
543
- import { readFileSync as readFileSync2 } from "node:fs";
713
+ import { readFileSync as readFileSync4 } from "node:fs";
544
714
  import { get as httpGet, request as httpRequest } from "node:http";
545
715
  var ACTION_TIMEOUT_MS = 6e3;
546
716
  var PROBE_TTL_MS = 2e4;
@@ -550,12 +720,12 @@ var MAX_GESTURE_MS = 2e4;
550
720
  var HandError = class extends Error {
551
721
  };
552
722
  function get(port, path, timeoutMs) {
553
- return new Promise((resolve, reject) => {
723
+ return new Promise((resolve2, reject) => {
554
724
  const req = httpGet({ host: "127.0.0.1", port, path, timeout: timeoutMs }, (res) => {
555
725
  const chunks = [];
556
726
  res.on("data", (chunk) => chunks.push(chunk));
557
727
  res.on("end", () => {
558
- resolve({
728
+ resolve2({
559
729
  status: res.statusCode ?? 0,
560
730
  body: Buffer.concat(chunks),
561
731
  header: (name) => {
@@ -570,7 +740,7 @@ function get(port, path, timeoutMs) {
570
740
  });
571
741
  }
572
742
  async function post(port, path, body, timeoutMs) {
573
- return new Promise((resolve, reject) => {
743
+ return new Promise((resolve2, reject) => {
574
744
  const data = Buffer.from(body, "utf8");
575
745
  const req = httpRequest(
576
746
  {
@@ -585,7 +755,7 @@ async function post(port, path, body, timeoutMs) {
585
755
  const chunks = [];
586
756
  res.on("data", (chunk) => chunks.push(chunk));
587
757
  res.on("end", () => {
588
- resolve({
758
+ resolve2({
589
759
  status: res.statusCode ?? 0,
590
760
  body: Buffer.concat(chunks),
591
761
  header: (name) => {
@@ -678,10 +848,10 @@ var RootActuator = class {
678
848
  this.log = log;
679
849
  }
680
850
  run(argv, timeoutMs = ACTION_TIMEOUT_MS) {
681
- return new Promise((resolve) => {
851
+ return new Promise((resolve2) => {
682
852
  execFile(argv[0], argv.slice(1), { timeout: timeoutMs, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
683
853
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
684
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
854
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
685
855
  });
686
856
  });
687
857
  }
@@ -727,10 +897,10 @@ var ShellActuator = class {
727
897
  if (!template) return { ok: false, detail: "shell \u901A\u9053\u6CA1\u914D\u6A21\u677F" };
728
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" };
729
899
  const probe = template.replace(/\{cmd\}/g, "id");
730
- const r = await new Promise((resolve) => {
900
+ const r = await new Promise((resolve2) => {
731
901
  execFile("sh", ["-c", probe], { timeout: 8e3, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
732
902
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
733
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
903
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
734
904
  });
735
905
  });
736
906
  if (r.code !== 0) return { ok: false, detail: flat(r.err || r.out, 140) || "\u6A21\u677F\u8DD1\u4E0D\u8D77\u6765" };
@@ -739,10 +909,10 @@ var ShellActuator = class {
739
909
  }
740
910
  async apply(command) {
741
911
  const argv = this.argv(command);
742
- const r = await new Promise((resolve) => {
912
+ const r = await new Promise((resolve2) => {
743
913
  execFile(argv[0], argv.slice(1), { timeout: ACTION_TIMEOUT_MS, maxBuffer: 1 << 20 }, (err, stdout, stderr) => {
744
914
  const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
745
- resolve({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
915
+ resolve2({ code, out: String(stdout ?? ""), err: String(stderr ?? "") });
746
916
  });
747
917
  });
748
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`);
@@ -941,7 +1111,7 @@ var Hands = class {
941
1111
  if (Date.now() - this.tokenCache.at < 3e4) return this.tokenCache.value;
942
1112
  let value = "";
943
1113
  try {
944
- const text = readFileSync2(file, "utf8").trim();
1114
+ const text = readFileSync4(file, "utf8").trim();
945
1115
  if (text && text.length <= 200) value = text;
946
1116
  } catch {
947
1117
  value = "";
@@ -2073,8 +2243,8 @@ var HandsWorld = class {
2073
2243
  try {
2074
2244
  const old = readPersona(dir);
2075
2245
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
2076
- if (old) writeFileSync2(join2(dir, `CONSTITUTION.bak.${stamp}.md`), old);
2077
- writeFileSync2(join2(dir, "CONSTITUTION.md"), content);
2246
+ if (old) writeFileSync4(join4(dir, `CONSTITUTION.bak.${stamp}.md`), old);
2247
+ writeFileSync4(join4(dir, "CONSTITUTION.md"), content);
2078
2248
  return `\u4EBA\u683C\u6362\u597D\u4E86\uFF08\u65E7\u7248\u5907\u4EFD\u6210 CONSTITUTION.bak.${stamp}.md\uFF09`;
2079
2249
  } catch (err) {
2080
2250
  return failed("\u5199\u4E0D\u8FDB\u53BB\uFF1A" + String(err).slice(0, 120));
@@ -2089,7 +2259,7 @@ var HandsWorld = class {
2089
2259
  try {
2090
2260
  const rows = await this.hands.memorySearch();
2091
2261
  lines = rows.length;
2092
- const target = join2(dir, "\u8BB0\u5FC6\u5E93.md");
2262
+ const target = join4(dir, "\u8BB0\u5FC6\u5E93.md");
2093
2263
  const r = mergeMemorySection(target, rows);
2094
2264
  if (r.wrote) wrote.push("\u8BB0\u5FC6\u5E93.md");
2095
2265
  const persona = readPersona(dir);
@@ -2260,7 +2430,7 @@ ${lines.join("\n")}
2260
2430
  mediaDir() {
2261
2431
  const base = this.opts.dataDir;
2262
2432
  if (!base) return null;
2263
- 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");
2264
2434
  return dir;
2265
2435
  }
2266
2436
  /** 钩子 5:QQ 在后台收到的消息,原样递给她;谁来的一眼能看清。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.20.1",
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",