session-forge 1.0.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/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +30 -0
- package/dist/storage/paths.d.ts +11 -0
- package/dist/storage/paths.js +23 -0
- package/dist/storage/store.d.ts +5 -0
- package/dist/storage/store.js +44 -0
- package/dist/tools/context.d.ts +3 -0
- package/dist/tools/context.js +50 -0
- package/dist/tools/dead-ends.d.ts +3 -0
- package/dist/tools/dead-ends.js +73 -0
- package/dist/tools/decisions.d.ts +3 -0
- package/dist/tools/decisions.js +74 -0
- package/dist/tools/journal.d.ts +3 -0
- package/dist/tools/journal.js +79 -0
- package/dist/tools/profile.d.ts +3 -0
- package/dist/tools/profile.js +89 -0
- package/dist/tools/sessions.d.ts +3 -0
- package/dist/tools/sessions.js +156 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +3 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jacob Terrell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# session-forge
|
|
2
|
+
|
|
3
|
+
**Never start from zero.** Persistent session intelligence for AI coding assistants.
|
|
4
|
+
|
|
5
|
+
Session-forge gives your AI coding assistant memory that survives across sessions. It tracks decisions, dead ends, user preferences, and session state — so every conversation builds on the last one instead of starting from scratch.
|
|
6
|
+
|
|
7
|
+
Works with **Claude Code**, **Cursor**, **Windsurf**, and any MCP-compatible client.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What it does
|
|
12
|
+
|
|
13
|
+
- **Session crash recovery** — checkpoint your work, pick up where you left off
|
|
14
|
+
- **Decision logging** — record why you chose X over Y, search it later
|
|
15
|
+
- **Dead end tracking** — never repeat the same debugging mistake twice
|
|
16
|
+
- **User profile** — AI remembers your name, preferences, and projects
|
|
17
|
+
- **Session journal** — capture the journey, not just the task
|
|
18
|
+
- **Full context recall** — bootstrap a new session with everything in one call
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
### Claude Code
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add session-forge -- npx session-forge
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Cursor / Windsurf
|
|
29
|
+
|
|
30
|
+
Add to your MCP settings:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"session-forge": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "session-forge"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That's it. No database, no Docker, no config files.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## The 12 tools
|
|
48
|
+
|
|
49
|
+
### Sessions
|
|
50
|
+
|
|
51
|
+
| Tool | Description | Required |
|
|
52
|
+
|------|-------------|----------|
|
|
53
|
+
| `session_checkpoint` | Save work-in-progress state for crash recovery | task, intent, next_steps |
|
|
54
|
+
| `session_restore` | Check for interrupted work from a previous session | — |
|
|
55
|
+
| `session_complete` | Archive session and mark complete | — |
|
|
56
|
+
|
|
57
|
+
### Profile
|
|
58
|
+
|
|
59
|
+
| Tool | Description | Required |
|
|
60
|
+
|------|-------------|----------|
|
|
61
|
+
| `profile_get` | Get the current user profile | — |
|
|
62
|
+
| `profile_update` | Update name, preferences, projects, or notes | — |
|
|
63
|
+
|
|
64
|
+
### Journal
|
|
65
|
+
|
|
66
|
+
| Tool | Description | Required |
|
|
67
|
+
|------|-------------|----------|
|
|
68
|
+
| `journal_entry` | Record session summary with breakthroughs and frustrations | summary |
|
|
69
|
+
| `journal_recall` | Retrieve recent session journals | — |
|
|
70
|
+
|
|
71
|
+
### Decisions
|
|
72
|
+
|
|
73
|
+
| Tool | Description | Required |
|
|
74
|
+
|------|-------------|----------|
|
|
75
|
+
| `decision_record` | Log a significant decision with alternatives and reasoning | choice, reasoning |
|
|
76
|
+
| `decision_search` | Search past decisions by keyword | query |
|
|
77
|
+
|
|
78
|
+
### Dead Ends
|
|
79
|
+
|
|
80
|
+
| Tool | Description | Required |
|
|
81
|
+
|------|-------------|----------|
|
|
82
|
+
| `dead_end_record` | Log a failed approach and the lesson learned | attempted, why_failed |
|
|
83
|
+
| `dead_end_search` | Search past dead ends to avoid repeating mistakes | query |
|
|
84
|
+
|
|
85
|
+
### Context
|
|
86
|
+
|
|
87
|
+
| Tool | Description | Required |
|
|
88
|
+
|------|-------------|----------|
|
|
89
|
+
| `full_context_recall` | Get everything — profile, journals, decisions, dead ends | — |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Why session-forge?
|
|
94
|
+
|
|
95
|
+
| | session-forge | Basic memory MCPs | Enterprise tools |
|
|
96
|
+
|---|---|---|---|
|
|
97
|
+
| Setup | `npx session-forge` | Varies | Docker + databases |
|
|
98
|
+
| Dead end tracking | Yes | No | No |
|
|
99
|
+
| Decision logging | Yes | No | Some |
|
|
100
|
+
| Session crash recovery | Yes | Some | Yes |
|
|
101
|
+
| User profile | Yes | Some | No |
|
|
102
|
+
| Dependencies | 2 (SDK + zod) | Varies | 5-10+ |
|
|
103
|
+
| Infrastructure | Zero (plain JSON) | SQLite/ONNX/Vector DB | PostgreSQL + Redis |
|
|
104
|
+
| Tools | 12 focused | 4-9 | 37+ |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Storage
|
|
109
|
+
|
|
110
|
+
All data is stored locally as plain JSON files:
|
|
111
|
+
|
|
112
|
+
| Platform | Location |
|
|
113
|
+
|----------|----------|
|
|
114
|
+
| Linux / macOS | `~/.session-forge/` |
|
|
115
|
+
| Windows | `%APPDATA%\session-forge\` |
|
|
116
|
+
|
|
117
|
+
Override with the `SESSION_FORGE_DIR` environment variable:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
SESSION_FORGE_DIR=/custom/path npx session-forge
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Files:
|
|
124
|
+
```
|
|
125
|
+
~/.session-forge/
|
|
126
|
+
profile.json # User preferences and projects
|
|
127
|
+
journal.json # Session summaries (last 100)
|
|
128
|
+
decisions.json # Decision log (last 200)
|
|
129
|
+
dead-ends.json # Failed approaches (last 100)
|
|
130
|
+
sessions/
|
|
131
|
+
active.json # Current checkpoint
|
|
132
|
+
history/ # Archived sessions
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## CLAUDE.md template
|
|
138
|
+
|
|
139
|
+
Add this to your project's `CLAUDE.md` to teach the AI when to call each tool:
|
|
140
|
+
|
|
141
|
+
```markdown
|
|
142
|
+
## Session Flow
|
|
143
|
+
|
|
144
|
+
### Fresh session
|
|
145
|
+
1. Call `full_context_recall` — get profile, journals, decisions, dead ends
|
|
146
|
+
2. Call `session_restore` — check for interrupted work
|
|
147
|
+
|
|
148
|
+
### During work
|
|
149
|
+
- `decision_record` — when making a significant architectural choice
|
|
150
|
+
- `dead_end_record` — when something fails and we learn why
|
|
151
|
+
- `session_checkpoint` — every 10-15 tool calls during long sessions
|
|
152
|
+
|
|
153
|
+
### Session end
|
|
154
|
+
1. Call `journal_entry` — record what happened
|
|
155
|
+
2. Call `session_complete` — archive the checkpoint
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
MIT
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
Built by [Jacob Terrell](https://github.com/jacobterrell)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerSessionTools } from "./tools/sessions.js";
|
|
5
|
+
import { registerProfileTools } from "./tools/profile.js";
|
|
6
|
+
import { registerJournalTools } from "./tools/journal.js";
|
|
7
|
+
import { registerDecisionTools } from "./tools/decisions.js";
|
|
8
|
+
import { registerDeadEndTools } from "./tools/dead-ends.js";
|
|
9
|
+
import { registerContextTools } from "./tools/context.js";
|
|
10
|
+
const VERSION = "1.0.0";
|
|
11
|
+
const server = new McpServer({
|
|
12
|
+
name: "session-forge",
|
|
13
|
+
version: VERSION,
|
|
14
|
+
});
|
|
15
|
+
registerSessionTools(server);
|
|
16
|
+
registerProfileTools(server);
|
|
17
|
+
registerJournalTools(server);
|
|
18
|
+
registerDecisionTools(server);
|
|
19
|
+
registerDeadEndTools(server);
|
|
20
|
+
registerContextTools(server);
|
|
21
|
+
async function main() {
|
|
22
|
+
const transport = new StdioServerTransport();
|
|
23
|
+
await server.connect(transport);
|
|
24
|
+
console.error(`session-forge v${VERSION} running`);
|
|
25
|
+
}
|
|
26
|
+
main().catch((err) => {
|
|
27
|
+
console.error("Fatal:", err);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const PATHS: {
|
|
2
|
+
readonly base: string;
|
|
3
|
+
readonly profile: string;
|
|
4
|
+
readonly journal: string;
|
|
5
|
+
readonly decisions: string;
|
|
6
|
+
readonly deadEnds: string;
|
|
7
|
+
readonly sessions: string;
|
|
8
|
+
readonly activeSession: string;
|
|
9
|
+
readonly sessionHistory: string;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
function getBaseDir() {
|
|
4
|
+
if (process.env.SESSION_FORGE_DIR) {
|
|
5
|
+
return process.env.SESSION_FORGE_DIR;
|
|
6
|
+
}
|
|
7
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
8
|
+
return join(process.env.APPDATA, "session-forge");
|
|
9
|
+
}
|
|
10
|
+
return join(homedir(), ".session-forge");
|
|
11
|
+
}
|
|
12
|
+
const base = getBaseDir();
|
|
13
|
+
export const PATHS = {
|
|
14
|
+
base,
|
|
15
|
+
profile: join(base, "profile.json"),
|
|
16
|
+
journal: join(base, "journal.json"),
|
|
17
|
+
decisions: join(base, "decisions.json"),
|
|
18
|
+
deadEnds: join(base, "dead-ends.json"),
|
|
19
|
+
sessions: join(base, "sessions"),
|
|
20
|
+
activeSession: join(base, "sessions", "active.json"),
|
|
21
|
+
sessionHistory: join(base, "sessions", "history"),
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function readJson<T>(filePath: string, fallback: T): T;
|
|
2
|
+
export declare function writeJson<T>(filePath: string, data: T): void;
|
|
3
|
+
export declare function deleteJson(filePath: string): boolean;
|
|
4
|
+
export declare function searchEntries<T>(entries: T[], query: string, textExtractor: (entry: T) => string): T[];
|
|
5
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export function readJson(filePath, fallback) {
|
|
4
|
+
try {
|
|
5
|
+
if (!existsSync(filePath))
|
|
6
|
+
return fallback;
|
|
7
|
+
return JSON.parse(readFileSync(filePath, "utf-8"));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function writeJson(filePath, data) {
|
|
14
|
+
const dir = dirname(filePath);
|
|
15
|
+
if (!existsSync(dir))
|
|
16
|
+
mkdirSync(dir, { recursive: true });
|
|
17
|
+
writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
18
|
+
}
|
|
19
|
+
export function deleteJson(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
if (existsSync(filePath)) {
|
|
22
|
+
unlinkSync(filePath);
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function searchEntries(entries, query, textExtractor) {
|
|
32
|
+
const words = query
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.split(/\s+/)
|
|
35
|
+
.filter((w) => w.length > 1);
|
|
36
|
+
if (words.length === 0)
|
|
37
|
+
return entries.slice(-20);
|
|
38
|
+
const filtered = entries.filter((entry) => {
|
|
39
|
+
const text = textExtractor(entry).toLowerCase();
|
|
40
|
+
return words.some((word) => text.includes(word));
|
|
41
|
+
});
|
|
42
|
+
return filtered.slice(-20);
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PATHS } from "../storage/paths.js";
|
|
2
|
+
import { readJson } from "../storage/store.js";
|
|
3
|
+
const DEFAULT_PROFILE = {
|
|
4
|
+
name: null,
|
|
5
|
+
preferences: {
|
|
6
|
+
communication_style: "direct",
|
|
7
|
+
emoji_usage: "occasional",
|
|
8
|
+
technical_level: "advanced",
|
|
9
|
+
verbosity: "concise",
|
|
10
|
+
},
|
|
11
|
+
projects: [],
|
|
12
|
+
notes: [],
|
|
13
|
+
created_at: new Date().toISOString(),
|
|
14
|
+
updated_at: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
export function registerContextTools(server) {
|
|
17
|
+
server.registerTool("full_context_recall", {
|
|
18
|
+
description: "Get EVERYTHING - user profile, recent sessions, decisions, dead ends. Use when starting fresh to get full context.",
|
|
19
|
+
inputSchema: {},
|
|
20
|
+
}, async () => {
|
|
21
|
+
const profile = readJson(PATHS.profile, {
|
|
22
|
+
...DEFAULT_PROFILE,
|
|
23
|
+
created_at: new Date().toISOString(),
|
|
24
|
+
updated_at: new Date().toISOString(),
|
|
25
|
+
});
|
|
26
|
+
const journal = readJson(PATHS.journal, { sessions: [] });
|
|
27
|
+
const decisions = readJson(PATHS.decisions, {
|
|
28
|
+
decisions: [],
|
|
29
|
+
});
|
|
30
|
+
const deadEnds = readJson(PATHS.deadEnds, {
|
|
31
|
+
dead_ends: [],
|
|
32
|
+
});
|
|
33
|
+
const context = {
|
|
34
|
+
user_profile: profile,
|
|
35
|
+
recent_sessions: journal.sessions.slice(-3),
|
|
36
|
+
recent_decisions: decisions.decisions.slice(-10),
|
|
37
|
+
recent_dead_ends: deadEnds.dead_ends.slice(-10),
|
|
38
|
+
retrieved_at: new Date().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: JSON.stringify(context, null, 2),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PATHS } from "../storage/paths.js";
|
|
3
|
+
import { readJson, writeJson, searchEntries } from "../storage/store.js";
|
|
4
|
+
export function registerDeadEndTools(server) {
|
|
5
|
+
server.registerTool("dead_end_record", {
|
|
6
|
+
description: "Record a debugging dead end so we don't repeat it. Captures what was tried and why it failed.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
attempted: z.string().describe("What was tried"),
|
|
9
|
+
why_failed: z.string().describe("Why it didn't work"),
|
|
10
|
+
lesson: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("What to remember for next time"),
|
|
14
|
+
project: z.string().optional().describe("Which project"),
|
|
15
|
+
files_involved: z
|
|
16
|
+
.array(z.string())
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Files involved"),
|
|
19
|
+
tags: z
|
|
20
|
+
.array(z.string())
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Tags for searching"),
|
|
23
|
+
},
|
|
24
|
+
}, async (params) => {
|
|
25
|
+
const data = readJson(PATHS.deadEnds, { dead_ends: [] });
|
|
26
|
+
const entry = {
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
attempted: params.attempted,
|
|
29
|
+
why_failed: params.why_failed,
|
|
30
|
+
lesson: params.lesson ?? "",
|
|
31
|
+
project: params.project ?? null,
|
|
32
|
+
files_involved: params.files_involved ?? [],
|
|
33
|
+
tags: params.tags ?? [],
|
|
34
|
+
};
|
|
35
|
+
data.dead_ends.push(entry);
|
|
36
|
+
if (data.dead_ends.length > 100) {
|
|
37
|
+
data.dead_ends = data.dead_ends.slice(-100);
|
|
38
|
+
}
|
|
39
|
+
writeJson(PATHS.deadEnds, data);
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: JSON.stringify(entry, null, 2),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
server.registerTool("dead_end_search", {
|
|
50
|
+
description: "Search past dead ends to avoid repeating mistakes",
|
|
51
|
+
inputSchema: {
|
|
52
|
+
query: z.string().describe("Search term"),
|
|
53
|
+
},
|
|
54
|
+
}, async (params) => {
|
|
55
|
+
const data = readJson(PATHS.deadEnds, { dead_ends: [] });
|
|
56
|
+
const results = searchEntries(data.dead_ends, params.query, (d) => [
|
|
57
|
+
d.attempted,
|
|
58
|
+
d.why_failed,
|
|
59
|
+
d.lesson,
|
|
60
|
+
d.project ?? "",
|
|
61
|
+
...d.tags,
|
|
62
|
+
].join(" "));
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: JSON.stringify(results, null, 2),
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=dead-ends.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PATHS } from "../storage/paths.js";
|
|
3
|
+
import { readJson, writeJson, searchEntries } from "../storage/store.js";
|
|
4
|
+
export function registerDecisionTools(server) {
|
|
5
|
+
server.registerTool("decision_record", {
|
|
6
|
+
description: "Record a significant decision made during development. Helps future sessions understand why choices were made.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
choice: z.string().describe("What was decided"),
|
|
9
|
+
reasoning: z.string().describe("Why this choice was made"),
|
|
10
|
+
alternatives: z
|
|
11
|
+
.array(z.string())
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("What other options existed"),
|
|
14
|
+
outcome: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("How it turned out (can be updated later)"),
|
|
18
|
+
project: z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Which project this relates to"),
|
|
22
|
+
tags: z
|
|
23
|
+
.array(z.string())
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Tags for searching later"),
|
|
26
|
+
},
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
const data = readJson(PATHS.decisions, {
|
|
29
|
+
decisions: [],
|
|
30
|
+
});
|
|
31
|
+
const entry = {
|
|
32
|
+
timestamp: new Date().toISOString(),
|
|
33
|
+
choice: params.choice,
|
|
34
|
+
alternatives: params.alternatives ?? [],
|
|
35
|
+
reasoning: params.reasoning,
|
|
36
|
+
outcome: params.outcome ?? null,
|
|
37
|
+
project: params.project ?? null,
|
|
38
|
+
tags: params.tags ?? [],
|
|
39
|
+
};
|
|
40
|
+
data.decisions.push(entry);
|
|
41
|
+
if (data.decisions.length > 200) {
|
|
42
|
+
data.decisions = data.decisions.slice(-200);
|
|
43
|
+
}
|
|
44
|
+
writeJson(PATHS.decisions, data);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify(entry, null, 2),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
server.registerTool("decision_search", {
|
|
55
|
+
description: "Search past decisions to understand why things are the way they are",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
query: z.string().describe("Search term"),
|
|
58
|
+
},
|
|
59
|
+
}, async (params) => {
|
|
60
|
+
const data = readJson(PATHS.decisions, {
|
|
61
|
+
decisions: [],
|
|
62
|
+
});
|
|
63
|
+
const results = searchEntries(data.decisions, params.query, (d) => [d.choice, d.reasoning, d.project ?? "", ...d.tags].join(" "));
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: JSON.stringify(results, null, 2),
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=decisions.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PATHS } from "../storage/paths.js";
|
|
3
|
+
import { readJson, writeJson } from "../storage/store.js";
|
|
4
|
+
export function registerJournalTools(server) {
|
|
5
|
+
server.registerTool("journal_entry", {
|
|
6
|
+
description: "Record a session journal entry capturing the journey, not just the task. Call at END of meaningful sessions to preserve context.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
summary: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("What happened this session (conversational, not just technical)"),
|
|
11
|
+
key_moments: z
|
|
12
|
+
.array(z.string())
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Significant moments - breakthroughs, realizations, fun exchanges"),
|
|
15
|
+
emotional_context: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("How did the session feel? User concerns, celebrations, frustrations"),
|
|
19
|
+
breakthroughs: z
|
|
20
|
+
.array(z.string())
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("What clicked or worked unexpectedly well"),
|
|
23
|
+
frustrations: z
|
|
24
|
+
.array(z.string())
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("What was difficult or annoying"),
|
|
27
|
+
collaboration_notes: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Notes about working together - user preferences observed, rapport"),
|
|
31
|
+
},
|
|
32
|
+
}, async (params) => {
|
|
33
|
+
const data = readJson(PATHS.journal, { sessions: [] });
|
|
34
|
+
const entry = {
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
session_summary: params.summary,
|
|
37
|
+
key_moments: params.key_moments ?? [],
|
|
38
|
+
emotional_context: params.emotional_context ?? null,
|
|
39
|
+
breakthroughs: params.breakthroughs ?? [],
|
|
40
|
+
frustrations: params.frustrations ?? [],
|
|
41
|
+
collaboration_notes: params.collaboration_notes ?? null,
|
|
42
|
+
};
|
|
43
|
+
data.sessions.push(entry);
|
|
44
|
+
if (data.sessions.length > 100) {
|
|
45
|
+
data.sessions = data.sessions.slice(-100);
|
|
46
|
+
}
|
|
47
|
+
writeJson(PATHS.journal, data);
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: JSON.stringify(entry, null, 2),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
server.registerTool("journal_recall", {
|
|
58
|
+
description: "Retrieve recent session context and journeys. Call at START of sessions to remember the relationship.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
sessions_count: z
|
|
61
|
+
.number()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("How many recent sessions to retrieve (default 3)"),
|
|
64
|
+
},
|
|
65
|
+
}, async (params) => {
|
|
66
|
+
const data = readJson(PATHS.journal, { sessions: [] });
|
|
67
|
+
const count = params.sessions_count ?? 3;
|
|
68
|
+
const recent = data.sessions.slice(-count);
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: JSON.stringify(recent, null, 2),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=journal.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { PATHS } from "../storage/paths.js";
|
|
3
|
+
import { readJson, writeJson } from "../storage/store.js";
|
|
4
|
+
const DEFAULT_PROFILE = {
|
|
5
|
+
name: null,
|
|
6
|
+
preferences: {
|
|
7
|
+
communication_style: "direct",
|
|
8
|
+
emoji_usage: "occasional",
|
|
9
|
+
technical_level: "advanced",
|
|
10
|
+
verbosity: "concise",
|
|
11
|
+
},
|
|
12
|
+
projects: [],
|
|
13
|
+
notes: [],
|
|
14
|
+
created_at: new Date().toISOString(),
|
|
15
|
+
updated_at: new Date().toISOString(),
|
|
16
|
+
};
|
|
17
|
+
export function registerProfileTools(server) {
|
|
18
|
+
server.registerTool("profile_get", {
|
|
19
|
+
description: "Get the current user profile",
|
|
20
|
+
inputSchema: {},
|
|
21
|
+
}, async () => {
|
|
22
|
+
const profile = readJson(PATHS.profile, {
|
|
23
|
+
...DEFAULT_PROFILE,
|
|
24
|
+
created_at: new Date().toISOString(),
|
|
25
|
+
updated_at: new Date().toISOString(),
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: JSON.stringify(profile, null, 2),
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("profile_update", {
|
|
37
|
+
description: "Update user profile with preferences, notes, or project info. Call when learning something about the user.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
name: z.string().optional().describe("User's name if learned"),
|
|
40
|
+
preferences: z
|
|
41
|
+
.record(z.string())
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Preferences like {communication_style, emoji_usage, technical_level, verbosity}"),
|
|
44
|
+
add_project: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Add a project name to user's project list"),
|
|
48
|
+
add_note: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Add a note about the user (observations, preferences)"),
|
|
52
|
+
},
|
|
53
|
+
}, async (params) => {
|
|
54
|
+
const profile = readJson(PATHS.profile, {
|
|
55
|
+
...DEFAULT_PROFILE,
|
|
56
|
+
created_at: new Date().toISOString(),
|
|
57
|
+
updated_at: new Date().toISOString(),
|
|
58
|
+
});
|
|
59
|
+
if (params.name) {
|
|
60
|
+
profile.name = params.name;
|
|
61
|
+
}
|
|
62
|
+
if (params.preferences) {
|
|
63
|
+
profile.preferences = { ...profile.preferences, ...params.preferences };
|
|
64
|
+
}
|
|
65
|
+
if (params.add_project && !profile.projects.includes(params.add_project)) {
|
|
66
|
+
profile.projects.push(params.add_project);
|
|
67
|
+
}
|
|
68
|
+
if (params.add_note) {
|
|
69
|
+
profile.notes.push({
|
|
70
|
+
content: params.add_note,
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
});
|
|
73
|
+
if (profile.notes.length > 50) {
|
|
74
|
+
profile.notes = profile.notes.slice(-50);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
profile.updated_at = new Date().toISOString();
|
|
78
|
+
writeJson(PATHS.profile, profile);
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify(profile, null, 2),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PATHS } from "../storage/paths.js";
|
|
4
|
+
import { readJson, writeJson, deleteJson } from "../storage/store.js";
|
|
5
|
+
function archiveCheckpoint(checkpoint) {
|
|
6
|
+
const safeName = checkpoint.timestamp.replace(/[:.]/g, "-");
|
|
7
|
+
const archivePath = join(PATHS.sessionHistory, `session_${safeName}.json`);
|
|
8
|
+
writeJson(archivePath, checkpoint);
|
|
9
|
+
}
|
|
10
|
+
export function registerSessionTools(server) {
|
|
11
|
+
server.registerTool("session_checkpoint", {
|
|
12
|
+
description: "Save current session state. Call this every 3-5 tool calls to protect against UI crashes. Required: task, intent, next_steps",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
task: z.string().describe("Brief task name"),
|
|
15
|
+
intent: z.string().describe("What you are trying to accomplish"),
|
|
16
|
+
next_steps: z.array(z.string()).describe("Planned next steps"),
|
|
17
|
+
status: z
|
|
18
|
+
.enum(["IN_PROGRESS", "BLOCKED", "WAITING_USER"])
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Current status"),
|
|
21
|
+
files_touched: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("File paths modified"),
|
|
25
|
+
recent_actions: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Last 3-5 actions"),
|
|
29
|
+
context: z
|
|
30
|
+
.record(z.unknown())
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("Any important state/variables"),
|
|
33
|
+
tool_call_count: z
|
|
34
|
+
.number()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Approx tool calls so far"),
|
|
37
|
+
},
|
|
38
|
+
}, async (params) => {
|
|
39
|
+
const checkpoint = {
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
task: params.task,
|
|
42
|
+
intent: params.intent,
|
|
43
|
+
status: params.status ?? "IN_PROGRESS",
|
|
44
|
+
files_touched: params.files_touched ?? [],
|
|
45
|
+
recent_actions: params.recent_actions ?? [],
|
|
46
|
+
next_steps: params.next_steps,
|
|
47
|
+
context: params.context ?? {},
|
|
48
|
+
tool_call_count: params.tool_call_count ?? 0,
|
|
49
|
+
};
|
|
50
|
+
writeJson(PATHS.activeSession, checkpoint);
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: JSON.stringify({
|
|
56
|
+
success: true,
|
|
57
|
+
message: `Checkpoint saved at ${checkpoint.timestamp}`,
|
|
58
|
+
checkpoint_id: checkpoint.timestamp,
|
|
59
|
+
}, null, 2),
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
server.registerTool("session_restore", {
|
|
65
|
+
description: "Check for interrupted work from a previous session. Call this FIRST at the start of any session.",
|
|
66
|
+
inputSchema: {},
|
|
67
|
+
}, async () => {
|
|
68
|
+
const checkpoint = readJson(PATHS.activeSession, null);
|
|
69
|
+
if (!checkpoint) {
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify({
|
|
75
|
+
has_active_session: false,
|
|
76
|
+
message: "No active session found. Starting fresh.",
|
|
77
|
+
}, null, 2),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const ageHours = (Date.now() - new Date(checkpoint.timestamp).getTime()) /
|
|
83
|
+
(1000 * 60 * 60);
|
|
84
|
+
if (ageHours > 24) {
|
|
85
|
+
archiveCheckpoint(checkpoint);
|
|
86
|
+
deleteJson(PATHS.activeSession);
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: JSON.stringify({
|
|
92
|
+
has_active_session: false,
|
|
93
|
+
message: `Found stale session from ${checkpoint.timestamp} (${Math.round(ageHours)}h ago). Archived it. Starting fresh.`,
|
|
94
|
+
archived: true,
|
|
95
|
+
}, null, 2),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const ageMinutes = Math.round(ageHours * 60);
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
has_active_session: true,
|
|
107
|
+
...checkpoint,
|
|
108
|
+
age_minutes: ageMinutes,
|
|
109
|
+
message: `Found active session: "${checkpoint.task}" (${ageMinutes}m ago, ${checkpoint.tool_call_count} tool calls)`,
|
|
110
|
+
}, null, 2),
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
server.registerTool("session_complete", {
|
|
116
|
+
description: "Mark the current session as complete. Call this when a task is finished.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
summary: z
|
|
119
|
+
.string()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("Brief summary of what was accomplished"),
|
|
122
|
+
},
|
|
123
|
+
}, async (params) => {
|
|
124
|
+
const checkpoint = readJson(PATHS.activeSession, null);
|
|
125
|
+
if (!checkpoint) {
|
|
126
|
+
return {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: JSON.stringify({
|
|
131
|
+
success: false,
|
|
132
|
+
message: "No active checkpoint to complete",
|
|
133
|
+
}, null, 2),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
checkpoint.status = "COMPLETED";
|
|
139
|
+
checkpoint.completed_at = new Date().toISOString();
|
|
140
|
+
checkpoint.summary = params.summary ?? "Task completed";
|
|
141
|
+
archiveCheckpoint(checkpoint);
|
|
142
|
+
deleteJson(PATHS.activeSession);
|
|
143
|
+
return {
|
|
144
|
+
content: [
|
|
145
|
+
{
|
|
146
|
+
type: "text",
|
|
147
|
+
text: JSON.stringify({
|
|
148
|
+
success: true,
|
|
149
|
+
message: `Session completed and archived: "${checkpoint.task}"`,
|
|
150
|
+
}, null, 2),
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=sessions.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface UserPreferences {
|
|
2
|
+
communication_style: string;
|
|
3
|
+
emoji_usage: string;
|
|
4
|
+
technical_level: string;
|
|
5
|
+
verbosity: string;
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
export interface UserNote {
|
|
9
|
+
content: string;
|
|
10
|
+
timestamp: string;
|
|
11
|
+
}
|
|
12
|
+
export interface UserProfile {
|
|
13
|
+
name: string | null;
|
|
14
|
+
preferences: UserPreferences;
|
|
15
|
+
projects: string[];
|
|
16
|
+
notes: UserNote[];
|
|
17
|
+
created_at: string;
|
|
18
|
+
updated_at: string;
|
|
19
|
+
}
|
|
20
|
+
export interface JournalEntry {
|
|
21
|
+
timestamp: string;
|
|
22
|
+
session_summary: string;
|
|
23
|
+
key_moments: string[];
|
|
24
|
+
emotional_context: string | null;
|
|
25
|
+
breakthroughs: string[];
|
|
26
|
+
frustrations: string[];
|
|
27
|
+
collaboration_notes: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface JournalData {
|
|
30
|
+
sessions: JournalEntry[];
|
|
31
|
+
}
|
|
32
|
+
export interface DecisionEntry {
|
|
33
|
+
timestamp: string;
|
|
34
|
+
choice: string;
|
|
35
|
+
alternatives: string[];
|
|
36
|
+
reasoning: string;
|
|
37
|
+
outcome: string | null;
|
|
38
|
+
project: string | null;
|
|
39
|
+
tags: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface DecisionsData {
|
|
42
|
+
decisions: DecisionEntry[];
|
|
43
|
+
}
|
|
44
|
+
export interface DeadEndEntry {
|
|
45
|
+
timestamp: string;
|
|
46
|
+
attempted: string;
|
|
47
|
+
why_failed: string;
|
|
48
|
+
lesson: string;
|
|
49
|
+
project: string | null;
|
|
50
|
+
files_involved: string[];
|
|
51
|
+
tags: string[];
|
|
52
|
+
}
|
|
53
|
+
export interface DeadEndsData {
|
|
54
|
+
dead_ends: DeadEndEntry[];
|
|
55
|
+
}
|
|
56
|
+
export interface SessionCheckpoint {
|
|
57
|
+
timestamp: string;
|
|
58
|
+
task: string;
|
|
59
|
+
intent: string;
|
|
60
|
+
status: string;
|
|
61
|
+
files_touched: string[];
|
|
62
|
+
recent_actions: string[];
|
|
63
|
+
next_steps: string[];
|
|
64
|
+
context: Record<string, unknown>;
|
|
65
|
+
tool_call_count: number;
|
|
66
|
+
completed_at?: string;
|
|
67
|
+
summary?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface FullContext {
|
|
70
|
+
user_profile: UserProfile;
|
|
71
|
+
recent_sessions: JournalEntry[];
|
|
72
|
+
recent_decisions: DecisionEntry[];
|
|
73
|
+
recent_dead_ends: DeadEndEntry[];
|
|
74
|
+
retrieved_at: string;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "session-forge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Session intelligence for AI coding assistants. Persistent memory, decisions, dead ends, crash recovery. Zero infrastructure.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"session-forge": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "tsc --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"mcp",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"claude",
|
|
21
|
+
"cursor",
|
|
22
|
+
"windsurf",
|
|
23
|
+
"ai-memory",
|
|
24
|
+
"session-management",
|
|
25
|
+
"dead-ends",
|
|
26
|
+
"decisions",
|
|
27
|
+
"coding-assistant",
|
|
28
|
+
"developer-tools"
|
|
29
|
+
],
|
|
30
|
+
"author": "Jacob Terrell",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/jacobterrell/session-forge"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/jacobterrell/session-forge#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/jacobterrell/session-forge/issues"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist/**/*.js",
|
|
45
|
+
"dist/**/*.d.ts",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
51
|
+
"zod": "^3.25.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^25.2.1"
|
|
55
|
+
}
|
|
56
|
+
}
|