@certalis/agent-smith 0.1.0 → 0.1.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.
package/dist/bin.js CHANGED
@@ -316,7 +316,7 @@ var init_models = __esm(() => {
316
316
  // ../../packages/core/src/pricing.ts
317
317
  import { promises as fs } from "fs";
318
318
  import { tmpdir } from "os";
319
- import { join } from "path";
319
+ import { join as join2 } from "path";
320
320
 
321
321
  class PriceCatalogue {
322
322
  static instance;
@@ -335,10 +335,10 @@ class PriceCatalogue {
335
335
  return PriceCatalogue.instance;
336
336
  }
337
337
  getCacheDir() {
338
- return join(tmpdir(), "ccflare");
338
+ return join2(tmpdir(), "ccflare");
339
339
  }
340
340
  getCachePath() {
341
- return join(this.getCacheDir(), "models.dev.json");
341
+ return join2(this.getCacheDir(), "models.dev.json");
342
342
  }
343
343
  getCacheDurationMs() {
344
344
  const hours = Number(process.env.CF_PRICING_REFRESH_HOURS) || 24;
@@ -1016,7 +1016,7 @@ import {
1016
1016
  unlinkSync
1017
1017
  } from "fs";
1018
1018
  import { tmpdir as tmpdir2 } from "os";
1019
- import { join as join2 } from "path";
1019
+ import { join as join3 } from "path";
1020
1020
  function writeToStderr(message, error) {
1021
1021
  if (typeof process === "undefined") {
1022
1022
  return;
@@ -1032,11 +1032,11 @@ class LogFileWriter {
1032
1032
  stream = null;
1033
1033
  maxFileSize = BUFFER_SIZES.LOG_FILE_MAX_SIZE;
1034
1034
  constructor() {
1035
- this.logDir = join2(tmpdir2(), "ccflare-logs");
1035
+ this.logDir = join3(tmpdir2(), "ccflare-logs");
1036
1036
  if (!existsSync(this.logDir)) {
1037
1037
  mkdirSync(this.logDir, { recursive: true });
1038
1038
  }
1039
- this.logFile = join2(this.logDir, "app.log");
1039
+ this.logFile = join3(this.logDir, "app.log");
1040
1040
  this.initStream();
1041
1041
  }
1042
1042
  initStream() {
@@ -1229,29 +1229,29 @@ var init_src3 = __esm(() => {
1229
1229
 
1230
1230
  // ../../packages/config/src/paths-common.ts
1231
1231
  import { homedir } from "os";
1232
- import { join as join3 } from "path";
1232
+ import { join as join4 } from "path";
1233
1233
  import { platform } from "process";
1234
1234
  function getPlatformConfigDir() {
1235
1235
  if (platform === "win32") {
1236
- const baseDir = process.env.LOCALAPPDATA ?? process.env.APPDATA ?? join3(homedir(), "AppData", "Local");
1237
- return join3(baseDir, "ccflare");
1236
+ const baseDir = process.env.LOCALAPPDATA ?? process.env.APPDATA ?? join4(homedir(), "AppData", "Local");
1237
+ return join4(baseDir, "ccflare");
1238
1238
  } else {
1239
1239
  const xdgConfig = process.env.XDG_CONFIG_HOME;
1240
- const baseDir = xdgConfig ?? join3(homedir(), ".config");
1241
- return join3(baseDir, "ccflare");
1240
+ const baseDir = xdgConfig ?? join4(homedir(), ".config");
1241
+ return join4(baseDir, "ccflare");
1242
1242
  }
1243
1243
  }
1244
1244
  var init_paths_common = () => {};
1245
1245
 
1246
1246
  // ../../packages/config/src/paths.ts
1247
- import { join as join4 } from "path";
1247
+ import { join as join5 } from "path";
1248
1248
  function resolveConfigPath() {
1249
1249
  const explicitPath = process.env.ccflare_CONFIG_PATH;
1250
1250
  if (explicitPath) {
1251
1251
  return explicitPath;
1252
1252
  }
1253
1253
  const configDir = getPlatformConfigDir();
1254
- return join4(configDir, "ccflare.json");
1254
+ return join5(configDir, "ccflare.json");
1255
1255
  }
1256
1256
  var init_paths = __esm(() => {
1257
1257
  init_paths_common();
@@ -1275,12 +1275,30 @@ function parseNumber(value) {
1275
1275
  const parsed = Number(value);
1276
1276
  return Number.isFinite(parsed) ? parsed : undefined;
1277
1277
  }
1278
+ function normalizeEnrollmentEmail(value) {
1279
+ if (typeof value !== "string")
1280
+ return;
1281
+ const email = value.trim().toLowerCase();
1282
+ return email.length <= 254 && patterns.email.test(email) ? email : undefined;
1283
+ }
1284
+ function sanitizeEnrollmentEmails(value) {
1285
+ if (!Array.isArray(value))
1286
+ return;
1287
+ const emails = value.map(normalizeEnrollmentEmail).filter((email) => email !== undefined);
1288
+ return [...new Set(emails)].slice(0, MAX_ENROLLMENT_EMAILS);
1289
+ }
1278
1290
  function sanitizeConfigData(value) {
1279
1291
  if (!isRecord(value)) {
1280
1292
  return {};
1281
1293
  }
1282
1294
  const sanitized = {};
1283
1295
  for (const [key, entry] of Object.entries(value)) {
1296
+ if (key === "enrollment_emails") {
1297
+ const emails = sanitizeEnrollmentEmails(entry);
1298
+ if (emails)
1299
+ sanitized.enrollment_emails = emails;
1300
+ continue;
1301
+ }
1284
1302
  if (key === "team_routing") {
1285
1303
  const teamRouting = sanitizeTeamRouting(entry);
1286
1304
  if (teamRouting)
@@ -1430,7 +1448,7 @@ function sanitizeTeamRouting(value) {
1430
1448
  }
1431
1449
  return result;
1432
1450
  }
1433
- var log2, PROVIDER_STRATEGIES, BUILTIN_PROVIDER_CONFIGS, BUILTIN_PROVIDER_CONFIG_BY_NAME, PROVIDER_NAME_PATTERN, TEAM_ROUTING_DEFAULTS, Config;
1451
+ var log2, PROVIDER_STRATEGIES, BUILTIN_PROVIDER_CONFIGS, BUILTIN_PROVIDER_CONFIG_BY_NAME, PROVIDER_NAME_PATTERN, TEAM_ROUTING_DEFAULTS, MAX_ENROLLMENT_EMAILS = 200, Config;
1434
1452
  var init_src4 = __esm(() => {
1435
1453
  init_src2();
1436
1454
  init_src3();
@@ -1674,11 +1692,28 @@ var init_src4 = __esm(() => {
1674
1692
  this.persistAccountAllowlist(current.filter((uuid) => uuid !== accountUuid));
1675
1693
  }
1676
1694
  persistAccountAllowlist(accountUuids) {
1677
- const teamRouting = {
1695
+ this.persistStrictly("team_routing", {
1678
1696
  ...this.data.team_routing,
1679
1697
  allowlisted_account_uuids: accountUuids
1680
- };
1681
- const next = { ...this.data, team_routing: teamRouting };
1698
+ });
1699
+ }
1700
+ getEnrollmentEmails() {
1701
+ return [...this.data.enrollment_emails ?? []];
1702
+ }
1703
+ setEnrollmentEmails(emails) {
1704
+ const normalized = emails.map(normalizeEnrollmentEmail);
1705
+ if (normalized.some((email) => email === undefined)) {
1706
+ throw new ValidationError("Invalid enrollment email", "enrollment_emails");
1707
+ }
1708
+ const unique = [...new Set(normalized)];
1709
+ if (unique.length > MAX_ENROLLMENT_EMAILS) {
1710
+ throw new ValidationError(`At most ${MAX_ENROLLMENT_EMAILS} enrollment emails`, "enrollment_emails");
1711
+ }
1712
+ this.persistStrictly("enrollment_emails", unique);
1713
+ return [...unique];
1714
+ }
1715
+ persistStrictly(key, value) {
1716
+ const next = { ...this.data, [key]: value };
1682
1717
  const temporary = `${this.configPath}.${crypto.randomUUID()}.tmp`;
1683
1718
  try {
1684
1719
  writeFileSync(temporary, JSON.stringify(next, null, 2), {
@@ -1689,13 +1724,9 @@ var init_src4 = __esm(() => {
1689
1724
  } finally {
1690
1725
  rmSync(temporary, { force: true });
1691
1726
  }
1692
- const oldValue = this.data.team_routing;
1727
+ const oldValue = this.data[key];
1693
1728
  this.data = next;
1694
- this.emit("change", {
1695
- key: "team_routing",
1696
- oldValue,
1697
- newValue: teamRouting
1698
- });
1729
+ this.emit("change", { key, oldValue, newValue: value });
1699
1730
  }
1700
1731
  getRuntime() {
1701
1732
  const defaults = {
@@ -1833,14 +1864,14 @@ var init_analytics_query = __esm(() => {
1833
1864
  });
1834
1865
 
1835
1866
  // ../../packages/database/src/paths.ts
1836
- import { join as join5 } from "path";
1867
+ import { join as join6 } from "path";
1837
1868
  function resolveDbPath() {
1838
1869
  const explicitPath = process.env.ccflare_DB_PATH;
1839
1870
  if (explicitPath) {
1840
1871
  return explicitPath;
1841
1872
  }
1842
1873
  const configDir = getPlatformConfigDir();
1843
- return join5(configDir, "ccflare.db");
1874
+ return join6(configDir, "ccflare.db");
1844
1875
  }
1845
1876
  var init_paths2 = __esm(() => {
1846
1877
  init_src4();
@@ -7014,72 +7045,157 @@ function enrollmentPage() {
7014
7045
  return new Response(`<!doctype html>
7015
7046
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
7016
7047
  <title>Connect a Claude subscription</title>
7017
- <style nonce="${nonce}">
7018
- :root{color-scheme:light;font-family:"Avenir Next",Avenir,"Segoe UI",sans-serif;color:#17334a;background:#edf3f7;line-height:1.6}
7019
- body{margin:0;padding:clamp(20px,6vw,64px)}main{max-width:42rem;margin:auto;background:#fff;padding:clamp(24px,5vw,48px);border-top:6px solid #235e82}
7020
- h1{font-size:clamp(1.8rem,5vw,2.5rem);line-height:1.2;margin:0 0 24px}h2{font-size:1.2rem;margin-top:32px}p{max-width:65ch}button,a.button{font:inherit;display:inline-block;border:0;border-radius:6px;background:#235e82;color:#fff;padding:12px 20px;text-decoration:none;cursor:pointer}button:disabled{opacity:.55;cursor:wait}button:focus-visible,a:focus-visible,input:focus-visible{outline:3px solid #b15c00;outline-offset:4px}input[type=password]{box-sizing:border-box;display:block;width:100%;font:inherit;padding:12px;border:1px solid #647686;border-radius:4px;margin:8px 0 16px}label{display:block}#account{font-weight:600;overflow-wrap:anywhere}#status{white-space:pre-line}small{display:block;margin-top:32px;color:#4b6172}[hidden]{display:none!important}
7021
- </style></head><body><main>
7022
- <h1>Connect your Claude subscription</h1>
7023
- <p>This adds your subscription to the shared agents pool. Only connect an account you have permission to share. Your password stays with Claude; the pool stores the authorization needed to run agents.</p>
7024
- <section id="begin"><h2>1. Sign in with Claude</h2><p>You will open Claude in a separate tab. Choose the account you want to share.</p><button id="start" type="button">Connect Claude</button><p><a id="authorize" class="button" target="_blank" rel="noopener noreferrer" hidden>Open Claude sign-in</a></p></section>
7025
- <form id="verify" hidden><h2>2. Check your account</h2><label for="code">Paste the authorization code from Claude</label><input id="code" type="password" autocomplete="off" spellcheck="false" required maxlength="4096"><button type="submit">Check account</button></form>
7026
- <form id="confirm" hidden><h2>3. Add this subscription</h2><p>Claude verified this account:</p><p id="account"></p><label><input id="consent" type="checkbox" required> I authorize the shared agents pool to use this subscription.</label><p><button type="submit">Add subscription to pool</button></p><p>Wrong account? Do not confirm. Close this page and ask the operator for a new link.</p></form>
7027
- <section id="connected" hidden><h2>Subscription connected</h2><p id="connected-account"></p><p>Testing this flow? Disconnect removes this seat's saved credentials and pool membership before letting you sign in again.</p><button id="disconnect" type="button">Disconnect and try again</button><p>Keep this page open to disconnect. This button expires after 24 hours; a page reload or server restart requires operator help. It does not sign you out of Claude's website, cancel your subscription, or stop a request already running.</p></section>
7028
- <p id="status" role="status" aria-live="polite"></p>
7029
- <small>This private link works once. After starting, each step expires in 10 minutes. Do not share this link or paste authorization codes into chat. Reloading this page requires a new invitation.</small>
7030
- <noscript>JavaScript is required for this private sign-in flow.</noscript>
7031
- </main><script nonce="${nonce}">
7048
+ <style nonce="${nonce}">${styles}</style></head><body>
7049
+ <header>Shared agents pool<small>Connect a Claude subscription</small><div class="bar"><span id="progress"></span></div></header>
7050
+ <main>
7051
+ <ol id="stepper" aria-label="Progress">
7052
+ <li data-step="email"><span class="dot">1</span><span class="label">Email</span></li>
7053
+ <li data-step="signin"><span class="dot">2</span><span class="label">Sign in</span></li>
7054
+ <li data-step="code"><span class="dot">3</span><span class="label">Paste code</span></li>
7055
+ <li data-step="confirm"><span class="dot">4</span><span class="label">Confirm</span></li>
7056
+ <li data-step="done"><span class="dot">5</span><span class="label">Done</span></li>
7057
+ </ol>
7058
+ <section class="card" id="step-email" hidden>
7059
+ <div class="avatar">${icon("mail")}</div>
7060
+ <h1 tabindex="-1">Connect your Claude subscription</h1>
7061
+ <p class="lead">This adds your subscription to the shared agents pool. Start with your work email: if it is on this pool's list, the next step lets you sign in with Claude.</p>
7062
+ <form id="request">
7063
+ <p class="notice err" id="email-error" role="alert" hidden></p>
7064
+ <label for="email">Work email<input id="email" type="email" autocomplete="email" spellcheck="false" required maxlength="254"></label>
7065
+ <button class="button" type="submit">Continue</button>
7066
+ </form>
7067
+ </section>
7068
+ <section class="card" id="step-signin" hidden>
7069
+ <p class="notice" id="signin-notice" role="status" hidden></p>
7070
+ <div class="avatar">${icon("key")}</div>
7071
+ <h1 tabindex="-1">Sign in with Claude</h1>
7072
+ <p class="lead" id="signin-for" hidden>Use the Claude account of <strong id="signin-email"></strong>. Only connect an account you have permission to share.</p>
7073
+ <ul class="facts">
7074
+ <li>Claude opens in a new tab. Sign in and pick the account to share.</li>
7075
+ <li>Your password stays with Claude. The pool only keeps the authorization it needs to run agents.</li>
7076
+ <li>Claude then shows a code: you paste it here in the next step.</li>
7077
+ </ul>
7078
+ <button class="button" id="start" type="button">Continue to Claude ${icon("open")}</button>
7079
+ </section>
7080
+ <section class="card" id="step-code" hidden>
7081
+ <div class="avatar">${icon("code")}</div>
7082
+ <h1 tabindex="-1">Paste the code from Claude</h1>
7083
+ <p class="lead" id="code-lead">Claude opened in a new tab. Once you have signed in, it shows an authorization code: copy it and paste it below.</p>
7084
+ <form id="verify">
7085
+ <label for="code">Authorization code<input id="code" type="password" autocomplete="off" spellcheck="false" required maxlength="4096"><span class="hint">The code stays hidden on screen. Never paste it into chat.</span></label>
7086
+ <button class="button" type="submit">Verify account</button>
7087
+ </form>
7088
+ <p class="aside">Closed the Claude tab? <a class="link" id="reopen" target="_blank" rel="noopener noreferrer">Open Claude sign-in again ${icon("open")}</a></p>
7089
+ </section>
7090
+ <section class="card" id="step-confirm" hidden>
7091
+ <div class="avatar">${icon("person")}</div>
7092
+ <h1 tabindex="-1">Is this the right account?</h1>
7093
+ <p class="lead">Claude verified the account below. Check it before adding it to the pool.</p>
7094
+ <form id="confirm">
7095
+ <div class="account">${icon("check")}<span id="account"></span></div>
7096
+ <label class="consent"><input id="consent" type="checkbox" required><span>I authorize the shared agents pool to use this subscription.</span></label>
7097
+ <button class="button" type="submit">Add to the pool</button>
7098
+ </form>
7099
+ <p class="aside">Wrong account? Do not confirm: start again and sign in with the right one.<a class="button secondary" href="/pool-enroll">Start again</a></p>
7100
+ </section>
7101
+ <section class="card" id="step-done" hidden>
7102
+ <div class="avatar ok">${icon("check")}</div>
7103
+ <h1 tabindex="-1">Subscription connected</h1>
7104
+ <p class="lead"><strong id="connected-account"></strong> is now part of the shared agents pool. You can close this page.</p>
7105
+ <div class="aside"><strong>Only testing?</strong> Disconnect removes this account's saved authorization and pool membership, then lets you sign in again. It stays available for 24 hours while this page is open; it does not sign you out of Claude or cancel your subscription.<button class="button secondary" id="disconnect" type="button">Disconnect and try again</button></div>
7106
+ </section>
7107
+ <section class="card" id="step-error" hidden>
7108
+ <div class="avatar err">${icon("alert")}</div>
7109
+ <h1 tabindex="-1">This sign-in can't continue</h1>
7110
+ <p class="lead" id="error-message"></p>
7111
+ <p class="aside">Nothing was added to the pool from this step.<a class="button secondary" href="/pool-enroll">Start again</a></p>
7112
+ </section>
7113
+ <p id="status" class="notice err" role="alert" hidden></p>
7114
+ </main>
7115
+ <footer>Each step expires after 10 minutes. Nothing is added to the pool until you confirm.</footer>
7116
+ <noscript><p class="notice err">JavaScript is required for this sign-in.</p></noscript>
7117
+ <script nonce="${nonce}">
7032
7118
  let token = location.hash.slice(1);
7033
7119
  history.replaceState(null, '', location.pathname);
7034
7120
  addEventListener('hashchange', () => { if (location.hash) location.reload(); });
7035
7121
  const get = id => document.getElementById(id);
7036
- const status = get('status');
7037
- async function post(step, values) {
7038
- const response = await fetch('/pool-enroll/' + step, {method:'POST', credentials:'omit', cache:'no-store', redirect:'error', headers:{'content-type':'application/json'}, body:JSON.stringify({token, ...values})});
7122
+ const steps = ['email', 'signin', 'code', 'confirm', 'done'];
7123
+ let current = 'email';
7124
+ function show(step) {
7125
+ const failed = step === 'error';
7126
+ const index = steps.indexOf(failed ? current : step);
7127
+ if (!failed) current = step;
7128
+ for (const name of [...steps, 'error']) get('step-' + name).hidden = name !== step;
7129
+ document.querySelectorAll('#stepper li').forEach((item, position) => {
7130
+ item.className = position < index || step === 'done' ? 'done' : position === index ? (failed ? 'failed' : 'active') : '';
7131
+ item.querySelector('.dot').innerHTML = item.className === 'done' ? '${icon("check")}' : String(position + 1);
7132
+ if (position === index) item.setAttribute('aria-current', 'step'); else item.removeAttribute('aria-current');
7133
+ });
7134
+ get('progress').style.width = Math.max(5, ((index + (step === 'done' ? 1 : 0)) / steps.length) * 100) + '%';
7135
+ (step === 'email' ? get('email') : get('step-' + step).querySelector('h1')).focus();
7136
+ }
7137
+ async function send(step, body) {
7138
+ const response = await fetch('/pool-enroll/' + step, {method:'POST', credentials:'omit', cache:'no-store', redirect:'error', headers:{'content-type':'application/json'}, body:JSON.stringify(body)});
7039
7139
  const data = await response.json();
7040
- if (!response.ok) throw new Error(data.error || 'Sign-in failed. Ask the operator for a new link.');
7140
+ if (!response.ok) throw new Error(data.error || 'Sign-in failed. Start again.');
7041
7141
  token = data.token || '';
7042
7142
  return data;
7043
7143
  }
7144
+ const post = (step, values) => send(step, {token, ...values});
7044
7145
  function fail(error) {
7045
- token = ''; get('code').value = ''; get('begin').hidden = true; get('verify').hidden = true; get('confirm').hidden = true;
7046
- status.textContent = error instanceof Error ? error.message : 'Sign-in failed. Ask for a new link.';
7146
+ token = ''; get('code').value = '';
7147
+ get('error-message').textContent = error instanceof Error ? error.message : 'Sign-in failed. Start again.';
7148
+ show('error');
7047
7149
  }
7048
- if (!/^[A-Za-z0-9_-]{43}$/.test(token)) fail(new Error('Open the private invitation link supplied by your pool operator.'));
7150
+ show(/^[A-Za-z0-9_-]{43}$/.test(token) ? 'signin' : 'email');
7151
+ get('request').addEventListener('submit', async event => {
7152
+ event.preventDefault(); event.submitter.disabled = true; get('email-error').hidden = true;
7153
+ try {
7154
+ const data = await send('request', {email: get('email').value});
7155
+ get('signin-email').textContent = data.email; get('signin-for').hidden = false;
7156
+ show('signin');
7157
+ } catch (error) {
7158
+ get('email-error').textContent = error instanceof Error ? error.message : 'This email cannot connect. Ask the operator.';
7159
+ get('email-error').hidden = false; get('email').focus();
7160
+ } finally { event.submitter.disabled = false; }
7161
+ });
7049
7162
  get('start').addEventListener('click', async () => {
7163
+ const tab = window.open('', '_blank');
7050
7164
  get('start').disabled = true;
7051
7165
  try {
7052
7166
  const data = await post('start', {});
7053
7167
  const url = new URL(data.authUrl);
7054
- if (url.origin !== 'https://claude.ai' || url.pathname !== '/oauth/authorize') throw new Error('Invalid sign-in destination. Ask the operator for help.');
7055
- get('authorize').href = url.href; get('authorize').hidden = false; get('start').hidden = true;
7056
- get('verify').hidden = false; status.textContent = 'Open Claude sign-in above, then return here with the code.';
7057
- } catch(error) { fail(error); }
7168
+ if (url.origin !== 'https://claude.ai' || url.pathname !== '/oauth/authorize') throw new Error('Invalid sign-in destination. Ask for help.');
7169
+ get('reopen').href = url.href;
7170
+ if (tab) { tab.opener = null; tab.location.href = url.href; }
7171
+ else get('code-lead').textContent = 'Your browser blocked the new tab. Open Claude sign-in with the link below, sign in, then paste the code it shows.';
7172
+ show('code');
7173
+ } catch (error) { if (tab) tab.close(); fail(error); }
7058
7174
  });
7059
7175
  get('verify').addEventListener('submit', async event => {
7060
- event.preventDefault(); const button = event.submitter; button.disabled = true;
7176
+ event.preventDefault(); event.submitter.disabled = true;
7061
7177
  const code = get('code').value.trim(); get('code').value = '';
7062
- try { const data = await post('verify', {code}); get('begin').hidden = true; get('verify').hidden = true; get('account').textContent = data.email; get('confirm').hidden = false; status.textContent = 'Check the account above before confirming.'; }
7063
- catch(error) { fail(error); }
7178
+ try { const data = await post('verify', {code}); get('account').textContent = data.email; show('confirm'); }
7179
+ catch (error) { fail(error); }
7064
7180
  });
7065
7181
  get('confirm').addEventListener('submit', async event => {
7066
7182
  event.preventDefault(); if (!get('consent').checked) return; event.submitter.disabled = true;
7067
- try { await post('confirm', {consent:true}); get('confirm').hidden = true; get('connected-account').textContent = get('account').textContent; get('connected').hidden = false; status.textContent = 'Subscription connected. If you are testing, disconnect before closing this page.'; }
7068
- catch(error) { fail(error); }
7183
+ try { await post('confirm', {consent:true}); get('connected-account').textContent = get('account').textContent; show('done'); }
7184
+ catch (error) { fail(error); }
7069
7185
  });
7070
7186
  get('disconnect').addEventListener('click', async () => {
7071
- get('disconnect').disabled = true;
7187
+ get('disconnect').disabled = true; get('status').hidden = true;
7072
7188
  try {
7073
7189
  await post('disconnect', {});
7074
- get('connected').hidden = true; get('connected-account').textContent = ''; get('account').textContent = '';
7075
- get('consent').checked = false; get('code').value = '';
7076
- get('authorize').removeAttribute('href'); get('authorize').hidden = true;
7190
+ get('account').textContent = ''; get('connected-account').textContent = ''; get('consent').checked = false;
7077
7191
  document.querySelectorAll('button').forEach(button => { button.disabled = false; });
7078
- get('start').hidden = false; get('begin').hidden = false;
7079
- status.textContent = 'Subscription disconnected. It is no longer in the pool. You can close this page or connect again.';
7080
- } catch(error) {
7192
+ get('signin-notice').textContent = 'Subscription disconnected: it is no longer in the pool. You can connect again or close this page.';
7193
+ get('signin-notice').hidden = false;
7194
+ show('signin');
7195
+ } catch (error) {
7081
7196
  get('disconnect').disabled = false;
7082
- status.textContent = 'Removal is not confirmed. ' + (error instanceof Error ? error.message : 'Keep this page open and ask the operator to check this seat.');
7197
+ get('status').textContent = 'Removal is not confirmed. ' + (error instanceof Error ? error.message : 'Keep this page open and ask for help with this seat.');
7198
+ get('status').hidden = false;
7083
7199
  }
7084
7200
  });
7085
7201
  </script></body></html>`, {
@@ -7093,7 +7209,61 @@ get('disconnect').addEventListener('click', async () => {
7093
7209
  }
7094
7210
  });
7095
7211
  }
7096
- var init_enrollment_page = () => {};
7212
+ var icons, icon = (name) => `<svg viewBox="0 0 24 24" aria-hidden="true">${icons[name]}</svg>`, styles = `
7213
+ :root{--page:#faf9f6;--card:#fff;--text:#191b21;--muted:#5a606b;--faint:#9aa0ab;--line:#e6e8ec;--border:rgba(25,27,33,.14);--brand:#fb5d22;--accent:#dc5518;--tint:#fff3ec;--ink:#182025;--ink-hover:#2a333b;--ok:#17a06b;--ok-bg:#e3f6ed;--ok-text:#0c6444;--err:#d92d20;--err-bg:#fef3f2;--err-text:#912018;color-scheme:light;font:15px/1.55 "Public Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;color:var(--text);background:var(--page)}
7214
+ *{box-sizing:border-box}body{margin:0;min-height:100vh}
7215
+ header{position:relative;background:#fff;border-bottom:1px solid var(--line);padding:16px clamp(16px,4vw,32px);font-weight:600}
7216
+ header small{display:block;font-weight:400;color:var(--muted);font-size:13px}
7217
+ .bar{position:absolute;left:0;right:0;bottom:-1px;height:4px;background:#f2f1ec}.bar span{display:block;height:100%;width:5%;background:var(--ink);transition:width .3s ease-in-out}
7218
+ main{max-width:560px;margin:0 auto;padding:32px 16px 48px}
7219
+ ol{list-style:none;display:flex;margin:0 0 28px;padding:0}
7220
+ ol li{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative;font-size:13px;color:var(--faint)}
7221
+ ol li+li::before{content:"";position:absolute;top:15px;right:calc(50% + 20px);width:calc(100% - 40px);height:2px;background:var(--line)}
7222
+ ol li.done+li::before{background:var(--ok-text)}
7223
+ ol .dot{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;font-weight:600;background:var(--line);color:var(--faint);border:2px solid var(--line)}
7224
+ ol .dot svg{width:16px;height:16px;stroke:#fff;stroke-width:3}
7225
+ ol li.active{color:var(--accent);font-weight:600}ol li.active .dot{background:var(--brand);border-color:var(--brand);color:#fff}
7226
+ ol li.done{color:var(--muted)}ol li.done .dot{background:var(--ok);border-color:var(--ok)}
7227
+ ol li.failed .dot{background:var(--err);border-color:var(--err);color:#fff}
7228
+ .card{background:var(--card);border-radius:16px;box-shadow:0 1px 2px rgba(16,18,24,.05);border:1px solid rgba(25,27,33,.08);padding:clamp(24px,5vw,40px);text-align:center}
7229
+ .avatar{width:64px;height:64px;margin:0 auto 16px;border-radius:50%;display:grid;place-items:center;background:var(--tint)}
7230
+ .avatar svg,.button svg{width:28px;height:28px;fill:none;stroke:var(--accent);stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
7231
+ .avatar.ok{background:var(--ok-bg)}.avatar.ok svg{stroke:var(--ok-text);stroke-width:2.4}.avatar.err{background:var(--err-bg)}.avatar.err svg{stroke:var(--err)}
7232
+ h1{font-size:22px;line-height:1.3;margin:0 0 8px;outline:none}
7233
+ .lead{color:var(--muted);margin:0 auto 24px;max-width:42ch}
7234
+ .facts{list-style:none;padding:0;margin:0 0 28px;text-align:left;display:grid;gap:10px}
7235
+ .facts li{display:flex;gap:10px;align-items:flex-start}.facts li::before{content:"";flex:none;width:6px;height:6px;margin-top:9px;border-radius:50%;background:var(--brand)}
7236
+ .button{font:inherit;font-weight:600;display:inline-flex;align-items:center;justify-content:center;gap:8px;width:100%;min-height:44px;padding:10px 16px;border-radius:9px;border:0;background:var(--ink);color:#fff;cursor:pointer;text-decoration:none}
7237
+ .button:hover{background:var(--ink-hover);box-shadow:0 6px 18px -6px rgb(24 32 37/.5)}.button svg{width:18px;height:18px;stroke:currentColor}
7238
+ .button.secondary{background:#fff;color:var(--text);border:1px solid var(--border)}.button.secondary:hover{box-shadow:0 1px 3px rgba(47,43,67,.12)}
7239
+ .button:disabled{opacity:.5;cursor:progress;box-shadow:none}
7240
+ .button:focus-visible,input:focus-visible,a:focus-visible{outline:3px solid var(--brand);outline-offset:3px}
7241
+ .link{display:inline-flex;align-items:center;gap:6px;color:var(--accent);font-weight:600;text-decoration:none}.link svg{width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:2}
7242
+ form{text-align:left;display:grid;gap:16px}label{font-weight:600}
7243
+ input[type=password],input[type=email]{display:block;width:100%;font:inherit;padding:11px 12px;margin-top:6px;border:1px solid var(--border);border-radius:9px;background:#fff}
7244
+ .hint{font-size:13px;color:var(--muted);margin:6px 0 0;font-weight:400}
7245
+ .account{display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:12px;background:var(--page);border:1px solid rgba(25,27,33,.08);font-weight:600;overflow-wrap:anywhere}
7246
+ .account svg{flex:none;width:22px;height:22px;fill:none;stroke:var(--ok-text);stroke-width:2.4}
7247
+ .consent{display:flex;gap:10px;align-items:flex-start;font-weight:400}.consent input{width:18px;height:18px;margin:2px 0 0;accent-color:var(--ink)}
7248
+ .notice{border-radius:12px;padding:12px 16px;margin:0 0 20px;text-align:left;background:var(--ok-bg);color:var(--ok-text)}
7249
+ .notice.err{background:var(--err-bg);color:var(--err-text)}
7250
+ .aside{margin-top:24px;padding-top:20px;border-top:1px solid var(--line);text-align:left;color:var(--muted);font-size:14px}
7251
+ .aside .button{margin-top:12px}
7252
+ footer{max-width:560px;margin:0 auto;padding:0 16px 32px;color:var(--muted);font-size:13px;text-align:center}
7253
+ @media (max-width:480px){ol li:not(.active) .label{display:none}}
7254
+ [hidden]{display:none!important}
7255
+ `;
7256
+ var init_enrollment_page = __esm(() => {
7257
+ icons = {
7258
+ mail: '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/>',
7259
+ key: '<path d="M15 7.5a3.5 3.5 0 1 1-3.4 4.3L4 19.5V22h3v-2h2v-2h2l1.2-1.2A3.5 3.5 0 0 1 15 7.5Z"/><circle cx="16" cy="11" r="1"/>',
7260
+ code: '<rect x="4" y="6" width="16" height="12" rx="2"/><path d="M8 10h.01M12 10h.01M16 10h.01M8 14h8"/>',
7261
+ person: '<circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/>',
7262
+ check: '<path d="m5 12.5 4.5 4.5L19 7.5"/>',
7263
+ alert: '<path d="M12 8v5M12 16.5h.01"/><circle cx="12" cy="12" r="9"/>',
7264
+ open: '<path d="M14 5h5v5M19 5l-8 8M18 14v4a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h4"/>'
7265
+ };
7266
+ });
7097
7267
 
7098
7268
  // ../../packages/api/src/handlers/enrollment.ts
7099
7269
  import { createHash, randomBytes as randomBytes2 } from "crypto";
@@ -7135,13 +7305,59 @@ class EnrollmentHandlers {
7135
7305
  return await action();
7136
7306
  } catch (error) {
7137
7307
  return error instanceof EnrollmentError ? json({ error: error.message }, error.status) : json({
7138
- error: "Enrollment could not be saved. Ask the operator to check the seat and issue a new link."
7308
+ error: "Enrollment could not be saved. Ask the operator to check the seat."
7139
7309
  }, 500);
7140
7310
  }
7141
7311
  }
7142
- issue = (req) => this.guarded(async () => {
7312
+ requireClaudeCode() {
7143
7313
  if (!this.context.providerRegistry.getOAuthProvider("claude-code"))
7144
7314
  throw new EnrollmentError(400, "Provider 'claude-code' is disabled. Set providers.claude-code.enabled to true in your config.");
7315
+ }
7316
+ reserve(name) {
7317
+ if (this.context.dbOps.getAccountByName(name))
7318
+ throw new EnrollmentError(409, "This seat is already in the pool.");
7319
+ for (const entry of this.entries.values())
7320
+ if (entry.details.name === name && entry.state.kind === "busy")
7321
+ throw new EnrollmentError(409, "Sign-in is already in progress");
7322
+ for (const [key, entry] of this.entries)
7323
+ if (entry.details.name === name)
7324
+ this.entries.delete(key);
7325
+ if (this.entries.size >= 100)
7326
+ throw new EnrollmentError(429, "Too many pending invitations");
7327
+ }
7328
+ request(body) {
7329
+ this.requireClaudeCode();
7330
+ if (Object.keys(body).some((key) => key !== "email"))
7331
+ throw new EnrollmentError(400, "Unknown request field");
7332
+ const email = typeof body.email === "string" ? body.email.trim().toLowerCase() : "";
7333
+ if (!this.context.config.getEnrollmentEmails().includes(email))
7334
+ throw new EnrollmentError(403, "This email is not on the list. Ask the operator to add it.");
7335
+ const name = email.slice(0, email.indexOf("@")).replace(/[^a-z0-9._+-]/g, "-").slice(0, 64);
7336
+ this.reserve(name);
7337
+ const issued = this.mint({ name, email, contactEmail: email }, { kind: "invited" }, tenMinutes);
7338
+ return json({ ...issued, email });
7339
+ }
7340
+ emails = (req) => this.guarded(async () => {
7341
+ const body = await readBody(req);
7342
+ const lists = Object.entries(body);
7343
+ if (lists.some(([key, value]) => key !== "add" && key !== "remove" || !Array.isArray(value) || value.some((email) => typeof email !== "string")))
7344
+ throw new EnrollmentError(400, "Expected { add?: string[], remove?: string[] }");
7345
+ const { config } = this.context;
7346
+ const add = body.add ?? [];
7347
+ const remove = new Set((body.remove ?? []).map((email) => email.trim().toLowerCase()));
7348
+ if (add.length === 0 && remove.size === 0)
7349
+ return json({ emails: config.getEnrollmentEmails() });
7350
+ const next = [...config.getEnrollmentEmails(), ...add].filter((email) => !remove.has(email.trim().toLowerCase()));
7351
+ try {
7352
+ return json({ emails: config.setEnrollmentEmails(next) });
7353
+ } catch (error) {
7354
+ if (error instanceof ValidationError)
7355
+ throw new EnrollmentError(400, error.message);
7356
+ throw error;
7357
+ }
7358
+ });
7359
+ issue = (req) => this.guarded(async () => {
7360
+ this.requireClaudeCode();
7145
7361
  const input = await readBody(req);
7146
7362
  const allowed = new Set(["name", "email", "contactEmail", "slackUserId"]);
7147
7363
  if (Object.keys(input).some((key) => !allowed.has(key)))
@@ -7164,16 +7380,7 @@ class EnrollmentHandlers {
7164
7380
  }
7165
7381
  if (details.name.length > 80 || details.slackUserId && !/^U[A-Z0-9]{8,20}$/.test(details.slackUserId) || [details.email, details.contactEmail].some((email) => email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)))
7166
7382
  throw new EnrollmentError(400, "Invalid invitation details");
7167
- if (this.context.dbOps.getAccountByName(details.name))
7168
- throw new EnrollmentError(409, "Seat already exists");
7169
- for (const entry of this.entries.values())
7170
- if (entry.details.name === details.name && entry.state.kind === "busy")
7171
- throw new EnrollmentError(409, "Sign-in is already in progress");
7172
- for (const [key, entry] of this.entries)
7173
- if (entry.details.name === details.name)
7174
- this.entries.delete(key);
7175
- if (this.entries.size >= 100)
7176
- throw new EnrollmentError(429, "Too many pending invitations");
7383
+ this.reserve(details.name);
7177
7384
  const issued = this.mint(details, { kind: "invited" }, oneDay);
7178
7385
  return json({
7179
7386
  url: `${this.origin.origin}/pool-enroll#${issued.token}`,
@@ -7195,12 +7402,14 @@ class EnrollmentHandlers {
7195
7402
  if (req.headers.get("origin") !== this.origin.origin)
7196
7403
  throw new EnrollmentError(403, "Untrusted enrollment origin");
7197
7404
  const body = await readBody(req);
7405
+ if (url.pathname === "/pool-enroll/request")
7406
+ return this.request(body);
7198
7407
  const token = typeof body.token === "string" && /^[A-Za-z0-9_-]{43}$/.test(body.token) ? body.token : "";
7199
7408
  const key = hash(token);
7200
7409
  const entry = this.entries.get(key);
7201
7410
  const expected = url.pathname === "/pool-enroll/start" ? "invited" : url.pathname === "/pool-enroll/verify" ? "started" : url.pathname === "/pool-enroll/confirm" ? "verified" : "connected";
7202
7411
  if (!entry || entry.expiresAt <= this.now() || entry.state.kind !== expected)
7203
- throw new EnrollmentError(410, "This link has expired or was already used. Ask the operator for a new link.");
7412
+ throw new EnrollmentError(410, "This sign-in has expired or was already used. Start again.");
7204
7413
  const state = entry.state;
7205
7414
  entry.state = { kind: "busy" };
7206
7415
  try {
@@ -7219,12 +7428,12 @@ class EnrollmentHandlers {
7219
7428
  try {
7220
7429
  seat = await this.oauth.verifyClaudeSeat(state.flow, body.code.trim());
7221
7430
  } catch {
7222
- throw new EnrollmentError(400, "Claude sign-in could not be verified. Ask for a new link and try again.");
7431
+ throw new EnrollmentError(400, "Claude sign-in could not be verified. Start again.");
7223
7432
  }
7224
7433
  this.assertCurrent(key, entry);
7225
7434
  const { email } = seat.identity;
7226
7435
  if (!email || entry.details.email && email.toLowerCase() !== entry.details.email.toLowerCase())
7227
- throw new EnrollmentError(400, "This Claude account does not match the invitation. Ask for a new link.");
7436
+ throw new EnrollmentError(400, "This Claude account does not match your email. Start again and sign in with the matching account.");
7228
7437
  this.assertNewSeat(entry.details.name, seat);
7229
7438
  return json({
7230
7439
  ...this.mint(entry.details, { kind: "verified", seat }, tenMinutes),
@@ -7267,7 +7476,7 @@ class EnrollmentHandlers {
7267
7476
  }
7268
7477
  assertCurrent(key, entry) {
7269
7478
  if (this.entries.get(key) !== entry || entry.expiresAt <= this.now())
7270
- throw new EnrollmentError(410, "Sign-in expired. Ask for a new link.");
7479
+ throw new EnrollmentError(410, "Sign-in expired. Start again.");
7271
7480
  }
7272
7481
  assertNewSeat(name, seat) {
7273
7482
  if (this.context.dbOps.getAccountByName(name) || this.context.dbOps.getAccountsByProvider("claude-code").some((account2) => account2.account_uuid === seat.identity.accountUuid))
@@ -7347,11 +7556,13 @@ async function readBody(req) {
7347
7556
  }
7348
7557
  var paths, tenMinutes = 600000, oneDay = 86400000, headers2, json = (value, status = 200) => Response.json(value, { status, headers: headers2 }), hash = (token) => createHash("sha256").update(token).digest("hex"), EnrollmentError;
7349
7558
  var init_enrollment = __esm(() => {
7559
+ init_src2();
7350
7560
  init_src8();
7351
7561
  init_src();
7352
7562
  init_enrollment_page();
7353
7563
  paths = new Set([
7354
7564
  "/pool-enroll",
7565
+ "/pool-enroll/request",
7355
7566
  "/pool-enroll/start",
7356
7567
  "/pool-enroll/verify",
7357
7568
  "/pool-enroll/confirm",
@@ -7768,8 +7979,10 @@ class APIRouter {
7768
7979
  this.registerHandlers();
7769
7980
  }
7770
7981
  registerHandlers() {
7771
- if (this.enrollment)
7982
+ if (this.enrollment) {
7772
7983
  this.staticHandlers.set("POST:/api/enrollment-invitations", this.enrollment.issue);
7984
+ this.staticHandlers.set("POST:/api/enrollment-emails", this.enrollment.emails);
7985
+ }
7773
7986
  const { config, dbOps, getProviders, getRuntimeHealth, providerRegistry } = this.context;
7774
7987
  const healthHandler = createHealthHandler(dbOps, config, getProviders, getRuntimeHealth);
7775
7988
  const statsHandler = createStatsHandler(dbOps);
@@ -11378,7 +11591,7 @@ var init_bootstrap_runtime = __esm(() => {
11378
11591
 
11379
11592
  // ../../packages/runtime-server/src/dashboard-assets.ts
11380
11593
  import { readFileSync as readFileSync2 } from "fs";
11381
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve as resolve2 } from "path";
11594
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve as resolve2 } from "path";
11382
11595
  function resolveConfiguredPath(configuredPath) {
11383
11596
  return isAbsolute2(configuredPath) ? configuredPath : resolve2(process.cwd(), configuredPath);
11384
11597
  }
@@ -11389,7 +11602,7 @@ function resolveDashboardManifestPath() {
11389
11602
  }
11390
11603
  const configuredDistDir = process.env[DASHBOARD_DIST_DIR_ENV]?.trim();
11391
11604
  if (configuredDistDir) {
11392
- return join6(resolveConfiguredPath(configuredDistDir), "manifest.json");
11605
+ return join7(resolveConfiguredPath(configuredDistDir), "manifest.json");
11393
11606
  }
11394
11607
  return Bun.resolveSync(DASHBOARD_MANIFEST_EXPORT, process.cwd());
11395
11608
  }
@@ -11408,7 +11621,7 @@ function loadDashboardAssets() {
11408
11621
  return dashboardAssets;
11409
11622
  }
11410
11623
  function resolveDashboardAsset(assetPath) {
11411
- return join6(loadDashboardAssets().distDir, assetPath.replace(/^\//, ""));
11624
+ return join7(loadDashboardAssets().distDir, assetPath.replace(/^\//, ""));
11412
11625
  }
11413
11626
  function serveDashboardFile(assetPath, contentType, cacheControl) {
11414
11627
  const fullPath = resolveDashboardAsset(assetPath);
@@ -13400,7 +13613,7 @@ import {
13400
13613
  writeFileSync as writeFileSync2
13401
13614
  } from "fs";
13402
13615
  import { homedir as homedir2 } from "os";
13403
- import { dirname as dirname4, join as join7 } from "path";
13616
+ import { dirname as dirname4, join as join8 } from "path";
13404
13617
  function shellQuote(value) {
13405
13618
  return `'${value.replaceAll("'", `'"'"'`)}'`;
13406
13619
  }
@@ -13446,7 +13659,7 @@ ${overrides} \\
13446
13659
  async function installClientWrappers(options) {
13447
13660
  mkdirSync4(options.binDir, { recursive: true, mode: 448 });
13448
13661
  if (options.claudePath) {
13449
- const claude = join7(options.binDir, "claude");
13662
+ const claude = join8(options.binDir, "claude");
13450
13663
  writeFileSync2(claude, claudeWrapper(options.claudePath, proxyBaseUrl(options.port, "claude-code", "anthropic-messages", options.providerPoolWires)), {
13451
13664
  encoding: "utf8",
13452
13665
  mode: 448
@@ -13454,7 +13667,7 @@ async function installClientWrappers(options) {
13454
13667
  chmodSync(claude, 448);
13455
13668
  }
13456
13669
  if (options.codexPath) {
13457
- const codex = join7(options.binDir, "codex");
13670
+ const codex = join8(options.binDir, "codex");
13458
13671
  writeFileSync2(codex, codexWrapper(options.codexPath, proxyBaseUrl(options.port, "codex", "openai-responses", options.providerPoolWires)), {
13459
13672
  encoding: "utf8",
13460
13673
  mode: 448
@@ -13464,14 +13677,14 @@ async function installClientWrappers(options) {
13464
13677
  }
13465
13678
  function defaultShellProfile() {
13466
13679
  const shell = process.env.SHELL?.split("/").pop();
13467
- return join7(homedir2(), shell === "bash" ? ".bashrc" : ".zshrc");
13680
+ return join8(homedir2(), shell === "bash" ? ".bashrc" : ".zshrc");
13468
13681
  }
13469
13682
  function getLocalRoutingPaths() {
13470
13683
  const configDir = dirname4(resolveConfigPath());
13471
13684
  return {
13472
- binDir: join7(configDir, "client-bin"),
13685
+ binDir: join8(configDir, "client-bin"),
13473
13686
  shellProfile: defaultShellProfile(),
13474
- statePath: join7(configDir, "client-routing.json")
13687
+ statePath: join8(configDir, "client-routing.json")
13475
13688
  };
13476
13689
  }
13477
13690
  function isExecutable(path) {
@@ -13486,7 +13699,7 @@ function findClientExecutable(name, binDir, path) {
13486
13699
  for (const directory of path.split(":")) {
13487
13700
  if (!directory || directory === binDir)
13488
13701
  continue;
13489
- const candidate = join7(directory, name);
13702
+ const candidate = join8(directory, name);
13490
13703
  if (isExecutable(candidate))
13491
13704
  return candidate;
13492
13705
  }
@@ -13517,7 +13730,7 @@ function getLocalRoutingStatus(paths2 = {}) {
13517
13730
  shellProfileHasRouting = existsSync3(shellProfile) && readFileSync3(shellProfile, "utf8").includes(ROUTING_BEGIN);
13518
13731
  } catch {}
13519
13732
  return {
13520
- enabled: shellProfileHasRouting && (existsSync3(join7(binDir, "claude")) || existsSync3(join7(binDir, "codex"))),
13733
+ enabled: shellProfileHasRouting && (existsSync3(join8(binDir, "claude")) || existsSync3(join8(binDir, "codex"))),
13521
13734
  binDir,
13522
13735
  shellProfile
13523
13736
  };
@@ -13567,7 +13780,7 @@ function disableLocalClientRouting(paths2 = {}) {
13567
13780
  writeFileSync2(shellProfile, removeRoutingFromShellProfile(readFileSync3(shellProfile, "utf8")), { encoding: "utf8", mode: 384 });
13568
13781
  }
13569
13782
  for (const name of ["claude", "codex"]) {
13570
- const wrapper = join7(binDir, name);
13783
+ const wrapper = join8(binDir, name);
13571
13784
  if (existsSync3(wrapper))
13572
13785
  unlinkSync2(wrapper);
13573
13786
  }
@@ -13826,7 +14039,7 @@ __export(exports_theme, {
13826
14039
  });
13827
14040
  import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
13828
14041
  import { homedir as homedir3 } from "os";
13829
- import { join as join8 } from "path";
14042
+ import { join as join9 } from "path";
13830
14043
  function loadPersistedThemeId() {
13831
14044
  try {
13832
14045
  if (existsSync4(CONFIG_FILE)) {
@@ -13868,8 +14081,8 @@ function getAvailableThemes() {
13868
14081
  var CONFIG_DIR, CONFIG_FILE, currentThemeId, currentColors, C, NAV_ITEMS, SCREEN_TITLES;
13869
14082
  var init_theme = __esm(() => {
13870
14083
  init_src12();
13871
- CONFIG_DIR = join8(homedir3(), ".config", "ccflare");
13872
- CONFIG_FILE = join8(CONFIG_DIR, "theme.json");
14084
+ CONFIG_DIR = join9(homedir3(), ".config", "ccflare");
14085
+ CONFIG_FILE = join9(CONFIG_DIR, "theme.json");
13873
14086
  currentThemeId = loadPersistedThemeId();
13874
14087
  currentColors = themeToTuiColors(getThemeById(currentThemeId) ?? getDefaultTheme("dark"));
13875
14088
  C = new Proxy({}, {
@@ -17477,8 +17690,10 @@ var init_main = __esm(() => {
17477
17690
  });
17478
17691
  });
17479
17692
 
17693
+ // src/bundled-assets.ts
17694
+ import { join } from "path";
17695
+ process.env.CF_DASHBOARD_DIST_DIR ??= join(import.meta.dir, "dashboard");
17696
+ process.env.CF_USAGE_WORKER_PATH ??= join(import.meta.dir, "post-processor.worker.js");
17697
+
17480
17698
  // src/bin.ts
17481
- import { join as join9 } from "path";
17482
- process.env.CF_DASHBOARD_DIST_DIR ??= join9(import.meta.dir, "dashboard");
17483
- process.env.CF_USAGE_WORKER_PATH ??= join9(import.meta.dir, "post-processor.worker.js");
17484
17699
  await Promise.resolve().then(() => (init_main(), exports_main));