botschat 0.1.12 → 0.1.13
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 +11 -15
- package/migrations/0012_push_tokens.sql +11 -0
- package/package.json +7 -1
- package/packages/api/src/do/connection-do.ts +90 -1
- package/packages/api/src/env.ts +2 -0
- package/packages/api/src/index.ts +4 -1
- package/packages/api/src/routes/auth.ts +39 -6
- package/packages/api/src/routes/push.ts +52 -0
- package/packages/api/src/utils/fcm.ts +167 -0
- package/packages/api/src/utils/firebase.ts +89 -1
- package/packages/plugin/package.json +1 -1
- package/packages/web/dist/assets/index-B9qN5gs6.js +1 -0
- package/packages/web/dist/assets/index-BQNMGVyU.js +2 -0
- package/packages/web/dist/assets/{index-CCBhODDo.css → index-Bd_RDcgO.css} +1 -1
- package/packages/web/dist/assets/{index-CCFgKLX_.js → index-Civeg2lm.js} +1 -1
- package/packages/web/dist/assets/index-Dk33VSnY.js +2 -0
- package/packages/web/dist/assets/index-Kr85Nj_-.js +1516 -0
- package/packages/web/dist/assets/{index-Dx64BDkP.js → index-lVB82JKU.js} +1 -1
- package/packages/web/dist/assets/index.esm-CtMkqqqb.js +599 -0
- package/packages/web/dist/assets/{web-DJQW-VLX.js → web-CUXjh_UA.js} +1 -1
- package/packages/web/dist/assets/web-vKLTVUul.js +1 -0
- package/packages/web/dist/index.html +6 -4
- package/packages/web/dist/sw.js +158 -1
- package/packages/web/index.html +4 -2
- package/packages/web/src/App.tsx +42 -2
- package/packages/web/src/api.ts +10 -0
- package/packages/web/src/components/AccountSettings.tsx +131 -0
- package/packages/web/src/components/DataConsentModal.tsx +249 -0
- package/packages/web/src/components/LoginPage.tsx +49 -9
- package/packages/web/src/firebase.ts +89 -2
- package/packages/web/src/foreground.ts +51 -0
- package/packages/web/src/main.tsx +2 -1
- package/packages/web/src/push.ts +205 -0
- package/scripts/dev.sh +139 -13
- package/scripts/mock-openclaw.mjs +382 -0
- package/packages/web/dist/assets/index-D8mBAwjS.js +0 -1516
- package/packages/web/dist/assets/index-E-nzPZl8.js +0 -2
|
@@ -11,7 +11,8 @@ initAnalytics();
|
|
|
11
11
|
if (Capacitor.isNativePlatform()) {
|
|
12
12
|
// Configure status bar and keyboard for native app
|
|
13
13
|
import("@capacitor/status-bar").then(({ StatusBar, Style }) => {
|
|
14
|
-
|
|
14
|
+
const isDark = document.documentElement.getAttribute("data-theme") !== "light";
|
|
15
|
+
StatusBar.setStyle({ style: isDark ? Style.Dark : Style.Light }).catch(() => {});
|
|
15
16
|
StatusBar.setOverlaysWebView({ overlay: true }).catch(() => {});
|
|
16
17
|
});
|
|
17
18
|
import("@capacitor/keyboard").then(({ Keyboard }) => {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Push notification initialization for Web (FCM) and Native (Capacitor).
|
|
3
|
+
*
|
|
4
|
+
* - Web: Firebase Cloud Messaging + Service Worker
|
|
5
|
+
* - iOS/Android: @capacitor/push-notifications
|
|
6
|
+
*
|
|
7
|
+
* For E2E encrypted messages, the E2E key is synced to IndexedDB so the
|
|
8
|
+
* Service Worker (web) or native handler can decrypt before showing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Capacitor } from "@capacitor/core";
|
|
12
|
+
import { pushApi } from "./api";
|
|
13
|
+
import { dlog } from "./debug-log";
|
|
14
|
+
import { E2eService } from "./e2e";
|
|
15
|
+
|
|
16
|
+
let initialized = false;
|
|
17
|
+
|
|
18
|
+
// ---- IndexedDB helpers for SW E2E key sync ----
|
|
19
|
+
|
|
20
|
+
const IDB_NAME = "botschat-sw";
|
|
21
|
+
const IDB_STORE = "keys";
|
|
22
|
+
const IDB_KEY = "e2e_key";
|
|
23
|
+
|
|
24
|
+
function openDB(): Promise<IDBDatabase> {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const req = indexedDB.open(IDB_NAME, 1);
|
|
27
|
+
req.onupgradeneeded = () => {
|
|
28
|
+
req.result.createObjectStore(IDB_STORE);
|
|
29
|
+
};
|
|
30
|
+
req.onsuccess = () => resolve(req.result);
|
|
31
|
+
req.onerror = () => reject(req.error);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Sync the current E2E key to IndexedDB so the Service Worker can decrypt. */
|
|
36
|
+
export async function syncE2eKeyToSW(): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
const db = await openDB();
|
|
39
|
+
const tx = db.transaction(IDB_STORE, "readwrite");
|
|
40
|
+
const store = tx.objectStore(IDB_STORE);
|
|
41
|
+
|
|
42
|
+
// Read the cached key from localStorage (base64-encoded Uint8Array)
|
|
43
|
+
const cachedKeyB64 = localStorage.getItem("botschat_e2e_key_cache");
|
|
44
|
+
if (cachedKeyB64) {
|
|
45
|
+
// Decode base64 to Uint8Array and store in IDB
|
|
46
|
+
const binary = atob(cachedKeyB64);
|
|
47
|
+
const key = new Uint8Array(binary.length);
|
|
48
|
+
for (let i = 0; i < binary.length; i++) {
|
|
49
|
+
key[i] = binary.charCodeAt(i);
|
|
50
|
+
}
|
|
51
|
+
store.put(key, IDB_KEY);
|
|
52
|
+
} else {
|
|
53
|
+
store.delete(IDB_KEY);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await new Promise<void>((resolve, reject) => {
|
|
57
|
+
tx.oncomplete = () => resolve();
|
|
58
|
+
tx.onerror = () => reject(tx.error);
|
|
59
|
+
});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
dlog.warn("Push", "Failed to sync E2E key to SW IndexedDB", err);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Clear the E2E key from SW IndexedDB (call on logout or key clear). */
|
|
66
|
+
export async function clearE2eKeyFromSW(): Promise<void> {
|
|
67
|
+
try {
|
|
68
|
+
const db = await openDB();
|
|
69
|
+
const tx = db.transaction(IDB_STORE, "readwrite");
|
|
70
|
+
tx.objectStore(IDB_STORE).delete(IDB_KEY);
|
|
71
|
+
await new Promise<void>((resolve, reject) => {
|
|
72
|
+
tx.oncomplete = () => resolve();
|
|
73
|
+
tx.onerror = () => reject(tx.error);
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
// Ignore — best effort
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---- Push initialization ----
|
|
81
|
+
|
|
82
|
+
export async function initPushNotifications(): Promise<void> {
|
|
83
|
+
if (initialized) return;
|
|
84
|
+
|
|
85
|
+
// Sync E2E key so push notifications can be decrypted
|
|
86
|
+
await syncE2eKeyToSW();
|
|
87
|
+
|
|
88
|
+
// Subscribe to E2E key changes to keep SW in sync
|
|
89
|
+
E2eService.subscribe(() => {
|
|
90
|
+
syncE2eKeyToSW().catch(() => {});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (Capacitor.isNativePlatform()) {
|
|
94
|
+
await initNativePush();
|
|
95
|
+
} else {
|
|
96
|
+
await initWebPush();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
initialized = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---- Web Push (Firebase Cloud Messaging) ----
|
|
103
|
+
|
|
104
|
+
async function initWebPush(): Promise<void> {
|
|
105
|
+
try {
|
|
106
|
+
if (!("Notification" in self)) {
|
|
107
|
+
dlog.warn("Push", "Notifications not supported in this browser");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const permission = await Notification.requestPermission();
|
|
112
|
+
if (permission !== "granted") {
|
|
113
|
+
dlog.warn("Push", "Notification permission denied");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { getMessaging, getToken, onMessage } = await import("firebase/messaging");
|
|
118
|
+
const { ensureFirebaseApp } = await import("./firebase");
|
|
119
|
+
|
|
120
|
+
const firebaseApp = ensureFirebaseApp();
|
|
121
|
+
if (!firebaseApp) {
|
|
122
|
+
dlog.warn("Push", "Firebase not configured (missing env vars)");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const messaging = getMessaging(firebaseApp);
|
|
127
|
+
|
|
128
|
+
// Get service worker registration
|
|
129
|
+
const registration = await navigator.serviceWorker.getRegistration();
|
|
130
|
+
if (!registration) {
|
|
131
|
+
dlog.warn("Push", "No service worker registration found");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const vapidKey = import.meta.env.VITE_FIREBASE_VAPID_KEY as string;
|
|
136
|
+
if (!vapidKey) {
|
|
137
|
+
dlog.warn("Push", "VITE_FIREBASE_VAPID_KEY not set — skipping web push");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const fcmToken = await getToken(messaging, {
|
|
142
|
+
vapidKey,
|
|
143
|
+
serviceWorkerRegistration: registration,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (fcmToken) {
|
|
147
|
+
dlog.info("Push", `FCM token obtained (${fcmToken.slice(0, 20)}...)`);
|
|
148
|
+
await pushApi.register(fcmToken, "web");
|
|
149
|
+
dlog.info("Push", "Token registered with backend");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Suppress foreground notifications (WS already delivers the message)
|
|
153
|
+
onMessage(messaging, (_payload) => {
|
|
154
|
+
dlog.info("Push", "Foreground FCM message received (suppressed)");
|
|
155
|
+
});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
dlog.error("Push", "Web push init failed", err);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---- Native Push (Capacitor) ----
|
|
162
|
+
|
|
163
|
+
async function initNativePush(): Promise<void> {
|
|
164
|
+
try {
|
|
165
|
+
const { PushNotifications } = await import("@capacitor/push-notifications");
|
|
166
|
+
|
|
167
|
+
const permResult = await PushNotifications.requestPermissions();
|
|
168
|
+
if (permResult.receive !== "granted") {
|
|
169
|
+
dlog.warn("Push", "Native push permission denied");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await PushNotifications.register();
|
|
174
|
+
|
|
175
|
+
PushNotifications.addListener("registration", async (token) => {
|
|
176
|
+
dlog.info("Push", `Native push token: ${token.value.slice(0, 20)}...`);
|
|
177
|
+
const platform = Capacitor.getPlatform() as "ios" | "android";
|
|
178
|
+
await pushApi.register(token.value, platform);
|
|
179
|
+
dlog.info("Push", "Native token registered with backend");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
PushNotifications.addListener("registrationError", (error) => {
|
|
183
|
+
dlog.error("Push", "Native push registration failed", error);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Data-only messages arrive here in foreground — suppress (WS handles it)
|
|
187
|
+
PushNotifications.addListener("pushNotificationReceived", (_notification) => {
|
|
188
|
+
dlog.info("Push", "Foreground native notification (suppressed)");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// User tapped a notification (app was in background)
|
|
192
|
+
PushNotifications.addListener("pushNotificationActionPerformed", (action) => {
|
|
193
|
+
dlog.info("Push", "Notification tapped", action);
|
|
194
|
+
// TODO: navigate to specific session from action.notification.data
|
|
195
|
+
});
|
|
196
|
+
} catch (err) {
|
|
197
|
+
dlog.error("Push", "Native push init failed", err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Unregister push token (call on logout). */
|
|
202
|
+
export async function unregisterPush(): Promise<void> {
|
|
203
|
+
initialized = false;
|
|
204
|
+
await clearE2eKeyFromSW();
|
|
205
|
+
}
|
package/scripts/dev.sh
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# BotsChat local dev startup script
|
|
3
3
|
# Usage:
|
|
4
|
-
# ./scripts/dev.sh — build
|
|
5
|
-
# ./scripts/dev.sh reset — nuke local DB, re-migrate, then start
|
|
4
|
+
# ./scripts/dev.sh — full dev env: build + migrate + server + mock AI + open browser
|
|
5
|
+
# ./scripts/dev.sh reset — nuke local DB, re-migrate, then start full dev env
|
|
6
|
+
# ./scripts/dev.sh server — only start wrangler dev server (no mock, no browser)
|
|
6
7
|
# ./scripts/dev.sh migrate — only run D1 migrations (no server)
|
|
7
8
|
# ./scripts/dev.sh build — only build web frontend (no server)
|
|
8
9
|
# ./scripts/dev.sh sync — sync plugin to mini.local + rebuild + restart gateway
|
|
9
10
|
# ./scripts/dev.sh logs — tail gateway logs on mini.local
|
|
11
|
+
# ./scripts/dev.sh mock — start mock OpenClaw standalone (foreground)
|
|
10
12
|
set -euo pipefail
|
|
11
13
|
|
|
12
14
|
cd "$(dirname "$0")/.."
|
|
13
15
|
ROOT="$(pwd)"
|
|
14
16
|
|
|
17
|
+
# ── Auto-set DEV_AUTH_SECRET ──────────────────────────────────────────
|
|
18
|
+
# For local dev, any string works. Use a fixed default so new developers
|
|
19
|
+
# can run `./scripts/dev.sh` without setting env vars first.
|
|
20
|
+
if [[ -z "${DEV_AUTH_SECRET:-}" ]]; then
|
|
21
|
+
DEV_AUTH_SECRET="botschat-local-dev-secret"
|
|
22
|
+
export DEV_AUTH_SECRET
|
|
23
|
+
fi
|
|
24
|
+
|
|
15
25
|
# ── Colours ──────────────────────────────────────────────────────────
|
|
16
|
-
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; CYAN='\033[0;36m'; NC='\033[0m'
|
|
26
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; CYAN='\033[0;36m'; DIM='\033[2m'; NC='\033[0m'
|
|
17
27
|
info() { echo -e "${CYAN}▸${NC} $*"; }
|
|
18
28
|
ok() { echo -e "${GREEN}✔${NC} $*"; }
|
|
19
29
|
warn() { echo -e "${YELLOW}▲${NC} $*"; }
|
|
20
30
|
fail() { echo -e "${RED}✖${NC} $*"; exit 1; }
|
|
21
31
|
|
|
32
|
+
# ── Process tracking ─────────────────────────────────────────────────
|
|
33
|
+
WRANGLER_PID=""
|
|
34
|
+
MOCK_PID=""
|
|
35
|
+
|
|
36
|
+
cleanup() {
|
|
37
|
+
[[ -n "$MOCK_PID" ]] && kill "$MOCK_PID" 2>/dev/null || true
|
|
38
|
+
[[ -n "$WRANGLER_PID" ]] && kill "$WRANGLER_PID" 2>/dev/null || true
|
|
39
|
+
wait 2>/dev/null || true
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
# ── Helpers ──────────────────────────────────────────────────────────
|
|
23
43
|
|
|
24
44
|
kill_port() {
|
|
@@ -32,6 +52,55 @@ kill_port() {
|
|
|
32
52
|
fi
|
|
33
53
|
}
|
|
34
54
|
|
|
55
|
+
wait_for_server() {
|
|
56
|
+
info "Waiting for server…"
|
|
57
|
+
local max=60 i=0
|
|
58
|
+
while ! curl -sf --max-time 1 -o /dev/null http://localhost:8787/ 2>/dev/null; do
|
|
59
|
+
sleep 1
|
|
60
|
+
i=$((i + 1))
|
|
61
|
+
if [[ $i -ge $max ]]; then
|
|
62
|
+
fail "Server didn't start within ${max}s"
|
|
63
|
+
fi
|
|
64
|
+
done
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get_mock_token() {
|
|
68
|
+
local BASE_URL="http://localhost:8787"
|
|
69
|
+
local TOKEN_JSON AUTH_TOKEN PAT_JSON PAT
|
|
70
|
+
|
|
71
|
+
TOKEN_JSON=$(curl -sf -X POST "$BASE_URL/api/dev-auth/login" \
|
|
72
|
+
-H 'Content-Type: application/json' \
|
|
73
|
+
-d "{\"secret\":\"$DEV_AUTH_SECRET\",\"userId\":\"dev-test-user\"}" 2>&1) || {
|
|
74
|
+
fail "Cannot reach $BASE_URL — is the server running?"
|
|
75
|
+
}
|
|
76
|
+
AUTH_TOKEN=$(echo "$TOKEN_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])" 2>/dev/null) || {
|
|
77
|
+
fail "Failed to parse auth token: $TOKEN_JSON"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
PAT_JSON=$(curl -sf -X POST "$BASE_URL/api/pairing-tokens" \
|
|
81
|
+
-H "Authorization: Bearer $AUTH_TOKEN" \
|
|
82
|
+
-H 'Content-Type: application/json' \
|
|
83
|
+
-d '{"label":"mock-openclaw"}' 2>&1) || {
|
|
84
|
+
fail "Failed to create pairing token"
|
|
85
|
+
}
|
|
86
|
+
PAT=$(echo "$PAT_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])" 2>/dev/null) || {
|
|
87
|
+
fail "Failed to parse pairing token: $PAT_JSON"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
echo "$PAT"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
open_browser() {
|
|
94
|
+
local url="http://localhost:8787/?dev_token=${DEV_AUTH_SECRET}"
|
|
95
|
+
if command -v open &>/dev/null; then
|
|
96
|
+
open "$url"
|
|
97
|
+
elif command -v xdg-open &>/dev/null; then
|
|
98
|
+
xdg-open "$url"
|
|
99
|
+
else
|
|
100
|
+
warn "Open in browser: $url"
|
|
101
|
+
fi
|
|
102
|
+
}
|
|
103
|
+
|
|
35
104
|
do_migrate() {
|
|
36
105
|
info "Applying D1 migrations (local)…"
|
|
37
106
|
npx wrangler d1 migrations apply botschat-db --local
|
|
@@ -51,20 +120,68 @@ do_build_web() {
|
|
|
51
120
|
ok "Web build complete (packages/web/dist)"
|
|
52
121
|
}
|
|
53
122
|
|
|
123
|
+
# ── Server-only start (foreground, no mock/browser) ──────────────────
|
|
124
|
+
|
|
54
125
|
do_start() {
|
|
55
126
|
kill_port 8787
|
|
56
127
|
info "Starting wrangler dev on 0.0.0.0:8787…"
|
|
57
|
-
exec npx wrangler dev --config wrangler.toml --ip 0.0.0.0 --var ENVIRONMENT:development --var DEV_AUTH_SECRET:"$
|
|
128
|
+
exec npx wrangler dev --config wrangler.toml --ip 0.0.0.0 --var ENVIRONMENT:development --var DEV_AUTH_SECRET:"$DEV_AUTH_SECRET"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# ── Full dev environment (server + mock + browser) ───────────────────
|
|
132
|
+
|
|
133
|
+
do_start_full() {
|
|
134
|
+
kill_port 8787
|
|
135
|
+
trap cleanup EXIT INT TERM
|
|
136
|
+
|
|
137
|
+
info "Starting wrangler dev on 0.0.0.0:8787…"
|
|
138
|
+
npx wrangler dev --config wrangler.toml --ip 0.0.0.0 \
|
|
139
|
+
--var ENVIRONMENT:development \
|
|
140
|
+
--var DEV_AUTH_SECRET:"$DEV_AUTH_SECRET" &
|
|
141
|
+
WRANGLER_PID=$!
|
|
142
|
+
|
|
143
|
+
wait_for_server
|
|
144
|
+
ok "Server ready on http://localhost:8787"
|
|
145
|
+
|
|
146
|
+
info "Starting Mock OpenClaw…"
|
|
147
|
+
local PAT
|
|
148
|
+
PAT=$(get_mock_token)
|
|
149
|
+
mkdir -p "$ROOT/.wrangler"
|
|
150
|
+
node "$ROOT/scripts/mock-openclaw.mjs" --token "$PAT" > "$ROOT/.wrangler/mock-openclaw.log" 2>&1 &
|
|
151
|
+
MOCK_PID=$!
|
|
152
|
+
ok "Mock OpenClaw connected (pid=$MOCK_PID)"
|
|
153
|
+
|
|
154
|
+
open_browser
|
|
155
|
+
|
|
156
|
+
echo ""
|
|
157
|
+
echo -e "${CYAN}╭──────────────────────────────────────────────────╮${NC}"
|
|
158
|
+
echo -e "${CYAN}│${NC} ${GREEN}BotsChat Dev Environment Ready${NC} ${CYAN}│${NC}"
|
|
159
|
+
echo -e "${CYAN}│${NC} ${CYAN}│${NC}"
|
|
160
|
+
echo -e "${CYAN}│${NC} Server: http://localhost:8787 ${CYAN}│${NC}"
|
|
161
|
+
echo -e "${CYAN}│${NC} Mock AI: running ${DIM}(log: .wrangler/mock-openclaw.log)${NC}"
|
|
162
|
+
echo -e "${CYAN}│${NC} Auth: auto-login enabled ${CYAN}│${NC}"
|
|
163
|
+
echo -e "${CYAN}│${NC} ${CYAN}│${NC}"
|
|
164
|
+
echo -e "${CYAN}│${NC} Press ${YELLOW}Ctrl+C${NC} to stop all services ${CYAN}│${NC}"
|
|
165
|
+
echo -e "${CYAN}╰──────────────────────────────────────────────────╯${NC}"
|
|
166
|
+
echo ""
|
|
167
|
+
|
|
168
|
+
wait $WRANGLER_PID 2>/dev/null || true
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ── Standalone mock (foreground) ─────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
do_mock() {
|
|
174
|
+
info "Getting auth token via dev-auth…"
|
|
175
|
+
local PAT
|
|
176
|
+
PAT=$(get_mock_token)
|
|
177
|
+
ok "Pairing token: ${PAT:0:16}***"
|
|
178
|
+
info "Starting Mock OpenClaw…"
|
|
179
|
+
exec node "$ROOT/scripts/mock-openclaw.mjs" --token "$PAT" "$@"
|
|
58
180
|
}
|
|
59
181
|
|
|
182
|
+
# ── Sync plugin to mini.local ────────────────────────────────────────
|
|
183
|
+
|
|
60
184
|
do_sync_plugin() {
|
|
61
|
-
# ── IMPORTANT ──────────────────────────────────────────────────
|
|
62
|
-
# Development repo and production plugin MUST be kept separate:
|
|
63
|
-
# Dev repo: mini:~/Projects/botschat-app/botsChat/packages/plugin/
|
|
64
|
-
# Production: mini:~/.openclaw/extensions/botschat/
|
|
65
|
-
# NEVER edit files directly in ~/.openclaw/extensions/botschat/.
|
|
66
|
-
# Always: edit dev repo → build → deploy artifacts to extensions.
|
|
67
|
-
# ────────────────────────────────────────────────────────────────
|
|
68
185
|
local REMOTE="mini.local"
|
|
69
186
|
local DEV_DIR="~/Projects/botschat-app/botsChat/packages/plugin"
|
|
70
187
|
local EXT_DIR="~/.openclaw/extensions/botschat"
|
|
@@ -105,6 +222,11 @@ case "$cmd" in
|
|
|
105
222
|
reset)
|
|
106
223
|
do_reset
|
|
107
224
|
do_build_web
|
|
225
|
+
do_start_full
|
|
226
|
+
;;
|
|
227
|
+
server)
|
|
228
|
+
do_build_web
|
|
229
|
+
do_migrate
|
|
108
230
|
do_start
|
|
109
231
|
;;
|
|
110
232
|
migrate)
|
|
@@ -119,10 +241,14 @@ case "$cmd" in
|
|
|
119
241
|
logs)
|
|
120
242
|
do_logs
|
|
121
243
|
;;
|
|
244
|
+
mock)
|
|
245
|
+
shift
|
|
246
|
+
do_mock "$@"
|
|
247
|
+
;;
|
|
122
248
|
*)
|
|
123
|
-
# Default: build + migrate +
|
|
249
|
+
# Default: full dev experience — build + migrate + server + mock + browser
|
|
124
250
|
do_build_web
|
|
125
251
|
do_migrate
|
|
126
|
-
|
|
252
|
+
do_start_full
|
|
127
253
|
;;
|
|
128
254
|
esac
|