@doguyilmaz/konvoy 0.3.0 → 0.3.2
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/package.json +1 -1
- package/src/args.ts +1 -1
- package/src/cli.ts +32 -23
- package/src/commands/repl.ts +58 -4
package/package.json
CHANGED
package/src/args.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface Args {
|
|
|
5
5
|
|
|
6
6
|
// Flags that are switches, so `konvoy rm --yes <slug>` keeps its slug. Their consumers are the
|
|
7
7
|
// `=== true` checks in cli.ts; a value flag (--session, --port) is anything not listed here.
|
|
8
|
-
const SWITCHES = new Set(['all', 'yes', 'global', 'chart', 'help'])
|
|
8
|
+
const SWITCHES = new Set(['all', 'yes', 'global', 'chart', 'help', 'h', 'version', 'v', 'V'])
|
|
9
9
|
|
|
10
10
|
export function parseArgs(argv: string[]): Args {
|
|
11
11
|
const positional: string[] = []
|
package/src/cli.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { cmdRename } from './commands/rename'
|
|
|
22
22
|
import { cmdUsage } from './commands/usage'
|
|
23
23
|
import { cmdDashboard } from './commands/dashboard'
|
|
24
24
|
import { formatCommandList, resolveCommandName, type CommandName } from './commands/table'
|
|
25
|
-
import { runRepl, startSession, type ReplIo } from './commands/repl'
|
|
25
|
+
import { runRepl, startSession, terminalIo, type ReplIo } from './commands/repl'
|
|
26
26
|
import type { AgentId } from './types'
|
|
27
27
|
import pkg from '../package.json'
|
|
28
28
|
|
|
@@ -35,7 +35,7 @@ export const USAGE = `konvoy ${VERSION}
|
|
|
35
35
|
${formatCommandList()}
|
|
36
36
|
|
|
37
37
|
agents: ${agentIds.join(', ')}
|
|
38
|
-
flags: --session <slug
|
|
38
|
+
flags: --session <slug>, --version (-v), --help (-h)
|
|
39
39
|
`
|
|
40
40
|
|
|
41
41
|
interface CommandContext {
|
|
@@ -115,24 +115,28 @@ const handlers: Record<CommandName, Handler> = {
|
|
|
115
115
|
}),
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
lines: console as unknown as AsyncIterable<string>,
|
|
120
|
-
write: (text) => {
|
|
121
|
-
process.stdout.write(text)
|
|
122
|
-
},
|
|
123
|
-
tty: Boolean(process.stdin.isTTY),
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
export async function main(argv: string[], io: ReplIo = stdio()): Promise<number> {
|
|
118
|
+
export async function main(argv: string[], io?: ReplIo): Promise<number> {
|
|
127
119
|
const args = parseArgs(argv)
|
|
128
120
|
const [command, ...rest] = args._
|
|
129
121
|
const cwd = process.cwd()
|
|
130
122
|
const slug = typeof args.flags.session === 'string' ? args.flags.session : undefined
|
|
131
123
|
|
|
132
|
-
if (
|
|
124
|
+
if (args.flags.version === true || args.flags.v === true || args.flags.V === true) {
|
|
125
|
+
console.log(`konvoy ${VERSION}`)
|
|
126
|
+
return 0
|
|
127
|
+
}
|
|
128
|
+
if (command === 'help' || args.flags.help === true || args.flags.h === true) {
|
|
133
129
|
console.log(USAGE)
|
|
134
130
|
return 0
|
|
135
131
|
}
|
|
132
|
+
if (!command) {
|
|
133
|
+
const stray = Object.keys(args.flags).filter((f) => f !== 'session')
|
|
134
|
+
if (stray.length > 0) {
|
|
135
|
+
console.error(`unknown flag ${stray[0]!.length === 1 ? '-' : '--'}${stray[0]}`)
|
|
136
|
+
console.log(USAGE)
|
|
137
|
+
return 2
|
|
138
|
+
}
|
|
139
|
+
}
|
|
136
140
|
|
|
137
141
|
try {
|
|
138
142
|
return command ? await dispatch(command, rest, cwd, slug, args) : await interactive(io, cwd, slug)
|
|
@@ -160,7 +164,7 @@ async function dispatch(command: string, rest: string[], cwd: string, slug: stri
|
|
|
160
164
|
return handlers[name]({ db, cfg, cwd, args, slug }, rest)
|
|
161
165
|
}
|
|
162
166
|
|
|
163
|
-
async function interactive(
|
|
167
|
+
async function interactive(given: ReplIo | undefined, cwd: string, slug: string | undefined): Promise<number> {
|
|
164
168
|
const db = openDb(dbPath())
|
|
165
169
|
const cfg = await loadConfig({ cwd: (slug ? getSessionBySlug(db, slug)?.cwd : undefined) ?? cwd })
|
|
166
170
|
const session = await startSession(db, cfg, cwd, slug)
|
|
@@ -168,16 +172,21 @@ async function interactive(io: ReplIo, cwd: string, slug: string | undefined): P
|
|
|
168
172
|
console.error(slug ? `no konvoy session named "${slug}"` : 'could not create a session here')
|
|
169
173
|
return 2
|
|
170
174
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
const io = given ?? terminalIo()
|
|
176
|
+
try {
|
|
177
|
+
return await runRepl(io, db, cfg, cwd, session, (tokens, current) => {
|
|
178
|
+
const a = parseArgs(tokens)
|
|
179
|
+
const [cmd = '', ...r] = a._
|
|
180
|
+
const name = resolveCommandName(cmd)
|
|
181
|
+
if (!name) {
|
|
182
|
+
console.error(`unknown command "/${cmd}" - /help lists them`)
|
|
183
|
+
return 2
|
|
184
|
+
}
|
|
185
|
+
return handlers[name]({ db, cfg, cwd, args: a, slug: current }, r)
|
|
186
|
+
})
|
|
187
|
+
} finally {
|
|
188
|
+
io.pause()
|
|
189
|
+
}
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
if (import.meta.main) {
|
package/src/commands/repl.ts
CHANGED
|
@@ -12,6 +12,51 @@ export interface ReplIo {
|
|
|
12
12
|
lines: AsyncIterable<string>
|
|
13
13
|
write: (text: string) => void
|
|
14
14
|
tty: boolean
|
|
15
|
+
/** stop reading stdin while a command runs, so a child that inherits the terminal gets every keystroke */
|
|
16
|
+
pause: () => void
|
|
17
|
+
resume: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function terminalIo(): ReplIo {
|
|
21
|
+
const queue: string[] = []
|
|
22
|
+
const decoder = new TextDecoder()
|
|
23
|
+
let buffered = ''
|
|
24
|
+
let ended = false
|
|
25
|
+
let wake: (() => void) | undefined
|
|
26
|
+
const flush = (): void => {
|
|
27
|
+
wake?.()
|
|
28
|
+
wake = undefined
|
|
29
|
+
}
|
|
30
|
+
process.stdin.on('data', (chunk: Uint8Array) => {
|
|
31
|
+
buffered += decoder.decode(chunk, { stream: true })
|
|
32
|
+
let at: number
|
|
33
|
+
while ((at = buffered.indexOf('\n')) >= 0) {
|
|
34
|
+
queue.push(buffered.slice(0, at))
|
|
35
|
+
buffered = buffered.slice(at + 1)
|
|
36
|
+
}
|
|
37
|
+
flush()
|
|
38
|
+
})
|
|
39
|
+
process.stdin.on('end', () => {
|
|
40
|
+
if (buffered) queue.push(buffered)
|
|
41
|
+
ended = true
|
|
42
|
+
flush()
|
|
43
|
+
})
|
|
44
|
+
async function* lines(): AsyncGenerator<string> {
|
|
45
|
+
for (;;) {
|
|
46
|
+
if (queue.length > 0) yield queue.shift()!
|
|
47
|
+
else if (ended) return
|
|
48
|
+
else await new Promise<void>((resolve) => (wake = resolve))
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
lines: lines(),
|
|
53
|
+
write: (text) => {
|
|
54
|
+
process.stdout.write(text)
|
|
55
|
+
},
|
|
56
|
+
tty: Boolean(process.stdin.isTTY),
|
|
57
|
+
pause: () => process.stdin.pause(),
|
|
58
|
+
resume: () => process.stdin.resume(),
|
|
59
|
+
}
|
|
15
60
|
}
|
|
16
61
|
|
|
17
62
|
/** runs one command line through the command table, as `konvoy <tokens>` would, for the given session */
|
|
@@ -52,6 +97,15 @@ export async function runRepl(
|
|
|
52
97
|
return fresh !== undefined
|
|
53
98
|
}
|
|
54
99
|
|
|
100
|
+
const exec = async (tokens: string[]): Promise<void> => {
|
|
101
|
+
io.pause()
|
|
102
|
+
try {
|
|
103
|
+
await run(tokens, session.slug)
|
|
104
|
+
} finally {
|
|
105
|
+
io.resume()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
55
109
|
prompt()
|
|
56
110
|
for await (const raw of io.lines) {
|
|
57
111
|
const line = raw.trim()
|
|
@@ -60,7 +114,7 @@ export async function runRepl(
|
|
|
60
114
|
continue
|
|
61
115
|
}
|
|
62
116
|
if (!line.startsWith('/')) {
|
|
63
|
-
await
|
|
117
|
+
await exec(['send', agent, line])
|
|
64
118
|
// failover never falls back, so the agent that answered is the one to keep talking to
|
|
65
119
|
const moved = lastTurnAgent(db, session.id)
|
|
66
120
|
if (moved && moved !== agent) agent = moved
|
|
@@ -94,13 +148,13 @@ export async function runRepl(
|
|
|
94
148
|
} else if (cmd === 'rename') {
|
|
95
149
|
if (rest.length === 0) console.error('usage: /rename <new-name>')
|
|
96
150
|
else {
|
|
97
|
-
await
|
|
151
|
+
await exec(['rename', session.slug, rest.join(' ')])
|
|
98
152
|
refresh()
|
|
99
153
|
}
|
|
100
154
|
} else if (cmd === 'attach') {
|
|
101
|
-
await
|
|
155
|
+
await exec(['attach', rest[0] ?? agent, ...rest.slice(1)])
|
|
102
156
|
} else {
|
|
103
|
-
await
|
|
157
|
+
await exec([cmd, ...rest])
|
|
104
158
|
if (cmd === 'resume' && rest[0]) {
|
|
105
159
|
const target = getSessionBySlug(db, rest[0])
|
|
106
160
|
if (target) {
|