acquaint 0.0.2__tar.gz

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.
Files changed (45) hide show
  1. acquaint-0.0.2/.github/workflows/ci.yml +43 -0
  2. acquaint-0.0.2/.gitignore +120 -0
  3. acquaint-0.0.2/LICENSE +21 -0
  4. acquaint-0.0.2/PKG-INFO +168 -0
  5. acquaint-0.0.2/README.md +141 -0
  6. acquaint-0.0.2/acquaint/__init__.py +63 -0
  7. acquaint-0.0.2/acquaint/__main__.py +73 -0
  8. acquaint-0.0.2/acquaint/brief.py +243 -0
  9. acquaint-0.0.2/acquaint/data/agents/profile-reader.md +18 -0
  10. acquaint-0.0.2/acquaint/data/agents/recipient-reader.md +16 -0
  11. acquaint-0.0.2/acquaint/data/deslop/tells.yaml +164 -0
  12. acquaint-0.0.2/acquaint/data/hooks/pre-push +48 -0
  13. acquaint-0.0.2/acquaint/data/policy.yaml +55 -0
  14. acquaint-0.0.2/acquaint/data/purposes.yaml +70 -0
  15. acquaint-0.0.2/acquaint/data/skills/acquaint/SKILL.md +66 -0
  16. acquaint-0.0.2/acquaint/data/skills/acquaint-profile/SKILL.md +126 -0
  17. acquaint-0.0.2/acquaint/data/skills/acquaint-read/SKILL.md +53 -0
  18. acquaint-0.0.2/acquaint/data/skills/acquaint-sync/SKILL.md +62 -0
  19. acquaint-0.0.2/acquaint/data/skills/acquaint-write/SKILL.md +79 -0
  20. acquaint-0.0.2/acquaint/data/skills/deslop/SKILL.md +74 -0
  21. acquaint-0.0.2/acquaint/data/templates/POLICY.md +44 -0
  22. acquaint-0.0.2/acquaint/data/templates/ledger.md +20 -0
  23. acquaint-0.0.2/acquaint/data/templates/person.md +29 -0
  24. acquaint-0.0.2/acquaint/deslop.py +262 -0
  25. acquaint-0.0.2/acquaint/edit.py +570 -0
  26. acquaint-0.0.2/acquaint/lint.py +384 -0
  27. acquaint-0.0.2/acquaint/lookup.py +483 -0
  28. acquaint-0.0.2/acquaint/mcp.py +102 -0
  29. acquaint-0.0.2/acquaint/records.py +557 -0
  30. acquaint-0.0.2/acquaint/render.py +53 -0
  31. acquaint-0.0.2/acquaint/resources.py +39 -0
  32. acquaint-0.0.2/acquaint/store.py +611 -0
  33. acquaint-0.0.2/acquaint/sync.py +493 -0
  34. acquaint-0.0.2/acquaint/tools.py +598 -0
  35. acquaint-0.0.2/pyproject.toml +185 -0
  36. acquaint-0.0.2/tests/conftest.py +26 -0
  37. acquaint-0.0.2/tests/test_deslop.py +111 -0
  38. acquaint-0.0.2/tests/test_edit_lint_brief.py +380 -0
  39. acquaint-0.0.2/tests/test_lookup.py +181 -0
  40. acquaint-0.0.2/tests/test_no_personal_data.py +114 -0
  41. acquaint-0.0.2/tests/test_records_store.py +273 -0
  42. acquaint-0.0.2/tests/test_skills.py +83 -0
  43. acquaint-0.0.2/tests/test_smoke.py +87 -0
  44. acquaint-0.0.2/tests/test_surfaces.py +118 -0
  45. acquaint-0.0.2/tests/test_sync.py +325 -0
@@ -0,0 +1,43 @@
1
+ # wads CI — calls the reusable workflow hosted in i2mint/wads.
2
+ #
3
+ # All configuration comes from this repo's pyproject.toml [tool.wads.ci.*].
4
+ # To customize the workflow itself (rare), replace this file with the
5
+ # full inline template `wads/data/github_ci_uv.yml` from i2mint/wads.
6
+ #
7
+ # Pinning: `@master` floats with wads. If you need version stability for
8
+ # a release-sensitive repo, change `@master` to a wads tag (e.g. `@0.2.15`;
9
+ # tags have no `v` prefix). A stub whose `secrets:` block passes the JSON
10
+ # transport (the default below) needs a tag from a release after 0.2.14 —
11
+ # older tags don't declare that secret and GitHub then rejects the
12
+ # workflow at parse time.
13
+ # CI failure does not block a published release — it blocks the publish
14
+ # step itself — so floating master is generally safe.
15
+ #
16
+ # Permissions: GitHub validates that the caller grants AT LEAST the
17
+ # permissions any job in the called workflow requests — at workflow-parse
18
+ # time, not at run-time, even if the job would be skipped via `if:`.
19
+ # The reusable workflow needs:
20
+ # contents: write for the publish job's version-bump push-back
21
+ # and for the github-pages job's gh-pages branch push
22
+ # pages: write for the github-pages job's REST API Pages config
23
+ # Both default to `write` on org-account GITHUB_TOKEN and need to be
24
+ # granted explicitly on personal-account callers (where the default is
25
+ # read-only). No `id-token: write` needed — the publish-github-pages
26
+ # action uses peaceiris/actions-gh-pages (branch-based) + REST API,
27
+ # not the OIDC `actions/deploy-pages` flow.
28
+ name: Continuous Integration
29
+ on: [push, pull_request]
30
+ jobs:
31
+ ci:
32
+ uses: i2mint/wads/.github/workflows/uv-ci.yml@master
33
+ permissions:
34
+ contents: write
35
+ pages: write
36
+ # Transport (NAMED, legacy): explicitly passes only the secrets
37
+ # listed below (PYPI_PASSWORD + those declared in
38
+ # [tool.wads.ci.env]). Every name must be in the frozen wads
39
+ # superset (wads/ci_secrets.py) or GitHub rejects the workflow
40
+ # at parse time. The default JSON transport has no such limit;
41
+ # regenerate with `wads-migrate ci-to-stub` to switch.
42
+ secrets:
43
+ PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
@@ -0,0 +1,120 @@
1
+ .claude/handoffs/
2
+ .claude/scratch/
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+
10
+ .DS_Store
11
+ # C extensions
12
+ *.so
13
+
14
+ # TLS certificates
15
+ ## Ignore all PEM files anywhere
16
+ *.pem
17
+ ## Also ignore any certs directory
18
+ certs/
19
+
20
+ # Distribution / packaging
21
+ .Python
22
+ build/
23
+ develop-eggs/
24
+ dist/
25
+ downloads/
26
+ eggs/
27
+ .eggs/
28
+ lib/
29
+ lib64/
30
+ parts/
31
+ sdist/
32
+ var/
33
+ wheels/
34
+ *.egg-info/
35
+ .installed.cfg
36
+ *.egg
37
+ MANIFEST
38
+ _build
39
+
40
+ # PyInstaller
41
+ # Usually these files are written by a python script from a template
42
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
43
+ *.manifest
44
+ *.spec
45
+
46
+ # Installer logs
47
+ pip-log.txt
48
+ pip-delete-this-directory.txt
49
+
50
+ # Unit test / coverage reports
51
+ htmlcov/
52
+ .tox/
53
+ .coverage
54
+ .coverage.*
55
+ .cache
56
+ nosetests.xml
57
+ coverage.xml
58
+ *.cover
59
+ .hypothesis/
60
+ .pytest_cache/
61
+
62
+ # Translations
63
+ *.mo
64
+ *.pot
65
+
66
+ # Django stuff:
67
+ *.log
68
+ local_settings.py
69
+ db.sqlite3
70
+
71
+ # Flask stuff:
72
+ instance/
73
+ .webassets-cache
74
+
75
+ # Scrapy stuff:
76
+ .scrapy
77
+
78
+ # Sphinx documentation
79
+ docs/_build/
80
+ docs/*
81
+
82
+ # PyBuilder
83
+ target/
84
+
85
+ # Jupyter Notebook
86
+ .ipynb_checkpoints
87
+
88
+ # pyenv
89
+ .python-version
90
+
91
+ # celery beat schedule file
92
+ celerybeat-schedule
93
+
94
+ # SageMath parsed files
95
+ *.sage.py
96
+
97
+ # Environments
98
+ .env
99
+ .venv
100
+ env/
101
+ venv/
102
+ ENV/
103
+ env.bak/
104
+ venv.bak/
105
+
106
+ # Spyder project settings
107
+ .spyderproject
108
+ .spyproject
109
+
110
+ # Rope project settings
111
+ .ropeproject
112
+
113
+ # mkdocs documentation
114
+ /site
115
+
116
+ # mypy
117
+ .mypy_cache/
118
+
119
+ # PyCharm
120
+ .idea
acquaint-0.0.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thor Whalen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.5
2
+ Name: acquaint
3
+ Version: 0.0.2
4
+ Summary: People, and what they are involved in, for AI agents: who someone is, how to reach them, how to read them, how to write to them.
5
+ Project-URL: Homepage, https://github.com/thorwhalen/acquaint
6
+ Project-URL: Repository, https://github.com/thorwhalen/acquaint
7
+ Project-URL: Documentation, https://thorwhalen.github.io/acquaint
8
+ Project-URL: Issues, https://github.com/thorwhalen/acquaint/issues
9
+ Author: Thor Whalen
10
+ License: mit
11
+ License-File: LICENSE
12
+ Keywords: agents,claude-code,contacts,deslop,people,profiles,writing
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: cw<0.2,>=0.1.1
15
+ Requires-Dist: dol
16
+ Requires-Dist: pyyaml
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
19
+ Requires-Dist: pytest>=7.0; extra == 'dev'
20
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
21
+ Provides-Extra: docs
22
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == 'docs'
23
+ Requires-Dist: sphinx>=6.0; extra == 'docs'
24
+ Provides-Extra: mcp
25
+ Requires-Dist: py2mcp; extra == 'mcp'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # acquaint
29
+
30
+ People, and what they are involved in, for AI agents: who someone is, how to reach them, how to read them, how to write to them.
31
+
32
+ ```bash
33
+ pip install acquaint
34
+
35
+ acquaint new person "Ada Lovelace"
36
+ acquaint remember ada-lovelace "prefers email for anything with attachments" --source "https://example.org/thread/1"
37
+ acquaint who ada -f aka # → Ada, Lovelace
38
+ acquaint brief ada-lovelace --purpose ask # everything to know before writing to her
39
+ ```
40
+
41
+ Tell an agent "write to Ada about the export" and it can find out, without being told again, who that is, where to reach her for this, what register to use, what to avoid, and where to record what it learns. The "how" is written down once, per person, and every agent, skill and tool reads it through the same verbs.
42
+
43
+ Records are hand-editable Markdown, one folder per person (or project, org, group), kept **outside any code repository**. Every preference carries its source, and `acquaint lint` fails when one does not.
44
+
45
+ ## What it records, and what it never does
46
+
47
+ A profile holds observable behaviour with evidence: "replies in one or two lines", "wants the decision first", quoted and linked. It never holds personality labels, moods, or special-category data (health, religion, politics, ethnicity, sexuality, union membership), stated or inferred, nor credentials, identifiers, or whole message bodies. Every store gets a `POLICY.md` stating this, and `lint` warns on tripwires.
48
+
49
+ The test for every line: would it survive being handed to the person it is about?
50
+
51
+ ## Where the data lives
52
+
53
+ The data root is the first of: the `data_dir` argument, `$ACQUAINT_DATA_DIR`, `data_dir` in `~/.config/acquaint/config.toml`, `~/.local/share/acquaint`.
54
+
55
+ ```
56
+ POLICY.md what may be recorded
57
+ people/ada-lovelace/
58
+ PROFILE.md entry file: identity frontmatter + Who · Reach · Write to them · Read them · Don't · Now · More
59
+ identities.yaml handles and addresses, with evidence
60
+ rules.yaml channel rules: when → do, who set it, source
61
+ links.yaml affiliations: project, org, group; role; period
62
+ style.md the writing card: AI tolerance, register, do, don't, blocklist, exemplars
63
+ views.md positions and standing objections, sourced
64
+ sources.md where their writing lives, how authorship was verified
65
+ log/2026-09.md append-only observations
66
+ projects/<id>/PROFILE.md What & status · Where things live · Who · Agents & skills · Norms · Now
67
+ orgs/<id>/ groups/<id>/ … any other kind
68
+ ```
69
+
70
+ Only `PROFILE.md` is required. A malformed file is reported and skipped; it never breaks lookups of anyone else.
71
+
72
+ ## The verbs
73
+
74
+ The same fifteen functions are the Python API (`acquaint.tools`), the CLI, and the MCP tools. Each returns a JSON-ready dict.
75
+
76
+ | Verb | Does |
77
+ |---|---|
78
+ | `who NAME [-f FIELD] [-b]` | one field, the identity block, or the whole entry file; lists candidates instead of guessing |
79
+ | `resolve HANDLE` | `github:octocat`, `email:…` → the person, with the evidence; a handle without its platform, a match by name only, or an inactive identity is reported, never acted on |
80
+ | `check TEXT` | before publishing: one person written as two ("Ada or Lovelace"), shared names, unknown names |
81
+ | `reach PERSON [--purpose --urgency --project --message-type --topic]` | ordered channels: the person's own rules > the operator's rules > project norms > observed habits > defaults |
82
+ | `brief PERSON [--purpose --project]` | card, writing style, views, reach, project norms, recent observations, reminders, and what is **not** known |
83
+ | `remember ENTITY TEXT [--source --kind]` | append a dated, sourced observation (or identity, preference, view, rule) |
84
+ | `lint [ENTITY]` | sources on every preference; parseable files; entry-file budget; policy tripwires |
85
+ | `style-lint TEXT [--recipient --tolerance]` | machine-writing tells, enforced by the reader's tolerance of AI-sounding text |
86
+ | `new KIND NAME [--qualifier --description]` | scaffold from a template; readable slug ids (`ada-lovelace`, `john-smith--example-org`) |
87
+ | `rename ID TO` | new id or name; links elsewhere rewritten (never inside URLs or logs); old forms kept as aliases |
88
+ | `forget ID [--confirm]` | remove the whole folder, leaving a salted tombstone so the person is not silently re-created |
89
+ | `sync init --repo OWNER/NAME` · `sync push` · `sync pull` · `sync status` | private-repository sync, below |
90
+
91
+ `--json` prints the result dict; `-` as the text of `check` or `style-lint` reads stdin.
92
+
93
+ Nothing acts on a guess: a name, alias, handle or email counts only when exactly one record has it exactly, and a partial match comes back as a suggestion. `rename` and `forget` need the exact id.
94
+
95
+ ## Sources
96
+
97
+ End every preference, view or rule with a source tag:
98
+
99
+ ```markdown
100
+ ## Write to them
101
+ - Lead with the decision, then the options. [source: self: "give me the recommendation first"]
102
+ - Short replies on chat, fuller ones by email. [source: log/2026-09.md#e03]
103
+ - No attachments over chat. [source: https://example.org/thread/1]
104
+ ```
105
+
106
+ A source is a permalink, a log or research anchor, the person's own words, `operator`, or `none located`. **A date alone is not a source**, and neither is a placeholder such as `unknown` or `TODO`: an unsourced claim with a date attached reads as observed when it was not. A nested bullet is its own line and needs its own source.
107
+
108
+ ## Agent skills
109
+
110
+ Six skills ship inside the package (`acquaint/data/skills/`) and install with `gh skill`:
111
+
112
+ | Skill | For |
113
+ |---|---|
114
+ | `acquaint` | the router: lookups at the right cost, `check` before publishing, `remember` with sources |
115
+ | `acquaint-profile` | building a profile from someone's own writing, with parallel `profile-reader` agents |
116
+ | `acquaint-write` | writing for a known reader, and sparring with a simulated one (`recipient-reader` agent) |
117
+ | `acquaint-read` | interpreting a message from a known person; AI-processing as a likelihood with evidence |
118
+ | `deslop` | prose without machine-writing tells, calibrated to the reader |
119
+ | `acquaint-sync` | private sync, and what it does not protect |
120
+
121
+ ```bash
122
+ gh skill install thorwhalen/acquaint acquaint --agent claude-code
123
+ ```
124
+
125
+ `gh skill` needs a recent `gh`; otherwise symlink the folders from the installed package's `acquaint/data/skills/` into `~/.claude/skills/`. If you already have a user-level skill named `deslop`, skip acquaint's or install it under another name: two skills with the same name at the same scope replace each other.
126
+
127
+ ## MCP
128
+
129
+ ```bash
130
+ pip install "acquaint[mcp]"
131
+ ```
132
+
133
+ ```json
134
+ {"mcpServers": {"acquaint": {"command": "acquaint-mcp"}}}
135
+ ```
136
+
137
+ The server exposes the tools that read locally, append or create (`who`, `resolve`, `check`, `reach`, `brief`, `remember`, `lint`, `new`, `style_lint`). Renaming, forgetting and syncing stay at the terminal. `data_dir` is not exposed: the data root is the server's (set `ACQUAINT_DATA_DIR` in the client configuration), never the model's.
138
+
139
+ ## Private sync
140
+
141
+ ```bash
142
+ acquaint sync init --repo <owner>/<name> --dry-run
143
+ acquaint sync init --repo <owner>/<name>
144
+ ```
145
+
146
+ `sync init` creates the repository through `gh` as private, refuses to continue unless `gh` reports it `PRIVATE`, and installs a pre-push hook, active in every worktree, that allows a push only through `origin`, only when `origin` has exactly the one URL recorded at init (no `pushurl` or `pushInsteadOf` redirect), only when that URL names the checked repository on github.com, and only while `gh` still reports it private. `push`, `pull` and `status` check again. `sync init` takes over only an empty folder or a clone of that same repository.
147
+
148
+ What this does **not** protect: a private repository is access control, not encryption (the host can read everything); `git push --no-verify` skips the hook; whoever controls the repository's git config or the `gh` on `PATH` controls what the guard sees; moving the data root disables the hook until `sync init --existing-only` runs again; file names and commit messages contain people's names; deleting a folder does not remove it from history or other clones. `git-remote-gcrypt` would encrypt contents, names and history; it is the planned upgrade, but the guard does not support it yet, so `sync init` refuses `gcrypt::` URLs ([#13](https://github.com/thorwhalen/acquaint/issues/13)).
149
+
150
+ ## Python
151
+
152
+ ```python
153
+ from acquaint import Store, who, brief
154
+
155
+ who("ada", field="aka")["value"] # ['Ada', 'Lovelace']
156
+ print(brief("ada-lovelace", purpose="ask")["text"])
157
+
158
+ store = Store() # MutableMapping[str, Entity] over a dol files store
159
+ entity = store[
160
+ "people/ada-lovelace"
161
+ ] # a mapping of that person's files, plus parsed views
162
+ entity.identities, entity.rules, entity.sections["Write to them"]
163
+ Store(files={}) # any MutableMapping[str, str] of files: a dict, a remote store
164
+ ```
165
+
166
+ ## Design
167
+
168
+ The seams, surfaces and deliberate non-seams are in [Discussion #1](https://github.com/thorwhalen/acquaint/discussions/1).
@@ -0,0 +1,141 @@
1
+ # acquaint
2
+
3
+ People, and what they are involved in, for AI agents: who someone is, how to reach them, how to read them, how to write to them.
4
+
5
+ ```bash
6
+ pip install acquaint
7
+
8
+ acquaint new person "Ada Lovelace"
9
+ acquaint remember ada-lovelace "prefers email for anything with attachments" --source "https://example.org/thread/1"
10
+ acquaint who ada -f aka # → Ada, Lovelace
11
+ acquaint brief ada-lovelace --purpose ask # everything to know before writing to her
12
+ ```
13
+
14
+ Tell an agent "write to Ada about the export" and it can find out, without being told again, who that is, where to reach her for this, what register to use, what to avoid, and where to record what it learns. The "how" is written down once, per person, and every agent, skill and tool reads it through the same verbs.
15
+
16
+ Records are hand-editable Markdown, one folder per person (or project, org, group), kept **outside any code repository**. Every preference carries its source, and `acquaint lint` fails when one does not.
17
+
18
+ ## What it records, and what it never does
19
+
20
+ A profile holds observable behaviour with evidence: "replies in one or two lines", "wants the decision first", quoted and linked. It never holds personality labels, moods, or special-category data (health, religion, politics, ethnicity, sexuality, union membership), stated or inferred, nor credentials, identifiers, or whole message bodies. Every store gets a `POLICY.md` stating this, and `lint` warns on tripwires.
21
+
22
+ The test for every line: would it survive being handed to the person it is about?
23
+
24
+ ## Where the data lives
25
+
26
+ The data root is the first of: the `data_dir` argument, `$ACQUAINT_DATA_DIR`, `data_dir` in `~/.config/acquaint/config.toml`, `~/.local/share/acquaint`.
27
+
28
+ ```
29
+ POLICY.md what may be recorded
30
+ people/ada-lovelace/
31
+ PROFILE.md entry file: identity frontmatter + Who · Reach · Write to them · Read them · Don't · Now · More
32
+ identities.yaml handles and addresses, with evidence
33
+ rules.yaml channel rules: when → do, who set it, source
34
+ links.yaml affiliations: project, org, group; role; period
35
+ style.md the writing card: AI tolerance, register, do, don't, blocklist, exemplars
36
+ views.md positions and standing objections, sourced
37
+ sources.md where their writing lives, how authorship was verified
38
+ log/2026-09.md append-only observations
39
+ projects/<id>/PROFILE.md What & status · Where things live · Who · Agents & skills · Norms · Now
40
+ orgs/<id>/ groups/<id>/ … any other kind
41
+ ```
42
+
43
+ Only `PROFILE.md` is required. A malformed file is reported and skipped; it never breaks lookups of anyone else.
44
+
45
+ ## The verbs
46
+
47
+ The same fifteen functions are the Python API (`acquaint.tools`), the CLI, and the MCP tools. Each returns a JSON-ready dict.
48
+
49
+ | Verb | Does |
50
+ |---|---|
51
+ | `who NAME [-f FIELD] [-b]` | one field, the identity block, or the whole entry file; lists candidates instead of guessing |
52
+ | `resolve HANDLE` | `github:octocat`, `email:…` → the person, with the evidence; a handle without its platform, a match by name only, or an inactive identity is reported, never acted on |
53
+ | `check TEXT` | before publishing: one person written as two ("Ada or Lovelace"), shared names, unknown names |
54
+ | `reach PERSON [--purpose --urgency --project --message-type --topic]` | ordered channels: the person's own rules > the operator's rules > project norms > observed habits > defaults |
55
+ | `brief PERSON [--purpose --project]` | card, writing style, views, reach, project norms, recent observations, reminders, and what is **not** known |
56
+ | `remember ENTITY TEXT [--source --kind]` | append a dated, sourced observation (or identity, preference, view, rule) |
57
+ | `lint [ENTITY]` | sources on every preference; parseable files; entry-file budget; policy tripwires |
58
+ | `style-lint TEXT [--recipient --tolerance]` | machine-writing tells, enforced by the reader's tolerance of AI-sounding text |
59
+ | `new KIND NAME [--qualifier --description]` | scaffold from a template; readable slug ids (`ada-lovelace`, `john-smith--example-org`) |
60
+ | `rename ID TO` | new id or name; links elsewhere rewritten (never inside URLs or logs); old forms kept as aliases |
61
+ | `forget ID [--confirm]` | remove the whole folder, leaving a salted tombstone so the person is not silently re-created |
62
+ | `sync init --repo OWNER/NAME` · `sync push` · `sync pull` · `sync status` | private-repository sync, below |
63
+
64
+ `--json` prints the result dict; `-` as the text of `check` or `style-lint` reads stdin.
65
+
66
+ Nothing acts on a guess: a name, alias, handle or email counts only when exactly one record has it exactly, and a partial match comes back as a suggestion. `rename` and `forget` need the exact id.
67
+
68
+ ## Sources
69
+
70
+ End every preference, view or rule with a source tag:
71
+
72
+ ```markdown
73
+ ## Write to them
74
+ - Lead with the decision, then the options. [source: self: "give me the recommendation first"]
75
+ - Short replies on chat, fuller ones by email. [source: log/2026-09.md#e03]
76
+ - No attachments over chat. [source: https://example.org/thread/1]
77
+ ```
78
+
79
+ A source is a permalink, a log or research anchor, the person's own words, `operator`, or `none located`. **A date alone is not a source**, and neither is a placeholder such as `unknown` or `TODO`: an unsourced claim with a date attached reads as observed when it was not. A nested bullet is its own line and needs its own source.
80
+
81
+ ## Agent skills
82
+
83
+ Six skills ship inside the package (`acquaint/data/skills/`) and install with `gh skill`:
84
+
85
+ | Skill | For |
86
+ |---|---|
87
+ | `acquaint` | the router: lookups at the right cost, `check` before publishing, `remember` with sources |
88
+ | `acquaint-profile` | building a profile from someone's own writing, with parallel `profile-reader` agents |
89
+ | `acquaint-write` | writing for a known reader, and sparring with a simulated one (`recipient-reader` agent) |
90
+ | `acquaint-read` | interpreting a message from a known person; AI-processing as a likelihood with evidence |
91
+ | `deslop` | prose without machine-writing tells, calibrated to the reader |
92
+ | `acquaint-sync` | private sync, and what it does not protect |
93
+
94
+ ```bash
95
+ gh skill install thorwhalen/acquaint acquaint --agent claude-code
96
+ ```
97
+
98
+ `gh skill` needs a recent `gh`; otherwise symlink the folders from the installed package's `acquaint/data/skills/` into `~/.claude/skills/`. If you already have a user-level skill named `deslop`, skip acquaint's or install it under another name: two skills with the same name at the same scope replace each other.
99
+
100
+ ## MCP
101
+
102
+ ```bash
103
+ pip install "acquaint[mcp]"
104
+ ```
105
+
106
+ ```json
107
+ {"mcpServers": {"acquaint": {"command": "acquaint-mcp"}}}
108
+ ```
109
+
110
+ The server exposes the tools that read locally, append or create (`who`, `resolve`, `check`, `reach`, `brief`, `remember`, `lint`, `new`, `style_lint`). Renaming, forgetting and syncing stay at the terminal. `data_dir` is not exposed: the data root is the server's (set `ACQUAINT_DATA_DIR` in the client configuration), never the model's.
111
+
112
+ ## Private sync
113
+
114
+ ```bash
115
+ acquaint sync init --repo <owner>/<name> --dry-run
116
+ acquaint sync init --repo <owner>/<name>
117
+ ```
118
+
119
+ `sync init` creates the repository through `gh` as private, refuses to continue unless `gh` reports it `PRIVATE`, and installs a pre-push hook, active in every worktree, that allows a push only through `origin`, only when `origin` has exactly the one URL recorded at init (no `pushurl` or `pushInsteadOf` redirect), only when that URL names the checked repository on github.com, and only while `gh` still reports it private. `push`, `pull` and `status` check again. `sync init` takes over only an empty folder or a clone of that same repository.
120
+
121
+ What this does **not** protect: a private repository is access control, not encryption (the host can read everything); `git push --no-verify` skips the hook; whoever controls the repository's git config or the `gh` on `PATH` controls what the guard sees; moving the data root disables the hook until `sync init --existing-only` runs again; file names and commit messages contain people's names; deleting a folder does not remove it from history or other clones. `git-remote-gcrypt` would encrypt contents, names and history; it is the planned upgrade, but the guard does not support it yet, so `sync init` refuses `gcrypt::` URLs ([#13](https://github.com/thorwhalen/acquaint/issues/13)).
122
+
123
+ ## Python
124
+
125
+ ```python
126
+ from acquaint import Store, who, brief
127
+
128
+ who("ada", field="aka")["value"] # ['Ada', 'Lovelace']
129
+ print(brief("ada-lovelace", purpose="ask")["text"])
130
+
131
+ store = Store() # MutableMapping[str, Entity] over a dol files store
132
+ entity = store[
133
+ "people/ada-lovelace"
134
+ ] # a mapping of that person's files, plus parsed views
135
+ entity.identities, entity.rules, entity.sections["Write to them"]
136
+ Store(files={}) # any MutableMapping[str, str] of files: a dict, a remote store
137
+ ```
138
+
139
+ ## Design
140
+
141
+ The seams, surfaces and deliberate non-seams are in [Discussion #1](https://github.com/thorwhalen/acquaint/discussions/1).
@@ -0,0 +1,63 @@
1
+ """acquaint: people, and what they are involved in, for AI agents.
2
+
3
+ Who someone is, how to reach them, how to read them, how to write to them, kept as
4
+ hand-editable Markdown with a source on every preference, outside any code repository.
5
+
6
+ The verbs are the same in Python, on the command line (``acquaint who ada -f aka``) and
7
+ over MCP::
8
+
9
+ >>> from acquaint import new, remember, who, brief # doctest: +SKIP
10
+ >>> new("person", "Ada Lovelace") # doctest: +SKIP
11
+ >>> remember("ada-lovelace", "prefers email for anything with attachments",
12
+ ... source="https://example.org/thread/1") # doctest: +SKIP
13
+ >>> who("ada", field="aka")["value"] # doctest: +SKIP
14
+ ['Ada', 'Lovelace']
15
+
16
+ For library use, :class:`Store` is a ``MutableMapping`` of entities over any mapping of
17
+ files (a ``dol`` files store by default).
18
+ """
19
+
20
+ from acquaint.store import AcquaintError, Entity, Store, data_dir
21
+ from acquaint.tools import (
22
+ SIDE_EFFECTS,
23
+ TOOLS,
24
+ brief,
25
+ check,
26
+ forget,
27
+ lint,
28
+ new,
29
+ reach,
30
+ remember,
31
+ rename,
32
+ resolve,
33
+ style_lint,
34
+ sync_init,
35
+ sync_pull,
36
+ sync_push,
37
+ sync_status,
38
+ who,
39
+ )
40
+
41
+ __all__ = [
42
+ "AcquaintError",
43
+ "Entity",
44
+ "SIDE_EFFECTS",
45
+ "Store",
46
+ "TOOLS",
47
+ "brief",
48
+ "check",
49
+ "data_dir",
50
+ "forget",
51
+ "lint",
52
+ "new",
53
+ "reach",
54
+ "remember",
55
+ "rename",
56
+ "resolve",
57
+ "style_lint",
58
+ "sync_init",
59
+ "sync_pull",
60
+ "sync_push",
61
+ "sync_status",
62
+ "who",
63
+ ]
@@ -0,0 +1,73 @@
1
+ # PYTHON_ARGCOMPLETE_OK
2
+ """``acquaint`` on the command line: ``cw`` over :data:`acquaint.tools.TOOLS`, with ``sync_*`` as a ``sync`` group.
3
+
4
+ ``--json`` anywhere prints the tool's result dict instead of text; ``-`` as the text
5
+ of ``check`` or ``style-lint`` reads it from stdin.
6
+ """
7
+
8
+ import functools
9
+ import json
10
+ import sys
11
+
12
+ import cw
13
+
14
+ from acquaint import tools
15
+ from acquaint.render import render
16
+
17
+
18
+ def _command(func):
19
+ @functools.wraps(func)
20
+ def command(*args, **kwargs):
21
+ try:
22
+ return func(*args, **kwargs)
23
+ except (tools.AcquaintError, ValueError) as error:
24
+ raise cw.CommandError(str(error)) from error
25
+
26
+ return command
27
+
28
+
29
+ def _egress(as_json):
30
+ def egress(result, *, out, err):
31
+ if as_json:
32
+ print(json.dumps(result, indent=2, ensure_ascii=False), file=out)
33
+ return 0 if result.get("ok", True) else 1
34
+ stdout, stderr, code = render(result)
35
+ print(stderr, file=err) if stderr else None
36
+ print(stdout, file=out) if stdout else None
37
+ return code
38
+
39
+ return egress
40
+
41
+
42
+ def main(argv=None):
43
+ for stream in (sys.stdout, sys.stderr): # a cp1252 pipe must not crash on "·" or "→"
44
+ getattr(stream, "reconfigure", lambda **_: None)(errors="backslashreplace")
45
+ argv = list(sys.argv[1:] if argv is None else argv)
46
+ as_json = "--json" in argv
47
+ commands = {
48
+ f.__name__.replace("_", "-"): _command(f)
49
+ for f in tools.TOOLS
50
+ if not f.__name__.startswith("sync_")
51
+ }
52
+ commands["sync"] = {
53
+ f.__name__.removeprefix("sync_"): _command(f)
54
+ for f in tools.TOOLS
55
+ if f.__name__.startswith("sync_")
56
+ }
57
+ stdin = {"text": {"codec": lambda text: sys.stdin.read() if text == "-" else text}}
58
+ config = {"check": stdin, "style-lint": stdin}
59
+ args = [a for a in argv if a != "--json"]
60
+ raise SystemExit(
61
+ cw.dispatch(
62
+ commands,
63
+ args,
64
+ prog="acquaint",
65
+ convention=cw.MODERN,
66
+ egress=_egress(as_json),
67
+ config=config,
68
+ )
69
+ )
70
+
71
+
72
+ if __name__ == "__main__":
73
+ main()