loor-cli 0.1.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,954 @@
1
+ import {
2
+ PackageRegistry,
3
+ ensureCache,
4
+ getPackagesDir
5
+ } from "./chunk-E6WOLYO3.js";
6
+ import "./chunk-AUVNDYJL.js";
7
+
8
+ // src/mcp/server.ts
9
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+
12
+ // src/mcp/tools/listPackages.ts
13
+ import { z } from "zod";
14
+ function registerListPackages(server, registry) {
15
+ server.tool(
16
+ "list_packages",
17
+ "List all available loor packages with descriptions, categories, and compatibility info",
18
+ {
19
+ category: z.enum(["core", "server", "client", "config"]).optional().describe("Filter by category"),
20
+ compatibility: z.enum(["api", "client"]).optional().describe("Filter by app compatibility")
21
+ },
22
+ async ({ category, compatibility }) => {
23
+ let packages = registry.getAll();
24
+ if (category) {
25
+ packages = packages.filter((p) => p.category === category);
26
+ }
27
+ if (compatibility) {
28
+ packages = packages.filter((p) => p.compatibility.includes(compatibility));
29
+ }
30
+ const result = packages.map((p) => ({
31
+ name: p.name,
32
+ description: p.description,
33
+ category: p.category,
34
+ compatibility: p.compatibility,
35
+ requires: p.requires
36
+ }));
37
+ return {
38
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
39
+ };
40
+ }
41
+ );
42
+ }
43
+
44
+ // src/mcp/tools/getPackageGuide.ts
45
+ import path from "path";
46
+ import fs from "fs-extra";
47
+ import { z as z2 } from "zod";
48
+ function registerGetPackageGuide(server, registry) {
49
+ server.tool(
50
+ "get_package_guide",
51
+ "Get detailed guide for a specific loor package including patterns, donts, architecture docs, and infra info",
52
+ {
53
+ name: z2.string().describe('Package name (e.g. "auth", "mediaKit", "expressRouteKit")')
54
+ },
55
+ async ({ name }) => {
56
+ const manifest = registry.get(name);
57
+ if (!manifest) {
58
+ return {
59
+ content: [{ type: "text", text: `Package "${name}" not found. Use list_packages to see available packages.` }],
60
+ isError: true
61
+ };
62
+ }
63
+ const sections = [];
64
+ sections.push(`# ${manifest.name}`);
65
+ sections.push(`${manifest.description}`);
66
+ sections.push(`**Category:** ${manifest.category} | **Compatibility:** ${manifest.compatibility.join(", ")}`);
67
+ if (manifest.requires.length > 0) {
68
+ sections.push(`**Requires:** ${manifest.requires.join(", ")}`);
69
+ }
70
+ if (manifest.ai?.patterns?.length) {
71
+ sections.push("\n## DO");
72
+ for (const p of manifest.ai.patterns) {
73
+ sections.push(`- ${p}`);
74
+ }
75
+ }
76
+ if (manifest.ai?.donts?.length) {
77
+ sections.push("\n## DON'T");
78
+ for (const d of manifest.ai.donts) {
79
+ sections.push(`- ${d}`);
80
+ }
81
+ }
82
+ if (manifest.apiInfra?.length) {
83
+ sections.push("\n## API Infrastructure Files");
84
+ for (const infra of manifest.apiInfra) {
85
+ sections.push(`- \`${infra.path}\` \u2014 ${infra.description}`);
86
+ }
87
+ }
88
+ if (manifest.clientSetup?.length) {
89
+ sections.push("\n## Client Setup Files");
90
+ for (const setup of manifest.clientSetup) {
91
+ sections.push(`- \`${setup.path}\` \u2014 ${setup.description}`);
92
+ }
93
+ }
94
+ if (manifest.envVars?.length) {
95
+ sections.push("\n## Environment Variables");
96
+ for (const env of manifest.envVars) {
97
+ const req = env.required ? "(required)" : "(optional)";
98
+ sections.push(`- \`${env.key}\` ${req} \u2014 ${env.description}`);
99
+ }
100
+ }
101
+ const pkgDir = path.join(registry.getPackagesDir(), name);
102
+ const archDocPath = manifest.ai?.guide ? path.join(pkgDir, manifest.ai.guide) : path.join(pkgDir, "docs", "architecture.md");
103
+ if (await fs.pathExists(archDocPath)) {
104
+ const archDoc = await fs.readFile(archDocPath, "utf-8");
105
+ sections.push("\n## Architecture Guide");
106
+ sections.push(archDoc);
107
+ }
108
+ const readmePath = path.join(pkgDir, "README.md");
109
+ if (await fs.pathExists(readmePath)) {
110
+ const readme = await fs.readFile(readmePath, "utf-8");
111
+ sections.push("\n## README");
112
+ sections.push(readme);
113
+ }
114
+ return {
115
+ content: [{ type: "text", text: sections.join("\n") }]
116
+ };
117
+ }
118
+ );
119
+ }
120
+
121
+ // src/mcp/tools/getConventions.ts
122
+ import { z as z3 } from "zod";
123
+
124
+ // src/mcp/context/conventions.ts
125
+ import path2 from "path";
126
+ import fs2 from "fs-extra";
127
+ async function loadConventions(packagesDir) {
128
+ const docsDir = path2.join(packagesDir, "..", "docs");
129
+ const packageStandardPath = path2.join(docsDir, "PACKAGE_STANDARD.md");
130
+ const packageArchPath = path2.join(docsDir, "PACKAGE_ARCHITECTURE.md");
131
+ const packageStandard = await fs2.pathExists(packageStandardPath) ? await fs2.readFile(packageStandardPath, "utf-8") : "";
132
+ const packageArch = await fs2.pathExists(packageArchPath) ? await fs2.readFile(packageArchPath, "utf-8") : "";
133
+ return {
134
+ architecture: packageArch || getFallbackArchitecture(),
135
+ packageStructure: packageStandard || getFallbackPackageStructure(),
136
+ routePattern: getRoutePatternConventions(),
137
+ featureDev: getFeatureDevConventions()
138
+ };
139
+ }
140
+ function getFallbackArchitecture() {
141
+ return `# Architecture Conventions
142
+
143
+ ## Hexagonal Architecture (Ports & Adapters)
144
+
145
+ - **Packages** define abstract ports (interfaces) in \`ports/\` folder
146
+ - **Apps** implement adapters in \`infra/\` folder
147
+ - Factory functions accept ports as dependencies for DI
148
+
149
+ ## Package Exports
150
+
151
+ \`\`\`
152
+ packages/<name>/src/
153
+ \u251C\u2500\u2500 index.ts # Pure exports (browser, RN, Edge compatible)
154
+ \u251C\u2500\u2500 server/index.ts # Node.js only exports
155
+ \u2514\u2500\u2500 client/index.ts # Browser only exports (optional)
156
+ \`\`\`
157
+
158
+ - Use \`Uint8Array\` instead of \`Buffer\` for universal compatibility
159
+ - Keep \`index.ts\` free of Node.js-specific imports (fs, streams, etc.)`;
160
+ }
161
+ function getFallbackPackageStructure() {
162
+ return `# Package Structure Conventions
163
+
164
+ ## Directory Layout
165
+
166
+ \`\`\`
167
+ packages/<name>/
168
+ \u251C\u2500\u2500 src/
169
+ \u2502 \u251C\u2500\u2500 index.ts # Pure exports
170
+ \u2502 \u251C\u2500\u2500 server/index.ts # Node.js adapters
171
+ \u2502 \u251C\u2500\u2500 client/index.ts # Browser adapters
172
+ \u2502 \u251C\u2500\u2500 core/ # Business logic
173
+ \u2502 \u251C\u2500\u2500 ports/ # Interface definitions
174
+ \u2502 \u2514\u2500\u2500 adapters/ # Adapter implementations
175
+ \u251C\u2500\u2500 docs/
176
+ \u2502 \u2514\u2500\u2500 architecture.md # Package design docs
177
+ \u251C\u2500\u2500 loor.json # Package manifest
178
+ \u251C\u2500\u2500 package.json
179
+ \u2514\u2500\u2500 tsconfig.json
180
+ \`\`\`
181
+
182
+ ## Key Rules
183
+
184
+ 1. Ports define interfaces, adapters implement them
185
+ 2. Core logic depends only on ports, never on adapters
186
+ 3. Apps wire adapters in \`src/infra/\` directory`;
187
+ }
188
+ function getRoutePatternConventions() {
189
+ return `# Route Pattern Conventions
190
+
191
+ ## File-System Route Discovery
192
+
193
+ Routes are placed in \`src/routes/<domain>/<action>/index.ts\` and auto-discovered by routerLoader.
194
+
195
+ ## RouteProvider Pattern
196
+
197
+ Each route extends \`RouteProvider\` and implements:
198
+ - \`method\`: HTTP method (GET, POST, PUT, DELETE)
199
+ - \`path\`: URL path with params (e.g. \`/admin/creators/:id\`)
200
+ - \`middleware\`: Array of Express middleware (auth, role check)
201
+ - \`validate(req)\`: Zod schema validation, returns typed data
202
+ - \`handle(req, res, data)\`: Business logic with validated data
203
+
204
+ ## Response Helpers
205
+
206
+ Use \`sendSuccess(res, data)\` and \`sendError(res, error)\` from expressRouteKit.
207
+
208
+ ## Example Structure
209
+
210
+ \`\`\`
211
+ src/routes/
212
+ \u251C\u2500\u2500 admin/
213
+ \u2502 \u251C\u2500\u2500 users/
214
+ \u2502 \u2502 \u251C\u2500\u2500 list/index.ts # GET /admin/users
215
+ \u2502 \u2502 \u251C\u2500\u2500 get/index.ts # GET /admin/users/:id
216
+ \u2502 \u2502 \u251C\u2500\u2500 create/index.ts # POST /admin/users
217
+ \u2502 \u2502 \u2514\u2500\u2500 delete/index.ts # DELETE /admin/users/:id
218
+ \u2502 \u2514\u2500\u2500 creators/
219
+ \u2502 \u251C\u2500\u2500 list/index.ts
220
+ \u2502 \u2514\u2500\u2500 approve/index.ts
221
+ \u251C\u2500\u2500 auth/
222
+ \u2502 \u2514\u2500\u2500 admin/
223
+ \u2502 \u251C\u2500\u2500 login/index.ts
224
+ \u2502 \u2514\u2500\u2500 refreshToken/index.ts
225
+ \u2514\u2500\u2500 media/
226
+ \u251C\u2500\u2500 image/upload/index.ts
227
+ \u2514\u2500\u2500 image/download/index.ts
228
+ \`\`\``;
229
+ }
230
+ function getFeatureDevConventions() {
231
+ return `# Feature Development Conventions (Client Apps)
232
+
233
+ ## Feature Folder Structure
234
+
235
+ \`\`\`
236
+ src/features/<featureName>/
237
+ \u251C\u2500\u2500 index.tsx # Main page/component
238
+ \u251C\u2500\u2500 components/
239
+ \u2502 \u251C\u2500\u2500 columns.tsx # DataTable column definitions
240
+ \u2502 \u251C\u2500\u2500 dataTableToolbar.tsx # Search/filter toolbar
241
+ \u2502 \u2514\u2500\u2500 selectionActionBar.tsx # Bulk action bar
242
+ \u251C\u2500\u2500 hooks/
243
+ \u2502 \u251C\u2500\u2500 use<Feature>Form.ts # Form logic (react-hook-form + zod)
244
+ \u2502 \u2514\u2500\u2500 useDelete<Feature>.ts # Delete mutation hook
245
+ \u251C\u2500\u2500 types/
246
+ \u2502 \u2514\u2500\u2500 index.ts # Feature-specific types
247
+ \u251C\u2500\u2500 validations/
248
+ \u2502 \u2514\u2500\u2500 index.ts # Zod schemas
249
+ \u2514\u2500\u2500 locales/
250
+ \u251C\u2500\u2500 en.ts # English translations
251
+ \u2514\u2500\u2500 ar.ts # Arabic translations
252
+ \`\`\`
253
+
254
+ ## Key Patterns
255
+
256
+ 1. Each feature is self-contained with its own components, hooks, types, locales
257
+ 2. DataTable pattern: columns.tsx + dataTableToolbar.tsx + selectionActionBar.tsx
258
+ 3. Form pattern: useForm hook wrapping react-hook-form with zodResolver
259
+ 4. API calls through RTK Query hooks from apiClient services
260
+ 5. All user-facing strings via i18n translation keys`;
261
+ }
262
+
263
+ // src/mcp/tools/getConventions.ts
264
+ function registerGetConventions(server, registry) {
265
+ server.tool(
266
+ "get_conventions",
267
+ "Get loor project conventions for architecture, package structure, route patterns, and feature development",
268
+ {
269
+ topic: z3.enum(["all", "architecture", "package_structure", "route_pattern", "feature_dev"]).optional().default("all").describe("Convention topic to retrieve")
270
+ },
271
+ async ({ topic }) => {
272
+ const conventions = await loadConventions(registry.getPackagesDir());
273
+ const sections = [];
274
+ if (topic === "all" || topic === "architecture") {
275
+ sections.push(conventions.architecture);
276
+ }
277
+ if (topic === "all" || topic === "package_structure") {
278
+ sections.push(conventions.packageStructure);
279
+ }
280
+ if (topic === "all" || topic === "route_pattern") {
281
+ sections.push(conventions.routePattern);
282
+ }
283
+ if (topic === "all" || topic === "feature_dev") {
284
+ sections.push(conventions.featureDev);
285
+ }
286
+ return {
287
+ content: [{ type: "text", text: sections.join("\n\n---\n\n") }]
288
+ };
289
+ }
290
+ );
291
+ }
292
+
293
+ // src/mcp/tools/howTo.ts
294
+ import { z as z4 } from "zod";
295
+
296
+ // src/mcp/context/howToGuides.ts
297
+ var guides = {
298
+ create_project: `# How To: Create a New Project
299
+
300
+ ## Interactive Mode
301
+
302
+ \`\`\`bash
303
+ loor init
304
+ \`\`\`
305
+
306
+ Prompts for: project name, app types, app names, and optional packages.
307
+
308
+ ## Non-Interactive Mode (CI / Scripted)
309
+
310
+ \`\`\`bash
311
+ loor init my-saas --apps api:backend,web-client:dashboard --packages mediaKit,notification
312
+ \`\`\`
313
+
314
+ ## --apps Flag Format
315
+
316
+ \`type\` or \`type:name\`, comma-separated:
317
+ - \`--apps api\` \u2192 API app with default name "api"
318
+ - \`--apps api:backend\` \u2192 API app named "backend"
319
+ - \`--apps api,web-client\` \u2192 API "api" + web-client "admin" (defaults)
320
+ - \`--apps api:backend,web-client:dash,web-client:portal\` \u2192 multiple apps
321
+
322
+ Valid types: \`api\`, \`web-client\`
323
+
324
+ ## --packages Flag Format
325
+
326
+ - \`--packages mediaKit,notification\` \u2192 include these optional packages
327
+ - \`--no-packages\` \u2192 skip optional packages (only required ones installed)
328
+ - *(omit flag)* \u2192 prompted interactively
329
+
330
+ ## Partial Flag Usage
331
+
332
+ Each flag independently skips its prompt:
333
+ - Only \`--apps\` \u2192 packages still prompted
334
+ - Only \`--packages\` \u2192 apps still prompted
335
+ - Both \u2192 fully non-interactive
336
+
337
+ ## After Creation
338
+
339
+ \`\`\`bash
340
+ cd my-saas
341
+ pnpm install
342
+ pnpm dev
343
+ \`\`\`
344
+
345
+ ## Adding More Apps Later
346
+
347
+ \`\`\`bash
348
+ loor add app web-client portal
349
+ pnpm install
350
+ \`\`\`
351
+
352
+ ## Adding More Packages Later
353
+
354
+ \`\`\`bash
355
+ loor add package scheduler
356
+ pnpm install
357
+ \`\`\``,
358
+ add_api_route: `# How To: Add an API Route
359
+
360
+ ## Steps
361
+
362
+ ### 1. Create the route file
363
+ Create \`src/routes/<domain>/<action>/index.ts\` in your API app.
364
+
365
+ ### 2. Extend RouteProvider
366
+ \`\`\`typescript
367
+ import { RouteProvider, sendSuccess, sendError } from '@workspace/expressRouteKit';
368
+ import { z } from 'zod';
369
+
370
+ const schema = z.object({
371
+ // define request validation
372
+ });
373
+
374
+ export default class extends RouteProvider {
375
+ method = 'post' as const;
376
+ path = '/domain/action';
377
+ middleware = []; // add auth, role middleware as needed
378
+
379
+ validate(req: Request) {
380
+ return schema.parse(req.body);
381
+ }
382
+
383
+ async handle(req: Request, res: Response, data: z.infer<typeof schema>) {
384
+ // business logic
385
+ sendSuccess(res, { result });
386
+ }
387
+ }
388
+ \`\`\`
389
+
390
+ ### 3. Add middleware (if protected)
391
+ \`\`\`typescript
392
+ import { bearerAuthMiddleware } from '@/infra/tokenService';
393
+ import { roleMiddleware } from '@workspace/auth/server';
394
+
395
+ middleware = [bearerAuthMiddleware, roleMiddleware(['admin'])];
396
+ \`\`\`
397
+
398
+ ### 4. Test
399
+ The route is auto-discovered by routerLoader. Restart the server and test the endpoint.
400
+
401
+ ## Key Points
402
+ - Routes are auto-discovered from the file system \u2014 no manual registration needed
403
+ - Always validate input with Zod in \`validate()\`
404
+ - Use \`sendSuccess()\` and \`sendError()\` for consistent response format
405
+ - Add rate limiting for public endpoints`,
406
+ add_feature: `# How To: Add a Client Feature
407
+
408
+ ## Steps
409
+
410
+ ### 1. Create feature directory
411
+ \`\`\`
412
+ src/features/<featureName>/
413
+ \u251C\u2500\u2500 index.tsx
414
+ \u251C\u2500\u2500 components/
415
+ \u251C\u2500\u2500 hooks/
416
+ \u251C\u2500\u2500 types/
417
+ \u251C\u2500\u2500 validations/
418
+ \u2514\u2500\u2500 locales/
419
+ \u251C\u2500\u2500 en.ts
420
+ \u2514\u2500\u2500 ar.ts
421
+ \`\`\`
422
+
423
+ ### 2. Define types
424
+ \`\`\`typescript
425
+ // types/index.ts
426
+ export interface MyEntity {
427
+ id: string;
428
+ name: string;
429
+ // ...
430
+ }
431
+ \`\`\`
432
+
433
+ ### 3. Create API service (if needed)
434
+ \`\`\`typescript
435
+ // In src/api/services/<domain>.ts
436
+ import { baseApi } from '../baseApi';
437
+
438
+ const api = baseApi.injectEndpoints({
439
+ endpoints: (build) => ({
440
+ listEntities: build.query({ query: () => '/entities' }),
441
+ createEntity: build.mutation({ query: (body) => ({ url: '/entities', method: 'POST', body }) }),
442
+ }),
443
+ });
444
+
445
+ export const { useListEntitiesQuery, useCreateEntityMutation } = api;
446
+ \`\`\`
447
+
448
+ ### 4. Build the page component
449
+ \`\`\`typescript
450
+ // index.tsx
451
+ import { DataTable } from '@workspace/ui';
452
+ import { columns } from './components/columns';
453
+
454
+ export default function MyFeaturePage() {
455
+ const { data } = useListEntitiesQuery();
456
+ return <DataTable columns={columns} data={data ?? []} />;
457
+ }
458
+ \`\`\`
459
+
460
+ ### 5. Add route
461
+ Register the page in your router config with appropriate auth guards.
462
+
463
+ ### 6. Add translations
464
+ Add \`en.ts\` and \`ar.ts\` locale files and register them with i18n.
465
+
466
+ ## Key Points
467
+ - Each feature is self-contained
468
+ - Use DataTable for list views with sorting, filtering, pagination
469
+ - Use react-hook-form + zod for forms
470
+ - All strings through i18n`,
471
+ add_package: `# How To: Add a Package to a Project
472
+
473
+ ## Using CLI (Recommended)
474
+
475
+ \`\`\`bash
476
+ loor add package <name>
477
+ \`\`\`
478
+
479
+ This will:
480
+ 1. Resolve transitive dependencies automatically
481
+ 2. Copy package source to \`packages/<name>/\`
482
+ 3. Generate infra adapters in API apps (\`src/infra/\`)
483
+ 4. Generate client setup files in client apps
484
+ 5. Add env vars to \`.env.example\`
485
+ 6. Merge app dependencies into \`package.json\`
486
+
487
+ ## Adding Packages During Project Init
488
+
489
+ You can also include packages at project creation time:
490
+
491
+ \`\`\`bash
492
+ # Interactive selection
493
+ loor init my-project
494
+
495
+ # Via CLI flag (non-interactive)
496
+ loor init my-project --apps api,web-client --packages mediaKit,notification
497
+
498
+ # Skip optional packages entirely
499
+ loor init my-project --apps api --no-packages
500
+ \`\`\`
501
+
502
+ ## Manual Steps (if needed)
503
+
504
+ ### 1. Copy package
505
+ Copy from the registry to \`packages/<name>/\`.
506
+
507
+ ### 2. Wire infrastructure
508
+ For API packages, create adapter files in \`apps/<apiApp>/src/infra/\`:
509
+ \`\`\`typescript
510
+ // Example: src/infra/logger.ts
511
+ import { createPinoLogger } from '@workspace/logger/server';
512
+ import config from '../config';
513
+
514
+ export const logger = createPinoLogger({
515
+ level: config.log.level,
516
+ pretty: config.log.pretty,
517
+ });
518
+ \`\`\`
519
+
520
+ ### 3. Add dependencies
521
+ Install the package's \`appDependencies\` in the relevant apps.
522
+
523
+ ### 4. Configure env vars
524
+ Add required env vars from the package manifest to \`.env\`.
525
+
526
+ ## Key Points
527
+ - Always check \`requires\` field \u2014 install dependencies first
528
+ - Use \`loor list\` to see available packages
529
+ - Run \`pnpm install\` after adding packages
530
+ - Use \`cli_usage\` MCP tool for full CLI reference`,
531
+ add_infra_adapter: `# How To: Add an Infrastructure Adapter
532
+
533
+ ## Overview
534
+ Adapters implement package ports (interfaces) with concrete dependencies. They live in \`apps/<appName>/src/infra/\`.
535
+
536
+ ## Steps
537
+
538
+ ### 1. Identify the port
539
+ Find the port interface in the package's \`src/ports/\` directory:
540
+ \`\`\`typescript
541
+ // packages/logger/src/ports/errorLog.port.ts
542
+ export interface ErrorLogPort {
543
+ error(message: string, meta?: Record<string, unknown>): void;
544
+ warn(message: string, meta?: Record<string, unknown>): void;
545
+ }
546
+ \`\`\`
547
+
548
+ ### 2. Create the adapter
549
+ \`\`\`typescript
550
+ // apps/myApi/src/infra/logger.ts
551
+ import { createPinoLogger } from '@workspace/logger/server';
552
+ import config from '../config';
553
+
554
+ export const logger = createPinoLogger({
555
+ level: config.log.level,
556
+ pretty: config.log.pretty,
557
+ });
558
+ \`\`\`
559
+
560
+ ### 3. Inject into services
561
+ Pass the adapter as a dependency where needed:
562
+ \`\`\`typescript
563
+ import { logger } from '@/infra/logger';
564
+ import { createAuthService } from '@workspace/auth';
565
+
566
+ export const authService = createAuthService({ logger });
567
+ \`\`\`
568
+
569
+ ### 4. Add config/env vars
570
+ If the adapter needs configuration:
571
+ - Add env vars to \`.env.example\`
572
+ - Add config keys to \`src/config/index.ts\`
573
+
574
+ ## Naming Convention
575
+ - File: \`src/infra/<adapterName>.ts\`
576
+ - Export: named export matching the port purpose (e.g. \`logger\`, \`tokenService\`, \`mediaStorage\`)
577
+
578
+ ## Key Points
579
+ - One adapter file per port implementation
580
+ - Import factory from \`@workspace/<package>/server\`
581
+ - Wire config values from \`src/config/\`
582
+ - Adapters are singletons \u2014 instantiate once, export for reuse`
583
+ };
584
+ function getHowToGuide(task) {
585
+ return guides[task];
586
+ }
587
+
588
+ // src/mcp/tools/howTo.ts
589
+ function registerHowTo(server) {
590
+ server.tool(
591
+ "how_to",
592
+ "Get step-by-step instructions for common loor development tasks",
593
+ {
594
+ task: z4.enum(["create_project", "add_api_route", "add_feature", "add_package", "add_infra_adapter"]).describe("The task you need step-by-step instructions for")
595
+ },
596
+ async ({ task }) => {
597
+ const guide = getHowToGuide(task);
598
+ return {
599
+ content: [{ type: "text", text: guide }]
600
+ };
601
+ }
602
+ );
603
+ }
604
+
605
+ // src/mcp/tools/cliUsage.ts
606
+ import { z as z5 } from "zod";
607
+ var commands = {
608
+ init: `# loor init [name]
609
+
610
+ Create a new loor monorepo project.
611
+
612
+ ## Arguments
613
+
614
+ - \`name\` (optional) \u2014 Project name. If omitted, prompted interactively.
615
+
616
+ ## Options
617
+
618
+ | Option | Description |
619
+ |--------|-------------|
620
+ | \`--apps <apps>\` | App types with optional names (comma-separated). Format: \`type\` or \`type:name\`. |
621
+ | \`--packages <packages>\` | Optional packages to include (comma-separated). |
622
+ | \`--no-packages\` | Skip optional packages \u2014 only required ones are installed. |
623
+
624
+ ## App Types
625
+
626
+ | Type | Description | Default Name |
627
+ |------|-------------|--------------|
628
+ | \`api\` | Express.js + MongoDB REST API | \`api\` |
629
+ | \`web-client\` | React + Vite frontend | \`admin\` |
630
+
631
+ Multiple apps of the same type are allowed (e.g., two web-client apps with different names).
632
+
633
+ ## Examples
634
+
635
+ ### Fully interactive
636
+ \`\`\`bash
637
+ loor init
638
+ \`\`\`
639
+
640
+ ### Fully non-interactive
641
+ \`\`\`bash
642
+ loor init my-saas --apps api:backend,web-client:dashboard --packages mediaKit,notification
643
+ \`\`\`
644
+
645
+ ### Custom app names, default packages prompt
646
+ \`\`\`bash
647
+ loor init my-saas --apps api:my-api,web-client:admin
648
+ \`\`\`
649
+
650
+ ### Default app names
651
+ \`\`\`bash
652
+ loor init my-saas --apps api,web-client
653
+ # api app \u2192 "api", web-client app \u2192 "admin"
654
+ \`\`\`
655
+
656
+ ### Skip optional packages
657
+ \`\`\`bash
658
+ loor init my-saas --apps api,web-client --no-packages
659
+ \`\`\`
660
+
661
+ ### Only specify packages (apps prompted interactively)
662
+ \`\`\`bash
663
+ loor init my-saas --packages mediaKit,notification
664
+ \`\`\`
665
+
666
+ ## Required Packages (always installed)
667
+
668
+ **API apps:** auth, base, expressRouteKit, logger, eslintConfig, typescriptConfig
669
+
670
+ **Web Client apps:** auth, base, storage, ui, i18n, routerProvider, commandMenu, apiClient, eslintConfig, typescriptConfig
671
+
672
+ ## What It Does
673
+
674
+ 1. Creates project directory with root configs (turbo.json, pnpm-workspace.yaml, tsconfig.json, etc.)
675
+ 2. Generates package.json programmatically
676
+ 3. Copies selected app templates from reference apps
677
+ 4. Copies all packages, then removes artifacts of unselected packages (subtractive approach)
678
+ 5. Resolves transitive package dependencies automatically
679
+ 6. Replaces \`@loor/\` scope with \`@<projectName>/\` across the project`,
680
+ add_app: `# loor add app <type> [name]
681
+
682
+ Add a new app to an existing project. Must be run from the project root.
683
+
684
+ ## Arguments
685
+
686
+ | Argument | Required | Description |
687
+ |----------|----------|-------------|
688
+ | \`type\` | Yes | App type: \`api\` or \`web-client\` |
689
+ | \`name\` | No | App name. Defaults to \`api\` for API, \`admin\` for web-client. |
690
+
691
+ ## Examples
692
+
693
+ \`\`\`bash
694
+ loor add app api
695
+ loor add app api backend
696
+ loor add app web-client dashboard
697
+ loor add app web-client portal
698
+ \`\`\`
699
+
700
+ ## What It Does
701
+
702
+ 1. Copies reference app template to \`apps/<name>/\`
703
+ 2. Installs required packages for the app type (if missing from project)
704
+ 3. Removes artifacts of non-installed packages from the new app
705
+ 4. You need to run \`pnpm install\` after`,
706
+ add_package: `# loor add package <name>
707
+
708
+ Add a package to an existing project. Must be run from the project root.
709
+
710
+ ## Arguments
711
+
712
+ | Argument | Required | Description |
713
+ |----------|----------|-------------|
714
+ | \`name\` | Yes | Package name from the registry |
715
+
716
+ ## Examples
717
+
718
+ \`\`\`bash
719
+ loor add package mediaKit
720
+ loor add package notification
721
+ loor add package scheduler
722
+ \`\`\`
723
+
724
+ ## What It Does
725
+
726
+ 1. Resolves transitive dependencies automatically
727
+ 2. Copies package source to \`packages/<name>/\`
728
+ 3. Generates infra adapters in API apps (\`src/infra/\`)
729
+ 4. Generates client setup files in client apps
730
+ 5. Adds env vars to \`.env.example\`
731
+ 6. Merges npm dependencies into app \`package.json\` files
732
+ 7. Injects config annotation blocks
733
+
734
+ ## Tips
735
+
736
+ - Use \`loor list\` to see all available packages
737
+ - Already-installed packages are skipped
738
+ - Run \`pnpm install\` after adding packages`,
739
+ list: `# loor list
740
+
741
+ List all available packages with descriptions, categories, and compatibility info.
742
+
743
+ \`\`\`bash
744
+ loor list
745
+ \`\`\`
746
+
747
+ Packages are organized by category: **core**, **server**, **client**, **config**.
748
+
749
+ Each entry shows: name, description, compatibility (api/client), and required dependencies.`,
750
+ update: `# loor update
751
+
752
+ Force-refresh the package registry from the remote source.
753
+
754
+ \`\`\`bash
755
+ loor update
756
+ \`\`\`
757
+
758
+ The registry is cached locally (\`~/.loor/registry/\`) with a 24-hour TTL. This command forces a re-download regardless of cache age.`,
759
+ config: `# loor config
760
+
761
+ Configure the CLI registry source \u2014 local development or remote production.
762
+
763
+ ## Options
764
+
765
+ | Option | Description |
766
+ |--------|-------------|
767
+ | \`--local [path]\` | Use a local repo as scaffold source. Defaults to current directory. |
768
+ | \`--remote\` | Use the remote GitHub registry (production). |
769
+ | *(no flags)* | Show current configuration. |
770
+
771
+ ## Examples
772
+
773
+ \`\`\`bash
774
+ # Show current config
775
+ loor config
776
+
777
+ # Use local repo (current directory)
778
+ loor config --local
779
+
780
+ # Use local repo (specific path)
781
+ loor config --local /path/to/loor
782
+
783
+ # Switch back to remote
784
+ loor config --remote
785
+ \`\`\`
786
+
787
+ ## Environment Variable
788
+
789
+ The \`LOOR_LOCAL\` env var overrides any persisted config. When set, CLI always uses the path it points to.`,
790
+ ai_init: `# loor ai init
791
+
792
+ Generate AI context files for the project.
793
+
794
+ \`\`\`bash
795
+ loor ai init
796
+ \`\`\`
797
+
798
+ ## What It Does
799
+
800
+ 1. Scans the project for installed apps and packages
801
+ 2. Generates \`CLAUDE.md\` with project-specific AI context:
802
+ - App list with types and paths
803
+ - Architecture overview
804
+ - Installed packages with DO/DON'T patterns
805
+ - Infra files and required env vars
806
+ - Conventions (hexagonal architecture, routes, features)
807
+ - App-specific context (routes, infra, features)
808
+ 3. Generates \`.mcp.json\` to register the loor MCP server
809
+
810
+ ## MCP Tools Available After Init
811
+
812
+ | Tool | Description |
813
+ |------|-------------|
814
+ | \`list_packages\` | List packages with optional category/compatibility filters |
815
+ | \`get_package_guide\` | Detailed package guide (patterns, infra, env vars, docs) |
816
+ | \`get_conventions\` | Project conventions (architecture, routes, features) |
817
+ | \`how_to\` | Step-by-step guides for common development tasks |
818
+ | \`cli_usage\` | Complete CLI command reference and usage examples |`,
819
+ overview: `# loor CLI \u2014 Complete Reference
820
+
821
+ ## All Commands
822
+
823
+ | Command | Description |
824
+ |---------|-------------|
825
+ | \`loor init [name]\` | Create a new monorepo project |
826
+ | \`loor add app <type> [name]\` | Add an app to existing project |
827
+ | \`loor add package <name>\` | Add a package to existing project |
828
+ | \`loor list\` | List available packages |
829
+ | \`loor update\` | Refresh registry cache |
830
+ | \`loor config\` | Configure registry source |
831
+ | \`loor ai init\` | Generate AI context files |
832
+
833
+ ## Global Options
834
+
835
+ | Option | Description |
836
+ |--------|-------------|
837
+ | \`--debug\` | Enable debug mode with detailed error output |
838
+ | \`--version\` | Show CLI version |
839
+ | \`--help\` | Show help for any command |
840
+
841
+ ## Init Command \u2014 Quick Reference
842
+
843
+ \`\`\`bash
844
+ # Fully interactive
845
+ loor init
846
+
847
+ # Fully non-interactive
848
+ loor init my-saas --apps api:backend,web-client:dashboard --packages mediaKit
849
+
850
+ # Skip optional packages
851
+ loor init my-saas --apps api,web-client --no-packages
852
+
853
+ # Only specify apps
854
+ loor init my-saas --apps api:my-api,web-client:admin
855
+
856
+ # Only specify packages
857
+ loor init my-saas --packages mediaKit,notification
858
+ \`\`\`
859
+
860
+ ## --apps Flag Format
861
+
862
+ \`type\` or \`type:name\`, comma-separated:
863
+ - \`--apps api\` \u2192 API app named "api"
864
+ - \`--apps api:backend\` \u2192 API app named "backend"
865
+ - \`--apps api,web-client\` \u2192 API "api" + web-client "admin"
866
+ - \`--apps api:backend,web-client:dash\` \u2192 API "backend" + web-client "dash"
867
+
868
+ Valid types: \`api\`, \`web-client\`
869
+
870
+ ## --packages Flag Format
871
+
872
+ Comma-separated package names:
873
+ - \`--packages mediaKit\` \u2192 add mediaKit
874
+ - \`--packages mediaKit,notification,scheduler\` \u2192 add multiple
875
+ - \`--no-packages\` \u2192 no optional packages
876
+
877
+ ## Environment Variables
878
+
879
+ | Variable | Description |
880
+ |----------|-------------|
881
+ | \`LOOR_LOCAL\` | Override config to use local repo path |
882
+ | \`LOOR_REPO\` | GitHub repo (default: ismailyagci/loor) |
883
+ | \`LOOR_BRANCH\` | Branch (default: master) |
884
+
885
+ ## Typical Workflows
886
+
887
+ ### New project (CI/scripted)
888
+ \`\`\`bash
889
+ loor init my-app --apps api,web-client:admin --packages mediaKit
890
+ cd my-app && pnpm install && pnpm dev
891
+ \`\`\`
892
+
893
+ ### Add second frontend
894
+ \`\`\`bash
895
+ cd my-app
896
+ loor add app web-client portal
897
+ pnpm install
898
+ \`\`\`
899
+
900
+ ### Add package
901
+ \`\`\`bash
902
+ cd my-app
903
+ loor add package notification
904
+ pnpm install
905
+ \`\`\`
906
+
907
+ ### Setup AI context
908
+ \`\`\`bash
909
+ cd my-app
910
+ loor ai init
911
+ \`\`\``
912
+ };
913
+ function registerCliUsage(server) {
914
+ server.tool(
915
+ "cli_usage",
916
+ "Get detailed CLI command reference, usage examples, and available options for loor commands",
917
+ {
918
+ command: z5.enum(["overview", "init", "add_app", "add_package", "list", "update", "config", "ai_init"]).optional().default("overview").describe('CLI command to get usage info for. Use "overview" for complete reference.')
919
+ },
920
+ async ({ command }) => {
921
+ const content = commands[command];
922
+ return {
923
+ content: [{ type: "text", text: content }]
924
+ };
925
+ }
926
+ );
927
+ }
928
+
929
+ // src/mcp/server.ts
930
+ async function startMcpServer() {
931
+ const originalLog = console.log;
932
+ console.log = console.error;
933
+ try {
934
+ await ensureCache();
935
+ } finally {
936
+ console.log = originalLog;
937
+ }
938
+ const registry = new PackageRegistry(await getPackagesDir());
939
+ await registry.load();
940
+ const server = new McpServer({
941
+ name: "loor",
942
+ version: "0.1.0"
943
+ });
944
+ registerListPackages(server, registry);
945
+ registerGetPackageGuide(server, registry);
946
+ registerGetConventions(server, registry);
947
+ registerHowTo(server);
948
+ registerCliUsage(server);
949
+ const transport = new StdioServerTransport();
950
+ await server.connect(transport);
951
+ }
952
+ export {
953
+ startMcpServer
954
+ };