@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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/cli.js +91 -40
- package/package.json +1 -5
- package/skill/SKILL.md +1 -1
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
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:
|
|
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
|
|
4013
|
-
lines.push(truncate(`${DIM}${hint}${RESET}${
|
|
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
|
-
|
|
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
|
|
4113
|
-
if (
|
|
4114
|
-
const s = sessions[
|
|
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
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
const
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
const
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
await
|
|
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.
|
|
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
|
},
|