summd 0.1.2
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 +51 -0
- package/dist/api.js +58 -0
- package/dist/config.js +28 -0
- package/dist/index.js +140 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# summd
|
|
2
|
+
|
|
3
|
+
CLI for [sum.md](https://sum.md) — Sum to anything.
|
|
4
|
+
|
|
5
|
+
Auto-summarize what you save. Surface it as context — for humans, AI agents, or any tool in your workflow.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g summd
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
summd key sk-summd-xxxx # generate at sum.md/settings
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
summd add "Today I learned..."
|
|
23
|
+
cat notes.md | summd add --tag ai
|
|
24
|
+
summd search "React performance"
|
|
25
|
+
summd list --since 7d --tag ai
|
|
26
|
+
summd content <id> | pbcopy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## AI Agent (MCP)
|
|
30
|
+
|
|
31
|
+
Claude and other MCP-compatible agents can search your knowledge base mid-conversation:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"sum.md": {
|
|
37
|
+
"type": "http",
|
|
38
|
+
"url": "https://sum.md/mcp",
|
|
39
|
+
"headers": { "Authorization": "Bearer sk-summd-xxxx" }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Options
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
--key <token> API key
|
|
49
|
+
--base <url> Override base URL
|
|
50
|
+
--json Raw JSON output
|
|
51
|
+
```
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { resolveKey, resolveBase } from './config.js';
|
|
2
|
+
let _id = 0;
|
|
3
|
+
async function mcp(method, params, opts) {
|
|
4
|
+
const key = resolveKey(opts.key);
|
|
5
|
+
const base = resolveBase(opts.base);
|
|
6
|
+
const res = await fetch(`${base}/mcp`, {
|
|
7
|
+
method: 'POST',
|
|
8
|
+
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
|
|
9
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: ++_id, method, params }),
|
|
10
|
+
});
|
|
11
|
+
if (res.status === 401) {
|
|
12
|
+
console.error('Unauthorized — check your API key.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
throw new Error(`HTTP ${res.status}`);
|
|
17
|
+
const json = await res.json();
|
|
18
|
+
if (json.error)
|
|
19
|
+
throw new Error(json.error.message);
|
|
20
|
+
return json.result;
|
|
21
|
+
}
|
|
22
|
+
function tool(name, args, opts) {
|
|
23
|
+
return mcp('tools/call', { name, arguments: args }, opts);
|
|
24
|
+
}
|
|
25
|
+
function text(result) {
|
|
26
|
+
return result.content.map(c => c.text).join('');
|
|
27
|
+
}
|
|
28
|
+
// ── Public API ────────────────────────────────────────────────────────────────
|
|
29
|
+
export async function add(content, tags, opts) {
|
|
30
|
+
return text(await tool('add_entry', { content, tags }, opts));
|
|
31
|
+
}
|
|
32
|
+
export async function search(query, limit, opts) {
|
|
33
|
+
return text(await tool('search_context', { query, limit }, opts));
|
|
34
|
+
}
|
|
35
|
+
export async function list(args, opts) {
|
|
36
|
+
return text(await tool('list_recent', args, opts));
|
|
37
|
+
}
|
|
38
|
+
export async function get(id, opts) {
|
|
39
|
+
const raw = text(await tool('get_entry', { id }, opts));
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
throw new Error(raw);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function tags(opts) {
|
|
48
|
+
const raw = text(await tool('list_tags', {}, opts));
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
throw new Error(raw);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function del(id, opts) {
|
|
57
|
+
return text(await tool('delete_entry', { id }, opts));
|
|
58
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
4
|
+
const CONFIG_DIR = join(homedir(), '.config', 'summd');
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
6
|
+
function load() {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function saveKey(apiKey) {
|
|
15
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
16
|
+
writeFileSync(CONFIG_FILE, JSON.stringify({ ...load(), apiKey }, null, 2));
|
|
17
|
+
}
|
|
18
|
+
export function resolveKey(inline) {
|
|
19
|
+
const key = inline ?? process.env.SUM_API_KEY ?? load().apiKey;
|
|
20
|
+
if (!key) {
|
|
21
|
+
console.error('No API key. Run: summd key <your-api-key>');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
return key;
|
|
25
|
+
}
|
|
26
|
+
export function resolveBase(inline) {
|
|
27
|
+
return inline ?? process.env.SUM_BASE_URL ?? load().baseUrl ?? 'https://sum.md';
|
|
28
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { createInterface } from 'readline';
|
|
4
|
+
import { program } from 'commander';
|
|
5
|
+
import { saveKey } from './config.js';
|
|
6
|
+
import * as api from './api.js';
|
|
7
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
8
|
+
function globalOpts() {
|
|
9
|
+
return program.opts();
|
|
10
|
+
}
|
|
11
|
+
function sinceToIso(since) {
|
|
12
|
+
if (!since)
|
|
13
|
+
return undefined;
|
|
14
|
+
const match = since.match(/^(\d+)(d|h|m)$/);
|
|
15
|
+
if (!match)
|
|
16
|
+
return since; // treat as raw ISO date
|
|
17
|
+
const [, n, unit] = match;
|
|
18
|
+
const ms = { d: 86400000, h: 3600000, m: 60000 }[unit];
|
|
19
|
+
return new Date(Date.now() - Number(n) * ms).toISOString();
|
|
20
|
+
}
|
|
21
|
+
async function readStdin() {
|
|
22
|
+
const rl = createInterface({ input: process.stdin });
|
|
23
|
+
const lines = [];
|
|
24
|
+
for await (const line of rl)
|
|
25
|
+
lines.push(line);
|
|
26
|
+
return lines.join('\n');
|
|
27
|
+
}
|
|
28
|
+
function formatDate(iso) {
|
|
29
|
+
return new Date(iso).toLocaleDateString('en', { month: 'short', day: 'numeric', year: 'numeric' });
|
|
30
|
+
}
|
|
31
|
+
// ── program ───────────────────────────────────────────────────────────────────
|
|
32
|
+
program
|
|
33
|
+
.name('summd')
|
|
34
|
+
.description('Sum to anything.')
|
|
35
|
+
.version('0.1.2')
|
|
36
|
+
.option('--key <token>', 'API key for this request')
|
|
37
|
+
.option('--base <url>', 'Override API base URL');
|
|
38
|
+
// ── summd key ─────────────────────────────────────────────────────────────────
|
|
39
|
+
program
|
|
40
|
+
.command('key <api-key>')
|
|
41
|
+
.description('Save API key to ~/.config/summd/config.json')
|
|
42
|
+
.action((apiKey) => {
|
|
43
|
+
saveKey(apiKey);
|
|
44
|
+
console.log('✓ API key saved');
|
|
45
|
+
});
|
|
46
|
+
// ── summd add ─────────────────────────────────────────────────────────────────
|
|
47
|
+
program
|
|
48
|
+
.command('add [content]')
|
|
49
|
+
.description('Add an entry — reads stdin if no content given')
|
|
50
|
+
.option('-t, --tag <tags>', 'Comma-separated tags')
|
|
51
|
+
.option('-f, --file <path>', 'Read content from file')
|
|
52
|
+
.action(async (content, cmd) => {
|
|
53
|
+
const tags = cmd.tag ? cmd.tag.split(',').map(t => t.trim()) : [];
|
|
54
|
+
let body = content;
|
|
55
|
+
if (!body && cmd.file)
|
|
56
|
+
body = readFileSync(cmd.file, 'utf8');
|
|
57
|
+
if (!body && !process.stdin.isTTY)
|
|
58
|
+
body = await readStdin();
|
|
59
|
+
if (!body) {
|
|
60
|
+
console.error('Provide content, --file, or pipe from stdin.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
console.log(await api.add(body, tags, globalOpts()));
|
|
64
|
+
});
|
|
65
|
+
// ── summd search ──────────────────────────────────────────────────────────────
|
|
66
|
+
program
|
|
67
|
+
.command('search <query>')
|
|
68
|
+
.description('Semantic search your knowledge base')
|
|
69
|
+
.option('-n, --limit <n>', 'Number of results', '5')
|
|
70
|
+
.action(async (query, cmd) => {
|
|
71
|
+
console.log(await api.search(query, Number(cmd.limit), globalOpts()));
|
|
72
|
+
});
|
|
73
|
+
// ── summd list ────────────────────────────────────────────────────────────────
|
|
74
|
+
program
|
|
75
|
+
.command('list')
|
|
76
|
+
.description('List recent entries')
|
|
77
|
+
.option('-n, --limit <n>', 'Number of entries', '10')
|
|
78
|
+
.option('--tag <tag>', 'Filter by tag')
|
|
79
|
+
.option('--since <value>', 'e.g. 7d, 24h, 30m — or ISO date')
|
|
80
|
+
.option('--json', 'Raw JSON output')
|
|
81
|
+
.action(async (cmd) => {
|
|
82
|
+
console.log(await api.list({ limit: Number(cmd.limit), tag: cmd.tag, since: sinceToIso(cmd.since) }, globalOpts()));
|
|
83
|
+
});
|
|
84
|
+
// ── summd get ─────────────────────────────────────────────────────────────────
|
|
85
|
+
program
|
|
86
|
+
.command('get <id>')
|
|
87
|
+
.description('Get full entry metadata and content')
|
|
88
|
+
.option('--json', 'Raw JSON output')
|
|
89
|
+
.action(async (id, cmd) => {
|
|
90
|
+
const entry = await api.get(id, globalOpts());
|
|
91
|
+
if (cmd.json) {
|
|
92
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
console.log(`id: ${entry.id}`);
|
|
96
|
+
console.log(`title: ${entry.title ?? '—'}`);
|
|
97
|
+
console.log(`status: ${entry.summary_status}`);
|
|
98
|
+
console.log(`tags: ${entry.tags.length ? entry.tags.map(t => `#${t}`).join(' ') : '—'}`);
|
|
99
|
+
console.log(`created: ${formatDate(entry.created_at)}`);
|
|
100
|
+
if (entry.summary)
|
|
101
|
+
console.log(`\nsummary: ${entry.summary}`);
|
|
102
|
+
if (entry.raw_md)
|
|
103
|
+
console.log(`\n---\n\n${entry.raw_md}`);
|
|
104
|
+
});
|
|
105
|
+
// ── summd content ─────────────────────────────────────────────────────────────
|
|
106
|
+
program
|
|
107
|
+
.command('content <id>')
|
|
108
|
+
.description('Print raw markdown — pipe-friendly')
|
|
109
|
+
.action(async (id) => {
|
|
110
|
+
const entry = await api.get(id, globalOpts());
|
|
111
|
+
if (!entry.raw_md) {
|
|
112
|
+
console.error('No content available.');
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
process.stdout.write(entry.raw_md);
|
|
116
|
+
});
|
|
117
|
+
// ── summd tags ────────────────────────────────────────────────────────────────
|
|
118
|
+
program
|
|
119
|
+
.command('tags')
|
|
120
|
+
.description('List all tags with entry counts')
|
|
121
|
+
.option('--json', 'Raw JSON output')
|
|
122
|
+
.action(async (cmd) => {
|
|
123
|
+
const result = await api.tags(globalOpts());
|
|
124
|
+
if (cmd.json) {
|
|
125
|
+
console.log(JSON.stringify(result, null, 2));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
for (const { tag, count } of result) {
|
|
129
|
+
console.log(`#${tag.padEnd(24)} ${count}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
// ── summd delete ──────────────────────────────────────────────────────────────
|
|
133
|
+
program
|
|
134
|
+
.command('delete <id>')
|
|
135
|
+
.alias('rm')
|
|
136
|
+
.description('Delete an entry')
|
|
137
|
+
.action(async (id) => {
|
|
138
|
+
console.log(await api.del(id, globalOpts()));
|
|
139
|
+
});
|
|
140
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "summd",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "CLI for sum.md — Sum to anything.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": { "summd": "./dist/index.js" },
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": ["dist", "README.md"],
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"commander": "^12.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"typescript": "^5.0.0"
|
|
20
|
+
},
|
|
21
|
+
"engines": { "node": ">=18" }
|
|
22
|
+
}
|