@hasna/terminal 3.8.0 → 3.8.1

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.
@@ -674,6 +674,32 @@ Match by function name, class name, method name (including ClassName.method), in
674
674
  pushed: push ?? false,
675
675
  }) }] };
676
676
  });
677
+ server.tool("bulk_commit", "Multiple logical commits in one call. Agent decides which files go in which commit, we handle all git commands. No AI cost. Use smart_commit instead if you want AI to decide the grouping.", {
678
+ commits: z.array(z.object({
679
+ message: z.string().describe("Commit message"),
680
+ files: z.array(z.string()).describe("Files to stage for this commit"),
681
+ })).describe("Array of logical commits"),
682
+ push: z.boolean().optional().describe("Push after all commits (default: true)"),
683
+ cwd: z.string().optional().describe("Working directory"),
684
+ }, async ({ commits, push, cwd }) => {
685
+ const start = Date.now();
686
+ const workDir = cwd ?? process.cwd();
687
+ const results = [];
688
+ for (const c of commits) {
689
+ const fileArgs = c.files.map(f => `"${f}"`).join(" ");
690
+ const cmd = `git add ${fileArgs} && git commit -m ${JSON.stringify(c.message)}`;
691
+ const r = await exec(cmd, workDir, 15000);
692
+ results.push({ message: c.message, files: c.files.length, ok: r.exitCode === 0 });
693
+ }
694
+ let pushed = false;
695
+ if (push !== false) {
696
+ const pushResult = await exec("git push", workDir, 30000);
697
+ pushed = pushResult.exitCode === 0;
698
+ }
699
+ invalidateBootCache();
700
+ logCall("bulk_commit", { command: `${commits.length} commits`, durationMs: Date.now() - start });
701
+ return { content: [{ type: "text", text: JSON.stringify({ commits: results, pushed, total: results.length }) }] };
702
+ });
677
703
  server.tool("run", "Run a project task by intent — test, build, lint, dev, typecheck, format. Auto-detects toolchain (bun/npm/pnpm/yarn/cargo/go/make). Saves ~100 tokens vs raw commands.", {
678
704
  task: z.enum(["test", "build", "lint", "dev", "start", "typecheck", "format", "check"]).describe("What to run"),
679
705
  args: z.string().optional().describe("Extra arguments (e.g., '--watch', 'src/foo.test.ts')"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/terminal",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "Smart terminal wrapper for AI agents and humans — structured output, token compression, MCP server, natural language",
5
5
  "type": "module",
6
6
  "files": [
package/src/mcp/server.ts CHANGED
@@ -923,6 +923,42 @@ Match by function name, class name, method name (including ClassName.method), in
923
923
  }
924
924
  );
925
925
 
926
+ server.tool(
927
+ "bulk_commit",
928
+ "Multiple logical commits in one call. Agent decides which files go in which commit, we handle all git commands. No AI cost. Use smart_commit instead if you want AI to decide the grouping.",
929
+ {
930
+ commits: z.array(z.object({
931
+ message: z.string().describe("Commit message"),
932
+ files: z.array(z.string()).describe("Files to stage for this commit"),
933
+ })).describe("Array of logical commits"),
934
+ push: z.boolean().optional().describe("Push after all commits (default: true)"),
935
+ cwd: z.string().optional().describe("Working directory"),
936
+ },
937
+ async ({ commits, push, cwd }) => {
938
+ const start = Date.now();
939
+ const workDir = cwd ?? process.cwd();
940
+ const results: { message: string; files: number; ok: boolean }[] = [];
941
+
942
+ for (const c of commits) {
943
+ const fileArgs = c.files.map(f => `"${f}"`).join(" ");
944
+ const cmd = `git add ${fileArgs} && git commit -m ${JSON.stringify(c.message)}`;
945
+ const r = await exec(cmd, workDir, 15000);
946
+ results.push({ message: c.message, files: c.files.length, ok: r.exitCode === 0 });
947
+ }
948
+
949
+ let pushed = false;
950
+ if (push !== false) {
951
+ const pushResult = await exec("git push", workDir, 30000);
952
+ pushed = pushResult.exitCode === 0;
953
+ }
954
+
955
+ invalidateBootCache();
956
+ logCall("bulk_commit", { command: `${commits.length} commits`, durationMs: Date.now() - start });
957
+
958
+ return { content: [{ type: "text" as const, text: JSON.stringify({ commits: results, pushed, total: results.length }) }] };
959
+ }
960
+ );
961
+
926
962
  server.tool(
927
963
  "run",
928
964
  "Run a project task by intent — test, build, lint, dev, typecheck, format. Auto-detects toolchain (bun/npm/pnpm/yarn/cargo/go/make). Saves ~100 tokens vs raw commands.",