Back to home@gausszhou

dsh-where-am-i

Neofetch for DeepSeek Harness.

Stars
0
Language
JavaScript
Created
Aug 20, 2026
Updated
Aug 23, 2026
GitHub repo

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

ScenarioBehavior
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 sessionnever 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:

KeyDefaultMeaning
fieldsall 13emit 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
includeSubagentsfalsewhen true, also inject into subagent sessions (skipped by default to avoid repeating per fan-out)
verbosefalseprint 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 factSourceLocation
You are an AI agent powered by DeepSeek Harness.dsh-system-promptsystem prompt section harness:identity, order −100
DSH source checkout pathdsh-app-boot / dsh-web-appsection harness:source, order −99
Web GUI URLdsh-web-app (web profile only)section app:web-surface, order −98
Deployment personaconfigsection 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-loopprompt variables
File sandbox policy, approval policydsh-sandbox-policy / dsh-user-approvalruntime context (user-role snapshots)
Timestamp + browser timezone + elapsed durationdsh-time-context (not enabled in the default composition)runtime context
tmux session/window/pane locationdsh-tmux-context (not in the web composition)runtime context
AGENTS.md / CLAUDE.md workspace instructionsdsh-agent-instructionsdurable 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-context per 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; renderPrompt output 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, no uname — everything comes from node:os, one synchronous /etc/os-release read (Linux/WSL: PRETTY_NAME, falling back to ID VERSION_ID), and Intl.
  • 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-empty id, role: "user", source.kind, content array); 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-release on non-Windows/macOS systems, the os line falls back to os.version()/os.type() and may be less friendly than a distro name.
  • The user line is not redacted — the model can see the local username running dsh (like neofetch). Drop user/host via fields if 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.