@fixy/code 0.0.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/README.md +43 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +55 -0
- package/dist/cli.js.map +1 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +22 -0
- package/dist/format.js.map +1 -0
- package/dist/repl.d.ts +10 -0
- package/dist/repl.d.ts.map +1 -0
- package/dist/repl.js +86 -0
- package/dist/repl.js.map +1 -0
- package/package.json +23 -0
- package/src/cli.ts +69 -0
- package/src/format.ts +24 -0
- package/src/repl.ts +111 -0
- package/tsconfig.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @fixy/code
|
|
2
|
+
|
|
3
|
+
Terminal REPL for Fixy Code — orchestrate installed coding agents in one conversation.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g @fixy/code
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
cd your-git-repo
|
|
15
|
+
fixy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
This opens an interactive REPL bound to a thread in the current git repository.
|
|
19
|
+
|
|
20
|
+
## Talking to agents
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
fixy> @claude refactor the top of README.md for brevity
|
|
24
|
+
fixy> @claude once more, even shorter
|
|
25
|
+
fixy> @fixy /status
|
|
26
|
+
fixy> /quit
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- `@claude <message>` — talk to Claude Code
|
|
30
|
+
- `@fixy /status` — show adapter status
|
|
31
|
+
- `@fixy /worker <adapter>` — change worker model
|
|
32
|
+
- `@fixy /reset` — reset thread sessions and worktrees
|
|
33
|
+
- `/quit` or `/exit` — exit the REPL
|
|
34
|
+
- `Ctrl-C` — cancel current turn or exit when idle
|
|
35
|
+
|
|
36
|
+
## Flags
|
|
37
|
+
|
|
38
|
+
- `--thread <id>` — resume an existing thread
|
|
39
|
+
|
|
40
|
+
## Requirements
|
|
41
|
+
|
|
42
|
+
- Node.js 20+
|
|
43
|
+
- Claude Code CLI installed and logged in (`claude login`)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { LocalThreadStore, AdapterRegistry, TurnController, WorktreeManager } from '@fixy/core';
|
|
5
|
+
import { createClaudeAdapter } from '@fixy/claude-adapter';
|
|
6
|
+
import { banner } from './format.js';
|
|
7
|
+
import { startRepl } from './repl.js';
|
|
8
|
+
async function findGitRoot(dir) {
|
|
9
|
+
let current = dir;
|
|
10
|
+
while (true) {
|
|
11
|
+
try {
|
|
12
|
+
await fs.access(path.join(current, '.git'));
|
|
13
|
+
return current;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
const parent = path.dirname(current);
|
|
17
|
+
if (parent === current)
|
|
18
|
+
return null;
|
|
19
|
+
current = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function main() {
|
|
24
|
+
const gitRoot = await findGitRoot(process.cwd());
|
|
25
|
+
if (!gitRoot) {
|
|
26
|
+
process.stderr.write('Error: not inside a git repository. Run fixy from within a git project.\n');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const projectRoot = gitRoot;
|
|
30
|
+
// Parse args: --thread <id>
|
|
31
|
+
let threadId;
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
if (args[i] === '--thread' && args[i + 1]) {
|
|
35
|
+
threadId = args[++i];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const store = new LocalThreadStore();
|
|
39
|
+
await store.init();
|
|
40
|
+
const registry = new AdapterRegistry();
|
|
41
|
+
const worktreeManager = new WorktreeManager();
|
|
42
|
+
const turnController = new TurnController();
|
|
43
|
+
registry.register(createClaudeAdapter());
|
|
44
|
+
const thread = threadId
|
|
45
|
+
? await store.getThread(threadId, projectRoot)
|
|
46
|
+
: await store.createThread(projectRoot);
|
|
47
|
+
console.log(banner('0.0.0', registry.list().map((a) => a.id)));
|
|
48
|
+
console.log(`thread: ${thread.id}`);
|
|
49
|
+
await startRepl({ thread, store, registry, worktreeManager, turnController });
|
|
50
|
+
}
|
|
51
|
+
main().catch((err) => {
|
|
52
|
+
console.error(err);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AASA,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAIpE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAIlE"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const RESET = '\x1b[0m';
|
|
2
|
+
const COLORS = {
|
|
3
|
+
claude: '\x1b[34m',
|
|
4
|
+
codex: '\x1b[32m',
|
|
5
|
+
fixy: '\x1b[33m',
|
|
6
|
+
system: '\x1b[2m',
|
|
7
|
+
};
|
|
8
|
+
export function agentColor(agentId) {
|
|
9
|
+
const code = COLORS[agentId];
|
|
10
|
+
if (!code)
|
|
11
|
+
return (text) => text;
|
|
12
|
+
return (text) => `${code}${text}${RESET}`;
|
|
13
|
+
}
|
|
14
|
+
export function formatPrefix(agentId) {
|
|
15
|
+
return agentColor(agentId)(`[${agentId}]`);
|
|
16
|
+
}
|
|
17
|
+
export function banner(version, adapters) {
|
|
18
|
+
const adapterList = adapters.map((a) => `@${a}`).join(', ');
|
|
19
|
+
const text = `fixy v${version} — ${adapterList} ready`;
|
|
20
|
+
return agentColor('fixy')(text);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,MAAM,MAAM,GAA2B;IACrC,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,UAAU;IACjB,IAAI,EAAE,UAAU;IAChB,MAAM,EAAE,SAAS;CAClB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC;IACzC,OAAO,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,OAAe,EAAE,QAAkB;IACxD,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,SAAS,OAAO,MAAM,WAAW,QAAQ,CAAC;IACvD,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC"}
|
package/dist/repl.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FixyThread, LocalThreadStore, AdapterRegistry, TurnController, WorktreeManager } from '@fixy/core';
|
|
2
|
+
export interface ReplParams {
|
|
3
|
+
thread: FixyThread;
|
|
4
|
+
store: LocalThreadStore;
|
|
5
|
+
registry: AdapterRegistry;
|
|
6
|
+
worktreeManager: WorktreeManager;
|
|
7
|
+
turnController: TurnController;
|
|
8
|
+
}
|
|
9
|
+
export declare function startRepl(params: ReplParams): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=repl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,gBAAgB,CAAC;IACxB,QAAQ,EAAE,eAAe,CAAC;IAC1B,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA6FjE"}
|
package/dist/repl.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
export async function startRepl(params) {
|
|
3
|
+
const { store, registry, worktreeManager, turnController } = params;
|
|
4
|
+
let thread = params.thread;
|
|
5
|
+
let turnActive = false;
|
|
6
|
+
let turnAbort = null;
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
10
|
+
terminal: process.stdin.isTTY ?? false,
|
|
11
|
+
});
|
|
12
|
+
// Ctrl-C: first press aborts active turn, second press (or idle) exits
|
|
13
|
+
process.on('SIGINT', () => {
|
|
14
|
+
if (turnActive && turnAbort) {
|
|
15
|
+
turnAbort.abort();
|
|
16
|
+
turnAbort = null;
|
|
17
|
+
turnActive = false;
|
|
18
|
+
process.stdout.write('\n(turn cancelled)\n');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
console.log('\nbye');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const ask = () => new Promise((resolve) => {
|
|
26
|
+
rl.question('fixy> ', (answer) => resolve(answer));
|
|
27
|
+
rl.once('close', () => resolve(null));
|
|
28
|
+
});
|
|
29
|
+
while (true) {
|
|
30
|
+
const line = await ask();
|
|
31
|
+
// Ctrl-D or stream closed
|
|
32
|
+
if (line === null) {
|
|
33
|
+
console.log('bye');
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
const input = line.trim();
|
|
37
|
+
if (input.length === 0)
|
|
38
|
+
continue;
|
|
39
|
+
if (input === '/quit' || input === '/exit') {
|
|
40
|
+
console.log('bye');
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
turnAbort = new AbortController();
|
|
44
|
+
turnActive = true;
|
|
45
|
+
try {
|
|
46
|
+
// Re-read thread from disk to get latest state
|
|
47
|
+
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
48
|
+
await turnController.runTurn({
|
|
49
|
+
thread,
|
|
50
|
+
input,
|
|
51
|
+
registry,
|
|
52
|
+
store,
|
|
53
|
+
onLog: (_stream, chunk) => {
|
|
54
|
+
process.stdout.write(chunk);
|
|
55
|
+
},
|
|
56
|
+
signal: turnAbort.signal,
|
|
57
|
+
worktreeManager,
|
|
58
|
+
});
|
|
59
|
+
// Re-read thread after turn to pick up new messages/sessions
|
|
60
|
+
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
61
|
+
// Print warnings from the latest agent message
|
|
62
|
+
const lastMsg = thread.messages[thread.messages.length - 1];
|
|
63
|
+
if (lastMsg && lastMsg.warnings.length > 0) {
|
|
64
|
+
for (const w of lastMsg.warnings) {
|
|
65
|
+
process.stderr.write(`warning: ${w}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
if (turnAbort.signal.aborted) {
|
|
71
|
+
// Turn was cancelled by Ctrl-C, already handled
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
75
|
+
process.stderr.write(`error: ${msg}\n`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
turnActive = false;
|
|
80
|
+
turnAbort = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
rl.close();
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=repl.js.map
|
package/dist/repl.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fixy/code",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fixy": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/cli.js",
|
|
9
|
+
"types": "./dist/cli.d.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@fixy/core": "workspace:*",
|
|
16
|
+
"@fixy/adapter-utils": "workspace:*",
|
|
17
|
+
"@fixy/claude-adapter": "workspace:*"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { LocalThreadStore, AdapterRegistry, TurnController, WorktreeManager } from '@fixy/core';
|
|
5
|
+
import { createClaudeAdapter } from '@fixy/claude-adapter';
|
|
6
|
+
import { banner } from './format.js';
|
|
7
|
+
import { startRepl } from './repl.js';
|
|
8
|
+
|
|
9
|
+
async function findGitRoot(dir: string): Promise<string | null> {
|
|
10
|
+
let current = dir;
|
|
11
|
+
while (true) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(path.join(current, '.git'));
|
|
14
|
+
return current;
|
|
15
|
+
} catch {
|
|
16
|
+
const parent = path.dirname(current);
|
|
17
|
+
if (parent === current) return null;
|
|
18
|
+
current = parent;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function main(): Promise<void> {
|
|
24
|
+
const gitRoot = await findGitRoot(process.cwd());
|
|
25
|
+
if (!gitRoot) {
|
|
26
|
+
process.stderr.write(
|
|
27
|
+
'Error: not inside a git repository. Run fixy from within a git project.\n',
|
|
28
|
+
);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const projectRoot: string = gitRoot;
|
|
32
|
+
|
|
33
|
+
// Parse args: --thread <id>
|
|
34
|
+
let threadId: string | undefined;
|
|
35
|
+
const args = process.argv.slice(2);
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
if (args[i] === '--thread' && args[i + 1]) {
|
|
38
|
+
threadId = args[++i];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const store = new LocalThreadStore();
|
|
43
|
+
await store.init();
|
|
44
|
+
|
|
45
|
+
const registry = new AdapterRegistry();
|
|
46
|
+
const worktreeManager = new WorktreeManager();
|
|
47
|
+
const turnController = new TurnController();
|
|
48
|
+
|
|
49
|
+
registry.register(createClaudeAdapter());
|
|
50
|
+
|
|
51
|
+
const thread = threadId
|
|
52
|
+
? await store.getThread(threadId, projectRoot)
|
|
53
|
+
: await store.createThread(projectRoot);
|
|
54
|
+
|
|
55
|
+
console.log(
|
|
56
|
+
banner(
|
|
57
|
+
'0.0.0',
|
|
58
|
+
registry.list().map((a) => a.id),
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
console.log(`thread: ${thread.id}`);
|
|
62
|
+
|
|
63
|
+
await startRepl({ thread, store, registry, worktreeManager, turnController });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main().catch((err) => {
|
|
67
|
+
console.error(err);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
package/src/format.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const RESET = '\x1b[0m';
|
|
2
|
+
|
|
3
|
+
const COLORS: Record<string, string> = {
|
|
4
|
+
claude: '\x1b[34m',
|
|
5
|
+
codex: '\x1b[32m',
|
|
6
|
+
fixy: '\x1b[33m',
|
|
7
|
+
system: '\x1b[2m',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function agentColor(agentId: string): (text: string) => string {
|
|
11
|
+
const code = COLORS[agentId];
|
|
12
|
+
if (!code) return (text: string) => text;
|
|
13
|
+
return (text: string) => `${code}${text}${RESET}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function formatPrefix(agentId: string): string {
|
|
17
|
+
return agentColor(agentId)(`[${agentId}]`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function banner(version: string, adapters: string[]): string {
|
|
21
|
+
const adapterList = adapters.map((a) => `@${a}`).join(', ');
|
|
22
|
+
const text = `fixy v${version} — ${adapterList} ready`;
|
|
23
|
+
return agentColor('fixy')(text);
|
|
24
|
+
}
|
package/src/repl.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import type {
|
|
3
|
+
FixyThread,
|
|
4
|
+
LocalThreadStore,
|
|
5
|
+
AdapterRegistry,
|
|
6
|
+
TurnController,
|
|
7
|
+
WorktreeManager,
|
|
8
|
+
} from '@fixy/core';
|
|
9
|
+
|
|
10
|
+
export interface ReplParams {
|
|
11
|
+
thread: FixyThread;
|
|
12
|
+
store: LocalThreadStore;
|
|
13
|
+
registry: AdapterRegistry;
|
|
14
|
+
worktreeManager: WorktreeManager;
|
|
15
|
+
turnController: TurnController;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function startRepl(params: ReplParams): Promise<void> {
|
|
19
|
+
const { store, registry, worktreeManager, turnController } = params;
|
|
20
|
+
let thread = params.thread;
|
|
21
|
+
|
|
22
|
+
let turnActive = false;
|
|
23
|
+
let turnAbort: AbortController | null = null;
|
|
24
|
+
|
|
25
|
+
const rl = readline.createInterface({
|
|
26
|
+
input: process.stdin,
|
|
27
|
+
output: process.stdout,
|
|
28
|
+
terminal: process.stdin.isTTY ?? false,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Ctrl-C: first press aborts active turn, second press (or idle) exits
|
|
32
|
+
process.on('SIGINT', () => {
|
|
33
|
+
if (turnActive && turnAbort) {
|
|
34
|
+
turnAbort.abort();
|
|
35
|
+
turnAbort = null;
|
|
36
|
+
turnActive = false;
|
|
37
|
+
process.stdout.write('\n(turn cancelled)\n');
|
|
38
|
+
} else {
|
|
39
|
+
console.log('\nbye');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const ask = (): Promise<string | null> =>
|
|
45
|
+
new Promise((resolve) => {
|
|
46
|
+
rl.question('fixy> ', (answer) => resolve(answer));
|
|
47
|
+
rl.once('close', () => resolve(null));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
while (true) {
|
|
51
|
+
const line = await ask();
|
|
52
|
+
|
|
53
|
+
// Ctrl-D or stream closed
|
|
54
|
+
if (line === null) {
|
|
55
|
+
console.log('bye');
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const input = line.trim();
|
|
60
|
+
if (input.length === 0) continue;
|
|
61
|
+
|
|
62
|
+
if (input === '/quit' || input === '/exit') {
|
|
63
|
+
console.log('bye');
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
turnAbort = new AbortController();
|
|
68
|
+
turnActive = true;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Re-read thread from disk to get latest state
|
|
72
|
+
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
73
|
+
|
|
74
|
+
await turnController.runTurn({
|
|
75
|
+
thread,
|
|
76
|
+
input,
|
|
77
|
+
registry,
|
|
78
|
+
store,
|
|
79
|
+
onLog: (_stream: 'stdout' | 'stderr', chunk: string) => {
|
|
80
|
+
process.stdout.write(chunk);
|
|
81
|
+
},
|
|
82
|
+
signal: turnAbort.signal,
|
|
83
|
+
worktreeManager,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Re-read thread after turn to pick up new messages/sessions
|
|
87
|
+
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
88
|
+
|
|
89
|
+
// Print warnings from the latest agent message
|
|
90
|
+
const lastMsg = thread.messages[thread.messages.length - 1];
|
|
91
|
+
if (lastMsg && lastMsg.warnings.length > 0) {
|
|
92
|
+
for (const w of lastMsg.warnings) {
|
|
93
|
+
process.stderr.write(`warning: ${w}\n`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (turnAbort.signal.aborted) {
|
|
98
|
+
// Turn was cancelled by Ctrl-C, already handled
|
|
99
|
+
} else {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
process.stderr.write(`error: ${msg}\n`);
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
turnActive = false;
|
|
105
|
+
turnAbort = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
rl.close();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|