@pushary/agent-hooks 0.67.1 → 0.69.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.
@@ -266,6 +266,29 @@ var writeStatusLine = (text) => {
266
266
  ` : ` ${text}
267
267
  `);
268
268
  };
269
+ var visibleLength = (text) => text.replace(/\x1b\[[0-9;]*m/g, "").length;
270
+ var clipToRow = (text, columns) => {
271
+ const budget = columns - 4;
272
+ if (budget <= 1 || visibleLength(text) <= budget) return text;
273
+ let visible = 0;
274
+ let out = "";
275
+ for (let i = 0; i < text.length; i += 1) {
276
+ const escape = /^\x1b\[[0-9;]*m/.exec(text.slice(i));
277
+ if (escape) {
278
+ out += escape[0];
279
+ i += escape[0].length - 1;
280
+ continue;
281
+ }
282
+ if (visible >= budget - 1) break;
283
+ out += text[i];
284
+ visible += 1;
285
+ }
286
+ return `${out}\u2026`;
287
+ };
288
+ var terminalColumns = () => {
289
+ const columns = process.stdout.columns;
290
+ return typeof columns === "number" && columns > 0 ? columns : 80;
291
+ };
269
292
  var startSpinner = (getLabel) => {
270
293
  if (!humanIsTty()) {
271
294
  writeHuman(` ${getLabel()}
@@ -283,7 +306,8 @@ var startSpinner = (getLabel) => {
283
306
  const frames = [" ", ". ", ".. ", "..."];
284
307
  let i = 0;
285
308
  const interval = setInterval(() => {
286
- writeHuman(`\r ${dim(frames[i++ % frames.length])} ${getLabel()}`);
309
+ const line = `${dim(frames[i++ % frames.length])} ${getLabel()}`;
310
+ writeHuman(`\r ${clipToRow(line, terminalColumns())}\x1B[K`);
287
311
  }, SPINNER_FRAME_MS);
288
312
  return (finalGlyph, finalLabel) => {
289
313
  clearInterval(interval);
@@ -338,6 +362,8 @@ var waitForDevice = async (apiKey, baseline) => {
338
362
  await sleep(CONNECT_POLL_INTERVAL_MS);
339
363
  }
340
364
  stop(yellow("!"), "Didn't detect a connection yet");
365
+ console.log(` ${dim("Signed in on the app already? Open it and allow notifications.")}`);
366
+ console.log(` ${dim("Signing in alone does not connect the phone. Then run")} ${cyan("pushary doctor")}${dim(".")}`);
341
367
  return { kind: "timeout" };
342
368
  } catch (err) {
343
369
  stop(yellow("!"), `Couldn't check connection ${dim(`(${err instanceof Error ? err.message : "network error"})`)}`);
@@ -484,6 +510,18 @@ var startPairing = async (cliPublicKey) => {
484
510
  return null;
485
511
  }
486
512
  };
513
+ var describePairFailure = (reason) => {
514
+ switch (reason) {
515
+ case "no_site":
516
+ return "Couldn't set up your workspace. Try again, or finish setup at pushary.com/dashboard";
517
+ case "blocked":
518
+ return "Finish setting up in the Pushary app, then run setup again";
519
+ case "authorize_failed":
520
+ return "The app could not authorize this terminal. Re-run setup to try again";
521
+ default:
522
+ return "Pairing failed. Re-run setup to try again";
523
+ }
524
+ };
487
525
  var cancelPairing = async (pairId) => {
488
526
  try {
489
527
  await fetch(`${apiBase()}/api/mobile/pair/start`, {
@@ -495,6 +533,7 @@ var cancelPairing = async (pairId) => {
495
533
  } catch {
496
534
  }
497
535
  };
536
+ var PAIR_MAX_CONSECUTIVE_FAILURES = 8;
498
537
  var claimPairing = async (pairId) => {
499
538
  try {
500
539
  const res = await fetch(`${apiBase()}/api/mobile/pair/claim?id=${encodeURIComponent(pairId)}`, {
@@ -506,6 +545,17 @@ var claimPairing = async (pairId) => {
506
545
  return null;
507
546
  }
508
547
  };
548
+ var ackPairing = async (pairId) => {
549
+ try {
550
+ await fetch(`${apiBase()}/api/mobile/pair/ack`, {
551
+ method: "POST",
552
+ headers: { "Content-Type": "application/json" },
553
+ body: JSON.stringify({ pairId }),
554
+ signal: AbortSignal.timeout(5e3)
555
+ });
556
+ } catch {
557
+ }
558
+ };
509
559
  var buildPairLinks = (pairId, publicKeyB64, baseUrl = apiBase()) => {
510
560
  const id = encodeURIComponent(pairId);
511
561
  const pk = encodeURIComponent(publicKeyB64);
@@ -533,12 +583,19 @@ var connectViaAppPairing = async (options = {}) => {
533
583
  console.log();
534
584
  const startedAt = Date.now();
535
585
  const deadline = startedAt + PAIR_TIMEOUT_MS;
586
+ let hintShown = false;
536
587
  const stop = startSpinner(() => {
537
588
  const left = Math.max(0, Math.ceil((deadline - Date.now()) / 6e4));
538
589
  if (Date.now() - startedAt < PAIR_HINT_AFTER_MS) {
539
590
  return "Waiting for the app to authorize";
540
591
  }
541
- return `Waiting for the app ${dim(`(${left}m left. No app yet? Install it, sign in with this account, then scan. Ctrl-C to stop)`)}`;
592
+ if (!hintShown) {
593
+ hintShown = true;
594
+ writeStatusLine(
595
+ `${dim("No app yet? Install it, sign in with this account, then scan. Ctrl-C to stop.")}`
596
+ );
597
+ }
598
+ return `Waiting for the app ${dim(`(${left}m left)`)}`;
542
599
  });
543
600
  let cancelled = false;
544
601
  const onInterrupt = () => {
@@ -548,10 +605,22 @@ var connectViaAppPairing = async (options = {}) => {
548
605
  void Promise.allSettled([Promise.resolve(flushed), cancelPairing(pairId)]).finally(() => process.exit(EXIT_ABORTED));
549
606
  };
550
607
  process.once("SIGINT", onInterrupt);
608
+ let consecutiveFailures = 0;
551
609
  try {
552
610
  while (Date.now() < deadline) {
553
611
  if (cancelled) return null;
554
612
  const claim = await claimPairing(pairId);
613
+ if (claim === null) {
614
+ consecutiveFailures += 1;
615
+ if (consecutiveFailures >= PAIR_MAX_CONSECUTIVE_FAILURES) {
616
+ stop(yellow("!"), "Can't reach pushary.com to finish pairing. Check your connection, then re-run setup.");
617
+ await cancelPairing(pairId);
618
+ return null;
619
+ }
620
+ await sleep(PAIR_POLL_INTERVAL_MS);
621
+ continue;
622
+ }
623
+ consecutiveFailures = 0;
555
624
  if (claim?.status === "authorized") {
556
625
  let apiKey;
557
626
  try {
@@ -561,6 +630,7 @@ var connectViaAppPairing = async (options = {}) => {
561
630
  await cancelPairing(pairId);
562
631
  return null;
563
632
  }
633
+ await ackPairing(pairId);
564
634
  stop(check, "App connected");
565
635
  return { apiKey, handle: claim.siteSlug };
566
636
  }
@@ -568,6 +638,10 @@ var connectViaAppPairing = async (options = {}) => {
568
638
  stop(yellow("!"), "Pairing expired \u2014 re-run setup to try again");
569
639
  return null;
570
640
  }
641
+ if (claim?.status === "failed") {
642
+ stop(yellow("!"), describePairFailure(claim.reason));
643
+ return null;
644
+ }
571
645
  await sleep(PAIR_POLL_INTERVAL_MS);
572
646
  }
573
647
  stop(yellow("!"), "Timed out waiting for the app");
@@ -36,6 +36,7 @@ interface ModeState {
36
36
  readonly relayUrl: string | null;
37
37
  readonly trainingConsent?: boolean;
38
38
  readonly scope?: ScopeContract | null;
39
+ readonly degraded?: boolean;
39
40
  }
40
41
  declare const fetchModeState: (apiKey: string, sessionId?: string) => Promise<ModeState>;
41
42
  declare const fetchModeOverride: (apiKey: string) => Promise<ApprovalMode | null>;
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-SONXGCZY.js";
3
+ } from "../chunk-C5I2RAMT.js";
4
4
  import "../chunk-CAJZAFVS.js";
5
- import "../chunk-YHG74UFF.js";
5
+ import "../chunk-3USMXNVB.js";
6
6
  import {
7
7
  askUser,
8
8
  cancelQuestion,
@@ -15,7 +15,7 @@ import {
15
15
  reportEvent,
16
16
  resolvePolicy,
17
17
  waitForAnswer
18
- } from "../chunk-XOUYM27W.js";
18
+ } from "../chunk-H46LSQRT.js";
19
19
  import "../chunk-WLGEY4UX.js";
20
20
  import "../chunk-BSZYIAZL.js";
21
21
  import "../chunk-SAF6HGAA.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.67.1",
3
+ "version": "0.69.0",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,12 +0,0 @@
1
- // src/answer.ts
2
- var denyReasonFrom = (value, note) => {
3
- const trimmed = note?.trim();
4
- if (trimmed) return `Denied from your phone: ${trimmed}`;
5
- return value && value !== "no" && value !== "yes" ? `Denied from your phone: ${value}` : "Denied via push notification";
6
- };
7
- var isDeferAnswer = (value) => value === "defer";
8
-
9
- export {
10
- denyReasonFrom,
11
- isDeferAnswer
12
- };