@wwkit/opm 1.0.18 → 1.0.20

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.md CHANGED
@@ -69,6 +69,9 @@ pnpm opm <command> [args] [options]
69
69
  | `ping <host|proxy|registry>` | 验证主机、代理或所有 registry 的可访问性 |
70
70
  | `env <action>` | 环境变量管理(list/get/set,跨平台:Unix profile / Windows user 级注册表) |
71
71
  | `config [section]` | 查看常用配置(proxy + 各 registry 段,段名动态生成) |
72
+ | `share <action>` | 文本分享服务(upload/zip/get/view/open/list/delete/clear/server) |
73
+ | `copy` | 系统剪切板 → 服务器(id=clipboard;剪切板为空时提示停止) |
74
+ | `paste` | 服务器 clipboard → 系统剪切板并显示(`opm share get clipboard` 的快捷) |
72
75
  | `opencode <action>` | opencode 运维命令(安装/升级/清理/env);裸 `opm opencode` 启动 TUI |
73
76
  | `harness <action>` | @wwkit/harness 插件管理(安装/升级/list/status/env/dir) |
74
77
 
@@ -94,16 +97,16 @@ pnpm opm <command> [args] [options]
94
97
 
95
98
  | Action | 说明 |
96
99
  |--------|------|
97
- | `version [name]` | 无 name 查看管理器本身版本;有 name 显示包在全局与项目作用域的版本(仅包管理器) |
100
+ | `version [name]` | 无 name 查看管理器本身版本;有 name 显示包在全局与项目作用域的版本号(仅包管理器;完整安装状态见 `view`) |
98
101
  | `registry` | 查询当前镜像地址 |
99
102
  | `registry set <url\|preset>` | 设置镜像地址(支持预设名) |
100
103
  | `registry presets` | 列出可用镜像预设 |
101
- | `search <name> [version]` | 查询包在源是否可用 |
102
- | `view <name>` | 查询包在全局与项目作用域的安装信息(仅包管理器;运行时查询版本是否已安装) |
104
+ | `search <name>[@<version>]` | 查询包在源是否可用 |
105
+ | `view <name>` | 查询包是否已安装,已安装显示基本信息(包管理器:全局+项目双作用域;运行时:指定版本是否已安装) |
103
106
  | `info <name>` | 查询包详情 |
104
107
  | `versions <name>` | 列出所有可用版本 |
105
- | `list` | 列出所有已安装包(仅运行时 node/php/python 与工具组 nvm/phpenv/uv 支持) |
106
- | `install <name> [version]` | 安装包 |
108
+ | `list [-g]` | 列出已安装包(包管理器:项目作用域,`-g` 全局;运行时/工具组:已安装版本,数组输出) |
109
+ | `install <name>[@<version>]` | 安装包(版本用 `@` 连接,业界惯例) |
107
110
  | `uninstall <name>` | 卸载包 |
108
111
  | `upgrade <name>` | 升级包 |
109
112
  | `cache clean` | 清理缓存 |
@@ -122,15 +125,15 @@ pnpm opm <command> [args] [options]
122
125
  | `opencode env` | 显示环境变量(OPENCODE_CONFIG_DIR / CONTENT) |
123
126
  | `opencode [-d <dir>]` | 启动 TUI(默认 cwd),自动注入环境变量;可透传 args 给 opencode |
124
127
  | `opencode clear` | 清理过期会话并 VACUUM 压缩数据库(缺省 `-d 1`,只保留今天) |
125
- | `opencode clear -d, --days <n>` | 保留最近 n 天(对齐本地零点,含今天) |
126
- | `opencode clear -h, --hours <n>` | 保留最近 n 小时(滚动窗口) |
128
+ | `opencode clear -d, --days <n>` | 保留最近 n 天(整数对齐本地零点含今天;小数 = 小时窗口,`0.5` = 12 小时) |
127
129
  | `opencode clear -n, --num <n>` | 保留最新 n 个会话 |
128
130
  | `opencode clear --dry-run` | 仅预览,不修改任何数据 |
129
131
  | `opencode clear -y` | 跳过确认提示 |
130
132
  | `opencode clear --backup` | 删除前备份数据库 |
131
133
  | `opencode clear --db <path>` | 覆盖 opencode 数据库路径 |
134
+ | `opencode clear -h` | 显示帮助(`-h` 不再表示 hours) |
132
135
 
133
- 保留策略参数(`--days` / `--hours` / `--num`)三选一,互斥。
136
+ 保留策略参数(`--days` / `--num`)二选一,互斥。
134
137
 
135
138
  ## 选项
136
139
 
@@ -151,12 +154,12 @@ opm npm registry # 查看配置中的激活 registry
151
154
  opm npm registry set taobao # 设置激活镜像为 taobao(写入 opm 配置)
152
155
  opm npm registry presets # 列出可用镜像预设
153
156
  opm npm search axios # 查询 axios 是否可用
154
- opm npm search axios 1.7.9 # 查询指定版本
157
+ opm npm search axios@1.7.9 # 查询指定版本
155
158
  opm npm view axios # 查询全局与项目作用域安装信息
156
159
  opm npm info axios # 包详情
157
160
  opm npm versions axios # 所有版本
158
161
  opm npm install axios # 安装
159
- opm npm install axios 1.7.9 # 安装指定版本
162
+ opm npm install axios@1.7.9 # 安装指定版本
160
163
  opm npm install -g typescript # 全局安装
161
164
  opm npm uninstall axios # 卸载
162
165
  opm npm upgrade axios # 升级
@@ -173,7 +176,7 @@ opm pip view requests # 查询全局与项目作用域安装
173
176
  opm pip info requests # 包详情
174
177
  opm pip versions requests # 所有版本
175
178
  opm pip install requests # 安装
176
- opm pip install requests 2.31.0 # 安装指定版本
179
+ opm pip install requests@2.31.0 # 安装指定版本
177
180
  opm pip uninstall requests # 卸载
178
181
  opm pip upgrade requests # 升级
179
182
  opm pip cache clean # 清理缓存
@@ -191,7 +194,7 @@ opm dnf view vim-enhanced # 查询全局作用域安装信息(
191
194
  opm dnf info vim-enhanced # 包详情
192
195
  opm dnf versions vim-enhanced # 所有可用版本
193
196
  opm dnf install htop # 安装
194
- opm dnf install vim-enhanced 9.0 # 安装指定版本
197
+ opm dnf install vim-enhanced@9.0 # 安装指定版本
195
198
  opm dnf uninstall htop # 卸载
196
199
  opm dnf upgrade htop # 升级
197
200
  opm dnf cache clean # 清理缓存
@@ -209,7 +212,7 @@ opm apt view curl # 查询全局作用域安装信息(
209
212
  opm apt info curl # 包详情
210
213
  opm apt versions curl # 所有可用版本
211
214
  opm apt install htop # 安装
212
- opm apt install curl 7.68.0-1ubuntu2.7 # 安装指定版本
215
+ opm apt install curl@7.68.0-1ubuntu2.7 # 安装指定版本
213
216
  opm apt uninstall htop # 卸载
214
217
  opm apt upgrade htop # 升级
215
218
  opm apt cache clean # 清理缓存
@@ -227,7 +230,7 @@ opm brew view wget # 查询全局作用域安装信息(无
227
230
  opm brew info wget # 包详情
228
231
  opm brew versions wget # 所有可用版本
229
232
  opm brew install wget # 安装
230
- opm brew install python 3.12 # 安装指定版本(versioned formula)
233
+ opm brew install python@3.12 # 安装指定版本(versioned formula)
231
234
  opm brew uninstall wget # 卸载
232
235
  opm brew upgrade wget # 升级
233
236
  opm brew cache clean # 清理缓存
@@ -241,12 +244,12 @@ opm composer registry # 查看配置中的激活镜像
241
244
  opm composer registry set aliyun # 设置为阿里云镜像
242
245
  opm composer registry presets # 列出可用镜像预设
243
246
  opm composer search monolog/monolog # 查询包是否可用
244
- opm composer search monolog/monolog 3.9.0 # 查询指定版本
247
+ opm composer search monolog/monolog@3.9.0 # 查询指定版本
245
248
  opm composer view monolog/monolog # 查询项目作用域安装信息(无全局作用域)
246
249
  opm composer info monolog/monolog # 包详情
247
250
  opm composer versions monolog/monolog # 所有可用版本
248
251
  opm composer install monolog/monolog # 安装(composer require)
249
- opm composer install monolog/monolog ^3.0 # 安装指定版本约束
252
+ opm composer install monolog/monolog@^3.0 # 安装指定版本约束
250
253
  opm composer uninstall monolog/monolog # 卸载(composer remove)
251
254
  opm composer upgrade monolog/monolog # 升级(composer update)
252
255
  opm composer cache clean # 清理缓存
@@ -531,9 +534,9 @@ opm ping registry -d # 输出完整 JSON 数组
531
534
  ```bash
532
535
  opm env list # 列出 opm 管理的环境变量(含配置文件路径)
533
536
  opm env list --all # 追加 process.env 全量,每条标注 managed: true/false
534
- opm env get PATH # 根据 key 取值(先查 opm 管理源,回退 process.env)
535
- opm env set FOO bar # 设置环境变量(替换已存在的)
536
- opm env set MY_API_KEY secret123 # 设置 API 密钥
537
+ opm env get -k PATH # 根据 key 取值(先查 opm 管理源,回退 process.env)
538
+ opm env set -k FOO -v bar # 设置环境变量(替换已存在的;仅命名参数)
539
+ opm env set -k MY_API_KEY -v secret123 # 设置 API 密钥
537
540
  opm env search api # 按关键词匹配 key 或 value(忽略大小写)
538
541
  opm env search api --all # 同时搜索 process.env
539
542
  opm env help # 通用介绍
@@ -544,10 +547,10 @@ opm env help # 通用介绍
544
547
  ```bash
545
548
  opm env list # 简化文本(按来源分组)
546
549
  opm env list --json # JSON 数组
547
- opm env get PATH # 简化:PATH = /usr/bin:/bin
548
- opm env get PATH --json # JSON 对象(含 source/scope)
549
- opm env set FOO bar # 简化:FOO = bar
550
- opm env set FOO bar --json # JSON 对象(含 written/source)
550
+ opm env get -k PATH # 简化:PATH = /usr/bin:/bin
551
+ opm env get -k PATH --json # JSON 对象(含 source/scope)
552
+ opm env set -k FOO -v bar # 简化:FOO = bar
553
+ opm env set -k FOO -v bar --json # JSON 对象(含 written/source)
551
554
  opm env search api # 简化:匹配项按来源分组
552
555
  opm env search api --json # JSON 数组
553
556
  ```
package/bin/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import fs from 'node:fs'
4
4
  import path from 'node:path'
5
+ import { getUserConfigFile, mergeBuiltinConfig } from '../src/config.js'
5
6
 
6
7
  let dir = process.cwd()
7
8
  for (let i = 0; i < 10; i++) {
@@ -23,6 +24,13 @@ for (let i = 0; i < 10; i++) {
23
24
  dir = parent
24
25
  }
25
26
 
27
+ // Lazy-init: if user config doesn't exist (postinstall skipped), create it now
28
+ if (!fs.existsSync(getUserConfigFile())) {
29
+ if (mergeBuiltinConfig()) {
30
+ console.log(`opm: config initialized at ${getUserConfigFile()}`)
31
+ }
32
+ }
33
+
26
34
  import { CLI } from '../src/cli/index.js'
27
35
 
28
36
  const cli = new CLI()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wwkit/opm",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "author": "bluesliu <langcai163@163.com>",
5
5
  "description": "Unified CLI — package management (npm/pip/dnf/apt) + config view + opencode maintenance",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "basic-ftp": "^6.2.1",
36
36
  "extract-zip": "^2.0.1",
37
37
  "systeminformation": "^5.23.0",
38
- "@wwkit/shared": "1.0.23"
38
+ "@wwkit/shared": "1.0.25"
39
39
  },
40
40
  "devDependencies": {
41
41
  "jest": "^29.7.0"
@@ -277,16 +277,11 @@ async _dir() {
277
277
  async _list() {
278
278
  const list = this._listInstalled()
279
279
  const active = this._readActive()
280
- output({
281
- name: this.name,
282
- count: list.length,
283
- active: active || undefined,
284
- versions: list.map(({ version }) => ({
285
- version,
286
- current: version === active,
287
- install: this._versionRoot(version),
288
- })),
289
- })
280
+ output(list.map(({ version }) => ({
281
+ version,
282
+ current: version === active,
283
+ install: this._versionRoot(version),
284
+ })))
290
285
  }
291
286
 
292
287
  async _install(rest) {
@@ -3,8 +3,9 @@
3
3
  *
4
4
  * 用法:
5
5
  * opm env list [--all] 列出 opm 管理的环境变量(含配置文件路径);--all 追加 process.env 全量
6
- * opm env get <key> 根据 key 取值(先查 opm 管理的配置源,回退 process.env)
7
- * opm env set <key> <value> 设置值(替换已存在的)
6
+ * opm env get -k <key> 根据 key 取值(先查 opm 管理的配置源,回退 process.env)
7
+ * opm env set -k <key> -v <value> 设置值(替换已存在的;仅命名参数,不用位置参数)
8
+ * opm env search <keyword> 按关键词搜索(key 或 value 包含,忽略大小写)
8
9
  * opm env help 通用介绍
9
10
  *
10
11
  * 跨平台策略(复用 @wwkit/shared 的 Shell):
@@ -320,9 +321,9 @@ export class EnvGroup {
320
321
  * @param {{ flags: object, positional: string[] }} parsed
321
322
  */
322
323
  async _get(parsed) {
323
- const key = parsed.positional[0] || parsed.flags.k || parsed.flags.key || ''
324
+ const key = parsed.flags.k || parsed.flags.key || ''
324
325
  if (!key) {
325
- console.error('Usage: opm env get <key>')
326
+ console.error('Usage: opm env get -k <key>')
326
327
  console.error('Missing required <key>')
327
328
  process.exit(1)
328
329
  }
@@ -349,11 +350,11 @@ export class EnvGroup {
349
350
  * @param {{ flags: object, positional: string[] }} parsed
350
351
  */
351
352
  async _set(parsed) {
352
- const key = parsed.positional[0] || parsed.flags.k || parsed.flags.key || ''
353
- const value = parsed.positional[1] ?? parsed.flags.v ?? parsed.flags.value ?? ''
353
+ const key = parsed.flags.k || parsed.flags.key || ''
354
+ const value = parsed.flags.v ?? parsed.flags.value ?? ''
354
355
 
355
356
  if (!key) {
356
- console.error('Usage: opm env set <key> <value>')
357
+ console.error('Usage: opm env set -k <key> -v <value>')
357
358
  console.error('Missing required <key>')
358
359
  process.exit(1)
359
360
  }
@@ -361,8 +362,8 @@ export class EnvGroup {
361
362
  console.error(`Invalid env key: "${key}" (must start with letter/underscore, only letters/digits/underscore)`)
362
363
  process.exit(1)
363
364
  }
364
- if (parsed.positional.length < 2 && !(parsed.flags.v || parsed.flags.value)) {
365
- console.error('Usage: opm env set <key> <value>')
365
+ if (!(parsed.flags.v || parsed.flags.value)) {
366
+ console.error('Usage: opm env set -k <key> -v <value>')
366
367
  console.error('Missing required <value>')
367
368
  process.exit(1)
368
369
  }
@@ -411,8 +412,8 @@ Actions:
411
412
  list [--all] List opm-managed env vars with their config file path
412
413
  (default: only vars set via "opm env set";
413
414
  --all appends all process.env vars, each tagged managed: true/false)
414
- get <key> Get value by key (checks opm-managed sources first, then process.env)
415
- set <key> <value> Set value, replacing any existing one
415
+ get -k <key> Get value by key (checks opm-managed sources first, then process.env)
416
+ set -k <key> -v <value> Set value, replacing any existing one
416
417
  (Unix: shell profile marker block; Windows: setx user-level registry)
417
418
  search <keyword> [--all] Search env vars whose key or value contains keyword (case-insensitive)
418
419
  (default: opm-managed vars; --all also searches process.env)
@@ -440,9 +441,9 @@ Platform behavior:
440
441
  Examples:
441
442
  opm env list
442
443
  opm env list --all # all process.env vars, tagged managed
443
- opm env get PATH
444
- opm env set FOO bar
445
- opm env set MY_API_KEY secret123
444
+ opm env get -k PATH
445
+ opm env set -k FOO -v bar
446
+ opm env set -k MY_API_KEY -v secret123
446
447
  opm env search api # match key or value containing "api" (case-insensitive)
447
448
  opm env search api --all # also search process.env
448
449
  opm env help
@@ -44,7 +44,7 @@ export class NvmGroup extends ToolManagerGroup {
44
44
  throw new Error(`${nvmDir} is not a git repository (nvm was not installed via git clone). Run \`opm nvm uninstall -y\` then \`opm nvm install\` to reinstall.`)
45
45
  }
46
46
  execSync(`cd "${nvmDir}" && git remote set-url origin "${getActiveRegistry('nvm')}" && git pull --tags`, {
47
- stdio: 'inherit',
47
+ stdio: shell.stdioForExec(true),
48
48
  shell: shell.resolveBash(),
49
49
  })
50
50
  return { action: 'upgraded' }
@@ -38,7 +38,7 @@ export class OverlayGroup {
38
38
 
39
39
  const dryRun = parsed.flags['dry-run'] === 'true'
40
40
 
41
- const from = path.resolve(parsed.flags.f || parsed.flags.from || process.cwd())
41
+ const from = path.resolve(parsed.flags.from || process.cwd())
42
42
  if (!fs.existsSync(from) || !fs.statSync(from).isDirectory()) {
43
43
  output({ error: 'Source directory does not exist or is not a directory', from })
44
44
  process.exit(1)
@@ -56,7 +56,7 @@ export class OverlayGroup {
56
56
  process.exit(1)
57
57
  }
58
58
 
59
- const to = parsed.flags.t || parsed.flags.to
59
+ const to = parsed.flags.to
60
60
  let globalRoot
61
61
  if (to) {
62
62
  globalRoot = path.resolve(to)
@@ -154,16 +154,16 @@ overwriting existing files. Useful for testing local changes without
154
154
  publishing.
155
155
 
156
156
  Options:
157
- -f, --from <dir> Source package directory (default: current directory)
158
- -t, --to <dir> Target node_modules directory (default: npm root -g)
157
+ --from <dir> Source package directory (default: current directory)
158
+ --to <dir> Target node_modules directory (default: npm root -g)
159
159
  --dry-run Preview files to copy without writing
160
160
  -h, --help Show this help
161
161
 
162
162
  Examples:
163
163
  opm overlay Overlay cwd package to global install
164
- opm overlay -f /path/to/package Overlay specific package
165
- opm overlay -t /custom/node_modules Overlay to custom target
166
- opm overlay --dry-run Preview what would be copied
164
+ opm overlay --from /path/to/package Overlay specific package
165
+ opm overlay --to /custom/node_modules Overlay to custom target
166
+ opm overlay --dry-run Preview what would be copied
167
167
  `)
168
168
  }
169
169
  }
@@ -35,6 +35,27 @@ const MANAGERS = {
35
35
  python: () => new PythonManager(),
36
36
  }
37
37
 
38
+ /**
39
+ * 解析业界惯例的 `<name>[@<version>]` 输入
40
+ * 统一对外格式:版本跟随包名用 @ 连接(npm/pip 等业界惯例)。
41
+ * 兼容 scoped 包(@babel/core@7.0.0 → name=@babel/core, version=7.0.0)。
42
+ * 若输入无 @(或 @ 在开头即 scoped 前缀),返回 name 与 fallbackVersion。
43
+ * @param {string|undefined} input
44
+ * @param {string} [fallbackVersion] - 旧式第二个位置参数(<name> <version>)兼容
45
+ * @returns {{ name: string, version: string }}
46
+ */
47
+ export function parseNameVersion(input, fallbackVersion = '') {
48
+ if (!input) return { name: input || '', version: fallbackVersion || '' }
49
+ const idx = input.lastIndexOf('@')
50
+ if (idx > 0) {
51
+ const version = input.slice(idx + 1)
52
+ if (version) {
53
+ return { name: input.slice(0, idx), version }
54
+ }
55
+ }
56
+ return { name: input, version: fallbackVersion || '' }
57
+ }
58
+
38
59
  const MANAGER_DESC = {
39
60
  npm: 'npm package manager',
40
61
  pip: 'pip package manager',
@@ -54,9 +75,10 @@ const ACTIONS = {
54
75
  installed: { desc: 'Check if the manager itself is installed' },
55
76
  registry: { desc: 'Query or set registry/mirror address' },
56
77
  search: { desc: 'Check if a package is available in the source' },
57
- view: { desc: 'Check package in global and project scopes' },
78
+ view: { desc: 'Check if a package is installed (global + project scopes)' },
58
79
  info: { desc: 'Show package details' },
59
80
  versions: { desc: 'List all available versions of a package' },
81
+ list: { desc: 'List installed packages (default: project scope, -g for global)' },
60
82
  install: { desc: 'Install a package' },
61
83
  uninstall: { desc: 'Uninstall a package' },
62
84
  upgrade: { desc: 'Upgrade a package' },
@@ -126,11 +148,13 @@ export class PackageGroup {
126
148
  case 'version': {
127
149
  const name = positional[0]
128
150
  if (name) {
151
+ // version 名副其实:只输出版本号(空串 = 该作用域未安装);
152
+ // 完整安装状态见 view
129
153
  const result = await manager.scopedInstalled(name, { proxy: opts.proxy })
130
154
  output({
131
155
  name: result.name,
132
- global: { installed: result.global.installed, ...(result.global.version ? { version: result.global.version } : {}) },
133
- project: { installed: result.project.installed, ...(result.project.version ? { version: result.project.version } : {}) },
156
+ global: { version: result.global.installed ? (result.global.version || '') : '' },
157
+ project: { version: result.project.installed ? (result.project.version || '') : '' },
134
158
  })
135
159
  } else {
136
160
  const result = await manager.getVersion()
@@ -166,10 +190,9 @@ export class PackageGroup {
166
190
  }
167
191
 
168
192
  case 'search': {
169
- const name = positional[0]
170
- const version = positional[1]
193
+ const { name, version } = parseNameVersion(positional[0], positional[1])
171
194
  if (!name) {
172
- console.error(`Usage: opm ${this.name} search <name> [version]`)
195
+ console.error(`Usage: opm ${this.name} search <name>[@<version>]`)
173
196
  process.exit(1)
174
197
  }
175
198
  await this._printEndpoint(manager, opts.proxy)
@@ -179,7 +202,7 @@ export class PackageGroup {
179
202
  }
180
203
 
181
204
  case 'view': {
182
- const name = positional[0]
205
+ const { name } = parseNameVersion(positional[0])
183
206
  if (!name) {
184
207
  console.error(`Usage: opm ${this.name} view <name>`)
185
208
  process.exit(1)
@@ -190,7 +213,7 @@ export class PackageGroup {
190
213
  }
191
214
 
192
215
  case 'info': {
193
- const name = positional[0]
216
+ const { name } = parseNameVersion(positional[0])
194
217
  if (!name) {
195
218
  console.error(`Usage: opm ${this.name} info <name>`)
196
219
  process.exit(1)
@@ -202,7 +225,7 @@ export class PackageGroup {
202
225
  }
203
226
 
204
227
  case 'versions': {
205
- const name = positional[0]
228
+ const { name } = parseNameVersion(positional[0])
206
229
  if (!name) {
207
230
  console.error(`Usage: opm ${this.name} versions <name>`)
208
231
  process.exit(1)
@@ -213,11 +236,16 @@ export class PackageGroup {
213
236
  break
214
237
  }
215
238
 
239
+ case 'list': {
240
+ const result = await manager.listInstalled({ global: !!opts.global })
241
+ output(result)
242
+ break
243
+ }
244
+
216
245
  case 'install': {
217
- const name = positional[0]
218
- const version = positional[1]
246
+ const { name, version } = parseNameVersion(positional[0], positional[1])
219
247
  if (!name) {
220
- console.error(`Usage: opm ${this.name} install <name> [version]`)
248
+ console.error(`Usage: opm ${this.name} install <name>[@<version>]`)
221
249
  process.exit(1)
222
250
  }
223
251
  await this._printEndpoint(manager, opts.proxy)
@@ -227,7 +255,7 @@ export class PackageGroup {
227
255
  }
228
256
 
229
257
  case 'uninstall': {
230
- const name = positional[0]
258
+ const { name } = parseNameVersion(positional[0])
231
259
  if (!name) {
232
260
  console.error(`Usage: opm ${this.name} uninstall <name>`)
233
261
  process.exit(1)
@@ -238,7 +266,7 @@ export class PackageGroup {
238
266
  }
239
267
 
240
268
  case 'upgrade': {
241
- const name = positional[0]
269
+ const { name } = parseNameVersion(positional[0])
242
270
  if (!name) {
243
271
  console.error(`Usage: opm ${this.name} upgrade <name>`)
244
272
  process.exit(1)
@@ -281,11 +309,12 @@ Actions:
281
309
  registry Show active registry (from opm config)
282
310
  registry set <url|preset> Set active registry (writes opm config)
283
311
  registry presets List available mirror presets
284
- search <name> [version] Check package availability in source
285
- view <name> Show package in global and project scopes
312
+ search <name>[@<version>] Check package availability in source
313
+ view <name> Check if a package is installed (global + project scopes)
286
314
  info <name> Show package details
287
315
  versions <name> List all available versions
288
- install <name> [version] Install a package
316
+ list [-g] List installed packages (default: project scope, -g for global)
317
+ install <name>[@<version>] Install a package
289
318
  uninstall <name> Uninstall a package
290
319
  upgrade <name> Upgrade a package to latest
291
320
  cache clean Clean cache
@@ -301,11 +330,12 @@ Options:
301
330
  Examples:
302
331
  opm ${this.name} registry
303
332
  opm ${this.name} registry set <preset|url>
304
- opm ${this.name} search <name>
333
+ opm ${this.name} search <name>[@<version>]
305
334
  opm ${this.name} view <name>
306
335
  opm ${this.name} info <name>
307
336
  opm ${this.name} versions <name>
308
- opm ${this.name} install <name>
337
+ opm ${this.name} list
338
+ opm ${this.name} install <name>[@<version>]
309
339
  opm ${this.name} uninstall <name>
310
340
  opm ${this.name} upgrade <name>
311
341
  opm ${this.name} cache clean
@@ -381,7 +411,7 @@ export class RuntimeGroup extends PackageGroup {
381
411
  return
382
412
  }
383
413
 
384
- const runtimeActions = new Set(['use', 'current', 'default', 'installed', 'dir', 'install', 'upgrade', 'search', 'versions', 'version', 'view', 'list'])
414
+ const runtimeActions = new Set(['use', 'current', 'default', 'installed', 'dir', 'install', 'upgrade', 'search', 'versions', 'version', 'view', 'list', 'info', 'cache'])
385
415
  if (runtimeActions.has(action)) {
386
416
  const manager = MANAGERS[this.name]()
387
417
  const parsed = parseFlags(rest)
@@ -510,6 +540,16 @@ export class RuntimeGroup extends PackageGroup {
510
540
  break
511
541
  }
512
542
 
543
+ case 'info': {
544
+ const result = await manager.packageInfo(this.name, { proxy: opts.proxy })
545
+ let current = ''
546
+ try {
547
+ current = (await manager.currentVersion()).current || ''
548
+ } catch {}
549
+ output({ name: this.name, ...result, current })
550
+ break
551
+ }
552
+
513
553
  default:
514
554
  return super._dispatch(manager, action, parsed, opts)
515
555
  }
@@ -541,6 +581,8 @@ Actions:
541
581
  use <version> Switch to a ${label} version (${useHint})
542
582
  current Show current active ${label} version (${currentHint})
543
583
  default <version> Set default ${label} version (${defaultHint})
584
+ info Show ${label} metadata and current version
585
+ cache clean Clean ${label} cache (where the underlying tool supports it)
544
586
  dir Show ${label} install directory
545
587
  help Show this help
546
588
 
@@ -52,12 +52,12 @@ export class PhpenvGroup extends ToolManagerGroup {
52
52
  install: () => installPhpenv(),
53
53
  upgrade: () => {
54
54
  execSync(`cd "${getPhpenvRoot()}" && git remote set-url origin "${getActiveRegistry('phpenv')}" && git pull --tags`, {
55
- stdio: 'inherit',
55
+ stdio: shell.stdioForExec(true),
56
56
  shell: shell.resolveBash(),
57
57
  })
58
58
  const pluginsDir = path.join(getPhpenvRoot(), 'plugins', 'php-build')
59
59
  if (fs.existsSync(pluginsDir)) {
60
- execSync(`cd "${pluginsDir}" && git pull`, { stdio: 'inherit', shell: shell.resolveBash() })
60
+ execSync(`cd "${pluginsDir}" && git pull`, { stdio: shell.stdioForExec(true), shell: shell.resolveBash() })
61
61
  }
62
62
  return { action: 'upgraded' }
63
63
  },