halo-agent 2.2.2 → 2.2.4
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/browser.js +34 -1
- package/index.js +17 -12
- package/package.json +1 -1
- package/poller.js +2 -1
- package/ui.js +70 -8
package/browser.js
CHANGED
|
@@ -135,7 +135,7 @@ async function connectToChrome(retries = 10, opts = {}) {
|
|
|
135
135
|
ctx = fresh.contexts()[0] || await fresh.newContext();
|
|
136
136
|
conn.context = ctx;
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
let page = await ctx.newPage();
|
|
139
139
|
if (url) {
|
|
140
140
|
try {
|
|
141
141
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
|
@@ -153,6 +153,39 @@ async function connectToChrome(retries = 10, opts = {}) {
|
|
|
153
153
|
await page.waitForTimeout(8000);
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
|
|
157
|
+
// VERIFY-AFTER-GOTO. connectOverCDP against the user's REAL Chrome
|
|
158
|
+
// has a tab-handle race: ctx.newPage() may hand back a different
|
|
159
|
+
// tab than the one that actually navigated (e.g. Chrome already
|
|
160
|
+
// had a restored New Tab open). Symptom: the agent then scans
|
|
161
|
+
// about:blank / chrome://newtab forever while the target URL is
|
|
162
|
+
// loaded in a sibling tab. Self-correct: if our handle isn't on
|
|
163
|
+
// the target host, find the tab that IS and adopt it.
|
|
164
|
+
try {
|
|
165
|
+
const here = page.url();
|
|
166
|
+
const targetHost = new URL(url).hostname;
|
|
167
|
+
const onTarget = (u) => { try { return new URL(u).hostname === targetHost; } catch { return false; } };
|
|
168
|
+
if (!onTarget(here)) {
|
|
169
|
+
// Look across all pages in this context for the one that navigated.
|
|
170
|
+
const pages = ctx.pages();
|
|
171
|
+
const match = pages.find((p) => onTarget(p.url()));
|
|
172
|
+
if (match && match !== page) {
|
|
173
|
+
console.warn(`[halo-agent] newPage handle was on "${here.slice(0, 40)}" but target loaded in another tab — adopting it.`);
|
|
174
|
+
// Close the stray blank tab we opened so it doesn't litter.
|
|
175
|
+
if (/^about:blank$|^chrome:\/\/newtab/i.test(here)) {
|
|
176
|
+
try { await page.close(); } catch {}
|
|
177
|
+
}
|
|
178
|
+
page = match;
|
|
179
|
+
await page.bringToFront().catch(() => {});
|
|
180
|
+
} else if (/^about:blank$|^chrome:\/\/newtab/i.test(here)) {
|
|
181
|
+
// Our tab is genuinely blank and nothing else navigated —
|
|
182
|
+
// the goto silently no-op'd. Retry the navigation on THIS tab.
|
|
183
|
+
console.warn('[halo-agent] Tab still blank after goto — retrying navigation on this handle...');
|
|
184
|
+
await page.goto(url, { waitUntil: 'commit', timeout: 30000 }).catch(() => {});
|
|
185
|
+
await page.waitForTimeout(4000);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch { /* best-effort self-correction; fall through */ }
|
|
156
189
|
}
|
|
157
190
|
return page;
|
|
158
191
|
},
|
package/index.js
CHANGED
|
@@ -38,14 +38,18 @@ async function main() {
|
|
|
38
38
|
let v = '';
|
|
39
39
|
try { v = require('./package.json').version; } catch {}
|
|
40
40
|
ui.banner(v);
|
|
41
|
+
console.log(` ${ui.bold('Usage')} ${ui.gray('halo-agent <command>')}`);
|
|
41
42
|
console.log('');
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
ui.commandTable([
|
|
44
|
+
['pair <code>', 'One-click pair with the HALO dashboard'],
|
|
45
|
+
['start', 'Connect to Chrome and run the agent'],
|
|
46
|
+
['install-autostart', 'Run automatically at every login'],
|
|
47
|
+
['uninstall-autostart', 'Remove the login auto-start'],
|
|
48
|
+
['init', 'First-time setup (legacy token paste)'],
|
|
49
|
+
['token <value>', 'Update the auth token'],
|
|
50
|
+
]);
|
|
51
|
+
console.log('');
|
|
52
|
+
ui.hint('Get a pairing code: HALO dashboard → Profile → Auto-apply agent → Get pairing code.');
|
|
49
53
|
console.log('');
|
|
50
54
|
process.exit(1);
|
|
51
55
|
}
|
|
@@ -123,7 +127,8 @@ async function runPair(code) {
|
|
|
123
127
|
spin.stop(`Paired with HALO`);
|
|
124
128
|
ui.muted(` Token saved to ~/.halo-agent/config.json`);
|
|
125
129
|
console.log('');
|
|
126
|
-
|
|
130
|
+
ui.nextStep('halo-agent start', 'connect Chrome and start applying');
|
|
131
|
+
ui.hint('Or `halo-agent install-autostart` to run it at every login.');
|
|
127
132
|
console.log('');
|
|
128
133
|
}
|
|
129
134
|
|
|
@@ -308,7 +313,7 @@ async function runInit() {
|
|
|
308
313
|
saveConfig(config);
|
|
309
314
|
console.log('');
|
|
310
315
|
ui.success('Config saved to ~/.halo-agent/config.json');
|
|
311
|
-
|
|
316
|
+
ui.nextStep('halo-agent start', 'connect Chrome and start applying');
|
|
312
317
|
console.log('');
|
|
313
318
|
}
|
|
314
319
|
|
|
@@ -467,14 +472,14 @@ async function waitForChromeGone(timeoutMs) {
|
|
|
467
472
|
async function runStart() {
|
|
468
473
|
const config = loadConfig();
|
|
469
474
|
if (!config || !config.token) {
|
|
470
|
-
ui.fail('
|
|
471
|
-
|
|
475
|
+
ui.fail('Not paired yet — no config found.');
|
|
476
|
+
ui.nextStep('halo-agent pair <code>', 'get a code from the HALO dashboard');
|
|
472
477
|
process.exit(1);
|
|
473
478
|
}
|
|
474
479
|
|
|
475
480
|
let version = '';
|
|
476
481
|
try { version = require('./package.json').version; } catch {}
|
|
477
|
-
ui.
|
|
482
|
+
ui.bigBanner(version); // hero frame — the one you'd screenshot
|
|
478
483
|
|
|
479
484
|
// The agent runs its own isolated Chrome instance (separate --user-data-dir
|
|
480
485
|
// at ~/.halo-agent/chrome-profile) so it never collides with the user's
|
package/package.json
CHANGED
package/poller.js
CHANGED
|
@@ -15,7 +15,8 @@ let sessionId = null;
|
|
|
15
15
|
|
|
16
16
|
async function startPolling(chromeConn, config) {
|
|
17
17
|
ui.success('Listening for jobs');
|
|
18
|
-
ui.
|
|
18
|
+
ui.hint(`Queue one from HALO: a job's prep page → Approve & send to agent.`);
|
|
19
|
+
ui.muted(` Polling every 5s · ${ui.cyan('Ctrl-C')} to stop`);
|
|
19
20
|
console.log('');
|
|
20
21
|
|
|
21
22
|
// Register agent session with backend
|
package/ui.js
CHANGED
|
@@ -41,15 +41,76 @@ const warn = (msg) => console.log(`${yellow('!')} ${msg}`);
|
|
|
41
41
|
const muted = (msg) => console.log(gray(msg));
|
|
42
42
|
const step = (msg) => console.log(`${gray(sym.dot)} ${msg}`);
|
|
43
43
|
|
|
44
|
-
//
|
|
45
|
-
//
|
|
44
|
+
// Strip ANSI so box math measures the VISIBLE width, not the escape-padded
|
|
45
|
+
// string length. Without this, colored content overflows the box border.
|
|
46
|
+
const STRIP_ANSI = /\x1b\[[0-9;]*m/g;
|
|
47
|
+
const visLen = (s) => String(s).replace(STRIP_ANSI, '').length;
|
|
48
|
+
|
|
49
|
+
// Box-drawing glyphs (rounded). ASCII fallback on legacy Windows terminals.
|
|
50
|
+
const box = (process.platform === 'win32' && !process.env.WT_SESSION)
|
|
51
|
+
? { tl: '+', tr: '+', bl: '+', br: '+', h: '-', v: '|' }
|
|
52
|
+
: { tl: '╭', tr: '╮', bl: '╰', br: '╯', h: '─', v: '│' };
|
|
53
|
+
|
|
54
|
+
// The compact boxed wordmark. Ember mark + tagline + version, in a rounded
|
|
55
|
+
// box — the Vercel/Bun look. Reads well on camera and in a small terminal,
|
|
56
|
+
// and ages better than big ASCII art. The ⬢ is HALO's hex mark.
|
|
46
57
|
function banner(version) {
|
|
47
|
-
const
|
|
48
|
-
const
|
|
58
|
+
const mark = `${ember('⬢')} ${ember(bold('HALO'))}`;
|
|
59
|
+
const ver = version ? `${gray('agent')} ${gray('v' + version)}` : gray('agent');
|
|
60
|
+
// Two content lines.
|
|
61
|
+
const line1 = `${mark} ${gray('·')} ${ver}`;
|
|
62
|
+
const line2 = gray('auto-apply, in your own Chrome');
|
|
63
|
+
const inner = Math.max(visLen(line1), visLen(line2)) + 2; // 1 space padding each side
|
|
64
|
+
const pad = (s) => ' ' + s + ' '.repeat(inner - visLen(s) - 1);
|
|
65
|
+
const top = gray(box.tl + box.h.repeat(inner) + box.tr);
|
|
66
|
+
const bot = gray(box.bl + box.h.repeat(inner) + box.br);
|
|
67
|
+
const bar = gray(box.v);
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(` ${top}`);
|
|
70
|
+
console.log(` ${bar}${pad(line1)}${bar}`);
|
|
71
|
+
console.log(` ${bar}${pad(line2)}${bar}`);
|
|
72
|
+
console.log(` ${bot}`);
|
|
73
|
+
console.log('');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Big block-letter wordmark for the hero moment (`start`) — the frame you'd
|
|
77
|
+
// screenshot for a LinkedIn post. Five-row ASCII HALO in ember, tagline +
|
|
78
|
+
// version underneath. Falls back to the compact boxed banner when color is
|
|
79
|
+
// off (logs/CI) so the auto-start service log doesn't get a wall of blocks.
|
|
80
|
+
const HALO_ART = [
|
|
81
|
+
'██ ██ █████ ██ ██████ ',
|
|
82
|
+
'██ ██ ██ ██ ██ ██ ██ ',
|
|
83
|
+
'███████ ███████ ██ ██ ██ ',
|
|
84
|
+
'██ ██ ██ ██ ██ ██ ██ ',
|
|
85
|
+
'██ ██ ██ ██ ███████ ██████ ',
|
|
86
|
+
];
|
|
87
|
+
function bigBanner(version) {
|
|
88
|
+
if (!useColor) { banner(version); return; }
|
|
89
|
+
const ver = version ? gray('· agent v' + version) : gray('· agent');
|
|
90
|
+
console.log('');
|
|
91
|
+
for (const row of HALO_ART) console.log(' ' + ember(row));
|
|
92
|
+
console.log(' ' + gray('auto-apply, in your own Chrome') + ' ' + ver);
|
|
49
93
|
console.log('');
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Affordance helper: the obvious next command, rendered consistently so the
|
|
97
|
+
// user always knows what to type next. Shown after pair/init/etc.
|
|
98
|
+
function nextStep(cmd, note) {
|
|
99
|
+
const tail = note ? ` ${gray(note)}` : '';
|
|
100
|
+
console.log(` ${gray('Next')} ${gray(sym.arrow)} ${cyan(cmd)}${tail}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A hint line — softer than nextStep, for "you can also…" affordances.
|
|
104
|
+
function hint(msg) {
|
|
105
|
+
console.log(` ${gray(sym.dot)} ${gray(msg)}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Aligned command table for the usage screen. rows: [[cmd, desc], ...].
|
|
109
|
+
function commandTable(rows) {
|
|
110
|
+
const w = Math.max(...rows.map(([c]) => c.length));
|
|
111
|
+
for (const [cmd, desc] of rows) {
|
|
112
|
+
console.log(` ${cyan(cmd.padEnd(w))} ${gray(desc)}`);
|
|
113
|
+
}
|
|
53
114
|
}
|
|
54
115
|
|
|
55
116
|
// Lightweight spinner for waiting states ("Connecting to Chrome..."). Frames
|
|
@@ -81,5 +142,6 @@ function spinner(message) {
|
|
|
81
142
|
|
|
82
143
|
module.exports = {
|
|
83
144
|
bold, dim, red, green, yellow, blue, magenta, cyan, gray, ember,
|
|
84
|
-
sym, success, fail, info, warn, muted, step,
|
|
145
|
+
sym, success, fail, info, warn, muted, step,
|
|
146
|
+
banner, bigBanner, spinner, nextStep, hint, commandTable, visLen,
|
|
85
147
|
};
|