@yuriai/web 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/assets/{AssistantView-D3j8FlaF.js → AssistantView-CLk1T4Of.js} +3 -3
- package/dist/assets/{DiagramView-TX1QZ1-S.js → DiagramView-BXVAioBW.js} +1 -1
- package/dist/assets/{MarkdownRenderer-DGUI8EIG.js → MarkdownRenderer-BY7SOGeo.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-DndseLUT.js → MarkdownRendererImpl-KSaMBtRF.js} +1 -1
- package/dist/assets/{MarkstreamRendererImpl-Ehxwt6MS.js → MarkstreamRendererImpl-BvkGmuQ6.js} +1 -1
- package/dist/assets/{MobileDetailNavigation-D7uM40jU.js → MobileDetailNavigation-DACAHTpS.js} +1 -1
- package/dist/assets/{MobileShareBridge-BMQKnoEb.js → MobileShareBridge-DLPBToEM.js} +1 -1
- package/dist/assets/{MobileTabPageHeader-Cco8ToA6.js → MobileTabPageHeader-CvtrPaC2.js} +1 -1
- package/dist/assets/{MultiRunWindow-CVI6D2LM.js → MultiRunWindow-4G9PetNl.js} +1 -1
- package/dist/assets/{SettingsView-Bx-5WRtO.js → SettingsView-pRXWe1jD.js} +1 -1
- package/dist/assets/{SettingsWindow-Cj6sYyvK.js → SettingsWindow-DGK3Earv.js} +1 -1
- package/dist/assets/{TerminalView-DW4OxMaM.js → TerminalView-CagyLr39.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DxKGYhQ6.js → ToolOutputDialog-DHukIjoH.js} +1 -1
- package/dist/assets/{appThemeRegistry-DlEr4DLc.js → appThemeRegistry-U9eZywBt.js} +1 -1
- package/dist/assets/{capgoAdapter-Onw1miMh.js → capgoAdapter-ChF9Cg3G.js} +1 -1
- package/dist/assets/{fileMentionTouchSelection-B26MejYr.js → fileMentionTouchSelection-Bip-B2J9.js} +1 -1
- package/dist/assets/{gitApi-BXhXdWkh.js → gitApi-BN0aozGg.js} +1 -1
- package/dist/assets/{insertionBoundaries-BxSGE3B7.js → insertionBoundaries-gfsrexDz.js} +1 -1
- package/dist/assets/{main-BdV8Awp-.js → main-C8xj4UeZ.js} +2 -2
- package/dist/assets/{main-CGpFMQTy.js → main-CjjADMt-.js} +4 -4
- package/dist/assets/{mcpQueries-uZJFBhGj.js → mcpQueries-DJCrzyc3.js} +1 -1
- package/dist/assets/{miniChat-B1aBBLiq.js → miniChat-D17acnB9.js} +2 -2
- package/dist/assets/{mobile-B8U8FARq.js → mobile-DiL1n1m5.js} +2 -2
- package/dist/assets/{mobileBackNavigation-D2yUxPpG.js → mobileBackNavigation-B1NT3K0K.js} +1 -1
- package/dist/assets/{multirun-DKsVtdZX.js → multirun-D2KlubbP.js} +1 -1
- package/dist/assets/{native-config-Bpk4DOW1.js → native-config-D9DfX51I.js} +1 -1
- package/dist/assets/{projectMeta-yox5XHhN.js → projectMeta-B5lVMdxF.js} +1 -1
- package/dist/assets/{renderElectronMiniChatApp-CdEZXhzq.js → renderElectronMiniChatApp-DQGRyvOy.js} +2 -2
- package/dist/assets/{renderMobileApp-BoV5wjPF.js → renderMobileApp-BlCyG-WF.js} +4 -4
- package/dist/assets/{runtimeConfig-B8s9sTX_.js → runtimeConfig-MKo97i27.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-BswmG9Bb.js → runtimeEndpointReset-DrO__UOn.js} +1 -1
- package/dist/assets/{sessionChangeSummary-BGbmGpQw.js → sessionChangeSummary-DN7H-rqE.js} +1 -1
- package/dist/assets/{sessionLookup-CGJA6W7O.js → sessionLookup-DezY-ONT.js} +1 -1
- package/dist/assets/{speechSettingsQueries-CjTT-adl.js → speechSettingsQueries-DFAUd_Aw.js} +1 -1
- package/dist/assets/{types-Bu1xM0fS.js → types-DZcrt4Z0.js} +1 -1
- package/dist/assets/{useAppFontEffects-D6yauGdg.js → useAppFontEffects-DUPuwJH5.js} +5 -5
- package/dist/assets/{useDesktopWindowControlsLayout-SKHaFoVu.js → useDesktopWindowControlsLayout-Co_z6hbP.js} +1 -1
- package/dist/assets/{useEffectiveDirectory-D6GwXhDS.js → useEffectiveDirectory-DXPmcf8E.js} +1 -1
- package/dist/assets/{useMobileNavigationStore-ByrZJd5L.js → useMobileNavigationStore-DcG6Ukmu.js} +3 -3
- package/dist/assets/{useWorkerHighlightedLines-CwC34Jql.js → useWorkerHighlightedLines-BnmJy3ZO.js} +1 -1
- package/dist/index.html +5 -5
- package/dist/mini-chat.html +5 -5
- package/dist/mobile.html +5 -5
- package/package.json +1 -1
- package/server/lib/content-search/DOCUMENTATION.md +5 -1
- package/server/lib/opencode/DOCUMENTATION.md +8 -7
- package/server/lib/opencode/config-entity-routes.js +5 -1
- package/server/lib/opencode/config-entity-routes.test.js +23 -0
- package/server/lib/opencode/ensure-cli.js +48 -11
- package/server/lib/opencode/ensure-cli.test.js +53 -1
- package/server/lib/opencode/env-runtime.js +5 -5
- package/server/lib/opencode/terminal-path.js +67 -0
- package/server/lib/opencode/terminal-path.test.js +68 -0
package/dist/assets/{useWorkerHighlightedLines-CwC34Jql.js → useWorkerHighlightedLines-BnmJy3ZO.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{it as t,rt as n}from"./vendor-base-ui-CdzJ6My_.js";import{v as r}from"./useUIStore-NMQx_cnG.js";import{n as i}from"./vendor-react-BP2cRVqX.js";import{f as a,r as o,t as s}from"./button-_ReBqWzC.js";import{n as c}from"./useThemeSystem-8jhE4Zyk.js";import{n as l}from"./Icon-2cKLcXGe.js";import{i as u,n as d,t as f}from"./tooltip-C73L9gDf.js";import{n as p}from"./MarkdownRenderer-
|
|
1
|
+
import{r as e}from"./rolldown-runtime-hePW80VL.js";import{it as t,rt as n}from"./vendor-base-ui-CdzJ6My_.js";import{v as r}from"./useUIStore-NMQx_cnG.js";import{n as i}from"./vendor-react-BP2cRVqX.js";import{f as a,r as o,t as s}from"./button-_ReBqWzC.js";import{n as c}from"./useThemeSystem-8jhE4Zyk.js";import{n as l}from"./Icon-2cKLcXGe.js";import{i as u,n as d,t as f}from"./tooltip-C73L9gDf.js";import{n as p}from"./MarkdownRenderer-BY7SOGeo.js";import{n as m,o as h,s as g,t as _}from"./markdown-worker-CkO3sT4Y.js";import{t as v}from"./vendor-tanstack-react-virtual-B_EO--9t.js";import{n as y}from"./markdownTheme-DHoKvD6O.js";var b=e(t(),1),x=i(),S=n(),C=`w-[min(30rem,calc(100cqw-1rem))] min-w-[280px] max-w-full overflow-hidden rounded-xl shadow-[inset_0_1px_0_0_rgba(255,255,255,0.8),inset_0_0_0_1px_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.10),0_1px_2px_-0.5px_rgba(0,0,0,0.08),0_4px_8px_-2px_rgba(0,0,0,0.08),0_12px_20px_-4px_rgba(0,0,0,0.08)] dark:shadow-[inset_0_1px_0_0_rgba(255,255,255,0.12),inset_0_0_0_1px_rgba(255,255,255,0.08),0_0_0_1px_rgba(0,0,0,0.36),0_1px_1px_-0.5px_rgba(0,0,0,0.22),0_3px_3px_-1.5px_rgba(0,0,0,0.20),0_6px_6px_-3px_rgba(0,0,0,0.16)]`,w={maxWidth:`min(28rem, calc(100cqw - 4ch))`,backgroundColor:`var(--surface-elevated)`,color:`var(--surface-elevated-foreground)`},T=`flex items-center justify-between gap-3 border-b border-[var(--surface-subtle)] px-3 py-2.5`,E=`text-xs font-medium leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,D=`text-xs tabular-nums leading-4 text-muted-foreground md:text-[0.8125rem] md:leading-5`,O=`m-0 list-none divide-y divide-[var(--surface-subtle)] p-0`,k=`${O} max-h-[min(22rem,50vh)] overflow-y-auto`,A=`${O} w-full`,ee={className:`p-0 rounded-none`,maxHeightClass:`max-h-[46vh]`,disableHorizontal:!0},j=`px-2.5 py-1.5 text-xs leading-4 md:py-2 md:text-[0.8125rem] md:leading-5`,M=`flex min-w-0 items-center gap-1 text-xs leading-4 text-muted-foreground md:gap-1.5 md:text-[0.8125rem] md:leading-5`,te=`min-w-0 truncate text-xs leading-4 text-foreground md:text-[0.8125rem] md:leading-5`,ne=`inline-flex shrink-0 items-center gap-1 text-xs leading-4 tabular-nums text-muted-foreground md:text-[0.8125rem] md:leading-5`,re=`h-3 w-3 shrink-0 md:h-3.5 md:w-3.5`,ie={in_progress:`text-foreground`,pending:`text-foreground`,completed:`text-muted-foreground line-through`,cancelled:`text-muted-foreground line-through`},ae={in_progress:`chat.statusRow.todo.status.inProgress`,pending:`chat.statusRow.todo.status.pending`,completed:`chat.statusRow.todo.status.completed`,cancelled:`chat.statusRow.todo.status.cancelled`},N=e=>{let t=(0,x.c)(26),{todo:n,isActive:r,activeRef:i}=e,{t:s}=a(),c=oe.has(n.status)?n.status:`pending`,p=ae[c],m;t[0]!==s||t[1]!==p?(m=s(p),t[0]=s,t[1]=p,t[2]=m):m=t[2];let h=m,g;t[3]===c?g=t[4]:(g=c===`in_progress`?(0,S.jsx)(l,{name:`record-circle`,className:`h-3.5 w-3.5 text-[var(--status-info)]`,"aria-hidden":`true`}):c===`completed`?(0,S.jsx)(l,{name:`checkbox-circle`,className:`h-3.5 w-3.5 text-[var(--status-success)]`,"aria-hidden":`true`}):(0,S.jsx)(l,{name:`time`,className:`h-3.5 w-3.5 text-muted-foreground`,"aria-hidden":`true`}),t[3]=c,t[4]=g);let _=g,v=r?`step`:void 0,y=r&&`bg-[var(--surface-muted)] before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-[var(--status-info)]`,b;t[5]===y?b=t[6]:(b=o(`group relative grid min-w-0 grid-cols-[1rem_minmax(0,1fr)] items-start gap-2.5`,j,y),t[5]=y,t[6]=b);let C;t[7]!==_||t[8]!==h?(C=(0,S.jsx)(u,{asChild:!0,children:(0,S.jsx)(`span`,{className:`flex size-4 items-center justify-center`,"aria-label":h,children:_})}),t[7]=_,t[8]=h,t[9]=C):C=t[9];let w;t[10]===h?w=t[11]:(w=(0,S.jsx)(d,{side:`left`,sideOffset:6,children:h}),t[10]=h,t[11]=w);let T;t[12]!==C||t[13]!==w?(T=(0,S.jsxs)(f,{children:[C,w]}),t[12]=C,t[13]=w,t[14]=T):T=t[14];let E=ie[c],D;t[15]===E?D=t[16]:(D=o(`block whitespace-normal break-words`,E),t[15]=E,t[16]=D);let O;t[17]!==D||t[18]!==n.content?(O=(0,S.jsx)(`span`,{className:`min-w-0 flex-1`,children:(0,S.jsx)(`span`,{className:D,children:n.content})}),t[17]=D,t[18]=n.content,t[19]=O):O=t[19];let k;return t[20]!==i||t[21]!==O||t[22]!==v||t[23]!==b||t[24]!==T?(k=(0,S.jsxs)(`li`,{ref:i,"aria-current":v,className:b,children:[T,O]}),t[20]=i,t[21]=O,t[22]=v,t[23]=b,t[24]=T,t[25]=k):k=t[25],k},oe=new Set([`in_progress`,`pending`,`completed`,`cancelled`]),P=e=>e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`).replace(/"/g,`"`).replace(/'/g,`'`),F=e=>e?Object.entries(e).map(([e,t])=>t==null?``:`${e.startsWith(`--`)?e:e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`)}:${typeof t==`number`?`${t}px`:t};`).join(``):``,I=(e,t,n)=>{let r=e.querySelector(`pre`);r&&(r.style.margin=`0`,r.style.background=`transparent`,r.style.whiteSpace=t?`pre-wrap`:`pre`,t&&(r.style.wordBreak=`break-word`,r.style.overflowWrap=`break-word`));let i=e.querySelector(`code`);if(i){let e=F(n);e&&i.setAttribute(`style`,`${i.getAttribute(`style`)??``}${e}`)}},L=e=>`<pre><code>${P(e)}</code></pre>`,R=e=>{let t=(0,x.c)(21),{code:n,language:r,className:i,style:a,codeStyle:s,wrap:l}=e,u=l!==void 0&&l,{currentTheme:d}=c(),f=b.useRef(null),p;t[0]===d?p=t[1]:(p=y(d),t[0]=d,t[1]=p);let m=p,h,g;t[2]!==n||t[3]!==s||t[4]!==u?(h=()=>{let e=f.current;e&&(e.innerHTML=L(n),I(e,u,s))},g=[n,u,s],t[2]=n,t[3]=s,t[4]=u,t[5]=h,t[6]=g):(h=t[5],g=t[6]),b.useLayoutEffect(h,g);let v,C;t[7]!==n||t[8]!==s||t[9]!==r||t[10]!==u?(v=()=>{let e=f.current;if(!e)return;let t=!0,i=new AbortController;return _(n,(r||`text`).toLowerCase(),{signal:i.signal,priority:`visible`}).then(n=>{!t||!e||!n||(e.innerHTML=n,I(e,u,s))}),()=>{t=!1,i.abort()}},C=[n,r,u,s],t[7]=n,t[8]=s,t[9]=r,t[10]=u,t[11]=v,t[12]=C):(v=t[11],C=t[12]),b.useEffect(v,C);let w;t[13]===i?w=t[14]:(w=o(`typography-code`,i),t[13]=i,t[14]=w);let T=m,E;t[15]!==a||t[16]!==T?(E={...T,...a},t[15]=a,t[16]=T,t[17]=E):E=t[17];let D;return t[18]!==w||t[19]!==E?(D=(0,S.jsx)(`div`,{ref:f,className:w,style:E}),t[18]=w,t[19]=E,t[20]=D):D=t[20],D},z=e=>{let t=e.replace(/^<file>\s*\n?/,``).replace(/\n?<\/file>\s*$/,``);return t=t.replace(/^\s*\d{5}\|\s?/gm,``),t.trim()},B=(e,t=``)=>{if(typeof e==`string`)return e;if(e==null)return t;if(typeof e==`number`||typeof e==`boolean`||typeof e==`bigint`)return String(e);try{return JSON.stringify(e)}catch{return t}},V=e=>e?e.includes(`<diagnostics`)||e.includes(`<file_diagnostics>`)||e.includes(`LSP errors detected`)||e.includes(`This file has errors`):!1,H=e=>e?e.replace(/\n{0,2}LSP errors detected[\s\S]*?<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/\n{0,2}This file has errors[\s\S]*?<\/file_diagnostics>/g,``).replace(/<diagnostics[^>]*>[\s\S]*?<\/diagnostics>/g,``).replace(/<file_diagnostics>[\s\S]*?<\/file_diagnostics>/g,``).trim():``,U=(e,t)=>!e||typeof e!=`object`?String(e):g(e,t||``),W=e=>{if(typeof e==`string`){let t=e.trim();return t.length>0?t:void 0}if(e&&typeof e==`object`){let t=e.patch;if(typeof t==`string`){let e=t.trim();return e.length>0?e:void 0}}},G=e=>{if(!e||typeof e!=`object`)return;let t=W(e.patch)??W(e.diff);if(t)return t;let n=Array.isArray(e.files)?e.files:[];for(let e of n){if(!e||typeof e!=`object`)continue;let t=W(e.patch)??W(e.diff);if(t)return t}},K=e=>{if(!e||typeof e!=`string`)return{data:null,isJson:!1};let t=e.trim();if(!t.startsWith(`{`)&&!t.startsWith(`[`)||!t.endsWith(`}`)&&!t.endsWith(`]`)||t.length<2)return{data:null,isJson:!1};try{let e=JSON.parse(t);return typeof e==`object`&&e?{data:e,isJson:!0}:{data:null,isJson:!1}}catch{return{data:null,isJson:!1}}},q=(e,t,n)=>{let r=z(e);if((t===`edit`||t===`multiedit`||t===`write`||t===`apply_patch`)&&V(r)&&(r=H(r)),(t===`edit`||t===`multiedit`||t===`apply_patch`)&&r.trim().length===0){let e=G(n);if(e)return e}return r},se=e=>{let t=e.match(/<type>(file|directory)<\/type>/i)?.[1]?.toLowerCase()??`unknown`,n=(e.match(/<content>([\s\S]*?)<\/content>/i)?.[1]??e).replace(/\r\n/g,`
|
|
2
2
|
`).split(`
|
|
3
3
|
`),r=e=>/\(\s*File has more lines\..*offset.*\)/i.test(e.trim());return{type:t,lines:n.map(e=>{let n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);if(t!==`directory`){let t=e.match(/^(\d+):\s?(.*)$/);if(t){let e=t[2],n=e.trim(),i=n.startsWith(`(`)&&n.endsWith(`)`)||r(n);return{lineNumber:i?null:Number(t[1]),text:e,isInfo:i}}}return{lineNumber:null,text:e,isInfo:i}}).filter((e,t,n)=>{if(e.text.trim().length>0)return!0;let r=n[t-1],i=n[t+1],a=!!(r?.isInfo||i?.isInfo),o=e.lineNumber!==null;return!(a&&o)})}},ce=(e,t)=>{try{let n=e.trim().split(`
|
|
4
4
|
`).filter(Boolean);if(n.length===0)return null;let i=[];return n.forEach(e=>{let t=e.match(/^(\s*)(.+)$/);if(t){let[,e,n]=t,r=Math.floor(e.length/2),a=!n.endsWith(`/`);i.push({name:n.replace(/\/$/,``),depth:r,isFile:a})}}),(0,S.jsx)(`div`,{className:o(`w-full min-w-0 font-mono space-y-0.5`,t?.unstyled?null:`p-3 bg-muted/20 rounded-xl border border-border/30`),style:r.tool.popup,children:i.map((e,t)=>(0,S.jsx)(`div`,{className:`min-w-0`,style:{paddingLeft:`${e.depth*20}px`},children:e.isFile?(0,S.jsx)(`span`,{className:`text-foreground/90 block truncate`,children:e.name}):(0,S.jsxs)(`span`,{className:`font-semibold text-foreground block truncate`,children:[e.name,`/`]})},t))})}catch{return null}},le={backgroundColor:`var(--status-info)`,opacity:.6},ue=(e,t,n)=>{try{let i=e.trim().split(`
|
package/dist/index.html
CHANGED
|
@@ -552,7 +552,7 @@
|
|
|
552
552
|
opacity: 0.72;
|
|
553
553
|
}
|
|
554
554
|
</style>
|
|
555
|
-
<script type="module" crossorigin src="/assets/main-
|
|
555
|
+
<script type="module" crossorigin src="/assets/main-C8xj4UeZ.js"></script>
|
|
556
556
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
557
557
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-ef9W7A43.js">
|
|
558
558
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CAZiOV2G.js">
|
|
@@ -575,10 +575,10 @@
|
|
|
575
575
|
<link rel="modulepreload" crossorigin href="/assets/platform-7bX6qtBV.js">
|
|
576
576
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-BHXvha-U.js">
|
|
577
577
|
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-DAOam_oV.js">
|
|
578
|
-
<link rel="modulepreload" crossorigin href="/assets/types-
|
|
579
|
-
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-
|
|
580
|
-
<link rel="modulepreload" crossorigin href="/assets/native-config-
|
|
581
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
578
|
+
<link rel="modulepreload" crossorigin href="/assets/types-DZcrt4Z0.js">
|
|
579
|
+
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-ChF9Cg3G.js">
|
|
580
|
+
<link rel="modulepreload" crossorigin href="/assets/native-config-D9DfX51I.js">
|
|
581
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-MKo97i27.js">
|
|
582
582
|
<link rel="stylesheet" crossorigin href="/assets/src-CmermOKP.css">
|
|
583
583
|
</head>
|
|
584
584
|
<body class="h-full bg-background text-foreground">
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-D17acnB9.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-ef9W7A43.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CAZiOV2G.js">
|
|
@@ -97,10 +97,10 @@
|
|
|
97
97
|
<link rel="modulepreload" crossorigin href="/assets/platform-7bX6qtBV.js">
|
|
98
98
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-BHXvha-U.js">
|
|
99
99
|
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-DAOam_oV.js">
|
|
100
|
-
<link rel="modulepreload" crossorigin href="/assets/types-
|
|
101
|
-
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-
|
|
102
|
-
<link rel="modulepreload" crossorigin href="/assets/native-config-
|
|
103
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
100
|
+
<link rel="modulepreload" crossorigin href="/assets/types-DZcrt4Z0.js">
|
|
101
|
+
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-ChF9Cg3G.js">
|
|
102
|
+
<link rel="modulepreload" crossorigin href="/assets/native-config-D9DfX51I.js">
|
|
103
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-MKo97i27.js">
|
|
104
104
|
<link rel="stylesheet" crossorigin href="/assets/src-CmermOKP.css">
|
|
105
105
|
</head>
|
|
106
106
|
<body class="h-full bg-background text-foreground">
|
package/dist/mobile.html
CHANGED
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-DiL1n1m5.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-hePW80VL.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/runtime-auth-ef9W7A43.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/runtime-switch-CAZiOV2G.js">
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
<link rel="modulepreload" crossorigin href="/assets/platform-7bX6qtBV.js">
|
|
87
87
|
<link rel="modulepreload" crossorigin href="/assets/terminalApi-BHXvha-U.js">
|
|
88
88
|
<link rel="modulepreload" crossorigin href="/assets/runtime-file-download-DAOam_oV.js">
|
|
89
|
-
<link rel="modulepreload" crossorigin href="/assets/types-
|
|
90
|
-
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-
|
|
91
|
-
<link rel="modulepreload" crossorigin href="/assets/native-config-
|
|
92
|
-
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-
|
|
89
|
+
<link rel="modulepreload" crossorigin href="/assets/types-DZcrt4Z0.js">
|
|
90
|
+
<link rel="modulepreload" crossorigin href="/assets/capgoAdapter-ChF9Cg3G.js">
|
|
91
|
+
<link rel="modulepreload" crossorigin href="/assets/native-config-D9DfX51I.js">
|
|
92
|
+
<link rel="modulepreload" crossorigin href="/assets/runtimeConfig-MKo97i27.js">
|
|
93
93
|
<link rel="stylesheet" crossorigin href="/assets/src-CmermOKP.css">
|
|
94
94
|
</head>
|
|
95
95
|
<body class="h-full bg-background text-foreground">
|
package/package.json
CHANGED
|
@@ -69,7 +69,11 @@ session/message APIs supply the history directly.
|
|
|
69
69
|
|
|
70
70
|
Web/desktop and hosted/Capacitor mobile mount the same `OnboardingScreen`
|
|
71
71
|
`content-index` step after connecting. It displays thirty-day progress, preserves
|
|
72
|
-
progress on request failure, and closes when verification succeeds.
|
|
72
|
+
progress on request failure, and closes when verification succeeds. The dialog
|
|
73
|
+
waits for a nonzero session total: pending catalog discovery and a successfully
|
|
74
|
+
empty fresh installation never flash onboarding. The status check continues
|
|
75
|
+
while the total is unknown/zero, so subsequently discovered history still opens
|
|
76
|
+
the step; hiding it does not certify coverage or cancel Host indexing. Continuing
|
|
73
77
|
in the background dismisses only this startup/runtime's UI, never writes a
|
|
74
78
|
completion marker and never cancels Host work. Polling stops after completion or
|
|
75
79
|
dismissal; runtime changes recheck. Older Hosts without the new status fields
|
|
@@ -32,8 +32,8 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
32
32
|
- `packages/web/server/lib/opencode/v1-migration-gate.js`: pure V1→v2 migration admission gate (`GET /api/experimental/migration/v1` → whether transcript may be fetched).
|
|
33
33
|
- `packages/web/server/lib/opencode/managed-capabilities-runtime.js`: managed-child scheduled-task resources, config injection, rotating bridge identity, and bridge authorization.
|
|
34
34
|
- `packages/web/server/lib/opencode/env-runtime.js`: OpenCode CLI/binary resolution and shell environment runtime.
|
|
35
|
-
- `packages/web/server/lib/opencode/ensure-cli.js`: detect local `opencode` version and install the
|
|
36
|
-
- `packages/web/server/lib/opencode/upgrade-screen.js`: startup gate for the running OpenCode below `2.0.20`. Client/schema dependencies and the
|
|
35
|
+
- `packages/web/server/lib/opencode/ensure-cli.js`: detect local `opencode` version and install the latest stable official V2 into the Yuri data dir when no usable CLI exists, falling back to the tested pin if registry lookup fails. Never uses a packaged/bundled binary.
|
|
36
|
+
- `packages/web/server/lib/opencode/upgrade-screen.js`: startup gate for the running OpenCode below `2.0.20`. Client/schema dependencies and the required-upgrade target are pinned to `2.0.23`; fresh automatic installations resolve latest stable 2.x. The compatible execution floor is independently `2.0.20` (credential API). An unreadable running version is unavailable even when the local CLI is compatible. `POST /api/opencode/install-required` upgrades the selected shared global CLI in place; other supported local installations use the owned-cache installer. Global upgrades do not persist a binary override, so subsequent launches still follow shell discovery. Success requires the running service to meet both the floor and selected target. External services are not replaced; compatible newer shared services are not automatically downgraded. Failure restores binary selection and reports `upgraded: false`, without claiming process or package-manager rollback.
|
|
37
37
|
- `packages/web/server/lib/opencode/env-config.js`: OpenCode-related environment variable parsing and validation (host/port/hostname).
|
|
38
38
|
- `packages/web/server/lib/opencode/hmr-state-runtime.js`: HMR-persistent runtime state initialization, auth-state bootstrap, and HMR sync helpers.
|
|
39
39
|
- `packages/web/server/lib/opencode/bootstrap-runtime.js`: base app bootstrap runtime for status/auth/tts/notification/Yuri route wiring.
|
|
@@ -167,9 +167,10 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
167
167
|
- `buildUpgradeStatusSnapshot` / `evaluateOwnedUpgradeResult` / `createUpgradeOperationState` / `resolveOwnedCacheBinaryPath`.
|
|
168
168
|
|
|
169
169
|
## Public exports (ensure-cli.js)
|
|
170
|
-
- `ensurePinnedOpenCode2Cli({ discoveredPath, pin, autoInstall, install, readVersion })`: reuse any discovered acceptable 2.x CLI (official name `opencode`).
|
|
170
|
+
- `ensurePinnedOpenCode2Cli({ discoveredPath, pin, autoInstall, install, readVersion })`: reuse any discovered acceptable 2.x CLI (official name `opencode`), then the newest verified owned cache. Only a fresh installation queries the stable 2.x release via `update-discovery.js`; an explicit pin or failed registry lookup uses the tested pin. Downloads stay under `~/.config/yuri/opencode-cli/<version>/opencode`. `YURI_OPENCODE2_AUTO_INSTALL=0` disables download (`OPENCODE_CLI_MISSING`).
|
|
171
171
|
- `installPinnedOpenCode2Cli(options)`: download `@opencode/cli-<os>-<arch>@pin`, extract, copy as `opencode`, chmod, verify `--version`.
|
|
172
|
-
- `installedOpenCode2BinaryPath(version)` / `readOpenCode2BinaryVersion(path)` / `isOpenCode2AutoInstallEnabled()`.
|
|
172
|
+
- `installedOpenCode2BinaryPath(version)` / `installedOpenCode2BinaryCandidates(options)` / `readOpenCode2BinaryVersion(path)` / `isOpenCode2AutoInstallEnabled()`. Cache candidates are stable 2.x directories ordered by numeric version, so an offline restart discovers a newer installation rather than reverting to the packaged pin.
|
|
173
|
+
- `terminal-path.js` exposes owned-cache CLIs to new terminals on installation/reuse. It writes an idempotent source line to zsh `.zprofile`/`.zshrc` (including `ZDOTDIR`), bash `.bashrc` and the active login profile, POSIX `.profile`, or fish `config.fish`. The sourced file in the Yuri data directory selects the verified executable directory. Windows updates only user PATH, retaining unrelated entries, broadcasts the environment change, and records `opencode-path.windows` in the data directory. Subsequent owned-cache upgrades refresh an existing terminal integration after binary verification. Existing global installations do not trigger shell edits. PATH-setup failure warns and keeps the working Yuri runtime; a later startup retries. Existing terminal processes need a new shell, and shell-file changes already completed before a failure are retained.
|
|
173
174
|
- `upgradeSelectedOpenCodeCli({ binaryPath, version, launchSpec, env })`: runs the selected CLI's official `upgrade <version>` without a shell, with a five-minute deadline and bounded captured output. Returns the original CLI entry path, preserving Homebrew symlinks. Windows launch specs unwrap native/runtime shims; unresolved batch wrappers require terminal upgrade instead of spawning console grandchildren. Child output is not returned or logged. Package-manager failures never fall back to another installation; callers verify the original entry's version before restarting. Package-manager file changes cannot be rolled back by restoring Yuri's binary selection.
|
|
174
175
|
|
|
175
176
|
## Public exports (v1-migration-gate.js)
|
|
@@ -179,7 +180,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
179
180
|
- `fetchV1MigrationGate({ url, headers, signal, fetchImpl })`: GET-only status poll used by lifecycle.
|
|
180
181
|
|
|
181
182
|
## Public exports (lifecycle.js)
|
|
182
|
-
- `createOpenCodeLifecycleRuntime(dependencies)`: creates lifecycle runtime for managed/external OpenCode process orchestration. Bootstrap reuses a healthy v2 `opencode serve` when present; otherwise starts managed `opencode serve`. Before managed spawn, `ensurePinnedOpenCode2CliEnv()` detects the local CLI version and installs the
|
|
183
|
+
- `createOpenCodeLifecycleRuntime(dependencies)`: creates lifecycle runtime for managed/external OpenCode process orchestration. Bootstrap reuses a healthy v2 `opencode serve` when present; otherwise starts managed `opencode serve`. Before managed spawn, `ensurePinnedOpenCode2CliEnv()` detects the local CLI version and installs official V2 through the fresh-install policy above when no usable CLI exists (skipped for external skip-start). Startup accepts both `server listening on http://127.0.0.1:PORT` and the legacy `opencode server listening on …` line. Readiness/health probes `GET /api/info` first, then `/global/health`, with Basic auth (username `opencode`). A body is admitted only when `evaluateOpenCodeHealthBody` accepts the version (rejects 1.x and missing/unknown). After health ok, `startOpenCode` / `waitForOpenCodeReady` poll `GET /api/experimental/migration/v1` (no POST; backfill is owned by opencode2). Each readiness attempt is one internal probe — health, then V1 migration, then contract publish — under one abort timer that covers **both** fetches so a hung migration cannot outrun the per-attempt budget. `waitForOpenCodeReady` only loops that probe until its deadline; `retryOpenCodeStartup` calls the same probe with a fresh generation, ownership, and shutdown scope. External instances are a read-only mount: restart/refresh only re-probe health + V1 migration (never kill, spawn, clear port, or flip `isExternalOpenCode`); health monitoring skips them; upgrade routes return 409. Startup does not prewarm persisted project directories: v2 session lists are storage-scoped and do not initialize locations, while location-scoped reads must follow actual consumer demand. External skip-start / auto-detect attach leave `isOpenCodeReady` false until the gate admits. `isOpenCodeReady` means transcript may be fetched only when the gate admits (`completed`, or no V1 library such as HTTP 404). `required` / `running` / `error` keep the ready gate closed; `error` is retried. The last gate result is stored on `state.v1Migration` and published on the Yuri `/health` snapshot so UI can render `phase` and running `progress` (`label` / `numerator` / `denominator`) plus `userNotice` (reuse message ids; in-progress tools become interrupted; V1 subtasks do not appear in v2).
|
|
183
184
|
- Shared official service (default for Web and Electron hosts, including mobile clients connected to those hosts): when `sharedService` is injected and neither an OpenCode port nor host is configured, bootstrap runs `opencode service start` before HMR/private-process reuse or any port-4096 probe. It attaches to the registered stable-channel service, adopts its password/base URL/port, and marks it external for shutdown. Closing Yuri leaves that service running; no managed capabilities (scheduled-task plugin, bridge token, `v2-plugin-host-shim`) are injected. Attach failure clears URL/port/password, reports an unavailable integration and permits explicit retry, without silently spawning a private server. Explicit host/port and shared-service opt-out retain their existing behavior; VS Code retains its extension-owned sidecar lifecycle.
|
|
184
185
|
- While attached, ordinary `restartOpenCode()` re-runs `service start` and adopts the latest registration before re-probing; a failed ensure propagates instead of probing stale credentials as success. Upgrade-only `restartOpenCode({ binaryPath })` bypasses settings rediscovery and runs the verified target's `service restart`. Configuration refresh uses `POST /api/location/reload` instead of process replacement (returns `{ reloaded: true, external: true }`). Health monitoring uses registration-PID liveness. Normal attachment/recovery does not version-pin or downgrade a healthy registered service; explicit in-app upgrade is the version-replacement operation.
|
|
185
186
|
- Returned API:
|
|
@@ -212,7 +213,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
212
213
|
- `remote-upgrade.integration.test.js` is opt-in via `YURI_TEST_REMOTE_UPGRADE=1`; it downloads official binaries, starts an isolated 2.0.16 shared service, discovers the remote latest release through the status route, upgrades through the HTTP route, and verifies the running version and cleared update availability. It never restarts the user's shared service.
|
|
213
214
|
|
|
214
215
|
## Public exports (env-runtime.js)
|
|
215
|
-
- `createOpenCodeEnvRuntime(dependencies)`: owns CLI environment and binary discovery state. Discovery order: settings/env, then interactive login-shell PATH `opencode` on macOS/Linux (host PATH if the shell snapshot is unavailable; Windows uses its hydrated host PATH), then known install locations, then
|
|
216
|
+
- `createOpenCodeEnvRuntime(dependencies)`: owns CLI environment and binary discovery state. Discovery order: settings/env, then interactive login-shell PATH `opencode` on macOS/Linux (host PATH if the shell snapshot is unavailable; Windows uses its hydrated host PATH), then known install locations, then the newest installed stable 2.x cache. Shell PATH order is authoritative for automatic discovery even if later host/runtime PATH augmentation prefers another installation. Shell aliases/functions are not executable PATH entries. Automatic discovery and bundled resources use only `opencode` (`opencode.exe` on Windows); 1.x binaries are skipped by version. Managed startup installs through the fresh-install policy above only when no usable 2.x CLI exists; a usable bundled CLI is the last resort if installation fails. `upgradeOpenCodeCli({ binaryPath, version })` supplies the selected launch spec and shell PATH to the official CLI upgrader. Explicit settings/environment overrides retain precedence.
|
|
216
217
|
- VS Code keeps a copied sidecar (`packages/vscode/src/opencode-sidecar.ts`) with the same invariants: discovery order may differ (no Electron bundled fallback), but reject list, listening lines, health path order (`/api/health` then `/global/health`), `{ healthy: true }`, and Basic username `opencode` must stay aligned. Changing one copy requires changing the other.
|
|
217
218
|
- Returned API:
|
|
218
219
|
- `applyLoginShellEnvSnapshot()`
|
|
@@ -378,7 +379,7 @@ Unrelated upstream failures are not retried.
|
|
|
378
379
|
- Provider catalog responses include only provider `id`, `name`, and safe models. Safe models include `id`, `name`, fixed text/audio/image/video/pdf capability modalities, bounded cost/limit fields, `release_date`, and variant names with `{}` values. v2 `ModelInfo.variants` is an array of `{ id, settings?, headers?, body? }`; projection keeps ids in array order as empty-object keys and drops request payloads. v2 `capabilities.tools` maps to `toolcall`, and `input`/`output` modality name arrays map to boolean maps. v2 `cost` tier arrays keep the untiered base price. Identifiers are bounded to 512 characters, display names to 1024, release dates to 64, numeric values to absolute 1e9, and catalog collections to 200 providers, 500 models per provider, and 100 defaults or variants. Provider credentials/configuration and unallowlisted model fields stay server-side. Malformed catalog roots and SDK error envelopes return HTTP 502. `partial: true` is structural only: provider/model/default truncation, dropped providers/models/defaults, or truncated variant dictionaries. Soft allowlist stripping of optional metadata (unknown modalities, non-boolean capability flags, out-of-range cost/limit numbers, empty/null/invalid `release_date`, invalid variant ids) keeps valid models and must not set `partial: true`, or UI refresh retains a stale complete snapshot after OpenCode provider updates. The same input shapes are accepted by the VS Code projector and `packages/ui/src/lib/configCatalogParser.ts`.
|
|
379
380
|
- Agents: `/api/config/agents/:name` and `/api/config/agents/:name/config`. A `native` create or update writes only the fields in that object into the selected user or project markdown file. `scope` chooses the file. `confirmDrop` removes fields the current format cannot represent; every other field already in the file stays unchanged.
|
|
380
381
|
- Commands: batched metadata via `POST /api/config/commands/metadata`; `{ catalog: true }` returns the compact autocomplete catalog without templates, plus CRUD at `/api/config/commands/:name`
|
|
381
|
-
- Global raw configs: `GET /api/config/global` discovers existing
|
|
382
|
+
- Global raw configs: `GET /api/config/global` always offers OpenCode and discovers existing plugin targets; `GET/PUT /api/config/global/:target` reads and writes `opencode`, `oh-my-opencode-slim`, and `oh-my-openagent` JSON or JSONC files. Missing OpenCode configuration returns an editable schema template without writing to disk; the first save creates `opencode.jsonc` (or the explicit `OPENCODE_CONFIG` path). Existing JSON/JSONC precedence and comments are preserved.
|
|
382
383
|
- MCP servers: `/api/config/mcp` and `/api/config/mcp/:name`
|
|
383
384
|
|
|
384
385
|
### Catalog / Yuri-owned API change checklist
|
|
@@ -166,7 +166,7 @@ async function findGlobalConfigPath(target, configDirectory) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
return null;
|
|
169
|
+
return target === 'opencode' ? configPath : null;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
function validateGlobalConfigContent(content) {
|
|
@@ -282,6 +282,10 @@ export const registerConfigEntityRoutes = (app, dependencies) => {
|
|
|
282
282
|
return res.json({ target: req.params.target, fileName: target.fileName, content });
|
|
283
283
|
} catch (error) {
|
|
284
284
|
if (error?.code === 'ENOENT') {
|
|
285
|
+
if (req.params.target === 'opencode') {
|
|
286
|
+
return res.json({ target: 'opencode', fileName: target.fileName,
|
|
287
|
+
content: '{\n "$schema": "https://opencode.ai/config.json"\n}\n' });
|
|
288
|
+
}
|
|
285
289
|
return res.status(404).json({ error: `${target.fileName} does not exist` });
|
|
286
290
|
}
|
|
287
291
|
console.error('Failed to read global configuration:', error);
|
|
@@ -495,6 +495,29 @@ describe('config entity agent metadata route', () => {
|
|
|
495
495
|
describe('global raw configuration routes', () => {
|
|
496
496
|
beforeEach(() => vi.stubEnv('OPENCODE_CONFIG', ''));
|
|
497
497
|
afterEach(() => vi.unstubAllEnvs());
|
|
498
|
+
it('offers an editable JSONC template on a fresh machine and creates it only on save', async () => {
|
|
499
|
+
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'yuri-global-config-'));
|
|
500
|
+
const configDirectory = path.join(root, 'opencode');
|
|
501
|
+
const app = express();
|
|
502
|
+
app.use(express.json());
|
|
503
|
+
registerConfigEntityRoutes(app, createDependencies(vi.fn(), configDirectory));
|
|
504
|
+
try {
|
|
505
|
+
const available = await request(app).get('/api/config/global').expect(200);
|
|
506
|
+
expect(available.body.targets).toEqual([{ target: 'opencode', fileName: 'opencode.jsonc' }]);
|
|
507
|
+
const template = await request(app).get('/api/config/global/opencode').expect(200);
|
|
508
|
+
expect(JSON.parse(template.body.content)).toEqual({ $schema: 'https://opencode.ai/config.json' });
|
|
509
|
+
await expect(fs.stat(configDirectory)).rejects.toMatchObject({ code: 'ENOENT' });
|
|
510
|
+
const content = '{\n // first configuration\n "model": "provider/model"\n}\n';
|
|
511
|
+
await request(app).put('/api/config/global/opencode').send({ content }).expect(200);
|
|
512
|
+
expect(await fs.readFile(path.join(configDirectory, 'opencode.jsonc'), 'utf8')).toBe(content);
|
|
513
|
+
const loaded = await request(app).get('/api/config/global/opencode').expect(200);
|
|
514
|
+
expect(loaded.body.content).toBe(content);
|
|
515
|
+
await request(app).get('/api/config/global/oh-my-openagent').expect(404);
|
|
516
|
+
} finally {
|
|
517
|
+
await fs.rm(root, { recursive: true, force: true });
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
498
521
|
it('reads and validates the configured OpenCode and oh-my-opencode files', async () => {
|
|
499
522
|
const app = express();
|
|
500
523
|
app.use(express.json());
|
|
@@ -2,8 +2,11 @@ import { execFile, spawnSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
+
import { createOpenCodeUpdateDiscovery } from './update-discovery.js';
|
|
6
|
+
import { configureOpenCodeTerminalPath } from './terminal-path.js';
|
|
5
7
|
import {
|
|
6
8
|
PINNED_OPENCODE2_VERSION,
|
|
9
|
+
compareOpenCode2Versions,
|
|
7
10
|
isAcceptableOpenCode2HealthVersion,
|
|
8
11
|
isOpenCode2VersionAtLeast,
|
|
9
12
|
npmPackageForOpenCode2,
|
|
@@ -12,6 +15,7 @@ import {
|
|
|
12
15
|
} from './opencode2-pin.js';
|
|
13
16
|
|
|
14
17
|
const INSTALL_FETCH_TIMEOUT_MS = 30_000;
|
|
18
|
+
const discoverLatestVersion = createOpenCodeUpdateDiscovery();
|
|
15
19
|
|
|
16
20
|
export async function upgradeSelectedOpenCodeCli({ binaryPath, version, launchSpec, env = process.env, execFileImpl = execFile }) {
|
|
17
21
|
if (launchSpec?.wrapperType === 'cmd-wrapper') {
|
|
@@ -55,6 +59,15 @@ export function installedOpenCode2BinaryPath(version = PINNED_OPENCODE2_VERSION,
|
|
|
55
59
|
return path.join(dataDir, 'opencode-cli', version, openCodeBinaryName(options.platform));
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
export function installedOpenCode2BinaryCandidates(options = {}) {
|
|
63
|
+
const root = path.dirname(path.dirname(installedOpenCode2BinaryPath(undefined, options)));
|
|
64
|
+
let versions;
|
|
65
|
+
try { versions = fs.readdirSync(root).filter((name) => /^2\.\d+\.\d+$/.test(name)); }
|
|
66
|
+
catch (error) { if (error.code === 'ENOENT') return []; throw error; }
|
|
67
|
+
return versions.sort((a, b) => compareOpenCode2Versions(b, a))
|
|
68
|
+
.map((version) => installedOpenCode2BinaryPath(version, options));
|
|
69
|
+
}
|
|
70
|
+
|
|
58
71
|
export function readOpenCode2BinaryVersion(binaryPath, options = {}) {
|
|
59
72
|
if (typeof binaryPath !== 'string' || !binaryPath.trim()) return '';
|
|
60
73
|
const run = typeof options.spawnSync === 'function' ? options.spawnSync : spawnSync;
|
|
@@ -217,12 +230,16 @@ export async function installPinnedOpenCode2Cli(options = {}) {
|
|
|
217
230
|
if (preparedVersion !== version) {
|
|
218
231
|
throw new Error(`Installed opencode2 version mismatch: expected ${version}, got ${preparedVersion || 'unknown'}`);
|
|
219
232
|
}
|
|
233
|
+
if (['opencode-path.sh', 'opencode-path.fish', 'opencode-path.windows'].some((name) => fs.existsSync(path.join(dataDir, name)))) {
|
|
234
|
+
try { configureOpenCodeTerminalPath(outputBinary, { ...options, dataDir }); }
|
|
235
|
+
catch (error) { console.warn('[OpenCode] Terminal PATH refresh failed:', error.message); }
|
|
236
|
+
}
|
|
220
237
|
return outputBinary;
|
|
221
238
|
}
|
|
222
239
|
|
|
223
240
|
/**
|
|
224
|
-
* Reuse any discovered acceptable 2.x CLI.
|
|
225
|
-
*
|
|
241
|
+
* Reuse any discovered acceptable 2.x CLI. Fresh installs resolve stable 2.x;
|
|
242
|
+
* an explicit pin or an unavailable registry uses the tested version instead.
|
|
226
243
|
*/
|
|
227
244
|
export async function ensurePinnedOpenCode2Cli(input = {}) {
|
|
228
245
|
const pin = input.pin || PINNED_OPENCODE2_VERSION;
|
|
@@ -232,18 +249,29 @@ export async function ensurePinnedOpenCode2Cli(input = {}) {
|
|
|
232
249
|
? input.readVersion
|
|
233
250
|
: (binaryPath) => readOpenCode2BinaryVersion(binaryPath, input);
|
|
234
251
|
const install = typeof input.install === 'function' ? input.install : installPinnedOpenCode2Cli;
|
|
252
|
+
const finish = (result) => {
|
|
253
|
+
const root = path.dirname(path.dirname(installedOpenCode2BinaryPath(pin, input)));
|
|
254
|
+
const relative = path.relative(root, result.path);
|
|
255
|
+
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
256
|
+
try { configureOpenCodeTerminalPath(result.path, input); }
|
|
257
|
+
catch (error) { console.warn('[OpenCode] CLI is available in Yuri, but terminal PATH setup failed:', error.message); }
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
};
|
|
235
261
|
|
|
236
262
|
if (discoveredPath) {
|
|
237
263
|
const version = readVersion(discoveredPath);
|
|
238
264
|
if (isAcceptableOpenCode2HealthVersion(version)) {
|
|
239
|
-
return { path: discoveredPath, version, source: 'discovered', installed: false };
|
|
265
|
+
return finish({ path: discoveredPath, version, source: 'discovered', installed: false });
|
|
240
266
|
}
|
|
241
267
|
}
|
|
242
268
|
|
|
243
|
-
const
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
269
|
+
const candidates = [...new Set([...installedOpenCode2BinaryCandidates(input), installedOpenCode2BinaryPath(pin, input)])];
|
|
270
|
+
for (const cached of candidates) {
|
|
271
|
+
const cachedVersion = readVersion(cached);
|
|
272
|
+
if (isOpenCode2VersionAtLeast(cachedVersion, pin)) {
|
|
273
|
+
return finish({ path: cached, version: cachedVersion, source: 'installed', installed: false });
|
|
274
|
+
}
|
|
247
275
|
}
|
|
248
276
|
|
|
249
277
|
if (!autoInstall) {
|
|
@@ -254,10 +282,19 @@ export async function ensurePinnedOpenCode2Cli(input = {}) {
|
|
|
254
282
|
throw error;
|
|
255
283
|
}
|
|
256
284
|
|
|
257
|
-
|
|
285
|
+
let target = pin;
|
|
286
|
+
if (!input.pin) {
|
|
287
|
+
try {
|
|
288
|
+
const latest = await (input.resolveLatestVersion || discoverLatestVersion)();
|
|
289
|
+
if (/^2\.\d+\.\d+$/.test(latest) && isOpenCode2VersionAtLeast(latest, pin)) target = latest;
|
|
290
|
+
} catch {
|
|
291
|
+
console.warn('[OpenCode] Latest version lookup failed; using the tested fallback version.');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const installedPath = await install({ ...input, version: target });
|
|
258
295
|
const version = readVersion(installedPath);
|
|
259
|
-
if (version !==
|
|
260
|
-
throw new Error(`Installed OpenCode version mismatch: expected ${
|
|
296
|
+
if (version !== target) {
|
|
297
|
+
throw new Error(`Installed OpenCode version mismatch: expected ${target}, got ${version || 'unknown'}`);
|
|
261
298
|
}
|
|
262
|
-
return { path: installedPath, version, source: 'installed', installed: true };
|
|
299
|
+
return finish({ path: installedPath, version, source: 'installed', installed: true });
|
|
263
300
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { PINNED_OPENCODE2_VERSION } from './opencode2-pin.js';
|
|
6
|
+
import { configureOpenCodeTerminalPath } from './terminal-path.js';
|
|
6
7
|
import {
|
|
7
8
|
ensurePinnedOpenCode2Cli,
|
|
8
9
|
installedOpenCode2BinaryPath,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
upgradeSelectedOpenCodeCli,
|
|
12
13
|
} from './ensure-cli.js';
|
|
13
14
|
|
|
15
|
+
vi.mock('./terminal-path.js', () => ({ configureOpenCodeTerminalPath: vi.fn() }));
|
|
16
|
+
|
|
14
17
|
const tempDirs = [];
|
|
15
18
|
|
|
16
19
|
const createTempDir = (prefix) => {
|
|
@@ -20,12 +23,60 @@ const createTempDir = (prefix) => {
|
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
afterEach(() => {
|
|
26
|
+
vi.clearAllMocks();
|
|
23
27
|
for (const dir of tempDirs.splice(0)) {
|
|
24
28
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
25
29
|
}
|
|
26
30
|
});
|
|
27
31
|
|
|
28
32
|
describe('ensurePinnedOpenCode2Cli', () => {
|
|
33
|
+
it('resolves the latest stable 2.x for a fresh installation', async () => {
|
|
34
|
+
const dataDir = createTempDir('yuri-latest-cli-');
|
|
35
|
+
const installed = path.join(dataDir, 'opencode-cli', '2.9.1', 'opencode');
|
|
36
|
+
const install = vi.fn(async () => installed);
|
|
37
|
+
const result = await ensurePinnedOpenCode2Cli({ dataDir,
|
|
38
|
+
resolveLatestVersion: async () => '2.9.1', install,
|
|
39
|
+
readVersion: (file) => file === installed ? '2.9.1' : '',
|
|
40
|
+
});
|
|
41
|
+
expect(install).toHaveBeenCalledWith(expect.objectContaining({ version: '2.9.1' }));
|
|
42
|
+
expect(result.version).toBe('2.9.1');
|
|
43
|
+
expect(configureOpenCodeTerminalPath).toHaveBeenCalledWith(installed, expect.objectContaining({ dataDir }));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it.each(['1.18.4', '3.0.0', '2.9.1-beta.1', '2.0.1'])('never chooses an unsuitable fresh-install target %s', async (latest) => {
|
|
47
|
+
const install = vi.fn(async () => '/installed/opencode');
|
|
48
|
+
await ensurePinnedOpenCode2Cli({ dataDir: createTempDir('yuri-invalid-cli-'), install,
|
|
49
|
+
resolveLatestVersion: async () => latest,
|
|
50
|
+
readVersion: (file) => file === '/installed/opencode' ? PINNED_OPENCODE2_VERSION : '',
|
|
51
|
+
});
|
|
52
|
+
expect(install).toHaveBeenCalledWith(expect.objectContaining({ version: PINNED_OPENCODE2_VERSION }));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('reuses the newest verified cache offline instead of reinstalling the pin', async () => {
|
|
56
|
+
const dataDir = createTempDir('yuri-new-cache-');
|
|
57
|
+
for (const version of [PINNED_OPENCODE2_VERSION, '2.9.1', '2.10.0']) {
|
|
58
|
+
fs.mkdirSync(path.join(dataDir, 'opencode-cli', version), { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
const latest = installedOpenCode2BinaryPath('2.10.0', { dataDir });
|
|
61
|
+
const resolveLatestVersion = vi.fn();
|
|
62
|
+
const result = await ensurePinnedOpenCode2Cli({ dataDir, resolveLatestVersion,
|
|
63
|
+
readVersion: (file) => path.basename(path.dirname(file)),
|
|
64
|
+
install: async () => { throw new Error('must not install'); },
|
|
65
|
+
});
|
|
66
|
+
expect(result).toMatchObject({ path: latest, version: '2.10.0', installed: false });
|
|
67
|
+
expect(resolveLatestVersion).not.toHaveBeenCalled();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses the tested pin when the registry is unavailable', async () => {
|
|
71
|
+
const dataDir = createTempDir('yuri-offline-cli-');
|
|
72
|
+
const install = vi.fn(async () => '/installed/opencode');
|
|
73
|
+
await ensurePinnedOpenCode2Cli({ dataDir, install,
|
|
74
|
+
resolveLatestVersion: async () => { throw new Error('offline'); },
|
|
75
|
+
readVersion: (file) => file === '/installed/opencode' ? PINNED_OPENCODE2_VERSION : '',
|
|
76
|
+
});
|
|
77
|
+
expect(install).toHaveBeenCalledWith(expect.objectContaining({ version: PINNED_OPENCODE2_VERSION }));
|
|
78
|
+
});
|
|
79
|
+
|
|
29
80
|
it('runs upgrade through the selected launch target and preserves its CLI entry path', async () => {
|
|
30
81
|
const dir = createTempDir('yuri-upgrade-cli-');
|
|
31
82
|
const script = path.join(dir, 'cli.cjs');
|
|
@@ -86,6 +137,7 @@ describe('ensurePinnedOpenCode2Cli', () => {
|
|
|
86
137
|
});
|
|
87
138
|
expect(result.source).toBe('discovered');
|
|
88
139
|
expect(result.path).toBe('/opt/old-opencode');
|
|
140
|
+
expect(configureOpenCodeTerminalPath).not.toHaveBeenCalled();
|
|
89
141
|
});
|
|
90
142
|
|
|
91
143
|
it('installs the pin only when no usable 2.x binary is present', async () => {
|
|
@@ -3,8 +3,8 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { mergePathValues } from './path-utils.js';
|
|
6
|
-
import {
|
|
7
|
-
import { ensurePinnedOpenCode2Cli,
|
|
6
|
+
import { isAcceptableOpenCode2HealthVersion, isOpenCode1xVersion } from './opencode2-pin.js';
|
|
7
|
+
import { ensurePinnedOpenCode2Cli, installedOpenCode2BinaryCandidates, readOpenCode2BinaryVersion, upgradeSelectedOpenCodeCli } from './ensure-cli.js';
|
|
8
8
|
|
|
9
9
|
export const createOpenCodeEnvRuntime = (deps) => {
|
|
10
10
|
const {
|
|
@@ -280,12 +280,12 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
280
280
|
};
|
|
281
281
|
|
|
282
282
|
const resolveInstalledOpenCodeCliPath = () => {
|
|
283
|
-
const installed =
|
|
283
|
+
const installed = installedOpenCode2BinaryCandidates({
|
|
284
284
|
env: process.env,
|
|
285
285
|
homedir: resolveHomeDir,
|
|
286
286
|
platform: process.platform,
|
|
287
|
-
});
|
|
288
|
-
if (
|
|
287
|
+
}).find((binary) => isExecutable(binary) && !isWindowsOpenCodeDesktopAppPath(binary));
|
|
288
|
+
if (installed) {
|
|
289
289
|
clearWslOpencodeResolution();
|
|
290
290
|
state.resolvedOpencodeBinarySource = 'installed';
|
|
291
291
|
return installed;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
export function configureOpenCodeTerminalPath(binaryPath, options = {}) {
|
|
7
|
+
const env = options.env || process.env;
|
|
8
|
+
const home = typeof options.homedir === 'function' ? options.homedir() : options.homedir || os.homedir();
|
|
9
|
+
const platform = options.platform || process.platform;
|
|
10
|
+
const dataDir = options.dataDir || env.YURI_DATA_DIR || path.join(home, '.config', 'yuri');
|
|
11
|
+
const directory = path.dirname(binaryPath);
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
const run = options.spawnSync || spawnSync;
|
|
14
|
+
const result = run('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command', `
|
|
15
|
+
$ErrorActionPreference = 'Stop'
|
|
16
|
+
$root = $env:YURI_CLI_CACHE_ROOT.TrimEnd('\\') + '\\'
|
|
17
|
+
$entries = @([Environment]::GetEnvironmentVariable('Path', 'User') -split ';' | Where-Object { $_ -and -not $_.StartsWith($root, [StringComparison]::OrdinalIgnoreCase) })
|
|
18
|
+
$value = (@($env:YURI_CLI_BIN_DIR) + $entries) -join ';'
|
|
19
|
+
[Environment]::SetEnvironmentVariable('Path', $value, 'User')
|
|
20
|
+
Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class YuriEnvironment { [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wParam, string lParam, uint flags, uint timeout, out UIntPtr result); }'
|
|
21
|
+
$result = [UIntPtr]::Zero
|
|
22
|
+
[void][YuriEnvironment]::SendMessageTimeout([IntPtr]0xffff, 0x1a, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$result)
|
|
23
|
+
`], { env: { ...env, YURI_CLI_CACHE_ROOT: path.join(dataDir, 'opencode-cli'), YURI_CLI_BIN_DIR: directory },
|
|
24
|
+
encoding: 'utf8', timeout: 15_000, windowsHide: true });
|
|
25
|
+
if (result.status !== 0) throw new Error('Cannot add OpenCode to the Windows user PATH');
|
|
26
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
27
|
+
fs.writeFileSync(path.join(dataDir, 'opencode-path.windows'), directory);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const shell = env.SHELL || (platform === 'darwin' ? '/bin/zsh' : '/bin/sh');
|
|
32
|
+
const kind = path.basename(shell);
|
|
33
|
+
const files = [];
|
|
34
|
+
if (kind === 'zsh') {
|
|
35
|
+
const run = options.spawnSync || spawnSync;
|
|
36
|
+
const probe = run(shell, ['-c', 'printf "%s" "${ZDOTDIR:-$HOME}"'], { env: { ...env, HOME: home }, encoding: 'utf8', timeout: 5000 });
|
|
37
|
+
if (probe.status !== 0 || !path.isAbsolute(probe.stdout || '')) throw new Error('Cannot resolve zsh startup directory');
|
|
38
|
+
files.push(path.join(probe.stdout, '.zprofile'), path.join(probe.stdout, '.zshrc'));
|
|
39
|
+
} else if (kind === 'bash') {
|
|
40
|
+
files.push(path.join(home, '.bashrc'));
|
|
41
|
+
files.push(['.bash_profile', '.bash_login', '.profile'].map((name) => path.join(home, name)).find((file) => fs.existsSync(file)) || path.join(home, '.profile'));
|
|
42
|
+
} else if (['sh', 'dash', 'ksh'].includes(kind)) {
|
|
43
|
+
files.push(path.join(home, '.profile'));
|
|
44
|
+
} else if (kind === 'fish') {
|
|
45
|
+
files.push(path.join(env.XDG_CONFIG_HOME || path.join(home, '.config'), 'fish', 'config.fish'));
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error(`Cannot configure OpenCode PATH for shell ${kind}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const quote = kind === 'fish'
|
|
51
|
+
? (value) => `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`
|
|
52
|
+
: (value) => `'${value.replace(/'/g, "'\\''")}'`;
|
|
53
|
+
const envFile = path.join(dataDir, kind === 'fish' ? 'opencode-path.fish' : 'opencode-path.sh');
|
|
54
|
+
const content = kind === 'fish'
|
|
55
|
+
? `contains -- ${quote(directory)} $PATH; or set -gx PATH ${quote(directory)} $PATH\n`
|
|
56
|
+
: `case ":$PATH:" in *${quote(`:${directory}:`)}*) ;; *) export PATH=${quote(directory)}:"$PATH" ;; esac\n`;
|
|
57
|
+
fs.mkdirSync(path.dirname(envFile), { recursive: true });
|
|
58
|
+
fs.writeFileSync(`${envFile}.tmp`, content, { mode: 0o600 });
|
|
59
|
+
fs.renameSync(`${envFile}.tmp`, envFile);
|
|
60
|
+
const line = `${kind === 'fish' ? 'source' : '.'} ${quote(envFile)} # Yuri OpenCode CLI PATH`;
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
63
|
+
let existing = '';
|
|
64
|
+
try { existing = fs.readFileSync(file, 'utf8'); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
65
|
+
if (!existing.split('\n').includes(line)) fs.appendFileSync(file, `\n${line}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|