dsh-jira-tasks 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +28 -10
- package/README.md +43 -18
- package/lib/client.js +209 -2
- package/lib/index.js +132 -13
- package/package.json +7 -2
package/README.en.md
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
[中文](README.md) · **English**
|
|
6
6
|
|
|
7
|
-
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
|
|
7
|
+
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 → Plugins → JIRA** (`JIRA_BASE_URL` / `JIRA_API_TOKEN` act as fallback); the project key and JQL are **configured per workspace** and persisted.
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- 📋 Panel shown below the composer in both new and active sessions (aligned with the input width in new sessions)
|
|
12
12
|
- 👤 Defaults to the current user (`assignee = currentUser()`) with status `开启 / 重新开启` (Open / Reopened)
|
|
13
|
+
- ⚙️ **Settings → Plugins → JIRA** edits the base URL and access token (the token is written to the credential store and never sent back to the browser)
|
|
14
|
+
- 🟢 The card auto-probes the connection and shows a status light: green = usable, red = unusable, grey = unconfigured; **Test connection** verifies unsaved drafts immediately
|
|
13
15
|
- ⚙️ Project key and JQL are saved per workspace; unconfigured workspaces show "unconfigured"
|
|
14
16
|
- 🔄 Auto-query on every new session, with a one-click refresh (⟳)
|
|
15
17
|
- 🔗 Click an issue to open its JIRA detail in a new tab
|
|
@@ -48,7 +50,22 @@ In a DSH session, use the Cordis tools: `cordis_define` (`kind: new`, `idPrefix:
|
|
|
48
50
|
|
|
49
51
|
### 1. JIRA base URL and token
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
Open **Settings → Plugins → Plugin configuration → JIRA** and fill in:
|
|
54
|
+
|
|
55
|
+
- **JIRA base URL**: e.g. `http://jira.example.com/` (stored in the user settings document and read back by the form)
|
|
56
|
+
- **Access token / PAT**: written to the credential store (`$DSH_HOME/.credentials.yaml`); the browser only ever sees "configured", never the token itself
|
|
57
|
+
|
|
58
|
+
Leaving the token blank on save keeps the existing one; clearing the address on save removes the override and falls back to the environment. Auth is auto-detected: tokens containing `:` use Basic, otherwise Bearer (JIRA PAT).
|
|
59
|
+
|
|
60
|
+
#### Connection test
|
|
61
|
+
|
|
62
|
+
The card's footer carries a status light and a **Test connection** button:
|
|
63
|
+
|
|
64
|
+
- Opening the card **auto-probes** once (against JIRA `/rest/api/2/myself`), and saving re-probes
|
|
65
|
+
- **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
|
|
66
|
+
- **Test connection** probes what is currently in the fields, saved or not, so you can check before saving
|
|
67
|
+
|
|
68
|
+
Environment variables / credentials still work as a **fallback** (used when the settings card is empty), hot-reloaded without a restart:
|
|
52
69
|
|
|
53
70
|
```yaml
|
|
54
71
|
JIRA_BASE_URL: "http://jira.example.com/"
|
|
@@ -57,7 +74,6 @@ JIRA_API_TOKEN: "<PAT or user:token>"
|
|
|
57
74
|
|
|
58
75
|
- Base URL aliases: `JIRA_BASE_URL` / `JIRA_URL`
|
|
59
76
|
- Token aliases: `JIRA_API_TOKEN` / `JIRA_TOKEN`
|
|
60
|
-
- Auth is auto-detected: tokens containing `:` use Basic, otherwise Bearer (JIRA PAT)
|
|
61
77
|
|
|
62
78
|
### 2. Project key and JQL (per workspace)
|
|
63
79
|
|
|
@@ -86,7 +102,8 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
86
102
|
|
|
87
103
|
| Message | Fix |
|
|
88
104
|
|---|---|
|
|
89
|
-
|
|
|
105
|
+
| JIRA base URL not configured | Address missing — see "Configuration 1" above |
|
|
106
|
+
| JIRA token not configured | Token missing — see "Configuration 1" above |
|
|
90
107
|
| 401 … | Invalid token or wrong auth scheme; verify with `curl -H "Authorization: Bearer <token>" <base>/rest/api/2/myself` |
|
|
91
108
|
| Cannot parse JIRA response | Network / proxy issue, curl produced no output |
|
|
92
109
|
</details>
|
|
@@ -107,15 +124,16 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
107
124
|
┌─────────── Browser (Client) ───────────┐ ┌──────────── Host ──────────────┐
|
|
108
125
|
│ conversation.composer.dock (active) │ │ webServer route /jira/api/search │
|
|
109
126
|
│ conversation.input.dock (new, order:99) │ │ ↓ │
|
|
110
|
-
│ ↓ on mount/refresh fetch POST │ │
|
|
111
|
-
│ render: list / error / unconfigured │ │
|
|
112
|
-
│ localStorage per-workspace
|
|
127
|
+
│ ↓ on mount/refresh fetch POST │ │ settings.get("jira-tasks") │
|
|
128
|
+
│ render: list / error / unconfigured │ │ credentials.resolve(JIRA_*) │
|
|
129
|
+
│ localStorage per-workspace key/JQL │ │ subprocess.spawn(curl …) │
|
|
130
|
+
│ settings.plugin.item (Settings card) │ │ ↓ stdout JSON │
|
|
113
131
|
└────────────────────────────────────────────┘ │ parse issues → {ok,issues} │
|
|
114
132
|
└────────────────────────────────┘
|
|
115
133
|
```
|
|
116
134
|
|
|
117
|
-
- **Host**: registers a `webServer` route `POST /jira/api/search`;
|
|
118
|
-
- **Client**: a standard `window.__ModuleLoader__.load({ id, factory })` web bundle; registers `conversation.composer.dock` (active sessions) and `conversation.input.dock` (new sessions, flex `order: 99` below the input, aligned width).
|
|
135
|
+
- **Host**: registers the `jira-tasks` settings namespace (`baseUrl`, readable) and a `webServer` route `POST /jira/api/search`; the address comes from the settings document, the token from the `credentials` service (Settings card / env / `$DSH_HOME/.credentials.yaml`, hot-reloaded); queries run through `subprocess` spawning `curl` directly, with the auth header passed via stdin (`--config -`) so the token never appears in argv.
|
|
136
|
+
- **Client**: a standard `window.__ModuleLoader__.load({ id, factory })` web bundle; registers `conversation.composer.dock` (active sessions) and `conversation.input.dock` (new sessions, flex `order: 99` below the input, aligned width), plus `settings.plugin.item` (`key: "jira-tasks"`) for the Settings card — the address is written through `settingsScope` and the token through `remote.credentials`.
|
|
119
137
|
- **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.
|
|
120
138
|
- **New-session display**: the DSH shell does not render `composer.dock` during the hero (blank session) phase, so the plugin also registers `input.dock` and de-duplicates by "session has messages".
|
|
121
139
|
|
|
@@ -126,7 +144,7 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
126
144
|
| Persistence | Lost on restart | Survives restart |
|
|
127
145
|
| Client→Host | `host.call` / `harness.handle` | `webServer` route + `fetch` |
|
|
128
146
|
| Client bundle | Injected per session | `/plugins/dsh-jira-tasks/client.js` |
|
|
129
|
-
| Config / credentials |
|
|
147
|
+
| Config / credentials | Env / `.credentials.yaml` only (no Settings card) | Settings card + same `.credentials.yaml` fallback |
|
|
130
148
|
</details>
|
|
131
149
|
|
|
132
150
|
## License
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**中文** · [English](README.en.md)
|
|
6
6
|
|
|
7
|
-
在 DSH 会话**输入框下方**展示当前 JIRA 项目**指派给当前用户**的「开启 / 重新开启」任务列表。JIRA
|
|
7
|
+
在 DSH 会话**输入框下方**展示当前 JIRA 项目**指派给当前用户**的「开启 / 重新开启」任务列表。JIRA 地址与令牌在**设置 → 插件 → JIRA** 中配置(`JIRA_BASE_URL` / `JIRA_API_TOKEN` 作为回退);项目 Key 与 JQL **按工作区配置**并持久化。
|
|
8
8
|
|
|
9
9
|
## 功能
|
|
10
10
|
|
|
@@ -14,14 +14,18 @@
|
|
|
14
14
|
<img width="1972" height="746" alt="image" src="https://github.com/user-attachments/assets/9f543e04-a5ad-4b86-85c8-baa2de26f44e" />
|
|
15
15
|
|
|
16
16
|
- 👤 默认仅显示当前用户(`assignee = currentUser()`)的「开启 / 重新开启」任务
|
|
17
|
+
- ⚙️ **设置 → 插件 → JIRA** 配置 JIRA 地址与访问令牌(令牌写入凭据存储,不回传前端)
|
|
18
|
+
- 🟢 设置卡片自动探测连接并显示状态灯:绿=可用、红=不可用、灰=未配置;点「测试连接」可用**未保存的草稿值**即时验证
|
|
17
19
|
- ⚙️ 项目 Key 与 JQL 按工作区保存;未配置的工作区显示"未配置"
|
|
18
20
|
- 🔄 打开新会话自动查询,面板内支持一键刷新(⟳)
|
|
21
|
+
- 🧭 点击标题可**收起 / 展开**面板;标题徽标显示查询命中的任务总数(列表最多展示 50 条,按更新时间倒序)
|
|
22
|
+
- 🏷️ 每条任务附状态徽章(按状态分类着色:新建 / 进行中 / 其他)、优先级与类型标签
|
|
19
23
|
- 🔗 任务可点击,在新标签页打开 JIRA 详情
|
|
20
24
|
- 🎨 颜色使用 DSH 主题令牌,浅色 / 深色主题自适应
|
|
21
25
|
|
|
22
26
|
## 安装
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
包已发布到公共 npm(`dsh-jira-tasks`);仓库的发布工作流同时把同一版本发布到 GitHub Packages(作用域包名 `@liu3734/dsh-jira-tasks`)。从 npm 安装(推荐):
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
31
|
dsh plugin --profile web add dsh-jira-tasks
|
|
@@ -29,6 +33,8 @@ dsh plugin --profile web add dsh-jira-tasks
|
|
|
29
33
|
|
|
30
34
|
**重启 DSH** 后生效。
|
|
31
35
|
|
|
36
|
+
> 若改用 GitHub Packages 源:先在 profile 的 `.npmrc` 配置 `@liu3734:registry=https://npm.pkg.github.com/` 及读取令牌,再执行 `dsh plugin --profile web add @liu3734/dsh-jira-tasks`。
|
|
37
|
+
|
|
32
38
|
<details>
|
|
33
39
|
<summary>手动安装(不使用 npm)</summary>
|
|
34
40
|
|
|
@@ -52,7 +58,22 @@ dsh plugin --profile web add dsh-jira-tasks
|
|
|
52
58
|
|
|
53
59
|
### 1. JIRA 地址与令牌
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
打开 **设置 → 插件 → 插件配置 → JIRA**,填写:
|
|
62
|
+
|
|
63
|
+
- **JIRA 地址**:如 `http://jira.example.com/`(存入用户设置文档,可在界面回读)
|
|
64
|
+
- **访问令牌 / PAT**:写入凭据存储(`$DSH_HOME/.credentials.yaml`),前端只显示"已配置",不回传令牌本身
|
|
65
|
+
|
|
66
|
+
留空并保存会保持已有令牌不变;地址留空并保存则清除设置项,回退到环境变量。认证自动识别:令牌含 `:` 用 Basic,否则用 Bearer(JIRA PAT)。
|
|
67
|
+
|
|
68
|
+
#### 连接测试
|
|
69
|
+
|
|
70
|
+
卡片底部有连接状态灯与 **测试连接** 按钮:
|
|
71
|
+
|
|
72
|
+
- 打开卡片时会**自动探测**一次(请求 JIRA `/rest/api/2/myself`),保存后也会自动重测
|
|
73
|
+
- **绿** = 地址与令牌可用(并显示当前登录用户);**红** = 不可用(显示 JIRA 返回的原因,如 401 认证失败);**灰** = 地址或令牌未配置
|
|
74
|
+
- 点 **测试连接** 会用**当前输入框里的内容**(未保存也可)立即测试,方便改完再存
|
|
75
|
+
|
|
76
|
+
以下环境变量 / 凭据仍作为**回退**(设置页未配置时生效,兼容旧部署),热加载无需重启:
|
|
56
77
|
|
|
57
78
|
```yaml
|
|
58
79
|
JIRA_BASE_URL: "http://jira.example.com/"
|
|
@@ -61,7 +82,6 @@ JIRA_API_TOKEN: "<PAT 或 user:token>"
|
|
|
61
82
|
|
|
62
83
|
- 地址别名:`JIRA_BASE_URL` / `JIRA_URL`
|
|
63
84
|
- 令牌别名:`JIRA_API_TOKEN` / `JIRA_TOKEN`
|
|
64
|
-
- 认证自动识别:令牌含 `:` 用 Basic,否则用 Bearer(JIRA PAT)
|
|
65
85
|
|
|
66
86
|
### 2. 项目 Key 与 JQL(按工作区)
|
|
67
87
|
|
|
@@ -88,11 +108,15 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
88
108
|
<details>
|
|
89
109
|
<summary>面板显示「查询失败」</summary>
|
|
90
110
|
|
|
111
|
+
面板中的提示位于「查询失败:」之后,与下列文案对应:
|
|
112
|
+
|
|
91
113
|
| 提示 | 处理 |
|
|
92
114
|
|---|---|
|
|
93
|
-
|
|
|
115
|
+
| 未设置项目 Key | 面板未配置项目,点标题右侧 ⚙ 填写项目 Key |
|
|
116
|
+
| 未配置 JIRA 地址(设置 → 插件 → JIRA,或环境变量 JIRA_BASE_URL) | 地址未写入,见上文「配置 1」 |
|
|
117
|
+
| 未配置 JIRA 令牌(设置 → 插件 → JIRA,或环境变量 JIRA_API_TOKEN) | 令牌未写入,见上文「配置 1」 |
|
|
94
118
|
| 401 … | 令牌无效或认证方式不对;先 `curl -H "Authorization: Bearer <token>" <base>/rest/api/2/myself` 验证 |
|
|
95
|
-
| 无法解析 JIRA
|
|
119
|
+
| 无法解析 JIRA 响应:… | 网络 / 代理问题,curl 无输出 |
|
|
96
120
|
</details>
|
|
97
121
|
|
|
98
122
|
<details>
|
|
@@ -108,20 +132,21 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
108
132
|
<summary>点击展开</summary>
|
|
109
133
|
|
|
110
134
|
```
|
|
111
|
-
|
|
112
|
-
│ conversation.composer.dock(活跃会话)
|
|
113
|
-
│ conversation.input.dock
|
|
114
|
-
│
|
|
115
|
-
│
|
|
116
|
-
│
|
|
117
|
-
|
|
118
|
-
|
|
135
|
+
┌────────────────────────────────────────┐ ┌────────────────────────────────────┐
|
|
136
|
+
│ conversation.composer.dock(活跃会话) │ │ webServer 路由 /jira/api/search │
|
|
137
|
+
│ conversation.input.dock(新会话) │ │ ↓ │
|
|
138
|
+
│ 面板 hero 布局(flex order:99) │ │ settings.get("jira-tasks").baseUrl │
|
|
139
|
+
│ ↓ 挂载 / 刷新时 fetch POST │ │ credentials.resolve(JIRA_API_TOKEN) │
|
|
140
|
+
│ 渲染:任务列表 / 错误 / 未配置 │ │ subprocess.spawn(curl …) │
|
|
141
|
+
│ localStorage 按工作区存取项目 Key/JQL │ │ ↓ stdout JSON │
|
|
142
|
+
│ settings.plugin.item(设置页卡片) │ │ 解析 issues → 返回 {ok,issues} │
|
|
143
|
+
└────────────────────────────────────────┘ └────────────────────────────────────┘
|
|
119
144
|
```
|
|
120
145
|
|
|
121
|
-
- **Host**:注册 `webServer` 路由 `POST /jira/api/search
|
|
122
|
-
- **Client**:`window.__ModuleLoader__.load({ id, factory })` 标准 web bundle;注册 `conversation.composer.dock
|
|
146
|
+
- **Host**:注册 `settings` 命名空间 `jira-tasks`(`baseUrl`,可读)与 `webServer` 路由 `POST /jira/api/search`;地址优先读设置文档,令牌经 `credentials` 服务解析(设置页写入 / 环境变量 / `$DSH_HOME/.credentials.yaml`,热加载);查询用 `subprocess` 直接 `spawn curl`,认证头经 stdin(`--config -`)传入,令牌不进入命令行参数。
|
|
147
|
+
- **Client**:`window.__ModuleLoader__.load({ id, factory })` 标准 web bundle;注册 `conversation.composer.dock`(活跃会话,注册 `order: 5`)与 `conversation.input.dock`(新会话,注册 `order: 10`)。新会话面板走 hero 布局:面板元素自身 `flex order: 99` 排在输入框之后下方,并以 `--dsh-composer-side-clearance` / `--dsh-composer-card-max-width` 与输入卡等宽。另注册 `settings.plugin.item`(`key: "jira-tasks"`)作为设置页卡片:地址经 `settingsScope` 写入命名空间,令牌经 `remote.credentials` 写入凭据存储。
|
|
123
148
|
- **为什么不用 `shell` 服务**:`shell` 会套 `sandbox-exec`,部分 macOS 上不可用(`sandbox_apply: Operation not permitted`);`subprocess` 是原始进程缝,无此问题。
|
|
124
|
-
- **新会话显示**:DSH 壳在 hero(空白会话)阶段不渲染 `composer.dock`,故额外注册 `input.dock
|
|
149
|
+
- **新会话显示**:DSH 壳在 hero(空白会话)阶段不渲染 `composer.dock`,故额外注册 `input.dock`,并用「会话是否已有消息」去重(新版 DSH 依据 `SessionSnapshot.blank`),避免双份面板。
|
|
125
150
|
|
|
126
151
|
**与动态插件版的差异**
|
|
127
152
|
|
|
@@ -130,7 +155,7 @@ dsh plugin --profile web remove dsh-jira-tasks
|
|
|
130
155
|
| 持久性 | 重启丢失 | 重启保留 |
|
|
131
156
|
| Client→Host 通信 | `host.call` / `harness.handle` | `webServer` 路由 + `fetch` |
|
|
132
157
|
| 客户端 bundle | 会话内注入 | `/plugins/dsh-jira-tasks/client.js` |
|
|
133
|
-
| 配置 / 凭据 |
|
|
158
|
+
| 配置 / 凭据 | 仅环境变量 / `.credentials.yaml`(无设置页卡片) | 设置页卡片 + 同一 `.credentials.yaml` 回退 |
|
|
134
159
|
</details>
|
|
135
160
|
|
|
136
161
|
## License
|
package/lib/client.js
CHANGED
|
@@ -43,7 +43,16 @@ window.__ModuleLoader__.load({
|
|
|
43
43
|
+ ".jt-btn:hover{background:var(--dsw-alias-interactive-bg-hover, rgba(127,127,127,.1));opacity:1}"
|
|
44
44
|
+ ".jt-btn-primary{background:var(--dsw-alias-button-primary-fill, #2f6fed);border-color:transparent;color:var(--dsw-alias-label-primary-foreground, #fff)}"
|
|
45
45
|
+ ".jt-btn-primary:hover{background:var(--dsw-alias-button-primary-hover, #3a7bfd)}"
|
|
46
|
-
+ ".jt-linklike{background:none;border:none;padding:0;cursor:pointer;color:var(--dsw-alias-brand-primary, #2f6fed);font-size:12px;text-decoration:underline}"
|
|
46
|
+
+ ".jt-linklike{background:none;border:none;padding:0;cursor:pointer;color:var(--dsw-alias-brand-primary, #2f6fed);font-size:12px;text-decoration:underline}"
|
|
47
|
+
+ ".jt-set-card{box-sizing:border-box;list-style:none;margin:0;border:1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.28));background:var(--dsw-alias-bg-layer-1, rgba(127,127,127,.05));border-radius:10px;padding:10px 12px}"
|
|
48
|
+
+ ".jt-set-head{display:flex;flex-direction:column;gap:2px;margin-bottom:8px}"
|
|
49
|
+
+ ".jt-set-title{font-weight:600;font-size:13px;color:var(--dsw-alias-label-primary, #222)}"
|
|
50
|
+
+ ".jt-set-desc{font-size:12px;color:var(--dsw-alias-label-secondary, #666);line-height:1.6}"
|
|
51
|
+
+ ".jt-probe{display:flex;align-items:center;gap:6px;margin:0 0 8px;font-size:12px;color:var(--dsw-alias-label-secondary, #666);line-height:1.6;word-break:break-word}"
|
|
52
|
+
+ ".jt-dot{flex:none;width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-secondary, #999)}"
|
|
53
|
+
+ ".jt-dot-ok{background:var(--dsw-alias-state-success-primary, #1a9d4f)}"
|
|
54
|
+
+ ".jt-dot-fail{background:var(--dsw-alias-state-error-primary, #d93026)}"
|
|
55
|
+
+ ".jt-dot-testing{background:var(--dsw-alias-state-warn-primary, #b76e00)}";
|
|
47
56
|
|
|
48
57
|
var STORAGE_KEY = "dsh.jiraTasks.config.v1";
|
|
49
58
|
|
|
@@ -103,6 +112,15 @@ window.__ModuleLoader__.load({
|
|
|
103
112
|
}).then(function (r) { return r.json(); });
|
|
104
113
|
}
|
|
105
114
|
|
|
115
|
+
// 探测 JIRA 地址/令牌可用性;可选传入未保存的草稿值。
|
|
116
|
+
function testConnection(payload) {
|
|
117
|
+
return fetch("/jira/api/test", {
|
|
118
|
+
method: "POST",
|
|
119
|
+
headers: { "Content-Type": "application/json" },
|
|
120
|
+
body: JSON.stringify(payload || {})
|
|
121
|
+
}).then(function (r) { return r.json(); });
|
|
122
|
+
}
|
|
123
|
+
|
|
106
124
|
function JiraTasksDock(props) {
|
|
107
125
|
var blankOnly = !!(props && props.blankOnly === true);
|
|
108
126
|
var useWorkspaces = props && typeof props.useWorkspaces === "function" ? props.useWorkspaces : null;
|
|
@@ -264,6 +282,185 @@ window.__ModuleLoader__.load({
|
|
|
264
282
|
);
|
|
265
283
|
}
|
|
266
284
|
|
|
285
|
+
// ---- 设置页卡片:设置 → 插件 → JIRA ----
|
|
286
|
+
// 地址存 settings 命名空间(可读);令牌写 credentials(永不随响应回传)。
|
|
287
|
+
var SETTINGS_NS = "jira-tasks";
|
|
288
|
+
var TOKEN_REF = "JIRA_API_TOKEN";
|
|
289
|
+
var TOKEN_REFS = ["JIRA_API_TOKEN", "JIRA_TOKEN"];
|
|
290
|
+
|
|
291
|
+
function useScopeSnapshot(scope) {
|
|
292
|
+
var state = React.useState(function () { return scope.getSnapshot(); });
|
|
293
|
+
var snapshot = state[0], setSnapshot = state[1];
|
|
294
|
+
React.useEffect(function () {
|
|
295
|
+
var update = function () { setSnapshot(scope.getSnapshot()); };
|
|
296
|
+
var off = scope.subscribe(update);
|
|
297
|
+
update();
|
|
298
|
+
return off;
|
|
299
|
+
}, []);
|
|
300
|
+
return snapshot;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function createJiraSettingsCard(scope, credentials) {
|
|
304
|
+
function readTokenInfo() {
|
|
305
|
+
return credentials.describe(TOKEN_REFS).then(function (res) {
|
|
306
|
+
if (!res || !res.ok || !res.value) return { configured: false, writable: false };
|
|
307
|
+
for (var i = 0; i < TOKEN_REFS.length; i++) {
|
|
308
|
+
var info = res.value[TOKEN_REFS[i]];
|
|
309
|
+
if (info && info.configured) return { configured: true, writable: info.writable !== false };
|
|
310
|
+
}
|
|
311
|
+
var primary = res.value[TOKEN_REF];
|
|
312
|
+
return { configured: false, writable: primary ? primary.writable !== false : true };
|
|
313
|
+
}).catch(function () {
|
|
314
|
+
return { configured: false, writable: false };
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return function JiraSettingsCard() {
|
|
319
|
+
var snapshot = useScopeSnapshot(scope);
|
|
320
|
+
var tokenInfoState = React.useState(null);
|
|
321
|
+
var tokenInfo = tokenInfoState[0], setTokenInfo = tokenInfoState[1];
|
|
322
|
+
var baseEditState = React.useState(undefined);
|
|
323
|
+
var baseEdit = baseEditState[0], setBaseEdit = baseEditState[1];
|
|
324
|
+
var tokenDraftState = React.useState("");
|
|
325
|
+
var tokenDraft = tokenDraftState[0], setTokenDraft = tokenDraftState[1];
|
|
326
|
+
var busyState = React.useState(false);
|
|
327
|
+
var busy = busyState[0], setBusy = busyState[1];
|
|
328
|
+
var failedState = React.useState("");
|
|
329
|
+
var failed = failedState[0], setFailed = failedState[1];
|
|
330
|
+
var probeState = React.useState({ phase: "idle" });
|
|
331
|
+
var probe = probeState[0], setProbe = probeState[1];
|
|
332
|
+
var probeGen = React.useRef(0);
|
|
333
|
+
|
|
334
|
+
React.useEffect(function () {
|
|
335
|
+
var alive = true;
|
|
336
|
+
readTokenInfo().then(function (info) { if (alive) setTokenInfo(info); });
|
|
337
|
+
return function () { alive = false; };
|
|
338
|
+
}, []);
|
|
339
|
+
|
|
340
|
+
// 自动测试:命名空间就绪后、以及已保存配置变更(含保存后)时各探测一次。
|
|
341
|
+
React.useEffect(function () {
|
|
342
|
+
if (snapshot.status !== "ready") return;
|
|
343
|
+
runProbe({});
|
|
344
|
+
}, [snapshot.status, snapshot.revision]);
|
|
345
|
+
|
|
346
|
+
var storedBase = snapshot.value && typeof snapshot.value.baseUrl === "string" ? snapshot.value.baseUrl : "";
|
|
347
|
+
var baseValue = baseEdit === undefined ? storedBase : baseEdit;
|
|
348
|
+
var dirty = (baseEdit !== undefined && baseEdit !== storedBase) || (tokenDraft || "").length > 0;
|
|
349
|
+
var writable = snapshot.writable !== false;
|
|
350
|
+
|
|
351
|
+
// 命名空间未挂载时不显示,避免留下一张无法操作的卡片。
|
|
352
|
+
if (snapshot.status === "unavailable") return null;
|
|
353
|
+
|
|
354
|
+
function save() {
|
|
355
|
+
if (busy || !dirty) return;
|
|
356
|
+
setBusy(true);
|
|
357
|
+
setFailed("");
|
|
358
|
+
var tasks = [];
|
|
359
|
+
if (baseEdit !== undefined && baseEdit !== storedBase) {
|
|
360
|
+
tasks.push(baseEdit === "" ? scope.unset("baseUrl") : scope.set("baseUrl", baseEdit));
|
|
361
|
+
}
|
|
362
|
+
var token = (tokenDraft || "").trim();
|
|
363
|
+
if (token) {
|
|
364
|
+
tasks.push(credentials.set(TOKEN_REF, token).then(function (res) {
|
|
365
|
+
if (res && res.ok === false) {
|
|
366
|
+
var err = res.error;
|
|
367
|
+
throw new Error((err && (err.message || err.code)) || "令牌保存失败");
|
|
368
|
+
}
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
Promise.all(tasks).then(function () {
|
|
372
|
+
return readTokenInfo();
|
|
373
|
+
}).then(function (info) {
|
|
374
|
+
setTokenInfo(info);
|
|
375
|
+
setBaseEdit(undefined);
|
|
376
|
+
setTokenDraft("");
|
|
377
|
+
setBusy(false);
|
|
378
|
+
}).catch(function (err) {
|
|
379
|
+
setBusy(false);
|
|
380
|
+
setFailed(String((err && err.message) || err));
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function discard() {
|
|
385
|
+
setBaseEdit(undefined);
|
|
386
|
+
setTokenDraft("");
|
|
387
|
+
setFailed("");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// 探测连接:payload 传入即测草稿,传空对象则测已保存/环境配置。
|
|
391
|
+
function runProbe(payload) {
|
|
392
|
+
var gen = ++probeGen.current;
|
|
393
|
+
setProbe({ phase: "testing", message: "正在测试连接…" });
|
|
394
|
+
testConnection(payload).then(function (res) {
|
|
395
|
+
if (gen !== probeGen.current) return;
|
|
396
|
+
if (res && res.ok) setProbe({ phase: "ok", message: "可用" + (res.user ? ":" + res.user : "") });
|
|
397
|
+
else if (res && res.code === "unconfigured") setProbe({ phase: "idle", message: "未配置地址或令牌" });
|
|
398
|
+
else setProbe({ phase: "fail", message: (res && res.error) || "不可用" });
|
|
399
|
+
}).catch(function (err) {
|
|
400
|
+
if (gen !== probeGen.current) return;
|
|
401
|
+
setProbe({ phase: "fail", message: String((err && err.message) || err) });
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
var tokenHint = tokenInfo === null
|
|
406
|
+
? "正在读取令牌状态…"
|
|
407
|
+
: (tokenInfo.configured ? "令牌已配置;留空并保存可保持不变。" : "令牌未配置。含 “:” 时用 Basic,否则用 Bearer。");
|
|
408
|
+
var dotClass = probe.phase === "ok" ? "jt-dot jt-dot-ok"
|
|
409
|
+
: probe.phase === "fail" ? "jt-dot jt-dot-fail"
|
|
410
|
+
: probe.phase === "testing" ? "jt-dot jt-dot-testing" : "jt-dot";
|
|
411
|
+
|
|
412
|
+
return h("li", { className: "jt-set-card" },
|
|
413
|
+
h("div", { className: "jt-set-head" },
|
|
414
|
+
h("span", { className: "jt-set-title" }, "JIRA"),
|
|
415
|
+
h("span", { className: "jt-set-desc" }, "JIRA 地址与访问令牌。项目 Key / JQL 仍按工作区在会话面板的 ⚙ 中配置。")
|
|
416
|
+
),
|
|
417
|
+
h("div", { className: "jt-field" },
|
|
418
|
+
h("label", { htmlFor: "jira-tasks-base-url" }, "JIRA 地址"),
|
|
419
|
+
h("input", {
|
|
420
|
+
id: "jira-tasks-base-url",
|
|
421
|
+
className: "jt-input",
|
|
422
|
+
value: baseValue,
|
|
423
|
+
placeholder: "http://jira.example.com/",
|
|
424
|
+
disabled: !writable || busy,
|
|
425
|
+
onChange: function (e) { setBaseEdit(e.target.value); },
|
|
426
|
+
onKeyDown: function (e) { if (e.key === "Enter") save(); }
|
|
427
|
+
})
|
|
428
|
+
),
|
|
429
|
+
h("div", { className: "jt-field" },
|
|
430
|
+
h("label", { htmlFor: "jira-tasks-token" }, "访问令牌 / PAT"),
|
|
431
|
+
h("input", {
|
|
432
|
+
id: "jira-tasks-token",
|
|
433
|
+
className: "jt-input",
|
|
434
|
+
type: "password",
|
|
435
|
+
value: tokenDraft,
|
|
436
|
+
placeholder: tokenInfo && tokenInfo.configured ? "已配置,留空保持不变" : "粘贴令牌",
|
|
437
|
+
autoComplete: "off",
|
|
438
|
+
disabled: !writable || busy,
|
|
439
|
+
onChange: function (e) { setTokenDraft(e.target.value); },
|
|
440
|
+
onKeyDown: function (e) { if (e.key === "Enter") save(); }
|
|
441
|
+
})
|
|
442
|
+
),
|
|
443
|
+
h("div", { className: "jt-hint", style: { marginBottom: "8px" } }, tokenHint),
|
|
444
|
+
h("div", { className: "jt-probe", role: "status" },
|
|
445
|
+
h("span", { className: dotClass, "aria-hidden": "true" }),
|
|
446
|
+
h("span", null, probe.message || "未测试")
|
|
447
|
+
),
|
|
448
|
+
failed ? h("div", { className: "jt-error", style: { marginBottom: "8px" } }, "保存失败:" + failed) : null,
|
|
449
|
+
h("div", { className: "jt-buttons" },
|
|
450
|
+
h("button", {
|
|
451
|
+
className: "jt-btn",
|
|
452
|
+
style: { marginRight: "auto" },
|
|
453
|
+
disabled: busy || probe.phase === "testing",
|
|
454
|
+
title: "用当前填写的内容测试 JIRA 地址与令牌",
|
|
455
|
+
onClick: function () { runProbe({ baseUrl: baseValue, token: (tokenDraft || "").trim() }); }
|
|
456
|
+
}, probe.phase === "testing" ? "测试中…" : "测试连接"),
|
|
457
|
+
h("button", { className: "jt-btn", disabled: !dirty || busy, onClick: discard }, "放弃"),
|
|
458
|
+
h("button", { className: "jt-btn jt-btn-primary", disabled: !dirty || busy || !writable, onClick: save }, busy ? "保存中…" : "保存")
|
|
459
|
+
)
|
|
460
|
+
);
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
267
464
|
function apply(ctx) {
|
|
268
465
|
// ctx.effect(cb) 会立即执行 cb 并把其返回值作为卸载时的清理函数,
|
|
269
466
|
// 所以样式注入必须放在回调内、返回移除函数,否则标签刚插入就被删除。
|
|
@@ -289,9 +486,19 @@ window.__ModuleLoader__.load({
|
|
|
289
486
|
return h(JiraTasksDock, Object.assign({}, props, { blankOnly: true }));
|
|
290
487
|
});
|
|
291
488
|
});
|
|
489
|
+
|
|
490
|
+
// 设置页卡片:宿主注册了 jira-tasks 命名空间时,由插件配置 Tab 分发到本卡片。
|
|
491
|
+
var settingsScope = ctx.get("settingsScope");
|
|
492
|
+
var remote = ctx.get("remote");
|
|
493
|
+
if (settingsScope && remote && remote.credentials) {
|
|
494
|
+
var card = createJiraSettingsCard(settingsScope.bind({ namespace: SETTINGS_NS }), remote.credentials);
|
|
495
|
+
slots.inject("settings.plugin.item", function () {
|
|
496
|
+
return slots.register({ name: "settings.plugin.item", key: SETTINGS_NS }, card);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
292
499
|
}
|
|
293
500
|
|
|
294
|
-
exports.inject = ["slots"];
|
|
501
|
+
exports.inject = ["slots", "settingsScope", "remote", "remote.credentials"];
|
|
295
502
|
exports.apply = apply;
|
|
296
503
|
return module.exports;
|
|
297
504
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-jira-tasks — HOST half (persistent profile plugin).
|
|
3
|
-
* Serves POST /jira/api/search: reads
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Serves POST /jira/api/search: reads the JIRA base URL from the `jira-tasks`
|
|
4
|
+
* settings namespace and the token from the credentials service (falling back
|
|
5
|
+
* to JIRA_BASE_URL / JIRA_API_TOKEN for existing setups), queries JIRA
|
|
6
|
+
* /rest/api/2/search through subprocess+curl (auth header via stdin
|
|
7
|
+
* --config -), returns normalized issue JSON.
|
|
6
8
|
*/
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
|
|
11
|
+
/** Settings namespace exposing the editable connection fields in the GUI. */
|
|
12
|
+
const SETTINGS_NS = 'jira-tasks';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Durable connection schema. `baseUrl` is a plain readable field; the token is
|
|
16
|
+
* deliberately NOT stored here — it stays in the credentials store (write-only
|
|
17
|
+
* from the client) so no secret ever lands in settings.yaml.
|
|
18
|
+
*/
|
|
19
|
+
const JiraSettingsSchema = z.object({
|
|
20
|
+
baseUrl: z.string().default('').description('JIRA 地址,例如 http://jira.example.com/')
|
|
21
|
+
});
|
|
22
|
+
|
|
7
23
|
export default {
|
|
8
24
|
inject: ['subprocess', 'credentials', 'sandboxPolicy', 'webServer'],
|
|
9
25
|
apply(ctx) {
|
|
@@ -39,6 +55,24 @@ export default {
|
|
|
39
55
|
return undefined;
|
|
40
56
|
}
|
|
41
57
|
|
|
58
|
+
// Register the GUI-editable section when the optional settings service is
|
|
59
|
+
// composed; without it the plugin keeps working from the composition/env.
|
|
60
|
+
ctx.inject(['settings'], (settingsCtx) => {
|
|
61
|
+
settingsCtx.settings.register(SETTINGS_NS, JiraSettingsSchema);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/** Base URL from the settings document, or '' when unset/unavailable. */
|
|
65
|
+
function settingsBaseUrl() {
|
|
66
|
+
try {
|
|
67
|
+
const settings = ctx.get('settings');
|
|
68
|
+
if (!settings) return '';
|
|
69
|
+
const section = settings.get(SETTINGS_NS);
|
|
70
|
+
return section && typeof section.baseUrl === 'string' ? section.baseUrl.trim() : '';
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return '';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
42
76
|
function buildAuthHeader(token) {
|
|
43
77
|
if (token.indexOf(':') !== -1) return 'Basic ' + Buffer.from(token, 'utf-8').toString('base64');
|
|
44
78
|
return 'Bearer ' + token;
|
|
@@ -83,14 +117,57 @@ export default {
|
|
|
83
117
|
return { exitCode: outcome.exitCode, stdout, stderr };
|
|
84
118
|
}
|
|
85
119
|
|
|
120
|
+
// 从 JIRA 错误响应体/curl stderr 中提取可读信息。
|
|
121
|
+
function errorTextFrom(body, stderr, fallback) {
|
|
122
|
+
let message = (stderr || '').trim() || fallback;
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(body);
|
|
125
|
+
if (parsed.errorMessages && parsed.errorMessages.length) message = parsed.errorMessages.join(';');
|
|
126
|
+
else if (parsed.message) message = parsed.message;
|
|
127
|
+
} catch (e) { /* body 非 JSON */ }
|
|
128
|
+
return message;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 连通性/鉴权探测:GET /rest/api/2/myself,stdout 末尾追加 HTTP 状态码。
|
|
132
|
+
async function probeJira(baseUrl, token) {
|
|
133
|
+
const auth = buildAuthHeader(token);
|
|
134
|
+
const cleanBase = baseUrl.replace(/\/+$/, '');
|
|
135
|
+
let curl;
|
|
136
|
+
try {
|
|
137
|
+
curl = await subprocess.resolveExecutable('curl');
|
|
138
|
+
} catch (e) {
|
|
139
|
+
curl = '/usr/bin/curl';
|
|
140
|
+
}
|
|
141
|
+
const handle = subprocess.spawn({
|
|
142
|
+
argv: [
|
|
143
|
+
curl, '-sS', '--max-time', '15',
|
|
144
|
+
'-H', 'Accept: application/json',
|
|
145
|
+
'--config', '-',
|
|
146
|
+
'-w', '\n%{http_code}',
|
|
147
|
+
cleanBase + '/rest/api/2/myself'
|
|
148
|
+
],
|
|
149
|
+
cwd: workspaceRoot,
|
|
150
|
+
stdio: {
|
|
151
|
+
stdin: { data: 'header = "Authorization: ' + auth + '"\n' },
|
|
152
|
+
stdout: { maxBytes: 1024 * 1024, spill: { maxBytes: 4 * 1024 * 1024 } },
|
|
153
|
+
stderr: { maxBytes: 256 * 1024, spill: { maxBytes: 1024 * 1024 } }
|
|
154
|
+
},
|
|
155
|
+
graceMs: 5000
|
|
156
|
+
});
|
|
157
|
+
const outcome = await handle.done;
|
|
158
|
+
const stdout = (handle.collected.stdout ? handle.collected.stdout.readFrom(0).text : '') || '';
|
|
159
|
+
const stderr = (handle.collected.stderr ? handle.collected.stderr.readFrom(0).text : '') || '';
|
|
160
|
+
return { exitCode: outcome.exitCode, stdout, stderr };
|
|
161
|
+
}
|
|
162
|
+
|
|
86
163
|
async function handleSearch(args) {
|
|
87
164
|
const projectKey = String((args && args.projectKey) || '').trim();
|
|
88
165
|
if (!projectKey) return { ok: false, error: '未设置项目 Key' };
|
|
89
166
|
try {
|
|
90
|
-
const baseUrl = await resolveFirst(['JIRA_BASE_URL', 'JIRA_URL']);
|
|
91
|
-
if (!baseUrl) return { ok: false, error: '
|
|
167
|
+
const baseUrl = settingsBaseUrl() || await resolveFirst(['JIRA_BASE_URL', 'JIRA_URL']);
|
|
168
|
+
if (!baseUrl) return { ok: false, error: '未配置 JIRA 地址(设置 → 插件 → JIRA,或环境变量 JIRA_BASE_URL)' };
|
|
92
169
|
const token = await resolveFirst(['JIRA_API_TOKEN', 'JIRA_TOKEN']);
|
|
93
|
-
if (!token) return { ok: false, error: '
|
|
170
|
+
if (!token) return { ok: false, error: '未配置 JIRA 令牌(设置 → 插件 → JIRA,或环境变量 JIRA_API_TOKEN)' };
|
|
94
171
|
|
|
95
172
|
const jql = buildJql(args && args.jql ? String(args.jql) : '', projectKey);
|
|
96
173
|
const raw = await queryJira(baseUrl, token, jql);
|
|
@@ -98,13 +175,7 @@ export default {
|
|
|
98
175
|
const stderr = raw.stderr;
|
|
99
176
|
|
|
100
177
|
if (raw.exitCode !== 0) {
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const parsed = JSON.parse(stdout);
|
|
104
|
-
if (parsed.errorMessages && parsed.errorMessages.length) message = parsed.errorMessages.join(';');
|
|
105
|
-
else if (parsed.message) message = parsed.message;
|
|
106
|
-
} catch (e) { /* stdout 非 JSON */ }
|
|
107
|
-
return { ok: false, error: message };
|
|
178
|
+
return { ok: false, error: errorTextFrom(stdout, stderr, 'curl 退出码 ' + String(raw.exitCode)) };
|
|
108
179
|
}
|
|
109
180
|
|
|
110
181
|
let data;
|
|
@@ -135,6 +206,43 @@ export default {
|
|
|
135
206
|
}
|
|
136
207
|
}
|
|
137
208
|
|
|
209
|
+
// 设置卡片“测试连接”:用草稿值(未保存也可测)或已保存/环境配置探测 JIRA。
|
|
210
|
+
async function handleTest(args) {
|
|
211
|
+
try {
|
|
212
|
+
const draftBase = args && typeof args.baseUrl === 'string' ? args.baseUrl.trim() : '';
|
|
213
|
+
const draftToken = args && typeof args.token === 'string' ? args.token.trim() : '';
|
|
214
|
+
const baseUrl = draftBase || settingsBaseUrl() || await resolveFirst(['JIRA_BASE_URL', 'JIRA_URL']);
|
|
215
|
+
if (!baseUrl) return { ok: false, code: 'unconfigured', error: '未配置 JIRA 地址' };
|
|
216
|
+
const token = draftToken || await resolveFirst(['JIRA_API_TOKEN', 'JIRA_TOKEN']);
|
|
217
|
+
if (!token) return { ok: false, code: 'unconfigured', error: '未配置 JIRA 令牌' };
|
|
218
|
+
|
|
219
|
+
const raw = await probeJira(baseUrl, token);
|
|
220
|
+
if (raw.exitCode !== 0) {
|
|
221
|
+
return { ok: false, code: 'network', error: (raw.stderr || '').trim() || ('curl 退出码 ' + String(raw.exitCode)) };
|
|
222
|
+
}
|
|
223
|
+
const nl = raw.stdout.lastIndexOf('\n');
|
|
224
|
+
const body = nl === -1 ? raw.stdout : raw.stdout.slice(0, nl);
|
|
225
|
+
const httpCode = Number((nl === -1 ? '' : raw.stdout.slice(nl + 1)).trim()) || 0;
|
|
226
|
+
|
|
227
|
+
if (httpCode >= 200 && httpCode < 300) {
|
|
228
|
+
let data = {};
|
|
229
|
+
try { data = JSON.parse(body); } catch (e) { /* 空/非 JSON 也视为连通 */ }
|
|
230
|
+
return {
|
|
231
|
+
ok: true,
|
|
232
|
+
baseUrl: baseUrl.replace(/\/+$/, ''),
|
|
233
|
+
user: data.displayName || data.name || data.key || ''
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
ok: false,
|
|
238
|
+
code: httpCode === 401 || httpCode === 403 ? 'auth' : 'http',
|
|
239
|
+
error: errorTextFrom(body, raw.stderr, 'HTTP ' + String(httpCode))
|
|
240
|
+
};
|
|
241
|
+
} catch (err) {
|
|
242
|
+
return { ok: false, code: 'error', error: String((err && err.message) || err) };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
138
246
|
if (webServer && typeof webServer.register === 'function') {
|
|
139
247
|
webServer.register({
|
|
140
248
|
kind: 'exact',
|
|
@@ -147,6 +255,17 @@ export default {
|
|
|
147
255
|
}
|
|
148
256
|
}
|
|
149
257
|
});
|
|
258
|
+
webServer.register({
|
|
259
|
+
kind: 'exact',
|
|
260
|
+
path: '/jira/api/test',
|
|
261
|
+
handler: async (req, res) => {
|
|
262
|
+
try {
|
|
263
|
+
sendJson(res, await handleTest(await readBody(req)));
|
|
264
|
+
} catch (e) {
|
|
265
|
+
sendJson(res, { ok: false, code: 'error', error: String((e && e.message) || e) });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
});
|
|
150
269
|
}
|
|
151
270
|
}
|
|
152
271
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-jira-tasks",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "JIRA open tasks panel for DeepSeek Harness (DSH): shows the current user's open/reopened issues below the composer, per-workspace project key, persistent profile bundle.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"./client": "./lib/client.js",
|
|
10
10
|
"./package.json": "./package.json"
|
|
11
11
|
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
14
|
+
},
|
|
12
15
|
"files": [
|
|
13
16
|
"lib",
|
|
14
17
|
"cordis.patch.yml",
|
|
@@ -36,7 +39,9 @@
|
|
|
36
39
|
"client": {
|
|
37
40
|
"platform": "web",
|
|
38
41
|
"inject": [
|
|
39
|
-
"@deepseek-ai/dsh-client-ui-conversation"
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
44
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
40
45
|
]
|
|
41
46
|
}
|
|
42
47
|
}
|