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,430 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { spawnSync } from "node:child_process";
5
+ import type { ActionLog, AgentConfig } from "./types.ts";
6
+ import { ActionTracker } from "./action-tracker.ts";
7
+
8
+ const TEXT_EXT = new Set([
9
+ ".ts",
10
+ ".tsx",
11
+ ".js",
12
+ ".jsx",
13
+ ".mjs",
14
+ ".cjs",
15
+ ".json",
16
+ ".md",
17
+ ".mdx",
18
+ ".css",
19
+ ".html",
20
+ ".yml",
21
+ ".yaml",
22
+ ".toml",
23
+ ".txt",
24
+ ]);
25
+
26
+ function isProbablyTextFile(filePath: string): boolean {
27
+ const ext = path.extname(filePath).toLowerCase();
28
+ return TEXT_EXT.has(ext) || ext === "";
29
+ }
30
+
31
+ export class ToolExecutor {
32
+ private overlay = new Map<string, string>();
33
+ private deleted = new Set<string>();
34
+ private readonly norm = (rel: string) =>
35
+ path.posix.normalize(rel.split(path.sep).join("/")).replace(/^\.\//, "");
36
+
37
+ constructor(
38
+ private readonly tracker: ActionTracker,
39
+ private readonly config: AgentConfig,
40
+ ) {}
41
+
42
+ private resolveSafe(rel: string): string {
43
+ const abs = path.resolve(this.config.codebasePath, rel);
44
+ const root = path.resolve(this.config.codebasePath);
45
+ const relCheck = path.relative(root, abs);
46
+ if (relCheck.startsWith("..") || path.isAbsolute(relCheck)) {
47
+ throw new Error(`Path escapes workspace: ${rel}`);
48
+ }
49
+ return abs;
50
+ }
51
+
52
+ private excluded(relPath: string): boolean {
53
+ const norm = this.norm(relPath);
54
+ const segments = norm.split("/");
55
+ const base = segments[segments.length - 1] ?? "";
56
+
57
+ for (const pat of this.config.excludePatterns) {
58
+ if (pat === "*.log" && base.endsWith(".log")) return true;
59
+ if (pat === ".env*" && base.startsWith(".env")) return true;
60
+ if (pat.includes("*")) continue;
61
+ if (segments.includes(pat) || norm === pat || norm.startsWith(`${pat}/`))
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+
67
+ private assertNotExcluded(rel: string, op: string): void {
68
+ if (this.excluded(rel)) {
69
+ throw new Error(`${op}: path is excluded by policy: ${rel}`);
70
+ }
71
+ }
72
+
73
+ getEffectiveText(rel: string): string | undefined {
74
+ const key = this.norm(rel);
75
+ if (this.deleted.has(key)) return undefined;
76
+ if (this.overlay.has(key)) return this.overlay.get(key);
77
+ const abs = this.resolveSafe(rel);
78
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) return undefined;
79
+ return fs.readFileSync(abs, "utf8");
80
+ }
81
+
82
+ readFile(rel: string): string {
83
+ this.assertNotExcluded(rel, "read_file");
84
+ const abs = this.resolveSafe(rel);
85
+ if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) {
86
+ throw new Error(`File not found: ${rel}`);
87
+ }
88
+ const st = fs.statSync(abs);
89
+ if (st.size > this.config.maxFileSizeToRead) {
90
+ throw new Error(`File too large: ${rel}`);
91
+ }
92
+ const text = fs.readFileSync(abs, "utf8");
93
+ this.tracker.log({
94
+ type: "code_analysis",
95
+ path: this.norm(rel),
96
+ details: { after: text, toolName: "read_file" },
97
+ status: "executed",
98
+ });
99
+ return text;
100
+ }
101
+
102
+ createFile(rel: string, content: string): string {
103
+ if (!this.config.tools.allowFileCreation)
104
+ throw new Error("File creation disabled");
105
+ this.assertNotExcluded(rel, "create_file");
106
+ const key = this.norm(rel);
107
+ const abs = this.resolveSafe(rel);
108
+ if (fs.existsSync(abs) && !this.deleted.has(key)) {
109
+ throw new Error(`create_file: already exists: ${rel}`);
110
+ }
111
+ this.deleted.delete(key);
112
+ this.overlay.set(key, content);
113
+ this.tracker.log({
114
+ type: "file_create",
115
+ path: key,
116
+ details: { after: content },
117
+ status: "pending",
118
+ });
119
+ return `Staged new file: ${key}`;
120
+ }
121
+
122
+ modifyFile(rel: string, content: string): string {
123
+ if (!this.config.tools.allowFileModification)
124
+ throw new Error("File modification disabled");
125
+ this.assertNotExcluded(rel, "modify_file");
126
+ const before = this.getEffectiveText(rel);
127
+ if (before === undefined)
128
+ throw new Error(`modify_file: file not found: ${rel}`);
129
+ const key = this.norm(rel);
130
+ this.overlay.set(key, content);
131
+ this.tracker.log({
132
+ type: "file_modify",
133
+ path: key,
134
+ details: { before, after: content },
135
+ status: "pending",
136
+ });
137
+ return `Staged update: ${key}`;
138
+ }
139
+
140
+ deleteFile(rel: string): string {
141
+ if (!this.config.tools.allowFileModification)
142
+ throw new Error("File deletion disabled");
143
+ this.assertNotExcluded(rel, "delete_file");
144
+ const before = this.getEffectiveText(rel);
145
+ if (before === undefined)
146
+ throw new Error(`delete_file: file not found: ${rel}`);
147
+ const key = this.norm(rel);
148
+ this.overlay.delete(key);
149
+ this.deleted.add(key);
150
+ this.tracker.log({
151
+ type: "file_delete",
152
+ path: key,
153
+ details: { before },
154
+ status: "pending",
155
+ });
156
+ return `Staged delete: ${key}`;
157
+ }
158
+
159
+ createFolder(rel: string): string {
160
+ if (!this.config.tools.allowFolderCreation)
161
+ throw new Error("Folder creation disabled");
162
+ this.assertNotExcluded(rel, "create_folder");
163
+ const key = this.norm(rel);
164
+ this.tracker.log({
165
+ type: "folder_create",
166
+ path: key,
167
+ details: { after: key },
168
+ status: "pending",
169
+ });
170
+ return `Staged folder: ${key}`;
171
+ }
172
+
173
+ listFiles(rel: string, recursive: boolean): string {
174
+ this.assertNotExcluded(rel, "list_files");
175
+ const abs = this.resolveSafe(rel);
176
+ if (!fs.existsSync(abs)) throw new Error(`list_files: not found: ${rel}`);
177
+
178
+ const lines: string[] = [];
179
+ const walk = (dir: string, prefix: string) => {
180
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
181
+ for (const ent of entries) {
182
+ const full = path.join(dir, ent.name);
183
+ const relP = path.relative(this.config.codebasePath, full);
184
+ if (this.excluded(relP)) continue;
185
+ if (ent.isDirectory()) {
186
+ lines.push(`${prefix}${ent.name}/`);
187
+ if (recursive) walk(full, `${prefix}${ent.name}/`);
188
+ } else {
189
+ lines.push(`${prefix}${ent.name}`);
190
+ }
191
+ }
192
+ };
193
+
194
+ if (fs.statSync(abs).isDirectory()) walk(abs, "");
195
+ else lines.push(path.relative(this.config.codebasePath, abs));
196
+
197
+ const out = lines.sort().join("\n");
198
+ this.tracker.log({
199
+ type: "code_analysis",
200
+ path: this.norm(rel),
201
+ details: { after: out, toolName: "list_files" },
202
+ status: "executed",
203
+ });
204
+ return out || "(empty)";
205
+ }
206
+
207
+ searchFiles(
208
+ rootRel: string,
209
+ globPattern: string,
210
+ contentQuery?: string,
211
+ ): string {
212
+ this.assertNotExcluded(rootRel, "search_files");
213
+ const rootAbs = this.resolveSafe(rootRel);
214
+ if (!fs.existsSync(rootAbs))
215
+ throw new Error(`search_files: root not found: ${rootRel}`);
216
+
217
+ const results: string[] = [];
218
+ const regexFromGlob = (g: string): RegExp => {
219
+ const escaped = g
220
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
221
+ .replace(/\*\*/g, "§§")
222
+ .replace(/\*/g, "[^/\\\\]*")
223
+ .replace(/§§/g, ".*")
224
+ .replace(/\?/g, ".");
225
+ return new RegExp(`^${escaped}$`, "i");
226
+ };
227
+ const nameRe = regexFromGlob(globPattern.replace(/\\/g, "/"));
228
+
229
+ const walk = (dir: string) => {
230
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
231
+ const full = path.join(dir, ent.name);
232
+ const relP = path
233
+ .relative(this.config.codebasePath, full)
234
+ .split(path.sep)
235
+ .join("/");
236
+ if (this.excluded(relP)) continue;
237
+ if (ent.isDirectory()) walk(full);
238
+ else if (nameRe.test(relP) || nameRe.test(ent.name)) {
239
+ if (contentQuery) {
240
+ if (!isProbablyTextFile(full)) continue;
241
+ const text = fs.readFileSync(full, "utf8");
242
+ if (!text.includes(contentQuery)) continue;
243
+ }
244
+ results.push(relP);
245
+ }
246
+ }
247
+ };
248
+
249
+ if (fs.statSync(rootAbs).isDirectory()) walk(rootAbs);
250
+ else {
251
+ const relP = path
252
+ .relative(this.config.codebasePath, rootAbs)
253
+ .split(path.sep)
254
+ .join("/");
255
+ results.push(relP);
256
+ }
257
+
258
+ const out = [...new Set(results)].sort().join("\n");
259
+ this.tracker.log({
260
+ type: "code_analysis",
261
+ path: this.norm(rootRel),
262
+ details: { after: out || "(no matches)", toolName: "search_files" },
263
+ status: "executed",
264
+ });
265
+ return out || "(no matches)";
266
+ }
267
+
268
+ analyzeCodebase(rootRel: string): string {
269
+ const rootAbs = this.resolveSafe(rootRel);
270
+ if (!fs.existsSync(rootAbs))
271
+ throw new Error(`analyze_codebase: not found: ${rootRel}`);
272
+
273
+ let files = 0;
274
+ let dirs = 0;
275
+ const walk = (dir: string) => {
276
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
277
+ const full = path.join(dir, ent.name);
278
+ const relP = path.relative(this.config.codebasePath, full);
279
+ if (this.excluded(relP)) continue;
280
+ if (ent.isDirectory()) {
281
+ dirs++;
282
+ walk(full);
283
+ } else {
284
+ files++;
285
+ }
286
+ }
287
+ };
288
+ if (fs.statSync(rootAbs).isDirectory()) walk(rootAbs);
289
+ else files = 1;
290
+
291
+ const summary = `Files: ${files} | Directories: ${dirs}`;
292
+ this.tracker.log({
293
+ type: "code_analysis",
294
+ path: this.norm(rootRel),
295
+ details: { after: summary, toolName: "analyze_codebase" },
296
+ status: "executed",
297
+ });
298
+ return summary;
299
+ }
300
+
301
+ queueShell(command: string): string {
302
+ if (!this.config.tools.allowShellExecution)
303
+ throw new Error("Shell execution disabled");
304
+ this.tracker.log({
305
+ type: "tool_execute",
306
+ path: "shell",
307
+ details: { command, toolName: "execute_shell" },
308
+ status: "pending",
309
+ });
310
+ return `Shell queued: ${command}`;
311
+ }
312
+
313
+ skillRoots(): string[] {
314
+ const extra =
315
+ process.env.SKILLS_DIRS?.split(/[;]/)
316
+ .map((s) => s.trim())
317
+ .filter(Boolean) ?? [];
318
+ return [
319
+ ...extra,
320
+ path.join(homedir(), ".cursor/skills-cursor"),
321
+ path.join(homedir(), ".claude/skills"),
322
+ ];
323
+ }
324
+
325
+ listSkills(): string {
326
+ const lines: string[] = [];
327
+ for (const root of this.skillRoots()) {
328
+ if (!fs.existsSync(root)) continue;
329
+ const walk = (dir: string) => {
330
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
331
+ const full = path.join(dir, ent.name);
332
+ if (ent.isDirectory()) walk(full);
333
+ else if (ent.name === "SKILL.md") lines.push(full);
334
+ }
335
+ };
336
+ walk(root);
337
+ }
338
+ const out = lines.sort().join("\n");
339
+ this.tracker.log({
340
+ type: "code_analysis",
341
+ path: "skills",
342
+ details: { after: out || "(none)", toolName: "list_skills" },
343
+ status: "executed",
344
+ });
345
+ return out || "(none)";
346
+ }
347
+
348
+ readSkill(skillPath: string): string {
349
+ const abs = path.isAbsolute(skillPath)
350
+ ? path.normalize(skillPath)
351
+ : path.normalize(path.resolve(this.config.codebasePath, skillPath));
352
+ const allowed = this.skillRoots().some((root) => {
353
+ const r = path.resolve(root);
354
+ return abs === r || abs.startsWith(r + path.sep);
355
+ });
356
+ if (!allowed) throw new Error("read_skill: outside skill roots");
357
+ const text = fs.readFileSync(abs, "utf8");
358
+ this.tracker.log({
359
+ type: "code_analysis",
360
+ path: abs,
361
+ details: { after: text, toolName: "read_skill" },
362
+ status: "executed",
363
+ });
364
+ return text;
365
+ }
366
+
367
+ applyApprovedFromTracker(): { errors: string[] } {
368
+ const errors: string[] = [];
369
+ const all = [...this.tracker.getActions()];
370
+
371
+ for (const a of all.filter(
372
+ (x) => x.type === "folder_create" && x.status === "approved",
373
+ )) {
374
+ try {
375
+ fs.mkdirSync(this.resolveSafe(a.path), { recursive: true });
376
+ } catch (e) {
377
+ errors.push(String(e));
378
+ }
379
+ }
380
+
381
+ const fileOps = all
382
+ .filter(
383
+ (a) =>
384
+ (a.type === "file_create" ||
385
+ a.type === "file_modify" ||
386
+ a.type === "file_delete") &&
387
+ a.status === "approved",
388
+ )
389
+ .sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
390
+
391
+ const lastByPath = new Map<string, ActionLog>();
392
+ for (const a of fileOps) lastByPath.set(this.norm(a.path), a);
393
+
394
+ for (const [p, a] of lastByPath) {
395
+ try {
396
+ if (a.type === "file_delete")
397
+ fs.rmSync(this.resolveSafe(p), { force: true });
398
+ else {
399
+ const target = this.resolveSafe(p);
400
+ fs.mkdirSync(path.dirname(target), { recursive: true });
401
+ fs.writeFileSync(target, a.details.after ?? "", "utf8");
402
+ }
403
+ } catch (e) {
404
+ errors.push(String(e));
405
+ }
406
+ }
407
+
408
+ for (const a of all.filter(
409
+ (x) => x.type === "tool_execute" && x.status === "approved",
410
+ )) {
411
+ const cmd = a.details.command;
412
+ if (!cmd) continue;
413
+ const r = spawnSync(cmd, {
414
+ shell: true,
415
+ cwd: this.config.codebasePath,
416
+ encoding: "utf8",
417
+ maxBuffer: 16 * 1024 * 1024,
418
+ });
419
+ if (r.status && r.status !== 0)
420
+ errors.push(`shell exit ${r.status}: ${cmd}`);
421
+ }
422
+
423
+ return { errors };
424
+ }
425
+
426
+ clearStaging(): void {
427
+ this.overlay.clear();
428
+ this.deleted.clear();
429
+ }
430
+ }
@@ -0,0 +1,55 @@
1
+ export type ActionType =
2
+ | "code_analysis"
3
+ | "file_create"
4
+ | "file_modify"
5
+ | "file_delete"
6
+ | "folder_create"
7
+ | "tool_execute";
8
+
9
+ export type ActionStatus = "pending" | "approved" | "rejected" | "executed";
10
+
11
+ export interface ActionDetails {
12
+ before?: string;
13
+ after?: string;
14
+ command?: string;
15
+ toolName?: string;
16
+ }
17
+
18
+ export interface ActionLog {
19
+ id: string;
20
+ timestamp: Date;
21
+ type: ActionType;
22
+ path: string;
23
+ details: ActionDetails;
24
+ status: ActionStatus;
25
+ userApproved?: boolean;
26
+ }
27
+
28
+ export interface AgentToolsConfig {
29
+ allowFileCreation: boolean;
30
+ allowFileModification: boolean;
31
+ allowFolderCreation: boolean;
32
+ allowShellExecution: boolean;
33
+ }
34
+
35
+ export interface AgentConfig {
36
+ codebasePath: string;
37
+ excludePatterns: string[];
38
+ maxFileSizeToRead: number;
39
+ tools: AgentToolsConfig;
40
+ }
41
+
42
+ export type ActionLogInput = Omit<ActionLog, "id" | "timestamp"> &
43
+ Partial<Pick<ActionLog, "id" | "timestamp">>;
44
+
45
+ const MUTATION_TYPES = new Set<ActionType>([
46
+ "file_create",
47
+ "file_modify",
48
+ "file_delete",
49
+ "folder_create",
50
+ "tool_execute",
51
+ ]);
52
+
53
+ export function isMutationType(type: ActionType): boolean {
54
+ return MUTATION_TYPES.has(type);
55
+ }
package/modes/cli.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import { runAgentMode } from "./agent/orchestrator.ts";
4
+
5
+ export async function runCliMode(): Promise<void> {
6
+ const mode = await select({
7
+ message: "Choose CLI sub-mode",
8
+ options: [
9
+ { value: "agent", label: "Agent Mode" },
10
+ { value: "plan", label: "Plan Mode" },
11
+ { value: "ask", label: "Ask Mode" },
12
+ { value: "back", label: "<- Back to main menu" },
13
+ ],
14
+ });
15
+ if (isCancel(mode) || mode === "back") return;
16
+
17
+ if (mode === "agent") {
18
+ await runAgentMode();
19
+ return;
20
+ }
21
+
22
+ if (mode === "plan" || mode === "ask") {
23
+ console.log(chalk.yellow("Mode is not implemented yet..."));
24
+ }
25
+ }
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import chalk from "chalk";
3
+ import { confirm, isCancel, text } from "@clack/prompts";
4
+ import { ToolLoopAgent, stepCountIs, tool } from "ai";
5
+ import { getAgentModel } from "../../ai/ai.config.ts";
6
+ import { ActionTracker } from "../agent/action-tracker.ts";
7
+ import { ToolExecutor } from "../agent/tool-executor.ts";
8
+ import { createAgentTools } from "../agent/agent-tools.ts";
9
+ import { defaultAgentConfig } from "../agent/config.ts";
10
+ import { runApprovalFlow } from "../agent/approval.ts";
11
+ import { renderTerminalMarkdown } from "../../tui/terminal-md.ts";
12
+
13
+
14
+ export function createAskTools(executor: ToolExecutor) {
15
+ return {
16
+ read_file: tool({
17
+ description: "Read a file in the codebase",
18
+ inputSchema: z.object({
19
+ path: z.string(),
20
+ }),
21
+ execute: async ({ path }) => executor.readFile(path),
22
+ }),
23
+ search_files: tool({
24
+ description: "Search for files in the codebase",
25
+ inputSchema: z.object({
26
+ root: z.string().default("."),
27
+ glob: z.string().default("**/*"),
28
+ query: z.string().optional(),
29
+ }),
30
+ execute: async ({ root, glob, query }) =>
31
+ executor.searchFiles(root, glob, query),
32
+ }),
33
+ list_skills: tool({
34
+ description: "List all available agent skills",
35
+ inputSchema: z.object({}),
36
+ execute: async () => executor.listSkills(),
37
+ }),
38
+ read_skill: tool({
39
+ description: "Read a skill file by path",
40
+ inputSchema: z.object({
41
+ path: z.string(),
42
+ }),
43
+ execute: async ({ path }) => executor.readSkill(path),
44
+ }),
45
+ }
46
+ }
47
+
48
+ export async function runAskMode(){
49
+ console.log(chalk.bold("\n 💬 Ask Mode \n"));
50
+ const questions = await text({ message: "What do you want to ask?" })
51
+ const config = defaultAgentConfig()
52
+ config.tools.allowFileCreation = true
53
+ config.tools.allowFileModification = false;
54
+ config.tools.allowFolderCreation = false;
55
+ config.tools.allowShellExecution = false;
56
+
57
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "my-cli-tool-himanshu-v1",
3
+ "version": "0.0.1",
4
+ "main": "index.ts",
5
+ "bin": {
6
+ "my-cli-tool-himanshu-v1": "./index.ts"
7
+ },
8
+ "author": "",
9
+ "license": "ISC",
10
+ "description": "",
11
+ "dependencies": {
12
+ "@clack/core": "^1.3.0",
13
+ "@clack/prompts": "^1.3.0",
14
+ "@mendable/firecrawl-js": "^4.23.0",
15
+ "@openrouter/ai-sdk-provider": "^2.9.0",
16
+ "@types/marked-terminal": "^6.1.1",
17
+ "ai": "^6.0.180",
18
+ "chalk": "^5.6.2",
19
+ "commander": "^14.0.3",
20
+ "diff": "^9.0.0",
21
+ "figlet": "^1.11.0",
22
+ "marked": "^18.0.3",
23
+ "marked-terminal": "^7.3.0",
24
+ "telegraf": "^4.16.3",
25
+ "zod": "^4.1.12"
26
+ },
27
+ "private": false,
28
+ "devDependencies": {
29
+ "@types/bun": "^1.3.14",
30
+ "@types/figlet": "^1.7.0",
31
+ "@types/node": "^25.9.2",
32
+ "typescript": "^6.0.3"
33
+ }
34
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "Preserve",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "types": ["bun"],
9
+ "moduleResolution": "bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "skipLibCheck": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noUncheckedIndexedAccess": true,
17
+ "noImplicitOverride": true,
18
+ "noUnusedLocals": false,
19
+ "noUnusedParameters": false,
20
+ "noPropertyAccessFromIndexSignature": false
21
+ },
22
+ "include": ["**/*.ts"],
23
+ "exclude": ["node_modules"]
24
+ }
@@ -0,0 +1,20 @@
1
+ import { marked } from "marked";
2
+ import { markedTerminal } from "marked-terminal";
3
+
4
+ let ready = false;
5
+
6
+ function ensureMarked(): void {
7
+ if (ready) return;
8
+ const w = Math.max(40, Math.min(process.stdout.columns || 80, 120));
9
+ marked.use(
10
+ markedTerminal({ width: w, reflowText: true }, {}) as Parameters<
11
+ typeof marked.use
12
+ >[0],
13
+ );
14
+ ready = true;
15
+ }
16
+
17
+ export function renderTerminalMarkdown(source: string): string {
18
+ ensureMarked();
19
+ return marked.parse(source.trimEnd(), { async: false }) as string;
20
+ }
package/tui/wakeup.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { select, isCancel } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import figlet from "figlet";
4
+ import { runCliMode } from "../modes/cli.ts";
5
+
6
+ const BANNER_FONT = "ANSI Shadow";
7
+ const SHADOW = chalk.hex("#5b4e9d");
8
+ const FACE = chalk.hex("e8dcf8").bold;
9
+
10
+ function printBannerWithShadow(ascii: string): void {
11
+ const bannerLines = ascii.replace(/\s+$/, "").split("\n");
12
+ const maxLen = Math.max(...bannerLines.map((l) => l.length), 0);
13
+ const rowWidth = maxLen + 2;
14
+
15
+ for (const line of bannerLines) {
16
+ console.log(SHADOW((" " + line).padEnd(rowWidth)));
17
+ }
18
+ process.stdout.write(`\x1b[${bannerLines.length}A`);
19
+ for (const line of bannerLines) {
20
+ console.log(FACE(line.padEnd(rowWidth)));
21
+ }
22
+ console.log();
23
+ }
24
+
25
+ export async function runWakeup(): Promise<void> {
26
+ let ascii: string;
27
+ try {
28
+ ascii = figlet.textSync("My CLI Tool", { font: BANNER_FONT });
29
+ } catch {
30
+ ascii = figlet.textSync("My CLI Tool", { font: "Standard" });
31
+ }
32
+ printBannerWithShadow(ascii);
33
+
34
+ const mode = await select({
35
+ message: "Which mode do you want to proceed with?",
36
+ options: [
37
+ { value: "cli", label: "CLI" },
38
+ { value: "telegram", label: "Telegram" },
39
+ { value: "exit", label: "Exit" },
40
+ ],
41
+ });
42
+ if (isCancel(mode)) {
43
+ process.exit(0);
44
+ }
45
+ if (mode === "cli") {
46
+ console.log(chalk.dim("CLI command running..."));
47
+ await runCliMode();
48
+ } else if (mode === "exit") {
49
+ console.log(chalk.dim("Exit command running..."));
50
+ } else {
51
+ console.log(chalk.dim("Telegram command running..."));
52
+ }
53
+ }