devchain-cli 0.4.5 → 0.5.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 (96) hide show
  1. package/dist/cli.js +7 -4
  2. package/dist/drizzle/0021_agent_profiles_family_slug.sql +3 -0
  3. package/dist/drizzle/meta/0021_snapshot.json +2968 -0
  4. package/dist/drizzle/meta/_journal.json +8 -1
  5. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +5 -0
  6. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
  7. package/dist/node_modules/@devchain/shared/schemas/export-schema.js +1 -0
  8. package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
  9. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.d.ts +2 -0
  10. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.d.ts.map +1 -0
  11. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.js +88 -0
  12. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.js.map +1 -0
  13. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  14. package/dist/server/common/errors/error-types.d.ts +4 -0
  15. package/dist/server/common/errors/error-types.js +8 -1
  16. package/dist/server/common/errors/error-types.js.map +1 -1
  17. package/dist/server/modules/core/controllers/health.controller.js +1 -1
  18. package/dist/server/modules/core/controllers/health.controller.js.map +1 -1
  19. package/dist/server/modules/core/core.module.js +2 -1
  20. package/dist/server/modules/core/core.module.js.map +1 -1
  21. package/dist/server/modules/core/services/preflight.service.d.ts +4 -1
  22. package/dist/server/modules/core/services/preflight.service.js +8 -3
  23. package/dist/server/modules/core/services/preflight.service.js.map +1 -1
  24. package/dist/server/modules/epics/services/epics.service.js +0 -51
  25. package/dist/server/modules/epics/services/epics.service.js.map +1 -1
  26. package/dist/server/modules/events/catalog/epic.created.d.ts +2 -2
  27. package/dist/server/modules/events/catalog/index.d.ts +2 -27
  28. package/dist/server/modules/events/catalog/index.js +0 -2
  29. package/dist/server/modules/events/catalog/index.js.map +1 -1
  30. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.d.ts +3 -0
  31. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js +87 -0
  32. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js.map +1 -1
  33. package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +2 -2
  34. package/dist/server/modules/mcp/mcp.module.js +2 -34
  35. package/dist/server/modules/mcp/mcp.module.js.map +1 -1
  36. package/dist/server/modules/mcp/services/mcp-provider-registration.service.js +75 -31
  37. package/dist/server/modules/mcp/services/mcp-provider-registration.service.js.map +1 -1
  38. package/dist/server/modules/profiles/controllers/profiles.controller.d.ts +1 -0
  39. package/dist/server/modules/profiles/controllers/profiles.controller.js +13 -0
  40. package/dist/server/modules/profiles/controllers/profiles.controller.js.map +1 -1
  41. package/dist/server/modules/profiles/dto.d.ts +3 -0
  42. package/dist/server/modules/profiles/dto.js +1 -0
  43. package/dist/server/modules/profiles/dto.js.map +1 -1
  44. package/dist/server/modules/projects/controllers/projects.controller.d.ts +59 -11
  45. package/dist/server/modules/projects/controllers/projects.controller.js +70 -2
  46. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  47. package/dist/server/modules/projects/services/projects.service.d.ts +63 -10
  48. package/dist/server/modules/projects/services/projects.service.js +310 -20
  49. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  50. package/dist/server/modules/providers/adapters/claude.adapter.js +12 -2
  51. package/dist/server/modules/providers/adapters/claude.adapter.js.map +1 -1
  52. package/dist/server/modules/providers/adapters/codex.adapter.js +12 -2
  53. package/dist/server/modules/providers/adapters/codex.adapter.js.map +1 -1
  54. package/dist/server/modules/providers/adapters/gemini.adapter.d.ts +9 -0
  55. package/dist/server/modules/providers/adapters/gemini.adapter.js +56 -0
  56. package/dist/server/modules/providers/adapters/gemini.adapter.js.map +1 -0
  57. package/dist/server/modules/providers/adapters/index.d.ts +2 -0
  58. package/dist/server/modules/providers/adapters/index.js +2 -0
  59. package/dist/server/modules/providers/adapters/index.js.map +1 -1
  60. package/dist/server/modules/providers/adapters/provider-adapter.factory.d.ts +4 -1
  61. package/dist/server/modules/providers/adapters/provider-adapter.factory.js +13 -7
  62. package/dist/server/modules/providers/adapters/provider-adapter.factory.js.map +1 -1
  63. package/dist/server/modules/providers/adapters/provider-adapters.module.d.ts +2 -0
  64. package/dist/server/modules/providers/adapters/provider-adapters.module.js +24 -0
  65. package/dist/server/modules/providers/adapters/provider-adapters.module.js.map +1 -0
  66. package/dist/server/modules/providers/controllers/providers.controller.js +3 -4
  67. package/dist/server/modules/providers/controllers/providers.controller.js.map +1 -1
  68. package/dist/server/modules/providers/providers.module.js +1 -3
  69. package/dist/server/modules/providers/providers.module.js.map +1 -1
  70. package/dist/server/modules/registry/services/unified-template.service.d.ts +1 -1
  71. package/dist/server/modules/registry/services/unified-template.service.js +17 -2
  72. package/dist/server/modules/registry/services/unified-template.service.js.map +1 -1
  73. package/dist/server/modules/sessions/services/sessions.service.js +1 -1
  74. package/dist/server/modules/storage/db/schema.d.ts +19 -0
  75. package/dist/server/modules/storage/db/schema.js +1 -0
  76. package/dist/server/modules/storage/db/schema.js.map +1 -1
  77. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +1 -0
  78. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  79. package/dist/server/modules/storage/local/local-storage.service.js +10 -4
  80. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  81. package/dist/server/modules/storage/models/domain.models.d.ts +3 -1
  82. package/dist/server/templates/claude-codex-advanced.json +150 -120
  83. package/dist/server/templates/claude-opus.json +182 -79
  84. package/dist/server/templates/simple-codex.json +288 -74
  85. package/dist/server/tsconfig.tsbuildinfo +1 -1
  86. package/dist/server/ui/assets/{index-BkGGbapJ.css → index-BoDZOB7c.css} +1 -1
  87. package/dist/server/ui/assets/index-CvNs6-rV.js +735 -0
  88. package/dist/server/ui/index.html +2 -2
  89. package/dist/templates/claude-codex-advanced.json +150 -120
  90. package/dist/templates/claude-opus.json +182 -79
  91. package/dist/templates/simple-codex.json +288 -74
  92. package/package.json +9 -1
  93. package/dist/server/modules/events/catalog/epic.assigned.d.ts +0 -32
  94. package/dist/server/modules/events/catalog/epic.assigned.js +0 -19
  95. package/dist/server/modules/events/catalog/epic.assigned.js.map +0 -1
  96. package/dist/server/ui/assets/index-DLpDwHdv.js +0 -733
@@ -15,12 +15,31 @@ export interface CreateFromTemplateInput {
15
15
  rootPath: string;
16
16
  slug: string;
17
17
  version?: string | null;
18
+ familyProviderMappings?: Record<string, string>;
19
+ }
20
+ export interface ProviderMappingRequired {
21
+ missingProviders: string[];
22
+ familyAlternatives: FamilyAlternative[];
23
+ canImport: boolean;
18
24
  }
19
25
  export interface ImportProjectInput {
20
26
  projectId: string;
21
27
  payload: unknown;
22
28
  dryRun?: boolean;
23
29
  statusMappings?: Record<string, string>;
30
+ familyProviderMappings?: Record<string, string>;
31
+ }
32
+ export interface FamilyAlternative {
33
+ familySlug: string;
34
+ defaultProvider: string;
35
+ defaultProviderAvailable: boolean;
36
+ availableProviders: string[];
37
+ hasAlternatives: boolean;
38
+ }
39
+ export interface FamilyAlternativesResult {
40
+ alternatives: FamilyAlternative[];
41
+ missingProviders: string[];
42
+ canImport: boolean;
24
43
  }
25
44
  export declare class ProjectsService {
26
45
  private readonly storage;
@@ -34,6 +53,18 @@ export declare class ProjectsService {
34
53
  listTemplates(): Promise<TemplateInfo[]>;
35
54
  getTemplateContent(templateId: string): Promise<unknown>;
36
55
  createFromTemplate(input: CreateFromTemplateInput): Promise<{
56
+ success: boolean;
57
+ providerMappingRequired: {
58
+ missingProviders: string[];
59
+ familyAlternatives: FamilyAlternative[];
60
+ canImport: boolean;
61
+ };
62
+ project?: undefined;
63
+ imported?: undefined;
64
+ mappings?: undefined;
65
+ initialPromptSet?: undefined;
66
+ message?: undefined;
67
+ } | {
37
68
  success: boolean;
38
69
  project: import("../../storage/models/domain.models").Project;
39
70
  imported: {
@@ -52,6 +83,7 @@ export declare class ProjectsService {
52
83
  };
53
84
  initialPromptSet: boolean;
54
85
  message: string;
86
+ providerMappingRequired?: undefined;
55
87
  }>;
56
88
  exportProject(projectId: string, opts?: {
57
89
  manifestOverrides?: Partial<ManifestData>;
@@ -128,6 +160,7 @@ export declare class ProjectsService {
128
160
  id: string;
129
161
  name: string;
130
162
  };
163
+ familySlug: string | null;
131
164
  options: string | null;
132
165
  instructions: string | null;
133
166
  temperature: number | null;
@@ -152,7 +185,7 @@ export declare class ProjectsService {
152
185
  } | null;
153
186
  }>;
154
187
  importProject(input: ImportProjectInput): Promise<{
155
- dryRun: boolean;
188
+ dryRun: true;
156
189
  missingProviders: string[];
157
190
  unmatchedStatuses: {
158
191
  id: string;
@@ -164,6 +197,11 @@ export declare class ProjectsService {
164
197
  label: string;
165
198
  color: string;
166
199
  }[];
200
+ providerMappingRequired?: {
201
+ missingProviders: string[];
202
+ familyAlternatives: FamilyAlternative[];
203
+ canImport: boolean;
204
+ };
167
205
  counts: {
168
206
  toImport: {
169
207
  prompts: number;
@@ -181,13 +219,18 @@ export declare class ProjectsService {
181
219
  watchers: number;
182
220
  subscribers: number;
183
221
  };
184
- imported?: undefined;
185
- deleted?: undefined;
186
- epics?: undefined;
187
222
  };
188
- success?: undefined;
223
+ } | {
224
+ success: boolean;
225
+ providerMappingRequired: {
226
+ missingProviders: string[];
227
+ familyAlternatives: FamilyAlternative[];
228
+ canImport: boolean;
229
+ };
189
230
  mode?: undefined;
190
231
  replaced?: undefined;
232
+ missingProviders?: undefined;
233
+ counts?: undefined;
191
234
  mappings?: undefined;
192
235
  initialPromptSet?: undefined;
193
236
  message?: undefined;
@@ -218,8 +261,6 @@ export declare class ProjectsService {
218
261
  agentRemapped: number;
219
262
  agentCleared: number;
220
263
  };
221
- toImport?: undefined;
222
- toDelete?: undefined;
223
264
  };
224
265
  mappings: {
225
266
  promptIdMap: Record<string, string>;
@@ -231,11 +272,22 @@ export declare class ProjectsService {
231
272
  };
232
273
  initialPromptSet: boolean;
233
274
  message: string;
234
- dryRun?: undefined;
235
- unmatchedStatuses?: undefined;
236
- templateStatuses?: undefined;
275
+ providerMappingRequired?: undefined;
237
276
  }>;
238
277
  private resolveProviders;
278
+ computeFamilyAlternatives(templateProfiles: Array<{
279
+ id?: string;
280
+ name: string;
281
+ provider: {
282
+ name: string;
283
+ };
284
+ familySlug?: string | null;
285
+ }>, templateAgents: Array<{
286
+ id?: string;
287
+ name: string;
288
+ profileId?: string;
289
+ }>): Promise<FamilyAlternativesResult>;
290
+ private selectProfilesForFamilies;
239
291
  private buildNameToIdMaps;
240
292
  private createWatchersFromPayload;
241
293
  private createSubscribersFromPayload;
@@ -243,4 +295,5 @@ export declare class ProjectsService {
243
295
  private normalizeProfileOptions;
244
296
  private getImportErrorMessage;
245
297
  private slugify;
298
+ getTemplateManifestForProject(projectId: string): Promise<ManifestData | null>;
246
299
  }
@@ -128,14 +128,28 @@ let ProjectsService = class ProjectsService {
128
128
  hint: 'Template file does not match expected export schema',
129
129
  });
130
130
  }
131
- const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
132
- const { available, missing: missingProviders } = await this.resolveProviders(providerNames);
133
- if (missingProviders.length > 0) {
134
- throw new error_types_1.ValidationError('Import aborted: missing providers', {
135
- missingProviders,
136
- hint: 'Install/configure providers by name before creating project from template.',
131
+ const familyResult = await this.computeFamilyAlternatives(payload.profiles, payload.agents);
132
+ const needsMapping = familyResult.alternatives.some((alt) => !alt.defaultProviderAvailable);
133
+ if (needsMapping && !input.familyProviderMappings) {
134
+ return {
135
+ success: false,
136
+ providerMappingRequired: {
137
+ missingProviders: familyResult.missingProviders,
138
+ familyAlternatives: familyResult.alternatives,
139
+ canImport: familyResult.canImport,
140
+ },
141
+ };
142
+ }
143
+ if (!familyResult.canImport) {
144
+ throw new error_types_1.ValidationError('Cannot import: some profile families have no available providers', {
145
+ hint: 'Install the required providers or use a different template',
146
+ missingProviders: familyResult.missingProviders,
147
+ familyAlternatives: familyResult.alternatives,
137
148
  });
138
149
  }
150
+ const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
151
+ const { available } = await this.resolveProviders(providerNames);
152
+ const selectedProfilesByFamily = this.selectProfilesForFamilies(payload.profiles, payload.agents, input.familyProviderMappings, available);
139
153
  const templatePayload = {
140
154
  prompts: payload.prompts.map((p) => ({
141
155
  id: p.id,
@@ -144,7 +158,7 @@ let ProjectsService = class ProjectsService {
144
158
  version: p.version,
145
159
  tags: p.tags,
146
160
  })),
147
- profiles: payload.profiles.map((prof) => {
161
+ profiles: selectedProfilesByFamily.profilesToCreate.map((prof) => {
148
162
  const providerId = available.get(prof.provider.name.trim().toLowerCase());
149
163
  if (!providerId) {
150
164
  throw new error_types_1.NotFoundError('Provider', prof.provider.name);
@@ -153,18 +167,22 @@ let ProjectsService = class ProjectsService {
153
167
  id: prof.id,
154
168
  name: prof.name,
155
169
  providerId,
170
+ familySlug: prof.familySlug ?? null,
156
171
  options: this.normalizeProfileOptions(prof.options),
157
172
  instructions: prof.instructions ?? null,
158
173
  temperature: prof.temperature ?? null,
159
174
  maxTokens: prof.maxTokens ?? null,
160
175
  };
161
176
  }),
162
- agents: payload.agents.map((a) => ({
163
- id: a.id,
164
- name: a.name,
165
- profileId: a.profileId,
166
- description: a.description,
167
- })),
177
+ agents: payload.agents.map((a) => {
178
+ const remappedProfileId = selectedProfilesByFamily.agentProfileMap.get(a.id ?? '') ?? a.profileId;
179
+ return {
180
+ id: a.id,
181
+ name: a.name,
182
+ profileId: remappedProfileId,
183
+ description: a.description,
184
+ };
185
+ }),
168
186
  statuses: payload.statuses.map((s) => ({
169
187
  id: s.id,
170
188
  label: s.label,
@@ -221,6 +239,7 @@ let ProjectsService = class ProjectsService {
221
239
  agentNameToId: agentNameToNewId,
222
240
  profileNameToId: profileNameToNewId,
223
241
  providerNameToId: available,
242
+ profileNameRemapMap: selectedProfilesByFamily.profileNameRemapMap,
224
243
  });
225
244
  const { created: subscribersCreated } = await this.createSubscribersFromPayload(result.project.id, payload.subscribers);
226
245
  const registryConfig = this.settings.getRegistryConfig();
@@ -324,6 +343,7 @@ let ProjectsService = class ProjectsService {
324
343
  id: prof.id,
325
344
  name: prof.name,
326
345
  provider: { id: provider.id, name: provider.name },
346
+ familySlug: prof.familySlug,
327
347
  options: sanitizeOptionsString(prof.options),
328
348
  instructions: prof.instructions,
329
349
  temperature: prof.temperature,
@@ -452,8 +472,11 @@ let ProjectsService = class ProjectsService {
452
472
  logger.info({ projectId: input.projectId, dryRun: input.dryRun }, 'importProject');
453
473
  const isDryRun = input.dryRun ?? false;
454
474
  const payload = shared_1.ExportSchema.parse(input.payload ?? {});
475
+ const familyResult = await this.computeFamilyAlternatives(payload.profiles, payload.agents);
476
+ const needsMapping = familyResult.alternatives.some((alt) => !alt.defaultProviderAvailable);
455
477
  const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
456
478
  const { available, missing: missingProviders } = await this.resolveProviders(providerNames);
479
+ const selectedProfilesByFamily = this.selectProfilesForFamilies(payload.profiles, payload.agents, input.familyProviderMappings, available);
457
480
  const [existingPrompts, existingProfiles, existingAgents, existingStatuses, existingWatchers, existingSubscribers,] = await Promise.all([
458
481
  this.storage.listPrompts({ projectId: input.projectId, limit: 10000, offset: 0 }),
459
482
  this.storage.listAgentProfiles({ projectId: input.projectId, limit: 10000, offset: 0 }),
@@ -479,7 +502,7 @@ let ProjectsService = class ProjectsService {
479
502
  }
480
503
  }
481
504
  if (isDryRun) {
482
- return {
505
+ const dryRunResponse = {
483
506
  dryRun: true,
484
507
  missingProviders,
485
508
  unmatchedStatuses,
@@ -490,7 +513,7 @@ let ProjectsService = class ProjectsService {
490
513
  counts: {
491
514
  toImport: {
492
515
  prompts: payload.prompts.length,
493
- profiles: payload.profiles.length,
516
+ profiles: selectedProfilesByFamily.profilesToCreate.length,
494
517
  agents: payload.agents.length,
495
518
  statuses: payload.statuses.length,
496
519
  watchers: payload.watchers.length,
@@ -506,10 +529,37 @@ let ProjectsService = class ProjectsService {
506
529
  },
507
530
  },
508
531
  };
532
+ if (needsMapping && !input.familyProviderMappings) {
533
+ dryRunResponse.providerMappingRequired = {
534
+ missingProviders: familyResult.missingProviders,
535
+ familyAlternatives: familyResult.alternatives,
536
+ canImport: familyResult.canImport,
537
+ };
538
+ }
539
+ return dryRunResponse;
540
+ }
541
+ if (needsMapping && !input.familyProviderMappings) {
542
+ return {
543
+ success: false,
544
+ providerMappingRequired: {
545
+ missingProviders: familyResult.missingProviders,
546
+ familyAlternatives: familyResult.alternatives,
547
+ canImport: familyResult.canImport,
548
+ },
549
+ };
509
550
  }
510
- if (missingProviders.length > 0) {
551
+ if (!familyResult.canImport) {
552
+ throw new error_types_1.ValidationError('Cannot import: some profile families have no available providers', {
553
+ hint: 'Install the required providers or use a different template',
554
+ missingProviders: familyResult.missingProviders,
555
+ familyAlternatives: familyResult.alternatives,
556
+ });
557
+ }
558
+ const selectedProviderNames = new Set(selectedProfilesByFamily.profilesToCreate.map((p) => p.provider.name.trim().toLowerCase()));
559
+ const unavailableSelectedProviders = Array.from(selectedProviderNames).filter((name) => !available.has(name));
560
+ if (unavailableSelectedProviders.length > 0) {
511
561
  throw new error_types_1.ValidationError('Import aborted: missing providers', {
512
- missingProviders,
562
+ missingProviders: unavailableSelectedProviders,
513
563
  hint: 'Install/configure providers by name before importing profiles.',
514
564
  });
515
565
  }
@@ -615,7 +665,7 @@ let ProjectsService = class ProjectsService {
615
665
  promptIdMap[p.id] = created.id;
616
666
  createdPrompts.push({ id: created.id, title: created.title });
617
667
  }
618
- for (const prof of payload.profiles) {
668
+ for (const prof of selectedProfilesByFamily.profilesToCreate) {
619
669
  const providerId = available.get(prof.provider.name.trim().toLowerCase());
620
670
  if (!providerId) {
621
671
  throw new error_types_1.NotFoundError('Provider', prof.provider.name);
@@ -624,6 +674,7 @@ let ProjectsService = class ProjectsService {
624
674
  projectId: input.projectId,
625
675
  name: prof.name,
626
676
  providerId,
677
+ familySlug: prof.familySlug ?? null,
627
678
  options: this.normalizeProfileOptions(prof.options),
628
679
  systemPrompt: null,
629
680
  instructions: prof.instructions ?? null,
@@ -634,7 +685,8 @@ let ProjectsService = class ProjectsService {
634
685
  profileIdMap[profKey] = created.id;
635
686
  }
636
687
  for (const a of payload.agents) {
637
- const oldProfileId = a.profileId ?? '';
688
+ const remappedProfileId = selectedProfilesByFamily.agentProfileMap.get(a.id ?? '');
689
+ const oldProfileId = remappedProfileId ?? a.profileId ?? '';
638
690
  const newProfileId = oldProfileId && profileIdMap[oldProfileId] ? profileIdMap[oldProfileId] : undefined;
639
691
  if (!newProfileId) {
640
692
  throw new error_types_1.ValidationError(`Profile mapping missing for agent ${a.name}`, {
@@ -655,7 +707,7 @@ let ProjectsService = class ProjectsService {
655
707
  ...a,
656
708
  id: a.id || `name:${a.name.trim().toLowerCase()}`,
657
709
  })),
658
- profiles: payload.profiles.map((p) => ({
710
+ profiles: selectedProfilesByFamily.profilesToCreate.map((p) => ({
659
711
  ...p,
660
712
  id: p.id || `name:${p.name.trim().toLowerCase()}`,
661
713
  })),
@@ -665,6 +717,7 @@ let ProjectsService = class ProjectsService {
665
717
  agentNameToId: agentNameToNewId,
666
718
  profileNameToId: profileNameToNewId,
667
719
  providerNameToId: available,
720
+ profileNameRemapMap: selectedProfilesByFamily.profileNameRemapMap,
668
721
  });
669
722
  const { subscriberIdMap } = await this.createSubscribersFromPayload(input.projectId, payload.subscribers);
670
723
  logger.info({
@@ -723,6 +776,24 @@ let ProjectsService = class ProjectsService {
723
776
  const archiveStatusId = templateLabelToStatusId.get('archive') ?? null;
724
777
  const settingsResult = await this.applyProjectSettings(input.projectId, mergedSettings, { promptTitleToId, statusLabelToId: templateLabelToStatusId }, archiveStatusId);
725
778
  const initialPromptSet = settingsResult.initialPromptSet;
779
+ if (payload._manifest?.slug) {
780
+ let templateSource = 'registry';
781
+ try {
782
+ this.unifiedTemplateService.getBundledTemplate(payload._manifest.slug);
783
+ templateSource = 'bundled';
784
+ }
785
+ catch {
786
+ templateSource = 'registry';
787
+ }
788
+ await this.settings.setProjectTemplateMetadata(input.projectId, {
789
+ templateSlug: payload._manifest.slug,
790
+ source: templateSource,
791
+ installedVersion: payload._manifest.version ?? null,
792
+ registryUrl: null,
793
+ installedAt: new Date().toISOString(),
794
+ });
795
+ logger.info({ projectId: input.projectId, slug: payload._manifest.slug, source: templateSource }, 'Updated template metadata after import');
796
+ }
726
797
  return {
727
798
  success: true,
728
799
  mode: 'replace',
@@ -778,6 +849,181 @@ let ProjectsService = class ProjectsService {
778
849
  const missing = Array.from(providerNames).filter((n) => !available.has(n));
779
850
  return { available, missing };
780
851
  }
852
+ async computeFamilyAlternatives(templateProfiles, templateAgents) {
853
+ const localProviders = await this.storage.listProviders();
854
+ const availableProviderNames = new Set(localProviders.items.map((p) => p.name.trim().toLowerCase()));
855
+ const profileById = new Map();
856
+ for (const prof of templateProfiles) {
857
+ if (prof.id) {
858
+ profileById.set(prof.id, prof);
859
+ }
860
+ }
861
+ const usedFamilySlugs = new Set();
862
+ for (const agent of templateAgents) {
863
+ if (agent.profileId) {
864
+ const profile = profileById.get(agent.profileId);
865
+ if (profile?.familySlug) {
866
+ usedFamilySlugs.add(profile.familySlug);
867
+ }
868
+ }
869
+ }
870
+ const familyProviders = new Map();
871
+ for (const prof of templateProfiles) {
872
+ const familySlug = prof.familySlug;
873
+ if (!familySlug)
874
+ continue;
875
+ if (!familyProviders.has(familySlug)) {
876
+ familyProviders.set(familySlug, new Map());
877
+ }
878
+ const providerName = prof.provider.name.trim().toLowerCase();
879
+ const familyMap = familyProviders.get(familySlug);
880
+ if (!familyMap.has(providerName)) {
881
+ familyMap.set(providerName, []);
882
+ }
883
+ familyMap.get(providerName).push(prof.name);
884
+ }
885
+ const alternatives = [];
886
+ const allMissingProviders = new Set();
887
+ let canImport = true;
888
+ for (const familySlug of usedFamilySlugs) {
889
+ const providersForFamily = familyProviders.get(familySlug);
890
+ if (!providersForFamily || providersForFamily.size === 0) {
891
+ logger.warn({ familySlug }, 'Family used by agent has no profiles');
892
+ continue;
893
+ }
894
+ const providerNamesForFamily = Array.from(providersForFamily.keys());
895
+ const defaultProvider = providerNamesForFamily[0];
896
+ const defaultProviderAvailable = availableProviderNames.has(defaultProvider);
897
+ const availableForFamily = providerNamesForFamily.filter((name) => availableProviderNames.has(name));
898
+ for (const provName of providerNamesForFamily) {
899
+ if (!availableProviderNames.has(provName)) {
900
+ allMissingProviders.add(provName);
901
+ }
902
+ }
903
+ const hasAlternatives = availableForFamily.length > 0;
904
+ if (!hasAlternatives) {
905
+ canImport = false;
906
+ }
907
+ alternatives.push({
908
+ familySlug,
909
+ defaultProvider,
910
+ defaultProviderAvailable,
911
+ availableProviders: availableForFamily.sort(),
912
+ hasAlternatives,
913
+ });
914
+ }
915
+ return {
916
+ alternatives,
917
+ missingProviders: Array.from(allMissingProviders).sort(),
918
+ canImport,
919
+ };
920
+ }
921
+ selectProfilesForFamilies(templateProfiles, templateAgents, familyProviderMappings, availableProviders) {
922
+ const profileById = new Map();
923
+ for (const prof of templateProfiles) {
924
+ if (prof.id) {
925
+ profileById.set(prof.id, prof);
926
+ }
927
+ }
928
+ const profilesByFamilyAndProvider = new Map();
929
+ for (const prof of templateProfiles) {
930
+ if (!prof.familySlug)
931
+ continue;
932
+ const family = prof.familySlug;
933
+ const providerName = prof.provider.name.trim().toLowerCase();
934
+ if (!profilesByFamilyAndProvider.has(family)) {
935
+ profilesByFamilyAndProvider.set(family, new Map());
936
+ }
937
+ const familyMap = profilesByFamilyAndProvider.get(family);
938
+ if (!familyMap.has(providerName)) {
939
+ familyMap.set(providerName, prof);
940
+ }
941
+ }
942
+ const familyOriginalProviders = new Map();
943
+ for (const agent of templateAgents) {
944
+ if (!agent.profileId)
945
+ continue;
946
+ const profile = profileById.get(agent.profileId);
947
+ if (!profile?.familySlug)
948
+ continue;
949
+ const providerName = profile.provider.name.trim().toLowerCase();
950
+ familyOriginalProviders.set(profile.familySlug, providerName);
951
+ }
952
+ const selectedProfileIdsByFamily = new Map();
953
+ for (const [familySlug, providerMap] of profilesByFamilyAndProvider) {
954
+ let selectedProvider;
955
+ if (familyProviderMappings?.[familySlug]) {
956
+ selectedProvider = familyProviderMappings[familySlug].trim().toLowerCase();
957
+ }
958
+ else {
959
+ const originalProvider = familyOriginalProviders.get(familySlug);
960
+ if (originalProvider &&
961
+ availableProviders.has(originalProvider) &&
962
+ providerMap.has(originalProvider)) {
963
+ selectedProvider = originalProvider;
964
+ }
965
+ else {
966
+ for (const provName of providerMap.keys()) {
967
+ if (availableProviders.has(provName)) {
968
+ selectedProvider = provName;
969
+ break;
970
+ }
971
+ }
972
+ }
973
+ }
974
+ if (selectedProvider && providerMap.has(selectedProvider)) {
975
+ const profile = providerMap.get(selectedProvider);
976
+ if (profile.id) {
977
+ selectedProfileIdsByFamily.set(familySlug, profile.id);
978
+ }
979
+ }
980
+ }
981
+ const profilesToCreate = [];
982
+ const usedProfileIds = new Set();
983
+ for (const prof of templateProfiles) {
984
+ if (!prof.id || usedProfileIds.has(prof.id))
985
+ continue;
986
+ const providerName = prof.provider.name.trim().toLowerCase();
987
+ if (availableProviders.has(providerName)) {
988
+ usedProfileIds.add(prof.id);
989
+ profilesToCreate.push(prof);
990
+ }
991
+ }
992
+ const agentProfileMap = new Map();
993
+ for (const agent of templateAgents) {
994
+ if (!agent.id || !agent.profileId)
995
+ continue;
996
+ const originalProfile = profileById.get(agent.profileId);
997
+ if (!originalProfile) {
998
+ agentProfileMap.set(agent.id, agent.profileId);
999
+ continue;
1000
+ }
1001
+ if (originalProfile.familySlug) {
1002
+ const selectedProfileId = selectedProfileIdsByFamily.get(originalProfile.familySlug);
1003
+ agentProfileMap.set(agent.id, selectedProfileId ?? agent.profileId);
1004
+ }
1005
+ else {
1006
+ agentProfileMap.set(agent.id, agent.profileId);
1007
+ }
1008
+ }
1009
+ const profileNameRemapMap = new Map();
1010
+ for (const [familySlug, providerMap] of profilesByFamilyAndProvider) {
1011
+ const selectedProfileId = selectedProfileIdsByFamily.get(familySlug);
1012
+ const selectedProfile = selectedProfileId
1013
+ ? templateProfiles.find((p) => p.id === selectedProfileId)
1014
+ : undefined;
1015
+ if (selectedProfile) {
1016
+ const selectedNameLower = selectedProfile.name.trim().toLowerCase();
1017
+ for (const profile of providerMap.values()) {
1018
+ const profileNameLower = profile.name.trim().toLowerCase();
1019
+ if (profileNameLower !== selectedNameLower) {
1020
+ profileNameRemapMap.set(profileNameLower, selectedNameLower);
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ return { profilesToCreate, agentProfileMap, profileNameRemapMap };
1026
+ }
781
1027
  buildNameToIdMaps(payload, mappings) {
782
1028
  const agentNameToId = new Map();
783
1029
  for (const a of payload.agents) {
@@ -823,6 +1069,20 @@ let ProjectsService = class ProjectsService {
823
1069
  }
824
1070
  case 'profile': {
825
1071
  scopeFilterId = maps.profileNameToId.get(scopeFilterNameLower) ?? null;
1072
+ if (!scopeFilterId && maps.profileNameRemapMap) {
1073
+ const remappedName = maps.profileNameRemapMap.get(scopeFilterNameLower);
1074
+ if (remappedName) {
1075
+ scopeFilterId = maps.profileNameToId.get(remappedName) ?? null;
1076
+ if (scopeFilterId) {
1077
+ logger.info({
1078
+ projectId,
1079
+ watcherName: w.name,
1080
+ originalProfile: w.scopeFilterName,
1081
+ remappedProfile: remappedName,
1082
+ }, 'Watcher profile scope remapped due to provider family selection');
1083
+ }
1084
+ }
1085
+ }
826
1086
  break;
827
1087
  }
828
1088
  case 'provider': {
@@ -999,6 +1259,36 @@ let ProjectsService = class ProjectsService {
999
1259
  .replace(/[^a-z0-9]+/g, '-')
1000
1260
  .replace(/(^-|-$)/g, '');
1001
1261
  }
1262
+ async getTemplateManifestForProject(projectId) {
1263
+ const metadata = this.settings.getProjectTemplateMetadata(projectId);
1264
+ if (!metadata?.templateSlug) {
1265
+ logger.debug({ projectId }, 'No template metadata for project');
1266
+ return null;
1267
+ }
1268
+ try {
1269
+ if (metadata.source === 'bundled') {
1270
+ const template = this.unifiedTemplateService.getBundledTemplate(metadata.templateSlug);
1271
+ return template.content._manifest ?? null;
1272
+ }
1273
+ else {
1274
+ const template = await this.unifiedTemplateService.getTemplate(metadata.templateSlug, metadata.installedVersion ?? undefined);
1275
+ if (template.source !== 'registry') {
1276
+ logger.debug({
1277
+ projectId,
1278
+ templateSlug: metadata.templateSlug,
1279
+ expectedSource: 'registry',
1280
+ actualSource: template.source,
1281
+ }, 'Template source mismatch - registry template not available, rejecting bundled fallback');
1282
+ return null;
1283
+ }
1284
+ return template.content._manifest ?? null;
1285
+ }
1286
+ }
1287
+ catch (error) {
1288
+ logger.debug({ projectId, templateSlug: metadata.templateSlug, error }, 'Failed to fetch template manifest for project');
1289
+ return null;
1290
+ }
1291
+ }
1002
1292
  };
1003
1293
  exports.ProjectsService = ProjectsService;
1004
1294
  exports.ProjectsService = ProjectsService = __decorate([