展示HN:Firm,一个基于文本的工作管理系统
Show HN: Firm, a text-based work management system

原始链接: https://github.com/42futures/firm

## Firm:将业务作为代码 Firm 是一款面向技术人员设计的文本化工作管理系统,旨在让您拥有和控制自己的业务数据。与分散的 SaaS 工具不同,Firm 允许您将整个业务——客户、项目、任务及其关系——表示为纯文本文件,并使用 Firm DSL 进行结构化。这种“业务即代码”的方法提供了版本控制、本地存储和通过开放数据模型进行定制的能力。 主要功能包括定义实体(人员、组织、项目)及其连接,并通过 CLI(`firm list`、`firm get`、`firm related`)查询这些关系,以及自动化工作流程。Firm 从这些文件中构建一个统一、可查询的业务图谱,使其可供人类和 AI 工具(如 LLM)访问。 除了 CLI,Firm 还提供 Rust 包(`firm_core`、`firm_lang`)用于程序化访问和集成到自定义应用程序中。它基于强大的数据模型,利用 REA 框架和 Schema.org,强调清晰的实体关系而非继承。Firm 正在积极开发中,并在 GitHub 上可用,为构建定制的业务自动化提供强大的基础。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 展示 HN:Firm,一个基于文本的工作管理系统 (github.com/42futures) 8 分,danielrothmann 发表于 59 分钟前 | 隐藏 | 过去 | 收藏 | 1 条评论 Protostome 5 分钟前 [–] 让人们使用终端来完成任务,而不是使用 Google/Microsoft 工具产生的臃肿软件几乎是不可能的……我生活在终端中,但公司里的大多数人,包括开发人员,都更愿意远离它。回复 考虑申请 YC 的 2026 年冬季批次!申请截止日期为 11 月 10 日 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

A text-based work management system for technologists.

Firm CLI demo

Modern businesses are natively digital, but lack a unified view. Your data is scattered across SaaS tools you don't control, so you piece together answers by jumping between platforms.

Your business is a graph: customers link to projects, projects link to tasks, people link to organizations. Firm lets you define these relationships in plain text files (you own!).

Version controlled, locally stored and structured as code with the Firm DSL. This structured representation of your work, business-as-code, makes your business readable to yourself and to the robots that help you run it.

  • Everything in one place: Organizations, contacts, projects, and how they relate.
  • Own your data: Plain text files and tooling that runs on your machine.
  • Open data model: Tailor to your business with custom schemas.
  • Automate anything: Search, report, integrate, whatever. It's just code.
  • AI-ready: LLMs can read, write, and query your business structure.

The Firm CLI is available to download via Github Releases. Install scripts are provided for desktop platforms to make that process easy.

curl -fsSL https://raw.githubusercontent.com/42futures/firm/main/install.sh | sudo bash
irm https://raw.githubusercontent.com/42futures/firm/main/install.ps1 | iex

Firm operates on a "workspace": a directory containing all your .firm DSL files. The Firm CLI processes every file in this workspace to build a unified, queryable graph of your business.

The first step is to add an entity to your workspace. You can do this either by using the CLI or by writing the DSL yourself.

Add entities with the CLI

Use firm add to interactively generate new entities. Out of the box, Firm supports a set of pre-built entity schemas for org mapping, customer relations and work management. The CLI will prompt you for the necessary info and generate corresponding DSL.

Adding new entity

> Type: organization
> ID: megacorp
> Name: Megacorp Ltd.
> Email: [email protected]
> Urls: ["corp.com"]

Writing generated DSL to file my_workspace/generated/organization.firm

Alternatively, you can create a .firm file and write the DSL yourself.

organization megacorp {
  name = "Megacorp Ltd."
  email = "[email protected]"
  urls = ["corp.com"]
}

Both of these methods achieve the same result: a new entity defined in your Firm workspace.

Once you have entities in your workspace, you can query them using the CLI.

Use firm list to see all entities of a specific type.

Found 7 entities with type 'task'

ID: task.design_homepage
Name: Design new homepage
Is completed: false
Assignee ref: person.jane_doe

...

To view the full details of a single entity, use firm get followed by the entity's type and ID.

$ firm get person john_doe
Found 'person' entity with ID 'john_doe'

ID: person.john_doe
Name: John Doe
Email: [email protected]

The power of Firm lies in its ability to travel a graph of your business. Use firm related to explore connections to/from any entity.

$ firm related contact john_doe
Found 1 relationships for 'contact' entity with ID 'john_doe'

ID: interaction.megacorp_intro
Type: Call
Subject: Initial discussion about Project X
Interaction date: 2025-09-30 09:45:00 +02:00
Initiator ref: person.jane_smith
Primary contact ref: contact.john_doe

You've seen the basic commands for interacting with a Firm workspace. The project is a work-in-progress, and you can expect to see more sophisticated features added over time, including a more powerful query engine and tools for running business workflows directly from the CLI.

Beyond the CLI, you can integrate Firm's core logic directly into your own software using the firm_core and firm_lang Rust packages. This allows you to build more powerful automations and integrations on top of Firm.

First, add the Firm crates to your Cargo.toml:

[dependencies]
firm_core = { git = "https://github.com/42futures/firm.git" }
firm_lang = { git = "https://github.com/42futures/firm.git" }

You can then load a workspace, build the entity graph, and query it programmatically:

use firm_lang::workspace::Workspace;
use firm_core::EntityGraph;

// Load workspace from a directory
let mut workspace = Workspace::new();
workspace.load_directory("./my_workspace")?;
let build = workspace.build()?;

// Build the graph from the workspace entities
let mut graph = EntityGraph::new();
graph.add_entities(build.entities)?;
graph.build();

// Query the graph for a specific entity
let lead = graph.get_entity(&EntityId::new("lead.ai_validation_project"))?;

// Traverse a relationship to another entity
let contact_ref = lead.get_field(FieldId::new("contact_ref"))?;
let contact = contact_ref.resolve_entity_reference(&graph)?;

This gives you full access to the underlying data structures, providing a foundation for building custom business automations.

Firm is organized as a Rust workspace with three crates:

Core data structures and graph operations.

  • Entity data model
  • Typed fields with references
  • Relationship graph with query capabilities
  • Entity schemas and validation

DSL parsing and generation.

  • Tree-sitter-based parser for .firm files
  • Conversion between DSL and entities
  • Workspace support for multi-file projects
  • DSL generation from entities

Grammar is defined in tree-sitter-firm.

Command-line interface, making the Firm workspace interactive.

Firm's data model is built on a few key concepts. Each concept is accessible declaratively through the .firm DSL for human-readable definitions, and programmatically through the Rust packages for building your own automations.

Entities are the fundamental business objects in your workspace, like people, organizations, or projects. Each entity has a unique ID, a type, and a collection of fields.

In the DSL, you define an entity with its type and ID, followed by its fields in a block:

person john_doe {
    name = "John Doe"
    email = "[email protected]"
}

In Rust, this corresponds to an Entity struct:

let person = Entity::new(EntityId::new("john_doe"), EntityType::new("person"))
    .with_field(FieldId::new("name"), "John Doe")
    .with_field(FieldId::new("email"), "[email protected]");

Fields are typed key-value pairs attached to an entity. Firm supports a rich set of types:

  • String
  • Integer
  • Float
  • Boolean
  • Currency
  • DateTime
  • List of other values
  • Reference to other fields or entities
  • Path to a local file

In the DSL, the syntax maps directly to these types:

my_task design_homepage {
    title = "Design new homepage"        // String
    priority = 1                         // Integer
    completed = false                    // Boolean
    budget = 5000.00 USD                 // Currency
    due_date = 2024-12-01 at 17:00 UTC   // DateTime
    tags = ["ui", "ux"]                  // List
    assignee = person.jane_doe           // Reference
    deliverable = path"./homepage.zip"   // Path
}

In Rust, these are represented by the FieldValue enum:

let value = FieldValue::Integer(42);

Relationships and the entity graph

The power of Firm comes from connecting entities. You create relationships using Reference fields.

When Firm processes your workspace, it builds the entity graph representing of all your entities (as nodes) and their relationships (as directed edges). This graph is what allows for traversal and querying.

In the DSL, creating a relationship is as simple as referencing another entity's ID.

contact john_at_acme {
    person_ref = person.john_doe
    organization_ref = organization.acme_corp
}

In Rust, you build the graph by loading entities and calling the .build() method, which resolves all references into queryable links.

let mut graph = EntityGraph::new();
graph.add_entities(workspace.build()?.entities)?;
graph.build(); // Builds relationships from references

// Now you can traverse the graph
let contact = graph.get_entity(&EntityId::new("contact.john_at_acme"))?;
let person_ref = contact.get_field(FieldId::new("person_ref"))?;
let person = person_ref.resolve_entity_reference(&graph)?;

Schemas allow you to define and enforce a structure for your entities, ensuring data consistency. You can specify which fields are required or optional and what their types should be.

In the DSL, you can define a schema that other entities can adhere to:

schema custom_project {
    field {
        name = "title"
        type = "string"
        required = true
    }
    field {
        name = "budget"
        type = "currency"
        required = false
    }
}

custom_project my_project {
    title  = "My custom project"
    budget = 42000 EUR
}

In Rust, you can define schemas programmatically to validate entities.

let schema = EntitySchema::new(EntityType::new("project"))
    .with_required_field(FieldId::new("title"), FieldType::String)
    .with_optional_field(FieldId::new("budget"), FieldType::Currency);

schema.validate(&some_project_entity)?;

Firm includes schemas for a range of built-in entities like Person, Organization, and Industry.

Firm's entity taxonomy is built on the REA model (Resources, Events, Agents) with inspiration from Schema.org, designed for flexible composition and efficient queries.

Every entity maps to a Resource (thing with value), an Event (thing that happens), or an Agent (thing that acts).

We separate objective reality from business relationships:

  • Fundamental entities represent things that exist independently (Person, Organization, Document)
  • Contextual entities represent your business relationships and processes (Contact, Lead, Project)

Entities reference each other rather than extending. One Person can be referenced by multiple Contact, Employee, and Partner entities simultaneously.

When the entity graph is built, all Reference values automatically create directed edges between entities. This enables traversal queries like "find all Tasks for Opportunities whose Contacts work at Organization X" without complex joins.

联系我们 contact @ memedata.com