@openscout/scout 0.2.17 → 0.2.19

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.
@@ -0,0 +1 @@
1
+ :root{--bg: #F9F9F8;--surface: #FFFFFF;--ink: #1C1C1A;--muted: #8A8A86;--dim: #C4C4C0;--border: #E4E4E2;--accent: #0066FF;--accent-soft: rgba(0, 102, 255, .08);--green: #22c55e;--red: #dc2626;--radius: 8px;--shadow-soft: rgba(24, 24, 22, .06);--sidebar-w: 280px;--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif: "Spectral", Georgia, serif;font-family:var(--font-sans);font-size:13px;line-height:1.5;color:var(--ink);background:var(--bg);-webkit-font-smoothing:antialiased}*{box-sizing:border-box;margin:0}body{margin:0}.s-app{display:flex;height:100vh;overflow:hidden}.s-sidebar{width:var(--sidebar-w);flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border);background:var(--surface);height:100vh;overflow:hidden}.s-content{flex:1;min-width:0;height:100vh;overflow-y:auto;background:var(--bg)}.s-sidebar-header{padding:16px 16px 12px;flex-shrink:0}.s-logo{font-family:var(--font-serif);font-size:18px;font-weight:600;letter-spacing:-.02em;cursor:pointer;-webkit-user-select:none;user-select:none}.s-sidebar-footer{flex-shrink:0;padding:8px;border-top:1px solid var(--border)}.s-nav-item{display:flex;align-items:center;gap:8px;width:100%;border:none;background:none;padding:6px 8px;border-radius:6px;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:background .12s ease,color .12s ease}.s-nav-item:hover{background:var(--bg);color:var(--ink)}.s-nav-item-active{background:var(--accent-soft);color:var(--accent)}.s-sidebar-label{padding:4px 16px 6px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);flex-shrink:0}.s-sidebar-list{flex:1;overflow-y:auto;padding-bottom:12px}.s-sidebar-empty{padding:24px 16px;text-align:center;font-size:11px;color:var(--dim)}.s-sidebar-row{display:flex;align-items:center;gap:8px;padding:8px 12px;margin:0 6px;border-radius:6px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s ease}.s-sidebar-row:hover{background:var(--bg)}.s-sidebar-row-active,.s-sidebar-row-active:hover{background:var(--accent-soft)}.s-sidebar-row-body{flex:1;min-width:0}.s-sidebar-row-header{display:flex;align-items:center;gap:5px}.s-sidebar-row-name{font-size:12px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-sidebar-row-preview{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px;line-height:1.3}.s-sidebar-row-preview-empty{font-style:italic;color:var(--dim)}.s-sidebar-row-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;align-self:flex-start;margin-top:2px}.s-welcome{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dim)}.s-welcome-inner{text-align:center}.s-welcome h2{font-family:var(--font-serif);font-size:24px;font-weight:600;color:var(--dim);margin-bottom:4px}.s-welcome p{font-size:12px}.s-home{padding:32px;height:100vh;overflow-y:auto}.s-home-header{margin-bottom:32px}.s-home-header h2{font-family:var(--font-serif);font-size:22px;font-weight:600;color:var(--ink);margin-bottom:4px}.s-home-header p{font-size:12px;color:var(--muted)}.s-home-section{margin-bottom:28px}.s-home-section-title{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);margin-bottom:10px}.s-home-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;max-width:480px}.s-home-card-row{display:flex;align-items:center;gap:10px;padding:10px 14px;font-size:12px}.s-home-card-row+.s-home-card-row{border-top:1px solid var(--border)}.s-home-card-row-label{color:var(--muted);font-size:11px;min-width:80px;flex-shrink:0}.s-home-card-row-value{color:var(--ink);font-family:var(--font-mono);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-home-steps{list-style:none;padding:0;max-width:480px}.s-home-steps li{display:flex;align-items:baseline;gap:10px;padding:6px 0;font-size:12px;color:var(--ink);line-height:1.5}.s-home-steps li:before{content:attr(data-step);font-size:10px;font-weight:700;font-family:var(--font-mono);color:var(--muted);flex-shrink:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bg);border:1px solid var(--border)}.s-home-steps code{font-family:var(--font-mono);font-size:11px;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 5px}.s-home-card-row-clickable{cursor:pointer;transition:background .1s ease}.s-home-card-row-clickable:hover{background:var(--bg)}.s-activity-stream{max-width:560px}.s-activity-row{display:flex;align-items:baseline;gap:8px;padding:4px 0;font-size:12px;line-height:1.5}.s-activity-row-clickable{cursor:pointer;border-radius:4px;padding:4px 6px;margin:0 -6px;transition:background .1s ease}.s-activity-row-clickable:hover{background:var(--surface)}.s-activity-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;min-width:36px}.s-activity-actor{font-weight:600;color:var(--ink);flex-shrink:0}.s-activity-kind{color:var(--muted)}.s-activity-title{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-activity-list{max-width:560px}.s-activity-list-row{display:flex;align-items:flex-start;gap:10px;padding:8px 0}.s-activity-list-row+.s-activity-list-row{border-top:1px solid var(--border)}.s-activity-list-row-clickable{cursor:pointer;border-radius:6px;padding:8px;margin:0 -8px;transition:background .1s ease}.s-activity-list-row-clickable:hover{background:var(--surface)}.s-activity-list-body{flex:1;min-width:0}.s-activity-list-header{display:flex;align-items:baseline;gap:6px}.s-activity-list-actor{font-size:12px;font-weight:600;color:var(--ink)}.s-activity-list-kind{font-size:11px;color:var(--muted)}.s-activity-list-title{font-size:12px;color:var(--ink);margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.s-activity-list-summary{font-size:11px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-error{color:var(--red);font-size:12px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--radius);margin:12px}.s-empty{padding:32px 0;color:var(--muted);font-size:12px}.s-empty p:first-child{font-size:13px;font-weight:500;color:var(--ink);margin-bottom:4px}.s-meta{font-size:11px;color:var(--muted);font-family:var(--font-mono)}.s-spacer{flex:1}.s-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex-shrink:0}.s-dot-sm{width:5px;height:5px}.s-badge{font-size:10px;font-family:var(--font-mono);color:var(--muted);background:var(--bg);border-radius:4px;padding:1px 6px;flex-shrink:0;text-transform:uppercase;letter-spacing:.04em}.s-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;letter-spacing:.01em}.s-back{border:none;background:none;padding:0;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:color .12s ease}.s-back:hover{color:var(--ink)}.s-chevron{width:12px;height:12px;flex-shrink:0;position:relative;opacity:.3;transition:opacity .14s ease}.s-chevron:before{content:"";position:absolute;top:3px;left:2px;width:6px;height:6px;border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(-45deg)}.s-avatar{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff}.s-avatar-sm{width:28px;height:28px;font-size:11px}.s-avatar-lg{width:56px;height:56px;font-size:20px}.s-conversation{display:flex;flex-direction:column;height:100vh}.s-conv-header{display:flex;align-items:center;gap:10px;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--bg);flex-shrink:0;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .12s ease}.s-conv-header:hover{background:#f6f6f5}.s-conv-header .s-back{margin-right:2px;font-size:16px;line-height:1}.s-conv-header-info{flex:1;min-width:0}.s-conv-header-name{font-size:13px;font-weight:600;color:var(--ink)}.s-conv-header-state{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--muted);margin-top:1px}.s-messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:6px}.s-msg{max-width:85%;align-self:flex-start}.s-msg-you{align-self:flex-end}.s-msg-header{display:flex;align-items:baseline;gap:6px;margin-bottom:2px;padding:0 2px}.s-msg-actor{font-size:11px;font-weight:600;color:var(--ink)}.s-msg-time{font-size:10px;color:var(--dim);font-family:var(--font-mono)}.s-msg-body{font-size:12px;line-height:1.5;color:var(--ink);white-space:pre-wrap;word-break:break-word;padding:8px 12px;border-radius:14px 14px 14px 4px;background:var(--surface);border:1px solid var(--border)}.s-msg-you .s-msg-body{border-radius:14px 14px 4px;background:var(--accent-soft);border-color:#0066ff1f}.s-msg-you .s-msg-header{justify-content:flex-end}.s-compose{display:flex;align-items:center;gap:8px;padding:10px 20px;border-top:1px solid var(--border);background:var(--bg);flex-shrink:0}.s-compose-input{flex:1;border:1px solid var(--border);background:var(--surface);border-radius:20px;padding:8px 14px;font-family:var(--font-sans);font-size:12px;color:var(--ink);outline:none;transition:border-color .12s ease}.s-compose-input:focus{border-color:var(--accent)}.s-compose-input:disabled{opacity:.5;cursor:not-allowed}.s-compose-send{width:32px;height:32px;border-radius:50%;border:none;background:var(--ink);color:#fff;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:opacity .12s ease}.s-compose-send:disabled{opacity:.3;cursor:not-allowed}.s-agent-profile{display:flex;flex-direction:column;align-items:flex-start;padding:8px 0 16px;gap:4px}.s-agent-profile-name{font-size:18px;font-weight:600;color:var(--ink);margin-top:8px}.s-agent-profile-handle{font-size:12px;font-family:var(--font-mono);color:var(--muted)}.s-agent-profile-state{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);margin-top:4px}.s-agent-details{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin-top:16px;max-width:480px}.s-detail-row{display:flex;align-items:baseline;justify-content:space-between;padding:7px 14px;font-size:12px;gap:12px}.s-detail-row+.s-detail-row{border-top:1px solid var(--border)}.s-detail-label{color:var(--muted);flex-shrink:0;font-size:11px}.s-detail-value{color:var(--ink);font-family:var(--font-mono);font-size:11px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-section-title{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:16px}.s-actions{display:flex;gap:8px;margin-bottom:16px}.s-btn{border:1px solid var(--border);background:var(--surface);padding:6px 14px;border-radius:var(--radius);font-family:var(--font-sans);font-size:12px;font-weight:500;cursor:pointer;transition:all .14s cubic-bezier(.16,1,.3,1)}.s-btn:hover{border-color:var(--dim);box-shadow:0 1px 2px var(--shadow-soft)}.s-btn:disabled{opacity:.4;cursor:not-allowed}.s-btn-primary{background:var(--ink);color:var(--bg);border-color:var(--ink)}.s-btn-primary:hover{background:#2a2a28;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr{max-width:240px;margin:20px 0;padding:16px;background:var(--surface);border:1px solid var(--border);border-radius:12px;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr svg{display:block;width:100%;height:auto}.s-pair-meta{max-width:360px;margin:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-pair-row{display:flex;align-items:center;justify-content:space-between;padding:7px 14px;font-size:12px}.s-pair-row+.s-pair-row{border-top:1px solid var(--border)}.s-pair-label{color:var(--muted);flex-shrink:0}.s-pair-value{display:flex;align-items:center;color:var(--ink);text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-pair-mono{font-family:var(--font-mono);font-size:11px}.s-sidebar-nav{padding:0 8px 4px;flex-shrink:0}.s-nav-badge{font-size:10px;font-weight:600;font-family:var(--font-mono);color:#fff;background:var(--green);border-radius:10px;padding:0 6px;min-width:18px;text-align:center;line-height:16px;margin-left:auto}.s-sidebar-avatar-wrap{position:relative;flex-shrink:0}.s-task-indicator{position:absolute;bottom:-1px;right:-1px;width:8px;height:8px;border-radius:50%;background:var(--green);border:2px solid var(--surface)}.s-sidebar-row-active .s-task-indicator{border-color:#0066ff14}.s-flight-banner{display:flex;align-items:center;gap:6px;padding:6px 20px;background:#22c55e0f;border-bottom:1px solid rgba(34,197,94,.12);font-size:11px;flex-shrink:0}.s-flight-banner-dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex-shrink:0;animation:s-pulse 2s ease-in-out infinite}@keyframes s-pulse{0%,to{opacity:1}50%{opacity:.4}}.s-flight-banner-label{font-weight:600;color:var(--ink)}.s-flight-banner-summary{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-flights{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-flight-row{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface);cursor:pointer;transition:background .12s ease}.s-flight-row:hover{background:#f6f6f5}.s-flight-row+.s-flight-row{border-top:1px solid var(--border)}.s-flight-body{flex:1;min-width:0}.s-flight-header{display:flex;align-items:center;gap:6px}.s-flight-name{font-size:12px;font-weight:600;color:var(--ink)}.s-flight-state{font-size:11px;font-family:var(--font-mono);font-weight:500}.s-flight-summary{font-size:11px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-content>div:not(.s-conversation):not(.s-welcome):not(.s-home):not(.s-agents-layout){padding:24px 32px;max-width:640px}.s-agents-layout{display:flex;height:100vh;overflow:hidden}.s-agents-list-panel{width:100%;height:100vh;overflow-y:auto;padding:24px 16px;transition:width .15s ease}.s-agents-layout-split .s-agents-list-panel{width:280px;flex-shrink:0;border-right:1px solid var(--border);padding:16px 8px}.s-agents-detail-panel{flex:1;min-width:0;height:100vh;overflow-y:auto;padding:24px 32px}.s-agent-list-row{display:flex;align-items:center;gap:10px;padding:8px;border-radius:6px;cursor:pointer;transition:background .1s ease}.s-agent-list-row:hover{background:var(--bg)}.s-agent-list-row-active,.s-agent-list-row-active:hover{background:var(--accent-soft)}.s-agent-list-body{flex:1;min-width:0}.s-agent-list-header{display:flex;align-items:center;gap:5px}.s-agent-list-name{font-size:12px;font-weight:600;color:var(--ink)}.s-agent-list-qualifier{font-size:10px;font-family:var(--font-mono);color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-agent-list-meta{display:flex;align-items:center;gap:8px;margin-top:1px;font-size:11px;color:var(--muted)}.s-agent-list-meta span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-agent-detail-header{display:flex;align-items:center;gap:16px;margin-bottom:20px}.s-agent-detail-name{font-size:18px;font-weight:600;color:var(--ink);display:flex;align-items:baseline;gap:8px}.s-agent-detail-qualifier{font-size:12px;font-family:var(--font-mono);color:var(--dim);font-weight:400}.s-agent-detail-state{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);margin-top:2px}.s-agent-detail-meta{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;max-width:480px}.s-agent-detail-section{margin-top:20px}.s-agent-detail-messages{max-width:480px}.s-agent-detail-msg{display:flex;align-items:baseline;gap:6px;padding:4px 0;font-size:12px}.s-agent-detail-msg+.s-agent-detail-msg{border-top:1px solid var(--border)}.s-agent-detail-msg-actor{font-weight:600;color:var(--ink);flex-shrink:0}.s-agent-detail-msg-body{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:767px){.s-sidebar{width:100%;position:absolute;inset:0;z-index:20;transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-content{position:absolute;inset:0;z-index:10;transform:translate(100%);transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-app{position:relative}.s-app-content-open .s-sidebar{transform:translate(-100%)}.s-app-content-open .s-content{transform:translate(0)}.s-conv-header .s-back{display:block}}@media(min-width:768px){.s-conv-header .s-back{display:none}}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--muted)}
@@ -7,8 +7,8 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com" />
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
9
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&family=Spectral:wght@500;600&display=swap" rel="stylesheet" />
10
- <script type="module" crossorigin src="/assets/index-D3-CRXol.js"></script>
11
- <link rel="stylesheet" crossorigin href="/assets/index-AbzmnQu4.css">
10
+ <script type="module" crossorigin src="/assets/index-Cm-Qrks0.js"></script>
11
+ <link rel="stylesheet" crossorigin href="/assets/index-DcR6uR4b.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/dist/main.mjs CHANGED
@@ -8736,7 +8736,9 @@ async function loadScoutAgentStatuses(input = {}) {
8736
8736
  });
8737
8737
  }
8738
8738
  async function upScoutAgent(input) {
8739
- return startLocalAgent(input);
8739
+ const status = await startLocalAgent(input);
8740
+ await registerScoutLocalAgentBinding({ agentId: status.agentId }).catch(() => {});
8741
+ return status;
8740
8742
  }
8741
8743
  async function downScoutAgent(agentId) {
8742
8744
  return stopLocalAgent(agentId);
@@ -9605,6 +9607,459 @@ var init_env = __esm(async () => {
9605
9607
  await init_service();
9606
9608
  });
9607
9609
 
9610
+ // ../runtime/dist/tailscale.js
9611
+ import { readFile as readFile7 } from "node:fs/promises";
9612
+ import { execFile } from "node:child_process";
9613
+ import { promisify } from "node:util";
9614
+ function parseStatusJson(raw) {
9615
+ const parsed = JSON.parse(raw);
9616
+ const peers = Object.entries(parsed.Peer ?? {});
9617
+ return peers.map(([fallbackId, peer]) => ({
9618
+ id: peer.ID ?? fallbackId,
9619
+ name: peer.HostName ?? peer.DNSName ?? fallbackId,
9620
+ dnsName: peer.DNSName,
9621
+ addresses: peer.TailscaleIPs ?? [],
9622
+ online: peer.Online ?? false,
9623
+ hostName: peer.HostName,
9624
+ os: peer.OS,
9625
+ tags: peer.Tags ?? []
9626
+ }));
9627
+ }
9628
+ async function readStatusFromFile(filePath) {
9629
+ const raw = await readFile7(filePath, "utf8");
9630
+ return parseStatusJson(raw);
9631
+ }
9632
+ async function readTailscalePeers() {
9633
+ const fixturePath = process.env.OPENSCOUT_TAILSCALE_STATUS_JSON;
9634
+ if (fixturePath) {
9635
+ return readStatusFromFile(fixturePath);
9636
+ }
9637
+ try {
9638
+ const tailscaleBin = process.env.OPENSCOUT_TAILSCALE_BIN ?? "tailscale";
9639
+ const { stdout } = await execFileAsync(tailscaleBin, ["status", "--json"]);
9640
+ return parseStatusJson(stdout);
9641
+ } catch {
9642
+ return [];
9643
+ }
9644
+ }
9645
+ var execFileAsync;
9646
+ var init_tailscale = __esm(() => {
9647
+ execFileAsync = promisify(execFile);
9648
+ });
9649
+
9650
+ // ../../apps/desktop/src/core/mesh/service.ts
9651
+ function readMeshEnvVars() {
9652
+ return {
9653
+ meshId: process.env.OPENSCOUT_MESH_ID ?? null,
9654
+ meshSeeds: process.env.OPENSCOUT_MESH_SEEDS ?? null,
9655
+ brokerHost: process.env.OPENSCOUT_BROKER_HOST ?? null,
9656
+ brokerPort: process.env.OPENSCOUT_BROKER_PORT ?? null,
9657
+ brokerUrl: process.env.OPENSCOUT_BROKER_URL ?? null,
9658
+ nodeId: process.env.OPENSCOUT_NODE_ID ?? null,
9659
+ nodeName: process.env.OPENSCOUT_NODE_NAME ?? null,
9660
+ discoveryIntervalMs: process.env.OPENSCOUT_MESH_DISCOVERY_INTERVAL_MS ?? null
9661
+ };
9662
+ }
9663
+ async function readTailscaleStatus() {
9664
+ const peers = await readTailscalePeers();
9665
+ return {
9666
+ available: peers.length > 0,
9667
+ peers,
9668
+ onlineCount: peers.filter((p) => p.online).length
9669
+ };
9670
+ }
9671
+ function computeWarnings(health, localNode, nodes, tailscale) {
9672
+ const warnings = [];
9673
+ if (!health.reachable) {
9674
+ warnings.push("Broker is not reachable. Run `scout setup` to start it.");
9675
+ return warnings;
9676
+ }
9677
+ if (localNode?.advertiseScope === "local") {
9678
+ warnings.push("Broker is bound to 127.0.0.1 — mesh peers cannot reach it. " + "Set OPENSCOUT_BROKER_HOST=0.0.0.0 or use your Tailscale IP.");
9679
+ }
9680
+ const remoteNodes = Object.values(nodes).filter((n) => n.id !== localNode?.id);
9681
+ if (remoteNodes.length === 0 && tailscale.onlineCount > 0) {
9682
+ warnings.push(`Tailscale has ${tailscale.onlineCount} online peer${tailscale.onlineCount === 1 ? "" : "s"} but no remote nodes were discovered. ` + "Run `scout mesh discover` to probe peers.");
9683
+ }
9684
+ if (!tailscale.available && remoteNodes.length === 0) {
9685
+ warnings.push("No Tailscale peers found and no mesh seeds configured. " + "Install Tailscale or set OPENSCOUT_MESH_SEEDS.");
9686
+ }
9687
+ return warnings;
9688
+ }
9689
+ async function loadMeshStatus() {
9690
+ const brokerUrl = resolveScoutBrokerUrl();
9691
+ const [health, context, tailscale] = await Promise.all([
9692
+ readScoutBrokerHealth(brokerUrl),
9693
+ loadScoutBrokerContext(brokerUrl),
9694
+ readTailscaleStatus()
9695
+ ]);
9696
+ const localNode = context?.node ?? null;
9697
+ const nodes = context?.snapshot.nodes ?? {};
9698
+ const meshId = health.meshId ?? localNode?.meshId ?? null;
9699
+ const warnings = computeWarnings(health, localNode, nodes, tailscale);
9700
+ return { brokerUrl, health, localNode, meshId, nodes, tailscale, warnings };
9701
+ }
9702
+ async function loadMeshDoctorReport() {
9703
+ const status = await loadMeshStatus();
9704
+ return { ...status, envVars: readMeshEnvVars() };
9705
+ }
9706
+ async function runMeshDiscover() {
9707
+ const brokerUrl = resolveScoutBrokerUrl();
9708
+ const tailscalePeers = await readTailscalePeers();
9709
+ const response = await fetch(new URL("/v1/mesh/discover", brokerUrl), {
9710
+ method: "POST",
9711
+ headers: { "content-type": "application/json", accept: "application/json" },
9712
+ body: JSON.stringify({})
9713
+ });
9714
+ if (!response.ok) {
9715
+ throw new Error(`Mesh discover failed: ${response.status} ${await response.text()}`);
9716
+ }
9717
+ const result = await response.json();
9718
+ return {
9719
+ brokerUrl,
9720
+ discovered: result.discovered,
9721
+ probes: result.probes ?? [],
9722
+ tailscalePeers
9723
+ };
9724
+ }
9725
+ async function runMeshPing(target) {
9726
+ const brokerUrl = resolveScoutBrokerUrl();
9727
+ const context = await loadScoutBrokerContext(brokerUrl);
9728
+ const localMeshId = context?.node.meshId ?? null;
9729
+ let probeUrl;
9730
+ if (target.startsWith("http://") || target.startsWith("https://")) {
9731
+ probeUrl = target;
9732
+ } else {
9733
+ const nodes = context?.snapshot.nodes ?? {};
9734
+ const matchedNode = Object.values(nodes).find((n) => n.id === target || n.name === target || n.hostName === target);
9735
+ if (matchedNode?.brokerUrl) {
9736
+ probeUrl = matchedNode.brokerUrl;
9737
+ } else {
9738
+ probeUrl = `http://${target}:65535`;
9739
+ }
9740
+ }
9741
+ const start = performance.now();
9742
+ try {
9743
+ const controller = new AbortController;
9744
+ const timeout = setTimeout(() => controller.abort(), 3000);
9745
+ const response = await fetch(new URL("/v1/node", probeUrl), {
9746
+ signal: controller.signal,
9747
+ headers: { accept: "application/json" }
9748
+ });
9749
+ clearTimeout(timeout);
9750
+ const latencyMs = Math.round(performance.now() - start);
9751
+ if (!response.ok) {
9752
+ return {
9753
+ target,
9754
+ url: probeUrl,
9755
+ reachable: false,
9756
+ latencyMs,
9757
+ node: null,
9758
+ meshIdMatch: false,
9759
+ localMeshId,
9760
+ error: `HTTP ${response.status}`
9761
+ };
9762
+ }
9763
+ const node = await response.json();
9764
+ return {
9765
+ target,
9766
+ url: probeUrl,
9767
+ reachable: true,
9768
+ latencyMs,
9769
+ node,
9770
+ meshIdMatch: localMeshId !== null && node.meshId === localMeshId,
9771
+ localMeshId,
9772
+ error: null
9773
+ };
9774
+ } catch (error) {
9775
+ return {
9776
+ target,
9777
+ url: probeUrl,
9778
+ reachable: false,
9779
+ latencyMs: Math.round(performance.now() - start),
9780
+ node: null,
9781
+ meshIdMatch: false,
9782
+ localMeshId,
9783
+ error: error instanceof Error ? error.message : String(error)
9784
+ };
9785
+ }
9786
+ }
9787
+ async function loadMeshNodes() {
9788
+ const brokerUrl = resolveScoutBrokerUrl();
9789
+ const context = await loadScoutBrokerContext(brokerUrl);
9790
+ return {
9791
+ brokerUrl,
9792
+ localNodeId: context?.node.id ?? null,
9793
+ nodes: context?.snapshot.nodes ?? {}
9794
+ };
9795
+ }
9796
+ var init_service4 = __esm(async () => {
9797
+ init_tailscale();
9798
+ await init_service();
9799
+ });
9800
+
9801
+ // ../../apps/desktop/src/ui/terminal/mesh.ts
9802
+ function dot(ok) {
9803
+ return ok ? "●" : "○";
9804
+ }
9805
+ function nodeScope(node) {
9806
+ return node.advertiseScope === "mesh" ? "mesh" : "local";
9807
+ }
9808
+ function ago(ts) {
9809
+ if (!ts)
9810
+ return "unknown";
9811
+ const seconds = Math.floor((Date.now() - ts) / 1000);
9812
+ if (seconds < 5)
9813
+ return "just now";
9814
+ if (seconds < 60)
9815
+ return `${seconds}s ago`;
9816
+ if (seconds < 3600)
9817
+ return `${Math.floor(seconds / 60)}m ago`;
9818
+ if (seconds < 86400)
9819
+ return `${Math.floor(seconds / 3600)}h ago`;
9820
+ return `${Math.floor(seconds / 86400)}d ago`;
9821
+ }
9822
+ function renderNodeBlock(node, isLocal) {
9823
+ const label = isLocal ? " (local)" : "";
9824
+ const lines = [
9825
+ ` ${dot(true)} ${node.name ?? node.id}${label}`,
9826
+ ` ID: ${node.id}`
9827
+ ];
9828
+ if (node.brokerUrl)
9829
+ lines.push(` Broker: ${node.brokerUrl}`);
9830
+ lines.push(` Scope: ${nodeScope(node)}`);
9831
+ if (node.hostName)
9832
+ lines.push(` Host: ${node.hostName}`);
9833
+ if (node.tailnetName)
9834
+ lines.push(` Tailnet: ${node.tailnetName}`);
9835
+ if (node.lastSeenAt)
9836
+ lines.push(` Last seen: ${ago(node.lastSeenAt)}`);
9837
+ return lines;
9838
+ }
9839
+ function renderMeshStatus(report) {
9840
+ const lines = [];
9841
+ lines.push("Mesh");
9842
+ lines.push(` ID: ${report.meshId ?? "unknown"}`);
9843
+ if (report.localNode) {
9844
+ lines.push(` Node: ${report.localNode.name} (${report.localNode.hostName ?? "unknown host"})`);
9845
+ }
9846
+ lines.push(` Broker: ${report.brokerUrl}`);
9847
+ lines.push(` Scope: ${report.localNode?.advertiseScope === "mesh" ? "mesh-reachable" : "local-only"}`);
9848
+ const tsLabel = report.tailscale.available ? `available (${report.tailscale.onlineCount} peer${report.tailscale.onlineCount === 1 ? "" : "s"} online)` : "not available";
9849
+ lines.push(` Tailscale: ${tsLabel}`);
9850
+ const allNodes = Object.values(report.nodes);
9851
+ const remoteNodes = allNodes.filter((n) => n.id !== report.localNode?.id);
9852
+ lines.push("");
9853
+ lines.push("Nodes");
9854
+ if (report.localNode) {
9855
+ lines.push(...renderNodeBlock(report.localNode, true));
9856
+ }
9857
+ if (remoteNodes.length > 0) {
9858
+ for (const node of remoteNodes) {
9859
+ lines.push(...renderNodeBlock(node, false));
9860
+ }
9861
+ } else {
9862
+ lines.push(" ○ No remote nodes discovered");
9863
+ }
9864
+ if (report.warnings.length > 0) {
9865
+ lines.push("");
9866
+ for (const w of report.warnings) {
9867
+ lines.push(`Tip: ${w}`);
9868
+ }
9869
+ }
9870
+ return lines.join(`
9871
+ `);
9872
+ }
9873
+ function renderMeshDoctor(report) {
9874
+ const lines = [];
9875
+ lines.push("Mesh diagnostics");
9876
+ lines.push("");
9877
+ lines.push("Local node:");
9878
+ if (report.localNode) {
9879
+ lines.push(` ID: ${report.localNode.id}`);
9880
+ lines.push(` Name: ${report.localNode.name}`);
9881
+ lines.push(` Mesh: ${report.localNode.meshId}`);
9882
+ lines.push(` Broker URL: ${report.brokerUrl}`);
9883
+ lines.push(` Advertise scope: ${report.localNode.advertiseScope}`);
9884
+ if (report.localNode.hostName) {
9885
+ lines.push(` Hostname: ${report.localNode.hostName}`);
9886
+ }
9887
+ } else {
9888
+ lines.push(" Broker not reachable — no local node info available.");
9889
+ }
9890
+ lines.push("");
9891
+ lines.push("Tailscale:");
9892
+ if (report.tailscale.available) {
9893
+ lines.push(` Status: available (${report.tailscale.peers.length} peer${report.tailscale.peers.length === 1 ? "" : "s"})`);
9894
+ for (const peer of report.tailscale.peers) {
9895
+ const ips = peer.addresses.join(", ");
9896
+ const status = peer.online ? "online" : "offline";
9897
+ lines.push(` ${dot(peer.online)} ${peer.name} (${ips}) — ${status}`);
9898
+ }
9899
+ } else {
9900
+ lines.push(" Status: not available (no peers found)");
9901
+ }
9902
+ const allNodes = Object.values(report.nodes);
9903
+ const remoteNodes = allNodes.filter((n) => n.id !== report.localNode?.id);
9904
+ lines.push("");
9905
+ lines.push("Remote nodes:");
9906
+ if (remoteNodes.length > 0) {
9907
+ for (const node of remoteNodes) {
9908
+ lines.push(...renderNodeBlock(node, false));
9909
+ }
9910
+ } else {
9911
+ lines.push(" None discovered");
9912
+ }
9913
+ if (report.warnings.length > 0) {
9914
+ lines.push("");
9915
+ lines.push("Warnings:");
9916
+ for (const w of report.warnings) {
9917
+ lines.push(` ! ${w}`);
9918
+ }
9919
+ }
9920
+ lines.push("");
9921
+ lines.push("Configuration:");
9922
+ const env = report.envVars;
9923
+ lines.push(` OPENSCOUT_MESH_ID: ${env.meshId ?? "(default: openscout)"}`);
9924
+ lines.push(` OPENSCOUT_MESH_SEEDS: ${env.meshSeeds || "(not set)"}`);
9925
+ lines.push(` OPENSCOUT_BROKER_HOST: ${env.brokerHost ?? "(default: 127.0.0.1)"}`);
9926
+ lines.push(` OPENSCOUT_BROKER_URL: ${env.brokerUrl ?? report.brokerUrl}`);
9927
+ lines.push(` OPENSCOUT_NODE_NAME: ${env.nodeName ?? "(default: hostname)"}`);
9928
+ lines.push(` OPENSCOUT_NODE_ID: ${env.nodeId ?? "(auto-derived)"}`);
9929
+ lines.push(` OPENSCOUT_MESH_DISCOVERY_INTERVAL_MS: ${env.discoveryIntervalMs ?? "0 (disabled)"}`);
9930
+ return lines.join(`
9931
+ `);
9932
+ }
9933
+ function renderMeshDiscover(report) {
9934
+ const lines = [];
9935
+ const onlinePeers = report.tailscalePeers.filter((p) => p.online);
9936
+ lines.push("Mesh discovery");
9937
+ lines.push(` Tailscale peers: ${onlinePeers.length} online`);
9938
+ lines.push(` Probed: ${report.probes.length} URL${report.probes.length === 1 ? "" : "s"}`);
9939
+ if (report.discovered.length > 0) {
9940
+ lines.push("");
9941
+ lines.push(`Found ${report.discovered.length} node${report.discovered.length === 1 ? "" : "s"}:`);
9942
+ for (const node of report.discovered) {
9943
+ lines.push(...renderNodeBlock(node, false));
9944
+ }
9945
+ } else {
9946
+ lines.push("");
9947
+ lines.push("No remote nodes found.");
9948
+ if (report.probes.length > 0) {
9949
+ lines.push(` Probed URLs: ${report.probes.slice(0, 5).join(", ")}${report.probes.length > 5 ? ` (+${report.probes.length - 5} more)` : ""}`);
9950
+ }
9951
+ }
9952
+ return lines.join(`
9953
+ `);
9954
+ }
9955
+ function renderMeshPing(report) {
9956
+ const lines = [];
9957
+ lines.push(`Ping ${report.target}`);
9958
+ lines.push(` URL: ${report.url}`);
9959
+ if (report.reachable) {
9960
+ lines.push(` Status: reachable (${report.latencyMs}ms)`);
9961
+ if (report.node) {
9962
+ lines.push(` Node: ${report.node.name} (${report.node.id})`);
9963
+ lines.push(` Mesh: ${report.node.meshId}`);
9964
+ if (report.meshIdMatch) {
9965
+ lines.push(" Mesh ID: matches local");
9966
+ } else if (report.localMeshId) {
9967
+ lines.push(` Mesh ID: mismatch (local=${report.localMeshId}, remote=${report.node.meshId})`);
9968
+ }
9969
+ if (report.node.hostName)
9970
+ lines.push(` Host: ${report.node.hostName}`);
9971
+ if (report.node.capabilities?.length) {
9972
+ lines.push(` Capabilities: ${report.node.capabilities.join(", ")}`);
9973
+ }
9974
+ }
9975
+ } else {
9976
+ lines.push(` Status: unreachable (${report.latencyMs}ms)`);
9977
+ if (report.error) {
9978
+ lines.push(` Error: ${report.error}`);
9979
+ }
9980
+ }
9981
+ return lines.join(`
9982
+ `);
9983
+ }
9984
+ function renderMeshNodes(input) {
9985
+ const allNodes = Object.values(input.nodes);
9986
+ if (allNodes.length === 0) {
9987
+ return "No nodes known. Run `scout mesh discover` to probe the mesh.";
9988
+ }
9989
+ const lines = [];
9990
+ lines.push(`${allNodes.length} node${allNodes.length === 1 ? "" : "s"}`);
9991
+ for (const node of allNodes) {
9992
+ const isLocal = node.id === input.localNodeId;
9993
+ lines.push(...renderNodeBlock(node, isLocal));
9994
+ }
9995
+ return lines.join(`
9996
+ `);
9997
+ }
9998
+
9999
+ // ../../apps/desktop/src/cli/commands/mesh.ts
10000
+ var exports_mesh = {};
10001
+ __export(exports_mesh, {
10002
+ runMeshCommand: () => runMeshCommand
10003
+ });
10004
+ async function runMeshCommand(context, args) {
10005
+ const subcommand = args[0] ?? "";
10006
+ switch (subcommand) {
10007
+ case "":
10008
+ case "status": {
10009
+ const report = await loadMeshStatus();
10010
+ context.output.writeValue(report, renderMeshStatus);
10011
+ return;
10012
+ }
10013
+ case "doctor": {
10014
+ const report = await loadMeshDoctorReport();
10015
+ context.output.writeValue(report, renderMeshDoctor);
10016
+ return;
10017
+ }
10018
+ case "nodes": {
10019
+ const result = await loadMeshNodes();
10020
+ context.output.writeValue(result, renderMeshNodes);
10021
+ return;
10022
+ }
10023
+ case "discover": {
10024
+ const report = await runMeshDiscover();
10025
+ context.output.writeValue(report, renderMeshDiscover);
10026
+ return;
10027
+ }
10028
+ case "ping": {
10029
+ const target = args[1]?.trim();
10030
+ if (!target) {
10031
+ context.stderr("Usage: scout mesh ping <node-id|name|url>");
10032
+ return;
10033
+ }
10034
+ const report = await runMeshPing(target);
10035
+ context.output.writeValue(report, renderMeshPing);
10036
+ return;
10037
+ }
10038
+ case "help":
10039
+ case "--help":
10040
+ case "-h": {
10041
+ context.output.writeText(MESH_HELP);
10042
+ return;
10043
+ }
10044
+ default: {
10045
+ context.stderr(`Unknown mesh subcommand: ${subcommand}`);
10046
+ context.output.writeText(MESH_HELP);
10047
+ }
10048
+ }
10049
+ }
10050
+ var MESH_HELP = `scout mesh — Mesh status and diagnostics
10051
+
10052
+ Subcommands:
10053
+ scout mesh Show mesh status (default)
10054
+ scout mesh doctor Full mesh diagnostics
10055
+ scout mesh nodes List known mesh nodes
10056
+ scout mesh discover Probe for remote mesh nodes
10057
+ scout mesh ping <node> Ping a specific node by ID, name, or URL
10058
+ `;
10059
+ var init_mesh2 = __esm(async () => {
10060
+ await init_service4();
10061
+ });
10062
+
9608
10063
  // ../../apps/desktop/src/core/pairing/runtime/config.ts
9609
10064
  import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
9610
10065
  import { homedir as homedir6 } from "node:os";
@@ -14797,7 +15252,7 @@ async function getScoutMobileActivity(filters = {}) {
14797
15252
  });
14798
15253
  }
14799
15254
  var DEFAULT_MOBILE_RECENT_TURN_LIMIT = 24, DEFAULT_MOBILE_HISTORY_PAGE_LIMIT = 40;
14800
- var init_service4 = __esm(async () => {
15255
+ var init_service5 = __esm(async () => {
14801
15256
  init_harness_catalog();
14802
15257
  init_setup();
14803
15258
  await __promiseAll([
@@ -15364,7 +15819,7 @@ var init_server = __esm(async () => {
15364
15819
  init_log();
15365
15820
  init_config2();
15366
15821
  init_security();
15367
- await init_service4();
15822
+ await init_service5();
15368
15823
  MARKER_FILES = [
15369
15824
  [".git", "git"],
15370
15825
  ["package.json", "node"],
@@ -30546,7 +31001,7 @@ var init_router = __esm(async () => {
30546
31001
  init_log();
30547
31002
  init_config2();
30548
31003
  init_db_queries();
30549
- await init_service4();
31004
+ await init_service5();
30550
31005
  t = initTRPC.context().create();
30551
31006
  logged = t.middleware(async ({ path: path2, type, next }) => {
30552
31007
  const start = Date.now();
@@ -31485,11 +31940,11 @@ function convertFormDataToBodyData(formData, options) {
31485
31940
  return form;
31486
31941
  }
31487
31942
  var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
31488
- const { all = false, dot = false } = options;
31943
+ const { all = false, dot: dot2 = false } = options;
31489
31944
  const headers = request instanceof HonoRequest ? request.raw.headers : request.headers;
31490
31945
  const contentType = headers.get("Content-Type");
31491
31946
  if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
31492
- return parseFormData(request, { all, dot });
31947
+ return parseFormData(request, { all, dot: dot2 });
31493
31948
  }
31494
31949
  return {};
31495
31950
  }, handleParsingAllValues = (form, key, value) => {
@@ -36662,7 +37117,7 @@ async function startScoutPairingSession(input) {
36662
37117
  throw error48;
36663
37118
  }
36664
37119
  }
36665
- var init_service5 = __esm(async () => {
37120
+ var init_service6 = __esm(async () => {
36666
37121
  await init_runtime2();
36667
37122
  });
36668
37123
 
@@ -36768,7 +37223,7 @@ async function runPairCommand(context, args) {
36768
37223
  }
36769
37224
  }
36770
37225
  var init_pair = __esm(async () => {
36771
- await init_service5();
37226
+ await init_service6();
36772
37227
  });
36773
37228
 
36774
37229
  // ../../apps/desktop/src/cli/commands/ps.ts
@@ -37278,6 +37733,8 @@ async function loadScoutCommandHandler(name) {
37278
37733
  return (await init_enroll().then(() => exports_enroll)).runEnrollCommand;
37279
37734
  case "env":
37280
37735
  return (await init_env().then(() => exports_env)).runEnvCommand;
37736
+ case "mesh":
37737
+ return (await init_mesh2().then(() => exports_mesh)).runMeshCommand;
37281
37738
  case "pair":
37282
37739
  return (await init_pair().then(() => exports_pair)).runPairCommand;
37283
37740
  case "ps":
@@ -37323,6 +37780,7 @@ var SCOUT_COMMANDS = [
37323
37780
  { name: "down", summary: "Stop one or all local agents" },
37324
37781
  { name: "ps", summary: "List configured local agents" },
37325
37782
  { name: "restart", summary: "Restart configured local agents" },
37783
+ { name: "mesh", summary: "Mesh status and diagnostics" },
37326
37784
  { name: "pair", summary: "Pair a companion device via QR" },
37327
37785
  { name: "server", summary: "Run the Scout web UI (Bun; see: scout server start / control-plane start)" },
37328
37786
  {
@@ -37344,7 +37802,7 @@ function findScoutCommandRegistration(name) {
37344
37802
  }
37345
37803
 
37346
37804
  // ../../apps/desktop/src/cli/help.ts
37347
- function renderScoutHelp(version2 = "0.2.17") {
37805
+ function renderScoutHelp(version2 = "0.2.18") {
37348
37806
  const commandLines = listScoutPrimaryCommands().map((command) => ` ${command.name.padEnd(12, " ")} ${command.summary}`).join(`
37349
37807
  `);
37350
37808
  const deprecatedLines = listScoutDeprecatedCommands().map((command) => ` ${command.name.padEnd(12, " ")} ${command.summary}`).join(`
@@ -37382,7 +37840,7 @@ function renderScoutHelp(version2 = "0.2.17") {
37382
37840
  init_options();
37383
37841
 
37384
37842
  // ../../apps/desktop/src/shared/product.ts
37385
- var SCOUT_APP_VERSION = "0.2.17";
37843
+ var SCOUT_APP_VERSION = "0.2.19";
37386
37844
 
37387
37845
  // ../../apps/desktop/src/cli/main.ts
37388
37846
  async function main2() {
@@ -7940,7 +7940,7 @@ init_setup();
7940
7940
 
7941
7941
  // apps/desktop/src/shared/product.ts
7942
7942
  var SCOUT_PRODUCT_NAME = "Scout";
7943
- var SCOUT_APP_VERSION = "0.2.17";
7943
+ var SCOUT_APP_VERSION = "0.2.19";
7944
7944
 
7945
7945
  // apps/desktop/src/shared/surface-capabilities.ts
7946
7946
  function resolveScoutSurfaceCapabilities(surface) {
@@ -11085,7 +11085,9 @@ init_src();
11085
11085
 
11086
11086
  // apps/desktop/src/core/agents/service.ts
11087
11087
  async function upScoutAgent(input) {
11088
- return startLocalAgent(input);
11088
+ const status = await startLocalAgent(input);
11089
+ await registerScoutLocalAgentBinding({ agentId: status.agentId }).catch(() => {});
11090
+ return status;
11089
11091
  }
11090
11092
 
11091
11093
  // apps/desktop/src/app/host/broker-actions.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openscout/scout",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Published Scout package that installs the `scout` command",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -22,6 +22,6 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@openscout/runtime": "0.2.17"
25
+ "@openscout/runtime": "0.2.19"
26
26
  }
27
27
  }
@@ -1 +0,0 @@
1
- :root{--bg: #F9F9F8;--surface: #FFFFFF;--ink: #1C1C1A;--muted: #8A8A86;--dim: #C4C4C0;--border: #E4E4E2;--accent: #0066FF;--accent-soft: rgba(0, 102, 255, .08);--green: #22c55e;--red: #dc2626;--radius: 8px;--shadow-soft: rgba(24, 24, 22, .06);--sidebar-w: 280px;--font-sans: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--font-serif: "Spectral", Georgia, serif;font-family:var(--font-sans);font-size:13px;line-height:1.5;color:var(--ink);background:var(--bg);-webkit-font-smoothing:antialiased}*{box-sizing:border-box;margin:0}body{margin:0}.s-app{display:flex;height:100vh;overflow:hidden}.s-sidebar{width:var(--sidebar-w);flex-shrink:0;display:flex;flex-direction:column;border-right:1px solid var(--border);background:var(--surface);height:100vh;overflow:hidden}.s-content{flex:1;min-width:0;height:100vh;overflow-y:auto;background:var(--bg)}.s-sidebar-header{padding:16px 16px 12px;flex-shrink:0}.s-logo{font-family:var(--font-serif);font-size:18px;font-weight:600;letter-spacing:-.02em;cursor:pointer;-webkit-user-select:none;user-select:none}.s-sidebar-footer{flex-shrink:0;padding:8px;border-top:1px solid var(--border)}.s-nav-item{display:flex;align-items:center;gap:8px;width:100%;border:none;background:none;padding:6px 8px;border-radius:6px;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:background .12s ease,color .12s ease}.s-nav-item:hover{background:var(--bg);color:var(--ink)}.s-nav-item-active{background:var(--accent-soft);color:var(--accent)}.s-sidebar-label{padding:4px 16px 6px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:var(--dim);flex-shrink:0}.s-sidebar-list{flex:1;overflow-y:auto;padding-bottom:12px}.s-sidebar-empty{padding:24px 16px;text-align:center;font-size:11px;color:var(--dim)}.s-sidebar-row{display:flex;align-items:center;gap:8px;padding:8px 12px;margin:0 6px;border-radius:6px;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .1s ease}.s-sidebar-row:hover{background:var(--bg)}.s-sidebar-row-active,.s-sidebar-row-active:hover{background:var(--accent-soft)}.s-sidebar-row-body{flex:1;min-width:0}.s-sidebar-row-header{display:flex;align-items:center;gap:5px}.s-sidebar-row-name{font-size:12px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-sidebar-row-preview{font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px;line-height:1.3}.s-sidebar-row-preview-empty{font-style:italic;color:var(--dim)}.s-sidebar-row-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;align-self:flex-start;margin-top:2px}.s-welcome{display:flex;align-items:center;justify-content:center;height:100%;color:var(--dim)}.s-welcome-inner{text-align:center}.s-welcome h2{font-family:var(--font-serif);font-size:24px;font-weight:600;color:var(--dim);margin-bottom:4px}.s-welcome p{font-size:12px}.s-error{color:var(--red);font-size:12px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:var(--radius);margin:12px}.s-empty{text-align:center;padding:48px 16px;color:var(--muted);font-size:12px}.s-empty p:first-child{font-size:13px;font-weight:500;color:var(--ink);margin-bottom:4px}.s-meta{font-size:11px;color:var(--muted);font-family:var(--font-mono)}.s-spacer{flex:1}.s-dot{display:inline-block;width:6px;height:6px;border-radius:50%;flex-shrink:0}.s-dot-sm{width:5px;height:5px}.s-badge{font-size:10px;font-family:var(--font-mono);color:var(--muted);background:var(--bg);border-radius:4px;padding:1px 6px;flex-shrink:0;text-transform:uppercase;letter-spacing:.04em}.s-time{font-size:10px;color:var(--dim);font-family:var(--font-mono);flex-shrink:0;letter-spacing:.01em}.s-back{border:none;background:none;padding:0;font-family:var(--font-sans);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:color .12s ease}.s-back:hover{color:var(--ink)}.s-chevron{width:12px;height:12px;flex-shrink:0;position:relative;opacity:.3;transition:opacity .14s ease}.s-chevron:before{content:"";position:absolute;top:3px;left:2px;width:6px;height:6px;border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(-45deg)}.s-avatar{width:32px;height:32px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#fff}.s-avatar-sm{width:28px;height:28px;font-size:11px}.s-avatar-lg{width:56px;height:56px;font-size:20px}.s-conversation{display:flex;flex-direction:column;height:100vh}.s-conv-header{display:flex;align-items:center;gap:10px;padding:10px 20px;border-bottom:1px solid var(--border);background:var(--bg);flex-shrink:0;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .12s ease}.s-conv-header:hover{background:#f6f6f5}.s-conv-header .s-back{margin-right:2px;font-size:16px;line-height:1}.s-conv-header-info{flex:1;min-width:0}.s-conv-header-name{font-size:13px;font-weight:600;color:var(--ink)}.s-conv-header-state{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--muted);margin-top:1px}.s-messages{flex:1;overflow-y:auto;padding:16px 20px;display:flex;flex-direction:column;gap:6px}.s-msg{max-width:85%;align-self:flex-start}.s-msg-you{align-self:flex-end}.s-msg-header{display:flex;align-items:baseline;gap:6px;margin-bottom:2px;padding:0 2px}.s-msg-actor{font-size:11px;font-weight:600;color:var(--ink)}.s-msg-time{font-size:10px;color:var(--dim);font-family:var(--font-mono)}.s-msg-body{font-size:12px;line-height:1.5;color:var(--ink);white-space:pre-wrap;word-break:break-word;padding:8px 12px;border-radius:14px 14px 14px 4px;background:var(--surface);border:1px solid var(--border)}.s-msg-you .s-msg-body{border-radius:14px 14px 4px;background:var(--accent-soft);border-color:#0066ff1f}.s-msg-you .s-msg-header{justify-content:flex-end}.s-compose{display:flex;align-items:center;gap:8px;padding:10px 20px;border-top:1px solid var(--border);background:var(--bg);flex-shrink:0}.s-compose-input{flex:1;border:1px solid var(--border);background:var(--surface);border-radius:20px;padding:8px 14px;font-family:var(--font-sans);font-size:12px;color:var(--ink);outline:none;transition:border-color .12s ease}.s-compose-input:focus{border-color:var(--accent)}.s-compose-input:disabled{opacity:.5;cursor:not-allowed}.s-compose-send{width:32px;height:32px;border-radius:50%;border:none;background:var(--ink);color:#fff;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:opacity .12s ease}.s-compose-send:disabled{opacity:.3;cursor:not-allowed}.s-agent-profile{display:flex;flex-direction:column;align-items:center;padding:24px 16px 16px;gap:4px}.s-agent-profile-name{font-size:18px;font-weight:600;color:var(--ink);margin-top:8px}.s-agent-profile-handle{font-size:12px;font-family:var(--font-mono);color:var(--muted)}.s-agent-profile-state{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);margin-top:4px}.s-agent-details{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin:16px 20px 0}.s-detail-row{display:flex;align-items:baseline;justify-content:space-between;padding:7px 14px;font-size:12px;gap:12px}.s-detail-row+.s-detail-row{border-top:1px solid var(--border)}.s-detail-label{color:var(--muted);flex-shrink:0;font-size:11px}.s-detail-value{color:var(--ink);font-family:var(--font-mono);font-size:11px;text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-section-title{font-size:15px;font-weight:600;color:var(--ink);margin-bottom:16px}.s-actions{display:flex;gap:8px;margin-bottom:16px}.s-btn{border:1px solid var(--border);background:var(--surface);padding:6px 14px;border-radius:var(--radius);font-family:var(--font-sans);font-size:12px;font-weight:500;cursor:pointer;transition:all .14s cubic-bezier(.16,1,.3,1)}.s-btn:hover{border-color:var(--dim);box-shadow:0 1px 2px var(--shadow-soft)}.s-btn:disabled{opacity:.4;cursor:not-allowed}.s-btn-primary{background:var(--ink);color:var(--bg);border-color:var(--ink)}.s-btn-primary:hover{background:#2a2a28;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr{max-width:320px;margin:20px auto;padding:20px;background:var(--surface);border:1px solid var(--border);border-radius:12px;box-shadow:0 2px 6px var(--shadow-soft)}.s-qr svg{display:block;width:100%;height:auto}.s-pair-meta{max-width:320px;margin:0 auto;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-pair-row{display:flex;align-items:center;justify-content:space-between;padding:7px 14px;font-size:12px}.s-pair-row+.s-pair-row{border-top:1px solid var(--border)}.s-pair-label{color:var(--muted);flex-shrink:0}.s-pair-value{display:flex;align-items:center;color:var(--ink);text-align:right;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-pair-mono{font-family:var(--font-mono);font-size:11px}.s-sidebar-nav{padding:0 8px 4px;flex-shrink:0}.s-nav-badge{font-size:10px;font-weight:600;font-family:var(--font-mono);color:#fff;background:var(--green);border-radius:10px;padding:0 6px;min-width:18px;text-align:center;line-height:16px;margin-left:auto}.s-sidebar-avatar-wrap{position:relative;flex-shrink:0}.s-task-indicator{position:absolute;bottom:-1px;right:-1px;width:8px;height:8px;border-radius:50%;background:var(--green);border:2px solid var(--surface)}.s-sidebar-row-active .s-task-indicator{border-color:#0066ff14}.s-flight-banner{display:flex;align-items:center;gap:6px;padding:6px 20px;background:#22c55e0f;border-bottom:1px solid rgba(34,197,94,.12);font-size:11px;flex-shrink:0}.s-flight-banner-dot{width:6px;height:6px;border-radius:50%;background:var(--green);flex-shrink:0;animation:s-pulse 2s ease-in-out infinite}@keyframes s-pulse{0%,to{opacity:1}50%{opacity:.4}}.s-flight-banner-label{font-weight:600;color:var(--ink)}.s-flight-banner-summary{color:var(--muted);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.s-flights{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.s-flight-row{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--surface);cursor:pointer;transition:background .12s ease}.s-flight-row:hover{background:#f6f6f5}.s-flight-row+.s-flight-row{border-top:1px solid var(--border)}.s-flight-body{flex:1;min-width:0}.s-flight-header{display:flex;align-items:center;gap:6px}.s-flight-name{font-size:12px;font-weight:600;color:var(--ink)}.s-flight-state{font-size:11px;font-family:var(--font-mono);font-weight:500}.s-flight-summary{font-size:11px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.s-content>div:not(.s-conversation):not(.s-welcome){padding:20px;max-width:560px;margin:0 auto}@media(max-width:767px){.s-sidebar{width:100%;position:absolute;inset:0;z-index:20;transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-content{position:absolute;inset:0;z-index:10;transform:translate(100%);transition:transform .2s cubic-bezier(.16,1,.3,1)}.s-app{position:relative}.s-app-content-open .s-sidebar{transform:translate(-100%)}.s-app-content-open .s-content{transform:translate(0)}.s-conv-header .s-back{display:block}}@media(min-width:768px){.s-conv-header .s-back{display:none}}::-webkit-scrollbar{width:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--muted)}