@abyssinia-labs/sokie 0.0.0-stage → 0.1.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/dist/inspector/favicon.svg +9 -0
- package/dist/inspector/index.html +61 -0
- package/dist/inspector/inspector.css +203 -0
- package/dist/inspector/inspector.js +271 -0
- package/dist/inspector/tokens.css +154 -0
- package/dist/inspector/tool.css +141 -0
- package/dist/sokie.mjs +66727 -0
- package/package.json +54 -4
- package/README.md +0 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<title>Sokie</title>
|
|
3
|
+
<style>.tile{fill:#0a0a0a}.visor{fill:#ffffff}.nose{fill:#22c55e}@media (prefers-color-scheme: dark){.tile{fill:#ffffff}.visor{fill:#000000}.nose{fill:#4ade80}}</style>
|
|
4
|
+
<rect class="tile" x="2" y="2" width="60" height="60" rx="15"/>
|
|
5
|
+
<rect class="visor" x="9" y="15" width="46" height="22" rx="11"/>
|
|
6
|
+
<rect class="tile" x="20" y="22" width="6" height="8" rx="3"/>
|
|
7
|
+
<rect class="tile" x="38" y="22" width="6" height="8" rx="3"/>
|
|
8
|
+
<circle class="nose" cx="32" cy="48" r="4"/>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>sokie dev</title>
|
|
7
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
8
|
+
<link rel="stylesheet" href="/tokens.css" />
|
|
9
|
+
<link rel="stylesheet" href="/inspector.css" />
|
|
10
|
+
<link rel="stylesheet" href="/tool.css" />
|
|
11
|
+
<script type="module" src="/inspector.js"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header class="bar">
|
|
15
|
+
<div class="identity">
|
|
16
|
+
<h1 id="server-name">Starting…</h1>
|
|
17
|
+
<p id="server-url" class="url" translate="no"></p>
|
|
18
|
+
</div>
|
|
19
|
+
<p id="status" class="status" role="status" aria-live="polite">Building the server</p>
|
|
20
|
+
</header>
|
|
21
|
+
|
|
22
|
+
<section id="findings" class="findings" hidden aria-label="Build findings"></section>
|
|
23
|
+
|
|
24
|
+
<main class="layout">
|
|
25
|
+
<nav class="catalog" aria-label="Catalog">
|
|
26
|
+
<h2>Tools <span id="tool-count" class="count"></span></h2>
|
|
27
|
+
<ul id="tools" class="items"></ul>
|
|
28
|
+
<h2>Prompts <span id="prompt-count" class="count"></span></h2>
|
|
29
|
+
<ul id="prompts" class="items quiet"></ul>
|
|
30
|
+
<h2>Resources <span id="resource-count" class="count"></span></h2>
|
|
31
|
+
<ul id="resources" class="items quiet"></ul>
|
|
32
|
+
</nav>
|
|
33
|
+
|
|
34
|
+
<article id="detail" class="detail">
|
|
35
|
+
<p class="empty">Choose a tool to read its description and call it.</p>
|
|
36
|
+
</article>
|
|
37
|
+
</main>
|
|
38
|
+
|
|
39
|
+
<template id="tool-detail">
|
|
40
|
+
<header class="tool-head">
|
|
41
|
+
<h2 class="tool-title">Tool</h2>
|
|
42
|
+
<code class="tool-name" translate="no"></code>
|
|
43
|
+
<ul class="hints" aria-label="Behaviour"></ul>
|
|
44
|
+
</header>
|
|
45
|
+
<p class="description"></p>
|
|
46
|
+
<form class="call">
|
|
47
|
+
<label for="arguments">Arguments</label>
|
|
48
|
+
<textarea id="arguments" name="arguments" rows="8" spellcheck="false" autocomplete="off"></textarea>
|
|
49
|
+
<p class="schema-note"></p>
|
|
50
|
+
<div class="actions">
|
|
51
|
+
<button type="submit" class="primary">Call tool</button>
|
|
52
|
+
<kbd>⌘ Enter</kbd>
|
|
53
|
+
</div>
|
|
54
|
+
</form>
|
|
55
|
+
<section class="result" hidden aria-live="polite">
|
|
56
|
+
<h3 class="result-head">Result</h3>
|
|
57
|
+
<pre class="result-body"></pre>
|
|
58
|
+
</section>
|
|
59
|
+
</template>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/* The sokie dev inspector. Every value comes from tokens.css (packages/tokens). */
|
|
2
|
+
|
|
3
|
+
* {
|
|
4
|
+
box-sizing: border-box;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
background: var(--sokie-ground);
|
|
10
|
+
color: var(--sokie-body);
|
|
11
|
+
font-family: var(--sokie-font-sans);
|
|
12
|
+
font-size: 15px;
|
|
13
|
+
line-height: 1.5;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
h1,
|
|
17
|
+
h2,
|
|
18
|
+
h3 {
|
|
19
|
+
color: var(--sokie-text);
|
|
20
|
+
margin: 0;
|
|
21
|
+
letter-spacing: var(--sokie-tracking-tight);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
code,
|
|
25
|
+
pre,
|
|
26
|
+
textarea,
|
|
27
|
+
kbd {
|
|
28
|
+
font-family: var(--sokie-font-mono);
|
|
29
|
+
font-size: 13px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.bar {
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-wrap: wrap;
|
|
35
|
+
gap: 8px 24px;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: space-between;
|
|
38
|
+
padding: 16px 24px;
|
|
39
|
+
background: var(--sokie-chrome);
|
|
40
|
+
border-bottom: 1px solid var(--sokie-border);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.bar h1 {
|
|
44
|
+
font-size: 20px;
|
|
45
|
+
font-weight: var(--sokie-weight-bold);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.url {
|
|
49
|
+
margin: 0;
|
|
50
|
+
color: var(--sokie-muted);
|
|
51
|
+
font-family: var(--sokie-font-mono);
|
|
52
|
+
font-size: 13px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.status {
|
|
56
|
+
margin: 0;
|
|
57
|
+
font-variant-numeric: tabular-nums;
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
gap: 8px;
|
|
61
|
+
color: var(--sokie-body);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.status::before {
|
|
65
|
+
content: "";
|
|
66
|
+
width: 8px;
|
|
67
|
+
height: 8px;
|
|
68
|
+
border-radius: 50%;
|
|
69
|
+
background: var(--sokie-muted);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.status[data-state="running"]::before {
|
|
73
|
+
background: var(--sokie-live);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.status[data-state="failed"]::before {
|
|
77
|
+
background: var(--sokie-danger);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.findings {
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: 12px 24px;
|
|
83
|
+
background: var(--sokie-danger-soft);
|
|
84
|
+
border-bottom: 1px solid var(--sokie-border);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.findings[data-level="warning"] {
|
|
88
|
+
background: var(--sokie-nested);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.findings p {
|
|
92
|
+
margin: 0;
|
|
93
|
+
font-family: var(--sokie-font-mono);
|
|
94
|
+
font-size: 13px;
|
|
95
|
+
color: var(--sokie-text);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.layout {
|
|
99
|
+
display: grid;
|
|
100
|
+
grid-template-columns: minmax(220px, 300px) 1fr;
|
|
101
|
+
min-height: calc(100vh - 72px);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.catalog {
|
|
105
|
+
padding: 16px 12px;
|
|
106
|
+
border-right: 1px solid var(--sokie-border);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.catalog h2 {
|
|
110
|
+
font-size: 14px;
|
|
111
|
+
font-weight: var(--sokie-weight-semibold);
|
|
112
|
+
padding: 12px 12px 6px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.count {
|
|
116
|
+
color: var(--sokie-muted);
|
|
117
|
+
font-weight: var(--sokie-weight-regular);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.items {
|
|
121
|
+
list-style: none;
|
|
122
|
+
margin: 0;
|
|
123
|
+
padding: 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.items button {
|
|
127
|
+
display: block;
|
|
128
|
+
width: 100%;
|
|
129
|
+
text-align: left;
|
|
130
|
+
padding: 8px 12px;
|
|
131
|
+
border: 0;
|
|
132
|
+
border-radius: var(--sokie-radius-md);
|
|
133
|
+
background: transparent;
|
|
134
|
+
color: var(--sokie-text);
|
|
135
|
+
font: inherit;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
touch-action: manipulation;
|
|
138
|
+
transition: background-color var(--sokie-duration-fast) var(--sokie-ease-out);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.items button:hover {
|
|
142
|
+
background: var(--sokie-nested);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.items button[aria-current="true"] {
|
|
146
|
+
background: var(--sokie-nested);
|
|
147
|
+
box-shadow: inset 2px 0 0 var(--sokie-primary);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.items .sub {
|
|
151
|
+
display: block;
|
|
152
|
+
color: var(--sokie-muted);
|
|
153
|
+
font-family: var(--sokie-font-mono);
|
|
154
|
+
font-size: 12px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.quiet li {
|
|
158
|
+
padding: 6px 12px;
|
|
159
|
+
color: var(--sokie-text);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* After a reload, what changed says so once: the save you just made, landing. */
|
|
163
|
+
.items .changed {
|
|
164
|
+
animation: landed var(--sokie-persona-live) var(--sokie-ease-out);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@keyframes landed {
|
|
168
|
+
from {
|
|
169
|
+
background: var(--sokie-live-soft);
|
|
170
|
+
box-shadow: inset 2px 0 0 var(--sokie-live);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:focus-visible {
|
|
175
|
+
outline: 2px solid var(--sokie-primary);
|
|
176
|
+
outline-offset: 2px;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@media (max-width: 720px) {
|
|
180
|
+
.layout {
|
|
181
|
+
grid-template-columns: 1fr;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.catalog {
|
|
185
|
+
border-right: 0;
|
|
186
|
+
border-bottom: 1px solid var(--sokie-border);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.detail {
|
|
190
|
+
padding: 20px 16px;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@media (prefers-reduced-motion: reduce) {
|
|
195
|
+
.items .changed,
|
|
196
|
+
.description.changed {
|
|
197
|
+
animation: none;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.items button {
|
|
201
|
+
transition: none;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// The sokie dev inspector page. It follows the dev session over server-sent
|
|
2
|
+
// events and re-lists the catalog whenever a reload lands, marking what
|
|
3
|
+
// changed. Calls go through the dev process (/api/call), which talks MCP to
|
|
4
|
+
// the running server like any client.
|
|
5
|
+
|
|
6
|
+
const $ = (id) => document.getElementById(id);
|
|
7
|
+
const mac = /Mac|iPhone|iPad/.test(navigator.platform);
|
|
8
|
+
|
|
9
|
+
let catalog = { tools: [], prompts: [], resources: [] };
|
|
10
|
+
let selected = null;
|
|
11
|
+
let revision = 0;
|
|
12
|
+
const drafts = new Map();
|
|
13
|
+
const results = new Map();
|
|
14
|
+
|
|
15
|
+
const HINTS = [
|
|
16
|
+
["readOnlyHint", "Read only", ""],
|
|
17
|
+
["destructiveHint", "Destructive", "destructive"],
|
|
18
|
+
["idempotentHint", "Idempotent", ""],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function fingerprint(tool) {
|
|
22
|
+
return JSON.stringify([tool.title, tool.description, tool.inputSchema, tool.annotations]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A starting point for the arguments: every required property, empty of its type. */
|
|
26
|
+
function skeletonOf(schema) {
|
|
27
|
+
const props = schema?.properties ?? {};
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const name of schema?.required ?? []) {
|
|
30
|
+
const type = props[name]?.type;
|
|
31
|
+
out[name] =
|
|
32
|
+
type === "number" || type === "integer"
|
|
33
|
+
? 0
|
|
34
|
+
: type === "boolean"
|
|
35
|
+
? false
|
|
36
|
+
: type === "array"
|
|
37
|
+
? []
|
|
38
|
+
: type === "object"
|
|
39
|
+
? skeletonOf(props[name])
|
|
40
|
+
: "";
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const skeleton = (schema) => JSON.stringify(skeletonOf(schema), null, 2);
|
|
46
|
+
|
|
47
|
+
/** Joins the hard-wrapped lines of a tool file into paragraphs; blank lines still part them. */
|
|
48
|
+
function reflow(text) {
|
|
49
|
+
return text
|
|
50
|
+
.split(/\n{2,}/)
|
|
51
|
+
.map((paragraph) => paragraph.replace(/\s*\n\s*/g, " "))
|
|
52
|
+
.join("\n\n");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function schemaNote(schema) {
|
|
56
|
+
const names = Object.keys(schema?.properties ?? {});
|
|
57
|
+
if (names.length === 0) return "This tool takes no arguments.";
|
|
58
|
+
const required = new Set(schema.required ?? []);
|
|
59
|
+
const optional = names.filter((n) => !required.has(n));
|
|
60
|
+
return optional.length > 0 ? `Optional: ${optional.join(", ")}.` : "Every argument is required.";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function renderStatus(state) {
|
|
64
|
+
const status = $("status");
|
|
65
|
+
status.dataset.state = state.status;
|
|
66
|
+
if (state.name) $("server-name").textContent = state.name;
|
|
67
|
+
$("server-url").textContent = `${state.serverUrl}/mcp`;
|
|
68
|
+
if (state.status === "running") {
|
|
69
|
+
const at = new Date(state.reloadedAt).toLocaleTimeString();
|
|
70
|
+
status.textContent = `Live, ${state.accessMode}. Reloaded at ${at} in ${state.reloadMs} ms`;
|
|
71
|
+
} else if (state.status === "failed") {
|
|
72
|
+
status.textContent =
|
|
73
|
+
state.revision > 0 ? "Build failed. The last good build is still serving" : "Build failed";
|
|
74
|
+
}
|
|
75
|
+
renderFindings(state);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderFindings(state) {
|
|
79
|
+
const box = $("findings");
|
|
80
|
+
const notes = state.findings.filter((f) => f.level !== "info");
|
|
81
|
+
const secrets = state.secrets;
|
|
82
|
+
const lines = [
|
|
83
|
+
...notes.map((f) => `${f.file || "."}: ${f.message}`),
|
|
84
|
+
...(secrets?.missing ?? []).map((s) => `${s.name} has no value in .sokie/dev.env.`),
|
|
85
|
+
...(secrets?.standInsMissing ?? []).map((name) => `${name} is not set in .sokie/dev.env.`),
|
|
86
|
+
];
|
|
87
|
+
box.replaceChildren(
|
|
88
|
+
...lines.map((text) => Object.assign(document.createElement("p"), { textContent: text })),
|
|
89
|
+
);
|
|
90
|
+
box.dataset.level = notes.some((f) => f.level === "error") ? "error" : "warning";
|
|
91
|
+
box.hidden = lines.length === 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderList(changed) {
|
|
95
|
+
$("tool-count").textContent = String(catalog.tools.length);
|
|
96
|
+
$("prompt-count").textContent = String(catalog.prompts.length);
|
|
97
|
+
$("resource-count").textContent = String(catalog.resources.length);
|
|
98
|
+
$("tools").replaceChildren(
|
|
99
|
+
...catalog.tools.map((tool) => {
|
|
100
|
+
const button = document.createElement("button");
|
|
101
|
+
button.type = "button";
|
|
102
|
+
button.setAttribute("aria-current", String(tool.name === selected));
|
|
103
|
+
button.append(tool.title ?? tool.name);
|
|
104
|
+
if (tool.title) {
|
|
105
|
+
button.append(
|
|
106
|
+
Object.assign(document.createElement("span"), {
|
|
107
|
+
className: "sub",
|
|
108
|
+
textContent: tool.name,
|
|
109
|
+
translate: false,
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (changed.has(tool.name)) button.classList.add("changed");
|
|
114
|
+
button.addEventListener("click", () => select(tool.name));
|
|
115
|
+
const item = document.createElement("li");
|
|
116
|
+
item.append(button);
|
|
117
|
+
return item;
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
const quiet = (items, describe) =>
|
|
121
|
+
items.map((item) => {
|
|
122
|
+
const li = document.createElement("li");
|
|
123
|
+
li.append(item.title ?? item.name);
|
|
124
|
+
li.append(
|
|
125
|
+
Object.assign(document.createElement("span"), {
|
|
126
|
+
className: "sub",
|
|
127
|
+
textContent: describe(item),
|
|
128
|
+
translate: false,
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
return li;
|
|
132
|
+
});
|
|
133
|
+
$("prompts").replaceChildren(...quiet(catalog.prompts, (p) => p.name));
|
|
134
|
+
$("resources").replaceChildren(...quiet(catalog.resources, (r) => r.uri));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderDetail(changed) {
|
|
138
|
+
const tool = catalog.tools.find((t) => t.name === selected);
|
|
139
|
+
const detail = $("detail");
|
|
140
|
+
if (!tool) {
|
|
141
|
+
const empty = Object.assign(document.createElement("p"), { className: "empty" });
|
|
142
|
+
empty.textContent =
|
|
143
|
+
catalog.tools.length > 0
|
|
144
|
+
? "Choose a tool to read its description and call it."
|
|
145
|
+
: "This server exposes no tools yet. Add a file under tools/ and save.";
|
|
146
|
+
detail.replaceChildren(empty);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const view = $("tool-detail").content.cloneNode(true);
|
|
150
|
+
view.querySelector(".tool-title").textContent = tool.title ?? tool.name;
|
|
151
|
+
view.querySelector(".tool-name").textContent = tool.name;
|
|
152
|
+
const hints = view.querySelector(".hints");
|
|
153
|
+
for (const [key, label, className] of HINTS) {
|
|
154
|
+
if (tool.annotations?.[key])
|
|
155
|
+
hints.append(Object.assign(document.createElement("li"), { textContent: label, className }));
|
|
156
|
+
}
|
|
157
|
+
const description = view.querySelector(".description");
|
|
158
|
+
description.textContent = reflow(tool.description ?? "");
|
|
159
|
+
if (changed.has(tool.name)) description.classList.add("changed");
|
|
160
|
+
const textarea = view.querySelector("textarea");
|
|
161
|
+
textarea.value = drafts.get(tool.name) ?? skeleton(tool.inputSchema);
|
|
162
|
+
textarea.addEventListener("input", () => drafts.set(tool.name, textarea.value));
|
|
163
|
+
textarea.addEventListener("keydown", (event) => {
|
|
164
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
textarea.form.requestSubmit();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
view.querySelector(".schema-note").textContent = schemaNote(tool.inputSchema);
|
|
170
|
+
view.querySelector("kbd").textContent = mac ? "⌘\u00a0Enter" : "Ctrl\u00a0Enter";
|
|
171
|
+
view.querySelector("form").addEventListener("submit", (event) => {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
call(tool.name, textarea.value);
|
|
174
|
+
});
|
|
175
|
+
detail.replaceChildren(view);
|
|
176
|
+
const last = results.get(tool.name);
|
|
177
|
+
if (last) showResult(tool.name, ...last);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function showResult(name, head, body, isError) {
|
|
181
|
+
results.set(name, [head, body, isError]);
|
|
182
|
+
const result = document.querySelector(".result");
|
|
183
|
+
if (!result || selected !== name) return;
|
|
184
|
+
result.hidden = false;
|
|
185
|
+
result.dataset.error = String(isError);
|
|
186
|
+
result.querySelector(".result-head").textContent = head;
|
|
187
|
+
result.querySelector(".result-body").textContent = body;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function contentText(result) {
|
|
191
|
+
return (result.content ?? [])
|
|
192
|
+
.map((part) => {
|
|
193
|
+
if (part.type !== "text") return JSON.stringify(part, null, 2);
|
|
194
|
+
try {
|
|
195
|
+
return JSON.stringify(JSON.parse(part.text), null, 2);
|
|
196
|
+
} catch {
|
|
197
|
+
return part.text;
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
.join("\n\n");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function call(name, text) {
|
|
204
|
+
let args;
|
|
205
|
+
try {
|
|
206
|
+
args = JSON.parse(text || "{}");
|
|
207
|
+
} catch (error) {
|
|
208
|
+
showResult(name, "The arguments are not valid JSON", error.message, true);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const button = document.querySelector(".call .primary");
|
|
212
|
+
button.disabled = true;
|
|
213
|
+
button.textContent = "Calling…";
|
|
214
|
+
try {
|
|
215
|
+
const response = await fetch("/api/call", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: { "content-type": "application/json" },
|
|
218
|
+
body: JSON.stringify({ name, arguments: args }),
|
|
219
|
+
});
|
|
220
|
+
const body = await response.json();
|
|
221
|
+
if (!response.ok) showResult(name, "The call did not reach the tool", body.error, true);
|
|
222
|
+
else if (body.result.isError)
|
|
223
|
+
showResult(
|
|
224
|
+
name,
|
|
225
|
+
`The tool returned an error after ${body.ms} ms`,
|
|
226
|
+
contentText(body.result),
|
|
227
|
+
true,
|
|
228
|
+
);
|
|
229
|
+
else showResult(name, `Result in ${body.ms} ms`, contentText(body.result), false);
|
|
230
|
+
} finally {
|
|
231
|
+
button.disabled = false;
|
|
232
|
+
button.textContent = "Call tool";
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function select(name) {
|
|
237
|
+
selected = name;
|
|
238
|
+
renderList(new Set());
|
|
239
|
+
renderDetail(new Set());
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function refresh() {
|
|
243
|
+
const response = await fetch("/api/catalog");
|
|
244
|
+
if (!response.ok) return;
|
|
245
|
+
const next = await response.json();
|
|
246
|
+
const before = new Map(catalog.tools.map((t) => [t.name, fingerprint(t)]));
|
|
247
|
+
const changed = new Set(
|
|
248
|
+
before.size === 0
|
|
249
|
+
? []
|
|
250
|
+
: next.tools.filter((t) => before.get(t.name) !== fingerprint(t)).map((t) => t.name),
|
|
251
|
+
);
|
|
252
|
+
catalog = next;
|
|
253
|
+
if (!selected && catalog.tools[0]) selected = catalog.tools[0].name;
|
|
254
|
+
renderList(changed);
|
|
255
|
+
renderDetail(changed);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const events = new EventSource("/api/events");
|
|
259
|
+
events.addEventListener("message", (event) => {
|
|
260
|
+
const state = JSON.parse(event.data);
|
|
261
|
+
renderStatus(state);
|
|
262
|
+
if (state.revision !== revision) {
|
|
263
|
+
revision = state.revision;
|
|
264
|
+
refresh();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
events.addEventListener("error", () => {
|
|
268
|
+
const status = $("status");
|
|
269
|
+
status.dataset.state = "failed";
|
|
270
|
+
status.textContent = "sokie dev stopped. Run it again to reconnect";
|
|
271
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* Generated from packages/tokens/src by `bun run build` in packages/tokens.
|
|
2
|
+
Do not edit: change the source and rebuild. */
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
--sokie-ground: #ffffff;
|
|
6
|
+
--sokie-chrome: #f7f7f7;
|
|
7
|
+
--sokie-card: #ffffff;
|
|
8
|
+
--sokie-nested: #f4f4f4;
|
|
9
|
+
--sokie-border: rgba(0, 0, 0, 0.08);
|
|
10
|
+
--sokie-border-strong: rgba(0, 0, 0, 0.2);
|
|
11
|
+
--sokie-text: #0a0a0a;
|
|
12
|
+
--sokie-body: #3d3d3d;
|
|
13
|
+
--sokie-muted: #6b6b6b;
|
|
14
|
+
--sokie-primary: #0a0a0a;
|
|
15
|
+
--sokie-on-primary: #ffffff;
|
|
16
|
+
--sokie-live: #16a34a;
|
|
17
|
+
--sokie-live-text: #15803d;
|
|
18
|
+
--sokie-live-soft: #f0fdf4;
|
|
19
|
+
--sokie-danger: #b91c1c;
|
|
20
|
+
--sokie-danger-soft: #fef2f2;
|
|
21
|
+
--sokie-warn: #b45309;
|
|
22
|
+
--sokie-warn-soft: #fffbeb;
|
|
23
|
+
--sokie-mark-tile: #0a0a0a;
|
|
24
|
+
--sokie-mark-visor: #ffffff;
|
|
25
|
+
--sokie-mark-nose: #22c55e;
|
|
26
|
+
--sokie-mark-drop: #3b82f6;
|
|
27
|
+
--sokie-agent-claude: #d97757;
|
|
28
|
+
--sokie-agent-neutral: #0a0a0a;
|
|
29
|
+
--sokie-agent-gemini: #e8eefd;
|
|
30
|
+
--sokie-agent-gemini-star: #3b82f6;
|
|
31
|
+
--sokie-agent-glyph: #ffffff;
|
|
32
|
+
--sokie-mark-error: #ef4444;
|
|
33
|
+
--sokie-mark-concern: #f59e0b;
|
|
34
|
+
--sokie-radius-sm: 6px;
|
|
35
|
+
--sokie-radius-md: 8px;
|
|
36
|
+
--sokie-radius-lg: 10px;
|
|
37
|
+
--sokie-radius-xl: 12px;
|
|
38
|
+
--sokie-radius-2xl: 16px;
|
|
39
|
+
--sokie-font-sans:
|
|
40
|
+
"Schibsted Grotesk Variable", "Schibsted Grotesk", ui-sans-serif, system-ui, sans-serif;
|
|
41
|
+
--sokie-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
42
|
+
--sokie-weight-regular: 400;
|
|
43
|
+
--sokie-weight-medium: 500;
|
|
44
|
+
--sokie-weight-semibold: 600;
|
|
45
|
+
--sokie-weight-bold: 700;
|
|
46
|
+
--sokie-weight-heavy: 800;
|
|
47
|
+
--sokie-tracking-tight: -0.02em;
|
|
48
|
+
--sokie-tracking-display: -0.045em;
|
|
49
|
+
--sokie-ease-standard: cubic-bezier(0.45, 0, 0.55, 1);
|
|
50
|
+
--sokie-ease-out: cubic-bezier(0.2, 0.7, 0.4, 1);
|
|
51
|
+
--sokie-ease-in: cubic-bezier(0.6, 0, 0.8, 0.3);
|
|
52
|
+
--sokie-duration-fast: 150ms;
|
|
53
|
+
--sokie-duration-base: 200ms;
|
|
54
|
+
--sokie-duration-slow: 300ms;
|
|
55
|
+
--sokie-persona-idle-breath: 3600ms;
|
|
56
|
+
--sokie-persona-idle-glance: 7000ms;
|
|
57
|
+
--sokie-persona-idle-blink: 6000ms;
|
|
58
|
+
--sokie-persona-listening: 4400ms;
|
|
59
|
+
--sokie-persona-working-bob: 600ms;
|
|
60
|
+
--sokie-persona-working-dart: 2400ms;
|
|
61
|
+
--sokie-persona-working-dots: 1200ms;
|
|
62
|
+
--sokie-persona-reading: 4200ms;
|
|
63
|
+
--sokie-persona-searching: 3600ms;
|
|
64
|
+
--sokie-persona-live: 2000ms;
|
|
65
|
+
--sokie-persona-error: 2600ms;
|
|
66
|
+
--sokie-persona-concerned-breath: 700ms;
|
|
67
|
+
--sokie-persona-concerned-glance: 3000ms;
|
|
68
|
+
--sokie-persona-sad: 5000ms;
|
|
69
|
+
--sokie-persona-asleep: 4000ms;
|
|
70
|
+
--sokie-persona-curious: 3200ms;
|
|
71
|
+
--sokie-persona-surprised: 2800ms;
|
|
72
|
+
--sokie-persona-wink: 2400ms;
|
|
73
|
+
--sokie-persona-connecting: 900ms;
|
|
74
|
+
--sokie-persona-concerned-twitch: 1400ms;
|
|
75
|
+
--sokie-persona-glint: 6000ms;
|
|
76
|
+
--sokie-persona-prop-question: 1600ms;
|
|
77
|
+
--sokie-persona-prop-question-wobble: 1800ms;
|
|
78
|
+
--sokie-persona-prop-sweat: 2200ms;
|
|
79
|
+
--sokie-persona-prop-tear: 2400ms;
|
|
80
|
+
--sokie-persona-prop-zzz: 3000ms;
|
|
81
|
+
--sokie-persona-agent-pulse: 2400ms;
|
|
82
|
+
--sokie-scene-wire-rest: 3000ms;
|
|
83
|
+
--sokie-scene-wire-hot: 1100ms;
|
|
84
|
+
--sokie-scene-wire-busy: 500ms;
|
|
85
|
+
--sokie-scene-ticker: 16000ms;
|
|
86
|
+
--sokie-scene-float: 6000ms;
|
|
87
|
+
--sokie-scene-marquee: 36000ms;
|
|
88
|
+
--sokie-scene-caret: 1000ms;
|
|
89
|
+
color-scheme: light;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@media (prefers-color-scheme: dark) {
|
|
93
|
+
:root:not([data-theme="light"]) {
|
|
94
|
+
--sokie-ground: #000000;
|
|
95
|
+
--sokie-chrome: #080808;
|
|
96
|
+
--sokie-card: #0f0f0f;
|
|
97
|
+
--sokie-nested: #161616;
|
|
98
|
+
--sokie-border: rgba(255, 255, 255, 0.1);
|
|
99
|
+
--sokie-border-strong: rgba(255, 255, 255, 0.22);
|
|
100
|
+
--sokie-text: #ffffff;
|
|
101
|
+
--sokie-body: #b8b8b8;
|
|
102
|
+
--sokie-muted: #999999;
|
|
103
|
+
--sokie-primary: #ffffff;
|
|
104
|
+
--sokie-on-primary: #000000;
|
|
105
|
+
--sokie-live: #4ade80;
|
|
106
|
+
--sokie-live-text: #4ade80;
|
|
107
|
+
--sokie-live-soft: rgba(74, 222, 128, 0.12);
|
|
108
|
+
--sokie-danger: #f87171;
|
|
109
|
+
--sokie-danger-soft: rgba(248, 113, 113, 0.12);
|
|
110
|
+
--sokie-warn: #fbbf24;
|
|
111
|
+
--sokie-warn-soft: rgba(251, 191, 36, 0.12);
|
|
112
|
+
--sokie-mark-tile: #ffffff;
|
|
113
|
+
--sokie-mark-visor: #000000;
|
|
114
|
+
--sokie-mark-nose: #4ade80;
|
|
115
|
+
--sokie-mark-drop: #60a5fa;
|
|
116
|
+
--sokie-agent-claude: #d97757;
|
|
117
|
+
--sokie-agent-neutral: #2a2a2a;
|
|
118
|
+
--sokie-agent-gemini: #ffffff;
|
|
119
|
+
--sokie-agent-gemini-star: #3b82f6;
|
|
120
|
+
--sokie-agent-glyph: #ffffff;
|
|
121
|
+
color-scheme: dark;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
:root[data-theme="dark"] {
|
|
126
|
+
--sokie-ground: #000000;
|
|
127
|
+
--sokie-chrome: #080808;
|
|
128
|
+
--sokie-card: #0f0f0f;
|
|
129
|
+
--sokie-nested: #161616;
|
|
130
|
+
--sokie-border: rgba(255, 255, 255, 0.1);
|
|
131
|
+
--sokie-border-strong: rgba(255, 255, 255, 0.22);
|
|
132
|
+
--sokie-text: #ffffff;
|
|
133
|
+
--sokie-body: #b8b8b8;
|
|
134
|
+
--sokie-muted: #999999;
|
|
135
|
+
--sokie-primary: #ffffff;
|
|
136
|
+
--sokie-on-primary: #000000;
|
|
137
|
+
--sokie-live: #4ade80;
|
|
138
|
+
--sokie-live-text: #4ade80;
|
|
139
|
+
--sokie-live-soft: rgba(74, 222, 128, 0.12);
|
|
140
|
+
--sokie-danger: #f87171;
|
|
141
|
+
--sokie-danger-soft: rgba(248, 113, 113, 0.12);
|
|
142
|
+
--sokie-warn: #fbbf24;
|
|
143
|
+
--sokie-warn-soft: rgba(251, 191, 36, 0.12);
|
|
144
|
+
--sokie-mark-tile: #ffffff;
|
|
145
|
+
--sokie-mark-visor: #000000;
|
|
146
|
+
--sokie-mark-nose: #4ade80;
|
|
147
|
+
--sokie-mark-drop: #60a5fa;
|
|
148
|
+
--sokie-agent-claude: #d97757;
|
|
149
|
+
--sokie-agent-neutral: #2a2a2a;
|
|
150
|
+
--sokie-agent-gemini: #ffffff;
|
|
151
|
+
--sokie-agent-gemini-star: #3b82f6;
|
|
152
|
+
--sokie-agent-glyph: #ffffff;
|
|
153
|
+
color-scheme: dark;
|
|
154
|
+
}
|