@mariozechner/pi-coding-agent 0.49.2 → 0.50.0
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/CHANGELOG.md +126 -1
- package/README.md +310 -1229
- package/dist/cli/args.d.ts +5 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +57 -22
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config-selector.d.ts +14 -0
- package/dist/cli/config-selector.d.ts.map +1 -0
- package/dist/cli/config-selector.js +31 -0
- package/dist/cli/config-selector.js.map +1 -0
- package/dist/cli/session-picker.d.ts.map +1 -1
- package/dist/cli/session-picker.js +1 -1
- package/dist/cli/session-picker.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +53 -34
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +262 -67
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +8 -18
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +39 -55
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +2 -1
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/diagnostics.d.ts +15 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +2 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/export-html/template.css +9 -0
- package/dist/core/export-html/template.js +6 -4
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +10 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +9 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +39 -12
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +112 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +9 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +13 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts +42 -2
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +154 -44
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +3 -2
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +129 -0
- package/dist/core/package-manager.d.ts.map +1 -0
- package/dist/core/package-manager.js +1148 -0
- package/dist/core/package-manager.js.map +1 -0
- package/dist/core/prompt-templates.d.ts +6 -0
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +114 -54
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/resource-loader.d.ts +160 -0
- package/dist/core/resource-loader.d.ts.map +1 -0
- package/dist/core/resource-loader.js +604 -0
- package/dist/core/resource-loader.js.map +1 -0
- package/dist/core/sdk.d.ts +14 -105
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +52 -304
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +45 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +39 -16
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +107 -25
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +18 -10
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +126 -93
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -27
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +16 -103
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +2 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -4
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +209 -97
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -2
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +5 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +5 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +29 -9
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +3 -2
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +4 -2
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +3 -2
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +4 -2
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +71 -0
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/config-selector.js +468 -0
- package/dist/modes/interactive/components/config-selector.js.map +1 -0
- package/dist/modes/interactive/components/custom-message.d.ts +3 -2
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +4 -2
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +9 -0
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +3 -4
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts +18 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +195 -87
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +12 -5
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +2 -2
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +2 -2
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +2 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +47 -2
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +566 -211
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/dark.json +1 -1
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme-schema.json +8 -1
- package/dist/modes/interactive/theme/theme.d.ts +8 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +79 -28
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +25 -89
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +32 -92
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/git.d.ts +2 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +6 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/shell.d.ts +1 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +16 -2
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/sleep.d.ts +5 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +17 -0
- package/dist/utils/sleep.js.map +1 -0
- package/docs/compaction.md +23 -21
- package/docs/custom-provider.md +538 -0
- package/docs/development.md +69 -0
- package/docs/extensions.md +180 -118
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/interactive-mode.png +0 -0
- package/docs/images/tree-view.png +0 -0
- package/docs/json.md +79 -0
- package/docs/keybindings.md +162 -0
- package/docs/models.md +193 -0
- package/docs/packages.md +163 -0
- package/docs/prompt-templates.md +67 -0
- package/docs/providers.md +147 -0
- package/docs/sdk.md +111 -178
- package/docs/session.md +167 -16
- package/docs/settings.md +216 -0
- package/docs/shell-aliases.md +13 -0
- package/docs/skills.md +111 -202
- package/docs/terminal-setup.md +65 -0
- package/docs/themes.md +295 -0
- package/docs/tui.md +36 -5
- package/docs/windows.md +17 -0
- package/examples/README.md +1 -0
- package/examples/extensions/README.md +24 -2
- package/examples/extensions/antigravity-image-gen.ts +413 -0
- package/examples/extensions/bookmark.ts +50 -0
- package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
- package/examples/extensions/custom-provider-anthropic/package.json +19 -0
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
- package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
- package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
- package/examples/extensions/doom-overlay/doom/build.sh +1 -1
- package/examples/extensions/event-bus.ts +43 -0
- package/examples/extensions/inline-bash.ts +94 -0
- package/examples/extensions/message-renderer.ts +59 -0
- package/examples/extensions/session-name.ts +27 -0
- package/examples/extensions/space-invaders.ts +560 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/03-custom-prompt.ts +20 -9
- package/examples/sdk/04-skills.ts +26 -27
- package/examples/sdk/06-extensions.ts +15 -6
- package/examples/sdk/07-context-files.ts +22 -18
- package/examples/sdk/08-prompt-templates.ts +19 -14
- package/examples/sdk/09-api-keys-and-oauth.ts +5 -12
- package/examples/sdk/10-settings.ts +3 -3
- package/examples/sdk/12-full-control.ts +16 -7
- package/examples/sdk/README.md +24 -30
- package/package.json +4 -4
- package/docs/theme.md +0 -617
- package/examples/extensions/chalk-logger.ts +0 -26
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Space Invaders game extension - play with /invaders command
|
|
3
|
+
* Uses Kitty keyboard protocol for smooth movement (press/release detection)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { isKeyRelease, Key, matchesKey, visibleWidth } from "@mariozechner/pi-tui";
|
|
8
|
+
|
|
9
|
+
const GAME_WIDTH = 60;
|
|
10
|
+
const GAME_HEIGHT = 24;
|
|
11
|
+
const TICK_MS = 50;
|
|
12
|
+
const PLAYER_Y = GAME_HEIGHT - 2;
|
|
13
|
+
const ALIEN_ROWS = 5;
|
|
14
|
+
const ALIEN_COLS = 11;
|
|
15
|
+
const ALIEN_START_Y = 2;
|
|
16
|
+
|
|
17
|
+
type Point = { x: number; y: number };
|
|
18
|
+
|
|
19
|
+
interface Bullet extends Point {
|
|
20
|
+
direction: -1 | 1; // -1 = up (player), 1 = down (alien)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Alien extends Point {
|
|
24
|
+
type: number; // 0, 1, 2 for different alien types
|
|
25
|
+
alive: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface Shield {
|
|
29
|
+
x: number;
|
|
30
|
+
segments: boolean[][]; // 4x3 grid of destructible segments
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface GameState {
|
|
34
|
+
player: { x: number; lives: number };
|
|
35
|
+
aliens: Alien[];
|
|
36
|
+
alienDirection: 1 | -1;
|
|
37
|
+
alienMoveCounter: number;
|
|
38
|
+
alienMoveDelay: number;
|
|
39
|
+
alienDropping: boolean;
|
|
40
|
+
bullets: Bullet[];
|
|
41
|
+
shields: Shield[];
|
|
42
|
+
score: number;
|
|
43
|
+
highScore: number;
|
|
44
|
+
level: number;
|
|
45
|
+
gameOver: boolean;
|
|
46
|
+
victory: boolean;
|
|
47
|
+
alienShootCounter: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface KeyState {
|
|
51
|
+
left: boolean;
|
|
52
|
+
right: boolean;
|
|
53
|
+
fire: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createShields(): Shield[] {
|
|
57
|
+
const shields: Shield[] = [];
|
|
58
|
+
const shieldPositions = [8, 22, 36, 50];
|
|
59
|
+
for (const x of shieldPositions) {
|
|
60
|
+
shields.push({
|
|
61
|
+
x,
|
|
62
|
+
segments: [
|
|
63
|
+
[true, true, true, true],
|
|
64
|
+
[true, true, true, true],
|
|
65
|
+
[true, false, false, true],
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return shields;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createAliens(): Alien[] {
|
|
73
|
+
const aliens: Alien[] = [];
|
|
74
|
+
for (let row = 0; row < ALIEN_ROWS; row++) {
|
|
75
|
+
const type = row === 0 ? 2 : row < 3 ? 1 : 0;
|
|
76
|
+
for (let col = 0; col < ALIEN_COLS; col++) {
|
|
77
|
+
aliens.push({
|
|
78
|
+
x: 4 + col * 5,
|
|
79
|
+
y: ALIEN_START_Y + row * 2,
|
|
80
|
+
type,
|
|
81
|
+
alive: true,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return aliens;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createInitialState(highScore = 0, level = 1): GameState {
|
|
89
|
+
return {
|
|
90
|
+
player: { x: Math.floor(GAME_WIDTH / 2), lives: 3 },
|
|
91
|
+
aliens: createAliens(),
|
|
92
|
+
alienDirection: 1,
|
|
93
|
+
alienMoveCounter: 0,
|
|
94
|
+
alienMoveDelay: Math.max(5, 20 - level * 2),
|
|
95
|
+
alienDropping: false,
|
|
96
|
+
bullets: [],
|
|
97
|
+
shields: createShields(),
|
|
98
|
+
score: 0,
|
|
99
|
+
highScore,
|
|
100
|
+
level,
|
|
101
|
+
gameOver: false,
|
|
102
|
+
victory: false,
|
|
103
|
+
alienShootCounter: 0,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
class SpaceInvadersComponent {
|
|
108
|
+
private state: GameState;
|
|
109
|
+
private keys: KeyState = { left: false, right: false, fire: false };
|
|
110
|
+
private interval: ReturnType<typeof setInterval> | null = null;
|
|
111
|
+
private onClose: () => void;
|
|
112
|
+
private onSave: (state: GameState | null) => void;
|
|
113
|
+
private tui: { requestRender: () => void };
|
|
114
|
+
private cachedLines: string[] = [];
|
|
115
|
+
private cachedWidth = 0;
|
|
116
|
+
private version = 0;
|
|
117
|
+
private cachedVersion = -1;
|
|
118
|
+
private paused: boolean;
|
|
119
|
+
private fireCooldown = 0;
|
|
120
|
+
private playerMoveCounter = 0;
|
|
121
|
+
|
|
122
|
+
// Opt-in to key release events for smooth movement
|
|
123
|
+
wantsKeyRelease = true;
|
|
124
|
+
|
|
125
|
+
constructor(
|
|
126
|
+
tui: { requestRender: () => void },
|
|
127
|
+
onClose: () => void,
|
|
128
|
+
onSave: (state: GameState | null) => void,
|
|
129
|
+
savedState?: GameState,
|
|
130
|
+
) {
|
|
131
|
+
this.tui = tui;
|
|
132
|
+
if (savedState && !savedState.gameOver && !savedState.victory) {
|
|
133
|
+
this.state = savedState;
|
|
134
|
+
this.paused = true;
|
|
135
|
+
} else {
|
|
136
|
+
this.state = createInitialState(savedState?.highScore);
|
|
137
|
+
this.paused = false;
|
|
138
|
+
this.startGame();
|
|
139
|
+
}
|
|
140
|
+
this.onClose = onClose;
|
|
141
|
+
this.onSave = onSave;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private startGame(): void {
|
|
145
|
+
this.interval = setInterval(() => {
|
|
146
|
+
if (!this.state.gameOver && !this.state.victory) {
|
|
147
|
+
this.tick();
|
|
148
|
+
this.version++;
|
|
149
|
+
this.tui.requestRender();
|
|
150
|
+
}
|
|
151
|
+
}, TICK_MS);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private tick(): void {
|
|
155
|
+
// Player movement (smooth, every other tick)
|
|
156
|
+
this.playerMoveCounter++;
|
|
157
|
+
if (this.playerMoveCounter >= 2) {
|
|
158
|
+
this.playerMoveCounter = 0;
|
|
159
|
+
if (this.keys.left && this.state.player.x > 2) {
|
|
160
|
+
this.state.player.x--;
|
|
161
|
+
}
|
|
162
|
+
if (this.keys.right && this.state.player.x < GAME_WIDTH - 3) {
|
|
163
|
+
this.state.player.x++;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fire cooldown
|
|
168
|
+
if (this.fireCooldown > 0) this.fireCooldown--;
|
|
169
|
+
|
|
170
|
+
// Player shooting
|
|
171
|
+
if (this.keys.fire && this.fireCooldown === 0) {
|
|
172
|
+
const playerBullets = this.state.bullets.filter((b) => b.direction === -1);
|
|
173
|
+
if (playerBullets.length < 2) {
|
|
174
|
+
this.state.bullets.push({ x: this.state.player.x, y: PLAYER_Y - 1, direction: -1 });
|
|
175
|
+
this.fireCooldown = 8;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Move bullets
|
|
180
|
+
this.state.bullets = this.state.bullets.filter((bullet) => {
|
|
181
|
+
bullet.y += bullet.direction;
|
|
182
|
+
return bullet.y >= 0 && bullet.y < GAME_HEIGHT;
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Alien movement
|
|
186
|
+
this.state.alienMoveCounter++;
|
|
187
|
+
if (this.state.alienMoveCounter >= this.state.alienMoveDelay) {
|
|
188
|
+
this.state.alienMoveCounter = 0;
|
|
189
|
+
this.moveAliens();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Alien shooting
|
|
193
|
+
this.state.alienShootCounter++;
|
|
194
|
+
if (this.state.alienShootCounter >= 30) {
|
|
195
|
+
this.state.alienShootCounter = 0;
|
|
196
|
+
this.alienShoot();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Collision detection
|
|
200
|
+
this.checkCollisions();
|
|
201
|
+
|
|
202
|
+
// Check victory
|
|
203
|
+
if (this.state.aliens.every((a) => !a.alive)) {
|
|
204
|
+
this.state.victory = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private moveAliens(): void {
|
|
209
|
+
const aliveAliens = this.state.aliens.filter((a) => a.alive);
|
|
210
|
+
if (aliveAliens.length === 0) return;
|
|
211
|
+
|
|
212
|
+
if (this.state.alienDropping) {
|
|
213
|
+
// Drop down
|
|
214
|
+
for (const alien of aliveAliens) {
|
|
215
|
+
alien.y++;
|
|
216
|
+
if (alien.y >= PLAYER_Y - 1) {
|
|
217
|
+
this.state.gameOver = true;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
this.state.alienDropping = false;
|
|
222
|
+
} else {
|
|
223
|
+
// Check if we need to change direction
|
|
224
|
+
const minX = Math.min(...aliveAliens.map((a) => a.x));
|
|
225
|
+
const maxX = Math.max(...aliveAliens.map((a) => a.x));
|
|
226
|
+
|
|
227
|
+
if (
|
|
228
|
+
(this.state.alienDirection === 1 && maxX >= GAME_WIDTH - 3) ||
|
|
229
|
+
(this.state.alienDirection === -1 && minX <= 2)
|
|
230
|
+
) {
|
|
231
|
+
this.state.alienDirection *= -1;
|
|
232
|
+
this.state.alienDropping = true;
|
|
233
|
+
} else {
|
|
234
|
+
// Move horizontally
|
|
235
|
+
for (const alien of aliveAliens) {
|
|
236
|
+
alien.x += this.state.alienDirection;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Speed up as fewer aliens remain
|
|
242
|
+
const aliveCount = aliveAliens.length;
|
|
243
|
+
if (aliveCount <= 5) {
|
|
244
|
+
this.state.alienMoveDelay = 1;
|
|
245
|
+
} else if (aliveCount <= 10) {
|
|
246
|
+
this.state.alienMoveDelay = 2;
|
|
247
|
+
} else if (aliveCount <= 20) {
|
|
248
|
+
this.state.alienMoveDelay = 3;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private alienShoot(): void {
|
|
253
|
+
const aliveAliens = this.state.aliens.filter((a) => a.alive);
|
|
254
|
+
if (aliveAliens.length === 0) return;
|
|
255
|
+
|
|
256
|
+
// Find bottom-most alien in each column
|
|
257
|
+
const columns = new Map<number, Alien>();
|
|
258
|
+
for (const alien of aliveAliens) {
|
|
259
|
+
const existing = columns.get(alien.x);
|
|
260
|
+
if (!existing || alien.y > existing.y) {
|
|
261
|
+
columns.set(alien.x, alien);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Random column shoots
|
|
266
|
+
const shooters = Array.from(columns.values());
|
|
267
|
+
if (shooters.length > 0 && this.state.bullets.filter((b) => b.direction === 1).length < 3) {
|
|
268
|
+
const shooter = shooters[Math.floor(Math.random() * shooters.length)];
|
|
269
|
+
this.state.bullets.push({ x: shooter.x, y: shooter.y + 1, direction: 1 });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private checkCollisions(): void {
|
|
274
|
+
const bulletsToRemove = new Set<Bullet>();
|
|
275
|
+
|
|
276
|
+
for (const bullet of this.state.bullets) {
|
|
277
|
+
// Player bullets hitting aliens
|
|
278
|
+
if (bullet.direction === -1) {
|
|
279
|
+
for (const alien of this.state.aliens) {
|
|
280
|
+
if (alien.alive && Math.abs(bullet.x - alien.x) <= 1 && bullet.y === alien.y) {
|
|
281
|
+
alien.alive = false;
|
|
282
|
+
bulletsToRemove.add(bullet);
|
|
283
|
+
const points = [10, 20, 30][alien.type];
|
|
284
|
+
this.state.score += points;
|
|
285
|
+
if (this.state.score > this.state.highScore) {
|
|
286
|
+
this.state.highScore = this.state.score;
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Alien bullets hitting player
|
|
294
|
+
if (bullet.direction === 1) {
|
|
295
|
+
if (Math.abs(bullet.x - this.state.player.x) <= 1 && bullet.y === PLAYER_Y) {
|
|
296
|
+
bulletsToRemove.add(bullet);
|
|
297
|
+
this.state.player.lives--;
|
|
298
|
+
if (this.state.player.lives <= 0) {
|
|
299
|
+
this.state.gameOver = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Bullets hitting shields
|
|
305
|
+
for (const shield of this.state.shields) {
|
|
306
|
+
const relX = bullet.x - shield.x;
|
|
307
|
+
const relY = bullet.y - (PLAYER_Y - 5);
|
|
308
|
+
if (relX >= 0 && relX < 4 && relY >= 0 && relY < 3) {
|
|
309
|
+
if (shield.segments[relY][relX]) {
|
|
310
|
+
shield.segments[relY][relX] = false;
|
|
311
|
+
bulletsToRemove.add(bullet);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.state.bullets = this.state.bullets.filter((b) => !bulletsToRemove.has(b));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
handleInput(data: string): void {
|
|
321
|
+
const released = isKeyRelease(data);
|
|
322
|
+
|
|
323
|
+
// Pause handling
|
|
324
|
+
if (this.paused && !released) {
|
|
325
|
+
if (matchesKey(data, Key.escape) || data === "q" || data === "Q") {
|
|
326
|
+
this.dispose();
|
|
327
|
+
this.onClose();
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.paused = false;
|
|
331
|
+
this.startGame();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ESC to pause and save
|
|
336
|
+
if (!released && matchesKey(data, Key.escape)) {
|
|
337
|
+
this.dispose();
|
|
338
|
+
this.onSave(this.state);
|
|
339
|
+
this.onClose();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Q to quit without saving
|
|
344
|
+
if (!released && (data === "q" || data === "Q")) {
|
|
345
|
+
this.dispose();
|
|
346
|
+
this.onSave(null);
|
|
347
|
+
this.onClose();
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Movement keys (track press/release state)
|
|
352
|
+
if (matchesKey(data, Key.left) || data === "a" || data === "A" || matchesKey(data, "a")) {
|
|
353
|
+
this.keys.left = !released;
|
|
354
|
+
}
|
|
355
|
+
if (matchesKey(data, Key.right) || data === "d" || data === "D" || matchesKey(data, "d")) {
|
|
356
|
+
this.keys.right = !released;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Fire key
|
|
360
|
+
if (matchesKey(data, Key.space) || data === " " || data === "f" || data === "F" || matchesKey(data, "f")) {
|
|
361
|
+
this.keys.fire = !released;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Restart on game over or victory
|
|
365
|
+
if (!released && (this.state.gameOver || this.state.victory)) {
|
|
366
|
+
if (data === "r" || data === "R" || data === " ") {
|
|
367
|
+
const highScore = this.state.highScore;
|
|
368
|
+
const nextLevel = this.state.victory ? this.state.level + 1 : 1;
|
|
369
|
+
this.state = createInitialState(highScore, nextLevel);
|
|
370
|
+
this.keys = { left: false, right: false, fire: false };
|
|
371
|
+
this.onSave(null);
|
|
372
|
+
this.version++;
|
|
373
|
+
this.tui.requestRender();
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
invalidate(): void {
|
|
379
|
+
this.cachedWidth = 0;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
render(width: number): string[] {
|
|
383
|
+
if (width === this.cachedWidth && this.cachedVersion === this.version) {
|
|
384
|
+
return this.cachedLines;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const lines: string[] = [];
|
|
388
|
+
|
|
389
|
+
// Colors
|
|
390
|
+
const dim = (s: string) => `\x1b[2m${s}\x1b[22m`;
|
|
391
|
+
const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
|
|
392
|
+
const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
|
|
393
|
+
const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
|
|
394
|
+
const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
|
|
395
|
+
const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`;
|
|
396
|
+
const white = (s: string) => `\x1b[97m${s}\x1b[0m`;
|
|
397
|
+
const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;
|
|
398
|
+
|
|
399
|
+
const boxWidth = GAME_WIDTH;
|
|
400
|
+
|
|
401
|
+
const boxLine = (content: string) => {
|
|
402
|
+
const contentLen = visibleWidth(content);
|
|
403
|
+
const padding = Math.max(0, boxWidth - contentLen);
|
|
404
|
+
return dim(" │") + content + " ".repeat(padding) + dim("│");
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Top border
|
|
408
|
+
lines.push(this.padLine(dim(` ╭${"─".repeat(boxWidth)}╮`), width));
|
|
409
|
+
|
|
410
|
+
// Header
|
|
411
|
+
const title = `${bold(green("SPACE INVADERS"))}`;
|
|
412
|
+
const scoreText = `Score: ${bold(yellow(String(this.state.score)))}`;
|
|
413
|
+
const highText = `Hi: ${bold(yellow(String(this.state.highScore)))}`;
|
|
414
|
+
const levelText = `Lv: ${bold(cyan(String(this.state.level)))}`;
|
|
415
|
+
const livesText = `${red("♥".repeat(this.state.player.lives))}`;
|
|
416
|
+
const header = `${title} │ ${scoreText} │ ${highText} │ ${levelText} │ ${livesText}`;
|
|
417
|
+
lines.push(this.padLine(boxLine(header), width));
|
|
418
|
+
|
|
419
|
+
// Separator
|
|
420
|
+
lines.push(this.padLine(dim(` ├${"─".repeat(boxWidth)}┤`), width));
|
|
421
|
+
|
|
422
|
+
// Game grid
|
|
423
|
+
for (let y = 0; y < GAME_HEIGHT; y++) {
|
|
424
|
+
let row = "";
|
|
425
|
+
for (let x = 0; x < GAME_WIDTH; x++) {
|
|
426
|
+
let char = " ";
|
|
427
|
+
let colored = false;
|
|
428
|
+
|
|
429
|
+
// Check aliens
|
|
430
|
+
for (const alien of this.state.aliens) {
|
|
431
|
+
if (alien.alive && alien.y === y && Math.abs(alien.x - x) <= 1) {
|
|
432
|
+
const sprites = [
|
|
433
|
+
x === alien.x ? "▼" : "╲╱"[x < alien.x ? 0 : 1],
|
|
434
|
+
x === alien.x ? "◆" : "╱╲"[x < alien.x ? 0 : 1],
|
|
435
|
+
x === alien.x ? "☆" : "◄►"[x < alien.x ? 0 : 1],
|
|
436
|
+
];
|
|
437
|
+
const colors = [green, cyan, magenta];
|
|
438
|
+
char = colors[alien.type](sprites[alien.type]);
|
|
439
|
+
colored = true;
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Check shields
|
|
445
|
+
if (!colored) {
|
|
446
|
+
for (const shield of this.state.shields) {
|
|
447
|
+
const relX = x - shield.x;
|
|
448
|
+
const relY = y - (PLAYER_Y - 5);
|
|
449
|
+
if (relX >= 0 && relX < 4 && relY >= 0 && relY < 3) {
|
|
450
|
+
if (shield.segments[relY][relX]) {
|
|
451
|
+
char = dim("█");
|
|
452
|
+
colored = true;
|
|
453
|
+
}
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check player
|
|
460
|
+
if (!colored && y === PLAYER_Y && Math.abs(x - this.state.player.x) <= 1) {
|
|
461
|
+
if (x === this.state.player.x) {
|
|
462
|
+
char = white("▲");
|
|
463
|
+
} else {
|
|
464
|
+
char = white("═");
|
|
465
|
+
}
|
|
466
|
+
colored = true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Check bullets
|
|
470
|
+
if (!colored) {
|
|
471
|
+
for (const bullet of this.state.bullets) {
|
|
472
|
+
if (bullet.x === x && bullet.y === y) {
|
|
473
|
+
char = bullet.direction === -1 ? yellow("│") : red("│");
|
|
474
|
+
colored = true;
|
|
475
|
+
break;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
row += colored ? char : " ";
|
|
481
|
+
}
|
|
482
|
+
lines.push(this.padLine(dim(" │") + row + dim("│"), width));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Separator
|
|
486
|
+
lines.push(this.padLine(dim(` ├${"─".repeat(boxWidth)}┤`), width));
|
|
487
|
+
|
|
488
|
+
// Footer
|
|
489
|
+
let footer: string;
|
|
490
|
+
if (this.paused) {
|
|
491
|
+
footer = `${yellow(bold("PAUSED"))} Press any key to continue, ${bold("Q")} to quit`;
|
|
492
|
+
} else if (this.state.gameOver) {
|
|
493
|
+
footer = `${red(bold("GAME OVER!"))} Press ${bold("R")} to restart, ${bold("Q")} to quit`;
|
|
494
|
+
} else if (this.state.victory) {
|
|
495
|
+
footer = `${green(bold("VICTORY!"))} Press ${bold("R")} for level ${this.state.level + 1}, ${bold("Q")} to quit`;
|
|
496
|
+
} else {
|
|
497
|
+
footer = `←→ or AD to move, ${bold("SPACE")}/F to fire, ${bold("ESC")} pause, ${bold("Q")} quit`;
|
|
498
|
+
}
|
|
499
|
+
lines.push(this.padLine(boxLine(footer), width));
|
|
500
|
+
|
|
501
|
+
// Bottom border
|
|
502
|
+
lines.push(this.padLine(dim(` ╰${"─".repeat(boxWidth)}╯`), width));
|
|
503
|
+
|
|
504
|
+
this.cachedLines = lines;
|
|
505
|
+
this.cachedWidth = width;
|
|
506
|
+
this.cachedVersion = this.version;
|
|
507
|
+
|
|
508
|
+
return lines;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
private padLine(line: string, width: number): string {
|
|
512
|
+
const visibleLen = line.replace(/\x1b\[[0-9;]*m/g, "").length;
|
|
513
|
+
const padding = Math.max(0, width - visibleLen);
|
|
514
|
+
return line + " ".repeat(padding);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
dispose(): void {
|
|
518
|
+
if (this.interval) {
|
|
519
|
+
clearInterval(this.interval);
|
|
520
|
+
this.interval = null;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const INVADERS_SAVE_TYPE = "space-invaders-save";
|
|
526
|
+
|
|
527
|
+
export default function (pi: ExtensionAPI) {
|
|
528
|
+
pi.registerCommand("invaders", {
|
|
529
|
+
description: "Play Space Invaders!",
|
|
530
|
+
|
|
531
|
+
handler: async (_args, ctx) => {
|
|
532
|
+
if (!ctx.hasUI) {
|
|
533
|
+
ctx.ui.notify("Space Invaders requires interactive mode", "error");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Load saved state from session
|
|
538
|
+
const entries = ctx.sessionManager.getEntries();
|
|
539
|
+
let savedState: GameState | undefined;
|
|
540
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
541
|
+
const entry = entries[i];
|
|
542
|
+
if (entry.type === "custom" && entry.customType === INVADERS_SAVE_TYPE) {
|
|
543
|
+
savedState = entry.data as GameState;
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
await ctx.ui.custom((tui, _theme, _kb, done) => {
|
|
549
|
+
return new SpaceInvadersComponent(
|
|
550
|
+
tui,
|
|
551
|
+
() => done(undefined),
|
|
552
|
+
(state) => {
|
|
553
|
+
pi.appendEntry(INVADERS_SAVE_TYPE, state);
|
|
554
|
+
},
|
|
555
|
+
savedState,
|
|
556
|
+
);
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
});
|
|
560
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-with-deps",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-with-deps",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.14.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"ms": "^2.1.3"
|
|
12
12
|
},
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { getModel } from "@mariozechner/pi-ai";
|
|
8
|
-
import {
|
|
8
|
+
import { AuthStorage, createAgentSession, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
9
9
|
|
|
10
10
|
// Set up auth storage and model registry
|
|
11
|
-
const authStorage =
|
|
12
|
-
const modelRegistry =
|
|
11
|
+
const authStorage = new AuthStorage();
|
|
12
|
+
const modelRegistry = new ModelRegistry(authStorage);
|
|
13
13
|
|
|
14
14
|
// Option 1: Find a specific built-in model by provider/id
|
|
15
15
|
const opus = getModel("anthropic", "claude-opus-4-5");
|
|
@@ -4,12 +4,19 @@
|
|
|
4
4
|
* Shows how to replace or modify the default system prompt.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { createAgentSession, DefaultResourceLoader, SessionManager } from "@mariozechner/pi-coding-agent";
|
|
8
8
|
|
|
9
9
|
// Option 1: Replace prompt entirely
|
|
10
|
-
const
|
|
11
|
-
|
|
10
|
+
const loader1 = new DefaultResourceLoader({
|
|
11
|
+
systemPromptOverride: () => `You are a helpful assistant that speaks like a pirate.
|
|
12
12
|
Always end responses with "Arrr!"`,
|
|
13
|
+
// Needed to avoid DefaultResourceLoader appending APPEND_SYSTEM.md from ~/.pi/agent or <cwd>/.pi.
|
|
14
|
+
appendSystemPromptOverride: () => [],
|
|
15
|
+
});
|
|
16
|
+
await loader1.reload();
|
|
17
|
+
|
|
18
|
+
const { session: session1 } = await createAgentSession({
|
|
19
|
+
resourceLoader: loader1,
|
|
13
20
|
sessionManager: SessionManager.inMemory(),
|
|
14
21
|
});
|
|
15
22
|
|
|
@@ -23,13 +30,17 @@ console.log("=== Replace prompt ===");
|
|
|
23
30
|
await session1.prompt("What is 2 + 2?");
|
|
24
31
|
console.log("\n");
|
|
25
32
|
|
|
26
|
-
// Option 2:
|
|
27
|
-
const
|
|
28
|
-
|
|
33
|
+
// Option 2: Append instructions to the default prompt
|
|
34
|
+
const loader2 = new DefaultResourceLoader({
|
|
35
|
+
appendSystemPromptOverride: (base) => [
|
|
36
|
+
...base,
|
|
37
|
+
"## Additional Instructions\n- Always be concise\n- Use bullet points when listing things",
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
await loader2.reload();
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- Use bullet points when listing things`,
|
|
42
|
+
const { session: session2 } = await createAgentSession({
|
|
43
|
+
resourceLoader: loader2,
|
|
33
44
|
sessionManager: SessionManager.inMemory(),
|
|
34
45
|
});
|
|
35
46
|
|