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/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
+ });