aiom_pack
Version:
Framework for interdependent (mcmc-like) behavioral experiments
222 lines (193 loc) • 7.26 kB
JavaScript
#!/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);
}