@vxnus/siduri 0.1.8 → 0.1.10
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.
- package/dist/builtin-manifests.d.ts +2 -0
- package/dist/builtin-manifests.js +492 -0
- package/dist/clean-machine-e2e.test.d.ts +1 -0
- package/dist/clean-machine-e2e.test.js +236 -0
- package/dist/configurators/behavior.d.ts +2 -0
- package/dist/configurators/behavior.js +36 -0
- package/dist/configurators/body.d.ts +2 -0
- package/dist/configurators/body.js +60 -0
- package/dist/configurators/brain.d.ts +6 -0
- package/dist/configurators/brain.js +80 -0
- package/dist/configurators/ear.d.ts +2 -0
- package/dist/configurators/ear.js +28 -0
- package/dist/configurators/hands.d.ts +2 -0
- package/dist/configurators/hands.js +27 -0
- package/dist/configurators/index.d.ts +24 -0
- package/dist/configurators/index.js +78 -0
- package/dist/configurators/knowledge.d.ts +6 -0
- package/dist/configurators/knowledge.js +100 -0
- package/dist/configurators/memory.d.ts +2 -0
- package/dist/configurators/memory.js +44 -0
- package/dist/configurators/mouth.d.ts +2 -0
- package/dist/configurators/mouth.js +15 -0
- package/dist/configurators/observation.d.ts +2 -0
- package/dist/configurators/observation.js +12 -0
- package/dist/configurators/types.d.ts +12 -0
- package/dist/configurators/types.js +2 -0
- package/dist/configurators/vision.d.ts +2 -0
- package/dist/configurators/vision.js +39 -0
- package/dist/configurators/voice.d.ts +2 -0
- package/dist/configurators/voice.js +477 -0
- package/dist/configurators.test.d.ts +1 -0
- package/dist/configurators.test.js +331 -0
- package/dist/db.d.ts +25 -0
- package/dist/db.js +152 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.js +87 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +69 -0
- package/dist/doctor-db.test.d.ts +1 -0
- package/dist/doctor-db.test.js +141 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +225 -0
- package/dist/generator.d.ts +21 -0
- package/dist/generator.js +666 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +197 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +401 -0
- package/dist/manifest.d.ts +33 -0
- package/dist/manifest.js +40 -0
- package/dist/providers/knowledge-hub.d.ts +37 -0
- package/dist/providers/knowledge-hub.js +141 -0
- package/dist/providers/openrouter.d.ts +32 -0
- package/dist/providers/openrouter.js +201 -0
- package/dist/release-check.d.ts +9 -0
- package/dist/release-check.js +133 -0
- package/dist/runtime-parity.test.d.ts +1 -0
- package/dist/runtime-parity.test.js +140 -0
- package/dist/web-template.d.ts +2 -0
- package/dist/web-template.js +557 -0
- package/package.json +1 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const generator_1 = require("./generator");
|
|
4
|
+
describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
5
|
+
const MOCK_MANIFESTS = {
|
|
6
|
+
brain: {
|
|
7
|
+
name: '@siduri-x/brain',
|
|
8
|
+
organType: 'brain',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
displayName: 'Brain (Cognition & Planning)',
|
|
11
|
+
entrypoint: './dist/index.js',
|
|
12
|
+
factory: 'OpenRouterBrain',
|
|
13
|
+
configKey: 'brain',
|
|
14
|
+
configSchema: { type: 'object', properties: { provider: { type: 'string' }, model: { type: 'string' } } },
|
|
15
|
+
environment: [{ name: 'OPENROUTER_API_KEY', required: true, secret: true }],
|
|
16
|
+
services: [{ name: 'LLM Inference', kind: 'http_service' }],
|
|
17
|
+
},
|
|
18
|
+
hands: {
|
|
19
|
+
name: '@siduri-x/hands',
|
|
20
|
+
organType: 'hands',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
displayName: 'Hands (MCP Tool Execution)',
|
|
23
|
+
entrypoint: './dist/index.js',
|
|
24
|
+
factory: 'DefaultHandsOrgan',
|
|
25
|
+
configKey: 'hands',
|
|
26
|
+
configSchema: { type: 'object', properties: { defaultTimeoutMs: { type: 'number' } } },
|
|
27
|
+
environment: [{ name: 'ACTION_POLICY_SECRET', required: true, secret: true }],
|
|
28
|
+
services: [],
|
|
29
|
+
},
|
|
30
|
+
memory: {
|
|
31
|
+
name: '@siduri-x/memory',
|
|
32
|
+
organType: 'memory',
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
displayName: 'Memory (PostgreSQL FTS Claims)',
|
|
35
|
+
entrypoint: './dist/index.js',
|
|
36
|
+
factory: 'PostgresMemoryOrgan',
|
|
37
|
+
configKey: 'memory',
|
|
38
|
+
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
39
|
+
environment: [{ name: 'DATABASE_URL', required: true, secret: true }],
|
|
40
|
+
services: [{ name: 'PostgreSQL', kind: 'database' }],
|
|
41
|
+
database: { engine: 'postgres', migrationsDir: './migrations' },
|
|
42
|
+
},
|
|
43
|
+
body: {
|
|
44
|
+
name: '@siduri-x/body',
|
|
45
|
+
organType: 'body',
|
|
46
|
+
version: '1.0.0',
|
|
47
|
+
displayName: 'Body (Live2D & Avatar State)',
|
|
48
|
+
entrypoint: './dist/index.js',
|
|
49
|
+
factory: 'NeutralBodyOrgan',
|
|
50
|
+
configKey: 'body',
|
|
51
|
+
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
52
|
+
environment: [],
|
|
53
|
+
services: [],
|
|
54
|
+
},
|
|
55
|
+
voice: {
|
|
56
|
+
name: '@siduri-x/voice',
|
|
57
|
+
organType: 'voice',
|
|
58
|
+
version: '1.0.0',
|
|
59
|
+
displayName: 'Voice (VOICEVOX Speech Synthesis)',
|
|
60
|
+
entrypoint: './dist/index.js',
|
|
61
|
+
factory: 'VoiceAdapter',
|
|
62
|
+
configKey: 'voice',
|
|
63
|
+
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
64
|
+
environment: [{ name: 'VOICEVOX_URL', default: 'http://localhost:50021' }],
|
|
65
|
+
services: [{ name: 'VOICEVOX', kind: 'http_service' }],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
test('Composition A: Brain only', () => {
|
|
69
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
70
|
+
name: 'brain-only-agent',
|
|
71
|
+
selectedManifests: [MOCK_MANIFESTS.brain],
|
|
72
|
+
});
|
|
73
|
+
const pkg = JSON.parse(files['package.json']);
|
|
74
|
+
expect(pkg.dependencies['@siduri-x/core']).toBeDefined();
|
|
75
|
+
expect(pkg.dependencies['@siduri-x/brain']).toBeDefined();
|
|
76
|
+
expect(pkg.dependencies['@siduri-x/memory']).toBeUndefined();
|
|
77
|
+
expect(pkg.dependencies['@siduri-x/hands']).toBeUndefined();
|
|
78
|
+
expect(pkg.dependencies['@siduri-x/voice']).toBeUndefined();
|
|
79
|
+
expect(pkg.dependencies['@siduri-x/body']).toBeUndefined();
|
|
80
|
+
// src/index.js
|
|
81
|
+
expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
|
|
82
|
+
expect(files['src/index.js']).not.toContain('@siduri-x/hands');
|
|
83
|
+
expect(files['src/index.js']).not.toContain('@siduri-x/memory');
|
|
84
|
+
expect(files['src/index.js']).not.toContain('siduri-runtime.js');
|
|
85
|
+
// siduri.config.json
|
|
86
|
+
const config = JSON.parse(files['siduri.config.json']);
|
|
87
|
+
expect(config.organs.brain).toBeDefined();
|
|
88
|
+
expect(config.organs.hands).toBeUndefined();
|
|
89
|
+
expect(config.organs.memory).toBeUndefined();
|
|
90
|
+
// .env.example
|
|
91
|
+
expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
|
|
92
|
+
expect(files['.env.example']).not.toContain('DATABASE_URL');
|
|
93
|
+
expect(files['.env.example']).not.toContain('ACTION_POLICY_SECRET');
|
|
94
|
+
// No body asset scaffold
|
|
95
|
+
expect(files.createAssetsBodyDir).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
test('Composition B: Brain + Hands', () => {
|
|
98
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
99
|
+
name: 'coding-agent',
|
|
100
|
+
selectedManifests: [MOCK_MANIFESTS.brain, MOCK_MANIFESTS.hands],
|
|
101
|
+
});
|
|
102
|
+
const pkg = JSON.parse(files['package.json']);
|
|
103
|
+
expect(pkg.dependencies['@siduri-x/core']).toBeDefined();
|
|
104
|
+
expect(pkg.dependencies['@siduri-x/brain']).toBeDefined();
|
|
105
|
+
expect(pkg.dependencies['@siduri-x/hands']).toBeDefined();
|
|
106
|
+
expect(pkg.dependencies['@siduri-x/memory']).toBeUndefined();
|
|
107
|
+
// src/index.js
|
|
108
|
+
expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@siduri-x/brain'");
|
|
109
|
+
expect(files['src/index.js']).toContain("import { DefaultHandsOrgan } from '@siduri-x/hands'");
|
|
110
|
+
expect(files['src/index.js']).not.toContain('@siduri-x/memory');
|
|
111
|
+
// .env.example
|
|
112
|
+
expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
|
|
113
|
+
expect(files['.env.example']).toContain('ACTION_POLICY_SECRET');
|
|
114
|
+
expect(files['.env.example']).not.toContain('DATABASE_URL');
|
|
115
|
+
expect(files.createAssetsBodyDir).toBe(false);
|
|
116
|
+
});
|
|
117
|
+
test('Composition C: Brain + Memory', () => {
|
|
118
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
119
|
+
name: 'memory-agent',
|
|
120
|
+
selectedManifests: [MOCK_MANIFESTS.brain, MOCK_MANIFESTS.memory],
|
|
121
|
+
});
|
|
122
|
+
const pkg = JSON.parse(files['package.json']);
|
|
123
|
+
expect(pkg.dependencies['@siduri-x/core']).toBeDefined();
|
|
124
|
+
expect(pkg.dependencies['@siduri-x/brain']).toBeDefined();
|
|
125
|
+
expect(pkg.dependencies['@siduri-x/memory']).toBeDefined();
|
|
126
|
+
expect(pkg.dependencies['@siduri-x/hands']).toBeUndefined();
|
|
127
|
+
// .env.example
|
|
128
|
+
expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
|
|
129
|
+
expect(files['.env.example']).toContain('DATABASE_URL');
|
|
130
|
+
expect(files['.env.example']).not.toContain('ACTION_POLICY_SECRET');
|
|
131
|
+
// README mentions database
|
|
132
|
+
expect(files['README.md']).toContain('DATABASE_URL');
|
|
133
|
+
expect(files['README.md']).toContain('npx @vxnus/siduri db push');
|
|
134
|
+
});
|
|
135
|
+
test('Composition D: Full with Body', () => {
|
|
136
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
137
|
+
name: 'companion-full',
|
|
138
|
+
selectedManifests: [
|
|
139
|
+
MOCK_MANIFESTS.brain,
|
|
140
|
+
MOCK_MANIFESTS.memory,
|
|
141
|
+
MOCK_MANIFESTS.hands,
|
|
142
|
+
MOCK_MANIFESTS.body,
|
|
143
|
+
MOCK_MANIFESTS.voice,
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
const pkg = JSON.parse(files['package.json']);
|
|
147
|
+
expect(pkg.dependencies['@siduri-x/core']).toBeDefined();
|
|
148
|
+
expect(pkg.dependencies['@siduri-x/brain']).toBeDefined();
|
|
149
|
+
expect(pkg.dependencies['@siduri-x/memory']).toBeDefined();
|
|
150
|
+
expect(pkg.dependencies['@siduri-x/hands']).toBeDefined();
|
|
151
|
+
expect(pkg.dependencies['@siduri-x/body']).toBeDefined();
|
|
152
|
+
expect(pkg.dependencies['@siduri-x/voice']).toBeDefined();
|
|
153
|
+
// Body & voice asset directories requested
|
|
154
|
+
expect(files.createAssetsBodyDir).toBe(true);
|
|
155
|
+
expect(files.createAssetsDirs).toContain('assets/body/companion-full');
|
|
156
|
+
expect(files.createAssetsDirs).toContain('assets/voice/companion-full');
|
|
157
|
+
// README mentions Live2D model assets & prerequisites
|
|
158
|
+
expect(files['README.md']).toContain('assets/body/companion-full');
|
|
159
|
+
expect(files['README.md']).toContain('assets/voice/companion-full');
|
|
160
|
+
expect(files['README.md']).toContain('Prerequisites');
|
|
161
|
+
// Docker compose generated for memory (voicevox is auto-downloaded at runtime)
|
|
162
|
+
expect(files['docker-compose.yml']).toBeDefined();
|
|
163
|
+
expect(files['docker-compose.yml']).toContain('postgres:15');
|
|
164
|
+
expect(files['README.md']).toContain('Voicevox engine executable will be securely auto-downloaded');
|
|
165
|
+
expect(pkg.scripts['services:up']).toBe('docker compose up -d');
|
|
166
|
+
expect(pkg.scripts['services:down']).toBe('docker compose down');
|
|
167
|
+
});
|
|
168
|
+
test('Composition E: Brain only has no docker-compose.yml', () => {
|
|
169
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
170
|
+
name: 'brain-solo',
|
|
171
|
+
selectedManifests: [MOCK_MANIFESTS.brain],
|
|
172
|
+
});
|
|
173
|
+
expect(files['docker-compose.yml']).toBeUndefined();
|
|
174
|
+
const pkg = JSON.parse(files['package.json']);
|
|
175
|
+
expect(pkg.scripts['services:up']).toBeUndefined();
|
|
176
|
+
expect(files['public/index.html']).toBeDefined();
|
|
177
|
+
expect(files['public/index.html']).toContain('Memory Console');
|
|
178
|
+
expect(files['public/index.html']).toContain('Companion Chat');
|
|
179
|
+
});
|
|
180
|
+
test('Generated runtime server template enforces loopback binding, safe health, and path containment', () => {
|
|
181
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
182
|
+
name: 'secure-companion',
|
|
183
|
+
selectedManifests: [MOCK_MANIFESTS.brain, MOCK_MANIFESTS.memory],
|
|
184
|
+
});
|
|
185
|
+
const srcIndexJs = files['src/index.js'];
|
|
186
|
+
// 1. Explicit loopback binding 127.0.0.1
|
|
187
|
+
expect(srcIndexJs).toContain("server.listen(Number(PORT), '127.0.0.1'");
|
|
188
|
+
expect(srcIndexJs).toContain("http://127.0.0.1:");
|
|
189
|
+
// 2. Minimal /health endpoint without config.organs or secrets
|
|
190
|
+
expect(srcIndexJs).toContain("pathname === '/health'");
|
|
191
|
+
expect(srcIndexJs).not.toContain('organs: config.organs');
|
|
192
|
+
// 3. Strict path traversal containment
|
|
193
|
+
expect(srcIndexJs).toContain('relative.startsWith(\'..\')');
|
|
194
|
+
expect(srcIndexJs).toContain('path.isAbsolute(relative)');
|
|
195
|
+
expect(srcIndexJs).toContain('decodeURIComponent');
|
|
196
|
+
});
|
|
197
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { OrganManifest } from './manifest';
|
|
3
|
+
export declare const CLI_VERSION = "0.1.10";
|
|
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>;
|
|
18
|
+
export declare function runCreateWizard(targetDir?: string): Promise<void>;
|
|
19
|
+
export declare function runCliDoctor(targetDir?: string): Promise<void>;
|
|
20
|
+
export declare function runCliDb(subcommand?: string, targetDir?: string): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
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;
|
|
14
|
+
exports.runCreateWizard = runCreateWizard;
|
|
15
|
+
exports.runCliDoctor = runCliDoctor;
|
|
16
|
+
exports.runCliDb = runCliDb;
|
|
17
|
+
const node_child_process_1 = require("node:child_process");
|
|
18
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
19
|
+
const promises_1 = require("node:fs/promises");
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const node_util_1 = require("node:util");
|
|
22
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
23
|
+
const discovery_1 = require("./discovery");
|
|
24
|
+
const generator_1 = require("./generator");
|
|
25
|
+
const doctor_1 = require("./doctor");
|
|
26
|
+
const db_1 = require("./db");
|
|
27
|
+
const configurators_1 = require("./configurators");
|
|
28
|
+
const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
29
|
+
exports.CLI_VERSION = '0.1.10';
|
|
30
|
+
exports.colors = {
|
|
31
|
+
cyan: '\u001b[36m',
|
|
32
|
+
dim: '\u001b[2m',
|
|
33
|
+
green: '\u001b[32m',
|
|
34
|
+
yellow: '\u001b[33m',
|
|
35
|
+
bold: '\u001b[1m',
|
|
36
|
+
reset: '\u001b[0m',
|
|
37
|
+
};
|
|
38
|
+
function printHeader() {
|
|
39
|
+
console.log(`\n${exports.colors.cyan}◈ SIDURI${exports.colors.reset} ${exports.colors.dim}companion setup (manifest-driven)${exports.colors.reset}`);
|
|
40
|
+
console.log(`${exports.colors.yellow}Version ${exports.CLI_VERSION}${exports.colors.reset} · composable standalone architecture\n`);
|
|
41
|
+
}
|
|
42
|
+
function printSection(title) {
|
|
43
|
+
console.log(`\n${exports.colors.cyan}── ${title} ${'─'.repeat(Math.max(2, 42 - title.length))}${exports.colors.reset}\n`);
|
|
44
|
+
}
|
|
45
|
+
function printSuccess(message) {
|
|
46
|
+
console.log(`${exports.colors.green}✓${exports.colors.reset} ${message}`);
|
|
47
|
+
}
|
|
48
|
+
function projectDirectoryName(value) {
|
|
49
|
+
const slug = value
|
|
50
|
+
.trim()
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
53
|
+
.replace(/^-+|-+$/g, '');
|
|
54
|
+
return slug || 'siduri';
|
|
55
|
+
}
|
|
56
|
+
function formatReviewSummary(companionName, selectedManifests, organSummaries) {
|
|
57
|
+
const lines = [];
|
|
58
|
+
lines.push(`\n${exports.colors.cyan}── Review ${companionName} ${'─'.repeat(Math.max(2, 42 - (companionName.length + 9)))}${exports.colors.reset}\n`);
|
|
59
|
+
lines.push(` ${exports.colors.bold}Companion${exports.colors.reset}`);
|
|
60
|
+
lines.push(` ${exports.colors.dim}Name:${exports.colors.reset} ${companionName}\n`);
|
|
61
|
+
for (const m of selectedManifests) {
|
|
62
|
+
const isRequired = m.organType === 'brain';
|
|
63
|
+
const tag = isRequired ? ` ${exports.colors.dim}· required${exports.colors.reset}` : '';
|
|
64
|
+
lines.push(` ${exports.colors.bold}${m.displayName.split(' ')[0] || m.organType}${exports.colors.reset}${tag}`);
|
|
65
|
+
const summary = organSummaries[m.configKey] || organSummaries[m.organType] || {};
|
|
66
|
+
const entries = Object.entries(summary);
|
|
67
|
+
if (entries.length === 0) {
|
|
68
|
+
lines.push(` ${exports.colors.dim}Provider:${exports.colors.reset} ${m.displayName}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
for (const [key, val] of entries) {
|
|
72
|
+
const valStr = String(val);
|
|
73
|
+
lines.push(` ${exports.colors.dim}${key}:${exports.colors.reset}${' '.repeat(Math.max(1, 10 - key.length))}${valStr}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
lines.push('');
|
|
77
|
+
}
|
|
78
|
+
lines.push(`${exports.colors.cyan}${'─'.repeat(46)}${exports.colors.reset}\n`);
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
async function withTask(label, task) {
|
|
82
|
+
process.stdout.write(`${exports.colors.dim}${label}${exports.colors.reset}`);
|
|
83
|
+
const frames = ['·', '•', '●', '•'];
|
|
84
|
+
let index = 0;
|
|
85
|
+
const timer = setInterval(() => {
|
|
86
|
+
process.stdout.write(`\r${exports.colors.dim}${label} ${frames[index++ % frames.length]}${exports.colors.reset}`);
|
|
87
|
+
}, 120);
|
|
88
|
+
try {
|
|
89
|
+
const result = await task();
|
|
90
|
+
clearInterval(timer);
|
|
91
|
+
process.stdout.write(`\r${exports.colors.green}✓${exports.colors.reset} ${label}\n`);
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
clearInterval(timer);
|
|
96
|
+
process.stdout.write(`\r${exports.colors.yellow}!${exports.colors.reset} ${label}\n`);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function nonEmpty(value) {
|
|
101
|
+
return value.trim().length > 0 || 'Please enter a value.';
|
|
102
|
+
}
|
|
103
|
+
async function runCreateWizard(targetDir) {
|
|
104
|
+
printHeader();
|
|
105
|
+
// 1. Discover manifests from installed or monorepo packages
|
|
106
|
+
const registry = discovery_1.OrganRegistry.discover();
|
|
107
|
+
const availableManifests = registry.getAll();
|
|
108
|
+
if (availableManifests.length === 0) {
|
|
109
|
+
throw new Error('No @siduri-x/* organ packages found. Please ensure organs are installed or in workspace.');
|
|
110
|
+
}
|
|
111
|
+
printSection('Companion Details');
|
|
112
|
+
const basicAnswers = await inquirer_1.default.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'name',
|
|
116
|
+
message: 'Companion name:',
|
|
117
|
+
default: 'Siduri',
|
|
118
|
+
validate: nonEmpty,
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
const companionName = basicAnswers.name;
|
|
122
|
+
const projectDir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : node_path_1.default.resolve(process.cwd(), projectDirectoryName(companionName));
|
|
123
|
+
printSection('Organ Selection');
|
|
124
|
+
console.log(`${exports.colors.dim}Select any combination of organs to compose into your standalone instance.${exports.colors.reset}\n`);
|
|
125
|
+
// Brain is required by architecture contract for cognition
|
|
126
|
+
const brainManifest = registry.get('brain');
|
|
127
|
+
const nonBrainManifests = availableManifests.filter((m) => m.organType !== 'brain');
|
|
128
|
+
const organChoices = nonBrainManifests.map((m) => ({
|
|
129
|
+
name: `${m.displayName} (${m.name})`,
|
|
130
|
+
value: m.organType,
|
|
131
|
+
checked: m.organType === 'memory', // default memory checked
|
|
132
|
+
}));
|
|
133
|
+
const { selectedOrganTypes } = await inquirer_1.default.prompt({
|
|
134
|
+
type: 'checkbox',
|
|
135
|
+
name: 'selectedOrganTypes',
|
|
136
|
+
message: 'Select organs to install:',
|
|
137
|
+
choices: organChoices,
|
|
138
|
+
});
|
|
139
|
+
const selectedManifests = [];
|
|
140
|
+
if (brainManifest) {
|
|
141
|
+
selectedManifests.push(brainManifest);
|
|
142
|
+
}
|
|
143
|
+
for (const organType of selectedOrganTypes) {
|
|
144
|
+
const m = registry.get(organType);
|
|
145
|
+
if (m)
|
|
146
|
+
selectedManifests.push(m);
|
|
147
|
+
}
|
|
148
|
+
// 2. Interactive Organ Configuration Stage
|
|
149
|
+
const organConfigs = {};
|
|
150
|
+
const organSummaries = {};
|
|
151
|
+
for (const m of selectedManifests) {
|
|
152
|
+
const isRequired = m.organType === 'brain';
|
|
153
|
+
const sectionTitle = isRequired ? `${m.displayName.split(' ')[0] || m.organType} · required` : m.displayName.split(' ')[0] || m.organType;
|
|
154
|
+
printSection(sectionTitle);
|
|
155
|
+
const res = await (0, configurators_1.configureOrgan)(m, {
|
|
156
|
+
companionName,
|
|
157
|
+
existingConfig: organConfigs[m.configKey],
|
|
158
|
+
});
|
|
159
|
+
organConfigs[m.configKey] = res.config;
|
|
160
|
+
organSummaries[m.configKey] = res.summary || {};
|
|
161
|
+
}
|
|
162
|
+
// 3. Final Review and Edit Loop
|
|
163
|
+
while (true) {
|
|
164
|
+
console.log(formatReviewSummary(companionName, selectedManifests, organSummaries));
|
|
165
|
+
const { reviewAction } = await inquirer_1.default.prompt({
|
|
166
|
+
type: 'list',
|
|
167
|
+
name: 'reviewAction',
|
|
168
|
+
message: `Create ${companionName} with this configuration?`,
|
|
169
|
+
choices: [
|
|
170
|
+
{ name: 'Yes, create', value: 'create' },
|
|
171
|
+
{ name: 'Go back and edit', value: 'edit' },
|
|
172
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
if (reviewAction === 'cancel') {
|
|
176
|
+
console.log('Instance creation cancelled.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (reviewAction === 'create') {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (reviewAction === 'edit') {
|
|
183
|
+
const editChoices = selectedManifests.map((m) => ({
|
|
184
|
+
name: `Edit ${m.displayName}`,
|
|
185
|
+
value: m.configKey,
|
|
186
|
+
}));
|
|
187
|
+
editChoices.push({ name: 'Edit all organs in sequence', value: '__ALL__' });
|
|
188
|
+
editChoices.push({ name: 'Back to review', value: '__BACK__' });
|
|
189
|
+
const { organToEdit } = await inquirer_1.default.prompt({
|
|
190
|
+
type: 'list',
|
|
191
|
+
name: 'organToEdit',
|
|
192
|
+
message: 'Which organ would you like to edit?',
|
|
193
|
+
choices: editChoices,
|
|
194
|
+
});
|
|
195
|
+
if (organToEdit === '__BACK__') {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const organsToReconfigure = organToEdit === '__ALL__'
|
|
199
|
+
? selectedManifests
|
|
200
|
+
: selectedManifests.filter((m) => m.configKey === organToEdit);
|
|
201
|
+
for (const m of organsToReconfigure) {
|
|
202
|
+
const isRequired = m.organType === 'brain';
|
|
203
|
+
const sectionTitle = isRequired ? `${m.displayName.split(' ')[0] || m.organType} · required` : m.displayName.split(' ')[0] || m.organType;
|
|
204
|
+
printSection(sectionTitle);
|
|
205
|
+
const res = await (0, configurators_1.configureOrgan)(m, {
|
|
206
|
+
companionName,
|
|
207
|
+
existingConfig: organConfigs[m.configKey],
|
|
208
|
+
});
|
|
209
|
+
organConfigs[m.configKey] = res.config;
|
|
210
|
+
organSummaries[m.configKey] = res.summary || {};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// 4. Generate Instance Files
|
|
215
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
216
|
+
name: companionName,
|
|
217
|
+
selectedManifests,
|
|
218
|
+
organConfigs,
|
|
219
|
+
});
|
|
220
|
+
await (0, promises_1.mkdir)(projectDir, { recursive: true });
|
|
221
|
+
await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'src'), { recursive: true });
|
|
222
|
+
const publicDir = node_path_1.default.join(projectDir, 'public');
|
|
223
|
+
await (0, promises_1.mkdir)(publicDir, { recursive: true });
|
|
224
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'package.json'), files['package.json'], 'utf8');
|
|
225
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'siduri.config.json'), files['siduri.config.json'], 'utf8');
|
|
226
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'siduri.schema.json'), files['siduri.schema.json'], 'utf8');
|
|
227
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, '.env.example'), files['.env.example'], 'utf8');
|
|
228
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'README.md'), files['README.md'], 'utf8');
|
|
229
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'src/index.js'), files['src/index.js'], 'utf8');
|
|
230
|
+
// Copy full Next.js apps/web production build if present
|
|
231
|
+
const webDistCandidates = [
|
|
232
|
+
node_path_1.default.resolve(__dirname, 'web-dist'),
|
|
233
|
+
node_path_1.default.resolve(__dirname, '../dist/web-dist'),
|
|
234
|
+
node_path_1.default.resolve(__dirname, '../../apps/web/out'),
|
|
235
|
+
node_path_1.default.resolve(process.cwd(), 'apps/web/out'),
|
|
236
|
+
];
|
|
237
|
+
const foundWebDist = webDistCandidates.find((p) => node_fs_1.default.existsSync(p));
|
|
238
|
+
if (foundWebDist) {
|
|
239
|
+
node_fs_1.default.cpSync(foundWebDist, publicDir, { recursive: true });
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(publicDir, 'index.html'), files['public/index.html'], 'utf8');
|
|
243
|
+
}
|
|
244
|
+
if (files['docker-compose.yml']) {
|
|
245
|
+
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'docker-compose.yml'), files['docker-compose.yml'], 'utf8');
|
|
246
|
+
}
|
|
247
|
+
if (files.createAssetsDirs && files.createAssetsDirs.length > 0) {
|
|
248
|
+
for (const dir of files.createAssetsDirs) {
|
|
249
|
+
await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, dir), { recursive: true });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (files.createAssetsBodyDir) {
|
|
253
|
+
await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'assets/body'), { recursive: true });
|
|
254
|
+
}
|
|
255
|
+
printSuccess(`Generated standalone files at ${projectDir}`);
|
|
256
|
+
// 5. Install dependencies from registry
|
|
257
|
+
try {
|
|
258
|
+
await withTask('Installing dependencies (npm install)', async () => {
|
|
259
|
+
await execFile('npm', ['install', '--no-audit', '--no-fund'], { cwd: projectDir });
|
|
260
|
+
});
|
|
261
|
+
printSuccess('Dependencies installed successfully.');
|
|
262
|
+
}
|
|
263
|
+
catch (err) {
|
|
264
|
+
console.warn(`${exports.colors.yellow}!${exports.colors.reset} Notice: npm install had warnings or requires network: ${err.message}`);
|
|
265
|
+
}
|
|
266
|
+
printSection('Instance Ready');
|
|
267
|
+
console.log(`Your Siduri companion is ready! Next steps:\n`);
|
|
268
|
+
console.log(` cd ${node_path_1.default.relative(process.cwd(), projectDir) || '.'}`);
|
|
269
|
+
console.log(` cp .env.example .env ${exports.colors.dim}# Fill in required API keys/credentials${exports.colors.reset}`);
|
|
270
|
+
if (files['docker-compose.yml']) {
|
|
271
|
+
console.log(` npm run services:up ${exports.colors.dim}# Start local Docker services (PostgreSQL / VOICEVOX)${exports.colors.reset}`);
|
|
272
|
+
}
|
|
273
|
+
const hasMemory = selectedManifests.some((m) => m.organType === 'memory');
|
|
274
|
+
if (hasMemory) {
|
|
275
|
+
console.log(` npx @vxnus/siduri db push ${exports.colors.dim}# Push PostgreSQL memory schema${exports.colors.reset}`);
|
|
276
|
+
}
|
|
277
|
+
console.log(` npm run doctor ${exports.colors.dim}# Run diagnostic health probes${exports.colors.reset}`);
|
|
278
|
+
console.log(` npm start ${exports.colors.dim}# Start Web Companion & Memory Console at http://localhost:3000${exports.colors.reset}\n`);
|
|
279
|
+
}
|
|
280
|
+
async function runCliDoctor(targetDir) {
|
|
281
|
+
printHeader();
|
|
282
|
+
const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
|
|
283
|
+
console.log(`${exports.colors.cyan}Siduri Doctor${exports.colors.reset}`);
|
|
284
|
+
console.log(`${exports.colors.dim}─────────────${exports.colors.reset}\n`);
|
|
285
|
+
try {
|
|
286
|
+
const report = await (0, doctor_1.runDoctor)({ projectDir: dir });
|
|
287
|
+
console.log(`${exports.colors.dim}Instance:${exports.colors.reset} ${report.instanceName}`);
|
|
288
|
+
console.log(`${exports.colors.dim}Organs:${exports.colors.reset} ${report.configuredOrgans.join(', ')}\n`);
|
|
289
|
+
const categories = ['Environment', 'Services', 'Database', 'Health Probe'];
|
|
290
|
+
for (const cat of categories) {
|
|
291
|
+
const items = report.results.filter((r) => r.category === cat);
|
|
292
|
+
if (items.length > 0) {
|
|
293
|
+
console.log(`${exports.colors.cyan}${cat}${exports.colors.reset}`);
|
|
294
|
+
for (const item of items) {
|
|
295
|
+
if (item.status === 'PASS') {
|
|
296
|
+
console.log(` ${exports.colors.green}✓${exports.colors.reset} ${item.name} ${exports.colors.dim}(${item.message || 'OK'})${exports.colors.reset}`);
|
|
297
|
+
}
|
|
298
|
+
else if (item.status === 'OPTIONAL_MISSING') {
|
|
299
|
+
console.log(` ${exports.colors.dim}○${exports.colors.reset} ${item.name} ${exports.colors.dim}(Optional, not set)${exports.colors.reset}`);
|
|
300
|
+
}
|
|
301
|
+
else if (item.status === 'SKIPPED') {
|
|
302
|
+
console.log(` ${exports.colors.dim}— ${item.name} (${item.message})${exports.colors.reset}`);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log(` ${exports.colors.yellow}✗${exports.colors.reset} ${item.name}`);
|
|
306
|
+
if (item.organName) {
|
|
307
|
+
console.log(` ${exports.colors.dim}Required by:${exports.colors.reset} ${item.organName}`);
|
|
308
|
+
}
|
|
309
|
+
if (item.message) {
|
|
310
|
+
console.log(` ${exports.colors.yellow}${item.message}${exports.colors.reset}`);
|
|
311
|
+
}
|
|
312
|
+
if (item.remediation) {
|
|
313
|
+
console.log(` ${exports.colors.dim}Remediation:${exports.colors.reset} ${item.remediation}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
console.log();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (report.passed) {
|
|
321
|
+
console.log(`${exports.colors.green}Result: PASS${exports.colors.reset}\n`);
|
|
322
|
+
process.exitCode = 0;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
console.log(`${exports.colors.yellow}Result: FAIL${exports.colors.reset}\n`);
|
|
326
|
+
process.exitCode = 1;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
console.error(`\n${exports.colors.yellow}Doctor Error:${exports.colors.reset} ${err.message}\n`);
|
|
331
|
+
process.exitCode = 2;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function runCliDb(subcommand, targetDir) {
|
|
335
|
+
printHeader();
|
|
336
|
+
if (subcommand !== 'push') {
|
|
337
|
+
console.log('Usage: siduri db push');
|
|
338
|
+
process.exitCode = 2;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
|
|
342
|
+
console.log(`${exports.colors.cyan}Siduri Database Migrations${exports.colors.reset}`);
|
|
343
|
+
console.log(`${exports.colors.dim}──────────────────────────${exports.colors.reset}\n`);
|
|
344
|
+
try {
|
|
345
|
+
const res = await (0, db_1.runDbPush)({ projectDir: dir });
|
|
346
|
+
if (res.status === 'NOOP') {
|
|
347
|
+
console.log(`${exports.colors.dim}— ${res.message}${exports.colors.reset}\n`);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
printSuccess(res.message);
|
|
351
|
+
if (res.appliedMigrations.length > 0) {
|
|
352
|
+
console.log(`${exports.colors.dim}Applied:${exports.colors.reset} ${res.appliedMigrations.join(', ')}`);
|
|
353
|
+
}
|
|
354
|
+
console.log();
|
|
355
|
+
}
|
|
356
|
+
process.exitCode = 0;
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
console.error(`\n${exports.colors.yellow}Database Migration Error:${exports.colors.reset} ${err.message}\n`);
|
|
360
|
+
process.exitCode = 3;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function main() {
|
|
364
|
+
const args = process.argv.slice(2);
|
|
365
|
+
const command = args[0];
|
|
366
|
+
if (command === '--version' || command === '-v') {
|
|
367
|
+
console.log(exports.CLI_VERSION);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (command === 'create') {
|
|
371
|
+
const targetDir = args[1];
|
|
372
|
+
await runCreateWizard(targetDir);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (command === 'doctor') {
|
|
376
|
+
const targetDir = args[1];
|
|
377
|
+
await runCliDoctor(targetDir);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (command === 'db') {
|
|
381
|
+
const subcommand = args[1];
|
|
382
|
+
const targetDir = args[2];
|
|
383
|
+
await runCliDb(subcommand, targetDir);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
printHeader();
|
|
387
|
+
console.log('Usage: npx @vxnus/siduri create [directory]');
|
|
388
|
+
console.log(' npx @vxnus/siduri doctor [directory]');
|
|
389
|
+
console.log(' npx @vxnus/siduri db push [directory]');
|
|
390
|
+
console.log(' npx @vxnus/siduri --version\n');
|
|
391
|
+
}
|
|
392
|
+
if (require.main === module) {
|
|
393
|
+
main().catch((error) => {
|
|
394
|
+
if (error && typeof error === 'object' && 'name' in error && error.name === 'ExitPromptError') {
|
|
395
|
+
console.log('\nOperation cancelled.');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
console.error(`\n${exports.colors.yellow}!${exports.colors.reset} ${error instanceof Error ? error.message : error}`);
|
|
399
|
+
process.exitCode = 1;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface OrganEnvironmentVar {
|
|
2
|
+
name: string;
|
|
3
|
+
required?: boolean;
|
|
4
|
+
secret?: boolean;
|
|
5
|
+
default?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface OrganServiceRequirement {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: 'database' | 'http_service' | 'process' | string;
|
|
11
|
+
optional?: boolean;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface OrganDatabaseRequirement {
|
|
15
|
+
engine: 'postgres' | 'sqlite' | string;
|
|
16
|
+
migrationsDir?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface OrganManifest {
|
|
19
|
+
name: string;
|
|
20
|
+
organType: string;
|
|
21
|
+
version: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
entrypoint: string;
|
|
25
|
+
factory: string;
|
|
26
|
+
configKey: string;
|
|
27
|
+
configSchema: Record<string, any>;
|
|
28
|
+
environment: OrganEnvironmentVar[];
|
|
29
|
+
services: OrganServiceRequirement[];
|
|
30
|
+
database?: OrganDatabaseRequirement | null;
|
|
31
|
+
healthCheck?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export declare function validateOrganManifest(manifest: unknown, sourcePath?: string): OrganManifest;
|