harlay 0.4.0 → 0.4.2

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/app/dist/main.cjs CHANGED
@@ -3195,24 +3195,85 @@ function assessHealth(s, context) {
3195
3195
 
3196
3196
  // ../../packages/core/src/webchat.ts
3197
3197
  var import_node_path19 = require("node:path");
3198
+ var LIMIT_NOTICES = [
3199
+ // claude.ai
3200
+ "Usage limit reached",
3201
+ "You've reached your session limit",
3202
+ "You've reached your weekly limit",
3203
+ "You've reached your weekly Cowork limit",
3204
+ "You've reached your usage limit",
3205
+ "You've reached your plan's usage limit",
3206
+ "You've reached your monthly spend limit",
3207
+ "You've reached your * limit. Get more usage",
3208
+ "Reached your session limit",
3209
+ "Reached your weekly limit",
3210
+ "Reached your weekly Cowork limit",
3211
+ "Reached your usage limit",
3212
+ "Reached your * limit$",
3213
+ "* limit reached$",
3214
+ "Out of free messages",
3215
+ "You hit your message limit",
3216
+ "You hit your 5-hour message limit",
3217
+ "You hit your weekly message limit",
3218
+ "You hit your weekly * limit",
3219
+ "You hit your usage limit",
3220
+ "You hit the Free plan's",
3221
+ "You just hit your free message limit",
3222
+ "You've hit your limit for Claude messages",
3223
+ "You've hit your session limit",
3224
+ "You've hit your weekly limit",
3225
+ "You've hit your usage limit",
3226
+ "You've hit your monthly spend limit",
3227
+ "Your usage limit is still exceeded",
3228
+ "Paused: Your usage limit",
3229
+ "Limit hit. Your week resets",
3230
+ "Limit hit. You can send new messages",
3231
+ "Try again after your * resets",
3232
+ "Send a message after your * resets",
3233
+ "Wait for your * to reset",
3234
+ "Your * resets * Try again then",
3235
+ "Your * resets * Send a message then",
3236
+ // ChatGPT, signed out
3237
+ "You have reached the anonymous message limit",
3238
+ // ChatGPT, signed in ("You've hit your usage limit", above, is ChatGPT's too). The server's own 429 message, which
3239
+ // ChatGPT itself spots by this start; the model cap; the out-of-messages panel's titles; Work's limit.
3240
+ "You've reached our limit of messages",
3241
+ "You've reached our limits of messages",
3242
+ "You've reached the current usage cap for",
3243
+ "You're out of messages with",
3244
+ "You've reached a limit$",
3245
+ "You've reached your Work limit"
3246
+ ];
3198
3247
  function isLimitText(s) {
3199
- const t = s.replace(/\s+/g, " ");
3200
- if (t.length > 400) return false;
3201
- if (!/\blimits?\b|out of (?:free )?messages/i.test(t)) return false;
3202
- return /\b(?:hit|reached|exceeded|out of|resets?|reset at|until|try again)\b/i.test(t);
3248
+ const self = isLimitText;
3249
+ if (!self.patterns) {
3250
+ self.patterns = (self.notices ?? LIMIT_NOTICES).map((n) => {
3251
+ const whole2 = n.endsWith("$");
3252
+ const body = (whole2 ? n.slice(0, -1) : n).replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".{1,60}?");
3253
+ return new RegExp(`^${body}${whole2 ? "$" : ""}`, "i");
3254
+ });
3255
+ }
3256
+ const t = s.replace(/[’‘]/g, "'").replace(/\s+/g, " ").trim();
3257
+ if (/\b(?:upload|image|file|length|storage|project|artifact|translation|read aloud)\b/i.test(t)) return false;
3258
+ return t.length > 0 && t.length <= 300 && self.patterns.some((p) => p.test(t));
3203
3259
  }
3260
+ var WEEKDAYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
3204
3261
  function limitResetsAt(s, now) {
3205
3262
  const inHours = /\bin (\d+) hours?\b/i.exec(s);
3206
3263
  if (inHours) return new Date(now.getTime() + Number(inHours[1]) * 36e5);
3207
3264
  const inMinutes = /\bin (\d+) min(?:ute)?s?\b/i.exec(s);
3208
3265
  if (inMinutes) return new Date(now.getTime() + Number(inMinutes[1]) * 6e4);
3209
- const m = /\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i.exec(s);
3266
+ const m = /\b(\d{1,2})(?::(\d{2}))?\s*(am|pm)\b/i.exec(s) ?? /\b(\d{1,2}):(\d{2})\b()/.exec(s);
3210
3267
  if (!m) return null;
3211
- let h = Number(m[1]) % 12;
3212
- if (m[3].toLowerCase() === "pm") h += 12;
3268
+ let h = Number(m[1]);
3269
+ if (m[3]) h = h % 12 + (m[3].toLowerCase() === "pm" ? 12 : 0);
3213
3270
  const at = new Date(now);
3214
3271
  at.setHours(h, m[2] ? Number(m[2]) : 0, 0, 0);
3215
- if (at.getTime() <= now.getTime()) at.setDate(at.getDate() + 1);
3272
+ const day = /\b(sun|mon|tue|wed|thu|fri|sat)[a-z]*\b/i.exec(s);
3273
+ if (day) {
3274
+ at.setDate(at.getDate() + (WEEKDAYS.indexOf(day[1].toLowerCase()) - at.getDay() + 7) % 7);
3275
+ if (at.getTime() <= now.getTime()) at.setDate(at.getDate() + 7);
3276
+ } else if (at.getTime() <= now.getTime()) at.setDate(at.getDate() + 1);
3216
3277
  return at;
3217
3278
  }
3218
3279
  function webChatTitle(c) {
@@ -3732,9 +3793,8 @@ function scanPage(root, ax, watch3, isLimit) {
3732
3793
  for (const k of ax.kids(n)) walk(k, depth + 1);
3733
3794
  };
3734
3795
  walk(root, 0);
3735
- const NOTICE = /^(?:you['’]?ve|you have|you['’]re|you are|usage limit|message limit|rate limit|limit reached)\b/i;
3736
3796
  const tail2 = watch3 ? last ? message(last.n, last.h) : void 0 : turns.at(-1);
3737
- if (tail2 && tail2.who === "agent" && tail2.text.length < 250 && NOTICE.test(tail2.text) && isLimit(tail2.text)) {
3797
+ if (tail2 && tail2.who === "agent" && isLimit(tail2.text)) {
3738
3798
  if (!limit) limit = tail2.text;
3739
3799
  if (!watch3) turns.pop();
3740
3800
  }
@@ -3775,6 +3835,7 @@ ObjC.bindFunction('AXUIElementCopyAttributeValue', ['int', ['id', 'id', 'id*']])
3775
3835
  ObjC.bindFunction('AXUIElementSetAttributeValue', ['int', ['id', 'id', 'id']]);
3776
3836
  ObjC.bindFunction('getppid', ['int', []]);
3777
3837
  const isLimitText = ${isLimitText.toString()};
3838
+ isLimitText.notices = ${JSON.stringify(LIMIT_NOTICES)};
3778
3839
  const scanPage = ${scanPage.toString()};
3779
3840
  function get(el, name) { const r = Ref(); return $.AXUIElementCopyAttributeValue(el, name, r) === 0 ? r[0] : null; }
3780
3841
  function str(v) { if (!v) return null; try { const s = ObjC.unwrap(v); return typeof s === 'string' ? s : null; } catch (e) { return null; } }
package/dist/harlay.mjs CHANGED
@@ -2479,7 +2479,8 @@ function harlayDataDir(home = homedir3()) {
2479
2479
  return join12(home, "Library", "Application Support", "Harlay");
2480
2480
  }
2481
2481
  async function uninstallApp(opts) {
2482
- await quitRunningApp();
2482
+ const app = join12(opts.dir, "Harlay.app");
2483
+ if (existsSync2(app)) await (opts.quit ?? quitRunningApp)();
2483
2484
  try {
2484
2485
  await removeBridge(opts.home, harlayDataDir(opts.home));
2485
2486
  opts.log("Restored your Claude Code status line settings.\n");
@@ -2487,7 +2488,6 @@ async function uninstallApp(opts) {
2487
2488
  opts.log(`Left ~/.claude/settings.json unchanged: ${e instanceof Error ? e.message : String(e)}
2488
2489
  `);
2489
2490
  }
2490
- const app = join12(opts.dir, "Harlay.app");
2491
2491
  if (!existsSync2(app)) {
2492
2492
  opts.log(`No Harlay.app in ${opts.dir}.
2493
2493
  `);
@@ -2553,7 +2553,7 @@ async function installApp(opts) {
2553
2553
  }
2554
2554
 
2555
2555
  // src/main.ts
2556
- var VERSION = "0.4.0";
2556
+ var VERSION = "0.4.2";
2557
2557
  var HELP = `harlay ${VERSION} \u2014 hand off a stalled AI coding session to another agent
2558
2558
 
2559
2559
  Usage:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harlay",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Move a coding task between AI agents (Claude Code, Codex, Cursor) when one stops. macOS desktop app and CLI.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
@@ -19,7 +19,8 @@
19
19
  },
20
20
  "scripts": {
21
21
  "build": "esbuild src/bin.ts --bundle --platform=node --format=esm --target=node22 --external:@electron/packager --outfile=dist/harlay.mjs --banner:js='#!/usr/bin/env node'",
22
- "prepack": "npm run build && node scripts/bundle-app.mjs"
22
+ "prepack": "npm run build && node scripts/bundle-app.mjs",
23
+ "postpack": "node -e \"require('node:fs').rmSync('app',{recursive:true,force:true})\""
23
24
  },
24
25
  "dependencies": {
25
26
  "@electron/packager": "^20.3.0"