@lyra-ai/toolkit 0.2.1 → 0.2.3
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/QUICKSTART.md +93 -1
- package/README.md +41 -2
- package/bin/skillhub.mjs +4 -0
- package/bin/toolkit.mjs +24 -16
- package/package.json +4 -2
- package/src/config-cmd.mjs +32 -39
- package/src/flush.mjs +3 -14
- package/src/install.mjs +10 -14
- package/src/logquery.mjs +5 -25
- package/src/shared/config.mjs +125 -0
- package/src/skillhub.mjs +5836 -0
- package/src/status.mjs +10 -11
- package/src/zentao.mjs +58 -10
package/src/status.mjs
CHANGED
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import os from 'node:os';
|
|
8
|
+
import { load as loadConfig, UPLOADED_PATH } from './shared/config.mjs';
|
|
8
9
|
|
|
9
10
|
const HOME = os.homedir();
|
|
10
11
|
const PLUGIN_DIR = path.join(HOME, '.claude', 'plugins', 'toolkit');
|
|
11
|
-
const TOOLKIT_DIR = path.join(HOME, '.toolkit');
|
|
12
12
|
|
|
13
13
|
export function status() {
|
|
14
14
|
const pluginExists = fs.existsSync(path.join(PLUGIN_DIR, '.claude-plugin', 'plugin.json'));
|
|
15
|
-
const
|
|
15
|
+
const config = loadConfig();
|
|
16
|
+
const configExists = !!config.registry || !!config.uid;
|
|
16
17
|
|
|
17
18
|
console.log('Toolkit Status');
|
|
18
19
|
console.log('════════════════════════════════════════');
|
|
@@ -29,28 +30,26 @@ export function status() {
|
|
|
29
30
|
console.log(` 版本: ${pj.version}`);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// 配置
|
|
33
|
+
// 配置(走 shared/config.mjs)
|
|
33
34
|
if (configExists) {
|
|
34
|
-
const config = JSON.parse(fs.readFileSync(path.join(TOOLKIT_DIR, 'config.json'), 'utf-8'));
|
|
35
35
|
console.log(` 平台地址: ${config.registry}${config.contexts?.metrics || '/ai-metrics'}/`);
|
|
36
36
|
console.log(` 身份 UID: ${config.uid?.slice(0, 12)}...`);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// 上传状态
|
|
40
|
-
const uploadedPath = path.join(TOOLKIT_DIR, 'uploaded.json');
|
|
41
40
|
try {
|
|
42
|
-
const up = JSON.parse(fs.readFileSync(
|
|
43
|
-
console.log(`
|
|
44
|
-
console.log(`
|
|
41
|
+
const up = JSON.parse(fs.readFileSync(UPLOADED_PATH, 'utf-8'));
|
|
42
|
+
console.log(` 已同步: ${up.uploaded_sids?.length || 0} 个会话`);
|
|
43
|
+
console.log(` 上次同步: ${up.last_flush || '从未'}`);
|
|
45
44
|
} catch {
|
|
46
|
-
console.log('
|
|
45
|
+
console.log(' 同步状态: 未初始化');
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
// 待传会话计数
|
|
50
49
|
const projectsDir = path.join(HOME, '.claude', 'projects');
|
|
51
50
|
let pending = 0;
|
|
52
51
|
if (fs.existsSync(projectsDir) && configExists) {
|
|
53
|
-
const up = JSON.parse(fs.readFileSync(
|
|
52
|
+
const up = JSON.parse(fs.readFileSync(UPLOADED_PATH, 'utf-8'));
|
|
54
53
|
const uploadedSet = new Set(up.uploaded_sids || []);
|
|
55
54
|
for (const projDir of fs.readdirSync(projectsDir)) {
|
|
56
55
|
const pp = path.join(projectsDir, projDir);
|
|
@@ -62,6 +61,6 @@ export function status() {
|
|
|
62
61
|
} catch {}
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
|
-
console.log(`
|
|
64
|
+
console.log(` 待同步: ${pending} 个会话`);
|
|
66
65
|
console.log('════════════════════════════════════════');
|
|
67
66
|
}
|
package/src/zentao.mjs
CHANGED
|
@@ -62,7 +62,7 @@ const HELP = `zentao-cli —— 面向 AI agent 的禅道 CLI
|
|
|
62
62
|
bug browse-types # 列出 sync --status 的合法 browseType
|
|
63
63
|
config 列出所有配置(密码脱敏)
|
|
64
64
|
config get <key> 取单个配置值(ZENTAO_PASSWORD 需 --raw 才显原文)
|
|
65
|
-
config set <key> <v> 写入 ~/.
|
|
65
|
+
config set <key> <v> 写入 ~/.toolkit/config.json(同时写 .env 兼容)
|
|
66
66
|
config unset <key> 删除配置项
|
|
67
67
|
config path 输出配置文件路径
|
|
68
68
|
init 向导式初始化配置(交互提示填 URL/用户/密码等)
|
|
@@ -71,8 +71,8 @@ const HELP = `zentao-cli —— 面向 AI agent 的禅道 CLI
|
|
|
71
71
|
ping 验证连通性 + 凭据
|
|
72
72
|
|
|
73
73
|
通用: --pretty(人读) --raw(原始 JSON) 默认输出规整 JSON
|
|
74
|
-
配置:
|
|
75
|
-
|
|
74
|
+
配置: 统一存 ~/.toolkit/config.json → zentao 段(也支持 ~/.zentao-cli/.env 兼容)
|
|
75
|
+
toolkit config set zentao.url <url> / zentao.username <u> / zentao.password <p>
|
|
76
76
|
`;
|
|
77
77
|
|
|
78
78
|
function fileTokenStore(cachePath) {
|
|
@@ -91,6 +91,15 @@ function loadFilesText() {
|
|
|
91
91
|
return files; // 项目级在前(优先级高)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// 从 ~/.toolkit/config.json → zentao 段读配置(优先级高于 .env)
|
|
95
|
+
function loadToolkitZentaoConfig() {
|
|
96
|
+
try {
|
|
97
|
+
const configPath = path.join(os.homedir(), '.toolkit', 'config.json');
|
|
98
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
99
|
+
return JSON.parse(raw).zentao || {};
|
|
100
|
+
} catch { return {}; }
|
|
101
|
+
}
|
|
102
|
+
|
|
94
103
|
function main() {
|
|
95
104
|
const argv = process.argv.slice(2);
|
|
96
105
|
const parsed = parseArgs(argv);
|
|
@@ -139,18 +148,28 @@ export function parseEnvFile(text) {
|
|
|
139
148
|
|
|
140
149
|
const REQUIRED = ['ZENTAO_URL', 'ZENTAO_USERNAME', 'ZENTAO_PASSWORD'];
|
|
141
150
|
|
|
142
|
-
export function loadConfig({ env, filesText }, { required } = {}) {
|
|
143
|
-
//
|
|
151
|
+
export function loadConfig({ env, filesText, toolkitZentao }, { required } = {}) {
|
|
152
|
+
// 优先级(高→低): OS env > 项目 .env > 用户 .env > ~/.toolkit/config.json → zentao 段
|
|
144
153
|
const merged = {};
|
|
154
|
+
|
|
155
|
+
// 1. toolkit config.json → zentao 段(最低优先级,作为底)
|
|
156
|
+
const tkZentao = toolkitZentao !== undefined ? toolkitZentao : loadToolkitZentaoConfig();
|
|
157
|
+
for (const [camel, upper] of Object.entries(ZENTAO_KEY_MAP)) {
|
|
158
|
+
if (tkZentao[camel] != null) merged[upper] = String(tkZentao[camel]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 2. .env 文件(项目级 > 用户级)
|
|
145
162
|
for (let i = filesText.length - 1; i >= 0; i--) {
|
|
146
163
|
Object.assign(merged, parseEnvFile(filesText[i]));
|
|
147
164
|
}
|
|
148
|
-
|
|
165
|
+
|
|
166
|
+
// 3. OS env(最高优先级)
|
|
167
|
+
Object.assign(merged, env);
|
|
149
168
|
|
|
150
169
|
const requiredKeys = required !== false ? REQUIRED : [];
|
|
151
170
|
const missing = requiredKeys.filter((k) => !merged[k]);
|
|
152
171
|
if (missing.length) {
|
|
153
|
-
throw new ZentaoError('CONFIG_ERROR',
|
|
172
|
+
throw new ZentaoError('CONFIG_ERROR', `缺少必需配置: ${missing.join(', ')}(toolkit config set zentao.url / zentao.username / zentao.password 或编辑 .env)`);
|
|
154
173
|
}
|
|
155
174
|
return {
|
|
156
175
|
zentaoUrl: String(merged.ZENTAO_URL || '').replace(/\/+$/, ''),
|
|
@@ -165,12 +184,27 @@ export function loadConfig({ env, filesText }, { required } = {}) {
|
|
|
165
184
|
// ---- config 读写(操作 ~/.zentao-cli/.env)----
|
|
166
185
|
|
|
167
186
|
export function configFilePath() {
|
|
187
|
+
// .env 文件路径(兼容读写);toolkit config.json 通过 loadToolkitZentaoConfig 读
|
|
168
188
|
return path.join(os.homedir(), '.zentao-cli', '.env');
|
|
169
189
|
}
|
|
170
190
|
|
|
191
|
+
export function toolkitConfigPath() {
|
|
192
|
+
return path.join(os.homedir(), '.toolkit', 'config.json');
|
|
193
|
+
}
|
|
194
|
+
|
|
171
195
|
// 已知配置键(用于 list 展示顺序与 set 校验提示)
|
|
172
196
|
const KNOWN_KEYS = ['ZENTAO_URL', 'ZENTAO_USERNAME', 'ZENTAO_PASSWORD', 'ZENTAO_DEFAULT_PROJECT', 'ZENTAO_TOKEN_TTL', 'ZENTAO_LOG_LEVEL', 'ZENTAO_CACHE_STALE_MS'];
|
|
173
197
|
|
|
198
|
+
// toolkit config.json 键名映射(camelCase ↔ UPPER_CASE)
|
|
199
|
+
const ZENTAO_KEY_MAP = {
|
|
200
|
+
'url': 'ZENTAO_URL',
|
|
201
|
+
'username': 'ZENTAO_USERNAME',
|
|
202
|
+
'password': 'ZENTAO_PASSWORD',
|
|
203
|
+
'default-project': 'ZENTAO_DEFAULT_PROJECT',
|
|
204
|
+
'token-ttl': 'ZENTAO_TOKEN_TTL',
|
|
205
|
+
'cache-stale-ms': 'ZENTAO_CACHE_STALE_MS',
|
|
206
|
+
};
|
|
207
|
+
|
|
174
208
|
// 敏感键:list/get 默认脱敏
|
|
175
209
|
const SENSITIVE_KEYS = new Set(['ZENTAO_PASSWORD']);
|
|
176
210
|
|
|
@@ -499,9 +533,23 @@ function cmdConfigSet(parsed, filesystem) {
|
|
|
499
533
|
const key = parsed.positional[0];
|
|
500
534
|
const value = parsed.positional[1];
|
|
501
535
|
if (!key || value === undefined) return { stderr: '用法:zentao config set <key> <value>', exitCode: 2 };
|
|
502
|
-
|
|
536
|
+
// 接受 ZENTAO_URL 或 url 或 URL 两种写法
|
|
537
|
+
const keyUpper = key.toUpperCase().replace(/^ZENTAO_/, 'ZENTAO_').startsWith('ZENTAO_') ? key.toUpperCase() : `ZENTAO_${key.toUpperCase()}`;
|
|
503
538
|
if (!KNOWN_KEYS.includes(keyUpper)) {
|
|
504
|
-
return { stderr: `未知配置项: ${key}(合法值: ${KNOWN_KEYS.join(', ')}
|
|
539
|
+
return { stderr: `未知配置项: ${key}(合法值: ${KNOWN_KEYS.join(', ')})。也可用 toolkit config set zentao.url <value>`, exitCode: 2 };
|
|
540
|
+
}
|
|
541
|
+
// 优先写 ~/.toolkit/config.json → zentao 段;同时也写 .env(兼容旧逻辑)
|
|
542
|
+
const camelKey = Object.entries(ZENTAO_KEY_MAP).find(([_, u]) => u === keyUpper)?.[0];
|
|
543
|
+
if (camelKey) {
|
|
544
|
+
try {
|
|
545
|
+
const configPath = path.join(os.homedir(), '.toolkit', 'config.json');
|
|
546
|
+
let tkConfig = {};
|
|
547
|
+
try { tkConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
|
|
548
|
+
if (!tkConfig.zentao) tkConfig.zentao = {};
|
|
549
|
+
tkConfig.zentao[camelKey] = value;
|
|
550
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
551
|
+
fs.writeFileSync(configPath, JSON.stringify(tkConfig, null, 2));
|
|
552
|
+
} catch { /* 写 toolkit config 失败不影响 .env */ }
|
|
505
553
|
}
|
|
506
554
|
setConfigKey(keyUpper, value, filesystem);
|
|
507
555
|
return { stdout: JSON.stringify({ set: keyUpper, value: maskIfSensitive(keyUpper, value) }, null, 2) };
|
|
@@ -516,7 +564,7 @@ function cmdConfigUnset(parsed, filesystem) {
|
|
|
516
564
|
}
|
|
517
565
|
|
|
518
566
|
function cmdConfigPath() {
|
|
519
|
-
return { stdout: configFilePath() };
|
|
567
|
+
return { stdout: toolkitConfigPath() + ' (zentao 段) / ' + configFilePath() + ' (.env 兼容)' };
|
|
520
568
|
}
|
|
521
569
|
|
|
522
570
|
// ---- init 向导 ----
|