@chatcode/cco-better-sidebar 0.18.0-rc.5 → 0.18.0-rc.6
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 +332 -271
- package/lib/client-mermaid.js +325 -268
- package/lib/client-registry.js +796 -439
- package/lib/client-terminal.js +331 -270
- package/lib/client.js +792 -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 +12 -0
- package/lib/types/plugin-market-shared.d.ts +22 -0
- package/package.json +1 -1
- package/src/client/PluginMarketView.module.css +14 -0
- package/src/client/PluginMarketView.tsx +86 -24
- package/src/client/PluginProjectPicker.tsx +73 -0
- package/src/client/api.ts +7 -5
- package/src/client/locales-ja.ts +13 -1
- package/src/client/locales.ts +60 -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,25 @@ export const zh = {
|
|
|
37
37
|
pluginMarketUser: '用户级',
|
|
38
38
|
pluginMarketInstalled: '插件安装成功。',
|
|
39
39
|
pluginMarketUninstalled: '插件卸载成功。',
|
|
40
|
-
pluginMarketSubtitle: '
|
|
40
|
+
pluginMarketSubtitle: '浏览插件市场,选择项目目录安装,或安装到用户环境。',
|
|
41
|
+
pluginMarketChooseProject: '选择项目目录',
|
|
42
|
+
pluginMarketProjectHint: '安装到项目时,请先选择目标项目文件夹。',
|
|
43
|
+
pluginMarketChoosingDirectory: '正在选择目录…',
|
|
44
|
+
pluginMarketInstalledAt: '插件已安装到:{path}',
|
|
45
|
+
pluginMarketInstallPath: '{scope}安装目录:{path}',
|
|
46
|
+
pluginMarketDirectoryPath: '目录路径',
|
|
47
|
+
pluginMarketOpenDirectory: '打开目录',
|
|
48
|
+
pluginMarketSelectDirectory: '选择此目录',
|
|
49
|
+
pluginMarketHomeDirectory: '主目录',
|
|
50
|
+
pluginMarketDirectoryTruncated: '目录过多,仅显示部分结果;可输入完整路径打开项目。',
|
|
41
51
|
pluginMarketSearch: '搜索市场或插件',
|
|
42
52
|
pluginMarketDefault: '默认',
|
|
43
53
|
pluginMarketBrowse: '查看插件',
|
|
44
54
|
pluginMarketNoDescription: '暂无描述',
|
|
45
55
|
pluginMarketInstalling: '安装中…',
|
|
46
56
|
pluginMarketUninstalling: '卸载中…',
|
|
57
|
+
pluginMarketInstallProgress: '正在安装 {name},请稍候…',
|
|
58
|
+
pluginMarketUninstallProgress: '正在卸载 {name},请稍候…',
|
|
47
59
|
accountMenuSettings: '设置',
|
|
48
60
|
accountMenuLogout: '退出登录',
|
|
49
61
|
accountMenuLogoutConfirmTitle: '退出登录?',
|
|
@@ -531,13 +543,25 @@ export const en: Record<keyof typeof zh, string> = {
|
|
|
531
543
|
pluginMarketUser: 'User',
|
|
532
544
|
pluginMarketInstalled: 'Plugin installed.',
|
|
533
545
|
pluginMarketUninstalled: 'Plugin uninstalled.',
|
|
534
|
-
pluginMarketSubtitle: 'Browse marketplaces
|
|
546
|
+
pluginMarketSubtitle: 'Browse marketplaces, choose a project directory, or install for your user.',
|
|
547
|
+
pluginMarketChooseProject: 'Choose project directory',
|
|
548
|
+
pluginMarketProjectHint: 'Choose a target project folder when installing for a project.',
|
|
549
|
+
pluginMarketChoosingDirectory: 'Choosing directory…',
|
|
550
|
+
pluginMarketInstalledAt: 'Plugin installed at: {path}',
|
|
551
|
+
pluginMarketInstallPath: '{scope} installation directory: {path}',
|
|
552
|
+
pluginMarketDirectoryPath: 'Directory path',
|
|
553
|
+
pluginMarketOpenDirectory: 'Open directory',
|
|
554
|
+
pluginMarketSelectDirectory: 'Select this directory',
|
|
555
|
+
pluginMarketHomeDirectory: 'Home',
|
|
556
|
+
pluginMarketDirectoryTruncated: 'Only some folders are shown. Enter the full path to open your project.',
|
|
535
557
|
pluginMarketSearch: 'Search marketplaces or plugins',
|
|
536
558
|
pluginMarketDefault: 'Default',
|
|
537
559
|
pluginMarketBrowse: 'Browse plugins',
|
|
538
560
|
pluginMarketNoDescription: 'No description',
|
|
539
561
|
pluginMarketInstalling: 'Installing…',
|
|
540
562
|
pluginMarketUninstalling: 'Uninstalling…',
|
|
563
|
+
pluginMarketInstallProgress: 'Installing {name}, please wait…',
|
|
564
|
+
pluginMarketUninstallProgress: 'Uninstalling {name}, please wait…',
|
|
541
565
|
accountMenuSettings: 'Settings',
|
|
542
566
|
accountMenuLogout: 'Log out',
|
|
543
567
|
accountMenuLogoutConfirmTitle: 'Log out?',
|
|
@@ -1038,9 +1062,42 @@ import { zhHK as zhHKDict } from './locales-zh-HK.ts'
|
|
|
1038
1062
|
import { zhTW as zhTWDict } from './locales-zh-TW.ts'
|
|
1039
1063
|
import { zhMO as zhMODict } from './locales-zh-MO.ts'
|
|
1040
1064
|
|
|
1041
|
-
// English fallback for the new account/model surfaces until third-language translations are supplied.
|
|
1065
|
+
// English fallback for the new account/model/marketplace surfaces until third-language translations are supplied.
|
|
1042
1066
|
// Keep this list explicit so the dictionary parity check still catches other missing keys.
|
|
1043
1067
|
const accountAndModelFallback = {
|
|
1068
|
+
pluginMarketTitle: en.pluginMarketTitle,
|
|
1069
|
+
pluginMarketProjectDirectory: en.pluginMarketProjectDirectory,
|
|
1070
|
+
pluginMarketInstalledTab: en.pluginMarketInstalledTab,
|
|
1071
|
+
pluginMarketRefresh: en.pluginMarketRefresh,
|
|
1072
|
+
pluginMarketLoading: en.pluginMarketLoading,
|
|
1073
|
+
pluginMarketRefreshing: en.pluginMarketRefreshing,
|
|
1074
|
+
pluginMarketEmpty: en.pluginMarketEmpty,
|
|
1075
|
+
pluginMarketBack: en.pluginMarketBack,
|
|
1076
|
+
pluginMarketInstall: en.pluginMarketInstall,
|
|
1077
|
+
pluginMarketUninstall: en.pluginMarketUninstall,
|
|
1078
|
+
pluginMarketProject: en.pluginMarketProject,
|
|
1079
|
+
pluginMarketUser: en.pluginMarketUser,
|
|
1080
|
+
pluginMarketInstalled: en.pluginMarketInstalled,
|
|
1081
|
+
pluginMarketUninstalled: en.pluginMarketUninstalled,
|
|
1082
|
+
pluginMarketSubtitle: en.pluginMarketSubtitle,
|
|
1083
|
+
pluginMarketChooseProject: en.pluginMarketChooseProject,
|
|
1084
|
+
pluginMarketProjectHint: en.pluginMarketProjectHint,
|
|
1085
|
+
pluginMarketChoosingDirectory: en.pluginMarketChoosingDirectory,
|
|
1086
|
+
pluginMarketInstalledAt: en.pluginMarketInstalledAt,
|
|
1087
|
+
pluginMarketInstallPath: en.pluginMarketInstallPath,
|
|
1088
|
+
pluginMarketDirectoryPath: en.pluginMarketDirectoryPath,
|
|
1089
|
+
pluginMarketOpenDirectory: en.pluginMarketOpenDirectory,
|
|
1090
|
+
pluginMarketSelectDirectory: en.pluginMarketSelectDirectory,
|
|
1091
|
+
pluginMarketHomeDirectory: en.pluginMarketHomeDirectory,
|
|
1092
|
+
pluginMarketDirectoryTruncated: en.pluginMarketDirectoryTruncated,
|
|
1093
|
+
pluginMarketSearch: en.pluginMarketSearch,
|
|
1094
|
+
pluginMarketDefault: en.pluginMarketDefault,
|
|
1095
|
+
pluginMarketBrowse: en.pluginMarketBrowse,
|
|
1096
|
+
pluginMarketNoDescription: en.pluginMarketNoDescription,
|
|
1097
|
+
pluginMarketInstalling: en.pluginMarketInstalling,
|
|
1098
|
+
pluginMarketUninstalling: en.pluginMarketUninstalling,
|
|
1099
|
+
pluginMarketInstallProgress: en.pluginMarketInstallProgress,
|
|
1100
|
+
pluginMarketUninstallProgress: en.pluginMarketUninstallProgress,
|
|
1044
1101
|
accountMenuLogoutConfirmTitle: en.accountMenuLogoutConfirmTitle,
|
|
1045
1102
|
accountMenuLogoutConfirmDescription: en.accountMenuLogoutConfirmDescription,
|
|
1046
1103
|
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[]>
|