opencode-navigator 0.25.0 → 0.27.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.
- package/CHANGELOG.md +14 -0
- package/HOST_API.md +125 -16
- package/LINT_COVERAGE.md +32 -29
- package/PROVIDERS.md +9 -0
- package/README.md +97 -12
- package/dist/{chunk-bv0nm1tj.js → chunk-dm1y8x49.js} +2 -1
- package/dist/gui-experimental.cjs +15526 -0
- package/dist/layout-preset-actions.js +37 -14
- package/dist/native-todo-setup.js +2 -1
- package/dist/navigator-updates.js +2 -2
- package/dist/onboarding.js +11 -4
- package/dist/project-profile.js +34 -38
- package/dist/provider-limits.js +213 -154
- package/dist/quick-actions-settings.js +1 -1
- package/dist/runtime-dependencies.json +4 -4
- package/dist/server.js +379 -0
- package/dist/sidebar-settings.js +42 -37
- package/dist/sidebar-shortcuts.js +1 -1
- package/dist/toolbar-settings.js +68 -20
- package/dist/tui.js +792 -661
- package/dist/upgrade-notes.js +1 -1
- package/package.json +30 -17
- /package/dist/{chunk-pwa1wzer.js → chunk-rrvwmnyh.js} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {SECTION_DEFINITIONS,PLUGIN_ID
|
|
2
|
-
} from "./chunk-
|
|
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&¤t.scope===target.scope
|
|
34
|
+
return isCurrent()&¤t.key===target.key&¤t.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(!
|
|
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
|
|
320
|
-
const
|
|
321
|
-
const mcpModel=
|
|
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
|
-
},
|
|
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=
|
|
344
|
-
if(
|
|
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-
|
|
2
|
+
} from "./chunk-dm1y8x49.js"
|
|
3
3
|
import {parseChangelog2,readBundledChangelog2
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-rrvwmnyh.js"
|
|
5
5
|
import {isNewerVersion,isSameMajorVersion
|
|
6
6
|
} from "./chunk-fd7394y1.js";
|
|
7
7
|
import {createSignal} from "solid-js"
|
package/dist/onboarding.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {SECTION_DEFINITIONS,PLUGIN_ID
|
|
2
|
-
} from "./chunk-
|
|
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
|
|
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"
|
|
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"
|
package/dist/project-profile.js
CHANGED
|
@@ -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
|
-
|
|
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("~"))
|
|
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("~"))
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const before
|
|
250
|
-
const existing
|
|
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])
|
|
261
|
-
}
|
|
262
|
-
if(hasMcp&&mcpPresets[name]&&Object.entries(profiles).some(([layoutName,mcpName])=>layoutName!==name&&mcpName===name))
|
|
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)
|
|
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{
|
|
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")
|