my-cli-tool-himanshu-v1 0.0.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.
@@ -0,0 +1,136 @@
1
+ import { select, isCancel } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import type { ActionTracker } from "./action-tracker.ts";
4
+ import type { ActionLog } from "./types.ts";
5
+ import { composeBeforeAfter, formatPatch } from "./diff-view.ts";
6
+ import { renderTerminalMarkdown } from "../../tui/terminal-md.ts";
7
+
8
+ interface ReviewGroup {
9
+ label: string;
10
+ actionIds: string[];
11
+ patch: string | null;
12
+ }
13
+
14
+ function groupPending(pending: ActionLog[]): ReviewGroup[] {
15
+ const byPath = new Map<string, ActionLog[]>();
16
+ const shells: ActionLog[] = [];
17
+
18
+ for (const a of pending) {
19
+ if (a.type === "tool_execute") {
20
+ shells.push(a);
21
+ continue;
22
+ }
23
+ const key = a.path;
24
+ if (!byPath.has(key)) byPath.set(key, []);
25
+ byPath.get(key)!.push(a);
26
+ }
27
+
28
+ const groups: ReviewGroup[] = [];
29
+
30
+ const pathEntries = [...byPath.entries()].sort(([a], [b]) =>
31
+ a.localeCompare(b),
32
+ );
33
+ for (const [p, acts] of pathEntries) {
34
+ const sorted = acts.sort(
35
+ (x, y) => x.timestamp.getTime() - y.timestamp.getTime(),
36
+ );
37
+ const ids = sorted.map((x) => x.id);
38
+
39
+ if (sorted.every((x) => x.type === "folder_create")) {
40
+ groups.push({
41
+ label: `Create folder: ${p}`,
42
+ actionIds: ids,
43
+ patch: null,
44
+ });
45
+ continue;
46
+ }
47
+
48
+ const { before, after } = composeBeforeAfter(sorted);
49
+ const patch = formatPatch(p, before, after);
50
+ const kinds = [...new Set(sorted.map((x) => x.type))].join(", ");
51
+ groups.push({ label: `${p} (${kinds})`, actionIds: ids, patch });
52
+ }
53
+
54
+ for (const s of shells) {
55
+ groups.push({
56
+ label: `Shell: ${s.details.command ?? "(no command)"}`,
57
+ actionIds: [s.id],
58
+ patch: null,
59
+ });
60
+ }
61
+
62
+ return groups;
63
+ }
64
+
65
+ export async function runApprovalFlow(
66
+ tracker: ActionTracker,
67
+ ): Promise<boolean> {
68
+ const pending = tracker.getPendingMutation();
69
+
70
+ if (pending.length === 0) {
71
+ console.log(
72
+ chalk.dim("\nNo staged file, folder, or shell changes to review.\n"),
73
+ );
74
+ return false;
75
+ }
76
+
77
+ const choice = await select({
78
+ message: "Apply staged changes?",
79
+ options: [
80
+ { value: "all", label: "Approve and apply all" },
81
+ { value: "select", label: "Review one by one" },
82
+ { value: "cancel", label: "Cancel" },
83
+ ],
84
+ });
85
+
86
+ if (isCancel(choice) || choice === "cancel") {
87
+ for (const a of pending) tracker.updateStatus(a.id, "rejected", false);
88
+ return false;
89
+ }
90
+
91
+ if (choice === "all") {
92
+ for (const a of pending) tracker.updateStatus(a.id, "approved", true);
93
+ return true;
94
+ }
95
+
96
+ for (const g of groupPending(pending)) {
97
+ while (true) {
98
+ const opt = await select({
99
+ message: chalk.bold(g.label),
100
+ options: [
101
+ { value: "accept", label: "Accept" },
102
+ { value: "diff", label: "Show diff", hint: g.patch ? "" : "N/A" },
103
+ { value: "reject", label: "Reject" },
104
+ ],
105
+ });
106
+
107
+ if (isCancel(opt)) {
108
+ for (const a of pending) tracker.updateStatus(a.id, "rejected", false);
109
+ return false;
110
+ }
111
+
112
+ if (opt === "diff") {
113
+ if (g.patch) {
114
+ console.log(
115
+ "\n" +
116
+ renderTerminalMarkdown("```diff\n" + g.patch + "\n```\n") +
117
+ "\n",
118
+ );
119
+ }
120
+
121
+ continue;
122
+ }
123
+
124
+ for (const id of g.actionIds) {
125
+ tracker.updateStatus(
126
+ id,
127
+ opt === "accept" ? "approved" : "rejected",
128
+ opt === "accept",
129
+ );
130
+ }
131
+ break;
132
+ }
133
+ }
134
+
135
+ return tracker.getActions().some((a) => a.status === "approved");
136
+ }
@@ -0,0 +1,68 @@
1
+ export type ActionType =
2
+ | 'file_create'
3
+ | 'file_modify'
4
+ | 'file_delete'
5
+ | 'folder_create'
6
+ | 'code_analysis'
7
+ | 'tool_execute';
8
+
9
+ export type ActionStatus = 'pending' | 'executed' | 'approved' | 'rejected';
10
+
11
+ export interface ActionLog {
12
+ id: string;
13
+ timestamp: Date;
14
+ type: ActionType;
15
+ path: string;
16
+ details: {
17
+ before?: string;
18
+ after?: string;
19
+ toolName?: string;
20
+ toolResult?: string;
21
+ error?: string;
22
+ command?: string;
23
+ };
24
+ status: ActionStatus;
25
+ userApproved?: boolean;
26
+ }
27
+
28
+ export interface AgentConfig {
29
+ codebasePath: string;
30
+ maxFileSizeToRead: number;
31
+ excludePatterns: string[];
32
+ tools: {
33
+ allowShellExecution: boolean;
34
+ allowFileModification: boolean;
35
+ allowFileCreation: boolean;
36
+ allowFolderCreation: boolean;
37
+ };
38
+ }
39
+
40
+ export const defaultAgentConfig = (): AgentConfig => ({
41
+ codebasePath: process.cwd(),
42
+ maxFileSizeToRead: 1024 * 1024 ,
43
+ excludePatterns: [
44
+ 'node_modules',
45
+ '.git',
46
+ 'dist',
47
+ 'build',
48
+ '.next',
49
+ '*.log',
50
+ '.env*',
51
+ ],
52
+ tools: {
53
+ allowShellExecution: true,
54
+ allowFileModification: true,
55
+ allowFileCreation: true,
56
+ allowFolderCreation: true,
57
+ },
58
+ });
59
+
60
+ export function isMutationType(t: ActionType): boolean {
61
+ return (
62
+ t === 'file_create' ||
63
+ t === 'file_modify' ||
64
+ t === 'file_delete' ||
65
+ t === 'folder_create' ||
66
+ t === 'tool_execute'
67
+ );
68
+ }
@@ -0,0 +1,21 @@
1
+ import type { ActionLog } from "./types.ts";
2
+ import { createTwoFilesPatch } from "diff";
3
+
4
+ export function formatPatch(filePath: string, before: string, after: string): string {
5
+ return createTwoFilesPatch(filePath,filePath, before, after, "", "", { context: 3 });
6
+ }
7
+
8
+
9
+ export function composeBeforeAfter(sorted: ActionLog[]): {
10
+ before: string;
11
+ after: string;
12
+ } {
13
+ const first = sorted[0]!;
14
+ const last = sorted[sorted.length - 1]!;
15
+ if (last.type === "file_delete")
16
+ return { before: last.details.before ?? "", after: "" };
17
+ const before =
18
+ first.type === "file_create" ? "" : (first.details.before ?? "");
19
+ const after = last.details.after ?? "";
20
+ return { before, after };
21
+ }
@@ -0,0 +1,66 @@
1
+ import { isCancel } from "@clack/core";
2
+ import chalk from "chalk";
3
+ import { text } from "@clack/prompts";
4
+ import { ToolLoopAgent, stepCountIs } from "ai";
5
+ import { getAgentModel } from "../../ai/ai.config.ts";
6
+ import { renderTerminalMarkdown } from "../../tui/terminal-md.ts";
7
+ import { ActionTracker } from "./action-tracker.ts";
8
+ import { createAgentTools } from "./agent-tools.ts";
9
+ import { defaultAgentConfig } from "./config.ts";
10
+ import { ToolExecutor } from "./tool-executor.ts";
11
+ import { runApprovalFlow } from "./approval.ts";
12
+
13
+ export async function runAgentMode(): Promise<void> {
14
+ console.log(chalk.bold("Agent Mode"));
15
+
16
+ const goal = await text({
17
+ message: "What would you like the agent to do?",
18
+ placeholder: "Concrete task for this codebase...",
19
+ });
20
+ if (isCancel(goal) || !goal.trim()) return;
21
+
22
+ const config = defaultAgentConfig();
23
+ const tracker = new ActionTracker();
24
+ const executor = new ToolExecutor(tracker, config);
25
+ const tools = createAgentTools(executor);
26
+ const agent = new ToolLoopAgent({
27
+ model: getAgentModel(),
28
+ stopWhen: stepCountIs(20),
29
+ instructions: [
30
+ `Workspace root: ${config.codebasePath}`,
31
+ "All mutations are staged until approval.",
32
+ ].join("\n"),
33
+ tools,
34
+ });
35
+
36
+ const result = await agent.generate({
37
+ prompt: goal.trim(),
38
+ onStepFinish: ({ toolCalls }) => {
39
+ for (const tc of toolCalls) {
40
+ const preview = JSON.stringify(tc.input).slice(0, 160);
41
+ console.log(
42
+ chalk.green(" ✓"),
43
+ chalk.bold(String(tc.toolName)),
44
+ chalk.dim(preview + (preview.length >= 160 ? "..." : "")),
45
+ );
46
+ }
47
+ },
48
+ });
49
+
50
+ if (result.text?.trim()) console.log(renderTerminalMarkdown(result.text));
51
+
52
+ const ok = await runApprovalFlow(tracker);
53
+ if (!ok) return executor.clearStaging();
54
+
55
+ const { errors } = executor.applyApprovedFromTracker();
56
+
57
+ if (errors.length) {
58
+ console.log(chalk.red("\nSome operations reported errors:\n"));
59
+ for (const e of errors) console.log(chalk.red(` • ${e}`));
60
+ }
61
+ else{
62
+ console.log(chalk.green('\n✓ Applied.\n'));
63
+ }
64
+
65
+ executor.clearStaging()
66
+ }