autosnippet 1.4.8 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -8
- package/bin/asnip.js +23 -2
- package/bin/ui.js +235 -6
- package/dashboard/dist/assets/index-B7_0pknk.css +1 -0
- package/dashboard/dist/assets/index-CVYuuLVX.js +34 -0
- package/dashboard/dist/assets/{markdown-BKNgxcKr.js → markdown-BCUlfb4D.js} +1 -1
- package/dashboard/dist/assets/{vendor-CZrGEjNs.js → vendor-d1L0Ri9G.js} +77 -57
- package/dashboard/dist/index.html +4 -4
- package/lib/guard/guardRules-iOS.js +781 -0
- package/lib/guard/guardRules.js +102 -0
- package/lib/guard/guardViolations.js +112 -0
- package/lib/infra/nativeUi.js +1 -1
- package/lib/infra/openBrowser.js +1 -1
- package/lib/recipe/parseRecipeMd.js +11 -1
- package/lib/recipe/recipeStats.js +198 -0
- package/lib/search/searchService.js +10 -5
- package/lib/spm/targetScanner.js +51 -0
- package/lib/watch/fileWatcher.js +82 -5
- package/package.json +5 -3
- package/resources/native-ui/README.md +29 -0
- package/resources/native-ui/main.swift +433 -0
- package/scripts/build-native-ui.js +2 -2
- package/scripts/cursor-rules/autosnippet-conventions.mdc +24 -0
- package/scripts/install-cursor-skill.js +36 -2
- package/scripts/mcp-server.js +238 -14
- package/skills/autosnippet-batch-scan/SKILL.md +53 -0
- package/skills/autosnippet-recipes/SKILL.md +8 -5
- package/skills/autosnippet-when/SKILL.md +3 -1
- package/dashboard/dist/assets/index-CdoJx4Qh.js +0 -34
- package/dashboard/dist/assets/index-CvacMLQG.css +0 -1
- /package/{bin → resources}/openChrome.applescript +0 -0
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ asd ui # 启动 Dashboard(建议常驻)
|
|
|
39
39
|
|
|
40
40
|
1. **组建知识库**:`asd ais <Target>` 或 `asd ais --all` → Dashboard Candidates 审核 → Recipe 入库
|
|
41
41
|
2. **依赖关系**:`asd spm-map` 或 Dashboard 刷新
|
|
42
|
-
3. **Cursor 集成**:`asd install:cursor-skill --mcp`(安装 Skills + MCP,需 `asd ui` 运行)
|
|
42
|
+
3. **Cursor 集成**:`asd install:cursor-skill --mcp`(安装 Skills + Cursor 规则 `.cursor/rules/` + MCP,需 `asd ui` 运行)
|
|
43
43
|
4. **语义索引**:`asd ui` 启动时自动 embed;也可手动 `asd embed`
|
|
44
44
|
|
|
45
45
|
### 闭环
|
|
@@ -57,7 +57,7 @@ asd ui # 启动 Dashboard(建议常驻)
|
|
|
57
57
|
| 指令 | 作用 |
|
|
58
58
|
|------|------|
|
|
59
59
|
| `// as:create` / `// as:c` | 无选项时只打开 Dashboard(路径已填),由用户点 Scan File 或 Use Copied Code。`-c` 强制用剪切板(静默创建或打开);`-f` 强制用路径(打开 Dashboard 并自动执行 Scan File) |
|
|
60
|
-
| `// as:guard` / `// as:g` [
|
|
60
|
+
| `// as:guard` / `// as:g` [关键词或规模] | 按知识库 AI 审查;无后缀时仅检查当前文件;后缀 **file** / **target** / **project** 可扩大范围(target=当前 target 内所有源文件,project=项目内所有源文件);其他为检索关键词 |
|
|
61
61
|
| `// as:search` / `// as:s` [关键词] | 从知识库检索并插入 Recipe/Snippet |
|
|
62
62
|
| `// as:include` / `// as:import` | Snippet 内头文件/模块标记,保存时自动注入 |
|
|
63
63
|
|
|
@@ -76,11 +76,17 @@ asd ui # 启动 Dashboard(建议常驻)
|
|
|
76
76
|
| `asd ais [Target]` | AI 扫描 Target → Candidates |
|
|
77
77
|
| `asd search [keyword] --copy` | 搜索并复制第一条到剪贴板 |
|
|
78
78
|
| `asd search [keyword] --pick` | 交互选择后复制/插入 |
|
|
79
|
-
| `asd install:cursor-skill --mcp` | 安装 Skills
|
|
79
|
+
| `asd install:cursor-skill --mcp` | 安装 Skills、Cursor 规则(`.cursor/rules/*.mdc`)并配置 MCP(需 `asd ui` 运行) |
|
|
80
80
|
| `asd install:full` | 全量安装;`--parser` 含 Swift 解析器;`--lancedb` 仅 LanceDB |
|
|
81
81
|
| `asd embed` | 手动构建语义向量索引(`asd ui` 启动时也会自动执行) |
|
|
82
82
|
| `asd spm-map` | 刷新 SPM 依赖映射(依赖关系图数据来源) |
|
|
83
83
|
|
|
84
|
+
### 用 Cursor 做批量扫描
|
|
85
|
+
|
|
86
|
+
除 `asd ais [Target]`(项目内 AI)外,可用 **Cursor 作为批量扫描工具**:在 Cursor 里让 Agent 通过 **MCP 工具**(`autosnippet_get_targets` → `autosnippet_get_target_files` → 按文件提取 → `autosnippet_submit_candidates`)扫描指定 Target,用 Cursor 模型提取候选并提交到 Dashboard,再到 **Candidates** 页审核入库。
|
|
87
|
+
|
|
88
|
+
简单一句:「扫描 BDNetwork ,生产 Recipes 到候选」。AutoSnippet 将所有能力都通过语义交给 Cursor 了。
|
|
89
|
+
|
|
84
90
|
## 全量安装与可选依赖
|
|
85
91
|
|
|
86
92
|
克隆或需完整能力时,**任意目录**执行:
|
|
@@ -100,7 +106,7 @@ asd install:full --lancedb # 仅安装 LanceDB(向量检索更快)
|
|
|
100
106
|
| 脚本 | 作用 |
|
|
101
107
|
|------|------|
|
|
102
108
|
| `scripts/ensure-parse-package.js` | 仅当 `ASD_BUILD_SWIFT_PARSER=1` 时构建 Swift 解析器并打印「正在安装…」;否则打印跳过说明并退出。 |
|
|
103
|
-
| `scripts/build-native-ui.js` | 仅在 macOS 上用本机 Swift 编译 `
|
|
109
|
+
| `scripts/build-native-ui.js` | 仅在 macOS 上用本机 Swift 编译 `resources/native-ui/main.swift` → `resources/native-ui/native-ui`;失败则静默跳过。 |
|
|
104
110
|
|
|
105
111
|
未安装或跳过不影响核心功能。详见 [npm lifecycle scripts](https://docs.npmjs.com/cli/v10/using-npm/scripts#life-cycle-scripts)。
|
|
106
112
|
|
|
@@ -108,15 +114,15 @@ asd install:full --lancedb # 仅安装 LanceDB(向量检索更快)
|
|
|
108
114
|
|
|
109
115
|
- **AI**:项目根 `.env`,设置 `ASD_GOOGLE_API_KEY` 等(见 `.env.example`)。可选 `ASD_AI_PROVIDER`、代理等。
|
|
110
116
|
- **LanceDB**:`asd install:full --lancedb`,在 boxspec 的 `context.storage.adapter` 中配置 `"lance"`。
|
|
111
|
-
- **Native UI**(可选):macOS 上 `npm install` 会尝试构建 `
|
|
117
|
+
- **Native UI**(可选):macOS 上 `npm install` 会尝试构建 `resources/native-ui/native-ui`(需本机 Swift);未构建时回退到 AppleScript/inquirer,功能正常。
|
|
112
118
|
|
|
113
119
|
## Recipe 格式
|
|
114
120
|
|
|
115
121
|
完整 Recipe 为 Markdown 文件,需包含:
|
|
116
122
|
|
|
117
123
|
- **Frontmatter**(`---` 包裹的 YAML):`title`、`trigger` 必填;可选 `category`、`language`、`headers` 等
|
|
118
|
-
-
|
|
119
|
-
-
|
|
124
|
+
- **Snippet / Code Reference**:下接代码块,供 Snippet 与检索使用
|
|
125
|
+
- **AI Context / Usage Guide**:使用说明,供 AI 与 Guard 检索
|
|
120
126
|
|
|
121
127
|
## 术语
|
|
122
128
|
|
|
@@ -125,7 +131,6 @@ asd install:full --lancedb # 仅安装 LanceDB(向量检索更快)
|
|
|
125
131
|
- **项目根**:含 `AutoSnippetRoot.boxspec.json` 的目录
|
|
126
132
|
|
|
127
133
|
**详细介绍**:启动 `asd ui` 后访问 Dashboard → **使用说明** 页;或参阅 [使用文档](docs/使用文档.md)(含 Skills 一览、AI 配置、闭环详解等)。
|
|
128
|
-
**发布流程**:CI 与 npm 发布见 [发布流程](docs/发布流程.md)(打 tag `v*` 触发自动发布)。
|
|
129
134
|
|
|
130
135
|
---
|
|
131
136
|
|
package/bin/asnip.js
CHANGED
|
@@ -868,13 +868,24 @@ commander
|
|
|
868
868
|
|
|
869
869
|
// --copy: 复制第一条到剪贴板
|
|
870
870
|
if (options.copy) {
|
|
871
|
-
const
|
|
871
|
+
const selected = results[0];
|
|
872
|
+
const code = selected.code || selected.content || '';
|
|
872
873
|
if (nativeUi.writeClipboard(code)) {
|
|
873
|
-
console.log(`✅ 已复制到剪贴板 (${
|
|
874
|
+
console.log(`✅ 已复制到剪贴板 (${selected.title}),Cmd+V 粘贴`);
|
|
874
875
|
} else {
|
|
875
876
|
console.log('--- 第一条结果 ---\n');
|
|
876
877
|
console.log(code);
|
|
877
878
|
}
|
|
879
|
+
try {
|
|
880
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
881
|
+
if (selected.type === 'recipe') {
|
|
882
|
+
recipeStats.recordRecipeUsage(projectRoot, {
|
|
883
|
+
trigger: selected.trigger,
|
|
884
|
+
recipeFilePath: selected.name,
|
|
885
|
+
source: 'human'
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
} catch (_) {}
|
|
878
889
|
return;
|
|
879
890
|
}
|
|
880
891
|
|
|
@@ -894,6 +905,16 @@ commander
|
|
|
894
905
|
console.log('已取消');
|
|
895
906
|
return;
|
|
896
907
|
}
|
|
908
|
+
try {
|
|
909
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
910
|
+
if (selected.type === 'recipe') {
|
|
911
|
+
recipeStats.recordRecipeUsage(projectRoot, {
|
|
912
|
+
trigger: selected.trigger,
|
|
913
|
+
recipeFilePath: selected.name,
|
|
914
|
+
source: 'human'
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
} catch (_) {}
|
|
897
918
|
if (options.insert) {
|
|
898
919
|
const insertPath = path.isAbsolute(options.insert) ? options.insert : path.join(projectRoot, options.insert);
|
|
899
920
|
try {
|
package/bin/ui.js
CHANGED
|
@@ -128,7 +128,7 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
128
128
|
}
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
// API: 上下文语义搜索(供 Agent/Skill
|
|
131
|
+
// API: 上下文语义搜索(供 Agent/Skill 调用),返回项合并 recipe-stats 供 AI 可见
|
|
132
132
|
app.post('/api/context/search', async (req, res) => {
|
|
133
133
|
try {
|
|
134
134
|
const { query, limit = 5, filter } = req.body;
|
|
@@ -139,7 +139,37 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
139
139
|
const ai = await AiFactory.getProvider(projectRoot);
|
|
140
140
|
if (!ai) return res.status(400).json({ error: 'AI 未配置,无法进行语义检索' });
|
|
141
141
|
const service = getInstance(projectRoot);
|
|
142
|
-
|
|
142
|
+
let items = await service.search(query, { limit, filter });
|
|
143
|
+
try {
|
|
144
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
145
|
+
// MCP/Agent 引用:本次搜索返回的 Recipe 记一次 ai 使用
|
|
146
|
+
for (const it of items) {
|
|
147
|
+
const meta = it.metadata || {};
|
|
148
|
+
if (meta.type !== 'recipe') continue;
|
|
149
|
+
const sourcePath = meta.sourcePath || meta.source || it.id || '';
|
|
150
|
+
const fileKey = sourcePath ? path.basename(sourcePath) : null;
|
|
151
|
+
if (fileKey) recipeStats.recordRecipeUsage(projectRoot, { recipeFilePath: fileKey, source: 'ai' });
|
|
152
|
+
}
|
|
153
|
+
const stats = recipeStats.getRecipeStats(projectRoot);
|
|
154
|
+
const byFileEntries = Object.values(stats.byFile || {});
|
|
155
|
+
items = items.map((it) => {
|
|
156
|
+
const meta = it.metadata || {};
|
|
157
|
+
const fileKey = path.basename(meta.sourcePath || meta.source || it.id || '');
|
|
158
|
+
const entry = fileKey ? (stats.byFile || {})[fileKey] : null;
|
|
159
|
+
if (!entry) return it;
|
|
160
|
+
const score = recipeStats.getAuthorityScore(entry, byFileEntries, {});
|
|
161
|
+
return {
|
|
162
|
+
...it,
|
|
163
|
+
stats: {
|
|
164
|
+
authority: entry.authority ?? 0,
|
|
165
|
+
guardUsageCount: entry.guardUsageCount ?? 0,
|
|
166
|
+
humanUsageCount: entry.humanUsageCount ?? 0,
|
|
167
|
+
aiUsageCount: entry.aiUsageCount ?? 0,
|
|
168
|
+
authorityScore: Math.round(score * 100) / 100
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
} catch (_) {}
|
|
143
173
|
res.json({ items });
|
|
144
174
|
} catch (err) {
|
|
145
175
|
console.error(`[API Error]`, err);
|
|
@@ -399,10 +429,34 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
399
429
|
};
|
|
400
430
|
|
|
401
431
|
const allMdFiles = getAllFiles(recipesDir);
|
|
432
|
+
let statsMap = {};
|
|
433
|
+
try {
|
|
434
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
435
|
+
const stats = recipeStats.getRecipeStats(projectRoot);
|
|
436
|
+
statsMap = stats.byFile || {};
|
|
437
|
+
} catch (_) {}
|
|
438
|
+
const byFileEntries = Object.values(statsMap);
|
|
402
439
|
recipes = allMdFiles.map(filePath => {
|
|
403
440
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
404
|
-
const relativePath = path.relative(recipesDir, filePath);
|
|
405
|
-
|
|
441
|
+
const relativePath = path.relative(recipesDir, filePath).replace(/\\/g, '/');
|
|
442
|
+
const fileKey = path.basename(relativePath);
|
|
443
|
+
const entry = statsMap[fileKey];
|
|
444
|
+
let stats = null;
|
|
445
|
+
if (entry) {
|
|
446
|
+
try {
|
|
447
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
448
|
+
const score = recipeStats.getAuthorityScore(entry, byFileEntries, {});
|
|
449
|
+
stats = {
|
|
450
|
+
authority: entry.authority ?? 0,
|
|
451
|
+
guardUsageCount: entry.guardUsageCount ?? 0,
|
|
452
|
+
humanUsageCount: entry.humanUsageCount ?? 0,
|
|
453
|
+
aiUsageCount: entry.aiUsageCount ?? 0,
|
|
454
|
+
lastUsedAt: entry.lastUsedAt || null,
|
|
455
|
+
authorityScore: Math.round(score * 100) / 100
|
|
456
|
+
};
|
|
457
|
+
} catch (_) {}
|
|
458
|
+
}
|
|
459
|
+
return { name: relativePath, content, stats };
|
|
406
460
|
});
|
|
407
461
|
}
|
|
408
462
|
|
|
@@ -732,6 +786,154 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
732
786
|
}
|
|
733
787
|
});
|
|
734
788
|
|
|
789
|
+
// API: 设置 Recipe 权威分(0~5)
|
|
790
|
+
app.post('/api/recipes/set-authority', async (req, res) => {
|
|
791
|
+
try {
|
|
792
|
+
const { name, authority } = req.body;
|
|
793
|
+
if (name == null || authority == null) {
|
|
794
|
+
return res.status(400).json({ error: 'name and authority (0-5) are required' });
|
|
795
|
+
}
|
|
796
|
+
const v = Math.max(0, Math.min(5, Number(authority)));
|
|
797
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
798
|
+
recipeStats.setAuthority(projectRoot, { recipeFilePath: name }, v);
|
|
799
|
+
res.json({ success: true, name, authority: v });
|
|
800
|
+
} catch (err) {
|
|
801
|
+
console.error(`[API Error]`, err);
|
|
802
|
+
res.status(500).json({ error: err.message });
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// API: 记录 Recipe 使用(供 MCP「确认代码使用」等场景,记为 human 使用)
|
|
807
|
+
app.post('/api/recipes/record-usage', async (req, res) => {
|
|
808
|
+
try {
|
|
809
|
+
const { recipeFilePaths, source } = req.body;
|
|
810
|
+
const list = Array.isArray(recipeFilePaths) ? recipeFilePaths : (recipeFilePaths != null ? [String(recipeFilePaths)] : []);
|
|
811
|
+
const src = source === 'human' || source === 'guard' || source === 'ai' ? source : 'human';
|
|
812
|
+
if (list.length === 0) {
|
|
813
|
+
return res.status(400).json({ error: 'recipeFilePaths (array or single string) is required' });
|
|
814
|
+
}
|
|
815
|
+
const recipeStats = require('../lib/recipe/recipeStats');
|
|
816
|
+
for (const name of list) {
|
|
817
|
+
const fileKey = typeof name === 'string' && name.trim() ? path.basename(name.trim()) : null;
|
|
818
|
+
if (fileKey) recipeStats.recordRecipeUsage(projectRoot, { recipeFilePath: fileKey, source: src });
|
|
819
|
+
}
|
|
820
|
+
res.json({ success: true, count: list.length, source: src });
|
|
821
|
+
} catch (err) {
|
|
822
|
+
console.error(`[API Error]`, err);
|
|
823
|
+
res.status(500).json({ error: err.message });
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
// API: Guard 规则表
|
|
828
|
+
app.get('/api/guard/rules', (req, res) => {
|
|
829
|
+
try {
|
|
830
|
+
const guardRules = require('../lib/guard/guardRules');
|
|
831
|
+
const data = guardRules.getGuardRules(projectRoot);
|
|
832
|
+
res.json(data);
|
|
833
|
+
} catch (err) {
|
|
834
|
+
console.error(`[API Error]`, err);
|
|
835
|
+
res.status(500).json({ error: err.message });
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
// API: 新增或更新一条 Guard 规则(Dashboard / AI 写入规则)
|
|
840
|
+
app.post('/api/guard/rules', (req, res) => {
|
|
841
|
+
try {
|
|
842
|
+
const { ruleId, message, severity, pattern, languages, note, dimension } = req.body;
|
|
843
|
+
if (!ruleId || !message || !severity || !pattern || !languages) {
|
|
844
|
+
return res.status(400).json({ error: 'ruleId、message、severity、pattern、languages 为必填' });
|
|
845
|
+
}
|
|
846
|
+
const guardRules = require('../lib/guard/guardRules');
|
|
847
|
+
const result = guardRules.addOrUpdateRule(projectRoot, ruleId, {
|
|
848
|
+
message,
|
|
849
|
+
severity,
|
|
850
|
+
pattern,
|
|
851
|
+
languages: Array.isArray(languages) ? languages : [languages].filter(Boolean),
|
|
852
|
+
note,
|
|
853
|
+
...(dimension === 'file' || dimension === 'target' || dimension === 'project' ? { dimension } : {})
|
|
854
|
+
});
|
|
855
|
+
res.json({ success: true, ...result });
|
|
856
|
+
} catch (err) {
|
|
857
|
+
console.error(`[API Error]`, err);
|
|
858
|
+
res.status(500).json({ error: err.message });
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// API: 根据用户语义描述由 AI 生成一条 Guard 规则(返回表单用,用户可修改后确认写入)
|
|
863
|
+
app.post('/api/guard/rules/generate', async (req, res) => {
|
|
864
|
+
try {
|
|
865
|
+
const { description } = req.body;
|
|
866
|
+
if (!description || typeof description !== 'string' || !description.trim()) {
|
|
867
|
+
return res.status(400).json({ error: '请提供语义描述(description)' });
|
|
868
|
+
}
|
|
869
|
+
const ai = await AiFactory.getProvider(projectRoot);
|
|
870
|
+
if (!ai) {
|
|
871
|
+
return res.status(400).json({ error: 'AI 未配置,无法生成规则。请先在项目根配置 .env 或 boxspec.ai' });
|
|
872
|
+
}
|
|
873
|
+
const prompt = `用户希望添加一条 Guard 静态检查规则,语义描述如下:
|
|
874
|
+
|
|
875
|
+
「${description.trim()}」
|
|
876
|
+
|
|
877
|
+
请根据上述描述,生成一条规则。你只能回复一个合法的 JSON 对象,不要包含任何其他文字、markdown 或代码块标记。JSON 必须包含且仅包含以下字段:
|
|
878
|
+
- ruleId: 字符串,英文、短横线格式,如 no-main-thread-sync
|
|
879
|
+
- message: 字符串,违反时提示的说明(中文或英文)
|
|
880
|
+
- severity: 字符串,只能是 "error" 或 "warning"
|
|
881
|
+
- pattern: 字符串,用于对代码每一行匹配的正则表达式;在 JSON 中反斜杠需双写,如 "dispatch_sync\\\\s*\\\\("
|
|
882
|
+
- languages: 数组,元素为 "objc" 和/或 "swift",如 ["objc","swift"]
|
|
883
|
+
- note: 字符串,可选,备注说明
|
|
884
|
+
- dimension: 字符串,可选,审查规模。只能是 "file"、"target"、"project" 之一,或不写该字段(表示任意规模均运行)。file=仅同文件内审查,target=仅同 SPM target 内,project=仅整个项目内。根据规则语义选择合适规模。
|
|
885
|
+
|
|
886
|
+
只输出这一份 JSON,不要解释。`;
|
|
887
|
+
const raw = await ai.chat(prompt);
|
|
888
|
+
let text = (raw && typeof raw === 'string' ? raw : String(raw)).trim();
|
|
889
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
890
|
+
if (jsonMatch) text = jsonMatch[0];
|
|
891
|
+
text = text.replace(/^```(?:json)?\s*|\s*```$/g, '').trim();
|
|
892
|
+
const rule = JSON.parse(text);
|
|
893
|
+
if (!rule.ruleId || !rule.message || !rule.pattern) {
|
|
894
|
+
return res.status(400).json({ error: 'AI 返回的规则缺少 ruleId、message 或 pattern' });
|
|
895
|
+
}
|
|
896
|
+
const out = {
|
|
897
|
+
ruleId: String(rule.ruleId).trim().replace(/\s+/g, '-'),
|
|
898
|
+
message: String(rule.message || '').trim(),
|
|
899
|
+
severity: rule.severity === 'error' ? 'error' : 'warning',
|
|
900
|
+
pattern: String(rule.pattern || '').trim(),
|
|
901
|
+
languages: Array.isArray(rule.languages) ? rule.languages.filter(l => l === 'objc' || l === 'swift') : ['objc', 'swift'],
|
|
902
|
+
note: rule.note != null ? String(rule.note).trim() : '',
|
|
903
|
+
dimension: rule.dimension === 'file' || rule.dimension === 'target' || rule.dimension === 'project' ? rule.dimension : ''
|
|
904
|
+
};
|
|
905
|
+
if (out.languages.length === 0) out.languages = ['objc', 'swift'];
|
|
906
|
+
res.json(out);
|
|
907
|
+
} catch (err) {
|
|
908
|
+
console.error(`[API Error]`, err);
|
|
909
|
+
res.status(500).json({ error: err.message || 'AI 生成规则失败' });
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
// API: Guard 违反记录
|
|
914
|
+
app.get('/api/guard/violations', (req, res) => {
|
|
915
|
+
try {
|
|
916
|
+
const guardViolations = require('../lib/guard/guardViolations');
|
|
917
|
+
const data = guardViolations.getGuardViolations(projectRoot);
|
|
918
|
+
res.json(data);
|
|
919
|
+
} catch (err) {
|
|
920
|
+
console.error(`[API Error]`, err);
|
|
921
|
+
res.status(500).json({ error: err.message });
|
|
922
|
+
}
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
// API: 清空 Guard 违反记录
|
|
926
|
+
app.post('/api/guard/violations/clear', (req, res) => {
|
|
927
|
+
try {
|
|
928
|
+
const guardViolations = require('../lib/guard/guardViolations');
|
|
929
|
+
guardViolations.clearRuns(projectRoot);
|
|
930
|
+
res.json({ success: true });
|
|
931
|
+
} catch (err) {
|
|
932
|
+
console.error(`[API Error]`, err);
|
|
933
|
+
res.status(500).json({ error: err.message });
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
|
|
735
937
|
// API: 获取 SPM Targets
|
|
736
938
|
app.get('/api/spm/targets', async (req, res) => {
|
|
737
939
|
try {
|
|
@@ -787,10 +989,20 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
787
989
|
}
|
|
788
990
|
});
|
|
789
991
|
|
|
790
|
-
// API: 获取 Target 将要扫描的文件列表(不调用 AI)
|
|
992
|
+
// API: 获取 Target 将要扫描的文件列表(不调用 AI)。支持 body.target 或 body.targetName(按名称查 target)
|
|
791
993
|
app.post('/api/spm/target-files', async (req, res) => {
|
|
792
994
|
try {
|
|
793
|
-
|
|
995
|
+
let target = req.body?.target;
|
|
996
|
+
if (!target && req.body?.targetName) {
|
|
997
|
+
const targets = await targetScanner.listAllTargets(projectRoot);
|
|
998
|
+
target = targets.find(t => t.name === req.body.targetName);
|
|
999
|
+
if (!target) {
|
|
1000
|
+
return res.status(404).json({ error: `未找到 Target: ${req.body.targetName}` });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (!target) {
|
|
1004
|
+
return res.status(400).json({ error: '需要 body.target 或 body.targetName' });
|
|
1005
|
+
}
|
|
794
1006
|
const files = await targetScanner.getTargetFilesContent(target);
|
|
795
1007
|
const scannedFiles = files.map(f => ({
|
|
796
1008
|
name: f.name,
|
|
@@ -842,6 +1054,23 @@ function launch(projectRoot, port = 3000, options = {}) {
|
|
|
842
1054
|
}
|
|
843
1055
|
});
|
|
844
1056
|
|
|
1057
|
+
// API: 追加候选(供 Cursor/MCP 批量扫描:Cursor AI 提取后提交,无需项目内 AI)
|
|
1058
|
+
app.post('/api/candidates/append', async (req, res) => {
|
|
1059
|
+
try {
|
|
1060
|
+
const { targetName, items, source, expiresInHours } = req.body;
|
|
1061
|
+
if (!targetName || !Array.isArray(items) || items.length === 0) {
|
|
1062
|
+
return res.status(400).json({ error: '需要 targetName 与 items(数组,至少一条)' });
|
|
1063
|
+
}
|
|
1064
|
+
const safeSource = (source && typeof source === 'string') ? source : 'cursor-scan';
|
|
1065
|
+
const hours = typeof expiresInHours === 'number' ? expiresInHours : 24;
|
|
1066
|
+
await candidateService.appendCandidates(projectRoot, String(targetName), items, safeSource, hours);
|
|
1067
|
+
res.json({ ok: true, count: items.length, targetName: String(targetName) });
|
|
1068
|
+
} catch (err) {
|
|
1069
|
+
console.error(`[API Error]`, err);
|
|
1070
|
+
res.status(500).json({ error: err.message });
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
|
|
845
1074
|
// API: 删除候选内容
|
|
846
1075
|
app.post('/api/candidates/delete', async (req, res) => {
|
|
847
1076
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-600:oklch(59.6% .145 163.225);--color-cyan-50:oklch(98.4% .019 200.873);--color-cyan-100:oklch(95.6% .045 203.388);--color-cyan-600:oklch(60.9% .126 221.723);--color-blue-50:oklch(97% .014 254.604);--color-blue-100:oklch(93.2% .032 255.585);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-300:oklch(80.9% .105 251.813);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-indigo-50:oklch(96.2% .018 272.314);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-600:oklch(51.1% .262 276.966);--color-purple-50:oklch(97.7% .014 308.299);--color-purple-100:oklch(94.6% .033 307.174);--color-purple-600:oklch(55.8% .288 302.321);--color-pink-50:oklch(97.1% .014 343.198);--color-pink-100:oklch(94.8% .028 342.258);--color-pink-600:oklch(59.2% .249 .584);--color-slate-50:oklch(98.4% .003 247.858);--color-slate-100:oklch(96.8% .007 247.896);--color-slate-200:oklch(92.9% .013 255.508);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-slate-700:oklch(37.2% .044 257.287);--color-slate-800:oklch(27.9% .041 260.031);--color-slate-900:oklch(20.8% .042 265.755);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wider:.05em;--tracking-widest:.1em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--ease-out:cubic-bezier(0,0,.2,1);--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-0{inset:calc(var(--spacing)*0)}.top-0\.5{top:calc(var(--spacing)*.5)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing)*4)}.top-full{top:100%}.right-0{right:calc(var(--spacing)*0)}.right-0\.5{right:calc(var(--spacing)*.5)}.right-4{right:calc(var(--spacing)*4)}.left-0\.5{left:calc(var(--spacing)*.5)}.left-3{left:calc(var(--spacing)*3)}.z-10{z-index:10}.z-50{z-index:50}.m-auto{margin:auto}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-auto{margin-inline:auto}.my-3{margin-block:calc(var(--spacing)*3)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-1\.5{margin-top:calc(var(--spacing)*1.5)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-6{margin-top:calc(var(--spacing)*6)}.mr-4{margin-right:calc(var(--spacing)*4)}.mb-0\.5{margin-bottom:calc(var(--spacing)*.5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-1\.5{margin-left:calc(var(--spacing)*1.5)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-6{-webkit-line-clamp:6;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.h-1\.5{height:calc(var(--spacing)*1.5)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-4{height:calc(var(--spacing)*4)}.h-8{height:calc(var(--spacing)*8)}.h-10{height:calc(var(--spacing)*10)}.h-16{height:calc(var(--spacing)*16)}.h-64{height:calc(var(--spacing)*64)}.h-\[85vh\]{height:85vh}.h-full{height:100%}.h-screen{height:100vh}.max-h-32{max-height:calc(var(--spacing)*32)}.max-h-60{max-height:calc(var(--spacing)*60)}.max-h-\[85vh\]{max-height:85vh}.max-h-\[90vh\]{max-height:90vh}.max-h-\[280px\]{max-height:280px}.min-h-0{min-height:calc(var(--spacing)*0)}.min-h-\[80px\]{min-height:80px}.min-h-\[100px\]{min-height:100px}.min-h-\[320px\]{min-height:320px}.min-h-\[400px\]{min-height:400px}.min-h-\[480px\]{min-height:480px}.w-1\.5{width:calc(var(--spacing)*1.5)}.w-3{width:calc(var(--spacing)*3)}.w-8{width:calc(var(--spacing)*8)}.w-10{width:calc(var(--spacing)*10)}.w-16{width:calc(var(--spacing)*16)}.w-64{width:calc(var(--spacing)*64)}.w-72{width:calc(var(--spacing)*72)}.w-80{width:calc(var(--spacing)*80)}.w-96{width:calc(var(--spacing)*96)}.w-\[512px\]{width:512px}.w-fit{width:fit-content}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[80\%\]{max-width:80%}.max-w-\[85\%\]{max-width:85%}.max-w-\[1400px\]{max-width:1400px}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-none{max-width:none}.min-w-\[32px\]{min-width:32px}.min-w-\[200px\]{min-width:200px}.min-w-full{min-width:100%}.flex-1{flex:1}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.scale-105{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.rotate-180{rotate:180deg}.animate-pulse{animation:var(--animate-pulse)}.animate-spin{animation:var(--animate-spin)}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.resize-y{resize:vertical}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.justify-start{justify-content:flex-start}.gap-0\.5{gap:calc(var(--spacing)*.5)}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*.5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*.5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-8>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*8)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-8{column-gap:calc(var(--spacing)*8)}.gap-y-4{row-gap:calc(var(--spacing)*4)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-4{border-style:var(--tw-border-style);border-width:4px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-none{--tw-border-style:none;border-style:none}.border-amber-100{border-color:var(--color-amber-100)}.border-amber-200{border-color:var(--color-amber-200)}.border-blue-100{border-color:var(--color-blue-100)}.border-blue-200{border-color:var(--color-blue-200)}.border-blue-600{border-color:var(--color-blue-600)}.border-cyan-100{border-color:var(--color-cyan-100)}.border-emerald-100{border-color:var(--color-emerald-100)}.border-indigo-100{border-color:var(--color-indigo-100)}.border-orange-100{border-color:var(--color-orange-100)}.border-pink-100{border-color:var(--color-pink-100)}.border-purple-100{border-color:var(--color-purple-100)}.border-red-200{border-color:var(--color-red-200)}.border-slate-100{border-color:var(--color-slate-100)}.border-slate-200{border-color:var(--color-slate-200)}.border-transparent{border-color:#0000}.border-t-blue-600{border-top-color:var(--color-blue-600)}.border-t-transparent{border-top-color:#0000}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-100{background-color:var(--color-amber-100)}.bg-amber-600{background-color:var(--color-amber-600)}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-cyan-50{background-color:var(--color-cyan-50)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-indigo-50{background-color:var(--color-indigo-50)}.bg-orange-50{background-color:var(--color-orange-50)}.bg-pink-50{background-color:var(--color-pink-50)}.bg-purple-50{background-color:var(--color-purple-50)}.bg-red-50{background-color:var(--color-red-50)}.bg-red-100{background-color:var(--color-red-100)}.bg-slate-50{background-color:var(--color-slate-50)}.bg-slate-50\/50{background-color:#f8fafc80}@supports (color:color-mix(in lab,red,red)){.bg-slate-50\/50{background-color:color-mix(in oklab,var(--color-slate-50)50%,transparent)}}.bg-slate-50\/80{background-color:#f8fafccc}@supports (color:color-mix(in lab,red,red)){.bg-slate-50\/80{background-color:color-mix(in oklab,var(--color-slate-50)80%,transparent)}}.bg-slate-100{background-color:var(--color-slate-100)}.bg-slate-200{background-color:var(--color-slate-200)}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-600{background-color:var(--color-slate-600)}.bg-slate-900{background-color:var(--color-slate-900)}.bg-slate-900\/50{background-color:#0f172b80}@supports (color:color-mix(in lab,red,red)){.bg-slate-900\/50{background-color:color-mix(in oklab,var(--color-slate-900)50%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/90{background-color:#ffffffe6}@supports (color:color-mix(in lab,red,red)){.bg-white\/90{background-color:color-mix(in oklab,var(--color-white)90%,transparent)}}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-1\.5{padding-inline:calc(var(--spacing)*1.5)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.py-2{padding-block:calc(var(--spacing)*2)}.py-2\.5{padding-block:calc(var(--spacing)*2.5)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-12{padding-right:calc(var(--spacing)*12)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pl-0{padding-left:calc(var(--spacing)*0)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-6{padding-left:calc(var(--spacing)*6)}.pl-10{padding-left:calc(var(--spacing)*10)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-500{color:var(--color-amber-500)}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-blue-400{color:var(--color-blue-400)}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-cyan-600{color:var(--color-cyan-600)}.text-emerald-600{color:var(--color-emerald-600)}.text-indigo-600{color:var(--color-indigo-600)}.text-orange-500{color:var(--color-orange-500)}.text-orange-600{color:var(--color-orange-600)}.text-pink-600{color:var(--color-pink-600)}.text-purple-600{color:var(--color-purple-600)}.text-red-500{color:var(--color-red-500)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-red-800{color:var(--color-red-800)}.text-slate-100{color:var(--color-slate-100)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-slate-700{color:var(--color-slate-700)}.text-slate-800{color:var(--color-slate-800)}.text-slate-900{color:var(--color-slate-900)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-80{opacity:.8}.opacity-90{opacity:.9}.opacity-100{opacity:1}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-blue-200{--tw-ring-color:var(--color-blue-200)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.backdrop-blur-\[2px\]{--tw-backdrop-blur:blur(2px);-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,)var(--tw-backdrop-brightness,)var(--tw-backdrop-contrast,)var(--tw-backdrop-grayscale,)var(--tw-backdrop-hue-rotate,)var(--tw-backdrop-invert,)var(--tw-backdrop-opacity,)var(--tw-backdrop-saturate,)var(--tw-backdrop-sepia,)}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-500{--tw-duration:.5s;transition-duration:.5s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}@media(hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.first\:mt-0:first-child{margin-top:calc(var(--spacing)*0)}.last\:mb-0:last-child{margin-bottom:calc(var(--spacing)*0)}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.last\:pb-0:last-child{padding-bottom:calc(var(--spacing)*0)}@media(hover:hover){.hover\:border-blue-300:hover{border-color:var(--color-blue-300)}.hover\:border-slate-200:hover{border-color:var(--color-slate-200)}.hover\:bg-amber-700:hover{background-color:var(--color-amber-700)}.hover\:bg-blue-50:hover{background-color:var(--color-blue-50)}.hover\:bg-blue-50\/50:hover{background-color:#eff6ff80}@supports (color:color-mix(in lab,red,red)){.hover\:bg-blue-50\/50:hover{background-color:color-mix(in oklab,var(--color-blue-50)50%,transparent)}}.hover\:bg-blue-100:hover{background-color:var(--color-blue-100)}.hover\:bg-blue-700:hover{background-color:var(--color-blue-700)}.hover\:bg-orange-50:hover{background-color:var(--color-orange-50)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-red-200:hover{background-color:var(--color-red-200)}.hover\:bg-slate-50:hover{background-color:var(--color-slate-50)}.hover\:bg-slate-100:hover{background-color:var(--color-slate-100)}.hover\:bg-slate-200:hover{background-color:var(--color-slate-200)}.hover\:bg-slate-700:hover{background-color:var(--color-slate-700)}.hover\:bg-slate-800:hover{background-color:var(--color-slate-800)}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:text-amber-400:hover{color:var(--color-amber-400)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-orange-600:hover{color:var(--color-orange-600)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-red-600:hover{color:var(--color-red-600)}.hover\:text-red-700:hover{color:var(--color-red-700)}.hover\:text-slate-600:hover{color:var(--color-slate-600)}.hover\:text-slate-700:hover{color:var(--color-slate-700)}.hover\:text-slate-900:hover{color:var(--color-slate-900)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}.focus\:border-blue-500:focus{border-color:var(--color-blue-500)}.focus\:border-transparent:focus{border-color:#0000}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-blue-500\/10:focus{--tw-ring-color:#3080ff1a}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-500\/10:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-500)10%,transparent)}}.focus\:ring-blue-500\/20:focus{--tw-ring-color:#3080ff33}@supports (color:color-mix(in lab,red,red)){.focus\:ring-blue-500\/20:focus{--tw-ring-color:color-mix(in oklab,var(--color-blue-500)20%,transparent)}}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:scale-95:active{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x)var(--tw-scale-y)}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-slate-300:disabled{background-color:var(--color-slate-300)}.disabled\:opacity-30:disabled{opacity:.3}.disabled\:opacity-50:disabled{opacity:.5}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}.no-scrollbar::-webkit-scrollbar{display:none}.no-scrollbar{-ms-overflow-style:none;scrollbar-width:none}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}
|