@cruxy/cli 0.19.0 → 0.21.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 (74) hide show
  1. package/dist/approval/classify.js +24 -0
  2. package/dist/approval/policy.js +7 -0
  3. package/dist/approval/prompt.js +7 -0
  4. package/dist/approval/types.d.ts +6 -0
  5. package/dist/brand/voice.d.ts +1 -1
  6. package/dist/brand/voice.js +1 -1
  7. package/dist/cli/commands/mcp.d.ts +9 -0
  8. package/dist/cli/commands/mcp.js +87 -0
  9. package/dist/cli/commands/run.js +30 -2
  10. package/dist/cli/program.js +2 -0
  11. package/dist/cli/session-factory.d.ts +2 -2
  12. package/dist/cli/session-factory.js +21 -2
  13. package/dist/config/schema.d.ts +344 -33
  14. package/dist/config/schema.js +94 -4
  15. package/dist/constants.d.ts +8 -0
  16. package/dist/constants.js +8 -0
  17. package/dist/errors/constructors.d.ts +40 -0
  18. package/dist/errors/constructors.js +113 -0
  19. package/dist/errors/types.d.ts +19 -0
  20. package/dist/errors/types.js +32 -0
  21. package/dist/lsp/client.d.ts +25 -0
  22. package/dist/lsp/client.js +43 -0
  23. package/dist/lsp/index.d.ts +8 -0
  24. package/dist/lsp/index.js +8 -0
  25. package/dist/lsp/pool.d.ts +48 -0
  26. package/dist/lsp/pool.js +132 -0
  27. package/dist/lsp/registry.d.ts +38 -0
  28. package/dist/lsp/registry.js +133 -0
  29. package/dist/lsp/server.d.ts +48 -0
  30. package/dist/lsp/server.js +264 -0
  31. package/dist/lsp/service.d.ts +44 -0
  32. package/dist/lsp/service.js +76 -0
  33. package/dist/lsp/tools/common.d.ts +23 -0
  34. package/dist/lsp/tools/common.js +75 -0
  35. package/dist/lsp/tools/find-definition.d.ts +23 -0
  36. package/dist/lsp/tools/find-definition.js +41 -0
  37. package/dist/lsp/tools/find-references.d.ts +23 -0
  38. package/dist/lsp/tools/find-references.js +41 -0
  39. package/dist/lsp/tools/get-diagnostics.d.ts +17 -0
  40. package/dist/lsp/tools/get-diagnostics.js +43 -0
  41. package/dist/lsp/tools/hover.d.ts +23 -0
  42. package/dist/lsp/tools/hover.js +38 -0
  43. package/dist/lsp/tools/index.d.ts +4 -0
  44. package/dist/lsp/tools/index.js +4 -0
  45. package/dist/lsp/transport.d.ts +39 -0
  46. package/dist/lsp/transport.js +208 -0
  47. package/dist/lsp/types.d.ts +107 -0
  48. package/dist/lsp/types.js +1 -0
  49. package/dist/mcp/adapter.d.ts +44 -0
  50. package/dist/mcp/adapter.js +70 -0
  51. package/dist/mcp/bounds.d.ts +35 -0
  52. package/dist/mcp/bounds.js +36 -0
  53. package/dist/mcp/client.d.ts +19 -0
  54. package/dist/mcp/client.js +93 -0
  55. package/dist/mcp/demarcate.d.ts +12 -0
  56. package/dist/mcp/demarcate.js +71 -0
  57. package/dist/mcp/index.d.ts +9 -0
  58. package/dist/mcp/index.js +8 -0
  59. package/dist/mcp/service.d.ts +54 -0
  60. package/dist/mcp/service.js +99 -0
  61. package/dist/mcp/transport.d.ts +30 -0
  62. package/dist/mcp/transport.js +188 -0
  63. package/dist/mcp/trust-gate.d.ts +35 -0
  64. package/dist/mcp/trust-gate.js +40 -0
  65. package/dist/mcp/trust.d.ts +52 -0
  66. package/dist/mcp/trust.js +111 -0
  67. package/dist/mcp/types.d.ts +52 -0
  68. package/dist/mcp/types.js +7 -0
  69. package/dist/tools/file/grep-files.d.ts +2 -2
  70. package/dist/tools/registry.js +3 -1
  71. package/dist/tools/types.d.ts +15 -1
  72. package/dist/utils/child-tree.d.ts +35 -0
  73. package/dist/utils/child-tree.js +76 -0
  74. package/package.json +1 -1
@@ -236,8 +236,8 @@ export declare const TestConfigSchema: z.ZodObject<{
236
236
  captureBytes: number;
237
237
  command?: string | undefined;
238
238
  }, {
239
- maxIterations?: number | undefined;
240
239
  command?: string | undefined;
240
+ maxIterations?: number | undefined;
241
241
  captureBytes?: number | undefined;
242
242
  }>;
243
243
  /**
@@ -264,9 +264,9 @@ export declare const SubagentConfigSchema: z.ZodObject<{
264
264
  maxIterations: number;
265
265
  timeoutMs?: number | undefined;
266
266
  }, {
267
+ timeoutMs?: number | undefined;
267
268
  maxTokens?: number | undefined;
268
269
  maxIterations?: number | undefined;
269
- timeoutMs?: number | undefined;
270
270
  }>>;
271
271
  }, "strict", z.ZodTypeAny, {
272
272
  maxDepth: number;
@@ -278,9 +278,9 @@ export declare const SubagentConfigSchema: z.ZodObject<{
278
278
  }, {
279
279
  maxDepth?: number | undefined;
280
280
  defaultBudget?: {
281
+ timeoutMs?: number | undefined;
281
282
  maxTokens?: number | undefined;
282
283
  maxIterations?: number | undefined;
283
- timeoutMs?: number | undefined;
284
284
  } | undefined;
285
285
  }>;
286
286
  /**
@@ -408,6 +408,59 @@ export declare const MemoryConfigSchema: z.ZodObject<{
408
408
  maxRecallTokens?: number | undefined;
409
409
  }>;
410
410
  export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
411
+ /**
412
+ * Per-language LSP integration (C.12): read-only, symbol-aware tools
413
+ * (find_definition, find_references, get_diagnostics, hover) backed by a
414
+ * managed pool of language servers. OFF by default — like the sandbox and
415
+ * hooks, it spawns and manages EXTERNAL server processes, a real execution
416
+ * surface the user opts into explicitly. When on, a missing server binary is
417
+ * an actionable, coded failure (CRUXY_E_LSP_SERVER_NOT_FOUND), never a silent
418
+ * empty result. Servers spawn lazily per language, are reused for the session,
419
+ * and are shut down cleanly (with a process-exit kill-tree backstop).
420
+ */
421
+ export declare const LspConfigSchema: z.ZodObject<{
422
+ /** Master switch. When false, the four LSP tools are not registered and no
423
+ * server ever spawns (the feature stays fully inert). */
424
+ enabled: z.ZodDefault<z.ZodBoolean>;
425
+ /**
426
+ * Per-language command overrides, keyed by language id (e.g. `typescript`,
427
+ * `python`, `go`, `rust`). The value is the full command line
428
+ * (`"gopls -remote=auto"`); it is split on whitespace. Anything omitted
429
+ * falls back to the built-in default table (see `lsp/registry.ts`).
430
+ */
431
+ servers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
432
+ /** Kill and fail (CRUXY_E_LSP_TIMEOUT) if the `initialize` handshake does
433
+ * not complete within this many ms. */
434
+ startupTimeout: z.ZodDefault<z.ZodNumber>;
435
+ /** Fail a single query (CRUXY_E_LSP_TIMEOUT) if the server does not respond
436
+ * within this many ms — the server is kept alive, only the request errors. */
437
+ requestTimeout: z.ZodDefault<z.ZodNumber>;
438
+ /** Maximum concurrently-running language servers; acquiring past the cap
439
+ * evicts the idle-oldest server first. */
440
+ maxServers: z.ZodDefault<z.ZodNumber>;
441
+ /** Shut a server down after it has been idle this many ms. */
442
+ idleTimeout: z.ZodDefault<z.ZodNumber>;
443
+ /** Cap on locations/diagnostics returned by a single tool call; the rest are
444
+ * summarized as an "N more" note (bounded output). */
445
+ maxResults: z.ZodDefault<z.ZodNumber>;
446
+ }, "strict", z.ZodTypeAny, {
447
+ startupTimeout: number;
448
+ requestTimeout: number;
449
+ servers: Record<string, string>;
450
+ enabled: boolean;
451
+ maxServers: number;
452
+ idleTimeout: number;
453
+ maxResults: number;
454
+ }, {
455
+ startupTimeout?: number | undefined;
456
+ requestTimeout?: number | undefined;
457
+ servers?: Record<string, string> | undefined;
458
+ enabled?: boolean | undefined;
459
+ maxServers?: number | undefined;
460
+ idleTimeout?: number | undefined;
461
+ maxResults?: number | undefined;
462
+ }>;
463
+ export type LspConfig = z.infer<typeof LspConfigSchema>;
411
464
  /** A per-tier price, in the user's own currency, PER MILLION TOKENS (C.22). */
412
465
  export declare const TierPriceSchema: z.ZodObject<{
413
466
  /** Price per 1,000,000 input tokens. */
@@ -540,20 +593,135 @@ export declare const UsageConfigSchema: z.ZodObject<{
540
593
  } | undefined;
541
594
  }>;
542
595
  export type UsageConfig = z.infer<typeof UsageConfigSchema>;
543
- /** MCP server entry — stdio or URL transport (wired up in a later phase). */
544
- export declare const McpServerSchema: z.ZodObject<{
596
+ /**
597
+ * One MCP server entry (C.27). A `command` (+ optional `args`/`env`) is a stdio
598
+ * server cruxy spawns as a child process; a `url` names a remote server. Exactly
599
+ * one transport must be given. Trusting a stdio server runs its code UNSANDBOXED
600
+ * with your privileges, which is why connection is gated by an explicit,
601
+ * fingerprinted trust decision (see `mcp/trust.ts`).
602
+ */
603
+ export declare const McpServerSchema: z.ZodEffects<z.ZodObject<{
604
+ /** stdio transport: the server program to spawn. */
545
605
  command: z.ZodOptional<z.ZodString>;
546
- args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
606
+ /** Arguments for `command`. */
607
+ args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
608
+ /** Extra environment variables for the spawned server (stdio only). */
609
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
610
+ /** Remote transport: the server URL (mutually exclusive with `command`). */
547
611
  url: z.ZodOptional<z.ZodString>;
548
612
  }, "strict", z.ZodTypeAny, {
613
+ args: string[];
614
+ env: Record<string, string>;
549
615
  command?: string | undefined;
550
- args?: string[] | undefined;
551
616
  url?: string | undefined;
552
617
  }, {
553
618
  command?: string | undefined;
619
+ url?: string | undefined;
554
620
  args?: string[] | undefined;
621
+ env?: Record<string, string> | undefined;
622
+ }>, {
623
+ args: string[];
624
+ env: Record<string, string>;
625
+ command?: string | undefined;
555
626
  url?: string | undefined;
627
+ }, {
628
+ command?: string | undefined;
629
+ url?: string | undefined;
630
+ args?: string[] | undefined;
631
+ env?: Record<string, string> | undefined;
632
+ }>;
633
+ export type McpServerConfig = z.infer<typeof McpServerSchema>;
634
+ /**
635
+ * MCP client integration (C.27): connect to trusted MCP servers and expose their
636
+ * tools to the agent. OFF by default — like the sandbox, LSP, and hooks, it runs
637
+ * EXTERNAL code (a stdio server executes UNSANDBOXED with your privileges), a
638
+ * real execution surface you opt into explicitly. When off, nothing connects or
639
+ * spawns and no MCP tool is ever registered. Every server-advertised tool is
640
+ * gated (destructive tier — a server can never self-declare a tool "safe"), its
641
+ * description and results are demarcated as untrusted external data with upstream
642
+ * model names scrubbed, and results are NEVER persisted. The tool list a server
643
+ * advertises is bounded (count + per-tool description/schema size) so a hostile
644
+ * server can't blow the context budget.
645
+ */
646
+ export declare const McpConfigSchema: z.ZodObject<{
647
+ /** Master switch. When false, no server is connected/spawned and no MCP tool
648
+ * is registered (the feature stays fully inert). */
649
+ enabled: z.ZodDefault<z.ZodBoolean>;
650
+ /** Named MCP servers, keyed by a short server id used as the tool prefix
651
+ * (`mcp__<server>__<tool>`) and in the trust prompt. */
652
+ servers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
653
+ /** stdio transport: the server program to spawn. */
654
+ command: z.ZodOptional<z.ZodString>;
655
+ /** Arguments for `command`. */
656
+ args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
657
+ /** Extra environment variables for the spawned server (stdio only). */
658
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
659
+ /** Remote transport: the server URL (mutually exclusive with `command`). */
660
+ url: z.ZodOptional<z.ZodString>;
661
+ }, "strict", z.ZodTypeAny, {
662
+ args: string[];
663
+ env: Record<string, string>;
664
+ command?: string | undefined;
665
+ url?: string | undefined;
666
+ }, {
667
+ command?: string | undefined;
668
+ url?: string | undefined;
669
+ args?: string[] | undefined;
670
+ env?: Record<string, string> | undefined;
671
+ }>, {
672
+ args: string[];
673
+ env: Record<string, string>;
674
+ command?: string | undefined;
675
+ url?: string | undefined;
676
+ }, {
677
+ command?: string | undefined;
678
+ url?: string | undefined;
679
+ args?: string[] | undefined;
680
+ env?: Record<string, string> | undefined;
681
+ }>>>;
682
+ /** Fail a server's `initialize` handshake (its tools are skipped) if it does
683
+ * not complete within this many ms. */
684
+ startupTimeout: z.ZodDefault<z.ZodNumber>;
685
+ /** Fail a single `tools/call` if the server does not respond within this many
686
+ * ms — the connection is kept, only the one call errors. */
687
+ requestTimeout: z.ZodDefault<z.ZodNumber>;
688
+ /** Max tools accepted from ONE server; extras are dropped with a visible note
689
+ * (a hostile server can't advertise thousands of tools to flood context). */
690
+ maxToolsPerServer: z.ZodDefault<z.ZodNumber>;
691
+ /** Max characters kept from a single tool's description; the rest is truncated
692
+ * with a visible marker. */
693
+ maxDescriptionChars: z.ZodDefault<z.ZodNumber>;
694
+ /** Max bytes kept from a single tool's advertised JSON input schema; an
695
+ * over-cap schema is replaced with a permissive one and a visible note. */
696
+ maxSchemaBytes: z.ZodDefault<z.ZodNumber>;
697
+ }, "strict", z.ZodTypeAny, {
698
+ startupTimeout: number;
699
+ requestTimeout: number;
700
+ servers: Record<string, {
701
+ args: string[];
702
+ env: Record<string, string>;
703
+ command?: string | undefined;
704
+ url?: string | undefined;
705
+ }>;
706
+ enabled: boolean;
707
+ maxToolsPerServer: number;
708
+ maxDescriptionChars: number;
709
+ maxSchemaBytes: number;
710
+ }, {
711
+ startupTimeout?: number | undefined;
712
+ requestTimeout?: number | undefined;
713
+ servers?: Record<string, {
714
+ command?: string | undefined;
715
+ url?: string | undefined;
716
+ args?: string[] | undefined;
717
+ env?: Record<string, string> | undefined;
718
+ }> | undefined;
719
+ enabled?: boolean | undefined;
720
+ maxToolsPerServer?: number | undefined;
721
+ maxDescriptionChars?: number | undefined;
722
+ maxSchemaBytes?: number | undefined;
556
723
  }>;
724
+ export type McpConfig = z.infer<typeof McpConfigSchema>;
557
725
  export declare const CruxyConfigSchema: z.ZodObject<{
558
726
  model: z.ZodDefault<z.ZodObject<{
559
727
  provider: z.ZodDefault<z.ZodEnum<["cruxy", "anthropic", "openai", "custom"]>>;
@@ -742,6 +910,48 @@ export declare const CruxyConfigSchema: z.ZodObject<{
742
910
  overlapLines?: number | undefined;
743
911
  } | undefined;
744
912
  }>>;
913
+ lsp: z.ZodDefault<z.ZodObject<{
914
+ /** Master switch. When false, the four LSP tools are not registered and no
915
+ * server ever spawns (the feature stays fully inert). */
916
+ enabled: z.ZodDefault<z.ZodBoolean>;
917
+ /**
918
+ * Per-language command overrides, keyed by language id (e.g. `typescript`,
919
+ * `python`, `go`, `rust`). The value is the full command line
920
+ * (`"gopls -remote=auto"`); it is split on whitespace. Anything omitted
921
+ * falls back to the built-in default table (see `lsp/registry.ts`).
922
+ */
923
+ servers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
924
+ /** Kill and fail (CRUXY_E_LSP_TIMEOUT) if the `initialize` handshake does
925
+ * not complete within this many ms. */
926
+ startupTimeout: z.ZodDefault<z.ZodNumber>;
927
+ /** Fail a single query (CRUXY_E_LSP_TIMEOUT) if the server does not respond
928
+ * within this many ms — the server is kept alive, only the request errors. */
929
+ requestTimeout: z.ZodDefault<z.ZodNumber>;
930
+ /** Maximum concurrently-running language servers; acquiring past the cap
931
+ * evicts the idle-oldest server first. */
932
+ maxServers: z.ZodDefault<z.ZodNumber>;
933
+ /** Shut a server down after it has been idle this many ms. */
934
+ idleTimeout: z.ZodDefault<z.ZodNumber>;
935
+ /** Cap on locations/diagnostics returned by a single tool call; the rest are
936
+ * summarized as an "N more" note (bounded output). */
937
+ maxResults: z.ZodDefault<z.ZodNumber>;
938
+ }, "strict", z.ZodTypeAny, {
939
+ startupTimeout: number;
940
+ requestTimeout: number;
941
+ servers: Record<string, string>;
942
+ enabled: boolean;
943
+ maxServers: number;
944
+ idleTimeout: number;
945
+ maxResults: number;
946
+ }, {
947
+ startupTimeout?: number | undefined;
948
+ requestTimeout?: number | undefined;
949
+ servers?: Record<string, string> | undefined;
950
+ enabled?: boolean | undefined;
951
+ maxServers?: number | undefined;
952
+ idleTimeout?: number | undefined;
953
+ maxResults?: number | undefined;
954
+ }>>;
745
955
  checkpoint: z.ZodDefault<z.ZodObject<{
746
956
  /** Auto-checkpoint before a run's first mutation (and enable `cruxy rollback`). */
747
957
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -773,9 +983,9 @@ export declare const CruxyConfigSchema: z.ZodObject<{
773
983
  maxIterations: number;
774
984
  timeoutMs?: number | undefined;
775
985
  }, {
986
+ timeoutMs?: number | undefined;
776
987
  maxTokens?: number | undefined;
777
988
  maxIterations?: number | undefined;
778
- timeoutMs?: number | undefined;
779
989
  }>>;
780
990
  }, "strict", z.ZodTypeAny, {
781
991
  maxDepth: number;
@@ -787,9 +997,9 @@ export declare const CruxyConfigSchema: z.ZodObject<{
787
997
  }, {
788
998
  maxDepth?: number | undefined;
789
999
  defaultBudget?: {
1000
+ timeoutMs?: number | undefined;
790
1001
  maxTokens?: number | undefined;
791
1002
  maxIterations?: number | undefined;
792
- timeoutMs?: number | undefined;
793
1003
  } | undefined;
794
1004
  }>>;
795
1005
  test: z.ZodDefault<z.ZodObject<{
@@ -804,8 +1014,8 @@ export declare const CruxyConfigSchema: z.ZodObject<{
804
1014
  captureBytes: number;
805
1015
  command?: string | undefined;
806
1016
  }, {
807
- maxIterations?: number | undefined;
808
1017
  command?: string | undefined;
1018
+ maxIterations?: number | undefined;
809
1019
  captureBytes?: number | undefined;
810
1020
  }>>;
811
1021
  sandbox: z.ZodDefault<z.ZodObject<{
@@ -1009,19 +1219,84 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1009
1219
  } | undefined;
1010
1220
  } | undefined;
1011
1221
  }>>;
1012
- mcpServers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
1013
- command: z.ZodOptional<z.ZodString>;
1014
- args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
1015
- url: z.ZodOptional<z.ZodString>;
1222
+ mcp: z.ZodDefault<z.ZodObject<{
1223
+ /** Master switch. When false, no server is connected/spawned and no MCP tool
1224
+ * is registered (the feature stays fully inert). */
1225
+ enabled: z.ZodDefault<z.ZodBoolean>;
1226
+ /** Named MCP servers, keyed by a short server id used as the tool prefix
1227
+ * (`mcp__<server>__<tool>`) and in the trust prompt. */
1228
+ servers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
1229
+ /** stdio transport: the server program to spawn. */
1230
+ command: z.ZodOptional<z.ZodString>;
1231
+ /** Arguments for `command`. */
1232
+ args: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
1233
+ /** Extra environment variables for the spawned server (stdio only). */
1234
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
1235
+ /** Remote transport: the server URL (mutually exclusive with `command`). */
1236
+ url: z.ZodOptional<z.ZodString>;
1237
+ }, "strict", z.ZodTypeAny, {
1238
+ args: string[];
1239
+ env: Record<string, string>;
1240
+ command?: string | undefined;
1241
+ url?: string | undefined;
1242
+ }, {
1243
+ command?: string | undefined;
1244
+ url?: string | undefined;
1245
+ args?: string[] | undefined;
1246
+ env?: Record<string, string> | undefined;
1247
+ }>, {
1248
+ args: string[];
1249
+ env: Record<string, string>;
1250
+ command?: string | undefined;
1251
+ url?: string | undefined;
1252
+ }, {
1253
+ command?: string | undefined;
1254
+ url?: string | undefined;
1255
+ args?: string[] | undefined;
1256
+ env?: Record<string, string> | undefined;
1257
+ }>>>;
1258
+ /** Fail a server's `initialize` handshake (its tools are skipped) if it does
1259
+ * not complete within this many ms. */
1260
+ startupTimeout: z.ZodDefault<z.ZodNumber>;
1261
+ /** Fail a single `tools/call` if the server does not respond within this many
1262
+ * ms — the connection is kept, only the one call errors. */
1263
+ requestTimeout: z.ZodDefault<z.ZodNumber>;
1264
+ /** Max tools accepted from ONE server; extras are dropped with a visible note
1265
+ * (a hostile server can't advertise thousands of tools to flood context). */
1266
+ maxToolsPerServer: z.ZodDefault<z.ZodNumber>;
1267
+ /** Max characters kept from a single tool's description; the rest is truncated
1268
+ * with a visible marker. */
1269
+ maxDescriptionChars: z.ZodDefault<z.ZodNumber>;
1270
+ /** Max bytes kept from a single tool's advertised JSON input schema; an
1271
+ * over-cap schema is replaced with a permissive one and a visible note. */
1272
+ maxSchemaBytes: z.ZodDefault<z.ZodNumber>;
1016
1273
  }, "strict", z.ZodTypeAny, {
1017
- command?: string | undefined;
1018
- args?: string[] | undefined;
1019
- url?: string | undefined;
1274
+ startupTimeout: number;
1275
+ requestTimeout: number;
1276
+ servers: Record<string, {
1277
+ args: string[];
1278
+ env: Record<string, string>;
1279
+ command?: string | undefined;
1280
+ url?: string | undefined;
1281
+ }>;
1282
+ enabled: boolean;
1283
+ maxToolsPerServer: number;
1284
+ maxDescriptionChars: number;
1285
+ maxSchemaBytes: number;
1020
1286
  }, {
1021
- command?: string | undefined;
1022
- args?: string[] | undefined;
1023
- url?: string | undefined;
1024
- }>>>;
1287
+ startupTimeout?: number | undefined;
1288
+ requestTimeout?: number | undefined;
1289
+ servers?: Record<string, {
1290
+ command?: string | undefined;
1291
+ url?: string | undefined;
1292
+ args?: string[] | undefined;
1293
+ env?: Record<string, string> | undefined;
1294
+ }> | undefined;
1295
+ enabled?: boolean | undefined;
1296
+ maxToolsPerServer?: number | undefined;
1297
+ maxDescriptionChars?: number | undefined;
1298
+ maxSchemaBytes?: number | undefined;
1299
+ }>>;
1025
1300
  logLevel: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error", "silent"]>>;
1026
1301
  }, "strict", z.ZodTypeAny, {
1027
1302
  cruxy: {
@@ -1120,6 +1395,15 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1120
1395
  overlapLines: number;
1121
1396
  };
1122
1397
  };
1398
+ lsp: {
1399
+ startupTimeout: number;
1400
+ requestTimeout: number;
1401
+ servers: Record<string, string>;
1402
+ enabled: boolean;
1403
+ maxServers: number;
1404
+ idleTimeout: number;
1405
+ maxResults: number;
1406
+ };
1123
1407
  test: {
1124
1408
  maxIterations: number;
1125
1409
  captureBytes: number;
@@ -1133,11 +1417,20 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1133
1417
  map: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">>;
1134
1418
  default?: "kavi" | "vaani" | "mira" | undefined;
1135
1419
  };
1136
- mcpServers: Record<string, {
1137
- command?: string | undefined;
1138
- args?: string[] | undefined;
1139
- url?: string | undefined;
1140
- }>;
1420
+ mcp: {
1421
+ startupTimeout: number;
1422
+ requestTimeout: number;
1423
+ servers: Record<string, {
1424
+ args: string[];
1425
+ env: Record<string, string>;
1426
+ command?: string | undefined;
1427
+ url?: string | undefined;
1428
+ }>;
1429
+ enabled: boolean;
1430
+ maxToolsPerServer: number;
1431
+ maxDescriptionChars: number;
1432
+ maxSchemaBytes: number;
1433
+ };
1141
1434
  logLevel: "debug" | "info" | "warn" | "error" | "silent";
1142
1435
  }, {
1143
1436
  cruxy?: {
@@ -1186,9 +1479,9 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1186
1479
  subagent?: {
1187
1480
  maxDepth?: number | undefined;
1188
1481
  defaultBudget?: {
1482
+ timeoutMs?: number | undefined;
1189
1483
  maxTokens?: number | undefined;
1190
1484
  maxIterations?: number | undefined;
1191
- timeoutMs?: number | undefined;
1192
1485
  } | undefined;
1193
1486
  } | undefined;
1194
1487
  model?: {
@@ -1236,9 +1529,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1236
1529
  overlapLines?: number | undefined;
1237
1530
  } | undefined;
1238
1531
  } | undefined;
1532
+ lsp?: {
1533
+ startupTimeout?: number | undefined;
1534
+ requestTimeout?: number | undefined;
1535
+ servers?: Record<string, string> | undefined;
1536
+ enabled?: boolean | undefined;
1537
+ maxServers?: number | undefined;
1538
+ idleTimeout?: number | undefined;
1539
+ maxResults?: number | undefined;
1540
+ } | undefined;
1239
1541
  test?: {
1240
- maxIterations?: number | undefined;
1241
1542
  command?: string | undefined;
1543
+ maxIterations?: number | undefined;
1242
1544
  captureBytes?: number | undefined;
1243
1545
  } | undefined;
1244
1546
  hooks?: {
@@ -1249,11 +1551,20 @@ export declare const CruxyConfigSchema: z.ZodObject<{
1249
1551
  map?: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">> | undefined;
1250
1552
  default?: "kavi" | "vaani" | "mira" | undefined;
1251
1553
  } | undefined;
1252
- mcpServers?: Record<string, {
1253
- command?: string | undefined;
1254
- args?: string[] | undefined;
1255
- url?: string | undefined;
1256
- }> | undefined;
1554
+ mcp?: {
1555
+ startupTimeout?: number | undefined;
1556
+ requestTimeout?: number | undefined;
1557
+ servers?: Record<string, {
1558
+ command?: string | undefined;
1559
+ url?: string | undefined;
1560
+ args?: string[] | undefined;
1561
+ env?: Record<string, string> | undefined;
1562
+ }> | undefined;
1563
+ enabled?: boolean | undefined;
1564
+ maxToolsPerServer?: number | undefined;
1565
+ maxDescriptionChars?: number | undefined;
1566
+ maxSchemaBytes?: number | undefined;
1567
+ } | undefined;
1257
1568
  logLevel?: "debug" | "info" | "warn" | "error" | "silent" | undefined;
1258
1569
  }>;
1259
1570
  export type CruxyConfig = z.infer<typeof CruxyConfigSchema>;
@@ -290,6 +290,44 @@ export const MemoryConfigSchema = z
290
290
  maxRecallTokens: z.number().int().positive().default(1000),
291
291
  })
292
292
  .strict();
293
+ /**
294
+ * Per-language LSP integration (C.12): read-only, symbol-aware tools
295
+ * (find_definition, find_references, get_diagnostics, hover) backed by a
296
+ * managed pool of language servers. OFF by default — like the sandbox and
297
+ * hooks, it spawns and manages EXTERNAL server processes, a real execution
298
+ * surface the user opts into explicitly. When on, a missing server binary is
299
+ * an actionable, coded failure (CRUXY_E_LSP_SERVER_NOT_FOUND), never a silent
300
+ * empty result. Servers spawn lazily per language, are reused for the session,
301
+ * and are shut down cleanly (with a process-exit kill-tree backstop).
302
+ */
303
+ export const LspConfigSchema = z
304
+ .object({
305
+ /** Master switch. When false, the four LSP tools are not registered and no
306
+ * server ever spawns (the feature stays fully inert). */
307
+ enabled: z.boolean().default(false),
308
+ /**
309
+ * Per-language command overrides, keyed by language id (e.g. `typescript`,
310
+ * `python`, `go`, `rust`). The value is the full command line
311
+ * (`"gopls -remote=auto"`); it is split on whitespace. Anything omitted
312
+ * falls back to the built-in default table (see `lsp/registry.ts`).
313
+ */
314
+ servers: z.record(z.string(), z.string().min(1)).default({}),
315
+ /** Kill and fail (CRUXY_E_LSP_TIMEOUT) if the `initialize` handshake does
316
+ * not complete within this many ms. */
317
+ startupTimeout: z.number().int().positive().default(15000),
318
+ /** Fail a single query (CRUXY_E_LSP_TIMEOUT) if the server does not respond
319
+ * within this many ms — the server is kept alive, only the request errors. */
320
+ requestTimeout: z.number().int().positive().default(10000),
321
+ /** Maximum concurrently-running language servers; acquiring past the cap
322
+ * evicts the idle-oldest server first. */
323
+ maxServers: z.number().int().positive().default(4),
324
+ /** Shut a server down after it has been idle this many ms. */
325
+ idleTimeout: z.number().int().positive().default(300000),
326
+ /** Cap on locations/diagnostics returned by a single tool call; the rest are
327
+ * summarized as an "N more" note (bounded output). */
328
+ maxResults: z.number().int().positive().default(100),
329
+ })
330
+ .strict();
293
331
  /** A per-tier price, in the user's own currency, PER MILLION TOKENS (C.22). */
294
332
  export const TierPriceSchema = z
295
333
  .object({
@@ -327,12 +365,63 @@ export const UsageConfigSchema = z
327
365
  .default({}),
328
366
  })
329
367
  .strict();
330
- /** MCP server entry — stdio or URL transport (wired up in a later phase). */
368
+ /**
369
+ * One MCP server entry (C.27). A `command` (+ optional `args`/`env`) is a stdio
370
+ * server cruxy spawns as a child process; a `url` names a remote server. Exactly
371
+ * one transport must be given. Trusting a stdio server runs its code UNSANDBOXED
372
+ * with your privileges, which is why connection is gated by an explicit,
373
+ * fingerprinted trust decision (see `mcp/trust.ts`).
374
+ */
331
375
  export const McpServerSchema = z
332
376
  .object({
333
- command: z.string().optional(),
334
- args: z.array(z.string()).optional(),
377
+ /** stdio transport: the server program to spawn. */
378
+ command: z.string().min(1).optional(),
379
+ /** Arguments for `command`. */
380
+ args: z.array(z.string()).default([]),
381
+ /** Extra environment variables for the spawned server (stdio only). */
382
+ env: z.record(z.string(), z.string()).default({}),
383
+ /** Remote transport: the server URL (mutually exclusive with `command`). */
335
384
  url: z.string().url().optional(),
385
+ })
386
+ .strict()
387
+ .refine((s) => Boolean(s.command) !== Boolean(s.url), {
388
+ message: "an MCP server needs exactly one of `command` (stdio) or `url`",
389
+ });
390
+ /**
391
+ * MCP client integration (C.27): connect to trusted MCP servers and expose their
392
+ * tools to the agent. OFF by default — like the sandbox, LSP, and hooks, it runs
393
+ * EXTERNAL code (a stdio server executes UNSANDBOXED with your privileges), a
394
+ * real execution surface you opt into explicitly. When off, nothing connects or
395
+ * spawns and no MCP tool is ever registered. Every server-advertised tool is
396
+ * gated (destructive tier — a server can never self-declare a tool "safe"), its
397
+ * description and results are demarcated as untrusted external data with upstream
398
+ * model names scrubbed, and results are NEVER persisted. The tool list a server
399
+ * advertises is bounded (count + per-tool description/schema size) so a hostile
400
+ * server can't blow the context budget.
401
+ */
402
+ export const McpConfigSchema = z
403
+ .object({
404
+ /** Master switch. When false, no server is connected/spawned and no MCP tool
405
+ * is registered (the feature stays fully inert). */
406
+ enabled: z.boolean().default(false),
407
+ /** Named MCP servers, keyed by a short server id used as the tool prefix
408
+ * (`mcp__<server>__<tool>`) and in the trust prompt. */
409
+ servers: z.record(z.string(), McpServerSchema).default({}),
410
+ /** Fail a server's `initialize` handshake (its tools are skipped) if it does
411
+ * not complete within this many ms. */
412
+ startupTimeout: z.number().int().positive().default(15000),
413
+ /** Fail a single `tools/call` if the server does not respond within this many
414
+ * ms — the connection is kept, only the one call errors. */
415
+ requestTimeout: z.number().int().positive().default(30000),
416
+ /** Max tools accepted from ONE server; extras are dropped with a visible note
417
+ * (a hostile server can't advertise thousands of tools to flood context). */
418
+ maxToolsPerServer: z.number().int().positive().default(32),
419
+ /** Max characters kept from a single tool's description; the rest is truncated
420
+ * with a visible marker. */
421
+ maxDescriptionChars: z.number().int().positive().default(1024),
422
+ /** Max bytes kept from a single tool's advertised JSON input schema; an
423
+ * over-cap schema is replaced with a permissive one and a visible note. */
424
+ maxSchemaBytes: z.number().int().positive().default(8192),
336
425
  })
337
426
  .strict();
338
427
  export const CruxyConfigSchema = z
@@ -346,6 +435,7 @@ export const CruxyConfigSchema = z
346
435
  context: ContextConfigSchema.default({}),
347
436
  approval: ApprovalConfigSchema.default({}),
348
437
  index: IndexConfigSchema.default({}),
438
+ lsp: LspConfigSchema.default({}),
349
439
  checkpoint: CheckpointConfigSchema.default({}),
350
440
  subagent: SubagentConfigSchema.default({}),
351
441
  test: TestConfigSchema.default({}),
@@ -354,7 +444,7 @@ export const CruxyConfigSchema = z
354
444
  routing: RoutingConfigSchema.default({}),
355
445
  memory: MemoryConfigSchema.default({}),
356
446
  usage: UsageConfigSchema.default({}),
357
- mcpServers: z.record(z.string(), McpServerSchema).default({}),
447
+ mcp: McpConfigSchema.default({}),
358
448
  logLevel: z.enum(LOG_LEVELS).default("info"),
359
449
  })
360
450
  .strict();
@@ -41,6 +41,14 @@ export declare const MEMORY_FILE_NAME = "entries.json";
41
41
  * (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
42
42
  * cloning a repo carries zero memory trust (C.29 supply-chain safety). */
43
43
  export declare const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
44
+ /**
45
+ * MCP server integration (C.27). Per-repo trust for configured MCP servers, in
46
+ * the GLOBAL dir only (`~/.cruxy/mcp-trust.json`) — its own file, independent of
47
+ * hook and memory trust, so cloning a repo carries zero MCP trust. Trusting a
48
+ * server runs its code UNSANDBOXED with your privileges, so this decision is
49
+ * fingerprinted and re-checked on every run (C.27 supply-chain safety).
50
+ */
51
+ export declare const MCP_TRUST_FILE_NAME = "mcp-trust.json";
44
52
  /**
45
53
  * Usage telemetry + cost tracking (C.22). Per-run/per-session usage records live
46
54
  * in the GLOBAL dir only (`~/.cruxy/usage/runs.json`), `0600` — LOCAL accounting
package/dist/constants.js CHANGED
@@ -61,6 +61,14 @@ export const MEMORY_FILE_NAME = "entries.json";
61
61
  * (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
62
62
  * cloning a repo carries zero memory trust (C.29 supply-chain safety). */
63
63
  export const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
64
+ /**
65
+ * MCP server integration (C.27). Per-repo trust for configured MCP servers, in
66
+ * the GLOBAL dir only (`~/.cruxy/mcp-trust.json`) — its own file, independent of
67
+ * hook and memory trust, so cloning a repo carries zero MCP trust. Trusting a
68
+ * server runs its code UNSANDBOXED with your privileges, so this decision is
69
+ * fingerprinted and re-checked on every run (C.27 supply-chain safety).
70
+ */
71
+ export const MCP_TRUST_FILE_NAME = "mcp-trust.json";
64
72
  /**
65
73
  * Usage telemetry + cost tracking (C.22). Per-run/per-session usage records live
66
74
  * in the GLOBAL dir only (`~/.cruxy/usage/runs.json`), `0600` — LOCAL accounting