@michengai/dsh-skills-manager 1.2.34 → 1.2.35
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 +12 -0
- package/CHANGELOG.zh-CN.md +12 -0
- package/README.en.md +2 -2
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/lib/client.js +1 -1
- package/lib/repositories.js +24 -174
- package/lib/repository-git.js +64 -80
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.35] - 2026-11-06
|
|
4
|
+
|
|
5
|
+
### Behavior Changes
|
|
6
|
+
|
|
7
|
+
- **Skill repositories are fetched on demand, and size limits apply while downloading**:
|
|
8
|
+
- every repository source now downloads only the skill directories, so README images and other unrelated assets no longer count; this requires Git on the machine, and adding a repository asks you to install Git when it is missing;
|
|
9
|
+
- size limits take effect during the transfer: free disk space is checked first, and an oversized repository stops immediately with the real limit and the actual size instead of failing after the download;
|
|
10
|
+
- installing reuses the already scanned checkout, so nothing is downloaded twice;
|
|
11
|
+
- upgrade note: the first install from a repository added before this version shows "repository cache missing or expired, refresh the repository and retry" — one refresh migrates it.
|
|
12
|
+
|
|
13
|
+
[中文文档](CHANGELOG.zh-CN.md)
|
|
14
|
+
|
|
3
15
|
## [1.2.34] - 2026-11-06
|
|
4
16
|
|
|
5
17
|
### Improvements
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [1.2.35] - 2026-11-06
|
|
4
|
+
|
|
5
|
+
### 行为变更
|
|
6
|
+
|
|
7
|
+
- **技能仓库统一按需检出,体积限制不再事后才报**:
|
|
8
|
+
- 所有仓库来源都只下载技能目录,仓库首页配图等无关内容不参与下载;这需要本机已安装 Git,未安装时添加仓库会直接提示安装;
|
|
9
|
+
- 体积上限在下载过程中生效:开始前先检查磁盘剩余空间,超出上限立即停止并给出实际上限与实际体积,不再等下载结束才失败;
|
|
10
|
+
- 已扫描的仓库在安装时直接复用本地检出结果,不再重复下载;
|
|
11
|
+
- 升级提示:旧仓库首次安装会提示「仓库缓存缺失或已失效,请刷新该仓库后重试」,点一次刷新即可完成迁移。
|
|
12
|
+
|
|
13
|
+
[English](CHANGELOG.md)
|
|
14
|
+
|
|
3
15
|
## [1.2.34] - 2026-11-06
|
|
4
16
|
|
|
5
17
|
### 体验优化
|
package/README.en.md
CHANGED
|
@@ -73,7 +73,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
73
73
|
|
|
74
74
|
Restart DSH, refresh the page, and open **Settings → Skills**. To update, select **Check for updates** or run the installation command again.
|
|
75
75
|
|
|
76
|
-
- Plugin `1.2.
|
|
76
|
+
- Plugin `1.2.35` is tested with DeepSeek Harness `0.2.0-rc.1`, `0.2.0-rc.2`, `0.2.1-rc.1`, `0.2.1-rc.2`, `0.2.1-rc.3`, `0.2.1-rc.4`, `0.2.1-rc.5`, `0.2.2-rc.1`.
|
|
77
77
|
- Host compatibility: Full DeepSeek Harness `0.2.x` line (`>=0.2.0-rc.1 <0.3.0-0`), forwards-compatible with subsequent `0.2.x` RC releases without requiring routine plugin version bumps. Legacy versions and breaking changes in the `0.3.0` generation are rejected by the version gate. If you still use a `0.1.x` host, install plugin `1.1.23` instead of `@latest`.
|
|
78
78
|
- **Reverse proxy deployment**: Supports hosting DSH behind prefix-stripping reverse proxies (e.g. `/app/dsh/`). API and update endpoints resolve against the page base URI (`document.baseURI`).
|
|
79
79
|
|
|
@@ -159,7 +159,7 @@ The DeepSeek Harness loader enforces strict identifier policies:
|
|
|
159
159
|
|
|
160
160
|
- The header **Check for updates** updates the plugin itself; repository **Refresh** checks subscribed skill contents without replacing local files.
|
|
161
161
|
- Repository installation can target user DSH skills, Shared Agent (`~/.agents/skills`, overridable via `DSH_AGENTS_HOME`), or `<project>/.dsh/skills` for registered workspaces. Unsafe paths and external agent roots (Codex, Claude) are locked against writes.
|
|
162
|
-
-
|
|
162
|
+
- All repository sources are fetched through Git on demand: only directories containing `SKILL.md` are downloaded, so README images and other unrelated assets are skipped (a subdirectory narrows the scope further). Without a usable local Git, repositories cannot be added and the plugin asks you to install Git.
|
|
163
163
|
- Local edits are detected and require explicit confirmation before overwriting; backup history is preserved for rollback.
|
|
164
164
|
|
|
165
165
|
## Supported Agent directories
|
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
73
73
|
|
|
74
74
|
安装后重启 DSH 并刷新页面,打开「设置 → 技能」即可使用。更新时可点击「检查更新」,或重新执行安装命令。
|
|
75
75
|
|
|
76
|
-
- `1.2.
|
|
76
|
+
- `1.2.35` 已验证兼容 DeepSeek Harness `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3`、`0.2.1-rc.4`、`0.2.1-rc.5`、`0.2.2-rc.1`。
|
|
77
77
|
- 宿主支持范围:DeepSeek Harness `0.2.x` 全系列(`>=0.2.0-rc.1 <0.3.0-0`),自动支持后续全部 0.2.x RC 版本,同代宿主升级无需重复等待发版。早期版本与 `0.3.0` 代际破坏性更新由版本门拦截。仍在使用 `0.1.x` 的,请安装插件 `1.1.23`,不要装 `@latest`。
|
|
78
78
|
- **反向代理部署**:支持将 DSH 部署在带路径前缀的反向代理后面(如 `/app/dsh/`),插件 API 统一自动按页面基址(`document.baseURI`)相对解析。
|
|
79
79
|
|
|
@@ -159,7 +159,7 @@ DeepSeek Harness 宿主加载机制对技能标识符遵循规范命名约束:
|
|
|
159
159
|
|
|
160
160
|
- 顶栏「检查更新」用于更新技能管理器插件自身;仓库区「刷新」仅检查技能仓库内容更新,不自动覆盖本地。
|
|
161
161
|
- 仓库安装可以写入用户目录下的 DSH skills 或公共 Agent(`~/.agents/skills`,可用 `DSH_AGENTS_HOME` 覆盖),也可以写入已登记工作区的 `<项目根>/.dsh/skills`。选择器显示实际配置路径,并禁用不安全路径;缺少的目录仅在确认安装后创建。不能安装到 Codex、Claude 等其他 Agent 目录。
|
|
162
|
-
-
|
|
162
|
+
- 所有仓库来源统一走 Git 按需检出:只下载包含 `SKILL.md` 的技能目录,仓库首页配图等无关内容不参与下载(指定子目录可进一步缩小范围);本机没有可用 Git 时无法添加仓库,会直接提示先安装 Git。
|
|
163
163
|
- 更新前检测本地修改,覆盖必须明确确认;历史备份不自动清理,可随时恢复上一版。
|
|
164
164
|
|
|
165
165
|
## 支持的 Agent 目录
|
package/README.zh-CN.md
CHANGED
|
@@ -73,7 +73,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
|
|
|
73
73
|
|
|
74
74
|
安装后重启 DSH 并刷新页面,打开「设置 → 技能」即可使用。更新时可点击「检查更新」,或重新执行安装命令。
|
|
75
75
|
|
|
76
|
-
- `1.2.
|
|
76
|
+
- `1.2.35` 已验证兼容 DeepSeek Harness `0.2.0-rc.1`、`0.2.0-rc.2`、`0.2.1-rc.1`、`0.2.1-rc.2`、`0.2.1-rc.3`、`0.2.1-rc.4`、`0.2.1-rc.5`、`0.2.2-rc.1`。
|
|
77
77
|
- 宿主支持范围:DeepSeek Harness `0.2.x` 全系列(`>=0.2.0-rc.1 <0.3.0-0`),自动支持后续全部 0.2.x RC 版本,同代宿主升级无需重复等待发版。早期版本与 `0.3.0` 代际破坏性更新由版本门拦截。仍在使用 `0.1.x` 的,请安装插件 `1.1.23`,不要装 `@latest`。
|
|
78
78
|
- **反向代理部署**:支持将 DSH 部署在带路径前缀的反向代理后面(如 `/app/dsh/`),插件 API 统一自动按页面基址(`document.baseURI`)相对解析。
|
|
79
79
|
|
|
@@ -159,7 +159,7 @@ DeepSeek Harness 宿主加载机制对技能标识符遵循规范命名约束:
|
|
|
159
159
|
|
|
160
160
|
- 顶栏「检查更新」用于更新技能管理器插件自身;仓库区「刷新」仅检查技能仓库内容更新,不自动覆盖本地。
|
|
161
161
|
- 仓库安装可以写入用户目录下的 DSH skills 或公共 Agent(`~/.agents/skills`,可用 `DSH_AGENTS_HOME` 覆盖),也可以写入已登记工作区的 `<项目根>/.dsh/skills`。选择器显示实际配置路径,并禁用不安全路径;缺少的目录仅在确认安装后创建。不能安装到 Codex、Claude 等其他 Agent 目录。
|
|
162
|
-
-
|
|
162
|
+
- 所有仓库来源统一走 Git 按需检出:只下载包含 `SKILL.md` 的技能目录,仓库首页配图等无关内容不参与下载(指定子目录可进一步缩小范围);本机没有可用 Git 时无法添加仓库,会直接提示先安装 Git。
|
|
163
163
|
- 更新前检测本地修改,覆盖必须明确确认;历史备份不自动清理,可随时恢复上一版。
|
|
164
164
|
|
|
165
165
|
## 支持的 Agent 目录
|
package/lib/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
window.__ModuleLoader__.load({ id: "@michengai/dsh-skills-manager", factory: (require) => {
|
|
2
2
|
var module = { exports: {} };
|
|
3
3
|
var exports = module.exports;
|
|
4
|
-
"use strict";var Fo=Object.create;var Xr=Object.defineProperty;var Vo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var Ko=Object.getPrototypeOf,qo=Object.prototype.hasOwnProperty;var Qo=(t,e,i,l)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of Wo(e))!qo.call(t,p)&&p!==i&&Xr(t,p,{get:()=>e[p],enumerable:!(l=Vo(e,p))||l.enumerable});return t};var na=(t,e,i)=>(i=t!=null?Fo(Ko(t)):{},Qo(e||!t||!t.__esModule?Xr(i,"default",{value:t,enumerable:!0}):i,t));var Fa=na(require("react"),1);var Dr={zh:{"repo.catalog.unknownLocation":"\u4F4D\u7F6E\u672A\u77E5","repo.catalog.reassociate":"\u9009\u62E9\u4F4D\u7F6E\u5E76\u91CD\u5173\u8054\u5B89\u88C5","repo.catalog.unknownHint":"\u6709\u65E7\u76EE\u5F55\u5B89\u88C5\u8BB0\u5F55\u4F4D\u7F6E\u672A\u77E5\uFF1B\u627E\u5230\u5BF9\u5E94\u6280\u80FD\u540E\u53EF\u9009\u62E9\u4F4D\u7F6E\u91CD\u5173\u8054\u5B89\u88C5\u3002","repo.catalog.loadMore":"\u518D\u52A0\u8F7D 10 \u6761","repo.catalog.installRefreshWarning":"\u6280\u80FD\u5DF2\u5B89\u88C5\u5E76\u542F\u7528\uFF0C\u4F46\u5217\u8868\u5237\u65B0\u5931\u8D25\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\u53D1\u73B0\u9875\uFF1B\u4E0D\u8981\u91CD\u590D\u5B89\u88C5\u3002","repo.catalog.uninstallRefreshWarning":"\u6280\u80FD\u5DF2\u79FB\u5165\u56DE\u6536\u7AD9\uFF0C\u4F46\u5217\u8868\u5237\u65B0\u5931\u8D25\u3002\u8BF7\u5237\u65B0\u6280\u80FD\u5217\u8868\u6216\u91CD\u65B0\u6253\u5F00\u8BBE\u7F6E\u9875\uFF1B\u4E0D\u8981\u91CD\u590D\u5378\u8F7D\u3002","repo.installElsewhere":"\u5B89\u88C5\u5230\u5176\u4ED6\u4F4D\u7F6E","repo.destinationUnavailable":"\u539F\u9879\u76EE\u672A\u767B\u8BB0\u6216\u76EE\u5F55\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u7BA1\u7406\u6B64\u526F\u672C\u3002\u8BF7\u5728\u4FA7\u680F\u91CD\u65B0\u767B\u8BB0\u539F\u9879\u76EE\u540E\u5237\u65B0\u3002","repo.scope.user":"\u5168\u5C40","repo.scope.project":"\u9879\u76EE","repo.source.dsh":"DSH","repo.source.agents":"Shared Agent","repo.refreshing":"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026","repo.refreshed":"\u5237\u65B0\u4E0E\u66F4\u65B0\u68C0\u67E5\u5B8C\u6210","repo.partialRefreshFailed":"\u90E8\u5206\u4ED3\u5E93\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF08{names}\uFF09\uFF0C\u5176\u4F59\u5DF2\u5B8C\u6210","repo.detailLoading":"\u6B63\u5728\u8BFB\u53D6\u6280\u80FD\u8BF4\u660E\u2026","repo.local":"\u672C\u673A","repo.source":"\u5B89\u88C5\u6765\u6E90","repo.update":"\u53EF\u66F4\u65B0","repo.review":"\u67E5\u770B\u66F4\u65B0","repo.rollback":"\u6062\u590D\u4E0A\u4E00\u7248","repo.confirmUpdate":"\u5907\u4EFD\u5E76\u66F4\u65B0","repo.confirmRollback":"\u5907\u4EFD\u5E76\u6062\u590D","repo.overwrite":"\u5907\u4EFD\u672C\u5730\u4FEE\u6539\u5E76\u66FF\u6362","repo.modified":"\u68C0\u6D4B\u5230\u672C\u5730\u4FEE\u6539\u3002\u66FF\u6362\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\uFF0C\u539F\u6587\u4EF6\u5C06\u5B8C\u6574\u5907\u4EFD\uFF0C\u53EF\u6062\u590D\u3002","repo.noChanges":"\u6587\u4EF6\u5185\u5BB9\u6CA1\u6709\u53D8\u5316\u3002","repo.added":"\u65B0\u589E","repo.removed":"\u5220\u9664","repo.modifiedFile":"\u4FEE\u6539","repo.updating":"\u66F4\u65B0\u4E2D\u2026","repo.updateProgress":"\u6B63\u5728\u6821\u9A8C\u3001\u5907\u4EFD\u5E76\u66FF\u6362\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.updateSuccess":"\u66F4\u65B0\u5B8C\u6210\uFF0C\u5DF2\u4FDD\u7559\u4E0A\u4E00\u7248\u5907\u4EFD\u3002","repo.rollbackSuccess":"\u5DF2\u6062\u590D\u4E0A\u4E00\u7248\u3002","repo.manageUpdate":"\u7BA1\u7406\u66F4\u65B0","error.repo.modified":"\u5B58\u5728\u672C\u5730\u4FEE\u6539\uFF0C\u8BF7\u9884\u89C8\u5E76\u786E\u8BA4\u5907\u4EFD\u540E\u66FF\u6362\u3002","scope.repositories":"\u53D1\u73B0\u6280\u80FD","repo.add":"\u6DFB\u52A0\u4ED3\u5E93","repo.manage":"\u7BA1\u7406\u4ED3\u5E93","repo.refresh":"\u5237\u65B0\u5E76\u68C0\u67E5\u66F4\u65B0","repo.save":"\u6DFB\u52A0\u5E76\u626B\u63CF","repo.subsource.github":"Git \u4ED3\u5E93","repo.subsource.skillsh":"skills.sh","repo.subsource.clawhub":"ClawHub","repo.subsource.popular":"\u70ED\u95E8\u4E0B\u8F7D Top 100","repo.catalog.states":"\u5168\u90E8\u72B6\u6001","repo.catalog.uninstalled":"\u672A\u5B89\u88C5","repo.catalog.searchClawhub":"\u641C\u7D22 ClawHub \u6280\u80FD","repo.catalog.searchLoading":"\u6B63\u5728\u641C\u7D22 ClawHub\u2026","repo.catalog.searchEmpty":"\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 ClawHub \u6280\u80FD\u3002","repo.catalog.searchError":"ClawHub \u641C\u7D22\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002","repo.catalog.searchSkillsh":"\u641C\u7D22 skills.sh \u6280\u80FD","repo.catalog.skillshLoading":"\u6B63\u5728\u641C\u7D22 skills.sh\u2026","repo.catalog.skillshEmpty":"\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 skills.sh \u6280\u80FD\u3002","repo.catalog.skillshError":"skills.sh \u641C\u7D22\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002","repo.sort.installs":"\u5B89\u88C5\u91CF\u4ECE\u9AD8\u5230\u4F4E","repo.sort.name":"\u540D\u79F0 A-Z","repo.subsource.catalogNote":"ClawHub \u4E0B\u8F7D\u91CF\u699C \xB7 \u6700\u591A 100 \u6761\uFF0C\u4EC5\u5BF9\u5F53\u524D\u663E\u793A\u6761\u76EE\u6392\u5E8F","repo.catalog.empty":"ClawHub \u5F53\u524D\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002","repo.catalog.loading":"\u6B63\u5728\u52A0\u8F7D ClawHub \u699C\u5355\u2026","repo.catalog.loadError":"ClawHub \u699C\u5355\u52A0\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002","repo.catalog.retry":"\u91CD\u8BD5","repo.catalog.loadingMore":"\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026","repo.catalog.downloads":"\u4E0B\u8F7D","repo.catalog.installs":"\u5B89\u88C5","repo.catalog.stars":"\u661F\u6807","repo.hint":"\u4ECE\u5DF2\u8BA2\u9605\u7684 Git \u4ED3\u5E93\u6216\u7B2C\u4E09\u65B9\u76EE\u5F55\u53D1\u73B0\u6280\u80FD\uFF0C\u5B89\u88C5\u5230\u7528\u6237\u76EE\u5F55\u7684 DSH skills \u6216 Shared Agent\uFF0C\u6216\u5DF2\u767B\u8BB0\u9879\u76EE\u7684 .dsh/skills\u3002","repo.footer":"\u5237\u65B0\u4F1A\u68C0\u67E5\u4ED3\u5E93\u4E0E\u6280\u80FD\u66F4\u65B0\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u66FF\u6362\u672C\u5730\u6587\u4EF6\u3002\u5386\u53F2\u5907\u4EFD\u4E0D\u4F1A\u81EA\u52A8\u6E05\u7406\uFF0C\u5360\u7528\u7A7A\u95F4\u4F1A\u968F\u66F4\u65B0\u589E\u52A0\u3002","repo.search":"\u641C\u7D22\u4ED3\u5E93\u6280\u80FD","repo.all":"\u5168\u90E8\u4ED3\u5E93","repo.states":"\u5168\u90E8\u72B6\u6001","repo.available":"\u53EF\u5B89\u88C5","repo.installed":"\u5DF2\u5B89\u88C5","repo.conflict":"\u540C\u540D\u51B2\u7A81","repo.invalid":"\u683C\u5F0F\u65E0\u6548","repo.install":"\u5B89\u88C5","repo.detail":"\u8BE6\u60C5","repo.installing":"\u5B89\u88C5\u4E2D\u2026","repo.installProgress":"\u6B63\u5728\u8BFB\u53D6\u5E76\u6821\u9A8C\u6280\u80FD\u6587\u4EF6\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.installSuccess":"\u5B89\u88C5\u6210\u529F\u5E76\u5DF2\u542F\u7528\uFF0C\u53EF\u5728\u5BF9\u8BDD\u4E2D\u7528\u659C\u6760\u8C03\u7528\u3002","repo.installDestination":"\u5B89\u88C5\u4F4D\u7F6E","repo.rootDsh":"DSH skills","repo.rootAgents":"Shared Agent","repo.rootDshHint":"\u4EC5\u4F9B DSH \u4F7F\u7528\uFF0C\u8DEF\u5F84\u4EE5\u5F53\u524D DSH_HOME \u4E3A\u51C6\u3002","repo.rootExternalHint":"\u4F7F\u7528\u8BE5\u76EE\u5F55\u7684\u5176\u4ED6 Agent \u4E5F\u80FD\u8BFB\u53D6\u6B64\u6280\u80FD\u3002","repo.installLocationsHint":"\u7528\u6237\u76EE\u5F55\u53EF\u9009 DSH skills \u6216 Shared Agent\uFF1B\u5DF2\u767B\u8BB0\u9879\u76EE\u4EC5\u5199\u5165 .dsh/skills\u3002\u9009\u62E9\u5668\u663E\u793A\u5B9E\u9645\u8DEF\u5F84\u3002\u7F3A\u5C11\u7684\u76EE\u5F55\u4F1A\u5728\u786E\u8BA4\u5B89\u88C5\u540E\u521B\u5EFA\u3002\u4E0D\u5B89\u5168\u7684\u8DEF\u5F84\u4E0D\u53EF\u9009\u3002","repo.uninstall":"\u5378\u8F7D","repo.confirmUninstall":"\u79FB\u81F3\u56DE\u6536\u7AD9","repo.uninstallHint":"\u4EC5\u5C06\u6B64\u4ED3\u5E93\u5DF2\u8DDF\u8E2A\u7684\u5B89\u88C5\u526F\u672C\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u5305\u62EC\u672C\u5730\u4FEE\u6539\u3002\u4E4B\u540E\u53EF\u5728\u56DE\u6536\u7AD9\u6062\u590D\u3002","repo.uninstallExternalWarning":"\u5378\u8F7D\u8BE5\u6280\u80FD\u4E5F\u4F1A\u5F71\u54CD\u4F7F\u7528\u6240\u9009\u76EE\u5F55\u7684\u5176\u4ED6 Agent\u3002","repo.uninstalling":"\u5378\u8F7D\u4E2D\u2026","repo.uninstallProgress":"\u6B63\u5728\u5C06\u5DF2\u5B89\u88C5\u7684\u6280\u80FD\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.uninstallSuccess":"\u6280\u80FD\u5DF2\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u53EF\u5728\u56DE\u6536\u7AD9\u6062\u590D\u3002","repo.empty":"\u8FD8\u6CA1\u6709\u6280\u80FD\u4ED3\u5E93\uFF0C\u6DFB\u52A0\u4E00\u4E2A Git \u4ED3\u5E93\u5F00\u59CB\u3002","repo.noSkills":"\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002\u53EF\u5237\u65B0\u4ED3\u5E93\u6216\u8C03\u6574\u7B5B\u9009\u3002","repo.url":"\u4ED3\u5E93\u5730\u5740","repo.urlPlaceholder":"owner/repo \u6216\u5B8C\u6574 Git \u5730\u5740","repo.ref":"\u5206\u652F\u6216\u6807\u7B7E\uFF08\u53EF\u9009\uFF09","repo.directory":"\u6280\u80FD\u5B50\u76EE\u5F55\uFF08\u53EF\u9009\uFF09","repo.defaultBranch":"\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\u5206\u652F","repo.defaultBranchLabel":"\u9ED8\u8BA4\u5206\u652F","repo.directoryHint":"\u4F8B\u5982 skills\uFF0C\u7559\u7A7A\u626B\u63CF\u6574\u4E2A\u4ED3\u5E93","repo.remove":"\u79FB\u9664\u4ED3\u5E93","repo.removeHint":"\u4EC5\u79FB\u9664\u4ED3\u5E93\u8BA2\u9605\uFF0C\u5DF2\u5B89\u88C5\u7684\u6280\u80FD\u4F1A\u4FDD\u7559\u3002","repo.removeConfirm":"\u786E\u8BA4\u79FB\u9664","repo.cancel":"\u53D6\u6D88","repo.close":"\u5173\u95ED","repo.loading":"\u5904\u7406\u4E2D\u2026","repo.success":"\u64CD\u4F5C\u5B8C\u6210","repo.installHint":"\u6280\u80FD\u53EF\u80FD\u5305\u542B\u811A\u672C\uFF0C\u8BF7\u5148\u67E5\u770B\u8BF4\u660E\u5E76\u786E\u8BA4\u4FE1\u4EFB\u6765\u6E90\u3002\u5B89\u88C5\u4F1A\u4FDD\u7559\u8D44\u6E90\u6587\u4EF6\uFF0C\u4E0D\u4F1A\u6267\u884C\u811A\u672C\u3002","repo.commit":"\u626B\u63CF\u7248\u672C","repo.notScanned":"\u5C1A\u672A\u626B\u63CF","repo.updated":"\u4E0A\u6B21\u5237\u65B0","repo.copyBody":"\u590D\u5236\u6B63\u6587","repo.copied":"\u5DF2\u590D\u5236","repo.copyFailed":"\u590D\u5236\u5931\u8D25","error.repo.tooLarge":"\u4ED3\u5E93\u8D85\u8FC7\u5927\u5C0F\u9650\u5236\uFF0C\u8BF7\u9009\u62E9\u8F83\u5C0F\u7684\u6280\u80FD\u4ED3\u5E93\u3002","error.repo.transientTooLarge":"\u4ED3\u5E93\u4E34\u65F6\u4E0B\u8F7D\u4F53\u79EF\u8FC7\u5927\uFF08\u5305\u542B Git \u5386\u53F2\u5BF9\u8C61\uFF09\uFF0C\u8BF7\u9009\u62E9\u8F83\u5C0F\u7684\u6280\u80FD\u4ED3\u5E93\u6216\u63D0\u9AD8\u4E34\u65F6\u5927\u5C0F\u9650\u5236\u3002","error.repo.noGit":"\u672A\u68C0\u6D4B\u5230 Git \u547D\u4EE4\u884C\u5DE5\u5177\uFF0C\u8BF7\u5148\u5B89\u88C5 Git \u540E\u91CD\u8BD5\u3002","error.repo.installState":"\u6280\u80FD\u6587\u4EF6\u5DF2\u4FDD\u7559\uFF0C\u4F46\u6765\u6E90\u72B6\u6001\u672A\u786E\u8BA4\u3002\u8BF7\u6062\u590D\u78C1\u76D8\u5199\u5165\u540E\u91CD\u65B0\u6253\u5F00\u4ED3\u5E93\u9875\uFF0C\u7CFB\u7EDF\u4F1A\u6821\u9A8C\u6587\u4EF6\u5E76\u6062\u590D\u8BB0\u5F55\u3002","error.repo.capacity":"\u5B89\u88C5\u6765\u6E90\u72B6\u6001\u8D85\u8FC7 16 MiB\u3002\u8BF7\u5148\u6E05\u7406\u4E0D\u9700\u8981\u7684\u56DE\u6536\u8BB0\u5F55\u6216\u65E7\u8EAB\u4EFD\uFF0C\u4ECD\u6709\u6548\u7684\u5B89\u88C5\u526F\u672C\u4F1A\u4FDD\u7559\u3002","error.repo.invalid":"\u4ED3\u5E93\u5730\u5740\u3001\u8DEF\u5F84\u6216\u5185\u5BB9\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u8F93\u5165\u53CA\u4ED3\u5E93\u5927\u5C0F\u3002","error.repo.network":"\u4ED3\u5E93\u8BBF\u95EE\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002\u4E0A\u6B21\u5217\u8868\u5DF2\u4FDD\u7559\u3002","error.repo.state":"\u4ED3\u5E93\u72B6\u6001\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u8BF7\u4FDD\u7559\u6587\u4EF6\u5E76\u4FEE\u590D\u540E\u91CD\u8BD5\u3002","error.repo.conflict":"\u540C\u540D\u6280\u80FD\u5DF2\u5B58\u5728\u6216\u5B89\u88C5\u5931\u8D25\uFF0C\u672C\u5730\u6587\u4EF6\u672A\u88AB\u8986\u76D6\u3002","error.repo.enableState":"\u6280\u80FD\u6587\u4EF6\u5DF2\u64A4\u9500\uFF0C\u4F46\u542F\u7528\u72B6\u6001\u65E0\u6CD5\u5199\u5165\u3002\u8BF7\u6062\u590D\u78C1\u76D8\u5199\u5165\u540E\u91CD\u8BD5\u5B89\u88C5\u3002"},en:{"repo.catalog.unknownLocation":"Location unknown","repo.catalog.reassociate":"Choose destination and reinstall to reassociate","repo.catalog.unknownHint":"Some legacy catalog installs have unknown locations. Find the skill and choose a destination to reinstall and reassociate.","repo.catalog.loadMore":"Load 10 more","repo.catalog.installRefreshWarning":"Installed and enabled, but the list could not refresh. Reopen Discover Skills; do not install again.","repo.catalog.uninstallRefreshWarning":"Moved to trash, but the list could not refresh. Refresh the skill list or reopen Settings; do not uninstall again.","repo.installElsewhere":"Install elsewhere","repo.destinationUnavailable":"The original project is unregistered or unavailable. Re-register it in the sidebar and refresh to manage this copy.","repo.scope.user":"Global","repo.scope.project":"Project","repo.source.dsh":"DSH","repo.source.agents":"Shared Agent","repo.refreshing":"Checking updates\u2026","repo.refreshed":"Refresh and update check completed","repo.partialRefreshFailed":"Failed to check updates for some repositories ({names}); others completed.","repo.detailLoading":"Loading skill instructions\u2026","repo.local":"Local","repo.source":"Installed from","repo.update":"Update available","repo.review":"Review update","repo.rollback":"Restore previous version","repo.confirmUpdate":"Back up and update","repo.confirmRollback":"Back up and restore","repo.overwrite":"Back up local edits and replace","repo.modified":"Local edits detected. Replacement overwrites current files and keeps a complete backup for restoration.","repo.noChanges":"No file changes.","repo.added":"Added","repo.removed":"Removed","repo.modifiedFile":"Modified","repo.updating":"Updating\u2026","repo.updateProgress":"Verifying, backing up and replacing files. Please wait\u2026","repo.updateSuccess":"Updated. Previous version backed up.","repo.rollbackSuccess":"Previous version restored.","repo.manageUpdate":"Manage updates","error.repo.modified":"Local edits detected. Review and confirm replacement with a backup.","scope.repositories":"Discover Skills","repo.add":"Add repository","repo.manage":"Manage repositories","repo.refresh":"Refresh & check updates","repo.save":"Add and scan","repo.subsource.github":"Git Repos","repo.subsource.skillsh":"skills.sh","repo.subsource.clawhub":"ClawHub","repo.subsource.popular":"Top 100 by downloads","repo.catalog.states":"All statuses","repo.catalog.uninstalled":"Not installed","repo.catalog.searchClawhub":"Search ClawHub skills","repo.catalog.searchLoading":"Searching ClawHub\u2026","repo.catalog.searchEmpty":"No matching ClawHub skills were found.","repo.catalog.searchError":"ClawHub search failed. Please retry.","repo.catalog.searchSkillsh":"Search skills.sh","repo.catalog.skillshLoading":"Searching skills.sh\u2026","repo.catalog.skillshEmpty":"No matching skills.sh skills were found.","repo.catalog.skillshError":"skills.sh search failed. Please retry.","repo.sort.installs":"Installs: high to low","repo.sort.name":"Name A\u2013Z","repo.subsource.catalogNote":"ClawHub download ranking \xB7 Up to 100; sort only displayed items","repo.catalog.empty":"No matching skills were returned by ClawHub.","repo.catalog.loading":"Loading ClawHub ranking\u2026","repo.catalog.loadError":"Failed to load the ClawHub ranking. Check your connection and retry.","repo.catalog.retry":"Retry","repo.catalog.loadingMore":"Loading more\u2026","repo.catalog.downloads":"downloads","repo.catalog.installs":"installs","repo.catalog.stars":"stars","repo.hint":"Discover skills from subscribed Git repositories or community registries, and install them to user DSH skills or Shared Agent, or a registered project's .dsh/skills.","repo.footer":"Refresh checks repositories and skill updates without replacing local files. Historical backups are not cleaned automatically and use additional disk space.","repo.search":"Search repository skills","repo.all":"All repositories","repo.states":"All statuses","repo.available":"Available","repo.installed":"Installed","repo.conflict":"Name conflict","repo.invalid":"Invalid format","repo.install":"Install","repo.detail":"Details","repo.installing":"Installing\u2026","repo.installProgress":"Reading and verifying skill files. Please wait\u2026","repo.installSuccess":"Installed and enabled. Invoke it from the slash menu.","repo.installDestination":"Installation destination","repo.rootDsh":"DSH skills","repo.rootAgents":"Shared Agent","repo.rootDshHint":"For DSH only. The path uses your current DSH_HOME.","repo.rootExternalHint":"Other agents using this directory can read this skill too.","repo.installLocationsHint":"User directories offer DSH skills or Shared Agent; registered projects write only to .dsh/skills. The chooser shows actual paths. Missing directories are created after confirmation. Unsafe paths cannot be selected.","repo.uninstall":"Uninstall","repo.confirmUninstall":"Move to Trash","repo.uninstallHint":"Move only this repository\u2019s tracked installed copy, including local edits, to Trash. You can restore it from Trash later.","repo.uninstallExternalWarning":"Uninstalling this skill also affects other agents that use the selected directory.","repo.uninstalling":"Uninstalling\u2026","repo.uninstallProgress":"Moving the installed skill to Trash. Please wait\u2026","repo.uninstallSuccess":"Skill moved to Trash. You can restore it from Trash.","repo.empty":"No repositories yet. Add a Git repository to get started.","repo.noSkills":"No matching skills. Refresh the repository or adjust your filters.","repo.url":"Repository URL","repo.urlPlaceholder":"owner/repo or full Git repository URL","repo.ref":"Branch or tag (optional)","repo.directory":"Skill subdirectory (optional)","repo.defaultBranch":"Leave empty for the default branch","repo.defaultBranchLabel":"default branch","repo.directoryHint":"For example: skills. Leave empty to scan the repository.","repo.remove":"Remove repository","repo.removeHint":"This removes the subscription only. Installed skills are kept.","repo.removeConfirm":"Confirm removal","repo.cancel":"Cancel","repo.close":"Close","repo.loading":"Working\u2026","repo.success":"Completed","repo.installHint":"Skills may include scripts. Read the instructions and trust the source before installing. Resources are copied; scripts are not executed.","repo.commit":"Snapshot version","repo.notScanned":"Not scanned yet","repo.updated":"Last refreshed","repo.copyBody":"Copy body","repo.copied":"Copied","repo.copyFailed":"Copy failed","error.repo.tooLarge":"Repository exceeds size limit. Choose a smaller repository.","error.repo.transientTooLarge":"Repository transient download size exceeds limit (including Git objects). Choose a smaller repository or increase temporary limit.","error.repo.noGit":"Git command-line tool not found. Please install Git and retry.","error.repo.installState":"Skill files were preserved but source tracking could not be saved. Restore disk write access and reopen Repositories to verify files and recover the record.","error.repo.capacity":"Source tracking exceeds 16 MiB. Remove unneeded Trash records or legacy identities first. Active installation copies are kept.","error.repo.invalid":"Invalid repository URL, path or contents. Check the input and repository size.","error.repo.network":"Repository request failed. Check your connection or retry later. The previous catalog was kept.","error.repo.state":"Repository state is corrupt or unreadable. Preserve the file and repair it before retrying.","error.repo.conflict":"A skill with this name exists or installation failed. Local files were not overwritten.","error.repo.enableState":"The copied skill was removed because its enabled state could not be saved. Restore disk write access and install again."}},Zo=`
|
|
4
|
+
"use strict";var Fo=Object.create;var Xr=Object.defineProperty;var Vo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var Ko=Object.getPrototypeOf,qo=Object.prototype.hasOwnProperty;var Qo=(t,e,i,l)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of Wo(e))!qo.call(t,p)&&p!==i&&Xr(t,p,{get:()=>e[p],enumerable:!(l=Vo(e,p))||l.enumerable});return t};var na=(t,e,i)=>(i=t!=null?Fo(Ko(t)):{},Qo(e||!t||!t.__esModule?Xr(i,"default",{value:t,enumerable:!0}):i,t));var Fa=na(require("react"),1);var Dr={zh:{"repo.catalog.unknownLocation":"\u4F4D\u7F6E\u672A\u77E5","repo.catalog.reassociate":"\u9009\u62E9\u4F4D\u7F6E\u5E76\u91CD\u5173\u8054\u5B89\u88C5","repo.catalog.unknownHint":"\u6709\u65E7\u76EE\u5F55\u5B89\u88C5\u8BB0\u5F55\u4F4D\u7F6E\u672A\u77E5\uFF1B\u627E\u5230\u5BF9\u5E94\u6280\u80FD\u540E\u53EF\u9009\u62E9\u4F4D\u7F6E\u91CD\u5173\u8054\u5B89\u88C5\u3002","repo.catalog.loadMore":"\u518D\u52A0\u8F7D 10 \u6761","repo.catalog.installRefreshWarning":"\u6280\u80FD\u5DF2\u5B89\u88C5\u5E76\u542F\u7528\uFF0C\u4F46\u5217\u8868\u5237\u65B0\u5931\u8D25\u3002\u8BF7\u91CD\u65B0\u6253\u5F00\u53D1\u73B0\u9875\uFF1B\u4E0D\u8981\u91CD\u590D\u5B89\u88C5\u3002","repo.catalog.uninstallRefreshWarning":"\u6280\u80FD\u5DF2\u79FB\u5165\u56DE\u6536\u7AD9\uFF0C\u4F46\u5217\u8868\u5237\u65B0\u5931\u8D25\u3002\u8BF7\u5237\u65B0\u6280\u80FD\u5217\u8868\u6216\u91CD\u65B0\u6253\u5F00\u8BBE\u7F6E\u9875\uFF1B\u4E0D\u8981\u91CD\u590D\u5378\u8F7D\u3002","repo.installElsewhere":"\u5B89\u88C5\u5230\u5176\u4ED6\u4F4D\u7F6E","repo.destinationUnavailable":"\u539F\u9879\u76EE\u672A\u767B\u8BB0\u6216\u76EE\u5F55\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u7BA1\u7406\u6B64\u526F\u672C\u3002\u8BF7\u5728\u4FA7\u680F\u91CD\u65B0\u767B\u8BB0\u539F\u9879\u76EE\u540E\u5237\u65B0\u3002","repo.scope.user":"\u5168\u5C40","repo.scope.project":"\u9879\u76EE","repo.source.dsh":"DSH","repo.source.agents":"Shared Agent","repo.refreshing":"\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026","repo.refreshed":"\u5237\u65B0\u4E0E\u66F4\u65B0\u68C0\u67E5\u5B8C\u6210","repo.partialRefreshFailed":"\u90E8\u5206\u4ED3\u5E93\u68C0\u67E5\u66F4\u65B0\u5931\u8D25\uFF08{names}\uFF09\uFF0C\u5176\u4F59\u5DF2\u5B8C\u6210","repo.detailLoading":"\u6B63\u5728\u8BFB\u53D6\u6280\u80FD\u8BF4\u660E\u2026","repo.local":"\u672C\u673A","repo.source":"\u5B89\u88C5\u6765\u6E90","repo.update":"\u53EF\u66F4\u65B0","repo.review":"\u67E5\u770B\u66F4\u65B0","repo.rollback":"\u6062\u590D\u4E0A\u4E00\u7248","repo.confirmUpdate":"\u5907\u4EFD\u5E76\u66F4\u65B0","repo.confirmRollback":"\u5907\u4EFD\u5E76\u6062\u590D","repo.overwrite":"\u5907\u4EFD\u672C\u5730\u4FEE\u6539\u5E76\u66FF\u6362","repo.modified":"\u68C0\u6D4B\u5230\u672C\u5730\u4FEE\u6539\u3002\u66FF\u6362\u4F1A\u8986\u76D6\u5F53\u524D\u6587\u4EF6\uFF0C\u539F\u6587\u4EF6\u5C06\u5B8C\u6574\u5907\u4EFD\uFF0C\u53EF\u6062\u590D\u3002","repo.noChanges":"\u6587\u4EF6\u5185\u5BB9\u6CA1\u6709\u53D8\u5316\u3002","repo.added":"\u65B0\u589E","repo.removed":"\u5220\u9664","repo.modifiedFile":"\u4FEE\u6539","repo.updating":"\u66F4\u65B0\u4E2D\u2026","repo.updateProgress":"\u6B63\u5728\u6821\u9A8C\u3001\u5907\u4EFD\u5E76\u66FF\u6362\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.updateSuccess":"\u66F4\u65B0\u5B8C\u6210\uFF0C\u5DF2\u4FDD\u7559\u4E0A\u4E00\u7248\u5907\u4EFD\u3002","repo.rollbackSuccess":"\u5DF2\u6062\u590D\u4E0A\u4E00\u7248\u3002","repo.manageUpdate":"\u7BA1\u7406\u66F4\u65B0","error.repo.modified":"\u5B58\u5728\u672C\u5730\u4FEE\u6539\uFF0C\u8BF7\u9884\u89C8\u5E76\u786E\u8BA4\u5907\u4EFD\u540E\u66FF\u6362\u3002","scope.repositories":"\u53D1\u73B0\u6280\u80FD","repo.add":"\u6DFB\u52A0\u4ED3\u5E93","repo.manage":"\u7BA1\u7406\u4ED3\u5E93","repo.refresh":"\u5237\u65B0\u5E76\u68C0\u67E5\u66F4\u65B0","repo.save":"\u6DFB\u52A0\u5E76\u626B\u63CF","repo.subsource.github":"Git \u4ED3\u5E93","repo.subsource.skillsh":"skills.sh","repo.subsource.clawhub":"ClawHub","repo.subsource.popular":"\u70ED\u95E8\u4E0B\u8F7D Top 100","repo.catalog.states":"\u5168\u90E8\u72B6\u6001","repo.catalog.uninstalled":"\u672A\u5B89\u88C5","repo.catalog.searchClawhub":"\u641C\u7D22 ClawHub \u6280\u80FD","repo.catalog.searchLoading":"\u6B63\u5728\u641C\u7D22 ClawHub\u2026","repo.catalog.searchEmpty":"\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 ClawHub \u6280\u80FD\u3002","repo.catalog.searchError":"ClawHub \u641C\u7D22\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002","repo.catalog.searchSkillsh":"\u641C\u7D22 skills.sh \u6280\u80FD","repo.catalog.skillshLoading":"\u6B63\u5728\u641C\u7D22 skills.sh\u2026","repo.catalog.skillshEmpty":"\u6CA1\u6709\u627E\u5230\u5339\u914D\u7684 skills.sh \u6280\u80FD\u3002","repo.catalog.skillshError":"skills.sh \u641C\u7D22\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5\u3002","repo.sort.installs":"\u5B89\u88C5\u91CF\u4ECE\u9AD8\u5230\u4F4E","repo.sort.name":"\u540D\u79F0 A-Z","repo.subsource.catalogNote":"ClawHub \u4E0B\u8F7D\u91CF\u699C \xB7 \u6700\u591A 100 \u6761\uFF0C\u4EC5\u5BF9\u5F53\u524D\u663E\u793A\u6761\u76EE\u6392\u5E8F","repo.catalog.empty":"ClawHub \u5F53\u524D\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002","repo.catalog.loading":"\u6B63\u5728\u52A0\u8F7D ClawHub \u699C\u5355\u2026","repo.catalog.loadError":"ClawHub \u699C\u5355\u52A0\u8F7D\u5931\u8D25\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002","repo.catalog.retry":"\u91CD\u8BD5","repo.catalog.loadingMore":"\u6B63\u5728\u52A0\u8F7D\u66F4\u591A\u2026","repo.catalog.downloads":"\u4E0B\u8F7D","repo.catalog.installs":"\u5B89\u88C5","repo.catalog.stars":"\u661F\u6807","repo.hint":"\u4ECE\u5DF2\u8BA2\u9605\u7684 Git \u4ED3\u5E93\u6216\u7B2C\u4E09\u65B9\u76EE\u5F55\u53D1\u73B0\u6280\u80FD\uFF0C\u5B89\u88C5\u5230\u7528\u6237\u76EE\u5F55\u7684 DSH skills \u6216 Shared Agent\uFF0C\u6216\u5DF2\u767B\u8BB0\u9879\u76EE\u7684 .dsh/skills\u3002","repo.footer":"\u5237\u65B0\u4F1A\u68C0\u67E5\u4ED3\u5E93\u4E0E\u6280\u80FD\u66F4\u65B0\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u66FF\u6362\u672C\u5730\u6587\u4EF6\u3002\u5386\u53F2\u5907\u4EFD\u4E0D\u4F1A\u81EA\u52A8\u6E05\u7406\uFF0C\u5360\u7528\u7A7A\u95F4\u4F1A\u968F\u66F4\u65B0\u589E\u52A0\u3002","repo.search":"\u641C\u7D22\u4ED3\u5E93\u6280\u80FD","repo.all":"\u5168\u90E8\u4ED3\u5E93","repo.states":"\u5168\u90E8\u72B6\u6001","repo.available":"\u53EF\u5B89\u88C5","repo.installed":"\u5DF2\u5B89\u88C5","repo.conflict":"\u540C\u540D\u51B2\u7A81","repo.invalid":"\u683C\u5F0F\u65E0\u6548","repo.install":"\u5B89\u88C5","repo.detail":"\u8BE6\u60C5","repo.installing":"\u5B89\u88C5\u4E2D\u2026","repo.installProgress":"\u6B63\u5728\u8BFB\u53D6\u5E76\u6821\u9A8C\u6280\u80FD\u6587\u4EF6\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.installSuccess":"\u5B89\u88C5\u6210\u529F\u5E76\u5DF2\u542F\u7528\uFF0C\u53EF\u5728\u5BF9\u8BDD\u4E2D\u7528\u659C\u6760\u8C03\u7528\u3002","repo.installDestination":"\u5B89\u88C5\u4F4D\u7F6E","repo.rootDsh":"DSH skills","repo.rootAgents":"Shared Agent","repo.rootDshHint":"\u4EC5\u4F9B DSH \u4F7F\u7528\uFF0C\u8DEF\u5F84\u4EE5\u5F53\u524D DSH_HOME \u4E3A\u51C6\u3002","repo.rootExternalHint":"\u4F7F\u7528\u8BE5\u76EE\u5F55\u7684\u5176\u4ED6 Agent \u4E5F\u80FD\u8BFB\u53D6\u6B64\u6280\u80FD\u3002","repo.installLocationsHint":"\u7528\u6237\u76EE\u5F55\u53EF\u9009 DSH skills \u6216 Shared Agent\uFF1B\u5DF2\u767B\u8BB0\u9879\u76EE\u4EC5\u5199\u5165 .dsh/skills\u3002\u9009\u62E9\u5668\u663E\u793A\u5B9E\u9645\u8DEF\u5F84\u3002\u7F3A\u5C11\u7684\u76EE\u5F55\u4F1A\u5728\u786E\u8BA4\u5B89\u88C5\u540E\u521B\u5EFA\u3002\u4E0D\u5B89\u5168\u7684\u8DEF\u5F84\u4E0D\u53EF\u9009\u3002","repo.uninstall":"\u5378\u8F7D","repo.confirmUninstall":"\u79FB\u81F3\u56DE\u6536\u7AD9","repo.uninstallHint":"\u4EC5\u5C06\u6B64\u4ED3\u5E93\u5DF2\u8DDF\u8E2A\u7684\u5B89\u88C5\u526F\u672C\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u5305\u62EC\u672C\u5730\u4FEE\u6539\u3002\u4E4B\u540E\u53EF\u5728\u56DE\u6536\u7AD9\u6062\u590D\u3002","repo.uninstallExternalWarning":"\u5378\u8F7D\u8BE5\u6280\u80FD\u4E5F\u4F1A\u5F71\u54CD\u4F7F\u7528\u6240\u9009\u76EE\u5F55\u7684\u5176\u4ED6 Agent\u3002","repo.uninstalling":"\u5378\u8F7D\u4E2D\u2026","repo.uninstallProgress":"\u6B63\u5728\u5C06\u5DF2\u5B89\u88C5\u7684\u6280\u80FD\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u8BF7\u7A0D\u5019\u2026","repo.uninstallSuccess":"\u6280\u80FD\u5DF2\u79FB\u81F3\u56DE\u6536\u7AD9\uFF0C\u53EF\u5728\u56DE\u6536\u7AD9\u6062\u590D\u3002","repo.empty":"\u8FD8\u6CA1\u6709\u6280\u80FD\u4ED3\u5E93\uFF0C\u6DFB\u52A0\u4E00\u4E2A Git \u4ED3\u5E93\u5F00\u59CB\u3002","repo.noSkills":"\u6CA1\u6709\u5339\u914D\u7684\u6280\u80FD\u3002\u53EF\u5237\u65B0\u4ED3\u5E93\u6216\u8C03\u6574\u7B5B\u9009\u3002","repo.url":"\u4ED3\u5E93\u5730\u5740","repo.urlPlaceholder":"owner/repo \u6216\u5B8C\u6574 Git \u5730\u5740","repo.ref":"\u5206\u652F\u6216\u6807\u7B7E\uFF08\u53EF\u9009\uFF09","repo.directory":"\u6280\u80FD\u5B50\u76EE\u5F55\uFF08\u53EF\u9009\uFF09","repo.defaultBranch":"\u7559\u7A7A\u4F7F\u7528\u9ED8\u8BA4\u5206\u652F","repo.defaultBranchLabel":"\u9ED8\u8BA4\u5206\u652F","repo.directoryHint":"\u4F8B\u5982 skills\uFF0C\u7559\u7A7A\u626B\u63CF\u6574\u4E2A\u4ED3\u5E93","repo.remove":"\u79FB\u9664\u4ED3\u5E93","repo.removeHint":"\u4EC5\u79FB\u9664\u4ED3\u5E93\u8BA2\u9605\uFF0C\u5DF2\u5B89\u88C5\u7684\u6280\u80FD\u4F1A\u4FDD\u7559\u3002","repo.removeConfirm":"\u786E\u8BA4\u79FB\u9664","repo.cancel":"\u53D6\u6D88","repo.close":"\u5173\u95ED","repo.loading":"\u5904\u7406\u4E2D\u2026","repo.success":"\u64CD\u4F5C\u5B8C\u6210","repo.installHint":"\u6280\u80FD\u53EF\u80FD\u5305\u542B\u811A\u672C\uFF0C\u8BF7\u5148\u67E5\u770B\u8BF4\u660E\u5E76\u786E\u8BA4\u4FE1\u4EFB\u6765\u6E90\u3002\u5B89\u88C5\u4F1A\u4FDD\u7559\u8D44\u6E90\u6587\u4EF6\uFF0C\u4E0D\u4F1A\u6267\u884C\u811A\u672C\u3002","repo.commit":"\u626B\u63CF\u7248\u672C","repo.notScanned":"\u5C1A\u672A\u626B\u63CF","repo.updated":"\u4E0A\u6B21\u5237\u65B0","repo.copyBody":"\u590D\u5236\u6B63\u6587","repo.copied":"\u5DF2\u590D\u5236","repo.copyFailed":"\u590D\u5236\u5931\u8D25","error.repo.tooLarge":"\u4ED3\u5E93\u8D85\u8FC7\u5927\u5C0F\u9650\u5236\uFF0C\u8BF7\u9009\u62E9\u8F83\u5C0F\u7684\u6280\u80FD\u4ED3\u5E93\u3002","error.repo.transientTooLarge":"\u4ED3\u5E93\u4E34\u65F6\u4E0B\u8F7D\u4F53\u79EF\u8FC7\u5927\uFF08\u5305\u542B Git \u5386\u53F2\u5BF9\u8C61\uFF09\uFF0C\u8BF7\u9009\u62E9\u8F83\u5C0F\u7684\u6280\u80FD\u4ED3\u5E93\u3002","error.repo.disk":"\u78C1\u76D8\u5269\u4F59\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u68C0\u51FA\u8BE5\u4ED3\u5E93\u3002\u8BF7\u6E05\u7406\u78C1\u76D8\u7A7A\u95F4\u540E\u91CD\u8BD5\u3002","error.repo.noGit":"\u672A\u68C0\u6D4B\u5230 Git \u547D\u4EE4\u884C\u5DE5\u5177\uFF0C\u8BF7\u5148\u5B89\u88C5 Git \u540E\u91CD\u8BD5\u3002","error.repo.installState":"\u6280\u80FD\u6587\u4EF6\u5DF2\u4FDD\u7559\uFF0C\u4F46\u6765\u6E90\u72B6\u6001\u672A\u786E\u8BA4\u3002\u8BF7\u6062\u590D\u78C1\u76D8\u5199\u5165\u540E\u91CD\u65B0\u6253\u5F00\u4ED3\u5E93\u9875\uFF0C\u7CFB\u7EDF\u4F1A\u6821\u9A8C\u6587\u4EF6\u5E76\u6062\u590D\u8BB0\u5F55\u3002","error.repo.capacity":"\u5B89\u88C5\u6765\u6E90\u72B6\u6001\u8D85\u8FC7 16 MiB\u3002\u8BF7\u5148\u6E05\u7406\u4E0D\u9700\u8981\u7684\u56DE\u6536\u8BB0\u5F55\u6216\u65E7\u8EAB\u4EFD\uFF0C\u4ECD\u6709\u6548\u7684\u5B89\u88C5\u526F\u672C\u4F1A\u4FDD\u7559\u3002","error.repo.invalid":"\u4ED3\u5E93\u5730\u5740\u3001\u8DEF\u5F84\u6216\u5185\u5BB9\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u8F93\u5165\u53CA\u4ED3\u5E93\u5927\u5C0F\u3002","error.repo.network":"\u4ED3\u5E93\u8BBF\u95EE\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u7A0D\u540E\u91CD\u8BD5\u3002\u4E0A\u6B21\u5217\u8868\u5DF2\u4FDD\u7559\u3002","error.repo.state":"\u4ED3\u5E93\u72B6\u6001\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u8BF7\u4FDD\u7559\u6587\u4EF6\u5E76\u4FEE\u590D\u540E\u91CD\u8BD5\u3002","error.repo.conflict":"\u540C\u540D\u6280\u80FD\u5DF2\u5B58\u5728\u6216\u5B89\u88C5\u5931\u8D25\uFF0C\u672C\u5730\u6587\u4EF6\u672A\u88AB\u8986\u76D6\u3002","error.repo.enableState":"\u6280\u80FD\u6587\u4EF6\u5DF2\u64A4\u9500\uFF0C\u4F46\u542F\u7528\u72B6\u6001\u65E0\u6CD5\u5199\u5165\u3002\u8BF7\u6062\u590D\u78C1\u76D8\u5199\u5165\u540E\u91CD\u8BD5\u5B89\u88C5\u3002"},en:{"repo.catalog.unknownLocation":"Location unknown","repo.catalog.reassociate":"Choose destination and reinstall to reassociate","repo.catalog.unknownHint":"Some legacy catalog installs have unknown locations. Find the skill and choose a destination to reinstall and reassociate.","repo.catalog.loadMore":"Load 10 more","repo.catalog.installRefreshWarning":"Installed and enabled, but the list could not refresh. Reopen Discover Skills; do not install again.","repo.catalog.uninstallRefreshWarning":"Moved to trash, but the list could not refresh. Refresh the skill list or reopen Settings; do not uninstall again.","repo.installElsewhere":"Install elsewhere","repo.destinationUnavailable":"The original project is unregistered or unavailable. Re-register it in the sidebar and refresh to manage this copy.","repo.scope.user":"Global","repo.scope.project":"Project","repo.source.dsh":"DSH","repo.source.agents":"Shared Agent","repo.refreshing":"Checking updates\u2026","repo.refreshed":"Refresh and update check completed","repo.partialRefreshFailed":"Failed to check updates for some repositories ({names}); others completed.","repo.detailLoading":"Loading skill instructions\u2026","repo.local":"Local","repo.source":"Installed from","repo.update":"Update available","repo.review":"Review update","repo.rollback":"Restore previous version","repo.confirmUpdate":"Back up and update","repo.confirmRollback":"Back up and restore","repo.overwrite":"Back up local edits and replace","repo.modified":"Local edits detected. Replacement overwrites current files and keeps a complete backup for restoration.","repo.noChanges":"No file changes.","repo.added":"Added","repo.removed":"Removed","repo.modifiedFile":"Modified","repo.updating":"Updating\u2026","repo.updateProgress":"Verifying, backing up and replacing files. Please wait\u2026","repo.updateSuccess":"Updated. Previous version backed up.","repo.rollbackSuccess":"Previous version restored.","repo.manageUpdate":"Manage updates","error.repo.modified":"Local edits detected. Review and confirm replacement with a backup.","scope.repositories":"Discover Skills","repo.add":"Add repository","repo.manage":"Manage repositories","repo.refresh":"Refresh & check updates","repo.save":"Add and scan","repo.subsource.github":"Git Repos","repo.subsource.skillsh":"skills.sh","repo.subsource.clawhub":"ClawHub","repo.subsource.popular":"Top 100 by downloads","repo.catalog.states":"All statuses","repo.catalog.uninstalled":"Not installed","repo.catalog.searchClawhub":"Search ClawHub skills","repo.catalog.searchLoading":"Searching ClawHub\u2026","repo.catalog.searchEmpty":"No matching ClawHub skills were found.","repo.catalog.searchError":"ClawHub search failed. Please retry.","repo.catalog.searchSkillsh":"Search skills.sh","repo.catalog.skillshLoading":"Searching skills.sh\u2026","repo.catalog.skillshEmpty":"No matching skills.sh skills were found.","repo.catalog.skillshError":"skills.sh search failed. Please retry.","repo.sort.installs":"Installs: high to low","repo.sort.name":"Name A\u2013Z","repo.subsource.catalogNote":"ClawHub download ranking \xB7 Up to 100; sort only displayed items","repo.catalog.empty":"No matching skills were returned by ClawHub.","repo.catalog.loading":"Loading ClawHub ranking\u2026","repo.catalog.loadError":"Failed to load the ClawHub ranking. Check your connection and retry.","repo.catalog.retry":"Retry","repo.catalog.loadingMore":"Loading more\u2026","repo.catalog.downloads":"downloads","repo.catalog.installs":"installs","repo.catalog.stars":"stars","repo.hint":"Discover skills from subscribed Git repositories or community registries, and install them to user DSH skills or Shared Agent, or a registered project's .dsh/skills.","repo.footer":"Refresh checks repositories and skill updates without replacing local files. Historical backups are not cleaned automatically and use additional disk space.","repo.search":"Search repository skills","repo.all":"All repositories","repo.states":"All statuses","repo.available":"Available","repo.installed":"Installed","repo.conflict":"Name conflict","repo.invalid":"Invalid format","repo.install":"Install","repo.detail":"Details","repo.installing":"Installing\u2026","repo.installProgress":"Reading and verifying skill files. Please wait\u2026","repo.installSuccess":"Installed and enabled. Invoke it from the slash menu.","repo.installDestination":"Installation destination","repo.rootDsh":"DSH skills","repo.rootAgents":"Shared Agent","repo.rootDshHint":"For DSH only. The path uses your current DSH_HOME.","repo.rootExternalHint":"Other agents using this directory can read this skill too.","repo.installLocationsHint":"User directories offer DSH skills or Shared Agent; registered projects write only to .dsh/skills. The chooser shows actual paths. Missing directories are created after confirmation. Unsafe paths cannot be selected.","repo.uninstall":"Uninstall","repo.confirmUninstall":"Move to Trash","repo.uninstallHint":"Move only this repository\u2019s tracked installed copy, including local edits, to Trash. You can restore it from Trash later.","repo.uninstallExternalWarning":"Uninstalling this skill also affects other agents that use the selected directory.","repo.uninstalling":"Uninstalling\u2026","repo.uninstallProgress":"Moving the installed skill to Trash. Please wait\u2026","repo.uninstallSuccess":"Skill moved to Trash. You can restore it from Trash.","repo.empty":"No repositories yet. Add a Git repository to get started.","repo.noSkills":"No matching skills. Refresh the repository or adjust your filters.","repo.url":"Repository URL","repo.urlPlaceholder":"owner/repo or full Git repository URL","repo.ref":"Branch or tag (optional)","repo.directory":"Skill subdirectory (optional)","repo.defaultBranch":"Leave empty for the default branch","repo.defaultBranchLabel":"default branch","repo.directoryHint":"For example: skills. Leave empty to scan the repository.","repo.remove":"Remove repository","repo.removeHint":"This removes the subscription only. Installed skills are kept.","repo.removeConfirm":"Confirm removal","repo.cancel":"Cancel","repo.close":"Close","repo.loading":"Working\u2026","repo.success":"Completed","repo.installHint":"Skills may include scripts. Read the instructions and trust the source before installing. Resources are copied; scripts are not executed.","repo.commit":"Snapshot version","repo.notScanned":"Not scanned yet","repo.updated":"Last refreshed","repo.copyBody":"Copy body","repo.copied":"Copied","repo.copyFailed":"Copy failed","error.repo.tooLarge":"Repository exceeds size limit. Choose a smaller repository.","error.repo.transientTooLarge":"Repository transient download size exceeds limit (including Git objects). Choose a smaller repository.","error.repo.disk":"Not enough free disk space to check out this repository. Free up space and retry.","error.repo.noGit":"Git command-line tool not found. Please install Git and retry.","error.repo.installState":"Skill files were preserved but source tracking could not be saved. Restore disk write access and reopen Repositories to verify files and recover the record.","error.repo.capacity":"Source tracking exceeds 16 MiB. Remove unneeded Trash records or legacy identities first. Active installation copies are kept.","error.repo.invalid":"Invalid repository URL, path or contents. Check the input and repository size.","error.repo.network":"Repository request failed. Check your connection or retry later. The previous catalog was kept.","error.repo.state":"Repository state is corrupt or unreadable. Preserve the file and repair it before retrying.","error.repo.conflict":"A skill with this name exists or installation failed. Local files were not overwritten.","error.repo.enableState":"The copied skill was removed because its enabled state could not be saved. Restore disk write access and install again."}},Zo=`
|
|
5
5
|
.dssm-subsources{display:flex;align-items:center;gap:6px;margin:0;border-bottom:0.5px solid var(--dsw-alias-border-l1,#3a3a3a);padding-bottom:6px}
|
|
6
6
|
.dssm-subsource-btn{padding:5px 12px;border:0.5px solid var(--dsw-alias-border-l2,#494949);background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-interactive-bg-hover,transparent));color:var(--dsw-alias-label-secondary,#b9b9b9);border-radius:var(--dsw-radius-sm,8px);font-size:12px;cursor:pointer;transition:background .15s,color .15s,border-color .15s}
|
|
7
7
|
.dssm-subsource-btn:hover{background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-interactive-bg-hover,transparent));color:var(--dsw-alias-label-primary,#eee)}
|
package/lib/repositories.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
-
import { unzipSync } from "fflate";
|
|
5
4
|
import { managerHomePath, resolveWorkspaceTarget, repositoryDestinations, trashRootPath, listTrash, restoreTrash as restoreSkillTrash, permanentlyDeleteTrash, parseSkillDoc, importUploadedSkill, deleteSkill, setSkillEnabled, readManagerState, state, createWorkspaceIndex } from "./core.js";
|
|
6
5
|
import { createRepositoryUpdater, fileIndex, signature, readSkillTree, repositoryInstallRoot, recordInstallRoot, selectInstallRecord } from "./repository-updates.js";
|
|
7
|
-
import { parseGitUrl, fetchGitRepository,
|
|
8
|
-
const LIMIT = 32 << 20;
|
|
6
|
+
import { parseGitUrl, fetchGitRepository, probeGitRemote } from "./repository-git.js";
|
|
9
7
|
const STATE_LIMIT = 16 << 20;
|
|
10
8
|
const STATE_READ_LIMIT = 64 << 20;
|
|
11
9
|
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
@@ -78,57 +76,8 @@ function validateStoredState(input) {
|
|
|
78
76
|
function parseRepositoryInput(input = {}) {
|
|
79
77
|
return parseGitUrl(input);
|
|
80
78
|
}
|
|
81
|
-
function decodeRepositoryArchive(bytes) {
|
|
82
|
-
if (bytes.byteLength > LIMIT) throw failure("\u4ED3\u5E93\u5F52\u6863\u8D85\u8FC7 32 MiB", "error.repo.tooLarge");
|
|
83
|
-
let total = 0, count = 0;
|
|
84
|
-
const seen = /* @__PURE__ */ new Set();
|
|
85
|
-
const archive = unzipSync(new Uint8Array(bytes), { filter(entry) {
|
|
86
|
-
safePath(entry.name.replace(/\/$/, ""));
|
|
87
|
-
const key = entry.name.toLowerCase().replace(/\/$/, "");
|
|
88
|
-
if (seen.has(key)) throw failure("\u4ED3\u5E93\u5B58\u5728\u91CD\u590D\u8DEF\u5F84");
|
|
89
|
-
seen.add(key);
|
|
90
|
-
total += entry.originalSize;
|
|
91
|
-
if (++count > 1e4 || entry.originalSize > LIMIT || total > 64 << 20) throw failure("\u4ED3\u5E93\u89E3\u538B\u5927\u5C0F\u6216\u6587\u4EF6\u6570\u91CF\u8D85\u9650");
|
|
92
|
-
return !entry.name.endsWith("/");
|
|
93
|
-
} });
|
|
94
|
-
const result = /* @__PURE__ */ Object.create(null);
|
|
95
|
-
let root;
|
|
96
|
-
for (const [path, data] of Object.entries(archive)) {
|
|
97
|
-
const slash = path.indexOf("/");
|
|
98
|
-
if (slash < 1) throw failure("\u4ED3\u5E93\u5F52\u6863\u8DEF\u5F84\u65E0\u6548");
|
|
99
|
-
const prefix = path.slice(0, slash);
|
|
100
|
-
if (root && root !== prefix) throw failure("\u4ED3\u5E93\u5F52\u6863\u6839\u76EE\u5F55\u4E0D\u552F\u4E00");
|
|
101
|
-
root = prefix;
|
|
102
|
-
const relative = safePath(path.slice(slash + 1));
|
|
103
|
-
if (data.byteLength > LIMIT) throw failure("\u4ED3\u5E93\u6587\u4EF6\u8FC7\u5927");
|
|
104
|
-
result[relative] = data;
|
|
105
|
-
}
|
|
106
|
-
return result;
|
|
107
|
-
}
|
|
108
|
-
async function readResponse(response, limit) {
|
|
109
|
-
if (!response.ok) throw Object.assign(failure(`\u4ED3\u5E93\u8BBF\u95EE\u5931\u8D25\uFF08HTTP ${response.status}\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`, "error.repo.network"), { httpStatus: response.status });
|
|
110
|
-
if (Number(response.headers.get("content-length")) > limit) throw failure("\u4ED3\u5E93\u5F52\u6863\u8D85\u8FC7 32 MiB", "error.repo.tooLarge");
|
|
111
|
-
const reader = response.body?.getReader();
|
|
112
|
-
if (!reader) throw failure("\u4ED3\u5E93\u54CD\u5E94\u4E3A\u7A7A", "error.repo.network");
|
|
113
|
-
const chunks = [];
|
|
114
|
-
let length = 0;
|
|
115
|
-
try {
|
|
116
|
-
for (; ; ) {
|
|
117
|
-
const { done, value } = await reader.read();
|
|
118
|
-
if (done) break;
|
|
119
|
-
length += value.length;
|
|
120
|
-
if (length > limit) throw failure("\u4ED3\u5E93\u5F52\u6863\u8D85\u8FC7 32 MiB", "error.repo.tooLarge");
|
|
121
|
-
chunks.push(value);
|
|
122
|
-
}
|
|
123
|
-
} finally {
|
|
124
|
-
await reader.cancel();
|
|
125
|
-
}
|
|
126
|
-
return Buffer.concat(chunks, length);
|
|
127
|
-
}
|
|
128
79
|
function createRepositoryManager({
|
|
129
|
-
fetchImpl = globalThis.fetch,
|
|
130
80
|
gitFetch,
|
|
131
|
-
sparseFetch = sparseFetchGitRepository,
|
|
132
81
|
probeRemote,
|
|
133
82
|
maxRepositoryBytes: rawMaxBytes = 2 * 1024 * 1024 * 1024,
|
|
134
83
|
maxTransientBytes: rawMaxTransientBytes,
|
|
@@ -140,8 +89,7 @@ function createRepositoryManager({
|
|
|
140
89
|
const maxRepositoryBytes = typeof rawMaxBytes === "number" && Number.isFinite(rawMaxBytes) && rawMaxBytes > 0 ? rawMaxBytes : 2 * 1024 * 1024 * 1024;
|
|
141
90
|
const maxTransientBytes = typeof rawMaxTransientBytes === "number" && Number.isFinite(rawMaxTransientBytes) && rawMaxTransientBytes > 0 ? rawMaxTransientBytes : maxRepositoryBytes * 2.5;
|
|
142
91
|
const gitTimeoutMs = typeof rawTimeoutMs === "number" && Number.isFinite(rawTimeoutMs) && rawTimeoutMs > 0 ? rawTimeoutMs : 9e5;
|
|
143
|
-
const
|
|
144
|
-
const defaultProbe = probeRemote ?? isDefaultFetch;
|
|
92
|
+
const defaultProbe = probeRemote ?? true;
|
|
145
93
|
const file = join(managerHomePath(), "repositories.json");
|
|
146
94
|
let queue = Promise.resolve();
|
|
147
95
|
let catalogWarnings = [];
|
|
@@ -222,16 +170,6 @@ function createRepositoryManager({
|
|
|
222
170
|
parseRepositoryInput({ url: repo.url || `${repo.owner}/${repo.name}`, ref: repo.ref, subdirectory: repo.subdirectory });
|
|
223
171
|
return repo;
|
|
224
172
|
}
|
|
225
|
-
async function request(url) {
|
|
226
|
-
try {
|
|
227
|
-
const response = await fetchImpl(url, { redirect: "error", signal: AbortSignal.timeout(6e4), headers: { Accept: "application/zip", "User-Agent": "dsh-skills-manager" } });
|
|
228
|
-
return await readResponse(response, LIMIT);
|
|
229
|
-
} catch (caught) {
|
|
230
|
-
const error = caught;
|
|
231
|
-
if (typeof error.code === "string" && error.code.startsWith("error.repo.")) throw error;
|
|
232
|
-
throw failure("\u4ED3\u5E93\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
173
|
async function cacheDirectory() {
|
|
236
174
|
const dir = join(managerHomePath(), "repository-cache");
|
|
237
175
|
if ((await fs.lstat(managerHomePath())).isSymbolicLink()) throw failure("\u7F13\u5B58\u76EE\u5F55\u4E0D\u80FD\u662F\u94FE\u63A5");
|
|
@@ -239,24 +177,12 @@ function createRepositoryManager({
|
|
|
239
177
|
if ((await fs.lstat(dir)).isSymbolicLink()) throw failure("\u7F13\u5B58\u76EE\u5F55\u4E0D\u80FD\u662F\u94FE\u63A5");
|
|
240
178
|
return dir;
|
|
241
179
|
}
|
|
242
|
-
async function cacheArchive(repo, bytes) {
|
|
243
|
-
const version = hash(bytes), dir = await cacheDirectory();
|
|
244
|
-
const target = join(dir, `${repo.id}-${version}.zip`), temporary = join(dir, randomUUID() + ".tmp");
|
|
245
|
-
try {
|
|
246
|
-
await fs.writeFile(temporary, bytes, { flag: "wx" });
|
|
247
|
-
await fs.rename(temporary, target);
|
|
248
|
-
} finally {
|
|
249
|
-
await fs.rm(temporary, { force: true });
|
|
250
|
-
}
|
|
251
|
-
return version;
|
|
252
|
-
}
|
|
253
180
|
async function pruneCache(repo) {
|
|
254
181
|
const dir = await cacheDirectory();
|
|
255
182
|
const keepDir = `${repo.id}-${repo.commit}`;
|
|
256
|
-
const keepZip = `${repo.id}-${repo.commit}.zip`;
|
|
257
183
|
const now = Date.now();
|
|
258
184
|
for (const name of await fs.readdir(dir)) {
|
|
259
|
-
if (name.startsWith(".stage-" + repo.id + "-") || name.startsWith(repo.id + "-") && name !== keepDir
|
|
185
|
+
if (name.startsWith(".stage-" + repo.id + "-") || name.startsWith(repo.id + "-") && name !== keepDir) {
|
|
260
186
|
await fs.rm(join(dir, name), { recursive: true, force: true }).catch(() => void 0);
|
|
261
187
|
} else if (name.startsWith(".stage-")) {
|
|
262
188
|
try {
|
|
@@ -272,57 +198,28 @@ function createRepositoryManager({
|
|
|
272
198
|
async function download(repo, path) {
|
|
273
199
|
const dir = await cacheDirectory();
|
|
274
200
|
const repoDir = join(dir, `${repo.id}-${repo.commit}`);
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (fileInfo.isSymbolicLink() || fileInfo.size > 32 << 20) throw failure("\u4ED3\u5E93\u6587\u4EF6\u8FC7\u5927");
|
|
293
|
-
result[prefix + rel] = await fs.readFile(full);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
await readDir(targetBase);
|
|
298
|
-
return result;
|
|
299
|
-
}
|
|
300
|
-
} catch (e) {
|
|
301
|
-
if (e.code && e.code.startsWith("error.repo.")) throw e;
|
|
302
|
-
if (e.code !== "ENOENT") throw e;
|
|
303
|
-
}
|
|
304
|
-
try {
|
|
305
|
-
const zipInfo = await fs.lstat(zipPath);
|
|
306
|
-
if (!zipInfo.isSymbolicLink() && zipInfo.isFile() && zipInfo.size <= LIMIT) {
|
|
307
|
-
const bytes = await fs.readFile(zipPath);
|
|
308
|
-
if (hash(bytes) === repo.commit) {
|
|
309
|
-
const archive = decodeRepositoryArchive(bytes);
|
|
310
|
-
if (!path) return archive;
|
|
311
|
-
const prefix = `${path}/`;
|
|
312
|
-
const filtered = /* @__PURE__ */ Object.create(null);
|
|
313
|
-
for (const [k, v] of Object.entries(archive)) {
|
|
314
|
-
if (k.startsWith(prefix)) filtered[k] = v;
|
|
315
|
-
}
|
|
316
|
-
return filtered;
|
|
201
|
+
const info = await fs.lstat(repoDir).catch(() => null);
|
|
202
|
+
if (!info || !info.isDirectory() || info.isSymbolicLink()) throw failure("\u4ED3\u5E93\u7F13\u5B58\u7F3A\u5931\u6216\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u8BE5\u4ED3\u5E93\u540E\u91CD\u8BD5");
|
|
203
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
204
|
+
const targetBase = path ? join(repoDir, path) : repoDir;
|
|
205
|
+
const prefix = path ? `${path}/` : "";
|
|
206
|
+
async function readDir(curr, relPrefix = "") {
|
|
207
|
+
const items = await fs.readdir(curr, { withFileTypes: true });
|
|
208
|
+
for (const item of items) {
|
|
209
|
+
safePath(item.name);
|
|
210
|
+
const full = join(curr, item.name);
|
|
211
|
+
const rel = relPrefix ? `${relPrefix}/${item.name}` : item.name;
|
|
212
|
+
if (item.isDirectory()) {
|
|
213
|
+
await readDir(full, rel);
|
|
214
|
+
} else if (item.isFile()) {
|
|
215
|
+
const fileInfo = await fs.lstat(full);
|
|
216
|
+
if (fileInfo.isSymbolicLink() || fileInfo.size > 32 << 20) throw failure("\u4ED3\u5E93\u6587\u4EF6\u8FC7\u5927");
|
|
217
|
+
result[prefix + rel] = await fs.readFile(full);
|
|
317
218
|
}
|
|
318
219
|
}
|
|
319
|
-
} catch (e) {
|
|
320
|
-
if (e.code && e.code.startsWith("error.repo.")) throw e;
|
|
321
|
-
}
|
|
322
|
-
if (/^[a-f0-9]{40}$/.test(repo.commit || "")) {
|
|
323
|
-
return decodeRepositoryArchive(await request(`https://codeload.github.com/${repo.owner}/${repo.name}/zip/${repo.commit}`));
|
|
324
220
|
}
|
|
325
|
-
|
|
221
|
+
await readDir(targetBase);
|
|
222
|
+
return result;
|
|
326
223
|
}
|
|
327
224
|
async function scanSkillsFromDirectory(dir, subdirectory = "", repoName = "") {
|
|
328
225
|
const skills = [];
|
|
@@ -380,18 +277,6 @@ function createRepositoryManager({
|
|
|
380
277
|
}
|
|
381
278
|
return skills;
|
|
382
279
|
}
|
|
383
|
-
async function fetchBranch(repo) {
|
|
384
|
-
const refs = repo.ref && repo.ref !== "HEAD" ? [`refs/heads/${repo.ref}`, `refs/tags/${repo.ref}`] : ["HEAD", "refs/heads/main", "refs/heads/master"];
|
|
385
|
-
for (let i = 0; i < refs.length; i++) {
|
|
386
|
-
try {
|
|
387
|
-
return await request(`https://codeload.github.com/${repo.owner}/${repo.name}/zip/${refs[i].split("/").map(encodeURIComponent).join("/")}`);
|
|
388
|
-
} catch (caught) {
|
|
389
|
-
const error = caught;
|
|
390
|
-
if (error.httpStatus !== 404 || i === refs.length - 1) throw error;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
throw failure("\u4ED3\u5E93\u5206\u652F\u4E0D\u5B58\u5728");
|
|
394
|
-
}
|
|
395
280
|
async function matches(record, fingerprint = signature(record.files), registered = registeredWorkspaces?.(), indexed) {
|
|
396
281
|
try {
|
|
397
282
|
safePath(record.name);
|
|
@@ -737,11 +622,7 @@ function createRepositoryManager({
|
|
|
737
622
|
const source = parseRepositoryInput(input);
|
|
738
623
|
const shouldProbe = !gitFetch && defaultProbe && input.skipProbe !== true && process.env.DSH_SKILLS_MANAGER_SKIP_PROBE !== "1";
|
|
739
624
|
if (shouldProbe) {
|
|
740
|
-
|
|
741
|
-
await probeGitRemote(source.url);
|
|
742
|
-
} else {
|
|
743
|
-
await probeGitHubHttp(source.owner, source.name, fetchImpl);
|
|
744
|
-
}
|
|
625
|
+
await probeGitRemote(source.url || `https://github.com/${source.owner}/${source.name}.git`);
|
|
745
626
|
}
|
|
746
627
|
return serialize(async () => {
|
|
747
628
|
const data = await read();
|
|
@@ -789,40 +670,10 @@ function createRepositoryManager({
|
|
|
789
670
|
const res = await gitFetch(repo, stageDir, { maxBytes: maxRepositoryBytes, maxTransientBytes, timeoutMs: gitTimeoutMs });
|
|
790
671
|
commit = res.commit;
|
|
791
672
|
skills = await scanSkillsFromDirectory(stageDir, repo.subdirectory, repo.name);
|
|
792
|
-
} else
|
|
673
|
+
} else {
|
|
793
674
|
const res = await fetchGitRepository(repo, stageDir, { maxBytes: maxRepositoryBytes, maxTransientBytes, timeoutMs: gitTimeoutMs });
|
|
794
675
|
commit = res.commit;
|
|
795
676
|
skills = await scanSkillsFromDirectory(stageDir, repo.subdirectory, repo.name);
|
|
796
|
-
} else {
|
|
797
|
-
let useArchive = false;
|
|
798
|
-
try {
|
|
799
|
-
const res = await sparseFetch(repo, stageDir, { maxBytes: maxRepositoryBytes, maxTransientBytes, timeoutMs: gitTimeoutMs });
|
|
800
|
-
commit = res.commit;
|
|
801
|
-
skills = await scanSkillsFromDirectory(stageDir, repo.subdirectory, repo.name);
|
|
802
|
-
} catch (caught) {
|
|
803
|
-
const error = caught;
|
|
804
|
-
if (error.code !== "error.repo.noGit" && error.code !== "error.repo.network") throw error;
|
|
805
|
-
await fs.rm(stageDir, { recursive: true, force: true }).catch(() => void 0);
|
|
806
|
-
useArchive = true;
|
|
807
|
-
}
|
|
808
|
-
if (useArchive) {
|
|
809
|
-
const bytes = await fetchBranch(repo);
|
|
810
|
-
const entries = decodeRepositoryArchive(bytes);
|
|
811
|
-
for (const [path, b] of Object.entries(entries)) {
|
|
812
|
-
if (path !== "SKILL.md" && !path.endsWith("/SKILL.md")) continue;
|
|
813
|
-
const directory = path === "SKILL.md" ? "" : path.slice(0, -9);
|
|
814
|
-
if (repo.subdirectory && directory !== repo.subdirectory && !directory.startsWith(`${repo.subdirectory}/`)) continue;
|
|
815
|
-
if (b.length > 256 << 10 || skills.length >= 500 || skills.reduce((n, s) => n + Buffer.byteLength(s.body, "utf8"), 0) + b.length > 4 << 20) throw failure("\u4ED3\u5E93\u6280\u80FD\u6570\u91CF\u6216\u8BF4\u660E\u957F\u5EA6\u8D85\u9650");
|
|
816
|
-
const parsed = parseSkillDoc(Buffer.from(b).toString("utf8"));
|
|
817
|
-
const name = String(parsed.map.name || "");
|
|
818
|
-
const description = String(parsed.map.description || "");
|
|
819
|
-
const prefix = directory ? directory + "/" : "";
|
|
820
|
-
const files = fileIndex(Object.fromEntries(Object.entries(entries).filter(([p]) => p.startsWith(prefix)).map(([p, b2]) => [p.slice(prefix.length), b2])));
|
|
821
|
-
if (files.length > 1e3) throw failure("\u6280\u80FD\u6587\u4EF6\u6570\u91CF\u8D85\u9650");
|
|
822
|
-
skills.push({ path: directory, files, name: name || directory.split("/").pop() || repo.name, description, body: parsed.body.trim(), documentHash: hash(b), valid: parsed.hasFrontmatter && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i.test(name) && name.length <= 100 && !!description });
|
|
823
|
-
}
|
|
824
|
-
commit = await cacheArchive(repo, bytes);
|
|
825
|
-
}
|
|
826
677
|
}
|
|
827
678
|
} catch (caught) {
|
|
828
679
|
const error = caught;
|
|
@@ -985,6 +836,5 @@ function createRepositoryManager({
|
|
|
985
836
|
}
|
|
986
837
|
export {
|
|
987
838
|
createRepositoryManager,
|
|
988
|
-
decodeRepositoryArchive,
|
|
989
839
|
parseRepositoryInput
|
|
990
840
|
};
|
package/lib/repository-git.js
CHANGED
|
@@ -231,6 +231,24 @@ function runGit(args, cwd, optionsOrTimeout, legacyGitBinary = "git") {
|
|
|
231
231
|
child.stdin.on("error", () => void 0);
|
|
232
232
|
child.stdin.end(options.input);
|
|
233
233
|
}
|
|
234
|
+
let measuring = false;
|
|
235
|
+
const quota = options.quota;
|
|
236
|
+
const watchdog = quota ? setInterval(() => {
|
|
237
|
+
if (measuring || killed) return;
|
|
238
|
+
measuring = true;
|
|
239
|
+
getDirectorySize(quota.dir).then((size) => {
|
|
240
|
+
measuring = false;
|
|
241
|
+
if (killed || size <= quota.limit) return;
|
|
242
|
+
killed = true;
|
|
243
|
+
killProcessTree(child);
|
|
244
|
+
reject(failure("\u4ED3\u5E93\u4E34\u65F6\u4E0B\u8F7D\u4F53\u79EF\u8FC7\u5927\uFF08\u5305\u542B Git \u5386\u53F2\u5BF9\u8C61\uFF09", "error.repo.transientTooLarge", { limit: quota.limit, actual: size }));
|
|
245
|
+
}, () => {
|
|
246
|
+
measuring = false;
|
|
247
|
+
});
|
|
248
|
+
}, 800) : void 0;
|
|
249
|
+
const stopWatchdog = () => {
|
|
250
|
+
if (watchdog) clearInterval(watchdog);
|
|
251
|
+
};
|
|
234
252
|
child.stdout.on("data", (chunk) => {
|
|
235
253
|
stdout += chunk;
|
|
236
254
|
});
|
|
@@ -239,11 +257,13 @@ function runGit(args, cwd, optionsOrTimeout, legacyGitBinary = "git") {
|
|
|
239
257
|
});
|
|
240
258
|
const timer = setTimeout(() => {
|
|
241
259
|
killed = true;
|
|
260
|
+
stopWatchdog();
|
|
242
261
|
killProcessTree(child);
|
|
243
262
|
reject(failure("Git \u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network"));
|
|
244
263
|
}, timeoutMs);
|
|
245
264
|
child.on("error", (err) => {
|
|
246
265
|
clearTimeout(timer);
|
|
266
|
+
stopWatchdog();
|
|
247
267
|
if (killed) return;
|
|
248
268
|
const code = err?.code;
|
|
249
269
|
if (code === "ENOENT") {
|
|
@@ -254,6 +274,7 @@ function runGit(args, cwd, optionsOrTimeout, legacyGitBinary = "git") {
|
|
|
254
274
|
});
|
|
255
275
|
child.on("close", (code) => {
|
|
256
276
|
clearTimeout(timer);
|
|
277
|
+
stopWatchdog();
|
|
257
278
|
if (killed) return;
|
|
258
279
|
if (code === 0) resolve({ stdout, stderr });
|
|
259
280
|
else reject(mapGitError(stderr || stdout));
|
|
@@ -279,83 +300,10 @@ async function probeGitRemote(repoUrl, options = {}) {
|
|
|
279
300
|
await runGit(["ls-remote", "--heads", "-q", "--", repoUrl], process.cwd(), { timeoutMs, gitBinary });
|
|
280
301
|
} catch (caught) {
|
|
281
302
|
const err = caught;
|
|
303
|
+
if (err?.code === "error.repo.noGit") throw err;
|
|
282
304
|
throw failure(err.message || "\u4ED3\u5E93\u65E0\u6CD5\u8BBF\u95EE\u6216\u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u5730\u5740\u6216\u6743\u9650", "error.repo.network");
|
|
283
305
|
}
|
|
284
306
|
}
|
|
285
|
-
async function probeGitHubHttp(owner, name, fetchImpl = globalThis.fetch, timeoutMs = 15e3) {
|
|
286
|
-
const controller = new AbortController();
|
|
287
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
288
|
-
try {
|
|
289
|
-
const res = await fetchImpl(`https://github.com/${owner}/${name}`, {
|
|
290
|
-
method: "HEAD",
|
|
291
|
-
redirect: "manual",
|
|
292
|
-
signal: controller.signal,
|
|
293
|
-
headers: { "User-Agent": "Mozilla/5.0" }
|
|
294
|
-
});
|
|
295
|
-
if (res.status === 404) {
|
|
296
|
-
throw failure("\u4ED3\u5E93\u65E0\u6CD5\u8BBF\u95EE\u6216\u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u5730\u5740\u6216\u6743\u9650", "error.repo.network");
|
|
297
|
-
}
|
|
298
|
-
} catch (caught) {
|
|
299
|
-
if (caught && caught.code === "error.repo.network") throw caught;
|
|
300
|
-
} finally {
|
|
301
|
-
clearTimeout(timer);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
async function fetchGitRepository(repo, destinationDir, options = {}) {
|
|
305
|
-
const maxBytes = options.maxBytes ?? 2 * 1024 * 1024 * 1024;
|
|
306
|
-
const maxTransientBytes = options.maxTransientBytes ?? maxBytes * 2.5;
|
|
307
|
-
const timeoutMs = options.timeoutMs ?? 9e5;
|
|
308
|
-
const deadline = Date.now() + timeoutMs;
|
|
309
|
-
const gitBinary = options.gitBinary || "git";
|
|
310
|
-
const remainingTimeout = () => {
|
|
311
|
-
const remain = deadline - Date.now();
|
|
312
|
-
if (remain <= 0) throw failure("Git \u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network");
|
|
313
|
-
return remain;
|
|
314
|
-
};
|
|
315
|
-
const repoUrl = repo.url || `https://github.com/${repo.owner}/${repo.name}.git`;
|
|
316
|
-
const ref = repo.ref && repo.ref !== "HEAD" ? repo.ref : "HEAD";
|
|
317
|
-
await fs.mkdir(destinationDir, { recursive: true });
|
|
318
|
-
try {
|
|
319
|
-
await runGit(["init", "-q"], destinationDir, { timeoutMs: remainingTimeout(), gitBinary });
|
|
320
|
-
const refsToTry = ref === "HEAD" ? ["HEAD"] : [`refs/heads/${ref}`, `refs/tags/${ref}`, ref];
|
|
321
|
-
let fetched = false;
|
|
322
|
-
let lastError;
|
|
323
|
-
for (const r of refsToTry) {
|
|
324
|
-
try {
|
|
325
|
-
await runGit(["fetch", "--progress", "--depth", "1", "--no-tags", "--", repoUrl, r], destinationDir, { timeoutMs: remainingTimeout(), gitBinary });
|
|
326
|
-
fetched = true;
|
|
327
|
-
break;
|
|
328
|
-
} catch (err) {
|
|
329
|
-
lastError = err;
|
|
330
|
-
if (r === refsToTry[refsToTry.length - 1]) {
|
|
331
|
-
try {
|
|
332
|
-
await runGit(["fetch", "--progress", "--no-tags", "--", repoUrl, r], destinationDir, { timeoutMs: remainingTimeout(), gitBinary });
|
|
333
|
-
fetched = true;
|
|
334
|
-
} catch {
|
|
335
|
-
throw err;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
if (!fetched) throw lastError || failure("Git \u5206\u652F\u6216\u6807\u7B7E\u4E0D\u5B58\u5728", "error.repo.network");
|
|
341
|
-
await runGit(["checkout", "-q", "FETCH_HEAD"], destinationDir, { timeoutMs: remainingTimeout(), gitBinary });
|
|
342
|
-
const { stdout: commitOut } = await runGit(["rev-parse", "FETCH_HEAD^{commit}"], destinationDir, { timeoutMs: remainingTimeout(), gitBinary });
|
|
343
|
-
const commit = commitOut.trim();
|
|
344
|
-
const rawSize = await getDirectorySize(destinationDir);
|
|
345
|
-
if (rawSize > maxTransientBytes) {
|
|
346
|
-
throw failure("\u4ED3\u5E93\u4E34\u65F6\u4E0B\u8F7D\u4F53\u79EF\u8FC7\u5927\uFF08\u5305\u542B Git \u5386\u53F2\u5BF9\u8C61\uFF09", "error.repo.transientTooLarge", { limit: maxTransientBytes, actual: rawSize });
|
|
347
|
-
}
|
|
348
|
-
await rmWithRetry(join(destinationDir, ".git"));
|
|
349
|
-
const totalSize = await getDirectorySize(destinationDir);
|
|
350
|
-
if (totalSize > maxBytes) {
|
|
351
|
-
throw failure("\u4ED3\u5E93\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "error.repo.tooLarge", { limit: maxBytes, actual: totalSize });
|
|
352
|
-
}
|
|
353
|
-
return { commit };
|
|
354
|
-
} catch (err) {
|
|
355
|
-
await rmWithRetry(destinationDir).catch(() => void 0);
|
|
356
|
-
throw err;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
307
|
const SKILL_DOC_SUFFIX = "/SKILL.md";
|
|
360
308
|
const EMPTY_CHECKOUT_PATTERN = "/.dsh-skills-manager-empty/**";
|
|
361
309
|
function checkoutPatternFor(directory) {
|
|
@@ -387,7 +335,38 @@ async function clearSparseCheckout(destinationDir, options) {
|
|
|
387
335
|
}
|
|
388
336
|
await fs.rm(join(destinationDir, ".git", "info", "sparse-checkout"), { force: true }).catch(() => void 0);
|
|
389
337
|
}
|
|
390
|
-
|
|
338
|
+
const DISK_PREFLIGHT_BYTES = 256 << 20;
|
|
339
|
+
function formatBytes(bytes) {
|
|
340
|
+
if (!Number.isFinite(bytes) || bytes < 0) return String(bytes);
|
|
341
|
+
const units = ["B", "KiB", "MiB", "GiB"];
|
|
342
|
+
let value = bytes, unit = 0;
|
|
343
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
344
|
+
value /= 1024;
|
|
345
|
+
unit += 1;
|
|
346
|
+
}
|
|
347
|
+
return `${unit === 0 || value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[unit]}`;
|
|
348
|
+
}
|
|
349
|
+
async function ensureDiskSpace(dir, requiredBytes) {
|
|
350
|
+
let free;
|
|
351
|
+
try {
|
|
352
|
+
const stats = await fs.statfs(dir);
|
|
353
|
+
free = Number(stats.bavail) * Number(stats.bsize);
|
|
354
|
+
} catch {
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
if (!Number.isFinite(free) || free >= requiredBytes) return;
|
|
358
|
+
throw failure(`\u78C1\u76D8\u5269\u4F59\u7A7A\u95F4\u4E0D\u8DB3\uFF08\u5269\u4F59 ${formatBytes(free)}\uFF0C\u81F3\u5C11\u9700\u8981 ${formatBytes(requiredBytes)}\uFF09`, "error.repo.disk", { available: free, required: requiredBytes });
|
|
359
|
+
}
|
|
360
|
+
async function sumTreeBlobBytes(destinationDir, revision, options) {
|
|
361
|
+
const { stdout } = await runGit(["ls-tree", "-r", "-l", "-z", "--", revision], destinationDir, options);
|
|
362
|
+
let total = 0;
|
|
363
|
+
for (const record of stdout.split("\0")) {
|
|
364
|
+
const size = /^\d{6} blob [0-9a-f]{40,64}\s+(\d+)\t/.exec(record);
|
|
365
|
+
if (size) total += Number(size[1]);
|
|
366
|
+
}
|
|
367
|
+
return total;
|
|
368
|
+
}
|
|
369
|
+
async function fetchGitRepository(repo, destinationDir, options = {}) {
|
|
391
370
|
const maxBytes = options.maxBytes ?? 2 * 1024 * 1024 * 1024;
|
|
392
371
|
const maxTransientBytes = options.maxTransientBytes ?? maxBytes * 2.5;
|
|
393
372
|
const timeoutMs = options.timeoutMs ?? 9e5;
|
|
@@ -396,21 +375,24 @@ async function sparseFetchGitRepository(repo, destinationDir, options = {}) {
|
|
|
396
375
|
const remainingTimeout = () => {
|
|
397
376
|
const remain = deadline - Date.now();
|
|
398
377
|
if (remain <= 0) throw failure("Git \u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network");
|
|
399
|
-
return { timeoutMs: remain, gitBinary };
|
|
378
|
+
return { timeoutMs: remain, gitBinary, quota: { dir: destinationDir, limit: maxTransientBytes } };
|
|
400
379
|
};
|
|
401
380
|
const repoUrl = repo.url || `https://github.com/${repo.owner}/${repo.name}.git`;
|
|
402
381
|
const ref = repo.ref && repo.ref !== "HEAD" ? repo.ref : "HEAD";
|
|
403
382
|
await fs.mkdir(destinationDir, { recursive: true });
|
|
383
|
+
await ensureDiskSpace(destinationDir, options.diskPreflightBytes ?? Math.min(maxTransientBytes, DISK_PREFLIGHT_BYTES));
|
|
404
384
|
try {
|
|
405
385
|
await runGit(["init", "-q"], destinationDir, remainingTimeout());
|
|
406
386
|
await runGit(["remote", "add", "origin", repoUrl], destinationDir, remainingTimeout());
|
|
407
387
|
const refsToTry = ref === "HEAD" ? ["HEAD"] : [`refs/heads/${ref}`, `refs/tags/${ref}`, ref];
|
|
408
388
|
let fetched = false;
|
|
389
|
+
let filtered = false;
|
|
409
390
|
let lastError;
|
|
410
391
|
for (const r of refsToTry) {
|
|
411
392
|
try {
|
|
412
393
|
await runGit(["fetch", "--progress", "--depth", "1", "--no-tags", "--filter=blob:none", "--", "origin", r], destinationDir, remainingTimeout());
|
|
413
394
|
fetched = true;
|
|
395
|
+
filtered = true;
|
|
414
396
|
break;
|
|
415
397
|
} catch (err) {
|
|
416
398
|
lastError = err;
|
|
@@ -434,6 +416,10 @@ async function sparseFetchGitRepository(repo, destinationDir, options = {}) {
|
|
|
434
416
|
if (!fetched) throw lastError || failure("Git \u5206\u652F\u6216\u6807\u7B7E\u4E0D\u5B58\u5728", "error.repo.network");
|
|
435
417
|
const { stdout: commitOut } = await runGit(["rev-parse", "FETCH_HEAD^{commit}"], destinationDir, remainingTimeout());
|
|
436
418
|
const commit = commitOut.trim();
|
|
419
|
+
if (!filtered) {
|
|
420
|
+
const bytes = await sumTreeBlobBytes(destinationDir, "FETCH_HEAD", remainingTimeout());
|
|
421
|
+
if (bytes > maxBytes) throw failure("\u4ED3\u5E93\u8D85\u8FC7\u5927\u5C0F\u9650\u5236", "error.repo.tooLarge", { limit: maxBytes, actual: bytes });
|
|
422
|
+
}
|
|
437
423
|
const patterns = skillCheckoutPatterns(await listTreePaths(destinationDir, "FETCH_HEAD", remainingTimeout()), repo.subdirectory);
|
|
438
424
|
if (patterns) {
|
|
439
425
|
try {
|
|
@@ -441,7 +427,7 @@ async function sparseFetchGitRepository(repo, destinationDir, options = {}) {
|
|
|
441
427
|
` });
|
|
442
428
|
} catch (err) {
|
|
443
429
|
const code = err?.code;
|
|
444
|
-
if (code === "error.repo.noGit" || code === "error.repo.network") throw err;
|
|
430
|
+
if (code === "error.repo.noGit" || code === "error.repo.network" || code === "error.repo.transientTooLarge") throw err;
|
|
445
431
|
await clearSparseCheckout(destinationDir, remainingTimeout());
|
|
446
432
|
}
|
|
447
433
|
}
|
|
@@ -465,8 +451,6 @@ export {
|
|
|
465
451
|
fetchGitRepository,
|
|
466
452
|
getDirectorySize,
|
|
467
453
|
parseGitUrl,
|
|
468
|
-
probeGitHubHttp,
|
|
469
454
|
probeGitRemote,
|
|
470
|
-
skillCheckoutPatterns
|
|
471
|
-
sparseFetchGitRepository
|
|
455
|
+
skillCheckoutPatterns
|
|
472
456
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.35",
|
|
4
4
|
"description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|