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 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
+ }