@vxnus/siduri 0.1.8 → 0.1.11

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 (114) hide show
  1. package/README.md +3 -4
  2. package/dist/builtin-manifests.d.ts +2 -0
  3. package/dist/builtin-manifests.js +492 -0
  4. package/dist/clean-machine-e2e.test.d.ts +1 -0
  5. package/dist/clean-machine-e2e.test.js +236 -0
  6. package/dist/configurators/behavior.d.ts +2 -0
  7. package/dist/configurators/behavior.js +36 -0
  8. package/dist/configurators/body.d.ts +2 -0
  9. package/dist/configurators/body.js +60 -0
  10. package/dist/configurators/brain.d.ts +6 -0
  11. package/dist/configurators/brain.js +80 -0
  12. package/dist/configurators/ear.d.ts +2 -0
  13. package/dist/configurators/ear.js +28 -0
  14. package/dist/configurators/hands.d.ts +2 -0
  15. package/dist/configurators/hands.js +27 -0
  16. package/dist/configurators/index.d.ts +24 -0
  17. package/dist/configurators/index.js +78 -0
  18. package/dist/configurators/knowledge.d.ts +6 -0
  19. package/dist/configurators/knowledge.js +100 -0
  20. package/dist/configurators/memory.d.ts +2 -0
  21. package/dist/configurators/memory.js +44 -0
  22. package/dist/configurators/mouth.d.ts +2 -0
  23. package/dist/configurators/mouth.js +15 -0
  24. package/dist/configurators/observation.d.ts +2 -0
  25. package/dist/configurators/observation.js +12 -0
  26. package/dist/configurators/types.d.ts +12 -0
  27. package/dist/configurators/types.js +2 -0
  28. package/dist/configurators/vision.d.ts +2 -0
  29. package/dist/configurators/vision.js +39 -0
  30. package/dist/configurators/voice.d.ts +2 -0
  31. package/dist/configurators/voice.js +477 -0
  32. package/dist/configurators.test.d.ts +1 -0
  33. package/dist/configurators.test.js +331 -0
  34. package/dist/db.d.ts +25 -0
  35. package/dist/db.js +152 -0
  36. package/dist/discovery.d.ts +13 -0
  37. package/dist/discovery.js +87 -0
  38. package/dist/discovery.test.d.ts +1 -0
  39. package/dist/discovery.test.js +69 -0
  40. package/dist/doctor-db.test.d.ts +1 -0
  41. package/dist/doctor-db.test.js +141 -0
  42. package/dist/doctor.d.ts +19 -0
  43. package/dist/doctor.js +249 -0
  44. package/dist/generator.d.ts +20 -0
  45. package/dist/generator.js +615 -0
  46. package/dist/generator.test.d.ts +1 -0
  47. package/dist/generator.test.js +197 -0
  48. package/dist/index.d.ts +20 -0
  49. package/dist/index.js +395 -0
  50. package/dist/manifest.d.ts +33 -0
  51. package/dist/manifest.js +40 -0
  52. package/dist/providers/knowledge-hub.d.ts +37 -0
  53. package/dist/providers/knowledge-hub.js +141 -0
  54. package/dist/providers/openrouter.d.ts +32 -0
  55. package/dist/providers/openrouter.js +201 -0
  56. package/dist/release-check.d.ts +9 -0
  57. package/dist/release-check.js +133 -0
  58. package/dist/runtime-parity.test.d.ts +1 -0
  59. package/dist/runtime-parity.test.js +141 -0
  60. package/dist/schema-validator.d.ts +9 -0
  61. package/dist/schema-validator.js +107 -0
  62. package/dist/web-dist/404/index.html +1 -0
  63. package/dist/web-dist/404.html +1 -0
  64. package/dist/web-dist/__next.__PAGE__.txt +35 -0
  65. package/dist/web-dist/__next._full.txt +32 -0
  66. package/dist/web-dist/__next._tree.txt +4 -0
  67. package/dist/web-dist/_next/static/chunks/09y7khbmco_v4.js +1 -0
  68. package/dist/web-dist/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  69. package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +1 -0
  70. package/dist/web-dist/_next/static/chunks/0xhe1hasjan-9.css +1 -0
  71. package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +1 -0
  72. package/dist/web-dist/_next/static/chunks/2_duynll0m51m.js +30 -0
  73. package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +31 -0
  74. package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +1 -0
  75. package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +22 -0
  76. package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +1 -0
  77. package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +1 -0
  78. package/dist/web-dist/_next/static/media/apple-icon.2g7-yefdphkmy.png +0 -0
  79. package/dist/web-dist/_next/static/media/favicon.3rlors1ui1gw9.ico +0 -0
  80. package/dist/web-dist/_next/static/media/icon.0580nb9h-ugue.svg +4 -0
  81. package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_buildManifest.js +11 -0
  82. package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_clientMiddlewareManifest.js +1 -0
  83. package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_ssgManifest.js +1 -0
  84. package/dist/web-dist/_not-found/__next._full.txt +17 -0
  85. package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +23 -0
  86. package/dist/web-dist/_not-found/__next._tree.txt +3 -0
  87. package/dist/web-dist/_not-found/index.html +1 -0
  88. package/dist/web-dist/_not-found/index.txt +17 -0
  89. package/dist/web-dist/apple-icon.png +0 -0
  90. package/dist/web-dist/apple-touch-icon.png +0 -0
  91. package/dist/web-dist/chat/__next._full.txt +18 -0
  92. package/dist/web-dist/chat/__next._tree.txt +3 -0
  93. package/dist/web-dist/chat/__next.chat.__PAGE__.txt +24 -0
  94. package/dist/web-dist/chat/index.html +1 -0
  95. package/dist/web-dist/chat/index.txt +18 -0
  96. package/dist/web-dist/favicon.ico +0 -0
  97. package/dist/web-dist/favicon.svg +4 -0
  98. package/dist/web-dist/icon.svg +4 -0
  99. package/dist/web-dist/index.html +1 -0
  100. package/dist/web-dist/index.txt +32 -0
  101. package/dist/web-dist/live2d/live2dcubismcore.min.js +9 -0
  102. package/dist/web-dist/logo-circle.png +0 -0
  103. package/dist/web-dist/logo-circle.svg +4 -0
  104. package/dist/web-dist/logo-white.svg +3 -0
  105. package/dist/web-dist/logo.png +0 -0
  106. package/dist/web-dist/logo.svg +3 -0
  107. package/dist/web-dist/operator/__next._full.txt +18 -0
  108. package/dist/web-dist/operator/__next._tree.txt +3 -0
  109. package/dist/web-dist/operator/__next.operator.__PAGE__.txt +24 -0
  110. package/dist/web-dist/operator/index.html +1 -0
  111. package/dist/web-dist/operator/index.txt +18 -0
  112. package/dist/web-template.d.ts +2 -0
  113. package/dist/web-template.js +557 -0
  114. package/package.json +1 -1
@@ -0,0 +1,69 @@
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_path_1 = __importDefault(require("node:path"));
7
+ const discovery_1 = require("./discovery");
8
+ const generator_1 = require("./generator");
9
+ describe('Discovery & Dynamic Composition System Tests (Phase 3)', () => {
10
+ const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
11
+ test('Registry successfully discovers all 11 organ packages from workspace', () => {
12
+ const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
13
+ const manifests = registry.getAll();
14
+ expect(manifests.length).toBe(11);
15
+ const organTypes = registry.getAvailableOrganTypes().sort();
16
+ expect(organTypes).toEqual([
17
+ 'behavior',
18
+ 'body',
19
+ 'brain',
20
+ 'ear',
21
+ 'hands',
22
+ 'knowledge',
23
+ 'memory',
24
+ 'mouth',
25
+ 'observation',
26
+ 'vision',
27
+ 'voice',
28
+ ]);
29
+ });
30
+ test('Generates valid instances from discovered manifests', () => {
31
+ const registry = discovery_1.OrganRegistry.discover([rootOrgansDir]);
32
+ // Test Brain + Hands + Vision
33
+ const brain = registry.get('brain');
34
+ const hands = registry.get('hands');
35
+ const vision = registry.get('vision');
36
+ const files = (0, generator_1.generateInstanceFiles)({
37
+ name: 'robotics-agent',
38
+ selectedManifests: [brain, hands, vision],
39
+ });
40
+ const pkg = JSON.parse(files['package.json']);
41
+ expect(Object.keys(pkg.dependencies).sort()).toEqual([
42
+ '@siduri-x/brain',
43
+ '@siduri-x/core',
44
+ '@siduri-x/hands',
45
+ '@siduri-x/vision',
46
+ ]);
47
+ const schema = JSON.parse(files['siduri.schema.json']);
48
+ expect(Object.keys(schema.properties.organs.properties).sort()).toEqual(['brain', 'hands', 'vision']);
49
+ const config = JSON.parse(files['siduri.config.json']);
50
+ expect(Object.keys(config.organs).sort()).toEqual(['brain', 'hands', 'vision']);
51
+ expect(files['.env.example']).toContain('OPENROUTER_API_KEY');
52
+ expect(files['.env.example']).toContain('ACTION_POLICY_SECRET');
53
+ expect(files['.env.example']).not.toContain('DATABASE_URL');
54
+ });
55
+ test('Validates manifest contract integrity during discovery', () => {
56
+ expect(() => {
57
+ const invalidRegistry = new discovery_1.OrganRegistry();
58
+ invalidRegistry.register({});
59
+ }).toThrow(/Invalid manifest/);
60
+ });
61
+ test('Fallback to builtin organ manifests when scanning non-existent search roots', () => {
62
+ const registry = discovery_1.OrganRegistry.discover(['/non-existent-directory/empty']);
63
+ const manifests = registry.getAll();
64
+ expect(manifests.length).toBe(11);
65
+ expect(registry.get('brain')).toBeDefined();
66
+ expect(registry.get('memory')).toBeDefined();
67
+ expect(registry.get('mouth')).toBeDefined();
68
+ });
69
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
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
+ 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);
50
+ }
51
+ }
52
+ // 1. Doctor without OPENROUTER_API_KEY -> FAIL (Brain Health Probe fails)
53
+ const doctorFail = await (0, doctor_1.runDoctor)({ projectDir: instanceDir, env: { OPENROUTER_API_KEY: '' } });
54
+ expect(doctorFail.passed).toBe(false);
55
+ expect(doctorFail.results.some((r) => r.category === 'Health Probe' && r.status === 'FAIL')).toBe(true);
56
+ // Database check must be SKIPPED
57
+ expect(doctorFail.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
58
+ // 2. Doctor with OPENROUTER_API_KEY -> PASS
59
+ const doctorPass = await (0, doctor_1.runDoctor)({
60
+ projectDir: instanceDir,
61
+ env: { OPENROUTER_API_KEY: 'test-key-123' },
62
+ });
63
+ expect(doctorPass.passed).toBe(true);
64
+ // 3. db push -> NOOP ("No database migrations are required...")
65
+ const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
66
+ expect(dbResult.status).toBe('NOOP');
67
+ expect(dbResult.message).toContain('No database migrations are required');
68
+ });
69
+ test('Test Matrix B: Brain + Hands instance doctor and db push', async () => {
70
+ const instanceDir = node_path_1.default.join(testTempRoot, 'brain-hands');
71
+ node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
72
+ const files = (0, generator_1.generateInstanceFiles)({
73
+ name: 'BrainHandsInstance',
74
+ selectedManifests: [brain, hands],
75
+ });
76
+ for (const [name, content] of Object.entries(files)) {
77
+ if (typeof content === 'string') {
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);
81
+ }
82
+ }
83
+ // Doctor checks both keys
84
+ const doctorRes = await (0, doctor_1.runDoctor)({
85
+ projectDir: instanceDir,
86
+ env: {
87
+ OPENROUTER_API_KEY: 'test-openrouter-key',
88
+ ACTION_POLICY_SECRET: 'test-policy-secret',
89
+ },
90
+ });
91
+ expect(doctorRes.passed).toBe(true);
92
+ expect(doctorRes.results.some((r) => r.name === 'OPENROUTER_API_KEY' && r.status === 'PASS')).toBe(true);
93
+ expect(doctorRes.results.some((r) => r.name === 'ACTION_POLICY_SECRET' && r.status === 'PASS')).toBe(true);
94
+ // Database check must be SKIPPED
95
+ expect(doctorRes.results.some((r) => r.category === 'Database' && r.status === 'SKIPPED')).toBe(true);
96
+ // db push returns NOOP
97
+ const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
98
+ expect(dbResult.status).toBe('NOOP');
99
+ });
100
+ test('Test Matrix C: Brain + Memory instance requires DATABASE_URL', async () => {
101
+ const instanceDir = node_path_1.default.join(testTempRoot, 'brain-memory');
102
+ node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
103
+ const files = (0, generator_1.generateInstanceFiles)({
104
+ name: 'BrainMemoryInstance',
105
+ selectedManifests: [brain, memory],
106
+ });
107
+ for (const [name, content] of Object.entries(files)) {
108
+ if (typeof content === 'string') {
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);
112
+ }
113
+ }
114
+ // 1. Doctor fails without DATABASE_URL
115
+ const doctorMissingDb = await (0, doctor_1.runDoctor)({
116
+ projectDir: instanceDir,
117
+ env: { OPENROUTER_API_KEY: 'test-key' },
118
+ });
119
+ expect(doctorMissingDb.passed).toBe(false);
120
+ expect(doctorMissingDb.results.some((r) => r.name === 'DATABASE_URL' && r.status === 'FAIL')).toBe(true);
121
+ // 2. db push fails clearly without DATABASE_URL
122
+ await expect((0, db_1.runDbPush)({ projectDir: instanceDir, env: {} })).rejects.toThrow(/DATABASE_URL is required/);
123
+ });
124
+ test('Test Matrix D: Diagnostics and Error Handling for invalid or missing configs', async () => {
125
+ // 1. Missing siduri.config.json
126
+ const nonExistentDir = node_path_1.default.join(testTempRoot, 'non-existent-dir');
127
+ await expect((0, doctor_1.runDoctor)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
128
+ await expect((0, db_1.runDbPush)({ projectDir: nonExistentDir })).rejects.toThrow(/siduri.config.json not found/);
129
+ // 2. Unreachable PostgreSQL host reported gracefully by doctor
130
+ const instDir = node_path_1.default.join(testTempRoot, 'brain-memory');
131
+ const doctorUnreachable = await (0, doctor_1.runDoctor)({
132
+ projectDir: instDir,
133
+ env: {
134
+ OPENROUTER_API_KEY: 'test-key',
135
+ DATABASE_URL: 'postgresql://postgres:postgres@127.0.0.1:59999/non_existent_db',
136
+ },
137
+ });
138
+ expect(doctorUnreachable.passed).toBe(false);
139
+ expect(doctorUnreachable.results.some((r) => r.category === 'Database' && r.status === 'FAIL')).toBe(true);
140
+ });
141
+ });
@@ -0,0 +1,19 @@
1
+ export interface DoctorCheckResult {
2
+ category: 'Environment' | 'Services' | 'Database' | 'Health Probe' | 'Configuration';
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,249 @@
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
+ const schema_validator_1 = require("./schema-validator");
12
+ async function runDoctor(options = {}) {
13
+ const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
14
+ const configPath = node_path_1.default.join(projectDir, 'siduri.config.json');
15
+ if (!node_fs_1.default.existsSync(configPath)) {
16
+ throw new Error(`siduri.config.json not found at ${projectDir}. Make sure you are in a Siduri instance directory.`);
17
+ }
18
+ // Load .env if present in projectDir
19
+ const envFile = node_path_1.default.join(projectDir, '.env');
20
+ const fileEnv = {};
21
+ if (node_fs_1.default.existsSync(envFile)) {
22
+ const lines = node_fs_1.default.readFileSync(envFile, 'utf8').split('\n');
23
+ for (const line of lines) {
24
+ const trimmed = line.trim();
25
+ if (!trimmed || trimmed.startsWith('#'))
26
+ continue;
27
+ const eqIdx = trimmed.indexOf('=');
28
+ if (eqIdx !== -1) {
29
+ const key = trimmed.slice(0, eqIdx).trim();
30
+ const val = trimmed.slice(eqIdx + 1).trim();
31
+ fileEnv[key] = val;
32
+ }
33
+ }
34
+ }
35
+ const effectiveEnv = {
36
+ ...process.env,
37
+ ...fileEnv,
38
+ ...(options.env || {}),
39
+ };
40
+ const config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf8'));
41
+ const configuredOrgansMap = config.organs || {};
42
+ const organKeys = Object.keys(configuredOrgansMap);
43
+ const registry = discovery_1.OrganRegistry.discover([
44
+ node_path_1.default.join(projectDir, 'node_modules/@siduri-x'),
45
+ node_path_1.default.resolve(__dirname, '../../packages/organs'),
46
+ node_path_1.default.resolve(process.cwd(), 'packages/organs'),
47
+ ]);
48
+ const selectedManifests = [];
49
+ const results = [];
50
+ // 0. Schema Validation Check
51
+ const schemaPath = node_path_1.default.join(projectDir, 'siduri.schema.json');
52
+ if (node_fs_1.default.existsSync(schemaPath)) {
53
+ try {
54
+ const schema = JSON.parse(node_fs_1.default.readFileSync(schemaPath, 'utf8'));
55
+ (0, schema_validator_1.validateCompanionConfig)(config, schema);
56
+ results.push({
57
+ category: 'Configuration',
58
+ name: 'Schema Conformance',
59
+ status: 'PASS',
60
+ message: 'siduri.config.json conforms to siduri.schema.json',
61
+ });
62
+ }
63
+ catch (err) {
64
+ results.push({
65
+ category: 'Configuration',
66
+ name: 'Schema Conformance',
67
+ status: 'FAIL',
68
+ message: err.message,
69
+ remediation: 'Update siduri.config.json to conform to the generated schema.',
70
+ });
71
+ }
72
+ }
73
+ // 1. Resolve manifests for selected organs
74
+ for (const organKey of organKeys) {
75
+ const m = registry.get(organKey) || registry.getAll().find((item) => item.configKey === organKey || item.organType === organKey);
76
+ if (!m) {
77
+ results.push({
78
+ category: 'Environment',
79
+ name: `Manifest resolution: ${organKey}`,
80
+ status: 'FAIL',
81
+ message: `Could not resolve package manifest for configured organ '${organKey}'.`,
82
+ remediation: `Ensure @siduri-x/${organKey} is installed in package.json and npm install has been run.`,
83
+ });
84
+ }
85
+ else {
86
+ selectedManifests.push(m);
87
+ }
88
+ }
89
+ // 2. Check Environment Variables (deduplicated)
90
+ const checkedEnvVars = new Set();
91
+ for (const m of selectedManifests) {
92
+ for (const envVar of m.environment || []) {
93
+ if (checkedEnvVars.has(envVar.name))
94
+ continue;
95
+ checkedEnvVars.add(envVar.name);
96
+ const val = effectiveEnv[envVar.name];
97
+ const isMissing = !val || val.trim() === '';
98
+ if (isMissing) {
99
+ if (envVar.required) {
100
+ results.push({
101
+ category: 'Environment',
102
+ name: envVar.name,
103
+ status: 'FAIL',
104
+ organName: m.name,
105
+ message: `Missing required environment variable '${envVar.name}'.`,
106
+ remediation: `Set ${envVar.name} in .env or your shell environment. ${envVar.description || ''}`,
107
+ });
108
+ }
109
+ else {
110
+ results.push({
111
+ category: 'Environment',
112
+ name: envVar.name,
113
+ status: 'OPTIONAL_MISSING',
114
+ organName: m.name,
115
+ message: `Optional variable '${envVar.name}' not set.`,
116
+ });
117
+ }
118
+ }
119
+ else {
120
+ results.push({
121
+ category: 'Environment',
122
+ name: envVar.name,
123
+ status: 'PASS',
124
+ organName: m.name,
125
+ message: 'Configured',
126
+ });
127
+ }
128
+ }
129
+ }
130
+ // 3. Check External Services
131
+ for (const m of selectedManifests) {
132
+ for (const service of m.services || []) {
133
+ results.push({
134
+ category: 'Services',
135
+ name: `${service.name} (${m.organType})`,
136
+ status: 'PASS',
137
+ organName: m.name,
138
+ message: 'Service requirement declared',
139
+ });
140
+ }
141
+ }
142
+ // 4. Database Check (only if an organ declares database requirement)
143
+ const dbOrgan = selectedManifests.find((m) => m.database !== null && m.database !== undefined);
144
+ if (dbOrgan) {
145
+ const dbUrl = effectiveEnv.DATABASE_URL;
146
+ if (!dbUrl) {
147
+ results.push({
148
+ category: 'Database',
149
+ name: 'PostgreSQL Connection',
150
+ status: 'FAIL',
151
+ organName: dbOrgan.name,
152
+ message: 'DATABASE_URL is not configured.',
153
+ remediation: 'Provide DATABASE_URL in .env (e.g. postgresql://postgres:postgres@localhost:5432/siduri)',
154
+ });
155
+ }
156
+ else {
157
+ const pool = new pg_1.Pool({ connectionString: dbUrl, connectionTimeoutMillis: 3000 });
158
+ try {
159
+ const client = await pool.connect();
160
+ await client.query('SELECT 1');
161
+ client.release();
162
+ await pool.end();
163
+ results.push({
164
+ category: 'Database',
165
+ name: 'PostgreSQL Connection',
166
+ status: 'PASS',
167
+ organName: dbOrgan.name,
168
+ message: 'Connection successful',
169
+ });
170
+ }
171
+ catch (err) {
172
+ await pool.end().catch(() => { });
173
+ results.push({
174
+ category: 'Database',
175
+ name: 'PostgreSQL Connection',
176
+ status: 'FAIL',
177
+ organName: dbOrgan.name,
178
+ message: `Connection failed: ${err.message}`,
179
+ remediation: 'Check database host availability, port, credentials, and network accessibility.',
180
+ });
181
+ }
182
+ }
183
+ }
184
+ else {
185
+ results.push({
186
+ category: 'Database',
187
+ name: 'PostgreSQL Database',
188
+ status: 'SKIPPED',
189
+ message: 'Not required by current composition',
190
+ });
191
+ }
192
+ // 5. Organ Health Probes
193
+ for (const m of selectedManifests) {
194
+ if (m.healthCheck) {
195
+ try {
196
+ // Resolve package entrypoint
197
+ const candidateEntrypoints = [
198
+ node_path_1.default.resolve(projectDir, 'node_modules', m.name, m.entrypoint),
199
+ node_path_1.default.resolve(__dirname, '../../packages/organs', m.organType, m.entrypoint),
200
+ node_path_1.default.resolve(process.cwd(), 'packages/organs', m.organType, m.entrypoint),
201
+ ];
202
+ const entryPath = candidateEntrypoints.find((p) => node_fs_1.default.existsSync(p));
203
+ if (entryPath) {
204
+ const mod = require(entryPath);
205
+ const probeFn = mod[m.healthCheck];
206
+ if (typeof probeFn === 'function') {
207
+ const organConf = configuredOrgansMap[m.configKey] || configuredOrgansMap[m.organType];
208
+ const probeRes = await probeFn({ config: organConf, env: effectiveEnv });
209
+ if (probeRes && probeRes.ok === false) {
210
+ results.push({
211
+ category: 'Health Probe',
212
+ name: `${m.displayName} Probe`,
213
+ status: 'FAIL',
214
+ organName: m.name,
215
+ message: probeRes.message || 'Health probe check failed',
216
+ remediation: `Check ${m.displayName} configuration in siduri.config.json.`,
217
+ });
218
+ }
219
+ else {
220
+ results.push({
221
+ category: 'Health Probe',
222
+ name: `${m.displayName} Probe`,
223
+ status: 'PASS',
224
+ organName: m.name,
225
+ message: probeRes?.message || 'Operational',
226
+ });
227
+ }
228
+ }
229
+ }
230
+ }
231
+ catch (err) {
232
+ results.push({
233
+ category: 'Health Probe',
234
+ name: `${m.displayName} Probe`,
235
+ status: 'FAIL',
236
+ organName: m.name,
237
+ message: `Probe invocation failed: ${err.message}`,
238
+ });
239
+ }
240
+ }
241
+ }
242
+ const passed = results.every((r) => r.status !== 'FAIL');
243
+ return {
244
+ instanceName: config.name || 'Siduri Instance',
245
+ configuredOrgans: organKeys,
246
+ results,
247
+ passed,
248
+ };
249
+ }
@@ -0,0 +1,20 @@
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
+ 'public/index.html': string;
10
+ createAssetsBodyDir?: boolean;
11
+ createAssetsDirs?: string[];
12
+ }
13
+ export interface InstanceGeneratorOptions {
14
+ name: string;
15
+ id?: string;
16
+ selectedManifests: OrganManifest[];
17
+ organConfigs?: Record<string, any>;
18
+ coreVersion?: string;
19
+ }
20
+ export declare function generateInstanceFiles(options: InstanceGeneratorOptions): GeneratedInstanceFiles;