@marcopeg/hal 1.0.22 → 1.0.25

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 (58) 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 +18 -1
  11. package/dist/bot/commands/loader.d.ts.map +1 -1
  12. package/dist/bot/commands/loader.js +39 -7
  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 +6 -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 +15 -2
  22. package/dist/bot/commands/model.js.map +1 -1
  23. package/dist/bot/commands/watcher.d.ts.map +1 -1
  24. package/dist/bot/commands/watcher.js +13 -4
  25. package/dist/bot/commands/watcher.js.map +1 -1
  26. package/dist/bot.d.ts.map +1 -1
  27. package/dist/bot.js +22 -4
  28. package/dist/bot.js.map +1 -1
  29. package/dist/cli.js +19 -16
  30. package/dist/cli.js.map +1 -1
  31. package/dist/config-writer.d.ts +2 -6
  32. package/dist/config-writer.d.ts.map +1 -1
  33. package/dist/config-writer.js +32 -28
  34. package/dist/config-writer.js.map +1 -1
  35. package/dist/config.d.ts +103 -56
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +141 -46
  38. package/dist/config.js.map +1 -1
  39. package/dist/engine/adapters/codex.d.ts.map +1 -1
  40. package/dist/engine/adapters/codex.js +4 -2
  41. package/dist/engine/adapters/codex.js.map +1 -1
  42. package/dist/engine/adapters/copilot.d.ts.map +1 -1
  43. package/dist/engine/adapters/copilot.js +8 -5
  44. package/dist/engine/adapters/copilot.js.map +1 -1
  45. package/dist/engine/adapters/cursor.d.ts.map +1 -1
  46. package/dist/engine/adapters/cursor.js +8 -3
  47. package/dist/engine/adapters/cursor.js.map +1 -1
  48. package/dist/engine/adapters/opencode.d.ts.map +1 -1
  49. package/dist/engine/adapters/opencode.js +4 -2
  50. package/dist/engine/adapters/opencode.js.map +1 -1
  51. package/dist/logger.d.ts +2 -1
  52. package/dist/logger.d.ts.map +1 -1
  53. package/dist/logger.js +54 -3
  54. package/dist/logger.js.map +1 -1
  55. package/dist/user/setup.d.ts.map +1 -1
  56. package/dist/user/setup.js +5 -1
  57. package/dist/user/setup.js.map +1 -1
  58. package/package.json +1 -1
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
@@ -102,11 +102,13 @@ const CommandsConfigSchema = z
102
102
  clean: SimpleCommandConfigSchema,
103
103
  git: GitConfigSchema,
104
104
  model: GitConfigSchema,
105
+ engine: GitConfigSchema,
105
106
  })
106
107
  .optional();
107
108
  const ProviderModelSchema = z.object({
108
109
  name: z.string().min(1),
109
110
  description: z.string().optional(),
111
+ default: z.boolean().optional(),
110
112
  });
111
113
  const ProvidersConfigSchema = z
112
114
  .object({
@@ -130,7 +132,6 @@ const GlobalsFileSchema = z
130
132
  .object({
131
133
  access: AccessSchema,
132
134
  engine: EngineConfigSchema,
133
- providers: ProvidersConfigSchema,
134
135
  logging: z
135
136
  .object({
136
137
  level: LogLevelSchema,
@@ -154,11 +155,17 @@ const GlobalsFileSchema = z
154
155
  commands: CommandsConfigSchema,
155
156
  })
156
157
  .optional();
158
+ // ─── Project map key (slug-like: safe for default cwd path segment) ─────────────
159
+ const PROJECT_KEY_REGEX = /^[a-zA-Z0-9_-]+$/;
160
+ /** Project map keys must be slug-like so default cwd is a safe path segment. */
161
+ const ProjectKeySchema = z
162
+ .string()
163
+ .regex(PROJECT_KEY_REGEX, "project key must be slug-like (letters, numbers, dashes, underscores only)");
157
164
  // ─── Per-project schema ────────────────────────────────────────────────────────
158
165
  const ProjectFileSchema = z.object({
159
166
  name: z.string().optional(),
160
167
  active: z.boolean().optional(),
161
- cwd: z.string().min(1, "project.cwd is required"),
168
+ cwd: z.string().min(1, "project.cwd must be non-empty when set").optional(),
162
169
  telegram: z.object({
163
170
  botToken: z.string().min(1, "project.telegram.botToken is required"),
164
171
  }),
@@ -189,12 +196,16 @@ const ProjectFileSchema = z.object({
189
196
  commands: CommandsConfigSchema,
190
197
  });
191
198
  // ─── Multi-project config file schema ─────────────────────────────────────────
199
+ const ProjectsMapSchema = z
200
+ .record(ProjectKeySchema, ProjectFileSchema)
201
+ .refine((rec) => Object.keys(rec).length >= 1, {
202
+ message: "At least one project is required",
203
+ });
192
204
  const MultiConfigFileSchema = z.object({
193
205
  globals: GlobalsFileSchema,
206
+ providers: ProvidersConfigSchema,
194
207
  context: z.record(z.string(), z.string()).optional(),
195
- projects: z
196
- .array(ProjectFileSchema)
197
- .min(1, "At least one project is required"),
208
+ projects: ProjectsMapSchema,
198
209
  });
199
210
  // ─── Local config partial schema ──────────────────────────────────────────────
200
211
  const LocalProjectSchema = ProjectFileSchema.partial().extend({
@@ -204,8 +215,9 @@ const LocalProjectSchema = ProjectFileSchema.partial().extend({
204
215
  const LocalConfigFileSchema = z
205
216
  .object({
206
217
  globals: GlobalsFileSchema,
218
+ providers: ProvidersConfigSchema,
207
219
  context: z.record(z.string(), z.string()).optional(),
208
- projects: z.array(LocalProjectSchema).optional(),
220
+ projects: z.record(ProjectKeySchema, LocalProjectSchema).optional(),
209
221
  })
210
222
  .optional();
211
223
  // ─── Config load result & errors ───────────────────────────────────────────────
@@ -231,6 +243,13 @@ function parseTelegramUserId(value, path) {
231
243
  }
232
244
  return num;
233
245
  }
246
+ /** Set name/cwd from map key when omitted. Mutates entries in place. */
247
+ function normalizeProjectMap(projects) {
248
+ for (const [key, entry] of Object.entries(projects)) {
249
+ entry.name = entry.name ?? key;
250
+ entry.cwd = entry.cwd ?? key;
251
+ }
252
+ }
234
253
  function normalizeAllowedUserIdsInConfig(config) {
235
254
  const globalsAccess = config.globals?.access;
236
255
  if (globalsAccess?.allowedUserIds != null) {
@@ -241,15 +260,14 @@ function normalizeAllowedUserIdsInConfig(config) {
241
260
  }
242
261
  globalsAccess.allowedUserIds = normalized;
243
262
  }
244
- for (let j = 0; j < config.projects.length; j++) {
245
- const project = config.projects[j];
263
+ for (const [key, project] of Object.entries(config.projects)) {
246
264
  const access = project.access;
247
265
  if (access?.allowedUserIds == null)
248
266
  continue;
249
267
  const raw = access.allowedUserIds;
250
268
  const normalized = [];
251
269
  for (let i = 0; i < raw.length; i++) {
252
- normalized.push(parseTelegramUserId(raw[i], `projects[${j}].access.allowedUserIds[${i}]`));
270
+ normalized.push(parseTelegramUserId(raw[i], `projects.${key}.access.allowedUserIds[${i}]`));
253
271
  }
254
272
  access.allowedUserIds = normalized;
255
273
  }
@@ -280,11 +298,11 @@ function resolveDataDir(dataDirRaw, projectCwd, configDir, slug) {
280
298
  return resolve(projectCwd, dataDirRaw);
281
299
  }
282
300
  // ─── 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);
301
+ export function resolveProjectConfig(key, project, globals, configDir, rootContext, providers) {
302
+ const slug = key;
303
+ const name = project.name ?? key;
304
+ const cwd = project.cwd ?? key;
305
+ const resolvedCwd = isAbsolute(cwd) ? cwd : resolve(configDir, cwd);
288
306
  const logDir = resolve(configDir, ".hal", "logs", slug);
289
307
  const dataDir = resolveDataDir(project.dataDir ?? globals.dataDir, resolvedCwd, configDir, slug);
290
308
  const hasTranscription = project.transcription !== undefined || globals.transcription !== undefined;
@@ -304,11 +322,42 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
304
322
  }
305
323
  return msg.text;
306
324
  }
325
+ // Resolve engine and provider models early (needed for command enabled flags)
326
+ const rawEngineName = project.engine?.name ?? globals.engine?.name;
327
+ if (!rawEngineName) {
328
+ throw new ConfigLoadError(`Configuration error: project "${key}" has no engine configured. ` +
329
+ "Set engine.name in the project or in globals.");
330
+ }
331
+ const engineName = rawEngineName;
332
+ const mergedProviders = {
333
+ ...(providers ?? {}),
334
+ ...(project.providers ?? {}),
335
+ };
336
+ const availableEngines = Object.keys(mergedProviders).filter((k) => {
337
+ const list = mergedProviders[k];
338
+ return Array.isArray(list) && list.length > 0;
339
+ });
340
+ const rawProviderModels = project.providers?.[engineName] ?? providers?.[engineName] ?? [];
341
+ const providerModels = rawProviderModels.map((m) => ({
342
+ name: m.name,
343
+ description: m.description,
344
+ default: m.default,
345
+ }));
346
+ const defaultEntries = providerModels.filter((m) => m.default === true);
347
+ const providerDefaultModel = defaultEntries.length === 1 ? defaultEntries[0].name : undefined;
307
348
  // Resolve command enabled flags (project > globals > default)
308
349
  const rawStart = project.commands?.start ?? globals.commands?.start;
309
350
  const rawHelp = project.commands?.help ?? globals.commands?.help;
310
351
  const rawReset = project.commands?.reset ?? globals.commands?.reset;
311
352
  const rawClean = project.commands?.clean ?? globals.commands?.clean;
353
+ const modelEnabled = (project.commands?.model?.enabled ??
354
+ globals.commands?.model?.enabled ??
355
+ true) &&
356
+ providerModels.length > 1;
357
+ const engineEnabled = (project.commands?.engine?.enabled ??
358
+ globals.commands?.engine?.enabled ??
359
+ true) &&
360
+ availableEngines.length > 1;
312
361
  const resolvedCommands = {
313
362
  start: {
314
363
  enabled: project.commands?.start?.enabled ??
@@ -351,23 +400,12 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
351
400
  globals.commands?.git?.enabled ??
352
401
  false,
353
402
  },
354
- model: {
355
- enabled: project.commands?.model?.enabled ??
356
- globals.commands?.model?.enabled ??
357
- true,
358
- },
403
+ model: { enabled: modelEnabled },
404
+ engine: { enabled: engineEnabled },
359
405
  };
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
406
  return {
369
407
  slug,
370
- name: project.name,
408
+ name,
371
409
  cwd: resolvedCwd,
372
410
  configDir,
373
411
  dataDir,
@@ -425,6 +463,8 @@ export function resolveProjectConfig(project, globals, configDir, rootContext) {
425
463
  }
426
464
  : undefined,
427
465
  providerModels,
466
+ providerDefaultModel,
467
+ availableEngines,
428
468
  context: hasContext ? { ...rootContext, ...project.context } : undefined,
429
469
  commands: resolvedCommands,
430
470
  };
@@ -467,6 +507,49 @@ export function validateAccessPolicies(projects) {
467
507
  throw new ConfigLoadError(`Configuration error: invalid access policy\n${errors.map((e) => ` - ${e}`).join("\n")}`);
468
508
  }
469
509
  }
510
+ // ─── Boot-time provider default uniqueness ────────────────────────────────────
511
+ const PROVIDER_ENGINE_KEYS = [
512
+ "claude",
513
+ "copilot",
514
+ "codex",
515
+ "opencode",
516
+ "cursor",
517
+ "antigravity",
518
+ ];
519
+ function countProviderDefaults(list) {
520
+ if (!Array.isArray(list))
521
+ return 0;
522
+ return list.filter((m) => m.default === true).length;
523
+ }
524
+ /**
525
+ * Validates that at most one model per providers.<engine> list has default: true.
526
+ * Call after config load and env substitution, before resolving project configs.
527
+ */
528
+ export function validateProviderDefaultUniqueness(config) {
529
+ const topProviders = config.providers;
530
+ if (topProviders) {
531
+ for (const engine of PROVIDER_ENGINE_KEYS) {
532
+ const list = topProviders[engine];
533
+ const n = countProviderDefaults(list);
534
+ if (n > 1) {
535
+ throw new ConfigLoadError(`Configuration error: at most one model in providers.${engine} may have default: true (found ${n}).`);
536
+ }
537
+ }
538
+ }
539
+ for (const [key, project] of Object.entries(config.projects)) {
540
+ const projectProviders = project.providers;
541
+ if (!projectProviders)
542
+ continue;
543
+ const projectLabel = project.name ?? key;
544
+ for (const engine of PROVIDER_ENGINE_KEYS) {
545
+ const list = projectProviders[engine];
546
+ const n = countProviderDefaults(list);
547
+ if (n > 1) {
548
+ throw new ConfigLoadError(`Configuration error: at most one model in projects["${key}"].providers.${engine} may have default: true (found ${n}). Project: ${projectLabel}.`);
549
+ }
550
+ }
551
+ }
552
+ }
470
553
  function loadEnvFiles(configDir, projectCwds) {
471
554
  const loadedFiles = [];
472
555
  const vars = {};
@@ -619,32 +702,36 @@ function mergeLocalIntoBase(base, local, baseFileName, localFileName) {
619
702
  const mergedGlobals = local.globals !== undefined
620
703
  ? deepMerge(base.globals ?? {}, local.globals)
621
704
  : base.globals;
705
+ const mergedProviders = local.providers !== undefined
706
+ ? base.providers
707
+ ? deepMerge(base.providers, local.providers)
708
+ : local.providers
709
+ : base.providers;
622
710
  const mergedContext = local.context !== undefined
623
711
  ? base.context
624
712
  ? { ...base.context, ...local.context }
625
713
  : local.context
626
714
  : 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.`);
715
+ if (!local.projects || Object.keys(local.projects).length === 0) {
716
+ return {
717
+ ...base,
718
+ globals: mergedGlobals,
719
+ providers: mergedProviders,
720
+ context: mergedContext,
721
+ };
722
+ }
723
+ const mergedProjects = { ...base.projects };
724
+ for (const [localKey, localProject] of Object.entries(local.projects)) {
725
+ if (!(localKey in mergedProjects)) {
726
+ throw new ConfigLoadError(`Configuration error: local project key "${localKey}" not found in ${baseFileName}.\n` +
727
+ ` Every key in ${localFileName} projects must exist in the base config.`);
643
728
  }
644
- mergedProjects[idx] = deepMerge(mergedProjects[idx], localProject);
729
+ mergedProjects[localKey] = deepMerge(mergedProjects[localKey], localProject);
645
730
  }
731
+ normalizeProjectMap(mergedProjects);
646
732
  return {
647
733
  globals: mergedGlobals,
734
+ providers: mergedProviders,
648
735
  context: mergedContext,
649
736
  projects: mergedProjects,
650
737
  };
@@ -681,6 +768,7 @@ function loadMultiConfigInternal(configDir) {
681
768
  throw new ConfigLoadError(`Configuration error in ${baseFileName}:\n${issues}`);
682
769
  }
683
770
  let merged = baseResult.data;
771
+ normalizeProjectMap(merged.projects);
684
772
  // 3. Load and merge local config
685
773
  const localResult = loadLocalConfig(configDir);
686
774
  if (localResult !== null) {
@@ -689,7 +777,13 @@ function loadMultiConfigInternal(configDir) {
689
777
  merged = mergeLocalIntoBase(merged, localResult.config, baseFileName, localFileName);
690
778
  }
691
779
  // 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));
780
+ // Stable order: sort project keys so iteration is deterministic.
781
+ const projectKeys = Object.keys(merged.projects).sort();
782
+ const rawCwds = projectKeys.map((key) => {
783
+ const p = merged.projects[key];
784
+ const cwd = p.cwd ?? key;
785
+ return isAbsolute(cwd) ? cwd : resolve(configDir, cwd);
786
+ });
693
787
  const envSources = loadEnvFiles(configDir, rawCwds);
694
788
  // 5. Substitute env vars in the merged raw object (before final Zod pass)
695
789
  const substituted = substituteEnvVars(merged, envSources.vars);
@@ -701,6 +795,7 @@ function loadMultiConfigInternal(configDir) {
701
795
  .join("\n");
702
796
  throw new ConfigLoadError(`Configuration error after environment variable substitution:\n${issues}`);
703
797
  }
798
+ normalizeProjectMap(finalResult.data.projects);
704
799
  // 7. Normalize allowedUserIds (string | number)[] → number[] with validation
705
800
  normalizeAllowedUserIdsInConfig(finalResult.data);
706
801
  return {