opencode-navigator 0.26.0 → 0.27.1

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.
@@ -1,5 +1,5 @@
1
1
  import {SECTION_DEFINITIONS,PLUGIN_ID
2
- } from "./chunk-bv0nm1tj.js"
2
+ } from "./chunk-dm1y8x49.js"
3
3
  import"./chunk-qmntexwm.js"
4
4
  import {mcpPresetPreview
5
5
  } from "./chunk-v6bz59xn.js";
@@ -16,6 +16,7 @@ import {memo as _$memo4} from "@opentui/solid"
16
16
  import {For as For3,createMemo as createMemo3,onMount as onMount2} from "solid-js"
17
17
  import {useDialogs as useDialogs3,useIcons as useIcons6} from "./tui.js"
18
18
  import {createMemo} from "solid-js"
19
+ var alwaysCurrent=()=>true
19
20
  function createLayoutPresetPreview(preferences,name){const scope=preferences.selectedScopeKey()
20
21
  const valid=()=>preferences.selectedScopeKey()===scope
21
22
  const preset=createMemo(()=>preferences.layoutPresets()[name])
@@ -28,14 +29,16 @@ return false
28
29
  preferences.applyLayoutPreset(value)
29
30
  return true
30
31
  }}}
31
- function createMcpPresetPreview(controller,preferences,name){const target=controller.target()
32
+ function createMcpPresetPreview(controller,preferences,name,isCurrent=alwaysCurrent){const target=controller.target()
32
33
  const valid=()=>{const current=controller.target()
33
- return current.key===target.key&&current.scope===target.scope
34
+ return isCurrent()&&current.key===target.key&&current.scope===target.scope
34
35
  }
35
36
  const preset=createMemo(()=>preferences.mcpPresets()[name])
36
- const preview=createMemo(()=>mcpPresetPreview(controller.list(target),preset()??{}))
37
+ const preview=createMemo(()=>mcpPresetPreview(valid()?controller.list(target):[],preset()??{}))
37
38
  const state=createMemo(()=>controller.state(target))
38
- const blocked=createMemo(()=>{if(!preset())
39
+ const blocked=createMemo(()=>{if(!valid())
40
+ return "Session or server changed; reopen the preview."
41
+ if(!preset())
39
42
  return "Preset no longer exists."
40
43
  if(controller.mutating(target))
41
44
  return "Wait for the current MCP operation to finish."
@@ -48,6 +51,28 @@ controller.refresh(target,true).catch(()=>{})
48
51
  },apply(){const value=preset()
49
52
  return value&&valid()&&!blocked()?controller.applyPreset(name,value,target):undefined
50
53
  }}}
54
+ var alwaysCurrent2=()=>true
55
+ function createWorkspacePresetPreview(preferences,name,controller,current=alwaysCurrent2){const layout=createLayoutPresetPreview(preferences,name)
56
+ const linked=preferences.workspaceProfiles()[name]
57
+ const mcp=controller&&linked?createMcpPresetPreview(controller,preferences,linked,current):undefined
58
+ const valid=()=>current()&&layout.valid()&&preferences.workspaceProfiles()[name]===linked&&(!mcp||mcp.valid())
59
+ const blocked=()=>{if(!valid())
60
+ return "Profile, scope, session or server changed. Reopen the preview."
61
+ if(linked&&!mcp)
62
+ return "Open the matching server session before applying its linked MCP preset."
63
+ return layout.blocked()||mcp?.blocked()||""
64
+ }
65
+ return{layout,mcp,linked,valid,blocked,apply(){if(blocked())
66
+ return
67
+ const request=mcp?.apply()
68
+ if(mcp&&!request)
69
+ return
70
+ if(!layout.apply())
71
+ return
72
+ return Promise.resolve(request).then(()=>{if(controller&&mcp&&controller.bulkState().failed.length>0)
73
+ throw new Error("Layout applied, but some MCP servers could not change. Review MCP failures and retry them.")
74
+ })
75
+ }}}
51
76
  import {mergeProps as _$mergeProps} from "@opentui/solid"
52
77
  import {createComponent as _$createComponent5} from "@opentui/solid"
53
78
  import {memo as _$memo3} from "@opentui/solid"
@@ -316,9 +341,9 @@ return{icon:icons.icon("mcp"),title:row.name,description,changed:row.change==="c
316
341
  }
317
342
  function LayoutPresetPreview(props){const dialogs=useDialogs3(props.api)
318
343
  const icons=useIcons6()
319
- const model=createLayoutPresetPreview(props.preferences,props.name)
320
- const linked=props.preferences.workspaceProfiles?.()[props.name]
321
- const mcpModel=props.controller&&linked?createMcpPresetPreview(props.controller,props.preferences,linked):undefined
344
+ const combined=createWorkspacePresetPreview(props.preferences,props.name,props.controller)
345
+ const model=combined.layout
346
+ const mcpModel=combined.mcp
322
347
  const rows=createMemo3(()=>model.preview()?.rows.map((row)=>{const changes=[]
323
348
  if(row.visibility[0]!==row.visibility[1])
324
349
  changes.push(`${row.visibility[0] ? "visible" : "hidden"} -> ${row.visibility[1] ? "visible" : "hidden"}`)
@@ -338,12 +363,11 @@ return _$createComponent6(PresetPreviewFrame,{get api(){return props.api
338
363
  },{icon:"done",label:`${rows().length - changed()} layout unchanged`,tone:"muted"
339
364
  }]
340
365
  },get rowCount(){return rows().length + mcpRows().length
341
- },valid:()=>model.valid()&&(!mcpModel||mcpModel.valid()),get blocked(){return model.blocked()||mcpModel?.blocked()
366
+ },get valid(){return combined.valid
367
+ },get blocked(){return combined.blocked()
342
368
  },get onRefresh(){return mcpModel?.refresh
343
- },notice:mcpModel?"Applies both presets; layout stays session-only.":"Applies layout changes. Save current layout as default to persist them.",onApply:()=>{const request=mcpModel?.apply()
344
- if(mcpModel&&!request)
345
- return
346
- if(!model.apply())
369
+ },notice:mcpModel?"Applies both presets; layout stays session-only.":"Applies layout changes. Save current layout as default to persist them.",onApply:()=>{const request=combined.apply()
370
+ if(!request)
347
371
  return
348
372
  if(mcpModel)
349
373
  dialogs.close()
@@ -352,7 +376,6 @@ dialogs.back()
352
376
  }
353
377
  props.api.ui.toast({variant:"success",title:mcpModel?"Workspace profile":"Layout presets",message:`${props.name} applied to ${model.scope()}`,duration:3000
354
378
  })
355
- if(request)
356
379
  reportMcpApplyError(props.api,request)
357
380
  },get children(){return [_$createComponent6(For3,{get each(){return rows()
358
381
  },children:(row)=>_$createComponent6(PresetPreviewRow,_$mergeProps2({get api(){return props.api
@@ -887,7 +887,8 @@ return
887
887
  setFiles((previous)=>({...previous,[target.key]:config.files}))
888
888
  set(target.key,{status:"ready",enabled:config.enabled,error:config.unavailable})
889
889
  } catch{if(!input.signal.aborted&&revisions.get(target.key)===revision)
890
- set(target.key,{status:"error",error:"Could not read OpenCode Todo configuration. Retry."})
890
+ set(target.key,{...state(sessionID),status:"error",error:"Could not read OpenCode Todo configuration. Retry."
891
+ })
891
892
  }}
892
893
  const unsubscribe=input.source.subscribe((directory)=>{if(active&&(!directory||directory===active.routing.directory))
893
894
  refresh(active.sessionID)
@@ -1,7 +1,7 @@
1
1
  import {PLUGIN_ID
2
- } from "./chunk-bv0nm1tj.js"
2
+ } from "./chunk-dm1y8x49.js"
3
3
  import {parseChangelog2,readBundledChangelog2
4
- } from "./chunk-pwa1wzer.js"
4
+ } from "./chunk-rrvwmnyh.js"
5
5
  import {isNewerVersion,isSameMajorVersion
6
6
  } from "./chunk-fd7394y1.js";
7
7
  import {createSignal} from "solid-js"
@@ -1,5 +1,5 @@
1
1
  import {SECTION_DEFINITIONS,PLUGIN_ID
2
- } from "./chunk-bv0nm1tj.js"
2
+ } from "./chunk-dm1y8x49.js"
3
3
  import"./chunk-qmntexwm.js";
4
4
  import {createComponent as _$createComponent3} from "@opentui/solid"
5
5
  import {effect as _$effect4} from "@opentui/solid"
@@ -34,8 +34,7 @@ var ONBOARDING_STEPS=[{title:"A control center that stays compact",summary:"Navi
34
34
  },{icon:"info",title:"Readable controls",description:"Use Nerd Font icons with the corner font, disable multiline masks, or choose Text fallback. Open Settings with Ctrl+,."
35
35
  }]
36
36
  }]
37
- function onboardingSteps(api){const capabilities=hostCapabilities(api)
38
- return ONBOARDING_STEPS.map((step,index)=>{if(index===1&&capabilities.nativeTodo)
37
+ function createOnboardingSteps(capabilities){return ONBOARDING_STEPS.map((step,index)=>{if(index===1&&capabilities.nativeTodo)
39
38
  return{...step,features:[{icon:"todo",title:"Todo filters and enablement",description:"If native Todo is off, use Enable Todo and confirm its server-config change. Edit tasks through chat and todowrite."
40
39
  },...step.features.slice(1)]
41
40
  }
@@ -43,15 +42,23 @@ if(index===3)
43
42
  return{...step,features:step.features.map((feature)=>{if(feature.icon==="lsp"&&!capabilities.lsp)
44
43
  return{...feature,title:"Language tools on this host",description:"OpenCode 2.x does not expose LSP status or actions. Use project lint and compiler commands instead."
45
44
  }
46
- if(feature.title==="Session permissions"&&capabilities.permissionMode===false)
45
+ if(feature.title==="Session permissions"&&!capabilities.permissionMode)
47
46
  return{...feature,description:"The Permissions action opens host Settings; choose Session \u2192 Permissions there."
48
47
  }
49
48
  return feature
50
49
  })
51
50
  }
51
+ if(index===5&&capabilities.desktop)
52
+ return{...step,features:step.features.map((feature)=>feature.title==="Readable controls"?{...feature,title:"Native Desktop controls",description:"Use the native panel or an opt-in in-app mini-window. Theme, typography and keyboard focus follow Desktop; terminal fonts are not required."
53
+ }:feature)
54
+ }
52
55
  return step
53
56
  })
54
57
  }
58
+ function onboardingSteps(api){const capabilities=hostCapabilities(api)
59
+ return createOnboardingSteps({nativeTodo:capabilities.nativeTodo===true,lsp:capabilities.lsp,permissionMode:capabilities.permissionMode!==false
60
+ })
61
+ }
55
62
  import {effect as _$effect2} from "@opentui/solid"
56
63
  import {memo as _$memo} from "@opentui/solid"
57
64
  import {createComponent as _$createComponent} from "@opentui/solid"
@@ -205,49 +205,29 @@ throw new Error("Settings JSON contains no supported settings")
205
205
  const settings={format:PORTABLE_SETTINGS_FORMAT,version:1,...hasLayout&&{layout:parseLayout(input.layout,unsupported)},...hasMcp&&{mcp:parseMcp(input.mcp)},...behavior.toolbarOverflow&&{behavior}}
206
206
  unsupported.sort()
207
207
  return{settings,unsupported}}
208
- var FILENAME=join(".opencode","navigator.json")
209
208
  var PRIVATE_KEYS=new Set(["favoriteSkills","favoriteMcpServers","favoriteQuickActions","skillGroups","recentSkills","recentQuickActions","skippedSkillConfirmations","onboardingCompleted","codexAccountBindings","pendingResetAttempt","lastNavigatorVersion","previousNavigatorVersion","worktrees","user"])
210
209
  function object2(value){if(!value||typeof value!=="object"||Array.isArray(value))
211
210
  throw new Error("Project settings must be an object")
212
211
  return value
213
212
  }
214
- async function readProject(file){const info=await lstat(file).catch((error)=>{if(error.code==="ENOENT")
215
- return
216
- throw error
217
- })
218
- if(info?.isSymbolicLink()||info&&!info.isFile())
219
- throw new Error("Project settings must be a regular file")
220
- try{return await readFile(file,"utf8")
221
- } catch(error){if(error.code==="ENOENT")
222
- return
223
- throw error
224
- }}
225
- function parseProject(source){if(source===undefined)
226
- return{}
227
- const input=object2(JSON.parse(source))
228
- const keys=Object.keys(input)
229
- for(const key of keys){if(PRIVATE_KEYS.has(key))
230
- throw new Error(`Project settings contain private field ${key}`)
231
- }
232
- parseConfiguredDefaults(input)
233
- return input
234
- }
235
- async function previewProjectProfile(root,nameInput,source,replace=false){const directory=await realpath(resolve(root))
236
- const name=nameInput.trim()
213
+ function buildProjectProfile(nameInput,source,before,replace=false){const name=nameInput.trim()
237
214
  if(!name||name.length>64)
238
215
  throw new Error("Profile names must contain 1\u201364 characters")
239
- if(name.includes("/")||name.includes("\\")||name.startsWith("~")){throw new Error("Profile names must not contain private paths")
240
- }
216
+ if(name.includes("/")||name.includes("\\")||name.startsWith("~"))
217
+ throw new Error("Profile names must not contain private paths")
241
218
  const parsed=parsePortableSettings(source)
242
219
  const layout=parsed.settings.layout&&parseSectionLayout(parsed.settings.layout)
243
220
  if(!layout)
244
221
  throw new Error("A project profile needs a layout")
245
222
  const servers=Object.keys(parsed.settings.mcp??{})
246
- for(const server of servers){if(server.includes("/")||server.includes("\\")||server.startsWith("~")){throw new Error(`MCP server name looks like a private path: ${server}`)
247
- }}
248
- const file=join(directory,FILENAME)
249
- const before=await readProject(file)
250
- const existing=parseProject(before)
223
+ for(const server of servers){if(server.includes("/")||server.includes("\\")||server.startsWith("~"))
224
+ throw new Error(`MCP server name looks like a private path: ${server}`)
225
+ }
226
+ const existing=before===undefined?{}:object2(JSON.parse(before))
227
+ for(const key of Object.keys(existing)){if(PRIVATE_KEYS.has(key))
228
+ throw new Error(`Project settings contain private field ${key}`)
229
+ }
230
+ parseConfiguredDefaults(existing)
251
231
  const layouts=object2(existing.layoutPresets??{})
252
232
  const mcpPresets=object2(existing.mcpPresets??{})
253
233
  const profiles=object2(existing.workspaceProfiles??{})
@@ -257,17 +237,33 @@ throw new Error(`Project profile already exists: ${collision}`)
257
237
  if(!collision&&Object.keys(layouts).length>=50)
258
238
  throw new Error("Project profiles are full")
259
239
  const hasMcp=Boolean(parsed.settings.mcp&&servers.length>0)
260
- if(hasMcp&&!replace&&mcpPresets[name]){throw new Error(`Project MCP preset already exists: ${name}`)
261
- }
262
- if(hasMcp&&mcpPresets[name]&&Object.entries(profiles).some(([layoutName,mcpName])=>layoutName!==name&&mcpName===name)){throw new Error(`MCP preset ${name} is linked to another project profile`)
263
- }
240
+ if(hasMcp&&!replace&&mcpPresets[name])
241
+ throw new Error(`Project MCP preset already exists: ${name}`)
242
+ if(hasMcp&&mcpPresets[name]&&Object.entries(profiles).some(([layoutName,mcpName])=>layoutName!==name&&mcpName===name))
243
+ throw new Error(`MCP preset ${name} is linked to another project profile`)
264
244
  const next={...existing,layoutPresets:{...layouts,[name]:layout},...hasMcp&&{mcpPresets:{...mcpPresets,[name]:parsed.settings.mcp},workspaceProfiles:{...profiles,[name]:name}}}
265
- if(!hasMcp&&profiles[name]&&replace){next.workspaceProfiles=Object.fromEntries(Object.entries(profiles).filter(([key])=>key!==name))
266
- }
245
+ if(!hasMcp&&profiles[name]&&replace)
246
+ next.workspaceProfiles=Object.fromEntries(Object.entries(profiles).filter(([key])=>key!==name))
267
247
  parseConfiguredDefaults(next)
268
- return{root:directory,file,name,before,after:`${JSON.stringify(next, undefined, 2)}
248
+ return{name,before,after:`${JSON.stringify(next, undefined, 2)}
269
249
  `,layout,mcp:parsed.settings.mcp,skipped:parsed.unsupported
270
250
  }}
251
+ var FILENAME=join(".opencode","navigator.json")
252
+ async function readProject(file){const info=await lstat(file).catch((error)=>{if(error.code==="ENOENT")
253
+ return
254
+ throw error
255
+ })
256
+ if(info?.isSymbolicLink()||info&&!info.isFile())
257
+ throw new Error("Project settings must be a regular file")
258
+ try{return await readFile(file,"utf8")
259
+ } catch(error){if(error.code==="ENOENT")
260
+ return
261
+ throw error
262
+ }}
263
+ async function previewProjectProfile(root,nameInput,source,replace=false){const directory=await realpath(resolve(root))
264
+ const file=join(directory,FILENAME)
265
+ const before=await readProject(file)
266
+ return{...buildProjectProfile(nameInput,source,before,replace),root:directory,file}}
271
267
  async function saveProjectProfile(preview,root){if(await realpath(resolve(root))!==preview.root)
272
268
  throw new Error("Workspace changed; preview again")
273
269
  const configDirectory=join(preview.root,".opencode")