Skip to content

Releases: JWE24-code/moqi

Moqi 0.5.1

Choose a tag to compare

@JWE24-code JWE24-code released this 31 Oct 09:45
d6d4197

Installs that work — on DeepSeek Harness 0.2.1, on Windows, on profiles made
before 0.2.1, and with a clear answer when something is missing — plus a fix
that stops a backslash from hiding a control character from permission rules.

Fixed

  • Install without pnpm always failed, now it says why. The profile depends
    on the app through a link: path, which npm rejects
    (EUNSUPPORTEDPROTOCOL), so the installer's npm fallback never worked: a
    machine without pnpm ended with a broken profile. The installer now stops
    with a message naming pnpm and how to install it.
  • Windows. The installer found dsh through /bin/sh, which Windows does
    not have, so no Harness packages were linked and the app could not boot. It
    now searches PATH with PATHEXT itself, finds dsh's package next to npm's
    dsh.cmd shim, and links with junctions, which need neither Developer Mode
    nor an elevated shell. The moqi launcher runs dsh's own entry script with
    Node instead of spawning dsh.cmd, which Node refuses without a shell.
  • moqi install repairs profiles made before dsh 0.2.1. Their
    pnpm-workspace.yaml lacked autoInstallPeers: false, so pnpm put plugins'
    @deepseek-ai/* peers into the profile, where they shadowed the Harness's
    own and could stop dsh from starting ("required plugin did not activate").
    The installer now adds or corrects that one line, keeping the rest of the
    file, and deletes the stale copies together with pnpm-lock.yaml so the
    install re-resolves without them. It prints a line for each repair, and
    nothing when the profile is fine.

Security

  • A backslash can no longer slip a control character past a permission
    rule.
    The command matcher refuses any command containing control
    characters, but a bash backslash directly before one consumed it unchecked,
    so a rule such as bash(echo *) could auto-allow a command carrying raw
    terminal escape sequences. Control characters are now refused over the whole
    command, in every quoting context and in rule patterns too; commands without
    control characters match exactly as before.

Changed

  • pnpm is required to install the profile (npm install -g pnpm, or
    corepack enable pnpm on Node 22/24). The Harness's own plugin manager
    needs it too.

Documentation

  • docs/install.md and the README cover pnpm, Windows, the dsh desktop app
    (no dsh on PATH, so moqi needs the CLI; what to do if the app stops
    starting after a plugin install), upgrading a profile made before dsh 0.2.1,
    Node version managers, and npm 11's "install scripts skipped" warning
    (harmless).

Internal

  • CI installs moqi the way a user does, on Linux, macOS and Windows: dsh
    at latest, the packed tarball, moqi install, boot to --dump-config, and
    no @deepseek-ai/* copy left in the profile. A guard fails the build if a
    Harness package is ever listed under dependencies.
  • A watch on every dsh release. Every three hours dsh-compat.yml checks
    npm for a new @deepseek-ai/dsh on latest/next and tests moqi and its
    three plugins against it — published and from main — recording results in
    a "dsh compatibility" issue and opening one issue per break.
  • SonarCloud at zero. The 55 findings 0.5 introduced are cleared:
    complexity splits in the permission rules parser, the git workspace, memory,
    and the app shell, each pinned by tests written against the old code first;
    no behavior change. Two sequential git loops keep await in the loop on
    purpose and say why.
  • npm test finds its suites. scripts/run-tests.mjs runs every
    tests/*-smoke.{ts,mjs}, then functional.ts and pty.ts, each in its
    own process; adding a suite no longer edits package.json. npm test -- <filter> runs a subset and --list prints the set.
  • Workflows are linted. CI runs actionlint on every workflow, since GitHub
    only parses a scheduled or manual one when it runs — which is how the dsh
    watch first shipped with a job-level runner.temp it refused to start.

Install: npm install -g @deepseek-ai/dsh pnpm moqi-tui@0.5.1, then moqi.

Moqi 0.5.0

Choose a tag to compare

@JWE24-code JWE24-code released this 18 Oct 20:50
0705178

Permission rules, agent memory, and session isolation — the agent asks less,
remembers across sessions, and works on a branch or worktree of its own.

Added

  • Approvals remember your answer. The approval panel shows the exact
    command (read from the session log, never a display summary) and offers
    1 allow once, 2 allow for this session, 3 always allow — for this
    project or, with tab, everywhere — and 4 deny. A rule such as
    bash(git log *) then answers the matching calls without a panel, naming
    itself on the status line; the Harness still audits every decision. Rules
    are kept under $DSH_HOME/moqi/permissions/, never in the repository.
    Matching fails closed: a compound command must match in every part, and
    substitutions, redirects (other than /dev/null), and input redirection
    always ask; a rule from a sandbox escalation names the mode it may grant.
  • /permissions lists the rules in force by scope, revokes one after a
    y/n, and opens the project or global rules file in $EDITOR; a file that no
    longer parses is reported and its rules are off, never guessed at.
  • shift+tab cycles the session mode: read-only → workspace-write → plan,
    using the Harness's own permission presets and plan mode. Plan keeps the
    workspace-write sandbox so an approved plan can be carried out; full access
    is never a step. The footer badges anything but workspace-write.
  • Agent memory. memory_save, memory_read and memory_forget keep
    facts across sessions under $DSH_HOME/memory/ (everywhere) and
    $DSH_HOME/memory/projects/<project>/, one file per fact plus a MEMORY.md
    index. Every session carries the index as runtime context: a memory saved
    anywhere reaches every open session at its next request, an unchanged list
    costs nothing, and the system prompt is never rewritten.
  • /memory shows the instruction files the Harness loads here beside the
    memories and index files, opens any of them in $EDITOR, and forgets one
    with /memory forget <name>. /memory off|on switches memory for every
    session and is remembered; --no-memory (or MOQI_MEMORY=off) does it for
    one run. Off hides the list and the tools; nothing is deleted.
  • # quick-add. A one-line # note is never sent to the model: a picker
    keeps it as a project or global memory, or appends it to the project's or
    the global AGENTS.md.
  • Sessions work on their own branch. A new session in a git repository
    starts on moqi/<date>-<id>, in place — no copy. /worktree <name> opens a
    session in a separate checkout under $DSH_HOME/worktrees/, outside the
    repository; /branch <name> switches the current one; /isolation off|branch|worktree and --isolation choose the default. /close removes
    only a branch with no commits of its own and a clean worktree, and launch
    tidies leftovers nothing open is using. The footer shows the branch.
  • The agent knows its workspace. An isolated session gets rules written
    from its real state: commit to this branch, leave the base alone, and either
    "this checkout is shared — never switch, stash, reset, or rebase" or "this
    is a fresh worktree — install dependencies first".
  • k closes a session in the Sessions list. On a row this app owns, k
    closes that session, as /close does. A session in another process, or on a
    peer, is left alone: there is no per-session control channel, and the only
    lever — the owning process — would end every session it holds.

Changed

  • The fleet is now Sessions. The cross-device overview is renamed from
    "fleet" to Sessions: ctrl+s, and /fleet is now /sessions. The list
    itself is unchanged — local sessions and peers, grouped by device.
  • /stack is now /tiled (ctrl+t). The stacked view is the tiled view:
    ctrl+t toggles it, alt+arrows move focus and alt+shift+arrows move a
    pane. ctrl+t previously toggled reasoner display, which remains available
    as /thinking.
  • @ completion, attachments, and /fork follow the session's own
    directory
    , so a worktree session completes and attaches its own files.

Removed

  • The ctrl+c sessions menu. The first ctrl+c now only arms the two-step
    quit; the Sessions view is the one place every session is listed. The
    redundant open-sessions picker (and its x-to-close row action) is gone;
    ctrl+n and /close still manage tabs.

Fixed

  • Approval panels were invisible in the tiled view with one session. With
    the tiled view saved and a single session open, every approval panel was
    dropped and the agent waited on an answer no one could see.
  • $EDITOR with arguments. A value like omarchy-launch-editor --inline
    or code --wait was run as one program name and failed; it now runs as a
    command line, with the file passed safely as one argument.
  • An approval's command came from the session on screen, not the one that
    asked, so a background session's approval showed no command.
  • $DSH_HOME is resolved one way everywhere, the Harness's own way (blank
    means ~/.dsh); a blank value no longer sends sessions or rules elsewhere.
  • A non-interactive run touches nothing. dsh --profile tui without a
    terminal refused only after starting a session — which rewrote the shared
    open-sessions list and, with 0.5's isolation, switched the repository onto a
    moqi/ branch. It now refuses before any of that, and moqi never saves a
    state it did not load.

moqi 0.4.2

Choose a tag to compare

@JWE24-code JWE24-code released this 01 Oct 15:13

Security

  • The profile installer refuses names that could leave the profiles
    directory.
    It blocked / and \ but accepted .., so a profile name could
    write one level up; it now takes a flat, dotless allowlist plus a
    resolved-path containment check.
  • CI installs and child processes are hardened. Workflow installs pass
    --ignore-scripts (npm is pinned; pnpm and the Harness install keep their
    scripts deliberately), and the dev scripts invoke /bin/sh and
    /usr/bin/env git by absolute path.

Changed

  • The Sonar backlog is down to maintainability smells. Every BUG and
    VULNERABILITY finding is resolved; a dead ternary, a reduce with no initial
    value, a redundant void, and a control character in the CSI regex are
    corrected, and dead imports and length-index reads are gone.

moqi 0.4.1

Choose a tag to compare

@JWE24-code JWE24-code released this 01 Oct 12:59

Changed

  • TuiApp's state now lives in focused controllers. The turn lifecycle
    moved to a TurnRunner, the open-session strip to SessionTabs, transcript
    search to SearchState, sign-in to a LoginFlow, and usage plus the fleet's
    peer list to tui/state and tui/fleet; confirmations and picker keys answer
    to their own modules. Sessions are listed through a session-list module,
    harness-to-surface adapters live in tui-adapt, and desktop bridges in
    local-platform. TuiApp is reduced to its entry points over those seams,
    and the decision is recorded in docs/adr/0001-state-owning-controllers.md.

Fixed

  • Session logs are decoded through one path. readLog duplicated the zstd
    detect/decompress that decodeLogBytes already owned, so the two could
    drift; readLog now keeps only the file concerns and delegates the decode, so
    a misnamed log decodes by content rather than failing.
  • An unnamed picker key no longer falls through into enter. A refactor left
    the picker's default navigation case directly above case enter with no
    break, so an arrow key both moved the selection and activated the row it had
    just focused — in /JevLoop, arrow keys toggled gates and enter looked inert.
  • Removed an unreachable role conditional in textOfRecord.

Added

  • Characterization tests pinning cross-session log-read limits: text before the
    maxBytes cut is found and text after it is not, an over-cap compressed log
    is skipped while truncated plain logs still count as scanned, and scan/skip
    counts hold on a mixed store.
  • Repository contract docs: AGENTS.md, a seeded CONTEXT.md glossary, and the
    whiteboard plus ADR map of the refactored shape.

moqi 0.4.0

Choose a tag to compare

@JWE24-code JWE24-code released this 30 Sep 22:08

Added

  • Panel commands on the tuiHost seam. ctx.tuiHost.registerPanel() lets a
    plugin contribute a command panel — rows the host draws, an enter action,
    and an optional masked secret prompt — so a feature's UI ships with its
    plugin instead of this app. moqi-jev-loop uses it for /JevLoop (its API key
    and four gates). Command matching is now case-insensitive, so a panel
    registered as JevLoop answers to /jevloop.

Changed

  • shift+enter is the composer's carriage return. Enter still submits;
    shift+enter (or ctrl+j) inserts a newline, so a draft can span several
    lines. Bracketed paste is enabled, so a multi-line paste is inserted whole
    rather than sending on its first line.
  • The dshfind listing is documented as automatic. The repository carries
    the dsh-plugin topic its marketplace indexes, so the README no longer
    describes a listing step that no release needs.
  • A plugin panel cannot shadow the app's own commands. The palette and
    runCommand now agree: on a name collision a built-in or Harness command
    wins, and a panel answers only to a name both leave free.

Fixed

  • CSI-u and modifyOtherKeys key reports are decoded. Terminals such as
    Ghostty and foot report modified keys as ESC [ 13 ; 2 u (shift+enter) or
    ESC [ 27 ; 5 ; 13 ~ (ctrl+enter) rather than a bare control byte; the
    decoder now names those keys instead of dropping them.
  • ctrl+j inserts a newline, as documented. The decoder had folded both
    Return (\r) and line feed (\n) into enter, so the advertised ctrl+j
    newline submitted instead. Return and line feed are now distinct.
  • /resume lists stored sessions again. Two defects left the picker
    empty: the query call passed a plain object where the session-query service
    takes an optional AbortSignal (signal?.throwIfAborted() threw, and the
    failure surfaced as "no earlier sessions found"), and the row mapper read
    id/createdAt off the top level of rows that carry them nested inside
    header, so every row was filtered out. The mapper now reads both shapes,
    and no signal is passed. /tree benefits from the same fix.
  • A panel survives a failed action or a cancelled prompt. Enter on a panel
    row used to close the panel and leave only a status line behind when the
    action threw; the panel now stays open and refreshes.

0.3.0

Choose a tag to compare

@JWE24-code JWE24-code released this 26 Sep 16:40
ff60f4f

Added

  • A first-run setup list. A fresh install opens with the four questions
    every new user has — interface language, color palette, provider sign-in,
    voice setup — as a list whose rows open the pickers that answer them.
    Closing it records that the machine has seen it; it never returns unasked.
  • Live preview in the pickers. Moving through /theme repaints the whole
    app in the highlighted palette, and /lang re-renders the chrome in the
    highlighted language; esc restores what was in force and enter keeps
    what you landed on. Browsing by arrow key is trying it on.
  • Tab completion for command arguments. /theme <par<Tab> completes a
    palette name (shared prefix first, then the picker), and /lang <Tab> does
    the same for languages. Completion and browsing end in the same place.
  • Mouse on by default. The wheel scrolls the transcript and a click on a
    tab in the session bar switches to it. Text selection needs shift while
    reporting is on (the app's own alt+c/ctrl+y copy needs nothing);
    --no-mouse restores plain-drag selection. Shift+click is deliberately
    passed to the terminal rather than treated as an app click.
  • The bell covers everything you stopped waiting for. A background job
    that finishes rings with a status line naming it, as does a fleet dispatch
    returning from a peer — not only session turns. Jobs are announced on the
    observed running-to-finished transition, so jobs that were already done
    when the app started stay quiet.

Fixed

  • The interface language no longer resets on restart. lang was written
    on every save but never read back, so a chosen language lasted exactly as
    long as the process did.

  • moqi, the app's own palette, and the new default. A Chinese ink
    painting, which is where the name comes from (墨气): Deep Ink ground, Xuan
    Paper text, Ink Wash selection and borders, Slate Smoke muted layer, with
    the accents kept in the same vocabulary — cinnabar warning, indigo-wash
    accent, celadon/bamboo/ochre semantics — and a light variant that is the
    same painting on paper. The app now starts on it; anyone with a persisted
    choice keeps theirs, and rose-pine remains in the table as the former
    default.

  • Fifteen new color palettes. /theme now offers twenty-one palettes where it
    offered five, spanning the styles the original set did not: pastels
    (Catppuccin), vivid neon (Dracula, Monokai, Synthwave), cool blues
    (Tokyo Night), organic greens (Everforest), editor classics (One, Material,
    Tomorrow), ink wash (Kanagawa), warm neutrals (Ayu), minimal paper, the
    green CRT (Phosphor), and two more accessibility corners alongside mono —
    modus, a pair built to published WCAG contrast guarantees, and
    contrast, saturated hues on a pure base. Published palettes use their own
    light and dark variants as published; where none exists (Dracula, Monokai,
    Nord) a light variant is derived from the palette's hues, as Nord's already
    was. paper, phosphor, synthwave, and contrast are this app's own.

  • A plan probe for OpenAI Codex (ChatGPT). The route has had a sign-in
    since /providers landed but reported nothing in /usage; it now shows the
    ChatGPT plan's tier, its 5-hour and weekly utilization, and any remaining
    credits. The endpoint has no published contract — it is what the first-party
    Codex client reads, reverse-engineered independently by more than one
    third-party tracker, and it has already moved once from x-codex-* response
    headers — so the parser is written to the schema those trackers agree on and
    refuses on any other shape. Its one real trap is handled: Codex states reset
    moments in Unix seconds where every other provider states milliseconds.

  • Provider-stated severity colors the plan bars. Anthropic's usage report
    states per window how pressed it considers the limit (normal, warning,
    critical), and that word now colors the bar instead of the local percentage
    thresholds — the provider knows where the real cliff sits for the plan and
    model in use, so critical at 60% is red there where a percentage rule would
    still call it roomy. An unrecognized or missing severity falls back to the
    local thresholds, which remain in force for providers that report only
    numbers.

  • The Claude block names what the week's allowance went to. The same
    response carries a per-surface breakdown of the 7-day window (Claude Code
    versus chat versus the rest), shown as a note naming the surfaces that used
    any of it — the fact that turns "the week is nearly spent" into a decision
    about where to spend the rest.

  • Plan readings are cached for a minute. Closing /usage and reopening it
    to re-check a number now answers from the earlier reading instead of
    re-hitting every provider; failures are cached on the same terms, so a
    provider that just refused is not hammered for a re-open. Countdowns on reset
    lines stay current regardless, being drawn from the reset time at render
    time.

  • Per-session color themes. /theme used to set one palette for the whole
    app; it is now per session, the same way the model already is. Switching
    tabs repaints in whichever theme that tab is on, a new session starts from
    the theme of the one it was opened from (then diverges independently), and
    a restored session brings its own theme back — falling back to the current
    default if it named a theme this build no longer ships.

  • Sign in to Claude Pro/Max and ChatGPT/Codex. /providers opens a picker
    over every credential ctx.authorization knows how to obtain — a
    human-guided sign-in a plain API key cannot replace. This app adds no
    provider knowledge of its own: it renders whatever flows are registered,
    the same way /plugins lists whatever packages compose the profile.
    Mounting @deepseek-ai/dsh-llm-pi-ai registers a flow for Anthropic (Claude
    Pro/Max) and OpenAI Codex (ChatGPT Plus/Pro) — the two providers it ships a
    login for — from the moment it mounts. A flow's notices and questions are
    rendered as a panel that owns the keyboard until the attempt settles: a
    message plus a page and code to act on, or a prompt (text, a pasted secret,
    or a choice of accounts) that enter answers and esc declines. The page
    lands on the clipboard the moment the notice does — not only once enter
    asks to open it, since the terminal is not always on the machine whose
    browser can reach it — and enter on a bare notice also opens it with the
    platform's own launcher (xdg-open/open/start). A device-code flow's
    page and code stay on screen once its own prompt takes over, rather than
    being replaced by it. Signing in authenticates the route; adding it to
    /model is still an ordinary dsh-llm-pi-ai config, documented in the
    README.

  • /usage reports what each provider's plan actually has left. A local
    tally of tokens cannot answer that question: only the provider knows what a
    prepaid balance is down to, how much of a 5-hour window is gone, or when
    either resets. The pane now asks, using the credentials already in the
    Harness credential store, and shows a block per route — DeepSeek's prepaid
    balance split into granted and topped up, z.ai's plan tier with its 5-hour
    and weekly credit windows and renewal price, and a Claude Pro/Max plan's
    5-hour and 7-day utilization. Every probe runs concurrently and every one
    resolves: a provider that is unset, down, or slow costs its own block one
    line of explanation and leaves the rest of the pane intact, because the
    comparison across providers is the whole point of it. The pane paints
    immediately with what it already knows and fills the plan half in as answers
    land. Quota bars are colored by how close the window is to its limit, not by
    provider identity — a quota is a status reading, not a category. No secret is
    read from a file, printed, or put in a URL: a token goes into an
    Authorization header and nowhere else. The parsers refuse rather than
    improvise, which is load-bearing: z.ai reports a window's limit in a field
    called usage and its consumption in currentValue, so the obvious
    reading of that payload would show a plan as fully spent while it was 1%
    used. The z.ai parser matches those names exactly and cross-checks them
    against the row's own remaining, so a future rename surfaces as a refusal
    rather than silently inverting the bars.

  • DeepSeek's peak window now states its throughput cost, not only its
    price.
    The two bite differently. Pricing is predictable and countdown-able
    — off-peak is half price, so a long job can wait for it. Throughput is the
    one that surprises people: DeepSeek enforces no per-account request limit and
    does not reject requests for load, it holds the connection open instead, so
    at peak what you experience is not an error but a turn that takes far longer
    than usual. Saying so is the difference between "DeepSeek is broken" and "it
    is 09:00 UTC on a Tuesday".

  • Token spend is reported in the provider's own four billed buckets, so a
    row splits prompt from output — priced differently everywhere, and one total
    hides which way a route is expensive — and reports the share of its prompt
    served from cache when there is one to report. That last is the one figure
    here you can act on: a rate that collapses is usually a cache that stopped
    being hit.

  • /usage is a full-screen colored dashboard rather than a markdown
    overlay, so it can carry real per-provider color: three rolling-window
    sections (session 5h, week 7d — the shape Anthropic's Claude Pro/Max and
    z.ai's GLM coding plan both rate-limit on — and a lifetime total that never
    forgets), each a bar chart of every provider's share of the tokens spent in
    that window
    , scaled to that window's own total rather than to its busiest
    provider, so two close providers read as two bars close in length rather
    than one ex...

Read more