diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87c77b28..77d008f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,21 @@ Historical entries retain their original delivery coordinates.
## Unreleased
+## 0.18.64
+
+Supported global CLI installs update to the latest stable release before an
+ordinary command, with at most one automatic check per day.
+
+- Add `ghostget update`, `check`, `status`, `enable`, and `disable`.
+- Keep updates out of SDK calls, source checkouts, project dependencies, CI,
+ nested clients, help, and gateway-only sessions. Exact pins need `update enable`.
+- Verify the immutable release archive before the owning package manager runs,
+ and wait until other commands using that installation have finished.
+
## 0.18.63
+Annotated tag rejected before publication; tag unchanged.
+
Present GhostGet with the shared studio design conventions.
- Group related studio tools by purpose, shorten the opening copy, and keep installation within reach.
diff --git a/README.md b/README.md
index 652a69b0..f25bf4aa 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Install [Bun 1.3.14](https://bun.sh/docs/installation) if needed, then install
GhostGet and read a public page:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.63/hraness-ghostget-0.18.63.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
ghostget read https://example.com
```
@@ -49,15 +49,34 @@ which always names the latest published release. Upgrading from Wrench? Read the
The optional Agent Skill teaches your agent when and how to use GhostGet:
```sh
-npx skills add hraness/ghostget#v0.18.63
+npx skills add hraness/ghostget#v0.18.64
# With Bun instead:
-bunx skills add hraness/ghostget#v0.18.63
+bunx skills add hraness/ghostget#v0.18.64
```
Start a new agent session, then ask: “Use GhostGet to read https://example.com
and summarize it.” The skill is instructions for your agent; the CLI install
above supplies the executable.
+### Update the CLI
+
+Supported global Bun and npm installs on macOS and Linux check for a stable
+GhostGet release before ordinary commands, at most once a day. Install the
+GitHub CLI (`gh`) and authenticate it with `gh auth login --hostname github.com`
+to let the updater download verified release archives. If your installed CLI
+does not have `update`, repeat this guide's global CLI install command once.
+Run `ghostget update` to update now,
+`ghostget update check --json` to inspect a release, or `ghostget update status`
+to see the saved setting. Use `ghostget update disable` to turn automatic updates
+off and `ghostget update enable` to turn them on. Exact version pins require
+`enable` before following new releases.
+
+Set `HRANESS_NO_UPDATE=1` for a command that must use the installed version.
+CI, nested clients, help, and gateway-only mode skip automatic checks. Manage
+source checkouts, project dependencies, and unsupported installations with their
+package manager. Agent Skill copies are refreshed separately through the skill
+installer. Updating the CLI preserves connected accounts and saved content.
+
## When to use something else
| Tool | Use it instead of GhostGet when |
@@ -164,7 +183,7 @@ firewall.
## Built-in provider catalog
-This v0.18.63 source tree supports executable actions for 21 services: Beeper,
+This v0.18.64 source tree supports executable actions for 21 services: Beeper,
Bluesky, ClasificadosOnline, Facebook, Facebook Groups, Facebook Marketplace,
GitHub, Gmail, Hacker News, Instagram, iMessage, LinkedIn, Reddit, Substack,
Threads, TikTok, Twitch, WebMCP Registry, WhatsApp, X, and YouTube. LinkedIn
@@ -248,7 +267,7 @@ For that same released coordinate, install GhostGet in an agent or application
that owns its own model, planning, tool loop, approvals, and interface:
```sh
-bun add https://github.com/hraness/ghostget/releases/download/v0.18.63/hraness-ghostget-0.18.63.tgz
+bun add https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
```
```ts
diff --git a/bun.lock b/bun.lock
index 5e8ababf..e6683614 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,6 +7,7 @@
"dependencies": {
"@1password/sdk": "0.5.0",
"@hraness/accounts-cli": "github:hraness/accounts-cli#v0.1.3",
+ "@hraness/cli-update": "https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz",
"@hraness/desktop-foundation": "https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz",
"@hraness/local-custody": "https://github.com/hraness/local-custody/releases/download/v0.9.1/hraness-local-custody-0.9.1.tgz",
"@hraness/message-like-me": "github:hraness/textbutler#83453cc7c17b49bb53fdfd89ccb69b8b44b30af1",
@@ -128,6 +129,8 @@
"@hraness/accounts-cli": ["@hraness/accounts-cli@github:hraness/accounts-cli#eaad8cb", { "dependencies": { "@hraness/suite-accounts": "github:hraness/suite-accounts#v0.9.13" } }, "hraness-accounts-cli-eaad8cb", "sha512-/r6nTHa9buZj9dLP4dgRtPkrKpfhs3/7gwcR/r3aAFnNmLXtUBms60NPP7O4EOel524638UhEnvYuZ1TSlgtAA=="],
+ "@hraness/cli-update": ["@hraness/cli-update@https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz", {}, "sha512-g0Bg0FYiyJqEJC9JGtAZsZ+r599c8iSNDd7ADi6weFCKBGuRXa2tOnn93+pTfLe1tcayXiLuAtDPp8meFHgKbg=="],
+
"@hraness/design-kit": ["@hraness/design-kit@github:hraness/design-kit#1f4aa08", { "dependencies": { "@hugeicons/core-free-icons": "4.2.3", "@stylexjs/stylex": "0.19.0", "motion": "12.42.0", "next-themes": "0.4.6", "react-aria-components": "1.19.0", "recharts": "3.8.0", "sugar-high": "^1.2.1", "web-haptics": "0.0.6" }, "peerDependencies": { "@hraness/ui": ">=0.5.16 <0.6.0", "react": ">=18 <20", "react-dom": ">=18 <20" }, "optionalPeers": ["@hraness/ui"] }, "hraness-design-kit-1f4aa08", "sha512-3Xa04ZJGeSFcwWJ+ncG5opo2lp8op4dLqdzjVokoVe4Itd3YUHNtJbOk9wb5t9TF8EX1T7jWfWlJCkSTmejkPA=="],
"@hraness/desktop-foundation": ["@hraness/desktop-foundation@https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz", { "bin": { "companion": "./dist/src/cli.js" } }, "sha512-+TlrHoM1wZ2rE4p3u1CIAz1+aGKPZhmiNOKjEKOYPZmTx8BFSyFLPeL06tupfX2WxcRzRZ3QwmOsyjlgSu/p1Q=="],
diff --git a/costs.json b/costs.json
index 85bc9f8c..0efd7156 100644
--- a/costs.json
+++ b/costs.json
@@ -32,6 +32,27 @@
"maxCalendarDays": 90
},
"status": "capture-required; public execution rejects before credentials or network"
+ },
+ "local:cli-update-policy": {
+ "kind": "authoritative",
+ "retention": "persistent",
+ "owner": "src/update.ts",
+ "deletion": "src/update.ts",
+ "budget": {
+ "maxBytesPerStateFile": 16384,
+ "maxCoordinationEntries": 4096
+ },
+ "source": "Explicit update policy and verified installed release receipts; no account or captured content"
+ },
+ "local:cli-update-archives": {
+ "kind": "derived",
+ "retention": "persistent",
+ "owner": "src/update.ts",
+ "source": "Verified immutable release archives retained for global package-manager replay",
+ "budget": {
+ "maxBytesPerArchive": 536870912
+ },
+ "expiry": "Retain manager-referenced archives; cleanup requires proving no installation or running manager references them"
}
},
"exempt": []
diff --git a/dist/apple-photos-client.js b/dist/apple-photos-client.js
index 6cddcb34..285d306b 100644
--- a/dist/apple-photos-client.js
+++ b/dist/apple-photos-client.js
@@ -1,7 +1,7 @@
// @bun
import {
GHOSTGET_VERSION
-} from "./index-adtr3kqc.js";
+} from "./index-rayqxqrk.js";
import {
canonicalJson,
sha256
diff --git a/dist/beeper-client.js b/dist/beeper-client.js
index bcd2815e..9ffb8b60 100644
--- a/dist/beeper-client.js
+++ b/dist/beeper-client.js
@@ -4,7 +4,7 @@ import {
} from "./index-26yq8q16.js";
import {
GHOSTGET_VERSION
-} from "./index-adtr3kqc.js";
+} from "./index-rayqxqrk.js";
import {
canonicalJson,
canonicalJsonSha256Matches,
diff --git a/dist/index-adtr3kqc.js b/dist/index-rayqxqrk.js
similarity index 62%
rename from dist/index-adtr3kqc.js
rename to dist/index-rayqxqrk.js
index 4d27f729..c96cb4a8 100644
--- a/dist/index-adtr3kqc.js
+++ b/dist/index-rayqxqrk.js
@@ -1,5 +1,5 @@
// @bun
// src/version.ts
-var GHOSTGET_VERSION = "0.18.63";
+var GHOSTGET_VERSION = "0.18.64";
export { GHOSTGET_VERSION };
diff --git a/docs/assurance.md b/docs/assurance.md
index 2e1f5686..c1ee1e10 100644
--- a/docs/assurance.md
+++ b/docs/assurance.md
@@ -2014,12 +2014,12 @@ Release admits source only with the exact commit's successful default-branch CI
- Assumptions: `github-api`, `github-enforcement`
- Not verified: Only the enumerated example cases are checked.
-#### `release-source-codeql-exact-two-languages`
+#### `release-source-codeql-exact-four-languages`
-Source admission requires exactly the Actions and JavaScript/TypeScript CodeQL jobs and analyses on the exact source and current main; missing or extra languages, or two exact-source CodeQL runs, are rejected.
+Source admission requires exactly the Actions, JavaScript/TypeScript, Python, and Rust CodeQL jobs and analyses on the exact source and current main; missing or extra languages, or two exact-source CodeQL runs, are rejected.
- Evidenced by example test.
-- Source: `docs/publishing.md`: “Admission requires exactly those two jobs and analyses, with missing or extra languages rejected.”
+- Source: `docs/publishing.md`: “Admission requires exactly those four jobs and analyses, with missing or extra languages rejected.”
- Evidence: `scripts/github-release-artifact.test.ts`
- Assumptions: `github-api`, `github-enforcement`
- Not verified: Only the enumerated example cases are checked.
diff --git a/docs/contracts.md b/docs/contracts.md
index 5005bbb3..48bd1fa0 100644
--- a/docs/contracts.md
+++ b/docs/contracts.md
@@ -128,7 +128,7 @@ matches some IDs lists the installed ones and exits 0.
{
"ok": true,
"contract": "ghostget.contract-catalog.v1",
- "ghostget": { "version": "0.18.63" },
+ "ghostget": { "version": "0.18.64" },
"generatedAt": "2026-09-21T20:00:00.000Z",
"vocabulary": {
"risks": ["R1", "R2", "R3", "R4"],
@@ -186,7 +186,7 @@ does not parse is an error (exit 3) and no catalog is read.
{
"ok": false,
"contract": "ghostget.contract-check.v1",
- "ghostget": { "version": "0.18.63" },
+ "ghostget": { "version": "0.18.64" },
"plan": { "collectionKey": "hraness-social-profile-statistics", "reads": 15 },
"reads": [
{ "index": 0, "accountKey": "x-hraness", "adapter": "x-web", "operation": "profiles.read",
diff --git a/docs/publishing.md b/docs/publishing.md
index 77c7353e..933824f0 100644
--- a/docs/publishing.md
+++ b/docs/publishing.md
@@ -32,11 +32,11 @@ Explicit focused local/native and coupled reproductions still apply under
`scripts/release-source-ci.ts` reads GitHub's current run attempt directly. It
requires the exact repository, active workflow ID/path, main-push source and tree,
-all eleven successful jobs, and ten actual checkout logs. Each source job records
-its exact workflow/lock hashes, Node/npm/Bun versions and GitHub-hosted platform
-before its frozen install. Admission also requires both successful
-exact-source CodeQL jobs and current main analyses for Actions
-and JavaScript/TypeScript, plus the successful
+all nineteen successful jobs, and eighteen actual checkout logs. Each source job
+records its exact workflow/lock hashes, Node/npm/Bun versions and GitHub-hosted platform
+before its frozen install. Admission also requires all four successful
+exact-source CodeQL jobs and current main analyses for Actions,
+JavaScript/TypeScript, Python, and Rust, plus the successful
security comparison on the merged PR's identical tree. Any present main comparison must succeed;
analysis result counts are recorded without asserting that no alerts exist.
The CodeQL app's check must identify that exact PR through its returned PR
@@ -59,9 +59,10 @@ permissions for these API reads. Publication and attestation permissions stay
unchanged.
PR #272 moved the repository-owned Swift menu into the pinned shared desktop
-foundation. The current repository has no tracked Swift source; GitHub's
-default setup scans Actions and JavaScript/TypeScript. Admission requires
-exactly those two jobs and analyses, with missing or extra languages rejected.
+foundation. The current repository has no tracked Swift source; its verification
+oracles added in PRs #357 and #358 include Python and Rust. GitHub's default setup
+scans Actions, JavaScript/TypeScript, Python, and Rust. Admission requires exactly
+those four jobs and analyses, with missing or extra languages rejected.
The shared foundation owns its native source checks and published artifacts.
Reintroducing another source language requires a reviewed coverage update.
@@ -242,12 +243,12 @@ delivery proceeds through a new source-qualified version.
## Install the canonical release
-These commands require the matching published immutable v0.18.63 release.
+These commands require the matching published immutable v0.18.64 release.
For the CLI:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.63/hraness-ghostget-0.18.63.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
ghostget --version
ghostget doctor --json
```
diff --git a/kb/launch/social-kit.md b/kb/launch/social-kit.md
index 0b56a6bd..cf3c5a98 100644
--- a/kb/launch/social-kit.md
+++ b/kb/launch/social-kit.md
@@ -57,7 +57,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.63.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -115,7 +115,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.63.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -173,7 +173,7 @@ The plan is for GhostGet to stay small. Your agent does the thinking, and GhostG
Post 9 of 9, 192 characters
```text
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.63.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -197,7 +197,7 @@ GhostGet is for Claude Code, Codex, Cursor, and other agents that run commands o
The plan is for GhostGet to stay small. Your agent does the thinking, and GhostGet runs only actions someone has reviewed. Each new service arrives as reviewed actions with their own previews.
-GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.63.
+GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
https://ghostget.com/blog/introducing-ghostget/
```
@@ -224,8 +224,8 @@ Topics: Developer Tools, Artificial Intelligence, Open Source
- Sometimes a post goes through but the answer gets lost on the way back. GhostGet writes down every send before it leaves and never sends it again on its own until it knows what happened.
- Anything beyond a read starts as a preview that shows the service, the account, and exactly what will be sent. Your agent can prepare it. Nothing is sent until someone confirms that exact preview.
- GhostGet is for Claude Code, Codex, Cursor, and other agents that run commands on your Mac or Linux machine and need to read the web and use the accounts you already have.
-- GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.63.
-- Latest release: v0.18.63. https://ghostget.com/blog/introducing-ghostget/
+- GhostGet is free, MIT licensed, and runs on macOS and Linux. The first step reads a public page and needs no account. Latest release: v0.18.64.
+- Latest release: v0.18.64. https://ghostget.com/blog/introducing-ghostget/
## Beats
@@ -251,4 +251,4 @@ Topics: Developer Tools, Artificial Intelligence, Open Source
- claimsTotal: 247. verification/claims.json, every claim
- claimsEvidenced: 228. verification/claims.json, status evidenced
- claimsConfigReadback: 15. verification/claims.json, layer configuration-readback
-- status: Latest release: v0.18.63. package.json version
+- status: Latest release: v0.18.64. package.json version
diff --git a/package.json b/package.json
index c1638012..27e3e5bc 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@hraness/ghostget",
- "version": "0.18.63",
+ "version": "0.18.64",
"description": "GhostGet gives your AI agent named web actions: read a page, archive one media item, or use a connected account, without credentials or a browser to steer.",
"license": "MIT",
"type": "module",
@@ -649,7 +649,8 @@
"src/control/registry.ts",
"src/control/registry-words.ts",
"src/control/retire-tray.ts",
- "src/control/status-view.ts"
+ "src/control/status-view.ts",
+ "src/update.ts"
],
"scripts": {
"check:theme": "bun scripts/check-paper-theme.mjs",
@@ -705,7 +706,8 @@
"typescript": "6.0.3",
"@1password/sdk": "0.5.0",
"@hraness/desktop-foundation": "https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz",
- "@steipete/sweet-cookie": "0.4.3"
+ "@steipete/sweet-cookie": "0.4.3",
+ "@hraness/cli-update": "https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz"
},
"devDependencies": {
"@hraness/design-kit": "github:hraness/design-kit#v0.35.0",
diff --git a/scripts/npm-release-workflow.test.ts b/scripts/npm-release-workflow.test.ts
index 09d29cb1..01e65a9f 100644
--- a/scripts/npm-release-workflow.test.ts
+++ b/scripts/npm-release-workflow.test.ts
@@ -1220,7 +1220,7 @@ describe("npm publication contract", () => {
(MAX_UNPACKED_BYTES + MAX_PACKED_ENTRIES * 1_023 + 1_024) / 512,
) * 512,
);
- expect(MAX_PACKAGE_TAR_BYTES).toBe(24_687_104);
+ expect(MAX_PACKAGE_TAR_BYTES).toBe(24_695_296);
expect(MAX_PACKAGE_TAR_BYTES % 512).toBe(0);
expect(artifact).toContain("maxOutputLength: MAX_PACKAGE_TAR_BYTES");
expect(artifact).not.toContain("const maximumTarBytes");
@@ -1430,8 +1430,8 @@ describe("npm publication contract", () => {
expect(budget).toContain("785b8fa60c329d7ac46bc8fcf4d959b5fa9d96455bba9e7cdea63f6a3827c4f6");
expect(Object.isFrozen(repairPackageMeasurement)).toBeTrue();
expect(repairPackageMeasurement).toMatchObject({
- archiveSha256: "9f6ff0fac3c7bd70ecb5f94c11e0b3ee5a7e5bb48cb77a24129f0af4e7538a39",
- packedBytes: 12_140_613, unpackedBytes: 24_047_027, entryCount: 624,
+ archiveSha256: "99bdbc86d7e2016b7a003496d4d6a2bec847d6c8c3b01aed57c1b55a3b25cbb3",
+ packedBytes: 12_154_912, unpackedBytes: 24_054_352, entryCount: 625,
packedPlatformProjection: 12_387, packedPortabilityAllowance: 4_096,
payloadPlatformProjection: 353, payloadAllowance: 65,
});
@@ -1447,8 +1447,8 @@ describe("npm publication contract", () => {
expect(budget).toContain("24,024,705 + 353 + 65 = 24,025,123 unpacked");
expect(budget).toContain("12,152,562 + 12,387 + 4,096 = 12,169,045 packed");
expect(budget).toContain("24,093,786 + 353 + 65 = 24,094,204 unpacked");
- expect(MAX_PACKED_BYTES).toBe(12_157_096);
- expect(MAX_PACKED_BYTES).toBe(12_140_613 + 12_387 + 4_096);
+ expect(MAX_PACKED_BYTES).toBe(12_171_395);
+ expect(MAX_PACKED_BYTES).toBe(12_154_912 + 12_387 + 4_096);
expect(budget).toContain("aa127b3193c9bb3b0cb5deece5927be60ccb7111a50169320d322ffdeaa13f39");
expect(budget).toContain("0c331bab3ab3df69a108e18f5f29845b0db90c281cbd6455c0d90fa0b24081e2");
expect(budget).toContain("873cad8139fda303e2d19c6afd61cf549cf9b4d1d76b2a1d6d632a6afe6bd0d1");
@@ -1543,8 +1543,8 @@ describe("npm publication contract", () => {
expect(budget).toContain("11,696,091 + 4,096 = 11,700,187");
expect(budget).toContain("35449445752 attempt 1, package job 105913938839");
expect(budget).toContain("exactly 596 files");
- expect(MAX_PACKED_ENTRIES).toBe(624);
- expect(MAX_PACKED_FILES).toBe(624);
+ expect(MAX_PACKED_ENTRIES).toBe(625);
+ expect(MAX_PACKED_FILES).toBe(625);
expect(budget).toContain("Ghostget 0.18.6 same-boot setup-cleanup candidate over main edbe567");
expect(budget).toContain("11,656,173");
expect(budget).toContain("22,513,450 payload bytes across exactly 557 files");
@@ -1569,7 +1569,7 @@ describe("npm publication contract", () => {
expect(budget).toContain("47684b3e2eb5cf3ed07fbb520aade8c7251d993f75262fbf1af627d9081a1a5f");
expect(budget).toContain("23,688,277 + 353 + 65 = 23,688,695");
expect(budget).toContain("23,759,283 + 353 + 65 = 23,759,701");
- expect(MAX_UNPACKED_BYTES).toBe(24_047_445);
+ expect(MAX_UNPACKED_BYTES).toBe(24_054_770);
expect(budget).toContain("23,037,873 + 65 = 23,037,938");
expect(budget).toContain("f9f3ab38a682690ceaa2699a7309997512030f0fa500a9dc29dcd108123dc41f");
expect(budget).toContain("23,038,557 + 65 = 23,038,622");
@@ -1602,7 +1602,7 @@ describe("npm publication contract", () => {
expect(budget).toContain("01875f12ab73a49d6c7d6bf520dc3d318db816addee2fa7981889f35c958cf7c");
expect(budget).toContain("b12909f08f7c19460ced56e30619f4860a1183f4b0106170c07837dae577a937");
expect(budget).toContain("0b212ac291218528dcf979370110a36f10850e046ca90a536057d9a44e807d1d");
- expect(MAX_UNPACKED_BYTES).toBe(24_047_027 + 353 + 65);
+ expect(MAX_UNPACKED_BYTES).toBe(24_054_352 + 353 + 65);
expect(budget).toContain("22,794,052 + 65 = 22,794,117");
expect(budget).toContain("c482efe748f880e3717727d6d39fd92a68953e6eea766642b329ba47ae772d80");
expect(budget).toContain("22,759,423 + 65 = 22,759,488");
@@ -1636,10 +1636,10 @@ describe("npm publication contract", () => {
expect(Object.isFrozen(range)).toBe(true);
}
expect(packageArtifactBudget).toEqual({
- entryCount: { min: 624, max: 624 },
- fileCount: { min: 624, max: 624 },
- packedBytes: { min: 1_600_000, max: 12_157_096 },
- unpackedBytes: { min: 9_000_000, max: 24_047_445 },
+ entryCount: { min: 625, max: 625 },
+ fileCount: { min: 625, max: 625 },
+ packedBytes: { min: 1_600_000, max: 12_171_395 },
+ unpackedBytes: { min: 9_000_000, max: 24_054_770 },
});
});
diff --git a/scripts/package-budget.ts b/scripts/package-budget.ts
index 32ab2e01..904535c2 100644
--- a/scripts/package-budget.ts
+++ b/scripts/package-budget.ts
@@ -2351,23 +2351,53 @@
// Preserve the same projections and allowances:
// 12,152,630 + 12,387 + 4,096 = 12,169,113 packed;
// 24,046,313 + 353 + 65 = 24,046,731 unpacked.
-// The 0.18.63 studio design release over reviewed main 20c1f999 retains
-// exactly 624 entries after its changelog, current version pins and rebuilt dist.
-// Two npm 11.19.0 packs --ignore-scripts with official Node 24.20.0 on darwin
-// arm64 measured 12,140,613 packed and 24,047,027 unpacked bytes; byte-identical
-// archive SHA-256 9f6ff0fac3c7bd70ecb5f94c11e0b3ee5a7e5bb48cb77a24129f0af4e7538a39.
-// Preserve the reviewed platform projections and portability allowances:
-// 12,140,613 + 12,387 + 4,096 = 12,157,096 packed;
-// 24,047,027 + 353 + 65 = 24,047,445 unpacked.
+// The earlier 0.18.62 updater candidate (not published) over 0.18.61
+// main adds src/update.ts and
+// the immutable shared updater dependency while retaining the new analytics
+// and marketing source. Two official Node 24.20.0/npm 11.19.0 packs
+// --ignore-scripts on darwin arm64 with zlib 1.3.2.1-motley-42c2f19
+// measured 625 entries, 12,154,748 packed and 24,053,537 unpacked bytes;
+// byte-identical SHA-256 c4184a9c6bb812cc7b3b8dab258a9f44271268f68ca769d47429e56fa63b25b1.
+// Preserve the same projections and allowances:
+// 12,154,748 + 12,387 + 4,096 = 12,171,231 packed;
+// 24,053,537 + 353 + 65 = 24,053,955 unpacked.
+// The earlier reserved 0.18.63 updater (not published) integrated 0.18.62
+// marketing and search-referrer privacy source (86dd8cdc). Both changelog
+// sections are retained; updater behavior and inventory are unchanged.
+// Two official Node 24.20.0/npm 11.19.0 --ignore-scripts packs on darwin
+// arm64 with zlib 1.3.2.1-motley-42c2f19 measured 625 entries,
+// 12,154,769 packed and 24,053,717 unpacked bytes; byte-identical SHA-256
+// 723397dd53644ed3c8c42c57ed62eb460d23d255b32354c2b091d65c05fba2a3.
+// Preserve the same projections and allowances:
+// 12,154,769 + 12,387 + 4,096 = 12,171,252 packed;
+// 24,053,717 + 353 + 65 = 24,054,135 unpacked.
+// The competing tagged 0.18.63 studio source at main773c537 contains no
+// updater. Its immutable annotated tag is preserved after rejected publication.
+// Its incoming budget records two Node 24.20.0/npm 11.19.0 packs at
+// 624 entries, 12,140,613 packed and 24,047,027 payload bytes; SHA-256
+// 9f6ff0fac3c7bd70ecb5f94c11e0b3ee5a7e5bb48cb77a24129f0af4e7538a39.
+// The exact main773c537 non-updater baseline reproduced with qualified
+// Node 24.20.0/npm 11.19.0/zlib 1.3.2.1-motley-42c2f19 on darwin arm64:
+// two identical archives, 624 entries, 12,152,843 packed, 24,047,027 payload;
+// SHA-256 ed461e46d1d9d4514cffe577415bd641d3cbfbd1c3cde0619d0fe55acf6170bb.
+// The forward 0.18.64 updater preserves that studio source and failed-tag
+// history. Two identical packs under the same toolchain measured 625 entries,
+// 12,154,912 packed and 24,054,352 payload bytes; SHA-256 below. The measured
+// addition is one src/update.ts file, 2,069 packed and 7,325 payload bytes.
+// The 425-byte payload increase over the previous updater candidate is solely
+// CHANGELOG.md, retaining studio changes and the annotated-tag failure.
+// Preserve the same projections and allowances:
+// 12,154,912 + 12,387 + 4,096 = 12,171,395 packed;
+// 24,054,352 + 353 + 65 = 24,054,770 payload.
export const repairPackageMeasurement = Object.freeze({
- scope: "Ghostget 0.18.63 studio design release over reviewed main 20c1f999",
+ scope: "Ghostget 0.18.64 CLI updater over preserved tagged studio main773c537",
command: "npm pack --ignore-scripts",
npmVersion: "11.19.0",
platform: "darwin-arm64",
- archiveSha256: "9f6ff0fac3c7bd70ecb5f94c11e0b3ee5a7e5bb48cb77a24129f0af4e7538a39",
- packedBytes: 12_140_613,
- unpackedBytes: 24_047_027,
- entryCount: 624,
+ archiveSha256: "99bdbc86d7e2016b7a003496d4d6a2bec847d6c8c3b01aed57c1b55a3b25cbb3",
+ packedBytes: 12_154_912,
+ unpackedBytes: 24_054_352,
+ entryCount: 625,
packedPlatformProjection: 12_387,
packedPortabilityAllowance: 4_096,
payloadPlatformProjection: 353,
diff --git a/scripts/package-smoke.ts b/scripts/package-smoke.ts
index aedaaab8..de9e36cc 100644
--- a/scripts/package-smoke.ts
+++ b/scripts/package-smoke.ts
@@ -629,6 +629,10 @@ async function verifyPackagedSkill(
) {
throw new Error("Packed Ghostget must pin the immutable Message Like Me v0.7.0 consumer contract.");
}
+ if (!("@hraness/cli-update" in manifest.dependencies)
+ || manifest.dependencies["@hraness/cli-update"] !== "https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz") {
+ throw new Error("Packed GhostGet must pin the immutable CLI updater release.");
+ }
const messageLikeMeRoot = join(
consumer,
"node_modules",
@@ -786,6 +790,16 @@ try {
consumer,
`ghostget ${packageVersion}\n`,
);
+ const updateStatus = Bun.spawn([join(consumer, "node_modules", ".bin", "ghostget"), "update", "status", "--json"], {
+ cwd: consumer, stdin: "ignore", stdout: "pipe", stderr: "pipe", timeout: 15_000,
+ env: { ...process.env, GHOSTGET_STATE_HOME: join(consumer, "isolated-update-state"), HRANESS_SUPPORT: "off" },
+ });
+ const [updateExit, updateStdout, updateStderr] = await Promise.all([updateStatus.exited, new Response(updateStatus.stdout).text(), new Response(updateStatus.stderr).text()]);
+ const updateResult = requireRecord(JSON.parse(updateStdout) as unknown, "Installed CLI update status");
+ if (updateExit !== 0 || updateStderr !== "" || updateResult.schema !== "hraness.cli-update.v1"
+ || updateResult.package !== packageName || updateResult.status !== "unsupported" || updateResult.supported !== false) {
+ throw new Error("Installed project CLI must report unsupported updates with one JSON result.");
+ }
await run([
process.execPath,
"--eval",
diff --git a/scripts/standalone-smoke.ts b/scripts/standalone-smoke.ts
index 30cd148e..e5e9e872 100644
--- a/scripts/standalone-smoke.ts
+++ b/scripts/standalone-smoke.ts
@@ -27,6 +27,7 @@ type InstalledClosurePackage = {
const expectedClosureRuntimeDependencies = Object.freeze({
"@1password/sdk": "0.5.0",
"@hraness/accounts-cli": "github:hraness/accounts-cli#v0.1.3",
+ "@hraness/cli-update": "https://github.com/hraness/cli-update/releases/download/v0.1.0/hraness-cli-update-0.1.0.tgz",
"@hraness/desktop-foundation": "https://github.com/hraness/desktop-foundation/releases/download/v0.9.0/hraness-desktop-foundation-0.9.0.tgz",
"@hraness/wordcell": "https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz",
"@hraness/local-custody": "https://github.com/hraness/local-custody/releases/download/v0.9.1/hraness-local-custody-0.9.1.tgz",
diff --git a/scripts/verification-claims.test.ts b/scripts/verification-claims.test.ts
index 33fd1194..ad8e82c2 100644
--- a/scripts/verification-claims.test.ts
+++ b/scripts/verification-claims.test.ts
@@ -547,7 +547,7 @@ describe("guideline coverage", () => {
inserted.push(line, "");
const changed = guide.lines.toSpliced(point, 0, ...inserted).join("\n");
expect(await findingsFor({ [guide.path]: changed }, register)).toEqual([unruled(guide.path, words.join(" "))]);
- }));
+ }), {}, "verification-claims/guideline-additions");
});
test("changing any guideline requires reviewing its claims and digest", async () => {
diff --git a/skills/ghostget/references/install.md b/skills/ghostget/references/install.md
index e33bbac0..3ebea533 100644
--- a/skills/ghostget/references/install.md
+++ b/skills/ghostget/references/install.md
@@ -17,14 +17,14 @@ If Bun is missing, stop and direct the user to the official
[Bun installation guide](https://bun.sh/docs/installation). Do not switch
package managers or pipe an unreviewed installer into a shell.
-This reference is authored for the exact v0.18.63 release coordinate. Use it
+This reference is authored for the exact v0.18.64 release coordinate. Use it
only from the matching release-bound Agent Skill after its canonical archive and
immutable GitHub Release exist. If the coordinate is not public, stop instead
of substituting `main`, another tag, or a different package version. Install
that exact release and verify a public-page read:
```sh
-bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.63/hraness-ghostget-0.18.63.tgz
+bun add --global https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
ghostget read https://example.com
```
@@ -47,21 +47,21 @@ for the requested workflow; an unconnected provider or missing media tool does
not prevent a public-page read, and overall provider readiness can be false on
a fresh installation.
-The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.63` is an optional npm
+The package is `@hraness/ghostget`; `@hraness/ghostget@0.18.64` is an optional npm
mirror only after verified registry publication. Canonical installation does not
wait for registry publication.
When upgrading from Wrench, use `ghostget` for new commands and
`GHOSTGET_STATE_HOME` for an explicit state root. Existing state is selected in
place; do not rename, copy, or delete a state directory as part of the upgrade.
-The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.63/docs/ghostget-migration.md)
+The [migration guide](https://github.com/hraness/ghostget/blob/v0.18.64/docs/ghostget-migration.md)
explains the retained state aliases and durable protocol names.
Do not clone the repository merely to run the CLI. Importing the SDK is a
separate project dependency and does not install a global command:
```sh
-bun add https://github.com/hraness/ghostget/releases/download/v0.18.63/hraness-ghostget-0.18.63.tgz
+bun add https://github.com/hraness/ghostget/releases/download/v0.18.64/hraness-ghostget-0.18.64.tgz
```
`ghostget adapter sync-bundled` upgrades exact bundled baselines, including an
@@ -81,3 +81,22 @@ or another operating-system dependency only when the requested route needs
it and the user has authorized that machine change. Re-run doctor after the
install. Do not weaken a provider or archive boundary when an optional tool is
absent.
+
+## Update the CLI
+
+Supported global Bun and npm installs on macOS and Linux check for a stable
+GhostGet release before ordinary commands, at most once a day. Install the
+GitHub CLI (`gh`) and authenticate it with `gh auth login --hostname github.com`
+to let the updater download verified release archives. If your installed CLI
+does not have `update`, repeat this guide's global CLI install command once.
+Run `ghostget update` to update now,
+`ghostget update check --json` to inspect a release, or `ghostget update status`
+to see the saved setting. Use `ghostget update disable` to turn automatic updates
+off and `ghostget update enable` to turn them on. Exact version pins require
+`enable` before following new releases.
+
+Set `HRANESS_NO_UPDATE=1` for a command that must use the installed version.
+CI, nested clients, help, and gateway-only mode skip automatic checks. Manage
+source checkouts, project dependencies, and unsupported installations with their
+package manager. Agent Skill copies are refreshed separately through the skill
+installer. Updating the CLI preserves connected accounts and saved content.
diff --git a/src/beeper-client-types.ts b/src/beeper-client-types.ts
index c2631067..21a822e1 100644
--- a/src/beeper-client-types.ts
+++ b/src/beeper-client-types.ts
@@ -96,7 +96,7 @@ export type BeeperContactInteractionExportReceipt = Readonly<{
implementation: Readonly<{
producer: Readonly<{
package: "@hraness/ghostget";
- version: "0.18.63";
+ version: "0.18.64";
}>;
officialCli: Readonly<{
implementation: "github.com/beeper/cli";
diff --git a/src/cli.ts b/src/cli.ts
index 7953cc7b..9086a455 100755
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -114,7 +114,7 @@ export function isImmediateGhostgetVersionRequest(
&& (second === undefined || second === "--json");
}
-/** `ghostget 0.18.63`, or `{"name":"ghostget","version":"0.18.63"}` with `--json`. */
+/** `ghostget 0.18.64`, or `{"name":"ghostget","version":"0.18.64"}` with `--json`. */
export function ghostgetVersionText(rawArguments: readonly string[]): string {
return rawArguments.includes("--json")
? `${JSON.stringify({ name: "ghostget", version: GHOSTGET_VERSION })}\n`
@@ -343,22 +343,45 @@ export async function runGhostgetCliProcess(
}
}
-if (import.meta.main) {
+/** Update only the real executable; SDK callers keep their original behavior. */
+export async function runGhostgetExecutable(
+ args: readonly string[] = process.argv.slice(2),
+ startUpdate = async (arguments_: readonly string[], depth: number | null) =>
+ (await import("./update")).startGhostgetUpdate(arguments_, depth),
+ runCommand = runGhostgetCliProcess,
+ reportRun = async (arguments_: readonly string[]) =>
+ (await import("./telemetry")).reportGhostgetCliRun(GHOSTGET_VERSION, process.env, arguments_),
+): PromiseA provider CLI is pinned bytes behind a semantic contractGhostGet starts the exact executable without a shell under private config, data, cache, and temporary roots. It strips ambient credentials and defaults, enforces its own deadline and byte bounds, and treats a lost or malformed post-dispatch response as indeterminate. A version range, package-manager channel, tag, launcher, or reported version cannot replace executable-byte identity.
Supported global installations check the fixed GhostGet GitHub repository for a stable immutable release. The updater verifies the selected archive and its published SHA-256 before asking the installation's package manager to replace it. It waits for running commands to finish and leaves connected accounts and saved content in place. Source checkouts and project dependencies keep their existing package-manager workflow.
+Automatic checks run before ordinary commands, at most once a day. Use ghostget update disable to opt out, ghostget update status to inspect the setting, or HRANESS_NO_UPDATE=1 for a command that must use the installed version. Exact version pins require ghostget update enable before following new releases. See the update guide for requirements and limits.
Source plugins ship as trusted in-process code. A portable plugin requires an explicit trust decision for one verified, content-addressed bundle and runs through a denied-by-default child-process protocol. The protocol limits the services a well-behaved plugin can request and contains ordinary crashes.
diff --git a/website/source/docs-tutorials-getting-started.html b/website/source/docs-tutorials-getting-started.html index f69a28dc..71851d3a 100644 --- a/website/source/docs-tutorials-getting-started.html +++ b/website/source/docs-tutorials-getting-started.html @@ -86,6 +86,13 @@With Bun, use {{GHOSTGET_SKILL_INSTALL_COMMAND_BUNX}}. Start a new agent session, then ask: “Use GhostGet to read https://example.com and summarize it.” The skill supplies instructions; the CLI you installed supplies the executable.
Supported global Bun and npm installs on macOS and Linux check for a stable release before ordinary commands, at most once a day. Install the GitHub CLI and authenticate it with gh auth login --hostname github.com so GhostGet can download verified release archives. For an older CLI without update, repeat the global CLI install command in this guide once. Run ghostget update to update immediately or ghostget update check --json to inspect the latest release.
Use ghostget update disable to turn automatic updates off, ghostget update enable to turn them on, and ghostget update status to inspect the setting. Exact version pins require enable before following new releases. Set HRANESS_NO_UPDATE=1 for a command that must use the installed version.
CI, nested clients, help, and gateway-only mode skip automatic checks. Manage source checkouts and project dependencies with their package manager. Refresh Agent Skill copies separately through the skill installer. Updating the CLI preserves your connected accounts and saved content.
+Choose a new directory for a local Markdown vault, then save your first page there:
diff --git a/website/source/index.html b/website/source/index.html index aaa90846..15da88ce 100644 --- a/website/source/index.html +++ b/website/source/index.html @@ -310,7 +310,7 @@Public-page reads need no account. Connect a service when you want its actions. A free Hraness account is optional and lets you manage support across our studio.
Yes, for connected services. Sign-ins stay in a local store and are omitted from GhostGet’s outputs. Your agent keeps any separate tools and permissions you give it.
Nothing. GhostGet is free and MIT licensed, with no hosted tier and no paid plan. You pay your model provider and any service subscriptions you already have.
The CLI and SDK send no analytics. Actions, sign-in, and messaging sync contact their selected services. This website uses cookieless analytics and honors Do Not Track. See privacy details.
The CLI sends an aggregate run report; set HRANESS_TELEMETRY=off to disable it. The SDK does not send that report. Actions, sign-in, and messaging sync contact their selected services. Supported global CLI installs also check GitHub for updates; use ghostget update disable to turn those checks off. This website uses cookieless analytics and honors Do Not Track. See privacy details.
macOS and Linux with Bun 1.3.14. Actions that read iMessage or Apple Photos need macOS. Beeper actions need Beeper Desktop on the same machine, and WhatsApp reads need a linked device.
Your agent supplies the model and plan. GhostGet supplies the CLI, SDK, account connections, and local controls.
Yes, in two cases. When a page can’t be read without rendering it, GhostGet runs its own browser, at most two at once; page reads try a single HTTP request first. Some signed-in provider actions, such as certain LinkedIn and Instagram reads, also run inside a contained browser session. In both cases the browser accepts no selectors, coordinates, or scripts from the caller.
New installations use ~/.local/share/ghostget as the default state home. Existing Wrench state is selected in place without moving or copying it. An operator can select a dedicated path with GHOSTGET_STATE_HOME; the earlier state-root environment aliases remain supported, and conflicting roots are rejected. GhostGet restricts its owned state directories to the current operating-system account and rejects shared, symbolic-link, or unexpectedly replaced state paths before trusted state is read.
State can include auth locators, managed OAuth credentials, encrypted exact-query snapshots, normalized read projections, provider and plugin state, plans, receipts, journals, recovery evidence, session material, locally installed tools, and capture working state. Durable page captures and media archives live in the vault or output path selected by the operator.
+CLI update preferences use ~/.local/state/hraness-cli-update. Installation and package-manager coordination records use separate private local paths; verified release archives are retained beside the global package directory. These records contain package and installation details, not connected-account data or captured content. Retained archives let the global package manager reinstall its recorded dependencies.
GhostGet's local control service stores permissions and imported API definitions in private files and public web request metadata in SQLite. The control owner (ghostget control serve, or the TUI) owns this service's private channel while it runs and lists output files. Activity retains up to 10,000 completed entries for at most 30 days, with active requests retained until settled. Entries contain timing, method, origin, configured endpoint scope, decision, status, and byte count. They do not contain query strings, response bodies, credentials, or hashes of the requested URL. History stays on the operator's machine.
The Apple Photos contact-evidence workflow creates bounded, transient full copies of the private Photos and Contacts SQLite databases with VACUUM INTO under an operation-owned private temporary directory and queries them without network access or source mutation. Those copies can include unselected columns and raw blobs. The privacy exclusions apply only to the returned JSON. GhostGet does not open, copy, or ask Photos to materialize referenced photo or video asset files. Ordinary success and handled failure remove the captures; forced termination or a crash can leave an exact leased copy until a later export proves its owner is dead and reclaims it. Its stdout artifact contains exact Photos and Contacts identifiers and biometric-derived cluster counts, so the caller must keep that output on an appropriately private path.
Local custody does not mean that every stored byte is encrypted. Exact provider-read snapshots and normalized projections use authenticated local encryption. A GhostGet-managed Gmail OAuth JSON file, for example, is mode-restricted private state, but it is not an operating-system keychain and is not encrypted at rest. Protect the local account, disk, state home, selected output paths, and backups.
@@ -73,6 +74,8 @@A page read or capture contacts the requested source through its selected acquisition method. URL metadata work can perform read-only Archive.today discovery. A media command contacts the selected source and may run operator-selected local tools. An authenticated capability sends only the checked request and credential material required by its exact provider, origin, transport, account realm, and contract.
Those sites and providers can observe ordinary request information such as the source IP address, request timing, their own account identifier, and the bounded action or query. A signed-in browser page can also emit telemetry owned by that provider. GhostGet does not turn those interactions into a claim that third parties retain nothing.
The CLI and SDK do not send ghostget.com analytics. They do make the network requests required by the operation the caller selected. GhostGet does not include a hosted relay, model, planner, or tool loop. A calling agent, application, terminal, logging system, or model service can receive whatever output the operator gives it; that downstream handling is outside GhostGet.
+The standalone CLI sends aggregate run telemetry to account.hraness.com: the CLI version, the top-level command name, and a local installation token used to count active installations. It excludes command arguments, paths, account data, and output. Set HRANESS_TELEMETRY=off or GHOSTGET_TELEMETRY=off to disable it. SDK calls do not send this CLI telemetry.
Supported global CLI installations also check GitHub for stable releases before ordinary commands, at most once a day, and download verified updates through the GitHub CLI. GitHub receives ordinary connection and release-request information; the updater does not send account state, captured content, or command arguments. Use ghostget update disable to turn automatic checks off or HRANESS_NO_UPDATE=1 for one command. SDK imports, help, CI, nested clients, and gateway-only mode do not trigger automatic checks.
WhatsApp automation starts linked-device synchronization only after an explicit owner start with the required managed permission. That sync maintains a provider connection until stopped; it is not a single local read. Initialization and the separate Message Like Me export do not start sync. Stopping the host or revoking its sync permission closes owned work, but does not remotely unlink the account or erase retained provider history.