Phase 1a: Tool-Repo-Skelett + Vault-Pfad-Discovery

- package.json mit Scripts (build, dev, test, init-vault, index, ingest, sync)
- TypeScript-Config (tsconfig.json) + Vitest-Config
- CLI-Entry (bin/mindomat.mjs + src/cli/index.ts)
- Vault-Pfad-Discovery (1.2) mit CLI-Arg + Env-Var Fallback
- 10 Unit-Tests fuer Vault-Pfad-Discovery (alle gruen)
- Stubs fuer init-vault, index, ingest, sync (Placeholder, volle Implementierung in 1.3/1.5/2.3/1.8)
- webapp/-Stubs (Phase 1.6 Vorbereitung)
- README mit Install-Anleitung aktualisiert

Smoke-Tests:
- mindomat --version      => mindomat 0.1.0
- mindomat --help         => vollstaendige Hilfe
- mindomat index          => Fehler 'Vault-Pfad fehlt' (Exit 2)
- mindomat index --vault  => Stub-Aufruf (Exit 0)
This commit is contained in:
Jannik committed 2026-09-05 11:11:55 +02:00
1 parent 707e96c116
commit f86c3bf8ce
17 files changed
+2135 -11

No files matched your search

+68 -11
View File
@@ -1,20 +1,77 @@
# Mind-o-Mat
Lokales Second-Brain-System für einen Obsidian-kompatiblen Markdown-Vault.
Lokales Second-Brain-System für Obsidian-kompatible Markdown-Vaults. Besteht aus zwei Repos:
> **Status:** Brainstorming-Phase. Spezifikation v0.1 in `10 Wiki/SPEC.md` ist Entwurf, nicht freigegeben.
- **Dieses Repo** (`Mind-o-Mat`) — die Code-Basis (Tool). Wird gebaut und über npm publiziert.
- **Vault-Repo** (z. B. `Mind-o-Mat-Vault`) — die Notizen. Wird mit dem Tool initialisiert und via gitea synchronisiert.
## Was es mal können soll
> **Status:** Phase 1a in Arbeit. CLI-Skelett + Vault-Pfad-Discovery implementiert, andere Befehle sind Stubs. Siehe `10 Wiki/Plan.md` für den vollständigen Plan.
- **Wissen wiederfinden** auch wenn das exakte Wort fehlt (Bedeutungssuche).
- **Konflikte automatisch markieren** zwischen Notizen, die sich widersprechen.
- **Graph-Ansicht** deines gesamten Vaults mit erkennbaren Themen-Clustern.
## Installation (Vault-User)
## Aktueller Stand
```bash
# Tool global installieren (einmalig)
npm install -g mindomat
- `10 Wiki/SPEC.md` — Entwurf der Spezifikation mit allen 5 Architektur-Bausteinen.
- Alles weitere ist TODO und wird in Iterationen aufgebaut.
# Vault initialisieren
mindomat init-vault C:\Pfad\Zu\Mein-Vault
cd C:\Pfad\Zu\Mein-Vault
git remote add origin https://git.example.com/Dein-Name/Mein-Vault.git
git push -u origin main
## Mitmachen
# QMD installieren (einmalig, ~2 GB Modelle)
npm install -g @tobilu/qmd
cd C:\Pfad\Zu\Mein-Vault
qmd collection add 00_Inbox --name inbox
qmd collection add 01_Daily --name daily
qmd collection add 10_Wiki --name wiki
qmd collection add 20_Projekte --name projekte
qmd embed
Im Moment ist das Repo vor allem ein Ort, um die Idee festzuhalten, bevor irgendwas implementiert wird. Siehe `10 Wiki/SPEC.md` für die offenen Fragen und den aktuellen Stand der Planung.
# Vault indizieren (erzeugt landkarte.json)
MINDOMAT_VAULT_PATH=C:\Pfad\Zu\Mein-Vault mindomat index
```
## CLI-Referenz
```bash
mindomat --version # Version anzeigen
mindomat --help # Hilfe
mindomat init-vault <pfad> # Vault-Struktur erstellen
mindomat index --vault <pfad> # Vault indizieren
mindomat ingest --vault <pfad> # Inbox-Notizen verarbeiten
mindomat sync --vault <pfad> # gitea-Sync
```
Der Vault-Pfad wird in dieser Reihenfolge aufgelöst:
1. CLI-Argument `--vault <pfad>` (oder `-v <pfad>`)
2. Environment-Variable `MINDOMAT_VAULT_PATH`
3. Fehler: klare Meldung
## Entwicklung (Tool-Contributor)
```bash
git clone https://git.orfel.de/Jannik/Mind-o-Mat
cd Mind-o-Mat
npm install
npm run build # TypeScript kompilieren
npm test # Unit-Tests (Vitest)
npm run test:watch # Tests im Watch-Mode
```
## Architektur
Siehe `10 Wiki/SPEC.md` (Spezifikation) und `10 Wiki/Plan.md` (20 Aufgaben, 5 Phasen).
**5 Bausteine:**
1. **Vault** — Markdown-Dateien, Single Source of Truth (im Vault-Repo)
2. **Indexer** — deterministisch, erzeugt `landkarte.json` (im Tool)
3. **QMD** — lokale Hybridsuche, ~2 GB GGUF-Modelle (im Vault installiert)
4. **Wiki** — Ingest pflegt Markdown-Seiten via Minimax-API
5. **Webapp/PWA** — React + Cytoscape, Graph-Ansicht, Mobile-Editor
## Lizenz
MIT
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env node
// Mind-o-Mat CLI Entry
// Phase 1.1: Grundgeruest. Liest Args, dispatcht an Befehle.
// Importiert von dist/ (nach `npm run build`).
import { runCli } from '../dist/cli/index.js';
runCli(process.argv.slice(2)).catch((err) => {
console.error('Fehler:', err.message);
process.exit(1);
});
+1632
View File
File diff suppressed because it is too large. Load diff
+51
View File
@@ -0,0 +1,51 @@
{
"name": "mindomat",
"version": "0.1.0",
"description": "Lokales Second-Brain-System für Obsidian-kompatible Markdown-Vaults",
"type": "module",
"bin": {
"mindomat": "./bin/mindomat.mjs"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"files": [
"bin",
"dist",
"README.md"
],
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"test:watch": "vitest",
"init-vault": "npm run build && node bin/mindomat.mjs init-vault",
"index": "npm run build && node bin/mindomat.mjs index",
"ingest": "npm run build && node bin/mindomat.mjs ingest",
"sync": "npm run build && node bin/mindomat.mjs sync"
},
"keywords": [
"second-brain",
"obsidian",
"markdown",
"knowledge-management",
"pkm"
],
"license": "MIT",
"engines": {
"node": ">=22.0.0"
},
"dependencies": {
"gray-matter": "^4.0.3"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.6.0",
"vitest": "^2.1.0"
}
}
+7
View File
@@ -0,0 +1,7 @@
// 1.5 index: Indiziert den Vault
// Phase 1.5: STUB
export async function indexCommand(vaultPath: string, _args: string[]): Promise<void> {
console.log(`index: würde Vault indizieren unter ${vaultPath}`);
console.log(' (Stub — vollständige Implementierung kommt in Phase 1.5)');
}
+86
View File
@@ -0,0 +1,86 @@
// CLI Entry: Argument-Parsing, Help, Version, Dispatch zu den Befehlen
// Phase 1.1
import { resolveVaultPath, assertVaultExists, VaultPathError } from '../vault-path.js';
import { initVaultCommand } from './init-vault.js';
import { indexCommand } from './index-cmd.js';
import { ingestCommand } from './ingest.js';
import { syncCommand } from './sync.js';
const VERSION = '0.1.0';
export async function runCli(args: string[]): Promise<void> {
// Global flags vor erstem Subkommando
if (args.includes('--version') || args.includes('-v')) {
console.log(`mindomat ${VERSION}`);
return;
}
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
printHelp();
return;
}
// Subkommando + Rest
const [command, ...rest] = args;
// Befehle, die KEIN Vault brauchen
if (command === 'init-vault') {
await initVaultCommand(rest);
return;
}
// Befehle, die Vault brauchen
let resolved;
try {
resolved = resolveVaultPath({ args: rest, env: process.env, cwd: process.cwd() });
} catch (err) {
if (err instanceof VaultPathError) {
console.error(err.message);
process.exit(2);
}
throw err;
}
assertVaultExists(resolved.path);
switch (command) {
case 'index':
await indexCommand(resolved.path, rest);
break;
case 'ingest':
await ingestCommand(resolved.path, rest);
break;
case 'sync':
await syncCommand(resolved.path, rest);
break;
default:
console.error(`Unbekanntes Kommando: ${command}`);
printHelp();
process.exit(2);
}
}
function printHelp(): void {
console.log(`mindomat ${VERSION} — lokales Second-Brain-System
Verwendung:
mindomat <kommando> [optionen]
Kommandos:
init-vault <pfad> Erstellt einen neuen Vault (Phase 1.3)
index Indiziert den Vault (Phase 1.5)
ingest Verarbeitet Inbox-Notizen (Phase 2.3)
sync Sync zu gitea (Phase 1.8)
Globale Optionen:
--vault <pfad>, -v Pfad zum Vault (sonst MINDOMAT_VAULT_PATH)
--version Version anzeigen
--help, -h Diese Hilfe
Beispiele:
mindomat init-vault C:\\GitHub\\Mein-Vault
mindomat index --vault C:\\GitHub\\Mein-Vault
MINDOMAT_VAULT_PATH=C:\\GitHub\\Mein-Vault mindomat index
`);
}
+7
View File
@@ -0,0 +1,7 @@
// 2.3 ingest: Verarbeitet Inbox-Notizen
// Phase 2.3: STUB
export async function ingestCommand(vaultPath: string, _args: string[]): Promise<void> {
console.log(`ingest: würde Notizen verarbeiten in ${vaultPath}`);
console.log(' (Stub — vollständige Implementierung kommt in Phase 2.3)');
}
+12
View File
@@ -0,0 +1,12 @@
// 1.3 init-vault: Erstellt einen neuen Vault
// Phase 1.3: STUB — wird in 1.3 implementiert
export async function initVaultCommand(args: string[]): Promise<void> {
const target = args[0];
if (!target) {
console.error('Verwendung: mindomat init-vault <pfad>');
process.exit(2);
}
console.log(`init-vault: würde Vault erstellen unter ${target}`);
console.log(' (Stub — vollständige Implementierung kommt in Phase 1.3)');
}
+7
View File
@@ -0,0 +1,7 @@
// 1.8 sync: Sync zu gitea
// Phase 1.8: STUB
export async function syncCommand(vaultPath: string, _args: string[]): Promise<void> {
console.log(`sync: würde syncen für Vault ${vaultPath}`);
console.log(' (Stub — vollständige Implementierung kommt in Phase 1.8)');
}
+5
View File
@@ -0,0 +1,5 @@
// Mind-o-Mat Library Entry
// Phase 1.1: Re-Exports fuer npm-Nutzer
export { resolveVaultPath } from './vault-path.js';
export type { VaultPathOptions } from './vault-path.js';
+94
View File
@@ -0,0 +1,94 @@
// 1.2 Vault-Pfad-Discovery
// Phase 1.2: Tool findet Vault ueber CLI-Arg oder Env-Var
import { existsSync, statSync } from 'node:fs';
import { resolve } from 'node:path';
export interface VaultPathOptions {
/** CLI-Argumente (z. B. process.argv) */
args: string[];
/** Environment-Variablen (z. B. process.env) */
env: NodeJS.ProcessEnv;
/** CWD fuer Pfad-Aufloesung */
cwd: string;
}
export interface ResolvedVault {
path: string;
source: 'cli' | 'env';
}
export class VaultPathError extends Error {
constructor(message: string, public readonly code: 'MISSING' | 'NOT_FOUND' | 'NOT_DIRECTORY') {
super(message);
this.name = 'VaultPathError';
}
}
/**
* Findet den Vault-Pfad in dieser Reihenfolge:
* 1. CLI-Argument --vault <pfad> (oder -v)
* 2. Environment-Variable MINDOMAT_VAULT_PATH
*
* Wirft VaultPathError mit klarem Code, wenn nichts gefunden oder Pfad ungueltig.
*/
export function resolveVaultPath(opts: VaultPathOptions): ResolvedVault {
const { args, env, cwd } = opts;
// 1. CLI-Arg
const cliPath = extractCliVaultArg(args);
if (cliPath) {
const abs = resolve(cwd, cliPath);
return { path: abs, source: 'cli' };
}
// 2. Env-Var
const envPath = env.MINDOMAT_VAULT_PATH;
if (envPath && envPath.trim().length > 0) {
const abs = resolve(cwd, envPath);
return { path: abs, source: 'env' };
}
// 3. Nichts gefunden
throw new VaultPathError(
'Vault-Pfad fehlt. Setze MINDOMAT_VAULT_PATH oder nutze --vault <pfad>.',
'MISSING'
);
}
/**
* Extrahiert --vault <pfad> oder -v <pfad> aus den Args.
* Unterstuetzt auch --vault=<pfad>.
*/
function extractCliVaultArg(args: string[]): string | null {
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--vault' || a === '-v') {
return args[i + 1] ?? null;
}
if (a.startsWith('--vault=')) {
return a.slice('--vault='.length);
}
}
return null;
}
/**
* Prueft, ob der Pfad ein existierendes Verzeichnis ist.
* Wirft VaultPathError wenn nicht.
*/
export function assertVaultExists(path: string): void {
if (!existsSync(path)) {
throw new VaultPathError(
`Vault-Pfad existiert nicht: ${path}`,
'NOT_FOUND'
);
}
const stat = statSync(path);
if (!stat.isDirectory()) {
throw new VaultPathError(
`Vault-Pfad ist kein Verzeichnis: ${path}`,
'NOT_DIRECTORY'
);
}
}
+115
View File
@@ -0,0 +1,115 @@
// Unit-Tests für 1.2 Vault-Pfad-Discovery
// Phase 1.2 Test
import { describe, it, expect } from 'vitest';
import { resolveVaultPath, assertVaultExists, VaultPathError } from '../src/vault-path.js';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
describe('resolveVaultPath', () => {
const cwd = process.cwd();
const env: NodeJS.ProcessEnv = {};
it('liest --vault <pfad> aus CLI-Args', () => {
const result = resolveVaultPath({
args: ['--vault', 'C:\\foo'],
env: {},
cwd,
});
expect(result.path).toBe('C:\\foo');
expect(result.source).toBe('cli');
});
it('liest -v <pfad> Kurzform', () => {
const result = resolveVaultPath({
args: ['-v', 'C:\\bar'],
env: {},
cwd,
});
expect(result.path).toBe('C:\\bar');
expect(result.source).toBe('cli');
});
it('liest --vault=<pfad> Gleichheits-Form', () => {
const result = resolveVaultPath({
args: ['--vault=C:\\baz'],
env: {},
cwd,
});
expect(result.path).toBe('C:\\baz');
expect(result.source).toBe('cli');
});
it('faellt zurück auf MINDOMAT_VAULT_PATH, wenn keine CLI-Arg', () => {
const result = resolveVaultPath({
args: [],
env: { MINDOMAT_VAULT_PATH: 'C:\\env-vault' },
cwd,
});
expect(result.path).toBe('C:\\env-vault');
expect(result.source).toBe('env');
});
it('CLI-Arg ueberschreibt Env-Var', () => {
const result = resolveVaultPath({
args: ['--vault', 'C:\\cli-wins'],
env: { MINDOMAT_VAULT_PATH: 'C:\\env-loses' },
cwd,
});
expect(result.path).toBe('C:\\cli-wins');
expect(result.source).toBe('cli');
});
it('wirft VaultPathError MISSING, wenn weder CLI noch Env', () => {
expect(() => resolveVaultPath({
args: [],
env: {},
cwd,
})).toThrow(VaultPathError);
try {
resolveVaultPath({ args: [], env: {}, cwd });
} catch (err) {
expect(err).toBeInstanceOf(VaultPathError);
expect((err as VaultPathError).code).toBe('MISSING');
}
});
it('wirft VaultPathError MISSING, wenn Env-Var leer', () => {
expect(() => resolveVaultPath({
args: [],
env: { MINDOMAT_VAULT_PATH: ' ' },
cwd,
})).toThrow(VaultPathError);
});
it('loest relative Pfade gegen cwd auf', () => {
const result = resolveVaultPath({
args: ['--vault', 'relativ'],
env: {},
cwd: 'C:\\base',
});
// Windows-spezifisch: join mit Backslashes
expect(result.path).toMatch(/relativ/);
expect(result.path).toMatch(/C:\\base/);
});
});
describe('assertVaultExists', () => {
let tmpDir: string;
it('laesst existierende Verzeichnisse durch', () => {
tmpDir = mkdtempSync(join(tmpdir(), 'mindomat-test-'));
expect(() => assertVaultExists(tmpDir)).not.toThrow();
rmSync(tmpDir, { recursive: true, force: true });
});
it('wirft NOT_FOUND fuer nicht existierende Pfade', () => {
try {
assertVaultExists('C:\\gibt-es-nicht\\garantiert\\nicht');
} catch (err) {
expect(err).toBeInstanceOf(VaultPathError);
expect((err as VaultPathError).code).toBe('NOT_FOUND');
}
});
});
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "tests", "webapp"]
}
+12
View File
@@ -0,0 +1,12 @@
// Vitest Config
// Phase 1.1
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['tests/**/*.test.ts'],
environment: 'node',
globals: false,
},
});
+1
View File
@@ -0,0 +1 @@
# Phase 1.6: Vite + React + TS Skelett kommt hier hin
+1
View File
@@ -0,0 +1 @@
# Mind-o-Mat Webapp
+1
View File
@@ -0,0 +1 @@
<!doctype html><html><head><meta charset='utf-8'><title>Mind-o-Mat</title></head><body><div id='root'></div><script type='module' src='/src/main.tsx'></script></body></html>