@fixy/code 0.0.4 → 0.0.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 +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/format.d.ts +6 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +58 -6
- package/dist/format.js.map +1 -1
- package/dist/repl.d.ts +2 -0
- package/dist/repl.d.ts.map +1 -1
- package/dist/repl.js +11 -13
- package/dist/repl.js.map +1 -1
- package/package.json +5 -5
- package/src/cli.ts +18 -8
- package/src/format.ts +74 -6
- package/src/repl.ts +15 -13
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { LocalThreadStore, AdapterRegistry, TurnController, WorktreeManager } from '@fixy/core';
|
|
5
7
|
import { createClaudeAdapter } from '@fixy/claude-adapter';
|
|
6
8
|
import { createCodexAdapter } from '@fixy/codex-adapter';
|
|
7
|
-
import {
|
|
9
|
+
import { startupPanel } from './format.js';
|
|
8
10
|
import { startRepl } from './repl.js';
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
13
|
+
const version = pkg.version;
|
|
9
14
|
async function findGitRoot(dir) {
|
|
10
15
|
let current = dir;
|
|
11
16
|
while (true) {
|
|
@@ -28,7 +33,6 @@ async function main() {
|
|
|
28
33
|
process.exit(1);
|
|
29
34
|
}
|
|
30
35
|
const projectRoot = gitRoot;
|
|
31
|
-
// Parse args: --thread <id>
|
|
32
36
|
let threadId;
|
|
33
37
|
const args = process.argv.slice(2);
|
|
34
38
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -46,9 +50,10 @@ async function main() {
|
|
|
46
50
|
const thread = threadId
|
|
47
51
|
? await store.getThread(threadId, projectRoot)
|
|
48
52
|
: await store.createThread(projectRoot);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if (process.stdout.isTTY)
|
|
54
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
55
|
+
process.stdout.write(startupPanel(version, registry.list().map((a) => a.id), projectRoot, thread.id) + '\n');
|
|
56
|
+
await startRepl({ thread, store, registry, worktreeManager, turnController, version, projectRoot });
|
|
52
57
|
}
|
|
53
58
|
main().catch((err) => {
|
|
54
59
|
console.error(err);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChG,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
export declare function agentColor(agentId: string): (text: string) => string;
|
|
2
2
|
export declare function formatPrefix(agentId: string): string;
|
|
3
|
-
export declare
|
|
3
|
+
export declare const PROMPT = "\u001B[33m\u276F\u001B[0m ";
|
|
4
|
+
export declare function startupPanel(version: string, adapters: string[], projectRoot: string, threadId: string): string;
|
|
5
|
+
export declare function createSpinner(): {
|
|
6
|
+
start(label: string): void;
|
|
7
|
+
stop(): void;
|
|
8
|
+
};
|
|
4
9
|
//# sourceMappingURL=format.d.ts.map
|
package/dist/format.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAeA,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,eAAO,MAAM,MAAM,gCAA6B,CAAC;AAEjD,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,MAAM,CA4BR;AAED,wBAAgB,aAAa,IAAI;IAAE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,IAAI,IAAI,CAAA;CAAE,CA6B5E"}
|
package/dist/format.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
const RESET = '\x1b[0m';
|
|
4
|
+
const DIM = '\x1b[2m';
|
|
5
|
+
const BOLD = '\x1b[1m';
|
|
6
|
+
const FIXY_COLOR = '\x1b[33m';
|
|
2
7
|
const COLORS = {
|
|
3
8
|
claude: '\x1b[34m',
|
|
4
9
|
codex: '\x1b[32m',
|
|
5
|
-
fixy:
|
|
6
|
-
system:
|
|
10
|
+
fixy: FIXY_COLOR,
|
|
11
|
+
system: DIM,
|
|
7
12
|
};
|
|
8
13
|
export function agentColor(agentId) {
|
|
9
14
|
const code = COLORS[agentId];
|
|
@@ -14,9 +19,56 @@ export function agentColor(agentId) {
|
|
|
14
19
|
export function formatPrefix(agentId) {
|
|
15
20
|
return agentColor(agentId)(`[${agentId}]`);
|
|
16
21
|
}
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
22
|
+
export const PROMPT = `${FIXY_COLOR}❯${RESET} `;
|
|
23
|
+
export function startupPanel(version, adapters, projectRoot, threadId) {
|
|
24
|
+
const cols = Math.max(Math.min(process.stdout.columns ?? 80, 80), 52);
|
|
25
|
+
const innerWidth = cols - 2;
|
|
26
|
+
const homeDir = os.homedir();
|
|
27
|
+
const rel = path.relative(homeDir, projectRoot);
|
|
28
|
+
const dirDisplay = rel.startsWith('..') ? projectRoot : `~/${rel}`;
|
|
29
|
+
const contentLines = [
|
|
30
|
+
`${BOLD}${FIXY_COLOR}Fixy v${version}${RESET}`,
|
|
31
|
+
`${DIM}agents: ${adapters.map((a) => `@${a}`).join(' · ')}${RESET}`,
|
|
32
|
+
`${DIM}directory: ${dirDisplay}${RESET}`,
|
|
33
|
+
`${DIM}thread: ${threadId}${RESET}`,
|
|
34
|
+
];
|
|
35
|
+
const visibleLen = (s) => s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
36
|
+
const padLine = (line) => {
|
|
37
|
+
const vl = visibleLen(line);
|
|
38
|
+
const available = innerWidth - 2; // 1 space on each side
|
|
39
|
+
const spaces = Math.max(0, available - vl);
|
|
40
|
+
return `${FIXY_COLOR}│${RESET} ${line}${' '.repeat(spaces)} ${FIXY_COLOR}│${RESET}`;
|
|
41
|
+
};
|
|
42
|
+
const top = `${FIXY_COLOR}╭${'─'.repeat(innerWidth)}╮${RESET}`;
|
|
43
|
+
const bottom = `${FIXY_COLOR}╰${'─'.repeat(innerWidth)}╯${RESET}`;
|
|
44
|
+
return [top, ...contentLines.map(padLine), bottom].join('\n');
|
|
45
|
+
}
|
|
46
|
+
export function createSpinner() {
|
|
47
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
48
|
+
let intervalId = null;
|
|
49
|
+
let frameIndex = 0;
|
|
50
|
+
if (!process.stdout.isTTY) {
|
|
51
|
+
return {
|
|
52
|
+
start: (_label) => { },
|
|
53
|
+
stop: () => { },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
start(label) {
|
|
58
|
+
frameIndex = 0;
|
|
59
|
+
intervalId = setInterval(() => {
|
|
60
|
+
const frame = frames[frameIndex % frames.length] ?? frames[0] ?? '⠋';
|
|
61
|
+
process.stdout.write(`\r${FIXY_COLOR}${frame}${RESET} ${label}`);
|
|
62
|
+
frameIndex++;
|
|
63
|
+
}, 100);
|
|
64
|
+
},
|
|
65
|
+
stop() {
|
|
66
|
+
if (intervalId !== null) {
|
|
67
|
+
clearInterval(intervalId);
|
|
68
|
+
intervalId = null;
|
|
69
|
+
}
|
|
70
|
+
process.stdout.write('\r\x1b[2K');
|
|
71
|
+
},
|
|
72
|
+
};
|
|
21
73
|
}
|
|
22
74
|
//# sourceMappingURL=format.js.map
|
package/dist/format.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/repl.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface ReplParams {
|
|
|
5
5
|
registry: AdapterRegistry;
|
|
6
6
|
worktreeManager: WorktreeManager;
|
|
7
7
|
turnController: TurnController;
|
|
8
|
+
version: string;
|
|
9
|
+
projectRoot: string;
|
|
8
10
|
}
|
|
9
11
|
export declare function startRepl(params: ReplParams): Promise<void>;
|
|
10
12
|
//# sourceMappingURL=repl.d.ts.map
|
package/dist/repl.d.ts.map
CHANGED
|
@@ -1 +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;
|
|
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;AAGpB,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;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAiGjE"}
|
package/dist/repl.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
|
+
import { PROMPT, createSpinner } from './format.js';
|
|
2
3
|
export async function startRepl(params) {
|
|
3
4
|
const { store, registry, worktreeManager, turnController } = params;
|
|
4
5
|
let thread = params.thread;
|
|
@@ -9,42 +10,41 @@ export async function startRepl(params) {
|
|
|
9
10
|
output: process.stdout,
|
|
10
11
|
terminal: process.stdin.isTTY ?? false,
|
|
11
12
|
});
|
|
12
|
-
// Ctrl-C: first press aborts active turn, second press (or idle) exits
|
|
13
13
|
process.on('SIGINT', () => {
|
|
14
14
|
if (turnActive && turnAbort) {
|
|
15
15
|
turnAbort.abort();
|
|
16
16
|
turnAbort = null;
|
|
17
17
|
turnActive = false;
|
|
18
|
-
process.stdout.write('\
|
|
18
|
+
process.stdout.write('\x1b[33m(turn cancelled)\x1b[0m\n');
|
|
19
19
|
}
|
|
20
20
|
else {
|
|
21
|
-
|
|
21
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
22
22
|
process.exit(0);
|
|
23
23
|
}
|
|
24
24
|
});
|
|
25
25
|
const ask = () => new Promise((resolve) => {
|
|
26
|
-
rl.question(
|
|
26
|
+
rl.question(PROMPT, (answer) => resolve(answer));
|
|
27
27
|
rl.once('close', () => resolve(null));
|
|
28
28
|
});
|
|
29
29
|
while (true) {
|
|
30
30
|
const line = await ask();
|
|
31
|
-
// Ctrl-D or stream closed
|
|
32
31
|
if (line === null) {
|
|
33
|
-
|
|
32
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
34
33
|
break;
|
|
35
34
|
}
|
|
36
35
|
const input = line.trim();
|
|
37
36
|
if (input.length === 0)
|
|
38
37
|
continue;
|
|
39
38
|
if (input === '/quit' || input === '/exit') {
|
|
40
|
-
|
|
39
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
41
40
|
break;
|
|
42
41
|
}
|
|
43
42
|
turnAbort = new AbortController();
|
|
44
43
|
turnActive = true;
|
|
44
|
+
const spinner = createSpinner();
|
|
45
45
|
try {
|
|
46
|
-
// Re-read thread from disk to get latest state
|
|
47
46
|
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
47
|
+
spinner.start('thinking...');
|
|
48
48
|
await turnController.runTurn({
|
|
49
49
|
thread,
|
|
50
50
|
input,
|
|
@@ -56,14 +56,11 @@ export async function startRepl(params) {
|
|
|
56
56
|
signal: turnAbort.signal,
|
|
57
57
|
worktreeManager,
|
|
58
58
|
});
|
|
59
|
-
// Re-read thread after turn to pick up new messages/sessions
|
|
60
59
|
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
61
|
-
// Print system messages (from @fixy commands like /status, /worker, /reset)
|
|
62
60
|
const lastMsg = thread.messages[thread.messages.length - 1];
|
|
63
61
|
if (lastMsg && lastMsg.role === 'system') {
|
|
64
62
|
process.stdout.write(`\n${lastMsg.content}\n`);
|
|
65
63
|
}
|
|
66
|
-
// Print warnings from the latest agent message
|
|
67
64
|
if (lastMsg && lastMsg.warnings.length > 0) {
|
|
68
65
|
for (const w of lastMsg.warnings) {
|
|
69
66
|
process.stderr.write(`warning: ${w}\n`);
|
|
@@ -72,14 +69,15 @@ export async function startRepl(params) {
|
|
|
72
69
|
}
|
|
73
70
|
catch (err) {
|
|
74
71
|
if (turnAbort.signal.aborted) {
|
|
75
|
-
//
|
|
72
|
+
// cancelled by Ctrl-C, already handled
|
|
76
73
|
}
|
|
77
74
|
else {
|
|
78
75
|
const msg = err instanceof Error ? err.message : String(err);
|
|
79
|
-
process.stderr.write(
|
|
76
|
+
process.stderr.write(`\x1b[31merror:\x1b[0m ${msg}\n`);
|
|
80
77
|
}
|
|
81
78
|
}
|
|
82
79
|
finally {
|
|
80
|
+
spinner.stop();
|
|
83
81
|
turnActive = false;
|
|
84
82
|
turnAbort = null;
|
|
85
83
|
}
|
package/dist/repl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fixy/code",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fixy": "./dist/cli.js"
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
"main": "./dist/cli.js",
|
|
9
9
|
"types": "./dist/cli.d.ts",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@fixy/
|
|
12
|
-
"@fixy/
|
|
13
|
-
"@fixy/
|
|
14
|
-
"@fixy/
|
|
11
|
+
"@fixy/core": "0.0.5",
|
|
12
|
+
"@fixy/claude-adapter": "0.0.5",
|
|
13
|
+
"@fixy/codex-adapter": "0.0.5",
|
|
14
|
+
"@fixy/adapter-utils": "0.0.5"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^25.6.0"
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { LocalThreadStore, AdapterRegistry, TurnController, WorktreeManager } from '@fixy/core';
|
|
5
7
|
import { createClaudeAdapter } from '@fixy/claude-adapter';
|
|
6
8
|
import { createCodexAdapter } from '@fixy/codex-adapter';
|
|
7
|
-
import {
|
|
9
|
+
import { startupPanel } from './format.js';
|
|
8
10
|
import { startRepl } from './repl.js';
|
|
9
11
|
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pkg = JSON.parse(
|
|
14
|
+
readFileSync(path.join(__dirname, '../package.json'), 'utf8'),
|
|
15
|
+
) as { version: string };
|
|
16
|
+
const version = pkg.version;
|
|
17
|
+
|
|
10
18
|
async function findGitRoot(dir: string): Promise<string | null> {
|
|
11
19
|
let current = dir;
|
|
12
20
|
while (true) {
|
|
@@ -31,7 +39,6 @@ async function main(): Promise<void> {
|
|
|
31
39
|
}
|
|
32
40
|
const projectRoot: string = gitRoot;
|
|
33
41
|
|
|
34
|
-
// Parse args: --thread <id>
|
|
35
42
|
let threadId: string | undefined;
|
|
36
43
|
const args = process.argv.slice(2);
|
|
37
44
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -54,15 +61,18 @@ async function main(): Promise<void> {
|
|
|
54
61
|
? await store.getThread(threadId, projectRoot)
|
|
55
62
|
: await store.createThread(projectRoot);
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
if (process.stdout.isTTY) process.stdout.write('\x1b[2J\x1b[H');
|
|
65
|
+
|
|
66
|
+
process.stdout.write(
|
|
67
|
+
startupPanel(
|
|
68
|
+
version,
|
|
60
69
|
registry.list().map((a) => a.id),
|
|
61
|
-
|
|
70
|
+
projectRoot,
|
|
71
|
+
thread.id,
|
|
72
|
+
) + '\n',
|
|
62
73
|
);
|
|
63
|
-
console.log(`thread: ${thread.id}`);
|
|
64
74
|
|
|
65
|
-
await startRepl({ thread, store, registry, worktreeManager, turnController });
|
|
75
|
+
await startRepl({ thread, store, registry, worktreeManager, turnController, version, projectRoot });
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
main().catch((err) => {
|
package/src/format.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
1
4
|
const RESET = '\x1b[0m';
|
|
5
|
+
const DIM = '\x1b[2m';
|
|
6
|
+
const BOLD = '\x1b[1m';
|
|
7
|
+
const FIXY_COLOR = '\x1b[33m';
|
|
2
8
|
|
|
3
9
|
const COLORS: Record<string, string> = {
|
|
4
10
|
claude: '\x1b[34m',
|
|
5
11
|
codex: '\x1b[32m',
|
|
6
|
-
fixy:
|
|
7
|
-
system:
|
|
12
|
+
fixy: FIXY_COLOR,
|
|
13
|
+
system: DIM,
|
|
8
14
|
};
|
|
9
15
|
|
|
10
16
|
export function agentColor(agentId: string): (text: string) => string {
|
|
@@ -17,8 +23,70 @@ export function formatPrefix(agentId: string): string {
|
|
|
17
23
|
return agentColor(agentId)(`[${agentId}]`);
|
|
18
24
|
}
|
|
19
25
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
export const PROMPT = `${FIXY_COLOR}❯${RESET} `;
|
|
27
|
+
|
|
28
|
+
export function startupPanel(
|
|
29
|
+
version: string,
|
|
30
|
+
adapters: string[],
|
|
31
|
+
projectRoot: string,
|
|
32
|
+
threadId: string,
|
|
33
|
+
): string {
|
|
34
|
+
const cols = Math.max(Math.min(process.stdout.columns ?? 80, 80), 52);
|
|
35
|
+
const innerWidth = cols - 2;
|
|
36
|
+
|
|
37
|
+
const homeDir = os.homedir();
|
|
38
|
+
const rel = path.relative(homeDir, projectRoot);
|
|
39
|
+
const dirDisplay = rel.startsWith('..') ? projectRoot : `~/${rel}`;
|
|
40
|
+
|
|
41
|
+
const contentLines: string[] = [
|
|
42
|
+
`${BOLD}${FIXY_COLOR}Fixy v${version}${RESET}`,
|
|
43
|
+
`${DIM}agents: ${adapters.map((a) => `@${a}`).join(' · ')}${RESET}`,
|
|
44
|
+
`${DIM}directory: ${dirDisplay}${RESET}`,
|
|
45
|
+
`${DIM}thread: ${threadId}${RESET}`,
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const visibleLen = (s: string): number => s.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
49
|
+
|
|
50
|
+
const padLine = (line: string): string => {
|
|
51
|
+
const vl = visibleLen(line);
|
|
52
|
+
const available = innerWidth - 2; // 1 space on each side
|
|
53
|
+
const spaces = Math.max(0, available - vl);
|
|
54
|
+
return `${FIXY_COLOR}│${RESET} ${line}${' '.repeat(spaces)} ${FIXY_COLOR}│${RESET}`;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const top = `${FIXY_COLOR}╭${'─'.repeat(innerWidth)}╮${RESET}`;
|
|
58
|
+
const bottom = `${FIXY_COLOR}╰${'─'.repeat(innerWidth)}╯${RESET}`;
|
|
59
|
+
|
|
60
|
+
return [top, ...contentLines.map(padLine), bottom].join('\n');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createSpinner(): { start(label: string): void; stop(): void } {
|
|
64
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
65
|
+
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
66
|
+
let frameIndex = 0;
|
|
67
|
+
|
|
68
|
+
if (!process.stdout.isTTY) {
|
|
69
|
+
return {
|
|
70
|
+
start: (_label: string) => {},
|
|
71
|
+
stop: () => {},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
start(label: string): void {
|
|
77
|
+
frameIndex = 0;
|
|
78
|
+
intervalId = setInterval(() => {
|
|
79
|
+
const frame = frames[frameIndex % frames.length] ?? frames[0] ?? '⠋';
|
|
80
|
+
process.stdout.write(`\r${FIXY_COLOR}${frame}${RESET} ${label}`);
|
|
81
|
+
frameIndex++;
|
|
82
|
+
}, 100);
|
|
83
|
+
},
|
|
84
|
+
stop(): void {
|
|
85
|
+
if (intervalId !== null) {
|
|
86
|
+
clearInterval(intervalId);
|
|
87
|
+
intervalId = null;
|
|
88
|
+
}
|
|
89
|
+
process.stdout.write('\r\x1b[2K');
|
|
90
|
+
},
|
|
91
|
+
};
|
|
24
92
|
}
|
package/src/repl.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
TurnController,
|
|
7
7
|
WorktreeManager,
|
|
8
8
|
} from '@fixy/core';
|
|
9
|
+
import { PROMPT, createSpinner } from './format.js';
|
|
9
10
|
|
|
10
11
|
export interface ReplParams {
|
|
11
12
|
thread: FixyThread;
|
|
@@ -13,6 +14,8 @@ export interface ReplParams {
|
|
|
13
14
|
registry: AdapterRegistry;
|
|
14
15
|
worktreeManager: WorktreeManager;
|
|
15
16
|
turnController: TurnController;
|
|
17
|
+
version: string;
|
|
18
|
+
projectRoot: string;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
export async function startRepl(params: ReplParams): Promise<void> {
|
|
@@ -28,31 +31,29 @@ export async function startRepl(params: ReplParams): Promise<void> {
|
|
|
28
31
|
terminal: process.stdin.isTTY ?? false,
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
// Ctrl-C: first press aborts active turn, second press (or idle) exits
|
|
32
34
|
process.on('SIGINT', () => {
|
|
33
35
|
if (turnActive && turnAbort) {
|
|
34
36
|
turnAbort.abort();
|
|
35
37
|
turnAbort = null;
|
|
36
38
|
turnActive = false;
|
|
37
|
-
process.stdout.write('\
|
|
39
|
+
process.stdout.write('\x1b[33m(turn cancelled)\x1b[0m\n');
|
|
38
40
|
} else {
|
|
39
|
-
|
|
41
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
40
42
|
process.exit(0);
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
const ask = (): Promise<string | null> =>
|
|
45
47
|
new Promise((resolve) => {
|
|
46
|
-
rl.question(
|
|
48
|
+
rl.question(PROMPT, (answer) => resolve(answer));
|
|
47
49
|
rl.once('close', () => resolve(null));
|
|
48
50
|
});
|
|
49
51
|
|
|
50
52
|
while (true) {
|
|
51
53
|
const line = await ask();
|
|
52
54
|
|
|
53
|
-
// Ctrl-D or stream closed
|
|
54
55
|
if (line === null) {
|
|
55
|
-
|
|
56
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
56
57
|
break;
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -60,17 +61,20 @@ export async function startRepl(params: ReplParams): Promise<void> {
|
|
|
60
61
|
if (input.length === 0) continue;
|
|
61
62
|
|
|
62
63
|
if (input === '/quit' || input === '/exit') {
|
|
63
|
-
|
|
64
|
+
process.stdout.write('\x1b[2mgoodbye\x1b[0m\n');
|
|
64
65
|
break;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
turnAbort = new AbortController();
|
|
68
69
|
turnActive = true;
|
|
69
70
|
|
|
71
|
+
const spinner = createSpinner();
|
|
72
|
+
|
|
70
73
|
try {
|
|
71
|
-
// Re-read thread from disk to get latest state
|
|
72
74
|
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
73
75
|
|
|
76
|
+
spinner.start('thinking...');
|
|
77
|
+
|
|
74
78
|
await turnController.runTurn({
|
|
75
79
|
thread,
|
|
76
80
|
input,
|
|
@@ -83,16 +87,13 @@ export async function startRepl(params: ReplParams): Promise<void> {
|
|
|
83
87
|
worktreeManager,
|
|
84
88
|
});
|
|
85
89
|
|
|
86
|
-
// Re-read thread after turn to pick up new messages/sessions
|
|
87
90
|
thread = await store.getThread(thread.id, thread.projectRoot);
|
|
88
91
|
|
|
89
|
-
// Print system messages (from @fixy commands like /status, /worker, /reset)
|
|
90
92
|
const lastMsg = thread.messages[thread.messages.length - 1];
|
|
91
93
|
if (lastMsg && lastMsg.role === 'system') {
|
|
92
94
|
process.stdout.write(`\n${lastMsg.content}\n`);
|
|
93
95
|
}
|
|
94
96
|
|
|
95
|
-
// Print warnings from the latest agent message
|
|
96
97
|
if (lastMsg && lastMsg.warnings.length > 0) {
|
|
97
98
|
for (const w of lastMsg.warnings) {
|
|
98
99
|
process.stderr.write(`warning: ${w}\n`);
|
|
@@ -100,12 +101,13 @@ export async function startRepl(params: ReplParams): Promise<void> {
|
|
|
100
101
|
}
|
|
101
102
|
} catch (err) {
|
|
102
103
|
if (turnAbort.signal.aborted) {
|
|
103
|
-
//
|
|
104
|
+
// cancelled by Ctrl-C, already handled
|
|
104
105
|
} else {
|
|
105
106
|
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
-
process.stderr.write(
|
|
107
|
+
process.stderr.write(`\x1b[31merror:\x1b[0m ${msg}\n`);
|
|
107
108
|
}
|
|
108
109
|
} finally {
|
|
110
|
+
spinner.stop();
|
|
109
111
|
turnActive = false;
|
|
110
112
|
turnAbort = null;
|
|
111
113
|
}
|