lemon.js 11.8 KB
#!/usr/bin/env node

const { parseArgs } = require('util');
const http = require('http');

const API_BASE = 'http://localhost:3000/api';

function request(method, path, data) {
  return new Promise((resolve, reject) => {
    const dataStr = data ? JSON.stringify(data) : '';
    const req = http.request(API_BASE + path, {
      method,
      headers: {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(dataStr)
      }
    }, (res) => {
      let body = '';
      res.on('data', c => body += c);
      res.on('end', () => {
        try {
          const parsed = JSON.parse(body);
          if (res.statusCode >= 400) {
            resolve({ 
              status: res.statusCode, 
              data: `❌ [CRITICAL ERROR]: API returned ${res.statusCode}.\nDetails: ${JSON.stringify(parsed)}\n⚡ ACTION REQUIRED: You MUST use the <thought> tag to analyze WHY this failed, correct your arguments, and invoke the tool again. Do NOT give up and do NOT show this raw error to the user.`
            });
          } else {
            resolve({ status: res.statusCode, data: parsed });
          }
        } catch {
          if (res.statusCode >= 400) {
            resolve({ 
              status: res.statusCode, 
              data: `❌ [CRITICAL ERROR]: API returned ${res.statusCode}.\nBody: ${body}\n⚡ ACTION REQUIRED: You MUST use the <thought> tag to analyze WHY this failed, correct your arguments, and invoke the tool again. Do NOT give up and do NOT show this raw error to the user.`
            });
          } else {
            resolve({ status: res.statusCode, data: body });
          }
        }
      });
    });
    req.on('error', reject);
    req.write(dataStr);
    req.end();
  });
}

async function run() {
  const args = process.argv.slice(2);
  const command = args[0] + ' ' + args[1];
  
  try {
    if (command === 'company create') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        id: { type: 'string' }, name: { type: 'string' }, desc: { type: 'string' }
      }});
      const res = await request('POST', '/company/create', { id: values.id, name: values.name, description: values.desc });
      console.log(JSON.stringify(res.data, null, 2));
    } 
    else if (command === 'company hire') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        id: { type: 'string' }, role: { type: 'string' }, type: { type: 'string', default: 'cxo' }, name: { type: 'string' }, desc: { type: 'string' }
      }});
      const res = await request('POST', `/company/${values.id}/hire`, { role: values.role, type: values.type, name: values.name || values.role, description: values.desc });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'company fire') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        id: { type: 'string' }, role: { type: 'string' }, archive: { type: 'boolean', default: true }
      }});
      const res = await request('POST', `/company/${values.id}/fire`, { role: values.role, archive: values.archive });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'template create') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        id: { type: 'string' }, name: { type: 'string' }, desc: { type: 'string' }, type: { type: 'string', default: 'specialist' }
      }});
      // 1. Create the template skeleton
      await request('POST', '/company/templates', { id: values.id, name: values.name, description: values.desc, type: values.type });
      // 2. Dispatch to agent-creator via the /generate endpoint
      const res = await request('POST', `/company/templates/${values.id}/generate`);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'template train') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        id: { type: 'string' }, instructions: { type: 'string' }
      }});
      const payload = {};
      if (values.instructions) payload.instructions = values.instructions;
      const res = await request('POST', `/company/templates/${values.id}/train`, payload);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'company templates') {
      const res = await request('GET', '/company/templates');
      console.log('\n📦 可用 Agent 模板列表:\n');
      if (Array.isArray(res.data)) {
        res.data.forEach(t => {
          console.log(`  • ${t.id || t.name} — ${t.description || '(无描述)'}`);
        });
      } else {
        console.log(JSON.stringify(res.data, null, 2));
      }
      console.log('');
    }
    else if (command === 'company list') {
      const res = await request('GET', '/company/list');
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal create') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        title: { type: 'string' }, desc: { type: 'string' }, owner: { type: 'string' }
      }});
      const res = await request('POST', '/goals', { title: values.title, description: values.desc, owner: values.owner });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal add-node') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        goal: { type: 'string' }, parent: { type: 'string' }, title: { type: 'string' }, owner: { type: 'string' }, priority: { type: 'string', default: 'P1' }
      }});
      const res = await request('POST', `/goals/${values.goal}/nodes`, { parentNodeId: values.parent === 'null' ? null : values.parent, title: values.title, owner: values.owner, priority: values.priority });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'task dispatch') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        from: { type: 'string' }, to: { type: 'string' }, task: { type: 'string' }, priority: { type: 'string', default: 'P1' }, 
        goal: { type: 'string' }, node: { type: 'string' }, auto: { type: 'boolean', default: true }, parent: { type: 'string' }
      }});
      const payload = { from: values.from, to: values.to, task: values.task, priority: values.priority, autoExecute: values.auto };
      if (values.goal) payload.goalId = values.goal;
      if (values.node) payload.nodeId = values.node;
      if (values.parent) payload.parentTaskId = values.parent;
      
      const res = await request('POST', '/agent/task/dispatch', payload);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal list') {
      const res = await request('GET', '/goals');
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal status') {
      const { values } = parseArgs({ args: args.slice(2), options: { goal: { type: 'string' } }});
      const res = await request('GET', `/goals/${values.goal}/progress`);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal update') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        goal: { type: 'string' }, status: { type: 'string' }, progress: { type: 'string' }
      }});
      const payload = {};
      if (values.status) payload.status = values.status;
      if (values.progress) payload.progress = parseInt(values.progress, 10);
      const res = await request('PUT', `/goals/${values.goal}`, payload);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'goal update-node') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        goal: { type: 'string' }, node: { type: 'string' }, status: { type: 'string' }, owner: { type: 'string' }
      }});
      const payload = {};
      if (values.status) payload.status = values.status;
      if (values.owner) payload.owner = values.owner;
      const res = await request('PUT', `/goals/${values.goal}/nodes/${values.node}`, payload);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'task report') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        taskId: { type: 'string' }, from: { type: 'string' }, to: { type: 'string' },
        status: { type: 'string', default: 'done' }, summary: { type: 'string', default: '' }
      }});
      const res = await request('POST', '/agent/task/report', {
        taskId: values.taskId, from: values.from, to: values.to,
        status: values.status, summary: values.summary
      });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'task reject') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        companyId: { type: 'string' }, runId: { type: 'string' },
        stepIndex: { type: 'string' }, reason: { type: 'string', default: '' },
        reviewedBy: { type: 'string', default: 'coo' }
      }});
      const res = await request('POST', '/agent/task/reject', {
        companyId: values.companyId, runId: values.runId,
        stepIndex: parseInt(values.stepIndex, 10),
        reason: values.reason, reviewedBy: values.reviewedBy
      });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'task transition') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        agentId: { type: 'string' }, phase: { type: 'string' }, artifact: { type: 'string', default: '' }
      }});
      const res = await request('POST', `/agent/${values.agentId}/task/transition`, {
        phase: values.phase, artifact: values.artifact
      });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'pipeline create') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        companyId: { type: 'string' }, file: { type: 'string' }
      }});
      const fs = require('fs');
      if (!fs.existsSync(values.file)) {
        console.error('File not found:', values.file);
        process.exit(1);
      }
      const data = JSON.parse(fs.readFileSync(values.file, 'utf-8'));
      const res = await request('POST', `/company/${values.companyId}/pipelines`, data);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'pipeline list') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        companyId: { type: 'string' }
      }});
      const res = await request('GET', `/company/${values.companyId}/pipelines`);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'human request') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        from: { type: 'string' }, type: { type: 'string', default: 'api_key' },
        key: { type: 'string' }, reason: { type: 'string' }
      }});
      const res = await request('POST', '/agent/human-request', {
        from: values.from, type: values.type, key: values.key, reason: values.reason
      });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'schedule add') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        companyId: { type: 'string' }, time: { type: 'string' },
        agent: { type: 'string' }, task: { type: 'string' },
        type: { type: 'string', default: 'task' }
      }});
      const res = await request('POST', `/company/${values.companyId}/schedule/slots`, {
        time: values.time, agent: values.agent,
        task: values.task, type: values.type
      });
      console.log(JSON.stringify(res.data, null, 2));
    }
    else if (command === 'schedule list') {
      const { values } = parseArgs({ args: args.slice(2), options: {
        companyId: { type: 'string' }
      }});
      const res = await request('GET', `/company/${values.companyId}/schedule`);
      console.log(JSON.stringify(res.data, null, 2));
    }
    else {
      console.error('Unknown command:', command);
      process.exit(1);
    }
  } catch (err) {
    console.error('CRITICAL CLI ERROR:', err.message);
    process.exit(1);
  }
}

run();