@hupan56/wlkj 3.3.5 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hupan56/wlkj",
3
- "version": "3.3.5",
3
+ "version": "3.3.6",
4
4
  "description": "AI Product R&D Workflow - PRD/Prototype/Search/Task/Report",
5
5
  "bin": {
6
6
  "wlkj": "bin/cli.js"
@@ -51,9 +51,21 @@ from foundation.integrations.zentao_client import ZentaoClient
51
51
  # ============================================================
52
52
 
53
53
  def _zentao_creds():
54
- """构建禅道客户端。凭据不全返回 None
55
- url 来自 .qoder/config.yaml (团队共享); user/password 来自 secrets/zentao.env (个人)。"""
56
- return ZentaoClient.from_config()
54
+ """构建禅道客户端。返回 (client, preset_account)
55
+
56
+ 凭据源优先级:
57
+ ① 云平台 per-user 凭据(Option A:get_zentao_creds)—— account 已知(preset)
58
+ ② 本地 .qoder/config.yaml + secrets/zentao.env —— account 走 member.json
59
+ 云凭据优先:demo 机器在内网可达禅道,平台凭据比本地文件更可能是最新配的。
60
+ 凭据全无 → (None, '')。
61
+ """
62
+ c = _cloud_zentao_creds()
63
+ if c and c.get('ok') and c.get('url') and c.get('account') and c.get('password'):
64
+ try:
65
+ return ZentaoClient(c['url'], c['account'], c['password']), c['account']
66
+ except Exception:
67
+ pass
68
+ return ZentaoClient.from_config(), ''
57
69
 
58
70
 
59
71
  def _cloud_my_panel():
@@ -107,6 +119,52 @@ def _cloud_my_panel():
107
119
  return data # {ok:True/False, ...}
108
120
 
109
121
 
122
+ def _cloud_zentao_creds():
123
+ """Option A:从平台取禅道凭据(url/account/password 解密)。
124
+
125
+ 用途:云到禅道被防火墙挡(my-panel 登录失败)时,本机(demo 机器,内网可达)
126
+ 用平台凭据直连禅道拉真实数据。凭据随绑定 token 取本人配置(同信任级)。
127
+ Returns: {ok:True, url, account, password} 或 None(云不可达/未配置)。
128
+ """
129
+ import json as _json
130
+ import urllib.request as _u
131
+ cfg_candidates = []
132
+ try:
133
+ from foundation.core.paths import get_repo_root
134
+ cfg_candidates.append(os.path.join(str(get_repo_root()), '.qoder', 'mcp_config.json'))
135
+ except Exception:
136
+ pass
137
+ cfg_candidates.append(os.path.join(_SCRIPTS, '..', 'mcp_config.json'))
138
+ token, url = '', ''
139
+ for cp in cfg_candidates:
140
+ try:
141
+ with open(cp, encoding='utf-8') as f:
142
+ cfg = _json.load(f)
143
+ srv = (cfg.get('mcpServers') or {}).get('wlinkj-knowledge') or {}
144
+ token = srv.get('token', '')
145
+ url = (srv.get('url') or '').rstrip('/')
146
+ if token and url:
147
+ break
148
+ except Exception:
149
+ continue
150
+ if not token or not url:
151
+ return None
152
+ endpoint = url if url.endswith('/mcp') else url + '/mcp'
153
+ body = _json.dumps({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
154
+ "params": {"name": "get_zentao_creds", "arguments": {}}}).encode('utf-8')
155
+ req = _u.Request(endpoint, data=body, method='POST')
156
+ req.add_header('Content-Type', 'application/json')
157
+ req.add_header('X-MCP-Token', token)
158
+ try:
159
+ with _u.urlopen(req, timeout=15) as resp:
160
+ r = _json.loads(resp.read().decode('utf-8', errors='replace'))
161
+ text = r.get('result', {}).get('content', [{}])[0].get('text', '')
162
+ data = _json.loads(text) if text else {}
163
+ return data if data.get('ok') else None
164
+ except Exception:
165
+ return None
166
+
167
+
110
168
  def _resolve_my_account(developer):
111
169
  """解析当前 developer → 禅道 account。返回 (account, 错误原因)。"""
112
170
  mem = get_member(developer)
@@ -451,28 +509,31 @@ def fetch_workbench(include_closed=False, kind=None):
451
509
  else:
452
510
  cloud_error = None # 云不可达(switch_project 没跑/平台宕机)→ 静默回退本地
453
511
 
454
- # ① 凭据(本地回退)
455
- client = _zentao_creds()
512
+ # ① 凭据(云平台优先 Option A,回退本地文件)
513
+ client, preset_account = _zentao_creds()
456
514
  if client is None:
457
- base_msg = ('禅道凭据未配置。推荐在【平台设置 → 禅道】配置(panel 自动走云);'
515
+ base_msg = ('禅道凭据未配置。推荐在【平台设置 → 禅道】配置(panel 自动走云取凭据);'
458
516
  '或本地 .qoder/config.yaml 的 zentao.url + secrets/zentao.env 账号密码。')
459
517
  if cloud_error:
460
- base_msg += '(注:平台已配凭据但云端连不上禅道:%s;本机若在内网可配本地凭据直连。)' % cloud_error
518
+ base_msg += '(云 my-panel 失败:%s' % cloud_error
461
519
  return {'ok': False, 'error': base_msg}, 3
462
520
 
463
521
  ok, msg = client.credentials_ok()
464
522
  if not ok:
465
523
  return {'ok': False, 'error': '禅道凭据不完整: %s' % msg}, 3
466
524
 
467
- # ② 内网连通
525
+ # ② 内网连通(本机能否直连禅道——demo 机器在内网可达)
468
526
  ok, msg = client.check_intranet()
469
527
  if not ok:
470
- return {'ok': False, 'error': '连不上禅道内网: %s' % msg}, 5
528
+ return {'ok': False, 'error': '本机连不上禅道内网: %s' % msg}, 5
471
529
 
472
- # ③ 归属
473
- my_account, err = _resolve_my_account(developer) if developer else (None, '未识别当前开发者身份。')
474
- if not my_account:
475
- return {'ok': False, 'error': err}, 3
530
+ # ③ 归属(云凭据带的 account 优先;否则走 member.json)
531
+ if preset_account:
532
+ my_account = preset_account
533
+ else:
534
+ my_account, err = _resolve_my_account(developer) if developer else (None, '未识别当前开发者身份。')
535
+ if not my_account:
536
+ return {'ok': False, 'error': err}, 3
476
537
 
477
538
  # ④ 收集 (三域并行, 按 kind 决定取哪几域)
478
539
  from concurrent.futures import ThreadPoolExecutor