clementine-agent 1.8.1 → 1.8.2
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.
|
@@ -26,19 +26,6 @@
|
|
|
26
26
|
*/
|
|
27
27
|
export interface TriggerFile {
|
|
28
28
|
jobName: string;
|
|
29
|
-
/**
|
|
30
|
-
* Bare job name (without `{agentSlug}:` prefix). Set by cron-scheduler
|
|
31
|
-
* for agent-scoped jobs so the loop can look the job up in
|
|
32
|
-
* agents/{agentSlug}/CRON.md. Optional for backward compat with
|
|
33
|
-
* triggers written before this field existed.
|
|
34
|
-
*/
|
|
35
|
-
bareName?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Owning agent slug, set by cron-scheduler. When present, the loop
|
|
38
|
-
* applies fixes to vault/00-System/agents/{agentSlug}/CRON.md instead
|
|
39
|
-
* of the central CRON.md. Falls back to scanning if absent (older triggers).
|
|
40
|
-
*/
|
|
41
|
-
agentSlug?: string;
|
|
42
29
|
consecutiveErrors: number;
|
|
43
30
|
recentErrors: string[];
|
|
44
31
|
triggeredAt: string;
|
|
@@ -74,11 +61,6 @@ export interface SelfImproveLoopOptions {
|
|
|
74
61
|
triggersDir?: string;
|
|
75
62
|
pendingDir?: string;
|
|
76
63
|
cronPath?: string;
|
|
77
|
-
/**
|
|
78
|
-
* Override the agents root (vault/00-System/agents). When a trigger
|
|
79
|
-
* has agentSlug, the loop reads/writes `${agentsDir}/${agentSlug}/CRON.md`.
|
|
80
|
-
*/
|
|
81
|
-
agentsDir?: string;
|
|
82
64
|
/**
|
|
83
65
|
* Disable the fs.watch event-driven path. Tests use this so they can
|
|
84
66
|
* call tick() directly without racing the watcher.
|
|
@@ -91,7 +73,6 @@ export declare class SelfImproveLoop {
|
|
|
91
73
|
private readonly triggersDir;
|
|
92
74
|
private readonly pendingDir;
|
|
93
75
|
private readonly cronPath;
|
|
94
|
-
private readonly agentsDir;
|
|
95
76
|
private readonly dispatcher;
|
|
96
77
|
private readonly watchEnabled;
|
|
97
78
|
private timer;
|
|
@@ -28,7 +28,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, watch, wr
|
|
|
28
28
|
import path from 'node:path';
|
|
29
29
|
import matter from 'gray-matter';
|
|
30
30
|
import pino from 'pino';
|
|
31
|
-
import {
|
|
31
|
+
import { BASE_DIR, SYSTEM_DIR } from '../config.js';
|
|
32
32
|
const logger = pino({ name: 'clementine.self-improve-loop' });
|
|
33
33
|
/**
|
|
34
34
|
* Fallback tick interval. The loop is primarily event-driven via fs.watch
|
|
@@ -46,7 +46,6 @@ const WATCH_DEBOUNCE_MS = 2000;
|
|
|
46
46
|
const TRIGGERS_DIR = path.join(BASE_DIR, 'self-improve', 'triggers');
|
|
47
47
|
const PENDING_CHANGES_DIR = path.join(BASE_DIR, 'self-improve', 'pending-changes');
|
|
48
48
|
const CRON_PATH = path.join(SYSTEM_DIR, 'CRON.md');
|
|
49
|
-
const AGENTS_ROOT = AGENTS_DIR;
|
|
50
49
|
// ── Pattern recognition ──────────────────────────────────────────────
|
|
51
50
|
const PATTERNS = [
|
|
52
51
|
{
|
|
@@ -109,108 +108,36 @@ export function classifyFailure(recentErrors) {
|
|
|
109
108
|
description: 'Unrecognized failure pattern. Owner needs to inspect the trigger file.',
|
|
110
109
|
};
|
|
111
110
|
}
|
|
112
|
-
function
|
|
111
|
+
function loadCronJob(jobName, cronPath) {
|
|
113
112
|
if (!existsSync(cronPath))
|
|
114
113
|
return null;
|
|
115
114
|
const raw = readFileSync(cronPath, 'utf-8');
|
|
116
115
|
const parsed = matter(raw);
|
|
117
116
|
const jobs = (parsed.data.jobs ?? []);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (typeof job.agent_slug === 'string')
|
|
124
|
-
return job.agent_slug;
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Locate a job's frontmatter entry in either the central CRON.md or an
|
|
129
|
-
* agent-scoped CRON.md. Search priority:
|
|
130
|
-
*
|
|
131
|
-
* 1. If trigger.agentSlug is set, look in agents/{slug}/CRON.md by bareName.
|
|
132
|
-
* 2. Otherwise look in central CRON.md by exact name.
|
|
133
|
-
* 3. Fall back to scanning agents/* for the bareName (covers older triggers
|
|
134
|
-
* that lack agentSlug — the cron-scheduler-prefixed jobName like
|
|
135
|
-
* `slug:name` lets us recover the slug).
|
|
136
|
-
*/
|
|
137
|
-
function loadCronJob(trigger, cronPath, agentsDir) {
|
|
138
|
-
const explicitSlug = trigger.agentSlug;
|
|
139
|
-
const bare = trigger.bareName ?? (explicitSlug && trigger.jobName.startsWith(`${explicitSlug}:`)
|
|
140
|
-
? trigger.jobName.slice(explicitSlug.length + 1)
|
|
141
|
-
: trigger.jobName);
|
|
142
|
-
// 1. Agent-scoped file when slug is known
|
|
143
|
-
if (explicitSlug) {
|
|
144
|
-
const agentCronPath = path.join(agentsDir, explicitSlug, 'CRON.md');
|
|
145
|
-
const file = readJobsFromFile(agentCronPath);
|
|
146
|
-
if (file) {
|
|
147
|
-
const job = file.jobs.find((j) => String(j.name ?? '') === bare);
|
|
148
|
-
if (job) {
|
|
149
|
-
return {
|
|
150
|
-
agentSlug: explicitSlug,
|
|
151
|
-
cronPath: agentCronPath,
|
|
152
|
-
bareName: bare,
|
|
153
|
-
job,
|
|
154
|
-
raw: file.raw,
|
|
155
|
-
parsed: file.parsed,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
// 2. Central CRON.md by full jobName (handles globally-defined jobs and
|
|
161
|
-
// legacy jobs tagged with agentSlug field directly in the central file)
|
|
162
|
-
const central = readJobsFromFile(cronPath);
|
|
163
|
-
if (central) {
|
|
164
|
-
const job = central.jobs.find((j) => String(j.name ?? '') === trigger.jobName);
|
|
165
|
-
if (job) {
|
|
166
|
-
return {
|
|
167
|
-
agentSlug: explicitSlug ?? readAgentSlug(job),
|
|
168
|
-
cronPath,
|
|
169
|
-
bareName: String(job.name ?? ''),
|
|
170
|
-
job,
|
|
171
|
-
raw: central.raw,
|
|
172
|
-
parsed: central.parsed,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// 3. Recover via scan: trigger jobName follows `{slug}:{bareName}` for
|
|
177
|
-
// agent-scoped jobs even when older triggers omit agentSlug.
|
|
178
|
-
if (!explicitSlug && trigger.jobName.includes(':')) {
|
|
179
|
-
const [slug, ...rest] = trigger.jobName.split(':');
|
|
180
|
-
const inferredBare = rest.join(':');
|
|
181
|
-
if (slug && inferredBare) {
|
|
182
|
-
const agentCronPath = path.join(agentsDir, slug, 'CRON.md');
|
|
183
|
-
const file = readJobsFromFile(agentCronPath);
|
|
184
|
-
if (file) {
|
|
185
|
-
const job = file.jobs.find((j) => String(j.name ?? '') === inferredBare);
|
|
186
|
-
if (job) {
|
|
187
|
-
return {
|
|
188
|
-
agentSlug: slug,
|
|
189
|
-
cronPath: agentCronPath,
|
|
190
|
-
bareName: inferredBare,
|
|
191
|
-
job,
|
|
192
|
-
raw: file.raw,
|
|
193
|
-
parsed: file.parsed,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return null;
|
|
117
|
+
const job = jobs.find((j) => String(j.name ?? '') === jobName);
|
|
118
|
+
if (!job)
|
|
119
|
+
return null;
|
|
120
|
+
const agentSlug = typeof job.agentSlug === 'string' ? job.agentSlug : (typeof job.agent_slug === 'string' ? job.agent_slug : undefined);
|
|
121
|
+
return { agentSlug, job, raw, parsed };
|
|
200
122
|
}
|
|
201
123
|
/**
|
|
202
|
-
* Apply the recipe's mutator to the job's frontmatter and write
|
|
203
|
-
*
|
|
204
|
-
* Returns true if a change was actually written.
|
|
124
|
+
* Apply the recipe's mutator to the job's frontmatter and write CRON.md
|
|
125
|
+
* back atomically. Returns true if a change was actually written.
|
|
205
126
|
*/
|
|
206
|
-
function applyCronEdit(
|
|
127
|
+
function applyCronEdit(jobName, recipe, cronPath) {
|
|
207
128
|
if (!recipe.apply)
|
|
208
129
|
return false;
|
|
130
|
+
const lookup = loadCronJob(jobName, cronPath);
|
|
131
|
+
if (!lookup) {
|
|
132
|
+
logger.warn({ jobName }, 'Job not found in CRON.md — cannot apply fix');
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
209
135
|
const changed = recipe.apply(lookup.job);
|
|
210
136
|
if (!changed)
|
|
211
137
|
return false;
|
|
138
|
+
// Re-stringify with the existing content body preserved.
|
|
212
139
|
const updated = matter.stringify(lookup.parsed.content, lookup.parsed.data);
|
|
213
|
-
writeFileSync(
|
|
140
|
+
writeFileSync(cronPath, updated);
|
|
214
141
|
return true;
|
|
215
142
|
}
|
|
216
143
|
function writePendingChange(record, dir) {
|
|
@@ -225,7 +152,6 @@ export class SelfImproveLoop {
|
|
|
225
152
|
triggersDir;
|
|
226
153
|
pendingDir;
|
|
227
154
|
cronPath;
|
|
228
|
-
agentsDir;
|
|
229
155
|
dispatcher;
|
|
230
156
|
watchEnabled;
|
|
231
157
|
timer = null;
|
|
@@ -239,7 +165,6 @@ export class SelfImproveLoop {
|
|
|
239
165
|
this.triggersDir = opts.triggersDir ?? TRIGGERS_DIR;
|
|
240
166
|
this.pendingDir = opts.pendingDir ?? PENDING_CHANGES_DIR;
|
|
241
167
|
this.cronPath = opts.cronPath ?? CRON_PATH;
|
|
242
|
-
this.agentsDir = opts.agentsDir ?? AGENTS_ROOT;
|
|
243
168
|
this.watchEnabled = opts.disableWatch !== true;
|
|
244
169
|
}
|
|
245
170
|
start() {
|
|
@@ -361,32 +286,23 @@ export class SelfImproveLoop {
|
|
|
361
286
|
}
|
|
362
287
|
async processOne(trigger, counts) {
|
|
363
288
|
const recipe = classifyFailure(trigger.recentErrors);
|
|
364
|
-
const lookup = loadCronJob(trigger, this.cronPath
|
|
365
|
-
const agentSlug =
|
|
289
|
+
const lookup = loadCronJob(trigger.jobName, this.cronPath);
|
|
290
|
+
const agentSlug = lookup?.agentSlug;
|
|
366
291
|
if (recipe.category === 'safe-cron-config') {
|
|
367
|
-
|
|
368
|
-
// Job vanished from CRON files (renamed/deleted). Nothing to fix.
|
|
369
|
-
counts.noop++;
|
|
370
|
-
logger.warn({ jobName: trigger.jobName, agentSlug }, 'Job not found in any CRON.md — cannot apply fix');
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const applied = applyCronEdit(lookup, recipe);
|
|
292
|
+
const applied = applyCronEdit(trigger.jobName, recipe, this.cronPath);
|
|
374
293
|
if (applied) {
|
|
375
294
|
counts.applied++;
|
|
376
|
-
const where = lookup.agentSlug
|
|
377
|
-
? `\`agents/${lookup.agentSlug}/CRON.md\``
|
|
378
|
-
: '`CRON.md`';
|
|
379
295
|
await this.notifyAgent(agentSlug, [
|
|
380
296
|
`🔧 **Auto-fixed** \`${trigger.jobName}\` after ${trigger.consecutiveErrors} consecutive failures.`,
|
|
381
297
|
'',
|
|
382
298
|
recipe.description,
|
|
383
299
|
'',
|
|
384
|
-
|
|
300
|
+
'I\'ll watch the next run to confirm it lands cleanly.',
|
|
385
301
|
].join('\n'));
|
|
386
302
|
}
|
|
387
303
|
else {
|
|
388
304
|
counts.noop++;
|
|
389
|
-
logger.info({ jobName: trigger.jobName
|
|
305
|
+
logger.info({ jobName: trigger.jobName }, 'Fix recipe applied is already in place — trigger removed without further action');
|
|
390
306
|
}
|
|
391
307
|
return;
|
|
392
308
|
}
|
package/dist/cli/browser.d.ts
CHANGED
|
@@ -32,5 +32,24 @@ export declare function cmdBrowserEnable(): Promise<void>;
|
|
|
32
32
|
*/
|
|
33
33
|
export declare function maybePromptBrowserHarness(): Promise<void>;
|
|
34
34
|
export declare function cmdBrowserDisable(): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Non-interactive connect — meant for callers that aren't a TTY (MCP tool,
|
|
37
|
+
* daemon-internal callers). Returns a structured result instead of prompting
|
|
38
|
+
* or printing decorative output. Caller decides how to surface failures.
|
|
39
|
+
*
|
|
40
|
+
* Behavior:
|
|
41
|
+
* - CDP already up → { ok: true, alreadyConnected: true }
|
|
42
|
+
* - No Chrome running → launch with flag, poll, return result
|
|
43
|
+
* - Chrome running without flag → if allowQuitChrome=false, refuse with
|
|
44
|
+
* a clear message; if true, quit + relaunch (DESTRUCTIVE — closes tabs).
|
|
45
|
+
*/
|
|
46
|
+
export declare function runConnectNonInteractive(opts?: {
|
|
47
|
+
allowQuitChrome?: boolean;
|
|
48
|
+
}): Promise<{
|
|
49
|
+
ok: boolean;
|
|
50
|
+
message: string;
|
|
51
|
+
alreadyConnected?: boolean;
|
|
52
|
+
needsForceQuit?: boolean;
|
|
53
|
+
}>;
|
|
35
54
|
export declare function cmdBrowserConnect(): Promise<void>;
|
|
36
55
|
//# sourceMappingURL=browser.d.ts.map
|
package/dist/cli/browser.js
CHANGED
|
@@ -354,11 +354,85 @@ export async function cmdBrowserDisable() {
|
|
|
354
354
|
console.log();
|
|
355
355
|
}
|
|
356
356
|
/**
|
|
357
|
-
*
|
|
358
|
-
*
|
|
357
|
+
* Non-interactive connect — meant for callers that aren't a TTY (MCP tool,
|
|
358
|
+
* daemon-internal callers). Returns a structured result instead of prompting
|
|
359
|
+
* or printing decorative output. Caller decides how to surface failures.
|
|
359
360
|
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
361
|
+
* Behavior:
|
|
362
|
+
* - CDP already up → { ok: true, alreadyConnected: true }
|
|
363
|
+
* - No Chrome running → launch with flag, poll, return result
|
|
364
|
+
* - Chrome running without flag → if allowQuitChrome=false, refuse with
|
|
365
|
+
* a clear message; if true, quit + relaunch (DESTRUCTIVE — closes tabs).
|
|
366
|
+
*/
|
|
367
|
+
export async function runConnectNonInteractive(opts = {}) {
|
|
368
|
+
if (await probeCdp()) {
|
|
369
|
+
return { ok: true, alreadyConnected: true, message: 'Already connected — Chrome is running with remote debugging on :9222.' };
|
|
370
|
+
}
|
|
371
|
+
if (process.platform !== 'darwin' && process.platform !== 'linux') {
|
|
372
|
+
return {
|
|
373
|
+
ok: false,
|
|
374
|
+
message: 'Auto-connect is only supported on macOS and Linux. Launch Chrome manually with --remote-debugging-port=9222.',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (isChromeRunning() && !opts.allowQuitChrome) {
|
|
378
|
+
return {
|
|
379
|
+
ok: false,
|
|
380
|
+
needsForceQuit: true,
|
|
381
|
+
message: 'Chrome is running without remote debugging. Connecting requires quitting Chrome and relaunching with --remote-debugging-port=9222 (this closes your current Chrome windows). Re-run with force_quit=true to proceed, or quit Chrome yourself first and call this again.',
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (isChromeRunning() && opts.allowQuitChrome) {
|
|
385
|
+
try {
|
|
386
|
+
if (process.platform === 'darwin') {
|
|
387
|
+
execSync('osascript -e \'tell application "Google Chrome" to quit\'', { stdio: 'pipe' });
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
try {
|
|
391
|
+
execSync('pkill -TERM -x "google-chrome|chromium|chrome"', { stdio: 'pipe' });
|
|
392
|
+
}
|
|
393
|
+
catch { /* ok */ }
|
|
394
|
+
}
|
|
395
|
+
for (let i = 0; i < 15; i++) {
|
|
396
|
+
if (!isChromeRunning())
|
|
397
|
+
break;
|
|
398
|
+
await new Promise(r => setTimeout(r, 300));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
return { ok: false, message: 'Failed to quit Chrome. Quit it manually and try again.' };
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
try {
|
|
406
|
+
if (process.platform === 'darwin') {
|
|
407
|
+
execSync('open -na "Google Chrome" --args --remote-debugging-port=9222', { stdio: 'pipe' });
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
const candidates = ['google-chrome', 'chromium', 'chrome'];
|
|
411
|
+
const bin = candidates.find(commandExists);
|
|
412
|
+
if (!bin) {
|
|
413
|
+
return { ok: false, message: 'No Chrome / Chromium binary found in PATH.' };
|
|
414
|
+
}
|
|
415
|
+
execSync(`nohup ${bin} --remote-debugging-port=9222 >/dev/null 2>&1 &`, { stdio: 'pipe' });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
return { ok: false, message: `Failed to launch Chrome: ${String(e).slice(0, 200)}` };
|
|
420
|
+
}
|
|
421
|
+
for (let i = 0; i < 24; i++) {
|
|
422
|
+
await new Promise(r => setTimeout(r, 250));
|
|
423
|
+
if (await probeCdp()) {
|
|
424
|
+
return { ok: true, message: 'Connected — Chrome is running with remote debugging on :9222.' };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return {
|
|
428
|
+
ok: false,
|
|
429
|
+
message: 'Chrome launched, but CDP socket isn\'t responding yet. Check that Chrome started successfully, then verify with: curl http://localhost:9222/json/version',
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Interactive CLI connect — wraps runConnectNonInteractive with TTY prompts
|
|
434
|
+
* and decorative output. Used by `clementine browser connect` and the auto-
|
|
435
|
+
* prompt flow.
|
|
362
436
|
*/
|
|
363
437
|
async function runConnect(opts = {}) {
|
|
364
438
|
// 1. Already connected? Done.
|
|
@@ -1184,27 +1184,19 @@ export class CronScheduler {
|
|
|
1184
1184
|
if (advice.shouldEscalate) {
|
|
1185
1185
|
this.logAdvisorEvent('escalation', job.name, advice.escalationReason ?? 'Escalated to unleashed');
|
|
1186
1186
|
}
|
|
1187
|
-
// Write targeted self-improvement trigger when consecutive errors are high
|
|
1188
|
-
// Include agentSlug + bareName so the self-improve loop can locate jobs
|
|
1189
|
-
// defined in per-agent CRON.md files (vault/00-System/agents/{slug}/CRON.md)
|
|
1190
|
-
// rather than only the central one.
|
|
1187
|
+
// Write targeted self-improvement trigger when consecutive errors are high
|
|
1191
1188
|
if (consErrors >= 3) {
|
|
1192
1189
|
try {
|
|
1193
1190
|
const triggerDir = path.join(BASE_DIR, 'self-improve', 'triggers');
|
|
1194
1191
|
mkdirSync(triggerDir, { recursive: true });
|
|
1195
1192
|
const triggerPath = path.join(triggerDir, `${job.name.replace(/[^a-zA-Z0-9_-]/g, '_')}.json`);
|
|
1196
|
-
const bareName = job.agentSlug && job.name.startsWith(`${job.agentSlug}:`)
|
|
1197
|
-
? job.name.slice(job.agentSlug.length + 1)
|
|
1198
|
-
: job.name;
|
|
1199
1193
|
writeFileSync(triggerPath, JSON.stringify({
|
|
1200
1194
|
jobName: job.name,
|
|
1201
|
-
bareName,
|
|
1202
|
-
agentSlug: job.agentSlug,
|
|
1203
1195
|
consecutiveErrors: consErrors,
|
|
1204
1196
|
recentErrors: this.runLog.readRecent(job.name, 3).map(e => e.error?.slice(0, 200)),
|
|
1205
1197
|
triggeredAt: new Date().toISOString(),
|
|
1206
1198
|
}, null, 2));
|
|
1207
|
-
logger.info({ job: job.name,
|
|
1199
|
+
logger.info({ job: job.name, consErrors }, 'Wrote self-improvement trigger for failing job');
|
|
1208
1200
|
}
|
|
1209
1201
|
catch { /* non-fatal */ }
|
|
1210
1202
|
}
|
|
@@ -1873,5 +1873,13 @@ export function registerAdminTools(server) {
|
|
|
1873
1873
|
logger.info({ jobName: job_name, runCount: updated.runCount }, 'Cron progress saved');
|
|
1874
1874
|
return textResult(`Progress saved for "${job_name}" (run #${updated.runCount}). ${(completedItems?.length ?? 0)} items completed, ${(updated.pendingItems?.length ?? 0)} pending.`);
|
|
1875
1875
|
});
|
|
1876
|
+
// ── Browser harness — chat-driven Chrome connect ────────────────────
|
|
1877
|
+
server.tool('browser_connect', 'Connect Chrome to the browser harness via CDP. Idempotent — if Chrome is already running with remote debugging on :9222 this is a no-op. If no Chrome is running, launches Chrome with --remote-debugging-port=9222. If Chrome is running normally without the flag, refuses unless force_quit=true (which closes the user\'s open tabs). Use this so the user can connect from any chat channel without dropping to the terminal.', {
|
|
1878
|
+
force_quit: z.boolean().optional().describe('If true, quit any running Chrome before relaunching with the debug flag. DESTRUCTIVE — closes the user\'s open tabs. Only set after the user has explicitly confirmed they want this. Defaults to false.'),
|
|
1879
|
+
}, async ({ force_quit }) => {
|
|
1880
|
+
const { runConnectNonInteractive } = await import('../cli/browser.js');
|
|
1881
|
+
const result = await runConnectNonInteractive({ allowQuitChrome: !!force_quit });
|
|
1882
|
+
return textResult(result.message);
|
|
1883
|
+
});
|
|
1876
1884
|
}
|
|
1877
1885
|
//# sourceMappingURL=admin-tools.js.map
|