im-pickle-rick 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 +242 -0
- package/bin.js +3 -0
- package/dist/pickle +0 -0
- package/dist/worker-executor.js +207 -0
- package/package.json +53 -0
- package/src/games/GameSidebarManager.test.ts +64 -0
- package/src/games/GameSidebarManager.ts +78 -0
- package/src/games/gameboy/GameboyView.test.ts +25 -0
- package/src/games/gameboy/GameboyView.ts +100 -0
- package/src/games/gameboy/gameboy-polyfills.ts +313 -0
- package/src/games/index.test.ts +9 -0
- package/src/games/index.ts +4 -0
- package/src/games/snake/SnakeGame.test.ts +35 -0
- package/src/games/snake/SnakeGame.ts +145 -0
- package/src/games/snake/SnakeView.test.ts +25 -0
- package/src/games/snake/SnakeView.ts +290 -0
- package/src/index.test.ts +24 -0
- package/src/index.ts +141 -0
- package/src/services/commands/worker.test.ts +14 -0
- package/src/services/commands/worker.ts +262 -0
- package/src/services/config/index.ts +2 -0
- package/src/services/config/settings.test.ts +42 -0
- package/src/services/config/settings.ts +220 -0
- package/src/services/config/state.test.ts +88 -0
- package/src/services/config/state.ts +130 -0
- package/src/services/config/types.ts +39 -0
- package/src/services/execution/index.ts +1 -0
- package/src/services/execution/pickle-source.test.ts +88 -0
- package/src/services/execution/pickle-source.ts +264 -0
- package/src/services/execution/prompt.test.ts +93 -0
- package/src/services/execution/prompt.ts +322 -0
- package/src/services/execution/sequential.test.ts +91 -0
- package/src/services/execution/sequential.ts +422 -0
- package/src/services/execution/worker-client.ts +94 -0
- package/src/services/execution/worker-executor.ts +41 -0
- package/src/services/execution/worker.test.ts +73 -0
- package/src/services/git/branch.test.ts +147 -0
- package/src/services/git/branch.ts +128 -0
- package/src/services/git/diff.test.ts +113 -0
- package/src/services/git/diff.ts +323 -0
- package/src/services/git/index.ts +4 -0
- package/src/services/git/pr.test.ts +104 -0
- package/src/services/git/pr.ts +192 -0
- package/src/services/git/worktree.test.ts +99 -0
- package/src/services/git/worktree.ts +141 -0
- package/src/services/providers/base.test.ts +86 -0
- package/src/services/providers/base.ts +438 -0
- package/src/services/providers/codex.test.ts +39 -0
- package/src/services/providers/codex.ts +208 -0
- package/src/services/providers/gemini.test.ts +40 -0
- package/src/services/providers/gemini.ts +169 -0
- package/src/services/providers/index.test.ts +28 -0
- package/src/services/providers/index.ts +41 -0
- package/src/services/providers/opencode.test.ts +64 -0
- package/src/services/providers/opencode.ts +228 -0
- package/src/services/providers/types.ts +44 -0
- package/src/skills/code-implementer.md +105 -0
- package/src/skills/code-researcher.md +78 -0
- package/src/skills/implementation-planner.md +105 -0
- package/src/skills/plan-reviewer.md +100 -0
- package/src/skills/prd-drafter.md +123 -0
- package/src/skills/research-reviewer.md +79 -0
- package/src/skills/ruthless-refactorer.md +52 -0
- package/src/skills/ticket-manager.md +135 -0
- package/src/types/index.ts +2 -0
- package/src/types/rpc.ts +14 -0
- package/src/types/tasks.ts +50 -0
- package/src/types.d.ts +9 -0
- package/src/ui/common.ts +28 -0
- package/src/ui/components/FilePickerView.test.ts +79 -0
- package/src/ui/components/FilePickerView.ts +161 -0
- package/src/ui/components/MultiLineInput.test.ts +27 -0
- package/src/ui/components/MultiLineInput.ts +233 -0
- package/src/ui/components/SessionChip.test.ts +69 -0
- package/src/ui/components/SessionChip.ts +481 -0
- package/src/ui/components/ToyboxSidebar.test.ts +36 -0
- package/src/ui/components/ToyboxSidebar.ts +329 -0
- package/src/ui/components/refactor_plan.md +35 -0
- package/src/ui/controllers/DashboardController.integration.test.ts +43 -0
- package/src/ui/controllers/DashboardController.ts +650 -0
- package/src/ui/dashboard.test.ts +43 -0
- package/src/ui/dashboard.ts +309 -0
- package/src/ui/dialogs/DashboardDialog.test.ts +146 -0
- package/src/ui/dialogs/DashboardDialog.ts +399 -0
- package/src/ui/dialogs/Dialog.test.ts +50 -0
- package/src/ui/dialogs/Dialog.ts +241 -0
- package/src/ui/dialogs/DialogSidebar.test.ts +60 -0
- package/src/ui/dialogs/DialogSidebar.ts +71 -0
- package/src/ui/dialogs/DiffViewDialog.test.ts +57 -0
- package/src/ui/dialogs/DiffViewDialog.ts +510 -0
- package/src/ui/dialogs/PRPreviewDialog.test.ts +50 -0
- package/src/ui/dialogs/PRPreviewDialog.ts +346 -0
- package/src/ui/dialogs/test-utils.ts +232 -0
- package/src/ui/file-picker-utils.test.ts +71 -0
- package/src/ui/file-picker-utils.ts +200 -0
- package/src/ui/input-chrome.test.ts +62 -0
- package/src/ui/input-chrome.ts +172 -0
- package/src/ui/logger.test.ts +68 -0
- package/src/ui/logger.ts +45 -0
- package/src/ui/mock-factory.ts +6 -0
- package/src/ui/spinner.test.ts +65 -0
- package/src/ui/spinner.ts +41 -0
- package/src/ui/test-setup.ts +300 -0
- package/src/ui/theme.test.ts +23 -0
- package/src/ui/theme.ts +16 -0
- package/src/ui/views/LandingView.integration.test.ts +21 -0
- package/src/ui/views/LandingView.test.ts +24 -0
- package/src/ui/views/LandingView.ts +221 -0
- package/src/ui/views/LogView.test.ts +24 -0
- package/src/ui/views/LogView.ts +277 -0
- package/src/ui/views/ToyboxView.test.ts +46 -0
- package/src/ui/views/ToyboxView.ts +323 -0
- package/src/utils/clipboard.test.ts +86 -0
- package/src/utils/clipboard.ts +100 -0
- package/src/utils/index.test.ts +68 -0
- package/src/utils/index.ts +95 -0
- package/src/utils/persona.test.ts +12 -0
- package/src/utils/persona.ts +8 -0
- package/src/utils/project-root.test.ts +38 -0
- package/src/utils/project-root.ts +22 -0
- package/src/utils/resources.test.ts +64 -0
- package/src/utils/resources.ts +92 -0
- package/src/utils/search.test.ts +48 -0
- package/src/utils/search.ts +103 -0
- package/src/utils/session-tracker.test.ts +46 -0
- package/src/utils/session-tracker.ts +67 -0
- package/src/utils/spinner.test.ts +54 -0
- package/src/utils/spinner.ts +87 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface SearchOptions {
|
|
5
|
+
limit?: number;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
ignore?: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SearchResult {
|
|
11
|
+
files: string[];
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function fuzzyScore(text: string, query: string): number {
|
|
16
|
+
if (!query) return 1;
|
|
17
|
+
const target = text.toLowerCase();
|
|
18
|
+
const pattern = query.toLowerCase();
|
|
19
|
+
|
|
20
|
+
let score = 0;
|
|
21
|
+
let queryIdx = 0;
|
|
22
|
+
|
|
23
|
+
// Check for exact substring match first (highest priority)
|
|
24
|
+
if (target.includes(pattern)) {
|
|
25
|
+
score += 100;
|
|
26
|
+
// Bonus for matching at start of path segment
|
|
27
|
+
if (target.startsWith(pattern) || target.includes("/" + pattern)) {
|
|
28
|
+
score += 50;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Fallback to subsequence matching
|
|
33
|
+
for (let i = 0; i < target.length && queryIdx < pattern.length; i++) {
|
|
34
|
+
if (target[i] === pattern[queryIdx]) {
|
|
35
|
+
queryIdx++;
|
|
36
|
+
if (queryIdx === 1) {
|
|
37
|
+
// Bonus for matching first char at start of segment
|
|
38
|
+
if (i === 0 || target[i-1] === "/" || target[i-1] === "_" || target[i-1] === "-") {
|
|
39
|
+
score += 10;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return queryIdx === pattern.length ? score + 1 : 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function fuzzyMatch(text: string, query: string): boolean {
|
|
49
|
+
return fuzzyScore(text, query) > 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function recursiveSearch(
|
|
53
|
+
dir: string,
|
|
54
|
+
query: string,
|
|
55
|
+
options: SearchOptions = {}
|
|
56
|
+
): Promise<SearchResult> {
|
|
57
|
+
const { limit = 20000, timeoutMs = 250, ignore = ["node_modules", ".git", "dist", ".DS_Store"] } = options;
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
const results: { path: string, score: number }[] = [];
|
|
60
|
+
let fileCount = 0;
|
|
61
|
+
let truncated = false;
|
|
62
|
+
|
|
63
|
+
async function walk(currentDir: string) {
|
|
64
|
+
if (truncated) return;
|
|
65
|
+
if (Date.now() - start >= timeoutMs) {
|
|
66
|
+
truncated = true;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (truncated) return;
|
|
74
|
+
if (ignore.includes(entry.name)) continue;
|
|
75
|
+
|
|
76
|
+
const fullPath = join(currentDir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
await walk(fullPath);
|
|
79
|
+
} else {
|
|
80
|
+
fileCount++;
|
|
81
|
+
const relativePath = fullPath.replace(dir + "/", "");
|
|
82
|
+
const score = fuzzyScore(relativePath, query);
|
|
83
|
+
if (score > 0) {
|
|
84
|
+
results.push({ path: fullPath, score });
|
|
85
|
+
}
|
|
86
|
+
if (fileCount >= limit) {
|
|
87
|
+
truncated = true;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
// Silently ignore
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await walk(dir);
|
|
98
|
+
|
|
99
|
+
// Sort by score descending
|
|
100
|
+
results.sort((a, b) => b.score - a.score);
|
|
101
|
+
|
|
102
|
+
return { files: results.map(r => r.path), truncated };
|
|
103
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach } from "bun:test";
|
|
2
|
+
import { sessionTracker, type TrackedSession } from "./session-tracker.js";
|
|
3
|
+
|
|
4
|
+
describe("SessionTracker", () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
sessionTracker.clear();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const mockSession: TrackedSession = {
|
|
10
|
+
id: "session-1",
|
|
11
|
+
prompt: "Test Prompt",
|
|
12
|
+
createdAt: 1000,
|
|
13
|
+
startedAt: "2026-02-03T00:00:00Z",
|
|
14
|
+
sessionDir: "/tmp/session-1"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
test("should add and track a session", () => {
|
|
18
|
+
sessionTracker.addSession(mockSession);
|
|
19
|
+
expect(sessionTracker.isTracked("session-1")).toBe(true);
|
|
20
|
+
expect(sessionTracker.getTrackedSessions()).toHaveLength(1);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should update a session", () => {
|
|
24
|
+
sessionTracker.addSession(mockSession);
|
|
25
|
+
sessionTracker.updateSession("session-1", { status: "running", iteration: 5 });
|
|
26
|
+
|
|
27
|
+
const sessions = sessionTracker.getTrackedSessions();
|
|
28
|
+
expect(sessions[0].status).toBe("running");
|
|
29
|
+
expect(sessions[0].iteration).toBe(5);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should remove a session", () => {
|
|
33
|
+
sessionTracker.addSession(mockSession);
|
|
34
|
+
sessionTracker.removeSession("session-1");
|
|
35
|
+
expect(sessionTracker.isTracked("session-1")).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("should sort sessions by createdAt descending", () => {
|
|
39
|
+
sessionTracker.addSession({ ...mockSession, id: "old", createdAt: 1000 });
|
|
40
|
+
sessionTracker.addSession({ ...mockSession, id: "new", createdAt: 2000 });
|
|
41
|
+
|
|
42
|
+
const sessions = sessionTracker.getTrackedSessions();
|
|
43
|
+
expect(sessions[0].id).toBe("new");
|
|
44
|
+
expect(sessions[1].id).toBe("old");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global session tracking utility
|
|
3
|
+
* Tracks sessions created from the current UI instance (LandingView/Dashboard)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface TrackedSession {
|
|
7
|
+
id: string;
|
|
8
|
+
prompt: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
startedAt: string;
|
|
11
|
+
sessionDir: string;
|
|
12
|
+
status?: string;
|
|
13
|
+
iteration?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class SessionTracker {
|
|
17
|
+
private sessions: Map<string, TrackedSession> = new Map();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add a session to the tracking list
|
|
21
|
+
*/
|
|
22
|
+
public addSession(session: TrackedSession): void {
|
|
23
|
+
this.sessions.set(session.id, session);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Update status/iteration metadata for a tracked session
|
|
28
|
+
*/
|
|
29
|
+
public updateSession(sessionId: string, partial: Partial<TrackedSession>): void {
|
|
30
|
+
const existing = this.sessions.get(sessionId);
|
|
31
|
+
if (!existing) return;
|
|
32
|
+
this.sessions.set(sessionId, { ...existing, ...partial });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Remove a session from tracking
|
|
37
|
+
*/
|
|
38
|
+
public removeSession(sessionId: string): void {
|
|
39
|
+
this.sessions.delete(sessionId);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get all tracked sessions
|
|
44
|
+
*/
|
|
45
|
+
public getTrackedSessions(): TrackedSession[] {
|
|
46
|
+
return Array.from(this.sessions.values()).sort(
|
|
47
|
+
(a, b) => b.createdAt - a.createdAt
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Clear all tracked sessions (for testing/cleanup)
|
|
53
|
+
*/
|
|
54
|
+
public clear(): void {
|
|
55
|
+
this.sessions.clear();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Check if a session is being tracked
|
|
60
|
+
*/
|
|
61
|
+
public isTracked(sessionId: string): boolean {
|
|
62
|
+
return this.sessions.has(sessionId);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Export singleton instance
|
|
67
|
+
export const sessionTracker = new SessionTracker();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { expect, test, describe, beforeEach, afterEach, mock } from "bun:test";
|
|
2
|
+
import { Spinner } from "./spinner.js";
|
|
3
|
+
|
|
4
|
+
describe("Spinner Utility", () => {
|
|
5
|
+
let spinner: Spinner;
|
|
6
|
+
let originalWrite: any;
|
|
7
|
+
let mockWrite: any;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
originalWrite = process.stdout.write;
|
|
11
|
+
mockWrite = mock(() => true);
|
|
12
|
+
process.stdout.write = mockWrite;
|
|
13
|
+
spinner = new Spinner();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
if (spinner['timer']) {
|
|
18
|
+
clearInterval(spinner['timer']);
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write = originalWrite;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should start and create newline", () => {
|
|
24
|
+
spinner.start("Thinking...");
|
|
25
|
+
const calls = mockWrite.mock.calls.flat();
|
|
26
|
+
expect(calls).toContain("\x1B[?25l");
|
|
27
|
+
expect(calls).toContain("\n"); // Check for the initial newline
|
|
28
|
+
expect(calls.some((c: string) => c.includes("Thinking..."))).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("should stop and show cursor", () => {
|
|
32
|
+
spinner.start("Work");
|
|
33
|
+
mockWrite.mockClear();
|
|
34
|
+
spinner.stop("Done");
|
|
35
|
+
|
|
36
|
+
const calls = mockWrite.mock.calls.flat();
|
|
37
|
+
expect(calls).toContain("\x1B[?25h");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should use move-up logic in printAbove", () => {
|
|
41
|
+
spinner.start("Spinning");
|
|
42
|
+
mockWrite.mockClear();
|
|
43
|
+
|
|
44
|
+
spinner.printAbove("Log message");
|
|
45
|
+
|
|
46
|
+
const calls = mockWrite.mock.calls.flat();
|
|
47
|
+
// Sequence: Clear -> Up -> Content -> Newline -> Render
|
|
48
|
+
expect(calls[0]).toBe('\r\x1B[K');
|
|
49
|
+
expect(calls[1]).toBe('\x1B[1A'); // The key fix
|
|
50
|
+
expect(calls[2]).toBe("Log message");
|
|
51
|
+
expect(calls[3]).toBe("\n");
|
|
52
|
+
expect(calls[4]).toContain("Spinning");
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
|
|
3
|
+
export class Spinner {
|
|
4
|
+
private timer: Timer | null = null;
|
|
5
|
+
private text: string = "";
|
|
6
|
+
private frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
7
|
+
private frameIndex = 0;
|
|
8
|
+
private isSpinning = false;
|
|
9
|
+
|
|
10
|
+
start(text: string) {
|
|
11
|
+
if (this.isSpinning) {
|
|
12
|
+
this.update(text);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
this.isSpinning = true;
|
|
16
|
+
this.text = text;
|
|
17
|
+
process.stdout.write("\x1B[?25l"); // Hide cursor
|
|
18
|
+
|
|
19
|
+
// Create a new line for the spinner to live on
|
|
20
|
+
process.stdout.write("\n");
|
|
21
|
+
|
|
22
|
+
this.render();
|
|
23
|
+
this.timer = setInterval(() => {
|
|
24
|
+
this.frameIndex = (this.frameIndex + 1) % this.frames.length;
|
|
25
|
+
this.render();
|
|
26
|
+
}, 80);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stop(message?: string, type: 'success' | 'error' = 'success') {
|
|
30
|
+
if (!this.isSpinning) return;
|
|
31
|
+
clearInterval(this.timer!);
|
|
32
|
+
this.timer = null;
|
|
33
|
+
this.isSpinning = false;
|
|
34
|
+
|
|
35
|
+
this.clearLine();
|
|
36
|
+
|
|
37
|
+
if (message) {
|
|
38
|
+
const symbol = type === 'success' ? pc.green('✔') : pc.red('✖');
|
|
39
|
+
process.stdout.write(`\r${symbol} ${message}\n`);
|
|
40
|
+
}
|
|
41
|
+
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
success(text: string) {
|
|
45
|
+
this.stop(text, 'success');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
error(text: string) {
|
|
49
|
+
this.stop(text, 'error');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
update(text: string) {
|
|
53
|
+
this.text = text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
printAbove(content: string) {
|
|
57
|
+
if (this.isSpinning) {
|
|
58
|
+
// 1. Clear the spinner line
|
|
59
|
+
process.stdout.write('\r\x1B[K');
|
|
60
|
+
// 2. Move cursor up to the previous line (the log line)
|
|
61
|
+
process.stdout.write('\x1B[1A');
|
|
62
|
+
// 3. Print content (this appends to the log line)
|
|
63
|
+
process.stdout.write(content);
|
|
64
|
+
|
|
65
|
+
// 4. Ensure we return to the spinner line
|
|
66
|
+
if (!content.endsWith('\n')) {
|
|
67
|
+
process.stdout.write('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 5. Redraw spinner
|
|
71
|
+
this.render();
|
|
72
|
+
} else {
|
|
73
|
+
// Fallback if not spinning
|
|
74
|
+
process.stdout.write(content);
|
|
75
|
+
if (!content.endsWith('\n')) process.stdout.write('\n');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private clearLine() {
|
|
80
|
+
process.stdout.write('\r\x1B[K');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private render() {
|
|
84
|
+
const frame = pc.cyan(this.frames[this.frameIndex]);
|
|
85
|
+
process.stdout.write(`\r${frame} ${this.text}`);
|
|
86
|
+
}
|
|
87
|
+
}
|