@sns-myagent/cli 0.2.0 → 0.3.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/README.md +7 -8
  3. package/bin/.gitkeep +0 -0
  4. package/bin/snscoder.js +7 -96
  5. package/dist/cli.js +22026 -0
  6. package/package.json +4 -3
  7. package/scripts/apply-pi-natives-patch.js +82 -56
  8. package/scripts/fetch-binary.mjs +106 -217
  9. package/scripts/smoke-test.sh +92 -0
  10. package/src/adapters/telegram/bot.ts +41 -1
  11. package/src/adapters/telegram/bridge.ts +186 -0
  12. package/src/adapters/telegram/handler.ts +138 -13
  13. package/src/adapters/telegram/index.ts +12 -2
  14. package/src/agents/__tests__/config.test.ts +79 -0
  15. package/src/agents/__tests__/resilience.test.ts +119 -0
  16. package/src/agents/__tests__/strategies.test.ts +83 -0
  17. package/src/agents/config.ts +367 -0
  18. package/src/agents/ensemble.ts +230 -0
  19. package/src/agents/resilience.ts +332 -0
  20. package/src/agents/strategies/best-of-n.ts +108 -0
  21. package/src/agents/strategies/consensus.ts +104 -0
  22. package/src/agents/strategies/critic.ts +131 -0
  23. package/src/agents/strategies/index.ts +12 -0
  24. package/src/agents/strategies/types.ts +68 -0
  25. package/src/async/__tests__/task-runner.test.ts +162 -0
  26. package/src/async/__tests__/task-store.test.ts +146 -0
  27. package/src/async/index.ts +28 -1
  28. package/src/async/notifier.ts +133 -0
  29. package/src/async/task-runner.ts +175 -0
  30. package/src/async/task-store.ts +170 -0
  31. package/src/async/types.ts +70 -0
  32. package/src/cli/entry.ts +3 -1
  33. package/src/cli/index.ts +74 -55
  34. package/src/config/index.ts +1 -1
  35. package/src/debug/index.ts +1 -1
  36. package/src/internal-urls/docs-index.generated.txt +0 -2
  37. package/src/modes/components/welcome.ts +13 -6
  38. package/src/modes/controllers/event-controller.ts +1 -1
  39. package/src/modes/setup-wizard/scenes/splash.ts +1 -1
  40. package/src/session/agent-session.ts +1 -1
  41. package/src/slash-commands/builtin-registry.ts +63 -0
  42. package/src/slash-commands/helpers/task.ts +181 -0
  43. package/src/tbm/__tests__/tbm.test.ts +660 -0
  44. package/src/tbm/comm-modes.ts +165 -0
  45. package/src/tbm/config.ts +136 -0
  46. package/src/tbm/context-delta.ts +146 -0
  47. package/src/tbm/context-pyramid.ts +202 -0
  48. package/src/tbm/dashboard.ts +131 -0
  49. package/src/tbm/index.ts +247 -0
  50. package/src/tbm/lazy-skills.ts +182 -0
  51. package/src/tbm/response-cache.ts +220 -0
  52. package/src/tbm/tombstone.ts +189 -0
  53. package/src/tbm/tool-compress.ts +230 -0
  54. package/src/tools/ask.ts +1 -1
  55. package/src/tui/chat-blocks.ts +205 -0
  56. package/src/tui/chat-ui.ts +270 -0
  57. package/src/tui/code-cell.ts +90 -1
  58. package/src/tui/command-palette.ts +189 -0
  59. package/src/tui/index.ts +4 -0
  60. package/src/tui/splash.ts +130 -0
  61. package/src/ui/banner.ts +69 -29
  62. package/src/ui/colors.ts +24 -0
  63. package/src/ui/error-display.ts +130 -0
  64. package/src/ui/gradient.ts +104 -0
  65. package/src/ui/index.ts +15 -0
  66. package/src/ui/memory-toast.ts +102 -0
  67. package/src/ui/status-bar.ts +36 -30
@@ -453,15 +453,22 @@ export class WelcomeComponent implements Component {
453
453
  }
454
454
  }
455
455
 
456
- export const PI_LOGO = ["▀██████████▀", " ╘██ ██ ", " ██ ██ ", " ██ ██ ", " ▄██▄ ▄██▄ "];
456
+ export const PI_LOGO = [
457
+ "███╗ ██╗███████╗██╗ ██╗██╗ ██╗███████╗",
458
+ "████╗ ██║██╔════╝╚██╗██╔╝██║ ██║██╔════╝",
459
+ "██╔██╗ ██║█████╗ ╚███╔╝ ██║ ██║███████╗",
460
+ "██║╚██╗██║██╔══╝ ██╔██╗ ██║ ██║╚════██║",
461
+ "██║ ╚████║███████╗██╔╝ ╚██╗╚██████╔╝███████║",
462
+ "╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝",
463
+ ];
457
464
 
458
465
  /** Multi-stop palette for the diagonal gradient. */
459
466
  const GRADIENT_STOPS: ReadonlyArray<readonly [number, number, number]> = [
460
- [255, 92, 200], // hot pink
461
- [200, 110, 255], // violet
462
- [120, 130, 255], // periwinkle
463
- [60, 200, 255], // bright cyan
464
- [120, 255, 220], // mint
467
+ [0, 210, 255], // #00d2ff — SNS cyan
468
+ [80, 150, 255], // transitional blue
469
+ [123, 47, 247], // #7b2ff7 — SNS purple
470
+ [200, 70, 180], // transitional pink-purple
471
+ [255, 107, 157], // #ff6b9d — SNS pink
465
472
  ];
466
473
 
467
474
  /** 256-color ramp fallback when truecolor isn't available. */
@@ -1182,7 +1182,7 @@ export class EventController {
1182
1182
 
1183
1183
  const sessionName = this.ctx.sessionManager.getSessionName();
1184
1184
  TERMINAL.sendNotification({
1185
- title: sessionName || "Oh My Pi",
1185
+ title: sessionName || "SnsCoder",
1186
1186
  body: "Complete",
1187
1187
  type: "completion",
1188
1188
  actions: "focus",
@@ -189,7 +189,7 @@ export function renderSetupSplash(width: number, height: number, elapsedMs: numb
189
189
  /** Centered fallback for windows too small to hold the full scene. */
190
190
  function renderCompactSplash(width: number, height: number, phase: number, shine: ShineConfig): string[] {
191
191
  const art = height >= 14 ? LARGE_LOGO : PI_LOGO;
192
- const content = [...gradientLogo(art, phase, shine), "", theme.bold("O h M y P i")];
192
+ const content = [...gradientLogo(art, phase, shine), "", theme.bold("S n s C o d e r")];
193
193
  const start = Math.max(0, Math.floor((height - content.length) / 2));
194
194
  const lines: string[] = [];
195
195
  for (let y = 0; y < height; y++) {
@@ -1376,7 +1376,7 @@ export class AgentSession {
1376
1376
  if (mode === "off") return;
1377
1377
  try {
1378
1378
  this.#powerAssertion = MacOSPowerAssertion.start({
1379
- reason: "Oh My Pi agent session",
1379
+ reason: "SnsCoder agent session",
1380
1380
  idle: true,
1381
1381
  display: mode === "display" || mode === "system",
1382
1382
  system: mode === "system",
@@ -32,6 +32,7 @@ import { urlHyperlinkAlways } from "../tui";
32
32
  import { getChangelogPath, parseChangelog } from "../utils/changelog";
33
33
  import { CollabQrCodeComponent } from "./helpers/collab-qrcode";
34
34
  import { handleCronCommand } from "./helpers/cron";
35
+ import { handleTaskCommand } from "./helpers/task";
35
36
  import { buildContextReportText } from "./helpers/context-report";
36
37
  import { formatDuration } from "./helpers/format";
37
38
  import { createMarketplaceManager } from "./helpers/marketplace-manager";
@@ -2233,11 +2234,73 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
2233
2234
  ],
2234
2235
  handle: handleCronCommand,
2235
2236
  },
2237
+ {
2238
+ name: "task",
2239
+ description: "Manage async background tasks",
2240
+ allowArgs: true,
2241
+ subcommands: [
2242
+ { name: "run", description: "Spawn async task in background", usage: "<description>" },
2243
+ { name: "list", description: "Show all tasks" },
2244
+ { name: "status", description: "Check specific task", usage: "<id>" },
2245
+ { name: "cancel", description: "Cancel running task", usage: "<id>" },
2246
+ { name: "result", description: "Get task result", usage: "<id>" },
2247
+ ],
2248
+ handle: handleTaskCommand,
2249
+ },
2236
2250
  {
2237
2251
  name: "quit",
2238
2252
  description: "Quit the application",
2239
2253
  handleTui: shutdownHandlerTui,
2240
2254
  },
2255
+ {
2256
+ name: "tokens",
2257
+ description: "Show Token Budget Manager dashboard — session stats, cache hits, compression, pyramid level",
2258
+ inlineHint: "[compact]",
2259
+ allowArgs: true,
2260
+ handle: async (command, runtime) => {
2261
+ try {
2262
+ // TBM is accessed via session.tbm if available
2263
+ const session = runtime.session as unknown as Record<string, unknown>;
2264
+ const tbm = session.tbm as { renderDashboard(): string; renderCompactDashboard(): string } | undefined;
2265
+ if (!tbm) {
2266
+ await runtime.output("TBM not initialized. Check config tbm.enabled.");
2267
+ return commandConsumed();
2268
+ }
2269
+ const compact = command.args.trim() === "compact";
2270
+ await runtime.output(compact ? tbm.renderCompactDashboard() : tbm.renderDashboard());
2271
+ return commandConsumed();
2272
+ } catch (err) {
2273
+ await runtime.output(`Failed to render TBM dashboard: ${errorMessage(err)}`);
2274
+ return commandConsumed();
2275
+ }
2276
+ },
2277
+ },
2278
+ {
2279
+ name: "mode",
2280
+ description: "Set communication mode: caveman (terse), normal, verbose, or auto",
2281
+ inlineHint: "[caveman|normal|verbose|auto|status]",
2282
+ allowArgs: true,
2283
+ handle: async (command, runtime) => {
2284
+ const arg = command.args.trim().toLowerCase();
2285
+ const session = runtime.session as unknown as Record<string, unknown>;
2286
+ const tbm = session.tbm as { setMode(mode: string): void; renderCompactDashboard(): string } | undefined;
2287
+ if (!tbm) {
2288
+ await runtime.output("TBM not initialized.");
2289
+ return commandConsumed();
2290
+ }
2291
+ if (!arg || arg === "status") {
2292
+ await runtime.output(tbm.renderCompactDashboard());
2293
+ return commandConsumed();
2294
+ }
2295
+ if (!["caveman", "normal", "verbose", "auto"].includes(arg)) {
2296
+ await runtime.output("Usage: /mode [caveman|normal|verbose|auto|status]");
2297
+ return commandConsumed();
2298
+ }
2299
+ tbm.setMode(arg);
2300
+ await runtime.output(`Communication mode set to: ${arg}`);
2301
+ return commandConsumed();
2302
+ },
2303
+ },
2241
2304
  ];
2242
2305
 
2243
2306
  const BUILTIN_SLASH_COMMAND_LOOKUP = new Map<string, SlashCommandSpec>();
@@ -0,0 +1,181 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // /task slash command handler
3
+ // ═══════════════════════════════════════════════════════════════════════════
4
+
5
+ import { getTaskStore } from "../../async/task-store";
6
+ import { getTaskRunner } from "../../async/task-runner";
7
+ import { formatTaskList, formatTaskStatus, cliNotify } from "../../async/notifier";
8
+ import type { ParsedSlashCommand, SlashCommandResult, SlashCommandRuntime } from "../types";
9
+ import { parseSubcommand } from "./parse";
10
+
11
+ /**
12
+ * Handle /task subcommands: run, list, status, cancel, result.
13
+ */
14
+ export async function handleTaskCommand(
15
+ command: ParsedSlashCommand,
16
+ runtime: SlashCommandRuntime,
17
+ ): Promise<SlashCommandResult> {
18
+ const { verb, rest } = parseSubcommand(command.args);
19
+ const store = getTaskStore();
20
+
21
+ switch (verb) {
22
+ case "run":
23
+ return taskRun(rest, store, runtime);
24
+ case "list":
25
+ case "ls":
26
+ return taskList(store, runtime);
27
+ case "status":
28
+ return taskStatus(rest, store, runtime);
29
+ case "cancel":
30
+ case "stop":
31
+ return taskCancel(rest, store, runtime);
32
+ case "result":
33
+ case "out":
34
+ return taskResult(rest, store, runtime);
35
+ default:
36
+ await runtime.output(
37
+ `Usage: /task <subcommand>\n` +
38
+ ` run <description> Spawn async task in background\n` +
39
+ ` list Show all tasks (pending + running)\n` +
40
+ ` status <id> Check specific task status\n` +
41
+ ` cancel <id> Cancel running/pending task\n` +
42
+ ` result <id> Get full task result\n`,
43
+ );
44
+ return { consumed: true };
45
+ }
46
+ }
47
+
48
+ async function taskRun(
49
+ description: string,
50
+ store: ReturnType<typeof getTaskStore>,
51
+ runtime: SlashCommandRuntime,
52
+ ): Promise<SlashCommandResult> {
53
+ if (!description.trim()) {
54
+ await runtime.output("Usage: /task run <description>");
55
+ return { consumed: true };
56
+ }
57
+
58
+ const task = store.create({ description: description.trim() });
59
+ const runner = getTaskRunner(store);
60
+
61
+ // Register a default prompt executor (idempotent — overwrites if exists)
62
+ runner.registerExecutor("prompt", async (t) => {
63
+ // Default executor: simulate work (real executor wired by agent session)
64
+ return { success: true, result: `Task "${t.description}" completed by default executor.` };
65
+ });
66
+
67
+ runner.start();
68
+ await runner.submit(task);
69
+
70
+ await runtime.output(
71
+ `✅ Task created: ${task.id.slice(0, 8)}\n` +
72
+ ` Description: ${task.description}\n` +
73
+ ` Status: pending → running in background\n` +
74
+ ` Check: /task status ${task.id.slice(0, 8)}`,
75
+ );
76
+ return { consumed: true };
77
+ }
78
+
79
+ async function taskList(
80
+ store: ReturnType<typeof getTaskStore>,
81
+ runtime: SlashCommandRuntime,
82
+ ): Promise<SlashCommandResult> {
83
+ const active = store.list("running");
84
+ const pending = store.list("pending", 10);
85
+ const recent = store.list("completed", 5);
86
+
87
+ const tasks = [...active, ...pending, ...recent];
88
+ await runtime.output(formatTaskList(tasks));
89
+
90
+ const counts = store.count();
91
+ await runtime.output(
92
+ `Total: ${counts.total} | Pending: ${counts.pending} | Running: ${counts.running} | Done: ${counts.completed} | Failed: ${counts.failed}`,
93
+ );
94
+ return { consumed: true };
95
+ }
96
+
97
+ async function taskStatus(
98
+ idPrefix: string,
99
+ store: ReturnType<typeof getTaskStore>,
100
+ runtime: SlashCommandRuntime,
101
+ ): Promise<SlashCommandResult> {
102
+ const id = idPrefix.trim();
103
+ if (!id) {
104
+ await runtime.output("Usage: /task status <id>");
105
+ return { consumed: true };
106
+ }
107
+
108
+ const task = findTaskByIdPrefix(store, id);
109
+ if (!task) {
110
+ await runtime.output(`Task not found: ${id}`);
111
+ return { consumed: true };
112
+ }
113
+
114
+ await runtime.output(formatTaskStatus(task));
115
+ return { consumed: true };
116
+ }
117
+
118
+ async function taskCancel(
119
+ idPrefix: string,
120
+ store: ReturnType<typeof getTaskStore>,
121
+ runtime: SlashCommandRuntime,
122
+ ): Promise<SlashCommandResult> {
123
+ const id = idPrefix.trim();
124
+ if (!id) {
125
+ await runtime.output("Usage: /task cancel <id>");
126
+ return { consumed: true };
127
+ }
128
+
129
+ const task = findTaskByIdPrefix(store, id);
130
+ if (!task) {
131
+ await runtime.output(`Task not found: ${id}`);
132
+ return { consumed: true };
133
+ }
134
+
135
+ const runner = getTaskRunner(store);
136
+ const cancelled = runner.cancel(task.id);
137
+ if (cancelled) {
138
+ await runtime.output(`🚫 Task ${task.id.slice(0, 8)} cancelled.`);
139
+ } else {
140
+ await runtime.output(`Task ${task.id.slice(0, 8)} is already ${task.status}, cannot cancel.`);
141
+ }
142
+ return { consumed: true };
143
+ }
144
+
145
+ async function taskResult(
146
+ idPrefix: string,
147
+ store: ReturnType<typeof getTaskStore>,
148
+ runtime: SlashCommandRuntime,
149
+ ): Promise<SlashCommandResult> {
150
+ const id = idPrefix.trim();
151
+ if (!id) {
152
+ await runtime.output("Usage: /task result <id>");
153
+ return { consumed: true };
154
+ }
155
+
156
+ const task = findTaskByIdPrefix(store, id);
157
+ if (!task) {
158
+ await runtime.output(`Task not found: ${id}`);
159
+ return { consumed: true };
160
+ }
161
+
162
+ if (task.status === "completed" && task.result) {
163
+ await runtime.output(`Result for ${task.id.slice(0, 8)}:\n${task.result}`);
164
+ } else if (task.status === "failed" && task.error) {
165
+ await runtime.output(`Error for ${task.id.slice(0, 8)}:\n${task.error}`);
166
+ } else {
167
+ await runtime.output(`Task ${task.id.slice(0, 8)} is ${task.status} — no result yet.`);
168
+ }
169
+ return { consumed: true };
170
+ }
171
+
172
+ /** Find task by full ID or prefix (first 8 chars) */
173
+ function findTaskByIdPrefix(store: ReturnType<typeof getTaskStore>, prefix: string) {
174
+ // Try full ID first
175
+ const full = store.getById(prefix);
176
+ if (full) return full;
177
+
178
+ // Try prefix match — search all recent tasks
179
+ const all = store.list(undefined, 100);
180
+ return all.find((t) => t.id.startsWith(prefix)) ?? null;
181
+ }