@grafema/cli 0.2.4-beta → 0.2.5-beta

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 (84) hide show
  1. package/README.md +73 -0
  2. package/dist/cli.js +1 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/commands/analyze.d.ts +9 -0
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +136 -52
  7. package/dist/commands/analyze.js.map +1 -0
  8. package/dist/commands/check.d.ts +2 -6
  9. package/dist/commands/check.d.ts.map +1 -1
  10. package/dist/commands/check.js +32 -46
  11. package/dist/commands/check.js.map +1 -0
  12. package/dist/commands/coverage.js +1 -0
  13. package/dist/commands/coverage.js.map +1 -0
  14. package/dist/commands/doctor/checks.d.ts.map +1 -1
  15. package/dist/commands/doctor/checks.js +9 -5
  16. package/dist/commands/doctor/checks.js.map +1 -0
  17. package/dist/commands/doctor/output.js +1 -0
  18. package/dist/commands/doctor/output.js.map +1 -0
  19. package/dist/commands/doctor/types.js +1 -0
  20. package/dist/commands/doctor/types.js.map +1 -0
  21. package/dist/commands/doctor.js +1 -0
  22. package/dist/commands/doctor.js.map +1 -0
  23. package/dist/commands/explain.js +1 -0
  24. package/dist/commands/explain.js.map +1 -0
  25. package/dist/commands/explore.d.ts.map +1 -1
  26. package/dist/commands/explore.js +9 -4
  27. package/dist/commands/explore.js.map +1 -0
  28. package/dist/commands/get.d.ts.map +1 -1
  29. package/dist/commands/get.js +7 -0
  30. package/dist/commands/get.js.map +1 -0
  31. package/dist/commands/impact.js +1 -0
  32. package/dist/commands/impact.js.map +1 -0
  33. package/dist/commands/init.d.ts.map +1 -1
  34. package/dist/commands/init.js +7 -1
  35. package/dist/commands/init.js.map +1 -0
  36. package/dist/commands/ls.d.ts.map +1 -1
  37. package/dist/commands/ls.js +7 -0
  38. package/dist/commands/ls.js.map +1 -0
  39. package/dist/commands/overview.d.ts.map +1 -1
  40. package/dist/commands/overview.js +1 -0
  41. package/dist/commands/overview.js.map +1 -0
  42. package/dist/commands/query.d.ts.map +1 -1
  43. package/dist/commands/query.js +68 -1
  44. package/dist/commands/query.js.map +1 -0
  45. package/dist/commands/schema.js +1 -0
  46. package/dist/commands/schema.js.map +1 -0
  47. package/dist/commands/server.d.ts +2 -1
  48. package/dist/commands/server.d.ts.map +1 -1
  49. package/dist/commands/server.js +128 -15
  50. package/dist/commands/server.js.map +1 -0
  51. package/dist/commands/stats.js +1 -0
  52. package/dist/commands/stats.js.map +1 -0
  53. package/dist/commands/trace.js +1 -0
  54. package/dist/commands/trace.js.map +1 -0
  55. package/dist/commands/types.js +1 -0
  56. package/dist/commands/types.js.map +1 -0
  57. package/dist/utils/codePreview.js +1 -0
  58. package/dist/utils/codePreview.js.map +1 -0
  59. package/dist/utils/errorFormatter.js +1 -0
  60. package/dist/utils/errorFormatter.js.map +1 -0
  61. package/dist/utils/formatNode.js +1 -0
  62. package/dist/utils/formatNode.js.map +1 -0
  63. package/dist/utils/progressRenderer.d.ts +119 -0
  64. package/dist/utils/progressRenderer.d.ts.map +1 -0
  65. package/dist/utils/progressRenderer.js +245 -0
  66. package/dist/utils/progressRenderer.js.map +1 -0
  67. package/dist/utils/spinner.d.ts +39 -0
  68. package/dist/utils/spinner.d.ts.map +1 -0
  69. package/dist/utils/spinner.js +84 -0
  70. package/dist/utils/spinner.js.map +1 -0
  71. package/package.json +5 -4
  72. package/src/commands/analyze.ts +150 -55
  73. package/src/commands/check.ts +36 -68
  74. package/src/commands/doctor/checks.ts +8 -5
  75. package/src/commands/explore.tsx +8 -4
  76. package/src/commands/get.ts +8 -0
  77. package/src/commands/impact.ts +1 -1
  78. package/src/commands/init.ts +6 -2
  79. package/src/commands/ls.ts +8 -0
  80. package/src/commands/overview.ts +0 -4
  81. package/src/commands/query.ts +77 -1
  82. package/src/commands/server.ts +142 -16
  83. package/src/utils/progressRenderer.ts +288 -0
  84. package/src/utils/spinner.ts +94 -0
@@ -160,9 +160,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
160
160
  }));
161
161
  }
162
162
  } catch (err) {
163
+ const message = err instanceof Error ? err.message : String(err);
163
164
  setState(s => ({
164
165
  ...s,
165
- error: (err as Error).message,
166
+ error: message,
166
167
  loading: false,
167
168
  }));
168
169
  }
@@ -387,9 +388,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
387
388
  error: results.length === 0 ? `No results for "${query}"` : null,
388
389
  }));
389
390
  } catch (err) {
391
+ const message = err instanceof Error ? err.message : String(err);
390
392
  setState(s => ({
391
393
  ...s,
392
- error: (err as Error).message,
394
+ error: message,
393
395
  loading: false,
394
396
  }));
395
397
  }
@@ -408,9 +410,10 @@ function Explorer({ backend, startNode, projectPath }: ExplorerProps) {
408
410
  loading: false,
409
411
  }));
410
412
  } catch (err) {
413
+ const message = err instanceof Error ? err.message : String(err);
411
414
  setState(s => ({
412
415
  ...s,
413
- error: (err as Error).message,
416
+ error: message,
414
417
  loading: false,
415
418
  }));
416
419
  }
@@ -1010,7 +1013,8 @@ async function runBatchExplore(
1010
1013
  outputResults(callees, 'callees', useJson, projectPath, target);
1011
1014
  }
1012
1015
  } catch (err) {
1013
- exitWithError(`Explore failed: ${(err as Error).message}`);
1016
+ const message = err instanceof Error ? err.message : String(err);
1017
+ exitWithError(`Explore failed: ${message}`);
1014
1018
  }
1015
1019
  }
1016
1020
 
@@ -12,6 +12,7 @@ import { existsSync } from 'fs';
12
12
  import { RFDBServerBackend } from '@grafema/core';
13
13
  import { formatNodeDisplay } from '../utils/formatNode.js';
14
14
  import { exitWithError } from '../utils/errorFormatter.js';
15
+ import { Spinner } from '../utils/spinner.js';
15
16
 
16
17
  interface GetOptions {
17
18
  project: string;
@@ -66,11 +67,15 @@ Examples:
66
67
  const backend = new RFDBServerBackend({ dbPath });
67
68
  await backend.connect();
68
69
 
70
+ const spinner = new Spinner('Querying graph...');
71
+ spinner.start();
72
+
69
73
  try {
70
74
  // Retrieve node by semantic ID
71
75
  const node = await backend.getNode(semanticId);
72
76
 
73
77
  if (!node) {
78
+ spinner.stop();
74
79
  exitWithError('Node not found', [
75
80
  `ID: ${semanticId}`,
76
81
  'Try: grafema query "<name>" to search for nodes',
@@ -81,6 +86,8 @@ Examples:
81
86
  const incomingEdges = await backend.getIncomingEdges(semanticId, null);
82
87
  const outgoingEdges = await backend.getOutgoingEdges(semanticId, null);
83
88
 
89
+ spinner.stop();
90
+
84
91
  if (options.json) {
85
92
  await outputJSON(backend, node, incomingEdges, outgoingEdges);
86
93
  } else {
@@ -88,6 +95,7 @@ Examples:
88
95
  }
89
96
 
90
97
  } finally {
98
+ spinner.stop();
91
99
  await backend.close();
92
100
  }
93
101
  });
@@ -10,7 +10,7 @@ import { Command } from 'commander';
10
10
  import { resolve, join, dirname } from 'path';
11
11
  import { relative } from 'path';
12
12
  import { existsSync } from 'fs';
13
- import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore, type CallerInfo } from '@grafema/core';
13
+ import { RFDBServerBackend, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
14
14
  import { formatNodeDisplay, formatNodeInline } from '../utils/formatNode.js';
15
15
  import { exitWithError } from '../utils/errorFormatter.js';
16
16
 
@@ -8,7 +8,6 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
8
8
  import { spawn } from 'child_process';
9
9
  import { createInterface } from 'readline';
10
10
  import { fileURLToPath } from 'url';
11
- import { exitWithError } from '../utils/errorFormatter.js';
12
11
  import { stringify as stringifyYAML } from 'yaml';
13
12
  import { DEFAULT_CONFIG } from '@grafema/core';
14
13
 
@@ -75,7 +74,9 @@ function askYesNo(question: string): Promise<boolean> {
75
74
  function runAnalyze(projectPath: string): Promise<number> {
76
75
  return new Promise((resolve) => {
77
76
  const cliPath = join(__dirname, '..', 'cli.js');
78
- const child = spawn('node', [cliPath, 'analyze', projectPath], {
77
+ // Use process.execPath (absolute path to current Node binary) instead of
78
+ // 'node' to avoid PATH lookup failures when nvm isn't loaded in the shell.
79
+ const child = spawn(process.execPath, [cliPath, 'analyze', projectPath], {
79
80
  stdio: 'inherit', // Pass through all I/O for user to see progress
80
81
  });
81
82
  child.on('close', (code) => resolve(code ?? 1));
@@ -92,6 +93,9 @@ function printNextSteps(): void {
92
93
  console.log(' 1. Review config: code .grafema/config.yaml');
93
94
  console.log(' 2. Build graph: grafema analyze');
94
95
  console.log(' 3. Explore: grafema overview');
96
+ console.log('');
97
+ console.log('For AI-assisted setup, use the Grafema MCP server');
98
+ console.log('with the "onboard_project" prompt.');
95
99
  }
96
100
 
97
101
  /**
@@ -15,6 +15,7 @@ import { resolve, join, relative } from 'path';
15
15
  import { existsSync } from 'fs';
16
16
  import { RFDBServerBackend } from '@grafema/core';
17
17
  import { exitWithError } from '../utils/errorFormatter.js';
18
+ import { Spinner } from '../utils/spinner.js';
18
19
 
19
20
  interface LsOptions {
20
21
  project: string;
@@ -66,6 +67,9 @@ Discover available types:
66
67
  const backend = new RFDBServerBackend({ dbPath });
67
68
  await backend.connect();
68
69
 
70
+ const spinner = new Spinner('Querying graph...');
71
+ spinner.start();
72
+
69
73
  try {
70
74
  const limit = parseInt(options.limit, 10);
71
75
  const nodeType = options.type;
@@ -73,6 +77,7 @@ Discover available types:
73
77
  // Check if type exists in graph
74
78
  const typeCounts = await backend.countNodesByType();
75
79
  if (!typeCounts[nodeType]) {
80
+ spinner.stop();
76
81
  const availableTypes = Object.keys(typeCounts).sort();
77
82
  exitWithError(`No nodes of type "${nodeType}" found`, [
78
83
  'Available types:',
@@ -103,6 +108,8 @@ Discover available types:
103
108
  const totalCount = typeCounts[nodeType];
104
109
  const showing = nodes.length;
105
110
 
111
+ spinner.stop();
112
+
106
113
  if (options.json) {
107
114
  console.log(JSON.stringify({
108
115
  type: nodeType,
@@ -125,6 +132,7 @@ Discover available types:
125
132
  }
126
133
  }
127
134
  } finally {
135
+ spinner.stop();
128
136
  await backend.close();
129
137
  }
130
138
  });
@@ -8,10 +8,6 @@ import { existsSync } from 'fs';
8
8
  import { RFDBServerBackend } from '@grafema/core';
9
9
  import { exitWithError } from '../utils/errorFormatter.js';
10
10
 
11
- interface NodeStats {
12
- type: string;
13
- count: number;
14
- }
15
11
 
16
12
  export const overviewCommand = new Command('overview')
17
13
  .description('Show project overview and statistics')
@@ -15,6 +15,7 @@ import { existsSync } from 'fs';
15
15
  import { RFDBServerBackend, parseSemanticId, findCallsInFunction as findCallsInFunctionCore, findContainingFunction as findContainingFunctionCore } from '@grafema/core';
16
16
  import { formatNodeDisplay, formatNodeInline, formatLocation } from '../utils/formatNode.js';
17
17
  import { exitWithError } from '../utils/errorFormatter.js';
18
+ import { Spinner } from '../utils/spinner.js';
18
19
 
19
20
  interface QueryOptions {
20
21
  project: string;
@@ -130,9 +131,13 @@ Examples:
130
131
  const backend = new RFDBServerBackend({ dbPath });
131
132
  await backend.connect();
132
133
 
134
+ const spinner = new Spinner('Querying graph...');
135
+ spinner.start();
136
+
133
137
  try {
134
138
  // Raw Datalog mode
135
139
  if (options.raw) {
140
+ spinner.stop();
136
141
  await executeRawQuery(backend, pattern, options);
137
142
  return;
138
143
  }
@@ -159,6 +164,8 @@ Examples:
159
164
  // Find matching nodes
160
165
  const nodes = await findNodes(backend, query, limit);
161
166
 
167
+ spinner.stop();
168
+
162
169
  // Check if query has scope constraints for suggestion
163
170
  const hasScope = query.file !== null || query.scopes.length > 0;
164
171
 
@@ -218,6 +225,7 @@ Examples:
218
225
  }
219
226
 
220
227
  } finally {
228
+ spinner.stop();
221
229
  await backend.close();
222
230
  }
223
231
  });
@@ -254,6 +262,11 @@ function parsePattern(pattern: string): { type: string | null; name: string } {
254
262
  emit: 'socketio:emit',
255
263
  on: 'socketio:on',
256
264
  listener: 'socketio:on',
265
+ // Grafema internal
266
+ plugin: 'grafema:plugin',
267
+ // Property access aliases (REG-395)
268
+ property: 'PROPERTY_ACCESS',
269
+ prop: 'PROPERTY_ACCESS',
257
270
  };
258
271
 
259
272
  if (typeMap[typeWord]) {
@@ -551,7 +564,8 @@ async function findNodes(
551
564
  'http:request',
552
565
  'socketio:event',
553
566
  'socketio:emit',
554
- 'socketio:on'
567
+ 'socketio:on',
568
+ 'PROPERTY_ACCESS'
555
569
  ];
556
570
 
557
571
  for (const nodeType of searchTypes) {
@@ -606,6 +620,21 @@ async function findNodes(
606
620
  nodeInfo.handlerName = node.handlerName as string | undefined;
607
621
  }
608
622
 
623
+ // Include plugin-specific fields
624
+ if (nodeType === 'grafema:plugin') {
625
+ nodeInfo.phase = node.phase as string | undefined;
626
+ nodeInfo.priority = node.priority as number | undefined;
627
+ nodeInfo.builtin = node.builtin as boolean | undefined;
628
+ nodeInfo.createsNodes = node.createsNodes as string[] | undefined;
629
+ nodeInfo.createsEdges = node.createsEdges as string[] | undefined;
630
+ nodeInfo.dependencies = node.dependencies as string[] | undefined;
631
+ }
632
+
633
+ // Include objectName for PROPERTY_ACCESS nodes (REG-395)
634
+ if (nodeType === 'PROPERTY_ACCESS') {
635
+ nodeInfo.objectName = node.objectName as string | undefined;
636
+ }
637
+
609
638
  results.push(nodeInfo);
610
639
  if (results.length >= limit) break;
611
640
  }
@@ -735,6 +764,12 @@ async function displayNode(node: NodeInfo, projectPath: string, backend: RFDBSer
735
764
  return;
736
765
  }
737
766
 
767
+ // Special formatting for Grafema plugin nodes
768
+ if (node.type === 'grafema:plugin') {
769
+ console.log(formatPluginDisplay(node, projectPath));
770
+ return;
771
+ }
772
+
738
773
  console.log(formatNodeDisplay(node, { projectPath }));
739
774
 
740
775
  // Add scope context if present
@@ -890,6 +925,47 @@ function formatSocketIONodeDisplay(node: NodeInfo, projectPath: string): string
890
925
  return lines.join('\n');
891
926
  }
892
927
 
928
+ /**
929
+ * Format Grafema plugin node for display.
930
+ *
931
+ * Output:
932
+ * [grafema:plugin] HTTPConnectionEnricher
933
+ * Phase: ENRICHMENT (priority: 50)
934
+ * Creates: edges: INTERACTS_WITH, HTTP_RECEIVES
935
+ * Dependencies: ExpressRouteAnalyzer, FetchAnalyzer, ExpressResponseAnalyzer
936
+ * Source: packages/core/src/plugins/enrichment/HTTPConnectionEnricher.ts
937
+ */
938
+ function formatPluginDisplay(node: NodeInfo, projectPath: string): string {
939
+ const lines: string[] = [];
940
+
941
+ lines.push(`[${node.type}] ${node.name}`);
942
+
943
+ const phase = (node.phase as string) || 'unknown';
944
+ const priority = (node.priority as number) ?? 0;
945
+ lines.push(` Phase: ${phase} (priority: ${priority})`);
946
+
947
+ const createsNodes = (node.createsNodes as string[]) || [];
948
+ const createsEdges = (node.createsEdges as string[]) || [];
949
+ const createsParts: string[] = [];
950
+ if (createsNodes.length > 0) createsParts.push(`nodes: ${createsNodes.join(', ')}`);
951
+ if (createsEdges.length > 0) createsParts.push(`edges: ${createsEdges.join(', ')}`);
952
+ if (createsParts.length > 0) {
953
+ lines.push(` Creates: ${createsParts.join('; ')}`);
954
+ }
955
+
956
+ const deps = (node.dependencies as string[]) || [];
957
+ if (deps.length > 0) {
958
+ lines.push(` Dependencies: ${deps.join(', ')}`);
959
+ }
960
+
961
+ if (node.file) {
962
+ const relPath = relative(projectPath, node.file);
963
+ lines.push(` Source: ${relPath}`);
964
+ }
965
+
966
+ return lines.join('\n');
967
+ }
968
+
893
969
  /**
894
970
  * Execute raw Datalog query (backwards compat)
895
971
  */
@@ -1,25 +1,94 @@
1
1
  /**
2
- * Server command - Manage RFDB server lifecycle
2
+ * Server command - Manage RFDB (Rega Flow Database) server lifecycle
3
3
  *
4
4
  * Provides explicit control over the RFDB server process:
5
5
  * grafema server start - Start detached server
6
6
  * grafema server stop - Stop server gracefully
7
7
  * grafema server status - Check if server is running
8
+ * grafema server graphql - Start GraphQL API server
8
9
  */
9
10
 
10
11
  import { Command } from 'commander';
11
- import { resolve, join } from 'path';
12
+ import { resolve, join, dirname } from 'path';
12
13
  import { existsSync, unlinkSync, writeFileSync, readFileSync } from 'fs';
13
14
  import { spawn } from 'child_process';
15
+ import { fileURLToPath } from 'url';
14
16
  import { setTimeout as sleep } from 'timers/promises';
15
- import { RFDBClient, loadConfig, findRfdbBinary, getBinaryNotFoundMessage } from '@grafema/core';
17
+ import { RFDBClient, loadConfig, RFDBServerBackend } from '@grafema/core';
16
18
  import { exitWithError } from '../utils/errorFormatter.js';
17
19
 
20
+ const __filename = fileURLToPath(import.meta.url);
21
+ const __dirname = dirname(__filename);
22
+
18
23
  // Extend config type for server settings
19
24
  interface ServerConfig {
20
25
  binaryPath?: string;
21
26
  }
22
27
 
28
+ /**
29
+ * Find RFDB server binary in order of preference:
30
+ * 1. Explicit path (from --binary flag or config)
31
+ * 2. Monorepo development (target/release, target/debug)
32
+ * 3. @grafema/rfdb npm package (prebuilt binaries)
33
+ * 4. ~/.local/bin/rfdb-server (user-installed)
34
+ */
35
+ function findServerBinary(explicitPath?: string): string | null {
36
+ // 1. Explicit path from --binary flag or config
37
+ if (explicitPath) {
38
+ const resolved = resolve(explicitPath);
39
+ if (existsSync(resolved)) {
40
+ return resolved;
41
+ }
42
+ // Explicit path specified but not found - this is an error
43
+ console.error(`Specified binary not found: ${resolved}`);
44
+ return null;
45
+ }
46
+
47
+ // 2. Check packages/rfdb-server in monorepo (for development)
48
+ const projectRoot = join(__dirname, '../../../..');
49
+ const releaseBinary = join(projectRoot, 'packages/rfdb-server/target/release/rfdb-server');
50
+ if (existsSync(releaseBinary)) {
51
+ return releaseBinary;
52
+ }
53
+
54
+ const debugBinary = join(projectRoot, 'packages/rfdb-server/target/debug/rfdb-server');
55
+ if (existsSync(debugBinary)) {
56
+ return debugBinary;
57
+ }
58
+
59
+ // 3. Check @grafema/rfdb npm package
60
+ try {
61
+ const rfdbPkg = require.resolve('@grafema/rfdb');
62
+ const rfdbDir = dirname(rfdbPkg);
63
+ const platform = process.platform;
64
+ const arch = process.arch;
65
+
66
+ let platformDir: string;
67
+ if (platform === 'darwin') {
68
+ platformDir = arch === 'arm64' ? 'darwin-arm64' : 'darwin-x64';
69
+ } else if (platform === 'linux') {
70
+ platformDir = arch === 'arm64' ? 'linux-arm64' : 'linux-x64';
71
+ } else {
72
+ platformDir = `${platform}-${arch}`;
73
+ }
74
+
75
+ const npmBinary = join(rfdbDir, 'prebuilt', platformDir, 'rfdb-server');
76
+ if (existsSync(npmBinary)) {
77
+ return npmBinary;
78
+ }
79
+ } catch {
80
+ // @grafema/rfdb not installed
81
+ }
82
+
83
+ // 4. Check ~/.local/bin (user-installed binary for unsupported platforms)
84
+ const homeBinary = join(process.env.HOME || '', '.local', 'bin', 'rfdb-server');
85
+ if (existsSync(homeBinary)) {
86
+ return homeBinary;
87
+ }
88
+
89
+ return null;
90
+ }
91
+
23
92
  /**
24
93
  * Check if server is running by attempting to ping it
25
94
  */
@@ -56,7 +125,7 @@ function getProjectPaths(projectPath: string) {
56
125
 
57
126
  // Create main server command with subcommands
58
127
  export const serverCommand = new Command('server')
59
- .description('Manage RFDB server lifecycle')
128
+ .description('Manage RFDB (Rega Flow Database) server lifecycle')
60
129
  .addHelpText('after', `
61
130
  Examples:
62
131
  grafema server start Start the RFDB server
@@ -104,34 +173,38 @@ serverCommand
104
173
  }
105
174
 
106
175
  // Determine binary path: CLI flag > config > auto-detect
107
- let explicitPath: string | undefined;
176
+ let binaryPath: string | null = null;
108
177
 
109
178
  if (options.binary) {
110
179
  // Explicit --binary flag
111
- explicitPath = options.binary;
180
+ binaryPath = findServerBinary(options.binary);
112
181
  } else {
113
182
  // Try to read from config
114
183
  try {
115
184
  const config = loadConfig(projectPath);
116
185
  const serverConfig = (config as unknown as { server?: ServerConfig }).server;
117
186
  if (serverConfig?.binaryPath) {
118
- explicitPath = serverConfig.binaryPath;
187
+ binaryPath = findServerBinary(serverConfig.binaryPath);
119
188
  }
120
189
  } catch {
121
190
  // Config not found or invalid - continue with auto-detect
122
191
  }
123
- }
124
192
 
125
- const binaryPath = findRfdbBinary({ explicitPath });
193
+ // Auto-detect if not specified
194
+ if (!binaryPath) {
195
+ binaryPath = findServerBinary();
196
+ }
197
+ }
126
198
 
127
199
  if (!binaryPath) {
128
- // If explicit path was given but not found, show specific error
129
- if (explicitPath) {
130
- exitWithError(`Specified binary not found: ${resolve(explicitPath)}`, [
131
- 'Check the path and try again'
132
- ]);
133
- }
134
- exitWithError('RFDB server binary not found', getBinaryNotFoundMessage().split('\n').slice(2));
200
+ exitWithError('RFDB server binary not found', [
201
+ 'Specify path: grafema server start --binary /path/to/rfdb-server',
202
+ 'Or add to config.yaml:',
203
+ ' server:',
204
+ ' binaryPath: /path/to/rfdb-server',
205
+ 'Or install: npm install @grafema/rfdb',
206
+ 'Or build: cargo build --release && cp target/release/rfdb-server ~/.local/bin/'
207
+ ]);
135
208
  }
136
209
 
137
210
  console.log(`Starting RFDB server...`);
@@ -323,3 +396,56 @@ serverCommand
323
396
  }
324
397
  }
325
398
  });
399
+
400
+ // grafema server graphql
401
+ serverCommand
402
+ .command('graphql')
403
+ .description('Start GraphQL API server (requires RFDB server running)')
404
+ .option('-p, --project <path>', 'Project path', '.')
405
+ .option('--port <number>', 'Port to listen on', '4000')
406
+ .option('--host <string>', 'Hostname to bind to', 'localhost')
407
+ .action(async (options: { project: string; port: string; host: string }) => {
408
+ const projectPath = resolve(options.project);
409
+ const { socketPath } = getProjectPaths(projectPath);
410
+
411
+ // Check if RFDB server is running
412
+ const status = await isServerRunning(socketPath);
413
+ if (!status.running) {
414
+ exitWithError('RFDB server not running', [
415
+ 'Start the server first: grafema server start',
416
+ 'Or run: grafema analyze (starts server automatically)'
417
+ ]);
418
+ }
419
+
420
+ // Create backend connection
421
+ const backend = new RFDBServerBackend({ socketPath });
422
+ await backend.connect();
423
+
424
+ // Import and start GraphQL server
425
+ const { startServer } = await import('@grafema/api');
426
+ const port = parseInt(options.port, 10);
427
+
428
+ console.log('Starting Grafema GraphQL API...');
429
+ console.log(` RFDB Socket: ${socketPath}`);
430
+ if (status.version) {
431
+ console.log(` RFDB Version: ${status.version}`);
432
+ }
433
+ console.log('');
434
+
435
+ const server = startServer({
436
+ backend,
437
+ port,
438
+ hostname: options.host,
439
+ });
440
+
441
+ // Handle shutdown
442
+ const shutdown = async () => {
443
+ console.log('\nShutting down GraphQL server...');
444
+ server.close();
445
+ await backend.close();
446
+ process.exit(0);
447
+ };
448
+
449
+ process.on('SIGINT', shutdown);
450
+ process.on('SIGTERM', shutdown);
451
+ });