triflux 10.3.4 → 10.4.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.
Files changed (120) hide show
  1. package/CLAUDE.md +193 -0
  2. package/LICENSE +21 -21
  3. package/hooks/hook-registry.json +256 -256
  4. package/hub/adaptive-inject.mjs +1 -1
  5. package/hub/assign-callbacks.mjs +120 -120
  6. package/hub/delegator/index.mjs +14 -14
  7. package/hub/delegator/tool-definitions.mjs +35 -35
  8. package/hub/hitl.mjs +143 -143
  9. package/hub/router.mjs +791 -791
  10. package/hub/session-fingerprint.mjs +1 -1
  11. package/hub/team/cli/commands/attach.mjs +37 -37
  12. package/hub/team/cli/commands/debug.mjs +74 -74
  13. package/hub/team/cli/commands/focus.mjs +53 -53
  14. package/hub/team/cli/commands/list.mjs +24 -24
  15. package/hub/team/cli/commands/start/start-in-process.mjs +40 -40
  16. package/hub/team/cli/commands/start/start-mux.mjs +73 -73
  17. package/hub/team/cli/commands/start/start-wt.mjs +69 -69
  18. package/hub/team/cli/commands/tasks.mjs +13 -13
  19. package/hub/team/cli/render.mjs +30 -30
  20. package/hub/team/cli/services/attach-fallback.mjs +54 -54
  21. package/hub/team/cli/services/member-selector.mjs +30 -30
  22. package/hub/team/cli/services/native-control.mjs +116 -116
  23. package/hub/team/cli/services/task-model.mjs +30 -30
  24. package/hub/team/notify.mjs +1 -1
  25. package/hub/team/orchestrator.mjs +161 -161
  26. package/hub/team/session.mjs +611 -611
  27. package/hub/team/shared.mjs +13 -13
  28. package/hub/tray.mjs +368 -368
  29. package/hub/workers/codex-mcp.mjs +507 -507
  30. package/hub/workers/factory.mjs +21 -21
  31. package/package.json +21 -55
  32. package/references/hosts.json +33 -0
  33. package/scripts/completions/tfx.bash +47 -47
  34. package/scripts/completions/tfx.fish +44 -44
  35. package/scripts/completions/tfx.zsh +83 -83
  36. package/scripts/hub-ensure.mjs +120 -120
  37. package/scripts/keyword-detector.mjs +272 -272
  38. package/scripts/keyword-rules-expander.mjs +521 -521
  39. package/scripts/lib/mcp-server-catalog.mjs +118 -118
  40. package/scripts/notion-read.mjs +553 -553
  41. package/scripts/test-tfx-route-no-claude-native.mjs +57 -57
  42. package/scripts/tfx-batch-stats.mjs +96 -96
  43. package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  44. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  45. package/skills/.omc/state/last-tool-error.json +7 -0
  46. package/skills/.omc/state/subagent-tracking.json +7 -0
  47. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  48. package/skills/tfx-workspace/async-tests/run-tests.sh +203 -0
  49. package/skills/tfx-workspace/evals/evals.json +79 -0
  50. package/skills/tfx-workspace/iteration-1/benchmark.json +162 -0
  51. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +11 -0
  52. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +9 -0
  53. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +154 -0
  54. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +5 -0
  55. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +9 -0
  56. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +126 -0
  57. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +5 -0
  58. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +11 -0
  59. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +9 -0
  60. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +119 -0
  61. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +5 -0
  62. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +9 -0
  63. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +115 -0
  64. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +5 -0
  65. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +10 -0
  66. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +8 -0
  67. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +86 -0
  68. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +5 -0
  69. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +8 -0
  70. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +81 -0
  71. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +5 -0
  72. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +12 -0
  73. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +10 -0
  74. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +316 -0
  75. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +5 -0
  76. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +10 -0
  77. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +352 -0
  78. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +5 -0
  79. package/skills/tfx-workspace/iteration-1/review.html +1325 -0
  80. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +12 -0
  81. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +10 -0
  82. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +97 -0
  83. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +5 -0
  84. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +10 -0
  85. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +94 -0
  86. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +5 -0
  87. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +12 -0
  88. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +10 -0
  89. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +209 -0
  90. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +5 -0
  91. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +10 -0
  92. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +193 -0
  93. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +5 -0
  94. package/skills/tfx-workspace/iteration-2/benchmark.json +62 -0
  95. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +13 -0
  96. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +11 -0
  97. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +382 -0
  98. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +5 -0
  99. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +11 -0
  100. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +333 -0
  101. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +5 -0
  102. package/skills/tfx-workspace/iteration-2/review.html +1325 -0
  103. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +217 -0
  104. package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +77 -0
  105. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +65 -0
  106. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +94 -0
  107. package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +82 -0
  108. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +133 -0
  109. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +426 -0
  110. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +101 -0
  111. package/.claude-plugin/marketplace.json +0 -34
  112. package/.claude-plugin/plugin.json +0 -22
  113. package/config/mcp-registry.json +0 -29
  114. package/tui/codex-profile.mjs +0 -402
  115. package/tui/core.mjs +0 -236
  116. package/tui/doctor.mjs +0 -328
  117. package/tui/gemini-profile.mjs +0 -254
  118. package/tui/monitor-data.mjs +0 -148
  119. package/tui/monitor.mjs +0 -295
  120. package/tui/setup.mjs +0 -442
@@ -1,118 +1,118 @@
1
- export const SEARCH_SERVER_ORDER = Object.freeze(['brave-search', 'tavily', 'exa']);
2
-
3
- export const MCP_SERVER_TOOL_CATALOG = Object.freeze({
4
- context7: Object.freeze(['resolve-library-id', 'query-docs']),
5
- 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
6
- exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
7
- tavily: Object.freeze(['tavily_search', 'tavily_extract']),
8
- playwright: Object.freeze([
9
- 'browser_navigate',
10
- 'browser_navigate_back',
11
- 'browser_snapshot',
12
- 'browser_take_screenshot',
13
- 'browser_wait_for',
14
- ]),
15
- 'sequential-thinking': Object.freeze(['sequentialthinking']),
16
- });
17
-
18
- export const MCP_SERVER_DOMAIN_TAGS = Object.freeze({
19
- context7: Object.freeze(['docs', 'reference', 'api', 'sdk', 'library']),
20
- 'brave-search': Object.freeze(['web', 'search', 'news', 'current']),
21
- exa: Object.freeze(['code', 'repository', 'examples', 'search']),
22
- tavily: Object.freeze(['research', 'search', 'news', 'verification', 'current']),
23
- playwright: Object.freeze(['browser', 'ui', 'visual', 'e2e']),
24
- 'sequential-thinking': Object.freeze(['analysis', 'planning', 'reasoning', 'security', 'review']),
25
- });
26
-
27
- export const DOMAIN_TAG_KEYWORDS = Object.freeze({
28
- docs: Object.freeze(['docs', 'documentation', 'manual', 'guide', '문서', '가이드', '매뉴얼']),
29
- reference: Object.freeze(['reference', 'spec', 'schema', 'official', '레퍼런스', '공식', '스펙', '스키마']),
30
- api: Object.freeze(['api', 'endpoint', 'interface', 'sdk', '호출', '엔드포인트']),
31
- sdk: Object.freeze(['sdk', 'library', 'package', 'framework', '라이브러리', '패키지', '프레임워크']),
32
- library: Object.freeze(['library', 'package', 'framework', 'module', '라이브러리', '패키지', '모듈']),
33
- web: Object.freeze(['web', 'site', 'article', 'forum', 'blog', 'reddit', '웹', '사이트', '기사', '포럼', '블로그']),
34
- search: Object.freeze(['search', 'browse', 'lookup', 'find', '검색', '조회', '탐색', '찾아']),
35
- news: Object.freeze(['latest', 'recent', 'news', 'today', 'release', 'announcement', '최신', '최근', '뉴스', '오늘', '릴리즈', '공지']),
36
- current: Object.freeze(['current', 'status', 'pricing', 'changelog', 'up-to-date', '현재', '상태', '가격', '변경사항']),
37
- research: Object.freeze(['research', 'verify', 'fact-check', 'investigate', '리서치', '검증', '조사']),
38
- verification: Object.freeze(['verify', 'validation', 'fact-check', 'audit', '검증', '확인', '감사']),
39
- code: Object.freeze(['code', 'repo', 'repository', 'source', 'implementation', 'bug', 'fix', 'test', 'snippet', 'cli', '코드', '리포', '저장소', '구현', '버그', '테스트', '예제', '스크립트']),
40
- repository: Object.freeze(['repo', 'repository', 'source', 'git', 'github', '리포', '저장소', '소스']),
41
- examples: Object.freeze(['example', 'examples', 'snippet', 'sample', '예제', '샘플']),
42
- browser: Object.freeze(['browser', 'page', 'dom', 'screenshot', 'render', '브라우저', '페이지', '스크린샷', '렌더']),
43
- ui: Object.freeze(['ui', 'ux', 'layout', 'responsive', 'css', 'html', '디자인', '레이아웃', '반응형']),
44
- visual: Object.freeze(['visual', 'screenshot', 'layout', 'render', 'screen', '화면', '시각', '스크린샷']),
45
- e2e: Object.freeze(['playwright', 'e2e', 'click', 'navigate', 'automation', 'playwright', '클릭', '이동', '자동화']),
46
- analysis: Object.freeze(['analysis', 'analyze', 'audit', 'compare', 'root cause', '분석', '검토', '비교', '원인']),
47
- planning: Object.freeze(['plan', 'planning', 'strategy', 'design', '계획', '전략', '설계']),
48
- reasoning: Object.freeze(['reason', 'reasoning', 'think', 'critique', '추론', '사고', '비평']),
49
- security: Object.freeze(['security', 'risk', 'threat', 'vulnerability', '보안', '위험', '취약점']),
50
- review: Object.freeze(['review', 'reviewer', 'inspect', '리뷰', '검수']),
51
- });
52
-
53
- export const SERVER_EXPLICIT_KEYWORDS = Object.freeze({
54
- context7: Object.freeze(['context7']),
55
- 'brave-search': Object.freeze(['brave', 'brave-search']),
56
- exa: Object.freeze(['exa']),
57
- tavily: Object.freeze(['tavily']),
58
- playwright: Object.freeze(['playwright']),
59
- 'sequential-thinking': Object.freeze(['sequential-thinking', 'sequential thinking']),
60
- });
61
-
62
- export function uniqueStrings(values = []) {
63
- return [...new Set(
64
- values
65
- .filter((value) => typeof value === 'string' && value.trim())
66
- .map((value) => value.trim()),
67
- )];
68
- }
69
-
70
- export function inferDomainTagsFromText(text = '') {
71
- if (typeof text !== 'string' || !text.trim()) return [];
72
- const normalized = text.toLocaleLowerCase();
73
- const matched = [];
74
-
75
- for (const [tag, keywords] of Object.entries(DOMAIN_TAG_KEYWORDS)) {
76
- if (keywords.some((keyword) => normalized.includes(String(keyword).toLocaleLowerCase()))) {
77
- matched.push(tag);
78
- }
79
- }
80
-
81
- return uniqueStrings(matched);
82
- }
83
-
84
- export function getDefaultServerMetadata(serverName = '') {
85
- const toolCount = MCP_SERVER_TOOL_CATALOG[serverName]?.length || 0;
86
- const domainTags = uniqueStrings([
87
- ...(MCP_SERVER_DOMAIN_TAGS[serverName] || []),
88
- ...inferDomainTagsFromText(serverName),
89
- ]);
90
-
91
- return {
92
- tool_count: toolCount,
93
- domain_tags: domainTags,
94
- };
95
- }
96
-
97
- export function normalizeServerMetadata(serverName = '', metadata = {}) {
98
- const fallback = getDefaultServerMetadata(serverName);
99
- const toolCount = Number.isFinite(metadata.tool_count)
100
- ? Math.max(0, Math.trunc(metadata.tool_count))
101
- : fallback.tool_count;
102
- const domainTags = uniqueStrings([
103
- ...fallback.domain_tags,
104
- ...(Array.isArray(metadata.domain_tags) ? metadata.domain_tags : []),
105
- ...inferDomainTagsFromText([
106
- serverName,
107
- typeof metadata.command === 'string' ? metadata.command : '',
108
- typeof metadata.url === 'string' ? metadata.url : '',
109
- ...(Array.isArray(metadata.args) ? metadata.args : []),
110
- ...(metadata.env && typeof metadata.env === 'object' ? Object.keys(metadata.env) : []),
111
- ].join(' ')),
112
- ]);
113
-
114
- return {
115
- tool_count: toolCount,
116
- domain_tags: domainTags,
117
- };
118
- }
1
+ export const SEARCH_SERVER_ORDER = Object.freeze(['brave-search', 'tavily', 'exa']);
2
+
3
+ export const MCP_SERVER_TOOL_CATALOG = Object.freeze({
4
+ context7: Object.freeze(['resolve-library-id', 'query-docs']),
5
+ 'brave-search': Object.freeze(['brave_web_search', 'brave_news_search']),
6
+ exa: Object.freeze(['web_search_exa', 'get_code_context_exa']),
7
+ tavily: Object.freeze(['tavily_search', 'tavily_extract']),
8
+ playwright: Object.freeze([
9
+ 'browser_navigate',
10
+ 'browser_navigate_back',
11
+ 'browser_snapshot',
12
+ 'browser_take_screenshot',
13
+ 'browser_wait_for',
14
+ ]),
15
+ 'sequential-thinking': Object.freeze(['sequentialthinking']),
16
+ });
17
+
18
+ export const MCP_SERVER_DOMAIN_TAGS = Object.freeze({
19
+ context7: Object.freeze(['docs', 'reference', 'api', 'sdk', 'library']),
20
+ 'brave-search': Object.freeze(['web', 'search', 'news', 'current']),
21
+ exa: Object.freeze(['code', 'repository', 'examples', 'search']),
22
+ tavily: Object.freeze(['research', 'search', 'news', 'verification', 'current']),
23
+ playwright: Object.freeze(['browser', 'ui', 'visual', 'e2e']),
24
+ 'sequential-thinking': Object.freeze(['analysis', 'planning', 'reasoning', 'security', 'review']),
25
+ });
26
+
27
+ export const DOMAIN_TAG_KEYWORDS = Object.freeze({
28
+ docs: Object.freeze(['docs', 'documentation', 'manual', 'guide', '문서', '가이드', '매뉴얼']),
29
+ reference: Object.freeze(['reference', 'spec', 'schema', 'official', '레퍼런스', '공식', '스펙', '스키마']),
30
+ api: Object.freeze(['api', 'endpoint', 'interface', 'sdk', '호출', '엔드포인트']),
31
+ sdk: Object.freeze(['sdk', 'library', 'package', 'framework', '라이브러리', '패키지', '프레임워크']),
32
+ library: Object.freeze(['library', 'package', 'framework', 'module', '라이브러리', '패키지', '모듈']),
33
+ web: Object.freeze(['web', 'site', 'article', 'forum', 'blog', 'reddit', '웹', '사이트', '기사', '포럼', '블로그']),
34
+ search: Object.freeze(['search', 'browse', 'lookup', 'find', '검색', '조회', '탐색', '찾아']),
35
+ news: Object.freeze(['latest', 'recent', 'news', 'today', 'release', 'announcement', '최신', '최근', '뉴스', '오늘', '릴리즈', '공지']),
36
+ current: Object.freeze(['current', 'status', 'pricing', 'changelog', 'up-to-date', '현재', '상태', '가격', '변경사항']),
37
+ research: Object.freeze(['research', 'verify', 'fact-check', 'investigate', '리서치', '검증', '조사']),
38
+ verification: Object.freeze(['verify', 'validation', 'fact-check', 'audit', '검증', '확인', '감사']),
39
+ code: Object.freeze(['code', 'repo', 'repository', 'source', 'implementation', 'bug', 'fix', 'test', 'snippet', 'cli', '코드', '리포', '저장소', '구현', '버그', '테스트', '예제', '스크립트']),
40
+ repository: Object.freeze(['repo', 'repository', 'source', 'git', 'github', '리포', '저장소', '소스']),
41
+ examples: Object.freeze(['example', 'examples', 'snippet', 'sample', '예제', '샘플']),
42
+ browser: Object.freeze(['browser', 'page', 'dom', 'screenshot', 'render', '브라우저', '페이지', '스크린샷', '렌더']),
43
+ ui: Object.freeze(['ui', 'ux', 'layout', 'responsive', 'css', 'html', '디자인', '레이아웃', '반응형']),
44
+ visual: Object.freeze(['visual', 'screenshot', 'layout', 'render', 'screen', '화면', '시각', '스크린샷']),
45
+ e2e: Object.freeze(['playwright', 'e2e', 'click', 'navigate', 'automation', 'playwright', '클릭', '이동', '자동화']),
46
+ analysis: Object.freeze(['analysis', 'analyze', 'audit', 'compare', 'root cause', '분석', '검토', '비교', '원인']),
47
+ planning: Object.freeze(['plan', 'planning', 'strategy', 'design', '계획', '전략', '설계']),
48
+ reasoning: Object.freeze(['reason', 'reasoning', 'think', 'critique', '추론', '사고', '비평']),
49
+ security: Object.freeze(['security', 'risk', 'threat', 'vulnerability', '보안', '위험', '취약점']),
50
+ review: Object.freeze(['review', 'reviewer', 'inspect', '리뷰', '검수']),
51
+ });
52
+
53
+ export const SERVER_EXPLICIT_KEYWORDS = Object.freeze({
54
+ context7: Object.freeze(['context7']),
55
+ 'brave-search': Object.freeze(['brave', 'brave-search']),
56
+ exa: Object.freeze(['exa']),
57
+ tavily: Object.freeze(['tavily']),
58
+ playwright: Object.freeze(['playwright']),
59
+ 'sequential-thinking': Object.freeze(['sequential-thinking', 'sequential thinking']),
60
+ });
61
+
62
+ export function uniqueStrings(values = []) {
63
+ return [...new Set(
64
+ values
65
+ .filter((value) => typeof value === 'string' && value.trim())
66
+ .map((value) => value.trim()),
67
+ )];
68
+ }
69
+
70
+ export function inferDomainTagsFromText(text = '') {
71
+ if (typeof text !== 'string' || !text.trim()) return [];
72
+ const normalized = text.toLocaleLowerCase();
73
+ const matched = [];
74
+
75
+ for (const [tag, keywords] of Object.entries(DOMAIN_TAG_KEYWORDS)) {
76
+ if (keywords.some((keyword) => normalized.includes(String(keyword).toLocaleLowerCase()))) {
77
+ matched.push(tag);
78
+ }
79
+ }
80
+
81
+ return uniqueStrings(matched);
82
+ }
83
+
84
+ export function getDefaultServerMetadata(serverName = '') {
85
+ const toolCount = MCP_SERVER_TOOL_CATALOG[serverName]?.length || 0;
86
+ const domainTags = uniqueStrings([
87
+ ...(MCP_SERVER_DOMAIN_TAGS[serverName] || []),
88
+ ...inferDomainTagsFromText(serverName),
89
+ ]);
90
+
91
+ return {
92
+ tool_count: toolCount,
93
+ domain_tags: domainTags,
94
+ };
95
+ }
96
+
97
+ export function normalizeServerMetadata(serverName = '', metadata = {}) {
98
+ const fallback = getDefaultServerMetadata(serverName);
99
+ const toolCount = Number.isFinite(metadata.tool_count)
100
+ ? Math.max(0, Math.trunc(metadata.tool_count))
101
+ : fallback.tool_count;
102
+ const domainTags = uniqueStrings([
103
+ ...fallback.domain_tags,
104
+ ...(Array.isArray(metadata.domain_tags) ? metadata.domain_tags : []),
105
+ ...inferDomainTagsFromText([
106
+ serverName,
107
+ typeof metadata.command === 'string' ? metadata.command : '',
108
+ typeof metadata.url === 'string' ? metadata.url : '',
109
+ ...(Array.isArray(metadata.args) ? metadata.args : []),
110
+ ...(metadata.env && typeof metadata.env === 'object' ? Object.keys(metadata.env) : []),
111
+ ].join(' ')),
112
+ ]);
113
+
114
+ return {
115
+ tool_count: toolCount,
116
+ domain_tags: domainTags,
117
+ };
118
+ }