@vxnus/siduri 0.0.4 → 0.0.6
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/README.md +47 -28
- package/dist/clean-machine-e2e.test.d.ts +1 -0
- package/dist/clean-machine-e2e.test.js +222 -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 +79 -0
- package/dist/discovery.test.d.ts +1 -0
- package/dist/discovery.test.js +60 -0
- package/dist/doctor-db.test.d.ts +1 -0
- package/dist/doctor-db.test.js +135 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +225 -0
- package/dist/generator.d.ts +18 -0
- package/dist/generator.js +215 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +158 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +202 -356
- package/dist/manifest.d.ts +33 -0
- package/dist/manifest.js +40 -0
- package/dist/release-check.d.ts +9 -0
- package/dist/release-check.js +131 -0
- package/package.json +12 -11
- package/dist/runtime.js +0 -3299
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const doctor_1 = require("./doctor");
|
|
9
|
+
const db_1 = require("./db");
|
|
10
|
+
const generator_1 = require("./generator");
|
|
11
|
+
const discovery_1 = require("./discovery");
|
|
12
|
+
describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
13
|
+
const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
|
|
14
|
+
const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
|
|
15
|
+
const brain = registry.get('brain');
|
|
16
|
+
const hands = registry.get('hands');
|
|
17
|
+
const memory = registry.get('memory');
|
|
18
|
+
const testTempRoot = node_path_1.default.resolve(__dirname, '../temp-phase4-test');
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
21
|
+
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
node_fs_1.default.mkdirSync(testTempRoot, { recursive: true });
|
|
24
|
+
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
27
|
+
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
test('redactDatabaseUrl redacts password and sensitive credentials', () => {
|
|
31
|
+
const raw = 'postgresql://myuser:supersecretpassword@db.example.com:5432/siduri_db';
|
|
32
|
+
const redacted = (0, db_1.redactDatabaseUrl)(raw);
|
|
33
|
+
expect(redacted).not.toContain('supersecretpassword');
|
|
34
|
+
expect(redacted).toContain('***');
|
|
35
|
+
expect(redacted).toContain('myuser');
|
|
36
|
+
expect(redacted).toContain('db.example.com:5432/siduri_db');
|
|
37
|
+
});
|
|
38
|
+
test('Test Matrix A: Brain only instance doctor and db push', async () => {
|
|
39
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-only');
|
|
40
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
41
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
42
|
+
name: 'BrainOnlyInstance',
|
|
43
|
+
selectedManifests: [brain],
|
|
44
|
+
});
|
|
45
|
+
for (const [name, content] of Object.entries(files)) {
|
|
46
|
+
if (typeof content === 'string') {
|
|
47
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// 1. Doctor without OPENROUTER_API_KEY -> FAIL (Brain Health Probe fails)
|
|
51
|
+
const doctorFail = await (0, doctor_1.runDoctor)({ projectDir: instanceDir, env: { OPENROUTER_API_KEY: '' } });
|
|
52
|
+
expect(doctorFail.passed).toBe(false);
|
|
53
|
+
expect(doctorFail.results.some((r) => r.category === 'Health Probe' && r.status === 'FAIL')).toBe(true);
|
|
54
|
+
// Database check must be SKIPPED
|
|
55
|
+
expect(doctorFail.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
56
|
+
// 2. Doctor with OPENROUTER_API_KEY -> PASS
|
|
57
|
+
const doctorPass = await (0, doctor_1.runDoctor)({
|
|
58
|
+
projectDir: instanceDir,
|
|
59
|
+
env: { OPENROUTER_API_KEY: 'test-key-123' },
|
|
60
|
+
});
|
|
61
|
+
expect(doctorPass.passed).toBe(true);
|
|
62
|
+
// 3. db push -> NOOP ("No database migrations are required...")
|
|
63
|
+
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
64
|
+
expect(dbResult.status).toBe('NOOP');
|
|
65
|
+
expect(dbResult.message).toContain('No database migrations are required');
|
|
66
|
+
});
|
|
67
|
+
test('Test Matrix B: Brain + Hands instance doctor and db push', async () => {
|
|
68
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-hands');
|
|
69
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
70
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
71
|
+
name: 'BrainHandsInstance',
|
|
72
|
+
selectedManifests: [brain, hands],
|
|
73
|
+
});
|
|
74
|
+
for (const [name, content] of Object.entries(files)) {
|
|
75
|
+
if (typeof content === 'string') {
|
|
76
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Doctor checks both keys
|
|
80
|
+
const doctorRes = await (0, doctor_1.runDoctor)({
|
|
81
|
+
projectDir: instanceDir,
|
|
82
|
+
env: {
|
|
83
|
+
OPENROUTER_API_KEY: 'test-openrouter-key',
|
|
84
|
+
ACTION_POLICY_SECRET: 'test-policy-secret',
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
expect(doctorRes.passed).toBe(true);
|
|
88
|
+
expect(doctorRes.results.some((r) => r.name === 'OPENROUTER_API_KEY' && r.status === 'PASS')).toBe(true);
|
|
89
|
+
expect(doctorRes.results.some((r) => r.name === 'ACTION_POLICY_SECRET' && r.status === 'PASS')).toBe(true);
|
|
90
|
+
// Database check must be SKIPPED
|
|
91
|
+
expect(doctorRes.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
|
|
92
|
+
// db push returns NOOP
|
|
93
|
+
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
94
|
+
expect(dbResult.status).toBe('NOOP');
|
|
95
|
+
});
|
|
96
|
+
test('Test Matrix C: Brain + Memory instance requires DATABASE_URL', async () => {
|
|
97
|
+
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
98
|
+
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
99
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
100
|
+
name: 'BrainMemoryInstance',
|
|
101
|
+
selectedManifests: [brain, memory],
|
|
102
|
+
});
|
|
103
|
+
for (const [name, content] of Object.entries(files)) {
|
|
104
|
+
if (typeof content === 'string') {
|
|
105
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(instanceDir, name), content);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 1. Doctor fails without DATABASE_URL
|
|
109
|
+
const doctorMissingDb = await (0, doctor_1.runDoctor)({
|
|
110
|
+
projectDir: instanceDir,
|
|
111
|
+
env: { OPENROUTER_API_KEY: 'test-key' },
|
|
112
|
+
});
|
|
113
|
+
expect(doctorMissingDb.passed).toBe(false);
|
|
114
|
+
expect(doctorMissingDb.results.some((r) => r.name === 'DATABASE_URL' && r.status === 'FAIL')).toBe(true);
|
|
115
|
+
// 2. db push fails clearly without DATABASE_URL
|
|
116
|
+
await expect((0, db_1.runDbPush)({ projectDir: instanceDir, env: {} })).rejects.toThrow(/DATABASE_URL is required/);
|
|
117
|
+
});
|
|
118
|
+
test('Test Matrix D: Diagnostics and Error Handling for invalid or missing configs', async () => {
|
|
119
|
+
// 1. Missing siduri.config.json
|
|
120
|
+
const nonExistentDir = node_path_1.default.join(testTempRoot, 'non-existent-dir');
|
|
121
|
+
await expect((0, doctor_1.runDoctor)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
122
|
+
await expect((0, db_1.runDbPush)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
|
|
123
|
+
// 2. Unreachable PostgreSQL host reported gracefully by doctor
|
|
124
|
+
const instDir = node_path_1.default.join(testTempRoot, 'brain-memory');
|
|
125
|
+
const doctorUnreachable = await (0, doctor_1.runDoctor)({
|
|
126
|
+
projectDir: instDir,
|
|
127
|
+
env: {
|
|
128
|
+
OPENROUTER_API_KEY: 'test-key',
|
|
129
|
+
DATABASE_URL: 'postgresql://postgres:postgres@127.0.0.1:59999/non_existent_db',
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
expect(doctorUnreachable.passed).toBe(false);
|
|
133
|
+
expect(doctorUnreachable.results.some((r) => r.category === 'Database' && r.status === 'FAIL')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
});
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DoctorCheckResult {
|
|
2
|
+
category: 'Environment' | 'Services' | 'Database' | 'Health Probe';
|
|
3
|
+
name: string;
|
|
4
|
+
status: 'PASS' | 'FAIL' | 'OPTIONAL_MISSING' | 'SKIPPED';
|
|
5
|
+
organName?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
remediation?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface DoctorReport {
|
|
10
|
+
instanceName: string;
|
|
11
|
+
configuredOrgans: string[];
|
|
12
|
+
results: DoctorCheckResult[];
|
|
13
|
+
passed: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface DoctorOptions {
|
|
16
|
+
projectDir?: string;
|
|
17
|
+
env?: Record<string, string | undefined>;
|
|
18
|
+
}
|
|
19
|
+
export declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDoctor = runDoctor;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const discovery_1 = require("./discovery");
|
|
10
|
+
const pg_1 = require("pg");
|
|
11
|
+
async function runDoctor(options = {}) {
|
|
12
|
+
const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
|
|
13
|
+
const configPath = node_path_1.default.join(projectDir, 'siduri.config.json');
|
|
14
|
+
if (!node_fs_1.default.existsSync(configPath)) {
|
|
15
|
+
throw new Error(`siduri.config.json not found at ${projectDir}. Make sure you are in a Siduri instance directory.`);
|
|
16
|
+
}
|
|
17
|
+
// Load .env if present in projectDir
|
|
18
|
+
const envFile = node_path_1.default.join(projectDir, '.env');
|
|
19
|
+
const fileEnv = {};
|
|
20
|
+
if (node_fs_1.default.existsSync(envFile)) {
|
|
21
|
+
const lines = node_fs_1.default.readFileSync(envFile, 'utf8').split('\n');
|
|
22
|
+
for (const line of lines) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
25
|
+
continue;
|
|
26
|
+
const eqIdx = trimmed.indexOf('=');
|
|
27
|
+
if (eqIdx !== -1) {
|
|
28
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
29
|
+
const val = trimmed.slice(eqIdx + 1).trim();
|
|
30
|
+
fileEnv[key] = val;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const effectiveEnv = {
|
|
35
|
+
...process.env,
|
|
36
|
+
...fileEnv,
|
|
37
|
+
...(options.env || {}),
|
|
38
|
+
};
|
|
39
|
+
const config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf8'));
|
|
40
|
+
const configuredOrgansMap = config.organs || {};
|
|
41
|
+
const organKeys = Object.keys(configuredOrgansMap);
|
|
42
|
+
const registry = discovery_1.OrganRegistry.discover([
|
|
43
|
+
node_path_1.default.join(projectDir, 'node_modules/@siduri-x'),
|
|
44
|
+
node_path_1.default.resolve(__dirname, '../../packages/organs'),
|
|
45
|
+
node_path_1.default.resolve(process.cwd(), 'packages/organs'),
|
|
46
|
+
]);
|
|
47
|
+
const selectedManifests = [];
|
|
48
|
+
const results = [];
|
|
49
|
+
// 1. Resolve manifests for selected organs
|
|
50
|
+
for (const organKey of organKeys) {
|
|
51
|
+
const m = registry.get(organKey) || registry.getAll().find((item) => item.configKey === organKey || item.organType === organKey);
|
|
52
|
+
if (!m) {
|
|
53
|
+
results.push({
|
|
54
|
+
category: 'Environment',
|
|
55
|
+
name: `Manifest resolution: ${organKey}`,
|
|
56
|
+
status: 'FAIL',
|
|
57
|
+
message: `Could not resolve package manifest for configured organ '${organKey}'.`,
|
|
58
|
+
remediation: `Ensure @siduri-x/${organKey} is installed in package.json and npm install has been run.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
selectedManifests.push(m);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 2. Check Environment Variables (deduplicated)
|
|
66
|
+
const checkedEnvVars = new Set();
|
|
67
|
+
for (const m of selectedManifests) {
|
|
68
|
+
for (const envVar of m.environment || []) {
|
|
69
|
+
if (checkedEnvVars.has(envVar.name))
|
|
70
|
+
continue;
|
|
71
|
+
checkedEnvVars.add(envVar.name);
|
|
72
|
+
const val = effectiveEnv[envVar.name];
|
|
73
|
+
const isMissing = !val || val.trim() === '';
|
|
74
|
+
if (isMissing) {
|
|
75
|
+
if (envVar.required) {
|
|
76
|
+
results.push({
|
|
77
|
+
category: 'Environment',
|
|
78
|
+
name: envVar.name,
|
|
79
|
+
status: 'FAIL',
|
|
80
|
+
organName: m.name,
|
|
81
|
+
message: `Missing required environment variable '${envVar.name}'.`,
|
|
82
|
+
remediation: `Set ${envVar.name} in .env or your shell environment. ${envVar.description || ''}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
results.push({
|
|
87
|
+
category: 'Environment',
|
|
88
|
+
name: envVar.name,
|
|
89
|
+
status: 'OPTIONAL_MISSING',
|
|
90
|
+
organName: m.name,
|
|
91
|
+
message: `Optional variable '${envVar.name}' not set.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
results.push({
|
|
97
|
+
category: 'Environment',
|
|
98
|
+
name: envVar.name,
|
|
99
|
+
status: 'PASS',
|
|
100
|
+
organName: m.name,
|
|
101
|
+
message: 'Configured',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 3. Check External Services
|
|
107
|
+
for (const m of selectedManifests) {
|
|
108
|
+
for (const service of m.services || []) {
|
|
109
|
+
results.push({
|
|
110
|
+
category: 'Services',
|
|
111
|
+
name: `${service.name} (${m.organType})`,
|
|
112
|
+
status: 'PASS',
|
|
113
|
+
organName: m.name,
|
|
114
|
+
message: 'Service requirement declared',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 4. Database Check (only if an organ declares database requirement)
|
|
119
|
+
const dbOrgan = selectedManifests.find((m) => m.database !== null && m.database !== undefined);
|
|
120
|
+
if (dbOrgan) {
|
|
121
|
+
const dbUrl = effectiveEnv.DATABASE_URL;
|
|
122
|
+
if (!dbUrl) {
|
|
123
|
+
results.push({
|
|
124
|
+
category: 'Database',
|
|
125
|
+
name: 'PostgreSQL Connection',
|
|
126
|
+
status: 'FAIL',
|
|
127
|
+
organName: dbOrgan.name,
|
|
128
|
+
message: 'DATABASE_URL is not configured.',
|
|
129
|
+
remediation: 'Provide DATABASE_URL in .env (e.g. postgresql://postgres:postgres@localhost:5432/siduri)',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const pool = new pg_1.Pool({ connectionString: dbUrl, connectionTimeoutMillis: 3000 });
|
|
134
|
+
try {
|
|
135
|
+
const client = await pool.connect();
|
|
136
|
+
await client.query('SELECT 1');
|
|
137
|
+
client.release();
|
|
138
|
+
await pool.end();
|
|
139
|
+
results.push({
|
|
140
|
+
category: 'Database',
|
|
141
|
+
name: 'PostgreSQL Connection',
|
|
142
|
+
status: 'PASS',
|
|
143
|
+
organName: dbOrgan.name,
|
|
144
|
+
message: 'Connection successful',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
await pool.end().catch(() => { });
|
|
149
|
+
results.push({
|
|
150
|
+
category: 'Database',
|
|
151
|
+
name: 'PostgreSQL Connection',
|
|
152
|
+
status: 'FAIL',
|
|
153
|
+
organName: dbOrgan.name,
|
|
154
|
+
message: `Connection failed: ${err.message}`,
|
|
155
|
+
remediation: 'Check database host availability, port, credentials, and network accessibility.',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
results.push({
|
|
162
|
+
category: 'Database',
|
|
163
|
+
name: 'PostgreSQL Database',
|
|
164
|
+
status: 'SKIPPED',
|
|
165
|
+
message: 'Not required by current composition',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// 5. Organ Health Probes
|
|
169
|
+
for (const m of selectedManifests) {
|
|
170
|
+
if (m.healthCheck) {
|
|
171
|
+
try {
|
|
172
|
+
// Resolve package entrypoint
|
|
173
|
+
const candidateEntrypoints = [
|
|
174
|
+
node_path_1.default.resolve(projectDir, 'node_modules', m.name, m.entrypoint),
|
|
175
|
+
node_path_1.default.resolve(__dirname, '../../packages/organs', m.organType, m.entrypoint),
|
|
176
|
+
node_path_1.default.resolve(process.cwd(), 'packages/organs', m.organType, m.entrypoint),
|
|
177
|
+
];
|
|
178
|
+
const entryPath = candidateEntrypoints.find((p) => node_fs_1.default.existsSync(p));
|
|
179
|
+
if (entryPath) {
|
|
180
|
+
const mod = require(entryPath);
|
|
181
|
+
const probeFn = mod[m.healthCheck];
|
|
182
|
+
if (typeof probeFn === 'function') {
|
|
183
|
+
const organConf = configuredOrgansMap[m.configKey] || configuredOrgansMap[m.organType];
|
|
184
|
+
const probeRes = await probeFn({ config: organConf, env: effectiveEnv });
|
|
185
|
+
if (probeRes && probeRes.ok === false) {
|
|
186
|
+
results.push({
|
|
187
|
+
category: 'Health Probe',
|
|
188
|
+
name: `${m.displayName} Probe`,
|
|
189
|
+
status: 'FAIL',
|
|
190
|
+
organName: m.name,
|
|
191
|
+
message: probeRes.message || 'Health probe check failed',
|
|
192
|
+
remediation: `Check ${m.displayName} configuration in siduri.config.json.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
results.push({
|
|
197
|
+
category: 'Health Probe',
|
|
198
|
+
name: `${m.displayName} Probe`,
|
|
199
|
+
status: 'PASS',
|
|
200
|
+
organName: m.name,
|
|
201
|
+
message: probeRes?.message || 'Operational',
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
results.push({
|
|
209
|
+
category: 'Health Probe',
|
|
210
|
+
name: `${m.displayName} Probe`,
|
|
211
|
+
status: 'FAIL',
|
|
212
|
+
organName: m.name,
|
|
213
|
+
message: `Probe invocation failed: ${err.message}`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const passed = results.every((r) => r.status !== 'FAIL');
|
|
219
|
+
return {
|
|
220
|
+
instanceName: config.name || 'Siduri Instance',
|
|
221
|
+
configuredOrgans: organKeys,
|
|
222
|
+
results,
|
|
223
|
+
passed,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OrganManifest } from './manifest';
|
|
2
|
+
export interface GeneratedInstanceFiles {
|
|
3
|
+
'package.json': string;
|
|
4
|
+
'siduri.config.json': string;
|
|
5
|
+
'siduri.schema.json': string;
|
|
6
|
+
'.env.example': string;
|
|
7
|
+
'README.md': string;
|
|
8
|
+
'src/index.js': string;
|
|
9
|
+
createAssetsBodyDir?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface InstanceGeneratorOptions {
|
|
12
|
+
name: string;
|
|
13
|
+
id?: string;
|
|
14
|
+
selectedManifests: OrganManifest[];
|
|
15
|
+
organConfigs?: Record<string, any>;
|
|
16
|
+
coreVersion?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function generateInstanceFiles(options: InstanceGeneratorOptions): GeneratedInstanceFiles;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateInstanceFiles = generateInstanceFiles;
|
|
4
|
+
function getDefaultConfigForManifest(manifest) {
|
|
5
|
+
const schema = manifest.configSchema || {};
|
|
6
|
+
const props = schema.properties || {};
|
|
7
|
+
const config = {};
|
|
8
|
+
for (const [key, val] of Object.entries(props)) {
|
|
9
|
+
if (val.default !== undefined) {
|
|
10
|
+
config[key] = val.default;
|
|
11
|
+
}
|
|
12
|
+
else if (val.enum && val.enum.length > 0) {
|
|
13
|
+
config[key] = val.enum[0];
|
|
14
|
+
}
|
|
15
|
+
else if (val.type === 'string') {
|
|
16
|
+
config[key] = '';
|
|
17
|
+
}
|
|
18
|
+
else if (val.type === 'number') {
|
|
19
|
+
config[key] = 0;
|
|
20
|
+
}
|
|
21
|
+
else if (val.type === 'boolean') {
|
|
22
|
+
config[key] = false;
|
|
23
|
+
}
|
|
24
|
+
else if (val.type === 'array') {
|
|
25
|
+
config[key] = [];
|
|
26
|
+
}
|
|
27
|
+
else if (val.type === 'object') {
|
|
28
|
+
config[key] = {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Provide sensible defaults for known common keys if missing
|
|
32
|
+
if (manifest.organType === 'brain') {
|
|
33
|
+
config.provider = config.provider || 'openrouter';
|
|
34
|
+
config.model = config.model || 'anthropic/claude-3.5-sonnet';
|
|
35
|
+
config.apiKeyEnv = 'OPENROUTER_API_KEY';
|
|
36
|
+
}
|
|
37
|
+
else if (manifest.organType === 'memory') {
|
|
38
|
+
config.provider = config.provider || 'postgres';
|
|
39
|
+
}
|
|
40
|
+
else if (manifest.organType === 'voice') {
|
|
41
|
+
config.provider = config.provider || 'voicevox';
|
|
42
|
+
config.speakerId = config.speakerId || 1;
|
|
43
|
+
config.baseUrl = config.baseUrl || 'http://localhost:50021';
|
|
44
|
+
}
|
|
45
|
+
else if (manifest.organType === 'body') {
|
|
46
|
+
config.provider = config.provider || 'live2d';
|
|
47
|
+
config.initialExpression = config.initialExpression || 'neutral';
|
|
48
|
+
}
|
|
49
|
+
else if (manifest.organType === 'hands') {
|
|
50
|
+
config.defaultTimeoutMs = config.defaultTimeoutMs || 10000;
|
|
51
|
+
config.providers = config.providers || [];
|
|
52
|
+
}
|
|
53
|
+
else if (manifest.organType === 'knowledge') {
|
|
54
|
+
config.provider = config.provider || 'none';
|
|
55
|
+
}
|
|
56
|
+
else if (manifest.organType === 'behavior') {
|
|
57
|
+
config.provider = config.provider || 'active_self';
|
|
58
|
+
}
|
|
59
|
+
else if (manifest.organType === 'vision') {
|
|
60
|
+
config.provider = config.provider || 'openrouter';
|
|
61
|
+
config.model = config.model || 'gpt-4-vision';
|
|
62
|
+
}
|
|
63
|
+
return config;
|
|
64
|
+
}
|
|
65
|
+
function generateInstanceFiles(options) {
|
|
66
|
+
const instanceName = options.name || 'my-siduri';
|
|
67
|
+
const instanceId = options.id || 'default';
|
|
68
|
+
const coreVersion = options.coreVersion || '^1.0.0';
|
|
69
|
+
const manifests = options.selectedManifests;
|
|
70
|
+
// 1. package.json
|
|
71
|
+
const dependencies = {
|
|
72
|
+
'@siduri-x/core': coreVersion,
|
|
73
|
+
};
|
|
74
|
+
for (const m of manifests) {
|
|
75
|
+
dependencies[m.name] = `^${m.version || '1.0.0'}`;
|
|
76
|
+
}
|
|
77
|
+
const packageJsonObj = {
|
|
78
|
+
name: instanceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'my-siduri',
|
|
79
|
+
private: true,
|
|
80
|
+
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
|
+
},
|
|
87
|
+
dependencies,
|
|
88
|
+
};
|
|
89
|
+
const packageJson = JSON.stringify(packageJsonObj, null, 2) + '\n';
|
|
90
|
+
// 2. siduri.config.json
|
|
91
|
+
const organsConfig = {};
|
|
92
|
+
for (const m of manifests) {
|
|
93
|
+
const customConfig = options.organConfigs?.[m.configKey] || options.organConfigs?.[m.organType];
|
|
94
|
+
organsConfig[m.configKey] = customConfig || getDefaultConfigForManifest(m);
|
|
95
|
+
}
|
|
96
|
+
const configObj = {
|
|
97
|
+
$schema: './siduri.schema.json',
|
|
98
|
+
id: instanceId,
|
|
99
|
+
name: instanceName,
|
|
100
|
+
organs: organsConfig,
|
|
101
|
+
};
|
|
102
|
+
const siduriConfigJson = JSON.stringify(configObj, null, 2) + '\n';
|
|
103
|
+
// 3. siduri.schema.json
|
|
104
|
+
const organPropertiesSchema = {};
|
|
105
|
+
for (const m of manifests) {
|
|
106
|
+
organPropertiesSchema[m.configKey] = m.configSchema || { type: 'object' };
|
|
107
|
+
}
|
|
108
|
+
const schemaObj = {
|
|
109
|
+
$schema: 'http://json-schema.org/draft-07/schema#',
|
|
110
|
+
title: `Siduri Configuration Schema (${instanceName})`,
|
|
111
|
+
type: 'object',
|
|
112
|
+
required: ['id', 'name', 'organs'],
|
|
113
|
+
additionalProperties: false,
|
|
114
|
+
properties: {
|
|
115
|
+
$schema: { type: 'string' },
|
|
116
|
+
id: { type: 'string', description: 'Unique companion isolation ID' },
|
|
117
|
+
name: { type: 'string', description: 'Display name of the companion' },
|
|
118
|
+
organs: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
properties: organPropertiesSchema,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const siduriSchemaJson = JSON.stringify(schemaObj, null, 2) + '\n';
|
|
126
|
+
// 4. .env.example
|
|
127
|
+
const envLines = [];
|
|
128
|
+
for (const m of manifests) {
|
|
129
|
+
if (m.environment && m.environment.length > 0) {
|
|
130
|
+
envLines.push(`# ${m.displayName || m.name}`);
|
|
131
|
+
for (const envVar of m.environment) {
|
|
132
|
+
if (envVar.description) {
|
|
133
|
+
envLines.push(`# ${envVar.description}${envVar.required ? ' (required)' : ' (optional)'}`);
|
|
134
|
+
}
|
|
135
|
+
const defaultVal = envVar.default || '';
|
|
136
|
+
envLines.push(`${envVar.name}=${defaultVal}`);
|
|
137
|
+
}
|
|
138
|
+
envLines.push('');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const envExample = envLines.length > 0 ? envLines.join('\n') : '# No external environment variables required\n';
|
|
142
|
+
// 5. src/index.js
|
|
143
|
+
const importLines = [
|
|
144
|
+
`import { readFile } from 'node:fs/promises';`,
|
|
145
|
+
`import { SiduriRuntime } from '@siduri-x/core';`,
|
|
146
|
+
];
|
|
147
|
+
for (const m of manifests) {
|
|
148
|
+
importLines.push(`import { ${m.factory} } from '${m.name}';`);
|
|
149
|
+
}
|
|
150
|
+
const instantiationLines = [];
|
|
151
|
+
const organMapEntries = [];
|
|
152
|
+
for (const m of manifests) {
|
|
153
|
+
const varName = m.configKey;
|
|
154
|
+
instantiationLines.push(`const ${varName} = new ${m.factory}(config.organs.${m.configKey});`);
|
|
155
|
+
organMapEntries.push(` ${varName},`);
|
|
156
|
+
}
|
|
157
|
+
const selectedDisplayNames = manifests.map((m) => m.displayName.split(' ')[0] || m.organType).join(', ');
|
|
158
|
+
const srcIndexJs = [
|
|
159
|
+
...importLines,
|
|
160
|
+
'',
|
|
161
|
+
`const config = JSON.parse(`,
|
|
162
|
+
` await readFile(new URL('../siduri.config.json', import.meta.url), 'utf8')`,
|
|
163
|
+
`);`,
|
|
164
|
+
'',
|
|
165
|
+
...instantiationLines,
|
|
166
|
+
'',
|
|
167
|
+
`const runtime = new SiduriRuntime(config.id, config, {`,
|
|
168
|
+
...organMapEntries,
|
|
169
|
+
`});`,
|
|
170
|
+
'',
|
|
171
|
+
`await runtime.initialize();`,
|
|
172
|
+
'',
|
|
173
|
+
`console.log(\`✓ Siduri [\${config.name}] initialized with [${selectedDisplayNames}].\`);`,
|
|
174
|
+
'',
|
|
175
|
+
].join('\n');
|
|
176
|
+
// 6. README.md
|
|
177
|
+
const readmeLines = [
|
|
178
|
+
`# ${instanceName}`,
|
|
179
|
+
'',
|
|
180
|
+
`Standalone Siduri instance generated with explicitly composed organs:`,
|
|
181
|
+
'',
|
|
182
|
+
...manifests.map((m) => `- **${m.displayName}** (\`${m.name}\`)`),
|
|
183
|
+
'',
|
|
184
|
+
'## Getting Started',
|
|
185
|
+
'',
|
|
186
|
+
'1. Install dependencies:',
|
|
187
|
+
'```bash',
|
|
188
|
+
'npm install',
|
|
189
|
+
'```',
|
|
190
|
+
'',
|
|
191
|
+
'2. Configure environment:',
|
|
192
|
+
'```bash',
|
|
193
|
+
'cp .env.example .env',
|
|
194
|
+
'```',
|
|
195
|
+
];
|
|
196
|
+
const hasMemory = manifests.some((m) => m.organType === 'memory');
|
|
197
|
+
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', '```');
|
|
199
|
+
}
|
|
200
|
+
const hasBody = manifests.some((m) => m.organType === 'body');
|
|
201
|
+
if (hasBody) {
|
|
202
|
+
readmeLines.push('', '### Avatar Assets', 'Place your Live2D Cubism model assets into `./assets/body/model/`:', '- `model.model3.json`', '- `model.moc3`', '- textures directory');
|
|
203
|
+
}
|
|
204
|
+
readmeLines.push('', '## Running the Instance', '', 'Start the companion:', '```bash', 'npm start', '```', '', 'Run diagnostics:', '```bash', 'npm run doctor', '```', '');
|
|
205
|
+
const readmeMd = readmeLines.join('\n');
|
|
206
|
+
return {
|
|
207
|
+
'package.json': packageJson,
|
|
208
|
+
'siduri.config.json': siduriConfigJson,
|
|
209
|
+
'siduri.schema.json': siduriSchemaJson,
|
|
210
|
+
'.env.example': envExample,
|
|
211
|
+
'README.md': readmeMd,
|
|
212
|
+
'src/index.js': srcIndexJs,
|
|
213
|
+
createAssetsBodyDir: hasBody,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|