@chatcode/cco-better-sidebar 0.18.0-rc.5 → 0.18.0-rc.7
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/README.md +1 -1
- package/README_EN.md +1 -1
- package/lib/client-editor.js +346 -273
- package/lib/client-mermaid.js +338 -269
- package/lib/client-registry.js +861 -441
- package/lib/client-terminal.js +344 -271
- package/lib/client.js +855 -435
- package/lib/index.js +47 -7
- package/lib/types/client/PluginProjectPicker.d.ts +13 -0
- package/lib/types/client/api.d.ts +7 -5
- package/lib/types/client/locales.d.ts +16 -0
- package/lib/types/plugin-market-shared.d.ts +22 -0
- package/package.json +1 -1
- package/src/client/PluginMarketView.module.css +15 -0
- package/src/client/PluginMarketView.tsx +113 -24
- package/src/client/PluginProjectPicker.tsx +73 -0
- package/src/client/api.ts +7 -5
- package/src/client/locales-ja.ts +17 -1
- package/src/client/locales.ts +72 -3
- package/src/plugin-market-api.ts +55 -9
- package/src/plugin-market-shared.ts +9 -0
package/src/client/locales.ts
CHANGED
|
@@ -37,13 +37,29 @@ export const zh = {
|
|
|
37
37
|
pluginMarketUser: '用户级',
|
|
38
38
|
pluginMarketInstalled: '插件安装成功。',
|
|
39
39
|
pluginMarketUninstalled: '插件卸载成功。',
|
|
40
|
-
pluginMarketSubtitle: '
|
|
40
|
+
pluginMarketSubtitle: '浏览插件市场,选择项目目录安装,或安装到用户环境。',
|
|
41
|
+
pluginMarketChooseProject: '选择项目目录',
|
|
42
|
+
pluginMarketProjectHint: '安装到项目时,请先选择目标项目文件夹。',
|
|
43
|
+
pluginMarketChoosingDirectory: '正在选择目录…',
|
|
44
|
+
pluginMarketAwaitingConfirmation: '等待确认…',
|
|
45
|
+
pluginMarketConfirmProjectInstall: '确认项目级安装',
|
|
46
|
+
pluginMarketConfirmProjectDescription: '将把 {name} 安装到以下项目目录。如需更改,请取消后在顶部选择项目目录。',
|
|
47
|
+
pluginMarketConfirmInstall: '确认安装',
|
|
48
|
+
pluginMarketInstalledAt: '插件已安装到:{path}',
|
|
49
|
+
pluginMarketInstallPath: '{scope}安装目录:{path}',
|
|
50
|
+
pluginMarketDirectoryPath: '目录路径',
|
|
51
|
+
pluginMarketOpenDirectory: '打开目录',
|
|
52
|
+
pluginMarketSelectDirectory: '选择此目录',
|
|
53
|
+
pluginMarketHomeDirectory: '主目录',
|
|
54
|
+
pluginMarketDirectoryTruncated: '目录过多,仅显示部分结果;可输入完整路径打开项目。',
|
|
41
55
|
pluginMarketSearch: '搜索市场或插件',
|
|
42
56
|
pluginMarketDefault: '默认',
|
|
43
57
|
pluginMarketBrowse: '查看插件',
|
|
44
58
|
pluginMarketNoDescription: '暂无描述',
|
|
45
59
|
pluginMarketInstalling: '安装中…',
|
|
46
60
|
pluginMarketUninstalling: '卸载中…',
|
|
61
|
+
pluginMarketInstallProgress: '正在安装 {name},请稍候…',
|
|
62
|
+
pluginMarketUninstallProgress: '正在卸载 {name},请稍候…',
|
|
47
63
|
accountMenuSettings: '设置',
|
|
48
64
|
accountMenuLogout: '退出登录',
|
|
49
65
|
accountMenuLogoutConfirmTitle: '退出登录?',
|
|
@@ -531,13 +547,29 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
531
547
|
pluginMarketUser: 'User',
|
|
532
548
|
pluginMarketInstalled: 'Plugin installed.',
|
|
533
549
|
pluginMarketUninstalled: 'Plugin uninstalled.',
|
|
534
|
-
pluginMarketSubtitle: 'Browse marketplaces
|
|
550
|
+
pluginMarketSubtitle: 'Browse marketplaces, choose a project directory, or install for your user.',
|
|
551
|
+
pluginMarketChooseProject: 'Choose project directory',
|
|
552
|
+
pluginMarketProjectHint: 'Choose a target project folder when installing for a project.',
|
|
553
|
+
pluginMarketChoosingDirectory: 'Choosing directory…',
|
|
554
|
+
pluginMarketAwaitingConfirmation: 'Awaiting confirmation…',
|
|
555
|
+
pluginMarketConfirmProjectInstall: 'Confirm project installation',
|
|
556
|
+
pluginMarketConfirmProjectDescription: '{name} will be installed in the project directory below. To change it, cancel and choose a project directory at the top of the page.',
|
|
557
|
+
pluginMarketConfirmInstall: 'Confirm installation',
|
|
558
|
+
pluginMarketInstalledAt: 'Plugin installed at: {path}',
|
|
559
|
+
pluginMarketInstallPath: '{scope} installation directory: {path}',
|
|
560
|
+
pluginMarketDirectoryPath: 'Directory path',
|
|
561
|
+
pluginMarketOpenDirectory: 'Open directory',
|
|
562
|
+
pluginMarketSelectDirectory: 'Select this directory',
|
|
563
|
+
pluginMarketHomeDirectory: 'Home',
|
|
564
|
+
pluginMarketDirectoryTruncated: 'Only some folders are shown. Enter the full path to open your project.',
|
|
535
565
|
pluginMarketSearch: 'Search marketplaces or plugins',
|
|
536
566
|
pluginMarketDefault: 'Default',
|
|
537
567
|
pluginMarketBrowse: 'Browse plugins',
|
|
538
568
|
pluginMarketNoDescription: 'No description',
|
|
539
569
|
pluginMarketInstalling: 'Installing…',
|
|
540
570
|
pluginMarketUninstalling: 'Uninstalling…',
|
|
571
|
+
pluginMarketInstallProgress: 'Installing {name}, please wait…',
|
|
572
|
+
pluginMarketUninstallProgress: 'Uninstalling {name}, please wait…',
|
|
541
573
|
accountMenuSettings: 'Settings',
|
|
542
574
|
accountMenuLogout: 'Log out',
|
|
543
575
|
accountMenuLogoutConfirmTitle: 'Log out?',
|
|
@@ -1038,9 +1070,46 @@ import { zhHK as zhHKDict } from './locales-zh-HK.ts'
|
|
|
1038
1070
|
import { zhTW as zhTWDict } from './locales-zh-TW.ts'
|
|
1039
1071
|
import { zhMO as zhMODict } from './locales-zh-MO.ts'
|
|
1040
1072
|
|
|
1041
|
-
// English fallback for the new account/model surfaces until third-language translations are supplied.
|
|
1073
|
+
// English fallback for the new account/model/marketplace surfaces until third-language translations are supplied.
|
|
1042
1074
|
// Keep this list explicit so the dictionary parity check still catches other missing keys.
|
|
1043
1075
|
const accountAndModelFallback = {
|
|
1076
|
+
pluginMarketTitle: en.pluginMarketTitle,
|
|
1077
|
+
pluginMarketProjectDirectory: en.pluginMarketProjectDirectory,
|
|
1078
|
+
pluginMarketInstalledTab: en.pluginMarketInstalledTab,
|
|
1079
|
+
pluginMarketRefresh: en.pluginMarketRefresh,
|
|
1080
|
+
pluginMarketLoading: en.pluginMarketLoading,
|
|
1081
|
+
pluginMarketRefreshing: en.pluginMarketRefreshing,
|
|
1082
|
+
pluginMarketEmpty: en.pluginMarketEmpty,
|
|
1083
|
+
pluginMarketBack: en.pluginMarketBack,
|
|
1084
|
+
pluginMarketInstall: en.pluginMarketInstall,
|
|
1085
|
+
pluginMarketUninstall: en.pluginMarketUninstall,
|
|
1086
|
+
pluginMarketProject: en.pluginMarketProject,
|
|
1087
|
+
pluginMarketUser: en.pluginMarketUser,
|
|
1088
|
+
pluginMarketInstalled: en.pluginMarketInstalled,
|
|
1089
|
+
pluginMarketUninstalled: en.pluginMarketUninstalled,
|
|
1090
|
+
pluginMarketSubtitle: en.pluginMarketSubtitle,
|
|
1091
|
+
pluginMarketChooseProject: en.pluginMarketChooseProject,
|
|
1092
|
+
pluginMarketProjectHint: en.pluginMarketProjectHint,
|
|
1093
|
+
pluginMarketChoosingDirectory: en.pluginMarketChoosingDirectory,
|
|
1094
|
+
pluginMarketAwaitingConfirmation: en.pluginMarketAwaitingConfirmation,
|
|
1095
|
+
pluginMarketConfirmProjectInstall: en.pluginMarketConfirmProjectInstall,
|
|
1096
|
+
pluginMarketConfirmProjectDescription: en.pluginMarketConfirmProjectDescription,
|
|
1097
|
+
pluginMarketConfirmInstall: en.pluginMarketConfirmInstall,
|
|
1098
|
+
pluginMarketInstalledAt: en.pluginMarketInstalledAt,
|
|
1099
|
+
pluginMarketInstallPath: en.pluginMarketInstallPath,
|
|
1100
|
+
pluginMarketDirectoryPath: en.pluginMarketDirectoryPath,
|
|
1101
|
+
pluginMarketOpenDirectory: en.pluginMarketOpenDirectory,
|
|
1102
|
+
pluginMarketSelectDirectory: en.pluginMarketSelectDirectory,
|
|
1103
|
+
pluginMarketHomeDirectory: en.pluginMarketHomeDirectory,
|
|
1104
|
+
pluginMarketDirectoryTruncated: en.pluginMarketDirectoryTruncated,
|
|
1105
|
+
pluginMarketSearch: en.pluginMarketSearch,
|
|
1106
|
+
pluginMarketDefault: en.pluginMarketDefault,
|
|
1107
|
+
pluginMarketBrowse: en.pluginMarketBrowse,
|
|
1108
|
+
pluginMarketNoDescription: en.pluginMarketNoDescription,
|
|
1109
|
+
pluginMarketInstalling: en.pluginMarketInstalling,
|
|
1110
|
+
pluginMarketUninstalling: en.pluginMarketUninstalling,
|
|
1111
|
+
pluginMarketInstallProgress: en.pluginMarketInstallProgress,
|
|
1112
|
+
pluginMarketUninstallProgress: en.pluginMarketUninstallProgress,
|
|
1044
1113
|
accountMenuLogoutConfirmTitle: en.accountMenuLogoutConfirmTitle,
|
|
1045
1114
|
accountMenuLogoutConfirmDescription: en.accountMenuLogoutConfirmDescription,
|
|
1046
1115
|
chatcodeGateChecking: en.chatcodeGateChecking,
|
package/src/plugin-market-api.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/** Shared config plugin routes inside the sidebar's existing trust fence. */
|
|
2
2
|
import type { Context } from './context-types.ts'
|
|
3
|
-
import
|
|
3
|
+
import { realpath, stat } from 'node:fs/promises'
|
|
4
|
+
import { isAbsolute } from 'node:path'
|
|
5
|
+
import type { PluginMarketService, PluginMarketScope, PluginProjectDirectories } from './plugin-market-shared.ts'
|
|
4
6
|
import { requireString, SidebarError } from './wire.ts'
|
|
5
7
|
|
|
8
|
+
interface DirectoryPicker {
|
|
9
|
+
capability(): { kind: 'native'; pick(signal: AbortSignal): Promise<string | null> }
|
|
10
|
+
| { kind: 'browse'; list(path?: string, signal?: AbortSignal): Promise<PluginProjectDirectories> }
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
export function buildPluginMarketApi(ctx: Pick<Context, 'get'>, cwdOf: (payload: unknown) => Promise<{ cwd: string }>): Record<string, (payload: unknown) => Promise<unknown>> {
|
|
7
14
|
const service = (): PluginMarketService => {
|
|
8
15
|
const value = ctx.get('chatcodePluginMarket', false) as PluginMarketService | undefined
|
|
@@ -14,17 +21,46 @@ export function buildPluginMarketApi(ctx: Pick<Context, 'get'>, cwdOf: (payload:
|
|
|
14
21
|
if (scope !== 'project' && scope !== 'user') throw new SidebarError('bad-request', 'scope must be project or user')
|
|
15
22
|
return scope
|
|
16
23
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
const selectedProject = async (payload: unknown): Promise<string> => {
|
|
25
|
+
if (typeof (payload as { projectPath?: unknown } | null)?.projectPath !== 'string') throw new SidebarError('bad-request', '请先选择目标项目目录。')
|
|
26
|
+
const path = requireString(payload, 'projectPath')
|
|
27
|
+
const qualified = process.platform !== 'win32' || /^(?:[A-Za-z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+)/.test(path)
|
|
28
|
+
if (!isAbsolute(path) || !qualified || path.includes('\0')) throw new SidebarError('bad-request', '请选择有效的项目目录。')
|
|
29
|
+
const canonical = await realpath(path)
|
|
30
|
+
if (!(await stat(canonical)).isDirectory()) throw new SidebarError('bad-request', '请选择项目文件夹。')
|
|
31
|
+
return canonical
|
|
32
|
+
}
|
|
33
|
+
const picker = () => {
|
|
34
|
+
const value = ctx.get('directoryPicker', false) as DirectoryPicker | undefined
|
|
35
|
+
if (!value) throw new SidebarError('method-error', '目录选择器未挂载,请启用宿主目录选择插件。', 503)
|
|
36
|
+
return value.capability()
|
|
37
|
+
}
|
|
38
|
+
// A sessionless settings page has no implicit project. Only user-scoped
|
|
39
|
+
// operations may use the launch cwd internally, which never becomes a target.
|
|
40
|
+
const contextOf = async (payload: unknown): Promise<{ sessionId: string; cwd?: string }> => {
|
|
21
41
|
const sessionId = (payload as { sessionId?: unknown } | null)?.sessionId
|
|
22
|
-
if (sessionId === undefined || sessionId === '') return { sessionId: ''
|
|
42
|
+
if (sessionId === undefined || sessionId === '') return { sessionId: '' }
|
|
23
43
|
requireString(payload, 'sessionId')
|
|
24
44
|
return { sessionId: sessionId as string, cwd: (await cwdOf(payload)).cwd }
|
|
25
45
|
}
|
|
46
|
+
const projectOf = async (payload: unknown): Promise<string | undefined> => {
|
|
47
|
+
if ((payload as { projectPath?: unknown } | null)?.projectPath !== undefined) return selectedProject(payload)
|
|
48
|
+
return (await contextOf(payload)).cwd
|
|
49
|
+
}
|
|
26
50
|
return {
|
|
27
51
|
'plugins.context': contextOf,
|
|
52
|
+
'plugins.project.pick': async () => {
|
|
53
|
+
const capability = picker()
|
|
54
|
+
if (capability.kind === 'browse') return { kind: 'browse' }
|
|
55
|
+
const path = await capability.pick(AbortSignal.timeout(120_000))
|
|
56
|
+
return { kind: 'native', path: path === null ? null : await selectedProject({ projectPath: path }) }
|
|
57
|
+
},
|
|
58
|
+
'plugins.project.directories': async payload => {
|
|
59
|
+
const capability = picker()
|
|
60
|
+
if (capability.kind !== 'browse') throw new SidebarError('method-error', '当前宿主使用系统文件夹选择窗口。')
|
|
61
|
+
const path = (payload as { path?: unknown } | null)?.path
|
|
62
|
+
return capability.list(path === undefined ? undefined : requireString(payload, 'path'), AbortSignal.timeout(15_000))
|
|
63
|
+
},
|
|
28
64
|
'plugins.marketplaces': () => service().marketplaces(),
|
|
29
65
|
'plugins.catalog': async payload => {
|
|
30
66
|
const market = service()
|
|
@@ -32,10 +68,20 @@ export function buildPluginMarketApi(ctx: Pick<Context, 'get'>, cwdOf: (payload:
|
|
|
32
68
|
if ((payload as { includeMetadata?: unknown }).includeMetadata !== true) return market.plugins(id)
|
|
33
69
|
return market.catalog ? market.catalog(id) : { plugins: await market.plugins(id), warning: '' }
|
|
34
70
|
},
|
|
35
|
-
'plugins.installed': async payload =>
|
|
36
|
-
|
|
71
|
+
'plugins.installed': async payload => {
|
|
72
|
+
const project = await projectOf(payload)
|
|
73
|
+
const records = await service().installed(project ?? process.cwd())
|
|
74
|
+
return project ? records : records.filter(record => record.scope === 'user')
|
|
75
|
+
},
|
|
76
|
+
'plugins.install': async payload => {
|
|
77
|
+
const scope = scopeOf(payload)
|
|
78
|
+
const project = scope === 'project' ? await selectedProject(payload) : await projectOf(payload)
|
|
79
|
+
return service().install(project ?? process.cwd(), requireString(payload, 'pluginId'), scope)
|
|
80
|
+
},
|
|
37
81
|
'plugins.uninstall': async payload => {
|
|
38
|
-
|
|
82
|
+
const scope = scopeOf(payload)
|
|
83
|
+
const project = scope === 'project' ? await selectedProject(payload) : await projectOf(payload)
|
|
84
|
+
await service().uninstall(project ?? process.cwd(), requireString(payload, 'pluginId'), scope)
|
|
39
85
|
return { ok: true }
|
|
40
86
|
},
|
|
41
87
|
}
|
|
@@ -11,6 +11,15 @@ export interface InstalledMarketplacePlugin extends MarketplacePlugin {
|
|
|
11
11
|
}
|
|
12
12
|
export interface MarketplaceCatalog { marketplaces: PluginMarketplace[]; warning: string }
|
|
13
13
|
export interface PluginCatalog { plugins: MarketplacePlugin[]; warning: string }
|
|
14
|
+
/** Host-owned paths for the remote Web directory browser. */
|
|
15
|
+
export interface PluginProjectDirectories {
|
|
16
|
+
path: string
|
|
17
|
+
home: string
|
|
18
|
+
crumbs: Array<{ name: string; path: string; hidden: boolean }>
|
|
19
|
+
entries: Array<{ name: string; path: string; hidden: boolean }>
|
|
20
|
+
truncated: boolean
|
|
21
|
+
}
|
|
22
|
+
export type PluginProjectPick = { kind: 'native'; path: string | null } | { kind: 'browse' }
|
|
14
23
|
export interface PluginMarketService {
|
|
15
24
|
marketplaces(): Promise<MarketplaceCatalog>
|
|
16
25
|
plugins(marketplaceId: string): Promise<MarketplacePlugin[]>
|