@hasna/contacts 0.6.36 → 0.8.1
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.
- package/README.md +123 -69
- package/dist/cli/commands/advanced.d.ts.map +1 -1
- package/dist/cli/commands/core.d.ts +1 -1
- package/dist/cli/commands/core.d.ts.map +1 -1
- package/dist/cli/commands/crm.d.ts.map +1 -1
- package/dist/cli/index.js +9206 -39957
- package/dist/cli/legacy.d.ts +18 -0
- package/dist/cli/legacy.d.ts.map +1 -0
- package/dist/cli/status-domain.preload.d.ts +2 -0
- package/dist/cli/status-domain.preload.d.ts.map +1 -0
- package/dist/cli/storage.d.ts.map +1 -1
- package/dist/cloud/client-config.d.ts +6 -0
- package/dist/cloud/client-config.d.ts.map +1 -0
- package/dist/cloud/http-storage.d.ts +49 -70
- package/dist/cloud/http-storage.d.ts.map +1 -1
- package/dist/cloud/resolver-inputs.d.ts +51 -0
- package/dist/cloud/resolver-inputs.d.ts.map +1 -0
- package/dist/db/paths.d.ts +13 -7
- package/dist/db/paths.d.ts.map +1 -1
- package/dist/generated/storage-kit/backend.d.ts +19 -0
- package/dist/generated/storage-kit/backend.d.ts.map +1 -0
- package/dist/generated/storage-kit/index.d.ts +2 -2
- package/dist/generated/storage-kit/index.d.ts.map +1 -1
- package/dist/generated/storage-kit/migrations.d.ts +21 -0
- package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
- package/dist/generated/storage-kit/own.d.ts +11 -0
- package/dist/generated/storage-kit/own.d.ts.map +1 -0
- package/dist/generated/storage-kit/pool.d.ts +5 -7
- package/dist/generated/storage-kit/pool.d.ts.map +1 -1
- package/dist/generated/storage-kit/query.d.ts +1 -1
- package/dist/generated/storage-kit/query.d.ts.map +1 -1
- package/dist/generated/storage-kit/tls.d.ts +30 -3
- package/dist/generated/storage-kit/tls.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +454 -5713
- package/dist/lib/images.d.ts.map +1 -1
- package/dist/mcp/handlers/advanced.d.ts.map +1 -1
- package/dist/mcp/handlers/core.d.ts +1 -1
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +549 -5895
- package/dist/mcp/startup-gate.d.ts +48 -0
- package/dist/mcp/startup-gate.d.ts.map +1 -0
- package/dist/mcp/storage-tools.d.ts.map +1 -1
- package/dist/sdk/index.d.ts +80 -11
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +1028 -2
- package/dist/server/cloud.d.ts +6 -7
- package/dist/server/cloud.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3892 -35295
- package/dist/server/security.d.ts +1 -1
- package/dist/server/security.d.ts.map +1 -1
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/store/index.d.ts +66 -93
- package/dist/store/index.d.ts.map +1 -1
- package/dist/types/store-dto.d.ts +273 -0
- package/dist/types/store-dto.d.ts.map +1 -0
- package/hasna.contract.json +77 -10
- package/package.json +22 -13
- package/dashboard/dist/assets/index-0l6aQb1t.css +0 -1
- package/dashboard/dist/assets/index-opnZdkVD.js +0 -229
- package/dashboard/dist/index.html +0 -13
- package/dist/generated/storage-kit/mode.d.ts +0 -48
- package/dist/generated/storage-kit/mode.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -7
- package/dist/lib/config.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hasna/contacts
|
|
2
2
|
|
|
3
|
-
Contact management for AI coding agents — CLI + MCP +
|
|
3
|
+
Contact management for AI coding agents — CLI + MCP + authenticated HTTP API
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@hasna/contacts)
|
|
6
6
|
[](LICENSE)
|
|
@@ -11,12 +11,92 @@ Contact management for AI coding agents — CLI + MCP + Web
|
|
|
11
11
|
npm install -g @hasna/contacts
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
+
## Configure the client
|
|
15
|
+
|
|
16
|
+
Every CLI, MCP, and package data operation uses one authenticated HTTPS `/v1`
|
|
17
|
+
authority resolved by the shared `@hasna/contracts` client chain
|
|
18
|
+
(hasna/apps#1720), resolved fresh on every request. There is no local database
|
|
19
|
+
fallback and no per-app env chain of its own.
|
|
20
|
+
|
|
21
|
+
Once a contacts API key resolves from any tier, the authority defaults to the
|
|
22
|
+
fleet gateway `https://api.hasna.com/contacts` — no URL configuration is
|
|
23
|
+
needed on a station. The credential tiers, in order:
|
|
24
|
+
|
|
25
|
+
1. explicit arguments / deliberate pointers — `HASNA_CONTACTS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_CONTACTS_API_KEY_REF` (secrets vault)
|
|
26
|
+
2. macOS Keychain — `hasna.credentials.contacts.api-key` / `.api-url`, account `HASNA_STATION` → `hostname -s` → `$USER`
|
|
27
|
+
3. disk — `~/.hasna/contacts/config/credentials` (owner-only 0400/0600, `HASNA_CONTACTS_API_KEY=…` or the `HASNA_CONTACTS_API_URL=…` alias)
|
|
28
|
+
4. environment — `HASNA_CONTACTS_API_KEY`
|
|
29
|
+
|
|
30
|
+
| Env var | Meaning |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `HASNA_CONTACTS_API_URL` | Explicit API base URL (HTTPS; overrides the fleet gateway). Legacy alias: `CONTACTS_API_URL`. |
|
|
33
|
+
| `HASNA_CONTACTS_API_KEY` | API key (env tier). Legacy alias: `CONTACTS_API_KEY`. |
|
|
34
|
+
| `HASNA_CONTACTS_API_KEY_OVERRIDE` | Deliberate per-run override that outranks the Keychain and disk. |
|
|
35
|
+
| `HASNA_CONTACTS_API_KEY_REF` | Secrets-vault pointer (`namespace/app/live/api_key`); terminal when unresolvable. |
|
|
36
|
+
| `HASNA_PROFILE` | Selects which identity (`credentials-<profile>`) the disk tier reads. |
|
|
37
|
+
| `HASNA_STATION` | Keychain account when set; else short hostname, then `$USER`. |
|
|
38
|
+
| `HASNA_HOME` | Replaces `~` for the `~/.hasna/…` credential/disk root. |
|
|
39
|
+
| `HASNA_CONFIG_HOME` | Replaces `~/.hasna/<app>/config` entirely. |
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Fully explicit:
|
|
43
|
+
export HASNA_CONTACTS_API_URL="https://contacts.example.com"
|
|
44
|
+
export HASNA_CONTACTS_API_KEY="…" # or configure the Keychain/disk tiers
|
|
45
|
+
contacts connection --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
An absent URL and key fails closed: operations exit non-zero and never open a
|
|
49
|
+
local store; `contacts connection` reports `transport: "unconfigured"`.
|
|
50
|
+
The fail-closed message starts on the FIRST stderr line — it names what is
|
|
51
|
+
missing and where the credential should live (`HASNA_CONTACTS_API_KEY`, the
|
|
52
|
+
Keychain item, the credentials-file path), never a value.
|
|
53
|
+
`HASNA_CONTACTS_STORAGE_MODE`, `CONTACTS_STORAGE_MODE`, contacts DB-path
|
|
54
|
+
variables, and contacts database URLs are rejected in client processes.
|
|
55
|
+
PostgreSQL URLs belong only to `contacts-serve` and the migration task.
|
|
56
|
+
|
|
14
57
|
## CLI Usage
|
|
15
58
|
|
|
16
59
|
```bash
|
|
60
|
+
contacts status # CLI version, resolved /v1 authority + sources, storage mode, record counts
|
|
61
|
+
contacts status --json
|
|
17
62
|
contacts --help
|
|
18
63
|
```
|
|
19
64
|
|
|
65
|
+
`contacts status` reports the authority the shared resolver actually decided
|
|
66
|
+
(`api`, the `/v1` base URL) and where each half came from — `api_url_source`,
|
|
67
|
+
`api_key_source`, `api_key_tier`: an env key name, a Keychain item reference,
|
|
68
|
+
a credentials-file path, or `default` for the fleet gateway; never a value.
|
|
69
|
+
It answers even on a box without an API key: an unconfigured client reports
|
|
70
|
+
storage `unconfigured` with the resolver's `issue` (a failed request on a
|
|
71
|
+
configured box reports storage `error` with the failure message) instead of
|
|
72
|
+
crashing, so agents can observe the configuration drift the command exists to
|
|
73
|
+
expose. `status` and `connection` are diagnostics and exit 0 with that report;
|
|
74
|
+
every data verb fails closed (non-zero exit, no local store).
|
|
75
|
+
|
|
76
|
+
## SDK
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { createContactsClient, ContactsV1Client } from "@hasna/contacts/sdk";
|
|
80
|
+
|
|
81
|
+
// Through the fleet resolver — the same @hasna/contracts chain the CLI and MCP
|
|
82
|
+
// server use: credential and authority resolved at construction, the key
|
|
83
|
+
// re-resolved on every request, the authority pinned. Nothing resolving throws.
|
|
84
|
+
const client = createContactsClient();
|
|
85
|
+
const { contacts } = await client.listContacts();
|
|
86
|
+
|
|
87
|
+
// Explicit pin: a caller-supplied baseUrl always requires a caller-supplied
|
|
88
|
+
// apiKey — the SDK never attaches an ambient credential to it.
|
|
89
|
+
const pinned = new ContactsV1Client({ baseUrl: "https://contacts.example.com", apiKey: "…" });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`createContactsClient()` resolves the literal tiers only — an explicit
|
|
93
|
+
`apiKey`, `HASNA_CONTACTS_API_KEY_OVERRIDE`, the Keychain item, the
|
|
94
|
+
credentials file, `HASNA_CONTACTS_API_KEY`. The secrets-vault pointer
|
|
95
|
+
`HASNA_CONTACTS_API_KEY_REF` is refused with
|
|
96
|
+
`CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED` (the SDK resolves synchronously per
|
|
97
|
+
request and cannot complete a vault lookup); the CLI and MCP server accept it.
|
|
98
|
+
|
|
99
|
+
|
|
20
100
|
## Audiences, consent, and suppression
|
|
21
101
|
|
|
22
102
|
Audience segments implement the `hasna.audience.v1` contract (distribution
|
|
@@ -50,6 +130,22 @@ suppressed addresses; the audience `--policy` (`opt_in`, `opt_out`,
|
|
|
50
130
|
contacts-mcp
|
|
51
131
|
```
|
|
52
132
|
|
|
133
|
+
`contacts-mcp` fails closed at startup: with no credential resolvable through
|
|
134
|
+
the chain above it exits non-zero BEFORE the stdio transport is connected or
|
|
135
|
+
the HTTP port is bound — an `initialize` request is never answered by an
|
|
136
|
+
unauthenticated server — and creates nothing under the app home. The first
|
|
137
|
+
stderr line names where the credential should live (the Keychain item, the
|
|
138
|
+
credentials-file path, `HASNA_CONTACTS_API_KEY`), never a value.
|
|
139
|
+
`--help` / `--version` answer ahead of the gate; every tool still re-resolves
|
|
140
|
+
the credential per request once the server is up.
|
|
141
|
+
|
|
142
|
+
The deliberate tiers are honoured at the gate as well. The secrets-vault
|
|
143
|
+
pointer `HASNA_CONTACTS_API_KEY_REF` is dereferenced once at startup (the
|
|
144
|
+
chain alone only checks the pointer's shape): a pointer the vault cannot
|
|
145
|
+
complete, a `HASNA_PROFILE` whose credentials file is missing, or an unsafe
|
|
146
|
+
(not owner-only) credentials file is a one-line refusal naming the pointer,
|
|
147
|
+
the file, or the item — never resolved around, never a stack trace.
|
|
148
|
+
|
|
53
149
|
## HTTP mode
|
|
54
150
|
|
|
55
151
|
Long-lived Streamable HTTP transport (stateless, bind `127.0.0.1` only):
|
|
@@ -63,7 +159,7 @@ MCP_HTTP=1 contacts-mcp
|
|
|
63
159
|
- Health: `GET http://127.0.0.1:8809/health`
|
|
64
160
|
- MCP: `http://127.0.0.1:8809/mcp`
|
|
65
161
|
|
|
66
|
-
The REST server
|
|
162
|
+
The REST server is a separate authenticated `/v1` surface.
|
|
67
163
|
|
|
68
164
|
## REST API
|
|
69
165
|
|
|
@@ -72,83 +168,41 @@ contacts-serve
|
|
|
72
168
|
```
|
|
73
169
|
|
|
74
170
|
`contacts-serve` binds to `127.0.0.1` by default. Use `--host <host>` or
|
|
75
|
-
`CONTACTS_HOST=<host>` only when intentionally exposing it beyond loopback.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
by default and only activates when all of the following are true:
|
|
79
|
-
|
|
80
|
-
- no contacts API token environment variable is configured
|
|
81
|
-
- `CONTACTS_ALLOW_UNAUTHENTICATED_LOOPBACK=1`
|
|
82
|
-
- the server is explicitly bound to a loopback host
|
|
83
|
-
|
|
84
|
-
The fallback does not trust the HTTP `Host` header.
|
|
171
|
+
`CONTACTS_HOST=<host>` only when intentionally exposing it beyond loopback. The
|
|
172
|
+
server requires PostgreSQL configuration and API-key signing configuration;
|
|
173
|
+
readiness fails closed when either is unavailable.
|
|
85
174
|
|
|
86
|
-
## Storage
|
|
175
|
+
## Storage and legacy data
|
|
87
176
|
|
|
88
|
-
|
|
89
|
-
|
|
177
|
+
The client never opens SQLite or PostgreSQL. The server owns PostgreSQL and all
|
|
178
|
+
authoritative data remains server-side. Inspect the value-free connection state
|
|
179
|
+
with:
|
|
90
180
|
|
|
91
181
|
```bash
|
|
92
|
-
contacts
|
|
93
|
-
contacts storage status --json
|
|
94
|
-
contacts storage push --tables contacts,companies
|
|
95
|
-
contacts storage pull --tables contacts,companies
|
|
96
|
-
contacts storage sync
|
|
182
|
+
contacts connection --json
|
|
97
183
|
```
|
|
98
184
|
|
|
99
|
-
|
|
185
|
+
Retired local databases are never auto-adopted or silently ignored. The
|
|
186
|
+
explicit migration aid only inspects and copies them; it never opens, changes,
|
|
187
|
+
deletes, or selects one as the live store:
|
|
100
188
|
|
|
101
189
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
190
|
+
contacts legacy inspect --json
|
|
191
|
+
contacts legacy preserve --source /exact/path/contacts.db \
|
|
192
|
+
--output /existing/directory/contacts.db.pre-https.20260901
|
|
104
193
|
```
|
|
105
194
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
Shared REST/dashboard deployments must set one of
|
|
118
|
-
`HASNA_CONTACTS_API_TOKENS`, `OPEN_CONTACTS_API_TOKENS`, or
|
|
119
|
-
`CONTACTS_API_TOKENS`. Values are comma-separated `token=scope scope` records.
|
|
120
|
-
Supported scopes include `contacts:read`, `contacts:write`,
|
|
121
|
-
`contacts:import`, `contacts:export`, `contacts:export:full`,
|
|
122
|
-
`documents:read`, `images:read`, `images:write`, `companies:*`, `tags:*`,
|
|
123
|
-
`stats:read`, `dashboard:read`, and `mcp:access`. Loopback-only development
|
|
124
|
-
without configured tokens remains allowed; shared hosts fail closed.
|
|
125
|
-
|
|
126
|
-
Server exports are redacted by default: email addresses, phone numbers,
|
|
127
|
-
addresses, notes, birthdays, social profiles, and custom fields are withheld.
|
|
128
|
-
Full exports require both `contacts:export` and `contacts:export:full`. Export,
|
|
129
|
-
import, document file read, image mutation, contact mutation, company mutation,
|
|
130
|
-
and tag mutation routes write audit entries. Document attachments are served
|
|
131
|
-
only from the managed contacts documents directory and use private `no-store`
|
|
132
|
-
cache headers.
|
|
133
|
-
|
|
134
|
-
`contacts cloud status`, `contacts cloud push`, `contacts cloud pull`, and
|
|
135
|
-
`contacts cloud sync` remain compatibility aliases for the contacts-owned
|
|
136
|
-
storage commands. They do not load or depend on the deprecated shared cloud
|
|
137
|
-
runtime. `contacts cloud feedback` saves feedback locally in the contacts
|
|
138
|
-
database.
|
|
139
|
-
|
|
140
|
-
## Data Directory
|
|
141
|
-
|
|
142
|
-
On-box data resolves through the XDG dirs per `@hasna/paths`:
|
|
143
|
-
|
|
144
|
-
- documents, the SQLite store, `config.json` and the vault config live in the
|
|
145
|
-
**XDG data root**: `~/.local/share/hasna/contacts/` (or `$HASNA_DATA_HOME/contacts`).
|
|
146
|
-
- the vault session state lives in the **XDG state root**:
|
|
147
|
-
`~/.local/state/hasna/contacts/` (or `$HASNA_STATE_HOME/contacts`).
|
|
148
|
-
|
|
149
|
-
The legacy `~/.hasna/contacts/` home is never written to; on first use any
|
|
150
|
-
pre-existing legacy store is adopted into the XDG roots (never clobbering an
|
|
151
|
-
existing XDG store).
|
|
195
|
+
If a SQLite WAL, journal, or shared-memory sidecar is present, preservation
|
|
196
|
+
refuses to proceed until every legacy process is stopped and the old client has checkpointed the database. To move
|
|
197
|
+
portable contact records, use a legacy release against the preserved copy to
|
|
198
|
+
export JSON, then run `contacts import exported.json` with the HTTPS client
|
|
199
|
+
configured. Existing files are never overwritten.
|
|
200
|
+
|
|
201
|
+
Preservation rejects source, ancestor, or output replacement races and verifies
|
|
202
|
+
the copied bytes with SHA-256 and stable output metadata before success. If a copy
|
|
203
|
+
fails after output creation, any private partial output is left untouched and
|
|
204
|
+
reported as unverified for manual inspection; the command never deletes a
|
|
205
|
+
pathname that another process might have replaced.
|
|
152
206
|
|
|
153
207
|
## License
|
|
154
208
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/advanced.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"advanced.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/advanced.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBzC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAq5B/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/core.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqBzC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAu5B5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crm.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/crm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"crm.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/crm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAi+B1D"}
|