fireqa-agent 0.1.3 → 0.1.5

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/dist/cli.js CHANGED
@@ -3,6 +3,11 @@ import { Command } from "commander";
3
3
  import { ConfigStore } from "./config/store.js";
4
4
  import { loginWithApiKey } from "./auth/api-key.js";
5
5
  import { CLI_ADAPTERS } from "./runner/adapters.js";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import os from "os";
9
+ import { spawn } from "child_process";
10
+ const PID_FILE = path.join(os.homedir(), ".fireqa", "agent.pid");
6
11
  const program = new Command();
7
12
  const store = new ConfigStore();
8
13
  program
@@ -51,7 +56,71 @@ program
51
56
  .action(async (options) => {
52
57
  const cliType = Object.keys(CLI_ADAPTERS).find(v => v === options.cliType) ?? "claude";
53
58
  store.save({ cliType, cli: CLI_ADAPTERS[cliType].defaultCommand });
59
+ // PID 기록
60
+ const pidDir = path.dirname(PID_FILE);
61
+ if (!fs.existsSync(pidDir))
62
+ fs.mkdirSync(pidDir, { recursive: true });
63
+ fs.writeFileSync(PID_FILE, String(process.pid));
64
+ // 종료 시 PID 파일 삭제
65
+ const cleanupPid = () => { try {
66
+ fs.unlinkSync(PID_FILE);
67
+ }
68
+ catch { } };
69
+ process.on("exit", cleanupPid);
70
+ process.on("SIGINT", () => { cleanupPid(); process.exit(0); });
71
+ process.on("SIGTERM", () => { cleanupPid(); process.exit(0); });
54
72
  const { startAgent } = await import("./runner/task-poller.js");
55
73
  await startAgent(store);
56
74
  });
75
+ program
76
+ .command("stop")
77
+ .description("실행 중인 에이전트 종료")
78
+ .action(() => {
79
+ if (!fs.existsSync(PID_FILE)) {
80
+ console.log("실행 중인 에이전트가 없습니다.");
81
+ return;
82
+ }
83
+ const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
84
+ try {
85
+ process.kill(pid, "SIGTERM");
86
+ fs.unlinkSync(PID_FILE);
87
+ console.log(`에이전트 종료됨 (PID: ${pid})`);
88
+ }
89
+ catch {
90
+ console.log("에이전트가 이미 종료되어 있습니다.");
91
+ try {
92
+ fs.unlinkSync(PID_FILE);
93
+ }
94
+ catch { }
95
+ }
96
+ });
97
+ program
98
+ .command("restart")
99
+ .description("에이전트 재시작 (최신 버전으로)")
100
+ .option("--cli-type <type>", "사용할 LLM CLI 타입 (claude | codex | gemini)")
101
+ .action((options) => {
102
+ // 실행 중인 프로세스 종료
103
+ if (fs.existsSync(PID_FILE)) {
104
+ const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
105
+ try {
106
+ process.kill(pid, "SIGTERM");
107
+ try {
108
+ fs.unlinkSync(PID_FILE);
109
+ }
110
+ catch { }
111
+ console.log(`기존 에이전트 종료됨 (PID: ${pid})`);
112
+ }
113
+ catch { }
114
+ }
115
+ // 새 프로세스로 재시작
116
+ const args = ["fireqa-agent@latest", "start"];
117
+ if (options.cliType)
118
+ args.push("--cli-type", options.cliType);
119
+ const child = spawn("npx", args, { stdio: "inherit", detached: false });
120
+ child.on("error", (err) => {
121
+ console.error(`재시작 실패: ${err.message}`);
122
+ process.exit(1);
123
+ });
124
+ child.on("close", (code) => process.exit(code ?? 0));
125
+ });
57
126
  program.parse();
@@ -14,6 +14,7 @@ export type SpawnResult = {
14
14
  export declare function spawnCli(cliType: CliType, command: string, prompt: string, options?: {
15
15
  sessionId?: string;
16
16
  mcpTools?: string[];
17
+ model?: string;
17
18
  onChunk?: (chunk: ParsedChunk) => void;
18
19
  signal?: AbortSignal;
19
20
  env?: Record<string, string>;
@@ -20,20 +20,21 @@ export const CLI_ADAPTERS = {
20
20
  loginHint: "gemini auth",
21
21
  },
22
22
  };
23
- function buildArgs(cliType, prompt, sessionId) {
23
+ function buildArgs(cliType, prompt, sessionId, model) {
24
24
  switch (cliType) {
25
25
  case "claude":
26
26
  return {
27
27
  args: [
28
- "--print", prompt,
28
+ "--print",
29
29
  "--output-format", "stream-json",
30
30
  "--verbose",
31
+ ...(model ? ["--model", model] : []),
31
32
  ...(sessionId ? ["--resume", sessionId] : []),
32
33
  ],
33
- stdinPrompt: null,
34
+ stdinPrompt: prompt,
34
35
  };
35
36
  case "codex": {
36
- const args = ["exec", "--json"];
37
+ const args = ["exec", "--json", ...(model ? ["--model", model] : [])];
37
38
  if (sessionId)
38
39
  args.push("resume", sessionId, "-");
39
40
  else
@@ -46,6 +47,7 @@ function buildArgs(cliType, prompt, sessionId) {
46
47
  "--output-format", "stream-json",
47
48
  "--approval-mode", "yolo",
48
49
  "--sandbox=none",
50
+ ...(model ? ["--model", model] : []),
49
51
  "--prompt", prompt,
50
52
  ...(sessionId ? ["--resume", sessionId] : []),
51
53
  ],
@@ -54,7 +56,7 @@ function buildArgs(cliType, prompt, sessionId) {
54
56
  }
55
57
  }
56
58
  export async function spawnCli(cliType, command, prompt, options) {
57
- const { args, stdinPrompt } = buildArgs(cliType, prompt, options?.sessionId);
59
+ const { args, stdinPrompt } = buildArgs(cliType, prompt, options?.sessionId, options?.model);
58
60
  return new Promise((resolve, reject) => {
59
61
  const child = spawn(command, args, {
60
62
  stdio: ["pipe", "pipe", "pipe"],
@@ -173,6 +173,7 @@ async function executeTask(config, api, task) {
173
173
  const result = await spawnCli(config.cliType, config.cli, task.prompt, {
174
174
  sessionId: task.sessionId ?? undefined,
175
175
  mcpTools: task.mcpTools,
176
+ model: task.context?.model,
176
177
  onChunk: (chunk) => { chunkBuffer.push(chunk); },
177
178
  signal: controller.signal,
178
179
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fireqa-agent",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "FireQA Agent CLI — connect your AI CLI (Claude Code, Codex, Gemini) to FireQA",
5
5
  "type": "module",
6
6
  "bin": {