Must invoke when presenting a file to the user. The CharlieBot server has a built-in file browser that serves any file on the host filesystem.
Install
npx skillscat add chnlich/charlie-bot/file-server Install via the SkillsCat registry.
File Server
Share files and directories with the user by generating links to the CharlieBot file server. The
server serves any file on the host filesystem.
URL Format
<base_url>/absolute_filepath/<absolute-path>Where <base_url> is the CharlieBot URL resolved from HOST MEMORY (look for the CharlieBot URL
entry).
The path after /absolute_filepath/ is the absolute filesystem path with its leading / removed.
The prefix names what has to follow it, so a path that dropped its leading segments reads as wrong
where it is written. This is the sole canonical prefix: the legacy /files and /file spellings
are unmounted and answer 404, so never write or repair a link onto them. The file server sits
behind the access key — an unauthenticated browser navigation gets the unlock form, not the file.
Examples:
| Filesystem path | URL |
|---|---|
/path/to/trace.json |
<base_url>/absolute_filepath/path/to/trace.json |
/path/to/results/ |
<base_url>/absolute_filepath/path/to/results/ |
Publish Lane
Links that reach readers beyond the operator come from charliebot publish <artifact-path>: the
command copies the file to <publish.dir>/<token>/<basename> and prints the published URL,<publish.public_base_url>/<token>/<basename>. The token is 22 random URL-safe characters, fresh
per publish, so publishing the same file twice gives two links and overwrites nothing. The host
serves the publish directory publicly, so anyone holding a link opens it, and a link cannot be
guessed from the page name. Publishing refuses unless <publish.dir>/index.html exists: without
it the host's static server would list the directory, and that listing would expose every link.
Reader links come only from charliebot publish: the Slack and Discord reply paths post the
reply text as written — they never rewrite links — and a reply that still contains a file-server
URL is refused with a 422 naming the link and the publish command that fixes it. The server-port
links above serve the operator's own review in the browser and the chat embeds.
Behavior
- File path: returns the file for download (auto-detected MIME type)
- Directory path: returns an HTML directory listing with clickable entries
When to Use
Whenever you need to present a file to the user (logs, traces, checkpoints, images, configs, and so
on), generate the URL instead of dumping file contents into chat. This is especially useful for:
- Large files (traces, logs, pickles)
- Binary files (images, model checkpoints)
- Directories the user may want to browse
Rules
Every file link is certified before it is pasted: probe the link's exact prefix and path on
the local server,curl -s --noproxy '*' -o /dev/null -w '%{http_code}' http://localhost:<server_port>/<prefix>/<path>, and read 200. The pasted link's public base
URL names the same server, so the local 200 certifies the path the reader opens; probe and
paste share the exact prefix and path, and nothing is reconstructed from memory.Present the link in markdown format:
[descriptive text](url)If the file is a Perfetto/Chrome trace (
.jsontrace from training/profiling, or a directory
of rank traces), ALWAYS also include a Perfetto viewer link alongside the file link. Read theperfettoskill for how to construct the viewer URL.Trace indicators: filename matches
trace_rank*.json/*trace*.json, lives under atrace/orprofile/dir, or the user called it a "trace"/"profile"/"perf capture".Write a raw filesystem path as plain text,
path:line, since the CharlieBot UI renders a markdown
link around a raw local path as a dead link. The form to wrap in[descriptive text](url)is a
file-server URL.An artifact shared beyond the operator (a Slack post, a group report) ships comment-disabled: the
comment tray belongs to the operator's own review flow.
HTML Artifacts
Generated HTML artifacts (reports, plans, dashboards) must satisfy:
- Full document with doctype, html, body tags.
- Self-contained: inline CSS/JS. External resources from
cdn.jsdelivr.netorunpkg.comonly. - Sandboxing: chat embeds render via srcdoc + sandbox attribute (no access to parent
window, cookies, or storage). The plan panel viewer runs same-origin without sandbox
because its in-frame comment tray requires same-origin; plan artifact content is
trusted master-authored output. - Aim for well-organized, visually polished pages that present information more densely
than markdown allows. Multiple artifacts per response are supported.
Cold-Read Gate
The gate applies to every genre one step before the page leaves the author: before eachcharliebot plan present or amend for a plan page, and before sharing for
understanding, sitrep, debug, and explain pages.
Cold-read gate: the page first passes its genre's mechanical DOM assertions, then one
zero-context model pass reads the file alone and answers seven questions: (1) the
problem, (2) the conclusion and its epistemic state, (3) what is asked of the reader,
(4) the section where the problem first became clear, (5) up to five re-read points,
(6) whether the page answers the trigger quoted in the prompt, and (7) the project terms whose
gloss is missing at first use, read for the Vocabulary rule's reader (prompts/master.md). Ship when answers (1)
through (3) match the author's intent, (4) names the first content section, the
epistemic state in (2) matches the page's own labels, (6) is a yes on every part of
the trigger message, and (7) is none; for plan and understanding pages, (3) names the
decisions the page asks for (Trade-offs or divergences). A re-read point in (5) naming
section 1's forks, or a jump between a fork and another section, also means revise
and re-run. Judge on these signals alone.
The trigger quoted in question 6 is the chat message that asked for the page; a plan
quotes the confirmed understanding's goal sentence verbatim and falls back to the
originating request when no understanding exists.
Invocation (the genre's assertions run first; the probe only fires once every one of
them passed):
charliebot artifact check <page-file> --genre <plan|understanding|sitrep|debug|explain> --trigger "<trigger message verbatim>"--assertions-only runs the assertions alone and mirrors the plan registration gate.
The seven-question prompt, the backend order, and the timeout live insrc/core/artifact_check.py; the command prints each tried backend's failure, then the
answering backend's id and the seven answers verbatim. Exit 0 means every assertion
passed; judging the answers stays with the reader of this gate.