omnikey-cli 1.0.27 → 1.0.29
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/README.md +116 -17
- package/backend-dist/agent/agentPrompts.js +6 -1
- package/backend-dist/agent/agentServer.js +52 -18
- package/backend-dist/agent/imageTool.js +167 -0
- package/backend-dist/agent/utils.js +13 -1
- package/backend-dist/ai-client.js +79 -0
- package/backend-dist/bucket-adapter/index.js +59 -0
- package/backend-dist/config.js +11 -0
- package/backend-dist/index.js +26 -5
- package/backend-dist/models/scheduledJob.js +97 -0
- package/backend-dist/scheduledJobExecutor.js +199 -0
- package/backend-dist/scheduledJobRoutes.js +186 -0
- package/backend-dist/web-search/browser-playwright.js +191 -80
- package/backend-dist/web-search/web-search-provider.js +15 -7
- package/dist/grantBrowserAccess.js +789 -0
- package/dist/index.js +35 -0
- package/dist/onboard.js +7 -6
- package/dist/scheduleJob.js +268 -0
- package/package.json +8 -7
- package/src/grantBrowserAccess.ts +936 -0
- package/src/index.ts +44 -0
- package/src/onboard.ts +8 -6
- package/src/scheduleJob.ts +309 -0
package/src/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ import { statusCmd } from './status';
|
|
|
9
9
|
import { showLogs } from './showLogs';
|
|
10
10
|
import { showConfig } from './showConfig';
|
|
11
11
|
import { setConfig } from './setConfig';
|
|
12
|
+
import { grantBrowserAccess, reopenBrowserDebugProfile } from './grantBrowserAccess';
|
|
13
|
+
import { scheduleAdd, scheduleList, scheduleRemove, scheduleRunNow } from './scheduleJob';
|
|
12
14
|
|
|
13
15
|
const program = new Command();
|
|
14
16
|
|
|
@@ -90,4 +92,46 @@ program
|
|
|
90
92
|
await startDaemon(port);
|
|
91
93
|
});
|
|
92
94
|
|
|
95
|
+
program
|
|
96
|
+
.command('grant-browser-access')
|
|
97
|
+
.description(
|
|
98
|
+
'Set up authenticated browser tab access for web fetch. ' +
|
|
99
|
+
'Detects installed browsers, selects a profile, and configures a remote debugging port (CDP). ' +
|
|
100
|
+
'On macOS you can also choose AppleScript mode instead.',
|
|
101
|
+
)
|
|
102
|
+
.action(async () => {
|
|
103
|
+
await grantBrowserAccess();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
program
|
|
107
|
+
.command('browser open')
|
|
108
|
+
.description('Reopen the browser with the Omnikey debug profile')
|
|
109
|
+
.action(async () => {
|
|
110
|
+
await reopenBrowserDebugProfile();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const scheduleCmd = program
|
|
114
|
+
.command('schedule')
|
|
115
|
+
.description('Manage scheduled prompt jobs');
|
|
116
|
+
|
|
117
|
+
scheduleCmd
|
|
118
|
+
.command('add')
|
|
119
|
+
.description('Add a new scheduled job')
|
|
120
|
+
.action(async () => { await scheduleAdd(); });
|
|
121
|
+
|
|
122
|
+
scheduleCmd
|
|
123
|
+
.command('list')
|
|
124
|
+
.description('List all scheduled jobs')
|
|
125
|
+
.action(async () => { await scheduleList(); });
|
|
126
|
+
|
|
127
|
+
scheduleCmd
|
|
128
|
+
.command('remove')
|
|
129
|
+
.description('Remove a scheduled job')
|
|
130
|
+
.action(async () => { await scheduleRemove(); });
|
|
131
|
+
|
|
132
|
+
scheduleCmd
|
|
133
|
+
.command('run-now <id>')
|
|
134
|
+
.description('Immediately run a scheduled job by ID')
|
|
135
|
+
.action(async (id: string) => { await scheduleRunNow(id); });
|
|
136
|
+
|
|
93
137
|
program.parseAsync(process.argv);
|
package/src/onboard.ts
CHANGED
|
@@ -10,11 +10,12 @@ const AI_PROVIDERS = [
|
|
|
10
10
|
];
|
|
11
11
|
|
|
12
12
|
const SEARCH_PROVIDERS = [
|
|
13
|
-
{ name: 'Skip
|
|
14
|
-
{ name: '
|
|
15
|
-
{ name: '
|
|
16
|
-
{ name: '
|
|
17
|
-
{ name: '
|
|
13
|
+
{ name: 'Skip', value: 'skip' },
|
|
14
|
+
{ name: 'DuckDuckGo', value: 'duckduckgo' },
|
|
15
|
+
{ name: 'Serper', value: 'serper' },
|
|
16
|
+
{ name: 'Brave Search', value: 'brave' },
|
|
17
|
+
{ name: 'Tavily', value: 'tavily' },
|
|
18
|
+
{ name: 'SearXNG', value: 'searxng' },
|
|
18
19
|
];
|
|
19
20
|
|
|
20
21
|
const AI_PROVIDER_KEY_ENV: Record<string, string> = {
|
|
@@ -61,7 +62,8 @@ export async function onboard() {
|
|
|
61
62
|
{
|
|
62
63
|
type: 'list',
|
|
63
64
|
name: 'provider',
|
|
64
|
-
message:
|
|
65
|
+
message:
|
|
66
|
+
'Select a web search provider for the AI agent. Supported providers: DuckDuckGo, Serper, Brave Search, Tavily, SearXNG:',
|
|
65
67
|
choices: SEARCH_PROVIDERS,
|
|
66
68
|
default: 'skip',
|
|
67
69
|
},
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { readConfig, getPort } from './utils';
|
|
5
|
+
|
|
6
|
+
interface ScheduledJobDto {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
cronExpression?: string | null;
|
|
11
|
+
runAt?: string | null;
|
|
12
|
+
isActive: boolean;
|
|
13
|
+
lastRunAt?: string | null;
|
|
14
|
+
nextRunAt?: string | null;
|
|
15
|
+
sessionId?: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getJwt(): Promise<string> {
|
|
19
|
+
const config = readConfig();
|
|
20
|
+
const port = getPort();
|
|
21
|
+
const licenseKey = config.OMNIKEY_LICENSE_KEY || '';
|
|
22
|
+
const baseUrl = `http://localhost:${port}`;
|
|
23
|
+
const res = await axios.post(
|
|
24
|
+
`${baseUrl}/api/subscription/activate`,
|
|
25
|
+
{ licenseKey },
|
|
26
|
+
{
|
|
27
|
+
timeout: 10_000,
|
|
28
|
+
},
|
|
29
|
+
);
|
|
30
|
+
return (res.data as { token: string }).token;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getBaseUrl(): string {
|
|
34
|
+
const port = getPort();
|
|
35
|
+
return `http://localhost:${port}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function authHeaders(): Promise<Record<string, string>> {
|
|
39
|
+
try {
|
|
40
|
+
const token = await getJwt();
|
|
41
|
+
return { Authorization: `Bearer ${token}` };
|
|
42
|
+
} catch {
|
|
43
|
+
// Self-hosted: try without auth
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readMultilinePrompt(): Promise<string> {
|
|
49
|
+
console.log('\nEnter prompt text below.');
|
|
50
|
+
console.log('Type END on a new line when finished.\n');
|
|
51
|
+
|
|
52
|
+
const rl = readline.createInterface({
|
|
53
|
+
input: process.stdin,
|
|
54
|
+
output: process.stdout,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const lines: string[] = [];
|
|
58
|
+
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const askLine = () => {
|
|
61
|
+
rl.question('', (line) => {
|
|
62
|
+
if (line.trim() === 'END') {
|
|
63
|
+
rl.close();
|
|
64
|
+
resolve(lines.join('\n').trim());
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
lines.push(line);
|
|
69
|
+
askLine();
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
askLine();
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function scheduleAdd(): Promise<void> {
|
|
78
|
+
const answers = await inquirer.prompt([
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'label',
|
|
82
|
+
message: 'Job label (e.g. "Daily standup summary"):',
|
|
83
|
+
validate: (v: string) => v.trim().length > 0 || 'Label is required',
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'scheduleType',
|
|
88
|
+
message: 'Schedule type:',
|
|
89
|
+
choices: [
|
|
90
|
+
{ name: 'Recurring (cron expression)', value: 'cron' },
|
|
91
|
+
{ name: 'One-time (specific date/time)', value: 'once' },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const promptText = await readMultilinePrompt();
|
|
97
|
+
if (!promptText) {
|
|
98
|
+
console.error('Prompt is required.');
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let cronExpression: string | undefined;
|
|
103
|
+
let runAt: string | undefined;
|
|
104
|
+
|
|
105
|
+
if (answers.scheduleType === 'cron') {
|
|
106
|
+
const presets = [
|
|
107
|
+
{ name: 'Every weekday at 9 AM (0 9 * * 1-5)', value: '0 9 * * 1-5' },
|
|
108
|
+
{ name: 'Every day at midnight (0 0 * * *)', value: '0 0 * * *' },
|
|
109
|
+
{ name: 'Every hour (0 * * * *)', value: '0 * * * *' },
|
|
110
|
+
{ name: 'Every Monday at 8 AM (0 8 * * 1)', value: '0 8 * * 1' },
|
|
111
|
+
{ name: 'Custom cron expression', value: '__custom__' },
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
const { preset } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'list',
|
|
117
|
+
name: 'preset',
|
|
118
|
+
message: 'Choose a schedule preset or enter custom:',
|
|
119
|
+
choices: presets,
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
if (preset === '__custom__') {
|
|
124
|
+
const { customCron } = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'input',
|
|
127
|
+
name: 'customCron',
|
|
128
|
+
message: 'Enter cron expression (5 fields, e.g. "0 9 * * 1-5"):',
|
|
129
|
+
validate: (v: string) =>
|
|
130
|
+
/^(\S+\s){4}\S+$/.test(v.trim()) || 'Invalid cron (must be 5 space-separated fields)',
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
cronExpression = customCron.trim();
|
|
134
|
+
} else {
|
|
135
|
+
cronExpression = preset;
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const { dateStr, timeStr } = await inquirer.prompt([
|
|
139
|
+
{
|
|
140
|
+
type: 'input',
|
|
141
|
+
name: 'dateStr',
|
|
142
|
+
message: 'Date (YYYY-MM-DD):',
|
|
143
|
+
validate: (v: string) => /^\d{4}-\d{2}-\d{2}$/.test(v.trim()) || 'Use YYYY-MM-DD format',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
type: 'input',
|
|
147
|
+
name: 'timeStr',
|
|
148
|
+
message: 'Time (HH:MM, 24-hour local time):',
|
|
149
|
+
validate: (v: string) => /^\d{2}:\d{2}$/.test(v.trim()) || 'Use HH:MM format',
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
const dt = new Date(`${dateStr.trim()}T${timeStr.trim()}:00`);
|
|
153
|
+
if (isNaN(dt.getTime())) {
|
|
154
|
+
console.error('Invalid date/time combination.');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (dt <= new Date()) {
|
|
158
|
+
console.error('Date/time must be in the future.');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
runAt = dt.toISOString();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const headers = await authHeaders();
|
|
166
|
+
const res = await axios.post<ScheduledJobDto>(
|
|
167
|
+
`${getBaseUrl()}/api/scheduled-jobs`,
|
|
168
|
+
{
|
|
169
|
+
label: answers.label.trim(),
|
|
170
|
+
prompt: promptText,
|
|
171
|
+
cronExpression,
|
|
172
|
+
runAt,
|
|
173
|
+
},
|
|
174
|
+
{ headers, timeout: 15_000 },
|
|
175
|
+
);
|
|
176
|
+
const job = res.data;
|
|
177
|
+
console.log('\nJob created successfully:');
|
|
178
|
+
printJobRow(job);
|
|
179
|
+
if (job.nextRunAt) {
|
|
180
|
+
console.log(` Next run: ${new Date(job.nextRunAt).toLocaleString()}`);
|
|
181
|
+
}
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
const msg = err.response?.data?.error ?? err.message;
|
|
184
|
+
console.error(`Error creating job: ${msg}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function scheduleList(): Promise<void> {
|
|
189
|
+
try {
|
|
190
|
+
const headers = await authHeaders();
|
|
191
|
+
const res = await axios.get<{ jobs: ScheduledJobDto[] }>(`${getBaseUrl()}/api/scheduled-jobs`, {
|
|
192
|
+
headers,
|
|
193
|
+
timeout: 10_000,
|
|
194
|
+
});
|
|
195
|
+
const { jobs } = res.data;
|
|
196
|
+
if (jobs.length === 0) {
|
|
197
|
+
console.log('No scheduled jobs found.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log('\nScheduled Jobs:');
|
|
201
|
+
console.log('─'.repeat(90));
|
|
202
|
+
console.log(
|
|
203
|
+
padRight('ID', 28) +
|
|
204
|
+
padRight('Label', 24) +
|
|
205
|
+
padRight('Schedule', 18) +
|
|
206
|
+
padRight('Next Run', 22) +
|
|
207
|
+
'Status',
|
|
208
|
+
);
|
|
209
|
+
console.log('─'.repeat(90));
|
|
210
|
+
for (const job of jobs) {
|
|
211
|
+
const schedule =
|
|
212
|
+
job.cronExpression ?? (job.runAt ? `Once: ${new Date(job.runAt).toLocaleString()}` : '—');
|
|
213
|
+
const nextRun = job.nextRunAt ? new Date(job.nextRunAt).toLocaleString() : '—';
|
|
214
|
+
const status = job.isActive ? 'Active' : 'Inactive';
|
|
215
|
+
console.log(
|
|
216
|
+
padRight(job.id.slice(0, 26), 28) +
|
|
217
|
+
padRight(job.label.slice(0, 22), 24) +
|
|
218
|
+
padRight(schedule.slice(0, 16), 18) +
|
|
219
|
+
padRight(nextRun.slice(0, 20), 22) +
|
|
220
|
+
status,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
console.log('─'.repeat(90));
|
|
224
|
+
} catch (err: any) {
|
|
225
|
+
const msg = err.response?.data?.error ?? err.message;
|
|
226
|
+
console.error(`Error fetching jobs: ${msg}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function scheduleRemove(): Promise<void> {
|
|
231
|
+
try {
|
|
232
|
+
const headers = await authHeaders();
|
|
233
|
+
const res = await axios.get<{ jobs: ScheduledJobDto[] }>(`${getBaseUrl()}/api/scheduled-jobs`, {
|
|
234
|
+
headers,
|
|
235
|
+
timeout: 10_000,
|
|
236
|
+
});
|
|
237
|
+
const { jobs } = res.data;
|
|
238
|
+
if (jobs.length === 0) {
|
|
239
|
+
console.log('No scheduled jobs to remove.');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const { jobId } = await inquirer.prompt([
|
|
244
|
+
{
|
|
245
|
+
type: 'list',
|
|
246
|
+
name: 'jobId',
|
|
247
|
+
message: 'Select a job to remove:',
|
|
248
|
+
choices: jobs.map((j) => ({
|
|
249
|
+
name: `${j.label} [${j.cronExpression ?? 'one-time'}] ${j.isActive ? '✓' : '✗'}`,
|
|
250
|
+
value: j.id,
|
|
251
|
+
})),
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
const { confirm } = await inquirer.prompt([
|
|
256
|
+
{
|
|
257
|
+
type: 'confirm',
|
|
258
|
+
name: 'confirm',
|
|
259
|
+
message: 'Are you sure you want to delete this job?',
|
|
260
|
+
default: false,
|
|
261
|
+
},
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
if (!confirm) {
|
|
265
|
+
console.log('Aborted.');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
await axios.delete(`${getBaseUrl()}/api/scheduled-jobs/${jobId}`, {
|
|
270
|
+
headers,
|
|
271
|
+
timeout: 10_000,
|
|
272
|
+
});
|
|
273
|
+
console.log('Job deleted.');
|
|
274
|
+
} catch (err: any) {
|
|
275
|
+
const msg = err.response?.data?.error ?? err.message;
|
|
276
|
+
console.error(`Error removing job: ${msg}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function scheduleRunNow(id: string): Promise<void> {
|
|
281
|
+
try {
|
|
282
|
+
const headers = await authHeaders();
|
|
283
|
+
await axios.post(
|
|
284
|
+
`${getBaseUrl()}/api/scheduled-jobs/${id}/run-now`,
|
|
285
|
+
{},
|
|
286
|
+
{
|
|
287
|
+
headers,
|
|
288
|
+
timeout: 10_000,
|
|
289
|
+
},
|
|
290
|
+
);
|
|
291
|
+
console.log(`Job ${id} triggered.`);
|
|
292
|
+
} catch (err: any) {
|
|
293
|
+
const msg = err.response?.data?.error ?? err.message;
|
|
294
|
+
console.error(`Error triggering job: ${msg}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function printJobRow(job: ScheduledJobDto): void {
|
|
299
|
+
console.log(` ID: ${job.id}`);
|
|
300
|
+
console.log(` Label: ${job.label}`);
|
|
301
|
+
console.log(
|
|
302
|
+
` Schedule: ${job.cronExpression ?? (job.runAt ? `Once at ${new Date(job.runAt).toLocaleString()}` : '—')}`,
|
|
303
|
+
);
|
|
304
|
+
console.log(` Active: ${job.isActive}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function padRight(str: string, width: number): string {
|
|
308
|
+
return str.length >= width ? str.slice(0, width) : str + ' '.repeat(width - str.length);
|
|
309
|
+
}
|