hermoso 0.1.0 → 0.1.1
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/bin/hermoso.mjs +59 -6
- package/package.json +1 -1
package/bin/hermoso.mjs
CHANGED
|
@@ -43,14 +43,23 @@ async function main() {
|
|
|
43
43
|
const [group, sub] = pos;
|
|
44
44
|
const cfg = await loadConfig();
|
|
45
45
|
|
|
46
|
-
// ---- auth:
|
|
46
|
+
// ---- auth: `login` opens the browser to mint + store an agent key (nothing to paste — matches the app's OAuth
|
|
47
|
+
// connectors); `--token <key>` is the manual fallback; a localhost base needs no auth (dev). ----
|
|
47
48
|
if (group === 'auth') {
|
|
48
49
|
if (sub === 'logout') { await saveConfig({}); return console.log('✓ Logged out (cleared ~/.hermoso/config.json)'); }
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (
|
|
53
|
-
return;
|
|
50
|
+
const apiBase = (flags.url || cfg.apiBase || 'https://app.hermoso.ai').replace(/\/$/, '');
|
|
51
|
+
const profile = flags.profile || cfg.profile || 'default';
|
|
52
|
+
const isLocal = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:|\/|$)/.test(apiBase);
|
|
53
|
+
if (flags.token) { await saveConfig({ apiBase, token: String(flags.token), profile }); return console.log(`✓ Signed in — key stored (~/.hermoso/config.json). API: ${apiBase}`); }
|
|
54
|
+
if (isLocal) { await saveConfig({ apiBase, token: '', profile }); return console.log(`✓ Local dev — no auth required. API: ${apiBase}`); }
|
|
55
|
+
if (sub === 'login') { // browser sign-in: spin a loopback server, open the app's /?cliauth page, receive a minted key
|
|
56
|
+
const key = await browserLogin(apiBase);
|
|
57
|
+
if (!key) return die(`Sign-in didn’t complete. Re-run "hermoso auth login", or paste a key: hermoso auth login --token <key> (create one in the app under Agents & API keys).`);
|
|
58
|
+
await saveConfig({ apiBase, token: key, profile });
|
|
59
|
+
return console.log(`✓ Signed in — key stored (~/.hermoso/config.json). API: ${apiBase}`);
|
|
60
|
+
}
|
|
61
|
+
await saveConfig({ apiBase, token: cfg.token || '', profile }); // bare `auth` / `auth --url …`: just record the base
|
|
62
|
+
return console.log(`✓ Saved. API: ${apiBase}${cfg.token ? ' · token stored' : ''}`);
|
|
54
63
|
}
|
|
55
64
|
if (group === 'version' || flags.version) {
|
|
56
65
|
console.log(`hermoso-cli 1.0.0 · API ${cfg.apiBase || process.env.HERMOSO_API_BASE || 'https://app.hermoso.ai'} · ${cfg.token ? 'authed' : 'no token — run: hermoso auth login --token <key>'}`);
|
|
@@ -145,4 +154,48 @@ add --json to any command for machine output.`);
|
|
|
145
154
|
}
|
|
146
155
|
} catch (e) { die(e?.message || String(e)); }
|
|
147
156
|
}
|
|
157
|
+
|
|
158
|
+
// ---- browser sign-in (loopback OAuth, like gh/firebase): spin a 127.0.0.1 server, open the app's cli-auth page,
|
|
159
|
+
// which mints an agent key and redirects the browser back to our loopback with ?key=&state=. Nothing is pasted;
|
|
160
|
+
// the key transits only the user's own machine. Times out after 3 min. Returns the hmk_ key, or null. ----
|
|
161
|
+
async function browserLogin(apiBase) {
|
|
162
|
+
const http = await import('node:http');
|
|
163
|
+
const crypto = await import('node:crypto');
|
|
164
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
165
|
+
return await new Promise((resolve) => {
|
|
166
|
+
let done = false;
|
|
167
|
+
const finish = (val) => { if (done) return; done = true; try { server.close(); } catch {} resolve(val); };
|
|
168
|
+
const server = http.createServer((req, res) => {
|
|
169
|
+
try {
|
|
170
|
+
const u = new URL(req.url, 'http://127.0.0.1');
|
|
171
|
+
if (u.pathname === '/favicon.ico') { res.writeHead(204); return res.end(); }
|
|
172
|
+
const key = u.searchParams.get('key') || '';
|
|
173
|
+
const st = u.searchParams.get('state') || '';
|
|
174
|
+
const ok = st === state && /^hmk_[A-Za-z0-9_-]{20,}$/.test(key);
|
|
175
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
176
|
+
res.end(cliAuthPage(ok ? 'You’re signed in ✓' : 'Sign-in didn’t complete', ok ? 'Hermoso CLI is connected. You can close this tab and return to your terminal.' : 'Please close this tab and run the command again.'));
|
|
177
|
+
finish(ok ? key : null);
|
|
178
|
+
} catch { try { res.writeHead(500); res.end('error'); } catch {} finish(null); }
|
|
179
|
+
});
|
|
180
|
+
server.on('error', () => finish(null));
|
|
181
|
+
server.listen(0, '127.0.0.1', () => {
|
|
182
|
+
const port = server.address().port;
|
|
183
|
+
const authUrl = `${apiBase}/?cliauth=1&port=${port}&state=${state}`;
|
|
184
|
+
console.log(`\nOpening your browser to sign in…\nIf it doesn’t open, paste this into your browser:\n ${authUrl}\n`);
|
|
185
|
+
openBrowser(authUrl);
|
|
186
|
+
});
|
|
187
|
+
setTimeout(() => finish(null), 180000);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function openBrowser(url) {
|
|
191
|
+
import('node:child_process').then(({ spawn }) => {
|
|
192
|
+
const plat = process.platform;
|
|
193
|
+
const cmd = plat === 'darwin' ? 'open' : plat === 'win32' ? 'cmd' : 'xdg-open';
|
|
194
|
+
const args = plat === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
195
|
+
try { const c = spawn(cmd, args, { stdio: 'ignore', detached: true }); c.on('error', () => {}); c.unref(); } catch {}
|
|
196
|
+
}).catch(() => {});
|
|
197
|
+
}
|
|
198
|
+
function cliAuthPage(title, body) {
|
|
199
|
+
return `<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Hermoso CLI</title><body style="font-family:system-ui,-apple-system,Segoe UI,sans-serif;background:#0e0e10;color:#f4f1ea;display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0"><div style="text-align:center;max-width:420px;padding:32px"><div style="font-family:Georgia,'Times New Roman',serif;font-size:24px;font-weight:700;letter-spacing:-.5px;margin-bottom:18px">hermoso<span style="color:#d9714e">.ai</span></div><h1 style="font-size:20px;margin:14px 0 8px;font-weight:600">${title}</h1><p style="color:#a7a29a;line-height:1.55;font-size:15px">${body}</p></div></body>`;
|
|
200
|
+
}
|
|
148
201
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hermoso",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Drive Hermoso — the AI ad studio — from any AI agent: MCP server, CLI, and Claude skills for researching winning ads and generating finished image & video ads.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "hermoso": "bin/hermoso.mjs" },
|