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.
- package/dist/index.js +183 -25
- 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
|
|
236
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
237
237
|
import { fileURLToPath } from "node:url";
|
|
238
|
-
import { join as
|
|
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/
|
|
517
|
-
import { existsSync,
|
|
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 (!
|
|
684
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
527
685
|
} catch {
|
|
528
686
|
}
|
|
529
|
-
this.file =
|
|
687
|
+
this.file = join3(dir, name);
|
|
530
688
|
}
|
|
531
689
|
read() {
|
|
532
690
|
if (this.cached) return this.cached;
|
|
533
691
|
try {
|
|
534
|
-
if (
|
|
535
|
-
const raw = JSON.parse(
|
|
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
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
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)
|
|
2089
|
-
|
|
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 =
|
|
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") ?
|
|
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.
|
|
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",
|