@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/README.md +10 -2
- package/dist/bin/buddy.js +59 -12
- package/dist/bin/buddy.js.map +2 -2
- package/dist/bin/core.js +499 -159
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +152 -1
- package/dist/bin/preview.js.map +4 -4
- package/dist/web/app.css +34 -0
- package/dist/web/app.js +89 -11
- package/dist/web/widgets.js +3 -2
- package/package.json +3 -3
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
|
|
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", () => {
|
package/dist/web/widgets.js
CHANGED
|
@@ -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)}
|
|
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.
|
|
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.
|
|
43
|
-
"@buddy/preview": "0.
|
|
42
|
+
"@buddy/cli-core": "0.13.0",
|
|
43
|
+
"@buddy/preview": "0.13.0"
|
|
44
44
|
}
|
|
45
45
|
}
|