jerob 1.0.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 (69) hide show
  1. package/CLI/cli.ts +42 -0
  2. package/README.md +137 -0
  3. package/SETUP.md +584 -0
  4. package/agent/action-tracker.ts +45 -0
  5. package/agent/agent-tools.ts +111 -0
  6. package/agent/approval.ts +137 -0
  7. package/agent/diff-view.ts +26 -0
  8. package/agent/orchestrator.ts +186 -0
  9. package/agent/tool-executor.ts +463 -0
  10. package/agent/types.ts +69 -0
  11. package/ask/orchestrator.ts +244 -0
  12. package/auth/auth.ts +567 -0
  13. package/auth/config-store.ts +77 -0
  14. package/auth/crypto.ts +51 -0
  15. package/auth/env-writer.ts +82 -0
  16. package/bin/jerob.js +28 -0
  17. package/config/ai.config.ts +163 -0
  18. package/email_ops/email-tools.ts +178 -0
  19. package/email_ops/email_functions.ts +443 -0
  20. package/email_ops/email_init.ts +92 -0
  21. package/email_ops/email_pass_store.ts +61 -0
  22. package/email_ops/email_server.ts +29 -0
  23. package/email_ops/types.ts +88 -0
  24. package/index.ts +176 -0
  25. package/package.json +88 -0
  26. package/plan/browser-agent/README.md +118 -0
  27. package/plan/browser-agent/USAGE.md +308 -0
  28. package/plan/browser-agent/evaluator.ts +353 -0
  29. package/plan/browser-agent/executor.ts +372 -0
  30. package/plan/browser-agent/index.ts +13 -0
  31. package/plan/browser-agent/orchestrator.ts +323 -0
  32. package/plan/browser-agent/planner.ts +200 -0
  33. package/plan/browser-agent/types.ts +62 -0
  34. package/plan/browser-tool.ts +128 -0
  35. package/plan/index.ts +12 -0
  36. package/plan/orchestrator.ts +214 -0
  37. package/plan/planner.ts +183 -0
  38. package/plan/selection.ts +50 -0
  39. package/plan/types.ts +13 -0
  40. package/plan/web-tools.ts +119 -0
  41. package/scheduler/ARCHITECTURE.md +263 -0
  42. package/scheduler/README.md +200 -0
  43. package/scheduler/SETUP-READY.sql +84 -0
  44. package/scheduler/check-status.sql +124 -0
  45. package/scheduler/config-sync.ts +91 -0
  46. package/scheduler/db-migrate.ts +271 -0
  47. package/scheduler/db.ts +162 -0
  48. package/scheduler/debug.ts +184 -0
  49. package/scheduler/orchestrator.ts +438 -0
  50. package/scheduler/planner.ts +170 -0
  51. package/scheduler/update-task-email.ts +70 -0
  52. package/supabase/.temp/cli-latest +1 -0
  53. package/supabase/.temp/gotrue-version +1 -0
  54. package/supabase/.temp/linked-project.json +1 -0
  55. package/supabase/.temp/pooler-url +1 -0
  56. package/supabase/.temp/postgres-version +1 -0
  57. package/supabase/.temp/project-ref +1 -0
  58. package/supabase/.temp/rest-version +1 -0
  59. package/supabase/.temp/storage-migration +1 -0
  60. package/supabase/.temp/storage-version +1 -0
  61. package/supabase/deploy.ps1 +50 -0
  62. package/supabase/functions/scheduler-tick/index.ts +496 -0
  63. package/supabase/supabase/.temp/linked-project.json +1 -0
  64. package/tsconfig.json +33 -0
  65. package/tui/spinner.ts +33 -0
  66. package/tui/spinup.ts +67 -0
  67. package/tui/terminal-render.ts +16 -0
  68. package/utils/llm-error.ts +185 -0
  69. package/utils/model-validator.ts +247 -0
@@ -0,0 +1,244 @@
1
+ import chalk from "chalk";
2
+ import { confirm, isCancel, select, text } from "@clack/prompts";
3
+ import { ToolLoopAgent, stepCountIs, tool } from "ai";
4
+ import { z } from "zod";
5
+ import { ActionTracker } from "../agent/action-tracker.ts";
6
+ import { ToolExecutor } from "../agent/tool-executor.ts";
7
+ import { defaultAgentConfig } from "../agent/types.ts";
8
+ import { runApprovalFlow } from "../agent/approval.ts";
9
+ import { renderHTMLMarkdown } from "../tui/terminal-render.ts";
10
+ import { getAgentModel } from "../config/ai.config.ts";
11
+ import { createWebTools } from "../plan/web-tools.ts";
12
+ import { withSpinner } from "../tui/spinner";
13
+ import { createEmailTools } from "../email_ops/email-tools";
14
+ import { sendMail } from "../email_ops/email_functions";
15
+ import { withLLMRetry, printLLMError } from "../utils/llm-error";
16
+
17
+ function createAskTools(executor: ToolExecutor) {
18
+ return {
19
+ read_file: tool({
20
+ description:
21
+ "Read a text file from the workspace. Use a path relative to the project root.",
22
+ inputSchema: z.object({
23
+ path: z.string().describe("Relative file path"),
24
+ }),
25
+ execute: async ({ path: p }) => executor.readFile(p),
26
+ }),
27
+
28
+ list_files: tool({
29
+ description: "List files and directories under a path.",
30
+ inputSchema: z.object({
31
+ path: z.string(),
32
+ recursive: z.boolean().optional().default(false),
33
+ }),
34
+ execute: async ({ path: p, recursive }) =>
35
+ executor.listFiles(p, recursive),
36
+ }),
37
+
38
+ search_files: tool({
39
+ description:
40
+ 'Find files matching a glob pattern (e.g. "*.ts", "**/*.md"). Optional content substring filter.',
41
+ inputSchema: z.object({
42
+ root: z.string().describe("Directory to search, relative to root"),
43
+ pattern: z
44
+ .string()
45
+ .describe("Glob-like pattern using * and ** (forward slashes)"),
46
+ content_contains: z.string().optional(),
47
+ }),
48
+ execute: async ({ root, pattern, content_contains }) =>
49
+ executor.searchFiles(root, pattern, content_contains),
50
+ }),
51
+
52
+ analyze_codebase: tool({
53
+ description:
54
+ "Summarize structure: file counts, size, extensions. Read-only.",
55
+ inputSchema: z.object({
56
+ path: z.string().default("."),
57
+ }),
58
+ execute: async ({ path: p }) => executor.analyzeCodebase(p),
59
+ }),
60
+
61
+ list_skills: tool({
62
+ description:
63
+ "List absolute paths to SKILL.md files under configured skill directories (Cursor / Claude).",
64
+ inputSchema: z.object({}),
65
+ execute: async () => executor.listSkills(),
66
+ }),
67
+
68
+ read_skill: tool({
69
+ description:
70
+ "Read a SKILL.md file. Path must be absolute and under skill roots, or use a path returned by list_skills.",
71
+ inputSchema: z.object({
72
+ path: z.string(),
73
+ }),
74
+ execute: async ({ path: p }) => executor.readSkill(p),
75
+ }),
76
+ };
77
+ }
78
+
79
+ function asMd(question: string, answer: string): string {
80
+ return `# Ask Mode\n\n## Question\n\n${question.trim()}\n\n## Answer\n\n${answer.trim()}\n`;
81
+ }
82
+
83
+ export async function runAskMode() {
84
+ console.log(chalk.bold("\n❓ Ask Mode\n"));
85
+
86
+ const config = defaultAgentConfig();
87
+ config.tools.allowFileCreation = true;
88
+ config.tools.allowFileModification = false;
89
+ config.tools.allowFolderCreation = false;
90
+ config.tools.allowShellExecution = false;
91
+
92
+ const tracker = new ActionTracker();
93
+ const executor = new ToolExecutor(tracker, config);
94
+
95
+ const tools = {
96
+ ...createAskTools(executor),
97
+ ...createWebTools(tracker),
98
+ ...createEmailTools(),
99
+ };
100
+
101
+ const agent = new ToolLoopAgent({
102
+ model: getAgentModel(),
103
+ stopWhen: stepCountIs(20),
104
+ tools,
105
+ });
106
+
107
+ const history: { question: string; answer: string }[] = [];
108
+ let shouldSaveSummary = false;
109
+
110
+ while (true) {
111
+ const question = await text({ message: "What do you want to ask?" });
112
+ if (isCancel(question) || !question.trim()) break;
113
+
114
+ const context = history
115
+ .map((item) => `User: ${item.question}\nAssistant: ${item.answer}`)
116
+ .join("\n\n");
117
+
118
+ const prompt = context
119
+ ? `Conversation so far:\n${context}\n\nNew question:\n${question.trim()}`
120
+ : question.trim();
121
+
122
+ let result;
123
+ try {
124
+ result = await withSpinner("Thinking…", async () =>
125
+ withLLMRetry(
126
+ () =>
127
+ agent.generate({
128
+ prompt,
129
+ onStepFinish: ({ toolCalls }) => {
130
+ for (const tc of toolCalls) {
131
+ const preview = JSON.stringify(tc.input).slice(0, 160);
132
+ console.log(
133
+ chalk.green(" ✓"),
134
+ chalk.bold(String(tc.toolName)),
135
+ chalk.dim(preview + (preview.length >= 160 ? "..." : "")),
136
+ );
137
+ }
138
+ },
139
+ }),
140
+ { maxRetries: 3, context: "Ask" }
141
+ ),
142
+ );
143
+ } catch (error) {
144
+ printLLMError(error, "Ask");
145
+ continue;
146
+ }
147
+
148
+ const answer = result.text?.trim() || "(no answer)";
149
+ history.push({ question: question.trim(), answer });
150
+ console.log("\n" + renderHTMLMarkdown(answer) + "\n");
151
+
152
+ const actions = tracker.getActions();
153
+ if (actions.length) {
154
+ console.log(chalk.bold('\nTool usage summary:'));
155
+ for (const a of actions) {
156
+ const details = a.details && (a.details.after ?? a.details.before ?? JSON.stringify(a.details));
157
+ const preview = typeof details === 'string' ? details.slice(0, 200) : String(details);
158
+ console.log(
159
+ ` - ${a.type} ${a.path ?? ''} (${a.status}) ${preview ? '- ' + preview.replace(/\n/g, ' ') : ''}`,
160
+ );
161
+ }
162
+ console.log();
163
+ }
164
+
165
+ const next = await select({
166
+ message: 'What next?',
167
+ options: [
168
+ { value: 'continue', label: 'Ask another question' },
169
+ { value: 'email', label: 'Send this answer to my email' },
170
+ { value: 'save', label: 'Save important summary and exit' },
171
+ { value: 'exit', label: 'Exit without saving' },
172
+ ],
173
+ });
174
+
175
+ if (isCancel(next) || next === 'exit') break;
176
+ if (next === 'email') {
177
+ const emailTo = await text({ message: 'Send to (email address)?' });
178
+ if (!isCancel(emailTo) && emailTo?.trim()) {
179
+ try {
180
+ await withSpinner('Sending email…', async () =>
181
+ sendMail({
182
+ to: emailTo.trim(),
183
+ subject: `Ask Mode: ${(question as string).trim().slice(0, 60)}`,
184
+ body: answer,
185
+ })
186
+ );
187
+ console.log(chalk.green('✓ Email sent\n'));
188
+ } catch (err) {
189
+ console.log(chalk.red(`✖ Email failed: ${err instanceof Error ? err.message : String(err)}\n`));
190
+ }
191
+ }
192
+ continue;
193
+ }
194
+ if (next === 'save') {
195
+ shouldSaveSummary = true;
196
+ break;
197
+ }
198
+ }
199
+
200
+ if (history.length === 0 || !shouldSaveSummary) return;
201
+
202
+ const summaryPrompt = `Summarize the most important points from this conversation. Keep it concise and save only the key facts or actions. Conversation:\n\n${history
203
+ .map((item) => `User: ${item.question}\nAssistant: ${item.answer}`)
204
+ .join("\n\n")}`;
205
+
206
+ let summaryResult;
207
+ try {
208
+ summaryResult = await withSpinner("Summarizing…", async () =>
209
+ withLLMRetry(
210
+ () => agent.generate({ prompt: summaryPrompt }),
211
+ { maxRetries: 2, context: "Ask summary" }
212
+ ),
213
+ );
214
+ } catch (err) {
215
+ printLLMError(err, "Ask summary");
216
+ return;
217
+ }
218
+ const summary = summaryResult.text?.trim() || history.map((item) => `- ${item.answer}`).join("\n");
219
+
220
+ const filename = await text({
221
+ message: 'Filename',
222
+ initialValue: 'ask-summary.md',
223
+ validate: (v) => {
224
+ const s = (v ?? '').trim();
225
+ if (!s) return 'Required';
226
+ if (s.includes('..') || s.includes('/') || s.includes('\\')) return 'No paths';
227
+ if (!s.toLowerCase().endsWith('.md')) return 'Must end with .md';
228
+ },
229
+ });
230
+ if (isCancel(filename)) return;
231
+
232
+ const content = `# Ask Mode Summary\n\n## Important Information\n\n${summary}\n`;
233
+ executor.createFile(filename, content);
234
+
235
+ const ok = await runApprovalFlow(tracker);
236
+ if (!ok) return executor.clearStaging();
237
+
238
+ const { errors, newFiles } = executor.applyApprovedFromTracker();
239
+ if (errors && errors.length) {
240
+ console.log(chalk.red('\nSome operations reported errors:\n'));
241
+ for (const e of errors) console.log(chalk.red(` • ${e}`));
242
+ }
243
+ executor.clearStaging();
244
+ }