Terminal-based AI coding agent with interactive TUI for autonomous code generation.

Features:
  - Interactive TUI with React/Ink
  - Autonomous agent with tool calls (bash, read, write, edit, glob, grep)
  - Permission system with pattern-based rules
  - Session management with auto-compaction
  - Dual providers: GitHub Copilot and Ollama
  - MCP server integration
  - Todo panel and theme system
  - Streaming responses
  - GitHub-compatible project context
This commit is contained in:
2026-01-27 23:33:06 -05:00
commit 0062e5d9d9
521 changed files with 66418 additions and 0 deletions

62
scripts/build.ts Normal file
View File

@@ -0,0 +1,62 @@
#!/usr/bin/env bun
/**
* Build script for codetyper-cli
*
* Uses the @opentui/solid plugin for JSX transformation during bundling.
*/
import solidPlugin from "../node_modules/@opentui/solid/scripts/solid-plugin";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { readFile, writeFile } from "fs/promises";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT_DIR = join(__dirname, "..");
process.chdir(ROOT_DIR);
// Sync version before building
const syncVersion = async (): Promise<void> => {
const packageJson = JSON.parse(
await readFile(join(ROOT_DIR, "package.json"), "utf-8"),
);
const { version } = packageJson;
await writeFile(
join(ROOT_DIR, "src/version.json"),
JSON.stringify({ version }, null, 2) + "\n",
);
console.log(`Synced version: ${version}`);
};
await syncVersion();
// Build the application
console.log("Building codetyper-cli...");
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "node",
conditions: ["node"],
plugins: [solidPlugin],
sourcemap: "external",
});
if (!result.success) {
console.error("Build failed:");
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
// Update shebang to use node
const distPath = join(ROOT_DIR, "dist/index.js");
let distContent = await readFile(distPath, "utf-8");
distContent = distContent.replace(/^#!.*\n/, "#!/usr/bin/env node\n");
await writeFile(distPath, distContent);
console.log("Build completed successfully!");

138
scripts/dev-watch.ts Normal file
View File

@@ -0,0 +1,138 @@
#!/usr/bin/env bun
/**
* Development watch script for codetyper-cli
*
* Watches for file changes and restarts the TUI application properly,
* handling terminal cleanup between restarts.
*/
import { spawn, type Subprocess } from "bun";
import { watch } from "chokidar";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT_DIR = join(__dirname, "..");
const WATCH_PATHS = ["src/**/*.ts", "src/**/*.tsx"];
const IGNORE_PATTERNS = [
"**/node_modules/**",
"**/dist/**",
"**/*.test.ts",
"**/*.spec.ts",
];
const DEBOUNCE_MS = 300;
let currentProcess: Subprocess | null = null;
let restartTimeout: ReturnType<typeof setTimeout> | null = null;
const clearTerminal = (): void => {
// Reset terminal and clear screen
process.stdout.write("\x1b[2J\x1b[H\x1b[3J");
};
const killProcess = async (): Promise<void> => {
if (currentProcess) {
try {
currentProcess.kill("SIGTERM");
// Wait a bit for graceful shutdown
await new Promise((resolve) => setTimeout(resolve, 100));
if (currentProcess.exitCode === null) {
currentProcess.kill("SIGKILL");
}
} catch {
// Process might already be dead
}
currentProcess = null;
}
};
const startProcess = (): void => {
clearTerminal();
console.log("\x1b[36m[dev-watch]\x1b[0m Starting codetyper...");
console.log("\x1b[90m─────────────────────────────────────\x1b[0m\n");
currentProcess = spawn({
cmd: ["bun", "src/index.ts"],
cwd: ROOT_DIR,
stdio: ["inherit", "inherit", "inherit"],
env: {
...process.env,
NODE_ENV: "development",
},
});
currentProcess.exited.then((code) => {
if (code !== 0 && code !== null) {
console.log(
`\n\x1b[33m[dev-watch]\x1b[0m Process exited with code ${code}`,
);
}
});
};
const scheduleRestart = (path: string): void => {
if (restartTimeout) {
clearTimeout(restartTimeout);
}
restartTimeout = setTimeout(async () => {
console.log(`\n\x1b[33m[dev-watch]\x1b[0m Change detected: ${path}`);
console.log("\x1b[33m[dev-watch]\x1b[0m Restarting...\n");
await killProcess();
startProcess();
}, DEBOUNCE_MS);
};
const main = async (): Promise<void> => {
console.log("\x1b[36m[dev-watch]\x1b[0m Watching for changes...");
console.log(`\x1b[90mRoot: ${ROOT_DIR}\x1b[0m`);
console.log(`\x1b[90mPaths: ${WATCH_PATHS.join(", ")}\x1b[0m`);
const watcher = watch(WATCH_PATHS, {
ignored: IGNORE_PATTERNS,
persistent: true,
ignoreInitial: true,
cwd: ROOT_DIR,
usePolling: false,
awaitWriteFinish: {
stabilityThreshold: 100,
pollInterval: 100,
},
});
watcher.on("ready", () => {
console.log("\x1b[32m[dev-watch]\x1b[0m Watcher ready\n");
});
watcher.on("error", (error) => {
console.error("\x1b[31m[dev-watch]\x1b[0m Watcher error:", error);
});
watcher.on("change", scheduleRestart);
watcher.on("add", scheduleRestart);
watcher.on("unlink", scheduleRestart);
// Handle exit signals
const cleanup = async (): Promise<void> => {
console.log("\n\x1b[36m[dev-watch]\x1b[0m Shutting down...");
await watcher.close();
await killProcess();
process.exit(0);
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
// Start the initial process
startProcess();
};
main().catch((err) => {
console.error("Error:", err);
process.exit(1);
});

29
scripts/sync-version.ts Normal file
View File

@@ -0,0 +1,29 @@
import { readFile, writeFile } from "fs/promises";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const ROOT_DIR = join(__dirname, "..");
const PACKAGE_JSON_PATH = join(ROOT_DIR, "package.json");
const VERSION_JSON_PATH = join(ROOT_DIR, "src/version.json");
const syncVersion = async (): Promise<void> => {
const packageJson = JSON.parse(await readFile(PACKAGE_JSON_PATH, "utf-8"));
const { version } = packageJson;
const versionJson = { version };
await writeFile(
VERSION_JSON_PATH,
JSON.stringify(versionJson, null, 2) + "\n",
);
console.log(`Synced version: ${version}`);
};
syncVersion().catch((error) => {
console.error("Failed to sync version:", error);
process.exit(1);
});