@aaronsb/kg-cli 0.9.4 → 0.9.5

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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
- * Embedding Commands (ADR-039)
4
- * Manages embedding model configuration
3
+ * Embedding Commands (ADR-039 + Migration 055)
4
+ * Manages embedding profile configuration (text + image model slots)
5
5
  */
6
6
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
7
  if (k2 === undefined) k2 = k;
@@ -42,59 +42,183 @@ const commander_1 = require("commander");
42
42
  const colors = __importStar(require("../colors"));
43
43
  const colors_1 = require("../colors");
44
44
  const utils_1 = require("./utils");
45
+ const fs = __importStar(require("fs"));
45
46
  /**
46
- * Create a new embedding configuration
47
+ * Create a new embedding profile
47
48
  */
48
49
  function createEmbeddingCreateCommand(client) {
49
50
  return new commander_1.Command('create')
50
- .description('Create a new embedding configuration (inactive)')
51
- .option('--provider <provider>', 'Provider: local or openai')
52
- .option('--model <model>', 'Model name')
53
- .option('--dimensions <dims>', 'Embedding dimensions', parseInt)
54
- .option('--precision <precision>', 'Precision: float16, float32, int8')
51
+ .description('Create a new embedding profile (inactive)')
52
+ .option('--name <name>', 'Profile name')
53
+ .option('--vector-space <tag>', 'Vector space compatibility tag')
54
+ .option('--multimodal', 'Text model handles both text and image', false)
55
+ .option('--provider <provider>', 'Text provider: local or openai (shorthand for --text-provider)')
56
+ .option('--model <model>', 'Text model name (shorthand for --text-model)')
57
+ .option('--dimensions <dims>', 'Text embedding dimensions (shorthand for --text-dimensions)', parseInt)
58
+ .option('--precision <precision>', 'Text precision: float16 or float32')
59
+ .option('--text-provider <provider>', 'Text provider: local or openai')
60
+ .option('--text-model <model>', 'Text model name')
61
+ .option('--text-dimensions <dims>', 'Text embedding dimensions', parseInt)
62
+ .option('--text-loader <loader>', 'Text loader: sentence-transformers, transformers, api')
63
+ .option('--text-revision <rev>', 'Text model revision/commit hash')
64
+ .option('--text-trust-remote-code', 'Trust remote code for text model', false)
65
+ .option('--image-provider <provider>', 'Image provider')
66
+ .option('--image-model <model>', 'Image model name')
67
+ .option('--image-dimensions <dims>', 'Image embedding dimensions', parseInt)
68
+ .option('--image-loader <loader>', 'Image loader: sentence-transformers, transformers, api')
69
+ .option('--image-revision <rev>', 'Image model revision/commit hash')
70
+ .option('--image-trust-remote-code', 'Trust remote code for image model', false)
71
+ .option('--text-query-prefix <prefix>', 'Text prefix for search queries (e.g. "search_query: ")')
72
+ .option('--text-document-prefix <prefix>', 'Text prefix for document ingestion (e.g. "search_document: ")')
55
73
  .option('--device <device>', 'Device: cpu, cuda, mps')
56
74
  .option('--memory <mb>', 'Max memory in MB', parseInt)
57
75
  .option('--threads <n>', 'Number of threads', parseInt)
58
76
  .option('--batch-size <n>', 'Batch size', parseInt)
77
+ .option('--from-json <file>', 'Import profile from JSON file')
59
78
  .action(async (options) => {
60
79
  try {
61
80
  console.log('\n' + (0, colors_1.separator)());
62
- console.log(colors.ui.title('Create Embedding Configuration'));
81
+ console.log(colors.ui.title('Create Embedding Profile'));
63
82
  console.log((0, colors_1.separator)());
64
- const config = {};
65
- if (options.provider)
66
- config.provider = options.provider;
67
- if (options.model)
68
- config.model_name = options.model;
69
- if (options.dimensions)
70
- config.embedding_dimensions = options.dimensions;
71
- if (options.precision)
72
- config.precision = options.precision;
73
- if (options.device)
74
- config.device = options.device;
75
- if (options.memory)
76
- config.max_memory_mb = options.memory;
77
- if (options.threads)
78
- config.num_threads = options.threads;
79
- if (options.batchSize)
80
- config.batch_size = options.batchSize;
83
+ let config = {};
84
+ // Handle JSON import
85
+ if (options.fromJson) {
86
+ const jsonContent = fs.readFileSync(options.fromJson, 'utf-8');
87
+ const imported = JSON.parse(jsonContent);
88
+ // Accept either { profile: { ... } } or raw profile object
89
+ const profile = imported.profile || imported;
90
+ config.name = profile.name;
91
+ config.vector_space = profile.vector_space;
92
+ config.multimodal = profile.multimodal || false;
93
+ // Text slot
94
+ if (profile.text) {
95
+ config.text_provider = profile.text.provider;
96
+ config.text_model_name = profile.text.model_name;
97
+ config.text_loader = profile.text.loader;
98
+ config.text_revision = profile.text.revision;
99
+ config.text_dimensions = profile.text.dimensions;
100
+ config.text_precision = profile.text.precision;
101
+ config.text_trust_remote_code = profile.text.trust_remote_code;
102
+ config.text_query_prefix = profile.text.query_prefix;
103
+ config.text_document_prefix = profile.text.document_prefix;
104
+ }
105
+ // Image slot
106
+ if (profile.image) {
107
+ config.image_provider = profile.image.provider;
108
+ config.image_model_name = profile.image.model_name;
109
+ config.image_loader = profile.image.loader;
110
+ config.image_revision = profile.image.revision;
111
+ config.image_dimensions = profile.image.dimensions;
112
+ config.image_precision = profile.image.precision;
113
+ config.image_trust_remote_code = profile.image.trust_remote_code;
114
+ }
115
+ // Resources
116
+ if (profile.resources) {
117
+ config.device = profile.resources.device;
118
+ config.max_memory_mb = profile.resources.max_memory_mb;
119
+ config.num_threads = profile.resources.num_threads;
120
+ config.batch_size = profile.resources.batch_size;
121
+ config.max_seq_length = profile.resources.max_seq_length;
122
+ config.normalize_embeddings = profile.resources.normalize_embeddings;
123
+ }
124
+ console.log(colors.status.info(`\nImporting from: ${options.fromJson}`));
125
+ }
126
+ else {
127
+ // Build config from CLI flags
128
+ if (options.name)
129
+ config.name = options.name;
130
+ if (options.vectorSpace)
131
+ config.vector_space = options.vectorSpace;
132
+ if (options.multimodal)
133
+ config.multimodal = true;
134
+ // Text slot (explicit or shorthand)
135
+ if (options.textProvider)
136
+ config.text_provider = options.textProvider;
137
+ else if (options.provider)
138
+ config.provider = options.provider;
139
+ if (options.textModel)
140
+ config.text_model_name = options.textModel;
141
+ else if (options.model)
142
+ config.model_name = options.model;
143
+ if (options.textDimensions)
144
+ config.text_dimensions = options.textDimensions;
145
+ else if (options.dimensions)
146
+ config.embedding_dimensions = options.dimensions;
147
+ if (options.textLoader)
148
+ config.text_loader = options.textLoader;
149
+ if (options.textRevision)
150
+ config.text_revision = options.textRevision;
151
+ if (options.textTrustRemoteCode)
152
+ config.text_trust_remote_code = true;
153
+ if (options.textQueryPrefix)
154
+ config.text_query_prefix = options.textQueryPrefix;
155
+ if (options.textDocumentPrefix)
156
+ config.text_document_prefix = options.textDocumentPrefix;
157
+ if (options.precision)
158
+ config.text_precision = options.precision;
159
+ // Image slot
160
+ if (options.imageProvider)
161
+ config.image_provider = options.imageProvider;
162
+ if (options.imageModel)
163
+ config.image_model_name = options.imageModel;
164
+ if (options.imageDimensions)
165
+ config.image_dimensions = options.imageDimensions;
166
+ if (options.imageLoader)
167
+ config.image_loader = options.imageLoader;
168
+ if (options.imageRevision)
169
+ config.image_revision = options.imageRevision;
170
+ if (options.imageTrustRemoteCode)
171
+ config.image_trust_remote_code = true;
172
+ // Resources
173
+ if (options.device)
174
+ config.device = options.device;
175
+ if (options.memory)
176
+ config.max_memory_mb = options.memory;
177
+ if (options.threads)
178
+ config.num_threads = options.threads;
179
+ if (options.batchSize)
180
+ config.batch_size = options.batchSize;
181
+ }
182
+ // Remove undefined values
183
+ config = Object.fromEntries(Object.entries(config).filter(([_, v]) => v !== undefined && v !== null));
81
184
  if (Object.keys(config).length === 0) {
82
- console.error(colors.status.error('\nNo configuration options provided'));
185
+ console.error(colors.status.error('\n No configuration options provided'));
83
186
  console.log(colors.status.dim(' Use --help to see available options\n'));
84
187
  process.exit(1);
85
188
  }
86
189
  const result = await client.updateEmbeddingConfig(config);
87
- console.log('\n' + colors.status.success(' Configuration created successfully'));
88
- console.log(`\n ${colors.ui.key('Config ID:')} ${colors.ui.value(result.config_id)}`);
190
+ console.log('\n' + colors.status.success(' Profile created successfully'));
191
+ console.log(`\n ${colors.ui.key('Profile ID:')} ${colors.ui.value(result.config_id)}`);
89
192
  console.log(` ${colors.ui.key('Status:')} ${colors.status.dim('Inactive')}`);
90
- console.log('\n' + colors.status.warning('⚠️ Next steps:'));
193
+ console.log('\n' + colors.status.warning(' Next steps:'));
91
194
  console.log(colors.status.dim(` 1. Review: kg admin embedding list`));
92
195
  console.log(colors.status.dim(` 2. Activate: kg admin embedding activate ${result.config_id}`));
93
196
  console.log(colors.status.dim(` 3. Apply: kg admin embedding reload\n`));
94
197
  console.log((0, colors_1.separator)() + '\n');
95
198
  }
96
199
  catch (error) {
97
- console.error(colors.status.error('Failed to create embedding configuration'));
200
+ console.error(colors.status.error(' Failed to create embedding profile'));
201
+ console.error(colors.status.error(error.response?.data?.detail || error.message));
202
+ process.exit(1);
203
+ }
204
+ });
205
+ }
206
+ /**
207
+ * Export an embedding profile as JSON
208
+ */
209
+ function createEmbeddingExportCommand(client) {
210
+ return new commander_1.Command('export')
211
+ .description('Export an embedding profile as JSON')
212
+ .argument('<profile-id>', 'Profile ID', parseInt)
213
+ .option('--profile-only', 'Strip metadata (id, timestamps)', false)
214
+ .action(async (profileId, options) => {
215
+ try {
216
+ const result = await client.exportEmbeddingProfile(profileId, options.profileOnly);
217
+ // Output as pretty-printed JSON to stdout
218
+ console.log(JSON.stringify(result, null, 2));
219
+ }
220
+ catch (error) {
221
+ console.error(colors.status.error(' Failed to export profile'));
98
222
  console.error(colors.status.error(error.response?.data?.detail || error.message));
99
223
  process.exit(1);
100
224
  }
@@ -109,11 +233,11 @@ function createEmbeddingReloadCommand(client) {
109
233
  .action(async () => {
110
234
  try {
111
235
  console.log('\n' + (0, colors_1.separator)());
112
- console.log(colors.ui.title('🔄 Hot Reload Embedding Model'));
236
+ console.log(colors.ui.title('Hot Reload Embedding Model'));
113
237
  console.log((0, colors_1.separator)());
114
- console.log(colors.status.info('\nReloading model from database configuration...'));
238
+ console.log(colors.status.info('\nReloading model from active profile...'));
115
239
  const result = await client.reloadEmbeddingModel();
116
- console.log('\n' + colors.status.success('Hot reload successful'));
240
+ console.log('\n' + colors.status.success(' Hot reload successful'));
117
241
  console.log(`\n ${colors.ui.key('Provider:')} ${colors.ui.value(result.provider)}`);
118
242
  if (result.model) {
119
243
  console.log(` ${colors.ui.key('Model:')} ${colors.ui.value(result.model)}`);
@@ -125,41 +249,60 @@ function createEmbeddingReloadCommand(client) {
125
249
  console.log('\n' + (0, colors_1.separator)() + '\n');
126
250
  }
127
251
  catch (error) {
128
- console.error(colors.status.error('Hot reload failed'));
252
+ console.error(colors.status.error(' Hot reload failed'));
129
253
  console.error(colors.status.error(error.response?.data?.detail || error.message));
130
254
  process.exit(1);
131
255
  }
132
256
  });
133
257
  }
134
258
  /**
135
- * List all embedding configurations
259
+ * List all embedding profiles
136
260
  */
137
261
  function createEmbeddingListCommand(client) {
138
262
  return new commander_1.Command('list')
139
- .description('List all embedding configurations')
263
+ .description('List all embedding profiles')
140
264
  .action(async () => {
141
265
  try {
142
266
  console.log('\n' + (0, colors_1.separator)());
143
- console.log(colors.ui.title('📋 Embedding Configurations'));
267
+ console.log(colors.ui.title('Embedding Profiles'));
144
268
  console.log((0, colors_1.separator)());
145
269
  const configs = await client.listEmbeddingConfigs();
146
270
  if (configs.length === 0) {
147
- console.log(colors.status.dim('\n No configurations found\n'));
271
+ console.log(colors.status.dim('\n No profiles found\n'));
148
272
  }
149
273
  else {
150
274
  console.log('');
151
275
  for (const config of configs) {
152
- const activeMarker = config.active ? colors.status.success('ACTIVE') : colors.status.dim('Inactive');
153
- const deleteProtected = config.delete_protected ? '🔒' : '';
154
- const changeProtected = config.change_protected ? '🔐' : '';
155
- const protection = [deleteProtected, changeProtected].filter(p => p).join(' ');
156
- console.log(` ${activeMarker} ${colors.ui.header(`Config ${config.id}`)} ${protection}`);
157
- console.log(` ${colors.ui.key('Provider:')} ${colors.ui.value(config.provider)}`);
158
- if (config.model_name) {
159
- console.log(` ${colors.ui.key('Model:')} ${colors.ui.value(config.model_name)}`);
276
+ const activeMarker = config.active ? colors.status.success('ACTIVE') : colors.status.dim('Inactive');
277
+ const deleteProtected = config.delete_protected ? 'D' : '';
278
+ const changeProtected = config.change_protected ? 'C' : '';
279
+ const protection = [deleteProtected, changeProtected].filter(p => p).join('');
280
+ const protectionStr = protection ? ` [${protection}]` : '';
281
+ const name = config.name || `Profile ${config.id}`;
282
+ const multimodalTag = config.multimodal ? colors.status.info(' [multimodal]') : '';
283
+ console.log(` ${activeMarker} ${colors.ui.header(name)}${multimodalTag}${protectionStr}`);
284
+ console.log(` ${colors.ui.key('ID:')} ${colors.ui.value(config.id)}`);
285
+ console.log(` ${colors.ui.key('Vector Space:')} ${colors.ui.value(config.vector_space || 'unset')}`);
286
+ // Text slot
287
+ console.log(` ${colors.ui.key('Text:')} ${colors.ui.value(config.text_provider || config.provider)} / ${colors.ui.value(config.text_model_name || config.model_name || 'N/A')}`);
288
+ if (config.text_dimensions || config.embedding_dimensions) {
289
+ console.log(` ${colors.ui.key('Dims:')} ${colors.ui.value((config.text_dimensions || config.embedding_dimensions).toString())} ${colors.ui.key('Loader:')} ${colors.ui.value(config.text_loader || 'auto')}`);
290
+ }
291
+ if (config.text_query_prefix || config.text_document_prefix) {
292
+ console.log(` ${colors.ui.key('Prefixes:')} query=${colors.ui.value(JSON.stringify(config.text_query_prefix || ''))} doc=${colors.ui.value(JSON.stringify(config.text_document_prefix || ''))}`);
293
+ }
294
+ // Image slot
295
+ if (!config.multimodal && config.image_model_name) {
296
+ console.log(` ${colors.ui.key('Image:')} ${colors.ui.value(config.image_provider)} / ${colors.ui.value(config.image_model_name)}`);
297
+ if (config.image_dimensions) {
298
+ console.log(` ${colors.ui.key('Dims:')} ${colors.ui.value(config.image_dimensions.toString())} ${colors.ui.key('Loader:')} ${colors.ui.value(config.image_loader || 'auto')}`);
299
+ }
160
300
  }
161
- if (config.embedding_dimensions) {
162
- console.log(` ${colors.ui.key('Dimensions:')} ${colors.ui.value(config.embedding_dimensions)}`);
301
+ else if (config.multimodal) {
302
+ console.log(` ${colors.ui.key('Image:')} ${colors.status.dim('(uses text model)')}`);
303
+ }
304
+ else {
305
+ console.log(` ${colors.ui.key('Image:')} ${colors.status.dim('(none)')}`);
163
306
  }
164
307
  if (config.delete_protected || config.change_protected) {
165
308
  const flags = [];
@@ -169,15 +312,14 @@ function createEmbeddingListCommand(client) {
169
312
  flags.push('change-protected');
170
313
  console.log(` ${colors.ui.key('Protection:')} ${colors.status.warning(flags.join(', '))}`);
171
314
  }
172
- console.log(` ${colors.status.dim('Updated: ' + new Date(config.updated_at).toLocaleString())}`);
173
- console.log(` ${colors.status.dim('By: ' + config.updated_by)}`);
315
+ console.log(` ${colors.status.dim('Updated: ' + new Date(config.updated_at).toLocaleString() + ' by ' + (config.updated_by || 'unknown'))}`);
174
316
  console.log('');
175
317
  }
176
318
  }
177
319
  console.log((0, colors_1.separator)() + '\n');
178
320
  }
179
321
  catch (error) {
180
- console.error(colors.status.error('Failed to list embedding configurations'));
322
+ console.error(colors.status.error(' Failed to list embedding profiles'));
181
323
  console.error(colors.status.error(error.response?.data?.detail || error.message));
182
324
  process.exit(1);
183
325
  }
@@ -188,33 +330,33 @@ function createEmbeddingListCommand(client) {
188
330
  */
189
331
  function createEmbeddingProtectCommand(client) {
190
332
  return new commander_1.Command('protect')
191
- .description('Enable protection flags on an embedding configuration')
192
- .argument('<config-id>', 'Configuration ID', parseInt)
333
+ .description('Enable protection flags on an embedding profile')
334
+ .argument('<config-id>', 'Profile ID', parseInt)
193
335
  .option('--delete', 'Enable delete protection')
194
336
  .option('--change', 'Enable change protection')
195
337
  .action(async (configId, options) => {
196
338
  try {
197
339
  console.log('\n' + (0, colors_1.separator)());
198
- console.log(colors.ui.title(`🔒 Protect Config ${configId}`));
340
+ console.log(colors.ui.title(`Protect Profile ${configId}`));
199
341
  console.log((0, colors_1.separator)());
200
342
  if (!options.delete && !options.change) {
201
- console.error(colors.status.error('\nMust specify at least one protection flag'));
343
+ console.error(colors.status.error('\n Must specify at least one protection flag'));
202
344
  console.log(colors.status.dim(' Use --delete and/or --change\n'));
203
345
  process.exit(1);
204
346
  }
205
347
  const result = await client.protectEmbeddingConfig(configId, options.delete ? true : undefined, options.change ? true : undefined);
206
- console.log('\n' + colors.status.success('Protection enabled'));
348
+ console.log('\n' + colors.status.success(' Protection enabled'));
207
349
  const flags = [];
208
350
  if (options.delete)
209
351
  flags.push('delete-protected');
210
352
  if (options.change)
211
353
  flags.push('change-protected');
212
- console.log(`\n ${colors.ui.key('Config ID:')} ${colors.ui.value(configId)}`);
354
+ console.log(`\n ${colors.ui.key('Profile ID:')} ${colors.ui.value(configId)}`);
213
355
  console.log(` ${colors.ui.key('Flags:')} ${colors.status.warning(flags.join(', '))}`);
214
356
  console.log('\n' + (0, colors_1.separator)() + '\n');
215
357
  }
216
358
  catch (error) {
217
- console.error(colors.status.error('Failed to set protection'));
359
+ console.error(colors.status.error(' Failed to set protection'));
218
360
  console.error(colors.status.error(error.response?.data?.detail || error.message));
219
361
  process.exit(1);
220
362
  }
@@ -225,62 +367,62 @@ function createEmbeddingProtectCommand(client) {
225
367
  */
226
368
  function createEmbeddingUnprotectCommand(client) {
227
369
  return new commander_1.Command('unprotect')
228
- .description('Disable protection flags on an embedding configuration')
229
- .argument('<config-id>', 'Configuration ID', parseInt)
370
+ .description('Disable protection flags on an embedding profile')
371
+ .argument('<config-id>', 'Profile ID', parseInt)
230
372
  .option('--delete', 'Disable delete protection')
231
373
  .option('--change', 'Disable change protection')
232
374
  .action(async (configId, options) => {
233
375
  try {
234
376
  console.log('\n' + (0, colors_1.separator)());
235
- console.log(colors.ui.title(`🔓 Unprotect Config ${configId}`));
377
+ console.log(colors.ui.title(`Unprotect Profile ${configId}`));
236
378
  console.log((0, colors_1.separator)());
237
379
  if (!options.delete && !options.change) {
238
- console.error(colors.status.error('\nMust specify at least one protection flag'));
380
+ console.error(colors.status.error('\n Must specify at least one protection flag'));
239
381
  console.log(colors.status.dim(' Use --delete and/or --change\n'));
240
382
  process.exit(1);
241
383
  }
242
384
  const result = await client.protectEmbeddingConfig(configId, options.delete ? false : undefined, options.change ? false : undefined);
243
- console.log('\n' + colors.status.success('Protection disabled'));
385
+ console.log('\n' + colors.status.success(' Protection disabled'));
244
386
  const flags = [];
245
387
  if (options.delete)
246
388
  flags.push('delete-protection');
247
389
  if (options.change)
248
390
  flags.push('change-protection');
249
- console.log(`\n ${colors.ui.key('Config ID:')} ${colors.ui.value(configId)}`);
391
+ console.log(`\n ${colors.ui.key('Profile ID:')} ${colors.ui.value(configId)}`);
250
392
  console.log(` ${colors.ui.key('Removed:')} ${colors.status.dim(flags.join(', '))}`);
251
393
  console.log('\n' + (0, colors_1.separator)() + '\n');
252
394
  }
253
395
  catch (error) {
254
- console.error(colors.status.error('Failed to remove protection'));
396
+ console.error(colors.status.error(' Failed to remove protection'));
255
397
  console.error(colors.status.error(error.response?.data?.detail || error.message));
256
398
  process.exit(1);
257
399
  }
258
400
  });
259
401
  }
260
402
  /**
261
- * Activate an embedding configuration
403
+ * Activate an embedding profile
262
404
  */
263
405
  function createEmbeddingActivateCommand(client) {
264
406
  return new commander_1.Command('activate')
265
- .description('Activate an embedding configuration (with automatic protection)')
266
- .argument('<config-id>', 'Configuration ID', parseInt)
407
+ .description('Activate an embedding profile (with automatic protection)')
408
+ .argument('<config-id>', 'Profile ID', parseInt)
267
409
  .option('--force', 'Force activation even with dimension mismatch (dangerous!)')
268
410
  .action(async (configId, options) => {
269
411
  try {
270
412
  console.log('\n' + (0, colors_1.separator)());
271
- console.log(colors.ui.title(`🔄 Activate Config ${configId}`));
413
+ console.log(colors.ui.title(`Activate Profile ${configId}`));
272
414
  console.log((0, colors_1.separator)());
273
415
  if (options.force) {
274
- console.log(colors.status.warning('\n⚠️ FORCE MODE: Bypassing dimension safety check'));
416
+ console.log(colors.status.warning('\n FORCE MODE: Bypassing dimension safety check'));
275
417
  console.log(colors.status.dim(' This may break vector search if dimensions change!\n'));
276
418
  }
277
- console.log(colors.status.info('\nActivating configuration...'));
278
- console.log(colors.status.dim(' Unlocking current config (change protection)'));
279
- console.log(colors.status.dim(' Switching to new config'));
280
- console.log(colors.status.dim(' Locking new config (delete + change protection)'));
419
+ console.log(colors.status.info('\nActivating profile...'));
420
+ console.log(colors.status.dim(' Unlocking current profile (change protection)'));
421
+ console.log(colors.status.dim(' Switching to new profile'));
422
+ console.log(colors.status.dim(' Locking new profile (delete + change protection)'));
281
423
  const result = await client.activateEmbeddingConfig(configId, options.force);
282
- console.log('\n' + colors.status.success(' Configuration activated successfully'));
283
- console.log(`\n ${colors.ui.key('Config ID:')} ${colors.ui.value(result.config_id)}`);
424
+ console.log('\n' + colors.status.success(' Profile activated successfully'));
425
+ console.log(`\n ${colors.ui.key('Profile ID:')} ${colors.ui.value(result.config_id)}`);
284
426
  console.log(` ${colors.ui.key('Provider:')} ${colors.ui.value(result.provider)}`);
285
427
  if (result.model) {
286
428
  console.log(` ${colors.ui.key('Model:')} ${colors.ui.value(result.model)}`);
@@ -288,12 +430,12 @@ function createEmbeddingActivateCommand(client) {
288
430
  if (result.dimensions) {
289
431
  console.log(` ${colors.ui.key('Dimensions:')} ${colors.ui.value(result.dimensions)}`);
290
432
  }
291
- console.log('\n' + colors.status.warning('⚠️ Next step: Hot reload to apply changes'));
433
+ console.log('\n' + colors.status.warning(' Next step: Hot reload to apply changes'));
292
434
  console.log(colors.status.dim(' Run: kg admin embedding reload\n'));
293
435
  console.log((0, colors_1.separator)() + '\n');
294
436
  }
295
437
  catch (error) {
296
- console.error(colors.status.error('Failed to activate configuration'));
438
+ console.error(colors.status.error(' Failed to activate profile'));
297
439
  console.error(colors.status.error(error.response?.data?.detail || error.message));
298
440
  process.exit(1);
299
441
  }
@@ -304,26 +446,26 @@ function createEmbeddingActivateCommand(client) {
304
446
  */
305
447
  function createEmbeddingDeleteCommand(client) {
306
448
  return new commander_1.Command('delete')
307
- .description('Delete an embedding configuration')
308
- .argument('<config-id>', 'Configuration ID', parseInt)
449
+ .description('Delete an embedding profile')
450
+ .argument('<config-id>', 'Profile ID', parseInt)
309
451
  .action(async (configId) => {
310
452
  try {
311
453
  console.log('\n' + (0, colors_1.separator)());
312
- console.log(colors.ui.title(`🗑️ Delete Config ${configId}`));
454
+ console.log(colors.ui.title(`Delete Profile ${configId}`));
313
455
  console.log((0, colors_1.separator)());
314
456
  // Confirm deletion
315
- const confirm = await (0, utils_1.prompt)(`\nDelete embedding config ${configId}? (yes/no): `);
457
+ const confirm = await (0, utils_1.prompt)(`\nDelete embedding profile ${configId}? (yes/no): `);
316
458
  if (confirm.toLowerCase() !== 'yes') {
317
459
  console.log(colors.status.dim('Cancelled\n'));
318
460
  process.exit(0);
319
461
  }
320
462
  const result = await client.deleteEmbeddingConfig(configId);
321
- console.log('\n' + colors.status.success(' Configuration deleted'));
322
- console.log(`\n ${colors.ui.key('Config ID:')} ${colors.ui.value(configId)}`);
463
+ console.log('\n' + colors.status.success(' Profile deleted'));
464
+ console.log(`\n ${colors.ui.key('Profile ID:')} ${colors.ui.value(configId)}`);
323
465
  console.log('\n' + (0, colors_1.separator)() + '\n');
324
466
  }
325
467
  catch (error) {
326
- console.error(colors.status.error('Failed to delete configuration'));
468
+ console.error(colors.status.error(' Failed to delete profile'));
327
469
  console.error(colors.status.error(error.response?.data?.detail || error.message));
328
470
  process.exit(1);
329
471
  }
@@ -339,7 +481,7 @@ function createEmbeddingStatusCommand(client) {
339
481
  .action(async (options) => {
340
482
  try {
341
483
  console.log((0, colors_1.separator)());
342
- console.log(colors.ui.title('📊 Embedding Coverage Status'));
484
+ console.log(colors.ui.title('Embedding Coverage Status'));
343
485
  if (options.ontology) {
344
486
  console.log(colors.status.dim(` Ontology: ${options.ontology}`));
345
487
  }
@@ -349,52 +491,52 @@ function createEmbeddingStatusCommand(client) {
349
491
  if (status.active_config) {
350
492
  console.log();
351
493
  console.log(colors.ui.header('Active Embedding Configuration:'));
352
- console.log(` ${colors.ui.key('Provider:')} ${colors.ui.value(status.active_config.provider)}`);
353
- console.log(` ${colors.ui.key('Model:')} ${colors.ui.value(status.active_config.model_name)}`);
354
- console.log(` ${colors.ui.key('Dimensions:')} ${colors.ui.value(status.active_config.embedding_dimensions.toString())}`);
494
+ console.log(` ${colors.ui.key('Provider:')} ${colors.ui.value(status.active_config.provider || status.active_config.text_provider)}`);
495
+ console.log(` ${colors.ui.key('Model:')} ${colors.ui.value(status.active_config.model_name || status.active_config.text_model_name)}`);
496
+ console.log(` ${colors.ui.key('Dimensions:')} ${colors.ui.value((status.active_config.embedding_dimensions || status.active_config.text_dimensions).toString())}`);
355
497
  }
356
498
  // Concepts
357
499
  console.log();
358
500
  console.log(colors.ui.header('Concepts (AGE Graph Nodes):'));
359
501
  console.log(` ${colors.ui.key('Total:')} ${colors.ui.value(status.concepts.total.toString())}`);
360
- console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.concepts.with_embeddings.toString())} (${status.concepts.percentage}%)`);
502
+ console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.concepts.with_embeddings.toString())} (${status.concepts.percentage}%)`);
361
503
  if (status.concepts.without_embeddings > 0) {
362
- console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.concepts.without_embeddings.toString())}`);
504
+ console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.concepts.without_embeddings.toString())}`);
363
505
  }
364
506
  if (status.concepts.incompatible_embeddings > 0) {
365
- console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.concepts.incompatible_embeddings.toString())} (model/dimension mismatch)`);
507
+ console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.concepts.incompatible_embeddings.toString())} (model/dimension mismatch)`);
366
508
  }
367
509
  // Sources
368
510
  console.log();
369
511
  console.log(colors.ui.header('Sources (Text Chunks):'));
370
512
  console.log(` ${colors.ui.key('Total:')} ${colors.ui.value(status.sources.total.toString())}`);
371
- console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.sources.with_embeddings.toString())} (${status.sources.percentage}%)`);
513
+ console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.sources.with_embeddings.toString())} (${status.sources.percentage}%)`);
372
514
  if (status.sources.without_embeddings > 0) {
373
- console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.sources.without_embeddings.toString())}`);
515
+ console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.sources.without_embeddings.toString())}`);
374
516
  }
375
517
  if (status.sources.stale_embeddings > 0) {
376
- console.log(` ${colors.status.error('Stale embeddings:')} ${colors.ui.value(status.sources.stale_embeddings.toString())} (hash mismatch - source changed)`);
518
+ console.log(` ${colors.status.error('Stale embeddings:')} ${colors.ui.value(status.sources.stale_embeddings.toString())} (hash mismatch)`);
377
519
  }
378
520
  if (status.sources.incompatible_embeddings > 0) {
379
- console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.sources.incompatible_embeddings.toString())} (model/dimension mismatch)`);
521
+ console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.sources.incompatible_embeddings.toString())} (model/dimension mismatch)`);
380
522
  }
381
523
  // Vocabulary
382
524
  console.log();
383
525
  console.log(colors.ui.header('Vocabulary (Relationship Types):'));
384
526
  console.log(` ${colors.ui.key('Total:')} ${colors.ui.value(status.vocabulary.total.toString())}`);
385
- console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.vocabulary.with_embeddings.toString())} (${status.vocabulary.percentage}%)`);
527
+ console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.vocabulary.with_embeddings.toString())} (${status.vocabulary.percentage}%)`);
386
528
  if (status.vocabulary.without_embeddings > 0) {
387
- console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.vocabulary.without_embeddings.toString())}`);
529
+ console.log(` ${colors.status.warning('Without embeddings:')} ${colors.ui.value(status.vocabulary.without_embeddings.toString())}`);
388
530
  }
389
531
  if (status.vocabulary.incompatible_embeddings > 0) {
390
- console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.vocabulary.incompatible_embeddings.toString())} (model/dimension mismatch)`);
532
+ console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.vocabulary.incompatible_embeddings.toString())} (model/dimension mismatch)`);
391
533
  }
392
- // Images (future)
534
+ // Images
393
535
  if (status.images && status.images.total > 0) {
394
536
  console.log();
395
537
  console.log(colors.ui.header('Images:'));
396
538
  console.log(` ${colors.ui.key('Total:')} ${colors.ui.value(status.images.total.toString())}`);
397
- console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.images.with_embeddings.toString())} (${status.images.percentage}%)`);
539
+ console.log(` ${colors.status.success('With embeddings:')} ${colors.ui.value(status.images.with_embeddings.toString())} (${status.images.percentage}%)`);
398
540
  }
399
541
  else if (status.images && status.images.note) {
400
542
  console.log();
@@ -406,19 +548,19 @@ function createEmbeddingStatusCommand(client) {
406
548
  console.log((0, colors_1.separator)());
407
549
  console.log(colors.ui.header('Overall Summary:'));
408
550
  console.log(` ${colors.ui.key('Total Entities:')} ${colors.ui.value(status.summary.total_entities.toString())}`);
409
- console.log(` ${colors.status.success('With Embeddings:')} ${colors.ui.value(status.summary.total_with_embeddings.toString())} (${status.summary.overall_percentage}%)`);
551
+ console.log(` ${colors.status.success('With Embeddings:')} ${colors.ui.value(status.summary.total_with_embeddings.toString())} (${status.summary.overall_percentage}%)`);
410
552
  if (status.summary.total_without_embeddings > 0) {
411
- console.log(` ${colors.status.warning('Without Embeddings:')} ${colors.ui.value(status.summary.total_without_embeddings.toString())}`);
553
+ console.log(` ${colors.status.warning('Without Embeddings:')} ${colors.ui.value(status.summary.total_without_embeddings.toString())}`);
412
554
  }
413
555
  if (status.summary.total_incompatible > 0) {
414
- console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.summary.total_incompatible.toString())} (requires regeneration)`);
556
+ console.log(` ${colors.status.error('Incompatible:')} ${colors.ui.value(status.summary.total_incompatible.toString())} (requires regeneration)`);
415
557
  }
416
558
  console.log((0, colors_1.separator)());
417
559
  console.log();
418
560
  }
419
561
  catch (error) {
420
562
  console.error();
421
- console.error(colors.status.error('Failed to get embedding status'));
563
+ console.error(colors.status.error(' Failed to get embedding status'));
422
564
  console.error(colors.status.dim(` ${error.message || error}`));
423
565
  console.error();
424
566
  process.exit(1);
@@ -447,7 +589,7 @@ function createEmbeddingRegenerateCommand(client) {
447
589
  // If no --type provided, show help and exit
448
590
  if (!options.type) {
449
591
  console.log();
450
- console.log(colors.status.warning(' No --type specified'));
592
+ console.log(colors.status.warning(' No --type specified'));
451
593
  console.log();
452
594
  console.log(colors.ui.header('Usage:'));
453
595
  console.log(' kg admin embedding regenerate --type <type> [options]');
@@ -474,58 +616,49 @@ function createEmbeddingRegenerateCommand(client) {
474
616
  }
475
617
  // Normal regeneration flow
476
618
  try {
477
- // Validate embedding type
478
619
  const validTypes = ['concept', 'source', 'vocabulary', 'all'];
479
620
  const embeddingType = options.type;
480
621
  if (!validTypes.includes(embeddingType)) {
481
622
  console.error();
482
- console.error(colors.status.error(`✗ Invalid --type: ${embeddingType}`));
623
+ console.error(colors.status.error(` Invalid --type: ${embeddingType}`));
483
624
  console.error(colors.status.dim(` Valid types: ${validTypes.join(', ')}`));
484
625
  console.error();
485
626
  process.exit(1);
486
627
  }
487
- // Validate flag combination
488
628
  if (options.onlyMissing && options.onlyIncompatible) {
489
629
  console.error();
490
- console.error(colors.status.error('Cannot use both --only-missing and --only-incompatible'));
630
+ console.error(colors.status.error(' Cannot use both --only-missing and --only-incompatible'));
491
631
  console.error(colors.status.dim(' Choose one: missing (no embeddings) or incompatible (wrong model/dimensions)'));
492
632
  console.error();
493
633
  process.exit(1);
494
634
  }
495
635
  console.log((0, colors_1.separator)());
496
- console.log(colors.ui.title(`🔄 Regenerating ${embeddingType.charAt(0).toUpperCase() + embeddingType.slice(1)} Embeddings`));
636
+ console.log(colors.ui.title(`Regenerating ${embeddingType.charAt(0).toUpperCase() + embeddingType.slice(1)} Embeddings`));
497
637
  console.log((0, colors_1.separator)());
498
638
  const params = {
499
639
  embedding_type: embeddingType,
500
640
  only_missing: options.onlyMissing || false,
501
641
  only_incompatible: options.onlyIncompatible || false
502
642
  };
503
- if (options.ontology) {
643
+ if (options.ontology)
504
644
  params.ontology = options.ontology;
505
- }
506
- if (options.limit) {
645
+ if (options.limit)
507
646
  params.limit = options.limit;
508
- }
509
647
  console.log();
510
648
  console.log(colors.status.info('Starting regeneration...'));
511
649
  console.log(colors.status.dim(` Type: ${embeddingType}`));
512
- if (options.ontology) {
650
+ if (options.ontology)
513
651
  console.log(colors.status.dim(` Ontology: ${options.ontology}`));
514
- }
515
- if (options.onlyMissing) {
652
+ if (options.onlyMissing)
516
653
  console.log(colors.status.dim(' Mode: Only missing embeddings'));
517
- }
518
- if (options.onlyIncompatible) {
654
+ if (options.onlyIncompatible)
519
655
  console.log(colors.status.dim(' Mode: Only incompatible embeddings (model migration)'));
520
- }
521
- if (options.limit) {
656
+ if (options.limit)
522
657
  console.log(colors.status.dim(` Limit: ${options.limit} entities`));
523
- }
524
658
  console.log();
525
659
  const result = await client.regenerateEmbeddings(params);
526
660
  console.log((0, colors_1.separator)());
527
- console.log(colors.status.success('Regeneration completed'));
528
- // Handle 'all' type response (has totals and per-type results)
661
+ console.log(colors.status.success(' Regeneration completed'));
529
662
  if (embeddingType === 'all' && result.totals) {
530
663
  console.log(` ${colors.stats.label('Total Processed:')} ${colors.stats.value(result.totals.processed_count.toString())} / ${result.totals.target_count}`);
531
664
  if (result.totals.failed_count > 0) {
@@ -545,7 +678,6 @@ function createEmbeddingRegenerateCommand(client) {
545
678
  }
546
679
  }
547
680
  else {
548
- // Single type response
549
681
  console.log(` ${colors.stats.label('Processed:')} ${colors.stats.value(result.processed_count.toString())} / ${result.target_count}`);
550
682
  if (result.failed_count > 0) {
551
683
  console.log(` ${colors.status.error('Failed:')} ${result.failed_count}`);
@@ -570,7 +702,7 @@ function createEmbeddingRegenerateCommand(client) {
570
702
  }
571
703
  catch (error) {
572
704
  console.error();
573
- console.error(colors.status.error('Failed to regenerate embeddings'));
705
+ console.error(colors.status.error(' Failed to regenerate embeddings'));
574
706
  console.error(colors.status.dim(` ${error.message || error}`));
575
707
  console.error();
576
708
  process.exit(1);
@@ -583,9 +715,10 @@ function createEmbeddingRegenerateCommand(client) {
583
715
  */
584
716
  function createEmbeddingCommand(client) {
585
717
  const embeddingCommand = new commander_1.Command('embedding')
586
- .description('Manage embedding model configuration (ADR-039)');
718
+ .description('Manage embedding profiles (text + image model configuration)');
587
719
  embeddingCommand.addCommand(createEmbeddingListCommand(client));
588
720
  embeddingCommand.addCommand(createEmbeddingCreateCommand(client));
721
+ embeddingCommand.addCommand(createEmbeddingExportCommand(client));
589
722
  embeddingCommand.addCommand(createEmbeddingActivateCommand(client));
590
723
  embeddingCommand.addCommand(createEmbeddingReloadCommand(client));
591
724
  embeddingCommand.addCommand(createEmbeddingProtectCommand(client));