dsh-where-am-i
Neofetch for DeepSeek Harness.
- Stars
- 0
- Language
- JavaScript
- Created
- Aug 20, 2026
- Updated
- Aug 23, 2026
Introduction
dsh-where-am-i
Neofetch for DeepSeek Harness.
English | 简体中文
A DeepSeek Harness (dsh) plugin that injects a neofetch-style device info block into every brand-new session — OS distro, kernel, architecture, hostname, CPU, memory, uptime, timezone, locale, shell/terminal, and Node version. It is injected exactly once, when a new session takes its first step, and then persists in that session's own history; existing sessions are completely untouched.
Behavior at a glance
| Scenario | Behavior |
|---|---|
| Brand-new session (first message after creation) | a device-info user message is injected; the model sees it at the start of the conversation and it persists until compaction |
| Existing sessions (created before a restart, resumed, or continued) | the system prompt is byte-identical and no message is appended — the request prefix and its KV cache are fully preserved |
| Later steps of the same session | never re-injected (the message is already durable history) |
| Subagent sessions (ephemeral workers spawned by the subagent/workflow tools) | skipped by default (avoids repeating per fan-out); opt in with includeSubagents: true |
Usage
Install into a dsh profile (once published, by package name):
dsh plugin --profile web add "@gausszhou/dsh-where-am-i"
For local development use a link dependency (same pattern as
dsh-opencode-session-id): add "@gausszhou/dsh-where-am-i": "link:/home/gauss/Code/gausszhou/dsh-where-am-i" to
~/.dsh/profiles/web/package.json dependencies and list it in
dsh.profile.bundles; the plugin's own cordis.patch.yml (via
dsh.bundle.patch) performs the insert.
After installing, restart dsh web (systemctl --user restart dsh-web) so
the bundle takes effect. Zero configuration needed.
What the model sees in a new session (real machine)
System info (the machine this dsh session runs on; sampled at session start):
host: gauss-KP
user: gauss
os: Ubuntu 26.04 LTS
kernel: 7.0.0-29-generic
arch: x86_64
cpu: 6 × Intel(R) Core(TM) i5-8400 CPU @ 2.80GHz
memory: 7.9 GiB used / 15.0 GiB total
uptime: 1d 4h 3m
timezone: Asia/Shanghai (GMT+08:00)
locale: zh_CN.UTF-8
shell: /bin/bash
term: dumb
node: v24.19.0
Configuration
All options are optional, with the defaults shown:
| Key | Default | Meaning |
|---|---|---|
fields | all 13 | emit only the listed field lines, in the default order, e.g. [os, arch, timezone]; unknown names are dropped; an empty-after-filter list falls back to all |
includeSubagents | false | when true, also inject into subagent sessions (skipped by default to avoid repeating per fan-out) |
verbose | false | print the injected snapshot to stdout (visible via journalctl -u dsh-web on a web deployment) |
Field list (emission order): host user os kernel arch cpu
memory uptime timezone locale shell term node. Fields that
cannot be sampled (e.g. shell on Windows without a SHELL variable) are
omitted automatically.
# ~/.dsh/profiles/web/cordis.patch.yml
- id: where-am-i
config:
fields: [os, kernel, arch, cpu, memory, timezone]
Design: what dsh prompts already carry
Before implementing, the existing prompt surface was surveyed
(dsh-system-prompt section/variable registration, dsh-agent-loop variable
providers, the runtime-context packages). Existing facts are listed below;
this plugin only fills the gaps and never duplicates:
| Existing fact | Source | Location |
|---|---|---|
You are an AI agent powered by DeepSeek Harness. | dsh-system-prompt | system prompt section harness:identity, order −100 |
| DSH source checkout path | dsh-app-boot / dsh-web-app | section harness:source, order −99 |
| Web GUI URL | dsh-web-app (web profile only) | section app:web-surface, order −98 |
| Deployment persona | config | section deployment:persona, order 0 |
| Tool guidance (read/write/edit/bash/web_search…) | each dsh-tool-* | sections tool:*, orders 100–199 |
{{provider}} {{model}} {{cwd}} (session cwd, evaluated per assembly) | dsh-agent-loop | prompt variables |
| File sandbox policy, approval policy | dsh-sandbox-policy / dsh-user-approval | runtime context (user-role snapshots) |
| Timestamp + browser timezone + elapsed duration | dsh-time-context (not enabled in the default composition) | runtime context |
| tmux session/window/pane location | dsh-tmux-context (not in the web composition) | runtime context |
AGENTS.md / CLAUDE.md workspace instructions | dsh-agent-instructions | durable user message |
Completely missing (what this plugin adds): OS/distro name, kernel release, CPU architecture, hostname, CPU model and core count, total/free memory, uptime, host locale, shell/terminal, Node version, and the host process timezone.
Deliberate omissions
cwd— already provided live by the{{cwd}}prompt variable, and the harness explicitly warns against inferring it from the checkout path.- date / current time — a frozen wall clock in durable history would go
stale; live timestamps come from
dsh-time-contextper request when enabled. - ASCII logo / desktop wallpaper — token noise with no agent value.
Why a persistent user message, not a system prompt section
The first version contributed a systemPrompt.section(), but that renders on
every step of every session once mounted — it can never be "once", and it
rewrites the shared system-prompt prefix of already-running sessions (KV
cache invalidated from the first changed token).
The current implementation uses the same seam as dsh-time-context /
dsh-tmux-context: a prepended agent/pre-step waterfall listener that
first delegates downstream (await next()) and, only when the step enters,
appends one durable, source-attributed user message to the batch. The
agent loop persists every message in the batch
(session.append("user/message", …)) after step/start, so:
- the injected message becomes that session's own history (visible from the start, retained until compaction);
- no system prompt section is registered → existing sessions' system prompt is byte-identical to before, prefix and KV cache fully preserved;
- later steps — of this session or any other — see the message already in history and never inject again (across restarts and plugin hot reloads, because the decision reads only the session log itself).
"Brand-new session" is decided from the session log alone: at pre-step time,
agent.session.events holds no user/message, assistant/message, or
tool/result event yet (the in-flight prompt of step 1 is persisted only
after the step enters — dsh-time-context's README states this explicitly).
Any resumed, continued, or already-injected session has at least one such
event and is skipped, forever. A batch with no user-sourced message at all
(plugin-only runtime-context or scheduled turns) is also never injected into.
Device facts are sampled at injection time (the new session's first step), so uptime/memory are as fresh as possible; the message is per-session history, so byte-level differences between sessions cost nothing.
Bottom line for "existing session prefixes"
- System prompt: the plugin registers no section;
renderPromptoutput for existing sessions is identical to before installation (asserted by the integration test). - Session history: no message is appended to existing sessions.
- The only affected surface is new sessions: injection happens before their first request, i.e. it is part of that session's own prefix — there is no old cache to invalidate.
Implementation details
- Zero subprocesses: no
sw_vers, nouname— everything comes fromnode:os, one synchronous/etc/os-releaseread (Linux/WSL:PRETTY_NAME, falling back toID VERSION_ID), andIntl. - Architecture names follow neofetch's mapping:
x64→x86_64,arm64→aarch64,ia32→i386,arm→armv7l. - macOS: the marketing version is derived from the Darwin release (Darwin 24 ↔ macOS 15, 19 ↔ 10.15; older releases are not guessed).
- Timezone is the host process timezone (IANA name + current UTC offset,
DST-aware) — a different concept from
dsh-time-context's browser zone. - The injected message satisfies the harness message contract
(
assertMessageEventShape: non-emptyid,role: "user",source.kind,contentarray); its source mirrors time-context's plugin-snapshot shape ({ kind: 'plugin', plugin: 'where-am-i', form: 'snapshot', sections: [...] }) so UI/trajectory attribution is consistent. inject: ["agents"](same as time-context): the plugin does not load in a tree without the agents registry.
Verification
node test/verify.mjs # unit: os-release parsing / arch mapping / macOS naming / byte & uptime formatting / timezone offset / config normalization / render subset / decision gates (user source, history, subagent) / message shape / no-cwd-no-date invariants + real-machine snapshot
node test/smoke-apply.mjs # wiring: new session injects once, existing session zero injection, same session never re-injects, no-user-source batches skipped, reject/abort passthrough, subagent gate, fields subset
node test/integration-real.mjs # real cordis + dsh-system-prompt + dsh-session: system prompt untouched, injected message persisted by a real Session and visible in derived history, no re-injection
Notes & limitations
- The snapshot is sampled at each new session's first step: uptime/memory go stale during that session (neofetch-at-session-start semantics; the message is history and is not refreshed).
- Without
/etc/os-releaseon non-Windows/macOS systems, theosline falls back toos.version()/os.type()and may be less friendly than a distro name. - The
userline is not redacted — the model can see the local username running dsh (like neofetch). Dropuser/hostviafieldsif you care about privacy. - Host-machine facts are reported; if dsh runs inside a container or remote execution, the kernel reported is the container's/remote one (e.g. WSL shows the WSL kernel).
- The message costs tokens on every request until compaction hides it; this is why existing sessions and subagents are excluded by default — the cost is paid once, only by genuinely new conversations.