@hasna/contacts 0.7.0 → 0.9.0

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 (85) hide show
  1. package/README.md +93 -13
  2. package/dist/browser/cli.d.ts +4 -0
  3. package/dist/browser/cli.d.ts.map +1 -0
  4. package/dist/browser/identity.d.ts +3 -0
  5. package/dist/browser/identity.d.ts.map +1 -0
  6. package/dist/browser/install.d.ts +32 -0
  7. package/dist/browser/install.d.ts.map +1 -0
  8. package/dist/browser/native.d.ts +3 -0
  9. package/dist/browser/native.d.ts.map +1 -0
  10. package/dist/browser/native.js +822 -0
  11. package/dist/browser/protocol.d.ts +26 -0
  12. package/dist/browser/protocol.d.ts.map +1 -0
  13. package/dist/browser/values.d.ts +18 -0
  14. package/dist/browser/values.d.ts.map +1 -0
  15. package/dist/cli/commands/core.d.ts.map +1 -1
  16. package/dist/cli/index.js +479 -149
  17. package/dist/cli/status-domain.preload.d.ts +1 -1
  18. package/dist/cli/status-domain.preload.d.ts.map +1 -1
  19. package/dist/cli/status-fixture.d.ts +17 -0
  20. package/dist/cli/status-fixture.d.ts.map +1 -0
  21. package/dist/cloud/http-storage.d.ts +20 -12
  22. package/dist/cloud/http-storage.d.ts.map +1 -1
  23. package/dist/cloud/resolver-inputs.d.ts +51 -0
  24. package/dist/cloud/resolver-inputs.d.ts.map +1 -0
  25. package/dist/db/paths.d.ts +4 -4
  26. package/dist/db/paths.d.ts.map +1 -1
  27. package/dist/generated/storage-kit/backend.d.ts +19 -0
  28. package/dist/generated/storage-kit/backend.d.ts.map +1 -0
  29. package/dist/generated/storage-kit/index.d.ts +2 -1
  30. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  31. package/dist/generated/storage-kit/migrations.d.ts +21 -0
  32. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  33. package/dist/generated/storage-kit/own.d.ts +11 -0
  34. package/dist/generated/storage-kit/own.d.ts.map +1 -0
  35. package/dist/generated/storage-kit/pool.d.ts +5 -17
  36. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  37. package/dist/generated/storage-kit/query.d.ts +1 -1
  38. package/dist/generated/storage-kit/query.d.ts.map +1 -1
  39. package/dist/generated/storage-kit/tls.d.ts +30 -3
  40. package/dist/generated/storage-kit/tls.d.ts.map +1 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +148 -28
  44. package/dist/lib/audience-contract.d.ts +9 -9
  45. package/dist/lib/compact-output.d.ts +67 -0
  46. package/dist/lib/compact-output.d.ts.map +1 -0
  47. package/dist/mcp/handlers/core.d.ts.map +1 -1
  48. package/dist/mcp/index.d.ts +11 -1
  49. package/dist/mcp/index.d.ts.map +1 -1
  50. package/dist/mcp/index.js +490 -212
  51. package/dist/mcp/profile.d.ts +8 -0
  52. package/dist/mcp/profile.d.ts.map +1 -0
  53. package/dist/mcp/register-tools.d.ts +3 -1
  54. package/dist/mcp/register-tools.d.ts.map +1 -1
  55. package/dist/mcp/startup-gate.d.ts +48 -0
  56. package/dist/mcp/startup-gate.d.ts.map +1 -0
  57. package/dist/mcp/storage-tools.d.ts +2 -1
  58. package/dist/mcp/storage-tools.d.ts.map +1 -1
  59. package/dist/mcp/tools.d.ts +7 -45
  60. package/dist/mcp/tools.d.ts.map +1 -1
  61. package/dist/sdk/index.d.ts +70 -3
  62. package/dist/sdk/index.d.ts.map +1 -1
  63. package/dist/sdk/index.js +990 -7
  64. package/dist/server/index.js +231 -59
  65. package/dist/server/pg-store.d.ts +1 -0
  66. package/dist/server/pg-store.d.ts.map +1 -1
  67. package/dist/store/index.d.ts.map +1 -1
  68. package/docs/chrome-autofill.md +45 -0
  69. package/extension/background.js +284 -0
  70. package/extension/content.js +387 -0
  71. package/extension/detect.js +228 -0
  72. package/extension/fill.js +112 -0
  73. package/extension/icons/128.png +0 -0
  74. package/extension/icons/16.png +0 -0
  75. package/extension/icons/32.png +0 -0
  76. package/extension/icons/48.png +0 -0
  77. package/extension/icons/icon.svg +17 -0
  78. package/extension/manifest.json +54 -0
  79. package/extension/popup.css +78 -0
  80. package/extension/popup.html +47 -0
  81. package/extension/popup.js +166 -0
  82. package/hasna.contract.json +20 -6
  83. package/package.json +10 -6
  84. package/dist/lib/config.d.ts +0 -7
  85. package/dist/lib/config.d.ts.map +0 -1
package/README.md CHANGED
@@ -11,36 +11,100 @@ Contact management for AI coding agents — CLI + MCP + authenticated HTTP API
11
11
  npm install -g @hasna/contacts
12
12
  ```
13
13
 
14
+ ## Chrome extension
15
+
16
+ The package ships a Chrome extension that fills name, email, phone, company
17
+ and address fields from your contacts — from the toolbar popup or from the
18
+ Contacts badge that appears inside detected form fields. It uses the same
19
+ hosted API and credential as the CLI. Run `contacts browser install`, load the
20
+ printed `extensionPath` unpacked in `chrome://extensions`, and see
21
+ [docs/chrome-autofill.md](docs/chrome-autofill.md) for how detection, the
22
+ picker and the security boundaries work.
23
+
14
24
  ## Configure the client
15
25
 
16
26
  Every CLI, MCP, and package data operation uses one authenticated HTTPS `/v1`
17
- authority. There is no built-in hosted URL and no local database fallback.
27
+ authority resolved by the shared `@hasna/contracts` client chain
28
+ (hasna/apps#1720), resolved fresh on every request. There is no local database
29
+ fallback and no per-app env chain of its own.
30
+
31
+ Once a contacts API key resolves from any tier, the authority defaults to the
32
+ fleet gateway `https://api.hasna.com/contacts` — no URL configuration is
33
+ needed on a station. The credential tiers, in order:
34
+
35
+ 1. explicit arguments / deliberate pointers — `HASNA_CONTACTS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_CONTACTS_API_KEY_REF` (secrets vault)
36
+ 2. macOS Keychain — `hasna.credentials.contacts.api-key` / `.api-url`, account `HASNA_STATION` → `hostname -s` → `$USER`
37
+ 3. disk — `~/.hasna/contacts/config/credentials` (owner-only 0400/0600, `HASNA_CONTACTS_API_KEY=…` or the `HASNA_CONTACTS_API_URL=…` alias)
38
+ 4. environment — `HASNA_CONTACTS_API_KEY`
39
+
40
+ | Env var | Meaning |
41
+ |---|---|
42
+ | `HASNA_CONTACTS_API_URL` | Explicit API base URL (HTTPS; overrides the fleet gateway). Legacy alias: `CONTACTS_API_URL`. |
43
+ | `HASNA_CONTACTS_API_KEY` | API key (env tier). Legacy alias: `CONTACTS_API_KEY`. |
44
+ | `HASNA_CONTACTS_API_KEY_OVERRIDE` | Deliberate per-run override that outranks the Keychain and disk. |
45
+ | `HASNA_CONTACTS_API_KEY_REF` | Secrets-vault pointer (`namespace/app/live/api_key`); terminal when unresolvable. |
46
+ | `HASNA_PROFILE` | Selects which identity (`credentials-<profile>`) the disk tier reads. |
47
+ | `HASNA_STATION` | Keychain account when set; else short hostname, then `$USER`. |
48
+ | `HASNA_HOME` | Replaces `~` for the `~/.hasna/…` credential/disk root. |
49
+ | `HASNA_CONFIG_HOME` | Replaces `~/.hasna/<app>/config` entirely. |
18
50
 
19
51
  ```bash
52
+ # Fully explicit:
20
53
  export HASNA_CONTACTS_API_URL="https://contacts.example.com"
21
- # Provision the API key through the @hasna/contracts credential chain. For
22
- # example, put HASNA_CONTACTS_API_KEY in the shared fleet/config credential chain,
23
- # or configure HASNA_CONTACTS_API_KEY_REF for the secrets client.
54
+ export HASNA_CONTACTS_API_KEY="…" # or configure the Keychain/disk tiers
24
55
  contacts connection --json
25
56
  ```
26
57
 
27
- An absent or invalid URL/key fails closed. `HASNA_CONTACTS_STORAGE_MODE`,
28
- `CONTACTS_STORAGE_MODE`, contacts DB-path variables, and contacts database URLs
29
- are rejected in client processes. PostgreSQL URLs belong only to
30
- `contacts-serve` and the migration task.
58
+ An absent URL and key fails closed: operations exit non-zero and never open a
59
+ local store; `contacts connection` reports `transport: "unconfigured"`.
60
+ The fail-closed message starts on the FIRST stderr line — it names what is
61
+ missing and where the credential should live (`HASNA_CONTACTS_API_KEY`, the
62
+ Keychain item, the credentials-file path), never a value.
63
+ `HASNA_CONTACTS_STORAGE_MODE`, `CONTACTS_STORAGE_MODE`, contacts DB-path
64
+ variables, and contacts database URLs are rejected in client processes.
65
+ PostgreSQL URLs belong only to `contacts-serve` and the migration task.
31
66
 
32
67
  ## CLI Usage
33
68
 
34
69
  ```bash
35
- contacts status # CLI version, API endpoint, storage mode, record counts
70
+ contacts status # CLI version, resolved /v1 authority + sources, storage mode, record counts
36
71
  contacts status --json
37
72
  contacts --help
38
73
  ```
39
74
 
40
- `contacts status` answers even on a box without an API key: an unconfigured
41
- client reports storage `unconfigured` (a failed request on a configured box
42
- reports storage `error` with the failure message) instead of crashing, so
43
- agents can observe the configuration drift the command exists to expose.
75
+ `contacts status` reports the authority the shared resolver actually decided
76
+ (`api`, the `/v1` base URL) and where each half came from — `api_url_source`,
77
+ `api_key_source`, `api_key_tier`: an env key name, a Keychain item reference,
78
+ a credentials-file path, or `default` for the fleet gateway; never a value.
79
+ It answers even on a box without an API key: an unconfigured client reports
80
+ storage `unconfigured` with the resolver's `issue` (a failed request on a
81
+ configured box reports storage `error` with the failure message) instead of
82
+ crashing, so agents can observe the configuration drift the command exists to
83
+ expose. `status` and `connection` are diagnostics and exit 0 with that report;
84
+ every data verb fails closed (non-zero exit, no local store).
85
+
86
+ ## SDK
87
+
88
+ ```ts
89
+ import { createContactsClient, ContactsV1Client } from "@hasna/contacts/sdk";
90
+
91
+ // Through the fleet resolver — the same @hasna/contracts chain the CLI and MCP
92
+ // server use: credential and authority resolved at construction, the key
93
+ // re-resolved on every request, the authority pinned. Nothing resolving throws.
94
+ const client = createContactsClient();
95
+ const { contacts } = await client.listContacts();
96
+
97
+ // Explicit pin: a caller-supplied baseUrl always requires a caller-supplied
98
+ // apiKey — the SDK never attaches an ambient credential to it.
99
+ const pinned = new ContactsV1Client({ baseUrl: "https://contacts.example.com", apiKey: "…" });
100
+ ```
101
+
102
+ `createContactsClient()` resolves the literal tiers only — an explicit
103
+ `apiKey`, `HASNA_CONTACTS_API_KEY_OVERRIDE`, the Keychain item, the
104
+ credentials file, `HASNA_CONTACTS_API_KEY`. The secrets-vault pointer
105
+ `HASNA_CONTACTS_API_KEY_REF` is refused with
106
+ `CONTACTS_CREDENTIAL_POINTER_UNSUPPORTED` (the SDK resolves synchronously per
107
+ request and cannot complete a vault lookup); the CLI and MCP server accept it.
44
108
 
45
109
 
46
110
  ## Audiences, consent, and suppression
@@ -76,6 +140,22 @@ suppressed addresses; the audience `--policy` (`opt_in`, `opt_out`,
76
140
  contacts-mcp
77
141
  ```
78
142
 
143
+ `contacts-mcp` fails closed at startup: with no credential resolvable through
144
+ the chain above it exits non-zero BEFORE the stdio transport is connected or
145
+ the HTTP port is bound — an `initialize` request is never answered by an
146
+ unauthenticated server — and creates nothing under the app home. The first
147
+ stderr line names where the credential should live (the Keychain item, the
148
+ credentials-file path, `HASNA_CONTACTS_API_KEY`), never a value.
149
+ `--help` / `--version` answer ahead of the gate; every tool still re-resolves
150
+ the credential per request once the server is up.
151
+
152
+ The deliberate tiers are honoured at the gate as well. The secrets-vault
153
+ pointer `HASNA_CONTACTS_API_KEY_REF` is dereferenced once at startup (the
154
+ chain alone only checks the pointer's shape): a pointer the vault cannot
155
+ complete, a `HASNA_PROFILE` whose credentials file is missing, or an unsafe
156
+ (not owner-only) credentials file is a one-line refusal naming the pointer,
157
+ the file, or the item — never resolved around, never a stack trace.
158
+
79
159
  ## HTTP mode
80
160
 
81
161
  Long-lived Streamable HTTP transport (stateless, bind `127.0.0.1` only):
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ /** `contacts browser …` — install and inspect the Chrome extension bridge. JSON output only. */
3
+ export declare function registerBrowserCommands(program: Command): void;
4
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/browser/cli.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIzC,gGAAgG;AAChG,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsB9D"}
@@ -0,0 +1,3 @@
1
+ export declare const EXTENSION_ID = "ceegpmbcoiomonopbhpifdalooccfgjj";
2
+ export declare const HOST_NAME = "com.hasna.contacts";
3
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/browser/identity.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,YAAY,qCAAqC,CAAC;AAC/D,eAAO,MAAM,SAAS,uBAAuB,CAAC"}
@@ -0,0 +1,32 @@
1
+ export declare function hostDirectories(home?: string, userDataDir?: string): string[];
2
+ export interface InstallOptions {
3
+ userDataDir?: string;
4
+ /** Test seam: the home directory that owns ~/.hasna/contacts and the Chrome profiles. */
5
+ home?: string;
6
+ /** Test seam: the package root that holds extension/ and dist/. */
7
+ root?: string;
8
+ /** Test seam: the absolute runtime used in the host shebang. */
9
+ execPath?: string;
10
+ }
11
+ export declare function installBrowser(options?: InstallOptions): {
12
+ ok: true;
13
+ extensionId: string;
14
+ extensionPath: string;
15
+ nativeHost: string;
16
+ nativeManifests: string[];
17
+ next: string;
18
+ };
19
+ export declare function browserStatus(options?: {
20
+ home?: string;
21
+ userDataDir?: string;
22
+ }): {
23
+ ok: true;
24
+ extensionId: string;
25
+ extensionInstalled: boolean;
26
+ nativeHostInstalled: boolean;
27
+ manifests: {
28
+ path: string;
29
+ present: boolean;
30
+ }[];
31
+ };
32
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/browser/install.ts"],"names":[],"mappings":"AAgBA,wBAAgB,eAAe,CAAC,IAAI,SAAY,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAYhF;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,cAAmB;;;;;;;EAuC1D;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO;;;;;;;;;EAclF"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bun
2
+ export {};
3
+ //# sourceMappingURL=native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../../src/browser/native.ts"],"names":[],"mappings":""}