incremnt 0.1.13 → 0.1.16
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 +11 -9
- package/package.json +3 -2
- package/src/contract.js +25 -0
- package/src/lib.js +57 -1
- package/src/mcp.js +57 -30
- package/src/openrouter.js +206 -119
- package/src/queries.js +293 -10
- package/src/remote.js +39 -1
- package/src/sync-service.js +259 -18
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line tool and MCP server for querying your [incremnt](https://incremnt.app) strength training data.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Setup
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g incremnt
|
|
9
|
+
incremnt login
|
|
10
|
+
incremnt mcp install # registers with Claude Desktop and Claude Code
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
This gives you two commands: `incremnt` (CLI) and `incremnt-mcp` (MCP server).
|
|
13
|
+
This gives you two commands: `incremnt` (CLI) and `incremnt-mcp` (MCP server). The `mcp install` step auto-registers the MCP server — restart Claude for it to take effect.
|
|
12
14
|
|
|
13
15
|
## CLI
|
|
14
16
|
|
|
@@ -44,6 +46,8 @@ If `--input` is omitted, the CLI checks `INCREMNT_SNAPSHOT`, then `ONEMORE_SNAPS
|
|
|
44
46
|
| `sessions show --id <id>` | Details for a single session |
|
|
45
47
|
| `programs current` | Active program state |
|
|
46
48
|
| `programs list` | All programs |
|
|
49
|
+
| `cycles list [--program-id <id>]` | Completed cycle summaries |
|
|
50
|
+
| `cycles show --id <id>` | Details for a completed cycle summary |
|
|
47
51
|
| `exercises history --name <name>` | Set-by-set history for an exercise |
|
|
48
52
|
| `records` | Personal records (best e1RM per exercise) |
|
|
49
53
|
| `login` | Authenticate with the hosted sync service |
|
|
@@ -58,6 +62,7 @@ If `--input` is omitted, the CLI checks `INCREMNT_SNAPSHOT`, then `ONEMORE_SNAPS
|
|
|
58
62
|
| `--pretty` | Human-readable formatted output (default is JSON) |
|
|
59
63
|
| `--input <path>` | Path to a local `.onemore.json` snapshot |
|
|
60
64
|
| `--limit <n>` | Limit number of results (for `sessions list`) |
|
|
65
|
+
| `--program-id <id>` | Filter cycle summaries to a program |
|
|
61
66
|
|
|
62
67
|
### Exercise matching
|
|
63
68
|
|
|
@@ -65,26 +70,23 @@ If `--input` is omitted, the CLI checks `INCREMNT_SNAPSHOT`, then `ONEMORE_SNAPS
|
|
|
65
70
|
|
|
66
71
|
## MCP Server
|
|
67
72
|
|
|
68
|
-
The package includes an [MCP](https://modelcontextprotocol.io) server that exposes the same queries as tools for AI assistants like Claude.
|
|
73
|
+
The package includes an [MCP](https://modelcontextprotocol.io) server that exposes the same read queries and program proposal commands as tools for AI assistants like Claude.
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
Run `incremnt mcp install` to auto-register the server with Claude Desktop and Claude Code (see [Setup](#setup) above).
|
|
71
76
|
|
|
72
|
-
|
|
77
|
+
To register manually instead, add to your Claude Code project config (`.mcp.json`):
|
|
73
78
|
|
|
74
79
|
```json
|
|
75
80
|
{
|
|
76
81
|
"mcpServers": {
|
|
77
82
|
"incremnt": {
|
|
78
83
|
"type": "stdio",
|
|
79
|
-
"command": "
|
|
80
|
-
"args": ["-y", "incremnt-mcp"]
|
|
84
|
+
"command": "incremnt-mcp"
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
```
|
|
85
89
|
|
|
86
|
-
Or run directly: `npx incremnt-mcp`
|
|
87
|
-
|
|
88
90
|
The MCP server uses the same auth session as the CLI — run `incremnt login` first.
|
|
89
91
|
|
|
90
92
|
## License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "incremnt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Command-line tool for querying your incremnt strength training data",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
15
|
"test": "node --test",
|
|
16
|
-
"dev:sync-fixture": "node ./scripts/dev-sync-fixture-server.js"
|
|
16
|
+
"dev:sync-fixture": "node ./scripts/dev-sync-fixture-server.js",
|
|
17
|
+
"postinstall": "node -e \"process.stdout.write('\\n incremnt MCP server installed.\\n Run: incremnt mcp install\\n to register it with Claude.\\n\\n')\""
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
19
20
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
package/src/contract.js
CHANGED
|
@@ -115,6 +115,31 @@ export const commandSchema = [
|
|
|
115
115
|
options: [
|
|
116
116
|
{ name: 'id', type: 'string', required: true, description: 'Cycle summary ID' }
|
|
117
117
|
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
command: 'health summary',
|
|
121
|
+
id: 'health-summary',
|
|
122
|
+
description: 'Health metrics summary (resting HR, HRV, VO2 Max, sleep, cardio)',
|
|
123
|
+
options: [
|
|
124
|
+
{ name: 'days', type: 'number', required: false, description: 'Last N days (default: 14)' }
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
command: 'ask history',
|
|
129
|
+
id: 'ask-history',
|
|
130
|
+
description: 'List past AI coach conversations',
|
|
131
|
+
options: [
|
|
132
|
+
{ name: 'limit', type: 'number', required: false, description: 'Max conversations to return (default: 20)' }
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
command: 'ask show',
|
|
137
|
+
id: 'ask-show',
|
|
138
|
+
description: 'Show a full AI coach conversation',
|
|
139
|
+
usage: 'ask show --id <conversation-id>',
|
|
140
|
+
options: [
|
|
141
|
+
{ name: 'id', type: 'string', required: true, description: 'Conversation ID' }
|
|
142
|
+
]
|
|
118
143
|
}
|
|
119
144
|
];
|
|
120
145
|
|
package/src/lib.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
-
import
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { spawn, execFileSync } from 'node:child_process';
|
|
3
5
|
import { capabilities, commandSchema, contractVersion, officialCommands, proposalSchema, readCommands, writeCommands, writeCommandSchema } from './contract.js';
|
|
4
6
|
import {
|
|
5
7
|
bootstrapSessionFromRemoteBaseUrl,
|
|
@@ -139,6 +141,40 @@ export async function runCli(argv, stdout, stderr) {
|
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
if (normalizedCommand === 'mcp install') {
|
|
145
|
+
try {
|
|
146
|
+
const mcpBin = resolveMcpBin();
|
|
147
|
+
const installed = [];
|
|
148
|
+
|
|
149
|
+
// Claude Desktop
|
|
150
|
+
const desktopPath = claudeDesktopConfigPath();
|
|
151
|
+
let desktopConfig = {};
|
|
152
|
+
try {
|
|
153
|
+
desktopConfig = JSON.parse(await fs.readFile(desktopPath, 'utf8'));
|
|
154
|
+
} catch { /* file doesn't exist yet */ }
|
|
155
|
+
desktopConfig.mcpServers = { ...desktopConfig.mcpServers, incremnt: { command: mcpBin } };
|
|
156
|
+
await fs.mkdir(path.dirname(desktopPath), { recursive: true });
|
|
157
|
+
await fs.writeFile(desktopPath, JSON.stringify(desktopConfig, null, 2) + '\n');
|
|
158
|
+
installed.push(` Claude Desktop: ${desktopPath}`);
|
|
159
|
+
|
|
160
|
+
// Claude Code CLI
|
|
161
|
+
const cliPath = path.join(os.homedir(), '.claude.json');
|
|
162
|
+
let cliConfig = {};
|
|
163
|
+
try {
|
|
164
|
+
cliConfig = JSON.parse(await fs.readFile(cliPath, 'utf8'));
|
|
165
|
+
} catch { /* file doesn't exist yet */ }
|
|
166
|
+
cliConfig.mcpServers = { ...cliConfig.mcpServers, incremnt: { type: 'stdio', command: mcpBin } };
|
|
167
|
+
await fs.writeFile(cliPath, JSON.stringify(cliConfig, null, 2) + '\n');
|
|
168
|
+
installed.push(` Claude Code: ${cliPath}`);
|
|
169
|
+
|
|
170
|
+
stdout.write(`Registered incremnt MCP server in:\n${installed.join('\n')}\n\nRestart Claude for the changes to take effect.\n`);
|
|
171
|
+
return 0;
|
|
172
|
+
} catch (error) {
|
|
173
|
+
stderr.write(`${error.message}\n`);
|
|
174
|
+
return 1;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
142
178
|
if (normalizedCommand === 'login') {
|
|
143
179
|
if (options.snapshot) {
|
|
144
180
|
try {
|
|
@@ -380,6 +416,26 @@ function browserCommandForPlatform() {
|
|
|
380
416
|
return null;
|
|
381
417
|
}
|
|
382
418
|
|
|
419
|
+
function claudeDesktopConfigPath() {
|
|
420
|
+
if (process.platform === 'darwin') {
|
|
421
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
422
|
+
}
|
|
423
|
+
if (process.platform === 'win32') {
|
|
424
|
+
return path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
425
|
+
}
|
|
426
|
+
return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function resolveMcpBin() {
|
|
430
|
+
try {
|
|
431
|
+
return execFileSync('which', ['incremnt-mcp'], { encoding: 'utf8' }).trim();
|
|
432
|
+
} catch {
|
|
433
|
+
// fall back to sibling binary next to the current incremnt binary
|
|
434
|
+
const dir = path.dirname(process.argv[1]);
|
|
435
|
+
return path.join(dir, 'incremnt-mcp');
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
383
439
|
function configuredAuthProviders(auth) {
|
|
384
440
|
const providers = auth?.providers ?? {};
|
|
385
441
|
const labels = [];
|
package/src/mcp.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
3
5
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
7
|
import { z } from 'zod';
|
|
6
|
-
import { commandSchema } from './contract.js';
|
|
8
|
+
import { commandSchema, writeCommands, writeCommandSchema } from './contract.js';
|
|
7
9
|
import { readSessionState } from './state.js';
|
|
8
10
|
import { createTransport } from './transport.js';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
name: 'incremnt',
|
|
12
|
-
version: '0.1.0'
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
for (const cmd of commandSchema) {
|
|
12
|
+
function commandShape(cmd) {
|
|
16
13
|
const shape = {};
|
|
17
14
|
|
|
18
15
|
for (const opt of cmd.options) {
|
|
@@ -22,39 +19,69 @@ for (const cmd of commandSchema) {
|
|
|
22
19
|
shape[opt.name] = field;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
return shape;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function registerMcpTools(server, {
|
|
26
|
+
readSessionStateFn = readSessionState,
|
|
27
|
+
createTransportFn = createTransport
|
|
28
|
+
} = {}) {
|
|
29
|
+
for (const cmd of [...commandSchema, ...writeCommandSchema]) {
|
|
30
|
+
server.tool(cmd.id, cmd.description, commandShape(cmd), async (args) => {
|
|
31
|
+
try {
|
|
32
|
+
const sessionState = await readSessionStateFn();
|
|
33
|
+
const transport = await createTransportFn({}, sessionState);
|
|
34
|
+
|
|
35
|
+
if (transport.expired) {
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text', text: 'Session expired. Run `incremnt login` to re-authenticate.' }],
|
|
38
|
+
isError: true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result = writeCommands.has(cmd.id)
|
|
43
|
+
? await transport.executeWriteCommand(cmd.id, args)
|
|
44
|
+
: await transport.executeReadCommand(cmd.id, args);
|
|
29
45
|
|
|
30
|
-
if (transport.expired) {
|
|
31
46
|
return {
|
|
32
|
-
content: [{ type: 'text', text:
|
|
33
|
-
isError: true
|
|
47
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
|
|
34
48
|
};
|
|
35
|
-
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const message = error?.message ?? String(error);
|
|
36
51
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
if (error?.code === 'SNAPSHOT_NOT_FOUND') {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: 'Not logged in. Run `incremnt login` first.' }],
|
|
55
|
+
isError: true
|
|
56
|
+
};
|
|
57
|
+
}
|
|
43
58
|
|
|
44
|
-
if (error?.code === 'SNAPSHOT_NOT_FOUND') {
|
|
45
59
|
return {
|
|
46
|
-
content: [{ type: 'text', text:
|
|
60
|
+
content: [{ type: 'text', text: message }],
|
|
47
61
|
isError: true
|
|
48
62
|
};
|
|
49
63
|
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return server;
|
|
68
|
+
}
|
|
50
69
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
70
|
+
export function createMcpServer(deps) {
|
|
71
|
+
const server = new McpServer({
|
|
72
|
+
name: 'incremnt',
|
|
73
|
+
version: '0.1.0'
|
|
56
74
|
});
|
|
75
|
+
|
|
76
|
+
return registerMcpTools(server, deps);
|
|
57
77
|
}
|
|
58
78
|
|
|
59
|
-
|
|
60
|
-
|
|
79
|
+
async function main() {
|
|
80
|
+
const transport = new StdioServerTransport();
|
|
81
|
+
const server = createMcpServer();
|
|
82
|
+
await server.connect(transport);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
86
|
+
await main();
|
|
87
|
+
}
|