@aaroncql/pim-agent 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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/bin/pim.ts +109 -0
  4. package/package.json +49 -0
  5. package/src/extensions/_init/index.ts +109 -0
  6. package/src/extensions/bash/capture.test.ts +126 -0
  7. package/src/extensions/bash/capture.ts +80 -0
  8. package/src/extensions/bash/format.test.ts +240 -0
  9. package/src/extensions/bash/format.ts +76 -0
  10. package/src/extensions/bash/index.ts +86 -0
  11. package/src/extensions/bash/run.test.ts +262 -0
  12. package/src/extensions/bash/run.ts +207 -0
  13. package/src/extensions/bash/schema.ts +54 -0
  14. package/src/extensions/command-picker/index.ts +52 -0
  15. package/src/extensions/command-picker/ranker.test.ts +46 -0
  16. package/src/extensions/command-picker/ranker.ts +17 -0
  17. package/src/extensions/edit/edit.test.ts +285 -0
  18. package/src/extensions/edit/edit.ts +382 -0
  19. package/src/extensions/edit/index.ts +54 -0
  20. package/src/extensions/edit/schema.ts +37 -0
  21. package/src/extensions/file-picker/catalog.test.ts +263 -0
  22. package/src/extensions/file-picker/catalog.ts +219 -0
  23. package/src/extensions/file-picker/index.test.ts +168 -0
  24. package/src/extensions/file-picker/index.ts +119 -0
  25. package/src/extensions/file-picker/ranker.test.ts +94 -0
  26. package/src/extensions/file-picker/ranker.ts +76 -0
  27. package/src/extensions/footer/git.test.ts +76 -0
  28. package/src/extensions/footer/git.ts +87 -0
  29. package/src/extensions/footer/index.test.ts +161 -0
  30. package/src/extensions/footer/index.ts +148 -0
  31. package/src/extensions/footer/powerline.ts +87 -0
  32. package/src/extensions/footer/segments.test.ts +164 -0
  33. package/src/extensions/footer/segments.ts +234 -0
  34. package/src/extensions/glob/glob.test.ts +171 -0
  35. package/src/extensions/glob/glob.ts +34 -0
  36. package/src/extensions/glob/index.test.ts +68 -0
  37. package/src/extensions/glob/index.ts +136 -0
  38. package/src/extensions/glob/render.test.ts +126 -0
  39. package/src/extensions/glob/render.ts +74 -0
  40. package/src/extensions/glob/schema.ts +52 -0
  41. package/src/extensions/grep/grep.test.ts +387 -0
  42. package/src/extensions/grep/grep.ts +215 -0
  43. package/src/extensions/grep/index.test.ts +68 -0
  44. package/src/extensions/grep/index.ts +158 -0
  45. package/src/extensions/grep/render.test.ts +269 -0
  46. package/src/extensions/grep/render.ts +243 -0
  47. package/src/extensions/grep/schema.ts +92 -0
  48. package/src/extensions/read/index.ts +84 -0
  49. package/src/extensions/read/read.test.ts +177 -0
  50. package/src/extensions/read/read.ts +206 -0
  51. package/src/extensions/read/render.test.ts +61 -0
  52. package/src/extensions/read/render.ts +33 -0
  53. package/src/extensions/read/schema.ts +27 -0
  54. package/src/extensions/subagent/index.test.ts +44 -0
  55. package/src/extensions/subagent/index.ts +30 -0
  56. package/src/extensions/subagent/render.test.ts +292 -0
  57. package/src/extensions/subagent/render.ts +359 -0
  58. package/src/extensions/subagent/schema.ts +9 -0
  59. package/src/extensions/subagent/subagent.test.ts +315 -0
  60. package/src/extensions/subagent/subagent.ts +418 -0
  61. package/src/extensions/system-prompt/index.ts +28 -0
  62. package/src/extensions/system-prompt/prompt.test.ts +64 -0
  63. package/src/extensions/system-prompt/prompt.ts +213 -0
  64. package/src/extensions/todo/index.test.ts +244 -0
  65. package/src/extensions/todo/index.ts +122 -0
  66. package/src/extensions/todo/render.test.ts +180 -0
  67. package/src/extensions/todo/render.ts +172 -0
  68. package/src/extensions/todo/schema.ts +24 -0
  69. package/src/extensions/todo/todo.test.ts +222 -0
  70. package/src/extensions/todo/todo.ts +188 -0
  71. package/src/extensions/tps/index.test.ts +254 -0
  72. package/src/extensions/tps/index.ts +136 -0
  73. package/src/extensions/web-fetch/JinaReaderClient.ts +230 -0
  74. package/src/extensions/web-fetch/WebViewFetchClient.ts +186 -0
  75. package/src/extensions/web-fetch/WebViewMarkdownSnapshot.test.ts +119 -0
  76. package/src/extensions/web-fetch/WebViewMarkdownSnapshot.ts +511 -0
  77. package/src/extensions/web-fetch/fetch.test.ts +244 -0
  78. package/src/extensions/web-fetch/fetch.ts +249 -0
  79. package/src/extensions/web-fetch/index.ts +107 -0
  80. package/src/extensions/web-fetch/render.test.ts +56 -0
  81. package/src/extensions/web-fetch/render.ts +39 -0
  82. package/src/extensions/web-fetch/schema.ts +23 -0
  83. package/src/extensions/web-search/ExaMcpClient.test.ts +143 -0
  84. package/src/extensions/web-search/ExaMcpClient.ts +258 -0
  85. package/src/extensions/web-search/index.ts +118 -0
  86. package/src/extensions/web-search/render.test.ts +21 -0
  87. package/src/extensions/web-search/render.ts +9 -0
  88. package/src/extensions/web-search/schema.ts +21 -0
  89. package/src/extensions/web-search/search.test.ts +53 -0
  90. package/src/extensions/web-search/search.ts +23 -0
  91. package/src/extensions/working-indicator/index.test.ts +21 -0
  92. package/src/extensions/working-indicator/index.ts +77 -0
  93. package/src/extensions/write/index.ts +76 -0
  94. package/src/extensions/write/render.test.ts +64 -0
  95. package/src/extensions/write/schema.ts +14 -0
  96. package/src/extensions/write/write.test.ts +108 -0
  97. package/src/extensions/write/write.ts +104 -0
  98. package/src/shared/DiffLines.test.ts +193 -0
  99. package/src/shared/DiffLines.ts +307 -0
  100. package/src/shared/DiffRenderer.test.ts +206 -0
  101. package/src/shared/DiffRenderer.ts +396 -0
  102. package/src/shared/DiffView.ts +199 -0
  103. package/src/shared/EditMatcher.test.ts +123 -0
  104. package/src/shared/EditMatcher.ts +826 -0
  105. package/src/shared/FileScanner.test.ts +158 -0
  106. package/src/shared/FileScanner.ts +41 -0
  107. package/src/shared/Fs.ts +46 -0
  108. package/src/shared/FsErrors.ts +72 -0
  109. package/src/shared/FuzzyMatcher.test.ts +114 -0
  110. package/src/shared/FuzzyMatcher.ts +73 -0
  111. package/src/shared/GitignoreFilter.test.ts +64 -0
  112. package/src/shared/GitignoreFilter.ts +142 -0
  113. package/src/shared/GlobExclusions.ts +23 -0
  114. package/src/shared/Levenshtein.ts +33 -0
  115. package/src/shared/Lines.test.ts +25 -0
  116. package/src/shared/Lines.ts +77 -0
  117. package/src/shared/McpClient.test.ts +235 -0
  118. package/src/shared/McpClient.ts +406 -0
  119. package/src/shared/OutputBudget.test.ts +99 -0
  120. package/src/shared/OutputBudget.ts +79 -0
  121. package/src/shared/Paths.test.ts +51 -0
  122. package/src/shared/Paths.ts +52 -0
  123. package/src/shared/PimSettings.test.ts +90 -0
  124. package/src/shared/PimSettings.ts +124 -0
  125. package/src/shared/Renderer.test.ts +190 -0
  126. package/src/shared/Renderer.ts +256 -0
  127. package/src/shared/SpillCache.test.ts +94 -0
  128. package/src/shared/SpillCache.ts +89 -0
  129. package/src/shared/Tools.test.ts +392 -0
  130. package/src/shared/Tools.ts +636 -0
  131. package/src/telegram/Bot.ts +198 -0
  132. package/src/telegram/Commands.ts +721 -0
  133. package/src/telegram/Config.test.ts +275 -0
  134. package/src/telegram/Config.ts +162 -0
  135. package/src/telegram/Markdown.test.ts +143 -0
  136. package/src/telegram/Markdown.ts +177 -0
  137. package/src/telegram/Message.ts +211 -0
  138. package/src/telegram/Renderer.test.ts +216 -0
  139. package/src/telegram/Renderer.ts +713 -0
  140. package/src/telegram/SendFileSchema.ts +19 -0
  141. package/src/telegram/SendFileTool.ts +94 -0
  142. package/src/telegram/Session.ts +579 -0
  143. package/src/telegram/SessionRegistry.test.ts +89 -0
  144. package/src/telegram/SessionRegistry.ts +170 -0
  145. package/src/telegram/Supervisor.ts +357 -0
  146. package/src/telegram/TaskScheduler.test.ts +278 -0
  147. package/src/telegram/TaskScheduler.ts +293 -0
  148. package/src/telegram/TaskSchema.ts +88 -0
  149. package/src/telegram/TaskStore.ts +73 -0
  150. package/src/telegram/TaskTool.test.ts +179 -0
  151. package/src/telegram/TaskTool.ts +159 -0
  152. package/src/telegram/TypingIndicator.ts +43 -0
  153. package/src/telegram/index.ts +32 -0
  154. package/src/themes/pim-dark.json +84 -0
  155. package/src/themes/pim-light.json +84 -0
@@ -0,0 +1,73 @@
1
+ import { readdir, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ import { Fs } from "../shared/Fs";
5
+ import type { ScheduledTask } from "./TaskSchema";
6
+
7
+ export class TaskStore {
8
+ public static async loadAll(
9
+ configDir: string
10
+ ): Promise<ReadonlyArray<ScheduledTask>> {
11
+ let entries: string[];
12
+ try {
13
+ entries = await readdir(TaskStore.dir(configDir));
14
+ } catch (err) {
15
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
16
+ return [];
17
+ }
18
+ throw err;
19
+ }
20
+ const loaded = await Promise.all(
21
+ entries
22
+ .filter((name) => name.endsWith(".json"))
23
+ .map((name) =>
24
+ Fs.readJsonOrEmpty<ScheduledTask | undefined>(
25
+ join(TaskStore.dir(configDir), name),
26
+ undefined
27
+ )
28
+ )
29
+ );
30
+ return loaded.filter(
31
+ (data): data is ScheduledTask =>
32
+ !!data && typeof data === "object" && "id" in data
33
+ );
34
+ }
35
+
36
+ public static async save(
37
+ configDir: string,
38
+ task: ScheduledTask
39
+ ): Promise<void> {
40
+ await Fs.writeAtomic(
41
+ TaskStore.path(configDir, task.id),
42
+ JSON.stringify(task, null, 2)
43
+ );
44
+ }
45
+
46
+ public static async delete(configDir: string, id: string): Promise<void> {
47
+ try {
48
+ await unlink(TaskStore.path(configDir, id));
49
+ } catch (err) {
50
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
51
+ throw err;
52
+ }
53
+ }
54
+ }
55
+
56
+ public static makeId(prompt: string): string {
57
+ const slug = prompt
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9]+/g, "-")
60
+ .replace(/^-+|-+$/g, "")
61
+ .slice(0, 40);
62
+ const rand = Math.random().toString(36).slice(2, 8);
63
+ return slug ? `${slug}-${rand}` : rand;
64
+ }
65
+
66
+ private static dir(configDir: string): string {
67
+ return join(configDir, "tasks");
68
+ }
69
+
70
+ private static path(configDir: string, id: string): string {
71
+ return join(TaskStore.dir(configDir), `${id}.json`);
72
+ }
73
+ }
@@ -0,0 +1,179 @@
1
+ import { mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
+
6
+ import type { SessionId } from "./Session";
7
+ import { TaskScheduler } from "./TaskScheduler";
8
+ import { TaskTool } from "./TaskTool";
9
+ import type { ScheduledTask, TaskToolInput } from "./TaskSchema";
10
+
11
+ let tmp: string;
12
+ const sessionId: SessionId = { chatId: 7, threadId: undefined };
13
+ const otherSessionId: SessionId = { chatId: 7, threadId: 99 };
14
+
15
+ beforeEach(async () => {
16
+ tmp = await mkdtemp(join(tmpdir(), "pim-task-tool-test-"));
17
+ });
18
+
19
+ afterEach(async () => {
20
+ await rm(tmp, { recursive: true, force: true });
21
+ });
22
+
23
+ function makeTool(opts?: { readonly now?: () => number }): {
24
+ readonly run: (
25
+ input: TaskToolInput
26
+ ) => Promise<{ readonly text: string; readonly details: unknown }>;
27
+ readonly scheduler: TaskScheduler;
28
+ } {
29
+ const scheduler = new TaskScheduler({
30
+ configDir: tmp,
31
+ runTask: async () => {},
32
+ now: opts?.now ?? ((): number => Date.now()),
33
+ });
34
+ const tool = TaskTool.build({ scheduler, sessionId });
35
+ const run = async (
36
+ input: TaskToolInput
37
+ ): Promise<{ readonly text: string; readonly details: unknown }> => {
38
+ const result = await tool.execute(
39
+ "test-call-id",
40
+ input,
41
+ new AbortController().signal,
42
+ undefined,
43
+ // ExtensionContext stub — tool doesn't use it
44
+ {} as never
45
+ );
46
+ return {
47
+ text: result.content
48
+ .filter((c): c is { type: "text"; text: string } => c.type === "text")
49
+ .map((c) => c.text)
50
+ .join("\n"),
51
+ details: result.details,
52
+ };
53
+ };
54
+ return { run, scheduler };
55
+ }
56
+
57
+ describe("task tool: create", () => {
58
+ test("creates an interval task and returns id + nextRun", async () => {
59
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
60
+ const { run } = makeTool({ now: () => t0 });
61
+ const result = await run({
62
+ action: "create",
63
+ prompt: "drink water",
64
+ schedule: { type: "interval", every: "1h" },
65
+ });
66
+ expect(result.text).toMatch(/^Created task /);
67
+ const details = result.details as ScheduledTask;
68
+ expect(details.prompt).toBe("drink water");
69
+ expect(details.nextRun).toBe(new Date(t0 + 3600_000).toISOString());
70
+ });
71
+
72
+ test("rejects sub-minute interval", async () => {
73
+ const { run } = makeTool();
74
+ await expect(
75
+ run({
76
+ action: "create",
77
+ prompt: "spam",
78
+ schedule: { type: "interval", every: "10s" },
79
+ })
80
+ ).rejects.toThrow(/at least 1 minute/);
81
+ });
82
+
83
+ test("rejects invalid cron", async () => {
84
+ const { run } = makeTool();
85
+ await expect(
86
+ run({
87
+ action: "create",
88
+ prompt: "test",
89
+ schedule: { type: "cron", expr: "not a cron" },
90
+ })
91
+ ).rejects.toThrow();
92
+ });
93
+
94
+ test("requires prompt and schedule", async () => {
95
+ const { run } = makeTool();
96
+ await expect(run({ action: "create" })).rejects.toThrow(/prompt/);
97
+ await expect(run({ action: "create", prompt: "hi" })).rejects.toThrow(
98
+ /schedule/
99
+ );
100
+ });
101
+ });
102
+
103
+ describe("task tool: list/delete/pause/resume", () => {
104
+ test("list filters to current sessionId", async () => {
105
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
106
+ const { run, scheduler } = makeTool({ now: () => t0 });
107
+ await run({
108
+ action: "create",
109
+ prompt: "mine",
110
+ schedule: { type: "interval", every: "1h" },
111
+ });
112
+ await scheduler.create(otherSessionId, {
113
+ prompt: "not mine",
114
+ schedule: { type: "interval", every: "1h" },
115
+ });
116
+ const result = await run({ action: "list" });
117
+ const details = result.details as { readonly tasks: ScheduledTask[] };
118
+ expect(details.tasks).toHaveLength(1);
119
+ expect(details.tasks[0]!.prompt).toBe("mine");
120
+ });
121
+
122
+ test("delete removes by id", async () => {
123
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
124
+ const { run } = makeTool({ now: () => t0 });
125
+ const created = await run({
126
+ action: "create",
127
+ prompt: "soon-gone",
128
+ schedule: { type: "interval", every: "1h" },
129
+ });
130
+ const id = (created.details as ScheduledTask).id;
131
+ await run({ action: "delete", id });
132
+ const list = await run({ action: "list" });
133
+ expect((list.details as { tasks: ScheduledTask[] }).tasks).toHaveLength(0);
134
+ });
135
+
136
+ test("delete refuses cross-session id", async () => {
137
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
138
+ const { run, scheduler } = makeTool({ now: () => t0 });
139
+ const foreign = await scheduler.create(otherSessionId, {
140
+ prompt: "not yours",
141
+ schedule: { type: "interval", every: "1h" },
142
+ });
143
+ await expect(run({ action: "delete", id: foreign.id })).rejects.toThrow(
144
+ /no task/
145
+ );
146
+ });
147
+
148
+ test("pause flips status; resume undoes it", async () => {
149
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
150
+ const { run } = makeTool({ now: () => t0 });
151
+ const created = await run({
152
+ action: "create",
153
+ prompt: "p",
154
+ schedule: { type: "interval", every: "1h" },
155
+ });
156
+ const id = (created.details as ScheduledTask).id;
157
+
158
+ const paused = await run({ action: "pause", id });
159
+ expect((paused.details as ScheduledTask).status).toBe("paused");
160
+
161
+ const resumed = await run({ action: "resume", id });
162
+ expect((resumed.details as ScheduledTask).status).toBe("active");
163
+ });
164
+ });
165
+
166
+ describe("task tool: update_prompt", () => {
167
+ test("changes prompt text", async () => {
168
+ const t0 = Date.parse("2026-05-14T12:00:00Z");
169
+ const { run } = makeTool({ now: () => t0 });
170
+ const created = await run({
171
+ action: "create",
172
+ prompt: "old",
173
+ schedule: { type: "interval", every: "1h" },
174
+ });
175
+ const id = (created.details as ScheduledTask).id;
176
+ const updated = await run({ action: "update_prompt", id, prompt: "new" });
177
+ expect((updated.details as ScheduledTask).prompt).toBe("new");
178
+ });
179
+ });
@@ -0,0 +1,159 @@
1
+ import {
2
+ defineTool,
3
+ type AgentToolResult,
4
+ type ToolDefinition,
5
+ } from "@earendil-works/pi-coding-agent";
6
+
7
+ import type { SessionId } from "./Session";
8
+ import type { TaskScheduler } from "./TaskScheduler";
9
+ import { taskToolSchema, type TaskToolInput } from "./TaskSchema";
10
+
11
+ export type TaskToolDeps = {
12
+ readonly scheduler: TaskScheduler;
13
+ readonly sessionId: SessionId;
14
+ };
15
+
16
+ export class TaskTool {
17
+ public static build(deps: TaskToolDeps): ToolDefinition {
18
+ return defineTool({
19
+ name: "task",
20
+ label: "task",
21
+ description:
22
+ "Manage scheduled/recurring tasks for this Telegram chat/thread.",
23
+ parameters: taskToolSchema,
24
+ async execute(_id, params) {
25
+ const input = params as TaskToolInput;
26
+ switch (input.action) {
27
+ case "create":
28
+ return await TaskTool.create(deps, input);
29
+ case "list":
30
+ return await TaskTool.list(deps);
31
+ case "delete":
32
+ return await TaskTool.delete(deps, input);
33
+ case "pause":
34
+ case "resume":
35
+ return await TaskTool.setStatus(deps, input);
36
+ case "update_prompt":
37
+ return await TaskTool.updatePrompt(deps, input);
38
+ }
39
+ },
40
+ });
41
+ }
42
+
43
+ private static async create(
44
+ deps: TaskToolDeps,
45
+ input: TaskToolInput
46
+ ): Promise<AgentToolResult<unknown>> {
47
+ if (!input.prompt) {
48
+ throw new Error("'prompt' is required for action=create");
49
+ }
50
+ if (!input.schedule) {
51
+ throw new Error("'schedule' is required for action=create");
52
+ }
53
+ const task = await deps.scheduler.create(deps.sessionId, {
54
+ prompt: input.prompt,
55
+ schedule: input.schedule,
56
+ expires: input.expires,
57
+ isolatedSession: input.isolatedSession,
58
+ });
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text" as const,
63
+ text: `Created task ${task.id}. Next run: ${task.nextRun}.`,
64
+ },
65
+ ],
66
+ details: task,
67
+ };
68
+ }
69
+
70
+ private static async list(
71
+ deps: TaskToolDeps
72
+ ): Promise<AgentToolResult<unknown>> {
73
+ const tasks = await deps.scheduler.list(deps.sessionId);
74
+ if (tasks.length === 0) {
75
+ return {
76
+ content: [
77
+ {
78
+ type: "text" as const,
79
+ text: "No tasks scheduled for this thread.",
80
+ },
81
+ ],
82
+ details: { tasks: [] },
83
+ };
84
+ }
85
+ return {
86
+ content: [{ type: "text" as const, text: JSON.stringify(tasks) }],
87
+ details: { tasks },
88
+ };
89
+ }
90
+
91
+ private static async delete(
92
+ deps: TaskToolDeps,
93
+ input: TaskToolInput
94
+ ): Promise<AgentToolResult<unknown>> {
95
+ if (!input.id) {
96
+ throw new Error("'id' is required for action=delete");
97
+ }
98
+ const ok = await deps.scheduler.delete(deps.sessionId, input.id);
99
+ if (!ok) {
100
+ throw new Error(`no task with id=${input.id} in this thread`);
101
+ }
102
+ return {
103
+ content: [{ type: "text" as const, text: `Deleted task ${input.id}.` }],
104
+ details: { id: input.id },
105
+ };
106
+ }
107
+
108
+ private static async setStatus(
109
+ deps: TaskToolDeps,
110
+ input: TaskToolInput
111
+ ): Promise<AgentToolResult<unknown>> {
112
+ if (!input.id) {
113
+ throw new Error(`'id' is required for action=${input.action}`);
114
+ }
115
+ const target = input.action === "pause" ? "paused" : "active";
116
+ const task = await deps.scheduler.setStatus(
117
+ deps.sessionId,
118
+ input.id,
119
+ target
120
+ );
121
+ if (!task) {
122
+ throw new Error(`no task with id=${input.id} in this thread`);
123
+ }
124
+ const text =
125
+ input.action === "pause"
126
+ ? `Paused task ${task.id}. Will not fire until resumed.`
127
+ : `Resumed task ${task.id}. Next run: ${task.nextRun}.`;
128
+ return {
129
+ content: [{ type: "text" as const, text }],
130
+ details: task,
131
+ };
132
+ }
133
+
134
+ private static async updatePrompt(
135
+ deps: TaskToolDeps,
136
+ input: TaskToolInput
137
+ ): Promise<AgentToolResult<unknown>> {
138
+ if (!input.id) {
139
+ throw new Error("'id' is required for action=update_prompt");
140
+ }
141
+ if (!input.prompt) {
142
+ throw new Error("'prompt' is required for action=update_prompt");
143
+ }
144
+ const task = await deps.scheduler.updatePrompt(
145
+ deps.sessionId,
146
+ input.id,
147
+ input.prompt
148
+ );
149
+ if (!task) {
150
+ throw new Error(`no task with id=${input.id} in this thread`);
151
+ }
152
+ return {
153
+ content: [
154
+ { type: "text" as const, text: `Updated prompt for task ${task.id}.` },
155
+ ],
156
+ details: task,
157
+ };
158
+ }
159
+ }
@@ -0,0 +1,43 @@
1
+ import type { Api } from "grammy";
2
+
3
+ import type { SessionId } from "./Session";
4
+
5
+ export class TypingIndicator {
6
+ private readonly api: Api;
7
+ private readonly sessionId: SessionId;
8
+ private timer: Timer | undefined;
9
+ private stopped = true;
10
+
11
+ public constructor(api: Api, sessionId: SessionId) {
12
+ this.api = api;
13
+ this.sessionId = sessionId;
14
+ }
15
+
16
+ public start(): void {
17
+ if (this.timer) {
18
+ return;
19
+ }
20
+ this.stopped = false;
21
+ void this.send();
22
+ this.timer = setInterval(() => void this.send(), 4_000);
23
+ }
24
+
25
+ public stop(): void {
26
+ this.stopped = true;
27
+ if (this.timer) {
28
+ clearInterval(this.timer);
29
+ this.timer = undefined;
30
+ }
31
+ }
32
+
33
+ private async send(): Promise<void> {
34
+ if (this.stopped) {
35
+ return;
36
+ }
37
+ await this.api
38
+ .sendChatAction(this.sessionId.chatId, "typing", {
39
+ message_thread_id: this.sessionId.threadId,
40
+ })
41
+ .catch(() => {});
42
+ }
43
+ }
@@ -0,0 +1,32 @@
1
+ import { Bot } from "./Bot";
2
+ import { Config } from "./Config";
3
+
4
+ export async function start(args: ReadonlyArray<string>): Promise<void> {
5
+ const cli = Config.parseArgs(args);
6
+ const config = await Config.load(cli);
7
+
8
+ if (cli.printConfig) {
9
+ console.log(
10
+ JSON.stringify(
11
+ { ...config, token: config.token ? "***" : undefined },
12
+ null,
13
+ 2
14
+ )
15
+ );
16
+ return;
17
+ }
18
+
19
+ const bot = new Bot(config);
20
+ let stopped = false;
21
+ const stop = (): void => {
22
+ if (stopped) {
23
+ return;
24
+ }
25
+ stopped = true;
26
+ void bot.stop();
27
+ };
28
+ process.once("SIGINT", stop);
29
+ process.once("SIGTERM", stop);
30
+
31
+ await bot.start();
32
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "pim-dark",
4
+ "vars": {
5
+ "black": 0,
6
+ "red": 1,
7
+ "green": 2,
8
+ "yellow": 3,
9
+ "blue": 4,
10
+ "magenta": 5,
11
+ "cyan": 6,
12
+ "white": 7,
13
+ "gray": 8,
14
+ "brightRed": 9,
15
+ "brightGreen": 10,
16
+ "brightYellow": 11,
17
+ "brightBlue": 12,
18
+ "brightMagenta": 13,
19
+ "brightCyan": 14,
20
+ "brightWhite": 15,
21
+
22
+ "panelBg": "#343541"
23
+ },
24
+ "colors": {
25
+ "accent": "cyan",
26
+ "border": "gray",
27
+ "borderAccent": "cyan",
28
+ "borderMuted": "gray",
29
+ "success": "green",
30
+ "error": "red",
31
+ "warning": "yellow",
32
+ "muted": "gray",
33
+ "dim": "gray",
34
+ "text": "white",
35
+ "thinkingText": "gray",
36
+
37
+ "selectedBg": "panelBg",
38
+ "userMessageBg": "panelBg",
39
+ "userMessageText": "",
40
+ "customMessageBg": "panelBg",
41
+ "customMessageText": "",
42
+ "customMessageLabel": "magenta",
43
+
44
+ "toolPendingBg": "",
45
+ "toolSuccessBg": "",
46
+ "toolErrorBg": "",
47
+ "toolTitle": "",
48
+ "toolOutput": "gray",
49
+
50
+ "mdHeading": "brightBlue",
51
+ "mdLink": "",
52
+ "mdLinkUrl": "brightCyan",
53
+ "mdCode": "brightMagenta",
54
+ "mdCodeBlock": "",
55
+ "mdCodeBlockBorder": "gray",
56
+ "mdQuote": "brightYellow",
57
+ "mdQuoteBorder": "brightYellow",
58
+ "mdHr": "gray",
59
+ "mdListBullet": "gray",
60
+
61
+ "toolDiffAdded": "green",
62
+ "toolDiffRemoved": "red",
63
+ "toolDiffContext": "gray",
64
+
65
+ "syntaxComment": "gray",
66
+ "syntaxKeyword": "magenta",
67
+ "syntaxFunction": "green",
68
+ "syntaxVariable": "magenta",
69
+ "syntaxString": "yellow",
70
+ "syntaxNumber": "blue",
71
+ "syntaxType": "cyan",
72
+ "syntaxOperator": "",
73
+ "syntaxPunctuation": "",
74
+
75
+ "thinkingOff": "gray",
76
+ "thinkingMinimal": "gray",
77
+ "thinkingLow": "gray",
78
+ "thinkingMedium": "gray",
79
+ "thinkingHigh": "gray",
80
+ "thinkingXhigh": "gray",
81
+
82
+ "bashMode": "yellow"
83
+ }
84
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/earendil-works/pi/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
3
+ "name": "pim-light",
4
+ "vars": {
5
+ "black": 0,
6
+ "red": 1,
7
+ "green": 2,
8
+ "yellow": 3,
9
+ "blue": 4,
10
+ "magenta": 5,
11
+ "cyan": 6,
12
+ "white": 7,
13
+ "gray": 8,
14
+ "brightRed": 9,
15
+ "brightGreen": 10,
16
+ "brightYellow": 11,
17
+ "brightBlue": 12,
18
+ "brightMagenta": 13,
19
+ "brightCyan": 14,
20
+ "brightWhite": 15,
21
+
22
+ "panelBg": "#e8e8e8"
23
+ },
24
+ "colors": {
25
+ "accent": "blue",
26
+ "border": "gray",
27
+ "borderAccent": "blue",
28
+ "borderMuted": "gray",
29
+ "success": "green",
30
+ "error": "red",
31
+ "warning": "yellow",
32
+ "muted": "gray",
33
+ "dim": "gray",
34
+ "text": "black",
35
+ "thinkingText": "gray",
36
+
37
+ "selectedBg": "panelBg",
38
+ "userMessageBg": "panelBg",
39
+ "userMessageText": "",
40
+ "customMessageBg": "panelBg",
41
+ "customMessageText": "",
42
+ "customMessageLabel": "magenta",
43
+
44
+ "toolPendingBg": "",
45
+ "toolSuccessBg": "",
46
+ "toolErrorBg": "",
47
+ "toolTitle": "",
48
+ "toolOutput": "gray",
49
+
50
+ "mdHeading": "blue",
51
+ "mdLink": "",
52
+ "mdLinkUrl": "cyan",
53
+ "mdCode": "magenta",
54
+ "mdCodeBlock": "",
55
+ "mdCodeBlockBorder": "gray",
56
+ "mdQuote": "yellow",
57
+ "mdQuoteBorder": "yellow",
58
+ "mdHr": "gray",
59
+ "mdListBullet": "gray",
60
+
61
+ "toolDiffAdded": "green",
62
+ "toolDiffRemoved": "red",
63
+ "toolDiffContext": "gray",
64
+
65
+ "syntaxComment": "gray",
66
+ "syntaxKeyword": "magenta",
67
+ "syntaxFunction": "green",
68
+ "syntaxVariable": "magenta",
69
+ "syntaxString": "yellow",
70
+ "syntaxNumber": "blue",
71
+ "syntaxType": "cyan",
72
+ "syntaxOperator": "",
73
+ "syntaxPunctuation": "",
74
+
75
+ "thinkingOff": "gray",
76
+ "thinkingMinimal": "gray",
77
+ "thinkingLow": "gray",
78
+ "thinkingMedium": "gray",
79
+ "thinkingHigh": "gray",
80
+ "thinkingXhigh": "gray",
81
+
82
+ "bashMode": "yellow"
83
+ }
84
+ }