opencode-navigator 0.19.2 → 0.20.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 +13 -0
- package/README.md +107 -3
- package/dist/{chunk-r37n7w97.js → chunk-c1kw6ktv.js} +3 -4
- package/dist/chunk-cmmnfes2.js +7 -0
- package/dist/layout-preset-actions.js +438 -0
- package/dist/mcp-groups.js +4 -4
- package/dist/project-profile.js +280 -0
- package/dist/provider-limits.js +739 -0
- package/dist/quick-actions-settings.js +2 -5
- package/dist/settings-import.js +40 -0
- package/dist/skill-groups.js +4 -4
- package/dist/trusted-skills.js +22 -0
- package/dist/tui.js +389 -395
- package/package.json +4 -2
- package/scripts/save-project-profile.mjs +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## [0.20.0] - 2026-10-05
|
|
6
|
+
|
|
7
|
+
- Add a default-visible Limits section for the selected model's provider-native quota windows, reset times, account identity, and freshness. Codex CLI support requires an explicit private account binding; unsupported providers show calm guidance.
|
|
8
|
+
- Review Codex banked reset credits and use them only after a separate confirmation and fresh eligibility checks. Preserve the same attempt key when reconciling an uncertain result, including after a restart.
|
|
9
|
+
- Save named, commit-safe project layout and MCP profiles to `.opencode/navigator.json` with the `opencode-navigator-profile` command, an explicit preview, and `--apply`; profile application remains manual.
|
|
10
|
+
|
|
11
|
+
## [0.19.3] - 2026-10-03
|
|
12
|
+
|
|
13
|
+
- Indent grouped Todo, Subagent, Skill, and MCP rows and align MCP server status indicators with their group headings.
|
|
14
|
+
- Place Skill, MCP, and Quick Action bookmarks before item names. Reveal unselected bookmarks on hover or keyboard focus, keep favorites visible, and keep labels steady.
|
|
15
|
+
|
|
5
16
|
## [0.19.2] - 2026-10-02
|
|
6
17
|
|
|
7
18
|
- Show MCP group connection states with the same radio-style indicators as individual servers, without Connect/Disconnect labels on group headings.
|
|
@@ -59,6 +70,8 @@
|
|
|
59
70
|
|
|
60
71
|
Earlier release notes are available on [GitHub Releases](https://github.com/St1ggy/opencode-navigator/releases).
|
|
61
72
|
|
|
73
|
+
[0.20.0]: https://github.com/St1ggy/opencode-navigator/compare/v0.19.3...v0.20.0
|
|
74
|
+
[0.19.3]: https://github.com/St1ggy/opencode-navigator/compare/v0.19.2...v0.19.3
|
|
62
75
|
[0.19.2]: https://github.com/St1ggy/opencode-navigator/compare/v0.19.1...v0.19.2
|
|
63
76
|
[0.19.1]: https://github.com/St1ggy/opencode-navigator/compare/v0.19.0...v0.19.1
|
|
64
77
|
[0.19.0]: https://github.com/St1ggy/opencode-navigator/compare/v0.18.2...v0.19.0
|
package/README.md
CHANGED
|
@@ -103,6 +103,12 @@ belong to each Skill's exact source path, so assignments survive when a Skill
|
|
|
103
103
|
is absent from the current workspace. Filters and Search Everything also match
|
|
104
104
|
group names; choosing a group heading never runs its Skills.
|
|
105
105
|
|
|
106
|
+
Rows beneath group headings are indented in Todo, Subagents, Skills, and MCP.
|
|
107
|
+
Skill, MCP, and Quick Action rows reserve a leading bookmark control: unselected
|
|
108
|
+
bookmarks appear on hover or keyboard focus, while favorites stay visible.
|
|
109
|
+
Labels do not shift when the control appears. MCP server status icons align with
|
|
110
|
+
their group status icons, including when a server has an error detail control.
|
|
111
|
+
|
|
106
112
|

|
|
107
113
|
|
|
108
114
|
Search Everything preserves one query across Skills, Subagents, MCP, and Actions:
|
|
@@ -349,6 +355,82 @@ dialog for the current session and `session.permissions` in the user-wide
|
|
|
349
355
|
`cli.json` (`"prompt"` or `"autoaccept"`) for its startup default. Restart
|
|
350
356
|
OpenCode after changing `cli.json`.
|
|
351
357
|
|
|
358
|
+
## Limits: provider quotas and banked resets
|
|
359
|
+
|
|
360
|
+
Navigator keeps subscription limits separate from OpenCode's session token and
|
|
361
|
+
cost statistics. The first supported source is Codex's documented app-server
|
|
362
|
+
`account/rateLimits/read` RPC. It reports provider-owned usage windows, reset
|
|
363
|
+
times, account identity, and any available banked reset credits. The installed
|
|
364
|
+
Codex 0.160.0 protocol also exposes the separate
|
|
365
|
+
`account/rateLimitResetCredit/consume` RPC. Navigator requires explicit
|
|
366
|
+
confirmation before using it and never redeems credits in the background.
|
|
367
|
+
Limits follows the model selected in the current OpenCode session. A Codex
|
|
368
|
+
account's windows appear only if the provider explicitly maps them to that
|
|
369
|
+
model; otherwise the section explains that a matching quota is unavailable.
|
|
370
|
+
Switching provider, model, session, or account invalidates stale reads and credit actions.
|
|
371
|
+
CodexBar is useful for discovering providers, but its cookie, credential-file,
|
|
372
|
+
and undocumented endpoint integrations are not Navigator data sources.
|
|
373
|
+
|
|
374
|
+
| Source | Machine-readable subscription limits | Banked reset credits |
|
|
375
|
+
| --- | --- | --- |
|
|
376
|
+
| Codex app-server | Rate-limit windows and account ID | Available count, optional details and expiry, confirmed manual consumption |
|
|
377
|
+
| Claude Code (not integrated) | Session-local `rate_limits` statusline payload after a Claude Code response | No documented cross-process consumption API |
|
|
378
|
+
| OpenCode session statistics | Token and cost statistics, not subscription quota | Not applicable |
|
|
379
|
+
| Other providers | Not shown until a documented machine-readable quota API is verified | No action without a documented consume API |
|
|
380
|
+
|
|
381
|
+
Limits is visible and expanded by default, first below the session title. Change
|
|
382
|
+
its visibility and order under **Settings → Sections** in Global or Current
|
|
383
|
+
worktree scope. Hiding or collapsing it does not stop background refreshes, and
|
|
384
|
+
does not grant permission to spend a reset credit. **Focus Limits** is also
|
|
385
|
+
available from the command palette.
|
|
386
|
+
|
|
387
|
+

|
|
388
|
+
|
|
389
|
+
### Link the intended Codex account
|
|
390
|
+
|
|
391
|
+
Install a Codex CLI exposing the documented read and consume RPCs (verified with
|
|
392
|
+
0.160.0), ensure `codex` is on the same machine's `PATH`, and sign in with a ChatGPT
|
|
393
|
+
account. Navigator starts `codex app-server --stdio` lazily; it does not start a
|
|
394
|
+
Codex subprocess for unsupported providers or before an explicit account link.
|
|
395
|
+
|
|
396
|
+
OpenCode does not expose which Codex CLI account its selected model uses. Select
|
|
397
|
+
**Link Codex CLI account** in Limits and confirm the reported account ID only if
|
|
398
|
+
you know it matches this OpenCode provider. The binding is private, user-wide,
|
|
399
|
+
and specific to the provider/model. **Unlink Codex account** removes it;
|
|
400
|
+
**Relink Codex account** is available when the CLI account has changed.
|
|
401
|
+
|
|
402
|
+
For a linked model, Limits shows only buckets with a provider-reported model
|
|
403
|
+
association, their native usage units and window durations, reset times, and
|
|
404
|
+
the last successful read time. It refreshes quietly every five seconds, even
|
|
405
|
+
when hidden. **Refresh** requests a read immediately; a failed read preserves
|
|
406
|
+
cached windows with a stale label and **Retry**. Unsupported mappings, changed
|
|
407
|
+
sign-in/account, and rate-limited reads have separate muted guidance.
|
|
408
|
+
|
|
409
|
+
### Review and manually use a banked reset
|
|
410
|
+
|
|
411
|
+
**Review reset credits** displays the provider's available count, optional credit
|
|
412
|
+
details, statuses, and expiry. Selecting a credit first checks fresh eligibility
|
|
413
|
+
and opens a separate confirmation tied to the current account and model. Cancel
|
|
414
|
+
or Escape returns to the credit list without spending anything.
|
|
415
|
+
|
|
416
|
+
The provider must report ordinary usage blocked, a matching quota window, and an
|
|
417
|
+
available reset credit. A displayed percentage alone does not establish
|
|
418
|
+
eligibility. A reset can affect other eligible Codex windows as well. Navigator
|
|
419
|
+
checks the account and eligibility again before submission. Read-only providers
|
|
420
|
+
do not gain a consume action.
|
|
421
|
+
|
|
422
|
+
After an uncertain result, **Reconcile pending attempt** reuses the same
|
|
423
|
+
idempotency key, including after a restart. Only its unresolved account/model,
|
|
424
|
+
optional credit ID, and attempt key are saved in private preferences. Definitive
|
|
425
|
+
outcomes clear that journal; quotas themselves are not saved to disk. A pending
|
|
426
|
+
attempt on another account must be resolved on its original binding first.
|
|
427
|
+
|
|
428
|
+

|
|
429
|
+
|
|
430
|
+
An absent Codex credit detail list does not mean there are zero credits: the
|
|
431
|
+
provider may report a count without individual rows. Navigator does not read
|
|
432
|
+
provider credential files or save credentials in preferences.
|
|
433
|
+
|
|
352
434
|
## Configuration sources
|
|
353
435
|
|
|
354
436
|
Navigator validates and deep-merges three simultaneous sources in this order:
|
|
@@ -451,9 +533,31 @@ sources are left untouched.
|
|
|
451
533
|
Configured files may define behavior, layout, desired MCP states, layout and MCP
|
|
452
534
|
presets, workspace-profile links, and MCP groups. Navigator deliberately ignores
|
|
453
535
|
favorites, recent-item history, trusted skills, onboarding state, paths, and other
|
|
454
|
-
private mutable data.
|
|
455
|
-
|
|
456
|
-
|
|
536
|
+
private mutable data. UI changes normally use the concurrency-safe preferences
|
|
537
|
+
file under OpenCode's state directory. Resetting a scope removes only its saved
|
|
538
|
+
override and reveals configured values again.
|
|
539
|
+
|
|
540
|
+
To deliberately save a named, commit-safe project profile, choose **Current
|
|
541
|
+
worktree** in Navigator Settings, copy the portable settings JSON, and paste it
|
|
542
|
+
into a temporary file. From that project's root, preview and then save it:
|
|
543
|
+
|
|
544
|
+
```sh
|
|
545
|
+
opencode-navigator-profile save Focus < /path/to/portable-settings.json
|
|
546
|
+
opencode-navigator-profile save Focus --apply < /path/to/portable-settings.json
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
If the command is not installed globally, run it with
|
|
550
|
+
`npx --yes --package=opencode-navigator opencode-navigator-profile` instead.
|
|
551
|
+
The command writes only the validated layout preset, desired MCP states, and
|
|
552
|
+
their optional link into `.opencode/navigator.json`. It preserves other project
|
|
553
|
+
configuration, rejects stale or symlinked targets, and never copies favorites,
|
|
554
|
+
history, trust choices, or private paths. Use `--replace` deliberately to update
|
|
555
|
+
an existing project profile. After saving, restart OpenCode to load the project
|
|
556
|
+
file, then open **Settings → Presets → Focus → Preview & apply**. The profile is
|
|
557
|
+
never applied merely by entering the project, and applying it does not save the
|
|
558
|
+
layout as a default. Saved Global or Current worktree presets with the same name
|
|
559
|
+
still take precedence, so use a distinct name if you want the project profile
|
|
560
|
+
to be visible in the current installation.
|
|
457
561
|
|
|
458
562
|
Choose `Global` or `Current worktree` before editing from the sidebar gear button.
|
|
459
563
|
Behavior changes apply immediately in the selected scope. Section visibility,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {PresetMenu} from "./tui.js"
|
|
2
1
|
import {createComponent as _$createComponent} from "@opentui/solid"
|
|
3
|
-
import {useDialogs} from "./tui.js"
|
|
2
|
+
import {SelectionMenu,useDialogs} from "./tui.js"
|
|
4
3
|
function GroupAssignmentDialog(props){const dialogs=useDialogs(props.api)
|
|
5
4
|
const options=()=>[...props.groups().map((group)=>({title:group,value:`:${group}`,description:group===props.assigned()?"Current":"Existing group"
|
|
6
5
|
})),{title:"Create new group",value:"+",description:"Type a name"
|
|
7
6
|
},{title:"Ungrouped",value:":",description:props.assigned()?"Remove assignment":"No group"
|
|
8
7
|
}]
|
|
9
|
-
return _$createComponent(
|
|
8
|
+
return _$createComponent(SelectionMenu,{get api(){return props.api
|
|
10
9
|
},get title(){return props.title
|
|
11
10
|
},get options(){return options()
|
|
12
11
|
},onSelect:(option)=>{if(option.value!=="+"){props.onAssign(option.value.slice(1)||undefined)
|
|
@@ -21,4 +20,4 @@ dialogs.back()
|
|
|
21
20
|
}})
|
|
22
21
|
}})
|
|
23
22
|
}
|
|
24
|
-
export{
|
|
23
|
+
export{GroupAssignmentDialog}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
var PLUGIN_ID="opencode-navigator"
|
|
2
|
+
var TOGGLE_COMMAND=`${PLUGIN_ID}.toggle`
|
|
3
|
+
var FOCUS_COMMAND=`${PLUGIN_ID}.focus`
|
|
4
|
+
var SEARCH_COMMAND=`${PLUGIN_ID}.search`;
|
|
5
|
+
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"]
|
|
6
|
+
var SECTION_DEFINITIONS=[{name:"limits",label:"Limits"},{name:"todo",label:"Todo"},{name:"subagents",label:"Subagents"},{name:"skills",label:"Skills"},{name:"quick_actions",label:"Quick actions"},{name:"lsp",label:"LSP"},{name:"mcp",label:"MCP"}]
|
|
7
|
+
export{QUICK_ACTION_IDS,SECTION_DEFINITIONS,PLUGIN_ID}
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import {SECTION_DEFINITIONS,PLUGIN_ID
|
|
2
|
+
} from "./chunk-cmmnfes2.js";
|
|
3
|
+
import {createComponent as _$createComponent7} from "@opentui/solid"
|
|
4
|
+
import {setProp as _$setProp5} from "@opentui/solid"
|
|
5
|
+
import {effect as _$effect5} from "@opentui/solid"
|
|
6
|
+
import {createTextNode as _$createTextNode5} from "@opentui/solid"
|
|
7
|
+
import {insertNode as _$insertNode5} from "@opentui/solid"
|
|
8
|
+
import {createElement as _$createElement5} from "@opentui/solid"
|
|
9
|
+
import {SelectionMenu} from "./tui.js"
|
|
10
|
+
import {mergeProps as _$mergeProps2} from "@opentui/solid"
|
|
11
|
+
import {createComponent as _$createComponent6} from "@opentui/solid"
|
|
12
|
+
import {memo as _$memo4} from "@opentui/solid"
|
|
13
|
+
import {For as For3,createMemo as createMemo3,onMount as onMount2} from "solid-js"
|
|
14
|
+
import {useDialogs as useDialogs3,useIcons as useIcons6} from "./tui.js"
|
|
15
|
+
import {createMemo} from "solid-js"
|
|
16
|
+
function mcpToggleAction(status){if(status==="pending")
|
|
17
|
+
return
|
|
18
|
+
return status==="connected"?"disconnect":"connect"
|
|
19
|
+
}
|
|
20
|
+
function mcpPresetPreview(items,states){const byName=new Map(items.map((item)=>[item.name,item]))
|
|
21
|
+
const names=[...new Set([...Object.keys(states),...byName.keys()])].sort((a,b)=>a.localeCompare(b))
|
|
22
|
+
const rows=names.map((name)=>{const item=byName.get(name)
|
|
23
|
+
const desired=Object.hasOwn(states,name)?states[name]:undefined
|
|
24
|
+
const action=item&&mcpToggleAction(item.status)
|
|
25
|
+
let change="unchanged"
|
|
26
|
+
if(desired&&!item)
|
|
27
|
+
change="missing"
|
|
28
|
+
else if(desired&&!action)
|
|
29
|
+
change="unavailable"
|
|
30
|
+
else if(desired==="enabled"&&action==="connect"||desired==="disabled"&&action==="disconnect"){change=action
|
|
31
|
+
}
|
|
32
|
+
return{name,status:item?.status,desired,change}})
|
|
33
|
+
const changes=rows.flatMap((row)=>row.change==="connect"||row.change==="disconnect"?[{name:row.name,action:row.change}]:[])
|
|
34
|
+
return{rows,changes}}
|
|
35
|
+
function createLayoutPresetPreview(preferences,name){const scope=preferences.selectedScopeKey()
|
|
36
|
+
const valid=()=>preferences.selectedScopeKey()===scope
|
|
37
|
+
const preset=createMemo(()=>preferences.layoutPresets()[name])
|
|
38
|
+
const preview=createMemo(()=>{const value=preset()
|
|
39
|
+
return value?preferences.previewLayoutPreset(value):undefined
|
|
40
|
+
})
|
|
41
|
+
return{valid,preview,scope:preferences.preferenceScopeLabel,blocked:()=>preset()?"":"Preset no longer exists.",apply(){const value=preset()
|
|
42
|
+
if(!valid()||!value)
|
|
43
|
+
return false
|
|
44
|
+
preferences.applyLayoutPreset(value)
|
|
45
|
+
return true
|
|
46
|
+
}}}
|
|
47
|
+
function createMcpPresetPreview(controller,preferences,name){const target=controller.target()
|
|
48
|
+
const valid=()=>{const current=controller.target()
|
|
49
|
+
return current.key===target.key&¤t.scope===target.scope
|
|
50
|
+
}
|
|
51
|
+
const preset=createMemo(()=>preferences.mcpPresets()[name])
|
|
52
|
+
const preview=createMemo(()=>mcpPresetPreview(controller.list(target),preset()??{}))
|
|
53
|
+
const state=createMemo(()=>controller.state(target))
|
|
54
|
+
const blocked=createMemo(()=>{if(!preset())
|
|
55
|
+
return "Preset no longer exists."
|
|
56
|
+
if(controller.mutating(target))
|
|
57
|
+
return "Wait for the current MCP operation to finish."
|
|
58
|
+
if(state().error)
|
|
59
|
+
return `Could not refresh MCP: ${state().error.message}. Retry to update the preview.`
|
|
60
|
+
return state().status==="ready"?"":"Refreshing MCP states\u2026"
|
|
61
|
+
})
|
|
62
|
+
return{valid,preview,blocked,scope:target.scope,refresh(){if(valid()&&!controller.mutating(target))
|
|
63
|
+
controller.refresh(target,true).catch(()=>{})
|
|
64
|
+
},apply(){const value=preset()
|
|
65
|
+
return value&&valid()&&!blocked()?controller.applyPreset(name,value,target):undefined
|
|
66
|
+
}}}
|
|
67
|
+
import {mergeProps as _$mergeProps} from "@opentui/solid"
|
|
68
|
+
import {createComponent as _$createComponent5} from "@opentui/solid"
|
|
69
|
+
import {memo as _$memo3} from "@opentui/solid"
|
|
70
|
+
import {For as For2,createMemo as createMemo2,onMount} from "solid-js"
|
|
71
|
+
import {useDialogs as useDialogs2,useIcons as useIcons5} from "./tui.js"
|
|
72
|
+
import {memo as _$memo} from "@opentui/solid"
|
|
73
|
+
import {use as _$use} from "@opentui/solid"
|
|
74
|
+
import {effect as _$effect3} from "@opentui/solid"
|
|
75
|
+
import {createTextNode as _$createTextNode3} from "@opentui/solid"
|
|
76
|
+
import {insertNode as _$insertNode3} from "@opentui/solid"
|
|
77
|
+
import {insert as _$insert3} from "@opentui/solid"
|
|
78
|
+
import {setProp as _$setProp3} from "@opentui/solid"
|
|
79
|
+
import {createElement as _$createElement3} from "@opentui/solid"
|
|
80
|
+
import {createComponent as _$createComponent3} from "@opentui/solid"
|
|
81
|
+
import {useTerminalDimensions} from "@opentui/solid"
|
|
82
|
+
import {Show as Show2,createEffect,createSignal,onCleanup} from "solid-js"
|
|
83
|
+
import {DialogHeader,DialogSurface,useDialogScroll,useDialogs,useIcons as useIcons3} from "./tui.js"
|
|
84
|
+
import {createComponent as _$createComponent} from "@opentui/solid"
|
|
85
|
+
import {effect as _$effect} from "@opentui/solid"
|
|
86
|
+
import {createTextNode as _$createTextNode} from "@opentui/solid"
|
|
87
|
+
import {insertNode as _$insertNode} from "@opentui/solid"
|
|
88
|
+
import {insert as _$insert} from "@opentui/solid"
|
|
89
|
+
import {setProp as _$setProp} from "@opentui/solid"
|
|
90
|
+
import {createElement as _$createElement} from "@opentui/solid"
|
|
91
|
+
import {Show} from "solid-js"
|
|
92
|
+
import {DialogAction,useIcons} from "./tui.js"
|
|
93
|
+
function PresetPreviewActions(props){const icons=useIcons()
|
|
94
|
+
return(()=>{var _el$=_$createElement("box"),_el$2=_$createElement("box"),_el$6=_$createElement("box")
|
|
95
|
+
_$insertNode(_el$,_el$2)
|
|
96
|
+
_$insertNode(_el$,_el$6)
|
|
97
|
+
_$setProp(_el$,"flexDirection","row")
|
|
98
|
+
_$setProp(_el$,"justifyContent","space-between")
|
|
99
|
+
_$setProp(_el$2,"flexGrow",1)
|
|
100
|
+
_$insert(_el$2,_$createComponent(Show,{get when(){return props.onRefresh
|
|
101
|
+
},get children(){var _el$3=_$createElement("text"),_el$4=_$createTextNode(` Refresh (`),_el$5=_$createTextNode(`)`)
|
|
102
|
+
_$insertNode(_el$3,_el$4)
|
|
103
|
+
_$insertNode(_el$3,_el$5)
|
|
104
|
+
_$setProp(_el$3,"onMouseUp",(event)=>{event.stopPropagation()
|
|
105
|
+
props.onRefresh?.()
|
|
106
|
+
})
|
|
107
|
+
_$insert(_el$3,()=>icons.icon("retry"),_el$4)
|
|
108
|
+
_$insert(_el$3,()=>icons.key("ctrl+r"),_el$5)
|
|
109
|
+
_$effect((_$p)=>_$setProp(_el$3,"fg",props.api.theme.current.accent,_$p))
|
|
110
|
+
return _el$3
|
|
111
|
+
}}))
|
|
112
|
+
_$setProp(_el$6,"flexDirection","row")
|
|
113
|
+
_$setProp(_el$6,"gap",1)
|
|
114
|
+
_$insert(_el$6,_$createComponent(DialogAction,{get api(){return props.api
|
|
115
|
+
},get id(){return `${props.prefix}.cancel`
|
|
116
|
+
},label:"Cancel",icon:"close",get selected(){return props.active==="cancel"
|
|
117
|
+
},onSelect:()=>props.onSelect("cancel"),onActivate:()=>props.onActivate("cancel")
|
|
118
|
+
}),null)
|
|
119
|
+
_$insert(_el$6,_$createComponent(DialogAction,{get api(){return props.api
|
|
120
|
+
},get id(){return `${props.prefix}.apply`
|
|
121
|
+
},label:"Apply",icon:"done",get selected(){return props.active==="apply"
|
|
122
|
+
},get disabled(){return props.disabled
|
|
123
|
+
},onSelect:()=>props.onSelect("apply"),onActivate:()=>props.onActivate("apply")
|
|
124
|
+
}),null)
|
|
125
|
+
return _el$
|
|
126
|
+
})()
|
|
127
|
+
}
|
|
128
|
+
import {effect as _$effect2} from "@opentui/solid"
|
|
129
|
+
import {createTextNode as _$createTextNode2} from "@opentui/solid"
|
|
130
|
+
import {insertNode as _$insertNode2} from "@opentui/solid"
|
|
131
|
+
import {insert as _$insert2} from "@opentui/solid"
|
|
132
|
+
import {createComponent as _$createComponent2} from "@opentui/solid"
|
|
133
|
+
import {setProp as _$setProp2} from "@opentui/solid"
|
|
134
|
+
import {createElement as _$createElement2} from "@opentui/solid"
|
|
135
|
+
import {For} from "solid-js"
|
|
136
|
+
import {SelectionBox,useIcons as useIcons2} from "./tui.js"
|
|
137
|
+
function PresetPreviewSummary(props){const icons=useIcons2()
|
|
138
|
+
const theme=()=>props.api.theme.current
|
|
139
|
+
const color=(tone)=>tone==="accent"?theme().accent:tone==="warning"?theme().warning:theme().textMuted
|
|
140
|
+
return(()=>{var _el$=_$createElement2("box")
|
|
141
|
+
_$setProp2(_el$,"flexDirection","row")
|
|
142
|
+
_$setProp2(_el$,"flexWrap","wrap")
|
|
143
|
+
_$setProp2(_el$,"gap",1)
|
|
144
|
+
_$insert2(_el$,_$createComponent2(For,{get each(){return props.stats
|
|
145
|
+
},children:(stat)=>_$createComponent2(SelectionBox,{height:1,get backgroundColor(){return theme().backgroundElement
|
|
146
|
+
},paddingLeft:1,paddingRight:1,get children(){var _el$2=_$createElement2("text"),_el$3=_$createTextNode2(` `)
|
|
147
|
+
_$insertNode2(_el$2,_el$3)
|
|
148
|
+
_$setProp2(_el$2,"wrapMode","none")
|
|
149
|
+
_$setProp2(_el$2,"height",1)
|
|
150
|
+
_$insert2(_el$2,()=>icons.icon(stat.icon),_el$3)
|
|
151
|
+
_$insert2(_el$2,()=>stat.label,null)
|
|
152
|
+
_$effect2((_$p)=>_$setProp2(_el$2,"fg",color(stat.tone),_$p))
|
|
153
|
+
return _el$2
|
|
154
|
+
}})
|
|
155
|
+
}))
|
|
156
|
+
return _el$
|
|
157
|
+
})()
|
|
158
|
+
}
|
|
159
|
+
function PresetPreviewFrame(props){const icons=useIcons3()
|
|
160
|
+
const dialogs=useDialogs(props.api)
|
|
161
|
+
const theme=()=>props.api.theme.current
|
|
162
|
+
const dimensions=useTerminalDimensions()
|
|
163
|
+
const scroll=useDialogScroll()
|
|
164
|
+
const [active,setActive]=createSignal("apply")
|
|
165
|
+
let body
|
|
166
|
+
const prefix=`${PLUGIN_ID}.preset-preview`
|
|
167
|
+
function activate(action){if(!props.valid()){dialogs.close()
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
if(action==="cancel"){dialogs.back()
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if(props.blocked)
|
|
174
|
+
return
|
|
175
|
+
try{props.onApply()
|
|
176
|
+
} catch(error){props.api.ui.toast({variant:"error",title:props.title,message:error instanceof Error?error.message:"Could not apply preset",duration:4000
|
|
177
|
+
})
|
|
178
|
+
}}
|
|
179
|
+
const unregister=props.api.keymap.registerLayer({mode:"modal",priority:1000,commands:[{name:`${prefix}.up`,run:()=>body?.scrollBy(-1)
|
|
180
|
+
},{name:`${prefix}.down`,run:()=>body?.scrollBy(1)
|
|
181
|
+
},{name:`${prefix}.move`,run:()=>{setActive((value)=>value==="apply"?"cancel":"apply")
|
|
182
|
+
}},{name:`${prefix}.submit`,run:()=>activate(active())
|
|
183
|
+
},{name:`${prefix}.refresh`,run:()=>props.onRefresh?.()
|
|
184
|
+
}],bindings:[{key:"up",cmd:`${prefix}.up`
|
|
185
|
+
},{key:"down",cmd:`${prefix}.down`
|
|
186
|
+
},{key:"tab",cmd:`${prefix}.move`
|
|
187
|
+
},{key:"left",cmd:`${prefix}.move`
|
|
188
|
+
},{key:"right",cmd:`${prefix}.move`
|
|
189
|
+
},{key:"return",cmd:`${prefix}.submit`
|
|
190
|
+
},{key:"ctrl+r",cmd:`${prefix}.refresh`
|
|
191
|
+
}]
|
|
192
|
+
})
|
|
193
|
+
onCleanup(unregister)
|
|
194
|
+
createEffect(()=>{if(!props.valid())
|
|
195
|
+
dialogs.close()
|
|
196
|
+
})
|
|
197
|
+
return _$createComponent3(DialogSurface,{get api(){return props.api
|
|
198
|
+
},lift:true,get children(){return [_$createComponent3(DialogHeader,{get api(){return props.api
|
|
199
|
+
},get title(){return props.title
|
|
200
|
+
},get onBack(){return dialogs.back
|
|
201
|
+
}}),(()=>{var _el$=_$createElement3("box"),_el$2=_$createElement3("text"),_el$3=_$createTextNode3(` Scope`),_el$4=_$createElement3("text")
|
|
202
|
+
_$insertNode3(_el$,_el$2)
|
|
203
|
+
_$insertNode3(_el$,_el$4)
|
|
204
|
+
_$setProp3(_el$,"flexDirection","row")
|
|
205
|
+
_$setProp3(_el$,"gap",1)
|
|
206
|
+
_$insertNode3(_el$2,_el$3)
|
|
207
|
+
_$setProp3(_el$2,"flexShrink",0)
|
|
208
|
+
_$insert3(_el$2,()=>icons.icon("scope"),_el$3)
|
|
209
|
+
_$setProp3(_el$4,"flexGrow",1)
|
|
210
|
+
_$setProp3(_el$4,"wrapMode","none")
|
|
211
|
+
_$setProp3(_el$4,"truncate",true)
|
|
212
|
+
_$setProp3(_el$4,"height",1)
|
|
213
|
+
_$insert3(_el$4,()=>props.scope)
|
|
214
|
+
_$effect3((_p$)=>{var _v$=theme().accent,_v$2=theme().textMuted
|
|
215
|
+
_v$!==_p$.e&&(_p$.e=_$setProp3(_el$2,"fg",_v$,_p$.e))
|
|
216
|
+
_v$2!==_p$.t&&(_p$.t=_$setProp3(_el$4,"fg",_v$2,_p$.t))
|
|
217
|
+
return _p$
|
|
218
|
+
},{e:undefined,t:undefined
|
|
219
|
+
})
|
|
220
|
+
return _el$
|
|
221
|
+
})(),_$createComponent3(PresetPreviewSummary,{get api(){return props.api
|
|
222
|
+
},get stats(){return props.stats
|
|
223
|
+
}}),(()=>{var _el$5=_$createElement3("scrollbox"),_el$6=_$createElement3("box")
|
|
224
|
+
_$insertNode3(_el$5,_el$6)
|
|
225
|
+
_$use((node)=>{body=node
|
|
226
|
+
scroll.ref(node)
|
|
227
|
+
},_el$5)
|
|
228
|
+
_$setProp3(_el$5,"scrollX",false)
|
|
229
|
+
_$setProp3(_el$5,"contentOptions",{minHeight:0
|
|
230
|
+
})
|
|
231
|
+
_$setProp3(_el$6,"gap",1)
|
|
232
|
+
_$insert3(_el$6,()=>props.children)
|
|
233
|
+
_$effect3((_p$)=>{var{restore:_v$3,save:_v$4}=scroll,_v$5=Math.min(Math.max(2,Math.floor(dimensions().height*0.75)-14),Math.max(1,props.rowCount*3-1))
|
|
234
|
+
_v$3!==_p$.e&&(_p$.e=_$setProp3(_el$5,"renderBefore",_v$3,_p$.e))
|
|
235
|
+
_v$4!==_p$.t&&(_p$.t=_$setProp3(_el$5,"renderAfter",_v$4,_p$.t))
|
|
236
|
+
_v$5!==_p$.a&&(_p$.a=_$setProp3(_el$5,"height",_v$5,_p$.a))
|
|
237
|
+
return _p$
|
|
238
|
+
},{e:undefined,t:undefined,a:undefined
|
|
239
|
+
})
|
|
240
|
+
return _el$5
|
|
241
|
+
})(),_$createComponent3(Show2,{get when(){return props.blocked||props.notice
|
|
242
|
+
},get children(){var _el$7=_$createElement3("box"),_el$8=_$createElement3("text"),_el$9=_$createElement3("text")
|
|
243
|
+
_$insertNode3(_el$7,_el$8)
|
|
244
|
+
_$insertNode3(_el$7,_el$9)
|
|
245
|
+
_$setProp3(_el$7,"flexDirection","row")
|
|
246
|
+
_$setProp3(_el$7,"gap",1)
|
|
247
|
+
_$setProp3(_el$8,"flexShrink",0)
|
|
248
|
+
_$insert3(_el$8,()=>icons.icon(props.blocked?"error":"info"))
|
|
249
|
+
_$setProp3(_el$9,"flexGrow",1)
|
|
250
|
+
_$setProp3(_el$9,"wrapMode","word")
|
|
251
|
+
_$setProp3(_el$9,"height",2)
|
|
252
|
+
_$insert3(_el$9,()=>props.blocked||props.notice)
|
|
253
|
+
_$effect3((_p$)=>{var _v$6=props.blocked?theme().warning:theme().accent,_v$7=props.blocked?theme().warning:theme().textMuted
|
|
254
|
+
_v$6!==_p$.e&&(_p$.e=_$setProp3(_el$8,"fg",_v$6,_p$.e))
|
|
255
|
+
_v$7!==_p$.t&&(_p$.t=_$setProp3(_el$9,"fg",_v$7,_p$.t))
|
|
256
|
+
return _p$
|
|
257
|
+
},{e:undefined,t:undefined
|
|
258
|
+
})
|
|
259
|
+
return _el$7
|
|
260
|
+
}}),_$createComponent3(PresetPreviewActions,{get api(){return props.api
|
|
261
|
+
},prefix,get active(){return active()
|
|
262
|
+
},get disabled(){return Boolean(props.blocked)
|
|
263
|
+
},onSelect:setActive,onActivate:activate,get onRefresh(){return props.onRefresh
|
|
264
|
+
}}),(()=>{var _el$0=_$createElement3("text"),_el$1=_$createTextNode3(` scroll \xB7 `),_el$10=_$createTextNode3(` select \xB7 `),_el$11=_$createTextNode3(` confirm`)
|
|
265
|
+
_$insertNode3(_el$0,_el$1)
|
|
266
|
+
_$insertNode3(_el$0,_el$10)
|
|
267
|
+
_$insertNode3(_el$0,_el$11)
|
|
268
|
+
_$insert3(_el$0,()=>icons.key("up/down"),_el$1)
|
|
269
|
+
_$insert3(_el$0,()=>icons.key("tab"),_el$10)
|
|
270
|
+
_$insert3(_el$0,()=>icons.key("enter"),_el$11)
|
|
271
|
+
_$effect3((_$p)=>_$setProp3(_el$0,"fg",theme().textMuted,_$p))
|
|
272
|
+
return _el$0
|
|
273
|
+
})()]
|
|
274
|
+
}})
|
|
275
|
+
}
|
|
276
|
+
import {createComponent as _$createComponent4} from "@opentui/solid"
|
|
277
|
+
import {effect as _$effect4} from "@opentui/solid"
|
|
278
|
+
import {createTextNode as _$createTextNode4} from "@opentui/solid"
|
|
279
|
+
import {insertNode as _$insertNode4} from "@opentui/solid"
|
|
280
|
+
import {insert as _$insert4} from "@opentui/solid"
|
|
281
|
+
import {setProp as _$setProp4} from "@opentui/solid"
|
|
282
|
+
import {createElement as _$createElement4} from "@opentui/solid"
|
|
283
|
+
import {memo as _$memo2} from "@opentui/solid"
|
|
284
|
+
import {TextAttributes} from "@opentui/core"
|
|
285
|
+
import {SelectionBox as SelectionBox2,useIcons as useIcons4} from "./tui.js"
|
|
286
|
+
function PresetPreviewRow(props){const icons=useIcons4()
|
|
287
|
+
const theme=()=>props.api.theme.current
|
|
288
|
+
const emphasized=()=>props.status!=="unchanged"
|
|
289
|
+
const statusColor=()=>props.status==="changed"?theme().accent:props.status==="skipped"?theme().warning:theme().textMuted
|
|
290
|
+
return _$createComponent4(SelectionBox2,{get backgroundColor(){return _$memo2(()=>!!emphasized())()?theme().backgroundElement:theme().backgroundPanel
|
|
291
|
+
},paddingLeft:1,paddingRight:1,get children(){return [(()=>{var _el$=_$createElement4("box"),_el$2=_$createElement4("text"),_el$3=_$createElement4("text"),_el$4=_$createElement4("text"),_el$5=_$createTextNode4(` `)
|
|
292
|
+
_$insertNode4(_el$,_el$2)
|
|
293
|
+
_$insertNode4(_el$,_el$3)
|
|
294
|
+
_$insertNode4(_el$,_el$4)
|
|
295
|
+
_$setProp4(_el$,"flexDirection","row")
|
|
296
|
+
_$setProp4(_el$,"gap",1)
|
|
297
|
+
_$setProp4(_el$2,"flexShrink",0)
|
|
298
|
+
_$insert4(_el$2,()=>props.icon)
|
|
299
|
+
_$setProp4(_el$3,"flexGrow",1)
|
|
300
|
+
_$setProp4(_el$3,"wrapMode","none")
|
|
301
|
+
_$setProp4(_el$3,"truncate",true)
|
|
302
|
+
_$setProp4(_el$3,"height",1)
|
|
303
|
+
_$insert4(_el$3,()=>props.title)
|
|
304
|
+
_$insertNode4(_el$4,_el$5)
|
|
305
|
+
_$setProp4(_el$4,"flexShrink",0)
|
|
306
|
+
_$insert4(_el$4,()=>icons.icon(props.statusIcon),_el$5)
|
|
307
|
+
_$insert4(_el$4,()=>props.statusLabel,null)
|
|
308
|
+
_$effect4((_p$)=>{var _v$=statusColor(),_v$2=emphasized()?theme().text:theme().textMuted,_v$3=emphasized()?TextAttributes.BOLD:undefined,_v$4=statusColor(),_v$5=emphasized()?TextAttributes.BOLD:undefined
|
|
309
|
+
_v$!==_p$.e&&(_p$.e=_$setProp4(_el$2,"fg",_v$,_p$.e))
|
|
310
|
+
_v$2!==_p$.t&&(_p$.t=_$setProp4(_el$3,"fg",_v$2,_p$.t))
|
|
311
|
+
_v$3!==_p$.a&&(_p$.a=_$setProp4(_el$3,"attributes",_v$3,_p$.a))
|
|
312
|
+
_v$4!==_p$.o&&(_p$.o=_$setProp4(_el$4,"fg",_v$4,_p$.o))
|
|
313
|
+
_v$5!==_p$.i&&(_p$.i=_$setProp4(_el$4,"attributes",_v$5,_p$.i))
|
|
314
|
+
return _p$
|
|
315
|
+
},{e:undefined,t:undefined,a:undefined,o:undefined,i:undefined
|
|
316
|
+
})
|
|
317
|
+
return _el$
|
|
318
|
+
})(),(()=>{var _el$6=_$createElement4("text"),_el$7=_$createTextNode4(` `)
|
|
319
|
+
_$insertNode4(_el$6,_el$7)
|
|
320
|
+
_$setProp4(_el$6,"wrapMode","none")
|
|
321
|
+
_$setProp4(_el$6,"truncate",true)
|
|
322
|
+
_$setProp4(_el$6,"height",1)
|
|
323
|
+
_$insert4(_el$6,()=>icons.icon("right"),_el$7)
|
|
324
|
+
_$insert4(_el$6,()=>props.description,null)
|
|
325
|
+
_$effect4((_$p)=>_$setProp4(_el$6,"fg",theme().textMuted,_$p))
|
|
326
|
+
return _el$6
|
|
327
|
+
})()]
|
|
328
|
+
}})
|
|
329
|
+
}
|
|
330
|
+
function reportMcpApplyError(api,request){request.catch((error)=>api.ui.toast({variant:"error",title:"Preset apply",message:error instanceof Error?error.message:"MCP preset failed",duration:4000
|
|
331
|
+
}))
|
|
332
|
+
}
|
|
333
|
+
function mcpPreviewRows(model,icons){return model.preview().rows.map((row)=>{const unchanged=`${row.status ?? "not available"} \xB7 unchanged`
|
|
334
|
+
const description=row.desired?{missing:`Not available in this workspace \xB7 target ${row.desired} \xB7 skipped`,unavailable:`${row.status} \xB7 target ${row.desired} \xB7 skipped`,connect:`${row.status} -> enabled \xB7 connect`,disconnect:`${row.status} -> disabled \xB7 disconnect`,unchanged
|
|
335
|
+
}[row.change]:`${unchanged} (not in preset)`
|
|
336
|
+
return{icon:icons.icon("mcp"),title:row.name,description,changed:row.change==="connect"||row.change==="disconnect",skipped:row.change==="missing"||row.change==="unavailable",status:row.change==="connect"||row.change==="disconnect"?"changed":row.change==="missing"||row.change==="unavailable"?"skipped":"unchanged",statusIcon:row.change==="connect"?"connected":row.change==="disconnect"?"disconnected":row.change==="missing"||row.change==="unavailable"?"pending":"done",statusLabel:row.change==="connect"?"CONNECT":row.change==="disconnect"?"DISCONNECT":row.change==="missing"||row.change==="unavailable"?"SKIPPED":"UNCHANGED"
|
|
337
|
+
}}).sort((a,b)=>Number(b.changed)-Number(a.changed)||Number(b.skipped)-Number(a.skipped))
|
|
338
|
+
}
|
|
339
|
+
function LayoutPresetPreview(props){const dialogs=useDialogs3(props.api)
|
|
340
|
+
const icons=useIcons6()
|
|
341
|
+
const model=createLayoutPresetPreview(props.preferences,props.name)
|
|
342
|
+
const linked=props.preferences.workspaceProfiles?.()[props.name]
|
|
343
|
+
const mcpModel=props.controller&&linked?createMcpPresetPreview(props.controller,props.preferences,linked):undefined
|
|
344
|
+
const rows=createMemo3(()=>model.preview()?.rows.map((row)=>{const changes=[]
|
|
345
|
+
if(row.visibility[0]!==row.visibility[1])
|
|
346
|
+
changes.push(`${row.visibility[0] ? "visible" : "hidden"} -> ${row.visibility[1] ? "visible" : "hidden"}`)
|
|
347
|
+
if(row.expansion[0]!==row.expansion[1])
|
|
348
|
+
changes.push(`${row.expansion[0] ? "expanded" : "collapsed"} -> ${row.expansion[1] ? "expanded" : "collapsed"}`)
|
|
349
|
+
if(row.position[0]!==row.position[1])
|
|
350
|
+
changes.push(`position ${row.position[0]} -> ${row.position[1]}`)
|
|
351
|
+
return{icon:icons.section(row.section),title:`${row.position[1]}. ${SECTION_DEFINITIONS.find((section) => section.name === row.section).label}`,description:changes.join(" \xB7 ")||"No changes",changed:changes.length>0,status:changes.length>0?"changed":"unchanged",statusIcon:changes.length>0?"edit":"done",statusLabel:changes.length>0?"CHANGED":"UNCHANGED"
|
|
352
|
+
}}).sort((a,b)=>Number(b.changed)-Number(a.changed))??[])
|
|
353
|
+
const mcpRows=createMemo3(()=>mcpModel?mcpPreviewRows(mcpModel,icons):[])
|
|
354
|
+
const changed=()=>rows().filter((row)=>row.changed).length
|
|
355
|
+
onMount2(()=>mcpModel?.refresh())
|
|
356
|
+
return _$createComponent6(PresetPreviewFrame,{get api(){return props.api
|
|
357
|
+
},get title(){return `${mcpModel ? "Workspace profile" : "Layout preview"}: ${props.name}`
|
|
358
|
+
},get scope(){return mcpModel?`${model.scope()} \xB7 MCP ${mcpModel.scope}`:model.scope()
|
|
359
|
+
},get stats(){return [{icon:"edit",label:`${changed()} layout changed`,tone:"accent"
|
|
360
|
+
},{icon:"done",label:`${rows().length - changed()} layout unchanged`,tone:"muted"
|
|
361
|
+
}]
|
|
362
|
+
},get rowCount(){return rows().length + mcpRows().length
|
|
363
|
+
},valid:()=>model.valid()&&(!mcpModel||mcpModel.valid()),get blocked(){return model.blocked()||mcpModel?.blocked()
|
|
364
|
+
},get onRefresh(){return mcpModel?.refresh
|
|
365
|
+
},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()
|
|
366
|
+
if(mcpModel&&!request)
|
|
367
|
+
return
|
|
368
|
+
if(!model.apply())
|
|
369
|
+
return
|
|
370
|
+
if(mcpModel)
|
|
371
|
+
dialogs.close()
|
|
372
|
+
else{dialogs.back()
|
|
373
|
+
dialogs.back()
|
|
374
|
+
}
|
|
375
|
+
props.api.ui.toast({variant:"success",title:mcpModel?"Workspace profile":"Layout presets",message:`${props.name} applied to ${model.scope()}`,duration:3000
|
|
376
|
+
})
|
|
377
|
+
if(request)
|
|
378
|
+
reportMcpApplyError(props.api,request)
|
|
379
|
+
},get children(){return [_$createComponent6(For3,{get each(){return rows()
|
|
380
|
+
},children:(row)=>_$createComponent6(PresetPreviewRow,_$mergeProps2({get api(){return props.api
|
|
381
|
+
}},row))
|
|
382
|
+
}),_$createComponent6(For3,{get each(){return mcpRows()
|
|
383
|
+
},children:(row)=>_$createComponent6(PresetPreviewRow,_$mergeProps2({get api(){return props.api
|
|
384
|
+
}},row))
|
|
385
|
+
})]
|
|
386
|
+
}})
|
|
387
|
+
}
|
|
388
|
+
function createLayoutPresetActions(input){function prompt(value="",renameFrom){input.dialogs.prompt({title:renameFrom?"Rename layout preset":"Save layout preset",description:()=>(()=>{var _el$=_$createElement5("text")
|
|
389
|
+
_$insertNode5(_el$,_$createTextNode5(`Save the current visible, expanded, and ordered sections.`))
|
|
390
|
+
_$effect5((_$p)=>_$setProp5(_el$,"fg",input.api.theme.current.textMuted,_$p))
|
|
391
|
+
return _el$
|
|
392
|
+
})(),placeholder:"Preset name",value,onConfirm(nameInput){const name=renameFrom?input.preferences.renameLayoutPreset(renameFrom,nameInput):input.preferences.saveLayoutPreset(nameInput)
|
|
393
|
+
input.dialogs.back()
|
|
394
|
+
if(renameFrom)
|
|
395
|
+
input.dialogs.back()
|
|
396
|
+
input.api.ui.toast({variant:"success",title:"Layout presets",message:renameFrom?`Renamed to ${name}`:`Saved ${name}`,duration:3000
|
|
397
|
+
})
|
|
398
|
+
}})
|
|
399
|
+
}
|
|
400
|
+
function open(name){const linked=input.preferences.workspaceProfiles?.()[name]
|
|
401
|
+
const link=()=>input.dialogs.prompt({title:`Link MCP \xB7 ${name}`,placeholder:"Preset; empty unlinks",value:linked??"",onConfirm(value){input.preferences.linkWorkspaceProfile?.(name,value.trim()||undefined)
|
|
402
|
+
input.dialogs.back()
|
|
403
|
+
input.dialogs.back()
|
|
404
|
+
}})
|
|
405
|
+
input.dialogs.open(()=>_$createComponent7(SelectionMenu,{get api(){return input.api
|
|
406
|
+
},title:name,get options(){return [{title:"Preview & apply",value:"apply",icon:"info",description:linked?`profile \xB7 ${linked}`:"review layout changes"
|
|
407
|
+
},{title:"Update from current",value:"update",icon:"save",description:"replace saved layout"
|
|
408
|
+
},{title:"Rename\u2026",value:"rename",description:"change preset name",icon:"edit"
|
|
409
|
+
},...input.mcp?[{title:linked?"Change MCP link\u2026":"Link MCP preset\u2026",value:"link",icon:"mcp",description:linked?`${linked} \xB7 empty unlinks`:"pair layout and MCP"
|
|
410
|
+
}]:[],{title:"Delete",value:"delete",description:"remove preset",icon:"delete"
|
|
411
|
+
}]
|
|
412
|
+
},onSelect:(option)=>{switch(option.value){case "apply":{input.dialogs.open(()=>_$createComponent7(LayoutPresetPreview,{get api(){return input.api
|
|
413
|
+
},get preferences(){return input.preferences
|
|
414
|
+
},get controller(){return input.mcp
|
|
415
|
+
},name
|
|
416
|
+
}),"large")
|
|
417
|
+
break
|
|
418
|
+
}
|
|
419
|
+
case "update":{input.preferences.updateLayoutPreset(name)
|
|
420
|
+
input.dialogs.back()
|
|
421
|
+
input.api.ui.toast({variant:"success",title:"Layout presets",message:`Updated ${name}`,duration:3000
|
|
422
|
+
})
|
|
423
|
+
break
|
|
424
|
+
}
|
|
425
|
+
case "rename":{prompt(name,name)
|
|
426
|
+
break
|
|
427
|
+
}
|
|
428
|
+
case "link":{link()
|
|
429
|
+
break
|
|
430
|
+
}
|
|
431
|
+
default:{input.preferences.deleteLayoutPreset(name)
|
|
432
|
+
input.dialogs.back()
|
|
433
|
+
}}}}))
|
|
434
|
+
}
|
|
435
|
+
return{open,prompt
|
|
436
|
+
}}
|
|
437
|
+
export{createLayoutPresetActions
|
|
438
|
+
}
|
package/dist/mcp-groups.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
} from "./chunk-
|
|
1
|
+
import {GroupAssignmentDialog
|
|
2
|
+
} from "./chunk-c1kw6ktv.js";
|
|
3
3
|
import {createComponent as _$createComponent2} from "@opentui/solid"
|
|
4
|
-
import {useDialogs} from "./tui.js"
|
|
4
|
+
import {SelectionMenu,useDialogs} from "./tui.js"
|
|
5
5
|
import {createComponent as _$createComponent} from "@opentui/solid"
|
|
6
6
|
function McpGroupAssignmentDialog(props){return _$createComponent(GroupAssignmentDialog,{get api(){return props.api
|
|
7
7
|
},get title(){return `Group for ${props.server}`
|
|
@@ -13,7 +13,7 @@ const options=()=>{const live=new Set(props.mcp.list(props.mcp.target()).map((it
|
|
|
13
13
|
return [...new Set([...live,...Object.keys(props.preferences.mcpServerGroups?.()??{})])].sort((a,b)=>a.localeCompare(b)).map((name)=>({title:name,value:name,description:[props.preferences.mcpServerGroups?.()[name]??"Ungrouped",!live.has(name)&&"not in workspace"].filter(Boolean).join(" \xB7 ")
|
|
14
14
|
}))
|
|
15
15
|
}
|
|
16
|
-
return _$createComponent2(
|
|
16
|
+
return _$createComponent2(SelectionMenu,{get api(){return props.api
|
|
17
17
|
},title:"MCP groups \xB7 user-wide",get options(){return options()
|
|
18
18
|
},onSelect:(option)=>dialogs.open(()=>_$createComponent2(McpGroupAssignmentDialog,{get api(){return props.api
|
|
19
19
|
},get preferences(){return props.preferences
|