sam-coder-cli 1.0.4 → 1.0.6

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/bin/agi-cli.js CHANGED
@@ -13,7 +13,7 @@ const execAsync = util.promisify(exec);
13
13
  const CONFIG_PATH = path.join(os.homedir(), '.sam-coder-config.json');
14
14
  let OPENROUTER_API_KEY;
15
15
  const MODEL = 'deepseek/deepseek-chat-v3-0324:free';
16
- const API_BASE_URL = 'https://openrouter.ai/api/v1';
16
+ let API_BASE_URL = 'https://openrouter.ai/api/v1';
17
17
 
18
18
  // Tool/Function definitions for the AI
19
19
  const tools = [
@@ -508,12 +508,10 @@ async function handleToolCalls(toolCalls, messages) {
508
508
  }
509
509
 
510
510
  // Process a query with tool calling
511
- async function processQueryWithTools(query, conversation = [], maxIterations = 5) {
511
+ async function processQueryWithTools(query, conversation = []) {
512
512
  // Add user message to conversation
513
513
  const userMessage = { role: 'user', content: query };
514
- let messages = [...conversation, userMessage];
515
- let iteration = 0;
516
- let finalResponse = null;
514
+ const messages = [...conversation, userMessage];
517
515
 
518
516
  // Add system message if this is the first message
519
517
  if (conversation.length === 0) {
@@ -526,54 +524,50 @@ If a tool fails, you can try again with different parameters or a different appr
526
524
  });
527
525
  }
528
526
 
529
- // Process in a loop to handle multiple tool calls
530
- while (iteration < maxIterations) {
531
- ui.startThinking();
532
-
533
- try {
534
- const response = await callOpenRouterWithTools(messages);
535
- const assistantMessage = response.choices[0].message;
536
- messages.push(assistantMessage);
537
-
538
- // If there are no tool calls, we're done
539
- if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
540
- ui.stopThinking();
541
- finalResponse = assistantMessage.content || 'No content in response';
542
- break;
543
- }
544
-
545
- // Process tool calls
546
- console.log(`đŸ› ī¸ Executing ${assistantMessage.tool_calls.length} tools...`);
547
- const toolResults = await handleToolCalls(assistantMessage.tool_calls, messages);
548
-
549
- // Add tool results to messages
550
- messages = [...messages, ...toolResults];
551
-
552
- // If we've reached max iterations, get a final response
553
- if (iteration === maxIterations - 1) {
554
- console.log('â„šī¸ Reached maximum number of iterations. Getting final response...');
555
- const finalResponseObj = await callOpenRouterWithTools(messages);
556
- finalResponse = finalResponseObj.choices[0].message.content || 'No content in final response';
557
- }
558
-
559
- iteration++;
560
- } catch (error) {
527
+ ui.startThinking();
528
+
529
+ try {
530
+ const response = await callOpenRouterWithTools(messages);
531
+ const assistantMessage = response.choices[0].message;
532
+ messages.push(assistantMessage);
533
+
534
+ // If there are no tool calls, we're done
535
+ if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
561
536
  ui.stopThinking();
562
- console.error('❌ Error during processing:', error);
563
- finalResponse = `An error occurred: ${error.message}`;
564
- break;
537
+ return {
538
+ response: assistantMessage.content || 'No content in response',
539
+ conversation: messages
540
+ };
565
541
  }
542
+
543
+ // Process tool calls
544
+ ui.stopThinking(); // Stop thinking while tools execute
545
+ console.log(`đŸ› ī¸ Executing ${assistantMessage.tool_calls.length} tools...`);
546
+ const toolResults = await handleToolCalls(assistantMessage.tool_calls, messages);
547
+
548
+ // Add tool results to messages
549
+ messages.push(...toolResults);
550
+
551
+ // Now, get the AI's response to the tool results
552
+ ui.startThinking();
553
+ const finalResponseObj = await callOpenRouterWithTools(messages);
554
+ const finalAssistantMessage = finalResponseObj.choices[0].message;
555
+ messages.push(finalAssistantMessage);
556
+ ui.stopThinking();
557
+
558
+ return {
559
+ response: finalAssistantMessage.content || 'Actions executed. What is the next step?',
560
+ conversation: messages
561
+ };
562
+
563
+ } catch (error) {
564
+ ui.stopThinking();
565
+ console.error('❌ Error during processing:', error);
566
+ return {
567
+ response: `An error occurred: ${error.message}`,
568
+ conversation: messages
569
+ };
566
570
  }
567
-
568
- // If we don't have a final response yet (shouldn't happen, but just in case)
569
- if (!finalResponse) {
570
- finalResponse = 'No response generated. The operation may have timed out or encountered an error.';
571
- }
572
-
573
- return {
574
- response: finalResponse,
575
- conversation: messages
576
- };
577
571
  }
578
572
 
579
573
  // Execute a single action from the action system
@@ -781,8 +775,19 @@ async function runSetup(isReconfig = false) {
781
775
  }
782
776
 
783
777
  const apiKey = await askQuestion('Enter OpenRouter API Key: ');
778
+ const proCode = await askQuestion('Enter Pro Plan activation code (or press Enter to skip): ');
779
+
780
+ let config = { OPENROUTER_API_KEY: apiKey, isPro: false };
781
+
782
+ if (proCode === '1234') {
783
+ console.log('✅ Pro Plan activated!');
784
+ const customEndpoint = await askQuestion('Enter custom OpenAI-compatible API endpoint: ');
785
+ config.isPro = true;
786
+ config.customApiBase = customEndpoint;
787
+ } else if (proCode) {
788
+ console.log('âš ī¸ Invalid activation code. Continuing with standard setup.');
789
+ }
784
790
 
785
- const config = { OPENROUTER_API_KEY: apiKey };
786
791
  await writeConfig(config);
787
792
 
788
793
  console.log('✅ Configuration saved successfully!');
@@ -799,6 +804,10 @@ async function start() {
799
804
  }
800
805
 
801
806
  OPENROUTER_API_KEY = config.OPENROUTER_API_KEY;
807
+ if (config.isPro && config.customApiBase) {
808
+ API_BASE_URL = config.customApiBase;
809
+ console.log(`🚀 Using Pro Plan custom endpoint: ${API_BASE_URL}`);
810
+ }
802
811
 
803
812
  ui.showHeader();
804
813
  console.log('Select Mode:');
@@ -1,125 +1,218 @@
1
1
  import pygame
2
- import time
3
2
  import random
4
3
 
5
4
  # Initialize pygame
6
5
  pygame.init()
7
6
 
8
- # Screen dimensions
9
- WIDTH, HEIGHT = 800, 600
7
+ # Constants
8
+ SCREEN_WIDTH = 300
9
+ SCREEN_HEIGHT = 600
10
+ BLOCK_SIZE = 30
11
+ GRID_WIDTH = 10
12
+ GRID_HEIGHT = 20
13
+ GAME_AREA_LEFT = (SCREEN_WIDTH - GRID_WIDTH * BLOCK_SIZE) // 2
14
+ GAME_AREA_TOP = SCREEN_HEIGHT - GRID_HEIGHT * BLOCK_SIZE
10
15
 
11
16
  # Colors
12
- WHITE = (255, 255, 255)
13
17
  BLACK = (0, 0, 0)
14
- RED = (213, 50, 80)
15
- GREEN = (0, 255, 0)
16
- BLUE = (50, 153, 213)
17
-
18
- # Snake block size
19
- BLOCK_SIZE = 20
18
+ WHITE = (255, 255, 255)
19
+ GRAY = (128, 128, 128)
20
+ COLORS = [
21
+ (0, 255, 255), # Cyan (I)
22
+ (0, 0, 255), # Blue (J)
23
+ (255, 165, 0), # Orange (L)
24
+ (255, 255, 0), # Yellow (O)
25
+ (0, 255, 0), # Green (S)
26
+ (128, 0, 128), # Purple (T)
27
+ (255, 0, 0) # Red (Z)
28
+ ]
29
+
30
+ # Tetrimino shapes
31
+ SHAPES = [
32
+ [[1, 1, 1, 1]], # I
33
+ [[1, 0, 0], [1, 1, 1]], # J
34
+ [[0, 0, 1], [1, 1, 1]], # L
35
+ [[1, 1], [1, 1]], # O
36
+ [[0, 1, 1], [1, 1, 0]], # S
37
+ [[0, 1, 0], [1, 1, 1]], # T
38
+ [[1, 1, 0], [0, 1, 1]] # Z
39
+ ]
40
+
41
+ # Set up the display
42
+ screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
43
+ pygame.display.set_caption("Tetris")
20
44
 
21
- # Clock for controlling the speed of the snake
22
45
  clock = pygame.time.Clock()
23
46
 
24
- # Font for score
25
- font_style = pygame.font.SysFont("bahnschrift", 25)
26
- score_font = pygame.font.SysFont("comicsansms", 35)
27
-
28
- def message(msg, color):
29
- mesg = font_style.render(msg, True, color)
30
- screen.blit(mesg, [WIDTH / 6, HEIGHT / 3])
31
-
32
- def your_score(score):
33
- value = score_font.render("Your Score: " + str(score), True, GREEN)
34
- screen.blit(value, [0, 0])
35
-
36
- def our_snake(block_size, snake_list):
37
- for pos in snake_list:
38
- pygame.draw.rect(screen, BLUE, [pos[0], pos[1], block_size, block_size])
39
-
40
- def gameLoop():
41
- game_over = False
42
- game_close = False
43
-
44
- x1 = WIDTH / 2
45
- y1 = HEIGHT / 2
46
-
47
- x1_change = 0
48
- y1_change = 0
49
-
50
- snake_list = []
51
- length_of_snake = 1
52
-
53
- foodx = round(random.randrange(0, WIDTH - BLOCK_SIZE) / 20.0) * 20.0
54
- foody = round(random.randrange(0, HEIGHT - BLOCK_SIZE) / 20.0) * 20.0
55
-
56
- while not game_over:
57
-
58
- while game_close:
59
- screen.fill(BLACK)
60
- message("You Lost! Press Q-Quit or C-Play Again", RED)
61
- your_score(length_of_snake - 1)
62
- pygame.display.update()
63
-
64
- for event in pygame.event.get():
65
- if event.type == pygame.KEYDOWN:
66
- if event.key == pygame.K_q:
67
- game_over = True
68
- game_close = False
69
- if event.key == pygame.K_c:
70
- gameLoop()
71
-
72
- for event in pygame.event.get():
73
- if event.type == pygame.QUIT:
47
+ # Game variables
48
+ grid = [[0 for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
49
+ current_piece = None
50
+ next_piece = None
51
+ current_x = GRID_WIDTH // 2
52
+ current_y = 0
53
+ score = 0
54
+ game_over = False
55
+ fall_time = 0
56
+ fall_speed = 0.5 # seconds
57
+
58
+ def new_piece():
59
+ global current_piece, next_piece, current_x, current_y
60
+ if next_piece is None:
61
+ next_piece = random.randint(0, len(SHAPES) - 1)
62
+ current_piece = next_piece
63
+ next_piece = random.randint(0, len(SHAPES) - 1)
64
+ current_x = GRID_WIDTH // 2 - len(SHAPES[current_piece][0]) // 2
65
+ current_y = 0
66
+ if check_collision():
67
+ return False
68
+ return True
69
+
70
+ def check_collision():
71
+ for y, row in enumerate(SHAPES[current_piece]):
72
+ for x, cell in enumerate(row):
73
+ if cell:
74
+ if (current_y + y >= GRID_HEIGHT or
75
+ current_x + x < 0 or
76
+ current_x + x >= GRID_WIDTH or
77
+ grid[current_y + y][current_x + x]):
78
+ return True
79
+ return False
80
+
81
+ def merge_piece():
82
+ for y, row in enumerate(SHAPES[current_piece]):
83
+ for x, cell in enumerate(row):
84
+ if cell:
85
+ grid[current_y + y][current_x + x] = current_piece + 1
86
+
87
+ def clear_lines():
88
+ global grid, score
89
+ lines_cleared = 0
90
+ for y in range(GRID_HEIGHT - 1, -1, -1):
91
+ if all(grid[y]):
92
+ lines_cleared += 1
93
+ for y2 in range(y, 0, -1):
94
+ grid[y2] = grid[y2 - 1][:]
95
+ grid[0] = [0] * GRID_WIDTH
96
+ score += lines_cleared ** 2 * 100
97
+
98
+ def draw_grid():
99
+ for y in range(GRID_HEIGHT):
100
+ for x in range(GRID_WIDTH):
101
+ if grid[y][x]:
102
+ pygame.draw.rect(
103
+ screen, COLORS[grid[y][x] - 1],
104
+ (GAME_AREA_LEFT + x * BLOCK_SIZE, GAME_AREA_TOP + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
105
+ )
106
+ pygame.draw.rect(
107
+ screen, WHITE,
108
+ (GAME_AREA_LEFT + x * BLOCK_SIZE, GAME_AREA_TOP + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE),
109
+ 1
110
+ )
111
+
112
+ def draw_piece():
113
+ for y, row in enumerate(SHAPES[current_piece]):
114
+ for x, cell in enumerate(row):
115
+ if cell:
116
+ pygame.draw.rect(
117
+ screen, COLORS[current_piece],
118
+ (GAME_AREA_LEFT + (current_x + x) * BLOCK_SIZE, GAME_AREA_TOP + (current_y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
119
+ )
120
+ pygame.draw.rect(
121
+ screen, WHITE,
122
+ (GAME_AREA_LEFT + (current_x + x) * BLOCK_SIZE, GAME_AREA_TOP + (current_y + y) * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE),
123
+ 1
124
+ )
125
+
126
+ def draw_next_piece():
127
+ if next_piece is not None:
128
+ font = pygame.font.SysFont(None, 24)
129
+ text = font.render("Next:", True, WHITE)
130
+ screen.blit(text, (20, 20))
131
+ for y, row in enumerate(SHAPES[next_piece]):
132
+ for x, cell in enumerate(row):
133
+ if cell:
134
+ pygame.draw.rect(
135
+ screen, COLORS[next_piece],
136
+ (40 + x * BLOCK_SIZE, 50 + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
137
+ )
138
+ pygame.draw.rect(
139
+ screen, WHITE,
140
+ (40 + x * BLOCK_SIZE, 50 + y * BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE),
141
+ 1
142
+ )
143
+
144
+ def draw_score():
145
+ font = pygame.font.SysFont(None, 36)
146
+ text = font.render(f"Score: {score}", True, WHITE)
147
+ screen.blit(text, (20, SCREEN_HEIGHT - 50))
148
+
149
+ def rotate_piece():
150
+ global current_piece
151
+ rotated = list(zip(*reversed(SHAPES[current_piece])))
152
+ for y in range(len(rotated)):
153
+ rotated[y] = list(rotated[y])
154
+ old_shape = SHAPES[current_piece]
155
+ SHAPES[current_piece] = rotated
156
+ if check_collision():
157
+ SHAPES[current_piece] = old_shape
158
+
159
+ # Main game loop
160
+ new_piece()
161
+ running = True
162
+ while running:
163
+ screen.fill(BLACK)
164
+ fall_time += clock.get_rawtime() / 1000 # Convert to seconds
165
+ clock.tick()
166
+
167
+ if fall_time >= fall_speed:
168
+ fall_time = 0
169
+ current_y += 1
170
+ if check_collision():
171
+ current_y -= 1
172
+ merge_piece()
173
+ clear_lines()
174
+ if not new_piece():
74
175
  game_over = True
75
- if event.type == pygame.KEYDOWN:
76
- if event.key == pygame.K_LEFT:
77
- x1_change = -BLOCK_SIZE
78
- y1_change = 0
79
- elif event.key == pygame.K_RIGHT:
80
- x1_change = BLOCK_SIZE
81
- y1_change = 0
82
- elif event.key == pygame.K_UP:
83
- y1_change = -BLOCK_SIZE
84
- x1_change = 0
85
- elif event.key == pygame.K_DOWN:
86
- y1_change = BLOCK_SIZE
87
- x1_change = 0
88
-
89
- if x1 >= WIDTH or x1 < 0 or y1 >= HEIGHT or y1 < 0:
90
- game_close = True
91
- x1 += x1_change
92
- y1 += y1_change
93
- screen.fill(BLACK)
94
- pygame.draw.rect(screen, GREEN, [foodx, foody, BLOCK_SIZE, BLOCK_SIZE])
95
- snake_head = []
96
- snake_head.append(x1)
97
- snake_head.append(y1)
98
- snake_list.append(snake_head)
99
- if len(snake_list) > length_of_snake:
100
- del snake_list[0]
101
-
102
- for pos in snake_list[:-1]:
103
- if pos == snake_head:
104
- game_close = True
105
-
106
- our_snake(BLOCK_SIZE, snake_list)
107
- your_score(length_of_snake - 1)
108
176
 
177
+ for event in pygame.event.get():
178
+ if event.type == pygame.QUIT:
179
+ running = False
180
+ if event.type == pygame.KEYDOWN:
181
+ if event.key == pygame.K_LEFT:
182
+ current_x -= 1
183
+ if check_collision():
184
+ current_x += 1
185
+ if event.key == pygame.K_RIGHT:
186
+ current_x += 1
187
+ if check_collision():
188
+ current_x -= 1
189
+ if event.key == pygame.K_DOWN:
190
+ current_y += 1
191
+ if check_collision():
192
+ current_y -= 1
193
+ if event.key == pygame.K_UP:
194
+ rotate_piece()
195
+ if event.key == pygame.K_SPACE:
196
+ while not check_collision():
197
+ current_y += 1
198
+ current_y -= 1
199
+ merge_piece()
200
+ clear_lines()
201
+ if not new_piece():
202
+ game_over = True
203
+
204
+ draw_grid()
205
+ draw_piece()
206
+ draw_next_piece()
207
+ draw_score()
208
+ pygame.display.update()
209
+
210
+ if game_over:
211
+ font = pygame.font.SysFont(None, 48)
212
+ text = font.render("GAME OVER", True, WHITE)
213
+ screen.blit(text, (SCREEN_WIDTH // 2 - 100, SCREEN_HEIGHT // 2 - 24))
109
214
  pygame.display.update()
215
+ pygame.time.wait(2000)
216
+ running = False
110
217
 
111
- if x1 == foodx and y1 == foody:
112
- foodx = round(random.randrange(0, WIDTH - BLOCK_SIZE) / 20.0) * 20.0
113
- foody = round(random.randrange(0, HEIGHT - BLOCK_SIZE) / 20.0) * 20.0
114
- length_of_snake += 1
115
-
116
- clock.tick(15)
117
-
118
- pygame.quit()
119
- quit()
120
-
121
- # Create the screen
122
- screen = pygame.display.set_mode((WIDTH, HEIGHT))
123
- pygame.display.set_caption('Snake Game')
124
-
125
- gameLoop()
218
+ pygame.quit()
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {
7
- "sam-coder": "./bin/agi-cli.js"
7
+ "sam-coder": "bin/agi-cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node ./bin/agi-cli.js"