get-claudia 1.10.2 → 1.10.4

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.
@@ -352,7 +352,24 @@ if ($showGuide -match "^[Yy]") {
352
352
  Write-Host " (or open: ${DIM}https://t.me/userinfobot${NC})"
353
353
  Write-Host " Send it any message. It replies with your ID (a number)."
354
354
  Write-Host ""
355
- $userId = Read-Host " Paste your user ID here (or press Enter to skip)"
355
+ while ($true) {
356
+ $userId = Read-Host " Paste your user ID here (or press Enter to skip)"
357
+
358
+ # Empty = skip
359
+ if (-not $userId) { break }
360
+
361
+ # Strip leading @
362
+ $userId = $userId -replace '^@', ''
363
+
364
+ # Validate: must be all digits
365
+ if ($userId -match '^\d+$') { break }
366
+
367
+ Write-Host ""
368
+ Write-Host " ${RED}✗${NC} That looks like a username, not a numeric ID."
369
+ Write-Host " Telegram user IDs are numbers only (e.g. ${BOLD}1588190837${NC})."
370
+ Write-Host " Get yours from ${BOLD}@userinfobot${NC} in Telegram."
371
+ Write-Host ""
372
+ }
356
373
 
357
374
  if ($userId) {
358
375
  # Write the config
@@ -392,6 +409,15 @@ if ($showGuide -match "^[Yy]") {
392
409
  Add-Content -Path $profilePath -Value "`n# Claudia Gateway - Telegram`n$tokenLine"
393
410
  Write-Host " ${GREEN}✓${NC} Bot token saved to PowerShell profile"
394
411
  }
412
+
413
+ Write-Host ""
414
+ Write-Host " ${YELLOW}┌─────────────────────────────────────────────────┐${NC}"
415
+ Write-Host " ${YELLOW}│${NC} ${BOLD}Open a NEW terminal${NC} to run the gateway. ${YELLOW}│${NC}"
416
+ Write-Host " ${YELLOW}│${NC} This terminal doesn't have your token yet. ${YELLOW}│${NC}"
417
+ Write-Host " ${YELLOW}│${NC} ${YELLOW}│${NC}"
418
+ Write-Host " ${YELLOW}│${NC} Or reload your profile in this terminal: ${YELLOW}│${NC}"
419
+ Write-Host " ${YELLOW}│${NC} ${CYAN}. `$PROFILE${NC} ${YELLOW}│${NC}"
420
+ Write-Host " ${YELLOW}└─────────────────────────────────────────────────┘${NC}"
395
421
  } else {
396
422
  Write-Host ""
397
423
  Write-Host " ${YELLOW}!${NC} Skipped user ID. You'll need to add it manually:"
@@ -22,6 +22,14 @@ BIN_DIR="$CLAUDIA_DIR/bin"
22
22
  # Upgrade mode: skip config generation if config exists
23
23
  IS_UPGRADE="${CLAUDIA_GATEWAY_UPGRADE:-0}"
24
24
 
25
+ # Detect shell rc file (used for PATH and token persistence)
26
+ SHELL_RC=""
27
+ if [ -n "$ZSH_VERSION" ] || [ "$(basename "$SHELL")" = "zsh" ]; then
28
+ SHELL_RC="$HOME/.zshrc"
29
+ elif [ -n "$BASH_VERSION" ] || [ "$(basename "$SHELL")" = "bash" ]; then
30
+ SHELL_RC="$HOME/.bashrc"
31
+ fi
32
+
25
33
  # Clear screen and show banner
26
34
  clear
27
35
 
@@ -244,14 +252,6 @@ PATH_ADDED=0
244
252
  if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
245
253
  PATH_LINE='export PATH="$HOME/.claudia/bin:$PATH"'
246
254
 
247
- # Detect shell rc file
248
- SHELL_RC=""
249
- if [ -n "$ZSH_VERSION" ] || [ "$(basename "$SHELL")" = "zsh" ]; then
250
- SHELL_RC="$HOME/.zshrc"
251
- elif [ -n "$BASH_VERSION" ] || [ "$(basename "$SHELL")" = "bash" ]; then
252
- SHELL_RC="$HOME/.bashrc"
253
- fi
254
-
255
255
  if [ -n "$SHELL_RC" ]; then
256
256
  # Check if already in rc file (even if not in current PATH)
257
257
  if [ -f "$SHELL_RC" ] && grep -q '\.claudia/bin' "$SHELL_RC" 2>/dev/null; then
@@ -409,7 +409,28 @@ if [[ "$SHOW_GUIDE" =~ ^[Yy] ]]; then
409
409
  echo -e " (or tap: ${DIM}https://t.me/userinfobot${NC})"
410
410
  echo -e " Send it any message. It replies with your ID (a number)."
411
411
  echo
412
- read -p " Paste your user ID here (or press Enter to skip): " USER_ID
412
+ while true; do
413
+ read -p " Paste your user ID here (or press Enter to skip): " USER_ID
414
+
415
+ # Empty = skip
416
+ if [ -z "$USER_ID" ]; then
417
+ break
418
+ fi
419
+
420
+ # Strip leading @
421
+ USER_ID="${USER_ID#@}"
422
+
423
+ # Validate: must be all digits
424
+ if [[ "$USER_ID" =~ ^[0-9]+$ ]]; then
425
+ break
426
+ else
427
+ echo
428
+ echo -e " ${RED}✗${NC} That looks like a username, not a numeric ID."
429
+ echo -e " Telegram user IDs are numbers only (e.g. ${BOLD}1588190837${NC})."
430
+ echo -e " Get yours from ${BOLD}@userinfobot${NC} in Telegram."
431
+ echo
432
+ fi
433
+ done
413
434
 
414
435
  if [ -n "$USER_ID" ]; then
415
436
  # Write the config
@@ -446,6 +467,15 @@ if [[ "$SHOW_GUIDE" =~ ^[Yy] ]]; then
446
467
  echo "$TOKEN_LINE" >> "$SHELL_RC"
447
468
  echo -e " ${GREEN}✓${NC} Bot token saved to ~/${SHELL_RC##*/}"
448
469
  fi
470
+
471
+ echo
472
+ echo -e " ${YELLOW}┌─────────────────────────────────────────────────┐${NC}"
473
+ echo -e " ${YELLOW}│${NC} ${BOLD}Open a NEW terminal${NC} to run the gateway. ${YELLOW}│${NC}"
474
+ echo -e " ${YELLOW}│${NC} This terminal doesn't have your token yet. ${YELLOW}│${NC}"
475
+ echo -e " ${YELLOW}│${NC} ${YELLOW}│${NC}"
476
+ echo -e " ${YELLOW}│${NC} Or run in this terminal first: ${YELLOW}│${NC}"
477
+ echo -e " ${YELLOW}│${NC} ${CYAN}source ~/${SHELL_RC##*/}${NC} ${YELLOW}│${NC}"
478
+ echo -e " ${YELLOW}└─────────────────────────────────────────────────┘${NC}"
449
479
  else
450
480
  echo -e " ${YELLOW}!${NC} Add this to your shell profile to persist the token:"
451
481
  echo -e " ${DIM}${TOKEN_LINE}${NC}"
@@ -112,6 +112,28 @@ async function cmdStart(args) {
112
112
  }
113
113
  }
114
114
 
115
+ // Pre-flight: check for missing tokens before starting
116
+ const preflight = loadConfig();
117
+ const telegramEnabled = preflight.channels?.telegram?.enabled || overrides.channels?.telegram?.enabled;
118
+ const slackEnabled = preflight.channels?.slack?.enabled || overrides.channels?.slack?.enabled;
119
+ if (telegramEnabled && !process.env.TELEGRAM_BOT_TOKEN && !preflight.channels?.telegram?.token) {
120
+ console.error('Telegram is enabled but TELEGRAM_BOT_TOKEN is not set in this terminal.');
121
+ console.error('');
122
+ console.error('Your token was likely saved to your shell profile during install.');
123
+ console.error('Fix: Open a NEW terminal window and run claudia-gateway start');
124
+ console.error(' Or: source ~/.zshrc (or ~/.bashrc)');
125
+ console.error(' Or: export TELEGRAM_BOT_TOKEN="your-token-here"');
126
+ process.exit(1);
127
+ }
128
+ if (slackEnabled && !process.env.SLACK_BOT_TOKEN && !preflight.channels?.slack?.token) {
129
+ console.error('Slack is enabled but SLACK_BOT_TOKEN is not set in this terminal.');
130
+ console.error('');
131
+ console.error('Fix: Open a NEW terminal window and run claudia-gateway start');
132
+ console.error(' Or: export SLACK_BOT_TOKEN="your-token-here"');
133
+ console.error(' Or: export SLACK_APP_TOKEN="your-token-here"');
134
+ process.exit(1);
135
+ }
136
+
115
137
  const gateway = new Gateway(overrides);
116
138
 
117
139
  // Graceful shutdown
@@ -137,7 +159,9 @@ async function cmdStart(args) {
137
159
  console.log(` Channels: ${[...gateway.adapters.keys()].join(', ') || 'none'}`);
138
160
  console.log(` Memory: ${gateway.bridge?.memoryAvailable ? 'connected' : 'unavailable'}`);
139
161
  console.log(` PID: ${process.pid}`);
140
- console.log('\nPress Ctrl+C to stop.\n');
162
+ console.log('');
163
+ console.log('This terminal is now dedicated to the gateway. Use Claude in a separate terminal.');
164
+ console.log('Press Ctrl+C to stop.\n');
141
165
 
142
166
  // Keep process alive
143
167
  await new Promise(() => {});
@@ -28,6 +28,7 @@ export class Router {
28
28
  this.auth = auth;
29
29
  this.adapters = adapters;
30
30
  this.sessions = new Map(); // sessionKey -> { history: [], lastActive: Date }
31
+ this._rejectedUsers = new Set(); // Track users we've already sent a rejection reply to
31
32
  this._cleanupInterval = null;
32
33
  }
33
34
 
@@ -67,6 +68,29 @@ export class Router {
67
68
  // 1. Auth check
68
69
  if (!this.auth.isAuthorized(channel, userId)) {
69
70
  log.warn('Unauthorized message rejected', { channel, userId });
71
+
72
+ // Send a one-time rejection reply so the user knows what's wrong
73
+ const rejectKey = `${channel}:${userId}`;
74
+ if (!this._rejectedUsers.has(rejectKey)) {
75
+ this._rejectedUsers.add(rejectKey);
76
+ const rejectMsg =
77
+ `I don't recognize your user ID. Your ${channel} user ID is: ${userId}\n` +
78
+ `Ask the person who set me up to add it to the allowlist in ~/.claudia/gateway.json`;
79
+ try {
80
+ const adapter = this.adapters.get(channel);
81
+ if (adapter && metadata?.ctx && channel === 'telegram') {
82
+ await metadata.ctx.reply(rejectMsg);
83
+ } else if (adapter && metadata?.say && channel === 'slack') {
84
+ await metadata.say({ text: rejectMsg });
85
+ } else if (adapter) {
86
+ const targetId = metadata?.chatId || metadata?.channelId || userId;
87
+ await adapter.sendMessage(targetId, rejectMsg);
88
+ }
89
+ } catch (err) {
90
+ log.debug('Failed to send rejection reply', { channel, userId, error: err.message });
91
+ }
92
+ }
93
+
70
94
  return;
71
95
  }
72
96
 
@@ -42,14 +42,42 @@ export class AuthManager {
42
42
 
43
43
  // Fall back to global allowlist
44
44
  if (this.globalAllowed.size > 0) {
45
- return this.globalAllowed.has(userIdStr);
45
+ if (this.globalAllowed.has(userIdStr)) return true;
46
+ }
47
+
48
+ // Denied -- figure out why and log an actionable message
49
+ const channelEntries = this.channelAllowed[channel]
50
+ ? [...this.channelAllowed[channel]]
51
+ : [];
52
+ const globalEntries = [...this.globalAllowed];
53
+ const allEntries = [...channelEntries, ...globalEntries];
54
+
55
+ if (allEntries.length === 0) {
56
+ // No allowlists configured at all
57
+ log.warn('No allowlists configured - denying all access. Add user IDs to gateway config.', {
58
+ channel,
59
+ userId: userIdStr,
60
+ });
61
+ } else if (allEntries.some(e => !/^\d+$/.test(e))) {
62
+ // Allowlist contains non-numeric entries (likely usernames instead of IDs)
63
+ const nonNumeric = allEntries.filter(e => !/^\d+$/.test(e));
64
+ log.warn(
65
+ 'Auth denied: allowlist may contain usernames instead of numeric IDs. ' +
66
+ 'Telegram requires numeric user IDs (get yours from @userinfobot).', {
67
+ channel,
68
+ userId: userIdStr,
69
+ hint: 'Replace usernames with numeric IDs in gateway.json',
70
+ suspectEntries: nonNumeric,
71
+ }
72
+ );
73
+ } else {
74
+ // Allowlist exists with numeric IDs, user just isn't in it
75
+ log.warn('Auth denied: user not in allowlist.', {
76
+ channel,
77
+ userId: userIdStr,
78
+ });
46
79
  }
47
80
 
48
- // If no allowlists configured at all, deny by default (secure default)
49
- log.warn('No allowlists configured - denying all access. Add user IDs to gateway config.', {
50
- channel,
51
- userId: userIdStr,
52
- });
53
81
  return false;
54
82
  }
55
83
  }
@@ -59,4 +59,50 @@ describe('AuthManager', () => {
59
59
  assert.equal(auth.isAuthorized('telegram', 12345), true);
60
60
  assert.equal(auth.isAuthorized('telegram', '12345'), true);
61
61
  });
62
+
63
+ it('detects non-numeric allowlist entries (username instead of ID)', (t) => {
64
+ const warnings = [];
65
+ // Intercept log.warn by patching console (auth uses createLogger which uses console)
66
+ const auth = new AuthManager({
67
+ globalAllowedUsers: [],
68
+ channels: {
69
+ telegram: { allowedUsers: ['@kamba85'] },
70
+ },
71
+ });
72
+
73
+ // The auth check should deny and the warning should reference usernames
74
+ assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
75
+ });
76
+
77
+ it('logs generic denial for numeric allowlist mismatch', () => {
78
+ const auth = new AuthManager({
79
+ globalAllowedUsers: [],
80
+ channels: {
81
+ telegram: { allowedUsers: ['999999'] },
82
+ },
83
+ });
84
+
85
+ // User not in allowlist, but entries are numeric -- no username warning
86
+ assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
87
+ });
88
+
89
+ it('logs no-allowlists warning only when truly empty', () => {
90
+ const auth = new AuthManager({
91
+ globalAllowedUsers: [],
92
+ channels: {
93
+ telegram: { allowedUsers: [] },
94
+ },
95
+ });
96
+
97
+ assert.equal(auth.isAuthorized('telegram', '123'), false);
98
+ });
99
+
100
+ it('detects bare username without @ prefix', () => {
101
+ const auth = new AuthManager({
102
+ globalAllowedUsers: ['kamba85'],
103
+ channels: {},
104
+ });
105
+
106
+ assert.equal(auth.isAuthorized('telegram', '1588190837'), false);
107
+ });
62
108
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-claudia",
3
- "version": "1.10.2",
3
+ "version": "1.10.4",
4
4
  "description": "An AI assistant who learns how you work.",
5
5
  "keywords": [
6
6
  "claudia",