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
package/dist/server.js
CHANGED
|
@@ -619,10 +619,389 @@ await response.dispose()
|
|
|
619
619
|
await rpc.dispose()
|
|
620
620
|
await service.dispose()
|
|
621
621
|
}}
|
|
622
|
+
import {randomUUID as randomUUID2} from "crypto"
|
|
623
|
+
import {randomUUID} from "crypto"
|
|
624
|
+
import {lstat,mkdir,readFile,realpath,rename,unlink,writeFile} from "fs/promises"
|
|
625
|
+
import {join as join2,resolve} from "path"
|
|
626
|
+
var SIDEBAR_SECTIONS=["limits","todo","subagents","skills","quick_actions","lsp","mcp"]
|
|
627
|
+
var QUICK_ACTION_IDS=["session.rename","session.timeline","session.copy","session.export","session.compact","session.list","session.new","session.fork","messages.copy","session.first","session.last","session.messages_last_user","session.message.next","session.message.previous","permission.mode"]
|
|
628
|
+
function parseQuickActionOrder(value){if(!Array.isArray(value))
|
|
629
|
+
return
|
|
630
|
+
const order=[...new Set(value.filter((id)=>QUICK_ACTION_IDS.includes(id)))]
|
|
631
|
+
return [...order,...QUICK_ACTION_IDS.filter((id)=>!order.includes(id))]
|
|
632
|
+
}
|
|
633
|
+
function parseQuickActionVisibility(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
634
|
+
return{}
|
|
635
|
+
const input=value
|
|
636
|
+
return Object.fromEntries(QUICK_ACTION_IDS.flatMap((id)=>typeof input[id]==="boolean"?[[id,input[id]]]:[]))
|
|
637
|
+
}
|
|
638
|
+
function parseSectionOrder(value){if(!Array.isArray(value))
|
|
639
|
+
return
|
|
640
|
+
const seen=new Set
|
|
641
|
+
const order=[]
|
|
642
|
+
for(const candidate of value){if(!SIDEBAR_SECTIONS.includes(candidate)||seen.has(candidate))
|
|
643
|
+
continue
|
|
644
|
+
seen.add(candidate)
|
|
645
|
+
order.push(candidate)
|
|
646
|
+
}
|
|
647
|
+
for(const section of SIDEBAR_SECTIONS){if(!seen.has(section)){if(section==="limits")
|
|
648
|
+
order.unshift(section)
|
|
649
|
+
else
|
|
650
|
+
order.push(section)
|
|
651
|
+
}}
|
|
652
|
+
return order
|
|
653
|
+
}
|
|
654
|
+
function parseSectionPreferences(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
655
|
+
return{}
|
|
656
|
+
const input=value
|
|
657
|
+
return Object.fromEntries(SIDEBAR_SECTIONS.flatMap((name)=>typeof input[name]==="boolean"?[[name,input[name]]]:[]))
|
|
658
|
+
}
|
|
659
|
+
function parseSectionLayout(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
660
|
+
return
|
|
661
|
+
const input=value
|
|
662
|
+
const sections=parseSectionPreferences(input.sections)
|
|
663
|
+
const expanded=parseSectionPreferences(input.expanded)
|
|
664
|
+
const order=parseSectionOrder(input.order)
|
|
665
|
+
if(Object.keys(sections).length===0&&Object.keys(expanded).length===0&&!order)
|
|
666
|
+
return
|
|
667
|
+
return{sections,expanded,...order&&{order}}}
|
|
668
|
+
function parseSectionItemLimits(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
669
|
+
return{}
|
|
670
|
+
const input=value
|
|
671
|
+
return Object.fromEntries(SIDEBAR_SECTIONS.flatMap((section)=>{const limit=input[section]
|
|
672
|
+
return typeof limit==="number"&&Number.isSafeInteger(limit)&&limit>=0?[[section,limit]]:[]
|
|
673
|
+
}))
|
|
674
|
+
}
|
|
675
|
+
function resolveSectionVisibility(defaults,value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
676
|
+
return defaults
|
|
677
|
+
const input=value
|
|
678
|
+
return Object.fromEntries(Object.entries(defaults).map(([name,visible])=>[name,typeof input[name]==="boolean"?input[name]:visible]))
|
|
679
|
+
}
|
|
680
|
+
function parseSectionVisibility(value){return resolveSectionVisibility({limits:true,todo:true,subagents:true,skills:true,quick_actions:true,lsp:true,mcp:true
|
|
681
|
+
},value)
|
|
682
|
+
}
|
|
683
|
+
var DEFAULT_LIMITS_REFRESH_MINUTES=5
|
|
684
|
+
var MAX_LIMITS_REFRESH_MINUTES=Math.floor(2147483647 / 60000)
|
|
685
|
+
function parseLimitsRefreshMinutes(value){return typeof value==="number"&&Number.isSafeInteger(value)&&value>=1&&value<=MAX_LIMITS_REFRESH_MINUTES?value:undefined
|
|
686
|
+
}
|
|
687
|
+
var PLUGIN_ID="opencode-navigator"
|
|
688
|
+
var TOGGLE_COMMAND=`${PLUGIN_ID}.toggle`
|
|
689
|
+
var FOCUS_COMMAND=`${PLUGIN_ID}.focus`
|
|
690
|
+
var SEARCH_COMMAND=`${PLUGIN_ID}.search`
|
|
691
|
+
var DEFAULT_SEARCH_KEY="ctrl+shift+k";
|
|
692
|
+
function pluginConfig(options){const iconStyle=options?.icon_style==="nerd"||options?.icon_style==="text"?options.icon_style:options?.lsp_icon_style
|
|
693
|
+
return{toggleKey:typeof options?.toggle_key==="string"&&options.toggle_key.trim()?options.toggle_key.trim():"ctrl+shift+b",focusKey:typeof options?.focus_key==="string"&&options.focus_key.trim()?options.focus_key.trim():"ctrl+shift+f",searchKey:typeof options?.search_key==="string"&&options.search_key.trim()?options.search_key.trim():DEFAULT_SEARCH_KEY,persistMcp:options?.persist_mcp!==false,limitsRefreshMinutes:parseLimitsRefreshMinutes(options?.limits_refresh_minutes)??DEFAULT_LIMITS_REFRESH_MINUTES,startInChat:options?.start_in_chat===true,showSessionTitle:options?.show_session_title!==false,showSessionDate:options?.show_session_date!==false,cornerFont:options?.corner_font!==false,sections:parseSectionVisibility(options?.sections),sectionOrder:parseSectionOrder(options?.section_order)??[...SIDEBAR_SECTIONS],lspIconStyle:iconStyle==="text"?"text":"nerd",rowDensity:options?.row_density==="comfortable"?"comfortable":"compact",toolbarOverflow:options?.toolbar_overflow==="wrap"?"wrap":"menu",sectionItemLimits:parseSectionItemLimits(options?.section_item_limits),quickActionOrder:parseQuickActionOrder(options?.quick_action_order)??[...QUICK_ACTION_IDS],quickActionVisibility:parseQuickActionVisibility(options?.quick_action_visibility)
|
|
694
|
+
}}
|
|
695
|
+
function record(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
696
|
+
return
|
|
697
|
+
return value
|
|
698
|
+
}
|
|
699
|
+
function parsePluginSettings(value){const input=record(value)
|
|
700
|
+
if(!input)
|
|
701
|
+
return{}
|
|
702
|
+
return{...typeof input.toggleKey==="string"&&input.toggleKey.trim()&&{toggleKey:input.toggleKey.trim()},...typeof input.focusKey==="string"&&input.focusKey.trim()&&{focusKey:input.focusKey.trim()},...typeof input.searchKey==="string"&&input.searchKey.trim()&&{searchKey:input.searchKey.trim()},...typeof input.persistMcp==="boolean"&&{persistMcp:input.persistMcp},...parseLimitsRefreshMinutes(input.limitsRefreshMinutes)!==undefined&&{limitsRefreshMinutes:parseLimitsRefreshMinutes(input.limitsRefreshMinutes)
|
|
703
|
+
},...typeof input.startInChat==="boolean"&&{startInChat:input.startInChat},...(input.toolbarOverflow==="menu"||input.toolbarOverflow==="wrap")&&{toolbarOverflow:input.toolbarOverflow
|
|
704
|
+
},...typeof input.showSessionTitle==="boolean"&&{showSessionTitle:input.showSessionTitle},...typeof input.showSessionDate==="boolean"&&{showSessionDate:input.showSessionDate},...typeof input.cornerFont==="boolean"&&{cornerFont:input.cornerFont},...(input.lspIconStyle==="nerd"||input.lspIconStyle==="text")&&{lspIconStyle:input.lspIconStyle},...(input.rowDensity==="compact"||input.rowDensity==="comfortable")&&{rowDensity:input.rowDensity
|
|
705
|
+
},...Object.keys(parseSectionItemLimits(input.sectionItemLimits)).length>0&&{sectionItemLimits:parseSectionItemLimits(input.sectionItemLimits)
|
|
706
|
+
},...parseQuickActionOrder(input.quickActionOrder)&&{quickActionOrder:parseQuickActionOrder(input.quickActionOrder)
|
|
707
|
+
},...Object.keys(parseQuickActionVisibility(input.quickActionVisibility)).length>0&&{quickActionVisibility:parseQuickActionVisibility(input.quickActionVisibility)
|
|
708
|
+
}}}
|
|
709
|
+
function parseLayoutPresets(value){const input=record(value)
|
|
710
|
+
if(!input)
|
|
711
|
+
return{}
|
|
712
|
+
const presets={}
|
|
713
|
+
const entries=Object.entries(input).sort(([left],[right])=>left.localeCompare(right))
|
|
714
|
+
for(const [candidate,preset] of entries){const name=candidate.trim().slice(0,64)
|
|
715
|
+
const layout=parseSectionLayout(preset)
|
|
716
|
+
if(name&&layout&&Object.keys(presets).length<50)
|
|
717
|
+
presets[name]=layout
|
|
718
|
+
}
|
|
719
|
+
return presets
|
|
720
|
+
}
|
|
721
|
+
function parseDesiredMcpStates(value){const input=record(value)
|
|
722
|
+
if(!input)
|
|
723
|
+
return{}
|
|
724
|
+
return Object.fromEntries(Object.entries(input).filter((entry)=>Boolean(entry[0])&&(entry[1]==="enabled"||entry[1]==="disabled")).sort(([left],[right])=>left.localeCompare(right)))
|
|
725
|
+
}
|
|
726
|
+
function parseMcpPresets(value){const input=record(value)
|
|
727
|
+
if(!input)
|
|
728
|
+
return{}
|
|
729
|
+
const presets={}
|
|
730
|
+
const names=new Set
|
|
731
|
+
const entries=Object.entries(input).sort(([left],[right])=>left.localeCompare(right))
|
|
732
|
+
for(const [candidate,preset] of entries){const name=candidate.trim().slice(0,64)
|
|
733
|
+
const states=parseDesiredMcpStates(preset)
|
|
734
|
+
const key=name.toLocaleLowerCase()
|
|
735
|
+
if(name&&!names.has(key)&&Object.keys(states).length>0&&Object.keys(presets).length<50){names.add(key)
|
|
736
|
+
presets[name]=states
|
|
737
|
+
}}
|
|
738
|
+
return presets
|
|
739
|
+
}
|
|
740
|
+
function parseMcpServerGroups(value){const input=record(value)
|
|
741
|
+
if(!input)
|
|
742
|
+
return{}
|
|
743
|
+
return Object.fromEntries(Object.entries(input).flatMap(([server,rawGroup])=>{const group=typeof rawGroup==="string"?rawGroup.trim().slice(0,64):""
|
|
744
|
+
return server.trim()&&group?[[server,group]]:[]
|
|
745
|
+
}))
|
|
746
|
+
}
|
|
747
|
+
function parseWorkspaceProfiles(value){return Object.fromEntries(Object.entries(record(value)??{}).flatMap(([layout,rawMcp])=>{const layoutName=layout.trim().slice(0,64)
|
|
748
|
+
const mcpName=typeof rawMcp==="string"?rawMcp.trim().slice(0,64):""
|
|
749
|
+
return layoutName&&mcpName?[[layoutName,mcpName]]:[]
|
|
750
|
+
}).slice(0,50))
|
|
751
|
+
}
|
|
752
|
+
function record2(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
753
|
+
return
|
|
754
|
+
return value
|
|
755
|
+
}
|
|
756
|
+
function present(value){return value&&Object.keys(value).length>0
|
|
757
|
+
}
|
|
758
|
+
function parseConfiguredDefaults(value){const input=record2(value)
|
|
759
|
+
if(!input)
|
|
760
|
+
throw new Error("Expected a JSON object")
|
|
761
|
+
const behavior=parsePluginSettings(input.behavior)
|
|
762
|
+
const layout=parseSectionLayout(input.layout)
|
|
763
|
+
const desiredMcpStates=parseDesiredMcpStates(input.mcp??input.desiredMcpStates)
|
|
764
|
+
const layoutPresets=parseLayoutPresets(input.layoutPresets)
|
|
765
|
+
const mcpPresets=parseMcpPresets(input.mcpPresets)
|
|
766
|
+
const workspaceProfiles=parseWorkspaceProfiles(input.workspaceProfiles)
|
|
767
|
+
const mcpServerGroups=parseMcpServerGroups(input.mcpServerGroups)
|
|
768
|
+
return{...present(behavior)&&{behavior},...layout&&{layout},...present(desiredMcpStates)&&{desiredMcpStates},...present(layoutPresets)&&{layoutPresets},...present(mcpPresets)&&{mcpPresets},...present(workspaceProfiles)&&{workspaceProfiles},...present(mcpServerGroups)&&{mcpServerGroups}}}
|
|
769
|
+
function mergeRecords(left,right){const result={...left}
|
|
770
|
+
for(const [key,value] of Object.entries(right)){const previous=record2(result[key])
|
|
771
|
+
const next=record2(value)
|
|
772
|
+
result[key]=previous&&next?mergeRecords(previous,next):value
|
|
773
|
+
}
|
|
774
|
+
return result
|
|
775
|
+
}
|
|
776
|
+
function mergeConfiguredDefaults(...layers){let merged={}
|
|
777
|
+
for(const layer of layers){if(layer)
|
|
778
|
+
merged=mergeRecords(merged,layer)
|
|
779
|
+
}
|
|
780
|
+
return parseConfiguredDefaults(merged)
|
|
781
|
+
}
|
|
782
|
+
function legacyDefaults(config){const{sections,sectionOrder,...behavior}=config
|
|
783
|
+
return{behavior,layout:{sections,expanded:{},...sectionOrder&&{order:sectionOrder}}}}
|
|
784
|
+
function configuredDefaultsFromPluginOptions(options){return mergeConfiguredDefaults(legacyDefaults(pluginConfig(options)),parseConfiguredDefaults(options??{}))
|
|
785
|
+
}
|
|
786
|
+
var PORTABLE_SETTINGS_FORMAT="opencode-navigator/settings"
|
|
787
|
+
function object2(value,path){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
788
|
+
throw new Error(`${path} must be an object`)
|
|
789
|
+
return value
|
|
790
|
+
}
|
|
791
|
+
function pointer(path,key){return `${path}/${key.replaceAll("~", "~0").replaceAll("/", "~1")}`
|
|
792
|
+
}
|
|
793
|
+
function unknownKeys(input,allowed,path,output){for(const key of Object.keys(input))
|
|
794
|
+
if(!allowed.includes(key))
|
|
795
|
+
output.push(pointer(path,key))
|
|
796
|
+
}
|
|
797
|
+
function parseVisibility(value,path,unsupported){const input=object2(value,path)
|
|
798
|
+
return Object.fromEntries(Object.entries(input).flatMap(([name,visible])=>{if(!SIDEBAR_SECTIONS.includes(name)){unsupported.push(pointer(path,name))
|
|
799
|
+
return []
|
|
800
|
+
}
|
|
801
|
+
if(typeof visible!=="boolean")
|
|
802
|
+
throw new Error(`${pointer(path, name)} must be a boolean`)
|
|
803
|
+
return [[name,visible]]
|
|
804
|
+
}))
|
|
805
|
+
}
|
|
806
|
+
function parseLayout(value,unsupported){const input=object2(value,"/layout")
|
|
807
|
+
unknownKeys(input,["sections","expanded","order"],"/layout",unsupported)
|
|
808
|
+
const sections=input.sections===undefined?{}:parseVisibility(input.sections,"/layout/sections",unsupported)
|
|
809
|
+
const expanded=input.expanded===undefined?{}:parseVisibility(input.expanded,"/layout/expanded",unsupported)
|
|
810
|
+
let order
|
|
811
|
+
if(input.order!==undefined){if(!Array.isArray(input.order))
|
|
812
|
+
throw new Error("/layout/order must be an array")
|
|
813
|
+
const supported=[]
|
|
814
|
+
const seen=new Set
|
|
815
|
+
for(const [index,name] of input.order.entries()){if(typeof name!=="string")
|
|
816
|
+
throw new Error(`/layout/order/${index} must be a section name`)
|
|
817
|
+
if(!SIDEBAR_SECTIONS.includes(name))
|
|
818
|
+
unsupported.push(`/layout/order/${index}`)
|
|
819
|
+
else if(seen.has(name))
|
|
820
|
+
throw new Error(`/layout/order/${index} duplicates ${name}`)
|
|
821
|
+
else{seen.add(name)
|
|
822
|
+
supported.push(name)
|
|
823
|
+
}}
|
|
824
|
+
order=parseSectionOrder(supported)
|
|
825
|
+
}
|
|
826
|
+
return{sections,expanded,...order&&{order}}}
|
|
827
|
+
function parseMcp(value){const input=object2(value,"/mcp")
|
|
828
|
+
return Object.fromEntries(Object.entries(input).map(([name,state])=>{if(!name.trim())
|
|
829
|
+
throw new Error("/mcp contains an empty server name")
|
|
830
|
+
if(state!=="enabled"&&state!=="disabled"){throw new Error(`${pointer("/mcp", name)} must be enabled or disabled`)
|
|
831
|
+
}
|
|
832
|
+
return [name,state]
|
|
833
|
+
}).sort(([left],[right])=>left.localeCompare(right)))
|
|
834
|
+
}
|
|
835
|
+
function parsePortableSettings(source){let value
|
|
836
|
+
try{value=JSON.parse(source)
|
|
837
|
+
} catch(error){throw new Error("Malformed settings JSON",{cause:error})
|
|
838
|
+
}
|
|
839
|
+
const input=object2(value,"Settings JSON")
|
|
840
|
+
if(input.format!==PORTABLE_SETTINGS_FORMAT)
|
|
841
|
+
throw new Error(`Expected format ${PORTABLE_SETTINGS_FORMAT}`)
|
|
842
|
+
if(input.version!==1)
|
|
843
|
+
throw new Error(`Unsupported settings version ${String(input.version)}`)
|
|
844
|
+
const unsupported=[]
|
|
845
|
+
unknownKeys(input,["format","version","layout","mcp","behavior"],"",unsupported)
|
|
846
|
+
const hasLayout=Object.hasOwn(input,"layout")
|
|
847
|
+
const hasMcp=Object.hasOwn(input,"mcp")
|
|
848
|
+
const behavior={}
|
|
849
|
+
if(input.behavior!==undefined){const raw=object2(input.behavior,"/behavior")
|
|
850
|
+
unknownKeys(raw,["toolbarOverflow"],"/behavior",unsupported)
|
|
851
|
+
if(raw.toolbarOverflow!==undefined){if(raw.toolbarOverflow!=="menu"&&raw.toolbarOverflow!=="wrap")
|
|
852
|
+
throw new Error("/behavior/toolbarOverflow must be menu or wrap")
|
|
853
|
+
behavior.toolbarOverflow=raw.toolbarOverflow
|
|
854
|
+
}}
|
|
855
|
+
if(!hasLayout&&!hasMcp&&!behavior.toolbarOverflow)
|
|
856
|
+
throw new Error("Settings JSON contains no supported settings")
|
|
857
|
+
const settings={format:PORTABLE_SETTINGS_FORMAT,version:1,...hasLayout&&{layout:parseLayout(input.layout,unsupported)},...hasMcp&&{mcp:parseMcp(input.mcp)},...behavior.toolbarOverflow&&{behavior}}
|
|
858
|
+
unsupported.sort()
|
|
859
|
+
return{settings,unsupported}}
|
|
860
|
+
var PRIVATE_KEYS=new Set(["favoriteSkills","favoriteMcpServers","favoriteQuickActions","skillGroups","recentSkills","recentQuickActions","skippedSkillConfirmations","onboardingCompleted","codexAccountBindings","pendingResetAttempt","lastNavigatorVersion","previousNavigatorVersion","worktrees","user"])
|
|
861
|
+
function object3(value){if(!value||typeof value!=="object"||Array.isArray(value))
|
|
862
|
+
throw new Error("Project settings must be an object")
|
|
863
|
+
return value
|
|
864
|
+
}
|
|
865
|
+
function buildProjectProfile(nameInput,source,before,replace=false){const name=nameInput.trim()
|
|
866
|
+
if(!name||name.length>64)
|
|
867
|
+
throw new Error("Profile names must contain 1\u201364 characters")
|
|
868
|
+
if(name.includes("/")||name.includes("\\")||name.startsWith("~"))
|
|
869
|
+
throw new Error("Profile names must not contain private paths")
|
|
870
|
+
const parsed=parsePortableSettings(source)
|
|
871
|
+
const layout=parsed.settings.layout&&parseSectionLayout(parsed.settings.layout)
|
|
872
|
+
if(!layout)
|
|
873
|
+
throw new Error("A project profile needs a layout")
|
|
874
|
+
const servers=Object.keys(parsed.settings.mcp??{})
|
|
875
|
+
for(const server of servers){if(server.includes("/")||server.includes("\\")||server.startsWith("~"))
|
|
876
|
+
throw new Error(`MCP server name looks like a private path: ${server}`)
|
|
877
|
+
}
|
|
878
|
+
const existing=before===undefined?{}:object3(JSON.parse(before))
|
|
879
|
+
for(const key of Object.keys(existing)){if(PRIVATE_KEYS.has(key))
|
|
880
|
+
throw new Error(`Project settings contain private field ${key}`)
|
|
881
|
+
}
|
|
882
|
+
parseConfiguredDefaults(existing)
|
|
883
|
+
const layouts=object3(existing.layoutPresets??{})
|
|
884
|
+
const mcpPresets=object3(existing.mcpPresets??{})
|
|
885
|
+
const profiles=object3(existing.workspaceProfiles??{})
|
|
886
|
+
const collision=Object.keys(layouts).find((candidate)=>candidate.toLocaleLowerCase()===name.toLocaleLowerCase())
|
|
887
|
+
if(collision&&(!replace||collision!==name))
|
|
888
|
+
throw new Error(`Project profile already exists: ${collision}`)
|
|
889
|
+
if(!collision&&Object.keys(layouts).length>=50)
|
|
890
|
+
throw new Error("Project profiles are full")
|
|
891
|
+
const hasMcp=Boolean(parsed.settings.mcp&&servers.length>0)
|
|
892
|
+
if(hasMcp&&!replace&&mcpPresets[name])
|
|
893
|
+
throw new Error(`Project MCP preset already exists: ${name}`)
|
|
894
|
+
if(hasMcp&&mcpPresets[name]&&Object.entries(profiles).some(([layoutName,mcpName])=>layoutName!==name&&mcpName===name))
|
|
895
|
+
throw new Error(`MCP preset ${name} is linked to another project profile`)
|
|
896
|
+
const next={...existing,layoutPresets:{...layouts,[name]:layout},...hasMcp&&{mcpPresets:{...mcpPresets,[name]:parsed.settings.mcp},workspaceProfiles:{...profiles,[name]:name}}}
|
|
897
|
+
if(!hasMcp&&profiles[name]&&replace)
|
|
898
|
+
next.workspaceProfiles=Object.fromEntries(Object.entries(profiles).filter(([key])=>key!==name))
|
|
899
|
+
parseConfiguredDefaults(next)
|
|
900
|
+
return{name,before,after:`${JSON.stringify(next, undefined, 2)}
|
|
901
|
+
`,layout,mcp:parsed.settings.mcp,skipped:parsed.unsupported
|
|
902
|
+
}}
|
|
903
|
+
var FILENAME=join2(".opencode","navigator.json")
|
|
904
|
+
async function readProject(file){const info=await lstat(file).catch((error)=>{if(error.code==="ENOENT")
|
|
905
|
+
return
|
|
906
|
+
throw error
|
|
907
|
+
})
|
|
908
|
+
if(info?.isSymbolicLink()||info&&!info.isFile())
|
|
909
|
+
throw new Error("Project settings must be a regular file")
|
|
910
|
+
try{return await readFile(file,"utf8")
|
|
911
|
+
} catch(error){if(error.code==="ENOENT")
|
|
912
|
+
return
|
|
913
|
+
throw error
|
|
914
|
+
}}
|
|
915
|
+
async function previewProjectProfile(root,nameInput,source,replace=false){const directory=await realpath(resolve(root))
|
|
916
|
+
const file=join2(directory,FILENAME)
|
|
917
|
+
const before=await readProject(file)
|
|
918
|
+
return{...buildProjectProfile(nameInput,source,before,replace),root:directory,file}}
|
|
919
|
+
async function saveProjectProfile(preview,root){if(await realpath(resolve(root))!==preview.root)
|
|
920
|
+
throw new Error("Workspace changed; preview again")
|
|
921
|
+
const configDirectory=join2(preview.root,".opencode")
|
|
922
|
+
await mkdir(configDirectory,{recursive:true})
|
|
923
|
+
const directoryInfo=await lstat(configDirectory)
|
|
924
|
+
if(!directoryInfo.isDirectory()||directoryInfo.isSymbolicLink()){throw new Error("Project configuration directory must not be a symlink")
|
|
925
|
+
}
|
|
926
|
+
if(await readProject(preview.file)!==preview.before)
|
|
927
|
+
throw new Error("Project settings changed; preview again")
|
|
928
|
+
const temporary=`${preview.file}.${randomUUID()}.tmp`
|
|
929
|
+
try{await writeFile(temporary,preview.after,{flag:"wx",mode:384})
|
|
930
|
+
if(await readProject(preview.file)!==preview.before)
|
|
931
|
+
throw new Error("Project settings changed; preview again")
|
|
932
|
+
await rename(temporary,preview.file)
|
|
933
|
+
} finally{await unlink(temporary).catch((error)=>{if(error.code!=="ENOENT")
|
|
934
|
+
throw error
|
|
935
|
+
})
|
|
936
|
+
}
|
|
937
|
+
return preview.file
|
|
938
|
+
}
|
|
939
|
+
var text2={type:"string",minLength:1,maxLength:1e6}
|
|
940
|
+
var NavigatorProfilesRpc={id:"opencode-navigator.profiles",methods:{defaults:{input:{type:"object",properties:{sessionID:text2},additionalProperties:false},output:{type:"string",maxLength:1e6}},preview:{input:{type:"object",properties:{sessionID:text2,name:text2,source:text2,replace:{type:"boolean"}},required:["sessionID","name","source"],additionalProperties:false
|
|
941
|
+
},output:{type:"object",properties:{token:text2,after:text2,file:text2},required:["token","after","file"],additionalProperties:false
|
|
942
|
+
}},save:{input:{type:"object",properties:{sessionID:text2,token:text2},required:["sessionID","token"],additionalProperties:false
|
|
943
|
+
},output:{type:"object",properties:{file:text2},required:["file"],additionalProperties:false}}},events:{}};
|
|
944
|
+
import {readFile as readFile2} from "fs/promises"
|
|
945
|
+
import {homedir} from "os"
|
|
946
|
+
import {join as join3} from "path"
|
|
947
|
+
async function readServerConfiguredDefaults(host,project){const warnings=[]
|
|
948
|
+
async function read(path,label){try{const source=await readFile2(path,"utf8")
|
|
949
|
+
if(source.length>1e6)
|
|
950
|
+
throw new Error("Configuration is too large")
|
|
951
|
+
return parseConfiguredDefaults(JSON.parse(source))
|
|
952
|
+
} catch(error){if(!error||typeof error!=="object"||!("code" in error)||error.code!=="ENOENT")
|
|
953
|
+
warnings.push(`${label} could not be loaded. Check its JSON and permissions, then reload Navigator preferences.`)
|
|
954
|
+
return
|
|
955
|
+
}}
|
|
956
|
+
const configRoot=process.env.XDG_CONFIG_HOME??join3(homedir(),".config")
|
|
957
|
+
const userPath=join3(configRoot,".opencode-navigator/settings.json")
|
|
958
|
+
const [user,local]=await Promise.all([read(userPath,"User Navigator defaults"),project&&!host.location.workspaceID?read(join3(host.location.directory,".opencode/navigator.json"),"Project Navigator defaults"):undefined])
|
|
959
|
+
return{defaults:mergeConfiguredDefaults(configuredDefaultsFromPluginOptions(host.options),user,local),warnings
|
|
960
|
+
}}
|
|
961
|
+
async function setupProjectProfileServer(host){const previews=new Map
|
|
962
|
+
async function verify(sessionID,signal){signal.throwIfAborted()
|
|
963
|
+
if(host.location.workspaceID)
|
|
964
|
+
throw new Error("Project profile writes require a server-owned directory, not a workspace-placed filesystem.")
|
|
965
|
+
const session=await host.session.get({sessionID})
|
|
966
|
+
signal.throwIfAborted()
|
|
967
|
+
if(session.location.directory!==host.location.directory)
|
|
968
|
+
throw new Error("Project profile session location changed. Reopen the preview.")
|
|
969
|
+
}
|
|
970
|
+
const rpc=await host.rpc.register(NavigatorProfilesRpc,{defaults:async (input,context)=>{const value=input
|
|
971
|
+
if(value.sessionID){const session=await host.session.get({sessionID:value.sessionID})
|
|
972
|
+
if(session.location.directory!==host.location.directory)
|
|
973
|
+
throw new Error("Configuration session location changed.")
|
|
974
|
+
}
|
|
975
|
+
context.signal.throwIfAborted()
|
|
976
|
+
const result=await readServerConfiguredDefaults(host,Boolean(value.sessionID))
|
|
977
|
+
context.signal.throwIfAborted()
|
|
978
|
+
return JSON.stringify(result)
|
|
979
|
+
},preview:async (input,context)=>{const value=input
|
|
980
|
+
await verify(value.sessionID,context.signal)
|
|
981
|
+
const preview=await previewProjectProfile(host.location.directory,value.name,value.source,value.replace)
|
|
982
|
+
context.signal.throwIfAborted()
|
|
983
|
+
const token=randomUUID2()
|
|
984
|
+
previews.set(token,{sessionID:value.sessionID,created:Date.now(),value:preview})
|
|
985
|
+
if(previews.size>20)
|
|
986
|
+
previews.delete(previews.keys().next().value)
|
|
987
|
+
return{token,after:preview.after,file:".opencode/navigator.json"}},save:async (input,context)=>{const value=input
|
|
988
|
+
const preview=previews.get(value.token)
|
|
989
|
+
if(!preview||preview.sessionID!==value.sessionID||Date.now()-preview.created>300000)
|
|
990
|
+
throw new Error("Project profile preview expired. Review the file again.")
|
|
991
|
+
await verify(value.sessionID,context.signal)
|
|
992
|
+
previews.delete(value.token)
|
|
993
|
+
await saveProjectProfile(preview.value,host.location.directory)
|
|
994
|
+
context.signal.throwIfAborted()
|
|
995
|
+
return{file:".opencode/navigator.json"}}})
|
|
996
|
+
return async ()=>{previews.clear()
|
|
997
|
+
await rpc.dispose()
|
|
998
|
+
}}
|
|
622
999
|
var server_plugin_default=Plugin.define({id:"opencode-navigator.todo-server",async setup(host){const archive=await host.rpc.register(LegacyTodoArchiveRpc,{list:async (value)=>readLegacyTodoArchive((key)=>host.storage.get(key),value.sessionID)
|
|
623
1000
|
})
|
|
624
1001
|
const quotaCleanup=typeof host.integration?.connection?.active==="function"&&typeof host.provider?.get==="function"?await setupProviderQuotaServer(host):undefined
|
|
1002
|
+
const profileCleanup=typeof host.session?.get==="function"?await setupProjectProfileServer(host):undefined
|
|
625
1003
|
return async ()=>{await quotaCleanup?.()
|
|
1004
|
+
await profileCleanup?.()
|
|
626
1005
|
await archive.dispose()
|
|
627
1006
|
}}})
|
|
628
1007
|
export{server_plugin_default as default
|
package/dist/sidebar-settings.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {SIDEBAR_SECTIONS,SECTION_DEFINITIONS,PLUGIN_ID,DEFAULT_SEARCH_KEY
|
|
2
|
-
} from "./chunk-
|
|
2
|
+
} from "./chunk-dm1y8x49.js"
|
|
3
3
|
import {isNewerVersion
|
|
4
4
|
} from "./chunk-fd7394y1.js"
|
|
5
5
|
import"./chunk-qmntexwm.js";
|
|
@@ -43,14 +43,27 @@ import {createComponent as _$createComponent} from "@opentui/solid"
|
|
|
43
43
|
import {useTerminalDimensions} from "@opentui/solid"
|
|
44
44
|
import {onCleanup} from "solid-js"
|
|
45
45
|
import {DialogAction,DialogHeader,DialogSurface,GuardedConfirmation,useDialogState} from "./tui.js"
|
|
46
|
+
var BUG_REPORT_REPOSITORY="St1ggy/opencode-navigator"
|
|
47
|
+
var BUG_REPORT_BASE=`https://github.com/${BUG_REPORT_REPOSITORY}/issues`
|
|
48
|
+
function bugReportBrowserTarget(draft){const target=new URL(`${BUG_REPORT_BASE}/new`)
|
|
49
|
+
target.searchParams.set("template","bug_report.yml")
|
|
50
|
+
target.searchParams.set("title",draft.title)
|
|
51
|
+
target.searchParams.set("report",draft.body)
|
|
52
|
+
const needsCopy=target.href.length>7000
|
|
53
|
+
if(needsCopy)
|
|
54
|
+
target.searchParams.delete("report")
|
|
55
|
+
const copyTitle=target.href.length>7000
|
|
56
|
+
if(copyTitle)
|
|
57
|
+
target.searchParams.delete("title")
|
|
58
|
+
return{url:target.href,needsCopy,copyText:copyTitle?`${draft.title}
|
|
59
|
+
|
|
60
|
+
${draft.body}`:draft.body}}
|
|
46
61
|
async function runProcessCommand(command,input="",signal,timeoutMs=15000,environment){if(signal?.aborted)
|
|
47
62
|
return{code:-1,stdout:"",timedOut:true}
|
|
48
63
|
let child
|
|
49
64
|
try{child=Bun.spawn([...command],{stdin:"pipe",stdout:"pipe",stderr:"ignore",env:environment?{...process.env,...environment}:undefined
|
|
50
65
|
})
|
|
51
66
|
} catch{return{code:-1,stdout:""}}
|
|
52
|
-
child.stdin.write(input)
|
|
53
|
-
child.stdin.end()
|
|
54
67
|
let timer
|
|
55
68
|
const chunks=[]
|
|
56
69
|
const output=()=>new TextDecoder().decode(Buffer.concat(chunks))
|
|
@@ -77,7 +90,11 @@ chunks.push(part.value)
|
|
|
77
90
|
}
|
|
78
91
|
return{code:await child.exited,stdout:output()}} finally{reader.releaseLock()
|
|
79
92
|
}}
|
|
80
|
-
|
|
93
|
+
const run=async ()=>{await child.stdin.write(input)
|
|
94
|
+
await child.stdin.end()
|
|
95
|
+
return read()
|
|
96
|
+
}
|
|
97
|
+
try{return await Promise.race([run(),cancelled])
|
|
81
98
|
} catch{return{code:-1,stdout:""}} finally{clearTimeout(timer)
|
|
82
99
|
signal?.removeEventListener("abort",cancel)
|
|
83
100
|
}}
|
|
@@ -92,21 +109,6 @@ return false
|
|
|
92
109
|
const result=await runProcessCommand(command,"",signal,4000)
|
|
93
110
|
return result.code===0
|
|
94
111
|
}
|
|
95
|
-
var BUG_REPORT_REPOSITORY="St1ggy/opencode-navigator"
|
|
96
|
-
var BUG_REPORT_BASE=`https://github.com/${BUG_REPORT_REPOSITORY}/issues`
|
|
97
|
-
function bugReportBrowserTarget(draft){const target=new URL(`${BUG_REPORT_BASE}/new`)
|
|
98
|
-
target.searchParams.set("template","bug_report.yml")
|
|
99
|
-
target.searchParams.set("title",draft.title)
|
|
100
|
-
target.searchParams.set("report",draft.body)
|
|
101
|
-
const needsCopy=target.href.length>7000
|
|
102
|
-
if(needsCopy)
|
|
103
|
-
target.searchParams.delete("report")
|
|
104
|
-
const copyTitle=target.href.length>7000
|
|
105
|
-
if(copyTitle)
|
|
106
|
-
target.searchParams.delete("title")
|
|
107
|
-
return{url:target.href,needsCopy,copyText:copyTitle?`${draft.title}
|
|
108
|
-
|
|
109
|
-
${draft.body}`:draft.body}}
|
|
110
112
|
function BugReportConfirmation(props){return _$createComponent(GuardedConfirmation,{get api(){return props.api
|
|
111
113
|
},title:"Send Bug report",get message(){return props.draft.title
|
|
112
114
|
},get current(){return props.current
|
|
@@ -396,24 +398,7 @@ function initialBugReport(environment){return{title:"",body:`## What happened
|
|
|
396
398
|
}}
|
|
397
399
|
function validBugReport(draft){return Boolean(draft.title.trim()&&draft.body.trim())
|
|
398
400
|
}
|
|
399
|
-
function
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
function createGitHubBugReportTransport(open,run=runGitHubCommand){return{open,async probe(signal){const version=await run(["gh","--version"],"",signal)
|
|
403
|
-
if(version.code!==0)
|
|
404
|
-
return{status:"missing",message:"Install GitHub CLI for direct sending, or open the browser."}
|
|
405
|
-
const auth=await run(["gh","auth","status","--active","--hostname","github.com"],"",signal)
|
|
406
|
-
if(auth.code!==0)
|
|
407
|
-
return{status:"unauthenticated",message:"Sign in with gh auth login, or open the browser."}
|
|
408
|
-
const help=await run(["gh","issue","create","--help"],"",signal)
|
|
409
|
-
return help.code===0&&help.stdout.includes("--type")?{status:"ready",message:"Ready to send through your authenticated GitHub CLI."}:{status:"unsupported",message:"Update gh to support the Bug issue type, or use the browser."}},async send(draft,signal){const result=await run(["gh","issue","create","--repo",`github.com/${BUG_REPORT_REPOSITORY}`,"--type","Bug",`--title=${draft.title.trim()}`,"--body-file","-"],draft.body,signal)
|
|
410
|
-
const url=/^https:\/\/github\.com\/St1ggy\/opencode-navigator\/issues\/\d+\s*$/m.exec(result.stdout)?.[0].trim()
|
|
411
|
-
if(url)
|
|
412
|
-
return result.code===0?{status:"created",message:"Bug report created.",url}:{status:"partial",message:"Issue created; open it to review the Bug type.",url}
|
|
413
|
-
if(result.timedOut||![-1,4,127].includes(result.code))
|
|
414
|
-
return{status:"uncertain",message:"Outcome unknown. Check GitHub before sending again."}
|
|
415
|
-
return{status:"failed",message:"Could not send. Check GitHub access, then retry."}}}}
|
|
416
|
-
function createBugReportController(environment,transport=createGitHubBugReportTransport(openExternalUrl),signal){const [draft,setDraft]=createSignal2(initialBugReport(environment))
|
|
401
|
+
function createBugReportModel(environment,transport,signal){const [draft,setDraft]=createSignal2(initialBugReport(environment))
|
|
417
402
|
const [readiness,setReadiness]=createSignal2({status:"checking",message:"Checking GitHub CLI\u2026"
|
|
418
403
|
})
|
|
419
404
|
const [busy,setBusy]=createSignal2(false)
|
|
@@ -463,6 +448,26 @@ return opened
|
|
|
463
448
|
} catch{setMessage(`Browser unavailable. Copy this URL: ${url}`)
|
|
464
449
|
return false
|
|
465
450
|
}}}}
|
|
451
|
+
function createGitHubBugReportTransport(open,run){return{open,async probe(signal){const version=await run(["gh","--version"],"",signal)
|
|
452
|
+
if(version.code!==0)
|
|
453
|
+
return{status:"missing",message:"Install GitHub CLI for direct sending, or open the browser."}
|
|
454
|
+
const auth=await run(["gh","auth","status","--active","--hostname","github.com"],"",signal)
|
|
455
|
+
if(auth.code!==0)
|
|
456
|
+
return{status:"unauthenticated",message:"Sign in with gh auth login, or open the browser."}
|
|
457
|
+
const help=await run(["gh","issue","create","--help"],"",signal)
|
|
458
|
+
return help.code===0&&help.stdout.includes("--type")?{status:"ready",message:"Ready to send through your authenticated GitHub CLI."}:{status:"unsupported",message:"Update gh to support the Bug issue type, or use the browser."}},async send(draft,signal){const result=await run(["gh","issue","create","--repo",`github.com/${BUG_REPORT_REPOSITORY}`,"--type","Bug",`--title=${draft.title.trim()}`,"--body-file","-"],draft.body,signal)
|
|
459
|
+
const url=/^https:\/\/github\.com\/St1ggy\/opencode-navigator\/issues\/\d+\s*$/m.exec(result.stdout)?.[0].trim()
|
|
460
|
+
if(url)
|
|
461
|
+
return result.code===0?{status:"created",message:"Bug report created.",url}:{status:"partial",message:"Issue created; open it to review the Bug type.",url}
|
|
462
|
+
if(result.timedOut||![-1,4,127].includes(result.code))
|
|
463
|
+
return{status:"uncertain",message:"Outcome unknown. Check GitHub before sending again."}
|
|
464
|
+
return{status:"failed",message:"Could not send. Check GitHub access, then retry."}}}}
|
|
465
|
+
var runGitHubCommand=(command,input,signal)=>runProcessCommand(command,input,signal,15000,{GH_PROMPT_DISABLED:"1",GH_HOST:"github.com",GH_PAGER:"cat"
|
|
466
|
+
})
|
|
467
|
+
function createGitHubBugReportTransport2(open,run=runGitHubCommand){return createGitHubBugReportTransport(open,run)
|
|
468
|
+
}
|
|
469
|
+
function createBugReportController(environment,transport=createGitHubBugReportTransport2(openExternalUrl),signal){return createBugReportModel(environment,transport,signal)
|
|
470
|
+
}
|
|
466
471
|
import {createMemo as createMemo2} from "solid-js"
|
|
467
472
|
import {supportsSidebarSection} from "./tui.js"
|
|
468
473
|
import {getNavigatorAboutServices} from "./tui.js"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {PLUGIN_ID
|
|
2
|
-
} from "./chunk-
|
|
2
|
+
} from "./chunk-dm1y8x49.js";
|
|
3
3
|
import {createSignal} from "solid-js"
|
|
4
4
|
import {supportsSidebarSection} from "./tui.js"
|
|
5
5
|
var ACTIONS=[{key:"h",label:"Toggle sidebar",icon:"sections",group:"navigate",command:"session.sidebar.toggle",section:undefined
|
package/dist/toolbar-settings.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {QUICK_ACTION_IDS
|
|
2
|
+
} from "./chunk-dm1y8x49.js"
|
|
2
3
|
import"./chunk-v6bz59xn.js";
|
|
3
4
|
import {createComponent as _$createComponent7} from "@opentui/solid"
|
|
4
5
|
import {mergeProps as _$mergeProps2} from "@opentui/solid"
|
|
5
6
|
import {memo as _$memo5} from "@opentui/solid"
|
|
6
|
-
import {createSignal} from "solid-js"
|
|
7
|
+
import {batch,createSignal} from "solid-js"
|
|
7
8
|
function toolbarPinKey(pin){return pin.kind==="action"?`action:${pin.id}`:`skill:${pin.location}`
|
|
8
9
|
}
|
|
10
|
+
function identifier(value){return typeof value==="string"&&/^[a-z][a-z\d-]{0,47}$/u.test(value)
|
|
11
|
+
}
|
|
12
|
+
function iconList(value){return Array.isArray(value)&&value.length<=3&&value.every((item)=>identifier(item))
|
|
13
|
+
}
|
|
9
14
|
function validToolbarPinText(text){return text.length<=128&&[...text].every((character)=>{const point=character.codePointAt(0)??0
|
|
10
15
|
return point>=32&&point!==127&&point!==8232&&point!==8233
|
|
11
16
|
})
|
|
@@ -16,6 +21,38 @@ if(state&&pin.stateIcons?.[state])
|
|
|
16
21
|
return [pin.stateIcons[state]]
|
|
17
22
|
return pin.icons??[pin.icon]
|
|
18
23
|
}
|
|
24
|
+
function parsePin(value){if(!value||typeof value!=="object")
|
|
25
|
+
return
|
|
26
|
+
const input=value
|
|
27
|
+
if(!identifier(input.icon))
|
|
28
|
+
return
|
|
29
|
+
if(input.icons!==undefined&&!iconList(input.icons))
|
|
30
|
+
return
|
|
31
|
+
const text=typeof input.text==="string"?input.text.trim():""
|
|
32
|
+
if(!validToolbarPinText(text))
|
|
33
|
+
return
|
|
34
|
+
const stateIcons=Object.fromEntries(Object.entries(input.stateIcons&&typeof input.stateIcons==="object"?input.stateIcons:{}).filter(([key,icon])=>identifier(key)&&identifier(icon)))
|
|
35
|
+
const stateIconLists=Object.fromEntries(Object.entries(input.stateIconLists&&typeof input.stateIconLists==="object"?input.stateIconLists:{}).filter(([key,entry])=>identifier(key)&&iconList(entry)))
|
|
36
|
+
const base=input.icons
|
|
37
|
+
if(!text&&(base?.length===0||Object.values(stateIconLists).some((list)=>list.length===0)))
|
|
38
|
+
return
|
|
39
|
+
const icons={icon:input.icon,...base&&{icons:[...base]},...text&&{text},...Object.keys(stateIcons).length>0&&{stateIcons},...Object.keys(stateIconLists).length>0&&{stateIconLists}}
|
|
40
|
+
if(input.kind==="action"&&QUICK_ACTION_IDS.includes(input.id))
|
|
41
|
+
return{kind:"action",id:input.id,...icons}
|
|
42
|
+
if(input.kind==="skill"&&typeof input.location==="string"&&input.location.trim()&&typeof input.name==="string"&&input.name.trim())
|
|
43
|
+
return{kind:"skill",location:input.location,name:input.name.trim().slice(0,128),...icons}
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
function parseToolbarPins(value){if(!Array.isArray(value))
|
|
47
|
+
return []
|
|
48
|
+
const keys=new Set
|
|
49
|
+
return value.flatMap((candidate)=>{const pin=parsePin(candidate)
|
|
50
|
+
if(!pin||keys.has(toolbarPinKey(pin))||keys.size>=100)
|
|
51
|
+
return []
|
|
52
|
+
keys.add(toolbarPinKey(pin))
|
|
53
|
+
return [pin]
|
|
54
|
+
})
|
|
55
|
+
}
|
|
19
56
|
import {createDialogStack} from "./tui.js"
|
|
20
57
|
import {createComponent as _$createComponent2} from "@opentui/solid"
|
|
21
58
|
import {createEffect,onCleanup,untrack} from "solid-js"
|
|
@@ -111,13 +148,33 @@ import {insertNode as _$insertNode} from "@opentui/solid"
|
|
|
111
148
|
import {createElement as _$createElement} from "@opentui/solid"
|
|
112
149
|
import {createComponent as _$createComponent3} from "@opentui/solid"
|
|
113
150
|
import {memo as _$memo2} from "@opentui/solid"
|
|
151
|
+
function toolbarPinAppearance(pin){return{base:[...toolbarPinIcons(pin)],text:pin.text??"",states:Object.fromEntries(Object.keys({...pin.stateIcons,...pin.stateIconLists}).map((state)=>[state,[...toolbarPinIcons(pin,state)]]))
|
|
152
|
+
}}
|
|
153
|
+
function toolbarPinAppearanceChange(source,draft,expected){const text=draft.text.trim()
|
|
154
|
+
if(!validToolbarPinText(draft.text))
|
|
155
|
+
throw new Error("Use a single line of up to 128 characters")
|
|
156
|
+
if(!iconList(draft.base)||Object.entries(draft.states).some(([key,values])=>!identifier(key)||!iconList(values)))
|
|
157
|
+
throw new Error("Choose zero to three valid icons for each appearance")
|
|
158
|
+
if(!text&&(draft.base.length===0||Object.values(draft.states).some((value)=>value.length===0)))
|
|
159
|
+
throw new Error("Add an icon or text for every configured appearance")
|
|
160
|
+
const stateIcons=Object.fromEntries(Object.entries(draft.states).filter(([,value])=>value.length===1).map(([key,value])=>[key,value[0]]))
|
|
161
|
+
const stateIconLists=Object.fromEntries(Object.entries(draft.states).filter(([,value])=>value.length!==1).map(([key,value])=>[key,[...value]]))
|
|
162
|
+
const pin={...source,icon:draft.base[0]??source.icon,icons:draft.base.length===1?undefined:[...draft.base],text:text||undefined,stateIcons,stateIconLists
|
|
163
|
+
}
|
|
164
|
+
if(parseToolbarPins([pin]).length!==1)
|
|
165
|
+
throw new Error("Choose up to three icons and a single-line label")
|
|
166
|
+
if(!expected)
|
|
167
|
+
return{operation:"pin",pin}
|
|
168
|
+
const keys=Object.keys({...toolbarPinAppearance(source).states,...draft.states})
|
|
169
|
+
return{operation:"configure",key:toolbarPinKey(pin),icon:pin.icon,icons:pin.icons??null,text:pin.text??null,expected,stateIcons:Object.fromEntries(keys.map((key)=>[key,stateIcons[key]??null])),stateIconLists:Object.fromEntries(keys.map((key)=>[key,stateIconLists[key]??null]))
|
|
170
|
+
}}
|
|
171
|
+
var PRIVATE_KEYS=new Set(["favoriteSkills","favoriteMcpServers","favoriteQuickActions","skillGroups","recentSkills","recentQuickActions","skippedSkillConfirmations","onboardingCompleted","codexAccountBindings","pendingResetAttempt","lastNavigatorVersion","previousNavigatorVersion","worktrees","user"]);
|
|
114
172
|
import {IconPicker,useDialogState,useDialogs as useDialogs2} from "./tui.js"
|
|
115
173
|
function createToolbarPinDraft(props){const dialogs=useDialogs2(props.api)
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
const [
|
|
119
|
-
const [
|
|
120
|
-
const [states,setStates]=useDialogState("pin-state-groups",initialStates)
|
|
174
|
+
const initial=toolbarPinAppearance(props.pin)
|
|
175
|
+
const [base,setBase]=useDialogState("pin-icons",[...initial.base])
|
|
176
|
+
const [text,setText]=useDialogState("pin-text",initial.text)
|
|
177
|
+
const [states,setStates]=useDialogState("pin-state-groups",Object.fromEntries(Object.entries(initial.states).map(([key,value])=>[key,[...value]])))
|
|
121
178
|
const [sourceRoute]=useDialogState("pin-route",JSON.stringify(props.api.route.current))
|
|
122
179
|
const [sourceTarget]=useDialogState("pin-target",props.skills?.target().key)
|
|
123
180
|
const group=(state)=>state?states()[state]??base():base()
|
|
@@ -145,20 +202,11 @@ dialogs.back()
|
|
|
145
202
|
}
|
|
146
203
|
function save(){try{if(sourceRoute()!==JSON.stringify(props.api.route.current)||sourceTarget()!==props.skills?.target().key)
|
|
147
204
|
throw new Error("Workspace or session changed; reopen pin configuration")
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const source=props.pin.kind==="skill"?props.pin.location:undefined
|
|
151
|
-
if(!props.expected&&source&&!props.skills?.list().some((skill)=>skill.location===source))
|
|
205
|
+
const source=props.pin.kind==="skill"?props.pin:undefined
|
|
206
|
+
if(!props.expected&&source&&!props.skills?.list().some((skill)=>skill.location===source.location&&skill.name===source.name))
|
|
152
207
|
throw new Error("Skill source changed; choose it again")
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const pin={...props.pin,icon:base()[0]??props.pin.icon,icons:base().length===1?undefined:base(),text:text()||undefined,stateIcons,stateIconLists
|
|
156
|
-
}
|
|
157
|
-
const keys=Object.keys({...initialStates,...states()
|
|
158
|
-
})
|
|
159
|
-
props.preferences.changeToolbarPin(props.expected?{operation:"configure",key:toolbarPinKey(pin),icon:pin.icon,icons:pin.icons??null,text:pin.text??null,expected:props.expected,stateIcons:Object.fromEntries(keys.map((key)=>[key,stateIcons[key]??null])),stateIconLists:Object.fromEntries(keys.map((key)=>[key,stateIconLists[key]??null]))
|
|
160
|
-
}:{operation:"pin",pin
|
|
161
|
-
})
|
|
208
|
+
props.preferences.changeToolbarPin(toolbarPinAppearanceChange(props.pin,{base:base(),text:text(),states:states()
|
|
209
|
+
},props.expected))
|
|
162
210
|
dialogs.back()
|
|
163
211
|
} catch(error){props.api.ui.toast({variant:"error",title:"Toolbar",message:String(error)
|
|
164
212
|
})
|