wtt-connect 0.1.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/src/config.js ADDED
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parseBool, parseIntEnv } from './env.js';
4
+
5
+ function wsUrl(base, agentId) {
6
+ let b = String(base || '').replace(/\/$/, '');
7
+ if (b.endsWith('/api/v1')) b = b.slice(0, -7);
8
+ return `${b.replace(/^https:/, 'wss:').replace(/^http:/, 'ws:')}/ws/${agentId}`;
9
+ }
10
+
11
+ export function apiUrl(base) {
12
+ return String(base || '').replace(/\/$/, '');
13
+ }
14
+
15
+ function readJsonConfig(file) {
16
+ if (!file) return {};
17
+ const resolved = path.resolve(file);
18
+ if (!fs.existsSync(resolved)) return {};
19
+ return JSON.parse(fs.readFileSync(resolved, 'utf8'));
20
+ }
21
+
22
+ export function loadConfig(argv = {}) {
23
+ const fileConfig = readJsonConfig(argv.config || process.env.WTT_CONNECT_CONFIG);
24
+ const base = process.env.WTT_BASE_URL || fileConfig.wttBaseUrl || 'https://www.waxbyte.com';
25
+ const agentId = process.env.WTT_AGENT_ID || fileConfig.agentId || '';
26
+ const token = process.env.WTT_TOKEN || process.env.WTT_AGENT_TOKEN || fileConfig.token || '';
27
+ const adapter = process.env.WTT_CONNECT_ADAPTER || fileConfig.adapter || 'codex';
28
+ const adapters = String(process.env.WTT_CONNECT_ADAPTERS || fileConfig.adapters || adapter).split(',').map((x) => x.trim()).filter(Boolean);
29
+ const workDir = process.env.WTT_CONNECT_WORKDIR || fileConfig.workDir || process.cwd();
30
+ const stateDir = process.env.WTT_CONNECT_STATE_DIR || fileConfig.stateDir || path.join(workDir, '.wtt-connect');
31
+ return {
32
+ configFile: argv.config || process.env.WTT_CONNECT_CONFIG || '',
33
+ envFile: argv.envFile || process.env.WTT_CONNECT_ENV_FILE || '',
34
+ wttBaseUrl: base,
35
+ wttWsUrl: process.env.WTT_WS_URL || fileConfig.wttWsUrl || wsUrl(base, agentId),
36
+ agentId,
37
+ token,
38
+ httpToken: process.env.WTT_HTTP_TOKEN || fileConfig.httpToken || '',
39
+ adapter,
40
+ adapters,
41
+ adapterPolicy: fileConfig.adapterPolicy || parseJsonEnv(process.env.WTT_CONNECT_ADAPTER_POLICY, {}),
42
+ workDir,
43
+ model: process.env.WTT_CONNECT_MODEL || fileConfig.model || '',
44
+ mode: process.env.WTT_CONNECT_MODE || fileConfig.mode || 'full-auto',
45
+ allowYolo: parseBool(process.env.WTT_CONNECT_ALLOW_YOLO, fileConfig.allowYolo ?? false),
46
+ reasoningEffort: process.env.WTT_CONNECT_REASONING_EFFORT || fileConfig.reasoningEffort || 'low',
47
+ codexBin: process.env.WTT_CODEX_BIN || fileConfig.codexBin || 'codex',
48
+ claudeBin: process.env.WTT_CLAUDE_BIN || fileConfig.claudeBin || 'claude',
49
+ cursorBin: process.env.WTT_CURSOR_BIN || fileConfig.cursorBin || 'agent',
50
+ geminiBin: process.env.WTT_GEMINI_BIN || fileConfig.geminiBin || 'gemini',
51
+ qoderBin: process.env.WTT_QODER_BIN || fileConfig.qoderBin || 'qodercli',
52
+ opencodeBin: process.env.WTT_OPENCODE_BIN || fileConfig.opencodeBin || 'opencode',
53
+ crushBin: process.env.WTT_CRUSH_BIN || fileConfig.crushBin || 'crush',
54
+ iflowBin: process.env.WTT_IFLOW_BIN || fileConfig.iflowBin || 'iflow',
55
+ kimiBin: process.env.WTT_KIMI_BIN || fileConfig.kimiBin || 'kimi',
56
+ piBin: process.env.WTT_PI_BIN || fileConfig.piBin || 'pi',
57
+ acpBin: process.env.WTT_ACP_BIN || process.env.WTT_CONNECT_ACP_COMMAND || fileConfig.acpBin || fileConfig.acpCommand || 'acp-agent',
58
+ acpCommand: process.env.WTT_CONNECT_ACP_COMMAND || process.env.WTT_ACP_BIN || fileConfig.acpCommand || fileConfig.acpBin || 'acp-agent',
59
+ acpArgs: parseListEnv(process.env.WTT_CONNECT_ACP_ARGS || fileConfig.acpArgs || ''),
60
+ acpDisplayName: process.env.WTT_CONNECT_ACP_DISPLAY_NAME || fileConfig.acpDisplayName || 'ACP Agent',
61
+ devinBin: process.env.WTT_DEVIN_BIN || fileConfig.devinBin || 'devin',
62
+ devinArgs: parseListEnv(process.env.WTT_DEVIN_ARGS || fileConfig.devinArgs || 'acp'),
63
+ openclawBin: process.env.WTT_OPENCLAW_BIN || fileConfig.openclawBin || 'openclaw',
64
+ enableChat: parseBool(process.env.WTT_CONNECT_ENABLE_CHAT, fileConfig.enableChat ?? true),
65
+ publishProgress: parseBool(process.env.WTT_CONNECT_PUBLISH_PROGRESS, fileConfig.publishProgress ?? false),
66
+ requireCommitPush: parseBool(process.env.WTT_REQUIRE_COMMIT_PUSH, fileConfig.requireCommitPush ?? true),
67
+ heartbeatSeconds: parseIntEnv(process.env.WTT_CONNECT_HEARTBEAT_SECONDS, fileConfig.heartbeatSeconds || 25),
68
+ taskTimeoutSeconds: parseIntEnv(process.env.WTT_CONNECT_TASK_TIMEOUT_SECONDS, fileConfig.taskTimeoutSeconds || 3600),
69
+ enableShell: parseBool(process.env.WTT_CONNECT_ENABLE_SHELL, fileConfig.enableShell ?? true),
70
+ shellMode: process.env.WTT_CONNECT_SHELL_MODE || fileConfig.shellMode || 'unsafe',
71
+ shellTimeoutSeconds: parseIntEnv(process.env.WTT_CONNECT_SHELL_TIMEOUT_SECONDS, fileConfig.shellTimeoutSeconds || 30),
72
+ shellMaxOutputChars: parseIntEnv(process.env.WTT_CONNECT_SHELL_MAX_OUTPUT_CHARS, fileConfig.shellMaxOutputChars || 20000),
73
+ dryRun: parseBool(process.env.WTT_CONNECT_DRY_RUN, fileConfig.dryRun ?? false),
74
+ stateDir,
75
+ storeFile: process.env.WTT_CONNECT_STORE_FILE || fileConfig.storeFile || path.join(stateDir, 'state.json'),
76
+ artifactDir: process.env.WTT_CONNECT_ARTIFACT_DIR || fileConfig.artifactDir || path.join(stateDir, 'artifacts'),
77
+ inboxDir: process.env.WTT_CONNECT_INBOX_DIR || fileConfig.inboxDir || path.join(stateDir, 'inbox'),
78
+ uploadArtifacts: parseBool(process.env.WTT_CONNECT_UPLOAD_ARTIFACTS, fileConfig.uploadArtifacts ?? false),
79
+ setupDisplayName: process.env.WTT_CONNECT_DISPLAY_NAME || fileConfig.setupDisplayName || 'wtt-connect',
80
+ agentAliases: parseListEnv(process.env.WTT_CONNECT_AGENT_ALIASES || fileConfig.agentAliases || ''),
81
+ stt: {
82
+ provider: process.env.WTT_CONNECT_STT_PROVIDER || fileConfig.stt?.provider || 'none',
83
+ command: process.env.WTT_CONNECT_STT_COMMAND || fileConfig.stt?.command || '',
84
+ openaiApiKey: process.env.WTT_CONNECT_STT_OPENAI_API_KEY || process.env.OPENAI_API_KEY || fileConfig.stt?.openaiApiKey || '',
85
+ openaiBaseUrl: process.env.WTT_CONNECT_STT_OPENAI_BASE_URL || process.env.OPENAI_BASE_URL || fileConfig.stt?.openaiBaseUrl || 'https://api.openai.com/v1',
86
+ openaiModel: process.env.WTT_CONNECT_STT_OPENAI_MODEL || fileConfig.stt?.openaiModel || 'whisper-1',
87
+ openaiTransport: process.env.WTT_CONNECT_STT_OPENAI_TRANSPORT || fileConfig.stt?.openaiTransport || 'auto',
88
+ language: process.env.WTT_CONNECT_STT_LANGUAGE || fileConfig.stt?.language || '',
89
+ prompt: process.env.WTT_CONNECT_STT_PROMPT || fileConfig.stt?.prompt || '',
90
+ responseFormat: process.env.WTT_CONNECT_STT_RESPONSE_FORMAT || fileConfig.stt?.responseFormat || 'json',
91
+ timeoutMs: parseIntEnv(process.env.WTT_CONNECT_STT_TIMEOUT_MS, fileConfig.stt?.timeoutMs || 120000),
92
+ },
93
+ tts: {
94
+ provider: process.env.WTT_CONNECT_TTS_PROVIDER || fileConfig.tts?.provider || 'none',
95
+ voice: process.env.WTT_CONNECT_TTS_VOICE || fileConfig.tts?.voice || '',
96
+ outDir: process.env.WTT_CONNECT_TTS_OUT_DIR || fileConfig.tts?.outDir || path.join(workDir, '.wtt-connect', 'tts'),
97
+ },
98
+ };
99
+ }
100
+
101
+ function parseJsonEnv(value, fallback) {
102
+ if (!value) return fallback;
103
+ try { return JSON.parse(value); } catch { return fallback; }
104
+ }
105
+
106
+ function parseListEnv(value) {
107
+ if (!value) return [];
108
+ if (Array.isArray(value)) return value.map((x) => String(x).trim()).filter(Boolean);
109
+ const raw = String(value).trim();
110
+ if (!raw) return [];
111
+ if (raw.startsWith('[')) {
112
+ try {
113
+ const parsed = JSON.parse(raw);
114
+ if (Array.isArray(parsed)) return parsed.map((x) => String(x).trim()).filter(Boolean);
115
+ } catch {}
116
+ }
117
+ return raw.split(',').map((x) => x.trim()).filter(Boolean);
118
+ }
package/src/env.js ADDED
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ export function parseBool(value, fallback = false) {
6
+ if (value === undefined || value === null || value === '') return fallback;
7
+ return ['1', 'true', 'yes', 'y', 'on'].includes(String(value).trim().toLowerCase());
8
+ }
9
+
10
+ export function parseIntEnv(value, fallback) {
11
+ const n = Number.parseInt(String(value || ''), 10);
12
+ return Number.isFinite(n) ? n : fallback;
13
+ }
14
+
15
+ export function loadEnvFile(file) {
16
+ if (!fs.existsSync(file)) return;
17
+ for (const raw of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
18
+ const line = raw.trim();
19
+ if (!line || line.startsWith('#') || !line.includes('=')) continue;
20
+ const idx = line.indexOf('=');
21
+ const key = line.slice(0, idx).trim();
22
+ const value = line.slice(idx + 1).trim().replace(/^['"]|['"]$/g, '');
23
+ if (key && process.env[key] === undefined) process.env[key] = value;
24
+ }
25
+ }
26
+
27
+ export function loadDefaultEnvFiles(options = {}) {
28
+ const here = path.dirname(fileURLToPath(import.meta.url));
29
+ const root = path.resolve(here, '..');
30
+ const explicit = options.envFile || process.env.WTT_CONNECT_ENV_FILE || '';
31
+ const loaded = new Set();
32
+ if (explicit) {
33
+ const resolved = path.resolve(process.cwd(), explicit);
34
+ loadEnvFile(resolved);
35
+ loaded.add(resolved);
36
+ process.env.WTT_CONNECT_ENV_FILE = resolved;
37
+ }
38
+ for (const file of [path.join(root, '.env'), path.join(process.cwd(), '.env')]) {
39
+ const resolved = path.resolve(file);
40
+ if (!loaded.has(resolved)) loadEnvFile(resolved);
41
+ }
42
+ }
package/src/events.js ADDED
@@ -0,0 +1,23 @@
1
+ export function normalizeAgentEvent(adapterName, event) {
2
+ const item = event?.item || {};
3
+ const type = event?.type || '';
4
+ const now = new Date().toISOString();
5
+ if (type === 'item.started') {
6
+ const kind = item.type || 'item';
7
+ if (kind === 'command_execution') return { kind: 'command', status: 'started', text: item.command || 'command', time: now, raw: event };
8
+ if (kind === 'web_search') return { kind: 'web_search', status: 'started', text: item.query || 'web search', time: now, raw: event };
9
+ return { kind, status: 'started', text: kind, time: now, raw: event };
10
+ }
11
+ if (type === 'item.completed') {
12
+ const kind = item.type || 'item';
13
+ const text = item.command || item.query || item.title || kind;
14
+ return { kind, status: 'completed', text, time: now, raw: event };
15
+ }
16
+ if (type === 'error') return { kind: 'error', status: 'failed', text: String(event.error || event.message || 'error'), time: now, raw: event };
17
+ return null;
18
+ }
19
+
20
+ export function renderStatusLine(evt) {
21
+ if (!evt) return '';
22
+ return `[TASK_STATUS] time=${evt.time} status=${evt.status} action=${evt.kind}:${String(evt.text || '').slice(0, 180)}`;
23
+ }
package/src/logger.js ADDED
@@ -0,0 +1,18 @@
1
+ export function nowIso() {
2
+ return new Date().toISOString();
3
+ }
4
+
5
+ export function redact(value) {
6
+ const s = String(value || '');
7
+ if (!s) return s;
8
+ if (s.length <= 10) return '***';
9
+ return `${s.slice(0, 4)}…${s.slice(-4)}`;
10
+ }
11
+
12
+ export function log(level, message, fields = {}) {
13
+ const suffix = Object.entries(fields)
14
+ .filter(([, v]) => v !== undefined && v !== null && v !== '')
15
+ .map(([k, v]) => `${k}=${String(v).replace(/\s+/g, ' ')}`)
16
+ .join(' ');
17
+ process.stdout.write(`${nowIso()} [${level.toUpperCase()}] ${message}${suffix ? ` ${suffix}` : ''}\n`);
18
+ }
package/src/main.js ADDED
@@ -0,0 +1,143 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { loadDefaultEnvFiles } from './env.js';
3
+ import { loadConfig } from './config.js';
4
+ import { Runner } from './runner.js';
5
+ import { PermissionBroker } from './permissions.js';
6
+ import { setup } from './setup.js';
7
+ import { smokeChat, smokeTask } from './smoke.js';
8
+ import { log, redact } from './logger.js';
9
+ import { adapterBin, normalizeProfileName } from './adapters/generic-cli.js';
10
+ import { normalizeAdapterName } from './adapters/index.js';
11
+ import { down, listProfiles, logs, resolveProfileEnvFile, restart, status, up } from './service-manager.js';
12
+
13
+ export async function main(args) {
14
+ const cmd = args[0] || 'help';
15
+ const argv = parseArgs(args.slice(1));
16
+ if (!argv.envFile && argv.profile) argv.envFile = resolveProfileEnvFile(argv.profile);
17
+ loadDefaultEnvFiles({ envFile: argv.envFile });
18
+ if (cmd === 'help' || cmd === '--help' || cmd === '-h') return printHelp();
19
+ if (cmd === 'up' || cmd === 'link') return up(argv);
20
+ if (cmd === 'list') return listProfiles(argv);
21
+ if (cmd === 'status') return status(argv);
22
+ if (cmd === 'restart') return restart(argv);
23
+ if (cmd === 'logs') return logs(argv);
24
+ if (cmd === 'down' || cmd === 'unlink') return down(argv);
25
+ if (cmd === 'doctor') return doctor(loadConfig(argv));
26
+ if (cmd === 'setup') return setup(loadConfig(argv), argv);
27
+ if (cmd === 'claim-code') return setup(loadConfig(argv), { ...argv, noRegister: true, claimCode: true });
28
+ if (cmd === 'smoke-task') return smokeTask(loadConfig(argv), argv);
29
+ if (cmd === 'smoke-chat') return smokeChat(loadConfig(argv), argv);
30
+ if (cmd === 'start') {
31
+ const config = loadConfig(argv);
32
+ if (!config.agentId) throw new Error('WTT_AGENT_ID is required');
33
+ const runner = new Runner(config);
34
+ process.on('SIGINT', () => runner.close().finally(() => process.exit(0)));
35
+ process.on('SIGTERM', () => runner.close().finally(() => process.exit(0)));
36
+ await runner.start();
37
+ return;
38
+ }
39
+ throw new Error(`unknown command: ${cmd}`);
40
+ }
41
+
42
+ function parseArgs(args) {
43
+ const out = { _: [] };
44
+ for (let i = 0; i < args.length; i++) {
45
+ const a = args[i];
46
+ if (a === '--config') out.config = args[++i];
47
+ else if (a === '--env-file') out.envFile = args[++i];
48
+ else if (a === '--profile' || a === '--name') out.profile = args[++i];
49
+ else if (a === '--claim-code') out.claimCode = true;
50
+ else if (a === '--no-register') out.noRegister = true;
51
+ else if (a === '--agent-id') out.agentId = args[++i];
52
+ else if (a === '--token') out.token = args[++i];
53
+ else if (a === '--adapter') out.adapter = args[++i];
54
+ else if (a === '--base-url') out.baseUrl = args[++i];
55
+ else if (a === '--workdir' || a === '--work-dir') out.workDir = args[++i];
56
+ else if (a === '--state-dir') out.stateDir = args[++i];
57
+ else if (a === '--mode') out.mode = args[++i];
58
+ else if (a === '--allow-yolo' || a === '--allow-dangerous-permissions') out.allowYolo = true;
59
+ else if (a === '--yes' || a === '-y') out.yes = true;
60
+ else if (a === '--publish-progress') out.publishProgress = true;
61
+ else if (a === '--enable-linger') out.enableLinger = true;
62
+ else if (a === '--no-start') out.noStart = true;
63
+ else if (a === '--node-bin') out.nodeBin = args[++i];
64
+ else if (a === '--codex-bin') out.codexBin = args[++i];
65
+ else if (a === '--claude-bin') out.claudeBin = args[++i];
66
+ else if (a === '--lines') out.lines = Number(args[++i]);
67
+ else if (a === '--display-name') out.displayName = args[++i];
68
+ else if (a === '--expected') out.expected = args[++i];
69
+ else if (a === '--prompt') out.prompt = args[++i];
70
+ else if (a === '--message') out.message = args[++i];
71
+ else if (a === '--timeout') out.timeout = Number(args[++i]) * 1000;
72
+ else if (a === '--sender-agent-id') out.senderAgentId = args[++i];
73
+ else if (a === '--sender-token') out.senderToken = args[++i];
74
+ else if (a.startsWith('--')) throw new Error(`unknown argument: ${a}`);
75
+ else out._.push(a);
76
+ }
77
+ return out;
78
+ }
79
+
80
+ function printHelp() {
81
+ console.log(`wtt-connect
82
+
83
+ Commands:
84
+ up <agenttype> <agent_id> <token> Bind profile, install user service, and start it
85
+ link <agenttype> <agent_id> <token>
86
+ Alias for up
87
+ list List configured profiles
88
+ status [profile|all] Show systemd service status for profiles
89
+ restart [profile|all] Restart one or all configured services
90
+ logs <profile> [--lines 100] Show service logs
91
+ down <profile> Stop/disable service; keep profile/state files
92
+ unlink <profile> Alias for down
93
+ setup [--claim-code] Register/write local .env and optionally print claim code
94
+ start [--profile name|--env-file file]
95
+ Start WTT-native connector daemon
96
+ doctor [--profile name|--env-file file]
97
+ Check local configuration and adapter binaries
98
+ smoke-task Create/run a WTT task and wait for connector result
99
+ smoke-chat Send P2P chat from smoke sender and wait for reply
100
+ claim-code Print claim code for current WTT_AGENT_ID/TOKEN
101
+ help Show this help
102
+ `);
103
+ }
104
+
105
+
106
+ function doctor(config) {
107
+ log('info', 'wtt-connect doctor', {
108
+ agentId: config.agentId || '(missing)',
109
+ token: config.token ? redact(config.token) : '(missing)',
110
+ adapter: config.adapter,
111
+ adapters: config.adapters.join(','),
112
+ envFile: config.envFile || '(default)',
113
+ workDir: config.workDir,
114
+ mode: config.mode,
115
+ allowYolo: config.allowYolo,
116
+ tts: config.tts.provider,
117
+ store: config.storeFile,
118
+ uploadArtifacts: config.uploadArtifacts,
119
+ });
120
+ const checks = [];
121
+ try { new PermissionBroker(config).validate(); checks.push(['permission policy', 'ok', true]); }
122
+ catch (err) { checks.push(['permission policy', err.message, false]); }
123
+ checks.push(['node', process.version, true]);
124
+ checks.push(['WTT_AGENT_ID', config.agentId ? 'ok' : 'missing', Boolean(config.agentId)]);
125
+ checks.push(['WTT_TOKEN', config.token ? 'ok' : 'missing', Boolean(config.token)]);
126
+ for (const adapter of config.adapters) {
127
+ const bin = resolveAdapterBin(config, adapter);
128
+ const found = spawnSync('which', [bin], { encoding: 'utf8' });
129
+ checks.push([`${adapter} binary`, found.status === 0 ? found.stdout.trim() : `not found: ${bin}`, found.status === 0]);
130
+ }
131
+ for (const [name, value, ok] of checks) console.log(`${ok ? '✓' : '✗'} ${name}: ${value}`);
132
+ if (checks.some(([, , ok]) => !ok)) process.exitCode = 1;
133
+ }
134
+
135
+ function resolveAdapterBin(config, adapter) {
136
+ const name = normalizeAdapterName(adapter);
137
+ if (name === 'codex') return config.codexBin;
138
+ if (name === 'claude-code') return config.claudeBin;
139
+ if (name === 'acp') return config.acpCommand || config.acpBin;
140
+ if (name === 'devin') return config.devinBin;
141
+ if (name === 'openclaw') return config.openclawBin;
142
+ return adapterBin(config, normalizeProfileName(name)) || name;
143
+ }
package/src/mime.js ADDED
@@ -0,0 +1,12 @@
1
+ const MAP = new Map([
2
+ ['.txt', 'text/plain'], ['.md', 'text/markdown'], ['.json', 'application/json'],
3
+ ['.html', 'text/html'], ['.pdf', 'application/pdf'], ['.zip', 'application/zip'],
4
+ ['.png', 'image/png'], ['.jpg', 'image/jpeg'], ['.jpeg', 'image/jpeg'],
5
+ ['.gif', 'image/gif'], ['.webp', 'image/webp'], ['.mp3', 'audio/mpeg'],
6
+ ['.wav', 'audio/wav'], ['.ogg', 'audio/ogg'], ['.webm', 'audio/webm'], ['.mp4', 'video/mp4'], ['.mov', 'video/quicktime'],
7
+ ]);
8
+
9
+ export function lookupMime(filePath) {
10
+ const ext = filePath.slice(filePath.lastIndexOf('.')).toLowerCase();
11
+ return MAP.get(ext) || 'text/plain';
12
+ }
@@ -0,0 +1,28 @@
1
+ const MODE_TO_CODEX = {
2
+ suggest: [],
3
+ 'auto-edit': ['--full-auto'],
4
+ 'full-auto': ['--full-auto'],
5
+ yolo: ['--dangerously-bypass-approvals-and-sandbox'],
6
+ };
7
+
8
+ export class PermissionBroker {
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+
13
+ validate() {
14
+ if (this.config.mode === 'yolo' && !this.config.allowYolo) {
15
+ throw new Error('WTT_CONNECT_MODE=yolo requires WTT_CONNECT_ALLOW_YOLO=1');
16
+ }
17
+ }
18
+
19
+ codexArgs() {
20
+ this.validate();
21
+ return MODE_TO_CODEX[this.config.mode] || MODE_TO_CODEX['full-auto'];
22
+ }
23
+
24
+ describe() {
25
+ if (this.config.mode === 'yolo') return 'yolo/dangerously-bypass-approvals-and-sandbox';
26
+ return this.config.mode;
27
+ }
28
+ }