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:
62
scripts/build.ts
Normal file
62
scripts/build.ts
Normal 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
138
scripts/dev-watch.ts
Normal 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
29
scripts/sync-version.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user