@alashi/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/v1/chat.js +296 -0
- package/assets/v1/system.css +532 -0
- package/dist/cli.mjs +8 -5
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +8 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{store-CwgDCAAx.mjs → store-Ckx0oLB6.mjs} +265 -43
- package/dist/store-Ckx0oLB6.mjs.map +1 -0
- package/package.json +4 -3
- package/templates/basic-app/ui/index.html +12 -281
- package/dist/store-CwgDCAAx.mjs.map +0 -1
|
@@ -5,7 +5,7 @@ import { randomUUID } from "node:crypto";
|
|
|
5
5
|
import { mkdir, readFile, readdir, rename, rm, symlink, writeFile } from "node:fs/promises";
|
|
6
6
|
import { extname, isAbsolute, join, resolve, sep } from "node:path";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { Hono } from "hono";
|
|
11
11
|
import { streamSSE } from "hono/streaming";
|
|
@@ -574,8 +574,10 @@ var Scheduler = class {
|
|
|
574
574
|
//#endregion
|
|
575
575
|
//#region src/settings-page.ts
|
|
576
576
|
/**
|
|
577
|
-
* Self-contained per-app settings page.
|
|
577
|
+
* Self-contained per-app settings page. Config data comes from
|
|
578
578
|
* GET /api/apps/:name/config at runtime; saving PUTs only changed keys.
|
|
579
|
+
* The read-only "App definition" section (instructions, agents, skills,
|
|
580
|
+
* allowed tools, MCP servers, jobs) comes from GET /api/apps/:name/manifest.
|
|
579
581
|
* The app name is injected via a JSON-encoded script constant, never into
|
|
580
582
|
* markup, so no escaping subtleties apply.
|
|
581
583
|
*/
|
|
@@ -586,42 +588,24 @@ function settingsPage(name) {
|
|
|
586
588
|
<meta charset="utf-8">
|
|
587
589
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
588
590
|
<title>Settings</title>
|
|
591
|
+
<link rel="stylesheet" href="/assets/v1/system.css">
|
|
589
592
|
<style>
|
|
590
|
-
|
|
591
|
-
* { box-sizing: border-box; }
|
|
592
|
-
body { margin: 0; font: 15px/1.5 system-ui, sans-serif; background: var(--bg); color: #1d1a24; }
|
|
593
|
-
header { background: #17141f; color: #fff; padding: 14px 24px; display: flex; align-items: center; gap: 12px; }
|
|
594
|
-
header a { color: #cbc4dd; text-decoration: none; }
|
|
595
|
-
main { max-width: 760px; margin: 24px auto; padding: 0 16px; }
|
|
596
|
-
.banner { border-radius: 8px; padding: 10px 14px; margin-bottom: 16px; font-weight: 500; }
|
|
597
|
-
.banner.ok { background: #e7f6ec; color: #16652f; }
|
|
598
|
-
.banner.warn { background: #fdeeee; color: #9c2121; }
|
|
599
|
-
.card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; margin-bottom: 16px; }
|
|
600
|
-
.card h2 { margin: 0; padding: 12px 16px; font-size: 15px; border-bottom: 1px solid var(--line);
|
|
601
|
-
display: flex; align-items: center; justify-content: space-between; }
|
|
602
|
-
.chip { font-size: 12px; font-weight: 600; border-radius: 999px; padding: 2px 10px; }
|
|
603
|
-
.chip.on { background: #e7f6ec; color: #16652f; }
|
|
604
|
-
.chip.off { background: #f1eef7; color: var(--muted); }
|
|
605
|
-
.card .body { padding: 12px 16px 16px; }
|
|
606
|
-
.field { margin-bottom: 12px; }
|
|
607
|
-
.field label { display: block; font-weight: 600; font-size: 13px; margin-bottom: 2px; }
|
|
608
|
-
.field .desc { font-size: 12.5px; color: var(--muted); margin-bottom: 4px; }
|
|
609
|
-
.field input { width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px;
|
|
610
|
-
font: inherit; }
|
|
611
|
-
.field input:focus { outline: 2px solid var(--accent); border-color: transparent; }
|
|
612
|
-
.req { color: #9c2121; }
|
|
613
|
-
button { background: var(--accent); color: #fff; border: 0; border-radius: 6px;
|
|
614
|
-
padding: 9px 18px; font: inherit; font-weight: 600; cursor: pointer; }
|
|
615
|
-
button:disabled { opacity: 0.5; cursor: default; }
|
|
593
|
+
.card { margin-bottom: 16px; }
|
|
616
594
|
#status { margin-left: 12px; color: var(--muted); }
|
|
595
|
+
#manifest-cards { margin-top: 32px; }
|
|
596
|
+
.ro-pre { white-space: pre-wrap; word-break: break-word; margin: 8px 0 0; }
|
|
597
|
+
.ro-item { margin-bottom: 12px; }
|
|
598
|
+
.ro-item:last-child { margin-bottom: 0; }
|
|
599
|
+
details summary { cursor: pointer; }
|
|
617
600
|
</style>
|
|
618
601
|
</head>
|
|
619
602
|
<body>
|
|
620
|
-
<header><a href="/">alashi</a> <span>/</span> <strong id="title"></strong> <span>settings</span></header>
|
|
621
|
-
<main>
|
|
603
|
+
<header class="topbar"><a href="/">alashi</a> <span>/</span> <strong id="title"></strong> <span>settings</span></header>
|
|
604
|
+
<main class="narrow">
|
|
622
605
|
<div id="banner" class="banner" hidden></div>
|
|
623
606
|
<div id="cards"></div>
|
|
624
|
-
<button id="save">Save changes</button><span id="status"></span>
|
|
607
|
+
<button id="save" class="primary">Save changes</button><span id="status"></span>
|
|
608
|
+
<div id="manifest-cards"></div>
|
|
625
609
|
</main>
|
|
626
610
|
<script>
|
|
627
611
|
const APP = ${JSON.stringify(name)};
|
|
@@ -650,8 +634,36 @@ function displayValue(value) {
|
|
|
650
634
|
return typeof value === 'string' ? value : value === undefined ? '' : JSON.stringify(value);
|
|
651
635
|
}
|
|
652
636
|
|
|
637
|
+
const ACRONYMS = new Set(['url', 'uri', 'api', 'id', 'ip', 'db', 'ssl', 'tls', 'ttl', 'http', 'https']);
|
|
638
|
+
|
|
639
|
+
function labelFor(key, decl) {
|
|
640
|
+
if (decl.label) return decl.label;
|
|
641
|
+
const words = key
|
|
642
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
643
|
+
.replace(/[_-]+/g, ' ')
|
|
644
|
+
.toLowerCase()
|
|
645
|
+
.split(' ');
|
|
646
|
+
return words
|
|
647
|
+
.map((w, i) => (ACRONYMS.has(w) ? w.toUpperCase() : i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w))
|
|
648
|
+
.join(' ');
|
|
649
|
+
}
|
|
650
|
+
|
|
653
651
|
function field(key, decl, data) {
|
|
654
652
|
const secret = decl.secret === true;
|
|
653
|
+
const isBool = !secret &&
|
|
654
|
+
(typeof decl.default === 'boolean' || typeof data.values[key] === 'boolean');
|
|
655
|
+
if (isBool) {
|
|
656
|
+
const input = el('input', {
|
|
657
|
+
type: 'checkbox',
|
|
658
|
+
onchange: () => state.dirty.set(key, { secret: false, input }),
|
|
659
|
+
});
|
|
660
|
+
input.checked = data.values[key] === true;
|
|
661
|
+
const labelChildren = [input, document.createTextNode(labelFor(key, decl))];
|
|
662
|
+
if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));
|
|
663
|
+
const parts = [el('label', { class: 'check' }, labelChildren)];
|
|
664
|
+
if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));
|
|
665
|
+
return el('div', { class: 'field' }, parts);
|
|
666
|
+
}
|
|
655
667
|
const isSet = secret ? data.secretsSet.includes(key) : data.values[key] !== undefined;
|
|
656
668
|
const input = el('input', {
|
|
657
669
|
type: secret ? 'password' : 'text',
|
|
@@ -659,7 +671,7 @@ function field(key, decl, data) {
|
|
|
659
671
|
placeholder: secret && isSet ? '(set — type to replace)' : '',
|
|
660
672
|
oninput: () => state.dirty.set(key, { secret, input }),
|
|
661
673
|
});
|
|
662
|
-
const labelChildren = [document.createTextNode(key)];
|
|
674
|
+
const labelChildren = [document.createTextNode(labelFor(key, decl))];
|
|
663
675
|
if (decl.required) labelChildren.push(el('span', { class: 'req', text: ' *' }));
|
|
664
676
|
const parts = [el('label', {}, labelChildren)];
|
|
665
677
|
if (decl.description) parts.push(el('div', { class: 'desc', text: decl.description }));
|
|
@@ -694,12 +706,27 @@ function render(data) {
|
|
|
694
706
|
const cards = document.getElementById('cards');
|
|
695
707
|
cards.replaceChildren();
|
|
696
708
|
const integrationKeys = new Set(data.integrations.flatMap((i) => i.configKeys));
|
|
697
|
-
|
|
698
|
-
|
|
709
|
+
|
|
710
|
+
// Non-integration keys, sectioned by their declared group ("General" first).
|
|
711
|
+
const sections = new Map();
|
|
712
|
+
for (const key of Object.keys(data.schema)) {
|
|
713
|
+
if (integrationKeys.has(key)) continue;
|
|
714
|
+
const title = data.schema[key].group ?? 'General';
|
|
715
|
+
if (!sections.has(title)) sections.set(title, []);
|
|
716
|
+
sections.get(title).push(key);
|
|
717
|
+
}
|
|
718
|
+
if (sections.has('General')) cards.append(card('General', sections.get('General'), data, undefined));
|
|
719
|
+
for (const [title, keys] of sections) {
|
|
720
|
+
if (title !== 'General') cards.append(card(title, keys, data, undefined));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Integration cards, titled by the keys' shared group when declared.
|
|
699
724
|
for (const integration of data.integrations) {
|
|
700
725
|
if (integration.configKeys.length === 0) continue;
|
|
726
|
+
const groups = new Set(integration.configKeys.map((k) => data.schema[k]?.group).filter(Boolean));
|
|
727
|
+
const title = groups.size === 1 ? [...groups][0] : integration.name;
|
|
701
728
|
cards.append(card(
|
|
702
|
-
|
|
729
|
+
title + (integration.optional ? ' (optional integration)' : ''),
|
|
703
730
|
integration.configKeys, data, integration.configured,
|
|
704
731
|
));
|
|
705
732
|
}
|
|
@@ -708,8 +735,83 @@ function render(data) {
|
|
|
708
735
|
}
|
|
709
736
|
}
|
|
710
737
|
|
|
738
|
+
function roCard(title, children) {
|
|
739
|
+
return el('div', { class: 'card' }, [
|
|
740
|
+
el('h2', { text: title }),
|
|
741
|
+
el('div', { class: 'body' }, children),
|
|
742
|
+
]);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function renderManifest(m) {
|
|
746
|
+
const root = document.getElementById('manifest-cards');
|
|
747
|
+
root.replaceChildren();
|
|
748
|
+
const cards = [];
|
|
749
|
+
|
|
750
|
+
const about = [el('div', { class: 'desc', text: 'Version ' + m.version + (m.model ? ' · model: ' + m.model : '') })];
|
|
751
|
+
if (m.description) about.unshift(el('div', { text: m.description }));
|
|
752
|
+
cards.push(roCard('About', about));
|
|
753
|
+
|
|
754
|
+
if (m.instructions) {
|
|
755
|
+
cards.push(roCard('Instructions', [el('pre', { class: 'ro-pre', text: m.instructions })]));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const agentNames = Object.keys(m.agents);
|
|
759
|
+
if (agentNames.length > 0) {
|
|
760
|
+
cards.push(roCard('Agents', agentNames.map((name) => {
|
|
761
|
+
const a = m.agents[name];
|
|
762
|
+
const meta = [a.model && 'model: ' + a.model, a.tools && 'tools: ' + a.tools.join(', ')]
|
|
763
|
+
.filter(Boolean).join(' · ');
|
|
764
|
+
const body = meta ? [el('div', { class: 'desc', text: meta })] : [];
|
|
765
|
+
body.push(el('pre', { class: 'ro-pre', text: a.prompt }));
|
|
766
|
+
return el('details', { class: 'ro-item' }, [
|
|
767
|
+
el('summary', {}, [el('strong', { text: name }), document.createTextNode(' — ' + a.description)]),
|
|
768
|
+
...body,
|
|
769
|
+
]);
|
|
770
|
+
})));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (m.skills.length > 0) {
|
|
774
|
+
cards.push(roCard('Skills', m.skills.map((s) => el('div', { class: 'ro-item' }, [
|
|
775
|
+
el('code', { text: s.dir + '/' }),
|
|
776
|
+
el('div', { class: 'desc', text: s.entries.length ? s.entries.join(', ') : '(directory not found or empty)' }),
|
|
777
|
+
]))));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (m.mcpServers.length > 0) {
|
|
781
|
+
cards.push(roCard('MCP servers', m.mcpServers.map((s) => el('div', { class: 'ro-item' }, [
|
|
782
|
+
el('strong', { text: s.name }),
|
|
783
|
+
document.createTextNode(' (' + s.type + (s.optional ? ', optional' : '') + ') '),
|
|
784
|
+
el('span', { class: 'chip ' + (s.configured ? 'on' : 'off'),
|
|
785
|
+
text: s.configured ? 'configured' : 'not configured' }),
|
|
786
|
+
el('div', { class: 'desc', text: s.type === 'stdio' ? s.command : s.url }),
|
|
787
|
+
]))));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (m.allowedTools.length > 0) {
|
|
791
|
+
cards.push(roCard('Allowed tools', m.allowedTools.map((t) =>
|
|
792
|
+
el('div', {}, [el('code', { text: t })]))));
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (m.jobs.length > 0) {
|
|
796
|
+
cards.push(roCard('Jobs', m.jobs.map((j) => el('div', { class: 'ro-item' }, [
|
|
797
|
+
el('strong', { text: j.name }),
|
|
798
|
+
el('code', { text: ' ' + j.schedule }),
|
|
799
|
+
...(j.description ? [el('div', { class: 'desc', text: j.description })] : []),
|
|
800
|
+
]))));
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
root.append(
|
|
804
|
+
el('h1', { text: 'App definition' }),
|
|
805
|
+
el('p', { class: 'subtitle', text: 'Declared by the app; read-only.' }),
|
|
806
|
+
...cards,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
711
810
|
async function load() {
|
|
712
|
-
const res = await
|
|
811
|
+
const [res, manRes] = await Promise.all([
|
|
812
|
+
fetch('/api/apps/' + APP + '/config'),
|
|
813
|
+
fetch('/api/apps/' + APP + '/manifest'),
|
|
814
|
+
]);
|
|
713
815
|
if (!res.ok) {
|
|
714
816
|
document.getElementById('banner').hidden = false;
|
|
715
817
|
document.getElementById('banner').className = 'banner warn';
|
|
@@ -717,13 +819,14 @@ async function load() {
|
|
|
717
819
|
return;
|
|
718
820
|
}
|
|
719
821
|
render(await res.json());
|
|
822
|
+
if (manRes.ok) renderManifest(await manRes.json());
|
|
720
823
|
}
|
|
721
824
|
|
|
722
825
|
document.getElementById('save').addEventListener('click', async () => {
|
|
723
826
|
const body = {};
|
|
724
827
|
for (const [key, { secret, input }] of state.dirty) {
|
|
725
828
|
if (secret && input.value === '') continue; // untouched secret: keep as-is
|
|
726
|
-
body[key] = parseInput(input.value);
|
|
829
|
+
body[key] = input.type === 'checkbox' ? input.checked : parseInput(input.value);
|
|
727
830
|
}
|
|
728
831
|
if (Object.keys(body).length === 0) return;
|
|
729
832
|
const status = document.getElementById('status');
|
|
@@ -768,9 +871,15 @@ function escapeHtml(value) {
|
|
|
768
871
|
* (omit conversationId to start a new conversation; the
|
|
769
872
|
* id is echoed in the x-conversation-id response header)
|
|
770
873
|
* GET /api/apps/:name/chat/conversations conversations, most recent first
|
|
874
|
+
* POST /api/apps/:name/chat/conversations { sessionId?, title? } -> create a conversation,
|
|
875
|
+
* optionally adopting an existing provider session (e.g. an
|
|
876
|
+
* app-run investigation) so chat continues where it left off
|
|
771
877
|
* GET /api/apps/:name/chat/:conversationId/history past turns from the provider transcript
|
|
772
878
|
* GET /api/apps/:name/jobs manifest-declared jobs with schedule/status
|
|
773
879
|
* POST /api/apps/:name/jobs/:job/run trigger a job now (409 while already running)
|
|
880
|
+
* GET /api/apps/:name/manifest read-only app definition: instructions, agents,
|
|
881
|
+
* skills (with dir contents), allowed tools,
|
|
882
|
+
* MCP servers (with configured state), jobs, model
|
|
774
883
|
* GET /api/apps/:name/config declared config schema + merged values
|
|
775
884
|
* (secrets masked) + readiness/integrations
|
|
776
885
|
* PUT /api/apps/:name/config merge keys into the per-app config layer
|
|
@@ -868,6 +977,30 @@ function createServer(store, provider) {
|
|
|
868
977
|
}
|
|
869
978
|
const app = new Hono();
|
|
870
979
|
app.onError((err, c) => c.json({ error: String(err) }, 500));
|
|
980
|
+
const assetsDir = fileURLToPath(new URL("../assets", import.meta.url));
|
|
981
|
+
app.get("/assets/*", async (c) => {
|
|
982
|
+
let rest;
|
|
983
|
+
try {
|
|
984
|
+
rest = decodeURIComponent(c.req.path.slice(8));
|
|
985
|
+
} catch {
|
|
986
|
+
return c.json({ error: "Not found" }, 404);
|
|
987
|
+
}
|
|
988
|
+
const filePath = resolve(assetsDir, rest);
|
|
989
|
+
if (!filePath.startsWith(assetsDir + sep)) return c.json({ error: "Not found" }, 404);
|
|
990
|
+
try {
|
|
991
|
+
const body = await readFile(filePath);
|
|
992
|
+
const type = CONTENT_TYPES[extname(filePath)] ?? "application/octet-stream";
|
|
993
|
+
return new Response(new Uint8Array(body), {
|
|
994
|
+
status: 200,
|
|
995
|
+
headers: {
|
|
996
|
+
"content-type": type,
|
|
997
|
+
"cache-control": "public, max-age=300"
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
} catch {
|
|
1001
|
+
return c.json({ error: "Not found" }, 404);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
871
1004
|
/** Readiness for the dashboard badge; undefined when the app fails to load. */
|
|
872
1005
|
async function readiness(name) {
|
|
873
1006
|
try {
|
|
@@ -883,15 +1016,38 @@ function createServer(store, provider) {
|
|
|
883
1016
|
const rows = (await Promise.all(records.map(async (r) => {
|
|
884
1017
|
const name = escapeHtml(r.name);
|
|
885
1018
|
const status = await readiness(r.name);
|
|
886
|
-
const badge = status
|
|
887
|
-
return `<
|
|
1019
|
+
const badge = !status ? "<span class=\"badge err\">failed to load</span>" : status.ready ? "<span class=\"badge ok\">ready</span>" : `<a href="/settings/${name}"><span class="badge err">needs setup</span></a>`;
|
|
1020
|
+
return `<tr>
|
|
1021
|
+
<td><a href="/app/${name}/">${name}</a></td>
|
|
1022
|
+
<td class="muted">v${escapeHtml(r.version)}</td>
|
|
1023
|
+
<td class="muted">${escapeHtml(r.source.type)}</td>
|
|
1024
|
+
<td>${badge}</td>
|
|
1025
|
+
<td style="text-align:right"><a href="/settings/${name}">settings</a></td>
|
|
1026
|
+
</tr>`;
|
|
888
1027
|
}))).join("\n");
|
|
1028
|
+
const body = records.length === 0 ? "<div class=\"empty\">No apps installed. Run <code>alashi install <name></code>.</div>" : `<table>
|
|
1029
|
+
<thead><tr><th>App</th><th>Version</th><th>Source</th><th>Status</th><th></th></tr></thead>
|
|
1030
|
+
<tbody>
|
|
1031
|
+
${rows}
|
|
1032
|
+
</tbody>
|
|
1033
|
+
</table>`;
|
|
889
1034
|
return c.html(`<!doctype html>
|
|
890
1035
|
<html lang="en">
|
|
891
|
-
<head
|
|
1036
|
+
<head>
|
|
1037
|
+
<meta charset="utf-8">
|
|
1038
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1039
|
+
<title>alashi</title>
|
|
1040
|
+
<link rel="stylesheet" href="/assets/v1/system.css">
|
|
1041
|
+
</head>
|
|
892
1042
|
<body>
|
|
893
|
-
<
|
|
894
|
-
|
|
1043
|
+
<header class="topbar"><strong>alashi</strong></header>
|
|
1044
|
+
<main class="narrow">
|
|
1045
|
+
<h1>Apps</h1>
|
|
1046
|
+
<p class="subtitle">Installed apps and their status.</p>
|
|
1047
|
+
<div class="panel">
|
|
1048
|
+
${body}
|
|
1049
|
+
</div>
|
|
1050
|
+
</main>
|
|
895
1051
|
</body>
|
|
896
1052
|
</html>
|
|
897
1053
|
`);
|
|
@@ -908,6 +1064,29 @@ ${records.length === 0 ? "<p>No apps installed. Run <code>alashi install <nam
|
|
|
908
1064
|
const { conversations } = await getChatState(name);
|
|
909
1065
|
return c.json({ conversations: [...conversations].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)) });
|
|
910
1066
|
});
|
|
1067
|
+
app.post("/api/apps/:name/chat/conversations", async (c) => {
|
|
1068
|
+
const name = c.req.param("name");
|
|
1069
|
+
if (!await getApp(name)) return c.json({ error: `App "${name}" is not installed` }, 404);
|
|
1070
|
+
let sessionId;
|
|
1071
|
+
let title;
|
|
1072
|
+
try {
|
|
1073
|
+
({sessionId, title} = await c.req.json());
|
|
1074
|
+
} catch {
|
|
1075
|
+
return c.json({ error: "Body must be JSON" }, 400);
|
|
1076
|
+
}
|
|
1077
|
+
if (sessionId !== void 0 && (typeof sessionId !== "string" || sessionId.length === 0)) return c.json({ error: "\"sessionId\" must be a non-empty string" }, 400);
|
|
1078
|
+
if (title !== void 0 && typeof title !== "string") return c.json({ error: "\"title\" must be a string" }, 400);
|
|
1079
|
+
const state = await getChatState(name);
|
|
1080
|
+
const convo = {
|
|
1081
|
+
id: randomUUID(),
|
|
1082
|
+
title: (title ?? "").slice(0, 80) || "New conversation",
|
|
1083
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1084
|
+
...sessionId !== void 0 && { sessionId }
|
|
1085
|
+
};
|
|
1086
|
+
state.conversations.push(convo);
|
|
1087
|
+
await store.writeChatState(name, state);
|
|
1088
|
+
return c.json({ conversation: convo }, 201);
|
|
1089
|
+
});
|
|
911
1090
|
app.get("/api/apps/:name/chat/:conversationId/history", async (c) => {
|
|
912
1091
|
const name = c.req.param("name");
|
|
913
1092
|
if (!await getApp(name)) return c.json({ error: `App "${name}" is not installed` }, 404);
|
|
@@ -927,6 +1106,49 @@ ${records.length === 0 ? "<p>No apps installed. Run <code>alashi install <nam
|
|
|
927
1106
|
session.then((s) => s.close()).catch(() => void 0);
|
|
928
1107
|
}
|
|
929
1108
|
}
|
|
1109
|
+
app.get("/api/apps/:name/manifest", async (c) => {
|
|
1110
|
+
const name = c.req.param("name");
|
|
1111
|
+
const running = await getApp(name);
|
|
1112
|
+
if (!running) return c.json({ error: `App "${name}" is not installed` }, 404);
|
|
1113
|
+
const { manifest } = running;
|
|
1114
|
+
const { merged, status } = await resolveAppConfig(store, manifest);
|
|
1115
|
+
const repoDir = store.appRepoDir(name);
|
|
1116
|
+
const skills = await Promise.all(manifest.skills.map(async (dir) => {
|
|
1117
|
+
let entries = [];
|
|
1118
|
+
try {
|
|
1119
|
+
entries = (await readdir(resolve(repoDir, dir))).sort();
|
|
1120
|
+
} catch {}
|
|
1121
|
+
return {
|
|
1122
|
+
dir,
|
|
1123
|
+
entries
|
|
1124
|
+
};
|
|
1125
|
+
}));
|
|
1126
|
+
const configured = new Map(status.integrations.map((i) => [i.name, i.configured]));
|
|
1127
|
+
const mcpServers = Object.entries(manifest.mcpServers).map(([server, decl]) => ({
|
|
1128
|
+
name: server,
|
|
1129
|
+
..."command" in decl ? {
|
|
1130
|
+
type: "stdio",
|
|
1131
|
+
command: [decl.command, ...decl.args ?? []].join(" ")
|
|
1132
|
+
} : {
|
|
1133
|
+
type: decl.type,
|
|
1134
|
+
url: decl.url
|
|
1135
|
+
},
|
|
1136
|
+
optional: decl.optional === true,
|
|
1137
|
+
configured: configured.get(server) ?? true
|
|
1138
|
+
}));
|
|
1139
|
+
return c.json({
|
|
1140
|
+
name: manifest.name,
|
|
1141
|
+
version: manifest.version,
|
|
1142
|
+
...manifest.description !== void 0 && { description: manifest.description },
|
|
1143
|
+
...manifest.instructions !== void 0 && { instructions: manifest.instructions },
|
|
1144
|
+
...typeof merged.model === "string" && { model: merged.model },
|
|
1145
|
+
agents: manifest.agents,
|
|
1146
|
+
skills,
|
|
1147
|
+
allowedTools: manifest.permissions.allowedTools,
|
|
1148
|
+
mcpServers,
|
|
1149
|
+
jobs: manifest.jobs
|
|
1150
|
+
});
|
|
1151
|
+
});
|
|
930
1152
|
app.get("/api/apps/:name/config", async (c) => {
|
|
931
1153
|
const name = c.req.param("name");
|
|
932
1154
|
const running = await getApp(name);
|
|
@@ -1198,4 +1420,4 @@ var Store = class {
|
|
|
1198
1420
|
//#endregion
|
|
1199
1421
|
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 };
|
|
1200
1422
|
|
|
1201
|
-
//# sourceMappingURL=store-
|
|
1423
|
+
//# sourceMappingURL=store-Ckx0oLB6.mjs.map
|