busbar-sf-agentscript 0.0.2

AgentScript parser, graph analysis, and LSP for Salesforce Agentforce
Documentation
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AgentScript Report</title>
  <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { margin: 0; background: #0d1117; color: #c9d1d9; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; min-height: 100vh; }
    #sidebar { width: 240px; min-height: 100vh; background: #161b22; border-right: 1px solid #30363d; position: fixed; top: 0; left: 0; bottom: 0; overflow-y: auto; display: flex; flex-direction: column; }
    .sidebar-header { padding: 20px 16px 16px; border-bottom: 1px solid #30363d; }
    .sidebar-title { font-size: 0.75rem; font-weight: 600; color: #8b949e; text-transform: uppercase; letter-spacing: 0.05em; }
    .sidebar-count { font-size: 0.7rem; color: #484f58; margin-top: 2px; }
    .nav-item { padding: 8px 16px; cursor: pointer; color: #c9d1d9; font-size: 0.875rem; border-left: 3px solid transparent; display: flex; align-items: center; gap: 8px; transition: background 0.1s; }
    .nav-item:hover { background: #21262d; }
    .nav-item.active { color: #58a6ff; border-left-color: #58a6ff; background: rgba(88,166,255,0.05); }
    .nav-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .badge { font-size: 0.65rem; color: #484f58; background: #21262d; border-radius: 10px; padding: 1px 6px; flex-shrink: 0; }
    .nav-item.active .badge { background: rgba(88,166,255,0.15); color: #58a6ff; }
    #main { margin-left: 240px; padding: 32px; flex: 1; min-width: 0; }
    .agent-section { display: none; }
    .agent-section.active { display: block; }
    .agent-header { margin-bottom: 20px; }
    .agent-name { font-size: 1.4rem; color: #58a6ff; margin: 0 0 2px 0; font-weight: 600; }
    .agent-label { font-size: 0.85rem; color: #8b949e; margin-bottom: 2px; }
    .agent-file { font-size: 0.75rem; color: #484f58; font-family: monospace; }
    .graph-container { background: #161b22; border-radius: 8px; padding: 24px; border: 1px solid #30363d; overflow: auto; cursor: pointer; }
    .stats-bar { margin-top: 10px; font-size: 0.8rem; color: #8b949e; background: #161b22; border-radius: 6px; padding: 9px 16px; border: 1px solid #30363d; display: flex; gap: 20px; flex-wrap: wrap; }
    .stats-bar strong { color: #c9d1d9; }
    .legend { margin-top: 8px; font-size: 0.72rem; color: #484f58; }
    .mermaid svg { max-width: 100%; height: auto; }

    /* Detail panel */
    .detail-panel { margin-top: 24px; }
    .detail-card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; }
    .detail-card + .detail-card { margin-top: 16px; }
    .detail-back { font-size: 0.78rem; color: #58a6ff; cursor: pointer; margin-bottom: 14px; display: inline-flex; align-items: center; gap: 4px; }
    .detail-back:hover { text-decoration: underline; }
    .detail-title { font-size: 1rem; font-weight: 600; color: #c9d1d9; margin: 0 0 4px 0; display: flex; align-items: center; gap: 8px; }
    .detail-desc { font-size: 0.82rem; color: #8b949e; margin: 0 0 16px 0; }
    .entry-badge { font-size: 0.65rem; background: rgba(88,166,255,0.15); color: #58a6ff; border: 1px solid rgba(88,166,255,0.3); border-radius: 10px; padding: 1px 7px; font-weight: 500; }
    .detail-section { margin-bottom: 14px; }
    .detail-section-title { font-size: 0.7rem; font-weight: 600; color: #8b949e; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px; }
    .tag-list { display: flex; flex-wrap: wrap; gap: 6px; }
    .tag { font-size: 0.78rem; padding: 2px 10px; border-radius: 12px; font-family: monospace; }
    .tag-action { background: rgba(188,135,255,0.12); color: #c8a4ff; border: 1px solid rgba(188,135,255,0.25); }
    .tag-topic { background: rgba(88,166,255,0.1); color: #79b8ff; border: 1px solid rgba(88,166,255,0.2); }
    .tag-var-read { background: rgba(57,211,83,0.1); color: #7ee787; border: 1px solid rgba(57,211,83,0.2); }
    .tag-var-write { background: rgba(255,166,77,0.1); color: #ffb347; border: 1px solid rgba(255,166,77,0.2); }
    .tag-delegate { background: rgba(88,118,255,0.1); color: #7080ff; border: 1px solid rgba(88,118,255,0.2); }
    .var-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
    .var-table th { color: #8b949e; font-weight: 500; text-align: left; padding: 4px 8px 8px 0; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em; }
    .var-table td { padding: 4px 8px 4px 0; color: #c9d1d9; border-top: 1px solid #21262d; font-family: monospace; }
    .var-type { color: #7ee787; }
    .var-mod { font-size: 0.7rem; color: #8b949e; }
    .empty-msg { font-size: 0.8rem; color: #484f58; font-style: italic; }
    .overview-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
    @media (max-width: 700px) { .overview-grid { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
  <nav id="sidebar">
    <div class="sidebar-header">
      <div class="sidebar-title">AgentScript</div>
      <div class="sidebar-count">2 agents</div>
    </div>
    
      <div class="nav-item" id="nav-agent-a" data-id="agent-a" onclick="showAgent('agent-a')">
        <span class="nav-name">AgentA</span>
        <span class="badge">0T</span>
      </div>
      <div class="nav-item" id="nav-agent-b" data-id="agent-b" onclick="showAgent('agent-b')">
        <span class="nav-name">AgentB</span>
        <span class="badge">0T</span>
      </div>
  </nav>
  <main id="main">
    
      <div class="agent-section" id="section-agent-a">
        <div class="agent-header">
          <h1 class="agent-name">AgentA</h1>
          <div class="agent-label">Agent A</div>
          <div class="agent-file">test/commands/agency/fixtures/agents-dir/agent-a.agent</div>
        </div>
        <div class="graph-container">
          <div class="mermaid">flowchart LR
  start_agent([start_agent])
  click start_agent onNodeClick</div>
        </div>
        
        <div class="stats-bar">
          <span><strong>0</strong> topics</span>
          <span><strong>1</strong> variables</span>
          <span><strong>0</strong> action defs</span>
          <span><strong>0</strong> reasoning steps</span>
        </div>
        <div class="legend">→ transition &nbsp;|&nbsp; ⇒ delegate &nbsp;|&nbsp; ([name]) = entry point &nbsp;|&nbsp; <em>click a node for details</em></div>
        <div class="detail-panel" id="detail-agent-a"></div>
      </div>
      <div class="agent-section" id="section-agent-b">
        <div class="agent-header">
          <h1 class="agent-name">AgentB</h1>
          <div class="agent-label">Agent B</div>
          <div class="agent-file">test/commands/agency/fixtures/agents-dir/agent-b.agent</div>
        </div>
        <div class="graph-container">
          <div class="mermaid">flowchart LR
  start_agent([start_agent])
  click start_agent onNodeClick</div>
        </div>
        
        <div class="stats-bar">
          <span><strong>0</strong> topics</span>
          <span><strong>0</strong> variables</span>
          <span><strong>0</strong> action defs</span>
          <span><strong>0</strong> reasoning steps</span>
        </div>
        <div class="legend">→ transition &nbsp;|&nbsp; ⇒ delegate &nbsp;|&nbsp; ([name]) = entry point &nbsp;|&nbsp; <em>click a node for details</em></div>
        <div class="detail-panel" id="detail-agent-b"></div>
      </div>
  </main>
  <script>
    const allData = {"agent-a":{"agentName":"AgentA","agentLabel":"Agent A","agentDescription":"First test agent for multi-file tests","variables":[{"name":"node","type":"unknown","mutable":false,"linked":false},{"name":"span","type":"unknown","mutable":false,"linked":false}],"topics":[{"safeId":"start_agent","name":"start_agent","description":null,"is_entry":true,"actions":[],"transitions_to":[],"delegates_to":[],"var_reads":[],"var_writes":[]}]},"agent-b":{"agentName":"AgentB","agentLabel":"Agent B","agentDescription":"Second test agent for multi-file tests","variables":[],"topics":[{"safeId":"start_agent","name":"start_agent","description":null,"is_entry":true,"actions":[],"transitions_to":[],"delegates_to":[],"var_reads":[],"var_writes":[]}]}};

    mermaid.initialize({ startOnLoad: false, theme: 'dark' });
    const rendered = new Set();
    let currentAgentId = null;

    function showAgent(id) {
      document.querySelectorAll('.agent-section').forEach(el => el.classList.remove('active'));
      document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
      const section = document.getElementById('section-' + id);
      const nav = document.getElementById('nav-' + id);
      if (!section) return;
      section.classList.add('active');
      if (nav) nav.classList.add('active');
      currentAgentId = id;
      if (!rendered.has(id)) {
        rendered.add(id);
        mermaid.run({ nodes: section.querySelectorAll('.mermaid') });
      }
      history.replaceState(null, '', '#' + id);
      showOverview(id);
    }

    function showOverview(agentId) {
      const data = allData[agentId];
      const panel = document.getElementById('detail-' + agentId);
      if (!panel || !data) return;

      const descHtml = data.agentDescription
        ? '<p class="detail-desc">' + esc(data.agentDescription) + '</p>' : '';

      const varRows = data.variables.length
        ? data.variables.map(v => {
            const mods = [v.mutable ? 'mutable' : '', v.linked ? 'linked' : ''].filter(Boolean).join(' ');
            return '<tr><td>' + esc(v.name) + '</td><td class="var-type">' + esc(v.type) + '</td><td class="var-mod">' + esc(mods) + '</td></tr>';
          }).join('')
        : '<tr><td colspan="3" class="empty-msg">No variables defined</td></tr>';

      panel.innerHTML = '<div class="detail-card">' +
        '<div class="detail-title">Agent Overview</div>' +
        descHtml +
        '<div class="overview-grid">' +
        '<div class="detail-section"><div class="detail-section-title">Variables (' + data.variables.length + ')</div>' +
        '<table class="var-table"><thead><tr><th>Name</th><th>Type</th><th>Modifiers</th></tr></thead><tbody>' + varRows + '</tbody></table></div>' +
        '<div class="detail-section"><div class="detail-section-title">Topics (' + data.topics.length + ')</div>' +
        '<div class="tag-list">' + data.topics.map(t =>
          '<span class="tag tag-topic">' + esc(t.name) + (t.is_entry ? ' ★' : '') + '</span>'
        ).join('') + '</div></div>' +
        '</div></div>';
    }

    function onNodeClick(nodeId) {
      if (!currentAgentId) return;
      const data = allData[currentAgentId];
      if (!data) return;
      const topic = data.topics.find(t => t.safeId === nodeId);
      if (!topic) return;

      const panel = document.getElementById('detail-' + currentAgentId);
      if (!panel) return;

      const descHtml = topic.description
        ? '<p class="detail-desc">' + esc(topic.description) + '</p>' : '';

      const actionsHtml = topic.actions.length
        ? '<div class="tag-list">' + topic.actions.map(a => '<span class="tag tag-action">' + esc(a) + '</span>').join('') + '</div>'
        : '<span class="empty-msg">None</span>';

      const transHtml = topic.transitions_to.length
        ? '<div class="tag-list">' + topic.transitions_to.map(t => '<span class="tag tag-topic">' + esc(t) + '</span>').join('') + '</div>'
        : '<span class="empty-msg">None (terminal topic)</span>';

      const delegHtml = topic.delegates_to.length
        ? '<div class="tag-list">' + topic.delegates_to.map(t => '<span class="tag tag-delegate">' + esc(t) + '</span>').join('') + '</div>'
        : '';

      const readsHtml = topic.var_reads.length
        ? '<div class="tag-list">' + topic.var_reads.map(v => '<span class="tag tag-var-read">' + esc(v) + '</span>').join('') + '</div>'
        : '<span class="empty-msg">None</span>';

      const writesHtml = topic.var_writes.length
        ? '<div class="tag-list">' + topic.var_writes.map(v => '<span class="tag tag-var-write">' + esc(v) + '</span>').join('') + '</div>'
        : '<span class="empty-msg">None</span>';

      panel.innerHTML =
        '<div class="detail-back" onclick="showOverview('' + currentAgentId + '')">← back to overview</div>' +
        '<div class="detail-card">' +
        '<div class="detail-title">' + esc(topic.name) + (topic.is_entry ? ' <span class="entry-badge">entry</span>' : '') + '</div>' +
        descHtml +
        (topic.actions.length ? '<div class="detail-section"><div class="detail-section-title">Actions (' + topic.actions.length + ')</div>' + actionsHtml + '</div>' : '') +
        '<div class="detail-section"><div class="detail-section-title">Transitions to</div>' + transHtml + '</div>' +
        (topic.delegates_to.length ? '<div class="detail-section"><div class="detail-section-title">Delegates to</div>' + delegHtml + '</div>' : '') +
        '<div class="detail-section"><div class="detail-section-title">Variables read</div>' + readsHtml + '</div>' +
        '<div class="detail-section"><div class="detail-section-title">Variables written</div>' + writesHtml + '</div>' +
        '</div>';
    }

    function esc(str) {
      if (!str) return '';
      return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
    }

    const hash = location.hash.slice(1);
    const firstId = 'agent-a';
    showAgent(hash && document.getElementById('section-' + hash) ? hash : firstId);
  </script>
</body>
</html>