Reference
@chloejs/ core/ services
The work a job does without asking anybody, called from a step.
What a job can do without asking anybody: running a command, sending mail, reading mail, reading and writing files in one folder, running one of an agent's own scripts, reading a web page.
import { run, deliverEmail } from "@chloejs/core/services";The same work offered to a model instead is "@chloejs/core/tools/index.ts: adding a
name here is publishing it.
deliverEmaileditFilesEmailSenderfeedToTextfolderTreehtmlToTextisPrivatelistFileslistScriptsmarkdownToHtmlmarkdownToTextPagereadFilesreadPageResultrunrunScriptssearchFileswriteFiles
deliverEmailfunction
export async function deliverEmail(
{ from, to, tag, replyTo, markdown }: EmailSender,
subject: string,
body: string,
provider?: SendingProvider,
): Promise<{ sent: true; id?: string; subject: string }>Sends one email and returns its id. The tag is put in front of the subject.
It goes by provider, else by email.provider in settings, and by nothing
when settings say "none".
editFilesfunction
export async function editFiles(
root: string,
path: string,
old: string,
replacement: string,
options: { commit?: boolean; message?: string; author?: string; in?: Place } = {},
)Change one part of a file: old must appear in it exactly once, and is
replaced by new, so a small change to a big file never sends the whole
file. Throws when old is not there or is there more than once, saying
which, so the caller can add the text around it. An empty new deletes
old. commit, message, author and in are as for writeFiles.
EmailSenderinterface
export interface EmailSender {
/** The From line, e.g. "Backups <info@example.com>". */
from: string;
/** Who it goes to. */
to: string[];
/** Prefix put in front of every subject, so an inbox can be filtered. */
tag?: string;
/** Where a reply goes, when not to `from`: a sending address that cannot receive mail needs this. */
replyTo?: string[];
/**
* The body is Markdown: it is sent as HTML with headings, lists, tables and
* links, plus a plain text copy without the symbols. Off, it is sent as
* written, as plain text only.
*/
markdown?: boolean;
}Who an agent's mail comes from, who it goes to, and the tag in front of every subject.
feedToTextfunction
export function feedToText(xml: string, base: string): { title?: string; text: string }An Atom or RSS feed as text: each entry is its title as a link, who wrote it and when, then its words. Raw, a feed's words are HTML escaped inside XML, and a slice of it holds a few entries where this holds a page of them.
folderTreefunction
export async function folderTree(root: string, { depth = 2, most = 200 } = {}): Promise<string[]>The folders inside root, depth levels down, one a line, each level
indented two spaces further. Files, hidden folders and the ones no file tool
may open are left out. At most most lines, then a line saying more were
left out.
htmlToTextfunction
export function htmlToText(html: string, base: string): { title?: string; text: string }HTML to readable text: blocks become lines, cells are split by " | ", links keep their address.
isPrivatefunction
export function isPrivate(address: string): booleanLoopback, private ranges, link local, multicast, and the same in IPv6, including an IPv4 address carried inside an IPv6 one in any spelling.
listFilesfunction
export async function listFiles(root: string, path?: string)List a folder. path is relative to root, and omitting it means the top.
listScriptsfunction
export async function listScripts(agent: string): Promise<string[]>What this agent has in scripts/, sorted. Nothing hidden.
markdownToHtmlfunction
export function markdownToHtml(markdown: string): stringMarkdown as email HTML: ## headings, - lists, | tables, > quotes,
fenced code, bold, code and links. Lines next to each other are one
paragraph, so a body wrapped at any width reads as prose rather than as a
ragged line per paragraph. Only a blank line starts a new one.
markdownToTextfunction
export function markdownToText(markdown: string): stringThe same Markdown as plain text: no #, ** or backticks, a table row as a: b, a link as words (address).
Pageinterface
export interface Page {
url: string;
status: number;
title?: string;
/** The page as plain text, links written as `[text](url)`. */
text: string;
/** Where the next slice starts, when the page was longer than one. */
next?: number;
}One fetched web page as plain text, in slices when it is longer than one.
readFilesfunction
export async function readFiles(
root: string,
path: string,
{ from, lines, limit }: { from?: number; lines?: number; limit?: number } = {},
)Read one file. path is relative to root and cannot leave it.
With from (the first line, counting from 1) or lines (how many), only
that part comes back, with from, to and the file's totalLines. With
limit (characters) and no range, a longer file comes back cut at the last
whole line that fits, saying so, so a big file is not read whole by
accident. With none of the three it is the whole file, as it always was.
readPagefunction
export async function readPage(address: string, from = 0): Promise<Page>Fetches address and returns it as text, a slice at a time starting at from.
Resultinterface
export interface Result {
exitCode: number;
stdout: string;
stderr: string;
}What a command came back with.
runfunction
export function run(
file: string,
args: string[],
options: { timeoutMs?: number; cwd?: string; env?: Record<string, string> } = {},
): Promise<Result>Runs one command with its arguments, never through a shell, and cuts output that is very long.
runScriptsfunction
export async function runScripts(
agent: string,
name: string,
args: string[] = [],
{ timeoutMs = 300_000, cwd }: { timeoutMs?: number; cwd?: string } = {},
): Promise<Result & { script: string; args: string[] }>Run one. A name not on disk is refused rather than resolved as a path, which
is what stops a ../ in a name reaching anything else on the box.
cwd defaults to the scripts folder, so a script may use relative paths.
An agent whose scripts work on a tree somewhere else passes that instead.
searchFilesfunction
export async function searchFiles(root: string, query: string, folder?: string, { around = 0 }: { around?: number } = {})Search a folder for text, case-insensitive and as written, not as a
pattern. folder narrows it, to a folder or one file. Paths in
the results are relative to root, the way the other functions here take
them. With around, each match comes with that many lines either side, and
matches close together in one file share one block, so a match can often be
understood without reading the file. Five matches a file at most, and
results stops at 80 matches, or 30 with around; matches counts what
was found before that.
writeFilesfunction
export async function writeFiles(
root: string,
path: string,
content: string,
{
commit = false,
message,
append = false,
author,
in: place,
}: { commit?: boolean; message?: string; append?: boolean; author?: string; in?: Place } = {},
)Write one file, replacing it, or with append adding to the end of it on a
line of its own, so a long file that only grows is never written out whole.
commit makes the write a git commit of that file alone, for a folder in a
repo, and then message is required. author is the agent it is written
under, and in which of its places this is, so the run writing it lists
the commit. Without an author it is this box's own git name.