@halooojustin/cch 0.1.0 → 0.2.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/README.md CHANGED
@@ -16,7 +16,7 @@ Claude Code stores conversation history in `~/.claude/projects/`, scoped per dir
16
16
  ## Install
17
17
 
18
18
  ```bash
19
- npm install -g cch
19
+ npm install -g @halooojustin/cch
20
20
  ch setup # adds shell aliases (cn, cnf, cls, cps, chs)
21
21
  source ~/.zshrc # or open a new terminal
22
22
  ```
package/README.zh-CN.md CHANGED
@@ -16,7 +16,7 @@ Claude Code 的对话历史存在 `~/.claude/projects/` 下,按项目目录隔
16
16
  ## 安装
17
17
 
18
18
  ```bash
19
- npm install -g cch
19
+ npm install -g @halooojustin/cch
20
20
  ch setup # 自动添加 shell 别名 (cn, cnf, cls, cps, chs)
21
21
  source ~/.zshrc # 或新开终端
22
22
  ```
package/dist/cli.js CHANGED
@@ -3547,6 +3547,10 @@ default_layout "${escapeKdl(layoutPath)}"
3547
3547
  try {
3548
3548
  execFileSync("zellij", ["kill-session", name], { stdio: "pipe" });
3549
3549
  } catch {
3550
+ try {
3551
+ execFileSync("zellij", ["delete-session", name], { stdio: "pipe" });
3552
+ } catch {
3553
+ }
3550
3554
  }
3551
3555
  }
3552
3556
  };
@@ -3751,7 +3755,7 @@ var CACHE_FILE = join2(CONFIG_DIR, "cache.json");
3751
3755
  var DEFAULT_CONFIG = {
3752
3756
  backend: "auto",
3753
3757
  claudeCommand: "claude",
3754
- claudeArgs: [],
3758
+ claudeArgs: ["--dangerously-skip-permissions"],
3755
3759
  historyLimit: 100
3756
3760
  };
3757
3761
  function ensureDir() {
@@ -3951,13 +3955,13 @@ async function killSession(name) {
3951
3955
  backend.killSession(name);
3952
3956
  removeSessionMeta(name);
3953
3957
  }
3954
- async function resumeInSession(sessionId, cwd) {
3958
+ async function resumeInSession(sessionId, cwd, description) {
3955
3959
  const backend = await getBackend();
3956
3960
  const config = getConfig();
3957
3961
  const dirName = basename2(cwd);
3958
3962
  const name = `ch-${dirName}-${sessionId.slice(0, 8)}`;
3959
3963
  setSessionMeta(name, {
3960
- description: `resumed: ${sessionId.slice(0, 8)}`,
3964
+ description: description || sessionId.slice(0, 8),
3961
3965
  cwd,
3962
3966
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
3963
3967
  });
@@ -3992,10 +3996,10 @@ function truncate(str, maxWidth) {
3992
3996
  }
3993
3997
  return str.slice(0, i);
3994
3998
  }
3995
- function interactiveSelect(items, hint = "Up/Down to navigate, Enter to select, Esc to cancel") {
3996
- if (!process.stdin.isTTY) return Promise.resolve(-1);
3999
+ function interactiveSelect(items, hint = "Up/Down to navigate, Enter to select, Esc to cancel", options) {
4000
+ if (!process.stdin.isTTY) return Promise.resolve({ value: -1, action: "cancel" });
3997
4001
  return new Promise((resolve) => {
3998
- let cursor = 0;
4002
+ let cursor = Math.min(options?.initialCursor ?? 0, items.length - 1);
3999
4003
  const cols = process.stdout.columns || 80;
4000
4004
  const rows = process.stdout.rows || 24;
4001
4005
  const pageSize = Math.min(items.length, rows - 4);
@@ -4009,8 +4013,8 @@ function interactiveSelect(items, hint = "Up/Down to navigate, Enter to select,
4009
4013
  function renderLines() {
4010
4014
  const { start, end } = getWindow();
4011
4015
  const lines = [];
4012
- const inputDisplay = inputBuf ? ` > ${inputBuf}_` : "";
4013
- lines.push(truncate(`${DIM}${hint}${RESET}${inputDisplay}`, cols));
4016
+ const statusDisplay = pendingDelete ? ` ${ESC}[31md \u2014 press d again to kill${RESET}` : inputBuf ? ` > ${inputBuf}_` : "";
4017
+ lines.push(truncate(`${DIM}${hint}${RESET}${statusDisplay}`, cols));
4014
4018
  for (let i = start; i < end; i++) {
4015
4019
  const raw = items[i].label;
4016
4020
  if (i === cursor) {
@@ -4047,20 +4051,37 @@ function interactiveSelect(items, hint = "Up/Down to navigate, Enter to select,
4047
4051
  drawnLines = lines.length;
4048
4052
  }
4049
4053
  let inputBuf = "";
4050
- function cleanup() {
4054
+ let pendingDelete = false;
4055
+ let deleteTimer = null;
4056
+ function cleanup(clearOutput = false) {
4057
+ if (deleteTimer) clearTimeout(deleteTimer);
4051
4058
  process.stdin.setRawMode(false);
4052
4059
  process.stdin.removeListener("data", onData);
4053
4060
  process.stdin.pause();
4061
+ if (clearOutput && drawnLines > 0) {
4062
+ for (let i = 0; i < drawnLines; i++) {
4063
+ process.stdout.write(`${ESC}[A`);
4064
+ }
4065
+ for (let i = 0; i < drawnLines; i++) {
4066
+ process.stdout.write(`${CLEAR_LINE}
4067
+ `);
4068
+ }
4069
+ for (let i = 0; i < drawnLines; i++) {
4070
+ process.stdout.write(`${ESC}[A`);
4071
+ }
4072
+ }
4054
4073
  process.stdout.write(CURSOR_SHOW);
4055
4074
  }
4056
4075
  function onData(buf) {
4057
4076
  const key = buf.toString();
4058
4077
  if (key === `${ESC}[A` || key === "k") {
4059
4078
  inputBuf = "";
4079
+ pendingDelete = false;
4060
4080
  if (cursor > 0) cursor--;
4061
4081
  draw();
4062
4082
  } else if (key === `${ESC}[B` || key === "j") {
4063
4083
  inputBuf = "";
4084
+ pendingDelete = false;
4064
4085
  if (cursor < items.length - 1) cursor++;
4065
4086
  draw();
4066
4087
  } else if (key === "\r" || key === "\n") {
@@ -4068,15 +4089,30 @@ function interactiveSelect(items, hint = "Up/Down to navigate, Enter to select,
4068
4089
  const num = parseInt(inputBuf, 10);
4069
4090
  if (num >= 1 && num <= items.length) {
4070
4091
  cleanup();
4071
- resolve(items[num - 1].value);
4092
+ resolve({ value: items[num - 1].value, action: "select" });
4072
4093
  return;
4073
4094
  }
4074
4095
  }
4075
4096
  cleanup();
4076
- resolve(items[cursor].value);
4097
+ resolve({ value: items[cursor].value, action: "select" });
4098
+ } else if (key === "d" && options?.deleteKey && !inputBuf) {
4099
+ if (pendingDelete) {
4100
+ pendingDelete = false;
4101
+ if (deleteTimer) clearTimeout(deleteTimer);
4102
+ cleanup(true);
4103
+ resolve({ value: items[cursor].value, action: "delete" });
4104
+ return;
4105
+ }
4106
+ pendingDelete = true;
4107
+ draw();
4108
+ deleteTimer = setTimeout(() => {
4109
+ pendingDelete = false;
4110
+ draw();
4111
+ }, 1e3);
4112
+ return;
4077
4113
  } else if (key === ESC || key === "q" || key === "") {
4078
4114
  cleanup();
4079
- resolve(-1);
4115
+ resolve({ value: -1, action: "cancel" });
4080
4116
  } else if (key === "\x7F" || key === "\b") {
4081
4117
  if (inputBuf.length > 0) {
4082
4118
  inputBuf = inputBuf.slice(0, -1);
@@ -4109,10 +4145,10 @@ async function lsCommand(n) {
4109
4145
  const ts = s.timestamp.slice(5, 16).replace("T", " ");
4110
4146
  return { label: `${num} ${project.padEnd(20)} ${ts} ${msg}`, value: i };
4111
4147
  });
4112
- const selected = await interactiveSelect(items);
4113
- if (selected >= 0) {
4114
- const s = sessions[selected];
4115
- await resumeInSession(s.sessionId, s.cwd);
4148
+ const result = await interactiveSelect(items);
4149
+ if (result.action === "select" && result.value >= 0) {
4150
+ const s = sessions[result.value];
4151
+ await resumeInSession(s.sessionId, s.cwd, s.firstMsg.replace(/\n/g, " ").slice(0, 50));
4116
4152
  }
4117
4153
  }
4118
4154
 
@@ -4144,7 +4180,7 @@ Found ${matches.length} sessions:
4144
4180
  const idx = parseInt(answer, 10);
4145
4181
  if (idx >= 1 && idx <= matches.length) {
4146
4182
  const s = matches[idx - 1];
4147
- await resumeInSession(s.sessionId, s.cwd);
4183
+ await resumeInSession(s.sessionId, s.cwd, s.firstMsg.replace(/\n/g, " ").slice(0, 50));
4148
4184
  }
4149
4185
  }
4150
4186
  }
@@ -4239,7 +4275,7 @@ async function defaultCommand(query) {
4239
4275
  rl.close();
4240
4276
  if (answer.trim().toLowerCase() !== "n") {
4241
4277
  const s = sessions[indices[0] - 1];
4242
- await resumeInSession(s.sessionId, s.cwd);
4278
+ await resumeInSession(s.sessionId, s.cwd, s.firstMsg.replace(/\n/g, " ").slice(0, 50));
4243
4279
  }
4244
4280
  } else {
4245
4281
  const answer = await new Promise((resolve) => {
@@ -4249,7 +4285,7 @@ async function defaultCommand(query) {
4249
4285
  const pick = parseInt(answer, 10);
4250
4286
  if (pick >= 1 && pick <= indices.length) {
4251
4287
  const s = sessions[indices[pick - 1] - 1];
4252
- await resumeInSession(s.sessionId, s.cwd);
4288
+ await resumeInSession(s.sessionId, s.cwd, s.firstMsg.replace(/\n/g, " ").slice(0, 50));
4253
4289
  }
4254
4290
  }
4255
4291
  }
@@ -4266,26 +4302,41 @@ async function newCommand(description, force) {
4266
4302
 
4267
4303
  // src/commands/ps.ts
4268
4304
  async function psCommand() {
4269
- const sessions = await listActiveSessions();
4270
- if (!sessions.length) {
4271
- console.log("No active multiplexer sessions.");
4272
- return;
4273
- }
4274
- const meta = getSessionsMeta();
4275
- sessions.sort((a, b) => {
4276
- const timeA = meta[a.name]?.createdAt || "";
4277
- const timeB = meta[b.name]?.createdAt || "";
4278
- return timeB.localeCompare(timeA);
4279
- });
4280
- const items = sessions.map((s, i) => {
4281
- const num = String(i + 1).padStart(3);
4282
- const desc = meta[s.name]?.description || "";
4283
- const label = desc ? `${num} ${s.name.padEnd(28)} ${s.created.padEnd(12)} ${desc}` : `${num} ${s.name.padEnd(28)} ${s.created}`;
4284
- return { label, value: i };
4285
- });
4286
- const selected = await interactiveSelect(items);
4287
- if (selected >= 0) {
4288
- await attachToSession(sessions[selected].name);
4305
+ let cursorPos = 0;
4306
+ while (true) {
4307
+ const sessions = await listActiveSessions();
4308
+ if (!sessions.length) {
4309
+ console.log("No active multiplexer sessions.");
4310
+ return;
4311
+ }
4312
+ const meta = getSessionsMeta();
4313
+ sessions.sort((a, b) => {
4314
+ const timeA = meta[a.name]?.createdAt || "";
4315
+ const timeB = meta[b.name]?.createdAt || "";
4316
+ return timeB.localeCompare(timeA);
4317
+ });
4318
+ const items = sessions.map((s, i) => {
4319
+ const num = String(i + 1).padStart(3);
4320
+ const desc = meta[s.name]?.description || "";
4321
+ const label = desc ? `${num} ${s.name.padEnd(28)} ${s.created.padEnd(12)} ${desc}` : `${num} ${s.name.padEnd(28)} ${s.created}`;
4322
+ return { label, value: i };
4323
+ });
4324
+ const result = await interactiveSelect(
4325
+ items,
4326
+ "Up/Down navigate, Enter attach, dd kill, Esc cancel",
4327
+ { deleteKey: true, initialCursor: cursorPos }
4328
+ );
4329
+ if (result.action === "cancel") return;
4330
+ if (result.action === "delete") {
4331
+ const s = sessions[result.value];
4332
+ cursorPos = result.value;
4333
+ await killSession(s.name);
4334
+ continue;
4335
+ }
4336
+ if (result.action === "select") {
4337
+ await attachToSession(sessions[result.value].name);
4338
+ return;
4339
+ }
4289
4340
  }
4290
4341
  }
4291
4342
 
@@ -4305,7 +4356,7 @@ async function resumeCommand(sessionId) {
4305
4356
  const sessions = loadSessions();
4306
4357
  const match = sessions.find((s) => s.sessionId === sessionId);
4307
4358
  if (match) {
4308
- await resumeInSession(match.sessionId, match.cwd);
4359
+ await resumeInSession(match.sessionId, match.cwd, match.firstMsg.replace(/\n/g, " ").slice(0, 50));
4309
4360
  } else {
4310
4361
  console.error(`Session not found: ${sessionId}`);
4311
4362
  console.error("Try `ch list` to see available sessions.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@halooojustin/cch",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code History — AI-powered conversation history management with Zellij/tmux session support",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,10 +30,6 @@
30
30
  "README.md",
31
31
  "README.zh-CN.md"
32
32
  ],
33
- "repository": {
34
- "type": "git",
35
- "url": "https://github.com/user/cch"
36
- },
37
33
  "engines": {
38
34
  "node": ">=18"
39
35
  },
package/skill/SKILL.md CHANGED
@@ -113,7 +113,7 @@ claude
113
113
  ### 5. 安装 CCH
114
114
 
115
115
  ```bash
116
- npm install -g cch
116
+ npm install -g @halooojustin/cch
117
117
  ```
118
118
 
119
119
  ### 6. 设置 shell 别名