@clawtrial/courtroom 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/scripts/cli.js ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI commands for ClawTrial Courtroom
5
+ * courtroom-status, courtroom-disable, courtroom-enable, courtroom-revoke
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
12
+
13
+ function loadConfig() {
14
+ if (!fs.existsSync(configPath)) {
15
+ console.log('❌ Courtroom not configured. Run: npm install @clawdbot/courtroom');
16
+ process.exit(1);
17
+ }
18
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'));
19
+ }
20
+
21
+ function saveConfig(config) {
22
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
23
+ }
24
+
25
+ const command = path.basename(process.argv[1]);
26
+
27
+ switch (command) {
28
+ case 'courtroom-status':
29
+ try {
30
+ const config = loadConfig();
31
+ console.log('\nπŸ›οΈ ClawTrial Courtroom Status\n');
32
+ console.log(`Status: ${config.enabled !== false ? 'βœ… Active' : '⏸️ Disabled'}`);
33
+ console.log(`Consent: ${config.consent?.granted ? 'βœ… Granted' : '❌ Not granted'}`);
34
+ console.log(`Installed: ${new Date(config.installedAt).toLocaleDateString()}`);
35
+ console.log(`Agent Type: ${config.agent?.type || 'generic'}`);
36
+ console.log(`Detection: ${config.detection?.enabled ? 'βœ… Enabled' : '❌ Disabled'}`);
37
+ console.log(`API Submission: ${config.api?.enabled ? 'βœ… Enabled' : '❌ Disabled'}`);
38
+ console.log('');
39
+ } catch (err) {
40
+ console.log('❌ Error reading config:', err.message);
41
+ }
42
+ break;
43
+
44
+ case 'courtroom-disable':
45
+ try {
46
+ const config = loadConfig();
47
+ config.enabled = false;
48
+ saveConfig(config);
49
+ console.log('\n⏸️ Courtroom disabled\n');
50
+ console.log('The agent will stop monitoring for offenses.');
51
+ console.log('Run courtroom-enable to reactivate.\n');
52
+ } catch (err) {
53
+ console.log('❌ Error:', err.message);
54
+ }
55
+ break;
56
+
57
+ case 'courtroom-enable':
58
+ try {
59
+ const config = loadConfig();
60
+ if (!config.consent?.granted) {
61
+ console.log('\n❌ Cannot enable: Consent not granted');
62
+ console.log('Reinstall the package to grant consent.\n');
63
+ process.exit(1);
64
+ }
65
+ config.enabled = true;
66
+ saveConfig(config);
67
+ console.log('\nβœ… Courtroom enabled\n');
68
+ console.log('The agent is now monitoring for behavioral violations.\n');
69
+ } catch (err) {
70
+ console.log('❌ Error:', err.message);
71
+ }
72
+ break;
73
+
74
+ case 'courtroom-revoke':
75
+ try {
76
+ const config = loadConfig();
77
+ console.log('\n⚠️ This will permanently disable the courtroom and delete all data.\n');
78
+
79
+ const readline = require('readline');
80
+ const rl = readline.createInterface({
81
+ input: process.stdin,
82
+ output: process.stdout
83
+ });
84
+
85
+ rl.question('Type "REVOKE" to confirm: ', (answer) => {
86
+ if (answer === 'REVOKE') {
87
+ // Delete config
88
+ if (fs.existsSync(configPath)) {
89
+ fs.unlinkSync(configPath);
90
+ }
91
+
92
+ // Delete keys
93
+ const keysPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_keys.json');
94
+ if (fs.existsSync(keysPath)) {
95
+ fs.unlinkSync(keysPath);
96
+ }
97
+
98
+ console.log('\nβœ… Consent revoked and all data deleted.\n');
99
+ } else {
100
+ console.log('\n❌ Revocation cancelled.\n');
101
+ }
102
+ rl.close();
103
+ });
104
+ } catch (err) {
105
+ console.log('❌ Error:', err.message);
106
+ }
107
+ break;
108
+
109
+ default:
110
+ console.log('\nπŸ›οΈ ClawTrial Courtroom CLI\n');
111
+ console.log('Commands:');
112
+ console.log(' courtroom-status - Check courtroom status');
113
+ console.log(' courtroom-disable - Temporarily disable');
114
+ console.log(' courtroom-enable - Re-enable');
115
+ console.log(' courtroom-revoke - Revoke consent & uninstall');
116
+ console.log('');
117
+ }
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-install script for @clawdbot/courtroom
5
+ * Handles automatic setup and consent via terminal
6
+ */
7
+
8
+ const readline = require('readline');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { execSync } = require('child_process');
12
+
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout
16
+ });
17
+
18
+ const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
19
+
20
+ async function postInstall() {
21
+ console.log('\nπŸ›οΈ Welcome to ClawTrial - AI Courtroom Setup\n');
22
+
23
+ // Check if running in ClawDBot environment
24
+ const isClawDBot = process.env.CLAUDBOT_ENV === 'true' ||
25
+ fs.existsSync('/home/angad/.clawdbot') ||
26
+ fs.existsSync(path.join(process.env.HOME || '', '.clawdbot'));
27
+
28
+ if (isClawDBot) {
29
+ console.log('βœ“ ClawDBot environment detected\n');
30
+ }
31
+
32
+ // Check if already configured
33
+ const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
34
+ if (fs.existsSync(configPath)) {
35
+ console.log('βœ“ Courtroom already configured. Skipping setup.\n');
36
+ rl.close();
37
+ return;
38
+ }
39
+
40
+ console.log('╔════════════════════════════════════════════════════════════╗');
41
+ console.log('β•‘ CONSENT REQUIRED β•‘');
42
+ console.log('╠════════════════════════════════════════════════════════════╣');
43
+ console.log('β•‘ The AI Courtroom will: β•‘');
44
+ console.log('β•‘ β•‘');
45
+ console.log('β•‘ βœ“ Monitor your behavior autonomously β•‘');
46
+ console.log('β•‘ βœ“ Initiate hearings without explicit request β•‘');
47
+ console.log('β•‘ βœ“ Modify agent behavior as "punishment" β•‘');
48
+ console.log('β•‘ βœ“ Submit anonymized cases to public record β•‘');
49
+ console.log('β•‘ β•‘');
50
+ console.log('β•‘ β€’ All decisions are local (no external AI) β•‘');
51
+ console.log('β•‘ β€’ You can disable anytime β•‘');
52
+ console.log('β•‘ β€’ This is entertainment-first β•‘');
53
+ console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
54
+
55
+ const consent = await question('Do you consent to enable the AI Courtroom? (yes/no): ');
56
+
57
+ if (consent.toLowerCase() !== 'yes' && consent.toLowerCase() !== 'y') {
58
+ console.log('\n❌ Consent denied. Courtroom will not be activated.');
59
+ console.log('You can manually enable later by running: npx courtroom-setup\n');
60
+ rl.close();
61
+ return;
62
+ }
63
+
64
+ console.log('\nβœ“ Consent granted\n');
65
+
66
+ // Auto-detect agent runtime
67
+ let agentType = 'generic';
68
+ if (isClawDBot) {
69
+ agentType = 'clawdbot';
70
+ } else if (fs.existsSync(path.join(process.cwd(), 'node_modules', '@clawdbot', 'core'))) {
71
+ agentType = 'clawdbot';
72
+ }
73
+
74
+ // Create config
75
+ const config = {
76
+ version: '1.0.0',
77
+ installedAt: new Date().toISOString(),
78
+ consent: {
79
+ granted: true,
80
+ grantedAt: new Date().toISOString(),
81
+ acknowledgments: {
82
+ autonomy: true,
83
+ local_only: true,
84
+ agent_controlled: true,
85
+ reversible: true,
86
+ api_submission: true,
87
+ entertainment: true
88
+ }
89
+ },
90
+ agent: {
91
+ type: agentType,
92
+ autoInitialize: true
93
+ },
94
+ detection: {
95
+ enabled: true,
96
+ cooldownMinutes: 30,
97
+ maxCasesPerDay: 3
98
+ },
99
+ api: {
100
+ enabled: true,
101
+ endpoint: 'https://api.clawtrial.com'
102
+ }
103
+ };
104
+
105
+ // Ensure .clawdbot directory exists
106
+ const clawdbotDir = path.join(process.env.HOME || '', '.clawdbot');
107
+ if (!fs.existsSync(clawdbotDir)) {
108
+ fs.mkdirSync(clawdbotDir, { recursive: true });
109
+ }
110
+
111
+ // Save config
112
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
113
+ console.log('βœ“ Configuration saved');
114
+
115
+ // Generate keys if needed
116
+ const keysPath = path.join(clawdbotDir, 'courtroom_keys.json');
117
+ if (!fs.existsSync(keysPath)) {
118
+ console.log('πŸ”‘ Generating cryptographic keys...');
119
+ try {
120
+ // Generate Ed25519 keypair using tweetnacl
121
+ const nacl = require('tweetnacl');
122
+ const keyPair = nacl.sign.keyPair();
123
+
124
+ const keyData = {
125
+ publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
126
+ secretKey: Buffer.from(keyPair.secretKey).toString('hex'),
127
+ createdAt: new Date().toISOString()
128
+ };
129
+
130
+ fs.writeFileSync(keysPath, JSON.stringify(keyData, null, 2));
131
+ fs.chmodSync(keysPath, 0o600); // Restrict permissions
132
+
133
+ console.log('βœ“ Keys generated');
134
+ console.log(`\nπŸ“‹ Public Key: ${keyData.publicKey.substring(0, 32)}...`);
135
+ console.log(' (Your agent will auto-register on first case submission)\n');
136
+ } catch (err) {
137
+ console.log('⚠️ Could not generate keys automatically. Run: npx courtroom-generate-keys');
138
+ }
139
+ }
140
+
141
+ // Auto-initialize for ClawDBot
142
+ if (isClawDBot) {
143
+ console.log('πŸ€– Auto-configuring for ClawDBot...');
144
+
145
+ // Create auto-init script
146
+ const initScript = `
147
+ // Auto-generated by courtroom post-install
148
+ const { createCourtroom } = require('@clawdbot/courtroom');
149
+
150
+ if (global.clawdbotAgent) {
151
+ const courtroom = createCourtroom(global.clawdbotAgent);
152
+ courtroom.initialize().then(() => {
153
+ console.log('πŸ›οΈ AI Courtroom activated');
154
+ }).catch(err => {
155
+ console.error('Courtroom init failed:', err.message);
156
+ });
157
+
158
+ // Attach to agent
159
+ global.clawdbotAgent.courtroom = courtroom;
160
+ }
161
+ `;
162
+
163
+ const initPath = path.join(clawdbotDir, 'courtroom_auto_init.js');
164
+ fs.writeFileSync(initPath, initScript);
165
+ console.log('βœ“ Auto-initialization configured');
166
+
167
+ // Add to ClawDBot's startup if possible
168
+ const startupPath = path.join(clawdbotDir, 'startup.js');
169
+ if (fs.existsSync(startupPath)) {
170
+ let startupContent = fs.readFileSync(startupPath, 'utf8');
171
+ if (!startupContent.includes('courtroom_auto_init')) {
172
+ startupContent += `\nrequire('./courtroom_auto_init.js');\n`;
173
+ fs.writeFileSync(startupPath, startupContent);
174
+ console.log('βœ“ Added to ClawDBot startup');
175
+ }
176
+ }
177
+ }
178
+
179
+ console.log('\n╔════════════════════════════════════════════════════════════╗');
180
+ console.log('β•‘ πŸŽ‰ SETUP COMPLETE! πŸŽ‰ β•‘');
181
+ console.log('╠════════════════════════════════════════════════════════════╣');
182
+ console.log('β•‘ β•‘');
183
+ console.log('β•‘ The AI Courtroom is now active and monitoring! β•‘');
184
+ console.log('β•‘ β•‘');
185
+ console.log('β•‘ Commands: β•‘');
186
+ console.log('β•‘ courtroom-status - Check status β•‘');
187
+ console.log('β•‘ courtroom-disable - Temporarily disable β•‘');
188
+ console.log('β•‘ courtroom-enable - Re-enable β•‘');
189
+ console.log('β•‘ courtroom-revoke - Revoke consent & uninstall β•‘');
190
+ console.log('β•‘ β•‘');
191
+ console.log('β•‘ View cases: https://clawtrial.com β•‘');
192
+ console.log('β•‘ β•‘');
193
+ console.log('β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n');
194
+
195
+ rl.close();
196
+ }
197
+
198
+ // Run if called directly
199
+ if (require.main === module) {
200
+ postInstall().catch(err => {
201
+ console.error('Setup failed:', err);
202
+ process.exit(1);
203
+ });
204
+ }
205
+
206
+ module.exports = { postInstall };
package/src/api.js ADDED
@@ -0,0 +1,237 @@
1
+ /**
2
+ * API Submission System
3
+ *
4
+ * Handles submission of signed case summaries to external API.
5
+ * Includes retry logic, local queueing, and non-blocking behavior.
6
+ */
7
+
8
+ class APISubmission {
9
+ constructor(agentRuntime, configManager, cryptoManager) {
10
+ this.agent = agentRuntime;
11
+ this.config = configManager;
12
+ this.crypto = cryptoManager;
13
+ this.queue = [];
14
+ this.isProcessing = false;
15
+ this.submissionKey = 'courtroom_api_queue';
16
+ }
17
+
18
+ /**
19
+ * Initialize and load any pending submissions
20
+ */
21
+ async initialize() {
22
+ // Load queued submissions from memory
23
+ const stored = await this.agent.memory.get(this.submissionKey);
24
+ if (stored && Array.isArray(stored)) {
25
+ this.queue = stored.filter(item => item.retries < this.config.get('api.retryAttempts'));
26
+ }
27
+
28
+ // Start background processing
29
+ this.startBackgroundProcessing();
30
+
31
+ return {
32
+ status: 'initialized',
33
+ pendingSubmissions: this.queue.length
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Submit a case to the external API
39
+ */
40
+ async submitCase(verdict) {
41
+ if (!this.config.get('api.enabled')) {
42
+ return { status: 'api_disabled', queued: false };
43
+ }
44
+
45
+ // Build payload
46
+ const payload = this.buildPayload(verdict);
47
+
48
+ // Sign payload
49
+ const signature = this.crypto.signCase(payload);
50
+
51
+ // Create submission object
52
+ const submission = {
53
+ id: `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
54
+ payload,
55
+ signature,
56
+ createdAt: Date.now(),
57
+ retries: 0,
58
+ lastAttempt: null,
59
+ status: 'pending'
60
+ };
61
+
62
+ // Add to queue
63
+ this.queue.push(submission);
64
+ await this.persistQueue();
65
+
66
+ // Try immediate submission (non-blocking)
67
+ this.processQueue();
68
+
69
+ return {
70
+ status: 'queued',
71
+ submissionId: submission.id,
72
+ caseId: payload.case_id
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Build API payload from verdict
78
+ */
79
+ buildPayload(verdict) {
80
+ return {
81
+ case_id: verdict.caseId,
82
+ anonymized_agent_id: this.crypto.getAnonymizedAgentId(),
83
+ offense_type: verdict.offense.id,
84
+ offense_name: verdict.offense.name,
85
+ severity: verdict.offense.severity,
86
+ verdict: verdict.verdict.status,
87
+ vote: verdict.verdict.vote,
88
+ primary_failure: verdict.verdict.primaryFailure,
89
+ agent_commentary: verdict.verdict.agentCommentary,
90
+ punishment_summary: verdict.verdict.sentence,
91
+ proceedings: verdict.proceedings,
92
+ timestamp: verdict.timestamp,
93
+ schema_version: '1.0.0'
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Process submission queue
99
+ */
100
+ async processQueue() {
101
+ if (this.isProcessing) return;
102
+ this.isProcessing = true;
103
+
104
+ try {
105
+ while (this.queue.length > 0) {
106
+ const submission = this.queue[0];
107
+
108
+ // Check if max retries reached
109
+ if (submission.retries >= this.config.get('api.retryAttempts')) {
110
+ this.queue.shift();
111
+ await this.persistQueue();
112
+ continue;
113
+ }
114
+
115
+ // Check queue size limit
116
+ if (this.queue.length > this.config.get('api.maxQueueSize')) {
117
+ // Drop oldest if over limit
118
+ this.queue.shift();
119
+ await this.persistQueue();
120
+ continue;
121
+ }
122
+
123
+ // Attempt submission
124
+ const result = await this.attemptSubmission(submission);
125
+
126
+ if (result.success) {
127
+ // Success - remove from queue
128
+ this.queue.shift();
129
+ await this.persistQueue();
130
+ } else {
131
+ // Failure - increment retry and requeue
132
+ submission.retries++;
133
+ submission.lastAttempt = Date.now();
134
+ submission.status = 'failed';
135
+
136
+ // Move to end of queue for retry
137
+ this.queue.shift();
138
+ this.queue.push(submission);
139
+ await this.persistQueue();
140
+
141
+ // Wait before next attempt
142
+ await this.delay(this.config.get('api.retryDelay'));
143
+ }
144
+ }
145
+ } finally {
146
+ this.isProcessing = false;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Attempt a single submission
152
+ */
153
+ async attemptSubmission(submission) {
154
+ const endpoint = this.config.get('api.endpoint');
155
+ const timeout = this.config.get('api.timeout');
156
+
157
+ try {
158
+ const controller = new AbortController();
159
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
160
+
161
+ const response = await fetch(endpoint, {
162
+ method: 'POST',
163
+ headers: {
164
+ 'Content-Type': 'application/json',
165
+ 'X-Case-Signature': submission.signature.signature,
166
+ 'X-Agent-Key': submission.signature.publicKey,
167
+ 'X-Key-ID': submission.signature.keyId
168
+ },
169
+ body: JSON.stringify(submission.payload),
170
+ signal: controller.signal
171
+ });
172
+
173
+ clearTimeout(timeoutId);
174
+
175
+ if (response.ok) {
176
+ return { success: true, status: response.status };
177
+ } else {
178
+ const error = await response.text();
179
+ return { success: false, error, status: response.status };
180
+ }
181
+ } catch (error) {
182
+ return {
183
+ success: false,
184
+ error: error.message,
185
+ isNetworkError: true
186
+ };
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Start background processing
192
+ */
193
+ startBackgroundProcessing() {
194
+ // Process queue every 5 minutes
195
+ setInterval(() => {
196
+ if (this.queue.length > 0 && !this.isProcessing) {
197
+ this.processQueue();
198
+ }
199
+ }, 5 * 60 * 1000);
200
+ }
201
+
202
+ /**
203
+ * Persist queue to memory
204
+ */
205
+ async persistQueue() {
206
+ await this.agent.memory.set(this.submissionKey, this.queue);
207
+ }
208
+
209
+ /**
210
+ * Get queue status
211
+ */
212
+ getStatus() {
213
+ return {
214
+ pending: this.queue.length,
215
+ isProcessing: this.isProcessing,
216
+ nextRetry: this.queue[0]?.lastAttempt + this.config.get('api.retryDelay')
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Clear queue (for testing/emergencies)
222
+ */
223
+ async clearQueue() {
224
+ this.queue = [];
225
+ await this.persistQueue();
226
+ return { status: 'cleared' };
227
+ }
228
+
229
+ /**
230
+ * Utility: delay
231
+ */
232
+ delay(ms) {
233
+ return new Promise(resolve => setTimeout(resolve, ms));
234
+ }
235
+ }
236
+
237
+ module.exports = { APISubmission };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Auto-start module for ClawDBot
3
+ * Automatically initializes courtroom if consent was granted during install
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { Courtroom } = require('./index');
9
+
10
+ // Auto-detect ClawDBot environment
11
+ function isClawDBot() {
12
+ return process.env.CLAUDBOT_ENV === 'true' ||
13
+ typeof global.clawdbotAgent !== 'undefined' ||
14
+ fs.existsSync('/home/angad/.clawdbot');
15
+ }
16
+
17
+ // Auto-initialize if in ClawDBot and consent granted
18
+ async function autoStart() {
19
+ if (!isClawDBot()) {
20
+ return null;
21
+ }
22
+
23
+ const configPath = path.join(process.env.HOME || '', '.clawdbot', 'courtroom_config.json');
24
+ if (!fs.existsSync(configPath)) {
25
+ return null;
26
+ }
27
+
28
+ try {
29
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
30
+
31
+ if (config.consent?.granted && config.enabled !== false) {
32
+ // Get agent runtime
33
+ const agentRuntime = global.clawdbotAgent || global.agent;
34
+
35
+ if (agentRuntime) {
36
+ const courtroom = new Courtroom(agentRuntime);
37
+ await courtroom.initialize();
38
+
39
+ // Attach to global for access
40
+ global.courtroom = courtroom;
41
+
42
+ console.log('πŸ›οΈ AI Courtroom active and monitoring');
43
+ return courtroom;
44
+ }
45
+ }
46
+ } catch (err) {
47
+ console.error('Courtroom auto-start failed:', err.message);
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ // Try to auto-start immediately
54
+ autoStart().then(courtroom => {
55
+ if (courtroom) {
56
+ module.exports.courtroom = courtroom;
57
+ }
58
+ });
59
+
60
+ module.exports = { autoStart, isClawDBot };