cortico-world-hands 0.17.0 → 0.19.0

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 +290 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ var HANDS_DEFAULTS = {
25
25
  hookGrams: "three",
26
26
  skinPackUrl: "",
27
27
  qqOwner: "",
28
+ ritualMorning: "08:00",
29
+ ritualNight: "23:00",
28
30
  mediaKeepDays: 1,
29
31
  mediaCleanHour: 4,
30
32
  mediaMaxMB: 0
@@ -220,6 +222,7 @@ var HANDS_LOOK_CONFIG_GROUP = {
220
222
 
221
223
  // src/world.ts
222
224
  import { fileURLToPath } from "node:url";
225
+ import { join } from "node:path";
223
226
 
224
227
  // src/policy.ts
225
228
  var PASS_RULES = [
@@ -1801,6 +1804,97 @@ var HANDS_TOOL_DECLS = [
1801
1804
  tags: ["flow"],
1802
1805
  description: "\u7ACB\u523B\u505C\u624B\uFF1A\u4E00\u5206\u949F\u4E4B\u5185\u4E0D\u518D\u52A8\u4EFB\u4F55\u4E00\u4E0B\uFF08\u5DF2\u7ECF\u53D1\u51FA\u7684\u4E0D\u7B97\uFF09\u3002\u5BF9\u65B9\u8BF4\u300C\u522B\u52A8\u4E86\u300D\u300C\u505C\u4E0B\u300D\u6216\u8005\u4F60\u53D1\u73B0\u81EA\u5DF1\u70B9\u9519\u5730\u65B9\u65F6\u7528\u5B83\u3002",
1803
1806
  parameters: { type: "object", properties: {}, required: [] }
1807
+ },
1808
+ {
1809
+ name: "agenda_add",
1810
+ tags: ["act"],
1811
+ description: '\u8BB0\u4E00\u6761\u65E5\u7A0B\uFF08\u5230\u70B9\u4F1A\u53EB\u9192\u5979\u4E00\u6B21\uFF09\u3002at \u7528 "2026-10-26 08:30" \u6216 "08:30"\uFF08\u4ECA\u5929/\u660E\u5929\u81EA\u52A8\u5224\u65AD\uFF09\u3002',
1812
+ parameters: {
1813
+ type: "object",
1814
+ properties: {
1815
+ title: { type: "string", description: "\u8981\u505A\u4EC0\u4E48" },
1816
+ at: { type: "string", description: '\u4EC0\u4E48\u65F6\u5019\uFF0C\u4F8B\u5982 "2026-10-26 08:30" \u6216 "20:30"' },
1817
+ who: { type: "string", description: "\u8C01\u8981\u6C42\u7684\uFF0C\u9ED8\u8BA4\u4E3B\u4EBA" }
1818
+ },
1819
+ required: ["title"]
1820
+ }
1821
+ },
1822
+ {
1823
+ name: "agenda_list",
1824
+ tags: ["read"],
1825
+ description: "\u770B\u8FD8\u6CA1\u505A\u7684\u65E5\u7A0B\u3002",
1826
+ parameters: { type: "object", properties: {} }
1827
+ },
1828
+ {
1829
+ name: "agenda_done",
1830
+ tags: ["act"],
1831
+ description: "\u628A\u4E00\u6761\u65E5\u7A0B\u6807\u8BB0\u5B8C\u6210\u3002",
1832
+ parameters: {
1833
+ type: "object",
1834
+ properties: { id: { type: "string", description: "agenda_list \u91CC\u7684 id" } },
1835
+ required: ["id"]
1836
+ }
1837
+ },
1838
+ {
1839
+ name: "affinity_get",
1840
+ tags: ["read"],
1841
+ description: "\u67E5\u67D0\u4E2A\u4EBA\uFF08QQ \u53F7\uFF09\u5728\u5979\u5FC3\u91CC\u7684\u5206\u6570\u3001\u6863\u4F4D\u3001\u8BE5\u7528\u4EC0\u4E48\u6001\u5EA6\u3002",
1842
+ parameters: {
1843
+ type: "object",
1844
+ properties: { qq: { type: "string", description: "\u5BF9\u65B9\u7684 QQ \u53F7\u6216\u540D\u5B57" } },
1845
+ required: ["qq"]
1846
+ }
1847
+ },
1848
+ {
1849
+ name: "affinity_adjust",
1850
+ tags: ["act"],
1851
+ description: "\u7ED9\u67D0\u4E2A\u4EBA\u8BB0\u4E00\u7B14\u5173\u7CFB\u53D8\u5316\uFF08\u53EF\u6B63\u53EF\u8D1F\uFF09\uFF0C\u5E76\u5199\u4E00\u53E5\u4E3A\u4EC0\u4E48\u3002",
1852
+ parameters: {
1853
+ type: "object",
1854
+ properties: {
1855
+ qq: { type: "string", description: "\u5BF9\u65B9\u7684 QQ \u53F7\u6216\u540D\u5B57" },
1856
+ delta: { type: "number", description: "\u53D8\u5316\u91CF\uFF0C\u6BD4\u5982 5 \u6216 -3" },
1857
+ why: { type: "string", description: "\u4E3A\u4EC0\u4E48" }
1858
+ },
1859
+ required: ["qq", "delta"]
1860
+ }
1861
+ },
1862
+ {
1863
+ name: "affinity_list",
1864
+ tags: ["read"],
1865
+ description: "\u770B\u5979\u5FC3\u91CC\u6392\u524D\u9762\u7684\u51E0\u4E2A\u4EBA\uFF0C\u8FDE\u540C\u5206\u6570\u548C\u6863\u4F4D\u3002",
1866
+ parameters: { type: "object", properties: {} }
1867
+ },
1868
+ {
1869
+ name: "ritual_set",
1870
+ tags: ["act"],
1871
+ description: "\u8BB0\u4E00\u4E2A\u7EAA\u5FF5\u65E5\uFF08\u6708-\u65E5\uFF09\u3002\u5230\u70B9\u4F1A\u63D0\u9192\u4E00\u6B21\u3002",
1872
+ parameters: {
1873
+ type: "object",
1874
+ properties: {
1875
+ date: { type: "string", description: '\u6708-\u65E5\uFF0C\u4F8B\u5982 "10-26"' },
1876
+ name: { type: "string", description: "\u662F\u4EC0\u4E48\u65E5\u5B50" }
1877
+ },
1878
+ required: ["date", "name"]
1879
+ }
1880
+ },
1881
+ {
1882
+ name: "ritual_list",
1883
+ tags: ["read"],
1884
+ description: "\u5217\u51FA\u5DF2\u8BB0\u7684\u7EAA\u5FF5\u65E5\u3002",
1885
+ parameters: { type: "object", properties: {} }
1886
+ },
1887
+ {
1888
+ name: "sync_now",
1889
+ tags: ["act"],
1890
+ description: "\u628A\u61FF\u5FC3\u90A3\u8FB9\u5B58\u7684\u8BB0\u5FC6\u540C\u6B65\u5230\u5DE5\u4F5C\u533A\u300C\u8BB0\u5FC6\u5E93.md\u300D\uFF0C\u5E76\u628A\u4EBA\u683C\u539F\u6587\u5907\u4EFD\u8FDB\u61FF\u5FC3\u3002\u60F3\u624B\u52A8\u5BF9\u4E00\u6B21\u65F6\u7528\u3002",
1891
+ parameters: { type: "object", properties: {} }
1892
+ },
1893
+ {
1894
+ name: "sync_status",
1895
+ tags: ["read"],
1896
+ description: "\u770B\u4E0A\u4E00\u6B21\u540C\u6B65\u662F\u4EC0\u4E48\u65F6\u5019\u3001\u540C\u6B65\u4E86\u591A\u5C11\u6761\u3001\u4EBA\u683C\u539F\u6587\u591A\u5C11\u5B57\u3002",
1897
+ parameters: { type: "object", properties: {} }
1804
1898
  }
1805
1899
  ];
1806
1900
 
@@ -1822,6 +1916,12 @@ var HandsWorld = class {
1822
1916
  pausedUntil = 0;
1823
1917
  recent = [];
1824
1918
  honchoClient = null;
1919
+ agendaTimer = null;
1920
+ syncTimer = null;
1921
+ lastSync = null;
1922
+ agenda;
1923
+ affinity;
1924
+ ritual;
1825
1925
  pollTimer = null;
1826
1926
  dailyDiaryTimer = null;
1827
1927
  lastDiaryDay = "";
@@ -1863,6 +1963,19 @@ var HandsWorld = class {
1863
1963
  /* ---------- 记忆钩子 ---------- */
1864
1964
  startHooks() {
1865
1965
  if (this.pollTimer) return;
1966
+ const dir = this.opts.dataDir ?? ".";
1967
+ this.agenda = new JsonStore(dir, "agenda.json", []);
1968
+ this.affinity = new JsonStore(dir, "affinity.json", {});
1969
+ this.ritual = new JsonStore(dir, "ritual.json", { anniversaries: [] });
1970
+ this.agendaTimer = setInterval(() => {
1971
+ void this.checkAgenda();
1972
+ }, 3e4);
1973
+ this.syncTimer = setInterval(() => {
1974
+ void this.runSync(false);
1975
+ }, 10 * 60 * 1e3);
1976
+ setTimeout(() => {
1977
+ void this.runSync(false);
1978
+ }, 45e3);
1866
1979
  this.pollTimer = setInterval(() => {
1867
1980
  void this.pollUserSpeech();
1868
1981
  }, 5e3);
@@ -1888,6 +2001,87 @@ var HandsWorld = class {
1888
2001
  this.pollTimer = null;
1889
2002
  this.mineTimer = null;
1890
2003
  }
2004
+ /** 每 30 秒看一次:日程到点投一条,早安/晚安/纪念日各一次。 */
2005
+ /** 懿心 ↔ Coopanin:记忆推成 记忆库.md,人格原文存进懿心。 */
2006
+ async runSync(manual) {
2007
+ const dir = this.opts.botDir || this.opts.dataDir || ".";
2008
+ const wrote = [];
2009
+ let lines = 0;
2010
+ let personaChars = 0;
2011
+ try {
2012
+ const rows = await this.hands.memorySearch();
2013
+ lines = rows.length;
2014
+ const target = join(dir, "\u8BB0\u5FC6\u5E93.md");
2015
+ const r = mergeMemorySection(target, rows);
2016
+ if (r.wrote) wrote.push("\u8BB0\u5FC6\u5E93.md");
2017
+ const persona = readPersona(dir);
2018
+ personaChars = persona.length;
2019
+ if (persona) {
2020
+ await this.hands.memorySave("\u4EBA\u683C\u539F\u6587", persona.slice(0, 12e3), "\u4EBA\u683C,\u540C\u6B65");
2021
+ wrote.push("\u4EBA\u683C\u539F\u6587\u2192\u61FF\u5FC3");
2022
+ }
2023
+ this.lastSync = { at: (/* @__PURE__ */ new Date()).toISOString(), lines, persona: personaChars };
2024
+ return `${manual ? "" : ""}\u540C\u6B65\u5B8C\u6210\uFF1A\u8BB0\u5FC6 ${lines} \u6761\uFF0C\u4EBA\u683C\u539F\u6587 ${personaChars} \u5B57${wrote.length ? "\uFF0C\u66F4\u65B0\u4E86 " + wrote.join("\u3001") : "\uFF0C\u6CA1\u6709\u53D8\u5316"}`;
2025
+ } catch (err) {
2026
+ return "\u540C\u6B65\u5931\u8D25\uFF1A" + String(err).slice(0, 120);
2027
+ }
2028
+ }
2029
+ async checkAgenda() {
2030
+ const now = /* @__PURE__ */ new Date();
2031
+ const iso = now.toISOString();
2032
+ const items = this.agenda?.read() ?? [];
2033
+ let changed = false;
2034
+ for (const item of items) {
2035
+ if (item.done || item.fired || !item.at) continue;
2036
+ if (new Date(item.at).getTime() <= now.getTime()) {
2037
+ item.fired = true;
2038
+ changed = true;
2039
+ await this.pushEvent(`[\u65E5\u7A0B] \u5230\u70B9\u4E86\uFF1A${item.title}\uFF08${item.who} \u8981\u7684\uFF09`, "agenda.due", "agenda");
2040
+ }
2041
+ }
2042
+ if (changed) this.agenda?.write(items);
2043
+ try {
2044
+ const st = this.ritualStore().read();
2045
+ const pad = (n) => String(n).padStart(2, "0");
2046
+ const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
2047
+ const hhmm = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
2048
+ const morning = this.opts.cfg.ritualMorning || "08:00";
2049
+ const night = this.opts.cfg.ritualNight || "23:00";
2050
+ let dirty = false;
2051
+ if (hhmm === morning && st.lastMorning !== today) {
2052
+ st.lastMorning = today;
2053
+ dirty = true;
2054
+ await this.pushEvent("[\u4EEA\u5F0F] \u65E9\u5B89\u65F6\u95F4\u5230\u4E86\uFF0C\u4E3B\u52A8\u8DDF\u4E3B\u4EBA\u8BF4\u53E5\u65E9\u5B89\u3002", "ritual.morning", "ritual");
2055
+ }
2056
+ if (hhmm === night && st.lastNight !== today) {
2057
+ st.lastNight = today;
2058
+ dirty = true;
2059
+ await this.pushEvent("[\u4EEA\u5F0F] \u665A\u5B89\u65F6\u95F4\u5230\u4E86\uFF0C\u8DDF\u4E3B\u4EBA\u9053\u4E2A\u665A\u5B89\u3002", "ritual.night", "ritual");
2060
+ }
2061
+ const md = `${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
2062
+ for (const a of st.anniversaries ?? []) {
2063
+ if (a.date === md && st.lastAnniversary !== today) {
2064
+ st.lastAnniversary = today;
2065
+ dirty = true;
2066
+ await this.pushEvent(`[\u4EEA\u5F0F] \u4ECA\u5929\u662F\u300C${a.name}\u300D\uFF0C\u8BB0\u5F97\u63D0\u4E00\u53E5\u3002`, "ritual.anniversary", "ritual");
2067
+ }
2068
+ }
2069
+ if (dirty) this.ritualStore().write(st);
2070
+ } catch {
2071
+ }
2072
+ }
2073
+ agendaStore() {
2074
+ if (!this.agenda) throw new Error("\u65E5\u7A0B\u8FD8\u6CA1\u51C6\u5907\u597D");
2075
+ return this.agenda;
2076
+ }
2077
+ affinityStore() {
2078
+ if (!this.affinity) throw new Error("\u5173\u7CFB\u518C\u8FD8\u6CA1\u51C6\u5907\u597D");
2079
+ return this.affinity;
2080
+ }
2081
+ ritualStore() {
2082
+ if (!this.ritual) throw new Error("\u4EEA\u5F0F\u518C\u8FD8\u6CA1\u51C6\u5907\u597D");
2083
+ return this.ritual;
2084
+ }
1891
2085
  async pushEvent(text, type, senderKey) {
1892
2086
  const host = this.host;
1893
2087
  if (!host) return;
@@ -2080,6 +2274,10 @@ ${lines.join("\n")}
2080
2274
  return await this.matchImage(args, false);
2081
2275
  case "hand_tap_image":
2082
2276
  return await this.matchImage(args, true);
2277
+ case "sync_now":
2278
+ return await this.runSync(true);
2279
+ case "sync_status":
2280
+ return this.lastSync ? `\u4E0A\u6B21\u540C\u6B65 ${this.lastSync.at.slice(0, 19)}\uFF0C\u8BB0\u5FC6 ${this.lastSync.lines} \u6761\uFF0C\u4EBA\u683C ${this.lastSync.persona} \u5B57` : "\u8FD8\u6CA1\u540C\u6B65\u8FC7";
2083
2281
  case "hand_see":
2084
2282
  return await this.see();
2085
2283
  case "hand_look":
@@ -2118,6 +2316,70 @@ ${lines.join("\n")}
2118
2316
  return await this.mediaClean(args);
2119
2317
  case "screen_wake":
2120
2318
  return await this.screenWake();
2319
+ case "agenda_add": {
2320
+ const title = typeof args.title === "string" ? args.title.trim() : "";
2321
+ if (!title) return failed("\u8981\u8BB0\u4EC0\u4E48\uFF1F");
2322
+ const at = normalizeWhen(typeof args.at === "string" ? args.at : "");
2323
+ const items = this.agendaStore().read();
2324
+ const id = "a" + Date.now().toString(36);
2325
+ items.push({ id, title, at, who: typeof args.who === "string" && args.who ? args.who : "\u4E3B\u4EBA", done: false });
2326
+ this.agendaStore().write(items);
2327
+ return at ? `\u8BB0\u4E0B\u4E86\uFF1A${title}\uFF0C${fmtWhen(at)} \u53EB\u4F60` : `\u8BB0\u4E0B\u4E86\uFF1A${title}\uFF08\u6CA1\u8BBE\u65F6\u95F4\uFF09`;
2328
+ }
2329
+ case "agenda_list": {
2330
+ const items = this.agendaStore().read().filter((i) => !i.done);
2331
+ if (items.length === 0) return "\u65E5\u7A0B\u662F\u7A7A\u7684\u3002";
2332
+ return items.map((i) => `- [${i.id}] ${i.title}${i.at ? " \xB7 " + fmtWhen(i.at) : ""}\uFF08${i.who}\uFF09`).join("\n");
2333
+ }
2334
+ case "agenda_done": {
2335
+ const id = typeof args.id === "string" ? args.id.trim() : "";
2336
+ const items = this.agendaStore().read();
2337
+ const hit = items.find((i) => i.id === id);
2338
+ if (!hit) return failed("\u6CA1\u6709\u8FD9\u6761\u65E5\u7A0B");
2339
+ hit.done = true;
2340
+ this.agendaStore().write(items);
2341
+ return `\u597D\u4E86\uFF1A\u300C${hit.title}\u300D\u5DF2\u5B8C\u6210`;
2342
+ }
2343
+ case "affinity_get": {
2344
+ const qq = typeof args.qq === "string" ? args.qq.trim() : "";
2345
+ if (!qq) return failed("\u8981\u67E5\u8C01\uFF1F");
2346
+ const book = this.affinityStore().read();
2347
+ const row = book[qq];
2348
+ if (!row) return `${qq} \u8FD8\u6CA1\u8BB0\u8FC7\uFF0C\u5206\u6570 0\uFF08\u4E00\u822C\uFF09\u3002`;
2349
+ return `${qq}\uFF1A${row.score} \u5206\uFF08${LEVELS(row.score)}\uFF09${row.slot ? "\uFF0C\u7B97\u300C" + row.slot + "\u300D" : ""}\u3002\u6700\u8FD1\uFF1A${(row.notes ?? []).slice(-3).map((n) => `${n.delta > 0 ? "+" : ""}${n.delta} ${n.why}`).join("\uFF1B") || "\u65E0"}`;
2350
+ }
2351
+ case "affinity_adjust": {
2352
+ const qq = typeof args.qq === "string" ? args.qq.trim() : "";
2353
+ const delta = Number(args.delta);
2354
+ if (!qq || !Number.isFinite(delta)) return failed("\u8981 qq \u548C delta");
2355
+ const why = typeof args.why === "string" ? args.why.trim() : "";
2356
+ const book = this.affinityStore().read();
2357
+ const row = book[qq] ?? { qq, score: 0, notes: [] };
2358
+ row.score = Math.max(-100, Math.min(100, row.score + delta));
2359
+ row.notes = [...row.notes ?? [], { at: (/* @__PURE__ */ new Date()).toISOString(), delta, why }].slice(-50);
2360
+ book[qq] = row;
2361
+ this.affinityStore().write(book);
2362
+ return `${qq} \u73B0\u5728\u662F ${row.score} \u5206\uFF08${LEVELS(row.score)}\uFF09`;
2363
+ }
2364
+ case "affinity_list": {
2365
+ const rows = Object.values(this.affinityStore().read()).sort((a, b) => b.score - a.score).slice(0, 8);
2366
+ if (rows.length === 0) return "\u5173\u7CFB\u518C\u8FD8\u662F\u7A7A\u7684\u3002";
2367
+ return rows.map((r) => `- ${r.qq}\uFF1A${r.score} \u5206\uFF08${LEVELS(r.score)}\uFF09${r.slot ? "\xB7 " + r.slot : ""}`).join("\n");
2368
+ }
2369
+ case "ritual_set": {
2370
+ const date = typeof args.date === "string" ? args.date.trim() : "";
2371
+ const name2 = typeof args.name === "string" ? args.name.trim() : "";
2372
+ if (!/^\d{1,2}-\d{1,2}$/.test(date) || !name2) return failed("date \u8981\u5199\u6210 \u6708-\u65E5\uFF0C\u6BD4\u5982 10-26");
2373
+ const st = this.ritualStore().read();
2374
+ st.anniversaries = [...(st.anniversaries ?? []).filter((a) => a.date !== date), { date, name: name2 }];
2375
+ this.ritualStore().write(st);
2376
+ return `\u8BB0\u4F4F\u4E86\uFF1A${date} \u662F\u300C${name2}\u300D`;
2377
+ }
2378
+ case "ritual_list": {
2379
+ const st = this.ritualStore().read();
2380
+ if (!st.anniversaries?.length) return "\u8FD8\u6CA1\u8BB0\u7EAA\u5FF5\u65E5\u3002";
2381
+ return st.anniversaries.map((a) => `- ${a.date} ${a.name}`).join("\n");
2382
+ }
2121
2383
  case "qq_reply":
2122
2384
  return await this.qqReply(args);
2123
2385
  case "screen_unlock":
@@ -2665,6 +2927,33 @@ ${this.recent.map((r) => `- ${r}`).join("\n")}`);
2665
2927
  function failed(text) {
2666
2928
  return { text, failed: true };
2667
2929
  }
2930
+ function normalizeWhen(input) {
2931
+ const text = input.trim();
2932
+ if (!text) return "";
2933
+ const full = /^(\d{4})-(\d{1,2})-(\d{1,2})[ T](\d{1,2}):(\d{2})$/.exec(text);
2934
+ if (full) {
2935
+ const d = new Date(Number(full[1]), Number(full[2]) - 1, Number(full[3]), Number(full[4]), Number(full[5]));
2936
+ return isNaN(d.getTime()) ? "" : d.toISOString();
2937
+ }
2938
+ const hm = /^(\d{1,2}):(\d{2})$/.exec(text);
2939
+ if (hm) {
2940
+ const now = /* @__PURE__ */ new Date();
2941
+ const d = new Date(now.getFullYear(), now.getMonth(), now.getDate(), Number(hm[1]), Number(hm[2]));
2942
+ if (d.getTime() <= now.getTime()) d.setDate(d.getDate() + 1);
2943
+ return d.toISOString();
2944
+ }
2945
+ const parsed = new Date(text);
2946
+ return isNaN(parsed.getTime()) ? "" : parsed.toISOString();
2947
+ }
2948
+ function fmtWhen(iso) {
2949
+ try {
2950
+ const d = new Date(iso);
2951
+ const pad = (n) => String(n).padStart(2, "0");
2952
+ return `${d.getMonth() + 1}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
2953
+ } catch {
2954
+ return iso;
2955
+ }
2956
+ }
2668
2957
 
2669
2958
  // src/definition.ts
2670
2959
  function handsDefinition() {
@@ -2672,7 +2961,7 @@ function handsDefinition() {
2672
2961
  id: HANDS_ID,
2673
2962
  label: "\u624B",
2674
2963
  defaults: () => structuredClone(HANDS_DEFAULTS),
2675
- create: (ctx) => new HandsWorld({ cfg: ctx.cfg, timezone: ctx.timezone, roots: [ctx.botDir, ctx.dataDir, ctx.repoRoot, ctx.packageDir], dataDir: ctx.dataDir })
2964
+ create: (ctx) => new HandsWorld({ cfg: ctx.cfg, timezone: ctx.timezone, roots: [ctx.botDir, ctx.dataDir, ctx.repoRoot, ctx.packageDir], botDir: ctx.botDir, dataDir: ctx.dataDir })
2676
2965
  };
2677
2966
  }
2678
2967
  var HANDS = handsDefinition();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
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",