@link-assistant/hive-mind 2.17.0 → 2.19.0

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.
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Shared runner for the `hive-models` bin command (issue #2202, R5).
5
+ *
6
+ * R5 asks for a listing that merges what this installation ships with what the
7
+ * providers are serving right now, "from fully supported, to hot loaded", per
8
+ * tool. This module is the CLI half of that; `telegram-models-command.lib.mjs`
9
+ * is the `/models` half, and both render through
10
+ * `model-catalogue-render.lib.mjs` so the two can never disagree.
11
+ *
12
+ * R6 is honoured here too: before printing a catalogue the runner gives the
13
+ * agentic CLIs a chance to update, because a stale `codex` binary is exactly
14
+ * what makes a new model look unavailable.
15
+ *
16
+ * @see https://github.com/link-assistant/hive-mind/issues/2202
17
+ */
18
+
19
+ import { ensureAgenticCliFreshness, describeFreshnessResult } from './agentic-cli-freshness.lib.mjs';
20
+ import { parseCliArgumentsWithLino } from './cli-arguments.lib.mjs';
21
+ import { MODEL_CATALOGUE_TOOLS, getMergedModelCatalogue } from './model-catalogue.lib.mjs';
22
+ import { formatModelCatalogueText } from './model-catalogue-render.lib.mjs';
23
+
24
+ export const HIVE_MODELS_HELP = `Usage: hive-models [--tool <name>...] [--refresh] [--details] [--json] [--no-update] [--verbose]
25
+
26
+ List the models Hive Mind can drive, merged from every source that can be read
27
+ without spending a token — the router's live catalogue, the provider listing
28
+ endpoints, the codex CLI's own catalogue, models.dev metadata, and the models
29
+ bundled with this installation.
30
+
31
+ Models are grouped so it is obvious what each one is:
32
+ Bundled and live shipped here and confirmed reachable now
33
+ Hot loaded a live source has it, this installation does not ship it
34
+ Bundled only shipped here, no live source confirmed it
35
+
36
+ Options:
37
+ -t, --tool <name> Restrict to one tool (${MODEL_CATALOGUE_TOOLS.join(', ')}).
38
+ Repeatable; defaults to every tool.
39
+ --refresh Ignore the cached answer and re-read every live source
40
+ --details Show context window, pricing, and which source had it
41
+ --json Print machine-readable JSON instead of text
42
+ --no-update Do not check the agentic CLIs for a newer version first
43
+ (also spelled --no-tool-update, as in /solve and /task)
44
+ -v, --verbose Print diagnostics to stderr
45
+ -h, --help Show this help and exit
46
+
47
+ Environment:
48
+ HIVE_MIND_MODELS_HOT_LOAD=0 Only list the bundled catalogue
49
+ HIVE_MIND_MODELS_ROUTER=0 Skip the router source specifically
50
+ HIVE_MIND_MODEL_CATALOGUE_TTL_MINUTES Raise the 60 minute cache lifetime
51
+ HIVE_MIND_AGENTIC_CLI_AUTO_UPDATE=0 Never update the CLIs
52
+
53
+ Examples:
54
+ hive-models # every tool, cached answers
55
+ hive-models --tool codex # just codex
56
+ hive-models --tool claude --details --refresh
57
+ hive-models --json | jq '.tools.claude.liveOnly'
58
+
59
+ Reference:
60
+ https://github.com/link-assistant/hive-mind/issues/2202
61
+ `;
62
+
63
+ const VALUE_FLAGS = new Set(['--tool', '-t']);
64
+ const BOOLEAN_FLAGS = new Set(['--refresh', '--details', '--json', '--no-update', '--no-tool-update', '--verbose', '-v', '--help', '-h']);
65
+
66
+ // `/solve`, `/hive` and `/task` spell the opt-out `--no-tool-update` (it lives in
67
+ // their `tool-*` namespace). Accept that spelling here too, so the flag an
68
+ // operator already knows works everywhere it makes sense (issue #2202, R6).
69
+ const normaliseUpdateFlag = arg => (arg === '--no-tool-update' ? '--no-update' : arg);
70
+
71
+ const createHiveModelsYargsConfig = yargsInstance => yargsInstance.usage('Usage: hive-models [--tool <name>...] [--refresh] [--details] [--json] [--no-update] [--verbose]').option('tool', { type: 'array', alias: 't', default: [] }).option('refresh', { type: 'boolean', default: false }).option('details', { type: 'boolean', default: false }).option('json', { type: 'boolean', default: false }).option('update', { type: 'boolean', default: true }).option('verbose', { type: 'boolean', alias: 'v', default: false }).option('help', { type: 'boolean', alias: 'h', default: false }).help(false).version(false).strict(false);
72
+
73
+ /**
74
+ * Parse argv for `hive-models`. Returns `error` as a string rather than
75
+ * throwing, so the bin can print it and exit non-zero.
76
+ */
77
+ export const parseHiveModelsArgs = argv => {
78
+ const result = { tools: [], refresh: false, details: false, json: false, update: true, verbose: false, help: false, error: null };
79
+ const help = argv.includes('--help') || argv.includes('-h');
80
+
81
+ for (let index = 0; index < argv.length; index += 1) {
82
+ const arg = argv[index];
83
+ const [name] = arg.split('=');
84
+ if (VALUE_FLAGS.has(name)) {
85
+ if (!arg.includes('=')) index += 1;
86
+ continue;
87
+ }
88
+ if (!BOOLEAN_FLAGS.has(arg)) {
89
+ result.error = `Unknown option: ${arg}`;
90
+ return result;
91
+ }
92
+ }
93
+
94
+ let parsed;
95
+ try {
96
+ parsed = parseCliArgumentsWithLino({
97
+ argv: argv.filter(arg => arg !== '--help' && arg !== '-h').map(normaliseUpdateFlag),
98
+ commandName: 'hive-models',
99
+ createYargsConfig: createHiveModelsYargsConfig,
100
+ lenv: { enabled: false },
101
+ getenv: { enabled: false },
102
+ });
103
+ } catch (err) {
104
+ result.error = err.message || String(err);
105
+ return result;
106
+ }
107
+
108
+ result.help = help;
109
+ result.refresh = parsed.refresh === true;
110
+ result.details = parsed.details === true;
111
+ result.json = parsed.json === true;
112
+ result.update = parsed.update !== false;
113
+ result.verbose = parsed.verbose === true || parsed.v === true;
114
+
115
+ const requested = []
116
+ .concat(parsed.tool ?? [])
117
+ .flatMap(entry =>
118
+ String(entry)
119
+ .split(',')
120
+ .map(part => part.trim().toLowerCase())
121
+ )
122
+ .filter(Boolean);
123
+ for (const tool of requested) {
124
+ if (!MODEL_CATALOGUE_TOOLS.includes(tool)) {
125
+ result.error = `Unknown tool: ${tool}. Known tools: ${MODEL_CATALOGUE_TOOLS.join(', ')}`;
126
+ return result;
127
+ }
128
+ if (!result.tools.includes(tool)) result.tools.push(tool);
129
+ }
130
+ if (result.tools.length === 0) result.tools = [...MODEL_CATALOGUE_TOOLS];
131
+ return result;
132
+ };
133
+
134
+ /**
135
+ * Top-level orchestrator used by the bin. `deps` is injected so tests can run
136
+ * the whole command without a network, a router, or a package registry.
137
+ */
138
+ export const runHiveModels = async (argv, deps = {}) => {
139
+ const { env = process.env, log = (...args) => console.log(...args), error = (...args) => console.error(...args), loadCatalogue = getMergedModelCatalogue, freshness = ensureAgenticCliFreshness } = deps;
140
+
141
+ const args = parseHiveModelsArgs(argv);
142
+ if (args.help) {
143
+ log(HIVE_MODELS_HELP);
144
+ return 0;
145
+ }
146
+ if (args.error) {
147
+ error(args.error);
148
+ return 1;
149
+ }
150
+
151
+ const debug = args.verbose ? (...parts) => error('[hive-models]', ...parts) : () => {};
152
+
153
+ // R6: refresh the CLIs before answering, so the list describes the binaries
154
+ // the next run will actually use. Best-effort — never fatal.
155
+ const refreshed = await freshness({ tools: args.tools, env, verbose: args.verbose, enabled: args.update, log: async message => debug(message) });
156
+ debug(`cli freshness: ${refreshed.status}${refreshed.reason ? ` (${refreshed.reason})` : ''}`);
157
+ const freshnessLine = describeFreshnessResult(refreshed);
158
+
159
+ const results = {};
160
+ let failures = 0;
161
+ for (const tool of args.tools) {
162
+ try {
163
+ results[tool] = await loadCatalogue({ tool, env, refresh: args.refresh, log: async message => debug(message) });
164
+ } catch (err) {
165
+ failures += 1;
166
+ error(`Could not build the ${tool} catalogue: ${err?.message ?? err}`);
167
+ }
168
+ }
169
+
170
+ if (args.json) {
171
+ log(JSON.stringify({ generatedAt: new Date().toISOString(), cliUpdate: refreshed, tools: results }, null, 2));
172
+ return failures > 0 && Object.keys(results).length === 0 ? 1 : 0;
173
+ }
174
+
175
+ if (freshnessLine) log(freshnessLine);
176
+ const sections = Object.values(results).map(merged => formatModelCatalogueText(merged, { details: args.details, defaultModel: merged.default }));
177
+ log(sections.join('\n\n'));
178
+ return failures > 0 && sections.length === 0 ? 1 : 0;
179
+ };
180
+
181
+ export default { HIVE_MODELS_HELP, parseHiveModelsArgs, runHiveModels };
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * `hive-models` — list every model Hive Mind can drive, merging the models
5
+ * bundled with this installation with the ones the providers are serving right
6
+ * now (issue #2202, R5).
7
+ *
8
+ * Live sources are read only through endpoints that cannot bill a token, and
9
+ * the merged answer is cached for an hour, so running this repeatedly is free.
10
+ *
11
+ * See issue #2202.
12
+ */
13
+
14
+ import { runHiveModels } from './hive-models.lib.mjs';
15
+ import { setupStdioLogInterceptor } from './lib.mjs';
16
+
17
+ setupStdioLogInterceptor();
18
+
19
+ const exitCode = await runHiveModels(process.argv.slice(2));
20
+ process.exit(exitCode);
package/src/hive.mjs CHANGED
@@ -86,6 +86,8 @@ if (isRunningDirectly) {
86
86
  const { validateYouTrackConfig, testYouTrackConnection, createYouTrackConfigFromEnv } = youTrackLib;
87
87
  const youTrackSync = await import('./youtrack/youtrack-sync.mjs');
88
88
  const { syncYouTrackToGitHub, formatIssuesForHive } = youTrackSync;
89
+ // Issue #2194: recovery diagnostics for URLs that had to be repaired before parsing.
90
+ const { formatUrlRepairs, hasNotableRepair, revealHiddenCharacters } = await import('./github-url-recovery.lib.mjs');
89
91
  const memCheck = await import('./memory-check.mjs');
90
92
  const { checkSystem } = memCheck;
91
93
  const exitHandler = await import('./exit-handler.lib.mjs');
@@ -185,6 +187,14 @@ if (isRunningDirectly) {
185
187
  console.error(' - owner/repo (will be converted to https://github.com/owner/repo)');
186
188
  await safeExit(1, 'Error occurred');
187
189
  }
190
+ // Issue #2194: report a repaired URL before monitoring starts, so the user
191
+ // can catch a wrong guess instead of watching the wrong repository.
192
+ if (hasNotableRepair(parsedUrl.repairs)) {
193
+ console.error('ℹ️ Repaired the GitHub URL before monitoring:');
194
+ console.error(` You typed: ${revealHiddenCharacters(githubUrl)}`);
195
+ console.error(` Using: ${parsedUrl.canonical || parsedUrl.normalized}`);
196
+ console.error(` Repaired: ${formatUrlRepairs(parsedUrl.repairs, { notableOnly: true })}`);
197
+ }
188
198
  // Check if it's a valid type for hive (user or repo)
189
199
  if (parsedUrl.type !== 'user' && parsedUrl.type !== 'repo') {
190
200
  console.error('Error: Invalid GitHub URL for monitoring');
@@ -508,6 +508,13 @@ en
508
508
  must
509
509
  be
510
510
  type "URL must be a GitHub {{allowedTypes}} (not {{type}})"
511
+ recovered """
512
+ ℹ️ I repaired the link before starting.
513
+
514
+ You sent: {{original}}
515
+ Using: {{used}}
516
+ Repaired: {{repairs}}
517
+ """
511
518
  language
512
519
  invalid """
513
520
  ❌ Invalid language. Supported: {{supported}}.
@@ -583,6 +590,7 @@ en
583
590
  usage "Usage: `/hive <github-url> [options]`"
584
591
  example "Example: `/hive https://github.com/owner/repo`"
585
592
  disabled "*/hive* - ❌ Disabled"
593
+ models "*/models* - List available models, merged from this installation and every live source. Usage: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
586
594
  limits "*/limits* - Show usage limits"
587
595
  version "*/version* - Show bot and runtime versions"
588
596
  language "*/language* `[en|ru|zh|hi]` - Set or show your preferred reply language (in-memory only, per-user)"
@@ -604,7 +612,7 @@ en
604
612
  isolation
605
613
  mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
606
614
  group
607
- note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
615
+ note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
608
616
  common
609
617
  options "🔧 *Common Options:*"
610
618
  model
@@ -508,6 +508,13 @@ hi
508
508
  must
509
509
  be
510
510
  type "URL GitHub {{allowedTypes}} होना चाहिए ({{type}} नहीं)"
511
+ recovered """
512
+ ℹ️ शुरू करने से पहले लिंक ठीक की गई।
513
+
514
+ आपने भेजा: {{original}}
515
+ उपयोग किया जा रहा है: {{used}}
516
+ ठीक किया गया: {{repairs}}
517
+ """
511
518
  language
512
519
  invalid """
513
520
  ❌ अमान्य भाषा। समर्थित: {{supported}}।
@@ -583,6 +590,7 @@ hi
583
590
  usage "उपयोग: `/hive <github-url> [options]`"
584
591
  example "उदाहरण: `/hive https://github.com/owner/repo`"
585
592
  disabled "*/hive* - ❌ अक्षम"
593
+ models "*/models* - उपलब्ध models दिखाएँ, जो इस installation और सभी live sources से मिलाकर बनाई गई हैं। उपयोग: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
586
594
  limits "*/limits* - उपयोग सीमाएँ दिखाएँ"
587
595
  version "*/version* - bot और runtime versions दिखाएँ"
588
596
  language "*/language* `[en|ru|zh|hi]` - अपनी पसंदीदा reply language सेट या दिखाएँ (in-memory, per-user)"
@@ -604,7 +612,7 @@ hi
604
612
  isolation
605
613
  mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
606
614
  group
607
- note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
615
+ note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
608
616
  common
609
617
  options "🔧 *Common Options:*"
610
618
  model
@@ -508,6 +508,13 @@ ru
508
508
  must
509
509
  be
510
510
  type "URL должен быть GitHub {{allowedTypes}} (не {{type}})"
511
+ recovered """
512
+ ℹ️ Ссылка была исправлена перед запуском.
513
+
514
+ Вы отправили: {{original}}
515
+ Используется: {{used}}
516
+ Исправлено: {{repairs}}
517
+ """
511
518
  language
512
519
  invalid """
513
520
  ❌ Неверный язык. Поддерживаются: {{supported}}.
@@ -583,6 +590,7 @@ ru
583
590
  usage "Использование: `/hive <github-url> [options]`"
584
591
  example "Пример: `/hive https://github.com/owner/repo`"
585
592
  disabled "*/hive* - ❌ Отключено"
593
+ models "*/models* - Показать доступные модели, объединённые из этой установки и всех живых источников. Использование: `/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
586
594
  limits "*/limits* - Показать лимиты использования"
587
595
  version "*/version* - Показать версии бота и среды выполнения"
588
596
  language "*/language* `[en|ru|zh|hi]` - Установить или показать предпочитаемый язык ответов (в памяти, для пользователя)"
@@ -604,7 +612,7 @@ ru
604
612
  isolation
605
613
  mode "🔒 *Режим изоляции:* `{{isolationBackend}}` (экспериментально)"
606
614
  group
607
- note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
615
+ note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /fix, /hive, /queue, /models, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
608
616
  common
609
617
  options "🔧 *Общие опции:*"
610
618
  model
@@ -508,6 +508,13 @@ zh
508
508
  must
509
509
  be
510
510
  type "URL 必须是 GitHub {{allowedTypes}}(不是 {{type}})"
511
+ recovered """
512
+ ℹ️ 开始前已修复链接。
513
+
514
+ 您发送的:{{original}}
515
+ 实际使用:{{used}}
516
+ 修复内容:{{repairs}}
517
+ """
511
518
  language
512
519
  invalid """
513
520
  ❌ 语言无效。支持的语言:{{supported}}。
@@ -583,6 +590,7 @@ zh
583
590
  usage "用法:`/hive <github-url> [options]`"
584
591
  example "示例:`/hive https://github.com/owner/repo`"
585
592
  disabled "*/hive* - ❌ 已禁用"
593
+ models "*/models* - 列出可用模型,合并本次安装自带的模型与所有实时来源的模型。用法:`/models [--tool claude|codex|...] [--details] [--refresh] [--all]`"
586
594
  limits "*/limits* - 显示使用限额"
587
595
  version "*/version* - 显示机器人和运行时版本"
588
596
  language "*/language* `[en|ru|zh|hi]` - 设置或显示首选回复语言(内存中,按用户)"
@@ -604,7 +612,7 @@ zh
604
612
  isolation
605
613
  mode "🔒 *隔离模式:* `{{isolationBackend}}`(实验性)"
606
614
  group
607
- note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/fix、/hive、/queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
615
+ note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/fix、/hive、/queue、/models、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
608
616
  common
609
617
  options "🔧 *常用选项:*"
610
618
  model