-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathask-user-cli.ts
More file actions
240 lines (200 loc) · 5.95 KB
/
Copy pathask-user-cli.ts
File metadata and controls
240 lines (200 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/usr/bin/env bun
/**
* ask-user-cli.ts
*
* CLI helper for the ask_user tool. Run this in a separate terminal to
* receive questions from opencode and provide responses.
*
* Usage:
* bun run .opencode/tool/ask-user-cli.ts
* # or
* chmod +x .opencode/tool/ask-user-cli.ts && ./.opencode/tool/ask-user-cli.ts
*/
import * as fs from "fs"
import * as path from "path"
import * as os from "os"
import * as readline from "readline"
// IPC Configuration (must match ask_user.ts)
const IPC_DIR = path.join(os.homedir(), ".opencode", "ask_user")
const POLL_INTERVAL_MS = 500
// ANSI color codes for pretty output
const colors = {
reset: "\x1b[0m",
bold: "\x1b[1m",
dim: "\x1b[2m",
cyan: "\x1b[36m",
yellow: "\x1b[33m",
green: "\x1b[32m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
red: "\x1b[31m",
}
// Question interface (must match ask_user.ts)
interface Question {
id: string
question: string
title?: string
sessionID: string
messageID: string
timestamp: number
}
interface Response {
id: string
response: string
responded: boolean
timestamp: number
}
// Ensure IPC directory exists
function ensureIpcDir() {
if (!fs.existsSync(IPC_DIR)) {
fs.mkdirSync(IPC_DIR, { recursive: true })
}
}
// Get all pending question files
function getPendingQuestions(): Question[] {
ensureIpcDir()
const files = fs.readdirSync(IPC_DIR)
.filter(f => f.startsWith("question_") && f.endsWith(".json"))
.sort() // Process in order
const questions: Question[] = []
for (const file of files) {
try {
const filepath = path.join(IPC_DIR, file)
const data = JSON.parse(fs.readFileSync(filepath, "utf-8"))
questions.push(data)
} catch (e) {
// Skip malformed files
}
}
return questions
}
// Write response file
function writeResponse(questionId: string, response: string, responded: boolean) {
const responseData: Response = {
id: questionId,
response,
responded,
timestamp: Date.now(),
}
const responseFile = path.join(IPC_DIR, `response_${questionId}.json`)
fs.writeFileSync(responseFile, JSON.stringify(responseData, null, 2))
}
// Format timestamp
function formatTime(timestamp: number): string {
return new Date(timestamp).toLocaleTimeString()
}
// Print a horizontal line
function printLine(char = "─", width = 60) {
console.log(colors.dim + char.repeat(width) + colors.reset)
}
// Print the header
function printHeader() {
console.clear()
console.log()
console.log(`${colors.bold}${colors.cyan} 🤖 opencode ask_user CLI${colors.reset}`)
console.log(`${colors.dim} Waiting for questions from the agent...${colors.reset}`)
console.log(`${colors.dim} Press Ctrl+C to exit${colors.reset}`)
printLine()
console.log()
}
// Print a question
function printQuestion(question: Question) {
console.log()
printLine("═")
console.log()
if (question.title) {
console.log(`${colors.bold}${colors.yellow} 📋 ${question.title}${colors.reset}`)
console.log()
}
console.log(`${colors.bold}${colors.cyan} ❓ Question:${colors.reset}`)
console.log()
// Print the question with proper indentation
const lines = question.question.split("\n")
for (const line of lines) {
console.log(` ${line}`)
}
console.log()
console.log(`${colors.dim} Session: ${question.sessionID.slice(0, 12)}... | Time: ${formatTime(question.timestamp)}${colors.reset}`)
printLine()
console.log()
}
// Prompt for user input
async function promptUser(question: Question): Promise<void> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
return new Promise((resolve) => {
console.log(`${colors.green} ✏️ Your response (press Enter twice to submit, or type 'cancel' to cancel):${colors.reset}`)
console.log()
let lines: string[] = []
let emptyLineCount = 0
const prompt = () => {
rl.question(" ", (line) => {
if (line === "") {
emptyLineCount++
if (emptyLineCount >= 1 && lines.length > 0) {
// Submit on empty line after content
rl.close()
const response = lines.join("\n").trim()
if (response.toLowerCase() === "cancel") {
console.log()
console.log(`${colors.yellow} ⚠️ Response cancelled${colors.reset}`)
writeResponse(question.id, "", false)
} else {
console.log()
console.log(`${colors.green} ✅ Response sent!${colors.reset}`)
writeResponse(question.id, response, true)
}
console.log()
resolve()
return
}
} else {
emptyLineCount = 0
lines.push(line)
}
prompt()
})
}
prompt()
})
}
// Set of processed question IDs to avoid duplicates
const processedQuestions = new Set<string>()
// Main loop
async function main() {
printHeader()
// Handle Ctrl+C gracefully
process.on("SIGINT", () => {
console.log()
console.log(`${colors.yellow} 👋 Goodbye!${colors.reset}`)
console.log()
process.exit(0)
})
// Main polling loop
while (true) {
const questions = getPendingQuestions()
for (const question of questions) {
// Skip already processed questions
if (processedQuestions.has(question.id)) {
continue
}
// Mark as being processed
processedQuestions.add(question.id)
// Display and prompt
printQuestion(question)
await promptUser(question)
// After responding, show waiting message again
console.log(`${colors.dim} Waiting for more questions...${colors.reset}`)
console.log()
}
// Wait before next poll
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
}
}
// Run
main().catch((error) => {
console.error(`${colors.red}Error: ${error.message}${colors.reset}`)
process.exit(1)
})