astrabot 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.
Files changed (47) hide show
  1. package/README.md +411 -0
  2. package/ai/ai.config.ts +27 -0
  3. package/ai/auto-retry.ts +117 -0
  4. package/ai/config-loader.ts +132 -0
  5. package/ai/index.ts +4 -0
  6. package/ai/retry-prompt.ts +30 -0
  7. package/bin/astra +2 -0
  8. package/core/retry/error-classifier.ts +208 -0
  9. package/core/retry/index.ts +29 -0
  10. package/core/retry/retry-config.ts +142 -0
  11. package/core/retry/retry-engine.ts +215 -0
  12. package/game/index.html +573 -0
  13. package/game/neon-breaker.html +1037 -0
  14. package/index.ts +140 -0
  15. package/modes/agent/action-tracker.ts +47 -0
  16. package/modes/agent/agent-tools.ts +338 -0
  17. package/modes/agent/approval.ts +184 -0
  18. package/modes/agent/diff-view.ts +34 -0
  19. package/modes/agent/orchestrator.ts +234 -0
  20. package/modes/agent/tool-executor.ts +993 -0
  21. package/modes/agent/types.ts +68 -0
  22. package/modes/ask/orchestrator.ts +230 -0
  23. package/modes/auto.ts +88 -0
  24. package/modes/cli.ts +43 -0
  25. package/modes/multi/agent-pool-manager.ts +337 -0
  26. package/modes/multi/examples.ts +441 -0
  27. package/modes/multi/message-broker.ts +179 -0
  28. package/modes/multi/multi-agent-orchestrator.ts +891 -0
  29. package/modes/multi/orchestrator.ts +414 -0
  30. package/modes/multi/types.ts +245 -0
  31. package/modes/multi/workflow-builder.ts +569 -0
  32. package/modes/plan/orchestrator.ts +198 -0
  33. package/modes/plan/planner.ts +121 -0
  34. package/modes/plan/selection.ts +43 -0
  35. package/modes/plan/types.ts +13 -0
  36. package/modes/plan/web-tools.ts +132 -0
  37. package/modes/setup.ts +210 -0
  38. package/package.json +62 -0
  39. package/session/index.ts +45 -0
  40. package/session/session-context.ts +188 -0
  41. package/session/session-manager.ts +374 -0
  42. package/session/session-tools.ts +109 -0
  43. package/session/store.ts +278 -0
  44. package/tsconfig.json +30 -0
  45. package/tui/spinner.ts +182 -0
  46. package/tui/terminal-md.ts +17 -0
  47. package/tui/wakeup.ts +231 -0
package/tui/wakeup.ts ADDED
@@ -0,0 +1,231 @@
1
+ import { select, isCancel, confirm } from "@clack/prompts";
2
+ import chalk from "chalk";
3
+ import figlet from "figlet";
4
+ import { runCliMode } from "../modes/cli";
5
+ import { getResumableSession, formatSessionLine } from "../session";
6
+ import { withSpinner } from "./spinner"; // Custom high-fidelity spinner
7
+
8
+ const BANNER_FONT = "ANSI Shadow";
9
+
10
+ // ── Clean & Minimal Color Palette ─────────────────────────────────────────
11
+ const C = {
12
+ brand: chalk.bold.hex("#a78bfa"), // Vibrant clean violet
13
+ text: chalk.hex("#f3f4f6"), // Off-white for high readability
14
+ dim: chalk.hex("#6b7280"), // Subtle gray for secondary metadata
15
+ success: chalk.bold.hex("#34d399"), // Emerald green accent
16
+ warning: chalk.bold.hex("#fbbf24"), // Amber yellow for attention items
17
+ };
18
+
19
+ /**
20
+ * Interpolates between two hex colors by a factor t ∈ [0, 1].
21
+ */
22
+ function lerpHex(from: string, to: string, t: number): string {
23
+ const f = from.replace("#", "");
24
+ const t2 = to.replace("#", "");
25
+ const r = Math.round(parseInt(f.slice(0, 2), 16) + (parseInt(t2.slice(0, 2), 16) - parseInt(f.slice(0, 2), 16)) * t);
26
+ const g = Math.round(parseInt(f.slice(2, 4), 16) + (parseInt(t2.slice(2, 4), 16) - parseInt(f.slice(2, 4), 16)) * t);
27
+ const b = Math.round(parseInt(f.slice(4, 6), 16) + (parseInt(t2.slice(4, 6), 16) - parseInt(f.slice(4, 6), 16)) * t);
28
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
29
+ }
30
+
31
+ // ── Star Field ──────────────────────────────────────────────────────────────
32
+ // Each star has a stable position and an independent phase offset so they
33
+ // twinkle asynchronously. Layout seeded deterministically — same every run.
34
+ type Star = { col: number; row: number; phaseOffset: number; speed: number };
35
+
36
+ const STAR_GLYPHS = ["·", "·", "+", "✦", "✧", "✦", "★"];
37
+ const STAR_COLORS = ["#3b2f6e", "#4c3a8a", "#7c5cbf", "#a78bfa", "#c4b0fd", "#e0d4ff"];
38
+
39
+ function buildStarField(lineCount: number): Star[] {
40
+ // Seeded LCG — stable positions across every frame / run
41
+ let seed = 0xdeadbeef;
42
+ const rand = () => {
43
+ seed ^= seed << 13;
44
+ seed ^= seed >> 17;
45
+ seed ^= seed << 5;
46
+ return (seed >>> 0) / 0xffffffff;
47
+ };
48
+
49
+ const stars: Star[] = [];
50
+ const density = 3; // stars per banner row
51
+ for (let row = 0; row < lineCount; row++) {
52
+ for (let i = 0; i < density; i++) {
53
+ stars.push({
54
+ row,
55
+ col: Math.floor(rand() * 38) + 2, // spread across ~38 cols
56
+ phaseOffset: rand() * Math.PI * 2, // independent twinkle start
57
+ speed: 0.6 + rand() * 1.2, // each star at its own rate
58
+ });
59
+ }
60
+ }
61
+ return stars;
62
+ }
63
+
64
+ /**
65
+ * Renders one star glyph at the current animation time.
66
+ */
67
+ function renderStar(star: Star, time: number): string {
68
+ const brightness = 0.5 + 0.5 * Math.sin(time * star.speed + star.phaseOffset);
69
+ const glyphIdx = Math.floor(brightness * (STAR_GLYPHS.length - 1));
70
+ const colorIdx = Math.floor(brightness * (STAR_COLORS.length - 1));
71
+ return chalk.hex(STAR_COLORS[colorIdx]!)(STAR_GLYPHS[glyphIdx]);
72
+ }
73
+
74
+ /**
75
+ * Returns one sidebar string per banner line — a fixed-width column of stars
76
+ * placed at stable positions, each pulsing independently.
77
+ */
78
+ function buildStarSidebar(stars: Star[], lineCount: number, time: number): string[] {
79
+ const WIDTH = 42;
80
+ // Build per-row character arrays; stars overwrite their slot with a chalk string
81
+ const rows: (string)[][] = Array.from({ length: lineCount }, () => Array(WIDTH).fill(" "));
82
+ for (const star of stars) {
83
+ if (star.row < lineCount && star.col < WIDTH) {
84
+ rows[star.row]![star.col] = renderStar(star, time);
85
+ }
86
+ }
87
+ return rows.map((cols) => cols.join(""));
88
+ }
89
+
90
+ // Pre-built star field — initialised once, reused every frame
91
+ let _starField: Star[] | null = null;
92
+
93
+ /**
94
+ * Prints the banner at a specific breath phase (0 = dim, 1 = full brightness),
95
+ * with a twinkling star field to the right. Clears the screen each frame.
96
+ */
97
+ function drawBanner(ascii: string, phase: number, time: number): void {
98
+ const color = lerpHex("#2e1f5e", "#a78bfa", phase);
99
+ const lines = ascii.split("\n").filter((l) => l.trim().length > 0);
100
+
101
+ if (!_starField) _starField = buildStarField(lines.length);
102
+ const sidebar = buildStarSidebar(_starField, lines.length, time);
103
+
104
+ console.clear();
105
+ console.log();
106
+ lines.forEach((line, i) => {
107
+ console.log(` ${chalk.bold.hex(color)(line)} ${sidebar[i]}`);
108
+ });
109
+ console.log(
110
+ `\n ${C.success("●")} ${C.text("ASTRA")} ${C.dim("│")} ${C.dim("AI-native development companion")}`
111
+ );
112
+ console.log(` ${C.dim(" Version 0.1.0 — Environment Ready")}\n`);
113
+ }
114
+
115
+ /**
116
+ * Plays one full inhale→exhale breath cycle on the banner (with live star
117
+ * twinkling throughout), then leaves it rendered at full brightness.
118
+ */
119
+ export async function printBanner(ascii: string): Promise<void> {
120
+ const DURATION_MS = 1600;
121
+ const FPS = 28;
122
+ const INTERVAL = Math.round(1000 / FPS);
123
+ const steps = Math.round(DURATION_MS / INTERVAL);
124
+ const startTime = Date.now();
125
+
126
+ await new Promise<void>((resolve) => {
127
+ let step = 0;
128
+ const timer = setInterval(() => {
129
+ if (step > steps) {
130
+ clearInterval(timer);
131
+ // Final frame: guaranteed full brightness
132
+ drawBanner(ascii, 1, (Date.now() - startTime) / 1000);
133
+ resolve();
134
+ return;
135
+ }
136
+ // Cosine curve: 1 → 0 → 1 (bright → dim → bright)
137
+ const phase = 0.5 + 0.5 * Math.cos((step / steps) * 2 * Math.PI);
138
+ const time = (Date.now() - startTime) / 1000;
139
+ drawBanner(ascii, phase, time);
140
+ step++;
141
+ }, INTERVAL);
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Standard initialization workflow wrapped in the custom loader framework
147
+ */
148
+ async function initializeSystem(): Promise<string> {
149
+ return await withSpinner(
150
+ {
151
+ message: "Initializing Astra development workspace...",
152
+ doneMessage: "Workspace initialized successfully.",
153
+ failMessage: "Initialization failed. Falling back to default canvas.",
154
+ },
155
+ async (ctx) => {
156
+ // Light natural pacing overhead to ensure the UI feels responsive
157
+ await new Promise((resolve) => setTimeout(resolve, 400));
158
+ ctx.updateMetric("Loading configuration");
159
+
160
+ let ascii = "";
161
+ try {
162
+ ascii = figlet.textSync("astra", { font: BANNER_FONT });
163
+ } catch {
164
+ ascii = figlet.textSync("astra", { font: "Standard" });
165
+ }
166
+
167
+ await new Promise((resolve) => setTimeout(resolve, 300));
168
+ ctx.updateMetric("Ready");
169
+ return ascii;
170
+ }
171
+ );
172
+ }
173
+
174
+ export async function runWakeup() {
175
+ console.clear();
176
+
177
+ // Boot up sequence displaying your metric-enhanced loader
178
+ const ascii = await initializeSystem();
179
+
180
+ while (true) {
181
+ console.clear();
182
+ await printBanner(ascii);
183
+
184
+ // Check for resumable session states
185
+ const recent = getResumableSession(process.cwd());
186
+ if (recent && recent.status === "interrupted") {
187
+ console.log(
188
+ ` ${C.warning("⏸ Previous session was interrupted:")} ${C.text(recent.lastGoal.slice(0, 60))}...`
189
+ );
190
+ console.log(` ${C.dim(formatSessionLine(recent))}\n`);
191
+
192
+ const resume = await confirm({
193
+ message: "Would you like to resume this session?",
194
+ initialValue: true,
195
+ });
196
+
197
+ if (!isCancel(resume) && resume) {
198
+ (globalThis as any).__ASTRA_RESUME_SESSION__ = recent.id;
199
+ console.log(C.dim(" Re-attaching to pipeline context..."));
200
+ await runCliMode();
201
+ continue;
202
+ }
203
+ }
204
+
205
+ // Clean, structured interactive select layout
206
+ const mode = await select({
207
+ message: "Select an execution mode to proceed:",
208
+ options: [
209
+ { value: "cli", label: "Interactive CLI Mode" },
210
+ //{ value: "telegram", label: "Telegram Gateway Interface" },
211
+ { value: "exit", label: "Exit Application" }
212
+ ]
213
+ });
214
+
215
+ if (isCancel(mode) || mode === "exit") {
216
+ console.log(C.dim("\n Session closed. Goodbye.\n"));
217
+ return;
218
+ }
219
+
220
+ if (mode === "cli") {
221
+ console.log(C.dim(" Launching local environment CLI..."));
222
+ await runCliMode();
223
+ continue;
224
+ }
225
+
226
+ if (mode === "telegram") {
227
+ console.log(C.dim(" Connecting to Telegram services..."));
228
+ await new Promise((resolve) => setTimeout(resolve, 1000));
229
+ }
230
+ }
231
+ }