claude-tg 1.0.4 → 1.0.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/daemon.js +42 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-tg",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Control Claude Code from Telegram — approve permissions, answer questions, reply to idle sessions, send files, all from your phone",
5
5
  "bin": {
6
6
  "claude-tg": "./bin/claude-tg.js"
package/src/daemon.js CHANGED
@@ -127,28 +127,33 @@ end tell`;
127
127
  return true;
128
128
  } catch {}
129
129
 
130
- // Try Terminal.app — focus the tab, type text, press Enter
130
+ // Try Terminal.app — focus the tab, paste text from clipboard, press Enter
131
131
  try {
132
+ // Use clipboard paste instead of keystroke (reliable for any text length)
133
+ const tmpTextFile = '/tmp/claude-tg-input.txt';
134
+ fs.writeFileSync(tmpTextFile, text.trim());
135
+
132
136
  const script = [
133
137
  'tell application "Terminal"',
138
+ ' activate',
134
139
  ' repeat with w in windows',
135
140
  ' repeat with t in tabs of w',
136
141
  ` if tty of t is "${ttyPath}" then`,
137
142
  ' set selected tab of w to t',
138
- ' set frontmost of w to true',
139
- ' delay 0.3',
140
- ' tell application "System Events"',
141
- ' tell process "Terminal"',
142
- ` keystroke "${escaped}"`,
143
- ' delay 0.2',
144
- ' keystroke return',
145
- ' end tell',
146
- ' end tell',
147
- ' return "ok"',
143
+ ' set index of w to 1',
148
144
  ' end if',
149
145
  ' end repeat',
150
146
  ' end repeat',
151
147
  'end tell',
148
+ `do shell script "cat ${tmpTextFile} | /usr/bin/pbcopy"`,
149
+ 'delay 0.5',
150
+ 'tell application "System Events"',
151
+ ' tell process "Terminal"',
152
+ ' keystroke "v" using command down',
153
+ ' delay 0.3',
154
+ ' key code 36',
155
+ ' end tell',
156
+ 'end tell',
152
157
  ].join('\n');
153
158
 
154
159
  fs.writeFileSync('/tmp/claude-tg-input.scpt', script);
@@ -482,7 +487,7 @@ async function handleElicitationCallback(ctx, cbData) {
482
487
  }
483
488
 
484
489
  if (!elicId) {
485
- ctx.answerCbQuery('Expired or already answered.');
490
+ try { await ctx.answerCbQuery('Expired or already answered.'); } catch {}
486
491
  return;
487
492
  }
488
493
 
@@ -550,7 +555,7 @@ async function handleElicitationCallback(ctx, cbData) {
550
555
  const q = elic.questions[qIdx];
551
556
 
552
557
  if (!q) {
553
- ctx.answerCbQuery('Invalid question.');
558
+ try { await ctx.answerCbQuery('Invalid question.'); } catch {}
554
559
  return;
555
560
  }
556
561
 
@@ -578,7 +583,7 @@ async function handleElicitationCallback(ctx, cbData) {
578
583
  const selected = toggles.get(qIdx) || new Set();
579
584
 
580
585
  if (selected.size === 0) {
581
- ctx.answerCbQuery('Select at least one option.');
586
+ try { await ctx.answerCbQuery('Select at least one option.'); } catch {}
582
587
  return;
583
588
  }
584
589
 
@@ -610,7 +615,7 @@ async function handleElicitationCallback(ctx, cbData) {
610
615
  const opt = q.options[optIdx];
611
616
 
612
617
  if (!opt) {
613
- ctx.answerCbQuery('Invalid option.');
618
+ try { await ctx.answerCbQuery('Invalid option.'); } catch {}
614
619
  return;
615
620
  }
616
621
 
@@ -622,10 +627,10 @@ async function handleElicitationCallback(ctx, cbData) {
622
627
 
623
628
  if (selected.has(optIdx)) {
624
629
  selected.delete(optIdx);
625
- ctx.answerCbQuery(`Deselected: ${opt.label}`);
630
+ try { await ctx.answerCbQuery(`Deselected: ${opt.label}`); } catch {}
626
631
  } else {
627
632
  selected.add(optIdx);
628
- ctx.answerCbQuery(`Selected: ${opt.label}`);
633
+ try { await ctx.answerCbQuery(`Selected: ${opt.label}`); } catch {}
629
634
  }
630
635
 
631
636
  // Rebuild buttons with selection indicators
@@ -715,8 +720,8 @@ function injectElicitationAnswers(ttyPath, questions, answers) {
715
720
  }
716
721
 
717
722
  // Submit the form
718
- events.push({ type: 'delay', value: 0.2 });
719
- events.push({ type: 'keystroke', value: 'return' });
723
+ events.push({ type: 'delay', value: 0.3 });
724
+ events.push({ type: 'key_code', value: 36 }); // Return key
720
725
 
721
726
  // Build key action lines for AppleScript
722
727
  const keyLines = events.map((e) => {
@@ -797,13 +802,13 @@ function injectElicitationAnswers(ttyPath, questions, answers) {
797
802
  function startBot() {
798
803
  bot = new Telegraf(config.botToken);
799
804
 
800
- bot.command('start', (ctx) => {
805
+ bot.command('start', async (ctx) => {
801
806
  const chatId = ctx.chat.id.toString();
802
- ctx.reply(`Chat ID registered: ${chatId}\n\nThis chat will receive Claude Code permission requests.`);
807
+ try { await ctx.reply(`Chat ID registered: ${chatId}\n\nThis chat will receive Claude Code permission requests.`); } catch {}
803
808
  log(`/start from chat ${chatId}`);
804
809
  });
805
810
 
806
- bot.command('status', (ctx) => {
811
+ bot.command('status', async (ctx) => {
807
812
  const pendingCount = pendingQuestions.size;
808
813
  const activeSessions = [...sessions.entries()].filter(([, s]) => {
809
814
  if (s.ttyPath) return isTtyAlive(s.ttyPath);
@@ -812,7 +817,7 @@ function startBot() {
812
817
 
813
818
  let msg = '';
814
819
  if (activeSessions.length === 0 && pendingCount === 0) {
815
- ctx.reply('No active sessions or pending requests.');
820
+ try { await ctx.reply('No active sessions or pending requests.'); } catch {}
816
821
  return;
817
822
  }
818
823
 
@@ -842,7 +847,7 @@ function startBot() {
842
847
  }
843
848
  }
844
849
 
845
- ctx.reply(msg);
850
+ try { await ctx.reply(msg); } catch {}
846
851
  });
847
852
 
848
853
  // Handle inline keyboard button presses (permission decisions + elicitations)
@@ -866,7 +871,7 @@ function startBot() {
866
871
  }
867
872
 
868
873
  if (!requestId) {
869
- ctx.answerCbQuery('Request expired or already answered.');
874
+ try { await ctx.answerCbQuery('Request expired or already answered.'); } catch {}
870
875
  return;
871
876
  }
872
877
 
@@ -917,7 +922,7 @@ function startBot() {
917
922
  elic.customWaitingMessageId = null;
918
923
  elic.customWaitingQIdx = null;
919
924
 
920
- ctx.reply(`✅ Custom answer for "${q.question}": ${text}`);
925
+ try { await ctx.reply(`✅ Custom answer for "${q.question}": ${text}`); } catch {}
921
926
  log(`Elicitation ${elicId}: q${qIdx} custom = "${text}"`);
922
927
 
923
928
  await sendElicitationQuestion(elicId);
@@ -933,7 +938,7 @@ function startBot() {
933
938
  const mapping = messageToSession.get(replyTo);
934
939
  if (mapping) {
935
940
  if (mapping.type === 'permission') {
936
- ctx.reply('⚠️ That was a permission request — use the buttons above.\nTo send text input, reply to a notification message.');
941
+ try { await ctx.reply('⚠️ That was a permission request — use the buttons above.\nTo send text input, reply to a notification message.'); } catch {}
937
942
  return;
938
943
  }
939
944
  targetSessionId = mapping.sessionId;
@@ -971,7 +976,7 @@ function startBot() {
971
976
  if (uniqueSessions.length === 1) {
972
977
  targetSessionId = uniqueSessions[0];
973
978
  } else if (uniqueSessions.length === 0) {
974
- ctx.reply('No idle Claude sessions to send input to.');
979
+ try { await ctx.reply('No idle Claude sessions to send input to.'); } catch {}
975
980
  return;
976
981
  } else {
977
982
  // Multiple sessions — ask user to be specific
@@ -980,7 +985,7 @@ function startBot() {
980
985
  const num = getSessionLabel(sid);
981
986
  return ` #${num} ${s?.label || 'unknown'}`;
982
987
  }).join('\n');
983
- ctx.reply(`Multiple sessions are waiting. Reply to a specific notification message to choose:\n\n${labels}`);
988
+ try { await ctx.reply(`Multiple sessions are waiting. Reply to a specific notification message to choose:\n\n${labels}`); } catch {}
984
989
  return;
985
990
  }
986
991
  }
@@ -988,16 +993,16 @@ function startBot() {
988
993
  // Send the text to the terminal
989
994
  const session = sessions.get(targetSessionId);
990
995
  if (!session || !session.ttyPath) {
991
- ctx.reply(`⚠️ No TTY for session #${getSessionLabel(targetSessionId)}. Open the terminal to respond.`);
996
+ try { await ctx.reply(`⚠️ No TTY for session #${getSessionLabel(targetSessionId)}. Open the terminal to respond.`); } catch {}
992
997
  return;
993
998
  }
994
999
 
995
1000
  const ok = sendInputToTerminal(session.ttyPath, text);
996
1001
  if (ok) {
997
1002
  const num = getSessionLabel(targetSessionId);
998
- ctx.reply(`➡️ Sent to #${num} ${session.label}`);
1003
+ try { await ctx.reply(`➡️ Sent to #${num} ${session.label}`); } catch {}
999
1004
  } else {
1000
- ctx.reply(`⚠️ Could not send to terminal. Session may have ended, or terminal app not recognized.`);
1005
+ try { await ctx.reply(`⚠️ Could not send to terminal. Session may have ended, or terminal app not recognized.`); } catch {}
1001
1006
  }
1002
1007
  });
1003
1008
 
@@ -1269,6 +1274,11 @@ function main() {
1269
1274
  log(`Unhandled rejection: ${err?.message || err}`);
1270
1275
  });
1271
1276
 
1277
+ process.on('uncaughtException', (err) => {
1278
+ log(`Uncaught exception: ${err?.message || err}`);
1279
+ // Don't crash — log and continue
1280
+ });
1281
+
1272
1282
  process.on('SIGTERM', () => {
1273
1283
  log('Received SIGTERM, shutting down');
1274
1284
  bot.stop('SIGTERM');