piclaw 0.0.13 → 0.0.14

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.
@@ -13,8 +13,8 @@
13
13
  <meta name="mobile-web-app-capable" content="yes" />
14
14
  <link rel="icon" type="image/svg+xml" href="/icon.svg" />
15
15
  <link rel="manifest" href="/manifest.json" />
16
- <script type="module" crossorigin src="/assets/index-DbZX-IVJ.js"></script>
17
- <link rel="stylesheet" crossorigin href="/assets/index-1zeVzeIQ.css">
16
+ <script type="module" crossorigin src="/assets/index-pwr3Q8Ee.js"></script>
17
+ <link rel="stylesheet" crossorigin href="/assets/index-DIAPMG-u.css">
18
18
  </head>
19
19
  <body style="background-color: #0a0e14">
20
20
  <div id="app"></div>
@@ -0,0 +1,149 @@
1
+ import { a as __require } from "../_runtime.mjs";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ /**
5
+ * Dummy PTY — fallback when no real PTY backend (Bun / node-pty) is available.
6
+ *
7
+ * Provides basic line-editing (backspace, Ctrl+C/D/L) and executes commands
8
+ * via `child_process.execFile` with a shell. No true PTY allocation — ANSI
9
+ * escape sequences from child processes won't work, but simple commands run fine.
10
+ */
11
+ function spawnDummyPty(opts) {
12
+ const dataListeners = /* @__PURE__ */ new Set();
13
+ const exitListeners = /* @__PURE__ */ new Set();
14
+ let cwd = opts?.cwd ?? process.cwd();
15
+ const env = { ...opts?.env };
16
+ const shell = opts?.shell ?? process.env.SHELL ?? "/bin/sh";
17
+ let line = "";
18
+ let alive = true;
19
+ let busy = false;
20
+ let pending = "";
21
+ function emit(data) {
22
+ for (const cb of dataListeners) cb(data);
23
+ }
24
+ function prompt() {
25
+ const home = env.HOME || "";
26
+ emit(`\x1b[1;32m${home && cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd}\x1b[0m $ `);
27
+ }
28
+ function exec(command) {
29
+ const trimmed = command.trim();
30
+ if (!trimmed) {
31
+ prompt();
32
+ return;
33
+ }
34
+ const cdMatch = trimmed.match(/^cd\s*(.*)/);
35
+ if (cdMatch) {
36
+ const target = cdMatch[1]?.trim() || env.HOME || "/";
37
+ const resolved = target.startsWith("~") ? (env.HOME || "/") + target.slice(1) : path.resolve(cwd, target);
38
+ try {
39
+ if (!__require("node:fs").statSync(resolved).isDirectory()) emit(`\x1b[31mcd: not a directory: ${target}\x1b[0m\r\n`);
40
+ else cwd = resolved;
41
+ } catch {
42
+ emit(`\x1b[31mcd: no such directory: ${target}\x1b[0m\r\n`);
43
+ }
44
+ prompt();
45
+ return;
46
+ }
47
+ const exportMatch = trimmed.match(/^export\s+(\w+)=(.*)/);
48
+ if (exportMatch) {
49
+ env[exportMatch[1]] = exportMatch[2].replace(/^["']|["']$/g, "");
50
+ prompt();
51
+ return;
52
+ }
53
+ busy = true;
54
+ execFile(shell, ["-c", trimmed], {
55
+ cwd,
56
+ env,
57
+ timeout: 3e4
58
+ }, (err, stdout, stderr) => {
59
+ if (!alive) return;
60
+ if (stderr) {
61
+ emit(`\x1b[31m${stderr.replaceAll("\n", "\r\n")}\x1b[0m`);
62
+ if (!stderr.endsWith("\n")) emit("\r\n");
63
+ }
64
+ if (stdout) {
65
+ emit(stdout.replaceAll("\n", "\r\n"));
66
+ if (!stdout.endsWith("\n")) emit("\r\n");
67
+ }
68
+ if (err && !stdout && !stderr) emit(`\x1b[31m${err.message}\x1b[0m\r\n`);
69
+ busy = false;
70
+ prompt();
71
+ if (pending) {
72
+ const queued = pending;
73
+ pending = "";
74
+ handle.write(queued);
75
+ }
76
+ });
77
+ }
78
+ queueMicrotask(() => {
79
+ emit("\x1B[2m(dummy shell — no PTY; basic commands work, interactive programs may not)\x1B[0m\r\n");
80
+ prompt();
81
+ });
82
+ const handle = {
83
+ write(data) {
84
+ if (!alive) return;
85
+ if (busy) {
86
+ pending += data;
87
+ return;
88
+ }
89
+ for (const ch of data) switch (ch) {
90
+ case "\r":
91
+ case "\n": {
92
+ emit("\r\n");
93
+ const cmd = line;
94
+ line = "";
95
+ exec(cmd);
96
+ break;
97
+ }
98
+ case "":
99
+ case "\b":
100
+ if (line.length > 0) {
101
+ line = line.slice(0, -1);
102
+ emit("\b \b");
103
+ }
104
+ break;
105
+ case "":
106
+ line = "";
107
+ emit("^C\r\n");
108
+ prompt();
109
+ break;
110
+ case "":
111
+ if (line.length === 0) {
112
+ alive = false;
113
+ emit("\r\nexit\r\n");
114
+ for (const cb of exitListeners) cb();
115
+ dataListeners.clear();
116
+ exitListeners.clear();
117
+ }
118
+ break;
119
+ case "\f":
120
+ emit("\x1B[2J\x1B[H");
121
+ prompt();
122
+ emit(line);
123
+ break;
124
+ default: if (ch >= " " || ch === " ") {
125
+ line += ch;
126
+ emit(ch);
127
+ }
128
+ }
129
+ },
130
+ resize() {},
131
+ kill() {
132
+ if (!alive) return;
133
+ alive = false;
134
+ for (const cb of exitListeners) cb();
135
+ dataListeners.clear();
136
+ exitListeners.clear();
137
+ },
138
+ onData(cb) {
139
+ dataListeners.add(cb);
140
+ return () => dataListeners.delete(cb);
141
+ },
142
+ onExit(cb) {
143
+ exitListeners.add(cb);
144
+ return () => exitListeners.delete(cb);
145
+ }
146
+ };
147
+ return handle;
148
+ }
149
+ export { spawnDummyPty };
@@ -37,15 +37,8 @@ var ResourceManager = class {
37
37
  async getProcesses(opts) {
38
38
  const sortBy = opts?.sortBy ?? "cpu";
39
39
  const limit = Math.min(opts?.limit ?? 50, 200);
40
- const sortFlag = sortBy === "ram" ? "-rss" : "-%cpu";
41
40
  try {
42
- const { stdout } = await execFileAsync("ps", [
43
- "axo",
44
- "pid,user,%cpu,rss,comm,args",
45
- "--no-headers",
46
- "--sort",
47
- sortFlag
48
- ]);
41
+ const { stdout } = await execFileAsync("ps", ["axo", "pid,user,%cpu,rss,comm,args"]);
49
42
  const results = [];
50
43
  for (const line of stdout.split("\n")) {
51
44
  const trimmed = line.trim();
@@ -53,12 +46,12 @@ var ResourceManager = class {
53
46
  const parts = trimmed.split(/\s+/);
54
47
  if (parts.length < 6) continue;
55
48
  const pid = Number.parseInt(parts[0], 10);
49
+ if (Number.isNaN(pid)) continue;
56
50
  const user = parts[1];
57
51
  const cpu = Number.parseFloat(parts[2]);
58
52
  const ram = Number.parseInt(parts[3], 10);
59
53
  const name = parts[4];
60
54
  const command = parts.slice(5).join(" ");
61
- if (Number.isNaN(pid)) continue;
62
55
  results.push({
63
56
  pid,
64
57
  user,
@@ -67,9 +60,9 @@ var ResourceManager = class {
67
60
  name,
68
61
  command
69
62
  });
70
- if (results.length >= limit) break;
71
63
  }
72
- return results;
64
+ results.sort((a, b) => sortBy === "ram" ? b.ram - a.ram : b.cpu - a.cpu);
65
+ return results.slice(0, limit);
73
66
  } catch (err) {
74
67
  logger.warn({ err }, "failed to get process list");
75
68
  return [];
@@ -77,16 +70,40 @@ var ResourceManager = class {
77
70
  }
78
71
  async getPorts() {
79
72
  try {
80
- const { stdout } = await execFileAsync("ss", ["-tlnpH"]);
81
- const tcpPorts = _parseSS(stdout, "tcp");
82
- const { stdout: udpOut } = await execFileAsync("ss", ["-ulnpH"]);
83
- const udpPorts = _parseSS(udpOut, "udp");
84
- return [...tcpPorts, ...udpPorts].sort((a, b) => a.port - b.port);
73
+ if (process.platform === "darwin") return await this._getPortsLsof();
74
+ return await this._getPortsSS();
85
75
  } catch (err) {
86
76
  logger.warn({ err }, "failed to get listening ports");
87
77
  return [];
88
78
  }
89
79
  }
80
+ async _getPortsSS() {
81
+ const { stdout } = await execFileAsync("ss", ["-tlnpH"]);
82
+ const tcpPorts = _parseSS(stdout, "tcp");
83
+ const { stdout: udpOut } = await execFileAsync("ss", ["-ulnpH"]);
84
+ const udpPorts = _parseSS(udpOut, "udp");
85
+ return [...tcpPorts, ...udpPorts].sort((a, b) => a.port - b.port);
86
+ }
87
+ async _getPortsLsof() {
88
+ const results = [];
89
+ const { stdout } = await execFileAsync("lsof", [
90
+ "-iTCP",
91
+ "-sTCP:LISTEN",
92
+ "-nP"
93
+ ]);
94
+ for (const entry of _parseLsof(stdout)) results.push({
95
+ ...entry,
96
+ protocol: "tcp"
97
+ });
98
+ try {
99
+ const { stdout: udpOut } = await execFileAsync("lsof", ["-iUDP", "-nP"]);
100
+ for (const entry of _parseLsof(udpOut)) results.push({
101
+ ...entry,
102
+ protocol: "udp"
103
+ });
104
+ } catch {}
105
+ return results.sort((a, b) => a.port - b.port);
106
+ }
90
107
  killProcess(pid, signal = "SIGTERM") {
91
108
  if (pid === process.pid) throw new Error("cannot kill own process");
92
109
  try {
@@ -244,4 +261,30 @@ function _parseSS(stdout, protocol) {
244
261
  }
245
262
  return results;
246
263
  }
264
+ function _parseLsof(stdout) {
265
+ const results = [];
266
+ for (const line of stdout.split("\n")) {
267
+ const trimmed = line.trim();
268
+ if (!trimmed || trimmed.startsWith("COMMAND")) continue;
269
+ const parts = trimmed.split(/\s+/);
270
+ if (parts.length < 9) continue;
271
+ const processName = parts[0];
272
+ const pid = Number.parseInt(parts[1], 10);
273
+ const user = parts[2];
274
+ const name = parts[8];
275
+ const lastColon = name.lastIndexOf(":");
276
+ if (lastColon === -1) continue;
277
+ const address = name.slice(0, lastColon);
278
+ const port = Number.parseInt(name.slice(lastColon + 1), 10);
279
+ if (Number.isNaN(port)) continue;
280
+ results.push({
281
+ port,
282
+ address,
283
+ pid,
284
+ process: processName,
285
+ user
286
+ });
287
+ }
288
+ return results;
289
+ }
247
290
  export { resourceManager as t };
@@ -1,6 +1,7 @@
1
1
  import { n as MAIN_GROUP_FOLDER, o as config, r as SESSIONS_DIR, t as GROUPS_DIR } from "./config.mjs";
2
2
  import { t as createLogger } from "./logger.mjs";
3
3
  import { t as streamBus } from "./stream.mjs";
4
+ import { a as resolveGroupModel } from "./session.mjs";
4
5
  import { C as setSession, D as updateTaskAfterRun, S as setRouterState, T as storeMessage, _ as removeRegisteredGroup, b as setConfig, d as getMessagesSince, f as getNewMessages, g as logTaskRun, h as initDatabase, i as deleteConfig, l as getConfig, m as getTaskById, o as getAllRegisteredGroups, p as getRouterState, r as deleteChat, s as getAllSessions, u as getDueTasks, v as removeSession, w as storeChatMetadata, x as setRegisteredGroup, y as removeTasksByChat } from "./db.mjs";
5
6
  import { t as terminalManager } from "./terminal.mjs";
6
7
  import { n as pi_exports } from "./pi.mjs";
@@ -873,6 +874,13 @@ var PiClawServer = class {
873
874
  if (!needsProcessing(group, agentMessages)) return true;
874
875
  const prompt = formatMessages(agentMessages);
875
876
  const lastMessageTimestamp = missedMessages[missedMessages.length - 1].timestamp;
877
+ if (!resolveGroupModel(group)) {
878
+ logger.warn({ group: group.name }, "No model available, notifying user");
879
+ await this.sendBotMessage(chatJid, "No AI model is currently available. Please configure a provider API key or log in via the admin panel.");
880
+ this.lastAgentTimestamp[chatJid] = lastMessageTimestamp;
881
+ await this.saveState();
882
+ return true;
883
+ }
876
884
  logger.info({
877
885
  group: group.name,
878
886
  messageCount: missedMessages.length
@@ -7,8 +7,13 @@ async function spawnPty(opts) {
7
7
  const { spawnBunPty } = await import("./bun.mjs");
8
8
  return spawnBunPty(opts);
9
9
  }
10
- const { spawnNodePty } = await import("./node.mjs");
11
- return spawnNodePty(opts);
10
+ try {
11
+ const { spawnNodePty } = await import("./node.mjs");
12
+ return spawnNodePty(opts);
13
+ } catch {
14
+ const { spawnDummyPty } = await import("./dummy.mjs");
15
+ return spawnDummyPty(opts);
16
+ }
12
17
  }
13
18
  async function spawnVirtualPty(...args) {
14
19
  const { spawnVirtualPty: fn } = await import("./virtual.mjs");
@@ -80,121 +80,121 @@ var public_assets_data_default = {
80
80
  "/icon.svg": {
81
81
  "type": "image/svg+xml",
82
82
  "etag": "\"41a-ki4Lp5uMw3sCqMRSciVIHajM4ps\"",
83
- "mtime": "2026-03-03T20:28:18.281Z",
83
+ "mtime": "2026-03-03T20:53:42.168Z",
84
84
  "size": 1050,
85
85
  "path": "../public/icon.svg"
86
86
  },
87
- "/index.html": {
88
- "type": "text/html; charset=utf-8",
89
- "etag": "\"381-6l9MPgXUJXTjzzmJEmsjNQOwMR0\"",
90
- "mtime": "2026-03-03T20:28:18.245Z",
91
- "size": 897,
92
- "path": "../public/index.html"
93
- },
94
- "/manifest.json": {
95
- "type": "application/json",
96
- "etag": "\"14e-DvUgXsoYV0AGNrrrXwbASbqGR5g\"",
97
- "mtime": "2026-03-03T20:28:18.281Z",
98
- "size": 334,
99
- "path": "../public/manifest.json"
100
- },
101
- "/sw.js": {
102
- "type": "text/javascript; charset=utf-8",
103
- "etag": "\"765-FHQ7M5HcgHxyZi8sVv0/baOJWD8\"",
104
- "mtime": "2026-03-03T20:28:18.281Z",
105
- "size": 1893,
106
- "path": "../public/sw.js"
107
- },
108
87
  "/assets/GeistPixel-Square-CwnHaJd_.woff2": {
109
88
  "type": "font/woff2",
110
89
  "etag": "\"6fc8-Nl3X3tuURsyt3ex8iF9PBK3Fymk\"",
111
- "mtime": "2026-03-03T20:28:18.245Z",
90
+ "mtime": "2026-03-03T20:53:42.114Z",
112
91
  "size": 28616,
113
92
  "path": "../public/assets/GeistPixel-Square-CwnHaJd_.woff2"
114
93
  },
115
- "/assets/defult-giDcMJuT.js": {
94
+ "/assets/defult-hGXPebpx.js": {
116
95
  "type": "text/javascript; charset=utf-8",
117
- "etag": "\"5be-K4S4He4BKVeI8Xs9LBuK+Y66iIY\"",
118
- "mtime": "2026-03-03T20:28:18.244Z",
96
+ "etag": "\"5be-RUGI/yhdf3kyd8BTrPT8CnPO/MM\"",
97
+ "mtime": "2026-03-03T20:53:42.113Z",
119
98
  "size": 1470,
120
- "path": "../public/assets/defult-giDcMJuT.js"
121
- },
122
- "/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2": {
123
- "type": "font/woff2",
124
- "etag": "\"3bcc-oSFlPnDlb7fAcQTPv6sqm6NgXXU\"",
125
- "mtime": "2026-03-03T20:28:18.245Z",
126
- "size": 15308,
127
- "path": "../public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2"
99
+ "path": "../public/assets/defult-hGXPebpx.js"
128
100
  },
129
101
  "/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2": {
130
102
  "type": "font/woff2",
131
103
  "etag": "\"3964-jfUkxTfHRj1cpO33jEsDk2lkrvM\"",
132
- "mtime": "2026-03-03T20:28:18.245Z",
104
+ "mtime": "2026-03-03T20:53:42.114Z",
133
105
  "size": 14692,
134
106
  "path": "../public/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2"
135
107
  },
108
+ "/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2": {
109
+ "type": "font/woff2",
110
+ "etag": "\"3bcc-oSFlPnDlb7fAcQTPv6sqm6NgXXU\"",
111
+ "mtime": "2026-03-03T20:53:42.114Z",
112
+ "size": 15308,
113
+ "path": "../public/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2"
114
+ },
136
115
  "/assets/geist-latin-wght-normal-Dm3htQBi.woff2": {
137
116
  "type": "font/woff2",
138
117
  "etag": "\"6ef0-pZqr0k2V92t+lxQ/ogxqTIOgDGM\"",
139
- "mtime": "2026-03-03T20:28:18.245Z",
118
+ "mtime": "2026-03-03T20:53:42.114Z",
140
119
  "size": 28400,
141
120
  "path": "../public/assets/geist-latin-wght-normal-Dm3htQBi.woff2"
142
121
  },
143
122
  "/assets/geist-mono-cyrillic-wght-normal-BZdD_g9V.woff2": {
144
123
  "type": "font/woff2",
145
124
  "etag": "\"3148-fDEowP3hdBu549ke2NSllgnKwFo\"",
146
- "mtime": "2026-03-03T20:28:18.245Z",
125
+ "mtime": "2026-03-03T20:53:42.114Z",
147
126
  "size": 12616,
148
127
  "path": "../public/assets/geist-mono-cyrillic-wght-normal-BZdD_g9V.woff2"
149
128
  },
150
129
  "/assets/geist-mono-latin-ext-wght-normal-b6lpi8_2.woff2": {
151
130
  "type": "font/woff2",
152
131
  "etag": "\"32f4-mFhyUXRKosFvmVg6XWwBAV3kvk4\"",
153
- "mtime": "2026-03-03T20:28:18.245Z",
132
+ "mtime": "2026-03-03T20:53:42.114Z",
154
133
  "size": 13044,
155
134
  "path": "../public/assets/geist-mono-latin-ext-wght-normal-b6lpi8_2.woff2"
156
135
  },
157
136
  "/assets/geist-mono-latin-wght-normal-Cjtb1TV-.woff2": {
158
137
  "type": "font/woff2",
159
138
  "etag": "\"7a88-DQWpi033XojA2ZmNMyp8i2zZURE\"",
160
- "mtime": "2026-03-03T20:28:18.245Z",
139
+ "mtime": "2026-03-03T20:53:42.114Z",
161
140
  "size": 31368,
162
141
  "path": "../public/assets/geist-mono-latin-wght-normal-Cjtb1TV-.woff2"
163
142
  },
164
- "/assets/index-1zeVzeIQ.css": {
143
+ "/assets/index-DIAPMG-u.css": {
165
144
  "type": "text/css; charset=utf-8",
166
- "etag": "\"f8e3-hGjt36kmPCu9Y68vj1R1mCUKJb4\"",
167
- "mtime": "2026-03-03T20:28:18.245Z",
168
- "size": 63715,
169
- "path": "../public/assets/index-1zeVzeIQ.css"
145
+ "etag": "\"f763-sz5YAC0HyCnTjJ2c6usb03qpBAw\"",
146
+ "mtime": "2026-03-03T20:53:42.114Z",
147
+ "size": 63331,
148
+ "path": "../public/assets/index-DIAPMG-u.css"
170
149
  },
171
150
  "/assets/md4x-CNLJ57xO.wasm": {
172
151
  "type": "application/wasm",
173
152
  "etag": "\"41116-fXWw13Hb4mtmx1zqWY937G5o04w\"",
174
- "mtime": "2026-03-03T20:28:18.245Z",
153
+ "mtime": "2026-03-03T20:53:42.114Z",
175
154
  "size": 266518,
176
155
  "path": "../public/assets/md4x-CNLJ57xO.wasm"
177
156
  },
178
157
  "/assets/md4x-CyfQToGJ.js": {
179
158
  "type": "text/javascript; charset=utf-8",
180
159
  "etag": "\"38-QLTgoA+K9j0djblpu5tawfdfO5c\"",
181
- "mtime": "2026-03-03T20:28:18.245Z",
160
+ "mtime": "2026-03-03T20:53:42.114Z",
182
161
  "size": 56,
183
162
  "path": "../public/assets/md4x-CyfQToGJ.js"
184
163
  },
185
- "/assets/index-DbZX-IVJ.js": {
164
+ "/index.html": {
165
+ "type": "text/html; charset=utf-8",
166
+ "etag": "\"381-yBM/ZxljxEUxXkxP0J20jLqkGlg\"",
167
+ "mtime": "2026-03-03T20:53:42.114Z",
168
+ "size": 897,
169
+ "path": "../public/index.html"
170
+ },
171
+ "/assets/index-pwr3Q8Ee.js": {
186
172
  "type": "text/javascript; charset=utf-8",
187
- "etag": "\"a6db5-hLPeaAbxnkKcph7XwtiKI94nI5Y\"",
188
- "mtime": "2026-03-03T20:28:18.242Z",
189
- "size": 683445,
190
- "path": "../public/assets/index-DbZX-IVJ.js"
173
+ "etag": "\"a6c43-rJ80bItOKiQbrver0cHvfNUQslc\"",
174
+ "mtime": "2026-03-03T20:53:42.112Z",
175
+ "size": 683075,
176
+ "path": "../public/assets/index-pwr3Q8Ee.js"
177
+ },
178
+ "/manifest.json": {
179
+ "type": "application/json",
180
+ "etag": "\"14e-DvUgXsoYV0AGNrrrXwbASbqGR5g\"",
181
+ "mtime": "2026-03-03T20:53:42.168Z",
182
+ "size": 334,
183
+ "path": "../public/manifest.json"
184
+ },
185
+ "/sw.js": {
186
+ "type": "text/javascript; charset=utf-8",
187
+ "etag": "\"765-FHQ7M5HcgHxyZi8sVv0/baOJWD8\"",
188
+ "mtime": "2026-03-03T20:53:42.168Z",
189
+ "size": 1893,
190
+ "path": "../public/sw.js"
191
191
  },
192
- "/assets/dist-6l2bMalG.js": {
192
+ "/assets/dist-BEy0WmVu.js": {
193
193
  "type": "text/javascript; charset=utf-8",
194
- "etag": "\"16e8b9-KzseyCFKLr4g4o0Ai670WLZMwqE\"",
195
- "mtime": "2026-03-03T20:28:18.244Z",
194
+ "etag": "\"16e8b9-Y1r1XYEfvzMzSrI+11dHfqZpFfY\"",
195
+ "mtime": "2026-03-03T20:53:42.113Z",
196
196
  "size": 1501369,
197
- "path": "../public/assets/dist-6l2bMalG.js"
197
+ "path": "../public/assets/dist-BEy0WmVu.js"
198
198
  }
199
199
  };
200
200
  function readAsset(id) {
package/bin/piclaw.mjs CHANGED
@@ -36,6 +36,8 @@ ${bold("Options:")}
36
36
 
37
37
  const port = Number(values.port || process.env.PORT || 3737);
38
38
 
39
+ const isWebContainer = process.env.SHELL === "/bin/jsh" || !!process.versions?.webcontainer;
40
+
39
41
  /** Check if a port is already listening on localhost */
40
42
  function isPortOpen(port) {
41
43
  return new Promise((resolve) => {
@@ -60,7 +62,8 @@ if (await isPortOpen(port)) {
60
62
  );
61
63
 
62
64
  // Preflight: need node:sqlite (Node ≥ 22.5) or sqlite3
63
- if (!("Bun" in globalThis)) {
65
+ // Skip in StackBlitz/WebContainer — it provides its own SQLite
66
+ if (!("Bun" in globalThis) && !isWebContainer) {
64
67
  let hasSqlite = false;
65
68
  try {
66
69
  await import("node:sqlite");
@@ -92,7 +95,8 @@ if (await isPortOpen(port)) {
92
95
  }
93
96
 
94
97
  // Open browser in app mode (chromeless window, no toolbar/tabs)
95
- if (serverUrl && !values["no-open"]) {
98
+ // Skip in StackBlitz/WebContainer — browser is already embedded
99
+ if (serverUrl && !values["no-open"] && !isWebContainer) {
96
100
  const { exec } = await import("node:child_process");
97
101
  const url = new URL(serverUrl);
98
102
  if (["::", "127.0.0.1", "localhost"].includes(url.hostname)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "piclaw",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "bin": {
5
5
  "piclaw": "./bin/piclaw.mjs"
6
6
  },