@chlrc/aiw 0.1.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 +681 -0
- package/README.zh-CN.md +681 -0
- package/bin/aiw +8 -0
- package/config/agents.toml +24 -0
- package/config/aiw.toml +41 -0
- package/config/commit-prompt.md +8 -0
- package/config/lazygit-delta.yml +15 -0
- package/package.json +42 -0
- package/scripts/install-global.sh +16 -0
- package/src/agent.mjs +53 -0
- package/src/cli.mjs +422 -0
- package/src/commit.mjs +175 -0
- package/src/config.mjs +190 -0
- package/src/deps.mjs +172 -0
- package/src/git.mjs +210 -0
- package/src/hooks.mjs +252 -0
- package/src/init.mjs +719 -0
- package/src/layout.mjs +54 -0
- package/src/prompt.mjs +60 -0
- package/src/run.mjs +78 -0
- package/src/workspace.mjs +1422 -0
package/src/layout.mjs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { aiwBinPath, resolveAgent } from "./config.mjs";
|
|
3
|
+
import { quoteShell } from "./run.mjs";
|
|
4
|
+
|
|
5
|
+
export function buildLayout(config, agentName) {
|
|
6
|
+
const agent = resolveAgent(config, agentName);
|
|
7
|
+
const aiw = quoteShell(aiwBinPath());
|
|
8
|
+
const agentCommand = [agent.cmd, ...agent.args].map(quoteShell).join(" ");
|
|
9
|
+
return {
|
|
10
|
+
direction: "vertical",
|
|
11
|
+
split: 0.56,
|
|
12
|
+
children: [
|
|
13
|
+
{
|
|
14
|
+
direction: "horizontal",
|
|
15
|
+
split: 0.34,
|
|
16
|
+
children: [
|
|
17
|
+
terminalPane("Files", `${aiw} files`),
|
|
18
|
+
terminalPane(agentTitle(agent.name), agentCommand)
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
direction: "horizontal",
|
|
23
|
+
split: 0.5,
|
|
24
|
+
children: [
|
|
25
|
+
terminalPane("Git", `${aiw} git`),
|
|
26
|
+
terminalPane("Diff", `${aiw} diff --watch`)
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function workspaceName(cwd, agentName) {
|
|
34
|
+
const repo = path.basename(cwd);
|
|
35
|
+
return `AI ${agentName}: ${repo}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function terminalPane(name, command) {
|
|
39
|
+
return {
|
|
40
|
+
pane: {
|
|
41
|
+
surfaces: [
|
|
42
|
+
{
|
|
43
|
+
type: "terminal",
|
|
44
|
+
name,
|
|
45
|
+
command
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function agentTitle(agentName) {
|
|
53
|
+
return agentName.slice(0, 1).toUpperCase() + agentName.slice(1);
|
|
54
|
+
}
|
package/src/prompt.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { commandExists } from "./run.mjs";
|
|
5
|
+
|
|
6
|
+
export async function askInput(label, options = {}) {
|
|
7
|
+
if (!process.stdin.isTTY && !options.allowNonTty) {
|
|
8
|
+
const error = new Error(`${label} is required, but stdin is not interactive`);
|
|
9
|
+
error.exitCode = 4;
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
const rl = readline.createInterface({ input, output });
|
|
13
|
+
try {
|
|
14
|
+
const suffix = options.defaultValue ? ` (${options.defaultValue})` : "";
|
|
15
|
+
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
16
|
+
return answer || options.defaultValue || "";
|
|
17
|
+
} finally {
|
|
18
|
+
rl.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function pickFromList(label, items, options = {}) {
|
|
23
|
+
if (items.length === 1 && !options.force) {
|
|
24
|
+
return items[0];
|
|
25
|
+
}
|
|
26
|
+
const orderedItems = orderDefault(items, options.defaultItem);
|
|
27
|
+
if (process.stdin.isTTY && commandExists("fzf")) {
|
|
28
|
+
const result = spawnSync("fzf", ["--prompt", `${label}> `], {
|
|
29
|
+
input: `${orderedItems.join("\n")}\n`,
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
32
|
+
});
|
|
33
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
34
|
+
return result.stdout.trim();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!process.stdin.isTTY) {
|
|
38
|
+
const error = new Error(`${label} requires an interactive terminal`);
|
|
39
|
+
error.exitCode = 4;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
orderedItems.forEach((item, index) => {
|
|
43
|
+
console.log(`${index + 1}. ${item}`);
|
|
44
|
+
});
|
|
45
|
+
const answer = await askInput(label);
|
|
46
|
+
const index = Number(answer);
|
|
47
|
+
if (!Number.isInteger(index) || index < 1 || index > orderedItems.length) {
|
|
48
|
+
const error = new Error(`invalid selection: ${answer}`);
|
|
49
|
+
error.exitCode = 4;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
return orderedItems[index - 1];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function orderDefault(items, defaultItem) {
|
|
56
|
+
if (!defaultItem || !items.includes(defaultItem)) {
|
|
57
|
+
return items;
|
|
58
|
+
}
|
|
59
|
+
return [defaultItem, ...items.filter((item) => item !== defaultItem)];
|
|
60
|
+
}
|
package/src/run.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export function commandExists(command) {
|
|
4
|
+
const result = spawnSync("sh", ["-lc", `command -v ${quoteShell(command)} >/dev/null 2>&1`], {
|
|
5
|
+
stdio: "ignore"
|
|
6
|
+
});
|
|
7
|
+
return result.status === 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function commandPath(command) {
|
|
11
|
+
const result = spawnSync("sh", ["-lc", `command -v ${quoteShell(command)}`], {
|
|
12
|
+
encoding: "utf8"
|
|
13
|
+
});
|
|
14
|
+
return result.status === 0 ? result.stdout.trim() : "";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function capture(command, args = [], options = {}) {
|
|
18
|
+
const result = spawnSync(command, args, {
|
|
19
|
+
cwd: options.cwd,
|
|
20
|
+
env: options.env,
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
23
|
+
});
|
|
24
|
+
if (result.status !== 0) {
|
|
25
|
+
const error = new Error((result.stderr || result.stdout || `${command} failed`).trim());
|
|
26
|
+
error.exitCode = result.status || 1;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
return result.stdout.trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function tryCapture(command, args = [], options = {}) {
|
|
33
|
+
const result = spawnSync(command, args, {
|
|
34
|
+
cwd: options.cwd,
|
|
35
|
+
env: options.env,
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
input: options.input,
|
|
38
|
+
stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"]
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
ok: result.status === 0,
|
|
42
|
+
status: result.status,
|
|
43
|
+
stdout: result.stdout.trim(),
|
|
44
|
+
stderr: result.stderr.trim()
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function runInherit(command, args = [], options = {}) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const child = spawn(command, args, {
|
|
51
|
+
cwd: options.cwd,
|
|
52
|
+
env: options.env || process.env,
|
|
53
|
+
stdio: "inherit"
|
|
54
|
+
});
|
|
55
|
+
child.on("error", reject);
|
|
56
|
+
child.on("close", (code) => {
|
|
57
|
+
if (code === 0) {
|
|
58
|
+
resolve();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const error = new Error(`${command} exited with code ${code}`);
|
|
62
|
+
error.exitCode = code || 1;
|
|
63
|
+
reject(error);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function quoteShell(value) {
|
|
69
|
+
const text = String(value);
|
|
70
|
+
if (text.length === 0) {
|
|
71
|
+
return "''";
|
|
72
|
+
}
|
|
73
|
+
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function sleep(ms) {
|
|
77
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
|
+
}
|