byterover-cli 3.3.0 → 3.5.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 (106) hide show
  1. package/dist/agent/core/domain/swarm/types.d.ts +132 -0
  2. package/dist/agent/core/domain/swarm/types.js +128 -0
  3. package/dist/agent/core/domain/tools/constants.d.ts +2 -0
  4. package/dist/agent/core/domain/tools/constants.js +2 -0
  5. package/dist/agent/core/interfaces/i-memory-provider.d.ts +45 -0
  6. package/dist/agent/core/interfaces/i-memory-provider.js +1 -0
  7. package/dist/agent/core/interfaces/i-sandbox-service.d.ts +8 -0
  8. package/dist/agent/core/interfaces/i-swarm-coordinator.d.ts +127 -0
  9. package/dist/agent/core/interfaces/i-swarm-coordinator.js +1 -0
  10. package/dist/agent/infra/agent/service-initializer.js +48 -0
  11. package/dist/agent/infra/map/map-shared.d.ts +2 -2
  12. package/dist/agent/infra/sandbox/sandbox-service.d.ts +10 -0
  13. package/dist/agent/infra/sandbox/sandbox-service.js +13 -0
  14. package/dist/agent/infra/sandbox/tools-sdk.d.ts +25 -0
  15. package/dist/agent/infra/sandbox/tools-sdk.js +24 -1
  16. package/dist/agent/infra/swarm/adapters/byterover-adapter.d.ts +39 -0
  17. package/dist/agent/infra/swarm/adapters/byterover-adapter.js +62 -0
  18. package/dist/agent/infra/swarm/adapters/gbrain-adapter.d.ts +63 -0
  19. package/dist/agent/infra/swarm/adapters/gbrain-adapter.js +209 -0
  20. package/dist/agent/infra/swarm/adapters/local-markdown-adapter.d.ts +41 -0
  21. package/dist/agent/infra/swarm/adapters/local-markdown-adapter.js +256 -0
  22. package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.d.ts +29 -0
  23. package/dist/agent/infra/swarm/adapters/memory-wiki-adapter.js +244 -0
  24. package/dist/agent/infra/swarm/adapters/obsidian-adapter.d.ts +37 -0
  25. package/dist/agent/infra/swarm/adapters/obsidian-adapter.js +201 -0
  26. package/dist/agent/infra/swarm/cli/query-renderer.d.ts +15 -0
  27. package/dist/agent/infra/swarm/cli/query-renderer.js +126 -0
  28. package/dist/agent/infra/swarm/config/swarm-config-loader.d.ts +14 -0
  29. package/dist/agent/infra/swarm/config/swarm-config-loader.js +82 -0
  30. package/dist/agent/infra/swarm/config/swarm-config-schema.d.ts +667 -0
  31. package/dist/agent/infra/swarm/config/swarm-config-schema.js +305 -0
  32. package/dist/agent/infra/swarm/provider-factory.d.ts +21 -0
  33. package/dist/agent/infra/swarm/provider-factory.js +67 -0
  34. package/dist/agent/infra/swarm/search-precision.d.ts +95 -0
  35. package/dist/agent/infra/swarm/search-precision.js +141 -0
  36. package/dist/agent/infra/swarm/swarm-coordinator.d.ts +59 -0
  37. package/dist/agent/infra/swarm/swarm-coordinator.js +436 -0
  38. package/dist/agent/infra/swarm/swarm-graph.d.ts +63 -0
  39. package/dist/agent/infra/swarm/swarm-graph.js +167 -0
  40. package/dist/agent/infra/swarm/swarm-merger.d.ts +29 -0
  41. package/dist/agent/infra/swarm/swarm-merger.js +66 -0
  42. package/dist/agent/infra/swarm/swarm-router.d.ts +12 -0
  43. package/dist/agent/infra/swarm/swarm-router.js +40 -0
  44. package/dist/agent/infra/swarm/swarm-write-router.d.ts +23 -0
  45. package/dist/agent/infra/swarm/swarm-write-router.js +45 -0
  46. package/dist/agent/infra/swarm/validation/config-validator.d.ts +16 -0
  47. package/dist/agent/infra/swarm/validation/config-validator.js +402 -0
  48. package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.d.ts +33 -0
  49. package/dist/agent/infra/swarm/validation/memory-swarm-validation-error.js +27 -0
  50. package/dist/agent/infra/swarm/wizard/config-scaffolder.d.ts +36 -0
  51. package/dist/agent/infra/swarm/wizard/config-scaffolder.js +96 -0
  52. package/dist/agent/infra/swarm/wizard/provider-detector.d.ts +54 -0
  53. package/dist/agent/infra/swarm/wizard/provider-detector.js +153 -0
  54. package/dist/agent/infra/swarm/wizard/swarm-wizard.d.ts +61 -0
  55. package/dist/agent/infra/swarm/wizard/swarm-wizard.js +187 -0
  56. package/dist/agent/infra/system-prompt/contributors/index.d.ts +1 -0
  57. package/dist/agent/infra/system-prompt/contributors/index.js +1 -0
  58. package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.d.ts +15 -0
  59. package/dist/agent/infra/system-prompt/contributors/swarm-state-contributor.js +65 -0
  60. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +14 -14
  61. package/dist/agent/infra/tools/implementations/curate-tool.js +2 -0
  62. package/dist/agent/infra/tools/implementations/swarm-query-tool.d.ts +9 -0
  63. package/dist/agent/infra/tools/implementations/swarm-query-tool.js +44 -0
  64. package/dist/agent/infra/tools/implementations/swarm-store-tool.d.ts +9 -0
  65. package/dist/agent/infra/tools/implementations/swarm-store-tool.js +43 -0
  66. package/dist/agent/infra/tools/tool-provider.js +1 -0
  67. package/dist/agent/infra/tools/tool-registry.d.ts +3 -0
  68. package/dist/agent/infra/tools/tool-registry.js +25 -1
  69. package/dist/agent/resources/tools/code_exec.txt +2 -0
  70. package/dist/agent/resources/tools/swarm_query.txt +38 -0
  71. package/dist/agent/resources/tools/swarm_store.txt +35 -0
  72. package/dist/oclif/commands/connectors/install.d.ts +2 -2
  73. package/dist/oclif/commands/connectors/install.js +15 -7
  74. package/dist/oclif/commands/swarm/curate.d.ts +13 -0
  75. package/dist/oclif/commands/swarm/curate.js +81 -0
  76. package/dist/oclif/commands/swarm/onboard.d.ts +6 -0
  77. package/dist/oclif/commands/swarm/onboard.js +233 -0
  78. package/dist/oclif/commands/swarm/query.d.ts +14 -0
  79. package/dist/oclif/commands/swarm/query.js +84 -0
  80. package/dist/oclif/commands/swarm/status.d.ts +41 -0
  81. package/dist/oclif/commands/swarm/status.js +278 -0
  82. package/dist/server/constants.d.ts +3 -2
  83. package/dist/server/constants.js +10 -9
  84. package/dist/server/core/domain/entities/agent.js +4 -0
  85. package/dist/server/core/domain/source/source-schema.d.ts +6 -6
  86. package/dist/server/core/domain/transport/schemas.d.ts +4 -4
  87. package/dist/server/infra/connectors/mcp/claude-desktop-config-path.d.ts +20 -0
  88. package/dist/server/infra/connectors/mcp/claude-desktop-config-path.js +47 -0
  89. package/dist/server/infra/connectors/mcp/mcp-connector-config.d.ts +19 -0
  90. package/dist/server/infra/connectors/mcp/mcp-connector-config.js +9 -0
  91. package/dist/server/infra/connectors/mcp/mcp-connector.js +12 -3
  92. package/dist/server/infra/http/provider-model-fetchers.js +1 -0
  93. package/dist/server/infra/process/feature-handlers.js +13 -0
  94. package/dist/server/infra/project/project-registry.js +13 -1
  95. package/dist/server/infra/transport/handlers/locations-handler.d.ts +2 -0
  96. package/dist/server/infra/transport/handlers/locations-handler.js +16 -1
  97. package/dist/server/infra/transport/handlers/vc-handler.d.ts +0 -4
  98. package/dist/server/infra/transport/handlers/vc-handler.js +5 -16
  99. package/dist/server/templates/skill/SKILL.md +163 -0
  100. package/dist/server/utils/gitignore.d.ts +1 -0
  101. package/dist/server/utils/gitignore.js +36 -4
  102. package/dist/shared/types/agent.d.ts +2 -1
  103. package/dist/shared/types/agent.js +2 -0
  104. package/dist/tui/features/connectors/components/connectors-flow.js +7 -2
  105. package/oclif.manifest.json +503 -323
  106. package/package.json +2 -2
@@ -0,0 +1,233 @@
1
+ /* eslint-disable camelcase -- wizard builds YAML-shaped config objects (snake_case keys) */
2
+ import { checkbox, confirm, input, select } from '@inquirer/prompts';
3
+ import { Command } from '@oclif/core';
4
+ import chalk from 'chalk';
5
+ import { load } from 'js-yaml';
6
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { safeValidateSwarmConfig } from '../../../agent/infra/swarm/config/swarm-config-schema.js';
9
+ import { scaffoldConfig } from '../../../agent/infra/swarm/wizard/config-scaffolder.js';
10
+ import { detectProviders } from '../../../agent/infra/swarm/wizard/provider-detector.js';
11
+ import { EscBackError, runMemoryWizard, WizardCancelledError } from '../../../agent/infra/swarm/wizard/swarm-wizard.js';
12
+ import { createEscapeSignal, isEscBack, isForceExit, isPromptCancelled, wizardInputTheme, wizardSelectTheme } from '../../lib/prompt-utils.js';
13
+ /**
14
+ * Wrap a prompt call with ESC back-navigation support.
15
+ * If the user presses ESC, throws EscBackError so the wizard goes back one step.
16
+ * If the user presses Ctrl+C, re-throws so the command exits.
17
+ */
18
+ async function withEscBack(esc, fn) {
19
+ try {
20
+ return await fn(esc.signal);
21
+ }
22
+ catch (error) {
23
+ if (isEscBack(error)) {
24
+ esc.reset();
25
+ throw new EscBackError();
26
+ }
27
+ if (isForceExit(error)) {
28
+ throw error;
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ /**
34
+ * Create the real wizard prompts backed by @inquirer/prompts.
35
+ * Wires createEscapeSignal() for ESC back-navigation on every prompt.
36
+ */
37
+ function createWizardPrompts(esc) {
38
+ return {
39
+ async configureBudget() {
40
+ const amount = await withEscBack(esc, (signal) => input({
41
+ default: '50',
42
+ message: 'Monthly budget cap in dollars:',
43
+ theme: wizardInputTheme,
44
+ }, { signal }));
45
+ const cents = Math.round(Number.parseFloat(amount) * 100);
46
+ return { globalMonthlyCents: cents };
47
+ },
48
+ async configureEnrichment(providerIds) {
49
+ const providerList = providerIds.filter((id) => id !== 'byterover').join(', ');
50
+ console.log('');
51
+ console.log(chalk.bold('Enrichment Topology'));
52
+ console.log(chalk.dim('─'.repeat(50)));
53
+ console.log('');
54
+ console.log('The swarm can run providers in two modes:');
55
+ console.log('');
56
+ console.log(` ${chalk.cyan('Parallel')} (enrichment off)`);
57
+ console.log(' All providers search independently at the same time.');
58
+ console.log(' Fastest, but each provider only sees your original query.');
59
+ console.log('');
60
+ console.log(` ${chalk.cyan('Chained')} (enrichment on) ${chalk.green('← recommended')}`);
61
+ console.log(` ByteRover searches first, then feeds its results to ${providerList}.`);
62
+ console.log(' Slightly slower, but downstream providers get richer context');
63
+ console.log(' from ByteRover\'s structured knowledge.');
64
+ console.log('');
65
+ console.log(chalk.dim('How routing interacts with enrichment:'));
66
+ console.log(chalk.dim(' The router decides WHICH providers handle each query type.'));
67
+ console.log(chalk.dim(' Enrichment decides the ORDER they run in.'));
68
+ console.log(chalk.dim(' If a provider is excluded by routing, its enrichment edge is skipped.'));
69
+ console.log('');
70
+ return withEscBack(esc, (signal) => confirm({
71
+ default: true,
72
+ message: 'Enable enrichment (recommended)?',
73
+ }, { signal }));
74
+ },
75
+ async configureProvider(provider) {
76
+ const config = {};
77
+ switch (provider.id) {
78
+ case 'gbrain': {
79
+ config.repo_path = await withEscBack(esc, (signal) => input({
80
+ message: 'GBrain brain repository path (your notes/data repo, not the CLI checkout):',
81
+ theme: wizardInputTheme,
82
+ }, { signal }));
83
+ config.search_mode = await withEscBack(esc, (signal) => select({
84
+ choices: [
85
+ { description: 'Vector + keyword + RRF fusion (best recall, needs OPENAI_API_KEY)', name: 'hybrid (recommended)', value: 'hybrid' },
86
+ { description: 'Full-text search only (fast, no API key needed)', name: 'keyword', value: 'keyword' },
87
+ { description: 'Vector similarity only (needs OPENAI_API_KEY)', name: 'vector', value: 'vector' },
88
+ ],
89
+ default: 'hybrid',
90
+ message: 'Search mode:',
91
+ theme: wizardSelectTheme,
92
+ }, { signal }));
93
+ break;
94
+ }
95
+ case 'hindsight': {
96
+ config.connection_string = await withEscBack(esc, (signal) => input({
97
+ // eslint-disable-next-line no-template-curly-in-string -- literal ${VAR} placeholder for resolveEnvVars
98
+ default: '${HINDSIGHT_DB_URL}',
99
+ message: 'Hindsight connection string:',
100
+ theme: wizardInputTheme,
101
+ }, { signal }));
102
+ break;
103
+ }
104
+ case 'honcho': {
105
+ config.api_key = await withEscBack(esc, (signal) => input({
106
+ // eslint-disable-next-line no-template-curly-in-string -- literal ${VAR} placeholder for resolveEnvVars
107
+ default: '${HONCHO_API_KEY}',
108
+ message: 'Honcho API key (or env var reference):',
109
+ theme: wizardInputTheme,
110
+ }, { signal }));
111
+ config.app_id = await withEscBack(esc, (signal) => input({
112
+ message: 'Honcho app ID:',
113
+ theme: wizardInputTheme,
114
+ }, { signal }));
115
+ break;
116
+ }
117
+ case 'local-markdown': {
118
+ const path = await withEscBack(esc, (signal) => input({
119
+ default: provider.path,
120
+ message: 'Markdown folder path:',
121
+ theme: wizardInputTheme,
122
+ }, { signal }));
123
+ const name = await withEscBack(esc, (signal) => input({
124
+ message: 'Human-readable name for this folder:',
125
+ theme: wizardInputTheme,
126
+ }, { signal }));
127
+ config.folders = [{
128
+ follow_wikilinks: true,
129
+ name,
130
+ path,
131
+ read_only: true,
132
+ }];
133
+ break;
134
+ }
135
+ case 'obsidian': {
136
+ config.vault_path = await withEscBack(esc, (signal) => input({
137
+ default: provider.path,
138
+ message: 'Obsidian vault path:',
139
+ theme: wizardInputTheme,
140
+ }, { signal }));
141
+ break;
142
+ }
143
+ }
144
+ return config;
145
+ },
146
+ async confirmWrite(summary) {
147
+ console.log('\n' + chalk.bold('Memory Swarm Configuration Summary:'));
148
+ console.log(summary);
149
+ console.log('');
150
+ return withEscBack(esc, (signal) => confirm({
151
+ default: true,
152
+ message: 'Write to .brv/swarm/config.yaml?',
153
+ }, { signal }));
154
+ },
155
+ async selectProviders(detected) {
156
+ const choices = detected.map((p, index) => {
157
+ const status = p.detected
158
+ ? chalk.green('detected')
159
+ : chalk.dim('not found');
160
+ const detail = p.path ? ` — ${p.path}` : p.envVar ? ` (${p.envVar})` : '';
161
+ const count = p.noteCount ? ` (${p.noteCount} files)` : '';
162
+ return {
163
+ checked: p.detected,
164
+ disabled: p.id === 'byterover' ? '(always on)' : false,
165
+ name: `${p.id}${detail}${count} [${status}]`,
166
+ value: String(index),
167
+ };
168
+ });
169
+ return withEscBack(esc, (signal) => checkbox({
170
+ choices,
171
+ message: 'Select providers to enable:',
172
+ theme: wizardSelectTheme,
173
+ }, { signal }));
174
+ },
175
+ };
176
+ }
177
+ export default class SwarmOnboard extends Command {
178
+ static description = 'Set up memory swarm with interactive onboarding wizard';
179
+ static examples = [
180
+ '<%= config.bin %> swarm onboard',
181
+ ];
182
+ async run() {
183
+ this.log(chalk.bold('\nMemory Swarm Onboarding'));
184
+ this.log('Scanning for memory providers...\n');
185
+ const esc = createEscapeSignal();
186
+ try {
187
+ // Step 1: Detect providers
188
+ const detected = await detectProviders();
189
+ // Step 2: Run wizard
190
+ const prompts = createWizardPrompts(esc);
191
+ const answers = await runMemoryWizard(prompts, detected);
192
+ // Step 3: Scaffold config
193
+ const { warnings, yaml: configYaml } = scaffoldConfig(answers);
194
+ // Surface scaffolding warnings (e.g. duplicate vaults dropped)
195
+ for (const warning of warnings) {
196
+ this.log(chalk.yellow(`⚠ ${warning}`));
197
+ }
198
+ // Step 4: Write config file
199
+ const configDir = join(process.cwd(), '.brv', 'swarm');
200
+ if (!existsSync(configDir)) {
201
+ mkdirSync(configDir, { recursive: true });
202
+ }
203
+ const configPath = join(configDir, 'config.yaml');
204
+ writeFileSync(configPath, configYaml);
205
+ this.log(chalk.green(`\n✓ Config written to ${configPath}`));
206
+ // Step 5: Validate roundtrip
207
+ const parsed = load(configYaml);
208
+ const validation = safeValidateSwarmConfig(parsed);
209
+ if (validation.success) {
210
+ this.log(chalk.green('✓ Config validated successfully'));
211
+ }
212
+ else {
213
+ this.log(chalk.yellow('⚠ Config validation warning — check the generated file.'));
214
+ }
215
+ // Step 6: Suggest next step
216
+ this.log(`\nRun ${chalk.cyan('brv swarm status')} to verify provider health.`);
217
+ }
218
+ catch (error) {
219
+ if (error instanceof WizardCancelledError) {
220
+ this.log('\nWizard cancelled. No files were written.');
221
+ return;
222
+ }
223
+ if (isPromptCancelled(error)) {
224
+ this.log('\nWizard cancelled.');
225
+ return;
226
+ }
227
+ throw error;
228
+ }
229
+ finally {
230
+ esc.cleanup();
231
+ }
232
+ }
233
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SwarmQuery extends Command {
3
+ static args: {
4
+ query: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ explain: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
+ 'max-results': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,84 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { FileSystemService } from '../../../agent/infra/file-system/file-system-service.js';
3
+ import { formatQueryResults, formatQueryResultsExplain, formatQueryResultsJson } from '../../../agent/infra/swarm/cli/query-renderer.js';
4
+ import { loadSwarmConfig } from '../../../agent/infra/swarm/config/swarm-config-loader.js';
5
+ import { buildProvidersFromConfig } from '../../../agent/infra/swarm/provider-factory.js';
6
+ import { SwarmCoordinator } from '../../../agent/infra/swarm/swarm-coordinator.js';
7
+ import { validateSwarmProviders } from '../../../agent/infra/swarm/validation/config-validator.js';
8
+ import { createSearchKnowledgeService } from '../../../agent/infra/tools/implementations/search-knowledge-service.js';
9
+ export default class SwarmQuery extends Command {
10
+ static args = {
11
+ query: Args.string({ description: 'Natural language query to search across memory providers', required: true }),
12
+ };
13
+ static description = 'Query the memory swarm across all active providers';
14
+ static examples = [
15
+ '<%= config.bin %> swarm query "auth tokens"',
16
+ '<%= config.bin %> swarm query "what changed yesterday" --format json',
17
+ ];
18
+ static flags = {
19
+ explain: Flags.boolean({
20
+ description: 'Show classification, routing, and enrichment details (ignored with --format json, which always includes all metadata)',
21
+ }),
22
+ format: Flags.string({
23
+ char: 'f',
24
+ default: 'text',
25
+ description: 'Output format',
26
+ options: ['text', 'json'],
27
+ }),
28
+ 'max-results': Flags.integer({
29
+ char: 'n',
30
+ description: 'Maximum number of results',
31
+ }),
32
+ };
33
+ async run() {
34
+ const { args, flags } = await this.parse(SwarmQuery);
35
+ const isJson = flags.format === 'json';
36
+ try {
37
+ const config = await loadSwarmConfig(process.cwd());
38
+ // Build a real SearchKnowledgeService so ByteRover can search the context tree.
39
+ // FileSystemService is lightweight — safe to construct here for CLI use.
40
+ const workingDirectory = process.cwd();
41
+ const fileSystemService = new FileSystemService({ workingDirectory });
42
+ await fileSystemService.initialize();
43
+ const searchService = createSearchKnowledgeService(fileSystemService, {
44
+ baseDirectory: workingDirectory,
45
+ });
46
+ // Validate enrichment topology — structural errors block execution.
47
+ // Provider-specific errors (bad paths, missing API keys) are handled
48
+ // by health checks at runtime, preserving degraded-mode semantics.
49
+ const validation = await validateSwarmProviders(config);
50
+ const topologyErrors = validation.errors.filter((e) => e.provider === 'enrichment');
51
+ if (topologyErrors.length > 0) {
52
+ const messages = topologyErrors.map((e) => e.message);
53
+ throw new Error(`Invalid enrichment topology:\n ${messages.join('\n ')}`);
54
+ }
55
+ const providers = buildProvidersFromConfig(config, { searchService });
56
+ const coordinator = new SwarmCoordinator(providers, config);
57
+ // Run health checks so unhealthy providers are skipped
58
+ await coordinator.refreshHealth();
59
+ const result = await coordinator.execute({
60
+ maxResults: flags['max-results'],
61
+ query: args.query,
62
+ });
63
+ if (isJson) {
64
+ this.log(formatQueryResultsJson(result));
65
+ }
66
+ else if (flags.explain) {
67
+ this.log(formatQueryResultsExplain(result, args.query));
68
+ }
69
+ else {
70
+ this.log(formatQueryResults(result, args.query));
71
+ }
72
+ }
73
+ catch (error) {
74
+ const message = error instanceof Error ? error.message : String(error);
75
+ if (isJson) {
76
+ this.log(JSON.stringify({ error: message, success: false }));
77
+ }
78
+ else {
79
+ this.logToStderr(`Error: ${message}`);
80
+ this.exit(2);
81
+ }
82
+ }
83
+ }
84
+ }
@@ -0,0 +1,41 @@
1
+ import { Command } from '@oclif/core';
2
+ import { loadSwarmConfig } from '../../../agent/infra/swarm/config/swarm-config-loader.js';
3
+ import { detectProviders } from '../../../agent/infra/swarm/wizard/provider-detector.js';
4
+ type DetectedProviders = Awaited<ReturnType<typeof detectProviders>>;
5
+ type SwarmConfig = Awaited<ReturnType<typeof loadSwarmConfig>>;
6
+ /**
7
+ * Format enrichment edges for display.
8
+ */
9
+ export declare function formatEnrichmentEdges(edges: Array<{
10
+ from: string;
11
+ to: string;
12
+ }>): string[];
13
+ /**
14
+ * Collect configured paths for path-based providers so we can detect
15
+ * newly discovered paths that aren't in the config yet.
16
+ */
17
+ export declare function getConfiguredSwarmPaths(config: SwarmConfig): Set<string>;
18
+ /**
19
+ * Build user-facing suggestions for detected providers that are not yet configured.
20
+ */
21
+ export declare function findSwarmStatusSuggestions(config: SwarmConfig, detected: DetectedProviders): string[];
22
+ export default class SwarmStatus extends Command {
23
+ static description: string;
24
+ static examples: string[];
25
+ static flags: {
26
+ format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
27
+ };
28
+ run(): Promise<void>;
29
+ private findSuggestions;
30
+ private renderCascadeNote;
31
+ private renderEnrichmentTopology;
32
+ private renderProviderLine;
33
+ private renderProviderStatusLines;
34
+ private renderSuggestionLines;
35
+ private renderSwarmSummary;
36
+ private renderTextOutput;
37
+ private renderValidationErrors;
38
+ private renderValidationWarnings;
39
+ private renderWriteTargets;
40
+ }
41
+ export {};
@@ -0,0 +1,278 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { loadSwarmConfig } from '../../../agent/infra/swarm/config/swarm-config-loader.js';
4
+ import { validateSwarmProviders } from '../../../agent/infra/swarm/validation/config-validator.js';
5
+ import { detectProviders } from '../../../agent/infra/swarm/wizard/provider-detector.js';
6
+ /**
7
+ * Format enrichment edges for display.
8
+ */
9
+ export function formatEnrichmentEdges(edges) {
10
+ return edges.map((e) => `${e.from} → ${e.to}`);
11
+ }
12
+ /**
13
+ * Collect configured paths for path-based providers so we can detect
14
+ * newly discovered paths that aren't in the config yet.
15
+ */
16
+ export function getConfiguredSwarmPaths(config) {
17
+ const paths = new Set();
18
+ const { providers } = config;
19
+ if (providers.obsidian?.vaultPath) {
20
+ paths.add(providers.obsidian.vaultPath);
21
+ }
22
+ if (providers.localMarkdown?.folders) {
23
+ for (const folder of providers.localMarkdown.folders) {
24
+ paths.add(folder.path);
25
+ }
26
+ }
27
+ // GBrain: repoPath is the brain data repo, not the detected CLI checkout path — do not add here.
28
+ return paths;
29
+ }
30
+ /**
31
+ * Build user-facing suggestions for detected providers that are not yet configured.
32
+ */
33
+ export function findSwarmStatusSuggestions(config, detected) {
34
+ const suggestions = [];
35
+ const configuredPaths = getConfiguredSwarmPaths(config);
36
+ for (const provider of detected) {
37
+ if (!provider.detected)
38
+ continue;
39
+ if (provider.id === 'byterover')
40
+ continue;
41
+ // GBrain: `provider.path` is the CLI/source checkout; config uses `repoPath` for brain data — never compare.
42
+ if (provider.id === 'gbrain') {
43
+ if (!config.providers.gbrain) {
44
+ const detail = provider.path ? ` at ${provider.path}` : '';
45
+ suggestions.push(`Found gbrain${detail} — not in config. Run \`brv swarm onboard\` to add it.`);
46
+ }
47
+ continue;
48
+ }
49
+ if (provider.path) {
50
+ if (!configuredPaths.has(provider.path)) {
51
+ suggestions.push(`Found ${provider.id} at ${provider.path} — not in config. Run \`brv swarm onboard\` to add it.`);
52
+ }
53
+ continue;
54
+ }
55
+ const providerKey = provider.id === 'local-markdown' ? 'localMarkdown' : provider.id;
56
+ const configured = config.providers[providerKey];
57
+ if (!configured) {
58
+ const detail = provider.envVar ? `(${provider.envVar} is set)` : '';
59
+ suggestions.push(`Found ${provider.id} ${detail} — not in config. Run \`brv swarm onboard\` to add it.`);
60
+ }
61
+ }
62
+ return suggestions;
63
+ }
64
+ export default class SwarmStatus extends Command {
65
+ static description = 'Show memory swarm provider health and connection status';
66
+ static examples = [
67
+ '<%= config.bin %> swarm status',
68
+ '<%= config.bin %> swarm status --format json',
69
+ ];
70
+ static flags = {
71
+ format: Flags.string({
72
+ char: 'f',
73
+ default: 'text',
74
+ description: 'Output format',
75
+ options: ['text', 'json'],
76
+ }),
77
+ };
78
+ async run() {
79
+ const { flags } = await this.parse(SwarmStatus);
80
+ const isJson = flags.format === 'json';
81
+ try {
82
+ // Load config
83
+ const config = await loadSwarmConfig(process.cwd());
84
+ // Run runtime validation
85
+ const validation = await validateSwarmProviders(config);
86
+ // Detect unconfigured providers (proactive suggestions)
87
+ const detected = await detectProviders();
88
+ const suggestions = findSwarmStatusSuggestions(config, detected);
89
+ if (isJson) {
90
+ this.logJson({
91
+ config: {
92
+ enrichment: { edges: config.enrichment?.edges ?? [] },
93
+ providers: Object.keys(config.providers).filter((k) => config.providers[k]?.enabled),
94
+ },
95
+ errors: validation.errors,
96
+ suggestions,
97
+ warnings: validation.warnings,
98
+ });
99
+ }
100
+ else {
101
+ this.renderTextOutput(config, validation, suggestions);
102
+ }
103
+ }
104
+ catch (error) {
105
+ if (isJson) {
106
+ this.logJson({ error: error.message, success: false });
107
+ }
108
+ else {
109
+ this.log(chalk.red(error.message));
110
+ }
111
+ }
112
+ }
113
+ findSuggestions(config, detected) {
114
+ return findSwarmStatusSuggestions(config, detected);
115
+ }
116
+ renderCascadeNote(validation) {
117
+ if (!validation.cascadeNote)
118
+ return;
119
+ this.log(`\n${chalk.dim('Note:')} ${validation.cascadeNote}`);
120
+ }
121
+ renderEnrichmentTopology(config) {
122
+ const edges = config.enrichment?.edges ?? [];
123
+ if (edges.length === 0)
124
+ return;
125
+ const lines = formatEnrichmentEdges(edges);
126
+ this.log(`\n${chalk.cyan('Enrichment Topology')}:`);
127
+ for (const line of lines) {
128
+ this.log(` ${line}`);
129
+ }
130
+ }
131
+ renderProviderLine(name, ok, detail, status) {
132
+ if (status === 'disabled') {
133
+ this.log(` ${chalk.dim('—')} ${name.padEnd(15)} ${chalk.dim(detail)}`);
134
+ }
135
+ else if (status === 'warning') {
136
+ this.log(` ${chalk.yellow('⚠')} ${name.padEnd(15)} ${detail}`);
137
+ }
138
+ else if (ok) {
139
+ this.log(` ${chalk.green('✓')} ${name.padEnd(15)} ${detail}`);
140
+ }
141
+ else {
142
+ this.log(` ${chalk.red('✗')} ${name.padEnd(15)} ${detail}`);
143
+ }
144
+ }
145
+ renderProviderStatusLines(config, validation) {
146
+ const { providers } = config;
147
+ this.renderProviderLine('ByteRover', providers.byterover.enabled, 'context-tree (always on)');
148
+ if (providers.obsidian) {
149
+ const hasError = validation.errors.some((e) => e.provider === 'obsidian');
150
+ const hasWarning = validation.warnings.some((w) => w.provider === 'obsidian');
151
+ this.renderProviderLine('Obsidian', providers.obsidian.enabled && !hasError, providers.obsidian.vaultPath, hasWarning ? 'warning' : undefined);
152
+ }
153
+ else {
154
+ this.renderProviderLine('Obsidian', false, 'not configured', 'disabled');
155
+ }
156
+ if (providers.localMarkdown) {
157
+ const hasError = validation.errors.some((e) => e.provider === 'local-markdown');
158
+ const folderCount = providers.localMarkdown.folders.length;
159
+ this.renderProviderLine('Local .md', providers.localMarkdown.enabled && !hasError, `${folderCount} folder(s)`);
160
+ }
161
+ else {
162
+ this.renderProviderLine('Local .md', false, 'not configured', 'disabled');
163
+ }
164
+ // Honcho and Hindsight temporarily disabled — adapters coming in Phase 3.
165
+ // When re-enabled, uncomment these blocks:
166
+ // if (providers.honcho) {
167
+ // const hasError = validation.errors.some((e) => e.provider === 'honcho')
168
+ // this.renderProviderLine('Honcho', providers.honcho.enabled && !hasError, 'cloud API')
169
+ // } else {
170
+ // this.renderProviderLine('Honcho', false, 'not configured', 'disabled')
171
+ // }
172
+ // if (providers.hindsight) {
173
+ // const hasError = validation.errors.some((e) => e.provider === 'hindsight')
174
+ // this.renderProviderLine('Hindsight', providers.hindsight.enabled && !hasError, 'Postgres')
175
+ // } else {
176
+ // this.renderProviderLine('Hindsight', false, 'not configured', 'disabled')
177
+ // }
178
+ if (providers.gbrain) {
179
+ const hasError = validation.errors.some((e) => e.provider === 'gbrain');
180
+ this.renderProviderLine('GBrain', providers.gbrain.enabled && !hasError, providers.gbrain.repoPath);
181
+ }
182
+ else {
183
+ this.renderProviderLine('GBrain', false, 'not configured', 'disabled');
184
+ }
185
+ if (providers.memoryWiki) {
186
+ const hasError = validation.errors.some((e) => e.provider === 'memory-wiki');
187
+ this.renderProviderLine('Memory Wiki', providers.memoryWiki.enabled && !hasError, providers.memoryWiki.vaultPath);
188
+ }
189
+ else {
190
+ this.renderProviderLine('Memory Wiki', false, 'not configured', 'disabled');
191
+ }
192
+ }
193
+ renderSuggestionLines(suggestions) {
194
+ if (suggestions.length === 0)
195
+ return;
196
+ this.log(`\n${chalk.cyan('Suggestions')}:`);
197
+ for (const suggestion of suggestions) {
198
+ this.log(` • ${suggestion}`);
199
+ }
200
+ }
201
+ renderSwarmSummary(config, validation) {
202
+ const { providers } = config;
203
+ const enabledCount = [
204
+ providers.byterover.enabled,
205
+ providers.obsidian?.enabled,
206
+ providers.localMarkdown?.enabled,
207
+ // Honcho and Hindsight temporarily disabled
208
+ providers.gbrain?.enabled,
209
+ providers.memoryWiki?.enabled,
210
+ ].filter(Boolean).length;
211
+ const totalProviders = [
212
+ true, // byterover always counts
213
+ providers.obsidian !== undefined,
214
+ providers.localMarkdown !== undefined,
215
+ providers.gbrain !== undefined,
216
+ providers.memoryWiki !== undefined,
217
+ ].filter(Boolean).length;
218
+ const errorCount = validation.errors.length;
219
+ const status = errorCount === 0
220
+ ? chalk.green('operational')
221
+ : chalk.yellow('degraded');
222
+ this.log(`\nSwarm is ${status} (${enabledCount}/${totalProviders} providers configured).`);
223
+ }
224
+ renderTextOutput(config, validation, suggestions) {
225
+ this.log(chalk.bold('\nMemory Swarm Health Check'));
226
+ this.log('═'.repeat(40));
227
+ this.renderProviderStatusLines(config, validation);
228
+ this.renderEnrichmentTopology(config);
229
+ this.renderWriteTargets(config);
230
+ this.renderValidationErrors(validation);
231
+ this.renderValidationWarnings(validation);
232
+ this.renderCascadeNote(validation);
233
+ this.renderSuggestionLines(suggestions);
234
+ this.renderSwarmSummary(config, validation);
235
+ }
236
+ renderValidationErrors(validation) {
237
+ if (validation.errors.length === 0)
238
+ return;
239
+ this.log(`\n${chalk.red('Errors')} (${validation.errors.length}):`);
240
+ for (const [i, error] of validation.errors.entries()) {
241
+ this.log(` ${i + 1}. ${error.provider ?? 'config'}: ${error.message}`);
242
+ if (error.suggestion) {
243
+ this.log(` ${chalk.dim(error.suggestion)}`);
244
+ }
245
+ }
246
+ }
247
+ renderValidationWarnings(validation) {
248
+ if (validation.warnings.length === 0)
249
+ return;
250
+ this.log(`\n${chalk.yellow('Warnings')} (${validation.warnings.length}):`);
251
+ for (const [i, warning] of validation.warnings.entries()) {
252
+ this.log(` ${i + 1}. ${warning.provider ?? 'config'}: ${warning.message}`);
253
+ }
254
+ }
255
+ renderWriteTargets(config) {
256
+ const { providers } = config;
257
+ const targets = [];
258
+ if (providers.gbrain?.enabled) {
259
+ targets.push('gbrain (entity, general)');
260
+ }
261
+ if (providers.localMarkdown?.enabled) {
262
+ for (const folder of providers.localMarkdown.folders) {
263
+ if (!folder.readOnly) {
264
+ targets.push(`local-markdown:${folder.name} (note, general)`);
265
+ }
266
+ }
267
+ }
268
+ if (providers.memoryWiki?.enabled) {
269
+ targets.push(`memory-wiki (${providers.memoryWiki.writePageType ?? 'concept'}, entity)`);
270
+ }
271
+ if (targets.length === 0)
272
+ return;
273
+ this.log(`\n${chalk.cyan('Write Targets')}:`);
274
+ for (const target of targets) {
275
+ this.log(` ${target}`);
276
+ }
277
+ }
278
+ }
@@ -66,5 +66,6 @@ export declare const OVERVIEW_EXTENSION = ".overview.md";
66
66
  export declare const MANIFEST_FILE = "_manifest.json";
67
67
  export declare const ARCHIVE_IMPORTANCE_THRESHOLD = 35;
68
68
  export declare const DEFAULT_GHOST_CUE_MAX_TOKENS = 220;
69
- /** .gitignore content for the context tree ignore derived artifacts only */
70
- export declare const CONTEXT_TREE_GITIGNORE = "# Derived artifacts \u2014 do not track\n.gitignore\n.snapshot.json\n_manifest.json\n_index.md\n*.abstract.md\n*.overview.md\n";
69
+ /** Patterns the context-tree .gitignore must contain (derived artifacts only). */
70
+ export declare const CONTEXT_TREE_GITIGNORE_PATTERNS: string[];
71
+ export declare const CONTEXT_TREE_GITIGNORE_HEADER = "# Derived artifacts \u2014 do not track";