skillfree 0.1.65 → 0.1.67

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/SKILL.md CHANGED
@@ -156,8 +156,11 @@ skillfree pilot --type pharma --target "IL-4" --status "III期临床"
156
156
  # 翻页(默认第1页)
157
157
  skillfree pilot --type pharma --target "STAT6" --page 2
158
158
 
159
- # 查询药物详情(需要先查管线获取 keyid)
160
- skillfree pilot --type pharma --model bcpm-drug-detail --keyid <keyid>
159
+ # 查询药物详情(自动查 keyid)
160
+ skillfree pilot --type pharma --model bcpm-drug-detail --drug "替雷利珠单抗"
161
+
162
+ # 查询全球临床试验结果(自动查 keyid)⭐ 新增
163
+ skillfree pilot --type pharma --model bcpm-clinical-result --drug "信迪利单抗"
161
164
 
162
165
  # 查询中国临床试验
163
166
  skillfree pilot --type pharma --model bcpm-cn-clinical --drug "度普利尤单抗"
@@ -175,18 +178,23 @@ skillfree pilot --type pharma --target "STAT6" --output result.json
175
178
  | model | 说明 | 主要参数 |
176
179
  |-------|------|---------|
177
180
  | `bcpm-drug-dev`(默认)| 全球药物研发管线 | --target / --drug / --indication / --company / --status |
178
- | `bcpm-drug-detail` | 药物详细信息(含销售额、适应症进展)| --keyid(必填)|
181
+ | `bcpm-drug-detail` | 药物详细信息(含销售额、适应症进展)| --drug(自动查 keyid)或 --keyid |
182
+ | `bcpm-clinical-result` | 全球临床试验结果 ⭐ 新增 | --drug(自动查 keyid)或 --keyid |
179
183
  | `bcpm-cn-clinical` | 中国临床试验注册 | --drug / --sponsor / --indication |
180
184
  | `bcpm-cn-evaluation` | 中国药品申报/评审 | --drug / --company |
181
185
 
182
- **典型两步工作流:**
186
+ **典型工作流(已简化):**
183
187
 
184
188
  ```bash
189
+ # 方式一:直接用药品名(推荐)⭐
190
+ skillfree pilot --type pharma --model bcpm-clinical-result --drug "信迪利单抗"
191
+
192
+ # 方式二:手动两步(如果需要精确控制)
185
193
  # 第一步:查管线,找到目标药物的 keyid
186
194
  skillfree pilot --type pharma --target "STAT6"
187
195
  # 输出中会显示每个药物的 keyid
188
196
 
189
- # 第二步:用 keyid 查详情(含全球销售额、各适应症研发现状)
197
+ # 第二步:用 keyid 查详情
190
198
  skillfree pilot --type pharma --model bcpm-drug-detail --keyid 0b0566619e92554c97b384c0e4c9ddfd
191
199
  ```
192
200
 
package/install.sh CHANGED
@@ -39,7 +39,7 @@ echo -e " ${DIM}─────────────────────
39
39
  echo ""
40
40
 
41
41
  # ── Step 1: 检查环境 ──────────────────────────────────────────────────────────
42
- step "[ 1 / 3 ] 检查环境"
42
+ step "[ 1 / 4 ] 检查环境"
43
43
 
44
44
  if ! command -v node &>/dev/null; then
45
45
  err "未检测到 Node.js,请先安装:https://nodejs.org(需要 18+)"
@@ -55,8 +55,76 @@ if ! command -v npm &>/dev/null; then
55
55
  fi
56
56
  success "npm $(npm -v)"
57
57
 
58
- # ── Step 2: 安装 CLI ──────────────────────────────────────────────────────────
59
- step "[ 2 / 3 ] 安装 SkillFree CLI"
58
+ # ── Step 2: 选择 AI 平台 ──────────────────────────────────────────────────────
59
+ step "[ 2 / 4 ] 选择你使用的 AI 平台"
60
+ echo ""
61
+ echo -e " SkillFree 将作为 Skill 安装到对应平台,${DIM}可多选,用逗号或空格分隔${NC}"
62
+ echo ""
63
+
64
+ # 自动检测已安装的平台
65
+ DETECTED=()
66
+ [ -d "$HOME/.openclaw" ] && DETECTED+=("openclaw")
67
+ [ -d "$HOME/.claude" ] && DETECTED+=("claude")
68
+ [ -d "$HOME/.codex" ] && DETECTED+=("codex")
69
+ [ -d "$HOME/.continue" ] && DETECTED+=("continue")
70
+ [ -d "$HOME/.cursor/extensions" ] && DETECTED+=("cursor")
71
+
72
+ echo -e " ${BOLD}可选平台:${NC}"
73
+ echo -e " ${CYAN} 1)${NC} OpenClaw ${DIM}→ ~/.openclaw/skills/skillfree/${NC}"
74
+ echo -e " ${CYAN} 2)${NC} Claude Code ${DIM}→ ~/.claude/skills/skillfree/${NC}"
75
+ echo -e " ${CYAN} 3)${NC} Codex CLI ${DIM}→ ~/.codex/skills/skillfree/${NC}"
76
+ echo -e " ${CYAN} 4)${NC} Continue.dev ${DIM}→ ~/.continue/skills/skillfree/${NC}"
77
+ echo -e " ${CYAN} 5)${NC} Cursor ${DIM}→ ~/.cursor/skills/skillfree/${NC}"
78
+ echo -e " ${CYAN} 0)${NC} 跳过(仅安装 CLI)"
79
+ echo ""
80
+
81
+ if [ ${#DETECTED[@]} -gt 0 ]; then
82
+ echo -e " ${GREEN}已检测到:${DETECTED[*]}${NC}"
83
+ echo ""
84
+ fi
85
+
86
+ printf " 请输入编号(如 1 或 1,2,3),直接回车选全部检测到的平台: "
87
+ read -r PLATFORM_INPUT </dev/tty
88
+
89
+ # 解析选择
90
+ SELECTED_PLATFORMS=()
91
+
92
+ if [ -z "$PLATFORM_INPUT" ]; then
93
+ # 回车 → 使用检测到的平台,没检测到则选 all
94
+ if [ ${#DETECTED[@]} -gt 0 ]; then
95
+ SELECTED_PLATFORMS=("${DETECTED[@]}")
96
+ else
97
+ SELECTED_PLATFORMS=("openclaw" "claude" "codex")
98
+ fi
99
+ elif [ "$PLATFORM_INPUT" = "0" ]; then
100
+ SELECTED_PLATFORMS=()
101
+ else
102
+ # 解析数字(支持逗号/空格分隔)
103
+ INPUT_CLEAN="${PLATFORM_INPUT//,/ }"
104
+ for num in $INPUT_CLEAN; do
105
+ case "$num" in
106
+ 1) SELECTED_PLATFORMS+=("openclaw") ;;
107
+ 2) SELECTED_PLATFORMS+=("claude") ;;
108
+ 3) SELECTED_PLATFORMS+=("codex") ;;
109
+ 4) SELECTED_PLATFORMS+=("continue") ;;
110
+ 5) SELECTED_PLATFORMS+=("cursor") ;;
111
+ esac
112
+ done
113
+ fi
114
+
115
+ if [ ${#SELECTED_PLATFORMS[@]} -gt 0 ]; then
116
+ echo ""
117
+ echo -e " ${GREEN}将安装到:${SELECTED_PLATFORMS[*]}${NC}"
118
+ else
119
+ echo ""
120
+ warn "未选择平台,仅安装 CLI 命令行工具"
121
+ fi
122
+
123
+ # 导出给 postinstall.js 使用
124
+ export SKILLFREE_PLATFORMS="${SELECTED_PLATFORMS[*]}"
125
+
126
+ # ── Step 3: 安装 CLI ──────────────────────────────────────────────────────────
127
+ step "[ 3 / 4 ] 安装 SkillFree CLI"
60
128
  echo ""
61
129
  echo -e " ${DIM}正在从 npm 安装 skillfree...${NC}"
62
130
  echo ""
@@ -91,8 +159,8 @@ if ! command -v skillfree &>/dev/null; then
91
159
  exit 0
92
160
  fi
93
161
 
94
- # ── Step 3: 登录(从 /dev/tty 读取,兼容 curl | bash)────────────────────────
95
- step "[ 3 / 3 ] 登录账号"
162
+ # ── Step 4: 登录(从 /dev/tty 读取,兼容 curl | bash)────────────────────────
163
+ step "[ 4 / 4 ] 登录账号"
96
164
  echo ""
97
165
  echo -e " 还没有账号?${CYAN}https://skillfree.tech/app${NC} 免费注册,注册即送 ¥5"
98
166
  echo -e " 注册后进入控制台 → ${BOLD}API Keys${NC} → 创建一个 Key,粘贴到下方"
@@ -120,7 +188,7 @@ while true; do
120
188
  done
121
189
 
122
190
  if [ -n "$API_KEY" ]; then
123
- SKILLFREE_API_KEY="$API_KEY" skillfree auth save "$API_KEY"
191
+ SKILLFREE_API_KEY="$API_KEY" SKILLFREE_PLATFORMS="$SKILLFREE_PLATFORMS" skillfree auth save "$API_KEY"
124
192
  fi
125
193
 
126
194
  # ── 完成 ──────────────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfree",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "🦞 一个 API,满足所有龙虾技能需求",
5
5
  "main": "bin/skillfree.js",
6
6
  "bin": {
package/postinstall.js CHANGED
@@ -2,24 +2,60 @@
2
2
  /**
3
3
  * postinstall.js
4
4
  * npm install -g skillfree 后自动:
5
- * 1. 将技能文件部署到 ~/.agents/skills/skillfree/ ~/.openclaw/skills/skillfree/
6
- * 2. 将 API Key 写入 ~/.openclaw/openclaw.json(让 OpenClaw 自动识别 skill)
5
+ * 1. 将技能文件部署到各 AI 平台的 skills/skillfree/ 目录
6
+ * 2. 将 API Key 写入 OpenClaw 配置(如果已安装)
7
+ *
8
+ * 支持的平台(通过 SKILLFREE_PLATFORMS 环境变量控制):
9
+ * openclaw → ~/.openclaw/skills/skillfree/ + ~/.agents/skills/skillfree/
10
+ * claude → ~/.claude/skills/skillfree/
11
+ * codex → ~/.codex/skills/skillfree/
12
+ * continue → ~/.continue/skills/skillfree/
13
+ * cursor → ~/.cursor/skills/skillfree/
7
14
  */
8
15
 
9
16
  const fs = require('fs')
10
17
  const path = require('path')
11
18
  const os = require('os')
12
19
 
13
- const PKG_DIR = __dirname
14
- const HOME = os.homedir()
15
- const API_KEY = process.env.SKILLFREE_API_KEY || ''
20
+ const PKG_DIR = __dirname
21
+ const HOME = os.homedir()
22
+ const API_KEY = process.env.SKILLFREE_API_KEY || ''
23
+
24
+ // ── 平台 → 技能安装路径映射 ─────────────────────────────────────────────────
25
+ const PLATFORM_PATHS = {
26
+ openclaw: [
27
+ path.join(HOME, '.openclaw', 'skills', 'skillfree'),
28
+ path.join(HOME, '.agents', 'skills', 'skillfree'),
29
+ ],
30
+ claude: [
31
+ path.join(HOME, '.claude', 'skills', 'skillfree'),
32
+ ],
33
+ codex: [
34
+ path.join(HOME, '.codex', 'skills', 'skillfree'),
35
+ ],
36
+ continue: [
37
+ path.join(HOME, '.continue', 'skills', 'skillfree'),
38
+ ],
39
+ cursor: [
40
+ path.join(HOME, '.cursor', 'skills', 'skillfree'),
41
+ ],
42
+ }
16
43
 
17
- // skill 部署目标目录(多个路径全部写入)
18
- const SKILL_TARGETS = [
19
- path.join(HOME, '.agents', 'skills', 'skillfree'),
20
- path.join(HOME, '.openclaw', 'skills', 'skillfree'),
21
- ]
44
+ // 解析目标平台
45
+ function getTargetPlatforms() {
46
+ const envVal = process.env.SKILLFREE_PLATFORMS || ''
47
+ if (!envVal.trim()) {
48
+ // 未指定时:自动检测已安装的平台
49
+ return Object.keys(PLATFORM_PATHS).filter(p => {
50
+ const baseDir = path.join(HOME, '.' + p)
51
+ return fs.existsSync(baseDir)
52
+ })
53
+ }
54
+ // 支持空格/逗号分隔
55
+ return envVal.split(/[\s,]+/).filter(p => p && PLATFORM_PATHS[p])
56
+ }
22
57
 
58
+ // ── 文件部署 ─────────────────────────────────────────────────────────────────
23
59
  const FILES = ['SKILL.md', 'package.json']
24
60
  const DIRS = ['bin', 'scripts']
25
61
 
@@ -48,33 +84,32 @@ function deploySkill(skillDir) {
48
84
  }
49
85
  fs.writeFileSync(
50
86
  path.join(skillDir, '.skillfree-installed'),
51
- JSON.stringify({ installedAt: new Date().toISOString(), version: require('./package.json').version }, null, 2) + '\n'
87
+ JSON.stringify({
88
+ installedAt: new Date().toISOString(),
89
+ version: require('./package.json').version,
90
+ }, null, 2) + '\n'
52
91
  )
53
92
  }
54
93
 
94
+ // ── OpenClaw 特有:写入 openclaw.json ────────────────────────────────────────
55
95
  function injectOpenclawConfig(apiKey) {
56
- // 只在有 openclaw 目录时注入
57
96
  const openclawDir = path.join(HOME, '.openclaw')
58
97
  if (!fs.existsSync(openclawDir)) return
59
98
 
60
99
  const cfgPath = path.join(openclawDir, 'openclaw.json')
61
-
62
- // ⚠️ 关键:文件不存在时直接 return,绝不用空对象覆盖
63
100
  if (!fs.existsSync(cfgPath)) return
64
101
 
65
102
  let cfg = {}
66
103
  try {
67
104
  cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'))
68
105
  } catch (e) {
69
- // JSON 解析失败(文件损坏等)→ 同样放弃写入,不覆盖
70
- console.warn('⚠️ openclaw.json 解析失败,跳过自动配置(文件可能损坏)')
106
+ console.warn('⚠️ openclaw.json 解析失败,跳过自动配置')
71
107
  return
72
108
  }
73
109
 
74
110
  cfg.skills = cfg.skills || {}
75
111
  cfg.skills.entries = cfg.skills.entries || {}
76
112
 
77
- // 只写 enabled,不覆盖用户已有的 apiKey
78
113
  const existing = cfg.skills.entries['skillfree'] || {}
79
114
  cfg.skills.entries['skillfree'] = {
80
115
  ...existing,
@@ -84,23 +119,46 @@ function injectOpenclawConfig(apiKey) {
84
119
 
85
120
  try {
86
121
  fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n')
87
- console.log('✅ openclaw.json 已更新,skill 下次启动自动生效')
122
+ console.log(' ✅ openclaw.json 已更新,skill 下次启动自动生效')
88
123
  } catch (e) {
89
124
  // 权限问题静默跳过
90
125
  }
91
126
  }
92
127
 
128
+ // ── 主流程 ───────────────────────────────────────────────────────────────────
93
129
  try {
94
- // 1. 部署 skill 到所有目标目录
95
- for (const target of SKILL_TARGETS) {
96
- deploySkill(target)
130
+ const platforms = getTargetPlatforms()
131
+
132
+ if (platforms.length === 0) {
133
+ // 没有检测到任何平台,保底装到 ~/.agents/skills/
134
+ const fallback = path.join(HOME, '.agents', 'skills', 'skillfree')
135
+ deploySkill(fallback)
136
+ console.log(`✅ SkillFree skill 已安装到 ${fallback}`)
137
+ return
138
+ }
139
+
140
+ const installed = []
141
+
142
+ for (const platform of platforms) {
143
+ const targets = PLATFORM_PATHS[platform] || []
144
+ for (const skillDir of targets) {
145
+ deploySkill(skillDir)
146
+ installed.push({ platform, skillDir })
147
+ }
148
+
149
+ // OpenClaw 额外注入 openclaw.json
150
+ if (platform === 'openclaw') {
151
+ injectOpenclawConfig(API_KEY)
152
+ }
97
153
  }
98
- console.log('✅ SkillFree skill 已安装到:')
99
- SKILL_TARGETS.forEach(t => console.log(` ${t}`))
100
154
 
101
- // 2. 注入 openclaw.json(让 skill 自动被 OpenClaw 识别)
102
- injectOpenclawConfig(API_KEY)
155
+ if (installed.length > 0) {
156
+ console.log('✅ SkillFree skill 已安装到:')
157
+ for (const { platform, skillDir } of installed) {
158
+ console.log(` [${platform}] ${skillDir}`)
159
+ }
160
+ }
103
161
 
104
162
  } catch (e) {
105
- // 静默失败,不影响正常安装
163
+ // 静默失败,不影响正常 npm 安装
106
164
  }
@@ -4,15 +4,34 @@ const path = require('path')
4
4
  const os = require('os')
5
5
  const { saveConfig, BASE_URL } = require('../lib/client')
6
6
 
7
+ // ── 平台 → 技能安装路径映射(与 postinstall.js 保持一致)─────────────────
8
+ const PLATFORM_PATHS = {
9
+ openclaw: [
10
+ path.join(os.homedir(), '.openclaw', 'skills', 'skillfree'),
11
+ path.join(os.homedir(), '.agents', 'skills', 'skillfree'),
12
+ ],
13
+ claude: [
14
+ path.join(os.homedir(), '.claude', 'skills', 'skillfree'),
15
+ ],
16
+ codex: [
17
+ path.join(os.homedir(), '.codex', 'skills', 'skillfree'),
18
+ ],
19
+ continue: [
20
+ path.join(os.homedir(), '.continue', 'skills', 'skillfree'),
21
+ ],
22
+ cursor: [
23
+ path.join(os.homedir(), '.cursor', 'skills', 'skillfree'),
24
+ ],
25
+ }
26
+
7
27
  /**
8
- * 将 apiKey 写入 ~/.openclaw/openclaw.json skills.entries.skillfree
9
- * 同时设置当前进程的 SKILLFREE_API_KEY 环境变量
28
+ * 将 apiKey 写入各平台配置 + shell 环境变量
10
29
  */
11
30
  function injectToOpenclaw(apiKey) {
12
- // 1. 写入 openclaw.json
31
+ // 1. OpenClaw: 写入 openclaw.json
13
32
  const openclawDir = path.join(os.homedir(), '.openclaw')
14
33
  const cfgPath = path.join(openclawDir, 'openclaw.json')
15
- if (fs.existsSync(openclawDir)) {
34
+ if (fs.existsSync(openclawDir) && fs.existsSync(cfgPath)) {
16
35
  let cfg = {}
17
36
  try { cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')) } catch {}
18
37
  cfg.skills = cfg.skills || {}
@@ -41,7 +60,6 @@ function injectToOpenclaw(apiKey) {
41
60
  if (fs.existsSync(f)) {
42
61
  const content = fs.readFileSync(f, 'utf8')
43
62
  if (content.includes('SKILLFREE_API_KEY')) {
44
- // 替换已有行
45
63
  const updated = content.replace(/\nexport SKILLFREE_API_KEY=.*\n?/g, exportLine)
46
64
  fs.writeFileSync(f, updated)
47
65
  } else {
@@ -53,7 +71,6 @@ function injectToOpenclaw(apiKey) {
53
71
  }
54
72
  }
55
73
  if (!wrote) {
56
- // 创建 .zshrc
57
74
  fs.writeFileSync(shellFiles[0], `# SkillFree API Key${exportLine}`)
58
75
  console.log('✅ ~/.zshrc 已创建并写入 SKILLFREE_API_KEY')
59
76
  }
@@ -224,9 +224,38 @@ async function pilot(flags) {
224
224
  if (flags.title) search.title = flags.title
225
225
  if (flags.sponsor) search.sponsor = flags.sponsor
226
226
 
227
- const inputs = pharmaModel === 'bcpm-drug-detail'
228
- ? { keyid: flags.keyid ?? prompt } // detail 模式需要 keyid
229
- : { search, page: Number(flags.page ?? 1) }
227
+ let inputs
228
+ let keyid = flags.keyid ?? prompt
229
+
230
+ // bcpm-clinical-result / bcpm-drug-detail 特殊处理:如果没有 keyid 但有 drug,自动查询获取 keyid
231
+ if ((pharmaModel === 'bcpm-clinical-result' || pharmaModel === 'bcpm-drug-detail') && !keyid && search.drug_name) {
232
+ console.log(`🔍 自动查询 "${search.drug_name}" 的 keyid...`)
233
+ const drugRes = await post('/pharma', {
234
+ model: 'bcpm-drug-dev',
235
+ inputs: { search: { drug_name: search.drug_name }, page: 1 }
236
+ })
237
+ if (drugRes.error) {
238
+ console.error(`❌ 查询药物失败: ${drugRes.error.message}`)
239
+ return
240
+ }
241
+ const list = drugRes.data?.list || []
242
+ if (list.length === 0) {
243
+ console.error(`❌ 未找到药物: ${search.drug_name}`)
244
+ return
245
+ }
246
+ keyid = list[0].keyid
247
+ console.log(`✅ 找到药物: ${list[0].drug_name_cn || list[0].drug_name} (keyid: ${keyid})\n`)
248
+ }
249
+
250
+ if (pharmaModel === 'bcpm-drug-detail' || pharmaModel === 'bcpm-clinical-result') {
251
+ if (!keyid) {
252
+ console.error(`❌ ${pharmaModel} 需要 --keyid 或 --drug 参数`)
253
+ return
254
+ }
255
+ inputs = { keyid, page: Number(flags.page ?? 1) }
256
+ } else {
257
+ inputs = { search, page: Number(flags.page ?? 1) }
258
+ }
230
259
 
231
260
  const res = await post('/pharma', { model: pharmaModel, inputs })
232
261
 
@@ -267,6 +296,20 @@ async function pilot(flags) {
267
296
  console.log(` • ${s.year} ${s.company}: ${s.sales}`)
268
297
  })
269
298
  }
299
+ } else if (pharmaModel === 'bcpm-clinical-result') {
300
+ // 临床试验结果
301
+ const list = data.list || []
302
+ const total = data.total || list.length
303
+ console.log(`共 ${total} 条结果,当前显示 ${list.length} 条\n`)
304
+ list.slice(0, 10).forEach((item, i) => {
305
+ console.log(`${i + 1}. [${item.registration_number}] ${item.title}`)
306
+ console.log(` 适应症: ${(item.indication || []).join(', ')}`)
307
+ console.log(` 分期: ${item.phase}`)
308
+ if (item.representative_data) console.log(` 代表性数据: ${item.representative_data}`)
309
+ if (item.result_tendency) console.log(` 结果趋势: ${item.result_tendency}`)
310
+ if (item.abstract?.length) console.log(` 摘要: ${item.abstract[0].slice(0, 150)}...`)
311
+ console.log('')
312
+ })
270
313
  } else {
271
314
  const list = data.list || []
272
315
  const total = data.total || list.length