@chalky/chalky 0.1.4 → 0.1.6
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/README.md +16 -5
- package/agent/SYSTEM.md +36 -0
- package/agent/extensions/app/README.md +72 -0
- package/{app/extensions/ui → agent/extensions/app}/agents.js +22 -16
- package/agent/extensions/app/guide.md +5 -0
- package/agent/extensions/app/index.ts +66 -0
- package/{app/extensions/ui/skills/agent-apps → agent/extensions/app/skills/app-agents}/SKILL.md +17 -18
- package/agent/extensions/app/skills/test-your-app/SKILL.md +23 -0
- package/{app/extensions/boards → agent/extensions/board}/browser.js +30 -32
- package/{app/extensions/boards → agent/extensions/board}/guide.md +1 -1
- package/{app/extensions/boards → agent/extensions/board}/index.ts +32 -30
- package/agent/extensions/card/README.md +88 -0
- package/agent/extensions/card/blocks/Button.js +34 -0
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/Column.js +2 -2
- package/agent/extensions/card/blocks/Markdown.js +37 -0
- package/{app/extensions/ui/components/Card.js → agent/extensions/card/blocks/Panel.js} +3 -3
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/Row.js +2 -2
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/TextField.js +3 -3
- package/{app/extensions/ui → agent/extensions/card}/browser.js +155 -129
- package/agent/extensions/card/guide.md +13 -0
- package/agent/extensions/card/index.ts +370 -0
- package/{app/extensions/commands → agent/extensions/command}/browser.js +3 -3
- package/agent/extensions/command/guide.md +2 -0
- package/{app/extensions/commands → agent/extensions/command}/index.ts +14 -16
- package/agent/extensions/doodle/README.md +57 -0
- package/agent/extensions/doodle/browser.js +1013 -0
- package/agent/extensions/doodle/guide.md +22 -0
- package/agent/extensions/doodle/index.ts +274 -0
- package/agent/extensions/file/browser.js +182 -0
- package/agent/extensions/file/guide.md +5 -0
- package/{app/extensions/files → agent/extensions/file}/index.ts +37 -18
- package/{app/extensions/frames → agent/extensions/frame}/README.md +13 -13
- package/{app/extensions/frames → agent/extensions/frame}/browser.js +20 -18
- package/agent/extensions/frame/guide.md +3 -0
- package/agent/extensions/frame/index.ts +90 -0
- package/{app/extensions/ui_history → agent/extensions/history}/browser.js +18 -18
- package/{app/extensions/ui_history → agent/extensions/history}/index.ts +34 -33
- package/{app → agent}/extensions/mcp/browser.js +1 -1
- package/agent/extensions/mcp/guide.md +3 -0
- package/{app → agent}/extensions/mcp/index.ts +2 -4
- package/{app/extensions/login → agent/extensions/model}/browser.js +67 -67
- package/{app → agent}/extensions/model/index.ts +8 -4
- package/{app/extensions/login/index.ts → agent/extensions/model/login.ts} +47 -46
- package/agent/extensions/note/README.md +37 -0
- package/agent/extensions/note/browser.js +382 -0
- package/agent/extensions/note/guide.md +9 -0
- package/agent/extensions/note/index.ts +215 -0
- package/agent/extensions/page/browser.js +453 -0
- package/{app → agent}/extensions/page/index.ts +72 -3
- package/{app → agent}/extensions/page/page.html +102 -49
- package/agent/extensions/page/skills/interact-with-page/SKILL.md +59 -0
- package/agent/extensions/prompt/index.ts +38 -0
- package/{app → agent}/extensions/reload/index.ts +3 -3
- package/{app → agent}/extensions/screen/browser.js +10 -23
- package/{app → agent}/extensions/screen/index.ts +11 -11
- package/{app/extensions/steps → agent/extensions/step}/index.ts +1 -1
- package/{app/extensions/swarm → agent/extensions/team}/README.md +19 -19
- package/{app/extensions/swarm → agent/extensions/team}/guide.md +4 -4
- package/{app/extensions/swarm → agent/extensions/team}/index.ts +41 -66
- package/{app/extensions/versions → agent/extensions/version}/browser.js +19 -10
- package/agent/extensions/version/index.ts +205 -0
- package/{app → agent}/extensions/viewer/README.md +4 -4
- package/{app → agent}/extensions/viewer/browser.js +8 -8
- package/agent/extensions/viewer/guide.md +3 -0
- package/{app → agent}/extensions/viewer/index.ts +22 -17
- package/{app → agent}/extensions/voice/browser.js +4 -3
- package/{app → agent}/extensions/voice/index.ts +8 -6
- package/{app/extensions/ui → agent/extensions/voice}/voice.js +5 -5
- package/agent/settings.json +8 -0
- package/{app/extensions/ui/skills/ui-check → agent/skills/browser-check}/SKILL.md +4 -4
- package/{app/extensions/ui/skills/ui-check → agent/skills/browser-check}/scripts/check.mjs +1 -1
- package/{app/extensions/ui/skills/ui-design → agent/skills/design}/SKILL.md +15 -15
- package/agent/skills/extension-map/SKILL.md +56 -0
- package/agent/skills/show-me/SKILL.md +66 -0
- package/{app → agent}/skills/write-skill/SKILL.md +1 -1
- package/bin/chalky.mjs +2 -2
- package/package.json +9 -8
- package/server.ts +313 -58
- package/app/SYSTEM.md +0 -31
- package/app/extensions/appdata/README.md +0 -10
- package/app/extensions/appdata/guide.md +0 -4
- package/app/extensions/appdata/index.ts +0 -47
- package/app/extensions/commands/guide.md +0 -3
- package/app/extensions/doodle/README.md +0 -15
- package/app/extensions/doodle/browser.js +0 -398
- package/app/extensions/doodle/guide.md +0 -6
- package/app/extensions/doodle/index.ts +0 -54
- package/app/extensions/files/browser.js +0 -96
- package/app/extensions/files/guide.md +0 -7
- package/app/extensions/frames/guide.md +0 -7
- package/app/extensions/frames/index.ts +0 -85
- package/app/extensions/mcp/guide.md +0 -5
- package/app/extensions/point/README.md +0 -22
- package/app/extensions/point/browser.js +0 -247
- package/app/extensions/point/guide.md +0 -6
- package/app/extensions/point/index.ts +0 -112
- package/app/extensions/prompt/index.ts +0 -19
- package/app/extensions/ui/README.md +0 -69
- package/app/extensions/ui/components/App.js +0 -28
- package/app/extensions/ui/components/Button.js +0 -28
- package/app/extensions/ui/components/Markdown.js +0 -37
- package/app/extensions/ui/guide.md +0 -7
- package/app/extensions/ui/index.ts +0 -291
- package/app/extensions/versions/index.ts +0 -168
- package/app/extensions/viewer/guide.md +0 -5
- package/app/mcp.json +0 -3
- package/app/settings.json +0 -3
- package/app/skills/plugin-map/SKILL.md +0 -49
- package/app/ui/apps/.keep +0 -0
- package/node_modules/xlsx/CHANGELOG.md +0 -361
- package/node_modules/xlsx/LICENSE +0 -201
- package/node_modules/xlsx/README.md +0 -41
- package/node_modules/xlsx/bin/xlsx.njs +0 -310
- package/node_modules/xlsx/bower.json +0 -22
- package/node_modules/xlsx/dist/LICENSE +0 -201
- package/node_modules/xlsx/dist/cpexcel.d.ts +0 -39
- package/node_modules/xlsx/dist/cpexcel.full.mjs +0 -1502
- package/node_modules/xlsx/dist/cpexcel.js +0 -1506
- package/node_modules/xlsx/dist/shim.min.js +0 -2
- package/node_modules/xlsx/dist/xlsx.core.min.js +0 -18
- package/node_modules/xlsx/dist/xlsx.core.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.extendscript.js +0 -28259
- package/node_modules/xlsx/dist/xlsx.full.min.js +0 -24
- package/node_modules/xlsx/dist/xlsx.full.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.mini.min.js +0 -10
- package/node_modules/xlsx/dist/xlsx.mini.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.zahl.js +0 -4
- package/node_modules/xlsx/dist/xlsx.zahl.mjs +0 -2
- package/node_modules/xlsx/dist/zahl.d.ts +0 -4
- package/node_modules/xlsx/package.json +0 -183
- package/node_modules/xlsx/types/index.d.ts +0 -1065
- package/node_modules/xlsx/types/tsconfig.json +0 -15
- package/node_modules/xlsx/xlsx.js +0 -28103
- package/node_modules/xlsx/xlsx.mjs +0 -28228
- package/node_modules/xlsx/xlsxworker.js +0 -14
- /package/{app/extensions/ui/components → agent/extensions/card/blocks}/Table.js +0 -0
- /package/{app/extensions/ui/components → agent/extensions/card/blocks}/Text.js +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
//
|
|
1
|
+
// The page side of the viewer extension: a preview window per file, opened by the file extension's "file-open" DOM
|
|
2
2
|
// event {file: {name, path, size}} (a click on a row of the file panel, or files dropped on the page).
|
|
3
3
|
// A window starts attached to its row: left of the panel, at the row's height, a notch pointing at it, the row
|
|
4
4
|
// lit. Only one window is attached at a time (opening another file replaces it); dragging its label (shown
|
|
5
|
-
// on hover, like a
|
|
5
|
+
// on hover, like a card's: name, ✕) or resizing it from the corner detaches it, and then it stays where
|
|
6
6
|
// it was put. ✕ closes it. Windows live only in this page (not saved, not seen by the agent).
|
|
7
|
-
// Pointing at a window with the pen:
|
|
7
|
+
// Pointing at a window with the pen: note/browser.js asks its partAt(x, y), which answers which file and where:
|
|
8
8
|
// {viewing, sheet, cell} | {viewing, slide} | {viewing, paragraph} | {viewing, line} | {viewing}.
|
|
9
9
|
// Drawing around something: partIn(points) answers what is inside the drawn shape (a thing whose middle is
|
|
10
10
|
// inside it; if none, the thing under the drawing's middle), with its content, at most MAX_PICK characters:
|
|
@@ -83,19 +83,19 @@ export default async function (page) {
|
|
|
83
83
|
const layer = el("div", { class: "viewers" });
|
|
84
84
|
document.body.append(layer);
|
|
85
85
|
|
|
86
|
-
const rowOf = (path) => document.querySelector(`#side .
|
|
86
|
+
const rowOf = (path) => document.querySelector(`#side .file-row[data-path="${CSS.escape(path)}"]`);
|
|
87
87
|
|
|
88
88
|
function place(w) { // attached: left of the panel, at the row's height (the folder button if the panel is closed)
|
|
89
89
|
if (!w.attached) return;
|
|
90
90
|
const row = rowOf(w.file.path);
|
|
91
91
|
const visible = row && row.offsetParent;
|
|
92
|
-
const anchor = visible ? row : document.querySelector("#side .
|
|
92
|
+
const anchor = visible ? row : document.querySelector("#side .file-toggle");
|
|
93
93
|
if (!anchor) return;
|
|
94
94
|
const a = anchor.getBoundingClientRect(), panel = (visible ? row.parentElement : anchor).getBoundingClientRect();
|
|
95
95
|
const h = w.box.offsetHeight, y = Math.max(72, Math.min(a.top - 14, innerHeight - h - 16)); // 72: below the palette
|
|
96
96
|
Object.assign(w.box.style, { right: `${innerWidth - panel.left + 14}px`, left: "", top: `${y}px` });
|
|
97
97
|
w.box.style.setProperty("--notch", `${Math.max(10, Math.min(a.top + a.height / 2 - y - 6, h - 22))}px`);
|
|
98
|
-
for (const r of document.querySelectorAll("#side .
|
|
98
|
+
for (const r of document.querySelectorAll("#side .file-row.viewing")) r.classList.remove("viewing");
|
|
99
99
|
if (visible) row.classList.add("viewing");
|
|
100
100
|
}
|
|
101
101
|
const placeAll = () => Object.values(windows).forEach(place);
|
|
@@ -103,7 +103,7 @@ export default async function (page) {
|
|
|
103
103
|
document.addEventListener("scroll", placeAll, true); // the panel's list scrolls
|
|
104
104
|
const side = document.getElementById("side");
|
|
105
105
|
if (side) new MutationObserver(() => { // the list redrawn or the panel opened or closed
|
|
106
|
-
for (const w of Object.values(windows)) if (!rowOf(w.file.path) && w.attached
|
|
106
|
+
for (const w of Object.values(windows)) if (!rowOf(w.file.path) && w.attached) close(w); // file gone (deleted too)
|
|
107
107
|
placeAll();
|
|
108
108
|
}).observe(side, { childList: true, subtree: true, attributes: true, attributeFilter: ["data-open", "hidden"] });
|
|
109
109
|
|
|
@@ -148,7 +148,7 @@ export default async function (page) {
|
|
|
148
148
|
left: `${Math.max(0, Math.min(r.left + dx, innerWidth - 60))}px`, top: `${Math.max(24, Math.min(r.top + dy, innerHeight - 40))}px` }));
|
|
149
149
|
corner.onpointerdown = (e) => dragging(w, e, (r, dx, dy) => Object.assign(box.style, {
|
|
150
150
|
width: `${Math.max(240, r.width + dx)}px`, height: `${Math.max(160, r.height + dy)}px` }));
|
|
151
|
-
box.partAt = (x, y) => { // for
|
|
151
|
+
box.partAt = (x, y) => { // for note/browser.js: which file, and where in it
|
|
152
152
|
const r = box.getBoundingClientRect();
|
|
153
153
|
const hit = document.elementsFromPoint(r.left + x, r.top + y).find((e) => box.contains(e));
|
|
154
154
|
const spot = hit?.closest("[data-part]");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Viewer: previews of the user's files (the ones in the
|
|
2
|
-
//
|
|
1
|
+
// Viewer: previews of the user's files (the ones in the file extension's folder), in a window on the page (the
|
|
2
|
+
// browser half: browser.js). browser.js opens a window when files' browser.js fires
|
|
3
3
|
// "file-open"; the window lives only in that page (a note pointed at it says which file and which spot).
|
|
4
4
|
// Parsing happens here, one library per format (plain previews, no LibreOffice):
|
|
5
5
|
// images, pdf shown by the browser itself, from /viewer/raw
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
// GET /viewer/view?path= {"kind": "image"|"pdf"|"text"|"sheets"|"doc"|"slides"|"none", ...} (see preview())
|
|
13
13
|
// GET /viewer/raw?path= the file's bytes, images and pdf only
|
|
14
14
|
// POST /viewer/shot a png of an area the user drew around on a pdf; saved in SHOTS, returns {"path"}
|
|
15
|
-
// Tool
|
|
15
|
+
// Tool viewer_read(path, offset?): the same content as text, for the agent (pdf text with unpdf). The Python tool was
|
|
16
16
|
// read_file; renamed so it isn't mistaken for pi's read (text and pictures, which the guide now names for pictures).
|
|
17
17
|
import fs from "node:fs";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
18
19
|
import path from "node:path";
|
|
19
|
-
import
|
|
20
|
+
import type { WorkSheet } from "xlsx"; // types only: erased when this runs
|
|
20
21
|
import mammoth from "mammoth";
|
|
21
22
|
import PptxParser from "node-pptx-parser";
|
|
22
23
|
import WordExtractor from "word-extractor";
|
|
@@ -25,13 +26,16 @@ import { Type } from "typebox";
|
|
|
25
26
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
26
27
|
|
|
27
28
|
const host = (globalThis as any).chalky;
|
|
28
|
-
|
|
29
|
+
// xlsx is a bundled dependency: npm keeps it inside our package's node_modules and never moves it up next to the
|
|
30
|
+
// others, so a plain import from this home copy doesn't find it. Look for it from the package's own folder instead.
|
|
31
|
+
const XLSX: typeof import("xlsx") = createRequire(path.join(host.root, "package.json"))("xlsx");
|
|
32
|
+
const DIR = path.join(host.workspace, "drops"); // the file extension's folder
|
|
29
33
|
const IMAGES: Record<string, string> = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif",
|
|
30
34
|
".webp": "image/webp", ".bmp": "image/bmp", ".svg": "image/svg+xml", ".avif": "image/avif", ".ico": "image/x-icon" };
|
|
31
35
|
const MAX_ROWS = 1000, MAX_COLS = 60; // per sheet shown
|
|
32
36
|
const MAX_TEXT = 300_000; // characters shown
|
|
33
|
-
const MAX_TOOL_TEXT = 40_000; // characters
|
|
34
|
-
const SHOTS = path.join(host.
|
|
37
|
+
const MAX_TOOL_TEXT = 40_000; // characters viewer_read returns
|
|
38
|
+
const SHOTS = path.join(host.data, "viewer"); // pictures of drawn-around pdf areas (outside the user's folder)
|
|
35
39
|
const MAX_SHOT = 10 * 1024 * 1024;
|
|
36
40
|
|
|
37
41
|
function inside(p: string | null) { // the real path, if it is a file in DIR; else null (no reading elsewhere)
|
|
@@ -41,7 +45,7 @@ function inside(p: string | null) { // the real path, if it is a file in DIR; e
|
|
|
41
45
|
} catch { return null; }
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
function sheet(name: string, ws:
|
|
48
|
+
function sheet(name: string, ws: WorkSheet) { // {name, rows, cut}: from A1, at most MAX_ROWS x MAX_COLS
|
|
45
49
|
if (!ws["!ref"]) return { name, rows: [], cut: false };
|
|
46
50
|
const r = XLSX.utils.decode_range(ws["!ref"]);
|
|
47
51
|
const cut = r.e.r >= MAX_ROWS || r.e.c >= MAX_COLS;
|
|
@@ -132,7 +136,7 @@ async function preview(p: string) {
|
|
|
132
136
|
return { kind: "text", text: head.subarray(0, n).toString("utf8"), cut: fs.statSync(p).size > MAX_TEXT };
|
|
133
137
|
}
|
|
134
138
|
|
|
135
|
-
async function asText(p: string): Promise<string> { // what
|
|
139
|
+
async function asText(p: string): Promise<string> { // what viewer_read returns: the preview, written out as text
|
|
136
140
|
const v: any = await preview(p);
|
|
137
141
|
if (v.kind === "image") return `${p} is an image: look at it with read.`;
|
|
138
142
|
if (v.kind === "pdf") {
|
|
@@ -188,22 +192,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
188
192
|
|
|
189
193
|
|
|
190
194
|
pi.registerTool({
|
|
191
|
-
name: "
|
|
195
|
+
name: "viewer_read", label: "Viewer read", exposure: "deferred",
|
|
192
196
|
description: "Read a document as text: docx, pptx, xlsx, xls, csv, pdf, doc, or any text file "
|
|
193
197
|
+ "(sheets come back as CSV, slides and pages numbered).",
|
|
194
|
-
parameters: Type.Object({ path: Type.String()
|
|
195
|
-
|
|
198
|
+
parameters: Type.Object({ path: Type.String(),
|
|
199
|
+
offset: Type.Optional(Type.Number({ description: "the character to start from, to read on in a long document" })) }),
|
|
200
|
+
async execute(_id, { path: p, offset = 0 }) {
|
|
196
201
|
let text: string;
|
|
197
202
|
if (!fs.existsSync(p) || !fs.statSync(p).isFile()) text = `error: no file ${p}`;
|
|
198
203
|
else {
|
|
199
|
-
try {
|
|
200
|
-
|
|
204
|
+
try {
|
|
205
|
+
const whole = await asText(p), end = offset + MAX_TOOL_TEXT;
|
|
206
|
+
text = whole.slice(offset, end);
|
|
207
|
+
if (end < whole.length) text += `\n[... cut: ${whole.length} characters in all; call again with offset=${end} for the rest]`;
|
|
208
|
+
} catch (e: any) { text = `error: could not read ${p}: ${e.name}: ${e.message}`; }
|
|
201
209
|
}
|
|
202
210
|
return { content: [{ type: "text", text }], details: undefined };
|
|
203
211
|
},
|
|
204
212
|
});
|
|
205
213
|
|
|
206
|
-
const guide = fs.readFileSync(path.join(import.meta.dirname, "guide.md"), "utf8") // short; README.md: the whole of it, read when it says
|
|
207
|
-
.replace("README.md", path.join(import.meta.dirname, "README.md"));
|
|
208
|
-
pi.on("before_agent_start", (event) => { event.systemPromptOptions.sections.viewer = guide; });
|
|
209
214
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
// to stop; each finished piece of speech is added to the input (to read, fix and send). /
|
|
1
|
+
// The page side of the voice extension: a microphone button left of the send button. Click to dictate, click again
|
|
2
|
+
// to stop; each finished piece of speech is added to the input (to read, fix and send). /voice/voice.js does the
|
|
3
3
|
// listening: the server's speech to text when it has STT_BASE_URL, else the browser's own recognition.
|
|
4
4
|
// While dictating, the icon is a sound wave whose bars follow the microphone's loudness (its own AnalyserNode, so
|
|
5
5
|
// it works in both modes).
|
|
6
|
-
import voice from "/
|
|
6
|
+
import voice from "/voice/voice.js";
|
|
7
7
|
|
|
8
8
|
const SVG = '<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-linecap="round" ' +
|
|
9
9
|
'stroke-linejoin="round">';
|
|
@@ -16,6 +16,7 @@ export default function (page) {
|
|
|
16
16
|
const button = document.createElement("button");
|
|
17
17
|
button.type = "button"; // in the form: not a send
|
|
18
18
|
button.className = "mic";
|
|
19
|
+
button.dataset.name = "dictate";
|
|
19
20
|
button.title = "Dictate";
|
|
20
21
|
button.innerHTML = MIC;
|
|
21
22
|
input.after(button);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// Voice input: speech to text
|
|
2
|
-
// button (browser.js) and /
|
|
1
|
+
// Voice input: speech to text. The browser half: a microphone button by the send
|
|
2
|
+
// button (browser.js) and /voice/voice.js, which apps can import too.
|
|
3
3
|
// Settings (environment, like MODEL_*): STT_BASE_URL, STT_MODEL, STT_API_KEY. With STT_BASE_URL set, audio goes to
|
|
4
4
|
// POST {STT_BASE_URL}/audio/transcriptions, OpenAI's endpoint, which most providers speak (OpenAI, Groq, Mistral,
|
|
5
5
|
// vLLM; others through LiteLLM). Without it, voice.js uses the browser's own speech recognition.
|
|
6
6
|
// The key stays here: apps are agent-written code, so they post audio to this server, never to the provider.
|
|
7
|
-
// GET /voice
|
|
8
|
-
// POST /voice
|
|
7
|
+
// GET /voice/config {"api": true | false}: whether audio can go to the API
|
|
8
|
+
// POST /voice/transcribe multipart: file (a complete audio file, e.g. chunk.webm), language?, prompt?
|
|
9
|
+
// GET /voice/voice.js the browser side, for the page (browser.js) and for apps
|
|
9
10
|
// -> {"text"} or {"error"} (with the provider's status)
|
|
10
11
|
import { Readable } from "node:stream";
|
|
11
12
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
@@ -17,9 +18,10 @@ export default function (_pi: ExtensionAPI) {
|
|
|
17
18
|
const json = (res: any, status: number, data: object) =>
|
|
18
19
|
res.writeHead(status, { "content-type": "application/json" }).end(JSON.stringify(data));
|
|
19
20
|
|
|
20
|
-
host.routes.set("/voice
|
|
21
|
+
host.routes.set("/voice/voice.js", (_req: any, res: any) => host.file(res, `${import.meta.dirname}/voice.js`));
|
|
22
|
+
host.routes.set("/voice/config", (_req: any, res: any) => json(res, 200, { api: !!base }));
|
|
21
23
|
|
|
22
|
-
host.routes.set("/voice
|
|
24
|
+
host.routes.set("/voice/transcribe", async (req: any, res: any) => {
|
|
23
25
|
if (!base) return json(res, 400, { error: "no STT_BASE_URL: use the browser's speech recognition" });
|
|
24
26
|
try {
|
|
25
27
|
// the page's form as is, plus the model (Node reads multipart itself: Request.formData)
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// Voice input for apps: speech from the microphone, as text. An app imports it:
|
|
2
|
-
// import voice from "/
|
|
2
|
+
// import voice from "/voice/voice.js";
|
|
3
3
|
// await voice.start(onText, opts) start listening; onText(text) gets each finished piece of speech, in order
|
|
4
4
|
// opts: language ("en", optional), onState(state, detail) with state one of
|
|
5
5
|
// "listening", "speech", "sending", "off", "error" (detail: the message)
|
|
6
6
|
// voice.stop() stop listening; a piece still being spoken is sent first
|
|
7
7
|
// voice.mode() "api" or "browser", known after start
|
|
8
|
-
// "api" (the
|
|
9
|
-
// (2 to 10 s), each sent as a complete audio file to /voice
|
|
8
|
+
// "api" (the server has STT_BASE_URL: the voice extension's index.ts): the mic is cut into pieces at pauses
|
|
9
|
+
// (2 to 10 s), each sent as a complete audio file to /voice/transcribe; silent pieces aren't sent.
|
|
10
10
|
// "browser": the browser's own speech recognition (Chrome, Edge, Safari; audio goes to their vendor).
|
|
11
11
|
const shot = new URLSearchParams(window.top.location.search).has("shot"); // a screenshot: don't listen
|
|
12
12
|
let mode = null, stopNow = null;
|
|
13
13
|
|
|
14
14
|
async function start(onText, { language = "", onState = () => {} } = {}) {
|
|
15
15
|
if (shot || stopNow) return;
|
|
16
|
-
const config = await fetch("/voice
|
|
16
|
+
const config = await fetch("/voice/config").then((r) => r.json()).catch(() => ({ api: false }));
|
|
17
17
|
mode = config.api ? "api" : "browser";
|
|
18
18
|
stopNow = mode === "api" ? await startApi(onText, language, onState) : startBrowser(onText, language, onState);
|
|
19
19
|
}
|
|
@@ -57,7 +57,7 @@ async function startApi(onText, language, onState) {
|
|
|
57
57
|
if (lastText) form.append("prompt", lastText.slice(-200));
|
|
58
58
|
onState("sending");
|
|
59
59
|
try {
|
|
60
|
-
const r = await fetch("/voice
|
|
60
|
+
const r = await fetch("/voice/transcribe", { method: "POST", body: form });
|
|
61
61
|
const data = await r.json();
|
|
62
62
|
if (data.error) return onState("error", data.error);
|
|
63
63
|
if (data.text.trim()) { lastText = data.text; onText(data.text.trim()); }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: Check a web UI in a real headless browser - JavaScript errors, failed requests, clipped or overflowing elements, how it looks, and whether clicks and keys work. Use
|
|
2
|
+
name: browser-check
|
|
3
|
+
description: Check a web UI in a real headless browser - JavaScript errors, failed requests, clipped or overflowing elements, how it looks, and whether clicks and keys work. Use to check the page itself or a block you wrote, or for steps beyond the app README's example (drag, keys, dialogs, sizes).
|
|
4
4
|
---
|
|
5
5
|
# Checking a UI in a headless browser
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ From this skill's folder (paths here are relative to it):
|
|
|
13
13
|
node scripts/check.mjs URL [STEPS ...] [--size 1280x800] [--shot /tmp/ui.png] [--full]
|
|
14
14
|
```
|
|
15
15
|
- URL: the user's page is `{page}/`. An app you built is served by it too
|
|
16
|
-
(`{page}/
|
|
16
|
+
(`{page}/built/apps/<name>/index.html`); any other file: `file:///<full path>`.
|
|
17
17
|
- Steps run in order after the page loads (it gets 1.5 s first):
|
|
18
18
|
`wait:500`, `click:#pause`, `drag:#card=200,0`, `altdrag:#top=-150,0` (Alt held), `hold:.label=800` (press and hold),
|
|
19
19
|
`dblclick:.label`, `type:#message=hello`, `key:Enter`, `eval:document.querySelectorAll('.agent').length`,
|
|
@@ -65,7 +65,7 @@ The command you ran, its counts ("0 errors, 0 layout problems at 1280x800 and 39
|
|
|
65
65
|
eval steps returned, and what you saw in the screenshot.
|
|
66
66
|
|
|
67
67
|
## Pitfalls
|
|
68
|
-
- `type:` splits at the first `=`: use a selector without one (`
|
|
68
|
+
- `type:` splits at the first `=`: use a selector without one (`block-textfield input`, not `[data-card=x] input`).
|
|
69
69
|
- `querySelector` doesn't reach inside a shadow root; use `eval:` with `el.shadowRoot.querySelector(...)`.
|
|
70
70
|
- Content that arrives over the WebSocket may need `wait:1000` before you check it.
|
|
71
71
|
- Chrome starts fresh for each run (no cookies, no storage); each run takes a few seconds.
|
|
@@ -247,7 +247,7 @@ async function main(url, steps, size, shotPath, full, fit) {
|
|
|
247
247
|
return 2;
|
|
248
248
|
}
|
|
249
249
|
const [w, h] = size.split("x").map(Number);
|
|
250
|
-
const profile = fs.mkdtempSync(path.join(os.tmpdir(), "
|
|
250
|
+
const profile = fs.mkdtempSync(path.join(os.tmpdir(), "browser-check-"));
|
|
251
251
|
const headless = path.basename(chromePath).startsWith("chrome-headless-shell") ? [] : ["--headless"]; // a full browser: no window
|
|
252
252
|
const chrome = spawn(chromePath, [...headless, "--no-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--no-first-run",
|
|
253
253
|
`--user-data-dir=${profile}`, "--remote-debugging-port=0", "about:blank"], { stdio: "ignore" });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: How to make what you build for the user look good and read clearly -
|
|
2
|
+
name: design
|
|
3
|
+
description: How to make what you build for the user look good and read clearly - charts, diagrams, game art, dashboards, one look across cards. Use for those, for a restyle, or for a look the user asks for; the app README has the basics a usual app needs.
|
|
4
4
|
---
|
|
5
5
|
# Designing what the user sees
|
|
6
6
|
|
|
@@ -11,21 +11,21 @@ Precedence: the user's own words, then the page's existing look, then your choic
|
|
|
11
11
|
## 1. Fit the page
|
|
12
12
|
The page is light, glassy and quiet (Apple-like): soft gradient background, translucent white glass,
|
|
13
13
|
thin edges, system font. Match it unless the user asks for another look.
|
|
14
|
-
- Use the page's tokens instead of literal colours (they are defined on the page and reach
|
|
14
|
+
- Use the page's tokens instead of literal colours (they are defined on the page and reach blocks;
|
|
15
15
|
apps in a frame copy the values they need):
|
|
16
16
|
`--text #1d1d1f`, `--soft rgba(29,29,31,.72)`, `--muted rgba(60,60,67,.55)`, `--accent #0071e3`,
|
|
17
17
|
`--danger #e5484d`, `--line rgba(0,0,0,.06)`, `--glass rgba(255,255,255,.45)`,
|
|
18
18
|
`--glass-strong rgba(255,255,255,.7)`, `--edge rgba(255,255,255,.75)`, `--radius 16px`.
|
|
19
|
-
- The built-in
|
|
20
|
-
prefer them, and style your own
|
|
19
|
+
- The built-in blocks (Panel, Button, Table, Text, TextField, Markdown) already use these tokens;
|
|
20
|
+
prefer them, and style your own blocks the same way.
|
|
21
21
|
- Font: inherit the page's system font. Use a Google Font only when the subject calls for it
|
|
22
22
|
(a pixel-art game, a poster), and always give a fallback stack.
|
|
23
23
|
- Show just the thing: no frame, card or background around it unless it needs one. Transparent app backgrounds.
|
|
24
|
-
- A user-requested look (pixel art, neon, paper) wins. Commit to it fully inside that
|
|
24
|
+
- A user-requested look (pixel art, neon, paper) wins. Commit to it fully inside that card,
|
|
25
25
|
and keep the rest of the page as it is.
|
|
26
26
|
|
|
27
27
|
## 2. Ground it in the subject
|
|
28
|
-
- Pick one concrete job for each
|
|
28
|
+
- Pick one concrete job for each card. Use real content, never lorem ipsum.
|
|
29
29
|
- Take at least one detail from the subject's own world: its real units, terms, conventions
|
|
30
30
|
(a chess clock shows mm:ss; a kanban card has an owner and a state).
|
|
31
31
|
- Structure must mean something: number things only when order matters; a divider only between real groups.
|
|
@@ -87,19 +87,19 @@ talk, what changes between two options.
|
|
|
87
87
|
|
|
88
88
|
## 9. Make it easy to reshape
|
|
89
89
|
The user changes what you build by hand (pins, levers on the back side, combining, undo), so build for that.
|
|
90
|
-
- One clear thing per
|
|
90
|
+
- One clear thing per card, with a name the user would say (`lunch-orders`, not `card3`).
|
|
91
91
|
- Write `about`: what it is for and any decision a later change should respect ("drinks are not counted").
|
|
92
|
-
- Put what changes in data (rows, scores, settings) and the look in
|
|
92
|
+
- Put what changes in data (rows, scores, settings) and the look in blocks: data changes are instant
|
|
93
93
|
for the user and never need you.
|
|
94
|
-
- Read other
|
|
95
|
-
- A
|
|
94
|
+
- Read other cards' data with `{"ref": "<card>", "path": "/..."}` instead of copying it, so it stays live.
|
|
95
|
+
- A block you write: its about block lists every prop with a type, and `levers` for the few a user
|
|
96
96
|
would want to change (a size, a mode, a label), written as plain sentences.
|
|
97
97
|
|
|
98
98
|
## 10. One look
|
|
99
99
|
What the user builds over time should look like one hand made it: a garden and the wrestler standing
|
|
100
100
|
in it share a palette, a way of shading and a level of detail.
|
|
101
101
|
- Before drawing something new, look at what is already there (a screenshot, and the code of the
|
|
102
|
-
|
|
102
|
+
cards it sits beside). Match its palette, shading, line weight and detail. Read their code and
|
|
103
103
|
reuse their colours and helpers rather than redrawing them from memory.
|
|
104
104
|
- Keep a small palette: a ground, a main colour for shapes, an ink for words, and one accent for the
|
|
105
105
|
one thing to look at. Mix shades from those (`color-mix(in srgb, <main> 70%, <ground>)`, lighter and
|
|
@@ -114,10 +114,10 @@ in it share a palette, a way of shading and a level of detail.
|
|
|
114
114
|
three. Neon, glows, gloss and full saturation everywhere look machine-made unless the subject calls for them.
|
|
115
115
|
- Games and 3D: the same idea as art direction, not exact colours. One sky or fog colour and one light
|
|
116
116
|
colour across the scene hold it together, however many materials it has; the accent marks what the
|
|
117
|
-
player should notice; the HUD and menus use the palette like any
|
|
117
|
+
player should notice; the HUD and menus use the palette like any card.
|
|
118
118
|
- A default, not a rule: when the user asks for another look, or a piece clearly needs its own world
|
|
119
119
|
(a dark space game beside a light garden), give it one, commit to it, and say so in its `about`.
|
|
120
120
|
|
|
121
121
|
## 11. Check it
|
|
122
|
-
Load
|
|
123
|
-
Fix
|
|
122
|
+
Load browser-check and look at a screenshot: overlaps, clipped text, labels colliding, things outside their box.
|
|
123
|
+
Fix everything you see before you show it: the user marks up details, not missing basics.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: extension-map
|
|
3
|
+
description: Find your way around your own code (the pi extensions in your agent folder, the page among them) without reading whole files. Use before changing yourself, or before planning work for others that will.
|
|
4
|
+
---
|
|
5
|
+
# Getting a map of your own code
|
|
6
|
+
|
|
7
|
+
Your agent folder ({agent} in your prompt) holds `extensions/`. Every feature is one extension, and every extension
|
|
8
|
+
is one folder, `extensions/<name>/`, named after the one thing it is (card, board, note, team...), holding all of it:
|
|
9
|
+
|
|
10
|
+
| File | What it is |
|
|
11
|
+
|---------------|--------------------------------------------------------------------|
|
|
12
|
+
| `index.ts` | the extension itself (pi loads it); starts with a header comment |
|
|
13
|
+
| `guide.md` | its short prompt section (found by itself: the prompt extension adds every one) |
|
|
14
|
+
| `README.md` | the whole of what the guide says, read when the guide says |
|
|
15
|
+
| `browser.js` | its part of the page (found by itself: the page extension loads every one) |
|
|
16
|
+
| `skills/` | skills only this feature needs (its index.ts gives them to pi) |
|
|
17
|
+
|
|
18
|
+
An extension can have other files too (`card/` has its `blocks/`, `page/` has `page.html`, `model/` has
|
|
19
|
+
`login.ts`). The headers are the documentation, kept with the code, so read them before the code itself.
|
|
20
|
+
|
|
21
|
+
## The naming rule
|
|
22
|
+
Everything an extension adds carries its name: tools `<name>_<verb>` (card_show, note_done, team_ask), page
|
|
23
|
+
messages `<name>_<verb>` (card_move, history_undo), page events `<name>` (card, history), routes `/<name>/...`
|
|
24
|
+
(/card/all, /note/all), its data in {data} (`<name>.json` or `<name>/`), and what it offers others as
|
|
25
|
+
`host.api.<name>` (host.api.card.watch, host.api.team.send). So from any one name you can find the rest.
|
|
26
|
+
|
|
27
|
+
## 1. The map: one line per extension
|
|
28
|
+
From your agent folder:
|
|
29
|
+
```
|
|
30
|
+
for d in extensions/*/; do printf '%-24s %s\n' "$d" "$(grep -m1 '^//' "$d/index.ts" | sed 's|^// \?||' | cut -c1-140)"; done
|
|
31
|
+
```
|
|
32
|
+
Also there:
|
|
33
|
+
- `extensions/page/`: the page itself (`page.html`), its list of browser parts, and what serves them.
|
|
34
|
+
- `extensions/card/blocks/*.js`: the blocks cards are built from, each starting with a comment line on what it
|
|
35
|
+
is, then its `about` block (props, levers, example; read its first 20 lines).
|
|
36
|
+
- `skills/<name>/SKILL.md`: skills about you as a whole (this one); a feature's own skills are in its folder.
|
|
37
|
+
- {built}: what you made for the user, not code: `card.json` (the cards), `board.json`, `apps/<name>/`.
|
|
38
|
+
- The host, `server.ts` (its path is in your prompt): the four folders, sessions, the web server, the version
|
|
39
|
+
history's rule, `globalThis.chalky`. Read its header; don't edit it.
|
|
40
|
+
|
|
41
|
+
## 2. The headers of the extensions involved
|
|
42
|
+
```
|
|
43
|
+
awk '/^import /{exit} 1' extensions/team/index.ts # everything above the imports
|
|
44
|
+
```
|
|
45
|
+
Read only the headers of the extensions your work touches. All of them at once is over 13,000 characters.
|
|
46
|
+
|
|
47
|
+
## 3. Then only the code you will change
|
|
48
|
+
Use `grep -n` to find the function, then `read` with `offset` (its first line) and `limit` for that part.
|
|
49
|
+
A whole file can be long (team's index.ts is over 15,000 characters). If you are planning work for other agents,
|
|
50
|
+
the headers are usually enough: let each agent read the code it owns.
|
|
51
|
+
|
|
52
|
+
## Keep the headers true
|
|
53
|
+
When you change what an extension provides (a tool, a route, a page message, an event, a host service), update
|
|
54
|
+
its header in the same change, and follow the naming rule. Other agents, and you after a reload, rely on it. A new
|
|
55
|
+
extension is a new folder with an `index.ts` whose header starts with a one-line summary: that line is its entry
|
|
56
|
+
in the map. After editing, call the reload tool.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: show-me
|
|
3
|
+
description: Answer "how do I…", "what does this do", "what can I do here" questions about Chalky itself by showing, not telling - explain each thing in a note pinned on it, use the page's own tools in front of the user with your pointer (a spare board, a test file), then hand over. Use when the user asks how to use Chalky, a tool, a button or a panel, or asks to be shown or walked through something on the page.
|
|
4
|
+
---
|
|
5
|
+
# Showing the user how Chalky works
|
|
6
|
+
|
|
7
|
+
The user learns a tool by seeing it used on their own screen. So answer a how-question by doing it on the page,
|
|
8
|
+
slowly, while they watch: say what the thing is, right on it, use it as they would, show what came out, then let
|
|
9
|
+
them try. A text card or a long reply describing the tools is the thing to avoid: it is what they asked you not to
|
|
10
|
+
do. Don't build a tour app either: they asked about the page they have, not about what you could build.
|
|
11
|
+
|
|
12
|
+
## How to show
|
|
13
|
+
- **Show what they asked about.** "How do frames work?" is one demo, shown fully. "What can I do?" is a short tour:
|
|
14
|
+
two or three tools, then offer the rest. "Show me all the tools" is all of them: keep each one short, one step
|
|
15
|
+
per tool (below). Ask nothing first: start, they can stop you.
|
|
16
|
+
- **One step per tool, in a tour.** Send a tool's calls together, in one step: delete the last tool's note, pin a
|
|
17
|
+
note on this tool's button, and one page_run that uses it once. For example, for Frames:
|
|
18
|
+
`note_done("n4", delete: true)`, `note_add(on: {element: "frames"}, text: "Frames: drag a box where something
|
|
19
|
+
should go and write what goes in it; Implement sends them to an agent. I'm drawing one now.")`, and `page_run`
|
|
20
|
+
that clicks the tool, drags a box at an empty spot and types in it. A tool that is better explained than used
|
|
21
|
+
(Dictate needs their microphone; Roll back is theirs to press) gets only the note.
|
|
22
|
+
- **Explain on the thing, in a note.** Before using a tool, pin a note on it (note_add) with a sentence or two: what
|
|
23
|
+
it is for and what you are about to do. The user reads it right where your pointer goes; your text between tool
|
|
24
|
+
calls never reaches the page. When you move on to the next thing, delete it (note_done(id, delete: true)) so only
|
|
25
|
+
one explanation is on the page at a time. For a quick pointer without words to read ("this one"), circle it in ink
|
|
26
|
+
instead (doodle_mark), and clear the mark (doodle_clear) when you move on.
|
|
27
|
+
- **Act with your pointer.** Use page_run's helpers (interact-with-page skill): click, type, drag, press. They move
|
|
28
|
+
your pointer, so the user can follow. One or two actions per page_run, ending with a small read, so you see what
|
|
29
|
+
happened before the next step. Check results with read() or a line of DOM code; look at the screen (screen_see)
|
|
30
|
+
only when the look itself matters.
|
|
31
|
+
- **Use spots for empty space.** Tools that work on the page itself (frames, the pen, text, the eraser, Point) need a
|
|
32
|
+
place, not a button: give a spot {x, y} in window px. `await drag({x: 700, y: 300}, 300, 180)` draws a frame or a
|
|
33
|
+
pen stroke there, `await click({x, y})` points or places, `await type({x, y}, "words")` clicks there and types
|
|
34
|
+
(into the box the click opened, or as the Text tool's words). Choose spots on the empty part of the board: first
|
|
35
|
+
close a panel that covers it (messages, versions: click its rail button), then check the spot, e.g.
|
|
36
|
+
`document.elementFromPoint(x, y).closest('#top') && !document.elementFromPoint(x, y).closest('[data-card]')`.
|
|
37
|
+
- **Never on their work.** Demo on a spare board (click "+" on the boards shelf) or with a test file you make.
|
|
38
|
+
Don't change, move or delete their cards, files or notes to show something.
|
|
39
|
+
- **Hand over, then tidy.** End with their turn: say in a sentence what to do next ("drag a box on the empty space
|
|
40
|
+
and type in it"), with the tool left on for them. Delete your explaining notes and clear your marks. Leave no
|
|
41
|
+
other tool on. Then ask whether to keep what the demo made (the board, the test file, a frame) or remove it, and
|
|
42
|
+
do what they say.
|
|
43
|
+
- **Point at what you must not press.** Rollback, Stop and deleting their things are theirs: put a note on the
|
|
44
|
+
button saying what it does. (The page refuses a rollback from your code anyway.)
|
|
45
|
+
- **If they take over** (page_run says so), stop: they are trying it. When they hand back, read the page again
|
|
46
|
+
before going on; they may have done the next step themselves.
|
|
47
|
+
- **Keep the reply short.** The page showed it. Your final reply says in two or three sentences what they saw and
|
|
48
|
+
what they can try.
|
|
49
|
+
|
|
50
|
+
## The page, for showing
|
|
51
|
+
Names are the data-name to use in note_add / doodle_mark (on: {element: "..."}) and page_run (`[data-name=...]`).
|
|
52
|
+
The toolbar's order changes: find things by name, never by position.
|
|
53
|
+
|
|
54
|
+
| What | Name | What it does, and how to show it |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| Point / notes | note | Click anything (or draw around an area) and write: a note pinned to that thing, a thread with an agent. Show: click the tool, click a spot on a thing, type, Enter. |
|
|
57
|
+
| Doodle pen | pen | Draw and write on the page, click through it; Enter in the message line sends the drawing as steps (don't send it). Show: click the tool, drag at a spot. |
|
|
58
|
+
| Text | text | Words placed on the page, sent with the doodle. Show: click the tool, type at a spot. |
|
|
59
|
+
| Eraser | eraser | Takes away lines, words or unsent frames. Show: click the tool, drag over what you drew. |
|
|
60
|
+
| Hide marks | show-marks | Hides or shows all marks. |
|
|
61
|
+
| Frames | frames | Boxes the user lays out for agents to fill. Show: click the tool, drag a box at a spot, type in its box, then Implement (implement) sends them to an agent. |
|
|
62
|
+
| Files | files | Opens the files panel: everything in the drops folder. A row dragged onto the page opens a note there; a click opens a preview (spreadsheets, PDF, Word, slides, pictures). An empty folder shows an empty panel: write a test file into it first. |
|
|
63
|
+
| Microphone | dictate | Speech to text in the message line. |
|
|
64
|
+
| Messages / Versions | messages, versions | The left rail: the conversation; and the history of the page, with a picture of each version and Roll back. |
|
|
65
|
+
| Boards shelf | boards, new-board | Numbered boards; "+" makes a new one and goes to it. |
|
|
66
|
+
| Message line | message-line | Where the user types to the agents. Don't send messages through it. |
|
|
@@ -19,7 +19,7 @@ skills/<name>/ in your code folder (the one your prompt names, with extension
|
|
|
19
19
|
assets/ optional: templates, data files
|
|
20
20
|
```
|
|
21
21
|
A skill that only one feature needs goes in that extension's folder instead, `extensions/<name>/skills/<skill>/`
|
|
22
|
-
(as `extensions/
|
|
22
|
+
(as `extensions/app/skills/`), and its `index.ts` gives the folder to pi:
|
|
23
23
|
`pi.on("resources_discover", () => ({ skillPaths: [path.join(import.meta.dirname, "skills")] }))`.
|
|
24
24
|
|
|
25
25
|
## SKILL.md
|
package/bin/chalky.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// chalky: starts Chalky from the installed package. Its home (
|
|
3
|
-
// ~/.chalky; the first run copies the package's
|
|
2
|
+
// chalky: starts Chalky from the installed package. Its home (agent/, built/, data/, workspace/) is CHALKY_HOME,
|
|
3
|
+
// else ~/.chalky; the first run copies the package's agent/ there. Node won't run TypeScript under node_modules
|
|
4
4
|
// (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so server.ts loads through jiti, as pi loads extensions.
|
|
5
5
|
// MODEL_BASE_URL=... MODEL_API_KEY=... [MODEL_NAME=...] chalky
|
|
6
6
|
import os from "node:os";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chalky/chalky",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Chalky on pi: agents build what you see in the browser, and you point at it and mark it up",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin/",
|
|
12
12
|
"server.ts",
|
|
13
|
-
"
|
|
14
|
-
"!
|
|
15
|
-
"!
|
|
16
|
-
"!
|
|
17
|
-
"!
|
|
18
|
-
"!
|
|
19
|
-
"
|
|
13
|
+
"agent/",
|
|
14
|
+
"!agent/auth.json",
|
|
15
|
+
"!agent/models-store.json",
|
|
16
|
+
"!agent/models.json",
|
|
17
|
+
"!agent/mcp-auth.json",
|
|
18
|
+
"!agent/mcp.json",
|
|
19
|
+
"!agent/ui/"
|
|
20
20
|
],
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
32
|
+
"diff3": "0.0.3",
|
|
32
33
|
"dom-to-image-more": "3.11.0",
|
|
33
34
|
"dompurify": "3.4.17",
|
|
34
35
|
"isomorphic-git": "1.44.2",
|