rampup 0.1.7 → 0.1.9

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/entitlements.js CHANGED
@@ -8,12 +8,13 @@ import { getIdToken } from './auth.js';
8
8
  const ENTITLEMENT_API_URL = process.env.ENTITLEMENT_API_URL ||
9
9
  'https://entitlement-service.rian-19c.workers.dev';
10
10
 
11
- // Action costs mapping
11
+ // Action costs mapping (must match token_costs in entitlement service)
12
12
  const ACTION_COSTS = {
13
13
  'learn': { action: 'chat', credits: 1 },
14
14
  'ask': { action: 'chat', credits: 1 },
15
15
  'guide': { action: 'generate', credits: 2 },
16
- 'voice': { action: 'chat', credits: 2 },
16
+ 'voice': { action: 'voice', credits: 2 }, // Per voice interaction
17
+ 'voice_session': { action: 'voice', credits: 2 }, // Session start
17
18
  'architect': { action: 'debug', credits: 3 },
18
19
  };
19
20
 
package/index.js CHANGED
@@ -739,6 +739,17 @@ program
739
739
  // Get fresh token after potential login
740
740
  const authToken = await getIdToken();
741
741
 
742
+ // Check entitlements before starting voice session
743
+ const idempotencyKey = `voice-${Date.now()}`;
744
+ const entitlementCheck = await checkAndBurnTokens('voice_session', idempotencyKey);
745
+ if (!entitlementCheck.allowed) {
746
+ console.log(chalk.red(`\n❌ ${entitlementCheck.reason}\n`));
747
+ process.exit(1);
748
+ }
749
+ if (entitlementCheck.balance !== undefined) {
750
+ console.log(chalk.dim(`Credits remaining: ${entitlementCheck.balance}\n`));
751
+ }
752
+
742
753
  const RAMP_API_URL = process.env.RAMP_API_URL || 'https://ramp-api-946191982468.us-central1.run.app';
743
754
 
744
755
  // Track usage
@@ -850,6 +861,7 @@ async function runRealtimeVoiceMode(authToken, context, projectPath, usage, usag
850
861
  let audioChunks = [];
851
862
  let transcriptChunks = [];
852
863
  let isListening = false;
864
+ let isPlayingAudio = false; // Mute mic while playing to prevent feedback loop
853
865
  let sessionDurationSeconds = 0;
854
866
  const sessionTimer = setInterval(() => sessionDurationSeconds++, 1000);
855
867
 
@@ -916,6 +928,11 @@ Be friendly, practical, and reference specific files when relevant. If asked abo
916
928
  }
917
929
  break;
918
930
 
931
+ case 'response.created':
932
+ // AI is starting to respond - mute mic to prevent feedback
933
+ isPlayingAudio = true;
934
+ break;
935
+
919
936
  case 'response.audio.delta':
920
937
  // Collect audio chunks
921
938
  if (event.delta) {
@@ -945,6 +962,8 @@ Be friendly, practical, and reference specific files when relevant. If asked abo
945
962
  audioChunks = [];
946
963
  transcriptChunks = [];
947
964
  }
965
+ // Resume listening after audio finishes
966
+ isPlayingAudio = false;
948
967
  break;
949
968
 
950
969
  case 'response.done':
@@ -987,8 +1006,8 @@ Be friendly, practical, and reference specific files when relevant. If asked abo
987
1006
  micInputStream = micInstance.getAudioStream();
988
1007
 
989
1008
  micInputStream.on('data', (chunk) => {
990
- if (isConnected && ws.readyState === WebSocket.OPEN) {
991
- // Send audio to OpenAI
1009
+ // Don't send audio while AI is speaking (prevents feedback loop)
1010
+ if (isConnected && ws.readyState === WebSocket.OPEN && !isPlayingAudio) {
992
1011
  ws.send(JSON.stringify({
993
1012
  type: 'input_audio_buffer.append',
994
1013
  audio: chunk.toString('base64'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampup",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Ramp - Understand any codebase in hours. AI-powered developer onboarding CLI.",
5
5
  "type": "module",
6
6
  "bin": {