claude-session-skill 1.1.7 → 1.1.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 +10 -1
- package/SKILL.md +12 -0
- package/dist/mcp-server.js +526 -1260
- package/dist/session.js +48 -0
- package/lib/__tests__/format.test.ts +33 -0
- package/lib/__tests__/mcp-server.test.ts +63 -0
- package/lib/__tests__/mcp-tools.test.ts +989 -0
- package/lib/create-server.ts +311 -0
- package/lib/format.ts +31 -1
- package/mcp-server.ts +2 -250
- package/package.json +1 -1
- package/session.ts +27 -0
package/dist/session.js
CHANGED
|
@@ -687,6 +687,9 @@ function truncate(s, max) {
|
|
|
687
687
|
return s;
|
|
688
688
|
return chars.slice(0, max - 3).join("") + "...";
|
|
689
689
|
}
|
|
690
|
+
function normalizeWhitespace(value) {
|
|
691
|
+
return value.replace(/\s+/g, " ").trim();
|
|
692
|
+
}
|
|
690
693
|
function formatDate(ts) {
|
|
691
694
|
if (!ts)
|
|
692
695
|
return "unknown";
|
|
@@ -700,6 +703,17 @@ function formatDate(ts) {
|
|
|
700
703
|
const h = hours % 12 || 12;
|
|
701
704
|
return `${month} ${day}, ${h}:${mins} ${ampm}`;
|
|
702
705
|
}
|
|
706
|
+
function formatNameTimestamp(ts) {
|
|
707
|
+
if (!ts)
|
|
708
|
+
return "unknown";
|
|
709
|
+
const d = new Date(ts);
|
|
710
|
+
const day = d.getDate().toString().padStart(2, "0");
|
|
711
|
+
const month = (d.getMonth() + 1).toString().padStart(2, "0");
|
|
712
|
+
const year = (d.getFullYear() % 100).toString().padStart(2, "0");
|
|
713
|
+
const hours = d.getHours().toString().padStart(2, "0");
|
|
714
|
+
const minutes = d.getMinutes().toString().padStart(2, "0");
|
|
715
|
+
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
|
716
|
+
}
|
|
703
717
|
function displayLine(s) {
|
|
704
718
|
if (s.topic && !isGarbageSummary2(s.topic)) {
|
|
705
719
|
const lines = s.topic.split(`
|
|
@@ -713,6 +727,17 @@ function displayLine(s) {
|
|
|
713
727
|
return s.lastMessage;
|
|
714
728
|
return s.firstMessage || "(no messages)";
|
|
715
729
|
}
|
|
730
|
+
function makeAutoSessionName(s, maxLength = 50) {
|
|
731
|
+
const prefix = formatNameTimestamp(s.firstTimestamp);
|
|
732
|
+
const summary = displayLine(s);
|
|
733
|
+
const cleanSummary = normalizeWhitespace(summary.startsWith("- ") ? summary.slice(2) : summary).replace(/^["']+|["']+$/g, "");
|
|
734
|
+
if (!cleanSummary)
|
|
735
|
+
return prefix;
|
|
736
|
+
const available = maxLength - prefix.length - 1;
|
|
737
|
+
if (available <= 0)
|
|
738
|
+
return prefix.slice(0, maxLength);
|
|
739
|
+
return `${prefix} ${truncate(cleanSummary, available)}`;
|
|
740
|
+
}
|
|
716
741
|
function makeLabel(s, summary) {
|
|
717
742
|
if (!s.name)
|
|
718
743
|
return summary;
|
|
@@ -919,6 +944,28 @@ async function main() {
|
|
|
919
944
|
console.log(`Named session ${(result.fullId ?? "").slice(0, 8)}... \u2192 "${sessionName.trim()}"`);
|
|
920
945
|
break;
|
|
921
946
|
}
|
|
947
|
+
case "autoname": {
|
|
948
|
+
const rest = args.slice(1);
|
|
949
|
+
const sessions = await buildIndex();
|
|
950
|
+
if (sessions.length === 0) {
|
|
951
|
+
console.error("No sessions found.");
|
|
952
|
+
process.exit(1);
|
|
953
|
+
}
|
|
954
|
+
const sessionId = rest[0] ?? sessions[0].id;
|
|
955
|
+
const resolved = resolveSession(sessions, sessionId);
|
|
956
|
+
if (!resolved.ok) {
|
|
957
|
+
console.error(resolved.error);
|
|
958
|
+
process.exit(1);
|
|
959
|
+
}
|
|
960
|
+
const generatedName = makeAutoSessionName(resolved.match);
|
|
961
|
+
const result = await nameSession(resolved.match.id, generatedName);
|
|
962
|
+
if (!result.ok) {
|
|
963
|
+
console.error(result.error);
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
console.log(`Named session ${(result.fullId ?? "").slice(0, 8)}... \u2192 "${generatedName}"`);
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
922
969
|
case "unname":
|
|
923
970
|
case "clear-name": {
|
|
924
971
|
const rest = args.slice(1);
|
|
@@ -979,6 +1026,7 @@ async function main() {
|
|
|
979
1026
|
session show <id> Show session details (partial ID ok)
|
|
980
1027
|
session name <name> Name the most recent session
|
|
981
1028
|
session name <id> <name> Name a specific session (partial ID ok)
|
|
1029
|
+
session autoname [<id>] Name a session from its summary + start time
|
|
982
1030
|
session unname [<id>] Clear a session's name
|
|
983
1031
|
session search <query> Search sessions by keyword
|
|
984
1032
|
session rebuild Force rebuild the index
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
formatSearchResults,
|
|
5
5
|
formatSessionDetail,
|
|
6
6
|
formatStats,
|
|
7
|
+
makeAutoSessionName,
|
|
7
8
|
} from "../format";
|
|
8
9
|
import type { SessionEntry } from "../indexer";
|
|
9
10
|
|
|
@@ -182,6 +183,38 @@ describe("formatStats", () => {
|
|
|
182
183
|
});
|
|
183
184
|
});
|
|
184
185
|
|
|
186
|
+
describe("makeAutoSessionName", () => {
|
|
187
|
+
test("prefixes name with dd/mm/yy HH:MM timestamp", () => {
|
|
188
|
+
const name = makeAutoSessionName(
|
|
189
|
+
makeSession({
|
|
190
|
+
firstTimestamp: new Date("2026-03-31T04:14:00Z").getTime(),
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(name).toMatch(/^\d{2}\/\d{2}\/\d{2} \d{2}:\d{2} /);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("uses summary text after timestamp prefix", () => {
|
|
198
|
+
const name = makeAutoSessionName(
|
|
199
|
+
makeSession({
|
|
200
|
+
topic: "- Fixed authentication bug in login flow",
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
expect(name).toContain("Fixed authentication bug");
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("stays within 50 characters", () => {
|
|
208
|
+
const name = makeAutoSessionName(
|
|
209
|
+
makeSession({
|
|
210
|
+
topic: `- ${"x".repeat(200)}`,
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(Array.from(name).length).toBeLessThanOrEqual(50);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
185
218
|
// ── formatDate edge cases (via formatSessionList) ─────────────────────────────
|
|
186
219
|
|
|
187
220
|
describe("formatDate edge cases (via formatSessionList)", () => {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, test, expect } from "bun:test";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* mcp-server.ts is a thin entry point that calls createServer() from
|
|
7
|
+
* lib/create-server.ts. All handler logic lives in create-server.ts,
|
|
8
|
+
* with business logic in indexer.ts, search.ts, and format.ts.
|
|
9
|
+
*
|
|
10
|
+
* These tests verify:
|
|
11
|
+
* 1. The version is read from package.json (not hardcoded)
|
|
12
|
+
* 2. The module can be parsed without errors
|
|
13
|
+
* 3. The server name matches expectations
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const ROOT = join(import.meta.dir, "../..");
|
|
17
|
+
const entrySource = readFileSync(join(ROOT, "mcp-server.ts"), "utf-8");
|
|
18
|
+
const serverSource = readFileSync(join(ROOT, "lib", "create-server.ts"), "utf-8");
|
|
19
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
20
|
+
|
|
21
|
+
describe("mcp-server", () => {
|
|
22
|
+
test("reads version from package.json, not hardcoded", () => {
|
|
23
|
+
// Should use pkg.version, not a string literal like "1.1.0"
|
|
24
|
+
expect(serverSource).toContain("pkg.version");
|
|
25
|
+
expect(serverSource).not.toMatch(/version:\s*["']\d+\.\d+\.\d+["']/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("uses correct server name", () => {
|
|
29
|
+
expect(serverSource).toContain('name: "claude-session"');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("declares all 7 expected tools", () => {
|
|
33
|
+
const expectedTools = [
|
|
34
|
+
"list_sessions",
|
|
35
|
+
"search_sessions",
|
|
36
|
+
"show_session",
|
|
37
|
+
"name_session",
|
|
38
|
+
"autoname_session",
|
|
39
|
+
"unname_session",
|
|
40
|
+
"session_stats",
|
|
41
|
+
];
|
|
42
|
+
for (const tool of expectedTools) {
|
|
43
|
+
expect(serverSource).toContain(`name: "${tool}"`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("package.json version is valid semver", () => {
|
|
48
|
+
expect(pkg.version).toMatch(/^\d+\.\d+\.\d+$/);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("never uses console.log (would corrupt JSON-RPC stdio)", () => {
|
|
52
|
+
// Check both the entry point and the server factory
|
|
53
|
+
for (const src of [entrySource, serverSource]) {
|
|
54
|
+
const noComments = src.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
55
|
+
expect(noComments).not.toContain("console.log");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("entry point uses createServer factory", () => {
|
|
60
|
+
expect(entrySource).toContain("createServer");
|
|
61
|
+
expect(entrySource).toContain("StdioServerTransport");
|
|
62
|
+
});
|
|
63
|
+
});
|