autohand-cli 0.2.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 +134 -0
- package/dist/agents-RB34F4XE.js +9 -0
- package/dist/agents-new-5I3B2W2I.js +9 -0
- package/dist/chunk-2EPIFDFM.js +68 -0
- package/dist/chunk-2NUX2RAI.js +145 -0
- package/dist/chunk-2QAL3HH4.js +79 -0
- package/dist/chunk-4UISIRMD.js +288 -0
- package/dist/chunk-55DQY6B5.js +49 -0
- package/dist/chunk-A7HRTONQ.js +382 -0
- package/dist/chunk-ALMJANSA.js +197 -0
- package/dist/chunk-GSOEIEOU.js +19 -0
- package/dist/chunk-I4HVBWYF.js +55 -0
- package/dist/chunk-KZ7VMQTC.js +20 -0
- package/dist/chunk-OC5YDNFC.js +373 -0
- package/dist/chunk-PQJIQBQ5.js +57 -0
- package/dist/chunk-PX5AGAEX.js +105 -0
- package/dist/chunk-QJ53OSGF.js +60 -0
- package/dist/chunk-SVLBJMYO.js +33 -0
- package/dist/chunk-TAZJSKFD.js +57 -0
- package/dist/chunk-TVWTD63Y.js +50 -0
- package/dist/chunk-UW2LYWIM.js +131 -0
- package/dist/chunk-VRI7EXV6.js +20 -0
- package/dist/chunk-XDVG3NM4.js +339 -0
- package/dist/chunk-YWKZF2SA.js +364 -0
- package/dist/chunk-ZWS3KSMK.js +30 -0
- package/dist/completion-Y42FKDT3.js +10 -0
- package/dist/export-WJ5P6E5Z.js +8 -0
- package/dist/feedback-NEDFOKMA.js +9 -0
- package/dist/formatters-UG6VZJJ5.js +8 -0
- package/dist/help-CNOV6OXY.js +10 -0
- package/dist/index.cjs +13418 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10450 -0
- package/dist/init-DML7AOII.js +8 -0
- package/dist/lint-TA2ZHVLM.js +8 -0
- package/dist/login-GPXDNB2F.js +10 -0
- package/dist/logout-43W7N6JU.js +10 -0
- package/dist/memory-4GSP7NKV.js +8 -0
- package/dist/model-HKEFSH5E.js +8 -0
- package/dist/new-EEZC4XXV.js +8 -0
- package/dist/quit-RSYIERO5.js +8 -0
- package/dist/resume-2NERFSTD.js +8 -0
- package/dist/session-H5QWKE5E.js +8 -0
- package/dist/sessions-4KXIT76T.js +8 -0
- package/dist/status-XAJH67SE.js +8 -0
- package/dist/undo-7QJBXARS.js +8 -0
- package/package.json +69 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/commands/help.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import terminalLink from "terminal-link";
|
|
4
|
+
async function help() {
|
|
5
|
+
console.log(chalk.cyan("\n\u{1F4DA} Available Commands:\n"));
|
|
6
|
+
const commands = [
|
|
7
|
+
{ cmd: "/quit", desc: "Exit Autohand" },
|
|
8
|
+
{ cmd: "/model", desc: "Configure providers (OpenRouter, Ollama, OpenAI, llama.cpp)" },
|
|
9
|
+
{ cmd: "/session", desc: "Show current session info" },
|
|
10
|
+
{ cmd: "/sessions", desc: "List sessions" },
|
|
11
|
+
{ cmd: "/resume", desc: "Resume a session by id" },
|
|
12
|
+
{ cmd: "/init", desc: "Create AGENTS.md file" },
|
|
13
|
+
{ cmd: "/agents", desc: "List available sub-agents" },
|
|
14
|
+
{ cmd: "/feedback", desc: "Send feedback with env details" },
|
|
15
|
+
{ cmd: "/help / ?", desc: "Show this help" }
|
|
16
|
+
];
|
|
17
|
+
commands.forEach(({ cmd, desc }) => {
|
|
18
|
+
console.log(` ${chalk.yellow(cmd.padEnd(14))} ${chalk.gray(desc)}`);
|
|
19
|
+
});
|
|
20
|
+
console.log(chalk.cyan("\n\u{1F4A1} Tips:\n"));
|
|
21
|
+
console.log(chalk.gray(" \u2022 Type @ to mention files for the AI"));
|
|
22
|
+
console.log(chalk.gray(" \u2022 Use arrow keys to navigate file suggestions"));
|
|
23
|
+
console.log(chalk.gray(" \u2022 Press Tab to autocomplete file paths"));
|
|
24
|
+
console.log(chalk.gray(" \u2022 Press Esc to cancel current operation\n"));
|
|
25
|
+
const docLink = terminalLink("docs.autohand.ai", "https://docs.autohand.ai");
|
|
26
|
+
console.log(chalk.gray(`For more information, visit ${docLink}
|
|
27
|
+
`));
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
var metadata = {
|
|
31
|
+
command: "/help",
|
|
32
|
+
description: "describe available slash commands and tips",
|
|
33
|
+
implemented: true
|
|
34
|
+
};
|
|
35
|
+
var aliasMetadata = {
|
|
36
|
+
command: "/?",
|
|
37
|
+
description: "alias for /help",
|
|
38
|
+
implemented: true
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export {
|
|
42
|
+
help,
|
|
43
|
+
metadata,
|
|
44
|
+
aliasMetadata
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* @license
|
|
48
|
+
* Copyright 2025 Autohand AI LLC
|
|
49
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
50
|
+
*/
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAuthClient,
|
|
3
|
+
saveConfig
|
|
4
|
+
} from "./chunk-A7HRTONQ.js";
|
|
5
|
+
import {
|
|
6
|
+
AUTH_CONFIG
|
|
7
|
+
} from "./chunk-2EPIFDFM.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/login.ts
|
|
10
|
+
import chalk from "chalk";
|
|
11
|
+
import enquirer from "enquirer";
|
|
12
|
+
var metadata = {
|
|
13
|
+
command: "/login",
|
|
14
|
+
description: "sign in to your Autohand account",
|
|
15
|
+
implemented: true
|
|
16
|
+
};
|
|
17
|
+
async function openBrowser(url) {
|
|
18
|
+
try {
|
|
19
|
+
const open = await import("open").then((m) => m.default).catch(() => null);
|
|
20
|
+
if (open) {
|
|
21
|
+
await open(url);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const { exec } = await import("child_process");
|
|
25
|
+
const { promisify } = await import("util");
|
|
26
|
+
const execAsync = promisify(exec);
|
|
27
|
+
const platform = process.platform;
|
|
28
|
+
let command;
|
|
29
|
+
if (platform === "darwin") {
|
|
30
|
+
command = `open "${url}"`;
|
|
31
|
+
} else if (platform === "win32") {
|
|
32
|
+
command = `start "" "${url}"`;
|
|
33
|
+
} else {
|
|
34
|
+
command = `xdg-open "${url}"`;
|
|
35
|
+
}
|
|
36
|
+
await execAsync(command);
|
|
37
|
+
return true;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function sleep(ms) {
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
}
|
|
45
|
+
async function login(ctx) {
|
|
46
|
+
const config = ctx.config;
|
|
47
|
+
if (config?.auth?.token && config?.auth?.user) {
|
|
48
|
+
const { continueLogin } = await enquirer.prompt({
|
|
49
|
+
type: "confirm",
|
|
50
|
+
name: "continueLogin",
|
|
51
|
+
message: `Already logged in as ${chalk.cyan(config.auth.user.email)}. Log in with a different account?`,
|
|
52
|
+
initial: false
|
|
53
|
+
});
|
|
54
|
+
if (!continueLogin) {
|
|
55
|
+
console.log(chalk.gray("Login cancelled."));
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const authClient = getAuthClient();
|
|
60
|
+
console.log(chalk.gray("Initiating authentication..."));
|
|
61
|
+
const initResult = await authClient.initiateDeviceAuth();
|
|
62
|
+
if (!initResult.success || !initResult.deviceCode || !initResult.userCode) {
|
|
63
|
+
console.log(chalk.red(`Failed to start login: ${initResult.error || "Unknown error"}`));
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.bold("To sign in, visit:"));
|
|
68
|
+
console.log(chalk.cyan.underline(initResult.verificationUriComplete || `${AUTH_CONFIG.authorizationUrl}?code=${initResult.userCode}`));
|
|
69
|
+
console.log();
|
|
70
|
+
console.log(chalk.gray("Or enter this code manually:"));
|
|
71
|
+
console.log(chalk.bold.yellow(` ${initResult.userCode}`));
|
|
72
|
+
console.log();
|
|
73
|
+
const browserOpened = await openBrowser(
|
|
74
|
+
initResult.verificationUriComplete || `${AUTH_CONFIG.authorizationUrl}?code=${initResult.userCode}`
|
|
75
|
+
);
|
|
76
|
+
if (browserOpened) {
|
|
77
|
+
console.log(chalk.gray("Browser opened. Complete the login in your browser."));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(chalk.yellow("Could not open browser automatically. Please visit the URL above."));
|
|
80
|
+
}
|
|
81
|
+
console.log();
|
|
82
|
+
console.log(chalk.gray("Waiting for authorization..."));
|
|
83
|
+
console.log(chalk.gray("(Press Ctrl+C to cancel)"));
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
const timeout = AUTH_CONFIG.authTimeout;
|
|
86
|
+
const pollInterval = initResult.interval ? initResult.interval * 1e3 : AUTH_CONFIG.pollInterval;
|
|
87
|
+
let dots = 0;
|
|
88
|
+
const maxDots = 3;
|
|
89
|
+
while (Date.now() - startTime < timeout) {
|
|
90
|
+
process.stdout.write(`\r${chalk.gray("Waiting" + ".".repeat(dots + 1) + " ".repeat(maxDots - dots))}`);
|
|
91
|
+
dots = (dots + 1) % (maxDots + 1);
|
|
92
|
+
await sleep(pollInterval);
|
|
93
|
+
const pollResult = await authClient.pollDeviceAuth(initResult.deviceCode);
|
|
94
|
+
if (pollResult.status === "authorized" && pollResult.token && pollResult.user) {
|
|
95
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
96
|
+
const expiresAt = new Date(Date.now() + AUTH_CONFIG.sessionExpiryDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
97
|
+
const updatedConfig = {
|
|
98
|
+
...config,
|
|
99
|
+
auth: {
|
|
100
|
+
token: pollResult.token,
|
|
101
|
+
user: pollResult.user,
|
|
102
|
+
expiresAt
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
await saveConfig(updatedConfig);
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(chalk.green("Login successful!"));
|
|
108
|
+
console.log(chalk.cyan(`Welcome, ${pollResult.user.name || pollResult.user.email}!`));
|
|
109
|
+
console.log();
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
if (pollResult.status === "expired") {
|
|
113
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
114
|
+
console.log(chalk.red("Authorization code expired. Please try again."));
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
process.stdout.write("\r" + " ".repeat(20) + "\r");
|
|
119
|
+
console.log(chalk.red("Authorization timed out. Please try again."));
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export {
|
|
124
|
+
metadata,
|
|
125
|
+
login
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* @license
|
|
129
|
+
* Copyright 2025 Autohand AI LLC
|
|
130
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
131
|
+
*/
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/commands/init.ts
|
|
2
|
+
async function init(ctx) {
|
|
3
|
+
await ctx.createAgentsFile();
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
var metadata = {
|
|
7
|
+
command: "/init",
|
|
8
|
+
description: "create an AGENTS.md file with instructions for Autohand",
|
|
9
|
+
implemented: true
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
init,
|
|
14
|
+
metadata
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* @license
|
|
18
|
+
* Copyright 2025 Autohand AI LLC
|
|
19
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
20
|
+
*/
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
// src/commands/formatters.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
// src/actions/formatters.ts
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var EXTERNAL_FORMATTERS = {
|
|
8
|
+
prettier: {
|
|
9
|
+
name: "prettier",
|
|
10
|
+
command: "prettier",
|
|
11
|
+
extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".json", ".css", ".scss", ".less", ".html", ".md", ".yaml", ".yml", ".graphql"],
|
|
12
|
+
description: "Opinionated code formatter for JavaScript, TypeScript, CSS, and more",
|
|
13
|
+
checkCmd: ["prettier", "--version"]
|
|
14
|
+
},
|
|
15
|
+
black: {
|
|
16
|
+
name: "black",
|
|
17
|
+
command: "black",
|
|
18
|
+
extensions: [".py", ".pyi"],
|
|
19
|
+
description: "The uncompromising Python code formatter",
|
|
20
|
+
checkCmd: ["black", "--version"]
|
|
21
|
+
},
|
|
22
|
+
rustfmt: {
|
|
23
|
+
name: "rustfmt",
|
|
24
|
+
command: "rustfmt",
|
|
25
|
+
extensions: [".rs"],
|
|
26
|
+
description: "Format Rust code according to style guidelines",
|
|
27
|
+
checkCmd: ["rustfmt", "--version"]
|
|
28
|
+
},
|
|
29
|
+
gofmt: {
|
|
30
|
+
name: "gofmt",
|
|
31
|
+
command: "gofmt",
|
|
32
|
+
extensions: [".go"],
|
|
33
|
+
description: "Format Go source code",
|
|
34
|
+
checkCmd: ["gofmt", "-h"]
|
|
35
|
+
},
|
|
36
|
+
clangformat: {
|
|
37
|
+
name: "clang-format",
|
|
38
|
+
command: "clang-format",
|
|
39
|
+
extensions: [".c", ".cpp", ".h", ".hpp", ".cc", ".cxx"],
|
|
40
|
+
description: "Format C/C++ code",
|
|
41
|
+
checkCmd: ["clang-format", "--version"]
|
|
42
|
+
},
|
|
43
|
+
shfmt: {
|
|
44
|
+
name: "shfmt",
|
|
45
|
+
command: "shfmt",
|
|
46
|
+
extensions: [".sh", ".bash"],
|
|
47
|
+
description: "Format shell scripts",
|
|
48
|
+
checkCmd: ["shfmt", "--version"]
|
|
49
|
+
},
|
|
50
|
+
sqlformat: {
|
|
51
|
+
name: "sqlformat",
|
|
52
|
+
command: "sqlformat",
|
|
53
|
+
extensions: [".sql"],
|
|
54
|
+
description: "Format SQL files",
|
|
55
|
+
checkCmd: ["sqlformat", "--version"]
|
|
56
|
+
},
|
|
57
|
+
xmllint: {
|
|
58
|
+
name: "xmllint",
|
|
59
|
+
command: "xmllint",
|
|
60
|
+
extensions: [".xml", ".xsl", ".xslt"],
|
|
61
|
+
description: "Format XML files",
|
|
62
|
+
checkCmd: ["xmllint", "--version"]
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
async function isCommandAvailable(command) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
const proc = spawn(command, ["--version"], {
|
|
68
|
+
stdio: "ignore",
|
|
69
|
+
shell: process.platform === "win32"
|
|
70
|
+
});
|
|
71
|
+
proc.on("error", () => resolve(false));
|
|
72
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
proc.kill();
|
|
75
|
+
resolve(false);
|
|
76
|
+
}, 2e3);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function runExternalFormatter(command, args, input, cwd) {
|
|
80
|
+
return new Promise((resolve) => {
|
|
81
|
+
const proc = spawn(command, args, {
|
|
82
|
+
cwd,
|
|
83
|
+
shell: process.platform === "win32",
|
|
84
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
85
|
+
});
|
|
86
|
+
let stdout = "";
|
|
87
|
+
let stderr = "";
|
|
88
|
+
proc.stdout.on("data", (data) => {
|
|
89
|
+
stdout += data.toString();
|
|
90
|
+
});
|
|
91
|
+
proc.stderr.on("data", (data) => {
|
|
92
|
+
stderr += data.toString();
|
|
93
|
+
});
|
|
94
|
+
proc.on("error", (err) => {
|
|
95
|
+
resolve({
|
|
96
|
+
success: false,
|
|
97
|
+
output: input,
|
|
98
|
+
error: `Failed to run ${command}: ${err.message}`
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
proc.on("close", (code) => {
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
resolve({ success: true, output: stdout || input });
|
|
104
|
+
} else {
|
|
105
|
+
resolve({
|
|
106
|
+
success: false,
|
|
107
|
+
output: input,
|
|
108
|
+
error: stderr || `${command} exited with code ${code}`
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
proc.stdin.write(input);
|
|
113
|
+
proc.stdin.end();
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
proc.kill();
|
|
116
|
+
resolve({
|
|
117
|
+
success: false,
|
|
118
|
+
output: input,
|
|
119
|
+
error: `${command} timed out after 30 seconds`
|
|
120
|
+
});
|
|
121
|
+
}, 3e4);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async function formatWithPrettier(contents, file, workspaceRoot) {
|
|
125
|
+
const ext = path.extname(file);
|
|
126
|
+
const parser = getPrettierParser(ext);
|
|
127
|
+
const result = await runExternalFormatter(
|
|
128
|
+
"prettier",
|
|
129
|
+
["--stdin-filepath", file, ...parser ? ["--parser", parser] : []],
|
|
130
|
+
contents,
|
|
131
|
+
workspaceRoot
|
|
132
|
+
);
|
|
133
|
+
if (!result.success) {
|
|
134
|
+
throw new Error(result.error || "Prettier formatting failed");
|
|
135
|
+
}
|
|
136
|
+
return result.output;
|
|
137
|
+
}
|
|
138
|
+
function getPrettierParser(ext) {
|
|
139
|
+
const parserMap = {
|
|
140
|
+
".js": "babel",
|
|
141
|
+
".jsx": "babel",
|
|
142
|
+
".ts": "typescript",
|
|
143
|
+
".tsx": "typescript",
|
|
144
|
+
".mjs": "babel",
|
|
145
|
+
".cjs": "babel",
|
|
146
|
+
".json": "json",
|
|
147
|
+
".css": "css",
|
|
148
|
+
".scss": "scss",
|
|
149
|
+
".less": "less",
|
|
150
|
+
".html": "html",
|
|
151
|
+
".md": "markdown",
|
|
152
|
+
".yaml": "yaml",
|
|
153
|
+
".yml": "yaml",
|
|
154
|
+
".graphql": "graphql"
|
|
155
|
+
};
|
|
156
|
+
return parserMap[ext] || null;
|
|
157
|
+
}
|
|
158
|
+
async function formatWithBlack(contents, file, workspaceRoot) {
|
|
159
|
+
const result = await runExternalFormatter(
|
|
160
|
+
"black",
|
|
161
|
+
["-", "--quiet"],
|
|
162
|
+
contents,
|
|
163
|
+
workspaceRoot
|
|
164
|
+
);
|
|
165
|
+
if (!result.success) {
|
|
166
|
+
throw new Error(result.error || "Black formatting failed");
|
|
167
|
+
}
|
|
168
|
+
return result.output;
|
|
169
|
+
}
|
|
170
|
+
async function formatWithRustfmt(contents, file, workspaceRoot) {
|
|
171
|
+
const result = await runExternalFormatter(
|
|
172
|
+
"rustfmt",
|
|
173
|
+
["--emit", "stdout"],
|
|
174
|
+
contents,
|
|
175
|
+
workspaceRoot
|
|
176
|
+
);
|
|
177
|
+
if (!result.success) {
|
|
178
|
+
throw new Error(result.error || "rustfmt formatting failed");
|
|
179
|
+
}
|
|
180
|
+
return result.output;
|
|
181
|
+
}
|
|
182
|
+
async function formatWithGofmt(contents, file, workspaceRoot) {
|
|
183
|
+
const result = await runExternalFormatter(
|
|
184
|
+
"gofmt",
|
|
185
|
+
[],
|
|
186
|
+
contents,
|
|
187
|
+
workspaceRoot
|
|
188
|
+
);
|
|
189
|
+
if (!result.success) {
|
|
190
|
+
throw new Error(result.error || "gofmt formatting failed");
|
|
191
|
+
}
|
|
192
|
+
return result.output;
|
|
193
|
+
}
|
|
194
|
+
async function formatWithClangFormat(contents, file, workspaceRoot) {
|
|
195
|
+
const result = await runExternalFormatter(
|
|
196
|
+
"clang-format",
|
|
197
|
+
[`--assume-filename=${file}`],
|
|
198
|
+
contents,
|
|
199
|
+
workspaceRoot
|
|
200
|
+
);
|
|
201
|
+
if (!result.success) {
|
|
202
|
+
throw new Error(result.error || "clang-format formatting failed");
|
|
203
|
+
}
|
|
204
|
+
return result.output;
|
|
205
|
+
}
|
|
206
|
+
async function formatWithShfmt(contents, file, workspaceRoot) {
|
|
207
|
+
const result = await runExternalFormatter(
|
|
208
|
+
"shfmt",
|
|
209
|
+
["-i", "2"],
|
|
210
|
+
// 2-space indent
|
|
211
|
+
contents,
|
|
212
|
+
workspaceRoot
|
|
213
|
+
);
|
|
214
|
+
if (!result.success) {
|
|
215
|
+
throw new Error(result.error || "shfmt formatting failed");
|
|
216
|
+
}
|
|
217
|
+
return result.output;
|
|
218
|
+
}
|
|
219
|
+
var builtinFormatters = {
|
|
220
|
+
json: async (contents) => {
|
|
221
|
+
const parsed = JSON.parse(contents);
|
|
222
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
223
|
+
},
|
|
224
|
+
trim: async (contents) => contents.trim() + "\n",
|
|
225
|
+
"normalize-newlines": async (contents) => {
|
|
226
|
+
return contents.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
227
|
+
},
|
|
228
|
+
"trailing-newline": async (contents) => {
|
|
229
|
+
return contents.endsWith("\n") ? contents : contents + "\n";
|
|
230
|
+
},
|
|
231
|
+
"remove-trailing-whitespace": async (contents) => {
|
|
232
|
+
return contents.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var externalFormatters = {
|
|
236
|
+
prettier: formatWithPrettier,
|
|
237
|
+
black: formatWithBlack,
|
|
238
|
+
rustfmt: formatWithRustfmt,
|
|
239
|
+
gofmt: formatWithGofmt,
|
|
240
|
+
"clang-format": formatWithClangFormat,
|
|
241
|
+
clangformat: formatWithClangFormat,
|
|
242
|
+
shfmt: formatWithShfmt
|
|
243
|
+
};
|
|
244
|
+
async function checkAvailableFormatters() {
|
|
245
|
+
const results = {};
|
|
246
|
+
for (const name of Object.keys(builtinFormatters)) {
|
|
247
|
+
results[name] = true;
|
|
248
|
+
}
|
|
249
|
+
const checks = Object.entries(EXTERNAL_FORMATTERS).map(async ([name, info]) => {
|
|
250
|
+
const available = await isCommandAvailable(info.command);
|
|
251
|
+
results[name] = available;
|
|
252
|
+
});
|
|
253
|
+
await Promise.all(checks);
|
|
254
|
+
return results;
|
|
255
|
+
}
|
|
256
|
+
async function listFormatters() {
|
|
257
|
+
const available = await checkAvailableFormatters();
|
|
258
|
+
const formatters = [
|
|
259
|
+
// Built-in formatters
|
|
260
|
+
{ name: "json", command: "built-in", extensions: [".json"], description: "Format JSON with 2-space indent", checkCmd: [], installed: true },
|
|
261
|
+
{ name: "trim", command: "built-in", extensions: ["*"], description: "Trim whitespace and ensure trailing newline", checkCmd: [], installed: true },
|
|
262
|
+
{ name: "normalize-newlines", command: "built-in", extensions: ["*"], description: "Convert all line endings to LF", checkCmd: [], installed: true },
|
|
263
|
+
{ name: "trailing-newline", command: "built-in", extensions: ["*"], description: "Ensure file ends with newline", checkCmd: [], installed: true },
|
|
264
|
+
{ name: "remove-trailing-whitespace", command: "built-in", extensions: ["*"], description: "Remove trailing whitespace from lines", checkCmd: [], installed: true },
|
|
265
|
+
// External formatters
|
|
266
|
+
...Object.entries(EXTERNAL_FORMATTERS).map(([name, info]) => ({
|
|
267
|
+
...info,
|
|
268
|
+
installed: available[name] ?? false
|
|
269
|
+
}))
|
|
270
|
+
];
|
|
271
|
+
return formatters;
|
|
272
|
+
}
|
|
273
|
+
async function applyFormatter(name, contents, file, workspaceRoot) {
|
|
274
|
+
const builtin = builtinFormatters[name];
|
|
275
|
+
if (builtin) {
|
|
276
|
+
return builtin(contents, file, workspaceRoot);
|
|
277
|
+
}
|
|
278
|
+
const external = externalFormatters[name];
|
|
279
|
+
if (external) {
|
|
280
|
+
return external(contents, file, workspaceRoot);
|
|
281
|
+
}
|
|
282
|
+
throw new Error(`Formatter "${name}" is not available. Run /formatters to see available formatters.`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/commands/formatters.ts
|
|
286
|
+
var metadata = {
|
|
287
|
+
command: "/formatters",
|
|
288
|
+
description: "List available code formatters",
|
|
289
|
+
implemented: true
|
|
290
|
+
};
|
|
291
|
+
async function execute() {
|
|
292
|
+
console.log();
|
|
293
|
+
console.log(chalk.cyan.bold("Available Code Formatters"));
|
|
294
|
+
console.log(chalk.gray("\u2500".repeat(60)));
|
|
295
|
+
console.log();
|
|
296
|
+
const formatters = await listFormatters();
|
|
297
|
+
const builtIn = formatters.filter((f) => f.command === "built-in");
|
|
298
|
+
const external = formatters.filter((f) => f.command !== "built-in");
|
|
299
|
+
console.log(chalk.yellow.bold("Built-in Formatters (always available):"));
|
|
300
|
+
console.log();
|
|
301
|
+
for (const f of builtIn) {
|
|
302
|
+
console.log(` ${chalk.green("\u2713")} ${chalk.white.bold(f.name)}`);
|
|
303
|
+
console.log(` ${chalk.gray(f.description)}`);
|
|
304
|
+
console.log(` ${chalk.gray("Extensions:")} ${f.extensions.join(", ")}`);
|
|
305
|
+
console.log();
|
|
306
|
+
}
|
|
307
|
+
console.log(chalk.yellow.bold("External Formatters:"));
|
|
308
|
+
console.log();
|
|
309
|
+
for (const f of external) {
|
|
310
|
+
const status = f.installed ? chalk.green("\u2713 installed") : chalk.red("\u2717 not found");
|
|
311
|
+
console.log(` ${f.installed ? chalk.green("\u2713") : chalk.red("\u2717")} ${chalk.white.bold(f.name)} ${chalk.gray(`(${f.command})`)} - ${status}`);
|
|
312
|
+
console.log(` ${chalk.gray(f.description)}`);
|
|
313
|
+
console.log(` ${chalk.gray("Extensions:")} ${f.extensions.join(", ")}`);
|
|
314
|
+
console.log();
|
|
315
|
+
}
|
|
316
|
+
console.log(chalk.gray("\u2500".repeat(60)));
|
|
317
|
+
console.log(chalk.gray("Usage: The agent can use format_file action with any installed formatter."));
|
|
318
|
+
console.log(chalk.gray("Install missing formatters via your package manager (npm, pip, cargo, etc.)"));
|
|
319
|
+
console.log();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export {
|
|
323
|
+
applyFormatter,
|
|
324
|
+
metadata,
|
|
325
|
+
execute
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* @license
|
|
329
|
+
* Copyright 2025 Autohand AI LLC
|
|
330
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
331
|
+
*
|
|
332
|
+
* Code Formatters
|
|
333
|
+
* Supports prettier, black, rustfmt, gofmt, and more
|
|
334
|
+
*/
|
|
335
|
+
/**
|
|
336
|
+
* @license
|
|
337
|
+
* Copyright 2025 Autohand AI LLC
|
|
338
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
339
|
+
*/
|