Skip to content
dsh-market Browse plugins GitHub 中文

liu3734/jira-tasks-dsh-plugin

JIRA task panel under the DSH composer: per-workspace project key and optional JQL, listing the issues assigned to the current user with status, priority and issue-type chips, a total count, collapse and refresh, links to JIRA, plus a settings card for the base URL and token with a connection probe.

Stars ★ 1 Category UI Enhancements Listed 2026-09-11 npm dsh-jira-tasks

Install

Inside DeepSeek Harness, with dsh-market

dsh plugin --profile web add dshmarket

Or from the command line

dsh plugin --profile web add dsh-jira-tasks

Installing runs third-party code with your own permissions — it can read your files, use your credentials and reach the network. Review the source first, and pin a commit (github:owner/repo#sha) when you can.

README

中文 · English

Shows the current JIRA project's open / reopened issues assigned to the current user below the DSH composer input. The JIRA base URL and token are configured in Settings → JIRA 配置 (the settings dialog's left navigation) (JIRA_BASE_URL / JIRA_API_TOKEN act as fallback); the project key and JQL are configured per workspace and persisted.

DSH versions targeted: 0.1.7 and 0.2.0 (verified on 0.1.7-rc.2 and 0.2.0-rc.2), on both the browser (web profile) and the Desktop app (the Electron-owned desktop profile). The client slots conversation.input.dock / plugins.item / settings.section, the settings service ctx.configForms (namespace = profile entry id jira-tasks), and the ctx.effect / configForms.whileServed disposal contracts are unchanged between 0.1.7 and 0.2.0 (verified package by package), so one lib/ covers both; the 0.1.6-era settingsScope / settings.register / settings.plugin.item APIs are gone and no longer used. The package declares its DSH range as >=0.1.7-rc.2 <0.3.0-0 (engines.dsh plus an optional @deepseek-ai/dsh peer): since 0.2.0 DSH checks that range on install and on profile startup, skipping a mismatched bundle and offering the exact-version allow-version exemption in the plugin manager. The peer is marked optional so pnpm never pulls the whole @deepseek-ai/dsh tree into the profile.

Features

  • 📋 Panel shown below the composer in both new and active sessions (aligned with the input width in new sessions)
  • 👤 Defaults to the current user (assignee = currentUser()) with status 开启 / 重新开启 (Open / Reopened)
  • ⚙️ Settings → JIRA 配置 (left navigation, below Agent presets) edits the base URL and access token (the token is written to the credential store and never sent back to the browser); the same JIRA card stays available in the Plugins panel
  • 🟢 The card auto-probes the connection and shows a status light: green = usable, red = unusable, grey = unconfigured; Test connection verifies unsaved drafts immediately
  • ⚙️ Project key and JQL are saved per workspace; unconfigured workspaces show "unconfigured"
  • 🔄 Auto-query on every new session, with a one-click refresh (⟳)
  • 🔗 Click an issue to open its JIRA detail in a new tab
  • 🎨 Uses DSH theme tokens; adapts to light / dark themes

Install

Published to npm. For the browser (web profile):

dsh plugin --profile web add dsh-jira-tasks

Or straight from GitHub (the repository root is the package directory; lib/ is prebuilt):

dsh plugin --profile web add github:liu3734/jira-tasks-dsh-plugin

Restart DSH to activate.

Desktop app (DeepSeek Harness.app)

The Desktop app does not use the web profile; it boots the Electron-owned desktop profile (~/.dsh/profiles/desktop/). That profile name is reserved: an npm-installed dsh refuses both boot and plugin operations for --profile desktop (profile "desktop" is managed exclusively by the Electron application). Use the Desktop-provided command instead:

  1. Open the Desktop app once (it initializes the desktop profile), then quit it completely

  2. In the app menu, open Manage dsh Command → Install (puts the Desktop's own dsh on your PATH)

  3. Install the plugin:

    dsh plugin --profile desktop add dsh-jira-tasks
    
  4. Confirm "dsh-jira-tasks" is listed in dsh.profile.bundles of ~/.dsh/profiles/desktop/package.json, then start the Desktop app

The renderer's origin is dsh-app://app. Electron forwards every non-static request to the local Host with its cookie, so the plugin's absolute-path fetch("/jira/api/search") keeps working; target="_blank" issue links are handed to the system browser. The project key / JQL live in that origin's localStorage, so they are separate from the browser configuration at http://127.0.0.1:<port> — configure each surface once.

Nothing shows up? Check dsh.profile.bundles first. DSH mounts this package as a profile layer only when "dsh-jira-tasks" is listed in dsh.profile.bundles of that profile's package.json (~/.dsh/profiles/web/package.json or ~/.dsh/profiles/desktop/package.json); being in dependencies alone is not enough — the startup log then prints patch: entry "jira-tasks" not found and the plugin silently never loads. dsh plugin --profile <name> add normally appends that row, but it will not re-append when the package is already a dependency; add "dsh-jira-tasks" to dsh.profile.bundles by hand.

Using GitHub Packages instead: configure @liu3734:registry=https://npm.pkg.github.com/ plus a read token in the profile's .npmrc, then run dsh plugin --profile web add @liu3734/dsh-jira-tasks.

  1. Copy this repository (its root is the package directory) into the profile's packages/dsh-jira-tasks/ (~/.dsh/profiles/web/ for the browser, ~/.dsh/profiles/desktop/ for the Desktop app; skip .git/)
  2. Edit that profile's package.json:
    • Add to dependencies: "dsh-jira-tasks": "file:./packages/dsh-jira-tasks"
    • Append to dsh.profile.bundles: "dsh-jira-tasks"
  3. Run pnpm install in the profile directory
  4. Restart DSH (the Desktop app must be fully quit and reopened)

Note: pnpm install copies the package into node_modules/ (not a symlink) — after editing sources, sync node_modules/dsh-jira-tasks or re-run install.

In a DSH session, use the Cordis tools: cordis_define (kind: new, idPrefix: "jira", sources in plugin/host.js / plugin/client.js) → cordis_run to activate. Dynamic plugins live only in process memory and disappear on restart — for trial use only.

Configuration

1. JIRA base URL and token

Open Settings → JIRA 配置 (the last item in the left navigation, below Agent presets; the JIRA card in the Plugins panel is the same form) and fill in:

  • JIRA base URL: e.g. http://jira.example.com/ (written to this plugin entry's Config.baseUrl — the jira-tasks row in the profile's cordis.patch.yml — and read back by the form; a host-refused write now surfaces as an error instead of failing silently)
  • Access token / PAT: written to the credential store ($DSH_HOME/.credentials.yaml) under the plugin-owned ref JIRA_TASKS_TOKEN; the browser only ever sees "configured", never the token itself

Leaving the token blank on save keeps the existing one; clearing the address on save removes the override and falls back to the credential store / environment. Auth is auto-detected: tokens containing : use Basic, otherwise Bearer (JIRA PAT).

The settings-page token takes precedence over the environment. When the environment that launched DSH already defines JIRA_API_TOKEN (a Windows user-level variable counts), the card is still editable: it stores the token in its own ref JIRA_TASKS_TOKEN, which DSH accepts (it only refuses to write a ref the launching environment shadows), and the Host resolves tokens in this order:

JIRA_TASKS_TOKEN (settings card) > JIRA_API_TOKEN > JIRA_TOKEN

Click Clear settings token in the card to fall back to the environment variable again.

Connection test

The card's footer carries a status light and a Test connection button:

  • Opening the card auto-probes once (against JIRA /rest/api/2/myself), and saving re-probes
  • Green = address and token work (the current user is shown); red = unusable (JIRA's reason, e.g. 401, is shown); grey = address or token not configured
  • Test connection probes what is currently in the fields, saved or not, so you can check before saving

Environment variables / credentials still work as a fallback (used when the settings card leaves baseUrl empty), hot-reloaded without a restart. Address precedence is Config.baseUrl > JIRA_BASE_URL > JIRA_URL, and JIRA_BASE_URL may live in the launching environment or in .credentials.yaml — note that a stale record (e.g. an old domain left in the credential file) becomes effective again as soon as the settings page clears the address:

JIRA_BASE_URL: "http://jira.example.com/"
JIRA_API_TOKEN: "<PAT or user:token>"
  • Base URL aliases: JIRA_BASE_URL / JIRA_URL
  • Token resolution order: JIRA_TASKS_TOKEN (written by the settings card) → JIRA_API_TOKEN → JIRA_TOKEN; the first match wins

2. Project key and JQL (per workspace)

  • Click ⚙ on the panel header to open settings (the form shows the target workspace)
  • Project key: e.g. HCPFYH1 — saved and queried immediately; auto-loaded for new sessions in that workspace
  • JQL: leave empty for the default, or write a custom JQL where {projectKey} (or {key}) is replaced by the project key

Default query:

project = "{projectKey}" AND status in ("开启", "重新开启") AND assignee = currentUser() ORDER BY updated DESC

The status names follow the Chinese workflow (开启/重新开启). For English statuses (Open/Reopened), set a custom JQL in ⚙.

Compatibility & verification

Dimension Coverage
DSH versions 0.1.7-rc.2 and 0.2.0-rc.2 (verified); declared range >=0.1.7-rc.2 <0.3.0-0 — the 0.1.7 and 0.2.x lines, excluding 0.3.0 and its prereleases
Surfaces Browser (dsh web / web profile) and the Desktop app (Electron, desktop profile, renderer origin dsh-app://app)
Host APIs Config / webServer.register / subprocess.spawn / credentials.resolve / settings.configure — signatures unchanged from 0.1.7 to 0.2.0
Client APIs window.__ModuleLoader__.load, slots.inject/register, configForms.get/whileServed, remote.credentials, slots conversation.input.dock / plugins.item / settings.section — contracts unchanged from 0.1.7 to 0.2.0

How it was verified (a throwaway DSH_HOME, the user's own profile untouched):

# Boot the target dsh in isolation; the profile's dsh.profile.bundles lists dsh-jira-tasks
DSH_HOME=/tmp/dsh-check node <dsh-0.2.0-rc.2>/lib/bin.js --profile web --no-open --port 19399

curl -sX POST http://127.0.0.1:19399/jira/api/test   -d '{}'                      # connectivity / auth
curl -sX POST http://127.0.0.1:19399/jira/api/search -d '{"projectKey":"<KEY>"}'  # issue query
curl -s "http://127.0.0.1:19399/?token=<token from the startup log>" | grep -o dsh-jira-tasks  # client bundle in __DSH_BOOT__

For the Desktop app additionally confirm the client bundle appears in the app's Code Cache (the renderer loaded and executed it) and that dsh.jiraTasks.config.v1 exists in its localStorage (the panel works and writes per workspace).

Uninstall

dsh plugin --profile web remove dsh-jira-tasks        # browser
dsh plugin --profile desktop remove dsh-jira-tasks    # Desktop app (needs the Desktop-provided dsh)

Troubleshooting

Message Fix
JIRA base URL not configured Address missing — see "Configuration 1" above
JIRA token not configured Token missing — see "Configuration 1" above
401 … Invalid token or wrong auth scheme; verify with curl -H "Authorization: Bearer <token>" <base>/rest/api/2/myself
Cannot parse JIRA response Network / proxy issue, curl produced no output

Yes. The card stores the token under the plugin-owned ref JIRA_TASKS_TOKEN instead of writing the environment's JIRA_API_TOKEN. DSH only refuses to write a ref the launching environment shadows, so its own ref is always writable: even with JIRA_API_TOKEN exported by the shell or the OS, the field accepts input, the save succeeds, and the Host prefers it:

JIRA_TASKS_TOKEN (settings card) > JIRA_API_TOKEN > JIRA_TOKEN
  • The card names the effective source: with a saved token it says "overrides environment variable JIRA_API_TOKEN"; without one it says "currently using environment variable JIRA_API_TOKEN — fill in and save to override"
  • Saving re-probes the connection; Clear settings token (fall back to environment) removes the override
  • The one remaining case that reports is supplied read-only by the launching environment is someone exporting JIRA_TASKS_TOKEN itself — that ref really is read-only then; remove it (Windows: System Properties → Environment Variables, or PowerShell [Environment]::SetEnvironmentVariable('JIRA_TASKS_TOKEN', $null, 'User')) and restart DSH
  • Make sure it is installed and DSH was restarted (the Desktop app must be fully quit and reopened); in new sessions the panel sits below the input
  • Check dsh.profile.bundles in the right profile: ~/.dsh/profiles/web/package.json for the browser, ~/.dsh/profiles/desktop/package.json for the Desktop app (the most common cause of a silent no-show — see the Install note; that profile can only be edited with the Desktop-provided dsh)
  • Check the DSH startup log: patch: entry "jira-tasks" not found means the profile layer was never composed; Plugin dsh-jira-tasks@<version> is incompatible with dsh <runtime> means the running version falls outside the declared >=0.1.7-rc.2 <0.3.0-0 range, so the bundle is skipped (follow the message, using dsh plugin allow-version or the plugin manager, to grant the exact-version exemption); webserver: duplicate exact route means a route was registered twice (the plugin releases its routes through ctx.effect, so this points at a second copy)
  • A console error client-modules: could not load "dsh-jira-tasks" means /plugins/dsh-jira-tasks/client.js was not served — confirm exports["./client"] resolves to the built lib/client.js
  • The Desktop panel says "unconfigured" although you configured it: project key / JQL are stored per origin, so dsh-app://app (the built-in window) and http://127.0.0.1:<port> (a browser) hold two independent configurations

Architecture & Implementation Details

┌────────────── Browser (Client) ──────────────┐      ┌─────────────── Host ───────────────┐
│ conversation.input.dock (both states)        │      │ webServer route /jira/api/search   │
│ CSS order:99 -> below the input, full width  │      │ entry Config.baseUrl (volatile)    │
│ on mount/refresh: fetch POST                 │      │ credentials.resolve(TOKEN_REFS)    │
│ render: list / error / unconfigured          │      │ subprocess.spawn(curl ...)         │
│ localStorage: per-workspace key/JQL          │      │ parse stdout JSON                  │
│ plugins.item (Plugins panel card)            │      │ return {ok, issues}                │
│ settings.section (Settings nav page)         │      │                                    │
└──────────────────────────────────────────────┘      └────────────────────────────────────┘

- **Host**: declares the entry's own `Config` (`baseUrl`, `volatile` — since DSH 0.1.5 a settings page is built from it; `settings.configure({ auto: false }, ctx.fiber)` turns the auto-generated page off and hands its disposer back to `ctx.effect`) plus `webServer` routes `POST /jira/api/search` and `POST /jira/api/test`, each wrapped in its own `ctx.effect` (a reload otherwise hits "duplicate route" and fails activation; non-POST requests get 405); the token comes from the `credentials` service resolved in the order `JIRA_TASKS_TOKEN` (written by the Settings card) → `JIRA_API_TOKEN` → `JIRA_TOKEN` (`$DSH_HOME/.credentials.yaml` / environment, hot-reloaded), so a saved card token overrides the environment; queries run through `subprocess` spawning `curl` directly, with the auth header passed via stdin (`--config -`) so the token never appears in argv.
- **Client**: a standard `window.__ModuleLoader__.load({ id, factory })` web bundle requiring only `"react"`; registers **only `conversation.input.dock`** (`order: 10`) — that seat renders in both new and active sessions and is a full-width row of `composerStack` (`flex-direction: column`), where the panel's own CSS `order: 99` places it below the input card at the card's width. `conversation.composer.dock` is deliberately NOT used: in 0.1.7 and 0.2.0 alike that seat renders into InputBar's `.dock` **row**, side by side with the context meter, so a full-width panel there gets its right-hand content covered by the meter. The address/token form comes from `ctx.configForms.get("jira-tasks")` and the token is written through `remote.credentials` to `JIRA_TASKS_TOKEN`; `configForms.whileServed` keeps it hidden when the host serves no such namespace. It is registered in **two places**: the Plugins-panel card `plugins.item` (`id: "jira-tasks"`, `order: 41`) and a Settings-navigation section `settings.section` (`id: "jira-tasks"`, `order: 30` — above Agent presets' 20, so it sits below it; `label: "JIRA 配置"`; ids outside the shell's `navIcon` allow-list fall back to the default gear icon). Both share one `JiraSettingsPage`, whose outer container is chosen by the external `variant: "page"` prop (`li.jt-set-card` card vs `div.jt-set-page` page) while the field block is the same array. The card's `summary` view is rendered by a dispatcher that calls **no hooks**, while the form lives in a separate `JiraSettingsPage`, so flipping `view` on one mounted instance never changes its hook count.
- **Why not the `shell` service**: `shell` wraps commands with `sandbox-exec`, which is broken on some macOS versions (`sandbox_apply: Operation not permitted`); `subprocess` is the raw process seam without this issue.
- **Why the Desktop app needs no code branch**: its Host is the same DSH as the web profile (same `@deepseek-ai/dsh-web-app` + `dsh-host-webserver`); only the renderer origin differs (`dsh-app://app`), and Electron's `protocol.handle` forwards every non-static request to the local Host with its cookie, so both `fetch("/jira/api/*")` and `/plugins/...` work. `http(s)` `target="_blank"` links are handed to the system browser by the main window's `setWindowOpenHandler`. The only difference that matters is origin-scoped localStorage, so each surface keeps its own project key / JQL.
- **One registration covers both states**: `conversation.input.dock` renders whenever a session and its input exist, so new and active sessions need no separate registrations (the older `composer.dock` + blank-session de-duplication was both unnecessary under 0.1.7 and the cause of the meter-row collision).

**Differences from the dynamic version**

| Aspect | Dynamic plugin | Persistent install (this package) |
|---|---|---|
| Persistence | Lost on restart | Survives restart |
| Client→Host | `host.call` / `harness.handle` | `webServer` route + `fetch` |
| Client bundle | Injected per session | `/plugins/dsh-jira-tasks/client.js` |
| Config / credentials | Env / `.credentials.yaml` only (no Settings card) | Settings nav page + Plugins-panel card + same `.credentials.yaml` fallback |
</details>

## License

MIT

Content from the project README on GitHub ↗

Comments

Comments live in GitHub Discussions. Sign in with GitHub to post or react.