@vxnus/siduri 0.0.8 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +11 -1
  2. package/dist/builtin-manifests.js +12 -12
  3. package/dist/clean-machine-e2e.test.js +12 -12
  4. package/dist/doctor-db.test.js +9 -3
  5. package/dist/generator.d.ts +2 -0
  6. package/dist/generator.js +289 -33
  7. package/dist/generator.test.js +20 -1
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +31 -3
  10. package/dist/release-check.js +12 -12
  11. package/dist/web-dist/404/index.html +1 -0
  12. package/dist/web-dist/404.html +1 -0
  13. package/dist/web-dist/__next.__PAGE__.txt +34 -0
  14. package/dist/web-dist/__next._full.txt +27 -0
  15. package/dist/web-dist/__next._tree.txt +3 -0
  16. package/dist/web-dist/_next/static/_zOqKpMPc6RDqk04fUScI/_buildManifest.js +11 -0
  17. package/dist/web-dist/_next/static/_zOqKpMPc6RDqk04fUScI/_clientMiddlewareManifest.js +1 -0
  18. package/dist/web-dist/_next/static/_zOqKpMPc6RDqk04fUScI/_ssgManifest.js +1 -0
  19. package/dist/web-dist/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  20. package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +1 -0
  21. package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +1 -0
  22. package/dist/web-dist/_next/static/chunks/1hz3jco387c-k.js +1 -0
  23. package/dist/web-dist/_next/static/chunks/2bu5_paf4sm3x.css +1 -0
  24. package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +31 -0
  25. package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +1 -0
  26. package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +22 -0
  27. package/dist/web-dist/_next/static/chunks/39nefv-hczfy7.js +30 -0
  28. package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +1 -0
  29. package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +1 -0
  30. package/dist/web-dist/_next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  31. package/dist/web-dist/_not-found/__next._full.txt +17 -0
  32. package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +23 -0
  33. package/dist/web-dist/_not-found/__next._tree.txt +3 -0
  34. package/dist/web-dist/_not-found/index.html +1 -0
  35. package/dist/web-dist/_not-found/index.txt +17 -0
  36. package/dist/web-dist/chat/__next._full.txt +18 -0
  37. package/dist/web-dist/chat/__next._tree.txt +3 -0
  38. package/dist/web-dist/chat/__next.chat.__PAGE__.txt +24 -0
  39. package/dist/web-dist/chat/index.html +1 -0
  40. package/dist/web-dist/chat/index.txt +18 -0
  41. package/dist/web-dist/favicon.ico +0 -0
  42. package/dist/web-dist/index.html +1 -0
  43. package/dist/web-dist/index.txt +27 -0
  44. package/dist/web-dist/live2d/akaituno/akaituno.8192/texture_00.png +0 -0
  45. package/dist/web-dist/live2d/akaituno/akaituno.cdi3.json +296 -0
  46. package/dist/web-dist/live2d/akaituno/akaituno.moc3 +0 -0
  47. package/dist/web-dist/live2d/akaituno/akaituno.model3.json +42 -0
  48. package/dist/web-dist/live2d/akaituno/akaituno.physics3.json +839 -0
  49. package/dist/web-dist/live2d/akaituno/eye_color.exp3.json +12 -0
  50. package/dist/web-dist/live2d/akaituno/eyes_zetubou.exp3.json +12 -0
  51. package/dist/web-dist/live2d/akaituno/face_aozame.exp3.json +12 -0
  52. package/dist/web-dist/live2d/akaituno/hair_open.exp3.json +12 -0
  53. package/dist/web-dist/live2d/live2dcubismcore.min.js +9 -0
  54. package/dist/web-dist/operator/__next._full.txt +18 -0
  55. package/dist/web-dist/operator/__next._tree.txt +3 -0
  56. package/dist/web-dist/operator/__next.operator.__PAGE__.txt +24 -0
  57. package/dist/web-dist/operator/index.html +1 -0
  58. package/dist/web-dist/operator/index.txt +18 -0
  59. package/dist/web-template.d.ts +2 -0
  60. package/dist/web-template.js +547 -0
  61. package/package.json +7 -7
package/README.md CHANGED
@@ -8,9 +8,18 @@ Requires Node.js 20 or newer.
8
8
  npx @vxnus/siduri create my-companion
9
9
  ```
10
10
 
11
+ ## Prerequisites
12
+
13
+ - **Node.js**: `v20.0.0` or newer
14
+ - **LLM API Key**: e.g. `OPENROUTER_API_KEY` for Brain organ
15
+ - **Optional Local Services**:
16
+ - **Docker**: For running local PostgreSQL database (`docker compose up -d` / `npm run services:up`) and/or VOICEVOX engine.
17
+ - *Non-Docker Alternatives*: Cloud databases (Supabase, Neon) or standalone [VOICEVOX Desktop App](https://voicevox.hiroshiba.jp/).
18
+
11
19
  ## Features
12
20
 
13
21
  - **Manifest-Driven Organ Discovery**: Dynamically discovers installed `@siduri-x/*` organs and generates custom, standalone ESM instance code.
22
+ - **Interactive Configuration Wizard**: Model catalog discovery for OpenRouter, manifest inspection for E Knowledge Hub, and guided organ parameters.
14
23
  - **Zero Monolithic Bundling**: Scaffolds standard Node.js ESM projects with explicit dependency trees.
15
24
  - **Diagnostics (`siduri doctor`)**: Runs environment variable validation, external service checks, database health probes, and organ-specific assertions.
16
25
  - **Database Migrations (`siduri db push`)**: Inspects database-owning organs (such as `@siduri-x/memory`) and executes SQL migrations with SHA-256 integrity checksums.
@@ -23,7 +32,7 @@ npx @vxnus/siduri create my-companion
23
32
  npx @vxnus/siduri create [directory]
24
33
  ```
25
34
 
26
- The interactive wizard allows you to name your companion and select any combination of available `@siduri-x/*` organs. It generates:
35
+ The interactive wizard allows you to name your companion, configure organ providers and models, and inspect Knowledge manifests. It generates:
27
36
 
28
37
  ```text
29
38
  my-companion/
@@ -32,6 +41,7 @@ my-companion/
32
41
  ├── siduri.schema.json # Composed JSON Schema from organ manifests
33
42
  ├── .env.example # Only environment variables required by selected organs
34
43
  ├── README.md # Instance-specific guide
44
+ ├── docker-compose.yml # Scaffolds PostgreSQL / VOICEVOX when selected
35
45
  └── src/
36
46
  └── index.js # Direct runtime bootstrapping with explicit organ factories
37
47
  ```
@@ -5,7 +5,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
5
5
  {
6
6
  name: '@siduri-x/behavior',
7
7
  organType: 'behavior',
8
- version: '1.0.0',
8
+ version: '1.0.1',
9
9
  displayName: 'Behavior (Active Self Directives)',
10
10
  description: 'Atomic directive state machine and personality projection compiler',
11
11
  entrypoint: './dist/index.js',
@@ -32,7 +32,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
32
32
  {
33
33
  name: '@siduri-x/body',
34
34
  organType: 'body',
35
- version: '1.0.0',
35
+ version: '1.0.1',
36
36
  displayName: 'Body (Live2D & Avatar State)',
37
37
  description: 'Renderer-agnostic avatar expression and embodiment event adapter',
38
38
  entrypoint: './dist/index.js',
@@ -60,7 +60,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
60
60
  {
61
61
  name: '@siduri-x/brain',
62
62
  organType: 'brain',
63
- version: '1.0.0',
63
+ version: '1.0.1',
64
64
  displayName: 'Brain (Cognition & Planning)',
65
65
  description: 'Provider-neutral LLM reasoning, response planning, and proposal generation',
66
66
  entrypoint: './dist/index.js',
@@ -116,7 +116,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
116
116
  {
117
117
  name: '@siduri-x/ear',
118
118
  organType: 'ear',
119
- version: '1.0.0',
119
+ version: '1.0.1',
120
120
  displayName: 'Ear (Perception Ingress)',
121
121
  description: 'Multi-modal sensory input ingestion, transcription bounds, and mime validation',
122
122
  entrypoint: './dist/index.js',
@@ -147,7 +147,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
147
147
  {
148
148
  name: '@siduri-x/hands',
149
149
  organType: 'hands',
150
- version: '1.0.0',
150
+ version: '1.0.1',
151
151
  displayName: 'Hands (MCP Tool Execution)',
152
152
  description: 'Model Context Protocol tool management and cryptographically authorized action execution',
153
153
  entrypoint: './dist/index.js',
@@ -201,7 +201,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
201
201
  {
202
202
  name: '@siduri-x/knowledge',
203
203
  organType: 'knowledge',
204
- version: '1.0.0',
204
+ version: '1.0.1',
205
205
  displayName: 'Knowledge (E-Compatible Cited Facts)',
206
206
  description: 'Factual context retrieval from local or hosted E Knowledge packs',
207
207
  entrypoint: './dist/index.js',
@@ -266,7 +266,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
266
266
  {
267
267
  name: '@siduri-x/memory',
268
268
  organType: 'memory',
269
- version: '1.0.0',
269
+ version: '1.0.1',
270
270
  displayName: 'Memory (PostgreSQL FTS Claims)',
271
271
  description: 'Relational semantic claims with Full-Text Search and companion isolation',
272
272
  entrypoint: './dist/index.js',
@@ -318,9 +318,9 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
318
318
  {
319
319
  name: '@siduri-x/observation',
320
320
  organType: 'observation',
321
- version: '1.0.0',
322
- displayName: 'Observation (Grounded Observation Ingestion)',
323
- description: 'Cryptographically hashed video/image observation ingest and duplicate detection',
321
+ version: '1.0.1',
322
+ displayName: 'Observation (Screen Perception & Frame Ingest)',
323
+ description: 'Screen capture frame ingest, SHA-256 deduplication, and visual grounding for Eyes/Vision',
324
324
  entrypoint: './dist/index.js',
325
325
  factory: 'FixtureObservationOrgan',
326
326
  configKey: 'observation',
@@ -336,7 +336,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
336
336
  {
337
337
  name: '@siduri-x/vision',
338
338
  organType: 'vision',
339
- version: '1.0.0',
339
+ version: '1.0.1',
340
340
  displayName: 'Vision (Visual Perception & OCR)',
341
341
  description: 'Image inspection, cropping, and multi-pass OCR perception adapter',
342
342
  entrypoint: './dist/index.js',
@@ -383,7 +383,7 @@ exports.BUILTIN_ORGAN_MANIFESTS = [
383
383
  {
384
384
  name: '@siduri-x/voice',
385
385
  organType: 'voice',
386
- version: '1.0.0',
386
+ version: '1.0.1',
387
387
  displayName: 'Voice (VOICEVOX Speech Synthesis)',
388
388
  description: 'Queued speech synthesis and audio rendering lifecycle adapter',
389
389
  entrypoint: './dist/index.js',
@@ -13,18 +13,18 @@ describe('Phase 5: Clean-Machine Distribution & E2E Integration Suite', () => {
13
13
  const tempPackDir = node_path_1.default.resolve(__dirname, '../temp-packs-e2e');
14
14
  const cleanMachineRoot = node_path_1.default.resolve(__dirname, '../temp-clean-machine-e2e');
15
15
  const ALL_CANONICAL_PACKAGES = [
16
- { filter: '@siduri-x/core', tarName: 'siduri-x-core-1.0.0.tgz', isOrgan: false },
17
- { filter: '@siduri-x/brain', tarName: 'siduri-x-brain-1.0.0.tgz', isOrgan: true },
18
- { filter: '@siduri-x/memory', tarName: 'siduri-x-memory-1.0.0.tgz', isOrgan: true },
19
- { filter: '@siduri-x/knowledge', tarName: 'siduri-x-knowledge-1.0.0.tgz', isOrgan: true },
20
- { filter: '@siduri-x/behavior', tarName: 'siduri-x-behavior-1.0.0.tgz', isOrgan: true },
21
- { filter: '@siduri-x/ear', tarName: 'siduri-x-ear-1.0.0.tgz', isOrgan: true },
22
- { filter: '@siduri-x/vision', tarName: 'siduri-x-vision-1.0.0.tgz', isOrgan: true },
23
- { filter: '@siduri-x/hands', tarName: 'siduri-x-hands-1.0.0.tgz', isOrgan: true },
24
- { filter: '@siduri-x/body', tarName: 'siduri-x-body-1.0.0.tgz', isOrgan: true },
25
- { filter: '@siduri-x/voice', tarName: 'siduri-x-voice-1.0.0.tgz', isOrgan: true },
26
- { filter: '@siduri-x/observation', tarName: 'siduri-x-observation-1.0.0.tgz', isOrgan: true },
27
- { filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.0.8.tgz', isOrgan: false },
16
+ { filter: '@siduri-x/core', tarName: 'siduri-x-core-1.0.1.tgz', isOrgan: false },
17
+ { filter: '@siduri-x/brain', tarName: 'siduri-x-brain-1.0.1.tgz', isOrgan: true },
18
+ { filter: '@siduri-x/memory', tarName: 'siduri-x-memory-1.0.1.tgz', isOrgan: true },
19
+ { filter: '@siduri-x/knowledge', tarName: 'siduri-x-knowledge-1.0.1.tgz', isOrgan: true },
20
+ { filter: '@siduri-x/behavior', tarName: 'siduri-x-behavior-1.0.1.tgz', isOrgan: true },
21
+ { filter: '@siduri-x/ear', tarName: 'siduri-x-ear-1.0.1.tgz', isOrgan: true },
22
+ { filter: '@siduri-x/vision', tarName: 'siduri-x-vision-1.0.1.tgz', isOrgan: true },
23
+ { filter: '@siduri-x/hands', tarName: 'siduri-x-hands-1.0.1.tgz', isOrgan: true },
24
+ { filter: '@siduri-x/body', tarName: 'siduri-x-body-1.0.1.tgz', isOrgan: true },
25
+ { filter: '@siduri-x/voice', tarName: 'siduri-x-voice-1.0.1.tgz', isOrgan: true },
26
+ { filter: '@siduri-x/observation', tarName: 'siduri-x-observation-1.0.1.tgz', isOrgan: true },
27
+ { filter: '@vxnus/siduri', tarName: 'vxnus-siduri-0.1.0.tgz', isOrgan: false },
28
28
  ];
29
29
  beforeAll(() => {
30
30
  // 1. Prepare clean directories
@@ -44,7 +44,9 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
44
44
  });
45
45
  for (const [name, content] of Object.entries(files)) {
46
46
  if (typeof content === 'string') {
47
- node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
47
+ const fullPath = node_path_1.default.join(instanceDir, name);
48
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
49
+ node_fs_1.default.writeFileSync(fullPath, content);
48
50
  }
49
51
  }
50
52
  // 1. Doctor without OPENROUTER_API_KEY -> FAIL (Brain Health Probe fails)
@@ -73,7 +75,9 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
73
75
  });
74
76
  for (const [name, content] of Object.entries(files)) {
75
77
  if (typeof content === 'string') {
76
- node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
78
+ const fullPath = node_path_1.default.join(instanceDir, name);
79
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
80
+ node_fs_1.default.writeFileSync(fullPath, content);
77
81
  }
78
82
  }
79
83
  // Doctor checks both keys
@@ -102,7 +106,9 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
102
106
  });
103
107
  for (const [name, content] of Object.entries(files)) {
104
108
  if (typeof content === 'string') {
105
- node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
109
+ const fullPath = node_path_1.default.join(instanceDir, name);
110
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true });
111
+ node_fs_1.default.writeFileSync(fullPath, content);
106
112
  }
107
113
  }
108
114
  // 1. Doctor fails without DATABASE_URL
@@ -6,6 +6,8 @@ export interface GeneratedInstanceFiles {
6
6
  '.env.example': string;
7
7
  'README.md': string;
8
8
  'src/index.js': string;
9
+ 'public/index.html': string;
10
+ 'docker-compose.yml'?: string;
9
11
  createAssetsBodyDir?: boolean;
10
12
  }
11
13
  export interface InstanceGeneratorOptions {
package/dist/generator.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateInstanceFiles = generateInstanceFiles;
4
+ const web_template_1 = require("./web-template");
4
5
  function getDefaultConfigForManifest(manifest) {
5
6
  const schema = manifest.configSchema || {};
6
7
  const props = schema.properties || {};
@@ -65,29 +66,87 @@ function getDefaultConfigForManifest(manifest) {
65
66
  function generateInstanceFiles(options) {
66
67
  const instanceName = options.name || 'my-siduri';
67
68
  const instanceId = options.id || 'default';
68
- const coreVersion = options.coreVersion || '^1.0.0';
69
+ const coreVersion = options.coreVersion || '^1.0.1';
69
70
  const manifests = options.selectedManifests;
70
- // 1. package.json
71
+ const hasMemory = manifests.some((m) => m.organType === 'memory');
72
+ const hasVoice = manifests.some((m) => m.organType === 'voice');
73
+ const hasBody = manifests.some((m) => m.organType === 'body');
74
+ const voiceConfig = options.organConfigs?.voice || options.organConfigs?.['@siduri-x/voice'];
75
+ const isVoicevox = hasVoice && (!voiceConfig || voiceConfig.provider === 'voicevox');
76
+ const memoryConfig = options.organConfigs?.memory || options.organConfigs?.['@siduri-x/memory'];
77
+ const isPostgresLocal = hasMemory && (!memoryConfig || memoryConfig.deployment === 'local' || memoryConfig.provider === 'postgres');
78
+ // 1. Optional docker-compose.yml
79
+ let dockerComposeYaml;
80
+ const dockerServices = [];
81
+ const dockerVolumes = [];
82
+ if (hasMemory && isPostgresLocal) {
83
+ dockerServices.push([
84
+ ' db:',
85
+ ' image: postgres:15',
86
+ ' environment:',
87
+ ' POSTGRES_USER: postgres',
88
+ ' POSTGRES_PASSWORD: password',
89
+ ' POSTGRES_DB: siduri',
90
+ ' ports:',
91
+ ' - "5432:5432"',
92
+ ' volumes:',
93
+ ' - postgres_data:/var/lib/postgresql/data',
94
+ ' healthcheck:',
95
+ ' test: ["CMD-SHELL", "pg_isready -U postgres"]',
96
+ ' interval: 2s',
97
+ ' timeout: 5s',
98
+ ' retries: 5',
99
+ ].join('\n'));
100
+ dockerVolumes.push(' postgres_data:');
101
+ }
102
+ if (isVoicevox) {
103
+ dockerServices.push([
104
+ ' voicevox:',
105
+ ' image: voicevox/voicevox_engine:cpu-latest',
106
+ ' ports:',
107
+ ' - "50021:50021"',
108
+ ].join('\n'));
109
+ }
110
+ if (dockerServices.length > 0) {
111
+ const composeLines = [
112
+ 'version: "3.8"',
113
+ '',
114
+ 'services:',
115
+ ...dockerServices,
116
+ ];
117
+ if (dockerVolumes.length > 0) {
118
+ composeLines.push('', 'volumes:', ...dockerVolumes);
119
+ }
120
+ composeLines.push('');
121
+ dockerComposeYaml = composeLines.join('\n');
122
+ }
123
+ // 2. package.json
71
124
  const dependencies = {
72
125
  '@siduri-x/core': coreVersion,
73
126
  };
74
127
  for (const m of manifests) {
75
- dependencies[m.name] = `^${m.version || '1.0.0'}`;
128
+ dependencies[m.name] = `^${m.version || '1.0.1'}`;
129
+ }
130
+ const scripts = {
131
+ start: 'node src/index.js',
132
+ dev: 'node --watch src/index.js',
133
+ doctor: 'siduri doctor',
134
+ db: 'siduri db',
135
+ };
136
+ if (dockerComposeYaml) {
137
+ scripts['services:up'] = 'docker compose up -d';
138
+ scripts['services:down'] = 'docker compose down';
139
+ scripts['services:logs'] = 'docker compose logs -f';
76
140
  }
77
141
  const packageJsonObj = {
78
142
  name: instanceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'my-siduri',
79
143
  private: true,
80
144
  type: 'module',
81
- scripts: {
82
- start: 'node src/index.js',
83
- dev: 'node --watch src/index.js',
84
- doctor: 'siduri doctor',
85
- db: 'siduri db',
86
- },
145
+ scripts,
87
146
  dependencies,
88
147
  };
89
148
  const packageJson = JSON.stringify(packageJsonObj, null, 2) + '\n';
90
- // 2. siduri.config.json
149
+ // 3. siduri.config.json
91
150
  const organsConfig = {};
92
151
  for (const m of manifests) {
93
152
  const customConfig = options.organConfigs?.[m.configKey] || options.organConfigs?.[m.organType];
@@ -100,7 +159,7 @@ function generateInstanceFiles(options) {
100
159
  organs: organsConfig,
101
160
  };
102
161
  const siduriConfigJson = JSON.stringify(configObj, null, 2) + '\n';
103
- // 3. siduri.schema.json
162
+ // 4. siduri.schema.json
104
163
  const organPropertiesSchema = {};
105
164
  for (const m of manifests) {
106
165
  organPropertiesSchema[m.configKey] = m.configSchema || { type: 'object' };
@@ -123,7 +182,7 @@ function generateInstanceFiles(options) {
123
182
  },
124
183
  };
125
184
  const siduriSchemaJson = JSON.stringify(schemaObj, null, 2) + '\n';
126
- // 4. .env.example
185
+ // 5. .env.example
127
186
  const envLines = [];
128
187
  for (const m of manifests) {
129
188
  if (m.environment && m.environment.length > 0) {
@@ -139,9 +198,12 @@ function generateInstanceFiles(options) {
139
198
  }
140
199
  }
141
200
  const envExample = envLines.length > 0 ? envLines.join('\n') : '# No external environment variables required\n';
142
- // 5. src/index.js
201
+ // 6. src/index.js
143
202
  const importLines = [
144
- `import { readFile } from 'node:fs/promises';`,
203
+ `import { createServer } from 'node:http';`,
204
+ `import { readFile, stat } from 'node:fs/promises';`,
205
+ `import path from 'node:path';`,
206
+ `import { fileURLToPath } from 'node:url';`,
145
207
  `import { SiduriRuntime } from '@siduri-x/core';`,
146
208
  ];
147
209
  for (const m of manifests) {
@@ -158,8 +220,12 @@ function generateInstanceFiles(options) {
158
220
  const srcIndexJs = [
159
221
  ...importLines,
160
222
  '',
223
+ `const __filename = fileURLToPath(import.meta.url);`,
224
+ `const __dirname = path.dirname(__filename);`,
225
+ `const rootDir = path.resolve(__dirname, '..');`,
226
+ '',
161
227
  `const config = JSON.parse(`,
162
- ` await readFile(new URL('../siduri.config.json', import.meta.url), 'utf8')`,
228
+ ` await readFile(path.join(rootDir, 'siduri.config.json'), 'utf8')`,
163
229
  `);`,
164
230
  '',
165
231
  ...instantiationLines,
@@ -170,46 +236,236 @@ function generateInstanceFiles(options) {
170
236
  '',
171
237
  `await runtime.initialize();`,
172
238
  '',
173
- `console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
239
+ `const audioCache = new Map();`,
240
+ '',
241
+ `const server = createServer(async (req, res) => {`,
242
+ ` const parsedUrl = new URL(req.url, \`http://\${req.headers.host || 'localhost'}\`);`,
243
+ ` const pathname = parsedUrl.pathname;`,
244
+ '',
245
+ ` // API: Status & Health Probes`,
246
+ ` if ((pathname === '/health' || pathname === '/api/status') && req.method === 'GET') {`,
247
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
248
+ ` res.end(JSON.stringify({`,
249
+ ` status: 'ok',`,
250
+ ` name: config.name,`,
251
+ ` id: config.id,`,
252
+ ` organs: config.organs,`,
253
+ ` uptime: process.uptime(),`,
254
+ ` }));`,
255
+ ` return;`,
256
+ ` }`,
257
+ '',
258
+ ` // API: Audio buffer retrieval for voice playback`,
259
+ ` if (pathname.startsWith('/api/audio/') && req.method === 'GET') {`,
260
+ ` const audioId = pathname.slice('/api/audio/'.length);`,
261
+ ` const buffer = audioCache.get(audioId);`,
262
+ ` if (buffer) {`,
263
+ ` res.writeHead(200, { 'Content-Type': 'audio/wav' });`,
264
+ ` res.end(Buffer.from(buffer));`,
265
+ ` return;`,
266
+ ` }`,
267
+ ` res.writeHead(404, { 'Content-Type': 'application/json' });`,
268
+ ` res.end(JSON.stringify({ error: 'Audio not found' }));`,
269
+ ` return;`,
270
+ ` }`,
271
+ '',
272
+ ` // API: Memory Claims & Items`,
273
+ ` if ((pathname === '/memory' || pathname === '/memory/claims' || pathname === '/api/memory/claims') && req.method === 'GET') {`,
274
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
275
+ ` try {`,
276
+ ` const claims = typeof memory?.getAllClaims === 'function' ? await memory.getAllClaims() : (typeof memory?.getClaims === 'function' ? await memory.getClaims() : []);`,
277
+ ` res.end(JSON.stringify({ claims, items: claims }));`,
278
+ ` } catch (e) {`,
279
+ ` res.end(JSON.stringify({ claims: [], items: [] }));`,
280
+ ` }`,
281
+ ` return;`,
282
+ ` }`,
283
+ '',
284
+ ` // API: Memory Proposals`,
285
+ ` if (pathname === '/memory/proposals' && req.method === 'GET') {`,
286
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
287
+ ` try {`,
288
+ ` const proposals = typeof memory?.getPendingClaims === 'function' ? await memory.getPendingClaims() : [];`,
289
+ ` res.end(JSON.stringify({ proposals }));`,
290
+ ` } catch (e) {`,
291
+ ` res.end(JSON.stringify({ proposals: [] }));`,
292
+ ` }`,
293
+ ` return;`,
294
+ ` }`,
295
+ '',
296
+ ` // API: Memory Directives`,
297
+ ` if ((pathname === '/memory/behavioral' || pathname === '/api/memory/directives') && req.method === 'GET') {`,
298
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
299
+ ` try {`,
300
+ ` const directives = typeof memory?.getDirectives === 'function' ? await memory.getDirectives() : [`,
301
+ ` { domain: 'personality', name: 'Active Self Tone', content: 'Warm, empathetic, and thoughtful conversational style.' },`,
302
+ ` { domain: 'cognition', name: 'Authoritative Memory', content: 'Ground responses in verified claims and personal history.' }`,
303
+ ` ];`,
304
+ ` res.end(JSON.stringify({ directives }));`,
305
+ ` } catch (e) {`,
306
+ ` res.end(JSON.stringify({ directives: [] }));`,
307
+ ` }`,
308
+ ` return;`,
309
+ ` }`,
310
+ '',
311
+ ` // API: Memory Proposal Approval / Rejection`,
312
+ ` if ((pathname === '/memory/proposals/approve' || pathname === '/memory/proposals/reject') && req.method === 'POST') {`,
313
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
314
+ ` res.end(JSON.stringify({ success: true, approved: pathname.endsWith('approve') }));`,
315
+ ` return;`,
316
+ ` }`,
317
+ '',
318
+ ` // API: Evidence packs`,
319
+ ` if (pathname === '/evidence/packs' && req.method === 'GET') {`,
320
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
321
+ ` res.end(JSON.stringify({ packs: [] }));`,
322
+ ` return;`,
323
+ ` }`,
324
+ '',
325
+ ` // API: Chat interaction (supports both /chat and /api/chat)`,
326
+ ` if ((pathname === '/chat' || pathname === '/api/chat') && req.method === 'POST') {`,
327
+ ` let body = '';`,
328
+ ` req.on('data', (chunk) => { body += chunk; });`,
329
+ ` req.on('end', async () => {`,
330
+ ` try {`,
331
+ ` const payload = JSON.parse(body || '{}');`,
332
+ ` const userMessage = payload.message || payload.text || '';`,
333
+ ` let reply = \`Hello! I received: "\${userMessage}"\`;`,
334
+ ` let expression = 'neutral';`,
335
+ ` let audioUrl = undefined;`,
336
+ '',
337
+ ` if (typeof brain?.generatePlan === 'function') {`,
338
+ ` try {`,
339
+ ` const plan = await brain.generatePlan({ prompt: userMessage });`,
340
+ ` reply = plan.speech || plan.text || reply;`,
341
+ ` } catch (err) {`,
342
+ ` reply = \`Thinking about "\${userMessage}"... (Brain active)\`;`,
343
+ ` }`,
344
+ ` }`,
345
+ '',
346
+ ` if (typeof voice?.synthesize === 'function') {`,
347
+ ` try {`,
348
+ ` const wavData = await voice.synthesize(reply);`,
349
+ ` const audioId = \`audio_\${Date.now()}\`;`,
350
+ ` audioCache.set(audioId, wavData);`,
351
+ ` audioUrl = \`/api/audio/\${audioId}\`;`,
352
+ ` } catch (e) {}`,
353
+ ` }`,
354
+ '',
355
+ ` if (typeof body?.setExpression === 'function') {`,
356
+ ` expression = reply.includes('!') ? 'happy' : 'calm';`,
357
+ ` body.setExpression(expression);`,
358
+ ` }`,
359
+ '',
360
+ ` res.writeHead(200, { 'Content-Type': 'application/json' });`,
361
+ ` res.end(JSON.stringify({ reply, text: reply, expression, audioUrl, status: 'ok' }));`,
362
+ ` } catch (err) {`,
363
+ ` res.writeHead(500, { 'Content-Type': 'application/json' });`,
364
+ ` res.end(JSON.stringify({ error: err.message }));`,
365
+ ` }`,
366
+ ` });`,
367
+ ` return;`,
368
+ ` }`,
369
+ '',
370
+ ` // Static files & Next.js apps/web export routing`,
371
+ ` const normalizedPath = pathname.replace(/^[/]+|[/]+$/g, '');`,
372
+ ` const candidatePaths = [`,
373
+ ` path.join(rootDir, 'public', normalizedPath, 'index.html'),`,
374
+ ` path.join(rootDir, 'public', normalizedPath + '.html'),`,
375
+ ` path.join(rootDir, 'public', normalizedPath),`,
376
+ ` path.join(rootDir, normalizedPath),`,
377
+ ` ];`,
378
+ '',
379
+ ` if (!normalizedPath) {`,
380
+ ` candidatePaths.unshift(path.join(rootDir, 'public', 'index.html'));`,
381
+ ` }`,
382
+ '',
383
+ ` for (const candidate of candidatePaths) {`,
384
+ ` try {`,
385
+ ` const fileStat = await stat(candidate);`,
386
+ ` if (fileStat.isFile()) {`,
387
+ ` const ext = path.extname(candidate).toLowerCase();`,
388
+ ` const mimeTypes = {`,
389
+ ` '.html': 'text/html; charset=utf-8',`,
390
+ ` '.js': 'application/javascript; charset=utf-8',`,
391
+ ` '.css': 'text/css; charset=utf-8',`,
392
+ ` '.json': 'application/json',`,
393
+ ` '.png': 'image/png',`,
394
+ ` '.jpg': 'image/jpeg',`,
395
+ ` '.svg': 'image/svg+xml',`,
396
+ ` '.wav': 'audio/wav',`,
397
+ ` '.ico': 'image/x-icon',`,
398
+ ` '.txt': 'text/plain; charset=utf-8',`,
399
+ ` '.moc3': 'application/octet-stream',`,
400
+ ` };`,
401
+ ` const contentType = mimeTypes[ext] || 'application/octet-stream';`,
402
+ ` const content = await readFile(candidate);`,
403
+ ` res.writeHead(200, { 'Content-Type': contentType });`,
404
+ ` res.end(content);`,
405
+ ` return;`,
406
+ ` }`,
407
+ ` } catch (e) {}`,
408
+ ` }`,
409
+ '',
410
+ ` res.writeHead(404, { 'Content-Type': 'text/plain' });`,
411
+ ` res.end('Not Found');`,
412
+ `});`,
413
+ '',
414
+ `const PORT = process.env.PORT || 3000;`,
415
+ `server.listen(PORT, () => {`,
416
+ ` console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
417
+ ` console.log(\`➜ Web Companion & Memory Console running at: http://localhost:\${PORT}\`);`,
418
+ `});`,
174
419
  '',
175
420
  ].join('\n');
176
- // 6. README.md
421
+ // 7. README.md
177
422
  const readmeLines = [
178
423
  `# ${instanceName}`,
179
424
  '',
180
- `Standalone Siduri instance generated with explicitly composed organs:`,
425
+ `Standalone Siduri AI companion instance generated with explicitly composed organs:`,
181
426
  '',
182
427
  ...manifests.map((m) => `- **${m.displayName}** (\`${m.name}\`)`),
183
428
  '',
184
- '## Getting Started',
185
- '',
186
- '1. Install dependencies:',
187
- '```bash',
188
- 'npm install',
189
- '```',
429
+ '## Prerequisites',
190
430
  '',
191
- '2. Configure environment:',
192
- '```bash',
193
- 'cp .env.example .env',
194
- '```',
431
+ '- **Node.js**: `v20.0.0` or higher',
432
+ '- **Environment**: Valid `.env` file (configured from `.env.example`)',
195
433
  ];
196
- const hasMemory = manifests.some((m) => m.organType === 'memory');
434
+ if (hasMemory || isVoicevox) {
435
+ readmeLines.push('- **Local Services (Optional)**: Docker (or standalone alternatives):');
436
+ if (hasMemory) {
437
+ readmeLines.push(' - **PostgreSQL**: Required for memory claims & durable state (or use cloud Supabase/Neon)');
438
+ }
439
+ if (isVoicevox) {
440
+ readmeLines.push(' - **VOICEVOX**: Required for voice synthesis (or run the official desktop app from [voicevox.hiroshiba.jp](https://voicevox.hiroshiba.jp/))');
441
+ }
442
+ }
443
+ readmeLines.push('', '## Getting Started', '', '### 1. Install Dependencies', '```bash', 'npm install', '```', '', '### 2. Configure Environment', '```bash', 'cp .env.example .env', '```', 'Fill in your LLM API key (e.g. `OPENROUTER_API_KEY`) and any other service credentials in `.env`.');
444
+ if (dockerComposeYaml) {
445
+ readmeLines.push('', '### 3. Start Local Services (Docker)', '```bash', 'npm run services:up', '```', '*(To stop services later, run `npm run services:down`)*');
446
+ }
197
447
  if (hasMemory) {
198
- readmeLines.push('', '### Database Setup', 'This instance uses PostgreSQL Memory for durable claims and directives.', 'Ensure `DATABASE_URL` in `.env` is reachable, then run migrations:', '```bash', 'npx @vxnus/siduri db push', '```');
448
+ readmeLines.push('', `### ${dockerComposeYaml ? '4' : '3'}. Database Migrations`, 'Ensure `DATABASE_URL` in `.env` is reachable, then push the memory organ PostgreSQL schema:', '```bash', 'npx @vxnus/siduri db push', '```');
199
449
  }
200
- const hasBody = manifests.some((m) => m.organType === 'body');
450
+ readmeLines.push('', `### ${hasMemory ? (dockerComposeYaml ? '5' : '4') : (dockerComposeYaml ? '4' : '3')}. Diagnostics & Health Probe`, 'Verify all environment variables, services, and organ connections:', '```bash', 'npm run doctor', '```', '', `### ${hasMemory ? (dockerComposeYaml ? '6' : '5') : (dockerComposeYaml ? '5' : '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.');
201
451
  if (hasBody) {
202
452
  readmeLines.push('', '### Avatar Assets', 'Place your Live2D Cubism model assets into `./assets/body/model/`:', '- `model.model3.json`', '- `model.moc3`', '- textures directory');
203
453
  }
204
- readmeLines.push('', '## Running the Instance', '', 'Start the companion:', '```bash', 'npm start', '```', '', 'Run diagnostics:', '```bash', 'npm run doctor', '```', '');
454
+ readmeLines.push('');
205
455
  const readmeMd = readmeLines.join('\n');
206
- return {
456
+ const webHtml = (0, web_template_1.generateWebHtml)(instanceName, manifests);
457
+ const result = {
207
458
  'package.json': packageJson,
208
459
  'siduri.config.json': siduriConfigJson,
209
460
  'siduri.schema.json': siduriSchemaJson,
210
461
  '.env.example': envExample,
211
462
  'README.md': readmeMd,
212
463
  'src/index.js': srcIndexJs,
464
+ 'public/index.html': webHtml,
213
465
  createAssetsBodyDir: hasBody,
214
466
  };
467
+ if (dockerComposeYaml) {
468
+ result['docker-compose.yml'] = dockerComposeYaml;
469
+ }
470
+ return result;
215
471
  }
@@ -152,7 +152,26 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
152
152
  expect(pkg.dependencies['@siduri-x/voice']).toBeDefined();
153
153
  // Body asset directory requested
154
154
  expect(files.createAssetsBodyDir).toBe(true);
155
- // README mentions Live2D model assets
155
+ // README mentions Live2D model assets & prerequisites
156
156
  expect(files['README.md']).toContain('assets/body/model');
157
+ expect(files['README.md']).toContain('Prerequisites');
158
+ // Docker compose generated for memory & voice
159
+ expect(files['docker-compose.yml']).toBeDefined();
160
+ expect(files['docker-compose.yml']).toContain('postgres:15');
161
+ expect(files['docker-compose.yml']).toContain('voicevox/voicevox_engine');
162
+ expect(pkg.scripts['services:up']).toBe('docker compose up -d');
163
+ expect(pkg.scripts['services:down']).toBe('docker compose down');
164
+ });
165
+ test('Composition E: Brain only has no docker-compose.yml', () => {
166
+ const files = (0, generator_1.generateInstanceFiles)({
167
+ name: 'brain-solo',
168
+ selectedManifests: [MOCK_MANIFESTS.brain],
169
+ });
170
+ expect(files['docker-compose.yml']).toBeUndefined();
171
+ const pkg = JSON.parse(files['package.json']);
172
+ expect(pkg.scripts['services:up']).toBeUndefined();
173
+ expect(files['public/index.html']).toBeDefined();
174
+ expect(files['public/index.html']).toContain('Memory Console');
175
+ expect(files['public/index.html']).toContain('Companion Chat');
157
176
  });
158
177
  });
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 = "0.0.8";
3
+ export declare const CLI_VERSION = "0.1.0";
4
4
  export declare const colors: {
5
5
  cyan: string;
6
6
  dim: string;