This commit is contained in:
tww 2026-03-15 20:34:36 +08:00
parent 6bbc82837b
commit bd83f91648
27 changed files with 129 additions and 96 deletions

View file

@ -50,7 +50,7 @@ ClawHub CLI 使用单一 slug 模型slug 校验规则为 `[a-z0-9]([a-z0-9-]*
- skillhub 自有 CLI 支持两种格式输入,内部统一转换为 namespace 坐标
**Well-known 发现:**
- skillhub 服务端提供 `/.well-known/clawhub.json`,返回 `{ "apiBase": "/api/compat/v1" }`
- skillhub 服务端提供 `/.well-known/clawhub.json`,返回 `{ "apiBase": "/api/v1" }`
- ClawHub CLI 通过此机制自动发现兼容层 API 基地址
## 2. 参考项目取舍

View file

@ -458,7 +458,7 @@ Session 中存储以下字段:
- 后端设置 `XSRF-TOKEN` Cookie`HttpOnly=false`
- 前端从 Cookie 读取 Token放入请求 Header `X-XSRF-TOKEN`
- 后端校验 Header 与 Cookie 是否一致
- CLI API`/api/v1/**`)与兼容层(`/api/compat/v1/**`)豁免 CSRF使用 Bearer Token无 Cookie
- CLI API`/api/v1/**`)与兼容层(`/api/v1/**`)豁免 CSRF使用 Bearer Token无 Cookie
## 9. 前端权限控制
@ -639,8 +639,8 @@ window.location.href = '/oauth2/authorization/github'
| 接口 | 所需凭证 | 额外判定 |
|------|---------|---------|
| `GET /api/compat/v1/whoami` | 任意有效 Bearer Token | 无 |
| `GET /api/compat/v1/search` | 可选(匿名限 PUBLIC | `SearchVisibilityScope` |
| `GET /api/compat/v1/resolve` | 可选(匿名限 PUBLIC | visibility |
| `GET /api/compat/v1/download/{slug}/{version}` | 可选(匿名限 PUBLIC | visibility |
| `POST /api/compat/v1/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上namespace 由 canonical slug 解析) |
| `GET /api/v1/whoami` | 任意有效 Bearer Token | 无 |
| `GET /api/v1/search` | 可选(匿名限 PUBLIC | `SearchVisibilityScope` |
| `GET /api/v1/resolve` | 可选(匿名限 PUBLIC | visibility |
| `GET /api/v1/download/{slug}/{version}` | 可选(匿名限 PUBLIC | visibility |
| `POST /api/v1/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上namespace 由 canonical slug 解析) |

View file

@ -49,7 +49,7 @@
- 审核通过后转为 `PUBLISHED`
- 审核拒绝后转为 `REJECTED`
- 例外:提交人持有 `SUPER_ADMIN` 平台角色时,发布入口直接创建 `skill_version(status=PUBLISHED)`,跳过 `review_task` 创建,同时不再要求其必须是目标 namespace 成员
- 上述例外必须对 Web、`/api/v1/publish``/api/compat/v1/publish` 保持一致
- 上述例外必须对 Web、`/api/v1/publish``/api/v1/publish` 保持一致
### 对象存储写入策略

View file

@ -239,7 +239,7 @@ Public API 的可见性规则:
发布状态约束:
- 普通用户发布成功后,`status``PENDING_REVIEW`
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish``/api/compat/v1/publish` 发布时,`status``PUBLISHED`,且不要求其必须是目标 namespace 成员
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish``/api/v1/publish` 发布时,`status``PUBLISHED`,且不要求其必须是目标 namespace 成员
- 当前版本保持该审核策略,不再提供“全员直发”的运行模式
## 7.4 Token API需登录
@ -387,7 +387,7 @@ Admin API 按最小权限拆分,不再统一要求 SUPER_ADMIN
## 7.10 ClawHub CLI 兼容层 API
兼容层 API 基地址为 `/api/compat/v1`,通过 `/.well-known/clawhub.json` 发现。兼容层使用 canonical slug双连字符映射规则详见 `00-product-direction.md` 1.1 节)。
兼容层 API 基地址为 `/api/v1`,通过 `/.well-known/clawhub.json` 发现。兼容层使用 canonical slug双连字符映射规则详见 `00-product-direction.md` 1.1 节)。
认证方式:`Authorization: Bearer <token>`。Bearer Token 可来自 CLI Device Flow 或平台 API Token。
@ -398,7 +398,7 @@ GET /.well-known/clawhub.json
响应:
{
"apiBase": "/api/compat/v1"
"apiBase": "/api/v1"
}
```
@ -406,15 +406,15 @@ GET /.well-known/clawhub.json
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/compat/v1/whoami` | 当前用户信息 |
| GET | `/api/compat/v1/search` | 搜索技能 |
| GET | `/api/compat/v1/resolve` | 通过 slug + version 解析版本 |
| GET | `/api/compat/v1/download/{slug}/{version}` | 下载技能 zip 包 |
| POST | `/api/compat/v1/publish` | 发布技能multipart/form-data`SUPER_ADMIN` 直发) |
| GET | `/api/v1/whoami` | 当前用户信息 |
| GET | `/api/v1/search` | 搜索技能 |
| GET | `/api/v1/resolve` | 通过 slug + version 解析版本 |
| GET | `/api/v1/download/{slug}/{version}` | 下载技能 zip 包 |
| POST | `/api/v1/publish` | 发布技能multipart/form-data`SUPER_ADMIN` 直发) |
### 兼容层请求/响应格式
**GET `/api/compat/v1/whoami`**
**GET `/api/v1/whoami`**
```json
{
@ -424,7 +424,7 @@ GET /.well-known/clawhub.json
}
```
**GET `/api/compat/v1/search?q={keyword}&page={page}&limit={limit}`**
**GET `/api/v1/search?q={keyword}&page={page}&limit={limit}`**
```json
{
@ -449,21 +449,21 @@ GET /.well-known/clawhub.json
注意:兼容层返回的 `slug` 为 canonical slug 格式(全局空间直接返回 skill slug团队空间返回 `namespace--skill`)。
**GET `/api/compat/v1/resolve?slug={slug}&version={version}`**
**GET `/api/v1/resolve?slug={slug}&version={version}`**
```json
{
"slug": "my-skill",
"version": "1.2.0",
"downloadUrl": "/api/compat/v1/download/my-skill/1.2.0"
"downloadUrl": "/api/v1/download/my-skill/1.2.0"
}
```
**GET `/api/compat/v1/download/{slug}/{version}`**
**GET `/api/v1/download/{slug}/{version}`**
返回指定版本的 zip 文件流。默认版本解析由 `resolve` 接口负责。
**POST `/api/compat/v1/publish`**
**POST `/api/v1/publish`**
```
Content-Type: multipart/form-data
@ -521,7 +521,7 @@ Parts:
- 版本策略URL path 版本 `/api/v1/`
- 幂等性:写操作通过 `X-Request-Id` + Redis 去重TTL 24h
### Compatibility API`/api/compat/v1/*`
### Compatibility API`/api/v1/*`
- 响应格式完全遵循 ClawHub 协议,不套统一响应包裹
- 错误响应遵循 ClawHub 格式:`{ error: string, message: string }`

View file

@ -9,7 +9,7 @@
- 一期同步发布模型,暂不考虑异步发布
- API Token 一期继承用户全部权限(非最小权限),后续版本细化
- CLI 主认证切换为 OAuth Device FlowBearer Token 统一用于 CLI API 和兼容层
- ClawHub CLI 兼容层基地址 `/api/compat/v1`,通过 `/.well-known/clawhub.json` 发现
- ClawHub CLI 兼容层基地址 `/api/v1`,通过 `/.well-known/clawhub.json` 发现
## Phase 1工程骨架 + 认证打通
@ -75,7 +75,7 @@
- 评分 + 收藏 + 计数器(原子更新)
- OAuth Device Flowdevice code、授权确认、轮询换取 Bearer Token
- CLI APIwhoami、publish、resolve、check
- ClawHub CLI 协议兼容层(`/api/compat/v1` 端点search、resolve、download、publish、whoami
- ClawHub CLI 协议兼容层(`/api/v1` 端点search、resolve、download、publish、whoami
- 兼容层 canonical slug 映射(`--` 双连字符规则)
- `/.well-known/clawhub.json` 发现端点
- 协议适配器与兼容性测试(针对 ClawHub CLI 的真实请求/响应样例)

View file

@ -2756,7 +2756,7 @@ public class ApiTokenAuthenticationFilter extends OncePerRequestFilter {
// 仅对 CLI 和 Token API 路径生效
String path = request.getRequestURI();
return !(path.startsWith("/api/v1/cli/") || path.startsWith("/api/v1/tokens")
|| path.startsWith("/api/compat/"));
|| path.startsWith("/api/"));
}
}
```
@ -2974,7 +2974,7 @@ public class SecurityConfig {
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(csrfHandler)
.ignoringRequestMatchers("/api/v1/cli/**", "/api/compat/**")
.ignoringRequestMatchers("/api/v1/cli/**", "/api/**")
)
.authorizeHttpRequests(auth -> auth
// 公开端点

View file

@ -6709,7 +6709,7 @@ export function FileTree({ files, onSelect }: {
import { CopyButton } from '@/shared/components/copy-button';
export function InstallCommand({ namespace, slug }: { namespace: string; slug: string }) {
const command = `skillhub install @${namespace}/${slug}`;
const command = `clawhub install @${namespace}/${slug}`;
return (
<div className="flex items-center gap-2 bg-muted rounded-lg px-3 py-2">
<code className="text-sm flex-1 font-mono truncate">{command}</code>

View file

@ -6428,7 +6428,7 @@ class WellKnownControllerTest {
void returns_api_base() throws Exception {
mockMvc.perform(get("/.well-known/clawhub.json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.apiBase").value("/api/compat/v1"));
.andExpect(jsonPath("$.apiBase").value("/api/v1"));
}
}
```
@ -6446,7 +6446,7 @@ import java.util.Map;
public class WellKnownController {
@GetMapping("/.well-known/clawhub.json")
public Map<String, String> clawHubDiscovery() {
return Map.of("apiBase", "/api/compat/v1");
return Map.of("apiBase", "/api/v1");
}
}
```
@ -6527,7 +6527,7 @@ class ClawHubCompatControllerTest {
@Test
void search_returns_compat_format() throws Exception {
mockMvc.perform(get("/api/compat/v1/search").param("q", "test"))
mockMvc.perform(get("/api/v1/search").param("q", "test"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.items").isArray());
}
@ -6536,14 +6536,14 @@ class ClawHubCompatControllerTest {
void resolve_parses_canonical_slug() throws Exception {
when(slugMapper.fromCanonical("my-skill"))
.thenReturn(new SkillCoordinate("global", "my-skill"));
mockMvc.perform(get("/api/compat/v1/resolve")
mockMvc.perform(get("/api/v1/resolve")
.param("slug", "my-skill"))
.andExpect(status().isOk());
}
@Test
void whoami_requires_auth() throws Exception {
mockMvc.perform(get("/api/compat/v1/whoami"))
mockMvc.perform(get("/api/v1/whoami"))
.andExpect(status().isUnauthorized());
}
}
@ -6566,7 +6566,7 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
@RequestMapping("/api/compat/v1")
@RequestMapping("/api/v1")
public class ClawHubCompatController {
private final CanonicalSlugMapper slugMapper;
@ -6591,7 +6591,7 @@ public class ClawHubCompatController {
SkillCoordinate coord = slugMapper.fromCanonical(slug);
// TODO: 调用 SkillQueryService 获取版本详情
return new ClawHubResolveResponse(slug, "", version,
"/api/compat/v1/download/" + slug + "/" + version, 0, 0);
"/api/v1/download/" + slug + "/" + version, 0, 0);
}
@GetMapping("/download/{slug}/{version}")

View file

@ -1016,14 +1016,14 @@ GET /.well-known/clawhub.json
Response 200:
{
"apiBase": "/api/compat/v1"
"apiBase": "/api/v1"
}
```
#### search - 搜索技能
```
GET /api/compat/v1/search?q=keyword&page=0&size=20
GET /api/v1/search?q=keyword&page=0&size=20
Authorization: Bearer sk_xxx可选
Response 200:
@ -1057,7 +1057,7 @@ Response 200:
#### resolve - 解析技能版本
```
GET /api/compat/v1/resolve?slug=my-skill&version=1.2.0
GET /api/v1/resolve?slug=my-skill&version=1.2.0
Authorization: Bearer sk_xxx可选
Response 200:
@ -1065,7 +1065,7 @@ Response 200:
"slug": "my-skill",
"name": "My Skill",
"version": "1.2.0",
"downloadUrl": "/api/compat/v1/download/my-skill/1.2.0",
"downloadUrl": "/api/v1/download/my-skill/1.2.0",
"fileCount": 5,
"totalSize": 12345
}
@ -1079,7 +1079,7 @@ Response 200:
#### download - 下载技能包
```
GET /api/compat/v1/download/{slug}/{version}
GET /api/v1/download/{slug}/{version}
Authorization: Bearer sk_xxx可选
Response 200:
@ -1097,7 +1097,7 @@ Content-Disposition: attachment; filename="my-skill-1.2.0.zip"
#### publish - 发布技能
```
POST /api/compat/v1/publish
POST /api/v1/publish
Authorization: Bearer sk_xxx
Content-Type: multipart/form-data
@ -1118,7 +1118,7 @@ Response 200:
#### whoami - 查询当前用户
```
GET /api/compat/v1/whoami
GET /api/v1/whoami
Authorization: Bearer sk_xxx
Response 200:
@ -1135,7 +1135,7 @@ Response 200:
```java
@RestController
@RequestMapping("/api/compat/v1")
@RequestMapping("/api/v1")
public class ClawHubCompatController {
private final CanonicalSlugMapper slugMapper;
@ -1193,7 +1193,7 @@ public class ClawHubCompatController {
slug,
detail.displayName(),
detail.version(),
"/api/compat/v1/download/" + slug + "/" + detail.version(),
"/api/v1/download/" + slug + "/" + detail.version(),
detail.fileCount(),
detail.totalSize()
);

View file

@ -11,19 +11,19 @@ description: 安装和使用技能
### 安装最新版本
```bash
skillhub install @team/my-skill
clawhub install @team/my-skill
```
### 安装指定版本
```bash
skillhub install @team/my-skill@1.2.0
clawhub install @team/my-skill@1.2.0
```
### 按标签安装
```bash
skillhub install @team/my-skill@beta
clawhub install @team/my-skill@beta
```
### 使用 ClawHub CLI 安装

View file

@ -12,7 +12,7 @@ description: 技能评分和收藏功能
查看已收藏的技能:
- Web进入"我的收藏"
- CLI`skillhub stars`
- CLI`clawhub stars`
## 技能评分

View file

@ -21,10 +21,10 @@ description: 发布技能到 SkillHub
```bash
# 1. 登录
skillhub login
clawhub login
# 2. 发布
skillhub publish ./my-skill.zip --namespace @team-myteam
clawhub publish ./my-skill.zip --namespace @team-myteam
```
## 通过 ClawHub CLI 发布

View file

@ -31,13 +31,13 @@ SkillHub 使用语义化版本Semantic Versioning`MAJOR.MINOR.PATCH`
### 创建/移动标签
```bash
skillhub tag set @team/my-skill beta 1.2.0
clawhub tag set @team/my-skill beta 1.2.0
```
### 删除标签
```bash
skillhub tag delete @team/my-skill beta
clawhub tag delete @team/my-skill beta
```
## 版本撤回

View file

@ -18,7 +18,7 @@ GET /.well-known/clawhub.json
```json
{
"apiBase": "/api/compat/v1"
"apiBase": "/api/v1"
}
```
@ -27,7 +27,7 @@ GET /.well-known/clawhub.json
### Whoami
```http
GET /api/compat/v1/whoami
GET /api/v1/whoami
```
响应:
@ -43,7 +43,7 @@ GET /api/compat/v1/whoami
### 搜索
```http
GET /api/compat/v1/search?q={keyword}&page={page}&limit={limit}
GET /api/v1/search?q={keyword}&page={page}&limit={limit}
```
响应:
@ -75,7 +75,7 @@ GET /api/compat/v1/search?q={keyword}&page={page}&limit={limit}
### 解析
```http
GET /api/compat/v1/resolve?slug={slug}&version={version}
GET /api/v1/resolve?slug={slug}&version={version}
```
响应:
@ -84,20 +84,20 @@ GET /api/compat/v1/resolve?slug={slug}&version={version}
{
"slug": "my-skill",
"version": "1.2.0",
"downloadUrl": "/api/compat/v1/download/my-skill/1.2.0"
"downloadUrl": "/api/v1/download/my-skill/1.2.0"
}
```
### 下载
```http
GET /api/compat/v1/download/{slug}/{version}
GET /api/v1/download/{slug}/{version}
```
### 发布
```http
POST /api/compat/v1/publish
POST /api/v1/publish
Content-Type: multipart/form-data
file: <zip-file>

View file

@ -11,19 +11,19 @@ description: Install and use skills
### Install Latest Version
```bash
skillhub install @team/my-skill
clawhub install @team/my-skill
```
### Install Specific Version
```bash
skillhub install @team/my-skill@1.2.0
clawhub install @team/my-skill@1.2.0
```
### Install by Tag
```bash
skillhub install @team/my-skill@beta
clawhub install @team/my-skill@beta
```
### Install with ClawHub CLI

View file

@ -12,7 +12,7 @@ Click the "Favorite" button on the skill detail page to favorite a skill.
View your favorite skills:
- Web: Go to "My Favorites"
- CLI: `skillhub stars`
- CLI: `clawhub stars`
## Skill Rating

View file

@ -31,13 +31,13 @@ You can create custom tags for version channel management:
### Create/Move Tag
```bash
skillhub tag set @team/my-skill beta 1.2.0
clawhub tag set @team/my-skill beta 1.2.0
```
### Delete Tag
```bash
skillhub tag delete @team/my-skill beta
clawhub tag delete @team/my-skill beta
```
## Version Withdrawal

View file

@ -18,7 +18,7 @@ Response:
```json
{
"apiBase": "/api/compat/v1"
"apiBase": "/api/v1"
}
```
@ -27,7 +27,7 @@ Response:
### Whoami
```http
GET /api/compat/v1/whoami
GET /api/v1/whoami
```
Response:
@ -43,7 +43,7 @@ Response:
### Search
```http
GET /api/compat/v1/search?q={keyword}&page={page}&limit={limit}
GET /api/v1/search?q={keyword}&page={page}&limit={limit}
```
Response:
@ -75,7 +75,7 @@ Response:
### Resolve
```http
GET /api/compat/v1/resolve?slug={slug}&version={version}
GET /api/v1/resolve?slug={slug}&version={version}
```
Response:
@ -84,20 +84,20 @@ Response:
{
"slug": "my-skill",
"version": "1.2.0",
"downloadUrl": "/api/compat/v1/download/my-skill/1.2.0"
"downloadUrl": "/api/v1/download/my-skill/1.2.0"
}
```
### Download
```http
GET /api/compat/v1/download/{slug}/{version}
GET /api/v1/download/{slug}/{version}
```
### Publish
```http
POST /api/compat/v1/publish
POST /api/v1/publish
Content-Type: multipart/form-data
file: <zip-file>

View file

@ -188,8 +188,8 @@ public class ClawHubCompatController {
}
@GetMapping("/download/{canonicalSlug}")
public ResponseEntity<Void> download(@PathVariable String canonicalSlug,
@RequestParam(defaultValue = "latest") String version) {
public ResponseEntity<Void> downloadByPath(@PathVariable String canonicalSlug,
@RequestParam(defaultValue = "latest") String version) {
SkillCoordinate coord = mapper.fromCanonical(canonicalSlug);
String location = "latest".equals(version)
? "/api/v1/skills/" + coord.namespace() + "/" + coord.slug() + "/download"
@ -199,6 +199,22 @@ public class ClawHubCompatController {
.build();
}
@GetMapping("/download")
public ResponseEntity<Void> downloadByQuery(@RequestParam String slug,
@RequestParam(defaultValue = "latest") String version) {
// For query param version, slug is just the skill slug without namespace
Skill skill = skillRepository.findBySlug(slug).stream().findFirst()
.orElseThrow(() -> new DomainNotFoundException("error.skill.notFound", slug));
Namespace ns = namespaceRepository.findById(skill.getNamespaceId())
.orElseThrow(() -> new DomainNotFoundException("error.namespace.notFound", skill.getNamespaceId()));
String location = "latest".equals(version)
? "/api/v1/skills/" + ns.getSlug() + "/" + skill.getSlug() + "/download"
: "/api/v1/skills/" + ns.getSlug() + "/" + skill.getSlug() + "/versions/" + version + "/download";
return ResponseEntity.status(HttpStatus.FOUND)
.header(HttpHeaders.LOCATION, location)
.build();
}
@GetMapping("/skills")
public ClawHubSearchResponse listSkills(
@RequestParam(defaultValue = "0") int page,
@ -269,7 +285,7 @@ public class ClawHubCompatController {
versionInfo = new ClawHubSkillResponse.VersionInfo(
latestVersionEntity.getVersion(),
versionCreatedAt,
latestVersionEntity.getChangelog(),
latestVersionEntity.getChangelog() == null ? "" : latestVersionEntity.getChangelog(),
null // license
);
}

View file

@ -60,6 +60,12 @@ public class RequestLoggingFilter extends OncePerRequestFilter {
}
sb.append("Response Status: ").append(response.getStatus()).append("\n");
String responseBody = getResponseBody(response);
if (responseBody != null && !responseBody.isBlank()) {
sb.append("Response Body: ").append(responseBody).append("\n");
}
sb.append("Duration: ").append(duration).append("ms\n");
sb.append("===================================");
@ -87,4 +93,16 @@ public class RequestLoggingFilter extends OncePerRequestFilter {
}
return null;
}
private String getResponseBody(ContentCachingResponseWrapper response) {
byte[] buf = response.getContentAsByteArray();
if (buf.length > 0) {
try {
return new String(buf, response.getCharacterEncoding());
} catch (UnsupportedEncodingException e) {
return "[unknown encoding]";
}
}
return null;
}
}

View file

@ -31,6 +31,6 @@ class WellKnownControllerTest {
void clawhubConfig_returns_apiBase() throws Exception {
mockMvc.perform(get("/.well-known/clawhub.json"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.apiBase").value("/api/compat/v1"));
.andExpect(jsonPath("$.apiBase").value("/api/v1"));
}
}

View file

@ -88,7 +88,7 @@ public class SecurityConfig {
if (path == null) {
return false;
}
return path.startsWith("/api/compat/")
return path.startsWith("/api/")
|| path.equals("/api/v1/publish")
|| path.startsWith("/api/v1/auth/device/");
};
@ -117,9 +117,9 @@ public class SecurityConfig {
"/v3/api-docs/**",
"/swagger-ui/**",
"/.well-known/**",
"/api/compat/v1/search",
"/api/compat/v1/resolve/**",
"/api/compat/v1/download/**"
"/api/v1/search",
"/api/v1/resolve/**",
"/api/v1/download/**"
).permitAll()
.requestMatchers("/actuator/prometheus").hasAnyRole("SUPER_ADMIN", "AUDITOR")
.requestMatchers(

View file

@ -84,7 +84,6 @@ public class ApiTokenAuthenticationFilter extends OncePerRequestFilter {
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return !(path.startsWith("/api/v1/")
|| path.startsWith("/api/web/")
|| path.startsWith("/api/compat/"));
|| path.startsWith("/api/web/"));
}
}

View file

@ -67,8 +67,7 @@ public class ApiTokenScopeFilter extends OncePerRequestFilter {
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path == null || (!path.startsWith("/api/v1/")
&& !path.startsWith("/api/web/")
&& !path.startsWith("/api/compat/"));
&& !path.startsWith("/api/web/"));
}
private boolean isApiTokenAuthentication(Authentication authentication) {

View file

@ -31,9 +31,10 @@ public class ApiTokenScopeService {
ScopeRule.allow("GET", "/api/v1/namespaces/*"),
ScopeRule.allow("GET", "/api/web/namespaces"),
ScopeRule.allow("GET", "/api/web/namespaces/*"),
ScopeRule.allow("GET", "/api/compat/v1/search"),
ScopeRule.allow("GET", "/api/compat/v1/resolve/**"),
ScopeRule.allow("GET", "/api/compat/v1/whoami"),
ScopeRule.allow("GET", "/api/v1/search"),
ScopeRule.allow("GET", "/api/v1/resolve/**"),
ScopeRule.allow("GET", "/api/v1/whoami"),
ScopeRule.allow("GET", "/api/v1/download"),
ScopeRule.allow(null, "/.well-known/**"),
ScopeRule.allow(null, "/actuator/health"),
ScopeRule.allow(null, "/v3/api-docs/**"),
@ -47,7 +48,7 @@ public class ApiTokenScopeService {
ScopeRule.require("POST", "/api/v1/skills/*/publish", "skill:publish"),
ScopeRule.require("POST", "/api/web/skills/*/publish", "skill:publish"),
ScopeRule.require("POST", "/api/v1/publish", "skill:publish"),
ScopeRule.require("POST", "/api/compat/v1/publish", "skill:publish")
ScopeRule.require("POST", "/api/v1/publish", "skill:publish")
);
private final ObjectMapper objectMapper;
@ -103,7 +104,7 @@ public class ApiTokenScopeService {
}
private boolean isApiPath(String path) {
return path != null && (path.startsWith("/api/v1/") || path.startsWith("/api/web/") || path.startsWith("/api/compat/"));
return path != null && (path.startsWith("/api/v1/") || path.startsWith("/api/web/") || path.startsWith("/api/"));
}
public record AuthorizationDecision(boolean allowed, String requiredScope, String message) {

View file

@ -564,7 +564,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/compat/v1/publish": {
"/api/v1/publish": {
parameters: {
query?: never;
header?: never;
@ -1012,7 +1012,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/compat/v1/whoami": {
"/api/v1/whoami": {
parameters: {
query?: never;
header?: never;
@ -1028,7 +1028,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/compat/v1/search": {
"/api/v1/search": {
parameters: {
query?: never;
header?: never;
@ -1044,7 +1044,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/compat/v1/resolve/{canonicalSlug}": {
"/api/v1/resolve/{canonicalSlug}": {
parameters: {
query?: never;
header?: never;
@ -1060,7 +1060,7 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/compat/v1/download/{canonicalSlug}": {
"/api/v1/download/{canonicalSlug}": {
parameters: {
query?: never;
header?: never;

View file

@ -13,8 +13,8 @@ export function InstallCommand({ namespace, slug, version }: InstallCommandProps
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const command = version
? `skillhub install ${namespace}/${slug}@${version}`
: `skillhub install ${namespace}/${slug}`
? `clawhub install ${namespace}/${slug}@${version}`
: `clawhub install ${namespace}/${slug}`
const handleCopy = async () => {
try {