Persona files

Persona files

Built-in personas ship with homebot. Your files go in $XDG_CONFIG_HOME/homebot/personas/<id>.toml (normally ~/.config/homebot/personas/<id>.toml). A local file with a built-in ID overrides that built-in. IDs are 1–32 ASCII characters: a lowercase letter followed by lowercase letters, digits or hyphens. none is reserved for turning the voice off. Run homebot persona ids for the current list of messages a persona may reword.

schema = 1
id = "tester" # optional; must match tester.toml
name = "Tester"
emoji = "🧪"
tagline = "A careful eye on the details"
extends = "neatpik" # optional; omitted means the embedded Neatpik
lang = "en" # optional

[colors]
accent = { light = "#31466B", dark = "#AFC4E9" }
ok = { light = "#245D38", dark = "#9DE0AF" }
muted = { light = 240, dark = 248 }

[lines]
"inbox.header.count" = ["{n:# thing needs|# things need} you."]

schema = 1, a name and one emoji are required. id is optional but, when present, must match the filename. The name is 1–32 characters. The tagline is one line of at most 100 characters and cannot contain $ or a backtick. lang is a BCP 47 language tag; English is the default. Colours may be #RRGGBB or ANSI indices 0–255. Each light/dark token omitted here is inherited from the parent. extends = "none" stops the chain at that file: the embedded Neatpik supplies nothing, so a colour the chain does not set stays the terminal’s own (persona show says terminal default for it), and a message with no line is said by the catalog’s English. Any other chain ends with the embedded Neatpik, then the catalog’s English. extends = "neatpik" is the default, so writing it is the same as omitting it: both mean the embedded copy, never a local neatpik.toml (that file only matters when you pick neatpik itself). Explicit parent chains may have at most four files, the persona included (the embedded Neatpik does not count), and cannot cycle. A [colors] that is not a table of accent, ok and muted, each with light and dark, makes the persona unusable. For each message ID, the first file in that chain with usable lines supplies all its variants.

File safety and checks

User files must be regular files of at most 256 KiB. A symlink is accepted only when it points to a regular file. Pipes, devices, sockets and folders are refused; reading cannot block the background helper on a pipe. Unknown TOML keys, invalid syntax, a bad header, an invalid ID or an invalid parent make the persona unusable. An invalid line variant is dropped; usable variants remain, and an ID with none falls through to its parent. homebot persona validate <id> loads the whole chain, as show and preview do, and prints every finding of every file in it to stderr. Each finding starts with the path of the file it is in, so a problem in a parent is not taken for one in the file you named. The path is relative to the config directory (personas/tester.toml) when the file is under it, and absolute otherwise. A missing or broken parent, a chain of more than four files and a cycle make the persona unusable, like a bad header in any of its files; invalid lines are reported as errors too, and validate exits with an error for them, although at load such a variant is only dropped (as said above). validate <file.toml> does the same for a file you have not installed yet, with the file as the first of the chain and the files it extends found by ID among the installed ones. With no argument it checks the persona in use; when that is none, there is no file to check and it says the neutral voice is selected. If homebot.yml cannot be loaded, the config error is reported as persona use reports it and the exit is 1; the persona named by HOMEBOT_PERSONA, if any, is still checked, and with none set there is nothing to check. Naming none is refused: it is reserved and has no file. Findings go to stderr, like every other warning and error of the CLI, so the stdout of show and preview is only what they print. homebot persona use <id> refuses a persona with any error in its chain, even a single bad line variant that would otherwise just be dropped, and homebot persona list marks such a persona [unusable] with its first error, the same verdict use and validate give: a persona is judged with the files it extends, so one under a broken parent, in a cycle or in a chain of five files is [unusable] too. When the error is in another file than the persona’s own, the reason starts with that file’s path, relative to the config directory when the file is under it. list still prints every persona when homebot.yml cannot be loaded, reports that error as use does, exits 1 and marks none as current.

Line IDs must be in homebot persona ids and marked for voice. A variant must parse, preserve required and fixed placeholders, plural counts (a count the English shows must still be shown, as a bare {n} or in a plural form with #: {n} {n:step|steps} and {n:# step|# steps} both show it, {n:step|steps} alone hides it), leading/trailing whitespace, a leading {name}: prefix, a leading ,, ; or (, placeholders that touch in the English ({a}{b}) still touching, punctuation that protects an appended command, and the original number of line breaks. An empty list of variants is an error too. The validator rejects control and format characters, including terminal escapes and bidi controls. In a line, it allows only the same newlines as the English; the emoji field alone may contain the joiner and variation marks needed for one emoji. It warns about low colour contrast, uncertain emoji width, duplicate or long variants, non-English language tags and possible added facts. A variant beyond the hard length limit is rejected. Run homebot persona preview <id> to see the voice in both themes, or add --event <message-id> and --all-variants to inspect individual lines. A slot the inbox fills with another message or a count is filled the same way (3 things need you (1 urgent)); any other slot shows a generic sample such as <profile>.

What is voiced

A persona words what homebot prints, not what it stores. The reasons homebot link gives for a link it will not make (a name that breaks the layout rules, something in the way in $HOME) are in the persona’s voice when printed, in link, link --dry-run, the inbox item and the error of the link unpack action. They are English, whatever the persona, in anything homebot stores, and when stdout is piped or --plain is set.

Selection and rotation

HOMEBOT_PERSONA (when nonempty) wins over persona: in homebot.yml; the default is Neatpik. none selects the neutral voice. A missing or unusable choice falls back to the embedded Neatpik, even if a broken local neatpik.toml shadows the built-in. A configured failure raises persona_broken in the inbox; a failing environment override warns once on a terminal. The neutral English is used when a message has no usable variant.

For each message ID, homebot picks one variant per process and tries not to repeat the previous process’s choice. Previous picks are stored per machine in $XDG_CACHE_HOME/homebot/voice.json; a cache read or write failure only changes the pick. The main nudge line uses a stable pick keyed on the inbox items; notifications and the nudge’s urgent suffix always use the same variant for a persona. Neither changes wording on every refresh. This state stays outside the home repo.

Generated file fields and voice IDs

These tables come from the accepted persona fields and the message catalog. They are updated by go run ./tools/gendocs docs.

Fields

Field Type Required Meaning
schema integer yes File format version; must be 1.
id string no Optional ID; when present, it must match the filename.
name string yes Speaker name, 1 to 32 characters.
emoji string yes One emoji grapheme, without ASCII characters.
tagline string no One line, at most 100 characters; no dollar sign or backtick.
extends string no Explicit parent ID; none stops explicit parent traversal, then embedded Neatpik remains the fallback.
lang string no BCP 47 language tag; English is the default.
colors object no Accent, ok and muted light/dark colours; missing values inherit.
lines object no Voice message IDs, each with an array of variants.

Voice IDs

ID Kind English
action.error.unavailable error {name}: this action is not available in this build
action.link.error.layout error {action} the repo breaks {n:a layout rule|# layout rules}:
action.link.error.unknown_tree error {action} unknown tree "{tree}" (use {autolink} or {private})
action.link.status.changed status {action} {n} changed
action.link.warning.in_way warning , {n} in the way (see: {cmd})
action.run.error.exit error {action} "{command}" exited with
action.unlock.status.decrypted status {action} {n} decrypted
action.unlock.status.nothing status {action} nothing to decrypt
action.unlock.warning.conflicts warning , {n} conflicts (see: {self}{unlock})
cleanup.diff.backup_is_link status The backup is a link to {dest}.
cleanup.diff.kind_mismatch status The backup is a {backup_kind}, but {path} is a {now_kind} now.
cleanup.diff.only_copy status Nothing is at {path} now; this backup is the only copy.
cleanup.diff.same status Identical to what is linked there now.
cleanup.error.merge_not_repo error {path} isn't a file in the repo, so there is nothing to merge into
cleanup.error.scratch_git error {git} {subcommand} in the merge scratch repo failed.
cleanup.error.use_not_repo error {path} isn't a file in the repo, so the backup can't go there
cleanup.hint.terminal hint Review them in a terminal:
cleanup.status.nothing_taken status Nothing taken from the backup.
cleanup.status.summary status Deleted {deleted}, used {used}, kept {kept}.
cleanup.status.summary_merged status Deleted {deleted}, used {used}, kept {kept}; merged lines {merged:# time|# times}.
cleanup.success.deleted success deleted
cleanup.success.merged success merged into {repo_path}; {self}{save} commits it
cleanup.success.none success No backups to review.
cleanup.success.used success copied into {repo_path} and deleted the backup; {self}{save} commits it
cli.error.args_hint hint See: {homebot}{command_space}
cli.error.unknown_action error No action named "{name}". See:
cli.error.unknown_command error Unknown command "{name}". See:
cli.error.version_args error {version} takes no arguments, not "{argument}". See:
config.error.autosave error {file}: {autosave} must be a mapping
config.error.autosave_commit error {file}: {commit} must be
config.error.autosave_push error {file}: {push} must be
config.error.export_duplicate error {file}: {exports_open}{i}{exports_close}: duplicate export name "{name}" (give each one {profiles} that don't overlap)
config.error.export_mapping error {where}: expected a mapping with
config.error.export_out_absolute error {where}: {out_key} {out} of export "{name}" is an absolute path; give a path inside the repo, relative to it
config.error.export_out_git error {where}: {out_key} {out} of export "{name}" is in {git_dir}, which belongs to git
config.error.export_out_managed error {where}: {out_key} {out} of export "{name}" is {managed}, which homebot keeps for itself; put exports under
config.error.export_out_managed_inside error {where}: {out_key} {out} of export "{name}" is inside {managed}, which homebot keeps for itself; put exports under
config.error.export_out_outside error {where}: {out_key} {out} of export "{name}" leads outside the repo
config.error.export_out_private error {where}: {out_key} {out} of export "{name}" is in {private}, where your secrets are
config.error.export_out_root error {where}: {out_key} {out} of export "{name}" is the repo itself; name a file or folder inside it
config.error.export_out_symlink error {where}: {out_key} {out} of export "{name}" goes through a symlink to {target}, which is outside the repo, the repo itself, or a path homebot keeps for itself
config.error.export_profiles error {where}.{profiles}: expected a list of profile names
config.error.export_required error {where}: {key_name} is required
config.error.exports error {file}: {exports} must be a list
config.error.limits error {file}: {limits} must be a mapping
config.error.limits_max error {file}: {max_file} must be a positive number
config.error.link_gone error {file}: "{link}" is gone. Wrap a folder's name in {marks} in {autolink} to keep it a real folder, e.g.
config.error.name error {where}: {name_key} must be a word, the command you call your homebot by, such as
config.error.name_syntax error {where}: {name_key} "{value}" must be
config.error.not_mapping error {file}: expected a mapping at the top level
config.error.old_name error homebot reads {config_file} now: rename {path} to
config.error.persona error {where}: {key} must be a string:
config.error.persona_syntax error {where}: {key} "{value}" must be
config.error.profiles error {file}: {profiles} must be a mapping
config.error.profiles_default error {file}: {default_key} must be a profile name
config.error.profiles_hosts error {file}: {hosts_key} must map host names to profile names
config.error.string_list error {where}: expected a string or a list of strings
config.error.triage error {file}: {triage} must be a mapping
config.error.triage_watch error {file}: {watch} entries end in {star} or {dot_star} (got "{value}")
config.error.unknown_key error {where}: unknown key "{key_name}" (expected {keys})
config.error.unknown_key_under error {where}: unknown key "{key_name}" under {parent} (expected {keys})
config.error.unknown_os error {where}: unknown OS "{value}" (use {linux} or {macos})
config.error.yaml error {file}: invalid {yaml}:
conflict.different error already exists with different content
conflict.file_vs_folder error is a file, but the repo has a folder here
conflict.folder_vs_file error is a folder, but the repo has a file here
conflict.symlink_outside error is a symlink to {dest}, outside this repo
context.error.no_config error No {config} found. Run {self} inside your home repo, or set {env}.
context.error.no_home error {env} is not set.
context.error.platform error homebot supports {oses}, not {platform}.
context.hint.setup hint New here? This clones your home repo or starts one:
export.error.empty_file error produced an empty file; kept the previous
export.error.empty_folder error produced an empty folder; kept the previous
export.error.exit error exited with {code}
export.error.kept_aside error {error}
The previous export is kept at {path}.
export.error.out_is_file error wrote a folder, but {out} is a file; kept the file. Remove it yourself, or give the export another
export.error.out_is_folder error wrote a file, but {out} is a folder; kept the folder. Remove it yourself, or give the export another
export.error.unknown error No export named {names} for this OS. Exports here: {known}.
export.reason.not_installed status {tool} is not installed
export.status.none status No exports configured for this OS in {config}.
export.warning.left_behind warning The temporary folder {path} could not be removed. It is safe to delete.
fsutil.error.bad_json error {path} is not valid {format}. Fix or delete it (it is machine state, safe to delete).
git.error.failed error {git} {args} failed.
helper.error.launchctl error {command} failed.
helper.error.no_systemd error No systemd user session here, so the hourly helper can't be installed.
Run this yourself, or from cron:
helper.error.systemctl error {command}{args} failed.
identity.error.config_failed error {git_config} {scope} {setting} {value} failed.
identity.hint.set_once hint Set it once:
identity.hint.track hint Track it so every machine gets it:
identity.prompt.email prompt Your email:
identity.prompt.name prompt Your name:
identity.status.skipped hint Skipped. Set it later:
identity.success.saved success saved {name} <{email}> in
identity.warning.unknown warning Git doesn't know who you are yet, and {bot} commits as you.
inbox.backups.title inbox {n:# backup|# backups} that {self}{link} moved aside: compare, then delete or keep
inbox.behind.title inbox {n:# new commit|# new commits} on the remote
inbox.blocked.title inbox A commit was refused
inbox.broken.title inbox Could not check
inbox.diverged.title inbox This machine and the remote both have new commits
inbox.done.heading status Done for you since you last looked:
inbox.export_failing.title inbox Export {name} is failing
inbox.gitconfig_shadow.detail inbox Git reads it after {repo_config}, so its name, email or signing win. Move it aside; {self}{cleanup} then shows it, and anything this machine needs goes in {local_config}.
inbox.gitconfig_shadow.title inbox {gitconfig} overrides the git config from the repo
inbox.header.clear success nothing needs you. Home is in order.
inbox.header.count inbox {n:# thing needs|# things need} you
inbox.helper_no_linger.detail inbox systemd ends your timers with your last session; with lingering on it keeps them running
inbox.helper_no_linger.title inbox The background helper stops when you log out
inbox.helper_stale.title inbox The background helper last ran
inbox.link_conflict.title inbox {n:# file is|# files are} in the way of links
inbox.link_invalid.title inbox {n:# layout error|# layout errors} in the repo; those parts aren't linked
inbox.link_pending.title inbox {n:# link|# links} to make or fix
inbox.missing_profile.detail inbox Write the steps that set up this machine
inbox.missing_profile.title inbox Profile {profile} does not exist yet
inbox.more hint …and {n} more:
inbox.name_blocked.title inbox The name {name} is blocked on this machine
inbox.name_helper_path.title inbox The helper cannot use the name {name} yet
inbox.name_no_bin.title inbox The name {name} needs a bin directory here
inbox.name_shadowed.detail inbox The other program is at {path}.
inbox.name_shadowed_other.title inbox Another program shadows the name
inbox.name_shadowed_ours.title inbox The name {name} shadows another program
inbox.never_pushed.title inbox Not backed up yet: nothing pushed to the remote
inbox.never_unpacked.last inbox (last unpacked: {other})
inbox.never_unpacked.title inbox Profile {profile} was never unpacked here
inbox.next_hint hint Do the first one now:
inbox.no_gitleaks.title inbox {tool} is missing, so every commit is refused
inbox.no_guard.title inbox The commit guard is not installed
inbox.no_helper.detail inbox It seals, exports, links and reminds you every hour
inbox.no_helper.title inbox The background helper is not installed
inbox.no_identity.detail inbox Commits by {self}{save} and the hourly autosave fail without a name and email.
inbox.no_identity.title inbox Git doesn't know who you are
inbox.no_keys.title inbox Secrets are not set up yet
inbox.no_profile.detail inbox Set {default_key} or {hosts_key} in
inbox.no_profile.title inbox No profile chosen for
inbox.no_remote.title inbox Not backed up: the repo has no remote
inbox.no_repo.title inbox {repo} is not a git repository
inbox.no_signing_key.detail inbox Every commit fails until it's here. Restore it ({self}{unlock}), or stop signing on this machine only:
inbox.no_signing_key.title inbox Commits are signed with {key_path}, which this machine doesn't have
inbox.notify.body notification {title}{more}. Run:
inbox.notify.more notification (+{n} more)
inbox.notify.title notification {bot} needs you
inbox.nudge nudge {bot}: {n:# thing needs|# things need} you{urgent}, run:
inbox.profile_changed.title inbox Profile {profile} changed since the last unpack
inbox.secret_conflict.title inbox {n:# secret|# secrets} changed here and on another machine
inbox.secret_locked.title inbox {n:# secret is|# secrets are} still locked
inbox.secret_unsealed.title inbox {n:# secret|# secrets} not sealed yet
inbox.secret_upstream.title inbox {n:# secret|# secrets} changed on another machine
inbox.tidy_failed.title inbox Automatic {step_name} failed
inbox.triage.title inbox {n:# new thing|# new things} to sort: track, ignore or snooze
inbox.uncommitted.title inbox {n:# uncommitted change|# uncommitted changes}
inbox.unpack_preview inbox It runs every step without asking; to see them first: {self}
inbox.unpushed.title inbox {n:# commit|# commits} not pushed
inbox.unreachable.title inbox Last reached the remote
inventory.exports.failing error {name} failing
inventory.exports.never status {name} never
inventory.exports.none status none configured
inventory.exports.ok status {name}
inventory.helper.installed status installed · last ran
inventory.helper.missing warning not installed ({self}{setup})
inventory.links.invalid error , {n} layout errors
inventory.links.value status {ok} ok, {to_fix} to fix, {blocked} blocked, {locked} locked
inventory.profile.changed warning , changed since
inventory.profile.never status never unpacked here
inventory.profile.none warning none chosen for this machine
inventory.profile.unpacked status unpacked
inventory.profile.value status {profile} ·
inventory.repo.never_pushed warning (never pushed: {self}{remote})
inventory.repo.no_remote warning (no remote: {self}{remote})
inventory.repo.not_repo error not a git repository
inventory.repo.upstream status →
inventory.repo.value status {branch}{upstream} · {ahead} ahead, {behind} behind, {uncommitted} uncommitted
inventory.secrets.count status {n}
inventory.secrets.no_keys warning no keys yet ({self}{keys_init})
inventory.secrets.none status none yet
inventory.triage.value status {to_sort} to sort, {ignored} ignored
keys.error.mismatch error The passphrases didn't match; nothing was saved.
keys.hint.commit hint Commit {identity} and {recipients}:
keys.hint.passphrase hint Leave it empty and {bot} makes up a strong one and prints it. Save it in your password manager: without it, the secrets in this repo can't be recovered.
keys.prompt.confirm prompt Confirm passphrase:
keys.prompt.passphrase prompt Enter passphrase (leave empty to autogenerate a secure one):
keys.status.autogenerated status using autogenerated passphrase ""
keys.status.creating status Creating your secrets key. {bot} asks for a passphrase next.
keys.success.created success Keys created. The public key:
layout.both_layers error provided by both {providers}; keep it in one place
layout.dot_parens error "{raw}" names no folder: {dot_names} would link outside the folder it is in; rename it
layout.double_parens error "{raw}": wrap the name in one pair of {marks}, like "{open}{name}"
layout.empty_parens error "{marks}" has no name inside
layout.file_parens error {marks} marks folders only: rename "{raw}" to ""
layout.inner_file_parens error {path}: {marks} marks folders only: rename "{raw}" to ""
layout.inside_repo error would be linked at {target}, inside the home repo; rename it
layout.must_mark error rename "{raw}" to "{open}{raw}{close}"{every_layer}: it holds {inner}, which must not be a link, so it can't be one either
layout.old_name error "{raw}" is the old layout: rename it to ""
layout.outside_folder error would be linked at {target}, outside the folder it is in; rename it
layout.spelling error spelled "{marked}" in {where_marked} but "{plain}" in {where_plain}; use one spelling everywhere
layout.top_dot error "{raw}": top-level names get their dot from homebot; rename it to "{open}{name}"
link.error.backup_exists error Cannot move {target} aside: {backup} is already there. Try again in a second.
link.hint.backup hint To move what is in the way aside (renamed {backup_pattern}, nothing is deleted):
link.hint.compare hint Compare and delete them when you have a minute:
link.status.dry_run_ok status {n} already linked
link.status.interrupted status Stopped; nothing else was moved or linked. Run {self}{link} again to finish.
link.status.moved_aside status moved aside
link.summary.base status {n:# change|# changes}, {ok} already linked
link.summary.blocked warning , {n} blocked
link.summary.invalid error , {n:# layout error|# layout errors} in the repo
link.summary.locked status , {n} locked ({self}{unlock})
lock.status.nothing status Nothing to seal.
lock.status.wiped status Sealed {sealed}, removed {n:# plaintext file|# plaintext files}.
lock.success.sealed success sealed
lock.warning.kept warning kept {private}{name} (not safely sealed; resolve it first)
manifest.error.action_name error {where}: "{name}" is not a valid action name
manifest.error.args error {where}: arguments must be a value or a list of values
manifest.error.description error {file}: {key} must be text
manifest.error.include error {file}: {key} must be a profile name or a list of them
manifest.error.loop error Profiles include each other in a loop:
manifest.error.name error {where}: {key} must be text
manifest.error.no_profile error No profile named "{name}" in {dir}.
manifest.error.no_profile.available error No profile named "{name}" in {dir}. Available:
manifest.error.not_mapping error {file}: expected a mapping with
manifest.error.old_extension error Profiles end in {ext} now: rename {path} to {name}
manifest.error.on error {where}: {key} must be {oses}, or a list of them
manifest.error.on_os error {where}: unknown OS "{value}" in {key} (use {oses})
manifest.error.one_action error {where}: each step needs exactly one action, found
manifest.error.one_action.none error {where}: each step needs exactly one action, found none
manifest.error.step_shape error {where}: expected
manifest.error.steps error {file}: {key} must be a list
manifest.error.unknown_key error {file}: unknown key "{key_name}" (expected {keys})
manifest.error.unknown_variable error {where}: unknown variable {braced_key} (available: {vars})
manifest.error.yaml error {file}: invalid {yaml}:
name.error.refused error Cannot use {name}: {reason} ({path})
name.error.refused_no_path error Cannot use {name}:
name.hint.path hint Add ~/.local/bin to your PATH to type {name}; homebot works now.
name.reason.foreign error a foreign file already has that name; it was left untouched
name.reason.in_repo error the home repo already places that name in ~/.local/bin
name.reason.no_homebot error there is no stable homebot binary to link to
name.reason.on_path error a program with that name is on PATH
name.reason.reserved error homebot uses it as a command
name.reason.shell error the shell uses that word
name.reason.synced_bin error another tool manages ~/.local/bin
name.reason.syntax error use 2 to 32 lowercase letters, digits and hyphens, starting with a letter
name.reason.temp_blocked error the temporary link is occupied; your current link was kept
name.reason.tool error homebot runs a program by that name
name.status.configured status Configured name:
name.status.effective status Effective name here:
name.status.off success Name turned off; homebot works as before.
name.status.old_stops warning The old name {name} stops working; no forwarding link is left.
name.status.set success Name set to {name}.
name.warning.in_work_tree warning Your ~/.local/bin is inside a git work tree; another tool could commit this link.
name.why.bad_record status The machine's name record at {path} cannot be read.
name.why.config_off status The machine still has a name link, but homebot.yml has no name.
name.why.link_broken status The recorded link at {path} is missing or points elsewhere.
name.why.no_record status This machine has not recorded that name yet.
name.why.not_on_path status The name's link is not on this process's PATH.
name.why.recorded_other status This machine still uses {recorded}; run homebot name {configured} to finish the change.
name.why.shadowed status Another program at {path} comes before the name's link on PATH.
next.hint.no_fix hint No automatic fix for this one; the detail above says what to change.
next.hint.run_yourself hint Run this yourself:
next.success.nothing success Nothing needs you.
pack.error.already_in_repo error {repo_path} is already in the repo. Pack the new things inside it instead.
pack.error.inside_managed error {path} is inside {dir}, which is already managed. It is in the repo already.
pack.error.is_repo error {path} is (or contains) the home repo itself.
pack.error.layer_not_linked error The {layer} layer is not linked on {os}, so a file packed there could not be linked back (use {layers}).
pack.error.link_failed error Moved {path} into the repo but could not link it back. Link it with:
pack.error.managed error {path} is already managed.
pack.error.missing error {path} does not exist.
pack.error.nested_repo error {path} is a git repository of its own: the home repo would hold only a pointer to it, and its files would never reach the remote. Remove its {git_dir}, or leave it out of the home.
pack.error.no_args error What should be packed? e.g.
pack.error.no_keys error Create keys before packing secrets:
pack.error.outside_home error Only things inside your home folder can be packed (got {path}).
pack.error.parens error "{name}" is wrapped in {marks}, which the repo uses to mark folders that must not be links, so it can't be tracked.
pack.error.plain_parent error {repo_path} is a plain folder, so it can't hold a new folder that must stay real. Rename it to {open}{name}{close} in every layer first.
pack.error.private_reserved error {path} can't go into {private}: git would stage it as plaintext there. {readme}, {gitignore}, {gitkeep} and {recipients} files, {age_glob} files and {git_dir} folders are the repo's own; rename or remove it first.
pack.error.source_not_removed error Copied {path} into the repo as {repo_path}, but could not remove the original: {reason}
Remove it, then link the copy with:
pack.error.symlink error {path} is a symlink. Pack the real file it points to instead.
pack.error.top_level error Only dotfiles and {exceptions} can be tracked at the top of your home folder (got {home_prefix}{name}).
pack.error.unknown_layer error Unknown layer "{layer}" (use {layers}).
pack.error.unreadable error Can't read {path}, so homebot can't check it for a git repository of its own. Nothing was moved; make it readable, or leave it out of the home.
pack.hint.commit hint Commit it with:
pack.status.moved status moved {from} →
pack.success.packed success packed {from} →
pack.success.sealed_suffix success (sealed {n})
persona.success.created success Created persona file:
persona.success.greeting success {bot} is ready.
persona.success.selected success {id} is selected.
remote.error.bad_name error "{name}" won't do:
remote.error.create error {cli} couldn't create {name}.
remote.error.no_address error {cli} created {name} but didn't say its address. Add it with:
remote.error.set_url error Couldn't set the remote to {url}.
remote.error.unreachable error Can't reach the remote.
remote.error.unreachable_login error Can't reach the remote: git has no login for {https} here.
remote.error.unreachable_ssh error Can't reach it over ssh either.
remote.heading.none status Nothing is backed up yet: the repo has no remote.
remote.hint.create_in_terminal hint Run this in a terminal to create a private {forges} repository for it:
remote.hint.existing hint Use a repository you already have:
remote.hint.first_save hint Nothing committed yet. This commits and pushes:
remote.hint.have_key hint You have an ssh key, so the same repository can be reached over ssh:
remote.hint.https hint There's no ssh key in {ssh_dir}, so the remote uses {https}. To let git use {cli}'s login:
remote.hint.log_in hint Log git in:
remote.hint.log_in_or_ssh hint Log git in to that host, or add an ssh key to {ssh_dir} and switch to the ssh address:
remote.hint.login hint {cli} is installed but not logged in. To offer {forge} too: {cli}
remote.hint.or_existing hint Or use a repository you already have:
remote.hint.or_ssh hint Or add an ssh key to {ssh_dir} and switch to the ssh address:
remote.hint.pick hint pick one of the letters above
remote.hint.rename hint This repo has a remote named {names}, but {bot} pushes to the one named {origin}. Rename it:
remote.hint.to_switch hint To switch:
remote.prompt.name prompt Name for the new private {forge} repository [{suggested}]:
remote.prompt.switch prompt Switch to it?
remote.prompt.url prompt URL:
remote.status.kept_https status Kept {https}. To switch later:
remote.status.not_now status Not now. When you're ready:
remote.status.up_to_date status Already backed up: the remote has every commit.
remote.success.created success created {name}, a private {forge} repository
remote.success.pushed success pushed the repo is backed up
remote.success.reachable success reachable
remote.success.remote success remote
remote.warning.waiting warning The remote has new commits. Commit or discard the changes here, then:
save.error.diverged error This machine and the remote changed the same files. Resolve it by hand:
{git_prefix}{repo_abs}
save.error.fast_forward error Could not fast-forward to the remote.
save.error.git_add error {git_add} failed.
save.error.push error Push failed.
save.error.rebase_stuck error This machine and the remote changed the same files, and git could not undo its rebase: {repo_abs} is still in the middle of one. Resolve the conflicts and run {continue_cmd}, or give up on it with:
{git_prefix}{repo_abs}
save.error.refused error Commit refused:
save.status.nothing status Nothing new to commit.
save.success.committed success committed {n:# file|# files}
save.success.pulled success pulled {n:# commit|# commits}
save.success.pushed success pushed {n:# commit|# commits}
save.success.sealed success sealed {n:# secret|# secrets}
save.warning.unreachable warning Couldn't reach the remote, so nothing was pushed. To see why:
save.warning.waiting warning The remote has new commits. Commit or discard the changes here, then save again.
secrets.error.cancelled error Stopped at the passphrase prompt; nothing changed.
secrets.error.decrypt error Could not decrypt {private}{name}{age}.
secrets.error.empty_recipients error {recipients} has no public key in it. Restore it from the repo's history.
secrets.error.encrypt error Could not encrypt {private}{name}.
secrets.error.encrypt_key error Encrypting the new key failed; nothing was saved.
secrets.error.identity_unreadable error {identity} is not a passphrase-encrypted age key. Restore it from the repo's history.
secrets.error.keys_exist error Keys already exist ({identity}, {recipients}). Delete both first to start over.
secrets.error.no_identity error No {identity} yet. Create keys first:
secrets.error.no_recipients error No {recipients} yet. Create keys first:
secrets.error.no_terminal error There is no terminal to ask for the passphrase on. Run this in a terminal.
secrets.error.passphrase error Could not decrypt {identity} (wrong passphrase?)
secrets.error.reserved error Refusing to write over {path}: it is not a secret.
secrets.error.save_gitignore error Writing {path} failed, so nothing was set up: without it, git would take the plaintext secrets in {private}.
secrets.error.save_key error Saving the new key to {path} failed; nothing was saved.
secrets.error.unsupported_ssh_key error Line {line} of {recipients} holds a key age cannot encrypt to ({key_type}). Use an {ed25519} key, or an {rsa} key of at least {bits}; homebot never seals to fewer keys than the file lists.
setup.error.clone_auth_gh error git couldn't sign in to GitHub to clone {url}. Sign in with the GitHub CLI, which sets git up to use that login, then run {setup} again:
setup.error.clone_auth_key error git couldn't sign in to {host} to clone {url}. Add an ssh key to your account there, then run {setup} again.
setup.error.clone_failed error Couldn't clone {url}. Check the address and that you can reach it, then run {setup} again.
setup.error.git_too_old error Your git is {found}, and homebot needs {minimum} or newer: its commit guard relies on a git command that older versions lack, so every save would be refused. Update git, then run {command} again.
setup.error.git_too_old_brew error Your git is {found}, and homebot needs {minimum} or newer: its commit guard relies on a git command that older versions lack, so every save would be refused. Install a newer one with Homebrew, then run {setup} again:
setup.error.home_taken error {path} is there but holds no {config}, so it isn't a home repo and setup won't touch it. Move it aside, or set {env} to another folder, then run {setup} again:
setup.error.install_failed error {command} failed. Install {tools} yourself, then run {setup} again.
setup.error.name_syntax error {flag} {name} must be
setup.error.no_terminal error No home repo yet, and setup starts or clones one only in a terminal. Run it in one:
{setup}
Or clone yours by hand:
{clone}
setup.error.not_a_home_repo error {url} has no {config}, so it isn't a home repo. It is in {path} now: move it aside, then run {setup} again.
setup.error.persona_syntax error {flag} {persona} must
setup.error.remove_flags error {remove_flag} cannot be combined with {name_flag} or
setup.error.root_relative error {env} is "{value}", which is not a full path, so homebot would find the repo only from this folder. Set it to a full path, or unset it, then run {setup} again.
setup.error.tools_missing error Missing: {tools}. The commit guard needs it to check every commit. Install it, then run {setup} again:
setup.error.xdg_relative error {name} is "{value}", which is not a full path, so the background helper can't use it. Set it to a full path, or unset it, then run {command} again.
setup.hint.keys hint Secrets aren't set up yet. To keep passwords and tokens in the repo, encrypted, create the keys:
setup.hint.name_path hint Add {bin} to your {path_env} in {file} to type {name}; homebot works now.
setup.hint.xdg hint The helper keeps the {xdg_glob} folders it was set up with. After changing {config_env}, {data_env}, {state_env} or {cache_env}, run {self}{setup} again.
setup.hint.zshrc hint Add this to your {zshrc} so new terminals tell you when something needs you:
setup.prompt.backup prompt {n:# file is|# files are} in the way of links. Move them aside (nothing is deleted) and link?
setup.prompt.change_name prompt This repo calls it {repo} on every machine. Change the shared name to {flag}?
setup.prompt.install_tools prompt Missing: {tools}. Install now with {command}?
setup.prompt.keys prompt Keep passwords and tokens in the repo, encrypted? This creates your keys.
setup.prompt.name prompt Give your homebot a command name? For example {example} ({enter} keeps homebot):
setup.prompt.name_path prompt Put {bin} on your {path_env} in {file} so the name works?
setup.prompt.save prompt Commit and push now?
setup.prompt.save_local prompt Commit now?
setup.prompt.triage prompt {n:# thing|# things} in your home could be tracked. Sort them now, one key each?
setup.prompt.unpack prompt Run {n:this step|these # steps}?
setup.prompt.url prompt URL of your home repo, or {example} on GitHub:
setup.prompt.zshrc prompt Add it to {file} now?
setup.status.changed_here status Changed on this machine:
setup.status.cloning status Cloning {url} into {path}…
setup.status.guard_present status Commit guard already installed.
setup.status.name_blocked status no name link for {name} was made here
setup.status.name_link status name link: {bin}{name} points to
setup.status.name_uncommitted status The new name is in {config} but stays uncommitted with your other work; your next save carries it.
setup.status.no_repo status No home repo yet.
setup.status.open_shell status From now on, type {name}. Open a new terminal first; on some Linux systems, log out and back in.
setup.status.path_added status Added {bin} to {file}.
setup.status.path_line status added the {path_env} startup line
setup.status.persona status Persona in this repo:
setup.status.removed status Background helper removed.
setup.status.repo_name status This repo calls your homebot {name}.
setup.status.repo_name_wins status This repo calls it {repo}; {name_flag} {flag} was ignored without a terminal.
setup.status.zsh_only status Your shell is {shell}. The startup line and the prompt count are {zsh}-only for now.
setup.status.zshrc_in_repo status That file links into your home repo, so {path} changed there.
setup.status.zshrc_linked status {file} is a link to {target}, and setup writes only through a link to a file in your home repo. Add the line there yourself.
setup.success.created success created {path}, a new home repo with a {profile} profile
setup.success.guard success installed commit guard ({hook_key})
setup.success.helper success installed
setup.success.zshrc success added the startup line to
setup.warning.clone_failed warning Couldn't clone {url}.
setup.warning.name_blocked warning {name} cannot be used as a command here: {reason}. Homebot will keep working.
setup.warning.name_invalid warning {name} must be {rule}. Try another name.
setup.warning.no_profile warning No profile for this machine ({host}) yet, so nothing was unpacked. Name one in {config} under {hosts} or {default}, then:
setup.warning.no_repo warning {root} is not a git repo yet; skipped the commit guard.
setup.warning.path_link warning Cannot safely edit {file} (link target: {target}). Add {bin} to your {path_env} yourself.
tidy.did.committed status committed {n:# file|# files}
tidy.did.exported status refreshed
tidy.did.guard status installed the commit guard
tidy.did.linked status fixed {n:# link|# links}
tidy.did.name_off status removed this machine's name link
tidy.did.named status kept this machine's name link in sync
tidy.did.pulled status pulled {n:# commit|# commits}
tidy.did.pushed status pushed {n:# commit|# commits}
tidy.did.sealed status sealed {n:# secret|# secrets}
tidy.error.autosave_no_guard error skipped, because the commit guard could not be installed this run (see the guard step)
tidy.error.lock error Could not lock {path}: {reason}. tidy takes a file lock so two runs never overlap, and the filesystem holding the state directory does not support it. Point {env} at a local disk.
tidy.status.inbox_count status {n:# thing|# things} in the inbox.
tidy.status.locked status Another tidy is running.
tidy.status.nothing status Nothing to tidy.
triage.error.git_add error {git_add} {key} failed.
triage.error.ignore_list error {file}: {ignore} must be a list of paths
triage.error.line_break error {key} has a line break in its name, which a {gitignore} line can't hold. Nothing was written; rename it, or snooze it.
triage.error.old_file error homebot reads {decisions} now: rename {path} to
triage.hint.header_suffix hint Each answer is saved right away; {quit} stops.
triage.hint.pick hint pick one of the letters above
triage.status.header status {n} things to decide.
triage.success.gitignored success added /{key} to
triage.success.ignored success will never ask about {key} again
triage.success.none success Nothing to sort. Everything has a decision.
triage.success.packed success packed {key} into
triage.success.snoozed success snoozed {key} for
triage.success.staged success staged
unlink.hint.helper hint The background helper links them again within the hour, as {self}{link} would. To keep a file unlinked, take it out of the repo.
unlink.status.removed status Removed {n:# link|# links}. To put them back:
unlock.error.conflict error conflict {private}{name}: changed here and in the repo
unlock.hint.ours hint Keep this machine's versions:
unlock.hint.theirs hint Or take the repo's:
unlock.prompt.passphrase prompt Enter passphrase:
unlock.status.linked status linked {n:# secret|# secrets}
unlock.success.unlocked success unlocked {n:# secret|# secrets}
unlock.success.verified_suffix success , verified
unpack.error.from_nan error {from_flag} takes a step number
unpack.error.from_range error {from_flag} must be between {first_step} and
unpack.error.no_action error Step {i}: no action named "{action}". See:
unpack.error.no_profile error Which profile? Pass one ({example}) or set {default_key} in {config_file}.
unpack.error.step_exit error Step {i} ({step}) exited with {code}.
unpack.error.step_failed error Step {i} failed:
unpack.hint.resume hint Fix it, then resume:
unpack.status.header status Unpacking {profile} on {host} ({os}): {n} steps
unpack.status.header_dry_run status Would unpack {profile} on {host} ({os}): {n} steps
unpack.success.done success Unpacked {profile}.