siduri 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/README.md +84 -0
- package/dist/builtin-manifests.d.ts +2 -0
- package/dist/builtin-manifests.js +524 -0
- package/dist/clean-machine-e2e.test.d.ts +1 -0
- package/dist/clean-machine-e2e.test.js +160 -0
- package/dist/clean-machine-smoke.test.d.ts +1 -0
- package/dist/clean-machine-smoke.test.js +165 -0
- package/dist/colors.d.ts +8 -0
- package/dist/colors.js +11 -0
- package/dist/configurators/behavior.d.ts +2 -0
- package/dist/configurators/behavior.js +77 -0
- package/dist/configurators/body.d.ts +2 -0
- package/dist/configurators/body.js +107 -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 +43 -0
- package/dist/configurators/hands.d.ts +2 -0
- package/dist/configurators/hands.js +43 -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 +146 -0
- package/dist/configurators/memory.d.ts +2 -0
- package/dist/configurators/memory.js +26 -0
- package/dist/configurators/mouth.d.ts +2 -0
- package/dist/configurators/mouth.js +35 -0
- package/dist/configurators/observation.d.ts +2 -0
- package/dist/configurators/observation.js +38 -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 +47 -0
- package/dist/configurators/voice.d.ts +2 -0
- package/dist/configurators/voice.js +373 -0
- package/dist/configurators.test.d.ts +1 -0
- package/dist/configurators.test.js +464 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +15 -0
- package/dist/discovery.d.ts +13 -0
- package/dist/discovery.js +85 -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 +52 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +266 -0
- package/dist/generator.d.ts +23 -0
- package/dist/generator.js +1094 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +252 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +607 -0
- package/dist/manifest.d.ts +33 -0
- package/dist/manifest.js +40 -0
- package/dist/project-dir.test.d.ts +1 -0
- package/dist/project-dir.test.js +37 -0
- package/dist/providers/knowledge-hub.d.ts +44 -0
- package/dist/providers/knowledge-hub.js +162 -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 +128 -0
- package/dist/runtime-parity.test.d.ts +1 -0
- package/dist/runtime-parity.test.js +141 -0
- package/dist/schema-validator.d.ts +9 -0
- package/dist/schema-validator.js +107 -0
- package/dist/web-dist/404/index.html +1 -0
- package/dist/web-dist/404.html +1 -0
- package/dist/web-dist/__next.__PAGE__.txt +34 -0
- package/dist/web-dist/__next._full.txt +32 -0
- package/dist/web-dist/__next._tree.txt +4 -0
- package/dist/web-dist/_next/static/4aL_iP3FHsLSyHzCnRQYD/_buildManifest.js +1 -0
- package/dist/web-dist/_next/static/4aL_iP3FHsLSyHzCnRQYD/_ssgManifest.js +1 -0
- package/dist/web-dist/_next/static/chunks/136-1d10af93b770a1c8.js +52 -0
- package/dist/web-dist/_next/static/chunks/1629039a.f535b3ff45566416.js +1 -0
- package/dist/web-dist/_next/static/chunks/202.59b8eb8a8c45f711.js +1 -0
- package/dist/web-dist/_next/static/chunks/2be4ccb0.ab2975eb50b4e166.js +1 -0
- package/dist/web-dist/_next/static/chunks/411.d68b2e48424f1e47.js +1 -0
- package/dist/web-dist/_next/static/chunks/426-9f7f6e7fffd4c3c3.js +1 -0
- package/dist/web-dist/_next/static/chunks/563b947f-8e2871a2ab7cd91e.js +1 -0
- package/dist/web-dist/_next/static/chunks/92e71660.11f182d50bce2be0.js +342 -0
- package/dist/web-dist/_next/static/chunks/app/_global-error/page-bce6d30fb2b1a3b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/app/_not-found/page-74658f4988eabcc7.js +1 -0
- package/dist/web-dist/_next/static/chunks/app/chat/page-ab56048b707a3ddd.js +34 -0
- package/dist/web-dist/_next/static/chunks/app/layout-14f1a3f31e58be17.js +1 -0
- package/dist/web-dist/_next/static/chunks/app/operator/page-6b2d0ba884183ad7.js +1 -0
- package/dist/web-dist/_next/static/chunks/app/page-fc4bf56cf33ae27b.js +1 -0
- package/dist/web-dist/_next/static/chunks/framework-7afb7035d9872ea0.js +1 -0
- package/dist/web-dist/_next/static/chunks/main-8553b996986f1210.js +5 -0
- package/dist/web-dist/_next/static/chunks/main-app-9e6a144626e5a5b9.js +1 -0
- package/dist/web-dist/_next/static/chunks/next/dist/client/components/builtin/app-error-bce6d30fb2b1a3b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/next/dist/client/components/builtin/forbidden-bce6d30fb2b1a3b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/next/dist/client/components/builtin/global-error-68ea4ac05dcaf701.js +1 -0
- package/dist/web-dist/_next/static/chunks/next/dist/client/components/builtin/not-found-bce6d30fb2b1a3b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/next/dist/client/components/builtin/unauthorized-bce6d30fb2b1a3b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web-dist/_next/static/chunks/webpack-9073f434bc17ce16.js +1 -0
- package/dist/web-dist/_next/static/css/ceeb8e5596664b11.css +1 -0
- package/dist/web-dist/_not-found/__next._full.txt +17 -0
- package/dist/web-dist/_not-found/__next._not-found.__PAGE__.txt +23 -0
- package/dist/web-dist/_not-found/__next._tree.txt +3 -0
- package/dist/web-dist/_not-found/index.html +1 -0
- package/dist/web-dist/_not-found/index.txt +17 -0
- package/dist/web-dist/apple-icon.png +0 -0
- package/dist/web-dist/apple-touch-icon.png +0 -0
- package/dist/web-dist/chat/__next._full.txt +18 -0
- package/dist/web-dist/chat/__next._tree.txt +3 -0
- package/dist/web-dist/chat/__next.chat.__PAGE__.txt +24 -0
- package/dist/web-dist/chat/index.html +1 -0
- package/dist/web-dist/chat/index.txt +18 -0
- package/dist/web-dist/favicon.ico +0 -0
- package/dist/web-dist/favicon.svg +4 -0
- package/dist/web-dist/icon.svg +4 -0
- package/dist/web-dist/index.html +1 -0
- package/dist/web-dist/index.txt +32 -0
- package/dist/web-dist/live2d/live2dcubismcore.min.js +9 -0
- package/dist/web-dist/logo-circle.png +0 -0
- package/dist/web-dist/logo-circle.svg +4 -0
- package/dist/web-dist/logo-white.svg +3 -0
- package/dist/web-dist/logo.png +0 -0
- package/dist/web-dist/logo.svg +3 -0
- package/dist/web-dist/operator/__next._full.txt +18 -0
- package/dist/web-dist/operator/__next._tree.txt +3 -0
- package/dist/web-dist/operator/__next.operator.__PAGE__.txt +24 -0
- package/dist/web-dist/operator/index.html +1 -0
- package/dist/web-dist/operator/index.txt +18 -0
- package/dist/web-template.d.ts +2 -0
- package/dist/web-template.js +589 -0
- package/package.json +44 -0
|
@@ -0,0 +1,464 @@
|
|
|
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 inquirer_1 = __importDefault(require("inquirer"));
|
|
7
|
+
const discovery_1 = require("./discovery");
|
|
8
|
+
const generator_1 = require("./generator");
|
|
9
|
+
const index_1 = require("./index");
|
|
10
|
+
const openrouter_1 = require("./providers/openrouter");
|
|
11
|
+
const knowledge_hub_1 = require("./providers/knowledge-hub");
|
|
12
|
+
const brain_1 = require("./configurators/brain");
|
|
13
|
+
const knowledge_1 = require("./configurators/knowledge");
|
|
14
|
+
const memory_1 = require("./configurators/memory");
|
|
15
|
+
const voice_1 = require("./configurators/voice");
|
|
16
|
+
const body_1 = require("./configurators/body");
|
|
17
|
+
const hands_1 = require("./configurators/hands");
|
|
18
|
+
const behavior_1 = require("./configurators/behavior");
|
|
19
|
+
const vision_1 = require("./configurators/vision");
|
|
20
|
+
const ear_1 = require("./configurators/ear");
|
|
21
|
+
const observation_1 = require("./configurators/observation");
|
|
22
|
+
// Mock inquirer.prompt
|
|
23
|
+
jest.mock('inquirer', () => ({
|
|
24
|
+
prompt: jest.fn(),
|
|
25
|
+
Separator: jest.fn((label) => ({ type: 'separator', line: label })),
|
|
26
|
+
}));
|
|
27
|
+
describe('Guided Manifest-Driven Configuration UX Specification Tests', () => {
|
|
28
|
+
const registry = discovery_1.OrganRegistry.discover();
|
|
29
|
+
const brainManifest = registry.get('brain');
|
|
30
|
+
const knowledgeManifest = registry.get('knowledge');
|
|
31
|
+
const archiveManifest = registry.get('archive');
|
|
32
|
+
const voiceManifest = registry.get('voice');
|
|
33
|
+
const bodyManifest = registry.get('body');
|
|
34
|
+
const handsManifest = registry.get('hands');
|
|
35
|
+
const behaviorManifest = registry.get('behavior');
|
|
36
|
+
const visionManifest = registry.get('vision');
|
|
37
|
+
const earManifest = registry.get('ear');
|
|
38
|
+
const observationManifest = registry.get('observation');
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
jest.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
describe('Discrepancy #1: OpenRouter Model Discovery & Brain Configurator', () => {
|
|
43
|
+
test('OpenRouter model provider parses model catalog correctly', async () => {
|
|
44
|
+
const mockModels = [
|
|
45
|
+
{ id: 'openai/gpt-4o-mini', name: 'OpenAI: GPT-4o Mini', description: 'Fast model', context_length: 128000 },
|
|
46
|
+
{ id: 'anthropic/claude-3.5-sonnet', name: 'Anthropic: Claude 3.5 Sonnet', context_length: 200000 },
|
|
47
|
+
];
|
|
48
|
+
const globalFetch = global.fetch;
|
|
49
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
50
|
+
ok: true,
|
|
51
|
+
json: async () => ({ data: mockModels }),
|
|
52
|
+
});
|
|
53
|
+
const provider = new openrouter_1.OpenRouterModelProvider('https://mock-api.com/models');
|
|
54
|
+
const models = await provider.listModels();
|
|
55
|
+
expect(models.length).toBe(2);
|
|
56
|
+
expect(models[0].id).toBe('openai/gpt-4o-mini');
|
|
57
|
+
expect(models[0].name).toBe('OpenAI: GPT-4o Mini');
|
|
58
|
+
expect(models[1].id).toBe('anthropic/claude-3.5-sonnet');
|
|
59
|
+
global.fetch = globalFetch;
|
|
60
|
+
});
|
|
61
|
+
test('Brain configurator selects OpenRouter model and retains canonical model ID', async () => {
|
|
62
|
+
const mockProvider = {
|
|
63
|
+
listModels: jest.fn().mockResolvedValue([
|
|
64
|
+
{ id: 'openai/gpt-4o-mini', name: 'OpenAI: GPT-4o Mini' },
|
|
65
|
+
{ id: 'anthropic/claude-3.5-sonnet', name: 'Anthropic: Claude 3.5 Sonnet' },
|
|
66
|
+
]),
|
|
67
|
+
};
|
|
68
|
+
inquirer_1.default.prompt
|
|
69
|
+
.mockResolvedValueOnce({ provider: 'openrouter' }) // Brain provider
|
|
70
|
+
.mockResolvedValueOnce({ selectedValue: 'openai/gpt-4o-mini' }); // Select model
|
|
71
|
+
const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: mockProvider });
|
|
72
|
+
expect(result.config.provider).toBe('openrouter');
|
|
73
|
+
expect(result.config.model).toBe('openai/gpt-4o-mini');
|
|
74
|
+
expect(result.summary?.Model).toBe('openai/gpt-4o-mini');
|
|
75
|
+
expect(result.summary?.Provider).toBe('OpenRouter');
|
|
76
|
+
});
|
|
77
|
+
test('Brain configurator handles discovery failure with curated fallback models', async () => {
|
|
78
|
+
const failingProvider = {
|
|
79
|
+
listModels: jest.fn().mockRejectedValue(new Error('Network request failed')),
|
|
80
|
+
};
|
|
81
|
+
inquirer_1.default.prompt
|
|
82
|
+
.mockResolvedValueOnce({ provider: 'openrouter' }) // Brain provider
|
|
83
|
+
.mockResolvedValueOnce({ failureAction: 'curated' }) // Pick curated fallback
|
|
84
|
+
.mockResolvedValueOnce({ selectedValue: 'anthropic/claude-3.5-sonnet' }); // Select model
|
|
85
|
+
const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: failingProvider });
|
|
86
|
+
expect(result.config.provider).toBe('openrouter');
|
|
87
|
+
expect(result.config.model).toBe('anthropic/claude-3.5-sonnet');
|
|
88
|
+
});
|
|
89
|
+
test('Brain configurator handles discovery failure with manual fallback entry', async () => {
|
|
90
|
+
const failingProvider = {
|
|
91
|
+
listModels: jest.fn().mockRejectedValue(new Error('Network request failed')),
|
|
92
|
+
};
|
|
93
|
+
inquirer_1.default.prompt
|
|
94
|
+
.mockResolvedValueOnce({ provider: 'openrouter' })
|
|
95
|
+
.mockResolvedValueOnce({ failureAction: 'manual' })
|
|
96
|
+
.mockResolvedValueOnce({ manualId: 'mistralai/mistral-large' });
|
|
97
|
+
const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest }, { modelProvider: failingProvider });
|
|
98
|
+
expect(result.config.provider).toBe('openrouter');
|
|
99
|
+
expect(result.config.model).toBe('mistralai/mistral-large');
|
|
100
|
+
});
|
|
101
|
+
test('Brain configurator supports OpenAI-compatible / Custom endpoint', async () => {
|
|
102
|
+
inquirer_1.default.prompt
|
|
103
|
+
.mockResolvedValueOnce({ provider: 'openai-compatible' })
|
|
104
|
+
.mockResolvedValueOnce({
|
|
105
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
106
|
+
model: 'llama3:latest',
|
|
107
|
+
apiKeyEnv: 'OLLAMA_API_KEY',
|
|
108
|
+
});
|
|
109
|
+
const result = await (0, brain_1.configureBrain)({ companionName: 'Sparkle', manifest: brainManifest });
|
|
110
|
+
expect(result.config.provider).toBe('openai-compatible');
|
|
111
|
+
expect(result.config.model).toBe('llama3:latest');
|
|
112
|
+
expect(result.config.baseUrl).toBe('http://localhost:11434/v1');
|
|
113
|
+
expect(result.config.apiKeyEnv).toBe('OLLAMA_API_KEY');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('Discrepancy #2 & #3: Knowledge Choices & Manifest Metadata Confirmation', () => {
|
|
117
|
+
test('Declining external pack uses sovereign Life Database by default and skips discovery', async () => {
|
|
118
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ useExternalPack: false });
|
|
119
|
+
const result = await (0, knowledge_1.configureKnowledge)({
|
|
120
|
+
companionName: 'Sparkle',
|
|
121
|
+
manifest: knowledgeManifest,
|
|
122
|
+
});
|
|
123
|
+
expect(result.config.provider).toBe('unified');
|
|
124
|
+
expect(result.config.lifeDatabase).toBe(true);
|
|
125
|
+
expect(result.summary?.['Life Database']).toBe('Enabled (siduri.sqlite)');
|
|
126
|
+
expect(result.summary?.['E-Pack']).toBe('None');
|
|
127
|
+
});
|
|
128
|
+
test('Attaching E Knowledge Hub pack discovers manifest and configures remote provider automatically', async () => {
|
|
129
|
+
const mockClient = {
|
|
130
|
+
resolveProvider: jest.fn().mockResolvedValue({
|
|
131
|
+
name: 'e-teyvat',
|
|
132
|
+
displayName: 'E Teyvat',
|
|
133
|
+
package: '@vxnus/e-teyvat',
|
|
134
|
+
version: '1.2.0',
|
|
135
|
+
description: 'Teyvat knowledge provider for Siduri.',
|
|
136
|
+
capabilities: ['Search', 'Retrieval', 'Context injection'],
|
|
137
|
+
distributionType: 'remote',
|
|
138
|
+
distribution: {
|
|
139
|
+
kind: 'provider',
|
|
140
|
+
url: 'https://teyvat.e.vxnus.xyz',
|
|
141
|
+
},
|
|
142
|
+
source: 'E Knowledge Hub',
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
inquirer_1.default.prompt
|
|
146
|
+
.mockResolvedValueOnce({ useExternalPack: true }) // Attach external pack
|
|
147
|
+
.mockResolvedValueOnce({ packId: '@vxnus/e-teyvat' }); // Package ID
|
|
148
|
+
const result = await (0, knowledge_1.configureKnowledge)({ companionName: 'Sparkle', manifest: knowledgeManifest }, { client: mockClient });
|
|
149
|
+
expect(result.config.provider).toBe('e-hub');
|
|
150
|
+
expect(result.config.packId).toBe('@vxnus/e-teyvat');
|
|
151
|
+
expect(result.summary?.Source).toBe('E Knowledge Hub');
|
|
152
|
+
expect(result.summary?.Provider).toBe('E Teyvat');
|
|
153
|
+
expect(result.summary?.Package).toBe('@vxnus/e-teyvat');
|
|
154
|
+
expect(result.summary?.Version).toBe('1.2.0');
|
|
155
|
+
expect(result.summary?.['E-Pack']).toContain('Remote');
|
|
156
|
+
});
|
|
157
|
+
test('Attaching hybrid pack allows choosing between remote and local with automated path', async () => {
|
|
158
|
+
const mockClient = {
|
|
159
|
+
resolveProvider: jest.fn().mockResolvedValue({
|
|
160
|
+
name: 'e-teyvat',
|
|
161
|
+
displayName: 'E Teyvat',
|
|
162
|
+
package: '@vxnus/e-teyvat',
|
|
163
|
+
version: '1.2.0',
|
|
164
|
+
description: 'Teyvat knowledge provider for Siduri.',
|
|
165
|
+
capabilities: ['Search', 'Retrieval', 'Context injection'],
|
|
166
|
+
distributionType: 'both',
|
|
167
|
+
distribution: {
|
|
168
|
+
kind: 'archive',
|
|
169
|
+
url: 'https://knowledge.e.vxnus.xyz/archives/teyvat.tar.gz',
|
|
170
|
+
},
|
|
171
|
+
source: 'E Knowledge Hub',
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
inquirer_1.default.prompt
|
|
175
|
+
.mockResolvedValueOnce({ useExternalPack: true })
|
|
176
|
+
.mockResolvedValueOnce({ packId: '@vxnus/e-teyvat' })
|
|
177
|
+
.mockResolvedValueOnce({ selectedMode: 'local' });
|
|
178
|
+
const result = await (0, knowledge_1.configureKnowledge)({ companionName: 'Sparkle', manifest: knowledgeManifest }, { client: mockClient });
|
|
179
|
+
expect(result.config.provider).toBe('e-knowledge');
|
|
180
|
+
expect(result.config.packPath).toBe('./assets/knowledge/vxnus-e-teyvat');
|
|
181
|
+
expect(result.summary?.['E-Pack']).toContain('Local @ ./assets/knowledge/vxnus-e-teyvat');
|
|
182
|
+
});
|
|
183
|
+
test('Knowledge manifest validation enforces name and version', () => {
|
|
184
|
+
expect(() => (0, knowledge_hub_1.validateKnowledgeManifest)({})).toThrow('Invalid knowledge manifest');
|
|
185
|
+
expect(() => (0, knowledge_hub_1.validateKnowledgeManifest)({ name: 'test' })).toThrow('missing \'version\'');
|
|
186
|
+
const valid = (0, knowledge_hub_1.validateKnowledgeManifest)({
|
|
187
|
+
name: 'e-teyvat',
|
|
188
|
+
displayName: 'E Teyvat',
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
description: 'Teyvat lore',
|
|
191
|
+
}, '@vxnus/e-teyvat');
|
|
192
|
+
expect(valid.name).toBe('e-teyvat');
|
|
193
|
+
expect(valid.package).toBe('@vxnus/e-teyvat');
|
|
194
|
+
expect(valid.version).toBe('1.0.0');
|
|
195
|
+
});
|
|
196
|
+
test('extractCapabilitiesList formats object and array capabilities correctly', () => {
|
|
197
|
+
const arrayCaps = (0, knowledge_hub_1.extractCapabilitiesList)(['Search', 'Retrieval', 'Custom Tool']);
|
|
198
|
+
expect(arrayCaps).toEqual(['Search', 'Retrieval', 'Custom Tool']);
|
|
199
|
+
const objCaps = (0, knowledge_hub_1.extractCapabilitiesList)({
|
|
200
|
+
search: true,
|
|
201
|
+
retrieval: true,
|
|
202
|
+
contextInjection: true,
|
|
203
|
+
semanticSearch: true,
|
|
204
|
+
});
|
|
205
|
+
expect(objCaps).toContain('Search');
|
|
206
|
+
expect(objCaps).toContain('Retrieval');
|
|
207
|
+
expect(objCaps).toContain('Context injection');
|
|
208
|
+
expect(objCaps).toContain('Semantic search');
|
|
209
|
+
});
|
|
210
|
+
test('Knowledge Hub client fallback returns known pack for @vxnus/e-teyvat', async () => {
|
|
211
|
+
const client = new knowledge_hub_1.KnowledgeHubClient('https://invalid-non-existent-url.local/api');
|
|
212
|
+
const manifest = await client.resolveProvider('@vxnus/e-teyvat');
|
|
213
|
+
expect(manifest.package).toBe('@vxnus/e-teyvat');
|
|
214
|
+
expect(manifest.displayName).toBe('E Teyvat');
|
|
215
|
+
expect(manifest.version).toBe('1.2.0');
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('Other Organ Configurators & Generic Router', () => {
|
|
219
|
+
test('Memory configurator configures SQLite with WAL and FTS5', async () => {
|
|
220
|
+
inquirer_1.default.prompt
|
|
221
|
+
.mockResolvedValueOnce({ provider: 'sqlite' });
|
|
222
|
+
const result = await (0, memory_1.configureMemory)({ companionName: 'Sparkle', manifest: archiveManifest });
|
|
223
|
+
expect(result.config.provider).toBe('sqlite');
|
|
224
|
+
expect(result.summary?.Database).toBe('SQLite (WAL + FTS5)');
|
|
225
|
+
expect(result.summary?.Storage).toBe('siduri.sqlite');
|
|
226
|
+
});
|
|
227
|
+
test('Voice configurator configures VOICEVOX engine and speaker ID', async () => {
|
|
228
|
+
inquirer_1.default.prompt
|
|
229
|
+
.mockResolvedValueOnce({ provider: 'voicevox' })
|
|
230
|
+
.mockResolvedValueOnce({ baseUrl: 'http://localhost:50021' })
|
|
231
|
+
.mockResolvedValueOnce({ speaker: { id: 2, label: '四国めたん (Shikoku Metan) - ノーマル (Normal) (ID: 2)' } });
|
|
232
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
233
|
+
expect(result.config.provider).toBe('voicevox');
|
|
234
|
+
expect(result.config.speakerId).toBe(2);
|
|
235
|
+
expect(result.summary?.['Speaker ID']).toBe(2);
|
|
236
|
+
});
|
|
237
|
+
test('Voice configurator configures RVC with Base TTS Engine', async () => {
|
|
238
|
+
inquirer_1.default.prompt
|
|
239
|
+
.mockResolvedValueOnce({ provider: 'rvc' })
|
|
240
|
+
.mockResolvedValueOnce({
|
|
241
|
+
modelPath: './assets/voice/sparkle/sparkle.pth',
|
|
242
|
+
indexPath: './assets/voice/sparkle/sparkle.index',
|
|
243
|
+
pitchShift: '12',
|
|
244
|
+
f0Method: 'rmvpe',
|
|
245
|
+
baseTts: 'edge-tts',
|
|
246
|
+
serviceUrl: 'http://localhost:50055',
|
|
247
|
+
});
|
|
248
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
249
|
+
expect(result.config.provider).toBe('edge-tts');
|
|
250
|
+
expect(result.config.rvc.enabled).toBe(true);
|
|
251
|
+
expect(result.config.rvc.pitchShift).toBe(12);
|
|
252
|
+
expect(result.summary?.['Base TTS']).toBe('edge-tts');
|
|
253
|
+
});
|
|
254
|
+
test('Voice configurator disables voice when none is selected', async () => {
|
|
255
|
+
inquirer_1.default.prompt
|
|
256
|
+
.mockResolvedValueOnce({ provider: 'none' });
|
|
257
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
258
|
+
expect(result.config.provider).toBe('none');
|
|
259
|
+
expect(result.summary?.Provider).toBe('None (Voice disabled)');
|
|
260
|
+
});
|
|
261
|
+
test('Voice configurator configures RVC with Kokoro as Base TTS Engine', async () => {
|
|
262
|
+
inquirer_1.default.prompt
|
|
263
|
+
.mockResolvedValueOnce({ provider: 'rvc' })
|
|
264
|
+
.mockResolvedValueOnce({
|
|
265
|
+
modelPath: './assets/voice/sparkle/sparkle.pth',
|
|
266
|
+
indexPath: './assets/voice/sparkle/sparkle.index',
|
|
267
|
+
pitchShift: '0',
|
|
268
|
+
f0Method: 'pm',
|
|
269
|
+
baseTts: 'kokoro',
|
|
270
|
+
serviceUrl: 'http://localhost:50055',
|
|
271
|
+
});
|
|
272
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
273
|
+
expect(result.config.provider).toBe('kokoro');
|
|
274
|
+
expect(result.config.rvc.enabled).toBe(true);
|
|
275
|
+
expect(result.summary?.['Base TTS']).toBe('kokoro');
|
|
276
|
+
});
|
|
277
|
+
test('Voice configurator configures RVC with Piper as Base TTS Engine', async () => {
|
|
278
|
+
inquirer_1.default.prompt
|
|
279
|
+
.mockResolvedValueOnce({ provider: 'rvc' })
|
|
280
|
+
.mockResolvedValueOnce({
|
|
281
|
+
modelPath: './assets/voice/sparkle/sparkle.pth',
|
|
282
|
+
indexPath: '',
|
|
283
|
+
pitchShift: '-6',
|
|
284
|
+
f0Method: 'harvest',
|
|
285
|
+
baseTts: 'piper',
|
|
286
|
+
serviceUrl: 'http://localhost:50055',
|
|
287
|
+
});
|
|
288
|
+
const result = await (0, voice_1.configureVoice)({ companionName: 'Sparkle', manifest: voiceManifest });
|
|
289
|
+
expect(result.config.provider).toBe('piper');
|
|
290
|
+
expect(result.config.rvc.enabled).toBe(true);
|
|
291
|
+
expect(result.config.rvc.pitchShift).toBe(-6);
|
|
292
|
+
expect(result.summary?.['Base TTS']).toBe('piper');
|
|
293
|
+
});
|
|
294
|
+
test('Body configurator configures Live2D and custom model path', async () => {
|
|
295
|
+
inquirer_1.default.prompt
|
|
296
|
+
.mockResolvedValueOnce({ provider: 'live2d' })
|
|
297
|
+
.mockResolvedValueOnce({
|
|
298
|
+
modelSource: './assets/body/sparkle/model.model3.json',
|
|
299
|
+
});
|
|
300
|
+
const result = await (0, body_1.configureBody)({ companionName: 'Sparkle', manifest: bodyManifest });
|
|
301
|
+
expect(result.config.provider).toBe('live2d');
|
|
302
|
+
expect(result.config.modelPath).toBe('./assets/body/sparkle/model.model3.json');
|
|
303
|
+
expect(result.config.modelUrl).toBe('/assets/body/sparkle/model.model3.json');
|
|
304
|
+
expect(result.config.initialExpression).toBe('neutral');
|
|
305
|
+
expect(result.summary?.['Model Path']).toBe('./assets/body/sparkle/model.model3.json');
|
|
306
|
+
});
|
|
307
|
+
test('Body configurator defaults model path to assets/body/default', async () => {
|
|
308
|
+
inquirer_1.default.prompt
|
|
309
|
+
.mockResolvedValueOnce({ provider: 'live2d' })
|
|
310
|
+
.mockResolvedValueOnce({
|
|
311
|
+
modelSource: '',
|
|
312
|
+
});
|
|
313
|
+
const result = await (0, body_1.configureBody)({ companionName: 'MyCompanion', manifest: bodyManifest });
|
|
314
|
+
expect(result.config.provider).toBe('live2d');
|
|
315
|
+
expect(result.config.modelPath).toBe('./assets/body/default/model.model3.json');
|
|
316
|
+
expect(result.config.modelUrl).toBe('/assets/body/default/model.model3.json');
|
|
317
|
+
expect(result.summary?.['Model Path']).toBe('./assets/body/default/model.model3.json');
|
|
318
|
+
});
|
|
319
|
+
test('Body configurator configures VRM 3D avatar with lookAt mode', async () => {
|
|
320
|
+
inquirer_1.default.prompt
|
|
321
|
+
.mockResolvedValueOnce({ provider: 'vrm' })
|
|
322
|
+
.mockResolvedValueOnce({
|
|
323
|
+
modelSource: './assets/body/vroid/model.vrm',
|
|
324
|
+
lookAtMode: 'cursor',
|
|
325
|
+
});
|
|
326
|
+
const result = await (0, body_1.configureBody)({ companionName: 'VRoidCompanion', manifest: bodyManifest });
|
|
327
|
+
expect(result.config.provider).toBe('vrm');
|
|
328
|
+
expect(result.config.format).toBe('vrm');
|
|
329
|
+
expect(result.config.modelPath).toBe('./assets/body/vroid/model.vrm');
|
|
330
|
+
expect(result.config.modelUrl).toBe('/assets/body/vroid/model.vrm');
|
|
331
|
+
expect(result.config.vrm?.lookAtMode).toBe('cursor');
|
|
332
|
+
expect(result.summary?.Provider).toBe('VRM (3D Avatar)');
|
|
333
|
+
expect(result.summary?.['LookAt Mode']).toBe('cursor');
|
|
334
|
+
});
|
|
335
|
+
test('Body configurator disables body when none is selected', async () => {
|
|
336
|
+
inquirer_1.default.prompt
|
|
337
|
+
.mockResolvedValueOnce({ provider: 'none' });
|
|
338
|
+
const result = await (0, body_1.configureBody)({ companionName: 'HeadlessCompanion', manifest: bodyManifest });
|
|
339
|
+
expect(result.config.provider).toBe('none');
|
|
340
|
+
expect(result.summary?.Provider).toBe('None (Headless)');
|
|
341
|
+
});
|
|
342
|
+
test('Hands configurator configures MCP tool execution timeout', async () => {
|
|
343
|
+
inquirer_1.default.prompt
|
|
344
|
+
.mockResolvedValueOnce({ provider: 'mcp' })
|
|
345
|
+
.mockResolvedValueOnce({ timeoutSeconds: '15' });
|
|
346
|
+
const result = await (0, hands_1.configureHands)({ companionName: 'Sparkle', manifest: handsManifest });
|
|
347
|
+
expect(result.config.defaultTimeoutMs).toBe(15000);
|
|
348
|
+
expect(result.summary?.Provider).toBeUndefined();
|
|
349
|
+
});
|
|
350
|
+
test('Hands configurator disables tool execution when none is selected', async () => {
|
|
351
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ provider: 'none' });
|
|
352
|
+
const result = await (0, hands_1.configureHands)({ companionName: 'Sparkle', manifest: handsManifest });
|
|
353
|
+
expect(result.config.provider).toBe('none');
|
|
354
|
+
expect(result.summary?.Provider).toBe('None (Hands disabled)');
|
|
355
|
+
});
|
|
356
|
+
test('Behavior configurator configures blank slate mode when later is chosen', async () => {
|
|
357
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ personaMode: 'later' });
|
|
358
|
+
const result = await (0, behavior_1.configureBehavior)({ companionName: 'Sparkle', manifest: behaviorManifest });
|
|
359
|
+
expect(result.config.provider).toBe('active_self');
|
|
360
|
+
expect(result.config.mode).toBe('blank_slate');
|
|
361
|
+
expect(result.summary?.Persona).toBe('Blank Slate (Evolve via interaction)');
|
|
362
|
+
});
|
|
363
|
+
test('Behavior configurator configures custom persona asset when now is chosen', async () => {
|
|
364
|
+
inquirer_1.default.prompt
|
|
365
|
+
.mockResolvedValueOnce({ personaMode: 'now' })
|
|
366
|
+
.mockResolvedValueOnce({
|
|
367
|
+
archetype: 'System Sentinel',
|
|
368
|
+
ethos: 'Snarky and loyal',
|
|
369
|
+
directive: 'No sycophantic greetings',
|
|
370
|
+
});
|
|
371
|
+
const result = await (0, behavior_1.configureBehavior)({ companionName: 'Sparkle', manifest: behaviorManifest });
|
|
372
|
+
expect(result.config.provider).toBe('active_self');
|
|
373
|
+
expect(result.config.mode).toBe('custom');
|
|
374
|
+
expect(result.config.archetype).toBe('System Sentinel');
|
|
375
|
+
expect(result.config.selfPath).toBe('./assets/self/default.self');
|
|
376
|
+
});
|
|
377
|
+
test('Vision configurator configures OpenRouter vision model', async () => {
|
|
378
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ model: 'gpt-4-vision' });
|
|
379
|
+
const result = await (0, vision_1.configureVision)({ companionName: 'Sparkle', manifest: visionManifest });
|
|
380
|
+
expect(result.config.provider).toBe('openrouter');
|
|
381
|
+
expect(result.config.model).toBe('gpt-4-vision');
|
|
382
|
+
});
|
|
383
|
+
test('Vision configurator disables vision when none is selected', async () => {
|
|
384
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ model: 'none' });
|
|
385
|
+
const result = await (0, vision_1.configureVision)({ companionName: 'Sparkle', manifest: visionManifest });
|
|
386
|
+
expect(result.config.provider).toBe('none');
|
|
387
|
+
expect(result.summary?.Provider).toBe('None (Vision disabled)');
|
|
388
|
+
});
|
|
389
|
+
test('Ear configurator enables sensory bounds by default', async () => {
|
|
390
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ choice: 'enabled' });
|
|
391
|
+
const result = await (0, ear_1.configureEar)({ companionName: 'Sparkle', manifest: earManifest });
|
|
392
|
+
expect(result.config.defaultSource).toBe('text_chat');
|
|
393
|
+
expect(result.config.maxTextLength).toBe(4000);
|
|
394
|
+
expect(result.config.maxAudioBytes).toBe(10485760);
|
|
395
|
+
expect(result.summary?.Status).toContain('Enabled');
|
|
396
|
+
});
|
|
397
|
+
test('Ear configurator disables sensory ingress when none is selected', async () => {
|
|
398
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ choice: 'none' });
|
|
399
|
+
const result = await (0, ear_1.configureEar)({ companionName: 'Sparkle', manifest: earManifest });
|
|
400
|
+
expect(result.config.provider).toBe('none');
|
|
401
|
+
expect(result.summary?.Provider).toBe('None (Ear disabled)');
|
|
402
|
+
});
|
|
403
|
+
test('Observation configurator disables observation by default as work in progress', async () => {
|
|
404
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ provider: 'none' });
|
|
405
|
+
const result = await (0, observation_1.configureObservation)({ companionName: 'Sparkle', manifest: observationManifest });
|
|
406
|
+
expect(result.config.provider).toBe('none');
|
|
407
|
+
expect(result.summary?.Provider).toContain('Disabled (Work in progress)');
|
|
408
|
+
});
|
|
409
|
+
test('Observation configurator configures experimental fixture ingest when chosen', async () => {
|
|
410
|
+
inquirer_1.default.prompt.mockResolvedValueOnce({ provider: 'fixture' });
|
|
411
|
+
const result = await (0, observation_1.configureObservation)({ companionName: 'Sparkle', manifest: observationManifest });
|
|
412
|
+
expect(result.summary?.Provider).toContain('Work in progress');
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
describe('Review Summary Formatting & Generator Integration', () => {
|
|
416
|
+
test('formatReviewSummary displays actual configuration values and metadata', () => {
|
|
417
|
+
const summaryOutput = (0, index_1.formatReviewSummary)('Sparkle', [brainManifest, archiveManifest, knowledgeManifest, voiceManifest], {
|
|
418
|
+
brain: { Provider: 'OpenRouter', Model: 'openai/gpt-4o-mini' },
|
|
419
|
+
archive: { Database: 'SQLite (WAL + FTS5)', Storage: 'siduri.sqlite' },
|
|
420
|
+
knowledge: { Source: 'E Knowledge Hub', Provider: 'E Teyvat', Package: '@vxnus/e-teyvat', Version: '1.2.0' },
|
|
421
|
+
voice: { Provider: 'VOICEVOX', 'Speaker ID': 1 },
|
|
422
|
+
});
|
|
423
|
+
expect(summaryOutput).toContain('Sparkle');
|
|
424
|
+
expect(summaryOutput).toContain('Brain');
|
|
425
|
+
expect(summaryOutput).toContain('OpenRouter');
|
|
426
|
+
expect(summaryOutput).toContain('openai/gpt-4o-mini');
|
|
427
|
+
expect(summaryOutput).toContain('Archive');
|
|
428
|
+
expect(summaryOutput).toContain('SQLite');
|
|
429
|
+
expect(summaryOutput).toContain('siduri.sqlite');
|
|
430
|
+
expect(summaryOutput).toContain('Knowledge');
|
|
431
|
+
expect(summaryOutput).toContain('E Knowledge Hub');
|
|
432
|
+
expect(summaryOutput).toContain('E Teyvat');
|
|
433
|
+
expect(summaryOutput).toContain('@vxnus/e-teyvat');
|
|
434
|
+
expect(summaryOutput).toContain('Voice');
|
|
435
|
+
expect(summaryOutput).toContain('VOICEVOX');
|
|
436
|
+
});
|
|
437
|
+
test('generateInstanceFiles accurately embeds configured organ values in siduri.config.json', () => {
|
|
438
|
+
const organConfigs = {
|
|
439
|
+
brain: { provider: 'openrouter', model: 'openai/gpt-4o-mini', apiKeyEnv: 'OPENROUTER_API_KEY' },
|
|
440
|
+
archive: { provider: 'sqlite' },
|
|
441
|
+
knowledge: { provider: 'e-hub', registryUrl: 'https://e.vxnus.xyz/api/v1/knowledge', packId: '@vxnus/e-teyvat' },
|
|
442
|
+
voice: { provider: 'voicevox', speakerId: 2, baseUrl: 'http://localhost:50021' },
|
|
443
|
+
};
|
|
444
|
+
const files = (0, generator_1.generateInstanceFiles)({
|
|
445
|
+
name: 'Sparkle',
|
|
446
|
+
selectedManifests: [brainManifest, archiveManifest, knowledgeManifest, voiceManifest],
|
|
447
|
+
organConfigs,
|
|
448
|
+
});
|
|
449
|
+
const config = JSON.parse(files['siduri.config.json']);
|
|
450
|
+
expect(config.name).toBe('Sparkle');
|
|
451
|
+
expect(config.organs.brain.provider).toBe('openrouter');
|
|
452
|
+
expect(config.organs.brain.model).toBe('openai/gpt-4o-mini');
|
|
453
|
+
expect(config.organs.archive.provider).toBe('sqlite');
|
|
454
|
+
expect(config.organs.knowledge.provider).toBe('e-hub');
|
|
455
|
+
expect(config.organs.knowledge.packId).toBe('@vxnus/e-teyvat');
|
|
456
|
+
expect(config.organs.voice.speakerId).toBe(2);
|
|
457
|
+
// Verify explicit imports in src/index.js
|
|
458
|
+
expect(files['src/index.js']).toContain("import { OpenRouterBrain } from '@sidurijs/brain'");
|
|
459
|
+
expect(files['src/index.js']).toContain("import { SqliteArchiveLedger } from '@sidurijs/archive'");
|
|
460
|
+
expect(files['src/index.js']).toContain("import { UnifiedKnowledgeOrgan } from '@sidurijs/knowledge'");
|
|
461
|
+
expect(files['src/index.js']).toContain("import { VoiceAdapter } from '@sidurijs/voice'");
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
});
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function redactDatabaseUrl(url: string): string;
|
|
2
|
+
export interface DbPushOptions {
|
|
3
|
+
projectDir?: string;
|
|
4
|
+
connectionString?: string;
|
|
5
|
+
env?: Record<string, string | undefined>;
|
|
6
|
+
}
|
|
7
|
+
export interface DbPushResult {
|
|
8
|
+
status: 'NOOP' | 'APPLIED' | 'UP_TO_DATE';
|
|
9
|
+
appliedMigrations: string[];
|
|
10
|
+
skippedMigrations: string[];
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function runDbPush(options?: DbPushOptions): Promise<DbPushResult>;
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.redactDatabaseUrl = redactDatabaseUrl;
|
|
4
|
+
exports.runDbPush = runDbPush;
|
|
5
|
+
function redactDatabaseUrl(url) {
|
|
6
|
+
return url;
|
|
7
|
+
}
|
|
8
|
+
async function runDbPush(options = {}) {
|
|
9
|
+
return {
|
|
10
|
+
status: 'NOOP',
|
|
11
|
+
appliedMigrations: [],
|
|
12
|
+
skippedMigrations: [],
|
|
13
|
+
message: 'SQLite manages schema natively. No CLI migrations required.',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { OrganManifest } from './manifest';
|
|
2
|
+
export declare class OrganRegistry {
|
|
3
|
+
private manifests;
|
|
4
|
+
constructor(manifests?: OrganManifest[]);
|
|
5
|
+
register(manifest: OrganManifest): void;
|
|
6
|
+
get(organType: string): OrganManifest | undefined;
|
|
7
|
+
getAll(): OrganManifest[];
|
|
8
|
+
getAvailableOrganTypes(): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Discover installed or monorepo organ packages.
|
|
11
|
+
*/
|
|
12
|
+
static discover(searchRoots?: string[]): OrganRegistry;
|
|
13
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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.OrganRegistry = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const manifest_1 = require("./manifest");
|
|
10
|
+
const builtin_manifests_1 = require("./builtin-manifests");
|
|
11
|
+
class OrganRegistry {
|
|
12
|
+
manifests = new Map();
|
|
13
|
+
constructor(manifests) {
|
|
14
|
+
if (manifests) {
|
|
15
|
+
for (const m of manifests) {
|
|
16
|
+
this.manifests.set(m.organType, m);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
register(manifest) {
|
|
21
|
+
const validated = (0, manifest_1.validateOrganManifest)(manifest);
|
|
22
|
+
this.manifests.set(validated.organType, validated);
|
|
23
|
+
}
|
|
24
|
+
get(organType) {
|
|
25
|
+
return this.manifests.get(organType);
|
|
26
|
+
}
|
|
27
|
+
getAll() {
|
|
28
|
+
return Array.from(this.manifests.values());
|
|
29
|
+
}
|
|
30
|
+
getAvailableOrganTypes() {
|
|
31
|
+
return Array.from(this.manifests.keys());
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Discover installed or monorepo organ packages.
|
|
35
|
+
*/
|
|
36
|
+
static discover(searchRoots) {
|
|
37
|
+
const registry = new OrganRegistry();
|
|
38
|
+
const rootsToScan = searchRoots && searchRoots.length > 0
|
|
39
|
+
? searchRoots
|
|
40
|
+
: [
|
|
41
|
+
node_path_1.default.resolve(__dirname, '../../packages/organs'),
|
|
42
|
+
node_path_1.default.resolve(process.cwd(), 'packages/organs'),
|
|
43
|
+
node_path_1.default.resolve(__dirname, '../../packages'),
|
|
44
|
+
node_path_1.default.resolve(process.cwd(), 'packages'),
|
|
45
|
+
node_path_1.default.resolve(process.cwd(), 'node_modules/@siduri-x'),
|
|
46
|
+
node_path_1.default.resolve(__dirname, '../node_modules/@siduri-x'),
|
|
47
|
+
];
|
|
48
|
+
const visitedDirs = new Set();
|
|
49
|
+
for (const root of rootsToScan) {
|
|
50
|
+
if (!node_fs_1.default.existsSync(root) || visitedDirs.has(root))
|
|
51
|
+
continue;
|
|
52
|
+
visitedDirs.add(root);
|
|
53
|
+
try {
|
|
54
|
+
const entries = node_fs_1.default.readdirSync(root, { withFileTypes: true });
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (!entry.isDirectory())
|
|
57
|
+
continue;
|
|
58
|
+
const candidateDir = node_path_1.default.join(root, entry.name);
|
|
59
|
+
const manifestPath = node_path_1.default.join(candidateDir, 'organ-manifest.json');
|
|
60
|
+
if (node_fs_1.default.existsSync(manifestPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
63
|
+
const manifest = (0, manifest_1.validateOrganManifest)(raw, manifestPath);
|
|
64
|
+
registry.register(manifest);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.warn(`[Siduri CLI] Warning: failed to parse manifest at ${manifestPath}: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
// Continue scanning other roots
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Always merge canonical built-in @sidurijs/* organ manifests as fallback for ones that weren't found
|
|
77
|
+
for (const builtinManifest of builtin_manifests_1.BUILTIN_ORGAN_MANIFESTS) {
|
|
78
|
+
if (!registry.get(builtinManifest.organType)) {
|
|
79
|
+
registry.register(builtinManifest);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return registry;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.OrganRegistry = OrganRegistry;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|