@timqi/pier 0.0.17 → 0.0.20
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/agent/config-sync.js +123 -0
- package/dist/agent/config.js +95 -3
- package/dist/agent/events.js +26 -5
- package/dist/agent/listing.js +12 -13
- package/dist/agent/pi.js +75 -27
- package/dist/boards/boards.js +7 -6
- package/dist/channels/attach.js +18 -4
- package/dist/channels/config.js +7 -2
- package/dist/channels/dedup.js +9 -1
- package/dist/channels/lark.js +2 -6
- package/dist/channels/receipts.js +21 -6
- package/dist/channels/slack-tool.js +86 -25
- package/dist/channels/slack.js +2 -10
- package/dist/channels/telegram.js +1 -0
- package/dist/config-sync-fetch.js +97 -0
- package/dist/config-sync-task.js +59 -0
- package/dist/config-sync.js +179 -0
- package/dist/core/hub.js +23 -6
- package/dist/core/inbox.js +1 -1
- package/dist/core/reply.js +76 -2
- package/dist/core/router.js +162 -37
- package/dist/db.js +50 -0
- package/dist/main.js +32 -1
- package/dist/secrets.js +18 -6
- package/dist/tasks/agent.js +25 -23
- package/dist/tasks/callbacks.js +1 -1
- package/dist/tasks/definitions.js +28 -17
- package/dist/tasks/execution.js +11 -3
- package/dist/tasks/messages.js +58 -16
- package/dist/tasks/routes.js +58 -10
- package/dist/tasks/runs.js +0 -5
- package/dist/tasks/service.js +14 -4
- package/dist/tasks/store.js +87 -10
- package/dist/tasks/tool.js +8 -4
- package/dist/tools.js +32 -2
- package/dist/web/auth.js +17 -23
- package/dist/web/config-sync.js +91 -0
- package/dist/web/config.js +61 -0
- package/dist/web/explorer.js +20 -70
- package/dist/web/fs.js +159 -0
- package/dist/web/instance.js +11 -0
- package/dist/web/public/assets/activity-DYo7QS7k.js +5 -0
- package/dist/web/public/assets/activity-DYo7QS7k.js.br +0 -0
- package/dist/web/public/assets/activity-DYo7QS7k.js.gz +0 -0
- package/dist/web/public/assets/boards-CwohTbtv.js +1 -0
- package/dist/web/public/assets/boards-CwohTbtv.js.br +0 -0
- package/dist/web/public/assets/boards-CwohTbtv.js.gz +0 -0
- package/dist/web/public/assets/code-p-Tq1bMK.js +1 -0
- package/dist/web/public/assets/explorer-50no_DrD.js +5 -0
- package/dist/web/public/assets/explorer-50no_DrD.js.br +0 -0
- package/dist/web/public/assets/explorer-50no_DrD.js.gz +0 -0
- package/dist/web/public/assets/{ghostty-web-CV7F9_iW.js → ghostty-web-Bvhh3ZFk.js} +1 -1
- package/dist/web/public/assets/ghostty-web-Bvhh3ZFk.js.br +0 -0
- package/dist/web/public/assets/ghostty-web-Bvhh3ZFk.js.gz +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js +3 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.br +0 -0
- package/dist/web/public/assets/hljs-tWqyD59G.js.gz +0 -0
- package/dist/web/public/assets/{index-CtR-Ob4T.css → index-DuZ2vERd.css} +1 -1
- package/dist/web/public/assets/index-DuZ2vERd.css.br +0 -0
- package/dist/web/public/assets/index-DuZ2vERd.css.gz +0 -0
- package/dist/web/public/assets/index-WrsNstrb.js +79 -0
- package/dist/web/public/assets/index-WrsNstrb.js.br +0 -0
- package/dist/web/public/assets/index-WrsNstrb.js.gz +0 -0
- package/dist/web/public/assets/runs-BAXZF-6r.js +1 -0
- package/dist/web/public/assets/runs-BAXZF-6r.js.br +0 -0
- package/dist/web/public/assets/runs-BAXZF-6r.js.gz +0 -0
- package/dist/web/public/assets/settings-BmNXgRne.js +6 -0
- package/dist/web/public/assets/settings-BmNXgRne.js.br +0 -0
- package/dist/web/public/assets/settings-BmNXgRne.js.gz +0 -0
- package/dist/web/public/assets/task-runs-CpgHxFLf.js +3 -0
- package/dist/web/public/assets/task-runs-CpgHxFLf.js.br +0 -0
- package/dist/web/public/assets/task-runs-CpgHxFLf.js.gz +0 -0
- package/dist/web/public/assets/tasks-DybtogPF.js +4 -0
- package/dist/web/public/assets/tasks-DybtogPF.js.br +0 -0
- package/dist/web/public/assets/tasks-DybtogPF.js.gz +0 -0
- package/dist/web/public/assets/terminal-m2_WYu5W.js +2 -0
- package/dist/web/public/assets/terminal-m2_WYu5W.js.br +0 -0
- package/dist/web/public/assets/terminal-m2_WYu5W.js.gz +0 -0
- package/dist/web/public/icon.svg.br +0 -0
- package/dist/web/public/icon.svg.gz +0 -0
- package/dist/web/public/index.html +13 -2
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/manifest.webmanifest.br +0 -0
- package/dist/web/public/manifest.webmanifest.gz +0 -0
- package/dist/web/public/sw.js.br +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/route.js +17 -0
- package/dist/web/server.js +152 -66
- package/dist/web/terminal.js +2 -7
- package/package.json +2 -2
- package/skills/pier-slack/SKILL.md +4 -16
- package/skills/pier-tasks/SKILL.md +14 -9
- package/dist/web/files.js +0 -183
- package/dist/web/public/assets/index-DAxKtidH.js +0 -93
package/dist/web/files.js
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
// Filesystem-facing routes: scoped Pi config editing, directory browsing for
|
|
2
|
-
// the cwd pickers, and session attachments. Split from server.ts, which keeps
|
|
3
|
-
// the session/queue/SSE routes; `guarded` lives here because both halves wrap
|
|
4
|
-
// their route bodies with it.
|
|
5
|
-
import { mkdir, readdir, readFile, realpath, stat } from "node:fs/promises";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
import { basename, dirname, extname, isAbsolute, resolve, sep } from "node:path";
|
|
8
|
-
import { INBOX_DIR } from "../core/inbox.js";
|
|
9
|
-
// Content types for the attachment route. Anything unlisted downloads as
|
|
10
|
-
// bytes — guessing a type we can't vouch for is how a file starts executing.
|
|
11
|
-
const FILE_TYPES = {
|
|
12
|
-
".png": "image/png",
|
|
13
|
-
".jpg": "image/jpeg",
|
|
14
|
-
".jpeg": "image/jpeg",
|
|
15
|
-
".gif": "image/gif",
|
|
16
|
-
".webp": "image/webp",
|
|
17
|
-
".avif": "image/avif",
|
|
18
|
-
".bmp": "image/bmp",
|
|
19
|
-
".pdf": "application/pdf",
|
|
20
|
-
".txt": "text/plain; charset=utf-8",
|
|
21
|
-
".md": "text/plain; charset=utf-8",
|
|
22
|
-
".log": "text/plain; charset=utf-8",
|
|
23
|
-
".json": "text/plain; charset=utf-8",
|
|
24
|
-
".csv": "text/plain; charset=utf-8",
|
|
25
|
-
".yaml": "text/plain; charset=utf-8",
|
|
26
|
-
".yml": "text/plain; charset=utf-8",
|
|
27
|
-
};
|
|
28
|
-
const MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
29
|
-
/** Register a route whose body may throw: the error becomes the JSON shape
|
|
30
|
-
* the UI expects, with the status the route owns. Takes the literal path so
|
|
31
|
-
* `c.req.param()` keeps its typed keys. */
|
|
32
|
-
export function guarded(app, method, path, status, fn) {
|
|
33
|
-
app.on(method, path, async (c) => {
|
|
34
|
-
try {
|
|
35
|
-
return await fn(c);
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
return c.json({ error: String(err) }, status);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
export function registerFileRoutes(app, { factory, config, nascentCwd, onConfigWritten }) {
|
|
43
|
-
// Scope comes from the client as "global" or a project cwd; only cwds Pi
|
|
44
|
-
// already knows (the session list) are accepted — never an arbitrary path.
|
|
45
|
-
const parseScope = async (raw) => {
|
|
46
|
-
if (!raw || raw === "global")
|
|
47
|
-
return { kind: "global" };
|
|
48
|
-
const known = await factory.list();
|
|
49
|
-
return known.some((s) => s.cwd === raw) ? { kind: "project", cwd: raw } : null;
|
|
50
|
-
};
|
|
51
|
-
app.get("/api/config", async (c) => {
|
|
52
|
-
c.header("cache-control", "no-store");
|
|
53
|
-
const scope = await parseScope(c.req.query("scope"));
|
|
54
|
-
if (!scope)
|
|
55
|
-
return c.json({ error: "unknown scope" }, 400);
|
|
56
|
-
return c.json({
|
|
57
|
-
// Where this scope's files live on disk — the UI labels "Global" with it.
|
|
58
|
-
dir: scope.kind === "global" ? config.globalDir : scope.cwd,
|
|
59
|
-
files: await config.listFiles(scope),
|
|
60
|
-
resources: await config.listResources(scope),
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
guarded(app, "GET", "/api/config/files/:name", 400, async (c) => {
|
|
64
|
-
c.header("cache-control", "no-store");
|
|
65
|
-
const scope = await parseScope(c.req.query("scope"));
|
|
66
|
-
if (!scope)
|
|
67
|
-
return c.json({ error: "unknown scope" }, 400);
|
|
68
|
-
return c.json({ content: await config.readFile(scope, c.req.param("name")) });
|
|
69
|
-
});
|
|
70
|
-
guarded(app, "PUT", "/api/config/files/:name", 400, async (c) => {
|
|
71
|
-
const scope = await parseScope(c.req.query("scope"));
|
|
72
|
-
if (!scope)
|
|
73
|
-
return c.json({ error: "unknown scope" }, 400);
|
|
74
|
-
const body = await c.req.json().catch(() => null);
|
|
75
|
-
if (typeof body?.content !== "string" || typeof body?.expected !== "string") {
|
|
76
|
-
return c.json({ error: "content and expected content required" }, 400);
|
|
77
|
-
}
|
|
78
|
-
const name = c.req.param("name");
|
|
79
|
-
await config.writeFile(scope, name, body.content, body.expected);
|
|
80
|
-
onConfigWritten?.();
|
|
81
|
-
return c.json({ ok: true, content: await config.readFile(scope, name) });
|
|
82
|
-
});
|
|
83
|
-
// Resource names may contain slashes — query params, not path params.
|
|
84
|
-
guarded(app, "GET", "/api/config/resource", 400, async (c) => {
|
|
85
|
-
c.header("cache-control", "no-store");
|
|
86
|
-
const scope = await parseScope(c.req.query("scope"));
|
|
87
|
-
if (!scope)
|
|
88
|
-
return c.json({ error: "unknown scope" }, 400);
|
|
89
|
-
const kind = c.req.query("kind");
|
|
90
|
-
const name = c.req.query("name");
|
|
91
|
-
if ((kind !== "extensions" && kind !== "skills") || !name) {
|
|
92
|
-
return c.json({ error: "kind and name required" }, 400);
|
|
93
|
-
}
|
|
94
|
-
return c.json({ content: await config.readResource(scope, kind, name) });
|
|
95
|
-
});
|
|
96
|
-
/** Real path of a file a session may expose: inside its cwd or the inbox
|
|
97
|
-
* (where inbound user attachments land — core/inbox.ts), nothing else. */
|
|
98
|
-
const resolveFile = async (id, raw) => {
|
|
99
|
-
const cwd = nascentCwd(id) ?? (await factory.find(id))?.cwd;
|
|
100
|
-
if (!cwd || !isAbsolute(raw))
|
|
101
|
-
return null;
|
|
102
|
-
try {
|
|
103
|
-
// realpath both ends, so neither `..` nor a symlink can step outside.
|
|
104
|
-
const roots = [await realpath(cwd)];
|
|
105
|
-
try {
|
|
106
|
-
roots.push(await realpath(INBOX_DIR));
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
/* no inbox yet — nothing was ever uploaded */
|
|
110
|
-
}
|
|
111
|
-
const target = await realpath(resolve(roots[0], raw));
|
|
112
|
-
if (!roots.some((root) => target === root || target.startsWith(root + sep)))
|
|
113
|
-
return null;
|
|
114
|
-
return (await stat(target)).isFile() ? target : null;
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return null; // missing, unreadable, or not a file
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
// Attachments, both directions: the agent links a file it produced, the
|
|
121
|
-
// chat renders a file the user sent — the client fetches the bytes here,
|
|
122
|
-
// read-only, and only from the session's own cwd or the inbox.
|
|
123
|
-
app.get("/api/sessions/:id/files", async (c) => {
|
|
124
|
-
const raw = c.req.query("path");
|
|
125
|
-
if (!raw)
|
|
126
|
-
return c.json({ error: "path required" }, 400);
|
|
127
|
-
const file = await resolveFile(c.req.param("id"), raw);
|
|
128
|
-
if (!file)
|
|
129
|
-
return c.json({ error: "no such file" }, 404);
|
|
130
|
-
const { size } = await stat(file);
|
|
131
|
-
if (size > MAX_FILE_BYTES)
|
|
132
|
-
return c.json({ error: "file too large" }, 413);
|
|
133
|
-
const name = basename(file);
|
|
134
|
-
// Unknown extension → octet-stream: never let the browser guess a type we
|
|
135
|
-
// can't vouch for (that is how an attachment starts executing).
|
|
136
|
-
const type = FILE_TYPES[extname(file).toLowerCase()] ?? "application/octet-stream";
|
|
137
|
-
const disposition = c.req.query("download") === "1" ? "attachment" : "inline";
|
|
138
|
-
return c.body(await readFile(file), 200, {
|
|
139
|
-
"content-type": type,
|
|
140
|
-
"content-disposition": `${disposition}; filename*=UTF-8''${encodeURIComponent(name)}`,
|
|
141
|
-
"cache-control": "private, max-age=60",
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
// Directory browsing for the working-directory picker (new session, IM chat
|
|
145
|
-
// config). Names only, never contents — the file route above stays the only
|
|
146
|
-
// way to read bytes, and it is scoped to a session's own cwd.
|
|
147
|
-
app.get("/api/fs/dirs", async (c) => {
|
|
148
|
-
const raw = c.req.query("path");
|
|
149
|
-
const path = raw ? resolve(raw) : homedir();
|
|
150
|
-
if (!isAbsolute(path))
|
|
151
|
-
return c.json({ error: "absolute path required" }, 400);
|
|
152
|
-
let entries = [];
|
|
153
|
-
try {
|
|
154
|
-
const dir = await readdir(path, { withFileTypes: true });
|
|
155
|
-
entries = dir
|
|
156
|
-
// Dotfiles are noise in a project picker; a hidden cwd can still be
|
|
157
|
-
// typed by hand.
|
|
158
|
-
.filter((e) => e.isDirectory() && !e.name.startsWith("."))
|
|
159
|
-
.map((e) => e.name)
|
|
160
|
-
.sort((a, b) => a.localeCompare(b));
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
return c.json({ error: "cannot read directory" }, 404);
|
|
164
|
-
}
|
|
165
|
-
const parent = dirname(path);
|
|
166
|
-
return c.json({ path, parent: parent === path ? null : parent, entries });
|
|
167
|
-
});
|
|
168
|
-
// Create a folder while picking one — a new project usually needs a new
|
|
169
|
-
// directory. A name, never a path: traversal is rejected, not normalized.
|
|
170
|
-
guarded(app, "POST", "/api/fs/dirs", 400, async (c) => {
|
|
171
|
-
const body = await c.req.json().catch(() => null);
|
|
172
|
-
const parent = typeof body?.path === "string" ? body.path : "";
|
|
173
|
-
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
174
|
-
if (!isAbsolute(parent))
|
|
175
|
-
return c.json({ error: "absolute path required" }, 400);
|
|
176
|
-
if (!name || name.length > 64 || /[/\\]|^\.\.?$/.test(name)) {
|
|
177
|
-
return c.json({ error: "invalid folder name" }, 400);
|
|
178
|
-
}
|
|
179
|
-
const path = resolve(parent, name);
|
|
180
|
-
await mkdir(path); // no recursive: the parent must already exist
|
|
181
|
-
return c.json({ path });
|
|
182
|
-
});
|
|
183
|
-
}
|