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.
- package/dist/index.js +195 -25
- 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
|
|
236
|
+
import { writeFileSync as writeFileSync4 } from "node:fs";
|
|
225
237
|
import { fileURLToPath } from "node:url";
|
|
226
|
-
import { join as
|
|
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/
|
|
505
|
-
import { existsSync,
|
|
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 (!
|
|
684
|
+
if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
|
|
515
685
|
} catch {
|
|
516
686
|
}
|
|
517
|
-
this.file =
|
|
687
|
+
this.file = join3(dir, name);
|
|
518
688
|
}
|
|
519
689
|
read() {
|
|
520
690
|
if (this.cached) return this.cached;
|
|
521
691
|
try {
|
|
522
|
-
if (
|
|
523
|
-
const raw = JSON.parse(
|
|
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
|
-
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 =
|
|
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)
|
|
2077
|
-
|
|
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 =
|
|
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") ?
|
|
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.
|
|
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",
|