@vxnus/siduri 0.0.7 → 0.0.8

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 (35) hide show
  1. package/dist/clean-machine-e2e.test.js +1 -1
  2. package/dist/configurators/behavior.d.ts +2 -0
  3. package/dist/configurators/behavior.js +36 -0
  4. package/dist/configurators/body.d.ts +2 -0
  5. package/dist/configurators/body.js +44 -0
  6. package/dist/configurators/brain.d.ts +6 -0
  7. package/dist/configurators/brain.js +80 -0
  8. package/dist/configurators/ear.d.ts +2 -0
  9. package/dist/configurators/ear.js +28 -0
  10. package/dist/configurators/hands.d.ts +2 -0
  11. package/dist/configurators/hands.js +27 -0
  12. package/dist/configurators/index.d.ts +23 -0
  13. package/dist/configurators/index.js +74 -0
  14. package/dist/configurators/knowledge.d.ts +6 -0
  15. package/dist/configurators/knowledge.js +100 -0
  16. package/dist/configurators/memory.d.ts +2 -0
  17. package/dist/configurators/memory.js +44 -0
  18. package/dist/configurators/observation.d.ts +2 -0
  19. package/dist/configurators/observation.js +12 -0
  20. package/dist/configurators/types.d.ts +12 -0
  21. package/dist/configurators/types.js +2 -0
  22. package/dist/configurators/vision.d.ts +2 -0
  23. package/dist/configurators/vision.js +39 -0
  24. package/dist/configurators/voice.d.ts +2 -0
  25. package/dist/configurators/voice.js +53 -0
  26. package/dist/configurators.test.d.ts +1 -0
  27. package/dist/configurators.test.js +275 -0
  28. package/dist/index.d.ts +16 -0
  29. package/dist/index.js +137 -54
  30. package/dist/providers/knowledge-hub.d.ts +37 -0
  31. package/dist/providers/knowledge-hub.js +141 -0
  32. package/dist/providers/openrouter.d.ts +32 -0
  33. package/dist/providers/openrouter.js +201 -0
  34. package/dist/release-check.js +1 -1
  35. package/package.json +1 -1
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const inquirer_1 = __importDefault(require("inquirer"));
7
+ const discovery_1 = require("./discovery");
8
+ const generator_1 = require("./generator");
9
+ const index_1 = require("./index");
10
+ const openrouter_1 = require("./providers/openrouter");
11
+ const knowledge_hub_1 = require("./providers/knowledge-hub");
12
+ const brain_1 = require("./configurators/brain");
13
+ const knowledge_1 = require("./configurators/knowledge");
14
+ const memory_1 = require("./configurators/memory");
15
+ const voice_1 = require("./configurators/voice");
16
+ const body_1 = require("./configurators/body");
17
+ const hands_1 = require("./configurators/hands");
18
+ const behavior_1 = require("./configurators/behavior");
19
+ const vision_1 = require("./configurators/vision");
20
+ // Mock inquirer.prompt
21
+ jest.mock('inquirer', () => ({
22
+ prompt: jest.fn(),
23
+ Separator: jest.fn((label) => ({ type: 'separator', line: label })),
24
+ }));
25
+ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
26
+ const registry = discovery_1.OrganRegistry.discover();
27
+ const brainManifest = registry.get('brain');
28
+ const knowledgeManifest = registry.get('knowledge');
29
+ const memoryManifest = registry.get('memory');
30
+ const voiceManifest = registry.get('voice');
31
+ const bodyManifest = registry.get('body');
32
+ const handsManifest = registry.get('hands');
33
+ const behaviorManifest = registry.get('behavior');
34
+ const visionManifest = registry.get('vision');
35
+ beforeEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+ describe('Discrepancy #1: OpenRouter Model Discovery & Brain Configurator', () => {
39
+ test('OpenRouter model provider parses model catalog correctly', async () => {
40
+ const mockModels = [
41
+ { id: 'openai/gpt-4o-mini', name: 'OpenAI: GPT-4o Mini', description: 'Fast model', context_length: 128000 },
42
+ { id: 'anthropic/claude-3.5-sonnet', name: 'Anthropic: Claude 3.5 Sonnet', context_length: 200000 },
43
+ ];
44
+ const globalFetch = global.fetch;
45
+ global.fetch = jest.fn().mockResolvedValue({
46
+ ok: true,
47
+ json: async () => ({ data: mockModels }),
48
+ });
49
+ const provider = new openrouter_1.OpenRouterModelProvider('https://mock-api.com/models');
50
+ const models = await provider.listModels();
51
+ expect(models.length).toBe(2);
52
+ expect(models[0].id).toBe('openai/gpt-4o-mini');
53
+ expect(models[0].name).toBe('OpenAI: GPT-4o Mini');
54
+ expect(models[1].id).toBe('anthropic/claude-3.5-sonnet');
55
+ global.fetch = globalFetch;
56
+ });
57
+ test('Brain configurator selects OpenRouter model and retains canonical model ID', async () => {
58
+ const mockProvider = {
59
+ listModels: jest.fn().mockResolvedValue([
60
+ { id: 'openai/gpt-4o-mini', name: 'OpenAI: GPT-4o Mini' },
61
+ { id: 'anthropic/claude-3.5-sonnet', name: 'Anthropic: Claude 3.5 Sonnet' },
62
+ ]),
63
+ };
64
+ inquirer_1.default.prompt
65
+ .mockResolvedValueOnce({ provider: 'openrouter' }) // Brain provider
66
+ .mockResolvedValueOnce({ selectedValue: 'openai/gpt-4o-mini' }); // Select model
67
+ const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: mockProvider });
68
+ expect(result.config.provider).toBe('openrouter');
69
+ expect(result.config.model).toBe('openai/gpt-4o-mini');
70
+ expect(result.summary?.Model).toBe('openai/gpt-4o-mini');
71
+ expect(result.summary?.Provider).toBe('OpenRouter');
72
+ });
73
+ test('Brain configurator handles discovery failure with curated fallback models', async () => {
74
+ const failingProvider = {
75
+ listModels: jest.fn().mockRejectedValue(new Error('Network request failed')),
76
+ };
77
+ inquirer_1.default.prompt
78
+ .mockResolvedValueOnce({ provider: 'openrouter' }) // Brain provider
79
+ .mockResolvedValueOnce({ failureAction: 'curated' }) // Pick curated fallback
80
+ .mockResolvedValueOnce({ selectedValue: 'anthropic/claude-3.5-sonnet' }); // Select model
81
+ const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: failingProvider });
82
+ expect(result.config.provider).toBe('openrouter');
83
+ expect(result.config.model).toBe('anthropic/claude-3.5-sonnet');
84
+ });
85
+ test('Brain configurator handles discovery failure with manual fallback entry', async () => {
86
+ const failingProvider = {
87
+ listModels: jest.fn().mockRejectedValue(new Error('Network request failed')),
88
+ };
89
+ inquirer_1.default.prompt
90
+ .mockResolvedValueOnce({ provider: 'openrouter' })
91
+ .mockResolvedValueOnce({ failureAction: 'manual' })
92
+ .mockResolvedValueOnce({ manualId: 'mistralai/mistral-large' });
93
+ const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: failingProvider });
94
+ expect(result.config.provider).toBe('openrouter');
95
+ expect(result.config.model).toBe('mistralai/mistral-large');
96
+ });
97
+ test('Brain configurator supports OpenAI-compatible / Custom endpoint', async () => {
98
+ inquirer_1.default.prompt
99
+ .mockResolvedValueOnce({ provider: 'openai-compatible' })
100
+ .mockResolvedValueOnce({
101
+ baseUrl: 'http://localhost:11434/v1',
102
+ model: 'llama3:latest',
103
+ apiKeyEnv: 'OLLAMA_API_KEY',
104
+ });
105
+ const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest });
106
+ expect(result.config.provider).toBe('openai-compatible');
107
+ expect(result.config.model).toBe('llama3:latest');
108
+ expect(result.config.baseUrl).toBe('http://localhost:11434/v1');
109
+ expect(result.config.apiKeyEnv).toBe('OLLAMA_API_KEY');
110
+ });
111
+ });
112
+ describe('Discrepancy #2 & #3: Knowledge Choices & Manifest Metadata Confirmation', () => {
113
+ test('Selecting "Do not use knowledge" sets provider: none and skips discovery', async () => {
114
+ inquirer_1.default.prompt.mockResolvedValueOnce({ source: 'none' });
115
+ const result = await (0, knowledge_1.configureKnowledge)({
116
+ companionName: 'Sparkle',
117
+ manifest: knowledgeManifest,
118
+ });
119
+ expect(result.config.provider).toBe('none');
120
+ expect(result.summary?.Source).toBe('Do not use knowledge');
121
+ });
122
+ test('Selecting "E Knowledge Hub" discovers manifest, extracts capabilities, and confirms provider', async () => {
123
+ const mockClient = {
124
+ resolveProvider: jest.fn().mockResolvedValue({
125
+ name: 'e-teyvat',
126
+ displayName: 'E Teyvat',
127
+ package: '@vxnus/e-teyvat',
128
+ version: '1.2.0',
129
+ description: 'Teyvat knowledge provider for Siduri.',
130
+ capabilities: ['Search', 'Retrieval', 'Context injection'],
131
+ source: 'E Knowledge Hub',
132
+ }),
133
+ };
134
+ inquirer_1.default.prompt
135
+ .mockResolvedValueOnce({ source: 'e-hub' }) // Knowledge source
136
+ .mockResolvedValueOnce({ packId: '@vxnus/e-teyvat' }) // Package ID
137
+ .mockResolvedValueOnce({ confirmProvider: 'yes' }); // Confirm provider
138
+ const result = await (0, knowledge_1.configureKnowledge)({ companionName: 'Sparkle', manifest: knowledgeManifest }, { client: mockClient });
139
+ expect(result.config.provider).toBe('e-hub');
140
+ expect(result.config.packId).toBe('@vxnus/e-teyvat');
141
+ expect(result.summary?.Source).toBe('E Knowledge Hub');
142
+ expect(result.summary?.Provider).toBe('E Teyvat');
143
+ expect(result.summary?.Package).toBe('@vxnus/e-teyvat');
144
+ expect(result.summary?.Version).toBe('1.2.0');
145
+ });
146
+ test('Knowledge manifest validation enforces name and version', () => {
147
+ expect(() => (0, knowledge_hub_1.validateKnowledgeManifest)({})).toThrow('Invalid knowledge manifest');
148
+ expect(() => (0, knowledge_hub_1.validateKnowledgeManifest)({ name: 'test' })).toThrow('missing \'version\'');
149
+ const valid = (0, knowledge_hub_1.validateKnowledgeManifest)({
150
+ name: 'e-teyvat',
151
+ displayName: 'E Teyvat',
152
+ version: '1.0.0',
153
+ description: 'Teyvat lore',
154
+ }, '@vxnus/e-teyvat');
155
+ expect(valid.name).toBe('e-teyvat');
156
+ expect(valid.package).toBe('@vxnus/e-teyvat');
157
+ expect(valid.version).toBe('1.0.0');
158
+ });
159
+ test('extractCapabilitiesList formats object and array capabilities correctly', () => {
160
+ const arrayCaps = (0, knowledge_hub_1.extractCapabilitiesList)(['Search', 'Retrieval', 'Custom Tool']);
161
+ expect(arrayCaps).toEqual(['Search', 'Retrieval', 'Custom Tool']);
162
+ const objCaps = (0, knowledge_hub_1.extractCapabilitiesList)({
163
+ search: true,
164
+ retrieval: true,
165
+ contextInjection: true,
166
+ semanticSearch: true,
167
+ });
168
+ expect(objCaps).toContain('Search');
169
+ expect(objCaps).toContain('Retrieval');
170
+ expect(objCaps).toContain('Context injection');
171
+ expect(objCaps).toContain('Semantic search');
172
+ });
173
+ test('Knowledge Hub client fallback returns known pack for @vxnus/e-teyvat', async () => {
174
+ const client = new knowledge_hub_1.KnowledgeHubClient('https://invalid-non-existent-url.local/api');
175
+ const manifest = await client.resolveProvider('@vxnus/e-teyvat');
176
+ expect(manifest.package).toBe('@vxnus/e-teyvat');
177
+ expect(manifest.displayName).toBe('E Teyvat');
178
+ expect(manifest.version).toBe('1.2.0');
179
+ });
180
+ });
181
+ describe('Other Organ Configurators & Generic Router', () => {
182
+ test('Memory configurator configures PostgreSQL with Supabase deployment', async () => {
183
+ inquirer_1.default.prompt
184
+ .mockResolvedValueOnce({ database: 'postgres' })
185
+ .mockResolvedValueOnce({ deployment: 'supabase' });
186
+ const result = await (0, memory_1.configureMemory)({ companionName: 'Sparkle', manifest: memoryManifest });
187
+ expect(result.config.provider).toBe('postgres');
188
+ expect(result.config.deployment).toBe('supabase');
189
+ expect(result.summary?.Deploy).toBe('Supabase');
190
+ });
191
+ test('Voice configurator configures VOICEVOX engine and speaker ID', async () => {
192
+ inquirer_1.default.prompt
193
+ .mockResolvedValueOnce({ provider: 'voicevox' })
194
+ .mockResolvedValueOnce({ baseUrl: 'http://localhost:50021', speakerId: '2' });
195
+ const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
196
+ expect(result.config.provider).toBe('voicevox');
197
+ expect(result.config.speakerId).toBe(2);
198
+ expect(result.summary?.['Speaker ID']).toBe(2);
199
+ });
200
+ test('Body configurator configures Live2D and expression', async () => {
201
+ inquirer_1.default.prompt
202
+ .mockResolvedValueOnce({ provider: 'live2d' })
203
+ .mockResolvedValueOnce({ initialExpression: 'happy' });
204
+ const result = await (0, body_1.configureBody)({ companionName: 'Sparkle', manifest: bodyManifest });
205
+ expect(result.config.provider).toBe('live2d');
206
+ expect(result.config.initialExpression).toBe('happy');
207
+ });
208
+ test('Hands configurator configures MCP tool execution timeout', async () => {
209
+ inquirer_1.default.prompt.mockResolvedValueOnce({ timeoutSeconds: '15' });
210
+ const result = await (0, hands_1.configureHands)({ companionName: 'Sparkle', manifest: handsManifest });
211
+ expect(result.config.defaultTimeoutMs).toBe(15000);
212
+ });
213
+ test('Behavior configurator configures active self personality preset', async () => {
214
+ inquirer_1.default.prompt.mockResolvedValueOnce({ preset: 'cheerful' });
215
+ const result = await (0, behavior_1.configureBehavior)({ companionName: 'Sparkle', manifest: behaviorManifest });
216
+ expect(result.config.provider).toBe('active_self');
217
+ expect(result.config.preset).toBe('cheerful');
218
+ });
219
+ test('Vision configurator configures OpenRouter vision model', async () => {
220
+ inquirer_1.default.prompt.mockResolvedValueOnce({ model: 'gpt-4-vision' });
221
+ const result = await (0, vision_1.configureVision)({ companionName: 'Sparkle', manifest: visionManifest });
222
+ expect(result.config.provider).toBe('openrouter');
223
+ expect(result.config.model).toBe('gpt-4-vision');
224
+ });
225
+ });
226
+ describe('Review Summary Formatting & Generator Integration', () => {
227
+ test('formatReviewSummary displays actual configuration values and metadata', () => {
228
+ const summaryOutput = (0, index_1.formatReviewSummary)('Sparkle', [brainManifest, memoryManifest, knowledgeManifest, voiceManifest], {
229
+ brain: { Provider: 'OpenRouter', Model: 'openai/gpt-4o-mini' },
230
+ memory: { Database: 'PostgreSQL', Deploy: 'Supabase' },
231
+ knowledge: { Source: 'E Knowledge Hub', Provider: 'E Teyvat', Package: '@vxnus/e-teyvat', Version: '1.2.0' },
232
+ voice: { Provider: 'VOICEVOX', 'Speaker ID': 1 },
233
+ });
234
+ expect(summaryOutput).toContain('Sparkle');
235
+ expect(summaryOutput).toContain('Brain');
236
+ expect(summaryOutput).toContain('OpenRouter');
237
+ expect(summaryOutput).toContain('openai/gpt-4o-mini');
238
+ expect(summaryOutput).toContain('Memory');
239
+ expect(summaryOutput).toContain('PostgreSQL');
240
+ expect(summaryOutput).toContain('Supabase');
241
+ expect(summaryOutput).toContain('Knowledge');
242
+ expect(summaryOutput).toContain('E Knowledge Hub');
243
+ expect(summaryOutput).toContain('E Teyvat');
244
+ expect(summaryOutput).toContain('@vxnus/e-teyvat');
245
+ expect(summaryOutput).toContain('Voice');
246
+ expect(summaryOutput).toContain('VOICEVOX');
247
+ });
248
+ test('generateInstanceFiles accurately embeds configured organ values in siduri.config.json', () => {
249
+ const organConfigs = {
250
+ brain: { provider: 'openrouter', model: 'openai/gpt-4o-mini', apiKeyEnv: 'OPENROUTER_API_KEY' },
251
+ memory: { provider: 'postgres', deployment: 'supabase' },
252
+ knowledge: { provider: 'e-hub', registryUrl: 'https://e.vxnus.xyz/api/v1/knowledge', packId: '@vxnus/e-teyvat' },
253
+ voice: { provider: 'voicevox', speakerId: 2, baseUrl: 'http://localhost:50021' },
254
+ };
255
+ const files = (0, generator_1.generateInstanceFiles)({
256
+ name: 'Sparkle',
257
+ selectedManifests: [brainManifest, memoryManifest, knowledgeManifest, voiceManifest],
258
+ organConfigs,
259
+ });
260
+ const config = JSON.parse(files['siduri.config.json']);
261
+ expect(config.name).toBe('Sparkle');
262
+ expect(config.organs.brain.provider).toBe('openrouter');
263
+ expect(config.organs.brain.model).toBe('openai/gpt-4o-mini');
264
+ expect(config.organs.memory.deployment).toBe('supabase');
265
+ expect(config.organs.knowledge.provider).toBe('e-hub');
266
+ expect(config.organs.knowledge.packId).toBe('@vxnus/e-teyvat');
267
+ expect(config.organs.voice.speakerId).toBe(2);
268
+ // Verify explicit imports in src/index.js
269
+ expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
270
+ expect(files['src/index.js']).toContain("import { PostgresMemoryOrgan } from '@siduri-x/memory'");
271
+ expect(files['src/index.js']).toContain("import { EKnowledgeAdapter } from '@siduri-x/knowledge'");
272
+ expect(files['src/index.js']).toContain("import { VoicevoxAdapter } from '@siduri-x/voice'");
273
+ });
274
+ });
275
+ });
package/dist/index.d.ts CHANGED
@@ -1,4 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ import { OrganManifest } from './manifest';
3
+ export declare const CLI_VERSION = "0.0.8";
4
+ export declare const colors: {
5
+ cyan: string;
6
+ dim: string;
7
+ green: string;
8
+ yellow: string;
9
+ bold: string;
10
+ reset: string;
11
+ };
12
+ export declare function printHeader(): void;
13
+ export declare function printSection(title: string): void;
14
+ export declare function printSuccess(message: string): void;
15
+ export declare function projectDirectoryName(value: string): string;
16
+ export declare function formatReviewSummary(companionName: string, selectedManifests: OrganManifest[], organSummaries: Record<string, Record<string, unknown>>): string;
17
+ export declare function withTask<T>(label: string, task: () => Promise<T>): Promise<T>;
2
18
  export declare function runCreateWizard(targetDir?: string): Promise<void>;
3
19
  export declare function runCliDoctor(targetDir?: string): Promise<void>;
4
20
  export declare function runCliDb(subcommand?: string, targetDir?: string): Promise<void>;
package/dist/index.js CHANGED
@@ -4,6 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.colors = exports.CLI_VERSION = void 0;
8
+ exports.printHeader = printHeader;
9
+ exports.printSection = printSection;
10
+ exports.printSuccess = printSuccess;
11
+ exports.projectDirectoryName = projectDirectoryName;
12
+ exports.formatReviewSummary = formatReviewSummary;
13
+ exports.withTask = withTask;
7
14
  exports.runCreateWizard = runCreateWizard;
8
15
  exports.runCliDoctor = runCliDoctor;
9
16
  exports.runCliDb = runCliDb;
@@ -16,24 +23,26 @@ const discovery_1 = require("./discovery");
16
23
  const generator_1 = require("./generator");
17
24
  const doctor_1 = require("./doctor");
18
25
  const db_1 = require("./db");
26
+ const configurators_1 = require("./configurators");
19
27
  const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
20
- const CLI_VERSION = '0.0.7';
21
- const colors = {
28
+ exports.CLI_VERSION = '0.0.8';
29
+ exports.colors = {
22
30
  cyan: '\u001b[36m',
23
31
  dim: '\u001b[2m',
24
32
  green: '\u001b[32m',
25
33
  yellow: '\u001b[33m',
34
+ bold: '\u001b[1m',
26
35
  reset: '\u001b[0m',
27
36
  };
28
37
  function printHeader() {
29
- console.log(`\n${colors.cyan}◈ SIDURI${colors.reset} ${colors.dim}companion setup (manifest-driven)${colors.reset}`);
30
- console.log(`${colors.yellow}Version ${CLI_VERSION}${colors.reset} · composable standalone architecture\n`);
38
+ console.log(`\n${exports.colors.cyan}◈ SIDURI${exports.colors.reset} ${exports.colors.dim}companion setup (manifest-driven)${exports.colors.reset}`);
39
+ console.log(`${exports.colors.yellow}Version ${exports.CLI_VERSION}${exports.colors.reset} · composable standalone architecture\n`);
31
40
  }
32
41
  function printSection(title) {
33
- console.log(`\n${colors.cyan}── ${title} ${'─'.repeat(Math.max(2, 42 - title.length))}${colors.reset}`);
42
+ console.log(`\n${exports.colors.cyan}── ${title} ${'─'.repeat(Math.max(2, 42 - title.length))}${exports.colors.reset}\n`);
34
43
  }
35
44
  function printSuccess(message) {
36
- console.log(`${colors.green}✓${colors.reset} ${message}`);
45
+ console.log(`${exports.colors.green}✓${exports.colors.reset} ${message}`);
37
46
  }
38
47
  function projectDirectoryName(value) {
39
48
  const slug = value
@@ -43,22 +52,47 @@ function projectDirectoryName(value) {
43
52
  .replace(/^-+|-+$/g, '');
44
53
  return slug || 'siduri';
45
54
  }
55
+ function formatReviewSummary(companionName, selectedManifests, organSummaries) {
56
+ const lines = [];
57
+ lines.push(`\n${exports.colors.cyan}── Review ${companionName} ${'─'.repeat(Math.max(2, 42 - (companionName.length + 9)))}${exports.colors.reset}\n`);
58
+ lines.push(` ${exports.colors.bold}Companion${exports.colors.reset}`);
59
+ lines.push(` ${exports.colors.dim}Name:${exports.colors.reset} ${companionName}\n`);
60
+ for (const m of selectedManifests) {
61
+ const isRequired = m.organType === 'brain';
62
+ const tag = isRequired ? ` ${exports.colors.dim}· required${exports.colors.reset}` : '';
63
+ lines.push(` ${exports.colors.bold}${m.displayName.split(' ')[0] || m.organType}${exports.colors.reset}${tag}`);
64
+ const summary = organSummaries[m.configKey] || organSummaries[m.organType] || {};
65
+ const entries = Object.entries(summary);
66
+ if (entries.length === 0) {
67
+ lines.push(` ${exports.colors.dim}Provider:${exports.colors.reset} ${m.displayName}`);
68
+ }
69
+ else {
70
+ for (const [key, val] of entries) {
71
+ const valStr = String(val);
72
+ lines.push(` ${exports.colors.dim}${key}:${exports.colors.reset}${' '.repeat(Math.max(1, 10 - key.length))}${valStr}`);
73
+ }
74
+ }
75
+ lines.push('');
76
+ }
77
+ lines.push(`${exports.colors.cyan}${'─'.repeat(46)}${exports.colors.reset}\n`);
78
+ return lines.join('\n');
79
+ }
46
80
  async function withTask(label, task) {
47
- process.stdout.write(`${colors.dim}${label}${colors.reset}`);
81
+ process.stdout.write(`${exports.colors.dim}${label}${exports.colors.reset}`);
48
82
  const frames = ['·', '•', '●', '•'];
49
83
  let index = 0;
50
84
  const timer = setInterval(() => {
51
- process.stdout.write(`\r${colors.dim}${label} ${frames[index++ % frames.length]}${colors.reset}`);
85
+ process.stdout.write(`\r${exports.colors.dim}${label} ${frames[index++ % frames.length]}${exports.colors.reset}`);
52
86
  }, 120);
53
87
  try {
54
88
  const result = await task();
55
89
  clearInterval(timer);
56
- process.stdout.write(`\r${colors.green}✓${colors.reset} ${label}\n`);
90
+ process.stdout.write(`\r${exports.colors.green}✓${exports.colors.reset} ${label}\n`);
57
91
  return result;
58
92
  }
59
93
  catch (error) {
60
94
  clearInterval(timer);
61
- process.stdout.write(`\r${colors.yellow}!${colors.reset} ${label}\n`);
95
+ process.stdout.write(`\r${exports.colors.yellow}!${exports.colors.reset} ${label}\n`);
62
96
  throw error;
63
97
  }
64
98
  }
@@ -86,7 +120,7 @@ async function runCreateWizard(targetDir) {
86
120
  const companionName = basicAnswers.name;
87
121
  const projectDir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : node_path_1.default.resolve(process.cwd(), projectDirectoryName(companionName));
88
122
  printSection('Organ Selection');
89
- console.log(`${colors.dim}Select any combination of organs to compose into your standalone instance.${colors.reset}\n`);
123
+ console.log(`${exports.colors.dim}Select any combination of organs to compose into your standalone instance.${exports.colors.reset}\n`);
90
124
  // Brain is required by architecture contract for cognition
91
125
  const brainManifest = registry.get('brain');
92
126
  const nonBrainManifests = availableManifests.filter((m) => m.organType !== 'brain');
@@ -110,28 +144,77 @@ async function runCreateWizard(targetDir) {
110
144
  if (m)
111
145
  selectedManifests.push(m);
112
146
  }
113
- printSection('Review Composition');
114
- console.log(` ${colors.dim}Companion Name:${colors.reset} ${companionName}`);
115
- console.log(` ${colors.dim}Target Path:${colors.reset} ${projectDir}`);
116
- console.log(` ${colors.dim}Core Protocol:${colors.reset} @siduri-x/core`);
117
- console.log(` ${colors.dim}Selected Organs:${colors.reset}`);
147
+ // 2. Interactive Organ Configuration Stage
148
+ const organConfigs = {};
149
+ const organSummaries = {};
118
150
  for (const m of selectedManifests) {
119
- console.log(` - ${m.displayName} (${colors.dim}${m.name}${colors.reset})`);
151
+ const isRequired = m.organType === 'brain';
152
+ const sectionTitle = isRequired ? `${m.displayName.split(' ')[0] || m.organType} · required` : m.displayName.split(' ')[0] || m.organType;
153
+ printSection(sectionTitle);
154
+ const res = await (0, configurators_1.configureOrgan)(m, {
155
+ companionName,
156
+ existingConfig: organConfigs[m.configKey],
157
+ });
158
+ organConfigs[m.configKey] = res.config;
159
+ organSummaries[m.configKey] = res.summary || {};
120
160
  }
121
- const { confirm } = await inquirer_1.default.prompt({
122
- type: 'confirm',
123
- name: 'confirm',
124
- message: 'Generate standalone Siduri instance with these organs?',
125
- default: true,
126
- });
127
- if (!confirm) {
128
- console.log('Instance creation cancelled.');
129
- return;
161
+ // 3. Final Review and Edit Loop
162
+ while (true) {
163
+ console.log(formatReviewSummary(companionName, selectedManifests, organSummaries));
164
+ const { reviewAction } = await inquirer_1.default.prompt({
165
+ type: 'list',
166
+ name: 'reviewAction',
167
+ message: `Create ${companionName} with this configuration?`,
168
+ choices: [
169
+ { name: 'Yes, create', value: 'create' },
170
+ { name: 'Go back and edit', value: 'edit' },
171
+ { name: 'Cancel', value: 'cancel' },
172
+ ],
173
+ });
174
+ if (reviewAction === 'cancel') {
175
+ console.log('Instance creation cancelled.');
176
+ return;
177
+ }
178
+ if (reviewAction === 'create') {
179
+ break;
180
+ }
181
+ if (reviewAction === 'edit') {
182
+ const editChoices = selectedManifests.map((m) => ({
183
+ name: `Edit ${m.displayName}`,
184
+ value: m.configKey,
185
+ }));
186
+ editChoices.push({ name: 'Edit all organs in sequence', value: '__ALL__' });
187
+ editChoices.push({ name: 'Back to review', value: '__BACK__' });
188
+ const { organToEdit } = await inquirer_1.default.prompt({
189
+ type: 'list',
190
+ name: 'organToEdit',
191
+ message: 'Which organ would you like to edit?',
192
+ choices: editChoices,
193
+ });
194
+ if (organToEdit === '__BACK__') {
195
+ continue;
196
+ }
197
+ const organsToReconfigure = organToEdit === '__ALL__'
198
+ ? selectedManifests
199
+ : selectedManifests.filter((m) => m.configKey === organToEdit);
200
+ for (const m of organsToReconfigure) {
201
+ const isRequired = m.organType === 'brain';
202
+ const sectionTitle = isRequired ? `${m.displayName.split(' ')[0] || m.organType} · required` : m.displayName.split(' ')[0] || m.organType;
203
+ printSection(sectionTitle);
204
+ const res = await (0, configurators_1.configureOrgan)(m, {
205
+ companionName,
206
+ existingConfig: organConfigs[m.configKey],
207
+ });
208
+ organConfigs[m.configKey] = res.config;
209
+ organSummaries[m.configKey] = res.summary || {};
210
+ }
211
+ }
130
212
  }
131
- // 2. Generate Files
213
+ // 4. Generate Instance Files
132
214
  const files = (0, generator_1.generateInstanceFiles)({
133
215
  name: companionName,
134
216
  selectedManifests,
217
+ organConfigs,
135
218
  });
136
219
  await (0, promises_1.mkdir)(projectDir, { recursive: true });
137
220
  await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'src'), { recursive: true });
@@ -145,7 +228,7 @@ async function runCreateWizard(targetDir) {
145
228
  await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'assets/body/model'), { recursive: true });
146
229
  }
147
230
  printSuccess(`Generated standalone files at ${projectDir}`);
148
- // 3. Install packages if not in dry-run
231
+ // 5. Install packages if not in dry-run
149
232
  try {
150
233
  await withTask('Installing dependencies (npm install)', async () => {
151
234
  await execFile('npm', ['install', '--no-audit', '--no-fund'], { cwd: projectDir });
@@ -153,48 +236,48 @@ async function runCreateWizard(targetDir) {
153
236
  printSuccess('Dependencies installed successfully.');
154
237
  }
155
238
  catch (err) {
156
- console.warn(`${colors.yellow}!${colors.reset} Notice: npm install had warnings or requires network: ${err.message}`);
239
+ console.warn(`${exports.colors.yellow}!${exports.colors.reset} Notice: npm install had warnings or requires network: ${err.message}`);
157
240
  }
158
241
  printSection('Instance Ready');
159
- console.log(`\nYour Siduri companion is ready! Next steps:\n`);
242
+ console.log(`Your Siduri companion is ready! Next steps:\n`);
160
243
  console.log(` cd ${node_path_1.default.relative(process.cwd(), projectDir) || '.'}`);
161
- console.log(` cp .env.example .env ${colors.dim}# Fill in required API keys/credentials${colors.reset}`);
162
- console.log(` npm start ${colors.dim}# Start your standalone companion${colors.reset}\n`);
244
+ console.log(` cp .env.example .env ${exports.colors.dim}# Fill in required API keys/credentials${exports.colors.reset}`);
245
+ console.log(` npm start ${exports.colors.dim}# Start your standalone companion${exports.colors.reset}\n`);
163
246
  }
164
247
  async function runCliDoctor(targetDir) {
165
248
  printHeader();
166
249
  const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
167
- console.log(`${colors.cyan}Siduri Doctor${colors.reset}`);
168
- console.log(`${colors.dim}─────────────${colors.reset}\n`);
250
+ console.log(`${exports.colors.cyan}Siduri Doctor${exports.colors.reset}`);
251
+ console.log(`${exports.colors.dim}─────────────${exports.colors.reset}\n`);
169
252
  try {
170
253
  const report = await (0, doctor_1.runDoctor)({ projectDir: dir });
171
- console.log(`${colors.dim}Instance:${colors.reset} ${report.instanceName}`);
172
- console.log(`${colors.dim}Organs:${colors.reset} ${report.configuredOrgans.join(', ')}\n`);
254
+ console.log(`${exports.colors.dim}Instance:${exports.colors.reset} ${report.instanceName}`);
255
+ console.log(`${exports.colors.dim}Organs:${exports.colors.reset} ${report.configuredOrgans.join(', ')}\n`);
173
256
  const categories = ['Environment', 'Services', 'Database', 'Health Probe'];
174
257
  for (const cat of categories) {
175
258
  const items = report.results.filter((r) => r.category === cat);
176
259
  if (items.length > 0) {
177
- console.log(`${colors.cyan}${cat}${colors.reset}`);
260
+ console.log(`${exports.colors.cyan}${cat}${exports.colors.reset}`);
178
261
  for (const item of items) {
179
262
  if (item.status === 'PASS') {
180
- console.log(` ${colors.green}✓${colors.reset} ${item.name} ${colors.dim}(${item.message || 'OK'})${colors.reset}`);
263
+ console.log(` ${exports.colors.green}✓${exports.colors.reset} ${item.name} ${exports.colors.dim}(${item.message || 'OK'})${exports.colors.reset}`);
181
264
  }
182
265
  else if (item.status === 'OPTIONAL_MISSING') {
183
- console.log(` ${colors.dim}○${colors.reset} ${item.name} ${colors.dim}(Optional, not set)${colors.reset}`);
266
+ console.log(` ${exports.colors.dim}○${exports.colors.reset} ${item.name} ${exports.colors.dim}(Optional, not set)${exports.colors.reset}`);
184
267
  }
185
268
  else if (item.status === 'SKIPPED') {
186
- console.log(` ${colors.dim}— ${item.name} (${item.message})${colors.reset}`);
269
+ console.log(` ${exports.colors.dim}— ${item.name} (${item.message})${exports.colors.reset}`);
187
270
  }
188
271
  else {
189
- console.log(` ${colors.yellow}✗${colors.reset} ${item.name}`);
272
+ console.log(` ${exports.colors.yellow}✗${exports.colors.reset} ${item.name}`);
190
273
  if (item.organName) {
191
- console.log(` ${colors.dim}Required by:${colors.reset} ${item.organName}`);
274
+ console.log(` ${exports.colors.dim}Required by:${exports.colors.reset} ${item.organName}`);
192
275
  }
193
276
  if (item.message) {
194
- console.log(` ${colors.yellow}${item.message}${colors.reset}`);
277
+ console.log(` ${exports.colors.yellow}${item.message}${exports.colors.reset}`);
195
278
  }
196
279
  if (item.remediation) {
197
- console.log(` ${colors.dim}Remediation:${colors.reset} ${item.remediation}`);
280
+ console.log(` ${exports.colors.dim}Remediation:${exports.colors.reset} ${item.remediation}`);
198
281
  }
199
282
  }
200
283
  }
@@ -202,16 +285,16 @@ async function runCliDoctor(targetDir) {
202
285
  }
203
286
  }
204
287
  if (report.passed) {
205
- console.log(`${colors.green}Result: PASS${colors.reset}\n`);
288
+ console.log(`${exports.colors.green}Result: PASS${exports.colors.reset}\n`);
206
289
  process.exitCode = 0;
207
290
  }
208
291
  else {
209
- console.log(`${colors.yellow}Result: FAIL${colors.reset}\n`);
292
+ console.log(`${exports.colors.yellow}Result: FAIL${exports.colors.reset}\n`);
210
293
  process.exitCode = 1;
211
294
  }
212
295
  }
213
296
  catch (err) {
214
- console.error(`\n${colors.yellow}Doctor Error:${colors.reset} ${err.message}\n`);
297
+ console.error(`\n${exports.colors.yellow}Doctor Error:${exports.colors.reset} ${err.message}\n`);
215
298
  process.exitCode = 2;
216
299
  }
217
300
  }
@@ -223,24 +306,24 @@ async function runCliDb(subcommand, targetDir) {
223
306
  return;
224
307
  }
225
308
  const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
226
- console.log(`${colors.cyan}Siduri Database Migrations${colors.reset}`);
227
- console.log(`${colors.dim}──────────────────────────${colors.reset}\n`);
309
+ console.log(`${exports.colors.cyan}Siduri Database Migrations${exports.colors.reset}`);
310
+ console.log(`${exports.colors.dim}──────────────────────────${exports.colors.reset}\n`);
228
311
  try {
229
312
  const res = await (0, db_1.runDbPush)({ projectDir: dir });
230
313
  if (res.status === 'NOOP') {
231
- console.log(`${colors.dim}— ${res.message}${colors.reset}\n`);
314
+ console.log(`${exports.colors.dim}— ${res.message}${exports.colors.reset}\n`);
232
315
  }
233
316
  else {
234
317
  printSuccess(res.message);
235
318
  if (res.appliedMigrations.length > 0) {
236
- console.log(`${colors.dim}Applied:${colors.reset} ${res.appliedMigrations.join(', ')}`);
319
+ console.log(`${exports.colors.dim}Applied:${exports.colors.reset} ${res.appliedMigrations.join(', ')}`);
237
320
  }
238
321
  console.log();
239
322
  }
240
323
  process.exitCode = 0;
241
324
  }
242
325
  catch (err) {
243
- console.error(`\n${colors.yellow}Database Migration Error:${colors.reset} ${err.message}\n`);
326
+ console.error(`\n${exports.colors.yellow}Database Migration Error:${exports.colors.reset} ${err.message}\n`);
244
327
  process.exitCode = 3;
245
328
  }
246
329
  }
@@ -248,7 +331,7 @@ async function main() {
248
331
  const args = process.argv.slice(2);
249
332
  const command = args[0];
250
333
  if (command === '--version' || command === '-v') {
251
- console.log(CLI_VERSION);
334
+ console.log(exports.CLI_VERSION);
252
335
  return;
253
336
  }
254
337
  if (command === 'create') {
@@ -279,7 +362,7 @@ if (require.main === module) {
279
362
  console.log('\nOperation cancelled.');
280
363
  return;
281
364
  }
282
- console.error(`\n${colors.yellow}!${colors.reset} ${error instanceof Error ? error.message : error}`);
365
+ console.error(`\n${exports.colors.yellow}!${exports.colors.reset} ${error instanceof Error ? error.message : error}`);
283
366
  process.exitCode = 1;
284
367
  });
285
368
  }