@my-life-buddies/cli 0.10.0 → 0.13.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 (33) hide show
  1. package/README.md +26 -12
  2. package/dist/bin/buddy.js +68 -12
  3. package/dist/bin/buddy.js.map +3 -3
  4. package/dist/bin/core.js +1077 -454
  5. package/dist/bin/core.js.map +4 -4
  6. package/dist/bin/preview.js +164 -2
  7. package/dist/bin/preview.js.map +4 -4
  8. package/dist/web/app.css +38 -0
  9. package/dist/web/app.js +103 -12
  10. package/dist/web/index.html +1 -1
  11. package/dist/web/widgets.js +4 -3
  12. package/package.json +3 -3
  13. package/resources/dada-object-avatar/SKILL.md +37 -0
  14. package/resources/dada-object-avatar/agents/openai.yaml +4 -0
  15. package/resources/dada-object-avatar/assets/examples/cat.png +0 -0
  16. package/resources/dada-object-avatar/assets/examples/cooking.png +0 -0
  17. package/resources/dada-object-avatar/assets/examples/divination.png +0 -0
  18. package/resources/dada-object-avatar/assets/examples/doctor.png +0 -0
  19. package/resources/dada-object-avatar/assets/examples/relationship.png +0 -0
  20. package/resources/dada-object-avatar/assets/examples/work.png +0 -0
  21. package/resources/dada-object-avatar/assets/preview.png +0 -0
  22. package/resources/dada-object-avatar/references/evaluation-rubric.md +22 -0
  23. package/resources/dada-object-avatar/references/examples.json +8 -0
  24. package/resources/dada-object-avatar/references/prompt-template.md +27 -0
  25. package/resources/dada-object-avatar/references/style-system.md +54 -0
  26. package/resources/dada-object-avatar/references/subject-design.md +23 -0
  27. package/resources/dada-object-avatar/references/training-guide.md +14 -0
  28. package/resources/dada-object-avatar/references/training-log.jsonl +0 -0
  29. package/resources/dada-object-avatar/references/training-record.schema.json +20 -0
  30. package/resources/dada-object-avatar/scripts/choose_gaze.py +36 -0
  31. package/resources/dada-object-avatar/scripts/record_example.py +22 -0
  32. package/resources/dada-object-avatar/scripts/validate_png.py +52 -0
  33. package/resources/dada-object-avatar.manifest.json +85 -0
@@ -43,6 +43,7 @@ var PreviewController = class {
43
43
  #simulator;
44
44
  #startController;
45
45
  #startPromise;
46
+ #stopPromise;
46
47
  #messages = [];
47
48
  #simulatorState = Object.freeze({ messages: Object.freeze([]) });
48
49
  #lastActiveRunId;
@@ -140,7 +141,17 @@ var PreviewController = class {
140
141
  this.#startPromise = startPromise;
141
142
  return startPromise;
142
143
  }
143
- async stop() {
144
+ stop() {
145
+ if (this.#stopPromise) return this.#stopPromise;
146
+ const stopping = this.#stop();
147
+ this.#stopPromise = stopping;
148
+ const clear = () => {
149
+ if (this.#stopPromise === stopping) this.#stopPromise = void 0;
150
+ };
151
+ void stopping.then(clear, clear);
152
+ return stopping;
153
+ }
154
+ async #stop() {
144
155
  if (this.#state.phase === "idle") return this.#state;
145
156
  if (this.#state.phase === "stopping") return this.#state;
146
157
  this.#setState({ phase: "stopping" });
@@ -1371,7 +1382,18 @@ var MlbSimulatorClient = class {
1371
1382
  const signal = AbortSignal.any([AbortSignal.timeout(3e4), ...options.signal ? [options.signal] : []]);
1372
1383
  const details = parsePreviewBuddyDetails(await this.#request("GET", `/app/buddies/${encodeURIComponent(this.#buddyId)}`, { signal }), this.#buddyId);
1373
1384
  this.#privateId = details.privateId ?? void 0;
1374
- return details;
1385
+ if (!details.avatar) return details;
1386
+ let avatarPreview = null;
1387
+ try {
1388
+ const url = new URL(details.avatar, this.#baseUrl);
1389
+ if (url.origin !== this.#baseUrl.origin || url.username || url.password || !url.pathname.startsWith("/media/")) throw new Error("Unsupported avatar URL");
1390
+ const image = await this.#widgetResource(url.href, false);
1391
+ const contentType = image.contentType.split(";", 1)[0].trim().toLowerCase();
1392
+ if (!["image/png", "image/jpeg", "image/webp"].includes(contentType)) throw new Error("Unsupported avatar type");
1393
+ avatarPreview = `data:${contentType};base64,${Buffer.from(image.body).toString("base64")}`;
1394
+ } catch {
1395
+ }
1396
+ return Object.freeze({ ...details, avatarPreview });
1375
1397
  }
1376
1398
  async widgets() {
1377
1399
  this.#assertResetComplete();
@@ -1662,9 +1684,149 @@ var MlbSimulatorClient = class {
1662
1684
  return value;
1663
1685
  }
1664
1686
  };
1687
+
1688
+ // apps/preview/src/bridge.ts
1689
+ import { hostname } from "node:os";
1690
+ import { randomBytes as randomBytes2 } from "node:crypto";
1691
+ import { setTimeout as delay } from "node:timers/promises";
1692
+ async function connectPreviewBridge(options) {
1693
+ const connectionId = randomBytes2(32).toString("base64url");
1694
+ const shutdown = new AbortController();
1695
+ const signal = AbortSignal.any([options.signal, shutdown.signal]);
1696
+ const running = /* @__PURE__ */ new Map();
1697
+ const replies = /* @__PURE__ */ new Map();
1698
+ const completed = /* @__PURE__ */ new Set();
1699
+ let lastContact = Date.now();
1700
+ let stopping;
1701
+ let interrupted = false;
1702
+ async function call(path, token, payload, requestSignal = signal) {
1703
+ const response = await fetch(new URL(path, options.webUrl), {
1704
+ method: "POST",
1705
+ redirect: "error",
1706
+ signal: AbortSignal.any([requestSignal, AbortSignal.timeout(4e3)]),
1707
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
1708
+ body: JSON.stringify(payload ?? {})
1709
+ });
1710
+ const value = await response.json();
1711
+ if (!response.ok) throw Object.assign(new Error(value.error || "\u540E\u53F0\u8FDE\u63A5\u5931\u8D25"), { status: response.status });
1712
+ return value;
1713
+ }
1714
+ const attach = async () => {
1715
+ const result = await call("api/preview/connect", await options.token(), {
1716
+ connectionId,
1717
+ buddyId: options.buddyId,
1718
+ projectRoot: options.controller.projectRoot,
1719
+ machineName: hostname(),
1720
+ platformOrigin: options.platformOrigin
1721
+ });
1722
+ lastContact = Date.now();
1723
+ return result;
1724
+ };
1725
+ const bootstrap = await fetch(options.localServer.bootstrapUrl, { redirect: "manual", signal });
1726
+ const cookie = bootstrap.headers.get("set-cookie")?.split(";", 1)[0];
1727
+ if (bootstrap.status !== 303 || !cookie) throw new Error("\u672C\u5730\u8C03\u8BD5\u670D\u52A1\u521D\u59CB\u5316\u5931\u8D25");
1728
+ const initial = await attach();
1729
+ if (initial.reused) return { reused: true, completion: Promise.resolve(), async close() {
1730
+ } };
1731
+ const watchdog = setInterval(() => {
1732
+ if (Date.now() - lastContact < 1e4 || stopping || interrupted) return;
1733
+ interrupted = true;
1734
+ stopping = options.controller.stop();
1735
+ void stopping.catch(() => shutdown.abort());
1736
+ options.onStatus?.("\u540E\u53F0\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u6B63\u5728\u505C\u6B62 DEV\uFF1B\u8FDE\u63A5\u6062\u590D\u540E\u53EF\u5728\u540E\u53F0\u91CD\u65B0\u542F\u52A8\u3002");
1737
+ }, 500);
1738
+ watchdog.unref();
1739
+ async function execute(command) {
1740
+ try {
1741
+ if (!command.path.startsWith("/api/") || command.path === "/api/events" || command.path.includes("..")) throw new Error("\u8C03\u8BD5\u8DEF\u5F84\u65E0\u6548");
1742
+ const target = new URL(command.path, options.localServer.origin);
1743
+ if (target.origin !== options.localServer.origin.origin) throw new Error("\u8C03\u8BD5\u8DEF\u5F84\u65E0\u6548");
1744
+ if ((await options.controller.project()).buddyId !== options.buddyId) throw new Error("\u5DE5\u7A0B buddyId \u5DF2\u6539\u53D8\uFF0C\u8BF7\u9000\u51FA CLI \u540E\u91CD\u65B0\u8FDE\u63A5\u5BF9\u5E94\u642D\u5B50");
1745
+ signal.throwIfAborted();
1746
+ if (interrupted) throw new Error("\u672C\u5730\u8FDE\u63A5\u5DF2\u4E2D\u65AD");
1747
+ const response = await fetch(target, {
1748
+ method: command.method,
1749
+ redirect: "error",
1750
+ signal: AbortSignal.any([signal, AbortSignal.timeout(28e3)]),
1751
+ headers: { Cookie: cookie, ...command.body ? { "Content-Type": "application/json" } : {} },
1752
+ ...command.body ? { body: command.body } : {}
1753
+ });
1754
+ const chunks = [];
1755
+ let size = 0;
1756
+ for await (const chunk of response.body ?? []) {
1757
+ size += chunk.length;
1758
+ if (size > 8 * 1024 * 1024) throw new Error("\u8C03\u8BD5\u54CD\u5E94\u8FC7\u5927");
1759
+ chunks.push(Buffer.from(chunk));
1760
+ }
1761
+ replies.set(command.id, { id: command.id, status: response.status, contentType: response.headers.get("content-type") ?? "application/json", body: Buffer.concat(chunks).toString("base64") });
1762
+ } catch {
1763
+ replies.set(command.id, { id: command.id, status: 502, contentType: "application/json", body: Buffer.from(JSON.stringify({ error: { code: "PREVIEW_LOCAL_FAILED", message: "\u672C\u5730\u8C03\u8BD5\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7EC8\u7AEF\u53CA\u5DE5\u7A0B buddyId" } })).toString("base64") });
1764
+ } finally {
1765
+ running.delete(command.id);
1766
+ }
1767
+ }
1768
+ const completion = (async () => {
1769
+ try {
1770
+ while (!signal.aborted) {
1771
+ try {
1772
+ if (stopping) {
1773
+ await stopping;
1774
+ stopping = void 0;
1775
+ }
1776
+ const result = await call("api/preview/poll", connectionId, { replies: [...replies.values()].slice(0, 1) });
1777
+ lastContact = Date.now();
1778
+ const recovered = interrupted;
1779
+ if (stopping) {
1780
+ await stopping;
1781
+ stopping = void 0;
1782
+ }
1783
+ if (recovered) {
1784
+ interrupted = false;
1785
+ options.onStatus?.("\u5DF2\u91CD\u65B0\u8FDE\u63A5\u540E\u53F0\uFF0C\u8BF7\u5728\u5F00\u53D1\u8C03\u8BD5\u9875\u542F\u52A8 DEV\u3002");
1786
+ }
1787
+ for (const id of result.acknowledged ?? []) {
1788
+ replies.delete(id);
1789
+ completed.add(id);
1790
+ }
1791
+ while (completed.size > 256) completed.delete(completed.values().next().value);
1792
+ for (const command of result.commands ?? []) {
1793
+ if (recovered && !running.has(command.id) && !replies.has(command.id) && !completed.has(command.id)) {
1794
+ replies.set(command.id, { id: command.id, status: 409, contentType: "application/json", body: Buffer.from(JSON.stringify({ error: { code: "PREVIEW_RECONNECTED", message: "\u672C\u5730\u8FDE\u63A5\u5DF2\u6062\u590D\uFF0C\u8BF7\u91CD\u65B0\u64CD\u4F5C" } })).toString("base64") });
1795
+ }
1796
+ if (!running.has(command.id) && !replies.has(command.id) && !completed.has(command.id) && running.size < 32) running.set(command.id, execute(command));
1797
+ }
1798
+ } catch (cause) {
1799
+ if (signal.aborted) break;
1800
+ const status = cause.status;
1801
+ if (status === 410) {
1802
+ await options.controller.stop();
1803
+ if ((await attach()).reused) throw new Error("\u5F53\u524D\u5DE5\u7A0B\u5DF2\u6709\u5176\u4ED6 CLI \u8FDE\u63A5\uFF0C\u8BF7\u91CD\u65B0\u8FD0\u884C preview \u6253\u5F00\u540E\u53F0");
1804
+ } else if (status && status >= 400 && status < 500 && status !== 429) throw cause;
1805
+ }
1806
+ await delay(replies.size ? 20 : 500, void 0, { signal }).catch(() => void 0);
1807
+ }
1808
+ } finally {
1809
+ clearInterval(watchdog);
1810
+ }
1811
+ })();
1812
+ void completion.catch(() => void 0);
1813
+ return {
1814
+ reused: false,
1815
+ completion,
1816
+ async close() {
1817
+ shutdown.abort();
1818
+ await completion.catch(() => void 0);
1819
+ await Promise.allSettled(running.values());
1820
+ await stopping;
1821
+ await options.controller.stop();
1822
+ await call("api/preview/disconnect", connectionId, {}, AbortSignal.timeout(2e3)).catch(() => void 0);
1823
+ }
1824
+ };
1825
+ }
1665
1826
  export {
1666
1827
  MlbSimulatorClient,
1667
1828
  PreviewController,
1829
+ connectPreviewBridge,
1668
1830
  createBuddyPreviewUrl,
1669
1831
  startPreviewControlServer
1670
1832
  };