@addai/node 0.27.1 → 0.28.0

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.
Files changed (34) hide show
  1. package/assets/vault-extension/assets/inter.woff2 +0 -0
  2. package/assets/vault-extension/background.js +45 -0
  3. package/assets/vault-extension/content.js +62 -0
  4. package/assets/vault-extension/icons/icon-128.png +0 -0
  5. package/assets/vault-extension/icons/icon-16.png +0 -0
  6. package/assets/vault-extension/icons/icon-32.png +0 -0
  7. package/assets/vault-extension/icons/icon-48.png +0 -0
  8. package/assets/vault-extension/manifest.json +39 -0
  9. package/dist/capabilities.js +2 -1
  10. package/dist/command-runner.js +109 -2
  11. package/dist/desktop/docker.d.ts +4 -1
  12. package/dist/desktop/docker.js +7 -3
  13. package/dist/desktop/engine.d.ts +13 -0
  14. package/dist/desktop/engine.js +35 -4
  15. package/dist/desktop/install-engine.d.ts +33 -2
  16. package/dist/desktop/install-engine.js +138 -12
  17. package/dist/desktop/learn/browser.d.ts +41 -0
  18. package/dist/desktop/learn/browser.js +136 -0
  19. package/dist/desktop/learn/injected.mjs +173 -0
  20. package/dist/desktop/learn/recorder.mjs +151 -0
  21. package/dist/desktop/learn/session.d.ts +51 -0
  22. package/dist/desktop/learn/session.js +224 -0
  23. package/dist/desktop/provider.d.ts +4 -0
  24. package/dist/desktop/start-engine.d.ts +7 -4
  25. package/dist/desktop/start-engine.js +50 -6
  26. package/dist/desktop/vault-extension.d.ts +21 -0
  27. package/dist/desktop/vault-extension.js +112 -0
  28. package/dist/desktop/vault-seed.mjs +112 -0
  29. package/dist/desktop/vault-session.d.ts +41 -0
  30. package/dist/desktop/vault-session.js +164 -0
  31. package/package.json +4 -3
  32. package/scripts/copy-assets.js +18 -0
  33. package/dist/tui.d.ts +0 -1
  34. package/dist/tui.js +0 -314
@@ -0,0 +1,62 @@
1
+ (function(){"use strict";const k={ITEMS_FOR_DOMAIN:"items:for-domain",ITEM_REVEAL:"item:reveal",ITEM_CREATE:"item:create",INFER_FORM:"infer:form"},$=80,E=a=>{if(typeof a!="string")return null;const e=a.replace(/\s+/g," ").trim();return e?e.length>$?e.slice(0,$):e:null},aa=a=>E(a.getAttribute("aria-label")),ea=a=>{if(!a.id)return null;const e=a.ownerDocument.querySelector(`label[for="${CSS.escape(a.id)}"]`);return e?E(e.textContent):null},ta=a=>{const e=a.closest("label");if(!e)return null;const t=e.cloneNode(!0);return t.querySelectorAll("input, textarea, select").forEach(r=>r.remove()),E(t.textContent)},ra=a=>E(a.getAttribute("placeholder")),na=a=>a&&(aa(a)||ea(a)||ta(a)||ra(a))||null,oa=new Set(["hidden"]),ia=a=>{var t,r;if(oa.has(a.type))return!0;const e=(r=(t=a.ownerDocument.defaultView)==null?void 0:t.getComputedStyle)==null?void 0:r.call(t,a);return e?e.display==="none"||e.visibility==="hidden":!1},da=a=>({_el:a,type:(a.type||a.tagName||"text").toLowerCase(),name:a.getAttribute("name")||null,autocomplete:a.getAttribute("autocomplete")||null,placeholder:a.getAttribute("placeholder")||null,label:na(a),ariaLabel:a.getAttribute("aria-label")||null,required:a.hasAttribute("required"),visible:!ia(a)}),sa=a=>{let e=2166136261;const t=a.slice().sort().join("|");for(let r=0;r<t.length;r++)e^=t.charCodeAt(r),e=e+((e<<1)+(e<<4)+(e<<7)+(e<<8)+(e<<24))>>>0;return e.toString(16).padStart(8,"0")},la=a=>sa(a.filter(e=>e.visible).map(e=>`${e.type}:${e.name||""}:${e.autocomplete||""}`)),D=(a,e)=>Array.from(a.querySelectorAll(e)).filter(t=>!t.disabled),R=(a,e)=>{const t=a.map(da),r=t.find(s=>s.type==="password"&&s.visible),i=t.find(s=>s.visible),n=r||i||t[0],d=(n==null?void 0:n._el)||null,o=t.map((s,c)=>({...s,id:`i_${c}`})),l=new Map(o.map((s,c)=>[s.id,t[c]._el]));return{formId:`form_${e}`,signature:la(o),inputs:o.map(({_el:s,...c})=>c),anchor:d,elementById:l}},ca=(a=document)=>{const e=[],t=new WeakSet;let r=0;const i=Array.from(a.querySelectorAll("form"));for(const d of i){const o=D(d,"input, select, textarea");o.length&&(o.forEach(l=>t.add(l)),e.push(R(o,r++)))}const n=D(a,'input[type="password"]').filter(d=>!t.has(d));for(const d of n){const o=d.parentElement;if(!o)continue;const l=Array.from(o.querySelectorAll("input, select, textarea")).filter(s=>!s.disabled&&!t.has(s));l.length&&(l.forEach(s=>t.add(s)),e.push(R(l,r++)))}return e},z="addai.forms",N=50;let f=null;const M=async()=>{if(f)return f;try{f=(await chrome.storage.local.get(z))[z]||{}}catch{f={}}return f},q=async a=>{f=a;try{await chrome.storage.local.set({[z]:a})}catch{}},pa=async(a,e)=>{const r=(await M())[a];if(!r)return null;const i=r.find(n=>n.signature===e);return i?i.mapping:null},ua=async(a,e,t,r)=>{const i=await M(),n=(i[a]||[]).filter(o=>o.signature!==e),d=Date.now();n.push({signature:e,mapping:t,fields:r,savedAt:d,lastUsedAt:d}),n.sort((o,l)=>l.lastUsedAt-o.lastUsedAt),n.length>N&&(n.length=N),await q({...i,[a]:n})},fa=async(a,e)=>{const t=await M(),r=t[a];if(!r)return;const i=r.findIndex(n=>n.signature===e);i<0||(r[i]={...r[i],lastUsedAt:Date.now()},await q({...t,[a]:r}))},ga=':host{--addai-brand: rgb(127, 86, 217);--addai-brand-strong: rgb(105, 65, 198);--addai-brand-soft: rgba(127, 86, 217, .12);--addai-brand-ring: rgba(127, 86, 217, .24);--addai-glass-fill: rgba(255, 255, 255, .66);--addai-glass-fill-strong: rgba(255, 255, 255, .92);--addai-glass-gloss: linear-gradient(180deg, rgba(255, 255, 255, .55) 0%, rgba(255, 255, 255, 0) 36%, rgba(255, 255, 255, 0) 70%, rgba(11, 15, 23, .04) 100%);--addai-glass-edge: rgba(11, 15, 23, .1);--addai-glass-edge-soft: rgba(11, 15, 23, .06);--addai-glass-inner: inset 0 1px 0 rgba(255, 255, 255, .7), inset 0 -1px 0 rgba(11, 15, 23, .04);--addai-glass-blur: blur(24px) saturate(180%);--addai-glass-blur-strong: blur(36px) saturate(190%);--addai-fg: rgb(16, 24, 40);--addai-fg-strong: rgb(16, 24, 40);--addai-muted: rgb(94, 100, 115);--addai-muted-strong: rgb(71, 79, 96);--addai-radius: 14px;--addai-radius-sm: 10px;--addai-radius-pill: 999px;--addai-shadow: 0 1px 2px rgba(16, 24, 40, .06), 0 12px 28px rgba(16, 24, 40, .14), 0 2px 6px rgba(16, 24, 40, .06);--addai-shadow-lifted: 0 1px 2px rgba(16, 24, 40, .06), 0 24px 48px rgba(16, 24, 40, .18), 0 4px 12px rgba(16, 24, 40, .08);--addai-font: "AddaiInter", "Open Sans", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--addai-font-feature: "cv11", "ss01", "ss03";all:initial;font-family:var(--addai-font);font-feature-settings:var(--addai-font-feature);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--addai-fg)}@media(prefers-color-scheme:dark){:host{--addai-brand: rgb(176, 156, 255);--addai-brand-strong: rgb(159, 138, 245);--addai-brand-soft: rgba(176, 156, 255, .18);--addai-brand-ring: rgba(176, 156, 255, .32);--addai-glass-fill: rgba(22, 24, 31, .62);--addai-glass-fill-strong: rgba(22, 24, 31, .88);--addai-glass-gloss: linear-gradient(180deg, rgba(255, 255, 255, .1) 0%, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0) 70%, rgba(0, 0, 0, .18) 100%);--addai-glass-edge: rgba(255, 255, 255, .1);--addai-glass-edge-soft: rgba(255, 255, 255, .06);--addai-glass-inner: inset 0 1px 0 rgba(255, 255, 255, .1), inset 0 -1px 0 rgba(0, 0, 0, .3);--addai-fg: rgb(248, 250, 252);--addai-fg-strong: rgb(255, 255, 255);--addai-muted: rgb(176, 184, 200);--addai-muted-strong: rgb(206, 213, 226);--addai-shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 16px 36px rgba(0, 0, 0, .5), 0 2px 6px rgba(0, 0, 0, .3);--addai-shadow-lifted: 0 1px 2px rgba(0, 0, 0, .4), 0 28px 60px rgba(0, 0, 0, .55), 0 4px 12px rgba(0, 0, 0, .35)}}.addai-root,.addai-root *{box-sizing:border-box}.addai-glyph{position:fixed;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;color:var(--addai-brand);background:var(--addai-glass-fill-strong);-webkit-backdrop-filter:var(--addai-glass-blur);backdrop-filter:var(--addai-glass-blur);border:1px solid var(--addai-glass-edge);border-radius:8px;cursor:pointer;z-index:2147483646;box-shadow:0 1px 2px #1018281f,var(--addai-glass-inner);transition:transform .18s cubic-bezier(.32,.72,0,1),box-shadow .18s ease,background .18s ease;padding:0;isolation:isolate;overflow:hidden}.addai-glyph:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(180deg,#ffffff73,#fff0 60%);pointer-events:none;z-index:0}.addai-glyph>svg{position:relative;z-index:1;width:14px;height:14px}.addai-glyph:hover{transform:scale(1.06);background:var(--addai-glass-fill-strong);box-shadow:0 1px 2px #1018281f,0 0 0 3px var(--addai-brand-ring),var(--addai-glass-inner)}.addai-glyph:active{transform:scale(.96)}.addai-glyph:focus-visible{outline:none;box-shadow:0 1px 2px #1018281f,0 0 0 3px var(--addai-brand-ring),var(--addai-glass-inner)}.addai-panel{position:fixed;width:320px;max-height:380px;background:var(--addai-glass-fill);-webkit-backdrop-filter:var(--addai-glass-blur-strong);backdrop-filter:var(--addai-glass-blur-strong);border:1px solid var(--addai-glass-edge);border-radius:var(--addai-radius);box-shadow:var(--addai-shadow-lifted),var(--addai-glass-inner);overflow:hidden;z-index:2147483647;display:flex;flex-direction:column;font-family:var(--addai-font);color:var(--addai-fg);isolation:isolate;animation:addai-panel-in .2s cubic-bezier(.32,.72,0,1)}.addai-panel:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:var(--addai-glass-gloss);pointer-events:none;z-index:0}.addai-panel>*{position:relative;z-index:1}@keyframes addai-panel-in{0%{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}.addai-panel header{display:flex;align-items:center;gap:8px;padding:12px 16px;font-size:12px;font-weight:500;letter-spacing:-.005em;color:var(--addai-muted-strong);border-bottom:1px solid var(--addai-glass-edge-soft)}.addai-panel header .addai-mark{width:14px;height:14px;color:var(--addai-brand);display:inline-flex;align-items:center;justify-content:center}.addai-panel header .addai-mark svg{width:14px;height:14px}.addai-list{overflow:auto;padding:6px}.addai-row{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--addai-radius-sm);cursor:pointer;font-size:13px;line-height:1.35;position:relative;transition:background-color .12s ease}.addai-row:hover,.addai-row[aria-selected=true]{background:var(--addai-brand-soft)}.addai-row[aria-selected=true]:before{content:"";position:absolute;left:4px;top:10px;bottom:10px;width:3px;border-radius:999px;background:var(--addai-brand)}.addai-avatar{flex-shrink:0;width:28px;height:28px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:var(--addai-brand-soft);color:var(--addai-brand-strong);font-weight:600;font-size:12px;letter-spacing:-.01em;border:1px solid var(--addai-glass-edge-soft)}.addai-row .addai-rowtext{min-width:0;flex:1 1 auto}.addai-row .addai-username{font-weight:600;font-size:13.5px;color:var(--addai-fg-strong);letter-spacing:-.005em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.addai-row .addai-meta{font-size:12px;color:var(--addai-muted);margin-top:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.addai-panel footer{display:flex;align-items:center;gap:8px;padding:11px 16px;font-size:12.5px;font-weight:500;border-top:1px solid var(--addai-glass-edge-soft);color:var(--addai-brand);cursor:pointer;transition:background-color .12s ease}.addai-panel footer:hover{background:var(--addai-brand-soft)}.addai-panel footer .addai-plus{width:14px;height:14px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--addai-brand-soft);color:var(--addai-brand);font-weight:700;font-size:11px;line-height:1}.addai-toast{position:fixed;bottom:20px;right:20px;width:340px;padding:16px 18px;background:var(--addai-glass-fill);-webkit-backdrop-filter:var(--addai-glass-blur-strong);backdrop-filter:var(--addai-glass-blur-strong);border:1px solid var(--addai-glass-edge);border-radius:var(--addai-radius);box-shadow:var(--addai-shadow-lifted),var(--addai-glass-inner);z-index:2147483647;font:13px/1.4 var(--addai-font);color:var(--addai-fg);isolation:isolate;animation:addai-panel-in .22s cubic-bezier(.32,.72,0,1)}.addai-toast:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:var(--addai-glass-gloss);pointer-events:none;z-index:0;border-radius:inherit}.addai-toast>*{position:relative;z-index:1}.addai-toast .addai-toast-head{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:500;letter-spacing:-.005em;color:var(--addai-muted-strong);margin-bottom:10px}.addai-toast .addai-toast-head .addai-mark{color:var(--addai-brand);display:inline-flex}.addai-toast .addai-toast-head .addai-mark svg{width:14px;height:14px}.addai-toast h4{margin:0 0 4px;font-size:14px;font-weight:600;letter-spacing:-.005em;color:var(--addai-fg-strong)}.addai-toast p{margin:0 0 14px;color:var(--addai-muted);font-size:12.5px}.addai-toast .addai-actions{display:flex;gap:8px;justify-content:flex-end}.addai-toast button{font:500 12.5px/1.2 var(--addai-font);padding:8px 14px;border-radius:var(--addai-radius-sm);border:1px solid var(--addai-glass-edge);background:transparent;color:var(--addai-fg);cursor:pointer;transition:background-color .12s ease,border-color .12s ease,transform .12s ease}.addai-toast button:hover{background:#1018280a}.addai-toast button:active{transform:scale(.97)}.addai-toast button.primary{background:var(--addai-brand);border-color:var(--addai-brand);color:#fff;box-shadow:0 1px 2px #10182829,inset 0 1px #ffffff2e}.addai-toast button.primary:hover{background:var(--addai-brand-strong);border-color:var(--addai-brand-strong)}.addai-toast button.primary:focus-visible{outline:none;box-shadow:0 0 0 3px var(--addai-brand-ring),0 1px 2px #10182829}@media(prefers-color-scheme:dark){.addai-toast button:hover{background:#ffffff0f}.addai-toast button.primary{color:#fff}}.addai-pill{position:fixed;top:16px;right:16px;display:inline-flex;align-items:center;gap:8px;padding:7px 14px;background:var(--addai-glass-fill);-webkit-backdrop-filter:var(--addai-glass-blur);backdrop-filter:var(--addai-glass-blur);border:1px solid var(--addai-glass-edge);border-radius:var(--addai-radius-pill);box-shadow:var(--addai-shadow),var(--addai-glass-inner);font:500 12.5px/1.2 var(--addai-font);color:var(--addai-fg-strong);z-index:2147483647;transform:translateY(-6px);opacity:0;transition:transform .22s cubic-bezier(.32,.72,0,1),opacity .22s ease;isolation:isolate}.addai-pill:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit;background:linear-gradient(180deg,#fff6,#fff0 60%);pointer-events:none;z-index:0}.addai-pill>*{position:relative;z-index:1}.addai-pill[data-visible=true]{transform:translateY(0);opacity:1}.addai-pill .addai-spinner{width:12px;height:12px;border:2px solid var(--addai-brand-soft);border-top-color:var(--addai-brand);border-radius:50%;animation:addai-spin .7s linear infinite}.addai-pill .addai-check{width:14px;height:14px;color:var(--addai-brand)}@keyframes addai-spin{to{transform:rotate(360deg)}}',H='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="8" cy="15" r="4"/><path d="M10.85 12.15 19 4"/><path d="m18 5 2 2"/><path d="m15 8 2 2"/></svg>',O=`<span class="addai-mark" aria-hidden="true">${H}</span>`,ba=a=>{const e=String(a).trim();if(!e)return"?";const t=e[0];return/[a-z0-9]/i.test(t)?t.toLowerCase():"?"};let u=null,g=null,b=null;const ma=()=>{if(u)return g;u=document.createElement("div"),u.setAttribute("data-addai-vault",""),u.style.cssText="all: initial; position: relative; z-index: 2147483647;",document.documentElement.appendChild(u),g=u.attachShadow({mode:"closed"});const a=chrome.runtime.getURL("assets/inter.woff2"),e=document.createElement("style");e.textContent=`
2
+ @font-face {
3
+ font-family: 'AddaiInter';
4
+ font-style: normal;
5
+ font-weight: 100 900;
6
+ font-display: swap;
7
+ src: url('${a}') format('woff2-variations');
8
+ }
9
+ `,g.appendChild(e);const t=document.createElement("style");t.textContent=ga,g.appendChild(t);const r=document.createElement("div");return r.className="addai-root",g.appendChild(r),g},A=()=>ma().querySelector(".addai-root"),m=a=>String(a??"").replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e]),ha=(a,e)=>{const t=A(),r=document.createElement("button");r.type="button",r.className="addai-glyph",r.innerHTML=H,r.setAttribute("aria-label","Sign in with +Ai Vault"),r.addEventListener("mousedown",o=>o.preventDefault()),r.addEventListener("click",o=>{o.preventDefault(),o.stopPropagation(),e()}),t.appendChild(r);const i=()=>{const o=a.getBoundingClientRect();r.style.top=`${o.top+(o.height-20)/2}px`,r.style.left=`${o.right-26}px`,r.style.display=o.width===0||o.height===0?"none":"inline-flex"};i();const n=new ResizeObserver(i);n.observe(a);const d=()=>i();return window.addEventListener("scroll",d,!0),window.addEventListener("resize",d),()=>{r.remove(),n.disconnect(),window.removeEventListener("scroll",d,!0),window.removeEventListener("resize",d)}},xa=(a,e)=>{const t=e.getBoundingClientRect(),i=window.innerHeight-t.bottom<200;a.style.left=`${Math.max(8,t.left)}px`,a.style.top=i?`${t.top-a.offsetHeight-4}px`:`${t.bottom+4}px`},F=a=>{b&&(a.composedPath().includes(u)||h())},h=()=>{b&&(b.remove(),b=null,document.removeEventListener("mousedown",F,!0))},va=(a,e,t)=>{const r=Array.from(a.querySelectorAll(".addai-row"));let i=0;const n=o=>{r.forEach(l=>l.removeAttribute("aria-selected")),i=(o+r.length)%r.length,r[i].setAttribute("aria-selected","true"),r[i].scrollIntoView({block:"nearest"})},d=o=>{if(!b){document.removeEventListener("keydown",d,!0);return}o.key==="ArrowDown"?(o.preventDefault(),n(i+1)):o.key==="ArrowUp"?(o.preventDefault(),n(i-1)):o.key==="Enter"?(o.preventDefault(),t(e[i])):o.key==="Escape"&&(o.preventDefault(),h())};document.addEventListener("keydown",d,!0)},wa=({anchor:a,items:e,onPick:t,onSaveNew:r})=>{h();const i=A(),n=document.createElement("div");n.className="addai-panel",n.setAttribute("role","listbox"),n.innerHTML=`
10
+ <header>${O}<span>+Ai vault</span></header>
11
+ <div class="addai-list"></div>
12
+ <footer><span class="addai-plus" aria-hidden="true">+</span><span>Save new credential</span></footer>
13
+ `;const d=n.querySelector(".addai-list");return e.forEach((o,l)=>{var W,X,J,Q,Z;const s=document.createElement("div");s.className="addai-row",s.setAttribute("role","option"),s.dataset.idx=String(l),l===0&&s.setAttribute("aria-selected","true");const c=((W=o.data)==null?void 0:W.username)||((X=o.data)==null?void 0:X.email)||o.name||"(saved login)",C=[(J=o.data)!=null&&J.username||(Q=o.data)!=null&&Q.email?o.name:null,(Z=o.shared_from_workspace)!=null&&Z.name?`from ${o.shared_from_workspace.name}`:null,o.domain||null,o.folderName||null].filter(Boolean);s.innerHTML=`
14
+ <div class="addai-avatar" aria-hidden="true">${m(ba(c))}</div>
15
+ <div class="addai-rowtext">
16
+ <div class="addai-username">${m(c)}</div>
17
+ <div class="addai-meta">${m(C.join(" · "))}</div>
18
+ </div>
19
+ `,s.addEventListener("mousedown",Da=>Da.preventDefault()),s.addEventListener("click",()=>t(o)),d.appendChild(s)}),n.querySelector("footer").addEventListener("click",()=>r()),i.appendChild(n),xa(n,a),va(n,e,t),document.addEventListener("mousedown",F,!0),b=n,n},ya=({domain:a,username:e,onSave:t,onDismiss:r})=>{const i=A(),n=document.createElement("div");n.className="addai-toast",n.innerHTML=`
20
+ <div class="addai-toast-head">${O}<span>+Ai vault</span></div>
21
+ <h4>Save this credential?</h4>
22
+ <p>${m(a)} · ${m(e||"(no username)")}</p>
23
+ <div class="addai-actions">
24
+ <button data-act="dismiss">Not now</button>
25
+ <button data-act="save" class="primary">Save to vault</button>
26
+ </div>
27
+ `,n.querySelector('[data-act="save"]').addEventListener("click",()=>{n.remove(),t()}),n.querySelector('[data-act="dismiss"]').addEventListener("click",()=>{n.remove(),r()}),i.appendChild(n),setTimeout(()=>{n.isConnected&&n.remove()},3e4)},ka='<svg class="addai-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="5 12 10 17 19 7"/></svg>';let p=null,S=null;const Ea=()=>{const a=A();return p&&p.isConnected||(p=document.createElement("div"),p.className="addai-pill",p.setAttribute("data-visible","false"),p.setAttribute("role","status"),p.setAttribute("aria-live","polite"),a.appendChild(p)),p},x=(a,e={})=>{if(S&&(clearTimeout(S),S=null),a==="hidden"){if(!p)return;p.setAttribute("data-visible","false"),S=setTimeout(()=>{p==null||p.remove(),p=null},220);return}const t=Ea();if(a==="thinking")t.innerHTML='<span class="addai-spinner" aria-hidden="true"></span><span>Reading form…</span>';else if(a==="recognized"){const r=e.count??0;t.innerHTML=`${ka}<span>Recognized ${r} field${r===1?"":"s"}</span>`}t.offsetWidth,t.setAttribute("data-visible","true")},Aa=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"value").set,Sa=(a,e)=>{a&&(Aa.call(a,e??""),a.dispatchEvent(new Event("input",{bubbles:!0})),a.dispatchEvent(new Event("change",{bubbles:!0})))};let j=!1;async function La(a){var n,d;if(j)return;const e=location.hostname;if(!e)return;let t;try{t=await a("keepalive:for-host",{host:e})}catch{return}const r=t!=null&&t.ok?(n=t.data)==null?void 0:n.credential:null,i=t!=null&&t.ok?(d=t.data)==null?void 0:d.provider:null;!r&&!i||(j=!0,_a(a,e,r,i))}function _a(a,e,t,r){const i=document.createElement("div");i.setAttribute("data-addai-keepalive",""),document.documentElement.appendChild(i);const n=i.attachShadow({mode:"closed"}),d=[];t&&d.push({key:"refresh",label:`Refresh &ldquo;${L(t.name)}&rdquo;`,busy:"Capturing session…",msg:"keepalive:capture",done:o=>`✓ Refreshed · ${o.count} cookies`}),r&&d.push({key:"create",label:t?`+ New ${L(r.label)}`:`Save ${L(r.label)} to Vault`,busy:"Creating credential…",msg:"keepalive:create",done:o=>`✓ Created &ldquo;${L(o.name)}&rdquo;`}),n.innerHTML=`
28
+ <style>
29
+ .wrap { position: fixed; bottom: 16px; right: 16px; z-index: 2147483647;
30
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
31
+ .card { display: inline-flex; flex-direction: column; gap: 6px;
32
+ background: #1d1f29; color: #fff; border: 1px solid #2d2f3c; border-radius: 14px;
33
+ padding: 10px; box-shadow: 0 6px 20px rgba(0,0,0,.3); }
34
+ .head { display: flex; align-items: center; gap: 8px; padding: 2px 4px 0; }
35
+ .head .dot { width: 7px; height: 7px; border-radius: 50%; background: #34d399; flex: none; }
36
+ .head .t { font-size: 11px; opacity: .7; }
37
+ .head .x { margin-left: auto; opacity: .45; font-size: 11px; cursor: pointer; }
38
+ .head .x:hover { opacity: .9; }
39
+ .btn { display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
40
+ background: #2a2d3a; color: #fff; border: 1px solid #353848; border-radius: 999px;
41
+ padding: 8px 14px; font-size: 12px; line-height: 1; user-select: none;
42
+ transition: transform .1s ease, background .15s ease; }
43
+ .btn:hover { transform: translateY(-1px); background: #323548; }
44
+ .btn[disabled] { cursor: default; opacity: .8; }
45
+ .btn .bdot { width: 8px; height: 8px; border-radius: 50%; background: #34d399; flex: none; }
46
+ .btn.err .bdot { background: #f87171; }
47
+ </style>
48
+ <div class="wrap">
49
+ <div class="card">
50
+ <div class="head">
51
+ <span class="dot"></span>
52
+ <span class="t">+Ai Vault</span>
53
+ <span class="x" data-x>&#10005;</span>
54
+ </div>
55
+ ${d.map((o,l)=>`
56
+ <div class="btn" role="button" tabindex="0" data-i="${l}">
57
+ <span class="bdot"></span>
58
+ <span class="label">${o.label}</span>
59
+ </div>`).join("")}
60
+ </div>
61
+ </div>`,n.querySelector("[data-x]").addEventListener("click",()=>i.remove()),n.querySelectorAll(".btn").forEach(o=>{const l=d[Number(o.dataset.i)],s=o.querySelector(".label");o.addEventListener("click",async()=>{if(o.hasAttribute("disabled"))return;o.setAttribute("disabled",""),s.textContent=l.busy;const c=await a(l.msg,{host:e});c!=null&&c.ok?(s.innerHTML=l.done(c.data),setTimeout(()=>i.remove(),2800)):(o.classList.add("err"),s.textContent=c!=null&&c.error?`Failed: ${c.error}`:"Failed",o.removeAttribute("disabled"))})})}function L(a){return String(a).replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e])}const v="addai.suppress",B=new WeakSet,P=new WeakSet,I=new Map;let w=!0;const y=(a,e)=>new Promise(t=>{if(!w){t(null);return}try{chrome.runtime.sendMessage({type:a,payload:e},r=>{if(chrome.runtime.lastError){/context invalidated/i.test(chrome.runtime.lastError.message||"")&&(w=!1,T.disconnect()),t(null);return}t(r)})}catch(r){/context invalidated/i.test((r==null?void 0:r.message)||"")&&(w=!1,T.disconnect()),t(null)}}),U=async(a,e)=>{if(!w)return e;try{return await a()}catch{return e}},Ca=async a=>U(async()=>{const r=((await chrome.storage.local.get(v))[v]||{})[a];return r&&r>Date.now()},!1),za=async a=>U(async()=>{const t=(await chrome.storage.local.get(v))[v]||{};t[a]=Date.now()+1440*60*1e3,await chrome.storage.local.set({[v]:t})},void 0),Ma={password:["username","password"],credential:["username","password"]},Ia=a=>a?Array.isArray(a)?a.map(e=>(e==null?void 0:e.key)||(e==null?void 0:e.name)).filter(Boolean):typeof a=="object"?Object.keys(a):[]:[],Ta=a=>{const e=a!=null&&a.data?Object.keys(a.data).filter(n=>n!=="url"):[],t=Ia(a==null?void 0:a.field_schema).filter(n=>n!=="url"),r=Array.isArray(a==null?void 0:a.custom_fields)?a.custom_fields.map(n=>(n==null?void 0:n.key)||(n==null?void 0:n.name)).filter(Boolean):[],i=Ma[a==null?void 0:a.item_type]||[];return Array.from(new Set([...e,...t,...r,...i]))},$a=(a,e)=>Object.values(a).filter(t=>typeof t=="string").every(t=>e.elementById.has(t)),Y=a=>Object.values(a).filter(e=>typeof e=="string").length,V=(a,e,t)=>{const r=async n=>{h();const d=await y(k.ITEM_REVEAL,{itemId:n.id});if(!(d!=null&&d.ok))return;const o=d.data;for(const[l,s]of Object.entries(e)){if(!s)continue;const c=a.elementById.get(s);if(!c)continue;const C=o[l];typeof C=="string"&&Sa(c,C)}},i=n=>{wa({anchor:n,items:t,onPick:r,onSaveNew:()=>h()})};for(const[,n]of Object.entries(e)){if(!n)continue;const d=a.elementById.get(n);!d||B.has(d)||(B.add(d),ha(d,()=>i(d)))}},K=a=>{var t;const e=(t=a.anchor)==null?void 0:t.closest("form");!e||P.has(e)||(P.add(e),e.addEventListener("submit",async()=>{const r=window.location.hostname;if(!r||await Ca(r))return;const i=Array.from(e.querySelectorAll('input[type="password"]'))[0],n=i==null?void 0:i.value;if(!n||n.length<4)return;const d=a.anchor&&a.anchor!==i?a.anchor:Array.from(e.querySelectorAll('input[type="email"], input[type="text"]'))[0],o=(d==null?void 0:d.value)||"";ya({domain:r,username:o,onSave:async()=>{await y(k.ITEM_CREATE,{name:r,url:window.location.origin,username:o,password:n})},onDismiss:()=>za(r)})},{capture:!0}))},_=async()=>{var n,d,o;if(!w)return;const a=window.location.hostname;if(!a)return;const e=ca(document);if(!e.length)return;const t=await y(k.ITEMS_FOR_DOMAIN,{domain:a});if(!(t!=null&&t.ok)||!t.data.items.length)return;const r=t.data.items,i=Array.from(new Set(r.flatMap(Ta)));if(i.length)for(const l of e){if(I.has(l.signature)){V(l,I.get(l.signature),r),K(l);continue}let s=await pa(a,l.signature);if(s&&!$a(s,l)&&(s=null),s)await fa(a,l.signature),x("recognized",{count:Y(s)});else{x("thinking");const c=await y(k.INFER_FORM,{fields:i,inputs:l.inputs,itemId:r[0].id,context:{host:a,title:document.title.slice(0,120),formAction:((o=(d=(n=l.anchor)==null?void 0:n.form)==null?void 0:d.getAttribute("action"))==null?void 0:o.slice(0,200))||null}});if(!(c!=null&&c.ok)){x("hidden");continue}s=c.data.mapping,await ua(a,l.signature,s,i),x("recognized",{count:Y(s)})}I.set(l.signature,s),V(l,s,r),K(l),setTimeout(()=>x("hidden"),2e3)}},T=new MutationObserver(()=>{clearTimeout(_.__t),_.__t=setTimeout(_,350)}),G=async()=>{await _(),T.observe(document.body,{childList:!0,subtree:!0}),La(y).catch(()=>{})};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",G):G()})();
62
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,39 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "+Ai Vault",
4
+ "short_name": "+Ai Vault",
5
+ "version": "0.1.0",
6
+ "description": "Autofill credentials from your +Ai Vault.",
7
+ "icons": {
8
+ "16": "icons/icon-16.png",
9
+ "32": "icons/icon-32.png",
10
+ "48": "icons/icon-48.png",
11
+ "128": "icons/icon-128.png"
12
+ },
13
+ "permissions": ["cookies", "storage", "activeTab", "scripting", "tabs"],
14
+ "host_permissions": ["https://*.add.ai/*", "<all_urls>"],
15
+ "background": {
16
+ "service_worker": "background.js",
17
+ "type": "module"
18
+ },
19
+ "content_scripts": [
20
+ {
21
+ "matches": ["<all_urls>"],
22
+ "js": ["content.js"],
23
+ "run_at": "document_idle",
24
+ "all_frames": false
25
+ }
26
+ ],
27
+ "action": {
28
+ "default_icon": {
29
+ "16": "icons/icon-16.png",
30
+ "32": "icons/icon-32.png"
31
+ }
32
+ },
33
+ "web_accessible_resources": [
34
+ {
35
+ "resources": ["assets/*"],
36
+ "matches": ["<all_urls>"]
37
+ }
38
+ ]
39
+ }
@@ -393,7 +393,8 @@ async function probeFlows() {
393
393
  let sandbox = {
394
394
  kind: 'not-loaded',
395
395
  isolated: false,
396
- note: 'The engine loads when flows are switched on for this machine.',
396
+ note: 'Loads on the first run this machine takes until then there is '
397
+ + 'nothing to report.',
397
398
  };
398
399
  if ((0, engine_loader_1.engineLoaded)()) {
399
400
  try {
@@ -66,6 +66,10 @@ const manager_1 = require("./desktop/manager");
66
66
  const creds_1 = require("./desktop/creds");
67
67
  const install_engine_1 = require("./desktop/install-engine");
68
68
  const engine_1 = require("./desktop/engine");
69
+ const session_1 = require("./desktop/learn/session");
70
+ const browser_1 = require("./desktop/learn/browser");
71
+ const vault_extension_1 = require("./desktop/vault-extension");
72
+ const vault_session_1 = require("./desktop/vault-session");
69
73
  const COMMAND_TIMEOUT_MS = 10 * 60 * 1000;
70
74
  /** Own version, read the same way index.ts does. Resolved here rather than
71
75
  * imported from index.ts, which already imports this module. */
@@ -577,8 +581,70 @@ async function runSetAutostart(cmd) {
577
581
  const DESKTOP_KINDS = [
578
582
  'desktop_create', 'desktop_start', 'desktop_stop',
579
583
  'desktop_delete', 'desktop_rebuild', 'desktop_sync_logins',
580
- 'desktop_exec',
584
+ 'desktop_exec', 'desktop_record_start', 'desktop_record_stop',
581
585
  ];
586
+ /** A desktop's browser has to be started with a debugging port for +Ai Learn
587
+ * to see which control was clicked, and with the vault extension loaded for
588
+ * logins to be fillable — both are read only at launch. Applying the wrapper
589
+ * whenever a desktop comes up means the fleet acquires them without anyone
590
+ * doing anything, and a rebuild cannot lose them. */
591
+ async function armBrowserForLearning(provider, row, onLog) {
592
+ try {
593
+ const { confDir } = (0, manager_1.ensureDirs)(row.id);
594
+ // The extension is staged first: the wrapper's --load-extension has to
595
+ // point at something that is already on disk, or the browser starts once
596
+ // without it and nobody knows why.
597
+ const vault = (0, vault_extension_1.ensureVaultExtension)(confDir);
598
+ if (vault.available)
599
+ await signDesktopIn(row, confDir, onLog);
600
+ const res = await (0, browser_1.ensureBrowserWrapper)(async (cmd) => {
601
+ const r = await (0, creds_1.execInDesktopCapture)(provider, row, cmd, 20_000);
602
+ return { ok: r.exitCode === 0, out: r.stdout.toString('utf8') };
603
+ }, confDir, vault.available ? vault_extension_1.EXTENSION_DIR : null);
604
+ if (res.changed) {
605
+ onLog(vault.available
606
+ ? 'browser set up so lessons can watch it, with the +Ai Vault loaded\n'
607
+ : 'browser set up so lessons can watch it\n');
608
+ }
609
+ if (res.note)
610
+ onLog(`${res.note}\n`);
611
+ }
612
+ catch (err) {
613
+ // Never fatal. A desktop that works but cannot be taught is much better
614
+ // than a create that fails over an optional feature.
615
+ onLog(`could not prepare the browser for lessons: ${err.message}\n`);
616
+ }
617
+ }
618
+ /** Leave a live add.ai session where the desktop's browser will find it.
619
+ *
620
+ * An installed vault with nobody signed in is an empty vault, and "empty"
621
+ * reads as broken. This is best-effort by design: a desktop whose entity has
622
+ * browser login switched off, or which two entities share with no default,
623
+ * gets a working browser and no session rather than a failed start. */
624
+ async function signDesktopIn(row, confDir, onLog) {
625
+ try {
626
+ const t = token();
627
+ if (!t)
628
+ return;
629
+ const res = await (0, vault_session_1.ensureVaultSession)(confDir, (desktopId, rotate) => (0, supabase_client_1.rpc)('runtime_desktop_login', { p_token: t, p_desktop_id: desktopId, p_rotate: rotate }), row.id);
630
+ if (res.session) {
631
+ onLog(res.rotated
632
+ ? `signed the browser in as ${res.session.entity_name ?? 'the entity'}\n`
633
+ : 'the browser\u2019s vault session is still good\n');
634
+ return;
635
+ }
636
+ // Said out loud, because every one of these is a thing somebody can fix.
637
+ const why = {
638
+ no_entity_assigned: 'no entity is assigned to this desktop',
639
+ shared_desktop_no_default: 'more than one entity works here and none is the default, so the vault stays signed out',
640
+ browser_login_disabled: 'browser login is switched off for this entity',
641
+ };
642
+ onLog(`the vault is installed but signed out: ${why[res.reason ?? ''] ?? res.reason}\n`);
643
+ }
644
+ catch (err) {
645
+ onLog(`could not sign the browser in: ${err.message}\n`);
646
+ }
647
+ }
582
648
  /** Setup scripts run as a FILE, never as a `sh -c` string: a script with
583
649
  * quotes or newlines must not need escaping to survive. */
584
650
  async function runSetupScript(provider, row, onLog) {
@@ -641,6 +707,7 @@ async function runDesktopCommand(cmd) {
641
707
  vnc_password: vncPassword,
642
708
  });
643
709
  await (0, creds_1.syncCredentials)(provider, built, onLog);
710
+ await armBrowserForLearning(provider, built, onLog);
644
711
  if (row.setup_script) {
645
712
  onLog('running setup script\n');
646
713
  try {
@@ -666,6 +733,35 @@ async function runDesktopCommand(cmd) {
666
733
  await (0, manager_1.setStatus)(row.id, { status: 'starting' });
667
734
  await provider.start(row);
668
735
  await (0, manager_1.setStatus)(row.id, { status: 'running', status_message: null });
736
+ await armBrowserForLearning(provider, row, onLog);
737
+ break;
738
+ }
739
+ case 'desktop_record_start': {
740
+ const recordingId = String(cmd.input?.recording_id ?? '');
741
+ if (!recordingId)
742
+ throw new Error('no recording on command');
743
+ const t = token();
744
+ const res = await (0, session_1.startLesson)({
745
+ provider, row, recordingId, onLog,
746
+ send: async (events) => {
747
+ if (!t)
748
+ return null;
749
+ return await (0, supabase_client_1.rpc)('desktop_recording_page_events', { p_token: t, p_recording_id: recordingId, p_events: events });
750
+ },
751
+ });
752
+ onLog(res.browserTrack
753
+ ? 'watching this desktop\'s browser\n'
754
+ : `${res.note ?? 'the browser could not be watched'}\n`);
755
+ // The command completes immediately: the recorder outlives it, and the
756
+ // stop command is what ends it. A command left running for the length
757
+ // of a lesson would look like a hang and would block the queue.
758
+ break;
759
+ }
760
+ case 'desktop_record_stop': {
761
+ const recordingId = String(cmd.input?.recording_id ?? '');
762
+ if (recordingId)
763
+ await (0, session_1.stopLesson)(recordingId);
764
+ onLog('stopped watching\n');
669
765
  break;
670
766
  }
671
767
  case 'desktop_stop':
@@ -829,7 +925,18 @@ async function runInstallEngine(cmd) {
829
925
  }
830
926
  return;
831
927
  }
832
- const engine = await (0, docker_1.getProvider)(true);
928
+ let engine = await (0, docker_1.getProvider)(true);
929
+ // The mac installer lays the app down and stops — nothing is running yet.
930
+ // Opening it is the rest of the same job, not a second errand for someone
931
+ // who pressed one button, so finish it here rather than reporting a
932
+ // half-done install as a failure.
933
+ if (!engine && process.platform === 'darwin') {
934
+ onLog('\nDocker Desktop installs closed. Opening it…\n');
935
+ if (await (0, start_engine_1.startEngine)(onLog, async () => null)) {
936
+ (0, docker_1.resetProviderCache)();
937
+ engine = await (0, docker_1.getProvider)(true);
938
+ }
939
+ }
833
940
  if (!engine) {
834
941
  await update(cmd.id, 'failed', { log }, 'Installed, but no engine is answering yet. On macOS, Docker Desktop has to be opened once before its daemon runs.');
835
942
  return;
@@ -3,7 +3,10 @@ import { DesktopProvider, EngineInfo, CreateOpts } from './provider';
3
3
  export declare class CliProvider implements DesktopProvider {
4
4
  readonly id: 'docker' | 'podman';
5
5
  private version;
6
- constructor(id: 'docker' | 'podman', version: string);
6
+ private binary;
7
+ /** `binary` defaults to the bare name for callers (and tests) that have no
8
+ * resolved path — on a machine where PATH is enough, they are the same. */
9
+ constructor(id: 'docker' | 'podman', version: string, binary?: string);
7
10
  private run;
8
11
  probe(): Promise<EngineInfo | null>;
9
12
  create(row: DesktopRow, opts: CreateOpts, onLog: (c: string) => void): Promise<string>;
@@ -12,15 +12,19 @@ const engine_1 = require("./engine");
12
12
  class CliProvider {
13
13
  id;
14
14
  version;
15
- constructor(id, version) {
15
+ binary;
16
+ /** `binary` defaults to the bare name for callers (and tests) that have no
17
+ * resolved path — on a machine where PATH is enough, they are the same. */
18
+ constructor(id, version, binary = id) {
16
19
  this.id = id;
17
20
  this.version = version;
21
+ this.binary = binary;
18
22
  }
19
23
  run(args, onLog, timeoutMs = 15 * 60_000) {
20
24
  return new Promise(resolve => {
21
25
  let stdout = '', stderr = '';
22
26
  try {
23
- const inv = (0, win_1.resolveCliInvocation)(this.id, args);
27
+ const inv = (0, win_1.resolveCliInvocation)(this.binary, args);
24
28
  const child = (0, child_process_1.spawn)(inv.file, inv.args, {
25
29
  env: process.env, windowsHide: true, timeout: timeoutMs,
26
30
  });
@@ -96,7 +100,7 @@ async function getProvider(force = false) {
96
100
  // An engine whose daemon is not answering is not a provider: every call
97
101
  // through it would fail at the socket. Studio reports the reason from the
98
102
  // capability probe instead.
99
- cached = info?.running ? new CliProvider(info.id, info.version) : null;
103
+ cached = info?.running ? new CliProvider(info.id, info.version, info.binary ?? info.id) : null;
100
104
  return cached;
101
105
  }
102
106
  function resetProviderCache() { cached = undefined; }
@@ -1,5 +1,18 @@
1
1
  import { EngineInfo } from './provider';
2
2
  export declare function parseEngineVersion(stdout: string): string | null;
3
+ /**
4
+ * Where an engine's CLI actually is.
5
+ *
6
+ * Docker Desktop puts `docker` in /usr/local/bin, and Homebrew's podman in
7
+ * its own prefix — neither is on the PATH of a daemon launchd started, which
8
+ * hands a job `PATH=/usr/bin:/bin:/usr/sbin:/sbin`. Probing the bare name
9
+ * there reports "no container engine" on a machine that has one, and the
10
+ * Desktops tab goes dark for a reason nobody can see. Look properly.
11
+ *
12
+ * Windows keeps its own resolution (`where`, then the npm shim candidates)
13
+ * inside findCliBinary, so this is one call for both.
14
+ */
15
+ export declare function engineBinary(id: 'docker' | 'podman'): string | null;
3
16
  /** Why an installed engine still cannot run anything. The three causes are
4
17
  * worth telling apart, because the fix for each is different and none of
5
18
  * them is "install Docker". */
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseEngineVersion = parseEngineVersion;
4
+ exports.engineBinary = engineBinary;
4
5
  exports.explainUnreachable = explainUnreachable;
5
6
  exports.detectEngine = detectEngine;
6
7
  // Which container engine is on this machine? Docker first (the verified
@@ -13,6 +14,33 @@ function parseEngineVersion(stdout) {
13
14
  const m = /version\s+v?(\d+\.\d+\.\d+)/i.exec(stdout);
14
15
  return m ? m[1] : null;
15
16
  }
17
+ /**
18
+ * Where an engine's CLI actually is.
19
+ *
20
+ * Docker Desktop puts `docker` in /usr/local/bin, and Homebrew's podman in
21
+ * its own prefix — neither is on the PATH of a daemon launchd started, which
22
+ * hands a job `PATH=/usr/bin:/bin:/usr/sbin:/sbin`. Probing the bare name
23
+ * there reports "no container engine" on a machine that has one, and the
24
+ * Desktops tab goes dark for a reason nobody can see. Look properly.
25
+ *
26
+ * Windows keeps its own resolution (`where`, then the npm shim candidates)
27
+ * inside findCliBinary, so this is one call for both.
28
+ */
29
+ function engineBinary(id) {
30
+ return (0, win_1.findCliBinary)(id, {
31
+ posixCandidates: [
32
+ `/usr/local/bin/${id}`,
33
+ `/opt/homebrew/bin/${id}`,
34
+ `/usr/bin/${id}`,
35
+ // Docker Desktop ships its own copy and only symlinks it into
36
+ // /usr/local/bin when the user lets it — this one is always there.
37
+ ...(id === 'docker' ? ['/Applications/Docker.app/Contents/Resources/bin/docker'] : []),
38
+ ],
39
+ win32Candidates: id === 'docker'
40
+ ? ['C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe']
41
+ : [],
42
+ });
43
+ }
16
44
  /** Run a command and hand back exit code + output. */
17
45
  function run(binary, args) {
18
46
  return new Promise(resolve => {
@@ -58,16 +86,19 @@ function explainUnreachable(output, platform) {
58
86
  */
59
87
  async function detectEngine() {
60
88
  for (const id of ['docker', 'podman']) {
61
- const v = await run(id, ['--version']);
89
+ const bin = engineBinary(id);
90
+ if (!bin)
91
+ continue;
92
+ const v = await run(bin, ['--version']);
62
93
  if (!v.ok)
63
94
  continue;
64
95
  const version = parseEngineVersion(v.out);
65
96
  if (!version)
66
97
  continue;
67
- const info = await run(id, ['info', '--format', '{{.ServerVersion}}']);
98
+ const info = await run(bin, ['info', '--format', '{{.ServerVersion}}']);
68
99
  if (info.ok)
69
- return { id, version, running: true };
70
- return { id, version, running: false, reason: explainUnreachable(info.out, process.platform) };
100
+ return { id, version, running: true, binary: bin };
101
+ return { id, version, running: false, binary: bin, reason: explainUnreachable(info.out, process.platform) };
71
102
  }
72
103
  return null;
73
104
  }
@@ -6,8 +6,39 @@ export interface InstallPlan {
6
6
  manual: string;
7
7
  needsRoot: boolean;
8
8
  }
9
- /** Pure, so the choice is testable on any machine. */
10
- export declare function installPlan(platform: NodeJS.Platform, isRoot: boolean): InstallPlan;
9
+ /**
10
+ * Where Homebrew actually lives.
11
+ *
12
+ * `brew` is not on the PATH of a daemon that did not come from a login shell —
13
+ * launchd hands a job `PATH=/usr/bin:/bin:/usr/sbin:/sbin`, and neither
14
+ * Homebrew prefix is in it. Spawning the bare name then fails ENOENT and the
15
+ * install reports "brew is not available here" on a machine that has brew,
16
+ * which is a lie the user cannot act on. Look where it really is instead.
17
+ */
18
+ export declare function findBrew(platform?: NodeJS.Platform): string | null;
19
+ /**
20
+ * Install Docker Desktop from Docker's own disk image.
21
+ *
22
+ * The path for a Mac with no Homebrew — which is most Macs. This is the same
23
+ * download the `docker` cask fetches, driven by the installer Docker ships
24
+ * inside the app, so it is the vendor's supported unattended install rather
25
+ * than something we invented.
26
+ *
27
+ * Root is needed for one step: putting the app in /Applications and laying
28
+ * down its privileged helper. `--user` is what stops that cost being paid
29
+ * twice — without it, the first launch stops at an admin prompt on a machine
30
+ * nobody is sitting at.
31
+ */
32
+ export declare function macDmgScript(user: string): string;
33
+ /** The current login name, for the `--user` the Docker installer wants. Never
34
+ * root: under `sudo` that would hand the app to the wrong account. */
35
+ export declare function installingUser(env?: NodeJS.ProcessEnv): string;
36
+ /**
37
+ * Pure, so the choice is testable on any machine — the two impure lookups
38
+ * (is brew here, who are we) are parameters with defaults rather than calls
39
+ * inside the body.
40
+ */
41
+ export declare function installPlan(platform: NodeJS.Platform, isRoot: boolean, brew?: string | null, user?: string): InstallPlan;
11
42
  /** Docker is there but this user cannot reach its socket. The fix is group
12
43
  * membership, not another install — and it needs root, so it takes the same
13
44
  * password round trip. */