UNPKG

aiom_pack

Version:

Framework for interdependent (mcmc-like) behavioral experiments

222 lines (193 loc) 7.26 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const readline = require('readline'); // Parse command line arguments const args = process.argv.slice(2); const command = args[0]; function showHelp() { console.log(` 🧪 Behavioral Experiments Framework CLI Usage: npx aiom_pack create [experiment-name(optional)] npx aiom_pack help Commands: create Create a new experiment help Show this help message Examples: npx aiom_pack create npx aiom_pack create my-study `); } async function createExperiment() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function ask(question) { return new Promise(resolve => rl.question(question, resolve)); } try { console.log('🧪 Creating a new aiom experiment...\n'); // Use command line argument or ask for name let name = args[1]; if (!name) { name = await ask('Experiment name: '); } const type = await ask('Experiment type (e.g., blockwise-MCMCP): '); const prolific = await ask('Will Use Prolific? (y/n): '); const experimentDir = path.join(process.cwd(), name); // Create directory structure fs.mkdirSync(experimentDir, { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'custom_text'), { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'models', 'gatekeepers'), { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'public', 'base'), { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'public', 'experiment'), { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'static', 'stimuli'), { recursive: true }); fs.mkdirSync(path.join(experimentDir, 'utils'), { recursive: true }); // copy package folder to experimentDir // fs.cpSync( // path.join(__dirname, '..', 'src', 'deploy'), // experimentDir, // { recursive: true, force: true } // ); fs.cpSync( path.join(__dirname, '..', 'src', 'models', 'gatekeepers', '7_basic_emotions'), path.join(experimentDir, 'models', 'gatekeepers'), { recursive: true, force: true } ); fs.cpSync( path.join(__dirname, '..', 'src', 'templates', 'stimuli'), path.join(experimentDir, 'static', 'stimuli'), { recursive: true, force: true } ); // Create default customized text file fs.cpSync( path.join(__dirname, '..', 'src', 'templates', 'custom_text'), path.join(experimentDir, 'custom_text'), { recursive: true, force: true } ); console.log('✅ Template files copied successfully.'); // Create package.json const packageJson = { name: name, version: "1.0.0", description: type, main: "app.js", scripts: { "start": "node app.js", "dev": "nodemon app.js", "download": "node node_modules/aiom_pack/src/utils/download.js", "deploy": "node node_modules/aiom_pack/src/services/heroku.js" }, dependencies: { "aiom_pack": "^1.1.8", "csv-writer": "^1.6.0" }, devDependencies: { "nodemon": "^3.0.0" } }; fs.writeFileSync( path.join(experimentDir, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create .env file based on your current configuration let envContent = `### Experiment Cofig: ${type} ### # Database Configuration (if prolific is false) DB_USER=postgres DB_PASSWORD=aiom DB_HOST=localhost DB_PORT=5432 DB_NAME=aiom # stimuli mode=image imageurl=http://localhost:8000 # prolific settings prolific=${prolific === 'y' ? 'true' : 'false'} # for all MCMCPs trial_per_participant_per_class=11 n_rest=10 class_questions=Who looks happier?|Who looks sadder?|Who looks more surprised?|Who looks angrier?|Who looks more neutral?|Who looks more disgusted?|Who looks more fearful? classes=happy|sad|surprise|angry|neutral|disgust|fear dim=3 lower_bound=-30 upper_bound=30 n_chain=1 # if not gatekeeper, proposal_cov is the covariance of the proposal distribution proposal_cov=15 # Post experiment settings (production_mode: webcam/upload) categorization=false production=false production_mode=webcam `; if (type === 'blockwise-MCMCP' || type === 'individual-MCMCP') { envContent += ` # gatekeeper: for individual and block-wise MCMCP (true/false) gatekeeper=false gatekeeper_dir=models/gatekeepers stuck_patience=20 `; } if (type === 'group-MCMCP') { envContent += ` # for group-level MCMCP group_table_name=group `; } if (type === 'consensual-MCMCP') { envContent += ` # for consensual MCMCP consensus_n=3 `; } else { envContent += ` # attention-check: for all MCMCPs except consensual MCMCP attention_check=false attention_check_rate=0.008 attention_check_dir=static/stimuli/attention_check `; } fs.writeFileSync(path.join(experimentDir, '.env'), envContent); console.log('\n✅ .env created'); // create .gitignore const gitignoreContent = `node_modules/ npm-debug.log .DS_Store`; fs.writeFileSync(path.join(experimentDir, '.gitignore'), gitignoreContent); console.log('\n✅ .gitignore created'); // Create app.js const appJs = `const { createExperiment } = require('aiom_pack'); const experiment = createExperiment({ experimentPath: __dirname }); const port = process.env.PORT || 3000; experiment.start(port); `; fs.writeFileSync(path.join(experimentDir, 'app.js'), appJs); console.log(`\n✅ Experiment "${name}" created successfully!`); console.log(`\nNext steps:`); console.log(` cd ${name}`); console.log(` npm install`); console.log(` npm start # Start the experiment`); console.log(` npm run dev # Start with auto-reload (development)`); console.log(` npm run download # Show available database tables`); console.log(` npm run download participants # Download participants data`); console.log(` npm run download --all # Download all data tables`); console.log(` npm run deploy # Deploy to Heroku`); } catch (error) { console.error('Error creating experiment:', error); process.exit(1); } finally { rl.close(); } } if (command === 'create') { createExperiment(); } else if (command === 'help' || !command) { showHelp(); } else { console.error(`Unknown command: ${command}`); showHelp(); process.exit(1); }