@selfagency/beans-mcp 0.1.1 → 0.1.2

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 (44) hide show
  1. package/{dist/beans-mcp-server.cjs → beans-mcp-server.cjs} +8 -4
  2. package/{dist/index.cjs → index.cjs} +8 -4
  3. package/{dist/index.d.ts → index.d.ts} +2 -1
  4. package/{dist/index.js → index.js} +8 -4
  5. package/package.json +27 -64
  6. package/.beans.yml +0 -6
  7. package/.claude/settings.local.json +0 -18
  8. package/.editorconfig +0 -13
  9. package/.github/workflows/release.yml +0 -235
  10. package/.github/workflows/test.yml +0 -80
  11. package/.husky/pre-commit +0 -1
  12. package/.nvmrc +0 -1
  13. package/.oxfmtrc.json +0 -11
  14. package/.oxlintrc.json +0 -37
  15. package/.vscode/settings.json +0 -3
  16. package/CHANGELOG.md +0 -140
  17. package/CONTRIBUTING.md +0 -139
  18. package/dist/README.md +0 -307
  19. package/dist/beans-mcp-server.cjs.map +0 -1
  20. package/dist/index.cjs.map +0 -1
  21. package/dist/index.js.map +0 -1
  22. package/dist/package.json +0 -43
  23. package/pnpm-workspace.yaml +0 -2
  24. package/scripts/release.js +0 -433
  25. package/scripts/write-dist-package.js +0 -53
  26. package/src/cli.ts +0 -14
  27. package/src/index.ts +0 -21
  28. package/src/internal/graphql.ts +0 -33
  29. package/src/internal/queryHelpers.ts +0 -157
  30. package/src/server/BeansMcpServer.ts +0 -600
  31. package/src/server/backend.ts +0 -358
  32. package/src/test/BeansMcpServer.test.ts +0 -514
  33. package/src/test/handlers.unit.test.ts +0 -184
  34. package/src/test/parseCliArgs.test.ts +0 -69
  35. package/src/test/protocol.e2e.test.ts +0 -884
  36. package/src/test/queryHelpers.test.ts +0 -524
  37. package/src/test/startBeansMcpServer.test.ts +0 -146
  38. package/src/test/tools-integration.test.ts +0 -912
  39. package/src/test/utils.test.ts +0 -80
  40. package/src/types.ts +0 -46
  41. package/src/utils.ts +0 -20
  42. package/tsconfig.json +0 -24
  43. package/tsup.config.ts +0 -42
  44. package/vitest.config.ts +0 -18
@@ -1,600 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { z } from 'zod';
3
- import { handleQueryOperation, sortBeans } from '../internal/queryHelpers';
4
- import {
5
- DEFAULT_MCP_PORT,
6
- MAX_DESCRIPTION_LENGTH,
7
- MAX_ID_LENGTH,
8
- MAX_METADATA_LENGTH,
9
- MAX_PATH_LENGTH,
10
- MAX_TITLE_LENGTH,
11
- } from '../types';
12
- import { makeTextAndStructured } from '../utils';
13
- import type { BackendInterface } from './backend';
14
-
15
- export { sortBeans };
16
-
17
- // Exported test seam: get a bean by id with consistent error messages
18
- export async function getBeanById(backend: BackendInterface, beanId: string) {
19
- try {
20
- const beans = await backend.list();
21
- const found = beans.find(b => b.id === beanId);
22
- if (!found) {
23
- throw new Error(`Bean not found: ${beanId}`);
24
- }
25
- return found;
26
- } catch (error) {
27
- throw new Error(`Failed to fetch bean ${beanId}: ${(error as Error).message}`);
28
- }
29
- }
30
-
31
- // Exported handler factories so unit tests can call handlers directly.
32
- export function initHandler(backend: BackendInterface) {
33
- return async ({ prefix }: { prefix?: string }) => {
34
- const result = await backend.init(prefix);
35
- return makeTextAndStructured(result);
36
- };
37
- }
38
-
39
- export function viewHandler(backend: BackendInterface) {
40
- return async ({ beanId }: { beanId: string }) => makeTextAndStructured({ bean: await getBeanById(backend, beanId) });
41
- }
42
-
43
- export function createHandler(backend: BackendInterface) {
44
- return async (input: {
45
- title: string;
46
- type: string;
47
- status?: string;
48
- priority?: string;
49
- description?: string;
50
- parent?: string;
51
- }) => makeTextAndStructured({ bean: await backend.create(input) });
52
- }
53
-
54
- export function editHandler(backend: BackendInterface) {
55
- return async ({
56
- beanId,
57
- ...updates
58
- }: {
59
- beanId: string;
60
- status?: string;
61
- type?: string;
62
- priority?: string;
63
- parent?: string;
64
- clearParent?: boolean;
65
- blocking?: string[];
66
- blockedBy?: string[];
67
- }) => makeTextAndStructured({ bean: await backend.update(beanId, updates) });
68
- }
69
-
70
- export function reopenHandler(backend: BackendInterface) {
71
- return async ({
72
- beanId,
73
- requiredCurrentStatus,
74
- targetStatus,
75
- }: {
76
- beanId: string;
77
- requiredCurrentStatus: 'completed' | 'scrapped';
78
- targetStatus: string;
79
- }) => {
80
- const bean = await getBeanById(backend, beanId);
81
- if (bean.status !== requiredCurrentStatus) {
82
- throw new Error(`Bean ${beanId} is not ${requiredCurrentStatus}`);
83
- }
84
- return makeTextAndStructured({
85
- bean: await backend.update(beanId, { status: targetStatus }),
86
- });
87
- };
88
- }
89
-
90
- export function updateHandler(backend: BackendInterface) {
91
- return async (input: {
92
- beanId: string;
93
- status?: string;
94
- type?: string;
95
- priority?: string;
96
- parent?: string;
97
- clearParent?: boolean;
98
- blocking?: string[];
99
- blockedBy?: string[];
100
- }) =>
101
- makeTextAndStructured({
102
- bean: await backend.update(input.beanId, {
103
- status: input.status,
104
- type: input.type,
105
- priority: input.priority,
106
- parent: input.parent,
107
- clearParent: input.clearParent,
108
- blocking: input.blocking,
109
- blockedBy: input.blockedBy,
110
- }),
111
- });
112
- }
113
-
114
- export function deleteHandler(backend: BackendInterface) {
115
- return async ({ beanId, force }: { beanId: string; force: boolean }) => {
116
- const bean = await getBeanById(backend, beanId);
117
- if (!force && bean.status !== 'draft' && bean.status !== 'scrapped') {
118
- throw new Error('Only draft and scrapped beans are deletable unless force=true');
119
- }
120
- return makeTextAndStructured(await backend.delete(beanId));
121
- };
122
- }
123
-
124
- export function queryHandler(backend: BackendInterface) {
125
- return async (opts: {
126
- operation: 'refresh' | 'filter' | 'search' | 'sort' | 'llm_context' | 'open_config';
127
- mode?: 'status-priority-type-title' | 'updated' | 'created' | 'id';
128
- statuses?: string[] | null;
129
- types?: string[] | null;
130
- search?: string;
131
- includeClosed?: boolean;
132
- tags?: string[] | null;
133
- writeToWorkspaceInstructions?: boolean;
134
- }) => handleQueryOperation(backend, opts);
135
- }
136
-
137
- export function beanFileHandler(backend: BackendInterface) {
138
- return async ({
139
- operation,
140
- path,
141
- content,
142
- overwrite,
143
- }: {
144
- operation: 'read' | 'edit' | 'create' | 'delete';
145
- path: string;
146
- content?: string;
147
- overwrite?: boolean;
148
- }) => {
149
- if (operation === 'read') {
150
- return makeTextAndStructured(await backend.readBeanFile(path));
151
- }
152
- if (operation === 'edit') {
153
- return makeTextAndStructured(await backend.editBeanFile(path, content || ''));
154
- }
155
- if (operation === 'create') {
156
- return makeTextAndStructured(await backend.createBeanFile(path, content || '', { overwrite }));
157
- }
158
- if (operation === 'delete') {
159
- return makeTextAndStructured(await backend.deleteBeanFile(path));
160
- }
161
- throw new Error('Unsupported operation');
162
- };
163
- }
164
-
165
- export function outputHandler(backend: BackendInterface) {
166
- return async ({ operation, lines }: { operation: 'read' | 'show'; lines?: number }) => {
167
- if (operation === 'read') {
168
- return makeTextAndStructured(await backend.readOutputLog({ lines }));
169
- }
170
- return makeTextAndStructured({
171
- message:
172
- 'When using VS Code UI, run command `Beans: Show Output` to open extension logs. In MCP mode, rely on tool error outputs and host logs.',
173
- });
174
- };
175
- }
176
- function registerTools(server: McpServer, backend: BackendInterface): void {
177
- // register exported handlers bound to this backend
178
-
179
- server.registerTool(
180
- 'beans_init',
181
- {
182
- title: 'Initialize Beans Workspace',
183
- description: 'Initialize Beans in the current workspace, equivalent to the extension init command.',
184
- inputSchema: z.object({
185
- prefix: z.string().max(32).optional().describe('Optional workspace prefix for bean IDs'),
186
- }),
187
- annotations: {
188
- readOnlyHint: false,
189
- destructiveHint: false,
190
- idempotentHint: true,
191
- openWorldHint: false,
192
- },
193
- },
194
- initHandler(backend),
195
- );
196
-
197
- server.registerTool(
198
- 'beans_view',
199
- {
200
- title: 'View Bean',
201
- description: 'Fetch full bean details by ID.',
202
- inputSchema: z.object({ beanId: z.string().min(1).max(MAX_ID_LENGTH) }),
203
- annotations: {
204
- readOnlyHint: true,
205
- destructiveHint: false,
206
- idempotentHint: true,
207
- openWorldHint: false,
208
- },
209
- },
210
- viewHandler(backend),
211
- );
212
-
213
- server.registerTool(
214
- 'beans_create',
215
- {
216
- title: 'Create Bean',
217
- description: 'Create a new bean.',
218
- inputSchema: z.object({
219
- title: z.string().min(1).max(MAX_TITLE_LENGTH),
220
- type: z.string().min(1).max(MAX_METADATA_LENGTH),
221
- status: z.string().max(MAX_METADATA_LENGTH).optional(),
222
- priority: z.string().max(MAX_METADATA_LENGTH).optional(),
223
- description: z.string().max(MAX_DESCRIPTION_LENGTH).optional(),
224
- parent: z.string().max(MAX_ID_LENGTH).optional(),
225
- }),
226
- annotations: {
227
- readOnlyHint: false,
228
- destructiveHint: false,
229
- idempotentHint: false,
230
- openWorldHint: false,
231
- },
232
- },
233
- createHandler(backend),
234
- );
235
-
236
- server.registerTool(
237
- 'beans_edit',
238
- {
239
- title: 'Edit Bean Metadata',
240
- description: 'Update bean metadata fields (status/type/priority/parent/blocking).',
241
- inputSchema: z.object({
242
- beanId: z.string().min(1).max(MAX_ID_LENGTH),
243
- status: z.string().max(MAX_METADATA_LENGTH).optional(),
244
- type: z.string().max(MAX_METADATA_LENGTH).optional(),
245
- priority: z.string().max(MAX_METADATA_LENGTH).optional(),
246
- parent: z.string().max(MAX_ID_LENGTH).optional(),
247
- clearParent: z.boolean().optional(),
248
- blocking: z.array(z.string().max(MAX_ID_LENGTH)).optional(),
249
- blockedBy: z.array(z.string().max(MAX_ID_LENGTH)).optional(),
250
- }),
251
- annotations: {
252
- readOnlyHint: false,
253
- destructiveHint: false,
254
- idempotentHint: false,
255
- openWorldHint: false,
256
- },
257
- },
258
- editHandler(backend),
259
- );
260
-
261
- server.registerTool(
262
- 'beans_reopen',
263
- {
264
- title: 'Reopen Bean',
265
- description: 'Reopen a completed or scrapped bean into a non-closed status.',
266
- inputSchema: z.object({
267
- beanId: z.string().min(1).max(MAX_ID_LENGTH),
268
- requiredCurrentStatus: z.enum(['completed', 'scrapped']),
269
- targetStatus: z.string().max(MAX_METADATA_LENGTH).default('todo'),
270
- }),
271
- annotations: {
272
- readOnlyHint: false,
273
- destructiveHint: false,
274
- idempotentHint: false,
275
- openWorldHint: false,
276
- },
277
- },
278
- reopenHandler(backend),
279
- );
280
-
281
- server.registerTool(
282
- 'beans_update',
283
- {
284
- title: 'Update Bean',
285
- description:
286
- 'Update bean metadata fields (status/type/priority/parent/blocking). Consolidated replacement for per-field update tools.',
287
- inputSchema: z.object({
288
- beanId: z.string().min(1).max(MAX_ID_LENGTH),
289
- status: z.string().max(MAX_METADATA_LENGTH).optional(),
290
- type: z.string().max(MAX_METADATA_LENGTH).optional(),
291
- priority: z.string().max(MAX_METADATA_LENGTH).optional(),
292
- parent: z.string().max(MAX_ID_LENGTH).optional(),
293
- clearParent: z.boolean().optional(),
294
- blocking: z.array(z.string().max(MAX_ID_LENGTH)).optional(),
295
- blockedBy: z.array(z.string().max(MAX_ID_LENGTH)).optional(),
296
- }),
297
- annotations: {
298
- readOnlyHint: false,
299
- destructiveHint: false,
300
- idempotentHint: false,
301
- openWorldHint: false,
302
- },
303
- },
304
- updateHandler(backend),
305
- );
306
-
307
- server.registerTool(
308
- 'beans_delete',
309
- {
310
- title: 'Delete Bean',
311
- description: 'Delete a bean (intended for draft/scrapped beans).',
312
- inputSchema: z.object({
313
- beanId: z.string().min(1).max(MAX_ID_LENGTH),
314
- force: z.boolean().default(false),
315
- }),
316
- annotations: {
317
- readOnlyHint: false,
318
- destructiveHint: true,
319
- idempotentHint: false,
320
- openWorldHint: false,
321
- },
322
- },
323
- deleteHandler(backend),
324
- );
325
-
326
- server.registerTool(
327
- 'beans_query',
328
- {
329
- title: 'Query Beans',
330
- description: 'Unified query tool for refresh, filter, search, and sort operations.',
331
- inputSchema: z.object({
332
- operation: z.enum(['refresh', 'filter', 'search', 'sort', 'llm_context', 'open_config']).default('refresh'),
333
- mode: z.enum(['status-priority-type-title', 'updated', 'created', 'id']).optional(),
334
- statuses: z.array(z.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
335
- types: z.array(z.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
336
- search: z.string().max(MAX_TITLE_LENGTH).optional(),
337
- includeClosed: z.boolean().optional(),
338
- tags: z.array(z.string().max(MAX_METADATA_LENGTH)).nullable().optional(),
339
- writeToWorkspaceInstructions: z.boolean().optional(),
340
- }),
341
- annotations: {
342
- readOnlyHint: true,
343
- destructiveHint: false,
344
- idempotentHint: true,
345
- openWorldHint: false,
346
- },
347
- },
348
- queryHandler(backend),
349
- );
350
-
351
- server.registerTool(
352
- 'beans_bean_file',
353
- {
354
- title: 'Bean File Operations',
355
- description: 'Read, create, edit, or delete files under .beans (operation param).',
356
- inputSchema: z.object({
357
- operation: z.enum(['read', 'edit', 'create', 'delete']),
358
- path: z.string().min(1).max(MAX_PATH_LENGTH),
359
- content: z.string().max(MAX_DESCRIPTION_LENGTH).optional(),
360
- overwrite: z.boolean().optional(),
361
- }),
362
- annotations: {
363
- readOnlyHint: false,
364
- destructiveHint: false,
365
- idempotentHint: false,
366
- openWorldHint: false,
367
- },
368
- },
369
- beanFileHandler(backend),
370
- );
371
-
372
- server.registerTool(
373
- 'beans_output',
374
- {
375
- title: 'Beans Output Tools',
376
- description: 'Read extension output log or show guidance (operation param).',
377
- inputSchema: z.object({
378
- operation: z.enum(['read', 'show']).default('read'),
379
- lines: z.number().int().min(1).max(5000).optional(),
380
- }),
381
- annotations: {
382
- readOnlyHint: true,
383
- destructiveHint: false,
384
- idempotentHint: true,
385
- openWorldHint: false,
386
- },
387
- },
388
- outputHandler(backend),
389
- );
390
- }
391
-
392
- /**
393
- * Thin delegation wrapper whose inner backend can be hot-swapped without
394
- * re-registering tools. Used by startBeansMcpServer to update the workspace
395
- * after MCP roots are discovered from the connected client.
396
- */
397
- export class MutableBackend implements BackendInterface {
398
- constructor(private inner: BackendInterface) {}
399
-
400
- setInner(b: BackendInterface) {
401
- this.inner = b;
402
- }
403
-
404
- init(prefix?: string) {
405
- return this.inner.init(prefix);
406
- }
407
- list(opts?: Parameters<BackendInterface['list']>[0]) {
408
- return this.inner.list(opts);
409
- }
410
- create(input: Parameters<BackendInterface['create']>[0]) {
411
- return this.inner.create(input);
412
- }
413
- update(id: string, updates: Parameters<BackendInterface['update']>[1]) {
414
- return this.inner.update(id, updates);
415
- }
416
- delete(id: string) {
417
- return this.inner.delete(id);
418
- }
419
- openConfig() {
420
- return this.inner.openConfig();
421
- }
422
- graphqlSchema() {
423
- return this.inner.graphqlSchema();
424
- }
425
- readOutputLog(opts?: Parameters<BackendInterface['readOutputLog']>[0]) {
426
- return this.inner.readOutputLog(opts);
427
- }
428
- readBeanFile(path: string) {
429
- return this.inner.readBeanFile(path);
430
- }
431
- editBeanFile(path: string, content: string) {
432
- return this.inner.editBeanFile(path, content);
433
- }
434
- createBeanFile(path: string, content: string, opts?: Parameters<BackendInterface['createBeanFile']>[2]) {
435
- return this.inner.createBeanFile(path, content, opts);
436
- }
437
- deleteBeanFile(path: string) {
438
- return this.inner.deleteBeanFile(path);
439
- }
440
- }
441
-
442
- /**
443
- * Ask the connected client for its MCP roots and return the first local
444
- * filesystem path, or null if the client declares no roots or does not
445
- * support the roots capability.
446
- */
447
- export async function resolveWorkspaceFromRoots(server: McpServer): Promise<string | null> {
448
- try {
449
- const { roots } = await server.server.listRoots();
450
- for (const root of roots) {
451
- if (root.uri.startsWith('file://')) {
452
- return new URL(root.uri).pathname;
453
- }
454
- }
455
- return null;
456
- } catch {
457
- return null;
458
- }
459
- }
460
-
461
- export async function createBeansMcpServer(opts: {
462
- workspaceRoot: string;
463
- cliPath?: string;
464
- name?: string;
465
- version?: string;
466
- logDir?: string;
467
- backend?: BackendInterface;
468
- }): Promise<{ server: McpServer; backend: BackendInterface }> {
469
- const { BeansCliBackend } = await import('./backend');
470
-
471
- const backend = opts.backend || new BeansCliBackend(opts.workspaceRoot, opts.cliPath || 'beans', opts.logDir);
472
-
473
- const server = new McpServer({
474
- name: opts.name || 'beans-mcp-server',
475
- version: opts.version || '0.1.0',
476
- });
477
-
478
- registerTools(server, backend);
479
-
480
- return { server, backend };
481
- }
482
-
483
- const HELP_TEXT = `Usage: beans-mcp-server [workspace-root] [options]
484
-
485
- Arguments:
486
- workspace-root Path to workspace root.
487
- Optional: if omitted, the server first asks the
488
- connected MCP client for its declared roots and
489
- falls back to the current directory.
490
-
491
- Options:
492
- --workspace <path> Alias for workspace-root positional argument
493
- --workspace-root <p> Alias for workspace-root positional argument
494
- --cli-path <path> Path to the beans CLI executable (default: beans)
495
- --port <number> MCP server port (default: ${DEFAULT_MCP_PORT})
496
- --log-dir <path> Directory for log output (default: workspace root)
497
- -h, --help Show this help message
498
-
499
- Workspace resolution order (highest to lowest priority):
500
- 1. --workspace-root CLI argument (or positional)
501
- 2. MCP roots declared by the connected client
502
- 3. Current working directory
503
-
504
- Environment variables:
505
- BEANS_MCP_PORT Override the default MCP port
506
- BEANS_VSCODE_MCP_PORT Override the default MCP port (VS Code extension)
507
- `;
508
-
509
- export function parseCliArgs(argv: string[]): {
510
- workspaceRoot: string;
511
- /** True when the caller explicitly supplied --workspace-root (or the positional arg). */
512
- workspaceExplicit: boolean;
513
- cliPath: string;
514
- port: number;
515
- logDir?: string;
516
- } {
517
- if (argv.includes('--help') || argv.includes('-h')) {
518
- process.stdout.write(HELP_TEXT);
519
- process.exit(0);
520
- }
521
-
522
- let workspaceRoot = process.cwd();
523
- let workspaceExplicit = false;
524
- let cliPath = 'beans';
525
- const envPort = Number.parseInt(process.env.BEANS_VSCODE_MCP_PORT || process.env.BEANS_MCP_PORT || '', 10);
526
- let port = Number.isInteger(envPort) && envPort > 0 ? envPort : DEFAULT_MCP_PORT;
527
- let logDir: string | undefined;
528
-
529
- for (let i = 0; i < argv.length; i += 1) {
530
- const arg = argv[i];
531
- if ((arg === '--workspace' || arg === '--workspace-root') && argv[i + 1]) {
532
- workspaceRoot = argv[i + 1]!;
533
- workspaceExplicit = true;
534
- i += 1;
535
- } else if (arg === '--cli-path' && argv[i + 1]) {
536
- cliPath = argv[i + 1]!;
537
- if (/[\s;&|><$(){}[\]`]/.test(cliPath)) {
538
- throw new Error('Invalid CLI path');
539
- }
540
- i += 1;
541
- } else if (arg === '--port' && argv[i + 1]) {
542
- const parsedPort = Number.parseInt(argv[i + 1]!, 10);
543
- if (Number.isInteger(parsedPort) && parsedPort > 0) {
544
- port = parsedPort;
545
- }
546
- i += 1;
547
- } else if (arg === '--log-dir' && argv[i + 1]) {
548
- logDir = argv[i + 1]!;
549
- i += 1;
550
- } else if (!arg.startsWith('-') && i === 0) {
551
- // positional workspace root
552
- workspaceRoot = arg;
553
- workspaceExplicit = true;
554
- }
555
- }
556
-
557
- // default logDir to the workspace root when not provided
558
- if (!logDir) {
559
- logDir = workspaceRoot;
560
- }
561
-
562
- return { workspaceRoot, workspaceExplicit, cliPath, port, logDir };
563
- }
564
-
565
- export async function startBeansMcpServer(
566
- argv: string[],
567
- /** For testing only: override the roots resolver so tests can cover the setInner branch. */
568
- _resolveRoots?: (server: McpServer) => Promise<string | null>,
569
- ): Promise<void> {
570
- const { BeansCliBackend } = await import('./backend');
571
- const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
572
-
573
- const { workspaceRoot, workspaceExplicit, cliPath, port, logDir } = parseCliArgs(argv);
574
- process.env.BEANS_VSCODE_MCP_PORT = String(port);
575
- process.env.BEANS_MCP_PORT = String(port);
576
-
577
- // Use a mutable delegate so we can hot-swap the workspace after roots discovery
578
- // without re-registering the MCP tools.
579
- const mutable = new MutableBackend(new BeansCliBackend(workspaceRoot, cliPath, logDir));
580
-
581
- const { server } = await createBeansMcpServer({
582
- workspaceRoot,
583
- cliPath,
584
- logDir,
585
- backend: mutable,
586
- });
587
-
588
- const transport = new StdioServerTransport();
589
- await server.connect(transport);
590
-
591
- // If the caller did not supply an explicit workspace, ask the connected client
592
- // for its declared MCP roots and use the first local filesystem path.
593
- if (!workspaceExplicit) {
594
- const resolver = _resolveRoots ?? resolveWorkspaceFromRoots;
595
- const rootPath = await resolver(server);
596
- if (rootPath) {
597
- mutable.setInner(new BeansCliBackend(rootPath, cliPath));
598
- }
599
- }
600
- }