Metadata-Version: 2.4
Name: vlmx-sh
Version: 0.0.2
Summary: CLI shell for VLMX
Requires-Python: >=3.11
Requires-Dist: jinja2>=3.1.0
Requires-Dist: pydantic
Requires-Dist: sqlmodel>=0.0.29
Requires-Dist: textual
Requires-Dist: tomlkit>=0.14.0
Description-Content-Type: text/markdown

# vlmx-sh

A terminal DSL shell for entrepreneurs — graph-based company data management through a natural language command interface.

## Overview

vlmx-sh is a Textual-based TUI application that lets you manage structured company data using a simple shell-like DSL. Organisations and their data live in SQLite databases; navigation mirrors a filesystem hierarchy (`cd`, `~`, `..`).

## Features

- **Context-aware navigation** — move between system, org, and app levels with `cd`
- **Natural language DSL** — commands like `add brand vision="We change the world"`
- **Graph data model** — nodes (companies, people, brands, …) and edges stored in SQLite
- **Per-org databases** — each organisation gets its own isolated `.db` file
- **Textual UI** — keyboard-driven terminal interface with org list sidebar

## Requirements

- Python 3.11+
- [uv](https://github.com/astral-sh/uv)

## Installation

```bash
git clone <repo-url>
cd vlmx-sh
uv sync
```

## Running

```bash
# Via the installed script
vlmx

# Or directly
python -m vlmx_sh
```

## DSL Commands

All commands follow the pattern: `<verb> [target] [field=value ...]`

### Navigation

| Command | Description |
|---|---|
| `cd acme` | Enter the `acme` org context |
| `cd ..` | Go up one level |
| `cd ~` | Return to system root |

### Org management

| Command | Description |
|---|---|
| `create company:Acme` | Create a new company and enter it |
| `drop company:Acme` | Permanently delete a company |
| `show company` | List all companies |

### Data management (inside an org context)

| Command | Description |
|---|---|
| `add brand vision="..." mission="..."` | Set brand fields (singleton) |
| `add person:Alice role="CEO"` | Add a named person node |
| `show brand` | Show all brand fields |
| `show brand vision mission` | Show specific fields |
| `show person` | List all persons |
| `edit person:Alice role="CTO"` | Update a named node |
| `delete brand vision` | Clear a specific field |
| `delete brand` | Clear all brand fields |
| `delete person:Alice` | Soft-delete a named node |

### Keyboard shortcuts

| Key | Action |
|---|---|
| `d` | Toggle dark / light mode |

## Architecture

```
src/vlmx_sh/
├── core/
│   ├── enums/          # OrgType, Legal, Currency, ContextLevel, TokenType, …
│   ├── models/         # Context, Word hierarchy, ValidationContext, responses
│   ├── nodes/          # BaseNode, org nodes, item nodes, meta nodes
│   └── registry.py     # Maps node classes ↔ DSL words
├── dsl/
│   ├── parser/         # 7-stage parsing pipeline (see below)
│   ├── ir/             # IRCommand — stable intermediate representation
│   ├── ast/            # FilterExpression
│   └── words/          # Verb registrations and macros
├── engine/
│   ├── executor.py     # Single UI entry point: text → parse → IR → dispatch
│   ├── router.py       # Dispatches action_id to registered handlers
│   └── handlers/       # crud.py (add/show/edit/delete), navigation.py (cd)
├── db/
│   ├── sqlite_backend.py  # Per-org SQLite (nodes + edges tables)
│   ├── org_backend.py     # create_org / drop_org lifecycle
│   └── paths.py           # Data directory helpers
├── storage/
│   └── org_registry.py    # org_registry.toml read/write (org name → db path)
└── ui/
    ├── app.py              # Textual App
    ├── screens/main/       # MainScreen (sidebar + command blocks)
    ├── widgets/            # CommandBlock (prompt + input + output)
    ├── formatters/         # Result → display text
    └── styles/design.tcss  # UI styles
```

### Parsing pipeline

Input text goes through seven sequential stages before becoming an `IRCommand`:

```
raw text
  → 0. Normalizer    — whitespace cleanup
  → 1. Tokenizer     — split into Token objects (handles quoted strings, operators)
  → 2. Classifier    — assign structural class (TEXT, OPERATOR, BRACKET)
  → 3. Recognizer    — match tokens to registry words (Verb, FieldWord, ItemWord, …)
  → 4. Interpreter   — typo correction, infer missing verb/entity in ORG context
  → 5. Splitter      — separate command tokens from filter tokens
  → 6. Filter parser — build FilterExpression from filter tokens
  → IRCommand        — stable, serialisable command object
```

### Data model

Two fixed SQLite tables per org:

```
nodes  (id, org_id, type, name, deprecated, created_at, updated_at, properties)
edges  (id, org_id, from_id, to_id, type, created_at, properties)
```

Type-specific fields are packed into the `properties` JSON column. Pydantic models on the app layer unpack them transparently via `to_storage_dict()` / `from_storage_dict()`.

### Node types

**Org nodes** — created at system level via `create`

| type | class |
|---|---|
| `company` | CompanyNode |
| `fund` | FundNode |
| `foundation` | FoundationNode |
| `subsidiary` | SubsidiaryNode |

**Item nodes** — created inside an org context via `add`

| type | singleton | description |
|---|---|---|
| `brand` | yes | Vision, mission, values |
| `address` | yes | Physical address |
| `market` | yes | Market data |
| `finance` | yes | Financial data |
| `values` | yes | Company values |
| `metadata` | yes | Arbitrary metadata |
| `person` | no | Team members |
| `offering` | no | Products / services |
| `competitor` | no | Competitor entries |
| `news` | no | News items |
| `portfolio` | no | Portfolio entries |
| `target` | no | Strategic targets |

**Meta nodes**

| type | description |
|---|---|
| `group` | Node grouping |
| `tag` | Tagging |

Singleton nodes are upserted (one per org); non-singleton nodes create a new record each time (identified by name).

### Context levels

```
SYS  (~)             — system root; create / list orgs
ORG  (~/acme)        — inside an org; manage all item nodes
APP  (~/acme/app)    — inside an app (future)
```

The `Context` object is immutable. Navigation produces a new `Context` instance.

## Data storage layout

```
data/
├── org_registry.toml          # All known orgs (id, name, db_path, created_at)
├── acme/
│   └── acme.db            # Org database (nodes + edges)
└── valmetrics/
    └── valmetrics.db
```

## Development

```bash
# Run tests
uv run pytest tests/

# Run with Textual dev tools (live reload)
uv run textual run --dev src/vlmx_sh/__main__.py
```

## Key design decisions

- **No ORM** — plain `sqlite3` for portability; codebase may be rewritten in Rust later
- **Two fixed tables** — schema never changes; all typed fields live in the `properties` blob
- **Layered imports** — `nodes/` must not import from `models/`; `models/` may import from `nodes/`
- **Immutable context** — `Context` is a frozen Pydantic model; navigation returns new instances
- **Registry-driven DSL** — words are auto-generated from node classes; verbs are registered manually
- **IR boundary** — the engine never sees parser internals; only `IRCommand` (plain data) crosses the boundary
