@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 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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function agentColor(agentId: string): (text: string) => string;
2
+ export declare function formatPrefix(agentId: string): string;
3
+ export declare function banner(version: string, adapters: string[]): string;
4
+ //# sourceMappingURL=format.d.ts.map
@@ -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
@@ -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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src"]
9
+ }