@mnemahq/cli 0.1.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.md ADDED
@@ -0,0 +1,83 @@
1
+ # Mnema Community License
2
+
3
+ _Version 1.0_
4
+
5
+ This Community License is based on the "Sustainable Use License" fair-code model.
6
+ It governs the **public core** of Mnema (the `mnema` repository). The enterprise
7
+ modules (`mnema-ee`) are covered by a separate proprietary license and are **not**
8
+ distributed under this license.
9
+
10
+ ## Acceptance
11
+
12
+ By using the software, you agree to all of the terms and conditions below.
13
+
14
+ ## Copyright License
15
+
16
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable,
17
+ non-transferable license to use, copy, distribute, make available, and prepare
18
+ derivative works of the software, in each case subject to the limitations and
19
+ conditions below.
20
+
21
+ ## Limitations
22
+
23
+ **Internal business purposes and personal use.** You may use the software for your
24
+ own internal business purposes or for personal use, and you may make the software
25
+ available to third parties **only** as part of a product or service you provide that
26
+ is not itself the software (or a modified version of it).
27
+
28
+ You may **not**, without a separate commercial agreement with the licensor:
29
+
30
+ 1. **Host or offer the software as a service** to third parties (commercial hosting,
31
+ SaaS, or a multi-tenant deployment operated for others).
32
+ 2. **Resell, sublicense, or redistribute the software as a product** whose primary
33
+ value is the software itself.
34
+ 3. **Embed the software** into a commercial product offered to third parties where
35
+ the software provides the core functionality.
36
+ 4. Remove or obscure any licensing, copyright, or other notices.
37
+
38
+ Using the software for **internal tooling, self-hosting for your own team or
39
+ organisation, evaluation, education, or development** is always permitted.
40
+
41
+ ## Trademarks
42
+
43
+ This license does not grant you any right to use the licensor's trademarks, trade
44
+ names, or logos (including "Mnema"). Forks may use the code; they may not use the name.
45
+
46
+ ## Patents
47
+
48
+ The licensor grants you a license, under any patent claims the licensor can license,
49
+ to make, use, sell, and import the software, limited to the same scope as the
50
+ copyright license above. This patent license terminates if you assert a patent claim
51
+ against anyone alleging the software infringes a patent.
52
+
53
+ ## Notices
54
+
55
+ You must ensure that anyone who gets a copy of any part of the software from you also
56
+ gets a copy of these terms, and you must retain all copyright and licensing notices.
57
+
58
+ ## No Other Rights
59
+
60
+ These terms do not imply any licenses other than those expressly granted here.
61
+
62
+ ## Termination
63
+
64
+ If you use the software in violation of these terms, such use is not licensed, and
65
+ your licenses will automatically terminate. If the licensor provides you notice of a
66
+ violation and you cease the violation within 30 days, your licenses are reinstated.
67
+
68
+ ## No Liability & As-Is
69
+
70
+ **As far as the law allows, the software comes "as is", without any warranty or
71
+ condition, and the licensor will not be liable to you for any damages arising out of
72
+ these terms or the use or nature of the software, under any kind of legal claim.**
73
+
74
+ ## Definitions
75
+
76
+ The **licensor** is the entity offering these terms. The **software** is the software
77
+ the licensor makes available under these terms, including any portion of it. **You**
78
+ refers to the individual or entity agreeing to these terms.
79
+
80
+ ---
81
+
82
+ For commercial hosting, embedding, or reselling rights — or for the enterprise
83
+ (`mnema-ee`) modules — contact the licensor for a commercial agreement.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @mnemahq/cli
2
+
3
+ Connect a repository to your [Mnema](https://mnema.theboringpeople.in) workspace: install
4
+ Claude Code session capture (real token cost, git, files touched), backfill past sessions, and
5
+ search your workspace — all from the terminal.
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ npm install -g @mnemahq/cli
11
+ mnema init
12
+ ```
13
+
14
+ The package is `@mnemahq/cli`; the command it installs is **`mnema`**. (The unscoped
15
+ name `mnema` is blocked on npm — its typosquat filter rejects it as too close to the
16
+ existing package `mem`.)
17
+
18
+ You'll be asked for your **workspace id** and a **hook token** (both in *Settings → Developer*), and
19
+ optionally an **API key** for search. `init` installs the capture hook, stores your secrets in the OS
20
+ keychain, and writes a `.mnema/config.json` (safe to commit — it holds no secrets).
21
+
22
+ Start a Claude Code session and it appears under **Sessions** with its cost.
23
+
24
+ ## Commands
25
+
26
+ | Command | What it does |
27
+ | --- | --- |
28
+ | `mnema init` | Link the repo, install session capture, store secrets |
29
+ | `mnema status` | Connection, hook, API reachability, last session |
30
+ | `mnema sessions` | Recent sessions for this repo (local + server) |
31
+ | `mnema sweep` | Backfill past local sessions (opt-in, no history backfill) |
32
+ | `mnema pull` | Export repo-bound docs into `.mnema/context/**.md` (committed, offline-readable) |
33
+ | `mnema search "q"` | Search your workspace |
34
+ | `mnema doctor` | Diagnose install, hooks, auth, connectivity |
35
+ | `mnema uninstall` | Remove hooks and stored secrets (`--purge` also deletes `.mnema/`) |
36
+
37
+ ## Secrets & privacy
38
+
39
+ Secrets are stored in the **OS keychain** (macOS Keychain, libsecret on Linux); if none is available
40
+ they fall back to a `0600` file under your config dir. A token is **never** written into the
41
+ committed `.mnema/config.json`. The capture hook sends metadata and file paths only — never file
42
+ contents. See the [privacy details](https://mnema.theboringpeople.in/docs/connect-agent).
43
+
44
+ `--origin <url>` (or `MNEMA_API_ORIGIN`) points the CLI at a self-hosted instance.
45
+
46
+ Requires Node 18+ and `git`.
47
+
48
+ ## `.mnema/` repo artifacts
49
+
50
+ `mnema init` scaffolds a committed `.mnema/` directory and `mnema pull` fills it:
51
+
52
+ ```
53
+ .mnema/
54
+ config.json workspace + repo binding (no secrets)
55
+ NOTABILITY.md editable prose describing what's worth capturing in this repo
56
+ context/**.md workspace docs bound to this repo — readable offline, diff in PRs
57
+ manifest.json sync bookkeeping (hashes only)
58
+ ```
59
+
60
+ **Sync is server-is-truth, but your local edits are never silently overwritten.** On
61
+ `mnema pull`: an untouched file is updated from the server; a file you edited while the
62
+ server didn't change is kept as-is; if both changed, the server version is written beside
63
+ yours as `*.remote.md` (gitignored) and the conflict is reported.
package/bin/mnema.mjs ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../src/cli.mjs';
3
+
4
+ run(process.argv.slice(2)).catch((err) => {
5
+ console.error(`\x1b[31mmnema: ${err?.message || err}\x1b[0m`);
6
+ process.exit(1);
7
+ });
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@mnemahq/cli",
3
+ "version": "0.1.0",
4
+ "description": "Mnema CLI — connect a repo to your Mnema workspace: install session capture, sweep past sessions, and search from the terminal.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mnema": "./bin/mnema.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "README.md",
13
+ "LICENSE.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "keywords": [
19
+ "mnema",
20
+ "cli",
21
+ "agent",
22
+ "claude-code",
23
+ "sessions"
24
+ ],
25
+ "license": "SEE LICENSE IN LICENSE",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "devDependencies": {
30
+ "vitest": "^3.2.7"
31
+ },
32
+ "scripts": {
33
+ "build": "node -e \"process.exit(0)\"",
34
+ "typecheck": "node --check bin/mnema.mjs && node --check src/cli.mjs && node --check src/util.mjs && node --check src/secrets.mjs",
35
+ "test": "vitest run"
36
+ }
37
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * .mnema/ repo artifacts (PR 5.4).
3
+ *
4
+ * `mnema pull` exports the workspace docs bound to this repo into
5
+ * `.mnema/context/**.md` — committed, human-readable, git-diffable, and readable
6
+ * by an agent with no network.
7
+ *
8
+ * Sync policy: SERVER IS TRUTH, but local edits are NEVER silently clobbered.
9
+ * - local file unchanged since last pull → overwrite with the server version
10
+ * - local edited, server unchanged → keep the local edit (local ahead)
11
+ * - both changed → CONFLICT: write the server version to
12
+ * `<file>.remote.md` (gitignored) and keep
13
+ * the local file; report it
14
+ * A manifest (`.mnema/manifest.json`, committed — hashes only, no content) records
15
+ * the server contentHash and the local hash at the last pull to detect all three.
16
+ */
17
+
18
+ import { createHash } from 'node:crypto';
19
+ import { join, dirname } from 'node:path';
20
+ import {
21
+ existsSync, readFileSync, writeFileSync, mkdirSync,
22
+ } from 'node:fs';
23
+
24
+ const MANIFEST_VERSION = 1;
25
+
26
+ function sha256(s) { return createHash('sha256').update(s, 'utf8').digest('hex'); }
27
+
28
+ /** Sanitize a doc path into a safe relative file path under context/ (no traversal). */
29
+ export function targetRelPath(docPath) {
30
+ const segments = String(docPath || 'untitled')
31
+ .split('/')
32
+ .map((seg) => seg.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, ''))
33
+ .filter((seg) => seg && seg !== '..' && seg !== '.');
34
+ const rel = segments.join('/') || 'untitled';
35
+ return `context/${rel}.md`.replace(/\.md\.md$/, '.md');
36
+ }
37
+
38
+ function mnemaDir(root) { return join(root, '.mnema'); }
39
+ function manifestPath(root) { return join(mnemaDir(root), 'manifest.json'); }
40
+
41
+ export function readManifest(root) {
42
+ try { return JSON.parse(readFileSync(manifestPath(root), 'utf8')); }
43
+ catch { return { version: MANIFEST_VERSION, files: {} }; }
44
+ }
45
+ function writeManifest(root, m) {
46
+ mkdirSync(mnemaDir(root), { recursive: true });
47
+ writeFileSync(manifestPath(root), JSON.stringify(m, null, 2) + '\n');
48
+ }
49
+
50
+ function renderFile(doc) {
51
+ const header = `<!-- Synced from Mnema · ${doc.path} · local edits are preserved and surfaced as conflicts on \`mnema pull\` -->\n\n`;
52
+ return header + (doc.markdown || '');
53
+ }
54
+
55
+ /**
56
+ * Applies a set of remote docs to .mnema/context, honouring the sync policy.
57
+ * Returns a summary { written, updated, kept, conflicts, upToDate, orphaned }.
58
+ */
59
+ export function applyContext(root, docs) {
60
+ const manifest = readManifest(root);
61
+ manifest.files = manifest.files || {};
62
+ const summary = { written: [], updated: [], kept: [], conflicts: [], upToDate: [], orphaned: [] };
63
+ const seen = new Set();
64
+
65
+ for (const doc of docs) {
66
+ const rel = targetRelPath(doc.path);
67
+ seen.add(rel);
68
+ const abs = join(mnemaDir(root), rel);
69
+ const entry = manifest.files[rel];
70
+ const content = renderFile(doc);
71
+ const remoteHash = doc.contentHash;
72
+
73
+ if (!existsSync(abs)) {
74
+ mkdirSync(dirname(abs), { recursive: true });
75
+ writeFileSync(abs, content);
76
+ manifest.files[rel] = { docId: doc.docId, remoteHash, localHash: sha256(content), pulledAt: new Date().toISOString() };
77
+ summary.written.push(rel);
78
+ continue;
79
+ }
80
+
81
+ const localNow = sha256(readFileSync(abs, 'utf8'));
82
+ const localEdited = !entry || localNow !== entry.localHash;
83
+ const remoteChanged = !entry || remoteHash !== entry.remoteHash;
84
+
85
+ if (!localEdited) {
86
+ if (remoteChanged) {
87
+ writeFileSync(abs, content);
88
+ manifest.files[rel] = { docId: doc.docId, remoteHash, localHash: sha256(content), pulledAt: new Date().toISOString() };
89
+ summary.updated.push(rel);
90
+ } else {
91
+ summary.upToDate.push(rel);
92
+ }
93
+ } else if (!remoteChanged) {
94
+ // Local ahead of an unchanged server — keep the local edit untouched.
95
+ summary.kept.push(rel);
96
+ } else {
97
+ // Both changed — surface the server version beside the local file, keep local.
98
+ const sidecar = abs.replace(/\.md$/, '.remote.md');
99
+ mkdirSync(dirname(sidecar), { recursive: true });
100
+ writeFileSync(sidecar, content);
101
+ summary.conflicts.push(rel);
102
+ }
103
+ }
104
+
105
+ // Docs that were pulled before but are no longer returned: leave the file in
106
+ // place (non-destructive) and just report it.
107
+ for (const rel of Object.keys(manifest.files)) {
108
+ if (!seen.has(rel)) summary.orphaned.push(rel);
109
+ }
110
+
111
+ writeManifest(root, manifest);
112
+ return summary;
113
+ }
114
+
115
+ /** Scaffold the editable artifacts on init: NOTABILITY.md + .gitignore + context/. */
116
+ export function scaffold(root) {
117
+ const dir = mnemaDir(root);
118
+ mkdirSync(join(dir, 'context'), { recursive: true });
119
+
120
+ const gitignore = join(dir, '.gitignore');
121
+ if (!existsSync(gitignore)) {
122
+ writeFileSync(gitignore, '# Conflict sidecars from `mnema pull` — transient, do not commit\n*.remote.md\n');
123
+ }
124
+
125
+ const keep = join(dir, 'context', '.gitkeep');
126
+ if (!existsSync(keep)) writeFileSync(keep, '');
127
+
128
+ const notability = join(dir, 'NOTABILITY.md');
129
+ if (!existsSync(notability)) {
130
+ writeFileSync(notability, `# What's worth remembering in this repo
131
+
132
+ Edit this file in prose. Mnema consults it when deciding which decisions, gotchas
133
+ and invariants from a coding session are worth capturing as durable knowledge — so
134
+ tune it to your team's taste instead of a numeric threshold.
135
+
136
+ ## Capture
137
+
138
+ - Non-obvious decisions and *why* an approach was chosen (or rejected)
139
+ - Gotchas that cost real time; invariants that must not be broken
140
+ - Anything a new engineer or agent would need to not repeat a mistake
141
+
142
+ ## Skip
143
+
144
+ - Routine, self-evident changes; anything already obvious from the diff
145
+ - Secrets, credentials, or customer data
146
+ `);
147
+ }
148
+ }