@cruxy/cli 0.17.0 → 0.19.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 (53) hide show
  1. package/dist/agent/loop.d.ts +15 -0
  2. package/dist/agent/loop.js +21 -0
  3. package/dist/agent/prompts.d.ts +7 -0
  4. package/dist/agent/prompts.js +6 -0
  5. package/dist/agent/session.d.ts +26 -1
  6. package/dist/agent/session.js +39 -6
  7. package/dist/cli/commands/memory.d.ts +8 -0
  8. package/dist/cli/commands/memory.js +98 -0
  9. package/dist/cli/commands/run.js +19 -0
  10. package/dist/cli/commands/usage.d.ts +9 -0
  11. package/dist/cli/commands/usage.js +81 -0
  12. package/dist/cli/program.js +4 -0
  13. package/dist/cli/session-factory.js +39 -1
  14. package/dist/config/schema.d.ts +336 -12
  15. package/dist/config/schema.js +56 -0
  16. package/dist/constants.d.ts +18 -0
  17. package/dist/constants.js +18 -0
  18. package/dist/errors/constructors.d.ts +21 -0
  19. package/dist/errors/constructors.js +64 -0
  20. package/dist/errors/types.d.ts +12 -0
  21. package/dist/errors/types.js +24 -0
  22. package/dist/hooks/types.d.ts +1 -1
  23. package/dist/memory/index.d.ts +7 -0
  24. package/dist/memory/index.js +7 -0
  25. package/dist/memory/recall.d.ts +32 -0
  26. package/dist/memory/recall.js +73 -0
  27. package/dist/memory/remember-tool.d.ts +25 -0
  28. package/dist/memory/remember-tool.js +56 -0
  29. package/dist/memory/secrets.d.ts +29 -0
  30. package/dist/memory/secrets.js +61 -0
  31. package/dist/memory/service.d.ts +92 -0
  32. package/dist/memory/service.js +164 -0
  33. package/dist/memory/store.d.ts +32 -0
  34. package/dist/memory/store.js +100 -0
  35. package/dist/memory/trust.d.ts +52 -0
  36. package/dist/memory/trust.js +106 -0
  37. package/dist/memory/types.d.ts +101 -0
  38. package/dist/memory/types.js +58 -0
  39. package/dist/plan/service.d.ts +13 -1
  40. package/dist/plan/service.js +4 -0
  41. package/dist/usage/collect.d.ts +40 -0
  42. package/dist/usage/collect.js +34 -0
  43. package/dist/usage/cost.d.ts +19 -0
  44. package/dist/usage/cost.js +29 -0
  45. package/dist/usage/index.d.ts +15 -0
  46. package/dist/usage/index.js +15 -0
  47. package/dist/usage/store.d.ts +37 -0
  48. package/dist/usage/store.js +83 -0
  49. package/dist/usage/summary.d.ts +32 -0
  50. package/dist/usage/summary.js +119 -0
  51. package/dist/usage/types.d.ts +220 -0
  52. package/dist/usage/types.js +47 -0
  53. package/package.json +1 -1
@@ -182,7 +182,7 @@ export declare const IndexConfigSchema: z.ZodObject<{
182
182
  };
183
183
  enabled: boolean;
184
184
  embedder: "fastembed";
185
- store: "auto" | "sqlite" | "memory";
185
+ store: "memory" | "auto" | "sqlite";
186
186
  maxFileBytes: number;
187
187
  chunk: {
188
188
  windowLines: number;
@@ -196,7 +196,7 @@ export declare const IndexConfigSchema: z.ZodObject<{
196
196
  } | undefined;
197
197
  enabled?: boolean | undefined;
198
198
  embedder?: "fastembed" | undefined;
199
- store?: "auto" | "sqlite" | "memory" | undefined;
199
+ store?: "memory" | "auto" | "sqlite" | undefined;
200
200
  maxFileBytes?: number | undefined;
201
201
  chunk?: {
202
202
  windowLines?: number | undefined;
@@ -320,18 +320,18 @@ export declare const SandboxConfigSchema: z.ZodObject<{
320
320
  */
321
321
  mounts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
322
322
  }, "strict", z.ZodTypeAny, {
323
+ memory: string;
323
324
  image: string;
324
325
  enabled: boolean;
325
- memory: string;
326
326
  network: "none" | "host-loopback" | "full";
327
327
  pids: number;
328
328
  cpus: number;
329
329
  mounts: string[];
330
330
  timeout?: number | undefined;
331
331
  }, {
332
+ memory?: string | undefined;
332
333
  image?: string | undefined;
333
334
  enabled?: boolean | undefined;
334
- memory?: string | undefined;
335
335
  network?: "none" | "host-loopback" | "full" | undefined;
336
336
  pids?: number | undefined;
337
337
  cpus?: number | undefined;
@@ -386,6 +386,160 @@ export declare const RoutingConfigSchema: z.ZodObject<{
386
386
  map?: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">> | undefined;
387
387
  default?: "kavi" | "vaani" | "mira" | undefined;
388
388
  }>;
389
+ /**
390
+ * Persistent memory (C.29): structured notes recalled across sessions. ON by
391
+ * default and safe to be so — user memory is self-authored (nothing you didn't
392
+ * write is injected) and project memory still requires an explicit
393
+ * `cruxy memory trust`, so a cloned repo never auto-injects. Recall is bounded
394
+ * by a token budget; the `remember` tool refuses to persist secrets.
395
+ */
396
+ export declare const MemoryConfigSchema: z.ZodObject<{
397
+ /** Master switch. When false, nothing is recalled and the `remember` tool
398
+ * is not registered (memory stays fully inert). */
399
+ enabled: z.ZodDefault<z.ZodBoolean>;
400
+ /** Token budget for the recalled block injected at session start; entries
401
+ * beyond it are pruned oldest-first (the omission is stated, never silent). */
402
+ maxRecallTokens: z.ZodDefault<z.ZodNumber>;
403
+ }, "strict", z.ZodTypeAny, {
404
+ enabled: boolean;
405
+ maxRecallTokens: number;
406
+ }, {
407
+ enabled?: boolean | undefined;
408
+ maxRecallTokens?: number | undefined;
409
+ }>;
410
+ export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
411
+ /** A per-tier price, in the user's own currency, PER MILLION TOKENS (C.22). */
412
+ export declare const TierPriceSchema: z.ZodObject<{
413
+ /** Price per 1,000,000 input tokens. */
414
+ input: z.ZodNumber;
415
+ /** Price per 1,000,000 output tokens. */
416
+ output: z.ZodNumber;
417
+ }, "strict", z.ZodTypeAny, {
418
+ input: number;
419
+ output: number;
420
+ }, {
421
+ input: number;
422
+ output: number;
423
+ }>;
424
+ /**
425
+ * Usage telemetry + cost tracking (C.22): LOCAL usage accounting only — nothing
426
+ * here is ever transmitted. Costs are opt-in and honest: prices are per-TIER and
427
+ * per-million-tokens, configured by the user; with none set, tokens are shown
428
+ * and cost is omitted (cruxy ships no bundled price table). The `prices` keys are
429
+ * the fixed {@link MODEL_TIERS}, so an upstream model name can never appear here
430
+ * (U.8 gag holds by construction) and a mistyped tier is rejected at config load.
431
+ */
432
+ export declare const UsageConfigSchema: z.ZodObject<{
433
+ /** Master switch. When false, no usage is collected, persisted, or shown. */
434
+ enabled: z.ZodDefault<z.ZodBoolean>;
435
+ /** Currency label to prefix costs with (e.g. "$", "€"). Empty ⇒ no symbol is
436
+ * assumed — a bare number is shown, since the user configured the prices. */
437
+ currency: z.ZodDefault<z.ZodString>;
438
+ /** How many past runs to keep in the store; older ones are pruned oldest-first. */
439
+ retention: z.ZodDefault<z.ZodNumber>;
440
+ /** Per-tier prices. Any tier omitted is simply unpriced (cost omitted for it). */
441
+ prices: z.ZodDefault<z.ZodObject<{
442
+ kavi: z.ZodOptional<z.ZodObject<{
443
+ /** Price per 1,000,000 input tokens. */
444
+ input: z.ZodNumber;
445
+ /** Price per 1,000,000 output tokens. */
446
+ output: z.ZodNumber;
447
+ }, "strict", z.ZodTypeAny, {
448
+ input: number;
449
+ output: number;
450
+ }, {
451
+ input: number;
452
+ output: number;
453
+ }>>;
454
+ vaani: z.ZodOptional<z.ZodObject<{
455
+ /** Price per 1,000,000 input tokens. */
456
+ input: z.ZodNumber;
457
+ /** Price per 1,000,000 output tokens. */
458
+ output: z.ZodNumber;
459
+ }, "strict", z.ZodTypeAny, {
460
+ input: number;
461
+ output: number;
462
+ }, {
463
+ input: number;
464
+ output: number;
465
+ }>>;
466
+ mira: z.ZodOptional<z.ZodObject<{
467
+ /** Price per 1,000,000 input tokens. */
468
+ input: z.ZodNumber;
469
+ /** Price per 1,000,000 output tokens. */
470
+ output: z.ZodNumber;
471
+ }, "strict", z.ZodTypeAny, {
472
+ input: number;
473
+ output: number;
474
+ }, {
475
+ input: number;
476
+ output: number;
477
+ }>>;
478
+ }, "strict", z.ZodTypeAny, {
479
+ kavi?: {
480
+ input: number;
481
+ output: number;
482
+ } | undefined;
483
+ vaani?: {
484
+ input: number;
485
+ output: number;
486
+ } | undefined;
487
+ mira?: {
488
+ input: number;
489
+ output: number;
490
+ } | undefined;
491
+ }, {
492
+ kavi?: {
493
+ input: number;
494
+ output: number;
495
+ } | undefined;
496
+ vaani?: {
497
+ input: number;
498
+ output: number;
499
+ } | undefined;
500
+ mira?: {
501
+ input: number;
502
+ output: number;
503
+ } | undefined;
504
+ }>>;
505
+ }, "strict", z.ZodTypeAny, {
506
+ enabled: boolean;
507
+ retention: number;
508
+ currency: string;
509
+ prices: {
510
+ kavi?: {
511
+ input: number;
512
+ output: number;
513
+ } | undefined;
514
+ vaani?: {
515
+ input: number;
516
+ output: number;
517
+ } | undefined;
518
+ mira?: {
519
+ input: number;
520
+ output: number;
521
+ } | undefined;
522
+ };
523
+ }, {
524
+ enabled?: boolean | undefined;
525
+ retention?: number | undefined;
526
+ currency?: string | undefined;
527
+ prices?: {
528
+ kavi?: {
529
+ input: number;
530
+ output: number;
531
+ } | undefined;
532
+ vaani?: {
533
+ input: number;
534
+ output: number;
535
+ } | undefined;
536
+ mira?: {
537
+ input: number;
538
+ output: number;
539
+ } | undefined;
540
+ } | undefined;
541
+ }>;
542
+ export type UsageConfig = z.infer<typeof UsageConfigSchema>;
389
543
  /** MCP server entry — stdio or URL transport (wired up in a later phase). */
390
544
  export declare const McpServerSchema: z.ZodObject<{
391
545
  command: z.ZodOptional<z.ZodString>;
@@ -567,7 +721,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
567
721
  };
568
722
  enabled: boolean;
569
723
  embedder: "fastembed";
570
- store: "auto" | "sqlite" | "memory";
724
+ store: "memory" | "auto" | "sqlite";
571
725
  maxFileBytes: number;
572
726
  chunk: {
573
727
  windowLines: number;
@@ -581,7 +735,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
581
735
  } | undefined;
582
736
  enabled?: boolean | undefined;
583
737
  embedder?: "fastembed" | undefined;
584
- store?: "auto" | "sqlite" | "memory" | undefined;
738
+ store?: "memory" | "auto" | "sqlite" | undefined;
585
739
  maxFileBytes?: number | undefined;
586
740
  chunk?: {
587
741
  windowLines?: number | undefined;
@@ -683,18 +837,18 @@ export declare const CruxyConfigSchema: z.ZodObject<{
683
837
  */
684
838
  mounts: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
685
839
  }, "strict", z.ZodTypeAny, {
840
+ memory: string;
686
841
  image: string;
687
842
  enabled: boolean;
688
- memory: string;
689
843
  network: "none" | "host-loopback" | "full";
690
844
  pids: number;
691
845
  cpus: number;
692
846
  mounts: string[];
693
847
  timeout?: number | undefined;
694
848
  }, {
849
+ memory?: string | undefined;
695
850
  image?: string | undefined;
696
851
  enabled?: boolean | undefined;
697
- memory?: string | undefined;
698
852
  network?: "none" | "host-loopback" | "full" | undefined;
699
853
  pids?: number | undefined;
700
854
  cpus?: number | undefined;
@@ -731,6 +885,130 @@ export declare const CruxyConfigSchema: z.ZodObject<{
731
885
  map?: Partial<Record<"main-turn" | "subagent" | "plan" | "commit-msg" | "classify" | "summarize", "kavi" | "vaani" | "mira">> | undefined;
732
886
  default?: "kavi" | "vaani" | "mira" | undefined;
733
887
  }>>;
888
+ memory: z.ZodDefault<z.ZodObject<{
889
+ /** Master switch. When false, nothing is recalled and the `remember` tool
890
+ * is not registered (memory stays fully inert). */
891
+ enabled: z.ZodDefault<z.ZodBoolean>;
892
+ /** Token budget for the recalled block injected at session start; entries
893
+ * beyond it are pruned oldest-first (the omission is stated, never silent). */
894
+ maxRecallTokens: z.ZodDefault<z.ZodNumber>;
895
+ }, "strict", z.ZodTypeAny, {
896
+ enabled: boolean;
897
+ maxRecallTokens: number;
898
+ }, {
899
+ enabled?: boolean | undefined;
900
+ maxRecallTokens?: number | undefined;
901
+ }>>;
902
+ usage: z.ZodDefault<z.ZodObject<{
903
+ /** Master switch. When false, no usage is collected, persisted, or shown. */
904
+ enabled: z.ZodDefault<z.ZodBoolean>;
905
+ /** Currency label to prefix costs with (e.g. "$", "€"). Empty ⇒ no symbol is
906
+ * assumed — a bare number is shown, since the user configured the prices. */
907
+ currency: z.ZodDefault<z.ZodString>;
908
+ /** How many past runs to keep in the store; older ones are pruned oldest-first. */
909
+ retention: z.ZodDefault<z.ZodNumber>;
910
+ /** Per-tier prices. Any tier omitted is simply unpriced (cost omitted for it). */
911
+ prices: z.ZodDefault<z.ZodObject<{
912
+ kavi: z.ZodOptional<z.ZodObject<{
913
+ /** Price per 1,000,000 input tokens. */
914
+ input: z.ZodNumber;
915
+ /** Price per 1,000,000 output tokens. */
916
+ output: z.ZodNumber;
917
+ }, "strict", z.ZodTypeAny, {
918
+ input: number;
919
+ output: number;
920
+ }, {
921
+ input: number;
922
+ output: number;
923
+ }>>;
924
+ vaani: z.ZodOptional<z.ZodObject<{
925
+ /** Price per 1,000,000 input tokens. */
926
+ input: z.ZodNumber;
927
+ /** Price per 1,000,000 output tokens. */
928
+ output: z.ZodNumber;
929
+ }, "strict", z.ZodTypeAny, {
930
+ input: number;
931
+ output: number;
932
+ }, {
933
+ input: number;
934
+ output: number;
935
+ }>>;
936
+ mira: z.ZodOptional<z.ZodObject<{
937
+ /** Price per 1,000,000 input tokens. */
938
+ input: z.ZodNumber;
939
+ /** Price per 1,000,000 output tokens. */
940
+ output: z.ZodNumber;
941
+ }, "strict", z.ZodTypeAny, {
942
+ input: number;
943
+ output: number;
944
+ }, {
945
+ input: number;
946
+ output: number;
947
+ }>>;
948
+ }, "strict", z.ZodTypeAny, {
949
+ kavi?: {
950
+ input: number;
951
+ output: number;
952
+ } | undefined;
953
+ vaani?: {
954
+ input: number;
955
+ output: number;
956
+ } | undefined;
957
+ mira?: {
958
+ input: number;
959
+ output: number;
960
+ } | undefined;
961
+ }, {
962
+ kavi?: {
963
+ input: number;
964
+ output: number;
965
+ } | undefined;
966
+ vaani?: {
967
+ input: number;
968
+ output: number;
969
+ } | undefined;
970
+ mira?: {
971
+ input: number;
972
+ output: number;
973
+ } | undefined;
974
+ }>>;
975
+ }, "strict", z.ZodTypeAny, {
976
+ enabled: boolean;
977
+ retention: number;
978
+ currency: string;
979
+ prices: {
980
+ kavi?: {
981
+ input: number;
982
+ output: number;
983
+ } | undefined;
984
+ vaani?: {
985
+ input: number;
986
+ output: number;
987
+ } | undefined;
988
+ mira?: {
989
+ input: number;
990
+ output: number;
991
+ } | undefined;
992
+ };
993
+ }, {
994
+ enabled?: boolean | undefined;
995
+ retention?: number | undefined;
996
+ currency?: string | undefined;
997
+ prices?: {
998
+ kavi?: {
999
+ input: number;
1000
+ output: number;
1001
+ } | undefined;
1002
+ vaani?: {
1003
+ input: number;
1004
+ output: number;
1005
+ } | undefined;
1006
+ mira?: {
1007
+ input: number;
1008
+ output: number;
1009
+ } | undefined;
1010
+ } | undefined;
1011
+ }>>;
734
1012
  mcpServers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
735
1013
  command: z.ZodOptional<z.ZodString>;
736
1014
  args: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -749,14 +1027,37 @@ export declare const CruxyConfigSchema: z.ZodObject<{
749
1027
  cruxy: {
750
1028
  gatewayUrl: string;
751
1029
  };
1030
+ memory: {
1031
+ enabled: boolean;
1032
+ maxRecallTokens: number;
1033
+ };
1034
+ usage: {
1035
+ enabled: boolean;
1036
+ retention: number;
1037
+ currency: string;
1038
+ prices: {
1039
+ kavi?: {
1040
+ input: number;
1041
+ output: number;
1042
+ } | undefined;
1043
+ vaani?: {
1044
+ input: number;
1045
+ output: number;
1046
+ } | undefined;
1047
+ mira?: {
1048
+ input: number;
1049
+ output: number;
1050
+ } | undefined;
1051
+ };
1052
+ };
752
1053
  checkpoint: {
753
1054
  enabled: boolean;
754
1055
  retention: number;
755
1056
  };
756
1057
  sandbox: {
1058
+ memory: string;
757
1059
  image: string;
758
1060
  enabled: boolean;
759
- memory: string;
760
1061
  network: "none" | "host-loopback" | "full";
761
1062
  pids: number;
762
1063
  cpus: number;
@@ -812,7 +1113,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
812
1113
  };
813
1114
  enabled: boolean;
814
1115
  embedder: "fastembed";
815
- store: "auto" | "sqlite" | "memory";
1116
+ store: "memory" | "auto" | "sqlite";
816
1117
  maxFileBytes: number;
817
1118
  chunk: {
818
1119
  windowLines: number;
@@ -842,14 +1143,37 @@ export declare const CruxyConfigSchema: z.ZodObject<{
842
1143
  cruxy?: {
843
1144
  gatewayUrl?: string | undefined;
844
1145
  } | undefined;
1146
+ memory?: {
1147
+ enabled?: boolean | undefined;
1148
+ maxRecallTokens?: number | undefined;
1149
+ } | undefined;
1150
+ usage?: {
1151
+ enabled?: boolean | undefined;
1152
+ retention?: number | undefined;
1153
+ currency?: string | undefined;
1154
+ prices?: {
1155
+ kavi?: {
1156
+ input: number;
1157
+ output: number;
1158
+ } | undefined;
1159
+ vaani?: {
1160
+ input: number;
1161
+ output: number;
1162
+ } | undefined;
1163
+ mira?: {
1164
+ input: number;
1165
+ output: number;
1166
+ } | undefined;
1167
+ } | undefined;
1168
+ } | undefined;
845
1169
  checkpoint?: {
846
1170
  enabled?: boolean | undefined;
847
1171
  retention?: number | undefined;
848
1172
  } | undefined;
849
1173
  sandbox?: {
1174
+ memory?: string | undefined;
850
1175
  image?: string | undefined;
851
1176
  enabled?: boolean | undefined;
852
- memory?: string | undefined;
853
1177
  network?: "none" | "host-loopback" | "full" | undefined;
854
1178
  pids?: number | undefined;
855
1179
  cpus?: number | undefined;
@@ -905,7 +1229,7 @@ export declare const CruxyConfigSchema: z.ZodObject<{
905
1229
  } | undefined;
906
1230
  enabled?: boolean | undefined;
907
1231
  embedder?: "fastembed" | undefined;
908
- store?: "auto" | "sqlite" | "memory" | undefined;
1232
+ store?: "memory" | "auto" | "sqlite" | undefined;
909
1233
  maxFileBytes?: number | undefined;
910
1234
  chunk?: {
911
1235
  windowLines?: number | undefined;
@@ -273,6 +273,60 @@ export const RoutingConfigSchema = z
273
273
  map: z.record(z.enum(TASK_CLASSES), z.enum(MODEL_TIERS)).default({}),
274
274
  })
275
275
  .strict();
276
+ /**
277
+ * Persistent memory (C.29): structured notes recalled across sessions. ON by
278
+ * default and safe to be so — user memory is self-authored (nothing you didn't
279
+ * write is injected) and project memory still requires an explicit
280
+ * `cruxy memory trust`, so a cloned repo never auto-injects. Recall is bounded
281
+ * by a token budget; the `remember` tool refuses to persist secrets.
282
+ */
283
+ export const MemoryConfigSchema = z
284
+ .object({
285
+ /** Master switch. When false, nothing is recalled and the `remember` tool
286
+ * is not registered (memory stays fully inert). */
287
+ enabled: z.boolean().default(true),
288
+ /** Token budget for the recalled block injected at session start; entries
289
+ * beyond it are pruned oldest-first (the omission is stated, never silent). */
290
+ maxRecallTokens: z.number().int().positive().default(1000),
291
+ })
292
+ .strict();
293
+ /** A per-tier price, in the user's own currency, PER MILLION TOKENS (C.22). */
294
+ export const TierPriceSchema = z
295
+ .object({
296
+ /** Price per 1,000,000 input tokens. */
297
+ input: z.number().nonnegative(),
298
+ /** Price per 1,000,000 output tokens. */
299
+ output: z.number().nonnegative(),
300
+ })
301
+ .strict();
302
+ /**
303
+ * Usage telemetry + cost tracking (C.22): LOCAL usage accounting only — nothing
304
+ * here is ever transmitted. Costs are opt-in and honest: prices are per-TIER and
305
+ * per-million-tokens, configured by the user; with none set, tokens are shown
306
+ * and cost is omitted (cruxy ships no bundled price table). The `prices` keys are
307
+ * the fixed {@link MODEL_TIERS}, so an upstream model name can never appear here
308
+ * (U.8 gag holds by construction) and a mistyped tier is rejected at config load.
309
+ */
310
+ export const UsageConfigSchema = z
311
+ .object({
312
+ /** Master switch. When false, no usage is collected, persisted, or shown. */
313
+ enabled: z.boolean().default(true),
314
+ /** Currency label to prefix costs with (e.g. "$", "€"). Empty ⇒ no symbol is
315
+ * assumed — a bare number is shown, since the user configured the prices. */
316
+ currency: z.string().default(""),
317
+ /** How many past runs to keep in the store; older ones are pruned oldest-first. */
318
+ retention: z.number().int().positive().default(50),
319
+ /** Per-tier prices. Any tier omitted is simply unpriced (cost omitted for it). */
320
+ prices: z
321
+ .object({
322
+ kavi: TierPriceSchema.optional(),
323
+ vaani: TierPriceSchema.optional(),
324
+ mira: TierPriceSchema.optional(),
325
+ })
326
+ .strict()
327
+ .default({}),
328
+ })
329
+ .strict();
276
330
  /** MCP server entry — stdio or URL transport (wired up in a later phase). */
277
331
  export const McpServerSchema = z
278
332
  .object({
@@ -298,6 +352,8 @@ export const CruxyConfigSchema = z
298
352
  sandbox: SandboxConfigSchema.default({}),
299
353
  hooks: HooksConfigSchema.default({}),
300
354
  routing: RoutingConfigSchema.default({}),
355
+ memory: MemoryConfigSchema.default({}),
356
+ usage: UsageConfigSchema.default({}),
301
357
  mcpServers: z.record(z.string(), McpServerSchema).default({}),
302
358
  logLevel: z.enum(LOG_LEVELS).default("info"),
303
359
  })
@@ -30,6 +30,24 @@ export declare const COMMANDS_DIR_NAME = "commands";
30
30
  /** Per-repo hook-trust record, in the GLOBAL dir only (`~/.cruxy/trust.json`) —
31
31
  * never in a repo, so cloning carries no trust (C.19 supply-chain safety). */
32
32
  export declare const TRUST_FILE_NAME = "trust.json";
33
+ /**
34
+ * Persistent memory (C.29). Structured entries live under the memory subdir of
35
+ * the project dir (`<cwd>/.cruxy/memory`) and the global dir (`~/.cruxy/memory`),
36
+ * one JSON file per scope — pure data, validated, never eval'd.
37
+ */
38
+ export declare const MEMORY_DIR_NAME = "memory";
39
+ export declare const MEMORY_FILE_NAME = "entries.json";
40
+ /** Per-repo PROJECT-memory trust record, in the GLOBAL dir only
41
+ * (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
42
+ * cloning a repo carries zero memory trust (C.29 supply-chain safety). */
43
+ export declare const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
44
+ /**
45
+ * Usage telemetry + cost tracking (C.22). Per-run/per-session usage records live
46
+ * in the GLOBAL dir only (`~/.cruxy/usage/runs.json`), `0600` — LOCAL accounting
47
+ * of your own usage, never transmitted. Pure data, validated, never eval'd.
48
+ */
49
+ export declare const USAGE_DIR_NAME = "usage";
50
+ export declare const USAGE_FILE_NAME = "runs.json";
33
51
  /**
34
52
  * Absolute path of the shipped builtin skills directory (`<pkg>/skills`).
35
53
  * Anchored the same way as the package.json lookup above: both `dist/` and
package/dist/constants.js CHANGED
@@ -50,6 +50,24 @@ export const COMMANDS_DIR_NAME = "commands";
50
50
  /** Per-repo hook-trust record, in the GLOBAL dir only (`~/.cruxy/trust.json`) —
51
51
  * never in a repo, so cloning carries no trust (C.19 supply-chain safety). */
52
52
  export const TRUST_FILE_NAME = "trust.json";
53
+ /**
54
+ * Persistent memory (C.29). Structured entries live under the memory subdir of
55
+ * the project dir (`<cwd>/.cruxy/memory`) and the global dir (`~/.cruxy/memory`),
56
+ * one JSON file per scope — pure data, validated, never eval'd.
57
+ */
58
+ export const MEMORY_DIR_NAME = "memory";
59
+ export const MEMORY_FILE_NAME = "entries.json";
60
+ /** Per-repo PROJECT-memory trust record, in the GLOBAL dir only
61
+ * (`~/.cruxy/memory-trust.json`) — its own file, independent of hook trust, so
62
+ * cloning a repo carries zero memory trust (C.29 supply-chain safety). */
63
+ export const MEMORY_TRUST_FILE_NAME = "memory-trust.json";
64
+ /**
65
+ * Usage telemetry + cost tracking (C.22). Per-run/per-session usage records live
66
+ * in the GLOBAL dir only (`~/.cruxy/usage/runs.json`), `0600` — LOCAL accounting
67
+ * of your own usage, never transmitted. Pure data, validated, never eval'd.
68
+ */
69
+ export const USAGE_DIR_NAME = "usage";
70
+ export const USAGE_FILE_NAME = "runs.json";
53
71
  /**
54
72
  * Absolute path of the shipped builtin skills directory (`<pkg>/skills`).
55
73
  * Anchored the same way as the package.json lookup above: both `dist/` and
@@ -136,6 +136,27 @@ export declare function subagentFailed(underlying?: unknown): CruxyError;
136
136
  * invents a test command — the fix is always to declare one.
137
137
  */
138
138
  export declare function testCommandNotFound(): CruxyError;
139
+ /**
140
+ * A memory write was refused because the content matched a known secret shape
141
+ * (C.29). Secrets are NEVER persisted to memory — the fix is to remember a
142
+ * non-secret description, not the secret itself.
143
+ */
144
+ export declare function memorySecretRefused(kind: string): CruxyError;
145
+ /**
146
+ * A repo's project memory is present but has not been trusted on this machine
147
+ * (C.29). It is never recalled into the model's context silently — a cloned repo
148
+ * cannot inject notes until you review and trust them.
149
+ */
150
+ export declare function memoryUntrusted(root: string): CruxyError;
151
+ /** A malformed memory entry was rejected (C.29) — excluded, never eval'd. */
152
+ export declare function memoryInvalid(detail: string): CruxyError;
153
+ /**
154
+ * The local usage store (`~/.cruxy/usage/runs.json`) is corrupt or unreadable
155
+ * (C.22). Never fatal: usage accounting is best-effort, so the read is SKIPPED
156
+ * and this is surfaced. The fix is always to reset the file — its only content
157
+ * is your own local usage history, so deleting it loses nothing but history.
158
+ */
159
+ export declare function usageRead(path: string, reason: string, underlying?: unknown): CruxyError;
139
160
  export declare function internal(underlying?: unknown): CruxyError;
140
161
  /**
141
162
  * Map a known provider/transport error (from `@cruxy/sdk`) to a typed
@@ -602,6 +602,70 @@ export function testCommandNotFound() {
602
602
  ],
603
603
  });
604
604
  }
605
+ // ── persistent memory (exit 14) — C.29 ────────────────────────────────────────
606
+ /**
607
+ * A memory write was refused because the content matched a known secret shape
608
+ * (C.29). Secrets are NEVER persisted to memory — the fix is to remember a
609
+ * non-secret description, not the secret itself.
610
+ */
611
+ export function memorySecretRefused(kind) {
612
+ return new CruxyError({
613
+ code: ErrorCode.MemorySecret,
614
+ title: "refused to save that note — it looks like it contains a secret",
615
+ cause: `the content matched a ${kind}; secrets are never written to memory`,
616
+ nextSteps: [
617
+ "remember a non-secret description instead (e.g. “the deploy key lives in 1Password”, not the key)",
618
+ ],
619
+ meta: { kind },
620
+ });
621
+ }
622
+ /**
623
+ * A repo's project memory is present but has not been trusted on this machine
624
+ * (C.29). It is never recalled into the model's context silently — a cloned repo
625
+ * cannot inject notes until you review and trust them.
626
+ */
627
+ export function memoryUntrusted(root) {
628
+ return new CruxyError({
629
+ code: ErrorCode.MemoryUntrusted,
630
+ title: "this project's memory has not been trusted",
631
+ cause: "project memory can arrive with a cloned repo (another author), so it is not recalled until you trust it",
632
+ nextSteps: [
633
+ "review the entries with `cruxy memory list`",
634
+ "then trust them with `cruxy memory trust .` (re-trust is required if they change)",
635
+ ],
636
+ meta: { root },
637
+ });
638
+ }
639
+ /** A malformed memory entry was rejected (C.29) — excluded, never eval'd. */
640
+ export function memoryInvalid(detail) {
641
+ return new CruxyError({
642
+ code: ErrorCode.MemoryInvalid,
643
+ title: "a memory entry is malformed and was excluded",
644
+ cause: detail,
645
+ nextSteps: [
646
+ "inspect the file with `cruxy memory list`, or clear it with `cruxy memory clear`",
647
+ ],
648
+ });
649
+ }
650
+ // ── usage telemetry (exit 2) — C.22 ───────────────────────────────────────────
651
+ /**
652
+ * The local usage store (`~/.cruxy/usage/runs.json`) is corrupt or unreadable
653
+ * (C.22). Never fatal: usage accounting is best-effort, so the read is SKIPPED
654
+ * and this is surfaced. The fix is always to reset the file — its only content
655
+ * is your own local usage history, so deleting it loses nothing but history.
656
+ */
657
+ export function usageRead(path, reason, underlying) {
658
+ return new CruxyError({
659
+ code: ErrorCode.UsageRead,
660
+ title: "could not read the local usage store",
661
+ cause: reason,
662
+ nextSteps: [
663
+ `delete ${path} to reset it (it holds only your local usage history)`,
664
+ ],
665
+ underlying,
666
+ meta: { path },
667
+ });
668
+ }
605
669
  // ── internal (exit 1) ─────────────────────────────────────────────────────────
606
670
  export function internal(underlying) {
607
671
  return new CruxyError({