@vxnus/siduri 2.0.36 → 2.0.38

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.
@@ -5,7 +5,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
5
5
  {
6
6
  name: '@siduri-x/self',
7
7
  organType: 'behavior',
8
- version: '2.0.7',
8
+ version: '2.0.11',
9
9
  displayName: 'Self & Persona (Identity & Directives)',
10
10
  description: 'Autonomous persona compiler, relational stances, and .self asset loader',
11
11
  entrypoint: './dist/index.js',
@@ -6,8 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.configureBehavior = configureBehavior;
7
7
  const inquirer_1 = __importDefault(require("inquirer"));
8
8
  async function configureBehavior(_context) {
9
- const companionName = _context.companionName || 'Companion';
10
- const companionSlug = companionName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
11
9
  const { personaMode } = await inquirer_1.default.prompt({
12
10
  type: 'list',
13
11
  name: 'personaMode',
@@ -59,7 +57,7 @@ async function configureBehavior(_context) {
59
57
  const archetype = personaAnswers.archetype.trim() || 'Knowledge Assistant & Research Partner';
60
58
  const ethos = personaAnswers.ethos.trim() || 'Direct technical candor, thoughtful, concise, and loyal';
61
59
  const directive = personaAnswers.directive.trim() || 'Speak concisely and stay in character without sycophantic filler';
62
- const selfPath = `./assets/self/${companionSlug}.self`;
60
+ const selfPath = './assets/self/default.self';
63
61
  return {
64
62
  config: {
65
63
  provider: 'active_self',
@@ -21,18 +21,21 @@ async function configureBody(_context) {
21
21
  summary: { Provider: 'None (Headless)' },
22
22
  };
23
23
  }
24
- const companionSlug = _context.companionName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
25
24
  const { modelSource } = await inquirer_1.default.prompt([
26
25
  {
27
26
  type: 'input',
28
27
  name: 'modelSource',
29
28
  message: 'Live2D Model path / URL (.model3.json):',
30
- default: `./assets/body/${companionSlug}/model.model3.json`,
29
+ default: './assets/body/default/model.model3.json',
31
30
  },
32
31
  ]);
33
- const modelPath = modelSource.trim() || `./assets/body/${companionSlug}/model.model3.json`;
32
+ const modelPath = modelSource.trim() || './assets/body/default/model.model3.json';
34
33
  const isHttpOrAbsolute = modelPath.startsWith('http://') || modelPath.startsWith('https://') || modelPath.startsWith('/');
35
- const webModelUrl = isHttpOrAbsolute ? modelPath : `/assets/body/${companionSlug}/model.model3.json`;
34
+ const webModelUrl = isHttpOrAbsolute
35
+ ? modelPath
36
+ : modelPath.startsWith('./')
37
+ ? modelPath.slice(1)
38
+ : `/${modelPath}`;
36
39
  return {
37
40
  config: {
38
41
  provider: 'live2d',
@@ -7,7 +7,6 @@ exports.configureVoice = configureVoice;
7
7
  const inquirer_1 = __importDefault(require("inquirer"));
8
8
  const colors_1 = require("../colors");
9
9
  async function configureVoice(_context) {
10
- const companionSlug = _context.companionName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
11
10
  const { provider } = await inquirer_1.default.prompt({
12
11
  type: 'list',
13
12
  name: 'provider',
@@ -35,21 +34,21 @@ async function configureVoice(_context) {
35
34
  }
36
35
  if (provider === 'rvc') {
37
36
  console.log(`\n ${colors_1.colors.cyan}ℹ RVC Voice Model Setup:${colors_1.colors.reset}`);
38
- console.log(` • ${colors_1.colors.dim}Voice Weights:${colors_1.colors.reset} Place your trained weights (.pth) in: ${colors_1.colors.green}./assets/voice/${companionSlug}/${companionSlug}.pth${colors_1.colors.reset}`);
39
- console.log(` • ${colors_1.colors.dim}Feature Index:${colors_1.colors.reset} Place optional .index in: ${colors_1.colors.green}./assets/voice/${companionSlug}/${companionSlug}.index${colors_1.colors.reset}`);
37
+ console.log(` • ${colors_1.colors.dim}Voice Weights:${colors_1.colors.reset} Place your trained weights (.pth) in: ${colors_1.colors.green}./assets/voice/default/default.pth${colors_1.colors.reset}`);
38
+ console.log(` • ${colors_1.colors.dim}Feature Index:${colors_1.colors.reset} Place optional .index in: ${colors_1.colors.green}./assets/voice/default/default.index${colors_1.colors.reset}`);
40
39
  console.log(` • ${colors_1.colors.dim}RVC Service:${colors_1.colors.reset} Ensure your headless RVC microservice is running before voice inference.\n`);
41
40
  const rvcAnswers = await inquirer_1.default.prompt([
42
41
  {
43
42
  type: 'input',
44
43
  name: 'modelPath',
45
44
  message: 'RVC Model Path (.pth weights):',
46
- default: `./assets/voice/${companionSlug}/${companionSlug}.pth`,
45
+ default: './assets/voice/default/default.pth',
47
46
  },
48
47
  {
49
48
  type: 'input',
50
49
  name: 'indexPath',
51
50
  message: 'RVC Feature Index Path (.index, optional):',
52
- default: `./assets/voice/${companionSlug}/${companionSlug}.index`,
51
+ default: './assets/voice/default/default.index',
53
52
  },
54
53
  {
55
54
  type: 'input',
@@ -90,7 +89,7 @@ async function configureVoice(_context) {
90
89
  rvc: {
91
90
  enabled: true,
92
91
  serviceUrl: rvcAnswers.serviceUrl.trim(),
93
- modelName: companionSlug,
92
+ modelName: 'default',
94
93
  modelPath: rvcAnswers.modelPath.trim(),
95
94
  indexPath: rvcAnswers.indexPath.trim() || undefined,
96
95
  pitchShift,
@@ -304,6 +304,18 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
304
304
  expect(result.config.initialExpression).toBe('neutral');
305
305
  expect(result.summary?.['Model Path']).toBe('./assets/body/sparkle/model.model3.json');
306
306
  });
307
+ test('Body configurator defaults model path to assets/body/default', async () => {
308
+ inquirer_1.default.prompt
309
+ .mockResolvedValueOnce({ provider: 'live2d' })
310
+ .mockResolvedValueOnce({
311
+ modelSource: '',
312
+ });
313
+ const result = await (0, body_1.configureBody)({ companionName: 'MyCompanion', manifest: bodyManifest });
314
+ expect(result.config.provider).toBe('live2d');
315
+ expect(result.config.modelPath).toBe('./assets/body/default/model.model3.json');
316
+ expect(result.config.modelUrl).toBe('/assets/body/default/model.model3.json');
317
+ expect(result.summary?.['Model Path']).toBe('./assets/body/default/model.model3.json');
318
+ });
307
319
  test('Hands configurator configures MCP tool execution timeout', async () => {
308
320
  inquirer_1.default.prompt
309
321
  .mockResolvedValueOnce({ provider: 'mcp' })
@@ -337,7 +349,7 @@ describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
337
349
  expect(result.config.provider).toBe('active_self');
338
350
  expect(result.config.mode).toBe('custom');
339
351
  expect(result.config.archetype).toBe('System Sentinel');
340
- expect(result.config.selfPath).toBe('./assets/self/sparkle.self');
352
+ expect(result.config.selfPath).toBe('./assets/self/default.self');
341
353
  });
342
354
  test('Vision configurator configures OpenRouter vision model', async () => {
343
355
  inquirer_1.default.prompt.mockResolvedValueOnce({ model: 'gpt-4-vision' });
package/dist/generator.js CHANGED
@@ -73,10 +73,9 @@ function getDefaultConfigForManifest(manifest) {
73
73
  }
74
74
  function generateInstanceFiles(options) {
75
75
  const instanceName = options.name || 'my-siduri';
76
- const companionSlug = instanceName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
77
76
  const instanceId = options.id || 'default';
78
77
  const coreVersion = options.coreVersion || '^2.0.15';
79
- const cliVersion = options.cliVersion || '^2.0.36';
78
+ const cliVersion = options.cliVersion || '^2.0.38';
80
79
  const canonicalOrder = ['brain', 'memory', 'knowledge', 'behavior', 'voice', 'body', 'mouth', 'hands', 'vision', 'ear', 'observation'];
81
80
  const manifests = [...options.selectedManifests].sort((a, b) => {
82
81
  const idxA = canonicalOrder.indexOf(a.organType);
@@ -192,7 +191,7 @@ function generateInstanceFiles(options) {
192
191
  ];
193
192
  for (const m of manifests) {
194
193
  if (m.name === '@siduri-x/self') {
195
- importLines.push(`import { ActiveSelfCompiler, SqliteSelfRepository, SelfPackageParser } from '@siduri-x/self';`);
194
+ importLines.push(`import { ActiveSelfCompiler, SqliteSelfRepository, SelfPackageParser, scanDirective } from '@siduri-x/self';`);
196
195
  }
197
196
  else {
198
197
  importLines.push(`import { ${m.factory} } from '${m.name}';`);
@@ -204,7 +203,7 @@ function generateInstanceFiles(options) {
204
203
  if (m.name === '@siduri-x/self') {
205
204
  instantiationLines.push(`const self = new SqliteSelfRepository({ dbPath: path.resolve(rootDir, 'siduri.sqlite') });`);
206
205
  instantiationLines.push(`const behavior = new ActiveSelfCompiler(config.organs.behavior);`);
207
- instantiationLines.push(`const selfFile = path.resolve(rootDir, config.organs.behavior?.selfPath || 'assets/self/${companionSlug}.self');`);
206
+ instantiationLines.push(`const selfFile = path.resolve(rootDir, config.organs.behavior?.selfPath || 'assets/self/default.self');`);
208
207
  instantiationLines.push(`try {`);
209
208
  instantiationLines.push(` const selfRaw = await readFile(selfFile, 'utf8').catch(() => null);`);
210
209
  instantiationLines.push(` if (selfRaw) {`);
@@ -347,6 +346,191 @@ function generateInstanceFiles(options) {
347
346
  ` return;`,
348
347
  ` }`,
349
348
  '',
349
+ ` // API: Teach Mode detected-self`,
350
+ ` if (pathname === '/teach/detected-self' && req.method === 'GET') {`,
351
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
352
+ ` if (typeof SelfPackageParser === 'undefined') {`,
353
+ ` res.end(JSON.stringify({ detected: false }));`,
354
+ ` return;`,
355
+ ` }`,
356
+ ` try {`,
357
+ ` const companionId = parsedUrl.searchParams.get('companionId') || config.id || 'default';`,
358
+ ` const explicitPath = parsedUrl.searchParams.get('path');`,
359
+ ` const envPath = process.env.SIDURI_SELF_PATH || process.env.SELF_PATH;`,
360
+ ` const configPath = config.organs?.behavior?.selfPath;`,
361
+ ` const candidates = [];`,
362
+ ` if (explicitPath) candidates.push(path.resolve(rootDir, explicitPath));`,
363
+ ` if (envPath) candidates.push(path.resolve(rootDir, envPath));`,
364
+ ` if (configPath) candidates.push(path.resolve(rootDir, configPath));`,
365
+ ` candidates.push(path.resolve(rootDir, 'assets', 'self', companionId + '.self'));`,
366
+ ` candidates.push(path.resolve(rootDir, 'assets', 'self', 'default.self'));`,
367
+ ` candidates.push(path.resolve(rootDir, companionId + '.self'));`,
368
+ '',
369
+ ` const searchDirs = [`,
370
+ ` path.resolve(rootDir, 'assets', 'self'),`,
371
+ ` path.resolve(rootDir, 'assets'),`,
372
+ ` path.resolve(rootDir),`,
373
+ ` ];`,
374
+ '',
375
+ ` let matchedFilePath = null;`,
376
+ ` for (const candidate of candidates) {`,
377
+ ` try {`,
378
+ ` const s = await stat(candidate);`,
379
+ ` if (s.isFile()) {`,
380
+ ` matchedFilePath = candidate;`,
381
+ ` break;`,
382
+ ` }`,
383
+ ` } catch {}`,
384
+ ` }`,
385
+ '',
386
+ ` if (!matchedFilePath) {`,
387
+ ` for (const dir of searchDirs) {`,
388
+ ` try {`,
389
+ ` const entries = await readdir(dir);`,
390
+ ` const selfFiles = entries.filter((f) => f.endsWith('.self'));`,
391
+ ` if (selfFiles.length > 0) {`,
392
+ ` const companionSelf = selfFiles.find((f) => f === companionId + '.self');`,
393
+ ` matchedFilePath = path.join(dir, companionSelf || selfFiles[0]);`,
394
+ ` break;`,
395
+ ` }`,
396
+ ` } catch {}`,
397
+ ` }`,
398
+ ` }`,
399
+ '',
400
+ ` if (!matchedFilePath) {`,
401
+ ` res.end(JSON.stringify({ detected: false }));`,
402
+ ` return;`,
403
+ ` }`,
404
+ '',
405
+ ` const content = await readFile(matchedFilePath, 'utf8');`,
406
+ ` const parsed = SelfPackageParser.parse(content);`,
407
+ ` let alreadyInstalled = false;`,
408
+ ` if (typeof self?.getIdentity === 'function') {`,
409
+ ` try {`,
410
+ ` const existingIdentity = await self.getIdentity(companionId);`,
411
+ ` const existingDirectives = await self.getActiveDirectives(companionId);`,
412
+ ` if (`,
413
+ ` existingIdentity &&`,
414
+ ` parsed.manifest?.identity?.name &&`,
415
+ ` existingIdentity.name.toLowerCase() === parsed.manifest.identity.name.toLowerCase() &&`,
416
+ ` existingDirectives.length > 0`,
417
+ ` ) {`,
418
+ ` alreadyInstalled = true;`,
419
+ ` }`,
420
+ ` } catch {}`,
421
+ ` }`,
422
+ '',
423
+ ` res.end(JSON.stringify({`,
424
+ ` detected: true,`,
425
+ ` filename: path.basename(matchedFilePath),`,
426
+ ` path: path.relative(rootDir, matchedFilePath),`,
427
+ ` content,`,
428
+ ` parsed,`,
429
+ ` alreadyInstalled,`,
430
+ ` }));`,
431
+ ` } catch (err) {`,
432
+ ` res.end(JSON.stringify({ detected: false, error: err.message }));`,
433
+ ` }`,
434
+ ` return;`,
435
+ ` }`,
436
+ '',
437
+ ` // API: Teach Mode upload-self`,
438
+ ` if (pathname === '/teach/upload-self' && req.method === 'POST') {`,
439
+ ` let body = '';`,
440
+ ` req.on('data', (chunk) => { body += chunk; });`,
441
+ ` req.on('end', async () => {`,
442
+ ` if (typeof SelfPackageParser === 'undefined') {`,
443
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
444
+ ` res.end(JSON.stringify({ error: 'Self organ is not configured' }));`,
445
+ ` return;`,
446
+ ` }`,
447
+ ` try {`,
448
+ ` const { content } = JSON.parse(body || '{}');`,
449
+ ` if (!content) {`,
450
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
451
+ ` res.end(JSON.stringify({ error: 'Missing content' }));`,
452
+ ` return;`,
453
+ ` }`,
454
+ ` const parsed = SelfPackageParser.parse(content);`,
455
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
456
+ ` res.end(JSON.stringify(parsed));`,
457
+ ` } catch (err) {`,
458
+ ` res.writeHead(500, { 'Content-Type': 'application/json' });`,
459
+ ` res.end(JSON.stringify({ error: err.message }));`,
460
+ ` }`,
461
+ ` });`,
462
+ ` return;`,
463
+ ` }`,
464
+ '',
465
+ ` // API: Teach Mode install-self`,
466
+ ` if (pathname === '/teach/install-self' && req.method === 'POST') {`,
467
+ ` let body = '';`,
468
+ ` req.on('data', (chunk) => { body += chunk; });`,
469
+ ` req.on('end', async () => {`,
470
+ ` if (typeof self === 'undefined' || typeof self?.setIdentity !== 'function') {`,
471
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
472
+ ` res.end(JSON.stringify({ error: 'Self organ is not configured' }));`,
473
+ ` return;`,
474
+ ` }`,
475
+ ` try {`,
476
+ ` const { companionId, manifest, approvedDirectiveIds } = JSON.parse(body || '{}');`,
477
+ ` const cid = companionId || config.id || 'default';`,
478
+ ` if (!manifest || !Array.isArray(approvedDirectiveIds)) {`,
479
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
480
+ ` res.end(JSON.stringify({ error: 'Missing required fields' }));`,
481
+ ` return;`,
482
+ ` }`,
483
+ ` if (!manifest.identity || !manifest.identity.name) {`,
484
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
485
+ ` res.end(JSON.stringify({ error: 'Invalid manifest: missing identity.name' }));`,
486
+ ` return;`,
487
+ ` }`,
488
+ ` const directivesToCommit = manifest.directives?.filter((d) => approvedDirectiveIds.includes(d.id)) || [];`,
489
+ ` for (const d of directivesToCommit) {`,
490
+ ` if (!d || typeof d.directive !== 'string') {`,
491
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
492
+ ` res.end(JSON.stringify({ error: 'Invalid directive entry: missing directive string' }));`,
493
+ ` return;`,
494
+ ` }`,
495
+ ` const scan = typeof scanDirective === 'function' ? scanDirective(d.directive) : { safe: true };`,
496
+ ` if (!scan.safe) {`,
497
+ ` res.writeHead(400, { 'Content-Type': 'application/json' });`,
498
+ ` res.end(JSON.stringify({ error: 'Safety check failed for directive: ' + scan.reason, directiveId: d.id, reason: scan.reason }));`,
499
+ ` return;`,
500
+ ` }`,
501
+ ` }`,
502
+ '',
503
+ ` await self.setIdentity({`,
504
+ ` companionId: cid,`,
505
+ ` name: manifest.identity.name,`,
506
+ ` archetype: manifest.identity.archetype,`,
507
+ ` origin: manifest.identity.origin,`,
508
+ ` ethos: manifest.identity.ethos,`,
509
+ ` version: manifest.version || '1.0.0',`,
510
+ ` updatedAt: new Date().toISOString(),`,
511
+ ` });`,
512
+ ` if (directivesToCommit.length > 0 && typeof self.commitDirectives === 'function') {`,
513
+ ` await self.commitDirectives(cid, directivesToCommit.map((d) => ({`,
514
+ ` id: d.id,`,
515
+ ` companionId: cid,`,
516
+ ` directive: d.directive,`,
517
+ ` category: d.category || 'behavioral',`,
518
+ ` status: 'active',`,
519
+ ` priority: d.priority || 50,`,
520
+ ` createdAt: new Date().toISOString(),`,
521
+ ` })));`,
522
+ ` }`,
523
+ '',
524
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
525
+ ` res.end(JSON.stringify({ success: true, companionId: cid, installedDirectives: directivesToCommit.length }));`,
526
+ ` } catch (err) {`,
527
+ ` res.writeHead(500, { 'Content-Type': 'application/json' });`,
528
+ ` res.end(JSON.stringify({ error: err.message }));`,
529
+ ` }`,
530
+ ` });`,
531
+ ` return;`,
532
+ ` }`,
533
+ '',
350
534
  ` // API: Voice & Observation health probes`,
351
535
  ` if (pathname === '/voice/health' && req.method === 'GET') {`,
352
536
  ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
@@ -849,12 +1033,12 @@ function generateInstanceFiles(options) {
849
1033
  readmeLines.push('', '### 3. Diagnostics & Health Probe', 'Verify all environment variables, schema conformance, services, and organ connections:', '```bash', 'npm run doctor', '```', '', '### 4. Start Companion & Web Console', 'Launch your companion runtime and Web UI / Memory Control Panel:', '```bash', 'npm start', '```', 'Then open `http://localhost:3000` in your browser.');
850
1034
  const createAssetsDirs = [];
851
1035
  if (hasBody) {
852
- createAssetsDirs.push(`assets/body/${companionSlug}`);
853
- readmeLines.push('', '### Body & Avatar Models', `Place your Live2D Cubism model assets into \`./assets/body/${companionSlug}/\`:`, '- `model.model3.json`', '- `model.moc3`', '- textures directory');
1036
+ createAssetsDirs.push('assets/body/default');
1037
+ readmeLines.push('', '### Body & Avatar Models', 'Place your Live2D Cubism model assets into `./assets/body/default/`:', '- `model.model3.json`', '- `model.moc3`', '- textures directory');
854
1038
  }
855
1039
  if (hasVoice) {
856
- createAssetsDirs.push(`assets/voice/${companionSlug}`);
857
- readmeLines.push('', '### Voice & RVC Models', `Place your character RVC voice models into \`./assets/voice/${companionSlug}/\`:`, `- \`${companionSlug}.pth\` (Target voice weights)`, `- \`${companionSlug}.index\` (Feature index file)`);
1040
+ createAssetsDirs.push('assets/voice/default');
1041
+ readmeLines.push('', '### Voice & RVC Models', 'Place your character RVC voice models into `./assets/voice/default/`:', '- `default.pth` (Target voice weights)', '- `default.index` (Feature index file)');
858
1042
  }
859
1043
  const knowledgeConfig = options.organConfigs?.knowledge || options.organConfigs?.['@siduri-x/knowledge'];
860
1044
  if (manifests.some((m) => m.organType === 'knowledge') && knowledgeConfig?.packPath) {
@@ -867,19 +1051,19 @@ function generateInstanceFiles(options) {
867
1051
  if (manifests.some((m) => m.organType === 'behavior')) {
868
1052
  createAssetsDirs.push('assets/self');
869
1053
  if (behaviorConfig?.mode === 'custom' || behaviorConfig?.archetype) {
870
- const selfRelPath = `assets/self/${companionSlug}.self`;
1054
+ const selfRelPath = 'assets/self/default.self';
871
1055
  const selfContent = [
872
1056
  `specVersion: "2.0.0"`,
873
1057
  `kind: "self"`,
874
- `id: "${companionSlug}-self"`,
875
- `name: "${instanceName} Persona"`,
1058
+ `id: "default-self"`,
1059
+ `name: "Default Persona"`,
876
1060
  `version: "1.0.0"`,
877
1061
  `author:`,
878
1062
  ` name: "Operator"`,
879
1063
  `license: "MIT"`,
880
1064
  ``,
881
1065
  `identity:`,
882
- ` name: "${(behaviorConfig.name || behaviorConfig.companionName || '').replace(/"/g, '\\"')}"`,
1066
+ ` name: "${(behaviorConfig.name || behaviorConfig.companionName || options.name || '').replace(/"/g, '\\"')}"`,
883
1067
  ` archetype: "${(behaviorConfig.archetype || 'Knowledge Assistant & Research Partner').replace(/"/g, '\\"')}"`,
884
1068
  ` origin: "Constructed companion"`,
885
1069
  ` ethos: "${(behaviorConfig.ethos || 'Direct technical candor, thoughtful, concise, and loyal').replace(/"/g, '\\"')}"`,
@@ -64,6 +64,18 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
64
64
  environment: [{ name: 'VOICEVOX_URL', default: 'http://localhost:50021' }],
65
65
  services: [{ name: 'VOICEVOX', kind: 'http_service' }],
66
66
  },
67
+ self: {
68
+ name: '@siduri-x/self',
69
+ organType: 'behavior',
70
+ version: '1.0.0',
71
+ displayName: 'Self (Active Persona & Directives)',
72
+ entrypoint: './dist/index.js',
73
+ factory: 'ActiveSelfCompiler',
74
+ configKey: 'behavior',
75
+ configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
76
+ environment: [],
77
+ services: [],
78
+ },
67
79
  };
68
80
  test('Composition A: Brain only', () => {
69
81
  const files = (0, generator_1.generateInstanceFiles)({
@@ -154,11 +166,11 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
154
166
  expect(pkg.dependencies['@siduri-x/voice']).toBeDefined();
155
167
  // Body & voice asset directories requested
156
168
  expect(files.createAssetsBodyDir).toBe(true);
157
- expect(files.createAssetsDirs).toContain('assets/body/companion-full');
158
- expect(files.createAssetsDirs).toContain('assets/voice/companion-full');
169
+ expect(files.createAssetsDirs).toContain('assets/body/default');
170
+ expect(files.createAssetsDirs).toContain('assets/voice/default');
159
171
  // README mentions Live2D model assets & prerequisites
160
- expect(files['README.md']).toContain('assets/body/companion-full');
161
- expect(files['README.md']).toContain('assets/voice/companion-full');
172
+ expect(files['README.md']).toContain('assets/body/default');
173
+ expect(files['README.md']).toContain('assets/voice/default');
162
174
  expect(files['README.md']).toContain('Prerequisites');
163
175
  // No docker compose generated (Docker completely removed, host-native runtime)
164
176
  expect(files['docker-compose.yml']).toBeUndefined();
@@ -207,4 +219,34 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
207
219
  expect(chatSlice).toContain("channel: 'direct'");
208
220
  expect(chatStreamSlice).toContain("channel: 'direct'");
209
221
  });
222
+ test('Composition F: Brain + Self persona manifest & Teach Mode endpoints', () => {
223
+ const files = (0, generator_1.generateInstanceFiles)({
224
+ name: 'BrattyCompanion',
225
+ selectedManifests: [MOCK_MANIFESTS.brain, MOCK_MANIFESTS.self],
226
+ organConfigs: {
227
+ behavior: {
228
+ provider: 'active_self',
229
+ mode: 'custom',
230
+ archetype: 'Bratty little sister',
231
+ ethos: 'bratty, warmth, smug',
232
+ directive: 'Speak as bratty little sister, calling me onii-chan',
233
+ selfPath: './assets/self/default.self',
234
+ },
235
+ },
236
+ });
237
+ // 1. Assets directory and .self file created in memory
238
+ expect(files.createAssetsDirs).toContain('assets/self');
239
+ expect(files['assets/self/default.self']).toBeDefined();
240
+ expect(files['assets/self/default.self']).toContain('archetype: "Bratty little sister"');
241
+ expect(files['assets/self/default.self']).toContain('ethos: "bratty, warmth, smug"');
242
+ expect(files['assets/self/default.self']).toContain('Speak as bratty little sister, calling me onii-chan');
243
+ expect(files['assets/self/default.self']).toContain('name: "BrattyCompanion"');
244
+ // 2. src/index.js imports scanDirective
245
+ const srcIndexJs = files['src/index.js'];
246
+ expect(srcIndexJs).toContain("import { ActiveSelfCompiler, SqliteSelfRepository, SelfPackageParser, scanDirective } from '@siduri-x/self';");
247
+ // 3. Teach Mode endpoints are generated
248
+ expect(srcIndexJs).toContain("pathname === '/teach/detected-self'");
249
+ expect(srcIndexJs).toContain("pathname === '/teach/upload-self'");
250
+ expect(srcIndexJs).toContain("pathname === '/teach/install-self'");
251
+ });
210
252
  });
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { OrganManifest } from './manifest';
3
- export declare const CLI_VERSION = "2.0.36";
3
+ export declare const CLI_VERSION = "2.0.38";
4
4
  import { colors } from './colors';
5
5
  export { colors };
6
6
  export declare function printHeader(): void;
package/dist/index.js CHANGED
@@ -61,7 +61,7 @@ const doctor_1 = require("./doctor");
61
61
  const db_1 = require("./db");
62
62
  const configurators_1 = require("./configurators");
63
63
  const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
64
- exports.CLI_VERSION = '2.0.36';
64
+ exports.CLI_VERSION = '2.0.38';
65
65
  const colors_1 = require("./colors");
66
66
  Object.defineProperty(exports, "colors", { enumerable: true, get: function () { return colors_1.colors; } });
67
67
  function printHeader() {
@@ -320,6 +320,25 @@ async function runCreateWizard(targetDir, options) {
320
320
  else if (files.createAssetsBodyDir) {
321
321
  await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'assets/body'), { recursive: true });
322
322
  }
323
+ // Write all additional generated files (such as assets/self/default.self or other assets)
324
+ const handledKeys = new Set([
325
+ 'package.json',
326
+ 'siduri.config.json',
327
+ 'siduri.schema.json',
328
+ '.env.example',
329
+ 'README.md',
330
+ 'src/index.js',
331
+ 'public/index.html',
332
+ 'createAssetsDirs',
333
+ 'createAssetsBodyDir',
334
+ ]);
335
+ for (const [relPath, content] of Object.entries(files)) {
336
+ if (handledKeys.has(relPath) || typeof content !== 'string')
337
+ continue;
338
+ const destPath = node_path_1.default.join(projectDir, relPath);
339
+ await (0, promises_1.mkdir)(node_path_1.default.dirname(destPath), { recursive: true });
340
+ await (0, promises_1.writeFile)(destPath, content, 'utf8');
341
+ }
323
342
  // If local knowledge archive is configured with a download URL, automatically fetch and unpack it
324
343
  const knowledgeConfig = organConfigs.knowledge || organConfigs['@siduri-x/knowledge'];
325
344
  if (knowledgeConfig?.pack?.mode === 'local' && knowledgeConfig?.pack?.archiveUrl) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxnus/siduri",
3
- "version": "2.0.36",
3
+ "version": "2.0.38",
4
4
  "description": "Experimental CLI for installing and configuring Siduri companions",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {