@vxnus/siduri 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,26 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const node_crypto_1 = require("node:crypto");
7
+ exports.runCreateWizard = runCreateWizard;
8
+ exports.runCliDoctor = runCliDoctor;
9
+ exports.runCliDb = runCliDb;
8
10
  const node_child_process_1 = require("node:child_process");
9
11
  const promises_1 = require("node:fs/promises");
10
- const node_os_1 = require("node:os");
11
12
  const node_path_1 = __importDefault(require("node:path"));
12
13
  const node_util_1 = require("node:util");
13
14
  const inquirer_1 = __importDefault(require("inquirer"));
14
- const e_knowledge_1 = require("@vxnus/e-knowledge");
15
+ const discovery_1 = require("./discovery");
16
+ const generator_1 = require("./generator");
17
+ const doctor_1 = require("./doctor");
18
+ const db_1 = require("./db");
15
19
  const execFile = (0, node_util_1.promisify)(node_child_process_1.execFile);
16
- const DEFAULT_REGISTRY_URL = 'https://e.vxnus.xyz/api/v1/knowledge';
17
- const CLI_VERSION = '0.0.5';
18
- const RUNTIME_DEPENDENCIES = {
19
- '@vxnus/e': '^0.1.4',
20
- '@vxnus/e-knowledge': '^0.1.4',
21
- cors: '^2.8.5',
22
- express: '^4.18.2',
23
- pg: '^8.23.0',
24
- ws: '^8.21.3',
25
- zod: '^4.4.3',
26
- };
20
+ const CLI_VERSION = '0.0.7';
27
21
  const colors = {
28
22
  cyan: '\u001b[36m',
29
23
  dim: '\u001b[2m',
@@ -32,8 +26,8 @@ const colors = {
32
26
  reset: '\u001b[0m',
33
27
  };
34
28
  function printHeader() {
35
- console.log(`\n${colors.cyan}◈ SIDURI${colors.reset} ${colors.dim}companion setup${colors.reset}`);
36
- console.log(`${colors.yellow}Experimental release 0.0.5${colors.reset} · configuration may change\n`);
29
+ console.log(`\n${colors.cyan}◈ SIDURI${colors.reset} ${colors.dim}companion setup (manifest-driven)${colors.reset}`);
30
+ console.log(`${colors.yellow}Version ${CLI_VERSION}${colors.reset} · composable standalone architecture\n`);
37
31
  }
38
32
  function printSection(title) {
39
33
  console.log(`\n${colors.cyan}── ${title} ${'─'.repeat(Math.max(2, 42 - title.length))}${colors.reset}`);
@@ -41,35 +35,13 @@ function printSection(title) {
41
35
  function printSuccess(message) {
42
36
  console.log(`${colors.green}✓${colors.reset} ${message}`);
43
37
  }
44
- function runtimePath() {
45
- const projectRuntime = node_path_1.default.join(process.cwd(), 'siduri-runtime.js');
46
- return node_path_1.default.resolve(pathExists(projectRuntime) ? projectRuntime : node_path_1.default.join(__dirname, 'runtime.js'));
47
- }
48
- function pathExists(filePath) {
49
- try {
50
- require('node:fs').accessSync(filePath);
51
- return true;
52
- }
53
- catch {
54
- return false;
55
- }
56
- }
57
- async function startRuntime() {
58
- const filePath = runtimePath();
59
- if (!pathExists(filePath)) {
60
- throw new Error('Siduri runtime bundle is missing. Reinstall the CLI or rebuild the package.');
61
- }
62
- await new Promise((resolve, reject) => {
63
- const child = (0, node_child_process_1.spawn)(process.execPath, [filePath], { stdio: 'inherit', env: process.env });
64
- child.once('error', reject);
65
- child.once('exit', (code, signal) => {
66
- if (signal)
67
- process.exitCode = 1;
68
- else if (code !== null)
69
- process.exitCode = code;
70
- resolve();
71
- });
72
- });
38
+ function projectDirectoryName(value) {
39
+ const slug = value
40
+ .trim()
41
+ .toLowerCase()
42
+ .replace(/[^a-z0-9]+/g, '-')
43
+ .replace(/^-+|-+$/g, '');
44
+ return slug || 'siduri';
73
45
  }
74
46
  async function withTask(label, task) {
75
47
  process.stdout.write(`${colors.dim}${label}${colors.reset}`);
@@ -93,351 +65,221 @@ async function withTask(label, task) {
93
65
  function nonEmpty(value) {
94
66
  return value.trim().length > 0 || 'Please enter a value.';
95
67
  }
96
- function urlValue(value) {
97
- try {
98
- const url = new URL(value);
99
- return ['http:', 'https:'].includes(url.protocol) || 'Use an HTTP(S) URL.';
68
+ async function runCreateWizard(targetDir) {
69
+ printHeader();
70
+ // 1. Discover manifests from installed or monorepo packages
71
+ const registry = discovery_1.OrganRegistry.discover();
72
+ const availableManifests = registry.getAll();
73
+ if (availableManifests.length === 0) {
74
+ throw new Error('No @siduri-x/* organ packages found. Please ensure organs are installed or in workspace.');
100
75
  }
101
- catch {
102
- return 'Use a valid HTTP(S) URL.';
76
+ printSection('Companion Details');
77
+ const basicAnswers = await inquirer_1.default.prompt([
78
+ {
79
+ type: 'input',
80
+ name: 'name',
81
+ message: 'Companion name:',
82
+ default: 'Siduri',
83
+ validate: nonEmpty,
84
+ },
85
+ ]);
86
+ const companionName = basicAnswers.name;
87
+ const projectDir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : node_path_1.default.resolve(process.cwd(), projectDirectoryName(companionName));
88
+ printSection('Organ Selection');
89
+ console.log(`${colors.dim}Select any combination of organs to compose into your standalone instance.${colors.reset}\n`);
90
+ // Brain is required by architecture contract for cognition
91
+ const brainManifest = registry.get('brain');
92
+ const nonBrainManifests = availableManifests.filter((m) => m.organType !== 'brain');
93
+ const organChoices = nonBrainManifests.map((m) => ({
94
+ name: `${m.displayName} (${m.name})`,
95
+ value: m.organType,
96
+ checked: m.organType === 'memory', // default memory checked
97
+ }));
98
+ const { selectedOrganTypes } = await inquirer_1.default.prompt({
99
+ type: 'checkbox',
100
+ name: 'selectedOrganTypes',
101
+ message: 'Select organs to install:',
102
+ choices: organChoices,
103
+ });
104
+ const selectedManifests = [];
105
+ if (brainManifest) {
106
+ selectedManifests.push(brainManifest);
103
107
  }
104
- }
105
- function safePart(value) {
106
- return value.replace(/[^a-zA-Z0-9._-]/g, '_');
107
- }
108
- function projectDirectoryName(value) {
109
- const slug = value
110
- .trim()
111
- .toLowerCase()
112
- .replace(/[^a-z0-9]+/g, '-')
113
- .replace(/^-+|-+$/g, '');
114
- return slug || 'siduri';
115
- }
116
- async function getJson(url) {
117
- const controller = new AbortController();
118
- const timeout = setTimeout(() => controller.abort(), 15000);
119
- let response;
120
- try {
121
- response = await fetch(url, { headers: { accept: 'application/json' }, signal: controller.signal });
108
+ for (const organType of selectedOrganTypes) {
109
+ const m = registry.get(organType);
110
+ if (m)
111
+ selectedManifests.push(m);
122
112
  }
123
- finally {
124
- clearTimeout(timeout);
113
+ printSection('Review Composition');
114
+ console.log(` ${colors.dim}Companion Name:${colors.reset} ${companionName}`);
115
+ console.log(` ${colors.dim}Target Path:${colors.reset} ${projectDir}`);
116
+ console.log(` ${colors.dim}Core Protocol:${colors.reset} @siduri-x/core`);
117
+ console.log(` ${colors.dim}Selected Organs:${colors.reset}`);
118
+ for (const m of selectedManifests) {
119
+ console.log(` - ${m.displayName} (${colors.dim}${m.name}${colors.reset})`);
125
120
  }
126
- if (!response.ok)
127
- throw new Error(`${url} returned HTTP ${response.status}`);
128
- return response.json();
129
- }
130
- async function findManifest(root) {
131
- try {
132
- await (0, promises_1.readFile)(node_path_1.default.join(root, 'manifest.json'), 'utf8');
133
- return root;
121
+ const { confirm } = await inquirer_1.default.prompt({
122
+ type: 'confirm',
123
+ name: 'confirm',
124
+ message: 'Generate standalone Siduri instance with these organs?',
125
+ default: true,
126
+ });
127
+ if (!confirm) {
128
+ console.log('Instance creation cancelled.');
129
+ return;
134
130
  }
135
- catch {
136
- // Archives may contain one top-level directory.
131
+ // 2. Generate Files
132
+ const files = (0, generator_1.generateInstanceFiles)({
133
+ name: companionName,
134
+ selectedManifests,
135
+ });
136
+ await (0, promises_1.mkdir)(projectDir, { recursive: true });
137
+ await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'src'), { recursive: true });
138
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'package.json'), files['package.json'], 'utf8');
139
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'siduri.config.json'), files['siduri.config.json'], 'utf8');
140
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'siduri.schema.json'), files['siduri.schema.json'], 'utf8');
141
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, '.env.example'), files['.env.example'], 'utf8');
142
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'README.md'), files['README.md'], 'utf8');
143
+ await (0, promises_1.writeFile)(node_path_1.default.join(projectDir, 'src/index.js'), files['src/index.js'], 'utf8');
144
+ if (files.createAssetsBodyDir) {
145
+ await (0, promises_1.mkdir)(node_path_1.default.join(projectDir, 'assets/body/model'), { recursive: true });
137
146
  }
138
- for (const entry of await (0, promises_1.readdir)(root, { withFileTypes: true })) {
139
- if (!entry.isDirectory())
140
- continue;
141
- try {
142
- await (0, promises_1.readFile)(node_path_1.default.join(root, entry.name, 'manifest.json'), 'utf8');
143
- return node_path_1.default.join(root, entry.name);
144
- }
145
- catch {
146
- // Continue searching immediate children.
147
- }
147
+ printSuccess(`Generated standalone files at ${projectDir}`);
148
+ // 3. Install packages if not in dry-run
149
+ try {
150
+ await withTask('Installing dependencies (npm install)', async () => {
151
+ await execFile('npm', ['install', '--no-audit', '--no-fund'], { cwd: projectDir });
152
+ });
153
+ printSuccess('Dependencies installed successfully.');
148
154
  }
149
- throw new Error('Downloaded archive does not contain a manifest.json at its root');
150
- }
151
- async function installArchive(pack) {
152
- const response = await fetch(pack.distribution.url);
153
- if (!response.ok)
154
- throw new Error(`Pack archive returned HTTP ${response.status}`);
155
- const archive = Buffer.from(await response.arrayBuffer());
156
- if (pack.distribution.checksum) {
157
- const checksum = (0, node_crypto_1.createHash)('sha256').update(archive).digest('hex');
158
- if (checksum !== pack.distribution.checksum) {
159
- throw new Error(`Pack archive checksum mismatch for ${pack.id}@${pack.version}`);
160
- }
155
+ catch (err) {
156
+ console.warn(`${colors.yellow}!${colors.reset} Notice: npm install had warnings or requires network: ${err.message}`);
161
157
  }
162
- const work = await (0, promises_1.mkdtemp)(node_path_1.default.join((0, node_os_1.tmpdir)(), 'siduri-pack-'));
158
+ printSection('Instance Ready');
159
+ console.log(`\nYour Siduri companion is ready! Next steps:\n`);
160
+ console.log(` cd ${node_path_1.default.relative(process.cwd(), projectDir) || '.'}`);
161
+ console.log(` cp .env.example .env ${colors.dim}# Fill in required API keys/credentials${colors.reset}`);
162
+ console.log(` npm start ${colors.dim}# Start your standalone companion${colors.reset}\n`);
163
+ }
164
+ async function runCliDoctor(targetDir) {
165
+ printHeader();
166
+ const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
167
+ console.log(`${colors.cyan}Siduri Doctor${colors.reset}`);
168
+ console.log(`${colors.dim}─────────────${colors.reset}\n`);
163
169
  try {
164
- const archivePath = node_path_1.default.join(work, 'pack.tar.gz');
165
- const extractedPath = node_path_1.default.join(work, 'extracted');
166
- await (0, promises_1.writeFile)(archivePath, archive);
167
- await (0, promises_1.mkdir)(extractedPath);
168
- const { stdout: listing } = await execFile('tar', ['-tzf', archivePath]);
169
- if (listing.split('\n').some((entry) => entry.startsWith('/') || entry.split('/').includes('..'))) {
170
- throw new Error('Pack archive contains an unsafe path');
170
+ const report = await (0, doctor_1.runDoctor)({ projectDir: dir });
171
+ console.log(`${colors.dim}Instance:${colors.reset} ${report.instanceName}`);
172
+ console.log(`${colors.dim}Organs:${colors.reset} ${report.configuredOrgans.join(', ')}\n`);
173
+ const categories = ['Environment', 'Services', 'Database', 'Health Probe'];
174
+ for (const cat of categories) {
175
+ const items = report.results.filter((r) => r.category === cat);
176
+ if (items.length > 0) {
177
+ console.log(`${colors.cyan}${cat}${colors.reset}`);
178
+ for (const item of items) {
179
+ if (item.status === 'PASS') {
180
+ console.log(` ${colors.green}✓${colors.reset} ${item.name} ${colors.dim}(${item.message || 'OK'})${colors.reset}`);
181
+ }
182
+ else if (item.status === 'OPTIONAL_MISSING') {
183
+ console.log(` ${colors.dim}○${colors.reset} ${item.name} ${colors.dim}(Optional, not set)${colors.reset}`);
184
+ }
185
+ else if (item.status === 'SKIPPED') {
186
+ console.log(` ${colors.dim}— ${item.name} (${item.message})${colors.reset}`);
187
+ }
188
+ else {
189
+ console.log(` ${colors.yellow}✗${colors.reset} ${item.name}`);
190
+ if (item.organName) {
191
+ console.log(` ${colors.dim}Required by:${colors.reset} ${item.organName}`);
192
+ }
193
+ if (item.message) {
194
+ console.log(` ${colors.yellow}${item.message}${colors.reset}`);
195
+ }
196
+ if (item.remediation) {
197
+ console.log(` ${colors.dim}Remediation:${colors.reset} ${item.remediation}`);
198
+ }
199
+ }
200
+ }
201
+ console.log();
202
+ }
203
+ }
204
+ if (report.passed) {
205
+ console.log(`${colors.green}Result: PASS${colors.reset}\n`);
206
+ process.exitCode = 0;
207
+ }
208
+ else {
209
+ console.log(`${colors.yellow}Result: FAIL${colors.reset}\n`);
210
+ process.exitCode = 1;
171
211
  }
172
- await execFile('tar', ['-xzf', archivePath, '-C', extractedPath, '--no-same-owner', '--no-same-permissions', '--no-absolute-names']);
173
- const sourcePath = await findManifest(extractedPath);
174
- const destination = node_path_1.default.join((0, node_os_1.homedir)(), '.siduri', 'knowledge', safePart(pack.publisher), safePart(pack.name), safePart(pack.version));
175
- await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
176
- await (0, promises_1.rm)(destination, { recursive: true, force: true });
177
- await (0, promises_1.cp)(sourcePath, destination, { recursive: true });
178
- return destination;
179
212
  }
180
- finally {
181
- await (0, promises_1.rm)(work, { recursive: true, force: true });
213
+ catch (err) {
214
+ console.error(`\n${colors.yellow}Doctor Error:${colors.reset} ${err.message}\n`);
215
+ process.exitCode = 2;
182
216
  }
183
217
  }
184
- async function chooseHubPack(registryUrl) {
185
- const { query } = await inquirer_1.default.prompt({
186
- type: 'input',
187
- name: 'query',
188
- message: 'Search E Knowledge Hub or enter package ID (e.g. @vxnus/e-teyvat):',
189
- });
190
- const normalized = query.trim().replace(/^@/, '');
191
- const [publisher, name] = normalized.split('/');
192
- const result = publisher && name && !query.includes(' ')
193
- ? await withTask('Searching E Knowledge Hub', () => getJson(`${registryUrl}/${encodeURIComponent(publisher)}/${encodeURIComponent(name)}`))
194
- : await withTask('Searching E Knowledge Hub', () => getJson(`${registryUrl}?q=${encodeURIComponent(query)}&limit=20`));
195
- const packs = 'packs' in result ? result.packs : [result];
196
- if (packs.length === 0)
197
- throw new Error(`No knowledge packs found for '${query}'`);
198
- if (packs.length === 1)
199
- return packs[0];
200
- const { selected } = await inquirer_1.default.prompt({
201
- type: 'list',
202
- name: 'selected',
203
- message: 'Select a knowledge pack:',
204
- choices: packs.map((pack) => ({ name: `${pack.id} v${pack.version}`, value: pack.id })),
205
- });
206
- return packs.find((pack) => pack.id === selected);
207
- }
208
- async function configureKnowledge() {
209
- const { mode } = await inquirer_1.default.prompt({
210
- type: 'list',
211
- name: 'mode',
212
- message: 'Knowledge source?',
213
- choices: [
214
- { name: 'E Knowledge Hub', value: 'hub' },
215
- { name: 'Installed local pack', value: 'local' },
216
- { name: 'Hosted provider URL', value: 'remote' },
217
- { name: 'Do not use knowledge', value: 'none' },
218
- ],
219
- });
220
- if (mode === 'none')
221
- return { provider: 'none' };
222
- if (mode === 'local') {
223
- const { packPath } = await inquirer_1.default.prompt({
224
- type: 'input',
225
- name: 'packPath',
226
- message: 'Path to the installed E knowledge pack:',
227
- default: './knowledge-pack',
228
- });
229
- const resolved = node_path_1.default.resolve(packPath);
230
- const loaded = await withTask('Validating local knowledge pack', () => (0, e_knowledge_1.loadPack)(resolved));
231
- printSuccess(`Knowledge ready · ${loaded.manifest.id} · revision ${loaded.revision.id}`);
232
- return { provider: 'e-knowledge', packPath: resolved };
218
+ async function runCliDb(subcommand, targetDir) {
219
+ printHeader();
220
+ if (subcommand !== 'push') {
221
+ console.log('Usage: siduri db push');
222
+ process.exitCode = 2;
223
+ return;
233
224
  }
234
- if (mode === 'remote') {
235
- const { baseUrl } = await inquirer_1.default.prompt({
236
- type: 'input',
237
- name: 'baseUrl',
238
- message: 'Remote knowledge provider URL:',
239
- });
240
- const provider = (0, e_knowledge_1.createRemoteProvider)({ baseUrl, timeoutMs: 5000 });
241
- const manifest = await withTask('Checking provider manifest', async () => {
242
- if (!provider.manifest) {
243
- throw new Error('Remote knowledge provider does not support manifest inspection');
225
+ const dir = targetDir ? node_path_1.default.resolve(process.cwd(), targetDir) : process.cwd();
226
+ console.log(`${colors.cyan}Siduri Database Migrations${colors.reset}`);
227
+ console.log(`${colors.dim}──────────────────────────${colors.reset}\n`);
228
+ try {
229
+ const res = await (0, db_1.runDbPush)({ projectDir: dir });
230
+ if (res.status === 'NOOP') {
231
+ console.log(`${colors.dim}— ${res.message}${colors.reset}\n`);
232
+ }
233
+ else {
234
+ printSuccess(res.message);
235
+ if (res.appliedMigrations.length > 0) {
236
+ console.log(`${colors.dim}Applied:${colors.reset} ${res.appliedMigrations.join(', ')}`);
244
237
  }
245
- return provider.manifest();
246
- });
247
- printSuccess(`Provider ready · ${manifest.id}`);
248
- return { provider: 'e-remote', baseUrl: baseUrl.replace(/\/+$/, ''), timeoutMs: 5000 };
249
- }
250
- const registryUrl = (process.env.E_REGISTRY_URL || process.env.SIDURI_KNOWLEDGE_REGISTRY_URL || DEFAULT_REGISTRY_URL).replace(/\/+$/, '');
251
- const pack = await chooseHubPack(registryUrl);
252
- if (pack.distribution.kind === 'archive') {
253
- const { install } = await inquirer_1.default.prompt({
254
- type: 'confirm',
255
- name: 'install',
256
- message: `Install ${pack.id} v${pack.version} locally?`,
257
- default: true,
258
- });
259
- if (install) {
260
- const packPath = await withTask(`Installing ${pack.id}@${pack.version}`, () => installArchive(pack));
261
- const loaded = await withTask('Validating installed knowledge pack', () => (0, e_knowledge_1.loadPack)(packPath));
262
- printSuccess(`Knowledge ready · ${loaded.manifest.id} · revision ${loaded.revision.id}`);
263
- return { provider: 'e-knowledge', packPath };
238
+ console.log();
264
239
  }
240
+ process.exitCode = 0;
265
241
  }
266
- if (pack.distribution.kind !== 'provider') {
267
- throw new Error('Archive installation was declined and no hosted provider is available');
242
+ catch (err) {
243
+ console.error(`\n${colors.yellow}Database Migration Error:${colors.reset} ${err.message}\n`);
244
+ process.exitCode = 3;
268
245
  }
269
- const provider = (0, e_knowledge_1.createRemoteProvider)({ baseUrl: pack.distribution.url, timeoutMs: 5000, manifest: pack });
270
- await withTask('Checking provider manifest', async () => {
271
- if (provider.manifest) {
272
- return provider.manifest();
273
- }
274
- const cleanUrl = pack.distribution.url.replace(/\/+$/, '');
275
- const res = await fetch(`${cleanUrl}/manifest`, { headers: { accept: 'application/json' } });
276
- if (res.ok)
277
- return res.json();
278
- return pack;
279
- });
280
- printSuccess(`Provider ready · ${pack.id}`);
281
- return { provider: 'e-hub', registryUrl, packId: pack.id, timeoutMs: 5000 };
282
246
  }
283
247
  async function main() {
284
- const command = process.argv[2];
248
+ const args = process.argv.slice(2);
249
+ const command = args[0];
285
250
  if (command === '--version' || command === '-v') {
286
251
  console.log(CLI_VERSION);
287
252
  return;
288
253
  }
289
- if (command === 'start') {
290
- await startRuntime();
254
+ if (command === 'create') {
255
+ const targetDir = args[1];
256
+ await runCreateWizard(targetDir);
291
257
  return;
292
258
  }
293
- if (command !== 'create') {
294
- printHeader();
295
- console.log('Usage: npx @vxnus/siduri create');
296
- console.log(' npx @vxnus/siduri start');
297
- console.log(' npx @vxnus/siduri --version');
259
+ if (command === 'doctor') {
260
+ const targetDir = args[1];
261
+ await runCliDoctor(targetDir);
262
+ return;
263
+ }
264
+ if (command === 'db') {
265
+ const subcommand = args[1];
266
+ const targetDir = args[2];
267
+ await runCliDb(subcommand, targetDir);
298
268
  return;
299
269
  }
300
270
  printHeader();
301
- printSection('Companion');
302
- const answers = await inquirer_1.default.prompt([
303
- { type: 'input', name: 'name', message: 'Companion name:', default: 'Siduri', validate: nonEmpty },
304
- {
305
- type: 'list',
306
- name: 'memoryEngine',
307
- message: 'Memory database?',
308
- choices: [
309
- { name: 'PostgreSQL', value: 'postgres' },
310
- { name: 'SQLite (future)', value: 'sqlite', disabled: 'Coming soon' },
311
- ],
312
- },
313
- ]);
314
- const { memoryDeployment } = await inquirer_1.default.prompt({
315
- type: 'list',
316
- name: 'memoryDeployment',
317
- message: 'PostgreSQL deployment?',
318
- choices: [
319
- { name: 'Local PostgreSQL', value: 'local' },
320
- { name: 'Neon', value: 'neon' },
321
- { name: 'Supabase', value: 'supabase' },
322
- { name: 'Other PostgreSQL provider', value: 'other' },
323
- ],
324
- });
325
- const projectPath = node_path_1.default.resolve(process.cwd(), projectDirectoryName(answers.name));
326
- await (0, promises_1.mkdir)(projectPath, { recursive: true });
327
- printSuccess(`${answers.name} · PostgreSQL / ${memoryDeployment} · ${projectPath}`);
328
- printSection('Brain · required');
329
- const { brainProvider } = await inquirer_1.default.prompt({
330
- type: 'list',
331
- name: 'brainProvider',
332
- message: 'Brain provider?',
333
- choices: [
334
- { name: 'OpenRouter (managed model routing)', value: 'openrouter' },
335
- { name: 'OpenAI-compatible API (custom endpoint)', value: 'openai-compatible' },
336
- ],
337
- });
338
- const brain = brainProvider === 'openrouter'
339
- ? {
340
- provider: 'openrouter',
341
- model: (await inquirer_1.default.prompt({ type: 'input', name: 'model', message: 'Model ID:', default: 'openai/gpt-4o-mini', validate: nonEmpty })).model,
342
- apiKeyEnv: 'OPENROUTER_API_KEY',
343
- }
344
- : await (async () => {
345
- const values = await inquirer_1.default.prompt([
346
- { type: 'input', name: 'baseUrl', message: 'OpenAI-compatible API base URL:', default: 'http://127.0.0.1:1234/v1', validate: urlValue },
347
- { type: 'input', name: 'model', message: 'Model ID:', default: 'local-model', validate: nonEmpty },
348
- { type: 'input', name: 'apiKeyEnv', message: 'API key environment variable:', default: 'OPENAI_COMPATIBLE_API_KEY', validate: nonEmpty },
349
- ]);
350
- return { provider: 'openai-compatible', ...values };
351
- })();
352
- printSuccess(`${brain.provider} · ${brain.model}`);
353
- printSection('Optional organs');
354
- const { voice } = await inquirer_1.default.prompt({
355
- type: 'list',
356
- name: 'voice',
357
- message: 'Voice provider?',
358
- choices: [
359
- { name: 'VOICEVOX', value: 'voicevox' },
360
- { name: 'Do not use voice', value: 'none' },
361
- ],
362
- });
363
- const knowledge = await configureKnowledge();
364
- const remaining = await inquirer_1.default.prompt([
365
- { type: 'list', name: 'behavior', message: 'Behavior preset?', choices: [{ name: 'Calm', value: 'Calm' }, { name: 'Cheerful, Encouraging', value: 'Cheerful, Encouraging' }, { name: 'Do not use custom behavior', value: 'none' }] },
366
- { type: 'list', name: 'body', message: 'Body provider?', choices: [{ name: 'Live2D Body (Renderer-agnostic)', value: 'live2d' }, { name: 'Do not use body', value: 'none' }] },
367
- { type: 'list', name: 'vision', message: 'Vision provider?', choices: [{ name: 'OpenRouter vision', value: 'openrouter' }, { name: 'Do not use vision', value: 'none' }] },
368
- ]);
369
- const config = {
370
- id: 'default',
371
- name: answers.name,
372
- brain,
373
- voice: { provider: voice, speakerId: 1 },
374
- memory: { provider: answers.memoryEngine, deployment: memoryDeployment },
375
- knowledge,
376
- behavior: { provider: remaining.behavior === 'none' ? 'none' : 'active_self', preset: remaining.behavior },
377
- body: { provider: remaining.body },
378
- vision: { provider: remaining.vision, model: 'gpt-4-vision' },
379
- };
380
- const configPath = node_path_1.default.join(projectPath, 'siduri.config.json');
381
- try {
382
- await (0, promises_1.readFile)(configPath, 'utf8');
383
- const { overwrite } = await inquirer_1.default.prompt({
384
- type: 'confirm',
385
- name: 'overwrite',
386
- message: `${node_path_1.default.basename(configPath)} already exists. Replace it?`,
387
- default: false,
388
- });
389
- if (!overwrite) {
390
- console.log('Configuration left unchanged.');
271
+ console.log('Usage: npx @vxnus/siduri create [directory]');
272
+ console.log(' npx @vxnus/siduri doctor [directory]');
273
+ console.log(' npx @vxnus/siduri db push [directory]');
274
+ console.log(' npx @vxnus/siduri --version\n');
275
+ }
276
+ if (require.main === module) {
277
+ main().catch((error) => {
278
+ if (error && typeof error === 'object' && 'name' in error && error.name === 'ExitPromptError') {
279
+ console.log('\nOperation cancelled.');
391
280
  return;
392
281
  }
393
- }
394
- catch {
395
- // New configuration.
396
- }
397
- printSection('Review');
398
- console.log(` ${colors.dim}companion${colors.reset} ${config.name}`);
399
- console.log(` ${colors.dim}brain${colors.reset} ${config.brain.provider} · ${config.brain.model}`);
400
- console.log(` ${colors.dim}memory${colors.reset} ${config.memory.provider}`);
401
- console.log(` ${colors.dim}voice${colors.reset} ${config.voice.provider}`);
402
- console.log(` ${colors.dim}knowledge${colors.reset} ${config.knowledge.provider}`);
403
- console.log(` ${colors.dim}behavior${colors.reset} ${config.behavior.provider}`);
404
- console.log(` ${colors.dim}body${colors.reset} ${config.body.provider}`);
405
- console.log(` ${colors.dim}vision${colors.reset} ${config.vision.provider}`);
406
- const { confirm } = await inquirer_1.default.prompt({
407
- type: 'confirm',
408
- name: 'confirm',
409
- message: 'Write this Siduri configuration?',
410
- default: true,
282
+ console.error(`\n${colors.yellow}!${colors.reset} ${error instanceof Error ? error.message : error}`);
283
+ process.exitCode = 1;
411
284
  });
412
- if (!confirm) {
413
- console.log('Configuration cancelled.');
414
- return;
415
- }
416
- await (0, promises_1.writeFile)(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
417
- printSuccess(`Configuration written · ${configPath}`);
418
- await (0, promises_1.cp)(node_path_1.default.join(__dirname, 'runtime.js'), node_path_1.default.join(projectPath, 'siduri-runtime.js'));
419
- await (0, promises_1.writeFile)(node_path_1.default.join(projectPath, 'package.json'), JSON.stringify({
420
- name: projectDirectoryName(answers.name),
421
- private: true,
422
- version: '0.0.0',
423
- scripts: { start: 'node siduri-runtime.js', dev: 'node siduri-runtime.js' },
424
- dependencies: RUNTIME_DEPENDENCIES,
425
- }, null, 2) + '\n', { mode: 0o600 });
426
- const keyEnv = config.brain.apiKeyEnv || 'OPENROUTER_API_KEY';
427
- await (0, promises_1.writeFile)(node_path_1.default.join(projectPath, '.env.example'), [
428
- `${keyEnv}=`,
429
- 'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/siduri',
430
- '',
431
- ].join('\n'), { mode: 0o600 });
432
- await withTask('Installing Siduri runtime dependencies', () => execFile('npm', ['install', '--no-audit', '--no-fund'], { cwd: projectPath }).then(() => undefined));
433
- printSuccess(`Siduri instance ready · ${projectPath}`);
434
- console.log(`${colors.dim}Next: cd ${projectDirectoryName(answers.name)} && npm run start${colors.reset}\n`);
435
285
  }
436
- main().catch((error) => {
437
- if (error && typeof error === 'object' && 'name' in error && error.name === 'ExitPromptError') {
438
- console.log('\nConfiguration cancelled.');
439
- return;
440
- }
441
- console.error(`\n${colors.yellow}!${colors.reset} ${error instanceof Error ? error.message : error}`);
442
- process.exitCode = 1;
443
- });
@@ -0,0 +1,33 @@
1
+ export interface OrganEnvironmentVar {
2
+ name: string;
3
+ required?: boolean;
4
+ secret?: boolean;
5
+ default?: string;
6
+ description?: string;
7
+ }
8
+ export interface OrganServiceRequirement {
9
+ name: string;
10
+ kind: 'database' | 'http_service' | 'process' | string;
11
+ optional?: boolean;
12
+ description?: string;
13
+ }
14
+ export interface OrganDatabaseRequirement {
15
+ engine: 'postgres' | 'sqlite' | string;
16
+ migrationsDir?: string;
17
+ }
18
+ export interface OrganManifest {
19
+ name: string;
20
+ organType: string;
21
+ version: string;
22
+ displayName: string;
23
+ description?: string;
24
+ entrypoint: string;
25
+ factory: string;
26
+ configKey: string;
27
+ configSchema: Record<string, any>;
28
+ environment: OrganEnvironmentVar[];
29
+ services: OrganServiceRequirement[];
30
+ database?: OrganDatabaseRequirement | null;
31
+ healthCheck?: string | null;
32
+ }
33
+ export declare function validateOrganManifest(manifest: unknown, sourcePath?: string): OrganManifest;