@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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[agents.codex]
|
|
2
|
+
cmd = "codex"
|
|
3
|
+
args = []
|
|
4
|
+
commit_args = ["exec", "--skip-git-repo-check", "--sandbox", "read-only", "--color", "never", "-"]
|
|
5
|
+
|
|
6
|
+
[agents.claude]
|
|
7
|
+
cmd = "claude"
|
|
8
|
+
args = []
|
|
9
|
+
commit_args = ["--print"]
|
|
10
|
+
|
|
11
|
+
[agents.opencode]
|
|
12
|
+
cmd = "opencode"
|
|
13
|
+
args = []
|
|
14
|
+
commit_args = ["run", "{{prompt}}"]
|
|
15
|
+
|
|
16
|
+
[agents.gemini]
|
|
17
|
+
cmd = "gemini"
|
|
18
|
+
args = []
|
|
19
|
+
commit_args = ["--prompt", "{{prompt}}"]
|
|
20
|
+
|
|
21
|
+
[agents.aider]
|
|
22
|
+
cmd = "aider"
|
|
23
|
+
args = []
|
|
24
|
+
commit_args = ["--message", "{{prompt}}"]
|
package/config/aiw.toml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[defaults]
|
|
2
|
+
agent = "codex"
|
|
3
|
+
editor = "nvim"
|
|
4
|
+
files = "yazi"
|
|
5
|
+
git = "lazygit"
|
|
6
|
+
diff = "cmux-git-diff"
|
|
7
|
+
diff_fallback = "delta"
|
|
8
|
+
tree_depth = 3
|
|
9
|
+
|
|
10
|
+
[paths]
|
|
11
|
+
code_root = "~/Code"
|
|
12
|
+
worktrees = "~/worktrees"
|
|
13
|
+
core_config = "~/.config/aiw"
|
|
14
|
+
|
|
15
|
+
[behavior]
|
|
16
|
+
require_git_repo = true
|
|
17
|
+
warn_dirty_before_new = true
|
|
18
|
+
open_cmux_after_new = true
|
|
19
|
+
use_worktrunk = true
|
|
20
|
+
remove_worktree_after_merge = true
|
|
21
|
+
|
|
22
|
+
[commit]
|
|
23
|
+
agent = "codex"
|
|
24
|
+
prompt_file = "commit-prompt.md"
|
|
25
|
+
retries = 3
|
|
26
|
+
max_diff_chars = 120000
|
|
27
|
+
|
|
28
|
+
[git]
|
|
29
|
+
lazygit_config = "lazygit-delta.yml"
|
|
30
|
+
|
|
31
|
+
[workspace]
|
|
32
|
+
stale_seconds = 604800
|
|
33
|
+
|
|
34
|
+
[workspace.hooks]
|
|
35
|
+
pre_init = []
|
|
36
|
+
pre_remove = []
|
|
37
|
+
|
|
38
|
+
# Optional global per-project hooks:
|
|
39
|
+
# [workspace.hooks.projects.aiw]
|
|
40
|
+
# pre_init = []
|
|
41
|
+
# pre_remove = []
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Generate a Git commit message for the staged diff.
|
|
2
|
+
|
|
3
|
+
Rules:
|
|
4
|
+
- Output only the commit message. Do not add Markdown fences or explanations.
|
|
5
|
+
- Prefer Conventional Commits when the change clearly fits one type.
|
|
6
|
+
- Keep the first line concise and specific.
|
|
7
|
+
- Use a body only when it clarifies non-obvious context or hook failures.
|
|
8
|
+
- Do not mention generated tooling unless it is part of the change.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
git:
|
|
2
|
+
pagers:
|
|
3
|
+
- pager: delta --dark --paging=never --line-numbers
|
|
4
|
+
colorArg: always
|
|
5
|
+
customCommands:
|
|
6
|
+
- key: '<c-a>'
|
|
7
|
+
context: 'global'
|
|
8
|
+
description: 'AI commit staged changes'
|
|
9
|
+
prompts:
|
|
10
|
+
- type: 'input'
|
|
11
|
+
title: 'Additional AI commit prompt (optional)'
|
|
12
|
+
key: 'AIWCommitPrompt'
|
|
13
|
+
initialValue: ''
|
|
14
|
+
command: 'aiw commit --prompt {{.Form.AIWCommitPrompt | quote}}'
|
|
15
|
+
output: terminal
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chlrc/aiw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Personal AI workflow bridge for cmux, Worktrunk, and CLI agents.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"ai",
|
|
8
|
+
"cli",
|
|
9
|
+
"cmux",
|
|
10
|
+
"workflow",
|
|
11
|
+
"worktree"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/KiritoKing/aiw-cli.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/KiritoKing/aiw-cli/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/KiritoKing/aiw-cli#readme",
|
|
21
|
+
"bin": {
|
|
22
|
+
"aiw": "bin/aiw"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"bin",
|
|
26
|
+
"src",
|
|
27
|
+
"config",
|
|
28
|
+
"scripts",
|
|
29
|
+
"README.zh-CN.md"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"registry": "https://registry.npmjs.org/"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"check": "node --check src/agent.mjs && node --check src/cli.mjs && node --check src/commit.mjs && node --check src/config.mjs && node --check src/deps.mjs && node --check src/git.mjs && node --check src/hooks.mjs && node --check src/init.mjs && node --check src/layout.mjs && node --check src/prompt.mjs && node --check src/run.mjs && node --check src/workspace.mjs",
|
|
37
|
+
"install:global": "scripts/install-global.sh"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
target="${AIW_INSTALL_DIR:-$HOME/.local/bin}"
|
|
6
|
+
|
|
7
|
+
mkdir -p "$target"
|
|
8
|
+
chmod +x "$root/bin/aiw"
|
|
9
|
+
ln -sf "$root/bin/aiw" "$target/aiw"
|
|
10
|
+
|
|
11
|
+
echo "Installed aiw -> $target/aiw"
|
|
12
|
+
if command -v aiw >/dev/null 2>&1; then
|
|
13
|
+
echo "Resolved aiw: $(command -v aiw)"
|
|
14
|
+
else
|
|
15
|
+
echo "aiw is installed, but $target is not on PATH for this shell."
|
|
16
|
+
fi
|
package/src/agent.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export function runAgentForText(agent, prompt, options = {}) {
|
|
4
|
+
const commitArgs = agent.commitArgs && agent.commitArgs.length > 0 ? agent.commitArgs : agent.args;
|
|
5
|
+
const expandedArgs = [];
|
|
6
|
+
let promptInArgs = false;
|
|
7
|
+
|
|
8
|
+
for (const arg of commitArgs) {
|
|
9
|
+
if (arg.includes("{{prompt}}")) {
|
|
10
|
+
expandedArgs.push(arg.replaceAll("{{prompt}}", prompt));
|
|
11
|
+
promptInArgs = true;
|
|
12
|
+
} else {
|
|
13
|
+
expandedArgs.push(arg);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = spawnSync(agent.cmd, expandedArgs, {
|
|
18
|
+
cwd: options.cwd,
|
|
19
|
+
env: process.env,
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
input: promptInArgs ? undefined : prompt,
|
|
22
|
+
maxBuffer: 1024 * 1024 * 16,
|
|
23
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
ok: result.status === 0,
|
|
28
|
+
status: result.status || 0,
|
|
29
|
+
stdout: result.stdout || "",
|
|
30
|
+
stderr: result.stderr || "",
|
|
31
|
+
command: [agent.cmd, ...expandedArgs]
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function cleanAgentText(text) {
|
|
36
|
+
let cleaned = stripAnsi(text).trim();
|
|
37
|
+
const fenced = cleaned.match(/```(?:[a-zA-Z0-9_-]+)?\s*([\s\S]*?)```/);
|
|
38
|
+
if (fenced) {
|
|
39
|
+
cleaned = fenced[1].trim();
|
|
40
|
+
}
|
|
41
|
+
cleaned = cleaned.replace(/^commit message:\s*/i, "").trim();
|
|
42
|
+
if (
|
|
43
|
+
(cleaned.startsWith('"') && cleaned.endsWith('"')) ||
|
|
44
|
+
(cleaned.startsWith("'") && cleaned.endsWith("'"))
|
|
45
|
+
) {
|
|
46
|
+
cleaned = cleaned.slice(1, -1).trim();
|
|
47
|
+
}
|
|
48
|
+
return cleaned;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stripAnsi(text) {
|
|
52
|
+
return text.replace(/\u001B\[[0-?]*[ -/]*[@-~]/g, "");
|
|
53
|
+
}
|
package/src/cli.mjs
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { expandHome, loadConfig, resolveAgent, aiwBinPath } from "./config.mjs";
|
|
5
|
+
import { runCommit } from "./commit.mjs";
|
|
6
|
+
import { assertGate, printDoctor } from "./deps.mjs";
|
|
7
|
+
import { assertGitRoot, gitRoot, isDirty, resolveRepo, selectBranch } from "./git.mjs";
|
|
8
|
+
import { runWorkspaceHook } from "./hooks.mjs";
|
|
9
|
+
import { commandInit } from "./init.mjs";
|
|
10
|
+
import { buildLayout, workspaceName } from "./layout.mjs";
|
|
11
|
+
import { commandExists, quoteShell, runInherit, sleep } from "./run.mjs";
|
|
12
|
+
import { commandWorkspace, recordWorkspaceTarget } from "./workspace.mjs";
|
|
13
|
+
|
|
14
|
+
export async function main(argv) {
|
|
15
|
+
const command = argv[0] || "help";
|
|
16
|
+
const rest = argv.slice(1);
|
|
17
|
+
const config = loadConfig();
|
|
18
|
+
|
|
19
|
+
switch (command) {
|
|
20
|
+
case "help":
|
|
21
|
+
case "-h":
|
|
22
|
+
case "--help":
|
|
23
|
+
printHelp();
|
|
24
|
+
return;
|
|
25
|
+
case "doctor":
|
|
26
|
+
await commandDoctor(config, rest);
|
|
27
|
+
return;
|
|
28
|
+
case "init":
|
|
29
|
+
await commandInit(config, rest);
|
|
30
|
+
return;
|
|
31
|
+
case "cmux-new":
|
|
32
|
+
case "new":
|
|
33
|
+
await commandCmuxNew(config, rest, command);
|
|
34
|
+
return;
|
|
35
|
+
case "layout":
|
|
36
|
+
await commandLayout(config, rest);
|
|
37
|
+
return;
|
|
38
|
+
case "workspace":
|
|
39
|
+
case "ws":
|
|
40
|
+
await commandWorkspace(config, rest);
|
|
41
|
+
return;
|
|
42
|
+
case "open":
|
|
43
|
+
await commandWorkspace(config, ["open", ...rest]);
|
|
44
|
+
return;
|
|
45
|
+
case "switch":
|
|
46
|
+
await commandWorkspace(config, ["open", ...rest]);
|
|
47
|
+
return;
|
|
48
|
+
case "list":
|
|
49
|
+
await commandWorkspace(config, ["list", ...rest]);
|
|
50
|
+
return;
|
|
51
|
+
case "done":
|
|
52
|
+
await commandWorkspace(config, ["done", ...rest]);
|
|
53
|
+
return;
|
|
54
|
+
case "remove":
|
|
55
|
+
await commandWorkspace(config, ["remove", ...rest]);
|
|
56
|
+
return;
|
|
57
|
+
case "gc":
|
|
58
|
+
case "clean":
|
|
59
|
+
await commandWorkspace(config, ["gc", ...rest]);
|
|
60
|
+
return;
|
|
61
|
+
case "diff":
|
|
62
|
+
await commandDiff(config, rest);
|
|
63
|
+
return;
|
|
64
|
+
case "commit":
|
|
65
|
+
await commandCommit(config, rest);
|
|
66
|
+
return;
|
|
67
|
+
case "git":
|
|
68
|
+
await commandGit(config, rest);
|
|
69
|
+
return;
|
|
70
|
+
case "files":
|
|
71
|
+
assertGate("files", config);
|
|
72
|
+
await runInherit(config.defaults.files || "yazi", [rest[0] || "."]);
|
|
73
|
+
return;
|
|
74
|
+
case "edit":
|
|
75
|
+
await commandEdit(config, rest);
|
|
76
|
+
return;
|
|
77
|
+
case "grep":
|
|
78
|
+
await commandGrep(config, rest);
|
|
79
|
+
return;
|
|
80
|
+
case "pick":
|
|
81
|
+
await commandPick(config, rest);
|
|
82
|
+
return;
|
|
83
|
+
case "tree":
|
|
84
|
+
await commandTree(config, rest);
|
|
85
|
+
return;
|
|
86
|
+
default: {
|
|
87
|
+
const error = new Error(`unknown command: ${command}`);
|
|
88
|
+
error.exitCode = 2;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function commandDoctor(config, argv) {
|
|
95
|
+
const flags = parseFlags(argv);
|
|
96
|
+
const agent = flags.agent ? resolveAgent(config, flags.agent) : undefined;
|
|
97
|
+
try {
|
|
98
|
+
printDoctor(config, {
|
|
99
|
+
json: flags.json,
|
|
100
|
+
gate: flags.gate,
|
|
101
|
+
agent
|
|
102
|
+
});
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if (error.exitCode === 10) {
|
|
105
|
+
process.exitCode = 10;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function commandCmuxNew(config, argv, commandName) {
|
|
113
|
+
const flags = parseFlags(argv);
|
|
114
|
+
const branchFromArgs = flags.positionals[0] && !isKnownAgent(config, flags.positionals[0])
|
|
115
|
+
? flags.positionals[0]
|
|
116
|
+
: "";
|
|
117
|
+
const agentFromArgs = commandName === "new"
|
|
118
|
+
? flags.positionals[1]
|
|
119
|
+
: flags.positionals.find((item) => isKnownAgent(config, item));
|
|
120
|
+
if (flags.local && (flags.branch || branchFromArgs || flags.create)) {
|
|
121
|
+
const error = new Error("--local cannot be combined with --branch, a branch argument, or --create");
|
|
122
|
+
error.exitCode = 2;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
const repo = await resolveRepo(process.cwd(), config.paths.code_root, flags.repo, {
|
|
126
|
+
pickRepo: flags.pickRepo,
|
|
127
|
+
interactive: !flags.repo
|
|
128
|
+
});
|
|
129
|
+
const branchSelection = flags.local
|
|
130
|
+
? { local: true }
|
|
131
|
+
: await selectBranch(repo, flags.branch || branchFromArgs, {
|
|
132
|
+
forceCreate: flags.create
|
|
133
|
+
});
|
|
134
|
+
const agent = await selectAgent(config, flags.agent || agentFromArgs);
|
|
135
|
+
|
|
136
|
+
if (branchSelection.local) {
|
|
137
|
+
assertGate("layout", config, agent);
|
|
138
|
+
const layoutArgs = ["layout", "--agent", agent.name];
|
|
139
|
+
if (flags.dryRun) {
|
|
140
|
+
console.log(`cd ${quoteShell(repo)} && ${quoteShell(aiwBinPath())} ${layoutArgs.map(quoteShell).join(" ")}`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await runInherit(aiwBinPath(), layoutArgs, { cwd: repo });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
assertGate("cmux-new", config, agent);
|
|
148
|
+
|
|
149
|
+
if (config.behavior.warn_dirty_before_new !== false && isDirty(repo)) {
|
|
150
|
+
console.warn(`[warn] ${repo} has uncommitted changes; Worktrunk will continue with the selected branch flow`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const layoutCommand = `${quoteShell(aiwBinPath())} layout --agent ${quoteShell(agent.name)}`;
|
|
154
|
+
const wtArgs = branchSelection.create
|
|
155
|
+
? ["switch", "--create", branchSelection.branch, "--base", "@", "-x", layoutCommand]
|
|
156
|
+
: ["switch", branchSelection.branch, "-x", layoutCommand];
|
|
157
|
+
if (flags.dryRun) {
|
|
158
|
+
console.log(`cd ${quoteShell(repo)} && wt ${wtArgs.map(quoteShell).join(" ")}`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
await runInherit("wt", wtArgs, { cwd: repo });
|
|
162
|
+
if (branchSelection.create && branchSelection.targetBranch) {
|
|
163
|
+
recordWorkspaceTarget(repo, branchSelection.branch, branchSelection.targetBranch);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function commandLayout(config, argv) {
|
|
168
|
+
const flags = parseFlags(argv);
|
|
169
|
+
const agent = resolveAgent(config, flags.agent || flags.positionals[0]);
|
|
170
|
+
const cwd = process.cwd();
|
|
171
|
+
let repo = "";
|
|
172
|
+
if (!flags.printJson) {
|
|
173
|
+
assertGate("layout", config, agent);
|
|
174
|
+
}
|
|
175
|
+
if (config.behavior.require_git_repo !== false) {
|
|
176
|
+
if (!flags.printJson) {
|
|
177
|
+
repo = assertGitRoot(cwd);
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
repo = gitRoot(cwd) || cwd;
|
|
181
|
+
}
|
|
182
|
+
const layout = buildLayout(config, agent.name);
|
|
183
|
+
const layoutJson = JSON.stringify(layout);
|
|
184
|
+
if (flags.printJson) {
|
|
185
|
+
console.log(JSON.stringify(layout, null, 2));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await runWorkspaceHook(config, "pre_init", {
|
|
189
|
+
repo: repo || cwd,
|
|
190
|
+
cwd: repo || cwd,
|
|
191
|
+
workspacePath: repo || cwd,
|
|
192
|
+
branch: "",
|
|
193
|
+
agent: agent.name,
|
|
194
|
+
dryRun: flags.dryRun
|
|
195
|
+
});
|
|
196
|
+
if (flags.dryRun) {
|
|
197
|
+
console.log(`cmux new-workspace --name ${quoteShell(workspaceName(cwd, agent.name))} --cwd ${quoteShell(cwd)} --focus true --layout ${quoteShell(layoutJson)}`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await runInherit("cmux", [
|
|
201
|
+
"new-workspace",
|
|
202
|
+
"--name",
|
|
203
|
+
workspaceName(cwd, agent.name),
|
|
204
|
+
"--cwd",
|
|
205
|
+
cwd,
|
|
206
|
+
"--focus",
|
|
207
|
+
"true",
|
|
208
|
+
"--layout",
|
|
209
|
+
layoutJson
|
|
210
|
+
]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function commandDiff(config, argv) {
|
|
214
|
+
const flags = parseFlags(argv);
|
|
215
|
+
assertGate("diff", config);
|
|
216
|
+
const mode = flags.staged ? "--staged" : flags.all ? "--all" : "";
|
|
217
|
+
if (flags.watch) {
|
|
218
|
+
for (;;) {
|
|
219
|
+
process.stdout.write("\x1Bc");
|
|
220
|
+
console.log(`[aiw diff] ${new Date().toLocaleTimeString()} ${mode}`.trim());
|
|
221
|
+
await runDiffOnce(mode);
|
|
222
|
+
await sleep(2000);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
await runDiffOnce(mode);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function commandGit(config, argv) {
|
|
229
|
+
assertGate("git", config);
|
|
230
|
+
const lazygit = config.defaults.git || "lazygit";
|
|
231
|
+
const lazygitConfig = resolveConfigFile(config, config.git.lazygit_config);
|
|
232
|
+
const args = lazygitConfig ? ["--use-config-file", lazygitConfig, ...argv] : argv;
|
|
233
|
+
await runInherit(lazygit, args);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function commandCommit(config, argv) {
|
|
237
|
+
const flags = parseFlags(argv);
|
|
238
|
+
const agent = resolveAgent(config, flags.agent || config.commit.agent || config.defaults.agent);
|
|
239
|
+
assertGate("commit", config, agent);
|
|
240
|
+
await runCommit(config, flags);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function resolveConfigFile(config, value) {
|
|
244
|
+
if (!value) {
|
|
245
|
+
return "";
|
|
246
|
+
}
|
|
247
|
+
const expanded = expandHome(value);
|
|
248
|
+
const resolved = path.isAbsolute(expanded) ? expanded : path.join(config.configDir, expanded);
|
|
249
|
+
return fs.existsSync(resolved) ? resolved : "";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function runDiffOnce(mode) {
|
|
253
|
+
if (!mode && commandExists("cmux-git-diff")) {
|
|
254
|
+
await runInherit("cmux-git-diff");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const args = mode === "--staged" ? ["diff", "--staged"] : mode === "--all" ? ["diff", "HEAD"] : ["diff"];
|
|
258
|
+
if (commandExists("delta")) {
|
|
259
|
+
await runInherit("sh", ["-lc", `git ${args.map(quoteShell).join(" ")} | delta`]);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
await runInherit("git", args);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function commandEdit(config, argv) {
|
|
266
|
+
assertGate("edit", config);
|
|
267
|
+
const target = argv[0];
|
|
268
|
+
if (!target) {
|
|
269
|
+
const error = new Error("Usage: aiw edit <file[:line]>");
|
|
270
|
+
error.exitCode = 2;
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
const editor = config.defaults.editor || process.env.EDITOR || "nvim";
|
|
274
|
+
const match = target.match(/^(.+):([0-9]+)$/);
|
|
275
|
+
if (match) {
|
|
276
|
+
await runInherit(editor, [`+${match[2]}`, match[1]]);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
await runInherit(editor, [target]);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async function commandGrep(config, argv) {
|
|
283
|
+
assertGate("grep", config);
|
|
284
|
+
const query = argv.join(" ");
|
|
285
|
+
if (!query) {
|
|
286
|
+
const error = new Error("Usage: aiw grep <query>");
|
|
287
|
+
error.exitCode = 2;
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
const editor = config.defaults.editor || process.env.EDITOR || "nvim";
|
|
291
|
+
await runInherit("sh", [
|
|
292
|
+
"-lc",
|
|
293
|
+
`rg -n ${quoteShell(query)} | fzf --delimiter ':' --preview 'bat --style=numbers --color=always --highlight-line {2} {1}' --bind ${quoteShell(`enter:execute(${editor} +{2} {1})`)}`
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function commandPick(config) {
|
|
298
|
+
assertGate("pick", config);
|
|
299
|
+
const editor = config.defaults.editor || process.env.EDITOR || "nvim";
|
|
300
|
+
await runInherit("sh", [
|
|
301
|
+
"-lc",
|
|
302
|
+
`fd -t f | fzf --preview 'bat --style=numbers --color=always {}' --bind ${quoteShell(`enter:execute(${editor} {})`)}`
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function commandTree(config, argv) {
|
|
307
|
+
const depth = argv[0] || String(config.defaults.tree_depth || 3);
|
|
308
|
+
if (commandExists("eza")) {
|
|
309
|
+
await runInherit("eza", ["--tree", `--level=${depth}`, "--git-ignore"]);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
await runInherit("find", [".", "-maxdepth", depth, "-type", "f"]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function parseFlags(argv) {
|
|
316
|
+
const flags = {
|
|
317
|
+
positionals: []
|
|
318
|
+
};
|
|
319
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
320
|
+
const arg = argv[index];
|
|
321
|
+
switch (arg) {
|
|
322
|
+
case "--agent":
|
|
323
|
+
flags.agent = argv[++index];
|
|
324
|
+
break;
|
|
325
|
+
case "--branch":
|
|
326
|
+
flags.branch = argv[++index];
|
|
327
|
+
break;
|
|
328
|
+
case "--repo":
|
|
329
|
+
flags.repo = argv[++index];
|
|
330
|
+
break;
|
|
331
|
+
case "--prompt":
|
|
332
|
+
flags.prompt = argv[++index];
|
|
333
|
+
break;
|
|
334
|
+
case "--prompt-file":
|
|
335
|
+
flags.promptFile = argv[++index];
|
|
336
|
+
break;
|
|
337
|
+
case "--retries":
|
|
338
|
+
flags.retries = argv[++index];
|
|
339
|
+
break;
|
|
340
|
+
case "--pick-repo":
|
|
341
|
+
case "--select-repo":
|
|
342
|
+
flags.pickRepo = true;
|
|
343
|
+
break;
|
|
344
|
+
case "--create":
|
|
345
|
+
flags.create = true;
|
|
346
|
+
break;
|
|
347
|
+
case "--local":
|
|
348
|
+
flags.local = true;
|
|
349
|
+
break;
|
|
350
|
+
case "--gate":
|
|
351
|
+
flags.gate = argv[++index];
|
|
352
|
+
break;
|
|
353
|
+
case "--json":
|
|
354
|
+
flags.json = true;
|
|
355
|
+
break;
|
|
356
|
+
case "--print-json":
|
|
357
|
+
flags.printJson = true;
|
|
358
|
+
break;
|
|
359
|
+
case "--print-prompt":
|
|
360
|
+
flags.printPrompt = true;
|
|
361
|
+
break;
|
|
362
|
+
case "--dry-run":
|
|
363
|
+
flags.dryRun = true;
|
|
364
|
+
break;
|
|
365
|
+
case "--watch":
|
|
366
|
+
flags.watch = true;
|
|
367
|
+
break;
|
|
368
|
+
case "--staged":
|
|
369
|
+
flags.staged = true;
|
|
370
|
+
break;
|
|
371
|
+
case "--all":
|
|
372
|
+
flags.all = true;
|
|
373
|
+
break;
|
|
374
|
+
case "--force":
|
|
375
|
+
case "-f":
|
|
376
|
+
flags.force = true;
|
|
377
|
+
break;
|
|
378
|
+
default:
|
|
379
|
+
flags.positionals.push(arg);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return flags;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function isKnownAgent(config, value) {
|
|
386
|
+
return Boolean(value && config.agents[value]);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function selectAgent(config, requestedAgent) {
|
|
390
|
+
if (requestedAgent) {
|
|
391
|
+
return resolveAgent(config, requestedAgent);
|
|
392
|
+
}
|
|
393
|
+
if (!process.stdin.isTTY) {
|
|
394
|
+
return resolveAgent(config, config.defaults.agent);
|
|
395
|
+
}
|
|
396
|
+
const { pickFromList } = await import("./prompt.mjs");
|
|
397
|
+
const agents = Object.keys(config.agents);
|
|
398
|
+
const selected = await pickFromList("Select agent", agents, {
|
|
399
|
+
defaultItem: config.defaults.agent || "codex"
|
|
400
|
+
});
|
|
401
|
+
return resolveAgent(config, selected);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function printHelp() {
|
|
405
|
+
const executable = path.relative(process.cwd(), fileURLToPath(import.meta.url)).startsWith("..")
|
|
406
|
+
? "aiw"
|
|
407
|
+
: "./bin/aiw";
|
|
408
|
+
console.log(`Usage: ${executable} <command> [options]
|
|
409
|
+
|
|
410
|
+
Commands:
|
|
411
|
+
init [--cmux-scope <home|code|none>] [--code-root <path>] [--config-dir <path>] [--dry-run]
|
|
412
|
+
doctor [--json] [--gate <p0|init|layout|cmux-new|workspace|worktrunk|diff|commit>] [--agent <name>]
|
|
413
|
+
cmux-new [--branch <branch>] [--agent <name>] [--repo <path>] [--pick-repo] [--create] [--local] [--dry-run]
|
|
414
|
+
layout [--agent <name>] [--print-json] [--dry-run]
|
|
415
|
+
workspace|ws <list|open|done|remove|gc> [options]
|
|
416
|
+
commit [--agent <name>] [--prompt <text>] [--prompt-file <path>] [--retries <n>] [--dry-run] [--print-prompt]
|
|
417
|
+
open | switch | list | done | remove | gc | clean
|
|
418
|
+
diff [--watch] [--staged] [--all]
|
|
419
|
+
git | files [path] | edit <file[:line]> | grep <query> | pick | tree [depth]
|
|
420
|
+
|
|
421
|
+
Main workflow commands run dependency gates before creating worktrees or opening cmux layouts.`);
|
|
422
|
+
}
|