skillgauntlet 1.3.3 → 1.3.4

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/lib/github.js CHANGED
@@ -11,10 +11,48 @@ const quiet = (cmd, args) => execFileSync(cmd, args, { encoding: 'utf8', stdio:
11
11
  const live = (cmd, args) => spawnSync(cmd, args, { stdio: 'inherit', shell: process.platform === 'win32', windowsHide: false });
12
12
 
13
13
  export const TOOLS = {
14
- git: { why: 'git', win: 'Git.Git', brew: 'git', url: 'https://git-scm.com/downloads' },
15
- gh: { why: 'gh', win: 'GitHub.cli', brew: 'gh', url: 'https://cli.github.com' },
14
+ git: { url: 'https://git-scm.com/downloads', pkg: { winget: 'Git.Git', choco: 'git', scoop: 'git', brew: 'git', apt: 'git', dnf: 'git', pacman: 'git', zypper: 'git', apk: 'git' } },
15
+ gh: { url: 'https://cli.github.com', pkg: { winget: 'GitHub.cli', choco: 'gh', scoop: 'gh', brew: 'gh', apt: 'gh', dnf: 'gh', pacman: 'github-cli', zypper: 'gh', apk: 'github-cli' } },
16
16
  };
17
17
 
18
+ // The package managers to look for on each system, in the order they are preferred.
19
+ export const MANAGERS = { win32: ['winget', 'choco', 'scoop'], darwin: ['brew'], linux: ['apt-get', 'dnf', 'pacman', 'zypper', 'apk', 'brew'] };
20
+
21
+ // The steps that install `pkg` with a manager. The system-wide ones need root, so they are run through sudo unless the
22
+ // user already is root; a `sudo` prompt for the password then appears on the user's own terminal.
23
+ function stepsFor(manager, pkg, id, asRoot) {
24
+ const root = (args) => (asRoot ? args : ['sudo', ...args]);
25
+ switch (manager) {
26
+ case 'winget': return [['winget', 'install', '--id', id, '-e', '--accept-source-agreements', '--accept-package-agreements']];
27
+ case 'choco': return [['choco', 'install', pkg, '-y']];
28
+ case 'scoop': return [['scoop', 'install', pkg]];
29
+ case 'brew': return [['brew', 'install', pkg]];
30
+ case 'apt-get': return [root(['apt-get', 'update']), root(['apt-get', 'install', '-y', pkg])];
31
+ case 'dnf': return [root(['dnf', 'install', '-y', pkg])];
32
+ case 'pacman': return [root(['pacman', '-S', '--noconfirm', pkg])];
33
+ case 'zypper': return [root(['zypper', '--non-interactive', 'install', pkg])];
34
+ case 'apk': return [root(['apk', 'add', pkg])];
35
+ default: return null;
36
+ }
37
+ }
38
+
39
+ // What this system can run to install a tool, or null when it cannot be done here. `ctx.have(cmd)` says whether a command
40
+ // exists; `ctx.uid` is the user id (0 is root). The first package manager found wins.
41
+ export function installPlan(tool, platform = process.platform, ctx = {}) {
42
+ const spec = TOOLS[tool];
43
+ if (!spec) return null;
44
+ const have = ctx.have || (() => false);
45
+ const asRoot = ctx.uid === 0;
46
+ const manager = (MANAGERS[platform] || []).find((m) => have(m));
47
+ if (!manager) return null;
48
+ const key = manager === 'apt-get' ? 'apt' : manager;
49
+ const pkg = spec.pkg[key];
50
+ if (!pkg) return null;
51
+ const sudoNeeded = !['winget', 'choco', 'scoop', 'brew'].includes(manager) && !asRoot;
52
+ if (sudoNeeded && !have('sudo')) return null; // neither root nor sudo: nothing that can be done from here
53
+ return { manager, steps: stepsFor(manager, pkg, pkg, asRoot), sudo: sudoNeeded };
54
+ }
55
+
18
56
  // Where a fresh install lands when the running process was started before it existed (its PATH is out of date).
19
57
  export function knownDirs(platform = process.platform) {
20
58
  if (platform === 'win32') return ['C:\\Program Files\\Git\\cmd', 'C:\\Program Files\\GitHub CLI', `${process.env.LOCALAPPDATA || ''}\\Programs\\Git\\cmd`];
@@ -31,40 +69,34 @@ export function addKnownDirsToPath(platform = process.platform, exists = fs.exis
31
69
  return add;
32
70
  }
33
71
 
34
- // The command that installs a tool on this system, or null when it cannot be done without a password or a guess.
35
- export function installPlan(tool, platform = process.platform) {
36
- const spec = TOOLS[tool];
37
- if (!spec) return null;
38
- if (platform === 'win32') return { cmd: 'winget', args: ['install', '--id', spec.win, '-e', '--accept-source-agreements', '--accept-package-agreements'] };
39
- if (platform === 'darwin') return { cmd: 'brew', args: ['install', spec.brew] };
40
- return null;
41
- }
42
-
43
72
  export const has = (tool, exec = quiet) => { try { exec(tool, ['--version']); return true; } catch { return false; } };
44
73
 
45
- const line = (plan) => `${plan.cmd} ${plan.args.join(' ')}`;
74
+ const line = (plan) => plan.steps.map((st) => st.join(' ')).join(' && ');
46
75
 
47
76
  // One tool: present, or offered for install with enter. True when it is usable afterwards.
48
- export async function ensureTool(tool, { exec = quiet, run = live, pick, out, platform = process.platform, exists = fs.existsSync } = {}) {
77
+ export async function ensureTool(tool, { exec = quiet, run = live, pick, out, platform = process.platform, exists = fs.existsSync, ctx = {} } = {}) {
49
78
  if (has(tool, exec)) return true;
50
79
  addKnownDirsToPath(platform, exists);
51
80
  if (has(tool, exec)) return true;
52
- const plan = installPlan(tool, platform);
81
+ const plan = installPlan(tool, platform, { have: (c) => has(c, exec), uid: typeof process.getuid === 'function' ? process.getuid() : undefined, ...ctx });
53
82
  out(`\n${t('toolMissing', { tool })}`);
54
83
  if (!plan) {
55
84
  out(t('toolNoAuto', { tool, url: TOOLS[tool].url }));
56
85
  return false;
57
86
  }
87
+ if (plan.sudo) out(t('toolSudo'));
58
88
  const choice = await pick(t('toolQ', { tool }), [
59
89
  { label: t('toolInstallNow', { tool }), hint: t('toolRuns', { cmd: line(plan) }), value: 'now' },
60
90
  { label: t('toolInstallSelf'), value: 'self' },
61
91
  ], 1);
62
92
  if (choice !== 'now') return false;
63
93
  out(`\n${t('toolInstalling', { tool })}\n`);
64
- const r = run(plan.cmd, plan.args);
65
- if (r?.error || (r && r.status !== 0 && r.status != null)) {
66
- out(t('toolInstallFailed', { tool, url: TOOLS[tool].url }));
67
- return false;
94
+ for (const [cmd, ...args] of plan.steps) {
95
+ const r = run(cmd, args);
96
+ if (r?.error || (r && r.status !== 0 && r.status != null)) {
97
+ out(t('toolInstallFailed', { tool, url: TOOLS[tool].url }));
98
+ return false;
99
+ }
68
100
  }
69
101
  addKnownDirsToPath(platform, exists);
70
102
  if (has(tool, exec)) {
package/lib/i18n.js CHANGED
@@ -166,6 +166,7 @@ const MESSAGES = {
166
166
  prClosed: '这个 PR 已被关闭:{url}',
167
167
  prNeedsReview: '没有自动合并,已转给维护者审核(常见原因:GitHub 账号注册不满 30 天,或今天已提交超过 5 条)。你不需要再做什么:{url}',
168
168
  prTimeout: '等了 {m} 分钟还没合并,PR 还在:{url},之后随时在浏览器里看。',
169
+ toolSudo: '这一步要用管理员权限安装:下面可能会让你输入这台电脑的登录密码(输入时不会显示字符,输完按回车)。',
169
170
  modelUnknown: '{host} 的模型清单里没有“{model}”。它现在支持:{list}。请重新选一个,或选“默认模型”。',
170
171
  updateAvail: '有新版本 {latest}(你现在用的是 {current})。',
171
172
  updateQ: '要更新吗?',
@@ -597,6 +598,7 @@ const MESSAGES = {
597
598
  prClosed: 'This pull request was closed: {url}',
598
599
  prNeedsReview: 'It was not merged automatically and has gone to a maintainer for review (common reasons: the GitHub account is under 30 days old, or more than 5 submissions today). You need do nothing more: {url}',
599
600
  prTimeout: 'Waited {m} minutes and it is not merged yet. The pull request is still there: {url}; look any time in the browser.',
601
+ toolSudo: 'This install needs administrator rights: you may be asked for this computer\'s password below (nothing is shown as you type; press enter when done).',
600
602
  modelUnknown: '{host} does not list a model called "{model}". It currently supports: {list}. Pick another, or choose the default model.',
601
603
  updateAvail: 'A newer version {latest} is available (you are on {current}).',
602
604
  updateQ: 'Update?',
@@ -1076,6 +1078,7 @@ Layer 0 always runs first: it is free, and it disqualifies most problems before
1076
1078
  prClosed: 'このプルリクエストはクローズされました:{url}',
1077
1079
  prNeedsReview: '自動マージされず、メンテナーのレビューに回りました(主な理由:GitHub アカウントが作成 30 日未満、または本日 5 件を超える提出)。これ以上の操作は不要です:{url}',
1078
1080
  prTimeout: '{m} 分待ちましたがまだマージされていません。プルリクエストは残っています:{url}。いつでもブラウザで確認できます。',
1081
+ toolSudo: 'このインストールには管理者権限が必要です。このコンピューターのパスワードの入力を求められることがあります(入力中は何も表示されません。入力後に Enter)。',
1079
1082
  modelUnknown: '{host} のモデル一覧に「{model}」はありません。現在対応:{list}。別のものを選ぶか、既定のモデルを選んでください。',
1080
1083
  updateAvail: '新しいバージョン {latest} があります(現在は {current})。',
1081
1084
  updateQ: '更新しますか?',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillgauntlet",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "Check an agent skill before you install it: context cost, permission surface, code risk, instruction hijacking, description quality, trigger rate and gain.",
5
5
  "type": "module",
6
6
  "bin": {