piclaw 0.0.19 → 0.0.21

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 (270) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/defult-D5RLDUrI.js +1 -0
  3. package/.output/public/assets/{dist-CMBqBOCp.js → dist-BH_oa-kv.js} +1 -1
  4. package/.output/public/assets/index-7JvURuHy.js +204 -0
  5. package/.output/public/assets/index-K43slwjJ.css +1 -0
  6. package/.output/public/index.html +11 -2
  7. package/.output/server/_...path_.get.mjs +16 -0
  8. package/.output/server/_chunks/app.mjs +261 -181
  9. package/.output/server/_chunks/browser.mjs +4 -1
  10. package/.output/server/_chunks/config.mjs +4 -0
  11. package/.output/server/_chunks/db.mjs +32 -28
  12. package/.output/server/_chunks/device-bus.mjs +123 -0
  13. package/.output/server/_chunks/dummy.mjs +1 -1
  14. package/.output/server/_chunks/logger.mjs +23 -0
  15. package/.output/server/_chunks/login.mjs +1 -1
  16. package/.output/server/_chunks/notes.mjs +1 -3
  17. package/.output/server/_chunks/renderer-template.mjs +1 -1
  18. package/.output/server/_chunks/sandbox.mjs +217 -0
  19. package/.output/server/_chunks/server.mjs +2302 -122
  20. package/.output/server/_chunks/terminal.mjs +63 -8
  21. package/.output/server/_chunks/uploads.mjs +60 -0
  22. package/.output/server/_chunks/virtual.mjs +192 -54
  23. package/.output/server/_id_.delete.mjs +5 -2
  24. package/.output/server/_id_.patch.mjs +2 -0
  25. package/.output/server/_id_2.delete.mjs +8 -0
  26. package/.output/server/_jid_.delete.mjs +5 -2
  27. package/.output/server/_jid_.patch.mjs +37 -4
  28. package/.output/server/_jid_2.delete.mjs +5 -2
  29. package/.output/server/_libs/@acemir/cssom+[...].mjs +2269 -1137
  30. package/.output/server/_libs/@google/genai.mjs +337 -273
  31. package/.output/server/_libs/@mariozechner/pi-agent-core+[...].mjs +381 -2073
  32. package/.output/server/_libs/@mariozechner/pi-coding-agent+[...].mjs +231 -131
  33. package/.output/server/_libs/_.mjs +3 -2
  34. package/.output/server/_libs/_10.mjs +2 -4
  35. package/.output/server/_libs/_11.mjs +2 -4
  36. package/.output/server/_libs/_12.mjs +2 -3
  37. package/.output/server/_libs/_13.mjs +2 -3
  38. package/.output/server/_libs/_14.mjs +2 -4
  39. package/.output/server/_libs/_15.mjs +2 -4
  40. package/.output/server/_libs/_16.mjs +2 -3
  41. package/.output/server/_libs/_17.mjs +2 -4
  42. package/.output/server/_libs/_18.mjs +2 -2
  43. package/.output/server/_libs/_19.mjs +2 -2
  44. package/.output/server/_libs/_2.mjs +3 -3
  45. package/.output/server/_libs/_20.mjs +2 -2
  46. package/.output/server/_libs/_21.mjs +2 -2
  47. package/.output/server/_libs/_22.mjs +2 -2
  48. package/.output/server/_libs/_23.mjs +2 -2
  49. package/.output/server/_libs/_24.mjs +2 -2
  50. package/.output/server/_libs/_25.mjs +2 -2
  51. package/.output/server/_libs/_26.mjs +2 -2
  52. package/.output/server/_libs/_27.mjs +2 -2
  53. package/.output/server/_libs/_28.mjs +2 -2
  54. package/.output/server/_libs/_29.mjs +2 -2
  55. package/.output/server/_libs/_3.mjs +3 -3
  56. package/.output/server/_libs/_30.mjs +2 -2
  57. package/.output/server/_libs/_31.mjs +2 -2
  58. package/.output/server/_libs/_32.mjs +2 -2
  59. package/.output/server/_libs/_33.mjs +2 -2
  60. package/.output/server/_libs/_34.mjs +2 -2
  61. package/.output/server/_libs/_35.mjs +2 -2
  62. package/.output/server/_libs/_36.mjs +2 -2
  63. package/.output/server/_libs/_37.mjs +2 -2
  64. package/.output/server/_libs/_38.mjs +2 -2
  65. package/.output/server/_libs/_39.mjs +2 -2
  66. package/.output/server/_libs/_4.mjs +4 -3
  67. package/.output/server/_libs/_40.mjs +2 -2
  68. package/.output/server/_libs/_41.mjs +2 -2
  69. package/.output/server/_libs/_42.mjs +2 -2
  70. package/.output/server/_libs/_43.mjs +2 -2
  71. package/.output/server/_libs/_44.mjs +2 -2
  72. package/.output/server/_libs/_45.mjs +2 -2
  73. package/.output/server/_libs/_46.mjs +2 -2
  74. package/.output/server/_libs/_47.mjs +2 -2
  75. package/.output/server/_libs/_48.mjs +2 -2
  76. package/.output/server/_libs/_49.mjs +2 -2
  77. package/.output/server/_libs/_5.mjs +2 -3
  78. package/.output/server/_libs/_50.mjs +2 -2
  79. package/.output/server/_libs/_51.mjs +2 -2
  80. package/.output/server/_libs/_52.mjs +2 -2
  81. package/.output/server/_libs/_53.mjs +2 -2
  82. package/.output/server/_libs/_54.mjs +2 -2
  83. package/.output/server/_libs/_55.mjs +2 -2
  84. package/.output/server/_libs/_56.mjs +2 -2
  85. package/.output/server/_libs/_57.mjs +2 -2
  86. package/.output/server/_libs/_58.mjs +2 -2
  87. package/.output/server/_libs/_59.mjs +2 -2
  88. package/.output/server/_libs/_6.mjs +2 -3
  89. package/.output/server/_libs/_60.mjs +2 -2
  90. package/.output/server/_libs/_61.mjs +2 -2
  91. package/.output/server/_libs/_62.mjs +2 -2
  92. package/.output/server/_libs/_63.mjs +2 -2
  93. package/.output/server/_libs/_64.mjs +2 -2
  94. package/.output/server/_libs/_65.mjs +2 -2
  95. package/.output/server/_libs/_66.mjs +2 -2
  96. package/.output/server/_libs/_67.mjs +2 -2
  97. package/.output/server/_libs/_68.mjs +2 -2
  98. package/.output/server/_libs/_69.mjs +2 -2
  99. package/.output/server/_libs/_7.mjs +2 -5
  100. package/.output/server/_libs/_70.mjs +2 -2
  101. package/.output/server/_libs/_71.mjs +2 -2
  102. package/.output/server/_libs/_72.mjs +2 -2
  103. package/.output/server/_libs/_73.mjs +2 -2
  104. package/.output/server/_libs/_74.mjs +2 -2
  105. package/.output/server/_libs/_75.mjs +2 -2
  106. package/.output/server/_libs/_76.mjs +2 -2
  107. package/.output/server/_libs/_77.mjs +2 -2
  108. package/.output/server/_libs/_78.mjs +2 -2
  109. package/.output/server/_libs/_79.mjs +2 -2
  110. package/.output/server/_libs/_8.mjs +2 -3
  111. package/.output/server/_libs/_80.mjs +2 -2
  112. package/.output/server/_libs/_81.mjs +2 -2
  113. package/.output/server/_libs/_82.mjs +2 -2
  114. package/.output/server/_libs/_83.mjs +2 -2
  115. package/.output/server/_libs/_84.mjs +2 -2
  116. package/.output/server/_libs/_85.mjs +2 -2
  117. package/.output/server/_libs/_86.mjs +2 -2
  118. package/.output/server/_libs/_87.mjs +2 -2
  119. package/.output/server/_libs/_88.mjs +2 -2
  120. package/.output/server/_libs/_89.mjs +2 -2
  121. package/.output/server/_libs/_9.mjs +2 -4
  122. package/.output/server/_libs/_90.mjs +5 -2
  123. package/.output/server/_libs/_91.mjs +3 -2
  124. package/.output/server/_libs/_92.mjs +2 -2
  125. package/.output/server/_libs/_93.mjs +2 -2
  126. package/.output/server/_libs/_94.mjs +2 -2
  127. package/.output/server/_libs/agent-base.mjs +1 -1
  128. package/.output/server/_libs/cheerio+[...].mjs +1 -1
  129. package/.output/server/_libs/data-uri-to-buffer.mjs +2 -67
  130. package/.output/server/_libs/data-urls+[...].mjs +1 -1
  131. package/.output/server/_libs/diff.mjs +1 -1
  132. package/.output/server/_libs/exodus__bytes.mjs +99 -81
  133. package/.output/server/_libs/fetch-blob+node-domexception.mjs +1 -1
  134. package/.output/server/_libs/h3+rou3+srvx.mjs +34 -4
  135. package/.output/server/_libs/html-encoding-sniffer.mjs +1 -1
  136. package/.output/server/_libs/https-proxy-agent.mjs +2 -2
  137. package/.output/server/_libs/jsdom.mjs +1 -1
  138. package/.output/server/_libs/just-bash+[...].mjs +4676 -3916
  139. package/.output/server/_libs/mariozechner__jiti.mjs +1 -1
  140. package/.output/server/_libs/mariozechner__pi-ai.mjs +1472 -0
  141. package/.output/server/_libs/md4x.mjs +1 -1
  142. package/.output/server/_libs/mime.mjs +838 -1
  143. package/.output/server/_libs/node-fetch.mjs +4 -4
  144. package/.output/server/_libs/node-liblzma.mjs +1 -1
  145. package/.output/server/_libs/silvia-odwyer__photon-node.mjs +1 -1
  146. package/.output/server/_routes/api/auth/approve.mjs +2 -0
  147. package/.output/server/_routes/api/auth/revoke.mjs +2 -0
  148. package/.output/server/_routes/api/auth/status.mjs +25 -6
  149. package/.output/server/_routes/api/browser2.mjs +1 -1
  150. package/.output/server/_routes/api/config2.mjs +2 -0
  151. package/.output/server/_routes/api/device_events.mjs +36 -0
  152. package/.output/server/_routes/api/files/groups.mjs +1 -2
  153. package/.output/server/_routes/api/files/raw.mjs +1 -1
  154. package/.output/server/_routes/api/groups.mjs +5 -3
  155. package/.output/server/_routes/api/groups2.mjs +18 -6
  156. package/.output/server/_routes/api/health.mjs +1 -2
  157. package/.output/server/_routes/api/messages.mjs +7 -1
  158. package/.output/server/_routes/api/notes/delete.mjs +4 -1
  159. package/.output/server/_routes/api/notes/write.mjs +2 -0
  160. package/.output/server/_routes/api/ntfy/setup.mjs +8 -0
  161. package/.output/server/_routes/api/pi/apikey.mjs +3 -2
  162. package/.output/server/_routes/api/pi/apikey_providers.mjs +1 -2
  163. package/.output/server/_routes/api/pi/commands.mjs +13 -3
  164. package/.output/server/_routes/api/pi/login/events.mjs +0 -1
  165. package/.output/server/_routes/api/pi/login/respond.mjs +2 -1
  166. package/.output/server/_routes/api/pi/login.mjs +1 -2
  167. package/.output/server/_routes/api/pi/logout.mjs +2 -1
  168. package/.output/server/_routes/api/pi/models.mjs +1 -2
  169. package/.output/server/_routes/api/pi/models_config2.mjs +2 -0
  170. package/.output/server/_routes/api/pi/settings2.mjs +2 -0
  171. package/.output/server/_routes/api/pi/status.mjs +1 -2
  172. package/.output/server/_routes/api/proxy.mjs +19 -1
  173. package/.output/server/_routes/api/sandbox.mjs +26 -0
  174. package/.output/server/_routes/api/sandbox2.mjs +17 -0
  175. package/.output/server/_routes/api/send.mjs +26 -18
  176. package/.output/server/_routes/api/status.mjs +1 -3
  177. package/.output/server/_routes/api/stop.mjs +11 -0
  178. package/.output/server/_routes/api/store/plugins.mjs +75 -0
  179. package/.output/server/_routes/api/store/skills.mjs +11 -0
  180. package/.output/server/_routes/api/tasks2.mjs +3 -2
  181. package/.output/server/_routes/api/telegram/setup.mjs +5 -2
  182. package/.output/server/_routes/api/telegram/status.mjs +1 -2
  183. package/.output/server/_routes/api/terminal2.mjs +2 -1
  184. package/.output/server/_routes/api/tunnel/setup.mjs +4 -2
  185. package/.output/server/_runtime.mjs +1 -2
  186. package/.output/server/_utils.mjs +10 -2
  187. package/.output/server/index.mjs +1 -1
  188. package/.output/server/node_modules/amdefine/amdefine.js +301 -0
  189. package/.output/server/node_modules/amdefine/package.json +16 -0
  190. package/.output/server/node_modules/compressjs/lib/BWT.js +420 -0
  191. package/.output/server/node_modules/compressjs/lib/BWTC.js +234 -0
  192. package/.output/server/node_modules/compressjs/lib/BitStream.js +108 -0
  193. package/.output/server/node_modules/compressjs/lib/Bzip2.js +936 -0
  194. package/.output/server/node_modules/compressjs/lib/CRC32.js +105 -0
  195. package/.output/server/node_modules/compressjs/lib/Context1Model.js +56 -0
  196. package/.output/server/node_modules/compressjs/lib/DefSumModel.js +152 -0
  197. package/.output/server/node_modules/compressjs/lib/DeflateDistanceModel.js +55 -0
  198. package/.output/server/node_modules/compressjs/lib/Dmc.js +197 -0
  199. package/.output/server/node_modules/compressjs/lib/DummyRangeCoder.js +81 -0
  200. package/.output/server/node_modules/compressjs/lib/FenwickModel.js +194 -0
  201. package/.output/server/node_modules/compressjs/lib/Huffman.js +514 -0
  202. package/.output/server/node_modules/compressjs/lib/HuffmanAllocator.js +227 -0
  203. package/.output/server/node_modules/compressjs/lib/LogDistanceModel.js +46 -0
  204. package/.output/server/node_modules/compressjs/lib/Lzjb.js +300 -0
  205. package/.output/server/node_modules/compressjs/lib/LzjbR.js +241 -0
  206. package/.output/server/node_modules/compressjs/lib/Lzp3.js +273 -0
  207. package/.output/server/node_modules/compressjs/lib/MTFModel.js +208 -0
  208. package/.output/server/node_modules/compressjs/lib/NoModel.js +46 -0
  209. package/.output/server/node_modules/compressjs/lib/PPM.js +343 -0
  210. package/.output/server/node_modules/compressjs/lib/RangeCoder.js +238 -0
  211. package/.output/server/node_modules/compressjs/lib/Simple.js +111 -0
  212. package/.output/server/node_modules/compressjs/lib/Stream.js +53 -0
  213. package/.output/server/node_modules/compressjs/lib/Util.js +324 -0
  214. package/.output/server/node_modules/compressjs/lib/freeze.js +14 -0
  215. package/.output/server/node_modules/compressjs/main.js +29 -0
  216. package/.output/server/node_modules/compressjs/package.json +35 -0
  217. package/.output/server/package.json +2 -1
  218. package/README.md +10 -1
  219. package/lib/index.d.mts +1 -0
  220. package/lib/index.mjs +1 -0
  221. package/lib/piclaw.mjs +100 -0
  222. package/lib/utils.mjs +96 -0
  223. package/package.json +16 -11
  224. package/.output/public/assets/defult-CMO6TZ5a.js +0 -1
  225. package/.output/public/assets/index-jdnbJw-M.js +0 -204
  226. package/.output/public/assets/index-ooXrRwgl.css +0 -1
  227. package/.output/server/_chunks/commands.mjs +0 -282
  228. package/.output/server/_chunks/pi.mjs +0 -202
  229. package/.output/server/_chunks/session.mjs +0 -1114
  230. package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -299
  231. package/.output/server/_libs/@aws-sdk/client-bedrock-runtime+[...].mjs +0 -17828
  232. package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -122
  233. package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -417
  234. package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -54
  235. package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -1151
  236. package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -50
  237. package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -369
  238. package/.output/server/_libs/@tootallnate/quickjs-emscripten+[...].mjs +0 -3011
  239. package/.output/server/_libs/_100.mjs +0 -2
  240. package/.output/server/_libs/_101.mjs +0 -2
  241. package/.output/server/_libs/_102.mjs +0 -5
  242. package/.output/server/_libs/_103.mjs +0 -3
  243. package/.output/server/_libs/_104.mjs +0 -2
  244. package/.output/server/_libs/_105.mjs +0 -3
  245. package/.output/server/_libs/_106.mjs +0 -2
  246. package/.output/server/_libs/_107.mjs +0 -2
  247. package/.output/server/_libs/_95.mjs +0 -2
  248. package/.output/server/_libs/_96.mjs +0 -2
  249. package/.output/server/_libs/_97.mjs +0 -2
  250. package/.output/server/_libs/_98.mjs +0 -2
  251. package/.output/server/_libs/_99.mjs +0 -2
  252. package/.output/server/_libs/amdefine.mjs +0 -188
  253. package/.output/server/_libs/ast-types.mjs +0 -2270
  254. package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -3141
  255. package/.output/server/_libs/basic-ftp.mjs +0 -1906
  256. package/.output/server/_libs/compressjs.mjs +0 -50
  257. package/.output/server/_libs/degenerator+[...].mjs +0 -9964
  258. package/.output/server/_libs/get-uri.mjs +0 -413
  259. package/.output/server/_libs/http-proxy-agent.mjs +0 -123
  260. package/.output/server/_libs/ip-address.mjs +0 -1423
  261. package/.output/server/_libs/lru-cache.mjs +0 -732
  262. package/.output/server/_libs/netmask.mjs +0 -139
  263. package/.output/server/_libs/pac-proxy-agent+[...].mjs +0 -3104
  264. package/.output/server/_libs/proxy-agent+proxy-from-env.mjs +0 -204
  265. package/.output/server/_libs/smithy__core.mjs +0 -192
  266. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  267. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  268. package/.output/server/node_modules/tslib/package.json +0 -47
  269. package/.output/server/node_modules/tslib/tslib.js +0 -484
  270. package/bin/piclaw.mjs +0 -195
@@ -1,3 +1,5 @@
1
+ import { t as deviceBus } from "./device-bus.mjs";
2
+ /** In-memory browser tab state, synced with admin UI */
1
3
  var state = {
2
4
  tabs: [],
3
5
  activeTabId: null,
@@ -7,11 +9,12 @@ var listeners = /* @__PURE__ */ new Set();
7
9
  function getBrowserState() {
8
10
  return state;
9
11
  }
10
- function setBrowserState(incoming) {
12
+ function setBrowserState(incoming, opts) {
11
13
  state.tabs = incoming.tabs;
12
14
  state.activeTabId = incoming.activeTabId;
13
15
  state.rev++;
14
16
  for (const fn of listeners) fn(state);
17
+ if (!opts?.silent && state.tabs.length > 0) deviceBus.emitDeviceEvent("browser", { state: { ...state } });
15
18
  }
16
19
  function subscribeBrowser(fn) {
17
20
  listeners.add(fn);
@@ -50,6 +50,10 @@ var CONFIG_DEFS = {
50
50
  env: "BRAVE_API_KEY",
51
51
  default: void 0
52
52
  },
53
+ sandbox: {
54
+ env: "SANDBOX",
55
+ default: void 0
56
+ },
53
57
  timezone: {
54
58
  env: "TZ",
55
59
  default: Intl.DateTimeFormat().resolvedOptions().timeZone
@@ -20,7 +20,8 @@ async function openDatabase(dbPath) {
20
20
  return openSqlite3Database(dbPath);
21
21
  }
22
22
  }
23
- var SCHEMA_VERSION = 3;
23
+ var SCHEMA_VERSION = 4;
24
+ var MINIMUM_VERSION = 4;
24
25
  var _db;
25
26
  function useDb() {
26
27
  if (!_db) throw new Error("Database not initialized — call initDatabase() first");
@@ -33,7 +34,8 @@ async function createSchema(database) {
33
34
  name TEXT,
34
35
  last_message_time TEXT,
35
36
  channel TEXT,
36
- is_group INTEGER DEFAULT 0
37
+ is_group INTEGER DEFAULT 0,
38
+ downloads TEXT
37
39
  );
38
40
  CREATE TABLE IF NOT EXISTS messages (
39
41
  id TEXT,
@@ -44,6 +46,7 @@ async function createSchema(database) {
44
46
  timestamp TEXT,
45
47
  is_from_me INTEGER,
46
48
  is_bot_message INTEGER DEFAULT 0,
49
+ meta TEXT,
47
50
  PRIMARY KEY (id, chat_jid),
48
51
  FOREIGN KEY (chat_jid) REFERENCES chats(jid)
49
52
  );
@@ -115,28 +118,18 @@ async function initDatabase() {
115
118
  const versionFile = path.join(STORE_DIR, "db.version");
116
119
  const dbExists = fs.existsSync(dbPath);
117
120
  const existing = dbExists && fs.existsSync(versionFile) ? Number.parseInt(fs.readFileSync(versionFile, "utf-8").trim(), 10) : 0;
118
- if (dbExists && existing !== SCHEMA_VERSION && existing !== SCHEMA_VERSION - 1) throw new Error(`Database schema version mismatch (have v${existing}, need v${SCHEMA_VERSION}). Delete ${STORE_DIR} to recreate.`);
121
+ if (dbExists && existing < MINIMUM_VERSION) {
122
+ logger.warn(`Database v${existing} is below minimum v${MINIMUM_VERSION}, recreating`);
123
+ fs.unlinkSync(dbPath);
124
+ }
125
+ const needsCreate = !fs.existsSync(dbPath);
119
126
  const db = await openDatabase(dbPath);
120
127
  await db.exec("PRAGMA journal_mode = WAL");
121
128
  await db.exec("PRAGMA busy_timeout = 5000");
122
- if (!dbExists) {
129
+ if (needsCreate) {
123
130
  await createSchema(db);
124
131
  fs.writeFileSync(versionFile, String(SCHEMA_VERSION));
125
- } else if (existing === 2) {
126
- await db.exec(`
127
- CREATE TABLE IF NOT EXISTS devices (
128
- id TEXT PRIMARY KEY,
129
- name TEXT NOT NULL,
130
- status TEXT NOT NULL,
131
- created_at TEXT NOT NULL,
132
- approved_at TEXT,
133
- last_seen TEXT NOT NULL,
134
- ip TEXT
135
- );
136
- `);
137
- fs.writeFileSync(versionFile, String(SCHEMA_VERSION));
138
- logger.info("Migrated database v2 → v3 (added devices table)");
139
- }
132
+ } else if (existing < SCHEMA_VERSION) fs.writeFileSync(versionFile, String(SCHEMA_VERSION));
140
133
  _db = db;
141
134
  await _setDbProvider({
142
135
  getConfig,
@@ -147,9 +140,18 @@ async function initDatabase() {
147
140
  }
148
141
  async function storeMessage(msg) {
149
142
  await useDb().prepare(`
150
- INSERT OR REPLACE INTO messages (id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message)
151
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
152
- `).run(msg.id, msg.chat_jid, msg.sender, msg.sender_name, msg.content, msg.timestamp, msg.is_from_me ? 1 : 0, msg.is_bot_message ? 1 : 0);
143
+ INSERT OR REPLACE INTO messages (id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message, meta)
144
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
145
+ `).run(msg.id, msg.chat_jid, msg.sender, msg.sender_name, msg.content, msg.timestamp, msg.is_from_me ? 1 : 0, msg.is_bot_message ? 1 : 0, msg.meta ? JSON.stringify(msg.meta) : null);
146
+ }
147
+ /** Parse meta JSON from DB rows */
148
+ function hydrateMeta(rows) {
149
+ for (const row of rows) if (typeof row.meta === "string") try {
150
+ row.meta = JSON.parse(row.meta);
151
+ } catch {
152
+ row.meta = void 0;
153
+ }
154
+ return rows;
153
155
  }
154
156
  async function getNewMessages(jids, sinceTimestamp, assistantName) {
155
157
  if (jids.length === 0) return {
@@ -163,27 +165,29 @@ async function getNewMessages(jids, sinceTimestamp, assistantName) {
163
165
  AND is_bot_message = 0
164
166
  AND sender_name != ?
165
167
  ORDER BY timestamp ASC`).all(...jids, sinceTimestamp, assistantName);
168
+ hydrateMeta(rows);
166
169
  return {
167
170
  messages: rows,
168
171
  newTimestamp: rows.length > 0 ? rows[rows.length - 1].timestamp : sinceTimestamp
169
172
  };
170
173
  }
171
174
  async function getMessagesSince(chatJid, sinceTimestamp, assistantName) {
172
- return await useDb().prepare(`SELECT * FROM messages
175
+ return hydrateMeta(await useDb().prepare(`SELECT * FROM messages
173
176
  WHERE chat_jid = ?
174
177
  AND timestamp > ?
175
178
  AND is_bot_message = 0
176
179
  AND sender_name != ?
177
- ORDER BY timestamp ASC`).all(chatJid, sinceTimestamp, assistantName);
180
+ ORDER BY timestamp ASC`).all(chatJid, sinceTimestamp, assistantName));
178
181
  }
179
- async function storeChatMetadata(jid, timestamp, name, channel, isGroup) {
180
- await useDb().prepare(`INSERT INTO chats (jid, name, last_message_time, channel, is_group)
181
- VALUES (?, ?, ?, ?, ?)
182
+ async function storeChatMetadata(jid, timestamp, name, channel, isGroup, downloads) {
183
+ await useDb().prepare(`INSERT INTO chats (jid, name, last_message_time, channel, is_group, downloads)
184
+ VALUES (?, ?, ?, ?, ?, ?)
182
185
  ON CONFLICT(jid) DO UPDATE SET
183
186
  name = COALESCE(?, name),
184
187
  last_message_time = ?,
185
188
  channel = COALESCE(?, channel),
186
- is_group = COALESCE(?, is_group)`).run(jid, name ?? null, timestamp, channel ?? null, isGroup ? 1 : 0, name ?? null, timestamp, channel ?? null, isGroup != null ? isGroup ? 1 : 0 : null);
189
+ is_group = COALESCE(?, is_group),
190
+ downloads = COALESCE(?, downloads)`).run(jid, name ?? null, timestamp, channel ?? null, isGroup ? 1 : 0, downloads ?? null, name ?? null, timestamp, channel ?? null, isGroup != null ? isGroup ? 1 : 0 : null, downloads ?? null);
187
191
  }
188
192
  async function getAllChats() {
189
193
  return await useDb().prepare("SELECT * FROM chats ORDER BY last_message_time DESC").all();
@@ -0,0 +1,123 @@
1
+ import { a as getDevice } from "./auth.mjs";
2
+ import crypto from "node:crypto";
3
+ /**
4
+ * DeviceBus — in-memory device presence tracking with event broadcasting.
5
+ *
6
+ * Tracks which devices have active SSE connections and broadcasts
7
+ * presence events (connect/disconnect) and custom events to all listeners.
8
+ */
9
+ var DeviceBus = class {
10
+ connectionsByDevice = /* @__PURE__ */ new Map();
11
+ connectionIndex = /* @__PURE__ */ new Map();
12
+ deviceNames = /* @__PURE__ */ new Map();
13
+ listeners = /* @__PURE__ */ new Set();
14
+ async connect(deviceId, meta) {
15
+ const conn = {
16
+ id: crypto.randomUUID(),
17
+ deviceId,
18
+ connectedAt: Date.now(),
19
+ meta
20
+ };
21
+ if (!this.deviceNames.has(deviceId)) {
22
+ const device = await getDevice(deviceId);
23
+ if (device) this.deviceNames.set(deviceId, device.name);
24
+ }
25
+ const isFirstConnection = !this.connectionsByDevice.has(deviceId) || this.connectionsByDevice.get(deviceId).size === 0;
26
+ let set = this.connectionsByDevice.get(deviceId);
27
+ if (!set) {
28
+ set = /* @__PURE__ */ new Set();
29
+ this.connectionsByDevice.set(deviceId, set);
30
+ }
31
+ set.add(conn);
32
+ this.connectionIndex.set(conn.id, conn);
33
+ const name = this.deviceNames.get(deviceId) || "Unknown";
34
+ if (isFirstConnection) this.emit({
35
+ type: "device_connected",
36
+ deviceId,
37
+ deviceName: name,
38
+ connections: set.size
39
+ });
40
+ this.emit({
41
+ type: "connection_added",
42
+ deviceId,
43
+ connectionId: conn.id,
44
+ connections: set.size
45
+ });
46
+ const unsubscribe = () => this.disconnect(conn.id);
47
+ return {
48
+ connectionId: conn.id,
49
+ unsubscribe
50
+ };
51
+ }
52
+ disconnect(connectionId) {
53
+ const conn = this.connectionIndex.get(connectionId);
54
+ if (!conn) return;
55
+ this.connectionIndex.delete(connectionId);
56
+ const set = this.connectionsByDevice.get(conn.deviceId);
57
+ if (set) {
58
+ set.delete(conn);
59
+ const remaining = set.size;
60
+ const name = this.deviceNames.get(conn.deviceId) || "Unknown";
61
+ this.emit({
62
+ type: "connection_removed",
63
+ deviceId: conn.deviceId,
64
+ connectionId,
65
+ connections: remaining
66
+ });
67
+ if (remaining === 0) {
68
+ this.connectionsByDevice.delete(conn.deviceId);
69
+ this.deviceNames.delete(conn.deviceId);
70
+ this.emit({
71
+ type: "device_disconnected",
72
+ deviceId: conn.deviceId,
73
+ deviceName: name
74
+ });
75
+ }
76
+ }
77
+ }
78
+ /** Emit a device event to all connected listeners */
79
+ emitDeviceEvent(topic, data) {
80
+ this.emit({
81
+ type: "device_event",
82
+ topic,
83
+ data
84
+ });
85
+ }
86
+ /** Emit a device event to a specific device's connections */
87
+ emitDeviceEventTo(deviceId, topic, data) {
88
+ this.emit({
89
+ type: "device_event",
90
+ deviceId,
91
+ topic,
92
+ data
93
+ });
94
+ }
95
+ /** Send a notification to all connected devices */
96
+ notify(notification) {
97
+ this.emitDeviceEvent("notification", notification);
98
+ }
99
+ /** Send a notification to a specific device */
100
+ notifyDevice(deviceId, notification) {
101
+ this.emitDeviceEventTo(deviceId, "notification", notification);
102
+ }
103
+ /** Get snapshot of all connected devices */
104
+ getConnectedDevices() {
105
+ const result = [];
106
+ for (const [deviceId, set] of this.connectionsByDevice) if (set.size > 0) result.push({
107
+ deviceId,
108
+ name: this.deviceNames.get(deviceId) || "Unknown",
109
+ connections: set.size
110
+ });
111
+ return result;
112
+ }
113
+ /** Subscribe to all presence events */
114
+ subscribe(listener) {
115
+ this.listeners.add(listener);
116
+ return () => this.listeners.delete(listener);
117
+ }
118
+ emit(event) {
119
+ for (const listener of this.listeners) listener(event);
120
+ }
121
+ };
122
+ const deviceBus = new DeviceBus();
123
+ export { deviceBus as t };
@@ -1,4 +1,4 @@
1
- import { a as __require } from "../_runtime.mjs";
1
+ import { i as __require } from "../_runtime.mjs";
2
2
  import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
4
  /**
@@ -64,6 +64,7 @@ function log(level, tag, dataOrMsg, msg) {
64
64
  if (currentLevel > LOG_LEVELS[level]) return;
65
65
  const [d, m] = typeof dataOrMsg === "string" ? [void 0, dataOrMsg] : [dataOrMsg, msg];
66
66
  levelMap[level].fn(fmt(level, tag, m, d));
67
+ if (LOG_LEVELS[level] >= LOG_LEVELS.warn) notifyLog(level, tag, m);
67
68
  try {
68
69
  const entry = JSON.stringify({
69
70
  level: LOG_LEVELS[level],
@@ -102,4 +103,26 @@ function getSystemLogs(opts) {
102
103
  const limit = opts?.limit ?? 100;
103
104
  return entries.slice(offset, offset + limit);
104
105
  }
106
+ var levelToNotificationType = {
107
+ warn: "warning",
108
+ error: "alert",
109
+ fatal: "alert"
110
+ };
111
+ var _deviceBus;
112
+ function notifyLog(level, tag, message) {
113
+ if (!_deviceBus) {
114
+ import("../_libs/_88.mjs").then((m) => {
115
+ _deviceBus = m.deviceBus;
116
+ _deviceBus.notify({
117
+ title: `[${tag}] ${message}`.slice(0, 120),
118
+ type: levelToNotificationType[level] ?? "warning"
119
+ });
120
+ });
121
+ return;
122
+ }
123
+ _deviceBus.notify({
124
+ title: `[${tag}] ${message}`.slice(0, 120),
125
+ type: levelToNotificationType[level] ?? "warning"
126
+ });
127
+ }
105
128
  export { getSystemLogs as n, createLogger as t };
@@ -1,5 +1,5 @@
1
1
  import { t as createLogger } from "./logger.mjs";
2
- import { i as modelRegistry, t as authStorage } from "./session.mjs";
2
+ import { c as authStorage, u as modelRegistry } from "./server.mjs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  var logger = createLogger("pi:login");
5
5
  var LoginSession = class LoginSession {
@@ -24,10 +24,8 @@ function writeNote(ref, content) {
24
24
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
25
25
  fs.writeFileSync(filePath, content);
26
26
  }
27
- /** Delete a note. Returns true if it existed. Virtual session notes cannot be deleted. */
27
+ /** Delete a note. Returns true if it existed. */
28
28
  function deleteNote(ref) {
29
- const { category } = parseRef(ref);
30
- if (category === VIRTUAL_CATEGORY) return false;
31
29
  const filePath = resolveNotePath(ref);
32
30
  if (!fs.existsSync(filePath)) return false;
33
31
  fs.unlinkSync(filePath);
@@ -1,5 +1,5 @@
1
1
  import { r as HTTPResponse } from "../_libs/h3+rou3+srvx.mjs";
2
- const rendererTemplate = () => new HTTPResponse("<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta\n name=\"viewport\"\n content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content\"\n />\n <title>PiClaw</title>\n <meta name=\"theme-color\" content=\"#0a0e14\" />\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/icon.svg\" />\n <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/icon-192.png\" />\n <link rel=\"apple-touch-icon\" href=\"/icon-192.png\" />\n <link rel=\"manifest\" href=\"/manifest.json\" />\n </head>\n <body style=\"background-color: #0a0e14\">\n <div id=\"app\"></div>\n <script type=\"module\" src=\"/src/app/index.ts\"><\/script>\n </body>\n</html>\n", { headers: { "content-type": "text/html; charset=utf-8" } });
2
+ const rendererTemplate = () => new HTTPResponse("<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta\n name=\"viewport\"\n content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content\"\n />\n <title>PiClaw</title>\n <meta name=\"theme-color\" content=\"#0a0e14\" />\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/icon.svg\" />\n <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/icon-192.png\" />\n <link rel=\"apple-touch-icon\" href=\"/icon-192.png\" />\n <link rel=\"manifest\" href=\"/manifest.json\" />\n </head>\n <body style=\"background-color: #0a0e14\">\n <div id=\"app\"></div>\n <script>\n // Electrobun app-window: persist appToken as cookie for auth bypass\n var t = new URLSearchParams(location.search).get(\"appToken\");\n if (t) {\n var prev = document.cookie.match(/piclaw_app_token=([^;]*)/)?.[1];\n document.cookie = \"piclaw_app_token=\" + t + \";path=/;SameSite=Lax\";\n if (prev && prev !== t) location.reload();\n }\n <\/script>\n <script type=\"module\" src=\"/src/app/index.ts\"><\/script>\n </body>\n</html>\n", { headers: { "content-type": "text/html; charset=utf-8" } });
3
3
  function renderIndexHTML(event) {
4
4
  return rendererTemplate(event.req);
5
5
  }
@@ -0,0 +1,217 @@
1
+ import { t as DATA_ROOT } from "./config.mjs";
2
+ import { t as createLogger } from "./logger.mjs";
3
+ import { n as Ie, r as ft, t as At } from "../_libs/just-bash+[...].mjs";
4
+ import { t as deviceBus } from "./device-bus.mjs";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ /**
8
+ * Centralized sandbox manager — tracks in-memory filesystem sandboxes.
9
+ *
10
+ * A `Sandbox` owns a single `InMemoryFs` or `OverlayFs` + `Bash` instance.
11
+ * Both PI SDK coding tools (`src/lib/pi/tools/sandbox.ts`) and virtual PTY
12
+ * terminals (`src/lib/pty/virtual.ts`) can share the same sandbox when they
13
+ * belong to the same group, so files created by the agent are visible in the
14
+ * terminal and vice-versa.
15
+ *
16
+ * When `realCwd` is provided, uses `OverlayFs` (copy-on-write over the real
17
+ * group directory) so the agent can read real files while writes stay in memory.
18
+ *
19
+ * State is persisted to `<DATA_ROOT>/sandboxes/<id>/state.json` on destroy
20
+ * and hydrated automatically when a sandbox with the same id is created.
21
+ */
22
+ var logger = createLogger("sandbox");
23
+ var SANDBOXES_DIR = path.resolve(DATA_ROOT, "sandboxes");
24
+ function defaultCwd(id) {
25
+ return `/home/${id}`;
26
+ }
27
+ function defaultEnv(id) {
28
+ return {
29
+ HOME: `/home/${id}`,
30
+ PATH: "/usr/bin:/bin"
31
+ };
32
+ }
33
+ var SandboxManager = class {
34
+ sandboxes = /* @__PURE__ */ new Map();
35
+ /** Get or create a sandbox for the given id (typically group folder). */
36
+ get(id, opts) {
37
+ let sb = this.sandboxes.get(id);
38
+ if (!sb) {
39
+ sb = this._create(id, opts);
40
+ this.sandboxes.set(id, sb);
41
+ logger.info({
42
+ id,
43
+ cwd: sb.cwd
44
+ }, "Sandbox created");
45
+ deviceBus.emitDeviceEvent("sandbox", {
46
+ action: "created",
47
+ id
48
+ });
49
+ }
50
+ return sb;
51
+ }
52
+ /** Check if a sandbox exists. */
53
+ has(id) {
54
+ return this.sandboxes.has(id);
55
+ }
56
+ /** Destroy a sandbox, persisting its state to disk. */
57
+ destroy(id) {
58
+ const sb = this.sandboxes.get(id);
59
+ if (!sb) return false;
60
+ this._persist(sb).catch((err) => {
61
+ logger.warn({
62
+ id,
63
+ err: err.message
64
+ }, "Failed to persist sandbox state");
65
+ });
66
+ this.sandboxes.delete(id);
67
+ logger.info({ id }, "Sandbox destroyed (state persisted)");
68
+ deviceBus.emitDeviceEvent("sandbox", {
69
+ action: "destroyed",
70
+ id
71
+ });
72
+ return true;
73
+ }
74
+ /** List all active sandbox ids. */
75
+ list() {
76
+ return [...this.sandboxes.keys()];
77
+ }
78
+ /** Get info for all active sandboxes. */
79
+ getAll() {
80
+ return [...this.sandboxes.values()].map((sb) => ({
81
+ id: sb.id,
82
+ cwd: sb.env.PWD || sb.cwd,
83
+ fileCount: sb.fs.getAllPaths().length,
84
+ overlay: sb.fs instanceof ft
85
+ }));
86
+ }
87
+ /** Destroy all sandboxes (persists each). */
88
+ shutdown() {
89
+ for (const id of this.sandboxes.keys()) this.destroy(id);
90
+ }
91
+ _create(id, opts) {
92
+ const persisted = this._hydrate(id);
93
+ const cwd = persisted?.cwd ?? opts?.cwd ?? defaultCwd(id);
94
+ const initialFiles = persisted?.files ?? opts?.files;
95
+ const useOverlay = !!opts?.realCwd;
96
+ let memFs;
97
+ if (useOverlay) {
98
+ memFs = new ft({
99
+ root: opts.realCwd,
100
+ mountPoint: cwd
101
+ });
102
+ if (initialFiles) for (const [p, content] of Object.entries(initialFiles)) memFs.writeFileSync(p, content);
103
+ } else memFs = new Ie(initialFiles);
104
+ const env = persisted?.env ?? {
105
+ ...defaultEnv(id),
106
+ PWD: cwd
107
+ };
108
+ if (persisted) logger.info({ id }, "Sandbox hydrated from persisted state");
109
+ if (useOverlay) logger.info({
110
+ id,
111
+ root: opts.realCwd,
112
+ mountPoint: cwd
113
+ }, "Sandbox using OverlayFs");
114
+ const bash = new At({
115
+ cwd: env.PWD || cwd,
116
+ fs: memFs
117
+ });
118
+ const realCwd = opts?.realCwd;
119
+ const sandbox = {
120
+ id,
121
+ fs: memFs,
122
+ cwd,
123
+ env,
124
+ realCwd,
125
+ async exec(command, execOpts) {
126
+ const result = await bash.exec(command, {
127
+ cwd: execOpts?.cwd || env.PWD || sandbox.cwd,
128
+ env
129
+ });
130
+ if (result.env.PWD) sandbox.cwd = result.env.PWD;
131
+ for (const [k, v] of Object.entries(result.env)) env[k] = v;
132
+ return {
133
+ stdout: result.stdout || "",
134
+ stderr: result.stderr || "",
135
+ exitCode: result.exitCode ?? 0
136
+ };
137
+ },
138
+ async writeBack() {
139
+ if (!realCwd || !(memFs instanceof ft)) return {
140
+ written: 0,
141
+ deleted: 0
142
+ };
143
+ const canonicalRoot = fs.realpathSync(realCwd);
144
+ const mountPoint = memFs.getMountPoint();
145
+ const overlayPaths = new Set(memFs.getAllPaths());
146
+ const safeRealPath = (virtualPath) => {
147
+ const rel = virtualPath.slice(mountPoint.length);
148
+ if (!rel && virtualPath !== mountPoint) return null;
149
+ const real = path.resolve(canonicalRoot, "." + (rel || "/"));
150
+ if (!real.startsWith(canonicalRoot + path.sep) && real !== canonicalRoot) return null;
151
+ return real;
152
+ };
153
+ let written = 0;
154
+ let deleted = 0;
155
+ for (const vPath of overlayPaths) {
156
+ if (!vPath.startsWith(mountPoint)) continue;
157
+ const realP = safeRealPath(vPath);
158
+ if (!realP) continue;
159
+ const stat = await memFs.lstat(vPath);
160
+ if (stat.isFile) {
161
+ const content = await memFs.readFile(vPath);
162
+ fs.mkdirSync(path.dirname(realP), { recursive: true });
163
+ fs.writeFileSync(realP, content);
164
+ written++;
165
+ } else if (stat.isDirectory) fs.mkdirSync(realP, { recursive: true });
166
+ }
167
+ logger.info({
168
+ id,
169
+ written
170
+ }, "Sandbox writeBack complete");
171
+ return {
172
+ written,
173
+ deleted
174
+ };
175
+ }
176
+ };
177
+ return sandbox;
178
+ }
179
+ /** Serialize sandbox state to disk. */
180
+ async _persist(sb) {
181
+ try {
182
+ const files = {};
183
+ const allPaths = sb.fs.getAllPaths();
184
+ for (const p of allPaths) if ((await sb.fs.lstat(p)).isFile) files[p] = await sb.fs.readFile(p);
185
+ const state = {
186
+ cwd: sb.env.PWD || sb.cwd,
187
+ env: sb.env,
188
+ files
189
+ };
190
+ const dir = path.join(SANDBOXES_DIR, sb.id);
191
+ fs.mkdirSync(dir, { recursive: true });
192
+ fs.writeFileSync(path.join(dir, "state.json"), JSON.stringify(state));
193
+ } catch (err) {
194
+ logger.warn({
195
+ id: sb.id,
196
+ err: err.message
197
+ }, "Failed to persist sandbox state");
198
+ }
199
+ }
200
+ /** Try to load persisted state from disk. */
201
+ _hydrate(id) {
202
+ try {
203
+ const stateFile = path.join(SANDBOXES_DIR, id, "state.json");
204
+ if (!fs.existsSync(stateFile)) return void 0;
205
+ const raw = fs.readFileSync(stateFile, "utf-8");
206
+ return JSON.parse(raw);
207
+ } catch (err) {
208
+ logger.warn({
209
+ id,
210
+ err: err.message
211
+ }, "Failed to hydrate sandbox state");
212
+ return;
213
+ }
214
+ }
215
+ };
216
+ const sandboxManager = new SandboxManager();
217
+ export { sandboxManager as t };