@vxnus/siduri 0.1.10 → 2.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/README.md +3 -4
- package/dist/builtin-manifests.js +15 -30
- package/dist/clean-machine-e2e.test.js +13 -89
- package/dist/configurators/memory.js +1 -1
- package/dist/configurators.test.js +1 -1
- package/dist/db.d.ts +0 -12
- package/dist/db.js +7 -144
- package/dist/discovery.js +5 -7
- package/dist/discovery.test.js +5 -4
- package/dist/doctor-db.test.js +2 -91
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +32 -40
- package/dist/generator.d.ts +0 -1
- package/dist/generator.js +16 -67
- package/dist/generator.test.js +7 -7
- package/dist/index.js +0 -6
- package/dist/release-check.js +4 -8
- package/dist/runtime-parity.test.js +2 -1
- 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 +35 -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/chunks/09y7khbmco_v4.js +1 -0
- package/dist/web-dist/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
- package/dist/web-dist/_next/static/chunks/0uwih7sydv413.js +1 -0
- package/dist/web-dist/_next/static/chunks/0xhe1hasjan-9.css +1 -0
- package/dist/web-dist/_next/static/chunks/1_yae15ffa5f6.js +1 -0
- package/dist/web-dist/_next/static/chunks/2_duynll0m51m.js +30 -0
- package/dist/web-dist/_next/static/chunks/2kcqevsmv6kh2.js +31 -0
- package/dist/web-dist/_next/static/chunks/2s3d1bfr0l4b0.js +1 -0
- package/dist/web-dist/_next/static/chunks/32z87id9k6i2z.js +22 -0
- package/dist/web-dist/_next/static/chunks/3bx2gs3idjmo1.js +1 -0
- package/dist/web-dist/_next/static/chunks/turbopack-3eipe4c-imn5l.js +1 -0
- package/dist/web-dist/_next/static/media/apple-icon.2g7-yefdphkmy.png +0 -0
- package/dist/web-dist/_next/static/media/favicon.3rlors1ui1gw9.ico +0 -0
- package/dist/web-dist/_next/static/media/icon.0580nb9h-ugue.svg +4 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_buildManifest.js +11 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_clientMiddlewareManifest.js +1 -0
- package/dist/web-dist/_next/static/pWHUixFDDvANZeAf-shqT/_ssgManifest.js +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/package.json +2 -4
package/dist/doctor-db.test.js
CHANGED
|
@@ -11,10 +11,9 @@ const generator_1 = require("./generator");
|
|
|
11
11
|
const discovery_1 = require("./discovery");
|
|
12
12
|
describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
13
13
|
const rootOrgansDir = node_path_1.default.resolve(__dirname, '../../packages/organs');
|
|
14
|
-
const registry = discovery_1.OrganRegistry.discover(
|
|
14
|
+
const registry = discovery_1.OrganRegistry.discover();
|
|
15
15
|
const brain = registry.get('brain');
|
|
16
16
|
const hands = registry.get('hands');
|
|
17
|
-
const memory = registry.get('memory');
|
|
18
17
|
const testTempRoot = node_path_1.default.resolve(__dirname, '../temp-phase4-test');
|
|
19
18
|
beforeAll(() => {
|
|
20
19
|
if (node_fs_1.default.existsSync(testTempRoot)) {
|
|
@@ -27,14 +26,6 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
|
27
26
|
node_fs_1.default.rmSync(testTempRoot, { recursive: true, force: true });
|
|
28
27
|
}
|
|
29
28
|
});
|
|
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
29
|
test('Test Matrix A: Brain only instance doctor and db push', async () => {
|
|
39
30
|
const instanceDir = node_path_1.default.join(testTempRoot, 'brain-only');
|
|
40
31
|
node_fs_1.default.mkdirSync(node_path_1.default.join(instanceDir, 'src'), { recursive: true });
|
|
@@ -49,93 +40,13 @@ describe('Phase 4: Diagnostics and Database Provisioning Tests', () => {
|
|
|
49
40
|
node_fs_1.default.writeFileSync(fullPath, content);
|
|
50
41
|
}
|
|
51
42
|
}
|
|
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
43
|
const doctorPass = await (0, doctor_1.runDoctor)({
|
|
60
44
|
projectDir: instanceDir,
|
|
61
45
|
env: { OPENROUTER_API_KEY: 'test-key-123' },
|
|
62
46
|
});
|
|
63
47
|
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
48
|
const dbResult = await (0, db_1.runDbPush)({ projectDir: instanceDir });
|
|
98
49
|
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);
|
|
50
|
+
expect(dbResult.message).toContain('SQLite manages schema natively.');
|
|
140
51
|
});
|
|
141
52
|
});
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface DoctorCheckResult {
|
|
2
|
-
category: 'Environment' | 'Services' | 'Database' | 'Health Probe';
|
|
2
|
+
category: 'Environment' | 'Services' | 'Database' | 'Health Probe' | 'Configuration';
|
|
3
3
|
name: string;
|
|
4
4
|
status: 'PASS' | 'FAIL' | 'OPTIONAL_MISSING' | 'SKIPPED';
|
|
5
5
|
organName?: string;
|
package/dist/doctor.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.runDoctor = runDoctor;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const discovery_1 = require("./discovery");
|
|
10
|
-
const
|
|
10
|
+
const schema_validator_1 = require("./schema-validator");
|
|
11
11
|
async function runDoctor(options = {}) {
|
|
12
12
|
const projectDir = options.projectDir ? node_path_1.default.resolve(options.projectDir) : process.cwd();
|
|
13
13
|
const configPath = node_path_1.default.join(projectDir, 'siduri.config.json');
|
|
@@ -46,6 +46,29 @@ async function runDoctor(options = {}) {
|
|
|
46
46
|
]);
|
|
47
47
|
const selectedManifests = [];
|
|
48
48
|
const results = [];
|
|
49
|
+
// 0. Schema Validation Check
|
|
50
|
+
const schemaPath = node_path_1.default.join(projectDir, 'siduri.schema.json');
|
|
51
|
+
if (node_fs_1.default.existsSync(schemaPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const schema = JSON.parse(node_fs_1.default.readFileSync(schemaPath, 'utf8'));
|
|
54
|
+
(0, schema_validator_1.validateCompanionConfig)(config, schema);
|
|
55
|
+
results.push({
|
|
56
|
+
category: 'Configuration',
|
|
57
|
+
name: 'Schema Conformance',
|
|
58
|
+
status: 'PASS',
|
|
59
|
+
message: 'siduri.config.json conforms to siduri.schema.json',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
results.push({
|
|
64
|
+
category: 'Configuration',
|
|
65
|
+
name: 'Schema Conformance',
|
|
66
|
+
status: 'FAIL',
|
|
67
|
+
message: err.message,
|
|
68
|
+
remediation: 'Update siduri.config.json to conform to the generated schema.',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
49
72
|
// 1. Resolve manifests for selected organs
|
|
50
73
|
for (const organKey of organKeys) {
|
|
51
74
|
const m = registry.get(organKey) || registry.getAll().find((item) => item.configKey === organKey || item.organType === organKey);
|
|
@@ -118,49 +141,18 @@ async function runDoctor(options = {}) {
|
|
|
118
141
|
// 4. Database Check (only if an organ declares database requirement)
|
|
119
142
|
const dbOrgan = selectedManifests.find((m) => m.database !== null && m.database !== undefined);
|
|
120
143
|
if (dbOrgan) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
}
|
|
144
|
+
results.push({
|
|
145
|
+
category: 'Database',
|
|
146
|
+
name: 'SQLite Database',
|
|
147
|
+
status: 'PASS',
|
|
148
|
+
organName: dbOrgan.name,
|
|
149
|
+
message: 'SQLite operates natively.',
|
|
150
|
+
});
|
|
159
151
|
}
|
|
160
152
|
else {
|
|
161
153
|
results.push({
|
|
162
154
|
category: 'Database',
|
|
163
|
-
name: '
|
|
155
|
+
name: 'SQLite Database',
|
|
164
156
|
status: 'SKIPPED',
|
|
165
157
|
message: 'Not required by current composition',
|
|
166
158
|
});
|
package/dist/generator.d.ts
CHANGED
package/dist/generator.js
CHANGED
|
@@ -72,7 +72,7 @@ function getDefaultConfigForManifest(manifest) {
|
|
|
72
72
|
function generateInstanceFiles(options) {
|
|
73
73
|
const instanceName = options.name || 'my-siduri';
|
|
74
74
|
const instanceId = options.id || 'default';
|
|
75
|
-
const coreVersion = options.coreVersion || '^1.0.
|
|
75
|
+
const coreVersion = options.coreVersion || '^1.0.9';
|
|
76
76
|
const manifests = options.selectedManifests;
|
|
77
77
|
const hasMemory = manifests.some((m) => m.organType === 'memory');
|
|
78
78
|
const hasVoice = manifests.some((m) => m.organType === 'voice');
|
|
@@ -82,56 +82,7 @@ function generateInstanceFiles(options) {
|
|
|
82
82
|
const isVoicevox = hasVoice && (!voiceConfig || voiceConfig.provider === 'voicevox');
|
|
83
83
|
const memoryConfig = options.organConfigs?.memory || options.organConfigs?.['@siduri-x/memory'];
|
|
84
84
|
const isPostgresLocal = hasMemory && (!memoryConfig || memoryConfig.deployment === 'local' || memoryConfig.provider === 'postgres');
|
|
85
|
-
// 1.
|
|
86
|
-
let dockerComposeYaml;
|
|
87
|
-
const dockerServices = [];
|
|
88
|
-
const dockerVolumes = [];
|
|
89
|
-
if (hasMemory && isPostgresLocal) {
|
|
90
|
-
dockerServices.push([
|
|
91
|
-
' db:',
|
|
92
|
-
' image: postgres:15',
|
|
93
|
-
' environment:',
|
|
94
|
-
' POSTGRES_USER: postgres',
|
|
95
|
-
' POSTGRES_PASSWORD: password',
|
|
96
|
-
' POSTGRES_DB: siduri',
|
|
97
|
-
' ports:',
|
|
98
|
-
' - "5432:5432"',
|
|
99
|
-
' volumes:',
|
|
100
|
-
' - postgres_data:/var/lib/postgresql/data',
|
|
101
|
-
' healthcheck:',
|
|
102
|
-
' test: ["CMD-SHELL", "pg_isready -U postgres"]',
|
|
103
|
-
' interval: 2s',
|
|
104
|
-
' timeout: 5s',
|
|
105
|
-
' retries: 5',
|
|
106
|
-
].join('\n'));
|
|
107
|
-
dockerVolumes.push(' postgres_data:');
|
|
108
|
-
}
|
|
109
|
-
if (hasVoice && voiceConfig?.rvc?.enabled) {
|
|
110
|
-
dockerServices.push([
|
|
111
|
-
' rvc:',
|
|
112
|
-
' image: ghcr.io/vxnus-studio/rvc-headless:latest',
|
|
113
|
-
' ports:',
|
|
114
|
-
' - "50055:50055"',
|
|
115
|
-
' volumes:',
|
|
116
|
-
' - ./assets/voice:/app/models',
|
|
117
|
-
' environment:',
|
|
118
|
-
' - RVC_MODELS_DIR=/app/models',
|
|
119
|
-
].join('\n'));
|
|
120
|
-
}
|
|
121
|
-
if (dockerServices.length > 0) {
|
|
122
|
-
const composeLines = [
|
|
123
|
-
'version: "3.8"',
|
|
124
|
-
'',
|
|
125
|
-
'services:',
|
|
126
|
-
...dockerServices,
|
|
127
|
-
];
|
|
128
|
-
if (dockerVolumes.length > 0) {
|
|
129
|
-
composeLines.push('', 'volumes:', ...dockerVolumes);
|
|
130
|
-
}
|
|
131
|
-
composeLines.push('');
|
|
132
|
-
dockerComposeYaml = composeLines.join('\n');
|
|
133
|
-
}
|
|
134
|
-
// 2. package.json
|
|
85
|
+
// 1. package.json
|
|
135
86
|
const dependencies = {
|
|
136
87
|
'@siduri-x/core': coreVersion,
|
|
137
88
|
};
|
|
@@ -144,11 +95,6 @@ function generateInstanceFiles(options) {
|
|
|
144
95
|
doctor: 'siduri doctor',
|
|
145
96
|
db: 'siduri db',
|
|
146
97
|
};
|
|
147
|
-
if (dockerComposeYaml) {
|
|
148
|
-
scripts['services:up'] = 'docker compose up -d';
|
|
149
|
-
scripts['services:down'] = 'docker compose down';
|
|
150
|
-
scripts['services:logs'] = 'docker compose logs -f';
|
|
151
|
-
}
|
|
152
98
|
const packageJsonObj = {
|
|
153
99
|
name: instanceName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'my-siduri',
|
|
154
100
|
private: true,
|
|
@@ -215,7 +161,7 @@ function generateInstanceFiles(options) {
|
|
|
215
161
|
`import { readFile, stat, readdir } from 'node:fs/promises';`,
|
|
216
162
|
`import path from 'node:path';`,
|
|
217
163
|
`import { fileURLToPath } from 'node:url';`,
|
|
218
|
-
`import { SiduriRuntime, dispatchCompanionChat } from '@siduri-x/core';`,
|
|
164
|
+
`import { SiduriRuntime, dispatchCompanionChat, validateCompanionConfig } from '@siduri-x/core';`,
|
|
219
165
|
];
|
|
220
166
|
for (const m of manifests) {
|
|
221
167
|
importLines.push(`import { ${m.factory} } from '${m.name}';`);
|
|
@@ -241,6 +187,16 @@ function generateInstanceFiles(options) {
|
|
|
241
187
|
`const config = JSON.parse(`,
|
|
242
188
|
` await readFile(path.join(rootDir, 'siduri.config.json'), 'utf8')`,
|
|
243
189
|
`);`,
|
|
190
|
+
`const schemaPath = path.join(rootDir, 'siduri.schema.json');`,
|
|
191
|
+
`try {`,
|
|
192
|
+
` const schema = JSON.parse(await readFile(schemaPath, 'utf8'));`,
|
|
193
|
+
` validateCompanionConfig(config, schema);`,
|
|
194
|
+
`} catch (err) {`,
|
|
195
|
+
` if (err?.code !== 'ENOENT') {`,
|
|
196
|
+
` console.error('Config validation failed:', err.message);`,
|
|
197
|
+
` throw err;`,
|
|
198
|
+
` }`,
|
|
199
|
+
`}`,
|
|
244
200
|
'',
|
|
245
201
|
...instantiationLines,
|
|
246
202
|
'',
|
|
@@ -621,20 +577,16 @@ function generateInstanceFiles(options) {
|
|
|
621
577
|
'- **Environment**: Valid `.env` file (configured from `.env.example`)',
|
|
622
578
|
];
|
|
623
579
|
if (hasMemory) {
|
|
624
|
-
readmeLines.push('- **
|
|
625
|
-
readmeLines.push(' - **PostgreSQL**: Required for memory claims & durable state (or use cloud Supabase/Neon)');
|
|
580
|
+
readmeLines.push('- **PostgreSQL (Optional)**: Required if PostgreSQL memory organ is enabled (local PostgreSQL server or cloud Supabase/Neon).');
|
|
626
581
|
}
|
|
627
582
|
if (isVoicevox) {
|
|
628
583
|
readmeLines.push('- **VOICEVOX**: Note: The Voicevox engine executable will be securely auto-downloaded at runtime by Siduri if no local URL is provided.');
|
|
629
584
|
}
|
|
630
585
|
readmeLines.push('', '## Getting Started', '', '### 1. Install Dependencies', '```bash', 'npm install', '```', '', '### 2. Configure Environment', '```bash', 'cp .env.example .env', '```', 'Fill in your LLM API key (e.g. `OPENROUTER_API_KEY`) and any other service credentials in `.env`.');
|
|
631
|
-
if (dockerComposeYaml) {
|
|
632
|
-
readmeLines.push('', '### 3. Start Local Services (Docker)', '```bash', 'npm run services:up', '```', '*(To stop services later, run `npm run services:down`)*');
|
|
633
|
-
}
|
|
634
586
|
if (hasMemory) {
|
|
635
|
-
readmeLines.push('',
|
|
587
|
+
readmeLines.push('', '### 3. Database Migrations', 'Ensure `DATABASE_URL` in `.env` is reachable, then push the memory organ PostgreSQL schema:', '```bash', 'npx @vxnus/siduri db push', '```');
|
|
636
588
|
}
|
|
637
|
-
readmeLines.push('', `### ${hasMemory ?
|
|
589
|
+
readmeLines.push('', `### ${hasMemory ? '4' : '3'}. Diagnostics & Health Probe`, 'Verify all environment variables, schema conformance, services, and organ connections:', '```bash', 'npm run doctor', '```', '', `### ${hasMemory ? '5' : '4'}. Start Companion & Web Console`, 'Launch your companion runtime and Web UI / Memory Control Panel:', '```bash', 'npm start', '```', 'Then open `http://localhost:3000` in your browser.');
|
|
638
590
|
const companionSlug = instanceName.toLowerCase().replace(/[^a-z0-9_-]/g, '') || 'default';
|
|
639
591
|
const createAssetsDirs = [];
|
|
640
592
|
if (hasBody) {
|
|
@@ -659,8 +611,5 @@ function generateInstanceFiles(options) {
|
|
|
659
611
|
createAssetsBodyDir: hasBody,
|
|
660
612
|
createAssetsDirs,
|
|
661
613
|
};
|
|
662
|
-
if (dockerComposeYaml) {
|
|
663
|
-
result['docker-compose.yml'] = dockerComposeYaml;
|
|
664
|
-
}
|
|
665
614
|
return result;
|
|
666
615
|
}
|
package/dist/generator.test.js
CHANGED
|
@@ -33,7 +33,7 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
33
33
|
version: '1.0.0',
|
|
34
34
|
displayName: 'Memory (PostgreSQL FTS Claims)',
|
|
35
35
|
entrypoint: './dist/index.js',
|
|
36
|
-
factory: '
|
|
36
|
+
factory: 'SqliteMemoryStore',
|
|
37
37
|
configKey: 'memory',
|
|
38
38
|
configSchema: { type: 'object', properties: { provider: { type: 'string' } } },
|
|
39
39
|
environment: [{ name: 'DATABASE_URL', required: true, secret: true }],
|
|
@@ -158,14 +158,13 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
158
158
|
expect(files['README.md']).toContain('assets/body/companion-full');
|
|
159
159
|
expect(files['README.md']).toContain('assets/voice/companion-full');
|
|
160
160
|
expect(files['README.md']).toContain('Prerequisites');
|
|
161
|
-
//
|
|
162
|
-
expect(files['docker-compose.yml']).
|
|
163
|
-
expect(files['docker-compose.yml']).toContain('postgres:15');
|
|
161
|
+
// No docker compose generated (Docker completely removed, host-native runtime)
|
|
162
|
+
expect(files['docker-compose.yml']).toBeUndefined();
|
|
164
163
|
expect(files['README.md']).toContain('Voicevox engine executable will be securely auto-downloaded');
|
|
165
|
-
expect(pkg.scripts['services:up']).
|
|
166
|
-
expect(pkg.scripts['services:down']).
|
|
164
|
+
expect(pkg.scripts['services:up']).toBeUndefined();
|
|
165
|
+
expect(pkg.scripts['services:down']).toBeUndefined();
|
|
167
166
|
});
|
|
168
|
-
test('Composition E:
|
|
167
|
+
test('Composition E: Standalone companion never generates docker-compose.yml and validates config', () => {
|
|
169
168
|
const files = (0, generator_1.generateInstanceFiles)({
|
|
170
169
|
name: 'brain-solo',
|
|
171
170
|
selectedManifests: [MOCK_MANIFESTS.brain],
|
|
@@ -173,6 +172,7 @@ describe('Instance Generator Composition Invariants (Phase 3)', () => {
|
|
|
173
172
|
expect(files['docker-compose.yml']).toBeUndefined();
|
|
174
173
|
const pkg = JSON.parse(files['package.json']);
|
|
175
174
|
expect(pkg.scripts['services:up']).toBeUndefined();
|
|
175
|
+
expect(files['src/index.js']).toContain('validateCompanionConfig');
|
|
176
176
|
expect(files['public/index.html']).toBeDefined();
|
|
177
177
|
expect(files['public/index.html']).toContain('Memory Console');
|
|
178
178
|
expect(files['public/index.html']).toContain('Companion Chat');
|
package/dist/index.js
CHANGED
|
@@ -241,9 +241,6 @@ async function runCreateWizard(targetDir) {
|
|
|
241
241
|
else {
|
|
242
242
|
await (0, promises_1.writeFile)(node_path_1.default.join(publicDir, 'index.html'), files['public/index.html'], 'utf8');
|
|
243
243
|
}
|
|
244
|
-
if (files['docker-compose.yml']) {
|
|
245
|
-
await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'docker-compose.yml'), files['docker-compose.yml'], 'utf8');
|
|
246
|
-
}
|
|
247
244
|
if (files.createAssetsDirs && files.createAssetsDirs.length > 0) {
|
|
248
245
|
for (const dir of files.createAssetsDirs) {
|
|
249
246
|
await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, dir), { recursive: true });
|
|
@@ -267,9 +264,6 @@ async function runCreateWizard(targetDir) {
|
|
|
267
264
|
console.log(`Your Siduri companion is ready! Next steps:\n`);
|
|
268
265
|
console.log(` cd ${node_path_1.default.relative(process.cwd(), projectDir) || '.'}`);
|
|
269
266
|
console.log(` cp .env.example .env ${exports.colors.dim}# Fill in required API keys/credentials${exports.colors.reset}`);
|
|
270
|
-
if (files['docker-compose.yml']) {
|
|
271
|
-
console.log(` npm run services:up ${exports.colors.dim}# Start local Docker services (PostgreSQL / VOICEVOX)${exports.colors.reset}`);
|
|
272
|
-
}
|
|
273
267
|
const hasMemory = selectedManifests.some((m) => m.organType === 'memory');
|
|
274
268
|
if (hasMemory) {
|
|
275
269
|
console.log(` npx @vxnus/siduri db push ${exports.colors.dim}# Push PostgreSQL memory schema${exports.colors.reset}`);
|
package/dist/release-check.js
CHANGED
|
@@ -19,11 +19,12 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
19
19
|
const getPkgVer = (dir) => JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.resolve(repoRoot, dir, 'package.json'), 'utf8')).version;
|
|
20
20
|
const canonicalPackages = [
|
|
21
21
|
{ name: '@siduri-x/core', dir: 'packages/core', isOrgan: false, tarName: `siduri-x-core-${getPkgVer('packages/core')}.tgz` },
|
|
22
|
+
{ name: '@siduri-x/self', dir: 'packages/self', isOrgan: false, tarName: `siduri-x-self-${getPkgVer('packages/self')}.tgz` },
|
|
23
|
+
{ name: '@siduri-x/knowledge', dir: 'packages/knowledge', isOrgan: false, tarName: `siduri-x-knowledge-${getPkgVer('packages/knowledge')}.tgz` },
|
|
24
|
+
{ name: '@siduri-x/memory', dir: 'packages/memory', isOrgan: false, tarName: `siduri-x-memory-${getPkgVer('packages/memory')}.tgz` },
|
|
22
25
|
{ name: '@siduri-x/brain', dir: 'packages/organs/brain', isOrgan: true, tarName: `siduri-x-brain-${getPkgVer('packages/organs/brain')}.tgz` },
|
|
23
|
-
{ name: '@siduri-x/memory', dir: 'packages/organs/memory', isOrgan: true, tarName: `siduri-x-memory-${getPkgVer('packages/organs/memory')}.tgz` },
|
|
24
|
-
{ name: '@siduri-x/knowledge', dir: 'packages/organs/knowledge', isOrgan: true, tarName: `siduri-x-knowledge-${getPkgVer('packages/organs/knowledge')}.tgz` },
|
|
25
|
-
{ name: '@siduri-x/behavior', dir: 'packages/organs/behavior', isOrgan: true, tarName: `siduri-x-behavior-${getPkgVer('packages/organs/behavior')}.tgz` },
|
|
26
26
|
{ name: '@siduri-x/ear', dir: 'packages/organs/ear', isOrgan: true, tarName: `siduri-x-ear-${getPkgVer('packages/organs/ear')}.tgz` },
|
|
27
|
+
{ name: '@siduri-x/eknowledge', dir: 'packages/organs/eknowledge', isOrgan: true, tarName: `siduri-x-eknowledge-${getPkgVer('packages/organs/eknowledge')}.tgz` },
|
|
27
28
|
{ name: '@siduri-x/vision', dir: 'packages/organs/vision', isOrgan: true, tarName: `siduri-x-vision-${getPkgVer('packages/organs/vision')}.tgz` },
|
|
28
29
|
{ name: '@siduri-x/hands', dir: 'packages/organs/hands', isOrgan: true, tarName: `siduri-x-hands-${getPkgVer('packages/organs/hands')}.tgz` },
|
|
29
30
|
{ name: '@siduri-x/body', dir: 'packages/organs/body', isOrgan: true, tarName: `siduri-x-body-${getPkgVer('packages/organs/body')}.tgz` },
|
|
@@ -88,11 +89,6 @@ function runReleaseCheck(repoRoot = node_path_1.default.resolve(__dirname, '../.
|
|
|
88
89
|
manifestsValidated++;
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
if (pkg.name === '@siduri-x/memory') {
|
|
92
|
-
if (!listing.some((l) => l.includes('package/migrations/001_initial_schema.sql'))) {
|
|
93
|
-
errors.push(`${pkg.name}: migrations/001_initial_schema.sql missing in tarball`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
92
|
if (pkg.name === '@vxnus/siduri') {
|
|
97
93
|
if (!pkgJson.bin || !pkgJson.bin.siduri) {
|
|
98
94
|
errors.push(`${pkg.name}: bin.siduri field missing in package.json`);
|
|
@@ -131,7 +131,8 @@ describe('Localweb and CLI Standalone 1:1 Runtime & Chat Parity', () => {
|
|
|
131
131
|
});
|
|
132
132
|
const srcIndexJs = files['src/index.js'];
|
|
133
133
|
// Ensures single source of truth import
|
|
134
|
-
expect(srcIndexJs).toContain("import { SiduriRuntime, dispatchCompanionChat } from '@siduri-x/core';");
|
|
134
|
+
expect(srcIndexJs).toContain("import { SiduriRuntime, dispatchCompanionChat, validateCompanionConfig } from '@siduri-x/core';");
|
|
135
|
+
expect(srcIndexJs).toContain('validateCompanionConfig(config, schema);');
|
|
135
136
|
// Ensures /chat uses dispatchCompanionChat directly without ad-hoc runtimeResult envelope
|
|
136
137
|
expect(srcIndexJs).toContain('const response = await dispatchCompanionChat(runtime,');
|
|
137
138
|
expect(srcIndexJs).toContain('res.end(JSON.stringify(response));');
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class ConfigValidationError extends Error {
|
|
2
|
+
errors: string[];
|
|
3
|
+
constructor(errors: string[]);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validates a companion configuration object against a JSON schema (draft-07 compatible).
|
|
7
|
+
* Throws ConfigValidationError if validation errors are detected.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateCompanionConfig(config: unknown, schema: any, path?: string): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConfigValidationError = void 0;
|
|
4
|
+
exports.validateCompanionConfig = validateCompanionConfig;
|
|
5
|
+
class ConfigValidationError extends Error {
|
|
6
|
+
errors;
|
|
7
|
+
constructor(errors) {
|
|
8
|
+
super(`Siduri configuration validation failed:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
|
|
9
|
+
this.name = 'ConfigValidationError';
|
|
10
|
+
this.errors = errors;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.ConfigValidationError = ConfigValidationError;
|
|
14
|
+
/**
|
|
15
|
+
* Validates a companion configuration object against a JSON schema (draft-07 compatible).
|
|
16
|
+
* Throws ConfigValidationError if validation errors are detected.
|
|
17
|
+
*/
|
|
18
|
+
function validateCompanionConfig(config, schema, path = '$') {
|
|
19
|
+
const errors = [];
|
|
20
|
+
function validateNode(value, nodeSchema, curPath) {
|
|
21
|
+
if (!nodeSchema || typeof nodeSchema !== 'object')
|
|
22
|
+
return;
|
|
23
|
+
// Type validation
|
|
24
|
+
if (nodeSchema.type !== undefined) {
|
|
25
|
+
const type = nodeSchema.type;
|
|
26
|
+
if (type === 'object') {
|
|
27
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
28
|
+
errors.push(`${curPath}: expected object, received ${value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (type === 'array') {
|
|
33
|
+
if (!Array.isArray(value)) {
|
|
34
|
+
errors.push(`${curPath}: expected array, received ${typeof value}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (type === 'string') {
|
|
39
|
+
if (typeof value !== 'string') {
|
|
40
|
+
errors.push(`${curPath}: expected string, received ${typeof value}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (type === 'number') {
|
|
45
|
+
if (typeof value !== 'number' || Number.isNaN(value)) {
|
|
46
|
+
errors.push(`${curPath}: expected number, received ${typeof value}`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (type === 'integer') {
|
|
51
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
52
|
+
errors.push(`${curPath}: expected integer, received ${typeof value}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (type === 'boolean') {
|
|
57
|
+
if (typeof value !== 'boolean') {
|
|
58
|
+
errors.push(`${curPath}: expected boolean, received ${typeof value}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Enum validation
|
|
64
|
+
if (Array.isArray(nodeSchema.enum)) {
|
|
65
|
+
if (!nodeSchema.enum.includes(value)) {
|
|
66
|
+
errors.push(`${curPath}: invalid value ${JSON.stringify(value)}, expected one of: ${nodeSchema.enum.map((v) => JSON.stringify(v)).join(', ')}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Object properties validation
|
|
71
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
72
|
+
if (Array.isArray(nodeSchema.required)) {
|
|
73
|
+
for (const reqKey of nodeSchema.required) {
|
|
74
|
+
if (value[reqKey] === undefined) {
|
|
75
|
+
errors.push(`${curPath}.${reqKey}: is required`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const definedProps = nodeSchema.properties || {};
|
|
80
|
+
if (nodeSchema.additionalProperties === false) {
|
|
81
|
+
for (const key of Object.keys(value)) {
|
|
82
|
+
// Allow $schema property at root level
|
|
83
|
+
if (curPath === '$' && key === '$schema')
|
|
84
|
+
continue;
|
|
85
|
+
if (!(key in definedProps)) {
|
|
86
|
+
errors.push(`${curPath}.${key}: unexpected property is not allowed`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
for (const [propName, propSchema] of Object.entries(definedProps)) {
|
|
91
|
+
if (value[propName] !== undefined) {
|
|
92
|
+
validateNode(value[propName], propSchema, `${curPath}.${propName}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Array items validation
|
|
97
|
+
if (Array.isArray(value) && nodeSchema.items) {
|
|
98
|
+
value.forEach((item, index) => {
|
|
99
|
+
validateNode(item, nodeSchema.items, `${curPath}[${index}]`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
validateNode(config, schema, path);
|
|
104
|
+
if (errors.length > 0) {
|
|
105
|
+
throw new ConfigValidationError(errors);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!DOCTYPE html><html lang="en" class="h-full antialiased dark"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/chunks/0xhe1hasjan-9.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/2kcqevsmv6kh2.js"/><script src="/_next/static/chunks/1_yae15ffa5f6.js" async=""></script><script src="/_next/static/chunks/3bx2gs3idjmo1.js" async=""></script><script src="/_next/static/chunks/32z87id9k6i2z.js" async=""></script><script src="/_next/static/chunks/turbopack-3eipe4c-imn5l.js" async=""></script><script src="/_next/static/chunks/0uwih7sydv413.js" async="" crossorigin=""></script><link rel="preload" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Serif+JP:wght@400;500;700&display=swap" as="style"/><meta name="robots" content="noindex"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous"/><title>404: This page could not be found.</title><title>Siduri — Local-First Companion</title><meta name="description" content="Siduri — a local-first companion with explicit memory, grounded knowledge, and optional presence."/><link rel="icon" href="/favicon.ico?favicon.3rlors1ui1gw9.ico" sizes="256x256" type="image/x-icon"/><link rel="icon" href="/favicon.ico"/><link rel="icon" href="/favicon.svg" type="image/svg+xml"/><link rel="apple-touch-icon" href="/apple-touch-icon.png"/><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Noto+Sans+JP:wght@400;500;700&family=Noto+Serif+JP:wght@400;500;700&display=swap" rel="stylesheet"/><script src="/_next/static/chunks/0cz1d0mv5g_q7.js" noModule=""></script></head><body class="min-h-full flex flex-col bg-[#0b0b0e] text-[#eee8df] selection:bg-[#d99a68]/30 selection:text-[#f4c28d]"><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/2kcqevsmv6kh2.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[81869,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"default\"]\n3:I[57830,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"default\"]\n4:I[44101,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n8:I[44101,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"ViewportBoundary\"]\na:I[44101,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"MetadataBoundary\"]\nc:I[4626,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"default\",1]\n:HL[\"/_next/static/chunks/0xhe1hasjan-9.css\",\"style\"]\n:HL[\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500\u0026family=Noto+Sans+JP:wght@400;500;700\u0026family=Noto+Serif+JP:wght@400;500;700\u0026display=swap\",\"style\"]\n7:X\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"c\":[\"\",\"_not-found\",\"\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4608]},\"$undefined\",\"$undefined\",4624],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/0xhe1hasjan-9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"script\",\"script-0\",{\"src\":\"/_next/static/chunks/0uwih7sydv413.js\",\"async\":true,\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"className\":\"h-full antialiased dark\",\"children\":[[\"$\",\"head\",null,{\"children\":[[\"$\",\"link\",null,{\"rel\":\"preconnect\",\"href\":\"https://fonts.googleapis.com\"}],[\"$\",\"link\",null,{\"rel\":\"preconnect\",\"href\":\"https://fonts.gstatic.com\",\"crossOrigin\":\"anonymous\"}],[\"$\",\"link\",null,{\"href\":\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500\u0026family=Noto+Sans+JP:wght@400;500;700\u0026family=Noto+Serif+JP:wght@400;500;700\u0026display=swap\",\"rel\":\"stylesheet\"}]]}],[\"$\",\"body\",null,{\"className\":\"min-h-full flex flex-col bg-[#0b0b0e] text-[#eee8df] selection:bg-[#d99a68]/30 selection:text-[#f4c28d]\",\"children\":[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],[]],\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]}]]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:1:props:children:props:notFound:0:1:props:style\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$0:f:0:1:0:props:children:1:props:children:1:props:children:props:notFound:0:1:props:children:props:children:1:props:style\",\"children\":404}],[\"$\",\"div\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:1:props:children:props:notFound:0:1:props:children:props:children:2:props:style\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$0:f:0:1:0:props:children:1:props:children:1:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style\",\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,null]},null,false,\"$7\"]},null,false,null],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$L8\",null,{\"children\":\"$L9\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$La\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lb\"}]}]}],null]}],false]],\"m\":\"$undefined\",\"G\":[\"$c\",[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/chunks/0xhe1hasjan-9.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]]],\"S\":true,\"h\":null,\"r\":\"$undefined\",\"s\":\"$undefined\",\"a\":\"$undefined\",\"l\":\"$undefined\",\"p\":\"$undefined\",\"d\":\"$undefined\",\"b\":\"pWHUixFDDvANZeAf-shqT\"}\n"])</script><script>self.__next_f.push([1,"7:C\n9:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"d:I[89236,[\"/_next/static/chunks/0uwih7sydv413.js\"],\"IconMark\"]\n6:null\nb:[[\"$\",\"title\",\"0\",{\"children\":\"Siduri — Local-First Companion\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"Siduri — a local-first companion with explicit memory, grounded knowledge, and optional presence.\"}],[\"$\",\"link\",\"2\",{\"rel\":\"icon\",\"href\":\"/favicon.ico?favicon.3rlors1ui1gw9.ico\",\"sizes\":\"256x256\",\"type\":\"image/x-icon\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/favicon.ico\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/favicon.svg\",\"type\":\"image/svg+xml\"}],[\"$\",\"link\",\"5\",{\"rel\":\"apple-touch-icon\",\"href\":\"/apple-touch-icon.png\"}],[\"$\",\"$Ld\",\"6\",{}]]\n"])</script></body></html>
|