@hraness/message-like-me 0.8.9 → 0.8.11
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/CHANGELOG.md +14 -0
- package/README.md +97 -2
- package/dist/cli.js +200 -134
- package/dist/support-runtime.js +538 -0
- package/docs/publishing.md +127 -6
- package/docs/support-foundation-notice.md +28 -0
- package/docs/textbutler/architecture.md +154 -0
- package/docs/textbutler/ghostget-contract.md +88 -0
- package/docs/textbutler/native-process-plan.md +213 -0
- package/docs/textbutler/whatsapp.md +73 -0
- package/package.json +15 -5
- package/skills/message-like-me/SKILL.md +10 -0
- package/skills/message-like-me/references/support.md +27 -0
package/docs/publishing.md
CHANGED
|
@@ -1,5 +1,99 @@
|
|
|
1
1
|
# Publish Message Like Me
|
|
2
2
|
|
|
3
|
+
## Textbutler informational-site delivery
|
|
4
|
+
|
|
5
|
+
The following explicit site-subject route supersedes the package-publication
|
|
6
|
+
prerequisite below for the informational Textbutler website only. It grants no
|
|
7
|
+
package publication, native app release, provider qualification, or live message
|
|
8
|
+
authority. The legacy tagged package route and its exact-byte/npm checks remain
|
|
9
|
+
unchanged. Source repository identity is the canonical `hraness/textbutler`
|
|
10
|
+
with unchanged numeric repository ID `1342143606`.
|
|
11
|
+
|
|
12
|
+
Use the existing `Promote website production` workflow with `site_sha` set to
|
|
13
|
+
the exact reviewed current `main` commit, `site_ci_run_id` and
|
|
14
|
+
`site_ci_run_attempt` set to its successful `CI` run, and `release_tag` empty.
|
|
15
|
+
Do not rerun a promotion attempt; make a fresh attempt-1 dispatch. The route
|
|
16
|
+
requires exactly the current standalone/site job and macOS fixture/native job,
|
|
17
|
+
rejects an older attempt after CI is rerun, rebuilds the site, and admits a
|
|
18
|
+
bounded immutable Actions build-manifest artifact by exact run, attempt, source
|
|
19
|
+
tree, site subtree, lockfile digest, artifact ID/digest, and manifest bytes.
|
|
20
|
+
|
|
21
|
+
An advancing site target uses the same `website-production` ref, shared
|
|
22
|
+
promotion concurrency, existing status-only App, environment, rulesets, and
|
|
23
|
+
ordinary GitHub Actions ref writer as the legacy route. Before the App key is
|
|
24
|
+
available, complete governed Git histories and all ordered workflow-tree
|
|
25
|
+
changes must pass the site control-epoch admission. A changed workflow range
|
|
26
|
+
requires the independently reviewed exact digest emitted by preflight in
|
|
27
|
+
`control_epoch_digest`; unchanged ranges reject a supplied digest. Site epochs
|
|
28
|
+
use domain `textbutler/control-epoch/site/v1`, bind target and workflow source to
|
|
29
|
+
the same current `main` commit, and carry `tag: null`. They cannot authorize a
|
|
30
|
+
legacy package release. Both routes retain checked helper hashes and code-owner
|
|
31
|
+
review for the complete authority implementation.
|
|
32
|
+
|
|
33
|
+
Preserve the environment's actual protection rules. If the existing key
|
|
34
|
+
environment requires a reviewer, satisfy that exact run's review through
|
|
35
|
+
GitHub's normal environment approval interface after source admission and
|
|
36
|
+
independent control review. The site redesign does not remove reviewers, wait
|
|
37
|
+
timers or other runtime gates; the legacy setup policy below is not permission
|
|
38
|
+
to bypass an existing protection.
|
|
39
|
+
|
|
40
|
+
The site writer consumes prior status, proves that the ordinary writer is
|
|
41
|
+
denied by the exact required App status, attests one target, revokes the App
|
|
42
|
+
token, verifies current rules/status/source again, and makes one sterile
|
|
43
|
+
fast-forward push with the expected-old lease. App-only steps receive no ref
|
|
44
|
+
token; writer/admission steps receive no App key. The terminal consumption step
|
|
45
|
+
runs even after failure or cancellation when admission completed. The final
|
|
46
|
+
read-only jobs verify status consumption, revocations, unchanged rules, and
|
|
47
|
+
twice-confirmed REST/GraphQL Vercel Production identity for the exact source.
|
|
48
|
+
|
|
49
|
+
`textbutler-site-attempt` retains the explicitly listed public phase receipts
|
|
50
|
+
for 30 days when the runner can upload them. Hard termination may prevent this
|
|
51
|
+
upload; remote ref/status/provider readback and the existing interrupted
|
|
52
|
+
authority cleanup remain authoritative. Never blindly repeat an uncertain
|
|
53
|
+
write. If the ref already equals the site source, a fresh dispatch takes a
|
|
54
|
+
read-only qualification route with no App key or ref token. It requires the
|
|
55
|
+
status already consumed by the exact App bot (including its numeric actor
|
|
56
|
+
identity), the unchanged App-pinned rules, and the matching Production
|
|
57
|
+
deployment after the original successful CI completion. The retry's newly
|
|
58
|
+
built manifest is not treated as that existing deployment's publication time.
|
|
59
|
+
Leave `control_epoch_digest` empty for this already-exact route. An outstanding
|
|
60
|
+
success status must first pass the documented custody cleanup, not be silently
|
|
61
|
+
cleared by read-only qualification.
|
|
62
|
+
|
|
63
|
+
After provider success, verify `textbutler.app` serves the exact intended
|
|
64
|
+
deployment, truthful development status, canonical metadata and legacy links.
|
|
65
|
+
The current Vercel project/production branch remain the target; domain or
|
|
66
|
+
repository renaming is a separate inspected provider mutation.
|
|
67
|
+
|
|
68
|
+
## Repository identity migration
|
|
69
|
+
|
|
70
|
+
The repository rename keeps numeric GitHub repository ID `1342143606` and the
|
|
71
|
+
existing GitHub Apps, rulesets, protected refs, status contexts, environment
|
|
72
|
+
reviewers, workflow IDs, Vercel project, and production branch unchanged. The
|
|
73
|
+
canonical source identity for new release and promotion runs is
|
|
74
|
+
`hraness/textbutler`; an old-name redirect is not authority for a new run.
|
|
75
|
+
The npm names `@hraness/message-like-me` and `@hraness/agentrouter`, their tag
|
|
76
|
+
namespaces, the `messagelikeme` command, wire schemas, and historical release
|
|
77
|
+
receipts remain unchanged. Do not rewrite an existing tag, npm version, or
|
|
78
|
+
provenance statement.
|
|
79
|
+
|
|
80
|
+
Merge this version-neutral control migration independently before the next
|
|
81
|
+
product/version change. Refresh the complete administrative controls census
|
|
82
|
+
and independently review the exact helper/workflow changes. Before tagging,
|
|
83
|
+
read back each applicable npm trusted publisher and require canonical repository
|
|
84
|
+
`hraness/textbutler`, its existing exact workflow filename and permission set.
|
|
85
|
+
An old publisher identity blocks publication; do not fall back to a personal
|
|
86
|
+
token or weaken its policy. Any needed provider reconciliation is a separate
|
|
87
|
+
inspected operation. Historical versions retain their original provenance and
|
|
88
|
+
are not evidence for a new canonical-repository publication.
|
|
89
|
+
|
|
90
|
+
Before production promotion, follow the existing no-digest preflight and exact
|
|
91
|
+
reviewed control-epoch transition below. Preserve the actual key-environment
|
|
92
|
+
review and satisfy it through GitHub's normal interface. Neither the rename nor
|
|
93
|
+
this source migration permits an out-of-band ref move or a protection change.
|
|
94
|
+
|
|
95
|
+
## Legacy package publication
|
|
96
|
+
|
|
3
97
|
Message Like Me builds one exact public package tarball, validates those bytes
|
|
4
98
|
on macOS and Linux, and publishes the same tarball plus `SHA256SUMS` to an
|
|
5
99
|
immutable GitHub Release. Only then does it publish that tarball to npm through
|
|
@@ -20,6 +114,33 @@ denial, expected-old lease, and provider readback are machine gates. An agent
|
|
|
20
114
|
may perform the independent review and exact dispatch required for a changed
|
|
21
115
|
workflow-control epoch; that review precedes dispatch.
|
|
22
116
|
|
|
117
|
+
## AgentRouter package publication
|
|
118
|
+
|
|
119
|
+
`@hraness/agentrouter` publishes through its own tag namespace and its own
|
|
120
|
+
tag-triggered workflow, `release-agentrouter.yml`, mirroring the root Release
|
|
121
|
+
contract. An annotated `agentrouter-v<version>` tag on a reviewed `main`
|
|
122
|
+
ancestor runs the same verify → exact-artifact → immutable GitHub Release →
|
|
123
|
+
read-only retry admission → OIDC-only npm writer → final public admission
|
|
124
|
+
chain, with the AgentRouter package manifest, tarball name, tag prefix, and
|
|
125
|
+
workflow path bound through the shared closed release-package descriptor. Both
|
|
126
|
+
release workflows share concurrency group `stable-release`, so a root and an
|
|
127
|
+
AgentRouter release can never interleave their GitHub Latest assertions. The
|
|
128
|
+
AgentRouter channel has no site or production-promotion coupling; its releases
|
|
129
|
+
distribute the package only.
|
|
130
|
+
|
|
131
|
+
Owner-side controls required once before the first AgentRouter tag:
|
|
132
|
+
|
|
133
|
+
- Extend the immutable-tag ruleset coverage to `refs/tags/agentrouter-v*` with
|
|
134
|
+
the same update and deletion restrictions and no bypass actors as the
|
|
135
|
+
existing `refs/tags/v*` scope.
|
|
136
|
+
- Ensure `@hraness/agentrouter` exists publicly under the Hraness npm scope,
|
|
137
|
+
then configure its sole trusted publisher as GitHub Actions repository
|
|
138
|
+
`hraness/textbutler`, workflow file `release-agentrouter.yml`. Require
|
|
139
|
+
the exact permission set `createPackage` plus npm's provider-imposed
|
|
140
|
+
`createStagedPackage`, and keep the staged-package inventory exactly empty.
|
|
141
|
+
Once trusted publishing is proven, disallow traditional token publication
|
|
142
|
+
for the package.
|
|
143
|
+
|
|
23
144
|
## Establish the production controls once
|
|
24
145
|
|
|
25
146
|
Apply these controls in order. Record the exact readbacks in the change review.
|
|
@@ -59,7 +180,7 @@ for this rollout and do not create a replacement Sites project.
|
|
|
59
180
|
production authorization. Give the App exactly repository permissions
|
|
60
181
|
`Commit statuses: Read and write` and implicit `Metadata: Read`, with no
|
|
61
182
|
organization permission. Install it on `hraness` with selected-repository
|
|
62
|
-
access to exactly `hraness/
|
|
183
|
+
access to exactly `hraness/textbutler`. Record its numeric App ID,
|
|
63
184
|
client ID, numeric installation ID, App slug, and the repository's numeric
|
|
64
185
|
ID `1342143606`. These are distinct identities. Read the repository ID from
|
|
65
186
|
GitHub's authenticated repository API and do not substitute a name at the
|
|
@@ -107,7 +228,7 @@ for this rollout and do not create a replacement Sites project.
|
|
|
107
228
|
published Release reports `immutable=true` before npm can run.
|
|
108
229
|
10. Ensure `@hraness/message-like-me` exists publicly under the Hraness npm
|
|
109
230
|
scope, then configure its sole trusted publisher as GitHub Actions repository
|
|
110
|
-
`hraness/
|
|
231
|
+
`hraness/textbutler`, workflow file `release.yml`. Require its exact
|
|
111
232
|
permission set to be `createPackage` plus npm's provider-imposed
|
|
112
233
|
`createStagedPackage`. The checked Release workflow uses only its reviewed
|
|
113
234
|
direct `npm publish` path, never `npm stage` or `stage publish`, and release
|
|
@@ -156,7 +277,7 @@ assertions together:
|
|
|
156
277
|
`hraness`, exactly `statuses:write` plus `metadata:read`, no `contents` or
|
|
157
278
|
`workflows` authority, and an exhaustive
|
|
158
279
|
`/installation/repositories` set of exactly
|
|
159
|
-
`{hraness/
|
|
280
|
+
`{hraness/textbutler}` with repository ID `1342143606`;
|
|
160
281
|
- `production-ref-writer-key` admits only `main`, has no required reviewers,
|
|
161
282
|
wait timer, or custom deployment-protection rules, disables administrator
|
|
162
283
|
bypass, and exposes only the expected key and checked variables;
|
|
@@ -273,7 +394,7 @@ When an established protected ref predates reviewed workflow-control changes:
|
|
|
273
394
|
mode: process.env.MODE,
|
|
274
395
|
previousSha: process.env.PREVIOUS_SHA,
|
|
275
396
|
protectedRef: process.env.PROTECTED_REF,
|
|
276
|
-
repository: "hraness/
|
|
397
|
+
repository: "hraness/textbutler",
|
|
277
398
|
repositoryId: 1342143606,
|
|
278
399
|
tag: process.env.VERIFIED_TAG,
|
|
279
400
|
targetSha: process.env.TARGET_SHA,
|
|
@@ -322,7 +443,7 @@ When an established protected ref predates reviewed workflow-control changes:
|
|
|
322
443
|
treat that target's `.github/workflows` tree OID as the baseline for the next
|
|
323
444
|
routine range. Re-read the permanent App's exact `statuses:write` plus
|
|
324
445
|
`metadata:read` permissions, absence of `contents` and `workflows` authority,
|
|
325
|
-
singleton `{hraness/
|
|
446
|
+
singleton `{hraness/textbutler}` repository selection, and the terminal
|
|
326
447
|
non-success status. A completed epoch requires no key rotation because it
|
|
327
448
|
created or replaced no credential and every short-lived App token was revoked;
|
|
328
449
|
an interrupted run still follows the separate quarantine and cleanup
|
|
@@ -629,7 +750,7 @@ missed slot is skipped rather than retried or shifted, and request, body, and
|
|
|
629
750
|
sleep latency all consume the same window. App identity, installation, mint,
|
|
630
751
|
DELETE, and observation bodies are streamed under a 1 MiB cap and scrubbed
|
|
631
752
|
after parsing. Every HTTP 200 must still describe the exact singleton selected
|
|
632
|
-
`hraness/
|
|
753
|
+
`hraness/textbutler` repository with ID `1342143606`. Acceptance requires
|
|
633
754
|
two distinct scheduled HTTP 401 authorization-denial reads. An HTTP 403 is
|
|
634
755
|
indeterminate because GitHub can use it for rate limiting or policy denial; it
|
|
635
756
|
never proves revocation. A 200 after either denial, only one denial, any other
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Shared support protocol notice
|
|
2
|
+
|
|
3
|
+
The CLI includes `@hraness/support-foundation` 0.3.0 from reviewed commit
|
|
4
|
+
`2d034b357680353574411217d68b02b6755b07ed` of
|
|
5
|
+
[Hraness Support Foundation](https://github.com/hraness/support-foundation).
|
|
6
|
+
Its code is bundled only for the command-line support flow; public SDK exports do not include it.
|
|
7
|
+
|
|
8
|
+
MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2026 Hraness
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Textbutler architecture
|
|
2
|
+
|
|
3
|
+
Textbutler is a personal message butler for macOS. An owner activates a bounded set of contacts. Each contact gets a private workspace that a coding agent can read and evolve. A separate daemon decides when to invoke that agent and controls every outward action.
|
|
4
|
+
|
|
5
|
+
The source includes the owner daemon, contact reply loop, versioned Ghostget automation protocol and macOS menu companion. Synthetic tests establish their control and recovery behavior. Live provider delivery has separate acceptance requirements below; the companion is a CLI artifact and has no signing or notarization gate.
|
|
6
|
+
|
|
7
|
+
## Ownership
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart LR
|
|
11
|
+
Menu[Menu companion] --> Control[Owner-only control socket]
|
|
12
|
+
Control --> Butler[Textbutler daemon]
|
|
13
|
+
Butler --> Router[Agentrouter]
|
|
14
|
+
Router --> Provider[Admitted agent provider]
|
|
15
|
+
Provider --> Tools[Contact-bound tool broker]
|
|
16
|
+
Tools --> Memory[One contact workspace]
|
|
17
|
+
Tools --> Web[Public web broker]
|
|
18
|
+
Tools --> Intents[Proposed actions]
|
|
19
|
+
Intents --> Butler
|
|
20
|
+
Butler --> Transport[Versioned transport adapter]
|
|
21
|
+
Transport --> Ghostget[Ghostget]
|
|
22
|
+
Ghostget --> Messages[iMessage and WhatsApp]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Ghostget owns native permissions, message and contact acquisition, provider actions, and receipts. Textbutler does not open chat.db or automate Messages directly. Agentrouter owns provider selection, shared-account leases, cancellation, model catalogs, qualification evidence, and a bounded tool interface. Textbutler owns conversation policy and the durable send transaction. The menu companion changes owner settings through a small local protocol; it never gives a model arbitrary local commands.
|
|
26
|
+
|
|
27
|
+
The background lifecycle uses a user LaunchAgent: native messaging belongs to the signed-in Mac user, and login persistence is independent of the menu companion. Installation records the exact runtime, entrypoint, data directory and generation; removal verifies its private receipt and loaded service identity. Uninstall preserves contact data. New settings start paused. A private SQLite custody lock prevents duplicate daemon ownership and permits recovery only for a dead recorded process and its exact unserved socket. A temporary live launchd install/start/uninstall test passed while preserving synthetic owner data. The CLI companion is the supported release path; desktop app packaging has been removed; the CLI companion is the only local runtime surface.
|
|
28
|
+
|
|
29
|
+
The daemon serves owner controls, enrollment jobs and a reply loop through its own supervised Ghostget process. Startup, enablement and recovery establish a silent event boundary, refresh bounded history and admit only subsequent eligible inbound messages. A failed catch-up pauses the affected conversation. No provider work starts without explicit owner account configuration.
|
|
30
|
+
|
|
31
|
+
## Contact data
|
|
32
|
+
|
|
33
|
+
The intended application-support tree is:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
Textbutler/
|
|
37
|
+
daemon.sock owner-only native control socket
|
|
38
|
+
state/
|
|
39
|
+
settings.json owner configuration and contact bindings
|
|
40
|
+
host.json optional private installed Ghostget CLI/account binding
|
|
41
|
+
runs.sqlite private run and send journal
|
|
42
|
+
daemon-custody.sqlite exclusive process/socket ownership
|
|
43
|
+
launch-agent-custody.sqlite lifecycle serialization
|
|
44
|
+
launch-agent.json exact LaunchAgent installation receipt
|
|
45
|
+
ghostget-automation-custody.json exact supervised messaging process claim
|
|
46
|
+
plugins/
|
|
47
|
+
extensions.json explicit owner-installed hook manifest
|
|
48
|
+
quiet-hours.ts example trusted executable extension
|
|
49
|
+
contacts/
|
|
50
|
+
<opaque-contact-id>/ the only model-visible workspace for one run
|
|
51
|
+
AGENTS.md editable response guidance; never authority
|
|
52
|
+
ABOUT.md relationship context and owner instructions
|
|
53
|
+
MEMORY.md concise, dated, source-attributed working notes
|
|
54
|
+
STYLE.md owner-style evidence and response preferences
|
|
55
|
+
history/ bounded, attributed conversation excerpts
|
|
56
|
+
notes/ task-specific notes and outstanding questions
|
|
57
|
+
attachments/ broker-admitted incoming files
|
|
58
|
+
outbox/ files proposed for this conversation
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Directory names use opaque identifiers, not contact names or phone numbers. Files are private to the Mac user. Models cannot traverse parent paths, links, other contact folders, or host configuration. The file broker supports reads, conditional writes, and exact edits. It exposes no symlink, directory, delete-tree, shell, or executable permission operations. Atomic replacement preserves the preceding file if a write fails. A stale memory revision produces a conflict instead of overwriting an owner's correction.
|
|
62
|
+
|
|
63
|
+
The owner chooses one verified direct conversation from a bounded Ghostget list. Enrollment rechecks the account incarnation and participant identity and creates a disabled contact. History import is a separate opt-in, limited to 200 recent, explicitly scoped messages, with message ID, time, and author preserved. The import records shortening and omissions; it does not fetch media. These records are context only, and historical automation may be unobservable. A later qualified initialization run may summarize preferences, conversational style, open tasks, and useful context into memory. It must distinguish evidence from inference and retain uncertainty. Later runs correct outdated notes and record sources. Proven butler output never becomes owner-style training evidence. No global person model or cross-contact retrieval is supplied by default.
|
|
64
|
+
|
|
65
|
+
The original Message Like Me corpus and profile tools remain an optional bounded bootstrap source. They do not become the live message transport. Old databases are not reset or silently migrated. There is no need to carry every previous archive/source feature into the menu companion.
|
|
66
|
+
|
|
67
|
+
## Reply admission
|
|
68
|
+
|
|
69
|
+
Default contact mode is smart, but a new contact starts disabled. Activating more than the configured limit fails atomically; no existing contact is displaced. The initial limit is five, with owner settings from one to fifty.
|
|
70
|
+
|
|
71
|
+
An inbound event must identify one activated direct conversation. Historical, outgoing, butler-authored, unknown-author, group, reaction-only, and delivery events do not start reply runs. Persisted event identity prevents a duplicate send. One run may own a contact at a time.
|
|
72
|
+
|
|
73
|
+
The daemon waits eight seconds after an incoming message to collect a burst. Newer messages supersede older candidates. Owner typing suppresses a reply when that signal exists. Any recent owner message causes a five-minute cooldown. The runtime checks current messages and settings again after composition. Disabling a contact or pressing global pause cancels pending runs and invalidates their grants.
|
|
74
|
+
|
|
75
|
+
A whole-word, case-insensitive `butler` invocation permits a response after those deterministic gates. In keyword mode, other messages stay silent. In smart mode, a tool-free cheap model returns a strict structured classification. It should answer useful assistance requests, and stay silent during ordinary conversation, acknowledgments, emotional exchanges, or uncertain intent. Confidence below 0.85 stays silent. Malformed results, exhausted accounts, stale model catalogs, or timeouts never escalate to a more expensive agent automatically.
|
|
76
|
+
|
|
77
|
+
Classifier choice comes from the selected provider's fresh available-model catalog, with explicit cost metadata and structured-output capability. No permanently hard-coded "cheap" model alias is assumed. An owner can pin a classifier or reply model after availability validation. Classifier and responder share the chosen provider/account policy; one classifier gets no tool authority.
|
|
78
|
+
|
|
79
|
+
No typing signal can prove the owner is absent. The current adapters expose no typing signal; the daemon retains its message-based cooldown and final revision checks. The selected provider/account must independently qualify before smart mode can invoke a model.
|
|
80
|
+
|
|
81
|
+
## Send transaction and disclosure
|
|
82
|
+
|
|
83
|
+
The model proposes actions. It does not dispatch them. Owner-installed hooks may shape the work or veto a reply, but all action validation, contact binding, limits, and disclosure run afterwards.
|
|
84
|
+
|
|
85
|
+
Every text action is wrapped by trusted code with the contact's three symbols. Each field must be one visible grapheme; an empty or invisible disclosure is rejected. Default rendering is `🤖{ hello this is my response }`.
|
|
86
|
+
|
|
87
|
+
Reactions, stickers, link previews, and app cards cannot literally carry that text prefix. A disclosed text companion is therefore the first action in a nontext response, and counts toward the eight-action maximum. The provider must execute in order and stop if that companion fails. App-specific cards may additionally identify Textbutler in their content, but never remove the companion requirement.
|
|
88
|
+
|
|
89
|
+
Before send, the runtime rechecks owner activity, conversation revision, current settings, capability availability, cancellation, attachment ownership, target message membership, and the contact grant. It asks the transport to prepare an exact plan with an expiry and digest. Immediately before submission, it journals the dispatch intent. The transport must atomically validate the grant, contact route, context revision, and plan digest at its own effect boundary.
|
|
90
|
+
|
|
91
|
+
`submitted` is not `delivered`. Partial and indeterminate outcomes pause further automated activity for that contact until explicit reconciliation. A crash while dispatching becomes indeterminate on recovery; it never causes a blind retry. A crash before dispatch abandons the run without sending. Provider failover cannot replay a possibly submitted action.
|
|
92
|
+
|
|
93
|
+
## Hooks and plugins
|
|
94
|
+
|
|
95
|
+
The initial lifecycle is `message.received`, `reply.decide`, `reply.compose`, `reply.before-send`, `reply.sent`, `memory.updated`, and `run.failed`. Hooks have a named/versioned owner-installed extension, deterministic registration order, a deadline, and a cancellation signal. Failure before dispatch closes admission. A notification-hook failure after a receipt cannot change that receipt or trigger resend.
|
|
96
|
+
|
|
97
|
+
Executable extensions are application code with the daemon's trust. They are installed outside contact folders; the agent cannot write them or turn message text into imports. Agent self-evolution means revising guidance and memory, not installing executable code. A future untrusted plugin mode needs its own process or language sandbox and explicit capabilities. In-process hooks are never described as a plugin security boundary.
|
|
98
|
+
|
|
99
|
+
The daemon reads a bounded private `plugins/extensions.json` manifest and preflights its complete inventory before importing listed TypeScript/JavaScript entry modules. Each default export must match the manifest ID/version and known hook names. Source digests appear in the loaded extension metadata. No directory scanning, package installation or hot reload occurs; changes require a full daemon process restart. The routed agent emits `memory.updated` only after a successful conditional write, with its path and committed revision. A notification failure does not undo that write or replay it.
|
|
100
|
+
|
|
101
|
+
## Agentrouter
|
|
102
|
+
|
|
103
|
+
Agentrouter begins as an MIT-licensed source package independent of Textbutler's product model. Its account lease coordinates the selected provider account without embedding credentials in a contact workspace. Credential resolvers remain trusted host services. A lease cannot be stolen merely because its time elapsed while a process might still be alive.
|
|
104
|
+
|
|
105
|
+
Provider adapters declare observed, exact-version qualification. A launch plan is not proof of a sandbox. Codex's shell-disable setting and Claude's exact tool list are useful inputs, but an adapter is not admitted until attempted shell/process calls, host file reads, inherited MCP/plugin configuration, auth-file access, alternate agents, and additional workspaces are demonstrably blocked. No bypass-permissions mode is acceptable.
|
|
106
|
+
|
|
107
|
+
The source implements a pinned Claude Agent SDK adapter with explicit API-key account binding, a private verified executable snapshot, isolated runtime directories, no built-in tools or inherited settings, broker-only MCP, bounded raw output, and joined process-group termination. Its production gate requires independent qualification of the exact executable and SDK identity. Synthetic protocol and native-runtime fixtures do not activate it. The experimental Codex app-server driver verifies each model request's tool inventory through a host relay and keeps contact storage outside native scratch. It has no live account transport or production registration; native confinement and adversarial custody qualification remain incomplete. See the [Agentrouter implementation and evidence](../../packages/agentrouter/README.md).
|
|
108
|
+
|
|
109
|
+
A separately selected Claude API adapter executes the bounded tool loop in the trusted host. It exposes only the six broker tools and never starts a model-selected process. Account checks bind the actual packaged runtime and private credential generation; replacing a credential invalidates old discovery and running account work. Fresh model availability and price metadata select a cheap classifier. This explicit API choice does not silently replace Claude Code or Codex and does not use their subscription authentication.
|
|
110
|
+
|
|
111
|
+
The model receives a fixed contact/workspace identity and a fixed run ID. File operations are brokered and conditional. Public web requests are separately bounded and must not reach loopback, private networks, local sockets, or cloud metadata through DNS or redirects. Message tools stage recipient-free intents for the one conversation. Unknown tool names and unknown input fields fail. Credential/account services are never model tools.
|
|
112
|
+
|
|
113
|
+
Oompa's existing runtime provider port and account/process-custody patterns are source references. Its ordinary workspace-write execution profile is not the requested contact-only sandbox. AI Charts is a prospective second consumer. Do not migrate either product to Agentrouter until an adapter has equivalent feature and recovery evidence; avoid changing their active work in this redesign.
|
|
114
|
+
|
|
115
|
+
## Ghostget contract and rich features
|
|
116
|
+
|
|
117
|
+
WhatsApp follows the same Ghostget ownership boundary. Its private wacli-backed transport provides durable observations and recipient-bound actions; pairing, session state and synchronization stay in Ghostget. The [WhatsApp guide](whatsapp.md) describes setup and action support. Textbutler does not embed WPPConnect or invoke wacli directly.
|
|
118
|
+
|
|
119
|
+
Ghostget owns its provider process and private control socket; Textbutler consumes only Ghostget's documented CLI and automation contracts.
|
|
120
|
+
|
|
121
|
+
The older generic messaging APIs retain expiring route references and owner-confirmed previews. Automation uses a separate explicit owner protocol with durable enrollment, revocable grants and event observations. The iMessage provider negotiates attachments, reactions, stickers, rich links and polls separately; unsupported App Clips and arbitrary experiences remain unavailable. Native Contacts directory discovery is not implemented in this protocol.
|
|
122
|
+
|
|
123
|
+
Text and file sending use the native helper's AppleScript path. The upstream rich-action bridge injects into Messages and requires SIP to be disabled. Textbutler and Ghostget never change that security setting or install the injection automatically. Rich actions therefore remain unavailable unless the required bridge is already working. This is a significant installation constraint, not a completed rich-messaging experience on a stock Mac.
|
|
124
|
+
|
|
125
|
+
Ghostget 0.18.2 includes the admitted imsg `0.14.1+private-transport.3` helper. Automated rich links use `send.rich` with `fetch_metadata: false`, constructing the URL and host-title card without helper metadata or image fetching. Availability still requires current managed permission and a compatible native bridge; this does not claim network isolation for Messages itself. The host never falls back to fetching an agent-provided URL.
|
|
126
|
+
|
|
127
|
+
The [Ghostget integration contract](ghostget-contract.md) documents the implemented binding, grant, event, action and receipt semantics.
|
|
128
|
+
|
|
129
|
+
The Textbutler transport supports capability negotiation, conversations, bounded history, cursor-based events, exact preparation, authorization and receipts. File paths become admitted bytes before crossing the boundary. Per-action permission and context checks stop a rich batch when conversation activity changes. Private database access stays inside Ghostget's provider implementation.
|
|
130
|
+
|
|
131
|
+
Linq's documented iMessage API includes attachments, reactions, stickers, rich links, App Clips, and experiences. App cards and rich links are standalone messages. Those hosted capabilities do not establish availability through native macOS Messages. Optional Linq would be a separate transport with explicit account setup, sender identity, webhook signature verification, replay protection, and the same Textbutler policy gates. It is not a way to silently route an owner's personal conversation through a different phone number.
|
|
132
|
+
|
|
133
|
+
Ghostget currently imports published Message Like Me bundle contracts. Keep that immutable package a leaf. Do not repoint it at the Textbutler runtime. Extract the neutral bundle contracts before reversing a live package dependency, or consume Ghostget's installed CLI contract without a package import in the interim. Preserve historical wire-format identifiers.
|
|
134
|
+
|
|
135
|
+
## macOS menu companion
|
|
136
|
+
|
|
137
|
+
The supported local surface is an unbundled status-item companion launched by the CLI. It exposes daemon state, contact and account readiness, capabilities, recent activity, pause/resume and status refresh. The website action opens the informational textbutler.app page. Contact enrollment, account setup and other settings remain daemon protocol capabilities without a current menu or web interface. The companion is not required to run the daemon.
|
|
138
|
+
|
|
139
|
+
One narrow native command accepts the versioned control request. It connects to the private user socket, bounds requests/responses, applies timeouts, and verifies same-user ownership. The companion has no generic shell, filesystem, opener, or network plugin and does not inherit access to arbitrary Ghostget operations.
|
|
140
|
+
|
|
141
|
+
## Admission still required
|
|
142
|
+
|
|
143
|
+
1. Use the verified Ghostget 0.18.2 package, which includes the reviewed automation source and native helpers. Real account synchronization, recipient identity, rich actions and revocation still require a bounded owner-authorized live test; artifact admission and synthetic fixtures do not prove delivery.
|
|
144
|
+
2. Independently qualify the native Claude SDK and Codex adapters for the requested no-shell, contact-only profile before enabling those choices. The separate Claude API path requires explicit account setup and packaged-runtime admission.
|
|
145
|
+
3. Publish the CLI package with its pinned desktop-foundation SDK dependency. Verify the package bytes, the verified pinned runner download, singleton behavior, and the shared autostart install/uninstall lifecycle. A source checkout or missing companion must never trigger a build at launch.
|
|
146
|
+
4. Keep historical repository and published package identities as compatibility and provenance anchors. The Textbutler site is assigned to `textbutler.app`; later identity migrations must preserve immutable artifacts and existing release protections.
|
|
147
|
+
|
|
148
|
+
## Sources
|
|
149
|
+
|
|
150
|
+
- [Codex App Server](https://learn.chatgpt.com/docs/app-server): programmatic threads, turns, tool requests, account operations, and sandbox configuration.
|
|
151
|
+
- [Codex security](https://learn.chatgpt.com/docs/security): sandbox and approval boundaries.
|
|
152
|
+
- [Claude Agent SDK permissions](https://platform.claude.com/docs/en/agent-sdk/permissions): tool permission controls.
|
|
153
|
+
- [Linq messages](https://docs.linqapp.com/channel/imessage/api/resources/chats/subresources/messages/): transport-specific rich message behavior.
|
|
154
|
+
- [Linq reactions](https://docs.linqapp.com/channel/imessage/api/resources/messages/methods/add_reaction/): emoji and sticker reactions.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Ghostget integration contract
|
|
2
|
+
|
|
3
|
+
Textbutler owns reply policy and contact memory. Ghostget owns messaging
|
|
4
|
+
accounts, native permissions, synchronization, event storage and outward
|
|
5
|
+
actions. Textbutler communicates with its own Ghostget owner process through
|
|
6
|
+
`ghostget messaging automation serve --stdio`; it does not share the Ghostget
|
|
7
|
+
menu companion's private helper or open provider databases.
|
|
8
|
+
|
|
9
|
+
[Ghostget 0.18.2](https://github.com/hraness/ghostget/releases/tag/v0.18.2) is the
|
|
10
|
+
verified published dependency for this contract. Its package includes both
|
|
11
|
+
private messaging runtimes; installation is explicit and starts no provider.
|
|
12
|
+
Older generic CLI routes do not become automation grants.
|
|
13
|
+
|
|
14
|
+
## Owner process
|
|
15
|
+
|
|
16
|
+
The private protocol is `ghostget.messaging-automation/1`. Every bounded JSON
|
|
17
|
+
request has an ID, method and parameters; every response repeats its protocol
|
|
18
|
+
and ID and carries either a checked result or a typed error. Initialization
|
|
19
|
+
selects up to two explicit accounts, one per network, through stdin. Contact
|
|
20
|
+
memory and model tools cannot configure that process or invoke its control API.
|
|
21
|
+
|
|
22
|
+
Textbutler records process custody before launch, bounds its streams and queue,
|
|
23
|
+
and removes custody only after a successful close response and verified clean
|
|
24
|
+
process exit. Cancellation and revocation can interrupt an active submission.
|
|
25
|
+
A malformed response, crash or uncertain cleanup retains the recovery fence.
|
|
26
|
+
Restarting Textbutler does not silently clear it.
|
|
27
|
+
|
|
28
|
+
## Contacts, events and grants
|
|
29
|
+
|
|
30
|
+
| Surface | Implemented contract |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `status`, `start` | Observe capabilities; explicitly start supported synchronization. |
|
|
33
|
+
| `conversations`, `enroll`, `enrollments` | Exact account generation and direct participant-bound conversation enrollment. |
|
|
34
|
+
| `poll`, `history`, `events` | Bounded history, durable observation cursors, revisions, catch-up and gap detection. |
|
|
35
|
+
| `grant`, `grant.get`, `grant.by-intent`, `revoke` | Recipient, action, expiry and quota limits; idempotent issuance lookup and immediate revocation. |
|
|
36
|
+
| `asset`, `prepare` | Admit exact attachment bytes and bind the ordered action list to a context revision and expiry. |
|
|
37
|
+
| `submit`, `cancel`, `run` | Journal an action claim before dispatch and retain accepted, failed, partial or indeterminate results. |
|
|
38
|
+
| `close` | Wait for owned provider cleanup before acknowledging shutdown. |
|
|
39
|
+
|
|
40
|
+
Enrollment records contain the provider account incarnation, source generation,
|
|
41
|
+
implementation identity, exact conversation coordinate and participants. Display
|
|
42
|
+
titles are labels, not authority. Account or participant replacement invalidates
|
|
43
|
+
the binding. Existing Textbutler version 1 read bindings remain readable;
|
|
44
|
+
automation requires explicit version 2 enrollment.
|
|
45
|
+
|
|
46
|
+
Ghostget's managed permissions must explicitly allow each requested operation.
|
|
47
|
+
A broad capability advertisement does not create a grant. Textbutler activation
|
|
48
|
+
issues a bounded grant only after checking the selected agent account and
|
|
49
|
+
conversation. It renews standing enabled-contact grants within their limits,
|
|
50
|
+
and persists grant intent before issuance so a lost response can be resolved
|
|
51
|
+
without blindly creating another grant. Disabled and uncommitted grants are
|
|
52
|
+
reconciled through revocation.
|
|
53
|
+
|
|
54
|
+
Startup, re-enablement and recovery drain old events silently. An unresolved gap
|
|
55
|
+
pauses automation. Textbutler waits for a new eligible inbound event and applies
|
|
56
|
+
debounce, owner cooldown, classification and rate limits. Ghostget rechecks
|
|
57
|
+
identity, permission, context and grant before dispatch, and observes intervening
|
|
58
|
+
conversation changes between actions. Neither component retries an uncertain
|
|
59
|
+
send automatically.
|
|
60
|
+
|
|
61
|
+
## Rich actions
|
|
62
|
+
|
|
63
|
+
Attachments, reactions, stickers, rich links and native polls are separate
|
|
64
|
+
capabilities. They become available only when the installed provider, current
|
|
65
|
+
account and managed permission admit them. Message targets must belong to the
|
|
66
|
+
enrolled conversation. Attachment and sticker paths are resolved by Textbutler's
|
|
67
|
+
contact file broker; Ghostget receives admitted bytes, not arbitrary paths.
|
|
68
|
+
|
|
69
|
+
Every response starts with disclosed text. For a nontext response, Textbutler
|
|
70
|
+
inserts a companion such as `🤖{ … }` before the rich actions. Execution stops
|
|
71
|
+
when a preceding action fails or the conversation changes. An accepted receipt
|
|
72
|
+
does not claim delivery.
|
|
73
|
+
|
|
74
|
+
Native iMessage exposes text and files through the pinned helper and its
|
|
75
|
+
currently usable rich methods for six standard tapbacks, stickers, links and
|
|
76
|
+
polls. Rich methods require the owner's separately configured native bridge.
|
|
77
|
+
App Clips and arbitrary mini-app experiences remain unavailable: no reviewed
|
|
78
|
+
native executor exists. Linq's hosted APIs are a design reference and cannot
|
|
79
|
+
silently substitute another sender for the owner's personal conversation.
|
|
80
|
+
|
|
81
|
+
## Verification boundary
|
|
82
|
+
|
|
83
|
+
The source tests cover account changes, cursor restart and gaps, grant revocation
|
|
84
|
+
and lost responses, exact byte admission, ordered actions, cancellation and
|
|
85
|
+
uncertain results using synthetic accounts. They do not establish live provider
|
|
86
|
+
delivery. A bounded live acceptance test needs explicit account, recipient and
|
|
87
|
+
message authorization and must record the exact provider/runtime versions.
|
|
88
|
+
See [WhatsApp](whatsapp.md) and [the architecture](architecture.md).
|