@alashi/cli 0.4.1 → 0.5.1

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/assets/v1/chat.js CHANGED
@@ -160,6 +160,32 @@
160
160
  return div;
161
161
  }
162
162
 
163
+ /* Pending assistant bubble: animated typing dots + a live note
164
+ * ("thinking…", "using tool: X"). Replaced by real content on the
165
+ * first text token, or by an error state. */
166
+ _appendPending() {
167
+ const note = el('span', { className: 'typing-note', textContent: 'thinking…' });
168
+ const bubble = el('div', { className: 'msg assistant pending' }, [
169
+ el('span', { className: 'typing' }, [el('span'), el('span'), el('span')]),
170
+ note,
171
+ ]);
172
+ this._log.appendChild(bubble);
173
+ this._log.scrollTop = this._log.scrollHeight;
174
+ return { bubble, note };
175
+ }
176
+
177
+ _showError(bubble, hasText, message) {
178
+ if (hasText) {
179
+ // keep the partial reply; surface the error below it
180
+ bubble.classList.remove('pending');
181
+ bubble.append(el('div', { className: 'msg-error-note', textContent: message }));
182
+ } else {
183
+ bubble.className = 'msg assistant error';
184
+ bubble.textContent = message;
185
+ }
186
+ this._log.scrollTop = this._log.scrollHeight;
187
+ }
188
+
163
189
  _setMsgText(div, text, role) {
164
190
  // User text stays plain; assistant text is markdown-rendered from an
165
191
  // escape-first pipeline (renderMarkdown escapes before rebuilding tags).
@@ -188,13 +214,17 @@
188
214
  this.conversationId = id;
189
215
  this._log.replaceChildren();
190
216
  this._status.textContent = 'loading history…';
217
+ const { bubble, note } = this._appendPending();
218
+ note.textContent = 'loading history…';
191
219
  try {
192
220
  const res = await fetch(this._hostApi(`/chat/${encodeURIComponent(id)}/history`));
193
221
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
194
222
  const { messages } = await res.json();
223
+ bubble.remove();
195
224
  for (const message of messages) this._appendMsg(message.text, message.role);
196
225
  this._status.textContent = '';
197
226
  } catch (err) {
227
+ this._showError(bubble, false, `Failed to load history: ${String(err)}`);
198
228
  this._status.textContent = String(err);
199
229
  }
200
230
  this._loadConversations().catch(() => {});
@@ -233,7 +263,7 @@
233
263
  this._input.value = '';
234
264
  this._send.disabled = true;
235
265
  this._appendMsg(message, 'user');
236
- const reply = this._appendMsg('', 'assistant');
266
+ const { bubble: reply, note } = this._appendPending();
237
267
  this._status.textContent = 'thinking…';
238
268
  let replyText = '';
239
269
 
@@ -265,14 +295,27 @@
265
295
  const event = JSON.parse(dataLine.slice(6));
266
296
  if (event.type === 'text') {
267
297
  replyText += event.text;
298
+ reply.classList.remove('pending');
268
299
  this._setMsgText(reply, replyText, 'assistant');
269
300
  this._log.scrollTop = this._log.scrollHeight;
270
301
  }
271
- if (event.type === 'tool_use') this._status.textContent = `using tool: ${event.name}`;
302
+ if (event.type === 'tool_use') {
303
+ this._status.textContent = `using tool: ${event.name}`;
304
+ if (reply.classList.contains('pending'))
305
+ note.textContent = `using tool: ${event.name}…`;
306
+ }
272
307
  if (event.type === 'result') {
273
308
  this._status.textContent = event.ok
274
309
  ? `done${event.costUsd ? ` ($${event.costUsd.toFixed(4)})` : ''}`
275
310
  : `error: ${event.error}`;
311
+ if (!event.ok) {
312
+ this._showError(reply, replyText !== '', `Something went wrong: ${event.error}`);
313
+ } else if (replyText === '') {
314
+ reply.className = 'msg assistant';
315
+ reply.replaceChildren(
316
+ el('span', { className: 'typing-note', textContent: '(no reply)' }),
317
+ );
318
+ }
276
319
  this._emit('turn', {
277
320
  ok: event.ok,
278
321
  costUsd: event.costUsd,
@@ -285,6 +328,7 @@
285
328
  if (isNew) this._loadConversations().catch(() => {});
286
329
  } catch (err) {
287
330
  this._status.textContent = String(err);
331
+ this._showError(reply, replyText !== '', `Something went wrong: ${String(err)}`);
288
332
  } finally {
289
333
  this._send.disabled = false;
290
334
  this._input.focus();
@@ -6,10 +6,37 @@
6
6
  * block — e.g. `:root { --accent: #dc2626; }` — but should not restyle
7
7
  * components. Breaking changes ship as /assets/v2/. */
8
8
 
9
+ /* ---- brand fonts (vendored, offline-friendly — no CDNs) ---- */
10
+ @font-face {
11
+ font-family: "Chakra Petch";
12
+ font-weight: 600;
13
+ font-display: swap;
14
+ src: url("/assets/v1/fonts/chakra-petch-600.woff2") format("woff2");
15
+ }
16
+ @font-face {
17
+ font-family: "Chakra Petch";
18
+ font-weight: 700;
19
+ font-display: swap;
20
+ src: url("/assets/v1/fonts/chakra-petch-700.woff2") format("woff2");
21
+ }
22
+ @font-face {
23
+ font-family: "JetBrains Mono";
24
+ font-weight: 400;
25
+ font-display: swap;
26
+ src: url("/assets/v1/fonts/jetbrains-mono-400.woff2") format("woff2");
27
+ }
28
+ @font-face {
29
+ font-family: "JetBrains Mono";
30
+ font-weight: 600;
31
+ font-display: swap;
32
+ src: url("/assets/v1/fonts/jetbrains-mono-600.woff2") format("woff2");
33
+ }
34
+
9
35
  /* ---- design tokens ---- */
10
36
  :root {
11
37
  --font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
12
- --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
38
+ --font-display: "Chakra Petch", system-ui, -apple-system, sans-serif;
39
+ --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
13
40
  --text-size: 15px;
14
41
 
15
42
  --bg: #f3f7f8;
@@ -59,6 +86,10 @@ h1 {
59
86
  h2 {
60
87
  font-size: 1.1rem;
61
88
  }
89
+ h1,
90
+ h2 {
91
+ font-family: var(--font-display);
92
+ }
62
93
  a {
63
94
  color: var(--accent);
64
95
  }
@@ -126,6 +157,7 @@ body.with-sidebar {
126
157
  }
127
158
  .brand {
128
159
  color: #fff;
160
+ font-family: var(--font-display);
129
161
  font-weight: 700;
130
162
  font-size: 1.05rem;
131
163
  padding: 0 0.75rem 1.25rem;
@@ -280,6 +312,8 @@ tbody tr.clickable:hover td {
280
312
  /* ---- buttons & inputs ---- */
281
313
  button {
282
314
  font: inherit;
315
+ font-family: var(--font-display);
316
+ font-weight: 600;
283
317
  border: 1px solid var(--border);
284
318
  background: var(--panel);
285
319
  color: var(--text);
@@ -305,20 +339,35 @@ button:disabled {
305
339
  opacity: 0.5;
306
340
  cursor: default;
307
341
  }
342
+ input:not([type]),
308
343
  input[type="text"],
309
- input[type="password"] {
344
+ input[type="password"],
345
+ input[type="email"],
346
+ input[type="number"],
347
+ input[type="search"],
348
+ input[type="url"],
349
+ textarea {
310
350
  font: inherit;
311
351
  padding: 0.5rem 0.75rem;
312
352
  border: 1px solid var(--border);
313
353
  border-radius: var(--radius-sm);
354
+ background: var(--panel);
355
+ color: var(--text);
314
356
  min-width: 0;
315
357
  width: 100%;
316
358
  }
359
+ textarea {
360
+ min-height: 5.5rem;
361
+ resize: vertical;
362
+ line-height: 1.5;
363
+ }
364
+ .row > input:not([type]),
317
365
  .row > input[type="text"] {
318
366
  flex: 1;
319
367
  width: auto;
320
368
  }
321
- input:focus {
369
+ input:focus,
370
+ textarea:focus {
322
371
  outline: 2px solid var(--accent-soft);
323
372
  border-color: var(--accent);
324
373
  }
@@ -330,6 +379,53 @@ select {
330
379
  background: var(--panel);
331
380
  }
332
381
 
382
+ /* ---- dialogs ---- */
383
+ dialog {
384
+ border: 1px solid var(--border);
385
+ border-radius: var(--radius);
386
+ background: var(--panel);
387
+ color: var(--text);
388
+ padding: 1.5rem;
389
+ width: min(44rem, 95vw);
390
+ max-height: 90vh;
391
+ overflow-y: auto;
392
+ box-shadow: 0 20px 50px rgba(10, 17, 32, 0.25);
393
+ }
394
+ dialog::backdrop {
395
+ background: rgba(10, 17, 32, 0.45);
396
+ }
397
+ dialog h2:first-child {
398
+ margin-top: 0;
399
+ }
400
+ .dialog-close {
401
+ position: absolute;
402
+ top: 0.6rem;
403
+ right: 0.6rem;
404
+ border: 0;
405
+ background: none;
406
+ padding: 0.25rem 0.55rem;
407
+ font-size: 1.3rem;
408
+ line-height: 1;
409
+ color: var(--muted);
410
+ }
411
+ .dialog-close:hover {
412
+ color: var(--text);
413
+ border: 0;
414
+ }
415
+
416
+ /* ---- form layout helpers ---- */
417
+ .stack {
418
+ display: flex;
419
+ flex-direction: column;
420
+ gap: 0.75rem;
421
+ }
422
+ .actions {
423
+ display: flex;
424
+ gap: 0.5rem;
425
+ justify-content: flex-end;
426
+ margin-top: 1rem;
427
+ }
428
+
333
429
  /* ---- badges & chips ---- */
334
430
  .badge {
335
431
  display: inline-block;
@@ -519,6 +615,57 @@ input[type="checkbox"] {
519
615
  .msg.assistant h6 {
520
616
  font-size: 0.92rem;
521
617
  }
618
+ .msg.assistant.pending {
619
+ display: flex;
620
+ align-items: center;
621
+ gap: 0.6rem;
622
+ color: var(--muted);
623
+ }
624
+ .msg.assistant.error {
625
+ background: var(--err-soft);
626
+ border-color: var(--err);
627
+ color: var(--err);
628
+ }
629
+ .msg-error-note {
630
+ margin-top: 0.5rem;
631
+ padding-top: 0.5rem;
632
+ border-top: 1px solid var(--border);
633
+ color: var(--err);
634
+ font-size: 0.85rem;
635
+ }
636
+ .typing {
637
+ display: inline-flex;
638
+ gap: 4px;
639
+ align-items: center;
640
+ }
641
+ .typing span {
642
+ width: 6px;
643
+ height: 6px;
644
+ border-radius: 50%;
645
+ background: var(--muted);
646
+ animation: typing-bounce 1s ease-in-out infinite;
647
+ }
648
+ .typing span:nth-child(2) {
649
+ animation-delay: 0.15s;
650
+ }
651
+ .typing span:nth-child(3) {
652
+ animation-delay: 0.3s;
653
+ }
654
+ @keyframes typing-bounce {
655
+ 0%,
656
+ 60%,
657
+ 100% {
658
+ opacity: 0.35;
659
+ transform: translateY(0);
660
+ }
661
+ 30% {
662
+ opacity: 1;
663
+ transform: translateY(-3px);
664
+ }
665
+ }
666
+ .typing-note {
667
+ font-size: 0.85rem;
668
+ }
522
669
  .chat-form {
523
670
  display: flex;
524
671
  gap: 0.5rem;
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as installApp, c as addRegistry, d as removeRegistry, g as loadApp, i as resolveAppConfig, n as createServer, o as removeApp, s as updateApp, t as Store } from "./store-Bk-PC18o.mjs";
2
+ import { a as installApp, c as addRegistry, d as removeRegistry, g as loadApp, i as resolveAppConfig, n as createServer, o as removeApp, s as updateApp, t as Store } from "./store-Cxxnjq70.mjs";
3
3
  import { APP_NAME_PATTERN, AppSdkError } from "@alashi/apps-sdk";
4
4
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
5
5
  import { join, resolve } from "node:path";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/compat.ts","../src/sources.ts","../src/store.ts","../src/installer.ts","../src/loader.ts","../src/registry.ts","../src/runtime.ts","../src/scheduler.ts","../src/server.ts"],"mappings":";;;;;;;iBAOgB,sBAAsB,UAAU;;;KCCpC;EACN;EAAa;EAAa;;EAC1B;EAAe;;;KAGT,YAAY;EAAkB;EAAkB;;iBAE5C,eAAe,cAAc;;iBAwBvB,YAAY,MAAM,eAAe,kBAAkB;;iBAanD,aAAa,MAAM,eAAe,kBAAkB;;;UC9CzD;EACf;EACA;;UAGe;EACf,YAAY;EACZ;;EAEA;;EAEA,OAAO,eAAe;;UAGP;EACf;;EAEA;EACA;EACA;;;UAIe;EACf,eAAe;;UAGA;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf,SAAS;EACT,MAAM;;UAGS;EACf;EACA;;EAEA;EACA,QAAQ;EACR;EACA;;;;;;;cAwBW;WACF;EAET,YAAY;MAIR;MAIA;MAIA;MAIA;;EAKJ,OAAO;EAOP,WAAW;EAIX,WAAW;EAIX,cAAc;EAId,cAAc;EAId,iBAAiB;EAIjB,gBAAgB;EAIhB,kBAAkB;EAOlB,oBAA0B,QAAQ;EAIlC,kBAAwB,QAAQ,eAAe;EAI/C,cAAoB,eAAe,QAAQ;EAI3C,eAAqB,QAAQ,gBAAgB;EAI7C,cAAoB,eAAe,QAAQ;EAI3C,eAAqB,cAAc,QAAQ,eAAe;EAI1D,cAAoB,eAAe,QAAQ;EAK3C,eAAqB,cAAc,OAAO,YAAY;EAItD,aAAmB,eAAe,QAAQ;EAK1C,cAAoB,cAAc,OAAO,WAAW;EAIpD,iBAAuB,QAAQ;;;;UC9IhB;;EAEf;;;;;;;iBAQoB,WACpB,OAAO,OACP,iBAAiB,eACjB,UAAS,iBACR,QAAQ;iBA0EW,UAAU,OAAO,OAAO,eAAe,QAAQ;UAuBpD;;EAEf;;iBAGoB,UACpB,OAAO,OACP,cACA,UAAS,gBACR;;;UCzIc;EACf,YAAY;EACZ,UAAU;EACV;;;;;;;iBAQoB,QAAQ,kBAAkB,QAAQ;;;UC5BvC;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf,MAAM;;;;;;;iBAkCc,gBAAgB,OAAO,OAAO,UAAU,cAAc,QAAQ;iBAuB9D,gBACpB,OAAO,OACP,eACC,QAAQ;iBASW,YAAY,OAAO,OAAO,cAAc,cAAc,QAAQ;iBAW9D,eAAe,OAAO,OAAO,eAAe;UAWjD;EACf;EACA,KAAK;;;iBAIe,WAAW,OAAO,OAAO,kBAAkB,QAAQ;;;;;;;;;;;;;iBC8BnD,gBACpB,OAAO,OACP,UAAU,aACV,UAAU,aACT,QAAQ;;;UC/IM;EACf,UAAU;EACV,KAAK;EACL,cAAc,gBAAgB,KAAK,eAAe;;UAGnC;EACf;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA,YAAY;;;;;;;;cAiBD;mBAQT;mBACA;UARM;UACA;UACA;;UAEA;EAER,YACE,OAAwB,OACxB,SAA0B,iBAAiB,QAAQ;EAGrD,SAAe;EAuCf;;;;;EAUA,IACE,iBACA,iBACA,UAAS,oBACR,QAAQ;;EAmCX,OAAa,kBAAkB,QAAQ;UA4B/B;;;;UCrHO;;EAEf,KAAK;;EAEL,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BK,aAAa,OAAO,OAAO,UAAU,aAAa"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/compat.ts","../src/sources.ts","../src/store.ts","../src/installer.ts","../src/loader.ts","../src/registry.ts","../src/runtime.ts","../src/scheduler.ts","../src/server.ts"],"mappings":";;;;;;;iBAOgB,sBAAsB,UAAU;;;KCCpC;EACN;EAAa;EAAa;;EAC1B;EAAe;;;KAGT,YAAY;EAAkB;EAAkB;;iBAE5C,eAAe,cAAc;;iBAwBvB,YAAY,MAAM,eAAe,kBAAkB;;iBAanD,aAAa,MAAM,eAAe,kBAAkB;;;UC9CzD;EACf;EACA;;UAGe;EACf,YAAY;EACZ;;EAEA;;EAEA,OAAO,eAAe;;UAGP;EACf;;EAEA;EACA;EACA;;;UAIe;EACf,eAAe;;UAGA;EACf;EACA;EACA;EACA;EACA;EACA;;;UAIe;EACf,SAAS;EACT,MAAM;;UAGS;EACf;EACA;;EAEA;EACA,QAAQ;EACR;EACA;;;;;;;cAwBW;WACF;EAET,YAAY;MAIR;MAIA;MAIA;MAIA;;EAKJ,OAAO;EAOP,WAAW;EAIX,WAAW;EAIX,cAAc;EAId,cAAc;EAId,iBAAiB;EAIjB,gBAAgB;EAIhB,kBAAkB;EAOlB,oBAA0B,QAAQ;EAIlC,kBAAwB,QAAQ,eAAe;EAI/C,cAAoB,eAAe,QAAQ;EAI3C,eAAqB,QAAQ,gBAAgB;EAI7C,cAAoB,eAAe,QAAQ;EAI3C,eAAqB,cAAc,QAAQ,eAAe;EAI1D,cAAoB,eAAe,QAAQ;EAK3C,eAAqB,cAAc,OAAO,YAAY;EAItD,aAAmB,eAAe,QAAQ;EAK1C,cAAoB,cAAc,OAAO,WAAW;EAIpD,iBAAuB,QAAQ;;;;UC9IhB;;EAEf;;;;;;;iBAQoB,WACpB,OAAO,OACP,iBAAiB,eACjB,UAAS,iBACR,QAAQ;iBA0EW,UAAU,OAAO,OAAO,eAAe,QAAQ;UAuBpD;;EAEf;;iBAGoB,UACpB,OAAO,OACP,cACA,UAAS,gBACR;;;UCzIc;EACf,YAAY;EACZ,UAAU;EACV;;;;;;;iBAQoB,QAAQ,kBAAkB,QAAQ;;;UC5BvC;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf,MAAM;;;;;;;iBAkCc,gBAAgB,OAAO,OAAO,UAAU,cAAc,QAAQ;iBAuB9D,gBACpB,OAAO,OACP,eACC,QAAQ;iBASW,YAAY,OAAO,OAAO,cAAc,cAAc,QAAQ;iBAW9D,eAAe,OAAO,OAAO,eAAe;UAWjD;EACf;EACA,KAAK;;;iBAIe,WAAW,OAAO,OAAO,kBAAkB,QAAQ;;;;;;;;;;;;;iBC8BnD,gBACpB,OAAO,OACP,UAAU,aACV,UAAU,aACT,QAAQ;;;UC/IM;EACf,UAAU;EACV,KAAK;EACL,cAAc,gBAAgB,KAAK,eAAe;;UAGnC;EACf;EACA;EACA;;EAEA;EACA;EACA;EACA;EACA,YAAY;;;;;;;;cAiBD;mBAQT;mBACA;UARM;UACA;UACA;;UAEA;EAER,YACE,OAAwB,OACxB,SAA0B,iBAAiB,QAAQ;EAGrD,SAAe;EAuCf;;;;;EAUA,IACE,iBACA,iBACA,UAAS,oBACR,QAAQ;;EAmCX,OAAa,kBAAkB,QAAQ;UA4B/B;;;;UCpHO;;EAEf,KAAK;;EAEL,WAAW;;EAEX,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BK,aAAa,OAAO,OAAO,UAAU,aAAa"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as checkSdkCompatibility, a as installApp, c as addRegistry, d as removeRegistry, f as resolveApp, g as loadApp, h as updateSource, l as readCachedIndex, m as parseSourceArg, n as createServer, o as removeApp, p as fetchSource, r as buildAppContext, s as updateApp, t as Store, u as refreshRegistry } from "./store-Bk-PC18o.mjs";
1
+ import { _ as checkSdkCompatibility, a as installApp, c as addRegistry, d as removeRegistry, f as resolveApp, g as loadApp, h as updateSource, l as readCachedIndex, m as parseSourceArg, n as createServer, o as removeApp, p as fetchSource, r as buildAppContext, s as updateApp, t as Store, u as refreshRegistry } from "./store-Cxxnjq70.mjs";
2
2
  export { Store, addRegistry, buildAppContext, checkSdkCompatibility, createServer, fetchSource, installApp, loadApp, parseSourceArg, readCachedIndex, refreshRegistry, removeApp, removeRegistry, resolveApp, updateApp, updateSource };
@@ -876,7 +876,8 @@ const CONTENT_TYPES = {
876
876
  ".svg": "image/svg+xml",
877
877
  ".png": "image/png",
878
878
  ".ico": "image/x-icon",
879
- ".txt": "text/plain; charset=utf-8"
879
+ ".txt": "text/plain; charset=utf-8",
880
+ ".woff2": "font/woff2"
880
881
  };
881
882
  function escapeHtml(value) {
882
883
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
@@ -1440,4 +1441,4 @@ var Store = class {
1440
1441
  //#endregion
1441
1442
  export { checkSdkCompatibility as _, installApp as a, addRegistry as c, removeRegistry as d, resolveApp as f, loadApp as g, updateSource as h, resolveAppConfig as i, readCachedIndex as l, parseSourceArg as m, createServer as n, removeApp as o, fetchSource as p, buildAppContext as r, updateApp as s, Store as t, refreshRegistry as u };
1442
1443
 
1443
- //# sourceMappingURL=store-Bk-PC18o.mjs.map
1444
+ //# sourceMappingURL=store-Cxxnjq70.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-Cxxnjq70.mjs","names":["execFileAsync"],"sources":["../src/compat.ts","../src/loader.ts","../src/sources.ts","../src/registry.ts","../src/installer.ts","../src/runtime.ts","../src/scheduler.ts","../src/settings-page.ts","../src/server.ts","../src/store.ts"],"sourcesContent":["import { type AppManifest, AppSdkError, SDK_VERSION } from '@alashi/apps-sdk';\nimport semver from 'semver';\n\n/**\n * Enforces the manifest's declared `sdkVersion` range against the SDK\n * this host is built with. Apps that declare nothing are accepted.\n */\nexport function checkSdkCompatibility(manifest: AppManifest): void {\n const range = manifest.sdkVersion;\n if (!range) return;\n if (!semver.validRange(range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" declares an invalid sdkVersion range: ${JSON.stringify(range)}`,\n );\n }\n if (!semver.satisfies(SDK_VERSION, range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" supports SDK ${range}, but this host uses SDK ${SDK_VERSION}. ` +\n 'Update the host (or the app) to a compatible version.',\n );\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type AppDefinition, type AppManifest, AppSdkError, parseManifest } from '@alashi/apps-sdk';\n\ninterface PackageJson {\n main?: string;\n exports?: unknown;\n}\n\nfunction entryFromExports(exportsField: unknown): string | undefined {\n if (typeof exportsField === 'string') return exportsField;\n if (typeof exportsField !== 'object' || exportsField === null) return undefined;\n const record = exportsField as Record<string, unknown>;\n const dot = record['.'] ?? record.default ?? record.import;\n if (typeof dot === 'string') return dot;\n if (typeof dot === 'object' && dot !== null) {\n const conditions = dot as Record<string, unknown>;\n const target = conditions.default ?? conditions.import;\n if (typeof target === 'string') return target;\n }\n return undefined;\n}\n\nexport interface LoadedApp {\n definition: AppDefinition;\n manifest: AppManifest;\n entryPath: string;\n}\n\n/**\n * Imports an app's entry module and validates its default export.\n * The manifest is always re-validated here — the host never trusts\n * that an app used defineApp().\n */\nexport async function loadApp(repoDir: string): Promise<LoadedApp> {\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(join(repoDir, 'package.json'), 'utf8')) as PackageJson;\n } catch (err) {\n throw new AppSdkError(`App has no readable package.json in ${repoDir}`, { cause: err });\n }\n\n const entry = entryFromExports(pkg.exports) ?? pkg.main ?? 'index.js';\n const entryPath = join(repoDir, entry);\n\n let mod: Record<string, unknown>;\n try {\n // Cache-bust so `alashi update` reloads changed code within one process.\n mod = (await import(`${pathToFileURL(entryPath).href}?t=${Date.now()}`)) as Record<\n string,\n unknown\n >;\n } catch (err) {\n let detail = String(err);\n // defineApp() in the app's own bundled SDK validates at import time; an\n // \"unrecognized key\" there means the manifest uses fields newer than that\n // bundled SDK understands (cross-package, so match by name not instanceof).\n if ((err as Error)?.name === 'ManifestValidationError' && /Unrecognized key/i.test(detail)) {\n detail +=\n \"\\nThe manifest may use fields newer than the app's bundled @alashi/apps-sdk — update the app's dependency to match its sdkVersion.\";\n }\n throw new AppSdkError(`Failed to import app entry ${entry}: ${detail}`, { cause: err });\n }\n\n const definition = mod.default as AppDefinition | undefined;\n if (typeof definition !== 'object' || definition === null || !('manifest' in definition)) {\n throw new AppSdkError(\n 'App entry must default-export an app definition (use defineApp() from @alashi/apps-sdk)',\n );\n }\n\n const manifest = parseManifest(definition.manifest);\n return { definition, manifest, entryPath };\n}\n","import { execFile } from 'node:child_process';\nimport { mkdir, symlink } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\n\nconst execFileAsync = promisify(execFile);\n\nexport type AppSourceSpec =\n | { type: 'git'; url: string; ref?: string }\n | { type: 'local'; path: string };\n\n/** How the CLI argument was understood: a source, or a name to resolve via registries. */\nexport type SourceArg = AppSourceSpec | { type: 'registry'; name: string };\n\nexport function parseSourceArg(arg: string): SourceArg {\n if (arg.startsWith('.') || isAbsolute(arg)) {\n return { type: 'local', path: resolve(arg) };\n }\n if (/^(https?:\\/\\/|git@|ssh:\\/\\/)/.test(arg)) {\n return { type: 'git', url: arg };\n }\n if (/^github\\.com\\//.test(arg)) {\n return { type: 'git', url: `https://${arg}` };\n }\n return { type: 'registry', name: arg };\n}\n\nexport async function git(args: string[], cwd?: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, cwd ? { cwd } : {});\n return stdout.trim();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(`git ${args[0]} failed: ${message}`, { cause: err });\n }\n}\n\n/** Materializes a source at destDir: git clone, or symlink for local dev installs. */\nexport async function fetchSource(spec: AppSourceSpec, destDir: string): Promise<void> {\n await mkdir(resolve(destDir, '..'), { recursive: true });\n if (spec.type === 'git') {\n const args = ['clone', '--depth', '1'];\n if (spec.ref) args.push('--branch', spec.ref);\n args.push(spec.url, destDir);\n await git(args);\n return;\n }\n await symlink(spec.path, destDir, 'dir');\n}\n\n/** Brings an installed source up to date in place. Local symlinks are always live. */\nexport async function updateSource(spec: AppSourceSpec, repoDir: string): Promise<void> {\n if (spec.type === 'git') {\n await git(['pull', '--ff-only'], repoDir);\n }\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { z } from 'zod';\nimport { fetchSource, git } from './sources.js';\nimport type { RegistryRef, Store } from './store.js';\n\nexport interface RegistryApp {\n name: string;\n repo: string;\n description?: string;\n ref?: string;\n sdkVersion?: string;\n}\n\nexport interface RegistryIndex {\n apps: RegistryApp[];\n}\n\nconst registryIndexSchema = z.object({\n apps: z.array(\n z.object({\n name: z.string().min(1),\n repo: z.string().min(1),\n description: z.string().optional(),\n ref: z.string().optional(),\n sdkVersion: z.string().optional(),\n }),\n ),\n});\n\nfunction parseIndex(raw: string, source: string): RegistryIndex {\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n throw new AppSdkError(`Registry ${source} did not return valid JSON`, { cause: err });\n }\n const result = registryIndexSchema.safeParse(data);\n if (!result.success) {\n throw new AppSdkError(`Registry ${source} has an invalid index: ${result.error.message}`);\n }\n return result.data;\n}\n\n/**\n * Fetches a registry index and caches it under registries/<name>.json.\n * URLs ending in .json are fetched over HTTP; anything else is treated\n * as a git repo containing index.json at its root (brew-tap style).\n */\nexport async function refreshRegistry(store: Store, registry: RegistryRef): Promise<RegistryIndex> {\n let raw: string;\n if (/^https?:\\/\\/.*\\.json$/.test(registry.url)) {\n const response = await fetch(registry.url);\n if (!response.ok) {\n throw new AppSdkError(`Registry fetch failed (${response.status}) for ${registry.url}`);\n }\n raw = await response.text();\n } else {\n const cloneDir = join(store.stagingDir, `registry-${registry.name}-${Date.now()}`);\n await fetchSource({ type: 'git', url: registry.url }, cloneDir);\n try {\n raw = await readFile(join(cloneDir, 'index.json'), 'utf8');\n } finally {\n await rm(cloneDir, { recursive: true, force: true });\n }\n }\n const index = parseIndex(raw, registry.url);\n await mkdir(store.registriesDir, { recursive: true });\n await writeFile(store.registryCachePath(registry.name), JSON.stringify(index, null, 2), 'utf8');\n return index;\n}\n\nexport async function readCachedIndex(\n store: Store,\n name: string,\n): Promise<RegistryIndex | undefined> {\n try {\n return parseIndex(await readFile(store.registryCachePath(name), 'utf8'), name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nexport async function addRegistry(store: Store, name: string, url: string): Promise<RegistryIndex> {\n const config = await store.readGlobalConfig();\n if (config.registries.some((r) => r.name === name)) {\n throw new AppSdkError(`Registry \"${name}\" already exists`);\n }\n const index = await refreshRegistry(store, { name, url });\n config.registries.push({ name, url });\n await store.writeGlobalConfig(config);\n return index;\n}\n\nexport async function removeRegistry(store: Store, name: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const next = config.registries.filter((r) => r.name !== name);\n if (next.length === config.registries.length) {\n throw new AppSdkError(`Registry \"${name}\" is not configured`);\n }\n config.registries = next;\n await store.writeGlobalConfig(config);\n await rm(store.registryCachePath(name), { force: true });\n}\n\nexport interface ResolvedApp {\n registry: string;\n app: RegistryApp;\n}\n\n/** Finds an app by name across configured registries (refreshing caches). */\nexport async function resolveApp(store: Store, appName: string): Promise<ResolvedApp> {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n throw new AppSdkError(\n `No registries configured. Add one with: alashi registry add <name> <url>`,\n );\n }\n for (const registry of config.registries) {\n let index: RegistryIndex;\n try {\n index = await refreshRegistry(store, registry);\n } catch {\n const cached = await readCachedIndex(store, registry.name);\n if (!cached) continue;\n index = cached;\n }\n const app = index.apps.find((a) => a.name === appName);\n if (app) return { registry: registry.name, app };\n }\n throw new AppSdkError(`App \"${appName}\" not found in any configured registry`);\n}\n\nexport { git };\n","import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { checkSdkCompatibility } from './compat.js';\nimport { loadApp } from './loader.js';\nimport { resolveApp } from './registry.js';\nimport { type AppSourceSpec, fetchSource, parseSourceArg, updateSource } from './sources.js';\nimport type { InstallRecord, Store } from './store.js';\n\nconst execFileAsync = promisify(execFile);\n\nasync function hasDependencies(dir: string): Promise<boolean> {\n try {\n const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8')) as {\n dependencies?: Record<string, string>;\n };\n return Object.keys(pkg.dependencies ?? {}).length > 0;\n } catch {\n return false;\n }\n}\n\n/** Lifecycle scripts stay off: installing an app must not execute arbitrary code. */\nasync function installDependencies(dir: string): Promise<void> {\n if (!(await hasDependencies(dir))) return;\n try {\n await execFileAsync(\n 'npm',\n ['install', '--ignore-scripts', '--omit=dev', '--no-fund', '--no-audit'],\n { cwd: dir },\n );\n } catch (err) {\n throw new AppSdkError(`npm install failed: ${err instanceof Error ? err.message : err}`, {\n cause: err,\n });\n }\n}\n\nexport interface InstallOptions {\n /** Expected app name (enforced against the manifest when set). */\n expectName?: string;\n}\n\n/**\n * Install flow: materialize the source into a staging dir, install deps\n * (scripts disabled), import + validate the manifest, and only then move\n * it into apps/<name>/repo. A failed validation never leaves a broken app.\n */\nexport async function installApp(\n store: Store,\n source: string | AppSourceSpec,\n options: InstallOptions = {},\n): Promise<InstallRecord> {\n let spec: AppSourceSpec;\n let expectName = options.expectName;\n\n if (typeof source === 'string') {\n const parsed = parseSourceArg(source);\n if (parsed.type === 'registry') {\n const resolved = await resolveApp(store, parsed.name);\n spec = {\n type: 'git',\n url: resolved.app.repo,\n ...(resolved.app.ref && { ref: resolved.app.ref }),\n };\n expectName = expectName ?? parsed.name;\n } else {\n spec = parsed;\n }\n } else {\n spec = source;\n }\n\n // Local sources are symlinked in place; git sources are staged first.\n let stageDir: string | undefined;\n let inspectDir: string;\n if (spec.type === 'local') {\n inspectDir = spec.path;\n } else {\n stageDir = join(store.stagingDir, randomUUID());\n await fetchSource(spec, stageDir);\n await installDependencies(stageDir);\n inspectDir = stageDir;\n }\n\n try {\n const { manifest } = await loadApp(inspectDir);\n checkSdkCompatibility(manifest);\n if (expectName && manifest.name !== expectName) {\n throw new AppSdkError(\n `App manifest name \"${manifest.name}\" does not match requested \"${expectName}\"`,\n );\n }\n const existing = await store.readAppRecord(manifest.name);\n if (existing) {\n throw new AppSdkError(\n `App \"${manifest.name}\" is already installed (use: alashi update ${manifest.name})`,\n );\n }\n\n const repoDir = store.appRepoDir(manifest.name);\n await mkdir(store.appDir(manifest.name), { recursive: true });\n if (stageDir) {\n await rename(stageDir, repoDir);\n stageDir = undefined;\n } else {\n await fetchSource(spec, repoDir);\n }\n await mkdir(store.appDataDir(manifest.name), { recursive: true });\n\n const now = new Date().toISOString();\n const record: InstallRecord = {\n name: manifest.name,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n source: spec,\n installedAt: now,\n updatedAt: now,\n };\n await store.writeAppRecord(record);\n return record;\n } finally {\n if (stageDir) await rm(stageDir, { recursive: true, force: true });\n }\n}\n\nexport async function updateApp(store: Store, name: string): Promise<InstallRecord> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n const repoDir = store.appRepoDir(name);\n await updateSource(record.source, repoDir);\n await installDependencies(repoDir);\n const { manifest } = await loadApp(repoDir);\n checkSdkCompatibility(manifest);\n if (manifest.name !== name) {\n throw new AppSdkError(`App renamed itself to \"${manifest.name}\"; refusing update`);\n }\n\n const updated: InstallRecord = {\n ...record,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n updatedAt: new Date().toISOString(),\n };\n await store.writeAppRecord(updated);\n return updated;\n}\n\nexport interface RemoveOptions {\n /** Also delete the app's data/ and config (default keeps them). */\n purge?: boolean;\n}\n\nexport async function removeApp(\n store: Store,\n name: string,\n options: RemoveOptions = {},\n): Promise<void> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n if (options.purge) {\n await rm(store.appDir(name), { recursive: true, force: true });\n return;\n }\n await rm(store.appRepoDir(name), { recursive: true, force: true });\n await rm(store.appRecordPath(name), { force: true });\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport {\n type AgentDefinition,\n type AIProvider,\n type AISession,\n type AppConfigStatus,\n type AppContext,\n type AppManifest,\n AppSdkError,\n ConfigError,\n type ConfigObject,\n evaluateAppConfig,\n interpolateConfig,\n JsonFileStore,\n type Logger,\n type McpServerConfig,\n manifestConfigDefaults,\n mergeConfig,\n parseAgentFile,\n type SessionOptions,\n} from '@alashi/apps-sdk';\nimport type { Store } from './store.js';\n\nfunction createLogger(prefix: string): Logger {\n const log = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>,\n ): void => {\n const line = `[${prefix}] ${message}`;\n if (data === undefined) console[level](line);\n else console[level](line, data);\n };\n return {\n debug: (message, data) => log('debug', message, data),\n info: (message, data) => log('info', message, data),\n warn: (message, data) => log('warn', message, data),\n error: (message, data) => log('error', message, data),\n };\n}\n\ninterface SessionDefaults {\n mcpServers: Record<string, McpServerConfig>;\n allowedTools: string[];\n agents: Record<string, AgentDefinition>;\n skillDirs: string[];\n instructions?: string;\n cwd: string;\n /** When set, createSession fails closed with this message (missing required config). */\n unreadyError?: string;\n}\n\n/**\n * Wraps the host provider so every session an app opens automatically gets\n * its manifest-declared MCP servers, tool allowlist, agents, skills,\n * instructions, and data-dir cwd. Per-call options still win where they\n * overlap.\n */\nfunction scopeProvider(provider: AIProvider, defaults: SessionDefaults): AIProvider {\n return {\n name: provider.name,\n async createSession(options: SessionOptions = {}): Promise<AISession> {\n if (defaults.unreadyError !== undefined) throw new ConfigError(defaults.unreadyError);\n const mcpServers = { ...defaults.mcpServers, ...options.mcpServers };\n const allowedTools = [\n ...new Set([...defaults.allowedTools, ...(options.allowedTools ?? [])]),\n ];\n const agents = { ...defaults.agents, ...options.agents };\n const skillDirs = [...new Set([...defaults.skillDirs, ...(options.skillDirs ?? [])])];\n return provider.createSession({\n cwd: defaults.cwd,\n ...(defaults.instructions !== undefined && { systemPrompt: defaults.instructions }),\n ...options,\n ...(Object.keys(mcpServers).length > 0 && { mcpServers }),\n ...(allowedTools.length > 0 && { allowedTools }),\n ...(Object.keys(agents).length > 0 && { agents }),\n ...(skillDirs.length > 0 && { skillDirs }),\n });\n },\n };\n}\n\n/**\n * Reads and parses the manifest-declared agent files. The agent's name is\n * the filename without the `.md` extension.\n */\nexport async function loadAgents(\n repoDir: string,\n paths: string[],\n): Promise<Record<string, AgentDefinition>> {\n const entries = await Promise.all(\n paths.map(async (path) => {\n const content = await readFile(resolve(repoDir, path), 'utf8');\n return [basename(path, '.md'), parseAgentFile(content, path)] as const;\n }),\n );\n return Object.fromEntries(entries);\n}\n\n/** Reads the manifest-declared instructions file; the content is the app's system prompt. */\nexport async function loadInstructions(\n repoDir: string,\n path: string | undefined,\n): Promise<string | undefined> {\n if (path === undefined) return undefined;\n const content = (await readFile(resolve(repoDir, path), 'utf8')).trim();\n if (content === '') {\n throw new AppSdkError(`Instructions file \"${path}\" is empty`);\n }\n return content;\n}\n\nexport interface ResolvedAppConfig {\n /** Merged config layers, before `${config.*}` interpolation. */\n merged: ConfigObject;\n status: AppConfigStatus;\n}\n\n/** Merges the config layers for an app and evaluates its setup state. */\nexport async function resolveAppConfig(\n store: Store,\n manifest: AppManifest,\n): Promise<ResolvedAppConfig> {\n const globalConfig = await store.readGlobalConfig();\n const merged = mergeConfig(\n manifestConfigDefaults(manifest),\n globalConfig.apps?.[manifest.name],\n await store.readAppConfig(manifest.name),\n );\n return { merged, status: evaluateAppConfig(manifest, merged) };\n}\n\n/**\n * Builds the AppContext for an installed app: merges the config layers\n * (manifest defaults < global apps[name] < per-app config), resolves\n * `${config.*}` placeholders (with `dataDir` available as a built-in key),\n * and scopes the AI provider to the app's manifest.\n *\n * Setup state shapes the session defaults: unconfigured MCP servers are\n * dropped (with their tools) so interpolation never hits a missing key, and\n * while required keys are missing the scoped provider fails closed.\n */\nexport async function buildAppContext(\n store: Store,\n manifest: AppManifest,\n provider: AIProvider,\n): Promise<AppContext> {\n const name = manifest.name;\n const dataDir = store.appDataDir(name);\n const repoDir = store.appRepoDir(name);\n await mkdir(dataDir, { recursive: true });\n\n const { merged, status } = await resolveAppConfig(store, manifest);\n // dataDir is a host-provided lookup key: apps reference it as ${config.dataDir}.\n const lookup: ConfigObject = { ...merged, dataDir };\n const interpolate = <T>(value: T): T =>\n interpolateConfig(value, lookup, { resolveRelativeTo: repoDir });\n\n const config = interpolate(merged);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const excluded = status.integrations.filter((i) => !i.configured).map((i) => i.name);\n const activeServers = Object.fromEntries(\n Object.entries(manifest.mcpServers)\n .filter(([server]) => !excluded.includes(server))\n .map(([server, { optional: _optional, ...decl }]) => [server, decl as McpServerConfig]),\n );\n const mcpServers = interpolate(activeServers);\n const allowedTools = manifest.permissions.allowedTools.filter(\n (tool) => !excluded.some((server) => tool.startsWith(`mcp__${server}__`)),\n );\n\n return {\n appName: name,\n dataDir,\n config,\n logger: createLogger(name),\n state: new JsonFileStore(join(dataDir, 'state.json')),\n ai: scopeProvider(provider, {\n mcpServers,\n allowedTools,\n agents: await loadAgents(repoDir, manifest.agents),\n skillDirs: manifest.skills.map((dir) => resolve(repoDir, dir)),\n ...(instructions !== undefined && { instructions }),\n cwd: dataDir,\n ...(!status.ready && {\n unreadyError: `App \"${name}\" is not configured: missing ${status.missing.join(', ')}. Set the missing keys in the dashboard settings or ${store.appConfigPath(name)}.`,\n }),\n }),\n };\n}\n","import type { AppContext, AppManifest } from '@alashi/apps-sdk';\nimport { Cron } from 'croner';\nimport type { JobRun, Store } from './store.js';\n\nexport interface SchedulableApp {\n manifest: AppManifest;\n ctx: AppContext;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nexport interface JobStatus {\n name: string;\n schedule: string;\n description?: string;\n /** False when the cron expression failed to parse (job never fires). */\n valid: boolean;\n running: boolean;\n nextRun?: string;\n lastRun?: string;\n recentRuns: JobRun[];\n}\n\nconst RUN_LOG_CAP = 50;\nconst RECENT_RUNS = 5;\n\nfunction nextFire(schedule: string, after?: Date): Date | null {\n // A Cron without a callback only computes times; it never schedules timers.\n return new Cron(schedule).nextRun(after ?? undefined);\n}\n\n/**\n * Runs manifest-declared jobs on their cron schedules. One instance per host\n * process; start() schedules every installed app's jobs, stop() cancels them.\n * A job missed while the host was down runs once at startup (\"catchup\"), but\n * only if it has run before — a fresh install waits for its first slot.\n */\nexport class Scheduler {\n private crons: Cron[] = [];\n private runningJobs = new Set<string>();\n private started = false;\n /** Serializes jobs.json read-modify-write across concurrently finishing jobs. */\n private stateQueue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly store: Store,\n private readonly getApp: (name: string) => Promise<SchedulableApp | undefined>,\n ) {}\n\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n for (const record of await this.store.listInstalled()) {\n let app: SchedulableApp | undefined;\n try {\n app = await this.getApp(record.name);\n } catch (err) {\n console.error(`[scheduler] skipping ${record.name}: failed to load (${String(err)})`);\n continue;\n }\n if (!app || app.manifest.jobs.length === 0) continue;\n const state = await this.store.readJobState(record.name);\n for (const job of app.manifest.jobs) {\n let cron: Cron;\n try {\n cron = new Cron(job.schedule, { paused: true }, () => {\n void this.run(record.name, job.name, 'schedule');\n });\n } catch (err) {\n app.ctx.logger.error(`Job \"${job.name}\" has an invalid schedule`, {\n schedule: job.schedule,\n error: String(err),\n });\n continue;\n }\n const last = state.lastRun[job.name];\n if (last) {\n const missed = nextFire(job.schedule, new Date(last));\n if (missed && missed.getTime() <= Date.now()) {\n void this.run(record.name, job.name, 'catchup');\n }\n }\n cron.resume();\n this.crons.push(cron);\n }\n }\n }\n\n stop(): void {\n for (const cron of this.crons) cron.stop();\n this.crons = [];\n this.started = false;\n }\n\n /**\n * Runs one job immediately. Returns the recorded run, 'skipped' when that\n * job is already mid-run, or undefined when the app/job doesn't exist.\n */\n async run(\n appName: string,\n jobName: string,\n trigger: JobRun['trigger'] = 'manual',\n ): Promise<JobRun | 'skipped' | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n const handler = app?.jobHandlers?.[jobName];\n if (!app || !handler || !app.manifest.jobs.some((j) => j.name === jobName)) return undefined;\n\n const key = `${appName}/${jobName}`;\n if (this.runningJobs.has(key)) return 'skipped';\n this.runningJobs.add(key);\n\n const startedAt = new Date().toISOString();\n let ok = true;\n let error: string | undefined;\n try {\n await handler(app.ctx);\n } catch (err) {\n ok = false;\n error = String(err);\n app.ctx.logger.error(`Job \"${jobName}\" failed`, { error });\n } finally {\n this.runningJobs.delete(key);\n }\n\n const run: JobRun = {\n job: jobName,\n trigger,\n startedAt,\n finishedAt: new Date().toISOString(),\n ok,\n ...(error !== undefined && { error }),\n };\n await this.recordRun(appName, run);\n return run;\n }\n\n /** Job statuses for an app, or undefined when the app isn't installed. */\n async status(appName: string): Promise<JobStatus[] | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n if (!app) return undefined;\n const state = await this.store.readJobState(appName);\n return app.manifest.jobs.map((job) => {\n let valid = true;\n let nextRun: string | undefined;\n try {\n nextRun = nextFire(job.schedule)?.toISOString();\n } catch {\n valid = false;\n }\n return {\n name: job.name,\n schedule: job.schedule,\n ...(job.description !== undefined && { description: job.description }),\n valid,\n running: this.runningJobs.has(`${appName}/${job.name}`),\n ...(nextRun !== undefined && { nextRun }),\n ...(state.lastRun[job.name] !== undefined && { lastRun: state.lastRun[job.name] }),\n recentRuns: state.runs\n .filter((r) => r.job === job.name)\n .slice(-RECENT_RUNS)\n .reverse(),\n };\n });\n }\n\n private recordRun(appName: string, run: JobRun): Promise<void> {\n const update = this.stateQueue.then(async () => {\n const state = await this.store.readJobState(appName);\n state.lastRun[run.job] = run.startedAt;\n state.runs.push(run);\n if (state.runs.length > RUN_LOG_CAP) state.runs.splice(0, state.runs.length - RUN_LOG_CAP);\n await this.store.writeJobState(appName, state);\n });\n this.stateQueue = update.catch(() => undefined);\n return update;\n }\n}\n","/**\n * Self-contained per-app settings page. Config data comes from\n * GET /api/apps/:name/config at runtime; saving PUTs only changed keys.\n * The read-only \"App definition\" section (instructions, agents, skills,\n * allowed tools, MCP servers, jobs) comes from GET /api/apps/:name/manifest.\n * The app name is injected via a JSON-encoded script constant, never into\n * markup, so no escaping subtleties apply.\n */\nexport function settingsPage(name: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Settings</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n<style>\n .card { margin-bottom: 16px; }\n #status { margin-left: 12px; color: var(--muted); }\n #manifest-cards { margin-top: 32px; }\n .ro-pre { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; }\n .ro-item { margin-bottom: 12px; }\n .ro-item:last-child { margin-bottom: 0; }\n details summary { cursor: pointer; }\n</style>\n</head>\n<body>\n<header class=\"topbar\"><a href=\"/\">alashi</a> <span>/</span> <strong id=\"title\"></strong> <span>settings</span></header>\n<main class=\"narrow\">\n <div id=\"banner\" class=\"banner\" hidden></div>\n <div id=\"cards\"></div>\n <button id=\"save\" class=\"primary\">Save changes</button><span id=\"status\"></span>\n <div id=\"manifest-cards\"></div>\n</main>\n<script>\nconst APP = ${JSON.stringify(name)};\ndocument.getElementById('title').textContent = APP;\n\nconst state = { data: null, dirty: new Map() };\n\nfunction el(tag, attrs = {}, children = []) {\n const node = document.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) {\n if (k === 'text') node.textContent = v;\n else if (k.startsWith('on')) node.addEventListener(k.slice(2), v);\n else node.setAttribute(k, v);\n }\n for (const child of children) node.append(child);\n return node;\n}\n\nfunction parseInput(raw) {\n if (raw === '') return null; // empty clears the user-set value\n try { const v = JSON.parse(raw); if (typeof v !== 'string') return v; } catch {}\n return raw;\n}\n\nfunction displayValue(value) {\n return typeof value === 'string' ? value : value === undefined ? '' : JSON.stringify(value);\n}\n\nconst ACRONYMS = new Set(['url', 'uri', 'api', 'id', 'ip', 'db', 'ssl', 'tls', 'ttl', 'http', 'https']);\n\nfunction labelFor(key, decl) {\n if (decl.label) return decl.label;\n const words = key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .toLowerCase()\n .split(' ');\n return words\n .map((w, i) => (ACRONYMS.has(w) ? w.toUpperCase() : i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w))\n .join(' ');\n}\n\nfunction field(key, decl, data) {\n const secret = decl.secret === true;\n const isBool = !secret &&\n (typeof decl.default === 'boolean' || typeof data.values[key] === 'boolean');\n if (isBool) {\n const input = el('input', {\n type: 'checkbox',\n onchange: () => state.dirty.set(key, { secret: false, input }),\n });\n input.checked = data.values[key] === true;\n const labelChildren = [input, document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', { class: 'check' }, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n return el('div', { class: 'field' }, parts);\n }\n const isSet = secret ? data.secretsSet.includes(key) : data.values[key] !== undefined;\n const input = el('input', {\n type: secret ? 'password' : 'text',\n value: secret ? '' : displayValue(data.values[key]),\n placeholder: secret && isSet ? '(set — type to replace)' : '',\n oninput: () => state.dirty.set(key, { secret, input }),\n });\n const labelChildren = [document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', {}, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n parts.push(input);\n return el('div', { class: 'field' }, parts);\n}\n\nfunction card(title, keys, data, configured) {\n const chip = configured === undefined\n ? ''\n : el('span', { class: 'chip ' + (configured ? 'on' : 'off'),\n text: configured ? 'configured' : 'not configured' });\n return el('div', { class: 'card' }, [\n el('h2', {}, chip === '' ? [document.createTextNode(title)] : [document.createTextNode(title), chip]),\n el('div', { class: 'body' }, keys.map((key) => field(key, data.schema[key] ?? {}, data))),\n ]);\n}\n\nfunction render(data) {\n state.data = data;\n state.dirty.clear();\n const banner = document.getElementById('banner');\n banner.hidden = false;\n if (data.ready) {\n banner.className = 'banner ok';\n banner.textContent = 'App is configured and ready.';\n } else {\n banner.className = 'banner warn';\n banner.textContent = 'Needs setup — missing: ' + data.missing.join(', ');\n }\n\n const cards = document.getElementById('cards');\n cards.replaceChildren();\n const integrationKeys = new Set(data.integrations.flatMap((i) => i.configKeys));\n\n // Non-integration keys, sectioned by their declared group (\"General\" first).\n const sections = new Map();\n for (const key of Object.keys(data.schema)) {\n if (integrationKeys.has(key)) continue;\n const title = data.schema[key].group ?? 'General';\n if (!sections.has(title)) sections.set(title, []);\n sections.get(title).push(key);\n }\n if (sections.has('General')) cards.append(card('General', sections.get('General'), data, undefined));\n for (const [title, keys] of sections) {\n if (title !== 'General') cards.append(card(title, keys, data, undefined));\n }\n\n // Integration cards, titled by the keys' shared group when declared.\n for (const integration of data.integrations) {\n if (integration.configKeys.length === 0) continue;\n const groups = new Set(integration.configKeys.map((k) => data.schema[k]?.group).filter(Boolean));\n const title = groups.size === 1 ? [...groups][0] : integration.name;\n cards.append(card(\n title + (integration.optional ? ' (optional integration)' : ''),\n integration.configKeys, data, integration.configured,\n ));\n }\n if (cards.childElementCount === 0) {\n cards.append(el('p', { text: 'This app declares no configurable settings.' }));\n }\n}\n\nfunction roCard(title, children) {\n return el('div', { class: 'card' }, [\n el('h2', { text: title }),\n el('div', { class: 'body' }, children),\n ]);\n}\n\nfunction renderManifest(m) {\n const root = document.getElementById('manifest-cards');\n root.replaceChildren();\n const cards = [];\n\n const about = [el('div', { class: 'desc', text: 'Version ' + m.version + (m.model ? ' · model: ' + m.model : '') })];\n if (m.description) about.unshift(el('div', { text: m.description }));\n cards.push(roCard('About', about));\n\n if (m.instructions) {\n cards.push(roCard('Instructions', [el('pre', { class: 'ro-pre', text: m.instructions })]));\n }\n\n const agentNames = Object.keys(m.agents);\n if (agentNames.length > 0) {\n cards.push(roCard('Agents', agentNames.map((name) => {\n const a = m.agents[name];\n const meta = [a.model && 'model: ' + a.model, a.tools && 'tools: ' + a.tools.join(', ')]\n .filter(Boolean).join(' · ');\n const body = meta ? [el('div', { class: 'desc', text: meta })] : [];\n body.push(el('pre', { class: 'ro-pre', text: a.prompt }));\n return el('details', { class: 'ro-item' }, [\n el('summary', {}, [el('strong', { text: name }), document.createTextNode(' — ' + a.description)]),\n ...body,\n ]);\n })));\n }\n\n if (m.skills.length > 0) {\n cards.push(roCard('Skills', m.skills.map((s) => el('div', { class: 'ro-item' }, [\n el('code', { text: s.dir + '/' }),\n el('div', { class: 'desc', text: s.entries.length ? s.entries.join(', ') : '(directory not found or empty)' }),\n ]))));\n }\n\n if (m.mcpServers.length > 0) {\n cards.push(roCard('MCP servers', m.mcpServers.map((s) => el('div', { class: 'ro-item' }, [\n el('strong', { text: s.name }),\n document.createTextNode(' (' + s.type + (s.optional ? ', optional' : '') + ') '),\n el('span', { class: 'chip ' + (s.configured ? 'on' : 'off'),\n text: s.configured ? 'configured' : 'not configured' }),\n el('div', { class: 'desc', text: s.type === 'stdio' ? s.command : s.url }),\n ]))));\n }\n\n if (m.allowedTools.length > 0) {\n cards.push(roCard('Allowed tools', m.allowedTools.map((t) =>\n el('div', {}, [el('code', { text: t })]))));\n }\n\n if (m.jobs.length > 0) {\n cards.push(roCard('Jobs', m.jobs.map((j) => el('div', { class: 'ro-item' }, [\n el('strong', { text: j.name }),\n el('code', { text: ' ' + j.schedule }),\n ...(j.description ? [el('div', { class: 'desc', text: j.description })] : []),\n ]))));\n }\n\n root.append(\n el('h1', { text: 'App definition' }),\n el('p', { class: 'subtitle', text: 'Declared by the app; read-only.' }),\n ...cards,\n );\n}\n\nasync function load() {\n const [res, manRes] = await Promise.all([\n fetch('/api/apps/' + APP + '/config'),\n fetch('/api/apps/' + APP + '/manifest'),\n ]);\n if (!res.ok) {\n document.getElementById('banner').hidden = false;\n document.getElementById('banner').className = 'banner warn';\n document.getElementById('banner').textContent = 'Failed to load config (' + res.status + ')';\n return;\n }\n render(await res.json());\n if (manRes.ok) renderManifest(await manRes.json());\n}\n\ndocument.getElementById('save').addEventListener('click', async () => {\n const body = {};\n for (const [key, { secret, input }] of state.dirty) {\n if (secret && input.value === '') continue; // untouched secret: keep as-is\n body[key] = input.type === 'checkbox' ? input.checked : parseInput(input.value);\n }\n if (Object.keys(body).length === 0) return;\n const status = document.getElementById('status');\n status.textContent = 'Saving…';\n const res = await fetch('/api/apps/' + APP + '/config', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n });\n status.textContent = res.ok ? 'Saved — app restarted.' : 'Save failed (' + res.status + ')';\n if (res.ok) await load();\n});\n\nload();\n</script>\n</body>\n</html>\n`;\n}\n","import { randomUUID } from 'node:crypto';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { extname, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n type AIProvider,\n type AISession,\n APP_NAME_PATTERN,\n type AppContext,\n type AppManifest,\n type FetchHandler,\n} from '@alashi/apps-sdk';\nimport { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport { loadApp } from './loader.js';\nimport { buildAppContext, loadAgents, loadInstructions, resolveAppConfig } from './runtime.js';\nimport { Scheduler } from './scheduler.js';\nimport { settingsPage } from './settings-page.js';\nimport type { ChatState, Conversation, Store } from './store.js';\n\ninterface RunningApp {\n manifest: AppManifest;\n ctx: AppContext;\n handler?: FetchHandler;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.ico': 'image/x-icon',\n '.txt': 'text/plain; charset=utf-8',\n '.woff2': 'font/woff2',\n};\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n}\n\nexport interface AppServer {\n /** The Hono app — pass its fetch to a Node server or call request() in tests. */\n app: Hono;\n /** Cron scheduler for manifest-declared jobs; call start() to begin firing. */\n scheduler: Scheduler;\n /** Stops the scheduler and closes all warm AI sessions. */\n close(): Promise<void>;\n}\n\n/**\n * The host HTTP surface:\n * GET / dashboard of installed apps\n * GET /settings/:name per-app settings page (config form)\n * GET /api/apps installed apps as JSON\n * POST /api/apps/:name/chat { message, conversationId? } -> SSE stream of AgentEvents\n * (omit conversationId to start a new conversation; the\n * id is echoed in the x-conversation-id response header)\n * GET /api/apps/:name/chat/conversations conversations, most recent first\n * POST /api/apps/:name/chat/conversations { sessionId?, title? } -> create a conversation,\n * optionally adopting an existing provider session (e.g. an\n * app-run investigation) so chat continues where it left off\n * GET /api/apps/:name/chat/:conversationId/history past turns from the provider transcript\n * GET /api/apps/:name/jobs manifest-declared jobs with schedule/status\n * POST /api/apps/:name/jobs/:job/run trigger a job now (409 while already running)\n * GET /api/apps/:name/manifest read-only app definition: instructions, agents,\n * skills (with dir contents), allowed tools,\n * MCP servers (with configured state), jobs, model\n * GET /api/apps/:name/config declared config schema + merged values\n * (secrets masked) + readiness/integrations\n * PUT /api/apps/:name/config merge keys into the per-app config layer\n * (null deletes a key) and restart the app\n * ALL /app/:name/api/* dispatched to the app's fetch handler (prefix stripped)\n * ALL /app/:name/* a matching static file from the app's ui dir wins,\n * otherwise the app's fetch handler (/app/:name stripped),\n * otherwise 404\n */\nexport function createServer(store: Store, provider: AIProvider): AppServer {\n const apps = new Map<string, Promise<RunningApp>>();\n const sessions = new Map<string, Promise<AISession>>();\n\n async function startApp(name: string): Promise<RunningApp> {\n const { definition, manifest } = await loadApp(store.appRepoDir(name));\n const ctx = await buildAppContext(store, manifest, provider);\n const handler = definition.routes ? await definition.routes(ctx) : undefined;\n return { manifest, ctx, handler, jobHandlers: definition.jobHandlers };\n }\n\n /** Loads an installed app once per process; undefined when not installed. */\n async function getApp(name: string): Promise<RunningApp | undefined> {\n if (!APP_NAME_PATTERN.test(name)) return undefined;\n if (!(await store.readAppRecord(name))) return undefined;\n let running = apps.get(name);\n if (!running) {\n running = startApp(name);\n running.catch(() => apps.delete(name));\n apps.set(name, running);\n }\n return running;\n }\n\n /** chat.json per app, loaded once and mutated in place so turns share one view. */\n const chatStates = new Map<string, Promise<ChatState>>();\n\n function getChatState(name: string): Promise<ChatState> {\n let state = chatStates.get(name);\n if (!state) {\n state = store.readChatState(name);\n state.catch(() => chatStates.delete(name));\n chatStates.set(name, state);\n }\n return state;\n }\n\n function getSession(name: string, running: RunningApp, convo: Conversation): Promise<AISession> {\n const key = `${name}/${convo.id}`;\n let session = sessions.get(key);\n if (!session) {\n const model = running.ctx.config.model;\n session = running.ctx.ai.createSession({\n ...(typeof model === 'string' && { model }),\n ...(convo.sessionId && { resume: convo.sessionId }),\n });\n session.catch(() => sessions.delete(key));\n sessions.set(key, session);\n }\n return session;\n }\n\n const scheduler = new Scheduler(store, getApp);\n\n /** Rebuilds the request with `prefix` stripped and hands it to the app's handler. */\n function dispatch(\n handler: FetchHandler,\n raw: Request,\n prefix: string,\n ): Response | Promise<Response> {\n const url = new URL(raw.url);\n url.pathname = url.pathname.slice(prefix.length) || '/';\n const request = new Request(url, {\n method: raw.method,\n headers: raw.headers,\n body: raw.body,\n ...(raw.body ? { duplex: 'half' } : {}),\n } as RequestInit);\n return handler(request);\n }\n\n /** A file from the app's ui dir, or undefined when there is no match. */\n async function serveStatic(\n name: string,\n manifest: AppManifest,\n reqPath: string,\n ): Promise<Response | undefined> {\n if (!manifest.ui) return undefined;\n const uiDir = resolve(store.appRepoDir(name), manifest.ui);\n let rest: string;\n try {\n rest = decodeURIComponent(reqPath.slice(`/app/${name}/`.length));\n } catch {\n return undefined;\n }\n if (rest === '' || rest.endsWith('/')) rest += 'index.html';\n const filePath = resolve(uiDir, rest);\n if (filePath !== uiDir && !filePath.startsWith(uiDir + sep)) return undefined;\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type },\n });\n } catch {\n return undefined;\n }\n }\n\n const app = new Hono();\n\n app.onError((err, c) => c.json({ error: String(err) }, 500));\n\n // System UI assets (design system, later vendored libs). Versioned paths:\n // breaking changes ship as /assets/v2/, existing apps keep working.\n const assetsDir = fileURLToPath(new URL('../assets', import.meta.url));\n\n app.get('/assets/*', async (c) => {\n let rest: string;\n try {\n rest = decodeURIComponent(c.req.path.slice('/assets/'.length));\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n const filePath = resolve(assetsDir, rest);\n if (!filePath.startsWith(assetsDir + sep)) return c.json({ error: 'Not found' }, 404);\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type, 'cache-control': 'public, max-age=300' },\n });\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n });\n\n /** Readiness for the dashboard badge; undefined when the app fails to load. */\n async function readiness(name: string): Promise<{ ready: boolean } | undefined> {\n try {\n const running = await getApp(name);\n if (!running) return undefined;\n return (await resolveAppConfig(store, running.manifest)).status;\n } catch {\n return undefined;\n }\n }\n\n app.get('/', async (c) => {\n const records = await store.listInstalled();\n const rows = (\n await Promise.all(\n records.map(async (r) => {\n const name = escapeHtml(r.name);\n const status = await readiness(r.name);\n const badge = !status\n ? '<span class=\"badge err\">failed to load</span>'\n : status.ready\n ? '<span class=\"badge ok\">ready</span>'\n : `<a href=\"/settings/${name}\"><span class=\"badge err\">needs setup</span></a>`;\n return `<tr>\n <td><a href=\"/app/${name}/\">${name}</a></td>\n <td class=\"muted\">v${escapeHtml(r.version)}</td>\n <td class=\"muted\">${escapeHtml(r.source.type)}</td>\n <td>${badge}</td>\n <td style=\"text-align:right\"><a href=\"/settings/${name}\">settings</a></td>\n</tr>`;\n }),\n )\n ).join('\\n');\n const body =\n records.length === 0\n ? '<div class=\"empty\">No apps installed. Run <code>alashi install &lt;name&gt;</code>.</div>'\n : `<table>\n<thead><tr><th>App</th><th>Version</th><th>Source</th><th>Status</th><th></th></tr></thead>\n<tbody>\n${rows}\n</tbody>\n</table>`;\n return c.html(\n `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>alashi</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n</head>\n<body>\n<header class=\"topbar\"><strong>alashi</strong></header>\n<main class=\"narrow\">\n<h1>Apps</h1>\n<p class=\"subtitle\">Installed apps and their status.</p>\n<div class=\"panel\">\n${body}\n</div>\n</main>\n</body>\n</html>\n`,\n );\n });\n\n app.get('/api/apps', async (c) => c.json({ apps: await store.listInstalled() }));\n\n app.get('/settings/:name', async (c) => {\n const name = c.req.param('name');\n if (!APP_NAME_PATTERN.test(name) || !(await store.readAppRecord(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n return c.html(settingsPage(name));\n });\n\n app.get('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { conversations } = await getChatState(name);\n return c.json({\n conversations: [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),\n });\n });\n\n app.post('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let sessionId: unknown;\n let title: unknown;\n try {\n ({ sessionId, title } = (await c.req.json()) as { sessionId?: unknown; title?: unknown });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (sessionId !== undefined && (typeof sessionId !== 'string' || sessionId.length === 0)) {\n return c.json({ error: '\"sessionId\" must be a non-empty string' }, 400);\n }\n if (title !== undefined && typeof title !== 'string') {\n return c.json({ error: '\"title\" must be a string' }, 400);\n }\n\n const state = await getChatState(name);\n const convo: Conversation = {\n id: randomUUID(),\n title: (title ?? '').slice(0, 80) || 'New conversation',\n updatedAt: new Date().toISOString(),\n ...(sessionId !== undefined && { sessionId }),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n return c.json({ conversation: convo }, 201);\n });\n\n app.get('/api/apps/:name/chat/:conversationId/history', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const conversationId = c.req.param('conversationId');\n const { conversations } = await getChatState(name);\n const convo = conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n const messages = convo.sessionId ? ((await provider.history?.(convo.sessionId)) ?? []) : [];\n return c.json({ messages });\n });\n\n /** Drops the cached app and its warm sessions so the next request rebuilds with fresh config. */\n function restartApp(name: string): void {\n apps.delete(name);\n for (const [key, session] of sessions) {\n if (!key.startsWith(`${name}/`)) continue;\n sessions.delete(key);\n void session.then((s) => s.close()).catch(() => undefined);\n }\n }\n\n app.get('/api/apps/:name/manifest', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n const repoDir = store.appRepoDir(name);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const skills = await Promise.all(\n manifest.skills.map(async (dir) => {\n let entries: string[] = [];\n try {\n entries = (await readdir(resolve(repoDir, dir))).sort();\n } catch {\n // A missing skill dir is still worth surfacing, just with no entries.\n }\n return { dir, entries };\n }),\n );\n\n const configured = new Map(status.integrations.map((i) => [i.name, i.configured]));\n // Raw manifest declarations only: `${config.*}` placeholders are never\n // interpolated here, so secret values cannot leak through env/headers.\n const mcpServers = Object.entries(manifest.mcpServers).map(([server, decl]) => ({\n name: server,\n ...('command' in decl\n ? { type: 'stdio', command: [decl.command, ...(decl.args ?? [])].join(' ') }\n : { type: decl.type, url: decl.url }),\n optional: decl.optional === true,\n configured: configured.get(server) ?? true,\n }));\n\n return c.json({\n name: manifest.name,\n version: manifest.version,\n ...(manifest.description !== undefined && { description: manifest.description }),\n ...(instructions !== undefined && { instructions }),\n ...(typeof merged.model === 'string' && { model: merged.model }),\n agents: await loadAgents(repoDir, manifest.agents),\n skills,\n allowedTools: manifest.permissions.allowedTools,\n mcpServers,\n jobs: manifest.jobs,\n });\n });\n\n app.get('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n // Secret values never leave the host; the UI only learns whether they are set.\n const values: Record<string, unknown> = { ...merged };\n const secretsSet: string[] = [];\n for (const [key, decl] of Object.entries(manifest.config)) {\n if (decl.secret !== true) continue;\n if (values[key] !== undefined) secretsSet.push(key);\n delete values[key];\n }\n return c.json({\n schema: manifest.config,\n values,\n secretsSet,\n userKeys: Object.keys(await store.readAppConfig(name)),\n ...status,\n });\n });\n\n app.put('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Body must be an object of config keys' }, 400);\n }\n\n const config = await store.readAppConfig(name);\n for (const [key, value] of Object.entries(body)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n if (value === null) delete config[key];\n else config[key] = value;\n }\n await store.writeAppConfig(name, config);\n restartApp(name);\n\n const { status } = await resolveAppConfig(store, running.manifest);\n return c.json({ ok: true, ...status });\n });\n\n app.get('/api/apps/:name/jobs', async (c) => {\n const name = c.req.param('name');\n const jobs = await scheduler.status(name);\n if (!jobs) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n return c.json({ jobs });\n });\n\n app.post('/api/apps/:name/jobs/:job/run', async (c) => {\n const result = await scheduler.run(c.req.param('name'), c.req.param('job'));\n if (result === undefined) return c.json({ error: 'Unknown app or job' }, 404);\n if (result === 'skipped') return c.json({ error: 'Job is already running' }, 409);\n return c.json({ run: result }, result.ok ? 200 : 500);\n });\n\n app.post('/api/apps/:name/chat', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let message: unknown;\n let conversationId: unknown;\n try {\n ({ message, conversationId } = (await c.req.json()) as {\n message?: unknown;\n conversationId?: unknown;\n });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof message !== 'string' || message.length === 0) {\n return c.json({ error: 'Body must be { \"message\": string }' }, 400);\n }\n if (conversationId !== undefined && typeof conversationId !== 'string') {\n return c.json({ error: '\"conversationId\" must be a string' }, 400);\n }\n\n const state = await getChatState(name);\n let convo: Conversation | undefined;\n if (conversationId !== undefined) {\n convo = state.conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n } else {\n convo = {\n id: randomUUID(),\n title: message.slice(0, 80),\n updatedAt: new Date().toISOString(),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n }\n const conversation = convo;\n\n let session: AISession;\n try {\n session = await getSession(name, running, conversation);\n } catch (err) {\n return c.json({ error: `Failed to start AI session: ${String(err)}` }, 502);\n }\n\n c.header('x-conversation-id', conversation.id);\n return streamSSE(c, async (stream) => {\n try {\n for await (const event of session.send(message)) {\n // One small write per turn: tracks the provider session and bumps recency.\n if (event.type === 'session') {\n conversation.sessionId = event.sessionId;\n conversation.updatedAt = new Date().toISOString();\n await store.writeChatState(name, state);\n }\n await stream.writeSSE({ event: event.type, data: JSON.stringify(event) });\n }\n } catch (err) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ type: 'result', ok: false, error: String(err) }),\n });\n }\n });\n });\n\n app.all('/app/:name/api/*', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n if (!running.handler) return c.json({ error: `App \"${name}\" has no routes` }, 404);\n return dispatch(running.handler, c.req.raw, `/app/${name}/api`);\n });\n\n // Relative asset URLs in app UIs only work with a trailing slash.\n app.get('/app/:name', (c) => c.redirect(`/app/${c.req.param('name')}/`));\n\n app.all('/app/:name/*', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n if (c.req.method === 'GET' || c.req.method === 'HEAD') {\n const file = await serveStatic(name, running.manifest, c.req.path);\n if (file) return file;\n }\n if (running.handler) return dispatch(running.handler, c.req.raw, `/app/${name}`);\n return c.json({ error: 'Not found' }, 404);\n });\n\n return {\n app,\n scheduler,\n async close() {\n scheduler.stop();\n const open = [...sessions.values()];\n sessions.clear();\n await Promise.all(\n open.map(async (p) => {\n try {\n await (await p).close();\n } catch {\n // Session may already be gone; closing is best-effort.\n }\n }),\n );\n },\n };\n}\n","import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { APP_NAME_PATTERN, AppSdkError, type ConfigObject } from '@alashi/apps-sdk';\nimport type { AppSourceSpec } from './sources.js';\n\nexport interface RegistryRef {\n name: string;\n url: string;\n}\n\nexport interface GlobalConfig {\n registries: RegistryRef[];\n port?: number;\n /** Bind address for `alashi serve`; defaults to loopback (the dashboard has no auth). */\n host?: string;\n /** Per-app global overrides, keyed by app name (middle config layer). */\n apps?: Record<string, ConfigObject>;\n}\n\nexport interface Conversation {\n id: string;\n /** Provider session backing this conversation; set after the first turn. */\n sessionId?: string;\n title?: string;\n updatedAt: string;\n}\n\n/** Host-side chat state for an app; the transcripts themselves live with the AI provider. */\nexport interface ChatState {\n conversations: Conversation[];\n}\n\nexport interface JobRun {\n job: string;\n trigger: 'schedule' | 'catchup' | 'manual';\n startedAt: string;\n finishedAt: string;\n ok: boolean;\n error?: string;\n}\n\n/** Host-side job state for an app: last run start per job plus a capped run log. */\nexport interface JobState {\n lastRun: Record<string, string>;\n runs: JobRun[];\n}\n\nexport interface InstallRecord {\n name: string;\n version: string;\n /** The SDK range the app's manifest declared at install/update time. */\n sdkVersion?: string;\n source: AppSourceSpec;\n installedAt: string;\n updatedAt: string;\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nasync function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(join(path, '..'), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tmp, path);\n}\n\n/**\n * The portable state directory ($ALASHI_HOME, default ~/.alashi).\n * Everything the host knows lives under this root so it can be moved\n * between machines as a unit.\n */\nexport class Store {\n readonly root: string;\n\n constructor(root?: string) {\n this.root = root ?? process.env.ALASHI_HOME ?? join(homedir(), '.alashi');\n }\n\n get configPath(): string {\n return join(this.root, 'config.json');\n }\n\n get appsDir(): string {\n return join(this.root, 'apps');\n }\n\n get registriesDir(): string {\n return join(this.root, 'registries');\n }\n\n get stagingDir(): string {\n return join(this.root, '.staging');\n }\n\n /** Validates the name before using it in a path (traversal guard). */\n appDir(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid app name: ${JSON.stringify(name)}`);\n }\n return join(this.appsDir, name);\n }\n\n appRepoDir(name: string): string {\n return join(this.appDir(name), 'repo');\n }\n\n appDataDir(name: string): string {\n return join(this.appDir(name), 'data');\n }\n\n appConfigPath(name: string): string {\n return join(this.appDir(name), 'config.json');\n }\n\n appRecordPath(name: string): string {\n return join(this.appDir(name), 'app.json');\n }\n\n appChatStatePath(name: string): string {\n return join(this.appDir(name), 'chat.json');\n }\n\n appJobStatePath(name: string): string {\n return join(this.appDir(name), 'jobs.json');\n }\n\n registryCachePath(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid registry name: ${JSON.stringify(name)}`);\n }\n return join(this.registriesDir, `${name}.json`);\n }\n\n async readGlobalConfig(): Promise<GlobalConfig> {\n return (await readJson<GlobalConfig>(this.configPath)) ?? { registries: [] };\n }\n\n async writeGlobalConfig(config: GlobalConfig): Promise<void> {\n await writeJson(this.configPath, config);\n }\n\n async readAppRecord(name: string): Promise<InstallRecord | undefined> {\n return readJson<InstallRecord>(this.appRecordPath(name));\n }\n\n async writeAppRecord(record: InstallRecord): Promise<void> {\n await writeJson(this.appRecordPath(record.name), record);\n }\n\n async readAppConfig(name: string): Promise<ConfigObject> {\n return (await readJson<ConfigObject>(this.appConfigPath(name))) ?? {};\n }\n\n async writeAppConfig(name: string, config: ConfigObject): Promise<void> {\n await writeJson(this.appConfigPath(name), config);\n }\n\n async readChatState(name: string): Promise<ChatState> {\n const state = await readJson<Partial<ChatState>>(this.appChatStatePath(name));\n return { conversations: state?.conversations ?? [] };\n }\n\n async writeChatState(name: string, state: ChatState): Promise<void> {\n await writeJson(this.appChatStatePath(name), state);\n }\n\n async readJobState(name: string): Promise<JobState> {\n const state = await readJson<Partial<JobState>>(this.appJobStatePath(name));\n return { lastRun: state?.lastRun ?? {}, runs: state?.runs ?? [] };\n }\n\n async writeJobState(name: string, state: JobState): Promise<void> {\n await writeJson(this.appJobStatePath(name), state);\n }\n\n async listInstalled(): Promise<InstallRecord[]> {\n let entries: string[];\n try {\n entries = await readdir(this.appsDir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n const records: InstallRecord[] = [];\n for (const entry of entries.sort()) {\n if (!APP_NAME_PATTERN.test(entry)) continue;\n const record = await this.readAppRecord(entry);\n if (record) records.push(record);\n }\n return records;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alashi/cli",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "alashi host: install, manage, and run Claude Code-powered apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,14 +29,14 @@
29
29
  "templates"
30
30
  ],
31
31
  "dependencies": {
32
- "@alashi/apps-sdk": "^0.3.0",
32
+ "@alashi/apps-sdk": "^0.4.0",
33
33
  "@hono/node-server": "^2.1.1",
34
34
  "commander": "^14.0.0",
35
35
  "croner": "^10.0.1",
36
36
  "hono": "^4.13.2",
37
37
  "semver": "^7.8.5",
38
38
  "zod": "^4.4.3",
39
- "@alashi/provider-claude": "^0.1.0"
39
+ "@alashi/provider-claude": "^0.1.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^26.2.0",
@@ -6,7 +6,7 @@ export default defineApp({
6
6
  name: '__APP_NAME__',
7
7
  version: '0.1.0',
8
8
  description: 'An alashi app',
9
- sdkVersion: '^0.3.0',
9
+ sdkVersion: '^0.4.0',
10
10
  ui: 'ui',
11
11
  configDefaults: {
12
12
  greeting: 'What should we work on?',
@@ -4,6 +4,6 @@
4
4
  "type": "module",
5
5
  "main": "index.mjs",
6
6
  "dependencies": {
7
- "@alashi/apps-sdk": "^0.3.0"
7
+ "@alashi/apps-sdk": "^0.4.0"
8
8
  }
9
9
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"store-Bk-PC18o.mjs","names":["execFileAsync"],"sources":["../src/compat.ts","../src/loader.ts","../src/sources.ts","../src/registry.ts","../src/installer.ts","../src/runtime.ts","../src/scheduler.ts","../src/settings-page.ts","../src/server.ts","../src/store.ts"],"sourcesContent":["import { type AppManifest, AppSdkError, SDK_VERSION } from '@alashi/apps-sdk';\nimport semver from 'semver';\n\n/**\n * Enforces the manifest's declared `sdkVersion` range against the SDK\n * this host is built with. Apps that declare nothing are accepted.\n */\nexport function checkSdkCompatibility(manifest: AppManifest): void {\n const range = manifest.sdkVersion;\n if (!range) return;\n if (!semver.validRange(range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" declares an invalid sdkVersion range: ${JSON.stringify(range)}`,\n );\n }\n if (!semver.satisfies(SDK_VERSION, range)) {\n throw new AppSdkError(\n `App \"${manifest.name}\" supports SDK ${range}, but this host uses SDK ${SDK_VERSION}. ` +\n 'Update the host (or the app) to a compatible version.',\n );\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { type AppDefinition, type AppManifest, AppSdkError, parseManifest } from '@alashi/apps-sdk';\n\ninterface PackageJson {\n main?: string;\n exports?: unknown;\n}\n\nfunction entryFromExports(exportsField: unknown): string | undefined {\n if (typeof exportsField === 'string') return exportsField;\n if (typeof exportsField !== 'object' || exportsField === null) return undefined;\n const record = exportsField as Record<string, unknown>;\n const dot = record['.'] ?? record.default ?? record.import;\n if (typeof dot === 'string') return dot;\n if (typeof dot === 'object' && dot !== null) {\n const conditions = dot as Record<string, unknown>;\n const target = conditions.default ?? conditions.import;\n if (typeof target === 'string') return target;\n }\n return undefined;\n}\n\nexport interface LoadedApp {\n definition: AppDefinition;\n manifest: AppManifest;\n entryPath: string;\n}\n\n/**\n * Imports an app's entry module and validates its default export.\n * The manifest is always re-validated here — the host never trusts\n * that an app used defineApp().\n */\nexport async function loadApp(repoDir: string): Promise<LoadedApp> {\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(join(repoDir, 'package.json'), 'utf8')) as PackageJson;\n } catch (err) {\n throw new AppSdkError(`App has no readable package.json in ${repoDir}`, { cause: err });\n }\n\n const entry = entryFromExports(pkg.exports) ?? pkg.main ?? 'index.js';\n const entryPath = join(repoDir, entry);\n\n let mod: Record<string, unknown>;\n try {\n // Cache-bust so `alashi update` reloads changed code within one process.\n mod = (await import(`${pathToFileURL(entryPath).href}?t=${Date.now()}`)) as Record<\n string,\n unknown\n >;\n } catch (err) {\n let detail = String(err);\n // defineApp() in the app's own bundled SDK validates at import time; an\n // \"unrecognized key\" there means the manifest uses fields newer than that\n // bundled SDK understands (cross-package, so match by name not instanceof).\n if ((err as Error)?.name === 'ManifestValidationError' && /Unrecognized key/i.test(detail)) {\n detail +=\n \"\\nThe manifest may use fields newer than the app's bundled @alashi/apps-sdk — update the app's dependency to match its sdkVersion.\";\n }\n throw new AppSdkError(`Failed to import app entry ${entry}: ${detail}`, { cause: err });\n }\n\n const definition = mod.default as AppDefinition | undefined;\n if (typeof definition !== 'object' || definition === null || !('manifest' in definition)) {\n throw new AppSdkError(\n 'App entry must default-export an app definition (use defineApp() from @alashi/apps-sdk)',\n );\n }\n\n const manifest = parseManifest(definition.manifest);\n return { definition, manifest, entryPath };\n}\n","import { execFile } from 'node:child_process';\nimport { mkdir, symlink } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\n\nconst execFileAsync = promisify(execFile);\n\nexport type AppSourceSpec =\n | { type: 'git'; url: string; ref?: string }\n | { type: 'local'; path: string };\n\n/** How the CLI argument was understood: a source, or a name to resolve via registries. */\nexport type SourceArg = AppSourceSpec | { type: 'registry'; name: string };\n\nexport function parseSourceArg(arg: string): SourceArg {\n if (arg.startsWith('.') || isAbsolute(arg)) {\n return { type: 'local', path: resolve(arg) };\n }\n if (/^(https?:\\/\\/|git@|ssh:\\/\\/)/.test(arg)) {\n return { type: 'git', url: arg };\n }\n if (/^github\\.com\\//.test(arg)) {\n return { type: 'git', url: `https://${arg}` };\n }\n return { type: 'registry', name: arg };\n}\n\nexport async function git(args: string[], cwd?: string): Promise<string> {\n try {\n const { stdout } = await execFileAsync('git', args, cwd ? { cwd } : {});\n return stdout.trim();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new AppSdkError(`git ${args[0]} failed: ${message}`, { cause: err });\n }\n}\n\n/** Materializes a source at destDir: git clone, or symlink for local dev installs. */\nexport async function fetchSource(spec: AppSourceSpec, destDir: string): Promise<void> {\n await mkdir(resolve(destDir, '..'), { recursive: true });\n if (spec.type === 'git') {\n const args = ['clone', '--depth', '1'];\n if (spec.ref) args.push('--branch', spec.ref);\n args.push(spec.url, destDir);\n await git(args);\n return;\n }\n await symlink(spec.path, destDir, 'dir');\n}\n\n/** Brings an installed source up to date in place. Local symlinks are always live. */\nexport async function updateSource(spec: AppSourceSpec, repoDir: string): Promise<void> {\n if (spec.type === 'git') {\n await git(['pull', '--ff-only'], repoDir);\n }\n}\n","import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { z } from 'zod';\nimport { fetchSource, git } from './sources.js';\nimport type { RegistryRef, Store } from './store.js';\n\nexport interface RegistryApp {\n name: string;\n repo: string;\n description?: string;\n ref?: string;\n sdkVersion?: string;\n}\n\nexport interface RegistryIndex {\n apps: RegistryApp[];\n}\n\nconst registryIndexSchema = z.object({\n apps: z.array(\n z.object({\n name: z.string().min(1),\n repo: z.string().min(1),\n description: z.string().optional(),\n ref: z.string().optional(),\n sdkVersion: z.string().optional(),\n }),\n ),\n});\n\nfunction parseIndex(raw: string, source: string): RegistryIndex {\n let data: unknown;\n try {\n data = JSON.parse(raw);\n } catch (err) {\n throw new AppSdkError(`Registry ${source} did not return valid JSON`, { cause: err });\n }\n const result = registryIndexSchema.safeParse(data);\n if (!result.success) {\n throw new AppSdkError(`Registry ${source} has an invalid index: ${result.error.message}`);\n }\n return result.data;\n}\n\n/**\n * Fetches a registry index and caches it under registries/<name>.json.\n * URLs ending in .json are fetched over HTTP; anything else is treated\n * as a git repo containing index.json at its root (brew-tap style).\n */\nexport async function refreshRegistry(store: Store, registry: RegistryRef): Promise<RegistryIndex> {\n let raw: string;\n if (/^https?:\\/\\/.*\\.json$/.test(registry.url)) {\n const response = await fetch(registry.url);\n if (!response.ok) {\n throw new AppSdkError(`Registry fetch failed (${response.status}) for ${registry.url}`);\n }\n raw = await response.text();\n } else {\n const cloneDir = join(store.stagingDir, `registry-${registry.name}-${Date.now()}`);\n await fetchSource({ type: 'git', url: registry.url }, cloneDir);\n try {\n raw = await readFile(join(cloneDir, 'index.json'), 'utf8');\n } finally {\n await rm(cloneDir, { recursive: true, force: true });\n }\n }\n const index = parseIndex(raw, registry.url);\n await mkdir(store.registriesDir, { recursive: true });\n await writeFile(store.registryCachePath(registry.name), JSON.stringify(index, null, 2), 'utf8');\n return index;\n}\n\nexport async function readCachedIndex(\n store: Store,\n name: string,\n): Promise<RegistryIndex | undefined> {\n try {\n return parseIndex(await readFile(store.registryCachePath(name), 'utf8'), name);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nexport async function addRegistry(store: Store, name: string, url: string): Promise<RegistryIndex> {\n const config = await store.readGlobalConfig();\n if (config.registries.some((r) => r.name === name)) {\n throw new AppSdkError(`Registry \"${name}\" already exists`);\n }\n const index = await refreshRegistry(store, { name, url });\n config.registries.push({ name, url });\n await store.writeGlobalConfig(config);\n return index;\n}\n\nexport async function removeRegistry(store: Store, name: string): Promise<void> {\n const config = await store.readGlobalConfig();\n const next = config.registries.filter((r) => r.name !== name);\n if (next.length === config.registries.length) {\n throw new AppSdkError(`Registry \"${name}\" is not configured`);\n }\n config.registries = next;\n await store.writeGlobalConfig(config);\n await rm(store.registryCachePath(name), { force: true });\n}\n\nexport interface ResolvedApp {\n registry: string;\n app: RegistryApp;\n}\n\n/** Finds an app by name across configured registries (refreshing caches). */\nexport async function resolveApp(store: Store, appName: string): Promise<ResolvedApp> {\n const config = await store.readGlobalConfig();\n if (config.registries.length === 0) {\n throw new AppSdkError(\n `No registries configured. Add one with: alashi registry add <name> <url>`,\n );\n }\n for (const registry of config.registries) {\n let index: RegistryIndex;\n try {\n index = await refreshRegistry(store, registry);\n } catch {\n const cached = await readCachedIndex(store, registry.name);\n if (!cached) continue;\n index = cached;\n }\n const app = index.apps.find((a) => a.name === appName);\n if (app) return { registry: registry.name, app };\n }\n throw new AppSdkError(`App \"${appName}\" not found in any configured registry`);\n}\n\nexport { git };\n","import { execFile } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { AppSdkError } from '@alashi/apps-sdk';\nimport { checkSdkCompatibility } from './compat.js';\nimport { loadApp } from './loader.js';\nimport { resolveApp } from './registry.js';\nimport { type AppSourceSpec, fetchSource, parseSourceArg, updateSource } from './sources.js';\nimport type { InstallRecord, Store } from './store.js';\n\nconst execFileAsync = promisify(execFile);\n\nasync function hasDependencies(dir: string): Promise<boolean> {\n try {\n const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8')) as {\n dependencies?: Record<string, string>;\n };\n return Object.keys(pkg.dependencies ?? {}).length > 0;\n } catch {\n return false;\n }\n}\n\n/** Lifecycle scripts stay off: installing an app must not execute arbitrary code. */\nasync function installDependencies(dir: string): Promise<void> {\n if (!(await hasDependencies(dir))) return;\n try {\n await execFileAsync(\n 'npm',\n ['install', '--ignore-scripts', '--omit=dev', '--no-fund', '--no-audit'],\n { cwd: dir },\n );\n } catch (err) {\n throw new AppSdkError(`npm install failed: ${err instanceof Error ? err.message : err}`, {\n cause: err,\n });\n }\n}\n\nexport interface InstallOptions {\n /** Expected app name (enforced against the manifest when set). */\n expectName?: string;\n}\n\n/**\n * Install flow: materialize the source into a staging dir, install deps\n * (scripts disabled), import + validate the manifest, and only then move\n * it into apps/<name>/repo. A failed validation never leaves a broken app.\n */\nexport async function installApp(\n store: Store,\n source: string | AppSourceSpec,\n options: InstallOptions = {},\n): Promise<InstallRecord> {\n let spec: AppSourceSpec;\n let expectName = options.expectName;\n\n if (typeof source === 'string') {\n const parsed = parseSourceArg(source);\n if (parsed.type === 'registry') {\n const resolved = await resolveApp(store, parsed.name);\n spec = {\n type: 'git',\n url: resolved.app.repo,\n ...(resolved.app.ref && { ref: resolved.app.ref }),\n };\n expectName = expectName ?? parsed.name;\n } else {\n spec = parsed;\n }\n } else {\n spec = source;\n }\n\n // Local sources are symlinked in place; git sources are staged first.\n let stageDir: string | undefined;\n let inspectDir: string;\n if (spec.type === 'local') {\n inspectDir = spec.path;\n } else {\n stageDir = join(store.stagingDir, randomUUID());\n await fetchSource(spec, stageDir);\n await installDependencies(stageDir);\n inspectDir = stageDir;\n }\n\n try {\n const { manifest } = await loadApp(inspectDir);\n checkSdkCompatibility(manifest);\n if (expectName && manifest.name !== expectName) {\n throw new AppSdkError(\n `App manifest name \"${manifest.name}\" does not match requested \"${expectName}\"`,\n );\n }\n const existing = await store.readAppRecord(manifest.name);\n if (existing) {\n throw new AppSdkError(\n `App \"${manifest.name}\" is already installed (use: alashi update ${manifest.name})`,\n );\n }\n\n const repoDir = store.appRepoDir(manifest.name);\n await mkdir(store.appDir(manifest.name), { recursive: true });\n if (stageDir) {\n await rename(stageDir, repoDir);\n stageDir = undefined;\n } else {\n await fetchSource(spec, repoDir);\n }\n await mkdir(store.appDataDir(manifest.name), { recursive: true });\n\n const now = new Date().toISOString();\n const record: InstallRecord = {\n name: manifest.name,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n source: spec,\n installedAt: now,\n updatedAt: now,\n };\n await store.writeAppRecord(record);\n return record;\n } finally {\n if (stageDir) await rm(stageDir, { recursive: true, force: true });\n }\n}\n\nexport async function updateApp(store: Store, name: string): Promise<InstallRecord> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n const repoDir = store.appRepoDir(name);\n await updateSource(record.source, repoDir);\n await installDependencies(repoDir);\n const { manifest } = await loadApp(repoDir);\n checkSdkCompatibility(manifest);\n if (manifest.name !== name) {\n throw new AppSdkError(`App renamed itself to \"${manifest.name}\"; refusing update`);\n }\n\n const updated: InstallRecord = {\n ...record,\n version: manifest.version,\n ...(manifest.sdkVersion && { sdkVersion: manifest.sdkVersion }),\n updatedAt: new Date().toISOString(),\n };\n await store.writeAppRecord(updated);\n return updated;\n}\n\nexport interface RemoveOptions {\n /** Also delete the app's data/ and config (default keeps them). */\n purge?: boolean;\n}\n\nexport async function removeApp(\n store: Store,\n name: string,\n options: RemoveOptions = {},\n): Promise<void> {\n const record = await store.readAppRecord(name);\n if (!record) throw new AppSdkError(`App \"${name}\" is not installed`);\n\n if (options.purge) {\n await rm(store.appDir(name), { recursive: true, force: true });\n return;\n }\n await rm(store.appRepoDir(name), { recursive: true, force: true });\n await rm(store.appRecordPath(name), { force: true });\n}\n","import { mkdir, readFile } from 'node:fs/promises';\nimport { basename, join, resolve } from 'node:path';\nimport {\n type AgentDefinition,\n type AIProvider,\n type AISession,\n type AppConfigStatus,\n type AppContext,\n type AppManifest,\n AppSdkError,\n ConfigError,\n type ConfigObject,\n evaluateAppConfig,\n interpolateConfig,\n JsonFileStore,\n type Logger,\n type McpServerConfig,\n manifestConfigDefaults,\n mergeConfig,\n parseAgentFile,\n type SessionOptions,\n} from '@alashi/apps-sdk';\nimport type { Store } from './store.js';\n\nfunction createLogger(prefix: string): Logger {\n const log = (\n level: 'debug' | 'info' | 'warn' | 'error',\n message: string,\n data?: Record<string, unknown>,\n ): void => {\n const line = `[${prefix}] ${message}`;\n if (data === undefined) console[level](line);\n else console[level](line, data);\n };\n return {\n debug: (message, data) => log('debug', message, data),\n info: (message, data) => log('info', message, data),\n warn: (message, data) => log('warn', message, data),\n error: (message, data) => log('error', message, data),\n };\n}\n\ninterface SessionDefaults {\n mcpServers: Record<string, McpServerConfig>;\n allowedTools: string[];\n agents: Record<string, AgentDefinition>;\n skillDirs: string[];\n instructions?: string;\n cwd: string;\n /** When set, createSession fails closed with this message (missing required config). */\n unreadyError?: string;\n}\n\n/**\n * Wraps the host provider so every session an app opens automatically gets\n * its manifest-declared MCP servers, tool allowlist, agents, skills,\n * instructions, and data-dir cwd. Per-call options still win where they\n * overlap.\n */\nfunction scopeProvider(provider: AIProvider, defaults: SessionDefaults): AIProvider {\n return {\n name: provider.name,\n async createSession(options: SessionOptions = {}): Promise<AISession> {\n if (defaults.unreadyError !== undefined) throw new ConfigError(defaults.unreadyError);\n const mcpServers = { ...defaults.mcpServers, ...options.mcpServers };\n const allowedTools = [\n ...new Set([...defaults.allowedTools, ...(options.allowedTools ?? [])]),\n ];\n const agents = { ...defaults.agents, ...options.agents };\n const skillDirs = [...new Set([...defaults.skillDirs, ...(options.skillDirs ?? [])])];\n return provider.createSession({\n cwd: defaults.cwd,\n ...(defaults.instructions !== undefined && { systemPrompt: defaults.instructions }),\n ...options,\n ...(Object.keys(mcpServers).length > 0 && { mcpServers }),\n ...(allowedTools.length > 0 && { allowedTools }),\n ...(Object.keys(agents).length > 0 && { agents }),\n ...(skillDirs.length > 0 && { skillDirs }),\n });\n },\n };\n}\n\n/**\n * Reads and parses the manifest-declared agent files. The agent's name is\n * the filename without the `.md` extension.\n */\nexport async function loadAgents(\n repoDir: string,\n paths: string[],\n): Promise<Record<string, AgentDefinition>> {\n const entries = await Promise.all(\n paths.map(async (path) => {\n const content = await readFile(resolve(repoDir, path), 'utf8');\n return [basename(path, '.md'), parseAgentFile(content, path)] as const;\n }),\n );\n return Object.fromEntries(entries);\n}\n\n/** Reads the manifest-declared instructions file; the content is the app's system prompt. */\nexport async function loadInstructions(\n repoDir: string,\n path: string | undefined,\n): Promise<string | undefined> {\n if (path === undefined) return undefined;\n const content = (await readFile(resolve(repoDir, path), 'utf8')).trim();\n if (content === '') {\n throw new AppSdkError(`Instructions file \"${path}\" is empty`);\n }\n return content;\n}\n\nexport interface ResolvedAppConfig {\n /** Merged config layers, before `${config.*}` interpolation. */\n merged: ConfigObject;\n status: AppConfigStatus;\n}\n\n/** Merges the config layers for an app and evaluates its setup state. */\nexport async function resolveAppConfig(\n store: Store,\n manifest: AppManifest,\n): Promise<ResolvedAppConfig> {\n const globalConfig = await store.readGlobalConfig();\n const merged = mergeConfig(\n manifestConfigDefaults(manifest),\n globalConfig.apps?.[manifest.name],\n await store.readAppConfig(manifest.name),\n );\n return { merged, status: evaluateAppConfig(manifest, merged) };\n}\n\n/**\n * Builds the AppContext for an installed app: merges the config layers\n * (manifest defaults < global apps[name] < per-app config), resolves\n * `${config.*}` placeholders (with `dataDir` available as a built-in key),\n * and scopes the AI provider to the app's manifest.\n *\n * Setup state shapes the session defaults: unconfigured MCP servers are\n * dropped (with their tools) so interpolation never hits a missing key, and\n * while required keys are missing the scoped provider fails closed.\n */\nexport async function buildAppContext(\n store: Store,\n manifest: AppManifest,\n provider: AIProvider,\n): Promise<AppContext> {\n const name = manifest.name;\n const dataDir = store.appDataDir(name);\n const repoDir = store.appRepoDir(name);\n await mkdir(dataDir, { recursive: true });\n\n const { merged, status } = await resolveAppConfig(store, manifest);\n // dataDir is a host-provided lookup key: apps reference it as ${config.dataDir}.\n const lookup: ConfigObject = { ...merged, dataDir };\n const interpolate = <T>(value: T): T =>\n interpolateConfig(value, lookup, { resolveRelativeTo: repoDir });\n\n const config = interpolate(merged);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const excluded = status.integrations.filter((i) => !i.configured).map((i) => i.name);\n const activeServers = Object.fromEntries(\n Object.entries(manifest.mcpServers)\n .filter(([server]) => !excluded.includes(server))\n .map(([server, { optional: _optional, ...decl }]) => [server, decl as McpServerConfig]),\n );\n const mcpServers = interpolate(activeServers);\n const allowedTools = manifest.permissions.allowedTools.filter(\n (tool) => !excluded.some((server) => tool.startsWith(`mcp__${server}__`)),\n );\n\n return {\n appName: name,\n dataDir,\n config,\n logger: createLogger(name),\n state: new JsonFileStore(join(dataDir, 'state.json')),\n ai: scopeProvider(provider, {\n mcpServers,\n allowedTools,\n agents: await loadAgents(repoDir, manifest.agents),\n skillDirs: manifest.skills.map((dir) => resolve(repoDir, dir)),\n ...(instructions !== undefined && { instructions }),\n cwd: dataDir,\n ...(!status.ready && {\n unreadyError: `App \"${name}\" is not configured: missing ${status.missing.join(', ')}. Set the missing keys in the dashboard settings or ${store.appConfigPath(name)}.`,\n }),\n }),\n };\n}\n","import type { AppContext, AppManifest } from '@alashi/apps-sdk';\nimport { Cron } from 'croner';\nimport type { JobRun, Store } from './store.js';\n\nexport interface SchedulableApp {\n manifest: AppManifest;\n ctx: AppContext;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nexport interface JobStatus {\n name: string;\n schedule: string;\n description?: string;\n /** False when the cron expression failed to parse (job never fires). */\n valid: boolean;\n running: boolean;\n nextRun?: string;\n lastRun?: string;\n recentRuns: JobRun[];\n}\n\nconst RUN_LOG_CAP = 50;\nconst RECENT_RUNS = 5;\n\nfunction nextFire(schedule: string, after?: Date): Date | null {\n // A Cron without a callback only computes times; it never schedules timers.\n return new Cron(schedule).nextRun(after ?? undefined);\n}\n\n/**\n * Runs manifest-declared jobs on their cron schedules. One instance per host\n * process; start() schedules every installed app's jobs, stop() cancels them.\n * A job missed while the host was down runs once at startup (\"catchup\"), but\n * only if it has run before — a fresh install waits for its first slot.\n */\nexport class Scheduler {\n private crons: Cron[] = [];\n private runningJobs = new Set<string>();\n private started = false;\n /** Serializes jobs.json read-modify-write across concurrently finishing jobs. */\n private stateQueue: Promise<unknown> = Promise.resolve();\n\n constructor(\n private readonly store: Store,\n private readonly getApp: (name: string) => Promise<SchedulableApp | undefined>,\n ) {}\n\n async start(): Promise<void> {\n if (this.started) return;\n this.started = true;\n for (const record of await this.store.listInstalled()) {\n let app: SchedulableApp | undefined;\n try {\n app = await this.getApp(record.name);\n } catch (err) {\n console.error(`[scheduler] skipping ${record.name}: failed to load (${String(err)})`);\n continue;\n }\n if (!app || app.manifest.jobs.length === 0) continue;\n const state = await this.store.readJobState(record.name);\n for (const job of app.manifest.jobs) {\n let cron: Cron;\n try {\n cron = new Cron(job.schedule, { paused: true }, () => {\n void this.run(record.name, job.name, 'schedule');\n });\n } catch (err) {\n app.ctx.logger.error(`Job \"${job.name}\" has an invalid schedule`, {\n schedule: job.schedule,\n error: String(err),\n });\n continue;\n }\n const last = state.lastRun[job.name];\n if (last) {\n const missed = nextFire(job.schedule, new Date(last));\n if (missed && missed.getTime() <= Date.now()) {\n void this.run(record.name, job.name, 'catchup');\n }\n }\n cron.resume();\n this.crons.push(cron);\n }\n }\n }\n\n stop(): void {\n for (const cron of this.crons) cron.stop();\n this.crons = [];\n this.started = false;\n }\n\n /**\n * Runs one job immediately. Returns the recorded run, 'skipped' when that\n * job is already mid-run, or undefined when the app/job doesn't exist.\n */\n async run(\n appName: string,\n jobName: string,\n trigger: JobRun['trigger'] = 'manual',\n ): Promise<JobRun | 'skipped' | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n const handler = app?.jobHandlers?.[jobName];\n if (!app || !handler || !app.manifest.jobs.some((j) => j.name === jobName)) return undefined;\n\n const key = `${appName}/${jobName}`;\n if (this.runningJobs.has(key)) return 'skipped';\n this.runningJobs.add(key);\n\n const startedAt = new Date().toISOString();\n let ok = true;\n let error: string | undefined;\n try {\n await handler(app.ctx);\n } catch (err) {\n ok = false;\n error = String(err);\n app.ctx.logger.error(`Job \"${jobName}\" failed`, { error });\n } finally {\n this.runningJobs.delete(key);\n }\n\n const run: JobRun = {\n job: jobName,\n trigger,\n startedAt,\n finishedAt: new Date().toISOString(),\n ok,\n ...(error !== undefined && { error }),\n };\n await this.recordRun(appName, run);\n return run;\n }\n\n /** Job statuses for an app, or undefined when the app isn't installed. */\n async status(appName: string): Promise<JobStatus[] | undefined> {\n const app = await this.getApp(appName).catch(() => undefined);\n if (!app) return undefined;\n const state = await this.store.readJobState(appName);\n return app.manifest.jobs.map((job) => {\n let valid = true;\n let nextRun: string | undefined;\n try {\n nextRun = nextFire(job.schedule)?.toISOString();\n } catch {\n valid = false;\n }\n return {\n name: job.name,\n schedule: job.schedule,\n ...(job.description !== undefined && { description: job.description }),\n valid,\n running: this.runningJobs.has(`${appName}/${job.name}`),\n ...(nextRun !== undefined && { nextRun }),\n ...(state.lastRun[job.name] !== undefined && { lastRun: state.lastRun[job.name] }),\n recentRuns: state.runs\n .filter((r) => r.job === job.name)\n .slice(-RECENT_RUNS)\n .reverse(),\n };\n });\n }\n\n private recordRun(appName: string, run: JobRun): Promise<void> {\n const update = this.stateQueue.then(async () => {\n const state = await this.store.readJobState(appName);\n state.lastRun[run.job] = run.startedAt;\n state.runs.push(run);\n if (state.runs.length > RUN_LOG_CAP) state.runs.splice(0, state.runs.length - RUN_LOG_CAP);\n await this.store.writeJobState(appName, state);\n });\n this.stateQueue = update.catch(() => undefined);\n return update;\n }\n}\n","/**\n * Self-contained per-app settings page. Config data comes from\n * GET /api/apps/:name/config at runtime; saving PUTs only changed keys.\n * The read-only \"App definition\" section (instructions, agents, skills,\n * allowed tools, MCP servers, jobs) comes from GET /api/apps/:name/manifest.\n * The app name is injected via a JSON-encoded script constant, never into\n * markup, so no escaping subtleties apply.\n */\nexport function settingsPage(name: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Settings</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n<style>\n .card { margin-bottom: 16px; }\n #status { margin-left: 12px; color: var(--muted); }\n #manifest-cards { margin-top: 32px; }\n .ro-pre { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; }\n .ro-item { margin-bottom: 12px; }\n .ro-item:last-child { margin-bottom: 0; }\n details summary { cursor: pointer; }\n</style>\n</head>\n<body>\n<header class=\"topbar\"><a href=\"/\">alashi</a> <span>/</span> <strong id=\"title\"></strong> <span>settings</span></header>\n<main class=\"narrow\">\n <div id=\"banner\" class=\"banner\" hidden></div>\n <div id=\"cards\"></div>\n <button id=\"save\" class=\"primary\">Save changes</button><span id=\"status\"></span>\n <div id=\"manifest-cards\"></div>\n</main>\n<script>\nconst APP = ${JSON.stringify(name)};\ndocument.getElementById('title').textContent = APP;\n\nconst state = { data: null, dirty: new Map() };\n\nfunction el(tag, attrs = {}, children = []) {\n const node = document.createElement(tag);\n for (const [k, v] of Object.entries(attrs)) {\n if (k === 'text') node.textContent = v;\n else if (k.startsWith('on')) node.addEventListener(k.slice(2), v);\n else node.setAttribute(k, v);\n }\n for (const child of children) node.append(child);\n return node;\n}\n\nfunction parseInput(raw) {\n if (raw === '') return null; // empty clears the user-set value\n try { const v = JSON.parse(raw); if (typeof v !== 'string') return v; } catch {}\n return raw;\n}\n\nfunction displayValue(value) {\n return typeof value === 'string' ? value : value === undefined ? '' : JSON.stringify(value);\n}\n\nconst ACRONYMS = new Set(['url', 'uri', 'api', 'id', 'ip', 'db', 'ssl', 'tls', 'ttl', 'http', 'https']);\n\nfunction labelFor(key, decl) {\n if (decl.label) return decl.label;\n const words = key\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .toLowerCase()\n .split(' ');\n return words\n .map((w, i) => (ACRONYMS.has(w) ? w.toUpperCase() : i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w))\n .join(' ');\n}\n\nfunction field(key, decl, data) {\n const secret = decl.secret === true;\n const isBool = !secret &&\n (typeof decl.default === 'boolean' || typeof data.values[key] === 'boolean');\n if (isBool) {\n const input = el('input', {\n type: 'checkbox',\n onchange: () => state.dirty.set(key, { secret: false, input }),\n });\n input.checked = data.values[key] === true;\n const labelChildren = [input, document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', { class: 'check' }, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n return el('div', { class: 'field' }, parts);\n }\n const isSet = secret ? data.secretsSet.includes(key) : data.values[key] !== undefined;\n const input = el('input', {\n type: secret ? 'password' : 'text',\n value: secret ? '' : displayValue(data.values[key]),\n placeholder: secret && isSet ? '(set — type to replace)' : '',\n oninput: () => state.dirty.set(key, { secret, input }),\n });\n const labelChildren = [document.createTextNode(labelFor(key, decl))];\n if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));\n const parts = [el('label', {}, labelChildren)];\n if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));\n parts.push(input);\n return el('div', { class: 'field' }, parts);\n}\n\nfunction card(title, keys, data, configured) {\n const chip = configured === undefined\n ? ''\n : el('span', { class: 'chip ' + (configured ? 'on' : 'off'),\n text: configured ? 'configured' : 'not configured' });\n return el('div', { class: 'card' }, [\n el('h2', {}, chip === '' ? [document.createTextNode(title)] : [document.createTextNode(title), chip]),\n el('div', { class: 'body' }, keys.map((key) => field(key, data.schema[key] ?? {}, data))),\n ]);\n}\n\nfunction render(data) {\n state.data = data;\n state.dirty.clear();\n const banner = document.getElementById('banner');\n banner.hidden = false;\n if (data.ready) {\n banner.className = 'banner ok';\n banner.textContent = 'App is configured and ready.';\n } else {\n banner.className = 'banner warn';\n banner.textContent = 'Needs setup — missing: ' + data.missing.join(', ');\n }\n\n const cards = document.getElementById('cards');\n cards.replaceChildren();\n const integrationKeys = new Set(data.integrations.flatMap((i) => i.configKeys));\n\n // Non-integration keys, sectioned by their declared group (\"General\" first).\n const sections = new Map();\n for (const key of Object.keys(data.schema)) {\n if (integrationKeys.has(key)) continue;\n const title = data.schema[key].group ?? 'General';\n if (!sections.has(title)) sections.set(title, []);\n sections.get(title).push(key);\n }\n if (sections.has('General')) cards.append(card('General', sections.get('General'), data, undefined));\n for (const [title, keys] of sections) {\n if (title !== 'General') cards.append(card(title, keys, data, undefined));\n }\n\n // Integration cards, titled by the keys' shared group when declared.\n for (const integration of data.integrations) {\n if (integration.configKeys.length === 0) continue;\n const groups = new Set(integration.configKeys.map((k) => data.schema[k]?.group).filter(Boolean));\n const title = groups.size === 1 ? [...groups][0] : integration.name;\n cards.append(card(\n title + (integration.optional ? ' (optional integration)' : ''),\n integration.configKeys, data, integration.configured,\n ));\n }\n if (cards.childElementCount === 0) {\n cards.append(el('p', { text: 'This app declares no configurable settings.' }));\n }\n}\n\nfunction roCard(title, children) {\n return el('div', { class: 'card' }, [\n el('h2', { text: title }),\n el('div', { class: 'body' }, children),\n ]);\n}\n\nfunction renderManifest(m) {\n const root = document.getElementById('manifest-cards');\n root.replaceChildren();\n const cards = [];\n\n const about = [el('div', { class: 'desc', text: 'Version ' + m.version + (m.model ? ' · model: ' + m.model : '') })];\n if (m.description) about.unshift(el('div', { text: m.description }));\n cards.push(roCard('About', about));\n\n if (m.instructions) {\n cards.push(roCard('Instructions', [el('pre', { class: 'ro-pre', text: m.instructions })]));\n }\n\n const agentNames = Object.keys(m.agents);\n if (agentNames.length > 0) {\n cards.push(roCard('Agents', agentNames.map((name) => {\n const a = m.agents[name];\n const meta = [a.model && 'model: ' + a.model, a.tools && 'tools: ' + a.tools.join(', ')]\n .filter(Boolean).join(' · ');\n const body = meta ? [el('div', { class: 'desc', text: meta })] : [];\n body.push(el('pre', { class: 'ro-pre', text: a.prompt }));\n return el('details', { class: 'ro-item' }, [\n el('summary', {}, [el('strong', { text: name }), document.createTextNode(' — ' + a.description)]),\n ...body,\n ]);\n })));\n }\n\n if (m.skills.length > 0) {\n cards.push(roCard('Skills', m.skills.map((s) => el('div', { class: 'ro-item' }, [\n el('code', { text: s.dir + '/' }),\n el('div', { class: 'desc', text: s.entries.length ? s.entries.join(', ') : '(directory not found or empty)' }),\n ]))));\n }\n\n if (m.mcpServers.length > 0) {\n cards.push(roCard('MCP servers', m.mcpServers.map((s) => el('div', { class: 'ro-item' }, [\n el('strong', { text: s.name }),\n document.createTextNode(' (' + s.type + (s.optional ? ', optional' : '') + ') '),\n el('span', { class: 'chip ' + (s.configured ? 'on' : 'off'),\n text: s.configured ? 'configured' : 'not configured' }),\n el('div', { class: 'desc', text: s.type === 'stdio' ? s.command : s.url }),\n ]))));\n }\n\n if (m.allowedTools.length > 0) {\n cards.push(roCard('Allowed tools', m.allowedTools.map((t) =>\n el('div', {}, [el('code', { text: t })]))));\n }\n\n if (m.jobs.length > 0) {\n cards.push(roCard('Jobs', m.jobs.map((j) => el('div', { class: 'ro-item' }, [\n el('strong', { text: j.name }),\n el('code', { text: ' ' + j.schedule }),\n ...(j.description ? [el('div', { class: 'desc', text: j.description })] : []),\n ]))));\n }\n\n root.append(\n el('h1', { text: 'App definition' }),\n el('p', { class: 'subtitle', text: 'Declared by the app; read-only.' }),\n ...cards,\n );\n}\n\nasync function load() {\n const [res, manRes] = await Promise.all([\n fetch('/api/apps/' + APP + '/config'),\n fetch('/api/apps/' + APP + '/manifest'),\n ]);\n if (!res.ok) {\n document.getElementById('banner').hidden = false;\n document.getElementById('banner').className = 'banner warn';\n document.getElementById('banner').textContent = 'Failed to load config (' + res.status + ')';\n return;\n }\n render(await res.json());\n if (manRes.ok) renderManifest(await manRes.json());\n}\n\ndocument.getElementById('save').addEventListener('click', async () => {\n const body = {};\n for (const [key, { secret, input }] of state.dirty) {\n if (secret && input.value === '') continue; // untouched secret: keep as-is\n body[key] = input.type === 'checkbox' ? input.checked : parseInput(input.value);\n }\n if (Object.keys(body).length === 0) return;\n const status = document.getElementById('status');\n status.textContent = 'Saving…';\n const res = await fetch('/api/apps/' + APP + '/config', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n });\n status.textContent = res.ok ? 'Saved — app restarted.' : 'Save failed (' + res.status + ')';\n if (res.ok) await load();\n});\n\nload();\n</script>\n</body>\n</html>\n`;\n}\n","import { randomUUID } from 'node:crypto';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { extname, resolve, sep } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n type AIProvider,\n type AISession,\n APP_NAME_PATTERN,\n type AppContext,\n type AppManifest,\n type FetchHandler,\n} from '@alashi/apps-sdk';\nimport { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport { loadApp } from './loader.js';\nimport { buildAppContext, loadAgents, loadInstructions, resolveAppConfig } from './runtime.js';\nimport { Scheduler } from './scheduler.js';\nimport { settingsPage } from './settings-page.js';\nimport type { ChatState, Conversation, Store } from './store.js';\n\ninterface RunningApp {\n manifest: AppManifest;\n ctx: AppContext;\n handler?: FetchHandler;\n jobHandlers?: Record<string, (ctx: AppContext) => Promise<void>>;\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.ico': 'image/x-icon',\n '.txt': 'text/plain; charset=utf-8',\n};\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n}\n\nexport interface AppServer {\n /** The Hono app — pass its fetch to a Node server or call request() in tests. */\n app: Hono;\n /** Cron scheduler for manifest-declared jobs; call start() to begin firing. */\n scheduler: Scheduler;\n /** Stops the scheduler and closes all warm AI sessions. */\n close(): Promise<void>;\n}\n\n/**\n * The host HTTP surface:\n * GET / dashboard of installed apps\n * GET /settings/:name per-app settings page (config form)\n * GET /api/apps installed apps as JSON\n * POST /api/apps/:name/chat { message, conversationId? } -> SSE stream of AgentEvents\n * (omit conversationId to start a new conversation; the\n * id is echoed in the x-conversation-id response header)\n * GET /api/apps/:name/chat/conversations conversations, most recent first\n * POST /api/apps/:name/chat/conversations { sessionId?, title? } -> create a conversation,\n * optionally adopting an existing provider session (e.g. an\n * app-run investigation) so chat continues where it left off\n * GET /api/apps/:name/chat/:conversationId/history past turns from the provider transcript\n * GET /api/apps/:name/jobs manifest-declared jobs with schedule/status\n * POST /api/apps/:name/jobs/:job/run trigger a job now (409 while already running)\n * GET /api/apps/:name/manifest read-only app definition: instructions, agents,\n * skills (with dir contents), allowed tools,\n * MCP servers (with configured state), jobs, model\n * GET /api/apps/:name/config declared config schema + merged values\n * (secrets masked) + readiness/integrations\n * PUT /api/apps/:name/config merge keys into the per-app config layer\n * (null deletes a key) and restart the app\n * ALL /app/:name/api/* dispatched to the app's fetch handler (prefix stripped)\n * ALL /app/:name/* a matching static file from the app's ui dir wins,\n * otherwise the app's fetch handler (/app/:name stripped),\n * otherwise 404\n */\nexport function createServer(store: Store, provider: AIProvider): AppServer {\n const apps = new Map<string, Promise<RunningApp>>();\n const sessions = new Map<string, Promise<AISession>>();\n\n async function startApp(name: string): Promise<RunningApp> {\n const { definition, manifest } = await loadApp(store.appRepoDir(name));\n const ctx = await buildAppContext(store, manifest, provider);\n const handler = definition.routes ? await definition.routes(ctx) : undefined;\n return { manifest, ctx, handler, jobHandlers: definition.jobHandlers };\n }\n\n /** Loads an installed app once per process; undefined when not installed. */\n async function getApp(name: string): Promise<RunningApp | undefined> {\n if (!APP_NAME_PATTERN.test(name)) return undefined;\n if (!(await store.readAppRecord(name))) return undefined;\n let running = apps.get(name);\n if (!running) {\n running = startApp(name);\n running.catch(() => apps.delete(name));\n apps.set(name, running);\n }\n return running;\n }\n\n /** chat.json per app, loaded once and mutated in place so turns share one view. */\n const chatStates = new Map<string, Promise<ChatState>>();\n\n function getChatState(name: string): Promise<ChatState> {\n let state = chatStates.get(name);\n if (!state) {\n state = store.readChatState(name);\n state.catch(() => chatStates.delete(name));\n chatStates.set(name, state);\n }\n return state;\n }\n\n function getSession(name: string, running: RunningApp, convo: Conversation): Promise<AISession> {\n const key = `${name}/${convo.id}`;\n let session = sessions.get(key);\n if (!session) {\n const model = running.ctx.config.model;\n session = running.ctx.ai.createSession({\n ...(typeof model === 'string' && { model }),\n ...(convo.sessionId && { resume: convo.sessionId }),\n });\n session.catch(() => sessions.delete(key));\n sessions.set(key, session);\n }\n return session;\n }\n\n const scheduler = new Scheduler(store, getApp);\n\n /** Rebuilds the request with `prefix` stripped and hands it to the app's handler. */\n function dispatch(\n handler: FetchHandler,\n raw: Request,\n prefix: string,\n ): Response | Promise<Response> {\n const url = new URL(raw.url);\n url.pathname = url.pathname.slice(prefix.length) || '/';\n const request = new Request(url, {\n method: raw.method,\n headers: raw.headers,\n body: raw.body,\n ...(raw.body ? { duplex: 'half' } : {}),\n } as RequestInit);\n return handler(request);\n }\n\n /** A file from the app's ui dir, or undefined when there is no match. */\n async function serveStatic(\n name: string,\n manifest: AppManifest,\n reqPath: string,\n ): Promise<Response | undefined> {\n if (!manifest.ui) return undefined;\n const uiDir = resolve(store.appRepoDir(name), manifest.ui);\n let rest: string;\n try {\n rest = decodeURIComponent(reqPath.slice(`/app/${name}/`.length));\n } catch {\n return undefined;\n }\n if (rest === '' || rest.endsWith('/')) rest += 'index.html';\n const filePath = resolve(uiDir, rest);\n if (filePath !== uiDir && !filePath.startsWith(uiDir + sep)) return undefined;\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type },\n });\n } catch {\n return undefined;\n }\n }\n\n const app = new Hono();\n\n app.onError((err, c) => c.json({ error: String(err) }, 500));\n\n // System UI assets (design system, later vendored libs). Versioned paths:\n // breaking changes ship as /assets/v2/, existing apps keep working.\n const assetsDir = fileURLToPath(new URL('../assets', import.meta.url));\n\n app.get('/assets/*', async (c) => {\n let rest: string;\n try {\n rest = decodeURIComponent(c.req.path.slice('/assets/'.length));\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n const filePath = resolve(assetsDir, rest);\n if (!filePath.startsWith(assetsDir + sep)) return c.json({ error: 'Not found' }, 404);\n try {\n const body = await readFile(filePath);\n const type = CONTENT_TYPES[extname(filePath)] ?? 'application/octet-stream';\n return new Response(new Uint8Array(body), {\n status: 200,\n headers: { 'content-type': type, 'cache-control': 'public, max-age=300' },\n });\n } catch {\n return c.json({ error: 'Not found' }, 404);\n }\n });\n\n /** Readiness for the dashboard badge; undefined when the app fails to load. */\n async function readiness(name: string): Promise<{ ready: boolean } | undefined> {\n try {\n const running = await getApp(name);\n if (!running) return undefined;\n return (await resolveAppConfig(store, running.manifest)).status;\n } catch {\n return undefined;\n }\n }\n\n app.get('/', async (c) => {\n const records = await store.listInstalled();\n const rows = (\n await Promise.all(\n records.map(async (r) => {\n const name = escapeHtml(r.name);\n const status = await readiness(r.name);\n const badge = !status\n ? '<span class=\"badge err\">failed to load</span>'\n : status.ready\n ? '<span class=\"badge ok\">ready</span>'\n : `<a href=\"/settings/${name}\"><span class=\"badge err\">needs setup</span></a>`;\n return `<tr>\n <td><a href=\"/app/${name}/\">${name}</a></td>\n <td class=\"muted\">v${escapeHtml(r.version)}</td>\n <td class=\"muted\">${escapeHtml(r.source.type)}</td>\n <td>${badge}</td>\n <td style=\"text-align:right\"><a href=\"/settings/${name}\">settings</a></td>\n</tr>`;\n }),\n )\n ).join('\\n');\n const body =\n records.length === 0\n ? '<div class=\"empty\">No apps installed. Run <code>alashi install &lt;name&gt;</code>.</div>'\n : `<table>\n<thead><tr><th>App</th><th>Version</th><th>Source</th><th>Status</th><th></th></tr></thead>\n<tbody>\n${rows}\n</tbody>\n</table>`;\n return c.html(\n `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>alashi</title>\n<link rel=\"stylesheet\" href=\"/assets/v1/system.css\">\n</head>\n<body>\n<header class=\"topbar\"><strong>alashi</strong></header>\n<main class=\"narrow\">\n<h1>Apps</h1>\n<p class=\"subtitle\">Installed apps and their status.</p>\n<div class=\"panel\">\n${body}\n</div>\n</main>\n</body>\n</html>\n`,\n );\n });\n\n app.get('/api/apps', async (c) => c.json({ apps: await store.listInstalled() }));\n\n app.get('/settings/:name', async (c) => {\n const name = c.req.param('name');\n if (!APP_NAME_PATTERN.test(name) || !(await store.readAppRecord(name))) {\n return c.json({ error: `App \"${name}\" is not installed` }, 404);\n }\n return c.html(settingsPage(name));\n });\n\n app.get('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { conversations } = await getChatState(name);\n return c.json({\n conversations: [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)),\n });\n });\n\n app.post('/api/apps/:name/chat/conversations', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let sessionId: unknown;\n let title: unknown;\n try {\n ({ sessionId, title } = (await c.req.json()) as { sessionId?: unknown; title?: unknown });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (sessionId !== undefined && (typeof sessionId !== 'string' || sessionId.length === 0)) {\n return c.json({ error: '\"sessionId\" must be a non-empty string' }, 400);\n }\n if (title !== undefined && typeof title !== 'string') {\n return c.json({ error: '\"title\" must be a string' }, 400);\n }\n\n const state = await getChatState(name);\n const convo: Conversation = {\n id: randomUUID(),\n title: (title ?? '').slice(0, 80) || 'New conversation',\n updatedAt: new Date().toISOString(),\n ...(sessionId !== undefined && { sessionId }),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n return c.json({ conversation: convo }, 201);\n });\n\n app.get('/api/apps/:name/chat/:conversationId/history', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const conversationId = c.req.param('conversationId');\n const { conversations } = await getChatState(name);\n const convo = conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n const messages = convo.sessionId ? ((await provider.history?.(convo.sessionId)) ?? []) : [];\n return c.json({ messages });\n });\n\n /** Drops the cached app and its warm sessions so the next request rebuilds with fresh config. */\n function restartApp(name: string): void {\n apps.delete(name);\n for (const [key, session] of sessions) {\n if (!key.startsWith(`${name}/`)) continue;\n sessions.delete(key);\n void session.then((s) => s.close()).catch(() => undefined);\n }\n }\n\n app.get('/api/apps/:name/manifest', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n const repoDir = store.appRepoDir(name);\n\n const instructions = await loadInstructions(repoDir, manifest.instructions);\n\n const skills = await Promise.all(\n manifest.skills.map(async (dir) => {\n let entries: string[] = [];\n try {\n entries = (await readdir(resolve(repoDir, dir))).sort();\n } catch {\n // A missing skill dir is still worth surfacing, just with no entries.\n }\n return { dir, entries };\n }),\n );\n\n const configured = new Map(status.integrations.map((i) => [i.name, i.configured]));\n // Raw manifest declarations only: `${config.*}` placeholders are never\n // interpolated here, so secret values cannot leak through env/headers.\n const mcpServers = Object.entries(manifest.mcpServers).map(([server, decl]) => ({\n name: server,\n ...('command' in decl\n ? { type: 'stdio', command: [decl.command, ...(decl.args ?? [])].join(' ') }\n : { type: decl.type, url: decl.url }),\n optional: decl.optional === true,\n configured: configured.get(server) ?? true,\n }));\n\n return c.json({\n name: manifest.name,\n version: manifest.version,\n ...(manifest.description !== undefined && { description: manifest.description }),\n ...(instructions !== undefined && { instructions }),\n ...(typeof merged.model === 'string' && { model: merged.model }),\n agents: await loadAgents(repoDir, manifest.agents),\n skills,\n allowedTools: manifest.permissions.allowedTools,\n mcpServers,\n jobs: manifest.jobs,\n });\n });\n\n app.get('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n const { manifest } = running;\n const { merged, status } = await resolveAppConfig(store, manifest);\n // Secret values never leave the host; the UI only learns whether they are set.\n const values: Record<string, unknown> = { ...merged };\n const secretsSet: string[] = [];\n for (const [key, decl] of Object.entries(manifest.config)) {\n if (decl.secret !== true) continue;\n if (values[key] !== undefined) secretsSet.push(key);\n delete values[key];\n }\n return c.json({\n schema: manifest.config,\n values,\n secretsSet,\n userKeys: Object.keys(await store.readAppConfig(name)),\n ...status,\n });\n });\n\n app.put('/api/apps/:name/config', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Body must be an object of config keys' }, 400);\n }\n\n const config = await store.readAppConfig(name);\n for (const [key, value] of Object.entries(body)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;\n if (value === null) delete config[key];\n else config[key] = value;\n }\n await store.writeAppConfig(name, config);\n restartApp(name);\n\n const { status } = await resolveAppConfig(store, running.manifest);\n return c.json({ ok: true, ...status });\n });\n\n app.get('/api/apps/:name/jobs', async (c) => {\n const name = c.req.param('name');\n const jobs = await scheduler.status(name);\n if (!jobs) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n return c.json({ jobs });\n });\n\n app.post('/api/apps/:name/jobs/:job/run', async (c) => {\n const result = await scheduler.run(c.req.param('name'), c.req.param('job'));\n if (result === undefined) return c.json({ error: 'Unknown app or job' }, 404);\n if (result === 'skipped') return c.json({ error: 'Job is already running' }, 409);\n return c.json({ run: result }, result.ok ? 200 : 500);\n });\n\n app.post('/api/apps/:name/chat', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n let message: unknown;\n let conversationId: unknown;\n try {\n ({ message, conversationId } = (await c.req.json()) as {\n message?: unknown;\n conversationId?: unknown;\n });\n } catch {\n return c.json({ error: 'Body must be JSON' }, 400);\n }\n if (typeof message !== 'string' || message.length === 0) {\n return c.json({ error: 'Body must be { \"message\": string }' }, 400);\n }\n if (conversationId !== undefined && typeof conversationId !== 'string') {\n return c.json({ error: '\"conversationId\" must be a string' }, 400);\n }\n\n const state = await getChatState(name);\n let convo: Conversation | undefined;\n if (conversationId !== undefined) {\n convo = state.conversations.find((conv) => conv.id === conversationId);\n if (!convo) return c.json({ error: 'Unknown conversation' }, 404);\n } else {\n convo = {\n id: randomUUID(),\n title: message.slice(0, 80),\n updatedAt: new Date().toISOString(),\n };\n state.conversations.push(convo);\n await store.writeChatState(name, state);\n }\n const conversation = convo;\n\n let session: AISession;\n try {\n session = await getSession(name, running, conversation);\n } catch (err) {\n return c.json({ error: `Failed to start AI session: ${String(err)}` }, 502);\n }\n\n c.header('x-conversation-id', conversation.id);\n return streamSSE(c, async (stream) => {\n try {\n for await (const event of session.send(message)) {\n // One small write per turn: tracks the provider session and bumps recency.\n if (event.type === 'session') {\n conversation.sessionId = event.sessionId;\n conversation.updatedAt = new Date().toISOString();\n await store.writeChatState(name, state);\n }\n await stream.writeSSE({ event: event.type, data: JSON.stringify(event) });\n }\n } catch (err) {\n await stream.writeSSE({\n event: 'result',\n data: JSON.stringify({ type: 'result', ok: false, error: String(err) }),\n });\n }\n });\n });\n\n app.all('/app/:name/api/*', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n if (!running.handler) return c.json({ error: `App \"${name}\" has no routes` }, 404);\n return dispatch(running.handler, c.req.raw, `/app/${name}/api`);\n });\n\n // Relative asset URLs in app UIs only work with a trailing slash.\n app.get('/app/:name', (c) => c.redirect(`/app/${c.req.param('name')}/`));\n\n app.all('/app/:name/*', async (c) => {\n const name = c.req.param('name');\n const running = await getApp(name);\n if (!running) return c.json({ error: `App \"${name}\" is not installed` }, 404);\n\n if (c.req.method === 'GET' || c.req.method === 'HEAD') {\n const file = await serveStatic(name, running.manifest, c.req.path);\n if (file) return file;\n }\n if (running.handler) return dispatch(running.handler, c.req.raw, `/app/${name}`);\n return c.json({ error: 'Not found' }, 404);\n });\n\n return {\n app,\n scheduler,\n async close() {\n scheduler.stop();\n const open = [...sessions.values()];\n sessions.clear();\n await Promise.all(\n open.map(async (p) => {\n try {\n await (await p).close();\n } catch {\n // Session may already be gone; closing is best-effort.\n }\n }),\n );\n },\n };\n}\n","import { mkdir, readdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { APP_NAME_PATTERN, AppSdkError, type ConfigObject } from '@alashi/apps-sdk';\nimport type { AppSourceSpec } from './sources.js';\n\nexport interface RegistryRef {\n name: string;\n url: string;\n}\n\nexport interface GlobalConfig {\n registries: RegistryRef[];\n port?: number;\n /** Bind address for `alashi serve`; defaults to loopback (the dashboard has no auth). */\n host?: string;\n /** Per-app global overrides, keyed by app name (middle config layer). */\n apps?: Record<string, ConfigObject>;\n}\n\nexport interface Conversation {\n id: string;\n /** Provider session backing this conversation; set after the first turn. */\n sessionId?: string;\n title?: string;\n updatedAt: string;\n}\n\n/** Host-side chat state for an app; the transcripts themselves live with the AI provider. */\nexport interface ChatState {\n conversations: Conversation[];\n}\n\nexport interface JobRun {\n job: string;\n trigger: 'schedule' | 'catchup' | 'manual';\n startedAt: string;\n finishedAt: string;\n ok: boolean;\n error?: string;\n}\n\n/** Host-side job state for an app: last run start per job plus a capped run log. */\nexport interface JobState {\n lastRun: Record<string, string>;\n runs: JobRun[];\n}\n\nexport interface InstallRecord {\n name: string;\n version: string;\n /** The SDK range the app's manifest declared at install/update time. */\n sdkVersion?: string;\n source: AppSourceSpec;\n installedAt: string;\n updatedAt: string;\n}\n\nasync function readJson<T>(path: string): Promise<T | undefined> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return undefined;\n throw err;\n }\n}\n\nasync function writeJson(path: string, value: unknown): Promise<void> {\n await mkdir(join(path, '..'), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tmp, path);\n}\n\n/**\n * The portable state directory ($ALASHI_HOME, default ~/.alashi).\n * Everything the host knows lives under this root so it can be moved\n * between machines as a unit.\n */\nexport class Store {\n readonly root: string;\n\n constructor(root?: string) {\n this.root = root ?? process.env.ALASHI_HOME ?? join(homedir(), '.alashi');\n }\n\n get configPath(): string {\n return join(this.root, 'config.json');\n }\n\n get appsDir(): string {\n return join(this.root, 'apps');\n }\n\n get registriesDir(): string {\n return join(this.root, 'registries');\n }\n\n get stagingDir(): string {\n return join(this.root, '.staging');\n }\n\n /** Validates the name before using it in a path (traversal guard). */\n appDir(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid app name: ${JSON.stringify(name)}`);\n }\n return join(this.appsDir, name);\n }\n\n appRepoDir(name: string): string {\n return join(this.appDir(name), 'repo');\n }\n\n appDataDir(name: string): string {\n return join(this.appDir(name), 'data');\n }\n\n appConfigPath(name: string): string {\n return join(this.appDir(name), 'config.json');\n }\n\n appRecordPath(name: string): string {\n return join(this.appDir(name), 'app.json');\n }\n\n appChatStatePath(name: string): string {\n return join(this.appDir(name), 'chat.json');\n }\n\n appJobStatePath(name: string): string {\n return join(this.appDir(name), 'jobs.json');\n }\n\n registryCachePath(name: string): string {\n if (!APP_NAME_PATTERN.test(name)) {\n throw new AppSdkError(`Invalid registry name: ${JSON.stringify(name)}`);\n }\n return join(this.registriesDir, `${name}.json`);\n }\n\n async readGlobalConfig(): Promise<GlobalConfig> {\n return (await readJson<GlobalConfig>(this.configPath)) ?? { registries: [] };\n }\n\n async writeGlobalConfig(config: GlobalConfig): Promise<void> {\n await writeJson(this.configPath, config);\n }\n\n async readAppRecord(name: string): Promise<InstallRecord | undefined> {\n return readJson<InstallRecord>(this.appRecordPath(name));\n }\n\n async writeAppRecord(record: InstallRecord): Promise<void> {\n await writeJson(this.appRecordPath(record.name), record);\n }\n\n async readAppConfig(name: string): Promise<ConfigObject> {\n return (await readJson<ConfigObject>(this.appConfigPath(name))) ?? {};\n }\n\n async writeAppConfig(name: string, config: ConfigObject): Promise<void> {\n await writeJson(this.appConfigPath(name), config);\n }\n\n async readChatState(name: string): Promise<ChatState> {\n const state = await readJson<Partial<ChatState>>(this.appChatStatePath(name));\n return { conversations: state?.conversations ?? [] };\n }\n\n async writeChatState(name: string, state: ChatState): Promise<void> {\n await writeJson(this.appChatStatePath(name), state);\n }\n\n async readJobState(name: string): Promise<JobState> {\n const state = await readJson<Partial<JobState>>(this.appJobStatePath(name));\n return { lastRun: state?.lastRun ?? {}, runs: state?.runs ?? [] };\n }\n\n async writeJobState(name: string, state: JobState): Promise<void> {\n await writeJson(this.appJobStatePath(name), state);\n }\n\n async listInstalled(): Promise<InstallRecord[]> {\n let entries: string[];\n try {\n entries = await readdir(this.appsDir);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n const records: InstallRecord[] = [];\n for (const entry of entries.sort()) {\n if (!APP_NAME_PATTERN.test(entry)) continue;\n const record = await this.readAppRecord(entry);\n if (record) records.push(record);\n }\n return records;\n }\n}\n"],"mappings":"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"}