skill-forge-hub 1.2.0 → 1.3.0
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.en.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
Version history of the SkillForge npm package `skill-forge-hub` (command name `skill-forge`).
|
|
6
6
|
|
|
7
|
+
## 1.3.0 (2026-11-12)
|
|
8
|
+
|
|
9
|
+
**prompt-pouch native dialogs eliminated: unified in-page confirm dialog in three modes**
|
|
10
|
+
|
|
11
|
+
- New cfMask unified dialog (confirm / alert / prompt modes) with Promise-based uiConfirm / uiAlert / uiPrompt; all 11 native window.confirm / alert / prompt calls absorbed — delete prompt/group, quick tag add, overwrite import, restore backup (corrupt notices x2 + overwrite confirm), auto-backup authorization (dual-semantics "Authorize directory" / "Not now"), change backup directory — zero native white dialogs left
|
|
12
|
+
- Dialog styling matches the page: reuses the .modal-mask overlay system (frosted glass + pop-in animation), adopts the five theme variables (warm gold / tech blue / IDEA dark / IDEA light / mint auto-recolor); destructive actions (delete / overwrite / restore) get a red-tinted confirm button; Esc / mask click cancel, prompt mode submits on Enter with default value prefilled
|
|
13
|
+
- Version stamp prompt-pouch@9 -> @10; usage-guide gains a "unified confirm dialog" entry
|
|
14
|
+
|
|
7
15
|
## 1.2.0 (2026-11-12)
|
|
8
16
|
|
|
9
17
|
**Chinese-style poster octet + collapsible dual docks + directory-visibility trio**
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
SkillForge(技能锻造坊)npm 包 `skill-forge-hub`(命令名 `skill-forge`)版本变更记录。
|
|
6
6
|
|
|
7
|
+
## 1.3.0(2026-11-12)
|
|
8
|
+
|
|
9
|
+
**prompt-pouch 原生弹框清零:页内统一确认弹框三形态**
|
|
10
|
+
|
|
11
|
+
- 新增 cfMask 统一确认弹框(确认 / 提示 / 输入三形态),Promise 化 uiConfirm / uiAlert / uiPrompt;11 处原生 window.confirm / alert / prompt 全部收编——删除提示词/分组、标签快捷加、覆盖导入、恢复备份(损坏提示 ×2 + 覆盖确认)、自动备份授权(「授权目录」/「暂不」双语义)、更换备份目录,浏览器原生白底弹框清零
|
|
12
|
+
- 弹框风格与页面同源:复用 .modal-mask 遮罩体系(毛玻璃 + 弹入动画),吃五主题变量(暖棕金 / 深蓝科技 / IDEA 黑亮 / IDEA 白亮 / 薄荷绿自动换色);危险操作(删除/覆盖/恢复)确认键红调按钮;Esc / 遮罩点击均可取消,prompt 形态 Enter 提交、默认值回填
|
|
13
|
+
- 版本戳 prompt-pouch@9 → @10;usage-guide 补充「统一确认弹框」说明条目
|
|
14
|
+
|
|
7
15
|
## 1.2.0(2026-11-12)
|
|
8
16
|
|
|
9
17
|
**海报国风八式 + 双坞折叠 + 目录能见度三件套**
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 还在每次从头教 AI?我把 21 种重复劳动锻造成了开箱即用的技能包
|
|
2
|
+
|
|
3
|
+
> 项目:SkillForge(技能锻造坊) · 开源协议:MIT · 仓库:https://github.com/windfallsheng/SkillForge
|
|
4
|
+
> 一句话安装:`npm i -g skill-forge-hub && skill-forge init`
|
|
5
|
+
|
|
6
|
+
## 一、一个熟悉的场景
|
|
7
|
+
|
|
8
|
+
你一定经历过:让 AI 写日报,它这次三段、下次五段;让它做 PPT,封面风格全靠运气;上个月调教好的「完美提示词」,这个月翻遍聊天记录也找不回来。
|
|
9
|
+
|
|
10
|
+
问题不在 AI 不够聪明,而在**每次对话都是零上下文起跑**。AI 记得住你上一句话,记不住你上个月的偏好。
|
|
11
|
+
|
|
12
|
+
于是有了 SkillForge——一个「AI 手艺」的锻造坊:把日复一日的重复劳作,锻造成一句指令就能完成的顺手工具。每个技能都是一份写给 AI 的手艺说明:入口薄薄一张 SKILL.md,重内容按阶段拆成资源分册,AI 执行到哪一步才读哪一册,上下文占用极小。
|
|
13
|
+
|
|
14
|
+
## 二、21 个技能,七个车间
|
|
15
|
+
|
|
16
|
+
目前工坊里锻造了 21 件技能,按「最终产出什么」分七类。
|
|
17
|
+
|
|
18
|
+
### 1. 报表汇演(6 件)——从一句话到五件套汇报
|
|
19
|
+
|
|
20
|
+
日报、周报、月报、季报、年报五件套 + 团队日报收集台。共同的本事:
|
|
21
|
+
|
|
22
|
+
- **多源成稿**:一段口述、一份文件、一张图片、几句补充,什么材料都能整理成稿;
|
|
23
|
+
- **12 行业职业词典**:开发写「联调、回归」,产品写「验收、迭代」,话术不跑偏;
|
|
24
|
+
- **自然语言日期**:「昨天的日报」「上周三的日报」直接定位,连续多日还能批量;
|
|
25
|
+
- **三件套落盘**:对话给 md 正文 + 落盘 HTML 页面 + PNG 截图,贴群聊、发邮件各有形态。
|
|
26
|
+
|
|
27
|
+
季报带环比同比双基准,年报五段式含新年规划,团队收集台能把成员的日报收拢成按日期分组的汇总。**同一套口述习惯,打通整条汇报链。**
|
|
28
|
+
|
|
29
|
+
### 2. 演示汇演(2 件)——PPT 的两副面孔
|
|
30
|
+
|
|
31
|
+
- **magic-deck**:内容 → 单文件 HTML 幻灯。16:9 舞台、十六套版式模板、十七套主题、21 套领域模板,自动播放、键盘热键、全屏纯净放映;
|
|
32
|
+
- **deck-stage**:报告 → 原生可编辑的 .pptx。常规页用 PptxGenJS 生成(数据卡、时间线、柱图、饼图可二次编辑),封面结尾页 HTML 截图保真,每页带演讲备注。
|
|
33
|
+
|
|
34
|
+
一个适合演示效果,一个适合交付修改,说「做个 PPT」它自会问你要哪种。
|
|
35
|
+
|
|
36
|
+
### 3. 文档装裱(3 件)——让文档体面地走出去
|
|
37
|
+
|
|
38
|
+
- **article-to-html**:Word / Markdown / 纯文本 → 精美独立 HTML(思源黑体 + Roboto 商用免费字体),图表用 HTML/CSS 矢量重绘,再逐章截出 Retina 2x 高清图;
|
|
39
|
+
- **twin-docs**:中文文档翻译并同步为英文版(或反向),术语表对齐、标题层级逐项对应、双向同步检查;
|
|
40
|
+
- **one-pager**:一句话想法 → 一页纸 HTML 海报。**32 套布局模板(含水墨、红金宫廷、青绿山水等国风)× 25 套配色主题 = 800 种版式**,主题坞悬停即显主题名,?shot 参数一键截 PNG。
|
|
41
|
+
|
|
42
|
+
### 4. 沟通协作(3 件)——把碎片收进体系
|
|
43
|
+
|
|
44
|
+
- **meeting-minutes**:口述材料 → 五段式会议纪要,行动项带负责人与截止时间——汇报链的源头,可被日报周报引用;
|
|
45
|
+
- **prompt-pouch**:本地提示词管理台。分组、搜索、收藏、标签、{变量} 插值、导入导出备份,好提示词从此有了回得来的家;
|
|
46
|
+
- **decision-log**:重要决策四元组日志(结论 / 取舍 / 被否方案 / 适用条件),随时检索复盘。
|
|
47
|
+
|
|
48
|
+
### 5. 工程提效(3 件)——文档与代码之间
|
|
49
|
+
|
|
50
|
+
- **spec-doc2spec**:Word/Markdown 需求文档 → 结构化需求切片(Pandoc 机械转换 + AI 校对防错);
|
|
51
|
+
- **spec-spec2doc**:Markdown 规格 → Word/PDF 交付文档;
|
|
52
|
+
- **release-notes**:git 两个版本标签之间的提交 → 中英双语发版说明 + CHANGELOG 条目。
|
|
53
|
+
|
|
54
|
+
### 6. 数据整备(2 件)——把散装数据收拾利落
|
|
55
|
+
|
|
56
|
+
- **data-prepper**:Excel/CSV/JSON 清洗结构化,清洗项明示、每步留痕、不猜不编,附数据概览报告;
|
|
57
|
+
- **commit-heatmap**:git 近一年提交 → GitHub 风格热力图(53 周 × 7 天格子 + 统计卡),离线零依赖。
|
|
58
|
+
|
|
59
|
+
### 7. 技能自造(2 件)——工坊的自我复制
|
|
60
|
+
|
|
61
|
+
- **meta-forge**:从需求出发锻造新技能的元技能,自动植入配置保护、版本钥匙、双轨文档等八大机制;
|
|
62
|
+
- **skill-doctor**:给技能目录做体检,frontmatter、资源引用、占位符残留逐项静态检查,报告行级定位。
|
|
63
|
+
|
|
64
|
+
## 三、为什么是「技能」而不是「提示词」
|
|
65
|
+
|
|
66
|
+
提示词是一次性的对话;技能是**可移植的手艺**:
|
|
67
|
+
|
|
68
|
+
1. **宿主中立**:换一份薄薄的 frontmatter 信封,同一份技能就能从 Qoder 带去 Claude Code、Cursor——不绑定任何厂商;
|
|
69
|
+
2. **配置保护**:你的个性化配置出站在 user-config.md,升级技能永不覆盖;配置结构变化时先备份再三选一;
|
|
70
|
+
3. **版本感知回炉**:模板升级后说一句「把我旧日报都升级成新样式」,旧产物批量换新,旧版先备份;
|
|
71
|
+
4. **零依赖离线**:全部技能离线可用,不联网、不传数据,产物留在你自己的目录里。
|
|
72
|
+
|
|
73
|
+
## 四、一分钟上手
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm i -g skill-forge-hub # 全局安装 CLI
|
|
77
|
+
skill-forge ls # 浏览 21 件技能(只读)
|
|
78
|
+
skill-forge init # 交互挑选,自动探测 AI 助手技能目录
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
装完直接对话触发:「写日报」「写周报」「记会议」「做个 PPT」「做张海报」,或者干脆丢给它一个文档。
|
|
82
|
+
|
|
83
|
+
升级同样轻松:`npm update -g skill-forge-hub` 升 CLI 本体,`skill-forge update` 升技能——升级清单支持 a=全部 / 编号挑选 / q=取消,你的配置全程受保护。
|
|
84
|
+
|
|
85
|
+
## 五、写在最后
|
|
86
|
+
|
|
87
|
+
AI 时代最贵的不再是「知道什么」,而是「把会做的事稳定复现」。SkillForge 的每一件技能,都是一次被验证过的调教被固化成了永久工艺——今天调好的手感,明天、下个月、换台机器、换个助手,都还在。
|
|
88
|
+
|
|
89
|
+
仓库已开源(MIT),21 件技能双语简介见 [SKILLS_CATALOG.md](https://github.com/windfallsheng/SkillForge/blob/main/SKILLS_CATALOG.md),另有两篇实操文章(《用 AI 技能包管提示词》《AI 写日报的正确姿势》)见 docs/articles/。欢迎 Star、试用、一起锻造。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skill-forge-hub",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "SkillForge (技能锻造坊) — a curated collection of AI craft skills. Install once, then pick skills interactively: daily/weekly/monthly/quarterly/annual reports, meeting minutes, article-to-HTML, magic-deck slides, deck-stage PPTX, prompt-pouch manager, Pandoc doc converters, meta-forge.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skill-forge": "cli.js"
|
package/prompt-pouch/SKILL.md
CHANGED
|
@@ -68,7 +68,7 @@ disable-model-invocation: false
|
|
|
68
68
|
|
|
69
69
|
## 产物升级(回炉)
|
|
70
70
|
|
|
71
|
-
生成/初始化页面时头部写版本戳 `<meta name="sf-template" content="prompt-pouch@
|
|
71
|
+
生成/初始化页面时头部写版本戳 `<meta name="sf-template" content="prompt-pouch@10">`(模板升级时递增 @N 并同步 reference.html 与本文档)。用户说「升级旧锦囊/回炉旧页面/旧锦囊换新样式」时:扫 `<DIR>` 下 `prompt-pouch.html` 读版本戳筛出旧版(无版本戳或缺少 `<meta name="pouch-id">` 稳定 ID 的产物视为 v0 最旧版,一并进清单)→ 列清单(文件/旧版本/有无用户数据)→ AskUserQuestion 选全部或部分 → 逐份升级:浅升级(仅 CSS/JS 变化)对旧页替换 `<style>` 块与交互脚本;深升级(DOM 结构变化)从 reference.html 重建,**必须沿用原页 pouch-id meta 值**(IndexedDB 用户数据按 pouch-id 命名空间存取,换 ID = 数据失联,重建后原收藏/分组照旧读回),`<DIR>/<DATA_DIR>/prompts.json` 数据文件一概不动,先备份旧页 `.bak-时间戳` → 报告三段式:✅ 已升级清单 / ⚠️ 手改保留说明 / 🟡 失败项原因。模板改动分级:业务改动(板块/段落/标题体系)、数据格式改动(字段/存储结构)、页面改动(样式/布局/交互)任一发生即模板升级,版本戳 +1 并同步 reference.html 与本文档;纯措辞润色不 bump。每次生成/初始化新锦囊时顺扫 `<DIR>` 读版本戳:发现旧版产物主动告知「技能模板已升级至 vN,检测到 X 份旧版锦囊,说『把我旧锦囊都升级成新样式』即可一键升级」,不让用户背触发语。
|
|
72
72
|
|
|
73
73
|
## ⚙️ 配置与自定义主题
|
|
74
74
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta name="sf-template" content="prompt-pouch@
|
|
6
|
+
<meta name="sf-template" content="prompt-pouch@10">
|
|
7
7
|
<meta name="pouch-id" content="REF-POUCH-0000">
|
|
8
8
|
<title>锦囊 Prompt Pouch — 提示词管理台</title>
|
|
9
9
|
<style>
|
|
@@ -475,6 +475,38 @@ mark.hl{ background:var(--gold-dim); color:var(--gold); border-radius:3px; paddi
|
|
|
475
475
|
color:var(--muted); font-family:inherit; font-size:12px; line-height:1.7;
|
|
476
476
|
}
|
|
477
477
|
.fp-ops{ display:flex; justify-content:flex-end; gap:10px; }
|
|
478
|
+
/* ============ 统一确认弹框(替代浏览器原生弹框) ============ */
|
|
479
|
+
.cf-panel{
|
|
480
|
+
width:460px; max-width:92vw; max-height:82vh; display:flex; flex-direction:column;
|
|
481
|
+
background:linear-gradient(180deg, var(--panel) 0%, var(--panel2) 100%);
|
|
482
|
+
border:1px solid var(--border); border-radius:16px; padding:20px 22px;
|
|
483
|
+
box-shadow:0 20px 60px rgba(0,0,0,.5);
|
|
484
|
+
animation:cfPop .18s ease;
|
|
485
|
+
}
|
|
486
|
+
@keyframes cfPop{ from{ transform:translateY(10px) scale(.97); opacity:0; } to{ transform:none; opacity:1; } }
|
|
487
|
+
.cf-title{
|
|
488
|
+
font-size:15px; font-weight:900; letter-spacing:1px; margin-bottom:10px;
|
|
489
|
+
background:linear-gradient(90deg,var(--star),var(--gold));
|
|
490
|
+
-webkit-background-clip:text; background-clip:text; color:transparent;
|
|
491
|
+
}
|
|
492
|
+
.cf-body{
|
|
493
|
+
font-size:13px; line-height:1.9; color:var(--text); white-space:pre-wrap; word-break:break-word;
|
|
494
|
+
max-height:46vh; overflow-y:auto; padding-right:4px;
|
|
495
|
+
}
|
|
496
|
+
.cf-field{ display:flex; flex-direction:column; gap:4px; margin-top:12px; }
|
|
497
|
+
.cf-field label{ font-size:12px; font-weight:700; color:var(--gold); letter-spacing:.5px; }
|
|
498
|
+
.cf-field input{
|
|
499
|
+
width:100%; padding:9px 12px; border-radius:9px;
|
|
500
|
+
background:rgba(0,0,0,.25); border:1px solid var(--border); outline:none;
|
|
501
|
+
color:var(--text); font-family:inherit; font-size:13px; transition:border-color .18s ease, box-shadow .18s ease;
|
|
502
|
+
}
|
|
503
|
+
.cf-field input:focus{ border-color:var(--gold); box-shadow:0 0 0 3px var(--gold-dim); }
|
|
504
|
+
.cf-ops{ display:flex; justify-content:flex-end; gap:10px; margin-top:16px; }
|
|
505
|
+
.btn.danger{
|
|
506
|
+
background:linear-gradient(135deg, #a63d34, #e06c5b); color:#fff;
|
|
507
|
+
font-weight:900; border:none;
|
|
508
|
+
}
|
|
509
|
+
.btn.danger:hover{ box-shadow:0 0 16px rgba(224,108,91,.45); }
|
|
478
510
|
/* 备份与数据安全 */
|
|
479
511
|
.bk-wrap{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
|
|
480
512
|
.bk-status{ font-size:12px; color:var(--muted); letter-spacing:1px; }
|
|
@@ -787,6 +819,22 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
787
819
|
</div>
|
|
788
820
|
</div>
|
|
789
821
|
|
|
822
|
+
<!-- 统一确认弹框(确认 / 提示 / 输入,替代浏览器原生弹框) -->
|
|
823
|
+
<div class="modal-mask" id="cfMask">
|
|
824
|
+
<div class="cf-panel">
|
|
825
|
+
<div class="cf-title" id="cfTitle">确认</div>
|
|
826
|
+
<div class="cf-body" id="cfBody"></div>
|
|
827
|
+
<div class="cf-field" id="cfFieldWrap" style="display:none">
|
|
828
|
+
<label id="cfLabel"></label>
|
|
829
|
+
<input id="cfInput" />
|
|
830
|
+
</div>
|
|
831
|
+
<div class="cf-ops">
|
|
832
|
+
<button class="btn" id="cfCancel">取消</button>
|
|
833
|
+
<button class="btn primary" id="cfOk">确定</button>
|
|
834
|
+
</div>
|
|
835
|
+
</div>
|
|
836
|
+
</div>
|
|
837
|
+
|
|
790
838
|
<div class="toast" id="toast"></div>
|
|
791
839
|
|
|
792
840
|
<footer>
|
|
@@ -1474,6 +1522,71 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1474
1522
|
if (toastTimer) clearTimeout(toastTimer);
|
|
1475
1523
|
toastTimer = setTimeout(function(){ el.className = 'toast'; }, 2800);
|
|
1476
1524
|
}
|
|
1525
|
+
/* ---------- 统一确认弹框(替代原生 confirm/alert/prompt) ---------- */
|
|
1526
|
+
var cfMask = document.getElementById('cfMask');
|
|
1527
|
+
var cfTitle = document.getElementById('cfTitle');
|
|
1528
|
+
var cfBody = document.getElementById('cfBody');
|
|
1529
|
+
var cfFieldWrap = document.getElementById('cfFieldWrap');
|
|
1530
|
+
var cfLabel = document.getElementById('cfLabel');
|
|
1531
|
+
var cfInput = document.getElementById('cfInput');
|
|
1532
|
+
var cfOk = document.getElementById('cfOk');
|
|
1533
|
+
var cfCancel = document.getElementById('cfCancel');
|
|
1534
|
+
var cfPending = null; var cfMode = 'confirm';
|
|
1535
|
+
function cfClose(res){
|
|
1536
|
+
cfMask.classList.remove('open');
|
|
1537
|
+
if (cfPending){ cfPending(res); cfPending = null; }
|
|
1538
|
+
}
|
|
1539
|
+
function cfOpen(opts){
|
|
1540
|
+
cfMode = opts.mode || 'confirm';
|
|
1541
|
+
cfTitle.textContent = opts.title || '确认';
|
|
1542
|
+
cfBody.textContent = opts.body || '';
|
|
1543
|
+
if (cfMode === 'prompt'){
|
|
1544
|
+
cfFieldWrap.style.display = '';
|
|
1545
|
+
cfLabel.textContent = opts.label || '';
|
|
1546
|
+
cfInput.value = opts.def || '';
|
|
1547
|
+
cfInput.placeholder = opts.placeholder || '';
|
|
1548
|
+
} else {
|
|
1549
|
+
cfFieldWrap.style.display = 'none';
|
|
1550
|
+
}
|
|
1551
|
+
cfOk.textContent = opts.okText || (cfMode === 'alert' ? '知道了' : '确定');
|
|
1552
|
+
cfOk.classList.toggle('danger', !!opts.danger);
|
|
1553
|
+
cfCancel.textContent = opts.cancelText || '取消';
|
|
1554
|
+
cfCancel.style.display = (cfMode === 'alert') ? 'none' : '';
|
|
1555
|
+
cfMask.classList.add('open');
|
|
1556
|
+
setTimeout(function(){
|
|
1557
|
+
if (cfMode === 'prompt'){ cfInput.focus(); cfInput.select(); }
|
|
1558
|
+
else cfOk.focus();
|
|
1559
|
+
}, 60);
|
|
1560
|
+
return new Promise(function(res){ cfPending = res; });
|
|
1561
|
+
}
|
|
1562
|
+
cfOk.addEventListener('click', function(){
|
|
1563
|
+
cfClose({ ok: true, value: (cfMode === 'prompt') ? cfInput.value : null });
|
|
1564
|
+
});
|
|
1565
|
+
cfCancel.addEventListener('click', function(){ cfClose({ ok: false, canceled: true, value: null }); });
|
|
1566
|
+
cfMask.addEventListener('click', function(e){
|
|
1567
|
+
if (e.target === this) cfClose({ ok: false, canceled: true, value: null });
|
|
1568
|
+
});
|
|
1569
|
+
cfInput.addEventListener('keydown', function(e){
|
|
1570
|
+
if (e.key === 'Enter'){ e.preventDefault(); cfOk.click(); }
|
|
1571
|
+
});
|
|
1572
|
+
document.addEventListener('keydown', function(e){
|
|
1573
|
+
if (!cfMask.classList.contains('open')) return;
|
|
1574
|
+
if (e.key === 'Escape') cfClose({ ok: false, canceled: true, value: null });
|
|
1575
|
+
});
|
|
1576
|
+
function uiConfirm(title, body, opts){
|
|
1577
|
+
opts = opts || {};
|
|
1578
|
+
return cfOpen({
|
|
1579
|
+
mode: 'confirm', title: title, body: body,
|
|
1580
|
+
okText: opts.okText, cancelText: opts.cancelText, danger: opts.danger
|
|
1581
|
+
}).then(function(r){ return !!r.ok; });
|
|
1582
|
+
}
|
|
1583
|
+
function uiAlert(title, body){
|
|
1584
|
+
return cfOpen({ mode: 'alert', title: title, body: body });
|
|
1585
|
+
}
|
|
1586
|
+
function uiPrompt(title, label, def, placeholder){
|
|
1587
|
+
return cfOpen({ mode: 'prompt', title: title, label: label, def: def, placeholder: placeholder })
|
|
1588
|
+
.then(function(r){ return r.canceled ? null : r.value; });
|
|
1589
|
+
}
|
|
1477
1590
|
function ensureWriteDir(){
|
|
1478
1591
|
if (bkDirHandle) return Promise.resolve(true);
|
|
1479
1592
|
return maybeAutoAuth().then(function(){ return !!bkDirHandle; });
|
|
@@ -1672,14 +1785,16 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1672
1785
|
}).then(function(ok){ if (ok) toast(editId ? '提示词已更新' : '提示词已入囊'); });
|
|
1673
1786
|
}
|
|
1674
1787
|
function delItem(p){
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1788
|
+
uiConfirm('删除提示词', '确定删除「' + p.title + '」?删除前会自动备份,可从 ↩ 恢复。', { danger: true, okText: '删除' }).then(function(ok){
|
|
1789
|
+
if (!ok) return;
|
|
1790
|
+
writePromptsJson({
|
|
1791
|
+
apply: function(data){
|
|
1792
|
+
data.prompts = data.prompts.filter(function(x){ return x.id !== p.id; });
|
|
1793
|
+
return data;
|
|
1794
|
+
},
|
|
1795
|
+
cmd: '请删除锦囊里「' + p.title + '」这条'
|
|
1796
|
+
}).then(function(ok2){ if (ok2) toast('已删除「' + p.title + '」'); });
|
|
1797
|
+
});
|
|
1683
1798
|
}
|
|
1684
1799
|
/* 标签快捷操作:卡片标签悬停 ✕ 删 / + 加(授权目录即写即存,未授权生成指令) */
|
|
1685
1800
|
function removeTag(p, t){
|
|
@@ -1701,21 +1816,22 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1701
1816
|
}
|
|
1702
1817
|
function quickAddTag(p){
|
|
1703
1818
|
var cur = (p.tags || []).slice(0, 3);
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1819
|
+
uiPrompt('添加标签', '为「' + p.title + '」添加标签(多个用逗号分隔,最多 3 个):', cur.join(', '), '如:日报,工作').then(function(input){
|
|
1820
|
+
if (input === null) return;
|
|
1821
|
+
var adds = input.split(/[,,]/).map(function(s){ return s.trim(); }).filter(Boolean);
|
|
1822
|
+
if (!adds.length) return;
|
|
1823
|
+
var merged = cur.concat(adds).slice(0, 3);
|
|
1824
|
+
writePromptsJson({
|
|
1825
|
+
apply: function(data){
|
|
1826
|
+
var item = data.prompts.find(function(x){ return x.id === p.id; });
|
|
1827
|
+
if (!item){ toast('该提示词已不存在,请刷新后重试', true); return null; }
|
|
1828
|
+
item.tags = merged;
|
|
1829
|
+
item.updatedAt = nowStamp();
|
|
1830
|
+
return data;
|
|
1831
|
+
},
|
|
1832
|
+
cmd: '请把锦囊里「' + p.title + '」的标签更新为:' + merged.join(', ') + '(同时把 updatedAt 更新为当前时间 YYYY-MM-DD HH:mm:ss)'
|
|
1833
|
+
}).then(function(ok){ if (ok) toast('标签已更新'); });
|
|
1834
|
+
});
|
|
1719
1835
|
}
|
|
1720
1836
|
document.getElementById('itemCancel').addEventListener('click', closeItemForm);
|
|
1721
1837
|
document.getElementById('itemSave').addEventListener('click', saveItem);
|
|
@@ -1785,15 +1901,17 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1785
1901
|
function delGroup(g){
|
|
1786
1902
|
var n = state.data.prompts.filter(function(p){ return p.group === g.id; }).length;
|
|
1787
1903
|
if (n > 0){ toast('「' + g.name + '」内还有 ' + n + ' 条提示词,请先移出或删除', true); return; }
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1904
|
+
uiConfirm('删除分组', '确定删除分组「' + g.name + '」?', { danger: true, okText: '删除' }).then(function(ok){
|
|
1905
|
+
if (!ok) return;
|
|
1906
|
+
if (state.curGroup === g.id) state.curGroup = 'all';
|
|
1907
|
+
writePromptsJson({
|
|
1908
|
+
apply: function(data){
|
|
1909
|
+
data.groups = data.groups.filter(function(x){ return x.id !== g.id; });
|
|
1910
|
+
return data;
|
|
1911
|
+
},
|
|
1912
|
+
cmd: '请删除锦囊分组「' + g.name + '」'
|
|
1913
|
+
}).then(function(ok2){ if (ok2) toast('分组「' + g.name + '」已删除'); });
|
|
1914
|
+
});
|
|
1797
1915
|
}
|
|
1798
1916
|
function reorderGroups(moveId, beforeId){
|
|
1799
1917
|
var ids = state.data.groups.map(function(g){ return g.id; });
|
|
@@ -1856,9 +1974,11 @@ footer.open .ht-arrow{ transform:rotate(180deg); }
|
|
|
1856
1974
|
}
|
|
1857
1975
|
} catch(err){ toast('JSON 解析失败,请确认文件内容', true); return; }
|
|
1858
1976
|
var cur = (state.data && state.data.prompts) ? state.data.prompts.length : 0;
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1977
|
+
uiConfirm('覆盖导入', '导入将覆盖现有 ' + cur + ' 条数据为 ' + d.prompts.length + ' 条(导入前自动备份,可从 ↩ 恢复)。确定继续?', { danger: true, okText: '覆盖导入' }).then(function(ok){
|
|
1978
|
+
if (!ok) return;
|
|
1979
|
+
writePromptsJson({ apply: function(){ return d; }, cmd: '' }).then(function(ok2){
|
|
1980
|
+
if (ok2) toast('已导入 ' + d.prompts.length + ' 条提示词');
|
|
1981
|
+
});
|
|
1862
1982
|
});
|
|
1863
1983
|
};
|
|
1864
1984
|
reader.readAsText(f, 'utf-8');
|
|
@@ -2182,23 +2302,24 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2182
2302
|
if (bkDirHandle) return Promise.resolve(true);
|
|
2183
2303
|
if (!bkSupported() || bkDeclined || pendingAuth) return Promise.resolve(false);
|
|
2184
2304
|
if (!state.data) return Promise.resolve(false);
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2305
|
+
return uiConfirm('锦囊自动备份', '数据一有变更就会自动备份,建议授权写入 prompt-pouch.html 所在目录(首次选一次,浏览器会记住)。\n\n点「授权目录」打开目录选择 → 选 prompt-pouch.html 所在文件夹即可;\n点「暂不」则改用浏览器本地快照(保留最近 5 份,清浏览器数据会丢)。', { okText: '授权目录', cancelText: '暂不' }).then(function(ok){
|
|
2306
|
+
if (!ok){
|
|
2307
|
+
bkDeclined = true;
|
|
2308
|
+
try{ localStorage.setItem(SF_NS + '-bk-declined', '1'); }catch(e){}
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
pendingAuth = true; updateBkUI();
|
|
2312
|
+
return window.showDirectoryPicker({ id: 'pouch-backup', mode: 'readwrite' })
|
|
2313
|
+
.then(function(h){
|
|
2314
|
+
bkDirHandle = h; pendingAuth = false; bkDeclined = false; subDirCache = {};
|
|
2315
|
+
try{ localStorage.removeItem(SF_NS + '-bk-declined'); }catch(e){}
|
|
2316
|
+
idbSet('bkHandle', bkDirHandle);
|
|
2317
|
+
updateBkUI();
|
|
2318
|
+
reloadFromDirIfNeeded();
|
|
2319
|
+
return true;
|
|
2320
|
+
})
|
|
2321
|
+
.catch(function(){ pendingAuth = false; updateBkUI(); return false; });
|
|
2322
|
+
});
|
|
2202
2323
|
}
|
|
2203
2324
|
function updateBkUI(){
|
|
2204
2325
|
if (!bkSupported()){
|
|
@@ -2285,7 +2406,7 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2285
2406
|
return;
|
|
2286
2407
|
}
|
|
2287
2408
|
if (!bkSupported()){
|
|
2288
|
-
|
|
2409
|
+
uiAlert('目录授权不可用', '当前浏览器不支持「选择目录」授权(Chrome / Edge 可用)。\n本页已自动使用本地快照备份(保留最近 5 份,随浏览器数据走);\n也可随时对 AI 说「导出锦囊数据」生成备份文件备用。');
|
|
2289
2410
|
return;
|
|
2290
2411
|
}
|
|
2291
2412
|
if (bkDirHandle && pendingAuth){
|
|
@@ -2308,17 +2429,19 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2308
2429
|
});
|
|
2309
2430
|
btnBkChange.addEventListener('click', function(){
|
|
2310
2431
|
if (!bkSupported()){ updateBkUI(); return; }
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
.
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2432
|
+
uiConfirm('更换备份目录', '建议仍选 prompt-pouch.html 所在目录:页面新增/编辑的分组与提示词、自动备份都写在这个目录,选别处会造成页面与数据文件不一致。\n\n确定继续选择目录?', { okText: '选择目录' }).then(function(ok){
|
|
2433
|
+
if (!ok) return;
|
|
2434
|
+
window.showDirectoryPicker({ id: 'pouch-backup', mode: 'readwrite' })
|
|
2435
|
+
.then(function(h){
|
|
2436
|
+
bkDirHandle = h; pendingAuth = false; bkDeclined = false; subDirCache = {};
|
|
2437
|
+
try{ localStorage.removeItem(SF_NS + '-bk-declined'); }catch(e){}
|
|
2438
|
+
idbSet('bkHandle', bkDirHandle);
|
|
2439
|
+
updateBkUI();
|
|
2440
|
+
writeBackupNow();
|
|
2441
|
+
reloadFromDirIfNeeded();
|
|
2442
|
+
})
|
|
2443
|
+
.catch(function(){});
|
|
2444
|
+
});
|
|
2322
2445
|
});
|
|
2323
2446
|
btnBkRestore.addEventListener('click', function(){
|
|
2324
2447
|
document.getElementById('bkList').innerHTML = '<div class="bk-empty">读取备份清单…</div>';
|
|
@@ -2369,32 +2492,33 @@ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(POUC
|
|
|
2369
2492
|
}
|
|
2370
2493
|
function doRestore(content){
|
|
2371
2494
|
var snap;
|
|
2372
|
-
try{ snap = JSON.parse(content); }catch(e){
|
|
2495
|
+
try{ snap = JSON.parse(content); }catch(e){ uiAlert('恢复失败', '备份文件损坏,无法恢复。'); return; }
|
|
2373
2496
|
if (!snap || !snap.data || !Array.isArray(snap.data.groups) || !Array.isArray(snap.data.prompts)){
|
|
2374
|
-
|
|
2497
|
+
uiAlert('恢复失败', '备份结构不完整,无法恢复。'); return;
|
|
2375
2498
|
}
|
|
2376
|
-
|
|
2499
|
+
uiConfirm('恢复备份', '将用这份备份覆盖当前数据:\n\n· 备份时间:' + fmtTime(snap.time)
|
|
2377
2500
|
+ '\n· 内容:' + snap.counts.groups + ' 分组 / ' + snap.counts.prompts + ' 条提示词\n\n'
|
|
2378
|
-
+ '恢复前会先把当前数据自动另存一份新备份,可随时再恢复回来。\n\n确定恢复?')
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2501
|
+
+ '恢复前会先把当前数据自动另存一份新备份,可随时再恢复回来。\n\n确定恢复?', { danger: true, okText: '恢复' }).then(function(ok){
|
|
2502
|
+
if (!ok) return;
|
|
2503
|
+
// 先把当前状态固化进备份(目录备份滚入 .prev.json / 快照环记一份),再应用所选备份
|
|
2504
|
+
(bkDirHandle ? writeBackupNow() : writeSnapNow()).then(function(){
|
|
2505
|
+
state.data = snap.data;
|
|
2506
|
+
syncMirror(snap.data);
|
|
2507
|
+
applyUserStateFrom(snap.userState || {});
|
|
2508
|
+
state.dirty = false; state.moves = [];
|
|
2509
|
+
persistUserState();
|
|
2510
|
+
renderNav(); render();
|
|
2511
|
+
document.getElementById('bkMask').classList.remove('open');
|
|
2512
|
+
if (bkDirHandle){
|
|
2513
|
+
return bkWrite('prompts.json', JSON.stringify(snap.data, null, 2)).then(function(){
|
|
2514
|
+
toast('✓ 已恢复并写入 prompts.json,刷新即见');
|
|
2515
|
+
}).catch(function(){
|
|
2516
|
+
toast('已恢复;写入 prompts.json 失败,请重新授权目录', true);
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
markDirty();
|
|
2520
|
+
toast('✓ 已恢复;未授权目录——点「📋 复制变更指令」交给 AI 落盘,或先「💾 授权目录备份」');
|
|
2521
|
+
});
|
|
2398
2522
|
});
|
|
2399
2523
|
}
|
|
2400
2524
|
document.getElementById('bkCancelBtn').addEventListener('click', function(){
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
- **搜索高亮**:搜索框输入关键词,命中卡片的标题/正文/标签中匹配词以主题色徽标高亮(五主题自适应);命中落在正文三行折叠区之外时,卡片顶部提示「正文命中,详见详情」,点开详情弹窗全文同样高亮
|
|
21
21
|
- **{变量} 插值**:内容含花括号占位(如「{功能名}」)的提示词,点复制会自动弹出填充面板,填入变量后实时预览再复制
|
|
22
22
|
- **自动备份(默认开启)**:数据一有变更 2 秒内自动备份——首次变更会请求授权写入 prompt-pouch.html 所在目录(弹窗选同目录即可,选一次后浏览器记住),写 backup/pouch-backup.json 并滚动保留上一版 .prev.json;未授权或浏览器不支持时自动降级为本地快照(浏览器内保留最近 5 份),页内「↩ 恢复」均可一键回滚;顶栏「📂 换目录」可随时重新配置备份目录
|
|
23
|
+
- **统一确认弹框**:删除提示词/分组、覆盖导入、恢复备份、授权与换备份目录等需要确认的操作,都用页内统一样式的确认弹框(不再弹浏览器原生对话框);删除/覆盖/恢复等危险操作为红调按钮,Esc 或点遮罩均可取消
|
|
23
24
|
- **详情弹窗**:点卡片「👁 详情」弹出全文查看(超长内容限高 52vh 内滚动),带复制与修改入口,附时间线(创建 / 上次修改 / 最近使用);含 {变量} 的模板会在弹窗里标出变量清单,复制时自动进入填充面板
|
|
24
25
|
- **页面直写(双笔)**:页面增删改直接写入 data/prompts.json(与自动备份共用同一目录授权);未授权时自动降级——生成操作指令复制到剪贴板,粘贴给 AI 落盘
|
|
25
26
|
- **侧栏局部滚动**:分组较多时,左侧「收藏 + 分组列表」区域独立滚动(窄滚动条,滚动区最大高度 = 屏幕高度减去顶栏、「全部」行与底部说明卡),「+ 新建分组」在「全部」行右侧不随滚动、「全部」固定不滚;底部「锦囊用法」说明卡完整展示不裁剪,侧栏随页面整体滚动即可读完;窄屏下自动恢复平铺排列
|