@aryacode/dsh-aryacode-polish 0.3.9 → 0.5.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 +85 -2
- package/README.md +5 -2
- package/client/face.js +436 -5
- package/client/switches.json +12 -0
- package/docs/github-workspaces.md +63 -0
- package/docs/workspace-folder.md +73 -0
- package/entries/github.js +347 -0
- package/entries/registry.js +14 -0
- package/lib/client.js +449 -6
- package/package.json +2 -2
package/lib/client.js
CHANGED
|
@@ -19,11 +19,23 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
"label": "Account balance",
|
|
20
20
|
"description": "Show the remaining account balance in the sidebar foot, above the account row.",
|
|
21
21
|
"defaultOn": true
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"key": "openWorkspaceFolder",
|
|
25
|
+
"label": "Open workspace folder",
|
|
26
|
+
"description": "Add an “Open workspace folder” row to a Session’s menu that opens its workspace in the file browser.",
|
|
27
|
+
"defaultOn": true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"key": "githubWorkspace",
|
|
31
|
+
"label": "Add a workspace from GitHub",
|
|
32
|
+
"description": "Sign in with the GitHub CLI, browse your repositories, and clone one as a workspace.",
|
|
33
|
+
"defaultOn": true
|
|
22
34
|
}
|
|
23
35
|
]
|
|
24
36
|
const ROW_ID = "polish"
|
|
25
37
|
const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
|
|
26
|
-
const CLIENT_VERSION = "0.
|
|
38
|
+
const CLIENT_VERSION = "0.5.0"
|
|
27
39
|
|
|
28
40
|
/**
|
|
29
41
|
* The two pure decisions behind the balance line: which wallet to show, and how
|
|
@@ -166,6 +178,19 @@ window.__ModuleLoader__.load({
|
|
|
166
178
|
const React = require('react')
|
|
167
179
|
const ReactDOM = require('react-dom')
|
|
168
180
|
|
|
181
|
+
/**
|
|
182
|
+
* The Shell's own UI primitives. Guarded because the menu row degrades to a plain
|
|
183
|
+
* button when a Shell answers no primitives, and a bundle that throws on load
|
|
184
|
+
* would take every other surface down with it.
|
|
185
|
+
*/
|
|
186
|
+
const primitives = (() => {
|
|
187
|
+
try {
|
|
188
|
+
return require('@deepseek-ai/dsh-client-ui-primitives')
|
|
189
|
+
} catch {
|
|
190
|
+
return undefined
|
|
191
|
+
}
|
|
192
|
+
})()
|
|
193
|
+
|
|
169
194
|
/** Shared spacing for the page's prose. */
|
|
170
195
|
const SPACING = { margin: '0 0 10px', opacity: 0.85, lineHeight: '1.5' }
|
|
171
196
|
|
|
@@ -666,22 +691,391 @@ window.__ModuleLoader__.load({
|
|
|
666
691
|
return React.createElement('div', { 'data-polish-balance': 'empty', style: { display: 'none' } })
|
|
667
692
|
}
|
|
668
693
|
const money = formatBalance(wallet)
|
|
694
|
+
// The foot is a single row shared with the GitHub entry, so the amount stays
|
|
695
|
+
// bare: a tooltip names it, and nothing here competes for width.
|
|
669
696
|
return React.createElement('div', {
|
|
670
697
|
title: 'Remaining account balance',
|
|
698
|
+
'data-polish-balance': 'ready',
|
|
671
699
|
style: {
|
|
672
700
|
display: 'flex',
|
|
673
701
|
alignItems: 'center',
|
|
674
702
|
gap: '6px',
|
|
675
|
-
padding: '2px
|
|
703
|
+
padding: '2px 4px 4px 8px',
|
|
676
704
|
fontSize: '12px',
|
|
677
705
|
lineHeight: '16px',
|
|
678
|
-
|
|
679
|
-
overflow: 'hidden',
|
|
706
|
+
flex: '0 0 auto',
|
|
680
707
|
whiteSpace: 'nowrap',
|
|
681
|
-
textOverflow: 'ellipsis',
|
|
682
708
|
opacity: 0.75,
|
|
709
|
+
fontVariantNumeric: 'tabular-nums',
|
|
710
|
+
},
|
|
711
|
+
}, money)
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* The workspace a Session belongs to.
|
|
716
|
+
*
|
|
717
|
+
* Occupants of `sidebar.workspaces.session.menu.item` address their data by
|
|
718
|
+
* Session id and must not bind or retain the Session, so the workspace is read
|
|
719
|
+
* from the Workspace snapshot at render time and never memoized.
|
|
720
|
+
*
|
|
721
|
+
* @param workspaces - the Shell's Workspace service, when it is loaded.
|
|
722
|
+
* @param sessionId - the row's Session.
|
|
723
|
+
* @returns the owning workspace, or undefined when it cannot be resolved.
|
|
724
|
+
*/
|
|
725
|
+
function workspaceOf(workspaces, sessionId) {
|
|
726
|
+
if (workspaces === undefined || workspaces === null || sessionId === undefined) return undefined
|
|
727
|
+
let items
|
|
728
|
+
try {
|
|
729
|
+
items = workspaces.list.getSnapshot().items
|
|
730
|
+
} catch {
|
|
731
|
+
return undefined
|
|
732
|
+
}
|
|
733
|
+
if (!Array.isArray(items)) return undefined
|
|
734
|
+
return items.find(item => Array.isArray(item.sessionIds)
|
|
735
|
+
&& item.sessionIds.indexOf(sessionId) !== -1
|
|
736
|
+
&& typeof item.path === 'string'
|
|
737
|
+
&& item.path !== '')
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* "Open workspace folder": one row in a Session's "…" menu.
|
|
742
|
+
*
|
|
743
|
+
* It renders nothing when the switch is off or the Session has no workspace the
|
|
744
|
+
* Host could open, because a menu row that cannot act is worse than no row.
|
|
745
|
+
*
|
|
746
|
+
* @param props - the row's Session id, our lazy `configForms`/`workspaces`, the
|
|
747
|
+
* opener, and the slot's injected `useMenuOpenState` hook.
|
|
748
|
+
* @returns the menu row, or null.
|
|
749
|
+
*/
|
|
750
|
+
function OpenFolderMenuItem(props) {
|
|
751
|
+
const enabled = useSwitch(props.configForms, 'openWorkspaceFolder', true)
|
|
752
|
+
// Hooks run unconditionally; the row simply renders nothing when it cannot act.
|
|
753
|
+
const menu = typeof props.useMenuOpenState === 'function' ? props.useMenuOpenState() : undefined
|
|
754
|
+
const closeMenu = Array.isArray(menu) && typeof menu[1] === 'function' ? menu[1] : undefined
|
|
755
|
+
const workspace = enabled ? workspaceOf(props.workspaces, props.sessionId) : undefined
|
|
756
|
+
if (!enabled || workspace === undefined) return null
|
|
757
|
+
const label = 'Open workspace folder'
|
|
758
|
+
const onSelect = () => {
|
|
759
|
+
if (closeMenu !== undefined) closeMenu(false)
|
|
760
|
+
const open = props.openFolder
|
|
761
|
+
if (typeof open !== 'function') return
|
|
762
|
+
void Promise.resolve()
|
|
763
|
+
.then(() => open(workspace))
|
|
764
|
+
.catch(error => {
|
|
765
|
+
// A menu row that silently does nothing is unfixable in the field, so the
|
|
766
|
+
// failure is reported instead of dropped. This is the only diagnostic the
|
|
767
|
+
// bundle writes.
|
|
768
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
769
|
+
console.warn('[aryacode-polish] open workspace folder failed:', error && error.message ? error.message : error)
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
}
|
|
773
|
+
if (primitives !== undefined && typeof primitives.MenuItemButton === 'function') {
|
|
774
|
+
const icon = typeof primitives.IconFolderOpenRegular === 'function'
|
|
775
|
+
? React.createElement(primitives.IconFolderOpenRegular)
|
|
776
|
+
: undefined
|
|
777
|
+
return React.createElement(primitives.MenuItemButton, { icon, onSelect }, label)
|
|
778
|
+
}
|
|
779
|
+
return React.createElement('button', { type: 'button', role: 'menuitem', onClick: onSelect }, label)
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/** The Host route the GitHub halves talk over. Document-relative, like every app route. */
|
|
783
|
+
const GITHUB_ROUTE = 'api/polish.github'
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Whether the GitHub panel is open. One store for the whole bundle: the footer
|
|
787
|
+
* row opens it, and the overlay panel is the only reader.
|
|
788
|
+
*/
|
|
789
|
+
const githubStore = (() => {
|
|
790
|
+
let open = false
|
|
791
|
+
const listeners = new Set()
|
|
792
|
+
return {
|
|
793
|
+
isOpen: () => open,
|
|
794
|
+
subscribe(listener) {
|
|
795
|
+
listeners.add(listener)
|
|
796
|
+
return () => { listeners.delete(listener) }
|
|
797
|
+
},
|
|
798
|
+
set(next) {
|
|
799
|
+
if (open === next) return
|
|
800
|
+
open = next
|
|
801
|
+
for (const listener of listeners) listener()
|
|
802
|
+
},
|
|
803
|
+
}
|
|
804
|
+
})()
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Post one action to the GitHub route.
|
|
808
|
+
* @param action - `status`, `repos` or `clone`.
|
|
809
|
+
* @param extra - fields the action needs.
|
|
810
|
+
* @returns the route's `value`, or throws with its own message.
|
|
811
|
+
*/
|
|
812
|
+
async function githubCall(action, extra) {
|
|
813
|
+
const response = await fetch(GITHUB_ROUTE, {
|
|
814
|
+
method: 'POST',
|
|
815
|
+
headers: { 'content-type': 'application/json' },
|
|
816
|
+
body: JSON.stringify({ action, ...(extra ?? {}) }),
|
|
817
|
+
})
|
|
818
|
+
const payload = await response.json().catch(() => undefined)
|
|
819
|
+
if (payload === undefined || typeof payload !== 'object') {
|
|
820
|
+
throw new Error(`the Host answered ${String(response.status)}`)
|
|
821
|
+
}
|
|
822
|
+
if (payload.ok !== true) throw new Error(payload.error || 'GitHub request failed')
|
|
823
|
+
return payload.value
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** Modal backdrop and panel skin, matching the Shell's own dialogs. */
|
|
827
|
+
const GITHUB_BACKDROP_STYLE = {
|
|
828
|
+
position: 'fixed',
|
|
829
|
+
inset: 0,
|
|
830
|
+
zIndex: 1200,
|
|
831
|
+
display: 'flex',
|
|
832
|
+
alignItems: 'center',
|
|
833
|
+
justifyContent: 'center',
|
|
834
|
+
background: 'rgba(16, 18, 22, 0.32)',
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const GITHUB_CARD_STYLE = {
|
|
838
|
+
...PANEL_STYLE,
|
|
839
|
+
position: 'static',
|
|
840
|
+
width: 'min(560px, calc(100vw - 32px))',
|
|
841
|
+
maxHeight: 'min(560px, calc(100vh - 64px))',
|
|
842
|
+
display: 'flex',
|
|
843
|
+
flexDirection: 'column',
|
|
844
|
+
overflow: 'hidden',
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const GITHUB_LIST_STYLE = {
|
|
848
|
+
marginTop: '10px',
|
|
849
|
+
overflowY: 'auto',
|
|
850
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.12))',
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const GITHUB_ROW_STYLE = {
|
|
854
|
+
display: 'flex',
|
|
855
|
+
alignItems: 'center',
|
|
856
|
+
gap: '8px',
|
|
857
|
+
width: '100%',
|
|
858
|
+
padding: '8px 4px',
|
|
859
|
+
border: 'none',
|
|
860
|
+
background: 'transparent',
|
|
861
|
+
color: 'inherit',
|
|
862
|
+
font: 'inherit',
|
|
863
|
+
textAlign: 'left',
|
|
864
|
+
cursor: 'pointer',
|
|
865
|
+
borderBottom: '0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.08))',
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const GITHUB_INPUT_STYLE = {
|
|
869
|
+
width: '100%',
|
|
870
|
+
boxSizing: 'border-box',
|
|
871
|
+
padding: '6px 8px',
|
|
872
|
+
font: 'inherit',
|
|
873
|
+
color: 'inherit',
|
|
874
|
+
background: 'transparent',
|
|
875
|
+
border: '0.5px solid var(--dsw-alias-border-l2, rgba(0,0,0,0.2))',
|
|
876
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* The add-a-workspace-from-GitHub dialog.
|
|
881
|
+
*
|
|
882
|
+
* Sign-in is the GitHub CLI's own session: the Host asks `gh` for the token it
|
|
883
|
+
* already holds, so this dialog never collects a credential. A clone becomes a
|
|
884
|
+
* workspace through the Workspace Remote, which is the same call the "+" menu
|
|
885
|
+
* makes after its folder picker returns a path.
|
|
886
|
+
*
|
|
887
|
+
* @param props - our lazy `configForms` and the `createWorkspace` opener.
|
|
888
|
+
* @returns the dialog, or null while it is closed or switched off.
|
|
889
|
+
*/
|
|
890
|
+
function GitHubPanel(props) {
|
|
891
|
+
const open = React.useSyncExternalStore(githubStore.subscribe, githubStore.isOpen)
|
|
892
|
+
const enabled = useSwitch(props.configForms, 'githubWorkspace', true)
|
|
893
|
+
const [phase, setPhase] = React.useState('idle')
|
|
894
|
+
const [account, setAccount] = React.useState(undefined)
|
|
895
|
+
const [repos, setRepos] = React.useState([])
|
|
896
|
+
const [query, setQuery] = React.useState('')
|
|
897
|
+
const [error, setError] = React.useState('')
|
|
898
|
+
const [busy, setBusy] = React.useState('')
|
|
899
|
+
|
|
900
|
+
React.useEffect(() => {
|
|
901
|
+
if (!open || !enabled) return undefined
|
|
902
|
+
let live = true
|
|
903
|
+
setPhase('loading')
|
|
904
|
+
setError('')
|
|
905
|
+
setBusy('')
|
|
906
|
+
setQuery('')
|
|
907
|
+
Promise.resolve()
|
|
908
|
+
.then(() => githubCall('status'))
|
|
909
|
+
.then(status => {
|
|
910
|
+
if (!live) return undefined
|
|
911
|
+
setAccount(status)
|
|
912
|
+
if (status.signedIn !== true) return undefined
|
|
913
|
+
return githubCall('repos').then(list => {
|
|
914
|
+
if (!live) return
|
|
915
|
+
setRepos(Array.isArray(list.repos) ? list.repos : [])
|
|
916
|
+
})
|
|
917
|
+
})
|
|
918
|
+
.then(() => { if (live) setPhase('ready') })
|
|
919
|
+
.catch(reason => {
|
|
920
|
+
if (!live) return
|
|
921
|
+
setError(reason instanceof Error ? reason.message : String(reason))
|
|
922
|
+
setPhase('ready')
|
|
923
|
+
})
|
|
924
|
+
return () => { live = false }
|
|
925
|
+
}, [open, enabled])
|
|
926
|
+
|
|
927
|
+
React.useEffect(() => {
|
|
928
|
+
if (!open) return undefined
|
|
929
|
+
const onKey = event => { if (event.key === 'Escape') githubStore.set(false) }
|
|
930
|
+
document.addEventListener('keydown', onKey)
|
|
931
|
+
return () => document.removeEventListener('keydown', onKey)
|
|
932
|
+
}, [open])
|
|
933
|
+
|
|
934
|
+
if (!open || !enabled) return null
|
|
935
|
+
|
|
936
|
+
const signedIn = account !== undefined && account.signedIn === true
|
|
937
|
+
const needle = query.trim().toLowerCase()
|
|
938
|
+
const visible = needle === ''
|
|
939
|
+
? repos
|
|
940
|
+
: repos.filter(repo => `${repo.fullName} ${repo.description}`.toLowerCase().includes(needle))
|
|
941
|
+
const root = account !== undefined && typeof account.root === 'string' ? account.root : ''
|
|
942
|
+
|
|
943
|
+
const clone = repo => {
|
|
944
|
+
if (busy !== '') return
|
|
945
|
+
setBusy(repo.fullName)
|
|
946
|
+
setError('')
|
|
947
|
+
Promise.resolve()
|
|
948
|
+
.then(() => githubCall('clone', { owner: repo.owner, name: repo.name }))
|
|
949
|
+
.then(result => Promise.resolve(props.createWorkspace(result.path)).then(() => result))
|
|
950
|
+
.then(result => {
|
|
951
|
+
setBusy('')
|
|
952
|
+
githubStore.set(false)
|
|
953
|
+
if (typeof console !== 'undefined' && typeof console.info === 'function') {
|
|
954
|
+
console.info('[aryacode-polish] workspace ready:', result.path)
|
|
955
|
+
}
|
|
956
|
+
})
|
|
957
|
+
.catch(reason => {
|
|
958
|
+
setBusy('')
|
|
959
|
+
setError(reason instanceof Error ? reason.message : String(reason))
|
|
960
|
+
})
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const rows = visible.slice(0, 200).map(repo => React.createElement('button', {
|
|
964
|
+
key: repo.fullName,
|
|
965
|
+
type: 'button',
|
|
966
|
+
style: GITHUB_ROW_STYLE,
|
|
967
|
+
disabled: busy !== '',
|
|
968
|
+
onClick: () => clone(repo),
|
|
969
|
+
title: repo.description === '' ? repo.fullName : `${repo.fullName} — ${repo.description}`,
|
|
970
|
+
'data-polish-github-row': repo.fullName,
|
|
971
|
+
},
|
|
972
|
+
React.createElement('span', { style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } },
|
|
973
|
+
repo.fullName,
|
|
974
|
+
repo.isPrivate ? React.createElement('span', { style: { marginLeft: '6px', opacity: 0.6 } }, 'private') : null,
|
|
975
|
+
repo.isFork ? React.createElement('span', { style: { marginLeft: '6px', opacity: 0.6 } }, 'fork') : null),
|
|
976
|
+
repo.cloned === true
|
|
977
|
+
? React.createElement('span', { style: { opacity: 0.6 } }, 'cloned')
|
|
978
|
+
: null,
|
|
979
|
+
busy === repo.fullName ? React.createElement('span', { style: { opacity: 0.7 } }, 'cloning…') : null))
|
|
980
|
+
|
|
981
|
+
return ReactDOM.createPortal(
|
|
982
|
+
React.createElement('div', {
|
|
983
|
+
style: GITHUB_BACKDROP_STYLE,
|
|
984
|
+
'data-polish-github': 'dialog',
|
|
985
|
+
onPointerDown: event => { if (event.target === event.currentTarget) githubStore.set(false) },
|
|
683
986
|
},
|
|
684
|
-
|
|
987
|
+
React.createElement('div', { style: GITHUB_CARD_STYLE, role: 'dialog', 'aria-label': 'Add a workspace from GitHub' },
|
|
988
|
+
React.createElement('div', { style: { ...PANEL_TITLE_STYLE, marginBottom: '4px' } },
|
|
989
|
+
React.createElement('span', null, 'Add a workspace from GitHub'),
|
|
990
|
+
React.createElement('button', {
|
|
991
|
+
type: 'button',
|
|
992
|
+
onClick: () => githubStore.set(false),
|
|
993
|
+
style: { border: 'none', background: 'transparent', color: 'inherit', font: 'inherit', cursor: 'pointer', opacity: 0.7 },
|
|
994
|
+
'aria-label': 'Close',
|
|
995
|
+
}, '×')),
|
|
996
|
+
React.createElement('div', { style: { opacity: 0.75, minHeight: '18px' } },
|
|
997
|
+
phase === 'loading' && !signedIn ? 'Checking the GitHub CLI session…' : null,
|
|
998
|
+
signedIn ? `Signed in as ${String(account.login)} · cloning into ${root}` : null,
|
|
999
|
+
!signedIn && phase !== 'loading'
|
|
1000
|
+
? 'The GitHub CLI is not signed in. Run `gh auth login` in a terminal, then reopen this dialog.'
|
|
1001
|
+
: null),
|
|
1002
|
+
signedIn
|
|
1003
|
+
? React.createElement('input', {
|
|
1004
|
+
style: { ...GITHUB_INPUT_STYLE, marginTop: '10px' },
|
|
1005
|
+
value: query,
|
|
1006
|
+
placeholder: 'Search your repositories',
|
|
1007
|
+
'aria-label': 'Search your repositories',
|
|
1008
|
+
autoFocus: true,
|
|
1009
|
+
onChange: event => setQuery(event.target.value),
|
|
1010
|
+
})
|
|
1011
|
+
: null,
|
|
1012
|
+
error !== ''
|
|
1013
|
+
? React.createElement('div', { style: { marginTop: '10px', color: 'var(--dsw-alias-label-error, #b42318)' } }, error)
|
|
1014
|
+
: null,
|
|
1015
|
+
signedIn
|
|
1016
|
+
? React.createElement('div', { style: GITHUB_LIST_STYLE, 'data-polish-github-list': String(visible.length) },
|
|
1017
|
+
phase === 'loading' && visible.length === 0 ? React.createElement('div', { style: { padding: '10px 4px', opacity: 0.7 } }, 'Loading repositories…') : null,
|
|
1018
|
+
phase === 'ready' && visible.length === 0 ? React.createElement('div', { style: { padding: '10px 4px', opacity: 0.7 } }, 'No repository matches that search.') : null,
|
|
1019
|
+
...rows)
|
|
1020
|
+
: null,
|
|
1021
|
+
React.createElement('p', { style: { margin: '10px 0 0', color: 'var(--dsw-alias-label-tertiary, inherit)' } },
|
|
1022
|
+
'The clone is added as a workspace. Sign-in comes from the GitHub CLI, so this plugin stores no token.')))
|
|
1023
|
+
, document.body)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* The footer row that opens the GitHub dialog.
|
|
1028
|
+
* @param props - our lazy `configForms`.
|
|
1029
|
+
* @returns the row, or null when the feature is switched off.
|
|
1030
|
+
*/
|
|
1031
|
+
function GithubEntry(props) {
|
|
1032
|
+
const enabled = useSwitch(props.configForms, 'githubWorkspace', true)
|
|
1033
|
+
if (!enabled) return null
|
|
1034
|
+
return React.createElement('div', { style: { padding: '2px 8px 4px', minWidth: 0 } },
|
|
1035
|
+
React.createElement('button', {
|
|
1036
|
+
type: 'button',
|
|
1037
|
+
'data-polish-github': 'entry',
|
|
1038
|
+
title: 'Add a workspace from GitHub',
|
|
1039
|
+
'aria-label': 'Add a workspace from GitHub',
|
|
1040
|
+
onClick: () => githubStore.set(true),
|
|
1041
|
+
style: {
|
|
1042
|
+
display: 'flex',
|
|
1043
|
+
alignItems: 'center',
|
|
1044
|
+
gap: '6px',
|
|
1045
|
+
maxWidth: '100%',
|
|
1046
|
+
minWidth: 0,
|
|
1047
|
+
padding: '2px 6px',
|
|
1048
|
+
border: 'none',
|
|
1049
|
+
borderRadius: 'var(--dsw-radius-sm, 6px)',
|
|
1050
|
+
background: 'transparent',
|
|
1051
|
+
color: 'var(--dsw-alias-label-tertiary, inherit)',
|
|
1052
|
+
font: 'inherit',
|
|
1053
|
+
fontSize: '12px',
|
|
1054
|
+
lineHeight: '18px',
|
|
1055
|
+
fontVariantNumeric: 'tabular-nums',
|
|
1056
|
+
whiteSpace: 'nowrap',
|
|
1057
|
+
overflow: 'hidden',
|
|
1058
|
+
textOverflow: 'ellipsis',
|
|
1059
|
+
cursor: 'pointer',
|
|
1060
|
+
},
|
|
1061
|
+
onMouseEnter: event => { event.currentTarget.style.background = 'var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,0.06))' },
|
|
1062
|
+
onMouseLeave: event => { event.currentTarget.style.background = 'transparent' },
|
|
1063
|
+
},
|
|
1064
|
+
React.createElement('svg', {
|
|
1065
|
+
width: 14,
|
|
1066
|
+
height: 14,
|
|
1067
|
+
viewBox: '0 0 16 16',
|
|
1068
|
+
fill: 'none',
|
|
1069
|
+
stroke: 'currentColor',
|
|
1070
|
+
strokeWidth: 1.2,
|
|
1071
|
+
strokeLinecap: 'round',
|
|
1072
|
+
strokeLinejoin: 'round',
|
|
1073
|
+
'aria-hidden': 'true',
|
|
1074
|
+
style: { flex: 'none' },
|
|
1075
|
+
},
|
|
1076
|
+
React.createElement('path', { d: 'M2.5 5.5h4l1.2 1.5h5.8v6.5h-11z' }),
|
|
1077
|
+
React.createElement('path', { d: 'M8 9.8v3.4M6.3 11.5h3.4' })),
|
|
1078
|
+
'Add from GitHub'))
|
|
685
1079
|
}
|
|
686
1080
|
|
|
687
1081
|
return {
|
|
@@ -720,6 +1114,55 @@ window.__ModuleLoader__.load({
|
|
|
720
1114
|
inject: () => ({ configForms, loadBalance }),
|
|
721
1115
|
}, BalanceLine))
|
|
722
1116
|
})
|
|
1117
|
+
// The Workspace service and the Session Remote are both required for the menu
|
|
1118
|
+
// row: it reads the folder from the first and opens it through the second.
|
|
1119
|
+
// They are declared on the inject list because a Remote namespace may not be
|
|
1120
|
+
// read through `ctx.get` — the Shell's guard refuses it.
|
|
1121
|
+
ctx.inject(['workspaces', 'remote', 'remote.session'], (workspaceCtx) => {
|
|
1122
|
+
const openFolder = async (workspace) => {
|
|
1123
|
+
const remote = workspaceCtx.remote.session
|
|
1124
|
+
// A Remote method answers an envelope (`{ ok, value }`), not the bare
|
|
1125
|
+
// return value; a Shell that answered a plain boolean is tolerated too.
|
|
1126
|
+
const answer = await remote.canOpenWorkspacePath()
|
|
1127
|
+
const available = answer !== null && typeof answer === 'object'
|
|
1128
|
+
? answer.ok === true && answer.value === true
|
|
1129
|
+
: answer === true
|
|
1130
|
+
if (!available) throw new Error('this Host cannot open a folder')
|
|
1131
|
+
const result = await remote.openWorkspacePath({ path: workspace.path })
|
|
1132
|
+
if (result !== null && typeof result === 'object' && 'ok' in result && result.ok !== true) {
|
|
1133
|
+
throw new Error('the Host refused to open the folder')
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
workspaceCtx.slots.inject('sidebar.workspaces.session.menu.item', () => workspaceCtx.slots.register({
|
|
1137
|
+
name: 'sidebar.workspaces.session.menu.item',
|
|
1138
|
+
id: 'polish-open-folder',
|
|
1139
|
+
order: 460,
|
|
1140
|
+
inject: () => ({ configForms, openFolder, workspaces: workspaceCtx.get('workspaces') }),
|
|
1141
|
+
}, OpenFolderMenuItem))
|
|
1142
|
+
})
|
|
1143
|
+
// Adding a workspace from GitHub goes through the Workspace service's own
|
|
1144
|
+
// create call — the same one the "+" menu makes once its folder picker
|
|
1145
|
+
// returns a path — so a clone lands in the sidebar exactly like a picked
|
|
1146
|
+
// folder does, with no Harness change anywhere. The service takes the plain
|
|
1147
|
+
// path and throws on refusal; the Remote namespace underneath it is
|
|
1148
|
+
// `ctx.remote.workspace` (singular), which is why this injects the service.
|
|
1149
|
+
ctx.inject(['workspaces'], (githubCtx) => {
|
|
1150
|
+
const createWorkspace = async (path) => {
|
|
1151
|
+
const workspace = await githubCtx.workspaces.create({ path })
|
|
1152
|
+
return workspace
|
|
1153
|
+
}
|
|
1154
|
+
githubCtx.slots.inject('shell.overlay', () => githubCtx.slots.register({
|
|
1155
|
+
name: 'shell.overlay',
|
|
1156
|
+
id: 'polish-github',
|
|
1157
|
+
inject: () => ({ configForms, createWorkspace }),
|
|
1158
|
+
}, GitHubPanel))
|
|
1159
|
+
githubCtx.slots.inject('sidebar.footer.action', () => githubCtx.slots.register({
|
|
1160
|
+
name: 'sidebar.footer.action',
|
|
1161
|
+
id: 'polish-github-entry',
|
|
1162
|
+
order: 4,
|
|
1163
|
+
inject: () => ({ configForms }),
|
|
1164
|
+
}, GithubEntry))
|
|
1165
|
+
})
|
|
723
1166
|
},
|
|
724
1167
|
}
|
|
725
1168
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-aryacode-polish",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AryaCode Polish: one install for the small things that make AryaCode feel finished. One Plugins row, one switch per quality-of-life feature.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build:client": "node scripts/build-client.mjs",
|
|
53
53
|
"prepack": "node scripts/build-client.mjs",
|
|
54
|
-
"test": "node --test test/patch.spec.mjs test/config.spec.mjs test/registry.spec.mjs test/cost.spec.mjs test/money.spec.mjs test/account.spec.mjs test/docs.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs"
|
|
54
|
+
"test": "node --test test/patch.spec.mjs test/config.spec.mjs test/registry.spec.mjs test/cost.spec.mjs test/money.spec.mjs test/account.spec.mjs test/github.spec.mjs test/docs.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"zod": "^4.4.3"
|