CLI shell for VLMX
Project description
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
.dbfile - Textual UI — keyboard-driven terminal interface with org list sidebar
Requirements
- Python 3.11+
- uv
Installation
git clone <repo-url>
cd vlmx-sh
uv sync
Running
# 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
# 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
sqlite3for portability; codebase may be rewritten in Rust later - Two fixed tables — schema never changes; all typed fields live in the
propertiesblob - Layered imports —
nodes/must not import frommodels/;models/may import fromnodes/ - Immutable context —
Contextis 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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vlmx_sh-0.0.2.tar.gz.
File metadata
- Download URL: vlmx_sh-0.0.2.tar.gz
- Upload date:
- Size: 47.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c28a2bba589499de149eb9038632cce9997c893d5b47eff45881de0eaab3aa8
|
|
| MD5 |
c0fcbc0f050c23750877c684b7f74192
|
|
| BLAKE2b-256 |
65860a4f7bf395ecb96503f105d16ad37ed27548d156001cd0d660628aa7e6f2
|
File details
Details for the file vlmx_sh-0.0.2-py3-none-any.whl.
File metadata
- Download URL: vlmx_sh-0.0.2-py3-none-any.whl
- Upload date:
- Size: 71.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ac5cffa1d0253520290db9e2b5f8191fdceb5d2fab6ac31e9b30518a7aa1750
|
|
| MD5 |
2ea2247f7f444210e6a240f304446988
|
|
| BLAKE2b-256 |
37f84d0b2a29e2eb0d72b6be30c7ad6bc36cccc9f88fceecff4a4c14ad198c3e
|