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.
- package/CLI/cli.ts +42 -0
- package/README.md +137 -0
- package/SETUP.md +584 -0
- package/agent/action-tracker.ts +45 -0
- package/agent/agent-tools.ts +111 -0
- package/agent/approval.ts +137 -0
- package/agent/diff-view.ts +26 -0
- package/agent/orchestrator.ts +186 -0
- package/agent/tool-executor.ts +463 -0
- package/agent/types.ts +69 -0
- package/ask/orchestrator.ts +244 -0
- package/auth/auth.ts +567 -0
- package/auth/config-store.ts +77 -0
- package/auth/crypto.ts +51 -0
- package/auth/env-writer.ts +82 -0
- package/bin/jerob.js +28 -0
- package/config/ai.config.ts +163 -0
- package/email_ops/email-tools.ts +178 -0
- package/email_ops/email_functions.ts +443 -0
- package/email_ops/email_init.ts +92 -0
- package/email_ops/email_pass_store.ts +61 -0
- package/email_ops/email_server.ts +29 -0
- package/email_ops/types.ts +88 -0
- package/index.ts +176 -0
- package/package.json +88 -0
- package/plan/browser-agent/README.md +118 -0
- package/plan/browser-agent/USAGE.md +308 -0
- package/plan/browser-agent/evaluator.ts +353 -0
- package/plan/browser-agent/executor.ts +372 -0
- package/plan/browser-agent/index.ts +13 -0
- package/plan/browser-agent/orchestrator.ts +323 -0
- package/plan/browser-agent/planner.ts +200 -0
- package/plan/browser-agent/types.ts +62 -0
- package/plan/browser-tool.ts +128 -0
- package/plan/index.ts +12 -0
- package/plan/orchestrator.ts +214 -0
- package/plan/planner.ts +183 -0
- package/plan/selection.ts +50 -0
- package/plan/types.ts +13 -0
- package/plan/web-tools.ts +119 -0
- package/scheduler/ARCHITECTURE.md +263 -0
- package/scheduler/README.md +200 -0
- package/scheduler/SETUP-READY.sql +84 -0
- package/scheduler/check-status.sql +124 -0
- package/scheduler/config-sync.ts +91 -0
- package/scheduler/db-migrate.ts +271 -0
- package/scheduler/db.ts +162 -0
- package/scheduler/debug.ts +184 -0
- package/scheduler/orchestrator.ts +438 -0
- package/scheduler/planner.ts +170 -0
- package/scheduler/update-task-email.ts +70 -0
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/gotrue-version +1 -0
- package/supabase/.temp/linked-project.json +1 -0
- package/supabase/.temp/pooler-url +1 -0
- package/supabase/.temp/postgres-version +1 -0
- package/supabase/.temp/project-ref +1 -0
- package/supabase/.temp/rest-version +1 -0
- package/supabase/.temp/storage-migration +1 -0
- package/supabase/.temp/storage-version +1 -0
- package/supabase/deploy.ps1 +50 -0
- package/supabase/functions/scheduler-tick/index.ts +496 -0
- package/supabase/supabase/.temp/linked-project.json +1 -0
- package/tsconfig.json +33 -0
- package/tui/spinner.ts +33 -0
- package/tui/spinup.ts +67 -0
- package/tui/terminal-render.ts +16 -0
- package/utils/llm-error.ts +185 -0
- 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
|
+
}
|