@my-life-buddies/cli 0.11.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.
package/dist/web/app.css CHANGED
@@ -517,3 +517,37 @@
517
517
  .message-avatar img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; }
518
518
  #buddy-profile-avatar { width: 32px; height: 32px; object-fit: contain; border-radius: 50%; }
519
519
  #buddy-profile-avatar[hidden] { display: none; }
520
+
521
+ /* The hosted console keeps debugger and simulator side by side, including narrow panels. */
522
+ body[data-hosted-preview] { min-width:700px; height:100dvh; min-height:0; display:flex; flex-direction:column; overflow-x:auto; overflow-y:hidden; }
523
+ body[data-hosted-preview] .brand { display:none; }
524
+ body[data-hosted-preview] .app-shell { flex:1; min-height:0; display:flex; flex-direction:column; }
525
+ body[data-hosted-preview] .sidebar { position:static; flex:none; width:auto; padding:8px 16px; border-inline-end:0; border-bottom:1px solid var(--color-rule); }
526
+ body[data-hosted-preview] .sidebar-section { margin:0; }
527
+ body[data-hosted-preview] .sidebar-label { display:none; }
528
+ body[data-hosted-preview] .debug-tabs { display:flex; flex-wrap:wrap; gap:8px; }
529
+ body[data-hosted-preview] .debug-tabs button { width:auto; }
530
+ body[data-hosted-preview] .application { margin-inline-start:0; flex:1; min-height:0; display:flex; flex-direction:column; }
531
+ body[data-hosted-preview] .topbar { display:contents; }
532
+ body[data-hosted-preview] .project-heading { display:none; }
533
+ body[data-hosted-preview] .debugger > .sidebar { position:sticky; top:0; z-index:var(--z-sticky); }
534
+ body[data-hosted-preview] .workspace { flex:1; min-height:0; grid-template-columns:minmax(340px,1fr) clamp(340px,40%,520px); overflow:hidden; }
535
+ body[data-hosted-preview] .debugger { overflow-y:auto; overscroll-behavior:contain; }
536
+ body[data-hosted-preview] .simulator-area { position:relative; inset:auto; height:100%; min-height:0; max-height:none; border-block-start:0; border-inline-start:1px solid var(--color-rule); }
537
+ body[data-hosted-preview] .run-strip { padding:20px 24px 0; }
538
+ body[data-hosted-preview] .run-metrics { margin-top:20px; }
539
+ body[data-hosted-preview] .debug-panel { padding:24px; }
540
+ body[data-hosted-preview] #dev-control[data-action="stop"] { background:var(--color-paper-raised); color:var(--color-ink); border:1px solid var(--color-rule); }
541
+ .local-connection { flex:none; padding:10px 20px; border-bottom:1px solid var(--color-rule); background:var(--color-paper-raised); font-size:13px; }
542
+ .local-connection-heading { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:8px 12px; }
543
+ .local-connection-heading strong { grid-column:1 / -1; min-width:0; overflow-wrap:anywhere; font-size:12px; font-weight:500; }
544
+ .local-connection pre { padding:14px; border-radius:8px; background:var(--color-paper); white-space:pre-wrap; overflow-wrap:anywhere; user-select:all; }
545
+ .local-connection p { line-height:1.7; }
546
+ #connect-instructions { max-height:250px; overflow-y:auto; }
547
+ @media (max-width:900px) {
548
+ body[data-hosted-preview] .workspace { grid-template-columns:minmax(340px,1fr) 320px; }
549
+ body[data-hosted-preview] .run-heading { flex-direction:column; gap:12px; }
550
+ body[data-hosted-preview] .run-identity, body[data-hosted-preview] .run-controls { flex:auto; width:100%; }
551
+ body[data-hosted-preview] .run-strip, body[data-hosted-preview] .debug-panel { padding-inline:16px; }
552
+ body[data-hosted-preview] .run-metrics { gap:8px; }
553
+ }
package/dist/web/app.js CHANGED
@@ -1,6 +1,17 @@
1
1
  (() => {
2
2
  "use strict";
3
3
 
4
+ const hosted = document.body?.hasAttribute?.("data-hosted-preview") === true;
5
+ const hostedBuddyId = hosted ? new URL(location.href).searchParams.get("buddyId") : "";
6
+ const connectionUrl = hosted ? new URL(`../api/buddies/${encodeURIComponent(hostedBuddyId)}/preview`, location.href) : undefined;
7
+ let connectionInstance = "", connectionReady = !hosted, connectionBusy = false, devPollBusy = false;
8
+ function previewApiUrl(path) {
9
+ if (!hosted) return path;
10
+ const url = new URL(connectionUrl.href + path);
11
+ url.searchParams.set("instance", connectionInstance);
12
+ return url.href;
13
+ }
14
+ window.previewApiUrl = previewApiUrl;
4
15
  const $ = (selector) => document.querySelector(selector);
5
16
  const elements = {
6
17
  projectName: $("#project-name"),
@@ -88,7 +99,8 @@
88
99
  }
89
100
 
90
101
  async function api(path, options = {}) {
91
- const response = await fetch(path, {
102
+ const instance = connectionInstance;
103
+ const response = await fetch(previewApiUrl(path), {
92
104
  ...options,
93
105
  headers: {
94
106
  Accept: "application/json",
@@ -97,8 +109,9 @@
97
109
  },
98
110
  });
99
111
  const value = await response.json().catch(() => ({}));
112
+ if (hosted && (!connectionReady || instance !== connectionInstance)) throw new Error("本地连接已改变,请等待重新连接");
100
113
  if (!response.ok) {
101
- const error = new Error(value?.error?.message || `请求失败(HTTP ${response.status})`);
114
+ const error = new Error((typeof value?.error === "string" ? value.error : value?.error?.message) || `请求失败(HTTP ${response.status})`);
102
115
  error.code = value?.error?.code || "PREVIEW_REQUEST_FAILED";
103
116
  error.status = response.status;
104
117
  throw error;
@@ -172,9 +185,10 @@
172
185
 
173
186
  function phaseCopy(phase) {
174
187
  return {
175
- idle: "未启动",
188
+ idle: hosted ? "已连接但 DEV 未启动" : "未启动",
189
+ disconnected: "未连接本地工程",
176
190
  starting: "正在启动",
177
- ready: "DEV 在线",
191
+ ready: "DEV 运行中",
178
192
  stopping: "正在停止",
179
193
  failed: "启动失败",
180
194
  }[phase] || phase;
@@ -230,11 +244,11 @@
230
244
  let pairingRequest = 0;
231
245
 
232
246
  function renderDev() {
233
- const phase = state.dev.phase;
247
+ const phase = connectionReady ? state.dev.phase : "disconnected";
234
248
  if (!elements.pairingPanel.hidden && pairingRevision !== state.dev.revision) void togglePairing(true);
235
249
  elements.connectionState.dataset.phase = phase;
236
- elements.connectionLabel.textContent = phaseCopy(phase);
237
- elements.devControl.disabled = phase === "starting" || phase === "stopping";
250
+ elements.connectionLabel.textContent = phase === "disconnected" ? "未连接本地工程" : phaseCopy(phase);
251
+ elements.devControl.disabled = !connectionReady || phase === "starting" || phase === "stopping";
238
252
  elements.devControl.dataset.action = phase === "ready" ? "stop" : "start";
239
253
  elements.devControl.textContent = phase === "ready" ? "停止 DEV" : phase === "starting" ? "正在启动…" : phase === "stopping" ? "正在停止…" : "启动 DEV";
240
254
  elements.environmentAgent.textContent = phase === "ready"
@@ -538,17 +552,17 @@
538
552
  }
539
553
 
540
554
  function syncPolling() {
541
- if (state.dev.phase === "ready" && state.polling === undefined) {
555
+ if (connectionReady && state.dev.phase === "ready" && state.polling === undefined) {
542
556
  void refreshSimulator();
543
557
  state.polling = window.setInterval(() => void refreshSimulator(), 1200);
544
- } else if (state.dev.phase !== "ready" && state.polling !== undefined) {
558
+ } else if ((!connectionReady || state.dev.phase !== "ready") && state.polling !== undefined) {
545
559
  window.clearInterval(state.polling);
546
560
  state.polling = undefined;
547
561
  }
548
562
  }
549
563
 
550
564
  async function controlDev() {
551
- if (state.dev.phase === "starting" || state.dev.phase === "stopping") return;
565
+ if (!connectionReady || state.dev.phase === "starting" || state.dev.phase === "stopping") return;
552
566
  elements.devControl.disabled = true;
553
567
  try {
554
568
  const path = state.dev.phase === "ready" ? "/api/dev/stop" : "/api/dev/start";
@@ -738,6 +752,7 @@
738
752
  }
739
753
 
740
754
  async function boot() {
755
+ if (hosted && !connectionReady) { void refreshConnection(); return; }
741
756
  try {
742
757
  const [project, dev, debug] = await Promise.all([
743
758
  api("/api/project"),
@@ -748,7 +763,7 @@
748
763
  state.dev = dev.state;
749
764
  state.debug = debug.debug;
750
765
  renderAll();
751
- connectEvents();
766
+ if (!hosted) connectEvents();
752
767
  // Load the cloud profile independently of Agent startup. If it fails,
753
768
  // the local project and DEV controls remain usable and show the error.
754
769
  const { buddy } = await api("/api/buddy");
@@ -759,6 +774,69 @@
759
774
  }
760
775
  }
761
776
 
777
+ function disconnected(message = "未连接本地工程") {
778
+ connectionReady = false;
779
+ state.dev = { phase: "idle", revision: -1 };
780
+ state.simulatorRequest++;
781
+ widgets?.reset();
782
+ void togglePairing(false);
783
+ renderDev(); syncPolling();
784
+ $("#local-status").textContent = message;
785
+ }
786
+
787
+ async function refreshConnection() {
788
+ if (connectionBusy) return;
789
+ connectionBusy = true;
790
+ try {
791
+ const response = await fetch(connectionUrl, { signal: AbortSignal.timeout(5000) });
792
+ const value = await response.json();
793
+ if (!response.ok) throw new Error(value.error || "连接状态读取失败");
794
+ $("#connect-command").textContent = value.command;
795
+ $("#copy-connect-command").disabled = false;
796
+ if (!value.connected) { disconnected(); return; }
797
+ const changed = connectionInstance !== value.instanceId;
798
+ const newlyConnected = !connectionReady || changed;
799
+ connectionInstance = value.instanceId;
800
+ connectionReady = true;
801
+ $("#local-status").textContent = ["已连接本地工程", value.machineName, value.projectRoot].filter(Boolean).join(" · ");
802
+ if (newlyConnected) {
803
+ state.dev = { phase: "idle", revision: -1 };
804
+ if (changed) {
805
+ state.simulator = { messages: [] }; state.debug = { runs: [], events: [], messages: [] };
806
+ state.selectedRunId = ""; pendingSend = undefined; state.buddy = undefined;
807
+ }
808
+ elements.errorBanner.hidden = true;
809
+ await boot();
810
+ }
811
+ } catch (cause) { disconnected(cause.message || "连接已中断,正在重试…"); }
812
+ finally { connectionBusy = false; }
813
+ }
814
+
815
+ async function pollHostedDev() {
816
+ if (!connectionReady || devPollBusy) return;
817
+ devPollBusy = true;
818
+ try {
819
+ const [dev, debug] = await Promise.all([api("/api/dev"), api("/api/debug")]);
820
+ applyDevState(dev.state); state.debug = debug.debug; renderAll();
821
+ } catch (cause) { showError(cause); }
822
+ finally { devPollBusy = false; }
823
+ }
824
+
825
+ if (hosted) {
826
+ renderDev();
827
+ $("#get-connect-command").addEventListener("click", () => {
828
+ $("#connect-instructions").hidden = !$("#connect-instructions").hidden;
829
+ void refreshConnection();
830
+ });
831
+ $("#copy-connect-command").addEventListener("click", async () => {
832
+ try { await navigator.clipboard.writeText($("#connect-command").textContent); toast("连接指令已复制,请在当前搭子的工程目录执行"); }
833
+ catch { showError(new Error("请手动复制下方的完整连接指令")); }
834
+ });
835
+ const statusTimer = window.setInterval(() => void refreshConnection(), 2000);
836
+ const devTimer = window.setInterval(() => void pollHostedDev(), 1200);
837
+ window.addEventListener("pagehide", () => { window.clearInterval(statusTimer); window.clearInterval(devTimer); window.clearInterval(state.polling); });
838
+ }
839
+
762
840
  elements.resetConversation.addEventListener("click", () => void resetConversation());
763
841
  elements.latestMessages.addEventListener("click", followLatestMessages);
764
842
  elements.chatMessages.addEventListener("scroll", () => {
@@ -1,5 +1,6 @@
1
1
  /* Client reference: my-life-buddies-ios dev 547818d, DadaConversationWidgets / DadaHostedWidgetPage. */
2
2
  (() => {
3
+ const previewUrl = path => window.previewApiUrl?.(path) ?? path;
3
4
  const el = (tag, className, text) => { const n = document.createElement(tag); n.className = className; if (text !== undefined) n.textContent = text; return n; };
4
5
  window.createSimulatorWidgets = ({ api, avatar }) => {
5
6
  const shortcut = document.querySelector('#widget-shortcut');
@@ -77,7 +78,7 @@
77
78
  for (const child of phone.children) if (child !== overlay) child.inert = true;
78
79
  dismiss.focus();
79
80
  try {
80
- const url = `/api/simulator/widgets/${encodeURIComponent(item.id)}`;
81
+ const url = previewUrl(`/api/simulator/widgets/${encodeURIComponent(item.id)}`);
81
82
  // Probe errors in the parent; actual HTML executes only inside the opaque sandbox document.
82
83
  const response = await fetch(url, { credentials: 'same-origin' });
83
84
  if (!response.ok) { const error = await response.json(); throw new Error(error.error?.message || error.error || '小挂件暂时无法打开'); }
@@ -117,7 +118,7 @@
117
118
  card(item) {
118
119
  const card = el('div', 'widget-entry'); card.append(el('h4', '', item.title));
119
120
  const details = el('div', 'widget-entry-details');
120
- if (item.type && item.imageMediaId) { const image = el('img', 'widget-entry-photo'); image.alt = `${item.title}的照片`; image.src = `/api/simulator/widgets/${encodeURIComponent(item.id)}/image?mediaId=${encodeURIComponent(item.imageMediaId)}`; image.addEventListener('error', () => { image.hidden = true; }); details.append(image); }
121
+ if (item.type && item.imageMediaId) { const image = el('img', 'widget-entry-photo'); image.alt = `${item.title}的照片`; image.src = previewUrl(`/api/simulator/widgets/${encodeURIComponent(item.id)}/image?mediaId=${encodeURIComponent(item.imageMediaId)}`); image.addEventListener('error', () => { image.hidden = true; }); details.append(image); }
121
122
  details.append(el('p', '', item.type ? item.summary?.trim() || '打开查看最新内容' : '此小挂件已删除或不可用')); card.append(details);
122
123
  if (item.type) card.append(button('去查看', () => open(item), 'widget-entry-open'));
123
124
  return card;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@my-life-buddies/cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "搭搭 Developer Platform 命令行工具",
6
6
  "publishConfig": {
@@ -39,7 +39,7 @@
39
39
  "ws": "8.21.3"
40
40
  },
41
41
  "devDependencies": {
42
- "@buddy/cli-core": "0.11.0",
43
- "@buddy/preview": "0.11.0"
42
+ "@buddy/cli-core": "0.13.0",
43
+ "@buddy/preview": "0.13.0"
44
44
  }
45
45
  }