@pushary/agent-hooks 0.60.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +52 -9
  3. package/dist/bin/pushary-claude.js +3 -3
  4. package/dist/bin/pushary-clean.js +26 -11
  5. package/dist/bin/pushary-codex-hook.js +3 -3
  6. package/dist/bin/pushary-codex.js +3 -3
  7. package/dist/bin/pushary-connect.d.ts +1 -0
  8. package/dist/bin/pushary-connect.js +92 -0
  9. package/dist/bin/pushary-daemon.js +7 -2
  10. package/dist/bin/pushary-doctor.js +142 -46
  11. package/dist/bin/pushary-gemini-hook.js +3 -3
  12. package/dist/bin/pushary-hook.js +9 -4
  13. package/dist/bin/pushary-login.d.ts +1 -0
  14. package/dist/bin/pushary-login.js +156 -0
  15. package/dist/bin/pushary-logout.d.ts +1 -0
  16. package/dist/bin/pushary-logout.js +102 -0
  17. package/dist/bin/pushary-mode.js +24 -13
  18. package/dist/bin/pushary-notification-hook.js +3 -3
  19. package/dist/bin/pushary-permission-denied-hook.js +4 -4
  20. package/dist/bin/pushary-permission-hook.js +4 -4
  21. package/dist/bin/pushary-post-hook.js +3 -3
  22. package/dist/bin/pushary-prompt-hook.js +3 -3
  23. package/dist/bin/pushary-session-end-hook.js +3 -3
  24. package/dist/bin/pushary-session-start-hook.js +3 -3
  25. package/dist/bin/pushary-setup.js +858 -547
  26. package/dist/bin/pushary-stats.js +6 -1
  27. package/dist/bin/pushary-status.d.ts +1 -0
  28. package/dist/bin/pushary-status.js +206 -0
  29. package/dist/bin/pushary-stop-hook.js +3 -3
  30. package/dist/bin/pushary-stopfailure-hook.js +3 -3
  31. package/dist/bin/pushary-suggestions.js +10 -5
  32. package/dist/bin/pushary-upgrade.js +11 -6
  33. package/dist/bin/pushary-wait.js +21 -12
  34. package/dist/bin/pushary.js +42 -60
  35. package/dist/{chunk-NKXSILEW.js → chunk-2UMNXADU.js} +18 -2
  36. package/dist/chunk-3EGEA4KH.js +44 -0
  37. package/dist/chunk-7QLSKOSU.js +19 -0
  38. package/dist/chunk-B26DNXCA.js +235 -0
  39. package/dist/chunk-GX64YGU3.js +544 -0
  40. package/dist/{chunk-BE5X3WXL.js → chunk-HI4AGGE6.js} +2 -2
  41. package/dist/{chunk-DWED7BS3.js → chunk-HNTKTK5B.js} +1 -1
  42. package/dist/chunk-HUJQSP4F.js +18 -0
  43. package/dist/{chunk-J7JWI3KU.js → chunk-MUEW424A.js} +19 -1
  44. package/dist/chunk-N4DA4CJB.js +57 -0
  45. package/dist/chunk-QHOVJXOT.js +255 -0
  46. package/dist/chunk-R6C4USIV.js +44 -0
  47. package/dist/{chunk-BQLCELYC.js → chunk-S4TFBJ5O.js} +7 -0
  48. package/dist/chunk-SML23YOT.js +216 -0
  49. package/dist/chunk-TX7KBKT7.js +79 -0
  50. package/dist/chunk-UKNAAVEE.js +217 -0
  51. package/dist/{chunk-WNRYRN2R.js → chunk-VFBYRR2N.js} +2 -2
  52. package/dist/src/index.js +4 -4
  53. package/package.json +11 -4
@@ -1,13 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ exitOnHelpFlag
4
+ } from "../chunk-HUJQSP4F.js";
5
+ import "../chunk-SML23YOT.js";
2
6
  import {
3
7
  readLedgerSummary
4
8
  } from "../chunk-WLGEY4UX.js";
5
9
  import "../chunk-DQAN3JQP.js";
6
10
  import {
7
11
  getApiKey
8
- } from "../chunk-NKXSILEW.js";
12
+ } from "../chunk-2UMNXADU.js";
9
13
 
10
14
  // bin/pushary-stats.ts
15
+ exitOnHelpFlag("stats");
11
16
  var DAYS = 7;
12
17
  var summary = readLedgerSummary(DAYS);
13
18
  var connected = true;
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ describeReach,
4
+ fetchChannels,
5
+ reachVerdict
6
+ } from "../chunk-GX64YGU3.js";
7
+ import "../chunk-3EGEA4KH.js";
8
+ import {
9
+ readKeySource
10
+ } from "../chunk-B26DNXCA.js";
11
+ import {
12
+ checkApiKey,
13
+ describeKeyCheck
14
+ } from "../chunk-UKNAAVEE.js";
15
+ import {
16
+ createIo
17
+ } from "../chunk-TX7KBKT7.js";
18
+ import "../chunk-7QLSKOSU.js";
19
+ import "../chunk-BC3VCZ3E.js";
20
+ import {
21
+ exitOnHelpFlag
22
+ } from "../chunk-HUJQSP4F.js";
23
+ import {
24
+ EXIT
25
+ } from "../chunk-SML23YOT.js";
26
+ import "../chunk-HNTKTK5B.js";
27
+ import "../chunk-DQAN3JQP.js";
28
+ import "../chunk-2UMNXADU.js";
29
+
30
+ // src/status.ts
31
+ var keySourceLine = (source) => {
32
+ if (source.kind === "none") {
33
+ return { label: "Key", value: "none found", ok: false };
34
+ }
35
+ const prefix = source.key ? source.key.split(".")[0] : "unknown";
36
+ const where = source.kind === "env" ? "$PUSHARY_API_KEY" : source.origin ?? "config file";
37
+ return { label: "Key", value: `${prefix} from ${where}`, ok: true };
38
+ };
39
+ var reachLine = (channels) => {
40
+ const verdict = reachVerdict(channels);
41
+ const line = describeReach(channels);
42
+ if (verdict.kind === "nobody") return { label: "Reachable", value: line, ok: false };
43
+ if (verdict.kind === "yours") {
44
+ const inPocket = (channels.yours?.phoneReachable ?? channels.phoneReachable) || channels.webPhone > 0;
45
+ return { label: "Reachable", value: line, ok: inPocket };
46
+ }
47
+ return { label: "Reachable", value: line, ok: false };
48
+ };
49
+ var siteLine = (channels) => {
50
+ const site = channels.site;
51
+ if (site === null || site.otherMembers === 0) return null;
52
+ const devices = site.appDevices + site.webActive;
53
+ return {
54
+ label: "Team",
55
+ value: `${devices} device${devices === 1 ? "" : "s"} across ${site.otherMembers + 1} members`,
56
+ ok: null
57
+ };
58
+ };
59
+ var buildStatusReport = (facts) => {
60
+ const lines = [keySourceLine(facts.source)];
61
+ if (facts.source.drift) {
62
+ lines.push({
63
+ label: "Key drift",
64
+ value: `$PUSHARY_API_KEY (${facts.source.drift.envPrefix}) differs from ${facts.source.drift.configPath} (${facts.source.drift.configPrefix}); the environment wins`,
65
+ ok: false
66
+ });
67
+ }
68
+ if (facts.source.kind === "none") {
69
+ return {
70
+ lines,
71
+ headline: "Not set up on this machine.",
72
+ next: ["Run `pushary setup` to connect an agent."],
73
+ exitCode: EXIT.NOT_CONFIGURED
74
+ };
75
+ }
76
+ const check = facts.keyCheck;
77
+ if (check && check.kind !== "ok") {
78
+ const report = describeKeyCheck(check);
79
+ lines.push({ label: "Server", value: report.message, ok: check.kind === "unreachable" ? null : false });
80
+ return { lines, headline: report.message, next: [...report.next], exitCode: report.exitCode };
81
+ }
82
+ if (check?.kind === "ok") {
83
+ const id = check.identity;
84
+ lines.push({ label: "Site", value: `${id.siteName || id.siteSlug} (${id.siteSlug})`, ok: true });
85
+ lines.push({
86
+ label: "Plan",
87
+ value: id.plan === id.surfacePlan ? `${id.plan} \xB7 ${id.subscriptionStatus}` : `${id.plan} (from ${id.surfacePlan}) \xB7 ${id.subscriptionStatus}`,
88
+ ok: true
89
+ });
90
+ if (id.workspaceMembers > 1) {
91
+ lines.push({
92
+ label: "Workspace",
93
+ value: `${id.workspaceMembers} members \u2014 some devices below may not be yours`,
94
+ ok: null
95
+ });
96
+ }
97
+ }
98
+ const channels = facts.channels?.kind === "ok" ? facts.channels.status : null;
99
+ if (channels) {
100
+ lines.push(reachLine(channels));
101
+ const site = siteLine(channels);
102
+ if (site) lines.push(site);
103
+ } else if (facts.channels && facts.channels.kind === "unreachable") {
104
+ lines.push({ label: "Reachable", value: `could not check (${facts.channels.detail})`, ok: null });
105
+ }
106
+ if (facts.source.drift) {
107
+ return {
108
+ lines,
109
+ headline: "Working, but two different keys are configured.",
110
+ next: [
111
+ "The shell export wins over the config file, and it may be the older one.",
112
+ "Run `pushary setup` to line them up, or open a new terminal."
113
+ ],
114
+ exitCode: EXIT.PROBLEMS_FOUND
115
+ };
116
+ }
117
+ if (facts.channels && facts.channels.kind === "unreachable") {
118
+ return {
119
+ lines,
120
+ headline: "Key is fine; could not read which devices are connected.",
121
+ next: ["Run `pushary status` again in a minute."],
122
+ exitCode: EXIT.UNREACHABLE
123
+ };
124
+ }
125
+ if (channels) {
126
+ const verdict = reachVerdict(channels);
127
+ if (verdict.kind !== "yours") {
128
+ return {
129
+ lines,
130
+ headline: verdict.kind === "nobody" ? "Configured, but no device can receive an approval." : verdict.kind === "others" ? "Configured, but the only devices here are your teammates'." : "Configured, and nothing proves an approval reaches you.",
131
+ next: verdict.kind === "nobody" ? ["Run `pushary connect` to add your phone, or install the Pushary app."] : ["Run `pushary connect` to add your own phone.", "Or `pushary doctor --roundtrip` to prove one of these is yours."],
132
+ exitCode: EXIT.NO_DEVICE
133
+ };
134
+ }
135
+ if (!(channels.yours?.phoneReachable ?? channels.phoneReachable) && channels.webPhone === 0) {
136
+ return {
137
+ lines,
138
+ headline: "Configured, but nothing in your pocket can receive an approval.",
139
+ next: ["Run `pushary connect` and connect your phone, or install the Pushary app."],
140
+ exitCode: EXIT.NO_DEVICE
141
+ };
142
+ }
143
+ }
144
+ return {
145
+ lines,
146
+ headline: "Ready.",
147
+ next: [],
148
+ exitCode: EXIT.OK
149
+ };
150
+ };
151
+ var statusJson = (facts, report) => ({
152
+ ok: report.exitCode === EXIT.OK,
153
+ headline: report.headline,
154
+ exitCode: report.exitCode,
155
+ keySource: facts.source.kind,
156
+ keyOrigin: facts.source.origin,
157
+ keyPrefix: facts.source.key ? facts.source.key.split(".")[0] : null,
158
+ drift: facts.source.drift ? {
159
+ env: facts.source.drift.envPrefix,
160
+ config: facts.source.drift.configPrefix,
161
+ configPath: facts.source.drift.configPath
162
+ } : null,
163
+ keyCheck: facts.keyCheck?.kind ?? null,
164
+ identity: facts.keyCheck?.kind === "ok" ? facts.keyCheck.identity : null,
165
+ channels: facts.channels?.kind === "ok" ? facts.channels.status : null,
166
+ channelsProbe: facts.channels?.kind ?? null
167
+ });
168
+
169
+ // bin/pushary-status.ts
170
+ exitOnHelpFlag("status");
171
+ var { bold, dim, green, red, yellow } = createIo();
172
+ var args = process.argv.slice(2);
173
+ var json = args.includes("--json");
174
+ var main = async () => {
175
+ const source = readKeySource();
176
+ let keyCheck = null;
177
+ let channels = null;
178
+ if (source.key) {
179
+ keyCheck = await checkApiKey(source.key);
180
+ if (keyCheck.kind === "ok") channels = await fetchChannels(source.key);
181
+ }
182
+ const facts = { source, keyCheck, channels };
183
+ const report = buildStatusReport(facts);
184
+ if (json) {
185
+ process.stdout.write(`${JSON.stringify(statusJson(facts, report), null, 2)}
186
+ `);
187
+ process.exit(report.exitCode);
188
+ }
189
+ const width = Math.max(...report.lines.map((line) => line.label.length)) + 2;
190
+ console.log();
191
+ console.log(` ${bold("Pushary")}`);
192
+ console.log();
193
+ for (const line of report.lines) {
194
+ const icon = line.ok === true ? green("\u2713") : line.ok === false ? red("\u2717") : dim("\xB7");
195
+ console.log(` ${icon} ${line.label.padEnd(width)}${line.value}`);
196
+ }
197
+ console.log();
198
+ console.log(` ${report.exitCode === 0 ? green(bold(report.headline)) : yellow(bold(report.headline))}`);
199
+ for (const next of report.next) console.log(` ${dim(next)}`);
200
+ console.log();
201
+ process.exit(report.exitCode);
202
+ };
203
+ main().catch((err) => {
204
+ console.error(` ${yellow("!")} status failed: ${err instanceof Error ? err.message : String(err)}`);
205
+ process.exit(1);
206
+ });
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-WNRYRN2R.js";
4
+ } from "../chunk-VFBYRR2N.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
- import "../chunk-DWED7BS3.js";
6
+ import "../chunk-HNTKTK5B.js";
7
7
  import "../chunk-DQAN3JQP.js";
8
- import "../chunk-NKXSILEW.js";
8
+ import "../chunk-2UMNXADU.js";
9
9
 
10
10
  // bin/pushary-stop-hook.ts
11
11
  var main = async () => {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStopFailure
4
- } from "../chunk-WNRYRN2R.js";
4
+ } from "../chunk-VFBYRR2N.js";
5
5
  import "../chunk-RN3NOEJF.js";
6
- import "../chunk-DWED7BS3.js";
6
+ import "../chunk-HNTKTK5B.js";
7
7
  import "../chunk-DQAN3JQP.js";
8
- import "../chunk-NKXSILEW.js";
8
+ import "../chunk-2UMNXADU.js";
9
9
 
10
10
  // bin/pushary-stopfailure-hook.ts
11
11
  var main = async () => {
@@ -1,14 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createIo
4
+ } from "../chunk-TX7KBKT7.js";
5
+ import {
6
+ exitOnHelpFlag
7
+ } from "../chunk-HUJQSP4F.js";
8
+ import "../chunk-SML23YOT.js";
2
9
  import {
3
10
  getApiKey,
4
11
  getBaseUrl
5
- } from "../chunk-NKXSILEW.js";
12
+ } from "../chunk-2UMNXADU.js";
6
13
 
7
14
  // bin/pushary-suggestions.ts
8
- var dim = (s) => `\x1B[2m${s}\x1B[0m`;
9
- var green = (s) => `\x1B[32m${s}\x1B[0m`;
10
- var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
11
- var red = (s) => `\x1B[31m${s}\x1B[0m`;
15
+ exitOnHelpFlag("suggestions");
16
+ var { dim, green, cyan, red } = createIo();
12
17
  var errorMessage = async (res) => {
13
18
  try {
14
19
  const body = await res.json();
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- execNpm,
4
- npmErrorMessage,
5
- resolveGlobalBinDir
6
- } from "../chunk-J7JWI3KU.js";
7
2
  import {
8
3
  addClaudeMcpServer,
9
4
  addPusharyHooks,
10
5
  addPusharyToolPermissions
11
6
  } from "../chunk-CAJZAFVS.js";
7
+ import {
8
+ execNpm,
9
+ npmErrorMessage,
10
+ resolveGlobalBinDir
11
+ } from "../chunk-MUEW424A.js";
12
+ import {
13
+ exitOnHelpFlag
14
+ } from "../chunk-HUJQSP4F.js";
15
+ import "../chunk-SML23YOT.js";
12
16
  import "../chunk-DQAN3JQP.js";
13
17
  import {
14
18
  getApiKey
15
- } from "../chunk-NKXSILEW.js";
19
+ } from "../chunk-2UMNXADU.js";
16
20
 
17
21
  // src/reapply.ts
18
22
  import { join, dirname } from "path";
@@ -64,6 +68,7 @@ var reapplyClaudeSettings = (apiKey, paths = {}) => {
64
68
  };
65
69
 
66
70
  // bin/pushary-upgrade.ts
71
+ exitOnHelpFlag("upgrade");
67
72
  var getInstalledVersion = () => {
68
73
  try {
69
74
  return execNpm("list -g --no-workspaces @pushary/agent-hooks --depth=0", { timeout: 1e4 }).toString().match(/@pushary\/agent-hooks@([\d.]+)/)?.[1] ?? "";
@@ -1,18 +1,30 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ exitOnFailedResponse
4
+ } from "../chunk-R6C4USIV.js";
2
5
  import {
3
6
  describeWaitLadder
4
7
  } from "../chunk-IIENZE2J.js";
8
+ import "../chunk-UKNAAVEE.js";
9
+ import {
10
+ createIo
11
+ } from "../chunk-TX7KBKT7.js";
12
+ import "../chunk-7QLSKOSU.js";
13
+ import {
14
+ exitOnHelpFlag
15
+ } from "../chunk-HUJQSP4F.js";
16
+ import {
17
+ EXIT
18
+ } from "../chunk-SML23YOT.js";
19
+ import "../chunk-DQAN3JQP.js";
5
20
  import {
6
21
  getApiKey,
7
22
  getBaseUrl
8
- } from "../chunk-NKXSILEW.js";
23
+ } from "../chunk-2UMNXADU.js";
9
24
 
10
25
  // bin/pushary-wait.ts
11
- var dim = (s) => `\x1B[2m${s}\x1B[0m`;
12
- var bold = (s) => `\x1B[1m${s}\x1B[0m`;
13
- var green = (s) => `\x1B[32m${s}\x1B[0m`;
14
- var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
15
- var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
26
+ exitOnHelpFlag("wait");
27
+ var { dim, bold, green, cyan, yellow } = createIo();
16
28
  var MAX_WINDOW = 300;
17
29
  var printLadder = (data) => {
18
30
  console.log();
@@ -36,7 +48,7 @@ var printLadder = (data) => {
36
48
  };
37
49
  var fetchPolicy = async (baseUrl, headers) => {
38
50
  const res = await fetch(`${baseUrl}/api/mcp/policy`, { headers });
39
- if (!res.ok) throw new Error(`policy fetch failed (${res.status})`);
51
+ if (!res.ok) await exitOnFailedResponse(res, baseUrl);
40
52
  return res.json();
41
53
  };
42
54
  var setWindow = async (baseUrl, headers, seconds) => {
@@ -45,10 +57,7 @@ var setWindow = async (baseUrl, headers, seconds) => {
45
57
  headers,
46
58
  body: JSON.stringify({ defaultPushFirstSeconds: seconds })
47
59
  });
48
- if (!res.ok) {
49
- const err = await res.json().catch(() => ({}));
50
- throw new Error(err.error ?? res.statusText);
51
- }
60
+ if (!res.ok) await exitOnFailedResponse(res, baseUrl);
52
61
  const data = await res.json();
53
62
  return data.defaultPushFirstSeconds;
54
63
  };
@@ -75,7 +84,7 @@ var main = async () => {
75
84
  if (!Number.isFinite(seconds) || seconds < 0 || seconds > MAX_WINDOW) {
76
85
  console.log(` ${yellow("!")} Invalid window: ${arg} ${dim(`(use 0-${MAX_WINDOW} seconds, or "status" / "clear")`)}`);
77
86
  console.log(` ${dim("Usage: pushary wait <seconds> | status | clear")}`);
78
- return;
87
+ process.exit(EXIT.USAGE);
79
88
  }
80
89
  const value = await setWindow(baseUrl, headers, Math.trunc(seconds));
81
90
  console.log(` ${green("\u2713")} Default push window set to ${cyan(`${value}s`)} ${dim("\u2014 ping your phone that long, then the terminal takes over")}`);
@@ -1,65 +1,47 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ COMMANDS,
4
+ EXIT,
5
+ getPackageVersion,
6
+ isCommandName,
7
+ renderHelp
8
+ } from "../chunk-SML23YOT.js";
2
9
 
3
10
  // bin/pushary.ts
4
- var command = process.argv[2];
5
- if (command === "setup") {
6
- await import("./pushary-setup.js");
7
- } else if (command === "hook") {
8
- await import("./pushary-hook.js");
9
- } else if (command === "clean") {
10
- await import("./pushary-clean.js");
11
- } else if (command === "doctor") {
12
- await import("./pushary-doctor.js");
13
- } else if (command === "mode") {
14
- await import("./pushary-mode.js");
15
- } else if (command === "wait") {
16
- await import("./pushary-wait.js");
17
- } else if (command === "stats") {
18
- await import("./pushary-stats.js");
19
- } else if (command === "suggestions") {
20
- await import("./pushary-suggestions.js");
21
- } else if (command === "upgrade") {
22
- await import("./pushary-upgrade.js");
23
- } else if (command === "claude") {
24
- await import("./pushary-claude.js");
25
- } else if (command === "daemon") {
26
- await import("./pushary-daemon.js");
27
- } else {
28
- console.log(`
29
- Pushary Agent Hooks
30
-
31
- Commands:
32
- setup Configure Claude Code, Codex, Gemini CLI, Hermes, or Cursor with Pushary
33
- claude Run Claude Code through Pushary \u2014 the native terminal, and reachable from
34
- your phone: send an instruction and it drives even a fully idle agent
35
- (press Ctrl-] to take the terminal back). Add --remote to start headless.
36
- daemon Keep this machine reachable to START a new session from your phone, even
37
- with nothing running. Leave it in a project dir; it launches a headless
38
- 'pushary claude --remote' when you send a prompt from the app.
39
- doctor Verify your Pushary installation is working
40
- clean Remove all Pushary configuration (--yes for non-interactive)
41
- mode Switch approval mode (push_only, push_first, terminal_only)
42
- wait Show or set the "wait for your phone" ladder (pushary wait 45)
43
- stats Show the approval moments your agents hit while not connected
44
- suggestions List rules mined from your own approvals; accept one with
45
- 'pushary suggestions accept <id>' to always-allow that pattern
46
- upgrade Update the globally installed hooks to the latest version
47
- hook Run as a PreToolUse hook (reads stdin, writes stdout)
48
-
49
- Setup options:
50
- --key <pk_xxx.xxx> Use this API key instead of prompting for one
51
- --connect web Connect your phone via the PWA QR code (default)
52
- --connect app Pair the native Pushary app, no key to paste
53
- --skip-phone Skip the phone connect step (CI or non-interactive shells)
54
-
55
- Usage:
56
- npx @pushary/agent-hooks@latest setup
57
- npx @pushary/agent-hooks@latest setup --key pk_xxx.xxx --skip-phone
58
- npx @pushary/agent-hooks@latest doctor
59
- npx @pushary/agent-hooks@latest mode push_only --for 30m
60
- npx @pushary/agent-hooks@latest wait 45
61
- pushary claude # native terminal, reachable from your phone when idle
62
- pushary claude --remote -p "start the refactor" # start headless, drive entirely from your phone
63
- pushary daemon # keep this machine ready to spawn a session from your phone
11
+ var LOADERS = {
12
+ setup: () => import("./pushary-setup.js"),
13
+ claude: () => import("./pushary-claude.js"),
14
+ daemon: () => import("./pushary-daemon.js"),
15
+ status: () => import("./pushary-status.js"),
16
+ login: () => import("./pushary-login.js"),
17
+ logout: () => import("./pushary-logout.js"),
18
+ connect: () => import("./pushary-connect.js"),
19
+ doctor: () => import("./pushary-doctor.js"),
20
+ clean: () => import("./pushary-clean.js"),
21
+ mode: () => import("./pushary-mode.js"),
22
+ wait: () => import("./pushary-wait.js"),
23
+ stats: () => import("./pushary-stats.js"),
24
+ suggestions: () => import("./pushary-suggestions.js"),
25
+ upgrade: () => import("./pushary-upgrade.js"),
26
+ hook: () => import("./pushary-hook.js")
27
+ };
28
+ var argv = process.argv.slice(2);
29
+ var command = argv[0] ?? "";
30
+ var wants = (...names) => argv.some((arg) => names.includes(arg));
31
+ if (!command || wants("--help", "-h") && !isCommandName(command)) {
32
+ process.stdout.write(renderHelp(getPackageVersion()));
33
+ process.exit(EXIT.OK);
34
+ }
35
+ if (wants("--version", "-v") && !isCommandName(command)) {
36
+ process.stdout.write(`${getPackageVersion()}
37
+ `);
38
+ process.exit(EXIT.OK);
39
+ }
40
+ if (!isCommandName(command)) {
41
+ process.stderr.write(`Unknown command: ${command}
42
+ `);
43
+ process.stderr.write(`Run \`pushary --help\`. Available: ${COMMANDS.map((c) => c.name).join(", ")}
64
44
  `);
45
+ process.exit(EXIT.USAGE);
65
46
  }
47
+ await LOADERS[command]();
@@ -12,18 +12,34 @@ var readKeyFromConfigFile = () => {
12
12
  return void 0;
13
13
  }
14
14
  };
15
+ var driftChecked = false;
16
+ var warnOnKeyDrift = (envKey) => {
17
+ if (driftChecked || process.env.PUSHARY_SUPPRESS_KEY_DRIFT_WARNING === "1") return;
18
+ driftChecked = true;
19
+ const fileKey = readKeyFromConfigFile();
20
+ if (!fileKey || fileKey === envKey) return;
21
+ const short = (key) => key.split(".")[0] ?? key;
22
+ process.stderr.write(
23
+ `[pushary] PUSHARY_API_KEY in your environment (${short(envKey)}) differs from ${configFilePath()} (${short(fileKey)}). The environment one is being used. Run 'pushary doctor' for details, or set PUSHARY_SUPPRESS_KEY_DRIFT_WARNING=1 if this is deliberate.
24
+ `
25
+ );
26
+ };
15
27
  var getApiKey = () => {
16
28
  const envKey = process.env.PUSHARY_API_KEY?.trim();
17
- if (envKey) return envKey;
29
+ if (envKey) {
30
+ warnOnKeyDrift(envKey);
31
+ return envKey;
32
+ }
18
33
  const fileKey = readKeyFromConfigFile();
19
34
  if (fileKey) return fileKey;
20
35
  throw new Error(
21
36
  "PUSHARY_API_KEY is not set and no key was found in ~/.pushary/config.json. Run `npx @pushary/agent-hooks setup`, or get your API key at https://pushary.com/sign-up?from=ai-coding"
22
37
  );
23
38
  };
24
- var getBaseUrl = () => process.env.PUSHARY_BASE_URL ?? "https://pushary.com";
39
+ var getBaseUrl = () => process.env.PUSHARY_BASE_URL?.trim() || process.env.PUSHARY_API_URL?.trim() || "https://pushary.com";
25
40
 
26
41
  export {
42
+ configFilePath,
27
43
  getApiKey,
28
44
  getBaseUrl
29
45
  };
@@ -0,0 +1,44 @@
1
+ // src/pairing.ts
2
+ import crypto from "crypto";
3
+ var X25519_SPKI_PREFIX = Buffer.from("302a300506032b656e032100", "hex");
4
+ var HKDF_INFO = Buffer.from("pushary-pair-v1");
5
+ var HEADER_BYTES = 32 + 12 + 16;
6
+ var rawToPublicKey = (raw) => crypto.createPublicKey({
7
+ key: Buffer.concat([X25519_SPKI_PREFIX, raw]),
8
+ format: "der",
9
+ type: "spki"
10
+ });
11
+ var publicKeyToRaw = (key) => key.export({ format: "der", type: "spki" }).subarray(-32);
12
+ var generateKeypair = () => {
13
+ const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519");
14
+ const publicKeyRaw = publicKeyToRaw(publicKey);
15
+ return { publicKeyB64: publicKeyRaw.toString("base64"), publicKeyRaw, privateKey };
16
+ };
17
+ var publicKeyFingerprint = (b64) => {
18
+ const raw = Buffer.from(b64, "base64");
19
+ const digest = crypto.createHash("sha256").update(raw).digest("hex");
20
+ return (digest.slice(0, 8).match(/.{2}/g) ?? []).join(" ").toUpperCase();
21
+ };
22
+ var openSealedKey = (blobB64, keypair) => {
23
+ const blob = Buffer.from(blobB64, "base64");
24
+ if (blob.length <= HEADER_BYTES) throw new Error("sealed payload too short");
25
+ const ephemeralRaw = blob.subarray(0, 32);
26
+ const nonce = blob.subarray(32, 44);
27
+ const tag = blob.subarray(44, 60);
28
+ const ciphertext = blob.subarray(60);
29
+ const shared = crypto.diffieHellman({
30
+ privateKey: keypair.privateKey,
31
+ publicKey: rawToPublicKey(ephemeralRaw)
32
+ });
33
+ const salt = Buffer.concat([ephemeralRaw, keypair.publicKeyRaw]);
34
+ const key = Buffer.from(crypto.hkdfSync("sha256", shared, salt, HKDF_INFO, 32));
35
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, nonce);
36
+ decipher.setAuthTag(tag);
37
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
38
+ };
39
+
40
+ export {
41
+ generateKeypair,
42
+ publicKeyFingerprint,
43
+ openSealedKey
44
+ };
@@ -0,0 +1,19 @@
1
+ // src/cli/errors.ts
2
+ var UsageError = class extends Error {
3
+ constructor(hint) {
4
+ super(hint);
5
+ this.hint = hint;
6
+ this.name = "UsageError";
7
+ }
8
+ };
9
+ var NetworkError = class extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "NetworkError";
13
+ }
14
+ };
15
+
16
+ export {
17
+ UsageError,
18
+ NetworkError
19
+ };