@marcopeg/hal 1.0.23 → 1.0.26

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 (75) hide show
  1. package/README.md +9 -12
  2. package/dist/bot/commands/engine-callback.d.ts +4 -0
  3. package/dist/bot/commands/engine-callback.d.ts.map +1 -0
  4. package/dist/bot/commands/engine-callback.js +39 -0
  5. package/dist/bot/commands/engine-callback.js.map +1 -0
  6. package/dist/bot/commands/engine.d.ts +4 -0
  7. package/dist/bot/commands/engine.d.ts.map +1 -0
  8. package/dist/bot/commands/engine.js +65 -0
  9. package/dist/bot/commands/engine.js.map +1 -0
  10. package/dist/bot/commands/loader.d.ts +7 -1
  11. package/dist/bot/commands/loader.d.ts.map +1 -1
  12. package/dist/bot/commands/loader.js +18 -3
  13. package/dist/bot/commands/loader.js.map +1 -1
  14. package/dist/bot/commands/message.d.ts.map +1 -1
  15. package/dist/bot/commands/message.js +2 -1
  16. package/dist/bot/commands/message.js.map +1 -1
  17. package/dist/bot/commands/model-callback.d.ts.map +1 -1
  18. package/dist/bot/commands/model-callback.js +7 -1
  19. package/dist/bot/commands/model-callback.js.map +1 -1
  20. package/dist/bot/commands/model.d.ts.map +1 -1
  21. package/dist/bot/commands/model.js +17 -3
  22. package/dist/bot/commands/model.js.map +1 -1
  23. package/dist/bot/commands/tasks.d.ts +5 -0
  24. package/dist/bot/commands/tasks.d.ts.map +1 -0
  25. package/dist/bot/commands/tasks.js +123 -0
  26. package/dist/bot/commands/tasks.js.map +1 -0
  27. package/dist/bot/commands/watcher.d.ts.map +1 -1
  28. package/dist/bot/commands/watcher.js +6 -5
  29. package/dist/bot/commands/watcher.js.map +1 -1
  30. package/dist/bot/handlers/index.d.ts +1 -0
  31. package/dist/bot/handlers/index.d.ts.map +1 -1
  32. package/dist/bot/handlers/index.js +1 -0
  33. package/dist/bot/handlers/index.js.map +1 -1
  34. package/dist/bot/handlers/mjs-callback.d.ts +15 -0
  35. package/dist/bot/handlers/mjs-callback.d.ts.map +1 -0
  36. package/dist/bot/handlers/mjs-callback.js +84 -0
  37. package/dist/bot/handlers/mjs-callback.js.map +1 -0
  38. package/dist/bot.d.ts.map +1 -1
  39. package/dist/bot.js +19 -6
  40. package/dist/bot.js.map +1 -1
  41. package/dist/cli.js +199 -68
  42. package/dist/cli.js.map +1 -1
  43. package/dist/config-writer.d.ts +2 -6
  44. package/dist/config-writer.d.ts.map +1 -1
  45. package/dist/config-writer.js +32 -28
  46. package/dist/config-writer.js.map +1 -1
  47. package/dist/config.d.ts +103 -56
  48. package/dist/config.d.ts.map +1 -1
  49. package/dist/config.js +154 -46
  50. package/dist/config.js.map +1 -1
  51. package/dist/engine/adapters/codex.d.ts.map +1 -1
  52. package/dist/engine/adapters/codex.js +4 -2
  53. package/dist/engine/adapters/codex.js.map +1 -1
  54. package/dist/engine/adapters/copilot.d.ts.map +1 -1
  55. package/dist/engine/adapters/copilot.js +8 -5
  56. package/dist/engine/adapters/copilot.js.map +1 -1
  57. package/dist/engine/adapters/cursor.d.ts.map +1 -1
  58. package/dist/engine/adapters/cursor.js +8 -3
  59. package/dist/engine/adapters/cursor.js.map +1 -1
  60. package/dist/engine/adapters/opencode.d.ts.map +1 -1
  61. package/dist/engine/adapters/opencode.js +4 -2
  62. package/dist/engine/adapters/opencode.js.map +1 -1
  63. package/dist/engine/cli-available.d.ts +6 -0
  64. package/dist/engine/cli-available.d.ts.map +1 -0
  65. package/dist/engine/cli-available.js +19 -0
  66. package/dist/engine/cli-available.js.map +1 -0
  67. package/dist/engine/opencode-models.d.ts +30 -0
  68. package/dist/engine/opencode-models.d.ts.map +1 -0
  69. package/dist/engine/opencode-models.js +96 -0
  70. package/dist/engine/opencode-models.js.map +1 -0
  71. package/dist/init-template.yaml +32 -0
  72. package/dist/user/setup.d.ts.map +1 -1
  73. package/dist/user/setup.js +5 -1
  74. package/dist/user/setup.js.map +1 -1
  75. package/package.json +2 -2
package/dist/config.d.ts CHANGED
@@ -7,6 +7,38 @@ declare const EngineNameSchema: z.ZodEnum<{
7
7
  cursor: "cursor";
8
8
  antigravity: "antigravity";
9
9
  }>;
10
+ declare const ProvidersConfigSchema: z.ZodOptional<z.ZodObject<{
11
+ claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
12
+ name: z.ZodString;
13
+ description: z.ZodOptional<z.ZodString>;
14
+ default: z.ZodOptional<z.ZodBoolean>;
15
+ }, z.core.$strip>>>;
16
+ copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
17
+ name: z.ZodString;
18
+ description: z.ZodOptional<z.ZodString>;
19
+ default: z.ZodOptional<z.ZodBoolean>;
20
+ }, z.core.$strip>>>;
21
+ codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
22
+ name: z.ZodString;
23
+ description: z.ZodOptional<z.ZodString>;
24
+ default: z.ZodOptional<z.ZodBoolean>;
25
+ }, z.core.$strip>>>;
26
+ opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
27
+ name: z.ZodString;
28
+ description: z.ZodOptional<z.ZodString>;
29
+ default: z.ZodOptional<z.ZodBoolean>;
30
+ }, z.core.$strip>>>;
31
+ cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
32
+ name: z.ZodString;
33
+ description: z.ZodOptional<z.ZodString>;
34
+ default: z.ZodOptional<z.ZodBoolean>;
35
+ }, z.core.$strip>>>;
36
+ antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
37
+ name: z.ZodString;
38
+ description: z.ZodOptional<z.ZodString>;
39
+ default: z.ZodOptional<z.ZodBoolean>;
40
+ }, z.core.$strip>>>;
41
+ }, z.core.$strip>>;
10
42
  declare const GlobalsFileSchema: z.ZodOptional<z.ZodObject<{
11
43
  access: z.ZodOptional<z.ZodObject<{
12
44
  allowedUserIds: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>>;
@@ -39,32 +71,6 @@ declare const GlobalsFileSchema: z.ZodOptional<z.ZodObject<{
39
71
  sandbox: z.ZodOptional<z.ZodBoolean>;
40
72
  }, z.core.$strip>>>;
41
73
  }, z.core.$strip>>;
42
- providers: z.ZodOptional<z.ZodObject<{
43
- claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
- name: z.ZodString;
45
- description: z.ZodOptional<z.ZodString>;
46
- }, z.core.$strip>>>;
47
- copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
48
- name: z.ZodString;
49
- description: z.ZodOptional<z.ZodString>;
50
- }, z.core.$strip>>>;
51
- codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
52
- name: z.ZodString;
53
- description: z.ZodOptional<z.ZodString>;
54
- }, z.core.$strip>>>;
55
- opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
56
- name: z.ZodString;
57
- description: z.ZodOptional<z.ZodString>;
58
- }, z.core.$strip>>>;
59
- cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
60
- name: z.ZodString;
61
- description: z.ZodOptional<z.ZodString>;
62
- }, z.core.$strip>>>;
63
- antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
64
- name: z.ZodString;
65
- description: z.ZodOptional<z.ZodString>;
66
- }, z.core.$strip>>>;
67
- }, z.core.$strip>>;
68
74
  logging: z.ZodOptional<z.ZodObject<{
69
75
  level: z.ZodOptional<z.ZodEnum<{
70
76
  debug: "debug";
@@ -138,12 +144,15 @@ declare const GlobalsFileSchema: z.ZodOptional<z.ZodObject<{
138
144
  model: z.ZodOptional<z.ZodObject<{
139
145
  enabled: z.ZodOptional<z.ZodBoolean>;
140
146
  }, z.core.$strip>>;
147
+ engine: z.ZodOptional<z.ZodObject<{
148
+ enabled: z.ZodOptional<z.ZodBoolean>;
149
+ }, z.core.$strip>>;
141
150
  }, z.core.$strip>>;
142
151
  }, z.core.$strip>>;
143
152
  declare const ProjectFileSchema: z.ZodObject<{
144
153
  name: z.ZodOptional<z.ZodString>;
145
154
  active: z.ZodOptional<z.ZodBoolean>;
146
- cwd: z.ZodString;
155
+ cwd: z.ZodOptional<z.ZodString>;
147
156
  telegram: z.ZodObject<{
148
157
  botToken: z.ZodString;
149
158
  }, z.core.$strip>;
@@ -182,26 +191,32 @@ declare const ProjectFileSchema: z.ZodObject<{
182
191
  claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
183
192
  name: z.ZodString;
184
193
  description: z.ZodOptional<z.ZodString>;
194
+ default: z.ZodOptional<z.ZodBoolean>;
185
195
  }, z.core.$strip>>>;
186
196
  copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
187
197
  name: z.ZodString;
188
198
  description: z.ZodOptional<z.ZodString>;
199
+ default: z.ZodOptional<z.ZodBoolean>;
189
200
  }, z.core.$strip>>>;
190
201
  codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
191
202
  name: z.ZodString;
192
203
  description: z.ZodOptional<z.ZodString>;
204
+ default: z.ZodOptional<z.ZodBoolean>;
193
205
  }, z.core.$strip>>>;
194
206
  opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
195
207
  name: z.ZodString;
196
208
  description: z.ZodOptional<z.ZodString>;
209
+ default: z.ZodOptional<z.ZodBoolean>;
197
210
  }, z.core.$strip>>>;
198
211
  cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
199
212
  name: z.ZodString;
200
213
  description: z.ZodOptional<z.ZodString>;
214
+ default: z.ZodOptional<z.ZodBoolean>;
201
215
  }, z.core.$strip>>>;
202
216
  antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
203
217
  name: z.ZodString;
204
218
  description: z.ZodOptional<z.ZodString>;
219
+ default: z.ZodOptional<z.ZodBoolean>;
205
220
  }, z.core.$strip>>>;
206
221
  }, z.core.$strip>>;
207
222
  logging: z.ZodOptional<z.ZodObject<{
@@ -278,6 +293,9 @@ declare const ProjectFileSchema: z.ZodObject<{
278
293
  model: z.ZodOptional<z.ZodObject<{
279
294
  enabled: z.ZodOptional<z.ZodBoolean>;
280
295
  }, z.core.$strip>>;
296
+ engine: z.ZodOptional<z.ZodObject<{
297
+ enabled: z.ZodOptional<z.ZodBoolean>;
298
+ }, z.core.$strip>>;
281
299
  }, z.core.$strip>>;
282
300
  }, z.core.$strip>;
283
301
  declare const MultiConfigFileSchema: z.ZodObject<{
@@ -313,32 +331,6 @@ declare const MultiConfigFileSchema: z.ZodObject<{
313
331
  sandbox: z.ZodOptional<z.ZodBoolean>;
314
332
  }, z.core.$strip>>>;
315
333
  }, z.core.$strip>>;
316
- providers: z.ZodOptional<z.ZodObject<{
317
- claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
318
- name: z.ZodString;
319
- description: z.ZodOptional<z.ZodString>;
320
- }, z.core.$strip>>>;
321
- copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
322
- name: z.ZodString;
323
- description: z.ZodOptional<z.ZodString>;
324
- }, z.core.$strip>>>;
325
- codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
326
- name: z.ZodString;
327
- description: z.ZodOptional<z.ZodString>;
328
- }, z.core.$strip>>>;
329
- opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
330
- name: z.ZodString;
331
- description: z.ZodOptional<z.ZodString>;
332
- }, z.core.$strip>>>;
333
- cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
334
- name: z.ZodString;
335
- description: z.ZodOptional<z.ZodString>;
336
- }, z.core.$strip>>>;
337
- antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
338
- name: z.ZodString;
339
- description: z.ZodOptional<z.ZodString>;
340
- }, z.core.$strip>>>;
341
- }, z.core.$strip>>;
342
334
  logging: z.ZodOptional<z.ZodObject<{
343
335
  level: z.ZodOptional<z.ZodEnum<{
344
336
  debug: "debug";
@@ -412,13 +404,48 @@ declare const MultiConfigFileSchema: z.ZodObject<{
412
404
  model: z.ZodOptional<z.ZodObject<{
413
405
  enabled: z.ZodOptional<z.ZodBoolean>;
414
406
  }, z.core.$strip>>;
407
+ engine: z.ZodOptional<z.ZodObject<{
408
+ enabled: z.ZodOptional<z.ZodBoolean>;
409
+ }, z.core.$strip>>;
415
410
  }, z.core.$strip>>;
416
411
  }, z.core.$strip>>;
412
+ providers: z.ZodOptional<z.ZodObject<{
413
+ claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
414
+ name: z.ZodString;
415
+ description: z.ZodOptional<z.ZodString>;
416
+ default: z.ZodOptional<z.ZodBoolean>;
417
+ }, z.core.$strip>>>;
418
+ copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
419
+ name: z.ZodString;
420
+ description: z.ZodOptional<z.ZodString>;
421
+ default: z.ZodOptional<z.ZodBoolean>;
422
+ }, z.core.$strip>>>;
423
+ codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
424
+ name: z.ZodString;
425
+ description: z.ZodOptional<z.ZodString>;
426
+ default: z.ZodOptional<z.ZodBoolean>;
427
+ }, z.core.$strip>>>;
428
+ opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
429
+ name: z.ZodString;
430
+ description: z.ZodOptional<z.ZodString>;
431
+ default: z.ZodOptional<z.ZodBoolean>;
432
+ }, z.core.$strip>>>;
433
+ cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
434
+ name: z.ZodString;
435
+ description: z.ZodOptional<z.ZodString>;
436
+ default: z.ZodOptional<z.ZodBoolean>;
437
+ }, z.core.$strip>>>;
438
+ antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
439
+ name: z.ZodString;
440
+ description: z.ZodOptional<z.ZodString>;
441
+ default: z.ZodOptional<z.ZodBoolean>;
442
+ }, z.core.$strip>>>;
443
+ }, z.core.$strip>>;
417
444
  context: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
418
- projects: z.ZodArray<z.ZodObject<{
445
+ projects: z.ZodRecord<z.ZodString, z.ZodObject<{
419
446
  name: z.ZodOptional<z.ZodString>;
420
447
  active: z.ZodOptional<z.ZodBoolean>;
421
- cwd: z.ZodString;
448
+ cwd: z.ZodOptional<z.ZodString>;
422
449
  telegram: z.ZodObject<{
423
450
  botToken: z.ZodString;
424
451
  }, z.core.$strip>;
@@ -457,26 +484,32 @@ declare const MultiConfigFileSchema: z.ZodObject<{
457
484
  claude: z.ZodOptional<z.ZodArray<z.ZodObject<{
458
485
  name: z.ZodString;
459
486
  description: z.ZodOptional<z.ZodString>;
487
+ default: z.ZodOptional<z.ZodBoolean>;
460
488
  }, z.core.$strip>>>;
461
489
  copilot: z.ZodOptional<z.ZodArray<z.ZodObject<{
462
490
  name: z.ZodString;
463
491
  description: z.ZodOptional<z.ZodString>;
492
+ default: z.ZodOptional<z.ZodBoolean>;
464
493
  }, z.core.$strip>>>;
465
494
  codex: z.ZodOptional<z.ZodArray<z.ZodObject<{
466
495
  name: z.ZodString;
467
496
  description: z.ZodOptional<z.ZodString>;
497
+ default: z.ZodOptional<z.ZodBoolean>;
468
498
  }, z.core.$strip>>>;
469
499
  opencode: z.ZodOptional<z.ZodArray<z.ZodObject<{
470
500
  name: z.ZodString;
471
501
  description: z.ZodOptional<z.ZodString>;
502
+ default: z.ZodOptional<z.ZodBoolean>;
472
503
  }, z.core.$strip>>>;
473
504
  cursor: z.ZodOptional<z.ZodArray<z.ZodObject<{
474
505
  name: z.ZodString;
475
506
  description: z.ZodOptional<z.ZodString>;
507
+ default: z.ZodOptional<z.ZodBoolean>;
476
508
  }, z.core.$strip>>>;
477
509
  antigravity: z.ZodOptional<z.ZodArray<z.ZodObject<{
478
510
  name: z.ZodString;
479
511
  description: z.ZodOptional<z.ZodString>;
512
+ default: z.ZodOptional<z.ZodBoolean>;
480
513
  }, z.core.$strip>>>;
481
514
  }, z.core.$strip>>;
482
515
  logging: z.ZodOptional<z.ZodObject<{
@@ -553,6 +586,9 @@ declare const MultiConfigFileSchema: z.ZodObject<{
553
586
  model: z.ZodOptional<z.ZodObject<{
554
587
  enabled: z.ZodOptional<z.ZodBoolean>;
555
588
  }, z.core.$strip>>;
589
+ engine: z.ZodOptional<z.ZodObject<{
590
+ enabled: z.ZodOptional<z.ZodBoolean>;
591
+ }, z.core.$strip>>;
556
592
  }, z.core.$strip>>;
557
593
  }, z.core.$strip>>;
558
594
  }, z.core.$strip>;
@@ -563,6 +599,7 @@ export type EngineName = z.infer<typeof EngineNameSchema>;
563
599
  export interface ProviderModel {
564
600
  name: string;
565
601
  description?: string;
602
+ default?: boolean;
566
603
  }
567
604
  export interface ResolvedProjectConfig {
568
605
  slug: string;
@@ -607,6 +644,8 @@ export interface ResolvedProjectConfig {
607
644
  } | undefined;
608
645
  context: Record<string, string> | undefined;
609
646
  providerModels: ProviderModel[];
647
+ providerDefaultModel: string | undefined;
648
+ availableEngines: EngineName[];
610
649
  commands: {
611
650
  start: {
612
651
  enabled: boolean;
@@ -636,6 +675,9 @@ export interface ResolvedProjectConfig {
636
675
  model: {
637
676
  enabled: boolean;
638
677
  };
678
+ engine: {
679
+ enabled: boolean;
680
+ };
639
681
  };
640
682
  }
641
683
  export declare class ConfigLoadError extends Error {
@@ -646,9 +688,14 @@ export interface LoadedConfigResult {
646
688
  loadedFiles: string[];
647
689
  }
648
690
  export declare function deriveSlug(name: string | undefined, cwd: string): string;
649
- export declare function resolveProjectConfig(project: ProjectFileEntry, globals: GlobalsFile, configDir: string, rootContext?: Record<string, string>): ResolvedProjectConfig;
691
+ export declare function resolveProjectConfig(key: string, project: ProjectFileEntry, globals: GlobalsFile, configDir: string, rootContext?: Record<string, string>, providers?: z.infer<typeof ProvidersConfigSchema>): ResolvedProjectConfig;
650
692
  export declare function validateProjects(projects: ResolvedProjectConfig[]): void;
651
693
  export declare function validateAccessPolicies(projects: ResolvedProjectConfig[]): void;
694
+ /**
695
+ * Validates that at most one model per providers.<engine> list has default: true.
696
+ * Call after config load and env substitution, before resolving project configs.
697
+ */
698
+ export declare function validateProviderDefaultUniqueness(config: MultiConfigFile): void;
652
699
  export type ConfigFormat = "json" | "jsonc" | "yaml";
653
700
  interface ResolvedConfigFile {
654
701
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -4,6 +4,7 @@ import { parse as parseEnv } from "dotenv";
4
4
  import stripJsonComments from "strip-json-comments";
5
5
  import { parse as parseYaml } from "yaml";
6
6
  import { z } from "zod";
7
+ import { isCliAvailable } from "./engine/cli-available.js";
7
8
  // ─── Zod helpers ──────────────────────────────────────────────────────────────
8
9
  const TranscriptionModelSchema = z.enum([
9
10
  "tiny",
@@ -102,11 +103,13 @@ const CommandsConfigSchema = z
102
103
  clean: SimpleCommandConfigSchema,
103
104
  git: GitConfigSchema,
104
105
  model: GitConfigSchema,
106
+ engine: GitConfigSchema,
105
107
  })
106
108
  .optional();
107
109
  const ProviderModelSchema = z.object({
108
110
  name: z.string().min(1),
109
111
  description: z.string().optional(),
112
+ default: z.boolean().optional(),
110
113
  });
111
114
  const ProvidersConfigSchema = z
112
115
  .object({
@@ -130,7 +133,6 @@ const GlobalsFileSchema = z
130
133
  .object({
131
134
  access: AccessSchema,
132
135
  engine: EngineConfigSchema,
133
- providers: ProvidersConfigSchema,
134
136
  logging: z
135
137
  .object({
136
138
  level: LogLevelSchema,
@@ -154,11 +156,17 @@ const GlobalsFileSchema = z
154
156
  commands: CommandsConfigSchema,
155
157
  })
156
158
  .optional();
159
+ // ─── Project map key (slug-like: safe for default cwd path segment) ─────────────
160
+ const PROJECT_KEY_REGEX = /^[a-zA-Z0-9_-]+$/;
161
+ /** Project map keys must be slug-like so default cwd is a safe path segment. */
162
+ const ProjectKeySchema = z
163
+ .string()
164
+ .regex(PROJECT_KEY_REGEX, "project key must be slug-like (letters, numbers, dashes, underscores only)");
157
165
  // ─── Per-project schema ────────────────────────────────────────────────────────
158
166
  const ProjectFileSchema = z.object({
159
167
  name: z.string().optional(),
160
168
  active: z.boolean().optional(),
161
- cwd: z.string().min(1, "project.cwd is required"),
169
+ cwd: z.string().min(1, "project.cwd must be non-empty when set").optional(),
162
170
  telegram: z.object({
163
171
  botToken: z.string().min(1, "project.telegram.botToken is required"),
164
172
  }),
@@ -189,12 +197,16 @@ const ProjectFileSchema = z.object({
189
197
  commands: CommandsConfigSchema,
190
198
  });
191
199
  // ─── Multi-project config file schema ─────────────────────────────────────────
200
+ const ProjectsMapSchema = z
201
+ .record(ProjectKeySchema, ProjectFileSchema)
202
+ .refine((rec) => Object.keys(rec).length >= 1, {
203
+ message: "At least one project is required",
204
+ });
192
205
  const MultiConfigFileSchema = z.object({
193
206
  globals: GlobalsFileSchema,
207
+ providers: ProvidersConfigSchema,
194
208
  context: z.record(z.string(), z.string()).optional(),
195
- projects: z
196
- .array(ProjectFileSchema)
197
- .min(1, "At least one project is required"),
209
+ projects: ProjectsMapSchema,
198
210
  });
199
211
  // ─── Local config partial schema ──────────────────────────────────────────────
200
212
  const LocalProjectSchema = ProjectFileSchema.partial().extend({
@@ -204,8 +216,9 @@ const LocalProjectSchema = ProjectFileSchema.partial().extend({
204
216
  const LocalConfigFileSchema = z
205
217
  .object({
206
218
  globals: GlobalsFileSchema,
219
+ providers: ProvidersConfigSchema,
207
220
  context: z.record(z.string(), z.string()).optional(),
208
- projects: z.array(LocalProjectSchema).optional(),
221
+ projects: z.record(ProjectKeySchema, LocalProjectSchema).optional(),
209
222
  })
210
223
  .optional();
211
224
  // ─── Config load result & errors ───────────────────────────────────────────────
@@ -231,6 +244,13 @@ function parseTelegramUserId(value, path) {
231
244
  }
232
245
  return num;
233
246
  }
247
+ /** Set name/cwd from map key when omitted. Mutates entries in place. */
248
+ function normalizeProjectMap(projects) {
249
+ for (const [key, entry] of Object.entries(projects)) {
250
+ entry.name = entry.name ?? key;
251
+ entry.cwd = entry.cwd ?? key;
252
+ }
253
+ }
234
254
  function normalizeAllowedUserIdsInConfig(config) {
235
255
  const globalsAccess = config.globals?.access;
236
256
  if (globalsAccess?.allowedUserIds != null) {
@@ -241,15 +261,14 @@ function normalizeAllowedUserIdsInConfig(config) {
241
261
  }
242
262
  globalsAccess.allowedUserIds = normalized;
243
263
  }
244
- for (let j = 0; j < config.projects.length; j++) {
245
- const project = config.projects[j];
264
+ for (const [key, project] of Object.entries(config.projects)) {
246
265
  const access = project.access;
247
266
  if (access?.allowedUserIds == null)
248
267
  continue;
249
268
  const raw = access.allowedUserIds;
250
269
  const normalized = [];
251
270
  for (let i = 0; i < raw.length; i++) {
252
- normalized.push(parseTelegramUserId(raw[i], `projects[${j}].access.allowedUserIds[${i}]`));
271
+ normalized.push(parseTelegramUserId(raw[i], `projects.${key}.access.allowedUserIds[${i}]`));
253
272
  }
254
273
  access.allowedUserIds = normalized;
255
274
  }
@@ -280,11 +299,11 @@ function resolveDataDir(dataDirRaw, projectCwd, configDir, slug) {
280
299
  return resolve(projectCwd, dataDirRaw);
281
300
  }
282
301
  // ─── Merge: project over globals over defaults ─────────────────────────────────
283
- export function resolveProjectConfig(project, globals, configDir, rootContext) {
284
- const resolvedCwd = isAbsolute(project.cwd)
285
- ? project.cwd
286
- : resolve(configDir, project.cwd);
287
- const slug = deriveSlug(project.name, project.cwd);
302
+ export function resolveProjectConfig(key, project, globals, configDir, rootContext, providers) {
303
+ const slug = key;
304
+ const name = project.name ?? key;
305
+ const cwd = project.cwd ?? key;
306
+ const resolvedCwd = isAbsolute(cwd) ? cwd : resolve(configDir, cwd);
288
307
  const logDir = resolve(configDir, ".hal", "logs", slug);
289
308
  const dataDir = resolveDataDir(project.dataDir ?? globals.dataDir, resolvedCwd, configDir, slug);
290
309
  const hasTranscription = project.transcription !== undefined || globals.transcription !== undefined;
@@ -304,11 +323,54 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
304
323
  }
305
324
  return msg.text;
306
325
  }
326
+ // Resolve engine and provider models early (needed for command enabled flags)
327
+ const rawEngineName = project.engine?.name ?? globals.engine?.name;
328
+ if (!rawEngineName) {
329
+ throw new ConfigLoadError(`Configuration error: project "${key}" has no engine configured. ` +
330
+ "Set engine.name in the project or in globals.");
331
+ }
332
+ const engineName = rawEngineName;
333
+ const mergedProviders = {
334
+ ...(providers ?? {}),
335
+ ...(project.providers ?? {}),
336
+ };
337
+ const availableEngines = Object.keys(mergedProviders).filter((k) => {
338
+ const list = mergedProviders[k];
339
+ return Array.isArray(list) && list.length > 0;
340
+ });
341
+ const rawProviderModels = project.providers?.[engineName] ?? providers?.[engineName] ?? [];
342
+ const providerModels = rawProviderModels.map((m) => ({
343
+ name: m.name,
344
+ description: m.description,
345
+ default: m.default,
346
+ }));
347
+ const defaultEntries = providerModels.filter((m) => m.default === true);
348
+ const providerDefaultModel = defaultEntries.length === 1 ? defaultEntries[0].name : undefined;
307
349
  // Resolve command enabled flags (project > globals > default)
308
350
  const rawStart = project.commands?.start ?? globals.commands?.start;
309
351
  const rawHelp = project.commands?.help ?? globals.commands?.help;
310
352
  const rawReset = project.commands?.reset ?? globals.commands?.reset;
311
353
  const rawClean = project.commands?.clean ?? globals.commands?.clean;
354
+ // Enable /model when we have a config list, or when the engine supports self-discovery and its CLI is available
355
+ const effectiveEngineCommand = project.engine?.command ?? globals.engine?.command ?? undefined;
356
+ const defaultCommandForEngine = engineName === "opencode"
357
+ ? "opencode"
358
+ : engineName === "cursor"
359
+ ? "agent"
360
+ : null;
361
+ const modelCliCommand = effectiveEngineCommand ?? defaultCommandForEngine;
362
+ const selfDiscoveryEnabled = rawProviderModels.length === 0 &&
363
+ (engineName === "opencode" || engineName === "cursor") &&
364
+ modelCliCommand !== null &&
365
+ isCliAvailable(modelCliCommand);
366
+ const modelEnabled = (project.commands?.model?.enabled ??
367
+ globals.commands?.model?.enabled ??
368
+ true) &&
369
+ (providerModels.length > 1 || selfDiscoveryEnabled);
370
+ const engineEnabled = (project.commands?.engine?.enabled ??
371
+ globals.commands?.engine?.enabled ??
372
+ true) &&
373
+ availableEngines.length > 1;
312
374
  const resolvedCommands = {
313
375
  start: {
314
376
  enabled: project.commands?.start?.enabled ??
@@ -351,23 +413,12 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
351
413
  globals.commands?.git?.enabled ??
352
414
  false,
353
415
  },
354
- model: {
355
- enabled: project.commands?.model?.enabled ??
356
- globals.commands?.model?.enabled ??
357
- true,
358
- },
416
+ model: { enabled: modelEnabled },
417
+ engine: { enabled: engineEnabled },
359
418
  };
360
- const engineName = (project.engine?.name ??
361
- globals.engine?.name ??
362
- "claude");
363
- const rawProviderModels = project.providers?.[engineName] ?? globals.providers?.[engineName] ?? [];
364
- const providerModels = rawProviderModels.map((m) => ({
365
- name: m.name,
366
- description: m.description,
367
- }));
368
419
  return {
369
420
  slug,
370
- name: project.name,
421
+ name,
371
422
  cwd: resolvedCwd,
372
423
  configDir,
373
424
  dataDir,
@@ -425,6 +476,8 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
425
476
  }
426
477
  : undefined,
427
478
  providerModels,
479
+ providerDefaultModel,
480
+ availableEngines,
428
481
  context: hasContext ? { ...rootContext, ...project.context } : undefined,
429
482
  commands: resolvedCommands,
430
483
  };
@@ -467,6 +520,49 @@ export function validateAccessPolicies(projects) {
467
520
  throw new ConfigLoadError(`Configuration error: invalid access policy\n${errors.map((e) => ` - ${e}`).join("\n")}`);
468
521
  }
469
522
  }
523
+ // ─── Boot-time provider default uniqueness ────────────────────────────────────
524
+ const PROVIDER_ENGINE_KEYS = [
525
+ "claude",
526
+ "copilot",
527
+ "codex",
528
+ "opencode",
529
+ "cursor",
530
+ "antigravity",
531
+ ];
532
+ function countProviderDefaults(list) {
533
+ if (!Array.isArray(list))
534
+ return 0;
535
+ return list.filter((m) => m.default === true).length;
536
+ }
537
+ /**
538
+ * Validates that at most one model per providers.<engine> list has default: true.
539
+ * Call after config load and env substitution, before resolving project configs.
540
+ */
541
+ export function validateProviderDefaultUniqueness(config) {
542
+ const topProviders = config.providers;
543
+ if (topProviders) {
544
+ for (const engine of PROVIDER_ENGINE_KEYS) {
545
+ const list = topProviders[engine];
546
+ const n = countProviderDefaults(list);
547
+ if (n > 1) {
548
+ throw new ConfigLoadError(`Configuration error: at most one model in providers.${engine} may have default: true (found ${n}).`);
549
+ }
550
+ }
551
+ }
552
+ for (const [key, project] of Object.entries(config.projects)) {
553
+ const projectProviders = project.providers;
554
+ if (!projectProviders)
555
+ continue;
556
+ const projectLabel = project.name ?? key;
557
+ for (const engine of PROVIDER_ENGINE_KEYS) {
558
+ const list = projectProviders[engine];
559
+ const n = countProviderDefaults(list);
560
+ if (n > 1) {
561
+ throw new ConfigLoadError(`Configuration error: at most one model in projects["${key}"].providers.${engine} may have default: true (found ${n}). Project: ${projectLabel}.`);
562
+ }
563
+ }
564
+ }
565
+ }
470
566
  function loadEnvFiles(configDir, projectCwds) {
471
567
  const loadedFiles = [];
472
568
  const vars = {};
@@ -619,32 +715,36 @@ function mergeLocalIntoBase(base, local, baseFileName, localFileName) {
619
715
  const mergedGlobals = local.globals !== undefined
620
716
  ? deepMerge(base.globals ?? {}, local.globals)
621
717
  : base.globals;
718
+ const mergedProviders = local.providers !== undefined
719
+ ? base.providers
720
+ ? deepMerge(base.providers, local.providers)
721
+ : local.providers
722
+ : base.providers;
622
723
  const mergedContext = local.context !== undefined
623
724
  ? base.context
624
725
  ? { ...base.context, ...local.context }
625
726
  : local.context
626
727
  : base.context;
627
- if (!local.projects || local.projects.length === 0) {
628
- return { ...base, globals: mergedGlobals, context: mergedContext };
629
- }
630
- const mergedProjects = [...base.projects];
631
- for (const localProject of local.projects) {
632
- const matchKey = localProject.name ?? localProject.cwd;
633
- const idx = mergedProjects.findIndex((bp) => {
634
- if (localProject.name)
635
- return bp.name === localProject.name;
636
- if (localProject.cwd)
637
- return bp.cwd === localProject.cwd;
638
- return false;
639
- });
640
- if (idx === -1) {
641
- throw new ConfigLoadError(`Configuration error: local project "${matchKey}" not found in ${baseFileName}.\n` +
642
- ` Every entry in ${localFileName} projects must match a base project by name or cwd.`);
728
+ if (!local.projects || Object.keys(local.projects).length === 0) {
729
+ return {
730
+ ...base,
731
+ globals: mergedGlobals,
732
+ providers: mergedProviders,
733
+ context: mergedContext,
734
+ };
735
+ }
736
+ const mergedProjects = { ...base.projects };
737
+ for (const [localKey, localProject] of Object.entries(local.projects)) {
738
+ if (!(localKey in mergedProjects)) {
739
+ throw new ConfigLoadError(`Configuration error: local project key "${localKey}" not found in ${baseFileName}.\n` +
740
+ ` Every key in ${localFileName} projects must exist in the base config.`);
643
741
  }
644
- mergedProjects[idx] = deepMerge(mergedProjects[idx], localProject);
742
+ mergedProjects[localKey] = deepMerge(mergedProjects[localKey], localProject);
645
743
  }
744
+ normalizeProjectMap(mergedProjects);
646
745
  return {
647
746
  globals: mergedGlobals,
747
+ providers: mergedProviders,
648
748
  context: mergedContext,
649
749
  projects: mergedProjects,
650
750
  };
@@ -681,6 +781,7 @@ function loadMultiConfigInternal(configDir) {
681
781
  throw new ConfigLoadError(`Configuration error in ${baseFileName}:\n${issues}`);
682
782
  }
683
783
  let merged = baseResult.data;
784
+ normalizeProjectMap(merged.projects);
684
785
  // 3. Load and merge local config
685
786
  const localResult = loadLocalConfig(configDir);
686
787
  if (localResult !== null) {
@@ -689,7 +790,13 @@ function loadMultiConfigInternal(configDir) {
689
790
  merged = mergeLocalIntoBase(merged, localResult.config, baseFileName, localFileName);
690
791
  }
691
792
  // 4. Load .env files (using raw cwds from merged config for path resolution)
692
- const rawCwds = merged.projects.map((p) => isAbsolute(p.cwd) ? p.cwd : resolve(configDir, p.cwd));
793
+ // Stable order: sort project keys so iteration is deterministic.
794
+ const projectKeys = Object.keys(merged.projects).sort();
795
+ const rawCwds = projectKeys.map((key) => {
796
+ const p = merged.projects[key];
797
+ const cwd = p.cwd ?? key;
798
+ return isAbsolute(cwd) ? cwd : resolve(configDir, cwd);
799
+ });
693
800
  const envSources = loadEnvFiles(configDir, rawCwds);
694
801
  // 5. Substitute env vars in the merged raw object (before final Zod pass)
695
802
  const substituted = substituteEnvVars(merged, envSources.vars);
@@ -701,6 +808,7 @@ function loadMultiConfigInternal(configDir) {
701
808
  .join("\n");
702
809
  throw new ConfigLoadError(`Configuration error after environment variable substitution:\n${issues}`);
703
810
  }
811
+ normalizeProjectMap(finalResult.data.projects);
704
812
  // 7. Normalize allowedUserIds (string | number)[] → number[] with validation
705
813
  normalizeAllowedUserIdsInConfig(finalResult.data);
706
814
  return {