@pushary/agent-hooks 0.60.0 → 0.62.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.
- package/CHANGELOG.md +161 -0
- package/README.md +52 -9
- package/dist/bin/pushary-claude.js +6 -6
- package/dist/bin/pushary-clean.js +83 -38
- package/dist/bin/pushary-codex-hook.js +6 -6
- package/dist/bin/pushary-codex.js +4 -4
- package/dist/bin/pushary-connect.d.ts +1 -0
- package/dist/bin/pushary-connect.js +92 -0
- package/dist/bin/pushary-daemon.js +42 -5
- package/dist/bin/pushary-doctor.js +246 -71
- package/dist/bin/pushary-gemini-hook.js +6 -6
- package/dist/bin/pushary-hook.js +10 -5
- package/dist/bin/pushary-login.d.ts +1 -0
- package/dist/bin/pushary-login.js +156 -0
- package/dist/bin/pushary-logout.d.ts +1 -0
- package/dist/bin/pushary-logout.js +144 -0
- package/dist/bin/pushary-mode.js +24 -13
- package/dist/bin/pushary-notification-hook.js +4 -4
- package/dist/bin/pushary-permission-denied-hook.js +5 -5
- package/dist/bin/pushary-permission-hook.js +5 -5
- package/dist/bin/pushary-post-hook.js +4 -4
- package/dist/bin/pushary-prompt-hook.js +4 -4
- package/dist/bin/pushary-session-end-hook.js +4 -4
- package/dist/bin/pushary-session-start-hook.js +4 -4
- package/dist/bin/pushary-setup.js +769 -518
- package/dist/bin/pushary-stats.js +6 -1
- package/dist/bin/pushary-status.d.ts +1 -0
- package/dist/bin/pushary-status.js +206 -0
- package/dist/bin/pushary-stop-hook.js +4 -4
- package/dist/bin/pushary-stopfailure-hook.js +4 -4
- package/dist/bin/pushary-suggestions.js +10 -5
- package/dist/bin/pushary-upgrade.js +184 -14
- package/dist/bin/pushary-wait.js +21 -12
- package/dist/bin/pushary.js +42 -60
- package/dist/chunk-247C4RE5.js +236 -0
- package/dist/{chunk-NKXSILEW.js → chunk-2UMNXADU.js} +18 -2
- package/dist/chunk-3EGEA4KH.js +44 -0
- package/dist/chunk-3O27ZSRE.js +569 -0
- package/dist/{chunk-BE5X3WXL.js → chunk-5SO3CEGJ.js} +5 -5
- package/dist/chunk-7QLSKOSU.js +19 -0
- package/dist/chunk-E2U35RLD.js +108 -0
- package/dist/chunk-ER657KRJ.js +168 -0
- package/dist/{chunk-WNRYRN2R.js → chunk-HIIBSYFJ.js} +5 -5
- package/dist/{chunk-DWED7BS3.js → chunk-HNTKTK5B.js} +1 -1
- package/dist/chunk-IIZZYUWK.js +18 -0
- package/dist/{chunk-J7JWI3KU.js → chunk-MUEW424A.js} +19 -1
- package/dist/chunk-N4DA4CJB.js +57 -0
- package/dist/chunk-N7XJ4L2W.js +79 -0
- package/dist/chunk-ONVEYEVR.js +44 -0
- package/dist/chunk-QHOVJXOT.js +255 -0
- package/dist/chunk-SP7OZXCQ.js +224 -0
- package/dist/chunk-TX7KBKT7.js +79 -0
- package/dist/chunk-UXXRRXUS.js +93 -0
- package/dist/chunk-YJ7YZRVV.js +217 -0
- package/dist/src/index.js +5 -5
- package/package.json +11 -4
- package/dist/chunk-BQLCELYC.js +0 -332
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.62.0
|
|
4
|
+
|
|
5
|
+
### `pushary clean` could leave your API key on disk
|
|
6
|
+
|
|
7
|
+
If `CODEX_HOME` was set, `clean` looked for Codex's `config.toml` in `~/.codex`
|
|
8
|
+
and never found the real one. It printed `Codex config (not found)`, exited `0`,
|
|
9
|
+
and left the key in the file. A user who ran `clean` to remove their key was told
|
|
10
|
+
it had worked.
|
|
11
|
+
|
|
12
|
+
The same fault ran through the rest of the Codex path. With `CODEX_HOME` set:
|
|
13
|
+
|
|
14
|
+
- `setup` wrote the MCP server, the trust hashes and the notify entry to
|
|
15
|
+
`~/.codex/config.toml` while writing `hooks.json`, `AGENTS.md` and the skill to
|
|
16
|
+
the real `CODEX_HOME`, splitting the install across two directories
|
|
17
|
+
- `setup --dry-run` named the relocated path, so the preview and the real run
|
|
18
|
+
disagreed about where the key would land
|
|
19
|
+
- `doctor` read the relocated config and reported the key missing
|
|
20
|
+
- `logout` treated an exported-but-empty `CODEX_HOME` as a path relative to the
|
|
21
|
+
current directory
|
|
22
|
+
|
|
23
|
+
If you use `CODEX_HOME`, run `pushary clean --dry-run` to see what is still
|
|
24
|
+
there, and check `$CODEX_HOME/config.toml` for a key you thought was gone.
|
|
25
|
+
|
|
26
|
+
### New
|
|
27
|
+
|
|
28
|
+
- `pushary clean --dry-run` lists everything it would remove and changes nothing
|
|
29
|
+
- `pushary doctor --bundle` writes a redacted diagnostics file for a support
|
|
30
|
+
thread. Keys, your home directory and the machine name are stripped.
|
|
31
|
+
- `pushary logout --revoke` kills the key server-side as well as locally, so it
|
|
32
|
+
stops working everywhere rather than only on this machine
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- `pushary upgrade` now refreshes Codex, Gemini CLI and Cursor, not only Claude
|
|
37
|
+
Code, and names the agents it actually touched. It matters most for Codex,
|
|
38
|
+
whose hooks are trusted by hash: a refreshed hook command without a refreshed
|
|
39
|
+
trust entry is a hook Codex silently declines to run.
|
|
40
|
+
- `pushary upgrade` with no global install now names the install modes it can
|
|
41
|
+
see and the command that updates each, instead of one generic line and exit 0
|
|
42
|
+
- `pushary daemon` stops on a rejected key or a closed plan and exits `4`
|
|
43
|
+
(`UNAUTHENTICATED`). It used to treat that like a network blip, back off to a
|
|
44
|
+
two-minute poll and sit there under an `online` banner, so a revoked key left a
|
|
45
|
+
daemon that looked healthy and would never launch anything again.
|
|
46
|
+
- Ctrl-C during app pairing now cancels the pairing on the server, so a QR still
|
|
47
|
+
on your screen stops being claimable immediately instead of staying live for
|
|
48
|
+
the rest of its five minutes
|
|
49
|
+
- `setup` prints the command surface when it finishes
|
|
50
|
+
|
|
51
|
+
## 0.61.0
|
|
52
|
+
|
|
53
|
+
The largest release since the CLI shipped. Setup no longer asks you to paste an
|
|
54
|
+
API key, the key is checked against the server before anything is written to your
|
|
55
|
+
machine, and commands exit with codes that mean something.
|
|
56
|
+
|
|
57
|
+
### Read this first: exit codes changed
|
|
58
|
+
|
|
59
|
+
Commands that used to print a failure and exit `0` now exit non-zero. If you run
|
|
60
|
+
any of these in CI or chained behind `&&`, this is the part that affects you.
|
|
61
|
+
|
|
62
|
+
| Command | Was | Now |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| any unknown command or `pushary` typo | `0`, printed help | `2` |
|
|
65
|
+
| `doctor` with a failing check | `0` | `5` |
|
|
66
|
+
| `setup` with a revoked key | `0` | `4` |
|
|
67
|
+
| `setup` with an expired or locked plan | `0` | `5` |
|
|
68
|
+
| `mode`, `wait` with a rejected key | `0` | non-zero |
|
|
69
|
+
| `status` with no key configured | did not exist | `3` |
|
|
70
|
+
|
|
71
|
+
The full ladder: `0` ok, `1` failed, `2` usage, `3` not configured,
|
|
72
|
+
`4` unauthenticated, `5` problems found, `6` no device, `7` input required,
|
|
73
|
+
`8` unreachable, `130` interrupted.
|
|
74
|
+
|
|
75
|
+
`pushary doctor && deploy` used to succeed against a machine doctor had just
|
|
76
|
+
declared broken. That is the change worth knowing about.
|
|
77
|
+
|
|
78
|
+
### Sign in instead of pasting a key
|
|
79
|
+
|
|
80
|
+
`pushary setup` opens a browser, shows you a code, and receives the key sealed to
|
|
81
|
+
a keypair generated on your machine. The key is never pasted, never shown in a
|
|
82
|
+
terminal, and never transits in a form the server could read.
|
|
83
|
+
|
|
84
|
+
- `pushary login` and `pushary logout` as standalone commands
|
|
85
|
+
- Pasting a key still works, as a fallback and via `--key`
|
|
86
|
+
- `--key-stdin` reads the key from stdin, so it stays out of your shell history
|
|
87
|
+
and out of the process list
|
|
88
|
+
|
|
89
|
+
### New commands
|
|
90
|
+
|
|
91
|
+
- `pushary status` reports what this machine is connected to, with `--json`
|
|
92
|
+
- `pushary login`, `pushary logout`, `pushary connect`
|
|
93
|
+
|
|
94
|
+
### Setup
|
|
95
|
+
|
|
96
|
+
- The key is verified against the server before a single file is written. A
|
|
97
|
+
revoked key or a locked plan stops the run instead of producing a config that
|
|
98
|
+
looks finished and never delivers.
|
|
99
|
+
- `--yes` runs setup with no prompts, for CI and images
|
|
100
|
+
- `--agents claude_code,codex,...` picks agents without asking
|
|
101
|
+
- `--dry-run` prints what it would do and touches nothing
|
|
102
|
+
- Agent detection is real, so an agent you do not have is reported as skipped
|
|
103
|
+
rather than counted as configured
|
|
104
|
+
- Ctrl-C in a trailing prompt no longer reports the whole run as failed
|
|
105
|
+
- A stale `PUSHARY_API_KEY` export in your shell profile is now replaced rather
|
|
106
|
+
than skipped. It used to win forever over a newly rotated key.
|
|
107
|
+
|
|
108
|
+
### Delivery is proved to your phone, not just to the workspace
|
|
109
|
+
|
|
110
|
+
Setup and doctor now check that an approval reaches **you**, not merely that
|
|
111
|
+
somebody in the workspace has a device. On a shared site where the only reachable
|
|
112
|
+
channel cannot be attributed to you, that is said plainly rather than reported as
|
|
113
|
+
success.
|
|
114
|
+
|
|
115
|
+
### Diagnostics
|
|
116
|
+
|
|
117
|
+
- `pushary doctor --json` for one machine-readable envelope
|
|
118
|
+
- `pushary doctor --roundtrip` sends a real approval and waits for the tap. It
|
|
119
|
+
works without a TTY, where the old confirm prompt could not run at all.
|
|
120
|
+
- `pushary doctor --no-push` for a check that stays silent
|
|
121
|
+
- doctor reads the same key the hooks will actually use, honoring
|
|
122
|
+
`PUSHARY_CONFIG_FILE`, and reports a Claude or Cursor config holding a
|
|
123
|
+
different key
|
|
124
|
+
|
|
125
|
+
### Correctness
|
|
126
|
+
|
|
127
|
+
- `--help` and `--version` print and exit instead of running the command.
|
|
128
|
+
`pushary daemon --help` used to start a daemon; `pushary hook --help` used to
|
|
129
|
+
block forever on stdin.
|
|
130
|
+
- Unknown flags are rejected on the commands that parse them, instead of being
|
|
131
|
+
coerced into a plausible wrong value. `--connect` with an unrecognized mode used
|
|
132
|
+
to silently mean `web`.
|
|
133
|
+
- Every file this CLI writes is written atomically, so an interrupted setup can
|
|
134
|
+
no longer truncate a large `~/.claude.json`
|
|
135
|
+
- An unparseable agent config is left alone instead of being overwritten
|
|
136
|
+
- Files holding your key are created `0600`
|
|
137
|
+
- `CODEX_HOME` is honored when locating `config.toml`
|
|
138
|
+
- Hermes setup works on Windows
|
|
139
|
+
- The Cursor plugin is staged and verified before the old one is replaced
|
|
140
|
+
- `installGlobally` pins the exact running version instead of drifting to latest
|
|
141
|
+
- `pushary clean` removes only the export line it wrote, not every line in your
|
|
142
|
+
shell profile that mentions the key variable
|
|
143
|
+
- Requests honor `HTTP_PROXY` and `HTTPS_PROXY`
|
|
144
|
+
- Color honors `NO_COLOR` and `FORCE_COLOR`, and turns itself off when output is
|
|
145
|
+
not a terminal
|
|
146
|
+
- `--json` output is machine-readable on stdout, with human text on stderr, so a
|
|
147
|
+
pipe never receives a spinner
|
|
148
|
+
|
|
149
|
+
### Pairing
|
|
150
|
+
|
|
151
|
+
The pairing QR now points at a real web page, so scanning it on a phone without
|
|
152
|
+
the app installed lands on install instructions for that platform instead of a
|
|
153
|
+
dead custom-scheme link.
|
|
154
|
+
|
|
155
|
+
### Known gaps
|
|
156
|
+
|
|
157
|
+
- `status`, `clean`, `mode`, `wait`, `stats`, `suggestions` and `upgrade` still
|
|
158
|
+
ignore an unknown flag rather than exiting `2`. Unchanged from 0.60.0.
|
|
159
|
+
- `pushary upgrade` reapplies Claude settings only. Other agents need `setup`.
|
|
160
|
+
- Codex hook trust still uses a fixed version ceiling, so a newer Codex is sent
|
|
161
|
+
to the manual `/hooks` step.
|
package/README.md
CHANGED
|
@@ -58,17 +58,59 @@ Any other MCP client can use notify and ask by pointing at the Pushary MCP endpo
|
|
|
58
58
|
|
|
59
59
|
## Commands
|
|
60
60
|
|
|
61
|
+
<!-- Managed: every command below is declared in src/cli/commands.ts, and a test
|
|
62
|
+
asserts this list matches it. Do not edit one without the other. -->
|
|
63
|
+
|
|
61
64
|
```
|
|
62
|
-
setup
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
|
|
66
|
+
claude Run Claude Code through Pushary, reachable from your phone even when idle
|
|
67
|
+
daemon Keep this machine reachable so your phone can START a new session
|
|
68
|
+
status One screen: which key is in force, whether the server accepts it, what can receive an approval
|
|
69
|
+
login Sign in from this terminal, no key to copy (--with-token to pipe one in)
|
|
70
|
+
logout Remove the key this machine stores, and say what it does not remove
|
|
71
|
+
connect Connect a phone without re-running setup (--app for the Pushary app)
|
|
72
|
+
doctor Verify your Pushary installation is working
|
|
73
|
+
clean Remove all Pushary configuration (--yes for non-interactive)
|
|
74
|
+
mode Switch approval mode (push_only, push_first, terminal_only)
|
|
75
|
+
wait Show or set the "wait for your phone" ladder (pushary wait 45)
|
|
76
|
+
stats Show the approval moments your agents hit while not connected
|
|
77
|
+
suggestions List rules mined from your own approvals
|
|
78
|
+
upgrade Update the globally installed hooks to the latest version
|
|
79
|
+
hook Run as a PreToolUse hook (reads stdin, writes stdout)
|
|
70
80
|
```
|
|
71
81
|
|
|
82
|
+
Run `pushary <command> --help` for one command's options.
|
|
83
|
+
|
|
84
|
+
### Non-interactive setup
|
|
85
|
+
|
|
86
|
+
`setup` asks which agents you use, so a run with no terminal on stdin needs to be
|
|
87
|
+
told instead. `--agents` or `--yes` does that; without either, setup takes the
|
|
88
|
+
defaults rather than waiting for a keypress that can never arrive.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Everything detected on this machine, no prompts
|
|
92
|
+
npx @pushary/agent-hooks@latest setup --key-stdin --yes --skip-phone < key.txt
|
|
93
|
+
|
|
94
|
+
# Named agents only
|
|
95
|
+
npx @pushary/agent-hooks@latest setup --agents claude_code,codex --yes --skip-phone
|
|
96
|
+
|
|
97
|
+
# Store the key and configure nothing
|
|
98
|
+
npx @pushary/agent-hooks@latest setup --agents none --yes
|
|
99
|
+
|
|
100
|
+
# Show what would be written, change nothing
|
|
101
|
+
npx @pushary/agent-hooks@latest setup --agents auto --dry-run
|
|
102
|
+
|
|
103
|
+
# One machine-readable object on stdout; every human line goes to stderr
|
|
104
|
+
npx @pushary/agent-hooks@latest setup --agents auto --yes --json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Prefer `--key-stdin` over `--key` in CI: `--key` puts the secret in argv, where
|
|
108
|
+
any other user on the box can read it out of `ps`.
|
|
109
|
+
|
|
110
|
+
Exit codes: `0` done, `2` bad usage, `3` no key resolvable, `4` the server
|
|
111
|
+
rejected the key, `5` finished with problems (an inactive plan, or an installer
|
|
112
|
+
that failed), `130` cancelled with Ctrl-C.
|
|
113
|
+
|
|
72
114
|
Examples:
|
|
73
115
|
|
|
74
116
|
```bash
|
|
@@ -106,7 +148,8 @@ If a policy cannot be fetched or a hook errors, the decision falls back to the a
|
|
|
106
148
|
- Website: [pushary.com](https://pushary.com/?from=npm)
|
|
107
149
|
- Docs: [pushary.com/docs](https://pushary.com/docs?from=npm)
|
|
108
150
|
- Sign up: [pushary.com/sign-up](https://pushary.com/sign-up?from=npm)
|
|
109
|
-
-
|
|
151
|
+
- Troubleshooting: [pushary.com/docs/agents/reference/troubleshooting](https://pushary.com/docs/agents/reference/troubleshooting?from=npm)
|
|
152
|
+
- Support and bug reports: [Discord](https://discord.gg/9v3VvUByrr) or [business@pushary.com](mailto:business@pushary.com)
|
|
110
153
|
|
|
111
154
|
## License
|
|
112
155
|
|
|
@@ -17,18 +17,18 @@ import {
|
|
|
17
17
|
reportEvent,
|
|
18
18
|
resolvePolicy,
|
|
19
19
|
waitForAnswer
|
|
20
|
-
} from "../chunk-
|
|
21
|
-
import
|
|
22
|
-
getMachineId
|
|
23
|
-
} from "../chunk-RN3NOEJF.js";
|
|
24
|
-
import "../chunk-DWED7BS3.js";
|
|
20
|
+
} from "../chunk-HIIBSYFJ.js";
|
|
21
|
+
import "../chunk-HNTKTK5B.js";
|
|
25
22
|
import {
|
|
26
23
|
redactSecrets
|
|
27
24
|
} from "../chunk-DQAN3JQP.js";
|
|
25
|
+
import {
|
|
26
|
+
getMachineId
|
|
27
|
+
} from "../chunk-RN3NOEJF.js";
|
|
28
28
|
import {
|
|
29
29
|
getApiKey,
|
|
30
30
|
getBaseUrl
|
|
31
|
-
} from "../chunk-
|
|
31
|
+
} from "../chunk-2UMNXADU.js";
|
|
32
32
|
|
|
33
33
|
// src/wrapper/claudeBinary.ts
|
|
34
34
|
import { statSync } from "fs";
|
|
@@ -1,28 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
removeClaudeMcpServers,
|
|
4
|
+
removePusharySettings
|
|
5
|
+
} from "../chunk-CAJZAFVS.js";
|
|
5
6
|
import {
|
|
6
|
-
removeCodexHooks,
|
|
7
|
-
removeGeminiSettings,
|
|
8
7
|
removeInstructionBlock
|
|
9
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-N7XJ4L2W.js";
|
|
9
|
+
import {
|
|
10
|
+
removeGeminiSettings
|
|
11
|
+
} from "../chunk-E2U35RLD.js";
|
|
12
|
+
import {
|
|
13
|
+
codexAgentsMd,
|
|
14
|
+
codexConfigToml,
|
|
15
|
+
codexHooksJson,
|
|
16
|
+
codexSkillDir,
|
|
17
|
+
removeCodexHooks
|
|
18
|
+
} from "../chunk-ER657KRJ.js";
|
|
19
|
+
import {
|
|
20
|
+
removeClaudeAlias
|
|
21
|
+
} from "../chunk-BC3VCZ3E.js";
|
|
10
22
|
import {
|
|
11
23
|
execNpm
|
|
12
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-MUEW424A.js";
|
|
13
25
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
exitOnHelpFlag
|
|
27
|
+
} from "../chunk-IIZZYUWK.js";
|
|
28
|
+
import "../chunk-SP7OZXCQ.js";
|
|
17
29
|
import "../chunk-DQAN3JQP.js";
|
|
18
30
|
|
|
19
31
|
// bin/pushary-clean.ts
|
|
20
|
-
import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync } from "fs";
|
|
32
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, rmSync, readdirSync } from "fs";
|
|
21
33
|
import { join } from "path";
|
|
22
34
|
import { homedir, tmpdir } from "os";
|
|
23
35
|
import { execSync } from "child_process";
|
|
24
36
|
import { confirm } from "@inquirer/prompts";
|
|
25
37
|
import { parse as parseTOML, stringify as stringifyTOML } from "smol-toml";
|
|
38
|
+
exitOnHelpFlag("clean");
|
|
26
39
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
27
40
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
28
41
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
@@ -61,8 +74,25 @@ var readJson = (path) => {
|
|
|
61
74
|
return null;
|
|
62
75
|
}
|
|
63
76
|
};
|
|
77
|
+
var dryRun = false;
|
|
78
|
+
var did = (past) => dryRun ? `(would ${past === "cleaned" ? "clean" : past === "removed" ? "remove" : past === "uninstalled" ? "uninstall" : past})` : `(${past})`;
|
|
79
|
+
var wouldNote = (action, path) => {
|
|
80
|
+
console.log(` ${dim("would")} ${action} ${path}`);
|
|
81
|
+
};
|
|
82
|
+
var guardedWrite = (path, contents) => {
|
|
83
|
+
if (dryRun) return wouldNote("rewrite", path);
|
|
84
|
+
writeFileSync(path, contents, "utf-8");
|
|
85
|
+
};
|
|
86
|
+
var guardedRemove = (path, options = {}) => {
|
|
87
|
+
if (dryRun) return wouldNote("remove ", path);
|
|
88
|
+
rmSync(path, { recursive: true, force: options.force });
|
|
89
|
+
};
|
|
90
|
+
var guardedExec = (command, options, describe) => {
|
|
91
|
+
if (dryRun) return wouldNote("run ", describe);
|
|
92
|
+
execSync(command, options);
|
|
93
|
+
};
|
|
64
94
|
var writeJson = (path, data) => {
|
|
65
|
-
|
|
95
|
+
guardedWrite(path, JSON.stringify(data, null, 2) + "\n");
|
|
66
96
|
};
|
|
67
97
|
var cleanSettingsFile = (path, label) => {
|
|
68
98
|
const data = readJson(path);
|
|
@@ -73,7 +103,7 @@ var cleanSettingsFile = (path, label) => {
|
|
|
73
103
|
const changed = removePusharySettings(data);
|
|
74
104
|
if (changed) {
|
|
75
105
|
writeJson(path, data);
|
|
76
|
-
console.log(` ${check} ${label} ${dim("
|
|
106
|
+
console.log(` ${check} ${label} ${dim(did("cleaned"))}`);
|
|
77
107
|
} else {
|
|
78
108
|
console.log(` ${skip} ${label} ${dim("(no pushary entries)")}`);
|
|
79
109
|
}
|
|
@@ -83,7 +113,12 @@ var main = async () => {
|
|
|
83
113
|
console.log(` ${bold("Pushary Clean")}`);
|
|
84
114
|
console.log(` ${dim("Removes all Pushary configuration")}`);
|
|
85
115
|
console.log();
|
|
86
|
-
|
|
116
|
+
dryRun = process.argv.includes("--dry-run");
|
|
117
|
+
if (dryRun) {
|
|
118
|
+
console.log(` ${bold("Dry run")} ${dim("- nothing will be changed")}`);
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
const assumeYes = dryRun || process.argv.includes("--yes") || process.argv.includes("-y");
|
|
87
122
|
if (!assumeYes && !process.stdin.isTTY) {
|
|
88
123
|
console.log(` ${yellow("!")} Non-interactive shell. Re-run with --yes to confirm removal.`);
|
|
89
124
|
process.exit(1);
|
|
@@ -113,7 +148,7 @@ var main = async () => {
|
|
|
113
148
|
if (mcpServers?.pushary) {
|
|
114
149
|
delete mcpServers.pushary;
|
|
115
150
|
writeJson(CURSOR_MCP, cursorData);
|
|
116
|
-
console.log(` ${check} Cursor MCP config ${dim("
|
|
151
|
+
console.log(` ${check} Cursor MCP config ${dim(did("cleaned"))}`);
|
|
117
152
|
} else {
|
|
118
153
|
console.log(` ${skip} Cursor MCP config ${dim("(no pushary entries)")}`);
|
|
119
154
|
}
|
|
@@ -121,7 +156,7 @@ var main = async () => {
|
|
|
121
156
|
console.log(` ${skip} Cursor MCP config ${dim("(not found)")}`);
|
|
122
157
|
}
|
|
123
158
|
if (existsSync(CURSOR_PLUGIN_DIR)) {
|
|
124
|
-
|
|
159
|
+
guardedRemove(CURSOR_PLUGIN_DIR);
|
|
125
160
|
console.log(` ${check} Cursor plugin ${dim("(removed from ~/.cursor/plugins/local)")}`);
|
|
126
161
|
} else {
|
|
127
162
|
console.log(` ${skip} Cursor plugin ${dim("(not installed)")}`);
|
|
@@ -144,12 +179,12 @@ var main = async () => {
|
|
|
144
179
|
console.log(` ${skip} Cursor gate ${dim("(no hooks.json)")}`);
|
|
145
180
|
}
|
|
146
181
|
if (existsSync(SKILL_DIR)) {
|
|
147
|
-
|
|
148
|
-
console.log(` ${check} Skill directory ${dim("
|
|
182
|
+
guardedRemove(SKILL_DIR);
|
|
183
|
+
console.log(` ${check} Skill directory ${dim(did("removed"))}`);
|
|
149
184
|
} else {
|
|
150
185
|
console.log(` ${skip} Skill directory ${dim("(not found)")}`);
|
|
151
186
|
}
|
|
152
|
-
const codexConfig =
|
|
187
|
+
const codexConfig = codexConfigToml();
|
|
153
188
|
try {
|
|
154
189
|
const config = readFileSync(codexConfig, "utf-8");
|
|
155
190
|
const hadPushary = config.includes("pushary");
|
|
@@ -163,19 +198,19 @@ var main = async () => {
|
|
|
163
198
|
parsed.notify = parsed.notify.filter((n) => typeof n !== "string" || !n.includes("pushary-codex"));
|
|
164
199
|
if (parsed.notify.length === 0) delete parsed.notify;
|
|
165
200
|
}
|
|
166
|
-
|
|
167
|
-
console.log(` ${check} Codex config ${dim("
|
|
201
|
+
guardedWrite(codexConfig, stringifyTOML(parsed));
|
|
202
|
+
console.log(` ${check} Codex config ${dim(did("cleaned"))}`);
|
|
168
203
|
} else {
|
|
169
204
|
console.log(` ${skip} Codex config ${dim("(no pushary entries)")}`);
|
|
170
205
|
}
|
|
171
206
|
} catch {
|
|
172
207
|
console.log(` ${skip} Codex config ${dim("(not found)")}`);
|
|
173
208
|
}
|
|
174
|
-
const
|
|
175
|
-
const codexHooksData = readJson(
|
|
209
|
+
const codexHooksJson2 = codexHooksJson();
|
|
210
|
+
const codexHooksData = readJson(codexHooksJson2);
|
|
176
211
|
if (codexHooksData) {
|
|
177
212
|
if (removeCodexHooks(codexHooksData)) {
|
|
178
|
-
writeJson(
|
|
213
|
+
writeJson(codexHooksJson2, codexHooksData);
|
|
179
214
|
console.log(` ${check} Codex hooks ${dim("(removed from ~/.codex/hooks.json)")}`);
|
|
180
215
|
} else {
|
|
181
216
|
console.log(` ${skip} Codex hooks ${dim("(no pushary entries)")}`);
|
|
@@ -183,16 +218,16 @@ var main = async () => {
|
|
|
183
218
|
} else {
|
|
184
219
|
console.log(` ${skip} Codex hooks ${dim("(not found)")}`);
|
|
185
220
|
}
|
|
186
|
-
const
|
|
187
|
-
if (removeInstructionBlock(
|
|
221
|
+
const codexAgentsMd2 = codexAgentsMd();
|
|
222
|
+
if (removeInstructionBlock(codexAgentsMd2)) {
|
|
188
223
|
console.log(` ${check} Codex AGENTS.md ${dim("(removed Pushary block)")}`);
|
|
189
224
|
} else {
|
|
190
225
|
console.log(` ${skip} Codex AGENTS.md ${dim("(no pushary block)")}`);
|
|
191
226
|
}
|
|
192
|
-
const
|
|
193
|
-
if (existsSync(
|
|
194
|
-
|
|
195
|
-
console.log(` ${check} Codex skill directory ${dim("
|
|
227
|
+
const codexSkillDir2 = codexSkillDir();
|
|
228
|
+
if (existsSync(codexSkillDir2)) {
|
|
229
|
+
guardedRemove(codexSkillDir2);
|
|
230
|
+
console.log(` ${check} Codex skill directory ${dim(did("removed"))}`);
|
|
196
231
|
} else {
|
|
197
232
|
console.log(` ${skip} Codex skill directory ${dim("(not found)")}`);
|
|
198
233
|
}
|
|
@@ -201,7 +236,7 @@ var main = async () => {
|
|
|
201
236
|
if (geminiSettings) {
|
|
202
237
|
if (removeGeminiSettings(geminiSettings)) {
|
|
203
238
|
writeJson(geminiSettingsPath, geminiSettings);
|
|
204
|
-
console.log(` ${check} Gemini CLI settings ${dim("
|
|
239
|
+
console.log(` ${check} Gemini CLI settings ${dim(did("cleaned"))}`);
|
|
205
240
|
} else {
|
|
206
241
|
console.log(` ${skip} Gemini CLI settings ${dim("(no pushary entries)")}`);
|
|
207
242
|
}
|
|
@@ -218,13 +253,13 @@ var main = async () => {
|
|
|
218
253
|
if (hermesPython) {
|
|
219
254
|
const snippet = 'from hermes_cli.config import load_config, save_config; c = load_config(); p = c.get("plugins") if isinstance(c.get("plugins"), dict) else {}; e = p.get("enabled") if isinstance(p.get("enabled"), list) else []; p["enabled"] = [x for x in e if x != "pushary"]; c["plugins"] = p; a = c.get("agent") if isinstance(c.get("agent"), dict) else {}; d = a.get("disabled_toolsets") if isinstance(a.get("disabled_toolsets"), list) else []; a["disabled_toolsets"] = [x for x in d if x != "clarify"]; c["agent"] = a; save_config(c)';
|
|
220
255
|
try {
|
|
221
|
-
|
|
256
|
+
guardedExec(`"${hermesPython}" -c '${snippet}'`, { stdio: "pipe", timeout: 15e3 }, "hermes plugin config update");
|
|
222
257
|
console.log(` ${check} Hermes config ${dim("(plugin disabled, clarify toolset restored)")}`);
|
|
223
258
|
} catch {
|
|
224
259
|
console.log(` ${skip} Hermes config ${dim("(could not update config.yaml)")}`);
|
|
225
260
|
}
|
|
226
261
|
try {
|
|
227
|
-
|
|
262
|
+
guardedExec(`"${hermesPython}" -m pip uninstall -y hermes-plugin-pushary`, { stdio: "pipe", timeout: 6e4 }, "pip uninstall hermes-plugin-pushary");
|
|
228
263
|
console.log(` ${check} Hermes plugin ${dim("(pip uninstalled)")}`);
|
|
229
264
|
} catch {
|
|
230
265
|
console.log(` ${skip} Hermes plugin ${dim("(not installed)")}`);
|
|
@@ -236,9 +271,19 @@ var main = async () => {
|
|
|
236
271
|
try {
|
|
237
272
|
const content = readFileSync(shellFile, "utf-8");
|
|
238
273
|
if (content.includes("PUSHARY_API_KEY")) {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
274
|
+
const isOurExport = (line) => /^\s*export\s+PUSHARY_API_KEY=['"]?pk_[A-Za-z0-9]+\.[A-Za-z0-9]+['"]?\s*$/.test(line);
|
|
275
|
+
const lines = content.split("\n");
|
|
276
|
+
const declined = lines.filter((l) => l.includes("PUSHARY_API_KEY") && !isOurExport(l));
|
|
277
|
+
const cleaned = lines.filter((l) => !isOurExport(l)).join("\n");
|
|
278
|
+
if (cleaned !== content) {
|
|
279
|
+
const backup = `${shellFile}.pushary-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
|
|
280
|
+
if (!dryRun) copyFileSync(shellFile, backup);
|
|
281
|
+
guardedWrite(shellFile, cleaned);
|
|
282
|
+
console.log(` ${check} ${shellFile.split("/").pop()} ${dim(`(removed the export, backup at ${backup})`)}`);
|
|
283
|
+
}
|
|
284
|
+
for (const line of declined) {
|
|
285
|
+
console.log(` ${skip} ${shellFile.split("/").pop()} ${dim(`left a line referencing PUSHARY_API_KEY: ${line.trim()}`)}`);
|
|
286
|
+
}
|
|
242
287
|
}
|
|
243
288
|
} catch {
|
|
244
289
|
}
|
|
@@ -250,7 +295,7 @@ var main = async () => {
|
|
|
250
295
|
console.log(` ${skip} claude alias ${dim("(not set)")}`);
|
|
251
296
|
}
|
|
252
297
|
if (existsSync(PUSHARY_DIR)) {
|
|
253
|
-
|
|
298
|
+
guardedRemove(PUSHARY_DIR);
|
|
254
299
|
console.log(` ${check} Local state ${dim("(~/.pushary removed: stored API key, ledger)")}`);
|
|
255
300
|
} else {
|
|
256
301
|
console.log(` ${skip} Local state ${dim("(~/.pushary not found)")}`);
|
|
@@ -260,7 +305,7 @@ var main = async () => {
|
|
|
260
305
|
for (const f of readdirSync(tmpdir())) {
|
|
261
306
|
if (!f.startsWith("pushary-")) continue;
|
|
262
307
|
try {
|
|
263
|
-
|
|
308
|
+
guardedRemove(join(tmpdir(), f), { force: true });
|
|
264
309
|
swept++;
|
|
265
310
|
} catch {
|
|
266
311
|
}
|
|
@@ -270,7 +315,7 @@ var main = async () => {
|
|
|
270
315
|
}
|
|
271
316
|
try {
|
|
272
317
|
execNpm("uninstall -g --no-workspaces @pushary/agent-hooks", { stdio: "ignore", timeout: 3e4 });
|
|
273
|
-
console.log(` ${check} Global package ${dim("
|
|
318
|
+
console.log(` ${check} Global package ${dim(did("uninstalled"))}`);
|
|
274
319
|
} catch {
|
|
275
320
|
console.log(` ${skip} Global package ${dim("(not installed)")}`);
|
|
276
321
|
}
|
|
@@ -31,24 +31,24 @@ import {
|
|
|
31
31
|
toCodexWire,
|
|
32
32
|
toPolicyLookup,
|
|
33
33
|
waitForAnswer
|
|
34
|
-
} from "../chunk-
|
|
35
|
-
import {
|
|
36
|
-
getMachineId
|
|
37
|
-
} from "../chunk-RN3NOEJF.js";
|
|
34
|
+
} from "../chunk-HIIBSYFJ.js";
|
|
38
35
|
import {
|
|
39
36
|
isGatingMoment,
|
|
40
37
|
recordKeylessMoment
|
|
41
38
|
} from "../chunk-WLGEY4UX.js";
|
|
42
|
-
import "../chunk-
|
|
39
|
+
import "../chunk-HNTKTK5B.js";
|
|
43
40
|
import {
|
|
44
41
|
DECISION_LINE_MAX,
|
|
45
42
|
effectiveWaitSeconds,
|
|
46
43
|
hookWaitClamped,
|
|
47
44
|
hookWaitDeadline
|
|
48
45
|
} from "../chunk-DQAN3JQP.js";
|
|
46
|
+
import {
|
|
47
|
+
getMachineId
|
|
48
|
+
} from "../chunk-RN3NOEJF.js";
|
|
49
49
|
import {
|
|
50
50
|
getApiKey
|
|
51
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-2UMNXADU.js";
|
|
52
52
|
|
|
53
53
|
// bin/pushary-codex-hook.ts
|
|
54
54
|
import { basename, join } from "path";
|
|
@@ -3,15 +3,15 @@ import {
|
|
|
3
3
|
askUser,
|
|
4
4
|
reportEvent,
|
|
5
5
|
waitForAnswer
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-HIIBSYFJ.js";
|
|
7
|
+
import "../chunk-HNTKTK5B.js";
|
|
8
|
+
import "../chunk-DQAN3JQP.js";
|
|
7
9
|
import {
|
|
8
10
|
getMachineId
|
|
9
11
|
} from "../chunk-RN3NOEJF.js";
|
|
10
|
-
import "../chunk-DWED7BS3.js";
|
|
11
|
-
import "../chunk-DQAN3JQP.js";
|
|
12
12
|
import {
|
|
13
13
|
getApiKey
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-2UMNXADU.js";
|
|
15
15
|
|
|
16
16
|
// bin/pushary-codex.ts
|
|
17
17
|
import { basename } from "path";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
printKeyCheck
|
|
4
|
+
} from "../chunk-ONVEYEVR.js";
|
|
5
|
+
import {
|
|
6
|
+
confirmAppConnection,
|
|
7
|
+
connectDevice,
|
|
8
|
+
printConnectInstructions
|
|
9
|
+
} from "../chunk-3O27ZSRE.js";
|
|
10
|
+
import "../chunk-3EGEA4KH.js";
|
|
11
|
+
import {
|
|
12
|
+
parseFlags
|
|
13
|
+
} from "../chunk-N4DA4CJB.js";
|
|
14
|
+
import {
|
|
15
|
+
readKeySource
|
|
16
|
+
} from "../chunk-247C4RE5.js";
|
|
17
|
+
import {
|
|
18
|
+
checkApiKey
|
|
19
|
+
} from "../chunk-YJ7YZRVV.js";
|
|
20
|
+
import {
|
|
21
|
+
createIo
|
|
22
|
+
} from "../chunk-TX7KBKT7.js";
|
|
23
|
+
import {
|
|
24
|
+
UsageError
|
|
25
|
+
} from "../chunk-7QLSKOSU.js";
|
|
26
|
+
import "../chunk-BC3VCZ3E.js";
|
|
27
|
+
import {
|
|
28
|
+
exitOnHelpFlag
|
|
29
|
+
} from "../chunk-IIZZYUWK.js";
|
|
30
|
+
import {
|
|
31
|
+
EXIT
|
|
32
|
+
} from "../chunk-SP7OZXCQ.js";
|
|
33
|
+
import "../chunk-HNTKTK5B.js";
|
|
34
|
+
import "../chunk-DQAN3JQP.js";
|
|
35
|
+
import "../chunk-2UMNXADU.js";
|
|
36
|
+
|
|
37
|
+
// bin/pushary-connect.ts
|
|
38
|
+
exitOnHelpFlag("connect");
|
|
39
|
+
var parseConnectOptions = (argv) => {
|
|
40
|
+
const flags = parseFlags(argv, { bools: ["app", "browser", "help", "h"], values: [] });
|
|
41
|
+
if (flags.command !== void 0 && flags.command !== "connect") {
|
|
42
|
+
throw new UsageError(`connect takes no positional arguments (got ${flags.command})`);
|
|
43
|
+
}
|
|
44
|
+
if (flags.bools.has("app") && flags.bools.has("browser")) {
|
|
45
|
+
throw new UsageError("pass either --app or --browser, not both");
|
|
46
|
+
}
|
|
47
|
+
return { mode: flags.bools.has("app") ? "app" : "browser" };
|
|
48
|
+
};
|
|
49
|
+
var options;
|
|
50
|
+
try {
|
|
51
|
+
options = parseConnectOptions(process.argv.slice(2));
|
|
52
|
+
} catch (err) {
|
|
53
|
+
process.stderr.write(` ! ${err instanceof Error ? err.message : String(err)}
|
|
54
|
+
`);
|
|
55
|
+
process.stderr.write(" Run `pushary connect --help` for the full list.\n");
|
|
56
|
+
process.exit(EXIT.USAGE);
|
|
57
|
+
}
|
|
58
|
+
var { bold, dim, cyan, yellow } = createIo();
|
|
59
|
+
var main = async () => {
|
|
60
|
+
const source = readKeySource();
|
|
61
|
+
if (!source.key) {
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${yellow("!")} No API key on this machine, so there is no site to connect a phone to.`);
|
|
64
|
+
console.log(` ${dim("Run")} ${cyan("pushary setup")} ${dim("first.")}`);
|
|
65
|
+
console.log();
|
|
66
|
+
process.exit(EXIT.NOT_CONFIGURED);
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(` ${bold("Pushary")} ${dim("connect a phone")}`);
|
|
70
|
+
console.log();
|
|
71
|
+
const keyCheck = await checkApiKey(source.key);
|
|
72
|
+
if (keyCheck.kind !== "ok") {
|
|
73
|
+
const report = printKeyCheck(keyCheck);
|
|
74
|
+
if (keyCheck.kind !== "unreachable") process.exit(report.exitCode);
|
|
75
|
+
console.log();
|
|
76
|
+
}
|
|
77
|
+
const result = options.mode === "app" ? await confirmAppConnection(source.key) : process.stdout.isTTY ? await connectDevice(source.key, { showCapabilities: true }) : await printConnectInstructions(source.key);
|
|
78
|
+
console.log();
|
|
79
|
+
if (result.reachesYou) {
|
|
80
|
+
console.log(` ${dim("Your phone can receive approvals. Try")} ${cyan("pushary doctor --roundtrip")} ${dim("to answer one.")}`);
|
|
81
|
+
console.log();
|
|
82
|
+
process.exit(EXIT.OK);
|
|
83
|
+
}
|
|
84
|
+
console.log(` ${dim("No device of yours can answer yet. Run")} ${cyan("pushary connect")} ${dim("again when you are ready.")}`);
|
|
85
|
+
console.log();
|
|
86
|
+
process.exit(EXIT.NO_DEVICE);
|
|
87
|
+
};
|
|
88
|
+
main().catch((err) => {
|
|
89
|
+
process.stderr.write(` ! connect failed: ${err instanceof Error ? err.message : String(err)}
|
|
90
|
+
`);
|
|
91
|
+
process.exit(EXIT.FAILED);
|
|
92
|
+
});
|