@lkangd/cc-settings-preset 1.0.6 → 1.0.8
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 +42 -6
- package/README.zh-hans.md +43 -7
- package/dist/cli.js +186 -18
- package/dist/cli.js.map +1 -1
- package/dist/core/args.d.ts +13 -0
- package/dist/core/args.js +45 -0
- package/dist/core/args.js.map +1 -1
- package/dist/core/json.d.ts +1 -0
- package/dist/core/json.js +22 -9
- package/dist/core/json.js.map +1 -1
- package/dist/core/paths.d.ts +2 -0
- package/dist/core/paths.js +6 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/schema.d.ts +77 -0
- package/dist/core/schema.js +31 -0
- package/dist/core/schema.js.map +1 -1
- package/dist/flows/config-flow.d.ts +27 -0
- package/dist/flows/config-flow.js +49 -0
- package/dist/flows/config-flow.js.map +1 -0
- package/dist/ink/components/two-column-settings-view.d.ts +4 -1
- package/dist/ink/components/two-column-settings-view.js +14 -2
- package/dist/ink/components/two-column-settings-view.js.map +1 -1
- package/dist/ink/components/yaml-tree-view.d.ts +3 -0
- package/dist/ink/components/yaml-tree-view.js +108 -0
- package/dist/ink/components/yaml-tree-view.js.map +1 -0
- package/dist/ink/config-app.d.ts +7 -0
- package/dist/ink/config-app.js +43 -0
- package/dist/ink/config-app.js.map +1 -0
- package/dist/ink/manage-app.d.ts +3 -1
- package/dist/ink/manage-app.js +2 -2
- package/dist/ink/manage-app.js.map +1 -1
- package/dist/ink/settings-select-app.d.ts +4 -1
- package/dist/ink/settings-select-app.js +5 -2
- package/dist/ink/settings-select-app.js.map +1 -1
- package/dist/services/ccsp-config-service.d.ts +6 -0
- package/dist/services/ccsp-config-service.js +21 -0
- package/dist/services/ccsp-config-service.js.map +1 -0
- package/dist/services/claude-session-service.d.ts +13 -0
- package/dist/services/claude-session-service.js +73 -0
- package/dist/services/claude-session-service.js.map +1 -0
- package/dist/services/launch-preset-service.d.ts +20 -3
- package/dist/services/launch-preset-service.js +94 -27
- package/dist/services/launch-preset-service.js.map +1 -1
- package/dist/services/settings-finalizer-service.d.ts +2 -1
- package/dist/services/settings-finalizer-service.js +3 -1
- package/dist/services/settings-finalizer-service.js.map +1 -1
- package/dist/services/statusline-injector-service.d.ts +1 -1
- package/dist/services/statusline-injector-service.js +1 -3
- package/dist/services/statusline-injector-service.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/v/@lkangd/cc-settings-preset?style=flat-square" alt="npm version" /></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/dm/@lkangd/cc-settings-preset?style=flat-square&color=cb3837&label=downloads" alt="npm downloads per month" /></a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/dt/@lkangd/cc-settings-preset?style=flat-square&label=total" alt="npm total downloads" /></a>
|
|
17
19
|
<a href="https://github.com/lkangd/cc-settings-preset/blob/main/LICENSE"><img src="https://img.shields.io/github/license/lkangd/cc-settings-preset?style=flat-square" alt="license" /></a>
|
|
18
20
|
<a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/@lkangd/cc-settings-preset?style=flat-square" alt="node version" /></a>
|
|
21
|
+
<a href="https://github.com/lkangd/cc-settings-preset/commits/main"><img src="https://img.shields.io/github/last-commit/lkangd/cc-settings-preset?style=flat-square" alt="last commit" /></a>
|
|
19
22
|
<a href="https://github.com/lkangd/cc-settings-preset"><img src="https://img.shields.io/github/stars/lkangd/cc-settings-preset?style=flat-square" alt="stars" /></a>
|
|
20
23
|
<a href="https://github.com/lkangd/cc-settings-preset/issues"><img src="https://img.shields.io/github/issues/lkangd/cc-settings-preset?style=flat-square" alt="issues" /></a>
|
|
21
24
|
</p>
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
</p>
|
|
30
33
|
|
|
31
34
|
<p align="center">
|
|
32
|
-
<em>Interactive flow: pick a global base preset (left), preview its JSON (right), then tune plugins / skills / MCP per launch before starting Claude Code.</em>
|
|
35
|
+
<em>Interactive flow: pick a global base preset (left), preview its settings as YAML or JSON (right), then tune plugins / skills / MCP per launch before starting Claude Code.</em>
|
|
33
36
|
</p>
|
|
34
37
|
|
|
35
38
|
**English** | [简体中文](README.zh-hans.md)
|
|
@@ -68,6 +71,12 @@ ccsp
|
|
|
68
71
|
# Forward Claude Code args (ccsp owns --settings)
|
|
69
72
|
ccsp claude --help
|
|
70
73
|
ccsp claude -p "review this PR"
|
|
74
|
+
|
|
75
|
+
# Resume the last session in this project with its original preset/launch config
|
|
76
|
+
ccsp --continue
|
|
77
|
+
|
|
78
|
+
# Resume a specific session by id, reusing the preset/launch config it was launched with
|
|
79
|
+
ccsp --resume 99580820-9437-475c-883c-399bcfba3c47
|
|
71
80
|
```
|
|
72
81
|
|
|
73
82
|
**First-time workflow:**
|
|
@@ -188,7 +197,7 @@ claude --settings <temp-file> [your other args]
|
|
|
188
197
|
│
|
|
189
198
|
▼
|
|
190
199
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
191
|
-
│ ⑤ Write .claude/.ccsp/tmp/*.json (gitignored; keep at most
|
|
200
|
+
│ ⑤ Write .claude/.ccsp/tmp/*.json (gitignored; keep at most 50 files) │
|
|
192
201
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
193
202
|
│
|
|
194
203
|
▼
|
|
@@ -214,8 +223,17 @@ claude --settings <temp-file> [your other args]
|
|
|
214
223
|
- **Two-layer split** — global “base environment” vs project “launch delta” for multi-repo work.
|
|
215
224
|
- **Visual toggles** — terminal TUI to browse JSON and flip plugins / skills / MCP.
|
|
216
225
|
- **Remembers last choice** — per project directory for base and launch presets.
|
|
226
|
+
- **Resumable sessions** — every launch is bound to its preset/launch config; `ccsp --continue` and `ccsp --resume <id>` restore the original config and resume the matching Claude session in one shot.
|
|
217
227
|
- **Safer defaults** — `.claude/.ccsp/` gets a `.gitignore` that ignores everything on init.
|
|
218
228
|
|
|
229
|
+
### Session resume
|
|
230
|
+
|
|
231
|
+
After Claude exits, CCSP records the **real** Claude session id (discovered by diffing `~/.claude/projects/<encoded-cwd>/` against a pre-launch snapshot — `--session-id` is unreliable in interactive mode, so we don't pin it) and binds it to the launch config used. Stored under `.claude/.ccsp/sessions.json` (capped at 50, pruned by last-used time):
|
|
232
|
+
|
|
233
|
+
- `ccsp --resume <uuid>` — re-finalize that session's stored config and run `claude --resume <uuid>` in one step.
|
|
234
|
+
- `ccsp --continue` — pick the **most recently exited** ccsp session in this project and resume it deterministically by id. Concretely: launch A, then B, exit A first, run `ccsp --continue` — you get A back with A's original preset/launch config (not B's).
|
|
235
|
+
- If the stored binding points at a Claude session that no longer exists, CCSP discards it and falls back to interactive preset selection.
|
|
236
|
+
|
|
219
237
|
### Limitations (read before assuming)
|
|
220
238
|
|
|
221
239
|
| Cannot / caveat | Explanation |
|
|
@@ -227,8 +245,9 @@ claude --settings <temp-file> [your other args]
|
|
|
227
245
|
| **No “add MCP server” in UI** | Can **deny** discovered MCPs via `deniedMcpServers`; cannot create new server entries. |
|
|
228
246
|
| **No headless / CI mode** | Main flow is Ink TUI; `create` / `manage` are interactive unless you edit JSON yourself. |
|
|
229
247
|
| **Derived presets** | Schema supports `derived` type; CLI does not expose full create/manage flow yet. |
|
|
230
|
-
| **Project store is local by default** | `launch-presets` and `
|
|
231
|
-
| **Temp file cap** | Oldest
|
|
248
|
+
| **Project store is local by default** | `launch-presets`, `tmp`, and `sessions.json` under `.claude/.ccsp/` are not in Git unless you opt in. |
|
|
249
|
+
| **Temp file cap** | Oldest temp settings in `tmp/` are pruned past 50 by last-used time; statusline scripts are cleaned every exit. |
|
|
250
|
+
| **Resume only sees ccsp launches** | `ccsp --continue` / `--resume` only know about sessions started through ccsp on the new code path; sessions started directly with `claude` aren't bound and won't appear. |
|
|
232
251
|
|
|
233
252
|
---
|
|
234
253
|
|
|
@@ -238,18 +257,33 @@ claude --settings <temp-file> [your other args]
|
|
|
238
257
|
|---------|-------------|
|
|
239
258
|
| `ccsp` | Default: pick base preset → configure launch layer → start `claude` |
|
|
240
259
|
| `ccsp claude [args...]` | Same, forwarding `args` to `claude` (without `--settings`) |
|
|
260
|
+
| `ccsp --continue` | Resume the most recently exited ccsp session in this project with its original preset / launch config |
|
|
261
|
+
| `ccsp --resume <uuid>` | Resume a specific session by id, reusing the preset / launch config it was launched with (falls back to interactive if the binding is unknown) |
|
|
241
262
|
| `ccsp create` | Interactively create a global base preset |
|
|
242
263
|
| `ccsp manage` | Manage global base presets (preview / rename / delete / create / launch) |
|
|
243
264
|
| `ccsp manage --project` | Manage launch presets for the current project |
|
|
265
|
+
| `ccsp config` | Configure ccsp preferences (env-only preview, statusline) |
|
|
244
266
|
|
|
245
267
|
### TUI shortcuts
|
|
246
268
|
|
|
247
|
-
**Base preset selection:** `j`/`k` or arrows to move, `Enter` to confirm, `q` to quit.
|
|
269
|
+
**Base preset selection:** `j`/`k` or arrows to move, `f` toggle between env-only and full settings preview, `Enter` to confirm, `q` to quit.
|
|
248
270
|
|
|
249
271
|
**Global manage (`ccsp manage`):** `l` launch, `r` rename, `d` delete, `c` create, `o` reveal in Finder, `q` quit.
|
|
250
272
|
|
|
251
273
|
**Project launch layer:** switch between presets and plugin / skill / MCP columns; save as launch preset. `Ctrl+L` refreshes the UI.
|
|
252
274
|
|
|
275
|
+
**Config (`ccsp config`):** `j`/`k` or arrows to move, `space`/`Enter` toggle the focused option, `q` quit.
|
|
276
|
+
|
|
277
|
+
### Preferences (`ccsp config`)
|
|
278
|
+
|
|
279
|
+
`ccsp config` opens a two-column view (options on the left, the focused option's description on the right) for global, per-user preferences stored in `~/.ccsp/config.json`. Both default to **enabled**:
|
|
280
|
+
|
|
281
|
+
| Option | Default | Effect |
|
|
282
|
+
|--------|---------|--------|
|
|
283
|
+
| **Global preset env-only** | enabled | The base preset selection screen previews only the `env` field of the selected preset by default. Press `f` on that screen to toggle between the env-only view and the full settings view. When disabled, the full settings are shown by default. |
|
|
284
|
+
| **Show statusline** | enabled | ccsp injects a statusline at the bottom of Claude Code showing the active preset and toggle summary (`CCSP: <base>/<launch> | plugins(…) | skills(…) | MCPs(…)`). When disabled, the statusline is not injected and no statusline scripts are generated. |
|
|
285
|
+
| **Settings preview format** | `yaml` | How the selected preset settings are rendered on the right of the preset selection (`ccsp`) and manage (`ccsp manage`) screens — `yaml` or `json`. Both are syntax-highlighted. |
|
|
286
|
+
|
|
253
287
|
---
|
|
254
288
|
|
|
255
289
|
## Directory Layout
|
|
@@ -259,6 +293,7 @@ claude --settings <temp-file> [your other args]
|
|
|
259
293
|
├── index.json # global base preset index
|
|
260
294
|
├── settings/
|
|
261
295
|
│ └── <name>-settings.json # base preset body
|
|
296
|
+
├── config.json # user preferences (env-only preview, statusline)
|
|
262
297
|
└── last-settings.json # last base preset name per project cwd
|
|
263
298
|
|
|
264
299
|
<project>/.claude/.ccsp/ # entire dir gitignored by default
|
|
@@ -266,7 +301,8 @@ claude --settings <temp-file> [your other args]
|
|
|
266
301
|
│ ├── index.json
|
|
267
302
|
│ └── <name>-launch.json # launch-layer overrides only
|
|
268
303
|
├── tmp/
|
|
269
|
-
│ └── <
|
|
304
|
+
│ └── <stem>-settings.json # finalized launch config (max 50, pruned by use time)
|
|
305
|
+
├── sessions.json # sessionId → launch config binding, for --continue / --resume
|
|
270
306
|
└── last-used.json # last launch preset used
|
|
271
307
|
```
|
|
272
308
|
|
package/README.zh-hans.md
CHANGED
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/v/@lkangd/cc-settings-preset?style=flat-square" alt="npm version" /></a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/dm/@lkangd/cc-settings-preset?style=flat-square&color=cb3837&label=downloads" alt="npm 月下载量" /></a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@lkangd/cc-settings-preset"><img src="https://img.shields.io/npm/dt/@lkangd/cc-settings-preset?style=flat-square&label=total" alt="npm 总下载量" /></a>
|
|
17
19
|
<a href="https://github.com/lkangd/cc-settings-preset/blob/main/LICENSE"><img src="https://img.shields.io/github/license/lkangd/cc-settings-preset?style=flat-square" alt="license" /></a>
|
|
18
20
|
<a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/@lkangd/cc-settings-preset?style=flat-square" alt="node version" /></a>
|
|
21
|
+
<a href="https://github.com/lkangd/cc-settings-preset/commits/main"><img src="https://img.shields.io/github/last-commit/lkangd/cc-settings-preset?style=flat-square" alt="最近提交" /></a>
|
|
19
22
|
<a href="https://github.com/lkangd/cc-settings-preset"><img src="https://img.shields.io/github/stars/lkangd/cc-settings-preset?style=flat-square" alt="stars" /></a>
|
|
20
23
|
<a href="https://github.com/lkangd/cc-settings-preset/issues"><img src="https://img.shields.io/github/issues/lkangd/cc-settings-preset?style=flat-square" alt="issues" /></a>
|
|
21
24
|
</p>
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
</p>
|
|
30
33
|
|
|
31
34
|
<p align="center">
|
|
32
|
-
<em
|
|
35
|
+
<em>典型流程:先选全局基础预设(左)并以 YAML 或 JSON 预览其配置(右),再在项目启动层切换插件 / Skill / MCP,最后启动 Claude Code。</em>
|
|
33
36
|
</p>
|
|
34
37
|
|
|
35
38
|
[English](README.md) | **简体中文**
|
|
@@ -68,6 +71,12 @@ ccsp
|
|
|
68
71
|
# 透传 Claude Code 参数(ccsp 会接管 --settings)
|
|
69
72
|
ccsp claude --help
|
|
70
73
|
ccsp claude -p "review this PR"
|
|
74
|
+
|
|
75
|
+
# 恢复本项目最近退出的会话,并复用其原始预设 / 启动配置
|
|
76
|
+
ccsp --continue
|
|
77
|
+
|
|
78
|
+
# 按 session id 精确恢复某个会话,复用启动时所用的预设 / 启动配置
|
|
79
|
+
ccsp --resume 99580820-9437-475c-883c-399bcfba3c47
|
|
71
80
|
```
|
|
72
81
|
|
|
73
82
|
**首次使用建议:**
|
|
@@ -118,7 +127,7 @@ claude --settings ~/.claude/my-api-1.json -- ...
|
|
|
118
127
|
|
|
119
128
|
### 5. 配置管理与预览
|
|
120
129
|
|
|
121
|
-
- `ccsp manage
|
|
130
|
+
- `ccsp manage`:浏览、重命名、删除全局基础预设,预览配置(YAML / JSON),并可直接从管理界面启动。
|
|
122
131
|
- `ccsp manage --project`:管理当前仓库下的启动预设(创建 / 保存 / 重命名 / 删除 / 启动)。
|
|
123
132
|
|
|
124
133
|
---
|
|
@@ -188,7 +197,7 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
188
197
|
│
|
|
189
198
|
▼
|
|
190
199
|
┌─────────────────────────────────────────────────────────────────────────┐
|
|
191
|
-
│ ⑤ 写入 .claude/.ccsp/tmp/*.json(目录默认 gitignore,最多保留
|
|
200
|
+
│ ⑤ 写入 .claude/.ccsp/tmp/*.json(目录默认 gitignore,最多保留 50 份) │
|
|
192
201
|
└─────────────────────────────────────────────────────────────────────────┘
|
|
193
202
|
│
|
|
194
203
|
▼
|
|
@@ -214,8 +223,17 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
214
223
|
- **两层分离**:全局「基础环境」与项目「启动差异」解耦,适合多仓库并行开发。
|
|
215
224
|
- **可视化开关**:在终端 TUI 中浏览 JSON、切换插件 / Skill / MCP,比手改数组更不易出错。
|
|
216
225
|
- **记忆上次选择**:按项目目录记住上次使用的基础预设与启动预设。
|
|
226
|
+
- **可恢复会话**:每次启动都会把会话与所用预设 / 启动配置绑定,`ccsp --continue` 与 `ccsp --resume <id>` 可一键恢复原配置并续上对应 Claude 会话。
|
|
217
227
|
- **安全默认值**:`.claude/.ccsp/` 初始化时写入 `.gitignore`(忽略全部),降低临时文件与本地预设误提交概率。
|
|
218
228
|
|
|
229
|
+
### 会话恢复(Session Resume)
|
|
230
|
+
|
|
231
|
+
Claude 退出后,CCSP 会**主动发现** Claude 真实分配的 session id(启动前对 `~/.claude/projects/<编码后的 cwd>/` 做快照,退出时做 diff——`--session-id` 在交互模式下并不可靠,所以我们不直接 pin),并把它和本次启动配置绑定,存入 `.claude/.ccsp/sessions.json`(最多 50 条,按 lastUsedAt 淘汰):
|
|
232
|
+
|
|
233
|
+
- `ccsp --resume <uuid>`:用绑定里的输入重新 finalize 启动配置,并以 `claude --resume <uuid>` 一步启动。
|
|
234
|
+
- `ccsp --continue`:选取本项目里**最近退出**的 ccsp 会话,按其 id 确定性地恢复。具体例子:先启 A 再启 B,先退出 A,再 `ccsp --continue`,恢复的是 A(带 A 当时的预设 / 启动配置),不是 B。
|
|
235
|
+
- 若绑定对应的 Claude 会话已不存在(如旧版本残留),CCSP 会丢弃该绑定并回退到交互式选择。
|
|
236
|
+
|
|
219
237
|
### 局限与误解澄清(请务必阅读)
|
|
220
238
|
|
|
221
239
|
| 不能做到 / 需注意 | 说明 |
|
|
@@ -227,8 +245,9 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
227
245
|
| **不能通过 UI 新增 MCP Server** | 仅支持基于已发现 MCP 的**禁用**(`deniedMcpServers`),不能创建新 server 配置。 |
|
|
228
246
|
| **无无头 / CI 模式** | 主流程为 Ink 交互界面;`create` / `manage` 亦为 TUI,不适合纯脚本无人值守选型(除非自行读写 JSON)。 |
|
|
229
247
|
| **Derived 预设** | 数据模型支持 `derived` 类型,但当前 CLI 未暴露创建/管理衍生预设的完整流程。 |
|
|
230
|
-
| **项目存储默认本地** | `launch-presets` 与 `
|
|
231
|
-
| **临时文件有上限** | `tmp`
|
|
248
|
+
| **项目存储默认本地** | `launch-presets`、`tmp` 与 `sessions.json` 都在 `.claude/.ccsp/` 下,默认不进 Git;团队共享需另行约定导出方式。 |
|
|
249
|
+
| **临时文件有上限** | `tmp` 中的 settings 文件按 lastUsedAt 淘汰,最多保留 50 份;statusline 脚本每次退出都会清理。 |
|
|
250
|
+
| **Resume 只覆盖 ccsp 启动的会话** | `ccsp --continue` / `--resume` 只能看到通过新版 ccsp 启动并记录过绑定的会话;直接用 `claude` 启动的会话不在范围内。 |
|
|
232
251
|
|
|
233
252
|
---
|
|
234
253
|
|
|
@@ -238,18 +257,33 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
238
257
|
|------|------|
|
|
239
258
|
| `ccsp` | 默认流程:选基础预设 → 配置启动层 → 启动 `claude` |
|
|
240
259
|
| `ccsp claude [args...]` | 同上,并将 `args` 传给 `claude`(不含 `--settings`) |
|
|
260
|
+
| `ccsp --continue` | 恢复本项目最近退出的 ccsp 会话,复用原始预设 / 启动配置 |
|
|
261
|
+
| `ccsp --resume <uuid>` | 按 session id 精确恢复某个会话,复用启动时所用的预设 / 启动配置(未知绑定时回退到交互式选择) |
|
|
241
262
|
| `ccsp create` | 交互式创建全局基础预设 |
|
|
242
263
|
| `ccsp manage` | 管理全局基础预设(预览 / 重命名 / 删除 / 新建 / 启动) |
|
|
243
264
|
| `ccsp manage --project` | 管理当前项目的启动预设 |
|
|
265
|
+
| `ccsp config` | 配置 ccsp 偏好(仅 env 预览、statusline) |
|
|
244
266
|
|
|
245
267
|
### 常用快捷键(TUI)
|
|
246
268
|
|
|
247
|
-
**选择基础预设:** `j`/`k` 或方向键移动,`Enter` 确认,`q` 退出。
|
|
269
|
+
**选择基础预设:** `j`/`k` 或方向键移动,`f` 在「仅 env」与「完整配置」预览间切换,`Enter` 确认,`q` 退出。
|
|
248
270
|
|
|
249
271
|
**管理全局预设(`ccsp manage`):** `l` 启动,`r` 重命名,`d` 删除,`c` 新建,`o` 在 Finder 中打开文件,`q` 退出。
|
|
250
272
|
|
|
251
273
|
**项目启动层:** 在预设与插件 / Skill / MCP 列间切换并开关;支持保存为启动预设。终端内 `Ctrl+L` 可刷新界面。
|
|
252
274
|
|
|
275
|
+
**配置(`ccsp config`):** `j`/`k` 或方向键移动,`space`/`Enter` 切换当前选项,`q` 退出。
|
|
276
|
+
|
|
277
|
+
### 偏好设置(`ccsp config`)
|
|
278
|
+
|
|
279
|
+
`ccsp config` 打开左右两栏视图(左侧为选项,右侧为当前聚焦选项的说明),用于管理存放在 `~/.ccsp/config.json` 的全局(按用户)偏好。两项默认均为 **开启**:
|
|
280
|
+
|
|
281
|
+
| 选项 | 默认 | 作用 |
|
|
282
|
+
|------|------|------|
|
|
283
|
+
| **Global preset env-only** | 开启 | 基础预设选择页默认只预览所选预设的 `env` 字段。在该页面按 `f` 可在「仅 env」与「完整配置」视图间切换;关闭后默认展示完整配置。 |
|
|
284
|
+
| **Show statusline** | 开启 | 在 Claude Code 底部注入 ccsp statusline,显示当前预设与开关概览(`CCSP: <base>/<launch> \| plugins(…) \| skills(…) \| MCPs(…)`)。关闭后不再注入,也不会生成任何 statusline 脚本。 |
|
|
285
|
+
| **Settings preview format** | `yaml` | 基础预设选择页(`ccsp`)与管理页(`ccsp manage`)右侧预览所选预设配置的渲染格式 —— `yaml` 或 `json`,两者都带语法高亮。 |
|
|
286
|
+
|
|
253
287
|
---
|
|
254
288
|
|
|
255
289
|
## 目录结构
|
|
@@ -259,6 +293,7 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
259
293
|
├── index.json # 全局基础预设索引
|
|
260
294
|
├── settings/
|
|
261
295
|
│ └── <name>-settings.json # 基础预设内容
|
|
296
|
+
├── config.json # 用户偏好(仅 env 预览、statusline)
|
|
262
297
|
└── last-settings.json # 各项目 cwd 上次使用的基础预设名
|
|
263
298
|
|
|
264
299
|
<项目>/.claude/.ccsp/ # 默认整目录 gitignore
|
|
@@ -266,7 +301,8 @@ claude --settings <临时文件> [你传入的其它参数]
|
|
|
266
301
|
│ ├── index.json
|
|
267
302
|
│ └── <name>-launch.json # 仅含启动层覆盖字段
|
|
268
303
|
├── tmp/
|
|
269
|
-
│ └── <
|
|
304
|
+
│ └── <stem>-settings.json # 本次启动的最终配置(最多 50 份,按使用时间淘汰)
|
|
305
|
+
├── sessions.json # sessionId → 启动配置 的绑定,供 --continue / --resume
|
|
270
306
|
└── last-used.json # 上次使用的启动预设
|
|
271
307
|
```
|
|
272
308
|
|
package/dist/cli.js
CHANGED
|
@@ -5,12 +5,14 @@ import React from 'react';
|
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { render, Text } from 'ink';
|
|
7
7
|
import figlet from 'figlet';
|
|
8
|
-
import {
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { isUuid, resolveSessionLaunch, sanitizeClaudeArgs } from './core/args.js';
|
|
9
10
|
import { CliError } from './core/errors.js';
|
|
10
11
|
import { readJsonFile } from './core/json.js';
|
|
11
12
|
import { createPathContext, resolveGlobalRoot, resolveUserClaudeSettingsPath } from './core/paths.js';
|
|
12
13
|
import { parseSettings } from './core/schema.js';
|
|
13
14
|
import { spawnClaude } from './core/spawn.js';
|
|
15
|
+
import { ConfigApp } from './ink/config-app.js';
|
|
14
16
|
import { CreateApp } from './ink/create-app.js';
|
|
15
17
|
import { GlobalShortcutHandler } from './ink/components/global-shortcut-handler.js';
|
|
16
18
|
import { InkResizeProvider } from './ink/components/resize-context.js';
|
|
@@ -20,7 +22,9 @@ import { ProjectLaunchApp } from './ink/project-launch-app.js';
|
|
|
20
22
|
import { ProjectManageApp } from './ink/project-manage-app.js';
|
|
21
23
|
import { SettingsSelectApp } from './ink/settings-select-app.js';
|
|
22
24
|
import { applyPluginOverrides, pluginStatesToEnabledPlugins, resolvePluginStates } from './services/plugin-service.js';
|
|
25
|
+
import { createCcspConfigService } from './services/ccsp-config-service.js';
|
|
23
26
|
import { createGlobalLastSettingsService } from './services/global-last-settings-service.js';
|
|
27
|
+
import { createClaudeSessionService } from './services/claude-session-service.js';
|
|
24
28
|
import { createLaunchPresetService } from './services/launch-preset-service.js';
|
|
25
29
|
import { applyDeniedMcpServers, applyPluginMcpAvailability, discoverMcpStates, mcpStatesToDeniedServers, resolveDeniedMcpServers } from './services/mcp-service.js';
|
|
26
30
|
import { createClaudeLoginService } from './services/claude-login-service.js';
|
|
@@ -35,7 +39,9 @@ const globalRoot = resolveGlobalRoot(context.homeDir);
|
|
|
35
39
|
const presetService = createPresetService(globalRoot);
|
|
36
40
|
const settingsSourceService = createSettingsSourceService(context);
|
|
37
41
|
const globalLastSettingsService = createGlobalLastSettingsService(context.homeDir);
|
|
42
|
+
const ccspConfigService = createCcspConfigService(globalRoot);
|
|
38
43
|
const launchPresetService = createLaunchPresetService(context.cwd);
|
|
44
|
+
const claudeSessionService = createClaudeSessionService(context.homeDir, context.cwd);
|
|
39
45
|
const claudeLoginService = createClaudeLoginService(context);
|
|
40
46
|
async function buildClaudeOfficialPresetItem() {
|
|
41
47
|
if (!(await claudeLoginService.isLoggedIn()))
|
|
@@ -67,6 +73,13 @@ export function createProgram() {
|
|
|
67
73
|
}
|
|
68
74
|
await manageInteractive();
|
|
69
75
|
});
|
|
76
|
+
program
|
|
77
|
+
.command('config')
|
|
78
|
+
.description('Configure ccsp preferences')
|
|
79
|
+
.action(async () => {
|
|
80
|
+
printBanner();
|
|
81
|
+
await configInteractive();
|
|
82
|
+
});
|
|
70
83
|
return program;
|
|
71
84
|
}
|
|
72
85
|
function stripAnsi(value) {
|
|
@@ -171,11 +184,27 @@ async function renderCreateApp() {
|
|
|
171
184
|
await waitForInkAppExit(app, createNode, process.stdout, state, onShortcut);
|
|
172
185
|
return result;
|
|
173
186
|
}
|
|
174
|
-
async function
|
|
187
|
+
async function configInteractive() {
|
|
188
|
+
const initialConfig = await ccspConfigService.read();
|
|
189
|
+
const createNode = () => h(ConfigApp, {
|
|
190
|
+
initialConfig,
|
|
191
|
+
onChange: (config) => {
|
|
192
|
+
void ccspConfigService.write(config);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const state = { resizeVersion: 0 };
|
|
196
|
+
let app;
|
|
197
|
+
const onShortcut = (input, key) => {
|
|
198
|
+
createGlobalShortcutHandler(app, createNode, process.stdout, state, onShortcut)(input, key);
|
|
199
|
+
};
|
|
200
|
+
app = render(wrapInkNode(createNode, state.resizeVersion, onShortcut));
|
|
201
|
+
await waitForInkAppExit(app, createNode, process.stdout, state, onShortcut);
|
|
202
|
+
}
|
|
203
|
+
async function renderSettingsSelectApp(items, options = {}) {
|
|
175
204
|
let result;
|
|
176
205
|
const createNode = () => h(SettingsSelectApp, {
|
|
177
206
|
items,
|
|
178
|
-
...
|
|
207
|
+
...options,
|
|
179
208
|
onSubmit: (value) => {
|
|
180
209
|
result = value;
|
|
181
210
|
}
|
|
@@ -212,13 +241,18 @@ async function resolveProjectManageBaseSettings() {
|
|
|
212
241
|
temporary: true
|
|
213
242
|
};
|
|
214
243
|
}
|
|
215
|
-
async function resolveInteractiveBaseSettings() {
|
|
244
|
+
async function resolveInteractiveBaseSettings(config) {
|
|
216
245
|
const officialItem = await buildClaudeOfficialPresetItem();
|
|
217
246
|
const presetItems = [...(officialItem ? [officialItem] : []), ...(await buildGlobalSettingsPresetItems())];
|
|
218
247
|
if (presetItems.length > 0) {
|
|
219
248
|
const rememberedName = await globalLastSettingsService.readLastUsed(context.cwd);
|
|
220
249
|
const initialName = rememberedName && presetItems.some(preset => preset.name === rememberedName) ? rememberedName : undefined;
|
|
221
|
-
const
|
|
250
|
+
const { globalPresetEnvOnly, settingsDisplayFormat } = config ?? await ccspConfigService.read();
|
|
251
|
+
const selected = await renderSettingsSelectApp(presetItems, {
|
|
252
|
+
...(initialName ? { initialName } : {}),
|
|
253
|
+
initialEnvOnly: globalPresetEnvOnly,
|
|
254
|
+
displayFormat: settingsDisplayFormat,
|
|
255
|
+
});
|
|
222
256
|
if (selected)
|
|
223
257
|
await globalLastSettingsService.writeLastUsed(context.cwd, selected.name);
|
|
224
258
|
return selected;
|
|
@@ -364,10 +398,11 @@ function launchResultToSettings(result) {
|
|
|
364
398
|
deniedMcpServers: mcpStatesToDeniedServers(result.toggles.mcps)
|
|
365
399
|
};
|
|
366
400
|
}
|
|
367
|
-
async function renderManageApp(items) {
|
|
401
|
+
async function renderManageApp(items, settingsDisplayFormat) {
|
|
368
402
|
let result;
|
|
369
403
|
const createNode = () => h(ManageApp, {
|
|
370
404
|
items,
|
|
405
|
+
displayFormat: settingsDisplayFormat,
|
|
371
406
|
onSubmit: (value) => {
|
|
372
407
|
result = value;
|
|
373
408
|
},
|
|
@@ -401,18 +436,36 @@ async function createPresetInteractive() {
|
|
|
401
436
|
return presetService.createBasePreset(selection.name, settings);
|
|
402
437
|
}
|
|
403
438
|
async function launchClaudeWithFinalizedSettings(input) {
|
|
404
|
-
const
|
|
439
|
+
const session = resolveSessionLaunch(input.args);
|
|
440
|
+
const stem = randomUUID();
|
|
405
441
|
const claudeSources = await settingsSourceService.discoverSettingsSources();
|
|
442
|
+
const statusLineEnabled = input.statusLineEnabled ?? (await ccspConfigService.read()).statusLineEnabled;
|
|
406
443
|
const settingsPath = await launchPresetService.writeTempSettings(await finalizeLaunchSettings(input.baseSettings, input.launchSettings, {
|
|
407
444
|
globalName: input.globalName,
|
|
408
445
|
projectPresetName: input.projectPresetName,
|
|
409
446
|
toggles: input.toggles,
|
|
410
447
|
context,
|
|
411
448
|
claudeSources,
|
|
412
|
-
|
|
413
|
-
|
|
449
|
+
stem,
|
|
450
|
+
statusLineEnabled,
|
|
451
|
+
}), stem);
|
|
452
|
+
const bindingInput = {
|
|
453
|
+
globalName: input.globalName,
|
|
454
|
+
projectPresetName: input.projectPresetName,
|
|
455
|
+
baseSettings: input.baseSettings,
|
|
456
|
+
launchSettings: input.launchSettings,
|
|
457
|
+
toggles: input.toggles,
|
|
458
|
+
};
|
|
459
|
+
// When we already know the session id (explicit --session-id / --resume <uuid>),
|
|
460
|
+
// record the binding upfront so the config is recoverable even if Claude is
|
|
461
|
+
// killed before exit. Otherwise snapshot Claude's project dir and discover
|
|
462
|
+
// the id post-spawn by diffing.
|
|
463
|
+
const sessionSnapshot = session.sessionId ? undefined : await claudeSessionService.snapshot();
|
|
464
|
+
if (session.sessionId) {
|
|
465
|
+
await launchPresetService.writeSessionBinding({ sessionId: session.sessionId, ...bindingInput });
|
|
466
|
+
}
|
|
414
467
|
try {
|
|
415
|
-
process.exitCode = await spawnClaude(settingsPath,
|
|
468
|
+
process.exitCode = await spawnClaude(settingsPath, session.args);
|
|
416
469
|
}
|
|
417
470
|
catch (error) {
|
|
418
471
|
if (error instanceof CliError) {
|
|
@@ -421,10 +474,17 @@ async function launchClaudeWithFinalizedSettings(input) {
|
|
|
421
474
|
throw error;
|
|
422
475
|
}
|
|
423
476
|
finally {
|
|
424
|
-
await launchPresetService.
|
|
477
|
+
await launchPresetService.cleanupTempScripts(stem);
|
|
478
|
+
const sessionId = session.sessionId ?? (sessionSnapshot && (await claudeSessionService.findNewSessionId(sessionSnapshot)));
|
|
479
|
+
if (sessionId) {
|
|
480
|
+
if (!session.sessionId) {
|
|
481
|
+
await launchPresetService.writeSessionBinding({ sessionId, ...bindingInput });
|
|
482
|
+
}
|
|
483
|
+
await launchPresetService.recordSessionExit(sessionId);
|
|
484
|
+
}
|
|
425
485
|
}
|
|
426
486
|
}
|
|
427
|
-
async function launchWithSelectedSettings(selectedSettings, rawClaudeArgs) {
|
|
487
|
+
async function launchWithSelectedSettings(selectedSettings, rawClaudeArgs, config) {
|
|
428
488
|
const sanitized = sanitizeClaudeArgs(rawClaudeArgs);
|
|
429
489
|
if (sanitized.removedSettings) {
|
|
430
490
|
process.stderr.write('\x1b[31mWarning: ccsp ignores passthrough --settings because it manages that flag.\x1b[0m\n');
|
|
@@ -453,29 +513,96 @@ async function launchWithSelectedSettings(selectedSettings, rawClaudeArgs) {
|
|
|
453
513
|
toggles: launchResult.toggles,
|
|
454
514
|
launchSettings,
|
|
455
515
|
args: sanitized.args,
|
|
516
|
+
...(config ? { statusLineEnabled: config.statusLineEnabled } : {}),
|
|
456
517
|
});
|
|
457
518
|
return 'done';
|
|
458
519
|
}
|
|
459
|
-
async function runInteractive(rawClaudeArgs) {
|
|
520
|
+
async function runInteractive(rawClaudeArgs, fallbackMode) {
|
|
460
521
|
printBanner();
|
|
522
|
+
const config = await ccspConfigService.read();
|
|
461
523
|
while (true) {
|
|
462
|
-
const selectedSettings = await resolveInteractiveBaseSettings();
|
|
524
|
+
const selectedSettings = await resolveInteractiveBaseSettings(config);
|
|
463
525
|
if (!selectedSettings)
|
|
464
526
|
return;
|
|
465
|
-
const
|
|
527
|
+
const launchArgs = fallbackMode === 'resume'
|
|
528
|
+
? ['--resume', ...rawClaudeArgs]
|
|
529
|
+
: fallbackMode === 'continue'
|
|
530
|
+
? rawClaudeArgs
|
|
531
|
+
: rawClaudeArgs;
|
|
532
|
+
const outcome = await launchWithSelectedSettings(selectedSettings, launchArgs, config);
|
|
466
533
|
if (outcome !== 'back')
|
|
467
534
|
return;
|
|
468
535
|
printBanner();
|
|
469
536
|
}
|
|
470
537
|
}
|
|
538
|
+
async function launchFromBinding(binding, extraArgs) {
|
|
539
|
+
const sanitized = sanitizeClaudeArgs(extraArgs);
|
|
540
|
+
if (sanitized.removedSettings) {
|
|
541
|
+
process.stderr.write('\x1b[31mWarning: ccsp ignores passthrough --settings because it manages that flag.\x1b[0m\n');
|
|
542
|
+
}
|
|
543
|
+
process.stderr.write(`\x1b[2mResuming ${binding.globalName}/${binding.projectPresetName} (session ${binding.sessionId})\x1b[0m\n`);
|
|
544
|
+
const filteredArgs = sanitized.args.filter((arg, index, args) => arg !== '--continue' &&
|
|
545
|
+
arg !== '-c' &&
|
|
546
|
+
!arg.startsWith('--continue=') &&
|
|
547
|
+
!arg.startsWith('--resume=') &&
|
|
548
|
+
arg !== '-r' &&
|
|
549
|
+
!(arg === '--resume') &&
|
|
550
|
+
!(arg === '--session-id') &&
|
|
551
|
+
!arg.startsWith('--session-id=') &&
|
|
552
|
+
!((args[index - 1] === '--resume' || args[index - 1] === '-r' || args[index - 1] === '--session-id') &&
|
|
553
|
+
!arg.startsWith('-')));
|
|
554
|
+
await launchClaudeWithFinalizedSettings({
|
|
555
|
+
baseSettings: binding.baseSettings,
|
|
556
|
+
globalName: binding.globalName,
|
|
557
|
+
projectPresetName: binding.projectPresetName,
|
|
558
|
+
toggles: binding.toggles,
|
|
559
|
+
launchSettings: binding.launchSettings,
|
|
560
|
+
args: ['--resume', binding.sessionId, ...filteredArgs],
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async function resolveLiveBinding(binding) {
|
|
564
|
+
if (!binding)
|
|
565
|
+
return undefined;
|
|
566
|
+
if (await claudeSessionService.hasSession(binding.sessionId))
|
|
567
|
+
return binding;
|
|
568
|
+
process.stderr.write(`\x1b[2mDiscarding stale binding (no Claude session for ${binding.sessionId}).\x1b[0m\n`);
|
|
569
|
+
await launchPresetService.deleteSessionBinding(binding.sessionId);
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
async function runResume(sessionId, extraArgs) {
|
|
573
|
+
const binding = await resolveLiveBinding(await launchPresetService.readSessionBinding(sessionId));
|
|
574
|
+
if (binding) {
|
|
575
|
+
await launchFromBinding(binding, extraArgs);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
process.stderr.write(`\x1b[2mNo saved ccsp launch config for session ${sessionId}; pick a preset to resume with.\x1b[0m\n`);
|
|
579
|
+
await runInteractive([sessionId, ...extraArgs], 'resume');
|
|
580
|
+
}
|
|
581
|
+
async function runContinue(extraArgs) {
|
|
582
|
+
// Walk newest-exited bindings until one points at a live Claude session.
|
|
583
|
+
// resolveLiveBinding prunes stale entries, so each loop iteration makes progress.
|
|
584
|
+
while (true) {
|
|
585
|
+
const candidate = await launchPresetService.findLatestExitedSession();
|
|
586
|
+
if (!candidate)
|
|
587
|
+
break;
|
|
588
|
+
const live = await resolveLiveBinding(candidate);
|
|
589
|
+
if (live) {
|
|
590
|
+
await launchFromBinding(live, extraArgs);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
process.stderr.write('\x1b[2mNo saved ccsp session to continue; pick a preset.\x1b[0m\n');
|
|
595
|
+
await runInteractive(extraArgs);
|
|
596
|
+
}
|
|
471
597
|
async function manageInteractive() {
|
|
598
|
+
const config = await ccspConfigService.read();
|
|
472
599
|
while (true) {
|
|
473
600
|
const items = await buildGlobalSettingsPresetItems();
|
|
474
|
-
const selection = await renderManageApp(items);
|
|
601
|
+
const selection = await renderManageApp(items, config.settingsDisplayFormat);
|
|
475
602
|
if (!selection || selection.type === 'exit')
|
|
476
603
|
return;
|
|
477
604
|
if (selection.type === 'launch') {
|
|
478
|
-
const outcome = await launchWithSelectedSettings(selection.item, []);
|
|
605
|
+
const outcome = await launchWithSelectedSettings(selection.item, [], config);
|
|
479
606
|
if (outcome === 'back')
|
|
480
607
|
continue;
|
|
481
608
|
return;
|
|
@@ -553,8 +680,49 @@ export async function main(argv = process.argv) {
|
|
|
553
680
|
await runInteractive([]);
|
|
554
681
|
return;
|
|
555
682
|
}
|
|
683
|
+
if (args[0] === '--continue' || args[0] === '-c') {
|
|
684
|
+
await runContinue(args.slice(1));
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
if (args[0] === '--resume' || args[0] === '-r') {
|
|
688
|
+
const id = args[1];
|
|
689
|
+
if (id && isUuid(id)) {
|
|
690
|
+
await runResume(id, args.slice(2));
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
await runInteractive(args);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
if (args[0]?.startsWith('--resume=')) {
|
|
697
|
+
const id = args[0].slice('--resume='.length);
|
|
698
|
+
if (isUuid(id)) {
|
|
699
|
+
await runResume(id, args.slice(1));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
await runInteractive(args);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
556
705
|
if (args[0] === 'claude') {
|
|
557
|
-
|
|
706
|
+
const claudeArgs = args.slice(1);
|
|
707
|
+
if (claudeArgs[0] === '--continue' || claudeArgs[0] === '-c') {
|
|
708
|
+
await runContinue(claudeArgs.slice(1));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (claudeArgs[0] === '--resume' || claudeArgs[0] === '-r') {
|
|
712
|
+
const id = claudeArgs[1];
|
|
713
|
+
if (id && isUuid(id)) {
|
|
714
|
+
await runResume(id, claudeArgs.slice(2));
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (claudeArgs[0]?.startsWith('--resume=')) {
|
|
719
|
+
const id = claudeArgs[0].slice('--resume='.length);
|
|
720
|
+
if (isUuid(id)) {
|
|
721
|
+
await runResume(id, claudeArgs.slice(1));
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
await runInteractive(claudeArgs);
|
|
558
726
|
return;
|
|
559
727
|
}
|
|
560
728
|
await createProgram().parseAsync(argv);
|